import { html, nothing } from 'lit'; import { LightElement } from '../lib/base.js'; import { t } from '../lib/i18n.js'; const PAGE_ID = 'system-agents'; const PER_PAGE = 20; function formatDate(iso) { if (!iso) return '—'; return new Date(iso).toLocaleString(undefined, { day: '2-digit', month: '2-digit', year: '2-digit', hour: '2-digit', minute: '2-digit', }); } function formatDuration(ms) { if (ms == null) return '—'; if (ms < 1000) return `${ms} ms`; const s = ms / 1000; if (s < 60) return `${s.toFixed(1)} s`; const m = Math.floor(s / 60); return `${m}m ${Math.round(s % 60)}s`; } const STATUS_ICON = { running: 'bi-arrow-repeat', completed: 'bi-check-circle', failed: 'bi-exclamation-circle', cancelled: 'bi-slash-circle', }; export class SystemAgentsPage extends LightElement { static properties = { _open: { state: true }, _items: { state: true }, _total: { state: true }, _page: { state: true }, _loading: { state: true }, _error: { state: true }, }; constructor() { super(); this._open = false; this._items = []; this._total = 0; this._page = 1; this._loading = false; this._error = null; } connectedCallback() { super.connectedCallback(); this.__onLocaleChanged = () => this.requestUpdate(); window.addEventListener('locale-changed', this.__onLocaleChanged); window.addEventListener('llm-page-change', (e) => { this._open = e.detail.page === PAGE_ID; this.style.display = this._open ? 'flex' : 'none'; if (this._open) this._fetch(this._page); }); } disconnectedCallback() { window.removeEventListener('locale-changed', this.__onLocaleChanged); super.disconnectedCallback(); } async _fetch(page) { this._loading = true; this._error = null; try { const params = new URLSearchParams({ page, per_page: PER_PAGE }); const res = await fetch(`/api/system-agents/runs?${params}`); if (!res.ok) throw new Error(`HTTP ${res.status}`); const data = await res.json(); this._items = data.items; this._total = data.total; this._page = data.page; } catch (e) { this._error = e.message; } finally { this._loading = false; } } _openSession(id) { if (id != null) window.location.hash = `session/${id}`; } get _totalPages() { return Math.max(1, Math.ceil(this._total / PER_PAGE)); } /// The agent's own counters. Rendered generically so a second system agent /// needs no change here: unknown keys fall back to the raw key name. _renderStats(stats) { if (!stats || typeof stats !== 'object') return '—'; const parts = Object.entries(stats) .filter(([, v]) => v != null) .map(([k, v]) => { const label = t(`system_agents.stat.${k}`); return `${v} ${label.startsWith('system_agents.') ? k.replace(/_/g, ' ') : label}`; }); return parts.length ? parts.join(' · ') : '—'; } _renderTable() { if (this._loading) return html`
| ${t('system_agents.table.agent')} | ${t('system_agents.table.started')} | ${t('system_agents.table.status')} | ${t('system_agents.table.duration')} | ${t('system_agents.table.result')} |
|---|---|---|---|---|
| ${r.agent_id} | ${formatDate(r.started_at)} | ${t(`system_agents.status.${r.status}`)} | ${formatDuration(r.duration_ms)} | ${r.error ? html`${r.error}` : this._renderStats(r.stats)} |
${t('system_agents.subtitle')}
${this._renderTable()} ${this._renderPagination()}