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 Link from "next/link";
|
||||
import { useMicrodaoDetail, useMicrodaoRooms } from "@/hooks/useMicrodao";
|
||||
import { useMicrodaoDetail, useMicrodaoRooms, useMicrodaoAgents } from "@/hooks/useMicrodao";
|
||||
import { DISTRICT_COLORS } from "@/lib/microdao";
|
||||
import { MicrodaoVisibilityCard } from "@/components/microdao/MicrodaoVisibilityCard";
|
||||
import { MicrodaoRoomsSection } from "@/components/microdao/MicrodaoRoomsSection";
|
||||
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 { CityChatWidget } from "@/components/city/CityChatWidget";
|
||||
import { AgentChatWidget } from "@/components/chat/AgentChatWidget";
|
||||
@@ -18,6 +19,7 @@ export default function MicrodaoDetailPage() {
|
||||
const slug = params?.slug as string;
|
||||
const { microdao, isLoading, error, mutate: refreshMicrodao } = useMicrodaoDetail(slug);
|
||||
const { rooms, mutate: refreshRooms } = useMicrodaoRooms(slug);
|
||||
const { agents: microdaoAgents } = useMicrodaoAgents(slug);
|
||||
|
||||
const handleRoomUpdated = () => {
|
||||
refreshRooms();
|
||||
@@ -224,48 +226,53 @@ export default function MicrodaoDetailPage() {
|
||||
onEnsureOrchestratorRoom={handleEnsureOrchestratorRoom}
|
||||
/>
|
||||
|
||||
<div className="grid md:grid-cols-2 gap-8">
|
||||
{/* Agents */}
|
||||
<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 Section - using new API */}
|
||||
<MicrodaoAgentsSection agents={microdaoAgents} microdaoSlug={slug} />
|
||||
|
||||
{microdao.agents.length === 0 ? (
|
||||
<div className="text-sm text-slate-500">Агенти ще не привʼязані.</div>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
{microdao.agents.map((a) => (
|
||||
<div
|
||||
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" />
|
||||
<div className="grid md:grid-cols-2 gap-8">
|
||||
{/* Legacy Agents (from microdao detail) - hidden if new API has data */}
|
||||
{microdaoAgents.length === 0 && (
|
||||
<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="text-sm text-slate-500">Агенти ще не привʼязані.</div>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
{microdao.agents.map((a) => (
|
||||
<div
|
||||
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>
|
||||
{a.role && (
|
||||
<div className="text-xs text-slate-500 capitalize">{a.role}</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">
|
||||
Core
|
||||
</span>
|
||||
)}
|
||||
</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">
|
||||
Core
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* Public Citizens */}
|
||||
<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";
|
||||
|
||||
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 { CityChatWidget } from "@/components/city/CityChatWidget";
|
||||
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",
|
||||
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({
|
||||
|
||||
@@ -215,3 +215,90 @@ export function useMicrodaoRooms(
|
||||
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