// @ts-nocheck import { zhongtaiMetricsUrl, CONFIG, loadModelSource, loadAnthropicConfig, loadOllamaConfig, loadOmlxConfig, loadSparkleConfig } from './config.js'; const els = { model: document.getElementById('badge-model'), cache: document.getElementById('badge-cache'), tokens: document.getElementById('badge-tokens'), mem: document.getElementById('badge-mem'), cpu: document.getElementById('badge-cpu'), }; let _timer = null; let _lastTokRate = 0; let _lastTokAt = 0; let _cacheRead = 0; let _cacheWrite = 0; let _cacheInput = 0; let _cacheAt = 0; let _cacheLast = null; export function reportTokens(tokens, durationMs) { if (!tokens || !durationMs || durationMs < 100) return; _lastTokRate = Math.round(tokens / (durationMs / 1000)); _lastTokAt = Date.now(); } let _streamStartAt = 0; let _streamChars = 0; let _streamTokens = 0; let _lastStreamUpdate = 0; export function reportStreamDelta(charCount) { const now = Date.now(); if (_streamStartAt === 0) { _streamStartAt = now; _streamChars = 0; _streamTokens = 0; } _streamChars += charCount; _streamTokens = Math.ceil(_streamChars / 3.5); const dt = (now - _streamStartAt) / 1000; if (dt > 0.3) { _lastTokRate = Math.round(_streamTokens / dt); _lastTokAt = now; _lastStreamUpdate = now; } } export function reportStreamEnd(finalTokens) { if (finalTokens && finalTokens > 0) { const dt = (Date.now() - _streamStartAt) / 1000; if (dt > 0.1) { _lastTokRate = Math.round(finalTokens / dt); _lastTokAt = Date.now(); } } _streamStartAt = 0; _streamChars = 0; _streamTokens = 0; } export function reportPromptCache(usage) { const parsed = parseCacheUsage(usage); if (!parsed) return; _cacheRead += parsed.read; _cacheWrite += parsed.write; _cacheInput += parsed.input; _cacheAt = Date.now(); _cacheLast = parsed; renderCacheBadge(); } export function resetPromptCache() { _cacheRead = 0; _cacheWrite = 0; _cacheInput = 0; _cacheAt = 0; _cacheLast = null; renderCacheBadge(); } function parseCacheUsage(usage) { if (!usage || typeof usage !== 'object') return null; const aRead = Number(usage.cache_read_input_tokens) || 0; const aWrite = Number(usage.cache_creation_input_tokens) || 0; const aInput = Number(usage.input_tokens) || 0; const hasAnthropicCacheKeys = ( 'cache_read_input_tokens' in usage || 'cache_creation_input_tokens' in usage ); const oPrompt = Number(usage.prompt_tokens) || 0; const oCached = Number( usage.prompt_tokens_details?.cached_tokens ?? usage.cached_tokens ) || 0; if (hasAnthropicCacheKeys) { const read = Math.max(aRead, oCached); const write = aWrite; const input = aInput > 0 ? aInput : Math.max(0, oPrompt - oCached); if (read + write + input <= 0) return null; return { read, write, input }; } if (oPrompt > 0 || oCached > 0) { return { read: oCached, write: 0, input: Math.max(0, (oPrompt || oCached) - oCached), }; } if (aInput > 0) { return { read: 0, write: 0, input: aInput }; } return null; } function renderCacheBadge() { if (!els.cache) return; const total = _cacheRead + _cacheWrite + _cacheInput; if (total <= 0) { els.cache.textContent = '缓存命中 -'; els.cache.className = 'badge'; els.cache.title = 'Prompt 缓存命中率(尚无数据)'; return; } const pct = Math.round((_cacheRead / total) * 100); els.cache.textContent = `缓存命中 ${pct}%`; els.cache.className = pct > 0 ? 'badge active' : 'badge'; const last = _cacheLast ? ` · 上次 读${_cacheLast.read}/写${_cacheLast.write}/新${_cacheLast.input}` : ''; els.cache.title = `本会话累计命中率 ${pct}%` + ` · 读 ${_cacheRead} · 写 ${_cacheWrite} · 未缓存 ${_cacheInput}` + last; } async function fetchOnce() { try { const r = await fetch(zhongtaiMetricsUrl()); if (!r.ok) return; const m = await r.json(); render(m); } catch (e) { } } function render(m) { const source = loadModelSource(); if (source === 'anthropic') { const cfg = loadAnthropicConfig(); const tierModel = ({ fast: cfg.fastModel, standard: cfg.standardModel, pro: cfg.proModel, })[cfg.currentTier] || cfg.fastModel; const tierLabel = ({ fast: '快速', standard: '平衡', pro: '增强' })[cfg.currentTier] || ''; const proto = cfg.protocol === 'openai' ? 'OpenAI' : 'Anthropic'; els.model.textContent = `云端 ${shortName(tierModel)}`; els.model.className = 'badge active'; els.model.title = `云端 · ${tierLabel}档 · ${proto} · ${cfg.baseUrl}`; } else if (source === 'omlx') { const cfg = loadOmlxConfig(); els.model.textContent = `oMLX ${shortName(cfg.model)}`; els.model.className = 'badge active'; els.model.title = `本地 oMLX · ${cfg.baseUrl}`; } else if (source === 'sparkle') { const cfg = loadSparkleConfig(); els.model.textContent = `Sparkle ${shortName(cfg.model)}`; els.model.className = 'badge active'; els.model.title = `本地 Sparkle · ${cfg.baseUrl}`; } else { const model = m.ollama; if (model?.running && model.models?.length > 0) { const m0 = model.models[0]; const name = shortName(m0.name); els.model.textContent = `本地 ${name}`; els.model.className = 'badge active'; } else { els.model.textContent = '本地 未加载'; els.model.className = 'badge'; } } renderCacheBadge(); if (_lastTokAt && Date.now() - _lastTokAt < 5000) { els.tokens.textContent = `速率 ${_lastTokRate} tok/s`; els.tokens.className = 'badge active'; } else { els.tokens.textContent = `速率 - tok/s`; els.tokens.className = 'badge'; _lastTokRate = 0; } if (m.memory) { const used = m.memory.used; const total = m.memory.total; const pct = m.memory.usagePercent.toFixed(0); const model = m.ollama; if (source === 'ollama' && model?.processMem && model.running) { els.mem.textContent = `模型 ${gb(model.processMem)}G · 系统 ${pct}%`; els.mem.title = `模型占用 ${gb(model.processMem)}GB · 系统内存压力 ${pct}%(活动监视器口径,总 ${gb(total)}GB)`; } else if (source === 'omlx') { els.mem.textContent = `内存/显存 ${gb(used)}/${gb(total)}GB · ${pct}%`; els.mem.title = `oMLX 模型占用看 admin dashboard · 系统内存压力 ${pct}%`; } else { els.mem.textContent = `内存 ${gb(used)}/${gb(total)}GB · ${pct}%`; els.mem.title = `系统内存使用 ${pct}%(总 ${gb(total)}GB)`; } els.mem.className = pct > 90 ? 'badge danger' : pct > 75 ? 'badge warn' : 'badge'; } if (m.cpu) { const cpu = m.cpu; const cpuPct = cpu.usagePercent.toFixed(0); els.cpu.textContent = `CPU ${cpuPct}% · ${cpu.count}核`; els.cpu.className = cpu.usagePercent > 80 ? 'badge danger' : cpu.usagePercent > 50 ? 'badge warn' : 'badge'; } } function gb(bytes) { return (bytes / 1024 / 1024 / 1024).toFixed(1); } export function shortName(name) { if (!name) return '?'; return String(name) .replace(':latest', '') .replace(/-A\d+B(?=-|$)/i, ''); } export function start() { if (_timer) return; fetchOnce(); _timer = setInterval(fetchOnce, CONFIG.METRICS_POLL_MS); } export function stop() { if (_timer) { clearInterval(_timer); _timer = null; } }