// @ts-nocheck import { getAll, clear, exportJson, subscribe, formatTs, formatType } from '../utils/audit-log.js'; let _badgeCountEl = null; let _filter = 'all'; let _expanded = new Set(); let _renderFn = null; export function initBadge(sidebarEl) { _badgeCountEl = sidebarEl.querySelector('#audit-count'); sidebarEl.querySelector('#btn-open-audit').addEventListener('click', () => { showAuditDialog(); }); subscribe(() => updateBadge()); updateBadge(); } function updateBadge() { if (_badgeCountEl) { _badgeCountEl.textContent = getAll().length; } } export function showAuditDialog() { const overlay = document.createElement('div'); overlay.className = 'dialog-overlay'; overlay.innerHTML = ` `; document.body.appendChild(overlay); document.body.classList.add('modal-open'); const tbody = overlay.querySelector('#audit-tbody'); const statsEl = overlay.querySelector('#audit-stats'); const renderTable = () => { const all = getAll(); const filtered = _filter === 'all' ? all : all.filter(e => e.type === _filter || e.type.startsWith(_filter + ':')); const counts = { user: all.filter(e => e.type === 'user').length, assistant: all.filter(e => e.type === 'assistant').length, tool: all.filter(e => e.type.startsWith('tool')).length, system: all.filter(e => e.type === 'system').length, error: all.filter(e => e.type === 'error').length, }; statsEl.innerHTML = `共 ${all.length} 条 · 用户 ${counts.user} / 模型 ${counts.assistant} / 工具 ${counts.tool} / 系统 ${counts.system} / 错误 ${counts.error}${_filter !== 'all' ? ` · 当前筛选 ${filtered.length}` : ''}`; if (filtered.length === 0) { tbody.innerHTML = '
暂无日志
'; return; } tbody.innerHTML = ''; for (const entry of filtered) { const expanded = _expanded.has(entry.id); const hasDetails = !!entry.details; const row = document.createElement('div'); row.className = `audit-row audit-${entry.type.replace(':', '-')} ${expanded ? 'expanded' : ''}`; row.innerHTML = `
${formatTs(entry.ts)}
${formatType(entry.type)}
${escapeHtml(entry.summary || '(空)')}
${hasDetails ? `${expanded ? '▼' : '▶'}` : ''}
`; if (hasDetails) { row.addEventListener('click', () => { if (_expanded.has(entry.id)) _expanded.delete(entry.id); else _expanded.add(entry.id); renderTable(); }); } tbody.appendChild(row); if (expanded && hasDetails) { const detail = document.createElement('div'); detail.className = 'audit-detail'; const json = JSON.stringify(entry.details, null, 2); const display = json.replace(/\\n/g, '\n').replace(/\\t/g, ' '); detail.innerHTML = `
${escapeHtml(display)}
`; tbody.appendChild(detail); } } }; _renderFn = renderTable; renderTable(); overlay.querySelectorAll('.filter-btn').forEach(btn => { btn.addEventListener('click', () => { _filter = btn.dataset.filter; overlay.querySelectorAll('.filter-btn').forEach(b => b.classList.toggle('active', b === btn)); renderTable(); }); }); overlay.querySelector('#audit-clear').addEventListener('click', () => { if (!confirm('确定清空所有日志?此操作不可撤销。')) return; clear(); _expanded.clear(); }); overlay.querySelector('#audit-export').addEventListener('click', () => { exportJson(); }); const close = () => { const unsub = subscribe(() => { if (document.body.contains(overlay)) { renderTable(); } else { unsub(); } }); overlay.remove(); document.body.classList.remove('modal-open'); }; overlay.querySelector('.dialog-close').addEventListener('click', close); overlay.addEventListener('click', (e) => { if (e.target === overlay) close(); }); const escHandler = (e) => { if (e.key === 'Escape') { close(); document.removeEventListener('keydown', escHandler); } }; document.addEventListener('keydown', escHandler); const unsubOnLog = subscribe(() => { if (document.body.contains(overlay)) { renderTable(); } else { unsubOnLog(); } }); } function escapeHtml(s) { if (s == null) return ''; return String(s) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"'); }