diff options
| author | soryu <soryu@soryu.co> | 2026-02-08 21:07:30 +0000 |
|---|---|---|
| committer | soryu <soryu@soryu.co> | 2026-02-08 21:07:30 +0000 |
| commit | 3662b334dfd68cfdf00ed44ae88927c2e1b2aabe (patch) | |
| tree | bff5ae1e189fb8bcc0211d97dab3b9acb4257038 /makima/frontend/src/components/directives | |
| parent | 87fa8c4af66745bd30bc84b6c5ef657dd4dec002 (diff) | |
| download | soryu-3662b334dfd68cfdf00ed44ae88927c2e1b2aabe.tar.gz soryu-3662b334dfd68cfdf00ed44ae88927c2e1b2aabe.zip | |
Remove directive mechanism
Diffstat (limited to 'makima/frontend/src/components/directives')
4 files changed, 0 insertions, 1029 deletions
diff --git a/makima/frontend/src/components/directives/DirectiveContractsTab.tsx b/makima/frontend/src/components/directives/DirectiveContractsTab.tsx deleted file mode 100644 index 28da117..0000000 --- a/makima/frontend/src/components/directives/DirectiveContractsTab.tsx +++ /dev/null @@ -1,148 +0,0 @@ -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, - }); - } - // Show monitoring/evaluation contracts - if (step.monitoringContractId) { - contracts.push({ - summary: { - id: step.monitoringContractId, - name: `${step.name} - Evaluation`, - contractType: "monitoring", - status: step.status === "evaluating" ? "active" : "completed", - phase: "plan", - taskCount: 1, - tasksDone: step.status === "evaluating" ? 0 : 1, - tasksRunning: step.status === "evaluating" ? 1 : 0, - tasksFailed: 0, - }, - label: `${step.name} eval`, - }); - } - } - } - - 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 deleted file mode 100644 index 6bdf5aa..0000000 --- a/makima/frontend/src/components/directives/DirectiveDetail.tsx +++ /dev/null @@ -1,425 +0,0 @@ -import { useState, useEffect, useRef } from "react"; -import { useNavigate } from "react-router"; -import type { - DirectiveWithChains, - DirectiveStatus, - 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; - onBack: () => void; - onDelete?: (id: string) => void; - onStart?: (id: string) => void; - onRefresh?: (updated: DirectiveWithChains) => void; -} - -type Tab = "overview" | "chain" | "contracts"; - -const statusColors: Record<DirectiveStatus, string> = { - draft: "text-[#888]", - planning: "text-yellow-400", - active: "text-green-400", - paused: "text-orange-400", - completed: "text-blue-400", - archived: "text-[#555]", - failed: "text-red-400", -}; - -function JsonSection({ - label, - data, -}: { - label: string; - data: unknown[] | unknown; -}) { - const items = Array.isArray(data) ? data : []; - if (items.length === 0) return null; - - return ( - <div> - <h4 className="font-mono text-[10px] text-[#75aafc] uppercase tracking-wider mb-1"> - {label} - </h4> - <div className="font-mono text-xs text-[#9bb8d8] bg-[rgba(0,0,0,0.2)] p-2 max-h-32 overflow-y-auto"> - {items.map((item, i) => ( - <div key={i} className="mb-0.5"> - {typeof item === "string" ? item : JSON.stringify(item)} - </div> - ))} - </div> - </div> - ); -} - -export function DirectiveDetail({ - directive, - onBack, - onDelete, - onStart, - onRefresh, -}: DirectiveDetailProps) { - const navigate = useNavigate(); - const [activeTab, setActiveTab] = useState<Tab>("overview"); - - // Auto-poll when directive is in an active state - const isLive = - directive.status === "planning" || directive.status === "active"; - const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null); - - useEffect(() => { - if (!isLive) { - if (intervalRef.current) { - clearInterval(intervalRef.current); - intervalRef.current = null; - } - return; - } - - intervalRef.current = setInterval(async () => { - try { - const updated = await getDirective(directive.id); - if (updated && onRefresh) { - onRefresh(updated); - } - } catch { - // Ignore poll errors - } - }, 5000); - - return () => { - if (intervalRef.current) { - clearInterval(intervalRef.current); - intervalRef.current = null; - } - }; - }, [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 */} - <div className="p-4 border-b border-dashed border-[rgba(117,170,252,0.35)]"> - <div className="flex items-center justify-between mb-3"> - <button - onClick={onBack} - className="font-mono text-xs text-[#75aafc] hover:text-[#9bc3ff] transition-colors" - > - ← Back to list - </button> - <div className="flex items-center gap-2"> - {onStart && directive.status === "draft" && ( - <button - onClick={() => onStart(directive.id)} - className="px-3 py-1.5 font-mono text-xs text-[#dbe7ff] bg-[#0f3c78] border border-[#3f6fb3] hover:bg-[#153667] transition-colors uppercase" - > - Start - </button> - )} - {onDelete && ( - <button - onClick={() => onDelete(directive.id)} - className="px-3 py-1.5 font-mono text-xs text-red-400 border border-red-400/30 hover:border-red-400/50 transition-colors uppercase" - > - Delete - </button> - )} - </div> - </div> - <div className="flex items-center gap-3 mb-2"> - <h2 className="font-mono text-lg text-[#dbe7ff]"> - {directive.title} - </h2> - <span - className={`font-mono text-xs uppercase ${ - statusColors[directive.status as DirectiveStatus] || "text-[#888]" - }`} - > - {directive.status} - </span> - {isLive && ( - <span className="font-mono text-[9px] text-yellow-400/60 animate-pulse"> - polling - </span> - )} - <span className="font-mono text-[10px] text-[#7788aa]"> - v{directive.version} - </span> - </div> - </div> - - {/* 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]" - } - `} - > - {tab.label} - {tab.count != null && tab.count > 0 && ( - <span className="ml-1 text-[10px] text-[#7788aa]"> - ({tab.count}) - </span> - )} - </button> - ))} - </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> - )} - - {/* 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> - - {/* 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> - - {/* 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> - - {/* 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> - <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> - )} - - {activeTab === "chain" && ( - <div className="space-y-4"> - {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... 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> - ) : ( - <> - {/* 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> - )} - - {activeTab === "contracts" && ( - <DirectiveContractsTab directive={directive} /> - )} - </div> - </div> - ); -} diff --git a/makima/frontend/src/components/directives/DirectiveList.tsx b/makima/frontend/src/components/directives/DirectiveList.tsx deleted file mode 100644 index 5afa36e..0000000 --- a/makima/frontend/src/components/directives/DirectiveList.tsx +++ /dev/null @@ -1,143 +0,0 @@ -import { useState } from "react"; -import type { DirectiveSummary, DirectiveStatus } from "../../lib/api"; - -interface DirectiveListProps { - directives: DirectiveSummary[]; - loading: boolean; - onSelect: (id: string) => void; - onCreate: () => void; - onDelete?: (directive: DirectiveSummary) => void; - selectedId?: string; -} - -const statusColors: Record<DirectiveStatus, string> = { - draft: "text-[#888]", - planning: "text-yellow-400", - active: "text-green-400", - paused: "text-orange-400", - completed: "text-blue-400", - archived: "text-[#555]", - failed: "text-red-400", -}; - -export function DirectiveList({ - directives, - loading, - onSelect, - onCreate, - selectedId, -}: DirectiveListProps) { - const [filter, setFilter] = useState<DirectiveStatus | "all">("all"); - - const filteredDirectives = - filter === "all" - ? directives - : directives.filter((d) => d.status === filter); - - if (loading) { - return ( - <div className="panel h-full flex items-center justify-center"> - <div className="font-mono text-[#9bc3ff] text-sm">Loading...</div> - </div> - ); - } - - return ( - <div className="panel h-full flex flex-col"> - {/* Header */} - <div className="p-4 border-b border-dashed border-[rgba(117,170,252,0.35)]"> - <div className="flex items-center justify-between mb-3"> - <h2 className="font-mono text-sm text-[#75aafc] uppercase tracking-wider"> - Directives - </h2> - <button - onClick={onCreate} - className="px-3 py-1.5 font-mono text-xs text-[#dbe7ff] bg-[#0f3c78] border border-[#3f6fb3] hover:bg-[#153667] transition-colors uppercase" - > - + New - </button> - </div> - - {/* Filter tabs */} - <div className="flex gap-1 flex-wrap"> - {(["all", "draft", "planning", "active", "paused", "completed", "failed"] as const).map( - (status) => ( - <button - key={status} - onClick={() => setFilter(status)} - className={` - px-2 py-1 font-mono text-[10px] uppercase tracking-wider transition-colors - ${ - filter === status - ? "bg-[rgba(117,170,252,0.1)] text-[#9bc3ff] border border-[rgba(117,170,252,0.3)]" - : "text-[#555] hover:text-[#75aafc]" - } - `} - > - {status} - </button> - ) - )} - </div> - </div> - - {/* List */} - <div className="flex-1 overflow-y-auto"> - {filteredDirectives.length === 0 ? ( - <div className="p-4 text-center"> - <p className="font-mono text-sm text-[#555]"> - {filter === "all" - ? "No directives yet" - : `No ${filter} directives`} - </p> - </div> - ) : ( - <div className="divide-y divide-[rgba(117,170,252,0.15)]"> - {filteredDirectives.map((directive) => ( - <button - key={directive.id} - onClick={() => onSelect(directive.id)} - className={` - w-full text-left p-4 transition-colors - ${ - selectedId === directive.id - ? "bg-[rgba(117,170,252,0.1)]" - : "hover:bg-[rgba(117,170,252,0.05)]" - } - `} - > - <div className="flex items-start justify-between gap-2 mb-2"> - <h3 className="font-mono text-sm text-[#dbe7ff] truncate"> - {directive.title} - </h3> - <span - className={`text-[10px] font-mono uppercase shrink-0 ${ - statusColors[directive.status] || "text-[#888]" - }`} - > - {directive.status} - </span> - </div> - - {directive.goal && ( - <p className="font-mono text-xs text-[#555] mb-2 line-clamp-2"> - {directive.goal} - </p> - )} - - <div className="flex items-center gap-3 text-[10px] font-mono text-[#555]"> - {directive.chainCount > 0 && ( - <span>{directive.chainCount} chains</span> - )} - {directive.stepCount > 0 && ( - <span>{directive.stepCount} steps</span> - )} - </div> - </button> - ))} - </div> - )} - </div> - </div> - ); -} 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> - ); -} |
