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 type { BaseToolCallProps } from '../shared/types.js';
|
||||
import { ToolCallContainer } from '../shared/LayoutComponents.js';
|
||||
import type { BaseToolCallProps } from '../../shared/types.js';
|
||||
import {
|
||||
groupContent,
|
||||
mapToolStatusToContainerStatus,
|
||||
} from '../shared/utils.js';
|
||||
import { useVSCode } from '../../../hooks/useVSCode.js';
|
||||
import { FileLink } from '../../ui/FileLink.js';
|
||||
import { handleOpenDiff } from '../../../utils/diffUtils.js';
|
||||
} from '../../shared/utils.js';
|
||||
import { FileLink } from '../../../ui/FileLink.js';
|
||||
import type { ToolCallContainerProps } from '../../shared/LayoutComponents.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)
|
||||
@@ -58,9 +85,6 @@ export const EditToolCall: React.FC<BaseToolCallProps> = ({ toolCall }) => {
|
||||
[vscode],
|
||||
);
|
||||
|
||||
// Extract filename from path
|
||||
const getFileName = (path: string): string => path.split('/').pop() || path;
|
||||
|
||||
// Automatically trigger openDiff when diff content is detected
|
||||
// Only trigger once per tool call by checking toolCallId
|
||||
useEffect(() => {
|
||||
@@ -88,10 +112,9 @@ export const EditToolCall: React.FC<BaseToolCallProps> = ({ toolCall }) => {
|
||||
// Error case: show error
|
||||
if (errors.length > 0) {
|
||||
const path = diffs[0]?.path || locations?.[0]?.path || '';
|
||||
const fileName = path ? getFileName(path) : '';
|
||||
return (
|
||||
<ToolCallContainer
|
||||
label={fileName ? 'Edit' : 'Edit'}
|
||||
label={'Edit'}
|
||||
status="error"
|
||||
toolCallId={toolCallId}
|
||||
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. */}
|
||||
<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 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 */}
|
||||
<span className="text-[14px] leading-none font-bold text-[var(--app-primary-foreground)]">
|
||||
Edit
|
||||
@@ -137,7 +160,7 @@ export const EditToolCall: React.FC<BaseToolCallProps> = ({ toolCall }) => {
|
||||
)}
|
||||
</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 w-full">{summary}</span>
|
||||
</div>
|
||||
@@ -148,13 +171,19 @@ export const EditToolCall: React.FC<BaseToolCallProps> = ({ toolCall }) => {
|
||||
|
||||
// Success case without diff: show file in compact format
|
||||
if (locations && locations.length > 0) {
|
||||
const fileName = getFileName(locations[0].path);
|
||||
const containerStatus = mapToolStatusToContainerStatus(toolCall.status);
|
||||
return (
|
||||
<ToolCallContainer
|
||||
label={`Edited ${fileName}`}
|
||||
label={`Edit`}
|
||||
status={containerStatus}
|
||||
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">
|
||||
<span className="flex-shrink-0 relative top-[-0.1em]">⎿</span>
|
||||
@@ -99,4 +99,4 @@
|
||||
/* Error content styling */
|
||||
.execute-toolcall-error-content {
|
||||
color: #c74e39;
|
||||
}
|
||||
}
|
||||
@@ -7,10 +7,37 @@
|
||||
*/
|
||||
|
||||
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 type { BaseToolCallProps } from '../../shared/types.js';
|
||||
import { safeTitle, groupContent } from '../../shared/utils.js';
|
||||
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
|
||||
@@ -18,7 +45,9 @@ import './Execute.css';
|
||||
*/
|
||||
export const ExecuteToolCall: React.FC<BaseToolCallProps> = ({ 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
|
||||
const { textOutputs, errors } = groupContent(content);
|
||||
@@ -26,8 +55,8 @@ export const ExecuteToolCall: React.FC<BaseToolCallProps> = ({ toolCall }) => {
|
||||
// Extract command from rawInput if available
|
||||
let inputCommand = commandText;
|
||||
if (rawInput && typeof rawInput === 'object') {
|
||||
const inputObj = rawInput as { command?: string };
|
||||
inputCommand = inputObj.command || commandText;
|
||||
const inputObj = rawInput as Record<string, unknown>;
|
||||
inputCommand = (inputObj.command as string | undefined) || commandText;
|
||||
} else if (typeof rawInput === 'string') {
|
||||
inputCommand = rawInput;
|
||||
}
|
||||
@@ -8,15 +8,44 @@
|
||||
|
||||
import type React from 'react';
|
||||
import { useCallback, useEffect, useMemo } from 'react';
|
||||
import type { BaseToolCallProps } from '../shared/types.js';
|
||||
import { ToolCallContainer } from '../shared/LayoutComponents.js';
|
||||
import type { BaseToolCallProps } from '../../shared/types.js';
|
||||
import {
|
||||
groupContent,
|
||||
mapToolStatusToContainerStatus,
|
||||
} from '../shared/utils.js';
|
||||
import { FileLink } from '../../ui/FileLink.js';
|
||||
import { useVSCode } from '../../../hooks/useVSCode.js';
|
||||
import { handleOpenDiff } from '../../../utils/diffUtils.js';
|
||||
} from '../../shared/utils.js';
|
||||
import { FileLink } from '../../../ui/FileLink.js';
|
||||
import { useVSCode } from '../../../../hooks/useVSCode.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
|
||||
@@ -7,18 +7,46 @@
|
||||
*/
|
||||
|
||||
import type React from 'react';
|
||||
import type { BaseToolCallProps } from '../shared/types.js';
|
||||
import type { BaseToolCallProps } from '../../shared/types.js';
|
||||
import {
|
||||
ToolCallContainer,
|
||||
ToolCallCard,
|
||||
ToolCallRow,
|
||||
LocationsList,
|
||||
} from '../shared/LayoutComponents.js';
|
||||
} from '../../shared/LayoutComponents.js';
|
||||
import {
|
||||
safeTitle,
|
||||
groupContent,
|
||||
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
|
||||
@@ -10,14 +10,14 @@ 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 './Read/ReadToolCall.js';
|
||||
import { ReadToolCall } from './done/Read/ReadToolCall.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 } from './Execute/Execute.js';
|
||||
import { ExecuteToolCall } from './done/Execute/Execute.js';
|
||||
import { UpdatedPlanToolCall } from './UpdatedPlan/UpdatedPlanToolCall.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';
|
||||
|
||||
/**
|
||||
@@ -92,7 +92,9 @@ export const getToolCallComponent = (
|
||||
/**
|
||||
* 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)
|
||||
if (!shouldShowToolCall(toolCall.kind)) {
|
||||
return null;
|
||||
@@ -102,7 +104,7 @@ export const ToolCallRouter: React.FC<BaseToolCallProps> = ({ toolCall }) => {
|
||||
const Component = getToolCallComponent(toolCall.kind, toolCall);
|
||||
|
||||
// Render the specialized component
|
||||
return <Component toolCall={toolCall} />;
|
||||
return <Component toolCall={toolCall} isFirst={isFirst} isLast={isLast} />;
|
||||
};
|
||||
|
||||
// Re-export types for convenience
|
||||
|
||||
@@ -14,7 +14,7 @@ import './LayoutComponents.css';
|
||||
/**
|
||||
* Props for ToolCallContainer - Claude Code style layout
|
||||
*/
|
||||
interface ToolCallContainerProps {
|
||||
export interface ToolCallContainerProps {
|
||||
/** Operation label (e.g., "Read", "Write", "Search") */
|
||||
label: string;
|
||||
/** Status for bullet color: 'success' | 'error' | 'warning' | 'loading' | 'default' */
|
||||
|
||||
@@ -56,6 +56,8 @@ export interface ToolCallData {
|
||||
*/
|
||||
export interface BaseToolCallProps {
|
||||
toolCall: ToolCallData;
|
||||
isFirst?: boolean;
|
||||
isLast?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -20,7 +20,13 @@ export const formatValue = (value: unknown): string => {
|
||||
return '';
|
||||
}
|
||||
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
|
||||
if (value instanceof Error) {
|
||||
|
||||
Reference in New Issue
Block a user