/** * 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; /** Thinking tokens (extended thinking content) - estimated from content */ thinkingTokens?: number; /** Text output tokens (Claude's text responses) - estimated from content */ textOutputTokens?: 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, totalTokens, thinkingTokens = 0, textOutputTokens = 0, }: Readonly<{ contextStats: ContextStats; totalTokens: number; thinkingTokens?: number; textOutputTokens?: number; }>): React.JSX.Element => { const [expanded, setExpanded] = useState(false); const { tokensByCategory } = contextStats; // Calculate combined thinking+text tokens and include in context total const thinkingTextTokens = thinkingTokens + textOutputTokens; const adjustedContextTotal = contextStats.totalEstimatedTokens + thinkingTextTokens; const contextPercent = totalTokens > 0 ? Math.min((adjustedContextTotal / totalTokens) * 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 const claudeMdPercent = totalTokens > 0 ? Math.min((tokensByCategory.claudeMd / totalTokens) * 100, 100).toFixed(1) : '0.0'; const mentionedFilesPercent = totalTokens > 0 ? Math.min((tokensByCategory.mentionedFiles / totalTokens) * 100, 100).toFixed(1) : '0.0'; const toolOutputsPercent = totalTokens > 0 ? Math.min((tokensByCategory.toolOutputs / totalTokens) * 100, 100).toFixed(1) : '0.0'; const thinkingTextPercent = totalTokens > 0 ? Math.min((thinkingTextTokens / totalTokens) * 100, 100).toFixed(1) : '0.0'; const taskCoordinationPercent = totalTokens > 0 ? Math.min((tokensByCategory.taskCoordination / totalTokens) * 100, 100).toFixed(1) : '0.0'; const userMessagesPercent = totalTokens > 0 ? Math.min((tokensByCategory.userMessages / totalTokens) * 100, 100).toFixed(1) : '0.0'; return (