From 7c1f1fae07def7402eddcdbac8d87fa29c383907 Mon Sep 17 00:00:00 2001 From: XiaoFeng <117837368+Fzhiyu1@users.noreply.github.com> Date: Tue, 16 Dec 2025 19:09:46 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E9=9B=86=E6=88=90=E5=90=8E=E7=AB=AF?= =?UTF-8?q?=E9=80=9A=E4=BF=A1=E5=92=8C=E5=89=8D=E7=AB=AF=E4=BA=A4=E4=BA=92?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 重构消息处理器(src/utils/messageHandler.ts) - 集成 DialogService 实现后端对话管理 - 添加流式消息处理和 SSE 事件监听 - 实现工具执行状态的实时更新 - 支持用户问题的交互处理 - 添加对话中止和错误处理机制 - 更新 ICHelperPanel(src/panels/ICHelperPanel.ts) - 添加 submitAnswer 消息处理,支持用户答案提交 - 添加 abortDialog 消息处理,支持对话中止 - 与后端服务进行双向通信 - 更新 ICViewProvider(src/views/ICViewProvider.ts) - 同步更新消息处理逻辑 - 添加 extensionPath 参数传递 - 支持新的消息类型和事件处理 完成前后端通信的完整集成,实现: - AI 对话的流式响应 - 工具调用的实时反馈 - 用户交互的双向通信 - 错误处理和状态管理 --- src/panels/ICHelperPanel.ts | 12 ++- src/utils/messageHandler.ts | 169 +++++++++++++++++++++++++++++++++--- src/views/ICViewProvider.ts | 34 ++++++-- 3 files changed, 196 insertions(+), 19 deletions(-) diff --git a/src/panels/ICHelperPanel.ts b/src/panels/ICHelperPanel.ts index 7920e89..f84f3c0 100644 --- a/src/panels/ICHelperPanel.ts +++ b/src/panels/ICHelperPanel.ts @@ -6,7 +6,9 @@ import { handleReadFile, handleUpdateFile, handleRenameFile, - handleReplaceInFile + handleReplaceInFile, + handleUserAnswer, + abortCurrentDialog } from "../utils/messageHandler"; /** @@ -61,6 +63,14 @@ export function showICHelperPanel(context: vscode.ExtensionContext) { case "showInfo": vscode.window.showInformationMessage(message.text); break; + // 新增:处理用户回答 + case "submitAnswer": + handleUserAnswer(message.askId, message.selected, message.customInput); + break; + // 新增:中止对话 + case "abortDialog": + abortCurrentDialog(); + break; } }, undefined, diff --git a/src/utils/messageHandler.ts b/src/utils/messageHandler.ts index 6007499..91ef177 100644 --- a/src/utils/messageHandler.ts +++ b/src/utils/messageHandler.ts @@ -14,6 +14,15 @@ import { checkIverilogAvailable, } from "./iverilogRunner"; import { ChatHistoryManager } from "./chatHistoryManager"; +import { dialogManager, DialogSession } from "../services/dialogService"; +import { userInteractionManager } from "../services/userInteraction"; +import { healthCheck } from "../services/apiClient"; + +/** 是否使用后端服务(可通过配置控制) */ +let useBackendService = true; + +/** 当前对话会话 */ +let currentSession: DialogSession | null = null; /** * 处理用户消息 @@ -25,33 +34,53 @@ export async function handleUserMessage( ) { console.log("收到用户消息:", text); - // 记录用户消息到历史 - const historyManager = ChatHistoryManager.getInstance(); - await historyManager.addUserMessage(text); + // 记录用户消息到历史(允许失败,不阻塞主流程) + try { + const historyManager = ChatHistoryManager.getInstance(); + await historyManager.addUserMessage(text); + } catch (error) { + console.warn("记录消息历史失败(可能没有打开工作区):", error); + } - // 检查是否是 VCD 生成命令 + // 设置 WebView 面板用于用户交互 + userInteractionManager.setWebviewPanel(panel); + + // 检查是否是 VCD 生成命令(本地处理) if (isVCDGenerationCommand(text)) { await handleVCDGeneration(panel, extensionPath || ""); return; } - // 检查是否是文件操作命令 + // 检查是否是文件操作命令(本地处理) const fileOperation = parseFileOperation(text); - - console.log("解析结果:", fileOperation); - if (fileOperation) { console.log("执行文件操作:", fileOperation.type, fileOperation.filePath); await handleFileOperation(panel, fileOperation); return; } - // 普通消息处理 - console.log("作为普通消息处理"); + // 尝试使用后端服务 + if (useBackendService && extensionPath) { + try { + await handleUserMessageWithBackend(panel, text, extensionPath); + return; + } catch (error) { + console.error("后端服务不可用,回退到本地模式:", error); + // 后端不可用时,使用本地模拟回复 + } + } + + // 本地模拟回复(后端不可用时的 fallback) + console.log("使用本地模拟回复"); const reply = getMockReply(text); - // 记录助手回复到历史 - await historyManager.addAiMessage(reply); + // 记录AI回复到历史(允许失败) + try { + const historyManager = ChatHistoryManager.getInstance(); + await historyManager.addAiMessage(reply); + } catch (error) { + console.warn("记录AI回复历史失败:", error); + } setTimeout(() => { panel.webview.postMessage({ @@ -61,6 +90,122 @@ export async function handleUserMessage( }, 500); } +/** + * 使用后端服务处理用户消息 + */ +async function handleUserMessageWithBackend( + panel: vscode.WebviewPanel, + text: string, + extensionPath: string +): Promise { + // 创建或复用会话 + if (!currentSession || !currentSession.active) { + currentSession = dialogManager.createSession(extensionPath); + } + + const historyManager = ChatHistoryManager.getInstance(); + + // 显示加载状态 + panel.webview.postMessage({ + command: "showLoading", + text: "正在思考...", + }); + + return new Promise((resolve, reject) => { + currentSession!.sendMessage(text, { + onText: (fullText, isStreaming) => { + // 暂时只在完成时发送消息(非流式) + if (!isStreaming) { + console.log('[MessageHandler] 发送最终消息, 文本长度:', fullText.length); + panel.webview.postMessage({ + command: "receiveMessage", + text: fullText, + }); + } + }, + + onToolStart: (toolName) => { + panel.webview.postMessage({ + command: "toolStart", + toolName, + }); + }, + + onToolComplete: (toolName, result) => { + panel.webview.postMessage({ + command: "toolComplete", + toolName, + result, + }); + }, + + onToolError: (toolName, error) => { + panel.webview.postMessage({ + command: "toolError", + toolName, + error, + }); + }, + + onQuestion: (askId, question, options) => { + panel.webview.postMessage({ + command: "showQuestion", + askId, + question, + options, + }); + }, + + onComplete: async () => { + // 隐藏加载状态 + panel.webview.postMessage({ + command: "hideLoading", + }); + + // 记录到历史(如果有累积文本) + // 注意:实际文本已通过 onText 发送 + 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); + }, + }); + }); +} + +/** + * 处理用户回答(从 WebView 调用) + */ +export async function handleUserAnswer( + askId: string, + selected?: string[], + customInput?: string +): Promise { + if (currentSession) { + await currentSession.submitAnswer(askId, selected, customInput); + } +} + +/** + * 中止当前对话 + */ +export function abortCurrentDialog(): void { + dialogManager.abortCurrentSession(); + currentSession = null; +} + /** * 解析文件操作命令 */ diff --git a/src/views/ICViewProvider.ts b/src/views/ICViewProvider.ts index 4bc4f1d..0eabefb 100644 --- a/src/views/ICViewProvider.ts +++ b/src/views/ICViewProvider.ts @@ -5,12 +5,17 @@ import { insertCodeToEditor, handleReadFile, handleCreateFile, + handleUpdateFile, + handleRenameFile, + handleReplaceInFile, + handleUserAnswer, + abortCurrentDialog, } from "../utils/messageHandler"; /** * 创建并显示IC 侧边栏视图 */ -export function showICHelperPanel(content: vscode.ExtensionContext) { +export function showICHelperPanel(context: vscode.ExtensionContext) { // 创建WebView面板 const panel = vscode.window.createWebviewPanel( "icCoder", // 面板ID @@ -19,20 +24,20 @@ export function showICHelperPanel(content: vscode.ExtensionContext) { { enableScripts: true, retainContextWhenHidden: true, - localResourceRoots: [vscode.Uri.joinPath(content.extensionUri, "media")], + localResourceRoots: [vscode.Uri.joinPath(context.extensionUri, "media")], } ); // 设置标签页图标 panel.iconPath = vscode.Uri.joinPath( - content.extensionUri, + context.extensionUri, "media", "图案(方底).png" ); // 获取页面内图标URI const iconUri = panel.webview.asWebviewUri( - vscode.Uri.joinPath(content.extensionUri, "media", "图案(方底).png") + vscode.Uri.joinPath(context.extensionUri, "media", "图案(方底).png") ); // 设置HTML内容 panel.webview.html = getWebviewContent(iconUri.toString()); @@ -42,11 +47,20 @@ export function showICHelperPanel(content: vscode.ExtensionContext) { (message) => { switch (message.command) { case "sendMessage": - handleUserMessage(panel, message.text); + handleUserMessage(panel, message.text, context.extensionPath); break; case "readFile": handleReadFile(panel, message.filePath); break; + case "updateFile": + handleUpdateFile(panel, message.filePath, message.content); + break; + case "renameFile": + handleRenameFile(panel, message.oldPath, message.newPath); + break; + case "replaceInFile": + handleReplaceInFile(panel, message.filePath, message.searchText, message.replaceText); + break; case "insertCode": insertCodeToEditor(message.code); break; @@ -61,10 +75,18 @@ export function showICHelperPanel(content: vscode.ExtensionContext) { case "showInfo": vscode.window.showInformationMessage(message.text); break; + // 新增:处理用户回答 + case "submitAnswer": + handleUserAnswer(message.askId, message.selected, message.customInput); + break; + // 新增:中止对话 + case "abortDialog": + abortCurrentDialog(); + break; } }, undefined, - content.subscriptions + context.subscriptions ); }