import { html } from 'lit'; import { unsafeHTML } from 'lit/directives/unsafe-html.js'; import { LightElement, renderMarkdown } from '../lib/base.js'; import { t } from '../lib/i18n.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(); this.__onLocaleChanged = () => { // The user-facing agent name/description are localized server-side, so a // language switch must refetch — a bare re-render would keep the strings // fetched under the previous locale. if (this._open) { if (this._detail) this._openDetail(this._detail.meta); else this._loadList(); } this.requestUpdate(); }; window.addEventListener('locale-changed', this.__onLocaleChanged); 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; }); } disconnectedCallback() { window.removeEventListener('locale-changed', this.__onLocaleChanged); super.disconnectedCallback(); } 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 ──────────────────────────────────────────────────────── _strengthLabel(strength) { return { very_high: t('agents.strength.very_high'), high: t('agents.strength.high'), average: t('agents.strength.average'), low: t('agents.strength.low'), very_low: t('agents.strength.very_low') }[strength] ?? strength; } _strengthDot(strength, size = '0.62rem') { if (!strength) return html`${'—'}`; return html` `; } // ── List view ───────────────────────────────────────────────────────────── _renderCard(agent) { return html`
${agent.friendly_description ?? agent.description}
${t('agents.empty')}
`; 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(t('agents.section.chat'), chat)} ${this._renderSection(t('agents.section.task'), task)} ${this._renderSection(t('agents.section.system'), system)} `; } // ── Detail view ─────────────────────────────────────────────────────────── _renderModelRow(m, i) { const isFirst = i === 0; return html`${meta.friendly_description ?? meta.description}
| ${t('agents.detail.id')} | ${meta.id} |
| ${t('agents.detail.strength')} | ${this._strengthDot(meta.strength)} ${this._strengthLabel(meta.strength)} |
| ${t('agents.detail.pinned_model')} | ${meta.client} |
| ${t('agents.detail.memory_files')} | ${meta.inject_memory.map(f => html`${f} |
${t('agents.detail.model_order_desc')}
${models.length === 0 ? html`${t('agents.detail.no_models')}
` : html`| ${t('agents.table.rank')} | ${t('agents.table.strength')} | ${t('agents.table.name')} | ${t('agents.table.model_id')} |
|---|