summaryrefslogtreecommitdiff
path: root/makima/frontend/src/components/directives/StepDiagram.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'makima/frontend/src/components/directives/StepDiagram.tsx')
-rw-r--r--makima/frontend/src/components/directives/StepDiagram.tsx313
1 files changed, 0 insertions, 313 deletions
diff --git a/makima/frontend/src/components/directives/StepDiagram.tsx b/makima/frontend/src/components/directives/StepDiagram.tsx
deleted file mode 100644
index 33892e0..0000000
--- a/makima/frontend/src/components/directives/StepDiagram.tsx
+++ /dev/null
@@ -1,313 +0,0 @@
-import { useNavigate } from "react-router";
-import type { ChainStep, ContractPhase } from "../../lib/api";
-import { PhaseProgressBarCompact } from "../contracts/PhaseProgressBar";
-
-interface StepDiagramProps {
- steps: ChainStep[];
-}
-
-const statusColors: Record<string, { border: string; dot: string; bg: string; glow: string }> = {
- pending: {
- border: "border-[#444]",
- dot: "bg-[#555]",
- bg: "bg-[rgba(40,40,50,0.6)]",
- glow: "",
- },
- running: {
- border: "border-yellow-400/60",
- dot: "bg-yellow-400",
- bg: "bg-[rgba(80,70,20,0.3)]",
- glow: "shadow-[0_0_8px_rgba(250,204,21,0.15)]",
- },
- evaluating: {
- border: "border-blue-400/60",
- dot: "bg-blue-400",
- bg: "bg-[rgba(20,50,80,0.3)]",
- glow: "shadow-[0_0_8px_rgba(96,165,250,0.15)]",
- },
- passed: {
- border: "border-green-400/60",
- dot: "bg-green-400",
- bg: "bg-[rgba(20,60,30,0.3)]",
- glow: "",
- },
- failed: {
- border: "border-red-400/60",
- dot: "bg-red-400",
- bg: "bg-[rgba(60,20,20,0.3)]",
- glow: "",
- },
-};
-
-const statusLabels: Record<string, string> = {
- pending: "Pending",
- ready: "Ready",
- running: "Running",
- evaluating: "Evaluating",
- passed: "Passed",
- failed: "Failed",
- rework: "Rework",
- skipped: "Skipped",
- blocked: "Blocked",
-};
-
-const confidenceColors: Record<string, string> = {
- green: "text-green-400",
- yellow: "text-yellow-400",
- red: "text-red-400",
-};
-
-/**
- * Assign depth to each step via topological sort based on dependsOn UUIDs.
- */
-function assignDepths(steps: ChainStep[]): Map<string, number> {
- const depths = new Map<string, number>();
- const stepMap = new Map(steps.map((s) => [s.id, s]));
-
- function getDepth(id: string): number {
- if (depths.has(id)) return depths.get(id)!;
- const step = stepMap.get(id);
- if (!step || !step.dependsOn || step.dependsOn.length === 0) {
- depths.set(id, 0);
- return 0;
- }
- const maxParent = Math.max(
- ...step.dependsOn.map((depId) => getDepth(depId))
- );
- const d = maxParent + 1;
- depths.set(id, d);
- return d;
- }
-
- for (const step of steps) {
- getDepth(step.id);
- }
-
- return depths;
-}
-
-function StepCard({ step }: { step: ChainStep }) {
- const navigate = useNavigate();
- const colors = statusColors[step.status] || statusColors.pending;
- const summary = step.contractSummary;
- const hasContract = !!step.contractId;
-
- return (
- <div
- className={`
- border ${colors.border} ${colors.bg} ${colors.glow}
- p-3 w-[220px] transition-all duration-200
- ${hasContract ? "cursor-pointer hover:brightness-125" : ""}
- `}
- onClick={() => {
- if (hasContract) navigate(`/contracts/${step.contractId}`);
- }}
- title={hasContract ? "View contract" : undefined}
- >
- {/* Status header */}
- <div className="flex items-center justify-between mb-2">
- <div className="flex items-center gap-2 min-w-0 flex-1">
- <div className={`w-2 h-2 rounded-full ${colors.dot} shrink-0 ${
- step.status === "running" ? "animate-pulse" : ""
- }`} />
- <span className="font-mono text-[11px] text-[#dbe7ff] truncate font-medium">
- {step.name}
- </span>
- </div>
- <span className={`font-mono text-[9px] uppercase tracking-wider shrink-0 ml-2 ${
- step.status === "passed" ? "text-green-400" :
- step.status === "failed" ? "text-red-400" :
- step.status === "running" ? "text-yellow-400" :
- step.status === "evaluating" ? "text-blue-400" :
- "text-[#555]"
- }`}>
- {statusLabels[step.status] || step.status}
- </span>
- </div>
-
- {/* Description */}
- {step.description && (
- <p className="font-mono text-[10px] text-[#7788aa] mb-2 line-clamp-2 leading-relaxed">
- {step.description}
- </p>
- )}
-
- {/* Evaluation info */}
- {(step.confidenceScore != null || step.evaluationCount > 0 || step.reworkCount > 0) && (
- <div className="border-t border-[rgba(117,170,252,0.1)] pt-2 mt-1">
- <div className="flex items-center gap-2 font-mono text-[9px]">
- {step.confidenceScore != null && (
- <span className={confidenceColors[step.confidenceLevel || ""] || "text-[#7788aa]"}>
- {Math.round(step.confidenceScore * 100)}% confidence
- </span>
- )}
- {step.evaluationCount > 0 && (
- <span className="text-[#7788aa]">
- eval #{step.evaluationCount}
- </span>
- )}
- {step.reworkCount > 0 && (
- <span className="text-orange-400">
- rework x{step.reworkCount}
- </span>
- )}
- </div>
- </div>
- )}
-
- {/* Monitoring link (when evaluating) */}
- {step.status === "evaluating" && step.monitoringContractId && (
- <div className="border-t border-[rgba(117,170,252,0.1)] pt-1.5 mt-1">
- <span
- className="font-mono text-[9px] text-blue-400 cursor-pointer hover:text-blue-300"
- onClick={(e) => {
- e.stopPropagation();
- navigate(`/contracts/${step.monitoringContractId}`);
- }}
- >
- evaluation contract &rarr;
- </span>
- </div>
- )}
-
- {/* Contract progress */}
- {summary && (
- <div className="border-t border-[rgba(117,170,252,0.1)] pt-2 mt-1">
- <div className="mb-1.5">
- <PhaseProgressBarCompact
- currentPhase={summary.phase as ContractPhase}
- />
- </div>
- <div className="flex items-center gap-3 font-mono text-[9px]">
- <span className="text-[#7788aa]">
- {summary.tasksDone}/{summary.taskCount} tasks
- </span>
- {summary.tasksRunning > 0 && (
- <span className="text-yellow-400">
- {summary.tasksRunning} running
- </span>
- )}
- {summary.tasksFailed > 0 && (
- <span className="text-red-400">
- {summary.tasksFailed} failed
- </span>
- )}
- </div>
- </div>
- )}
-
- {/* Contract link arrow */}
- {hasContract && !summary && step.status !== "evaluating" && (
- <div className="border-t border-[rgba(117,170,252,0.1)] pt-1.5 mt-1">
- <span className="font-mono text-[9px] text-[#75aafc]">
- view contract &rarr;
- </span>
- </div>
- )}
- </div>
- );
-}
-
-/** Vertical connector between levels */
-function LevelConnector({ count }: { count: number }) {
- return (
- <div className="flex justify-center py-1">
- <div className="flex items-center gap-3">
- {Array.from({ length: count }).map((_, i) => (
- <div key={i} className="flex flex-col items-center">
- <div className="w-px h-4 bg-[rgba(117,170,252,0.25)]" />
- <div className="text-[rgba(117,170,252,0.4)] text-[10px] leading-none">&darr;</div>
- <div className="w-px h-4 bg-[rgba(117,170,252,0.25)]" />
- </div>
- ))}
- </div>
- </div>
- );
-}
-
-export function StepDiagram({ steps }: StepDiagramProps) {
- if (steps.length === 0) {
- return (
- <p className="font-mono text-xs text-[#7788aa]">No steps to display.</p>
- );
- }
-
- const depths = assignDepths(steps);
- const maxDepth = Math.max(...Array.from(depths.values()));
-
- // Group steps by depth level
- const levels: ChainStep[][] = [];
- for (let d = 0; d <= maxDepth; d++) {
- levels.push(
- steps
- .filter((s) => depths.get(s.id) === d)
- .sort((a, b) => a.orderIndex - b.orderIndex)
- );
- }
-
- // Compute overall progress
- const passedCount = steps.filter(s => s.status === "passed").length;
- const failedCount = steps.filter(s => s.status === "failed").length;
- const runningCount = steps.filter(s => s.status === "running").length;
- const evaluatingCount = steps.filter(s => s.status === "evaluating").length;
-
- return (
- <div>
- {/* Progress summary */}
- <div className="flex items-center gap-4 mb-4 font-mono text-[10px]">
- <span className="text-[#7788aa]">
- {levels.length} level{levels.length !== 1 ? "s" : ""} &middot; {steps.length} step{steps.length !== 1 ? "s" : ""}
- </span>
- {passedCount > 0 && (
- <span className="text-green-400">{passedCount} passed</span>
- )}
- {runningCount > 0 && (
- <span className="text-yellow-400">{runningCount} running</span>
- )}
- {evaluatingCount > 0 && (
- <span className="text-blue-400">{evaluatingCount} evaluating</span>
- )}
- {failedCount > 0 && (
- <span className="text-red-400">{failedCount} failed</span>
- )}
- <div className="flex-1 h-1 bg-[rgba(117,170,252,0.1)] rounded-full overflow-hidden">
- <div
- className="h-full bg-green-400/60 rounded-full transition-all duration-500"
- style={{ width: `${(passedCount / steps.length) * 100}%` }}
- />
- </div>
- <span className="text-[#7788aa]">
- {Math.round((passedCount / steps.length) * 100)}%
- </span>
- </div>
-
- {/* Chain flow */}
- <div className="flex flex-col items-center">
- {levels.map((level, li) => (
- <div key={li}>
- {/* Level label */}
- {levels.length > 1 && (
- <div className="flex justify-center mb-2">
- <span className="font-mono text-[9px] text-[#555] uppercase tracking-widest px-2 py-0.5 border border-[rgba(117,170,252,0.1)] bg-[rgba(0,0,0,0.3)]">
- {li === 0 ? "Start" : li === maxDepth ? "Final" : `Level ${li}`}
- </span>
- </div>
- )}
-
- {/* Steps at this level */}
- <div className="flex items-start justify-center gap-3 flex-wrap">
- {level.map((step) => (
- <StepCard key={step.id} step={step} />
- ))}
- </div>
-
- {/* Connector to next level */}
- {li < maxDepth && (
- <LevelConnector count={Math.min(level.length, 3)} />
- )}
- </div>
- ))}
- </div>
- </div>
- );
-}