diff options
| author | soryu <soryu@soryu.co> | 2026-02-08 16:18:13 +0000 |
|---|---|---|
| committer | soryu <soryu@soryu.co> | 2026-02-08 16:18:13 +0000 |
| commit | c0f220582cd61f0d88e42dfbc29d55b3be1e3b19 (patch) | |
| tree | 5105fe5ffb6e80d3e82f806411628b0587c0685c /makima/frontend/src | |
| parent | 62d411f61893486680ded5921a8b86b483ee1144 (diff) | |
| download | soryu-c0f220582cd61f0d88e42dfbc29d55b3be1e3b19.tar.gz soryu-c0f220582cd61f0d88e42dfbc29d55b3be1e3b19.zip | |
Fix directive deletion and stop local only on contracts
Diffstat (limited to 'makima/frontend/src')
| -rw-r--r-- | makima/frontend/src/components/directives/DirectiveDetail.tsx | 188 | ||||
| -rw-r--r-- | makima/frontend/src/components/directives/StepDiagram.tsx | 281 |
2 files changed, 237 insertions, 232 deletions
diff --git a/makima/frontend/src/components/directives/DirectiveDetail.tsx b/makima/frontend/src/components/directives/DirectiveDetail.tsx index 06d8ba2..6bdf5aa 100644 --- a/makima/frontend/src/components/directives/DirectiveDetail.tsx +++ b/makima/frontend/src/components/directives/DirectiveDetail.tsx @@ -3,8 +3,6 @@ import { useNavigate } from "react-router"; import type { DirectiveWithChains, DirectiveStatus, - ChainWithSteps, - ChainStep, ContractPhase, } from "../../lib/api"; import { getDirective } from "../../lib/api"; @@ -32,122 +30,6 @@ const statusColors: Record<DirectiveStatus, string> = { failed: "text-red-400", }; -const stepStatusColors: Record<string, string> = { - pending: "text-[#888]", - running: "text-yellow-400", - passed: "text-green-400", - failed: "text-red-400", -}; - -const stepStatusIcons: Record<string, string> = { - pending: "\u25CB", // ○ - running: "\u25D4", // ◔ - passed: "\u25CF", // ● - failed: "\u2715", // ✕ -}; - -function StepRow({ step }: { step: ChainStep }) { - const navigate = useNavigate(); - const color = stepStatusColors[step.status] || "text-[#888]"; - const icon = stepStatusIcons[step.status] || "\u25CB"; - const summary = step.contractSummary; - - return ( - <div className="flex items-start gap-2 py-1.5 px-2 hover:bg-[rgba(117,170,252,0.05)]"> - <span className={`font-mono text-[11px] ${color} mt-px`}>{icon}</span> - <div className="flex-1 min-w-0"> - <div className="flex items-center gap-2"> - <span className="font-mono text-[11px] text-[#dbe7ff] truncate"> - {step.name} - </span> - <span className={`font-mono text-[9px] uppercase ${color}`}> - {step.status} - </span> - </div> - {summary && ( - <div className="flex items-center gap-2 mt-0.5"> - <PhaseProgressBarCompact - currentPhase={summary.phase as ContractPhase} - /> - <span className="font-mono text-[9px] text-[#7788aa]"> - {summary.tasksDone}/{summary.taskCount} tasks - </span> - {step.contractId && ( - <button - onClick={(e) => { - e.stopPropagation(); - navigate(`/contracts/${step.contractId}`); - }} - className="font-mono text-[9px] text-[#75aafc] hover:text-white transition-colors" - > - contract → - </button> - )} - </div> - )} - {!summary && step.contractId && ( - <button - onClick={() => navigate(`/contracts/${step.contractId}`)} - className="font-mono text-[9px] text-[#75aafc] hover:text-white transition-colors mt-0.5" - > - contract → - </button> - )} - {step.description && ( - <p className="font-mono text-[10px] text-[#7788aa] truncate mt-0.5"> - {step.description} - </p> - )} - </div> - </div> - ); -} - -function ChainCard({ chainWithSteps }: { chainWithSteps: ChainWithSteps }) { - const chain = chainWithSteps; - const steps = chainWithSteps.steps || []; - - return ( - <div className="border border-dashed border-[rgba(117,170,252,0.25)] bg-[rgba(117,170,252,0.03)]"> - <div className="p-3"> - <div className="flex items-center justify-between mb-1"> - <span className="font-mono text-xs text-[#dbe7ff]"> - {chain.name} - </span> - <span className="font-mono text-[10px] text-[#7788aa] uppercase"> - gen {chain.generation} · {chain.status} - </span> - </div> - {chain.description && ( - <p className="font-mono text-[11px] text-[#7788aa] mb-1"> - {chain.description} - </p> - )} - <div className="flex gap-3 font-mono text-[10px] text-[#7788aa]"> - <span> - {chain.completedSteps}/{chain.totalSteps} steps - </span> - {chain.failedSteps > 0 && ( - <span className="text-red-400">{chain.failedSteps} failed</span> - )} - {chain.currentConfidence != null && ( - <span> - confidence: {(chain.currentConfidence * 100).toFixed(0)}% - </span> - )} - </div> - </div> - {steps.length > 0 && ( - <div className="border-t border-dashed border-[rgba(117,170,252,0.15)]"> - {steps.map((step) => ( - <StepRow key={step.id} step={step} /> - ))} - </div> - )} - </div> - ); -} - function JsonSection({ label, data, @@ -484,39 +366,53 @@ export function DirectiveDetail({ {activeTab === "chain" && ( <div className="space-y-4"> - {/* Step diagram */} - {directive.chains.length > 0 && ( - <div> - <h4 className="font-mono text-[10px] text-[#75aafc] uppercase tracking-wider mb-2"> - Step Dependencies - </h4> - <StepDiagram - steps={directive.chains.flatMap((c) => c.steps)} - /> - </div> - )} - - {/* Chain cards */} - <div> - <h4 className="font-mono text-[10px] text-[#75aafc] uppercase tracking-wider mb-2"> - Chains ({directive.chains.length}) - </h4> - {directive.chains.length === 0 ? ( - <p className="font-mono text-xs text-[#7788aa]"> + {directive.chains.length === 0 ? ( + <div className="flex flex-col items-center justify-center py-12"> + <div className="font-mono text-[40px] text-[#333] mb-3"> + {directive.status === "planning" ? "\u2699" : "\u25CB"} + </div> + <p className="font-mono text-xs text-[#7788aa] text-center max-w-[300px]"> {directive.status === "planning" - ? "Planning in progress... chains will appear when the planner completes." + ? "Planning in progress... the chain will appear when the planner submits a plan." : directive.status === "draft" ? "No chains yet. Start the directive to begin planning." : "No chains created for this directive."} </p> - ) : ( - <div className="space-y-2"> - {directive.chains.map((cws) => ( - <ChainCard key={cws.id} chainWithSteps={cws} /> - ))} - </div> - )} - </div> + </div> + ) : ( + <> + {/* Chain metadata header */} + {directive.chains.map((chain) => ( + <div key={chain.id} className="space-y-3"> + <div className="flex items-center gap-3 pb-2 border-b border-dashed border-[rgba(117,170,252,0.15)]"> + <span className="font-mono text-xs text-[#dbe7ff]"> + {chain.name} + </span> + <span className="font-mono text-[10px] text-[#7788aa] uppercase"> + gen {chain.generation} + </span> + <span className={`font-mono text-[10px] uppercase ${ + chain.status === "completed" ? "text-green-400" : + chain.status === "failed" ? "text-red-400" : + chain.status === "running" ? "text-yellow-400" : + "text-[#7788aa]" + }`}> + {chain.status} + </span> + <span className="font-mono text-[10px] text-[#7788aa] ml-auto"> + {chain.completedSteps}/{chain.totalSteps} steps + {chain.failedSteps > 0 && ( + <span className="text-red-400 ml-1"> + ({chain.failedSteps} failed) + </span> + )} + </span> + </div> + <StepDiagram steps={chain.steps} /> + </div> + ))} + </> + )} </div> )} diff --git a/makima/frontend/src/components/directives/StepDiagram.tsx b/makima/frontend/src/components/directives/StepDiagram.tsx index 5c65ae1..91a3438 100644 --- a/makima/frontend/src/components/directives/StepDiagram.tsx +++ b/makima/frontend/src/components/directives/StepDiagram.tsx @@ -6,23 +6,49 @@ interface StepDiagramProps { steps: ChainStep[]; } -const statusBorderColors: Record<string, string> = { - pending: "border-[#555]", - running: "border-yellow-400", - passed: "border-green-400", - failed: "border-red-400", +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 statusDotColors: Record<string, string> = { - pending: "bg-[#555]", - running: "bg-yellow-400", - passed: "bg-green-400", - failed: "bg-red-400", +const statusLabels: Record<string, string> = { + pending: "Pending", + running: "Running", + evaluating: "Evaluating", + passed: "Passed", + failed: "Failed", }; /** - * Assign depth to each step via topological sort. - * Steps with no dependsOn = depth 0. Steps depending only on depth-0 = depth 1. Etc. + * 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>(); @@ -50,9 +76,108 @@ function assignDepths(steps: ChainStep[]): Map<string, number> { return depths; } -export function StepDiagram({ steps }: StepDiagramProps) { +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> + )} + + {/* 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 && ( + <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> @@ -62,7 +187,7 @@ export function StepDiagram({ steps }: StepDiagramProps) { const depths = assignDepths(steps); const maxDepth = Math.max(...Array.from(depths.values())); - // Group steps by depth + // Group steps by depth level const levels: ChainStep[][] = []; for (let d = 0; d <= maxDepth; d++) { levels.push( @@ -72,81 +197,65 @@ export function StepDiagram({ steps }: StepDiagramProps) { ); } - // Build position map for connectors - const stepPositions = new Map<string, { level: number; index: number }>(); - levels.forEach((level, li) => { - level.forEach((step, si) => { - stepPositions.set(step.id, { level: li, index: si }); - }); - }); + // 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" || s.status === "evaluating").length; return ( - <div className="space-y-3"> - {levels.map((level, li) => ( - <div key={li} className="flex items-start gap-2 flex-wrap"> - {li > 0 && ( - <div className="w-full flex justify-center mb-1"> - <div className="w-px h-3 bg-[rgba(117,170,252,0.3)]" /> - </div> - )} - {level.map((step) => { - const borderColor = - statusBorderColors[step.status] || "border-[#555]"; - const dotColor = statusDotColors[step.status] || "bg-[#555]"; - const summary = step.contractSummary; - const hasContract = !!step.contractId; - - return ( - <div - key={step.id} - className={` - border ${borderColor} bg-[rgba(0,0,0,0.2)] p-2 min-w-[180px] max-w-[220px] - ${hasContract ? "cursor-pointer hover:bg-[rgba(117,170,252,0.05)]" : ""} - transition-colors - `} - onClick={() => { - if (hasContract) navigate(`/contracts/${step.contractId}`); - }} - title={hasContract ? "View contract" : undefined} - > - <div className="flex items-center gap-1.5 mb-1"> - <div className={`w-1.5 h-1.5 rounded-full ${dotColor}`} /> - <span className="font-mono text-[11px] text-[#dbe7ff] truncate flex-1"> - {step.name} - </span> - {hasContract && ( - <span className="font-mono text-[9px] text-[#75aafc] shrink-0"> - → - </span> - )} - </div> - {summary && ( - <> - <div className="mb-1"> - <PhaseProgressBarCompact - currentPhase={summary.phase as ContractPhase} - /> - </div> - <div className="font-mono text-[9px] text-[#7788aa]"> - {summary.tasksDone}/{summary.taskCount} tasks - {summary.tasksRunning > 0 && ( - <span className="text-yellow-400 ml-1"> - {summary.tasksRunning} running - </span> - )} - {summary.tasksFailed > 0 && ( - <span className="text-red-400 ml-1"> - {summary.tasksFailed} failed - </span> - )} - </div> - </> - )} - </div> - ); - })} + <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} active</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> ); } |
