feat: 实现计划管理工具和进度条实时更新

- 添加 plan_step_add/remove/update 和 plan_summary_update 事件支持
- 添加 onPhaseProgress 回调,联动独立进度条组件
- 扩展 MessageSegment 接口支持 progress 类型
- 映射 phaseId (sim -> simulation) 适配进度条
This commit is contained in:
XiaoFeng
2026-01-09 19:26:55 +08:00
parent 5546791549
commit 5c19be22d3
5 changed files with 504 additions and 10 deletions

View File

@ -18,7 +18,7 @@ import { getUserIdFromToken } from '../utils/jwtUtils';
* 消息段落类型
*/
export interface MessageSegment {
type: 'text' | 'tool' | 'question' | 'agent' | 'plan';
type: 'text' | 'tool' | 'question' | 'agent' | 'plan' | 'progress';
content?: string;
toolName?: string;
toolStatus?: 'running' | 'success' | 'error';
@ -33,8 +33,11 @@ export interface MessageSegment {
agentSteps?: AgentStep[];
// 计划相关字段
planTitle?: string;
planPhases?: import('../types/api').PlanPhase[];
planSteps?: string[];
planSummary?: string;
// 进度条相关字段(独立于 plan用于执行模式
progressPhases?: import('../types/api').PlanPhase[];
}
/**
@ -63,7 +66,7 @@ export interface DialogCallbacks {
/** 工具确认请求Ask 模式) */
onToolConfirm?: (confirmId: number, toolName: string, toolInput: Record<string, unknown>) => void;
/** 计划确认请求Plan 模式) */
onPlanConfirm?: (confirmId: number, title: string, steps: string[], summary: string) => void;
onPlanConfirm?: (confirmId: number, title: string, phases: import('../types/api').PlanPhase[] | undefined, steps: string[] | undefined, summary: string) => void;
/** 显示问题ask_user */
onQuestion?: (askId: string, question: string, options: string[]) => void;
/** 实时更新段落(流式过程中) */
@ -76,6 +79,8 @@ export interface DialogCallbacks {
onNotification?: (message: string) => void;
/** 上下文使用量更新 */
onContextUsage?: (data: { currentTokens: number; maxTokens: number; percentage: number }) => void;
/** 阶段进度更新 */
onPhaseProgress?: (phaseId: string, status: string) => void;
}
/**
@ -530,10 +535,12 @@ export class DialogSession {
const askId = `ask_${data.confirmId}`;
// 添加计划段落到聊天界面(包含 askId 用于响应)
// 支持新格式phases和旧格式steps
this.segments.push({
type: 'plan',
askId: askId,
planTitle: data.title,
planPhases: data.phases,
planSteps: data.steps,
planSummary: data.summary
});
@ -554,7 +561,108 @@ export class DialogSession {
}
// 调用回调通知 UI
callbacks.onPlanConfirm?.(data.confirmId, data.title, data.steps, data.summary);
callbacks.onPlanConfirm?.(data.confirmId, data.title, data.phases, data.steps, data.summary);
},
onPhaseProgress: (data: import('../types/api').PhaseProgressEvent) => {
console.log('[DialogSession] onPhaseProgress:', data.phaseId, data.status);
// 1. 尝试更新 plan segment兼容旧逻辑
for (let i = this.segments.length - 1; i >= 0; i--) {
const seg = this.segments[i];
if (seg.type === 'plan' && seg.planPhases) {
seg.planPhases = seg.planPhases.map(phase => {
if (phase.id === data.phaseId) {
return { ...phase, status: data.status };
}
return phase;
});
callbacks.onSegmentUpdate?.(this.segments);
break;
}
}
// 2. 通知外部更新独立进度条
callbacks.onPhaseProgress?.(data.phaseId, data.status);
},
onPlanStepAdd: (data: import('../types/api').PlanStepAddEvent) => {
console.log('[DialogSession] onPlanStepAdd:', data.phaseId, data.step);
for (let i = this.segments.length - 1; i >= 0; i--) {
const seg = this.segments[i];
if (seg.type === 'plan' && seg.planPhases) {
seg.planPhases = seg.planPhases.map(phase => {
if (phase.id === data.phaseId) {
const newSteps = [...(phase.steps || [])];
if (data.index >= 0 && data.index < newSteps.length) {
newSteps.splice(data.index, 0, data.step);
} else {
newSteps.push(data.step);
}
return { ...phase, steps: newSteps };
}
return phase;
});
break;
}
}
callbacks.onSegmentUpdate?.(this.segments);
},
onPlanStepRemove: (data: import('../types/api').PlanStepRemoveEvent) => {
console.log('[DialogSession] onPlanStepRemove:', data.phaseId, data.stepIndex);
for (let i = this.segments.length - 1; i >= 0; i--) {
const seg = this.segments[i];
if (seg.type === 'plan' && seg.planPhases) {
seg.planPhases = seg.planPhases.map(phase => {
if (phase.id === data.phaseId && phase.steps) {
const newSteps = [...phase.steps];
newSteps.splice(data.stepIndex, 1);
return { ...phase, steps: newSteps };
}
return phase;
});
break;
}
}
callbacks.onSegmentUpdate?.(this.segments);
},
onPlanStepUpdate: (data: import('../types/api').PlanStepUpdateEvent) => {
console.log('[DialogSession] onPlanStepUpdate:', data.phaseId, data.stepIndex);
for (let i = this.segments.length - 1; i >= 0; i--) {
const seg = this.segments[i];
if (seg.type === 'plan' && seg.planPhases) {
seg.planPhases = seg.planPhases.map(phase => {
if (phase.id === data.phaseId && phase.steps) {
const newSteps = [...phase.steps];
if (data.stepIndex >= 0 && data.stepIndex < newSteps.length) {
newSteps[data.stepIndex] = data.step;
}
return { ...phase, steps: newSteps };
}
return phase;
});
break;
}
}
callbacks.onSegmentUpdate?.(this.segments);
},
onPlanSummaryUpdate: (data: import('../types/api').PlanSummaryUpdateEvent) => {
console.log('[DialogSession] onPlanSummaryUpdate');
for (let i = this.segments.length - 1; i >= 0; i--) {
const seg = this.segments[i];
if (seg.type === 'plan') {
seg.planSummary = data.summary;
break;
}
}
callbacks.onSegmentUpdate?.(this.segments);
},
onAskUser: async (data: AskUserEvent) => {