import { useGifGenerator } from '@/hooks/useGifGenerator'; import { useJsonExporter } from '@/hooks/useJsonExporter'; import { selectError, selectFinalStep, selectSteps, selectTrace, useAgentStore } from '@/stores/agentStore'; import { AgentStep, AgentTraceMetadata } from '@/types/agent'; import ImageIcon from '@mui/icons-material/Image'; import MonitorIcon from '@mui/icons-material/Monitor'; import PlayCircleIcon from '@mui/icons-material/PlayCircle'; import { Box, Button, CircularProgress, keyframes, Typography } from '@mui/material'; import React from 'react'; import { useNavigate } from 'react-router-dom'; import { CompletionView } from './completionview/CompletionView'; // Animation for live indicator const livePulse = keyframes` 0%, 100% { opacity: 1; transform: scale(1); } 50% { opacity: 0.7; transform: scale(1.2); } `; interface SandboxViewerProps { vncUrl: string; isAgentProcessing?: boolean; metadata?: AgentTraceMetadata; traceStartTime?: Date; selectedStep?: AgentStep | null; // The step to display in time-travel mode isRunning?: boolean; // Is the agent currently running } export const SandboxViewer: React.FC = ({ vncUrl, isAgentProcessing = false, metadata, traceStartTime, selectedStep, isRunning = false }) => { const navigate = useNavigate(); const error = useAgentStore(selectError); const finalStep = useAgentStore(selectFinalStep); const steps = useAgentStore(selectSteps); const trace = useAgentStore(selectTrace); const resetAgent = useAgentStore((state) => state.resetAgent); const setSelectedStepIndex = useAgentStore((state) => state.setSelectedStepIndex); // Get the latest screenshot from steps (for non-VNC mode) const latestScreenshot = steps && steps.length > 0 ? steps[steps.length - 1].image : null; // Hook to generate GIF const { isGenerating, error: gifError, generateAndDownloadGif } = useGifGenerator({ steps: steps || [], traceId: finalStep?.metadata.traceId || '', }); // Hook to export JSON const { downloadTraceAsJson } = useJsonExporter({ trace, steps: steps || [], metadata: finalStep?.metadata || metadata, finalStep, }); // Extract final_answer from the last step, or fallback to last thought const getFinalAnswer = (): string | null => { console.log('🔍 getFinalAnswer - steps:', steps); if (!steps || steps.length === 0) { console.log('❌ No steps available'); return null; } // Try to find final_answer in any step (iterate backwards) for (let i = steps.length - 1; i >= 0; i--) { const step = steps[i]; if (step.actions && Array.isArray(step.actions)) { const finalAnswerAction = step.actions.find( (action) => action.function_name === 'final_answer' ); if (finalAnswerAction) { // Handle both named parameter and positional argument const result = finalAnswerAction?.parameters?.answer || finalAnswerAction?.parameters?.arg_0 || null; console.log('✅ Final answer found in step', i + 1, ':', result); return result; } } } console.log('🔍 No final_answer found, looking for last thought...'); // Fallback: find the last step with a thought (iterate backwards) for (let i = steps.length - 1; i >= 0; i--) { const step = steps[i]; if (step.thought) { console.log('📝 Using thought from step', i + 1, 'as fallback:', step.thought); return step.thought; } } console.log('❌ No final answer or thought found in any step'); return null; }; const finalAnswer = getFinalAnswer(); console.log('🎯 Final answer to display:', finalAnswer); // Determine if we should show success/fail status const showStatus = !isRunning && !selectedStep && finalStep; // Handler to go back to home const handleBackToHome = () => { // Reset frontend state useAgentStore.getState().resetAgent(); // Reload the page to reconnect websocket window.location.href = '/'; }; // Handler to go back to live mode const handleGoLive = () => { setSelectedStepIndex(null); }; return ( {/* Live Badge or Go Live Button */} {vncUrl && !showStatus && ( <> {!selectedStep ? ( // Live Badge when in live mode theme.palette.mode === 'dark' ? 'rgba(0, 0, 0, 0.7)' : 'rgba(255, 255, 255, 0.9)', backdropFilter: 'blur(8px)', borderRadius: 0.75, border: '1px solid', borderColor: 'primary.main', boxShadow: (theme) => theme.palette.mode === 'dark' ? '0 2px 8px rgba(0, 0, 0, 0.4)' : '0 2px 8px rgba(0, 0, 0, 0.1)', }} > Live ) : ( // Go Live Button when viewing a specific step )} )} {showStatus && finalStep ? ( // Show success/fail status when agent has completed ) : selectedStep ? ( // Time-travel mode: Show screenshot of selected step {selectedStep.image ? ( Step screenshot ) : ( No screenshot available This step doesn't have a screenshot )} ) : vncUrl ? ( // Live mode: Show VNC stream