refactor: 优化 ICHelperPanel 组件结构

- 将 1346 行的单文件拆分为 7 个职责单一的模块
   - authHelper: 认证和登录检查
   - userInfoHelper: 用户信息管理
   - conversationHelper: 会话历史加载
   - vcdHelper: VCD 文件处理
   - contextHelper: 上下文管理
   - fileHelper: 文件操作
   - messageRouter: 消息路由分发
   - 主组件精简至 157 行,提升可维护性
This commit is contained in:
Roe-xin
2026-03-12 11:58:43 +08:00
parent 2f6eae9f2b
commit 2a280aaa93
8 changed files with 1218 additions and 1270 deletions

View File

@ -0,0 +1,396 @@
/**
* 消息路由处理模块
* 功能:处理 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 { 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,
} 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 "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;
}
}