import { AgentMessage, BatchProgressDetails, BatchItemStatus } from "@vertesia/common"; import { Button, cn, useToast } from "@vertesia/ui/core"; import dayjs from "dayjs"; import { CheckCircle, AlertCircle, ChevronDown, ChevronRight, CopyIcon, Layers, Loader2, } from "lucide-react"; import { useState, memo } from "react"; import { PulsatingCircle } from "../AnimatedThinkingDots"; import { useUITranslation } from "../../../../i18n/index.js"; export interface BatchProgressPanelProps { message: AgentMessage; batchData: BatchProgressDetails; isRunning?: boolean; /** Additional className for the root container */ className?: string; /** Additional className for the header row */ headerClassName?: string; /** Additional className for the sender label */ senderClassName?: string; /** Additional className for the progress bar section */ progressBarClassName?: string; /** Additional className for the expanded item list */ itemListClassName?: string; /** Additional className for individual item rows */ itemClassName?: string; /** Additional className for the collapsed summary */ summaryClassName?: string; } /** className overrides for BatchProgressPanel — subset of BatchProgressPanelProps containing only className props. */ export type BatchProgressPanelClassNames = Partial>; function BatchProgressPanelComponent({ message, batchData, isRunning = false, className, headerClassName, senderClassName, progressBarClassName, itemListClassName, itemClassName, summaryClassName, }: BatchProgressPanelProps) { const [isExpanded, setIsExpanded] = useState(false); const toast = useToast(); const { t } = useUITranslation(); const { tool_name, total, completed, succeeded, failed, items, started_at, completed_at } = batchData; const progress = total > 0 ? (completed / total) * 100 : 0; const hasErrors = failed > 0; const isComplete = completed === total && !isRunning; // Determine overall status const getOverallStatus = () => { if (isRunning || !isComplete) return "running"; if (failed === total) return "error"; if (failed > 0) return "warning"; return "completed"; }; const overallStatus = getOverallStatus(); // Status indicator const renderStatusIndicator = () => { if (isRunning || !isComplete) { return ; } if (overallStatus === "completed") { return ; } if (overallStatus === "error" || overallStatus === "warning") { return ; } return ; }; // Border color based on status const getBorderColor = () => { if (overallStatus === "completed") return "border-l-success"; if (overallStatus === "error") return "border-l-destructive"; if (overallStatus === "warning") return "border-l-attention"; return "border-l-blue-500"; }; // Progress bar color const getProgressColor = () => { if (hasErrors) return "bg-attention"; if (isComplete) return "bg-success"; return "bg-blue-500"; }; const copyToClipboard = () => { const content = JSON.stringify(batchData, null, 2); navigator.clipboard.writeText(content).then(() => { toast({ status: "success", title: t('agent.copiedBatchDetails'), duration: 2000, }); }); }; const duration = completed_at ? completed_at - started_at : Date.now() - started_at; const durationSec = (duration / 1000).toFixed(1); return (
{/* Header */}
setIsExpanded(!isExpanded)} >
{renderStatusIndicator()} {t('agent.batch')} {tool_name} {completed}/{total} {isExpanded ? ( ) : ( )}
{durationSec}s {dayjs(started_at).format("HH:mm:ss")}
{/* Progress bar */}
{succeeded > 0 && ( {succeeded} )} {failed > 0 && ( {failed} )} {isRunning && completed < total && ( {total - completed} )}
{/* Expanded item list */} {isExpanded && items.length > 0 && (
{items.map((item: BatchItemStatus) => (
{/* Status icon */}
{item.status === "success" && ( )} {item.status === "error" && ( )} {item.status === "running" && ( )} {item.status === "pending" && (
)}
{/* Item ID */} {item.id} {/* Message */} {item.message || (item.status === "pending" ? t('agent.waiting') : "")} {/* Duration */} {item.duration_ms !== undefined && ( {(item.duration_ms / 1000).toFixed(1)}s )}
))}
)} {/* Summary message if collapsed and has message */} {!isExpanded && message.message && (
{message.message}
)}
); } // Memoize to prevent unnecessary re-renders const BatchProgressPanel = memo(BatchProgressPanelComponent, (prevProps, nextProps) => { if (prevProps.isRunning !== nextProps.isRunning) return false; if (prevProps.batchData.completed !== nextProps.batchData.completed) return false; if (prevProps.batchData.succeeded !== nextProps.batchData.succeeded) return false; if (prevProps.batchData.failed !== nextProps.batchData.failed) return false; return true; }); export default BatchProgressPanel;