diff --git a/package.json b/package.json index c6a416a..9f6fe4f 100644 --- a/package.json +++ b/package.json @@ -70,27 +70,7 @@ "id": "iccoder", "label": "IC Coder" } - ], - "configuration": { - "title": "IC Coder", - "properties": { - "icCoder.backendUrl": { - "type": "string", - "default": "http://192.168.1.108:2233", - "description": "后端服务地址" - }, - "icCoder.timeout": { - "type": "number", - "default": 60000, - "description": "请求超时时间(毫秒)" - }, - "icCoder.userId": { - "type": "string", - "default": "default-user", - "description": "用户ID(临时配置)" - } - } - } + ] }, "scripts": { "vscode:prepublish": "pnpm run package", diff --git a/src/config/settings.ts b/src/config/settings.ts index 8b6e490..15ce17d 100644 --- a/src/config/settings.ts +++ b/src/config/settings.ts @@ -1,6 +1,6 @@ /** * 配置管理 - * 从 VSCode 设置读取配置项 + * 后端地址已预配置,用户无需手动设置 */ import * as vscode from "vscode"; @@ -14,7 +14,7 @@ export interface IccoderConfig { userId: string; } -/** 默认配置 */ +/** 默认配置(预配置,不暴露给用户) */ const DEFAULT_CONFIG: IccoderConfig = { backendUrl: "http://192.168.1.108:2233", timeout: 60000, @@ -23,15 +23,10 @@ const DEFAULT_CONFIG: IccoderConfig = { /** * 获取配置项 + * 直接返回预配置的值,用户无需手动配置 */ export function getConfig(): IccoderConfig { - const config = vscode.workspace.getConfiguration("icCoder"); - - return { - backendUrl: config.get("backendUrl", DEFAULT_CONFIG.backendUrl), - timeout: config.get("timeout", DEFAULT_CONFIG.timeout), - userId: config.get("userId", DEFAULT_CONFIG.userId), - }; + return { ...DEFAULT_CONFIG }; } /** diff --git a/src/services/dialogService.ts b/src/services/dialogService.ts index 69aac6d..aad42ec 100644 --- a/src/services/dialogService.ts +++ b/src/services/dialogService.ts @@ -434,30 +434,64 @@ export class DialogSession { onToolConfirm: async (data: ToolConfirmEvent) => { console.log('[DialogSession] onToolConfirm:', data.toolName, data.confirmId); - // 调用回调通知 UI 显示确认对话框 + // 结束当前文本段落 + this.finalizeTextSegment(); + + // 生成工具描述 + const toolDescription = this.getToolDescription(data.toolName, data.toolInput); + + // 构建问题文本 + const toolNameMap: Record = { + 'file_write': '写入文件', + 'file_delete': '删除文件', + 'syntax_check': '语法检查', + 'simulation': '运行仿真' + }; + const toolDisplayName = toolNameMap[data.toolName] || data.toolName; + const question = `确认执行操作:${toolDisplayName}\n\n${toolDescription}`; + + // 生成唯一的 askId + const askId = `tool_confirm_${data.confirmId}`; + + // 添加问题段落到聊天界面 + this.segments.push({ + type: 'question', + askId: askId, + question: question, + options: ['确认执行', '取消'] + }); + + // 实时发送段落更新 + callbacks.onSegmentUpdate?.(this.segments); + + // 调用回调通知 UI callbacks.onToolConfirm?.(data.confirmId, data.toolName, data.toolInput); - // 使用 VSCode 快速选择框显示确认对话框 - const toolDescription = this.getToolDescription(data.toolName, data.toolInput); - const result = await vscode.window.showWarningMessage( - `确认执行操作: ${data.toolName}`, - { modal: true, detail: toolDescription }, - '确认执行', - '取消' - ); - - const approved = result === '确认执行'; - console.log('[DialogSession] 用户确认结果:', approved); - - // 发送确认响应到后端 + // 使用 userInteractionManager 等待用户回答 try { - await submitToolConfirm({ - confirmId: data.confirmId, - taskId: this.taskId, - approved - }); + await userInteractionManager.handleAskUser( + { + askId: askId, + question: question, + options: ['确认执行', '取消'] + } as AskUserEvent, + this.taskId + ); + + // 注意:用户回答后,需要在 receiveAnswer 中处理 tool_confirm 类型的 askId + // 这里不直接调用 submitToolConfirm,而是在 userInteractionManager 中统一处理 } catch (error) { - console.error('[DialogSession] 发送确认响应失败:', error); + console.error('[DialogSession] 处理工具确认失败:', error); + // 如果出错,默认取消执行 + try { + await submitToolConfirm({ + confirmId: data.confirmId, + taskId: this.taskId, + approved: false + }); + } catch (submitError) { + console.error('[DialogSession] 发送取消响应失败:', submitError); + } } }, diff --git a/src/services/userInteraction.ts b/src/services/userInteraction.ts index ff1dcfb..e14d192 100644 --- a/src/services/userInteraction.ts +++ b/src/services/userInteraction.ts @@ -3,7 +3,7 @@ * 处理 ask_user 事件,通过 WebView 显示问题并收集用户回答 */ import * as vscode from 'vscode'; -import { submitAnswer } from './apiClient'; +import { submitAnswer, submitToolConfirm } from './apiClient'; import type { AskUserEvent, AnswerRequest } from '../types/api'; /** @@ -114,21 +114,46 @@ export class UserInteractionManager { taskId: string, answer: string ): Promise { - const request: AnswerRequest = { - askId, - taskId, - customInput: answer - }; + // 检查是否是工具确认类型的问题 + if (askId.startsWith('tool_confirm_')) { + // 提取 confirmId + const confirmId = parseInt(askId.replace('tool_confirm_', '')); + const approved = answer === '确认执行'; - try { - const response = await submitAnswer(request); - if (!response.success) { - throw new Error(response.error || '提交回答失败'); + console.log(`[UserInteraction] 提交工具确认: confirmId=${confirmId}, approved=${approved}`); + + try { + const response = await submitToolConfirm({ + confirmId, + taskId, + approved + }); + if (!response.success) { + throw new Error(response.error || '提交工具确认失败'); + } + console.log(`[UserInteraction] 工具确认已提交: confirmId=${confirmId}`); + } catch (error) { + console.error(`[UserInteraction] 提交工具确认失败: confirmId=${confirmId}`, error); + throw error; + } + } else { + // 普通问题回答 + const request: AnswerRequest = { + askId, + taskId, + customInput: answer + }; + + try { + const response = await submitAnswer(request); + if (!response.success) { + throw new Error(response.error || '提交回答失败'); + } + console.log(`[UserInteraction] 回答已提交: askId=${askId}`); + } catch (error) { + console.error(`[UserInteraction] 提交回答失败: askId=${askId}`, error); + throw error; } - console.log(`[UserInteraction] 回答已提交: askId=${askId}`); - } catch (error) { - console.error(`[UserInteraction] 提交回答失败: askId=${askId}`, error); - throw error; } } diff --git a/src/views/agentModeSelector.ts b/src/views/agentModeSelector.ts index fbb298b..272ab4c 100644 --- a/src/views/agentModeSelector.ts +++ b/src/views/agentModeSelector.ts @@ -1,12 +1,11 @@ /** * 模式选择器组件 - * 提供 Plan/Ask/Agent/Auto 四种模式的选择功能 + * 提供 Plan/Ask/Agent 四种模式的选择功能 * * 模式说明: * - Plan: 只读模式,只能查询分析,不能写文件 * - Ask: 逐个确认,每个写操作需用户确认 * - Agent: 智能体自主,自动执行大部分操作 - * - Auto: 完全自动,所有操作自动执行 */ /** @@ -35,10 +34,6 @@ export function getModeSelectorContent(): string { Agent 智能体自主 -
- Auto - 完全自动 -
智能体自主模式 @@ -164,8 +159,7 @@ export function getModeSelectorScript(): string { const tooltipMap = { 'plan': '只读模式 - 只能查询分析', 'ask': '逐个确认 - 每个写操作需确认', - 'agent': '智能体自主模式', - 'auto': '完全自动 - 所有操作自动执行' + 'agent': '智能体自主模式',' }; modeTooltip.textContent = tooltipMap[value] || '切换模式'; } diff --git a/src/views/messageArea.ts b/src/views/messageArea.ts index 498710b..3466583 100644 --- a/src/views/messageArea.ts +++ b/src/views/messageArea.ts @@ -410,7 +410,7 @@ export function getMessageAreaStyles(): string { display: block; } .tool-segment-header.collapsed .tool-collapse-icon { - transform: rotate(0deg); + transform: rotate(-90deg); } .tool-segment-header:not(.collapsed) .tool-collapse-icon { transform: rotate(0deg); @@ -890,6 +890,9 @@ export function getMessageAreaScript(): string { // 存储已回答问题的状态 const answeredQuestions = new Map(); // askId -> answer + // 存储工具展开/折叠状态 + const toolCollapseStates = new Map(); // index -> isCollapsed + // 实时更新分段消息(按后端返回顺序) function updateSegmentsRealtime(segments, isComplete) { console.log('[WebView] updateSegmentsRealtime 被调用, segments:', segments, 'isComplete:', isComplete); @@ -921,9 +924,19 @@ export function getMessageAreaScript(): string { messagesEl.appendChild(currentSegmentedMessage); } + // 保存当前所有工具的展开/折叠状态 + if (currentSegmentedMessage) { + const toolHeaders = currentSegmentedMessage.querySelectorAll('.tool-segment-header[data-collapsible="true"]'); + toolHeaders.forEach((header, idx) => { + const isCollapsed = header.classList.contains('collapsed'); + toolCollapseStates.set(idx, isCollapsed); + }); + } + // 清空容器并重新渲染所有段落 currentSegmentedMessage.innerHTML = ''; + let toolIndex = 0; // 用于跟踪工具段落的索引 segments.forEach((segment, index) => { const segmentDiv = document.createElement('div'); segmentDiv.className = 'message-segment segment-' + segment.type; @@ -942,13 +955,19 @@ export function getMessageAreaScript(): string { // 检查工具结果是否过长(超过一行显示不下) const shouldCollapse = toolResult && toolResult.length > 60; + // 恢复之前保存的展开/折叠状态 + const savedState = toolCollapseStates.get(toolIndex); + const isCollapsed = savedState !== undefined ? savedState : shouldCollapse; + const currentToolIndex = toolIndex; + toolIndex++; // 递增工具索引 + segmentDiv.innerHTML = \` -
- \${shouldCollapse ? collapseIconSvg : getToolIcon(segment.toolName)} +
+ \${shouldCollapse ? \`\${collapseIconSvg}\` : getToolIcon(segment.toolName)} \${getToolDisplayName(segment.toolName) || '工具'} \${toolResult && !shouldCollapse ? \`\${toolResult}\` : ''}
- \${shouldCollapse ? \`\` : ''} + \${shouldCollapse ? \`
\${toolResult}
\` : ''} \`; // 如果是仿真工具且成功完成,尝试添加波形预览 @@ -974,27 +993,24 @@ export function getMessageAreaScript(): string { setTimeout(() => { const header = segmentDiv.querySelector('.tool-segment-header'); const content = segmentDiv.querySelector('.tool-segment-content'); - const iconCollapsed = segmentDiv.querySelector('.icon-collapsed'); - const iconExpanded = segmentDiv.querySelector('.icon-expanded'); if (header && content) { header.addEventListener('click', function() { const isCollapsed = header.classList.contains('collapsed'); + const toolIdx = parseInt(header.getAttribute('data-tool-index') || '0'); if (isCollapsed) { // 展开 header.classList.remove('collapsed'); content.classList.remove('collapsed'); content.style.maxHeight = content.scrollHeight + 'px'; - if (iconCollapsed) iconCollapsed.style.display = 'none'; - if (iconExpanded) iconExpanded.style.display = 'block'; + toolCollapseStates.set(toolIdx, false); } else { // 折叠 header.classList.add('collapsed'); content.classList.add('collapsed'); content.style.maxHeight = '0'; - if (iconCollapsed) iconCollapsed.style.display = 'block'; - if (iconExpanded) iconExpanded.style.display = 'none'; + toolCollapseStates.set(toolIdx, true); } }); } @@ -1149,7 +1165,7 @@ export function getMessageAreaScript(): string { segmentDiv.innerHTML = \`
- \${shouldCollapse ? collapseIconSvg : getToolIcon(segment.toolName)} + \${shouldCollapse ? \`\${collapseIconSvg}\` : getToolIcon(segment.toolName)} \${getToolDisplayName(segment.toolName) || '工具'} \${toolResult && !shouldCollapse ? \`\${toolResult}\` : ''}
@@ -1179,27 +1195,24 @@ export function getMessageAreaScript(): string { setTimeout(() => { const header = segmentDiv.querySelector('.tool-segment-header'); const content = segmentDiv.querySelector('.tool-segment-content'); - const iconCollapsed = segmentDiv.querySelector('.icon-collapsed'); - const iconExpanded = segmentDiv.querySelector('.icon-expanded'); if (header && content) { header.addEventListener('click', function() { const isCollapsed = header.classList.contains('collapsed'); + const toolIdx = parseInt(header.getAttribute('data-tool-index') || '0'); if (isCollapsed) { // 展开 header.classList.remove('collapsed'); content.classList.remove('collapsed'); content.style.maxHeight = content.scrollHeight + 'px'; - if (iconCollapsed) iconCollapsed.style.display = 'none'; - if (iconExpanded) iconExpanded.style.display = 'block'; + toolCollapseStates.set(toolIdx, false); } else { // 折叠 header.classList.add('collapsed'); content.classList.add('collapsed'); content.style.maxHeight = '0'; - if (iconCollapsed) iconCollapsed.style.display = 'block'; - if (iconExpanded) iconExpanded.style.display = 'none'; + toolCollapseStates.set(toolIdx, true); } }); }