import React, { useEffect, useState, useRef, useCallback } from 'react'; import { getDirective, DirectiveStep, DirectiveWithSteps } from '../../../services/directiveApi'; import './StepsDiagram.css'; interface StepsDiagramComponentProps { directiveId: string; } type StepStatus = 'pending' | 'ready' | 'running' | 'completed' | 'failed' | 'skipped'; const STATUS_LABELS: Record = { pending: 'Pending', ready: 'Ready', running: 'Running', completed: 'Done', failed: 'Failed', skipped: 'Skipped', }; function formatTime(dateStr: string): string { if (!dateStr) return ''; const d = new Date(dateStr); return d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); } function StepCard({ step }: { step: DirectiveStep }) { const status = (step.status || 'pending').toLowerCase() as StepStatus; return (
{step.name} {STATUS_LABELS[status] || status}
{step.description && (

{step.description}

)}
#{step.orderIndex} {status === 'running' && ( In progress... )} {status === 'completed' && step.completedAt && ( Completed {formatTime(step.completedAt)} )}
); } export function StepsDiagramComponent({ directiveId }: StepsDiagramComponentProps) { const [steps, setSteps] = useState([]); const [directiveStatus, setDirectiveStatus] = useState(''); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const intervalRef = useRef | null>(null); const prevStepCountRef = useRef(0); const fetchSteps = useCallback(async () => { try { const data: DirectiveWithSteps = await getDirective(directiveId); setSteps(data.steps || []); setDirectiveStatus(data.status || ''); setError(null); } catch (err) { setError(err instanceof Error ? err.message : 'Failed to load steps'); } finally { setLoading(false); } }, [directiveId]); useEffect(() => { fetchSteps(); intervalRef.current = setInterval(fetchSteps, 5000); return () => { if (intervalRef.current) clearInterval(intervalRef.current); }; }, [fetchSteps]); // Track when new steps appear for animation useEffect(() => { prevStepCountRef.current = steps.length; }, [steps.length]); const completedCount = steps.filter(s => s.status?.toLowerCase() === 'completed').length; const totalCount = steps.length; const isActive = ['active', 'running', 'planning'].includes(directiveStatus.toLowerCase()); const isBuilding = isActive && steps.length === 0; const isAddingSteps = isActive && steps.length > 0 && steps.length > prevStepCountRef.current; // Group steps by orderIndex const groupedSteps: Map = new Map(); const sortedSteps = [...steps].sort((a, b) => a.orderIndex - b.orderIndex); for (const step of sortedSteps) { const idx = step.orderIndex; if (!groupedSteps.has(idx)) groupedSteps.set(idx, []); groupedSteps.get(idx)!.push(step); } const orderGroups = Array.from(groupedSteps.entries()).sort((a, b) => a[0] - b[0]); if (loading) { return (
Steps Authored by Makima
Loading steps...
); } if (error) { return (
Steps Authored by Makima
Failed to load steps: {error}
); } return (
Steps {totalCount > 0 && ( {completedCount}/{totalCount} completed )}
Authored by Makima
{isBuilding && (
Makima is building the plan...
)} {totalCount === 0 && !isBuilding && (
No steps defined yet.
)} {totalCount > 0 && (
{orderGroups.map(([orderIndex, groupSteps], groupIdx) => ( {groupIdx > 0 && (
)}
{groupSteps.map((step) => ( ))}
))}
)}
); }