import React, { useState, useEffect, useRef } from 'react'; import { AppBar, Toolbar, Box, Typography, Chip, IconButton, CircularProgress, keyframes, Button } from '@mui/material'; import ArrowBackIcon from '@mui/icons-material/ArrowBack'; import LightModeOutlined from '@mui/icons-material/LightModeOutlined'; import DarkModeOutlined from '@mui/icons-material/DarkModeOutlined'; import CheckIcon from '@mui/icons-material/Check'; import CloseIcon from '@mui/icons-material/Close'; import AccessTimeIcon from '@mui/icons-material/AccessTime'; import InputIcon from '@mui/icons-material/Input'; import OutputIcon from '@mui/icons-material/Output'; import SmartToyIcon from '@mui/icons-material/SmartToy'; import FormatListNumberedIcon from '@mui/icons-material/FormatListNumbered'; import HourglassEmptyIcon from '@mui/icons-material/HourglassEmpty'; import StopCircleIcon from '@mui/icons-material/StopCircle'; import { useAgentStore, selectTrace, selectError, selectIsDarkMode, selectMetadata, selectIsConnectingToE2B, selectFinalStep } from '@/stores/agentStore'; interface HeaderProps { isAgentProcessing: boolean; onBackToHome?: () => void; } // Animation for the running task border - smooth oscillation (primary) const borderPulse = keyframes` 0%, 100% { border-color: rgba(79, 134, 198, 0.5); box-shadow: 0 0 0 0 rgba(79, 134, 198, 0.3); } 50% { border-color: rgba(79, 134, 198, 1); box-shadow: 0 0 8px 2px rgba(79, 134, 198, 0.4); } `; // Animation for the background glow (primary) const backgroundPulse = keyframes` 0%, 100% { background-color: rgba(79, 134, 198, 0.08); } 50% { background-color: rgba(79, 134, 198, 0.15); } `; // Animation for token flash - smooth glow effect const tokenFlash = keyframes` 0% { filter: brightness(1); text-shadow: none; } 25% { filter: brightness(1.4); text-shadow: 0 0 8px rgba(79, 134, 198, 0.6); } 100% { filter: brightness(1); text-shadow: none; } `; // Animation for token icon flash const iconFlash = keyframes` 0% { filter: brightness(1); transform: scale(1); } 25% { filter: brightness(1.6); transform: scale(1.15); } 100% { filter: brightness(1); transform: scale(1); } `; export const Header: React.FC = ({ isAgentProcessing, onBackToHome }) => { const trace = useAgentStore(selectTrace); const error = useAgentStore(selectError); const finalStep = useAgentStore(selectFinalStep); const isDarkMode = useAgentStore(selectIsDarkMode); const toggleDarkMode = useAgentStore((state) => state.toggleDarkMode); const metadata = useAgentStore(selectMetadata); const isConnectingToE2B = useAgentStore(selectIsConnectingToE2B); const [elapsedTime, setElapsedTime] = useState(0); const [inputTokenFlash, setInputTokenFlash] = useState(false); const [outputTokenFlash, setOutputTokenFlash] = useState(false); const prevInputTokens = useRef(0); const prevOutputTokens = useRef(0); // Update elapsed time every 100ms when agent is processing useEffect(() => { if (isAgentProcessing && trace?.timestamp) { const interval = setInterval(() => { const now = new Date(); const startTime = new Date(trace.timestamp); const elapsed = (now.getTime() - startTime.getTime()) / 1000; setElapsedTime(elapsed); }, 100); return () => clearInterval(interval); } else if (metadata && metadata.duration > 0) { setElapsedTime(metadata.duration); } }, [isAgentProcessing, trace?.timestamp, metadata]); // Detect token changes and trigger flash animation useEffect(() => { if (metadata) { // Input tokens changed if (metadata.inputTokensUsed > prevInputTokens.current && prevInputTokens.current > 0) { setInputTokenFlash(true); setTimeout(() => setInputTokenFlash(false), 800); } prevInputTokens.current = metadata.inputTokensUsed; // Output tokens changed if (metadata.outputTokensUsed > prevOutputTokens.current && prevOutputTokens.current > 0) { setOutputTokenFlash(true); setTimeout(() => setOutputTokenFlash(false), 800); } prevOutputTokens.current = metadata.outputTokensUsed; } }, [metadata?.inputTokensUsed, metadata?.outputTokensUsed]); // Determine task status - Use finalStep as source of truth const getTaskStatus = () => { // If we have a final step, use its type if (finalStep) { switch (finalStep.type) { case 'failure': return { label: 'Task failed', color: 'error', icon: }; case 'stopped': return { label: 'Task stopped', color: 'warning', icon: }; case 'max_steps_reached': return { label: 'Max steps reached', color: 'warning', icon: }; case 'success': return { label: 'Completed', color: 'success', icon: }; } } // Otherwise check running states if (isConnectingToE2B) return { label: 'Starting...', color: 'primary', icon: }; if (isAgentProcessing || trace?.isRunning) return { label: 'Running', color: 'primary', icon: }; return { label: 'Ready', color: 'default', icon: }; }; const taskStatus = getTaskStatus(); // Extract model name from modelId (e.g., "Qwen/Qwen3-VL-8B-Instruct" -> "Qwen3-VL-8B-Instruct") const modelName = trace?.modelId?.split('/').pop() || 'Unknown Model'; // Handler for emergency stop const handleEmergencyStop = () => { const stopTask = (window as Window & { __stopCurrentTask?: () => void }).__stopCurrentTask; if (stopTask) { stopTask(); } }; return ( {/* First row: Back button + Task info + Connection Status */} {/* Left side: Back button + Task info */} {trace?.instruction || 'No task running'} {/* Right side: Emergency Stop + Dark Mode */} {/* Emergency Stop Button - Only show when agent is processing */} {isAgentProcessing && ( )} {isDarkMode ? : } {/* Second row: Status + Model + Metadata - Only show when we have trace data */} {trace && ( {/* Status Badge - Compact */} {taskStatus.icon} {taskStatus.label} {/* Divider */} {/* Model */} {modelName} {/* Steps Count */} {metadata && ( <> {metadata.numberOfSteps} {metadata.numberOfSteps === 1 ? 'Step' : 'Steps'} )} {/* Time */} {(isAgentProcessing || metadata) && ( <> {elapsedTime.toFixed(1)}s )} {/* Input Tokens */} {metadata && metadata.inputTokensUsed > 0 && ( <> {metadata.inputTokensUsed.toLocaleString()} )} {/* Output Tokens */} {metadata && metadata.outputTokensUsed > 0 && ( <> {metadata.outputTokensUsed.toLocaleString()} )} )} ); };