From f8bf715abc8a89ec0c6370b40403c509b1ce2870 Mon Sep 17 00:00:00 2001 From: Adam Malczewski Date: Wed, 10 Jun 2026 10:06:27 +0900 Subject: feat(metrics): per-turn + per-step token/timing metrics bubbles Consume wire@0.4.0 / transport-contract@0.4.0 metrics: usage.stepId, step-complete (ttft/decode/genTotal), done.durationMs/usage, and the durable GET /conversations/:id/metrics endpoint. - core/metrics: pure live-fold + durable-merge reducer; decode-rate TPS; head-aligned, stable placement; progressive per-step rows (each shown as its step ends) with the turn-total row gated on the done event. - features/chat: store folds metric events + hydrates durable TurnMetrics; ChatView renders inline step bubbles + a turn-total bubble. - app: MetricsSync HTTP effect (tolerates 404) injected into chat stores. - scripts/live-probe: drives the metrics path; live-verified 17/17 vs bin/up. - docs: regenerate .dispatch wire/transport mirrors to 0.4.0; glossary terms (turn/step metrics, TTFT, decode time, TPS, metrics bubble); trim handoff. --- src/features/chat/ui/ChatView.svelte | 54 ++++++++++++++++++++++++++++++------ 1 file changed, 46 insertions(+), 8 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 3a078fb..ba6e961 100644 --- a/src/features/chat/ui/ChatView.svelte +++ b/src/features/chat/ui/ChatView.svelte @@ -1,17 +1,33 @@ @@ -102,9 +118,31 @@ {/snippet}
- {#each rows as { group, key } (key)} - {#if group.kind === "single"} - {@render chunkRow(group.chunk)} + {#each keyedRows as { row, key } (key)} + {#if row.kind === "step-metrics"} + {@const sv = viewStepMetrics(row.step, row.index)} +
+
+
+ {sv.label} · {sv.tokensLabel} + {#if sv.tps} · {sv.tps}{/if} + {#if sv.genTotal} · {sv.genTotal}{/if} +
+
+
+ {:else if row.kind === "turn-metrics"} + {@const turnView = viewTurnMetrics(row.turn)} +
+
+
+ turn · {turnView.tokensLabel} ({turnView.breakdown}) + {#if turnView.tps} · {turnView.tps}{/if} + {#if turnView.duration} · {turnView.duration}{/if} +
+
+
+ {:else if row.group.kind === "single"} + {@render chunkRow(row.group.chunk)} {:else}