feat: 模式传递和 API 调用
- dialogService 接收并传递 mode 参数 - apiClient 构造带 mode 的请求 - messageHandler 从 WebView 消息获取 mode
This commit is contained in:
@ -6,7 +6,7 @@ import * as https from 'https';
|
|||||||
import * as http from 'http';
|
import * as http from 'http';
|
||||||
import { URL } from 'url';
|
import { URL } from 'url';
|
||||||
import { getApiUrl, getConfig } from '../config/settings';
|
import { getApiUrl, getConfig } from '../config/settings';
|
||||||
import type { ToolCallResult, AnswerRequest, ToolResultResponse, AnswerResponse } from '../types/api';
|
import type { ToolCallResult, AnswerRequest, ToolResultResponse, AnswerResponse, ToolConfirmResponse } from '../types/api';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HTTP 请求选项
|
* HTTP 请求选项
|
||||||
@ -103,6 +103,18 @@ export async function submitAnswer(answer: AnswerRequest): Promise<AnswerRespons
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提交工具确认响应(Ask 模式)
|
||||||
|
* POST /api/tool/confirm
|
||||||
|
*/
|
||||||
|
export async function submitToolConfirm(response: ToolConfirmResponse): Promise<ToolResultResponse> {
|
||||||
|
console.log(`[API] 提交工具确认: confirmId=${response.confirmId}, approved=${response.approved}`);
|
||||||
|
return request<ToolResultResponse>('/api/tool/confirm', {
|
||||||
|
method: 'POST',
|
||||||
|
body: response
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 健康检查
|
* 健康检查
|
||||||
* GET /api/dialog/health
|
* GET /api/dialog/health
|
||||||
|
|||||||
@ -7,13 +7,14 @@ import { startStreamDialog, generateTaskId, SSEController, SSECallbacks } from '
|
|||||||
import { executeToolCall, createToolExecutorContext, ToolExecutorContext } from './toolExecutor';
|
import { executeToolCall, createToolExecutorContext, ToolExecutorContext } from './toolExecutor';
|
||||||
import { userInteractionManager } from './userInteraction';
|
import { userInteractionManager } from './userInteraction';
|
||||||
import { getConfig } from '../config/settings';
|
import { getConfig } from '../config/settings';
|
||||||
import type { DialogRequest, ToolCallRequest, AskUserEvent } from '../types/api';
|
import type { DialogRequest, ToolCallRequest, AskUserEvent, RunMode, ToolConfirmEvent, PlanConfirmEvent } from '../types/api';
|
||||||
|
import { submitToolConfirm, submitAnswer } from './apiClient';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 消息段落类型
|
* 消息段落类型
|
||||||
*/
|
*/
|
||||||
export interface MessageSegment {
|
export interface MessageSegment {
|
||||||
type: 'text' | 'tool' | 'question' | 'agent';
|
type: 'text' | 'tool' | 'question' | 'agent' | 'plan';
|
||||||
content?: string;
|
content?: string;
|
||||||
toolName?: string;
|
toolName?: string;
|
||||||
toolStatus?: 'running' | 'success' | 'error';
|
toolStatus?: 'running' | 'success' | 'error';
|
||||||
@ -26,6 +27,10 @@ export interface MessageSegment {
|
|||||||
agentName?: string;
|
agentName?: string;
|
||||||
agentStatus?: 'running' | 'completed' | 'error';
|
agentStatus?: 'running' | 'completed' | 'error';
|
||||||
agentSteps?: AgentStep[];
|
agentSteps?: AgentStep[];
|
||||||
|
// 计划相关字段
|
||||||
|
planTitle?: string;
|
||||||
|
planSteps?: string[];
|
||||||
|
planSummary?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -51,6 +56,10 @@ export interface DialogCallbacks {
|
|||||||
onToolComplete?: (toolName: string, result: string) => void;
|
onToolComplete?: (toolName: string, result: string) => void;
|
||||||
/** 工具执行错误 */
|
/** 工具执行错误 */
|
||||||
onToolError?: (toolName: string, error: string) => void;
|
onToolError?: (toolName: string, error: string) => void;
|
||||||
|
/** 工具确认请求(Ask 模式) */
|
||||||
|
onToolConfirm?: (confirmId: number, toolName: string, toolInput: Record<string, unknown>) => void;
|
||||||
|
/** 计划确认请求(Plan 模式) */
|
||||||
|
onPlanConfirm?: (confirmId: number, title: string, steps: string[], summary: string) => void;
|
||||||
/** 显示问题(ask_user) */
|
/** 显示问题(ask_user) */
|
||||||
onQuestion?: (askId: string, question: string, options: string[]) => void;
|
onQuestion?: (askId: string, question: string, options: string[]) => void;
|
||||||
/** 实时更新段落(流式过程中) */
|
/** 实时更新段落(流式过程中) */
|
||||||
@ -75,8 +84,9 @@ export class DialogSession {
|
|||||||
private segments: MessageSegment[] = [];
|
private segments: MessageSegment[] = [];
|
||||||
private currentTextSegment: MessageSegment | null = null;
|
private currentTextSegment: MessageSegment | null = null;
|
||||||
|
|
||||||
constructor(extensionPath: string) {
|
constructor(extensionPath: string, existingTaskId?: string) {
|
||||||
this.taskId = generateTaskId();
|
// 支持复用现有 taskId(用于 Plan 模式确认后继续执行)
|
||||||
|
this.taskId = existingTaskId || generateTaskId();
|
||||||
this.toolContext = createToolExecutorContext(extensionPath);
|
this.toolContext = createToolExecutorContext(extensionPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,12 +152,52 @@ export class DialogSession {
|
|||||||
return this.isActive;
|
return this.isActive;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取工具操作描述(用于确认对话框)
|
||||||
|
*/
|
||||||
|
private getToolDescription(toolName: string, toolInput: Record<string, unknown>): string {
|
||||||
|
const lines: string[] = [];
|
||||||
|
|
||||||
|
switch (toolName) {
|
||||||
|
case 'file_write':
|
||||||
|
lines.push(`文件路径: ${toolInput.path || '未知'}`);
|
||||||
|
if (toolInput.content) {
|
||||||
|
const content = String(toolInput.content);
|
||||||
|
lines.push(`内容长度: ${content.length} 字符`);
|
||||||
|
lines.push(`内容预览: ${content.substring(0, 100)}${content.length > 100 ? '...' : ''}`);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'file_delete':
|
||||||
|
lines.push(`删除文件: ${toolInput.path || '未知'}`);
|
||||||
|
break;
|
||||||
|
case 'syntax_check':
|
||||||
|
lines.push('执行语法检查');
|
||||||
|
if (toolInput.code) {
|
||||||
|
const code = String(toolInput.code);
|
||||||
|
lines.push(`代码长度: ${code.length} 字符`);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'simulation':
|
||||||
|
lines.push(`RTL文件: ${toolInput.rtlPath || '未知'}`);
|
||||||
|
lines.push(`TB文件: ${toolInput.tbPath || '未知'}`);
|
||||||
|
if (toolInput.duration) {
|
||||||
|
lines.push(`仿真时长: ${toolInput.duration}`);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
lines.push(`参数: ${JSON.stringify(toolInput, null, 2)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return lines.join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 发送消息并开始流式对话
|
* 发送消息并开始流式对话
|
||||||
*/
|
*/
|
||||||
async sendMessage(
|
async sendMessage(
|
||||||
message: string,
|
message: string,
|
||||||
callbacks: DialogCallbacks
|
callbacks: DialogCallbacks,
|
||||||
|
mode?: RunMode
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
if (this.isActive) {
|
if (this.isActive) {
|
||||||
callbacks.onError?.('当前有对话正在进行中');
|
callbacks.onError?.('当前有对话正在进行中');
|
||||||
@ -164,7 +214,7 @@ export class DialogSession {
|
|||||||
taskId: this.taskId,
|
taskId: this.taskId,
|
||||||
message,
|
message,
|
||||||
userId: config.userId,
|
userId: config.userId,
|
||||||
toolMode: 'AGENT'
|
mode: mode || 'agent'
|
||||||
};
|
};
|
||||||
|
|
||||||
const sseCallbacks: SSECallbacks = {
|
const sseCallbacks: SSECallbacks = {
|
||||||
@ -248,6 +298,72 @@ export class DialogSession {
|
|||||||
callbacks.onSegmentUpdate?.(this.segments);
|
callbacks.onSegmentUpdate?.(this.segments);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onToolConfirm: async (data: ToolConfirmEvent) => {
|
||||||
|
console.log('[DialogSession] onToolConfirm:', data.toolName, data.confirmId);
|
||||||
|
|
||||||
|
// 调用回调通知 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);
|
||||||
|
|
||||||
|
// 发送确认响应到后端
|
||||||
|
try {
|
||||||
|
await submitToolConfirm({
|
||||||
|
confirmId: data.confirmId,
|
||||||
|
taskId: this.taskId,
|
||||||
|
approved
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[DialogSession] 发送确认响应失败:', error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onPlanConfirm: async (data: PlanConfirmEvent) => {
|
||||||
|
console.log('[DialogSession] onPlanConfirm:', data.title);
|
||||||
|
|
||||||
|
// 结束当前文本段落
|
||||||
|
this.finalizeTextSegment();
|
||||||
|
|
||||||
|
const askId = `ask_${data.confirmId}`;
|
||||||
|
|
||||||
|
// 添加计划段落到聊天界面(包含 askId 用于响应)
|
||||||
|
this.segments.push({
|
||||||
|
type: 'plan',
|
||||||
|
askId: askId,
|
||||||
|
planTitle: data.title,
|
||||||
|
planSteps: data.steps,
|
||||||
|
planSummary: data.summary
|
||||||
|
});
|
||||||
|
|
||||||
|
// 实时发送段落更新
|
||||||
|
callbacks.onSegmentUpdate?.(this.segments);
|
||||||
|
|
||||||
|
// 注册问题到前端(类似 askUser),以便用户回答时能找到
|
||||||
|
const planEvent = {
|
||||||
|
askId: askId,
|
||||||
|
question: `请确认执行计划:${data.title}`,
|
||||||
|
options: ['确认执行', '修改计划', '取消']
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
await userInteractionManager.handleAskUser(planEvent as AskUserEvent, this.taskId);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[DialogSession] 处理计划确认失败:', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用回调通知 UI
|
||||||
|
callbacks.onPlanConfirm?.(data.confirmId, data.title, data.steps, data.summary);
|
||||||
|
},
|
||||||
|
|
||||||
onAskUser: async (data: AskUserEvent) => {
|
onAskUser: async (data: AskUserEvent) => {
|
||||||
this.finalizeTextSegment();
|
this.finalizeTextSegment();
|
||||||
this.segments.push({
|
this.segments.push({
|
||||||
@ -402,13 +518,15 @@ class DialogManager {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建新会话
|
* 创建新会话
|
||||||
|
* @param extensionPath 扩展路径
|
||||||
|
* @param existingTaskId 可选,复用现有的 taskId(用于 Plan 模式确认后继续执行)
|
||||||
*/
|
*/
|
||||||
createSession(extensionPath: string): DialogSession {
|
createSession(extensionPath: string, existingTaskId?: string): DialogSession {
|
||||||
// 如果有活跃会话,先中止
|
// 如果有活跃会话,先中止
|
||||||
if (this.currentSession?.active) {
|
if (this.currentSession?.active) {
|
||||||
this.currentSession.abort();
|
this.currentSession.abort();
|
||||||
}
|
}
|
||||||
this.currentSession = new DialogSession(extensionPath);
|
this.currentSession = new DialogSession(extensionPath, existingTaskId);
|
||||||
return this.currentSession;
|
return this.currentSession;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -19,19 +19,43 @@ import { dialogManager, DialogSession } from "../services/dialogService";
|
|||||||
import { userInteractionManager } from "../services/userInteraction";
|
import { userInteractionManager } from "../services/userInteraction";
|
||||||
import { healthCheck } from "../services/apiClient";
|
import { healthCheck } from "../services/apiClient";
|
||||||
|
|
||||||
|
import type { RunMode } from '../types/api';
|
||||||
|
|
||||||
/** 是否使用后端服务(可通过配置控制) */
|
/** 是否使用后端服务(可通过配置控制) */
|
||||||
let useBackendService = true;
|
let useBackendService = true;
|
||||||
|
|
||||||
/** 当前对话会话 */
|
/** 当前对话会话 */
|
||||||
let currentSession: DialogSession | null = null;
|
let currentSession: DialogSession | null = null;
|
||||||
|
|
||||||
|
/** 待执行的计划(Plan 模式确认后自动执行) */
|
||||||
|
let pendingPlanExecution: {
|
||||||
|
panel: vscode.WebviewPanel;
|
||||||
|
planTitle: string;
|
||||||
|
extensionPath: string;
|
||||||
|
taskId: string; // 保存 taskId 以便复用
|
||||||
|
} | null = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置待执行的计划(由 ICHelperPanel 调用)
|
||||||
|
*/
|
||||||
|
export function setPendingPlanExecution(
|
||||||
|
panel: vscode.WebviewPanel,
|
||||||
|
planTitle: string,
|
||||||
|
extensionPath: string,
|
||||||
|
taskId: string
|
||||||
|
): void {
|
||||||
|
pendingPlanExecution = { panel, planTitle, extensionPath, taskId };
|
||||||
|
console.log('[MessageHandler] 设置待执行计划:', planTitle, 'taskId:', taskId);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理用户消息
|
* 处理用户消息
|
||||||
*/
|
*/
|
||||||
export async function handleUserMessage(
|
export async function handleUserMessage(
|
||||||
panel: vscode.WebviewPanel,
|
panel: vscode.WebviewPanel,
|
||||||
text: string,
|
text: string,
|
||||||
extensionPath?: string
|
extensionPath?: string,
|
||||||
|
mode?: RunMode
|
||||||
) {
|
) {
|
||||||
console.log("收到用户消息:", text);
|
console.log("收到用户消息:", text);
|
||||||
|
|
||||||
@ -63,7 +87,7 @@ export async function handleUserMessage(
|
|||||||
// 尝试使用后端服务
|
// 尝试使用后端服务
|
||||||
if (useBackendService && extensionPath) {
|
if (useBackendService && extensionPath) {
|
||||||
try {
|
try {
|
||||||
await handleUserMessageWithBackend(panel, text, extensionPath);
|
await handleUserMessageWithBackend(panel, text, extensionPath, mode);
|
||||||
return;
|
return;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("后端服务不可用,回退到本地模式:", error);
|
console.error("后端服务不可用,回退到本地模式:", error);
|
||||||
@ -97,11 +121,16 @@ export async function handleUserMessage(
|
|||||||
async function handleUserMessageWithBackend(
|
async function handleUserMessageWithBackend(
|
||||||
panel: vscode.WebviewPanel,
|
panel: vscode.WebviewPanel,
|
||||||
text: string,
|
text: string,
|
||||||
extensionPath: string
|
extensionPath: string,
|
||||||
|
mode?: RunMode,
|
||||||
|
reuseTaskId?: string // 可选,复用现有 taskId(用于 Plan 模式确认后继续执行)
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
// 创建或复用会话
|
// 创建或复用会话
|
||||||
if (!currentSession || !currentSession.active) {
|
if (!currentSession || !currentSession.active) {
|
||||||
currentSession = dialogManager.createSession(extensionPath);
|
currentSession = dialogManager.createSession(extensionPath, reuseTaskId);
|
||||||
|
if (reuseTaskId) {
|
||||||
|
console.log('[MessageHandler] 复用 taskId 创建会话:', reuseTaskId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const historyManager = ChatHistoryManager.getInstance();
|
const historyManager = ChatHistoryManager.getInstance();
|
||||||
@ -184,6 +213,29 @@ async function handleUserMessageWithBackend(
|
|||||||
console.warn("保存AI响应历史失败:", error);
|
console.warn("保存AI响应历史失败:", error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 检查是否有待执行的计划(Plan 模式确认后自动执行)
|
||||||
|
if (pendingPlanExecution) {
|
||||||
|
const { panel: execPanel, planTitle, extensionPath: execPath, taskId: reuseTaskId } = pendingPlanExecution;
|
||||||
|
pendingPlanExecution = null;
|
||||||
|
console.log('[MessageHandler] 自动执行计划:', planTitle, '复用 taskId:', reuseTaskId);
|
||||||
|
|
||||||
|
// 延迟一小段时间确保当前对话完全结束
|
||||||
|
setTimeout(async () => {
|
||||||
|
try {
|
||||||
|
// 复用 taskId 创建新会话,确保知识图谱数据不丢失
|
||||||
|
await handleUserMessageWithBackend(
|
||||||
|
execPanel,
|
||||||
|
`请按照刚才的计划执行:${planTitle}`,
|
||||||
|
execPath,
|
||||||
|
'agent',
|
||||||
|
reuseTaskId // 复用 Plan 模式的 taskId
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('[MessageHandler] 自动执行计划失败:', err);
|
||||||
|
}
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
|
||||||
resolve();
|
resolve();
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -201,7 +253,7 @@ async function handleUserMessageWithBackend(
|
|||||||
onNotification: (message) => {
|
onNotification: (message) => {
|
||||||
vscode.window.showInformationMessage(message);
|
vscode.window.showInformationMessage(message);
|
||||||
},
|
},
|
||||||
});
|
}, mode);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -226,6 +278,75 @@ export function abortCurrentDialog(): void {
|
|||||||
currentSession = null;
|
currentSession = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前会话的 taskId
|
||||||
|
*/
|
||||||
|
export function getCurrentTaskId(): string | null {
|
||||||
|
return currentSession?.getTaskId() || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理计划操作(Plan 模式)
|
||||||
|
* @param panel WebView 面板
|
||||||
|
* @param action 操作类型:confirm/modify/cancel
|
||||||
|
* @param planTitle 计划标题
|
||||||
|
* @param extensionPath 扩展路径
|
||||||
|
*/
|
||||||
|
export async function handlePlanAction(
|
||||||
|
panel: vscode.WebviewPanel,
|
||||||
|
action: string,
|
||||||
|
planTitle: string,
|
||||||
|
extensionPath: string
|
||||||
|
): Promise<void> {
|
||||||
|
console.log('[handlePlanAction] action:', action, 'planTitle:', planTitle);
|
||||||
|
|
||||||
|
switch (action) {
|
||||||
|
case 'confirm':
|
||||||
|
// 确认执行:切换到 Agent 模式并发送执行消息
|
||||||
|
panel.webview.postMessage({
|
||||||
|
command: 'switchMode',
|
||||||
|
mode: 'agent'
|
||||||
|
});
|
||||||
|
// 发送执行消息
|
||||||
|
await handleUserMessage(
|
||||||
|
panel,
|
||||||
|
`请按照刚才的计划执行:${planTitle}`,
|
||||||
|
extensionPath,
|
||||||
|
'agent'
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'modify':
|
||||||
|
// 修改计划:提示用户输入修改建议
|
||||||
|
const modification = await vscode.window.showInputBox({
|
||||||
|
prompt: '请输入您对计划的修改建议',
|
||||||
|
placeHolder: '例如:第2步需要先检查文件是否存在...',
|
||||||
|
ignoreFocusOut: true
|
||||||
|
});
|
||||||
|
if (modification) {
|
||||||
|
await handleUserMessage(
|
||||||
|
panel,
|
||||||
|
`请根据以下建议修改计划:${modification}`,
|
||||||
|
extensionPath,
|
||||||
|
'plan'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'cancel':
|
||||||
|
// 取消计划:通知用户
|
||||||
|
panel.webview.postMessage({
|
||||||
|
command: 'addMessage',
|
||||||
|
text: '计划已取消。',
|
||||||
|
sender: 'bot'
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
console.warn('[handlePlanAction] 未知操作:', action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 解析文件操作命令
|
* 解析文件操作命令
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user