import * as vscode from "vscode"; import * as path from "path"; import { readFileContent } from "./readFiles"; import { createFile, createOrOverwriteFile, deleteFile, updateFile, renameFile, replaceFile, } from "./createFiles"; import { generateVCD, checkVerilogProject, checkIverilogAvailable, } from "./iverilogRunner"; import { ChatHistoryManager } from "./chatHistoryManager"; import { dialogManager, DialogSession } from "../services/dialogService"; import { userInteractionManager } from "../services/userInteraction"; import { healthCheck } from "../services/apiClient"; import { checkBalanceBeforeSend } from "../services/creditsService"; import type { RunMode, ServiceTier } from "../types/api"; /** 是否使用后端服务(可通过配置控制) */ let useBackendService = true; /** 当前对话会话 */ let currentSession: DialogSession | null = null; /** 最后一个活跃的 taskId(用于压缩等操作) */ let lastTaskId: string | null = null; /** 待执行的计划(Plan 模式确认后自动执行) */ let pendingPlanExecution: { panel: vscode.WebviewPanel; planTitle: string; extensionPath: string; taskId: string; // 保存 taskId 以便复用 serviceTier?: ServiceTier; // 保存服务等级 } | null = null; /** * 设置待执行的计划(由 ICHelperPanel 调用) */ export function setPendingPlanExecution( panel: vscode.WebviewPanel, planTitle: string, extensionPath: string, taskId: string, serviceTier?: ServiceTier ): void { pendingPlanExecution = { panel, planTitle, extensionPath, taskId, serviceTier }; console.log("[MessageHandler] 设置待执行计划:", planTitle, "taskId:", taskId, "serviceTier:", serviceTier); } /** * 处理用户消息 */ export async function handleUserMessage( panel: vscode.WebviewPanel, text: string, extensionPath?: string, mode?: RunMode, serviceTier?: ServiceTier // 新增:服务等级参数 ) { console.log("收到用户消息:", text); // 记录用户消息到历史(允许失败,不阻塞主流程) try { const historyManager = ChatHistoryManager.getInstance(); await historyManager.addUserMessage(text); } catch (error) { console.warn("记录消息历史失败(可能没有打开工作区):", error); } // 设置 WebView 面板用于用户交互 userInteractionManager.setWebviewPanel(panel); // 检查是否是 VCD 生成命令(本地处理) if (isVCDGenerationCommand(text)) { await handleVCDGeneration(panel, extensionPath || ""); return; } // 检查是否是文件操作命令(本地处理) const fileOperation = parseFileOperation(text); if (fileOperation) { console.log("执行文件操作:", fileOperation.type, fileOperation.filePath); await handleFileOperation(panel, fileOperation); return; } // 发送前检测余额 const balanceCheck = await checkBalanceBeforeSend(); if (!balanceCheck.allowed) { console.warn("[MessageHandler] 余额不足,阻止发送:", balanceCheck.message); // 显示错误提示 const selection = await vscode.window.showWarningMessage( balanceCheck.message || "资源点余额不足", "去充值" ); if (selection === "去充值") { vscode.env.openExternal(vscode.Uri.parse("https://iccoder.com/recharge")); } // 恢复输入状态 panel.webview.postMessage({ command: "updateSegments", segments: [], isComplete: true, }); return; } // 尝试使用后端服务 if (useBackendService && extensionPath) { try { await handleUserMessageWithBackend(panel, text, extensionPath, mode, undefined, serviceTier); return; } catch (error) { console.error("后端服务不可用:", error); panel.webview.postMessage({ command: "updateStatus", text: "后端服务不可用", type: "error", }); // 恢复输入状态 panel.webview.postMessage({ command: "updateSegments", segments: [], isComplete: true, }); throw error; } } // 如果没有 extensionPath,显示错误 panel.webview.postMessage({ command: "updateStatus", text: "无法处理消息:缺少必要参数", type: "error", }); } /** * 使用后端服务处理用户消息 */ async function handleUserMessageWithBackend( panel: vscode.WebviewPanel, text: string, extensionPath: string, mode?: RunMode, reuseTaskId?: string, // 可选,复用现有 taskId(用于 Plan 模式确认后继续执行) serviceTier?: ServiceTier // 新增:服务等级参数 ): Promise { const historyManager = ChatHistoryManager.getInstance(); // 获取 historyManager 中的 taskId(由 ICHelperPanel 创建) // 优先使用 reuseTaskId,其次使用 historyManager 的 taskId const taskIdToUse = reuseTaskId || historyManager.getCurrentTaskId(); // 创建或复用会话 if (!currentSession || !currentSession.active) { currentSession = dialogManager.createSession(extensionPath, taskIdToUse || undefined); // 保存 taskId 用于后续操作(如压缩) lastTaskId = currentSession.getTaskId(); console.log("[MessageHandler] 创建会话: taskId=", lastTaskId, "来源=", taskIdToUse ? "historyManager" : "新生成"); } // 显示状态栏 panel.webview.postMessage({ command: "updateStatus", text: "思考中...", type: "thinking", }); return new Promise((resolve, reject) => { 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); 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, serviceTier: savedServiceTier, } = pendingPlanExecution; pendingPlanExecution = null; console.log( "[MessageHandler] 自动执行计划:", planTitle, "复用 taskId:", reuseTaskId, "serviceTier:", savedServiceTier ); // 延迟一小段时间确保当前对话完全结束 setTimeout(async () => { try { // 复用 taskId 创建新会话,确保知识图谱数据不丢失 await handleUserMessageWithBackend( execPanel, `请按照刚才的计划执行:${planTitle}`, execPath, "agent", reuseTaskId, // 复用 Plan 模式的 taskId savedServiceTier // 传递保存的服务等级 ); } catch (err) { console.error("[MessageHandler] 自动执行计划失败:", err); } }, 500); } resolve(); }, onError: (message) => { panel.webview.postMessage({ command: "hideLoading", }); panel.webview.postMessage({ command: "receiveMessage", text: `❌ 错误: ${message}`, }); // 恢复输入状态 panel.webview.postMessage({ command: "updateSegments", segments: [], isComplete: true, }); reject(new Error(message)); }, onNotification: (message) => { vscode.window.showInformationMessage(message); }, onContextUsage: (data) => { // 发送上下文使用量到 WebView panel.webview.postMessage({ command: "contextUsage", currentTokens: data.currentTokens, maxTokens: data.maxTokens, percentage: data.percentage, }); }, onPhaseProgress: (phaseId, status) => { // 发送阶段进度更新到 WebView // 映射 phaseId: sim -> simulation const stepMap: Record = { spec: "spec", design: "design", sim: "simulation", done: "done", }; const step = stepMap[phaseId] || phaseId; if (status === "current") { // 显示进度条并更新到当前步骤 panel.webview.postMessage({ type: "showProgress" }); panel.webview.postMessage({ type: "updateProgress", step }); } else if (status === "completed") { // 更新到下一步(或完成) const steps = ["spec", "design", "simulation", "done"]; const currentIndex = steps.indexOf(step); if (currentIndex < steps.length - 1) { panel.webview.postMessage({ type: "updateProgress", step: steps[currentIndex + 1], }); } else { panel.webview.postMessage({ type: "completeProgress" }); } } }, }, mode, serviceTier // 传递服务等级 ); }); } /** * 处理用户回答(从 WebView 调用) */ export async function handleUserAnswer( askId: string, selected?: string[], customInput?: string ): Promise { if (currentSession) { await currentSession.submitAnswer(askId, selected, customInput); } } /** * 中止当前对话 */ export async function abortCurrentDialog(): Promise { if (currentSession) { // 保存当前已有的对话内容 const segments = currentSession.getSegments(); if (segments && segments.length > 0) { try { const historyManager = ChatHistoryManager.getInstance(); const textContent = segments .filter((s) => s.type === "text" && s.content) .map((s) => s.content) .join("\n"); // 添加中止标记 const abortedContent = textContent + "\n\n[对话已被用户中止]"; await historyManager.addAiMessage(abortedContent, undefined, segments); console.log("[MessageHandler] 已保存中止前的对话内容"); } catch (error) { console.warn("[MessageHandler] 保存中止对话失败:", error); } } } // 通知 WebView 重置分段消息容器 const panel = userInteractionManager.getWebviewPanel(); if (panel) { panel.webview.postMessage({ command: "resetSegmentedMessage" }); console.log("[MessageHandler] 已发送重置分段消息命令"); } dialogManager.abortCurrentSession(); currentSession = null; } /** * 获取当前会话的 taskId */ export function getCurrentTaskId(): string | null { return currentSession?.getTaskId() || lastTaskId; } /** * 设置最后的 taskId(加载历史会话时调用) */ export function setLastTaskId(taskId: string): void { lastTaskId = taskId; console.log("[MessageHandler] 设置 lastTaskId:", taskId); } /** * 处理计划操作(Plan 模式) * @param panel WebView 面板 * @param action 操作类型:confirm/modify/cancel * @param planTitle 计划标题 * @param extensionPath 扩展路径 */ export async function handlePlanAction( panel: vscode.WebviewPanel, action: string, planTitle: string, extensionPath: string, serviceTier?: ServiceTier ): Promise { console.log("[handlePlanAction] action:", action, "planTitle:", planTitle, "serviceTier:", serviceTier); switch (action) { case "confirm": // 确认执行:切换到 Agent 模式并发送执行消息 panel.webview.postMessage({ command: "switchMode", mode: "agent", }); // 发送执行消息 await handleUserMessage( panel, `请按照刚才的计划执行:${planTitle}`, extensionPath, "agent", serviceTier ); break; case "modify": // 修改计划:提示用户输入修改建议 const modification = await vscode.window.showInputBox({ prompt: "请输入您对计划的修改建议", placeHolder: "例如:第2步需要先检查文件是否存在...", ignoreFocusOut: true, }); if (modification) { await handleUserMessage( panel, `请根据以下建议修改计划:${modification}`, extensionPath, "plan", serviceTier ); } break; case "cancel": // 取消计划:通知用户 panel.webview.postMessage({ command: "addMessage", text: "计划已取消。", sender: "bot", }); break; default: console.warn("[handlePlanAction] 未知操作:", action); } } /** * 解析文件操作命令 */ function parseFileOperation(text: string): { type: "create" | "delete" | "read" | "update" | "rename" | "replace"; filePath: string; content?: string; newPath?: string; searchText?: string; replaceText?: string; } | null { const lowerText = text.toLowerCase().trim(); // 匹配创建文件:创建一个 xxx.ts 文件 const createMatch = lowerText.match(/创建(?:一个)?(.+?\.\w+)(?:文件)?/); if (createMatch) { const filePath = createMatch[1].trim(); return { type: "create", filePath: filePath, content: getDefaultContent(filePath), }; } // 匹配删除文件:删除 xxx.ts 文件 const deleteMatch = lowerText.match(/删除(.+?\.\w+)(?:文件)?/); if (deleteMatch) { const filePath = deleteMatch[1].trim(); return { type: "delete", filePath: filePath, }; } // 匹配重命名文件:将 xxx.ts 重命名为 yyy.ts 或 把 xxx.ts 改名为 yyy.ts(优先匹配,避免被修改匹配) const renameMatch = lowerText.match( /(?:将|把)\s*(.+?\.\w+)\s*(?:重命名|改名)\s*(?:为|成)\s*(.+?\.\w+)/ ); if (renameMatch) { const oldPath = renameMatch[1].trim(); const newPath = renameMatch[2].trim(); return { type: "rename", filePath: oldPath, newPath: newPath, }; } // 匹配替换内容:支持多种格式 // 格式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*["'](.+?)["']/ ); if (replaceMatch1) { const filePath = replaceMatch1[1].trim(); const searchText = replaceMatch1[2].trim(); const replaceText = replaceMatch1[3].trim(); return { type: "replace", filePath: filePath, searchText: searchText, replaceText: replaceText, }; } // 格式2: 将 xxx.ts 文件 "aaa" 替换为 "bbb" const replaceMatch2 = lowerText.match( /(?:将|把)\s*(.+?\.\w+)\s*(?:文件)?\s*["'](.+?)["']\s*替换\s*(?:为|成)\s*["'](.+?)["']/ ); if (replaceMatch2) { const filePath = replaceMatch2[1].trim(); const searchText = replaceMatch2[2].trim(); const replaceText = replaceMatch2[3].trim(); return { type: "replace", filePath: filePath, searchText: searchText, replaceText: replaceText, }; } // 匹配读取文件:读取 xxx.ts 文件 或 打开 xxx.ts const readMatch = lowerText.match(/(?:读取|打开)\s*(.+?\.\w+)\s*(?:文件)?/); if (readMatch) { const filePath = readMatch[1].trim(); return { type: "read", filePath: filePath, }; } // 匹配修改文件:修改 xxx.ts 文件(放在最后,避免误匹配) const updateMatch = lowerText.match(/修改\s*(.+?\.\w+)\s*(?:文件)?/); if (updateMatch) { const filePath = updateMatch[1].trim(); return { type: "update", filePath: filePath, }; } return null; } /** * 处理文件操作 */ async function handleFileOperation( panel: vscode.WebviewPanel, operation: { type: "create" | "delete" | "read" | "update" | "rename" | "replace"; filePath: string; content?: string; newPath?: string; searchText?: string; replaceText?: string; } ) { const historyManager = ChatHistoryManager.getInstance(); try { let responseText = ""; switch (operation.type) { case "create": await createFile(operation.filePath, operation.content || ""); responseText = `✅ 文件创建成功: ${operation.filePath}`; panel.webview.postMessage({ command: "receiveMessage", text: responseText, }); vscode.window.showInformationMessage( `文件创建成功: ${operation.filePath}` ); await historyManager.addAiMessage(responseText); break; case "delete": await deleteFile(operation.filePath); responseText = `✅ 文件删除成功: ${operation.filePath}`; panel.webview.postMessage({ command: "receiveMessage", text: responseText, }); vscode.window.showInformationMessage( `文件删除成功: ${operation.filePath}` ); await historyManager.addAiMessage(responseText); break; case "read": const content = await readFileContent(operation.filePath); panel.webview.postMessage({ command: "fileContent", content: content, filePath: operation.filePath, }); await historyManager.addAiMessage(`读取文件: ${operation.filePath}`); break; case "update": const currentContent = await readFileContent(operation.filePath); panel.webview.postMessage({ command: "editFile", content: currentContent, filePath: operation.filePath, }); await historyManager.addAiMessage(`编辑文件: ${operation.filePath}`); break; case "rename": if (!operation.newPath) { throw new Error("缺少新文件名"); } await renameFile(operation.filePath, operation.newPath); responseText = `✅ 文件重命名成功: ${operation.filePath} → ${operation.newPath}`; panel.webview.postMessage({ command: "receiveMessage", text: responseText, }); vscode.window.showInformationMessage( `文件重命名成功: ${operation.filePath} → ${operation.newPath}` ); await historyManager.addAiMessage(responseText); break; case "replace": if (!operation.searchText || !operation.replaceText) { throw new Error("缺少替换内容"); } await replaceFile( operation.filePath, operation.searchText, operation.replaceText ); responseText = `✅ 文件内容替换成功: ${operation.filePath}`; panel.webview.postMessage({ command: "receiveMessage", text: responseText, }); vscode.window.showInformationMessage( `文件内容替换成功: ${operation.filePath}` ); await historyManager.addAiMessage(responseText); break; } } catch (error) { const errorMsg = error instanceof Error ? error.message : "操作失败"; panel.webview.postMessage({ command: "receiveMessage", text: `❌ ${errorMsg}`, }); vscode.window.showErrorMessage(errorMsg); await historyManager.addAiMessage(`❌ ${errorMsg}`); } } /** * 根据文件扩展名生成默认内容 */ function getDefaultContent(filePath: string): string { const ext = filePath.split(".").pop()?.toLowerCase(); switch (ext) { case "ts": return `// ${filePath}\n\nexport {};\n`; case "js": return `// ${filePath}\n\n`; case "json": return "{\n \n}\n"; case "md": return `# ${filePath}\n\n`; case "html": return ` Document `; case "css": return `/* ${filePath} */\n\n`; case "v": case "sv": return `// ${filePath}\n\nmodule ${ filePath.split(".")[0] } (\n \n);\n\nendmodule\n`; default: return ""; } } /** * 处理文件读取请求 */ export async function handleReadFile( panel: vscode.WebviewPanel, filePath: string ) { try { const content = await readFileContent(filePath); panel.webview.postMessage({ command: "fileContent", content: content, filePath: filePath, }); } catch (error) { panel.webview.postMessage({ command: "fileError", error: error instanceof Error ? error.message : "读取文件失败", }); } } /** * 处理文件创建请求 */ export async function handleCreateFile( panel: vscode.WebviewPanel, filePath: string, content: string, overwrite: boolean = false //是否覆盖 ) { try { if (overwrite) { await createOrOverwriteFile(filePath, content); } else { await createFile(filePath, content); } panel.webview.postMessage({ command: "fileCreated", filePath: filePath, message: " 文件创建成功", }); vscode.window.showInformationMessage(`文件创建成功: ${filePath}`); } catch (error) { panel.webview.postMessage({ command: "fileCreateError", error: error instanceof Error ? error.message : "创建文件失败", }); vscode.window.showErrorMessage( `创建文件失败: ${error instanceof Error ? error.message : "未知错误"}` ); } } /** * 处理文件更新请求 */ export async function handleUpdateFile( panel: vscode.WebviewPanel, filePath: string, content: string ) { try { await updateFile(filePath, content); panel.webview.postMessage({ command: "fileUpdated", filePath: filePath, message: " 文件更新成功", }); vscode.window.showInformationMessage(`文件更新成功: ${filePath}`); } catch (error) { panel.webview.postMessage({ command: "fileUpdateError", error: error instanceof Error ? error.message : "更新文件失败", }); vscode.window.showErrorMessage( `更新文件失败: ${error instanceof Error ? error.message : "未知错误"}` ); } } /** * 处理文件重命名请求 */ export async function handleRenameFile( panel: vscode.WebviewPanel, oldPath: string, newPath: string ) { try { await renameFile(oldPath, newPath); panel.webview.postMessage({ command: "fileRenamed", oldPath: oldPath, newPath: newPath, message: "文件重命名成功", }); vscode.window.showInformationMessage( `文件重命名成功: ${oldPath} → ${newPath}` ); } catch (error) { panel.webview.postMessage({ command: "fileRenameError", error: error instanceof Error ? error.message : "重命名文件失败", }); vscode.window.showErrorMessage( `重命名文件失败: ${error instanceof Error ? error.message : "未知错误"}` ); } } /** * 处理文件内容替换请求 */ export async function handleReplaceInFile( panel: vscode.WebviewPanel, filePath: string, searchText: string, replaceText: string ) { try { await replaceFile(filePath, searchText, replaceText); panel.webview.postMessage({ command: "fileReplaced", filePath: filePath, message: "文件内容替换成功", }); vscode.window.showInformationMessage(`文件内容替换成功: ${filePath}`); } catch (error) { panel.webview.postMessage({ command: "fileReplaceError", error: error instanceof Error ? error.message : "替换文件内容失败", }); vscode.window.showErrorMessage( `替换文件内容失败: ${error instanceof Error ? error.message : "未知错误"}` ); } } /** * 将代码插入到编辑器 */ export function insertCodeToEditor(code: string) { const editor = vscode.window.activeTextEditor; if (editor) { editor.edit((editBuilder) => { editBuilder.insert(editor.selection.active, code); }); vscode.window.showInformationMessage("代码已插入"); } else { vscode.window.showWarningMessage("请先打开一个编辑器"); } } /** * 检查是否是 VCD 生成命令 */ function isVCDGenerationCommand(text: string): boolean { const lowerText = text.toLowerCase().trim(); // 匹配各种 VCD 生成命令 const vcdPatterns = [ /生成\s*vcd/, /创建\s*vcd/, /运行\s*仿真/, /执行\s*仿真/, /iverilog/, /生成\s*波形/, /仿真\s*生成/, ]; return vcdPatterns.some((pattern) => pattern.test(lowerText)); } /** * 处理 VCD 生成请求 */ async function handleVCDGeneration( panel: vscode.WebviewPanel, extensionPath: string ) { try { // 获取当前工作区路径 const workspaceFolders = vscode.workspace.workspaceFolders; if (!workspaceFolders || workspaceFolders.length === 0) { panel.webview.postMessage({ command: "receiveMessage", text: "❌ 请先打开一个工作区文件夹", }); vscode.window.showErrorMessage("请先打开一个工作区文件夹"); return; } const projectPath = workspaceFolders[0].uri.fsPath; // 发送开始消息 panel.webview.postMessage({ command: "receiveMessage", text: "🔍 正在检查项目文件...", }); // 1. 检查 iverilog 是否可用 const iverilogCheck = await checkIverilogAvailable(extensionPath); if (!iverilogCheck.available) { panel.webview.postMessage({ command: "receiveMessage", text: `❌ ${iverilogCheck.message}\n\n请参考插件文档安装 iverilog 工具。`, }); vscode.window.showErrorMessage(iverilogCheck.message); return; } // 2. 检查项目文件完整性 const projectCheck = await checkVerilogProject(projectPath); if (!projectCheck.isComplete) { let errorMsg = "❌ 项目文件不完整:\n\n"; if (projectCheck.allVerilogFiles.length === 0) { errorMsg += "• 未找到任何 Verilog 文件 (.v 或 .sv)\n"; } else { errorMsg += `• 找到 ${projectCheck.allVerilogFiles.length} 个 Verilog 文件\n`; if (!projectCheck.hasTopModule) { errorMsg += "• ❌ 缺少顶层模块文件\n"; } else { errorMsg += `• ✅ 顶层模块: ${projectCheck.topModuleFile}\n`; } if (!projectCheck.hasTestbench) { errorMsg += "• ❌ 缺少 testbench 文件\n"; errorMsg += "\n提示: testbench 文件应包含 $dumpfile 和 $dumpvars 语句来生成 VCD 文件。\n"; } else { errorMsg += `• ✅ Testbench: ${projectCheck.testbenchFile}\n`; } } if (projectCheck.errors.length > 0) { errorMsg += "\n错误信息:\n" + projectCheck.errors.join("\n"); } panel.webview.postMessage({ command: "receiveMessage", text: errorMsg, }); vscode.window.showWarningMessage("项目文件不完整,无法生成 VCD"); return; } // 3. 显示项目检查结果 panel.webview.postMessage({ command: "receiveMessage", text: `✅ 项目检查通过!\n\n找到 ${projectCheck.allVerilogFiles.length} 个 Verilog 文件\n• 顶层模块: ${projectCheck.topModuleFile}\n• Testbench: ${projectCheck.testbenchFile}\n\n🚀 开始编译和仿真...`, }); // 4. 生成 VCD 文件 const result = await generateVCD(projectPath, extensionPath); if (result.success) { let successMsg = `✅ ${result.message}`; if (result.stdout) { successMsg += `\n\n仿真输出:\n${result.stdout}`; } // 发送带波形预览的消息 if (result.vcdFilePath) { const fileName = path.basename(result.vcdFilePath); panel.webview.postMessage({ command: "vcdGenerated", text: successMsg, vcdFilePath: result.vcdFilePath, fileName: fileName, }); vscode.window.showInformationMessage(`VCD 文件生成成功: ${fileName}`); } else { panel.webview.postMessage({ command: "receiveMessage", text: successMsg, }); } } else { let errorMsg = `❌ ${result.message}`; if (result.stderr) { errorMsg += `\n\n错误输出:\n${result.stderr}`; } if (result.stdout) { errorMsg += `\n\n标准输出:\n${result.stdout}`; } panel.webview.postMessage({ command: "receiveMessage", text: errorMsg, }); vscode.window.showErrorMessage("VCD 文件生成失败"); } } catch (error) { const errorMsg = `❌ 生成 VCD 文件时出错: ${ error instanceof Error ? error.message : "未知错误" }`; panel.webview.postMessage({ command: "receiveMessage", text: errorMsg, }); vscode.window.showErrorMessage(errorMsg); } }