auto-sync: 2026-05-18 11:56:29

This commit is contained in:
cfdaily
2026-05-18 11:56:29 +08:00
parent 6a20c10d7b
commit c09e6a4b02
+81
View File
@@ -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);