import React, { memo, useMemo, useCallback } from 'react'; import { getToolConfig } from './configs/toolConfigs'; import { OneLineDisplay, CollapsibleDisplay, DiffViewer, MarkdownContent, FileListContent, TodoListContent, TaskListContent, TextContent } from './components'; import type { Project } from '../../../types/app'; type DiffLine = { type: string; content: string; lineNum: number; }; interface ToolRendererProps { toolName: string; toolInput: any; toolResult?: any; toolId?: string; mode: 'input' | 'result'; onFileOpen?: (filePath: string, diffInfo?: any) => void; createDiff?: (oldStr: string, newStr: string) => DiffLine[]; selectedProject?: Project | null; autoExpandTools?: boolean; showRawParameters?: boolean; rawToolInput?: string; } function getToolCategory(toolName: string): string { if (['Edit', 'Write', 'ApplyPatch'].includes(toolName)) return 'edit'; if (['Grep', 'Glob'].includes(toolName)) return 'search'; if (toolName === 'Bash') return 'bash'; if (['TodoWrite', 'TodoRead'].includes(toolName)) return 'todo'; if (['TaskCreate', 'TaskUpdate', 'TaskList', 'TaskGet'].includes(toolName)) return 'task'; if (toolName === 'Task') return 'agent'; // Subagent task if (toolName === 'exit_plan_mode' || toolName === 'ExitPlanMode') return 'plan'; return 'default'; } /** * Main tool renderer router * Routes to OneLineDisplay or CollapsibleDisplay based on tool config */ export const ToolRenderer: React.FC = memo(({ toolName, toolInput, toolResult, toolId, mode, onFileOpen, createDiff, selectedProject, autoExpandTools = false, showRawParameters = false, rawToolInput }) => { const config = getToolConfig(toolName); const displayConfig: any = mode === 'input' ? config.input : config.result; if (!displayConfig) return null; const parsedData = useMemo(() => { try { const rawData = mode === 'input' ? toolInput : toolResult; return typeof rawData === 'string' ? JSON.parse(rawData) : rawData; } catch { return mode === 'input' ? toolInput : toolResult; } }, [mode, toolInput, toolResult]); const handleAction = useCallback(() => { if (displayConfig.action === 'open-file' && onFileOpen) { const value = displayConfig.getValue?.(parsedData) || ''; onFileOpen(value); } }, [displayConfig, parsedData, onFileOpen]); if (displayConfig.type === 'one-line') { const value = displayConfig.getValue?.(parsedData) || ''; const secondary = displayConfig.getSecondary?.(parsedData); return ( ); } if (displayConfig.type === 'collapsible') { const title = typeof displayConfig.title === 'function' ? displayConfig.title(parsedData) : displayConfig.title || 'Details'; const defaultOpen = displayConfig.defaultOpen !== undefined ? displayConfig.defaultOpen : autoExpandTools; const contentProps = displayConfig.getContentProps?.(parsedData, { selectedProject, createDiff, onFileOpen }) || {}; // Build the content component based on contentType let contentComponent: React.ReactNode = null; switch (displayConfig.contentType) { case 'diff': if (createDiff) { contentComponent = ( onFileOpen?.(contentProps.filePath)} /> ); } break; case 'markdown': contentComponent = ; break; case 'file-list': contentComponent = ( ); break; case 'todo-list': if (contentProps.todos?.length > 0) { contentComponent = ( ); } break; case 'task': contentComponent = ; break; case 'text': contentComponent = ( ); break; case 'success-message': { const msg = displayConfig.getMessage?.(parsedData) || 'Success'; contentComponent = (
{msg}
); break; } } // For edit tools, make the title (filename) clickable to open the file const handleTitleClick = (toolName === 'Edit' || toolName === 'Write' || toolName === 'ApplyPatch') && contentProps.filePath && onFileOpen ? () => onFileOpen(contentProps.filePath) : undefined; return ( {contentComponent} ); } return null; }); ToolRenderer.displayName = 'ToolRenderer';