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 { 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<void> {
// 创建或复用会话
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<void> {
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",