@jeffreycao/copilot-api 1.13.3 → 1.13.5

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
+ }
101
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
+ }
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
  }
@@ -606,8 +783,8 @@
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,16 @@
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
+ `;
645
823
  }
646
824
 
647
825
  /**
@@ -665,26 +843,29 @@
665
843
  if (unlimited) progressBarColor = "var(--color-blue)";
666
844
 
667
845
  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
- `;
846
+ <div class="metric-card">
847
+ <div class="flex items-center justify-between gap-3">
848
+ <div class="metric-label capitalize">
849
+ <span class="metric-dot" style="background-color: ${progressBarColor};"></span>
850
+ <span>${escapeHtml(title.replace(/_/g, " "))}</span>
851
+ </div>
852
+ ${
853
+ unlimited
854
+ ? `<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>`
855
+ : `<span class="text-xs font-semibold" style="color: var(--color-gray);">${percentUsed.toFixed(1)}% used</span>`
856
+ }
857
+ </div>
858
+ <div class="mt-5">
859
+ <div class="w-full progress-bar-bg h-2">
860
+ <div class="progress-bar-fg h-2" style="width: ${unlimited ? 100 : percentUsed}%; background-color: ${progressBarColor};"></div>
861
+ </div>
862
+ </div>
863
+ <div class="mt-4 flex items-center justify-between gap-4 text-xs font-medium" style="color: var(--color-gray);">
864
+ <span>${used} / ${unlimited ? "∞" : entitlement.toLocaleString()}</span>
865
+ <span>${unlimited ? "∞" : remaining.toLocaleString()} remaining</span>
866
+ </div>
867
+ </div>
868
+ `;
688
869
  }
689
870
 
690
871
  /**
@@ -694,7 +875,7 @@
694
875
  */
695
876
  function formatObject(obj) {
696
877
  if (obj === null || typeof obj !== "object") {
697
- return `<span style="color: var(--color-green-accent);">${escapeHtml(JSON.stringify(obj))}</span>`;
878
+ return `<span style="color: var(--color-green);">${escapeHtml(JSON.stringify(obj))}</span>`;
698
879
  }
699
880
 
700
881
  return (
@@ -707,18 +888,18 @@
707
888
  if (Array.isArray(value)) {
708
889
  displayValue =
709
890
  value.length > 0
710
- ? `<span style='color: var(--color-gray-accent)'>[...${value.length} items]</span>`
711
- : `<span style='color: var(--color-gray-accent)'>[]</span>`;
891
+ ? `<span style='color: var(--color-gray)'>[...${value.length} items]</span>`
892
+ : `<span style='color: var(--color-gray)'>[]</span>`;
712
893
  } else if (typeof value === "object" && value !== null) {
713
894
  displayValue = formatObject(value);
714
895
  } 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>`;
896
+ displayValue = `<span class="font-semibold" style="color: ${value ? "var(--color-green)" : "var(--color-red)"}">${escapeHtml(String(value))}</span>`;
716
897
  } else {
717
- displayValue = `<span style="color: var(--color-blue-accent);">${escapeHtml(JSON.stringify(value))}</span>`;
898
+ displayValue = `<span style="color: var(--color-blue);">${escapeHtml(JSON.stringify(value))}</span>`;
718
899
  }
719
900
 
720
901
  return `<div class="mt-1">
721
- <span class="capitalize font-semibold" style="color: var(--color-fg-medium);">${formattedKey}:</span>
902
+ <span class="capitalize font-semibold" style="color: var(--color-fg-lightest);">${formattedKey}:</span>
722
903
  ${typeof value === "object" && value !== null && !Array.isArray(value) ? displayValue : ` ${displayValue}`}
723
904
  </div>`;
724
905
  })
@@ -739,15 +920,16 @@
739
920
 
740
921
  const formattedDetails = formatObject(data);
741
922
  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
- `;
923
+ <section id="detailed-data" class="dashboard-section">
924
+ <h2 class="section-heading">
925
+ <i data-lucide="file-text" class="h-5 w-5" style="color: var(--color-blue);"></i>
926
+ <span>Copilot Usage API Response</span>
927
+ </h2>
928
+ <div class="panel p-4 relative font-mono text-xs code-block overflow-auto" style="max-height: 420px;">
929
+ ${formattedDetails}
930
+ </div>
931
+ </section>
932
+ `;
751
933
  }
752
934
 
753
935
  function renderTokenUsageSection() {
@@ -774,31 +956,32 @@
774
956
  summary?.range || dailySummary?.range || eventsPage?.range || null;
775
957
  const totalPages = eventsPage ? Math.max(eventsPage.total_pages, 1) : 1;
776
958
  const eventsMeta = eventsPage
777
- ? `Page ${eventsPage.page} / ${totalPages} · ${formatNumber(
959
+ ? `Page ${eventsPage.page} / ${totalPages} - ${formatNumber(
778
960
  eventsPage.total
779
961
  )} events`
780
962
  : "No detail page loaded yet.";
781
963
 
782
964
  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">
965
+ <section id="token-usage" class="dashboard-section">
966
+ <div class="mb-3 flex flex-col gap-2 sm:flex-row sm:items-start sm:justify-between">
785
967
  <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
968
+ <h2 class="section-heading mb-1">
969
+ <i data-lucide="database" class="h-5 w-5" style="color: var(--color-blue);"></i>
970
+ <span>Token Usage</span>
788
971
  </h2>
789
- <p class="mt-1 text-xs" style="color: var(--color-gray);">${escapeHtml(
972
+ <p class="section-subtitle mb-0">${escapeHtml(
790
973
  renderTokenUsageRangeText(state.tokenUsagePeriod, activeRange)
791
974
  )}</p>
792
975
  </div>
793
- <div class="flex flex-wrap items-center gap-2 text-xs" style="color: var(--color-gray-accent);">
976
+ <div class="flex flex-wrap items-center gap-2 text-xs font-medium" style="color: var(--color-gray);">
794
977
  ${
795
978
  state.isTokenUsageLoading
796
- ? "<span>Refreshing summary...</span>"
979
+ ? `<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
980
  : ""
798
981
  }
799
982
  ${
800
983
  state.isEventsLoading
801
- ? "<span>Refreshing details...</span>"
984
+ ? `<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
985
  : ""
803
986
  }
804
987
  </div>
@@ -813,7 +996,7 @@
813
996
  : ""
814
997
  }
815
998
 
816
- <div class="grid grid-cols-2 lg:grid-cols-3 xl:grid-cols-6 gap-3 ${
999
+ <div class="metric-grid ${
817
1000
  state.isTokenUsageLoading ? "opacity-60" : ""
818
1001
  }">
819
1002
  ${renderTokenUsageMetric(
@@ -858,48 +1041,48 @@
858
1041
  state.tokenUsageDailyError
859
1042
  )}
860
1043
 
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>
1044
+ <div class="mt-4 grid grid-cols-1 2xl:grid-cols-[minmax(0,1fr)_minmax(0,1.18fr)] gap-4">
1045
+ <div class="panel">
1046
+ <div class="panel-header">
1047
+ <div>
1048
+ <p class="panel-title">Model Breakdown</p>
1049
+ <p class="panel-meta">${summary ? `${formatNumber(summary.byModel.length)} models` : "No summary rows loaded."}</p>
1050
+ </div>
866
1051
  </div>
1052
+ ${renderTokenUsageModelBreakdown(summary)}
867
1053
  </div>
868
- ${renderTokenUsageModelBreakdown(summary)}
869
- </div>
870
1054
 
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
- )}
1055
+ <div class="panel">
1056
+ <div class="panel-header flex-col items-start sm:flex-row sm:items-center">
1057
+ <div>
1058
+ <p class="panel-title">Request Events</p>
1059
+ <p class="panel-meta">${escapeHtml(eventsMeta)}</p>
1060
+ </div>
1061
+ <div class="flex items-center gap-2">
1062
+ ${renderPaginationButton(
1063
+ "previous",
1064
+ "Previous",
1065
+ !eventsPage || state.isEventsLoading || eventsPage.page <= 1
1066
+ )}
1067
+ ${renderPaginationButton(
1068
+ "next",
1069
+ "Next",
1070
+ !eventsPage
1071
+ || state.isEventsLoading
1072
+ || eventsPage.page >= totalPages
1073
+ )}
1074
+ </div>
892
1075
  </div>
1076
+ ${
1077
+ state.tokenUsageEventsError
1078
+ ? `<div class="p-4">${renderError(
1079
+ state.tokenUsageEventsError,
1080
+ "Token usage details failed"
1081
+ )}</div>`
1082
+ : ""
1083
+ }
1084
+ ${renderTokenUsageEventsTable(eventsPage)}
893
1085
  </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
1086
  </div>
904
1087
  </section>
905
1088
  `;
@@ -973,28 +1156,84 @@
973
1156
  ].join("");
974
1157
 
975
1158
  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>
1159
+ <div class="mt-4 grid grid-cols-1 xl:grid-cols-[minmax(0,1fr)_304px] gap-4">
1160
+ <div class="panel">
1161
+ <div class="panel-header">
1162
+ <div>
1163
+ <p class="panel-title">Token Usage Over Time</p>
1164
+ <p class="panel-meta">${escapeHtml(
1165
+ renderTokenUsageRangeText(
1166
+ state.tokenUsagePeriod,
1167
+ dailySummary?.range || null
1168
+ )
1169
+ )}</p>
1170
+ </div>
1171
+ </div>
1172
+ ${
1173
+ error
1174
+ ? `<div class="p-4">${renderError(
1175
+ error,
1176
+ "Token usage daily trend failed"
1177
+ )}</div>`
1178
+ : ""
1179
+ }
1180
+ ${renderTokenUsageTrendChart(dailySummary, selectedModel)}
984
1181
  </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)}
1182
+
1183
+ <aside class="panel">
1184
+ <div class="panel-header">
1185
+ <div>
1186
+ <p class="panel-title">Filters</p>
1187
+ <p class="panel-meta">Applied to the trend chart</p>
1188
+ </div>
1189
+ </div>
1190
+ <div class="p-4 space-y-4">
1191
+ <div>
1192
+ <label class="field-label">Model</label>
1193
+ <select
1194
+ data-token-usage-trend-model
1195
+ class="select-control input-focus text-sm"
1196
+ >${options}</select>
1197
+ </div>
1198
+ <div>
1199
+ <div class="field-label">Metric</div>
1200
+ <div class="flex flex-wrap gap-2">
1201
+ ${renderTokenUsageTrendMetricButtons()}
1202
+ </div>
1203
+ </div>
1204
+ </div>
1205
+ </aside>
994
1206
  </div>
995
1207
  `;
996
1208
  }
997
1209
 
1210
+ function renderTokenUsageTrendMetricButtons() {
1211
+ const metrics = getTokenUsageTrendMetrics();
1212
+ return [
1213
+ {
1214
+ color: "var(--color-fg-lightest)",
1215
+ key: ALL_METRICS_VALUE,
1216
+ label: "All",
1217
+ },
1218
+ ...metrics,
1219
+ ]
1220
+ .map((metric) => {
1221
+ const isActive = state.tokenUsageTrendMetric === metric.key;
1222
+ return `
1223
+ <button
1224
+ type="button"
1225
+ data-trend-metric="${metric.key}"
1226
+ class="pill-button inline-flex items-center gap-1.5 px-2.5 py-1.5 text-xs font-semibold"
1227
+ 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)"};"
1228
+ >
1229
+ <span class="h-2 w-2 rounded-full" style="background-color: ${metric.color};"></span>
1230
+ ${escapeHtml(metric.label)}
1231
+ </button>
1232
+ `;
1233
+ })
1234
+ .join("");
1235
+ }
1236
+
998
1237
  function renderTokenUsageTrendChart(dailySummary, selectedModel) {
999
1238
  const days = dailySummary?.days || [];
1000
1239
  const metrics = getTokenUsageTrendMetrics();
@@ -1155,33 +1394,14 @@
1155
1394
  })
1156
1395
  .join("");
1157
1396
  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
1397
  ...metrics.map(
1168
1398
  (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);">
1399
+ <span class="inline-flex items-center gap-1.5 text-xs font-medium" style="color: var(--color-gray);">
1180
1400
  <span class="h-2 w-2 rounded-full" style="background-color: ${
1181
1401
  metric.color
1182
1402
  };"></span>
1183
1403
  ${escapeHtml(metric.label)}
1184
- </button>
1404
+ </span>
1185
1405
  `
1186
1406
  ),
1187
1407
  ].join("");
@@ -1212,7 +1432,7 @@
1212
1432
  ${lines}
1213
1433
  ${hitRects}
1214
1434
  </svg>
1215
- <div class="mt-2 flex flex-wrap gap-3">
1435
+ <div class="mt-2 flex flex-wrap gap-5">
1216
1436
  ${legend}
1217
1437
  </div>
1218
1438
  ${activeValues}
@@ -1229,26 +1449,30 @@
1229
1449
 
1230
1450
  function renderTokenUsageMetric(label, value, accentColor) {
1231
1451
  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(
1452
+ <div class="metric-card">
1453
+ <div class="metric-label">
1454
+ <span class="metric-dot" style="background-color: ${accentColor};"></span>
1455
+ <span>${escapeHtml(label)}</span>
1456
+ </div>
1457
+ <div class="metric-value" style="color: var(--color-fg-lightest);">${renderTokenUsageValueLines(
1234
1458
  typeof value === "number" ? formatNumber(value) : value
1235
1459
  )}</div>
1236
- <div class="mt-1 text-xs uppercase tracking-wide" style="color: var(--color-gray-accent);">${escapeHtml(
1237
- label
1238
- )}</div>
1460
+ <div class="metric-note">Current period</div>
1239
1461
  </div>
1240
1462
  `;
1241
1463
  }
1242
1464
 
1243
1465
  function renderTokenUsageCostMetric(label, value, accentColor) {
1244
1466
  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(
1467
+ <div class="metric-card">
1468
+ <div class="metric-label">
1469
+ <span class="metric-dot" style="background-color: ${accentColor};"></span>
1470
+ <span>${escapeHtml(label)}</span>
1471
+ </div>
1472
+ <div class="metric-value" style="color: var(--color-fg-lightest);">${renderTokenUsageValueLines(
1250
1473
  value
1251
1474
  )}</div>
1475
+ <div class="metric-note">Estimated total</div>
1252
1476
  </div>
1253
1477
  `;
1254
1478
  }
@@ -1297,8 +1521,8 @@
1297
1521
 
1298
1522
  return `
1299
1523
  <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);">
1524
+ <table class="data-table min-w-[860px] text-left">
1525
+ <thead>
1302
1526
  <tr>
1303
1527
  <th class="px-4 py-2 font-semibold">Model</th>
1304
1528
  <th class="px-4 py-2 text-right font-semibold">Requests</th>
@@ -1376,8 +1600,8 @@
1376
1600
 
1377
1601
  return `
1378
1602
  <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);">
1603
+ <table class="data-table min-w-[1000px] text-left">
1604
+ <thead>
1381
1605
  <tr>
1382
1606
  <th class="px-4 py-2 font-semibold">Time</th>
1383
1607
  <th class="px-4 py-2 font-semibold">User</th>
@@ -1406,8 +1630,7 @@
1406
1630
  <button
1407
1631
  type="button"
1408
1632
  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);"
1633
+ class="pill-button px-3 py-1.5 text-xs font-semibold"
1411
1634
  ${disabled ? "disabled" : ""}
1412
1635
  >
1413
1636
  ${label}
@@ -1417,7 +1640,7 @@
1417
1640
 
1418
1641
  function renderEmptyState(message) {
1419
1642
  return `
1420
- <div class="px-4 py-6 text-sm" style="color: var(--color-gray);">
1643
+ <div class="px-4 py-8 text-sm" style="color: var(--color-gray);">
1421
1644
  ${escapeHtml(message)}
1422
1645
  </div>
1423
1646
  `;
@@ -1430,7 +1653,7 @@
1430
1653
  function renderSpinner() {
1431
1654
  return `
1432
1655
  <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>
1656
+ <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
1657
  </div>`;
1435
1658
  }
1436
1659
 
@@ -1442,8 +1665,8 @@
1442
1665
  function renderError(message, title = "An Error Occurred") {
1443
1666
  return `
1444
1667
  <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);"
1668
+ class="p-3 border rounded-lg"
1669
+ style="background-color: rgba(220, 38, 38, 0.08); border-color: rgba(220, 38, 38, 0.22); color: var(--color-red);"
1447
1670
  role="alert"
1448
1671
  >
1449
1672
  <div class="flex items-start">
@@ -1463,10 +1686,10 @@
1463
1686
  */
1464
1687
  function renderWelcomeMessage() {
1465
1688
  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>
1689
+ <div class="panel text-center py-16 px-4">
1690
+ <i data-lucide="info" class="mx-auto h-10 w-10" style="color: var(--color-blue);"></i>
1691
+ <h3 class="mt-3 text-lg font-bold" style="color: var(--color-fg-lightest);">Usage data is ready to load</h3>
1692
+ <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
1693
  </div>
1471
1694
  `;
1472
1695
  }
@@ -1812,36 +2035,5 @@
1812
2035
  });
1813
2036
  </script>
1814
2037
 
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
2038
  </body>
1847
2039
  </html>