import React, { useRef, useEffect } from 'react'; import { Box, Typography, CircularProgress, Button } from '@mui/material'; import CheckIcon from '@mui/icons-material/Check'; import CloseIcon from '@mui/icons-material/Close'; import StopCircleIcon from '@mui/icons-material/StopCircle'; import HourglassEmptyIcon from '@mui/icons-material/HourglassEmpty'; import AccessTimeIcon from '@mui/icons-material/AccessTime'; import CableIcon from '@mui/icons-material/Cable'; import { AgentTraceMetadata } from '@/types/agent'; import { useAgentStore, selectSelectedStepIndex, selectFinalStep, selectIsConnectingToE2B, selectIsAgentProcessing } from '@/stores/agentStore'; interface TimelineProps { metadata: AgentTraceMetadata; isRunning: boolean; } export const Timeline: React.FC = ({ metadata, isRunning }) => { const timelineRef = useRef(null); const selectedStepIndex = useAgentStore(selectSelectedStepIndex); const setSelectedStepIndex = useAgentStore((state) => state.setSelectedStepIndex); const finalStep = useAgentStore(selectFinalStep); const isConnectingToE2B = useAgentStore(selectIsConnectingToE2B); const isAgentProcessing = useAgentStore(selectIsAgentProcessing); // Show connection indicator if connecting or if we have started processing const showConnectionIndicator = isConnectingToE2B || isAgentProcessing || (metadata.numberOfSteps > 0) || finalStep; // Generate array of steps with their status // Only show completed steps + current step if running const totalStepsToShow = isRunning && !isConnectingToE2B ? metadata.numberOfSteps + 1 // Show completed steps + current step : metadata.numberOfSteps; // Show only completed steps when not running // Calculate total width for the line (including finalStep if present) const lineWidth = finalStep ? `calc(${totalStepsToShow} * (40px + 12px) + 52px)` // Add space for finalStep (40px + 12px gap) : `calc(${totalStepsToShow} * (40px + 12px))`; const steps = Array.from({ length: totalStepsToShow }, (_, index) => ({ stepNumber: index + 1, stepIndex: index, isCompleted: index < metadata.numberOfSteps, // Step is current if: we're at the right index AND running AND not connecting to E2B isCurrent: (index === metadata.numberOfSteps && isRunning && !isConnectingToE2B) || (index === 0 && metadata.numberOfSteps === 0 && isRunning && !isConnectingToE2B), isSelected: selectedStepIndex === index, })); // Handle step click const handleStepClick = (stepIndex: number, isCompleted: boolean, isCurrent: boolean) => { if (isCompleted) { setSelectedStepIndex(stepIndex); } else if (isCurrent) { // Clicking on the current step (with animation) goes back to live mode setSelectedStepIndex(null); } }; // Handle final step click (goes to live mode showing the final status) const handleFinalStepClick = () => { setSelectedStepIndex(null); }; // Auto-scroll to current step while running useEffect(() => { if (timelineRef.current && isRunning) { // Only auto-scroll while running, not when finished const currentStepElement = timelineRef.current.querySelector(`[data-step="${metadata.numberOfSteps}"]`); if (currentStepElement) { currentStepElement.scrollIntoView({ behavior: 'smooth', inline: 'center', block: 'nearest' }); } } }, [metadata.numberOfSteps, isRunning]); return ( {/* Header with step count */} Timeline {selectedStepIndex !== null && ( - Viewing step {selectedStepIndex + 1} )} {selectedStepIndex !== null && ( )} {/* Horizontal scrollable step indicators */} theme.palette.mode === 'dark' ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.3)', zIndex: 0, pointerEvents: 'none', }, }} > {/* Connection indicator (step 0) */} {showConnectionIndicator && ( {/* White circle background to hide the line */} {/* White background to hide the line */} {/* Connection icon */} {isConnectingToE2B ? ( ) : ( )} {/* Connection label */} {isConnectingToE2B ? 'Connecting' : 'Connected'} )} {/* Render steps and insert final step at the right position */} {steps.map((step, index) => ( handleStepClick(step.stepIndex, step.isCompleted, step.isCurrent)} sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 0.75, minWidth: 40, flexShrink: 0, position: 'relative', zIndex: 1, cursor: (step.isCompleted || step.isCurrent) ? 'pointer' : 'default', '&:hover': (step.isCompleted || step.isCurrent) ? { '& .step-dot': { transform: 'scale(1.15)', }, } : {}, }} > {/* White circle background to hide the line */} {/* White background to hide the line */} {/* Step dot */} {step.isCurrent ? ( ) : ( theme.palette.mode === 'dark' ? 'grey.800' : 'grey.300', // Light grey for future steps transition: 'all 0.2s ease', boxShadow: step.isCompleted || step.isSelected ? step.isSelected ? '0 0 8px rgba(255, 167, 38, 0.5)' : '0 2px 4px rgba(0,0,0,0.1)' : 'none', }} /> {/* White dot for selected step */} {step.isSelected && ( )} )} {/* Step number - show for all steps */} theme.palette.mode === 'dark' ? 'grey.700' : 'grey.400'), whiteSpace: 'nowrap', lineHeight: 1, }} > {step.stepNumber} {/* Insert final step indicator right after the last completed step */} {finalStep && step.stepNumber === metadata.numberOfSteps && ( {/* White circle background to hide the line */} {/* White background to hide the line */} {/* Final step icon */} {finalStep.type === 'success' ? ( ) : finalStep.type === 'stopped' ? ( ) : finalStep.type === 'max_steps_reached' ? ( ) : finalStep.type === 'sandbox_timeout' ? ( ) : ( )} {/* Final step label */} {finalStep.type === 'success' ? 'End' : finalStep.type === 'stopped' ? 'Stopped' : finalStep.type === 'max_steps_reached' ? 'Max Steps' : finalStep.type === 'sandbox_timeout' ? 'Timeout' : 'Failed'} )} ))} ); };