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", () => {