const $=s=>document.querySelector(s), fmt=n=>new Intl.NumberFormat('en',{notation:'compact',maximumFractionDigits:1}).format(n||0), fmt2=n=>new Intl.NumberFormat('en',{notation:'compact',minimumFractionDigits:2,maximumFractionDigits:2}).format(n||0), money=n=>new Intl.NumberFormat('en-US',{style:'currency',currency:'USD',maximumFractionDigits:n<10?2:0}).format(n||0), pct=n=>`${(n||0).toFixed(1)}%`; let data, chartMode='models'; const palette=['#7357ff','#20a4a8','#ff805d','#e4b63f','#4b8cff','#d45bba','#72a33a','#9b6b43','#32343a','#a5a2ff','#19b875','#e85d5d']; const modelColor=name=>{let hash=2166136261;for(const char of String(name))hash=Math.imul(hash^char.codePointAt(0),16777619);return palette[(hash>>>0)%palette.length]}; async function load(){ const q=new URLSearchParams(); for(const id of ['from','to','project','model','source']) if($('#'+id).value) q.set(id,$('#'+id).value); $('#subtitle').textContent='Reading local session history…'; try{data=await fetch('/api/stats?'+q).then(r=>r.json());if(data.meta?.schemaVersion!==2)throw new Error('The Codex Lens server is outdated. Stop it and run npm start again.');render()}catch(e){$('#subtitle').innerHTML=`${e.message}`} } function options(el,values,label){const old=el.value;el.innerHTML=``+values.map(x=>``).join('');el.value=old} function esc(s){return String(s).replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]))} function render(){ const {total:t,meta}=data; options($('#project'),data.filters.projects,'projects');options($('#model'),data.filters.models,'models');options($('#source'),data.filters.sources||[],'sources'); $('#subtitle').textContent=`${meta.files} sessions · ${meta.events.toLocaleString()} model calls · scanned ${new Date(meta.scannedAt).toLocaleTimeString()}`; $('#cost').textContent=money(t.cost);const coverage=t.total?t.priced/t.total*100:0;$('#coverage').textContent=`${pct(coverage)} token pricing coverage · standard API rates`; const uncached=Math.max(0,t.input-t.cached); $('#tokens').textContent=fmt2(t.total);$('#tokenMix').textContent=`${fmt2(uncached)} normal input · ${fmt2(t.cached)} cached · ${fmt2(t.output)} output`; const cr=t.input?t.cached/t.input*100:0;$('#cache').textContent=pct(cr);$('#cacheSaved').textContent=`${fmt(t.cached)} cached input tokens`; const matchedSessions=data.meta.matchedSessions??data.meta.files??data.sessions.length;$('#activity').textContent=`${matchedSessions} / ${t.turns.toLocaleString()}`;$('#duration').textContent=`${(t.duration/36e5).toFixed(1)} active task hours`; const peak=data.days.reduce((a,d)=>d.total>(a?.total||0)?d:a,null); $('#uncached').textContent=fmt(uncached);$('#uncachedShare').textContent=`${pct(t.input?uncached/t.input*100:0)} of input`; $('#reasoning').textContent=fmt(t.reasoning);$('#reasoningShare').textContent=`${pct(t.output?t.reasoning/t.output*100:0)} of output`; $('#avgSession').textContent=fmt(t.total/(matchedSessions||1));$('#avgCost').textContent=`${money(t.cost/(matchedSessions||1))} API-equiv.`; $('#peakDay').textContent=peak?new Date(peak.name+'T12:00:00').toLocaleDateString(undefined,{month:'short',day:'numeric'}):'—';$('#peakTokens').textContent=peak?`${fmt(peak.total)} tokens`:'No activity'; $('#activeDays').textContent=data.days.length;$('#streak').textContent=`Longest streak ${longestStreak(data.days)} days`; $('#modelCount').textContent=data.models.length;$('#topModel').textContent=data.models[0]?`${data.models[0].name} leads`:'No models'; const daily=data.days.map(x=>x.total).sort((a,b)=>a-b), median=daily.length?(daily[Math.floor((daily.length-1)/2)]+daily[Math.ceil((daily.length-1)/2)])/2:0; $('#avgCall').textContent=fmt(t.total/(meta.events||1));$('#callsPerDay').textContent=`${(meta.events/(data.days.length||1)).toFixed(1)} calls / active day`; $('#medianDay').textContent=fmt(median);$('#dailyAverage').textContent=`${fmt(t.total/(data.days.length||1))} daily average`; $('#outputRate').textContent=pct(t.total?t.output/t.total*100:0); const cacheSaving=data.models.reduce((sum,m)=>{const p=data.prices[m.name];return sum+(p?m.cached*(p.input-p.cached)/1e6:0)},0);$('#cacheSaving').textContent=money(cacheSaving); renderChart(); const mm=Math.max(...data.models.map(x=>x.total),1);$('#modelBars').innerHTML='
'+data.models.slice(0,9).map(m=>{const base=modelColor(m.name),uncached=Math.max(0,m.input-m.cached),sum=uncached+m.cached+m.output||1,tip=[m.name,`Uncached input ${fmt(uncached)} (${pct(uncached/sum*100)})`,`Cached input ${fmt(m.cached)} (${pct(m.cached/sum*100)})`,`Output ${fmt(m.output)} (${pct(m.output/sum*100)})`,`Reasoning output ${fmt(m.reasoning)}`,`Cache hit rate ${pct(m.input?m.cached/m.input*100:0)}`,`API-equivalent cost ${money(m.cost)}`].join(' ');return `No project activity in this range
'; document.querySelectorAll('.project-bar').forEach(b=>b.onclick=()=>{$('#project').value=b.dataset.project;load();scrollTo({top:0,behavior:'smooth'})}); const query=$('#projectSearch').value.toLowerCase(), sort=$('#projectSort').value; const rows=data.projects.filter(x=>x.name.toLowerCase().includes(query)).sort((a,b)=>sort==='cost'?b.cost-a.cost:sort==='cache'?(b.cached/(b.input||1))-(a.cached/(a.input||1)):sort==='output'?b.output-a.output:b.total-a.total); $('#projectsBody').innerHTML=rows.slice(0,100).map(x=>`No usage in this range
'; $('#axisNote').textContent='Stacked totals by model · hover any day for details'; }else{ $('#modelLegend').innerHTML='Uncached inputCached inputOutput'; chart.innerHTML=data.days.map(d=>{const u=Math.max(0,d.input-d.cached),sum=u+d.cached+d.output||1,parts=[['Uncached',u,'var(--purple)'],['Cached',d.cached,'var(--blue)'],['Output',d.output,'var(--orange)']],tip=[`${d.name} · token composition`,...parts.map(x=>`${x[0]} ${fmt(x[1])} (${pct(x[1]/sum*100)})`)].join(' ');return `