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
This commit is contained in:
42
apps/web/src/app/api/microdao/[slug]/agents/route.ts
Normal file
42
apps/web/src/app/api/microdao/[slug]/agents/route.ts
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
|
|
||||||
|
const CITY_API_URL = process.env.CITY_API_URL || "http://localhost:7001";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET /api/microdao/[slug]/agents
|
||||||
|
* Get all agents for a MicroDAO
|
||||||
|
*/
|
||||||
|
export async function GET(
|
||||||
|
request: NextRequest,
|
||||||
|
context: { params: Promise<{ slug: string }> }
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const { slug } = await context.params;
|
||||||
|
|
||||||
|
const response = await fetch(`${CITY_API_URL}/city/microdao/${slug}/agents`, {
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
cache: "no-store",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const text = await response.text();
|
||||||
|
console.error("Failed to get microdao agents:", response.status, text);
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: `Backend error: ${response.status}` },
|
||||||
|
{ status: response.status }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
return NextResponse.json(data);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error getting microdao agents:", error);
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "Internal server error" },
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -2,11 +2,12 @@
|
|||||||
|
|
||||||
import { useParams, useRouter } from "next/navigation";
|
import { useParams, useRouter } from "next/navigation";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useMicrodaoDetail, useMicrodaoRooms } from "@/hooks/useMicrodao";
|
import { useMicrodaoDetail, useMicrodaoRooms, useMicrodaoAgents } from "@/hooks/useMicrodao";
|
||||||
import { DISTRICT_COLORS } from "@/lib/microdao";
|
import { DISTRICT_COLORS } from "@/lib/microdao";
|
||||||
import { MicrodaoVisibilityCard } from "@/components/microdao/MicrodaoVisibilityCard";
|
import { MicrodaoVisibilityCard } from "@/components/microdao/MicrodaoVisibilityCard";
|
||||||
import { MicrodaoRoomsSection } from "@/components/microdao/MicrodaoRoomsSection";
|
import { MicrodaoRoomsSection } from "@/components/microdao/MicrodaoRoomsSection";
|
||||||
import { MicrodaoRoomsAdminPanel } from "@/components/microdao/MicrodaoRoomsAdminPanel";
|
import { MicrodaoRoomsAdminPanel } from "@/components/microdao/MicrodaoRoomsAdminPanel";
|
||||||
|
import { MicrodaoAgentsSection } from "@/components/microdao/MicrodaoAgentsSection";
|
||||||
import { ChevronLeft, Users, MessageSquare, Crown, Building2, Globe, Lock, Layers, BarChart3, Bot, MessageCircle } from "lucide-react";
|
import { ChevronLeft, Users, MessageSquare, Crown, Building2, Globe, Lock, Layers, BarChart3, Bot, MessageCircle } from "lucide-react";
|
||||||
import { CityChatWidget } from "@/components/city/CityChatWidget";
|
import { CityChatWidget } from "@/components/city/CityChatWidget";
|
||||||
import { AgentChatWidget } from "@/components/chat/AgentChatWidget";
|
import { AgentChatWidget } from "@/components/chat/AgentChatWidget";
|
||||||
@@ -18,6 +19,7 @@ export default function MicrodaoDetailPage() {
|
|||||||
const slug = params?.slug as string;
|
const slug = params?.slug as string;
|
||||||
const { microdao, isLoading, error, mutate: refreshMicrodao } = useMicrodaoDetail(slug);
|
const { microdao, isLoading, error, mutate: refreshMicrodao } = useMicrodaoDetail(slug);
|
||||||
const { rooms, mutate: refreshRooms } = useMicrodaoRooms(slug);
|
const { rooms, mutate: refreshRooms } = useMicrodaoRooms(slug);
|
||||||
|
const { agents: microdaoAgents } = useMicrodaoAgents(slug);
|
||||||
|
|
||||||
const handleRoomUpdated = () => {
|
const handleRoomUpdated = () => {
|
||||||
refreshRooms();
|
refreshRooms();
|
||||||
@@ -224,48 +226,53 @@ export default function MicrodaoDetailPage() {
|
|||||||
onEnsureOrchestratorRoom={handleEnsureOrchestratorRoom}
|
onEnsureOrchestratorRoom={handleEnsureOrchestratorRoom}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="grid md:grid-cols-2 gap-8">
|
{/* MicroDAO Agents Section - using new API */}
|
||||||
{/* Agents */}
|
<MicrodaoAgentsSection agents={microdaoAgents} microdaoSlug={slug} />
|
||||||
<section className="bg-slate-800/30 border border-slate-700/50 rounded-xl p-6 space-y-4 h-full">
|
|
||||||
<h2 className="text-lg font-semibold text-slate-100 flex items-center gap-2">
|
|
||||||
<Bot className="w-5 h-5 text-cyan-400" />
|
|
||||||
Агентська команда
|
|
||||||
<span className="text-sm font-normal text-slate-500">({microdao.agents.length})</span>
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
{microdao.agents.length === 0 ? (
|
<div className="grid md:grid-cols-2 gap-8">
|
||||||
<div className="text-sm text-slate-500">Агенти ще не привʼязані.</div>
|
{/* Legacy Agents (from microdao detail) - hidden if new API has data */}
|
||||||
) : (
|
{microdaoAgents.length === 0 && (
|
||||||
<div className="space-y-2">
|
<section className="bg-slate-800/30 border border-slate-700/50 rounded-xl p-6 space-y-4 h-full">
|
||||||
{microdao.agents.map((a) => (
|
<h2 className="text-lg font-semibold text-slate-100 flex items-center gap-2">
|
||||||
<div
|
<Bot className="w-5 h-5 text-cyan-400" />
|
||||||
key={a.agent_id}
|
Агентська команда
|
||||||
className="bg-slate-900/50 border border-slate-700/30 rounded-lg px-4 py-3 flex items-center justify-between hover:border-slate-600/50 transition-colors"
|
<span className="text-sm font-normal text-slate-500">({microdao.agents.length})</span>
|
||||||
>
|
</h2>
|
||||||
<div className="space-y-0.5">
|
|
||||||
<Link
|
{microdao.agents.length === 0 ? (
|
||||||
href={`/agents/${a.agent_id}`}
|
<div className="text-sm text-slate-500">Агенти ще не привʼязані.</div>
|
||||||
className="text-sm font-medium text-slate-200 hover:text-cyan-400 transition-colors flex items-center gap-2"
|
) : (
|
||||||
>
|
<div className="space-y-2">
|
||||||
{a.display_name}
|
{microdao.agents.map((a) => (
|
||||||
{a.agent_id === microdao.orchestrator_agent_id && (
|
<div
|
||||||
<Crown className="w-3 h-3 text-amber-400" />
|
key={a.agent_id}
|
||||||
|
className="bg-slate-900/50 border border-slate-700/30 rounded-lg px-4 py-3 flex items-center justify-between hover:border-slate-600/50 transition-colors"
|
||||||
|
>
|
||||||
|
<div className="space-y-0.5">
|
||||||
|
<Link
|
||||||
|
href={`/agents/${a.agent_id}`}
|
||||||
|
className="text-sm font-medium text-slate-200 hover:text-cyan-400 transition-colors flex items-center gap-2"
|
||||||
|
>
|
||||||
|
{a.display_name}
|
||||||
|
{a.agent_id === microdao.orchestrator_agent_id && (
|
||||||
|
<Crown className="w-3 h-3 text-amber-400" />
|
||||||
|
)}
|
||||||
|
</Link>
|
||||||
|
{a.role && (
|
||||||
|
<div className="text-xs text-slate-500 capitalize">{a.role}</div>
|
||||||
)}
|
)}
|
||||||
</Link>
|
</div>
|
||||||
{a.role && (
|
{a.is_core && (
|
||||||
<div className="text-xs text-slate-500 capitalize">{a.role}</div>
|
<span className="text-[10px] px-2 py-0.5 rounded-full bg-violet-500/10 text-violet-400 border border-violet-500/30 uppercase tracking-wide">
|
||||||
|
Core
|
||||||
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{a.is_core && (
|
))}
|
||||||
<span className="text-[10px] px-2 py-0.5 rounded-full bg-violet-500/10 text-violet-400 border border-violet-500/30 uppercase tracking-wide">
|
</div>
|
||||||
Core
|
)}
|
||||||
</span>
|
</section>
|
||||||
)}
|
)}
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{/* Public Citizens */}
|
{/* Public Citizens */}
|
||||||
<section className="bg-slate-800/30 border border-slate-700/50 rounded-xl p-6 space-y-4 h-full">
|
<section className="bg-slate-800/30 border border-slate-700/50 rounded-xl p-6 space-y-4 h-full">
|
||||||
|
|||||||
214
apps/web/src/components/microdao/MicrodaoAgentsSection.tsx
Normal file
214
apps/web/src/components/microdao/MicrodaoAgentsSection.tsx
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
"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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { MessageCircle, Home, Users, FlaskConical, Shield, Gavel, Hash, Users2, Bot, PlusCircle } from "lucide-react";
|
import { MessageCircle, Home, Users, FlaskConical, Shield, Gavel, Hash, Users2, Bot, PlusCircle, Crown } from "lucide-react";
|
||||||
import { CityRoomSummary } from "@/lib/types/microdao";
|
import { CityRoomSummary } from "@/lib/types/microdao";
|
||||||
import { CityChatWidget } from "@/components/city/CityChatWidget";
|
import { CityChatWidget } from "@/components/city/CityChatWidget";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
@@ -51,6 +51,58 @@ const ROLE_META: Record<string, { label: string; chipClass: string; icon: React.
|
|||||||
chipClass: "bg-fuchsia-500/10 text-fuchsia-300 border-fuchsia-500/30",
|
chipClass: "bg-fuchsia-500/10 text-fuchsia-300 border-fuchsia-500/30",
|
||||||
icon: <Bot className="w-3.5 h-3.5" />,
|
icon: <Bot className="w-3.5 h-3.5" />,
|
||||||
},
|
},
|
||||||
|
// New room roles for MicroDAO
|
||||||
|
operations: {
|
||||||
|
label: "Operations",
|
||||||
|
chipClass: "bg-orange-500/10 text-orange-300 border-orange-500/30",
|
||||||
|
icon: <Users className="w-3.5 h-3.5" />,
|
||||||
|
},
|
||||||
|
knowledge: {
|
||||||
|
label: "Knowledge Base",
|
||||||
|
chipClass: "bg-cyan-500/10 text-cyan-300 border-cyan-500/30",
|
||||||
|
icon: <FlaskConical className="w-3.5 h-3.5" />,
|
||||||
|
},
|
||||||
|
treasury: {
|
||||||
|
label: "Treasury",
|
||||||
|
chipClass: "bg-yellow-500/10 text-yellow-300 border-yellow-500/30",
|
||||||
|
icon: <Shield className="w-3.5 h-3.5" />,
|
||||||
|
},
|
||||||
|
"ai-core": {
|
||||||
|
label: "AI Core",
|
||||||
|
chipClass: "bg-purple-500/10 text-purple-300 border-purple-500/30",
|
||||||
|
icon: <Bot className="w-3.5 h-3.5" />,
|
||||||
|
},
|
||||||
|
// District-specific
|
||||||
|
events: {
|
||||||
|
label: "Events",
|
||||||
|
chipClass: "bg-pink-500/10 text-pink-300 border-pink-500/30",
|
||||||
|
icon: <Users className="w-3.5 h-3.5" />,
|
||||||
|
},
|
||||||
|
masters: {
|
||||||
|
label: "Masters",
|
||||||
|
chipClass: "bg-indigo-500/10 text-indigo-300 border-indigo-500/30",
|
||||||
|
icon: <Crown className="w-3.5 h-3.5" />,
|
||||||
|
},
|
||||||
|
supply: {
|
||||||
|
label: "Supply Chain",
|
||||||
|
chipClass: "bg-green-500/10 text-green-300 border-green-500/30",
|
||||||
|
icon: <Users className="w-3.5 h-3.5" />,
|
||||||
|
},
|
||||||
|
producers: {
|
||||||
|
label: "Producers",
|
||||||
|
chipClass: "bg-lime-500/10 text-lime-300 border-lime-500/30",
|
||||||
|
icon: <Users2 className="w-3.5 h-3.5" />,
|
||||||
|
},
|
||||||
|
compute: {
|
||||||
|
label: "Compute Grid",
|
||||||
|
chipClass: "bg-amber-500/10 text-amber-300 border-amber-500/30",
|
||||||
|
icon: <Bot className="w-3.5 h-3.5" />,
|
||||||
|
},
|
||||||
|
providers: {
|
||||||
|
label: "Providers",
|
||||||
|
chipClass: "bg-orange-500/10 text-orange-300 border-orange-500/30",
|
||||||
|
icon: <Users2 className="w-3.5 h-3.5" />,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export function MicrodaoRoomsSection({
|
export function MicrodaoRoomsSection({
|
||||||
|
|||||||
@@ -215,3 +215,90 @@ export function useMicrodaoRooms(
|
|||||||
mutate: fetchData,
|
mutate: fetchData,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// useMicrodaoAgents - fetch all agents for a MicroDAO
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
export interface MicrodaoAgent {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
kind: string;
|
||||||
|
status: string;
|
||||||
|
avatar_url?: string;
|
||||||
|
gov_level?: string;
|
||||||
|
role: string;
|
||||||
|
is_core: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UseMicrodaoAgentsOptions {
|
||||||
|
refreshInterval?: number;
|
||||||
|
enabled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UseMicrodaoAgentsResult {
|
||||||
|
agents: MicrodaoAgent[];
|
||||||
|
microdaoId: string | null;
|
||||||
|
microdaoSlug: string | null;
|
||||||
|
isLoading: boolean;
|
||||||
|
error: Error | null;
|
||||||
|
mutate: () => Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useMicrodaoAgents(
|
||||||
|
slug: string | undefined,
|
||||||
|
options: UseMicrodaoAgentsOptions = {}
|
||||||
|
): UseMicrodaoAgentsResult {
|
||||||
|
const { refreshInterval = 60000, enabled = true } = options;
|
||||||
|
|
||||||
|
const [agents, setAgents] = useState<MicrodaoAgent[]>([]);
|
||||||
|
const [microdaoId, setMicrodaoId] = useState<string | null>(null);
|
||||||
|
const [microdaoSlug, setMicrodaoSlug] = useState<string | null>(null);
|
||||||
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
const [error, setError] = useState<Error | null>(null);
|
||||||
|
|
||||||
|
const fetchData = useCallback(async () => {
|
||||||
|
if (!slug || !enabled) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
setIsLoading(true);
|
||||||
|
const res = await fetch(`/api/microdao/${encodeURIComponent(slug)}/agents`);
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
throw new Error(`Failed to fetch agents: ${res.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await res.json();
|
||||||
|
setAgents(data.agents || []);
|
||||||
|
setMicrodaoId(data.microdao_id);
|
||||||
|
setMicrodaoSlug(data.microdao_slug);
|
||||||
|
} catch (err) {
|
||||||
|
setError(err instanceof Error ? err : new Error(String(err)));
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
}, [slug, enabled]);
|
||||||
|
|
||||||
|
// Initial fetch
|
||||||
|
useEffect(() => {
|
||||||
|
fetchData();
|
||||||
|
}, [fetchData]);
|
||||||
|
|
||||||
|
// Auto-refresh
|
||||||
|
useEffect(() => {
|
||||||
|
if (!enabled || refreshInterval <= 0) return;
|
||||||
|
|
||||||
|
const interval = setInterval(fetchData, refreshInterval);
|
||||||
|
return () => clearInterval(interval);
|
||||||
|
}, [fetchData, refreshInterval, enabled]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
agents,
|
||||||
|
microdaoId,
|
||||||
|
microdaoSlug,
|
||||||
|
isLoading,
|
||||||
|
error,
|
||||||
|
mutate: fetchData,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user