121 lines
4.3 KiB
TypeScript
121 lines
4.3 KiB
TypeScript
"use client";
|
||
|
||
import { useState, useEffect } from "react";
|
||
import { AgentTeam, defaultTeams } from "@/lib/council/types";
|
||
import AgentModal from "./AgentModal";
|
||
|
||
const STORAGE_KEY = "horus:council";
|
||
|
||
export default function Council() {
|
||
const [teams, setTeams] = useState<AgentTeam[]>(defaultTeams);
|
||
const [selectedTeam, setSelectedTeam] = useState<string | null>(null);
|
||
const [selectedAgent, setSelectedAgent] = useState<string | null>(null);
|
||
|
||
useEffect(() => {
|
||
if (typeof window === "undefined") return;
|
||
const saved = localStorage.getItem(STORAGE_KEY);
|
||
if (saved) {
|
||
try {
|
||
setTeams(JSON.parse(saved));
|
||
} catch {}
|
||
}
|
||
}, []);
|
||
|
||
useEffect(() => {
|
||
if (typeof window === "undefined") return;
|
||
localStorage.setItem(STORAGE_KEY, JSON.stringify(teams));
|
||
}, [teams]);
|
||
|
||
const getStatusColor = (status: string) => {
|
||
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 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>
|
||
</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 - Grid Layout */}
|
||
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
|
||
{currentTeam.agents.map((agent) => (
|
||
<button
|
||
key={agent.id}
|
||
onClick={() => setSelectedAgent(agent.id)}
|
||
className="p-4 rounded-lg border border-white/10 bg-white/5 hover:bg-white/10 hover:border-brand-pink/50 transition text-left"
|
||
>
|
||
<div className="flex items-center gap-2 mb-2">
|
||
<span className={`px-2 py-0.5 rounded text-xs font-medium ${getStatusColor(agent.status)}`}>
|
||
{agent.status.toUpperCase()}
|
||
</span>
|
||
</div>
|
||
<p className="font-medium">{agent.name}</p>
|
||
<p className="text-xs text-white/50">{agent.role}</p>
|
||
<div className="mt-3 pt-2 border-t border-white/10">
|
||
<span className="text-xs text-brand-pink">🧑💼 Open →</span>
|
||
</div>
|
||
</button>
|
||
))}
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* Agent Modal */}
|
||
{selectedAgent && (
|
||
<AgentModal agentId={selectedAgent} onClose={() => setSelectedAgent(null)} />
|
||
)}
|
||
</div>
|
||
);
|
||
}
|