/** * TokenUsageDisplay - Compact token usage display with detailed breakdown on hover. * Shows total tokens with an info icon that reveals a popover with: * - Input tokens breakdown * - Cache read/write tokens * - Output tokens * - Optional model information */ import React, { useEffect, useRef, useState } from 'react'; import { createPortal } from 'react-dom'; import { COLOR_TEXT_MUTED, COLOR_TEXT_SECONDARY } from '@renderer/constants/cssVariables'; import { formatCostUsd } from '@shared/utils/costFormatting'; import { getModelColorClass } from '@shared/utils/modelParser'; import { formatTokensCompact as formatTokens, formatTokensDetailed, } from '@shared/utils/tokenFormatting'; import { ChevronRight, Info } from 'lucide-react'; import type { ClaudeMdStats } from '@renderer/types/claudeMd'; import type { ContextStats } from '@renderer/types/contextInjection'; import type { ModelInfo } from '@shared/utils/modelParser'; interface TokenUsageDisplayProps { /** Input tokens count */ inputTokens: number; /** Output tokens count */ outputTokens: number; /** Cache read tokens count */ cacheReadTokens: number; /** Cache creation/write tokens count */ cacheCreationTokens: number; /** Optional model name for display */ modelName?: string; /** Optional model family for color styling */ modelFamily?: ModelInfo['family']; /** Size variant - 'sm' for compact, 'md' for slightly larger */ size?: 'sm' | 'md'; /** Optional CLAUDE.md injection statistics (deprecated, use contextStats) */ claudeMdStats?: ClaudeMdStats; /** Optional unified context statistics */ contextStats?: ContextStats; /** Phase number for this AI group */ phaseNumber?: number; /** Total number of phases in the session */ totalPhases?: number; /** Optional USD cost for this usage */ costUsd?: number; } /** * Expandable section showing session-wide context breakdown. * Shows accumulated totals for CLAUDE.md, mentioned files, tool outputs, and thinking+text. */ const SessionContextSection = ({ contextStats, totalInputTokens, }: Readonly<{ contextStats: ContextStats; totalInputTokens: number; }>): React.JSX.Element => { const [expanded, setExpanded] = useState(false); const { tokensByCategory } = contextStats; // contextStats.totalEstimatedTokens already includes all categories (CLAUDE.md, @files, // tool outputs, thinking+text, task coordination, user messages) - no manual adjustment needed. // Visible Context is always shown as a share of prompt-side input tokens so this section // stays aligned with the unified context contract instead of silently switching semantics. const contextPercent = totalInputTokens > 0 ? Math.min((contextStats.totalEstimatedTokens / totalInputTokens) * 100, 100).toFixed(1) : '0.0'; // Count accumulated injections by category const claudeMdCount = contextStats.accumulatedInjections.filter( (inj) => inj.category === 'claude-md' ).length; const mentionedFilesCount = contextStats.accumulatedInjections.filter( (inj) => inj.category === 'mentioned-file' ).length; const toolOutputsCount = contextStats.accumulatedInjections.filter( (inj) => inj.category === 'tool-output' ).length; const taskCoordinationCount = contextStats.accumulatedInjections.filter( (inj) => inj.category === 'task-coordination' ).length; const userMessagesCount = contextStats.accumulatedInjections.filter( (inj) => inj.category === 'user-message' ).length; // Calculate percentages for each category (relative to total input tokens) const claudeMdPercent = totalInputTokens > 0 ? Math.min((tokensByCategory.claudeMd / totalInputTokens) * 100, 100).toFixed(1) : '0.0'; const mentionedFilesPercent = totalInputTokens > 0 ? Math.min((tokensByCategory.mentionedFiles / totalInputTokens) * 100, 100).toFixed(1) : '0.0'; const toolOutputsPercent = totalInputTokens > 0 ? Math.min((tokensByCategory.toolOutputs / totalInputTokens) * 100, 100).toFixed(1) : '0.0'; const thinkingTextPercent = totalInputTokens > 0 ? Math.min((tokensByCategory.thinkingText / totalInputTokens) * 100, 100).toFixed(1) : '0.0'; const taskCoordinationPercent = totalInputTokens > 0 ? Math.min((tokensByCategory.taskCoordination / totalInputTokens) * 100, 100).toFixed(1) : '0.0'; const userMessagesPercent = totalInputTokens > 0 ? Math.min((tokensByCategory.userMessages / totalInputTokens) * 100, 100).toFixed(1) : '0.0'; return (