import { updateTraceEvaluation } from '@/services/api'; import { useAgentStore } from '@/stores/agentStore'; import { AgentStep, AgentTrace, FinalStep } from '@/types/agent'; import AccessTimeIcon from '@mui/icons-material/AccessTime'; import AddIcon from '@mui/icons-material/Add'; import AssignmentIcon from '@mui/icons-material/Assignment'; import ChatBubbleOutlineIcon from '@mui/icons-material/ChatBubbleOutline'; import CheckIcon from '@mui/icons-material/Check'; import CloseIcon from '@mui/icons-material/Close'; import FormatListNumberedIcon from '@mui/icons-material/FormatListNumbered'; import HourglassEmptyIcon from '@mui/icons-material/HourglassEmpty'; import InputIcon from '@mui/icons-material/Input'; import OutputIcon from '@mui/icons-material/Output'; import SmartToyIcon from '@mui/icons-material/SmartToy'; import StopCircleIcon from '@mui/icons-material/StopCircle'; import ThumbDownIcon from '@mui/icons-material/ThumbDown'; import ThumbUpIcon from '@mui/icons-material/ThumbUp'; import { Alert, Box, Button, Divider, IconButton, Paper, Tooltip, Typography } from '@mui/material'; import React, { useState } from 'react'; import { DownloadGifButton } from './DownloadGifButton'; import { DownloadJsonButton } from './DownloadJsonButton'; interface CompletionViewProps { finalStep: FinalStep; trace?: AgentTrace; steps?: AgentStep[]; finalAnswer?: string | null; isGenerating: boolean; gifError: string | null; onGenerateGif: () => void; onDownloadJson: () => void; onBackToHome: () => void; } /** * Component displaying the completion status (success or failure) of a task */ export const CompletionView: React.FC = ({ finalStep, trace, steps, finalAnswer, isGenerating, gifError, onGenerateGif, onDownloadJson, onBackToHome, }) => { const updateTraceEvaluationInStore = useAgentStore((state) => state.updateTraceEvaluation); const [evaluation, setEvaluation] = useState<'success' | 'failed' | 'not_evaluated'>( finalStep.metadata.user_evaluation || 'not_evaluated' ); const [isVoting, setIsVoting] = useState(false); const handleTraceEvaluation = async (vote: 'success' | 'failed') => { if (isVoting || !trace?.id) return; const newEvaluation = evaluation === vote ? 'not_evaluated' : vote; setIsVoting(true); try { await updateTraceEvaluation(trace.id, newEvaluation); setEvaluation(newEvaluation); // Update the store so the evaluation is reflected in JSON export updateTraceEvaluationInStore(newEvaluation); } catch (error) { console.error('Failed to update trace evaluation:', error); } finally { setIsVoting(false); } }; const getStatusConfig = () => { switch (finalStep.type) { case 'success': return { icon: , title: 'Task Completed Successfully!', color: 'success.main', }; case 'stopped': return { icon: , title: 'Task Stopped', color: 'warning.main', }; case 'max_steps_reached': return { icon: , title: 'Maximum Steps Reached', color: 'warning.main', }; case 'sandbox_timeout': return { icon: , title: 'Max Sandbox Time Reached', color: 'error.main', }; case 'failure': default: return { icon: , title: 'Task Failed (Agent Internal Error)', color: 'error.main', }; } }; const statusConfig = getStatusConfig(); // Format model name for display const formatModelName = (modelId: string) => { const parts = modelId.split('/'); return parts.length > 1 ? parts[1] : modelId; }; return ( {/* Status Header - Compact */} { const rgba = finalStep.type === 'success' ? '102, 187, 106' : (finalStep.type === 'failure' || finalStep.type === 'sandbox_timeout') ? '244, 67, 54' : '255, 152, 0'; return `0 2px 8px ${theme.palette.mode === 'dark' ? `rgba(${rgba}, 0.3)` : `rgba(${rgba}, 0.2)`}`; }, }} > {React.cloneElement(statusConfig.icon, { sx: { fontSize: 24, color: 'white' } })} {statusConfig.title} {/* Single Report Box - Task + Agent + Response + Metrics */} theme.palette.mode === 'dark' ? 'rgba(255,255,255,0.03)' : 'rgba(0,0,0,0.03)', borderRadius: 1.5, border: '1px solid', borderColor: 'divider', }} > {/* Task */} {trace?.instruction && ( Task {trace.instruction} )} {/* Agent Response */} {finalAnswer && ( Agent Response {finalAnswer} )} {/* Trace Evaluation */} Was this task completed successfully? {/* Evaluation buttons */} handleTraceEvaluation('success')} disabled={isVoting} sx={{ padding: '4px', color: evaluation === 'success' ? 'success.main' : 'action.disabled', '&:hover': { color: 'success.main', backgroundColor: (theme) => theme.palette.mode === 'dark' ? 'rgba(102, 187, 106, 0.1)' : 'rgba(102, 187, 106, 0.08)', }, }} > handleTraceEvaluation('failed')} disabled={isVoting} sx={{ padding: '4px', color: evaluation === 'failed' ? 'error.main' : 'action.disabled', '&:hover': { color: 'error.main', backgroundColor: (theme) => theme.palette.mode === 'dark' ? 'rgba(244, 67, 54, 0.1)' : 'rgba(244, 67, 54, 0.08)', }, }} > {/* Divider before metrics */} {/* Metrics */} {/* Agent */} {trace?.modelId && ( <> {formatModelName(trace.modelId)} {/* Divider */} )} {/* Steps Count */} {finalStep.metadata.numberOfSteps} {finalStep.metadata.numberOfSteps === 1 ? 'Step' : 'Steps'} {/* Divider */} {/* Duration */} {finalStep.metadata.duration.toFixed(1)}s {/* Divider */} {/* Input Tokens */} {finalStep.metadata.inputTokensUsed.toLocaleString()} {/* Divider */} {/* Output Tokens */} {finalStep.metadata.outputTokensUsed.toLocaleString()} {/* GIF Error Alert */} {gifError && ( {gifError} )} {/* Action Buttons */} {/* Download buttons */} {/* New Task button - larger and below */} ); };