import React, { useState } from "react"; import { FileCode, FileSymlink, FolderPlus, FileX, Replace, CheckCircle, AlertTriangle, ExternalLink, CircleDashed, Code, Eye, FileSpreadsheet } from "lucide-react"; import { ToolViewProps } from "./types"; import { extractFilePath, extractFileContent, getFileType, formatTimestamp, getToolTitle } from "./utils"; import { GenericToolView } from "./GenericToolView"; import { MarkdownRenderer, processUnicodeContent } from "@/components/file-renderers/markdown-renderer"; import { CsvRenderer } from "@/components/file-renderers/csv-renderer"; import { cn } from "@/lib/utils"; import { useTheme } from "next-themes"; import { CodeBlockCode } from "@/components/ui/code-block"; import { constructHtmlPreviewUrl } from "@/lib/utils/url"; // Type for operation type type FileOperation = "create" | "rewrite" | "delete"; // Map file extensions to language names for syntax highlighting const getLanguageFromFileName = (fileName: string): string => { const extension = fileName.split('.').pop()?.toLowerCase() || ''; // Map of file extensions to language names for syntax highlighting const extensionMap: Record = { // Web languages 'html': 'html', 'htm': 'html', 'css': 'css', 'scss': 'scss', 'sass': 'scss', 'less': 'less', 'js': 'javascript', 'jsx': 'jsx', 'ts': 'typescript', 'tsx': 'tsx', 'json': 'json', 'jsonc': 'json', // Build and config files 'xml': 'xml', 'yml': 'yaml', 'yaml': 'yaml', 'toml': 'toml', 'ini': 'ini', 'env': 'bash', 'gitignore': 'bash', 'dockerignore': 'bash', // Scripting languages 'py': 'python', 'rb': 'ruby', 'php': 'php', 'go': 'go', 'java': 'java', 'kt': 'kotlin', 'c': 'c', 'cpp': 'cpp', 'h': 'c', 'hpp': 'cpp', 'cs': 'csharp', 'swift': 'swift', 'rs': 'rust', // Shell scripts 'sh': 'bash', 'bash': 'bash', 'zsh': 'bash', 'ps1': 'powershell', 'bat': 'batch', 'cmd': 'batch', // Markup languages (excluding markdown which has its own renderer) 'svg': 'svg', 'tex': 'latex', // Data formats 'graphql': 'graphql', 'gql': 'graphql', }; return extensionMap[extension] || 'text'; }; export function FileOperationToolView({ assistantContent, toolContent, assistantTimestamp, toolTimestamp, isSuccess = true, isStreaming = false, name, project }: ToolViewProps) { const { resolvedTheme } = useTheme(); const isDarkTheme = resolvedTheme === 'dark'; // Determine operation type from content or name const getOperationType = (): FileOperation => { // First check tool name if available if (name) { if (name.includes("create")) return "create"; if (name.includes("rewrite")) return "rewrite"; if (name.includes("delete")) return "delete"; } if (!assistantContent) return "create"; // default fallback if (assistantContent.includes("")) return "create"; if (assistantContent.includes("")) return "rewrite"; if (assistantContent.includes("delete-file") || assistantContent.includes("")) return "delete"; // Check for tool names as a fallback if (assistantContent.toLowerCase().includes("create file")) return "create"; if (assistantContent.toLowerCase().includes("rewrite file")) return "rewrite"; if (assistantContent.toLowerCase().includes("delete file")) return "delete"; // Default to create if we can't determine return "create"; }; const operation = getOperationType(); const filePath = extractFilePath(assistantContent); const toolTitle = getToolTitle(name || `file-${operation}`); // Only extract content for create and rewrite operations const fileContent = operation !== "delete" ? extractFileContent(assistantContent, operation === "create" ? 'create-file' : 'full-file-rewrite') : null; // For debugging - show raw content if file path can't be extracted for delete operations const showDebugInfo = !filePath && operation === "delete"; // Process file path - handle potential newlines and clean up const processedFilePath = filePath ? filePath.trim().replace(/\\n/g, '\n').split('\n')[0] : null; // For create and rewrite, prepare content for display const contentLines = fileContent ? fileContent.replace(/\\n/g, '\n').split('\n') : []; const fileName = processedFilePath ? processedFilePath.split('/').pop() || processedFilePath : ''; const fileType = processedFilePath ? getFileType(processedFilePath) : ''; const isMarkdown = fileName.endsWith('.md'); const isHtml = fileName.endsWith('.html'); const isCsv = fileName.endsWith('.csv'); const language = getLanguageFromFileName(fileName); const hasHighlighting = language !== 'text'; // Construct HTML file preview URL if we have a sandbox and the file is HTML const htmlPreviewUrl = (isHtml && project?.sandbox?.sandbox_url && processedFilePath) ? constructHtmlPreviewUrl(project.sandbox.sandbox_url, processedFilePath) : undefined; console.log('HTML Preview URL:', htmlPreviewUrl); // Add state for view mode toggle (code or preview) const [viewMode, setViewMode] = useState<'code' | 'preview'>(isHtml || isMarkdown || isCsv ? 'preview' : 'code'); // Fall back to generic view if file path is missing or if content is missing for non-delete operations if ((!filePath && !showDebugInfo) || (operation !== "delete" && !fileContent)) { return ( ); } // Operation-specific configs const configs = { create: { icon: FolderPlus, successMessage: "File created successfully" }, rewrite: { icon: Replace, successMessage: "File rewritten successfully" }, delete: { icon: FileX, successMessage: "File deleted successfully" } }; const config = configs[operation]; const Icon = config.icon; return (
{/* File Content for create and rewrite operations */} {operation !== "delete" && fileContent && !isStreaming && (
{/* IDE Header */}
{isMarkdown ? : isCsv ? : } {fileName}
{/* View switcher for HTML files */} {isHtml && htmlPreviewUrl && isSuccess && (
)} {/* View switcher for Markdown files */} {isMarkdown && isSuccess && (
)} {/* View switcher for CSV files */} {isCsv && isSuccess && (
)} {hasHighlighting ? language.toUpperCase() : fileType}
{/* File Content (Code View with Syntax Highlighting) */} {viewMode === 'code' || (!isHtml && !isMarkdown && !isCsv) || !isSuccess ? (
{hasHighlighting ? (
{contentLines.map((_, idx) => (
{idx + 1}
))}
) : (
{contentLines.map((line, idx) => (
{idx + 1}
{processUnicodeContent(line) || ' '}
))}
)}
) : null} {/* HTML Preview with iframe */} {isHtml && viewMode === 'preview' && htmlPreviewUrl && isSuccess && (