Frontend: - MicrodaoAgentsSection component with role badges - useMicrodaoAgents hook - Extended room_role mapping (operations, knowledge, treasury, ai-core, etc.) - API route for /api/microdao/[slug]/agents Matrix: All 13 new rooms synced with Matrix
215 lines
7.6 KiB
TypeScript
215 lines
7.6 KiB
TypeScript
"use client";
|
||
|
||
import Link from "next/link";
|
||
import { Bot, Crown, Users, Shield, Sparkles } from "lucide-react";
|
||
import { MicrodaoAgent } from "@/hooks/useMicrodao";
|
||
|
||
interface MicrodaoAgentsSectionProps {
|
||
agents: MicrodaoAgent[];
|
||
microdaoSlug?: string;
|
||
}
|
||
|
||
const ROLE_META: Record<string, { label: string; chipClass: string; icon: React.ReactNode }> = {
|
||
orchestrator: {
|
||
label: "Orchestrator",
|
||
chipClass: "bg-fuchsia-500/10 text-fuchsia-300 border-fuchsia-500/30",
|
||
icon: <Crown className="w-3.5 h-3.5" />,
|
||
},
|
||
district_lead: {
|
||
label: "District Lead",
|
||
chipClass: "bg-purple-500/10 text-purple-300 border-purple-500/30",
|
||
icon: <Crown className="w-3.5 h-3.5" />,
|
||
},
|
||
core_team: {
|
||
label: "Core Team",
|
||
chipClass: "bg-indigo-500/10 text-indigo-300 border-indigo-500/30",
|
||
icon: <Users className="w-3.5 h-3.5" />,
|
||
},
|
||
member: {
|
||
label: "Member",
|
||
chipClass: "bg-slate-500/10 text-slate-300 border-slate-500/30",
|
||
icon: <Bot className="w-3.5 h-3.5" />,
|
||
},
|
||
guardian: {
|
||
label: "Guardian",
|
||
chipClass: "bg-rose-500/10 text-rose-300 border-rose-500/30",
|
||
icon: <Shield className="w-3.5 h-3.5" />,
|
||
},
|
||
steward: {
|
||
label: "Steward",
|
||
chipClass: "bg-emerald-500/10 text-emerald-300 border-emerald-500/30",
|
||
icon: <Sparkles className="w-3.5 h-3.5" />,
|
||
},
|
||
};
|
||
|
||
const STATUS_COLORS: Record<string, string> = {
|
||
active: "bg-emerald-400",
|
||
inactive: "bg-slate-500",
|
||
suspended: "bg-yellow-400",
|
||
offline: "bg-slate-600",
|
||
};
|
||
|
||
export function MicrodaoAgentsSection({ agents, microdaoSlug }: MicrodaoAgentsSectionProps) {
|
||
if (!agents || agents.length === 0) {
|
||
return (
|
||
<section className="bg-slate-800/30 border border-slate-700/50 rounded-xl p-6 space-y-4">
|
||
<h2 className="text-lg font-semibold text-slate-100 flex items-center gap-2">
|
||
<Bot className="w-5 h-5 text-violet-400" />
|
||
Агенти MicroDAO
|
||
</h2>
|
||
<p className="text-sm text-slate-500">
|
||
Для цього MicroDAO ще не призначені агенти.
|
||
</p>
|
||
</section>
|
||
);
|
||
}
|
||
|
||
// Group agents by role
|
||
const orchestrators = agents.filter(a => a.role === "orchestrator" || a.role === "district_lead");
|
||
const coreTeam = agents.filter(a => a.role === "core_team" || a.is_core);
|
||
const others = agents.filter(a => !orchestrators.includes(a) && !coreTeam.includes(a));
|
||
|
||
return (
|
||
<section className="bg-slate-800/30 border border-slate-700/50 rounded-xl p-6 space-y-6">
|
||
<div className="flex items-center justify-between">
|
||
<h2 className="text-lg font-semibold text-slate-100 flex items-center gap-2">
|
||
<Bot className="w-5 h-5 text-violet-400" />
|
||
Агенти MicroDAO
|
||
<span className="text-sm font-normal text-slate-500">({agents.length})</span>
|
||
</h2>
|
||
</div>
|
||
|
||
{/* Orchestrator(s) */}
|
||
{orchestrators.length > 0 && (
|
||
<div className="space-y-3">
|
||
<div className="text-xs text-slate-400 uppercase tracking-wider font-medium">
|
||
Orchestrator
|
||
</div>
|
||
<div className="grid gap-3">
|
||
{orchestrators.map(agent => (
|
||
<AgentCard key={agent.id} agent={agent} featured />
|
||
))}
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* Core Team */}
|
||
{coreTeam.length > 0 && (
|
||
<div className="space-y-3">
|
||
<div className="text-xs text-slate-400 uppercase tracking-wider font-medium">
|
||
Core Team
|
||
</div>
|
||
<div className="grid gap-3 md:grid-cols-2">
|
||
{coreTeam.map(agent => (
|
||
<AgentCard key={agent.id} agent={agent} />
|
||
))}
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* Other Members */}
|
||
{others.length > 0 && (
|
||
<div className="space-y-3">
|
||
<div className="text-xs text-slate-400 uppercase tracking-wider font-medium">
|
||
Members
|
||
</div>
|
||
<div className="grid gap-3 md:grid-cols-2 lg:grid-cols-3">
|
||
{others.map(agent => (
|
||
<AgentCard key={agent.id} agent={agent} compact />
|
||
))}
|
||
</div>
|
||
</div>
|
||
)}
|
||
</section>
|
||
);
|
||
}
|
||
|
||
interface AgentCardProps {
|
||
agent: MicrodaoAgent;
|
||
featured?: boolean;
|
||
compact?: boolean;
|
||
}
|
||
|
||
function AgentCard({ agent, featured = false, compact = false }: AgentCardProps) {
|
||
const roleMeta = ROLE_META[agent.role] || ROLE_META.member;
|
||
const statusColor = STATUS_COLORS[agent.status] || STATUS_COLORS.offline;
|
||
|
||
if (compact) {
|
||
return (
|
||
<Link href={`/agents/${agent.id}`}>
|
||
<div className="flex items-center gap-3 p-3 bg-slate-900/50 border border-slate-700/30 rounded-lg hover:border-slate-600/50 transition-colors">
|
||
<div className="relative">
|
||
<div className="w-8 h-8 rounded-full bg-violet-500/20 flex items-center justify-center">
|
||
{agent.avatar_url ? (
|
||
<img src={agent.avatar_url} alt={agent.name} className="w-8 h-8 rounded-full" />
|
||
) : (
|
||
<Bot className="w-4 h-4 text-violet-400" />
|
||
)}
|
||
</div>
|
||
<span className={`absolute -bottom-0.5 -right-0.5 w-2.5 h-2.5 rounded-full border-2 border-slate-900 ${statusColor}`} />
|
||
</div>
|
||
<div className="flex-1 min-w-0">
|
||
<div className="text-sm font-medium text-slate-200 truncate">{agent.name}</div>
|
||
</div>
|
||
</div>
|
||
</Link>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<Link href={`/agents/${agent.id}`}>
|
||
<div className={`p-4 rounded-xl border transition-colors ${
|
||
featured
|
||
? "bg-gradient-to-br from-fuchsia-950/30 to-violet-950/30 border-fuchsia-500/30 hover:border-fuchsia-400/50"
|
||
: "bg-slate-900/50 border-slate-700/30 hover:border-slate-600/50"
|
||
}`}>
|
||
<div className="flex items-start gap-4">
|
||
{/* Avatar */}
|
||
<div className="relative">
|
||
<div className={`rounded-full bg-violet-500/20 flex items-center justify-center ${
|
||
featured ? "w-14 h-14" : "w-10 h-10"
|
||
}`}>
|
||
{agent.avatar_url ? (
|
||
<img src={agent.avatar_url} alt={agent.name} className={`rounded-full ${featured ? "w-14 h-14" : "w-10 h-10"}`} />
|
||
) : (
|
||
<Bot className={`text-violet-400 ${featured ? "w-7 h-7" : "w-5 h-5"}`} />
|
||
)}
|
||
</div>
|
||
<span className={`absolute -bottom-0.5 -right-0.5 w-3 h-3 rounded-full border-2 border-slate-900 ${statusColor}`} />
|
||
</div>
|
||
|
||
{/* Info */}
|
||
<div className="flex-1 min-w-0 space-y-1">
|
||
<div className="flex items-center gap-2">
|
||
<span className={`font-medium ${featured ? "text-lg text-white" : "text-base text-slate-200"}`}>
|
||
{agent.name}
|
||
</span>
|
||
{agent.is_core && (
|
||
<span className="text-[10px] uppercase tracking-wider px-1.5 py-0.5 rounded bg-amber-500/10 text-amber-300 border border-amber-500/20">
|
||
Core
|
||
</span>
|
||
)}
|
||
</div>
|
||
|
||
{/* Role badge */}
|
||
<div className={`inline-flex items-center gap-1.5 px-2 py-0.5 rounded-full border text-[11px] ${roleMeta.chipClass}`}>
|
||
{roleMeta.icon}
|
||
<span>{roleMeta.label}</span>
|
||
</div>
|
||
|
||
{/* Kind and Gov Level */}
|
||
{(agent.kind || agent.gov_level) && (
|
||
<div className="flex items-center gap-2 text-xs text-slate-500">
|
||
{agent.kind && <span>{agent.kind}</span>}
|
||
{agent.kind && agent.gov_level && <span>•</span>}
|
||
{agent.gov_level && <span>{agent.gov_level}</span>}
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</Link>
|
||
);
|
||
}
|
||
|