Merge pull request #835 from tombii/fix/cache-page-ui-polish

fix(ui): improve cache page header sizing and context
This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-03-30 20:47:40 -03:00
committed by GitHub
3 changed files with 171 additions and 129 deletions
+7 -1
View File
@@ -5,6 +5,7 @@ import { Card, Button, EmptyState } from "@/shared/components";
import { useNotificationStore } from "@/store/notificationStore";
import { useTranslations } from "next-intl";
import CacheEntriesTab from "./components/CacheEntriesTab";
import CacheStatsCard from "../settings/components/CacheStatsCard";
// ─── Types ───────────────────────────────────────────────────────────────────
@@ -371,7 +372,9 @@ export default function CachePage() {
<div className="text-lg font-semibold tabular-nums text-green-500">
{promptCacheHitRate.toFixed(1)}%
</div>
<div className="text-xs text-text-muted mt-0.5">{t("cacheHitRate")}</div>
<div className="text-xs text-text-muted mt-0.5">
{t("cacheHitRate")} ({pc.requestsWithCacheControl}/{pc.totalRequests})
</div>
</div>
<div className="p-3 rounded-lg bg-surface/50">
<div className="text-lg font-semibold tabular-nums text-blue-400">
@@ -432,6 +435,9 @@ export default function CachePage() {
</Card>
)}
{/* Prompt Cache Metrics (cumulative with reset) */}
<CacheStatsCard />
{/* Cache Trend (24h) */}
{trend.length > 0 && (
<Card>
@@ -1,6 +1,6 @@
"use client";
import { useState, useEffect } from "react";
import { useState, useEffect, useCallback } from "react";
import { Card } from "@/shared/components";
import { useTranslations } from "next-intl";
@@ -33,19 +33,26 @@ interface CacheMetrics {
lastUpdated: string;
}
const REFRESH_INTERVAL_MS = 10_000;
const REFRESH_INTERVAL_SECONDS = REFRESH_INTERVAL_MS / 1000;
export default function CacheStatsCard() {
const [metrics, setMetrics] = useState<CacheMetrics | null>(null);
const [resetting, setResetting] = useState(false);
const t = useTranslations("settings");
const t = useTranslations("cache");
const fetchMetrics = () => {
const fetchMetrics = useCallback(() => {
fetch("/api/settings/cache-metrics")
.then((r) => r.json())
.then(setMetrics)
.catch(() => {});
};
}, []);
useEffect(fetchMetrics, []);
useEffect(() => {
void fetchMetrics();
const id = setInterval(() => void fetchMetrics(), REFRESH_INTERVAL_MS);
return () => clearInterval(id);
}, [fetchMetrics]);
const handleReset = async () => {
setResetting(true);
@@ -63,132 +70,148 @@ export default function CacheStatsCard() {
: 0;
return (
<Card className="p-6">
<div className="flex items-center justify-between mb-4">
<h3 className="text-lg font-semibold text-text-main flex items-center gap-2">
<span className="material-symbols-outlined text-[20px]">insights</span>
Prompt Cache Metrics
</h3>
<button
onClick={handleReset}
disabled={resetting}
className="px-3 py-1.5 text-xs rounded-lg bg-red-500/10 text-red-400 hover:bg-red-500/20 transition-colors disabled:opacity-50"
>
{resetting ? "Resetting..." : "Reset Metrics"}
</button>
</div>
{metrics ? (
<div className="space-y-4">
{/* Overview Stats */}
<div className="grid grid-cols-2 gap-4 text-sm">
<div>
<p className="text-text-muted">Total Requests</p>
<p className="font-mono text-lg text-text-main">{metrics.totalRequests}</p>
</div>
<div>
<p className="text-text-muted">With Cache Control</p>
<p className="font-mono text-lg text-text-main">{metrics.requestsWithCacheControl}</p>
</div>
<Card>
<div className="p-5 flex flex-col gap-4">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<span
className="material-symbols-outlined text-base text-text-muted"
aria-hidden="true"
>
insights
</span>
<h2 className="font-medium text-sm">{t("cacheMetrics")}</h2>
</div>
{/* Token Stats */}
<div className="grid grid-cols-3 gap-4 text-sm">
<div>
<p className="text-text-muted">Input Tokens</p>
<p className="font-mono text-lg text-text-main">
{metrics.totalInputTokens.toLocaleString()}
</p>
</div>
<div>
<p className="text-text-muted">Cached Tokens (Read)</p>
<p className="font-mono text-lg text-green-400">
{metrics.totalCachedTokens.toLocaleString()}
</p>
</div>
<div>
<p className="text-text-muted">Cache Creation (Write)</p>
<p className="font-mono text-lg text-blue-400">
{metrics.totalCacheCreationTokens.toLocaleString()}
</p>
</div>
<div className="flex items-center gap-3">
<span className="text-xs text-text-muted">
{t("autoRefresh", { seconds: REFRESH_INTERVAL_SECONDS })}
</span>
<button
onClick={handleReset}
disabled={resetting}
className="px-3 py-1.5 text-xs rounded-lg bg-red-500/10 text-red-400 hover:bg-red-500/20 transition-colors disabled:opacity-50"
>
{resetting ? t("resetting") : t("resetMetrics")}
</button>
</div>
{/* Cache Ratio */}
<div className="rounded-lg bg-surface/50 border border-border/30 p-3">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-text-main">Cache Reuse Ratio</p>
<p className="text-xs text-text-muted">Cached tokens / Total input tokens</p>
</div>
<p className="font-mono text-xl text-green-400">{cacheHitRate.toFixed(1)}%</p>
</div>
{/* Progress bar */}
<div className="mt-2 h-2 rounded-full bg-border/30 overflow-hidden">
<div
className="h-full bg-green-500 transition-all duration-300"
style={{ width: `${Math.min(cacheHitRate, 100)}%` }}
/>
</div>
</div>
{/* Savings */}
<div className="grid grid-cols-2 gap-4 text-sm">
<div>
<p className="text-text-muted">Tokens Saved</p>
<p className="font-mono text-lg text-green-400">
{metrics.tokensSaved.toLocaleString()}
</p>
</div>
<div>
<p className="text-text-muted">Est. Cost Saved</p>
<p className="font-mono text-lg text-green-400">
${metrics.estimatedCostSaved.toFixed(4)}
</p>
</div>
</div>
{/* By Provider */}
{Object.keys(metrics.byProvider).length > 0 && (
<div className="pt-3 border-t border-border/30">
<p className="text-xs font-medium text-text-muted mb-2">By Provider</p>
<div className="space-y-2">
{Object.entries(metrics.byProvider).map(([provider, stats]) => {
const providerCacheRate =
stats.inputTokens > 0 ? (stats.cachedTokens / stats.inputTokens) * 100 : 0;
return (
<div
key={provider}
className="flex items-center justify-between px-3 py-2 rounded bg-surface/30 text-xs"
>
<div className="flex items-center gap-3">
<span className="text-text-main capitalize w-24">{provider}</span>
<span className="text-text-muted">{stats.requests} reqs</span>
</div>
<div className="flex items-center gap-4 font-mono">
<span className="text-text-muted" title="Input tokens">
In: {stats.inputTokens.toLocaleString()}
</span>
<span className="text-green-400" title="Cached tokens (reads)">
Cached: {stats.cachedTokens.toLocaleString()}
</span>
<span className="text-blue-400" title="Cache creation tokens (writes)">
Write: {stats.cacheCreationTokens.toLocaleString()}
</span>
<span className="text-green-400 w-12 text-right">
{providerCacheRate.toFixed(0)}%
</span>
</div>
</div>
);
})}
</div>
</div>
)}
</div>
) : (
<p className="text-sm text-text-muted">Loading cache metrics...</p>
)}
{metrics ? (
<div className="flex flex-col gap-4">
{/* Overview Stats */}
<div className="grid grid-cols-2 gap-4 text-sm">
<div>
<p className="text-text-muted">{t("totalRequests")}</p>
<p className="font-mono text-lg text-text-main">{metrics.totalRequests}</p>
</div>
<div>
<p className="text-text-muted">{t("withCacheControl")}</p>
<p className="font-mono text-lg text-text-main">
{metrics.requestsWithCacheControl}
</p>
</div>
</div>
{/* Token Stats */}
<div className="grid grid-cols-3 gap-4 text-sm">
<div>
<p className="text-text-muted">{t("inputTokens")}</p>
<p className="font-mono text-lg text-text-main">
{metrics.totalInputTokens.toLocaleString()}
</p>
</div>
<div>
<p className="text-text-muted">{t("cachedTokensRead")}</p>
<p className="font-mono text-lg text-green-400">
{metrics.totalCachedTokens.toLocaleString()}
</p>
</div>
<div>
<p className="text-text-muted">{t("cacheCreationWrite")}</p>
<p className="font-mono text-lg text-blue-400">
{metrics.totalCacheCreationTokens.toLocaleString()}
</p>
</div>
</div>
{/* Cache Ratio */}
<div className="rounded-lg bg-surface/50 border border-border/30 p-3">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-text-main">{t("cacheReuseRatio")}</p>
<p className="text-xs text-text-muted">{t("cacheReuseRatioDesc")}</p>
</div>
<p className="font-mono text-xl text-green-400">{cacheHitRate.toFixed(1)}%</p>
</div>
{/* Progress bar */}
<div className="mt-2 h-2 rounded-full bg-border/30 overflow-hidden">
<div
className="h-full bg-green-500 transition-all duration-300"
style={{ width: `${Math.min(cacheHitRate, 100)}%` }}
/>
</div>
</div>
{/* Savings */}
<div className="grid grid-cols-2 gap-4 text-sm">
<div>
<p className="text-text-muted">{t("tokensSaved")}</p>
<p className="font-mono text-lg text-green-400">
{metrics.tokensSaved.toLocaleString()}
</p>
</div>
<div>
<p className="text-text-muted">{t("estCostSaved")}</p>
<p className="font-mono text-lg text-green-400">
${metrics.estimatedCostSaved.toFixed(4)}
</p>
</div>
</div>
{/* By Provider */}
{Object.keys(metrics.byProvider).length > 0 && (
<div className="pt-3 border-t border-border/30">
<p className="text-xs font-medium text-text-muted mb-2">{t("byProvider")}</p>
<div className="space-y-2">
{Object.entries(metrics.byProvider).map(([provider, stats]) => {
const providerCacheRate =
stats.inputTokens > 0 ? (stats.cachedTokens / stats.inputTokens) * 100 : 0;
return (
<div
key={provider}
className="flex items-center justify-between px-3 py-2 rounded bg-surface/30 text-xs"
>
<div className="flex items-center gap-3">
<span className="text-text-main capitalize w-24">{provider}</span>
<span className="text-text-muted">
{stats.requests} {t("requestsShort")}
</span>
</div>
<div className="flex items-center gap-4 font-mono">
<span className="text-text-muted" title={t("inputTokens")}>
{t("inputShort")}: {stats.inputTokens.toLocaleString()}
</span>
<span className="text-green-400" title={t("cachedTokensRead")}>
{t("cachedShort")}: {stats.cachedTokens.toLocaleString()}
</span>
<span className="text-blue-400" title={t("cacheCreationWrite")}>
{t("writeShort")}: {stats.cacheCreationTokens.toLocaleString()}
</span>
<span className="text-green-400 w-12 text-right">
{providerCacheRate.toFixed(0)}%
</span>
</div>
</div>
);
})}
</div>
</div>
)}
</div>
) : (
<p className="text-sm text-text-muted">{t("loading")}</p>
)}
</div>
</Card>
);
}
+13
View File
@@ -2937,6 +2937,19 @@
"cacheHitRate": "Cache Hit Rate",
"cachedTokens": "Cached Tokens",
"cacheCreationTokens": "Cache Creation Tokens",
"cacheMetrics": "Prompt Cache Metrics",
"withCacheControl": "With Cache Control",
"cachedTokensRead": "Cached Tokens (Read)",
"cacheCreationWrite": "Cache Creation (Write)",
"cacheReuseRatio": "Cache Reuse Ratio",
"cacheReuseRatioDesc": "Cached tokens / Total input tokens",
"estCostSaved": "Est. Cost Saved",
"requestsShort": "reqs",
"inputShort": "In",
"cachedShort": "Cached",
"writeShort": "Write",
"resetting": "Resetting...",
"resetMetrics": "Reset Metrics",
"byProvider": "Breakdown by Provider",
"provider": "Provider",
"requests": "Requests",