summaryrefslogtreecommitdiffhomepage
path: root/src/app
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-06-10 10:06:27 +0900
committerAdam Malczewski <[email protected]>2026-06-10 10:06:27 +0900
commitf8bf715abc8a89ec0c6370b40403c509b1ce2870 (patch)
tree915600a766e042a8491ac57423542cde1dda1eb6 /src/app
parentccfd2f4157c1cbbb3d8aeceee94d9e963a82ab03 (diff)
downloaddispatch-web-f8bf715abc8a89ec0c6370b40403c509b1ce2870.tar.gz
dispatch-web-f8bf715abc8a89ec0c6370b40403c509b1ce2870.zip
feat(metrics): per-turn + per-step token/timing metrics bubbles
Consume [email protected] / [email protected] 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.
Diffstat (limited to 'src/app')
-rw-r--r--src/app/App.svelte2
-rw-r--r--src/app/store.svelte.ts14
2 files changed, 14 insertions, 2 deletions
diff --git a/src/app/App.svelte b/src/app/App.svelte
index 61b4cb9..857a1e5 100644
--- a/src/app/App.svelte
+++ b/src/app/App.svelte
@@ -62,7 +62,7 @@
<div class="flex-1 overflow-y-auto">
{#key store.activeConversationId}
- <ChatView chunks={store.activeChat.chunks} />
+ <ChatView chunks={store.activeChat.chunks} turnMetrics={store.activeChat.turnMetrics} />
{/key}
</div>
diff --git a/src/app/store.svelte.ts b/src/app/store.svelte.ts
index 760c390..fe3c55c 100644
--- a/src/app/store.svelte.ts
+++ b/src/app/store.svelte.ts
@@ -2,6 +2,7 @@ import type {
ChatDeltaMessage,
ChatErrorMessage,
ConversationHistoryResponse,
+ ConversationMetricsResponse,
ModelsResponse,
} from "@dispatch/transport-contract";
import type { SurfaceServerMessage, SurfaceSpec } from "@dispatch/ui-contract";
@@ -17,7 +18,7 @@ import {
subscribe as protocolSubscribe,
unsubscribe as protocolUnsubscribe,
} from "../core/protocol";
-import type { ChatStore } from "../features/chat";
+import type { ChatStore, MetricsSync } from "../features/chat";
import { createChatStore } from "../features/chat";
import type { ConversationCache } from "../features/conversation-cache";
import { createConversationCache } from "../features/conversation-cache";
@@ -73,6 +74,15 @@ function createHistorySync(
};
}
+function createMetricsSync(httpBase: string, fetchImpl: typeof fetch): MetricsSync {
+ return async (conversationId: string) => {
+ const url = `${httpBase}/conversations/${encodeURIComponent(conversationId)}/metrics`;
+ const res = await fetchImpl(url);
+ if (!res.ok) return { turns: [] };
+ return (await res.json()) as ConversationMetricsResponse;
+ };
+}
+
export function createAppStore(opts?: CreateAppStoreOptions): AppStore {
let protocol = $state<ProtocolState>(protocolInitialState());
let selectedId = $state<string | null>(null);
@@ -112,6 +122,7 @@ export function createAppStore(opts?: CreateAppStoreOptions): AppStore {
);
const historySync = createHistorySync(httpBase, fetchImpl);
+ const metricsSync = createMetricsSync(httpBase, fetchImpl);
const chatStores = new Map<string, ChatStore>();
@@ -125,6 +136,7 @@ export function createAppStore(opts?: CreateAppStoreOptions): AppStore {
},
},
historySync,
+ metricsSync,
cache,
});
}