import { useState, useMemo } from "react"; import { ReactFlow, Edge, Controls, Background, BackgroundVariant, MarkerType, } from "@xyflow/react"; import "@xyflow/react/dist/style.css"; import type { DirectiveWithProgress, DirectiveGraphResponse } from "../../lib/api"; import { StepNodeComponent, stepStatusStyles } from "./StepNode"; // Node types for React Flow const nodeTypes = { step: StepNodeComponent, }; export function ChainTab({ directive, graph }: { directive: DirectiveWithProgress; graph: DirectiveGraphResponse | null }) { const [viewMode, setViewMode] = useState<"dag" | "list">("dag"); // Convert graph to React Flow nodes and edges const { nodes, edges } = useMemo(() => { if (!graph || !graph.nodes.length) { // Fallback: generate positions from directive.steps const stepNodes = directive.steps.map((step, index) => ({ id: step.id, type: "step" as const, position: { x: (index % 3) * 220 + 50, y: Math.floor(index / 3) * 120 + 50, }, data: { id: step.id, name: step.name, stepType: step.stepType, status: step.status, confidenceScore: step.confidenceScore, confidenceLevel: step.confidenceLevel, contractId: step.contractId, editorX: null, editorY: null, }, })); // Build edges from dependencies const stepEdges: Edge[] = []; directive.steps.forEach((step) => { (step.dependsOn ?? []).forEach((depName) => { const depStep = directive.steps.find((s) => s.name === depName); if (depStep) { stepEdges.push({ id: `${depStep.id}-${step.id}`, source: depStep.id, target: step.id, type: "smoothstep", markerEnd: { type: MarkerType.ArrowClosed, color: "#556677" }, style: { stroke: "#556677", strokeWidth: 2 }, }); } }); }); return { nodes: stepNodes, edges: stepEdges }; } // Use graph data const graphNodes = graph.nodes.map((node) => ({ id: node.id, type: "step" as const, position: { x: node.editorX ?? 50, y: node.editorY ?? 50, }, data: { ...node }, })); const graphEdges: Edge[] = graph.edges.map((edge) => ({ id: `${edge.source}-${edge.target}`, source: edge.source, target: edge.target, type: "smoothstep", markerEnd: { type: MarkerType.ArrowClosed, color: "#556677" }, style: { stroke: "#556677", strokeWidth: 2 }, })); return { nodes: graphNodes, edges: graphEdges }; }, [graph, directive.steps]); if (!directive.chain) { return (

No chain generated yet. Start the directive to generate a chain.

); } return (
{/* Chain info header */}
{directive.chain.name}
Generation {directive.chain.generation}
{directive.chain.completedSteps}/{directive.chain.totalSteps} steps
{/* View toggle */}
{viewMode === "dag" ? ( /* DAG visualization */
{directive.steps.length === 0 ? (

No steps in chain

) : ( )}
) : ( /* List view */

Steps

{directive.steps.length === 0 ? (

No steps in chain

) : ( directive.steps.map((step) => { const styles = stepStatusStyles[step.status] || stepStatusStyles.pending; return (
{step.name} {step.status} {step.confidenceScore !== null && ( ({Math.round(step.confidenceScore * 100)}%) )}
{step.stepType}
{step.description && (

{step.description}

)} {step.dependsOn?.length > 0 && (
Depends on: {step.dependsOn.join(", ")}
)}
); }) )}
)}
); }