mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-06-30 09:02:56 +08:00
fix: remove one-line logic from messagecomponent
This commit is contained in:
@@ -145,112 +145,31 @@ const MessageComponent = memo(({ message, index, prevMessage, createDiff, onFile
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
|
|
||||||
{message.isToolUse && !['Read', 'TodoWrite', 'TodoRead'].includes(message.toolName) ? (
|
|
||||||
(() => {
|
|
||||||
// Minimize Grep and Glob tools since they happen frequently
|
|
||||||
const isSearchTool = ['Grep', 'Glob'].includes(message.toolName);
|
|
||||||
|
|
||||||
if (isSearchTool) {
|
{message.isToolUse ? (
|
||||||
return (
|
<>
|
||||||
<>
|
<div className="flex flex-col">
|
||||||
<div className="group relative bg-gray-50/50 dark:bg-gray-800/30 border-l-2 border-blue-400 dark:border-blue-500 pl-3 py-2 my-2">
|
<div className="flex flex-col">
|
||||||
<div className="flex items-center justify-between gap-3">
|
<Markdown className="prose prose-sm max-w-none dark:prose-invert">{message.displayText}</Markdown>
|
||||||
<div className="flex items-center gap-2 text-xs text-gray-600 dark:text-gray-400 flex-1 min-w-0">
|
|
||||||
<svg className="w-3.5 h-3.5 text-blue-500 dark:text-blue-400 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
|
||||||
</svg>
|
|
||||||
<span className="font-medium flex-shrink-0">{message.toolName}</span>
|
|
||||||
<span className="text-gray-400 dark:text-gray-500 flex-shrink-0">•</span>
|
|
||||||
{message.toolInput && (() => {
|
|
||||||
try {
|
|
||||||
const input = JSON.parse(message.toolInput);
|
|
||||||
return (
|
|
||||||
<span className="font-mono truncate flex-1 min-w-0">
|
|
||||||
{input.pattern && <span>{t('search.pattern')} <span className="text-blue-600 dark:text-blue-400">{input.pattern}</span></span>}
|
|
||||||
{input.path && <span className="ml-2">{t('search.in')} {input.path}</span>}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
})()}
|
|
||||||
</div>
|
|
||||||
{message.toolResult && (
|
|
||||||
<a
|
|
||||||
href={`#tool-result-${message.toolId}`}
|
|
||||||
className="flex-shrink-0 text-xs text-blue-600 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-300 font-medium transition-colors flex items-center gap-1"
|
|
||||||
>
|
|
||||||
<span>{t('tools.searchResults')}</span>
|
|
||||||
<svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
|
||||||
</svg>
|
|
||||||
</a>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Full display for other tools
|
|
||||||
return (
|
|
||||||
<div className="group relative bg-gradient-to-br from-blue-50/50 to-indigo-50/50 dark:from-blue-950/20 dark:to-indigo-950/20 border border-blue-100/30 dark:border-blue-800/30 rounded-lg p-3 mb-2">
|
|
||||||
{/* Decorative gradient overlay */}
|
|
||||||
<div className="absolute inset-0 bg-gradient-to-br from-blue-500/3 to-indigo-500/3 dark:from-blue-400/3 dark:to-indigo-400/3 rounded-lg opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
|
|
||||||
|
|
||||||
<div className="relative flex items-center justify-between mb-3">
|
|
||||||
<div className="flex items-center gap-3">
|
|
||||||
<div className="relative w-8 h-8 bg-gradient-to-br from-blue-500 to-indigo-600 dark:from-blue-400 dark:to-indigo-500 rounded-lg flex items-center justify-center shadow-lg shadow-blue-500/20 dark:shadow-blue-400/20">
|
|
||||||
<svg className="w-4 h-4 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
|
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
|
||||||
</svg>
|
|
||||||
{/* Subtle pulse animation */}
|
|
||||||
<div className="absolute inset-0 rounded-lg bg-blue-500 dark:bg-blue-400 animate-pulse opacity-20"></div>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col">
|
|
||||||
<span className="font-semibold text-gray-900 dark:text-white text-sm">
|
|
||||||
{message.toolName}
|
|
||||||
</span>
|
|
||||||
<span className="text-xs text-gray-500 dark:text-gray-400 font-mono">
|
|
||||||
{message.toolId}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{onShowSettings && (
|
|
||||||
<button
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
onShowSettings();
|
|
||||||
}}
|
|
||||||
className="p-2 rounded-lg hover:bg-white/60 dark:hover:bg-gray-800/60 transition-all duration-200 group/btn backdrop-blur-sm"
|
|
||||||
title={t('tools.settings')}
|
|
||||||
>
|
|
||||||
<svg className="w-4 h-4 text-gray-600 dark:text-gray-400 group-hover/btn:text-blue-600 dark:group-hover/btn:text-blue-400 transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
|
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
{/* All tool input rendering now handled by ToolRenderer */}
|
|
||||||
{message.toolInput && (() => {
|
{message.toolInput && (
|
||||||
// Use new ToolRenderer for all tools (config-driven)
|
<ToolRenderer
|
||||||
return (
|
toolName={message.toolName}
|
||||||
<ToolRenderer
|
toolInput={message.toolInput}
|
||||||
toolName={message.toolName}
|
toolResult={message.toolResult}
|
||||||
toolInput={message.toolInput}
|
toolId={message.toolId}
|
||||||
mode="input"
|
mode="input"
|
||||||
onFileOpen={onFileOpen}
|
onFileOpen={onFileOpen}
|
||||||
createDiff={createDiff}
|
createDiff={createDiff}
|
||||||
selectedProject={selectedProject}
|
selectedProject={selectedProject}
|
||||||
autoExpandTools={autoExpandTools}
|
autoExpandTools={autoExpandTools}
|
||||||
showRawParameters={showRawParameters}
|
showRawParameters={showRawParameters}
|
||||||
rawToolInput={message.toolInput}
|
onShowSettings={onShowSettings}
|
||||||
/>
|
rawToolInput={message.toolInput}
|
||||||
);
|
/>
|
||||||
})()}
|
)}
|
||||||
|
|
||||||
{/* Tool Result Section */}
|
{/* Tool Result Section */}
|
||||||
{message.toolResult && (() => {
|
{message.toolResult && (() => {
|
||||||
@@ -714,9 +633,7 @@ const MessageComponent = memo(({ message, index, prevMessage, createDiff, onFile
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})()}
|
})()}
|
||||||
</div>
|
</>
|
||||||
);
|
|
||||||
})()
|
|
||||||
) : message.isInteractivePrompt ? (
|
) : message.isInteractivePrompt ? (
|
||||||
// Special handling for interactive prompts
|
// Special handling for interactive prompts
|
||||||
<div className="bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-800 rounded-lg p-4">
|
<div className="bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-800 rounded-lg p-4">
|
||||||
@@ -800,84 +717,6 @@ const MessageComponent = memo(({ message, index, prevMessage, createDiff, onFile
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : message.isToolUse && message.toolName === 'Read' ? (
|
|
||||||
// Simple Read tool indicator
|
|
||||||
(() => {
|
|
||||||
try {
|
|
||||||
const input = JSON.parse(message.toolInput);
|
|
||||||
if (input.file_path) {
|
|
||||||
const filename = input.file_path.split('/').pop();
|
|
||||||
return (
|
|
||||||
<div className="bg-gray-50/50 dark:bg-gray-800/30 border-l-2 border-gray-400 dark:border-gray-500 pl-3 py-2 my-2">
|
|
||||||
<div className="flex items-center gap-2 text-xs text-gray-600 dark:text-gray-400">
|
|
||||||
<svg className="w-3.5 h-3.5 text-gray-500 dark:text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253" />
|
|
||||||
</svg>
|
|
||||||
<span className="font-medium">Read</span>
|
|
||||||
<button
|
|
||||||
onClick={() => onFileOpen && onFileOpen(input.file_path)}
|
|
||||||
className="text-blue-600 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-300 font-mono transition-colors"
|
|
||||||
>
|
|
||||||
{filename}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
return (
|
|
||||||
<div className="bg-gray-50/50 dark:bg-gray-800/30 border-l-2 border-gray-400 dark:border-gray-500 pl-3 py-2 my-2">
|
|
||||||
<div className="flex items-center gap-2 text-xs text-gray-600 dark:text-gray-400">
|
|
||||||
<svg className="w-3.5 h-3.5 text-gray-500 dark:text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253" />
|
|
||||||
</svg>
|
|
||||||
<span className="font-medium">Read file</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
})()
|
|
||||||
) : message.isToolUse && message.toolName === 'TodoWrite' ? (
|
|
||||||
// Simple TodoWrite tool indicator with tasks
|
|
||||||
(() => {
|
|
||||||
try {
|
|
||||||
const input = JSON.parse(message.toolInput);
|
|
||||||
if (input.todos && Array.isArray(input.todos)) {
|
|
||||||
return (
|
|
||||||
<div className="bg-gray-50/50 dark:bg-gray-800/30 border-l-2 border-gray-400 dark:border-gray-500 pl-3 py-2 my-2">
|
|
||||||
<div className="flex items-center gap-2 text-xs text-gray-600 dark:text-gray-400 mb-2">
|
|
||||||
<svg className="w-3.5 h-3.5 text-gray-500 dark:text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4" />
|
|
||||||
</svg>
|
|
||||||
<span className="font-medium">Update todo list</span>
|
|
||||||
</div>
|
|
||||||
<TodoList todos={input.todos} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
return (
|
|
||||||
<div className="bg-gray-50/50 dark:bg-gray-800/30 border-l-2 border-gray-400 dark:border-gray-500 pl-3 py-2 my-2">
|
|
||||||
<div className="flex items-center gap-2 text-xs text-gray-600 dark:text-gray-400">
|
|
||||||
<svg className="w-3.5 h-3.5 text-gray-500 dark:text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4" />
|
|
||||||
</svg>
|
|
||||||
<span className="font-medium">Update todo list</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
})()
|
|
||||||
) : message.isToolUse && message.toolName === 'TodoRead' ? (
|
|
||||||
// Simple TodoRead tool indicator
|
|
||||||
<div className="bg-gray-50/50 dark:bg-gray-800/30 border-l-2 border-gray-400 dark:border-gray-500 pl-3 py-2 my-2">
|
|
||||||
<div className="flex items-center gap-2 text-xs text-gray-600 dark:text-gray-400">
|
|
||||||
<svg className="w-3.5 h-3.5 text-gray-500 dark:text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" />
|
|
||||||
</svg>
|
|
||||||
<span className="font-medium">Read todo list</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : message.isThinking ? (
|
) : message.isThinking ? (
|
||||||
/* Thinking messages - collapsible by default */
|
/* Thinking messages - collapsible by default */
|
||||||
<div className="text-sm text-gray-700 dark:text-gray-300">
|
<div className="text-sm text-gray-700 dark:text-gray-300">
|
||||||
|
|||||||
@@ -13,12 +13,12 @@ interface ToolRendererProps {
|
|||||||
toolName: string;
|
toolName: string;
|
||||||
toolInput: any;
|
toolInput: any;
|
||||||
toolResult?: any;
|
toolResult?: any;
|
||||||
|
toolId?: string;
|
||||||
mode: 'input' | 'result';
|
mode: 'input' | 'result';
|
||||||
// Callbacks and helpers
|
|
||||||
onFileOpen?: (filePath: string, diffInfo?: any) => void;
|
onFileOpen?: (filePath: string, diffInfo?: any) => void;
|
||||||
createDiff?: (oldStr: string, newStr: string) => DiffLine[];
|
createDiff?: (oldStr: string, newStr: string) => DiffLine[];
|
||||||
selectedProject?: Project | null;
|
selectedProject?: Project | null;
|
||||||
// Display options
|
onShowSettings?: () => void;
|
||||||
autoExpandTools?: boolean;
|
autoExpandTools?: boolean;
|
||||||
showRawParameters?: boolean;
|
showRawParameters?: boolean;
|
||||||
rawToolInput?: string;
|
rawToolInput?: string;
|
||||||
@@ -32,10 +32,12 @@ export const ToolRenderer: React.FC<ToolRendererProps> = ({
|
|||||||
toolName,
|
toolName,
|
||||||
toolInput,
|
toolInput,
|
||||||
toolResult,
|
toolResult,
|
||||||
|
toolId,
|
||||||
mode,
|
mode,
|
||||||
onFileOpen,
|
onFileOpen,
|
||||||
createDiff,
|
createDiff,
|
||||||
selectedProject,
|
selectedProject,
|
||||||
|
onShowSettings,
|
||||||
autoExpandTools = false,
|
autoExpandTools = false,
|
||||||
showRawParameters = false,
|
showRawParameters = false,
|
||||||
rawToolInput
|
rawToolInput
|
||||||
@@ -45,7 +47,6 @@ export const ToolRenderer: React.FC<ToolRendererProps> = ({
|
|||||||
|
|
||||||
if (!displayConfig) return null;
|
if (!displayConfig) return null;
|
||||||
|
|
||||||
// Parse tool input/result
|
|
||||||
let parsedData: any;
|
let parsedData: any;
|
||||||
try {
|
try {
|
||||||
const rawData = mode === 'input' ? toolInput : toolResult;
|
const rawData = mode === 'input' ? toolInput : toolResult;
|
||||||
@@ -54,9 +55,6 @@ export const ToolRenderer: React.FC<ToolRendererProps> = ({
|
|||||||
parsedData = mode === 'input' ? toolInput : toolResult;
|
parsedData = mode === 'input' ? toolInput : toolResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// ONE-LINE DISPLAY
|
|
||||||
// ============================================================================
|
|
||||||
if (displayConfig.type === 'one-line') {
|
if (displayConfig.type === 'one-line') {
|
||||||
const value = displayConfig.getValue?.(parsedData) || '';
|
const value = displayConfig.getValue?.(parsedData) || '';
|
||||||
const secondary = displayConfig.getSecondary?.(parsedData);
|
const secondary = displayConfig.getSecondary?.(parsedData);
|
||||||
@@ -69,6 +67,9 @@ export const ToolRenderer: React.FC<ToolRendererProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<OneLineDisplay
|
<OneLineDisplay
|
||||||
|
toolName={toolName}
|
||||||
|
toolResult={toolResult}
|
||||||
|
toolId={toolId}
|
||||||
icon={displayConfig.icon}
|
icon={displayConfig.icon}
|
||||||
label={displayConfig.label}
|
label={displayConfig.label}
|
||||||
value={value}
|
value={value}
|
||||||
@@ -76,14 +77,11 @@ export const ToolRenderer: React.FC<ToolRendererProps> = ({
|
|||||||
action={displayConfig.action}
|
action={displayConfig.action}
|
||||||
onAction={handleAction}
|
onAction={handleAction}
|
||||||
colorScheme={displayConfig.colorScheme}
|
colorScheme={displayConfig.colorScheme}
|
||||||
resultId={mode === 'input' ? `tool-result-${toolName}` : undefined}
|
resultId={mode === 'input' ? `tool-result-${toolId}` : undefined}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// COLLAPSIBLE DISPLAY
|
|
||||||
// ============================================================================
|
|
||||||
if (displayConfig.type === 'collapsible') {
|
if (displayConfig.type === 'collapsible') {
|
||||||
const title = typeof displayConfig.title === 'function'
|
const title = typeof displayConfig.title === 'function'
|
||||||
? displayConfig.title(parsedData)
|
? displayConfig.title(parsedData)
|
||||||
@@ -93,14 +91,12 @@ export const ToolRenderer: React.FC<ToolRendererProps> = ({
|
|||||||
? displayConfig.defaultOpen
|
? displayConfig.defaultOpen
|
||||||
: autoExpandTools;
|
: autoExpandTools;
|
||||||
|
|
||||||
// Get content props from config
|
|
||||||
const contentProps = displayConfig.getContentProps?.(parsedData, {
|
const contentProps = displayConfig.getContentProps?.(parsedData, {
|
||||||
selectedProject,
|
selectedProject,
|
||||||
createDiff,
|
createDiff,
|
||||||
onFileOpen
|
onFileOpen
|
||||||
}) || {};
|
}) || {};
|
||||||
|
|
||||||
// Render content based on contentType
|
|
||||||
let contentComponent = null;
|
let contentComponent = null;
|
||||||
|
|
||||||
switch (displayConfig.contentType) {
|
switch (displayConfig.contentType) {
|
||||||
@@ -169,7 +165,6 @@ export const ToolRenderer: React.FC<ToolRendererProps> = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Action button for file operations
|
|
||||||
let actionButton = null;
|
let actionButton = null;
|
||||||
if (displayConfig.actionButton === 'file-button' && contentProps.filePath) {
|
if (displayConfig.actionButton === 'file-button' && contentProps.filePath) {
|
||||||
const handleFileClick = async (e: React.MouseEvent) => {
|
const handleFileClick = async (e: React.MouseEvent) => {
|
||||||
@@ -177,7 +172,6 @@ export const ToolRenderer: React.FC<ToolRendererProps> = ({
|
|||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if (!onFileOpen) return;
|
if (!onFileOpen) return;
|
||||||
|
|
||||||
// For Edit/ApplyPatch tools, fetch current file and reverse-apply the edit
|
|
||||||
if (toolName === 'Edit' || toolName === 'ApplyPatch') {
|
if (toolName === 'Edit' || toolName === 'ApplyPatch') {
|
||||||
try {
|
try {
|
||||||
const { api } = await import('../../../utils/api');
|
const { api } = await import('../../../utils/api');
|
||||||
@@ -202,7 +196,6 @@ export const ToolRenderer: React.FC<ToolRendererProps> = ({
|
|||||||
onFileOpen(contentProps.filePath);
|
onFileOpen(contentProps.filePath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// For Write tool, fetch written file
|
|
||||||
else if (toolName === 'Write') {
|
else if (toolName === 'Write') {
|
||||||
try {
|
try {
|
||||||
const { api } = await import('../../../utils/api');
|
const { api } = await import('../../../utils/api');
|
||||||
@@ -235,6 +228,8 @@ export const ToolRenderer: React.FC<ToolRendererProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<CollapsibleDisplay
|
<CollapsibleDisplay
|
||||||
|
toolName={toolName}
|
||||||
|
toolId={toolId}
|
||||||
title={title}
|
title={title}
|
||||||
defaultOpen={defaultOpen}
|
defaultOpen={defaultOpen}
|
||||||
action={actionButton}
|
action={actionButton}
|
||||||
@@ -248,12 +243,10 @@ export const ToolRenderer: React.FC<ToolRendererProps> = ({
|
|||||||
}}
|
}}
|
||||||
showRawParameters={mode === 'input' && showRawParameters}
|
showRawParameters={mode === 'input' && showRawParameters}
|
||||||
rawContent={rawToolInput}
|
rawContent={rawToolInput}
|
||||||
|
onShowSettings={onShowSettings}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// SPECIAL / HIDDEN
|
|
||||||
// ============================================================================
|
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import { CollapsibleSection } from './CollapsibleSection';
|
|||||||
type ContentType = 'diff' | 'markdown' | 'file-list' | 'todo-list' | 'text';
|
type ContentType = 'diff' | 'markdown' | 'file-list' | 'todo-list' | 'text';
|
||||||
|
|
||||||
interface CollapsibleDisplayProps {
|
interface CollapsibleDisplayProps {
|
||||||
|
toolName: string;
|
||||||
|
toolId?: string;
|
||||||
title: string;
|
title: string;
|
||||||
defaultOpen?: boolean;
|
defaultOpen?: boolean;
|
||||||
action?: React.ReactNode;
|
action?: React.ReactNode;
|
||||||
@@ -12,15 +14,12 @@ interface CollapsibleDisplayProps {
|
|||||||
showRawParameters?: boolean;
|
showRawParameters?: boolean;
|
||||||
rawContent?: string;
|
rawContent?: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
onShowSettings?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Unified collapsible display for complex tool inputs and results
|
|
||||||
* Used by: Edit, Write, Plan, TodoWrite, Grep/Glob (results), etc.
|
|
||||||
*
|
|
||||||
* Content is rendered by specialized components based on contentType
|
|
||||||
*/
|
|
||||||
export const CollapsibleDisplay: React.FC<CollapsibleDisplayProps> = ({
|
export const CollapsibleDisplay: React.FC<CollapsibleDisplayProps> = ({
|
||||||
|
toolName,
|
||||||
|
toolId,
|
||||||
title,
|
title,
|
||||||
defaultOpen = false,
|
defaultOpen = false,
|
||||||
action,
|
action,
|
||||||
@@ -28,65 +27,98 @@ export const CollapsibleDisplay: React.FC<CollapsibleDisplayProps> = ({
|
|||||||
contentProps,
|
contentProps,
|
||||||
showRawParameters = false,
|
showRawParameters = false,
|
||||||
rawContent,
|
rawContent,
|
||||||
className = ''
|
className = '',
|
||||||
|
onShowSettings
|
||||||
}) => {
|
}) => {
|
||||||
// Import content renderers dynamically based on type
|
|
||||||
const renderContent = () => {
|
const renderContent = () => {
|
||||||
switch (contentType) {
|
switch (contentType) {
|
||||||
case 'diff':
|
case 'diff':
|
||||||
// DiffViewer already exists - will be imported by ToolRenderer
|
|
||||||
return contentProps.DiffViewer;
|
return contentProps.DiffViewer;
|
||||||
|
|
||||||
case 'markdown':
|
case 'markdown':
|
||||||
// Markdown component already exists - will be imported by ToolRenderer
|
|
||||||
return contentProps.MarkdownComponent;
|
return contentProps.MarkdownComponent;
|
||||||
|
|
||||||
case 'file-list':
|
case 'file-list':
|
||||||
// FileListContent will be created
|
|
||||||
return contentProps.FileListComponent;
|
return contentProps.FileListComponent;
|
||||||
|
|
||||||
case 'todo-list':
|
case 'todo-list':
|
||||||
// TodoListContent will be created
|
|
||||||
return contentProps.TodoListComponent;
|
return contentProps.TodoListComponent;
|
||||||
|
|
||||||
case 'text':
|
case 'text':
|
||||||
// TextContent will be created
|
|
||||||
return contentProps.TextComponent;
|
return contentProps.TextComponent;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return <div className="text-gray-500">Unknown content type: {contentType}</div>;
|
return <div className="text-gray-500">Unknown content type: {contentType}</div>;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CollapsibleSection
|
<div className="group relative bg-gradient-to-br from-blue-50/50 to-indigo-50/50 dark:from-blue-950/20 dark:to-indigo-950/20 border border-blue-100/30 dark:border-blue-800/30 rounded-lg p-3 mb-2">
|
||||||
title={title}
|
<div className="absolute inset-0 bg-gradient-to-br from-blue-500/3 to-indigo-500/3 dark:from-blue-400/3 dark:to-indigo-400/3 rounded-lg opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
|
||||||
open={defaultOpen}
|
|
||||||
action={action}
|
|
||||||
className={className}
|
|
||||||
>
|
|
||||||
{/* Main content */}
|
|
||||||
{renderContent()}
|
|
||||||
|
|
||||||
{/* Optional raw parameters viewer */}
|
<div className="relative flex items-center justify-between mb-3">
|
||||||
{showRawParameters && rawContent && (
|
<div className="flex items-center gap-3">
|
||||||
<details className="relative mt-3 pl-6 group/raw" open={defaultOpen}>
|
<div className="relative w-8 h-8 bg-gradient-to-br from-blue-500 to-indigo-600 dark:from-blue-400 dark:to-indigo-500 rounded-lg flex items-center justify-center shadow-lg shadow-blue-500/20 dark:shadow-blue-400/20">
|
||||||
<summary className="flex items-center gap-2 text-xs font-medium text-gray-600 dark:text-gray-400 cursor-pointer hover:text-blue-600 dark:hover:text-blue-400 transition-colors duration-200 p-2 rounded-lg hover:bg-white/50 dark:hover:bg-gray-800/50">
|
<svg className="w-4 h-4 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<svg
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
|
||||||
className="w-3 h-3 transition-transform duration-200 group-open/raw:rotate-180"
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
|
||||||
</svg>
|
</svg>
|
||||||
View raw parameters
|
<div className="absolute inset-0 rounded-lg bg-blue-500 dark:bg-blue-400 animate-pulse opacity-20"></div>
|
||||||
</summary>
|
</div>
|
||||||
<pre className="mt-2 text-xs bg-gray-50 dark:bg-gray-800/50 border border-gray-200/60 dark:border-gray-700/60 p-3 rounded-lg whitespace-pre-wrap break-words overflow-hidden text-gray-700 dark:text-gray-300 font-mono">
|
<div className="flex flex-col">
|
||||||
{rawContent}
|
<span className="font-semibold text-gray-900 dark:text-white text-sm">
|
||||||
</pre>
|
{toolName}
|
||||||
</details>
|
</span>
|
||||||
)}
|
{toolId && (
|
||||||
</CollapsibleSection>
|
<span className="text-xs text-gray-500 dark:text-gray-400 font-mono">
|
||||||
|
{toolId}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{onShowSettings && (
|
||||||
|
<button
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onShowSettings();
|
||||||
|
}}
|
||||||
|
className="p-2 rounded-lg hover:bg-white/60 dark:hover:bg-gray-800/60 transition-all duration-200 group/btn backdrop-blur-sm"
|
||||||
|
title="Settings"
|
||||||
|
>
|
||||||
|
<svg className="w-4 h-4 text-gray-600 dark:text-gray-400 group-hover/btn:text-blue-600 dark:group-hover/btn:text-blue-400 transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<CollapsibleSection
|
||||||
|
title={title}
|
||||||
|
open={defaultOpen}
|
||||||
|
action={action}
|
||||||
|
className={className}
|
||||||
|
>
|
||||||
|
{renderContent()}
|
||||||
|
|
||||||
|
{showRawParameters && rawContent && (
|
||||||
|
<details className="relative mt-3 pl-6 group/raw" open={defaultOpen}>
|
||||||
|
<summary className="flex items-center gap-2 text-xs font-medium text-gray-600 dark:text-gray-400 cursor-pointer hover:text-blue-600 dark:hover:text-blue-400 transition-colors duration-200 p-2 rounded-lg hover:bg-white/50 dark:hover:bg-gray-800/50">
|
||||||
|
<svg
|
||||||
|
className="w-3 h-3 transition-transform duration-200 group-open/raw:rotate-180"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
||||||
|
</svg>
|
||||||
|
View raw parameters
|
||||||
|
</summary>
|
||||||
|
<pre className="mt-2 text-xs bg-gray-50 dark:bg-gray-800/50 border border-gray-200/60 dark:border-gray-700/60 p-3 rounded-lg whitespace-pre-wrap break-words overflow-hidden text-gray-700 dark:text-gray-300 font-mono">
|
||||||
|
{rawContent}
|
||||||
|
</pre>
|
||||||
|
</details>
|
||||||
|
)}
|
||||||
|
</CollapsibleSection>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import React, { useState } from 'react';
|
|||||||
type ActionType = 'copy' | 'open-file' | 'jump-to-results' | 'none';
|
type ActionType = 'copy' | 'open-file' | 'jump-to-results' | 'none';
|
||||||
|
|
||||||
interface OneLineDisplayProps {
|
interface OneLineDisplayProps {
|
||||||
|
toolName: string;
|
||||||
icon?: string;
|
icon?: string;
|
||||||
label?: string;
|
label?: string;
|
||||||
value: string;
|
value: string;
|
||||||
@@ -14,6 +15,8 @@ interface OneLineDisplayProps {
|
|||||||
secondary?: string;
|
secondary?: string;
|
||||||
};
|
};
|
||||||
resultId?: string; // For jump-to-results
|
resultId?: string; // For jump-to-results
|
||||||
|
toolResult?: any; // For showing result link
|
||||||
|
toolId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -21,6 +24,7 @@ interface OneLineDisplayProps {
|
|||||||
* Used by: Bash, Read, Grep/Glob (minimized), TodoRead, etc.
|
* Used by: Bash, Read, Grep/Glob (minimized), TodoRead, etc.
|
||||||
*/
|
*/
|
||||||
export const OneLineDisplay: React.FC<OneLineDisplayProps> = ({
|
export const OneLineDisplay: React.FC<OneLineDisplayProps> = ({
|
||||||
|
toolName,
|
||||||
icon,
|
icon,
|
||||||
label,
|
label,
|
||||||
value,
|
value,
|
||||||
@@ -31,7 +35,9 @@ export const OneLineDisplay: React.FC<OneLineDisplayProps> = ({
|
|||||||
primary: 'text-gray-700 dark:text-gray-300',
|
primary: 'text-gray-700 dark:text-gray-300',
|
||||||
secondary: 'text-gray-500 dark:text-gray-400'
|
secondary: 'text-gray-500 dark:text-gray-400'
|
||||||
},
|
},
|
||||||
resultId
|
resultId,
|
||||||
|
toolResult,
|
||||||
|
toolId
|
||||||
}) => {
|
}) => {
|
||||||
const [copied, setCopied] = useState(false);
|
const [copied, setCopied] = useState(false);
|
||||||
|
|
||||||
@@ -96,39 +102,51 @@ export const OneLineDisplay: React.FC<OneLineDisplayProps> = ({
|
|||||||
|
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mt-2 text-sm flex items-center gap-2">
|
<div className="group relative bg-gray-50/50 dark:bg-gray-800/30 border-l-2 border-blue-400 dark:border-blue-500 pl-3 py-2 my-2">
|
||||||
{/* Icon */}
|
<div className="flex items-center justify-between gap-3">
|
||||||
{icon && (
|
<div className="flex items-center gap-2 text-xs text-gray-600 dark:text-gray-400 flex-1 min-w-0">
|
||||||
<span className={`${colorScheme.primary} text-xs flex-shrink-0`}>
|
{icon ? (
|
||||||
{icon}
|
<span className="text-blue-500 dark:text-blue-400 flex-shrink-0">
|
||||||
</span>
|
{icon}
|
||||||
)}
|
</span>
|
||||||
|
) : label ? (
|
||||||
|
<span className="font-medium flex-shrink-0">{label}</span>
|
||||||
|
) : (
|
||||||
|
<span className="font-medium flex-shrink-0">{toolName}</span>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Label */}
|
<span className="text-gray-400 dark:text-gray-500 flex-shrink-0">•</span>
|
||||||
{label && (
|
|
||||||
<span className={colorScheme.primary}>{label}</span>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Value - different rendering based on action type */}
|
{action === 'open-file' ? (
|
||||||
{action === 'open-file' ? (
|
renderActionButton()
|
||||||
renderActionButton()
|
) : (
|
||||||
) : (
|
<span className={`font-mono truncate flex-1 min-w-0 ${colorScheme.primary}`}>
|
||||||
<span className={`${colorScheme.primary} ${action === 'none' ? '' : 'font-mono'}`}>
|
{value}
|
||||||
{value}
|
</span>
|
||||||
</span>
|
)}
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Secondary text (e.g., description) */}
|
{secondary && (
|
||||||
{secondary && (
|
<span className={`text-xs ${colorScheme.secondary} italic ml-2`}>
|
||||||
<span className={`text-xs ${colorScheme.secondary} italic`}>
|
({secondary})
|
||||||
({secondary})
|
</span>
|
||||||
</span>
|
)}
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Action button (copy, jump) */}
|
{action === 'copy' && renderActionButton()}
|
||||||
{action !== 'open-file' && renderActionButton()}
|
</div>
|
||||||
|
|
||||||
|
{action === 'jump-to-results' && toolResult && (
|
||||||
|
<a
|
||||||
|
href={`#tool-result-${toolId}`}
|
||||||
|
className="flex-shrink-0 text-xs text-blue-600 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-300 font-medium transition-colors flex items-center gap-1"
|
||||||
|
>
|
||||||
|
<span>Search results</span>
|
||||||
|
<svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user