@zhangferry-dev/tokendash 1.5.0 → 1.6.0
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/dist/client/assets/{index-BPWY9q0y.js → index-CY4G_b0x.js} +47 -47
- package/dist/client/assets/index-_yA9tOzZ.css +1 -0
- package/dist/client/index.html +2 -2
- package/dist/client/popover.html +822 -396
- package/dist/electron-server.cjs +91 -51
- package/dist/electron-server.cjs.map +2 -2
- package/dist/server/claudeBlocksParser.js +2 -2
- package/dist/server/claudeJsonlParser.d.ts +1 -1
- package/dist/server/claudeJsonlParser.js +22 -19
- package/dist/server/codexParser.js +72 -32
- package/electron/main.cjs +35 -50
- package/electron/preload.cjs +11 -0
- package/electron/updateService.cjs +148 -0
- package/package.json +1 -1
- package/dist/client/assets/index-iYDpTV63.css +0 -1
package/dist/client/popover.html
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
--border: oklch(88% 0.008 240);
|
|
15
15
|
--accent: oklch(69% 0.11 162);
|
|
16
16
|
--accent-strong: oklch(61% 0.13 162);
|
|
17
|
+
--notice: oklch(67% 0.16 72);
|
|
17
18
|
--error-bg: oklch(95% 0.02 80);
|
|
18
19
|
--error-fg: oklch(45% 0.08 25);
|
|
19
20
|
--shadow: 0 26px 70px rgba(20, 28, 38, 0.14), 0 8px 18px rgba(20, 28, 38, 0.08);
|
|
@@ -44,6 +45,8 @@
|
|
|
44
45
|
|
|
45
46
|
.shell {
|
|
46
47
|
position: relative;
|
|
48
|
+
display: flex;
|
|
49
|
+
flex-direction: column;
|
|
47
50
|
width: 100%;
|
|
48
51
|
height: 100%;
|
|
49
52
|
background: rgba(255, 255, 255, 0.76);
|
|
@@ -57,16 +60,37 @@
|
|
|
57
60
|
.toolbar {
|
|
58
61
|
display: flex;
|
|
59
62
|
align-items: center;
|
|
60
|
-
justify-content:
|
|
61
|
-
padding:
|
|
63
|
+
justify-content: space-between;
|
|
64
|
+
padding: 10px 24px 9px;
|
|
62
65
|
border-bottom: 1px solid rgba(225, 230, 228, 0.94);
|
|
63
66
|
background: linear-gradient(180deg, rgba(255, 255, 255, 0.84), rgba(248, 250, 249, 0.78));
|
|
64
67
|
}
|
|
65
68
|
|
|
66
69
|
.toolbar-label {
|
|
67
|
-
font-size:
|
|
68
|
-
|
|
70
|
+
font-size: 15px;
|
|
71
|
+
line-height: 1.1;
|
|
72
|
+
color: var(--fg);
|
|
69
73
|
margin: 0;
|
|
74
|
+
letter-spacing: 0;
|
|
75
|
+
font-weight: 500;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.toolbar-label strong {
|
|
79
|
+
font-weight: 700;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.cache-pill {
|
|
83
|
+
display: inline-flex;
|
|
84
|
+
align-items: center;
|
|
85
|
+
justify-content: center;
|
|
86
|
+
min-height: 22px;
|
|
87
|
+
padding: 0 9px;
|
|
88
|
+
border-radius: 8px;
|
|
89
|
+
border: 1px solid color-mix(in oklch, var(--accent-strong) 36%, var(--border));
|
|
90
|
+
background: color-mix(in oklch, var(--accent) 12%, white);
|
|
91
|
+
color: color-mix(in oklch, var(--accent-strong) 76%, black);
|
|
92
|
+
font-size: 11px;
|
|
93
|
+
font-weight: 600;
|
|
70
94
|
}
|
|
71
95
|
|
|
72
96
|
/* Error banner — shown when API is unreachable */
|
|
@@ -82,56 +106,105 @@
|
|
|
82
106
|
.error-banner.visible { display: block; }
|
|
83
107
|
|
|
84
108
|
.content {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
109
|
+
flex: 1 1 auto;
|
|
110
|
+
min-height: 0;
|
|
111
|
+
padding: 0 24px 0;
|
|
112
|
+
display: flex;
|
|
113
|
+
flex-direction: column;
|
|
90
114
|
background: linear-gradient(180deg, rgba(250, 252, 251, 0.85), rgba(244, 247, 245, 0.7));
|
|
91
115
|
}
|
|
92
116
|
|
|
93
117
|
.content.has-error {
|
|
94
|
-
height:
|
|
118
|
+
min-height: 0;
|
|
95
119
|
}
|
|
96
120
|
|
|
97
121
|
.summary {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
122
|
+
flex: 0 0 auto;
|
|
123
|
+
padding: 7px 0 7px;
|
|
124
|
+
border-bottom: 1px solid var(--border);
|
|
101
125
|
}
|
|
102
126
|
|
|
103
|
-
.
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
127
|
+
.summary-top {
|
|
128
|
+
display: grid;
|
|
129
|
+
grid-template-columns: minmax(0, 1fr) auto;
|
|
130
|
+
align-items: end;
|
|
131
|
+
gap: 12px;
|
|
132
|
+
margin-bottom: 8px;
|
|
108
133
|
}
|
|
109
134
|
|
|
110
135
|
.card-label {
|
|
111
136
|
margin: 0 0 6px;
|
|
112
|
-
font-size:
|
|
137
|
+
font-size: 12px;
|
|
113
138
|
color: var(--muted);
|
|
114
|
-
|
|
115
|
-
letter-spacing: 0.08em;
|
|
139
|
+
letter-spacing: 0;
|
|
116
140
|
}
|
|
117
141
|
|
|
118
142
|
.card-value {
|
|
119
143
|
margin: 0;
|
|
120
144
|
font-family: var(--font-display);
|
|
121
|
-
font-size:
|
|
122
|
-
line-height:
|
|
145
|
+
font-size: 32px;
|
|
146
|
+
line-height: 0.95;
|
|
123
147
|
letter-spacing: -0.05em;
|
|
124
148
|
font-weight: 700;
|
|
125
149
|
}
|
|
126
150
|
|
|
127
151
|
.card-value.empty { color: var(--muted); }
|
|
128
152
|
|
|
129
|
-
.
|
|
130
|
-
margin:
|
|
153
|
+
.cost-value {
|
|
154
|
+
margin: 0 0 2px;
|
|
155
|
+
font-family: var(--font-display);
|
|
156
|
+
font-size: 25px;
|
|
157
|
+
line-height: 1;
|
|
158
|
+
color: color-mix(in oklch, var(--accent-strong) 76%, black);
|
|
159
|
+
letter-spacing: -0.04em;
|
|
160
|
+
font-weight: 600;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.mini-metrics {
|
|
164
|
+
display: grid;
|
|
165
|
+
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
166
|
+
border-top: 1px solid var(--border);
|
|
167
|
+
padding-top: 7px;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.mini-metric {
|
|
171
|
+
min-width: 0;
|
|
172
|
+
padding-inline: 14px;
|
|
173
|
+
border-left: 1px solid var(--border);
|
|
174
|
+
text-align: center;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.mini-metric:first-child {
|
|
178
|
+
padding-left: 0;
|
|
179
|
+
border-left: 0;
|
|
180
|
+
text-align: left;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
.mini-metric:last-child {
|
|
184
|
+
padding-right: 0;
|
|
185
|
+
text-align: right;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.mini-label {
|
|
189
|
+
margin: 0 0 5px;
|
|
131
190
|
font-size: 11px;
|
|
132
191
|
color: var(--muted);
|
|
133
192
|
}
|
|
134
193
|
|
|
194
|
+
.mini-value {
|
|
195
|
+
margin: 0;
|
|
196
|
+
font-family: var(--font-display);
|
|
197
|
+
font-size: 15px;
|
|
198
|
+
line-height: 1;
|
|
199
|
+
color: var(--fg);
|
|
200
|
+
letter-spacing: -0.02em;
|
|
201
|
+
font-weight: 600;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.mini-value.input { color: color-mix(in oklch, var(--accent-strong) 76%, black); }
|
|
205
|
+
.mini-value.output { color: oklch(45% 0.13 255); }
|
|
206
|
+
.mini-value.cached { color: oklch(62% 0.15 75); }
|
|
207
|
+
|
|
135
208
|
/* Shimmer skeleton for loading state */
|
|
136
209
|
.skeleton {
|
|
137
210
|
display: inline-block;
|
|
@@ -149,56 +222,92 @@
|
|
|
149
222
|
}
|
|
150
223
|
|
|
151
224
|
.chart-panel {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
border:
|
|
155
|
-
|
|
225
|
+
flex: 0 0 auto;
|
|
226
|
+
padding: 9px 0 7px;
|
|
227
|
+
border-radius: 0;
|
|
228
|
+
border: 0;
|
|
229
|
+
border-bottom: 1px solid var(--border);
|
|
230
|
+
background: transparent;
|
|
156
231
|
}
|
|
157
232
|
|
|
158
233
|
.chart-header {
|
|
159
|
-
|
|
234
|
+
display: flex;
|
|
235
|
+
justify-content: flex-start;
|
|
236
|
+
align-items: baseline;
|
|
237
|
+
gap: 10px;
|
|
238
|
+
margin-bottom: 10px;
|
|
160
239
|
}
|
|
161
240
|
|
|
162
241
|
.eyebrow {
|
|
163
242
|
margin: 0;
|
|
164
|
-
font-family: var(--font-
|
|
165
|
-
font-size:
|
|
166
|
-
color: var(--
|
|
167
|
-
text-transform:
|
|
168
|
-
letter-spacing: 0
|
|
243
|
+
font-family: var(--font-body);
|
|
244
|
+
font-size: 13px;
|
|
245
|
+
color: var(--fg);
|
|
246
|
+
text-transform: none;
|
|
247
|
+
letter-spacing: 0;
|
|
248
|
+
font-weight: 700;
|
|
169
249
|
}
|
|
170
250
|
|
|
171
251
|
.chart-frame {
|
|
172
252
|
display: grid;
|
|
173
|
-
grid-template-columns:
|
|
174
|
-
|
|
253
|
+
grid-template-columns: 34px 1fr;
|
|
254
|
+
grid-template-rows: 88px 18px;
|
|
255
|
+
gap: 5px;
|
|
175
256
|
align-items: stretch;
|
|
176
|
-
|
|
257
|
+
row-gap: 5px;
|
|
177
258
|
}
|
|
178
259
|
|
|
179
260
|
.y-axis {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
align-items: flex-end;
|
|
184
|
-
width: max-content;
|
|
185
|
-
padding-block: 6px 24px;
|
|
261
|
+
position: relative;
|
|
262
|
+
grid-column: 1;
|
|
263
|
+
grid-row: 1;
|
|
186
264
|
color: var(--muted);
|
|
187
265
|
font-family: var(--font-mono);
|
|
188
|
-
font-size:
|
|
266
|
+
font-size: 9px;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
.y-axis span {
|
|
270
|
+
position: absolute;
|
|
271
|
+
inset-inline-end: 0;
|
|
272
|
+
line-height: 1;
|
|
273
|
+
transform: translateY(-50%);
|
|
189
274
|
}
|
|
190
275
|
|
|
191
276
|
.plot {
|
|
192
277
|
position: relative;
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
278
|
+
grid-column: 2;
|
|
279
|
+
grid-row: 1;
|
|
280
|
+
border-inline-start: 0;
|
|
281
|
+
border-block-end: 0;
|
|
282
|
+
padding: 0 10px 0 12px;
|
|
196
283
|
display: grid;
|
|
197
284
|
align-items: end;
|
|
198
|
-
gap:
|
|
199
|
-
background:
|
|
200
|
-
|
|
201
|
-
|
|
285
|
+
gap: 3px;
|
|
286
|
+
background: transparent;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
.plot::before {
|
|
290
|
+
content: "";
|
|
291
|
+
position: absolute;
|
|
292
|
+
inset-block: 0;
|
|
293
|
+
inset-inline-start: 0;
|
|
294
|
+
width: 1px;
|
|
295
|
+
background: rgba(180, 190, 187, 0.72);
|
|
296
|
+
pointer-events: none;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
.plot-grid {
|
|
300
|
+
position: absolute;
|
|
301
|
+
inset: 0;
|
|
302
|
+
pointer-events: none;
|
|
303
|
+
z-index: 0;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
.plot-grid-line {
|
|
307
|
+
position: absolute;
|
|
308
|
+
inset-inline: 0;
|
|
309
|
+
height: 1px;
|
|
310
|
+
border-top: 1px dashed rgba(174, 186, 181, 0.58);
|
|
202
311
|
}
|
|
203
312
|
|
|
204
313
|
/* Empty state for chart */
|
|
@@ -210,7 +319,7 @@
|
|
|
210
319
|
height: 100%;
|
|
211
320
|
gap: 6px;
|
|
212
321
|
color: var(--muted);
|
|
213
|
-
font-size:
|
|
322
|
+
font-size: 10px;
|
|
214
323
|
text-align: center;
|
|
215
324
|
}
|
|
216
325
|
|
|
@@ -232,30 +341,62 @@
|
|
|
232
341
|
display: flex;
|
|
233
342
|
align-items: end;
|
|
234
343
|
justify-content: center;
|
|
344
|
+
z-index: 1;
|
|
235
345
|
}
|
|
236
346
|
|
|
237
347
|
.bar {
|
|
238
348
|
width: 100%;
|
|
239
|
-
max-width:
|
|
240
|
-
border-radius:
|
|
349
|
+
max-width: 7px;
|
|
350
|
+
border-radius: 4px 4px 2px 2px;
|
|
241
351
|
background: linear-gradient(180deg, color-mix(in oklch, var(--accent) 60%, white), var(--accent));
|
|
242
352
|
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.45);
|
|
243
353
|
}
|
|
244
354
|
|
|
245
|
-
.bar.zero { opacity: 0
|
|
355
|
+
.bar.zero { opacity: 0; }
|
|
246
356
|
|
|
247
357
|
.bar.peak {
|
|
248
358
|
background: linear-gradient(180deg, color-mix(in oklch, var(--accent) 85%, white), var(--accent-strong));
|
|
249
359
|
}
|
|
250
360
|
|
|
251
361
|
.bar-label {
|
|
252
|
-
position: absolute;
|
|
253
|
-
inset-block-end: -20px;
|
|
254
362
|
font-family: var(--font-mono);
|
|
255
|
-
font-size:
|
|
363
|
+
font-size: 9px;
|
|
256
364
|
color: var(--muted);
|
|
257
365
|
}
|
|
258
366
|
|
|
367
|
+
.x-axis {
|
|
368
|
+
grid-column: 2;
|
|
369
|
+
grid-row: 2;
|
|
370
|
+
display: grid;
|
|
371
|
+
align-items: start;
|
|
372
|
+
padding: 0 10px 0 12px;
|
|
373
|
+
font-family: var(--font-mono);
|
|
374
|
+
font-size: 9px;
|
|
375
|
+
color: var(--muted);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
.x-axis-label {
|
|
379
|
+
justify-self: center;
|
|
380
|
+
position: relative;
|
|
381
|
+
line-height: 1;
|
|
382
|
+
white-space: nowrap;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
.x-axis-label::before {
|
|
386
|
+
content: "";
|
|
387
|
+
position: absolute;
|
|
388
|
+
inset-block-start: -7px;
|
|
389
|
+
inset-inline-start: 50%;
|
|
390
|
+
width: 1px;
|
|
391
|
+
height: 4px;
|
|
392
|
+
background: rgba(150, 162, 158, 0.46);
|
|
393
|
+
transform: translateX(-50%);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
.x-axis-label.is-unlabeled {
|
|
397
|
+
color: transparent;
|
|
398
|
+
}
|
|
399
|
+
|
|
259
400
|
.bar-tip {
|
|
260
401
|
position: absolute;
|
|
261
402
|
inset-block-end: calc(var(--height) + 4px);
|
|
@@ -269,62 +410,168 @@
|
|
|
269
410
|
white-space: nowrap;
|
|
270
411
|
}
|
|
271
412
|
|
|
413
|
+
.usage-panel {
|
|
414
|
+
flex: 0 0 auto;
|
|
415
|
+
padding: 6px 0 3px;
|
|
416
|
+
overflow: visible;
|
|
417
|
+
border-bottom: 1px solid rgba(211, 218, 215, 0.82);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
.usage-panel:last-of-type {
|
|
421
|
+
flex: 1 1 auto;
|
|
422
|
+
border-bottom: 0;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
.usage-panel + .usage-panel {
|
|
426
|
+
border-bottom: 0;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
.usage-title {
|
|
430
|
+
margin: 0;
|
|
431
|
+
font-size: 13px;
|
|
432
|
+
font-weight: 700;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
.usage-header {
|
|
436
|
+
display: grid;
|
|
437
|
+
grid-template-columns: minmax(0, 1fr) 44px 44px 50px;
|
|
438
|
+
align-items: center;
|
|
439
|
+
gap: 8px;
|
|
440
|
+
margin-bottom: 4px;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
.usage-header-stat {
|
|
444
|
+
font-family: var(--font-mono);
|
|
445
|
+
font-size: 9px;
|
|
446
|
+
color: var(--muted);
|
|
447
|
+
text-align: right;
|
|
448
|
+
text-transform: uppercase;
|
|
449
|
+
letter-spacing: 0.5px;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
.usage-list {
|
|
453
|
+
display: grid;
|
|
454
|
+
gap: 1px;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
.usage-row {
|
|
458
|
+
display: grid;
|
|
459
|
+
grid-template-columns: minmax(0, 1fr) 44px 44px 50px;
|
|
460
|
+
align-items: center;
|
|
461
|
+
gap: 8px;
|
|
462
|
+
min-height: 21px;
|
|
463
|
+
border-bottom: 0;
|
|
464
|
+
font-size: 11px;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
.usage-row + .usage-row {
|
|
468
|
+
box-shadow: none;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
.usage-name {
|
|
472
|
+
min-width: 0;
|
|
473
|
+
overflow: hidden;
|
|
474
|
+
text-overflow: ellipsis;
|
|
475
|
+
white-space: nowrap;
|
|
476
|
+
color: var(--fg);
|
|
477
|
+
font-weight: 500;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
.usage-stat {
|
|
481
|
+
font-family: var(--font-mono);
|
|
482
|
+
font-size: 10px;
|
|
483
|
+
white-space: nowrap;
|
|
484
|
+
text-align: right;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
.usage-stat.input { color: color-mix(in oklch, var(--accent-strong) 76%, black); }
|
|
488
|
+
.usage-stat.output { color: oklch(45% 0.13 255); }
|
|
489
|
+
.usage-stat.cached { color: oklch(62% 0.15 75); }
|
|
490
|
+
|
|
491
|
+
.usage-empty {
|
|
492
|
+
padding: 12px 0 11px;
|
|
493
|
+
color: var(--muted);
|
|
494
|
+
font-size: 12px;
|
|
495
|
+
text-align: center;
|
|
496
|
+
}
|
|
497
|
+
|
|
272
498
|
.actions {
|
|
273
499
|
display: grid;
|
|
274
500
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
275
|
-
gap:
|
|
276
|
-
|
|
501
|
+
gap: 0;
|
|
502
|
+
margin-top: auto;
|
|
503
|
+
margin-inline: -24px;
|
|
504
|
+
padding: 3px 18px 4px;
|
|
505
|
+
border-top: 1px solid var(--border);
|
|
506
|
+
background: rgba(248, 250, 249, 0.86);
|
|
277
507
|
}
|
|
278
508
|
|
|
279
509
|
.action {
|
|
280
510
|
appearance: none;
|
|
281
|
-
border:
|
|
282
|
-
border-radius:
|
|
283
|
-
min-height:
|
|
284
|
-
padding: 0
|
|
511
|
+
border: 0;
|
|
512
|
+
border-radius: 10px;
|
|
513
|
+
min-height: 25px;
|
|
514
|
+
padding: 0 6px;
|
|
285
515
|
font-family: var(--font-body);
|
|
286
|
-
font-size:
|
|
287
|
-
font-weight:
|
|
516
|
+
font-size: 11px;
|
|
517
|
+
font-weight: 500;
|
|
288
518
|
cursor: pointer;
|
|
289
519
|
transition: transform 160ms ease, background-color 160ms ease, border-color 160ms ease, opacity 160ms ease;
|
|
520
|
+
background: transparent;
|
|
521
|
+
color: var(--muted);
|
|
522
|
+
display: inline-flex;
|
|
523
|
+
align-items: center;
|
|
524
|
+
justify-content: center;
|
|
290
525
|
}
|
|
291
526
|
|
|
292
|
-
.action:hover {
|
|
527
|
+
.action:hover { background: rgba(233, 238, 235, 0.72); }
|
|
293
528
|
.action:disabled { opacity: 0.6; cursor: default; transform: none; }
|
|
294
529
|
|
|
295
|
-
.action
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
530
|
+
.action-icon {
|
|
531
|
+
display: inline-flex;
|
|
532
|
+
align-items: center;
|
|
533
|
+
justify-content: center;
|
|
534
|
+
width: 15px;
|
|
535
|
+
height: 15px;
|
|
536
|
+
margin-right: 6px;
|
|
537
|
+
vertical-align: -1px;
|
|
299
538
|
}
|
|
300
539
|
|
|
301
|
-
.action
|
|
302
|
-
|
|
303
|
-
|
|
540
|
+
.action-icon svg {
|
|
541
|
+
width: 15px;
|
|
542
|
+
height: 15px;
|
|
543
|
+
stroke: currentColor;
|
|
544
|
+
fill: none;
|
|
545
|
+
stroke-width: 2;
|
|
546
|
+
stroke-linecap: round;
|
|
547
|
+
stroke-linejoin: round;
|
|
304
548
|
}
|
|
305
549
|
|
|
550
|
+
.action.primary,
|
|
551
|
+
.action.secondary { color: var(--muted); }
|
|
552
|
+
|
|
306
553
|
.drawer-overlay {
|
|
307
554
|
position: absolute;
|
|
308
555
|
inset: 0;
|
|
309
|
-
background:
|
|
556
|
+
background: rgba(255, 255, 255, 0.76);
|
|
557
|
+
border: 1px solid rgba(255, 255, 255, 0.72);
|
|
558
|
+
border-radius: var(--radius-shell);
|
|
310
559
|
opacity: 0;
|
|
311
560
|
pointer-events: none;
|
|
312
561
|
transition: opacity 200ms ease;
|
|
313
562
|
overflow: hidden;
|
|
314
|
-
|
|
563
|
+
backdrop-filter: blur(28px);
|
|
315
564
|
}
|
|
316
565
|
|
|
317
566
|
.drawer {
|
|
318
567
|
height: 100%;
|
|
319
|
-
display:
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
padding: 14px;
|
|
568
|
+
display: flex;
|
|
569
|
+
flex-direction: column;
|
|
570
|
+
padding: 0 24px;
|
|
323
571
|
border-radius: var(--radius-shell);
|
|
324
|
-
background: rgba(
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
box-shadow: 0 18px 48px rgba(20, 28, 38, 0.14);
|
|
572
|
+
background: linear-gradient(180deg, rgba(250, 252, 251, 0.85), rgba(244, 247, 245, 0.7));
|
|
573
|
+
border: none;
|
|
574
|
+
box-shadow: none;
|
|
328
575
|
transform: translateY(10px);
|
|
329
576
|
opacity: 0;
|
|
330
577
|
transition: transform 220ms ease, opacity 220ms ease;
|
|
@@ -343,36 +590,67 @@
|
|
|
343
590
|
.drawer-header {
|
|
344
591
|
display: flex;
|
|
345
592
|
align-items: center;
|
|
346
|
-
|
|
593
|
+
gap: 8px;
|
|
594
|
+
min-height: 48px;
|
|
595
|
+
border-bottom: 1px solid var(--border);
|
|
347
596
|
}
|
|
348
597
|
|
|
349
598
|
.drawer-back {
|
|
350
599
|
appearance: none;
|
|
351
|
-
border:
|
|
352
|
-
background:
|
|
353
|
-
color: var(--
|
|
600
|
+
border: 0;
|
|
601
|
+
background: transparent;
|
|
602
|
+
color: var(--muted);
|
|
354
603
|
display: inline-flex;
|
|
355
604
|
align-items: center;
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
height:
|
|
359
|
-
padding: 0
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
font-
|
|
605
|
+
justify-content: center;
|
|
606
|
+
width: 22px;
|
|
607
|
+
height: 22px;
|
|
608
|
+
padding: 0;
|
|
609
|
+
margin-left: -4px;
|
|
610
|
+
border-radius: 6px;
|
|
611
|
+
font-size: 0;
|
|
363
612
|
font-family: var(--font-body);
|
|
364
613
|
cursor: pointer;
|
|
614
|
+
transition: background-color 160ms ease, color 160ms ease;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
.drawer-back:hover {
|
|
618
|
+
background: rgba(233, 238, 235, 0.72);
|
|
619
|
+
color: var(--fg);
|
|
365
620
|
}
|
|
366
621
|
|
|
367
622
|
.drawer-back-arrow {
|
|
368
|
-
|
|
369
|
-
|
|
623
|
+
display: inline-flex;
|
|
624
|
+
align-items: center;
|
|
625
|
+
justify-content: center;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
.drawer-back-arrow svg {
|
|
629
|
+
width: 15px;
|
|
630
|
+
height: 15px;
|
|
631
|
+
stroke: currentColor;
|
|
632
|
+
fill: none;
|
|
633
|
+
stroke-width: 2;
|
|
634
|
+
stroke-linecap: round;
|
|
635
|
+
stroke-linejoin: round;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
.drawer-heading {
|
|
639
|
+
min-width: 0;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
.drawer-title {
|
|
643
|
+
margin: 0;
|
|
644
|
+
font-size: 15px;
|
|
645
|
+
line-height: 1.1;
|
|
646
|
+
font-weight: 700;
|
|
370
647
|
}
|
|
371
648
|
|
|
372
649
|
.settings-list {
|
|
373
650
|
display: grid;
|
|
374
651
|
align-content: start;
|
|
375
|
-
gap:
|
|
652
|
+
gap: 0;
|
|
653
|
+
padding: 7px 0 0;
|
|
376
654
|
}
|
|
377
655
|
|
|
378
656
|
.settings-row {
|
|
@@ -380,10 +658,14 @@
|
|
|
380
658
|
align-items: center;
|
|
381
659
|
justify-content: space-between;
|
|
382
660
|
gap: 12px;
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
border: 1px solid var(--border);
|
|
386
|
-
background:
|
|
661
|
+
min-height: 48px;
|
|
662
|
+
padding: 6px 0;
|
|
663
|
+
border-bottom: 1px solid var(--border);
|
|
664
|
+
background: transparent;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
.settings-row.has-update {
|
|
668
|
+
border-color: color-mix(in oklch, var(--notice) 30%, var(--border));
|
|
387
669
|
}
|
|
388
670
|
|
|
389
671
|
.settings-copy {
|
|
@@ -392,24 +674,48 @@
|
|
|
392
674
|
|
|
393
675
|
.settings-label {
|
|
394
676
|
margin: 0;
|
|
395
|
-
font-size:
|
|
396
|
-
font-weight:
|
|
677
|
+
font-size: 12px;
|
|
678
|
+
font-weight: 700;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
.settings-title-line {
|
|
682
|
+
display: flex;
|
|
683
|
+
align-items: center;
|
|
684
|
+
gap: 6px;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
.update-pill {
|
|
688
|
+
display: none;
|
|
689
|
+
padding: 2px 6px;
|
|
690
|
+
border-radius: 7px;
|
|
691
|
+
background: color-mix(in oklch, var(--notice) 20%, white);
|
|
692
|
+
color: color-mix(in oklch, var(--notice) 82%, black);
|
|
693
|
+
font-family: var(--font-mono);
|
|
694
|
+
font-size: 9px;
|
|
695
|
+
font-weight: 700;
|
|
696
|
+
letter-spacing: 0;
|
|
697
|
+
text-transform: uppercase;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
.settings-row.has-update .update-pill {
|
|
701
|
+
display: inline-flex;
|
|
397
702
|
}
|
|
398
703
|
|
|
399
704
|
.settings-meta {
|
|
400
705
|
margin: 3px 0 0;
|
|
401
|
-
font-size:
|
|
706
|
+
font-size: 10px;
|
|
402
707
|
color: var(--muted);
|
|
708
|
+
line-height: 1.25;
|
|
403
709
|
}
|
|
404
710
|
|
|
405
711
|
.settings-value {
|
|
406
712
|
display: inline-flex;
|
|
407
713
|
align-items: center;
|
|
408
714
|
justify-content: center;
|
|
409
|
-
min-height:
|
|
410
|
-
min-width:
|
|
715
|
+
min-height: 26px;
|
|
716
|
+
min-width: 54px;
|
|
411
717
|
padding-inline: 10px;
|
|
412
|
-
border-radius:
|
|
718
|
+
border-radius: 9px;
|
|
413
719
|
border: 1px solid var(--border);
|
|
414
720
|
background: rgba(255, 255, 255, 0.96);
|
|
415
721
|
color: var(--muted);
|
|
@@ -421,92 +727,26 @@
|
|
|
421
727
|
.settings-value.button {
|
|
422
728
|
cursor: pointer;
|
|
423
729
|
color: var(--fg);
|
|
730
|
+
transition: background-color 160ms ease, border-color 160ms ease;
|
|
424
731
|
}
|
|
425
732
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
position: relative;
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
.agent-dropdown-trigger {
|
|
432
|
-
cursor: pointer;
|
|
433
|
-
color: var(--fg);
|
|
434
|
-
padding-right: 24px;
|
|
435
|
-
position: relative;
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
.agent-dropdown-trigger::after {
|
|
439
|
-
content: "▾";
|
|
440
|
-
position: absolute;
|
|
441
|
-
right: 10px;
|
|
442
|
-
font-size: 10px;
|
|
443
|
-
top: 50%;
|
|
444
|
-
transform: translateY(-50%);
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
.agent-dropdown-menu {
|
|
448
|
-
display: none;
|
|
449
|
-
position: absolute;
|
|
450
|
-
right: 0;
|
|
451
|
-
top: calc(100% + 6px);
|
|
452
|
-
min-width: 160px;
|
|
453
|
-
padding: 6px;
|
|
454
|
-
border-radius: 14px;
|
|
455
|
-
border: 1px solid var(--border);
|
|
456
|
-
background: white;
|
|
457
|
-
box-shadow: 0 8px 24px rgba(20, 28, 38, 0.12);
|
|
458
|
-
z-index: 10;
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
.agent-dropdown-menu.open { display: block; }
|
|
462
|
-
|
|
463
|
-
.agent-option {
|
|
464
|
-
display: flex;
|
|
465
|
-
align-items: center;
|
|
466
|
-
gap: 8px;
|
|
467
|
-
padding: 8px 10px;
|
|
468
|
-
border-radius: 10px;
|
|
469
|
-
cursor: pointer;
|
|
470
|
-
font-size: 13px;
|
|
471
|
-
font-weight: 500;
|
|
472
|
-
transition: background 120ms ease;
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
.agent-option:hover { background: rgba(240, 244, 242, 0.9); }
|
|
476
|
-
|
|
477
|
-
.agent-checkbox {
|
|
478
|
-
width: 16px;
|
|
479
|
-
height: 16px;
|
|
480
|
-
border-radius: 4px;
|
|
481
|
-
border: 1.5px solid var(--border);
|
|
482
|
-
display: flex;
|
|
483
|
-
align-items: center;
|
|
484
|
-
justify-content: center;
|
|
485
|
-
font-size: 10px;
|
|
486
|
-
flex-shrink: 0;
|
|
487
|
-
transition: background 120ms ease, border-color 120ms ease;
|
|
733
|
+
.settings-value.button:hover {
|
|
734
|
+
background: rgba(247, 249, 248, 0.96);
|
|
488
735
|
}
|
|
489
736
|
|
|
490
|
-
.
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
.agent-checkbox.checked::after {
|
|
496
|
-
content: "✓";
|
|
497
|
-
color: white;
|
|
737
|
+
.settings-value.button.primary-update {
|
|
738
|
+
border-color: color-mix(in oklch, var(--notice) 45%, var(--border));
|
|
739
|
+
background: color-mix(in oklch, var(--notice) 18%, white);
|
|
740
|
+
color: color-mix(in oklch, var(--notice) 74%, black);
|
|
498
741
|
font-weight: 700;
|
|
499
|
-
font-size: 10px;
|
|
500
742
|
}
|
|
501
743
|
|
|
502
744
|
.settings-divider {
|
|
503
|
-
height:
|
|
504
|
-
background: var(--border);
|
|
505
|
-
margin: 4px 0;
|
|
745
|
+
height: 8px;
|
|
506
746
|
}
|
|
507
747
|
|
|
508
748
|
.settings-quit-row {
|
|
509
|
-
border-
|
|
749
|
+
border-bottom: 0;
|
|
510
750
|
}
|
|
511
751
|
|
|
512
752
|
.settings-quit-btn {
|
|
@@ -514,19 +754,21 @@
|
|
|
514
754
|
}
|
|
515
755
|
|
|
516
756
|
.settings-version {
|
|
517
|
-
|
|
757
|
+
margin-top: auto;
|
|
758
|
+
border-top: 1px solid var(--border);
|
|
759
|
+
text-align: left;
|
|
518
760
|
font-family: var(--font-mono);
|
|
519
|
-
font-size:
|
|
761
|
+
font-size: 10px;
|
|
520
762
|
color: var(--muted);
|
|
521
|
-
letter-spacing: 0
|
|
522
|
-
text-transform:
|
|
523
|
-
padding-block:
|
|
763
|
+
letter-spacing: 0;
|
|
764
|
+
text-transform: none;
|
|
765
|
+
padding-block: 10px 13px;
|
|
524
766
|
}
|
|
525
767
|
|
|
526
768
|
.switch {
|
|
527
769
|
position: relative;
|
|
528
|
-
width:
|
|
529
|
-
height:
|
|
770
|
+
width: 42px;
|
|
771
|
+
height: 24px;
|
|
530
772
|
border-radius: 999px;
|
|
531
773
|
border: 1px solid color-mix(in srgb, #000 7%, transparent);
|
|
532
774
|
background: rgba(228, 231, 229, 0.9);
|
|
@@ -539,8 +781,8 @@
|
|
|
539
781
|
position: absolute;
|
|
540
782
|
inset-block-start: 2px;
|
|
541
783
|
inset-inline-start: 2px;
|
|
542
|
-
width:
|
|
543
|
-
height:
|
|
784
|
+
width: 18px;
|
|
785
|
+
height: 18px;
|
|
544
786
|
border-radius: 50%;
|
|
545
787
|
background: white;
|
|
546
788
|
box-shadow: 0 1px 3px rgba(20, 28, 38, 0.18);
|
|
@@ -559,7 +801,8 @@
|
|
|
559
801
|
<body>
|
|
560
802
|
<main class="shell" aria-label="Token dashboard menu" data-view="main">
|
|
561
803
|
<header class="toolbar">
|
|
562
|
-
<p class="toolbar-label">Today <span id="date"></span></p>
|
|
804
|
+
<p class="toolbar-label"><strong>Today</strong> · <span id="date"></span></p>
|
|
805
|
+
<div class="cache-pill" id="cache-pill">cache --</div>
|
|
563
806
|
</header>
|
|
564
807
|
|
|
565
808
|
<div class="error-banner" id="error-banner">
|
|
@@ -568,22 +811,27 @@
|
|
|
568
811
|
|
|
569
812
|
<section class="content" id="content">
|
|
570
813
|
<section class="summary" aria-label="Summary metrics">
|
|
571
|
-
<
|
|
572
|
-
<
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
<p class="
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
814
|
+
<div class="summary-top">
|
|
815
|
+
<div>
|
|
816
|
+
<p class="card-label">Total</p>
|
|
817
|
+
<p class="card-value" id="total-tokens"><span class="skeleton"></span></p>
|
|
818
|
+
</div>
|
|
819
|
+
<p class="cost-value" id="total-cost">$--</p>
|
|
820
|
+
</div>
|
|
821
|
+
<div class="mini-metrics" aria-label="Token breakdown">
|
|
822
|
+
<div class="mini-metric">
|
|
823
|
+
<p class="mini-label">In</p>
|
|
824
|
+
<p class="mini-value input" id="input"><span class="skeleton"></span></p>
|
|
825
|
+
</div>
|
|
826
|
+
<div class="mini-metric">
|
|
827
|
+
<p class="mini-label">Out</p>
|
|
828
|
+
<p class="mini-value output" id="output"><span class="skeleton"></span></p>
|
|
829
|
+
</div>
|
|
830
|
+
<div class="mini-metric">
|
|
831
|
+
<p class="mini-label">Cached</p>
|
|
832
|
+
<p class="mini-value cached" id="cached"><span class="skeleton"></span></p>
|
|
833
|
+
</div>
|
|
834
|
+
</div>
|
|
587
835
|
</section>
|
|
588
836
|
|
|
589
837
|
<section class="chart-panel" aria-label="Hourly usage">
|
|
@@ -594,17 +842,41 @@
|
|
|
594
842
|
<div class="y-axis" id="y-axis" aria-hidden="true"></div>
|
|
595
843
|
<div class="plot" id="plot">
|
|
596
844
|
<div class="chart-empty">
|
|
597
|
-
<div class="chart-empty-icon">📊</div>
|
|
598
845
|
<div class="chart-empty-title">No usage yet</div>
|
|
599
846
|
<div>Start a session to see your hourly breakdown.</div>
|
|
600
847
|
</div>
|
|
601
848
|
</div>
|
|
849
|
+
<div class="x-axis" id="x-axis" aria-hidden="true"></div>
|
|
850
|
+
</div>
|
|
851
|
+
</section>
|
|
852
|
+
|
|
853
|
+
<section class="usage-panel" aria-label="Projects">
|
|
854
|
+
<div class="usage-header">
|
|
855
|
+
<span class="usage-title">Projects</span>
|
|
856
|
+
<span class="usage-header-stat">in</span>
|
|
857
|
+
<span class="usage-header-stat">out</span>
|
|
858
|
+
<span class="usage-header-stat">cached</span>
|
|
859
|
+
</div>
|
|
860
|
+
<div class="usage-list" id="projects-list">
|
|
861
|
+
<div class="usage-empty">Loading projects...</div>
|
|
862
|
+
</div>
|
|
863
|
+
</section>
|
|
864
|
+
|
|
865
|
+
<section class="usage-panel" aria-label="Agents">
|
|
866
|
+
<div class="usage-header">
|
|
867
|
+
<span class="usage-title">Agents</span>
|
|
868
|
+
<span class="usage-header-stat">in</span>
|
|
869
|
+
<span class="usage-header-stat">out</span>
|
|
870
|
+
<span class="usage-header-stat">cached</span>
|
|
871
|
+
</div>
|
|
872
|
+
<div class="usage-list" id="agents-list">
|
|
873
|
+
<div class="usage-empty">Loading agents...</div>
|
|
602
874
|
</div>
|
|
603
875
|
</section>
|
|
604
876
|
|
|
605
877
|
<section class="actions" aria-label="Primary actions">
|
|
606
|
-
<button class="action primary" type="button" id="open-dashboard">
|
|
607
|
-
<button class="action secondary" type="button" id="open-settings">Settings</button>
|
|
878
|
+
<button class="action primary" type="button" id="open-dashboard"><span class="action-icon" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M3 3v18h18"/><path d="M18 17V9"/><path d="M13 17V5"/><path d="M8 17v-3"/></svg></span>Dashboard</button>
|
|
879
|
+
<button class="action secondary" type="button" id="open-settings"><span class="action-icon" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"/><circle cx="12" cy="12" r="3"/></svg></span>Settings</button>
|
|
608
880
|
</section>
|
|
609
881
|
</section>
|
|
610
882
|
|
|
@@ -612,14 +884,18 @@
|
|
|
612
884
|
<section class="drawer" aria-label="Settings drawer">
|
|
613
885
|
<div class="drawer-header">
|
|
614
886
|
<button class="drawer-back" type="button" aria-label="Back" id="close-settings">
|
|
615
|
-
<span class="drawer-back-arrow"
|
|
887
|
+
<span class="drawer-back-arrow"><svg viewBox="0 0 24 24"><path d="m15 18-6-6 6-6"/></svg></span>
|
|
616
888
|
</button>
|
|
889
|
+
<p class="drawer-title">Settings</p>
|
|
617
890
|
</div>
|
|
618
891
|
|
|
619
892
|
<div class="settings-list">
|
|
620
|
-
<article class="settings-row">
|
|
893
|
+
<article class="settings-row" id="update-row">
|
|
621
894
|
<div class="settings-copy">
|
|
622
|
-
<
|
|
895
|
+
<div class="settings-title-line">
|
|
896
|
+
<p class="settings-label">Check for updates</p>
|
|
897
|
+
<span class="update-pill" id="update-pill">New</span>
|
|
898
|
+
</div>
|
|
623
899
|
<p class="settings-meta" id="update-status">Check whether a newer build is available.</p>
|
|
624
900
|
</div>
|
|
625
901
|
<button class="settings-value button" type="button" id="check-updates">Check</button>
|
|
@@ -633,17 +909,6 @@
|
|
|
633
909
|
<button class="switch" type="button" id="launch-at-login" role="switch" aria-checked="false" aria-label="Launch at login"></button>
|
|
634
910
|
</article>
|
|
635
911
|
|
|
636
|
-
<article class="settings-row" id="agent-filter-row">
|
|
637
|
-
<div class="settings-copy">
|
|
638
|
-
<p class="settings-label">Data sources</p>
|
|
639
|
-
<p class="settings-meta">Filter which agents contribute to metrics.</p>
|
|
640
|
-
</div>
|
|
641
|
-
<div class="agent-dropdown-wrap">
|
|
642
|
-
<button class="settings-value button agent-dropdown-trigger" type="button" id="agent-dropdown-btn">All</button>
|
|
643
|
-
<div class="agent-dropdown-menu" id="agent-dropdown-menu"></div>
|
|
644
|
-
</div>
|
|
645
|
-
</article>
|
|
646
|
-
|
|
647
912
|
<div class="settings-divider"></div>
|
|
648
913
|
|
|
649
914
|
<article class="settings-row settings-quit-row">
|
|
@@ -665,16 +930,14 @@
|
|
|
665
930
|
var port = window.location.port || '3456';
|
|
666
931
|
var apiBase = 'http://localhost:' + port;
|
|
667
932
|
var appInfo = { version: '--', launchAtLogin: false };
|
|
668
|
-
var
|
|
669
|
-
var selectedAgents = [];
|
|
670
|
-
var isFirstLoad = true;
|
|
933
|
+
var updateInfo = null;
|
|
671
934
|
|
|
672
935
|
// --- Helpers ---
|
|
673
936
|
|
|
674
937
|
function formatNumber(n) {
|
|
675
938
|
if (!Number.isFinite(n) || n <= 0) return '0';
|
|
676
|
-
if (n >= 1e6) return (n / 1e6).toFixed(1) + 'M';
|
|
677
|
-
if (n >= 1e3) return (n / 1e3).toFixed(1) + 'K';
|
|
939
|
+
if (n >= 1e6) return trimTrailingZero((n / 1e6).toFixed(n >= 10e6 ? 1 : 2)) + 'M';
|
|
940
|
+
if (n >= 1e3) return trimTrailingZero((n / 1e3).toFixed(n >= 100e3 ? 0 : 1)) + 'K';
|
|
678
941
|
return String(Math.round(n));
|
|
679
942
|
}
|
|
680
943
|
|
|
@@ -682,6 +945,32 @@
|
|
|
682
945
|
return (Number.isFinite(n) ? n : 0).toFixed(1) + '%';
|
|
683
946
|
}
|
|
684
947
|
|
|
948
|
+
function formatCost(n) {
|
|
949
|
+
var value = Number(n) || 0;
|
|
950
|
+
if (value >= 100) return '$' + Math.round(value);
|
|
951
|
+
if (value >= 10) return '$' + value.toFixed(1);
|
|
952
|
+
return '$' + value.toFixed(2);
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
function trimTrailingZero(value) {
|
|
956
|
+
return String(value).replace(/\.0+$/, '').replace(/(\.\d*[1-9])0+$/, '$1');
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
function formatProjectName(path) {
|
|
960
|
+
var value = String(path || 'Unknown');
|
|
961
|
+
var parts = value.split('/').filter(Boolean);
|
|
962
|
+
return parts[parts.length - 1] || value;
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
function escapeHtml(value) {
|
|
966
|
+
return String(value)
|
|
967
|
+
.replace(/&/g, '&')
|
|
968
|
+
.replace(/</g, '<')
|
|
969
|
+
.replace(/>/g, '>')
|
|
970
|
+
.replace(/"/g, '"')
|
|
971
|
+
.replace(/'/g, ''');
|
|
972
|
+
}
|
|
973
|
+
|
|
685
974
|
function getTodayString() {
|
|
686
975
|
var d = new Date();
|
|
687
976
|
return d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0') + '-' + String(d.getDate()).padStart(2, '0');
|
|
@@ -700,25 +989,17 @@
|
|
|
700
989
|
// --- Dynamic chart hours based on current time ---
|
|
701
990
|
|
|
702
991
|
function getChartHours() {
|
|
703
|
-
var now = new Date();
|
|
704
|
-
var currentHour = now.getHours();
|
|
705
992
|
var hours = [];
|
|
706
|
-
for (var h = 0; h
|
|
993
|
+
for (var h = 0; h < 24; h += 1) {
|
|
707
994
|
hours.push(h);
|
|
708
995
|
}
|
|
709
|
-
// At least 4 bars
|
|
710
|
-
while (hours.length < 4) {
|
|
711
|
-
var next = hours[hours.length - 1] + 2;
|
|
712
|
-
if (next > 23) break;
|
|
713
|
-
hours.push(next);
|
|
714
|
-
}
|
|
715
996
|
return hours;
|
|
716
997
|
}
|
|
717
998
|
|
|
718
999
|
// --- Loading & Error states ---
|
|
719
1000
|
|
|
720
1001
|
function showSkeletons() {
|
|
721
|
-
['total-tokens', 'input', 'output', '
|
|
1002
|
+
['total-tokens', 'input', 'output', 'cached'].forEach(function(id) {
|
|
722
1003
|
var el = document.getElementById(id);
|
|
723
1004
|
if (el && !el.querySelector('.skeleton')) return; // already has data
|
|
724
1005
|
if (el) el.innerHTML = '<span class="skeleton"></span>';
|
|
@@ -762,12 +1043,15 @@
|
|
|
762
1043
|
setCardValue('total-tokens', formatNumber(totalTokens), totalTokens === 0);
|
|
763
1044
|
setCardValue('input', formatNumber(totalInput), totalInput === 0);
|
|
764
1045
|
setCardValue('output', formatNumber(totalOutput), totalOutput === 0);
|
|
765
|
-
setCardValue('
|
|
1046
|
+
setCardValue('cached', formatNumber(totalCacheRead), totalCacheRead === 0);
|
|
1047
|
+
document.getElementById('total-cost').textContent = formatCost(totalCost);
|
|
1048
|
+
applyMetricFit(document.getElementById('total-cost'), formatCost(totalCost));
|
|
1049
|
+
document.getElementById('cache-pill').textContent = 'cache ' + formatPercent(cacheRate);
|
|
766
1050
|
|
|
767
1051
|
return {
|
|
768
1052
|
today: todayStr,
|
|
769
|
-
agentKey: selectedAgents.slice().sort().join(','),
|
|
770
1053
|
totalTokens: totalTokens,
|
|
1054
|
+
totalInput: totalInput,
|
|
771
1055
|
totalCost: totalCost,
|
|
772
1056
|
totalCacheRead: totalCacheRead
|
|
773
1057
|
};
|
|
@@ -777,6 +1061,24 @@
|
|
|
777
1061
|
var el = document.getElementById(id);
|
|
778
1062
|
el.textContent = text;
|
|
779
1063
|
el.classList.toggle('empty', isEmpty);
|
|
1064
|
+
applyMetricFit(el, text);
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
function applyMetricFit(el, text) {
|
|
1068
|
+
if (!el) return;
|
|
1069
|
+
var length = String(text || '').length;
|
|
1070
|
+
|
|
1071
|
+
if (el.id === 'total-tokens') {
|
|
1072
|
+
el.style.fontSize = length >= 8 ? '27px' : length >= 6 ? '29px' : '32px';
|
|
1073
|
+
return;
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
if (el.id === 'total-cost') {
|
|
1077
|
+
el.style.fontSize = length >= 8 ? '21px' : length >= 6 ? '23px' : '25px';
|
|
1078
|
+
return;
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
el.style.fontSize = length >= 8 ? '13px' : '15px';
|
|
780
1082
|
}
|
|
781
1083
|
|
|
782
1084
|
// --- Chart rendering ---
|
|
@@ -807,10 +1109,12 @@
|
|
|
807
1109
|
|
|
808
1110
|
var plot = document.getElementById('plot');
|
|
809
1111
|
var axis = document.getElementById('y-axis');
|
|
1112
|
+
var xAxis = document.getElementById('x-axis');
|
|
810
1113
|
|
|
811
1114
|
// Empty state
|
|
812
1115
|
if (maxValue === 0) {
|
|
813
1116
|
axis.innerHTML = '';
|
|
1117
|
+
xAxis.innerHTML = '';
|
|
814
1118
|
plot.innerHTML = '';
|
|
815
1119
|
plot.style.gridTemplateColumns = '';
|
|
816
1120
|
plot.innerHTML = '<div class="chart-empty">' +
|
|
@@ -820,55 +1124,77 @@
|
|
|
820
1124
|
return;
|
|
821
1125
|
}
|
|
822
1126
|
|
|
823
|
-
var axisInfo =
|
|
824
|
-
renderAxis(
|
|
1127
|
+
var axisInfo = buildAxis(maxValue);
|
|
1128
|
+
renderAxis(axisInfo);
|
|
825
1129
|
var maxEntry = selected.reduce(function(max, entry) {
|
|
826
1130
|
return !max || entry.value > max.value ? entry : max;
|
|
827
1131
|
}, null);
|
|
828
|
-
renderBars(selected, axisInfo
|
|
1132
|
+
renderBars(selected, axisInfo, maxEntry);
|
|
829
1133
|
}
|
|
830
1134
|
|
|
831
|
-
function
|
|
832
|
-
|
|
833
|
-
if (value <= 0) return { top: 3, step: 1 };
|
|
834
|
-
var rawStep = value / 3;
|
|
1135
|
+
function niceIncrement(rawStep) {
|
|
1136
|
+
if (rawStep <= 0) return 1;
|
|
835
1137
|
var exp = Math.floor(Math.log10(rawStep));
|
|
836
1138
|
var base = Math.pow(10, exp);
|
|
837
1139
|
var frac = rawStep / base;
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
1140
|
+
if (frac <= 1) return base;
|
|
1141
|
+
if (frac <= 2) return 2 * base;
|
|
1142
|
+
if (frac <= 2.5) return 2.5 * base;
|
|
1143
|
+
if (frac <= 5) return 5 * base;
|
|
1144
|
+
return 10 * base;
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
function buildAxis(value) {
|
|
1148
|
+
if (value <= 0) return { top: 4, ticks: [4, 3, 2, 1, 0] };
|
|
1149
|
+
var step = niceIncrement(value / 4);
|
|
1150
|
+
var top = step * Math.ceil(value / step);
|
|
1151
|
+
if (top / value > 1.35 && top > step) top -= step;
|
|
1152
|
+
if (top < value) top += step;
|
|
1153
|
+
var segments = Math.max(3, Math.min(5, Math.round(top / step)));
|
|
1154
|
+
top = step * segments;
|
|
1155
|
+
|
|
1156
|
+
var ticks = [];
|
|
1157
|
+
for (var i = segments; i >= 0; i--) {
|
|
1158
|
+
ticks.push(step * i);
|
|
1159
|
+
}
|
|
1160
|
+
return { top: top, ticks: ticks };
|
|
842
1161
|
}
|
|
843
1162
|
|
|
844
|
-
function renderAxis(
|
|
1163
|
+
function renderAxis(info) {
|
|
845
1164
|
var axis = document.getElementById('y-axis');
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
'<span>' + formatNumber(
|
|
849
|
-
|
|
850
|
-
'<span>' + formatNumber(info.top - 2 * info.step) + '</span>',
|
|
851
|
-
'<span>0</span>'
|
|
852
|
-
].join('');
|
|
1165
|
+
axis.innerHTML = info.ticks.map(function(tick) {
|
|
1166
|
+
var top = ((info.top - tick) / info.top) * 100;
|
|
1167
|
+
return '<span style="top: ' + top + '%">' + formatNumber(tick) + '</span>';
|
|
1168
|
+
}).join('');
|
|
853
1169
|
}
|
|
854
1170
|
|
|
855
|
-
function renderBars(selected,
|
|
1171
|
+
function renderBars(selected, axisInfo, maxEntry) {
|
|
856
1172
|
var plot = document.getElementById('plot');
|
|
1173
|
+
var xAxis = document.getElementById('x-axis');
|
|
857
1174
|
plot.innerHTML = '';
|
|
858
|
-
plot.style.gridTemplateColumns = 'repeat(
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
1175
|
+
plot.style.gridTemplateColumns = 'repeat(24, minmax(0, 1fr))';
|
|
1176
|
+
xAxis.innerHTML = '';
|
|
1177
|
+
xAxis.style.gridTemplateColumns = 'repeat(24, minmax(0, 1fr))';
|
|
1178
|
+
renderGrid(plot, axisInfo);
|
|
1179
|
+
|
|
1180
|
+
// Dynamic gap and bar width based on bar count
|
|
1181
|
+
var count = selected.filter(function(entry) { return entry.value > 0; }).length;
|
|
1182
|
+
var gap = '2px';
|
|
1183
|
+
var barMaxWidth = count > 10 ? '7px' : count > 6 ? '9px' : '12px';
|
|
862
1184
|
plot.style.gap = gap;
|
|
1185
|
+
xAxis.style.gap = gap;
|
|
863
1186
|
|
|
864
1187
|
selected.forEach(function(entry) {
|
|
865
|
-
|
|
1188
|
+
if (entry.value <= 0) return;
|
|
1189
|
+
var height = entry.value > 0 ? Math.max(8, (entry.value / axisInfo.top) * 100) : 0;
|
|
866
1190
|
var wrap = document.createElement('div');
|
|
867
1191
|
wrap.className = 'bar-wrap';
|
|
1192
|
+
wrap.style.gridColumn = String(entry.hour + 1);
|
|
868
1193
|
wrap.style.setProperty('--height', height + '%');
|
|
869
1194
|
|
|
870
1195
|
var bar = document.createElement('div');
|
|
871
1196
|
bar.className = 'bar';
|
|
1197
|
+
bar.style.maxWidth = barMaxWidth + 'px';
|
|
872
1198
|
if (entry.value === 0) {
|
|
873
1199
|
bar.classList.add('zero');
|
|
874
1200
|
} else if (maxEntry && entry.hour === maxEntry.hour && entry.value === maxEntry.value) {
|
|
@@ -884,12 +1210,148 @@
|
|
|
884
1210
|
wrap.appendChild(tip);
|
|
885
1211
|
}
|
|
886
1212
|
|
|
1213
|
+
plot.appendChild(wrap);
|
|
1214
|
+
});
|
|
1215
|
+
|
|
1216
|
+
renderActiveHourLabels(xAxis, selected, maxEntry);
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
function renderGrid(plot, info) {
|
|
1220
|
+
var grid = document.createElement('div');
|
|
1221
|
+
grid.className = 'plot-grid';
|
|
1222
|
+
|
|
1223
|
+
info.ticks.forEach(function(tick) {
|
|
1224
|
+
var line = document.createElement('span');
|
|
1225
|
+
line.className = 'plot-grid-line';
|
|
1226
|
+
line.style.top = (((info.top - tick) / info.top) * 100) + '%';
|
|
1227
|
+
grid.appendChild(line);
|
|
1228
|
+
});
|
|
1229
|
+
|
|
1230
|
+
plot.appendChild(grid);
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
function renderActiveHourLabels(xAxis, selected, maxEntry) {
|
|
1234
|
+
var active = selected.filter(function(entry) { return entry.value > 0; });
|
|
1235
|
+
var labeledHours = [];
|
|
1236
|
+
|
|
1237
|
+
active.forEach(function(entry) {
|
|
1238
|
+
var isPeak = maxEntry && entry.hour === maxEntry.hour;
|
|
1239
|
+
var hasRoom = labeledHours.every(function(hour) {
|
|
1240
|
+
return Math.abs(entry.hour - hour) >= 3;
|
|
1241
|
+
});
|
|
1242
|
+
var shouldLabel = isPeak || hasRoom;
|
|
1243
|
+
|
|
1244
|
+
if (shouldLabel) labeledHours.push(entry.hour);
|
|
1245
|
+
|
|
887
1246
|
var label = document.createElement('span');
|
|
888
|
-
label.className = '
|
|
889
|
-
label.
|
|
890
|
-
|
|
1247
|
+
label.className = 'x-axis-label' + (shouldLabel ? '' : ' is-unlabeled');
|
|
1248
|
+
label.style.gridColumn = String(entry.hour + 1);
|
|
1249
|
+
label.textContent = String(entry.hour).padStart(2, '0') + ':00';
|
|
1250
|
+
xAxis.appendChild(label);
|
|
1251
|
+
});
|
|
1252
|
+
}
|
|
891
1253
|
|
|
892
|
-
|
|
1254
|
+
function renderProjects(allProjects) {
|
|
1255
|
+
var todayStr = getTodayString();
|
|
1256
|
+
var totals = {};
|
|
1257
|
+
|
|
1258
|
+
(allProjects || []).forEach(function(data) {
|
|
1259
|
+
var projects = data && data.projects;
|
|
1260
|
+
if (!projects) return;
|
|
1261
|
+
|
|
1262
|
+
Object.keys(projects).forEach(function(projectPath) {
|
|
1263
|
+
var todayEntries = (projects[projectPath] || []).filter(function(entry) {
|
|
1264
|
+
return entry && entry.date === todayStr;
|
|
1265
|
+
});
|
|
1266
|
+
if (todayEntries.length === 0) return;
|
|
1267
|
+
|
|
1268
|
+
if (!totals[projectPath]) {
|
|
1269
|
+
totals[projectPath] = { path: projectPath, input: 0, output: 0, cached: 0, total: 0 };
|
|
1270
|
+
}
|
|
1271
|
+
todayEntries.forEach(function(entry) {
|
|
1272
|
+
totals[projectPath].input += entry.inputTokens || 0;
|
|
1273
|
+
totals[projectPath].output += entry.outputTokens || 0;
|
|
1274
|
+
totals[projectPath].cached += entry.cacheReadTokens || 0;
|
|
1275
|
+
totals[projectPath].total += entry.totalTokens || 0;
|
|
1276
|
+
});
|
|
1277
|
+
});
|
|
1278
|
+
});
|
|
1279
|
+
|
|
1280
|
+
var rows = Object.keys(totals).map(function(key) { return totals[key]; })
|
|
1281
|
+
.sort(function(a, b) { return b.total - a.total; })
|
|
1282
|
+
.slice(0, 4);
|
|
1283
|
+
var list = document.getElementById('projects-list');
|
|
1284
|
+
|
|
1285
|
+
if (rows.length === 0) {
|
|
1286
|
+
list.innerHTML = '<div class="usage-empty">No project usage today.</div>';
|
|
1287
|
+
return;
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
list.innerHTML = '';
|
|
1291
|
+
rows.forEach(function(row) {
|
|
1292
|
+
var item = document.createElement('div');
|
|
1293
|
+
item.className = 'usage-row';
|
|
1294
|
+
item.innerHTML =
|
|
1295
|
+
'<span class="usage-name" title="' + escapeHtml(row.path) + '">' + escapeHtml(formatProjectName(row.path)) + '</span>' +
|
|
1296
|
+
'<span class="usage-stat input">' + formatNumber(row.input) + '</span>' +
|
|
1297
|
+
'<span class="usage-stat output">' + formatNumber(row.output) + '</span>' +
|
|
1298
|
+
'<span class="usage-stat cached">' + formatNumber(row.cached) + '</span>';
|
|
1299
|
+
list.appendChild(item);
|
|
1300
|
+
});
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
function formatAgentName(agent) {
|
|
1304
|
+
var names = {
|
|
1305
|
+
claude: 'Claude Code',
|
|
1306
|
+
codex: 'Codex',
|
|
1307
|
+
openclaw: 'OpenClaw',
|
|
1308
|
+
opencode: 'OpenCode'
|
|
1309
|
+
};
|
|
1310
|
+
return names[agent] || agent;
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
function renderAgents(allDaily, agents) {
|
|
1314
|
+
var todayStr = getTodayString();
|
|
1315
|
+
var rows = (agents || []).map(function(agent, index) {
|
|
1316
|
+
var data = (allDaily || [])[index];
|
|
1317
|
+
var entry = null;
|
|
1318
|
+
if (data && Array.isArray(data.daily)) {
|
|
1319
|
+
entry = data.daily.find(function(day) { return day && day.date === todayStr; });
|
|
1320
|
+
}
|
|
1321
|
+
var input = entry ? (entry.inputTokens || 0) : 0;
|
|
1322
|
+
var output = entry ? (entry.outputTokens || 0) : 0;
|
|
1323
|
+
var cached = entry ? (entry.cacheReadTokens || 0) : 0;
|
|
1324
|
+
return {
|
|
1325
|
+
name: formatAgentName(agent),
|
|
1326
|
+
input: input,
|
|
1327
|
+
output: output,
|
|
1328
|
+
cached: cached,
|
|
1329
|
+
total: entry ? (entry.totalTokens || input + output + cached) : 0
|
|
1330
|
+
};
|
|
1331
|
+
}).filter(function(row) {
|
|
1332
|
+
return row.total > 0 || row.input > 0 || row.output > 0;
|
|
1333
|
+
}).sort(function(a, b) {
|
|
1334
|
+
return b.total - a.total;
|
|
1335
|
+
});
|
|
1336
|
+
|
|
1337
|
+
var list = document.getElementById('agents-list');
|
|
1338
|
+
if (!list) return;
|
|
1339
|
+
|
|
1340
|
+
if (rows.length === 0) {
|
|
1341
|
+
list.innerHTML = '<div class="usage-empty">No agent usage today.</div>';
|
|
1342
|
+
return;
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
list.innerHTML = '';
|
|
1346
|
+
rows.forEach(function(row) {
|
|
1347
|
+
var item = document.createElement('div');
|
|
1348
|
+
item.className = 'usage-row';
|
|
1349
|
+
item.innerHTML =
|
|
1350
|
+
'<span class="usage-name" title="' + escapeHtml(row.name) + '">' + escapeHtml(row.name) + '</span>' +
|
|
1351
|
+
'<span class="usage-stat input">' + formatNumber(row.input) + '</span>' +
|
|
1352
|
+
'<span class="usage-stat output">' + formatNumber(row.output) + '</span>' +
|
|
1353
|
+
'<span class="usage-stat cached">' + formatNumber(row.cached) + '</span>';
|
|
1354
|
+
list.appendChild(item);
|
|
893
1355
|
});
|
|
894
1356
|
}
|
|
895
1357
|
|
|
@@ -914,85 +1376,74 @@
|
|
|
914
1376
|
return Promise.resolve();
|
|
915
1377
|
}
|
|
916
1378
|
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
}
|
|
935
|
-
}
|
|
936
|
-
|
|
937
|
-
function updateAgentDropdownLabel() {
|
|
938
|
-
var btn = document.getElementById('agent-dropdown-btn');
|
|
939
|
-
if (selectedAgents.length === availableAgents.length) {
|
|
940
|
-
btn.textContent = 'All';
|
|
1379
|
+
function setUpdateUi(state, message, nextInfo) {
|
|
1380
|
+
var row = document.getElementById('update-row');
|
|
1381
|
+
var button = document.getElementById('check-updates');
|
|
1382
|
+
var status = document.getElementById('update-status');
|
|
1383
|
+
updateInfo = nextInfo || updateInfo;
|
|
1384
|
+
|
|
1385
|
+
row.classList.toggle('has-update', state === 'available' || state === 'ready');
|
|
1386
|
+
button.classList.toggle('primary-update', state === 'available');
|
|
1387
|
+
button.disabled = state === 'checking' || state === 'downloading';
|
|
1388
|
+
status.textContent = message;
|
|
1389
|
+
|
|
1390
|
+
if (state === 'available') {
|
|
1391
|
+
button.textContent = 'Download';
|
|
1392
|
+
button.dataset.action = 'download';
|
|
1393
|
+
} else if (state === 'ready') {
|
|
1394
|
+
button.textContent = 'Done';
|
|
1395
|
+
button.dataset.action = 'check';
|
|
941
1396
|
} else {
|
|
942
|
-
|
|
1397
|
+
button.textContent = state === 'checking' ? 'Checking' : state === 'downloading' ? 'Downloading' : 'Check';
|
|
1398
|
+
button.dataset.action = 'check';
|
|
943
1399
|
}
|
|
944
1400
|
}
|
|
945
1401
|
|
|
946
|
-
function
|
|
947
|
-
|
|
948
|
-
menu.innerHTML = '';
|
|
949
|
-
|
|
950
|
-
availableAgents.forEach(function(agent) {
|
|
951
|
-
var isChecked = selectedAgents.indexOf(agent) !== -1;
|
|
952
|
-
var option = document.createElement('div');
|
|
953
|
-
option.className = 'agent-option';
|
|
954
|
-
option.innerHTML =
|
|
955
|
-
'<div class="agent-checkbox' + (isChecked ? ' checked' : '') + '"></div>' +
|
|
956
|
-
'<span>' + agent.charAt(0).toUpperCase() + agent.slice(1) + '</span>';
|
|
957
|
-
|
|
958
|
-
option.addEventListener('click', function(e) {
|
|
959
|
-
e.stopPropagation();
|
|
960
|
-
var idx = selectedAgents.indexOf(agent);
|
|
961
|
-
if (idx !== -1) {
|
|
962
|
-
// Don't allow deselecting the last agent
|
|
963
|
-
if (selectedAgents.length <= 1) return;
|
|
964
|
-
selectedAgents.splice(idx, 1);
|
|
965
|
-
} else {
|
|
966
|
-
selectedAgents.push(agent);
|
|
967
|
-
}
|
|
968
|
-
saveSelectedAgents();
|
|
969
|
-
renderAgentDropdown();
|
|
970
|
-
updateAgentDropdownLabel();
|
|
971
|
-
fetchData();
|
|
972
|
-
});
|
|
973
|
-
|
|
974
|
-
menu.appendChild(option);
|
|
975
|
-
});
|
|
1402
|
+
function checkForUpdates() {
|
|
1403
|
+
setUpdateUi('checking', 'Checking GitHub Releases...', null);
|
|
976
1404
|
|
|
977
|
-
|
|
1405
|
+
var promise = window.electronAPI && window.electronAPI.checkForUpdates
|
|
1406
|
+
? window.electronAPI.checkForUpdates()
|
|
1407
|
+
: Promise.resolve({ currentVersion: appInfo.version, latestVersion: appInfo.version, upToDate: true });
|
|
978
1408
|
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
1409
|
+
return promise.then(function(result) {
|
|
1410
|
+
if (result.error) {
|
|
1411
|
+
setUpdateUi('idle', 'Unable to reach update service right now.', result);
|
|
1412
|
+
return;
|
|
1413
|
+
}
|
|
1414
|
+
if (result.upToDate) {
|
|
1415
|
+
setUpdateUi('idle', 'You are up to date on version ' + result.currentVersion + '.', result);
|
|
1416
|
+
return;
|
|
1417
|
+
}
|
|
1418
|
+
if (!result.asset || !result.asset.url) {
|
|
1419
|
+
setUpdateUi('idle', 'Version ' + result.latestVersion + ' is available, but no macOS DMG was attached.', result);
|
|
1420
|
+
return;
|
|
1421
|
+
}
|
|
1422
|
+
setUpdateUi('available', 'Version ' + result.latestVersion + ' is available.', result);
|
|
1423
|
+
}).catch(function() {
|
|
1424
|
+
setUpdateUi('idle', 'Unable to reach update service right now.', null);
|
|
1425
|
+
});
|
|
982
1426
|
}
|
|
983
1427
|
|
|
984
|
-
function
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
if (saved) {
|
|
988
|
-
// Filter saved to only include agents that are actually available
|
|
989
|
-
selectedAgents = saved.filter(function(a) { return agents.indexOf(a) !== -1; });
|
|
990
|
-
if (selectedAgents.length === 0) selectedAgents = agents.slice(); // fallback
|
|
991
|
-
} else {
|
|
992
|
-
selectedAgents = agents.slice(); // default: all selected
|
|
1428
|
+
function downloadUpdate() {
|
|
1429
|
+
if (!updateInfo || !updateInfo.asset) {
|
|
1430
|
+
return checkForUpdates();
|
|
993
1431
|
}
|
|
994
|
-
|
|
995
|
-
|
|
1432
|
+
if (!window.electronAPI || !window.electronAPI.downloadUpdate) {
|
|
1433
|
+
setUpdateUi('idle', 'Download is only available in the macOS app.', updateInfo);
|
|
1434
|
+
return Promise.resolve();
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
setUpdateUi('downloading', 'Preparing download...', updateInfo);
|
|
1438
|
+
return window.electronAPI.downloadUpdate(updateInfo).then(function(result) {
|
|
1439
|
+
if (!result || !result.ok) {
|
|
1440
|
+
setUpdateUi('available', result && result.error ? result.error : 'Download failed.', updateInfo);
|
|
1441
|
+
return;
|
|
1442
|
+
}
|
|
1443
|
+
setUpdateUi('ready', 'Downloaded. The installer disk image has been opened.', updateInfo);
|
|
1444
|
+
}).catch(function() {
|
|
1445
|
+
setUpdateUi('available', 'Download failed. Please try again.', updateInfo);
|
|
1446
|
+
});
|
|
996
1447
|
}
|
|
997
1448
|
|
|
998
1449
|
// --- Data fetching ---
|
|
@@ -1005,30 +1456,30 @@
|
|
|
1005
1456
|
.then(function(agentData) {
|
|
1006
1457
|
var agents = (agentData && agentData.available) ? agentData.available : ['claude'];
|
|
1007
1458
|
|
|
1008
|
-
|
|
1009
|
-
if (isFirstLoad || agents.length !== availableAgents.length) {
|
|
1010
|
-
initAgentFilter(agents);
|
|
1011
|
-
isFirstLoad = false;
|
|
1012
|
-
}
|
|
1013
|
-
|
|
1014
|
-
// Use filtered agents
|
|
1015
|
-
var fetchAgents = selectedAgents.length > 0 ? selectedAgents : agents;
|
|
1016
|
-
|
|
1017
|
-
return Promise.all(fetchAgents.map(function(agent) {
|
|
1459
|
+
return Promise.all(agents.map(function(agent) {
|
|
1018
1460
|
return fetchJson(apiBase + '/api/daily?agent=' + agent).catch(function() { return null; });
|
|
1019
1461
|
})).then(function(dailyResults) {
|
|
1020
|
-
|
|
1021
|
-
var blockPromises = fetchAgents.map(function(agent) {
|
|
1462
|
+
var blockPromises = agents.map(function(agent) {
|
|
1022
1463
|
return fetchJson(apiBase + '/api/blocks?agent=' + agent).catch(function() { return null; });
|
|
1023
1464
|
});
|
|
1024
|
-
|
|
1025
|
-
return
|
|
1465
|
+
var projectPromises = agents.map(function(agent) {
|
|
1466
|
+
return fetchJson(apiBase + '/api/projects?agent=' + agent).catch(function() { return null; });
|
|
1467
|
+
});
|
|
1468
|
+
return Promise.all([Promise.all(blockPromises), Promise.all(projectPromises)]).then(function(results) {
|
|
1469
|
+
return {
|
|
1470
|
+
agents: agents,
|
|
1471
|
+
dailyResults: dailyResults,
|
|
1472
|
+
blockResults: results[0],
|
|
1473
|
+
projectResults: results[1]
|
|
1474
|
+
};
|
|
1026
1475
|
});
|
|
1027
1476
|
});
|
|
1028
1477
|
})
|
|
1029
1478
|
.then(function(data) {
|
|
1030
1479
|
var traySnapshot = renderMetrics(data.dailyResults);
|
|
1031
1480
|
renderChart(data.blockResults);
|
|
1481
|
+
renderProjects(data.projectResults);
|
|
1482
|
+
renderAgents(data.dailyResults, data.agents);
|
|
1032
1483
|
if (window.electronAPI && window.electronAPI.updateTraySnapshot && traySnapshot.totalTokens > 0) {
|
|
1033
1484
|
window.electronAPI.updateTraySnapshot(traySnapshot).catch(function() {});
|
|
1034
1485
|
}
|
|
@@ -1074,28 +1525,11 @@
|
|
|
1074
1525
|
});
|
|
1075
1526
|
|
|
1076
1527
|
document.getElementById('check-updates').addEventListener('click', function() {
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
var promise = window.electronAPI && window.electronAPI.checkForUpdates
|
|
1083
|
-
? window.electronAPI.checkForUpdates()
|
|
1084
|
-
: Promise.resolve({ currentVersion: appInfo.version, latestVersion: appInfo.version, upToDate: true });
|
|
1085
|
-
|
|
1086
|
-
promise.then(function(result) {
|
|
1087
|
-
if (result.error) {
|
|
1088
|
-
status.textContent = 'Unable to reach update service right now.';
|
|
1089
|
-
return;
|
|
1090
|
-
}
|
|
1091
|
-
status.textContent = result.upToDate
|
|
1092
|
-
? 'You are up to date.'
|
|
1093
|
-
: 'Version ' + result.latestVersion + ' is available.';
|
|
1094
|
-
}).catch(function() {
|
|
1095
|
-
status.textContent = 'Unable to reach update service right now.';
|
|
1096
|
-
}).finally(function() {
|
|
1097
|
-
button.disabled = false;
|
|
1098
|
-
});
|
|
1528
|
+
if (this.dataset.action === 'download') {
|
|
1529
|
+
downloadUpdate();
|
|
1530
|
+
} else {
|
|
1531
|
+
checkForUpdates();
|
|
1532
|
+
}
|
|
1099
1533
|
});
|
|
1100
1534
|
|
|
1101
1535
|
document.getElementById('quit-app').addEventListener('click', function() {
|
|
@@ -1104,23 +1538,15 @@
|
|
|
1104
1538
|
}
|
|
1105
1539
|
});
|
|
1106
1540
|
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
var menu = document.getElementById('agent-dropdown-menu');
|
|
1117
|
-
if (menu) menu.classList.remove('open');
|
|
1118
|
-
});
|
|
1119
|
-
|
|
1120
|
-
// Prevent dropdown from closing when clicking inside it
|
|
1121
|
-
document.getElementById('agent-dropdown-menu').addEventListener('click', function(e) {
|
|
1122
|
-
e.stopPropagation();
|
|
1123
|
-
});
|
|
1541
|
+
if (window.electronAPI && window.electronAPI.onUpdateDownloadProgress) {
|
|
1542
|
+
window.electronAPI.onUpdateDownloadProgress(function(progress) {
|
|
1543
|
+
if (!progress) return;
|
|
1544
|
+
var label = progress.percent === null
|
|
1545
|
+
? 'Downloading...'
|
|
1546
|
+
: 'Downloading ' + progress.percent + '%...';
|
|
1547
|
+
setUpdateUi('downloading', label, updateInfo);
|
|
1548
|
+
});
|
|
1549
|
+
}
|
|
1124
1550
|
|
|
1125
1551
|
// --- Init ---
|
|
1126
1552
|
|