diff --git a/static/opax.html b/static/opax.html index d30edfc..7d6cecb 100644 --- a/static/opax.html +++ b/static/opax.html @@ -253,6 +253,7 @@ .md-viewer hr{border:none;border-top:1px solid var(--border);margin:20px 0} .md-viewer-loading{display:flex;align-items:center;justify-content:center;height:200px;color:var(--text-faint);font-family:var(--mono);font-size:13px;gap:10px} +
@@ -593,16 +594,62 @@ async function loadBilling(){ h+='
' }else{ h+='
✅ Ingen anomalier
' } const mods = mod?.modules||mod?.data||[] + const cols = ['#7c5cff','#00d4a8','#ffb547','#ff5c7c','#3ecf8e','#5c8fff','#ff9f7c','#c084fc'] if(mods.length){ - const cols=['#7c5cff','#00d4a8','#ffb547','#ff5c7c','#3ecf8e'] - h+='
Token-forbruk per modul
' + h+='
Token-forbruk per modul
' + h+='
ModulTokensInputOutput
' mods.forEach((m,i)=>{ h+=`` }) h+='
ModulTokensInputOutput
${esc(m.module||m.name||'—')}${(m.total_tokens||0).toLocaleString('no')}${(m.input_tokens||0).toLocaleString('no')}${(m.output_tokens||0).toLocaleString('no')}
' + h+=`
+ +
` + el.innerHTML = h + renderDonut(mods, cols) + return } el.innerHTML=h }catch(e){ el.innerHTML=`
Feil
${e.message}
` } } +function renderDonut(mods, cols) { + const canvas = document.getElementById('mod-donut') + if (!canvas) return + if (canvas._chart) canvas._chart.destroy() + canvas._chart = new Chart(canvas, { + type: 'doughnut', + data: { + labels: mods.map(m => m.module || m.name || '—'), + datasets: [{ + data: mods.map(m => m.total_tokens || 0), + backgroundColor: mods.map((_, i) => cols[i % cols.length]), + borderColor: '#0a0a0f', + borderWidth: 3, + hoverOffset: 6, + }] + }, + options: { + cutout: '68%', + plugins: { + legend: { + position: 'bottom', + labels: { + color: '#8b909c', + font: { size: 11, family: '-apple-system,BlinkMacSystemFont,Inter,system-ui,sans-serif' }, + padding: 14, + boxWidth: 10, + boxHeight: 10, + } + }, + tooltip: { + callbacks: { + label: ctx => ` ${ctx.label}: ${ctx.parsed.toLocaleString('no')} tokens` + } + } + } + } + }) +} + // BUILD PANEL async function loadBuild(){ const el=document.getElementById('build-body') @@ -707,20 +754,14 @@ function mdShowGrid(){ function renderMd(text){ let s=esc(text) - // code blocks first s=s.replace(/```[^\n]*\n([\s\S]*?)```/g,'
$1
') s=s.replace(/`([^`]+)`/g,'$1') - // headings s=s.replace(/^### (.+)$/gm,'

$1

') s=s.replace(/^## (.+)$/gm,'

$1

') s=s.replace(/^# (.+)$/gm,'

$1

') - // bold s=s.replace(/\*\*(.+?)\*\*/g,'$1') - // hr s=s.replace(/^---$/gm,'
') - // blockquote s=s.replace(/^> (.+)$/gm,'
$1
') - // tables (simple) s=s.replace(/((?:^\|.+\|\n)+)/gm, tbl=>{ const rows=tbl.trim().split('\n').filter(r=>!/^\|[-| :]+\|$/.test(r)) if(!rows.length) return tbl @@ -734,11 +775,9 @@ function renderMd(text){ out+='' return out }) - // lists s=s.replace(/^[-*] (.+)$/gm,'
  • $1
  • ') s=s.replace(/(
  • .*<\/li>)/s, m=>'') s=s.replace(/^\d+\. (.+)$/gm,'
  • $1
  • ') - // paragraphs — wrap loose lines s=s.replace(/^(?!<[htublp|]).+$/gm, line=>line.trim()?`

    ${line}

    `:'') return s }