diff options
Diffstat (limited to 'makima/frontend/src/components/directives/ChainTab.tsx')
| -rw-r--r-- | makima/frontend/src/components/directives/ChainTab.tsx | 212 |
1 files changed, 212 insertions, 0 deletions
diff --git a/makima/frontend/src/components/directives/ChainTab.tsx b/makima/frontend/src/components/directives/ChainTab.tsx new file mode 100644 index 0000000..ccefe81 --- /dev/null +++ b/makima/frontend/src/components/directives/ChainTab.tsx @@ -0,0 +1,212 @@ +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 ( + <div className="text-center py-8"> + <p className="font-mono text-sm text-[#556677]"> + No chain generated yet. Start the directive to generate a chain. + </p> + </div> + ); + } + + return ( + <div className="space-y-4"> + {/* Chain info header */} + <div className="flex items-center justify-between p-3 bg-[rgba(117,170,252,0.05)] border border-[rgba(117,170,252,0.15)]"> + <div> + <div className="font-mono text-sm text-[#dbe7ff]">{directive.chain.name}</div> + <div className="font-mono text-[10px] text-[#556677]">Generation {directive.chain.generation}</div> + </div> + <div className="flex items-center gap-4"> + <div className="font-mono text-xs text-[#556677]"> + {directive.chain.completedSteps}/{directive.chain.totalSteps} steps + </div> + {/* View toggle */} + <div className="flex border border-[rgba(117,170,252,0.2)]"> + <button + onClick={() => setViewMode("dag")} + className={`px-2 py-1 font-mono text-[10px] uppercase ${ + viewMode === "dag" + ? "bg-[rgba(117,170,252,0.2)] text-[#75aafc]" + : "text-[#556677] hover:text-[#75aafc]" + }`} + > + DAG + </button> + <button + onClick={() => setViewMode("list")} + className={`px-2 py-1 font-mono text-[10px] uppercase ${ + viewMode === "list" + ? "bg-[rgba(117,170,252,0.2)] text-[#75aafc]" + : "text-[#556677] hover:text-[#75aafc]" + }`} + > + List + </button> + </div> + </div> + </div> + + {viewMode === "dag" ? ( + /* DAG visualization */ + <div className="h-[400px] border border-[rgba(117,170,252,0.1)] rounded bg-[#050d18]"> + {directive.steps.length === 0 ? ( + <div className="flex items-center justify-center h-full"> + <p className="font-mono text-sm text-[#556677]">No steps in chain</p> + </div> + ) : ( + <ReactFlow + nodes={nodes} + edges={edges} + nodeTypes={nodeTypes} + fitView + fitViewOptions={{ padding: 0.2 }} + minZoom={0.5} + maxZoom={1.5} + defaultEdgeOptions={{ + type: "smoothstep", + style: { stroke: "#556677", strokeWidth: 2 }, + }} + > + <Background variant={BackgroundVariant.Dots} gap={20} size={1} color="#1a2a3a" /> + <Controls className="!bg-[#0a1628] !border-[rgba(117,170,252,0.2)]" /> + </ReactFlow> + )} + </div> + ) : ( + /* List view */ + <div className="space-y-2"> + <h3 className="font-mono text-xs text-[#75aafc] uppercase">Steps</h3> + {directive.steps.length === 0 ? ( + <p className="font-mono text-sm text-[#556677]">No steps in chain</p> + ) : ( + directive.steps.map((step) => { + const styles = stepStatusStyles[step.status] || stepStatusStyles.pending; + + return ( + <div + key={step.id} + className="p-3 bg-[rgba(117,170,252,0.02)] border border-[rgba(117,170,252,0.1)]" + > + <div className="flex items-center justify-between"> + <div className="flex items-center gap-2"> + <span className="font-mono text-sm text-[#dbe7ff]">{step.name}</span> + <span + className="px-1.5 py-0.5 font-mono text-[10px] uppercase border" + style={{ color: styles.text, borderColor: `${styles.border}50` }} + > + {step.status} + </span> + {step.confidenceScore !== null && ( + <span className="font-mono text-[10px] text-[#556677]"> + ({Math.round(step.confidenceScore * 100)}%) + </span> + )} + </div> + <div className="font-mono text-[10px] text-[#556677]">{step.stepType}</div> + </div> + {step.description && ( + <p className="font-mono text-xs text-[#556677] mt-1">{step.description}</p> + )} + {step.dependsOn?.length > 0 && ( + <div className="font-mono text-[10px] text-[#556677] mt-1"> + Depends on: {step.dependsOn.join(", ")} + </div> + )} + </div> + ); + }) + )} + </div> + )} + </div> + ); +} |
