From a0073401f542b2ec95660d4dfba8bfa3342fed3e Mon Sep 17 00:00:00 2001 From: Horus Date: Fri, 27 Feb 2026 17:36:34 +0100 Subject: [PATCH] Add Agent Command Centers - /agent/[id] with vertical sections --- app/agent/[id]/page.tsx | 439 +++++++++++++++++++++++++++++++++ components/council/Council.tsx | 10 + data/changelog.json | 10 + data/execution-logs.json | 1 + data/recurring-templates.json | 120 +++++++++ 5 files changed, 580 insertions(+) create mode 100644 app/agent/[id]/page.tsx create mode 100644 data/changelog.json create mode 100644 data/execution-logs.json create mode 100644 data/recurring-templates.json diff --git a/app/agent/[id]/page.tsx b/app/agent/[id]/page.tsx new file mode 100644 index 0000000..4d5f59e --- /dev/null +++ b/app/agent/[id]/page.tsx @@ -0,0 +1,439 @@ +"use client"; + +import { useState, useEffect } from "react"; +import { useRouter } from "next/navigation"; + +const AGENT_CONFIG: Record = { + horus: { name: "Horus", avatar: "πŸ‘οΈ", color: "#ff7bc0", role: "Master Orchestrator", description: "Command center, delegates tasks, manages all agents" }, + thoth: { name: "Thoth", avatar: "🧠", color: "#8b5cf6", role: "Strategy & Research", description: "SiteMente planning, research, analysis" }, + "thoth-trading": { name: "Thoth Trading", avatar: "πŸ“ˆ", color: "#f59e0b", role: "Market Research", description: "Crypto market analysis and research" }, + ptah: { name: "Ptah", avatar: "πŸ—οΈ", color: "#10b981", role: "Dev & Ops", description: "Development, deployment, technical implementation" }, + seshat: { name: "Seshat", avatar: "πŸ“œ", color: "#ec4899", role: "Content & SEO", description: "Content strategy, SEO, marketing copy" }, + anubis: { name: "Anubis", avatar: "🐺", color: "#6366f1", role: "Outreach & Growth", description: "Lead generation, client acquisition" }, + sekhmet: { name: "Sekhmet", avatar: "βš”οΈ", color: "#ef4444", role: "Risk Management", description: "Trade execution and risk" }, +}; + +interface Task { + id: string; + title: string; + status: string; + priority: string; +} + +interface RecurringTemplate { + id: string; + name: string; + enabled: boolean; + schedule: { type: string; time?: string; dayOfWeek?: number }; + taskTemplate: { title: string; description: string }; +} + +interface ExecutionLog { + id: string; + taskTitle: string; + startedAt: string; + status: string; + output?: string; +} + +interface ChangeLogEntry { + id: string; + date: string; + type: string; + description: string; +} + +interface BrainownNote { + date: string; + content: string; +} + +export default function AgentCenterPage({ params }: { params: { id: string } }) { + const router = useRouter(); + const agentId = params.id; + const agent = AGENT_CONFIG[agentId] || { name: agentId, avatar: "πŸ€–", color: "#666", role: "Agent", description: "" }; + + const [tasks, setTasks] = useState([]); + const [templates, setTemplates] = useState([]); + const [logs, setLogs] = useState([]); + const [changelog, setChangelog] = useState([]); + const [brainownNotes, setBrainownNotes] = useState([]); + const [loading, setLoading] = useState(true); + + // Accordion state + const [expandedSections, setExpandedSections] = useState>({ + tasks: true, + recurring: true, + logs: true, + changelog: true, + brainown: true, + }); + + // Brainown editing + const [editingNote, setEditingNote] = useState(null); + const [noteContent, setNoteContent] = useState(""); + + useEffect(() => { + fetchAllData(); + }, [agentId]); + + const fetchAllData = async () => { + setLoading(true); + + // Fetch tasks for this agent + try { + const tRes = await fetch("/api/tasks"); + if (tRes.ok) { + const allTasks = await tRes.json(); + const agentTasks = allTasks.filter((t: any) => t.assignee === agentId || t.project === getAgentProject(agentId)); + setTasks(agentTasks); + } + } catch {} + + // Fetch recurring templates for this agent + try { + const rRes = await fetch("/api/recurring"); + if (rRes.ok) { + const allTemplates = await rRes.json(); + const agentTemplates = allTemplates.filter((t: any) => t.agent === agentId); + setTemplates(agentTemplates); + } + } catch {} + + // Fetch execution logs for this agent + try { + const lRes = await fetch(`/api/execution-logs?agent=${agentId}&limit=20`); + if (lRes.ok) { + setLogs(await lRes.json()); + } + } catch {} + + // Fetch changelog for this agent + try { + const cRes = await fetch(`/api/changelog?agent=${agentId}&limit=20`); + if (cRes.ok) { + setChangelog(await cRes.json()); + } + } catch {} + + // Fetch brainown notes for this agent + try { + const bRes = await fetch(`/api/agent-outputs?agent=${agentId}`); + if (bRes.ok) { + const dates = await bRes.json(); + const notes: BrainownNote[] = []; + for (const date of dates.slice(0, 10)) { + const noteRes = await fetch(`/api/agent-outputs?agent=${agentId}&date=${date}`); + if (noteRes.ok) { + const note = await noteRes.json(); + if (note.content) { + notes.push({ date, content: note.content }); + } + } + } + setBrainownNotes(notes); + } + } catch {} + + setLoading(false); + }; + + const getAgentProject = (id: string): string => { + const projectMap: Record = { + thoth: "sitemente", + "thoth-trading": "trading", + ptah: "infrastructure", + seshat: "sitemente", + anubis: "sitemente", + sekhmet: "trading", + }; + return projectMap[id] || "sitemente"; + }; + + const toggleSection = (section: string) => { + setExpandedSections(prev => ({ ...prev, [section]: !prev[section] })); + }; + + const toggleTemplate = async (templateId: string, enabled: boolean) => { + await fetch("/api/recurring", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ action: "toggle", templateId, enabled }) + }); + fetchAllData(); + }; + + const runNow = async (templateId: string) => { + await fetch("/api/recurring", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ action: "run-now", templateId }) + }); + fetchAllData(); + }; + + const saveNote = async (date: string) => { + await fetch("/api/agent-outputs", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ action: "save", agent: agentId, date, content: noteContent }) + }); + setEditingNote(null); + fetchAllData(); + }; + + const getStatusColor = (status: string) => { + switch (status) { + case "running": return "text-yellow-400"; + case "completed": return "text-green-400"; + case "failed": return "text-red-400"; + default: return "text-white/50"; + } + }; + + const getPriorityColor = (priority: string) => { + switch (priority) { + case "critical": return "text-red-400"; + case "high": return "text-orange-400"; + case "medium": return "text-yellow-400"; + default: return "text-white/50"; + } + }; + + if (loading) { + return ( +
+
Loading {agent.name}...
+
+ ); + } + + return ( +
+ {/* Header */} +
+ +
+ {agent.avatar} +
+
+

+ {agent.name} +

+

{agent.role}

+
+
+ +

{agent.description}

+ + {/* Vertical Sections - Accordion Style */} +
+ + {/* πŸ“‹ TASKS */} +
+ + {expandedSections.tasks && ( +
+ {tasks.length === 0 ? ( +

No tasks assigned

+ ) : ( + tasks.map((task) => ( +
+
+ [{task.priority}] + {task.title} +
+ + {task.status} + +
+ )) + )} +
+ )} +
+ + {/* πŸ”„ RECURRING */} +
+ + {expandedSections.recurring && ( +
+ {templates.length === 0 ? ( +

No recurring tasks

+ ) : ( + templates.map((template) => ( +
+
+
+ {template.taskTemplate.title} +

{template.taskTemplate.description}

+
+
+ + +
+
+
+ {template.schedule.type === "hourly" ? "Hourly" : + template.schedule.time ? `Daily @ ${template.schedule.time}` : + template.schedule.type} +
+
+ )) + )} +
+ )} +
+ + {/* πŸ“Š EXECUTION LOGS */} +
+ + {expandedSections.logs && ( +
+ {logs.length === 0 ? ( +

No execution logs

+ ) : ( + logs.slice(0, 10).map((log) => ( +
+
+ {log.taskTitle} +

+ {new Date(log.startedAt).toLocaleString()} +

+
+ + {log.status} + +
+ )) + )} +
+ )} +
+ + {/* πŸ“ CHANGE LOG */} +
+ + {expandedSections.changelog && ( +
+ {changelog.length === 0 ? ( +

No changes recorded

+ ) : ( + changelog.slice(0, 10).map((entry) => ( +
+
+ + {entry.type.replace("_", " ")} + + + {new Date(entry.date).toLocaleDateString()} + +
+

{entry.description}

+
+ )) + )} +
+ )} +
+ + {/* 🧠 BRAINOWN */} +
+ + {expandedSections.brainown && ( +
+ {brainownNotes.length === 0 ? ( +

No notes yet

+ ) : ( + brainownNotes.map((note) => ( +
+
+ πŸ“ {note.date} + {editingNote === note.date ? ( +
+ + +
+ ) : ( + + )} +
+ {editingNote === note.date ? ( +