Add a desktop Direct-chat header indicator for estimated tokens used against the active model context window. - Estimate chat token usage client-side, including streaming text, and format compact counts. - Render the context-window badge only for non-mobile Direct-chat threads with known model context windows. - Cover desktop, mobile, floating-narrow, rooms, unknown-context, and streaming surfaces with tests. - Document the dashboard behavior and add a release changeset. Files changed: .changeset/fn-7141-chat-context-window.md | 7 + docs/dashboard-guide.md | 2 + packages/dashboard/app/components/ChatView.css | 22 +++ packages/dashboard/app/components/ChatView.tsx | 35 +++- .../__tests__/ChatView.context-window.test.tsx | 193 +++++++++++++++++++++ .../app/utils/__tests__/estimateChatTokens.test.ts | 34 ++++ packages/dashboard/app/utils/estimateChatTokens.ts | 29 ++++ packages/i18n/locales/en/app.json | 1 + 8 files changed, 322 insertions(+), 1 deletion(-) Fusion-Task-Id: FN-7141 Fusion-Task-Lineage: 3ede9af4-b8d6-4da6-996f-cd9277ef49ac Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
30 lines
1002 B
TypeScript
30 lines
1002 B
TypeScript
export interface ChatTokenEstimateMessage {
|
|
content?: string | null;
|
|
}
|
|
|
|
/*
|
|
FNXC:ChatContextWindow 2026-06-27-00:00:
|
|
Direct chat does not persist provider token usage on ChatMessageInfo, so the header budget gauge must remain an explicit client-side estimate. Use the conservative four-characters-per-token heuristic and include live streaming text so long responses update without backend changes.
|
|
*/
|
|
export function estimateChatTokens(messages: ChatTokenEstimateMessage[], streamingText?: string | null): number {
|
|
const totalChars = messages.reduce((sum, message) => sum + (message.content?.length ?? 0), 0) + (streamingText?.length ?? 0);
|
|
return Math.ceil(totalChars / 4);
|
|
}
|
|
|
|
export function formatTokenCount(n: number): string {
|
|
if (!Number.isFinite(n) || n <= 0) {
|
|
return "0";
|
|
}
|
|
|
|
if (n < 1000) {
|
|
return String(Math.round(n));
|
|
}
|
|
|
|
const thousands = n / 1000;
|
|
if (thousands < 10) {
|
|
return `~${Number(thousands.toFixed(1))}k`;
|
|
}
|
|
|
|
return `${Math.round(thousands)}k`;
|
|
}
|