mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-19 09:33:53 +00:00
refactor(vscode-ide-companion): restructure tool call components
Restructure tool call components with dedicated container implementations - Move tool call components to done subdirectory - Implement specialized ToolCallContainer for each tool type - Update component routing in ToolCallRouter - Add isFirst/isLast props for better layout control - Improve shared types and layout components
This commit is contained in:
@@ -7,15 +7,42 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { useEffect, useCallback, useMemo } from 'react';
|
import { useEffect, useCallback, useMemo } from 'react';
|
||||||
import type { BaseToolCallProps } from '../shared/types.js';
|
import type { BaseToolCallProps } from '../../shared/types.js';
|
||||||
import { ToolCallContainer } from '../shared/LayoutComponents.js';
|
|
||||||
import {
|
import {
|
||||||
groupContent,
|
groupContent,
|
||||||
mapToolStatusToContainerStatus,
|
mapToolStatusToContainerStatus,
|
||||||
} from '../shared/utils.js';
|
} from '../../shared/utils.js';
|
||||||
import { useVSCode } from '../../../hooks/useVSCode.js';
|
import { FileLink } from '../../../ui/FileLink.js';
|
||||||
import { FileLink } from '../../ui/FileLink.js';
|
import type { ToolCallContainerProps } from '../../shared/LayoutComponents.js';
|
||||||
import { handleOpenDiff } from '../../../utils/diffUtils.js';
|
import { useVSCode } from '../../../../hooks/useVSCode.js';
|
||||||
|
import { handleOpenDiff } from '../../../../utils/diffUtils.js';
|
||||||
|
|
||||||
|
export const ToolCallContainer: React.FC<ToolCallContainerProps> = ({
|
||||||
|
label,
|
||||||
|
status = 'success',
|
||||||
|
children,
|
||||||
|
toolCallId: _toolCallId,
|
||||||
|
labelSuffix,
|
||||||
|
className: _className,
|
||||||
|
}) => (
|
||||||
|
<div
|
||||||
|
className={`qwen-message message-item ${_className || ''} relative pl-[30px] py-2 select-text toolcall-container toolcall-status-${status}`}
|
||||||
|
>
|
||||||
|
<div className="EditToolCall toolcall-content-wrapper flex flex-col gap-1 min-w-0 max-w-full">
|
||||||
|
<div className="flex items-baseline gap-1.5 relative min-w-0">
|
||||||
|
<span className="text-[14px] leading-none font-bold text-[var(--app-primary-foreground)]">
|
||||||
|
{label}
|
||||||
|
</span>
|
||||||
|
<span className="text-[11px] text-[var(--app-secondary-foreground)]">
|
||||||
|
{labelSuffix}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{children && (
|
||||||
|
<div className="text-[var(--app-secondary-foreground)]">{children}</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculate diff summary (added/removed lines)
|
* Calculate diff summary (added/removed lines)
|
||||||
@@ -58,9 +85,6 @@ export const EditToolCall: React.FC<BaseToolCallProps> = ({ toolCall }) => {
|
|||||||
[vscode],
|
[vscode],
|
||||||
);
|
);
|
||||||
|
|
||||||
// Extract filename from path
|
|
||||||
const getFileName = (path: string): string => path.split('/').pop() || path;
|
|
||||||
|
|
||||||
// Automatically trigger openDiff when diff content is detected
|
// Automatically trigger openDiff when diff content is detected
|
||||||
// Only trigger once per tool call by checking toolCallId
|
// Only trigger once per tool call by checking toolCallId
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -88,10 +112,9 @@ export const EditToolCall: React.FC<BaseToolCallProps> = ({ toolCall }) => {
|
|||||||
// Error case: show error
|
// Error case: show error
|
||||||
if (errors.length > 0) {
|
if (errors.length > 0) {
|
||||||
const path = diffs[0]?.path || locations?.[0]?.path || '';
|
const path = diffs[0]?.path || locations?.[0]?.path || '';
|
||||||
const fileName = path ? getFileName(path) : '';
|
|
||||||
return (
|
return (
|
||||||
<ToolCallContainer
|
<ToolCallContainer
|
||||||
label={fileName ? 'Edit' : 'Edit'}
|
label={'Edit'}
|
||||||
status="error"
|
status="error"
|
||||||
toolCallId={toolCallId}
|
toolCallId={toolCallId}
|
||||||
labelSuffix={
|
labelSuffix={
|
||||||
@@ -123,7 +146,7 @@ export const EditToolCall: React.FC<BaseToolCallProps> = ({ toolCall }) => {
|
|||||||
{/* IMPORTANT: Always include min-w-0/max-w-full on inner wrappers to prevent overflow. */}
|
{/* IMPORTANT: Always include min-w-0/max-w-full on inner wrappers to prevent overflow. */}
|
||||||
<div className="toolcall-edit-content flex flex-col gap-1 min-w-0 max-w-full">
|
<div className="toolcall-edit-content flex flex-col gap-1 min-w-0 max-w-full">
|
||||||
<div className="flex items-center justify-between min-w-0">
|
<div className="flex items-center justify-between min-w-0">
|
||||||
<div className="flex items-center gap-2 min-w-0">
|
<div className="flex items-baseline gap-1.5 min-w-0">
|
||||||
{/* Align the inline Edit label styling with shared toolcall label: larger + bold */}
|
{/* Align the inline Edit label styling with shared toolcall label: larger + bold */}
|
||||||
<span className="text-[14px] leading-none font-bold text-[var(--app-primary-foreground)]">
|
<span className="text-[14px] leading-none font-bold text-[var(--app-primary-foreground)]">
|
||||||
Edit
|
Edit
|
||||||
@@ -137,7 +160,7 @@ export const EditToolCall: React.FC<BaseToolCallProps> = ({ toolCall }) => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="inline-flex text-[var(--app-secondary-foreground)] text-[0.85em] opacity-70 flex-row items-start w-full gap-1 flex items-center">
|
<div className="inline-flex text-[var(--app-secondary-foreground)] text-[0.85em] opacity-70 flex-row items-start w-full gap-1 flex items-baseline">
|
||||||
<span className="flex-shrink-0 relative top-[-0.1em]">⎿</span>
|
<span className="flex-shrink-0 relative top-[-0.1em]">⎿</span>
|
||||||
<span className="flex-shrink-0 w-full">{summary}</span>
|
<span className="flex-shrink-0 w-full">{summary}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -148,13 +171,19 @@ export const EditToolCall: React.FC<BaseToolCallProps> = ({ toolCall }) => {
|
|||||||
|
|
||||||
// Success case without diff: show file in compact format
|
// Success case without diff: show file in compact format
|
||||||
if (locations && locations.length > 0) {
|
if (locations && locations.length > 0) {
|
||||||
const fileName = getFileName(locations[0].path);
|
|
||||||
const containerStatus = mapToolStatusToContainerStatus(toolCall.status);
|
const containerStatus = mapToolStatusToContainerStatus(toolCall.status);
|
||||||
return (
|
return (
|
||||||
<ToolCallContainer
|
<ToolCallContainer
|
||||||
label={`Edited ${fileName}`}
|
label={`Edit`}
|
||||||
status={containerStatus}
|
status={containerStatus}
|
||||||
toolCallId={toolCallId}
|
toolCallId={toolCallId}
|
||||||
|
labelSuffix={
|
||||||
|
<FileLink
|
||||||
|
path={locations[0].path}
|
||||||
|
showFullPath={false}
|
||||||
|
className="text-xs font-mono text-[var(--app-secondary-foreground)] hover:underline"
|
||||||
|
/>
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<div className="inline-flex text-[var(--app-secondary-foreground)] text-[0.85em] opacity-70 flex-row items-start w-full gap-1 flex items-center">
|
<div className="inline-flex text-[var(--app-secondary-foreground)] text-[0.85em] opacity-70 flex-row items-start w-full gap-1 flex items-center">
|
||||||
<span className="flex-shrink-0 relative top-[-0.1em]">⎿</span>
|
<span className="flex-shrink-0 relative top-[-0.1em]">⎿</span>
|
||||||
@@ -99,4 +99,4 @@
|
|||||||
/* Error content styling */
|
/* Error content styling */
|
||||||
.execute-toolcall-error-content {
|
.execute-toolcall-error-content {
|
||||||
color: #c74e39;
|
color: #c74e39;
|
||||||
}
|
}
|
||||||
@@ -7,10 +7,37 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import type React from 'react';
|
import type React from 'react';
|
||||||
import type { BaseToolCallProps } from '../shared/types.js';
|
import type { BaseToolCallProps } from '../../shared/types.js';
|
||||||
import { ToolCallContainer } from '../shared/LayoutComponents.js';
|
import { safeTitle, groupContent } from '../../shared/utils.js';
|
||||||
import { safeTitle, groupContent } from '../shared/utils.js';
|
|
||||||
import './Execute.css';
|
import './Execute.css';
|
||||||
|
import type { ToolCallContainerProps } from '../../shared/LayoutComponents.js';
|
||||||
|
|
||||||
|
export const ToolCallContainer: React.FC<ToolCallContainerProps> = ({
|
||||||
|
label,
|
||||||
|
status = 'success',
|
||||||
|
children,
|
||||||
|
toolCallId: _toolCallId,
|
||||||
|
labelSuffix,
|
||||||
|
className: _className,
|
||||||
|
}) => (
|
||||||
|
<div
|
||||||
|
className={`ExecuteToolCall qwen-message message-item ${_className || ''} relative pl-[30px] py-2 select-text toolcall-container toolcall-status-${status}`}
|
||||||
|
>
|
||||||
|
<div className="toolcall-content-wrapper flex flex-col gap-0 min-w-0 max-w-full">
|
||||||
|
<div className="flex items-baseline gap-1.5 relative min-w-0">
|
||||||
|
<span className="text-[14px] leading-none font-bold text-[var(--app-primary-foreground)]">
|
||||||
|
{label}
|
||||||
|
</span>
|
||||||
|
<span className="text-[11px] text-[var(--app-secondary-foreground)]">
|
||||||
|
{labelSuffix}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{children && (
|
||||||
|
<div className="text-[var(--app-secondary-foreground)]">{children}</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Specialized component for Execute tool calls
|
* Specialized component for Execute tool calls
|
||||||
@@ -18,7 +45,9 @@ import './Execute.css';
|
|||||||
*/
|
*/
|
||||||
export const ExecuteToolCall: React.FC<BaseToolCallProps> = ({ toolCall }) => {
|
export const ExecuteToolCall: React.FC<BaseToolCallProps> = ({ toolCall }) => {
|
||||||
const { title, content, rawInput, toolCallId } = toolCall;
|
const { title, content, rawInput, toolCallId } = toolCall;
|
||||||
const commandText = safeTitle(title);
|
const commandText = safeTitle(
|
||||||
|
(rawInput as Record<string, unknown>)?.description || title,
|
||||||
|
);
|
||||||
|
|
||||||
// Group content by type
|
// Group content by type
|
||||||
const { textOutputs, errors } = groupContent(content);
|
const { textOutputs, errors } = groupContent(content);
|
||||||
@@ -26,8 +55,8 @@ export const ExecuteToolCall: React.FC<BaseToolCallProps> = ({ toolCall }) => {
|
|||||||
// Extract command from rawInput if available
|
// Extract command from rawInput if available
|
||||||
let inputCommand = commandText;
|
let inputCommand = commandText;
|
||||||
if (rawInput && typeof rawInput === 'object') {
|
if (rawInput && typeof rawInput === 'object') {
|
||||||
const inputObj = rawInput as { command?: string };
|
const inputObj = rawInput as Record<string, unknown>;
|
||||||
inputCommand = inputObj.command || commandText;
|
inputCommand = (inputObj.command as string | undefined) || commandText;
|
||||||
} else if (typeof rawInput === 'string') {
|
} else if (typeof rawInput === 'string') {
|
||||||
inputCommand = rawInput;
|
inputCommand = rawInput;
|
||||||
}
|
}
|
||||||
@@ -8,15 +8,44 @@
|
|||||||
|
|
||||||
import type React from 'react';
|
import type React from 'react';
|
||||||
import { useCallback, useEffect, useMemo } from 'react';
|
import { useCallback, useEffect, useMemo } from 'react';
|
||||||
import type { BaseToolCallProps } from '../shared/types.js';
|
import type { BaseToolCallProps } from '../../shared/types.js';
|
||||||
import { ToolCallContainer } from '../shared/LayoutComponents.js';
|
|
||||||
import {
|
import {
|
||||||
groupContent,
|
groupContent,
|
||||||
mapToolStatusToContainerStatus,
|
mapToolStatusToContainerStatus,
|
||||||
} from '../shared/utils.js';
|
} from '../../shared/utils.js';
|
||||||
import { FileLink } from '../../ui/FileLink.js';
|
import { FileLink } from '../../../ui/FileLink.js';
|
||||||
import { useVSCode } from '../../../hooks/useVSCode.js';
|
import { useVSCode } from '../../../../hooks/useVSCode.js';
|
||||||
import { handleOpenDiff } from '../../../utils/diffUtils.js';
|
import { handleOpenDiff } from '../../../../utils/diffUtils.js';
|
||||||
|
import type { ToolCallContainerProps } from '../../shared/LayoutComponents.js';
|
||||||
|
|
||||||
|
export const ToolCallContainer: React.FC<ToolCallContainerProps> = ({
|
||||||
|
label,
|
||||||
|
status = 'success',
|
||||||
|
children,
|
||||||
|
toolCallId: _toolCallId,
|
||||||
|
labelSuffix,
|
||||||
|
className: _className,
|
||||||
|
}) => (
|
||||||
|
<div
|
||||||
|
className={`ReadToolCall qwen-message message-item ${_className || ''} relative pl-[30px] py-2 select-text toolcall-container toolcall-status-${status}`}
|
||||||
|
>
|
||||||
|
<div className="toolcall-content-wrapper flex flex-col gap-1 min-w-0 max-w-full">
|
||||||
|
<div className="flex items-baseline gap-1.5 relative min-w-0">
|
||||||
|
<span className="text-[14px] leading-none font-bold text-[var(--app-primary-foreground)]">
|
||||||
|
{label}
|
||||||
|
</span>
|
||||||
|
<span className="text-[11px] text-[var(--app-secondary-foreground)]">
|
||||||
|
{labelSuffix}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{children && (
|
||||||
|
<div className="text-[var(--app-secondary-foreground)] py-1">
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Specialized component for Read tool calls
|
* Specialized component for Read tool calls
|
||||||
@@ -7,18 +7,46 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import type React from 'react';
|
import type React from 'react';
|
||||||
import type { BaseToolCallProps } from '../shared/types.js';
|
import type { BaseToolCallProps } from '../../shared/types.js';
|
||||||
import {
|
import {
|
||||||
ToolCallContainer,
|
|
||||||
ToolCallCard,
|
ToolCallCard,
|
||||||
ToolCallRow,
|
ToolCallRow,
|
||||||
LocationsList,
|
LocationsList,
|
||||||
} from '../shared/LayoutComponents.js';
|
} from '../../shared/LayoutComponents.js';
|
||||||
import {
|
import {
|
||||||
safeTitle,
|
safeTitle,
|
||||||
groupContent,
|
groupContent,
|
||||||
mapToolStatusToContainerStatus,
|
mapToolStatusToContainerStatus,
|
||||||
} from '../shared/utils.js';
|
} from '../../shared/utils.js';
|
||||||
|
|
||||||
|
import type { ToolCallContainerProps } from '../../shared/LayoutComponents.js';
|
||||||
|
|
||||||
|
export const ToolCallContainer: React.FC<ToolCallContainerProps> = ({
|
||||||
|
label,
|
||||||
|
status = 'success',
|
||||||
|
children,
|
||||||
|
toolCallId: _toolCallId,
|
||||||
|
labelSuffix,
|
||||||
|
className: _className,
|
||||||
|
}) => (
|
||||||
|
<div
|
||||||
|
className={`SearchToolCall qwen-message message-item ${_className || ''} relative pl-[30px] py-2 select-text toolcall-container toolcall-status-${status}`}
|
||||||
|
>
|
||||||
|
<div className="toolcall-content-wrapper flex flex-col gap-0 min-w-0 max-w-full">
|
||||||
|
<div className="flex items-baseline gap-1.5 relative min-w-0">
|
||||||
|
<span className="text-[14px] leading-none font-bold text-[var(--app-primary-foreground)]">
|
||||||
|
{label}
|
||||||
|
</span>
|
||||||
|
<span className="text-[11px] text-[var(--app-secondary-foreground)]">
|
||||||
|
{labelSuffix}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{children && (
|
||||||
|
<div className="text-[var(--app-secondary-foreground)]">{children}</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Specialized component for Search tool calls
|
* Specialized component for Search tool calls
|
||||||
@@ -10,14 +10,14 @@ import type React from 'react';
|
|||||||
import type { BaseToolCallProps } from './shared/types.js';
|
import type { BaseToolCallProps } from './shared/types.js';
|
||||||
import { shouldShowToolCall } from './shared/utils.js';
|
import { shouldShowToolCall } from './shared/utils.js';
|
||||||
import { GenericToolCall } from './GenericToolCall.js';
|
import { GenericToolCall } from './GenericToolCall.js';
|
||||||
import { ReadToolCall } from './Read/ReadToolCall.js';
|
import { ReadToolCall } from './done/Read/ReadToolCall.js';
|
||||||
import { WriteToolCall } from './Write/WriteToolCall.js';
|
import { WriteToolCall } from './Write/WriteToolCall.js';
|
||||||
import { EditToolCall } from './Edit/EditToolCall.js';
|
import { EditToolCall } from './done/Edit/EditToolCall.js';
|
||||||
import { ExecuteToolCall as BashExecuteToolCall } from './Bash/Bash.js';
|
import { ExecuteToolCall as BashExecuteToolCall } from './Bash/Bash.js';
|
||||||
import { ExecuteToolCall } from './Execute/Execute.js';
|
import { ExecuteToolCall } from './done/Execute/Execute.js';
|
||||||
import { UpdatedPlanToolCall } from './UpdatedPlan/UpdatedPlanToolCall.js';
|
import { UpdatedPlanToolCall } from './UpdatedPlan/UpdatedPlanToolCall.js';
|
||||||
import { ExecuteNodeToolCall } from './ExecuteNode/ExecuteNodeToolCall.js';
|
import { ExecuteNodeToolCall } from './ExecuteNode/ExecuteNodeToolCall.js';
|
||||||
import { SearchToolCall } from './Search/SearchToolCall.js';
|
import { SearchToolCall } from './done/Search/SearchToolCall.js';
|
||||||
import { ThinkToolCall } from './Think/ThinkToolCall.js';
|
import { ThinkToolCall } from './Think/ThinkToolCall.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -92,7 +92,9 @@ export const getToolCallComponent = (
|
|||||||
/**
|
/**
|
||||||
* Main tool call component that routes to specialized implementations
|
* Main tool call component that routes to specialized implementations
|
||||||
*/
|
*/
|
||||||
export const ToolCallRouter: React.FC<BaseToolCallProps> = ({ toolCall }) => {
|
export const ToolCallRouter: React.FC<
|
||||||
|
BaseToolCallProps & { isFirst?: boolean; isLast?: boolean }
|
||||||
|
> = ({ toolCall, isFirst, isLast }) => {
|
||||||
// Check if we should show this tool call (hide internal ones)
|
// Check if we should show this tool call (hide internal ones)
|
||||||
if (!shouldShowToolCall(toolCall.kind)) {
|
if (!shouldShowToolCall(toolCall.kind)) {
|
||||||
return null;
|
return null;
|
||||||
@@ -102,7 +104,7 @@ export const ToolCallRouter: React.FC<BaseToolCallProps> = ({ toolCall }) => {
|
|||||||
const Component = getToolCallComponent(toolCall.kind, toolCall);
|
const Component = getToolCallComponent(toolCall.kind, toolCall);
|
||||||
|
|
||||||
// Render the specialized component
|
// Render the specialized component
|
||||||
return <Component toolCall={toolCall} />;
|
return <Component toolCall={toolCall} isFirst={isFirst} isLast={isLast} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Re-export types for convenience
|
// Re-export types for convenience
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import './LayoutComponents.css';
|
|||||||
/**
|
/**
|
||||||
* Props for ToolCallContainer - Claude Code style layout
|
* Props for ToolCallContainer - Claude Code style layout
|
||||||
*/
|
*/
|
||||||
interface ToolCallContainerProps {
|
export interface ToolCallContainerProps {
|
||||||
/** Operation label (e.g., "Read", "Write", "Search") */
|
/** Operation label (e.g., "Read", "Write", "Search") */
|
||||||
label: string;
|
label: string;
|
||||||
/** Status for bullet color: 'success' | 'error' | 'warning' | 'loading' | 'default' */
|
/** Status for bullet color: 'success' | 'error' | 'warning' | 'loading' | 'default' */
|
||||||
|
|||||||
@@ -56,6 +56,8 @@ export interface ToolCallData {
|
|||||||
*/
|
*/
|
||||||
export interface BaseToolCallProps {
|
export interface BaseToolCallProps {
|
||||||
toolCall: ToolCallData;
|
toolCall: ToolCallData;
|
||||||
|
isFirst?: boolean;
|
||||||
|
isLast?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -20,7 +20,13 @@ export const formatValue = (value: unknown): string => {
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
if (typeof value === 'string') {
|
if (typeof value === 'string') {
|
||||||
return value;
|
// TODO: 尝试从 string 取出 Output 部分
|
||||||
|
try {
|
||||||
|
value = (JSON.parse(value) as { output?: unknown }).output ?? value;
|
||||||
|
} catch (_error) {
|
||||||
|
// ignore JSON parse errors
|
||||||
|
}
|
||||||
|
return value as string;
|
||||||
}
|
}
|
||||||
// Handle Error objects specially
|
// Handle Error objects specially
|
||||||
if (value instanceof Error) {
|
if (value instanceof Error) {
|
||||||
|
|||||||
Reference in New Issue
Block a user