diff options
Diffstat (limited to 'makima/frontend/src/components/directives/StepDiagram.tsx')
| -rw-r--r-- | makima/frontend/src/components/directives/StepDiagram.tsx | 313 |
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 → - </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 → - </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">↓</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" : ""} · {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> - ); -} |
