diff options
Diffstat (limited to 'makima/frontend/src')
| -rw-r--r-- | makima/frontend/src/components/directives/DirectiveDetail.tsx | 230 | ||||
| -rw-r--r-- | makima/frontend/src/hooks/useDirectiveMemories.ts | 86 | ||||
| -rw-r--r-- | makima/frontend/src/lib/api.ts | 176 |
3 files changed, 1 insertions, 491 deletions
diff --git a/makima/frontend/src/components/directives/DirectiveDetail.tsx b/makima/frontend/src/components/directives/DirectiveDetail.tsx index 369cdaa..f9e7eed 100644 --- a/makima/frontend/src/components/directives/DirectiveDetail.tsx +++ b/makima/frontend/src/components/directives/DirectiveDetail.tsx @@ -1,8 +1,7 @@ import { useState, useMemo, useEffect, useRef } from "react"; -import type { DirectiveWithSteps, DirectiveStatus, MemoryCategory } from "../../lib/api"; +import type { DirectiveWithSteps, DirectiveStatus } from "../../lib/api"; import { DirectiveDAG } from "./DirectiveDAG"; import { DirectiveLogStream } from "./DirectiveLogStream"; -import { useDirectiveMemories } from "../../hooks/useDirectiveMemories"; import { useMultiTaskSubscription } from "../../hooks/useMultiTaskSubscription"; const STATUS_BADGE: Record<DirectiveStatus, { color: string; label: string }> = { @@ -13,18 +12,6 @@ const STATUS_BADGE: Record<DirectiveStatus, { color: string; label: string }> = archived: { color: "text-[#556677] border-[#2a3a5a]", label: "ARCHIVED" }, }; -const CATEGORY_COLORS: Record<MemoryCategory, { text: string; border: string; bg: string; label: string }> = { - decision: { text: "text-amber-400", border: "border-amber-800", bg: "bg-amber-900/20", label: "Decision" }, - context: { text: "text-cyan-400", border: "border-cyan-800", bg: "bg-cyan-900/20", label: "Context" }, - preference: { text: "text-violet-400", border: "border-violet-800", bg: "bg-violet-900/20", label: "Preference" }, - learning: { text: "text-emerald-400", border: "border-emerald-800", bg: "bg-emerald-900/20", label: "Learning" }, - issue: { text: "text-red-400", border: "border-red-800", bg: "bg-red-900/20", label: "Issue" }, - progress: { text: "text-blue-400", border: "border-blue-800", bg: "bg-blue-900/20", label: "Progress" }, - other: { text: "text-[#7788aa]", border: "border-[#2a3a5a]", bg: "bg-[#1a2540]", label: "Other" }, -}; - -const ALL_CATEGORIES: MemoryCategory[] = ["decision", "context", "preference", "learning", "issue", "progress", "other"]; - interface DirectiveDetailProps { directive: DirectiveWithSteps; onStart: () => void; @@ -66,25 +53,6 @@ export function DirectiveDetail({ const terminalStatuses = new Set(["completed", "failed", "skipped"]); const hasTerminalTasks = directive.steps.some((s) => s.taskId && terminalStatuses.has(s.status)); - // Memory panel state - const [memoryOpen, setMemoryOpen] = useState(false); - const [addingMemory, setAddingMemory] = useState(false); - const [newCategory, setNewCategory] = useState<MemoryCategory>("context"); - const [newContent, setNewContent] = useState(""); - const [confirmClear, setConfirmClear] = useState(false); - - const { - grouped, - loading: memoryLoading, - error: memoryError, - add: addMemory, - remove: removeMemory, - clearAll: clearMemories, - refresh: refreshMemories, - } = useDirectiveMemories(directive.id); - - const totalMemories = Object.values(grouped).reduce((sum, arr) => sum + arr.length, 0); - // Build task map from directive steps and orchestrator const taskMap = useMemo(() => { const map = new Map<string, string>(); @@ -123,21 +91,6 @@ export function DirectiveDetail({ setEditingGoal(false); }; - const handleAddMemory = async () => { - if (!newContent.trim()) return; - await addMemory({ - category: newCategory, - content: newContent.trim(), - }); - setNewContent(""); - setAddingMemory(false); - }; - - const handleClearAll = async () => { - await clearMemories(); - setConfirmClear(false); - }; - return ( <div className="flex flex-col h-full overflow-y-auto"> {/* Header */} @@ -346,187 +299,6 @@ export function DirectiveDetail({ )} </div> - {/* Memory Panel */} - <div className="px-4 py-3 border-b border-[rgba(117,170,252,0.1)]"> - {/* Memory header — always visible */} - <div className="flex items-center justify-between"> - <button - type="button" - onClick={() => setMemoryOpen((v) => !v)} - className="flex items-center gap-1.5 group" - > - <span className="text-[10px] font-mono text-[#556677] group-hover:text-[#9bc3ff] transition-colors"> - {memoryOpen ? "\u25BC" : "\u25B6"} - </span> - <span className="text-[10px] font-mono text-[#9bc3ff] uppercase tracking-wide"> - Memory - </span> - {totalMemories > 0 && ( - <span className="text-[9px] font-mono text-[#556677] ml-1"> - ({totalMemories}) - </span> - )} - </button> - <div className="flex items-center gap-2" /> - </div> - - {/* Collapsible content */} - {memoryOpen && ( - <div className="mt-2"> - {memoryError && ( - <div className="text-[10px] font-mono text-red-400 mb-2 px-2 py-1 bg-red-900/10 border border-red-800/30 rounded"> - {memoryError} - </div> - )} - - {memoryLoading ? ( - <div className="text-[10px] font-mono text-[#556677] py-2">Loading...</div> - ) : totalMemories === 0 ? ( - <div className="text-[10px] font-mono text-[#556677] py-2"> - No memory entries yet. - </div> - ) : ( - /* Grouped entries */ - <div className="flex flex-col gap-2"> - {ALL_CATEGORIES.map((cat) => { - const entries = grouped[cat]; - if (entries.length === 0) return null; - const style = CATEGORY_COLORS[cat]; - return ( - <div key={cat}> - <div className="flex items-center gap-1.5 mb-1"> - <span className={`text-[9px] font-mono ${style.text} uppercase tracking-wider`}> - {style.label} - </span> - <span className="text-[9px] font-mono text-[#556677]"> - ({entries.length}) - </span> - </div> - <div className="flex flex-col gap-1"> - {entries.map((entry) => ( - <div - key={entry.id} - className={`flex items-start gap-2 px-2 py-1.5 rounded border ${style.border} ${style.bg}`} - > - <div className="flex-1 min-w-0"> - <p className="text-[10px] font-mono text-[#c0d0e0] whitespace-pre-wrap break-words"> - {entry.content} - </p> - </div> - <button - type="button" - onClick={() => removeMemory(entry.id)} - className="text-[9px] font-mono text-[#556677] hover:text-red-400 shrink-0 mt-0.5" - title="Delete entry" - > - x - </button> - </div> - ))} - </div> - </div> - ); - })} - </div> - )} - - {/* Action bar: Add + Clear */} - <div className="flex items-center gap-2 mt-2 pt-2 border-t border-[rgba(117,170,252,0.1)]"> - <button - type="button" - onClick={() => setAddingMemory((v) => !v)} - className="text-[10px] font-mono text-[#75aafc] hover:text-white border border-[rgba(117,170,252,0.2)] rounded px-2 py-0.5" - > - {addingMemory ? "Cancel" : "+ Add"} - </button> - {totalMemories > 0 && ( - <> - {confirmClear ? ( - <div className="flex items-center gap-1.5 ml-auto"> - <span className="text-[9px] font-mono text-red-400">Clear all?</span> - <button - type="button" - onClick={handleClearAll} - className="text-[9px] font-mono text-red-400 hover:text-red-300 border border-red-800 rounded px-1.5 py-0.5" - > - Yes - </button> - <button - type="button" - onClick={() => setConfirmClear(false)} - className="text-[9px] font-mono text-[#7788aa] hover:text-white border border-[#2a3a5a] rounded px-1.5 py-0.5" - > - No - </button> - </div> - ) : ( - <button - type="button" - onClick={() => setConfirmClear(true)} - className="text-[10px] font-mono text-[#556677] hover:text-red-400 ml-auto" - > - Clear all - </button> - )} - </> - )} - <button - type="button" - onClick={refreshMemories} - className="text-[9px] font-mono text-[#556677] hover:text-[#75aafc]" - title="Refresh memories" - > - [refresh] - </button> - </div> - - {/* Add form */} - {addingMemory && ( - <div className="mt-2 p-2 bg-[#0a1628] border border-[rgba(117,170,252,0.15)] rounded flex flex-col gap-2"> - <div className="flex items-center gap-2"> - <label className="text-[9px] font-mono text-[#7788aa] shrink-0">Category</label> - <select - value={newCategory} - onChange={(e) => setNewCategory(e.target.value as MemoryCategory)} - className="bg-[#1a2540] border border-[rgba(117,170,252,0.2)] rounded px-1.5 py-0.5 text-[10px] font-mono text-white flex-1" - > - {ALL_CATEGORIES.map((c) => ( - <option key={c} value={c}> - {CATEGORY_COLORS[c].label} - </option> - ))} - </select> - </div> - <textarea - value={newContent} - onChange={(e) => setNewContent(e.target.value)} - placeholder="Memory content..." - className="w-full bg-[#1a2540] border border-[rgba(117,170,252,0.2)] rounded px-2 py-1.5 text-[10px] font-mono text-white resize-y min-h-[40px] placeholder:text-[#556677]" - rows={2} - /> - <div className="flex gap-1.5"> - <button - type="button" - onClick={handleAddMemory} - disabled={!newContent.trim()} - className="text-[10px] font-mono text-emerald-400 hover:text-emerald-300 border border-emerald-800 rounded px-2 py-0.5 disabled:opacity-40 disabled:cursor-not-allowed" - > - Save - </button> - <button - type="button" - onClick={() => { setAddingMemory(false); setNewContent(""); }} - className="text-[10px] font-mono text-[#7788aa] hover:text-white border border-[#2a3a5a] rounded px-2 py-0.5" - > - Cancel - </button> - </div> - </div> - )} - </div> - )} - </div> - {/* DAG */} <div className="px-4 py-3 flex-1"> <span className="text-[10px] font-mono text-[#9bc3ff] uppercase tracking-wide block mb-2"> diff --git a/makima/frontend/src/hooks/useDirectiveMemories.ts b/makima/frontend/src/hooks/useDirectiveMemories.ts deleted file mode 100644 index b2a87d0..0000000 --- a/makima/frontend/src/hooks/useDirectiveMemories.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { useState, useEffect, useCallback } from "react"; -import { - type DirectiveMemory, - type MemoryCategory, - type CreateDirectiveMemoryRequest, - listDirectiveMemories, - createDirectiveMemory, - deleteDirectiveMemory, -} from "../lib/api"; - -export function useDirectiveMemories(directiveId: string | undefined) { - const [memories, setMemories] = useState<DirectiveMemory[]>([]); - const [loading, setLoading] = useState(false); - const [error, setError] = useState<string | null>(null); - - const refreshMemories = useCallback(async () => { - if (!directiveId) return; - try { - setLoading(true); - setError(null); - const response = await listDirectiveMemories(directiveId); - setMemories(response.memories); - } catch (e) { - setError(e instanceof Error ? e.message : "Failed to load memories"); - } finally { - setLoading(false); - } - }, [directiveId]); - - useEffect(() => { - refreshMemories(); - }, [refreshMemories]); - - const add = useCallback(async (req: CreateDirectiveMemoryRequest) => { - if (!directiveId) return; - try { - setError(null); - await createDirectiveMemory(directiveId, req); - await refreshMemories(); - } catch (e) { - setError(e instanceof Error ? e.message : "Failed to add memory"); - } - }, [directiveId, refreshMemories]); - - const remove = useCallback(async (memoryId: string) => { - if (!directiveId) return; - try { - setError(null); - await deleteDirectiveMemory(directiveId, memoryId); - await refreshMemories(); - } catch (e) { - setError(e instanceof Error ? e.message : "Failed to delete memory"); - } - }, [directiveId, refreshMemories]); - - const clearAll = useCallback(async () => { - if (!directiveId) return; - try { - setError(null); - await Promise.all(memories.map((m) => deleteDirectiveMemory(directiveId, m.id))); - setMemories([]); - } catch (e) { - setError(e instanceof Error ? e.message : "Failed to clear memories"); - } - }, [directiveId, memories]); - - /** Group entries by category */ - const grouped = memories.reduce<Record<MemoryCategory, DirectiveMemory[]>>( - (acc, entry) => { - acc[entry.category].push(entry); - return acc; - }, - { decision: [], context: [], preference: [], learning: [], issue: [], progress: [], other: [] }, - ); - - return { - memories, - grouped, - loading, - error, - refresh: refreshMemories, - add, - remove, - clearAll, - }; -} diff --git a/makima/frontend/src/lib/api.ts b/makima/frontend/src/lib/api.ts index 9d9cb1c..480041c 100644 --- a/makima/frontend/src/lib/api.ts +++ b/makima/frontend/src/lib/api.ts @@ -3246,179 +3246,3 @@ export async function cleanupDirectiveTasks(id: string): Promise<{ deleted: numb return res.json(); } -// ============================================================================= -// Directive Memory Types & API -// ============================================================================= - -/** Category of a directive memory entry */ -export type MemoryCategory = - | "decision" - | "learning" - | "context" - | "preference" - | "issue" - | "progress" - | "other"; - -/** A single memory entry associated with a directive */ -export interface DirectiveMemory { - id: string; - directiveId: string; - /** The memory content text */ - content: string; - /** Category for organizing memories */ - category: MemoryCategory; - /** Which step created this memory (null if directive-level) */ - stepId: string | null; - /** Which task created this memory (null if manually added) */ - taskId: string | null; - /** Importance score (1-10, higher = more important) */ - importance: number; - createdAt: string; - updatedAt: string; -} - -/** Response from listing directive memories */ -export interface DirectiveMemoryListResponse { - memories: DirectiveMemory[]; - total: number; -} - -/** Request to create a new directive memory */ -export interface CreateDirectiveMemoryRequest { - content: string; - category?: MemoryCategory; - stepId?: string; - taskId?: string; - importance?: number; -} - -/** Request to update a directive memory */ -export interface UpdateDirectiveMemoryRequest { - content?: string; - category?: MemoryCategory; - importance?: number; -} - -// Directive Memory API functions - -/** - * List all memories for a directive. - * Optionally filter by category or step. - */ -export async function listDirectiveMemories( - directiveId: string, - params?: { category?: MemoryCategory; stepId?: string } -): Promise<DirectiveMemoryListResponse> { - const searchParams = new URLSearchParams(); - if (params?.category) searchParams.set("category", params.category); - if (params?.stepId) searchParams.set("stepId", params.stepId); - const query = searchParams.toString(); - const url = `${API_BASE}/api/v1/directives/${directiveId}/memories${query ? `?${query}` : ""}`; - const res = await authFetch(url); - if (!res.ok) throw new Error(`Failed to list directive memories: ${res.statusText}`); - return res.json(); -} - -/** - * Get a single memory entry by ID. - */ -export async function getDirectiveMemory( - directiveId: string, - memoryId: string -): Promise<DirectiveMemory> { - const res = await authFetch( - `${API_BASE}/api/v1/directives/${directiveId}/memories/${memoryId}` - ); - if (!res.ok) throw new Error(`Failed to get directive memory: ${res.statusText}`); - return res.json(); -} - -/** - * Create a new memory entry for a directive. - */ -export async function createDirectiveMemory( - directiveId: string, - req: CreateDirectiveMemoryRequest -): Promise<DirectiveMemory> { - const res = await authFetch(`${API_BASE}/api/v1/directives/${directiveId}/memories`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(req), - }); - if (!res.ok) throw new Error(`Failed to create directive memory: ${res.statusText}`); - return res.json(); -} - -/** - * Update an existing memory entry. - */ -export async function updateDirectiveMemory( - directiveId: string, - memoryId: string, - req: UpdateDirectiveMemoryRequest -): Promise<DirectiveMemory> { - const res = await authFetch( - `${API_BASE}/api/v1/directives/${directiveId}/memories/${memoryId}`, - { - method: "PUT", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(req), - } - ); - if (!res.ok) throw new Error(`Failed to update directive memory: ${res.statusText}`); - return res.json(); -} - -/** - * Delete a memory entry. - */ -export async function deleteDirectiveMemory( - directiveId: string, - memoryId: string -): Promise<void> { - const res = await authFetch( - `${API_BASE}/api/v1/directives/${directiveId}/memories/${memoryId}`, - { method: "DELETE" } - ); - if (!res.ok) throw new Error(`Failed to delete directive memory: ${res.statusText}`); -} - -/** - * Batch create multiple memory entries for a directive. - * Useful when a task completes and wants to store multiple learnings at once. - */ -export async function batchCreateDirectiveMemories( - directiveId: string, - memories: CreateDirectiveMemoryRequest[] -): Promise<DirectiveMemory[]> { - const res = await authFetch( - `${API_BASE}/api/v1/directives/${directiveId}/memories/batch`, - { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(memories), - } - ); - if (!res.ok) throw new Error(`Failed to batch create directive memories: ${res.statusText}`); - return res.json(); -} - -/** - * Get a formatted memory context string for a directive. - * This returns memories formatted for injection into task prompts. - * Optionally filter by category or limit the number of memories returned. - */ -export async function getDirectiveMemoryContext( - directiveId: string, - params?: { category?: MemoryCategory; limit?: number } -): Promise<{ context: string; memoryCount: number }> { - const searchParams = new URLSearchParams(); - if (params?.category) searchParams.set("category", params.category); - if (params?.limit) searchParams.set("limit", params.limit.toString()); - const query = searchParams.toString(); - const url = `${API_BASE}/api/v1/directives/${directiveId}/memories/context${query ? `?${query}` : ""}`; - const res = await authFetch(url); - if (!res.ok) throw new Error(`Failed to get directive memory context: ${res.statusText}`); - return res.json(); -} |
