diff --git a/frontend/src/components/thread/tool-call-side-panel.tsx b/frontend/src/components/thread/tool-call-side-panel.tsx index cc255e12..574f85a6 100644 --- a/frontend/src/components/thread/tool-call-side-panel.tsx +++ b/frontend/src/components/thread/tool-call-side-panel.tsx @@ -9,9 +9,7 @@ import { Slider } from "@/components/ui/slider"; import { CommandToolView } from "./tool-views/CommandToolView"; import { StrReplaceToolView } from "./tool-views/StrReplaceToolView"; import { GenericToolView } from "./tool-views/GenericToolView"; -import { CreateFileToolView } from "./tool-views/CreateFileToolView"; -import { FileRewriteToolView } from "./tool-views/FileRewriteToolView"; -import { DeleteFileToolView } from "./tool-views/DeleteFileToolView"; +import { FileOperationToolView } from "./tool-views/FileOperationToolView"; import { BrowserToolView } from "./tool-views/BrowserToolView"; import { WebSearchToolView } from "./tool-views/WebSearchToolView"; import { WebCrawlToolView } from "./tool-views/WebCrawlToolView"; @@ -66,33 +64,32 @@ function getToolView( /> ); case 'create-file': - return ( - - ); case 'full-file-rewrite': - return ( - - ); case 'delete-file': return ( - + ); + case 'browser-navigate': + case 'browser-click': + case 'browser-extract': + case 'browser-fill': + case 'browser-wait': + return ( + ); case 'web-search': @@ -105,7 +102,7 @@ function getToolView( isSuccess={isSuccess} /> ); - case 'crawl-webpage': + case 'web-crawl': return ( - ); - } - - // Split content into lines for line numbering - const contentLines = fileContent.split('\n'); - const fileType = getFileType(filePath); - const fileName = filePath.split('/').pop() || filePath; - const isMarkdown = fileName.endsWith('.md'); - - return ( -
-
-
-
- -
-
-

Create File

-
-
- - {toolContent && ( -
- {isSuccess ? 'Success' : 'Failed'} -
- )} -
- -
- {/* IDE Header */} -
-
- {isMarkdown ? - : - - } - {fileName} -
- - {fileType} - -
- - {/* File Path Bar */} -
-
- - {filePath} -
-
- - {/* IDE Content Area */} -
-
- {contentLines.map((line, idx) => ( -
-
- {idx + 1} -
-
- {line || ' '} -
-
- ))} - {/* Add an empty line at the end */} -
-
-
- - {/* Status Footer */} - {isSuccess ? ( -
- - {fileName} created successfully -
- ) : ( -
- - Failed to create file -
- )} -
- -
- {assistantTimestamp && ( -
Called: {formatTimestamp(assistantTimestamp)}
- )} - {toolTimestamp && ( -
Result: {formatTimestamp(toolTimestamp)}
- )} -
-
- ); -} \ No newline at end of file diff --git a/frontend/src/components/thread/tool-views/DeleteFileToolView.tsx b/frontend/src/components/thread/tool-views/DeleteFileToolView.tsx deleted file mode 100644 index 7fbb2295..00000000 --- a/frontend/src/components/thread/tool-views/DeleteFileToolView.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import React from "react"; -import { FileX, CheckCircle, AlertTriangle } from "lucide-react"; -import { ToolViewProps } from "./types"; -import { extractFilePath, formatTimestamp } from "./utils"; -import { GenericToolView } from "./GenericToolView"; - -export function DeleteFileToolView({ - assistantContent, - toolContent, - assistantTimestamp, - toolTimestamp, - isSuccess = true -}: ToolViewProps) { - const filePath = extractFilePath(assistantContent); - - if (!filePath) { - return ( - - ); - } - - return ( -
-
-
-
- -
-
-

Delete File

-
-
- - {toolContent && ( -
- {isSuccess ? 'Success' : 'Failed'} -
- )} -
- -
-
-
- - File Deleted -
-
- -
-
- {filePath} -

This file has been deleted

-
-
- - {isSuccess && ( -
- - File deleted successfully -
- )} - - {!isSuccess && ( -
- - Failed to delete file -
- )} -
- -
- {assistantTimestamp && ( -
Called: {formatTimestamp(assistantTimestamp)}
- )} - {toolTimestamp && ( -
Result: {formatTimestamp(toolTimestamp)}
- )} -
-
- ); -} \ No newline at end of file diff --git a/frontend/src/components/thread/tool-views/FileOperationToolView.tsx b/frontend/src/components/thread/tool-views/FileOperationToolView.tsx new file mode 100644 index 00000000..1f9b21c2 --- /dev/null +++ b/frontend/src/components/thread/tool-views/FileOperationToolView.tsx @@ -0,0 +1,277 @@ +import React from "react"; +import { FileCode, FileSymlink, FolderPlus, FileX, Replace, CheckCircle, AlertTriangle } from "lucide-react"; +import { ToolViewProps } from "./types"; +import { extractFilePath, extractFileContent, getFileType, formatTimestamp } from "./utils"; +import { GenericToolView } from "./GenericToolView"; + +// Type for operation type +type FileOperation = "create" | "rewrite" | "delete"; + +export function FileOperationToolView({ + assistantContent, + toolContent, + assistantTimestamp, + toolTimestamp, + isSuccess = true, + name +}: ToolViewProps & { name?: string }) { + // 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); + + // 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"; + + // 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: { + title: "Create File", + icon: FolderPlus, + color: "text-blue-500", + bgColor: "bg-blue-50", + successMessage: "File created successfully" + }, + rewrite: { + title: "Rewrite File", + icon: Replace, + color: "text-amber-500", + bgColor: "bg-amber-50", + successMessage: "File rewritten successfully" + }, + delete: { + title: "Delete File", + icon: FileX, + color: "text-red-500", + bgColor: "bg-red-50", + successMessage: "File deleted successfully" + } + }; + + const config = configs[operation]; + + // 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 Icon = config.icon; + + return ( +
+ {/* Header */} +
+
+
+ +
+
+

{config.title}

+

{processedFilePath}

+
+
+ + {toolContent && ( +
+ {isSuccess ? 'Success' : 'Failed'} +
+ )} +
+ + {/* File Content for create and rewrite operations */} + {operation !== "delete" && fileContent && ( +
+ {/* IDE Header */} +
+
+ {isMarkdown ? + : + + } + {fileName} +
+ + {fileType} + +
+ + {/* File Content */} +
+
+ {contentLines.map((line, idx) => ( +
+
+ {idx + 1} +
+
+ {line || ' '} +
+
+ ))} +
+
+
+
+ )} + + {/* Debug info for delete operations with missing file path */} + {showDebugInfo && ( +
+
+

Debug Info: Unable to extract file path

+
+
+

Raw Assistant Content:

+
+              {assistantContent || "No content"}
+            
+
+
+ )} + + {/* Delete view with unknown path */} + {operation === "delete" && !processedFilePath && ( +
+
+
+ +
+

File Deleted

+
+

Unknown file path

+
+

A file has been deleted but the path could not be determined

+
+ + {/* Status footer */} +
+
+ {isSuccess ? ( + + ) : ( + + )} + + {isSuccess ? config.successMessage : `Failed to delete file`} + +
+
+
+ )} + + {/* Delete view */} + {operation === "delete" && processedFilePath && ( +
+
+
+ +
+

File Deleted

+
+ {processedFilePath} +
+

This file has been permanently removed

+
+ + {/* Status footer */} +
+
+ {isSuccess ? ( + + ) : ( + + )} + + {isSuccess ? config.successMessage : `Failed to delete file`} + +
+
+
+ )} + + {/* Status footer - only show for non-delete operations as delete has its own */} + {operation !== "delete" && ( +
+
+ {isSuccess ? ( + + ) : ( + + )} + + {isSuccess ? config.successMessage : `Failed to ${operation} file`} + +
+
+ )} + + {/* Timestamps */} +
+ {assistantTimestamp && ( +
+ Requested: + {formatTimestamp(assistantTimestamp)} +
+ )} + {toolTimestamp && ( +
+ Completed: + {formatTimestamp(toolTimestamp)} +
+ )} +
+
+ ); +} \ No newline at end of file diff --git a/frontend/src/components/thread/tool-views/FileRewriteToolView.tsx b/frontend/src/components/thread/tool-views/FileRewriteToolView.tsx deleted file mode 100644 index 591631da..00000000 --- a/frontend/src/components/thread/tool-views/FileRewriteToolView.tsx +++ /dev/null @@ -1,122 +0,0 @@ -import React from "react"; -import { FileCode, FileSymlink, Replace, CheckCircle, AlertTriangle } from "lucide-react"; -import { ToolViewProps } from "./types"; -import { extractFilePath, extractFileContent, getFileType, formatTimestamp } from "./utils"; -import { GenericToolView } from "./GenericToolView"; - -export function FileRewriteToolView({ - assistantContent, - toolContent, - assistantTimestamp, - toolTimestamp, - isSuccess = true -}: ToolViewProps) { - const filePath = extractFilePath(assistantContent); - const fileContent = extractFileContent(assistantContent, 'full-file-rewrite'); - - if (!filePath || !fileContent) { - return ( - - ); - } - - // Split content into lines for line numbering - const contentLines = fileContent.split('\n'); - const fileType = getFileType(filePath); - const fileName = filePath.split('/').pop() || filePath; - const isMarkdown = fileName.endsWith('.md'); - - return ( -
-
-
-
- -
-
-

File Rewrite

-
-
- - {toolContent && ( -
- {isSuccess ? 'Success' : 'Failed'} -
- )} -
- -
- {/* IDE Header */} -
-
- {isMarkdown ? - : - - } - {fileName} -
- - {fileType} - -
- - {/* File Path Bar */} -
-
- - {filePath} -
-
- - {/* IDE Content Area */} -
-
- {contentLines.map((line, idx) => ( -
-
- {idx + 1} -
-
- {line || ' '} -
-
- ))} - {/* Add an empty line at the end */} -
-
-
- - {/* Status Footer */} - {isSuccess ? ( -
- - {fileName} rewritten successfully -
- ) : ( -
- - Failed to rewrite file -
- )} -
- -
- {assistantTimestamp && ( -
Called: {formatTimestamp(assistantTimestamp)}
- )} - {toolTimestamp && ( -
Result: {formatTimestamp(toolTimestamp)}
- )} -
-
- ); -} \ No newline at end of file diff --git a/frontend/src/components/thread/tool-views/utils.ts b/frontend/src/components/thread/tool-views/utils.ts index bf1d5038..70388bf5 100644 --- a/frontend/src/components/thread/tool-views/utils.ts +++ b/frontend/src/components/thread/tool-views/utils.ts @@ -85,16 +85,53 @@ export function extractFilePath(content: string | undefined): string | null { } // Look for file_path in different formats - const filePathMatch = content.match(/file_path=["']([\s\S]*?)["']/); - if (filePathMatch) return filePathMatch[1]; + const filePathMatch = content.match(/file_path=["']([\s\S]*?)["']/i) || + content.match(/target_file=["']([\s\S]*?)["']/i) || + content.match(/path=["']([\s\S]*?)["']/i); + if (filePathMatch) { + const path = filePathMatch[1].trim(); + // Handle newlines and return first line if multiple lines + return cleanFilePath(path); + } // Look for file_path in XML-like tags - const xmlFilePathMatch = content.match(/]*file_path=["']([\s\S]*?)["']/i) || + content.match(/]*>([^<]+)<\/delete-file>/i); + if (xmlFilePathMatch) { + return cleanFilePath(xmlFilePathMatch[1]); + } + + // Look for file paths in delete operations in particular + if (content.toLowerCase().includes('delete') || content.includes('delete-file')) { + // Look for patterns like "Deleting file: path/to/file.txt" + const deletePathMatch = content.match(/(?:delete|remove|deleting)\s+(?:file|the file)?:?\s+["']?([\w\-./\\]+\.\w+)["']?/i); + if (deletePathMatch) return cleanFilePath(deletePathMatch[1]); + + // Look for isolated file paths with extensions + const fileMatch = content.match(/["']?([\w\-./\\]+\.\w+)["']?/); + if (fileMatch) return cleanFilePath(fileMatch[1]); + } return null; } +// Helper to clean and process a file path string, handling escaped chars +function cleanFilePath(path: string): string { + if (!path) return path; + + // Handle escaped newlines and other escaped characters + return path + .replace(/\\n/g, '\n') // Replace \n with actual newlines + .replace(/\\t/g, '\t') // Replace \t with actual tabs + .replace(/\\r/g, '') // Remove \r + .replace(/\\\\/g, '\\') // Replace \\ with \ + .replace(/\\"/g, '"') // Replace \" with " + .replace(/\\'/g, "'") // Replace \' with ' + .split('\n')[0] // Take only the first line if multiline + .trim(); // Trim whitespace +} + // Helper to extract str-replace old and new strings export function extractStrReplaceContent(content: string | undefined): { oldStr: string | null, newStr: string | null } { if (!content) return { oldStr: null, newStr: null }; @@ -114,7 +151,26 @@ export function extractFileContent(content: string | undefined, toolType: 'creat const tagName = toolType === 'create-file' ? 'create-file' : 'full-file-rewrite'; const contentMatch = content.match(new RegExp(`<${tagName}[^>]*>([\\s\\S]*?)<\\/${tagName}>`, 'i')); - return contentMatch ? contentMatch[1] : null; + + if (contentMatch && contentMatch[1]) { + return processFileContent(contentMatch[1]); + } + + return null; +} + +// Helper to process and clean file content +function processFileContent(content: string): string { + if (!content) return content; + + // Handle escaped characters + return content + .replace(/\\n/g, '\n') // Replace \n with actual newlines + .replace(/\\t/g, '\t') // Replace \t with actual tabs + .replace(/\\r/g, '') // Remove \r + .replace(/\\\\/g, '\\') // Replace \\ with \ + .replace(/\\"/g, '"') // Replace \" with " + .replace(/\\'/g, "'"); // Replace \' with ' } // Helper to determine file type (for syntax highlighting)