Files
IC-Coder-Plugin/docs/数据流程详解.md
Roe-xin 1df7462778 docs: 添加数据流程详解文档 + fix: 修复消息渲染逻辑
- 新增完整的数据流程文档,详细说明从用户输入到响应显示的全流程
   - 修复 messageArea.ts 中的消息渲染逻辑:
     - 移除用户消息时重置分段容器的逻辑
     - 移除对话完成时跳过 segments 处理的逻辑
     - 确保对话完成时正确渲染最终的 segments
2026-01-08 17:24:36 +08:00

1028 lines
28 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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<void> {
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``<br>`
6. 恢复代码块和行内代码
**示例:**
```javascript
// 输入
"## 标题\n这是**粗体**和*斜体*\n```js\nconst a = 1;\n```"
// 输出
"<h2>标题</h2><br>这是<strong>粗体</strong>和<em>斜体</em><br><pre><code class='language-js'>const a = 1;</code></pre>"
```
---
#### 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
<div class="message-segment segment-tool low-profile">
<div class="tool-segment-header" data-collapsible="true">
<span class="tool-collapse-icon"></span>
<span class="tool-segment-name">已完成文件读取 x3</span>
</div>
<div class="tool-segment-content collapsed">
<span class="tool-segment-result">文件内容...</span>
</div>
</div>
```
---
#### 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
<div class="message-segment segment-question">
<div class="question-text">请选择一个选项:</div>
<div class="question-options" data-ask-id="ask_123456">
<button class="question-option" data-option="选项1">选项1</button>
<button class="question-option selected" data-option="选项2">选项2</button>
<button class="question-option" data-option="选项3">选项3</button>
</div>
<div class="custom-input-container">
<input type="text" class="custom-input" placeholder="输入其他答案..." />
<button class="custom-submit">提交</button>
</div>
</div>
```
---
#### 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 = `<div>${segment.content}</div>`;
}
```
---
### 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