tfrere's picture
tfrere HF Staff
Front update (#5)
67b042f
raw
history blame
11.4 kB
import React from 'react';
import { Box, Typography, Button, Divider, Alert, Paper } from '@mui/material';
import CheckIcon from '@mui/icons-material/Check';
import CloseIcon from '@mui/icons-material/Close';
import AddIcon from '@mui/icons-material/Add';
import SmartToyIcon from '@mui/icons-material/SmartToy';
import AssignmentIcon from '@mui/icons-material/Assignment';
import ChatBubbleOutlineIcon from '@mui/icons-material/ChatBubbleOutline';
import AccessTimeIcon from '@mui/icons-material/AccessTime';
import InputIcon from '@mui/icons-material/Input';
import OutputIcon from '@mui/icons-material/Output';
import FormatListNumberedIcon from '@mui/icons-material/FormatListNumbered';
import { FinalStep, AgentTrace, AgentStep } from '@/types/agent';
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<CompletionViewProps> = ({
finalStep,
trace,
steps,
finalAnswer,
isGenerating,
gifError,
onGenerateGif,
onDownloadJson,
onBackToHome,
}) => {
const isSuccess = finalStep.type === 'success';
const statusColor = isSuccess ? 'success.main' : 'error.main';
// Format model name for display
const formatModelName = (modelId: string) => {
const parts = modelId.split('/');
return parts.length > 1 ? parts[1] : modelId;
};
return (
<Box
sx={{
width: '100%',
maxWidth: 600,
mx: 'auto',
p: 2,
display: 'flex',
flexDirection: 'column',
gap: 1.5,
}}
>
{/* Status Header - Compact */}
<Box sx={{ textAlign: 'center', mb: 0.5 }}>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 1.5, mb: 0.75 }}>
<Box
sx={{
width: 40,
height: 40,
borderRadius: '50%',
backgroundColor: statusColor,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
boxShadow: (theme) =>
isSuccess
? `0 2px 8px ${theme.palette.mode === 'dark' ? 'rgba(102, 187, 106, 0.3)' : 'rgba(102, 187, 106, 0.2)'}`
: `0 2px 8px ${theme.palette.mode === 'dark' ? 'rgba(244, 67, 54, 0.3)' : 'rgba(244, 67, 54, 0.2)'}`,
}}
>
{isSuccess ? (
<CheckIcon sx={{ fontSize: 24, color: 'white' }} />
) : (
<CloseIcon sx={{ fontSize: 24, color: 'white' }} />
)}
</Box>
<Typography
variant="h6"
sx={{
fontWeight: 700,
color: statusColor,
fontSize: '1.1rem',
letterSpacing: '-0.5px',
}}
>
{isSuccess ? 'Task Completed' : 'Task Failed'}
</Typography>
</Box>
</Box>
{/* Single Report Box - Task + Agent + Response + Metrics */}
<Paper
elevation={0}
sx={{
p: 2.5,
backgroundColor: (theme) => 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 && (
<Box sx={{ mb: 2 }}>
<Box sx={{ display: 'flex', alignItems: 'flex-start', gap: 1.5 }}>
<AssignmentIcon sx={{ fontSize: 18, color: 'text.secondary', mt: 0.25, flexShrink: 0 }} />
<Box sx={{ flex: 1, minWidth: 0 }}>
<Typography
variant="caption"
sx={{
fontWeight: 700,
color: 'text.secondary',
fontSize: '0.7rem',
textTransform: 'uppercase',
letterSpacing: '0.5px',
display: 'block',
mb: 0.5,
}}
>
Task
</Typography>
<Typography
variant="body2"
sx={{
color: 'text.primary',
fontWeight: 700,
lineHeight: 1.5,
fontSize: '0.85rem',
}}
>
{trace.instruction}
</Typography>
</Box>
</Box>
</Box>
)}
{/* Agent Response */}
{finalAnswer && (
<Box sx={{ mb: 2 }}>
<Box sx={{ display: 'flex', alignItems: 'flex-start', gap: 1.5 }}>
<ChatBubbleOutlineIcon
sx={{
fontSize: 18,
color: 'text.secondary',
mt: 0.25,
flexShrink: 0
}}
/>
<Box sx={{ flex: 1, minWidth: 0 }}>
<Typography
variant="caption"
sx={{
fontWeight: 700,
color: 'text.secondary',
fontSize: '0.7rem',
textTransform: 'uppercase',
letterSpacing: '0.5px',
display: 'block',
mb: 0.75,
}}
>
Agent Response
</Typography>
<Typography
variant="body2"
sx={{
color: 'text.primary',
lineHeight: 1.5,
fontSize: '0.85rem',
whiteSpace: 'pre-wrap',
wordBreak: 'break-word',
}}
>
{finalAnswer}
</Typography>
</Box>
</Box>
</Box>
)}
{/* Divider before metrics */}
<Divider sx={{ my: 2 }} />
{/* Metrics */}
<Box
sx={{
display: 'flex',
alignItems: 'center',
gap: 1.5,
flexWrap: 'wrap',
justifyContent: 'center',
}}
>
{/* Agent */}
{trace?.modelId && (
<>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
<SmartToyIcon sx={{ fontSize: '0.85rem', color: 'primary.main' }} />
<Typography
variant="caption"
sx={{
color: 'text.primary',
fontFamily: 'monospace',
fontSize: '0.75rem',
fontWeight: 700,
}}
>
{formatModelName(trace.modelId)}
</Typography>
</Box>
{/* Divider */}
<Box sx={{ width: '1px', height: 16, backgroundColor: 'divider' }} />
</>
)}
{/* Steps Count */}
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
<FormatListNumberedIcon sx={{ fontSize: '0.85rem', color: 'primary.main' }} />
<Typography
variant="caption"
sx={{
fontSize: '0.75rem',
fontWeight: 700,
color: 'text.primary',
mr: 0.5,
}}
>
{finalStep.metadata.numberOfSteps}
</Typography>
<Typography
variant="caption"
sx={{
fontSize: '0.7rem',
fontWeight: 400,
color: 'text.secondary',
}}
>
{finalStep.metadata.numberOfSteps === 1 ? 'Step' : 'Steps'}
</Typography>
</Box>
{/* Divider */}
<Box sx={{ width: '1px', height: 16, backgroundColor: 'divider' }} />
{/* Duration */}
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
<AccessTimeIcon sx={{ fontSize: '0.85rem', color: 'primary.main' }} />
<Typography
variant="caption"
sx={{
fontSize: '0.75rem',
fontWeight: 700,
color: 'text.primary',
}}
>
{finalStep.metadata.duration.toFixed(1)}s
</Typography>
</Box>
{/* Divider */}
<Box sx={{ width: '1px', height: 16, backgroundColor: 'divider' }} />
{/* Input Tokens */}
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
<InputIcon sx={{ fontSize: '0.85rem', color: 'primary.main' }} />
<Typography
variant="caption"
sx={{
fontSize: '0.75rem',
fontWeight: 700,
color: 'text.primary',
}}
>
{finalStep.metadata.inputTokensUsed.toLocaleString()}
</Typography>
</Box>
{/* Divider */}
<Box sx={{ width: '1px', height: 16, backgroundColor: 'divider' }} />
{/* Output Tokens */}
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
<OutputIcon sx={{ fontSize: '0.85rem', color: 'primary.main' }} />
<Typography
variant="caption"
sx={{
fontSize: '0.75rem',
fontWeight: 700,
color: 'text.primary',
}}
>
{finalStep.metadata.outputTokensUsed.toLocaleString()}
</Typography>
</Box>
</Box>
</Paper>
{/* GIF Error Alert */}
{gifError && (
<Alert severity="error" sx={{ fontSize: '0.72rem', py: 0.5 }}>
{gifError}
</Alert>
)}
{/* Action Buttons */}
<Box
sx={{
display: 'flex',
flexDirection: 'column',
gap: 1.5,
alignItems: 'center',
}}
>
{/* Download buttons */}
<Box
sx={{
display: 'flex',
gap: 1,
justifyContent: 'center',
flexWrap: 'wrap',
}}
>
<DownloadGifButton
isGenerating={isGenerating}
onClick={onGenerateGif}
disabled={!steps || steps.length === 0}
/>
<DownloadJsonButton onClick={onDownloadJson} disabled={!trace} />
</Box>
{/* New Task button - larger and below */}
<Button
variant="contained"
startIcon={<AddIcon sx={{ fontSize: 20 }} />}
onClick={onBackToHome}
color="primary"
sx={{
textTransform: 'none',
fontWeight: 700,
fontSize: '0.9rem',
px: 3,
py: 1,
boxShadow: 2,
minWidth: 200,
'&:hover': {
boxShadow: 4,
},
}}
>
New Task
</Button>
</Box>
</Box>
);
};