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()}