feat(CC-079): add Agent Center page (8 agent templates with tool mapping + Hermes architecture)

This commit is contained in:
admin-valentin 2026-08-02 12:58:12 +00:00
parent 6cafc2ea9d
commit 9857b027c1

View file

@ -0,0 +1,178 @@
'use client';
import { useState } from 'react';
import { useQuery } from '@tanstack/react-query';
import Link from 'next/link';
import { apiFetch } from '../../../lib/api';
import { useSession } from '../../../components/session-provider';
interface DataSource { id: string; name: string; type: string; status: string; }
interface AgentTemplate {
id: string; name: string; description: string; icon: string;
tools: string[]; category: string; endpoint: string | null; status: 'active' | 'planned';
}
const AGENTS: AgentTemplate[] = [
{
id: 'research', name: 'Research Agent', description: 'Caută și sintetizează informații despre companii, persoane sau subiecte.',
icon: '🔍', tools: ['AnythingLLM', 'ClickHouse', 'OpenRouter'], category: 'Intelligence',
endpoint: '/v1/intelligence/companies/search', status: 'active',
},
{
id: 'document', name: 'Document Analyst', description: 'Analizează documente, extrage entități, clasifică și rezumă.',
icon: '📄', tools: ['Hermes', 'AnythingLLM'], category: 'Documents',
endpoint: null, status: 'planned',
},
{
id: 'financial', name: 'Financial Advisor', description: 'Analizează tranzacții, detectează anomalii, proiectează cashflow.',
icon: '💰', tools: ['ClickHouse', 'Hermes', 'OpenRouter'], category: 'Finance',
endpoint: null, status: 'planned',
},
{
id: 'crm', name: 'CRM Intelligence', description: 'Sugerează urmăriri, detectează relații reci, prioritizează contacte.',
icon: '🤝', tools: ['Hermes', 'ClickHouse'], category: 'CRM',
endpoint: null, status: 'planned',
},
{
id: 'compliance', name: 'Compliance Monitor', description: 'Verifică GDPR, detectează inconsistențe în clasificarea datelor, alertează.',
icon: '🛡️', tools: ['ClickHouse', 'Hermes'], category: 'Privacy',
endpoint: null, status: 'planned',
},
{
id: 'scheduler', name: 'Action Scheduler', description: 'Propune, programează și monitorizează acțiuni AI cu aprobare umană.',
icon: '🤖', tools: ['Hermes', 'n8n', 'CEO OS API'], category: 'Automation',
endpoint: '/v1/approvals', status: 'active',
},
{
id: 'email', name: 'Email Drafter', description: 'Redactează emailuri pe baza contextului din CRM, documente și conversații.',
icon: '✉️', tools: ['Hermes', 'OpenRouter'], category: 'Communication',
endpoint: null, status: 'planned',
},
{
id: 'opportunity', name: 'Opportunity Scout', description: 'Scanează surse externe, identifică oportunități relevante pentru profilul utilizatorului.',
icon: '🎯', tools: ['Hermes', 'AnythingLLM', 'ClickHouse'], category: 'Growth',
endpoint: null, status: 'planned',
},
];
const CATEGORIES = ['all', 'Intelligence', 'Documents', 'Finance', 'CRM', 'Privacy', 'Automation', 'Communication', 'Growth'];
export default function AgentCenterPage() {
const { activeTenant } = useSession();
const tenantId = activeTenant?.tenantId ?? '';
const [category, setCategory] = useState('all');
const [selected, setSelected] = useState<AgentTemplate | null>(null);
const { data: dataSources = [] } = useQuery({
queryKey: ['agent-sources', tenantId],
queryFn: () => apiFetch<DataSource[]>('/v1/data-sources', { tenantId }),
enabled: Boolean(tenantId), staleTime: 120_000,
});
const filtered = AGENTS.filter((a) => category === 'all' || a.category === category);
const active = AGENTS.filter((a) => a.status === 'active').length;
return (
<div className="max-w-5xl space-y-6 p-6">
<div>
<h1 className="font-display text-2xl font-semibold text-ink">Agent Center</h1>
<p className="text-sm text-ink-faint mt-1">
{active} agenți activi · {AGENTS.length - active} planificați · Rutați prin Hermes
</p>
</div>
{/* Architecture note */}
<div className="card p-4 bg-primary/5 border-primary/20 space-y-1">
<p className="text-xs font-semibold text-ink">Arhitectură CEO OS AI</p>
<p className="text-xs text-ink-faint">
Toate cererile AI sunt rutate prin <strong>Hermes</strong> (152.53.112.35) pentru real-time learning și customizare per client.
Hermes orchestrează OpenRouter Gemini / NVIDIA ca fallback.
</p>
<div className="flex flex-wrap gap-2 mt-2">
{['Hermes', 'OpenRouter', 'AnythingLLM', 'ClickHouse', 'n8n'].map((tool) => (
<span key={tool} className="rounded-full bg-muted px-2 py-0.5 text-[9px] text-ink-faint">{tool}</span>
))}
</div>
</div>
{/* Category filter */}
<div className="flex flex-wrap gap-1">
{CATEGORIES.map((c) => (
<button key={c} onClick={() => setCategory(c)}
className={`rounded-full border px-3 py-1 text-xs ${category === c ? 'bg-primary/10 border-primary text-ink' : 'bg-card border-border text-ink-faint'}`}>
{c === 'all' ? 'Toți' : c}
</button>
))}
</div>
<div className="grid gap-4 lg:grid-cols-3">
<div className="lg:col-span-2 grid gap-3 sm:grid-cols-2">
{filtered.map((agent) => (
<button key={agent.id} onClick={() => setSelected(agent)}
className={`card p-4 text-left hover:border-primary/40 transition-colors ${selected?.id === agent.id ? 'border-primary/50 bg-primary/5' : ''}`}>
<div className="flex items-center gap-2 mb-2">
<span className="text-xl">{agent.icon}</span>
<span className="text-sm font-medium text-ink">{agent.name}</span>
<span className={`ml-auto rounded-full px-1.5 py-0.5 text-[8px] font-bold ${agent.status === 'active' ? 'bg-signal-ok/10 text-signal-ok' : 'bg-muted text-ink-faint'}`}>
{agent.status}
</span>
</div>
<p className="text-[10px] text-ink-faint line-clamp-2">{agent.description}</p>
<div className="flex flex-wrap gap-1 mt-2">
{agent.tools.slice(0, 3).map((t) => (
<span key={t} className="rounded-full bg-muted px-1.5 py-0.5 text-[8px] text-ink-faint">{t}</span>
))}
</div>
</button>
))}
</div>
<div>
{selected ? (
<div className="card p-5 space-y-4 sticky top-4">
<div className="flex items-center gap-2">
<span className="text-2xl">{selected.icon}</span>
<div>
<p className="text-sm font-semibold text-ink">{selected.name}</p>
<p className="text-[10px] text-ink-faint">{selected.category}</p>
</div>
</div>
<p className="text-xs text-ink-faint">{selected.description}</p>
<div className="space-y-1">
<p className="text-[10px] font-semibold text-ink-faint">Instrumente</p>
{selected.tools.map((t) => (
<div key={t} className="flex items-center gap-2">
<span className={`w-1.5 h-1.5 rounded-full ${dataSources.some((ds) => ds.name.toLowerCase().includes(t.toLowerCase())) ? 'bg-signal-ok' : 'bg-border'}`} />
<span className="text-xs text-ink">{t}</span>
</div>
))}
</div>
{selected.endpoint && (
<div className="text-[10px] text-ink-faint border-t border-border/50 pt-3">
<p className="font-semibold mb-0.5">Endpoint activ</p>
<code className="bg-muted px-1 py-0.5 rounded">{selected.endpoint}</code>
</div>
)}
{selected.status === 'active' ? (
<Link href={selected.endpoint ? `/dashboard/ai/approvals` : '#'}
className="block w-full rounded-lg bg-primary px-4 py-2 text-center text-sm font-medium text-white hover:bg-primary/90">
Deschide
</Link>
) : (
<div className="rounded-lg bg-muted px-4 py-2 text-center text-xs text-ink-faint">
În planificare A4
</div>
)}
</div>
) : (
<div className="card p-6 text-center space-y-2">
<p className="text-2xl">🤖</p>
<p className="text-xs text-ink-faint">Selectează un agent.</p>
</div>
)}
</div>
</div>
</div>
);
}