mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-19 09:33:53 +00:00
refactor(vscode-ide-companion): 重构工具调用组件
- 重构 ExecuteToolCall、GenericToolCall、ReadToolCall 等组件 - 统一工具调用组件的展示样式和交互逻辑 - 优化代码结构,提高可维护性 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -8,63 +8,82 @@
|
||||
|
||||
import type React from 'react';
|
||||
import type { BaseToolCallProps } from './shared/types.js';
|
||||
import {
|
||||
ToolCallCard,
|
||||
ToolCallRow,
|
||||
StatusIndicator,
|
||||
CodeBlock,
|
||||
} from './shared/LayoutComponents.js';
|
||||
import { formatValue, safeTitle, groupContent } from './shared/utils.js';
|
||||
import { ToolCallCard, ToolCallRow } from './shared/LayoutComponents.js';
|
||||
import { safeTitle, groupContent } from './shared/utils.js';
|
||||
|
||||
/**
|
||||
* Specialized component for Execute tool calls
|
||||
* Optimized for displaying command execution with stdout/stderr
|
||||
* Shows command + output (if any) or error
|
||||
*/
|
||||
export const ExecuteToolCall: React.FC<BaseToolCallProps> = ({ toolCall }) => {
|
||||
const { title, status, rawInput, content } = toolCall;
|
||||
const titleText = safeTitle(title);
|
||||
const { title, content } = toolCall;
|
||||
const commandText = safeTitle(title);
|
||||
|
||||
// Group content by type
|
||||
const { textOutputs, errors, otherData } = groupContent(content);
|
||||
const { textOutputs, errors } = groupContent(content);
|
||||
|
||||
return (
|
||||
<ToolCallCard icon="⚡">
|
||||
{/* Title row */}
|
||||
<ToolCallRow label="Execute">
|
||||
<StatusIndicator status={status} text={titleText} />
|
||||
</ToolCallRow>
|
||||
|
||||
{/* Command */}
|
||||
{rawInput && (
|
||||
// Error case: show command + error
|
||||
if (errors.length > 0) {
|
||||
return (
|
||||
<ToolCallCard icon="⚡">
|
||||
<ToolCallRow label="Command">
|
||||
<CodeBlock>{formatValue(rawInput)}</CodeBlock>
|
||||
</ToolCallRow>
|
||||
)}
|
||||
|
||||
{/* Standard output */}
|
||||
{textOutputs.length > 0 && (
|
||||
<ToolCallRow label="Output">
|
||||
<CodeBlock>{textOutputs.join('\n')}</CodeBlock>
|
||||
</ToolCallRow>
|
||||
)}
|
||||
|
||||
{/* Standard error / Errors */}
|
||||
{errors.length > 0 && (
|
||||
<ToolCallRow label="Error">
|
||||
<div style={{ color: '#c74e39' }}>
|
||||
<CodeBlock>{errors.join('\n')}</CodeBlock>
|
||||
<div style={{ fontFamily: 'var(--app-monospace-font-family)' }}>
|
||||
{commandText}
|
||||
</div>
|
||||
</ToolCallRow>
|
||||
)}
|
||||
|
||||
{/* Exit code or other execution details */}
|
||||
{otherData.length > 0 && (
|
||||
<ToolCallRow label="Details">
|
||||
<CodeBlock>
|
||||
{otherData.map((data: unknown) => formatValue(data)).join('\n\n')}
|
||||
</CodeBlock>
|
||||
<ToolCallRow label="Error">
|
||||
<div
|
||||
style={{
|
||||
color: '#c74e39',
|
||||
fontWeight: 500,
|
||||
whiteSpace: 'pre-wrap',
|
||||
}}
|
||||
>
|
||||
{errors.join('\n')}
|
||||
</div>
|
||||
</ToolCallRow>
|
||||
)}
|
||||
</ToolCallCard>
|
||||
);
|
||||
}
|
||||
|
||||
// Success with output: show command + output (limited)
|
||||
if (textOutputs.length > 0) {
|
||||
const output = textOutputs.join('\n');
|
||||
const truncatedOutput =
|
||||
output.length > 500 ? output.substring(0, 500) + '...' : output;
|
||||
|
||||
return (
|
||||
<ToolCallCard icon="⚡">
|
||||
<ToolCallRow label="Command">
|
||||
<div style={{ fontFamily: 'var(--app-monospace-font-family)' }}>
|
||||
{commandText}
|
||||
</div>
|
||||
</ToolCallRow>
|
||||
<ToolCallRow label="Output">
|
||||
<div
|
||||
style={{
|
||||
fontFamily: 'var(--app-monospace-font-family)',
|
||||
fontSize: '13px',
|
||||
whiteSpace: 'pre-wrap',
|
||||
opacity: 0.9,
|
||||
}}
|
||||
>
|
||||
{truncatedOutput}
|
||||
</div>
|
||||
</ToolCallRow>
|
||||
</ToolCallCard>
|
||||
);
|
||||
}
|
||||
|
||||
// Success without output: show command only
|
||||
return (
|
||||
<ToolCallCard icon="⚡">
|
||||
<ToolCallRow label="Executed">
|
||||
<div style={{ fontFamily: 'var(--app-monospace-font-family)' }}>
|
||||
{commandText}
|
||||
</div>
|
||||
</ToolCallRow>
|
||||
</ToolCallCard>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -11,86 +11,114 @@ import type { BaseToolCallProps } from './shared/types.js';
|
||||
import {
|
||||
ToolCallCard,
|
||||
ToolCallRow,
|
||||
StatusIndicator,
|
||||
CodeBlock,
|
||||
LocationsList,
|
||||
} from './shared/LayoutComponents.js';
|
||||
import { DiffDisplay } from './shared/DiffDisplay.js';
|
||||
import {
|
||||
formatValue,
|
||||
safeTitle,
|
||||
getKindIcon,
|
||||
groupContent,
|
||||
} from './shared/utils.js';
|
||||
import { safeTitle, groupContent } from './shared/utils.js';
|
||||
import { useVSCode } from '../../hooks/useVSCode.js';
|
||||
|
||||
/**
|
||||
* Generic tool call component that can display any tool call type
|
||||
* Used as fallback for unknown tool call kinds
|
||||
* Minimal display: show description and outcome
|
||||
*/
|
||||
export const GenericToolCall: React.FC<BaseToolCallProps> = ({ toolCall }) => {
|
||||
const { kind, title, status, rawInput, content, locations } = toolCall;
|
||||
const kindIcon = getKindIcon(kind);
|
||||
const titleText = safeTitle(title);
|
||||
const { kind, title, content, locations } = toolCall;
|
||||
const operationText = safeTitle(title);
|
||||
const vscode = useVSCode();
|
||||
|
||||
// Group content by type
|
||||
const { textOutputs, errors, diffs, otherData } = groupContent(content);
|
||||
const { textOutputs, errors, diffs } = groupContent(content);
|
||||
|
||||
return (
|
||||
<ToolCallCard icon={kindIcon}>
|
||||
{/* Title row */}
|
||||
<ToolCallRow label="Tool">
|
||||
<StatusIndicator status={status} text={titleText} />
|
||||
</ToolCallRow>
|
||||
const handleOpenDiff = (
|
||||
path: string | undefined,
|
||||
oldText: string | null | undefined,
|
||||
newText: string | undefined,
|
||||
) => {
|
||||
if (path) {
|
||||
vscode.postMessage({
|
||||
type: 'openDiff',
|
||||
data: { path, oldText: oldText || '', newText: newText || '' },
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
{/* Input row */}
|
||||
{rawInput && (
|
||||
<ToolCallRow label="Input">
|
||||
<CodeBlock>{formatValue(rawInput)}</CodeBlock>
|
||||
// Error case: show operation + error
|
||||
if (errors.length > 0) {
|
||||
return (
|
||||
<ToolCallCard icon="🔧">
|
||||
<ToolCallRow label={kind}>
|
||||
<div>{operationText}</div>
|
||||
</ToolCallRow>
|
||||
)}
|
||||
<ToolCallRow label="Error">
|
||||
<div style={{ color: '#c74e39', fontWeight: 500 }}>
|
||||
{errors.join('\n')}
|
||||
</div>
|
||||
</ToolCallRow>
|
||||
</ToolCallCard>
|
||||
);
|
||||
}
|
||||
|
||||
{/* Locations row */}
|
||||
{locations && locations.length > 0 && (
|
||||
<ToolCallRow label="Files">
|
||||
// Success with diff: show diff
|
||||
if (diffs.length > 0) {
|
||||
return (
|
||||
<ToolCallCard icon="🔧">
|
||||
{diffs.map(
|
||||
(item: import('./shared/types.js').ToolCallContent, idx: number) => (
|
||||
<div key={`diff-${idx}`} style={{ gridColumn: '1 / -1' }}>
|
||||
<DiffDisplay
|
||||
path={item.path}
|
||||
oldText={item.oldText}
|
||||
newText={item.newText}
|
||||
onOpenDiff={() =>
|
||||
handleOpenDiff(item.path, item.oldText, item.newText)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
)}
|
||||
</ToolCallCard>
|
||||
);
|
||||
}
|
||||
|
||||
// Success with output: show operation + output (truncated)
|
||||
if (textOutputs.length > 0) {
|
||||
const output = textOutputs.join('\n');
|
||||
const truncatedOutput =
|
||||
output.length > 300 ? output.substring(0, 300) + '...' : output;
|
||||
|
||||
return (
|
||||
<ToolCallCard icon="🔧">
|
||||
<ToolCallRow label={kind}>
|
||||
<div>{operationText}</div>
|
||||
</ToolCallRow>
|
||||
<ToolCallRow label="Output">
|
||||
<div
|
||||
style={{
|
||||
whiteSpace: 'pre-wrap',
|
||||
fontFamily: 'var(--app-monospace-font-family)',
|
||||
fontSize: '13px',
|
||||
opacity: 0.9,
|
||||
}}
|
||||
>
|
||||
{truncatedOutput}
|
||||
</div>
|
||||
</ToolCallRow>
|
||||
</ToolCallCard>
|
||||
);
|
||||
}
|
||||
|
||||
// Success with files: show operation + file list
|
||||
if (locations && locations.length > 0) {
|
||||
return (
|
||||
<ToolCallCard icon="🔧">
|
||||
<ToolCallRow label={kind}>
|
||||
<LocationsList locations={locations} />
|
||||
</ToolCallRow>
|
||||
)}
|
||||
</ToolCallCard>
|
||||
);
|
||||
}
|
||||
|
||||
{/* Output row - combined text outputs */}
|
||||
{textOutputs.length > 0 && (
|
||||
<ToolCallRow label="Output">
|
||||
<CodeBlock>{textOutputs.join('\n')}</CodeBlock>
|
||||
</ToolCallRow>
|
||||
)}
|
||||
|
||||
{/* Error row - combined errors */}
|
||||
{errors.length > 0 && (
|
||||
<ToolCallRow label="Error">
|
||||
<div style={{ color: '#c74e39' }}>{errors.join('\n')}</div>
|
||||
</ToolCallRow>
|
||||
)}
|
||||
|
||||
{/* Diff rows */}
|
||||
{diffs.map(
|
||||
(item: import('./shared/types.js').ToolCallContent, idx: number) => (
|
||||
<ToolCallRow key={`diff-${idx}`} label="Diff">
|
||||
<DiffDisplay
|
||||
path={item.path}
|
||||
oldText={item.oldText}
|
||||
newText={item.newText}
|
||||
/>
|
||||
</ToolCallRow>
|
||||
),
|
||||
)}
|
||||
|
||||
{/* Other data rows */}
|
||||
{otherData.length > 0 && (
|
||||
<ToolCallRow label="Data">
|
||||
<CodeBlock>
|
||||
{otherData.map((data: unknown) => formatValue(data)).join('\n\n')}
|
||||
</CodeBlock>
|
||||
</ToolCallRow>
|
||||
)}
|
||||
</ToolCallCard>
|
||||
);
|
||||
// No output
|
||||
return null;
|
||||
};
|
||||
|
||||
@@ -11,66 +11,45 @@ import type { BaseToolCallProps } from './shared/types.js';
|
||||
import {
|
||||
ToolCallCard,
|
||||
ToolCallRow,
|
||||
StatusIndicator,
|
||||
CodeBlock,
|
||||
LocationsList,
|
||||
} from './shared/LayoutComponents.js';
|
||||
import { formatValue, safeTitle, groupContent } from './shared/utils.js';
|
||||
import { groupContent } from './shared/utils.js';
|
||||
|
||||
/**
|
||||
* Specialized component for Read tool calls
|
||||
* Optimized for displaying file reading operations
|
||||
* Minimal display: just show file name, hide content (too verbose)
|
||||
*/
|
||||
export const ReadToolCall: React.FC<BaseToolCallProps> = ({ toolCall }) => {
|
||||
const { title, status, rawInput, content, locations } = toolCall;
|
||||
const titleText = safeTitle(title);
|
||||
const { content, locations } = toolCall;
|
||||
|
||||
// Group content by type
|
||||
const { textOutputs, errors, otherData } = groupContent(content);
|
||||
const { errors } = groupContent(content);
|
||||
|
||||
return (
|
||||
<ToolCallCard icon="📖">
|
||||
{/* Title row */}
|
||||
<ToolCallRow label="Read">
|
||||
<StatusIndicator status={status} text={titleText} />
|
||||
</ToolCallRow>
|
||||
// Error case: show error with operation label
|
||||
if (errors.length > 0) {
|
||||
return (
|
||||
<ToolCallCard icon="📖">
|
||||
<ToolCallRow label="Read">
|
||||
<div style={{ color: '#c74e39', fontWeight: 500 }}>
|
||||
{errors.join('\n')}
|
||||
</div>
|
||||
</ToolCallRow>
|
||||
</ToolCallCard>
|
||||
);
|
||||
}
|
||||
|
||||
{/* File path(s) */}
|
||||
{locations && locations.length > 0 && (
|
||||
<ToolCallRow label="File">
|
||||
// Success case: show which file was read
|
||||
if (locations && locations.length > 0) {
|
||||
return (
|
||||
<ToolCallCard icon="📖">
|
||||
<ToolCallRow label="Read">
|
||||
<LocationsList locations={locations} />
|
||||
</ToolCallRow>
|
||||
)}
|
||||
</ToolCallCard>
|
||||
);
|
||||
}
|
||||
|
||||
{/* Input parameters (e.g., line range, offset) */}
|
||||
{rawInput && (
|
||||
<ToolCallRow label="Options">
|
||||
<CodeBlock>{formatValue(rawInput)}</CodeBlock>
|
||||
</ToolCallRow>
|
||||
)}
|
||||
|
||||
{/* File content output */}
|
||||
{textOutputs.length > 0 && (
|
||||
<ToolCallRow label="Content">
|
||||
<CodeBlock>{textOutputs.join('\n')}</CodeBlock>
|
||||
</ToolCallRow>
|
||||
)}
|
||||
|
||||
{/* Error handling */}
|
||||
{errors.length > 0 && (
|
||||
<ToolCallRow label="Error">
|
||||
<div style={{ color: '#c74e39' }}>{errors.join('\n')}</div>
|
||||
</ToolCallRow>
|
||||
)}
|
||||
|
||||
{/* Other data */}
|
||||
{otherData.length > 0 && (
|
||||
<ToolCallRow label="Details">
|
||||
<CodeBlock>
|
||||
{otherData.map((data: unknown) => formatValue(data)).join('\n\n')}
|
||||
</CodeBlock>
|
||||
</ToolCallRow>
|
||||
)}
|
||||
</ToolCallCard>
|
||||
);
|
||||
// No file info, don't show
|
||||
return null;
|
||||
};
|
||||
|
||||
@@ -11,66 +11,56 @@ import type { BaseToolCallProps } from './shared/types.js';
|
||||
import {
|
||||
ToolCallCard,
|
||||
ToolCallRow,
|
||||
StatusIndicator,
|
||||
CodeBlock,
|
||||
LocationsList,
|
||||
} from './shared/LayoutComponents.js';
|
||||
import { formatValue, safeTitle, groupContent } from './shared/utils.js';
|
||||
import { safeTitle, groupContent } from './shared/utils.js';
|
||||
|
||||
/**
|
||||
* Specialized component for Search tool calls
|
||||
* Optimized for displaying search operations and results
|
||||
* Shows query + result count or file list
|
||||
*/
|
||||
export const SearchToolCall: React.FC<BaseToolCallProps> = ({ toolCall }) => {
|
||||
const { title, status, rawInput, content, locations } = toolCall;
|
||||
const titleText = safeTitle(title);
|
||||
const { title, content, locations } = toolCall;
|
||||
const queryText = safeTitle(title);
|
||||
|
||||
// Group content by type
|
||||
const { textOutputs, errors, otherData } = groupContent(content);
|
||||
const { errors } = groupContent(content);
|
||||
|
||||
return (
|
||||
<ToolCallCard icon="🔍">
|
||||
{/* Title row */}
|
||||
<ToolCallRow label="Search">
|
||||
<StatusIndicator status={status} text={titleText} />
|
||||
</ToolCallRow>
|
||||
|
||||
{/* Search query/pattern */}
|
||||
{rawInput && (
|
||||
<ToolCallRow label="Query">
|
||||
<CodeBlock>{formatValue(rawInput)}</CodeBlock>
|
||||
// Error case: show search query + error
|
||||
if (errors.length > 0) {
|
||||
return (
|
||||
<ToolCallCard icon="🔍">
|
||||
<ToolCallRow label="Search">
|
||||
<div style={{ fontFamily: 'var(--app-monospace-font-family)' }}>
|
||||
{queryText}
|
||||
</div>
|
||||
</ToolCallRow>
|
||||
)}
|
||||
<ToolCallRow label="Error">
|
||||
<div style={{ color: '#c74e39', fontWeight: 500 }}>
|
||||
{errors.join('\n')}
|
||||
</div>
|
||||
</ToolCallRow>
|
||||
</ToolCallCard>
|
||||
);
|
||||
}
|
||||
|
||||
{/* Search results - files found */}
|
||||
{locations && locations.length > 0 && (
|
||||
<ToolCallRow label="Results">
|
||||
// Success case with results: show search query + file list
|
||||
if (locations && locations.length > 0) {
|
||||
return (
|
||||
<ToolCallCard icon="🔍">
|
||||
<ToolCallRow label="Search">
|
||||
<div style={{ fontFamily: 'var(--app-monospace-font-family)' }}>
|
||||
{queryText}
|
||||
</div>
|
||||
</ToolCallRow>
|
||||
<ToolCallRow label={`Found (${locations.length})`}>
|
||||
<LocationsList locations={locations} />
|
||||
</ToolCallRow>
|
||||
)}
|
||||
</ToolCallCard>
|
||||
);
|
||||
}
|
||||
|
||||
{/* Search output details */}
|
||||
{textOutputs.length > 0 && (
|
||||
<ToolCallRow label="Matches">
|
||||
<CodeBlock>{textOutputs.join('\n')}</CodeBlock>
|
||||
</ToolCallRow>
|
||||
)}
|
||||
|
||||
{/* Error handling */}
|
||||
{errors.length > 0 && (
|
||||
<ToolCallRow label="Error">
|
||||
<div style={{ color: '#c74e39' }}>{errors.join('\n')}</div>
|
||||
</ToolCallRow>
|
||||
)}
|
||||
|
||||
{/* Other search metadata */}
|
||||
{otherData.length > 0 && (
|
||||
<ToolCallRow label="Details">
|
||||
<CodeBlock>
|
||||
{otherData.map((data: unknown) => formatValue(data)).join('\n\n')}
|
||||
</CodeBlock>
|
||||
</ToolCallRow>
|
||||
)}
|
||||
</ToolCallCard>
|
||||
);
|
||||
// No results
|
||||
return null;
|
||||
};
|
||||
|
||||
@@ -8,63 +8,56 @@
|
||||
|
||||
import type React from 'react';
|
||||
import type { BaseToolCallProps } from './shared/types.js';
|
||||
import {
|
||||
ToolCallCard,
|
||||
ToolCallRow,
|
||||
StatusIndicator,
|
||||
CodeBlock,
|
||||
} from './shared/LayoutComponents.js';
|
||||
import { formatValue, safeTitle, groupContent } from './shared/utils.js';
|
||||
import { ToolCallCard, ToolCallRow } from './shared/LayoutComponents.js';
|
||||
import { groupContent } from './shared/utils.js';
|
||||
|
||||
/**
|
||||
* Specialized component for Think tool calls
|
||||
* Optimized for displaying AI reasoning and thought processes
|
||||
* Minimal display: just show the thoughts (no context)
|
||||
*/
|
||||
export const ThinkToolCall: React.FC<BaseToolCallProps> = ({ toolCall }) => {
|
||||
const { title, status, rawInput, content } = toolCall;
|
||||
const titleText = safeTitle(title);
|
||||
const { content } = toolCall;
|
||||
|
||||
// Group content by type
|
||||
const { textOutputs, errors, otherData } = groupContent(content);
|
||||
const { textOutputs, errors } = groupContent(content);
|
||||
|
||||
return (
|
||||
<ToolCallCard icon="💭">
|
||||
{/* Title row */}
|
||||
<ToolCallRow label="Thinking">
|
||||
<StatusIndicator status={status} text={titleText} />
|
||||
</ToolCallRow>
|
||||
|
||||
{/* Thinking context/prompt */}
|
||||
{rawInput && (
|
||||
<ToolCallRow label="Context">
|
||||
<CodeBlock>{formatValue(rawInput)}</CodeBlock>
|
||||
</ToolCallRow>
|
||||
)}
|
||||
|
||||
{/* Thought content */}
|
||||
{textOutputs.length > 0 && (
|
||||
<ToolCallRow label="Thoughts">
|
||||
<div style={{ fontStyle: 'italic', opacity: 0.95 }}>
|
||||
{textOutputs.join('\n\n')}
|
||||
// Error case (rare for thinking)
|
||||
if (errors.length > 0) {
|
||||
return (
|
||||
<ToolCallCard icon="💭">
|
||||
<ToolCallRow label="Error">
|
||||
<div style={{ color: '#c74e39', fontWeight: 500 }}>
|
||||
{errors.join('\n')}
|
||||
</div>
|
||||
</ToolCallRow>
|
||||
)}
|
||||
</ToolCallCard>
|
||||
);
|
||||
}
|
||||
|
||||
{/* Error handling */}
|
||||
{errors.length > 0 && (
|
||||
<ToolCallRow label="Error">
|
||||
<div style={{ color: '#c74e39' }}>{errors.join('\n')}</div>
|
||||
</ToolCallRow>
|
||||
)}
|
||||
// Show thoughts with label
|
||||
if (textOutputs.length > 0) {
|
||||
const thoughts = textOutputs.join('\n\n');
|
||||
const truncatedThoughts =
|
||||
thoughts.length > 500 ? thoughts.substring(0, 500) + '...' : thoughts;
|
||||
|
||||
{/* Other reasoning data */}
|
||||
{otherData.length > 0 && (
|
||||
<ToolCallRow label="Details">
|
||||
<CodeBlock>
|
||||
{otherData.map((data: unknown) => formatValue(data)).join('\n\n')}
|
||||
</CodeBlock>
|
||||
return (
|
||||
<ToolCallCard icon="💭">
|
||||
<ToolCallRow label="Thinking">
|
||||
<div
|
||||
style={{
|
||||
fontStyle: 'italic',
|
||||
opacity: 0.9,
|
||||
lineHeight: 1.6,
|
||||
}}
|
||||
>
|
||||
{truncatedThoughts}
|
||||
</div>
|
||||
</ToolCallRow>
|
||||
)}
|
||||
</ToolCallCard>
|
||||
);
|
||||
</ToolCallCard>
|
||||
);
|
||||
}
|
||||
|
||||
// Empty thoughts
|
||||
return null;
|
||||
};
|
||||
|
||||
@@ -11,26 +11,24 @@ import type { BaseToolCallProps } from './shared/types.js';
|
||||
import {
|
||||
ToolCallCard,
|
||||
ToolCallRow,
|
||||
StatusIndicator,
|
||||
CodeBlock,
|
||||
LocationsList,
|
||||
} from './shared/LayoutComponents.js';
|
||||
import { DiffDisplay } from './shared/DiffDisplay.js';
|
||||
import { formatValue, safeTitle, groupContent } from './shared/utils.js';
|
||||
import { groupContent } from './shared/utils.js';
|
||||
import { useVSCode } from '../../hooks/useVSCode.js';
|
||||
|
||||
/**
|
||||
* Specialized component for Write/Edit tool calls
|
||||
* Optimized for displaying file writing and editing operations with diffs
|
||||
* Follows minimal display principle: only show what matters
|
||||
*/
|
||||
export const WriteToolCall: React.FC<BaseToolCallProps> = ({ toolCall }) => {
|
||||
const { kind, title, status, rawInput, content, locations } = toolCall;
|
||||
const titleText = safeTitle(title);
|
||||
const { kind, status: _status, content, locations } = toolCall;
|
||||
const isEdit = kind.toLowerCase() === 'edit';
|
||||
const vscode = useVSCode();
|
||||
|
||||
// Group content by type
|
||||
const { textOutputs, errors, diffs, otherData } = groupContent(content);
|
||||
const { errors, diffs } = groupContent(content);
|
||||
|
||||
const handleOpenDiff = (
|
||||
path: string | undefined,
|
||||
@@ -45,65 +43,52 @@ export const WriteToolCall: React.FC<BaseToolCallProps> = ({ toolCall }) => {
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ToolCallCard icon="✏️">
|
||||
{/* Title row */}
|
||||
<ToolCallRow label={isEdit ? 'Edit' : 'Write'}>
|
||||
<StatusIndicator status={status} text={titleText} />
|
||||
</ToolCallRow>
|
||||
// Error case: show error with operation label
|
||||
if (errors.length > 0) {
|
||||
return (
|
||||
<ToolCallCard icon="✏️">
|
||||
<ToolCallRow label={isEdit ? 'Edit' : 'Write'}>
|
||||
<div style={{ color: '#c74e39', fontWeight: 500 }}>
|
||||
{errors.join('\n')}
|
||||
</div>
|
||||
</ToolCallRow>
|
||||
</ToolCallCard>
|
||||
);
|
||||
}
|
||||
|
||||
{/* File path(s) */}
|
||||
{locations && locations.length > 0 && (
|
||||
<ToolCallRow label="File">
|
||||
// Success case with diff: show diff (already has file path)
|
||||
if (diffs.length > 0) {
|
||||
return (
|
||||
<ToolCallCard icon="✏️">
|
||||
{diffs.map(
|
||||
(item: import('./shared/types.js').ToolCallContent, idx: number) => (
|
||||
<div key={`diff-${idx}`} style={{ gridColumn: '1 / -1' }}>
|
||||
<DiffDisplay
|
||||
path={item.path}
|
||||
oldText={item.oldText}
|
||||
newText={item.newText}
|
||||
onOpenDiff={() =>
|
||||
handleOpenDiff(item.path, item.oldText, item.newText)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
)}
|
||||
</ToolCallCard>
|
||||
);
|
||||
}
|
||||
|
||||
// Success case without diff: show operation + file
|
||||
if (locations && locations.length > 0) {
|
||||
return (
|
||||
<ToolCallCard icon="✏️">
|
||||
<ToolCallRow label={isEdit ? 'Edited' : 'Created'}>
|
||||
<LocationsList locations={locations} />
|
||||
</ToolCallRow>
|
||||
)}
|
||||
</ToolCallCard>
|
||||
);
|
||||
}
|
||||
|
||||
{/* Input parameters (e.g., old_string, new_string for edits) */}
|
||||
{rawInput && (
|
||||
<ToolCallRow label="Changes">
|
||||
<CodeBlock>{formatValue(rawInput)}</CodeBlock>
|
||||
</ToolCallRow>
|
||||
)}
|
||||
|
||||
{/* Diff display - most important for write/edit operations */}
|
||||
{diffs.map(
|
||||
(item: import('./shared/types.js').ToolCallContent, idx: number) => (
|
||||
<ToolCallRow key={`diff-${idx}`} label="Diff">
|
||||
<DiffDisplay
|
||||
path={item.path}
|
||||
oldText={item.oldText}
|
||||
newText={item.newText}
|
||||
onOpenDiff={() =>
|
||||
handleOpenDiff(item.path, item.oldText, item.newText)
|
||||
}
|
||||
/>
|
||||
</ToolCallRow>
|
||||
),
|
||||
)}
|
||||
|
||||
{/* Success message or output */}
|
||||
{textOutputs.length > 0 && (
|
||||
<ToolCallRow label="Result">
|
||||
<CodeBlock>{textOutputs.join('\n')}</CodeBlock>
|
||||
</ToolCallRow>
|
||||
)}
|
||||
|
||||
{/* Error handling */}
|
||||
{errors.length > 0 && (
|
||||
<ToolCallRow label="Error">
|
||||
<div style={{ color: '#c74e39' }}>{errors.join('\n')}</div>
|
||||
</ToolCallRow>
|
||||
)}
|
||||
|
||||
{/* Other data */}
|
||||
{otherData.length > 0 && (
|
||||
<ToolCallRow label="Details">
|
||||
<CodeBlock>
|
||||
{otherData.map((data: unknown) => formatValue(data)).join('\n\n')}
|
||||
</CodeBlock>
|
||||
</ToolCallRow>
|
||||
)}
|
||||
</ToolCallCard>
|
||||
);
|
||||
// No output, don't show anything
|
||||
return null;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user