diff options
| author | soryu <soryu@soryu.co> | 2026-02-07 18:27:54 +0000 |
|---|---|---|
| committer | soryu <soryu@soryu.co> | 2026-02-07 18:27:54 +0000 |
| commit | 97e21c8296ec5f91912d56980ebf3b18a1ca3507 (patch) | |
| tree | 3650e2eb62ab5b387006563ce64139aa7688da5f /makima/frontend/src | |
| parent | 8f757f561eeb397aaea70d7c10d41445cc5e50b5 (diff) | |
| download | soryu-97e21c8296ec5f91912d56980ebf3b18a1ca3507.tar.gz soryu-97e21c8296ec5f91912d56980ebf3b18a1ca3507.zip | |
Add directive monitor contracts
Diffstat (limited to 'makima/frontend/src')
| -rw-r--r-- | makima/frontend/src/components/directives/DirectiveContractsTab.tsx | 131 | ||||
| -rw-r--r-- | makima/frontend/src/components/directives/DirectiveDetail.tsx | 393 | ||||
| -rw-r--r-- | makima/frontend/src/components/directives/StepDiagram.tsx | 152 | ||||
| -rw-r--r-- | makima/frontend/src/lib/api.ts | 14 |
4 files changed, 585 insertions, 105 deletions
diff --git a/makima/frontend/src/components/directives/DirectiveContractsTab.tsx b/makima/frontend/src/components/directives/DirectiveContractsTab.tsx new file mode 100644 index 0000000..59ebfc8 --- /dev/null +++ b/makima/frontend/src/components/directives/DirectiveContractsTab.tsx @@ -0,0 +1,131 @@ +import { useNavigate } from "react-router"; +import type { + DirectiveWithChains, + StepContractSummary, + ContractPhase, +} from "../../lib/api"; +import { PhaseProgressBarCompact } from "../contracts/PhaseProgressBar"; + +interface DirectiveContractsTabProps { + directive: DirectiveWithChains; +} + +const statusColors: Record<string, string> = { + active: "text-green-400", + completed: "text-blue-400", + archived: "text-[#555]", +}; + +function ContractCard({ + summary, + label, +}: { + summary: StepContractSummary; + label: string; +}) { + const navigate = useNavigate(); + + const progressPct = + summary.taskCount > 0 + ? Math.round((summary.tasksDone / summary.taskCount) * 100) + : 0; + + return ( + <div + className="border border-dashed border-[rgba(117,170,252,0.25)] bg-[rgba(117,170,252,0.03)] p-3 cursor-pointer hover:bg-[rgba(117,170,252,0.06)] transition-colors" + onClick={() => navigate(`/contracts/${summary.id}`)} + > + <div className="flex items-center justify-between mb-1.5"> + <div className="flex items-center gap-2 min-w-0"> + <span className="font-mono text-[11px] text-[#dbe7ff] truncate"> + {summary.name} + </span> + <span className="font-mono text-[9px] text-[#7788aa] uppercase shrink-0"> + {summary.contractType} + </span> + </div> + <div className="flex items-center gap-2 shrink-0"> + <span + className={`font-mono text-[9px] uppercase ${statusColors[summary.status] || "text-[#888]"}`} + > + {summary.status} + </span> + <span className="font-mono text-[9px] text-[#75aafc]">→</span> + </div> + </div> + + <div className="flex items-center gap-2 mb-1.5"> + <span className="font-mono text-[9px] text-[#7788aa] uppercase shrink-0"> + {label} + </span> + <PhaseProgressBarCompact + currentPhase={summary.phase as ContractPhase} + /> + </div> + + {/* Task progress bar */} + <div className="flex items-center gap-2"> + <div className="flex-1 h-1 bg-[rgba(117,170,252,0.1)] rounded-full overflow-hidden"> + <div + className="h-full bg-[#3f6fb3] rounded-full transition-all" + style={{ width: `${progressPct}%` }} + /> + </div> + <span className="font-mono text-[9px] text-[#7788aa] shrink-0"> + {summary.tasksDone}/{summary.taskCount} tasks + </span> + </div> + </div> + ); +} + +export function DirectiveContractsTab({ + directive, +}: DirectiveContractsTabProps) { + // Collect all contract summaries + const contracts: { summary: StepContractSummary; label: string }[] = []; + + if (directive.orchestratorContractSummary) { + contracts.push({ + summary: directive.orchestratorContractSummary, + label: "Planning", + }); + } + + for (const chain of directive.chains) { + for (const step of chain.steps) { + if (step.contractSummary) { + contracts.push({ + summary: step.contractSummary, + label: step.name, + }); + } + } + } + + if (contracts.length === 0) { + return ( + <div className="text-center py-8"> + <p className="font-mono text-xs text-[#7788aa]"> + {directive.status === "draft" + ? "No contracts yet. Start the directive to begin planning." + : directive.status === "planning" + ? "Planning in progress... contracts will appear when steps are created." + : "No contracts associated with this directive."} + </p> + </div> + ); + } + + return ( + <div className="space-y-2"> + {contracts.map((c) => ( + <ContractCard + key={c.summary.id} + summary={c.summary} + label={c.label} + /> + ))} + </div> + ); +} diff --git a/makima/frontend/src/components/directives/DirectiveDetail.tsx b/makima/frontend/src/components/directives/DirectiveDetail.tsx index 094cdf2..95dc7cc 100644 --- a/makima/frontend/src/components/directives/DirectiveDetail.tsx +++ b/makima/frontend/src/components/directives/DirectiveDetail.tsx @@ -1,12 +1,16 @@ -import { useEffect, useRef } from "react"; +import { useState, useEffect, useRef } from "react"; import { useNavigate } from "react-router"; import type { DirectiveWithChains, DirectiveStatus, ChainWithSteps, ChainStep, + ContractPhase, } from "../../lib/api"; import { getDirective } from "../../lib/api"; +import { PhaseProgressBarCompact } from "../contracts/PhaseProgressBar"; +import { StepDiagram } from "./StepDiagram"; +import { DirectiveContractsTab } from "./DirectiveContractsTab"; interface DirectiveDetailProps { directive: DirectiveWithChains; @@ -16,6 +20,8 @@ interface DirectiveDetailProps { onRefresh?: (updated: DirectiveWithChains) => void; } +type Tab = "overview" | "chain" | "contracts"; + const statusColors: Record<DirectiveStatus, string> = { draft: "text-[#888]", planning: "text-yellow-400", @@ -36,17 +42,18 @@ const stepStatusColors: Record<string, string> = { const stepStatusIcons: Record<string, string> = { pending: "\u25CB", // ○ running: "\u25D4", // ◔ - passed: "\u25CF", // ● - failed: "\u2715", // ✕ + 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 px-2 hover:bg-[rgba(117,170,252,0.05)]"> + <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"> @@ -57,21 +64,41 @@ function StepRow({ step }: { step: ChainStep }) { {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"> + <p className="font-mono text-[10px] text-[#7788aa] truncate mt-0.5"> {step.description} </p> )} </div> - {step.contractId && ( - <button - onClick={() => navigate(`/contracts/${step.contractId}`)} - className="font-mono text-[9px] text-[#75aafc] hover:text-white transition-colors shrink-0" - title="View contract" - > - contract → - </button> - )} </div> ); } @@ -84,7 +111,9 @@ function ChainCard({ chainWithSteps }: { chainWithSteps: ChainWithSteps }) { <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-xs text-[#dbe7ff]"> + {chain.name} + </span> <span className="font-mono text-[10px] text-[#7788aa] uppercase"> gen {chain.generation} · {chain.status} </span> @@ -102,7 +131,9 @@ function ChainCard({ chainWithSteps }: { chainWithSteps: ChainWithSteps }) { <span className="text-red-400">{chain.failedSteps} failed</span> )} {chain.currentConfidence != null && ( - <span>confidence: {(chain.currentConfidence * 100).toFixed(0)}%</span> + <span> + confidence: {(chain.currentConfidence * 100).toFixed(0)}% + </span> )} </div> </div> @@ -151,6 +182,7 @@ export function DirectiveDetail({ onRefresh, }: DirectiveDetailProps) { const navigate = useNavigate(); + const [activeTab, setActiveTab] = useState<Tab>("overview"); // Auto-poll when directive is in an active state const isLive = @@ -185,6 +217,31 @@ export function DirectiveDetail({ }; }, [isLive, directive.id, onRefresh]); + // Count total steps and completed steps across all chains + const totalSteps = directive.chains.reduce( + (sum, c) => sum + c.totalSteps, + 0 + ); + const completedSteps = directive.chains.reduce( + (sum, c) => sum + c.completedSteps, + 0 + ); + + // Count contracts + const contractCount = + (directive.orchestratorContractSummary ? 1 : 0) + + directive.chains.reduce( + (sum, c) => + sum + c.steps.filter((s) => s.contractSummary != null).length, + 0 + ); + + const tabs: { key: Tab; label: string; count?: number }[] = [ + { key: "overview", label: "Overview" }, + { key: "chain", label: "Chain", count: totalSteps }, + { key: "contracts", label: "Contracts", count: contractCount }, + ]; + return ( <div className="panel h-full flex flex-col"> {/* Header */} @@ -235,109 +292,235 @@ export function DirectiveDetail({ </h2> </div> - {/* Content */} - <div className="flex-1 overflow-y-auto p-4 space-y-4"> - {/* Orchestrator contract link */} - {directive.orchestratorContractId && ( - <div className="flex items-center gap-2 p-2 border border-dashed border-[rgba(117,170,252,0.2)] bg-[rgba(117,170,252,0.03)]"> - <span className="font-mono text-[10px] text-[#7788aa] uppercase"> - Planning Contract - </span> - <button - onClick={() => - navigate(`/contracts/${directive.orchestratorContractId}`) + {/* Tabs */} + <div className="flex border-b border-[rgba(117,170,252,0.2)]"> + {tabs.map((tab) => ( + <button + key={tab.key} + onClick={() => setActiveTab(tab.key)} + className={` + px-4 py-2 font-mono text-xs uppercase tracking-wider transition-colors + ${ + activeTab === tab.key + ? "text-[#dbe7ff] border-b-2 border-[#75aafc]" + : "text-[#555] hover:text-[#9bc3ff]" } - className="font-mono text-[11px] text-[#75aafc] hover:text-white transition-colors" - > - {directive.orchestratorContractId.slice(0, 8)}... → - </button> - {directive.status === "planning" && ( - <span className="font-mono text-[9px] text-yellow-400 animate-pulse"> - planning in progress + `} + > + {tab.label} + {tab.count != null && tab.count > 0 && ( + <span className="ml-1 text-[10px] text-[#7788aa]"> + ({tab.count}) </span> )} - </div> - )} + </button> + ))} + </div> - {/* Goal */} - <div> - <h4 className="font-mono text-[10px] text-[#75aafc] uppercase tracking-wider mb-1"> - Goal - </h4> - <p className="font-mono text-xs text-[#9bb8d8] whitespace-pre-wrap"> - {directive.goal} - </p> - </div> + {/* Tab content */} + <div className="flex-1 overflow-y-auto p-4"> + {activeTab === "overview" && ( + <div className="space-y-4"> + {/* Orchestrator contract link */} + {directive.orchestratorContractId && ( + <div className="flex items-center gap-2 p-2 border border-dashed border-[rgba(117,170,252,0.2)] bg-[rgba(117,170,252,0.03)]"> + <span className="font-mono text-[10px] text-[#7788aa] uppercase"> + Planning Contract + </span> + {directive.orchestratorContractSummary && ( + <PhaseProgressBarCompact + currentPhase={ + directive.orchestratorContractSummary + .phase as ContractPhase + } + /> + )} + <button + onClick={() => + navigate( + `/contracts/${directive.orchestratorContractId}` + ) + } + className="font-mono text-[11px] text-[#75aafc] hover:text-white transition-colors" + > + {directive.orchestratorContractSummary?.name || + directive.orchestratorContractId.slice(0, 8) + "..."}{" "} + → + </button> + {directive.status === "planning" && ( + <span className="font-mono text-[9px] text-yellow-400 animate-pulse"> + planning in progress + </span> + )} + </div> + )} - {/* Config */} - <div className="grid grid-cols-2 gap-2"> - <div> - <span className="font-mono text-[10px] text-[#7788aa] uppercase"> - Autonomy - </span> - <div className="font-mono text-xs text-[#dbe7ff]"> - {directive.autonomyLevel} + {/* Goal */} + <div> + <h4 className="font-mono text-[10px] text-[#75aafc] uppercase tracking-wider mb-1"> + Goal + </h4> + <p className="font-mono text-xs text-[#9bb8d8] whitespace-pre-wrap"> + {directive.goal} + </p> </div> - </div> - <div> - <span className="font-mono text-[10px] text-[#7788aa] uppercase"> - Chains - </span> - <div className="font-mono text-xs text-[#dbe7ff]"> - {directive.chainGenerationCount} generated + + {/* Config grid */} + <div className="grid grid-cols-3 gap-2"> + <div> + <span className="font-mono text-[10px] text-[#7788aa] uppercase"> + Autonomy + </span> + <div className="font-mono text-xs text-[#dbe7ff]"> + {directive.autonomyLevel} + </div> + </div> + <div> + <span className="font-mono text-[10px] text-[#7788aa] uppercase"> + Chains + </span> + <div className="font-mono text-xs text-[#dbe7ff]"> + {directive.chainGenerationCount} generated + </div> + </div> + <div> + <span className="font-mono text-[10px] text-[#7788aa] uppercase"> + Cost + </span> + <div className="font-mono text-xs text-[#dbe7ff]"> + ${directive.totalCostUsd.toFixed(2)} + </div> + </div> + {directive.repositoryUrl && ( + <div className="col-span-3"> + <span className="font-mono text-[10px] text-[#7788aa] uppercase"> + Repository + </span> + <div className="font-mono text-xs text-[#dbe7ff] truncate"> + {directive.repositoryUrl} + </div> + </div> + )} </div> - </div> - <div> - <span className="font-mono text-[10px] text-[#7788aa] uppercase"> - Cost - </span> - <div className="font-mono text-xs text-[#dbe7ff]"> - ${directive.totalCostUsd.toFixed(2)} + + {/* Stat cards */} + <div className="grid grid-cols-3 gap-2"> + <div className="border border-dashed border-[rgba(117,170,252,0.2)] p-2 text-center"> + <div className="font-mono text-lg text-[#dbe7ff]"> + {totalSteps} + </div> + <div className="font-mono text-[9px] text-[#7788aa] uppercase"> + Total Steps + </div> + </div> + <div className="border border-dashed border-[rgba(117,170,252,0.2)] p-2 text-center"> + <div className="font-mono text-lg text-green-400"> + {completedSteps} + </div> + <div className="font-mono text-[9px] text-[#7788aa] uppercase"> + Completed + </div> + </div> + <div className="border border-dashed border-[rgba(117,170,252,0.2)] p-2 text-center"> + <div className="font-mono text-lg text-[#dbe7ff]"> + ${directive.totalCostUsd.toFixed(2)} + </div> + <div className="font-mono text-[9px] text-[#7788aa] uppercase"> + Cost + </div> + </div> </div> - </div> - {directive.repositoryUrl && ( + + {/* Structured sections */} + <JsonSection label="Requirements" data={directive.requirements} /> + <JsonSection + label="Acceptance Criteria" + data={directive.acceptanceCriteria} + /> + <JsonSection label="Constraints" data={directive.constraints} /> + <JsonSection + label="External Dependencies" + data={directive.externalDependencies} + /> + + {/* Metadata */} <div> - <span className="font-mono text-[10px] text-[#7788aa] uppercase"> - Repository - </span> - <div className="font-mono text-xs text-[#dbe7ff] truncate"> - {directive.repositoryUrl} + <h4 className="font-mono text-[10px] text-[#75aafc] uppercase tracking-wider mb-1"> + Metadata + </h4> + <div className="grid grid-cols-2 gap-1 font-mono text-[10px]"> + <span className="text-[#7788aa]">Created</span> + <span className="text-[#9bb8d8]"> + {new Date(directive.createdAt).toLocaleString()} + </span> + <span className="text-[#7788aa]">Updated</span> + <span className="text-[#9bb8d8]"> + {new Date(directive.updatedAt).toLocaleString()} + </span> + {directive.startedAt && ( + <> + <span className="text-[#7788aa]">Started</span> + <span className="text-[#9bb8d8]"> + {new Date(directive.startedAt).toLocaleString()} + </span> + </> + )} + {directive.completedAt && ( + <> + <span className="text-[#7788aa]">Completed</span> + <span className="text-[#9bb8d8]"> + {new Date(directive.completedAt).toLocaleString()} + </span> + </> + )} + <span className="text-[#7788aa]">Version</span> + <span className="text-[#9bb8d8]">{directive.version}</span> </div> </div> - )} - </div> + </div> + )} - {/* Structured sections */} - <JsonSection label="Requirements" data={directive.requirements} /> - <JsonSection - label="Acceptance Criteria" - data={directive.acceptanceCriteria} - /> - <JsonSection label="Constraints" data={directive.constraints} /> - <JsonSection - label="External Dependencies" - data={directive.externalDependencies} - /> + {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> + )} - {/* Chains */} - <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.status === "planning" - ? "Planning in progress... chains will appear when the planner completes." - : "No chains yet. Chains are created during planning."} - </p> - ) : ( - <div className="space-y-2"> - {directive.chains.map((cws) => ( - <ChainCard key={cws.id} chainWithSteps={cws} /> - ))} + {/* 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.status === "planning" + ? "Planning in progress... chains will appear when the planner completes." + : 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> + </div> + )} + + {activeTab === "contracts" && ( + <DirectiveContractsTab directive={directive} /> + )} </div> </div> ); diff --git a/makima/frontend/src/components/directives/StepDiagram.tsx b/makima/frontend/src/components/directives/StepDiagram.tsx new file mode 100644 index 0000000..5c65ae1 --- /dev/null +++ b/makima/frontend/src/components/directives/StepDiagram.tsx @@ -0,0 +1,152 @@ +import { useNavigate } from "react-router"; +import type { ChainStep, ContractPhase } from "../../lib/api"; +import { PhaseProgressBarCompact } from "../contracts/PhaseProgressBar"; + +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 statusDotColors: Record<string, string> = { + pending: "bg-[#555]", + running: "bg-yellow-400", + passed: "bg-green-400", + failed: "bg-red-400", +}; + +/** + * Assign depth to each step via topological sort. + * Steps with no dependsOn = depth 0. Steps depending only on depth-0 = depth 1. Etc. + */ +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; +} + +export function StepDiagram({ steps }: StepDiagramProps) { + const navigate = useNavigate(); + + 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 + 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) + ); + } + + // 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 }); + }); + }); + + 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> + ))} + </div> + ); +} diff --git a/makima/frontend/src/lib/api.ts b/makima/frontend/src/lib/api.ts index ccc7156..9782a07 100644 --- a/makima/frontend/src/lib/api.ts +++ b/makima/frontend/src/lib/api.ts @@ -3075,6 +3075,18 @@ export interface DirectiveChain { updatedAt: string; } +export interface StepContractSummary { + id: string; + name: string; + contractType: string; + phase: string; + status: string; + taskCount: number; + tasksDone: number; + tasksRunning: number; + tasksFailed: number; +} + export interface ChainStep { id: string; chainId: string; @@ -3092,6 +3104,7 @@ export interface ChainStep { startedAt: string | null; completedAt: string | null; createdAt: string; + contractSummary: StepContractSummary | null; } export interface ChainWithSteps extends DirectiveChain { @@ -3099,6 +3112,7 @@ export interface ChainWithSteps extends DirectiveChain { } export interface DirectiveWithChains extends Directive { + orchestratorContractSummary: StepContractSummary | null; chains: ChainWithSteps[]; } |
