auto-sync: 2026-05-18 11:56:29
This commit is contained in:
@@ -139,6 +139,87 @@ function EventTimeline({ events }: { events: any[] }) {
|
||||
);
|
||||
}
|
||||
|
||||
// ── v2.7: 子 Task 面板 ──
|
||||
|
||||
function SubtaskPanel({ taskId, stagesJson }: { taskId: string; stagesJson?: string }) {
|
||||
const [subtasks, setSubtasks] = useState<any[]>([]);
|
||||
const [progress, setProgress] = useState<any>(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 (
|
||||
<div style={{ marginBottom: 16 }}>
|
||||
<SectionLabel icon="📊" title={`子任务进度 ${done}/${total}`} />
|
||||
|
||||
{/* Stage 进度条 */}
|
||||
{stages.length > 0 && (
|
||||
<div style={{ display: 'flex', gap: 2, marginBottom: 8, padding: '6px 8px', background: 'var(--panel2)', borderRadius: 8 }}>
|
||||
{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 (
|
||||
<div key={s.id} style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 2 }}>
|
||||
<div style={{ width: '100%', height: 4, background: '#1c2236', borderRadius: 2, overflow: 'hidden' }}>
|
||||
<div style={{
|
||||
width: `${pct * 100}%`, height: '100%', borderRadius: 2,
|
||||
background: pct >= 1 ? '#2ecc8a' : isActive ? '#6a9eff' : '#f59e0b',
|
||||
transition: 'width .3s',
|
||||
}} />
|
||||
</div>
|
||||
<span style={{ fontSize: 9, color: isActive ? '#6a9eff' : pct >= 1 ? '#2ecc8a' : 'var(--muted)', fontWeight: isActive ? 700 : 400 }}>
|
||||
{s.label}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 子 Task 列表 */}
|
||||
{subtasks.length > 0 && (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
|
||||
{subtasks.map((st: any) => {
|
||||
const ssm = STATUS_META[st.status] || STATUS_META.pending;
|
||||
const emoji = st.org ? (AGENT_EMOJI[st.org] || '🤖') : '';
|
||||
return (
|
||||
<div key={st.id} style={{
|
||||
display: 'flex', alignItems: 'center', gap: 8,
|
||||
padding: '6px 10px', background: 'var(--panel2)', borderRadius: 6,
|
||||
fontSize: 12,
|
||||
}}>
|
||||
<span style={{ fontSize: 9, padding: '1px 5px', borderRadius: 3, border: `1px solid ${ssm.color}44`, color: ssm.color, background: ssm.bg }}>{ssm.label}</span>
|
||||
<span style={{ flex: 1, color: '#dde4f8', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{st.title}</span>
|
||||
{st.stage && <span style={{ fontSize: 10, color: '#f59e0b' }}>{st.stage}</span>}
|
||||
{emoji && <span style={{ fontSize: 11 }}>{emoji}</span>}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// ── 主组件 ──
|
||||
export default function TaskModal() {
|
||||
const modalTaskId = useStore(s => s.modalTaskId);
|
||||
|
||||
Reference in New Issue
Block a user