mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-02-14 12:47:33 +00: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">
|
||||
|
||||
{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) {
|
||||
return (
|
||||
<>
|
||||
<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 items-center justify-between gap-3">
|
||||
<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>
|
||||
{message.isToolUse ? (
|
||||
<>
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-col">
|
||||
<Markdown className="prose prose-sm max-w-none dark:prose-invert">{message.displayText}</Markdown>
|
||||
</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>
|
||||
{/* All tool input rendering now handled by ToolRenderer */}
|
||||
{message.toolInput && (() => {
|
||||
// Use new ToolRenderer for all tools (config-driven)
|
||||
return (
|
||||
<ToolRenderer
|
||||
toolName={message.toolName}
|
||||
toolInput={message.toolInput}
|
||||
mode="input"
|
||||
onFileOpen={onFileOpen}
|
||||
createDiff={createDiff}
|
||||
selectedProject={selectedProject}
|
||||
autoExpandTools={autoExpandTools}
|
||||
showRawParameters={showRawParameters}
|
||||
rawToolInput={message.toolInput}
|
||||
/>
|
||||
);
|
||||
})()}
|
||||
|
||||
{message.toolInput && (
|
||||
<ToolRenderer
|
||||
toolName={message.toolName}
|
||||
toolInput={message.toolInput}
|
||||
toolResult={message.toolResult}
|
||||
toolId={message.toolId}
|
||||
mode="input"
|
||||
onFileOpen={onFileOpen}
|
||||
createDiff={createDiff}
|
||||
selectedProject={selectedProject}
|
||||
autoExpandTools={autoExpandTools}
|
||||
showRawParameters={showRawParameters}
|
||||
onShowSettings={onShowSettings}
|
||||
rawToolInput={message.toolInput}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Tool Result Section */}
|
||||
{message.toolResult && (() => {
|
||||
@@ -714,9 +633,7 @@ const MessageComponent = memo(({ message, index, prevMessage, createDiff, onFile
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
</div>
|
||||
);
|
||||
})()
|
||||
</>
|
||||
) : message.isInteractivePrompt ? (
|
||||
// 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">
|
||||
@@ -800,84 +717,6 @@ const MessageComponent = memo(({ message, index, prevMessage, createDiff, onFile
|
||||
</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 ? (
|
||||
/* Thinking messages - collapsible by default */
|
||||
<div className="text-sm text-gray-700 dark:text-gray-300">
|
||||
|
||||
@@ -13,12 +13,12 @@ interface ToolRendererProps {
|
||||
toolName: string;
|
||||
toolInput: any;
|
||||
toolResult?: any;
|
||||
toolId?: string;
|
||||
mode: 'input' | 'result';
|
||||
// Callbacks and helpers
|
||||
onFileOpen?: (filePath: string, diffInfo?: any) => void;
|
||||
createDiff?: (oldStr: string, newStr: string) => DiffLine[];
|
||||
selectedProject?: Project | null;
|
||||
// Display options
|
||||
onShowSettings?: () => void;
|
||||
autoExpandTools?: boolean;
|
||||
showRawParameters?: boolean;
|
||||
rawToolInput?: string;
|
||||
@@ -32,10 +32,12 @@ export const ToolRenderer: React.FC<ToolRendererProps> = ({
|
||||
toolName,
|
||||
toolInput,
|
||||
toolResult,
|
||||
toolId,
|
||||
mode,
|
||||
onFileOpen,
|
||||
createDiff,
|
||||
selectedProject,
|
||||
onShowSettings,
|
||||
autoExpandTools = false,
|
||||
showRawParameters = false,
|
||||
rawToolInput
|
||||
@@ -45,7 +47,6 @@ export const ToolRenderer: React.FC<ToolRendererProps> = ({
|
||||
|
||||
if (!displayConfig) return null;
|
||||
|
||||
// Parse tool input/result
|
||||
let parsedData: any;
|
||||
try {
|
||||
const rawData = mode === 'input' ? toolInput : toolResult;
|
||||
@@ -54,9 +55,6 @@ export const ToolRenderer: React.FC<ToolRendererProps> = ({
|
||||
parsedData = mode === 'input' ? toolInput : toolResult;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// ONE-LINE DISPLAY
|
||||
// ============================================================================
|
||||
if (displayConfig.type === 'one-line') {
|
||||
const value = displayConfig.getValue?.(parsedData) || '';
|
||||
const secondary = displayConfig.getSecondary?.(parsedData);
|
||||
@@ -69,6 +67,9 @@ export const ToolRenderer: React.FC<ToolRendererProps> = ({
|
||||
|
||||
return (
|
||||
<OneLineDisplay
|
||||
toolName={toolName}
|
||||
toolResult={toolResult}
|
||||
toolId={toolId}
|
||||
icon={displayConfig.icon}
|
||||
label={displayConfig.label}
|
||||
value={value}
|
||||
@@ -76,14 +77,11 @@ export const ToolRenderer: React.FC<ToolRendererProps> = ({
|
||||
action={displayConfig.action}
|
||||
onAction={handleAction}
|
||||
colorScheme={displayConfig.colorScheme}
|
||||
resultId={mode === 'input' ? `tool-result-${toolName}` : undefined}
|
||||
resultId={mode === 'input' ? `tool-result-${toolId}` : undefined}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// COLLAPSIBLE DISPLAY
|
||||
// ============================================================================
|
||||
if (displayConfig.type === 'collapsible') {
|
||||
const title = typeof displayConfig.title === 'function'
|
||||
? displayConfig.title(parsedData)
|
||||
@@ -93,14 +91,12 @@ export const ToolRenderer: React.FC<ToolRendererProps> = ({
|
||||
? displayConfig.defaultOpen
|
||||
: autoExpandTools;
|
||||
|
||||
// Get content props from config
|
||||
const contentProps = displayConfig.getContentProps?.(parsedData, {
|
||||
selectedProject,
|
||||
createDiff,
|
||||
onFileOpen
|
||||
}) || {};
|
||||
|
||||
// Render content based on contentType
|
||||
let contentComponent = null;
|
||||
|
||||
switch (displayConfig.contentType) {
|
||||
@@ -169,7 +165,6 @@ export const ToolRenderer: React.FC<ToolRendererProps> = ({
|
||||
);
|
||||
}
|
||||
|
||||
// Action button for file operations
|
||||
let actionButton = null;
|
||||
if (displayConfig.actionButton === 'file-button' && contentProps.filePath) {
|
||||
const handleFileClick = async (e: React.MouseEvent) => {
|
||||
@@ -177,7 +172,6 @@ export const ToolRenderer: React.FC<ToolRendererProps> = ({
|
||||
e.stopPropagation();
|
||||
if (!onFileOpen) return;
|
||||
|
||||
// For Edit/ApplyPatch tools, fetch current file and reverse-apply the edit
|
||||
if (toolName === 'Edit' || toolName === 'ApplyPatch') {
|
||||
try {
|
||||
const { api } = await import('../../../utils/api');
|
||||
@@ -202,7 +196,6 @@ export const ToolRenderer: React.FC<ToolRendererProps> = ({
|
||||
onFileOpen(contentProps.filePath);
|
||||
}
|
||||
}
|
||||
// For Write tool, fetch written file
|
||||
else if (toolName === 'Write') {
|
||||
try {
|
||||
const { api } = await import('../../../utils/api');
|
||||
@@ -235,6 +228,8 @@ export const ToolRenderer: React.FC<ToolRendererProps> = ({
|
||||
|
||||
return (
|
||||
<CollapsibleDisplay
|
||||
toolName={toolName}
|
||||
toolId={toolId}
|
||||
title={title}
|
||||
defaultOpen={defaultOpen}
|
||||
action={actionButton}
|
||||
@@ -248,12 +243,10 @@ export const ToolRenderer: React.FC<ToolRendererProps> = ({
|
||||
}}
|
||||
showRawParameters={mode === 'input' && showRawParameters}
|
||||
rawContent={rawToolInput}
|
||||
onShowSettings={onShowSettings}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// SPECIAL / HIDDEN
|
||||
// ============================================================================
|
||||
return null;
|
||||
};
|
||||
|
||||
@@ -4,6 +4,8 @@ import { CollapsibleSection } from './CollapsibleSection';
|
||||
type ContentType = 'diff' | 'markdown' | 'file-list' | 'todo-list' | 'text';
|
||||
|
||||
interface CollapsibleDisplayProps {
|
||||
toolName: string;
|
||||
toolId?: string;
|
||||
title: string;
|
||||
defaultOpen?: boolean;
|
||||
action?: React.ReactNode;
|
||||
@@ -12,15 +14,12 @@ interface CollapsibleDisplayProps {
|
||||
showRawParameters?: boolean;
|
||||
rawContent?: 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> = ({
|
||||
toolName,
|
||||
toolId,
|
||||
title,
|
||||
defaultOpen = false,
|
||||
action,
|
||||
@@ -28,65 +27,98 @@ export const CollapsibleDisplay: React.FC<CollapsibleDisplayProps> = ({
|
||||
contentProps,
|
||||
showRawParameters = false,
|
||||
rawContent,
|
||||
className = ''
|
||||
className = '',
|
||||
onShowSettings
|
||||
}) => {
|
||||
// Import content renderers dynamically based on type
|
||||
const renderContent = () => {
|
||||
switch (contentType) {
|
||||
case 'diff':
|
||||
// DiffViewer already exists - will be imported by ToolRenderer
|
||||
return contentProps.DiffViewer;
|
||||
|
||||
case 'markdown':
|
||||
// Markdown component already exists - will be imported by ToolRenderer
|
||||
return contentProps.MarkdownComponent;
|
||||
|
||||
case 'file-list':
|
||||
// FileListContent will be created
|
||||
return contentProps.FileListComponent;
|
||||
|
||||
case 'todo-list':
|
||||
// TodoListContent will be created
|
||||
return contentProps.TodoListComponent;
|
||||
|
||||
case 'text':
|
||||
// TextContent will be created
|
||||
return contentProps.TextComponent;
|
||||
|
||||
default:
|
||||
return <div className="text-gray-500">Unknown content type: {contentType}</div>;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<CollapsibleSection
|
||||
title={title}
|
||||
open={defaultOpen}
|
||||
action={action}
|
||||
className={className}
|
||||
>
|
||||
{/* Main content */}
|
||||
{renderContent()}
|
||||
<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">
|
||||
<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>
|
||||
|
||||
{/* Optional raw parameters viewer */}
|
||||
{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" />
|
||||
<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>
|
||||
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 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">
|
||||
{toolName}
|
||||
</span>
|
||||
{toolId && (
|
||||
<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';
|
||||
|
||||
interface OneLineDisplayProps {
|
||||
toolName: string;
|
||||
icon?: string;
|
||||
label?: string;
|
||||
value: string;
|
||||
@@ -14,6 +15,8 @@ interface OneLineDisplayProps {
|
||||
secondary?: string;
|
||||
};
|
||||
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.
|
||||
*/
|
||||
export const OneLineDisplay: React.FC<OneLineDisplayProps> = ({
|
||||
toolName,
|
||||
icon,
|
||||
label,
|
||||
value,
|
||||
@@ -31,7 +35,9 @@ export const OneLineDisplay: React.FC<OneLineDisplayProps> = ({
|
||||
primary: 'text-gray-700 dark:text-gray-300',
|
||||
secondary: 'text-gray-500 dark:text-gray-400'
|
||||
},
|
||||
resultId
|
||||
resultId,
|
||||
toolResult,
|
||||
toolId
|
||||
}) => {
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
@@ -96,39 +102,51 @@ export const OneLineDisplay: React.FC<OneLineDisplayProps> = ({
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mt-2 text-sm flex items-center gap-2">
|
||||
{/* Icon */}
|
||||
{icon && (
|
||||
<span className={`${colorScheme.primary} text-xs flex-shrink-0`}>
|
||||
{icon}
|
||||
</span>
|
||||
)}
|
||||
<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 items-center justify-between gap-3">
|
||||
<div className="flex items-center gap-2 text-xs text-gray-600 dark:text-gray-400 flex-1 min-w-0">
|
||||
{icon ? (
|
||||
<span className="text-blue-500 dark:text-blue-400 flex-shrink-0">
|
||||
{icon}
|
||||
</span>
|
||||
) : label ? (
|
||||
<span className="font-medium flex-shrink-0">{label}</span>
|
||||
) : (
|
||||
<span className="font-medium flex-shrink-0">{toolName}</span>
|
||||
)}
|
||||
|
||||
{/* Label */}
|
||||
{label && (
|
||||
<span className={colorScheme.primary}>{label}</span>
|
||||
)}
|
||||
<span className="text-gray-400 dark:text-gray-500 flex-shrink-0">•</span>
|
||||
|
||||
{/* Value - different rendering based on action type */}
|
||||
{action === 'open-file' ? (
|
||||
renderActionButton()
|
||||
) : (
|
||||
<span className={`${colorScheme.primary} ${action === 'none' ? '' : 'font-mono'}`}>
|
||||
{value}
|
||||
</span>
|
||||
)}
|
||||
{action === 'open-file' ? (
|
||||
renderActionButton()
|
||||
) : (
|
||||
<span className={`font-mono truncate flex-1 min-w-0 ${colorScheme.primary}`}>
|
||||
{value}
|
||||
</span>
|
||||
)}
|
||||
|
||||
{/* Secondary text (e.g., description) */}
|
||||
{secondary && (
|
||||
<span className={`text-xs ${colorScheme.secondary} italic`}>
|
||||
({secondary})
|
||||
</span>
|
||||
)}
|
||||
{secondary && (
|
||||
<span className={`text-xs ${colorScheme.secondary} italic ml-2`}>
|
||||
({secondary})
|
||||
</span>
|
||||
)}
|
||||
|
||||
{/* Action button (copy, jump) */}
|
||||
{action !== 'open-file' && renderActionButton()}
|
||||
{action === 'copy' && 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>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user