Skip to content

Commit 458a302

Browse files
committed
Add a live token estimate to the composer
Token usage was only ever shown after a response finished (per-message and per-session cost). Now the composer shows a running estimate — draft tokens recomputed on every keystroke (cheap, just the current text), context tokens (system prompt + conversation so far) memoized separately so it only recalculates when the conversation itself changes, not on every keystroke in a long chat. Reuses the existing estimateTokens/estimateMessagesTokens heuristic already powering context compaction, so the number is consistent with when the app decides to fold older messages away.
1 parent 1f6b45c commit 458a302

2 files changed

Lines changed: 24 additions & 0 deletions

File tree

frontend/src/lib/translations.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export interface Dictionary {
1010
noMatchingChats: string;
1111
model: string;
1212
sendMessage: string;
13+
contextTokensEstimate: (draftTokens: number, contextTokens: number) => string;
1314
startConversationWith: (model: string) => string;
1415
showEarlierMessages: (count: number) => string;
1516
attach: string;
@@ -368,6 +369,10 @@ export const en: Dictionary = {
368369
noMatchingChats: "No matching chats.",
369370
model: "Model",
370371
sendMessage: "Send a message...",
372+
contextTokensEstimate: (draftTokens, contextTokens) =>
373+
draftTokens > 0
374+
? `~${draftTokens.toLocaleString()} tokens in draft · ~${contextTokens.toLocaleString()} in context`
375+
: `~${contextTokens.toLocaleString()} tokens in context`,
371376
startConversationWith: (model) => `Start a conversation with ${model}.`,
372377
showEarlierMessages: (count) => `Show ${count} earlier message${count === 1 ? "" : "s"}`,
373378
attach: "Attach",
@@ -736,6 +741,10 @@ export const tr: Dictionary = {
736741
noMatchingChats: "Eşleşen sohbet yok.",
737742
model: "Model",
738743
sendMessage: "Bir mesaj gönderin...",
744+
contextTokensEstimate: (draftTokens, contextTokens) =>
745+
draftTokens > 0
746+
? `Taslakta ~${draftTokens.toLocaleString()} token · bağlamda ~${contextTokens.toLocaleString()}`
747+
: `Bağlamda ~${contextTokens.toLocaleString()} token`,
739748
startConversationWith: (model) => `${model} ile sohbete başlayın.`,
740749
showEarlierMessages: (count) => `${count} önceki mesajı göster`,
741750
attach: "Ekle",

frontend/src/pages/Chat.tsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ import {
7474
COMPACTION_BUDGET_TOKENS,
7575
COMPACTION_KEEP_RECENT,
7676
buildSummarizationPrompt,
77+
estimateMessagesTokens,
78+
estimateTokens,
7779
planCompaction,
7880
shouldCompact,
7981
} from "@/lib/context-compaction";
@@ -1407,6 +1409,14 @@ export default function Chat() {
14071409
: 0,
14081410
[messages, parsedModel]
14091411
);
1412+
// Recomputed only when the conversation itself changes, not on every
1413+
// keystroke — draftTokens below covers the composer text separately so
1414+
// typing stays cheap even in a very long chat.
1415+
const contextTokens = useMemo(
1416+
() => estimateTokens(sessionSystemPrompt ?? "") + estimateMessagesTokens(messages) + (contextSummary ? estimateTokens(contextSummary) : 0),
1417+
[messages, sessionSystemPrompt, contextSummary]
1418+
);
1419+
const draftTokens = useMemo(() => estimateTokens(input), [input]);
14101420

14111421
if (!hasApi) {
14121422
return (
@@ -2379,6 +2389,11 @@ export default function Chat() {
23792389
</Button>
23802390
)}
23812391
</div>
2392+
{(draftTokens > 0 || contextTokens > 0) && (
2393+
<p className="mt-1 px-1 text-right text-[10px] text-muted-foreground/70">
2394+
{t.contextTokensEstimate(draftTokens, contextTokens)}
2395+
</p>
2396+
)}
23822397
</div>
23832398
</div>
23842399
<PromptVariableDialog

0 commit comments

Comments
 (0)