import React, { useState } from 'react'; import { FileDiff, CheckCircle, AlertTriangle, ExternalLink, Loader2, Code, Eye, File, Copy, Check, Minus, Plus, } from 'lucide-react'; import { extractFilePath, extractFileContent, extractStreamingFileContent, formatTimestamp, getToolTitle, normalizeContentToString, extractToolData, } from '../utils'; import { MarkdownRenderer, processUnicodeContent, } from '@/components/file-renderers/markdown-renderer'; import { CsvRenderer } from '@/components/file-renderers/csv-renderer'; import { XlsxRenderer } from '@/components/file-renderers/xlsx-renderer'; import { cn } from '@/lib/utils'; import { useTheme } from 'next-themes'; import { CodeBlockCode } from '@/components/ui/code-block'; import { constructHtmlPreviewUrl } from '@/lib/utils/url'; import { Card, CardContent, CardHeader, CardTitle, } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { ScrollArea } from "@/components/ui/scroll-area"; import { extractFileEditData, generateLineDiff, calculateDiffStats, LineDiff, DiffStats, getLanguageFromFileName, getOperationType, getOperationConfigs, getFileIcon, processFilePath, getFileName, getFileExtension, isFileType, hasLanguageHighlighting, splitContentIntoLines, type FileOperation, type OperationConfig, } from './_utils'; import { ToolViewProps } from '../types'; import { GenericToolView } from '../GenericToolView'; import { LoadingState } from '../shared/LoadingState'; import { toast } from 'sonner'; import ReactDiffViewer from 'react-diff-viewer-continued'; const UnifiedDiffView: React.FC<{ oldCode: string; newCode: string }> = ({ oldCode, newCode }) => ( ); const ErrorState: React.FC<{ message?: string }> = ({ message }) => (

Invalid File Edit

{message || "Could not extract the file changes from the tool result."}

); export function FileEditToolView({ name = 'edit-file', assistantContent, toolContent, assistantTimestamp, toolTimestamp, isSuccess = true, isStreaming = false, project, }: ToolViewProps): JSX.Element { const { resolvedTheme } = useTheme(); const isDarkTheme = resolvedTheme === 'dark'; // Add copy functionality state const [isCopyingContent, setIsCopyingContent] = useState(false); // Copy functions const copyToClipboard = async (text: string) => { try { await navigator.clipboard.writeText(text); return true; } catch (err) { console.error('Failed to copy text: ', err); return false; } }; const handleCopyContent = async () => { if (!updatedContent) return; setIsCopyingContent(true); const success = await copyToClipboard(updatedContent); if (success) { toast.success('File content copied to clipboard'); } else { toast.error('Failed to copy file content'); } setTimeout(() => setIsCopyingContent(false), 500); }; const operation = getOperationType(name, assistantContent); const configs = getOperationConfigs(); const config = configs[operation] || configs['edit']; // fallback to edit config const Icon = FileDiff; // Always use FileDiff for edit operations const { filePath, originalContent, updatedContent, actualIsSuccess, actualToolTimestamp, errorMessage, } = extractFileEditData( assistantContent, toolContent, isSuccess, toolTimestamp, assistantTimestamp ); const toolTitle = getToolTitle(name); const processedFilePath = processFilePath(filePath); const fileName = getFileName(processedFilePath); const fileExtension = getFileExtension(fileName); const isMarkdown = isFileType.markdown(fileExtension); const isHtml = isFileType.html(fileExtension); const isCsv = isFileType.csv(fileExtension); const isXlsx = isFileType.xlsx(fileExtension); const language = getLanguageFromFileName(fileName); const hasHighlighting = hasLanguageHighlighting(language); const contentLines = splitContentIntoLines(updatedContent); const htmlPreviewUrl = isHtml && project?.sandbox?.sandbox_url && processedFilePath ? constructHtmlPreviewUrl(project.sandbox.sandbox_url, processedFilePath) : undefined; const FileIcon = getFileIcon(fileName); const lineDiff = originalContent && updatedContent ? generateLineDiff(originalContent, updatedContent) : []; const stats: DiffStats = calculateDiffStats(lineDiff); const shouldShowError = !isStreaming && (!actualIsSuccess || (actualIsSuccess && (originalContent === null || updatedContent === null))); if (!isStreaming && !processedFilePath && !updatedContent) { return ( ); } const renderFilePreview = () => { if (!updatedContent) { return (

No content to preview

); } if (isHtml && htmlPreviewUrl) { return (