From 9e9f18884c78c21f5785908fb7ccd00e2fa5436b Mon Sep 17 00:00:00 2001 From: soryu Date: Sat, 7 Feb 2026 01:11:26 +0000 Subject: Add new directive initial implementation --- makima/frontend/src/components/NavStrip.tsx | 1 + .../src/components/directives/DirectiveDetail.tsx | 200 +++++++++++++++++++++ .../src/components/directives/DirectiveList.tsx | 135 ++++++++++++++ 3 files changed, 336 insertions(+) create mode 100644 makima/frontend/src/components/directives/DirectiveDetail.tsx create mode 100644 makima/frontend/src/components/directives/DirectiveList.tsx (limited to 'makima/frontend/src/components') diff --git a/makima/frontend/src/components/NavStrip.tsx b/makima/frontend/src/components/NavStrip.tsx index fb95c7f..f7e67db 100644 --- a/makima/frontend/src/components/NavStrip.tsx +++ b/makima/frontend/src/components/NavStrip.tsx @@ -11,6 +11,7 @@ interface NavLink { const NAV_LINKS: NavLink[] = [ { label: "Listen", href: "/listen" }, { label: "Contracts", href: "/contracts", requiresAuth: true }, + { label: "Directives", href: "/directives", requiresAuth: true }, { label: "Board", href: "/workflow", requiresAuth: true }, { label: "Mesh", href: "/mesh", requiresAuth: true }, { label: "History", href: "/history", requiresAuth: true }, diff --git a/makima/frontend/src/components/directives/DirectiveDetail.tsx b/makima/frontend/src/components/directives/DirectiveDetail.tsx new file mode 100644 index 0000000..3634a79 --- /dev/null +++ b/makima/frontend/src/components/directives/DirectiveDetail.tsx @@ -0,0 +1,200 @@ +import type { + DirectiveWithChains, + DirectiveStatus, + DirectiveChain, +} from "../../lib/api"; + +interface DirectiveDetailProps { + directive: DirectiveWithChains; + onBack: () => void; + onDelete?: (id: string) => void; +} + +const statusColors: Record = { + 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 ChainCard({ chain }: { chain: DirectiveChain }) { + return ( +
+
+ {chain.name} + + gen {chain.generation} · {chain.status} + +
+ {chain.description && ( +

+ {chain.description} +

+ )} +
+ + {chain.completedSteps}/{chain.totalSteps} steps + + {chain.failedSteps > 0 && ( + {chain.failedSteps} failed + )} + {chain.currentConfidence != null && ( + confidence: {(chain.currentConfidence * 100).toFixed(0)}% + )} +
+
+ ); +} + +function JsonSection({ + label, + data, +}: { + label: string; + data: unknown[] | unknown; +}) { + const items = Array.isArray(data) ? data : []; + if (items.length === 0) return null; + + return ( +
+

+ {label} +

+
+ {items.map((item, i) => ( +
+ {typeof item === "string" ? item : JSON.stringify(item)} +
+ ))} +
+
+ ); +} + +export function DirectiveDetail({ + directive, + onBack, + onDelete, +}: DirectiveDetailProps) { + return ( +
+ {/* Header */} +
+
+ + + {directive.status} + + + v{directive.version} + + {onDelete && ( + + )} +
+

+ {directive.title} +

+
+ + {/* Content */} +
+ {/* Goal */} +
+

+ Goal +

+

+ {directive.goal} +

+
+ + {/* Config */} +
+
+ + Autonomy + +
+ {directive.autonomyLevel} +
+
+
+ + Chains + +
+ {directive.chainGenerationCount} generated +
+
+
+ + Cost + +
+ ${directive.totalCostUsd.toFixed(2)} +
+
+ {directive.repositoryUrl && ( +
+ + Repository + +
+ {directive.repositoryUrl} +
+
+ )} +
+ + {/* Structured sections */} + + + + + + {/* Chains */} +
+

+ Chains ({directive.chains.length}) +

+ {directive.chains.length === 0 ? ( +

+ No chains yet. Chains are created during planning. +

+ ) : ( +
+ {directive.chains.map((chain) => ( + + ))} +
+ )} +
+
+
+ ); +} diff --git a/makima/frontend/src/components/directives/DirectiveList.tsx b/makima/frontend/src/components/directives/DirectiveList.tsx new file mode 100644 index 0000000..a900b7b --- /dev/null +++ b/makima/frontend/src/components/directives/DirectiveList.tsx @@ -0,0 +1,135 @@ +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 = { + 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, + onDelete, + selectedId, +}: DirectiveListProps) { + const [filter, setFilter] = useState("all"); + + const filteredDirectives = + filter === "all" + ? directives + : directives.filter((d) => d.status === filter); + + if (loading) { + return ( +
+
Loading...
+
+ ); + } + + return ( +
+ {/* Header */} +
+
+

+ Directives +

+ +
+ {/* Filter tabs */} +
+ {(["all", "draft", "planning", "active", "paused", "completed", "failed"] as const).map( + (status) => ( + + ) + )} +
+
+ + {/* List */} +
+ {filteredDirectives.length === 0 ? ( +
+

+ {directives.length === 0 + ? "No directives yet" + : "No matching directives"} +

+
+ ) : ( + filteredDirectives.map((directive) => ( +
onSelect(directive.id)} + onContextMenu={(e) => { + if (onDelete) { + e.preventDefault(); + } + }} + className={`p-3 border-b border-dashed border-[rgba(117,170,252,0.15)] cursor-pointer transition-colors hover:bg-[rgba(117,170,252,0.05)] ${ + selectedId === directive.id + ? "bg-[rgba(117,170,252,0.1)]" + : "" + }`} + > +
+
+
+ {directive.title} +
+
+ {directive.goal} +
+
+
+ + {directive.status} + + + {directive.chainCount}ch / {directive.stepCount}st + +
+
+
+ )) + )} +
+
+ ); +} -- cgit v1.2.3