/** * 用户交互处理器 * 处理 ask_user 事件,通过 WebView 显示问题并收集用户回答 */ import * as vscode from 'vscode'; import { submitAnswer, submitToolConfirm } from './apiClient'; import type { AskUserEvent, AnswerRequest, QuestionItem } from '../types/api'; /** * 待处理的用户问题 */ interface PendingQuestion { askId: string; taskId: string; questions: QuestionItem[]; resolve: (answer: string) => void; reject: (error: Error) => void; } /** * 用户交互管理器 */ export class UserInteractionManager { private pendingQuestions = new Map(); private webviewPanel: vscode.WebviewPanel | null = null; /** * 设置 WebView 面板(用于发送消息) */ setWebviewPanel(panel: vscode.WebviewPanel): void { this.webviewPanel = panel; } /** * 获取 WebView 面板 */ getWebviewPanel(): vscode.WebviewPanel | null { return this.webviewPanel; } /** * 处理 ask_user 事件 * @param event ask_user 事件数据 * @param taskId 当前任务ID */ async handleAskUser(event: AskUserEvent, taskId: string): Promise { const { askId, questions } = event; console.log(`[UserInteraction] 收到问题: askId=${askId}, count=${questions.length}`); // 注意:问题显示已经通过 dialogService 的 onSegmentUpdate 统一处理 // 这里不再单独发送 showQuestion 命令,避免重复显示 // 创建 Promise 等待用户回答 return new Promise((resolve, reject) => { this.pendingQuestions.set(askId, { askId, taskId, questions, resolve: (answer: string) => { this.submitUserAnswer(askId, taskId, answer) .then(() => resolve()) .catch(reject); }, reject }); // 设置超时(2小时) setTimeout(() => { if (this.pendingQuestions.has(askId)) { this.pendingQuestions.delete(askId); reject(new Error('用户回答超时')); } }, 7200000); }); } /** * 处理用户提交的回答(从 WebView 调用) * @param askId 问题ID * @param selected 选中的选项(旧格式) * @param customInput 自定义输入(旧格式) * @param answers 新格式:按问题索引的答案 * @param fallbackTaskId 当问题不存在时使用的 taskId(用于直接发送到后端) */ async receiveAnswer( askId: string, selected?: string[], customInput?: string, answers?: { [questionIndex: string]: string[] }, fallbackTaskId?: string ): Promise { const pending = this.pendingQuestions.get(askId); // 构建答案字符串 let answer = ''; if (answers && Object.keys(answers).length > 0) { // 新格式:多问题答案 answer = Object.entries(answers) .sort(([a], [b]) => parseInt(a) - parseInt(b)) .map(([_, vals]) => vals.join('; ')) .join(' | '); } else { // 旧格式:单问题答案 answer = customInput || selected?.join(', ') || ''; } if (!pending) { // 问题不存在(可能是页面刷新或会话切换后),尝试直接发送到后端 if (fallbackTaskId) { console.log(`[UserInteraction] 问题不在 pendingQuestions 中,直接发送到后端: askId=${askId}, taskId=${fallbackTaskId}`); await this.submitUserAnswer(askId, fallbackTaskId, answer, answers); } else { console.warn(`[UserInteraction] 问题不存在且无 fallbackTaskId: askId=${askId}`); } return; } console.log(`[UserInteraction] 收到用户回答: askId=${askId}, answer=${answer}`); // 移除待处理问题 this.pendingQuestions.delete(askId); // 触发 resolve pending.resolve(answer); } /** * 提交用户回答到后端 */ private async submitUserAnswer( askId: string, taskId: string, answer: string, answers?: { [questionIndex: string]: string[] } ): Promise { // 检查是否是工具确认类型的问题 if (askId.startsWith('tool_confirm_')) { // 提取 confirmId const confirmId = parseInt(askId.replace('tool_confirm_', '')); const approved = answer === '确认执行'; 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, answers: answers, customInput: answers ? undefined : 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; } } } /** * 取消所有待处理的问题 */ cancelAll(): void { for (const [askId, pending] of this.pendingQuestions) { pending.reject(new Error('用户交互已取消')); } this.pendingQuestions.clear(); } /** * 检查是否有待处理的问题 */ hasPendingQuestions(): boolean { return this.pendingQuestions.size > 0; } /** * 检查特定问题是否存在 */ hasPendingQuestion(askId: string): boolean { return this.pendingQuestions.has(askId); } } // 全局实例 export const userInteractionManager = new UserInteractionManager();