Files
sitemente/components/council/Council.tsx
T
2026-02-16 13:19:21 +00:00

189 lines
7.3 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { useState, useEffect } from "react";
import { Agent, AgentTeam, defaultTeams } from "@/lib/council/types";
const STORAGE_KEY = "horus:council";
export default function Council() {
const [teams, setTeams] = useState<AgentTeam[]>(defaultTeams);
const [selectedTeam, setSelectedTeam] = useState<string | null>(null);
const [runningTask, setRunningTask] = useState<string>("");
const [agentOutputs, setAgentOutputs] = useState<Record<string, string>>({});
// Load from localStorage
useEffect(() => {
if (typeof window === "undefined") return;
const saved = localStorage.getItem(STORAGE_KEY);
if (saved) {
try {
setTeams(JSON.parse(saved));
} catch {}
}
}, []);
// Save changes
useEffect(() => {
if (typeof window === "undefined") return;
localStorage.setItem(STORAGE_KEY, JSON.stringify(teams));
}, [teams]);
const getStatusColor = (status: Agent["status"]) => {
switch (status) {
case "working": return "text-yellow-400 bg-yellow-400/20";
case "completed": return "text-green-400 bg-green-400/20";
case "error": return "text-red-400 bg-red-400/20";
default: return "text-white/50 bg-white/10";
}
};
const spawnAgent = async (agent: Agent, task: string) => {
if (!task.trim()) return;
// Update agent status
setTeams(prev => prev.map(team => ({
...team,
agents: team.agents.map(a => a.id === agent.id ? { ...a, status: "working" as const, currentTask: task } : a)
})));
setRunningTask("");
// Simulate agent work (in reality this would call actual agents)
const output = `[🤖 ${agent.name}] Starting task: "${task}"\n\n`;
setAgentOutputs(prev => ({ ...prev, [agent.id]: output + "⏳ Processing...\n" }));
// Simulate completion after delay
setTimeout(() => {
const result = output + `✅ Task completed: ${task}\n\n💡 Result: Task processed successfully.`;
setAgentOutputs(prev => ({ ...prev, [agent.id]: result }));
setTeams(prev => prev.map(team => ({
...team,
agents: team.agents.map(a => a.id === agent.id ? {
...a,
status: "completed" as const,
lastRun: new Date().toISOString(),
currentTask: undefined
} : a)
})));
}, 3000 + Math.random() * 2000);
};
const currentTeam = teams.find(t => t.id === selectedTeam);
return (
<div className="space-y-6">
{/* Header */}
<div>
<h2 className="text-2xl font-bold flex items-center gap-2">
🏛 Council
</h2>
<p className="text-white/60 mt-1">
Manage AI agent teams for each project. I'm the boss — I delegate tasks and oversee execution.
</p>
</div>
{/* Team Grid */}
<div className="grid gap-4 md:grid-cols-3">
{teams.map((team) => (
<button
key={team.id}
onClick={() => setSelectedTeam(team.id)}
className={`p-4 rounded-xl border text-left transition ${
selectedTeam === team.id
? "border-brand-pink bg-brand-pink/10"
: "border-white/10 bg-white/5 hover:border-white/30"
}`}
>
<div className="flex items-center gap-3 mb-2">
<span className="text-2xl">{team.icon}</span>
<div>
<p className="font-semibold">{team.name}</p>
<p className="text-xs text-white/50">{team.agents.length} agents</p>
</div>
</div>
<p className="text-sm text-white/60">{team.description}</p>
<div className="mt-3 flex gap-1">
{team.agents.slice(0, 4).map(a => (
<span key={a.id} className={`w-2 h-2 rounded-full ${getStatusColor(a.status).split(' ')[1].replace('/', '-')}`} />
))}
</div>
</button>
))}
</div>
{/* Selected Team Detail */}
{currentTeam && (
<div className="rounded-xl border border-white/10 bg-white/5 p-6">
<div className="flex items-center justify-between mb-4">
<div className="flex items-center gap-3">
<span className="text-2xl">{currentTeam.icon}</span>
<div>
<h3 className="font-semibold">{currentTeam.name}</h3>
<p className="text-sm text-white/50">{currentTeam.description}</p>
</div>
</div>
<button onClick={() => setSelectedTeam(null)} className="text-white/50 hover:text-white">✕</button>
</div>
{/* Agents */}
<div className="space-y-3">
{currentTeam.agents.map((agent) => (
<div key={agent.id} className="p-4 rounded-lg border border-white/10 bg-white/5">
<div className="flex items-center justify-between mb-2">
<div className="flex items-center gap-2">
<span className={`px-2 py-0.5 rounded text-xs font-medium ${getStatusColor(agent.status)}`}>
{agent.status.toUpperCase()}
</span>
<span className="font-medium">{agent.name}</span>
<span className="text-white/50">— {agent.role}</span>
</div>
{agent.lastRun && (
<span className="text-xs text-white/40">Last: {new Date(agent.lastRun).toLocaleTimeString()}</span>
)}
</div>
<p className="text-sm text-white/60 mb-3">{agent.description}</p>
{/* Task Input */}
<div className="flex gap-2">
<input
type="text"
value={runningTask}
onChange={(e) => setRunningTask(e.target.value)}
placeholder={`Ask ${agent.name} to...`}
className="flex-1 bg-white/10 border border-white/20 rounded-lg px-3 py-2 text-sm placeholder:text-white/40 focus:outline-none focus:border-brand-pink"
onKeyDown={(e) => e.key === "Enter" && spawnAgent(agent, runningTask)}
/>
<button
onClick={() => spawnAgent(agent, runningTask)}
disabled={agent.status === "working" || !runningTask.trim()}
className="px-4 py-2 bg-brand-pink rounded-lg text-sm font-medium disabled:opacity-50 hover:bg-[#ff7bc0] transition"
>
🚀 Run
</button>
</div>
{/* Output */}
{agentOutputs[agent.id] && (
<div className="mt-3 p-3 rounded-lg bg-black/30 text-sm font-mono text-white/80 whitespace-pre-wrap max-h-40 overflow-y-auto">
{agentOutputs[agent.id]}
</div>
)}
</div>
))}
</div>
</div>
)}
{/* Horus Boss Note */}
<div className="p-4 rounded-xl border border-brand-pink/30 bg-brand-pink/10">
<p className="text-sm text-brand-pink">
👁️ <strong>Note:</strong> I coordinate these agents. They run as sub-tasks within my sessions.
In production, they'd be separate AI processes. Currently simulating real agent spawning requires
the OpenClaw sub-agent system to be configured.
</p>
</div>
</div>
);
}