// Generated, do not edit. sha256:a36be83c24f4e2c2e73be5d6705dd33c4759901705776f21398caba72366f10a import { currentNotifications } from '../data/notifications.js'; import { countAiToolCallsSince } from '../data/ai-audit.js'; import { getPref } from '../data/preferences.js'; import { listProviders, getProvider } from '../data/providers.js'; import { listPriceSource } from '../data/price-source.js'; import { listPriceGaps } from '../data/price-history.js'; import { apiCallsByProvider } from '../data/api-metrics.js'; import { rateSourceFor } from '../price/providers.js'; import { t } from '../i18n/index.js'; import { CONFIG, TARGETS, NOTIFICATION_TARGETS, SOURCE_ENDPOINTS, targetHash, targetTab } from '../../shared/config.js'; import { registerAttention } from './attention.js'; import { appSetting, baseCurrency } from '../app-settings.js'; import { licenseChangedSinceSeen } from '../license.js'; import { placeAvailable } from '../routes.js'; async function integrationsNeedingKey() { const rijen = await listProviders(); const configs = await Promise.all(rijen.map(r => getProvider(r.key))); const providers = configs .filter(p => p && p.isActive && p.authenticationRequired && p.authenticationKind !== 'none'); const out = []; for (const p of providers) { let complete = false; try { const st = await (await fetch(`/api/keys/status?provider=${encodeURIComponent(p.key)}&authenticationKind=${encodeURIComponent(p.authenticationKind)}`)).json(); complete = !!(st && st.complete); } catch { continue; } if (!complete) { out.push({ id: `integration:${p.key}`, severity: 'problem', route: TARGETS.PROVIDER_KEYS.route, tab: targetTab(TARGETS.PROVIDER_KEYS), hash: targetHash(TARGETS.PROVIDER_KEYS), label: p.name + t('attention.integrationNoKeySuffix'), }); } } return out; } async function sourcesNearLimit() { const maand = `${new Date().toISOString().slice(0, 7)}-01T00:00`; const calls = new Map((await apiCallsByProvider(maand)).map(r => [r.providerId, r.calls])); const out = []; for (const r of await listProviders()) { if (!r.isActive || !calls.get(r.id)) continue; const p = await getProvider(r.key); if (!p || !p.callsPerMonth) continue; const n = calls.get(r.id); const deel = n / p.callsPerMonth; if (deel < CONFIG.API_USAGE.WARN_SHARE) continue; out.push({ id: `usage:${p.key}`, severity: deel >= 1 ? 'problem' : 'attention', route: TARGETS.PROVIDER_KEYS.route, tab: targetTab(TARGETS.PROVIDER_KEYS), hash: targetHash(TARGETS.PROVIDER_KEYS), label: t(deel >= 1 ? 'attention.usageOver' : 'attention.usageNear', { naam: p.name, calls: n, limiet: p.callsPerMonth }), }); } return out; } async function sourcesWithoutRate() { const configs = new Map(); for (const r of await listProviders()) configs.set(r.id, await getProvider(r.key)); const wanted = new Map(); for (const cel of await listPriceSource()) { const p = configs.get(cel.providerId); if (cel.rank == null || !p || !p.isActive) continue; for (const e of (p ? p.endpoints : []).filter(x => SOURCE_ENDPOINTS[cel.capability].includes(x.capability))) { const currency = e.quoteCurrency; if (!currency || currency === baseCurrency()) continue; wanted.set(`${p.key}|${currency}`, { provider: p, currency }); } } const out = []; for (const { provider, currency } of wanted.values()) { if (await rateSourceFor(currency)) continue; out.push({ id: `fx:${provider.key}:${currency}`, severity: 'problem', route: TARGETS.SOURCE_PROPOSALS.route, tab: targetTab(TARGETS.SOURCE_PROPOSALS), hash: targetHash(TARGETS.SOURCE_PROPOSALS), label: `${provider.name}: ${t('integrations.noRateSource', { valuta: currency })}`, }); } return out; } const GAPS_TTL_MS = 10 * 60 * 1000; let gapsCache = { at: 0, total: 0 }; if (typeof document !== 'undefined') { document.addEventListener('price-gaps-changed', () => { gapsCache = { at: 0, total: 0 }; }); } async function priceGaps() { if (!placeAvailable(TARGETS.PRICE_HISTORY)) return []; if (Date.now() - gapsCache.at > GAPS_TTL_MS) { try { gapsCache = { at: Date.now(), total: (await listPriceGaps()).total }; } catch { return []; } } if (!gapsCache.total) return []; return [{ id: 'price:gaps', severity: 'attention', route: TARGETS.PRICE_HISTORY.route, tab: targetTab(TARGETS.PRICE_HISTORY), hash: targetHash(TARGETS.PRICE_HISTORY), label: t('attention.priceGaps', { n: gapsCache.total }), }]; } const ACTIE_LABEL = { reconcile: n => t('attention.actionsReconcilePrefix') + n + t('attention.actionsReconcileSuffix'), interest: n => t('attention.actionsInterestPrefix') + n, valuation: n => t('attention.actionsValuationPrefix') + n, ai_proposal: n => t('attention.actionsAiPrefix') + n, reservation: n => t('attention.actionsReservationPrefix') + n, }; async function openActions() { const meldingen = await currentNotifications(); return meldingen.filter(n => ACTIE_LABEL[n.kind] && n.count > 0).map(n => ({ id: `actions:${n.kind}`, severity: 'attention', route: NOTIFICATION_TARGETS[n.kind].route, tab: targetTab(NOTIFICATION_TARGETS[n.kind]), hash: targetHash(NOTIFICATION_TARGETS[n.kind]), count: n.count, label: ACTIE_LABEL[n.kind](n.count), })); } function backupReminder() { const last = appSetting('last_backup_at'); const dayMs = 86400000; if (!last) { return [{ id: 'backup:never', route: null, severity: 'attention', hash: targetHash(TARGETS.PORTFOLIOS), label: t('attention.backupNever') }]; } const days = Math.floor((Date.now() - new Date(last).getTime()) / dayMs); if (Number.isFinite(days) && days >= CONFIG.BACKUP.REMIND_AFTER_DAYS) { return [{ id: 'backup:stale', route: null, severity: 'attention', hash: targetHash(TARGETS.PORTFOLIOS), label: t('attention.backupStalePrefix') + days + t('attention.backupStaleSuffix') }]; } return []; } async function aiActivity() { const gezien = await getPref('ai', 'seen_call_id', 0); const { count } = await countAiToolCallsSince(Number(gezien) || 0); if (count <= 0) return []; return [{ id: 'ai:activity', severity: 'attention', route: TARGETS.AI_ACTIVITY.route, tab: targetTab(TARGETS.AI_ACTIVITY), hash: targetHash(TARGETS.AI_ACTIVITY), count, label: t('attention.aiActivityPrefix') + count + t('attention.aiActivitySuffix'), }]; } async function licenseChanged() { if (!await licenseChangedSinceSeen()) return []; return [{ id: 'license:changed', severity: 'attention', route: TARGETS.LICENSE.route, tab: targetTab(TARGETS.LICENSE), hash: targetHash(TARGETS.LICENSE), label: t('attention.licenseChanged'), }]; } let registered = false; function healthCheckReminder() { const last = appSetting('last_health_check_at'); const days = last ? Math.floor((Date.now() - new Date(last).getTime()) / 86400000) : null; if (!last) { return [{ id: 'health:never', route: null, severity: 'attention', hash: targetHash(TARGETS.HEALTH), label: t('attention.healthNever') }]; } if (Number.isFinite(days) && days >= CONFIG.HEALTH_CHECK.REMIND_AFTER_DAYS) { return [{ id: 'health:stale', route: null, severity: 'attention', hash: targetHash(TARGETS.HEALTH), label: t('attention.healthStalePrefix') + days + t('attention.healthStaleSuffix') }]; } return []; } export function registerAttentionSources() { if (registered) return; registered = true; registerAttention(integrationsNeedingKey); registerAttention(sourcesWithoutRate); registerAttention(sourcesNearLimit); registerAttention(priceGaps); registerAttention(openActions); registerAttention(backupReminder); registerAttention(healthCheckReminder); registerAttention(aiActivity); registerAttention(licenseChanged); }