feat: 集成后端通信和前端交互功能

- 重构消息处理器(src/utils/messageHandler.ts)
  - 集成 DialogService 实现后端对话管理
  - 添加流式消息处理和 SSE 事件监听
  - 实现工具执行状态的实时更新
  - 支持用户问题的交互处理
  - 添加对话中止和错误处理机制

- 更新 ICHelperPanel(src/panels/ICHelperPanel.ts)
  - 添加 submitAnswer 消息处理,支持用户答案提交
  - 添加 abortDialog 消息处理,支持对话中止
  - 与后端服务进行双向通信

- 更新 ICViewProvider(src/views/ICViewProvider.ts)
  - 同步更新消息处理逻辑
  - 添加 extensionPath 参数传递
  - 支持新的消息类型和事件处理

完成前后端通信的完整集成,实现:
- AI 对话的流式响应
- 工具调用的实时反馈
- 用户交互的双向通信
- 错误处理和状态管理
This commit is contained in:
XiaoFeng
2025-12-16 19:09:46 +08:00
parent c61e29a41f
commit 7c1f1fae07
3 changed files with 196 additions and 19 deletions

View File

@ -6,7 +6,9 @@ import {
handleReadFile, handleReadFile,
handleUpdateFile, handleUpdateFile,
handleRenameFile, handleRenameFile,
handleReplaceInFile handleReplaceInFile,
handleUserAnswer,
abortCurrentDialog
} from "../utils/messageHandler"; } from "../utils/messageHandler";
/** /**
@ -61,6 +63,14 @@ export function showICHelperPanel(context: vscode.ExtensionContext) {
case "showInfo": case "showInfo":
vscode.window.showInformationMessage(message.text); vscode.window.showInformationMessage(message.text);
break; break;
// 新增:处理用户回答
case "submitAnswer":
handleUserAnswer(message.askId, message.selected, message.customInput);
break;
// 新增:中止对话
case "abortDialog":
abortCurrentDialog();
break;
} }
}, },
undefined, undefined,

View File

@ -14,6 +14,15 @@ import {
checkIverilogAvailable, checkIverilogAvailable,
} from "./iverilogRunner"; } from "./iverilogRunner";
import { ChatHistoryManager } from "./chatHistoryManager"; 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); console.log("收到用户消息:", text);
// 记录用户消息到历史 // 记录用户消息到历史(允许失败,不阻塞主流程)
const historyManager = ChatHistoryManager.getInstance(); try {
await historyManager.addUserMessage(text); const historyManager = ChatHistoryManager.getInstance();
await historyManager.addUserMessage(text);
} catch (error) {
console.warn("记录消息历史失败(可能没有打开工作区):", error);
}
// 检查是否是 VCD 生成命令 // 设置 WebView 面板用于用户交互
userInteractionManager.setWebviewPanel(panel);
// 检查是否是 VCD 生成命令(本地处理)
if (isVCDGenerationCommand(text)) { if (isVCDGenerationCommand(text)) {
await handleVCDGeneration(panel, extensionPath || ""); await handleVCDGeneration(panel, extensionPath || "");
return; return;
} }
// 检查是否是文件操作命令 // 检查是否是文件操作命令(本地处理)
const fileOperation = parseFileOperation(text); const fileOperation = parseFileOperation(text);
console.log("解析结果:", fileOperation);
if (fileOperation) { if (fileOperation) {
console.log("执行文件操作:", fileOperation.type, fileOperation.filePath); console.log("执行文件操作:", fileOperation.type, fileOperation.filePath);
await handleFileOperation(panel, fileOperation); await handleFileOperation(panel, fileOperation);
return; 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); const reply = getMockReply(text);
// 记录助手回复到历史 // 记录AI回复到历史(允许失败)
await historyManager.addAiMessage(reply); try {
const historyManager = ChatHistoryManager.getInstance();
await historyManager.addAiMessage(reply);
} catch (error) {
console.warn("记录AI回复历史失败:", error);
}
setTimeout(() => { setTimeout(() => {
panel.webview.postMessage({ panel.webview.postMessage({
@ -61,6 +90,122 @@ export async function handleUserMessage(
}, 500); }, 500);
} }
/**
* 使用后端服务处理用户消息
*/
async function handleUserMessageWithBackend(
panel: vscode.WebviewPanel,
text: string,
extensionPath: string
): Promise<void> {
// 创建或复用会话
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<void> {
if (currentSession) {
await currentSession.submitAnswer(askId, selected, customInput);
}
}
/**
* 中止当前对话
*/
export function abortCurrentDialog(): void {
dialogManager.abortCurrentSession();
currentSession = null;
}
/** /**
* 解析文件操作命令 * 解析文件操作命令
*/ */

View File

@ -5,12 +5,17 @@ import {
insertCodeToEditor, insertCodeToEditor,
handleReadFile, handleReadFile,
handleCreateFile, handleCreateFile,
handleUpdateFile,
handleRenameFile,
handleReplaceInFile,
handleUserAnswer,
abortCurrentDialog,
} from "../utils/messageHandler"; } from "../utils/messageHandler";
/** /**
* 创建并显示IC 侧边栏视图 * 创建并显示IC 侧边栏视图
*/ */
export function showICHelperPanel(content: vscode.ExtensionContext) { export function showICHelperPanel(context: vscode.ExtensionContext) {
// 创建WebView面板 // 创建WebView面板
const panel = vscode.window.createWebviewPanel( const panel = vscode.window.createWebviewPanel(
"icCoder", // 面板ID "icCoder", // 面板ID
@ -19,20 +24,20 @@ export function showICHelperPanel(content: vscode.ExtensionContext) {
{ {
enableScripts: true, enableScripts: true,
retainContextWhenHidden: true, retainContextWhenHidden: true,
localResourceRoots: [vscode.Uri.joinPath(content.extensionUri, "media")], localResourceRoots: [vscode.Uri.joinPath(context.extensionUri, "media")],
} }
); );
// 设置标签页图标 // 设置标签页图标
panel.iconPath = vscode.Uri.joinPath( panel.iconPath = vscode.Uri.joinPath(
content.extensionUri, context.extensionUri,
"media", "media",
"图案(方底).png" "图案(方底).png"
); );
// 获取页面内图标URI // 获取页面内图标URI
const iconUri = panel.webview.asWebviewUri( const iconUri = panel.webview.asWebviewUri(
vscode.Uri.joinPath(content.extensionUri, "media", "图案(方底).png") vscode.Uri.joinPath(context.extensionUri, "media", "图案(方底).png")
); );
// 设置HTML内容 // 设置HTML内容
panel.webview.html = getWebviewContent(iconUri.toString()); panel.webview.html = getWebviewContent(iconUri.toString());
@ -42,11 +47,20 @@ export function showICHelperPanel(content: vscode.ExtensionContext) {
(message) => { (message) => {
switch (message.command) { switch (message.command) {
case "sendMessage": case "sendMessage":
handleUserMessage(panel, message.text); handleUserMessage(panel, message.text, context.extensionPath);
break; break;
case "readFile": case "readFile":
handleReadFile(panel, message.filePath); handleReadFile(panel, message.filePath);
break; 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": case "insertCode":
insertCodeToEditor(message.code); insertCodeToEditor(message.code);
break; break;
@ -61,10 +75,18 @@ export function showICHelperPanel(content: vscode.ExtensionContext) {
case "showInfo": case "showInfo":
vscode.window.showInformationMessage(message.text); vscode.window.showInformationMessage(message.text);
break; break;
// 新增:处理用户回答
case "submitAnswer":
handleUserAnswer(message.askId, message.selected, message.customInput);
break;
// 新增:中止对话
case "abortDialog":
abortCurrentDialog();
break;
} }
}, },
undefined, undefined,
content.subscriptions context.subscriptions
); );
} }