Files
microdao-daarion/apps/web/src/app/agents/page.tsx
Apple 0e743e5629 fix: restore microdao/agent logos and banner asset urls
- Enhanced normalizeAssetUrl to handle all edge cases
- Added normalizeAssetUrl to all avatar/logo/banner usages:
  - agents/page.tsx
  - agents/[agentId]/page.tsx
  - citizens/page.tsx
  - citizens/[slug]/page.tsx
  - MicrodaoAgentsSection.tsx
  - MicrodaoBrandingCard.tsx
  - AgentSummaryCard.tsx
  - AgentChatWidget.tsx

Task: TASK_PHASE_ASSET_BRANDING_HOTFIX_v1
2025-12-01 11:40:03 -08:00

243 lines
9.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, Users, Building2, Server, ExternalLink, Shield, Plus } from 'lucide-react';
import { useAgentList } from '@/hooks/useAgents';
import { AgentSummary, getGovLevelBadge } from '@/lib/types/agents';
import { AgentPresenceBadge } from '@/components/ui/AgentPresenceBadge';
import { normalizeAssetUrl } from '@/lib/utils/assetUrl';
// Kind emoji mapping
const kindEmoji: Record<string, string> = {
vision: '👁️',
curator: '🎨',
security: '🛡️',
finance: '💰',
civic: '🏛️',
oracle: '🔮',
builder: '🏗️',
research: '🔬',
marketing: '📢',
orchestrator: '🎭',
mediator: '⚖️',
assistant: '🤖',
};
function getNodeBadge(nodeId: string | undefined | null): { label: string; color: string } {
if (!nodeId) return { label: 'Unknown', color: 'bg-gray-500/20 text-gray-400' };
if (nodeId.includes('node-1')) return { label: 'НОДА1', color: 'bg-emerald-500/20 text-emerald-400' };
if (nodeId.includes('node-2')) return { label: 'НОДА2', color: 'bg-amber-500/20 text-amber-400' };
return { label: 'НОДА', color: 'bg-purple-500/20 text-purple-400' };
}
function AgentCard({ agent }: { agent: AgentSummary }) {
const isOnline = agent.status === 'online';
const statusColor = isOnline ? 'text-emerald-400' : 'text-white/40';
const emoji = kindEmoji[agent.kind] || '🤖';
const nodeBadge = getNodeBadge(agent.home_node?.id);
const govBadge = getGovLevelBadge(agent.gov_level);
return (
<Link
href={`/agents/${agent.id}`}
className="group bg-white/5 backdrop-blur-md rounded-2xl border border-white/10 p-6 hover:border-violet-500/50 transition-all hover:bg-white/10"
>
{/* Header */}
<div className="flex items-start gap-4 mb-4">
<div className="relative">
<div className="w-14 h-14 rounded-xl bg-gradient-to-br from-violet-500/30 to-purple-600/30 flex items-center justify-center flex-shrink-0 overflow-hidden">
{normalizeAssetUrl(agent.avatar_url) ? (
// eslint-disable-next-line @next/next/no-img-element
<img
src={normalizeAssetUrl(agent.avatar_url)!}
alt={agent.display_name}
className="w-full h-full object-cover"
/>
) : (
<span className="text-2xl">{emoji}</span>
)}
</div>
{/* Presence Badge */}
<div className="absolute -top-1 -right-1">
<AgentPresenceBadge agentId={agent.id} size="sm" />
</div>
</div>
<div className="flex-1 min-w-0">
<h3 className="text-lg font-semibold text-white truncate group-hover:text-violet-400 transition-colors">
{agent.display_name}
</h3>
{agent.title && (
<p className="text-sm text-cyan-400">{agent.title}</p>
)}
<p className="text-xs text-white/50 mt-1">{agent.kind}</p>
</div>
</div>
{/* District & MicroDAO */}
<div className="space-y-2 mb-4">
{agent.district && (
<div className="flex items-center gap-2 text-sm text-white/60">
<Building2 className="w-4 h-4" />
<span>{agent.district}</span>
</div>
)}
{agent.primary_microdao_name && (
<div className="flex items-center gap-2 text-sm text-white/60">
<Users className="w-4 h-4" />
<span>{agent.primary_microdao_name}</span>
</div>
)}
</div>
{/* Footer */}
<div className="pt-4 border-t border-white/10 flex items-center justify-between">
<div className="flex items-center gap-2 flex-wrap">
<span className={`flex items-center gap-1.5 text-xs ${statusColor}`}>
<span
className={`w-2 h-2 rounded-full ${
isOnline ? 'bg-emerald-500' : 'bg-white/30'
}`}
/>
{isOnline ? 'online' : 'offline'}
</span>
<span className={`px-2 py-0.5 rounded text-[10px] font-medium ${nodeBadge.color}`}>
{nodeBadge.label}
</span>
{/* Gov Level Badge (A1) */}
{agent.gov_level && (
<span className={`px-2 py-0.5 rounded text-[10px] font-medium flex items-center gap-1 ${govBadge.color}`}>
<Shield className="w-3 h-3" />
{govBadge.label}
</span>
)}
{agent.is_public && (
<span className="px-2 py-0.5 rounded text-[10px] font-medium bg-cyan-500/20 text-cyan-400">
Public
</span>
)}
</div>
<span className="text-violet-400 text-sm group-hover:translate-x-1 transition-transform">
Open
</span>
</div>
</Link>
);
}
export default function AgentsPage() {
const { agents, total, isLoading, error } = useAgentList({ limit: 100 });
return (
<div className="min-h-screen bg-gradient-to-b from-slate-900 via-slate-900 to-slate-950">
<div className="max-w-7xl mx-auto px-4 py-12">
{/* Header */}
<div className="mb-8">
<div className="flex items-center justify-between">
<div>
<div className="flex items-center gap-3 mb-2">
<Bot className="w-8 h-8 text-violet-400" />
<h1 className="text-4xl font-bold bg-gradient-to-r from-violet-400 to-purple-400 bg-clip-text text-transparent">
Agent Console
</h1>
</div>
<p className="text-white/60 text-lg">
Всі AI-агенти мережі DAARION
</p>
<p className="text-cyan-400 mt-2">
Знайдено агентів: {total}
</p>
</div>
<Link
href="/agents/new"
className="inline-flex items-center gap-2 px-6 py-3 bg-gradient-to-r from-violet-500 to-purple-600 rounded-xl text-white font-semibold hover:from-violet-400 hover:to-purple-500 transition-all shadow-lg shadow-violet-500/25 hover:shadow-violet-500/40"
>
<Plus className="w-5 h-5" />
Новий агент
</Link>
</div>
</div>
{/* Filters */}
<div className="bg-white/5 backdrop-blur-md rounded-2xl border border-white/10 p-6 mb-8">
<div className="flex flex-wrap gap-4">
<div className="flex items-center gap-2">
<Server className="w-4 h-4 text-white/40" />
<span className="text-sm text-white/60">Фільтр по нодах:</span>
<span className="px-2 py-1 rounded text-xs font-medium bg-emerald-500/20 text-emerald-400">
НОДА1 (Production)
</span>
<span className="px-2 py-1 rounded text-xs font-medium bg-amber-500/20 text-amber-400">
НОДА2 (Development)
</span>
</div>
</div>
</div>
{/* Content */}
{error && (
<div className="bg-red-500/10 border border-red-500/30 rounded-xl p-6 text-center mb-8">
<p className="text-red-400">Помилка завантаження агентів</p>
</div>
)}
{isLoading ? (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{Array.from({ length: 6 }).map((_, i) => (
<div
key={i}
className="bg-white/5 rounded-2xl border border-white/10 p-6 animate-pulse"
>
<div className="flex items-start gap-4 mb-4">
<div className="w-14 h-14 rounded-xl bg-white/10" />
<div className="flex-1 space-y-2">
<div className="h-5 bg-white/10 rounded w-3/4" />
<div className="h-4 bg-white/10 rounded w-1/2" />
</div>
</div>
<div className="h-4 bg-white/10 rounded w-full mb-2" />
<div className="h-4 bg-white/10 rounded w-2/3" />
</div>
))}
</div>
) : agents.length === 0 ? (
<div className="bg-white/5 backdrop-blur-md rounded-2xl border border-white/10 p-12 text-center">
<Bot className="w-16 h-16 text-white/20 mx-auto mb-4" />
<h2 className="text-xl font-semibold text-white mb-2">
Агенти не знайдені
</h2>
<p className="text-white/50">
Наразі немає доступних агентів. Спробуйте пізніше.
</p>
</div>
) : (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{agents.map((agent) => (
<AgentCard key={agent.id} agent={agent} />
))}
</div>
)}
{/* Links */}
<div className="mt-12 flex flex-wrap gap-4">
<Link
href="/citizens"
className="flex items-center gap-2 px-4 py-2 bg-cyan-500/10 hover:bg-cyan-500/20 border border-cyan-500/30 rounded-lg text-cyan-400 transition-colors"
>
<Users className="w-4 h-4" />
Публічні громадяни
<ExternalLink className="w-3 h-3" />
</Link>
<Link
href="/nodes"
className="flex items-center gap-2 px-4 py-2 bg-purple-500/10 hover:bg-purple-500/20 border border-purple-500/30 rounded-lg text-purple-400 transition-colors"
>
<Server className="w-4 h-4" />
Node Dashboard
<ExternalLink className="w-3 h-3" />
</Link>
</div>
</div>
</div>
);
}