(function () { const STORAGE_LAST_SEEN_KEY = 'cyberstrike-notification-last-seen-at'; const POLL_INTERVAL_ACTIVE_MS = 15000; const POLL_INTERVAL_HIDDEN_MS = 60000; const MAX_RENDER_ITEMS = 20; const state = { inFlight: false, timerId: null, dropdownOpen: false, lastSeenAt: readLastSeenAt(), items: [], unreadCount: 0, }; function readLastSeenAt() { try { const raw = localStorage.getItem(STORAGE_LAST_SEEN_KEY); const n = Number(raw); if (Number.isFinite(n) && n > 0) return n; } catch (e) { console.warn('读取通知已读时间失败:', e); } return 0; } function persistLastSeenAt(ts) { try { localStorage.setItem(STORAGE_LAST_SEEN_KEY, String(ts)); } catch (e) { console.warn('保存通知已读时间失败:', e); } } function getTimeMs(value) { if (!value) return 0; const d = new Date(value); const ms = d.getTime(); return Number.isFinite(ms) ? ms : 0; } function getLocale() { if (typeof window !== 'undefined') { if (typeof window.__locale === 'string' && window.__locale) { return window.__locale; } if (typeof window.currentLang === 'string' && window.currentLang) { return window.currentLang; } } return 'zh-CN'; } function formatTime(value) { const ms = getTimeMs(value); if (!ms) return '-'; return new Date(ms).toLocaleString(getLocale()); } function htmlEscape(value) { if (typeof window.escapeHtml === 'function') { return window.escapeHtml(value == null ? '' : String(value)); } const div = document.createElement('div'); div.textContent = value == null ? '' : String(value); return div.innerHTML; } function t(key, fallback, params) { if (typeof window !== 'undefined' && typeof window.t === 'function') { try { const translated = window.t(key, params || {}); if (translated && translated !== key) return translated; } catch (_ignored) {} } return fallback; } async function apiJson(url, options) { if (typeof window.apiFetch !== 'function') return null; const res = await window.apiFetch(url, options || {}); if (!res.ok) return null; return res.json(); } async function fetchNotificationSummary() { const url = '/api/notifications/summary?since=' + encodeURIComponent(String(state.lastSeenAt || 0)) + '&limit=80&lang=' + encodeURIComponent(getLocale()); try { const summary = await apiJson(url); if (summary && typeof summary === 'object') { return summary; } } catch (_ignored) {} return null; } function renderBadge(count) { const badge = document.getElementById('notification-badge'); const btn = document.getElementById('notification-bell-btn'); if (!badge || !btn) return; if (count <= 0) { badge.style.display = 'none'; btn.classList.remove('has-alert'); return; } const text = count > 99 ? '99+' : String(count); badge.innerHTML = '' + htmlEscape(text) + ''; badge.style.display = 'inline-block'; btn.classList.add('has-alert'); } function countP0(items) { return (Array.isArray(items) ? items : []).reduce((acc, item) => { if (!item || item.level !== 'p0') return acc; if (typeof item.count === 'number' && item.count > 0) return acc + item.count; return acc + 1; }, 0); } function markableItems(items) { return (Array.isArray(items) ? items : []).filter(item => item && item.actionable !== true && item.id); } function hasAction(item) { if (!item || !item.type) return false; if (item.type === 'vulnerability_created' && item.vulnerabilityId) return true; if ((item.type === 'task_completed' || item.type === 'long_running_tasks') && item.conversationId) return true; if (item.type === 'task_failed' && item.executionId) return true; if (item.type === 'hitl_pending') return true; return false; } function openNotificationTarget(item) { if (!item || !item.type) return; if (item.type === 'vulnerability_created' && item.vulnerabilityId) { window.location.hash = 'vulnerabilities?id=' + encodeURIComponent(item.vulnerabilityId); return; } if ((item.type === 'task_completed' || item.type === 'long_running_tasks') && item.conversationId) { window.location.hash = 'chat?conversation=' + encodeURIComponent(item.conversationId); return; } if (item.type === 'task_failed' && item.executionId) { window.location.hash = 'mcp-monitor'; setTimeout(function () { if (typeof showMCPDetail === 'function') { showMCPDetail(item.executionId); } }, 450); return; } if (item.type === 'hitl_pending') { window.location.hash = 'hitl'; } } async function markItemsRead(eventIds) { if (!Array.isArray(eventIds) || !eventIds.length) return true; const payload = { eventIds: eventIds }; try { const result = await apiJson('/api/notifications/read', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }); return !!result; } catch (_ignored) { return false; } } function renderNotificationList(items) { const list = document.getElementById('notification-list'); if (!list) return; const renderItems = Array.isArray(items) ? items.slice(0, MAX_RENDER_ITEMS) : []; if (!renderItems.length) { list.innerHTML = '