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`
this._openDetail(agent)}>
${agent.icon ? html` ${agent.name} ` : ''}
${agent.name} ${agent.id}

${agent.friendly_description ?? agent.description}

${agent.strength ? html` ${this._strengthDot(agent.strength)} ${this._strengthLabel(agent.strength)} ` : ''} ${agent.client ? html` ${agent.client} ` : ''}
`; } _renderSection(title, agents) { if (agents.length === 0) return ''; return html`

${title}

${agents.map(a => this._renderCard(a))}
`; } _renderList() { if (this._loading) return html`
${t('agents.loading')}
`; if (this._error) return html`
${this._error}
`; if (this._agents.length === 0) return html`

${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` ${i + 1} ${this._strengthDot(m.strength)} ${m.name} ${m.is_default ? html`${t('agents.detail.default')}` : ''} ${m.model_id} `; } _renderDetail() { if (this._loading && !this._detail) return html`
${t('agents.loading')}
`; if (!this._detail) return ''; const { meta, prompt, models } = this._detail; return html`
${meta.icon ? html` ${meta.name} ` : ''}

${meta.name}

${meta.friendly_description ?? meta.description}

${this._error ? html`
${this._error}
` : ''}

${t('agents.detail.meta')}

${meta.strength ? html` ` : ''} ${meta.client ? html` ` : ''} ${meta.inject_memory?.length ? html` ` : ''}
${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')}

${t('agents.detail.model_order_desc')}

${models.length === 0 ? html`

${t('agents.detail.no_models')}

` : html`
${models.map((m, i) => this._renderModelRow(m, i))}
${t('agents.table.rank')} ${t('agents.table.strength')} ${t('agents.table.name')} ${t('agents.table.model_id')}
` }

${t('agents.detail.prompt')}

${unsafeHTML(renderMarkdown(prompt))}
`; } // ── Root render ─────────────────────────────────────────────────────────── render() { return html`
${this._detail ? this._renderDetail() : html`

${unsafeHTML(t('agents.banner.title'))}

${unsafeHTML(t('agents.banner.text'))}

${this._renderList()}
` }
`; } }