From be71976a1f2a8bab3f74649389639a282ab61fdb Mon Sep 17 00:00:00 2001 From: yiliang114 <1204183885@qq.com> Date: Mon, 8 Dec 2025 00:54:26 +0800 Subject: [PATCH] chore(vscode-ide-companion): refactor directory structure --- packages/vscode-ide-companion/esbuild.js | 4 +- packages/vscode-ide-companion/package.json | 2 +- .../src/agents/qwenTypes.ts | 75 ---- .../src/cli/cliContextManager.ts | 5 - .../src/commands/index.ts | 1 - .../src/{auth/index.ts => constants/auth.ts} | 0 .../vscode-ide-companion/src/extension.ts | 8 +- .../src/{acp => services}/acpConnection.ts | 6 +- .../src/{acp => services}/acpFileHandler.ts | 0 .../{acp => services}/acpMessageHandler.ts | 6 +- .../{acp => services}/acpSessionManager.ts | 14 +- .../{auth => services}/authStateManager.ts | 0 .../conversationStore.ts | 2 +- .../{agents => services}/qwenAgentManager.ts | 29 +- .../qwenConnectionHandler.ts | 10 +- .../src/services/qwenSessionManager.ts | 114 ------- .../qwenSessionUpdateHandler.ts | 7 +- .../src/{constants => types}/acpTypes.ts | 7 - .../completionItemTypes.ts} | 1 - .../src/{acp => types}/connectionTypes.ts | 33 +- .../src/types/qwenTypes.ts | 112 ++++++ .../vscode-ide-companion/src/webview/App.tsx | 26 +- .../src/webview/MessageHandler.ts | 4 +- .../src/webview/WebViewProvider.ts | 10 +- .../PermissionDrawer/PermissionDrawer.tsx | 18 +- .../webview/components/icons/SpecialIcons.tsx | 8 - .../{ui/layouts => layout}/ChatHeader.tsx | 2 +- .../{ui => layout}/CompletionMenu.tsx | 2 +- .../components/{ui => layout}/EmptyState.tsx | 0 .../components/{ui => layout}/FileLink.tsx | 0 .../components/{ui => layout}/InfoBanner.tsx | 0 .../components/{ => layout}/InputForm.tsx | 9 +- .../{session => layout}/SessionSelector.tsx | 0 .../webview/components/layouts/ChatHeader.tsx | 109 ------ .../messages/Assistant/AssistantMessage.tsx | 2 +- .../MarkdownRenderer/MarkdownRenderer.css | 8 +- .../MarkdownRenderer/MarkdownRenderer.tsx | 20 +- .../{ => messages}/MessageContent.tsx | 0 .../messages/QwenMessageTimeline.css | 61 ---- .../components/messages/StreamingMessage.tsx | 39 --- .../components/messages/ThinkingMessage.tsx | 2 +- .../components/messages/UserMessage.tsx | 2 +- .../src/webview/components/messages/index.tsx | 1 - .../{ => messages}/toolcalls/Bash/Bash.css | 0 .../{ => messages}/toolcalls/Bash/Bash.tsx | 4 +- .../toolcalls}/Edit/EditToolCall.tsx | 34 +- .../toolcalls}/Execute/Execute.css | 0 .../toolcalls}/Execute/Execute.tsx | 6 +- .../toolcalls/GenericToolCall.tsx | 28 +- .../toolcalls}/Read/ReadToolCall.tsx | 8 +- .../toolcalls}/Search/SearchToolCall.tsx | 8 +- .../toolcalls/Think/ThinkToolCall.tsx | 0 .../{ => messages/toolcalls}/ToolCall.tsx | 21 +- .../UpdatedPlan}/CheckboxDisplay.tsx | 0 .../UpdatedPlan/UpdatedPlanToolCall.tsx | 4 +- .../toolcalls/Write/WriteToolCall.tsx | 2 +- .../{ => messages}/toolcalls/index.tsx | 29 +- .../toolcalls/shared/LayoutComponents.css | 0 .../toolcalls/shared/LayoutComponents.tsx | 2 +- .../{ => messages}/toolcalls/shared/types.ts | 0 .../{ => messages}/toolcalls/shared/utils.ts | 2 +- .../toolcalls/ExecuteNode/ExecuteNode.css | 38 --- .../ExecuteNode/ExecuteNodeToolCall.tsx | 83 ----- .../toolcalls/Think/TodoWriteToolCall.tsx | 137 -------- .../toolcalls/shared/DiffDisplay.css | 322 ------------------ .../toolcalls/shared/DiffDisplay.tsx | 160 --------- .../toolcalls/shared/SimpleTimeline.css | 70 ---- .../webview/handlers/BaseMessageHandler.ts | 4 +- .../src/webview/handlers/MessageRouter.ts | 4 +- .../webview/handlers/SessionMessageHandler.ts | 11 +- .../hooks/message/useMessageHandling.ts | 8 +- .../hooks/session/useSessionManagement.ts | 4 +- .../src/webview/hooks/useCompletionTrigger.ts | 2 +- .../src/webview/hooks/useToolCalls.ts | 4 +- .../src/webview/hooks/useWebViewMessages.ts | 6 +- .../src/webview/index.tsx | 2 +- .../{ClaudeCodeStyles.css => styles.css} | 12 +- .../src/webview/styles/timeline.css | 126 +++++++ .../src/webview/types/toolCall.ts | 39 --- .../src/webview/utils/diffUtils.ts | 4 +- .../vscode-ide-companion/tailwind.config.js | 6 +- 81 files changed, 409 insertions(+), 1540 deletions(-) delete mode 100644 packages/vscode-ide-companion/src/agents/qwenTypes.ts rename packages/vscode-ide-companion/src/{auth/index.ts => constants/auth.ts} (100%) rename packages/vscode-ide-companion/src/{acp => services}/acpConnection.ts (98%) rename packages/vscode-ide-companion/src/{acp => services}/acpFileHandler.ts (100%) rename packages/vscode-ide-companion/src/{acp => services}/acpMessageHandler.ts (98%) rename packages/vscode-ide-companion/src/{acp => services}/acpSessionManager.ts (97%) rename packages/vscode-ide-companion/src/{auth => services}/authStateManager.ts (100%) rename packages/vscode-ide-companion/src/{storage => services}/conversationStore.ts (97%) rename packages/vscode-ide-companion/src/{agents => services}/qwenAgentManager.ts (98%) rename packages/vscode-ide-companion/src/{agents => services}/qwenConnectionHandler.ts (97%) rename packages/vscode-ide-companion/src/{agents => services}/qwenSessionUpdateHandler.ts (97%) rename packages/vscode-ide-companion/src/{constants => types}/acpTypes.ts (96%) rename packages/vscode-ide-companion/src/{webview/types/CompletionTypes.ts => types/completionItemTypes.ts} (87%) rename packages/vscode-ide-companion/src/{acp => types}/connectionTypes.ts (51%) create mode 100644 packages/vscode-ide-companion/src/types/qwenTypes.ts rename packages/vscode-ide-companion/src/webview/components/{ui/layouts => layout}/ChatHeader.tsx (96%) rename packages/vscode-ide-companion/src/webview/components/{ui => layout}/CompletionMenu.tsx (98%) rename packages/vscode-ide-companion/src/webview/components/{ui => layout}/EmptyState.tsx (100%) rename packages/vscode-ide-companion/src/webview/components/{ui => layout}/FileLink.tsx (100%) rename packages/vscode-ide-companion/src/webview/components/{ui => layout}/InfoBanner.tsx (100%) rename packages/vscode-ide-companion/src/webview/components/{ => layout}/InputForm.tsx (97%) rename packages/vscode-ide-companion/src/webview/components/{session => layout}/SessionSelector.tsx (100%) delete mode 100644 packages/vscode-ide-companion/src/webview/components/layouts/ChatHeader.tsx rename packages/vscode-ide-companion/src/webview/components/{ => messages}/MarkdownRenderer/MarkdownRenderer.css (95%) rename packages/vscode-ide-companion/src/webview/components/{ => messages}/MarkdownRenderer/MarkdownRenderer.tsx (97%) rename packages/vscode-ide-companion/src/webview/components/{ => messages}/MessageContent.tsx (100%) delete mode 100644 packages/vscode-ide-companion/src/webview/components/messages/QwenMessageTimeline.css delete mode 100644 packages/vscode-ide-companion/src/webview/components/messages/StreamingMessage.tsx rename packages/vscode-ide-companion/src/webview/components/{ => messages}/toolcalls/Bash/Bash.css (100%) rename packages/vscode-ide-companion/src/webview/components/{ => messages}/toolcalls/Bash/Bash.tsx (97%) rename packages/vscode-ide-companion/src/webview/components/{toolcalls/done => messages/toolcalls}/Edit/EditToolCall.tsx (87%) rename packages/vscode-ide-companion/src/webview/components/{toolcalls/done => messages/toolcalls}/Execute/Execute.css (100%) rename packages/vscode-ide-companion/src/webview/components/{toolcalls/done => messages/toolcalls}/Execute/Execute.tsx (96%) rename packages/vscode-ide-companion/src/webview/components/{ => messages}/toolcalls/GenericToolCall.tsx (78%) rename packages/vscode-ide-companion/src/webview/components/{toolcalls/done => messages/toolcalls}/Read/ReadToolCall.tsx (95%) rename packages/vscode-ide-companion/src/webview/components/{toolcalls/done => messages/toolcalls}/Search/SearchToolCall.tsx (97%) rename packages/vscode-ide-companion/src/webview/components/{ => messages}/toolcalls/Think/ThinkToolCall.tsx (100%) rename packages/vscode-ide-companion/src/webview/components/{ => messages/toolcalls}/ToolCall.tsx (54%) rename packages/vscode-ide-companion/src/webview/components/{ui => messages/toolcalls/UpdatedPlan}/CheckboxDisplay.tsx (100%) rename packages/vscode-ide-companion/src/webview/components/{ => messages}/toolcalls/UpdatedPlan/UpdatedPlanToolCall.tsx (97%) rename packages/vscode-ide-companion/src/webview/components/{ => messages}/toolcalls/Write/WriteToolCall.tsx (98%) rename packages/vscode-ide-companion/src/webview/components/{ => messages}/toolcalls/index.tsx (69%) rename packages/vscode-ide-companion/src/webview/components/{ => messages}/toolcalls/shared/LayoutComponents.css (100%) rename packages/vscode-ide-companion/src/webview/components/{ => messages}/toolcalls/shared/LayoutComponents.tsx (98%) rename packages/vscode-ide-companion/src/webview/components/{ => messages}/toolcalls/shared/types.ts (100%) rename packages/vscode-ide-companion/src/webview/components/{ => messages}/toolcalls/shared/utils.ts (98%) delete mode 100644 packages/vscode-ide-companion/src/webview/components/toolcalls/ExecuteNode/ExecuteNode.css delete mode 100644 packages/vscode-ide-companion/src/webview/components/toolcalls/ExecuteNode/ExecuteNodeToolCall.tsx delete mode 100644 packages/vscode-ide-companion/src/webview/components/toolcalls/Think/TodoWriteToolCall.tsx delete mode 100644 packages/vscode-ide-companion/src/webview/components/toolcalls/shared/DiffDisplay.css delete mode 100644 packages/vscode-ide-companion/src/webview/components/toolcalls/shared/DiffDisplay.tsx delete mode 100644 packages/vscode-ide-companion/src/webview/components/toolcalls/shared/SimpleTimeline.css rename packages/vscode-ide-companion/src/webview/styles/{ClaudeCodeStyles.css => styles.css} (85%) create mode 100644 packages/vscode-ide-companion/src/webview/styles/timeline.css delete mode 100644 packages/vscode-ide-companion/src/webview/types/toolCall.ts diff --git a/packages/vscode-ide-companion/esbuild.js b/packages/vscode-ide-companion/esbuild.js index c0efb969..032c3c13 100644 --- a/packages/vscode-ide-companion/esbuild.js +++ b/packages/vscode-ide-companion/esbuild.js @@ -46,8 +46,8 @@ const cssInjectPlugin = { let css = await fs.promises.readFile(args.path, 'utf8'); - // For ClaudeCodeStyles.css, we need to resolve @import statements - if (args.path.endsWith('ClaudeCodeStyles.css')) { + // For styles.css, we need to resolve @import statements + if (args.path.endsWith('styles.css')) { // Read all imported CSS files and inline them const importRegex = /@import\s+'([^']+)';/g; let match; diff --git a/packages/vscode-ide-companion/package.json b/packages/vscode-ide-companion/package.json index 34e85801..c29059dc 100644 --- a/packages/vscode-ide-companion/package.json +++ b/packages/vscode-ide-companion/package.json @@ -130,7 +130,7 @@ "scripts": { "prepackage": "npm run generate:notices && npm run check-types && npm run lint && npm run build:prod", "build": "npm run build:dev", - "build:dev": "node esbuild.js", + "build:dev": "npm run check-types && npm run lint && node esbuild.js", "build:prod": "node esbuild.js --production", "generate:notices": "node ./scripts/generate-notices.js", "prepare": "npm run generate:notices", diff --git a/packages/vscode-ide-companion/src/agents/qwenTypes.ts b/packages/vscode-ide-companion/src/agents/qwenTypes.ts deleted file mode 100644 index d8599084..00000000 --- a/packages/vscode-ide-companion/src/agents/qwenTypes.ts +++ /dev/null @@ -1,75 +0,0 @@ -/** - * @license - * Copyright 2025 Qwen Team - * SPDX-License-Identifier: Apache-2.0 - */ -import type { AcpPermissionRequest } from '../constants/acpTypes.js'; - -export interface ChatMessage { - role: 'user' | 'assistant'; - content: string; - timestamp: number; -} - -/** - * Plan Entry - */ -export interface PlanEntry { - /** Entry content */ - content: string; - /** Priority */ - priority?: 'high' | 'medium' | 'low'; - /** Status */ - status: 'pending' | 'in_progress' | 'completed'; -} - -/** - * Tool Call Update Data - */ -export interface ToolCallUpdateData { - /** Tool call ID */ - toolCallId: string; - /** Tool type */ - kind?: string; - /** Tool title */ - title?: string; - /** Status */ - status?: string; - /** Raw input */ - rawInput?: unknown; - /** Content */ - content?: Array>; - /** Location information */ - locations?: Array<{ path: string; line?: number | null }>; -} - -/** - * Callback Functions Collection - */ -export interface QwenAgentCallbacks { - /** Message callback */ - onMessage?: (message: ChatMessage) => void; - /** Stream text chunk callback */ - onStreamChunk?: (chunk: string) => void; - /** Thought text chunk callback */ - onThoughtChunk?: (chunk: string) => void; - /** Tool call callback */ - onToolCall?: (update: ToolCallUpdateData) => void; - /** Plan callback */ - onPlan?: (entries: PlanEntry[]) => void; - /** Permission request callback */ - onPermissionRequest?: (request: AcpPermissionRequest) => Promise; - /** End of turn callback (e.g., stopReason === 'end_turn') */ - onEndTurn?: () => void; - /** Initialize modes & capabilities info from ACP initialize */ - onModeInfo?: (info: { - currentModeId?: 'plan' | 'default' | 'auto-edit' | 'yolo'; - availableModes?: Array<{ - id: 'plan' | 'default' | 'auto-edit' | 'yolo'; - name: string; - description: string; - }>; - }) => void; - /** Mode changed notification */ - onModeChanged?: (modeId: 'plan' | 'default' | 'auto-edit' | 'yolo') => void; -} diff --git a/packages/vscode-ide-companion/src/cli/cliContextManager.ts b/packages/vscode-ide-companion/src/cli/cliContextManager.ts index 8257003f..6e7171d8 100644 --- a/packages/vscode-ide-companion/src/cli/cliContextManager.ts +++ b/packages/vscode-ide-companion/src/cli/cliContextManager.ts @@ -6,11 +6,6 @@ import type { CliFeatureFlags, CliVersionInfo } from './cliVersionManager.js'; -/** - * CLI Context Manager - * - * Manages the current CLI context including version information and feature availability - */ export class CliContextManager { private static instance: CliContextManager; private currentVersionInfo: CliVersionInfo | null = null; diff --git a/packages/vscode-ide-companion/src/commands/index.ts b/packages/vscode-ide-companion/src/commands/index.ts index db7c2ae0..50e9e3c9 100644 --- a/packages/vscode-ide-companion/src/commands/index.ts +++ b/packages/vscode-ide-companion/src/commands/index.ts @@ -68,7 +68,6 @@ export function registerNewCommands( ), ); - // TODO: qwenCode.openNewChatTab (not contributed in package.json; used programmatically) disposables.push( vscode.commands.registerCommand(openNewChatTabCommand, async () => { const provider = createWebViewProvider(); diff --git a/packages/vscode-ide-companion/src/auth/index.ts b/packages/vscode-ide-companion/src/constants/auth.ts similarity index 100% rename from packages/vscode-ide-companion/src/auth/index.ts rename to packages/vscode-ide-companion/src/constants/auth.ts diff --git a/packages/vscode-ide-companion/src/extension.ts b/packages/vscode-ide-companion/src/extension.ts index 1ef92d4b..2adfaef1 100644 --- a/packages/vscode-ide-companion/src/extension.ts +++ b/packages/vscode-ide-companion/src/extension.ts @@ -121,7 +121,9 @@ export async function activate(context: vscode.ExtensionContext) { const providers = webViewProviders.filter( (p) => typeof p.shouldSuppressDiff === 'function', ); - if (providers.length === 0) return false; + if (providers.length === 0) { + return false; + } return providers.every((p) => p.shouldSuppressDiff()); }, ); @@ -194,7 +196,7 @@ export async function activate(context: vscode.ExtensionContext) { if (docUri && docUri.scheme === DIFF_SCHEME) { diffManager.acceptDiff(docUri); } - // 如果 WebView 正在 request_permission,主动选择一个允许选项(优先 once) + // If WebView is requesting permission, actively select an allow option (prefer once) try { for (const provider of webViewProviders) { if (provider?.hasPendingPermission()) { @@ -211,7 +213,7 @@ export async function activate(context: vscode.ExtensionContext) { if (docUri && docUri.scheme === DIFF_SCHEME) { diffManager.cancelDiff(docUri); } - // 如果 WebView 正在 request_permission,主动选择拒绝/取消 + // If WebView is requesting permission, actively select reject/cancel try { for (const provider of webViewProviders) { if (provider?.hasPendingPermission()) { diff --git a/packages/vscode-ide-companion/src/acp/acpConnection.ts b/packages/vscode-ide-companion/src/services/acpConnection.ts similarity index 98% rename from packages/vscode-ide-companion/src/acp/acpConnection.ts rename to packages/vscode-ide-companion/src/services/acpConnection.ts index 1b8f54b3..29b68837 100644 --- a/packages/vscode-ide-companion/src/acp/acpConnection.ts +++ b/packages/vscode-ide-companion/src/services/acpConnection.ts @@ -4,20 +4,20 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { JSONRPC_VERSION } from '../constants/acpTypes.js'; +import { JSONRPC_VERSION } from '../types/acpTypes.js'; import type { AcpBackend, AcpMessage, AcpPermissionRequest, AcpResponse, AcpSessionUpdate, -} from '../constants/acpTypes.js'; +} from '../types/acpTypes.js'; import type { ChildProcess, SpawnOptions } from 'child_process'; import { spawn } from 'child_process'; import type { PendingRequest, AcpConnectionCallbacks, -} from './connectionTypes.js'; +} from '../types/connectionTypes.js'; import { AcpMessageHandler } from './acpMessageHandler.js'; import { AcpSessionManager } from './acpSessionManager.js'; import { determineNodePathForCli } from '../cli/cliPathDetector.js'; diff --git a/packages/vscode-ide-companion/src/acp/acpFileHandler.ts b/packages/vscode-ide-companion/src/services/acpFileHandler.ts similarity index 100% rename from packages/vscode-ide-companion/src/acp/acpFileHandler.ts rename to packages/vscode-ide-companion/src/services/acpFileHandler.ts diff --git a/packages/vscode-ide-companion/src/acp/acpMessageHandler.ts b/packages/vscode-ide-companion/src/services/acpMessageHandler.ts similarity index 98% rename from packages/vscode-ide-companion/src/acp/acpMessageHandler.ts rename to packages/vscode-ide-companion/src/services/acpMessageHandler.ts index e0af2ba9..2ef58dc1 100644 --- a/packages/vscode-ide-companion/src/acp/acpMessageHandler.ts +++ b/packages/vscode-ide-companion/src/services/acpMessageHandler.ts @@ -17,13 +17,13 @@ import type { AcpResponse, AcpSessionUpdate, AcpPermissionRequest, -} from '../constants/acpTypes.js'; +} from '../types/acpTypes.js'; import { CLIENT_METHODS } from '../constants/acpSchema.js'; import type { PendingRequest, AcpConnectionCallbacks, -} from './connectionTypes.js'; -import { AcpFileHandler } from './acpFileHandler.js'; +} from '../types/connectionTypes.js'; +import { AcpFileHandler } from '../services/acpFileHandler.js'; import type { ChildProcess } from 'child_process'; /** diff --git a/packages/vscode-ide-companion/src/acp/acpSessionManager.ts b/packages/vscode-ide-companion/src/services/acpSessionManager.ts similarity index 97% rename from packages/vscode-ide-companion/src/acp/acpSessionManager.ts rename to packages/vscode-ide-companion/src/services/acpSessionManager.ts index 9fc8e56d..1d01cc2a 100644 --- a/packages/vscode-ide-companion/src/acp/acpSessionManager.ts +++ b/packages/vscode-ide-companion/src/services/acpSessionManager.ts @@ -10,14 +10,14 @@ * Responsible for managing ACP protocol session operations, including initialization, authentication, session creation, and switching */ -import { JSONRPC_VERSION } from '../constants/acpTypes.js'; +import { JSONRPC_VERSION } from '../types/acpTypes.js'; import type { AcpRequest, AcpNotification, AcpResponse, -} from '../constants/acpTypes.js'; +} from '../types/acpTypes.js'; import { AGENT_METHODS } from '../constants/acpSchema.js'; -import type { PendingRequest } from './connectionTypes.js'; +import type { PendingRequest } from '../types/connectionTypes.js'; import type { ChildProcess } from 'child_process'; /** @@ -313,8 +313,12 @@ export class AcpSessionManager { try { // session/list requires cwd in params per ACP schema const params: Record = { cwd }; - if (options?.cursor !== undefined) params.cursor = options.cursor; - if (options?.size !== undefined) params.size = options.size; + if (options?.cursor !== undefined) { + params.cursor = options.cursor; + } + if (options?.size !== undefined) { + params.size = options.size; + } const response = await this.sendRequest( AGENT_METHODS.session_list, diff --git a/packages/vscode-ide-companion/src/auth/authStateManager.ts b/packages/vscode-ide-companion/src/services/authStateManager.ts similarity index 100% rename from packages/vscode-ide-companion/src/auth/authStateManager.ts rename to packages/vscode-ide-companion/src/services/authStateManager.ts diff --git a/packages/vscode-ide-companion/src/storage/conversationStore.ts b/packages/vscode-ide-companion/src/services/conversationStore.ts similarity index 97% rename from packages/vscode-ide-companion/src/storage/conversationStore.ts rename to packages/vscode-ide-companion/src/services/conversationStore.ts index 3940d11c..8a31af9c 100644 --- a/packages/vscode-ide-companion/src/storage/conversationStore.ts +++ b/packages/vscode-ide-companion/src/services/conversationStore.ts @@ -5,7 +5,7 @@ */ import type * as vscode from 'vscode'; -import type { ChatMessage } from '../agents/qwenAgentManager.js'; +import type { ChatMessage } from './qwenAgentManager.js'; export interface Conversation { id: string; diff --git a/packages/vscode-ide-companion/src/agents/qwenAgentManager.ts b/packages/vscode-ide-companion/src/services/qwenAgentManager.ts similarity index 98% rename from packages/vscode-ide-companion/src/agents/qwenAgentManager.ts rename to packages/vscode-ide-companion/src/services/qwenAgentManager.ts index 2217d2dd..d8d136c0 100644 --- a/packages/vscode-ide-companion/src/agents/qwenAgentManager.ts +++ b/packages/vscode-ide-companion/src/services/qwenAgentManager.ts @@ -3,27 +3,24 @@ * Copyright 2025 Qwen Team * SPDX-License-Identifier: Apache-2.0 */ -import { AcpConnection } from '../acp/acpConnection.js'; +import { AcpConnection } from './acpConnection.js'; import type { AcpSessionUpdate, AcpPermissionRequest, -} from '../constants/acpTypes.js'; -import { - QwenSessionReader, - type QwenSession, -} from '../services/qwenSessionReader.js'; -import { QwenSessionManager } from '../services/qwenSessionManager.js'; -import type { AuthStateManager } from '../auth/authStateManager.js'; +} from '../types/acpTypes.js'; +import { QwenSessionReader, type QwenSession } from './qwenSessionReader.js'; +import { QwenSessionManager } from './qwenSessionManager.js'; +import type { AuthStateManager } from './authStateManager.js'; import type { ChatMessage, PlanEntry, ToolCallUpdateData, QwenAgentCallbacks, -} from './qwenTypes.js'; -import { QwenConnectionHandler } from './qwenConnectionHandler.js'; +} from '../types/qwenTypes.js'; +import { QwenConnectionHandler } from '../services/qwenConnectionHandler.js'; import { QwenSessionUpdateHandler } from './qwenSessionUpdateHandler.js'; import { CliContextManager } from '../cli/cliContextManager.js'; -import { authMethod } from '../auth/index.js'; +import { authMethod } from '../constants/auth.js'; export type { ChatMessage, PlanEntry, ToolCallUpdateData }; @@ -496,7 +493,9 @@ export class QwenAgentManager { const fs = await import('fs'); const readline = await import('readline'); try { - if (!fs.existsSync(filePath)) return []; + if (!fs.existsSync(filePath)) { + return []; + } const fileStream = fs.createReadStream(filePath, { encoding: 'utf-8' }); const rl = readline.createInterface({ input: fileStream, @@ -505,7 +504,9 @@ export class QwenAgentManager { const records: unknown[] = []; for await (const line of rl) { const trimmed = line.trim(); - if (!trimmed) continue; + if (!trimmed) { + continue; + } try { const obj = JSON.parse(trimmed); records.push(obj); @@ -988,7 +989,7 @@ export class QwenAgentManager { if (!supportsSessionLoad) { throw new Error( - `CLI version does not support session/load method. Please upgrade to version 0.2.4 or later.`, + `CLI version does not support session/load method. Please upgrade to version 0.4.0 or later.`, ); } diff --git a/packages/vscode-ide-companion/src/agents/qwenConnectionHandler.ts b/packages/vscode-ide-companion/src/services/qwenConnectionHandler.ts similarity index 97% rename from packages/vscode-ide-companion/src/agents/qwenConnectionHandler.ts rename to packages/vscode-ide-companion/src/services/qwenConnectionHandler.ts index 00fdd2f7..9bceb60c 100644 --- a/packages/vscode-ide-companion/src/agents/qwenConnectionHandler.ts +++ b/packages/vscode-ide-companion/src/services/qwenConnectionHandler.ts @@ -11,12 +11,12 @@ */ import * as vscode from 'vscode'; -import type { AcpConnection } from '../acp/acpConnection.js'; +import type { AcpConnection } from './acpConnection.js'; import type { QwenSessionReader } from '../services/qwenSessionReader.js'; -import type { AuthStateManager } from '../auth/authStateManager.js'; +import type { AuthStateManager } from '../services/authStateManager.js'; import { CliVersionManager } from '../cli/cliVersionManager.js'; import { CliContextManager } from '../cli/cliContextManager.js'; -import { authMethod } from '../auth/index.js'; +import { authMethod } from '../constants/auth.js'; /** * Qwen Connection Handler class @@ -54,12 +54,12 @@ export class QwenConnectionHandler { // Show warning if CLI version is below minimum requirement if (!versionInfo.isSupported) { console.warn( - `[QwenAgentManager] CLI version ${versionInfo.version} is below minimum required version ${'0.2.4'}`, + `[QwenAgentManager] CLI version ${versionInfo.version} is below minimum required version ${'0.4.0'}`, ); // TODO: Wait to determine release version number // vscode.window.showWarningMessage( - // `Qwen Code CLI version ${versionInfo.version} is below the minimum required version. Some features may not work properly. Please upgrade to version 0.2.4 or later.`, + // `Qwen Code CLI version ${versionInfo.version} is below the minimum required version. Some features may not work properly. Please upgrade to version 0.4.0 or later.`, // ); } diff --git a/packages/vscode-ide-companion/src/services/qwenSessionManager.ts b/packages/vscode-ide-companion/src/services/qwenSessionManager.ts index 05376ae6..c26cb26b 100644 --- a/packages/vscode-ide-companion/src/services/qwenSessionManager.ts +++ b/packages/vscode-ide-companion/src/services/qwenSessionManager.ts @@ -170,120 +170,6 @@ export class QwenSessionManager { } } - /** - * Find checkpoint file for a given sessionId - * Tries both checkpoint-{sessionId}.json and searches session files for matching sessionId - * - * @param sessionId - Session ID to find checkpoint for - * @param workingDir - Current working directory - * @returns Checkpoint tag if found, null otherwise - */ - async findCheckpointTag( - sessionId: string, - workingDir: string, - ): Promise { - try { - const projectHash = this.getProjectHash(workingDir); - const projectDir = path.join(this.qwenDir, 'tmp', projectHash); - - // First, try direct checkpoint with sessionId - const directCheckpoint = path.join( - projectDir, - `checkpoint-${sessionId}.json`, - ); - if (fs.existsSync(directCheckpoint)) { - console.log( - '[QwenSessionManager] Found direct checkpoint:', - directCheckpoint, - ); - return sessionId; - } - - // Second, look for session file with this sessionId to get conversationId - const sessionDir = path.join(projectDir, 'chats'); - if (fs.existsSync(sessionDir)) { - const files = fs.readdirSync(sessionDir); - for (const file of files) { - if (file.startsWith('session-') && file.endsWith('.json')) { - try { - const filePath = path.join(sessionDir, file); - const content = fs.readFileSync(filePath, 'utf-8'); - const session = JSON.parse(content) as QwenSession; - - if (session.sessionId === sessionId) { - console.log( - '[QwenSessionManager] Found matching session file:', - file, - ); - // Now check if there's a checkpoint with this conversationId - // We need to store conversationId in session files or use another strategy - // For now, return null and let it fallback - break; - } - } catch { - // Skip invalid files - } - } - } - } - - console.log( - '[QwenSessionManager] No checkpoint found for sessionId:', - sessionId, - ); - return null; - } catch (error) { - console.error('[QwenSessionManager] Error finding checkpoint:', error); - return null; - } - } - - /** - * Load a checkpoint by tag - * - * @param tag - Checkpoint tag - * @param workingDir - Current working directory - * @returns Loaded checkpoint messages or null if not found - */ - async loadCheckpoint( - tag: string, - workingDir: string, - ): Promise { - try { - const projectHash = this.getProjectHash(workingDir); - const projectDir = path.join(this.qwenDir, 'tmp', projectHash); - const filename = `checkpoint-${tag}.json`; - const filePath = path.join(projectDir, filename); - - if (!fs.existsSync(filePath)) { - console.log( - `[QwenSessionManager] Checkpoint file not found: ${filePath}`, - ); - return null; - } - - const content = fs.readFileSync(filePath, 'utf-8'); - const checkpointMessages = JSON.parse(content) as Array<{ - role: 'user' | 'model'; - parts: Array<{ text: string }>; - }>; - - // Convert back to QwenMessage format - const messages: QwenMessage[] = checkpointMessages.map((msg) => ({ - id: crypto.randomUUID(), - timestamp: new Date().toISOString(), - type: msg.role === 'user' ? ('user' as const) : ('qwen' as const), - content: msg.parts[0]?.text || '', - })); - - console.log(`[QwenSessionManager] Checkpoint loaded: ${filePath}`); - return messages; - } catch (error) { - console.error('[QwenSessionManager] Failed to load checkpoint:', error); - return null; - } - } - /** * Save current conversation as a named session (checkpoint-like functionality) * diff --git a/packages/vscode-ide-companion/src/agents/qwenSessionUpdateHandler.ts b/packages/vscode-ide-companion/src/services/qwenSessionUpdateHandler.ts similarity index 97% rename from packages/vscode-ide-companion/src/agents/qwenSessionUpdateHandler.ts rename to packages/vscode-ide-companion/src/services/qwenSessionUpdateHandler.ts index 171468cc..db0023e5 100644 --- a/packages/vscode-ide-companion/src/agents/qwenSessionUpdateHandler.ts +++ b/packages/vscode-ide-companion/src/services/qwenSessionUpdateHandler.ts @@ -10,11 +10,8 @@ * Handles session updates from ACP and dispatches them to appropriate callbacks */ -import type { - AcpSessionUpdate, - ApprovalModeValue, -} from '../constants/acpTypes.js'; -import type { QwenAgentCallbacks } from './qwenTypes.js'; +import type { AcpSessionUpdate, ApprovalModeValue } from '../types/acpTypes.js'; +import type { QwenAgentCallbacks } from '../types/qwenTypes.js'; /** * Qwen Session Update Handler class diff --git a/packages/vscode-ide-companion/src/constants/acpTypes.ts b/packages/vscode-ide-companion/src/types/acpTypes.ts similarity index 96% rename from packages/vscode-ide-companion/src/constants/acpTypes.ts rename to packages/vscode-ide-companion/src/types/acpTypes.ts index 01afb46c..128b6426 100644 --- a/packages/vscode-ide-companion/src/constants/acpTypes.ts +++ b/packages/vscode-ide-companion/src/types/acpTypes.ts @@ -4,13 +4,6 @@ * SPDX-License-Identifier: Apache-2.0 */ -/** - * ACP Types for VSCode Extension - * - * This file provides types for ACP protocol communication. - */ - -// ACP JSON-RPC Protocol Types export const JSONRPC_VERSION = '2.0' as const; export type AcpBackend = 'qwen' | 'claude' | 'gemini' | 'codex'; diff --git a/packages/vscode-ide-companion/src/webview/types/CompletionTypes.ts b/packages/vscode-ide-companion/src/types/completionItemTypes.ts similarity index 87% rename from packages/vscode-ide-companion/src/webview/types/CompletionTypes.ts rename to packages/vscode-ide-companion/src/types/completionItemTypes.ts index 463d4f50..8bc884b3 100644 --- a/packages/vscode-ide-companion/src/webview/types/CompletionTypes.ts +++ b/packages/vscode-ide-companion/src/types/completionItemTypes.ts @@ -6,7 +6,6 @@ import type React from 'react'; -// Shared type for completion items used by the input completion system export interface CompletionItem { id: string; label: string; diff --git a/packages/vscode-ide-companion/src/acp/connectionTypes.ts b/packages/vscode-ide-companion/src/types/connectionTypes.ts similarity index 51% rename from packages/vscode-ide-companion/src/acp/connectionTypes.ts rename to packages/vscode-ide-companion/src/types/connectionTypes.ts index 056c60fb..20af647a 100644 --- a/packages/vscode-ide-companion/src/acp/connectionTypes.ts +++ b/packages/vscode-ide-companion/src/types/connectionTypes.ts @@ -4,60 +4,29 @@ * SPDX-License-Identifier: Apache-2.0 */ -/** - * ACP Connection Type Definitions - * - * Contains all types and interface definitions required for ACP connection - */ - import type { ChildProcess } from 'child_process'; -import type { - AcpSessionUpdate, - AcpPermissionRequest, -} from '../constants/acpTypes.js'; +import type { AcpSessionUpdate, AcpPermissionRequest } from './acpTypes.js'; -/** - * Pending Request Information - */ export interface PendingRequest { - /** Success callback */ resolve: (value: T) => void; - /** Failure callback */ reject: (error: Error) => void; - /** Timeout timer ID */ timeoutId?: NodeJS.Timeout; - /** Request method name */ method: string; } -/** - * ACP Connection Callback Function Types - */ export interface AcpConnectionCallbacks { - /** Session update callback */ onSessionUpdate: (data: AcpSessionUpdate) => void; - /** Permission request callback */ onPermissionRequest: (data: AcpPermissionRequest) => Promise<{ optionId: string; }>; - /** Turn end callback */ onEndTurn: () => void; } -/** - * ACP Connection State - */ export interface AcpConnectionState { - /** Child process instance */ child: ChildProcess | null; - /** Pending requests map */ pendingRequests: Map>; - /** Next request ID */ nextRequestId: number; - /** Current session ID */ sessionId: string | null; - /** Whether initialized */ isInitialized: boolean; - /** Backend type */ backend: string | null; } diff --git a/packages/vscode-ide-companion/src/types/qwenTypes.ts b/packages/vscode-ide-companion/src/types/qwenTypes.ts new file mode 100644 index 00000000..3b9c6153 --- /dev/null +++ b/packages/vscode-ide-companion/src/types/qwenTypes.ts @@ -0,0 +1,112 @@ +/** + * @license + * Copyright 2025 Qwen Team + * SPDX-License-Identifier: Apache-2.0 + */ +import type { AcpPermissionRequest } from './acpTypes.js'; + +export interface ChatMessage { + role: 'user' | 'assistant'; + content: string; + timestamp: number; +} + +/** + * Plan Entry + * Represents a single step in the AI's execution plan + */ +export interface PlanEntry { + /** The detailed description of this plan step */ + content: string; + /** The priority level of this plan step */ + priority?: 'high' | 'medium' | 'low'; + /** The current execution status of this plan step */ + status: 'pending' | 'in_progress' | 'completed'; +} + +/** + * Tool Call Update Data + * Contains information about a tool call execution or update + */ +export interface ToolCallUpdateData { + /** Unique identifier for this tool call */ + toolCallId: string; + /** The type of tool being called (e.g., 'read', 'write', 'execute') */ + kind?: string; + /** Human-readable title or description of the tool call */ + title?: string; + /** Current execution status of the tool call */ + status?: string; + /** Raw input parameters passed to the tool */ + rawInput?: unknown; + /** Content or output data from the tool execution */ + content?: Array>; + /** File locations associated with this tool call */ + locations?: Array<{ path: string; line?: number | null }>; +} + +/** + * Callback Functions Collection + * Defines all possible callback functions for the Qwen Agent + */ +export interface QwenAgentCallbacks { + /** Callback for receiving chat messages from the agent */ + onMessage?: (message: ChatMessage) => void; + /** Callback for receiving streamed text chunks during generation */ + onStreamChunk?: (chunk: string) => void; + /** Callback for receiving thought process chunks during generation */ + onThoughtChunk?: (chunk: string) => void; + /** Callback for receiving tool call updates during execution */ + onToolCall?: (update: ToolCallUpdateData) => void; + /** Callback for receiving execution plan updates */ + onPlan?: (entries: PlanEntry[]) => void; + /** Callback for handling permission requests from the agent */ + onPermissionRequest?: (request: AcpPermissionRequest) => Promise; + /** Callback triggered when the agent reaches the end of a turn */ + onEndTurn?: () => void; + /** Callback for receiving mode information after ACP initialization */ + onModeInfo?: (info: { + currentModeId?: 'plan' | 'default' | 'auto-edit' | 'yolo'; + availableModes?: Array<{ + id: 'plan' | 'default' | 'auto-edit' | 'yolo'; + name: string; + description: string; + }>; + }) => void; + /** Callback for receiving notifications when the mode changes */ + onModeChanged?: (modeId: 'plan' | 'default' | 'auto-edit' | 'yolo') => void; +} + +/** + * Tool call update type + */ +export interface ToolCallUpdate { + type: 'tool_call' | 'tool_call_update'; + toolCallId: string; + kind?: string; + title?: string; + status?: 'pending' | 'in_progress' | 'completed' | 'failed'; + rawInput?: unknown; + content?: Array<{ + type: 'content' | 'diff'; + content?: { + type: string; + text?: string; + [key: string]: unknown; + }; + path?: string; + oldText?: string | null; + newText?: string; + [key: string]: unknown; + }>; + locations?: Array<{ + path: string; + line?: number | null; + }>; + timestamp?: number; // Add timestamp field for message ordering +} + +/** + * Edit mode type + */ +export type EditMode = 'ask' | 'auto' | 'plan' | 'yolo'; diff --git a/packages/vscode-ide-companion/src/webview/App.tsx b/packages/vscode-ide-companion/src/webview/App.tsx index 35824ba0..e0e44fae 100644 --- a/packages/vscode-ide-companion/src/webview/App.tsx +++ b/packages/vscode-ide-companion/src/webview/App.tsx @@ -24,15 +24,15 @@ import type { ToolCall as PermissionToolCall, } from './components/PermissionDrawer/PermissionRequest.js'; import type { TextMessage } from './hooks/message/useMessageHandling.js'; -import type { ToolCallData } from './components/ToolCall.js'; +import type { ToolCallData } from './components/messages/toolcalls/ToolCall.js'; import { PermissionDrawer } from './components/PermissionDrawer/PermissionDrawer.js'; -import { ToolCall } from './components/ToolCall.js'; -import { hasToolCallOutput } from './components/toolcalls/shared/utils.js'; -import { EmptyState } from './components/ui/EmptyState.js'; -import { type CompletionItem } from './types/CompletionTypes.js'; +import { ToolCall } from './components/messages/toolcalls/ToolCall.js'; +import { hasToolCallOutput } from './components/messages/toolcalls/shared/utils.js'; +import { EmptyState } from './components/layout/EmptyState.js'; +import { type CompletionItem } from '../types/completionItemTypes.js'; import { useCompletionTrigger } from './hooks/useCompletionTrigger.js'; -import { InfoBanner } from './components/ui/InfoBanner.js'; -import { ChatHeader } from './components/ui/layouts/ChatHeader.js'; +import { InfoBanner } from './components/layout/InfoBanner.js'; +import { ChatHeader } from './components/layout/ChatHeader.js'; import { UserMessage, AssistantMessage, @@ -40,11 +40,11 @@ import { WaitingMessage, InterruptedMessage, } from './components/messages/index.js'; -import { InputForm } from './components/InputForm.js'; -import { SessionSelector } from './components/session/SessionSelector.js'; +import { InputForm } from './components/layout/InputForm.js'; +import { SessionSelector } from './components/layout/SessionSelector.js'; import { FileIcon, UserIcon } from './components/icons/index.js'; -import type { EditMode } from './types/toolCall.js'; -import type { PlanEntry } from '../agents/qwenTypes.js'; +import type { EditMode } from '../types/qwenTypes.js'; +import type { PlanEntry } from '../types/qwenTypes.js'; export const App: React.FC = () => { const vscode = useVSCode(); @@ -609,7 +609,7 @@ export const App: React.FC = () => { const isToolCallType = ( x: unknown, ): x is { type: 'in-progress-tool-call' | 'completed-tool-call' } => - x && + !!x && typeof x === 'object' && 'type' in (x as Record) && ((x as { type: string }).type === 'in-progress-tool-call' || @@ -782,8 +782,6 @@ export const App: React.FC = () => { onClose={() => setPermissionRequest(null)} /> )} - - {/* Claude-style dropdown is rendered inside InputForm for proper anchoring */} ); }; diff --git a/packages/vscode-ide-companion/src/webview/MessageHandler.ts b/packages/vscode-ide-companion/src/webview/MessageHandler.ts index 000eece7..1eca4a20 100644 --- a/packages/vscode-ide-companion/src/webview/MessageHandler.ts +++ b/packages/vscode-ide-companion/src/webview/MessageHandler.ts @@ -4,8 +4,8 @@ * SPDX-License-Identifier: Apache-2.0 */ -import type { QwenAgentManager } from '../agents/qwenAgentManager.js'; -import type { ConversationStore } from '../storage/conversationStore.js'; +import type { QwenAgentManager } from '../services/qwenAgentManager.js'; +import type { ConversationStore } from '../services/conversationStore.js'; import { MessageRouter } from './handlers/MessageRouter.js'; /** diff --git a/packages/vscode-ide-companion/src/webview/WebViewProvider.ts b/packages/vscode-ide-companion/src/webview/WebViewProvider.ts index 36751b8b..31f5db30 100644 --- a/packages/vscode-ide-companion/src/webview/WebViewProvider.ts +++ b/packages/vscode-ide-companion/src/webview/WebViewProvider.ts @@ -5,17 +5,17 @@ */ import * as vscode from 'vscode'; -import { QwenAgentManager } from '../agents/qwenAgentManager.js'; -import { ConversationStore } from '../storage/conversationStore.js'; -import type { AcpPermissionRequest } from '../constants/acpTypes.js'; +import { QwenAgentManager } from '../services/qwenAgentManager.js'; +import { ConversationStore } from '../services/conversationStore.js'; +import type { AcpPermissionRequest } from '../types/acpTypes.js'; import { CliDetector } from '../cli/cliDetector.js'; -import { AuthStateManager } from '../auth/authStateManager.js'; +import { AuthStateManager } from '../services/authStateManager.js'; import { PanelManager } from '../webview/PanelManager.js'; import { MessageHandler } from '../webview/MessageHandler.js'; import { WebViewContent } from '../webview/WebViewContent.js'; import { CliInstaller } from '../cli/cliInstaller.js'; import { getFileName } from './utils/webviewUtils.js'; -import { authMethod } from '../auth/index.js'; +import { authMethod } from '../constants/auth.js'; import { runQwenCodeCommand } from '../commands/index.js'; export class WebViewProvider { diff --git a/packages/vscode-ide-companion/src/webview/components/PermissionDrawer/PermissionDrawer.tsx b/packages/vscode-ide-companion/src/webview/components/PermissionDrawer/PermissionDrawer.tsx index e0ec3e84..2eb659e8 100644 --- a/packages/vscode-ide-companion/src/webview/components/PermissionDrawer/PermissionDrawer.tsx +++ b/packages/vscode-ide-companion/src/webview/components/PermissionDrawer/PermissionDrawer.tsx @@ -29,7 +29,7 @@ export const PermissionDrawer: React.FC = ({ const [focusedIndex, setFocusedIndex] = useState(0); const [customMessage, setCustomMessage] = useState(''); const containerRef = useRef(null); - // 将自定义输入的 ref 类型修正为 HTMLInputElement,避免后续强转 + // Correct the ref type for custom input to HTMLInputElement to avoid subsequent forced casting const customInputRef = useRef(null); console.log('PermissionDrawer rendered with isOpen:', isOpen, toolCall); @@ -139,7 +139,9 @@ export const PermissionDrawer: React.FC = ({ options.find((o) => o.optionId === 'cancel')?.optionId || 'cancel'; onResponse(rejectOptionId); - if (onClose) onClose(); + if (onClose) { + onClose(); + } } }; @@ -196,7 +198,7 @@ export const PermissionDrawer: React.FC = ({ toolCall.kind === 'bash') && toolCall.title && (
= ({ setCustomMessage={setCustomMessage} onFocusRow={() => setFocusedIndex(options.length)} onSubmitReject={() => { - if (rejectOptionId) onResponse(rejectOptionId); + if (rejectOptionId) { + onResponse(rejectOptionId); + } }} inputRef={customInputRef} /> @@ -264,14 +268,14 @@ export const PermissionDrawer: React.FC = ({ }; /** - * CustomMessageInputRow: 复用的自定义输入行组件(无 hooks) + * CustomMessageInputRow: Reusable custom input row component (without hooks) */ interface CustomMessageInputRowProps { isFocused: boolean; customMessage: string; setCustomMessage: (val: string) => void; - onFocusRow: () => void; // 鼠标移入或输入框 focus 时设置焦点 - onSubmitReject: () => void; // Enter 提交时触发(选择 reject 选项) + onFocusRow: () => void; // Set focus when mouse enters or input box is focused + onSubmitReject: () => void; // Triggered when Enter is pressed (selecting reject option) inputRef: React.RefObject; } diff --git a/packages/vscode-ide-companion/src/webview/components/icons/SpecialIcons.tsx b/packages/vscode-ide-companion/src/webview/components/icons/SpecialIcons.tsx index 3c528d66..48c5db84 100644 --- a/packages/vscode-ide-companion/src/webview/components/icons/SpecialIcons.tsx +++ b/packages/vscode-ide-companion/src/webview/components/icons/SpecialIcons.tsx @@ -16,10 +16,6 @@ interface ThinkingIconProps extends IconProps { enabled?: boolean; } -/** - * Thinking/brain wave icon (16x16) - * Used for thinking mode toggle - */ export const ThinkingIcon: React.FC = ({ size = 16, className, @@ -53,10 +49,6 @@ export const ThinkingIcon: React.FC = ({ ); -/** - * Terminal/code editor icon (20x20) - * Used for terminal preference info banner - */ export const TerminalIcon: React.FC = ({ size = 20, className, diff --git a/packages/vscode-ide-companion/src/webview/components/ui/layouts/ChatHeader.tsx b/packages/vscode-ide-companion/src/webview/components/layout/ChatHeader.tsx similarity index 96% rename from packages/vscode-ide-companion/src/webview/components/ui/layouts/ChatHeader.tsx rename to packages/vscode-ide-companion/src/webview/components/layout/ChatHeader.tsx index 786969a3..82cc905f 100644 --- a/packages/vscode-ide-companion/src/webview/components/ui/layouts/ChatHeader.tsx +++ b/packages/vscode-ide-companion/src/webview/components/layout/ChatHeader.tsx @@ -5,7 +5,7 @@ */ import type React from 'react'; -import { ChevronDownIcon, PlusIcon } from '../../icons/index.js'; +import { ChevronDownIcon, PlusIcon } from '../icons/index.js'; interface ChatHeaderProps { currentSessionTitle: string; diff --git a/packages/vscode-ide-companion/src/webview/components/ui/CompletionMenu.tsx b/packages/vscode-ide-companion/src/webview/components/layout/CompletionMenu.tsx similarity index 98% rename from packages/vscode-ide-companion/src/webview/components/ui/CompletionMenu.tsx rename to packages/vscode-ide-companion/src/webview/components/layout/CompletionMenu.tsx index 72ec1307..20010ea9 100644 --- a/packages/vscode-ide-companion/src/webview/components/ui/CompletionMenu.tsx +++ b/packages/vscode-ide-companion/src/webview/components/layout/CompletionMenu.tsx @@ -6,7 +6,7 @@ import type React from 'react'; import { useEffect, useRef, useState } from 'react'; -import type { CompletionItem } from '../../types/CompletionTypes.js'; +import type { CompletionItem } from '../../../types/completionItemTypes.js'; interface CompletionMenuProps { items: CompletionItem[]; diff --git a/packages/vscode-ide-companion/src/webview/components/ui/EmptyState.tsx b/packages/vscode-ide-companion/src/webview/components/layout/EmptyState.tsx similarity index 100% rename from packages/vscode-ide-companion/src/webview/components/ui/EmptyState.tsx rename to packages/vscode-ide-companion/src/webview/components/layout/EmptyState.tsx diff --git a/packages/vscode-ide-companion/src/webview/components/ui/FileLink.tsx b/packages/vscode-ide-companion/src/webview/components/layout/FileLink.tsx similarity index 100% rename from packages/vscode-ide-companion/src/webview/components/ui/FileLink.tsx rename to packages/vscode-ide-companion/src/webview/components/layout/FileLink.tsx diff --git a/packages/vscode-ide-companion/src/webview/components/ui/InfoBanner.tsx b/packages/vscode-ide-companion/src/webview/components/layout/InfoBanner.tsx similarity index 100% rename from packages/vscode-ide-companion/src/webview/components/ui/InfoBanner.tsx rename to packages/vscode-ide-companion/src/webview/components/layout/InfoBanner.tsx diff --git a/packages/vscode-ide-companion/src/webview/components/InputForm.tsx b/packages/vscode-ide-companion/src/webview/components/layout/InputForm.tsx similarity index 97% rename from packages/vscode-ide-companion/src/webview/components/InputForm.tsx rename to packages/vscode-ide-companion/src/webview/components/layout/InputForm.tsx index a08a4a7b..eba3e376 100644 --- a/packages/vscode-ide-companion/src/webview/components/InputForm.tsx +++ b/packages/vscode-ide-companion/src/webview/components/layout/InputForm.tsx @@ -16,11 +16,10 @@ import { LinkIcon, ArrowUpIcon, StopIcon, -} from './icons/index.js'; -import { CompletionMenu } from './ui/CompletionMenu.js'; -import type { CompletionItem } from '../types/CompletionTypes.js'; - -type EditMode = 'ask' | 'auto' | 'plan' | 'yolo'; +} from '../icons/index.js'; +import { CompletionMenu } from '../layout/CompletionMenu.js'; +import type { CompletionItem } from '../../../types/completionItemTypes.js'; +import type { EditMode } from '../../../types/qwenTypes.js'; interface InputFormProps { inputText: string; diff --git a/packages/vscode-ide-companion/src/webview/components/session/SessionSelector.tsx b/packages/vscode-ide-companion/src/webview/components/layout/SessionSelector.tsx similarity index 100% rename from packages/vscode-ide-companion/src/webview/components/session/SessionSelector.tsx rename to packages/vscode-ide-companion/src/webview/components/layout/SessionSelector.tsx diff --git a/packages/vscode-ide-companion/src/webview/components/layouts/ChatHeader.tsx b/packages/vscode-ide-companion/src/webview/components/layouts/ChatHeader.tsx deleted file mode 100644 index 2c51f9ea..00000000 --- a/packages/vscode-ide-companion/src/webview/components/layouts/ChatHeader.tsx +++ /dev/null @@ -1,109 +0,0 @@ -/** - * @license - * Copyright 2025 Qwen Team - * SPDX-License-Identifier: Apache-2.0 - */ - -import type React from 'react'; - -interface ChatHeaderProps { - currentSessionTitle: string; - onLoadSessions: () => void; - onSaveSession: () => void; - onNewSession: () => void; -} - -export const ChatHeader: React.FC = ({ - currentSessionTitle, - onLoadSessions, - onSaveSession: _onSaveSession, - onNewSession, -}) => ( -
- {/* Past Conversations Button */} - - - {/* Spacer */} -
- - {/* Save Session Button */} - {/* */} - - {/* New Session Button */} - -
-); diff --git a/packages/vscode-ide-companion/src/webview/components/messages/Assistant/AssistantMessage.tsx b/packages/vscode-ide-companion/src/webview/components/messages/Assistant/AssistantMessage.tsx index d6320e62..a5260ddb 100644 --- a/packages/vscode-ide-companion/src/webview/components/messages/Assistant/AssistantMessage.tsx +++ b/packages/vscode-ide-companion/src/webview/components/messages/Assistant/AssistantMessage.tsx @@ -5,7 +5,7 @@ */ import type React from 'react'; -import { MessageContent } from '../../MessageContent.js'; +import { MessageContent } from '../MessageContent.js'; import './AssistantMessage.css'; interface AssistantMessageProps { diff --git a/packages/vscode-ide-companion/src/webview/components/MarkdownRenderer/MarkdownRenderer.css b/packages/vscode-ide-companion/src/webview/components/messages/MarkdownRenderer/MarkdownRenderer.css similarity index 95% rename from packages/vscode-ide-companion/src/webview/components/MarkdownRenderer/MarkdownRenderer.css rename to packages/vscode-ide-companion/src/webview/components/messages/MarkdownRenderer/MarkdownRenderer.css index a693e628..d335e7da 100644 --- a/packages/vscode-ide-companion/src/webview/components/MarkdownRenderer/MarkdownRenderer.css +++ b/packages/vscode-ide-companion/src/webview/components/messages/MarkdownRenderer/MarkdownRenderer.css @@ -135,8 +135,8 @@ border: 1px solid var(--app-primary-border-color); border-radius: var(--corner-radius-small, 4px); padding: 0.2em 0.4em; - white-space: pre-wrap; /* 支持自动换行 */ - word-break: break-word; /* 在必要时断词 */ + white-space: pre-wrap; /* Support automatic line wrapping */ + word-break: break-word; /* Break words when necessary */ } .markdown-content pre { @@ -207,8 +207,8 @@ background: none; border: none; padding: 0; - white-space: pre-wrap; /* 支持自动换行 */ - word-break: break-word; /* 在必要时断词 */ + white-space: pre-wrap; /* Support automatic line wrapping */ + word-break: break-word; /* Break words when necessary */ } .markdown-content .file-path-link { diff --git a/packages/vscode-ide-companion/src/webview/components/MarkdownRenderer/MarkdownRenderer.tsx b/packages/vscode-ide-companion/src/webview/components/messages/MarkdownRenderer/MarkdownRenderer.tsx similarity index 97% rename from packages/vscode-ide-companion/src/webview/components/MarkdownRenderer/MarkdownRenderer.tsx rename to packages/vscode-ide-companion/src/webview/components/messages/MarkdownRenderer/MarkdownRenderer.tsx index 09c41652..cf34a6e1 100644 --- a/packages/vscode-ide-companion/src/webview/components/MarkdownRenderer/MarkdownRenderer.tsx +++ b/packages/vscode-ide-companion/src/webview/components/messages/MarkdownRenderer/MarkdownRenderer.tsx @@ -126,7 +126,9 @@ export const MarkdownRenderer: React.FC = ({ */ const processFilePaths = (html: string): string => { // If DOM is not available, bail out to avoid breaking SSR - if (typeof document === 'undefined') return html; + if (typeof document === 'undefined') { + return html; + } // Build non-global variants to avoid .test() statefulness const FILE_PATH_NO_G = new RegExp( @@ -193,7 +195,9 @@ export const MarkdownRenderer: React.FC = ({ } // Ignore other external protocols - if (/^(https?|mailto|ftp|data):/i.test(href)) return; + if (/^(https?|mailto|ftp|data):/i.test(href)) { + return; + } const candidate = href || text; @@ -289,7 +293,9 @@ export const MarkdownRenderer: React.FC = ({ e: React.MouseEvent, ) => { const target = e.target as HTMLElement | null; - if (!target) return; + if (!target) { + return; + } // Handle copy button clicks for fenced code blocks const copyBtn = (target.closest && @@ -322,10 +328,14 @@ export const MarkdownRenderer: React.FC = ({ // Find nearest anchor with our marker class const anchor = (target.closest && target.closest('a.file-path-link')) as HTMLAnchorElement | null; - if (!anchor) return; + if (!anchor) { + return; + } const filePath = anchor.getAttribute('data-file-path'); - if (!filePath) return; + if (!filePath) { + return; + } e.preventDefault(); e.stopPropagation(); diff --git a/packages/vscode-ide-companion/src/webview/components/MessageContent.tsx b/packages/vscode-ide-companion/src/webview/components/messages/MessageContent.tsx similarity index 100% rename from packages/vscode-ide-companion/src/webview/components/MessageContent.tsx rename to packages/vscode-ide-companion/src/webview/components/messages/MessageContent.tsx diff --git a/packages/vscode-ide-companion/src/webview/components/messages/QwenMessageTimeline.css b/packages/vscode-ide-companion/src/webview/components/messages/QwenMessageTimeline.css deleted file mode 100644 index 119f5124..00000000 --- a/packages/vscode-ide-companion/src/webview/components/messages/QwenMessageTimeline.css +++ /dev/null @@ -1,61 +0,0 @@ -/** - * @license - * Copyright 2025 Qwen Team - * SPDX-License-Identifier: Apache-2.0 - * - * Custom timeline styles for qwen-message message-item elements - * - * 实现原理: - * 1. 为所有AI消息项(qwen-message.message-item:not(.user-message-container))创建垂直连接线 - * 2. 当用户消息(user-message-container)隔断AI消息序列时,会自动重新开始一组新的时间线规则 - * 3. 每组AI消息序列的开始元素(top设为15px),结束元素(底部预留15px) - */ - -/* 默认的连接线样式 - 为所有AI消息项创建完整高度的连接线 */ -.qwen-message.message-item:not(.user-message-container)::after { - content: ''; - position: absolute; - left: 12px; - top: 0; - bottom: 0; - width: 1px; - background-color: var(--app-primary-border-color); - z-index: 0; -} - -/* 处理每组AI消息序列的开始 - 包括整个消息列表的第一个AI消息和被用户消息隔断后的新AI消息 */ -.qwen-message.message-item:not(.user-message-container):first-child::after, -.user-message-container + .qwen-message.message-item:not(.user-message-container)::after, -/* 如果前一个兄弟不是 .qwen-message.message-item(例如等待提示、哨兵元素或卡片样式工具调用),也作为一组新开始 */ -.chat-messages > :not(.qwen-message.message-item) - + .qwen-message.message-item:not(.user-message-container)::after { - top: 15px; -} - -/* 处理每组AI消息序列的结尾 */ -/* 后一个兄弟是用户消息时 */ -.qwen-message.message-item:not(.user-message-container):has(+ .user-message-container)::after, -/* 或者后一个兄弟不是 .qwen-message.message-item(如等待提示、哨兵元素、卡片样式工具调用等)时 */ -.qwen-message.message-item:not(.user-message-container):has(+ :not(.qwen-message.message-item))::after, -/* 真正是父容器最后一个子元素时 */ -.qwen-message.message-item:not(.user-message-container):last-child::after { - /* 注意:同时设置 top 和 bottom 时高度为 (容器高度 - top - bottom)。 - * 这里期望“底部留 15px 间距”,因此 bottom 应为 15px(而不是 calc(100% - 15px))。 */ - top: 0; - bottom: calc(100% - 15px); -} - -.user-message-container:first-child { - margin-top: 0; -} - -.message-item { - padding: 8px 0; - width: 100%; - align-items: flex-start; - padding-left: 30px; - user-select: text; - position: relative; - padding-top: 8px; - padding-bottom: 8px; -} diff --git a/packages/vscode-ide-companion/src/webview/components/messages/StreamingMessage.tsx b/packages/vscode-ide-companion/src/webview/components/messages/StreamingMessage.tsx deleted file mode 100644 index 05a6b447..00000000 --- a/packages/vscode-ide-companion/src/webview/components/messages/StreamingMessage.tsx +++ /dev/null @@ -1,39 +0,0 @@ -/** - * @license - * Copyright 2025 Qwen Team - * SPDX-License-Identifier: Apache-2.0 - */ - -import type React from 'react'; -import { MessageContent } from '../MessageContent.js'; - -interface StreamingMessageProps { - content: string; - onFileClick?: (path: string) => void; -} - -export const StreamingMessage: React.FC = ({ - content, - onFileClick, -}) => ( -
-
- -
-
- ● -
-
-); diff --git a/packages/vscode-ide-companion/src/webview/components/messages/ThinkingMessage.tsx b/packages/vscode-ide-companion/src/webview/components/messages/ThinkingMessage.tsx index 166158b6..ab366b72 100644 --- a/packages/vscode-ide-companion/src/webview/components/messages/ThinkingMessage.tsx +++ b/packages/vscode-ide-companion/src/webview/components/messages/ThinkingMessage.tsx @@ -5,7 +5,7 @@ */ import type React from 'react'; -import { MessageContent } from '../MessageContent.js'; +import { MessageContent } from './MessageContent.js'; interface ThinkingMessageProps { content: string; diff --git a/packages/vscode-ide-companion/src/webview/components/messages/UserMessage.tsx b/packages/vscode-ide-companion/src/webview/components/messages/UserMessage.tsx index 19fdb415..0f8cd939 100644 --- a/packages/vscode-ide-companion/src/webview/components/messages/UserMessage.tsx +++ b/packages/vscode-ide-companion/src/webview/components/messages/UserMessage.tsx @@ -5,7 +5,7 @@ */ import type React from 'react'; -import { MessageContent } from '../MessageContent.js'; +import { MessageContent } from './MessageContent.js'; interface FileContext { fileName: string; diff --git a/packages/vscode-ide-companion/src/webview/components/messages/index.tsx b/packages/vscode-ide-companion/src/webview/components/messages/index.tsx index a8c7224a..2ec06e87 100644 --- a/packages/vscode-ide-companion/src/webview/components/messages/index.tsx +++ b/packages/vscode-ide-companion/src/webview/components/messages/index.tsx @@ -7,6 +7,5 @@ export { UserMessage } from './UserMessage.js'; export { AssistantMessage } from './Assistant/AssistantMessage.js'; export { ThinkingMessage } from './ThinkingMessage.js'; -export { StreamingMessage } from './StreamingMessage.js'; export { WaitingMessage } from './Waiting/WaitingMessage.js'; export { InterruptedMessage } from './Waiting/InterruptedMessage.js'; diff --git a/packages/vscode-ide-companion/src/webview/components/toolcalls/Bash/Bash.css b/packages/vscode-ide-companion/src/webview/components/messages/toolcalls/Bash/Bash.css similarity index 100% rename from packages/vscode-ide-companion/src/webview/components/toolcalls/Bash/Bash.css rename to packages/vscode-ide-companion/src/webview/components/messages/toolcalls/Bash/Bash.css diff --git a/packages/vscode-ide-companion/src/webview/components/toolcalls/Bash/Bash.tsx b/packages/vscode-ide-companion/src/webview/components/messages/toolcalls/Bash/Bash.tsx similarity index 97% rename from packages/vscode-ide-companion/src/webview/components/toolcalls/Bash/Bash.tsx rename to packages/vscode-ide-companion/src/webview/components/messages/toolcalls/Bash/Bash.tsx index 9d3d5f11..91cbba91 100644 --- a/packages/vscode-ide-companion/src/webview/components/toolcalls/Bash/Bash.tsx +++ b/packages/vscode-ide-companion/src/webview/components/messages/toolcalls/Bash/Bash.tsx @@ -10,8 +10,8 @@ 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 { useVSCode } from '../../../hooks/useVSCode.js'; -import { createAndOpenTempFile } from '../../../utils/tempFileManager.js'; +import { useVSCode } from '../../../../hooks/useVSCode.js'; +import { createAndOpenTempFile } from '../../../../utils/tempFileManager.js'; import './Bash.css'; /** diff --git a/packages/vscode-ide-companion/src/webview/components/toolcalls/done/Edit/EditToolCall.tsx b/packages/vscode-ide-companion/src/webview/components/messages/toolcalls/Edit/EditToolCall.tsx similarity index 87% rename from packages/vscode-ide-companion/src/webview/components/toolcalls/done/Edit/EditToolCall.tsx rename to packages/vscode-ide-companion/src/webview/components/messages/toolcalls/Edit/EditToolCall.tsx index e7e01a15..4f668902 100644 --- a/packages/vscode-ide-companion/src/webview/components/toolcalls/done/Edit/EditToolCall.tsx +++ b/packages/vscode-ide-companion/src/webview/components/messages/toolcalls/Edit/EditToolCall.tsx @@ -7,16 +7,15 @@ */ import { useEffect, useCallback, useMemo } from 'react'; -import type { BaseToolCallProps } from '../../shared/types.js'; +import type { BaseToolCallProps } from '../shared/types.js'; import { groupContent, mapToolStatusToContainerStatus, -} from '../../shared/utils.js'; -import { FileLink } from '../../../ui/FileLink.js'; -import type { ToolCallContainerProps } from '../../shared/LayoutComponents.js'; +} from '../shared/utils.js'; +import { FileLink } from '../../../layout/FileLink.js'; +import type { ToolCallContainerProps } from '../shared/LayoutComponents.js'; import { useVSCode } from '../../../../hooks/useVSCode.js'; import { handleOpenDiff } from '../../../../utils/diffUtils.js'; -import { DiffDisplay } from '../../shared/DiffDisplay.js'; export const ToolCallContainer: React.FC = ({ label, @@ -138,31 +137,6 @@ export const EditToolCall: React.FC = ({ toolCall }) => {
edit failed
- {/* Inline diff preview(s) */} - {diffs.length > 0 && ( -
- {diffs.map( - ( - item: import('../../shared/types.js').ToolCallContent, - idx: number, - ) => ( - - handleOpenDiffInternal( - item.path || path, - item.oldText, - item.newText, - ) - } - /> - ), - )} -
- )}
); diff --git a/packages/vscode-ide-companion/src/webview/components/toolcalls/done/Execute/Execute.css b/packages/vscode-ide-companion/src/webview/components/messages/toolcalls/Execute/Execute.css similarity index 100% rename from packages/vscode-ide-companion/src/webview/components/toolcalls/done/Execute/Execute.css rename to packages/vscode-ide-companion/src/webview/components/messages/toolcalls/Execute/Execute.css diff --git a/packages/vscode-ide-companion/src/webview/components/toolcalls/done/Execute/Execute.tsx b/packages/vscode-ide-companion/src/webview/components/messages/toolcalls/Execute/Execute.tsx similarity index 96% rename from packages/vscode-ide-companion/src/webview/components/toolcalls/done/Execute/Execute.tsx rename to packages/vscode-ide-companion/src/webview/components/messages/toolcalls/Execute/Execute.tsx index 64e02ca7..516ee6e9 100644 --- a/packages/vscode-ide-companion/src/webview/components/toolcalls/done/Execute/Execute.tsx +++ b/packages/vscode-ide-companion/src/webview/components/messages/toolcalls/Execute/Execute.tsx @@ -7,10 +7,10 @@ */ import type React from 'react'; -import type { BaseToolCallProps } from '../../shared/types.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'; +import type { ToolCallContainerProps } from '../shared/LayoutComponents.js'; export const ToolCallContainer: React.FC = ({ label, diff --git a/packages/vscode-ide-companion/src/webview/components/toolcalls/GenericToolCall.tsx b/packages/vscode-ide-companion/src/webview/components/messages/toolcalls/GenericToolCall.tsx similarity index 78% rename from packages/vscode-ide-companion/src/webview/components/toolcalls/GenericToolCall.tsx rename to packages/vscode-ide-companion/src/webview/components/messages/toolcalls/GenericToolCall.tsx index c4a23713..50e88443 100644 --- a/packages/vscode-ide-companion/src/webview/components/toolcalls/GenericToolCall.tsx +++ b/packages/vscode-ide-companion/src/webview/components/messages/toolcalls/GenericToolCall.tsx @@ -14,10 +14,7 @@ import { ToolCallRow, LocationsList, } from './shared/LayoutComponents.js'; -import { DiffDisplay } from './shared/DiffDisplay.js'; import { safeTitle, groupContent } from './shared/utils.js'; -import { useVSCode } from '../../hooks/useVSCode.js'; -import { handleOpenDiff } from '../../utils/diffUtils.js'; /** * Generic tool call component that can display any tool call type @@ -27,10 +24,9 @@ import { handleOpenDiff } from '../../utils/diffUtils.js'; export const GenericToolCall: React.FC = ({ toolCall }) => { const { kind, title, content, locations, toolCallId } = toolCall; const operationText = safeTitle(title); - const vscode = useVSCode(); // Group content by type - const { textOutputs, errors, diffs } = groupContent(content); + const { textOutputs, errors } = groupContent(content); // Error case: show operation + error in card layout if (errors.length > 0) { @@ -46,28 +42,6 @@ export const GenericToolCall: React.FC = ({ toolCall }) => { ); } - // Success with diff: show diff in card layout - if (diffs.length > 0) { - return ( - - {diffs.map( - (item: import('./shared/types.js').ToolCallContent, idx: number) => ( -
- - handleOpenDiff(vscode, item.path, item.oldText, item.newText) - } - /> -
- ), - )} -
- ); - } - // Success with output: use card for long output, compact for short if (textOutputs.length > 0) { const output = textOutputs.join('\n'); diff --git a/packages/vscode-ide-companion/src/webview/components/toolcalls/done/Read/ReadToolCall.tsx b/packages/vscode-ide-companion/src/webview/components/messages/toolcalls/Read/ReadToolCall.tsx similarity index 95% rename from packages/vscode-ide-companion/src/webview/components/toolcalls/done/Read/ReadToolCall.tsx rename to packages/vscode-ide-companion/src/webview/components/messages/toolcalls/Read/ReadToolCall.tsx index b1344623..230a7ef8 100644 --- a/packages/vscode-ide-companion/src/webview/components/toolcalls/done/Read/ReadToolCall.tsx +++ b/packages/vscode-ide-companion/src/webview/components/messages/toolcalls/Read/ReadToolCall.tsx @@ -8,15 +8,15 @@ import type React from 'react'; import { useCallback, useEffect, useMemo } from 'react'; -import type { BaseToolCallProps } from '../../shared/types.js'; +import type { BaseToolCallProps } from '../shared/types.js'; import { groupContent, mapToolStatusToContainerStatus, -} from '../../shared/utils.js'; -import { FileLink } from '../../../ui/FileLink.js'; +} from '../shared/utils.js'; +import { FileLink } from '../../../layout/FileLink.js'; import { useVSCode } from '../../../../hooks/useVSCode.js'; import { handleOpenDiff } from '../../../../utils/diffUtils.js'; -import type { ToolCallContainerProps } from '../../shared/LayoutComponents.js'; +import type { ToolCallContainerProps } from '../shared/LayoutComponents.js'; export const ToolCallContainer: React.FC = ({ label, diff --git a/packages/vscode-ide-companion/src/webview/components/toolcalls/done/Search/SearchToolCall.tsx b/packages/vscode-ide-companion/src/webview/components/messages/toolcalls/Search/SearchToolCall.tsx similarity index 97% rename from packages/vscode-ide-companion/src/webview/components/toolcalls/done/Search/SearchToolCall.tsx rename to packages/vscode-ide-companion/src/webview/components/messages/toolcalls/Search/SearchToolCall.tsx index 84679171..8c76c739 100644 --- a/packages/vscode-ide-companion/src/webview/components/toolcalls/done/Search/SearchToolCall.tsx +++ b/packages/vscode-ide-companion/src/webview/components/messages/toolcalls/Search/SearchToolCall.tsx @@ -7,15 +7,15 @@ */ import type React from 'react'; -import type { BaseToolCallProps } from '../../shared/types.js'; -import { FileLink } from '../../../ui/FileLink.js'; +import type { BaseToolCallProps } from '../shared/types.js'; +import { FileLink } from '../../../layout/FileLink.js'; import { safeTitle, groupContent, mapToolStatusToContainerStatus, -} from '../../shared/utils.js'; +} from '../shared/utils.js'; -import type { ToolCallContainerProps } from '../../shared/LayoutComponents.js'; +import type { ToolCallContainerProps } from '../shared/LayoutComponents.js'; export const ToolCallContainer: React.FC = ({ label, diff --git a/packages/vscode-ide-companion/src/webview/components/toolcalls/Think/ThinkToolCall.tsx b/packages/vscode-ide-companion/src/webview/components/messages/toolcalls/Think/ThinkToolCall.tsx similarity index 100% rename from packages/vscode-ide-companion/src/webview/components/toolcalls/Think/ThinkToolCall.tsx rename to packages/vscode-ide-companion/src/webview/components/messages/toolcalls/Think/ThinkToolCall.tsx diff --git a/packages/vscode-ide-companion/src/webview/components/ToolCall.tsx b/packages/vscode-ide-companion/src/webview/components/messages/toolcalls/ToolCall.tsx similarity index 54% rename from packages/vscode-ide-companion/src/webview/components/ToolCall.tsx rename to packages/vscode-ide-companion/src/webview/components/messages/toolcalls/ToolCall.tsx index 36a18368..6cda54a2 100644 --- a/packages/vscode-ide-companion/src/webview/components/ToolCall.tsx +++ b/packages/vscode-ide-companion/src/webview/components/messages/toolcalls/ToolCall.tsx @@ -10,31 +10,18 @@ */ import type React from 'react'; -import { ToolCallRouter } from './toolcalls/index.js'; +import { ToolCallRouter } from './index.js'; // Re-export types from the toolcalls module for backward compatibility export type { ToolCallData, BaseToolCallProps as ToolCallProps, -} from './toolcalls/shared/types.js'; +} from './shared/types.js'; // Re-export the content type for external use -export type { ToolCallContent } from './toolcalls/shared/types.js'; - -/** - * Main ToolCall component - * Routes to specialized components based on the tool call kind - * - * Supported kinds: - * - read: File reading operations - * - write/edit: File writing and editing operations - * - execute/bash/command: Command execution - * - search/grep/glob/find: Search operations - * - think/thinking: AI reasoning - * - All others: Generic display - */ +export type { ToolCallContent } from './shared/types.js'; export const ToolCall: React.FC<{ - toolCall: import('./toolcalls/shared/types.js').ToolCallData; + toolCall: import('./shared/types.js').ToolCallData; isFirst?: boolean; isLast?: boolean; }> = ({ toolCall, isFirst, isLast }) => ( diff --git a/packages/vscode-ide-companion/src/webview/components/ui/CheckboxDisplay.tsx b/packages/vscode-ide-companion/src/webview/components/messages/toolcalls/UpdatedPlan/CheckboxDisplay.tsx similarity index 100% rename from packages/vscode-ide-companion/src/webview/components/ui/CheckboxDisplay.tsx rename to packages/vscode-ide-companion/src/webview/components/messages/toolcalls/UpdatedPlan/CheckboxDisplay.tsx diff --git a/packages/vscode-ide-companion/src/webview/components/toolcalls/UpdatedPlan/UpdatedPlanToolCall.tsx b/packages/vscode-ide-companion/src/webview/components/messages/toolcalls/UpdatedPlan/UpdatedPlanToolCall.tsx similarity index 97% rename from packages/vscode-ide-companion/src/webview/components/toolcalls/UpdatedPlan/UpdatedPlanToolCall.tsx rename to packages/vscode-ide-companion/src/webview/components/messages/toolcalls/UpdatedPlan/UpdatedPlanToolCall.tsx index cdd0a09b..a8373887 100644 --- a/packages/vscode-ide-companion/src/webview/components/toolcalls/UpdatedPlan/UpdatedPlanToolCall.tsx +++ b/packages/vscode-ide-companion/src/webview/components/messages/toolcalls/UpdatedPlan/UpdatedPlanToolCall.tsx @@ -10,8 +10,8 @@ import type React from 'react'; import type { BaseToolCallProps } from '../shared/types.js'; import type { ToolCallContainerProps } from '../shared/LayoutComponents.js'; import { groupContent, safeTitle } from '../shared/utils.js'; -import { CheckboxDisplay } from '../../ui/CheckboxDisplay.js'; -import type { PlanEntry } from '../../../../agents/qwenTypes.js'; +import { CheckboxDisplay } from './CheckboxDisplay.js'; +import type { PlanEntry } from '../../../../../types/qwenTypes.js'; type EntryStatus = 'pending' | 'in_progress' | 'completed'; diff --git a/packages/vscode-ide-companion/src/webview/components/toolcalls/Write/WriteToolCall.tsx b/packages/vscode-ide-companion/src/webview/components/messages/toolcalls/Write/WriteToolCall.tsx similarity index 98% rename from packages/vscode-ide-companion/src/webview/components/toolcalls/Write/WriteToolCall.tsx rename to packages/vscode-ide-companion/src/webview/components/messages/toolcalls/Write/WriteToolCall.tsx index f2ad115f..d0e6307b 100644 --- a/packages/vscode-ide-companion/src/webview/components/toolcalls/Write/WriteToolCall.tsx +++ b/packages/vscode-ide-companion/src/webview/components/messages/toolcalls/Write/WriteToolCall.tsx @@ -13,7 +13,7 @@ import { groupContent, mapToolStatusToContainerStatus, } from '../shared/utils.js'; -import { FileLink } from '../../ui/FileLink.js'; +import { FileLink } from '../../../layout/FileLink.js'; /** * Specialized component for Write tool calls diff --git a/packages/vscode-ide-companion/src/webview/components/toolcalls/index.tsx b/packages/vscode-ide-companion/src/webview/components/messages/toolcalls/index.tsx similarity index 69% rename from packages/vscode-ide-companion/src/webview/components/toolcalls/index.tsx rename to packages/vscode-ide-companion/src/webview/components/messages/toolcalls/index.tsx index 11020a7a..18a0fbf3 100644 --- a/packages/vscode-ide-companion/src/webview/components/toolcalls/index.tsx +++ b/packages/vscode-ide-companion/src/webview/components/messages/toolcalls/index.tsx @@ -10,14 +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 './done/Read/ReadToolCall.js'; +import { ReadToolCall } from './Read/ReadToolCall.js'; import { WriteToolCall } from './Write/WriteToolCall.js'; -import { EditToolCall } from './done/Edit/EditToolCall.js'; +import { EditToolCall } from './Edit/EditToolCall.js'; import { ExecuteToolCall as BashExecuteToolCall } from './Bash/Bash.js'; -import { ExecuteToolCall } from './done/Execute/Execute.js'; +import { ExecuteToolCall } from './Execute/Execute.js'; import { UpdatedPlanToolCall } from './UpdatedPlan/UpdatedPlanToolCall.js'; -import { ExecuteNodeToolCall } from './ExecuteNode/ExecuteNodeToolCall.js'; -import { SearchToolCall } from './done/Search/SearchToolCall.js'; +import { SearchToolCall } from './Search/SearchToolCall.js'; import { ThinkToolCall } from './Think/ThinkToolCall.js'; /** @@ -25,7 +24,6 @@ import { ThinkToolCall } from './Think/ThinkToolCall.js'; */ export const getToolCallComponent = ( kind: string, - toolCall?: import('./shared/types.js').ToolCallData, ): React.FC => { const normalizedKind = kind.toLowerCase(); @@ -41,24 +39,6 @@ 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': @@ -71,7 +51,6 @@ export const getToolCallComponent = ( case 'update_todos': case 'todowrite': return UpdatedPlanToolCall; - // return TodoWriteToolCall; case 'search': case 'grep': diff --git a/packages/vscode-ide-companion/src/webview/components/toolcalls/shared/LayoutComponents.css b/packages/vscode-ide-companion/src/webview/components/messages/toolcalls/shared/LayoutComponents.css similarity index 100% rename from packages/vscode-ide-companion/src/webview/components/toolcalls/shared/LayoutComponents.css rename to packages/vscode-ide-companion/src/webview/components/messages/toolcalls/shared/LayoutComponents.css diff --git a/packages/vscode-ide-companion/src/webview/components/toolcalls/shared/LayoutComponents.tsx b/packages/vscode-ide-companion/src/webview/components/messages/toolcalls/shared/LayoutComponents.tsx similarity index 98% rename from packages/vscode-ide-companion/src/webview/components/toolcalls/shared/LayoutComponents.tsx rename to packages/vscode-ide-companion/src/webview/components/messages/toolcalls/shared/LayoutComponents.tsx index 0e24a9e0..55592aca 100644 --- a/packages/vscode-ide-companion/src/webview/components/toolcalls/shared/LayoutComponents.tsx +++ b/packages/vscode-ide-companion/src/webview/components/messages/toolcalls/shared/LayoutComponents.tsx @@ -8,7 +8,7 @@ */ import type React from 'react'; -import { FileLink } from '../../ui/FileLink.js'; +import { FileLink } from '../../../layout/FileLink.js'; import './LayoutComponents.css'; /** diff --git a/packages/vscode-ide-companion/src/webview/components/toolcalls/shared/types.ts b/packages/vscode-ide-companion/src/webview/components/messages/toolcalls/shared/types.ts similarity index 100% rename from packages/vscode-ide-companion/src/webview/components/toolcalls/shared/types.ts rename to packages/vscode-ide-companion/src/webview/components/messages/toolcalls/shared/types.ts diff --git a/packages/vscode-ide-companion/src/webview/components/toolcalls/shared/utils.ts b/packages/vscode-ide-companion/src/webview/components/messages/toolcalls/shared/utils.ts similarity index 98% rename from packages/vscode-ide-companion/src/webview/components/toolcalls/shared/utils.ts rename to packages/vscode-ide-companion/src/webview/components/messages/toolcalls/shared/utils.ts index 4f43eb8d..4ae9efd6 100644 --- a/packages/vscode-ide-companion/src/webview/components/toolcalls/shared/utils.ts +++ b/packages/vscode-ide-companion/src/webview/components/messages/toolcalls/shared/utils.ts @@ -20,7 +20,7 @@ export const formatValue = (value: unknown): string => { return ''; } if (typeof value === 'string') { - // TODO: 尝试从 string 取出 Output 部分 + // TODO: Trying to take out the Output part from the string try { value = (JSON.parse(value) as { output?: unknown }).output ?? value; } catch (_error) { 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 deleted file mode 100644 index 99f3121f..00000000 --- a/packages/vscode-ide-companion/src/webview/components/toolcalls/ExecuteNode/ExecuteNode.css +++ /dev/null @@ -1,38 +0,0 @@ -/** - * @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 deleted file mode 100644 index 36826ade..00000000 --- a/packages/vscode-ide-companion/src/webview/components/toolcalls/ExecuteNode/ExecuteNodeToolCall.tsx +++ /dev/null @@ -1,83 +0,0 @@ -/** - * @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, - mapToolStatusToContainerStatus, -} 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/Think/TodoWriteToolCall.tsx b/packages/vscode-ide-companion/src/webview/components/toolcalls/Think/TodoWriteToolCall.tsx deleted file mode 100644 index 86bd0428..00000000 --- a/packages/vscode-ide-companion/src/webview/components/toolcalls/Think/TodoWriteToolCall.tsx +++ /dev/null @@ -1,137 +0,0 @@ -/** - * @license - * Copyright 2025 Qwen Team - * SPDX-License-Identifier: Apache-2.0 - * - * TodoWrite tool call component - specialized for todo list 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 TodoEntry { - 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 todo list with - [ ] / - [x] from text as much as possible -const parseTodoEntries = (textOutputs: string[]): TodoEntry[] => { - const text = textOutputs.join('\n'); - const lines = text.split(/\r?\n/); - const entries: TodoEntry[] = []; - - // Accept [ ], [x]/[X] and in-progress markers [-] or [*] - 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 === '-' || 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 TodoWrite tool calls - * Optimized for displaying todo list update operations - */ -export const TodoWriteToolCall: 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 = parseTodoEntries(textOutputs); - - const label = safeTitle(toolCall.title) || 'Update Todos'; - - 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/shared/DiffDisplay.css b/packages/vscode-ide-companion/src/webview/components/toolcalls/shared/DiffDisplay.css deleted file mode 100644 index 6d656cbd..00000000 --- a/packages/vscode-ide-companion/src/webview/components/toolcalls/shared/DiffDisplay.css +++ /dev/null @@ -1,322 +0,0 @@ -/** - * @license - * Copyright 2025 Qwen Team - * SPDX-License-Identifier: Apache-2.0 - * - * DiffDisplay 组件样式 - */ - -/* ======================================== - 容器样式 - ======================================== */ - -.diff-display-container { - font-family: var(--vscode-font-family); - font-size: var(--vscode-font-size); - width: 100%; -} - -/* ======================================== - 紧凑视图样式 - 超简洁版本 - ======================================== */ - -.diff-display-container { - border: 1px solid var(--vscode-panel-border); - border-radius: 4px; - background: var(--vscode-editor-background); - overflow: hidden; -} - -.diff-compact-clickable { - padding: 6px 10px; - cursor: pointer; - user-select: none; - transition: all 0.15s ease-in-out; -} - -.diff-compact-clickable:hover { - background: var(--vscode-list-hoverBackground); -} - -.diff-compact-clickable:active { - opacity: 0.9; -} - -.diff-compact-clickable:focus { - outline: 1px solid var(--vscode-focusBorder); - outline-offset: -1px; -} - -.diff-compact-header { - display: flex; - justify-content: space-between; - align-items: center; - gap: 12px; -} - -.diff-file-info { - flex: 1; - min-width: 0; /* 允许文字截断 */ - font-weight: 500; - font-size: 0.95em; -} - -.diff-file-info .file-link { - color: var(--vscode-foreground); -} - -.diff-stats { - display: flex; - gap: 8px; - align-items: center; - font-family: var(--vscode-editor-font-family, 'Menlo', 'Monaco', 'Courier New', monospace); - font-size: 0.85em; - flex-shrink: 0; -} - -.diff-stats > span { - font-weight: 600; - white-space: nowrap; -} - -.stat-added { - color: var(--vscode-gitDecoration-addedResourceForeground, #4ec9b0); -} - -.stat-removed { - color: var(--vscode-gitDecoration-deletedResourceForeground, #f48771); -} - -.stat-changed { - color: var(--vscode-gitDecoration-modifiedResourceForeground, #e5c07b); -} - -.stat-no-change { - color: var(--vscode-descriptionForeground); - opacity: 0.7; -} - -.diff-compact-actions { - padding: 6px 10px 8px; - border-top: 1px solid var(--vscode-panel-border); - background: var(--vscode-editorGroupHeader-tabsBackground); - display: flex; - justify-content: flex-end; -} - -/* ======================================== - 完整视图样式 - ======================================== */ - -/* 已移除完整视图,统一为简洁模式 + 预览 */ - -/* 预览区域(仅变更行) */ -.diff-preview { - margin: 0; - padding: 8px 10px; - background: var(--vscode-textCodeBlock-background, rgba(0, 0, 0, 0.06)); - border-top: 1px solid var(--vscode-panel-border); - max-height: 320px; - overflow: auto; -} - -.diff-file-path { - font-weight: 500; - flex: 1; -} - -.diff-header-actions { - display: flex; - gap: 8px; -} - -.diff-line { - white-space: pre; - font-family: var(--vscode-editor-font-family, 'Menlo', 'Monaco', 'Courier New', monospace); - font-size: 0.88em; - line-height: 1.45; -} - -.diff-line.added { - background: var(--vscode-diffEditor-insertedLineBackground, rgba(76, 175, 80, 0.18)); - color: var(--vscode-diffEditor-insertedTextForeground, #b5f1cc); -} - -.diff-line.removed { - background: var(--vscode-diffEditor-removedLineBackground, rgba(244, 67, 54, 0.18)); - color: var(--vscode-diffEditor-removedTextForeground, #f6b1a7); -} - -.diff-line.no-change { - color: var(--vscode-descriptionForeground); - opacity: 0.8; -} - -.diff-omitted { - color: var(--vscode-descriptionForeground); - font-style: italic; - padding-top: 6px; -} - -.diff-section { - padding: 12px; - background: var(--vscode-editor-background); -} - -.diff-section + .diff-section { - border-top: 1px solid var(--vscode-panel-border); -} - -.diff-label { - font-size: 0.85em; - color: var(--vscode-descriptionForeground); - margin-bottom: 6px; - text-transform: uppercase; - letter-spacing: 0.5px; - font-weight: 600; -} - -/* ======================================== - 按钮样式 - ======================================== */ - -.diff-action-button { - display: inline-flex; - align-items: center; - gap: 6px; - padding: 6px 12px; - border: none; - border-radius: 4px; - font-size: 0.9em; - font-family: var(--vscode-font-family); - cursor: pointer; - transition: all 0.15s ease-in-out; - white-space: nowrap; -} - -.diff-action-button svg { - flex-shrink: 0; -} - -.diff-action-button.primary { - background: var(--vscode-button-background); - color: var(--vscode-button-foreground); -} - -.diff-action-button.primary:hover { - background: var(--vscode-button-hoverBackground); -} - -.diff-action-button.primary:active { - opacity: 0.9; -} - -.diff-action-button.secondary { - background: transparent; - color: var(--vscode-textLink-foreground); - padding: 4px 8px; - font-size: 0.85em; -} - -.diff-action-button.secondary:hover { - background: var(--vscode-button-secondaryHoverBackground); - text-decoration: underline; -} - -.diff-action-button:focus { - outline: 1px solid var(--vscode-focusBorder); - outline-offset: 2px; -} - -.diff-action-button:disabled { - opacity: 0.5; - cursor: not-allowed; -} - -/* ======================================== - 代码块样式 - ======================================== */ - -.diff-section .code-block { - margin: 0; - padding: 12px; - background: var(--vscode-textCodeBlock-background, rgba(0, 0, 0, 0.1)); - border: 1px solid var(--vscode-panel-border); - border-radius: 4px; - overflow-x: auto; - font-family: var(--vscode-editor-font-family, 'Menlo', 'Monaco', 'Courier New', monospace); - font-size: 0.9em; - line-height: 1.5; -} - -.diff-section .code-content { - white-space: pre; - color: var(--vscode-editor-foreground); -} - -/* ======================================== - 响应式调整 - ======================================== */ - -@media (max-width: 600px) { - .diff-compact-header { - flex-direction: column; - align-items: flex-start; - } - - .diff-stats { - align-self: flex-start; - } -} - -/* ======================================== - 高对比度模式支持 - ======================================== */ - -@media (prefers-contrast: high) { - .diff-compact-view, - .diff-full-view { - border-width: 2px; - } - - .diff-stats > span { - font-weight: 700; - border: 1px solid currentColor; - } - - .diff-action-button { - border: 1px solid currentColor; - } -} - -/* ======================================== - 深色主题优化 - ======================================== */ - -@media (prefers-color-scheme: dark) { - .diff-compact-view:hover { - box-shadow: 0 2px 12px rgba(0, 0, 0, 0.3); - } - - .stat-added { - background: rgba(78, 201, 176, 0.2); - } - - .stat-removed { - background: rgba(244, 135, 113, 0.2); - } - - .stat-changed { - background: rgba(229, 192, 123, 0.2); - } -} - -/* ======================================== - LocationsList 样式(用于 FileLink 列表) - ======================================== */ - -.locations-list { - display: flex; - flex-direction: column; - gap: 6px; -} diff --git a/packages/vscode-ide-companion/src/webview/components/toolcalls/shared/DiffDisplay.tsx b/packages/vscode-ide-companion/src/webview/components/toolcalls/shared/DiffDisplay.tsx deleted file mode 100644 index 78af74f5..00000000 --- a/packages/vscode-ide-companion/src/webview/components/toolcalls/shared/DiffDisplay.tsx +++ /dev/null @@ -1,160 +0,0 @@ -/** - * @license - * Copyright 2025 Qwen Team - * SPDX-License-Identifier: Apache-2.0 - * - * Diff display component for showing file changes - */ - -import type React from 'react'; -import { useMemo } from 'react'; -import { FileLink } from '../../ui/FileLink.js'; -import { - calculateDiffStats, - formatDiffStatsDetailed, -} from '../../../utils/diffStats.js'; -import { OpenDiffIcon } from '../../icons/index.js'; -import './DiffDisplay.css'; -import { - computeLineDiff, - truncateOps, - type DiffOp, -} from '../../../utils/simpleDiff.js'; - -/** - * Props for DiffDisplay - */ -interface DiffDisplayProps { - path?: string; - oldText?: string | null; - newText?: string; - onOpenDiff?: () => void; - /** Whether to display statistics */ - showStats?: boolean; -} - -/** - * Display diff with compact stats or full before/after sections - * Supports toggling between compact and full view modes - */ -export const DiffDisplay: React.FC = ({ - path, - oldText, - newText, - onOpenDiff, - showStats = true, -}) => { - // Statistics (recalculate only when text changes) - const stats = useMemo( - () => calculateDiffStats(oldText, newText), - [oldText, newText], - ); - - // Only generate changed lines (additions/deletions), do not render context - const ops: DiffOp[] = useMemo( - () => computeLineDiff(oldText, newText), - [oldText, newText], - ); - const { - items: previewOps, - truncated, - omitted, - } = useMemo(() => truncateOps(ops), [ops]); - - return ( -
-
{ - if (e.key === 'Enter' || e.key === ' ') { - e.preventDefault(); - onOpenDiff?.(); - } - }} - > -
- {path && ( -
- -
- )} - {showStats && ( -
- {stats.added > 0 && ( - +{stats.added} - )} - {stats.removed > 0 && ( - -{stats.removed} - )} - {stats.changed > 0 && ( - ~{stats.changed} - )} - {stats.total === 0 && ( - No changes - )} -
- )} -
-
- - {/* Only draw preview area for diff lines */} -
-        
- {previewOps.length === 0 && ( -
(no changes)
- )} - {previewOps.map((op, idx) => { - if (op.type === 'add') { - const line = op.line; - return ( -
- +{line || ' '} -
- ); - } - if (op.type === 'remove') { - const line = op.line; - return ( -
- -{line || ' '} -
- ); - } - return null; - })} - {truncated && ( -
- … {omitted} lines omitted -
- )} -
-
- - {/* Provide explicit open button below preview (optional) */} - {onOpenDiff && ( -
- -
- )} -
- ); -}; diff --git a/packages/vscode-ide-companion/src/webview/components/toolcalls/shared/SimpleTimeline.css b/packages/vscode-ide-companion/src/webview/components/toolcalls/shared/SimpleTimeline.css deleted file mode 100644 index cb7557ed..00000000 --- a/packages/vscode-ide-companion/src/webview/components/toolcalls/shared/SimpleTimeline.css +++ /dev/null @@ -1,70 +0,0 @@ -/** - * @license - * Copyright 2025 Qwen Team - * SPDX-License-Identifier: Apache-2.0 - * - * Simplified timeline styles for tool calls and messages - * Only keeping actually used styles - */ - -/* ToolCallContainer timeline styles (from LayoutComponents.css) */ -.toolcall-container { - position: relative; - padding-left: 30px; - padding-top: 8px; - padding-bottom: 8px; -} - -/* ToolCallContainer timeline connector */ -.toolcall-container::after { - content: ''; - position: absolute; - left: 12px; - top: 0; - bottom: 0; - width: 1px; - background-color: var(--app-primary-border-color); -} - -/* First item: connector starts from status point position */ -.toolcall-container:first-child::after { - top: 24px; -} - -/* Last item: connector shows only upper part */ -.toolcall-container:last-child::after { - height: calc(100% - 24px); - top: 0; - bottom: auto; -} - -/* AssistantMessage timeline styles */ -.assistant-message-container { - position: relative; - padding-left: 30px; - padding-top: 8px; - padding-bottom: 8px; -} - -/* AssistantMessage timeline connector */ -.assistant-message-container::after { - content: ''; - position: absolute; - left: 12px; - top: 0; - bottom: 0; - width: 1px; - background-color: var(--app-primary-border-color); -} - -/* First item: connector starts from status point position */ -.assistant-message-container:first-child::after { - top: 24px; -} - -/* Last item: connector shows only upper part */ -.assistant-message-container:last-child::after { - height: calc(100% - 24px); - top: 0; - bottom: auto; -} \ No newline at end of file diff --git a/packages/vscode-ide-companion/src/webview/handlers/BaseMessageHandler.ts b/packages/vscode-ide-companion/src/webview/handlers/BaseMessageHandler.ts index e82d572f..4d01fd02 100644 --- a/packages/vscode-ide-companion/src/webview/handlers/BaseMessageHandler.ts +++ b/packages/vscode-ide-companion/src/webview/handlers/BaseMessageHandler.ts @@ -4,8 +4,8 @@ * SPDX-License-Identifier: Apache-2.0 */ -import type { QwenAgentManager } from '../../agents/qwenAgentManager.js'; -import type { ConversationStore } from '../../storage/conversationStore.js'; +import type { QwenAgentManager } from '../../services/qwenAgentManager.js'; +import type { ConversationStore } from '../../services/conversationStore.js'; /** * Base message handler interface diff --git a/packages/vscode-ide-companion/src/webview/handlers/MessageRouter.ts b/packages/vscode-ide-companion/src/webview/handlers/MessageRouter.ts index 9ddeec8b..b60086d3 100644 --- a/packages/vscode-ide-companion/src/webview/handlers/MessageRouter.ts +++ b/packages/vscode-ide-companion/src/webview/handlers/MessageRouter.ts @@ -5,8 +5,8 @@ */ import type { IMessageHandler } from './BaseMessageHandler.js'; -import type { QwenAgentManager } from '../../agents/qwenAgentManager.js'; -import type { ConversationStore } from '../../storage/conversationStore.js'; +import type { QwenAgentManager } from '../../services/qwenAgentManager.js'; +import type { ConversationStore } from '../../services/conversationStore.js'; import { SessionMessageHandler } from './SessionMessageHandler.js'; import { FileMessageHandler } from './FileMessageHandler.js'; import { EditorMessageHandler } from './EditorMessageHandler.js'; diff --git a/packages/vscode-ide-companion/src/webview/handlers/SessionMessageHandler.ts b/packages/vscode-ide-companion/src/webview/handlers/SessionMessageHandler.ts index c46b4657..65a40942 100644 --- a/packages/vscode-ide-companion/src/webview/handlers/SessionMessageHandler.ts +++ b/packages/vscode-ide-companion/src/webview/handlers/SessionMessageHandler.ts @@ -6,7 +6,7 @@ import * as vscode from 'vscode'; import { BaseMessageHandler } from './BaseMessageHandler.js'; -import type { ChatMessage } from '../../agents/qwenAgentManager.js'; +import type { ChatMessage } from '../../services/qwenAgentManager.js'; /** * Session message handler @@ -581,10 +581,11 @@ export class SessionMessageHandler extends BaseMessageHandler { let sessionDetails: Record | null = null; try { const allSessions = await this.agentManager.getSessionList(); - sessionDetails = allSessions.find( - (s: { id?: string; sessionId?: string }) => - s.id === sessionId || s.sessionId === sessionId, - ); + sessionDetails = + allSessions.find( + (s: { id?: string; sessionId?: string }) => + s.id === sessionId || s.sessionId === sessionId, + ) || null; } catch (err) { console.log( '[SessionMessageHandler] Could not get session details:', diff --git a/packages/vscode-ide-companion/src/webview/hooks/message/useMessageHandling.ts b/packages/vscode-ide-companion/src/webview/hooks/message/useMessageHandling.ts index a2354bd4..17fde331 100644 --- a/packages/vscode-ide-companion/src/webview/hooks/message/useMessageHandling.ts +++ b/packages/vscode-ide-companion/src/webview/hooks/message/useMessageHandling.ts @@ -73,7 +73,9 @@ export const useMessageHandling = () => { const appendStreamChunk = useCallback( (chunk: string) => { // Ignore late chunks after user cancelled streaming (until next streamStart) - if (!isStreaming) return; + if (!isStreaming) { + return; + } setMessages((prev) => { let idx = streamingMessageIndexRef.current; @@ -157,7 +159,9 @@ export const useMessageHandling = () => { // Thought handling appendThinkingChunk: (chunk: string) => { // Ignore late thoughts after user cancelled streaming - if (!isStreaming) return; + if (!isStreaming) { + return; + } setMessages((prev) => { let idx = thinkingMessageIndexRef.current; const next = prev.slice(); diff --git a/packages/vscode-ide-companion/src/webview/hooks/session/useSessionManagement.ts b/packages/vscode-ide-companion/src/webview/hooks/session/useSessionManagement.ts index 63458855..9fba4a80 100644 --- a/packages/vscode-ide-companion/src/webview/hooks/session/useSessionManagement.ts +++ b/packages/vscode-ide-companion/src/webview/hooks/session/useSessionManagement.ts @@ -59,7 +59,9 @@ export const useSessionManagement = (vscode: VSCodeAPI) => { }, [vscode]); const handleLoadMoreSessions = useCallback(() => { - if (!hasMore || isLoading || nextCursor === undefined) return; + if (!hasMore || isLoading || nextCursor === undefined) { + return; + } setIsLoading(true); vscode.postMessage({ type: 'getQwenSessions', diff --git a/packages/vscode-ide-companion/src/webview/hooks/useCompletionTrigger.ts b/packages/vscode-ide-companion/src/webview/hooks/useCompletionTrigger.ts index 859d80a2..8f6848c1 100644 --- a/packages/vscode-ide-companion/src/webview/hooks/useCompletionTrigger.ts +++ b/packages/vscode-ide-companion/src/webview/hooks/useCompletionTrigger.ts @@ -6,7 +6,7 @@ import type { RefObject } from 'react'; import { useState, useEffect, useCallback, useRef, useMemo } from 'react'; -import type { CompletionItem } from '../types/CompletionTypes.js'; +import type { CompletionItem } from '../../types/completionItemTypes.js'; interface CompletionTriggerState { isOpen: boolean; diff --git a/packages/vscode-ide-companion/src/webview/hooks/useToolCalls.ts b/packages/vscode-ide-companion/src/webview/hooks/useToolCalls.ts index e2c8e9b2..faf1cac4 100644 --- a/packages/vscode-ide-companion/src/webview/hooks/useToolCalls.ts +++ b/packages/vscode-ide-companion/src/webview/hooks/useToolCalls.ts @@ -5,8 +5,8 @@ */ import { useState, useCallback } from 'react'; -import type { ToolCallData } from '../components/ToolCall.js'; -import type { ToolCallUpdate } from '../types/toolCall.js'; +import type { ToolCallData } from '../components/messages/toolcalls/ToolCall.js'; +import type { ToolCallUpdate } from '../../types/qwenTypes.js'; /** * Tool call management Hook diff --git a/packages/vscode-ide-companion/src/webview/hooks/useWebViewMessages.ts b/packages/vscode-ide-companion/src/webview/hooks/useWebViewMessages.ts index c61a1b6d..e94e1645 100644 --- a/packages/vscode-ide-companion/src/webview/hooks/useWebViewMessages.ts +++ b/packages/vscode-ide-companion/src/webview/hooks/useWebViewMessages.ts @@ -6,13 +6,13 @@ import { useEffect, useRef, useCallback } from 'react'; import { useVSCode } from './useVSCode.js'; -import type { Conversation } from '../../storage/conversationStore.js'; +import type { Conversation } from '../../services/conversationStore.js'; import type { PermissionOption, ToolCall as PermissionToolCall, } from '../components/PermissionDrawer/PermissionRequest.js'; -import type { ToolCallUpdate, EditMode } from '../types/toolCall.js'; -import type { PlanEntry } from '../../agents/qwenTypes.js'; +import type { ToolCallUpdate, EditMode } from '../../types/qwenTypes.js'; +import type { PlanEntry } from '../../types/qwenTypes.js'; interface UseWebViewMessagesProps { // Session management diff --git a/packages/vscode-ide-companion/src/webview/index.tsx b/packages/vscode-ide-companion/src/webview/index.tsx index d9d8d821..b2a526fa 100644 --- a/packages/vscode-ide-companion/src/webview/index.tsx +++ b/packages/vscode-ide-companion/src/webview/index.tsx @@ -11,7 +11,7 @@ import './styles/tailwind.css'; // eslint-disable-next-line import/no-internal-modules import './styles/App.css'; // eslint-disable-next-line import/no-internal-modules -import './styles/ClaudeCodeStyles.css'; +import './styles/styles.css'; const container = document.getElementById('root'); if (container) { diff --git a/packages/vscode-ide-companion/src/webview/styles/ClaudeCodeStyles.css b/packages/vscode-ide-companion/src/webview/styles/styles.css similarity index 85% rename from packages/vscode-ide-companion/src/webview/styles/ClaudeCodeStyles.css rename to packages/vscode-ide-companion/src/webview/styles/styles.css index 655816ae..cd430654 100644 --- a/packages/vscode-ide-companion/src/webview/styles/ClaudeCodeStyles.css +++ b/packages/vscode-ide-companion/src/webview/styles/styles.css @@ -8,12 +8,9 @@ */ /* Import component styles */ -@import '../components/toolcalls/shared/DiffDisplay.css'; @import '../components/messages/Assistant/AssistantMessage.css'; -@import '../components/toolcalls/shared/SimpleTimeline.css'; -@import '../components/messages/QwenMessageTimeline.css'; -@import '../components/MarkdownRenderer/MarkdownRenderer.css'; - +@import './timeline.css'; +@import '../components/messages/MarkdownRenderer/MarkdownRenderer.css'; /* =========================== CSS Variables (from Claude Code root styles) @@ -56,7 +53,10 @@ --app-code-background: var(--vscode-textCodeBlock-background); /* Warning/Error Styles */ - --app-warning-background: var(--vscode-editorWarning-background, rgba(255, 204, 0, 0.1)); + --app-warning-background: var( + --vscode-editorWarning-background, + rgba(255, 204, 0, 0.1) + ); --app-warning-border: var(--vscode-editorWarning-foreground, #ffcc00); --app-warning-foreground: var(--vscode-editorWarning-foreground, #ffcc00); } diff --git a/packages/vscode-ide-companion/src/webview/styles/timeline.css b/packages/vscode-ide-companion/src/webview/styles/timeline.css new file mode 100644 index 00000000..25d5cc85 --- /dev/null +++ b/packages/vscode-ide-companion/src/webview/styles/timeline.css @@ -0,0 +1,126 @@ +/** + * @license + * Copyright 2025 Qwen Team + * SPDX-License-Identifier: Apache-2.0 + * + * Unified timeline styles for tool calls and messages + */ + +/* ========================================== + ToolCallContainer timeline styles + ========================================== */ +.toolcall-container { + position: relative; + padding-left: 30px; + padding-top: 8px; + padding-bottom: 8px; +} + +/* ToolCallContainer timeline connector */ +.toolcall-container::after { + content: ''; + position: absolute; + left: 12px; + top: 0; + bottom: 0; + width: 1px; + background-color: var(--app-primary-border-color); +} + +/* First item: connector starts from status point position */ +.toolcall-container:first-child::after { + top: 24px; +} + +/* Last item: connector shows only upper part */ +.toolcall-container:last-child::after { + height: calc(100% - 24px); + top: 0; + bottom: auto; +} + +/* ========================================== + AssistantMessage timeline styles + ========================================== */ +.assistant-message-container { + position: relative; + padding-left: 30px; + padding-top: 8px; + padding-bottom: 8px; +} + +/* AssistantMessage timeline connector */ +.assistant-message-container::after { + content: ''; + position: absolute; + left: 12px; + top: 0; + bottom: 0; + width: 1px; + background-color: var(--app-primary-border-color); +} + +/* First item: connector starts from status point position */ +.assistant-message-container:first-child::after { + top: 24px; +} + +/* Last item: connector shows only upper part */ +.assistant-message-container:last-child::after { + height: calc(100% - 24px); + top: 0; + bottom: auto; +} + +/* ========================================== + Custom timeline styles for qwen-message message-item elements + ========================================== */ + +/* Default connector style - creates full-height connectors for all AI message items */ +.qwen-message.message-item:not(.user-message-container)::after { + content: ''; + position: absolute; + left: 12px; + top: 0; + bottom: 0; + width: 1px; + background-color: var(--app-primary-border-color); + z-index: 0; +} + +/* Handle the start of each AI message sequence - includes the first AI message in the entire message list and new AI messages interrupted by user messages */ +.qwen-message.message-item:not(.user-message-container):first-child::after, +.user-message-container + .qwen-message.message-item:not(.user-message-container)::after, +/* If the previous sibling is not .qwen-message.message-item (such as waiting prompts, sentinel elements, or card-style tool calls), also treat as a new group start */ +.chat-messages > :not(.qwen-message.message-item) + + .qwen-message.message-item:not(.user-message-container)::after { + top: 15px; +} + +/* Handle the end of each AI message sequence */ +/* When the next sibling is a user message */ +.qwen-message.message-item:not(.user-message-container):has(+ .user-message-container)::after, +/* Or when the next sibling is not .qwen-message.message-item (such as waiting prompts, sentinel elements, card-style tool calls, etc.) */ +.qwen-message.message-item:not(.user-message-container):has(+ :not(.qwen-message.message-item))::after, +/* When it's truly the last child element of the parent container */ +.qwen-message.message-item:not(.user-message-container):last-child::after { + /* Note: When setting both top and bottom, the height is (container height - top - bottom). + * Here we expect "15px spacing at the bottom", so bottom should be 15px (not calc(100% - 15px)). */ + top: 0; + bottom: calc(100% - 15px); +} + +.user-message-container:first-child { + margin-top: 0; +} + +.message-item { + padding: 8px 0; + width: 100%; + align-items: flex-start; + padding-left: 30px; + user-select: text; + position: relative; + padding-top: 8px; + padding-bottom: 8px; +} \ No newline at end of file diff --git a/packages/vscode-ide-companion/src/webview/types/toolCall.ts b/packages/vscode-ide-companion/src/webview/types/toolCall.ts deleted file mode 100644 index 47051189..00000000 --- a/packages/vscode-ide-companion/src/webview/types/toolCall.ts +++ /dev/null @@ -1,39 +0,0 @@ -/** - * @license - * Copyright 2025 Qwen Team - * SPDX-License-Identifier: Apache-2.0 - */ - -/** - * Tool call update type - */ -export interface ToolCallUpdate { - type: 'tool_call' | 'tool_call_update'; - toolCallId: string; - kind?: string; - title?: string; - status?: 'pending' | 'in_progress' | 'completed' | 'failed'; - rawInput?: unknown; - content?: Array<{ - type: 'content' | 'diff'; - content?: { - type: string; - text?: string; - [key: string]: unknown; - }; - path?: string; - oldText?: string | null; - newText?: string; - [key: string]: unknown; - }>; - locations?: Array<{ - path: string; - line?: number | null; - }>; - timestamp?: number; // Add timestamp field for message ordering -} - -/** - * Edit mode type - */ -export type EditMode = 'ask' | 'auto' | 'plan' | 'yolo'; diff --git a/packages/vscode-ide-companion/src/webview/utils/diffUtils.ts b/packages/vscode-ide-companion/src/webview/utils/diffUtils.ts index 3e6dde6b..dac37cf3 100644 --- a/packages/vscode-ide-companion/src/webview/utils/diffUtils.ts +++ b/packages/vscode-ide-companion/src/webview/utils/diffUtils.ts @@ -6,7 +6,7 @@ * Shared utilities for handling diff operations in the webview */ -import type { WebviewApi } from 'vscode-webview'; +import type { VSCodeAPI } from '../hooks/useVSCode.js'; /** * Handle opening a diff view for a file @@ -16,7 +16,7 @@ import type { WebviewApi } from 'vscode-webview'; * @param newText New content (right side) */ export const handleOpenDiff = ( - vscode: WebviewApi, + vscode: VSCodeAPI, path: string | undefined, oldText: string | null | undefined, newText: string | undefined, diff --git a/packages/vscode-ide-companion/tailwind.config.js b/packages/vscode-ide-companion/tailwind.config.js index 0c8baa20..956f785c 100644 --- a/packages/vscode-ide-companion/tailwind.config.js +++ b/packages/vscode-ide-companion/tailwind.config.js @@ -7,11 +7,7 @@ /* eslint-env node */ /** @type {import('tailwindcss').Config} */ export default { - content: [ - // Progressive adoption strategy: Only scan newly created Tailwind components - './src/webview/**/**/*.{js,jsx,ts,tsx}', - './src/webview/components/ui/CheckboxDisplay.tsx', - ], + content: ['./src/webview/**/**/*.{js,jsx,ts,tsx}'], theme: { extend: { keyframes: {