diff --git a/ui/src/lib/format.test.ts b/ui/src/lib/format.test.ts index dcdeeef8e0c3..3a555f2e422c 100644 --- a/ui/src/lib/format.test.ts +++ b/ui/src/lib/format.test.ts @@ -7,6 +7,7 @@ import { formatDateTimeMs, formatDateMs, formatCompactTokenCount, + formatContextTokenCapacity, formatDurationCompact, formatDurationHuman, formatMs, @@ -209,6 +210,7 @@ describe("formatCompactTokenCount", () => { it("formats millions with one decimal, trimming a trailing .0", () => { expect(formatCompactTokenCount(1_000_000)).toBe("1M"); + expect(formatCompactTokenCount(1_050_000)).toBe("1.1M"); expect(formatCompactTokenCount(1_500_000)).toBe("1.5M"); }); @@ -235,6 +237,21 @@ describe("formatCompactTokenCount", () => { }); }); +describe("formatContextTokenCapacity", () => { + it("truncates million-scale capacity to at most one decimal", () => { + expect(formatContextTokenCapacity(1_000_000)).toBe("1M"); + expect(formatContextTokenCapacity(1_050_000)).toBe("1M"); + expect(formatContextTokenCapacity(1_100_000)).toBe("1.1M"); + }); + + it("preserves shared compact formatting below one million", () => { + expect(formatContextTokenCapacity(999)).toBe("999"); + expect(formatContextTokenCapacity(1_000)).toBe("1k"); + expect(formatContextTokenCapacity(32_768)).toBe("32.8k"); + expect(formatContextTokenCapacity(999_999)).toBe("1M"); + }); +}); + describe("formatTokens", () => { it("rolls a value that rounds up to 1000k over into the M branch", () => { expect(formatTokens(999_500)).toBe("1.0M"); diff --git a/ui/src/lib/format.ts b/ui/src/lib/format.ts index c9649f1fd7a4..d11f5bf8621a 100644 --- a/ui/src/lib/format.ts +++ b/ui/src/lib/format.ts @@ -325,3 +325,10 @@ export function formatCompactTokenCount( } return String(tokens); } + +export function formatContextTokenCapacity(tokens: number): string { + if (tokens < 1_000_000) { + return formatCompactTokenCount(tokens); + } + return `${Math.floor(tokens / 100_000) / 10}M`; +} diff --git a/ui/src/pages/chat/chat-view.test.ts b/ui/src/pages/chat/chat-view.test.ts index 317ae767de14..24d7c74df5ca 100644 --- a/ui/src/pages/chat/chat-view.test.ts +++ b/ui/src/pages/chat/chat-view.test.ts @@ -5106,7 +5106,7 @@ describe("chat model controls", () => { ); expect(modelOption?.querySelector(".chat-controls__model-option-meta")?.textContent).toBe( - "1.1M context", + "1M context", ); expect(modelOption?.closest("openclaw-tooltip")).toBeNull(); }); diff --git a/ui/src/pages/chat/components/chat-model-controls.ts b/ui/src/pages/chat/components/chat-model-controls.ts index 97f05564239b..7eaad164e06f 100644 --- a/ui/src/pages/chat/components/chat-model-controls.ts +++ b/ui/src/pages/chat/components/chat-model-controls.ts @@ -26,7 +26,7 @@ import { formatThinkingOverrideLabel, resolveChatThinkingSelectState, } from "../../../lib/chat/thinking.ts"; -import { formatCompactTokenCount } from "../../../lib/format.ts"; +import { formatContextTokenCapacity } from "../../../lib/format.ts"; import { areUiSessionKeysEquivalent } from "../../../lib/sessions/session-key.ts"; import { moveChatModelProviderFocus, selectChatModelProvider } from "./chat-model-provider-menu.ts"; @@ -658,7 +658,7 @@ function renderChatModelReasoningSelect(params: { const modelMeta = [ entry.contextWindow ? t("chat.modelControls.contextWindow", { - count: formatCompactTokenCount(entry.contextWindow), + count: formatContextTokenCapacity(entry.contextWindow), }) : "", entry.supportsTools === false ? t("chat.modelControls.chatOnly") : "",