Skip to content
This repository was archived by the owner on May 8, 2026. It is now read-only.

Commit 56a5c34

Browse files
author
Claude Usage Tracker
committed
Add device breakdown with toggle and total processed display
1 parent c8c8cf2 commit 56a5c34

2 files changed

Lines changed: 324 additions & 2 deletions

File tree

create_index.py

Lines changed: 162 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,28 @@
4545
.stat-card .label { font-size: 0.9em; opacity: 0.9; margin-bottom: 5px; }
4646
.stat-card .value { font-size: 2em; font-weight: bold; }
4747
.progress-section { margin: 40px 0; }
48+
.total-processed {
49+
text-align: center;
50+
margin-bottom: 20px;
51+
padding: 15px;
52+
background: #f0f0f0;
53+
border-radius: 10px;
54+
}
55+
.total-processed .label {
56+
color: #666;
57+
font-size: 0.9em;
58+
margin-bottom: 5px;
59+
}
60+
.total-processed .value {
61+
font-size: 1.8em;
62+
font-weight: bold;
63+
color: #667eea;
64+
}
65+
.total-processed .exact {
66+
color: #999;
67+
font-size: 0.9em;
68+
margin-top: 5px;
69+
}
4870
.progress-header {
4971
display: flex;
5072
justify-content: space-between;
@@ -90,6 +112,51 @@
90112
.projection.warning { background: #f8d7da; border-left-color: #dc3545; }
91113
.projection-title { font-size: 1.2em; font-weight: bold; margin-bottom: 10px; color: #333; }
92114
.loading { text-align: center; padding: 40px; color: #666; }
115+
.devices-section { margin-top: 40px; }
116+
.devices-header {
117+
font-size: 1.3em;
118+
font-weight: bold;
119+
color: #333;
120+
margin-bottom: 15px;
121+
cursor: pointer;
122+
display: flex;
123+
align-items: center;
124+
gap: 10px;
125+
}
126+
.devices-header:hover { color: #667eea; }
127+
.toggle-icon { transition: transform 0.3s; }
128+
.toggle-icon.open { transform: rotate(90deg); }
129+
.devices-list { display: none; }
130+
.devices-list.open { display: block; }
131+
.device-card {
132+
background: #f9f9f9;
133+
padding: 20px;
134+
border-radius: 10px;
135+
margin-bottom: 15px;
136+
border-left: 4px solid #667eea;
137+
}
138+
.device-header {
139+
display: flex;
140+
justify-content: space-between;
141+
align-items: center;
142+
margin-bottom: 10px;
143+
}
144+
.device-name {
145+
font-size: 1.2em;
146+
font-weight: bold;
147+
color: #333;
148+
}
149+
.device-cost {
150+
color: #667eea;
151+
font-size: 1.1em;
152+
font-weight: bold;
153+
}
154+
.device-stats {
155+
color: #666;
156+
font-size: 0.9em;
157+
line-height: 1.6;
158+
}
159+
.device-stats strong { color: #333; }
93160
</style>
94161
</head>
95162
<body>
@@ -106,6 +173,11 @@
106173
<div class="stat-card"><div class="label">Total Cost</div><div class="value" id="totalCost">-</div></div>
107174
</div>
108175
<div class="progress-section">
176+
<div class="total-processed">
177+
<div class="label">💰 TOTAL PROCESSED</div>
178+
<div class="value" id="totalProcessed">0M</div>
179+
<div class="exact" id="totalProcessedExact">0 tokens</div>
180+
</div>
109181
<div class="progress-header">
110182
<span class="progress-label">🎯 Goal Progress</span>
111183
<span class="progress-percentage" id="percentage">0%</span>
@@ -123,10 +195,99 @@
123195
<div class="projection-title">🔮 Projection</div>
124196
<div id="projectionContent"></div>
125197
</div>
198+
<div class="devices-section">
199+
<div class="devices-header" onclick="toggleDevices()">
200+
<span class="toggle-icon" id="toggleIcon">▶</span>
201+
<span>📱 Device Breakdown</span>
202+
</div>
203+
<div class="devices-list" id="devicesList"></div>
204+
</div>
126205
</div>
127206
</div>
128207
<script>
129-
const GOAL=100000000;function fmt(t){return(t/1000000).toFixed(2)+"M"}async function load(){try{const devs=['yangpyungpc','bohees-macbook-air-local'];const data=(await Promise.all(devs.map(async d=>{const r=await fetch(`data/${d}.json`);return r.ok?await r.json():null}))).filter(d=>d);const u={i:0,o:0,c:0,s:0};let cost=0;data.forEach(d=>{u.i+=d.usage.input_tokens||0;u.o+=d.usage.output_tokens||0;u.c+=d.usage.cache_creation_tokens||0;u.s+=d.usage.total_sessions||0;cost+=d.estimated_cost||0});const tot=u.i+u.o+u.c,rem=GOAL-tot,pct=tot/GOAL*100;document.getElementById('totalDevices').textContent=data.length;document.getElementById('totalSessions').textContent=u.s.toLocaleString();document.getElementById('totalCost').textContent='$'+cost.toFixed(2);document.getElementById('percentage').textContent=pct.toFixed(1)+'%';document.getElementById('current').textContent=fmt(tot);document.getElementById('remaining').textContent=fmt(rem);setTimeout(()=>document.getElementById('progressBar').style.width=Math.min(pct,100)+'%',100);const now=new Date(),dl=new Date('2025-12-31T23:59:59Z'),ps=new Date('2025-10-01T00:00:00Z');const dr=Math.ceil((dl-now)/86400000);if(dr>0){const de=Math.max(1,Math.ceil((now-ps)/86400000)),avg=tot/de;const td=Math.ceil((dl-ps)/86400000),proj=avg*td,dt=rem/dr;const p=document.getElementById('projection');p.style.display='block';let h=`<p><strong>Days remaining:</strong> ${dr}</p>`;h+=`<p><strong>Daily target:</strong> ${fmt(dt)}</p>`;h+=`<p><strong>Current pace:</strong> ${fmt(avg)}/day</p>`;h+=`<p><strong>Projected total:</strong> ${fmt(proj)}</p>`;if(proj>=GOAL){p.className='projection success';h+=`<p style="margin-top:10px;font-weight:bold;">✅ ON TRACK! (+${fmt(proj-GOAL)})</p>`}else{p.className='projection warning';h+=`<p style="margin-top:10px;font-weight:bold;">⚠️ BEHIND PACE (-${fmt(GOAL-proj)})<br>Need +${fmt(dt-avg)}/day</p>`}document.getElementById('projectionContent').innerHTML=h}document.getElementById('loading').style.display='none';document.getElementById('content').style.display='block'}catch(e){document.getElementById('loading').innerHTML='<p>❌ Error loading data</p>'}}load();setInterval(load,300000);
208+
const GOAL=100000000;
209+
function fmt(t){return(t/1000000).toFixed(2)+"M"}
210+
function toggleDevices(){
211+
const list=document.getElementById('devicesList');
212+
const icon=document.getElementById('toggleIcon');
213+
list.classList.toggle('open');
214+
icon.classList.toggle('open');
215+
icon.textContent=list.classList.contains('open')?'▼':'▶';
216+
}
217+
async function load(){
218+
try{
219+
const devs=['yangpyungpc','bohees-macbook-air-local'];
220+
const data=(await Promise.all(devs.map(async d=>{
221+
const r=await fetch(`data/${d}.json`);
222+
return r.ok?await r.json():null
223+
}))).filter(d=>d);
224+
const u={i:0,o:0,c:0,s:0};
225+
let cost=0;
226+
data.forEach(d=>{
227+
u.i+=d.usage.input_tokens||0;
228+
u.o+=d.usage.output_tokens||0;
229+
u.c+=d.usage.cache_creation_tokens||0;
230+
u.s+=d.usage.total_sessions||0;
231+
cost+=d.estimated_cost||0
232+
});
233+
const tot=u.i+u.o+u.c,rem=GOAL-tot,pct=tot/GOAL*100;
234+
document.getElementById('totalDevices').textContent=data.length;
235+
document.getElementById('totalSessions').textContent=u.s.toLocaleString();
236+
document.getElementById('totalCost').textContent='$'+cost.toFixed(2);
237+
document.getElementById('totalProcessed').textContent=fmt(tot);
238+
document.getElementById('totalProcessedExact').textContent=tot.toLocaleString()+' tokens';
239+
document.getElementById('percentage').textContent=pct.toFixed(1)+'%';
240+
document.getElementById('current').textContent=fmt(tot);
241+
document.getElementById('remaining').textContent=fmt(rem);
242+
setTimeout(()=>document.getElementById('progressBar').style.width=Math.min(pct,100)+'%',100);
243+
const now=new Date(),dl=new Date('2025-12-31T23:59:59Z'),ps=new Date('2025-10-01T00:00:00Z');
244+
const dr=Math.ceil((dl-now)/86400000);
245+
if(dr>0){
246+
const de=Math.max(1,Math.ceil((now-ps)/86400000)),avg=tot/de;
247+
const td=Math.ceil((dl-ps)/86400000),proj=avg*td,dt=rem/dr;
248+
const p=document.getElementById('projection');
249+
p.style.display='block';
250+
let h=`<p><strong>Days remaining:</strong> ${dr}</p>`;
251+
h+=`<p><strong>Daily target:</strong> ${fmt(dt)}</p>`;
252+
h+=`<p><strong>Current pace:</strong> ${fmt(avg)}/day</p>`;
253+
h+=`<p><strong>Projected total:</strong> ${fmt(proj)}</p>`;
254+
if(proj>=GOAL){
255+
p.className='projection success';
256+
h+=`<p style="margin-top:10px;font-weight:bold;">✅ ON TRACK! (+${fmt(proj-GOAL)})</p>`
257+
}else{
258+
p.className='projection warning';
259+
h+=`<p style="margin-top:10px;font-weight:bold;">⚠️ BEHIND PACE (-${fmt(GOAL-proj)})<br>Need +${fmt(dt-avg)}/day</p>`
260+
}
261+
document.getElementById('projectionContent').innerHTML=h
262+
}
263+
const devicesList=document.getElementById('devicesList');
264+
devicesList.innerHTML=data.sort((a,b)=>(b.estimated_cost||0)-(a.estimated_cost||0)).map(d=>{
265+
const usage=d.usage;
266+
const devTot=usage.input_tokens+usage.output_tokens+usage.cache_creation_tokens;
267+
const lastUpd=new Date(d.last_updated).toLocaleString('ko-KR');
268+
return`<div class="device-card">
269+
<div class="device-header">
270+
<div class="device-name">🖥️ ${d.device_id}</div>
271+
<div class="device-cost">$${(d.estimated_cost||0).toFixed(2)}</div>
272+
</div>
273+
<div class="device-stats">
274+
<strong>Total Processed:</strong> ${fmt(devTot)} (${devTot.toLocaleString()} tokens)<br>
275+
<strong>Sessions:</strong> ${usage.total_sessions.toLocaleString()}<br>
276+
<strong>Input:</strong> ${usage.input_tokens.toLocaleString()} |
277+
<strong>Output:</strong> ${usage.output_tokens.toLocaleString()}<br>
278+
<strong>Cache Creation:</strong> ${usage.cache_creation_tokens.toLocaleString()}<br>
279+
<strong>Last Updated:</strong> ${lastUpd}
280+
</div>
281+
</div>`
282+
}).join('');
283+
document.getElementById('loading').style.display='none';
284+
document.getElementById('content').style.display='block'
285+
}catch(e){
286+
document.getElementById('loading').innerHTML='<p>❌ Error loading data</p>'
287+
}
288+
}
289+
load();
290+
setInterval(load,300000);
130291
</script>
131292
</body>
132293
</html>"""

0 commit comments

Comments
 (0)