import { useMemo } from "react"; import type { DirectiveStep } from "../../lib/api"; import { StepNode } from "./StepNode"; import { OrchestratorStepNode, type OrchestratorStepType, type OrchestratorStepStatus, } from "./OrchestratorStepNode"; export interface VirtualStep { type: OrchestratorStepType; taskId: string; status: OrchestratorStepStatus; label: string; hasQuestions?: boolean; } export interface SpecializedStep { id: string; name: string; type: "orchestrator" | "completion"; taskId: string; status: "running" | "completed"; } interface DirectiveDAGProps { steps: DirectiveStep[]; specializedSteps?: SpecializedStep[]; onComplete?: (stepId: string) => void; onFail?: (stepId: string) => void; onSkip?: (stepId: string) => void; } interface Layer { steps: DirectiveStep[]; } /** Types that should appear before the regular DAG steps */ const BEFORE_TYPES = new Set([ "planning", "replanning", "plan-orders", ]); function topoSort(steps: DirectiveStep[]): Layer[] { if (steps.length === 0) return []; // Group steps by orderIndex — each unique orderIndex is one execution phase const byOrder = new Map(); for (const step of steps) { const group = byOrder.get(step.orderIndex) ?? []; group.push(step); byOrder.set(step.orderIndex, group); } // Sort groups by ascending orderIndex const sortedKeys = [...byOrder.keys()].sort((a, b) => a - b); return sortedKeys.map((key) => ({ steps: byOrder.get(key)!.sort((a, b) => a.name.localeCompare(b.name)), })); } export function DirectiveDAG({ steps, specializedSteps, onComplete, onFail, onSkip }: DirectiveDAGProps) { const layers = useMemo(() => topoSort(steps), [steps]); const orchestratorSteps = specializedSteps?.filter(s => s.type === "orchestrator") ?? []; const completionSteps = specializedSteps?.filter(s => s.type === "completion") ?? []; if (steps.length === 0 && orchestratorSteps.length === 0 && completionSteps.length === 0) { return (
No steps yet. Add steps to build the DAG.
); } return (
{/* Orchestrator steps (Planning/Cleanup/Orders) - rendered above regular steps */} {orchestratorSteps.map(step => ( ))} {/* Connector line if both orchestrator step and regular steps exist */} {orchestratorSteps.length > 0 && layers.length > 0 && (
)} {/* Regular step layers */} {layers.map((layer, layerIdx) => (
{layerIdx > 0 && (
)}
{afterSteps.map((vs) => ( ))}
))} {/* Connector line if both regular steps and completion step exist */} {completionSteps.length > 0 && layers.length > 0 && (
)} {/* Completion steps (PR creation) - rendered below regular steps */} {completionSteps.map(step => ( ))}
); } function SpecializedStepNode({ step }: { step: SpecializedStep }) { const themeColors = step.type === "orchestrator" ? { bg: "bg-[#1a1a30]", border: "border-[rgba(117,170,252,0.3)]", text: "text-[#75aafc]", dot: "bg-[#75aafc]", label: step.name.startsWith("Cleanup") ? "CLEANUP" : step.name.startsWith("Pick up") ? "ORDERS" : "PLANNING", } : { bg: "bg-[#1a1a10]", border: "border-yellow-900/50", text: "text-yellow-400", dot: "bg-yellow-400", label: "PR", }; return (
{themeColors.label} {step.name} View task
); }