/** * 消息路由处理模块 * 功能:处理 webview 消息的路由分发 * 依赖:各个 helper 模块和 messageHandler * 使用场景:webview 消息接收时 */ import * as vscode from "vscode"; import { handleUserMessage, insertCodeToEditor, handleReadFile, handleUpdateFile, handleRenameFile, handleReplaceInFile, handleUserAnswer, abortCurrentDialog, handleOptimizePrompt, handlePlanAction, getCurrentTaskId, handleAcceptChange, handleRejectChange, handleOpenFileDiff, startChangeSession, } from "../../utils/messageHandler"; import { loadPersonalRules, savePersonalRule, updatePersonalRule, deletePersonalRule, updatePersonalRulesEnabled, } from "../../utils/personalRulesManager"; import { compactDialog } from "../../services/apiClient"; import { ChatHistoryManager } from "../../utils/chatHistoryManager"; import { getCachedUserInfo } from "../../services/userService"; import { loadConversationHistory, selectConversation, } from "./conversationHelper"; import { getVCDFileInfo } from "./vcdHelper"; import { handleAddContextFile, handleAddContextFolder, handleAddContextImage, handleAddContextDocument, handleAddContextDocumentSet, handleGetDocumentSetList, } from "./contextHelper"; import { openFile, openFileWithSelection, openFilePathTag } from "./fileHelper"; export async function handleWebviewMessage( message: any, panel: vscode.WebviewPanel, context: vscode.ExtensionContext, ) { const historyManager = ChatHistoryManager.getInstance(); const panelId = (panel as any).__uniqueId; switch (message.command) { case "sendMessage": if (!historyManager.getPanelTask(panelId)) { const workspacePath = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath; if (workspacePath) { try { const taskMeta = await historyManager.createTask( workspacePath, "新对话", ); historyManager.setPanelTask( panelId, taskMeta.taskId, workspacePath, ); } catch (error) { console.error("创建任务失败:", error); } } } historyManager.switchToPanelTask(panelId); const sessionId = `session_${panelId}_${Date.now()}`; startChangeSession(sessionId); panel.webview.postMessage({ type: "showProgress" }); handleUserMessage( panel, message.text, context.extensionPath, message.mode, message.model, message.contextItems, ); break; case "readFile": handleReadFile(panel, message.filePath); 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": insertCodeToEditor(message.code); break; case "showInfo": vscode.window.showInformationMessage(message.text); break; case "openWaveformViewer": if (message.vcdFilePath) { vscode.commands.executeCommand( "ic-coder.openVCDViewer", message.vcdFilePath, ); } break; case "getVCDInfo": if (message.vcdFilePath && message.containerId) { getVCDFileInfo(panel, message.vcdFilePath, message.containerId); } break; case "createNewConversation": const { showICHelperPanel } = require("../ICHelperPanel"); showICHelperPanel(context, panel.viewColumn); break; case "loadConversationHistory": loadConversationHistory(panel, message.offset || 0, message.limit || 10); break; case "selectConversation": if (message.conversationId) { selectConversation( panel, message.conversationId, context.extensionPath, ); } break; case "submitAnswer": void handleUserAnswer( message.askId, message.selected, message.customInput, message.answers, ); break; case "abortDialog": void abortCurrentDialog(); break; case "compressConversation": { const taskId = getCurrentTaskId(); if (taskId) { compactDialog(taskId) .then((result) => { panel.webview.postMessage({ command: "receiveMessage", text: result.success ? "✅ 会话压缩完成" : `❌ 压缩失败: ${result.error || "未知错误"}`, }); }) .catch((err) => { panel.webview.postMessage({ command: "receiveMessage", text: `❌ 压缩失败: ${err.message || "网络错误"}`, }); }); } else { panel.webview.postMessage({ command: "receiveMessage", text: "❌ 没有活跃的会话", }); } } break; case "optimizePrompt": if (typeof message.prompt === "string") { void handleOptimizePrompt(panel, message.prompt); } else { panel.webview.postMessage({ command: "optimizeResult", success: false, error: "提示词为空或格式错误", }); } break; case "logout": vscode.commands.executeCommand("ic-coder.logout"); break; case "openFile": if (message.filePath) { await openFile(message.filePath); } break; case "openFileWithSelection": if (message.filePath) { await openFileWithSelection( message.filePath, message.startLine, message.endLine, ); } break; case "openFilePathTag": if (message.filePath) { await openFilePathTag( message.filePath, message.startLine, message.endLine, ); } break; case "acceptChange": if (message.changeId) { await handleAcceptChange(panel, message.changeId); } break; case "rejectChange": if (message.changeId) { await handleRejectChange(panel, message.changeId); } break; case "openFileDiff": if (message.changeId) { await handleOpenFileDiff(panel, message.changeId); } break; case "checkInvitationCode": { const userInfo = getCachedUserInfo(); if (userInfo?.isPluginTrial === true) { panel.webview.postMessage({ command: "invitationCodeStatus", verified: true, }); } else { const { InvitationService, } = require("../../services/invitationService"); const isVerified = await InvitationService.isVerified(context); panel.webview.postMessage({ command: "invitationCodeStatus", verified: isVerified, }); } } break; case "checkWelcomeModal": { const userInfo = getCachedUserInfo(); if (userInfo?.isPluginTrial === true) { if (userInfo.pluginTrialExpiresAt === undefined) { break; } if (userInfo.pluginTrialExpiresAt !== null) { const now = Date.now(); const isExpired = now >= userInfo.pluginTrialExpiresAt; if (isExpired) { break; } } panel.webview.postMessage({ command: "showWelcomeModal" }); } } break; case "checkTrialExpiration": { const { TrialExpirationService, } = require("../../services/trialExpirationService"); const trialService = new TrialExpirationService(context, panel); await trialService.checkExpiration(); } break; case "verifyInvitationCode": { const { InvitationService, } = require("../../services/invitationService"); const result = await InvitationService.verifyCode(message.code); if (result.success) { await InvitationService.saveVerificationStatus(context, message.code); panel.webview.postMessage({ command: "invitationCodeVerified", success: true, }); setTimeout(() => { panel.webview.postMessage({ command: "showNdtWelcomeModal" }); }, 300); } else { panel.webview.postMessage({ command: "invitationCodeVerified", success: false, message: result.message, }); } } break; case "openICCoder": vscode.env.openExternal(vscode.Uri.parse("https://www.iccoder.com")); break; case "openTutorial": vscode.env.openExternal( vscode.Uri.parse( "https://www.iccoder.com/guides/quick-start/first-task-plugin", ), ); break; case "openUserManual": vscode.env.openExternal(vscode.Uri.parse("https://www.iccoder.com")); break; case "openUserFeedback": panel.webview.postMessage({ command: "showFeedbackQRCode" }); break; case "planAction": if (message.action === "confirm") { panel.webview.postMessage({ command: "switchMode", mode: "agent" }); } else if (message.action === "modify" || message.action === "cancel") { void handlePlanAction( panel, message.action, message.planTitle || "", context.extensionPath, message.model, ); } break; case "addContextFile": await handleAddContextFile(panel); break; case "addContextFolder": await handleAddContextFolder(panel); break; case "addContextDocumentSet": await handleAddContextDocumentSet(panel); break; case "getDocumentSetList": await handleGetDocumentSetList(panel); break; case "saveDocumentSet": const { saveDocumentSet } = await import("./contextHelper"); saveDocumentSet(message.documents || [], message.name || "", panel); break; case "deleteDocumentSet": const { deleteDocumentSet } = await import("./contextHelper"); deleteDocumentSet(message.id, panel); break; case "changeDocumentSetName": const { changeDocumentSetName } = await import("./contextHelper"); changeDocumentSetName(message.id, message.newName, panel); break; case "openContextSettings": panel.webview.postMessage({ command: "openSettingsTab", tab: "context", }); break; case "selectFilesForDocset": const uris = await vscode.window.showOpenDialog({ canSelectMany: true, canSelectFiles: true, canSelectFolders: false, openLabel: "选择文件", filters: { 支持的文件: ["md", "txt", "v", "sv", "pdf"], }, }); if (uris && uris.length > 0) { const fs = require("fs"); const path = require("path"); const selectedFiles: Array<{ name: string; absolutePath: string; size: number; }> = []; const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10 MB const MAX_TOTAL_SIZE = 50 * 1024 * 1024; // 50 MB const MAX_FILES = 1000; let totalSize = 0; const errors: string[] = []; for (const uri of uris) { const filePath = uri.fsPath; const ext = path.extname(filePath).toLowerCase(); if (![".md", ".txt", ".v", ".sv", ".pdf"].includes(ext)) { errors.push(`文件 ${path.basename(filePath)} 格式不支持`); continue; } try { const stat = fs.statSync(filePath); if (stat.size > MAX_FILE_SIZE) { errors.push(`文件 ${path.basename(filePath)} 超过 10 MB`); continue; } if (totalSize + stat.size > MAX_TOTAL_SIZE) { errors.push("文档集总大小超过 50 MB"); break; } if (selectedFiles.length >= MAX_FILES) { errors.push("文件数量超过 1000 个"); break; } selectedFiles.push({ name: path.basename(filePath), absolutePath: filePath, size: stat.size, }); totalSize += stat.size; } catch (err) { errors.push(`无法读取文件 ${path.basename(filePath)}`); } } panel.webview.postMessage({ command: "filesSelectedForDocset", files: selectedFiles, errors: errors.length > 0 ? errors : undefined, }); } break; case "addContextImage": await handleAddContextImage(panel); break; case "addContextDocument": await handleAddContextDocument(panel); break; case "checkWorkspace": const hasWorkspace = !!( vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders.length > 0 ); if (!hasWorkspace) { vscode.window .showWarningMessage( "请先打开一个文件夹作为工作区,这样我就能更好地为您服务了 😊", "打开文件夹", ) .then((selection) => { if (selection === "打开文件夹") { vscode.commands.executeCommand("vscode.openFolder"); } }); } panel.webview.postMessage({ command: "workspaceStatus", hasWorkspace: hasWorkspace, }); break; case "openExternalUrl": if (message.url) { vscode.env.openExternal(vscode.Uri.parse(message.url)); } break; case "loadPersonalRules": { const data = loadPersonalRules(); panel.webview.postMessage({ command: "personalRulesLoaded", data: data, }); } break; case "savePersonalRule": { const success = await savePersonalRule( message.name, message.content, message.enabled, ); if (success) { const data = loadPersonalRules(); panel.webview.postMessage({ command: "personalRulesLoaded", data: data, }); } } break; case "updatePersonalRule": { const success = await updatePersonalRule( message.filename, message.name, message.content, message.enabled, ); if (success) { const data = loadPersonalRules(); panel.webview.postMessage({ command: "personalRulesLoaded", data: data, }); } } break; case "deletePersonalRule": { const success = await deletePersonalRule(message.filename); if (success) { const data = loadPersonalRules(); panel.webview.postMessage({ command: "personalRulesLoaded", data: data, }); } } break; case "updatePersonalRulesEnabled": { const success = await updatePersonalRulesEnabled(message.enabled); if (success) { const data = loadPersonalRules(); panel.webview.postMessage({ command: "personalRulesLoaded", data: data, }); } } break; case "loadDocumentSets": const { getDocumentSets } = await import("./contextHelper"); panel.webview.postMessage({ command: "documentSetSaved", documentSets: getDocumentSets(), }); break; } }