diff --git a/src/app/(dashboard)/dashboard/cache/page.tsx b/src/app/(dashboard)/dashboard/cache/page.tsx index 24064e84..0f514543 100644 --- a/src/app/(dashboard)/dashboard/cache/page.tsx +++ b/src/app/(dashboard)/dashboard/cache/page.tsx @@ -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() {
{promptCacheHitRate.toFixed(1)}%
-
{t("cacheHitRate")}
+
+ {t("cacheHitRate")} ({pc.requestsWithCacheControl}/{pc.totalRequests}) +
@@ -432,6 +435,9 @@ export default function CachePage() { )} + {/* Prompt Cache Metrics (cumulative with reset) */} + + {/* Cache Trend (24h) */} {trend.length > 0 && ( diff --git a/src/app/(dashboard)/dashboard/settings/components/CacheStatsCard.tsx b/src/app/(dashboard)/dashboard/settings/components/CacheStatsCard.tsx index d2699076..4798fbba 100644 --- a/src/app/(dashboard)/dashboard/settings/components/CacheStatsCard.tsx +++ b/src/app/(dashboard)/dashboard/settings/components/CacheStatsCard.tsx @@ -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(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 ( - -
-

- insights - Prompt Cache Metrics -

- -
- - {metrics ? ( -
- {/* Overview Stats */} -
-
-

Total Requests

-

{metrics.totalRequests}

-
-
-

With Cache Control

-

{metrics.requestsWithCacheControl}

-
+ +
+
+
+ +

{t("cacheMetrics")}

- - {/* Token Stats */} -
-
-

Input Tokens

-

- {metrics.totalInputTokens.toLocaleString()} -

-
-
-

Cached Tokens (Read)

-

- {metrics.totalCachedTokens.toLocaleString()} -

-
-
-

Cache Creation (Write)

-

- {metrics.totalCacheCreationTokens.toLocaleString()} -

-
+
+ + {t("autoRefresh", { seconds: REFRESH_INTERVAL_SECONDS })} + +
- - {/* Cache Ratio */} -
-
-
-

Cache Reuse Ratio

-

Cached tokens / Total input tokens

-
-

{cacheHitRate.toFixed(1)}%

-
- {/* Progress bar */} -
-
-
-
- - {/* Savings */} -
-
-

Tokens Saved

-

- {metrics.tokensSaved.toLocaleString()} -

-
-
-

Est. Cost Saved

-

- ${metrics.estimatedCostSaved.toFixed(4)} -

-
-
- - {/* By Provider */} - {Object.keys(metrics.byProvider).length > 0 && ( -
-

By Provider

-
- {Object.entries(metrics.byProvider).map(([provider, stats]) => { - const providerCacheRate = - stats.inputTokens > 0 ? (stats.cachedTokens / stats.inputTokens) * 100 : 0; - return ( -
-
- {provider} - {stats.requests} reqs -
-
- - In: {stats.inputTokens.toLocaleString()} - - - Cached: {stats.cachedTokens.toLocaleString()} - - - Write: {stats.cacheCreationTokens.toLocaleString()} - - - {providerCacheRate.toFixed(0)}% - -
-
- ); - })} -
-
- )}
- ) : ( -

Loading cache metrics...

- )} + + {metrics ? ( +
+ {/* Overview Stats */} +
+
+

{t("totalRequests")}

+

{metrics.totalRequests}

+
+
+

{t("withCacheControl")}

+

+ {metrics.requestsWithCacheControl} +

+
+
+ + {/* Token Stats */} +
+
+

{t("inputTokens")}

+

+ {metrics.totalInputTokens.toLocaleString()} +

+
+
+

{t("cachedTokensRead")}

+

+ {metrics.totalCachedTokens.toLocaleString()} +

+
+
+

{t("cacheCreationWrite")}

+

+ {metrics.totalCacheCreationTokens.toLocaleString()} +

+
+
+ + {/* Cache Ratio */} +
+
+
+

{t("cacheReuseRatio")}

+

{t("cacheReuseRatioDesc")}

+
+

{cacheHitRate.toFixed(1)}%

+
+ {/* Progress bar */} +
+
+
+
+ + {/* Savings */} +
+
+

{t("tokensSaved")}

+

+ {metrics.tokensSaved.toLocaleString()} +

+
+
+

{t("estCostSaved")}

+

+ ${metrics.estimatedCostSaved.toFixed(4)} +

+
+
+ + {/* By Provider */} + {Object.keys(metrics.byProvider).length > 0 && ( +
+

{t("byProvider")}

+
+ {Object.entries(metrics.byProvider).map(([provider, stats]) => { + const providerCacheRate = + stats.inputTokens > 0 ? (stats.cachedTokens / stats.inputTokens) * 100 : 0; + return ( +
+
+ {provider} + + {stats.requests} {t("requestsShort")} + +
+
+ + {t("inputShort")}: {stats.inputTokens.toLocaleString()} + + + {t("cachedShort")}: {stats.cachedTokens.toLocaleString()} + + + {t("writeShort")}: {stats.cacheCreationTokens.toLocaleString()} + + + {providerCacheRate.toFixed(0)}% + +
+
+ ); + })} +
+
+ )} +
+ ) : ( +

{t("loading")}

+ )} +
); } diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index 43463d7c..0a1ce25a 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -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",