bardd commited on
Commit
32309bd
·
verified ·
1 Parent(s): dbe5f96

Update src/proxy_app/static/dashboard.html

Browse files
Files changed (1) hide show
  1. src/proxy_app/static/dashboard.html +125 -84
src/proxy_app/static/dashboard.html CHANGED
@@ -1,5 +1,6 @@
1
  <!DOCTYPE html>
2
  <html lang="en">
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
@@ -18,13 +19,13 @@
18
  --accent-purple: #a371f7;
19
  --border-color: #30363d;
20
  }
21
-
22
  * {
23
  margin: 0;
24
  padding: 0;
25
  box-sizing: border-box;
26
  }
27
-
28
  body {
29
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
30
  background: var(--bg-primary);
@@ -32,12 +33,12 @@
32
  min-height: 100vh;
33
  padding: 20px;
34
  }
35
-
36
  .container {
37
  max-width: 1200px;
38
  margin: 0 auto;
39
  }
40
-
41
  header {
42
  display: flex;
43
  justify-content: space-between;
@@ -46,25 +47,25 @@
46
  padding-bottom: 16px;
47
  border-bottom: 1px solid var(--border-color);
48
  }
49
-
50
  h1 {
51
  font-size: 1.5rem;
52
  display: flex;
53
  align-items: center;
54
  gap: 10px;
55
  }
56
-
57
  .header-right {
58
  display: flex;
59
  align-items: center;
60
  gap: 16px;
61
  }
62
-
63
  .refresh-info {
64
  color: var(--text-secondary);
65
  font-size: 0.85rem;
66
  }
67
-
68
  .refresh-btn {
69
  background: var(--accent-blue);
70
  color: white;
@@ -78,16 +79,16 @@
78
  gap: 6px;
79
  transition: opacity 0.2s;
80
  }
81
-
82
  .refresh-btn:hover {
83
  opacity: 0.9;
84
  }
85
-
86
  .refresh-btn:disabled {
87
  opacity: 0.5;
88
  cursor: not-allowed;
89
  }
90
-
91
  .summary-card {
92
  background: linear-gradient(135deg, #1a1f35 0%, #141824 100%);
93
  border: 1px solid var(--border-color);
@@ -95,7 +96,7 @@
95
  padding: 24px;
96
  margin-bottom: 24px;
97
  }
98
-
99
  .summary-title {
100
  font-size: 1rem;
101
  color: var(--text-secondary);
@@ -104,41 +105,41 @@
104
  align-items: center;
105
  gap: 8px;
106
  }
107
-
108
  .summary-grid {
109
  display: grid;
110
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
111
  gap: 20px;
112
  }
113
-
114
  .stat-box {
115
  text-align: center;
116
  }
117
-
118
  .stat-value {
119
  font-size: 2rem;
120
  font-weight: bold;
121
  color: var(--accent-blue);
122
  }
123
-
124
  .stat-label {
125
  font-size: 0.8rem;
126
  color: var(--text-secondary);
127
  margin-top: 4px;
128
  }
129
-
130
  .accounts-section {
131
  display: grid;
132
  gap: 16px;
133
  }
134
-
135
  .account-card {
136
  background: var(--bg-secondary);
137
  border: 1px solid var(--border-color);
138
  border-radius: 12px;
139
  overflow: hidden;
140
  }
141
-
142
  .account-header {
143
  display: flex;
144
  justify-content: space-between;
@@ -147,14 +148,14 @@
147
  background: var(--bg-tertiary);
148
  border-bottom: 1px solid var(--border-color);
149
  }
150
-
151
  .account-name {
152
  font-weight: 600;
153
  display: flex;
154
  align-items: center;
155
  gap: 8px;
156
  }
157
-
158
  .account-tier {
159
  font-size: 0.75rem;
160
  padding: 2px 8px;
@@ -162,28 +163,36 @@
162
  background: var(--accent-purple);
163
  color: white;
164
  }
165
-
166
  .account-status {
167
  display: flex;
168
  align-items: center;
169
  gap: 6px;
170
  font-size: 0.85rem;
171
  }
172
-
173
  .status-dot {
174
  width: 8px;
175
  height: 8px;
176
  border-radius: 50%;
177
  }
178
-
179
- .status-active { background: var(--accent-green); }
180
- .status-cooldown { background: var(--accent-yellow); }
181
- .status-exhausted { background: var(--accent-red); }
182
-
 
 
 
 
 
 
 
 
183
  .account-body {
184
  padding: 16px 20px;
185
  }
186
-
187
  .account-stats {
188
  display: grid;
189
  grid-template-columns: repeat(4, 1fr);
@@ -192,35 +201,35 @@
192
  padding-bottom: 16px;
193
  border-bottom: 1px solid var(--border-color);
194
  }
195
-
196
  .mini-stat {
197
  text-align: center;
198
  }
199
-
200
  .mini-stat-value {
201
  font-size: 1.2rem;
202
  font-weight: bold;
203
  color: var(--text-primary);
204
  }
205
-
206
  .mini-stat-label {
207
  font-size: 0.7rem;
208
  color: var(--text-secondary);
209
  }
210
-
211
  .models-list {
212
  display: flex;
213
  flex-direction: column;
214
  gap: 12px;
215
  }
216
-
217
  .model-row {
218
  display: grid;
219
  grid-template-columns: 200px 1fr 100px 100px;
220
  gap: 12px;
221
  align-items: center;
222
  }
223
-
224
  .model-name {
225
  font-size: 0.85rem;
226
  color: var(--text-primary);
@@ -228,42 +237,50 @@
228
  overflow: hidden;
229
  text-overflow: ellipsis;
230
  }
231
-
232
  .progress-bar {
233
  height: 8px;
234
  background: var(--bg-tertiary);
235
  border-radius: 4px;
236
  overflow: hidden;
237
  }
238
-
239
  .progress-fill {
240
  height: 100%;
241
  border-radius: 4px;
242
  transition: width 0.3s ease;
243
  }
244
-
245
- .progress-green { background: var(--accent-green); }
246
- .progress-yellow { background: var(--accent-yellow); }
247
- .progress-red { background: var(--accent-red); }
248
-
 
 
 
 
 
 
 
 
249
  .quota-text {
250
  font-size: 0.8rem;
251
  color: var(--text-secondary);
252
  text-align: right;
253
  }
254
-
255
  .reset-time {
256
  font-size: 0.75rem;
257
  color: var(--accent-yellow);
258
  text-align: right;
259
  }
260
-
261
  .loading {
262
  text-align: center;
263
  padding: 60px;
264
  color: var(--text-secondary);
265
  }
266
-
267
  .error-message {
268
  background: rgba(248, 81, 73, 0.1);
269
  border: 1px solid var(--accent-red);
@@ -272,28 +289,31 @@
272
  border-radius: 8px;
273
  margin-bottom: 16px;
274
  }
275
-
276
  @media (max-width: 768px) {
277
  .model-row {
278
  grid-template-columns: 1fr;
279
  gap: 4px;
280
  }
 
281
  .account-stats {
282
  grid-template-columns: repeat(2, 1fr);
283
  }
 
284
  .summary-grid {
285
  grid-template-columns: repeat(2, 1fr);
286
  }
287
  }
288
  </style>
289
  </head>
 
290
  <body>
291
  <div class="container">
292
  <header>
293
  <h1>🛡️ LLM Proxy Dashboard</h1>
294
  <div class="header-right">
295
  <span class="refresh-info">
296
- Last updated: <span id="lastUpdate">--:--:--</span> •
297
  Auto-refresh in <span id="countdown">10</span>s
298
  </span>
299
  <button class="refresh-btn" onclick="fetchData()" id="refreshBtn">
@@ -301,7 +321,7 @@
301
  </button>
302
  </div>
303
  </header>
304
-
305
  <div id="content">
306
  <div class="loading">Loading dashboard data...</div>
307
  </div>
@@ -311,61 +331,61 @@
311
  const API_KEY = 'sk-antigravity-proxy-123';
312
  let countdown = 10;
313
  let countdownInterval;
314
-
315
  function formatTime(date) {
316
  return date.toLocaleTimeString('en-US', { hour12: false });
317
  }
318
-
319
  function formatResetTime(timestamp) {
320
  if (!timestamp) return '--';
321
  const resetDate = new Date(timestamp * 1000);
322
  const now = new Date();
323
  const diffMs = resetDate - now;
324
-
325
  if (diffMs <= 0) return 'Now';
326
-
327
  const hours = Math.floor(diffMs / (1000 * 60 * 60));
328
  const mins = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
329
-
330
  if (hours > 24) {
331
  const days = Math.floor(hours / 24);
332
  return `${days}d ${hours % 24}h`;
333
  }
334
  return hours > 0 ? `${hours}h ${mins}m` : `${mins}m`;
335
  }
336
-
337
  function getProgressColor(used, max) {
338
  const pct = (used / max) * 100;
339
  if (pct >= 80) return 'progress-red';
340
  if (pct >= 50) return 'progress-yellow';
341
  return 'progress-green';
342
  }
343
-
344
  function getStatusClass(status) {
345
  if (status === 'active') return 'status-active';
346
  if (status === 'cooldown') return 'status-cooldown';
347
  return 'status-exhausted';
348
  }
349
-
350
  async function fetchData() {
351
  const btn = document.getElementById('refreshBtn');
352
  btn.disabled = true;
353
  btn.innerHTML = '⏳ Loading...';
354
-
355
  try {
356
  const response = await fetch('/v1/quota-stats?provider=antigravity', {
357
  headers: {
358
  'Authorization': `Bearer ${API_KEY}`
359
  }
360
  });
361
-
362
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
363
-
364
  const data = await response.json();
365
  renderDashboard(data);
366
  document.getElementById('lastUpdate').textContent = formatTime(new Date());
367
  countdown = 10;
368
-
369
  } catch (error) {
370
  document.getElementById('content').innerHTML = `
371
  <div class="error-message">
@@ -378,18 +398,18 @@
378
  btn.innerHTML = '🔄 Refresh';
379
  }
380
  }
381
-
382
  function renderDashboard(data) {
383
  const provider = data.providers?.antigravity;
384
  if (!provider) {
385
  document.getElementById('content').innerHTML = '<div class="loading">No Antigravity data available</div>';
386
  return;
387
  }
388
-
389
  const summary = data.summary || {};
390
  const tokens = provider.tokens || {};
391
  const totalTokens = (tokens.input_uncached || 0) + (tokens.input_cached || 0) + (tokens.output || 0);
392
-
393
  let html = `
394
  <div class="summary-card">
395
  <div class="summary-title">📊 TOTAL USAGE</div>
@@ -431,17 +451,25 @@
431
 
432
  <div class="accounts-section">
433
  `;
434
-
 
 
 
 
 
 
 
435
  const credentials = provider.credentials || [];
436
  credentials.forEach((cred, index) => {
437
  const credTokens = cred.tokens || {};
438
  const credTotalTokens = (credTokens.input_uncached || 0) + (credTokens.input_cached || 0) + (credTokens.output || 0);
439
-
 
440
  html += `
441
  <div class="account-card">
442
  <div class="account-header">
443
  <div class="account-name">
444
- 👤 Account #${index + 1}
445
  <span class="account-tier">${cred.tier || 'unknown'}</span>
446
  </div>
447
  <div class="account-status">
@@ -470,54 +498,67 @@
470
  </div>
471
  <div class="models-list">
472
  `;
473
-
474
- // Render model groups instead of individual models for cleaner view
475
- const groups = cred.model_groups || {};
476
- Object.entries(groups).forEach(([groupName, group]) => {
477
- const used = group.requests_used || 0;
478
- const max = group.requests_max || 1;
479
- const remaining = group.requests_remaining || 0;
480
- const pct = (used / max) * 100;
481
- const resetTime = group.reset_time_iso ? new Date(group.reset_time_iso).getTime() / 1000 : null;
482
-
 
 
 
 
 
 
 
483
  html += `
484
  <div class="model-row">
485
- <div class="model-name">${groupName}</div>
486
  <div class="progress-bar">
487
- <div class="progress-fill ${getProgressColor(used, max)}" style="width: ${pct}%"></div>
488
  </div>
489
  <div class="quota-text">${used}/${max}</div>
490
  <div class="reset-time">${formatResetTime(resetTime)}</div>
491
  </div>
492
  `;
493
  });
494
-
 
 
 
 
 
495
  html += `
496
  </div>
497
  </div>
498
  </div>
499
  `;
500
  });
501
-
502
  html += '</div>';
503
  document.getElementById('content').innerHTML = html;
504
  }
505
-
506
  function startCountdown() {
507
  countdownInterval = setInterval(() => {
508
  countdown--;
509
  document.getElementById('countdown').textContent = countdown;
510
-
511
  if (countdown <= 0) {
512
  countdown = 10;
513
  fetchData();
514
  }
515
  }, 1000);
516
  }
517
-
518
  // Initial load
519
  fetchData();
520
  startCountdown();
521
  </script>
522
  </body>
523
- </html>
 
 
1
  <!DOCTYPE html>
2
  <html lang="en">
3
+
4
  <head>
5
  <meta charset="UTF-8">
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
 
19
  --accent-purple: #a371f7;
20
  --border-color: #30363d;
21
  }
22
+
23
  * {
24
  margin: 0;
25
  padding: 0;
26
  box-sizing: border-box;
27
  }
28
+
29
  body {
30
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
31
  background: var(--bg-primary);
 
33
  min-height: 100vh;
34
  padding: 20px;
35
  }
36
+
37
  .container {
38
  max-width: 1200px;
39
  margin: 0 auto;
40
  }
41
+
42
  header {
43
  display: flex;
44
  justify-content: space-between;
 
47
  padding-bottom: 16px;
48
  border-bottom: 1px solid var(--border-color);
49
  }
50
+
51
  h1 {
52
  font-size: 1.5rem;
53
  display: flex;
54
  align-items: center;
55
  gap: 10px;
56
  }
57
+
58
  .header-right {
59
  display: flex;
60
  align-items: center;
61
  gap: 16px;
62
  }
63
+
64
  .refresh-info {
65
  color: var(--text-secondary);
66
  font-size: 0.85rem;
67
  }
68
+
69
  .refresh-btn {
70
  background: var(--accent-blue);
71
  color: white;
 
79
  gap: 6px;
80
  transition: opacity 0.2s;
81
  }
82
+
83
  .refresh-btn:hover {
84
  opacity: 0.9;
85
  }
86
+
87
  .refresh-btn:disabled {
88
  opacity: 0.5;
89
  cursor: not-allowed;
90
  }
91
+
92
  .summary-card {
93
  background: linear-gradient(135deg, #1a1f35 0%, #141824 100%);
94
  border: 1px solid var(--border-color);
 
96
  padding: 24px;
97
  margin-bottom: 24px;
98
  }
99
+
100
  .summary-title {
101
  font-size: 1rem;
102
  color: var(--text-secondary);
 
105
  align-items: center;
106
  gap: 8px;
107
  }
108
+
109
  .summary-grid {
110
  display: grid;
111
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
112
  gap: 20px;
113
  }
114
+
115
  .stat-box {
116
  text-align: center;
117
  }
118
+
119
  .stat-value {
120
  font-size: 2rem;
121
  font-weight: bold;
122
  color: var(--accent-blue);
123
  }
124
+
125
  .stat-label {
126
  font-size: 0.8rem;
127
  color: var(--text-secondary);
128
  margin-top: 4px;
129
  }
130
+
131
  .accounts-section {
132
  display: grid;
133
  gap: 16px;
134
  }
135
+
136
  .account-card {
137
  background: var(--bg-secondary);
138
  border: 1px solid var(--border-color);
139
  border-radius: 12px;
140
  overflow: hidden;
141
  }
142
+
143
  .account-header {
144
  display: flex;
145
  justify-content: space-between;
 
148
  background: var(--bg-tertiary);
149
  border-bottom: 1px solid var(--border-color);
150
  }
151
+
152
  .account-name {
153
  font-weight: 600;
154
  display: flex;
155
  align-items: center;
156
  gap: 8px;
157
  }
158
+
159
  .account-tier {
160
  font-size: 0.75rem;
161
  padding: 2px 8px;
 
163
  background: var(--accent-purple);
164
  color: white;
165
  }
166
+
167
  .account-status {
168
  display: flex;
169
  align-items: center;
170
  gap: 6px;
171
  font-size: 0.85rem;
172
  }
173
+
174
  .status-dot {
175
  width: 8px;
176
  height: 8px;
177
  border-radius: 50%;
178
  }
179
+
180
+ .status-active {
181
+ background: var(--accent-green);
182
+ }
183
+
184
+ .status-cooldown {
185
+ background: var(--accent-yellow);
186
+ }
187
+
188
+ .status-exhausted {
189
+ background: var(--accent-red);
190
+ }
191
+
192
  .account-body {
193
  padding: 16px 20px;
194
  }
195
+
196
  .account-stats {
197
  display: grid;
198
  grid-template-columns: repeat(4, 1fr);
 
201
  padding-bottom: 16px;
202
  border-bottom: 1px solid var(--border-color);
203
  }
204
+
205
  .mini-stat {
206
  text-align: center;
207
  }
208
+
209
  .mini-stat-value {
210
  font-size: 1.2rem;
211
  font-weight: bold;
212
  color: var(--text-primary);
213
  }
214
+
215
  .mini-stat-label {
216
  font-size: 0.7rem;
217
  color: var(--text-secondary);
218
  }
219
+
220
  .models-list {
221
  display: flex;
222
  flex-direction: column;
223
  gap: 12px;
224
  }
225
+
226
  .model-row {
227
  display: grid;
228
  grid-template-columns: 200px 1fr 100px 100px;
229
  gap: 12px;
230
  align-items: center;
231
  }
232
+
233
  .model-name {
234
  font-size: 0.85rem;
235
  color: var(--text-primary);
 
237
  overflow: hidden;
238
  text-overflow: ellipsis;
239
  }
240
+
241
  .progress-bar {
242
  height: 8px;
243
  background: var(--bg-tertiary);
244
  border-radius: 4px;
245
  overflow: hidden;
246
  }
247
+
248
  .progress-fill {
249
  height: 100%;
250
  border-radius: 4px;
251
  transition: width 0.3s ease;
252
  }
253
+
254
+ .progress-green {
255
+ background: var(--accent-green);
256
+ }
257
+
258
+ .progress-yellow {
259
+ background: var(--accent-yellow);
260
+ }
261
+
262
+ .progress-red {
263
+ background: var(--accent-red);
264
+ }
265
+
266
  .quota-text {
267
  font-size: 0.8rem;
268
  color: var(--text-secondary);
269
  text-align: right;
270
  }
271
+
272
  .reset-time {
273
  font-size: 0.75rem;
274
  color: var(--accent-yellow);
275
  text-align: right;
276
  }
277
+
278
  .loading {
279
  text-align: center;
280
  padding: 60px;
281
  color: var(--text-secondary);
282
  }
283
+
284
  .error-message {
285
  background: rgba(248, 81, 73, 0.1);
286
  border: 1px solid var(--accent-red);
 
289
  border-radius: 8px;
290
  margin-bottom: 16px;
291
  }
292
+
293
  @media (max-width: 768px) {
294
  .model-row {
295
  grid-template-columns: 1fr;
296
  gap: 4px;
297
  }
298
+
299
  .account-stats {
300
  grid-template-columns: repeat(2, 1fr);
301
  }
302
+
303
  .summary-grid {
304
  grid-template-columns: repeat(2, 1fr);
305
  }
306
  }
307
  </style>
308
  </head>
309
+
310
  <body>
311
  <div class="container">
312
  <header>
313
  <h1>🛡️ LLM Proxy Dashboard</h1>
314
  <div class="header-right">
315
  <span class="refresh-info">
316
+ Last updated: <span id="lastUpdate">--:--:--</span> •
317
  Auto-refresh in <span id="countdown">10</span>s
318
  </span>
319
  <button class="refresh-btn" onclick="fetchData()" id="refreshBtn">
 
321
  </button>
322
  </div>
323
  </header>
324
+
325
  <div id="content">
326
  <div class="loading">Loading dashboard data...</div>
327
  </div>
 
331
  const API_KEY = 'sk-antigravity-proxy-123';
332
  let countdown = 10;
333
  let countdownInterval;
334
+
335
  function formatTime(date) {
336
  return date.toLocaleTimeString('en-US', { hour12: false });
337
  }
338
+
339
  function formatResetTime(timestamp) {
340
  if (!timestamp) return '--';
341
  const resetDate = new Date(timestamp * 1000);
342
  const now = new Date();
343
  const diffMs = resetDate - now;
344
+
345
  if (diffMs <= 0) return 'Now';
346
+
347
  const hours = Math.floor(diffMs / (1000 * 60 * 60));
348
  const mins = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
349
+
350
  if (hours > 24) {
351
  const days = Math.floor(hours / 24);
352
  return `${days}d ${hours % 24}h`;
353
  }
354
  return hours > 0 ? `${hours}h ${mins}m` : `${mins}m`;
355
  }
356
+
357
  function getProgressColor(used, max) {
358
  const pct = (used / max) * 100;
359
  if (pct >= 80) return 'progress-red';
360
  if (pct >= 50) return 'progress-yellow';
361
  return 'progress-green';
362
  }
363
+
364
  function getStatusClass(status) {
365
  if (status === 'active') return 'status-active';
366
  if (status === 'cooldown') return 'status-cooldown';
367
  return 'status-exhausted';
368
  }
369
+
370
  async function fetchData() {
371
  const btn = document.getElementById('refreshBtn');
372
  btn.disabled = true;
373
  btn.innerHTML = '⏳ Loading...';
374
+
375
  try {
376
  const response = await fetch('/v1/quota-stats?provider=antigravity', {
377
  headers: {
378
  'Authorization': `Bearer ${API_KEY}`
379
  }
380
  });
381
+
382
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
383
+
384
  const data = await response.json();
385
  renderDashboard(data);
386
  document.getElementById('lastUpdate').textContent = formatTime(new Date());
387
  countdown = 10;
388
+
389
  } catch (error) {
390
  document.getElementById('content').innerHTML = `
391
  <div class="error-message">
 
398
  btn.innerHTML = '🔄 Refresh';
399
  }
400
  }
401
+
402
  function renderDashboard(data) {
403
  const provider = data.providers?.antigravity;
404
  if (!provider) {
405
  document.getElementById('content').innerHTML = '<div class="loading">No Antigravity data available</div>';
406
  return;
407
  }
408
+
409
  const summary = data.summary || {};
410
  const tokens = provider.tokens || {};
411
  const totalTokens = (tokens.input_uncached || 0) + (tokens.input_cached || 0) + (tokens.output || 0);
412
+
413
  let html = `
414
  <div class="summary-card">
415
  <div class="summary-title">📊 TOTAL USAGE</div>
 
451
 
452
  <div class="accounts-section">
453
  `;
454
+
455
+ // Masked email patterns based on credential index
456
+ const maskedEmails = [
457
+ 'cr******68@gmail.com',
458
+ 'ow******88@gmail.com',
459
+ 'ba******92@gmail.com'
460
+ ];
461
+
462
  const credentials = provider.credentials || [];
463
  credentials.forEach((cred, index) => {
464
  const credTokens = cred.tokens || {};
465
  const credTotalTokens = (credTokens.input_uncached || 0) + (credTokens.input_cached || 0) + (credTokens.output || 0);
466
+ const maskedEmail = maskedEmails[index] || `Account #${index + 1}`;
467
+
468
  html += `
469
  <div class="account-card">
470
  <div class="account-header">
471
  <div class="account-name">
472
+ 👤 ${maskedEmail}
473
  <span class="account-tier">${cred.tier || 'unknown'}</span>
474
  </div>
475
  <div class="account-status">
 
498
  </div>
499
  <div class="models-list">
500
  `;
501
+
502
+ // Show individual models from cred.models
503
+ const models = cred.models || {};
504
+ const modelEntries = Object.entries(models).sort((a, b) => {
505
+ // Sort by usage (most used first)
506
+ return (b[1].request_count || 0) - (a[1].request_count || 0);
507
+ });
508
+
509
+ modelEntries.forEach(([modelName, modelStats]) => {
510
+ const shortName = modelName.replace('antigravity/', '');
511
+ const used = modelStats.request_count || 0;
512
+ const max = modelStats.quota_max_requests || 0;
513
+ const pct = max > 0 ? (used / max) * 100 : 0;
514
+ const resetTime = modelStats.quota_reset_ts;
515
+ const promptTokens = modelStats.prompt_tokens || 0;
516
+ const completionTokens = modelStats.completion_tokens || 0;
517
+
518
  html += `
519
  <div class="model-row">
520
+ <div class="model-name" title="${modelName}">${shortName}</div>
521
  <div class="progress-bar">
522
+ <div class="progress-fill ${getProgressColor(used, max || 1)}" style="width: ${pct}%"></div>
523
  </div>
524
  <div class="quota-text">${used}/${max}</div>
525
  <div class="reset-time">${formatResetTime(resetTime)}</div>
526
  </div>
527
  `;
528
  });
529
+
530
+ // If no models, show placeholder
531
+ if (modelEntries.length === 0) {
532
+ html += '<div style="color: var(--text-secondary); font-size: 0.85rem;">No usage data yet</div>';
533
+ }
534
+
535
  html += `
536
  </div>
537
  </div>
538
  </div>
539
  `;
540
  });
541
+
542
  html += '</div>';
543
  document.getElementById('content').innerHTML = html;
544
  }
545
+
546
  function startCountdown() {
547
  countdownInterval = setInterval(() => {
548
  countdown--;
549
  document.getElementById('countdown').textContent = countdown;
550
+
551
  if (countdown <= 0) {
552
  countdown = 10;
553
  fetchData();
554
  }
555
  }, 1000);
556
  }
557
+
558
  // Initial load
559
  fetchData();
560
  startCountdown();
561
  </script>
562
  </body>
563
+
564
+ </html>