From f238050b85c3b99a702a8635059735e1a3b3a4f4 Mon Sep 17 00:00:00 2001 From: Simos Mikelatos Date: Fri, 5 Jun 2026 17:33:22 +0000 Subject: [PATCH] feat(chat): open cost modal from token usage --- .../chat/hooks/useChatComposerState.ts | 22 ++++++++++++++++--- src/components/chat/view/ChatInterface.tsx | 2 ++ .../chat/view/subcomponents/ChatComposer.tsx | 4 +++- .../view/subcomponents/TokenUsageSummary.tsx | 12 ++++++---- 4 files changed, 32 insertions(+), 8 deletions(-) diff --git a/src/components/chat/hooks/useChatComposerState.ts b/src/components/chat/hooks/useChatComposerState.ts index 7d2fc9b4..dca2b2f8 100644 --- a/src/components/chat/hooks/useChatComposerState.ts +++ b/src/components/chat/hooks/useChatComposerState.ts @@ -311,7 +311,7 @@ export function useChatComposerState({ }, [addMessage]); const executeCommand = useCallback( - async (command: SlashCommand, rawInput?: string) => { + async (command: SlashCommand, rawInput?: string, options?: { preserveInput?: boolean }) => { if (!command || !selectedProject) { return; } @@ -368,8 +368,10 @@ export function useChatComposerState({ const result = (await response.json()) as CommandExecutionResult; if (result.type === 'builtin') { handleBuiltInCommand(result); - setInput(''); - inputValueRef.current = ''; + if (!options?.preserveInput) { + setInput(''); + inputValueRef.current = ''; + } } else if (result.type === 'custom') { await handleCustomCommand(result); } @@ -400,6 +402,19 @@ export function useChatComposerState({ ], ); + const showCostModal = useCallback(() => { + executeCommand( + { + name: '/cost', + description: 'Display token usage information', + namespace: 'builtin', + metadata: { type: 'builtin' }, + } as SlashCommand, + '/cost', + { preserveInput: true }, + ); + }, [executeCommand]); + const { slashCommands, slashCommandsCount, @@ -1049,5 +1064,6 @@ export function useChatComposerState({ isInputFocused, commandModalPayload, closeCommandModal, + showCostModal, }; } diff --git a/src/components/chat/view/ChatInterface.tsx b/src/components/chat/view/ChatInterface.tsx index 850ae589..01ecb68a 100644 --- a/src/components/chat/view/ChatInterface.tsx +++ b/src/components/chat/view/ChatInterface.tsx @@ -178,6 +178,7 @@ function ChatInterface({ isInputFocused: _isInputFocused, commandModalPayload, closeCommandModal, + showCostModal, } = useChatComposerState({ selectedProject, selectedSession, @@ -368,6 +369,7 @@ function ChatInterface({ permissionMode={permissionMode} onModeSwitch={cyclePermissionMode} tokenBudget={tokenBudget} + onShowTokenUsage={showCostModal} slashCommandsCount={slashCommandsCount} onToggleCommandMenu={handleToggleCommandMenu} hasInput={Boolean(input.trim())} diff --git a/src/components/chat/view/subcomponents/ChatComposer.tsx b/src/components/chat/view/subcomponents/ChatComposer.tsx index 8b543f6a..4812078b 100644 --- a/src/components/chat/view/subcomponents/ChatComposer.tsx +++ b/src/components/chat/view/subcomponents/ChatComposer.tsx @@ -58,6 +58,7 @@ interface ChatComposerProps { permissionMode: PermissionMode | string; onModeSwitch: () => void; tokenBudget: Record | null; + onShowTokenUsage: () => void; slashCommandsCount: number; onToggleCommandMenu: () => void; hasInput: boolean; @@ -111,6 +112,7 @@ export default function ChatComposer({ permissionMode, onModeSwitch, tokenBudget, + onShowTokenUsage, slashCommandsCount, onToggleCommandMenu, hasInput, @@ -353,7 +355,7 @@ export default function ChatComposer({ - + | null; + onClick?: () => void; }; const formatTokenCount = (value: number) => { @@ -29,7 +30,7 @@ const readUsageNumber = (value: unknown) => { return Number.isFinite(parsed) ? parsed : 0; }; -export default function TokenUsageSummary({ usage }: TokenUsageSummaryProps) { +export default function TokenUsageSummary({ usage, onClick }: TokenUsageSummaryProps) { const breakdown = usage?.breakdown && typeof usage.breakdown === 'object' ? usage.breakdown as Record @@ -39,15 +40,18 @@ export default function TokenUsageSummary({ usage }: TokenUsageSummaryProps) { const usedTokens = readUsageNumber(usage?.used) || inputTokens + outputTokens; return ( -
{formatTokenCount(usedTokens)} tokens -
+ ); }