'use client'; import { useState } from 'react'; import { HugeiconsIcon } from "@hugeicons/react"; import type { IconSvgElement } from "@hugeicons/react"; import { File01Icon, FileEditIcon, CommandLineIcon, Search01Icon, Wrench01Icon, ArrowDown01Icon, ArrowRight01Icon, Loading02Icon, CheckmarkCircle02Icon, CancelCircleIcon, } from "@hugeicons/core-free-icons"; import { cn } from '@/lib/utils'; import { CodeBlock } from './CodeBlock'; type ToolStatus = 'running' | 'success' | 'error'; interface ToolCallBlockProps { name: string; input: unknown; result?: string; isError?: boolean; status?: ToolStatus; duration?: number; } // Classify tools by name function getToolCategory(name: string): 'read' | 'write' | 'bash' | 'search' | 'other' { const lower = name.toLowerCase(); if (lower === 'read' || lower === 'readfile' || lower === 'read_file') return 'read'; if (lower === 'write' || lower === 'edit' || lower === 'writefile' || lower === 'write_file' || lower === 'create_file' || lower === 'createfile' || lower === 'notebookedit' || lower === 'notebook_edit') return 'write'; if (lower === 'bash' || lower === 'execute' || lower === 'run' || lower === 'shell' || lower === 'execute_command') return 'bash'; if (lower === 'search' || lower === 'glob' || lower === 'grep' || lower === 'find_files' || lower === 'search_files' || lower === 'websearch' || lower === 'web_search') return 'search'; return 'other'; } function getToolIcon(category: ReturnType): IconSvgElement { switch (category) { case 'read': return File01Icon; case 'write': return FileEditIcon; case 'bash': return CommandLineIcon; case 'search': return Search01Icon; case 'other': return Wrench01Icon; } } function getToolSummary(name: string, input: unknown, category: ReturnType): string { const inp = input as Record | undefined; if (!inp) return name; switch (category) { case 'read': { const path = (inp.file_path || inp.path || inp.filePath || '') as string; return path ? extractFilename(path) : name; } case 'write': { const path = (inp.file_path || inp.path || inp.filePath || '') as string; return path ? extractFilename(path) : name; } case 'bash': { const cmd = (inp.command || inp.cmd || '') as string; if (cmd) { const truncated = cmd.length > 80 ? cmd.slice(0, 77) + '...' : cmd; return truncated; } return name; } case 'search': { const pattern = (inp.pattern || inp.query || inp.glob || '') as string; return pattern ? `"${pattern}"` : name; } default: return name; } } function extractFilename(path: string): string { const parts = path.split('/'); return parts[parts.length - 1] || path; } function getFilePath(input: unknown): string { const inp = input as Record | undefined; if (!inp) return ''; return (inp.file_path || inp.path || inp.filePath || '') as string; } function StatusIndicator({ status }: { status: ToolStatus }) { switch (status) { case 'running': return ( ); case 'success': return ; case 'error': return ; } } // Detect simple diff in Write/Edit tools (old_string/new_string) function renderDiff(input: unknown): React.ReactNode | null { const inp = input as Record | undefined; if (!inp) return null; const oldStr = (inp.old_string ?? inp.oldString ?? '') as string; const newStr = (inp.new_string ?? inp.newString ?? '') as string; if (!oldStr && !newStr) return null; const oldLines = oldStr ? oldStr.split('\n') : []; const newLines = newStr ? newStr.split('\n') : []; return (
{oldLines.length > 0 && oldLines.map((line, i) => (
- {line}
))} {newLines.length > 0 && newLines.map((line, i) => (
+ {line}
))}
); } export function ToolCallBlock({ name, input, result, isError, status = result !== undefined ? (isError ? 'error' : 'success') : 'running', duration, }: ToolCallBlockProps) { const [expanded, setExpanded] = useState(false); const category = getToolCategory(name); const toolIconData = getToolIcon(category); const summary = getToolSummary(name, input, category); const filePath = getFilePath(input); const renderContent = () => { switch (category) { case 'read': { return (
{filePath && (
{filePath}
)} {result && ( )} {!result && status === 'running' && (
Reading file...
)}
); } case 'write': { const diff = renderDiff(input); const inp = input as Record | undefined; const content = (inp?.content || inp?.new_source || inp?.new_string || '') as string; return (
{filePath && (
{filePath}
)} {diff} {!diff && content && ( )} {result && (
{result.slice(0, 500)}
)}
); } case 'bash': { const inp = input as Record | undefined; const command = (inp?.command || inp?.cmd || '') as string; return (
{command && (
$ {command}
)} {result && (
{result.slice(0, 5000)}
)} {!result && status === 'running' && (
Executing...
)}
); } case 'search': { const inp = input as Record | undefined; const pattern = (inp?.pattern || inp?.query || inp?.glob || '') as string; return (
{pattern && (
Pattern: {pattern}
)} {result && (
{result.split('\n').slice(0, 50).map((line, i) => (
{line}
))} {result.split('\n').length > 50 && (
... and {result.split('\n').length - 50} more lines
)}
)}
); } default: { return (
Input
                {JSON.stringify(input, null, 2)}
              
{result && (
Output
                  {result.slice(0, 3000)}
                
)}
); } } }; const statusBorderColor = { running: 'border-blue-500/70', success: 'border-green-500/50', error: 'border-red-500/60', }[status]; const statusBgColor = { running: 'bg-blue-500/[0.03] dark:bg-blue-500/[0.05]', success: 'bg-transparent', error: 'bg-red-500/[0.03] dark:bg-red-500/[0.05]', }[status]; return (
{renderContent()}
); } function guessLanguageFromPath(path: string): string { if (!path) return 'text'; const ext = path.split('.').pop()?.toLowerCase() || ''; const map: Record = { ts: 'typescript', tsx: 'tsx', js: 'javascript', jsx: 'jsx', py: 'python', rb: 'ruby', go: 'go', rs: 'rust', java: 'java', kt: 'kotlin', swift: 'swift', css: 'css', scss: 'scss', html: 'html', json: 'json', yaml: 'yaml', yml: 'yaml', md: 'markdown', sql: 'sql', sh: 'bash', toml: 'toml', xml: 'xml', c: 'c', cpp: 'cpp', h: 'c', }; return map[ext] || 'text'; } function formatDuration(ms: number): string { if (ms < 1000) return `${ms}ms`; return `${(ms / 1000).toFixed(1)}s`; }