Files
Agent-System/web/src/engine/phaseEventMapper.ts
T

80 lines
3.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { PhaseEvent } from '../types/workbench';
/** 后端 phase 事件载荷(与 StreamEvent['phase'] 结构兼容,保持 mapper 独立可测) */
export type PhaseEventPayload = {
phase_id?: string;
message?: string;
role?: string;
step_index?: number;
total_steps?: number;
progress?: number;
};
/** phase_id → 图标/短标签映射(与 bizdev-cli/main.go 的 CLI 映射保持一致) */
const PHASE_META: Record<string, { icon: string; label: string }> = {
squad_assembly: { icon: '👥', label: '组建小队' },
knowledge_retrieval: { icon: '📚', label: '知识检索' },
knowledge_retrieved: { icon: '📚', label: '知识命中' },
step_executing: { icon: '⚙️', label: '专员执行' },
step_completed: { icon: '✅', label: '步骤完成' },
quality_gate: { icon: '🛡️', label: '质量门禁' },
advisor_briefing: { icon: '💡', label: '参谋简报' },
stage_transition: { icon: '🚀', label: '阶段跃迁' },
stage_started: { icon: '🚀', label: '阶段启动' },
stage_completed: { icon: '🎉', label: '阶段完成' },
task_failed: { icon: '⚠️', label: '任务异常' },
};
/** 收口型 phase:不追加新节点,仅把当前 running 条目置为 completed */
const CLOSE_PHASES = new Set(['step_completed', 'stage_completed']);
function decorateLabel(base: string, phase: PhaseEventPayload): string {
const { step_index, total_steps, progress } = phase;
if (step_index && step_index > 0 && total_steps && total_steps > 0) {
return `${base}(第 ${step_index}/${total_steps} 步)`;
}
if (progress && progress > 0) {
return `${base}(${progress}%)`;
}
return base;
}
/** 条目 key:同 phase 且无有效 step_index 时刷新文案;带 step_index 的重复 phase 各占一节点 */
function stepKeyOf(phase: PhaseEventPayload): string {
const idx = phase.step_index ?? 0;
return idx > 0 ? `${phase.phase_id}#${idx}` : `${phase.phase_id}`;
}
/**
* applyPhaseEvent 把一条后端 phase 事件折叠进 PhaseEventStrip 序列(纯函数)。
* 进度求真:仅由真实 phase 事件驱动,无任何人为进度推演。
*/
export function applyPhaseEvent(strip: PhaseEvent[], phase?: PhaseEventPayload): PhaseEvent[] {
if (!phase?.phase_id) return strip;
const meta = PHASE_META[phase.phase_id] ?? { icon: '⏳', label: '任务推进' };
// 进度求真:优先展示后端下发的真实进度文案,短标签仅作缺省
const base = phase.message?.trim() || meta.label;
const label = decorateLabel(base, phase);
const key = stepKeyOf(phase);
// 已存在的条目:仅刷新文案(同阶段/同步骤进度更新)
const existingIdx = strip.findIndex((e) => e.step === key);
if (existingIdx >= 0) {
return strip.map((e, i) => (i === existingIdx ? { ...e, label } : e));
}
// 收口型 phase:running 条目置 completed,不追加新节点
if (CLOSE_PHASES.has(phase.phase_id)) {
return strip.map((e) => (e.status === 'running' ? { ...e, status: 'completed' as const } : e));
}
// 新 phase:先行条目全部置 completed,追加当前节点为 running
const settled = strip.map((e) =>
e.status === 'running' ? { ...e, status: 'completed' as const } : e
);
return [
...settled,
{ step: key, icon: meta.icon, label, status: 'running' as const },
];
}