@jeffreycao/copilot-api 1.13.4 → 1.13.6

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/pages/index.html CHANGED
@@ -1,5 +1,5 @@
1
1
  <!doctype html>
2
- <html lang="en" class="dark">
2
+ <html lang="en">
3
3
  <head>
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
@@ -14,82 +14,288 @@
14
14
  rel="stylesheet"
15
15
  />
16
16
 
17
- <script src="https://unpkg.com/lucide-react@0.378.0/dist/umd/lucide.min.js"></script>
17
+ <script src="https://unpkg.com/lucide@0.378.0/dist/umd/lucide.min.js"></script>
18
18
 
19
19
  <style>
20
- /* Gruvbox-themed color palette */
21
20
  :root {
22
- /* Main Color Palette */
23
- --color-red: #cc241d;
24
- --color-green: #98971a;
25
- --color-yellow: #d79921;
26
- --color-blue: #458588;
27
- --color-purple: #b16286;
28
- --color-aqua: #689d6a;
29
- --color-orange: #d65d0e;
30
- --color-gray: #a89984;
31
-
32
- /* Accent/Lighter/Darker Shades of Main Colors */
33
- --color-red-accent: #fb4934;
34
- --color-green-accent: #b8bb26;
35
- --color-yellow-accent: #fabd2f;
36
- --color-blue-accent: #83a598;
37
- --color-purple-accent: #d3869b;
38
- --color-aqua-accent: #8ec07c;
39
- --color-orange-accent: #fe8019;
40
- --color-gray-accent: #928374;
41
-
42
- /* Background Colors */
43
- --color-bg-darkest: #1d2021; /* bg0_h */
44
- --color-bg: #282828; /* bg and bg0 */
45
- --color-bg-light-1: #3c3836; /* bg1 */
46
- --color-bg-light-2: #504945; /* bg2 */
47
- --color-bg-light-3: #665c54; /* bg3 */
48
- --color-bg-light-4: #7c6f64; /* bg4 */
49
- --color-bg-soft: #32302f; /* bg0_s */
50
-
51
- /* Foreground Colors */
52
- --color-fg-darker: #a89984; /* fg4 - duplicate of gray */
53
- --color-fg-dark: #bdae93; /* fg3 */
54
- --color-fg-medium: #d5c4a1; /* fg2 */
55
- --color-fg-light: #ebdbb2; /* fg and fg1 */
56
- --color-fg-lightest: #fbf1c7; /* fg0 */
21
+ --color-red: #dc2626;
22
+ --color-green: #16a34a;
23
+ --color-yellow: #d97706;
24
+ --color-blue: #2563eb;
25
+ --color-purple: #7c3aed;
26
+ --color-aqua: #0891b2;
27
+ --color-orange: #ea580c;
28
+ --color-gray: #64748b;
29
+
30
+ --color-red-accent: #ef4444;
31
+ --color-green-accent: #22c55e;
32
+ --color-yellow-accent: #f59e0b;
33
+ --color-blue-accent: #1d4ed8;
34
+ --color-purple-accent: #8b5cf6;
35
+ --color-aqua-accent: #0ea5e9;
36
+ --color-orange-accent: #f97316;
37
+ --color-gray-accent: #94a3b8;
38
+
39
+ --color-bg-darkest: #0f172a;
40
+ --color-bg: #ffffff;
41
+ --color-bg-light-1: #f8fafc;
42
+ --color-bg-light-2: #e2e8f0;
43
+ --color-bg-light-3: #cbd5e1;
44
+ --color-bg-light-4: #94a3b8;
45
+ --color-bg-soft: #f1f5f9;
46
+
47
+ --color-fg-darker: #64748b;
48
+ --color-fg-dark: #475569;
49
+ --color-fg-medium: #334155;
50
+ --color-fg-light: #1e293b;
51
+ --color-fg-lightest: #0f172a;
52
+ }
53
+
54
+ * {
55
+ box-sizing: border-box;
57
56
  }
58
57
 
59
- /* Custom styles using the new palette */
60
58
  body {
61
59
  font-family: "Inter", sans-serif;
62
- background-color: var(--color-bg-darkest);
60
+ background-color: #eef2f7;
63
61
  color: var(--color-fg-light);
64
62
  }
65
63
 
66
- /* Custom progress bar styles */
64
+ .app-shell {
65
+ min-height: 100vh;
66
+ background: linear-gradient(180deg, #f8fafc 0%, #eef2f7 100%);
67
+ }
68
+
69
+ .main-shell {
70
+ min-width: 0;
71
+ padding: 28px 32px 40px;
72
+ }
73
+
74
+ .topbar {
75
+ margin: 0 auto 18px;
76
+ max-width: 1720px;
77
+ }
78
+
79
+ .topbar-row {
80
+ display: flex;
81
+ align-items: flex-start;
82
+ justify-content: space-between;
83
+ gap: 20px;
84
+ margin-bottom: 16px;
85
+ }
86
+
87
+ .page-title {
88
+ font-size: clamp(28px, 3vw, 38px);
89
+ font-weight: 750;
90
+ line-height: 1.05;
91
+ color: var(--color-fg-lightest);
92
+ }
93
+
94
+ .page-subtitle {
95
+ margin-top: 8px;
96
+ color: var(--color-gray);
97
+ font-size: 14px;
98
+ }
99
+
100
+ .control-surface {
101
+ display: grid;
102
+ grid-template-columns:
103
+ minmax(320px, 1fr)
104
+ minmax(220px, 320px)
105
+ 150px
106
+ 132px;
107
+ gap: 12px;
108
+ align-items: end;
109
+ }
110
+
111
+ .field-label {
112
+ display: block;
113
+ margin-bottom: 6px;
114
+ color: var(--color-fg-medium);
115
+ font-size: 12px;
116
+ font-weight: 700;
117
+ }
118
+
119
+ .input-control,
120
+ .select-control {
121
+ width: 100%;
122
+ height: 42px;
123
+ border: 1px solid var(--color-bg-light-3);
124
+ border-radius: 8px;
125
+ background: rgba(255, 255, 255, 0.88);
126
+ color: var(--color-fg-lightest);
127
+ font-size: 14px;
128
+ outline: none;
129
+ transition:
130
+ border-color 0.16s ease,
131
+ box-shadow 0.16s ease,
132
+ background-color 0.16s ease;
133
+ }
134
+
135
+ .input-control {
136
+ padding: 0 12px;
137
+ }
138
+
139
+ .select-control {
140
+ padding: 0 34px 0 12px;
141
+ }
142
+
143
+ .input-control::placeholder {
144
+ color: #94a3b8;
145
+ }
146
+
147
+ .primary-action {
148
+ height: 42px;
149
+ display: inline-flex;
150
+ align-items: center;
151
+ justify-content: center;
152
+ gap: 8px;
153
+ border: 1px solid transparent;
154
+ border-radius: 8px;
155
+ background: var(--color-blue);
156
+ color: #ffffff;
157
+ font-size: 14px;
158
+ font-weight: 700;
159
+ transition:
160
+ background-color 0.16s ease,
161
+ transform 0.16s ease;
162
+ }
163
+
164
+ .primary-action:hover {
165
+ background: var(--color-blue-accent);
166
+ }
167
+
168
+ .content-area {
169
+ max-width: 1720px;
170
+ margin: 0 auto;
171
+ }
172
+
173
+ .dashboard-section {
174
+ margin-bottom: 16px;
175
+ }
176
+
177
+ .section-heading {
178
+ display: flex;
179
+ align-items: center;
180
+ gap: 9px;
181
+ margin-bottom: 12px;
182
+ color: var(--color-fg-lightest);
183
+ font-size: 16px;
184
+ font-weight: 750;
185
+ }
186
+
187
+ .section-subtitle {
188
+ margin-top: -6px;
189
+ margin-bottom: 12px;
190
+ color: var(--color-gray);
191
+ font-size: 12px;
192
+ }
193
+
194
+ .panel {
195
+ border: 1px solid var(--color-bg-light-2);
196
+ border-radius: 8px;
197
+ background: rgba(255, 255, 255, 0.94);
198
+ box-shadow: 0 12px 28px rgba(15, 23, 42, 0.05);
199
+ }
200
+
201
+ .panel-header {
202
+ display: flex;
203
+ align-items: center;
204
+ justify-content: space-between;
205
+ gap: 12px;
206
+ min-height: 54px;
207
+ padding: 14px 16px;
208
+ border-bottom: 1px solid var(--color-bg-light-2);
209
+ }
210
+
211
+ .panel-title {
212
+ color: var(--color-fg-lightest);
213
+ font-size: 14px;
214
+ font-weight: 750;
215
+ }
216
+
217
+ .panel-meta {
218
+ margin-top: 3px;
219
+ color: var(--color-gray);
220
+ font-size: 12px;
221
+ }
222
+
223
+ .metric-grid {
224
+ display: grid;
225
+ grid-template-columns: repeat(auto-fit, minmax(185px, 1fr));
226
+ gap: 14px;
227
+ }
228
+
229
+ .metric-card {
230
+ min-height: 112px;
231
+ padding: 16px;
232
+ border: 1px solid var(--color-bg-light-2);
233
+ border-radius: 8px;
234
+ background: rgba(255, 255, 255, 0.96);
235
+ box-shadow: 0 10px 22px rgba(15, 23, 42, 0.04);
236
+ }
237
+
238
+ .metric-label {
239
+ display: flex;
240
+ align-items: center;
241
+ gap: 8px;
242
+ color: var(--color-fg-medium);
243
+ font-size: 13px;
244
+ font-weight: 650;
245
+ }
246
+
247
+ .metric-dot {
248
+ width: 10px;
249
+ height: 10px;
250
+ border-radius: 999px;
251
+ }
252
+
253
+ .metric-value {
254
+ margin-top: 16px;
255
+ color: var(--color-fg-lightest);
256
+ font-size: clamp(20px, 2vw, 28px);
257
+ font-weight: 800;
258
+ line-height: 1.1;
259
+ }
260
+
261
+ .metric-note {
262
+ margin-top: 8px;
263
+ color: var(--color-gray);
264
+ font-size: 12px;
265
+ }
266
+
67
267
  .progress-bar-bg {
68
- background-color: var(--color-bg-light-1);
268
+ background-color: var(--color-bg-soft);
269
+ border-radius: 999px;
270
+ overflow: hidden;
69
271
  }
272
+
70
273
  .progress-bar-fg {
274
+ border-radius: 999px;
71
275
  transition: width 0.5s ease-in-out;
72
276
  }
73
277
 
74
- /* Custom scrollbar for the raw data view */
75
278
  .code-block::-webkit-scrollbar {
76
279
  width: 8px;
77
280
  height: 8px;
78
281
  }
282
+
79
283
  .code-block::-webkit-scrollbar-track {
80
- background: var(--color-bg);
284
+ background: var(--color-bg-light-1);
81
285
  }
286
+
82
287
  .code-block::-webkit-scrollbar-thumb {
83
288
  background: var(--color-bg-light-3);
289
+ border-radius: 999px;
84
290
  }
291
+
85
292
  .code-block::-webkit-scrollbar-thumb:hover {
86
293
  background: var(--color-bg-light-4);
87
294
  }
88
295
 
89
- /* Style for focus rings to use variables */
90
296
  .input-focus:focus {
91
- --tw-ring-color: var(--color-blue);
92
297
  border-color: var(--color-blue);
298
+ box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.14);
93
299
  }
94
300
 
95
301
  button:disabled,
@@ -98,155 +304,124 @@
98
304
  opacity: 0.55;
99
305
  }
100
306
 
307
+ .data-table {
308
+ width: 100%;
309
+ border-collapse: separate;
310
+ border-spacing: 0;
311
+ }
312
+
313
+ .data-table thead {
314
+ background: var(--color-bg-light-1);
315
+ color: var(--color-fg-medium);
316
+ }
317
+
318
+ .data-table th {
319
+ font-size: 12px;
320
+ font-weight: 750;
321
+ }
322
+
323
+ .data-table td {
324
+ font-size: 12px;
325
+ }
326
+
327
+ .data-table th,
328
+ .data-table td {
329
+ border-bottom: 1px solid var(--color-bg-light-2);
330
+ }
101
331
 
332
+ .pill-button {
333
+ border: 1px solid var(--color-bg-light-2);
334
+ border-radius: 7px;
335
+ background: var(--color-bg);
336
+ color: var(--color-fg-medium);
337
+ }
338
+
339
+ .quick-state-grid {
340
+ display: grid;
341
+ grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
342
+ gap: 12px;
343
+ }
344
+
345
+ @media (max-width: 1100px) {
346
+ .control-surface {
347
+ grid-template-columns: minmax(0, 1fr) 170px 132px;
348
+ }
349
+
350
+ .endpoint-field {
351
+ grid-column: 1 / -1;
352
+ }
353
+ }
354
+
355
+ @media (max-width: 640px) {
356
+ .main-shell {
357
+ padding: 20px 14px 28px;
358
+ }
359
+
360
+ .topbar-row {
361
+ display: block;
362
+ }
363
+
364
+ .control-surface {
365
+ grid-template-columns: 1fr;
366
+ }
367
+
368
+ .endpoint-field {
369
+ grid-column: auto;
370
+ }
371
+ }
102
372
  </style>
103
373
  </head>
104
374
  <body class="antialiased">
105
- <div id="app" class="min-h-screen p-4 sm:p-6">
106
- <div class="max-w-7xl mx-auto">
107
- <!-- Header Section -->
108
- <header class="mb-6">
109
- <h1
110
- class="text-2xl font-bold flex items-center gap-2"
111
- style="color: var(--color-fg-lightest)"
112
- >
113
- <svg
114
- xmlns="http://www.w3.org/2000/svg"
115
- width="24"
116
- height="24"
117
- viewBox="0 0 24 24"
118
- fill="none"
119
- stroke="currentColor"
120
- stroke-width="2"
121
- stroke-linecap="round"
122
- stroke-linejoin="round"
123
- class="lucide lucide-gauge-circle h-7 w-7"
124
- style="color: var(--color-aqua-accent)"
125
- >
126
- <path d="M15.6 3.3a10 10 0 1 0 5.1 5.1" />
127
- <path
128
- d="M12 12a1 1 0 0 0-1-1v4a1 1 0 0 0 1 1h4a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1h-3z"
129
- />
130
- <path d="M12 6.8a10 10 0 0 0 -3.2 7.2" />
131
- </svg>
132
- <span>Copilot API Usage Dashboard</span>
133
- </h1>
134
- <p class="mt-1 text-sm" style="color: var(--color-gray)">
135
- Should be the same as the one in VSCode
136
- </p>
137
- </header>
375
+ <div id="app" class="app-shell">
376
+ <div class="main-shell">
377
+ <header class="topbar">
378
+ <div class="topbar-row">
379
+ <div>
380
+ <h1 class="page-title">Token Usage</h1>
381
+ <p class="page-subtitle">Copilot quota and token billing dashboard</p>
382
+ </div>
383
+ </div>
138
384
 
139
- <!-- Form Section -->
140
- <div
141
- class="mb-6 p-4 border"
142
- style="
143
- background-color: var(--color-bg-soft);
144
- border-color: var(--color-bg-light-2);
145
- "
146
- >
147
- <form
148
- id="endpoint-form"
149
- class="grid grid-cols-1 xl:grid-cols-[minmax(0,2fr)_minmax(0,1fr)_180px_auto] gap-3 items-end"
150
- >
151
- <div class="w-full">
152
- <label
153
- for="endpoint-url"
154
- class="mb-1 block font-semibold whitespace-nowrap text-sm"
155
- style="color: var(--color-fg-lightest)"
156
- >Usage Endpoint URL</label
157
- >
385
+ <form id="endpoint-form" class="control-surface">
386
+ <div class="endpoint-field">
387
+ <label for="endpoint-url" class="field-label">Usage Endpoint URL</label>
158
388
  <input
159
389
  type="text"
160
390
  id="endpoint-url"
161
- class="w-full px-3 py-2 border focus:ring-1 transition input-focus text-sm"
162
- style="
163
- background-color: var(--color-bg-darkest);
164
- border-color: var(--color-bg-light-3);
165
- color: var(--color-fg-medium);
166
- "
391
+ class="input-control input-focus"
167
392
  placeholder="http://localhost:4141/usage"
168
393
  />
169
394
  </div>
170
- <div class="w-full">
171
- <label
172
- for="x-api-key"
173
- class="mb-1 block font-semibold whitespace-nowrap text-sm"
174
- style="color: var(--color-fg-lightest)"
175
- >x-api-key</label
176
- >
395
+ <div>
396
+ <label for="x-api-key" class="field-label">x-api-key</label>
177
397
  <input
178
398
  type="password"
179
399
  id="x-api-key"
180
- class="w-full px-3 py-2 border focus:ring-1 transition input-focus text-sm"
181
- style="
182
- background-color: var(--color-bg-darkest);
183
- border-color: var(--color-bg-light-3);
184
- color: var(--color-fg-medium);
185
- "
400
+ class="input-control input-focus"
186
401
  placeholder="Optional when auth is enabled"
187
402
  autocomplete="off"
188
403
  spellcheck="false"
189
404
  />
190
405
  </div>
191
- <div class="w-full">
192
- <label
193
- for="token-usage-period"
194
- class="mb-1 block font-semibold whitespace-nowrap text-sm"
195
- style="color: var(--color-fg-lightest)"
196
- >Token Usage Period</label
197
- >
406
+ <div>
407
+ <label for="token-usage-period" class="field-label">Period</label>
198
408
  <select
199
409
  id="token-usage-period"
200
- class="w-full px-3 py-2 border focus:ring-1 transition input-focus text-sm"
201
- style="
202
- background-color: var(--color-bg-darkest);
203
- border-color: var(--color-bg-light-3);
204
- color: var(--color-fg-medium);
205
- "
410
+ class="select-control input-focus"
206
411
  >
207
412
  <option value="day">Day</option>
208
- <option value="week">Last 7 days</option>
209
- <option value="month">Last 30 days</option>
413
+ <option value="week">Week</option>
414
+ <option value="month">Month</option>
210
415
  </select>
211
416
  </div>
212
- <button
213
- id="fetch-button"
214
- type="submit"
215
- class="w-full xl:w-auto font-bold py-2 px-5 transition-colors flex items-center justify-center gap-2 text-sm"
216
- style="
217
- background-color: var(--color-blue);
218
- color: var(--color-bg-darkest);
219
- "
220
- >
221
- <svg
222
- xmlns="http://www.w3.org/2000/svg"
223
- width="24"
224
- height="24"
225
- viewBox="0 0 24 24"
226
- fill="none"
227
- stroke="currentColor"
228
- stroke-width="2"
229
- stroke-linecap="round"
230
- stroke-linejoin="round"
231
- class="lucide lucide-refresh-cw h-4 w-4"
232
- >
233
- <path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8" />
234
- <path d="M21 3v5h-5" />
235
- <path d="M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16" />
236
- <path d="M3 21v-5h5" />
237
- </svg>
238
- <span>Fetch</span>
417
+ <button id="fetch-button" type="submit" class="primary-action">
418
+ <i data-lucide="refresh-cw" class="h-4 w-4"></i>
419
+ <span>Refresh</span>
239
420
  </button>
240
421
  </form>
241
- <p class="mt-3 text-xs" style="color: var(--color-gray-accent)">
242
- The page derives <code>/token-usage</code> and
243
- <code>/token-usage/events</code> from the usage endpoint and sends
244
- <code>x-api-key</code> to all requests when provided.
245
- </p>
246
- </div>
247
-
248
- <!-- Content Area for dynamic data -->
249
- <main id="content-area"></main>
422
+ </header>
423
+
424
+ <main id="content-area" class="content-area"></main>
250
425
  </div>
251
426
  </div>
252
427
 
@@ -492,7 +667,9 @@
492
667
 
493
668
  function updateControls() {
494
669
  fetchButton.disabled = state.isLoading;
495
- fetchButtonLabel.textContent = state.isLoading ? "Fetching..." : "Fetch";
670
+ fetchButtonLabel.textContent = state.isLoading
671
+ ? "Refreshing..."
672
+ : "Refresh";
496
673
  tokenUsagePeriodSelect.disabled =
497
674
  state.isLoading || state.isTokenUsageLoading;
498
675
  }
@@ -601,13 +778,13 @@
601
778
  contentArea.innerHTML = renderWelcomeMessage();
602
779
  } else if (state.error && !state.data) {
603
780
  contentArea.innerHTML = `
604
- ${renderError(state.error, "Usage request failed")}
605
781
  ${renderTokenUsageSection()}
782
+ ${renderUsageErrorSection()}
606
783
  `;
607
784
  } else if (state.data) {
608
785
  contentArea.innerHTML = `
609
- ${renderUsageQuotas(state.data.quota_snapshots)}
610
786
  ${renderTokenUsageSection()}
787
+ ${renderUsageQuotas(state.data.quota_snapshots)}
611
788
  ${renderDetailedData(state.data)}
612
789
  `;
613
790
  } else {
@@ -633,15 +810,32 @@
633
810
  .join("");
634
811
 
635
812
  return `
636
- <section id="usage-quotas" class="mb-6">
637
- <h2 class="text-xl font-bold mb-3 flex items-center gap-2" style="color: var(--color-fg-lightest);">
638
- <i data-lucide="bar-chart-big"></i> Usage Quotas
639
- </h2>
640
- <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
641
- ${quotaCards}
642
- </div>
643
- </section>
644
- `;
813
+ <section id="usage-quotas" class="dashboard-section">
814
+ <h2 class="section-heading">
815
+ <i data-lucide="gauge" class="h-5 w-5" style="color: var(--color-blue);"></i>
816
+ <span>Copilot Quotas</span>
817
+ </h2>
818
+ <div class="metric-grid">
819
+ ${quotaCards}
820
+ </div>
821
+ </section>
822
+ `;
823
+ }
824
+
825
+ function renderUsageErrorSection() {
826
+ if (!state.error || state.data) {
827
+ return "";
828
+ }
829
+
830
+ return `
831
+ <section id="usage-quotas" class="dashboard-section">
832
+ <h2 class="section-heading">
833
+ <i data-lucide="gauge" class="h-5 w-5" style="color: var(--color-blue);"></i>
834
+ <span>Copilot Quotas</span>
835
+ </h2>
836
+ ${renderError(state.error, "Usage request failed")}
837
+ </section>
838
+ `;
645
839
  }
646
840
 
647
841
  /**
@@ -665,26 +859,29 @@
665
859
  if (unlimited) progressBarColor = "var(--color-blue)";
666
860
 
667
861
  return `
668
- <div class="p-4 border" style="background-color: var(--color-bg); border-color: var(--color-bg-light-2);">
669
- <div class="flex justify-between items-center mb-2">
670
- <h3 class="text-md font-semibold capitalize" style="color: var(--color-fg-lightest);">${escapeHtml(title.replace(/_/g, " "))}</h3>
671
- ${
672
- unlimited
673
- ? `<span class="px-2 py-0.5 text-xs font-medium" style="color: var(--color-blue-accent); background-color: var(--color-bg-light-1);">Unlimited</span>`
674
- : `<span class="text-sm font-mono" style="color: var(--color-fg-medium);">${percentUsed.toFixed(1)}% Used</span>`
675
- }
676
- </div>
677
- <div class="mb-3">
678
- <div class="w-full progress-bar-bg h-2">
679
- <div class="progress-bar-fg h-2" style="width: ${unlimited ? 100 : percentUsed}%; background-color: ${progressBarColor};"></div>
680
- </div>
681
- </div>
682
- <div class="flex justify-between text-xs font-mono" style="color: var(--color-fg-dark);">
683
- <span>${used} / ${unlimited ? "∞" : entitlement.toLocaleString()}</span>
684
- <span>${unlimited ? "∞" : remaining.toLocaleString()} remaining</span>
685
- </div>
686
- </div>
687
- `;
862
+ <div class="metric-card">
863
+ <div class="flex items-center justify-between gap-3">
864
+ <div class="metric-label capitalize">
865
+ <span class="metric-dot" style="background-color: ${progressBarColor};"></span>
866
+ <span>${escapeHtml(title.replace(/_/g, " "))}</span>
867
+ </div>
868
+ ${
869
+ unlimited
870
+ ? `<span class="rounded-md px-2 py-1 text-xs font-semibold" style="background-color: rgba(37, 99, 235, 0.1); color: var(--color-blue);">Unlimited</span>`
871
+ : `<span class="text-xs font-semibold" style="color: var(--color-gray);">${percentUsed.toFixed(1)}% used</span>`
872
+ }
873
+ </div>
874
+ <div class="mt-5">
875
+ <div class="w-full progress-bar-bg h-2">
876
+ <div class="progress-bar-fg h-2" style="width: ${unlimited ? 100 : percentUsed}%; background-color: ${progressBarColor};"></div>
877
+ </div>
878
+ </div>
879
+ <div class="mt-4 flex items-center justify-between gap-4 text-xs font-medium" style="color: var(--color-gray);">
880
+ <span>${used} / ${unlimited ? "∞" : entitlement.toLocaleString()}</span>
881
+ <span>${unlimited ? "∞" : remaining.toLocaleString()} remaining</span>
882
+ </div>
883
+ </div>
884
+ `;
688
885
  }
689
886
 
690
887
  /**
@@ -694,7 +891,7 @@
694
891
  */
695
892
  function formatObject(obj) {
696
893
  if (obj === null || typeof obj !== "object") {
697
- return `<span style="color: var(--color-green-accent);">${escapeHtml(JSON.stringify(obj))}</span>`;
894
+ return `<span style="color: var(--color-green);">${escapeHtml(JSON.stringify(obj))}</span>`;
698
895
  }
699
896
 
700
897
  return (
@@ -707,18 +904,18 @@
707
904
  if (Array.isArray(value)) {
708
905
  displayValue =
709
906
  value.length > 0
710
- ? `<span style='color: var(--color-gray-accent)'>[...${value.length} items]</span>`
711
- : `<span style='color: var(--color-gray-accent)'>[]</span>`;
907
+ ? `<span style='color: var(--color-gray)'>[...${value.length} items]</span>`
908
+ : `<span style='color: var(--color-gray)'>[]</span>`;
712
909
  } else if (typeof value === "object" && value !== null) {
713
910
  displayValue = formatObject(value);
714
911
  } else if (typeof value === "boolean") {
715
- displayValue = `<span class="font-semibold" style="color: ${value ? "var(--color-green-accent)" : "var(--color-red-accent)"}">${escapeHtml(String(value))}</span>`;
912
+ displayValue = `<span class="font-semibold" style="color: ${value ? "var(--color-green)" : "var(--color-red)"}">${escapeHtml(String(value))}</span>`;
716
913
  } else {
717
- displayValue = `<span style="color: var(--color-blue-accent);">${escapeHtml(JSON.stringify(value))}</span>`;
914
+ displayValue = `<span style="color: var(--color-blue);">${escapeHtml(JSON.stringify(value))}</span>`;
718
915
  }
719
916
 
720
917
  return `<div class="mt-1">
721
- <span class="capitalize font-semibold" style="color: var(--color-fg-medium);">${formattedKey}:</span>
918
+ <span class="capitalize font-semibold" style="color: var(--color-fg-lightest);">${formattedKey}:</span>
722
919
  ${typeof value === "object" && value !== null && !Array.isArray(value) ? displayValue : ` ${displayValue}`}
723
920
  </div>`;
724
921
  })
@@ -739,15 +936,16 @@
739
936
 
740
937
  const formattedDetails = formatObject(data);
741
938
  return `
742
- <section id="detailed-data">
743
- <h2 class="text-xl font-bold mb-3 flex items-center gap-2" style="color: var(--color-fg-lightest);">
744
- <i data-lucide="file-text"></i> Usage API Response
745
- </h2>
746
- <div class="border p-4 relative font-mono text-xs code-block overflow-auto" style="background-color: var(--color-bg-darkest); border-color: var(--color-bg-light-2);">
747
- ${formattedDetails}
748
- </div>
749
- </section>
750
- `;
939
+ <section id="detailed-data" class="dashboard-section">
940
+ <h2 class="section-heading">
941
+ <i data-lucide="file-text" class="h-5 w-5" style="color: var(--color-blue);"></i>
942
+ <span>Copilot Usage API Response</span>
943
+ </h2>
944
+ <div class="panel p-4 relative font-mono text-xs code-block overflow-auto" style="max-height: 420px;">
945
+ ${formattedDetails}
946
+ </div>
947
+ </section>
948
+ `;
751
949
  }
752
950
 
753
951
  function renderTokenUsageSection() {
@@ -774,31 +972,32 @@
774
972
  summary?.range || dailySummary?.range || eventsPage?.range || null;
775
973
  const totalPages = eventsPage ? Math.max(eventsPage.total_pages, 1) : 1;
776
974
  const eventsMeta = eventsPage
777
- ? `Page ${eventsPage.page} / ${totalPages} · ${formatNumber(
975
+ ? `Page ${eventsPage.page} / ${totalPages} - ${formatNumber(
778
976
  eventsPage.total
779
977
  )} events`
780
978
  : "No detail page loaded yet.";
781
979
 
782
980
  return `
783
- <section id="token-usage" class="mb-6">
784
- <div class="mb-3 flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
981
+ <section id="token-usage" class="dashboard-section">
982
+ <div class="mb-3 flex flex-col gap-2 sm:flex-row sm:items-start sm:justify-between">
785
983
  <div>
786
- <h2 class="text-xl font-bold flex items-center gap-2" style="color: var(--color-fg-lightest);">
787
- <i data-lucide="database"></i> Token Usage
984
+ <h2 class="section-heading mb-1">
985
+ <i data-lucide="database" class="h-5 w-5" style="color: var(--color-blue);"></i>
986
+ <span>Token Usage</span>
788
987
  </h2>
789
- <p class="mt-1 text-xs" style="color: var(--color-gray);">${escapeHtml(
988
+ <p class="section-subtitle mb-0">${escapeHtml(
790
989
  renderTokenUsageRangeText(state.tokenUsagePeriod, activeRange)
791
990
  )}</p>
792
991
  </div>
793
- <div class="flex flex-wrap items-center gap-2 text-xs" style="color: var(--color-gray-accent);">
992
+ <div class="flex flex-wrap items-center gap-2 text-xs font-medium" style="color: var(--color-gray);">
794
993
  ${
795
994
  state.isTokenUsageLoading
796
- ? "<span>Refreshing summary...</span>"
995
+ ? `<span class="rounded-md px-2 py-1" style="background-color: rgba(37, 99, 235, 0.08); color: var(--color-blue);">Refreshing summary...</span>`
797
996
  : ""
798
997
  }
799
998
  ${
800
999
  state.isEventsLoading
801
- ? "<span>Refreshing details...</span>"
1000
+ ? `<span class="rounded-md px-2 py-1" style="background-color: rgba(37, 99, 235, 0.08); color: var(--color-blue);">Refreshing details...</span>`
802
1001
  : ""
803
1002
  }
804
1003
  </div>
@@ -813,7 +1012,7 @@
813
1012
  : ""
814
1013
  }
815
1014
 
816
- <div class="grid grid-cols-2 lg:grid-cols-3 xl:grid-cols-6 gap-3 ${
1015
+ <div class="metric-grid ${
817
1016
  state.isTokenUsageLoading ? "opacity-60" : ""
818
1017
  }">
819
1018
  ${renderTokenUsageMetric(
@@ -858,48 +1057,48 @@
858
1057
  state.tokenUsageDailyError
859
1058
  )}
860
1059
 
861
- <div class="mt-4 border" style="background-color: var(--color-bg); border-color: var(--color-bg-light-2);">
862
- <div class="px-4 py-3 border-b flex items-center justify-between gap-3" style="background-color: var(--color-bg-soft); border-color: var(--color-bg-light-2);">
863
- <div>
864
- <p class="text-sm font-semibold" style="color: var(--color-fg-lightest);">By Model</p>
865
- <p class="mt-1 text-xs" style="color: var(--color-gray-accent);">${summary ? `${formatNumber(summary.byModel.length)} models` : "No summary rows loaded."}</p>
1060
+ <div class="mt-4 grid grid-cols-1 2xl:grid-cols-[minmax(0,1fr)_minmax(0,1.18fr)] gap-4">
1061
+ <div class="panel">
1062
+ <div class="panel-header">
1063
+ <div>
1064
+ <p class="panel-title">Model Breakdown</p>
1065
+ <p class="panel-meta">${summary ? `${formatNumber(summary.byModel.length)} models` : "No summary rows loaded."}</p>
1066
+ </div>
866
1067
  </div>
1068
+ ${renderTokenUsageModelBreakdown(summary)}
867
1069
  </div>
868
- ${renderTokenUsageModelBreakdown(summary)}
869
- </div>
870
1070
 
871
- <div class="mt-4 border" style="background-color: var(--color-bg); border-color: var(--color-bg-light-2);">
872
- <div class="px-4 py-3 border-b flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between" style="background-color: var(--color-bg-soft); border-color: var(--color-bg-light-2);">
873
- <div>
874
- <p class="text-sm font-semibold" style="color: var(--color-fg-lightest);">Event Details</p>
875
- <p class="mt-1 text-xs" style="color: var(--color-gray-accent);">${escapeHtml(
876
- eventsMeta
877
- )}</p>
878
- </div>
879
- <div class="flex items-center gap-2">
880
- ${renderPaginationButton(
881
- "previous",
882
- "Previous",
883
- !eventsPage || state.isEventsLoading || eventsPage.page <= 1
884
- )}
885
- ${renderPaginationButton(
886
- "next",
887
- "Next",
888
- !eventsPage
889
- || state.isEventsLoading
890
- || eventsPage.page >= totalPages
891
- )}
1071
+ <div class="panel">
1072
+ <div class="panel-header flex-col items-start sm:flex-row sm:items-center">
1073
+ <div>
1074
+ <p class="panel-title">Request Events</p>
1075
+ <p class="panel-meta">${escapeHtml(eventsMeta)}</p>
1076
+ </div>
1077
+ <div class="flex items-center gap-2">
1078
+ ${renderPaginationButton(
1079
+ "previous",
1080
+ "Previous",
1081
+ !eventsPage || state.isEventsLoading || eventsPage.page <= 1
1082
+ )}
1083
+ ${renderPaginationButton(
1084
+ "next",
1085
+ "Next",
1086
+ !eventsPage
1087
+ || state.isEventsLoading
1088
+ || eventsPage.page >= totalPages
1089
+ )}
1090
+ </div>
892
1091
  </div>
1092
+ ${
1093
+ state.tokenUsageEventsError
1094
+ ? `<div class="p-4">${renderError(
1095
+ state.tokenUsageEventsError,
1096
+ "Token usage details failed"
1097
+ )}</div>`
1098
+ : ""
1099
+ }
1100
+ ${renderTokenUsageEventsTable(eventsPage)}
893
1101
  </div>
894
- ${
895
- state.tokenUsageEventsError
896
- ? `<div class="p-4">${renderError(
897
- state.tokenUsageEventsError,
898
- "Token usage details failed"
899
- )}</div>`
900
- : ""
901
- }
902
- ${renderTokenUsageEventsTable(eventsPage)}
903
1102
  </div>
904
1103
  </section>
905
1104
  `;
@@ -973,28 +1172,84 @@
973
1172
  ].join("");
974
1173
 
975
1174
  return `
976
- <div class="mt-4 border" style="background-color: var(--color-bg); border-color: var(--color-bg-light-2);">
977
- <div class="px-4 py-3 border-b flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between" style="background-color: var(--color-bg-soft); border-color: var(--color-bg-light-2);">
978
- <p class="text-sm font-semibold" style="color: var(--color-fg-lightest);">Daily Trend</p>
979
- <select
980
- data-token-usage-trend-model
981
- class="px-3 py-1.5 border input-focus text-xs"
982
- style="background-color: var(--color-bg); border-color: var(--color-bg-light-2); color: var(--color-fg-light);"
983
- >${options}</select>
1175
+ <div class="mt-4 grid grid-cols-1 xl:grid-cols-[minmax(0,1fr)_304px] gap-4">
1176
+ <div class="panel">
1177
+ <div class="panel-header">
1178
+ <div>
1179
+ <p class="panel-title">Token Usage Over Time</p>
1180
+ <p class="panel-meta">${escapeHtml(
1181
+ renderTokenUsageRangeText(
1182
+ state.tokenUsagePeriod,
1183
+ dailySummary?.range || null
1184
+ )
1185
+ )}</p>
1186
+ </div>
1187
+ </div>
1188
+ ${
1189
+ error
1190
+ ? `<div class="p-4">${renderError(
1191
+ error,
1192
+ "Token usage daily trend failed"
1193
+ )}</div>`
1194
+ : ""
1195
+ }
1196
+ ${renderTokenUsageTrendChart(dailySummary, selectedModel)}
984
1197
  </div>
985
- ${
986
- error
987
- ? `<div class="p-4">${renderError(
988
- error,
989
- "Token usage daily trend failed"
990
- )}</div>`
991
- : ""
992
- }
993
- ${renderTokenUsageTrendChart(dailySummary, selectedModel)}
1198
+
1199
+ <aside class="panel">
1200
+ <div class="panel-header">
1201
+ <div>
1202
+ <p class="panel-title">Filters</p>
1203
+ <p class="panel-meta">Applied to the trend chart</p>
1204
+ </div>
1205
+ </div>
1206
+ <div class="p-4 space-y-4">
1207
+ <div>
1208
+ <label class="field-label">Model</label>
1209
+ <select
1210
+ data-token-usage-trend-model
1211
+ class="select-control input-focus text-sm"
1212
+ >${options}</select>
1213
+ </div>
1214
+ <div>
1215
+ <div class="field-label">Metric</div>
1216
+ <div class="flex flex-wrap gap-2">
1217
+ ${renderTokenUsageTrendMetricButtons()}
1218
+ </div>
1219
+ </div>
1220
+ </div>
1221
+ </aside>
994
1222
  </div>
995
1223
  `;
996
1224
  }
997
1225
 
1226
+ function renderTokenUsageTrendMetricButtons() {
1227
+ const metrics = getTokenUsageTrendMetrics();
1228
+ return [
1229
+ {
1230
+ color: "var(--color-fg-lightest)",
1231
+ key: ALL_METRICS_VALUE,
1232
+ label: "All",
1233
+ },
1234
+ ...metrics,
1235
+ ]
1236
+ .map((metric) => {
1237
+ const isActive = state.tokenUsageTrendMetric === metric.key;
1238
+ return `
1239
+ <button
1240
+ type="button"
1241
+ data-trend-metric="${metric.key}"
1242
+ class="pill-button inline-flex items-center gap-1.5 px-2.5 py-1.5 text-xs font-semibold"
1243
+ style="background-color: ${isActive ? "rgba(37, 99, 235, 0.1)" : "var(--color-bg)"}; border-color: ${isActive ? "rgba(37, 99, 235, 0.28)" : "var(--color-bg-light-2)"}; color: ${isActive ? "var(--color-blue)" : "var(--color-fg-medium)"};"
1244
+ >
1245
+ <span class="h-2 w-2 rounded-full" style="background-color: ${metric.color};"></span>
1246
+ ${escapeHtml(metric.label)}
1247
+ </button>
1248
+ `;
1249
+ })
1250
+ .join("");
1251
+ }
1252
+
998
1253
  function renderTokenUsageTrendChart(dailySummary, selectedModel) {
999
1254
  const days = dailySummary?.days || [];
1000
1255
  const metrics = getTokenUsageTrendMetrics();
@@ -1155,33 +1410,14 @@
1155
1410
  })
1156
1411
  .join("");
1157
1412
  const legend = [
1158
- `<button type="button" data-trend-metric="${ALL_METRICS_VALUE}" class="inline-flex items-center border px-2 py-1 text-xs ${
1159
- state.tokenUsageTrendMetric === ALL_METRICS_VALUE
1160
- ? "font-semibold"
1161
- : ""
1162
- }" style="background-color: ${
1163
- state.tokenUsageTrendMetric === ALL_METRICS_VALUE
1164
- ? "var(--color-bg-light-1)"
1165
- : "var(--color-bg)"
1166
- }; border-color: var(--color-bg-light-2); color: var(--color-fg-light);">All</button>`,
1167
1413
  ...metrics.map(
1168
1414
  (metric) => `
1169
- <button type="button" data-trend-metric="${
1170
- metric.key
1171
- }" class="inline-flex items-center gap-1.5 border px-2 py-1 text-xs ${
1172
- state.tokenUsageTrendMetric === metric.key
1173
- ? "font-semibold"
1174
- : ""
1175
- }" style="background-color: ${
1176
- state.tokenUsageTrendMetric === metric.key
1177
- ? "var(--color-bg-light-1)"
1178
- : "var(--color-bg)"
1179
- }; border-color: var(--color-bg-light-2); color: var(--color-fg-light);">
1415
+ <span class="inline-flex items-center gap-1.5 text-xs font-medium" style="color: var(--color-gray);">
1180
1416
  <span class="h-2 w-2 rounded-full" style="background-color: ${
1181
1417
  metric.color
1182
1418
  };"></span>
1183
1419
  ${escapeHtml(metric.label)}
1184
- </button>
1420
+ </span>
1185
1421
  `
1186
1422
  ),
1187
1423
  ].join("");
@@ -1212,7 +1448,7 @@
1212
1448
  ${lines}
1213
1449
  ${hitRects}
1214
1450
  </svg>
1215
- <div class="mt-2 flex flex-wrap gap-3">
1451
+ <div class="mt-2 flex flex-wrap gap-5">
1216
1452
  ${legend}
1217
1453
  </div>
1218
1454
  ${activeValues}
@@ -1229,26 +1465,30 @@
1229
1465
 
1230
1466
  function renderTokenUsageMetric(label, value, accentColor) {
1231
1467
  return `
1232
- <div class="p-4 border" style="background-color: var(--color-bg); border-color: var(--color-bg-light-2);">
1233
- <div class="text-lg leading-6 font-bold" style="color: ${accentColor};">${renderTokenUsageValueLines(
1468
+ <div class="metric-card">
1469
+ <div class="metric-label">
1470
+ <span class="metric-dot" style="background-color: ${accentColor};"></span>
1471
+ <span>${escapeHtml(label)}</span>
1472
+ </div>
1473
+ <div class="metric-value" style="color: var(--color-fg-lightest);">${renderTokenUsageValueLines(
1234
1474
  typeof value === "number" ? formatNumber(value) : value
1235
1475
  )}</div>
1236
- <div class="mt-1 text-xs uppercase tracking-wide" style="color: var(--color-gray-accent);">${escapeHtml(
1237
- label
1238
- )}</div>
1476
+ <div class="metric-note">Current period</div>
1239
1477
  </div>
1240
1478
  `;
1241
1479
  }
1242
1480
 
1243
1481
  function renderTokenUsageCostMetric(label, value, accentColor) {
1244
1482
  return `
1245
- <div class="p-4 min-h-[90px] border" style="background-color: var(--color-bg); border-color: var(--color-bg-light-2);">
1246
- <div class="text-xs uppercase tracking-wide" style="color: var(--color-gray-accent);">${escapeHtml(
1247
- label
1248
- )}</div>
1249
- <div class="mt-2 min-h-[3rem] text-lg leading-6 font-bold" style="color: ${accentColor};">${renderTokenUsageValueLines(
1483
+ <div class="metric-card">
1484
+ <div class="metric-label">
1485
+ <span class="metric-dot" style="background-color: ${accentColor};"></span>
1486
+ <span>${escapeHtml(label)}</span>
1487
+ </div>
1488
+ <div class="metric-value" style="color: var(--color-fg-lightest);">${renderTokenUsageValueLines(
1250
1489
  value
1251
1490
  )}</div>
1491
+ <div class="metric-note">Estimated total</div>
1252
1492
  </div>
1253
1493
  `;
1254
1494
  }
@@ -1297,8 +1537,8 @@
1297
1537
 
1298
1538
  return `
1299
1539
  <div class="overflow-auto ${state.isTokenUsageLoading ? "opacity-60" : ""}">
1300
- <table class="w-full min-w-[860px] text-left text-xs sm:text-sm">
1301
- <thead style="background-color: var(--color-bg-light-1); color: var(--color-fg-medium);">
1540
+ <table class="data-table min-w-[860px] text-left">
1541
+ <thead>
1302
1542
  <tr>
1303
1543
  <th class="px-4 py-2 font-semibold">Model</th>
1304
1544
  <th class="px-4 py-2 text-right font-semibold">Requests</th>
@@ -1376,8 +1616,8 @@
1376
1616
 
1377
1617
  return `
1378
1618
  <div class="overflow-auto ${state.isEventsLoading ? "opacity-60" : ""}">
1379
- <table class="w-full min-w-[1000px] text-left text-xs sm:text-sm">
1380
- <thead style="background-color: var(--color-bg-light-1); color: var(--color-fg-medium);">
1619
+ <table class="data-table min-w-[1000px] text-left">
1620
+ <thead>
1381
1621
  <tr>
1382
1622
  <th class="px-4 py-2 font-semibold">Time</th>
1383
1623
  <th class="px-4 py-2 font-semibold">User</th>
@@ -1406,8 +1646,7 @@
1406
1646
  <button
1407
1647
  type="button"
1408
1648
  data-page-action="${action}"
1409
- class="px-3 py-1.5 border text-xs font-medium"
1410
- style="background-color: var(--color-bg-darkest); border-color: var(--color-bg-light-2); color: var(--color-fg-light);"
1649
+ class="pill-button px-3 py-1.5 text-xs font-semibold"
1411
1650
  ${disabled ? "disabled" : ""}
1412
1651
  >
1413
1652
  ${label}
@@ -1417,7 +1656,7 @@
1417
1656
 
1418
1657
  function renderEmptyState(message) {
1419
1658
  return `
1420
- <div class="px-4 py-6 text-sm" style="color: var(--color-gray);">
1659
+ <div class="px-4 py-8 text-sm" style="color: var(--color-gray);">
1421
1660
  ${escapeHtml(message)}
1422
1661
  </div>
1423
1662
  `;
@@ -1430,7 +1669,7 @@
1430
1669
  function renderSpinner() {
1431
1670
  return `
1432
1671
  <div class="flex justify-center items-center py-20">
1433
- <div class="animate-spin h-12 w-12 rounded-full border-4 border-transparent border-t-4" style="border-top-color: var(--color-blue);"></div>
1672
+ <div class="animate-spin h-12 w-12 rounded-full border-4 border-transparent border-t-4" style="border-top-color: var(--color-blue); background-color: rgba(255, 255, 255, 0.65);"></div>
1434
1673
  </div>`;
1435
1674
  }
1436
1675
 
@@ -1442,8 +1681,8 @@
1442
1681
  function renderError(message, title = "An Error Occurred") {
1443
1682
  return `
1444
1683
  <div
1445
- class="p-3 border"
1446
- style="background-color: rgba(204, 36, 29, 0.2); border-color: var(--color-red); color: var(--color-red-accent);"
1684
+ class="p-3 border rounded-lg"
1685
+ style="background-color: rgba(220, 38, 38, 0.08); border-color: rgba(220, 38, 38, 0.22); color: var(--color-red);"
1447
1686
  role="alert"
1448
1687
  >
1449
1688
  <div class="flex items-start">
@@ -1463,10 +1702,10 @@
1463
1702
  */
1464
1703
  function renderWelcomeMessage() {
1465
1704
  return `
1466
- <div class="text-center py-16 px-4 border" style="background-color: var(--color-bg-soft); border-color: var(--color-bg-light-2);">
1467
- <i data-lucide="info" class="mx-auto h-10 w-10" style="color: var(--color-gray-accent);"></i>
1468
- <h3 class="mt-2 text-lg font-semibold" style="color: var(--color-fg-lightest);">Welcome!</h3>
1469
- <p class="mt-1 text-sm" style="color: var(--color-gray);">Enter a usage endpoint, optionally provide an x-api-key, and click "Fetch" to load usage and token usage data.</p>
1705
+ <div class="panel text-center py-16 px-4">
1706
+ <i data-lucide="info" class="mx-auto h-10 w-10" style="color: var(--color-blue);"></i>
1707
+ <h3 class="mt-3 text-lg font-bold" style="color: var(--color-fg-lightest);">Usage data is ready to load</h3>
1708
+ <p class="mt-2 text-sm" style="color: var(--color-gray);">Refresh the configured endpoint to view Copilot quotas, token totals, trends, and request events.</p>
1470
1709
  </div>
1471
1710
  `;
1472
1711
  }
@@ -1812,36 +2051,5 @@
1812
2051
  });
1813
2052
  </script>
1814
2053
 
1815
- <footer
1816
- class="text-center py-4 text-xs"
1817
- style="color: var(--color-gray-accent)"
1818
- >
1819
- <p>
1820
- Vibe coded by
1821
- <a
1822
- href="https://gemini.google.com"
1823
- target="_blank"
1824
- rel="noopener noreferrer"
1825
- class="underline transition-colors"
1826
- style="color: var(--color-fg-dark)"
1827
- onmouseover="this.style.color='var(--color-fg-light)'"
1828
- onmouseout="this.style.color='var(--color-fg-dark)'"
1829
- >
1830
- Gemini
1831
- </a>
1832
- - Based on
1833
- <a
1834
- href="https://github.com/uheej0625/copilot-usage-viewer"
1835
- target="_blank"
1836
- rel="noopener noreferrer"
1837
- class="underline transition-colors"
1838
- style="color: var(--color-fg-dark)"
1839
- onmouseover="this.style.color='var(--color-fg-light)'"
1840
- onmouseout="this.style.color='var(--color-fg-dark)'"
1841
- >
1842
- copilot-usage-viewer</a
1843
- >
1844
- </p>
1845
- </footer>
1846
2054
  </body>
1847
2055
  </html>