diff --git a/packages/vscode-ide-companion/src/webview/components/toolcalls/Execute/Execute.tsx b/packages/vscode-ide-companion/src/webview/components/toolcalls/Execute/Execute.tsx index 3f9255be..1f9de179 100644 --- a/packages/vscode-ide-companion/src/webview/components/toolcalls/Execute/Execute.tsx +++ b/packages/vscode-ide-companion/src/webview/components/toolcalls/Execute/Execute.tsx @@ -35,7 +35,12 @@ export const ExecuteToolCall: React.FC = ({ toolCall }) => { // Error case if (errors.length > 0) { return ( - + {/* Branch connector summary (Claude-like) */}
diff --git a/packages/vscode-ide-companion/src/webview/components/toolcalls/ExecuteNode/ExecuteNode.css b/packages/vscode-ide-companion/src/webview/components/toolcalls/ExecuteNode/ExecuteNode.css new file mode 100644 index 00000000..99f3121f --- /dev/null +++ b/packages/vscode-ide-companion/src/webview/components/toolcalls/ExecuteNode/ExecuteNode.css @@ -0,0 +1,38 @@ +/** + * @license + * Copyright 2025 Qwen Team + * SPDX-License-Identifier: Apache-2.0 + * + * ExecuteNode tool call styles + */ + +/* Error content styling */ +.execute-node-error-content { + color: #c74e39; + margin-top: 4px; +} + +/* Preformatted content */ +.execute-node-pre { + margin: 0; + font-family: var(--app-monospace-font-family); + font-size: 0.85em; + white-space: pre-wrap; + word-break: break-word; +} + +/* Error preformatted content */ +.execute-node-error-pre { + color: #c74e39; +} + +/* Output content styling */ +.execute-node-output-content { + background-color: var(--app-code-background); + border: 0.5px solid var(--app-input-border); + border-radius: 5px; + margin: 8px 0; + padding: 8px; + max-width: 100%; + box-sizing: border-box; +} \ No newline at end of file diff --git a/packages/vscode-ide-companion/src/webview/components/toolcalls/ExecuteNode/ExecuteNodeToolCall.tsx b/packages/vscode-ide-companion/src/webview/components/toolcalls/ExecuteNode/ExecuteNodeToolCall.tsx new file mode 100644 index 00000000..82569e1e --- /dev/null +++ b/packages/vscode-ide-companion/src/webview/components/toolcalls/ExecuteNode/ExecuteNodeToolCall.tsx @@ -0,0 +1,75 @@ +/** + * @license + * Copyright 2025 Qwen Team + * SPDX-License-Identifier: Apache-2.0 + * + * ExecuteNode tool call component - specialized for node/npm execution operations + */ + +import type React from 'react'; +import type { BaseToolCallProps } from '../shared/types.js'; +import { ToolCallContainer } from '../shared/LayoutComponents.js'; +import { safeTitle, groupContent } from '../shared/utils.js'; +import './ExecuteNode.css'; + +/** + * Specialized component for ExecuteNode tool calls + * Shows: Execute bullet + description + branch connector + */ +export const ExecuteNodeToolCall: React.FC = ({ + toolCall, +}) => { + const { title, content, rawInput, toolCallId } = toolCall; + const commandText = safeTitle(title); + + // Group content by type + const { textOutputs, errors } = groupContent(content); + + // Extract command from rawInput if available + let _inputCommand = commandText; + if (rawInput && typeof rawInput === 'object') { + const inputObj = rawInput as { command?: string }; + _inputCommand = inputObj.command || commandText; + } else if (typeof rawInput === 'string') { + _inputCommand = rawInput; + } + + // Error case + if (errors.length > 0) { + return ( + + {/* Branch connector summary (Claude-like) */} +
+ + {commandText} +
+ {/* Error content */} +
+
+            {errors.join('\n')}
+          
+
+
+ ); + } + + // Success case: show command with branch connector (similar to the example) + return ( + +
+ + {commandText} +
+ {textOutputs.length > 0 && ( +
+
{textOutputs.join('\n')}
+
+ )} +
+ ); +}; diff --git a/packages/vscode-ide-companion/src/webview/components/toolcalls/Read/ReadToolCall.tsx b/packages/vscode-ide-companion/src/webview/components/toolcalls/Read/ReadToolCall.tsx new file mode 100644 index 00000000..4ecd63b2 --- /dev/null +++ b/packages/vscode-ide-companion/src/webview/components/toolcalls/Read/ReadToolCall.tsx @@ -0,0 +1,76 @@ +/** + * @license + * Copyright 2025 Qwen Team + * SPDX-License-Identifier: Apache-2.0 + * + * Read tool call component - specialized for file reading operations + */ + +import type React from 'react'; +import type { BaseToolCallProps } from '../shared/types.js'; +import { ToolCallContainer } from '../shared/LayoutComponents.js'; +import { groupContent } from '../shared/utils.js'; +import { FileLink } from '../../ui/FileLink.js'; + +/** + * Specialized component for Read tool calls + * Optimized for displaying file reading operations + * Shows: Read filename (no content preview) + */ +export const ReadToolCall: React.FC = ({ toolCall }) => { + const { content, locations, toolCallId } = toolCall; + + // Group content by type + const { errors } = groupContent(content); + + // Error case: show error + if (errors.length > 0) { + const path = locations?.[0]?.path || ''; + return ( + + ) : undefined + } + > + {errors.join('\n')} + + ); + } + + // Success case: show which file was read with filename in label + if (locations && locations.length > 0) { + const path = locations[0].path; + return ( + + ) : undefined + } + > + {null} + + ); + } + + // No file info, don't show + return null; +}; diff --git a/packages/vscode-ide-companion/src/webview/components/toolcalls/UpdatedPlan/UpdatedPlanToolCall.tsx b/packages/vscode-ide-companion/src/webview/components/toolcalls/UpdatedPlan/UpdatedPlanToolCall.tsx new file mode 100644 index 00000000..d08b3ab5 --- /dev/null +++ b/packages/vscode-ide-companion/src/webview/components/toolcalls/UpdatedPlan/UpdatedPlanToolCall.tsx @@ -0,0 +1,140 @@ +/** + * @license + * Copyright 2025 Qwen Team + * SPDX-License-Identifier: Apache-2.0 + * + * UpdatedPlan tool call component - specialized for plan update operations + */ + +import type React from 'react'; +import type { BaseToolCallProps } from '../shared/types.js'; +import { ToolCallContainer } from '../shared/LayoutComponents.js'; +import { groupContent, safeTitle } from '../shared/utils.js'; +import { CheckboxDisplay } from '../../ui/CheckboxDisplay.js'; + +type EntryStatus = 'pending' | 'in_progress' | 'completed'; + +interface PlanEntry { + content: string; + status: EntryStatus; +} + +const mapToolStatusToBullet = ( + status: import('../shared/types.js').ToolCallStatus, +): 'success' | 'error' | 'warning' | 'loading' | 'default' => { + switch (status) { + case 'completed': + return 'success'; + case 'failed': + return 'error'; + case 'in_progress': + return 'warning'; + case 'pending': + return 'loading'; + default: + return 'default'; + } +}; + +// Parse plan entries with - [ ] / - [x] from text as much as possible +const parsePlanEntries = (textOutputs: string[]): PlanEntry[] => { + const text = textOutputs.join('\n'); + const lines = text.split(/\r?\n/); + const entries: PlanEntry[] = []; + + const todoRe = /^(?:\s*(?:[-*]|\d+[.)])\s*)?\[( |x|X|-)\]\s+(.*)$/; + for (const line of lines) { + const m = line.match(todoRe); + if (m) { + const mark = m[1]; + const title = m[2].trim(); + const status: EntryStatus = + mark === 'x' || mark === 'X' + ? 'completed' + : mark === '-' + ? 'in_progress' + : 'pending'; + if (title) { + entries.push({ content: title, status }); + } + } + } + + // If no match is found, fall back to treating non-empty lines as pending items + if (entries.length === 0) { + for (const line of lines) { + const title = line.trim(); + if (title) { + entries.push({ content: title, status: 'pending' }); + } + } + } + + return entries; +}; + +/** + * Specialized component for UpdatedPlan tool calls + * Optimized for displaying plan update operations + */ +export const UpdatedPlanToolCall: React.FC = ({ + toolCall, +}) => { + const { content, status } = toolCall; + const { errors, textOutputs } = groupContent(content); + + // Error-first display + if (errors.length > 0) { + return ( + + {errors.join('\n')} + + ); + } + + const entries = parsePlanEntries(textOutputs); + + const label = safeTitle(toolCall.title) || 'Updated Plan'; + + return ( + +
    + {entries.map((entry, idx) => { + const isDone = entry.status === 'completed'; + const isIndeterminate = entry.status === 'in_progress'; + return ( +
  • + + +
    + {entry.content} +
    +
  • + ); + })} +
+
+ ); +}; diff --git a/packages/vscode-ide-companion/src/webview/components/toolcalls/index.tsx b/packages/vscode-ide-companion/src/webview/components/toolcalls/index.tsx index 7f1ea623..3ba0be4f 100644 --- a/packages/vscode-ide-companion/src/webview/components/toolcalls/index.tsx +++ b/packages/vscode-ide-companion/src/webview/components/toolcalls/index.tsx @@ -10,11 +10,13 @@ import type React from 'react'; import type { BaseToolCallProps } from './shared/types.js'; import { shouldShowToolCall } from './shared/utils.js'; import { GenericToolCall } from './GenericToolCall.js'; -import { ReadToolCall } from './ReadToolCall.js'; +import { ReadToolCall } from './Read/ReadToolCall.js'; import { WriteToolCall } from './WriteToolCall.js'; import { EditToolCall } from './Edit/EditToolCall.js'; import { ExecuteToolCall as BashExecuteToolCall } from './Bash/Bash.js'; import { ExecuteToolCall } from './Execute/Execute.js'; +import { UpdatedPlanToolCall } from './UpdatedPlan/UpdatedPlanToolCall.js'; +import { ExecuteNodeToolCall } from './ExecuteNode/ExecuteNodeToolCall.js'; import { SearchToolCall } from './SearchToolCall.js'; import { ThinkToolCall } from './ThinkToolCall.js'; import { TodoWriteToolCall } from './TodoWriteToolCall.js'; @@ -24,6 +26,7 @@ import { TodoWriteToolCall } from './TodoWriteToolCall.js'; */ export const getToolCallComponent = ( kind: string, + toolCall?: import('./shared/types.js').ToolCallData, ): React.FC => { const normalizedKind = kind.toLowerCase(); @@ -39,12 +42,35 @@ export const getToolCallComponent = ( return EditToolCall; case 'execute': + // Check if this is a node/npm version check command + if (toolCall) { + const commandText = + typeof toolCall.rawInput === 'string' + ? toolCall.rawInput + : typeof toolCall.rawInput === 'object' && + toolCall.rawInput !== null + ? (toolCall.rawInput as { command?: string }).command || '' + : ''; + + // TODO: + if ( + commandText.includes('node --version') || + commandText.includes('npm --version') + ) { + return ExecuteNodeToolCall; + } + } return ExecuteToolCall; case 'bash': case 'command': return BashExecuteToolCall; + case 'updated_plan': + case 'updatedplan': + case 'todo_write': + return UpdatedPlanToolCall; + case 'search': case 'grep': case 'glob': @@ -56,7 +82,8 @@ export const getToolCallComponent = ( return ThinkToolCall; case 'todowrite': - case 'todo_write': + return TodoWriteToolCall; + // case 'todo_write': case 'update_todos': return TodoWriteToolCall; @@ -82,7 +109,7 @@ export const ToolCallRouter: React.FC = ({ toolCall }) => { } // Get the appropriate component for this kind - const Component = getToolCallComponent(toolCall.kind); + const Component = getToolCallComponent(toolCall.kind, toolCall); // Render the specialized component return ; diff --git a/packages/vscode-ide-companion/src/webview/components/toolcalls/shared/LayoutComponents.tsx b/packages/vscode-ide-companion/src/webview/components/toolcalls/shared/LayoutComponents.tsx index 8d0395cd..2d54c5b2 100644 --- a/packages/vscode-ide-companion/src/webview/components/toolcalls/shared/LayoutComponents.tsx +++ b/packages/vscode-ide-companion/src/webview/components/toolcalls/shared/LayoutComponents.tsx @@ -25,6 +25,8 @@ interface ToolCallContainerProps { toolCallId?: string; /** Optional trailing content rendered next to label (e.g., clickable filename) */ labelSuffix?: React.ReactNode; + /** Optional custom class name */ + className?: string; } // NOTE: We previously computed a bullet color class in JS, but the current @@ -40,9 +42,10 @@ export const ToolCallContainer: React.FC = ({ children, toolCallId: _toolCallId, labelSuffix, + className: _className, }) => (
{/* Timeline connector line using ::after pseudo-element */}
@@ -50,15 +53,18 @@ export const ToolCallContainer: React.FC = ({ {label} - {/* {toolCallId && ( + {/* TODO: for 调试 */} + {_toolCallId && ( - [{toolCallId.slice(-8)}] + [{_toolCallId.slice(-8)}] - )} */} + )} {labelSuffix}
{children && ( -
{children}
+
+ {children} +
)}