From 1581544b7fa2103cce63d2aa3c19580a1995adf7 Mon Sep 17 00:00:00 2001 From: chengyongru <2755839590@qq.com> Date: Sun, 23 Aug 2026 04:24:43 +0800 Subject: [PATCH] fix(webui): clarify aggregate turn token usage --- webui/src/components/MessageBubble.tsx | 19 +++++++++++++++++++ webui/src/tests/message-bubble.test.tsx | 11 +++++++---- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/webui/src/components/MessageBubble.tsx b/webui/src/components/MessageBubble.tsx index 5d14bd09e..d527749e9 100644 --- a/webui/src/components/MessageBubble.tsx +++ b/webui/src/components/MessageBubble.tsx @@ -192,9 +192,11 @@ function compactDuration(milliseconds: number): string { function TurnUsageMeta({ usage, latencyMs, + contextWindowTokens, }: { usage: TurnUsage; latencyMs?: number; + contextWindowTokens?: number; }) { const { t } = useTranslation(); const prompt = usage.prompt_tokens; @@ -210,10 +212,26 @@ function TurnUsageMeta({ ) { parts.push(`${Math.round(Math.min(1, usage.cached_tokens / prompt) * 100)}% cached`); } + if (typeof usage.request_count === "number" && usage.request_count > 1) { + parts.push(t("message.usage.requests", { + count: usage.request_count, + defaultValue: "{{count}} calls this turn", + })); + } if (typeof latencyMs === "number" && latencyMs >= 0) parts.push(compactDuration(latencyMs)); if (parts.length === 0) return null; const details: string[] = []; + if (typeof usage.context_tokens === "number" && usage.context_tokens >= 0) { + const capacity = typeof contextWindowTokens === "number" && contextWindowTokens > 0 + ? ` / ${formatCompactTokenCount(contextWindowTokens)}` + : ""; + details.push(t("message.usage.context", { + tokens: `${approximate}${formatCompactTokenCount(usage.context_tokens)}`, + capacity, + defaultValue: "Context now: {{tokens}}{{capacity}}", + })); + } if (approximate) { details.push(t("message.usage.estimated", { defaultValue: "Includes estimated usage" })); } @@ -621,6 +639,7 @@ export function MessageBubble({ ) : null} {showAssistantTimestamp ? ( diff --git a/webui/src/tests/message-bubble.test.tsx b/webui/src/tests/message-bubble.test.tsx index 369abde2f..601624176 100644 --- a/webui/src/tests/message-bubble.test.tsx +++ b/webui/src/tests/message-bubble.test.tsx @@ -1013,7 +1013,7 @@ describe("MessageBubble", () => { expect(screen.queryByLabelText("File attachment")).not.toBeInTheDocument(); }); - it("keeps turn usage focused on the completed reply", () => { + it("distinguishes aggregate turn usage from the latest context", () => { const message: UIMessage = { id: "a-usage", role: "assistant", @@ -1032,10 +1032,13 @@ describe("MessageBubble", () => { render(); - const usage = screen.getByText("12.4K in · 823 out · 78% cached · 18s"); + const usage = screen.getByText("12.4K in · 823 out · 78% cached · 3 calls this turn · 18s"); expect(usage).toHaveAttribute("data-turn-usage"); - expect(usage).not.toHaveAttribute("tabindex"); - expect(screen.queryByRole("tooltip")).not.toBeInTheDocument(); + expect(usage).toHaveAttribute("tabindex", "0"); + + fireEvent.focus(usage); + + expect(screen.getByRole("tooltip")).toHaveTextContent("Context now: 8.1K / 128K"); }); it("marks estimated usage and omits cache when the provider did not report it", () => {