From 1b873d3badd773b770a9600a0800433661aaf7f1 Mon Sep 17 00:00:00 2001 From: Randi <55005611+rdself@users.noreply.github.com> Date: Fri, 3 Apr 2026 09:17:30 -0400 Subject: [PATCH] fix settings persistence and cache analytics layout (#952) --- open-sse/handlers/chatCore.ts | 22 +- .../components/DiversityScoreCard.tsx | 189 +++++++++++------- .../(dashboard)/dashboard/analytics/page.tsx | 6 +- src/app/(dashboard)/dashboard/cache/page.tsx | 150 ++++++++++++-- .../settings/components/MemorySkillsTab.tsx | 15 +- .../(dashboard)/dashboard/settings/page.tsx | 3 - src/app/api/settings/__tests__/memory.test.ts | 104 ++++++++++ src/app/api/settings/memory/route.ts | 61 ++++++ src/lib/memory/retrieval.ts | 17 +- src/lib/memory/schemas.ts | 2 +- src/lib/memory/settings.ts | 97 +++++++++ tests/integration/integration-wiring.test.mjs | 6 +- tests/unit/memory-settings.test.mjs | 68 +++++++ 13 files changed, 632 insertions(+), 108 deletions(-) create mode 100644 src/app/api/settings/__tests__/memory.test.ts create mode 100644 src/app/api/settings/memory/route.ts create mode 100644 src/lib/memory/settings.ts create mode 100644 tests/unit/memory-settings.test.mjs diff --git a/open-sse/handlers/chatCore.ts b/open-sse/handlers/chatCore.ts index bd9ec422..cfb903bc 100644 --- a/open-sse/handlers/chatCore.ts +++ b/open-sse/handlers/chatCore.ts @@ -99,6 +99,11 @@ import { generateRequestId } from "@/shared/utils/requestId"; import { normalizePayloadForLog } from "@/lib/logPayloads"; import { injectMemory, shouldInjectMemory } from "@/lib/memory/injection"; import { retrieveMemories } from "@/lib/memory/retrieval"; +import { + DEFAULT_MEMORY_SETTINGS, + getMemorySettings, + toMemoryRetrievalConfig, +} from "@/lib/memory/settings"; import { buildClaudeCodeCompatibleRequest, isClaudeCodeCompatibleProvider, @@ -714,9 +719,22 @@ export async function handleChatCore({ }); } - if (apiKeyInfo?.id && shouldInjectMemory(body as Parameters[0])) { + const memorySettings = apiKeyInfo?.id + ? await getMemorySettings().catch(() => DEFAULT_MEMORY_SETTINGS) + : null; + + if ( + apiKeyInfo?.id && + memorySettings && + shouldInjectMemory(body as Parameters[0], { + enabled: memorySettings.enabled && memorySettings.maxTokens > 0, + }) + ) { try { - const memories = await retrieveMemories(apiKeyInfo.id); + const memories = await retrieveMemories( + apiKeyInfo.id, + toMemoryRetrievalConfig(memorySettings) + ); if (memories.length > 0) { const injected = injectMemory( body as Parameters[0], diff --git a/src/app/(dashboard)/dashboard/analytics/components/DiversityScoreCard.tsx b/src/app/(dashboard)/dashboard/analytics/components/DiversityScoreCard.tsx index 92592e9d..ffddf3fa 100644 --- a/src/app/(dashboard)/dashboard/analytics/components/DiversityScoreCard.tsx +++ b/src/app/(dashboard)/dashboard/analytics/components/DiversityScoreCard.tsx @@ -2,29 +2,55 @@ import { useEffect, useState } from "react"; import { Card } from "@/shared/components"; -import { useTranslations } from "next-intl"; + +interface DiversityProviderStat { + share: number; +} + +interface DiversityReport { + score: number; + providers: Record; + windowSize: number; + ttlMs: number; +} export default function DiversityScoreCard() { - const [data, setData] = useState(null); + const [data, setData] = useState(null); const [loading, setLoading] = useState(true); - const t = useTranslations("analytics"); useEffect(() => { - fetch("/api/analytics/diversity") - .then((res) => res.json()) - .then((json) => { - setData(json); - setLoading(false); - }) - .catch((err) => { - console.error(err); - setLoading(false); - }); + let cancelled = false; + + async function loadDiversity() { + try { + const res = await fetch("/api/analytics/diversity"); + if (!res.ok) { + throw new Error(`Failed to fetch diversity analytics: ${res.status}`); + } + + const json = (await res.json()) as DiversityReport; + if (!cancelled) { + setData(json); + } + } catch (error) { + console.error(error); + } finally { + if (!cancelled) { + setLoading(false); + } + } + } + + void loadDiversity(); + + return () => { + cancelled = true; + }; }, []); if (loading || !data) { return ( - +
); @@ -47,40 +73,43 @@ export default function DiversityScoreCard() { } return ( - -
- pie_chart -

- Provider Diversity Score -

+ +
+
+
+ pie_chart +

Provider Diversity

+
+

+ Provider concentration snapshot for the recent traffic window. +

+
- Shannon Entropy + Shannon entropy
-
-
- - {scorePercentage}% - - {riskLabel} -
- - {/* Simple CSS Donut */} -
- +
+
+ +
+ + {scorePercentage}% + + score +
+
+ +
+
+
{riskLabel}
+
+ Higher values indicate traffic is spread across multiple providers instead of + clustering on one vendor. +
+
+ + {Object.keys(data.providers || {}).length === 0 ? ( +
+ No recent usage data available. +
+ ) : ( +
+ {Object.entries(data.providers) + .sort(([, a], [, b]) => b.share - a.share) + .slice(0, 4) + .map(([provider, stat]) => ( +
+
+ {provider} + + {Math.round(stat.share * 100)}% + +
+
+
+
+
+ ))} +
+ )}
-
-

- Provider Share -

- - {Object.keys(data.providers || {}).length === 0 ? ( -
- No recent usage data available. -
- ) : ( -
- {Object.entries(data.providers) - .sort(([, a]: any, [, b]: any) => b.share - a.share) - .slice(0, 4) // Top 4 providers - .map(([provider, stat]: [string, any]) => ( -
-
- - {provider} - - - {Math.round(stat.share * 100)}% - -
-
-
-
-
- ))} -
- )} -
- -
- Window: {data.windowSize} reqs - Based on Last {Math.round(data.ttlMs / 60000)} mins +
+
Window: {data.windowSize} reqs
+
+ Based on last {Math.round(data.ttlMs / 60000)} mins +
); diff --git a/src/app/(dashboard)/dashboard/analytics/page.tsx b/src/app/(dashboard)/dashboard/analytics/page.tsx index dbea9045..2f7a2006 100644 --- a/src/app/(dashboard)/dashboard/analytics/page.tsx +++ b/src/app/(dashboard)/dashboard/analytics/page.tsx @@ -45,13 +45,11 @@ export default function AnalyticsPage() { /> {activeTab === "overview" && ( -
-
- -
+
}> +
)} {activeTab === "evals" && } diff --git a/src/app/(dashboard)/dashboard/cache/page.tsx b/src/app/(dashboard)/dashboard/cache/page.tsx index b50798a5..3ed8993f 100644 --- a/src/app/(dashboard)/dashboard/cache/page.tsx +++ b/src/app/(dashboard)/dashboard/cache/page.tsx @@ -5,7 +5,6 @@ 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 ─────────────────────────────────────────────────────────────────── @@ -192,7 +191,18 @@ export default function CachePage() { const promptCacheHitRate = pc && pc.totalRequests > 0 ? (pc.requestsWithCacheControl / pc.totalRequests) * 100 : 0; - const providerEntries = pc ? Object.entries(pc.byProvider) : []; + const promptCacheReuseRatio = + pc && pc.totalInputTokens > 0 ? (pc.totalCachedTokens / pc.totalInputTokens) * 100 : 0; + const providerEntries = pc + ? Object.entries(pc.byProvider).sort( + ([, left], [, right]) => right.cachedTokens - left.cachedTokens + ) + : []; + const strategyEntries = pc + ? Object.entries(pc.byStrategy).sort( + ([, left], [, right]) => right.cachedTokens - left.cachedTokens + ) + : []; const maxTrendRequests = Math.max(1, ...trend.map((p) => p.requests)); @@ -351,30 +361,47 @@ export default function CachePage() { {pc && (
-
-