import React, { useState, useEffect } from 'react'; import { CheckCircle2, CheckCircle, AlertTriangle, Loader2, ListChecks, Sparkles, Trophy, Paperclip, ExternalLink, } from 'lucide-react'; import { ToolViewProps } from './types'; import { formatTimestamp, getToolTitle, normalizeContentToString, extractToolData, getFileIconAndColor, } from './utils'; import { extractCompleteData } from './complete-tool/_utils'; import { cn } from '@/lib/utils'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; import { ScrollArea } from "@/components/ui/scroll-area"; import { Progress } from '@/components/ui/progress'; import { Markdown } from '@/components/ui/markdown'; import { FileAttachment } from '../file-attachment'; interface CompleteContent { summary?: string; result?: string | null; tasksCompleted?: string[]; finalOutput?: string; attachments?: string[]; } interface CompleteToolViewProps extends ToolViewProps { onFileClick?: (filePath: string) => void; } export function CompleteToolView({ name = 'complete', assistantContent, toolContent, assistantTimestamp, toolTimestamp, isSuccess = true, isStreaming = false, onFileClick, project, }: CompleteToolViewProps) { const [completeData, setCompleteData] = useState({}); const [progress, setProgress] = useState(0); const { text, attachments, status, actualIsSuccess, actualToolTimestamp, actualAssistantTimestamp } = extractCompleteData( assistantContent, toolContent, isSuccess, toolTimestamp, assistantTimestamp ); useEffect(() => { if (assistantContent) { try { const contentStr = normalizeContentToString(assistantContent); if (!contentStr) return; let cleanContent = contentStr .replace(/[\s\S]*?<\/function_calls>/g, '') .replace(//g, '') .trim(); const completeMatch = cleanContent.match(/]*>([^<]*)<\/complete>/); if (completeMatch) { setCompleteData(prev => ({ ...prev, summary: completeMatch[1].trim() })); } else if (cleanContent) { setCompleteData(prev => ({ ...prev, summary: cleanContent })); } const attachmentsMatch = contentStr.match(/attachments=["']([^"']*)["']/i); if (attachmentsMatch) { const attachments = attachmentsMatch[1].split(',').map(a => a.trim()).filter(a => a.length > 0); setCompleteData(prev => ({ ...prev, attachments })); } const taskMatches = cleanContent.match(/- ([^\n]+)/g); if (taskMatches) { const tasks = taskMatches.map(task => task.replace('- ', '').trim()); setCompleteData(prev => ({ ...prev, tasksCompleted: tasks })); } } catch (e) { console.error('Error parsing complete content:', e); } } }, [assistantContent]); useEffect(() => { if (toolContent && !isStreaming) { try { const contentStr = normalizeContentToString(toolContent); if (!contentStr) return; const toolResultMatch = contentStr.match(/ToolResult\([^)]*output=['"]([^'"]+)['"]/); if (toolResultMatch) { setCompleteData(prev => ({ ...prev, result: toolResultMatch[1] })); } else { setCompleteData(prev => ({ ...prev, result: contentStr })); } } catch (e) { console.error('Error parsing tool response:', e); } } }, [toolContent, isStreaming]); useEffect(() => { if (isStreaming) { const timer = setInterval(() => { setProgress((prevProgress) => { if (prevProgress >= 95) { clearInterval(timer); return prevProgress; } return prevProgress + 5; }); }, 300); return () => clearInterval(timer); } else { setProgress(100); } }, [isStreaming]); const isImageFile = (filePath: string): boolean => { const filename = filePath.split('/').pop() || ''; return filename.match(/\.(jpg|jpeg|png|gif|webp|svg|bmp)$/i) !== null; }; const isPreviewableFile = (filePath: string): boolean => { const ext = filePath.split('.').pop()?.toLowerCase() || ''; return ext === 'html' || ext === 'htm' || ext === 'md' || ext === 'markdown' || ext === 'csv' || ext === 'tsv'; }; const toolTitle = getToolTitle(name) || 'Task Complete'; const handleFileClick = (filePath: string) => { if (onFileClick) { onFileClick(filePath); } }; return (
{toolTitle}
{!isStreaming && ( {actualIsSuccess ? ( ) : ( )} {actualIsSuccess ? 'Completed' : 'Failed'} )} {isStreaming && ( Completing )}
{/* Success Animation/Icon - Only show when completed successfully and no text/attachments */} {!isStreaming && actualIsSuccess && !text && !attachments && !completeData.summary && !completeData.tasksCompleted && (
)} {/* Text/Summary Section */} {(text || completeData.summary) && (
{text || completeData.summary}
)} {/* Attachments Section */} {attachments && attachments.length > 0 ? (
Files ({attachments.length})
4 ? "grid-cols-1 sm:grid-cols-2 md:grid-cols-3" : "grid-cols-1 sm:grid-cols-2" )}> {attachments .sort((a, b) => { const aIsImage = isImageFile(a); const bIsImage = isImageFile(b); const aIsPreviewable = isPreviewableFile(a); const bIsPreviewable = isPreviewableFile(b); if (aIsImage && !bIsImage) return -1; if (!aIsImage && bIsImage) return 1; if (aIsPreviewable && !bIsPreviewable) return -1; if (!aIsPreviewable && bIsPreviewable) return 1; return 0; }) .map((attachment, index) => { const isImage = isImageFile(attachment); const isPreviewable = isPreviewableFile(attachment); const shouldSpanFull = (attachments!.length % 2 === 1 && attachments!.length > 1 && index === attachments!.length - 1); return (
); })}
) : completeData.attachments && completeData.attachments.length > 0 ? (
Files ({completeData.attachments.length})
{completeData.attachments.map((attachment, index) => { const { icon: FileIcon, color, bgColor } = getFileIconAndColor(attachment); const fileName = attachment.split('/').pop() || attachment; const filePath = attachment.includes('/') ? attachment.substring(0, attachment.lastIndexOf('/')) : ''; return ( ); })}
) : null} {/* Tasks Completed Section */} {completeData.tasksCompleted && completeData.tasksCompleted.length > 0 && (
Tasks Completed
{completeData.tasksCompleted.map((task, index) => (
{task}
))}
)} {/* Progress Section for Streaming */} {isStreaming && (
Completing task... {progress}%
)} {/* Empty State */} {!text && !attachments && !completeData.summary && !completeData.result && !completeData.attachments && !completeData.tasksCompleted && !isStreaming && (

Task Completed

No additional details provided

)}
{/* Footer */}
Task Completion
{toolTimestamp && !isStreaming ? formatTimestamp(toolTimestamp) : assistantTimestamp ? formatTimestamp(assistantTimestamp) : ''}
); }