summaryrefslogtreecommitdiff
path: root/makima/frontend/src/components/directives/DirectiveDAG.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'makima/frontend/src/components/directives/DirectiveDAG.tsx')
-rw-r--r--makima/frontend/src/components/directives/DirectiveDAG.tsx114
1 files changed, 105 insertions, 9 deletions
diff --git a/makima/frontend/src/components/directives/DirectiveDAG.tsx b/makima/frontend/src/components/directives/DirectiveDAG.tsx
index 27a80ac..8c7def9 100644
--- a/makima/frontend/src/components/directives/DirectiveDAG.tsx
+++ b/makima/frontend/src/components/directives/DirectiveDAG.tsx
@@ -1,9 +1,31 @@
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;
@@ -13,6 +35,13 @@ interface Layer {
steps: DirectiveStep[];
}
+/** Types that should appear before the regular DAG steps */
+const BEFORE_TYPES = new Set<OrchestratorStepType>([
+ "planning",
+ "replanning",
+ "plan-orders",
+]);
+
function topoSort(steps: DirectiveStep[]): Layer[] {
if (steps.length === 0) return [];
@@ -31,10 +60,13 @@ function topoSort(steps: DirectiveStep[]): Layer[] {
}));
}
-export function DirectiveDAG({ steps, onComplete, onFail, onSkip }: DirectiveDAGProps) {
+export function DirectiveDAG({ steps, specializedSteps, onComplete, onFail, onSkip }: DirectiveDAGProps) {
const layers = useMemo(() => topoSort(steps), [steps]);
- if (steps.length === 0) {
+ 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 (
<div className="text-center py-8 text-[#7788aa] font-mono text-sm">
No steps yet. Add steps to build the DAG.
@@ -44,6 +76,19 @@ export function DirectiveDAG({ steps, onComplete, onFail, onSkip }: DirectiveDAG
return (
<div className="flex flex-col gap-4 items-center py-4">
+ {/* Orchestrator steps (Planning/Cleanup/Orders) - rendered above regular steps */}
+ {orchestratorSteps.map(step => (
+ <SpecializedStepNode key={step.id} step={step} />
+ ))}
+
+ {/* Connector line if both orchestrator step and regular steps exist */}
+ {orchestratorSteps.length > 0 && layers.length > 0 && (
+ <div className="flex justify-center py-1">
+ <div className="w-px h-4 bg-[rgba(117,170,252,0.2)]" />
+ </div>
+ )}
+
+ {/* Regular step layers */}
{layers.map((layer, layerIdx) => (
<div key={layerIdx}>
{layerIdx > 0 && (
@@ -52,18 +97,69 @@ export function DirectiveDAG({ steps, onComplete, onFail, onSkip }: DirectiveDAG
</div>
)}
<div className="flex flex-wrap gap-3 justify-center">
- {layer.steps.map((step) => (
- <StepNode
- key={step.id}
- step={step}
- onComplete={onComplete ? () => onComplete(step.id) : undefined}
- onFail={onFail ? () => onFail(step.id) : undefined}
- onSkip={onSkip ? () => onSkip(step.id) : undefined}
+ {afterSteps.map((vs) => (
+ <OrchestratorStepNode
+ key={`${vs.type}-${vs.taskId}`}
+ type={vs.type}
+ taskId={vs.taskId}
+ status={vs.status}
+ label={vs.label}
+ hasQuestions={vs.hasQuestions}
/>
))}
</div>
</div>
))}
+
+ {/* Connector line if both regular steps and completion step exist */}
+ {completionSteps.length > 0 && layers.length > 0 && (
+ <div className="flex justify-center py-1">
+ <div className="w-px h-4 bg-[rgba(117,170,252,0.2)]" />
+ </div>
+ )}
+
+ {/* Completion steps (PR creation) - rendered below regular steps */}
+ {completionSteps.map(step => (
+ <SpecializedStepNode key={step.id} step={step} />
+ ))}
+ </div>
+ );
+}
+
+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 (
+ <div className={`flex items-center gap-2 px-3 py-2 ${themeColors.bg} border ${themeColors.border} rounded-lg mx-2`}>
+ <span className={`inline-block w-2 h-2 rounded-full ${themeColors.dot} animate-pulse`} />
+ <span className={`text-[9px] font-mono uppercase tracking-wide ${themeColors.text} opacity-60`}>
+ {themeColors.label}
+ </span>
+ <span className={`text-[11px] font-mono ${themeColors.text} flex-1 truncate`}>
+ {step.name}
+ </span>
+ <a
+ href={`/mesh/${step.taskId}`}
+ className="text-[9px] font-mono text-[#556677] hover:text-white underline"
+ >
+ View task
+ </a>
</div>
);
}