Skip to content

Commit 7859bd2

Browse files
committed
feat: add token usage tracking and display in ChatInterface
- Introduced TokenCountInfo component to display total token usage in the chat interface. - Updated EphemeralStore to manage session-specific token usage data. - Enhanced event handling to capture and store token usage from incoming events. - Integrated token usage display within the ChatInterface for improved user feedback.
1 parent cc1cce9 commit 7859bd2

File tree

4 files changed

+55
-3
lines changed

4 files changed

+55
-3
lines changed

src/components/chat/ChatInterface.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { ForkOriginBanner } from './ForkOriginBanner';
1717
import { useEphemeralStore } from '@/stores/EphemeralStore';
1818
import { ChangesSummary } from './ChangesSummary';
1919
import { ModelSelector } from "./ModelSelector";
20+
import TokenCountInfo from "@/components/common/TokenCountInfo";
2021

2122
interface ChatInterfaceProps {
2223
sessionId: string;
@@ -107,6 +108,7 @@ export const ChatInterface: React.FC<ChatInterfaceProps> = ({
107108
const messages = [...sessionMessages];
108109
const isLoading = currentConversation?.isLoading || false;
109110
const fileDiffMap = useEphemeralStore((s) => s.sessionFileDiffs[activeSessionId]);
111+
const sessionUsage = useEphemeralStore((s) => s.sessionTokenUsage[activeSessionId]);
110112

111113
// Update activeSessionId when sessionId prop changes
112114
useEffect(() => {
@@ -480,7 +482,8 @@ export const ChatInterface: React.FC<ChatInterfaceProps> = ({
480482
/>
481483
<ModelSelector />
482484
<ReasoningEffortSelector />
483-
</div>
485+
<TokenCountInfo usage={sessionUsage} />
486+
</div>
484487
</div>
485488
</div>
486489
);
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
type TokenCountShape = {
2+
input_tokens?: number;
3+
cached_input_tokens?: number;
4+
output_tokens?: number;
5+
reasoning_output_tokens?: number;
6+
total_tokens: number;
7+
};
8+
9+
interface TokenCountInfoProps {
10+
usage: TokenCountShape | null | undefined;
11+
className?: string;
12+
}
13+
14+
export function TokenCountInfo({ usage, className }: TokenCountInfoProps) {
15+
if (!usage || typeof usage.total_tokens !== "number") {
16+
return null;
17+
}
18+
19+
return (
20+
<div className={"flex items-center gap-3 " + (className ?? "")}>
21+
<div className="text-sm">
22+
<span className="text-muted-foreground">Total</span>{" "}
23+
<span className="font-medium">{usage.total_tokens.toLocaleString()}</span>
24+
<span className="text-muted-foreground"> tokens</span>
25+
</div>
26+
</div>
27+
);
28+
}
29+
30+
export default TokenCountInfo;

src/hooks/codexEvents/sessionHandlers.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { toast } from "sonner";
33
import { generateUniqueId } from "@/utils/genUniqueId";
44
import type { ChatMessage } from "@/types/chat";
55
import type { CodexEventHandler } from "./types";
6+
import { useEphemeralStore } from "@/stores/EphemeralStore";
67

78
const handleSessionConfigured: CodexEventHandler = (event, context) => {
89
if (event.msg.type !== "session_configured") {
@@ -178,11 +179,13 @@ const handleTurnAborted: CodexEventHandler = (event, context) => {
178179
}
179180
};
180181

181-
const handleTokenCount: CodexEventHandler = (event) => {
182+
const handleTokenCount: CodexEventHandler = (event, context) => {
182183
if (event.msg.type !== "token_count") {
183184
return;
184185
}
185-
console.log("token_count", event.msg);
186+
const { sessionId } = context;
187+
const usage = event.msg.info?.total_token_usage;
188+
useEphemeralStore.getState().setSessionTokenUsage(sessionId, usage ?? undefined);
186189
};
187190

188191
const handleStreamError: CodexEventHandler = (event) => {

src/stores/EphemeralStore.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,26 @@
11
import { create } from 'zustand';
22

3+
interface TokenCountShape {
4+
input_tokens?: number;
5+
cached_input_tokens?: number;
6+
output_tokens?: number;
7+
reasoning_output_tokens?: number;
8+
total_tokens: number;
9+
}
10+
311
interface EphemeralStoreState {
412
// sessionId -> filePath -> { unified, updatedAt }
513
sessionFileDiffs: Record<string, Record<string, { unified: string; updatedAt: number }>>;
14+
// sessionId -> latest token usage
15+
sessionTokenUsage: Record<string, TokenCountShape | undefined>;
616
setTurnDiff: (sessionId: string, unifiedDiff: string) => void;
717
clearTurnDiffs: (sessionId: string) => void;
18+
setSessionTokenUsage: (sessionId: string, usage?: TokenCountShape) => void;
819
}
920

1021
export const useEphemeralStore = create<EphemeralStoreState>((set) => ({
1122
sessionFileDiffs: {},
23+
sessionTokenUsage: {},
1224
setTurnDiff: (sessionId, unifiedDiff) =>
1325
set((state) => {
1426
// Parse unified diff into per-file segments and merge (overwrite by file)
@@ -65,4 +77,8 @@ export const useEphemeralStore = create<EphemeralStoreState>((set) => ({
6577
delete next[sessionId];
6678
return { sessionFileDiffs: next };
6779
}),
80+
setSessionTokenUsage: (sessionId, usage) =>
81+
set((state) => ({
82+
sessionTokenUsage: { ...state.sessionTokenUsage, [sessionId]: usage },
83+
})),
6884
}));

0 commit comments

Comments
 (0)