Files
microdao-daarion/apps/web/src/components/microdao/MicrodaoAgentsSection.tsx
Apple 7b61786c96 feat: MicroDAO Agents Section + Room roles
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
2025-11-30 11:57:24 -08:00

215 lines
7.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"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>
);
}