import { html } from 'lit'; import { unsafeHTML } from 'lit/directives/unsafe-html.js'; import { LightElement, renderMarkdown } from '../lib/base.js'; const STRENGTH_COLORS = { very_high: '#ef4444', high: '#f97316', average: '#eab308', low: '#84cc16', very_low: '#22c55e', }; const STRENGTH_LABELS = { very_high: 'Very High', high: 'High', average: 'Average', low: 'Low', very_low: 'Very Low', }; export class AgentsPage extends LightElement { static properties = { _open: { state: true }, _agents: { state: true }, _detail: { state: true }, // null | { meta, prompt, models } _loading: { state: true }, _error: { state: true }, }; constructor() { super(); this._open = false; this._agents = []; this._detail = null; this._loading = false; this._error = null; } connectedCallback() { super.connectedCallback(); window.addEventListener('llm-page-change', (e) => { this._open = e.detail.page === 'agents'; this.style.display = this._open ? 'flex' : 'none'; if (this._open && this._agents.length === 0) this._loadList(); if (!this._open) this._detail = null; }); } async _loadList() { this._loading = true; this._error = null; try { const res = await fetch('/api/agents'); if (!res.ok) throw new Error(`HTTP ${res.status}`); this._agents = await res.json(); } catch (e) { this._error = e.message; } finally { this._loading = false; } } async _openDetail(agent) { this._loading = true; this._error = null; try { const res = await fetch(`/api/agents/${agent.id}`); if (!res.ok) throw new Error(`HTTP ${res.status}`); this._detail = await res.json(); } catch (e) { this._error = e.message; } finally { this._loading = false; } } _back() { this._detail = null; this._error = null; } // ── Render helpers ──────────────────────────────────────────────────────── _strengthDot(strength, size = '0.62rem') { if (!strength) return html`—`; return html` `; } _scopePill(scope) { return html`${scope}`; } // ── List view ───────────────────────────────────────────────────────────── _renderCard(agent) { return html`
${agent.friendly_description ?? agent.description}
No agents found.
`; // Group by role: chat entry-points, dispatchable task executors, and // runtime-internal system agents (e.g. tic). const chat = this._agents.filter(a => a.type === 'chat'); const task = this._agents.filter(a => a.type === 'task'); const system = this._agents.filter(a => a.type === 'system'); return html` ${this._renderSection('Chat', chat)} ${this._renderSection('Task Executors', task)} ${this._renderSection('System', system)} `; } // ── Detail view ─────────────────────────────────────────────────────────── _renderModelRow(m, i) { const isFirst = i === 0; return html`${meta.friendly_description ?? meta.description}
| ID | ${meta.id} |
| Strength | ${this._strengthDot(meta.strength)} ${STRENGTH_LABELS[meta.strength] ?? meta.strength} |
| Scope | ${this._scopePill(meta.scope)} |
| Pinned model | ${meta.client} |
| Memory files | ${meta.inject_memory.map(f => html`${f} |
Models sorted by how well they match this agent's requirements. The system uses the first available model from the top.
${models.length === 0 ? html`No models configured.
` : html`| # | Strength | Name | Model ID | Scope |
|---|