diff --git a/engraphis/classic_assets/dashboard.js b/engraphis/classic_assets/dashboard.js index 76dd4ad0..282692a5 100644 --- a/engraphis/classic_assets/dashboard.js +++ b/engraphis/classic_assets/dashboard.js @@ -131,23 +131,23 @@ function setWS(name){ } async function loadWorkspaceList(){const d=await api('/workspaces');WORKSPACES=d.workspaces||[];if(!WS&&WORKSPACES.length){WORKSPACES.sort((a,b)=>(b.memories||0)-(a.memories||0));setWS(WORKSPACES[0].name)}} -/* overview */ -function formatTokenCount(value){return Math.max(0,Math.round(Number(value)||0)).toLocaleString()} -function savingsPercent(value){return Math.max(0,Math.min(100,Number(value)*100||0))} -function savingsCount(value){return Math.max(0,Math.round(Number(value)||0))} -function savingsNode(tag,cls,text){const n=document.createElement(tag);if(cls)n.className=cls;if(text!=null)n.textContent=text;return n} -function renderOverviewSavings(data,error){ - const el=document.getElementById('ov-savings'); - if(!el)return; - el.replaceChildren(); - if(error){el.append(savingsNode('div','empty','Savings estimate unavailable.'));return} - const e=(data&&data.estimated)||{},eligible=savingsCount(e.eligible_receipt_count),excluded=savingsCount(e.excluded_receipt_count)+savingsCount(e.unclassified_receipt_count)+savingsCount(e.invalid_estimate_count),saved=Number(e.saved_tokens)||0,ratio=Number(e.savings_ratio)||0,counters=Array.isArray(e.by_token_counter)?e.by_token_counter:[]; - if(!eligible){el.append(savingsNode('div','empty','No receipt-backed context savings yet.'),savingsNode('div','field-hint','Eligible deliveries will appear after adaptive context or context-delivery calls. '+excluded+' excluded or unclassified '+(excluded===1?'delivery':'deliveries')+'.'),savingsNode('div','field-hint','Measures estimated prompt-context reduction; it does not measure provider billing.'));return} - const counter=counters.length===1?'Counter: '+(counters[0].token_counter||'unknown'):counters.length?counters.length+' token counters (kept separate)':'Counter: unknown',pct=savingsPercent(ratio),pctLabel=pct.toFixed(1); - const hero=savingsNode('div','savings-hero'),total=savingsNode('div'),rate=savingsNode('div','savings-rate'),progress=document.createElement('progress'); - total.append(savingsNode('div','stat-val savings-number',formatTokenCount(saved)),savingsNode('div','savings-unit','tokens avoided'));rate.append(savingsNode('strong','',pctLabel+'%'),savingsNode('span','','estimated reduction'));hero.append(total,rate);progress.className='savings-progress';progress.max=100;progress.value=pct;progress.setAttribute('aria-label',pctLabel+'% estimated context reduction'); - el.append(hero,progress,savingsNode('div','savings-summary','Across '+eligible+' eligible context deliveries'),savingsNode('div','field-hint','Baseline '+formatTokenCount(e.baseline_tokens)+' → emitted '+formatTokenCount(e.emitted_tokens)+' · confidence: '+(e.confidence||'unknown')),savingsNode('div','field-hint',counter+(excluded?' · '+excluded+' excluded/unclassified':'')),savingsNode('div','field-hint','Measures estimated prompt-context reduction; it does not measure provider billing.')); -} +/* overview */ +function formatTokenCount(value){return Math.max(0,Math.round(Number(value)||0)).toLocaleString()} +function savingsPercent(value){return Math.max(0,Math.min(100,Number(value)*100||0))} +function savingsCount(value){return Math.max(0,Math.round(Number(value)||0))} +function savingsNode(tag,cls,text){const n=document.createElement(tag);if(cls)n.className=cls;if(text!=null)n.textContent=text;return n} +function renderOverviewSavings(data,error){ + const el=document.getElementById('ov-savings'); + if(!el)return; + el.replaceChildren(); + if(error){el.append(savingsNode('div','empty','Savings estimate unavailable.'));return} + const e=(data&&data.estimated)||{},eligible=savingsCount(e.eligible_receipt_count),excluded=savingsCount(e.excluded_receipt_count)+savingsCount(e.unclassified_receipt_count)+savingsCount(e.invalid_estimate_count),saved=Number(e.saved_tokens)||0,ratio=Number(e.savings_ratio)||0,counters=Array.isArray(e.by_token_counter)?e.by_token_counter:[]; + if(!eligible){el.append(savingsNode('div','empty','No receipt-backed context savings yet.'),savingsNode('div','field-hint','Eligible deliveries will appear after adaptive context or context-delivery calls. '+excluded+' excluded or unclassified '+(excluded===1?'delivery':'deliveries')+'.'),savingsNode('div','field-hint','Measures estimated prompt-context reduction; it does not measure provider billing.'));return} + const counter=counters.length===1?'Counter: '+(counters[0].token_counter||'unknown'):counters.length?counters.length+' token counters (kept separate)':'Counter: unknown',pct=savingsPercent(ratio),pctLabel=pct.toFixed(1); + const hero=savingsNode('div','savings-hero'),total=savingsNode('div'),rate=savingsNode('div','savings-rate'),progress=document.createElement('progress'); + total.append(savingsNode('div','stat-val savings-number',formatTokenCount(saved)),savingsNode('div','savings-unit','tokens avoided'));rate.append(savingsNode('strong','',pctLabel+'%'),savingsNode('span','','estimated reduction'));hero.append(total,rate);progress.className='savings-progress';progress.max=100;progress.value=pct;progress.setAttribute('aria-label',pctLabel+'% estimated context reduction'); + el.append(hero,progress,savingsNode('div','savings-summary','Across '+eligible+' eligible context deliveries'),savingsNode('div','field-hint','Baseline '+formatTokenCount(e.baseline_tokens)+' → emitted '+formatTokenCount(e.emitted_tokens)+' · confidence: '+(e.confidence||'unknown')),savingsNode('div','field-hint',counter+(excluded?' · '+excluded+' excluded/unclassified':'')),savingsNode('div','field-hint','Measures estimated prompt-context reduction; it does not measure provider billing.')); +} async function loadOverview(){try{const st=await api('/stats?workspace='+encodeURIComponent(WS||''));setViewDesc('overview',(st.memories||0)+' memories · '+(st.workspaces||0)+' workspaces');const cards=[['Memories',st.memories],['Live rows',st.total_rows],['Workspaces',st.workspaces],['Sessions',st.sessions]];document.getElementById('stat-grid').innerHTML=cards.map(c=>`
${c[1]!=null?c[1]:'—'}
${c[0]}
`).join('');document.getElementById('nav-mem-count').textContent=st.memories||'';const bt=st.by_type||{};const tot=Object.values(bt).reduce((a,b)=>a+b,0)||1;document.getElementById('ov-types').innerHTML=Object.keys(bt).length?Object.entries(bt).map(([k,v])=>`
${esc(k)}
${v}
`).join(''):'
No memories
';try{renderOverviewSavings(await api('/context-savings?workspace='+encodeURIComponent(WS||'')))}catch(_err){renderOverviewSavings(null,true)}loadOverviewAnalytics()}catch(e){const msg='Overview unavailable: '+e.message;setViewDesc('overview',msg);document.getElementById('stat-grid').innerHTML='
'+esc(msg)+'
';document.getElementById('ov-types').innerHTML='
Memory types could not be loaded.
';document.getElementById('ov-savings').innerHTML='
Savings estimate could not be loaded.
';document.getElementById('ov-analytics').innerHTML='
Analytics could not be loaded.
';toast(msg,'err')}} async function loadOverviewAnalytics(){ const el=document.getElementById('ov-analytics'),lock=document.getElementById('ov-lock'); @@ -427,7 +427,7 @@ async function loadReceipts(){const el=document.getElementById('audit-body');el. async function downloadReceipts(){try{const d=await api('/receipts/export?workspace='+encodeURIComponent(WS||''));const blob=new Blob([JSON.stringify(d,null,2)],{type:'application/json'});const a=document.createElement('a');a.href=URL.createObjectURL(blob);a.download='engraphis-receipts-'+(WS||'workspace')+'.json';a.click();URL.revokeObjectURL(a.href);toast('Privacy-safe receipts exported','ok')}catch(e){toast(e.message,'err')}} let SAVINGS_PRESET='all'; -function savingsPresetQuery(){const p=new URLSearchParams({workspace:WS||''});if(SAVINGS_PRESET==='current')p.set('release_version','1.6');if(SAVINGS_PRESET==='7d')p.set('from_ts',String(Date.now()/1000-604800));return p.toString()} +function savingsPresetQuery(){const p=new URLSearchParams({workspace:WS||''});if(SAVINGS_PRESET==='current')p.set('release_version','1.6');if(SAVINGS_PRESET==='7d')p.set('from_ts',String(Date.now()/1000-604800));return p.toString()} function renderSavingsDetail(s){const e=(s&&s.estimated)||{},eligible=Number(e.eligible_receipt_count)||0,excluded=(Number(e.excluded_receipt_count)||0)+(Number(e.unclassified_receipt_count)||0)+(Number(e.invalid_estimate_count)||0),basisRows=(e.by_basis||[]).map(x=>'
'+esc((x.basis||'unclassified').replaceAll('_',' '))+' · '+esc(x.confidence||'unknown')+''+formatTokenCount(x.baseline_tokens)+' → '+formatTokenCount(x.emitted_tokens)+' · '+formatTokenCount(x.saved_tokens)+' saved ('+(x.receipt_count||0)+' delivery)
').join(''),counterRows=(e.by_token_counter||[]).map(x=>'
'+esc(x.token_counter||'unknown')+''+formatTokenCount(x.saved_tokens)+' saved · '+(x.receipt_count||0)+' eligible delivery
').join(''),preset=SAVINGS_PRESET==='current'?'Current release':SAVINGS_PRESET==='7d'?'Last 7 days':SAVINGS_PRESET==='since'?'Since tracking started':'All time';const buttons=['since','current','7d','all'].map(x=>'').join('');return '
Estimated context saved
View'+buttons+'
'+(eligible?'
'+formatTokenCount(e.saved_tokens)+' tokens
Baseline '+formatTokenCount(e.baseline_tokens)+' → emitted '+formatTokenCount(e.emitted_tokens)+' · '+(Number(e.savings_ratio||0)*100).toFixed(1)+'% estimated reduction
'+eligible+' eligible deliveries · confidence: '+esc(e.confidence||'unknown')+' · range: '+preset+'
'+(basisRows||'
No basis breakdown available.
')+(counterRows?'
Token counters
'+counterRows:''):'
No eligible estimates in this range.
')+'
'+excluded+' excluded or unclassified delivery(s). Measures estimated prompt-context reduction; it does not measure provider billing.
'} async function loadReceipts(){const el=document.getElementById('audit-body');el.innerHTML='
';try{if(!window.__savingsPresetBound){window.__savingsPresetBound=true;document.addEventListener('click',function(ev){const button=ev.target.closest('[data-savings-preset]');if(!button)return;SAVINGS_PRESET=button.getAttribute('data-savings-preset')||'all';loadReceipts()})}const q='workspace='+encodeURIComponent(WS||''),sq=savingsPresetQuery();const [d,v,s]=await Promise.all([api('/receipts?'+q+'&limit=500'),api('/receipts/verify?'+q),api('/context-savings?'+sq)]);const rows=d.entries||[],packed=(s.by_token_counter||[]).map(x=>'
'+esc(x.token_counter||'unknown')+''+formatTokenCount(x.context_tokens)+' packed / '+formatTokenCount(x.source_tokens)+' source · '+formatTokenCount(x.saved_tokens)+' legacy saved
').join('');const packedCard='
Packed context accounting
Packing savings compare retrieved source tokens with emitted context. They are not added again to adaptive history savings.
'+(packed||'
No complete context-usage receipts yet.
')+'
';el.innerHTML=renderSavingsDetail(s)+packedCard+'
Receipt chain '+(v.valid?'verified':'invalid')+'
'+(v.count||0)+' receipts · head '+esc((v.head||'').slice(0,24))+'
'+(rows.length?'
'+rows.map(r=>'
'+esc(r.operation||'operation')+''+esc((r.hash||'').slice(0,20))+' · '+esc(r.status||'ok')+' · '+(r.target_count||0)+' target(s)'+(r.ts_ms?fmtRel(r.ts_ms/1000):'')+'
').join('')+'
':'
No receipts yet.
')}catch(e){el.innerHTML='
'+esc(e.message)+'
'}} @@ -1197,7 +1197,7 @@ function loadGraphEngine(){ if(GRAPH_ENGINE_LOADING)return GRAPH_ENGINE_LOADING; GRAPH_ENGINE_LOADING=new Promise((resolve,reject)=>{ const script=document.createElement('script'); - script.src='/v2-assets/engraphis-graph.js?v=20260809-pin-only-physics'; + script.src='/v2-assets/engraphis-graph.js?v=20260809-physics-guard'; /* A 200 that never registers the global is a corrupt/truncated asset, not a success — resolving there would hand graphRenderEngine() an undefined EngraphisGraph. */ script.onload=()=>{typeof EngraphisGraph==='undefined'?reject(new Error('Graph engine asset loaded without registering EngraphisGraph')):resolve()}; diff --git a/engraphis/classic_assets/index.html b/engraphis/classic_assets/index.html index d38302fd..87d6adf2 100644 --- a/engraphis/classic_assets/index.html +++ b/engraphis/classic_assets/index.html @@ -6,7 +6,7 @@ Engraphis - + @@ -351,6 +351,6 @@ graph view. dashboard.js fetches both on demand from graphRender(); see loadForceGraph() and loadGraphEngine(). scripts/externalize_dashboard_assets.py enforces both halves: they stay out of this file, and the lazy references still have to resolve. --> - + diff --git a/engraphis/dashboard_assets/index.html b/engraphis/dashboard_assets/index.html index 2550a7a1..16532ee9 100644 --- a/engraphis/dashboard_assets/index.html +++ b/engraphis/dashboard_assets/index.html @@ -7,7 +7,7 @@ Engraphis Ledger - + @@ -15,6 +15,7 @@