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/components/directives/DirectiveDetail.tsx | |
| parent | 8f757f561eeb397aaea70d7c10d41445cc5e50b5 (diff) | |
| download | soryu-97e21c8296ec5f91912d56980ebf3b18a1ca3507.tar.gz soryu-97e21c8296ec5f91912d56980ebf3b18a1ca3507.zip | |
Add directive monitor contracts
Diffstat (limited to 'makima/frontend/src/components/directives/DirectiveDetail.tsx')
| -rw-r--r-- | makima/frontend/src/components/directives/DirectiveDetail.tsx | 393 |
1 files changed, 288 insertions, 105 deletions
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> ); |
