## 主要修改
### dialogService.ts
- 移除 `vscode.window.showWarningMessage` 弹窗
- 将工具确认改为添加 question 类型的 segment
- 使用 `userInteractionManager.handleAskUser` 等待用户回答
- 生成唯一的 askId: `tool_confirm_{confirmId}`
### userInteraction.ts
- 导入 `submitToolConfirm` 方法
- 在 `submitUserAnswer` 中识别工具确认类型的 askId
- 根据用户选择("确认执行" / "取消")调用对应的 API
## 用户体验改进
- 工具确认问题自然融入对话流程
- 用户可以看到历史确认记录
- 非阻塞式交互,体验更流畅
173 lines
4.6 KiB
TypeScript
173 lines
4.6 KiB
TypeScript
/**
|
||
* 用户交互处理器
|
||
* 处理 ask_user 事件,通过 WebView 显示问题并收集用户回答
|
||
*/
|
||
import * as vscode from 'vscode';
|
||
import { submitAnswer, submitToolConfirm } from './apiClient';
|
||
import type { AskUserEvent, AnswerRequest } from '../types/api';
|
||
|
||
/**
|
||
* 待处理的用户问题
|
||
*/
|
||
interface PendingQuestion {
|
||
askId: string;
|
||
taskId: string;
|
||
question: string;
|
||
options: string[];
|
||
resolve: (answer: string) => void;
|
||
reject: (error: Error) => void;
|
||
}
|
||
|
||
/**
|
||
* 用户交互管理器
|
||
*/
|
||
export class UserInteractionManager {
|
||
private pendingQuestions = new Map<string, PendingQuestion>();
|
||
private webviewPanel: vscode.WebviewPanel | null = null;
|
||
|
||
/**
|
||
* 设置 WebView 面板(用于发送消息)
|
||
*/
|
||
setWebviewPanel(panel: vscode.WebviewPanel): void {
|
||
this.webviewPanel = panel;
|
||
}
|
||
|
||
/**
|
||
* 处理 ask_user 事件
|
||
* @param event ask_user 事件数据
|
||
* @param taskId 当前任务ID
|
||
*/
|
||
async handleAskUser(event: AskUserEvent, taskId: string): Promise<void> {
|
||
const { askId, question, options } = event;
|
||
|
||
console.log(`[UserInteraction] 收到问题: askId=${askId}, question=${question}`);
|
||
|
||
// 注意:问题显示已经通过 dialogService 的 onSegmentUpdate 统一处理
|
||
// 这里不再单独发送 showQuestion 命令,避免重复显示
|
||
|
||
// 创建 Promise 等待用户回答
|
||
return new Promise((resolve, reject) => {
|
||
this.pendingQuestions.set(askId, {
|
||
askId,
|
||
taskId,
|
||
question,
|
||
options,
|
||
resolve: (answer: string) => {
|
||
this.submitUserAnswer(askId, taskId, answer)
|
||
.then(() => resolve())
|
||
.catch(reject);
|
||
},
|
||
reject
|
||
});
|
||
|
||
// 设置超时(5分钟)
|
||
setTimeout(() => {
|
||
if (this.pendingQuestions.has(askId)) {
|
||
this.pendingQuestions.delete(askId);
|
||
reject(new Error('用户回答超时'));
|
||
}
|
||
}, 300000);
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 处理用户提交的回答(从 WebView 调用)
|
||
* @param askId 问题ID
|
||
* @param selected 选中的选项
|
||
* @param customInput 自定义输入
|
||
*/
|
||
async receiveAnswer(
|
||
askId: string,
|
||
selected?: string[],
|
||
customInput?: string
|
||
): Promise<void> {
|
||
const pending = this.pendingQuestions.get(askId);
|
||
if (!pending) {
|
||
console.warn(`[UserInteraction] 问题不存在或已超时: askId=${askId}`);
|
||
return;
|
||
}
|
||
|
||
// 构建答案
|
||
const answer = customInput || selected?.join(', ') || '';
|
||
|
||
console.log(`[UserInteraction] 收到用户回答: askId=${askId}, answer=${answer}`);
|
||
|
||
// 移除待处理问题
|
||
this.pendingQuestions.delete(askId);
|
||
|
||
// 触发 resolve
|
||
pending.resolve(answer);
|
||
}
|
||
|
||
/**
|
||
* 提交用户回答到后端
|
||
*/
|
||
private async submitUserAnswer(
|
||
askId: string,
|
||
taskId: string,
|
||
answer: string
|
||
): Promise<void> {
|
||
// 检查是否是工具确认类型的问题
|
||
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,
|
||
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;
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 取消所有待处理的问题
|
||
*/
|
||
cancelAll(): void {
|
||
for (const [askId, pending] of this.pendingQuestions) {
|
||
pending.reject(new Error('用户交互已取消'));
|
||
}
|
||
this.pendingQuestions.clear();
|
||
}
|
||
|
||
/**
|
||
* 检查是否有待处理的问题
|
||
*/
|
||
hasPendingQuestions(): boolean {
|
||
return this.pendingQuestions.size > 0;
|
||
}
|
||
}
|
||
|
||
// 全局实例
|
||
export const userInteractionManager = new UserInteractionManager();
|