Spaces:
Runtime error
Runtime error
File size: 4,057 Bytes
cd8bd0a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 | "use client";
import { useTranslations } from "next-intl";
// βββ Types ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
interface MemoryCardsProps {
memoryEntries?: number;
dbEntries?: number;
hits?: number;
misses?: number;
hitRate?: string;
tokensSaved?: number;
loading?: boolean;
error?: string | null;
onRetry?: () => void;
}
// βββ Internal StatCard ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
function StatCard({
icon,
label,
value,
sub,
valueClass = "text-text",
}: {
icon: string;
label: string;
value: string | number;
sub?: string;
valueClass?: string;
}) {
return (
<div className="flex flex-col gap-1 p-4 rounded-xl bg-surface-raised border border-border/40">
<div className="flex items-center gap-1.5 text-text-muted text-xs">
<span className="material-symbols-outlined text-base leading-none" aria-hidden="true">
{icon}
</span>
{label}
</div>
<div className={`text-2xl font-semibold tabular-nums ${valueClass}`}>{value}</div>
{sub && <div className="text-xs text-text-muted">{sub}</div>}
</div>
);
}
// βββ Skeleton card ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
function SkeletonCard() {
return (
<div
data-testid="skeleton"
className="h-24 rounded-xl bg-surface-raised border border-border/40 animate-pulse"
/>
);
}
// βββ MemoryCards ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
export default function MemoryCards({
memoryEntries = 0,
dbEntries = 0,
hits = 0,
misses: _misses = 0,
hitRate,
tokensSaved = 0,
loading = false,
error = null,
onRetry,
}: MemoryCardsProps) {
const t = useTranslations("cache");
if (loading) {
return (
<div
data-testid="memory-cards"
className="grid grid-cols-2 md:grid-cols-4 gap-4 transition-opacity duration-200"
>
<SkeletonCard />
<SkeletonCard />
<SkeletonCard />
<SkeletonCard />
</div>
);
}
if (error) {
return (
<div
data-testid="memory-cards"
className="flex flex-col items-center gap-3 p-6 rounded-xl bg-surface-raised border border-border/40 text-center transition-opacity duration-200"
>
<span className="material-symbols-outlined text-3xl text-red-400" aria-hidden="true">
error_outline
</span>
<p className="text-sm text-text-muted">{error}</p>
{onRetry && (
<button
onClick={onRetry}
className="mt-1 px-4 py-1.5 rounded-lg bg-primary text-primary-foreground text-xs font-medium hover:opacity-90 transition-opacity"
>
Retry
</button>
)}
</div>
);
}
return (
<div
data-testid="memory-cards"
className="grid grid-cols-2 md:grid-cols-4 gap-4 transition-opacity duration-200"
>
<StatCard
icon="memory"
label={t("memoryEntries")}
value={memoryEntries}
sub={t("memoryEntriesSub")}
/>
<StatCard icon="storage" label={t("dbEntries")} value={dbEntries} sub={t("dbEntriesSub")} />
<StatCard
icon="trending_up"
label={t("cacheHits")}
value={hits}
sub={hitRate ?? ""}
valueClass="text-green-500"
/>
<StatCard
icon="token"
label={t("tokensSaved")}
value={tokensSaved}
sub={t("tokensSavedSub")}
valueClass="text-blue-400"
/>
</div>
);
}
|