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:
@ -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,
|
||||||
|
|||||||
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 解析文件操作命令
|
* 解析文件操作命令
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -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
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user