diff --git a/package.json b/package.json index 003c9cf..c6a416a 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,8 @@ }, "icon": "media/icon.png", "categories": [ - "Other" + "Chat", + "Programming Languages" ], "keywords": [ "IC", @@ -75,7 +76,7 @@ "properties": { "icCoder.backendUrl": { "type": "string", - "default": "http://localhost:2233", + "default": "http://192.168.1.108:2233", "description": "后端服务地址" }, "icCoder.timeout": { diff --git a/src/config/settings.ts b/src/config/settings.ts index 621be84..8b6e490 100644 --- a/src/config/settings.ts +++ b/src/config/settings.ts @@ -16,8 +16,8 @@ export interface IccoderConfig { /** 默认配置 */ const DEFAULT_CONFIG: IccoderConfig = { - backendUrl: "http://localhost:8080", - timeout: 3600000, // 1小时 + backendUrl: "http://192.168.1.108:2233", + timeout: 60000, userId: "default-user", }; diff --git a/src/constants/toolIcons.ts b/src/constants/toolIcons.ts index 8d866f9..6fff308 100644 --- a/src/constants/toolIcons.ts +++ b/src/constants/toolIcons.ts @@ -70,3 +70,98 @@ export const stopIconSvg = ` `; + +/** + * 探索智能体图标 SVG + */ +export const agentIconSvg = ` +`; + +/** + * planner 图标 SVG + */ +export const plannerIconSvg = ``; + +/** + * 保存知识库图标 SVG + */ +export const saveKnowledgeIconSvg = ` + + + + + +`; + +/** + * 文件读取图标 SVG + */ +export const fileReadIconSvg = ` + + + + + + +`; + +/** + * 文件删除图标 SVG + */ +export const fileDeleteIconSvg = ` + + + + + + +`; + +/** + * 仿真图标 SVG + */ +export const simulationIconSvg = ` + + + + + + +`; + +/** + * 波形分析图标 SVG + */ +export const waveformIconSvg = ` + + + + + + +`; + +/** + * 知识库加载图标 SVG + */ +export const knowledgeLoadIconSvg = ` + + + + + + +`; + +/** + * 状态转换图标 SVG + */ +export const stateTransitionIconSvg = ` + + + + + + + +`; diff --git a/src/panels/ICHelperPanel.ts b/src/panels/ICHelperPanel.ts index f5e149f..7d9c114 100644 --- a/src/panels/ICHelperPanel.ts +++ b/src/panels/ICHelperPanel.ts @@ -26,21 +26,27 @@ export async function showICHelperPanel( ) { // 检查用户是否已登录 try { - const session = await vscode.authentication.getSession("iccoder", [], { createIfNone: false }); + const session = await vscode.authentication.getSession("iccoder", [], { + createIfNone: false, + }); if (!session) { - vscode.window.showWarningMessage("请先登录后再使用 IC Coder", "立即登录").then((selection) => { + vscode.window + .showWarningMessage("请先登录后再使用 IC Coder", "立即登录") + .then((selection) => { + if (selection === "立即登录") { + vscode.commands.executeCommand("ic-coder.login"); + } + }); + return; + } + } catch (error) { + vscode.window + .showWarningMessage("请先登录后再使用 IC Coder", "立即登录") + .then((selection) => { if (selection === "立即登录") { vscode.commands.executeCommand("ic-coder.login"); } }); - return; - } - } catch (error) { - vscode.window.showWarningMessage("请先登录后再使用 IC Coder", "立即登录").then((selection) => { - if (selection === "立即登录") { - vscode.commands.executeCommand("ic-coder.login"); - } - }); return; } @@ -110,7 +116,12 @@ export async function showICHelperPanel( // 切换到当前面板的任务上下文 historyManager.switchToPanelTask(panelId); - handleUserMessage(panel, message.text, context.extensionPath, message.mode); + handleUserMessage( + panel, + message.text, + context.extensionPath, + message.mode + ); break; case "readFile": handleReadFile(panel, message.filePath); @@ -186,22 +197,54 @@ export async function showICHelperPanel( break; // 处理计划操作(只做模式切换,响应已通过 submitAnswer 发送) case "planAction": - if (message.action === 'confirm') { + if (message.action === "confirm") { // 确认执行:切换到 Agent 模式 panel.webview.postMessage({ - command: 'switchMode', - mode: 'agent' + command: "switchMode", + mode: "agent", }); // 获取当前会话的 taskId,用于复用知识图谱数据 const taskId = getCurrentTaskId(); if (taskId) { // 设置待执行的计划,对话结束后自动执行(复用 taskId) - setPendingPlanExecution(panel, message.planTitle || '计划', context.extensionPath, taskId); + setPendingPlanExecution( + panel, + message.planTitle || "计划", + context.extensionPath, + taskId + ); } else { - console.warn('[ICHelperPanel] 无法获取当前 taskId,知识图谱数据可能丢失'); + console.warn( + "[ICHelperPanel] 无法获取当前 taskId,知识图谱数据可能丢失" + ); } } 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; } }, undefined, diff --git a/src/utils/createFiles.ts b/src/utils/createFiles.ts index cf1d8a5..c8a089c 100644 --- a/src/utils/createFiles.ts +++ b/src/utils/createFiles.ts @@ -16,7 +16,9 @@ export async function createFile( if (workspaceFolders && workspaceFolders.length > 0) { absolutePath = path.join(workspaceFolders[0].uri.fsPath, filePath); } else { - throw new Error("没有打开的工作区,无法创建相对路径的文件"); + throw new Error( + "请先打开一个文件夹作为工作区,这样我就能为您创建文件了" + ); } } @@ -28,7 +30,7 @@ export async function createFile( throw new Error(`文件已存在: ${absolutePath}`); } catch (error: any) { // 如果文件不存在,继续创建 - if (error.code !== 'FileNotFound') { + if (error.code !== "FileNotFound") { throw error; } } @@ -65,7 +67,9 @@ export async function createOrOverwriteFile( if (workspaceFolders && workspaceFolders.length > 0) { absolutePath = path.join(workspaceFolders[0].uri.fsPath, filePath); } else { - throw new Error("没有打开的工作区,无法创建相对路径的文件"); + throw new Error( + "请先打开一个文件夹作为工作区,这样我就能为您创建文件了" + ); } } @@ -99,7 +103,9 @@ export async function createDirectory(dirPath: string): Promise { if (workspaceFolders && workspaceFolders.length > 0) { absolutePath = path.join(workspaceFolders[0].uri.fsPath, dirPath); } else { - throw new Error("没有打开的工作区,无法创建相对路径的目录"); + throw new Error( + "请先打开一个文件夹作为工作区,这样我就能为您创建目录了" + ); } } @@ -115,7 +121,7 @@ export async function createDirectory(dirPath: string): Promise { } } catch (error: any) { // 如果目录不存在,继续创建 - if (error.code !== 'FileNotFound') { + if (error.code !== "FileNotFound") { throw error; } } @@ -161,7 +167,9 @@ export async function deleteFile(filePath: string): Promise { if (workspaceFolders && workspaceFolders.length > 0) { absolutePath = path.join(workspaceFolders[0].uri.fsPath, filePath); } else { - throw new Error("没有打开的工作区,无法删除相对路径的文件"); + throw new Error( + "请先打开一个文件夹作为工作区,这样我就能为您删除文件了" + ); } } @@ -197,7 +205,9 @@ export async function updateFile( if (workspaceFolders && workspaceFolders.length > 0) { absolutePath = path.join(workspaceFolders[0].uri.fsPath, filePath); } else { - throw new Error("没有打开的工作区,无法修改相对路径的文件"); + throw new Error( + "请先打开一个文件夹作为工作区,这样我就能为您修改文件了" + ); } } @@ -236,7 +246,9 @@ export async function appendToFile( if (workspaceFolders && workspaceFolders.length > 0) { absolutePath = path.join(workspaceFolders[0].uri.fsPath, filePath); } else { - throw new Error("没有打开的工作区,无法追加相对路径的文件"); + throw new Error( + "请先打开一个文件夹作为工作区,这样我就能为您追加文件内容了" + ); } } @@ -274,7 +286,9 @@ export async function replaceFile( if (workspaceFolders && workspaceFolders.length > 0) { absolutePath = path.join(workspaceFolders[0].uri.fsPath, filePath); } else { - throw new Error("没有打开的工作区,无法修改相对路径的文件"); + throw new Error( + "请先打开一个文件夹作为工作区,这样我就能为您修改文件了" + ); } } @@ -291,14 +305,17 @@ export async function replaceFile( // 转义特殊字符,将字符串作为字面量处理 const escapeRegExp = (str: string) => { - return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); }; // 替换内容 - 如果是字符串,先转义特殊字符 let newContent: string; - if (typeof searchValue === 'string') { + if (typeof searchValue === "string") { const escapedSearch = escapeRegExp(searchValue); - newContent = fileContent.replace(new RegExp(escapedSearch, "g"), replaceValue); + newContent = fileContent.replace( + new RegExp(escapedSearch, "g"), + replaceValue + ); } else { newContent = fileContent.replace(searchValue, replaceValue); } @@ -330,7 +347,9 @@ export async function insertAtLine( if (workspaceFolders && workspaceFolders.length > 0) { absolutePath = path.join(workspaceFolders[0].uri.fsPath, filePath); } else { - throw new Error("没有打开的工作区,无法修改相对路径的文件"); + throw new Error( + "请先打开一个文件夹作为工作区,这样我就能为您修改文件了" + ); } } @@ -382,7 +401,9 @@ export async function renameFile( absoluteNewPath = path.join(workspaceRoot, newPath); } } else { - throw new Error("没有打开的工作区,无法重命名相对路径的文件"); + throw new Error( + "请先打开一个文件夹作为工作区,这样我就能为您重命名文件了" + ); } const oldUri = vscode.Uri.file(absoluteOldPath); @@ -401,10 +422,13 @@ export async function renameFile( throw new Error(`目标文件已存在: ${absoluteNewPath}`); } catch (error: any) { // 如果文件不存在,继续重命名 - if (error.code !== 'FileNotFound' && !error.message.includes('目标文件已存在')) { + if ( + error.code !== "FileNotFound" && + !error.message.includes("目标文件已存在") + ) { throw error; } - if (error.message.includes('目标文件已存在')) { + if (error.message.includes("目标文件已存在")) { throw error; } } diff --git a/src/views/agentCard.ts b/src/views/agentCard.ts index 1515750..2783cd7 100644 --- a/src/views/agentCard.ts +++ b/src/views/agentCard.ts @@ -2,310 +2,176 @@ * 智能体卡片组件 * * 功能说明: - * - 显示子智能体的执行过程 - * - 支持展开/收起步骤详情 - * - 显示执行状态和统计信息 + * - 提供智能体执行状态的可视化展示 + * - 显示智能体名称、状态和执行步骤 + * - 支持实时更新步骤信息 */ -import type { - AgentStartEvent, - AgentProgressEvent, - AgentCompleteEvent, - AgentErrorEvent -} from '../types/api'; +import { agentIconSvg } from "../constants/toolIcons"; /** - * 获取智能体卡片样式 + * 获取智能体卡片的样式 */ export function getAgentCardStyles(): string { return ` + /* 智能体卡片样式 */ + .segment-agent { + margin: 8px 0; + } .agent-card { - background: var(--vscode-editor-background); border: 1px solid var(--vscode-input-border); border-radius: 8px; - margin: 10px 0; overflow: hidden; + background: var(--vscode-editor-background); } - .agent-card-header { - display: flex; - align-items: center; - justify-content: space-between; - padding: 10px 12px; - background: var(--vscode-sideBar-background); - cursor: pointer; - user-select: none; - } - .agent-card-header:hover { - background: var(--vscode-list-hoverBackground); - } - .agent-card-title { + .agent-header { display: flex; align-items: center; gap: 8px; - font-weight: 500; + padding: 8px 12px; + background: var(--vscode-sideBar-background); + border-bottom: 1px solid var(--vscode-input-border); } - .agent-card-icon { + .agent-icon { font-size: 16px; } - .agent-card-status { - font-size: 12px; - padding: 2px 6px; - border-radius: 4px; + .agent-name { + font-weight: 500; + flex: 1; } - .agent-card-status.running { + .agent-status { + font-size: 11px; + padding: 2px 8px; + border-radius: 10px; + } + .agent-status.running { background: var(--vscode-inputValidation-infoBackground); color: var(--vscode-inputValidation-infoForeground); } - .agent-card-status.completed { - background: var(--vscode-testing-iconPassed); + .agent-status.completed { + background: #28a745; color: white; } - .agent-card-status.error { - background: var(--vscode-testing-iconFailed); + .agent-status.error { + background: #dc3545; color: white; } - .agent-card-toggle { + .agent-body { + padding: 8px; + } + .agent-steps-container { + max-height: 150px; + overflow-y: auto; font-size: 12px; - color: var(--vscode-descriptionForeground); - } - .agent-card-body { - padding: 12px; - border-top: 1px solid var(--vscode-input-border); - } - .agent-card-body.collapsed { - display: none; - } - .agent-card-instruction { - font-size: 13px; - color: var(--vscode-descriptionForeground); - margin-bottom: 10px; - padding-bottom: 10px; - border-bottom: 1px dashed var(--vscode-input-border); - } - .agent-steps { - font-size: 13px; } .agent-step { display: flex; - align-items: flex-start; - gap: 8px; - padding: 6px 0; - border-left: 2px solid var(--vscode-input-border); - padding-left: 12px; - margin-left: 6px; + align-items: center; + gap: 6px; + padding: 4px 8px; + border-radius: 4px; + margin-bottom: 4px; + background: var(--vscode-list-hoverBackground); } .agent-step:last-child { - border-left-color: transparent; + margin-bottom: 0; } - .agent-step-icon { + .step-icon { flex-shrink: 0; } - .agent-step-content { - flex: 1; - min-width: 0; - } - .agent-step-name { + .step-name { font-weight: 500; + color: var(--vscode-foreground); } - .agent-step-result { - font-size: 12px; + .step-result { color: var(--vscode-descriptionForeground); - margin-top: 2px; - white-space: nowrap; + font-size: 11px; overflow: hidden; text-overflow: ellipsis; + white-space: nowrap; } - .agent-card-summary { - font-size: 13px; - padding: 8px 12px; - background: var(--vscode-sideBar-background); - border-top: 1px solid var(--vscode-input-border); - } - .agent-card-error { - color: var(--vscode-errorForeground); - padding: 8px 12px; - background: var(--vscode-inputValidation-errorBackground); + .agent-step-placeholder { + color: var(--vscode-descriptionForeground); + font-style: italic; + padding: 8px; + text-align: center; } `; } /** - * 渲染智能体卡片(启动状态) - */ -export function renderAgentCardStart(event: AgentStartEvent): string { - return ` - - - - 🤖 - ${event.agentName} - 执行中 - - ▼ - - - 指令:${escapeHtml(event.instruction)} - - - - - - `; -} - -/** - * 渲染步骤项(进行中) - */ -export function renderAgentStepRunning(event: AgentProgressEvent): string { - const inputStr = event.toolInput ? JSON.stringify(event.toolInput) : ''; - const shortInput = inputStr.length > 50 ? inputStr.substring(0, 50) + '...' : inputStr; - - return ` - - 🔄 - - ${event.toolName} - ${escapeHtml(shortInput)} - - - `; -} - -/** - * 更新步骤项(完成) - */ -export function getStepCompleteUpdate(event: AgentProgressEvent): { icon: string; result: string } { - const result = event.toolResult || ''; - const shortResult = result.length > 80 ? result.substring(0, 80) + '...' : result; - return { - icon: event.status === 'completed' ? '✅' : '❌', - result: shortResult - }; -} - -/** - * 获取智能体卡片脚本 + * 获取智能体卡片的脚本 */ export function getAgentCardScript(): string { return ` - // 智能体状态存储 - const agentStates = {}; - - // 切换智能体卡片展开/收起 - function toggleAgentCard(agentId) { - const body = document.getElementById('agent-body-' + agentId); - const header = body?.previousElementSibling; - const toggle = header?.querySelector('.agent-card-toggle'); - - if (body && toggle) { - body.classList.toggle('collapsed'); - toggle.textContent = body.classList.contains('collapsed') ? '▶' : '▼'; - } - } - - // 处理智能体启动事件 - function handleAgentStart(event) { - agentStates[event.agentId] = { - status: 'running', - steps: [] + // 工具名称中文映射 + function getAgentToolDisplayName(toolName) { + const toolNameMap = { + 'file_read': '文件读取', + 'file_write': '文件写入', + 'file_delete': '文件删除', + 'file_list': '检索文件', + 'syntax_check': '语法检查', + 'simulation': '仿真', + 'waveform_summary': '波形分析', + 'knowledge_save': '保存知识库', + 'knowledge_load': '加载知识库', + 'queryKnowledgeSummary': '查询知识摘要', + 'queryRules': '查询规则', + 'setModule': '设置模块', + 'addSignal': '添加信号', + 'addSignalExample': '添加信号示例', + 'validateKnowledgeGraph': '验证知识图谱', + 'querySignals': '查询信号', + 'addPlan': '添加计划', + 'addEdge': '添加边', + 'showPlan': '显示计划', + 'spawnExplorer': '代码探索' }; - - // 在当前消息中添加智能体卡片 - const currentMessage = document.querySelector('.bot-message:last-child .message-content'); - if (currentMessage) { - currentMessage.insertAdjacentHTML('beforeend', renderAgentCardStart(event)); - } + return toolNameMap[toolName] || toolName; } - // 处理智能体进度事件 - function handleAgentProgress(event) { - const stepsContainer = document.getElementById('agent-steps-' + event.agentId); - if (!stepsContainer) return; + /** + * 渲染智能体卡片 + * @param {Object} segment - 智能体段落数据 + * @param {HTMLElement} segmentDiv - 段落容器元素 + */ + function renderAgentCard(segment, segmentDiv) { + segmentDiv.className += ' segment-agent'; - if (event.status === 'running') { - // 添加新步骤 - stepsContainer.insertAdjacentHTML('beforeend', renderAgentStepRunning(event)); - } else if (event.status === 'completed') { - // 更新步骤状态 - const stepEl = document.getElementById('agent-step-' + event.agentId + '-' + event.step); - if (stepEl) { - const iconEl = stepEl.querySelector('.agent-step-icon'); - const resultEl = stepEl.querySelector('.agent-step-result'); - if (iconEl) iconEl.textContent = '✅'; - if (resultEl) { - const result = event.toolResult || ''; - resultEl.textContent = result.length > 80 ? result.substring(0, 80) + '...' : result; - } + const statusText = segment.agentStatus === 'completed' ? '完成' + : segment.agentStatus === 'error' ? '错误' : '执行中'; + const statusClass = segment.agentStatus || 'running'; + + const stepsHtml = (segment.agentSteps || []).map(step => { + const icon = step.status === 'completed' ? '✅' : step.status === 'error' ? '❌' : '🔄'; + const displayName = getAgentToolDisplayName(step.toolName); + const result = step.toolResult ? \`: \${step.toolResult.substring(0, 50)}\${step.toolResult.length > 50 ? '...' : ''}\` : ''; + return \`\${icon}\${displayName}\${result}\`; + }).join(''); + + segmentDiv.innerHTML = \` + + + ${agentIconSvg} + \${segment.agentName || '智能体'} + \${statusText} + + + + \${stepsHtml || '等待执行...'} + + + + \`; + + // 自动滚动到最新步骤 + setTimeout(() => { + const container = segmentDiv.querySelector('.agent-steps-container'); + if (container) { + container.scrollTop = container.scrollHeight; } - } - } - - // 处理智能体完成事件 - function handleAgentComplete(event) { - const card = document.getElementById('agent-' + event.agentId); - if (!card) return; - - // 更新状态 - const statusEl = card.querySelector('.agent-card-status'); - if (statusEl) { - statusEl.className = 'agent-card-status completed'; - statusEl.textContent = '完成'; - } - - // 添加摘要 - const body = card.querySelector('.agent-card-body'); - if (body) { - body.insertAdjacentHTML('beforeend', - '' + escapeHtml(event.summary) + '' - ); - } - - // 自动收起 - body?.classList.add('collapsed'); - const toggle = card.querySelector('.agent-card-toggle'); - if (toggle) toggle.textContent = '▶'; - } - - // 处理智能体错误事件 - function handleAgentError(event) { - const card = document.getElementById('agent-' + event.agentId); - if (!card) return; - - // 更新状态 - const statusEl = card.querySelector('.agent-card-status'); - if (statusEl) { - statusEl.className = 'agent-card-status error'; - statusEl.textContent = '错误'; - } - - // 添加错误信息 - const body = card.querySelector('.agent-card-body'); - if (body) { - body.insertAdjacentHTML('beforeend', - '错误:' + escapeHtml(event.error) + '' - ); - } - } - - // HTML 转义 - function escapeHtml(text) { - if (!text) return ''; - const div = document.createElement('div'); - div.textContent = text; - return div.innerHTML; + }, 0); } `; } - -/** - * HTML 转义(服务端使用) - */ -function escapeHtml(text: string): string { - if (!text) return ''; - return text - .replace(/&/g, '&') - .replace(//g, '>') - .replace(/"/g, '"') - .replace(/'/g, '''); -} diff --git a/src/views/inputArea.ts b/src/views/inputArea.ts index d1596a4..dc3d0f8 100644 --- a/src/views/inputArea.ts +++ b/src/views/inputArea.ts @@ -2,37 +2,29 @@ import { getWaveformPreviewContent } from "./waveformPreviewContent"; import { getModelSelectorContent, getModelSelectorStyles, - getModelSelectorScript + getModelSelectorScript, } from "./modelSelector"; import { getModeSelectorContent, getModeSelectorStyles, - getModeSelectorScript + getModeSelectorScript, } from "./agentModeSelector"; import { getContextButtonContent, getContextButtonStyles, - getContextButtonScript + getContextButtonScript, } from "./contextButton"; import { getContextCompressContent, getContextCompressStyles, - getContextCompressScript + getContextCompressScript, } from "./contextCompress"; -import { - getPlanToggleContent, - getPlanToggleStyles, - getPlanToggleScript -} from "./planToggle"; import { getOptimizeButtonContent, getOptimizeButtonStyles, - getOptimizeButtonScript + getOptimizeButtonScript, } from "./optimizeButton"; -import { - sendIconSvg, - stopIconSvg -} from "../constants/toolIcons"; +import { sendIconSvg, stopIconSvg } from "../constants/toolIcons"; /** * 获取输入区域的 HTML 内容 @@ -45,7 +37,6 @@ export function getInputAreaContent(): string { ${getContextButtonContent()} - ${getPlanToggleContent()} { + if (!hasCheckedWorkspace) { + hasCheckedWorkspace = true; + vscode.postMessage({ command: 'checkWorkspace' }); + } + }); + // 初始化时调整一次高度 autoResizeTextarea(); - - // 聚焦到输入框 - messageInput.focus(); } // 切换发送按钮状态 @@ -326,6 +324,14 @@ export function getInputAreaScript(): string { const text = messageInput.value.trim(); if (!text) return; + // 检查工作区状态 + if (!hasWorkspace) { + // 如果没有工作区,阻止发送并清空输入框 + messageInput.value = ''; + autoResizeTextarea(); + return; + } + const mode = getCurrentMode(); // 从模式选择器组件获取当前模式 const model = getCurrentModel(); // 从模型选择器组件获取当前模型 const planMode = document.getElementById('planToggle')?.checked || false; diff --git a/src/views/messageArea.ts b/src/views/messageArea.ts index 635f8d2..498710b 100644 --- a/src/views/messageArea.ts +++ b/src/views/messageArea.ts @@ -13,13 +13,23 @@ import { collapseIconSvg, fileWriteIconSvg, + fileReadIconSvg, + fileDeleteIconSvg, syntaxCheckIconSvg, SearchCode, + agentIconSvg, + saveKnowledgeIconSvg, + simulationIconSvg, + waveformIconSvg, + knowledgeLoadIconSvg, + stateTransitionIconSvg, } from "../constants/toolIcons"; import { getWaveformPreviewContent, getWaveformPreviewScript, } from "./waveformPreviewContent"; +import { getAgentCardStyles, getAgentCardScript } from "./agentCard"; +import { getPlanCardStyles, getPlanCardScript } from "./planCard"; /** * 获取消息区域的 HTML 内容 @@ -416,6 +426,28 @@ export function getMessageAreaStyles(): string { height: 100%; display: block; } + .tool-file-read-icon { + width: 16px; + height: 16px; + flex-shrink: 0; + margin-right: 6px; + } + .tool-file-read-icon svg { + width: 100%; + height: 100%; + display: block; + } + .tool-file-delete-icon { + width: 16px; + height: 16px; + flex-shrink: 0; + margin-right: 6px; + } + .tool-file-delete-icon svg { + width: 100%; + height: 100%; + display: block; + } .tool-syntax-check-icon { width: 16px; height: 16px; @@ -438,6 +470,61 @@ export function getMessageAreaStyles(): string { height: 100%; display: block; } + .tool-save-knowledge-icon { + width: 16px; + height: 16px; + flex-shrink: 0; + margin-right: 6px; + } + .tool-save-knowledge-icon svg { + width: 100%; + height: 100%; + display: block; + } + .tool-simulation-icon { + width: 16px; + height: 16px; + flex-shrink: 0; + margin-right: 6px; + } + .tool-simulation-icon svg { + width: 100%; + height: 100%; + display: block; + } + .tool-waveform-icon { + width: 16px; + height: 16px; + flex-shrink: 0; + margin-right: 6px; + } + .tool-waveform-icon svg { + width: 100%; + height: 100%; + display: block; + } + .tool-knowledge-load-icon { + width: 16px; + height: 16px; + flex-shrink: 0; + margin-right: 6px; + } + .tool-knowledge-load-icon svg { + width: 100%; + height: 100%; + display: block; + } + .tool-state-transition-icon { + width: 16px; + height: 16px; + flex-shrink: 0; + margin-right: 6px; + } + .tool-state-transition-icon svg { + width: 100%; + height: 100%; + display: block; + } .tool-segment-content { overflow: hidden; transition: max-height 0.3s ease; @@ -528,170 +615,9 @@ export function getMessageAreaStyles(): string { border-radius: 4px; font-size: 12px;} - /* 智能体卡片样式 */ - .segment-agent { - margin: 8px 0; - } - .agent-card { - border: 1px solid var(--vscode-input-border); - border-radius: 8px; - overflow: hidden; - background: var(--vscode-editor-background); - } - .agent-header { - display: flex; - align-items: center; - gap: 8px; - padding: 8px 12px; - background: var(--vscode-sideBar-background); - border-bottom: 1px solid var(--vscode-input-border); - } - .agent-icon { - font-size: 16px; - } - .agent-name { - font-weight: 500; - flex: 1; - } - .agent-status { - font-size: 11px; - padding: 2px 8px; - border-radius: 10px; - } - .agent-status.running { - background: var(--vscode-inputValidation-infoBackground); - color: var(--vscode-inputValidation-infoForeground); - } - .agent-status.completed { - background: #28a745; - color: white; - } - .agent-status.error { - background: #dc3545; - color: white; - } - .agent-body { - padding: 8px; - } - .agent-steps-container { - max-height: 150px; - overflow-y: auto; - font-size: 12px; - } - .agent-step { - display: flex; - align-items: center; - gap: 6px; - padding: 4px 8px; - border-radius: 4px; - margin-bottom: 4px; - background: var(--vscode-list-hoverBackground); - } - .agent-step:last-child { - margin-bottom: 0; - } - .step-icon { - flex-shrink: 0; - } - .step-name { - font-weight: 500; - color: var(--vscode-foreground); - } - .step-result { - color: var(--vscode-descriptionForeground); - font-size: 11px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - .agent-step-placeholder { - color: var(--vscode-descriptionForeground); - font-style: italic; - padding: 8px; - text-align: center; - } + ${getAgentCardStyles()} - /* 计划卡片样式 */ - .segment-plan { - margin: 8px 0; - } - .plan-card { - border: 1px solid var(--vscode-input-border); - border-radius: 8px; - overflow: hidden; - background: var(--vscode-editor-background); - } - .plan-header { - display: flex; - align-items: center; - gap: 8px; - padding: 10px 12px; - background: var(--vscode-sideBar-background); - border-bottom: 1px solid var(--vscode-input-border); - } - .plan-icon { - font-size: 18px; - } - .plan-title { - font-weight: 600; - font-size: 14px; - } - .plan-body { - padding: 12px; - } - .plan-summary { - color: var(--vscode-descriptionForeground); - margin-bottom: 10px; - font-size: 13px; - } - .plan-steps { - font-size: 13px; - } - .plan-step { - padding: 6px 8px; - margin-bottom: 4px; - background: var(--vscode-list-hoverBackground); - border-radius: 4px; - } - .plan-step:last-child { - margin-bottom: 0; - } - .step-num { - color: var(--vscode-textLink-foreground); - font-weight: 500; - margin-right: 4px; - } - .plan-actions { - display: flex; - gap: 8px; - padding: 12px; - border-top: 1px solid var(--vscode-input-border); - background: var(--vscode-sideBar-background); - } - .plan-btn { - padding: 6px 16px; - border-radius: 4px; - border: none; - cursor: pointer; - font-size: 12px; - font-weight: 500; - } - .plan-btn-confirm { - background: var(--vscode-button-background); - color: var(--vscode-button-foreground); - } - .plan-btn-confirm:hover { - background: var(--vscode-button-hoverBackground); - } - .plan-btn-modify { - background: var(--vscode-input-background); - color: var(--vscode-foreground); - border: 1px solid var(--vscode-input-border); - } - .plan-btn-cancel { - background: transparent; - color: var(--vscode-descriptionForeground); - } + ${getPlanCardStyles()} ${getWaveformPreviewContent()} `; @@ -705,18 +631,75 @@ export function getMessageAreaScript(): string { // 工具图标定义 const collapseIconSvg = \`${collapseIconSvg}\`; const fileWriteIconSvg = \`${fileWriteIconSvg}\`; + const fileReadIconSvg = \`${fileReadIconSvg}\`; + const fileDeleteIconSvg = \`${fileDeleteIconSvg}\`; const syntaxCheckIconSvg = \`${syntaxCheckIconSvg}\`; const searchCodeIconSvg = \`${SearchCode}\`; + const saveKnowledgeIconSvg = \`${saveKnowledgeIconSvg}\`; + const simulationIconSvg = \`${simulationIconSvg}\`; + const waveformIconSvg = \`${waveformIconSvg}\`; + const knowledgeLoadIconSvg = \`${knowledgeLoadIconSvg}\`; + const stateTransitionIconSvg = \`${stateTransitionIconSvg}\`; + + ${getAgentCardScript()} + + ${getPlanCardScript()} + + // 获取工具图标 + function getToolIcon(toolName) { + const iconMap = { + 'file_read': fileReadIconSvg, + 'file_write': fileWriteIconSvg, + 'file_delete': fileDeleteIconSvg, + 'file_list': searchCodeIconSvg, + 'syntax_check': syntaxCheckIconSvg, + 'simulation': simulationIconSvg, + 'waveform_summary': waveformIconSvg, + 'knowledge_save': saveKnowledgeIconSvg, + 'knowledge_load': knowledgeLoadIconSvg, + 'queryKnowledgeSummary': knowledgeLoadIconSvg, + 'queryRules': knowledgeLoadIconSvg, + 'setModule': fileWriteIconSvg, + 'addSignal': fileWriteIconSvg, + 'addSignalExample': fileWriteIconSvg, + 'validateKnowledgeGraph': syntaxCheckIconSvg, + 'querySignals': searchCodeIconSvg, + 'addPlan': fileWriteIconSvg, + 'addEdge': fileWriteIconSvg, + 'showPlan': searchCodeIconSvg, + 'addRule': fileWriteIconSvg, + 'updateNode': fileWriteIconSvg, + 'addStateTransition': stateTransitionIconSvg + }; + return iconMap[toolName] || ''; + } // 工具名称映射 function getToolDisplayName(toolName) { const toolNameMap = { 'file_read': '已完成文件读取', 'file_write': '已完成文件写入', + 'file_delete': '已完成文件删除', 'file_list': '已检索代码文件', 'syntax_check': '已完成语法检查', 'simulation': '已完成仿真', - 'waveform_summary': '已完成波形分析' + 'waveform_summary': '已完成波形分析', + 'knowledge_save': '已保存知识库', + 'knowledge_load': '已加载知识库', + 'queryKnowledgeSummary': '已查询知识摘要', + 'queryRules': '已查询规则', + 'setModule': '已设置模块', + 'addSignal': '已添加信号', + 'addSignalExample': '已添加信号示例', + 'validateKnowledgeGraph': '已验证知识图谱', + 'querySignals': '已查询信号', + 'addPlan': '已添加计划', + 'addEdge': '已添加边', + 'showPlan': '已显示计划', + 'addRule': '已添加规则', + 'updateNode': '已更新节点', + 'addStateTransition': '已添加状态转换', + 'spawnExplorer': '代码探索' }; return toolNameMap[toolName] || toolName; } @@ -949,6 +932,10 @@ export function getMessageAreaScript(): string { segmentDiv.className += ' segment-text'; segmentDiv.innerHTML = formatText(segment.content); } else if (segment.type === 'tool') { + // 过滤掉不需要显示的工具 + if (segment.toolName === 'spawnExplorer') { + return; + } const statusIcon = segment.toolStatus === 'error' ? '❌' : '🔧'; const toolResult = segment.toolResult || ''; @@ -957,10 +944,7 @@ export function getMessageAreaScript(): string { segmentDiv.innerHTML = \` - \${shouldCollapse ? collapseIconSvg : ''} - \${!shouldCollapse && segment.toolStatus === 'success' && segment.toolName === 'file_write' ? fileWriteIconSvg : ''} - \${!shouldCollapse && segment.toolStatus === 'success' && segment.toolName === 'syntax_check' ? syntaxCheckIconSvg : ''} - \${!shouldCollapse && segment.toolStatus === 'success' && segment.toolName === 'file_list' ? searchCodeIconSvg : ''} + \${shouldCollapse ? collapseIconSvg : getToolIcon(segment.toolName)} \${getToolDisplayName(segment.toolName) || '工具'} \${toolResult && !shouldCollapse ? \`\${toolResult}\` : ''} @@ -1083,126 +1067,10 @@ export function getMessageAreaScript(): string { } } else if (segment.type === 'agent') { // 智能体卡片渲染 - segmentDiv.className += ' segment-agent'; - const statusText = segment.agentStatus === 'completed' ? '完成' - : segment.agentStatus === 'error' ? '错误' : '执行中'; - const statusClass = segment.agentStatus || 'running'; - - const stepsHtml = (segment.agentSteps || []).map(step => { - const icon = step.status === 'completed' ? '✅' : step.status === 'error' ? '❌' : '🔄'; - const result = step.toolResult ? \`: \${step.toolResult.substring(0, 50)}\${step.toolResult.length > 50 ? '...' : ''}\` : ''; - return \`\${icon}\${step.toolName}\${result}\`; - }).join(''); - - segmentDiv.innerHTML = \` - - - 🤖 - \${segment.agentName || '智能体'} - \${statusText} - - - - \${stepsHtml || '等待执行...'} - - - - \`; - - // 自动滚动到最新步骤 - setTimeout(() => { - const container = segmentDiv.querySelector('.agent-steps-container'); - if (container) { - container.scrollTop = container.scrollHeight; - } - }, 0); + renderAgentCard(segment, segmentDiv); } else if (segment.type === 'plan') { - // 计划卡片渲染(类似 askUser) - segmentDiv.className += ' segment-plan'; - - // 检查是否已回答 - const isAnswered = answeredQuestions.has(segment.askId); - const selectedAnswer = answeredQuestions.get(segment.askId); - - if (isAnswered) { - segmentDiv.classList.add('answered'); - } - - const stepsHtml = (segment.planSteps || []).map((step, i) => - \`\${i + 1}. \${step}\` - ).join(''); - - // 选项按钮 - const options = ['确认执行', '修改计划', '取消']; - const optionsHtml = options.map(opt => { - const isSelected = isAnswered && opt === selectedAnswer; - return \`\${opt}\`; - }).join(''); - - segmentDiv.innerHTML = \` - - - 📋 - \${segment.planTitle || '执行计划'} - - - \${segment.planSummary || ''} - \${stepsHtml} - - - \${optionsHtml} - - - 提交 - - - - \`; - - // 只在未回答时添加事件监听 - if (!isAnswered) { - setTimeout(() => { - const optionButtons = segmentDiv.querySelectorAll('.question-option'); - optionButtons.forEach(btn => { - btn.addEventListener('click', function() { - const option = this.getAttribute('data-option'); - // 发送答案到后端 - handleQuestionAnswerInSegment(segment.askId, option, segmentDiv); - // 同时发送 planAction 用于模式切换 - const actionMap = { - '确认执行': 'confirm', - '修改计划': 'modify', - '取消': 'cancel' - }; - vscode.postMessage({ - command: 'planAction', - action: actionMap[option] || option, - planTitle: segment.planTitle - }); - }); - }); - - const submitBtn = segmentDiv.querySelector('.custom-submit'); - const customInput = segmentDiv.querySelector('.custom-input'); - if (submitBtn && customInput) { - submitBtn.addEventListener('click', function() { - const customValue = customInput.value.trim(); - if (customValue) { - handleQuestionAnswerInSegment(segment.askId, customValue, segmentDiv); - } - }); - - customInput.addEventListener('keypress', function(e) { - if (e.key === 'Enter') { - const customValue = customInput.value.trim(); - if (customValue) { - handleQuestionAnswerInSegment(segment.askId, customValue, segmentDiv); - } - } - }); - } - }, 0); - } + // 计划卡片渲染(使用独立组件) + renderPlanCardInSegment(segment, segmentDiv, answeredQuestions); } currentSegmentedMessage.appendChild(segmentDiv); @@ -1269,6 +1137,10 @@ export function getMessageAreaScript(): string { segmentDiv.className += ' segment-text'; segmentDiv.innerHTML = formatText(segment.content); } else if (segment.type === 'tool') { + // 过滤掉不需要显示的工具 + if (segment.toolName === 'spawnExplorer') { + return; + } const statusIcon = segment.toolStatus === 'error' ? '❌' : '🔧'; const toolResult = segment.toolResult || ''; @@ -1277,10 +1149,7 @@ export function getMessageAreaScript(): string { segmentDiv.innerHTML = \` - \${shouldCollapse ? collapseIconSvg : ''} - \${!shouldCollapse && segment.toolStatus === 'success' && segment.toolName === 'file_write' ? fileWriteIconSvg : ''} - \${!shouldCollapse && segment.toolStatus === 'success' && segment.toolName === 'syntax_check' ? syntaxCheckIconSvg : ''} - \${!shouldCollapse && segment.toolStatus === 'success' && segment.toolName === 'file_list' ? searchCodeIconSvg : ''} + \${shouldCollapse ? collapseIconSvg : getToolIcon(segment.toolName)} \${getToolDisplayName(segment.toolName) || '工具'} \${toolResult && !shouldCollapse ? \`\${toolResult}\` : ''} @@ -1347,72 +1216,10 @@ export function getMessageAreaScript(): string { \`; } else if (segment.type === 'agent') { // 智能体卡片渲染 - segmentDiv.className += ' segment-agent'; - const statusText = segment.agentStatus === 'completed' ? '完成' - : segment.agentStatus === 'error' ? '错误' : '执行中'; - const statusClass = segment.agentStatus || 'running'; - - const stepsHtml = (segment.agentSteps || []).map(step => { - const icon = step.status === 'completed' ? '✅' : step.status === 'error' ? '❌' : '🔄'; - const result = step.toolResult ? \`: \${step.toolResult.substring(0, 50)}\${step.toolResult.length > 50 ? '...' : ''}\` : ''; - return \`\${icon}\${step.toolName}\${result}\`; - }).join(''); - - segmentDiv.innerHTML = \` - - - 🤖 - \${segment.agentName || '智能体'} - \${statusText} - - - - \${stepsHtml || '等待执行...'} - - - - \`; + renderAgentCard(segment, segmentDiv); } else if (segment.type === 'plan') { - // 计划卡片渲染 - segmentDiv.className += ' segment-plan'; - const stepsHtml = (segment.planSteps || []).map((step, i) => - \`\${i + 1}. \${step}\` - ).join(''); - - segmentDiv.innerHTML = \` - - - 📋 - \${segment.planTitle || '执行计划'} - - - \${segment.planSummary || ''} - \${stepsHtml} - - - 确认执行 - 修改计划 - 取消 - - - \`; - - // 绑定按钮事件 - setTimeout(() => { - const planCard = segmentDiv.querySelector('.plan-card'); - if (planCard) { - planCard.querySelectorAll('.plan-btn').forEach(btn => { - btn.addEventListener('click', (e) => { - const action = e.currentTarget?.dataset?.action; - vscode.postMessage({ - command: 'planAction', - action: action, - planTitle: segment.planTitle - }); - }); - }); - } - }, 0); + // 计划卡片渲染(使用独立组件) + renderPlanCardStatic(segment, segmentDiv); } container.appendChild(segmentDiv); diff --git a/src/views/planCard.ts b/src/views/planCard.ts new file mode 100644 index 0000000..6f2213a --- /dev/null +++ b/src/views/planCard.ts @@ -0,0 +1,273 @@ +/** + * 计划卡片组件 + * + * 功能说明: + * - 显示执行计划的卡片界面 + * - 包含计划标题、摘要和步骤列表 + * - 提供确认执行、修改计划、取消等操作按钮 + */ + +import { plannerIconSvg } from "../constants/toolIcons"; + +/** + * 获取计划卡片的样式 + */ +export function getPlanCardStyles(): string { + return ` + /* 计划卡片样式 */ + .segment-plan { + margin: 12px 0; + } + .plan-card { + border: 1px solid var(--vscode-input-border); + border-radius: 8px; + overflow: hidden; + background: var(--vscode-editor-background); + } + .plan-header { + display: flex; + align-items: center; + gap: 10px; + padding: 12px 16px; + background: var(--vscode-sideBar-background); + border-bottom: 1px solid var(--vscode-input-border); + } + .plan-icon { + font-size: 18px; + } + .plan-title { + font-weight: 600; + font-size: 14px; + } + .plan-body { + padding: 16px; + } + .plan-summary { + color: var(--vscode-descriptionForeground); + margin-bottom: 12px; + font-size: 13px; + line-height: 1.5; + } + .plan-steps { + font-size: 13px; + } + .plan-step { + padding: 8px 12px; + margin-bottom: 6px; + background: var(--vscode-list-hoverBackground); + border-radius: 4px; + line-height: 1.5; + } + .plan-step:last-child { + margin-bottom: 0; + } + .step-num { + color: var(--vscode-textLink-foreground); + font-weight: 500; + margin-right: 6px; + } + .plan-actions { + display: flex; + flex-direction: column; + gap: 10px; + padding: 14px 16px; + border-top: 1px solid var(--vscode-input-border); + background: var(--vscode-sideBar-background); + } + .plan-actions .question-options { + display: flex; + flex-wrap: wrap; + gap: 8px; + } + .plan-btn { + padding: 8px 18px; + border-radius: 4px; + border: none; + cursor: pointer; + font-size: 12px; + font-weight: 500; + } + .plan-btn-confirm { + background: var(--vscode-button-background); + color: var(--vscode-button-foreground); + } + .plan-btn-confirm:hover { + background: var(--vscode-button-hoverBackground); + } + .plan-btn-modify { + background: var(--vscode-input-background); + color: var(--vscode-foreground); + border: 1px solid var(--vscode-input-border); + } + .plan-btn-cancel { + background: transparent; + color: var(--vscode-descriptionForeground); + } + .plan-actions .custom-input-container { + display: flex; + gap: 8px; + width: 100%; + } + .plan-actions .custom-input { + flex: 1; + padding: 8px 12px; + background: var(--vscode-input-background); + color: var(--vscode-input-foreground); + border: 1px solid var(--vscode-input-border); + border-radius: 4px; + font-size: 13px; + } + .plan-actions .custom-submit { + padding: 8px 18px; + background: var(--vscode-button-background); + color: var(--vscode-button-foreground); + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 12px; + font-weight: 500; + } + .plan-actions .custom-submit:hover { + background: var(--vscode-button-hoverBackground); + } + `; +} + +/** + * 获取计划卡片的脚本 + */ +export function getPlanCardScript(): string { + return ` + // 渲染计划卡片(在 updateSegmentsRealtime 中使用) + function renderPlanCardInSegment(segment, segmentDiv, answeredQuestions) { + segmentDiv.className += ' segment-plan'; + + // 检查是否已回答 + const isAnswered = answeredQuestions.has(segment.askId); + const selectedAnswer = answeredQuestions.get(segment.askId); + + if (isAnswered) { + segmentDiv.classList.add('answered'); + } + + const stepsHtml = (segment.planSteps || []).map((step, i) => + \`\${i + 1}. \${step}\` + ).join(''); + + // 选项按钮 + const options = ['确认执行', '修改计划', '取消']; + const optionsHtml = options.map(opt => { + const isSelected = isAnswered && opt === selectedAnswer; + return \`\${opt}\`; + }).join(''); + + segmentDiv.innerHTML = \` + + + ${plannerIconSvg} + \${segment.planTitle || '执行计划'} + + + \${segment.planSummary || ''} + \${stepsHtml} + + + \${optionsHtml} + + + 提交 + + + + \`; + + // 只在未回答时添加事件监听 + if (!isAnswered) { + setTimeout(() => { + const optionButtons = segmentDiv.querySelectorAll('.question-option'); + optionButtons.forEach(btn => { + btn.addEventListener('click', function() { + const option = this.getAttribute('data-option'); + // 发送答案到后端 + handleQuestionAnswerInSegment(segment.askId, option, segmentDiv); + // 同时发送 planAction 用于模式切换 + const actionMap = { + '确认执行': 'confirm', + '修改计划': 'modify', + '取消': 'cancel' + }; + vscode.postMessage({ + command: 'planAction', + action: actionMap[option] || option, + planTitle: segment.planTitle + }); + }); + }); + + const submitBtn = segmentDiv.querySelector('.custom-submit'); + const customInput = segmentDiv.querySelector('.custom-input'); + if (submitBtn && customInput) { + submitBtn.addEventListener('click', function() { + const customValue = customInput.value.trim(); + if (customValue) { + handleQuestionAnswerInSegment(segment.askId, customValue, segmentDiv); + } + }); + + customInput.addEventListener('keypress', function(e) { + if (e.key === 'Enter') { + const customValue = customInput.value.trim(); + if (customValue) { + handleQuestionAnswerInSegment(segment.askId, customValue, segmentDiv); + } + } + }); + } + }, 0); + } + } + + // 渲染计划卡片(在 renderSegments 中使用) + function renderPlanCardStatic(segment, segmentDiv) { + segmentDiv.className += ' segment-plan'; + const stepsHtml = (segment.planSteps || []).map((step, i) => + \`\${i + 1}. \${step}\` + ).join(''); + + segmentDiv.innerHTML = \` + + + 📋 + \${segment.planTitle || '执行计划'} + + + \${segment.planSummary || ''} + \${stepsHtml} + + + 确认执行 + 修改计划 + 取消 + + + \`; + + // 绑定按钮事件 + setTimeout(() => { + const planCard = segmentDiv.querySelector('.plan-card'); + if (planCard) { + planCard.querySelectorAll('.plan-btn').forEach(btn => { + btn.addEventListener('click', (e) => { + const action = e.currentTarget?.dataset?.action; + vscode.postMessage({ + command: 'planAction', + action: action, + planTitle: segment.planTitle + }); + }); + }); + } + }, 0); + } + `; +} diff --git a/src/views/waveformPreviewContent.ts b/src/views/waveformPreviewContent.ts index 6c98a77..9e20df4 100644 --- a/src/views/waveformPreviewContent.ts +++ b/src/views/waveformPreviewContent.ts @@ -263,61 +263,63 @@ export function getWaveformPreviewScript(): string { const timeRange = maxTime - minTime || 1; // 绘制波形 - let pathData = ''; - let lastX = leftMargin; - let lastValue = signal.values[0].value; + if (signal.width === 1) { + // 单比特信号 - 绘制数字波形 + let pathData = ''; + const yHigh = y; + const yLow = y + signalHeight; - signal.values.forEach((point, i) => { - const x = leftMargin + ((point.time - minTime) / timeRange) * waveformWidth; - const value = point.value; - - if (signal.width === 1) { - // 单比特信号 - 绘制数字波形 - const yHigh = y; - const yLow = y + signalHeight; - const currentY = (value === '1') ? yHigh : yLow; + signal.values.forEach((point, i) => { + const x = leftMargin + ((point.time - minTime) / timeRange) * waveformWidth; + const currentY = (point.value === '1') ? yHigh : yLow; if (i === 0) { pathData = \`M \${x} \${currentY}\`; } else { - // 绘制垂直跳变 - const prevY = (lastValue === '1') ? yHigh : yLow; + const prevValue = signal.values[i - 1].value; + const prevY = (prevValue === '1') ? yHigh : yLow; if (prevY !== currentY) { pathData += \` L \${x} \${prevY} L \${x} \${currentY}\`; } else { pathData += \` L \${x} \${currentY}\`; } } + }); - lastValue = value; - lastX = x; - } else { - // 多比特信号 - 绘制总线波形(梯形) - const yTop = y + 5; - const yBottom = y + signalHeight - 5; - const transitionWidth = 5; - - if (i === 0) { - pathData = \`M \${x} \${yTop + (yBottom - yTop) / 2}\`; - } else { - // 绘制梯形过渡 - pathData += \` L \${x - transitionWidth} \${yTop} L \${x} \${yTop + (yBottom - yTop) / 2}\`; - } - - lastX = x; - } - }); - - // 延伸到右边界 - if (signal.width === 1) { - const lastY = (lastValue === '1') ? y : (y + signalHeight); + // 延伸到右边界 + const lastValue = signal.values[signal.values.length - 1].value; + const lastY = (lastValue === '1') ? yHigh : yLow; pathData += \` L \${leftMargin + waveformWidth} \${lastY}\`; - } else { - const yMid = y + signalHeight / 2; - pathData += \` L \${leftMargin + waveformWidth} \${yMid}\`; - } - svgContent += \`\`; + svgContent += \`\`; + } else { + // 多比特信号 - 绘制总线波形(上下双线) + const yTop = y + 5; + const yBottom = y + signalHeight - 5; + const transitionWidth = 4; + + let topPath = \`M \${leftMargin} \${yTop}\`; + let bottomPath = \`M \${leftMargin} \${yBottom}\`; + + signal.values.forEach((point, i) => { + const x = leftMargin + ((point.time - minTime) / timeRange) * waveformWidth; + + // 上线和下线都延伸到变化点 + topPath += \` L \${x} \${yTop}\`; + bottomPath += \` L \${x} \${yBottom}\`; + + // 绘制梯形过渡 + topPath += \` L \${x + transitionWidth} \${yBottom} L \${x + transitionWidth} \${yTop}\`; + bottomPath += \` L \${x + transitionWidth} \${yTop} L \${x + transitionWidth} \${yBottom}\`; + }); + + // 延伸到右边界 + topPath += \` L \${leftMargin + waveformWidth} \${yTop}\`; + bottomPath += \` L \${leftMargin + waveformWidth} \${yBottom}\`; + + svgContent += \`\`; + svgContent += \`\`; + } }); // 绘制时间轴 diff --git a/src/views/webviewContent.ts b/src/views/webviewContent.ts index beb7c67..691492a 100644 --- a/src/views/webviewContent.ts +++ b/src/views/webviewContent.ts @@ -499,8 +499,6 @@ export function getWebviewContent(iconUri?: string): string { } } - messageInput.focus(); - // 监听来自插件的消息 window.addEventListener('message', event => { const message = event.data; @@ -572,6 +570,14 @@ export function getWebviewContent(iconUri?: string): string { hideLoadingIndicator(); break; + case 'workspaceStatus': + // 更新工作区状态 + if (typeof hasWorkspace !== 'undefined') { + hasWorkspace = message.hasWorkspace; + console.log('[WebView] 工作区状态:', hasWorkspace); + } + break; + case 'vcdInfo': // 渲染迷你波形预览信息 try {