|
45 | 45 | .stat-card .label { font-size: 0.9em; opacity: 0.9; margin-bottom: 5px; } |
46 | 46 | .stat-card .value { font-size: 2em; font-weight: bold; } |
47 | 47 | .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 | + } |
48 | 70 | .progress-header { |
49 | 71 | display: flex; |
50 | 72 | justify-content: space-between; |
|
90 | 112 | .projection.warning { background: #f8d7da; border-left-color: #dc3545; } |
91 | 113 | .projection-title { font-size: 1.2em; font-weight: bold; margin-bottom: 10px; color: #333; } |
92 | 114 | .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; } |
93 | 160 | </style> |
94 | 161 | </head> |
95 | 162 | <body> |
|
106 | 173 | <div class="stat-card"><div class="label">Total Cost</div><div class="value" id="totalCost">-</div></div> |
107 | 174 | </div> |
108 | 175 | <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> |
109 | 181 | <div class="progress-header"> |
110 | 182 | <span class="progress-label">🎯 Goal Progress</span> |
111 | 183 | <span class="progress-percentage" id="percentage">0%</span> |
|
123 | 195 | <div class="projection-title">🔮 Projection</div> |
124 | 196 | <div id="projectionContent"></div> |
125 | 197 | </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> |
126 | 205 | </div> |
127 | 206 | </div> |
128 | 207 | <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); |
130 | 291 | </script> |
131 | 292 | </body> |
132 | 293 | </html>""" |
|
0 commit comments