import { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { cn } from '../../../../lib/utils'; import SessionProviderLogo from '../../../llm-logo-provider/SessionProviderLogo'; type ClaudeStatusProps = { status: { text?: string; tokens?: number; can_interrupt?: boolean; } | null; onAbort?: () => void; isLoading: boolean; provider?: string; }; const ACTION_KEYS = [ 'claudeStatus.actions.thinking', 'claudeStatus.actions.processing', 'claudeStatus.actions.analyzing', 'claudeStatus.actions.working', 'claudeStatus.actions.computing', 'claudeStatus.actions.reasoning', ]; const DEFAULT_ACTION_WORDS = ['Thinking', 'Processing', 'Analyzing', 'Working', 'Computing', 'Reasoning']; const ANIMATION_STEPS = 40; const PROVIDER_LABEL_KEYS: Record = { claude: 'messageTypes.claude', codex: 'messageTypes.codex', cursor: 'messageTypes.cursor', gemini: 'messageTypes.gemini', }; function formatElapsedTime(totalSeconds: number, t: (key: string, options?: Record) => string) { const minutes = Math.floor(totalSeconds / 60); const seconds = totalSeconds % 60; if (minutes < 1) { return t('claudeStatus.elapsed.seconds', { count: seconds, defaultValue: '{{count}}s' }); } return t('claudeStatus.elapsed.minutesSeconds', { minutes, seconds, defaultValue: '{{minutes}}m {{seconds}}s', }); } export default function ClaudeStatus({ status, onAbort, isLoading, provider = 'claude', }: ClaudeStatusProps) { const { t } = useTranslation('chat'); const [elapsedTime, setElapsedTime] = useState(0); const [animationPhase, setAnimationPhase] = useState(0); useEffect(() => { if (!isLoading) { setElapsedTime(0); return; } const startTime = Date.now(); const timer = window.setInterval(() => { const elapsed = Math.floor((Date.now() - startTime) / 1000); setElapsedTime(elapsed); }, 1000); return () => window.clearInterval(timer); }, [isLoading]); useEffect(() => { if (!isLoading) { return; } const timer = window.setInterval(() => { setAnimationPhase((previous) => (previous + 1) % ANIMATION_STEPS); }, 500); return () => window.clearInterval(timer); }, [isLoading]); // Note: showThinking only controls the reasoning accordion in messages, not this processing indicator if (!isLoading && !status) { return null; } const actionWords = ACTION_KEYS.map((key, index) => t(key, { defaultValue: DEFAULT_ACTION_WORDS[index] })); const actionIndex = Math.floor(elapsedTime / 3) % actionWords.length; const statusText = status?.text || actionWords[actionIndex]; const cleanStatusText = statusText.replace(/[.]+$/, ''); const canInterrupt = isLoading && status?.can_interrupt !== false; const providerLabelKey = PROVIDER_LABEL_KEYS[provider]; const providerLabel = providerLabelKey ? t(providerLabelKey) : t('claudeStatus.providers.assistant', { defaultValue: 'Assistant' }); const animatedDots = '.'.repeat((animationPhase % 3) + 1); const elapsedLabel = elapsedTime > 0 ? t('claudeStatus.elapsed.label', { time: formatElapsedTime(elapsedTime, t), defaultValue: '{{time}} elapsed', }) : t('claudeStatus.elapsed.startingNow', { defaultValue: 'Starting now' }); return (
{isLoading && ( )}
{providerLabel} {isLoading ? t('claudeStatus.state.live', { defaultValue: 'Live' }) : t('claudeStatus.state.paused', { defaultValue: 'Paused' })}

{cleanStatusText} {isLoading && {animatedDots}}

{elapsedLabel}
{canInterrupt && onAbort && (

{t('claudeStatus.controls.pressEscToStop', { defaultValue: 'Press Esc anytime to stop' })}

)}
); }