Files
AtreidesAssetsFree/client/ui/attention-sources.js
T

203 lines
7.7 KiB
JavaScript
Raw Permalink Normal View History

2026-10-29 11:57:29 +01:00
// Generated, do not edit. sha256:51b53dc3f01e74bb7f221f45ce7f2b9b6f6a164a12cedc96e33ad4f1b1b701a0
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';
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 (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);
}