"use client" import { useCallback, useSyncExternalStore } from "react" import { Coins } from "lucide-react" import { useTranslations } from "next-intl" import { useSessionStats } from "@/contexts/session-stats-context" import { useConnectionStore } from "@/contexts/acp-connections-context" import { Popover, PopoverContent, PopoverTrigger, } from "@/components/ui/popover" const ICON_RADIUS = 6 const ICON_CENTER = 8 const ICON_VIEWBOX = 16 const ICON_CIRCUMFERENCE = 2 * Math.PI * ICON_RADIUS function formatNumber(n: number): string { return n.toLocaleString() } function formatTokenCount(n: number): string { if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M` if (n >= 1_000) return `${(n / 1_000).toFixed(1)}K` return String(n) } function formatPercent(percent: number | null): string { if (percent == null) return "--" return `${percent.toFixed(1)}%` } export function StatusBarTokens() { const t = useTranslations("Folder.statusBar.tokens") const store = useConnectionStore() const { sessionStats } = useSessionStats() const usage = sessionStats?.total_usage const subscribeActiveKey = useCallback( (cb: () => void) => store.subscribeActiveKey(cb), [store] ) const getActiveKey = useCallback(() => store.getActiveKey(), [store]) const activeKey = useSyncExternalStore( subscribeActiveKey, getActiveKey, getActiveKey ) const subscribeConn = useCallback( (cb: () => void) => { if (!activeKey) return () => {} return store.subscribeKey(activeKey, cb) }, [store, activeKey] ) const getConnSnapshot = useCallback( () => (activeKey ? store.getConnection(activeKey) : undefined), [store, activeKey] ) const activeConn = useSyncExternalStore( subscribeConn, getConnSnapshot, getConnSnapshot ) const rawLiveUsed = activeConn?.usage?.used ?? null const rawLiveSize = activeConn?.usage?.size ?? null // Treat live used=0 as "no data" so we fall back to sessionStats — // Claude Code sends used=0 for synthetic local commands (/context etc.) const liveContextUsed = rawLiveUsed != null && rawLiveUsed > 0 ? rawLiveUsed : null const liveContextMax = rawLiveSize != null && rawLiveSize > 0 ? rawLiveSize : null const contextUsed = liveContextUsed ?? sessionStats?.context_window_used_tokens ?? null const contextMax = liveContextMax ?? sessionStats?.context_window_max_tokens ?? null const contextPercentRaw = (liveContextUsed != null && liveContextMax != null && liveContextMax > 0 ? (liveContextUsed / liveContextMax) * 100 : sessionStats?.context_window_usage_percent) ?? (contextUsed != null && contextMax != null && contextMax > 0 ? (contextUsed / contextMax) * 100 : null) const contextPercent = contextPercentRaw == null ? null : Math.max(0, Math.min(100, contextPercentRaw)) const hasContext = contextPercent != null const hasUsage = usage != null const fallbackTotal = hasUsage ? usage.input_tokens + usage.output_tokens + usage.cache_creation_input_tokens + usage.cache_read_input_tokens : null const total = sessionStats?.total_tokens ?? fallbackTotal const dashOffset = ICON_CIRCUMFERENCE * (1 - (contextPercent ?? 0) / 100) const rows: { key: "input" | "output" | "cacheRead" | "cacheWrite" | "total" value: number }[] = [] if (hasUsage) { rows.push( { key: "input", value: usage.input_tokens }, { key: "output", value: usage.output_tokens }, { key: "cacheRead", value: usage.cache_read_input_tokens }, { key: "cacheWrite", value: usage.cache_creation_input_tokens } ) } if (total != null) { rows.push({ key: "total", value: total }) } const hasTokenSection = rows.length > 0 if (!hasContext && !hasTokenSection) return null return ( {hasContext ? (
{t("contextWindow")} {formatPercent(contextPercent)}
{t("usedMax")} {contextUsed == null || contextMax == null ? "--" : `${formatNumber(contextUsed)} / ${formatNumber(contextMax)}`}
) : null} {hasTokenSection ? ( <>
{t("tokenUsage")}
{rows.map((row) => (
{t(row.key)} {formatNumber(row.value)}
))}
) : null} ) }