agentix-cli 0.17.0 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +75 -27
- package/dist/agent-57MG4LBM.js +2 -0
- package/dist/audit-JU3HWISB.js +3 -0
- package/dist/audit-JU3HWISB.js.map +1 -0
- package/dist/{board-dashboard-CXIVJKDP.js → board-dashboard-HXDOYGIG.js} +2237 -69
- package/dist/board-dashboard-HXDOYGIG.js.map +1 -0
- package/dist/chunk-23AUU4S2.js +102 -0
- package/dist/chunk-23AUU4S2.js.map +1 -0
- package/dist/{chunk-EKTPCSN4.js → chunk-4ITQP3XL.js} +2 -2
- package/dist/chunk-4MREHZP5.js +111 -0
- package/dist/chunk-4MREHZP5.js.map +1 -0
- package/dist/chunk-4YCH6IZV.js +2 -0
- package/dist/chunk-53NRX2NA.js +2 -0
- package/dist/chunk-53NRX2NA.js.map +1 -0
- package/dist/chunk-6B74TO2J.js +743 -0
- package/dist/chunk-6B74TO2J.js.map +1 -0
- package/dist/chunk-7P5QRX5W.js +108 -0
- package/dist/chunk-7P5QRX5W.js.map +1 -0
- package/dist/chunk-A6BKSZQA.js +3 -0
- package/dist/chunk-A6BKSZQA.js.map +1 -0
- package/dist/{chunk-6PPIFDOQ.js → chunk-CNNLREHD.js} +67 -22
- package/dist/{chunk-6PPIFDOQ.js.map → chunk-CNNLREHD.js.map} +1 -1
- package/dist/chunk-DJEZ2HVU.js +5 -0
- package/dist/chunk-DJEZ2HVU.js.map +1 -0
- package/dist/chunk-DLVOHCTA.js +5 -0
- package/dist/{chunk-MBEQASP3.js.map → chunk-DLVOHCTA.js.map} +1 -1
- package/dist/chunk-E4ZZILAK.js +15 -0
- package/dist/chunk-E4ZZILAK.js.map +1 -0
- package/dist/{chunk-63UYRJIC.js → chunk-IKBHHTIT.js} +2 -2
- package/dist/chunk-JGXE2YNV.js +46 -0
- package/dist/chunk-JGXE2YNV.js.map +1 -0
- package/dist/chunk-JTJIPNXY.js +2 -0
- package/dist/chunk-JTJIPNXY.js.map +1 -0
- package/dist/chunk-MBOLXKA2.js +6 -0
- package/dist/chunk-MBOLXKA2.js.map +1 -0
- package/dist/{inbox-L5ZZ623Y.js → chunk-MTRWXRFF.js} +69 -4
- package/dist/chunk-MTRWXRFF.js.map +1 -0
- package/dist/{processes-XF5CV4LK.js → chunk-NOF4HSGK.js} +3 -3
- package/dist/{processes-XF5CV4LK.js.map → chunk-NOF4HSGK.js.map} +1 -1
- package/dist/chunk-QW2OYR3N.js +7 -0
- package/dist/chunk-QW2OYR3N.js.map +1 -0
- package/dist/chunk-SR3X25TN.js +3 -0
- package/dist/chunk-SR3X25TN.js.map +1 -0
- package/dist/chunk-SV3I4CI7.js +440 -0
- package/dist/chunk-SV3I4CI7.js.map +1 -0
- package/dist/{chunk-TT2Q7YMZ.js → chunk-TC3BSUP7.js} +6 -6
- package/dist/{chunk-TT2Q7YMZ.js.map → chunk-TC3BSUP7.js.map} +1 -1
- package/dist/chunk-VJUTQAPJ.js +2 -0
- package/dist/chunk-VJUTQAPJ.js.map +1 -0
- package/dist/chunk-VWTBHLMX.js +1 -0
- package/dist/chunk-WFHIIVLL.js +719 -0
- package/dist/chunk-WFHIIVLL.js.map +1 -0
- package/dist/chunk-WPPUOTY6.js +6 -0
- package/dist/chunk-WPPUOTY6.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/cli.js.map +1 -1
- package/dist/{compaction-X46EQEPS.js → compaction-BTUJJ3LJ.js} +4 -4
- package/dist/config-QXWKEQON.js +2 -0
- package/dist/config-mutator-53XGR2NT.js +2 -0
- package/dist/conflict-detector-D7VIYPDI.js +2 -0
- package/dist/conflict-detector-D7VIYPDI.js.map +1 -0
- package/dist/{context-planner-G2J7FNNU.js → context-planner-I7ID7XF3.js} +3 -3
- package/dist/debug-N3B5NVJU.js +2 -0
- package/dist/discover-XJXQXXIO.js +4 -0
- package/dist/discover-XJXQXXIO.js.map +1 -0
- package/dist/doctor-SEOUFHSL.js +2 -0
- package/dist/{editor-chat-R54E65W5.js → editor-chat-RVLCKKB7.js} +2 -2
- package/dist/heal-YEQYWX2L.js +2 -0
- package/dist/inbox-BIN2S5XN.js +2 -0
- package/dist/index.d.ts +1292 -122
- package/dist/index.js +8 -8
- package/dist/index.js.map +1 -1
- package/dist/{ingest-whatsapp-NS4OWDOU.js → ingest-whatsapp-LZE62INA.js} +2 -2
- package/dist/loader-5CA3O5GM.js +2 -0
- package/dist/loader-6IHUCPVO.js +2 -0
- package/dist/memory-extract-KRCHD3OP.js +2 -0
- package/dist/migrate-v2-QO7SLKIM.js +4 -0
- package/dist/migrate-v2-QO7SLKIM.js.map +1 -0
- package/dist/processes-RJ6RDP32.js +2 -0
- package/dist/program-SASDC2GV.js +2 -0
- package/dist/providers-WVCMIW67.js +2 -0
- package/dist/query-HZYYRCLU.js +2 -0
- package/dist/recipes-EPLE2SNT.js +2 -0
- package/dist/registry-HM4ELSXS.js +2 -0
- package/dist/rotation-memo-GMTZPMNE.js +13 -0
- package/dist/rotation-memo-GMTZPMNE.js.map +1 -0
- package/dist/scheduler-XVFIK4XH.js +2 -0
- package/dist/{slack-PSQLVTGB.js → slack-6P7ZJ32Y.js} +2 -2
- package/dist/store-BAPDZHXE.js +2 -0
- package/dist/{subagent-2SJHSSJS.js → subagent-WV7QKQ3L.js} +2 -2
- package/dist/{task-store-NM7VKNLE.js → task-store-RHISEU6V.js} +2 -2
- package/dist/task-store-RHISEU6V.js.map +1 -0
- package/dist/{telegram-W4SLFMNY.js → telegram-PIYDRM46.js} +2 -2
- package/dist/timers-5LPZN55J.js +2 -0
- package/dist/timers-5LPZN55J.js.map +1 -0
- package/dist/types-VECTMYTO.js +2 -0
- package/dist/types-VECTMYTO.js.map +1 -0
- package/dist/types-ZRLHHCGB.js +2 -0
- package/dist/types-ZRLHHCGB.js.map +1 -0
- package/dist/web/activity-graph.global.js +654 -0
- package/dist/web/activity-graph.global.js.map +1 -0
- package/dist/web/workflow-editor.global.js +35 -12
- package/dist/web/workflow-editor.global.js.map +1 -1
- package/dist/{whatsapp-DCHRBRVV.js → whatsapp-PFRDMUMT.js} +2 -2
- package/dist/{whatsapp-state-6D6FHHD2.js → whatsapp-state-XMTCSRXU.js} +2 -2
- package/dist/wiki-53W5NLZV.js +2 -0
- package/dist/wiki-53W5NLZV.js.map +1 -0
- package/dist/workspace-setup-FPGN5VNY.js +2 -0
- package/dist/workspace-setup-FPGN5VNY.js.map +1 -0
- package/package.json +13 -2
- package/dist/agent-4AOMVXRJ.js +0 -2
- package/dist/board-dashboard-CXIVJKDP.js.map +0 -1
- package/dist/chunk-34NG7UDX.js +0 -11
- package/dist/chunk-34NG7UDX.js.map +0 -1
- package/dist/chunk-4HP6UC2X.js +0 -13
- package/dist/chunk-4HP6UC2X.js.map +0 -1
- package/dist/chunk-5WNGHDFC.js +0 -164
- package/dist/chunk-5WNGHDFC.js.map +0 -1
- package/dist/chunk-ABCHHNG6.js +0 -625
- package/dist/chunk-ABCHHNG6.js.map +0 -1
- package/dist/chunk-FIOB3MEX.js +0 -2
- package/dist/chunk-HCFSHGK4.js +0 -46
- package/dist/chunk-HCFSHGK4.js.map +0 -1
- package/dist/chunk-HHJGKD5B.js +0 -71
- package/dist/chunk-HHJGKD5B.js.map +0 -1
- package/dist/chunk-LEIYC3BF.js +0 -104
- package/dist/chunk-LEIYC3BF.js.map +0 -1
- package/dist/chunk-MBEQASP3.js +0 -5
- package/dist/chunk-MCJUAE6V.js +0 -6
- package/dist/chunk-MCJUAE6V.js.map +0 -1
- package/dist/chunk-SRHLXZRF.js +0 -7
- package/dist/chunk-SRHLXZRF.js.map +0 -1
- package/dist/chunk-WZEYII2S.js +0 -6
- package/dist/chunk-WZEYII2S.js.map +0 -1
- package/dist/chunk-XDJSYQST.js +0 -6
- package/dist/chunk-XDJSYQST.js.map +0 -1
- package/dist/chunk-YPOELN6Z.js +0 -798
- package/dist/chunk-YPOELN6Z.js.map +0 -1
- package/dist/config-KCYHMKCT.js +0 -2
- package/dist/config-mutator-QP4GLBOE.js +0 -2
- package/dist/debug-3MEUO2P2.js +0 -2
- package/dist/heal-JDXUPRET.js +0 -2
- package/dist/inbox-L5ZZ623Y.js.map +0 -1
- package/dist/loader-PTCXLJEX.js +0 -2
- package/dist/memory-extract-WH4FZ456.js +0 -2
- package/dist/program-FFWEILAS.js +0 -2
- package/dist/providers-AKQ4HTB4.js +0 -2
- package/dist/query-PKGYE5HR.js +0 -2
- package/dist/registry-IFMDJWQ7.js +0 -2
- package/dist/scheduler-63D5KFO7.js +0 -2
- package/dist/store-5MJOO74U.js +0 -2
- package/dist/timers-PCB4ZIBD.js +0 -2
- package/dist/types-C7IFH42Z.js +0 -2
- package/dist/types-OIFU3KSQ.js +0 -2
- package/dist/usage-dashboard-AXPXB2KP.js +0 -803
- package/dist/usage-dashboard-AXPXB2KP.js.map +0 -1
- package/dist/wiki-UDPYFJBL.js +0 -2
- package/dist/workspace-setup-C4VB3QZV.js +0 -2
- /package/dist/{agent-4AOMVXRJ.js.map → agent-57MG4LBM.js.map} +0 -0
- /package/dist/{chunk-EKTPCSN4.js.map → chunk-4ITQP3XL.js.map} +0 -0
- /package/dist/{chunk-FIOB3MEX.js.map → chunk-4YCH6IZV.js.map} +0 -0
- /package/dist/{chunk-63UYRJIC.js.map → chunk-IKBHHTIT.js.map} +0 -0
- /package/dist/{config-KCYHMKCT.js.map → chunk-VWTBHLMX.js.map} +0 -0
- /package/dist/{compaction-X46EQEPS.js.map → compaction-BTUJJ3LJ.js.map} +0 -0
- /package/dist/{config-mutator-QP4GLBOE.js.map → config-QXWKEQON.js.map} +0 -0
- /package/dist/{debug-3MEUO2P2.js.map → config-mutator-53XGR2NT.js.map} +0 -0
- /package/dist/{context-planner-G2J7FNNU.js.map → context-planner-I7ID7XF3.js.map} +0 -0
- /package/dist/{heal-JDXUPRET.js.map → debug-N3B5NVJU.js.map} +0 -0
- /package/dist/{loader-PTCXLJEX.js.map → doctor-SEOUFHSL.js.map} +0 -0
- /package/dist/{editor-chat-R54E65W5.js.map → editor-chat-RVLCKKB7.js.map} +0 -0
- /package/dist/{memory-extract-WH4FZ456.js.map → heal-YEQYWX2L.js.map} +0 -0
- /package/dist/{program-FFWEILAS.js.map → inbox-BIN2S5XN.js.map} +0 -0
- /package/dist/{ingest-whatsapp-NS4OWDOU.js.map → ingest-whatsapp-LZE62INA.js.map} +0 -0
- /package/dist/{providers-AKQ4HTB4.js.map → loader-5CA3O5GM.js.map} +0 -0
- /package/dist/{query-PKGYE5HR.js.map → loader-6IHUCPVO.js.map} +0 -0
- /package/dist/{registry-IFMDJWQ7.js.map → memory-extract-KRCHD3OP.js.map} +0 -0
- /package/dist/{scheduler-63D5KFO7.js.map → processes-RJ6RDP32.js.map} +0 -0
- /package/dist/{store-5MJOO74U.js.map → program-SASDC2GV.js.map} +0 -0
- /package/dist/{task-store-NM7VKNLE.js.map → providers-WVCMIW67.js.map} +0 -0
- /package/dist/{timers-PCB4ZIBD.js.map → query-HZYYRCLU.js.map} +0 -0
- /package/dist/{types-C7IFH42Z.js.map → recipes-EPLE2SNT.js.map} +0 -0
- /package/dist/{types-OIFU3KSQ.js.map → registry-HM4ELSXS.js.map} +0 -0
- /package/dist/{wiki-UDPYFJBL.js.map → scheduler-XVFIK4XH.js.map} +0 -0
- /package/dist/{slack-PSQLVTGB.js.map → slack-6P7ZJ32Y.js.map} +0 -0
- /package/dist/{workspace-setup-C4VB3QZV.js.map → store-BAPDZHXE.js.map} +0 -0
- /package/dist/{subagent-2SJHSSJS.js.map → subagent-WV7QKQ3L.js.map} +0 -0
- /package/dist/{telegram-W4SLFMNY.js.map → telegram-PIYDRM46.js.map} +0 -0
- /package/dist/{whatsapp-DCHRBRVV.js.map → whatsapp-PFRDMUMT.js.map} +0 -0
- /package/dist/{whatsapp-state-6D6FHHD2.js.map → whatsapp-state-XMTCSRXU.js.map} +0 -0
|
@@ -1,803 +0,0 @@
|
|
|
1
|
-
import{a as v}from"./chunk-34NG7UDX.js";import"./chunk-FIOB3MEX.js";import{createServer as C}from"http";import{readdirSync as T}from"fs";function R(i=4201){let c=new v;C(async(u,a)=>{let t=new URL(u.url||"/",`http://localhost:${i}`),s=t.pathname;if(a.setHeader("Access-Control-Allow-Origin","*"),s==="/api/usage"){let r=t.searchParams.get("from")||"",n=t.searchParams.get("to")||"",d=w(c,r,n);a.writeHead(200,{"Content-Type":"application/json"}),a.end(JSON.stringify(d));return}if(s==="/api/usage.csv"){let r=t.searchParams.get("from")||"",n=t.searchParams.get("to")||"",d=w(c,r,n),p="date,tasks,input,output,cache_read,cache_create,cost_usd,top_agent",h=d.map(o=>{let l=Object.entries(o.agents).sort(([,x],[,e])=>e.cost-x.cost)[0];return[o.date,o.tasks,o.input,o.output,o.cacheRead,o.cacheCreate,o.cost.toFixed(6),l?l[0]:""].join(",")}).join(`
|
|
2
|
-
`);a.writeHead(200,{"Content-Type":"text/csv; charset=utf-8","Content-Disposition":`attachment; filename="agentx-usage-${Date.now()}.csv"`}),a.end(p+`
|
|
3
|
-
`+h+`
|
|
4
|
-
`);return}a.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),a.end(M())}).listen(i,()=>{console.log(`
|
|
5
|
-
Usage dashboard: http://localhost:${i}
|
|
6
|
-
`)})}function w(i,c,g){let u=i.dir,a=[];try{for(let t of T(u)){if(!t.endsWith(".json"))continue;let s=t.replace(".json","");c&&s<c||g&&s>g||a.push(s)}}catch{return[]}return a.sort(),a.map(t=>{let s=i.getDate(t);if(!s)return null;let r=0,n=0,d=0,p=0,h=0,o=0,l={};for(let[x,e]of Object.entries(s.agents)){let m=v.calculateCost(e),b=(e.inputTokens||0)+(e.tier2InputTokens||0),f=(e.outputTokens||0)+(e.tier2OutputTokens||0),k=(e.cacheReadTokens||0)+(e.tier2CacheReadTokens||0),y=(e.cacheCreateTokens||0)+(e.tier2CacheCreateTokens||0);r+=e.tasks,n+=b,d+=f,p+=k,h+=y,o+=m,l[x]={tasks:e.tasks,cost:m,input:b,output:f,cacheRead:k,cacheCreate:y,model:e.model}}return{date:t,tasks:r,input:n,output:d,cacheRead:p,cacheCreate:h,cost:o,agents:l}}).filter(t=>t!==null&&t.tasks>0)}function M(){return`<!DOCTYPE html>
|
|
7
|
-
<html lang="en" data-theme="dark">
|
|
8
|
-
<head>
|
|
9
|
-
<meta charset="utf-8">
|
|
10
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
11
|
-
<title>Usage \xB7 AgentX</title>
|
|
12
|
-
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
13
|
-
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
14
|
-
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
|
15
|
-
<style>${E}</style>
|
|
16
|
-
</head>
|
|
17
|
-
<body>
|
|
18
|
-
|
|
19
|
-
<header class="topbar">
|
|
20
|
-
<div class="brand">
|
|
21
|
-
<div class="brand-mark">\xA7</div>
|
|
22
|
-
<div class="brand-path"><b>agentx</b><span class="slash">/</span><b>observability</b><span class="slash">/</span>usage</div>
|
|
23
|
-
</div>
|
|
24
|
-
<div class="env-badge" id="envBadge">local</div>
|
|
25
|
-
<div class="topbar-right">
|
|
26
|
-
<a class="backlink" href="/" id="backToMain" title="Back to dashboard">
|
|
27
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M19 12H5M12 19l-7-7 7-7"/></svg>
|
|
28
|
-
<span>Dashboard</span>
|
|
29
|
-
</a>
|
|
30
|
-
<div class="theme-switch" role="tablist" aria-label="Theme">
|
|
31
|
-
<button data-t="dark" class="active">Dark</button>
|
|
32
|
-
<button data-t="midnight">Midnight</button>
|
|
33
|
-
</div>
|
|
34
|
-
</div>
|
|
35
|
-
</header>
|
|
36
|
-
|
|
37
|
-
<div class="page">
|
|
38
|
-
<div class="page-header">
|
|
39
|
-
<div class="page-title">
|
|
40
|
-
<h1>Token usage & cost</h1>
|
|
41
|
-
<p><span class="live-dot"></span>Live telemetry across all agents. Last ingest <span id="lastIngest" class="mono">\u2014</span>.</p>
|
|
42
|
-
</div>
|
|
43
|
-
<div class="controls">
|
|
44
|
-
<div class="segmented" id="rangeSeg">
|
|
45
|
-
<button data-range="7">7d</button>
|
|
46
|
-
<button data-range="14">14d</button>
|
|
47
|
-
<button data-range="30" class="active">30d</button>
|
|
48
|
-
<button data-range="90">90d</button>
|
|
49
|
-
<button data-range="0">All</button>
|
|
50
|
-
</div>
|
|
51
|
-
<div class="date-picker" title="Range">
|
|
52
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="4" width="18" height="18" rx="2"/><path d="M16 2v4M8 2v4M3 10h18"/></svg>
|
|
53
|
-
<span id="rangeLabel">\u2014</span>
|
|
54
|
-
</div>
|
|
55
|
-
<a class="btn-ghost" id="exportBtn" href="#">
|
|
56
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
|
|
57
|
-
Export CSV
|
|
58
|
-
</a>
|
|
59
|
-
</div>
|
|
60
|
-
</div>
|
|
61
|
-
|
|
62
|
-
<div class="kpi-row" id="kpis"></div>
|
|
63
|
-
|
|
64
|
-
<div class="hero-grid">
|
|
65
|
-
<div class="card">
|
|
66
|
-
<div class="card-head">
|
|
67
|
-
<div>
|
|
68
|
-
<div class="card-title">Cost over time</div>
|
|
69
|
-
<div class="card-sub">Daily spend, stacked by token category</div>
|
|
70
|
-
</div>
|
|
71
|
-
<div class="card-meta">
|
|
72
|
-
<div class="legend">
|
|
73
|
-
<span class="legend-item"><span class="legend-swatch" style="background:var(--input-c)"></span>Input</span>
|
|
74
|
-
<span class="legend-item"><span class="legend-swatch" style="background:var(--output-c)"></span>Output</span>
|
|
75
|
-
<span class="legend-item"><span class="legend-swatch" style="background:var(--cache-r-c)"></span>Cache read</span>
|
|
76
|
-
<span class="legend-item"><span class="legend-swatch" style="background:var(--cache-w-c)"></span>Cache write</span>
|
|
77
|
-
</div>
|
|
78
|
-
</div>
|
|
79
|
-
</div>
|
|
80
|
-
<div class="card-body hero-chart-wrap">
|
|
81
|
-
<div class="hero-overlay">
|
|
82
|
-
<div class="hero-stat"><div class="v mono" id="heroTotalCost">$0</div><div class="l">Total \xB7 range</div></div>
|
|
83
|
-
<div class="hero-stat"><div class="v mono" id="heroPeak">$0</div><div class="l">Peak day</div></div>
|
|
84
|
-
<div class="hero-stat"><div class="v mono" id="heroAvg">$0</div><div class="l">Daily avg</div></div>
|
|
85
|
-
</div>
|
|
86
|
-
<canvas id="heroChart" class="hero-canvas"></canvas>
|
|
87
|
-
</div>
|
|
88
|
-
</div>
|
|
89
|
-
|
|
90
|
-
<div class="card dist-card">
|
|
91
|
-
<div class="card-head" style="padding:0 0 10px">
|
|
92
|
-
<div>
|
|
93
|
-
<div class="card-title">Token mix</div>
|
|
94
|
-
<div class="card-sub">Volume by category \xB7 range</div>
|
|
95
|
-
</div>
|
|
96
|
-
</div>
|
|
97
|
-
<div id="distList"></div>
|
|
98
|
-
<div class="saved-chip">
|
|
99
|
-
<div class="l">Estimated savings from cache</div>
|
|
100
|
-
<div class="v mono" id="savedChip">$0</div>
|
|
101
|
-
</div>
|
|
102
|
-
</div>
|
|
103
|
-
</div>
|
|
104
|
-
|
|
105
|
-
<div class="two-col">
|
|
106
|
-
<div class="card">
|
|
107
|
-
<div class="card-head">
|
|
108
|
-
<div>
|
|
109
|
-
<div class="card-title">Top agents by spend</div>
|
|
110
|
-
<div class="card-sub">Ranked across the selected range</div>
|
|
111
|
-
</div>
|
|
112
|
-
</div>
|
|
113
|
-
<div class="agent-list" id="agentList"></div>
|
|
114
|
-
</div>
|
|
115
|
-
|
|
116
|
-
<div class="card">
|
|
117
|
-
<div class="card-head">
|
|
118
|
-
<div>
|
|
119
|
-
<div class="card-title">Cache efficiency</div>
|
|
120
|
-
<div class="card-sub">Hit rate and avoided input cost</div>
|
|
121
|
-
</div>
|
|
122
|
-
<span class="env-badge" id="cacheHealth" style="border-color:color-mix(in oklch,var(--accent) 40%,var(--border))">
|
|
123
|
-
<span style="color:var(--accent);font-weight:600">\u2014</span>
|
|
124
|
-
</span>
|
|
125
|
-
</div>
|
|
126
|
-
<div class="card-body">
|
|
127
|
-
<div class="gauge">
|
|
128
|
-
<div class="gauge-dial">
|
|
129
|
-
<svg width="140" height="140" viewBox="0 0 140 140">
|
|
130
|
-
<circle cx="70" cy="70" r="58" stroke="var(--surface-2)" stroke-width="12" fill="none"/>
|
|
131
|
-
<circle id="gaugeArc" cx="70" cy="70" r="58" stroke="var(--accent)" stroke-width="12" fill="none" stroke-linecap="round" stroke-dasharray="364" stroke-dashoffset="364" transform="rotate(-90 70 70)"/>
|
|
132
|
-
</svg>
|
|
133
|
-
<div class="gauge-readout">
|
|
134
|
-
<div class="num mono" id="gaugeNum">0%</div>
|
|
135
|
-
<div class="lbl">Hit rate</div>
|
|
136
|
-
</div>
|
|
137
|
-
</div>
|
|
138
|
-
<div class="cache-stats">
|
|
139
|
-
<div class="cache-stat"><div class="lbl">Cache reads</div><div class="val mono" id="cacheReads">0 <span class="sub">tok</span></div></div>
|
|
140
|
-
<div class="cache-stat"><div class="lbl">Cache writes</div><div class="val mono" id="cacheWrites">0 <span class="sub">tok</span></div></div>
|
|
141
|
-
<div class="cache-stat"><div class="lbl">Reuse multiplier</div><div class="val mono" id="reuseMult">0\xD7</div></div>
|
|
142
|
-
<div class="cache-stat"><div class="lbl">Avoided input</div><div class="val mono" id="avoidedInput">0</div></div>
|
|
143
|
-
</div>
|
|
144
|
-
</div>
|
|
145
|
-
</div>
|
|
146
|
-
</div>
|
|
147
|
-
</div>
|
|
148
|
-
|
|
149
|
-
<div class="card table-card">
|
|
150
|
-
<div class="table-head">
|
|
151
|
-
<div>
|
|
152
|
-
<div class="card-title">Daily detail</div>
|
|
153
|
-
<div class="card-sub">Per-day breakdown with agent attribution</div>
|
|
154
|
-
</div>
|
|
155
|
-
<div class="table-filters">
|
|
156
|
-
<button class="filter-chip active" data-filter="all">All days</button>
|
|
157
|
-
<button class="filter-chip" data-filter="weekdays">Weekdays</button>
|
|
158
|
-
<button class="filter-chip" data-filter="over10">> $10</button>
|
|
159
|
-
</div>
|
|
160
|
-
</div>
|
|
161
|
-
<div class="table-scroll">
|
|
162
|
-
<table class="data-table">
|
|
163
|
-
<thead>
|
|
164
|
-
<tr>
|
|
165
|
-
<th>Date</th>
|
|
166
|
-
<th class="num">Tasks</th>
|
|
167
|
-
<th>Mix</th>
|
|
168
|
-
<th class="num">Input</th>
|
|
169
|
-
<th class="num">Output</th>
|
|
170
|
-
<th class="num">Cache R</th>
|
|
171
|
-
<th class="num">Cache W</th>
|
|
172
|
-
<th class="num">Cost</th>
|
|
173
|
-
<th>Top agents</th>
|
|
174
|
-
</tr>
|
|
175
|
-
</thead>
|
|
176
|
-
<tbody id="dailyTbody"></tbody>
|
|
177
|
-
</table>
|
|
178
|
-
</div>
|
|
179
|
-
</div>
|
|
180
|
-
</div>
|
|
181
|
-
|
|
182
|
-
<script>${L}</script>
|
|
183
|
-
</body>
|
|
184
|
-
</html>`}var E=`
|
|
185
|
-
:root {
|
|
186
|
-
--bg: oklch(0.16 0.01 250);
|
|
187
|
-
--bg-elev: oklch(0.19 0.012 250);
|
|
188
|
-
--surface: oklch(0.21 0.013 250);
|
|
189
|
-
--surface-2: oklch(0.24 0.014 250);
|
|
190
|
-
--border: oklch(0.28 0.015 250);
|
|
191
|
-
--border-soft: oklch(0.24 0.012 250);
|
|
192
|
-
--text: oklch(0.96 0.005 250);
|
|
193
|
-
--text-2: oklch(0.78 0.01 250);
|
|
194
|
-
--text-3: oklch(0.58 0.012 250);
|
|
195
|
-
--text-4: oklch(0.42 0.012 250);
|
|
196
|
-
--accent: oklch(0.86 0.19 128);
|
|
197
|
-
--accent-dim: oklch(0.86 0.19 128 / 0.14);
|
|
198
|
-
--accent-ink: oklch(0.22 0.05 128);
|
|
199
|
-
--input-c: oklch(0.75 0.12 240);
|
|
200
|
-
--output-c: oklch(0.78 0.13 180);
|
|
201
|
-
--cache-r-c: oklch(0.72 0.10 290);
|
|
202
|
-
--cache-w-c: oklch(0.78 0.13 60);
|
|
203
|
-
--pos: oklch(0.82 0.16 150);
|
|
204
|
-
--neg: oklch(0.72 0.19 25);
|
|
205
|
-
--radius: 10px;
|
|
206
|
-
--radius-sm: 6px;
|
|
207
|
-
--radius-lg: 14px;
|
|
208
|
-
--shadow-sm: 0 1px 0 oklch(1 0 0 / 0.03) inset, 0 1px 2px oklch(0 0 0 / 0.3);
|
|
209
|
-
}
|
|
210
|
-
html[data-theme="midnight"] { --bg: oklch(0.13 0.015 265); }
|
|
211
|
-
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
212
|
-
html, body { background: var(--bg); }
|
|
213
|
-
body { color: var(--text); font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; font-size: 13px; line-height: 1.4; -webkit-font-smoothing: antialiased; min-height: 100vh; }
|
|
214
|
-
.mono { font-family: 'JetBrains Mono', ui-monospace, monospace; font-variant-numeric: tabular-nums; }
|
|
215
|
-
button, a { font-family: inherit; cursor: pointer; }
|
|
216
|
-
a { color: inherit; text-decoration: none; }
|
|
217
|
-
|
|
218
|
-
/* TOP BAR */
|
|
219
|
-
.topbar { position: sticky; top: 0; z-index: 10; display: flex; align-items: center; gap: 20px; padding: 12px 24px; background: color-mix(in oklch, var(--bg) 88%, transparent); backdrop-filter: blur(12px); border-bottom: 1px solid var(--border-soft); }
|
|
220
|
-
.brand { display: flex; align-items: center; gap: 10px; font-weight: 600; font-size: 13px; letter-spacing: -0.01em; }
|
|
221
|
-
.brand-mark { width: 22px; height: 22px; border-radius: 6px; background: linear-gradient(135deg, var(--accent) 0%, oklch(0.78 0.14 160) 100%); display: grid; place-items: center; color: var(--accent-ink); font-weight: 800; font-size: 11px; box-shadow: 0 0 0 1px oklch(1 0 0 / 0.08), 0 2px 8px oklch(0.86 0.19 128 / 0.25); }
|
|
222
|
-
.brand-path { color: var(--text-3); font-weight: 400; }
|
|
223
|
-
.brand-path b { color: var(--text-2); font-weight: 500; }
|
|
224
|
-
.slash { color: var(--text-4); margin: 0 4px; }
|
|
225
|
-
.env-badge { display: inline-flex; align-items: center; gap: 6px; padding: 3px 8px 3px 6px; border: 1px solid var(--border); border-radius: 999px; font-size: 11px; color: var(--text-2); background: var(--surface); }
|
|
226
|
-
.env-badge::before { content: ''; width: 6px; height: 6px; border-radius: 50%; background: var(--pos); box-shadow: 0 0 0 2px oklch(0.82 0.16 150 / 0.2); }
|
|
227
|
-
.topbar-right { margin-left: auto; display: flex; align-items: center; gap: 10px; }
|
|
228
|
-
.backlink { display: inline-flex; align-items: center; gap: 6px; padding: 5px 10px; font-size: 12px; color: var(--text-2); border: 1px solid var(--border); border-radius: 7px; transition: all .12s; }
|
|
229
|
-
.backlink:hover { color: var(--text); border-color: var(--text-4); background: var(--surface); }
|
|
230
|
-
.backlink svg { width: 13px; height: 13px; }
|
|
231
|
-
.theme-switch { display: inline-flex; padding: 2px; background: var(--surface); border: 1px solid var(--border); border-radius: 7px; }
|
|
232
|
-
.theme-switch button { background: transparent; border: 0; color: var(--text-3); padding: 3px 10px; font-size: 11px; font-weight: 500; border-radius: 5px; letter-spacing: 0.01em; transition: all .12s; }
|
|
233
|
-
.theme-switch button.active { background: var(--surface-2); color: var(--text); }
|
|
234
|
-
|
|
235
|
-
/* PAGE */
|
|
236
|
-
.page { max-width: 1440px; margin: 0 auto; padding: 24px 24px 80px; }
|
|
237
|
-
.page-header { display: flex; align-items: flex-end; justify-content: space-between; gap: 20px; flex-wrap: wrap; margin-bottom: 24px; }
|
|
238
|
-
.page-title h1 { font-size: 22px; font-weight: 600; letter-spacing: -0.015em; margin-bottom: 6px; }
|
|
239
|
-
.page-title p { font-size: 13px; color: var(--text-3); max-width: 540px; }
|
|
240
|
-
.page-title .live-dot { display: inline-block; width: 6px; height: 6px; border-radius: 50%; background: var(--accent); margin-right: 6px; vertical-align: middle; animation: pulse 2s ease-in-out infinite; }
|
|
241
|
-
@keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.4; } }
|
|
242
|
-
|
|
243
|
-
.controls { display: flex; gap: 8px; align-items: center; }
|
|
244
|
-
.segmented { display: inline-flex; padding: 2px; background: var(--surface); border: 1px solid var(--border); border-radius: 8px; }
|
|
245
|
-
.segmented button { background: transparent; border: 0; color: var(--text-3); padding: 5px 10px; font-size: 12px; font-weight: 500; border-radius: 6px; transition: all .12s; }
|
|
246
|
-
.segmented button.active { background: var(--surface-2); color: var(--text); box-shadow: 0 1px 0 oklch(1 0 0 / 0.04) inset, 0 1px 2px oklch(0 0 0 / 0.2); }
|
|
247
|
-
.segmented button:hover:not(.active) { color: var(--text); }
|
|
248
|
-
.date-picker { display: inline-flex; align-items: center; gap: 8px; background: var(--surface); border: 1px solid var(--border); border-radius: 8px; padding: 5px 10px; font-size: 12px; color: var(--text-2); }
|
|
249
|
-
.date-picker svg { width: 13px; height: 13px; color: var(--text-3); }
|
|
250
|
-
.btn-ghost { background: var(--surface); color: var(--text-2); border: 1px solid var(--border); padding: 5px 12px; font-size: 12px; font-weight: 500; border-radius: 8px; display: inline-flex; align-items: center; gap: 6px; }
|
|
251
|
-
.btn-ghost:hover { border-color: var(--text-4); color: var(--text); }
|
|
252
|
-
.btn-ghost svg { width: 13px; height: 13px; }
|
|
253
|
-
|
|
254
|
-
/* KPI ROW */
|
|
255
|
-
.kpi-row { display: grid; grid-template-columns: repeat(6, 1fr); gap: 0; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg); overflow: hidden; margin-bottom: 20px; box-shadow: var(--shadow-sm); }
|
|
256
|
-
.kpi { padding: 18px 20px; border-right: 1px solid var(--border-soft); display: flex; flex-direction: column; gap: 8px; position: relative; min-width: 0; }
|
|
257
|
-
.kpi:last-child { border-right: 0; }
|
|
258
|
-
.kpi-label { font-size: 11px; color: var(--text-3); text-transform: uppercase; letter-spacing: 0.06em; font-weight: 500; }
|
|
259
|
-
.kpi-value { font-size: 26px; font-weight: 600; letter-spacing: -0.02em; line-height: 1.1; display: flex; align-items: baseline; gap: 4px; }
|
|
260
|
-
.kpi-value .unit { font-size: 13px; color: var(--text-3); font-weight: 500; }
|
|
261
|
-
.kpi-accent .kpi-value { color: var(--accent); }
|
|
262
|
-
.kpi-delta { font-size: 11px; display: inline-flex; align-items: center; gap: 3px; color: var(--text-3); font-weight: 500; }
|
|
263
|
-
.kpi-delta.up { color: var(--neg); }
|
|
264
|
-
.kpi-delta.down { color: var(--pos); }
|
|
265
|
-
.kpi-delta svg { width: 10px; height: 10px; }
|
|
266
|
-
.kpi-spark { height: 22px; margin-top: auto; display: flex; align-items: flex-end; gap: 1.5px; }
|
|
267
|
-
.kpi-spark .bar { flex: 1; background: var(--text-4); border-radius: 1px; min-height: 2px; opacity: 0.5; }
|
|
268
|
-
.kpi-accent .kpi-spark .bar { background: var(--accent); opacity: 0.7; }
|
|
269
|
-
.kpi-spark .bar:last-child { opacity: 1; background: var(--accent); }
|
|
270
|
-
|
|
271
|
-
/* CARD */
|
|
272
|
-
.card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg); box-shadow: var(--shadow-sm); display: flex; flex-direction: column; min-width: 0; }
|
|
273
|
-
.card-head { padding: 14px 20px 10px; display: flex; align-items: center; justify-content: space-between; gap: 12px; }
|
|
274
|
-
.card-title { font-size: 13px; font-weight: 600; letter-spacing: -0.005em; }
|
|
275
|
-
.card-sub { font-size: 12px; color: var(--text-3); margin-top: 2px; }
|
|
276
|
-
.card-head .card-meta { display: flex; align-items: center; gap: 10px; }
|
|
277
|
-
.card-body { padding: 4px 20px 18px; flex: 1; min-width: 0; }
|
|
278
|
-
.legend { display: flex; gap: 12px; flex-wrap: wrap; }
|
|
279
|
-
.legend-item { display: inline-flex; align-items: center; gap: 6px; font-size: 11px; color: var(--text-2); font-weight: 500; }
|
|
280
|
-
.legend-swatch { width: 8px; height: 8px; border-radius: 2px; }
|
|
281
|
-
|
|
282
|
-
/* HERO GRID */
|
|
283
|
-
.hero-grid { display: grid; grid-template-columns: 1fr 320px; gap: 16px; margin-bottom: 16px; }
|
|
284
|
-
@media (max-width: 1200px) { .hero-grid { grid-template-columns: 1fr; } }
|
|
285
|
-
.hero-chart-wrap { position: relative; }
|
|
286
|
-
.hero-canvas { width: 100%; height: 300px; display: block; }
|
|
287
|
-
.hero-overlay { position: absolute; top: 14px; left: 20px; display: flex; gap: 22px; pointer-events: none; }
|
|
288
|
-
.hero-stat .v { font-size: 22px; font-weight: 600; letter-spacing: -0.02em; color: var(--text); }
|
|
289
|
-
.hero-stat .l { font-size: 10px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-3); font-weight: 500; margin-top: 2px; }
|
|
290
|
-
|
|
291
|
-
/* DIST */
|
|
292
|
-
.dist-card { padding: 14px 18px 18px; }
|
|
293
|
-
.dist-row { display: grid; grid-template-columns: 80px 1fr 92px; align-items: center; gap: 10px; padding: 9px 0; border-bottom: 1px dashed var(--border-soft); }
|
|
294
|
-
.dist-row:last-child { border-bottom: 0; }
|
|
295
|
-
.dist-label { display: flex; align-items: center; gap: 7px; font-size: 12px; color: var(--text-2); font-weight: 500; }
|
|
296
|
-
.dist-dot { width: 8px; height: 8px; border-radius: 2px; }
|
|
297
|
-
.dist-bar { position: relative; height: 6px; background: var(--surface-2); border-radius: 3px; overflow: hidden; }
|
|
298
|
-
.dist-bar-fill { height: 100%; border-radius: 3px; transition: width .6s cubic-bezier(0.22,1,0.36,1); }
|
|
299
|
-
.dist-val { font-family: 'JetBrains Mono', monospace; font-size: 11px; color: var(--text-2); text-align: right; font-weight: 500; }
|
|
300
|
-
.saved-chip { margin-top: 16px; padding: 10px 12px; background: var(--accent-dim); border: 1px solid color-mix(in oklch, var(--accent) 35%, transparent); border-radius: 8px; display: flex; align-items: center; justify-content: space-between; }
|
|
301
|
-
.saved-chip .l { font-size: 11px; color: var(--text-2); }
|
|
302
|
-
.saved-chip .v { font-size: 14px; font-weight: 600; color: var(--accent); }
|
|
303
|
-
|
|
304
|
-
/* TWO COL */
|
|
305
|
-
.two-col { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-bottom: 16px; }
|
|
306
|
-
@media (max-width: 1100px) { .two-col { grid-template-columns: 1fr; } }
|
|
307
|
-
|
|
308
|
-
/* AGENT LEADERBOARD */
|
|
309
|
-
.agent-list { display: flex; flex-direction: column; }
|
|
310
|
-
.agent-row { display: grid; grid-template-columns: 22px 1fr 60px 90px 90px; align-items: center; gap: 12px; padding: 10px 20px; border-top: 1px solid var(--border-soft); transition: background .12s; }
|
|
311
|
-
.agent-row:hover { background: var(--bg-elev); }
|
|
312
|
-
.agent-rank { font-family: 'JetBrains Mono', monospace; font-size: 11px; color: var(--text-4); font-weight: 500; }
|
|
313
|
-
.agent-name-wrap { display: flex; align-items: center; gap: 10px; min-width: 0; }
|
|
314
|
-
.agent-ico { width: 24px; height: 24px; border-radius: 6px; display: grid; place-items: center; font-size: 11px; font-weight: 700; flex-shrink: 0; }
|
|
315
|
-
.agent-name { font-size: 12.5px; font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
316
|
-
.agent-model { font-size: 10.5px; color: var(--text-3); margin-top: 1px; }
|
|
317
|
-
.agent-tasks { font-family: 'JetBrains Mono', monospace; font-size: 11.5px; color: var(--text-2); text-align: right; }
|
|
318
|
-
.agent-bar-wrap { position: relative; height: 5px; background: var(--surface-2); border-radius: 2px; overflow: hidden; }
|
|
319
|
-
.agent-bar-fill { height: 100%; border-radius: 2px; transition: width .7s cubic-bezier(0.22,1,0.36,1); }
|
|
320
|
-
.agent-cost { font-family: 'JetBrains Mono', monospace; font-size: 12.5px; font-weight: 600; text-align: right; color: var(--text); }
|
|
321
|
-
.agent-cost .sub { display: block; font-size: 10px; color: var(--text-3); font-weight: 400; margin-top: 1px; }
|
|
322
|
-
|
|
323
|
-
/* GAUGE */
|
|
324
|
-
.gauge { display: grid; grid-template-columns: 140px 1fr; gap: 24px; align-items: center; padding: 6px 0 4px; }
|
|
325
|
-
.gauge-dial { position: relative; width: 140px; height: 140px; }
|
|
326
|
-
.gauge-readout { position: absolute; inset: 0; display: grid; place-items: center; text-align: center; }
|
|
327
|
-
.gauge-readout .num { font-size: 26px; font-weight: 600; letter-spacing: -0.02em; }
|
|
328
|
-
.gauge-readout .lbl { font-size: 10px; color: var(--text-3); letter-spacing: 0.06em; text-transform: uppercase; margin-top: 2px; }
|
|
329
|
-
.cache-stats { display: flex; flex-direction: column; gap: 10px; }
|
|
330
|
-
.cache-stat { display: flex; justify-content: space-between; align-items: baseline; border-bottom: 1px dashed var(--border-soft); padding-bottom: 8px; }
|
|
331
|
-
.cache-stat:last-child { border-bottom: 0; }
|
|
332
|
-
.cache-stat .lbl { font-size: 11px; color: var(--text-3); text-transform: uppercase; letter-spacing: 0.05em; }
|
|
333
|
-
.cache-stat .val { font-size: 14px; font-weight: 600; }
|
|
334
|
-
.cache-stat .val .sub { font-size: 10px; color: var(--text-3); margin-left: 4px; font-weight: 400; }
|
|
335
|
-
|
|
336
|
-
/* TABLE */
|
|
337
|
-
.table-card { margin-top: 16px; }
|
|
338
|
-
.table-head { display: flex; align-items: center; justify-content: space-between; padding: 14px 20px 12px; }
|
|
339
|
-
.table-filters { display: flex; gap: 6px; }
|
|
340
|
-
.filter-chip { background: transparent; border: 1px solid var(--border); color: var(--text-3); font-size: 11px; padding: 3px 10px; border-radius: 999px; transition: all .12s; }
|
|
341
|
-
.filter-chip:hover, .filter-chip.active { color: var(--text); background: var(--surface-2); border-color: var(--text-4); }
|
|
342
|
-
.table-scroll { overflow-x: auto; }
|
|
343
|
-
.data-table { width: 100%; border-collapse: collapse; font-size: 12.5px; }
|
|
344
|
-
.data-table thead th { text-align: left; padding: 8px 20px; background: var(--bg-elev); border-top: 1px solid var(--border-soft); border-bottom: 1px solid var(--border-soft); font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-3); font-weight: 500; white-space: nowrap; }
|
|
345
|
-
.data-table thead th.num, .data-table tbody td.num { text-align: right; font-family: 'JetBrains Mono', monospace; }
|
|
346
|
-
.data-table tbody td { padding: 10px 20px; border-bottom: 1px solid var(--border-soft); color: var(--text-2); vertical-align: middle; white-space: nowrap; }
|
|
347
|
-
.data-table tbody tr:hover td { background: var(--bg-elev); color: var(--text); }
|
|
348
|
-
.data-table tbody tr:last-child td { border-bottom: 0; }
|
|
349
|
-
.row-date { color: var(--text); font-weight: 500; }
|
|
350
|
-
.row-date .day { color: var(--text-3); font-weight: 400; margin-left: 6px; font-size: 11px; }
|
|
351
|
-
.row-cost { color: var(--accent); font-weight: 600; }
|
|
352
|
-
.minibar { display: inline-flex; gap: 1px; height: 12px; width: 80px; align-items: flex-end; }
|
|
353
|
-
.minibar span { flex: 1; border-radius: 1px; min-height: 1px; display: block; height: 100%; }
|
|
354
|
-
.agent-chips { display: inline-flex; gap: 4px; flex-wrap: wrap; }
|
|
355
|
-
.agent-chip { display: inline-flex; align-items: center; gap: 5px; padding: 2px 7px 2px 5px; background: var(--surface-2); border: 1px solid var(--border); border-radius: 999px; font-size: 10.5px; color: var(--text-2); }
|
|
356
|
-
.agent-chip .d { width: 6px; height: 6px; border-radius: 50%; }
|
|
357
|
-
.empty { padding: 40px 20px; text-align: center; color: var(--text-3); font-size: 13px; }
|
|
358
|
-
|
|
359
|
-
@media (max-width: 1024px) {
|
|
360
|
-
.kpi-row { grid-template-columns: repeat(3, 1fr); }
|
|
361
|
-
.kpi:nth-child(3) { border-right: 0; }
|
|
362
|
-
.kpi { border-bottom: 1px solid var(--border-soft); }
|
|
363
|
-
.kpi:nth-last-child(-n+3) { border-bottom: 0; }
|
|
364
|
-
}
|
|
365
|
-
@media (max-width: 680px) {
|
|
366
|
-
.kpi-row { grid-template-columns: repeat(2, 1fr); }
|
|
367
|
-
.kpi { border-right: 1px solid var(--border-soft); border-bottom: 1px solid var(--border-soft); }
|
|
368
|
-
.kpi:nth-child(2n) { border-right: 0; }
|
|
369
|
-
}
|
|
370
|
-
`,L=`
|
|
371
|
-
/* ---- state ---- */
|
|
372
|
-
let data = [];
|
|
373
|
-
let currentRange = 30;
|
|
374
|
-
let tableFilter = 'all';
|
|
375
|
-
|
|
376
|
-
/* ---- formatters ---- */
|
|
377
|
-
function fmtTok(n) {
|
|
378
|
-
if (n >= 1e9) return (n/1e9).toFixed(2) + 'B';
|
|
379
|
-
if (n >= 1e6) return (n/1e6).toFixed(2) + 'M';
|
|
380
|
-
if (n >= 1e3) return (n/1e3).toFixed(1) + 'K';
|
|
381
|
-
return String(Math.round(n));
|
|
382
|
-
}
|
|
383
|
-
function fmtCost(n, digits) {
|
|
384
|
-
if (digits === undefined) digits = 2;
|
|
385
|
-
if (!isFinite(n)) n = 0;
|
|
386
|
-
if (n >= 10000) return '$' + (n/1000).toFixed(1) + 'K';
|
|
387
|
-
if (n === 0) return '$0';
|
|
388
|
-
if (n < 0.01) return '$' + n.toFixed(4);
|
|
389
|
-
return '$' + n.toFixed(digits);
|
|
390
|
-
}
|
|
391
|
-
function fmtDate(iso) {
|
|
392
|
-
const d = new Date(iso + 'T00:00:00');
|
|
393
|
-
return d.toLocaleDateString(undefined, { month: 'short', day: 'numeric' });
|
|
394
|
-
}
|
|
395
|
-
function fmtDay(iso) {
|
|
396
|
-
const d = new Date(iso + 'T00:00:00');
|
|
397
|
-
return d.toLocaleDateString(undefined, { weekday: 'short' });
|
|
398
|
-
}
|
|
399
|
-
function isWeekday(iso) {
|
|
400
|
-
const d = new Date(iso + 'T00:00:00');
|
|
401
|
-
const day = d.getDay();
|
|
402
|
-
return day !== 0 && day !== 6;
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
/* deterministic color per agent id \u2014 hashed hue around the cool palette */
|
|
406
|
-
const AGENT_COLOR_CACHE = {};
|
|
407
|
-
function agentColor(id) {
|
|
408
|
-
if (AGENT_COLOR_CACHE[id]) return AGENT_COLOR_CACHE[id];
|
|
409
|
-
let h = 0;
|
|
410
|
-
for (let i = 0; i < id.length; i++) h = ((h << 5) - h + id.charCodeAt(i)) | 0;
|
|
411
|
-
const hue = Math.abs(h) % 360;
|
|
412
|
-
const c = 'oklch(0.74 0.13 ' + hue + ')';
|
|
413
|
-
AGENT_COLOR_CACHE[id] = c;
|
|
414
|
-
return c;
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
function getCss(name) {
|
|
418
|
-
return getComputedStyle(document.documentElement).getPropertyValue(name).trim();
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
/* ---- theme ---- */
|
|
422
|
-
(function initTheme() {
|
|
423
|
-
try {
|
|
424
|
-
const saved = localStorage.getItem('ax-usage-theme');
|
|
425
|
-
if (saved) document.documentElement.setAttribute('data-theme', saved);
|
|
426
|
-
} catch (e) {}
|
|
427
|
-
})();
|
|
428
|
-
document.querySelectorAll('.theme-switch button').forEach(b => {
|
|
429
|
-
b.addEventListener('click', () => {
|
|
430
|
-
const t = b.dataset.t;
|
|
431
|
-
document.documentElement.setAttribute('data-theme', t);
|
|
432
|
-
try { localStorage.setItem('ax-usage-theme', t); } catch (e) {}
|
|
433
|
-
document.querySelectorAll('.theme-switch button').forEach(x => x.classList.toggle('active', x === b));
|
|
434
|
-
});
|
|
435
|
-
const cur = document.documentElement.getAttribute('data-theme') || 'dark';
|
|
436
|
-
b.classList.toggle('active', b.dataset.t === cur);
|
|
437
|
-
});
|
|
438
|
-
|
|
439
|
-
/* back-to-dashboard: link to the same host at the main-dashboard port */
|
|
440
|
-
(function wireBack() {
|
|
441
|
-
const a = document.getElementById('backToMain');
|
|
442
|
-
if (a) a.href = window.location.protocol + '//' + window.location.hostname + ':4202/';
|
|
443
|
-
})();
|
|
444
|
-
|
|
445
|
-
/* ---- fetch ---- */
|
|
446
|
-
function rangeToParams() {
|
|
447
|
-
const params = new URLSearchParams();
|
|
448
|
-
if (currentRange > 0) {
|
|
449
|
-
const to = new Date();
|
|
450
|
-
const from = new Date(Date.now() - currentRange * 86400000);
|
|
451
|
-
params.set('from', from.toISOString().slice(0,10));
|
|
452
|
-
params.set('to', to.toISOString().slice(0,10));
|
|
453
|
-
}
|
|
454
|
-
return params;
|
|
455
|
-
}
|
|
456
|
-
async function load() {
|
|
457
|
-
const params = rangeToParams();
|
|
458
|
-
try {
|
|
459
|
-
const res = await fetch('/api/usage?' + params);
|
|
460
|
-
data = await res.json();
|
|
461
|
-
} catch (e) { data = []; }
|
|
462
|
-
render();
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
/* ---- range segmented + export ---- */
|
|
466
|
-
document.querySelectorAll('#rangeSeg button').forEach(b => {
|
|
467
|
-
b.addEventListener('click', () => {
|
|
468
|
-
document.querySelectorAll('#rangeSeg button').forEach(x => x.classList.remove('active'));
|
|
469
|
-
b.classList.add('active');
|
|
470
|
-
currentRange = parseInt(b.dataset.range, 10);
|
|
471
|
-
load();
|
|
472
|
-
});
|
|
473
|
-
});
|
|
474
|
-
document.querySelectorAll('.filter-chip').forEach(c => {
|
|
475
|
-
c.addEventListener('click', () => {
|
|
476
|
-
document.querySelectorAll('.filter-chip').forEach(x => x.classList.remove('active'));
|
|
477
|
-
c.classList.add('active');
|
|
478
|
-
tableFilter = c.dataset.filter;
|
|
479
|
-
renderTable();
|
|
480
|
-
});
|
|
481
|
-
});
|
|
482
|
-
document.getElementById('exportBtn').addEventListener('click', (e) => {
|
|
483
|
-
e.preventDefault();
|
|
484
|
-
const params = rangeToParams();
|
|
485
|
-
window.location.href = '/api/usage.csv?' + params;
|
|
486
|
-
});
|
|
487
|
-
|
|
488
|
-
/* ---- KPIs ---- */
|
|
489
|
-
function renderKPIs() {
|
|
490
|
-
const totalCost = data.reduce((s,d) => s + d.cost, 0);
|
|
491
|
-
const totalTasks = data.reduce((s,d) => s + d.tasks, 0);
|
|
492
|
-
const totalInput = data.reduce((s,d) => s + d.input, 0);
|
|
493
|
-
const totalOutput = data.reduce((s,d) => s + d.output, 0);
|
|
494
|
-
const totalCacheR = data.reduce((s,d) => s + d.cacheRead, 0);
|
|
495
|
-
const totalCacheW = data.reduce((s,d) => s + d.cacheCreate, 0);
|
|
496
|
-
const avgDaily = data.length ? totalCost / data.length : 0;
|
|
497
|
-
const hitRate = (totalCacheR + totalInput) > 0 ? (totalCacheR / (totalCacheR + totalInput) * 100) : 0;
|
|
498
|
-
const costPerTask = totalTasks > 0 ? totalCost / totalTasks : 0;
|
|
499
|
-
|
|
500
|
-
const L = data.length;
|
|
501
|
-
const last7 = data.slice(Math.max(0, L-7)).reduce((s,d)=>s+d.cost,0);
|
|
502
|
-
const prev7 = data.slice(Math.max(0, L-14), Math.max(0, L-7)).reduce((s,d)=>s+d.cost,0);
|
|
503
|
-
const wow = prev7 > 0 ? ((last7 - prev7) / prev7 * 100) : 0;
|
|
504
|
-
const taskLast = data.slice(Math.max(0, L-7)).reduce((s,d)=>s+d.tasks,0);
|
|
505
|
-
const taskPrev = data.slice(Math.max(0, L-14), Math.max(0, L-7)).reduce((s,d)=>s+d.tasks,0);
|
|
506
|
-
const taskDelta = taskPrev > 0 ? ((taskLast-taskPrev)/taskPrev*100) : 0;
|
|
507
|
-
|
|
508
|
-
const kpis = [
|
|
509
|
-
{ label: 'Total spend', value: fmtCost(totalCost), delta: wow, deltaLabel: 'vs prev 7d', sparks: data.map(d=>d.cost), accent: true },
|
|
510
|
-
{ label: 'Daily average', value: fmtCost(avgDaily), sub: '/ day', sparks: data.map(d=>d.cost) },
|
|
511
|
-
{ label: 'Tasks run', value: totalTasks.toLocaleString(), delta: taskDelta, deltaLabel: 'vs prev 7d', sparks: data.map(d=>d.tasks) },
|
|
512
|
-
{ label: 'Tokens processed', value: fmtTok(totalInput + totalOutput + totalCacheR + totalCacheW), sub: 'tok', sparks: data.map(d => d.input + d.output + d.cacheRead + d.cacheCreate) },
|
|
513
|
-
{ label: 'Cache hit rate', value: hitRate.toFixed(0) + '%', sparks: data.map(d => d.cacheRead / Math.max(1, d.cacheRead + d.input) * 100) },
|
|
514
|
-
{ label: 'Cost / task', value: fmtCost(costPerTask, 3), sparks: data.map(d => d.cost / Math.max(1, d.tasks)) },
|
|
515
|
-
];
|
|
516
|
-
|
|
517
|
-
document.getElementById('kpis').innerHTML = kpis.map(k => {
|
|
518
|
-
const max = Math.max.apply(null, k.sparks.concat([0.0001]));
|
|
519
|
-
const bars = k.sparks.map(v => '<div class="bar" style="height:' + Math.max(2, (v/max)*100) + '%"></div>').join('');
|
|
520
|
-
let deltaHtml = '';
|
|
521
|
-
if (k.delta !== undefined) {
|
|
522
|
-
const up = k.delta > 0;
|
|
523
|
-
const cls = up ? 'up' : 'down';
|
|
524
|
-
const arrow = up
|
|
525
|
-
? '<svg viewBox="0 0 10 10" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M2 7l3-3 3 3M5 4v5"/></svg>'
|
|
526
|
-
: '<svg viewBox="0 0 10 10" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M2 3l3 3 3-3M5 6V1"/></svg>';
|
|
527
|
-
deltaHtml = '<div class="kpi-delta ' + cls + '">' + arrow + Math.abs(k.delta).toFixed(1) + '% ' + k.deltaLabel + '</div>';
|
|
528
|
-
}
|
|
529
|
-
const sub = k.sub ? '<span class="unit">' + k.sub + '</span>' : '';
|
|
530
|
-
return '<div class="kpi ' + (k.accent ? 'kpi-accent' : '') + '">' +
|
|
531
|
-
'<div class="kpi-label">' + k.label + '</div>' +
|
|
532
|
-
'<div class="kpi-value mono">' + k.value + sub + '</div>' +
|
|
533
|
-
deltaHtml +
|
|
534
|
-
'<div class="kpi-spark">' + bars + '</div>' +
|
|
535
|
-
'</div>';
|
|
536
|
-
}).join('');
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
/* ---- Hero stacked bar chart ---- */
|
|
540
|
-
function setupCanvas(id, hOverride) {
|
|
541
|
-
const canvas = document.getElementById(id);
|
|
542
|
-
const dpr = window.devicePixelRatio || 1;
|
|
543
|
-
const wrap = canvas.parentElement;
|
|
544
|
-
const rect = wrap.getBoundingClientRect();
|
|
545
|
-
const w = rect.width;
|
|
546
|
-
const h = hOverride || canvas.clientHeight || 260;
|
|
547
|
-
canvas.width = Math.max(1, Math.round(w * dpr));
|
|
548
|
-
canvas.height = Math.max(1, Math.round(h * dpr));
|
|
549
|
-
canvas.style.width = w + 'px';
|
|
550
|
-
canvas.style.height = h + 'px';
|
|
551
|
-
const ctx = canvas.getContext('2d');
|
|
552
|
-
ctx.scale(dpr, dpr);
|
|
553
|
-
ctx.clearRect(0, 0, w, h);
|
|
554
|
-
return { ctx, w, h };
|
|
555
|
-
}
|
|
556
|
-
function roundRect(ctx, x, y, w, h, r) {
|
|
557
|
-
if (w <= 0 || h <= 0) return;
|
|
558
|
-
r = Math.min(r, w/2, h/2);
|
|
559
|
-
ctx.beginPath();
|
|
560
|
-
ctx.moveTo(x+r, y); ctx.lineTo(x+w-r, y);
|
|
561
|
-
ctx.quadraticCurveTo(x+w, y, x+w, y+r);
|
|
562
|
-
ctx.lineTo(x+w, y+h); ctx.lineTo(x, y+h);
|
|
563
|
-
ctx.lineTo(x, y+r);
|
|
564
|
-
ctx.quadraticCurveTo(x, y, x+r, y);
|
|
565
|
-
ctx.closePath();
|
|
566
|
-
}
|
|
567
|
-
function renderHero() {
|
|
568
|
-
const { ctx, w, h } = setupCanvas('heroChart', 300);
|
|
569
|
-
if (!data.length) {
|
|
570
|
-
ctx.fillStyle = getCss('--text-3');
|
|
571
|
-
ctx.font = '12px Inter';
|
|
572
|
-
ctx.textAlign = 'center';
|
|
573
|
-
ctx.fillText('No usage data in this range', w/2, h/2);
|
|
574
|
-
document.getElementById('heroTotalCost').textContent = '$0';
|
|
575
|
-
document.getElementById('heroPeak').textContent = '$0';
|
|
576
|
-
document.getElementById('heroAvg').textContent = '$0';
|
|
577
|
-
return;
|
|
578
|
-
}
|
|
579
|
-
const pad = { t: 70, r: 16, b: 28, l: 48 };
|
|
580
|
-
const cw = w - pad.l - pad.r;
|
|
581
|
-
const ch = h - pad.t - pad.b;
|
|
582
|
-
|
|
583
|
-
const series = data.map(d => {
|
|
584
|
-
const tot = d.input + d.output + d.cacheRead + d.cacheCreate;
|
|
585
|
-
if (!tot) return { input:0, output:0, cacheR:0, cacheW:0, total: d.cost };
|
|
586
|
-
return {
|
|
587
|
-
input: d.cost * (d.input / tot),
|
|
588
|
-
output: d.cost * (d.output / tot),
|
|
589
|
-
cacheR: d.cost * (d.cacheRead / tot),
|
|
590
|
-
cacheW: d.cost * (d.cacheCreate / tot),
|
|
591
|
-
total: d.cost,
|
|
592
|
-
};
|
|
593
|
-
});
|
|
594
|
-
const maxTot = Math.max.apply(null, series.map(s=>s.total).concat([0.001]));
|
|
595
|
-
|
|
596
|
-
ctx.font = '10.5px JetBrains Mono, monospace';
|
|
597
|
-
ctx.textAlign = 'right';
|
|
598
|
-
ctx.textBaseline = 'middle';
|
|
599
|
-
for (let i = 0; i <= 4; i++) {
|
|
600
|
-
const v = (maxTot / 4) * i;
|
|
601
|
-
const y = pad.t + ch - (i / 4) * ch;
|
|
602
|
-
ctx.strokeStyle = getCss('--border-soft');
|
|
603
|
-
ctx.lineWidth = 1;
|
|
604
|
-
ctx.beginPath(); ctx.moveTo(pad.l, y + 0.5); ctx.lineTo(pad.l + cw, y + 0.5); ctx.stroke();
|
|
605
|
-
ctx.fillStyle = getCss('--text-4');
|
|
606
|
-
ctx.fillText(fmtCost(v, 0), pad.l - 8, y);
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
ctx.fillStyle = getCss('--text-3');
|
|
610
|
-
ctx.textAlign = 'center';
|
|
611
|
-
ctx.textBaseline = 'top';
|
|
612
|
-
ctx.font = '10.5px Inter';
|
|
613
|
-
const nLabels = Math.min(7, data.length);
|
|
614
|
-
for (let i = 0; i < nLabels; i++) {
|
|
615
|
-
const idx = Math.round((i / (nLabels - 1 || 1)) * (data.length - 1));
|
|
616
|
-
const x = pad.l + (idx / Math.max(1, data.length - 1)) * cw;
|
|
617
|
-
ctx.fillText(fmtDate(data[idx].date), x, pad.t + ch + 10);
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
const catOrder = [
|
|
621
|
-
{ k: 'input', color: getCss('--input-c') },
|
|
622
|
-
{ k: 'output', color: getCss('--output-c') },
|
|
623
|
-
{ k: 'cacheR', color: getCss('--cache-r-c') },
|
|
624
|
-
{ k: 'cacheW', color: getCss('--cache-w-c') },
|
|
625
|
-
];
|
|
626
|
-
const barW = Math.max(2, (cw / data.length) * 0.7);
|
|
627
|
-
const gap = cw / data.length;
|
|
628
|
-
|
|
629
|
-
data.forEach((d, i) => {
|
|
630
|
-
const s = series[i];
|
|
631
|
-
const x = pad.l + i * gap + (gap - barW) / 2;
|
|
632
|
-
let yOff = 0;
|
|
633
|
-
catOrder.forEach((c, ci) => {
|
|
634
|
-
const v = s[c.k];
|
|
635
|
-
const barH = (v / maxTot) * ch;
|
|
636
|
-
const y = pad.t + ch - yOff - barH;
|
|
637
|
-
ctx.fillStyle = c.color;
|
|
638
|
-
const radius = ci === catOrder.length - 1 ? 2 : 0;
|
|
639
|
-
roundRect(ctx, x, y, barW, barH + 0.5, radius);
|
|
640
|
-
ctx.fill();
|
|
641
|
-
yOff += barH;
|
|
642
|
-
});
|
|
643
|
-
});
|
|
644
|
-
|
|
645
|
-
const total = series.reduce((s, p) => s + p.total, 0);
|
|
646
|
-
const peak = Math.max.apply(null, series.map(p => p.total));
|
|
647
|
-
const avg = total / Math.max(1, series.length);
|
|
648
|
-
document.getElementById('heroTotalCost').textContent = fmtCost(total);
|
|
649
|
-
document.getElementById('heroPeak').textContent = fmtCost(peak);
|
|
650
|
-
document.getElementById('heroAvg').textContent = fmtCost(avg);
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
/* ---- Distribution + savings chip ---- */
|
|
654
|
-
function renderDistribution() {
|
|
655
|
-
const tot = { input:0, output:0, cacheRead:0, cacheCreate:0 };
|
|
656
|
-
data.forEach(d => { tot.input += d.input; tot.output += d.output; tot.cacheRead += d.cacheRead; tot.cacheCreate += d.cacheCreate; });
|
|
657
|
-
const grand = tot.input + tot.output + tot.cacheRead + tot.cacheCreate;
|
|
658
|
-
const items = [
|
|
659
|
-
{ lbl: 'Input', v: tot.input, c: 'var(--input-c)' },
|
|
660
|
-
{ lbl: 'Output', v: tot.output, c: 'var(--output-c)' },
|
|
661
|
-
{ lbl: 'Cache read', v: tot.cacheRead, c: 'var(--cache-r-c)' },
|
|
662
|
-
{ lbl: 'Cache write', v: tot.cacheCreate, c: 'var(--cache-w-c)' },
|
|
663
|
-
];
|
|
664
|
-
const max = Math.max.apply(null, items.map(i => i.v).concat([1]));
|
|
665
|
-
document.getElementById('distList').innerHTML = items.map(it => {
|
|
666
|
-
const pct = grand > 0 ? (it.v / grand * 100) : 0;
|
|
667
|
-
const barPct = (it.v / max) * 100;
|
|
668
|
-
return '<div class="dist-row">' +
|
|
669
|
-
'<div class="dist-label"><span class="dist-dot" style="background:' + it.c + '"></span>' + it.lbl + '</div>' +
|
|
670
|
-
'<div class="dist-bar"><div class="dist-bar-fill" style="width:' + barPct + '%;background:' + it.c + '"></div></div>' +
|
|
671
|
-
'<div class="dist-val">' + fmtTok(it.v) + ' \xB7 ' + pct.toFixed(0) + '%</div>' +
|
|
672
|
-
'</div>';
|
|
673
|
-
}).join('');
|
|
674
|
-
// rough savings: cache-read tokens priced at ~10% of input. Matches mockup formula.
|
|
675
|
-
const savedCost = (tot.cacheRead * 0.9) / 1e6 * 3;
|
|
676
|
-
document.getElementById('savedChip').textContent = fmtCost(savedCost);
|
|
677
|
-
}
|
|
678
|
-
|
|
679
|
-
/* ---- Agent leaderboard ---- */
|
|
680
|
-
function renderAgents() {
|
|
681
|
-
const totals = {};
|
|
682
|
-
data.forEach(d => {
|
|
683
|
-
for (const id in d.agents) {
|
|
684
|
-
const a = d.agents[id];
|
|
685
|
-
if (!totals[id]) totals[id] = { cost:0, tasks:0, input:0, output:0, cacheR:0, cacheW:0, model: a.model || '' };
|
|
686
|
-
totals[id].cost += a.cost; totals[id].tasks += a.tasks;
|
|
687
|
-
totals[id].input += a.input; totals[id].output += a.output;
|
|
688
|
-
totals[id].cacheR += a.cacheRead; totals[id].cacheW += a.cacheCreate;
|
|
689
|
-
if (a.model) totals[id].model = a.model;
|
|
690
|
-
}
|
|
691
|
-
});
|
|
692
|
-
const items = Object.keys(totals).map(id => [id, totals[id]]).sort((a,b) => b[1].cost - a[1].cost).slice(0, 12);
|
|
693
|
-
const max = items[0] ? items[0][1].cost : 1;
|
|
694
|
-
const container = document.getElementById('agentList');
|
|
695
|
-
if (!items.length) { container.innerHTML = '<div class="empty">No agent activity</div>'; return; }
|
|
696
|
-
container.innerHTML = items.map((pair, i) => {
|
|
697
|
-
const id = pair[0], a = pair[1];
|
|
698
|
-
const c = agentColor(id);
|
|
699
|
-
const initials = id.split('-').map(s => s[0]).join('').slice(0,2).toUpperCase();
|
|
700
|
-
const iconBg = 'color-mix(in oklch, ' + c + ' 22%, var(--surface-2))';
|
|
701
|
-
const barPct = (a.cost / max) * 100;
|
|
702
|
-
return '<div class="agent-row">' +
|
|
703
|
-
'<div class="agent-rank">' + String(i+1).padStart(2,'0') + '</div>' +
|
|
704
|
-
'<div class="agent-name-wrap">' +
|
|
705
|
-
'<div class="agent-ico" style="background:' + iconBg + ';color:' + c + '">' + initials + '</div>' +
|
|
706
|
-
'<div style="min-width:0">' +
|
|
707
|
-
'<div class="agent-name">' + id + '</div>' +
|
|
708
|
-
(a.model ? '<div class="agent-model">' + a.model + '</div>' : '') +
|
|
709
|
-
'</div>' +
|
|
710
|
-
'</div>' +
|
|
711
|
-
'<div class="agent-tasks">' + a.tasks.toLocaleString() + '</div>' +
|
|
712
|
-
'<div class="agent-bar-wrap"><div class="agent-bar-fill" style="width:' + barPct + '%;background:' + c + '"></div></div>' +
|
|
713
|
-
'<div class="agent-cost">' + fmtCost(a.cost) + '<span class="sub">' + fmtTok(a.input + a.output + a.cacheR + a.cacheW) + ' tok</span></div>' +
|
|
714
|
-
'</div>';
|
|
715
|
-
}).join('');
|
|
716
|
-
}
|
|
717
|
-
|
|
718
|
-
/* ---- Cache gauge ---- */
|
|
719
|
-
function renderCache() {
|
|
720
|
-
const tot = data.reduce((s,d) => ({ r: s.r + d.cacheRead, w: s.w + d.cacheCreate, i: s.i + d.input }), { r:0, w:0, i:0 });
|
|
721
|
-
const hit = (tot.r + tot.i) > 0 ? tot.r / (tot.r + tot.i) * 100 : 0;
|
|
722
|
-
const circ = 2 * Math.PI * 58;
|
|
723
|
-
const offset = circ - (hit / 100) * circ;
|
|
724
|
-
const arc = document.getElementById('gaugeArc');
|
|
725
|
-
arc.style.strokeDasharray = circ.toFixed(1);
|
|
726
|
-
arc.style.strokeDashoffset = circ.toFixed(1);
|
|
727
|
-
requestAnimationFrame(() => {
|
|
728
|
-
arc.style.transition = 'stroke-dashoffset 1s cubic-bezier(0.22,1,0.36,1)';
|
|
729
|
-
arc.style.strokeDashoffset = offset.toFixed(1);
|
|
730
|
-
});
|
|
731
|
-
document.getElementById('gaugeNum').textContent = hit.toFixed(0) + '%';
|
|
732
|
-
document.getElementById('cacheReads').innerHTML = fmtTok(tot.r) + ' <span class="sub">tok</span>';
|
|
733
|
-
document.getElementById('cacheWrites').innerHTML = fmtTok(tot.w) + ' <span class="sub">tok</span>';
|
|
734
|
-
const mult = tot.w > 0 ? (tot.r / tot.w) : 0;
|
|
735
|
-
document.getElementById('reuseMult').textContent = mult.toFixed(1) + '\xD7';
|
|
736
|
-
document.getElementById('avoidedInput').textContent = fmtTok(tot.r);
|
|
737
|
-
const healthLabel = hit >= 60 ? 'Healthy' : hit >= 30 ? 'Warming up' : 'Cold';
|
|
738
|
-
document.getElementById('cacheHealth').innerHTML = '<span style="color:var(--accent);font-weight:600">' + healthLabel + '</span>';
|
|
739
|
-
}
|
|
740
|
-
|
|
741
|
-
/* ---- Daily table ---- */
|
|
742
|
-
function renderTable() {
|
|
743
|
-
const tbody = document.getElementById('dailyTbody');
|
|
744
|
-
let rows = data.slice().reverse();
|
|
745
|
-
if (tableFilter === 'weekdays') rows = rows.filter(r => isWeekday(r.date));
|
|
746
|
-
else if (tableFilter === 'over10') rows = rows.filter(r => r.cost > 10);
|
|
747
|
-
if (!rows.length) { tbody.innerHTML = '<tr><td colspan="9" class="empty">No rows match this filter.</td></tr>'; return; }
|
|
748
|
-
tbody.innerHTML = rows.map(d => {
|
|
749
|
-
const tot = d.input + d.output + d.cacheRead + d.cacheCreate || 1;
|
|
750
|
-
const mix = [
|
|
751
|
-
{ v: d.input, c: 'var(--input-c)' },
|
|
752
|
-
{ v: d.output, c: 'var(--output-c)' },
|
|
753
|
-
{ v: d.cacheRead, c: 'var(--cache-r-c)' },
|
|
754
|
-
{ v: d.cacheCreate, c: 'var(--cache-w-c)' },
|
|
755
|
-
];
|
|
756
|
-
const mixHtml = mix.map(m => '<span style="flex:' + (m.v/tot) + ';background:' + m.c + '"></span>').join('');
|
|
757
|
-
const topAgents = Object.keys(d.agents).map(id => [id, d.agents[id]]).sort((a,b) => b[1].cost - a[1].cost).slice(0, 3);
|
|
758
|
-
const chips = topAgents.map(pair => {
|
|
759
|
-
const id = pair[0];
|
|
760
|
-
return '<span class="agent-chip"><span class="d" style="background:' + agentColor(id) + '"></span>' + id + '</span>';
|
|
761
|
-
}).join('');
|
|
762
|
-
return '<tr>' +
|
|
763
|
-
'<td class="row-date">' + fmtDate(d.date) + '<span class="day">' + fmtDay(d.date) + '</span></td>' +
|
|
764
|
-
'<td class="num">' + d.tasks.toLocaleString() + '</td>' +
|
|
765
|
-
'<td><div class="minibar">' + mixHtml + '</div></td>' +
|
|
766
|
-
'<td class="num">' + fmtTok(d.input) + '</td>' +
|
|
767
|
-
'<td class="num">' + fmtTok(d.output) + '</td>' +
|
|
768
|
-
'<td class="num">' + fmtTok(d.cacheRead) + '</td>' +
|
|
769
|
-
'<td class="num">' + fmtTok(d.cacheCreate) + '</td>' +
|
|
770
|
-
'<td class="num row-cost">' + fmtCost(d.cost) + '</td>' +
|
|
771
|
-
'<td>' + chips + '</td>' +
|
|
772
|
-
'</tr>';
|
|
773
|
-
}).join('');
|
|
774
|
-
}
|
|
775
|
-
|
|
776
|
-
/* ---- meta chrome ---- */
|
|
777
|
-
function updateRangeLabel() {
|
|
778
|
-
const el = document.getElementById('rangeLabel');
|
|
779
|
-
const li = document.getElementById('lastIngest');
|
|
780
|
-
const badge = document.getElementById('envBadge');
|
|
781
|
-
if (badge) badge.textContent = window.location.hostname;
|
|
782
|
-
if (!data.length) { el.textContent = 'No data'; if (li) li.textContent = '\u2014'; return; }
|
|
783
|
-
el.textContent = fmtDate(data[0].date) + ' \u2013 ' + fmtDate(data[data.length - 1].date);
|
|
784
|
-
if (li) li.textContent = data[data.length - 1].date;
|
|
785
|
-
}
|
|
786
|
-
|
|
787
|
-
/* ---- main render ---- */
|
|
788
|
-
function render() {
|
|
789
|
-
updateRangeLabel();
|
|
790
|
-
renderKPIs();
|
|
791
|
-
renderHero();
|
|
792
|
-
renderDistribution();
|
|
793
|
-
renderAgents();
|
|
794
|
-
renderCache();
|
|
795
|
-
renderTable();
|
|
796
|
-
}
|
|
797
|
-
|
|
798
|
-
window.addEventListener('resize', () => { renderHero(); });
|
|
799
|
-
|
|
800
|
-
/* initial */
|
|
801
|
-
load();
|
|
802
|
-
`,z=R;export{z as default,R as startUsageDashboard};
|
|
803
|
-
//# sourceMappingURL=usage-dashboard-AXPXB2KP.js.map
|