From 3d535fd3e1a8518b94e1f2a9119ab953ac3d075a Mon Sep 17 00:00:00 2001 From: Roe-xin Date: Wed, 31 Dec 2025 18:02:38 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BC=98=E5=8C=96=E5=90=8E=E7=AB=AF?= =?UTF-8?q?=E6=9C=8D=E5=8A=A1=E4=B8=8D=E5=8F=AF=E7=94=A8=E6=97=B6=E7=9A=84?= =?UTF-8?q?=E9=94=99=E8=AF=AF=E5=A4=84=E7=90=86=EF=BC=8C=E7=A7=BB=E9=99=A4?= =?UTF-8?q?=E6=9C=AC=E5=9C=B0=E6=A8=A1=E6=8B=9F=E5=9B=9E=E5=A4=8D=E9=80=BB?= =?UTF-8?q?=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils/messageHandler.ts | 364 +++++++++++++++++------------------- 1 file changed, 172 insertions(+), 192 deletions(-) diff --git a/src/utils/messageHandler.ts b/src/utils/messageHandler.ts index 368abcf..0495f0e 100644 --- a/src/utils/messageHandler.ts +++ b/src/utils/messageHandler.ts @@ -19,7 +19,7 @@ import { dialogManager, DialogSession } from "../services/dialogService"; import { userInteractionManager } from "../services/userInteraction"; import { healthCheck } from "../services/apiClient"; -import type { RunMode } from '../types/api'; +import type { RunMode } from "../types/api"; /** 是否使用后端服务(可通过配置控制) */ let useBackendService = true; @@ -32,7 +32,7 @@ let pendingPlanExecution: { panel: vscode.WebviewPanel; planTitle: string; extensionPath: string; - taskId: string; // 保存 taskId 以便复用 + taskId: string; // 保存 taskId 以便复用 } | null = null; /** @@ -45,7 +45,7 @@ export function setPendingPlanExecution( taskId: string ): void { pendingPlanExecution = { panel, planTitle, extensionPath, taskId }; - console.log('[MessageHandler] 设置待执行计划:', planTitle, 'taskId:', taskId); + console.log("[MessageHandler] 设置待执行计划:", planTitle, "taskId:", taskId); } /** @@ -90,29 +90,22 @@ export async function handleUserMessage( await handleUserMessageWithBackend(panel, text, extensionPath, mode); return; } catch (error) { - console.error("后端服务不可用,回退到本地模式:", error); - // 后端不可用时,使用本地模拟回复 + console.error("后端服务不可用:", error); + panel.webview.postMessage({ + command: "updateStatus", + text: "后端服务不可用", + type: "error", + }); + throw error; } } - // 本地模拟回复(后端不可用时的 fallback) - console.log("使用本地模拟回复"); - const reply = getMockReply(text); - - // 记录AI回复到历史(允许失败) - try { - const historyManager = ChatHistoryManager.getInstance(); - await historyManager.addAiMessage(reply); - } catch (error) { - console.warn("记录AI回复历史失败:", error); - } - - setTimeout(() => { - panel.webview.postMessage({ - command: "receiveMessage", - text: reply, - }); - }, 500); + // 如果没有 extensionPath,显示错误 + panel.webview.postMessage({ + command: "updateStatus", + text: "无法处理消息:缺少必要参数", + type: "error", + }); } /** @@ -123,13 +116,13 @@ async function handleUserMessageWithBackend( text: string, extensionPath: string, mode?: RunMode, - reuseTaskId?: string // 可选,复用现有 taskId(用于 Plan 模式确认后继续执行) + reuseTaskId?: string // 可选,复用现有 taskId(用于 Plan 模式确认后继续执行) ): Promise { // 创建或复用会话 if (!currentSession || !currentSession.active) { currentSession = dialogManager.createSession(extensionPath, reuseTaskId); if (reuseTaskId) { - console.log('[MessageHandler] 复用 taskId 创建会话:', reuseTaskId); + console.log("[MessageHandler] 复用 taskId 创建会话:", reuseTaskId); } } @@ -143,117 +136,134 @@ async function handleUserMessageWithBackend( }); return new Promise((resolve, reject) => { - currentSession!.sendMessage(text, { - onText: (fullText, isStreaming) => { - // 不再单独处理文本,统一通过 onSegmentUpdate 处理 + currentSession!.sendMessage( + text, + { + onText: (fullText, isStreaming) => { + // 不再单独处理文本,统一通过 onSegmentUpdate 处理 + }, + + onSegmentUpdate: (segments) => { + // 实时发送段落更新,按后端返回顺序展示 + panel.webview.postMessage({ + command: "updateSegments", + segments: segments, + }); + }, + + onToolStart: (toolName) => { + // 更新状态栏 + panel.webview.postMessage({ + command: "updateStatus", + text: `正在执行 ${toolName}...`, + type: "working", + }); + }, + + onToolComplete: (toolName, result) => { + // 工具完成,不需要单独处理,通过 onSegmentUpdate 统一更新 + }, + + onToolError: (toolName, error) => { + // 工具错误,不需要单独处理,通过 onSegmentUpdate 统一更新 + }, + + onQuestion: (askId, question, options) => { + // 只更新状态栏,问题显示由 onSegmentUpdate 统一处理 + panel.webview.postMessage({ + command: "updateStatus", + text: "等待用户回答...", + type: "working", + }); + }, + + onComplete: async (segments) => { + // 隐藏状态栏 + panel.webview.postMessage({ + command: "hideStatus", + }); + + // 最后一次发送完整的段落 + console.log("[MessageHandler] 对话完成, 段落数:", segments.length); + console.log( + "[MessageHandler] segments 内容:", + JSON.stringify(segments) + ); + + const result = await panel.webview.postMessage({ + command: "updateSegments", + segments: segments, + isComplete: true, + }); + console.log("[MessageHandler] postMessage 返回值:", result); + + // 保存完整的 segments 到历史记录 + try { + // 将完整的 segments 保存到一条 AI 消息中 + // 这样加载时可以完整还原对话样式 + const textContent = segments + .filter((s) => s.type === "text" && s.content) + .map((s) => s.content) + .join("\n"); + + await historyManager.addAiMessage(textContent, undefined, segments); + } catch (error) { + console.warn("保存AI响应历史失败:", error); + } + + // 检查是否有待执行的计划(Plan 模式确认后自动执行) + if (pendingPlanExecution) { + const { + panel: execPanel, + planTitle, + extensionPath: execPath, + taskId: reuseTaskId, + } = pendingPlanExecution; + pendingPlanExecution = null; + console.log( + "[MessageHandler] 自动执行计划:", + planTitle, + "复用 taskId:", + reuseTaskId + ); + + // 延迟一小段时间确保当前对话完全结束 + setTimeout(async () => { + try { + // 复用 taskId 创建新会话,确保知识图谱数据不丢失 + await handleUserMessageWithBackend( + execPanel, + `请按照刚才的计划执行:${planTitle}`, + execPath, + "agent", + reuseTaskId // 复用 Plan 模式的 taskId + ); + } catch (err) { + console.error("[MessageHandler] 自动执行计划失败:", err); + } + }, 500); + } + + resolve(); + }, + + onError: (message) => { + panel.webview.postMessage({ + command: "hideLoading", + }); + panel.webview.postMessage({ + command: "receiveMessage", + text: `❌ 错误: ${message}`, + }); + reject(new Error(message)); + }, + + onNotification: (message) => { + vscode.window.showInformationMessage(message); + }, }, - - onSegmentUpdate: (segments) => { - // 实时发送段落更新,按后端返回顺序展示 - panel.webview.postMessage({ - command: "updateSegments", - segments: segments, - }); - }, - - onToolStart: (toolName) => { - // 更新状态栏 - panel.webview.postMessage({ - command: "updateStatus", - text: `正在执行 ${toolName}...`, - type: "working", - }); - }, - - onToolComplete: (toolName, result) => { - // 工具完成,不需要单独处理,通过 onSegmentUpdate 统一更新 - }, - - onToolError: (toolName, error) => { - // 工具错误,不需要单独处理,通过 onSegmentUpdate 统一更新 - }, - - onQuestion: (askId, question, options) => { - // 只更新状态栏,问题显示由 onSegmentUpdate 统一处理 - panel.webview.postMessage({ - command: "updateStatus", - text: "等待用户回答...", - type: "working", - }); - }, - - onComplete: async (segments) => { - // 隐藏状态栏 - panel.webview.postMessage({ - command: "hideStatus", - }); - - // 最后一次发送完整的段落 - console.log('[MessageHandler] 对话完成, 段落数:', segments.length); - console.log('[MessageHandler] segments 内容:', JSON.stringify(segments)); - - const result = await panel.webview.postMessage({ - command: "updateSegments", - segments: segments, - isComplete: true, - }); - console.log('[MessageHandler] postMessage 返回值:', result); - - // 保存完整的 segments 到历史记录 - try { - // 将完整的 segments 保存到一条 AI 消息中 - // 这样加载时可以完整还原对话样式 - const textContent = segments - .filter(s => s.type === 'text' && s.content) - .map(s => s.content) - .join('\n'); - - await historyManager.addAiMessage(textContent, undefined, segments); - } catch (error) { - console.warn("保存AI响应历史失败:", error); - } - - // 检查是否有待执行的计划(Plan 模式确认后自动执行) - if (pendingPlanExecution) { - const { panel: execPanel, planTitle, extensionPath: execPath, taskId: reuseTaskId } = pendingPlanExecution; - pendingPlanExecution = null; - console.log('[MessageHandler] 自动执行计划:', planTitle, '复用 taskId:', reuseTaskId); - - // 延迟一小段时间确保当前对话完全结束 - setTimeout(async () => { - try { - // 复用 taskId 创建新会话,确保知识图谱数据不丢失 - await handleUserMessageWithBackend( - execPanel, - `请按照刚才的计划执行:${planTitle}`, - execPath, - 'agent', - reuseTaskId // 复用 Plan 模式的 taskId - ); - } catch (err) { - console.error('[MessageHandler] 自动执行计划失败:', err); - } - }, 500); - } - - resolve(); - }, - - onError: (message) => { - panel.webview.postMessage({ - command: "hideLoading", - }); - panel.webview.postMessage({ - command: "receiveMessage", - text: `❌ 错误: ${message}`, - }); - reject(new Error(message)); - }, - - onNotification: (message) => { - vscode.window.showInformationMessage(message); - }, - }, mode); + mode + ); }); } @@ -298,52 +308,52 @@ export async function handlePlanAction( planTitle: string, extensionPath: string ): Promise { - console.log('[handlePlanAction] action:', action, 'planTitle:', planTitle); + console.log("[handlePlanAction] action:", action, "planTitle:", planTitle); switch (action) { - case 'confirm': + case "confirm": // 确认执行:切换到 Agent 模式并发送执行消息 panel.webview.postMessage({ - command: 'switchMode', - mode: 'agent' + command: "switchMode", + mode: "agent", }); // 发送执行消息 await handleUserMessage( panel, `请按照刚才的计划执行:${planTitle}`, extensionPath, - 'agent' + "agent" ); break; - case 'modify': + case "modify": // 修改计划:提示用户输入修改建议 const modification = await vscode.window.showInputBox({ - prompt: '请输入您对计划的修改建议', - placeHolder: '例如:第2步需要先检查文件是否存在...', - ignoreFocusOut: true + prompt: "请输入您对计划的修改建议", + placeHolder: "例如:第2步需要先检查文件是否存在...", + ignoreFocusOut: true, }); if (modification) { await handleUserMessage( panel, `请根据以下建议修改计划:${modification}`, extensionPath, - 'plan' + "plan" ); } break; - case 'cancel': + case "cancel": // 取消计划:通知用户 panel.webview.postMessage({ - command: 'addMessage', - text: '计划已取消。', - sender: 'bot' + command: "addMessage", + text: "计划已取消。", + sender: "bot", }); break; default: - console.warn('[handlePlanAction] 未知操作:', action); + console.warn("[handlePlanAction] 未知操作:", action); } } @@ -382,7 +392,9 @@ function parseFileOperation(text: string): { } // 匹配重命名文件:将 xxx.ts 重命名为 yyy.ts 或 把 xxx.ts 改名为 yyy.ts(优先匹配,避免被修改匹配) - const renameMatch = lowerText.match(/(?:将|把)\s*(.+?\.\w+)\s*(?:重命名|改名)\s*(?:为|成)\s*(.+?\.\w+)/); + const renameMatch = lowerText.match( + /(?:将|把)\s*(.+?\.\w+)\s*(?:重命名|改名)\s*(?:为|成)\s*(.+?\.\w+)/ + ); if (renameMatch) { const oldPath = renameMatch[1].trim(); const newPath = renameMatch[2].trim(); @@ -397,7 +409,9 @@ function parseFileOperation(text: string): { // 格式1: 在 xxx.ts 中将 "aaa" 替换为 "bbb" // 格式2: 将 xxx.ts 文件 "aaa" 替换为 "bbb" // 格式3: 将 xxx.ts 文件 'aaa' 替换为 'bbb' - const replaceMatch1 = lowerText.match(/在\s*(.+?\.\w+)\s*(?:文件)?中?\s*(?:将|把)\s*["'](.+?)["']\s*替换\s*(?:为|成)\s*["'](.+?)["']/); + const replaceMatch1 = lowerText.match( + /在\s*(.+?\.\w+)\s*(?:文件)?中?\s*(?:将|把)\s*["'](.+?)["']\s*替换\s*(?:为|成)\s*["'](.+?)["']/ + ); if (replaceMatch1) { const filePath = replaceMatch1[1].trim(); const searchText = replaceMatch1[2].trim(); @@ -411,7 +425,9 @@ function parseFileOperation(text: string): { } // 格式2: 将 xxx.ts 文件 "aaa" 替换为 "bbb" - const replaceMatch2 = lowerText.match(/(?:将|把)\s*(.+?\.\w+)\s*(?:文件)?\s*["'](.+?)["']\s*替换\s*(?:为|成)\s*["'](.+?)["']/); + const replaceMatch2 = lowerText.match( + /(?:将|把)\s*(.+?\.\w+)\s*(?:文件)?\s*["'](.+?)["']\s*替换\s*(?:为|成)\s*["'](.+?)["']/ + ); if (replaceMatch2) { const filePath = replaceMatch2[1].trim(); const searchText = replaceMatch2[2].trim(); @@ -739,41 +755,6 @@ export async function handleReplaceInFile( } } -/** - * 获取模拟回复 - */ -function getMockReply(question: string): string { - const replies = [ - `已收到您的问题:"${question}" - -这是一个演示版本,实际需要连接AI服务。 - -示例回复:这是一个计数器模板: -\`\`\`verilog -module counter ( - input clk, - input rst_n, - output reg [3:0] count -); - always @(posedge clk or negedge rst_n) begin - if (!rst_n) count <= 0; - else count <= count + 1; - end -endmodule -\`\`\``, - - `感谢提问!关于"${question}",在真实版本中我会: -1. 分析您的代码上下文 -2. 提供优化建议 -3. 生成完整代码 -4. 解释设计原理 - -当前是演示版,请点击侧边栏按钮快速生成代码。`, - ]; - - return replies[Math.floor(Math.random() * replies.length)]; -} - /** * 将代码插入到编辑器 */ @@ -866,7 +847,8 @@ async function handleVCDGeneration( if (!projectCheck.hasTestbench) { errorMsg += "• ❌ 缺少 testbench 文件\n"; - errorMsg += "\n提示: testbench 文件应包含 $dumpfile 和 $dumpvars 语句来生成 VCD 文件。\n"; + errorMsg += + "\n提示: testbench 文件应包含 $dumpfile 和 $dumpvars 语句来生成 VCD 文件。\n"; } else { errorMsg += `• ✅ Testbench: ${projectCheck.testbenchFile}\n`; } @@ -910,9 +892,7 @@ async function handleVCDGeneration( fileName: fileName, }); - vscode.window.showInformationMessage( - `VCD 文件生成成功: ${fileName}` - ); + vscode.window.showInformationMessage(`VCD 文件生成成功: ${fileName}`); } else { panel.webview.postMessage({ command: "receiveMessage",