// Inbox: pending changes grouped by agent. Approve/reject calls the API // then re-fetches. On approve, navigate to the resulting entity_id if // the response carries one. import { api } from '../api.js'; import { el, mount, clear } from '../dom.js'; import { navigate } from '../router.js'; import { emit } from '../state.js'; const ROUTE_BY_TYPE = { page: id => '#/page/' + id, project: id => '#/project/' + id, task: () => '#/', ref: id => '#/ref/' + id, resource: id => '#/resource/' + id, source_doc: () => '#/', space: id => '#/space/' + id }; function diffBlock(diff) { if (!diff) return null; return el('pre', {}, JSON.stringify(diff, null, 2)); } function pendingRow(row, onActed) { const meta = el('p', { style: { margin: '0 0 6px' } }, el('span', { class: 'status idle' }, row.entity_type), ' ', el('span', { class: 'muted' }, row.action), row.reason ? el('span', { class: 'muted' }, ' — ' + row.reason) : null ); async function approve() { try { const res = await api.post(`/api/pending-changes/${row.id}/approve`); onActed(); const route = ROUTE_BY_TYPE[row.entity_type]; if (res.entity_id && route) navigate(route(res.entity_id)); } catch (e) { alert('approve failed: ' + e.message); } } async function reject() { try { await api.post(`/api/pending-changes/${row.id}/reject`); onActed(); } catch (e) { alert('reject failed: ' + e.message); } } return el('div', { class: 'card', style: { marginBottom: '10px' } }, meta, el('details', {}, el('summary', { class: 'muted', style: { cursor: 'pointer', fontSize: '11px' } }, 'payload'), el('pre', {}, JSON.stringify(row.payload, null, 2)) ), diffBlock(null), el('div', { style: { display: 'flex', gap: '8px', marginTop: '8px' } }, el('button', { class: 'primary', onclick: approve }, 'Approve'), el('button', { class: 'ghost', onclick: reject }, 'Reject') ) ); } async function refresh(container) { let rows = []; try { rows = await api.get('/api/pending-changes?limit=200'); } catch (e) { clear(container); container.appendChild(el('p', { class: 'muted' }, 'Could not load: ' + e.message)); return; } emit('pending-count', rows.length); clear(container); if (!rows.length) { container.appendChild(el('p', { class: 'muted' }, 'Inbox is empty.')); return; } const byAgent = new Map(); for (const r of rows) { if (!byAgent.has(r.agent_id)) byAgent.set(r.agent_id, []); byAgent.get(r.agent_id).push(r); } for (const [agentId, items] of byAgent) { container.appendChild(el('div', { class: 'sb-title' }, 'Agent ' + agentId.slice(0, 8))); for (const row of items) container.appendChild(pendingRow(row, () => refresh(container))); } } export async function render(main) { const wrap = el('div'); mount(main, el('h1', { class: 'view-h1' }, 'Inbox'), el('p', { class: 'view-sub' }, 'Pending agent suggestions awaiting your call.'), wrap ); refresh(wrap); }