import { useState, useEffect, useRef } from 'react'; import type { Agent } from '../types'; import { t } from '../i18n'; const MODEL_BADGE: Record = { haiku: 'badge-green', sonnet: 'badge-blue', opus: 'badge-purple', }; function modelBadgeClass(model: string): string { const key = model.toLowerCase(); if (key.includes('haiku')) return 'badge-green'; if (key.includes('opus')) return 'badge-purple'; if (key.includes('sonnet')) return 'badge-blue'; return MODEL_BADGE[key] ?? 'badge-gray'; } interface AgentCardProps { agent: Agent; onDelete: (id: string) => void; } function AgentCard({ agent, onDelete }: AgentCardProps) { const [expanded, setExpanded] = useState(false); const [confirmDelete, setConfirmDelete] = useState(false); const handleDelete = (e: React.MouseEvent) => { e.stopPropagation(); if (confirmDelete) { onDelete(agent.id); } else { setConfirmDelete(true); } }; const handleCancelDelete = (e: React.MouseEvent) => { e.stopPropagation(); setConfirmDelete(false); }; return (
setExpanded((v) => !v)} >

{agent.name}

{agent.model}

{agent.description}

e.stopPropagation()}> {confirmDelete ? ( <> ) : ( )} {expanded ? '▲' : '▼'}
{expanded && (
{agent.description && (

{agent.description}

)} {agent.tools && agent.tools.length > 0 && (

{t('agents.tools')}:

{agent.tools.map((tool) => ( {tool} ))}
)} {!agent.description && (!agent.tools || agent.tools.length === 0) && (

No additional details available.

)}
)}
); } export default function AgentsPage() { const [agents, setAgents] = useState([]); const [loading, setLoading] = useState(true); const [showCreateModal, setShowCreateModal] = useState(false); const [createPrompt, setCreatePrompt] = useState(''); const [createStatus, setCreateStatus] = useState(''); const [importStatus, setImportStatus] = useState(''); const importRef = useRef(null); const refreshAgents = () => { fetch('/api/agents') .then((r) => r.json()) .then((data: Agent[]) => { if (Array.isArray(data)) setAgents(data); }) .catch(() => {}); }; useEffect(() => { fetch('/api/agents') .then((r) => r.json()) .then((data: Agent[]) => { if (Array.isArray(data)) setAgents(data); }) .catch(() => {}) .finally(() => setLoading(false)); }, []); const handleExport = async () => { try { const res = await fetch('/api/agents/export'); const data = await res.json(); const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `claude-agent-agents-${new Date().toISOString().slice(0, 10)}.json`; a.click(); URL.revokeObjectURL(url); } catch (err) { console.error('Export failed:', err); } }; const handleImportFile = async (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; try { const text = await file.text(); if (file.name.endsWith('.json')) { const bundle = JSON.parse(text); if (bundle.agents && Array.isArray(bundle.agents)) { const res = await fetch('/api/agents/import-bundle', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ agents: bundle.agents }), }); const result = await res.json(); setImportStatus(`Imported ${result.imported} agents`); } else { setImportStatus('Invalid bundle format (expected { agents: [...] })'); } } else if (file.name.endsWith('.md')) { const name = file.name.replace(/\.md$/i, '').toLowerCase(); const res = await fetch('/api/agents/import', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name, content: text }), }); const result = await res.json(); setImportStatus(`Imported agent: ${result.name}`); } refreshAgents(); setTimeout(() => setImportStatus(''), 3000); } catch (err) { setImportStatus(`Import failed: ${(err as Error).message}`); } e.target.value = ''; }; const handleCreate = () => { setShowCreateModal(true); setCreatePrompt(''); setCreateStatus(''); }; const handleCreateSubmit = async () => { if (!createPrompt.trim()) return; setCreateStatus('Creating agent via AI... (this uses a chat session)'); try { const sessionRes = await fetch('/api/sessions', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ title: `Create agent: ${createPrompt.slice(0, 50)}` }), }); const session = await sessionRes.json(); const wsUrl = `${window.location.protocol === 'https:' ? 'wss:' : 'ws:'}//${window.location.host}/ws`; const ws = new WebSocket(wsUrl); ws.onopen = () => { ws.send(JSON.stringify({ type: 'subscribe', sessionId: session.id })); setTimeout(() => { ws.send(JSON.stringify({ type: 'chat', sessionId: session.id, content: `/skill-creator agent: ${createPrompt}` })); }, 500); }; ws.onmessage = (event) => { const msg = JSON.parse(event.data); if (msg.type === 'result') { setCreateStatus(msg.success ? 'Agent created! Refreshing list...' : 'Creation failed.'); ws.close(); refreshAgents(); setTimeout(() => { setShowCreateModal(false); setCreateStatus(''); }, 2000); } else if (msg.type === 'assistant_message') { setCreateStatus(msg.content.slice(0, 200) + '...'); } }; } catch (err) { setCreateStatus(`Error: ${(err as Error).message}`); } }; const handleDelete = async (id: string) => { setAgents((prev) => prev.filter((a) => a.id !== id)); try { await fetch(`/api/agents/${id}`, { method: 'DELETE' }); } catch { // optimistic removal already applied } }; return (
{/* Header */}

{t('agents.title')}

{t('agents.subtitle')}

{/* Create modal */} {showCreateModal && (

{t('agents.createHint')}

setCreatePrompt(e.target.value)} placeholder="e.g. A research agent that searches the web and summarizes findings" className="input-base flex-1" onKeyDown={(e) => e.key === 'Enter' && handleCreateSubmit()} />
{createStatus &&

{createStatus}

}
)} {/* Import status */} {importStatus && (

{importStatus}

)} {/* Content */}
{loading ? (
Loading agents...
) : agents.length === 0 ? (
🤖

{t('agents.noAgents')}

) : (
{agents.map((agent) => ( ))}
)}
); }