From 89ca80bac1e143a4ec5ba6e2e1d4998acce2553c Mon Sep 17 00:00:00 2001
From: Adam Malczewski <github@tradam.dev>
Date: Wed, 10 Jun 2026 10:34:37 +0900
Subject: feat(metrics): inline cache hit-rate badges (last turn + chat total)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Derive cache hit rate (cacheReadTokens / inputTokens) from data already
folded in core/metrics — no backend/contract change.

- core/metrics: computeCachePct + viewCacheRate (pct + success/warning/error
  level by 66/33 thresholds + isHit); thread a running cumulativeUsage onto
  each finalized turn-metrics row for the conversation total.
- ChatView: render two labelled, colour-coded percentage badges in the
  turn-total bubble — "Last turn:" (that turn) and "Chat Total:" (cumulative).
- Honour backend caveats: absent cache fields -> 0, divide-by-zero guarded,
  a legitimate 0% renders plainly (not "no data").
---
 src/features/chat/ui/ChatView.svelte | 36 +++++++++++++++++++++++++++++++-----
 1 file changed, 31 insertions(+), 5 deletions(-)

(limited to 'src/features/chat/ui/ChatView.svelte')

diff --git a/src/features/chat/ui/ChatView.svelte b/src/features/chat/ui/ChatView.svelte
index ba6e961..3d1421d 100644
--- a/src/features/chat/ui/ChatView.svelte
+++ b/src/features/chat/ui/ChatView.svelte
@@ -1,6 +1,18 @@
 <script lang="ts">
 	import { groupRenderedChunks, type RenderedChunk } from "../index";
-	import { interleaveTurnMetrics, viewStepMetrics, viewTurnMetrics, type TurnMetricsEntry } from "../../../core/metrics";
+	import {
+		interleaveTurnMetrics,
+		viewCacheRate,
+		viewStepMetrics,
+		viewTurnMetrics,
+		type TurnMetricsEntry,
+	} from "../../../core/metrics";
+
+	const badgeClass = {
+		success: "badge-success",
+		warning: "badge-warning",
+		error: "badge-error",
+	} as const;
 
 	let {
 		chunks,
@@ -132,12 +144,26 @@
 			</div>
 		{:else if row.kind === "turn-metrics"}
 			{@const turnView = viewTurnMetrics(row.turn)}
+			{@const lastCache = viewCacheRate(row.turn.usage)}
+			{@const chatCache = viewCacheRate(row.cumulativeUsage)}
 			<div class="chat chat-start">
 				<div class="chat-bubble w-full max-w-5xl bg-transparent p-0">
-					<div class="text-xs opacity-70">
-						turn · {turnView.tokensLabel} ({turnView.breakdown})
-						{#if turnView.tps} · {turnView.tps}{/if}
-						{#if turnView.duration} · {turnView.duration}{/if}
+					<div class="flex flex-col gap-1 text-xs">
+						<div class="opacity-70">
+							turn · {turnView.tokensLabel} ({turnView.breakdown})
+							{#if turnView.tps} · {turnView.tps}{/if}
+							{#if turnView.duration} · {turnView.duration}{/if}
+						</div>
+						<div class="flex flex-wrap items-center gap-x-3 gap-y-1">
+							<span class="flex items-center gap-1">
+								<span class="opacity-70">Last turn:</span>
+								<span class="badge badge-sm {badgeClass[lastCache.level]}">{lastCache.pct}%</span>
+							</span>
+							<span class="flex items-center gap-1">
+								<span class="opacity-70">Chat Total:</span>
+								<span class="badge badge-sm {badgeClass[chatCache.level]}">{chatCache.pct}%</span>
+							</span>
+						</div>
 					</div>
 				</div>
 			</div>
-- 
cgit v1.2.3

