fix: 优化后端服务不可用时的错误处理,移除本地模拟回复逻辑

This commit is contained in:
Roe-xin
2025-12-31 18:02:38 +08:00
parent ecdbe0bdc0
commit 3d535fd3e1

View File

@ -19,7 +19,7 @@ import { dialogManager, DialogSession } from "../services/dialogService";
import { userInteractionManager } from "../services/userInteraction"; import { userInteractionManager } from "../services/userInteraction";
import { healthCheck } from "../services/apiClient"; import { healthCheck } from "../services/apiClient";
import type { RunMode } from '../types/api'; import type { RunMode } from "../types/api";
/** 是否使用后端服务(可通过配置控制) */ /** 是否使用后端服务(可通过配置控制) */
let useBackendService = true; let useBackendService = true;
@ -32,7 +32,7 @@ let pendingPlanExecution: {
panel: vscode.WebviewPanel; panel: vscode.WebviewPanel;
planTitle: string; planTitle: string;
extensionPath: string; extensionPath: string;
taskId: string; // 保存 taskId 以便复用 taskId: string; // 保存 taskId 以便复用
} | null = null; } | null = null;
/** /**
@ -45,7 +45,7 @@ export function setPendingPlanExecution(
taskId: string taskId: string
): void { ): void {
pendingPlanExecution = { panel, planTitle, extensionPath, taskId }; 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); await handleUserMessageWithBackend(panel, text, extensionPath, mode);
return; return;
} catch (error) { } catch (error) {
console.error("后端服务不可用,回退到本地模式:", error); console.error("后端服务不可用:", error);
// 后端不可用时,使用本地模拟回复 panel.webview.postMessage({
command: "updateStatus",
text: "后端服务不可用",
type: "error",
});
throw error;
} }
} }
// 本地模拟回复(后端不可用时的 fallback // 如果没有 extensionPath显示错误
console.log("使用本地模拟回复"); panel.webview.postMessage({
const reply = getMockReply(text); command: "updateStatus",
text: "无法处理消息:缺少必要参数",
// 记录AI回复到历史允许失败 type: "error",
try { });
const historyManager = ChatHistoryManager.getInstance();
await historyManager.addAiMessage(reply);
} catch (error) {
console.warn("记录AI回复历史失败:", error);
}
setTimeout(() => {
panel.webview.postMessage({
command: "receiveMessage",
text: reply,
});
}, 500);
} }
/** /**
@ -123,13 +116,13 @@ async function handleUserMessageWithBackend(
text: string, text: string,
extensionPath: string, extensionPath: string,
mode?: RunMode, mode?: RunMode,
reuseTaskId?: string // 可选,复用现有 taskId用于 Plan 模式确认后继续执行) reuseTaskId?: string // 可选,复用现有 taskId用于 Plan 模式确认后继续执行)
): Promise<void> { ): Promise<void> {
// 创建或复用会话 // 创建或复用会话
if (!currentSession || !currentSession.active) { if (!currentSession || !currentSession.active) {
currentSession = dialogManager.createSession(extensionPath, reuseTaskId); currentSession = dialogManager.createSession(extensionPath, reuseTaskId);
if (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) => { return new Promise((resolve, reject) => {
currentSession!.sendMessage(text, { currentSession!.sendMessage(
onText: (fullText, isStreaming) => { text,
// 不再单独处理文本,统一通过 onSegmentUpdate 处理 {
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);
},
}, },
mode
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);
}); });
} }
@ -298,52 +308,52 @@ export async function handlePlanAction(
planTitle: string, planTitle: string,
extensionPath: string extensionPath: string
): Promise<void> { ): Promise<void> {
console.log('[handlePlanAction] action:', action, 'planTitle:', planTitle); console.log("[handlePlanAction] action:", action, "planTitle:", planTitle);
switch (action) { switch (action) {
case 'confirm': case "confirm":
// 确认执行:切换到 Agent 模式并发送执行消息 // 确认执行:切换到 Agent 模式并发送执行消息
panel.webview.postMessage({ panel.webview.postMessage({
command: 'switchMode', command: "switchMode",
mode: 'agent' mode: "agent",
}); });
// 发送执行消息 // 发送执行消息
await handleUserMessage( await handleUserMessage(
panel, panel,
`请按照刚才的计划执行:${planTitle}`, `请按照刚才的计划执行:${planTitle}`,
extensionPath, extensionPath,
'agent' "agent"
); );
break; break;
case 'modify': case "modify":
// 修改计划:提示用户输入修改建议 // 修改计划:提示用户输入修改建议
const modification = await vscode.window.showInputBox({ const modification = await vscode.window.showInputBox({
prompt: '请输入您对计划的修改建议', prompt: "请输入您对计划的修改建议",
placeHolder: '例如第2步需要先检查文件是否存在...', placeHolder: "例如第2步需要先检查文件是否存在...",
ignoreFocusOut: true ignoreFocusOut: true,
}); });
if (modification) { if (modification) {
await handleUserMessage( await handleUserMessage(
panel, panel,
`请根据以下建议修改计划:${modification}`, `请根据以下建议修改计划:${modification}`,
extensionPath, extensionPath,
'plan' "plan"
); );
} }
break; break;
case 'cancel': case "cancel":
// 取消计划:通知用户 // 取消计划:通知用户
panel.webview.postMessage({ panel.webview.postMessage({
command: 'addMessage', command: "addMessage",
text: '计划已取消。', text: "计划已取消。",
sender: 'bot' sender: "bot",
}); });
break; break;
default: 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优先匹配避免被修改匹配 // 匹配重命名文件:将 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) { if (renameMatch) {
const oldPath = renameMatch[1].trim(); const oldPath = renameMatch[1].trim();
const newPath = renameMatch[2].trim(); const newPath = renameMatch[2].trim();
@ -397,7 +409,9 @@ function parseFileOperation(text: string): {
// 格式1: 在 xxx.ts 中将 "aaa" 替换为 "bbb" // 格式1: 在 xxx.ts 中将 "aaa" 替换为 "bbb"
// 格式2: 将 xxx.ts 文件 "aaa" 替换为 "bbb" // 格式2: 将 xxx.ts 文件 "aaa" 替换为 "bbb"
// 格式3: 将 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) { if (replaceMatch1) {
const filePath = replaceMatch1[1].trim(); const filePath = replaceMatch1[1].trim();
const searchText = replaceMatch1[2].trim(); const searchText = replaceMatch1[2].trim();
@ -411,7 +425,9 @@ function parseFileOperation(text: string): {
} }
// 格式2: 将 xxx.ts 文件 "aaa" 替换为 "bbb" // 格式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) { if (replaceMatch2) {
const filePath = replaceMatch2[1].trim(); const filePath = replaceMatch2[1].trim();
const searchText = replaceMatch2[2].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) { if (!projectCheck.hasTestbench) {
errorMsg += "• ❌ 缺少 testbench 文件\n"; errorMsg += "• ❌ 缺少 testbench 文件\n";
errorMsg += "\n提示: testbench 文件应包含 $dumpfile 和 $dumpvars 语句来生成 VCD 文件。\n"; errorMsg +=
"\n提示: testbench 文件应包含 $dumpfile 和 $dumpvars 语句来生成 VCD 文件。\n";
} else { } else {
errorMsg += `• ✅ Testbench: ${projectCheck.testbenchFile}\n`; errorMsg += `• ✅ Testbench: ${projectCheck.testbenchFile}\n`;
} }
@ -910,9 +892,7 @@ async function handleVCDGeneration(
fileName: fileName, fileName: fileName,
}); });
vscode.window.showInformationMessage( vscode.window.showInformationMessage(`VCD 文件生成成功: ${fileName}`);
`VCD 文件生成成功: ${fileName}`
);
} else { } else {
panel.webview.postMessage({ panel.webview.postMessage({
command: "receiveMessage", command: "receiveMessage",