diff --git a/docs/数据流程详解.md b/docs/数据流程详解.md new file mode 100644 index 0000000..2fd079c --- /dev/null +++ b/docs/数据流程详解.md @@ -0,0 +1,1027 @@ +# IC Coder Plugin 数据流程详解 + +## 概述 + +本文档详细说明从用户输入文本到最终显示响应的完整数据流程。 + +--- + +## 📊 完整数据流程图 + +``` +用户输入文本 + ↓ +前端 WebView (inputArea.ts) + ↓ vscode.postMessage +VS Code Extension (ICHelperPanel.ts) + ↓ onDidReceiveMessage +消息处理器 (messageHandler.ts) + ↓ handleUserMessage +后端服务 (dialogService.ts) + ↓ 回调函数 +前端 WebView (webviewContent.ts) + ↓ window.addEventListener +消息区域渲染 (messageArea.ts) + ↓ +显示给用户 +``` + +--- + +## 第一阶段:用户输入 → 前端处理 + +### 1. 用户在输入框输入文本并点击发送 + +**文件位置**: `src/views/inputArea.ts:415-422` + +```javascript +// 用户点击发送按钮或按下 Enter 键 +function sendMessage() { + const text = messageInput.value.trim(); + if (!text) return; + + // 获取当前配置 + const mode = getCurrentMode(); // 运行模式 (agent/plan等) + const model = getCurrentModel(); // 选择的模型 + const planMode = document.getElementById('planToggle')?.checked || false; + const contextItems = window.getContextItems ? window.getContextItems() : []; + + // 1. 立即显示用户消息 + addMessage(text, 'user'); + + // 2. 更新 UI 状态 + hasMessages = true; + updateInputAreaLayout(); + setSendButtonState(true); // 切换为暂停按钮 + + // 3. 发送消息到 VS Code 扩展 + vscode.postMessage({ + command: 'sendMessage', + text: text, + mode: mode, + model: model, + planMode: planMode, + contextItems: contextItems + }); + + // 4. 清空输入框 + messageInput.value = ''; + autoResizeTextarea(); + messageInput.focus(); + + // 5. 重置优化状态 + resetOptimizeButton(); +} +``` + +**前端同时做的事情:** +- ✅ 显示用户消息到聊天区域 +- ✅ 切换发送按钮为暂停状态 +- ✅ 清空输入框并重置高度 +- ✅ 更新布局(如果是第一条消息) + +--- + +## 第二阶段:VS Code 扩展接收消息 + +### 2. ICHelperPanel 接收并处理消息 + +**文件位置**: `src/panels/ICHelperPanel.ts:112-152` + +```javascript +// 监听来自 WebView 的消息 +panel.webview.onDidReceiveMessage(async (message) => { + const historyManager = ChatHistoryManager.getInstance(); + const panelId = (panel as any).__uniqueId; + + switch (message.command) { + case "sendMessage": + // 步骤 1: 确保面板有任务上下文 (taskId) + 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); + } + } + } + + // 步骤 2: 切换到当前面板的任务上下文 + historyManager.switchToPanelTask(panelId); + + // 步骤 3: 显示进度条 + panel.webview.postMessage({ type: 'showProgress' }); + + // 步骤 4: 调用消息处理器 + handleUserMessage( + panel, + message.text, + context.extensionPath, + message.mode + ); + break; + } +}); +``` + +**关键概念:** +- **panelId**: 每个 WebView 面板的唯一标识 +- **taskId**: 每个对话任务的唯一标识,用于历史记录管理 +- **workspacePath**: 当前工作区路径 + +--- + +## 第三阶段:消息处理器处理 + +### 3. handleUserMessage 处理用户消息 + +**文件位置**: `src/utils/messageHandler.ts:56-117` + +```javascript +export async function handleUserMessage( + panel: vscode.WebviewPanel, + text: string, + extensionPath?: string, + mode?: RunMode +) { + console.log("收到用户消息:", text); + + // 步骤 1: 记录用户消息到历史(允许失败,不阻塞主流程) + try { + const historyManager = ChatHistoryManager.getInstance(); + await historyManager.addUserMessage(text); + } catch (error) { + console.warn("记录消息历史失败(可能没有打开工作区):", error); + } + + // 步骤 2: 设置 WebView 面板用于用户交互 + userInteractionManager.setWebviewPanel(panel); + + // 步骤 3: 检查是否是 VCD 生成命令(本地处理) + if (isVCDGenerationCommand(text)) { + await handleVCDGeneration(panel, extensionPath || ""); + return; + } + + // 步骤 4: 检查是否是文件操作命令(本地处理) + const fileOperation = parseFileOperation(text); + if (fileOperation) { + console.log("执行文件操作:", fileOperation.type, fileOperation.filePath); + await handleFileOperation(panel, fileOperation); + return; + } + + // 步骤 5: 使用后端服务处理 + if (useBackendService && extensionPath) { + try { + await handleUserMessageWithBackend(panel, text, extensionPath, mode); + return; + } catch (error) { + console.error("后端服务不可用:", error); + panel.webview.postMessage({ + command: "updateStatus", + text: "后端服务不可用", + type: "error", + }); + // 恢复输入状态 + panel.webview.postMessage({ + command: "updateSegments", + segments: [], + isComplete: true, + }); + throw error; + } + } +} +``` + +**处理流程:** +1. 记录用户消息到历史数据库 +2. 设置用户交互管理器 +3. 检查特殊命令(VCD生成、文件操作) +4. 调用后端服务处理 + +--- + +## 第四阶段:后端服务处理 + +### 4. handleUserMessageWithBackend 调用后端 + +**文件位置**: `src/utils/messageHandler.ts:122-149` + +```javascript +async function handleUserMessageWithBackend( + panel: vscode.WebviewPanel, + text: string, + extensionPath: string, + mode?: RunMode, + reuseTaskId?: string // 可选,复用现有 taskId +): Promise { + const historyManager = ChatHistoryManager.getInstance(); + + // 步骤 1: 获取或创建会话 + const taskIdToUse = reuseTaskId || historyManager.getCurrentTaskId(); + + if (!currentSession || !currentSession.active) { + currentSession = dialogManager.createSession( + extensionPath, + taskIdToUse || undefined + ); + // 保存 taskId 用于后续操作(如压缩) + lastTaskId = currentSession.getTaskId(); + console.log( + "[MessageHandler] 创建会话: taskId=", + lastTaskId, + "来源=", + taskIdToUse ? "historyManager" : "新生成" + ); + } + + // 步骤 2: 显示状态栏 + panel.webview.postMessage({ + command: "updateStatus", + text: "思考中...", + type: "thinking", + }); + + // 步骤 3: 发送消息到后端,并注册回调函数 + return new Promise((resolve, reject) => { + currentSession!.sendMessage( + text, + { + // 回调 1: 文本更新(已废弃,统一通过 onSegmentUpdate 处理) + onText: (fullText, isStreaming) => { + // 不再单独处理文本 + }, + + // 回调 2: 段落更新(核心回调) + onSegmentUpdate: (segments) => { + // 实时发送段落更新到前端 + panel.webview.postMessage({ + command: "updateSegments", + segments: segments, + }); + }, + + // 回调 3: 工具开始执行 + onToolStart: (toolName) => { + panel.webview.postMessage({ + command: "updateStatus", + text: `正在执行 ${toolName}...`, + type: "working", + }); + }, + + // 回调 4: 工具执行完成 + onToolComplete: (toolName, result) => { + // 通过 onSegmentUpdate 统一更新 + }, + + // 回调 5: 工具执行错误 + onToolError: (toolName, error) => { + // 通过 onSegmentUpdate 统一更新 + }, + + // 回调 6: 用户问题 + onQuestion: (askId, question, options) => { + panel.webview.postMessage({ + command: "updateStatus", + text: "等待用户回答...", + type: "working", + }); + }, + + // 回调 7: 对话完成 + onComplete: async (segments) => { + // 隐藏状态栏 + panel.webview.postMessage({ + command: "hideStatus", + }); + + // 最后一次发送完整的段落 + panel.webview.postMessage({ + command: "updateSegments", + segments: segments, + isComplete: true, // 标记对话完成 + }); + + // 保存完整的 segments 到历史记录 + const textContent = segments + .filter((s) => s.type === "text" && s.content) + .map((s) => s.content) + .join("\n"); + await historyManager.addAiMessage(textContent, undefined, segments); + + resolve(); + }, + + // 回调 8: 错误处理 + onError: (error) => { + panel.webview.postMessage({ + command: "updateStatus", + text: `错误: ${error.message}`, + type: "error", + }); + reject(error); + }, + }, + mode + ); + }); +} +``` + +**关键回调函数:** +- `onSegmentUpdate`: 实时更新段落(核心) +- `onToolStart/Complete/Error`: 工具执行状态 +- `onQuestion`: 用户问题 +- `onComplete`: 对话完成 +- `onError`: 错误处理 + +--- + +## 第五阶段:前端接收并处理响应 + +### 5. WebView 监听消息 + +**文件位置**: `src/views/webviewContent.ts:523-546` + +```javascript +// 监听来自插件的消息 +window.addEventListener('message', event => { + const message = event.data; + console.log('[WebView] 收到消息:', message.command, message); + + switch (message.command) { + case 'updateSegments': + // 实时更新分段消息(核心处理) + console.log('[WebView] 实时更新段落, segments:', message.segments); + updateSegmentsRealtime(message.segments, message.isComplete); + + // 如果对话完成,恢复发送按钮状态 + if (message.isComplete && typeof setSendButtonState === 'function') { + setSendButtonState(false); + } + break; + + case 'updateStatus': + // 更新状态栏 + const statusBar = document.getElementById('statusBar'); + const statusText = document.getElementById('statusText'); + if (statusBar && statusText) { + statusBar.style.display = 'flex'; + statusText.textContent = message.text; + statusBar.className = 'status-bar ' + (message.type || ''); + } + break; + + case 'hideStatus': + // 隐藏状态栏 + const statusBarHide = document.getElementById('statusBar'); + if (statusBarHide) { + statusBarHide.style.display = 'none'; + } + break; + } +}); +``` + +**消息类型:** +- `updateSegments`: 更新段落(核心) +- `updateStatus`: 更新状态栏 +- `hideStatus`: 隐藏状态栏 + +--- + +## 第六阶段:消息区域渲染 + +### 6. updateSegmentsRealtime 实时渲染段落 + +**文件位置**: `src/views/messageArea.ts:903-1171` + +这是整个数据流程中最复杂的部分,负责将后端返回的 segments 数据渲染成用户可见的 UI。 + +```javascript +function updateSegmentsRealtime(segments, isComplete) { + console.log('[WebView] updateSegmentsRealtime 被调用, segments:', segments, 'isComplete:', isComplete); + + if (!segments || segments.length === 0) { + return; + } + + // 步骤 1: 创建或复用分段消息容器 + if (!currentSegmentedMessage) { + // 移除流式消息(如果有) + if (currentStreamingMessage) { + currentStreamingMessage.remove(); + currentStreamingMessage = null; + } + + // 移除所有工具状态消息 + const toolStatuses = messagesEl.querySelectorAll('.tool-status'); + toolStatuses.forEach(el => el.remove()); + + // 创建新容器 + currentSegmentedMessage = document.createElement('div'); + currentSegmentedMessage.className = 'message bot-message segmented-message'; + messagesEl.appendChild(currentSegmentedMessage); + } + + // 步骤 2: 保存工具的展开/折叠状态 + const toolHeaders = currentSegmentedMessage.querySelectorAll('.tool-segment-header[data-collapsible="true"]'); + toolHeaders.forEach((header, idx) => { + const isCollapsed = header.classList.contains('collapsed'); + toolCollapseStates.set(idx, isCollapsed); + }); + + // 步骤 3: 清空容器并重新渲染所有段落 + currentSegmentedMessage.innerHTML = ''; + + // 步骤 4: 合并连续相同的工具调用 + const mergedSegments = mergeConsecutiveTools(segments); + + // 步骤 5: 遍历每个 segment 并渲染 + let toolIndex = 0; + mergedSegments.forEach((segment, index) => { + const segmentDiv = document.createElement('div'); + segmentDiv.className = 'message-segment segment-' + segment.type; + + // 根据 segment 类型渲染 + if (segment.type === 'text') { + renderTextSegment(segmentDiv, segment); + } else if (segment.type === 'tool') { + renderToolSegment(segmentDiv, segment, toolIndex); + toolIndex++; + } else if (segment.type === 'question') { + renderQuestionSegment(segmentDiv, segment); + } else if (segment.type === 'agent') { + renderAgentCard(segment, segmentDiv); + } else if (segment.type === 'plan') { + renderPlanCardInSegment(segment, segmentDiv, answeredQuestions); + } + + currentSegmentedMessage.appendChild(segmentDiv); + }); + + // 步骤 6: 如果对话完成,添加操作按钮 + if (isComplete) { + addMessageActions(currentSegmentedMessage, segments); + currentSegmentedMessage = null; + } + + // 步骤 7: 智能滚动到底部 + smartScrollToBottom(); +} +``` + +**渲染流程说明:** + +1. **容器管理**: 创建或复用 `currentSegmentedMessage` 容器 +2. **状态保持**: 保存工具的展开/折叠状态,避免重新渲染时丢失 +3. **清空重绘**: 每次更新都清空容器并重新渲染所有段落 +4. **工具合并**: 连续相同的工具调用会合并显示(如 "已完成文件读取 x3") +5. **类型渲染**: 根据 segment.type 调用不同的渲染函数 +6. **完成处理**: 对话完成时添加操作按钮(复制、点赞、点踩) +7. **智能滚动**: 只在用户位于底部时自动滚动 + +--- + +### 7. Segment 类型详解 + +#### 7.1 Text Segment (文本段落) + +**数据结构:** +```javascript +{ + type: 'text', + content: '这是 AI 的回复内容...' +} +``` + +**渲染函数**: `formatText()` (第 1359-1432 行) + +**处理流程:** +1. 提取代码块 (\`\`\`language\ncode\`\`\`) +2. 提取行内代码 (\`code\`) +3. 转义 HTML 特殊字符 +4. 处理 Markdown 语法: + - 标题: `#`, `##`, `###` + - 粗体: `**text**` + - 斜体: `*text*` + - 列表: `-`, `*`, `1.` + - 链接: `[text](url)` +5. 处理换行: `\n` → `
` +6. 恢复代码块和行内代码 + +**示例:** +```javascript +// 输入 +"## 标题\n这是**粗体**和*斜体*\n```js\nconst a = 1;\n```" + +// 输出 +"

标题


这是粗体斜体
const a = 1;
" +``` + +--- + +#### 7.2 Tool Segment (工具调用段落) + +**数据结构:** +```javascript +{ + type: 'tool', + toolName: 'file_read', // 工具名称 + toolStatus: 'success', // 状态: success/error + toolResult: '文件内容...', // 工具执行结果 + toolCount: 3, // 合并后的计数(可选) + vcdFilePath: '/path/to/file.vcd' // 特殊字段(仿真工具) +} +``` + +**渲染逻辑** (第 976-1053 行): + +1. **过滤工具**: 跳过不需要显示的工具(如 `spawnExplorer`) +2. **低调样式**: 所有工具调用使用低调样式(小字体、低透明度) +3. **工具图标**: 根据 `toolName` 显示对应图标 +4. **工具名称**: 映射为中文显示名称(如 `file_read` → "已完成文件读取") +5. **结果显示**: + - 短结果(≤60字符): 直接显示在同一行 + - 长结果(>60字符): 默认折叠,点击展开 +6. **特殊处理**: 仿真工具成功后添加波形预览组件 + +**工具名称映射:** +```javascript +const toolNameMap = { + 'file_read': '已完成文件读取', + 'file_write': '已完成文件写入', + 'file_delete': '已完成文件删除', + 'file_list': '已检索代码文件', + 'syntax_check': '已完成语法检查', + 'simulation': '已完成仿真', + 'waveform_summary': '已完成波形分析', + 'knowledge_save': '已保存知识库', + 'addSignal': '信号分析完成', + // ... 更多映射 +}; +``` + +**HTML 结构:** +```html +
+
+ + 已完成文件读取 x3 +
+ +
+``` + +--- + +#### 7.3 Question Segment (用户问题段落) + +**数据结构:** +```javascript +{ + type: 'question', + askId: 'ask_123456', // 问题唯一标识 + question: '请选择一个选项:', // 问题文本 + options: ['选项1', '选项2', '选项3'] // 选项列表 +} +``` + +**渲染逻辑** (第 1054-1118 行): + +1. **检查状态**: 从 `answeredQuestions` Map 中检查是否已回答 +2. **显示问题**: 显示问题文本 +3. **显示选项**: 渲染选项按钮 +4. **自定义输入**: 提供自定义输入框("其他"选项) +5. **事件监听**: + - 点击选项按钮 → 提交答案 + - 输入自定义答案 → 提交答案 + - 回车键 → 提交答案 +6. **状态更新**: 提交后标记为已回答,高亮选中选项 + +**交互流程:** +``` +用户点击选项/输入自定义答案 + ↓ +handleQuestionAnswerInSegment() + ↓ +保存答案到 answeredQuestions Map + ↓ +标记问题为已回答 (添加 .answered 类) + ↓ +高亮选中选项 (添加 .selected 类) + ↓ +发送答案到后端 (vscode.postMessage) +``` + +**HTML 结构:** +```html +
+
请选择一个选项:
+
+ + + +
+
+ + +
+
+``` + +--- + +#### 7.4 Agent Segment (智能体卡片) + +**数据结构:** +```javascript +{ + type: 'agent', + agentName: 'CodeExplorer', + agentStatus: 'running', // running/completed/error + agentMessage: '正在探索代码...' +} +``` + +**渲染**: 调用 `renderAgentCard()` 函数(定义在 `agentCard.ts`) + +--- + +#### 7.5 Plan Segment (计划卡片) + +**数据结构:** +```javascript +{ + type: 'plan', + planTitle: '实现用户认证功能', + planSteps: [ + { step: 1, description: '创建用户模型', status: 'pending' }, + { step: 2, description: '实现登录接口', status: 'pending' } + ] +} +``` + +**渲染**: 调用 `renderPlanCardInSegment()` 函数(定义在 `planCard.ts`) + +--- + +## 第七阶段:关键特性说明 + +### 8.1 实时更新机制 + +**核心原理**: 每次后端推送新数据时,前端都会**清空容器并重新渲染所有段落** + +**为什么这样设计?** +- ✅ 简化状态管理,避免复杂的 diff 算法 +- ✅ 确保显示内容与后端数据完全一致 +- ✅ 支持段落顺序变化和内容更新 + +**性能优化:** +- 保存工具的展开/折叠状态(`toolCollapseStates` Map) +- 保存问题的回答状态(`answeredQuestions` Map) +- 智能滚动(只在用户位于底部时自动滚动) + +--- + +### 8.2 工具合并机制 + +**目的**: 减少视觉噪音,提升用户体验 + +**实现** (第 946-966 行): +```javascript +// 合并连续相同的工具调用 +const mergedSegments = []; +let i = 0; +while (i < segments.length) { + const segment = segments[i]; + if (segment.type === 'tool') { + // 统计连续相同的工具调用 + let count = 1; + while (i + count < segments.length && + segments[i + count].type === 'tool' && + segments[i + count].toolName === segment.toolName) { + count++; + } + // 添加合并后的段落(带计数) + mergedSegments.push({ ...segment, toolCount: count }); + i += count; + } else { + mergedSegments.push(segment); + i++; + } +} +``` + +**效果:** +``` +原始: [file_read, file_read, file_read, text, file_write, file_write] +合并: [file_read x3, text, file_write x2] +``` + +--- + +### 8.3 智能滚动机制 + +**目的**: 只在用户位于底部时自动滚动,避免打断用户阅读 + +**实现** (第 714-724 行): +```javascript +// 检查用户是否在底部附近(允许50px的误差) +function isUserNearBottom() { + const threshold = 50; + return messagesEl.scrollHeight - messagesEl.scrollTop - messagesEl.clientHeight < threshold; +} + +// 智能滚动:只有用户在底部附近时才自动滚动 +function smartScrollToBottom() { + if (isUserNearBottom()) { + messagesEl.scrollTop = messagesEl.scrollHeight; + } +} +``` + +**用户体验:** +- ✅ 用户在底部 → 自动滚动到最新消息 +- ✅ 用户在阅读历史消息 → 不自动滚动,避免打断 + +--- + +### 8.4 状态保持机制 + +**问题**: 每次重新渲染都会清空容器,如何保持用户的交互状态? + +**解决方案**: 使用 Map 存储状态 + +**1. 工具折叠状态** (`toolCollapseStates` Map): +```javascript +// 保存状态(重新渲染前) +const toolHeaders = currentSegmentedMessage.querySelectorAll('.tool-segment-header[data-collapsible="true"]'); +toolHeaders.forEach((header, idx) => { + const isCollapsed = header.classList.contains('collapsed'); + toolCollapseStates.set(idx, isCollapsed); +}); + +// 恢复状态(渲染时) +const savedState = toolCollapseStates.get(toolIndex); +const isCollapsed = savedState !== undefined ? savedState : shouldCollapse; +``` + +**2. 问题回答状态** (`answeredQuestions` Map): +```javascript +// 保存答案 +answeredQuestions.set(askId, answer); + +// 检查是否已回答 +const isAnswered = answeredQuestions.has(segment.askId); +const selectedAnswer = answeredQuestions.get(segment.askId); +``` + +--- + +## 完整数据流程总结 + +### 时序图 + +``` +用户输入 "帮我读取 main.v 文件" + ↓ (0ms) +前端显示用户消息 + ↓ (1ms) +vscode.postMessage({ command: 'sendMessage', text: '...' }) + ↓ (2ms) +ICHelperPanel.onDidReceiveMessage + ↓ (3ms) +创建/获取 taskId + ↓ (5ms) +handleUserMessage + ↓ (10ms) +handleUserMessageWithBackend + ↓ (15ms) +dialogManager.createSession + ↓ (20ms) +currentSession.sendMessage + ↓ (100ms - 后端处理) +onSegmentUpdate 回调 (多次) + ↓ (101ms, 150ms, 200ms...) +panel.webview.postMessage({ command: 'updateSegments', segments: [...] }) + ↓ (102ms, 151ms, 201ms...) +window.addEventListener('message') + ↓ (103ms, 152ms, 202ms...) +updateSegmentsRealtime(segments, false) + ↓ (105ms, 155ms, 205ms...) +渲染 segments 到 DOM + ↓ (3000ms - 对话完成) +onComplete 回调 + ↓ (3001ms) +panel.webview.postMessage({ command: 'updateSegments', segments: [...], isComplete: true }) + ↓ (3002ms) +updateSegmentsRealtime(segments, true) + ↓ (3005ms) +添加操作按钮 (复制、点赞、点踩) + ↓ (3010ms) +恢复发送按钮状态 +``` + +--- + +### 关键文件索引 + +| 文件 | 职责 | 关键函数 | +|------|------|----------| +| `inputArea.ts` | 输入框组件 | `sendMessage()` | +| `ICHelperPanel.ts` | 面板管理 | `onDidReceiveMessage()` | +| `messageHandler.ts` | 消息处理核心 | `handleUserMessage()`, `handleUserMessageWithBackend()` | +| `webviewContent.ts` | WebView 主入口 | `window.addEventListener('message')` | +| `messageArea.ts` | 消息渲染核心 | `updateSegmentsRealtime()`, `formatText()` | +| `agentCard.ts` | 智能体卡片 | `renderAgentCard()` | +| `planCard.ts` | 计划卡片 | `renderPlanCardInSegment()` | +| `waveformPreviewContent.ts` | 波形预览 | `createWaveformPreview()` | + +--- + +### 数据结构总览 + +**Segment 类型定义:** +```typescript +type Segment = + | TextSegment + | ToolSegment + | QuestionSegment + | AgentSegment + | PlanSegment; + +interface TextSegment { + type: 'text'; + content: string; +} + +interface ToolSegment { + type: 'tool'; + toolName: string; + toolStatus: 'success' | 'error'; + toolResult?: string; + toolCount?: number; + vcdFilePath?: string; + fileName?: string; +} + +interface QuestionSegment { + type: 'question'; + askId: string; + question: string; + options: string[]; +} + +interface AgentSegment { + type: 'agent'; + agentName: string; + agentStatus: 'running' | 'completed' | 'error'; + agentMessage: string; +} + +interface PlanSegment { + type: 'plan'; + planTitle: string; + planSteps: Array<{ + step: number; + description: string; + status: 'pending' | 'running' | 'completed' | 'error'; + }>; +} +``` + +--- + +### 消息通信协议 + +**前端 → 后端 (vscode.postMessage):** + +| Command | 参数 | 说明 | +|---------|------|------| +| `sendMessage` | `text`, `mode`, `model`, `planMode`, `contextItems` | 发送用户消息 | +| `submitAnswer` | `askId`, `selected`, `customInput` | 提交问题答案 | +| `abortDialog` | - | 中止当前对话 | +| `openWaveformViewer` | `vcdFilePath` | 打开波形查看器 | + +**后端 → 前端 (panel.webview.postMessage):** + +| Command | 参数 | 说明 | +|---------|------|------| +| `updateSegments` | `segments`, `isComplete` | 更新段落(核心) | +| `updateStatus` | `text`, `type` | 更新状态栏 | +| `hideStatus` | - | 隐藏状态栏 | +| `showProgress` | - | 显示进度条 | +| `resetSegmentedMessage` | - | 重置分段消息容器 | + +--- + +## 常见问题 FAQ + +### Q1: 为什么每次更新都要清空容器重新渲染? + +**A**: 这是一种简化的状态管理策略: +- ✅ 避免复杂的 diff 算法 +- ✅ 确保显示内容与后端数据完全一致 +- ✅ 支持段落顺序变化 +- ✅ 代码简单易维护 + +性能影响很小,因为: +- 渲染速度很快(通常 < 10ms) +- 用户感知不到闪烁 +- 通过状态保持机制避免丢失用户交互 + +--- + +### Q2: 工具折叠状态如何保持? + +**A**: 使用 `toolCollapseStates` Map 存储: +```javascript +// 重新渲染前保存 +toolCollapseStates.set(toolIndex, isCollapsed); + +// 渲染时恢复 +const savedState = toolCollapseStates.get(toolIndex); +``` + +--- + +### Q3: 如何添加新的 Segment 类型? + +**A**: 按以下步骤操作: + +1. 在 `updateSegmentsRealtime()` 中添加类型判断 +2. 创建对应的渲染函数 +3. 在 `messageArea.ts` 中添加样式 +4. 更新工具图标映射(如果需要) + +示例: +```javascript +// 1. 添加类型判断 +else if (segment.type === 'myNewType') { + renderMyNewType(segmentDiv, segment); +} + +// 2. 创建渲染函数 +function renderMyNewType(container, segment) { + container.innerHTML = `
${segment.content}
`; +} +``` + +--- + +### Q4: 如何调试数据流程? + +**A**: 在浏览器开发者工具中查看日志: + +1. 打开 VS Code 开发者工具: `Help` → `Toggle Developer Tools` +2. 切换到 `Console` 标签 +3. 查看关键日志: + - `[WebView] 收到消息:` - 前端接收消息 + - `[WebView] updateSegmentsRealtime 被调用` - 渲染触发 + - `[MessageHandler] 创建会话:` - 后端会话创建 + +--- + +## 总结 + +本文档详细说明了 IC Coder Plugin 从用户输入到显示响应的完整数据流程: + +1. **用户输入** → 前端显示并发送消息 +2. **VS Code 扩展** → 接收消息并创建任务上下文 +3. **消息处理器** → 调用后端服务 +4. **后端服务** → 处理消息并通过回调推送数据 +5. **前端接收** → 监听消息并触发渲染 +6. **消息渲染** → 根据 segment 类型渲染 UI +7. **用户交互** → 保持状态并响应用户操作 + +核心设计理念: +- ✅ **实时更新**: 支持流式数据推送 +- ✅ **状态保持**: 避免丢失用户交互 +- ✅ **智能滚动**: 不打断用户阅读 +- ✅ **工具合并**: 减少视觉噪音 +- ✅ **简化管理**: 清空重绘而非复杂 diff + +--- + +**文档版本**: v1.0 +**最后更新**: 2026-01-08 +**作者**: IC Coder Team + diff --git a/src/views/messageArea.ts b/src/views/messageArea.ts index 9c836d1..4d5ed9f 100644 --- a/src/views/messageArea.ts +++ b/src/views/messageArea.ts @@ -725,12 +725,6 @@ export function getMessageAreaScript(): string { // 添加消息 function addMessage(text, sender) { - // 如果是用户消息,先重置当前分段消息容器 - // 确保用户消息不会被添加到上一轮 AI 消息容器内部 - if (sender === 'user' && currentSegmentedMessage) { - currentSegmentedMessage = null; - } - const div = document.createElement('div'); div.className = \`message \${sender}-message\`; @@ -909,12 +903,6 @@ export function getMessageAreaScript(): string { function updateSegmentsRealtime(segments, isComplete) { console.log('[WebView] updateSegmentsRealtime 被调用, segments:', segments, 'isComplete:', isComplete); - // 如果是完成标记,不处理 segments,直接返回 - if (isComplete) { - console.log('[WebView] 对话完成,跳过 segments 处理'); - return; - } - if (!segments || segments.length === 0) { console.log('[WebView] segments 为空,跳过渲染'); return;