diff --git a/src/frontend/src/components/TaskModal.tsx b/src/frontend/src/components/TaskModal.tsx index 24654b9..44109af 100644 --- a/src/frontend/src/components/TaskModal.tsx +++ b/src/frontend/src/components/TaskModal.tsx @@ -139,6 +139,87 @@ function EventTimeline({ events }: { events: any[] }) { ); } +// ── v2.7: 子 Task 面板 ── + +function SubtaskPanel({ taskId, stagesJson }: { taskId: string; stagesJson?: string }) { + const [subtasks, setSubtasks] = useState([]); + const [progress, setProgress] = useState(null); + + useEffect(() => { + let mounted = true; + (async () => { + const [st, pg] = await Promise.all([ + api.listSubtasks(taskId), + api.taskProgress(taskId), + ]); + if (mounted) { + setSubtasks(st as any[]); + setProgress(pg); + } + })(); + return () => { mounted = false; }; + }, [taskId]); + + if (subtasks.length === 0 && !progress) return null; + + const stages = progress?.stages || []; + const total = progress?.total_subtasks || subtasks.length; + const done = progress?.done_subtasks || 0; + const activeStage = progress?.active_stage; + + return ( +
+ + + {/* Stage 进度条 */} + {stages.length > 0 && ( +
+ {stages.map((s: any) => { + const pct = s.total > 0 ? s.done / s.total : 0; + const isActive = s.active > 0 || (s.total > 0 && s.done < s.total && s.label === activeStage); + return ( +
+
+
= 1 ? '#2ecc8a' : isActive ? '#6a9eff' : '#f59e0b', + transition: 'width .3s', + }} /> +
+ = 1 ? '#2ecc8a' : 'var(--muted)', fontWeight: isActive ? 700 : 400 }}> + {s.label} + +
+ ); + })} +
+ )} + + {/* 子 Task 列表 */} + {subtasks.length > 0 && ( +
+ {subtasks.map((st: any) => { + const ssm = STATUS_META[st.status] || STATUS_META.pending; + const emoji = st.org ? (AGENT_EMOJI[st.org] || '🤖') : ''; + return ( +
+ {ssm.label} + {st.title} + {st.stage && {st.stage}} + {emoji && {emoji}} +
+ ); + })} +
+ )} +
+ ); +} + // ── 主组件 ── export default function TaskModal() { const modalTaskId = useStore(s => s.modalTaskId);