/** * dashboard-client/src/components/SavingsByModelTable.tsx — Savings by Model. * * 14 columns aggregated client-side from /api/index repos by model. * Replicates html.ts renderByModel logic: group by modelName, accumulate * tokens/cost/sessions, collapse numeric ranges when repos disagree. */ import type React from "react"; import { useMemo } from "react"; import type { IndexesIndexRow } from "@contracts"; import { computeCacheSavings } from "@pricing"; export interface SavingsByModelTableProps { repos: IndexesIndexRow[]; } interface ModelGroup { model: string; provider: string; checkpoints: number; tokensSaved: number; tokensIn: number; tokensOut: number; sessions: number; usd: number; inRates: number[]; outRates: number[]; ctxWindows: number[]; maxTokens: number[]; reasoning: boolean | null; lastAt: number; cacheRead: number; cacheWrite: number; } /** Collapse numeric samples: "—" | single | "lo–hi" (matches html.ts). */ function collapseNum(samples: number[]): string { if (!samples.length) return "\u2014"; const lo = Math.min(...samples); const hi = Math.max(...samples); return lo === hi ? lo.toLocaleString() : `${lo.toLocaleString()}\u2013${hi.toLocaleString()}`; } /** Collapse rate samples: "—" | "$rate" | "$lo–$hi" (matches html.ts). */ function collapseRate(samples: number[]): string { if (!samples.length) return "\u2014"; const lo = Math.min(...samples); const hi = Math.max(...samples); const fmt = (v: number): string => `$${v.toFixed(6)}`; return lo === hi ? fmt(lo) : `${fmt(lo)}\u2013${fmt(hi)}`; } /** Group repos by model, accumulate totals, sort by tokensSaved desc. */ function aggregate(repos: IndexesIndexRow[]): ModelGroup[] { const map: Record = {}; for (const r of repos) { const key = (r.modelName && r.modelName.trim()) || "(unknown)"; if (!map[key]) { map[key] = { model: key, provider: r.providerName ?? r.provider ?? "\u2014", checkpoints: 0, tokensSaved: 0, tokensIn: 0, tokensOut: 0, sessions: 0, usd: 0, inRates: [], outRates: [], ctxWindows: [], maxTokens: [], reasoning: null, lastAt: 0, cacheRead: 0, cacheWrite: 0, }; } const g = map[key]; g.checkpoints += r.checkpointCount || 0; g.tokensSaved += r.tokensSaved || 0; g.tokensIn += r.tokensDropped || 0; g.tokensOut += r.tokensKept || 0; g.sessions += r.sessions || 0; if (r.inputRate) { g.usd += (r.tokensSaved || 0) * r.inputRate; g.inRates.push(r.inputRate); } if (r.outputRate) g.outRates.push(r.outputRate); if (r.contextWindow) g.ctxWindows.push(r.contextWindow); if (r.maxTokens) g.maxTokens.push(r.maxTokens); if (r.reasoning != null) g.reasoning = r.reasoning; if (r.providerCacheRead) g.cacheRead += r.providerCacheRead; if (r.providerCacheWrite) g.cacheWrite += r.providerCacheWrite; if (r.lastCompactedAt && r.lastCompactedAt > g.lastAt) g.lastAt = r.lastCompactedAt; } return Object.values(map).sort((a, b) => b.tokensSaved - a.tokensSaved); } /** Tooltip text copied verbatim from html.ts title attributes. */ const TOOLTIPS = { tokensIn: "Tokens dropped from context by compaction (the input reclaimed)", tokensOut: "Tokens kept as compacted summaries still in context (the output retained)", ctxWindow: "Model context window (max input tokens the model accepts)", maxOut: "Model max output tokens per turn", reas: "Reasoning-capable model", sessions: "Distinct sessions with at least one checkpoint", inRate: "USD per input token", outRate: "USD per output token", } as const; export function SavingsByModelTable({ repos, }: SavingsByModelTableProps): React.ReactElement { const groups = useMemo(() => aggregate(repos), [repos]); return (

How much context & cost mega-compact has reclaimed, grouped by the model you were running. Compression ratio reflects workload/content, not model quality.

{groups.length === 0 && ( )} {groups.map((g) => { const freed = (g.tokensIn || 0) - (g.tokensOut || 0); const usd = g.usd > 0 ? `$${g.usd.toFixed(4)}` : "\u2014"; const when = g.lastAt ? new Date(g.lastAt).toLocaleString() : "\u2014"; const reas = g.reasoning == null ? "\u2014" : g.reasoning ? "yes" : "no"; return ( ); })}
Model Provider Tokens In Tokens Out Freed Ctx Window Max Out Reas. Sessions Checkpoints In $/tok Out $/tok $ Saved Cache Hit % Cache $ Saved Last Used
No repositories registered yet.
{g.model} {g.provider} {g.tokensIn.toLocaleString()} {g.tokensOut.toLocaleString()} {freed.toLocaleString()} {collapseNum(g.ctxWindows)} {collapseNum(g.maxTokens)} {reas} {g.sessions.toLocaleString()} {g.checkpoints.toLocaleString()} {collapseRate(g.inRates)} {collapseRate(g.outRates)} {usd} {g.cacheRead > 0 ? `${( (g.cacheRead * 100) / (g.cacheRead + g.cacheWrite + g.tokensIn || 1) ).toFixed(1)}%` : "—"} {g.cacheRead > 0 ? `$${computeCacheSavings(g.cacheRead, g.cacheWrite, g.inRates[0] || 0).netSaved.toFixed(4)}` : "—"} {when}

Tokens In = Σ original region tokens dropped by compaction. Tokens Out = Σ compacted summary tokens still retained in context. Freed = Tokens In − Tokens Out (net context reclaimed). Ctx Window / Max Out / Reas. come from the latest captured model snapshot for each repo.

); }