import React, { useCallback, useEffect, useState } from 'react';
import { api } from './api.js';
import { copyToast } from './toast.js';
const STATUS_COLOR = { online: '#36f0b0', pending: '#f2b85f', lost: '#ff9f5a', down: '#ff7a8c' };
const TAG_EMOJI = { backend: 'π οΈ', model: 'π§ ', frontend: 'π¨', ui: 'ποΈ', web: 'π', data: 'π', devops: 'βοΈ', ml: 'π€', security: 'π', api: 'π', bot: 'π€', chat: 'π¬', test: 'π§ͺ', docs: 'π', design: 'βοΈ', ops: 'π¦', research: 'π¬' };
const classEmoji = (c) => { for (const t of (c.tags || [])) if (TAG_EMOJI[t]) return TAG_EMOJI[t]; return 'π‘οΈ'; };
function avatarStyle(name) { let h = 0; for (const ch of String(name)) h = (h * 31 + ch.charCodeAt(0)) >>> 0; const a = h % 360, b = (a + 70 + h % 90) % 360; return { background: `linear-gradient(135deg, hsl(${a} 68% 46%), hsl(${b} 64% 32%))` }; }
// ---- Browse Agents: all public classes, filter by tag --------------------
export function AgentBrowse({ auth, setRoute }) {
const [classes, setClasses] = useState([]);
const [tags, setTags] = useState({});
const [tag, setTag] = useState('');
const [q, setQ] = useState('');
const [open, setOpen] = useState(null);
const [err, setErr] = useState('');
const load = useCallback(async () => {
setErr('');
try {
const [c, s] = await Promise.all([api(`/agents/classes${tag ? `?tag=${encodeURIComponent(tag)}` : ''}`), api('/agents/stats')]);
setClasses((c.classes || []).filter((x) => !q || (x.name + x.tags.join() + x.description).toLowerCase().includes(q.toLowerCase())));
setTags(s.tags || {});
} catch (e) { setErr(e.message); }
}, [tag, q]);
useEffect(() => { load(); }, [load]);
return (
Browse Agents
setQ(e.target.value)} />
{Object.entries(tags).sort((a, b) => b[1] - a[1]).map(([t, n]) => (
))}
{err &&
{err}
}
{classes.map((c) => (
))}
{!classes.length && (
No agents here yet β be the first.
- Publish a class in β name it, tag it (e.g.
backend, model).
- Run an instance β your broker connects and it goes online.
- Use it β embed it as a chatbox or drive it through a web terminal.
)}
{open &&
{ setOpen(null); load(); }} />}
);
}
function ClassModal({ id, auth, onClose }) {
const [data, setData] = useState(null);
const [sub, setSub] = useState(null);
const [use, setUse] = useState(null); // instance to use
const [err, setErr] = useState('');
const load = useCallback(() => api(`/agents/classes/${id}`).then(setData).catch((e) => setErr(e.message)), [id]);
useEffect(() => { load(); }, [load]);
const subscribe = async () => { try { const r = await api(`/agents/classes/${id}/subscribe`, { method: 'POST' }); setSub(r.token); } catch (e) { setErr(e.message); } };
if (!data) return null;
const c = data.class;
return (
e.stopPropagation()}>
{classEmoji(c)}
{c.name}
{c.kind} by {c.owner}
{c.description}
{c.tags.map((t) => {t})}
{err &&
{err}
}
{auth.user ? : Log in to subscribe.}
{sub && copyToast(sub)}>{sub} β§}
Instances
{data.instances.map((i) => (
#{i.id} {i.name}
{i.status}{i.broker ? ` Β· ${i.broker}` : ''} Β· {i.visibility}
))}
{!data.instances.length &&
No instances published. The owner can publish one in My Booth.
}
{use &&
setUse(null)} />}
);
}
// ---- Use an instance: chatbox + web-pty ----------------------------------
export function UsePanel({ instance, onClose }) {
const [mode, setMode] = useState('chatbox');
const [sess, setSess] = useState(null);
const [accesskey, setAccesskey] = useState(instance.accesskey || '');
const [err, setErr] = useState('');
const start = useCallback(async () => {
setErr('');
try {
const r = await api(`/agents/instances/${instance.id}/sessions`, { method: 'POST', body: { connector: mode, accesskey: accesskey || undefined } });
setSess(r);
} catch (e) { setErr(e.message); }
}, [instance.id, mode, accesskey]);
useEffect(() => { if (instance.accesskey) start(); }, []); // owner: auto-start
return (
{!sess && (
{!instance.accesskey && instance.visibility !== 'public' && (
setAccesskey(e.target.value)} />
)}
{err &&
{err}
}
)}
{sess && (
<>
{mode === 'chatbox' && (
Embed this agent on any site
{``}
)}
>
)}
);
}
// ---- My Booth: publish classes + instances, manage ------------------------
export function KeysCard() {
const [keys, setKeys] = useState([]);
const [name, setName] = useState('');
const [created, setCreated] = useState(null);
const [err, setErr] = useState('');
const load = useCallback(() => api('/keys').then((d) => setKeys(d.keys || [])).catch((e) => setErr(e.message)), []);
useEffect(() => { load(); }, [load]);
const mint = async (e) => { e.preventDefault(); setErr(''); try { const r = await api('/keys', { method: 'POST', body: { name } }); setCreated(r); setName(''); load(); } catch (e2) { setErr(e2.message); } };
const del = async (id) => { try { await api(`/keys/${id}`, { method: 'DELETE' }); load(); } catch (e2) { setErr(e2.message); } };
return (
π My agent keys
Mint a key for your own agent to read/write amerc docs, netdisk and the agent platform. Send it as Authorization: Bearer <key>.
{created &&
β
minted β copy now, shown once:
copyToast(created.key)}>{created.key} β§}
{err &&
{err}
}
{keys.map((k) => (
{k.prefix}β¦ {k.name}
{k.last_used ? `used ${new Date(k.last_used).toLocaleDateString()}` : 'never used'}
))}
{!keys.length &&
No keys yet β mint one above.
}
);
}
export function BoothDashboard({ auth, setRoute }) {
const [classes, setClasses] = useState([]);
const [insts, setInsts] = useState([]);
const [err, setErr] = useState('');
const [form, setForm] = useState({ name: '', tags: '', description: '', kind: 'codex', command: 'codex --yolo', visibility: 'public' });
const [newInst, setNewInst] = useState(null);
const load = useCallback(async () => {
setErr('');
try {
const c = await api('/agents/classes?mine=1');
setClasses(c.classes || []);
const all = await Promise.all((c.classes || []).map((x) => api(`/agents/instances?classId=${x.id}`).then((r) => r.instances).catch(() => [])));
setInsts(all.flat());
} catch (e) { setErr(e.message); }
}, []);
useEffect(() => { if (auth.user) load(); }, [auth.user, load]);
if (!auth.ready) return ;
if (!auth.user) return (
My Booth
Log in to publish and run agents.
);
const publishClass = async (e) => {
e.preventDefault();
try { await api('/agents/classes', { method: 'POST', body: { ...form, tags: form.tags.split(',').map((t) => t.trim()).filter(Boolean) } }); setForm({ ...form, name: '', tags: '', description: '' }); load(); }
catch (e2) { setErr(e2.message); }
};
const publishInstance = async (classId) => {
try { const r = await api('/agents/instances', { method: 'POST', body: { classId, visibility: 'public' } }); setNewInst(r); load(); }
catch (e2) { setErr(e2.message); }
};
const shutdown = async (i) => { try { await api(`/agents/instances/${i.id}`, { method: 'DELETE', body: { accesskey: i.accesskey } }); load(); } catch (e2) { setErr(e2.message); } };
return (
My Booth
{auth.user.handle}
{err &&
{err}
}
My classes
{classes.map((c) => (
{classEmoji(c)}
{c.name}{c.visibility}
{c.onlineCount}/{c.instanceCount}
{c.tags.map((t) => {t})}
))}
{!classes.length &&
No classes yet β publish one on the left.
}
My instances
{insts.map((i) => (
#{i.id} {i.status}
{i.accesskey && copyToast(i.accesskey)}>{i.accesskey.slice(0, 14)}β¦ β§}
))}
{!insts.length &&
No instances. Publish one from a class above.
}
{newInst && (
setNewInst(null)}>
e.stopPropagation()}>
Instance #{newInst.id} created
Run your broker against this instance, then it shows online. The accesskey is the relay token.
)}
);
}