@zhangferry-dev/tokendash 1.5.0 → 1.6.1
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 -395
- package/dist/electron-server.cjs +120 -55
- 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/dist/server/index.js +18 -3
- package/dist/server/routes/api.d.ts +6 -1
- package/dist/server/routes/api.js +11 -1
- package/electron/main.cjs +67 -56
- package/electron/npmSync.cjs +62 -0
- package/electron/preload.cjs +11 -0
- package/electron/serverReuse.cjs +59 -0
- package/electron/updateService.cjs +220 -0
- package/package.json +1 -1
- package/dist/client/assets/index-iYDpTV63.css +0 -1
- package/dist/server/ccusage.d.ts +0 -7
- package/dist/server/ccusage.js +0 -69
- package/electron/main.js +0 -291
- package/electron/trayBadge.js +0 -30
- package/resources/entitlements.mac.plist +0 -10
- package/resources/icon.png +0 -0
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,169 @@
|
|
|
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
|
-
|
|
556
|
+
z-index: 20;
|
|
557
|
+
background: rgba(250, 252, 251, 0.96);
|
|
558
|
+
border: 1px solid rgba(255, 255, 255, 0.72);
|
|
559
|
+
border-radius: var(--radius-shell);
|
|
310
560
|
opacity: 0;
|
|
311
561
|
pointer-events: none;
|
|
312
562
|
transition: opacity 200ms ease;
|
|
313
563
|
overflow: hidden;
|
|
314
|
-
|
|
564
|
+
backdrop-filter: blur(28px);
|
|
315
565
|
}
|
|
316
566
|
|
|
317
567
|
.drawer {
|
|
318
568
|
height: 100%;
|
|
319
|
-
display:
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
padding: 14px;
|
|
569
|
+
display: flex;
|
|
570
|
+
flex-direction: column;
|
|
571
|
+
padding: 0 24px;
|
|
323
572
|
border-radius: var(--radius-shell);
|
|
324
|
-
background: rgba(
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
box-shadow: 0 18px 48px rgba(20, 28, 38, 0.14);
|
|
573
|
+
background: linear-gradient(180deg, rgba(250, 252, 251, 0.98), rgba(244, 247, 245, 0.96));
|
|
574
|
+
border: none;
|
|
575
|
+
box-shadow: none;
|
|
328
576
|
transform: translateY(10px);
|
|
329
577
|
opacity: 0;
|
|
330
578
|
transition: transform 220ms ease, opacity 220ms ease;
|
|
@@ -343,36 +591,67 @@
|
|
|
343
591
|
.drawer-header {
|
|
344
592
|
display: flex;
|
|
345
593
|
align-items: center;
|
|
346
|
-
|
|
594
|
+
gap: 8px;
|
|
595
|
+
min-height: 48px;
|
|
596
|
+
border-bottom: 1px solid var(--border);
|
|
347
597
|
}
|
|
348
598
|
|
|
349
599
|
.drawer-back {
|
|
350
600
|
appearance: none;
|
|
351
|
-
border:
|
|
352
|
-
background:
|
|
353
|
-
color: var(--
|
|
601
|
+
border: 0;
|
|
602
|
+
background: transparent;
|
|
603
|
+
color: var(--muted);
|
|
354
604
|
display: inline-flex;
|
|
355
605
|
align-items: center;
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
height:
|
|
359
|
-
padding: 0
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
font-
|
|
606
|
+
justify-content: center;
|
|
607
|
+
width: 22px;
|
|
608
|
+
height: 22px;
|
|
609
|
+
padding: 0;
|
|
610
|
+
margin-left: -4px;
|
|
611
|
+
border-radius: 6px;
|
|
612
|
+
font-size: 0;
|
|
363
613
|
font-family: var(--font-body);
|
|
364
614
|
cursor: pointer;
|
|
615
|
+
transition: background-color 160ms ease, color 160ms ease;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
.drawer-back:hover {
|
|
619
|
+
background: rgba(233, 238, 235, 0.72);
|
|
620
|
+
color: var(--fg);
|
|
365
621
|
}
|
|
366
622
|
|
|
367
623
|
.drawer-back-arrow {
|
|
368
|
-
|
|
369
|
-
|
|
624
|
+
display: inline-flex;
|
|
625
|
+
align-items: center;
|
|
626
|
+
justify-content: center;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
.drawer-back-arrow svg {
|
|
630
|
+
width: 15px;
|
|
631
|
+
height: 15px;
|
|
632
|
+
stroke: currentColor;
|
|
633
|
+
fill: none;
|
|
634
|
+
stroke-width: 2;
|
|
635
|
+
stroke-linecap: round;
|
|
636
|
+
stroke-linejoin: round;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
.drawer-heading {
|
|
640
|
+
min-width: 0;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
.drawer-title {
|
|
644
|
+
margin: 0;
|
|
645
|
+
font-size: 15px;
|
|
646
|
+
line-height: 1.1;
|
|
647
|
+
font-weight: 700;
|
|
370
648
|
}
|
|
371
649
|
|
|
372
650
|
.settings-list {
|
|
373
651
|
display: grid;
|
|
374
652
|
align-content: start;
|
|
375
|
-
gap:
|
|
653
|
+
gap: 0;
|
|
654
|
+
padding: 7px 0 0;
|
|
376
655
|
}
|
|
377
656
|
|
|
378
657
|
.settings-row {
|
|
@@ -380,10 +659,14 @@
|
|
|
380
659
|
align-items: center;
|
|
381
660
|
justify-content: space-between;
|
|
382
661
|
gap: 12px;
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
border: 1px solid var(--border);
|
|
386
|
-
background:
|
|
662
|
+
min-height: 48px;
|
|
663
|
+
padding: 6px 0;
|
|
664
|
+
border-bottom: 1px solid var(--border);
|
|
665
|
+
background: transparent;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
.settings-row.has-update {
|
|
669
|
+
border-color: color-mix(in oklch, var(--notice) 30%, var(--border));
|
|
387
670
|
}
|
|
388
671
|
|
|
389
672
|
.settings-copy {
|
|
@@ -392,24 +675,48 @@
|
|
|
392
675
|
|
|
393
676
|
.settings-label {
|
|
394
677
|
margin: 0;
|
|
395
|
-
font-size:
|
|
396
|
-
font-weight:
|
|
678
|
+
font-size: 12px;
|
|
679
|
+
font-weight: 700;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
.settings-title-line {
|
|
683
|
+
display: flex;
|
|
684
|
+
align-items: center;
|
|
685
|
+
gap: 6px;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
.update-pill {
|
|
689
|
+
display: none;
|
|
690
|
+
padding: 2px 6px;
|
|
691
|
+
border-radius: 7px;
|
|
692
|
+
background: color-mix(in oklch, var(--notice) 20%, white);
|
|
693
|
+
color: color-mix(in oklch, var(--notice) 82%, black);
|
|
694
|
+
font-family: var(--font-mono);
|
|
695
|
+
font-size: 9px;
|
|
696
|
+
font-weight: 700;
|
|
697
|
+
letter-spacing: 0;
|
|
698
|
+
text-transform: uppercase;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
.settings-row.has-update .update-pill {
|
|
702
|
+
display: inline-flex;
|
|
397
703
|
}
|
|
398
704
|
|
|
399
705
|
.settings-meta {
|
|
400
706
|
margin: 3px 0 0;
|
|
401
|
-
font-size:
|
|
707
|
+
font-size: 10px;
|
|
402
708
|
color: var(--muted);
|
|
709
|
+
line-height: 1.25;
|
|
403
710
|
}
|
|
404
711
|
|
|
405
712
|
.settings-value {
|
|
406
713
|
display: inline-flex;
|
|
407
714
|
align-items: center;
|
|
408
715
|
justify-content: center;
|
|
409
|
-
min-height:
|
|
410
|
-
min-width:
|
|
716
|
+
min-height: 26px;
|
|
717
|
+
min-width: 54px;
|
|
411
718
|
padding-inline: 10px;
|
|
412
|
-
border-radius:
|
|
719
|
+
border-radius: 9px;
|
|
413
720
|
border: 1px solid var(--border);
|
|
414
721
|
background: rgba(255, 255, 255, 0.96);
|
|
415
722
|
color: var(--muted);
|
|
@@ -421,92 +728,26 @@
|
|
|
421
728
|
.settings-value.button {
|
|
422
729
|
cursor: pointer;
|
|
423
730
|
color: var(--fg);
|
|
731
|
+
transition: background-color 160ms ease, border-color 160ms ease;
|
|
424
732
|
}
|
|
425
733
|
|
|
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;
|
|
734
|
+
.settings-value.button:hover {
|
|
735
|
+
background: rgba(247, 249, 248, 0.96);
|
|
488
736
|
}
|
|
489
737
|
|
|
490
|
-
.
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
.agent-checkbox.checked::after {
|
|
496
|
-
content: "✓";
|
|
497
|
-
color: white;
|
|
738
|
+
.settings-value.button.primary-update {
|
|
739
|
+
border-color: color-mix(in oklch, var(--notice) 45%, var(--border));
|
|
740
|
+
background: color-mix(in oklch, var(--notice) 18%, white);
|
|
741
|
+
color: color-mix(in oklch, var(--notice) 74%, black);
|
|
498
742
|
font-weight: 700;
|
|
499
|
-
font-size: 10px;
|
|
500
743
|
}
|
|
501
744
|
|
|
502
745
|
.settings-divider {
|
|
503
|
-
height:
|
|
504
|
-
background: var(--border);
|
|
505
|
-
margin: 4px 0;
|
|
746
|
+
height: 8px;
|
|
506
747
|
}
|
|
507
748
|
|
|
508
749
|
.settings-quit-row {
|
|
509
|
-
border-
|
|
750
|
+
border-bottom: 0;
|
|
510
751
|
}
|
|
511
752
|
|
|
512
753
|
.settings-quit-btn {
|
|
@@ -514,19 +755,21 @@
|
|
|
514
755
|
}
|
|
515
756
|
|
|
516
757
|
.settings-version {
|
|
517
|
-
|
|
758
|
+
margin-top: auto;
|
|
759
|
+
border-top: 1px solid var(--border);
|
|
760
|
+
text-align: left;
|
|
518
761
|
font-family: var(--font-mono);
|
|
519
|
-
font-size:
|
|
762
|
+
font-size: 10px;
|
|
520
763
|
color: var(--muted);
|
|
521
|
-
letter-spacing: 0
|
|
522
|
-
text-transform:
|
|
523
|
-
padding-block:
|
|
764
|
+
letter-spacing: 0;
|
|
765
|
+
text-transform: none;
|
|
766
|
+
padding-block: 10px 13px;
|
|
524
767
|
}
|
|
525
768
|
|
|
526
769
|
.switch {
|
|
527
770
|
position: relative;
|
|
528
|
-
width:
|
|
529
|
-
height:
|
|
771
|
+
width: 42px;
|
|
772
|
+
height: 24px;
|
|
530
773
|
border-radius: 999px;
|
|
531
774
|
border: 1px solid color-mix(in srgb, #000 7%, transparent);
|
|
532
775
|
background: rgba(228, 231, 229, 0.9);
|
|
@@ -539,8 +782,8 @@
|
|
|
539
782
|
position: absolute;
|
|
540
783
|
inset-block-start: 2px;
|
|
541
784
|
inset-inline-start: 2px;
|
|
542
|
-
width:
|
|
543
|
-
height:
|
|
785
|
+
width: 18px;
|
|
786
|
+
height: 18px;
|
|
544
787
|
border-radius: 50%;
|
|
545
788
|
background: white;
|
|
546
789
|
box-shadow: 0 1px 3px rgba(20, 28, 38, 0.18);
|
|
@@ -559,7 +802,8 @@
|
|
|
559
802
|
<body>
|
|
560
803
|
<main class="shell" aria-label="Token dashboard menu" data-view="main">
|
|
561
804
|
<header class="toolbar">
|
|
562
|
-
<p class="toolbar-label">Today <span id="date"></span></p>
|
|
805
|
+
<p class="toolbar-label"><strong>Today</strong> · <span id="date"></span></p>
|
|
806
|
+
<div class="cache-pill" id="cache-pill">cache --</div>
|
|
563
807
|
</header>
|
|
564
808
|
|
|
565
809
|
<div class="error-banner" id="error-banner">
|
|
@@ -568,22 +812,27 @@
|
|
|
568
812
|
|
|
569
813
|
<section class="content" id="content">
|
|
570
814
|
<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
|
-
|
|
815
|
+
<div class="summary-top">
|
|
816
|
+
<div>
|
|
817
|
+
<p class="card-label">Total</p>
|
|
818
|
+
<p class="card-value" id="total-tokens"><span class="skeleton"></span></p>
|
|
819
|
+
</div>
|
|
820
|
+
<p class="cost-value" id="total-cost">$--</p>
|
|
821
|
+
</div>
|
|
822
|
+
<div class="mini-metrics" aria-label="Token breakdown">
|
|
823
|
+
<div class="mini-metric">
|
|
824
|
+
<p class="mini-label">In</p>
|
|
825
|
+
<p class="mini-value input" id="input"><span class="skeleton"></span></p>
|
|
826
|
+
</div>
|
|
827
|
+
<div class="mini-metric">
|
|
828
|
+
<p class="mini-label">Out</p>
|
|
829
|
+
<p class="mini-value output" id="output"><span class="skeleton"></span></p>
|
|
830
|
+
</div>
|
|
831
|
+
<div class="mini-metric">
|
|
832
|
+
<p class="mini-label">Cached</p>
|
|
833
|
+
<p class="mini-value cached" id="cached"><span class="skeleton"></span></p>
|
|
834
|
+
</div>
|
|
835
|
+
</div>
|
|
587
836
|
</section>
|
|
588
837
|
|
|
589
838
|
<section class="chart-panel" aria-label="Hourly usage">
|
|
@@ -594,17 +843,41 @@
|
|
|
594
843
|
<div class="y-axis" id="y-axis" aria-hidden="true"></div>
|
|
595
844
|
<div class="plot" id="plot">
|
|
596
845
|
<div class="chart-empty">
|
|
597
|
-
<div class="chart-empty-icon">📊</div>
|
|
598
846
|
<div class="chart-empty-title">No usage yet</div>
|
|
599
847
|
<div>Start a session to see your hourly breakdown.</div>
|
|
600
848
|
</div>
|
|
601
849
|
</div>
|
|
850
|
+
<div class="x-axis" id="x-axis" aria-hidden="true"></div>
|
|
851
|
+
</div>
|
|
852
|
+
</section>
|
|
853
|
+
|
|
854
|
+
<section class="usage-panel" aria-label="Projects">
|
|
855
|
+
<div class="usage-header">
|
|
856
|
+
<span class="usage-title">Projects</span>
|
|
857
|
+
<span class="usage-header-stat">in</span>
|
|
858
|
+
<span class="usage-header-stat">out</span>
|
|
859
|
+
<span class="usage-header-stat">cached</span>
|
|
860
|
+
</div>
|
|
861
|
+
<div class="usage-list" id="projects-list">
|
|
862
|
+
<div class="usage-empty">Loading projects...</div>
|
|
863
|
+
</div>
|
|
864
|
+
</section>
|
|
865
|
+
|
|
866
|
+
<section class="usage-panel" aria-label="Agents">
|
|
867
|
+
<div class="usage-header">
|
|
868
|
+
<span class="usage-title">Agents</span>
|
|
869
|
+
<span class="usage-header-stat">in</span>
|
|
870
|
+
<span class="usage-header-stat">out</span>
|
|
871
|
+
<span class="usage-header-stat">cached</span>
|
|
872
|
+
</div>
|
|
873
|
+
<div class="usage-list" id="agents-list">
|
|
874
|
+
<div class="usage-empty">Loading agents...</div>
|
|
602
875
|
</div>
|
|
603
876
|
</section>
|
|
604
877
|
|
|
605
878
|
<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>
|
|
879
|
+
<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>
|
|
880
|
+
<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
881
|
</section>
|
|
609
882
|
</section>
|
|
610
883
|
|
|
@@ -612,14 +885,18 @@
|
|
|
612
885
|
<section class="drawer" aria-label="Settings drawer">
|
|
613
886
|
<div class="drawer-header">
|
|
614
887
|
<button class="drawer-back" type="button" aria-label="Back" id="close-settings">
|
|
615
|
-
<span class="drawer-back-arrow"
|
|
888
|
+
<span class="drawer-back-arrow"><svg viewBox="0 0 24 24"><path d="m15 18-6-6 6-6"/></svg></span>
|
|
616
889
|
</button>
|
|
890
|
+
<p class="drawer-title">Settings</p>
|
|
617
891
|
</div>
|
|
618
892
|
|
|
619
893
|
<div class="settings-list">
|
|
620
|
-
<article class="settings-row">
|
|
894
|
+
<article class="settings-row" id="update-row">
|
|
621
895
|
<div class="settings-copy">
|
|
622
|
-
<
|
|
896
|
+
<div class="settings-title-line">
|
|
897
|
+
<p class="settings-label">Check for updates</p>
|
|
898
|
+
<span class="update-pill" id="update-pill">New</span>
|
|
899
|
+
</div>
|
|
623
900
|
<p class="settings-meta" id="update-status">Check whether a newer build is available.</p>
|
|
624
901
|
</div>
|
|
625
902
|
<button class="settings-value button" type="button" id="check-updates">Check</button>
|
|
@@ -633,17 +910,6 @@
|
|
|
633
910
|
<button class="switch" type="button" id="launch-at-login" role="switch" aria-checked="false" aria-label="Launch at login"></button>
|
|
634
911
|
</article>
|
|
635
912
|
|
|
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
913
|
<div class="settings-divider"></div>
|
|
648
914
|
|
|
649
915
|
<article class="settings-row settings-quit-row">
|
|
@@ -665,16 +931,14 @@
|
|
|
665
931
|
var port = window.location.port || '3456';
|
|
666
932
|
var apiBase = 'http://localhost:' + port;
|
|
667
933
|
var appInfo = { version: '--', launchAtLogin: false };
|
|
668
|
-
var
|
|
669
|
-
var selectedAgents = [];
|
|
670
|
-
var isFirstLoad = true;
|
|
934
|
+
var updateInfo = null;
|
|
671
935
|
|
|
672
936
|
// --- Helpers ---
|
|
673
937
|
|
|
674
938
|
function formatNumber(n) {
|
|
675
939
|
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';
|
|
940
|
+
if (n >= 1e6) return trimTrailingZero((n / 1e6).toFixed(n >= 10e6 ? 1 : 2)) + 'M';
|
|
941
|
+
if (n >= 1e3) return trimTrailingZero((n / 1e3).toFixed(n >= 100e3 ? 0 : 1)) + 'K';
|
|
678
942
|
return String(Math.round(n));
|
|
679
943
|
}
|
|
680
944
|
|
|
@@ -682,6 +946,32 @@
|
|
|
682
946
|
return (Number.isFinite(n) ? n : 0).toFixed(1) + '%';
|
|
683
947
|
}
|
|
684
948
|
|
|
949
|
+
function formatCost(n) {
|
|
950
|
+
var value = Number(n) || 0;
|
|
951
|
+
if (value >= 100) return '$' + Math.round(value);
|
|
952
|
+
if (value >= 10) return '$' + value.toFixed(1);
|
|
953
|
+
return '$' + value.toFixed(2);
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
function trimTrailingZero(value) {
|
|
957
|
+
return String(value).replace(/\.0+$/, '').replace(/(\.\d*[1-9])0+$/, '$1');
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
function formatProjectName(path) {
|
|
961
|
+
var value = String(path || 'Unknown');
|
|
962
|
+
var parts = value.split('/').filter(Boolean);
|
|
963
|
+
return parts[parts.length - 1] || value;
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
function escapeHtml(value) {
|
|
967
|
+
return String(value)
|
|
968
|
+
.replace(/&/g, '&')
|
|
969
|
+
.replace(/</g, '<')
|
|
970
|
+
.replace(/>/g, '>')
|
|
971
|
+
.replace(/"/g, '"')
|
|
972
|
+
.replace(/'/g, ''');
|
|
973
|
+
}
|
|
974
|
+
|
|
685
975
|
function getTodayString() {
|
|
686
976
|
var d = new Date();
|
|
687
977
|
return d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0') + '-' + String(d.getDate()).padStart(2, '0');
|
|
@@ -700,25 +990,17 @@
|
|
|
700
990
|
// --- Dynamic chart hours based on current time ---
|
|
701
991
|
|
|
702
992
|
function getChartHours() {
|
|
703
|
-
var now = new Date();
|
|
704
|
-
var currentHour = now.getHours();
|
|
705
993
|
var hours = [];
|
|
706
|
-
for (var h = 0; h
|
|
994
|
+
for (var h = 0; h < 24; h += 1) {
|
|
707
995
|
hours.push(h);
|
|
708
996
|
}
|
|
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
997
|
return hours;
|
|
716
998
|
}
|
|
717
999
|
|
|
718
1000
|
// --- Loading & Error states ---
|
|
719
1001
|
|
|
720
1002
|
function showSkeletons() {
|
|
721
|
-
['total-tokens', 'input', 'output', '
|
|
1003
|
+
['total-tokens', 'input', 'output', 'cached'].forEach(function(id) {
|
|
722
1004
|
var el = document.getElementById(id);
|
|
723
1005
|
if (el && !el.querySelector('.skeleton')) return; // already has data
|
|
724
1006
|
if (el) el.innerHTML = '<span class="skeleton"></span>';
|
|
@@ -762,12 +1044,15 @@
|
|
|
762
1044
|
setCardValue('total-tokens', formatNumber(totalTokens), totalTokens === 0);
|
|
763
1045
|
setCardValue('input', formatNumber(totalInput), totalInput === 0);
|
|
764
1046
|
setCardValue('output', formatNumber(totalOutput), totalOutput === 0);
|
|
765
|
-
setCardValue('
|
|
1047
|
+
setCardValue('cached', formatNumber(totalCacheRead), totalCacheRead === 0);
|
|
1048
|
+
document.getElementById('total-cost').textContent = formatCost(totalCost);
|
|
1049
|
+
applyMetricFit(document.getElementById('total-cost'), formatCost(totalCost));
|
|
1050
|
+
document.getElementById('cache-pill').textContent = 'cache ' + formatPercent(cacheRate);
|
|
766
1051
|
|
|
767
1052
|
return {
|
|
768
1053
|
today: todayStr,
|
|
769
|
-
agentKey: selectedAgents.slice().sort().join(','),
|
|
770
1054
|
totalTokens: totalTokens,
|
|
1055
|
+
totalInput: totalInput,
|
|
771
1056
|
totalCost: totalCost,
|
|
772
1057
|
totalCacheRead: totalCacheRead
|
|
773
1058
|
};
|
|
@@ -777,6 +1062,24 @@
|
|
|
777
1062
|
var el = document.getElementById(id);
|
|
778
1063
|
el.textContent = text;
|
|
779
1064
|
el.classList.toggle('empty', isEmpty);
|
|
1065
|
+
applyMetricFit(el, text);
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
function applyMetricFit(el, text) {
|
|
1069
|
+
if (!el) return;
|
|
1070
|
+
var length = String(text || '').length;
|
|
1071
|
+
|
|
1072
|
+
if (el.id === 'total-tokens') {
|
|
1073
|
+
el.style.fontSize = length >= 8 ? '27px' : length >= 6 ? '29px' : '32px';
|
|
1074
|
+
return;
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
if (el.id === 'total-cost') {
|
|
1078
|
+
el.style.fontSize = length >= 8 ? '21px' : length >= 6 ? '23px' : '25px';
|
|
1079
|
+
return;
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
el.style.fontSize = length >= 8 ? '13px' : '15px';
|
|
780
1083
|
}
|
|
781
1084
|
|
|
782
1085
|
// --- Chart rendering ---
|
|
@@ -807,10 +1110,12 @@
|
|
|
807
1110
|
|
|
808
1111
|
var plot = document.getElementById('plot');
|
|
809
1112
|
var axis = document.getElementById('y-axis');
|
|
1113
|
+
var xAxis = document.getElementById('x-axis');
|
|
810
1114
|
|
|
811
1115
|
// Empty state
|
|
812
1116
|
if (maxValue === 0) {
|
|
813
1117
|
axis.innerHTML = '';
|
|
1118
|
+
xAxis.innerHTML = '';
|
|
814
1119
|
plot.innerHTML = '';
|
|
815
1120
|
plot.style.gridTemplateColumns = '';
|
|
816
1121
|
plot.innerHTML = '<div class="chart-empty">' +
|
|
@@ -820,55 +1125,77 @@
|
|
|
820
1125
|
return;
|
|
821
1126
|
}
|
|
822
1127
|
|
|
823
|
-
var axisInfo =
|
|
824
|
-
renderAxis(
|
|
1128
|
+
var axisInfo = buildAxis(maxValue);
|
|
1129
|
+
renderAxis(axisInfo);
|
|
825
1130
|
var maxEntry = selected.reduce(function(max, entry) {
|
|
826
1131
|
return !max || entry.value > max.value ? entry : max;
|
|
827
1132
|
}, null);
|
|
828
|
-
renderBars(selected, axisInfo
|
|
1133
|
+
renderBars(selected, axisInfo, maxEntry);
|
|
829
1134
|
}
|
|
830
1135
|
|
|
831
|
-
function
|
|
832
|
-
|
|
833
|
-
if (value <= 0) return { top: 3, step: 1 };
|
|
834
|
-
var rawStep = value / 3;
|
|
1136
|
+
function niceIncrement(rawStep) {
|
|
1137
|
+
if (rawStep <= 0) return 1;
|
|
835
1138
|
var exp = Math.floor(Math.log10(rawStep));
|
|
836
1139
|
var base = Math.pow(10, exp);
|
|
837
1140
|
var frac = rawStep / base;
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
1141
|
+
if (frac <= 1) return base;
|
|
1142
|
+
if (frac <= 2) return 2 * base;
|
|
1143
|
+
if (frac <= 2.5) return 2.5 * base;
|
|
1144
|
+
if (frac <= 5) return 5 * base;
|
|
1145
|
+
return 10 * base;
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
function buildAxis(value) {
|
|
1149
|
+
if (value <= 0) return { top: 4, ticks: [4, 3, 2, 1, 0] };
|
|
1150
|
+
var step = niceIncrement(value / 4);
|
|
1151
|
+
var top = step * Math.ceil(value / step);
|
|
1152
|
+
if (top / value > 1.35 && top > step) top -= step;
|
|
1153
|
+
if (top < value) top += step;
|
|
1154
|
+
var segments = Math.max(3, Math.min(5, Math.round(top / step)));
|
|
1155
|
+
top = step * segments;
|
|
1156
|
+
|
|
1157
|
+
var ticks = [];
|
|
1158
|
+
for (var i = segments; i >= 0; i--) {
|
|
1159
|
+
ticks.push(step * i);
|
|
1160
|
+
}
|
|
1161
|
+
return { top: top, ticks: ticks };
|
|
842
1162
|
}
|
|
843
1163
|
|
|
844
|
-
function renderAxis(
|
|
1164
|
+
function renderAxis(info) {
|
|
845
1165
|
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('');
|
|
1166
|
+
axis.innerHTML = info.ticks.map(function(tick) {
|
|
1167
|
+
var top = ((info.top - tick) / info.top) * 100;
|
|
1168
|
+
return '<span style="top: ' + top + '%">' + formatNumber(tick) + '</span>';
|
|
1169
|
+
}).join('');
|
|
853
1170
|
}
|
|
854
1171
|
|
|
855
|
-
function renderBars(selected,
|
|
1172
|
+
function renderBars(selected, axisInfo, maxEntry) {
|
|
856
1173
|
var plot = document.getElementById('plot');
|
|
1174
|
+
var xAxis = document.getElementById('x-axis');
|
|
857
1175
|
plot.innerHTML = '';
|
|
858
|
-
plot.style.gridTemplateColumns = 'repeat(
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
1176
|
+
plot.style.gridTemplateColumns = 'repeat(24, minmax(0, 1fr))';
|
|
1177
|
+
xAxis.innerHTML = '';
|
|
1178
|
+
xAxis.style.gridTemplateColumns = 'repeat(24, minmax(0, 1fr))';
|
|
1179
|
+
renderGrid(plot, axisInfo);
|
|
1180
|
+
|
|
1181
|
+
// Dynamic gap and bar width based on bar count
|
|
1182
|
+
var count = selected.filter(function(entry) { return entry.value > 0; }).length;
|
|
1183
|
+
var gap = '2px';
|
|
1184
|
+
var barMaxWidth = count > 10 ? '7px' : count > 6 ? '9px' : '12px';
|
|
862
1185
|
plot.style.gap = gap;
|
|
1186
|
+
xAxis.style.gap = gap;
|
|
863
1187
|
|
|
864
1188
|
selected.forEach(function(entry) {
|
|
865
|
-
|
|
1189
|
+
if (entry.value <= 0) return;
|
|
1190
|
+
var height = entry.value > 0 ? Math.max(8, (entry.value / axisInfo.top) * 100) : 0;
|
|
866
1191
|
var wrap = document.createElement('div');
|
|
867
1192
|
wrap.className = 'bar-wrap';
|
|
1193
|
+
wrap.style.gridColumn = String(entry.hour + 1);
|
|
868
1194
|
wrap.style.setProperty('--height', height + '%');
|
|
869
1195
|
|
|
870
1196
|
var bar = document.createElement('div');
|
|
871
1197
|
bar.className = 'bar';
|
|
1198
|
+
bar.style.maxWidth = barMaxWidth + 'px';
|
|
872
1199
|
if (entry.value === 0) {
|
|
873
1200
|
bar.classList.add('zero');
|
|
874
1201
|
} else if (maxEntry && entry.hour === maxEntry.hour && entry.value === maxEntry.value) {
|
|
@@ -884,12 +1211,148 @@
|
|
|
884
1211
|
wrap.appendChild(tip);
|
|
885
1212
|
}
|
|
886
1213
|
|
|
1214
|
+
plot.appendChild(wrap);
|
|
1215
|
+
});
|
|
1216
|
+
|
|
1217
|
+
renderActiveHourLabels(xAxis, selected, maxEntry);
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
function renderGrid(plot, info) {
|
|
1221
|
+
var grid = document.createElement('div');
|
|
1222
|
+
grid.className = 'plot-grid';
|
|
1223
|
+
|
|
1224
|
+
info.ticks.forEach(function(tick) {
|
|
1225
|
+
var line = document.createElement('span');
|
|
1226
|
+
line.className = 'plot-grid-line';
|
|
1227
|
+
line.style.top = (((info.top - tick) / info.top) * 100) + '%';
|
|
1228
|
+
grid.appendChild(line);
|
|
1229
|
+
});
|
|
1230
|
+
|
|
1231
|
+
plot.appendChild(grid);
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
function renderActiveHourLabels(xAxis, selected, maxEntry) {
|
|
1235
|
+
var active = selected.filter(function(entry) { return entry.value > 0; });
|
|
1236
|
+
var labeledHours = [];
|
|
1237
|
+
|
|
1238
|
+
active.forEach(function(entry) {
|
|
1239
|
+
var isPeak = maxEntry && entry.hour === maxEntry.hour;
|
|
1240
|
+
var hasRoom = labeledHours.every(function(hour) {
|
|
1241
|
+
return Math.abs(entry.hour - hour) >= 3;
|
|
1242
|
+
});
|
|
1243
|
+
var shouldLabel = isPeak || hasRoom;
|
|
1244
|
+
|
|
1245
|
+
if (shouldLabel) labeledHours.push(entry.hour);
|
|
1246
|
+
|
|
887
1247
|
var label = document.createElement('span');
|
|
888
|
-
label.className = '
|
|
1248
|
+
label.className = 'x-axis-label' + (shouldLabel ? '' : ' is-unlabeled');
|
|
1249
|
+
label.style.gridColumn = String(entry.hour + 1);
|
|
889
1250
|
label.textContent = String(entry.hour).padStart(2, '0');
|
|
890
|
-
|
|
1251
|
+
xAxis.appendChild(label);
|
|
1252
|
+
});
|
|
1253
|
+
}
|
|
891
1254
|
|
|
892
|
-
|
|
1255
|
+
function renderProjects(allProjects) {
|
|
1256
|
+
var todayStr = getTodayString();
|
|
1257
|
+
var totals = {};
|
|
1258
|
+
|
|
1259
|
+
(allProjects || []).forEach(function(data) {
|
|
1260
|
+
var projects = data && data.projects;
|
|
1261
|
+
if (!projects) return;
|
|
1262
|
+
|
|
1263
|
+
Object.keys(projects).forEach(function(projectPath) {
|
|
1264
|
+
var todayEntries = (projects[projectPath] || []).filter(function(entry) {
|
|
1265
|
+
return entry && entry.date === todayStr;
|
|
1266
|
+
});
|
|
1267
|
+
if (todayEntries.length === 0) return;
|
|
1268
|
+
|
|
1269
|
+
if (!totals[projectPath]) {
|
|
1270
|
+
totals[projectPath] = { path: projectPath, input: 0, output: 0, cached: 0, total: 0 };
|
|
1271
|
+
}
|
|
1272
|
+
todayEntries.forEach(function(entry) {
|
|
1273
|
+
totals[projectPath].input += entry.inputTokens || 0;
|
|
1274
|
+
totals[projectPath].output += entry.outputTokens || 0;
|
|
1275
|
+
totals[projectPath].cached += entry.cacheReadTokens || 0;
|
|
1276
|
+
totals[projectPath].total += entry.totalTokens || 0;
|
|
1277
|
+
});
|
|
1278
|
+
});
|
|
1279
|
+
});
|
|
1280
|
+
|
|
1281
|
+
var rows = Object.keys(totals).map(function(key) { return totals[key]; })
|
|
1282
|
+
.sort(function(a, b) { return b.total - a.total; })
|
|
1283
|
+
.slice(0, 4);
|
|
1284
|
+
var list = document.getElementById('projects-list');
|
|
1285
|
+
|
|
1286
|
+
if (rows.length === 0) {
|
|
1287
|
+
list.innerHTML = '<div class="usage-empty">No project usage today.</div>';
|
|
1288
|
+
return;
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
list.innerHTML = '';
|
|
1292
|
+
rows.forEach(function(row) {
|
|
1293
|
+
var item = document.createElement('div');
|
|
1294
|
+
item.className = 'usage-row';
|
|
1295
|
+
item.innerHTML =
|
|
1296
|
+
'<span class="usage-name" title="' + escapeHtml(row.path) + '">' + escapeHtml(formatProjectName(row.path)) + '</span>' +
|
|
1297
|
+
'<span class="usage-stat input">' + formatNumber(row.input) + '</span>' +
|
|
1298
|
+
'<span class="usage-stat output">' + formatNumber(row.output) + '</span>' +
|
|
1299
|
+
'<span class="usage-stat cached">' + formatNumber(row.cached) + '</span>';
|
|
1300
|
+
list.appendChild(item);
|
|
1301
|
+
});
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
function formatAgentName(agent) {
|
|
1305
|
+
var names = {
|
|
1306
|
+
claude: 'Claude Code',
|
|
1307
|
+
codex: 'Codex',
|
|
1308
|
+
openclaw: 'OpenClaw',
|
|
1309
|
+
opencode: 'OpenCode'
|
|
1310
|
+
};
|
|
1311
|
+
return names[agent] || agent;
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
function renderAgents(allDaily, agents) {
|
|
1315
|
+
var todayStr = getTodayString();
|
|
1316
|
+
var rows = (agents || []).map(function(agent, index) {
|
|
1317
|
+
var data = (allDaily || [])[index];
|
|
1318
|
+
var entry = null;
|
|
1319
|
+
if (data && Array.isArray(data.daily)) {
|
|
1320
|
+
entry = data.daily.find(function(day) { return day && day.date === todayStr; });
|
|
1321
|
+
}
|
|
1322
|
+
var input = entry ? (entry.inputTokens || 0) : 0;
|
|
1323
|
+
var output = entry ? (entry.outputTokens || 0) : 0;
|
|
1324
|
+
var cached = entry ? (entry.cacheReadTokens || 0) : 0;
|
|
1325
|
+
return {
|
|
1326
|
+
name: formatAgentName(agent),
|
|
1327
|
+
input: input,
|
|
1328
|
+
output: output,
|
|
1329
|
+
cached: cached,
|
|
1330
|
+
total: entry ? (entry.totalTokens || input + output + cached) : 0
|
|
1331
|
+
};
|
|
1332
|
+
}).filter(function(row) {
|
|
1333
|
+
return row.total > 0 || row.input > 0 || row.output > 0;
|
|
1334
|
+
}).sort(function(a, b) {
|
|
1335
|
+
return b.total - a.total;
|
|
1336
|
+
});
|
|
1337
|
+
|
|
1338
|
+
var list = document.getElementById('agents-list');
|
|
1339
|
+
if (!list) return;
|
|
1340
|
+
|
|
1341
|
+
if (rows.length === 0) {
|
|
1342
|
+
list.innerHTML = '<div class="usage-empty">No agent usage today.</div>';
|
|
1343
|
+
return;
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
list.innerHTML = '';
|
|
1347
|
+
rows.forEach(function(row) {
|
|
1348
|
+
var item = document.createElement('div');
|
|
1349
|
+
item.className = 'usage-row';
|
|
1350
|
+
item.innerHTML =
|
|
1351
|
+
'<span class="usage-name" title="' + escapeHtml(row.name) + '">' + escapeHtml(row.name) + '</span>' +
|
|
1352
|
+
'<span class="usage-stat input">' + formatNumber(row.input) + '</span>' +
|
|
1353
|
+
'<span class="usage-stat output">' + formatNumber(row.output) + '</span>' +
|
|
1354
|
+
'<span class="usage-stat cached">' + formatNumber(row.cached) + '</span>';
|
|
1355
|
+
list.appendChild(item);
|
|
893
1356
|
});
|
|
894
1357
|
}
|
|
895
1358
|
|
|
@@ -914,85 +1377,74 @@
|
|
|
914
1377
|
return Promise.resolve();
|
|
915
1378
|
}
|
|
916
1379
|
|
|
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';
|
|
1380
|
+
function setUpdateUi(state, message, nextInfo) {
|
|
1381
|
+
var row = document.getElementById('update-row');
|
|
1382
|
+
var button = document.getElementById('check-updates');
|
|
1383
|
+
var status = document.getElementById('update-status');
|
|
1384
|
+
updateInfo = nextInfo || updateInfo;
|
|
1385
|
+
|
|
1386
|
+
row.classList.toggle('has-update', state === 'available' || state === 'ready');
|
|
1387
|
+
button.classList.toggle('primary-update', state === 'available');
|
|
1388
|
+
button.disabled = state === 'checking' || state === 'downloading';
|
|
1389
|
+
status.textContent = message;
|
|
1390
|
+
|
|
1391
|
+
if (state === 'available') {
|
|
1392
|
+
button.textContent = 'Download';
|
|
1393
|
+
button.dataset.action = 'download';
|
|
1394
|
+
} else if (state === 'ready') {
|
|
1395
|
+
button.textContent = 'Done';
|
|
1396
|
+
button.dataset.action = 'check';
|
|
941
1397
|
} else {
|
|
942
|
-
|
|
1398
|
+
button.textContent = state === 'checking' ? 'Checking' : state === 'downloading' ? 'Downloading' : 'Check';
|
|
1399
|
+
button.dataset.action = 'check';
|
|
943
1400
|
}
|
|
944
1401
|
}
|
|
945
1402
|
|
|
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
|
-
});
|
|
1403
|
+
function checkForUpdates() {
|
|
1404
|
+
setUpdateUi('checking', 'Checking GitHub Releases...', null);
|
|
976
1405
|
|
|
977
|
-
|
|
1406
|
+
var promise = window.electronAPI && window.electronAPI.checkForUpdates
|
|
1407
|
+
? window.electronAPI.checkForUpdates()
|
|
1408
|
+
: Promise.resolve({ currentVersion: appInfo.version, latestVersion: appInfo.version, upToDate: true });
|
|
978
1409
|
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
1410
|
+
return promise.then(function(result) {
|
|
1411
|
+
if (result.error) {
|
|
1412
|
+
setUpdateUi('idle', 'Unable to reach update service right now.', result);
|
|
1413
|
+
return;
|
|
1414
|
+
}
|
|
1415
|
+
if (result.upToDate) {
|
|
1416
|
+
setUpdateUi('idle', 'You are up to date on version ' + result.currentVersion + '.', result);
|
|
1417
|
+
return;
|
|
1418
|
+
}
|
|
1419
|
+
if (!result.asset || !result.asset.url) {
|
|
1420
|
+
setUpdateUi('idle', 'Version ' + result.latestVersion + ' is available, but no macOS DMG was attached.', result);
|
|
1421
|
+
return;
|
|
1422
|
+
}
|
|
1423
|
+
setUpdateUi('available', 'Version ' + result.latestVersion + ' is available.', result);
|
|
1424
|
+
}).catch(function() {
|
|
1425
|
+
setUpdateUi('idle', 'Unable to reach update service right now.', null);
|
|
1426
|
+
});
|
|
982
1427
|
}
|
|
983
1428
|
|
|
984
|
-
function
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
} else {
|
|
992
|
-
selectedAgents = agents.slice(); // default: all selected
|
|
1429
|
+
function downloadUpdate() {
|
|
1430
|
+
if (!updateInfo || !updateInfo.asset) {
|
|
1431
|
+
return checkForUpdates();
|
|
1432
|
+
}
|
|
1433
|
+
if (!window.electronAPI || !window.electronAPI.downloadUpdate) {
|
|
1434
|
+
setUpdateUi('idle', 'Download is only available in the macOS app.', updateInfo);
|
|
1435
|
+
return Promise.resolve();
|
|
993
1436
|
}
|
|
994
|
-
|
|
995
|
-
|
|
1437
|
+
|
|
1438
|
+
setUpdateUi('downloading', 'Preparing download...', updateInfo);
|
|
1439
|
+
return window.electronAPI.downloadUpdate(updateInfo).then(function(result) {
|
|
1440
|
+
if (!result || !result.ok) {
|
|
1441
|
+
setUpdateUi('available', result && result.error ? result.error : 'Download failed.', updateInfo);
|
|
1442
|
+
return;
|
|
1443
|
+
}
|
|
1444
|
+
setUpdateUi('ready', 'Downloaded. The installer disk image has been opened.', updateInfo);
|
|
1445
|
+
}).catch(function() {
|
|
1446
|
+
setUpdateUi('available', 'Download failed. Please try again.', updateInfo);
|
|
1447
|
+
});
|
|
996
1448
|
}
|
|
997
1449
|
|
|
998
1450
|
// --- Data fetching ---
|
|
@@ -1005,30 +1457,30 @@
|
|
|
1005
1457
|
.then(function(agentData) {
|
|
1006
1458
|
var agents = (agentData && agentData.available) ? agentData.available : ['claude'];
|
|
1007
1459
|
|
|
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) {
|
|
1460
|
+
return Promise.all(agents.map(function(agent) {
|
|
1018
1461
|
return fetchJson(apiBase + '/api/daily?agent=' + agent).catch(function() { return null; });
|
|
1019
1462
|
})).then(function(dailyResults) {
|
|
1020
|
-
|
|
1021
|
-
var blockPromises = fetchAgents.map(function(agent) {
|
|
1463
|
+
var blockPromises = agents.map(function(agent) {
|
|
1022
1464
|
return fetchJson(apiBase + '/api/blocks?agent=' + agent).catch(function() { return null; });
|
|
1023
1465
|
});
|
|
1024
|
-
|
|
1025
|
-
return
|
|
1466
|
+
var projectPromises = agents.map(function(agent) {
|
|
1467
|
+
return fetchJson(apiBase + '/api/projects?agent=' + agent).catch(function() { return null; });
|
|
1468
|
+
});
|
|
1469
|
+
return Promise.all([Promise.all(blockPromises), Promise.all(projectPromises)]).then(function(results) {
|
|
1470
|
+
return {
|
|
1471
|
+
agents: agents,
|
|
1472
|
+
dailyResults: dailyResults,
|
|
1473
|
+
blockResults: results[0],
|
|
1474
|
+
projectResults: results[1]
|
|
1475
|
+
};
|
|
1026
1476
|
});
|
|
1027
1477
|
});
|
|
1028
1478
|
})
|
|
1029
1479
|
.then(function(data) {
|
|
1030
1480
|
var traySnapshot = renderMetrics(data.dailyResults);
|
|
1031
1481
|
renderChart(data.blockResults);
|
|
1482
|
+
renderProjects(data.projectResults);
|
|
1483
|
+
renderAgents(data.dailyResults, data.agents);
|
|
1032
1484
|
if (window.electronAPI && window.electronAPI.updateTraySnapshot && traySnapshot.totalTokens > 0) {
|
|
1033
1485
|
window.electronAPI.updateTraySnapshot(traySnapshot).catch(function() {});
|
|
1034
1486
|
}
|
|
@@ -1074,28 +1526,11 @@
|
|
|
1074
1526
|
});
|
|
1075
1527
|
|
|
1076
1528
|
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
|
-
});
|
|
1529
|
+
if (this.dataset.action === 'download') {
|
|
1530
|
+
downloadUpdate();
|
|
1531
|
+
} else {
|
|
1532
|
+
checkForUpdates();
|
|
1533
|
+
}
|
|
1099
1534
|
});
|
|
1100
1535
|
|
|
1101
1536
|
document.getElementById('quit-app').addEventListener('click', function() {
|
|
@@ -1104,23 +1539,15 @@
|
|
|
1104
1539
|
}
|
|
1105
1540
|
});
|
|
1106
1541
|
|
|
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
|
-
});
|
|
1542
|
+
if (window.electronAPI && window.electronAPI.onUpdateDownloadProgress) {
|
|
1543
|
+
window.electronAPI.onUpdateDownloadProgress(function(progress) {
|
|
1544
|
+
if (!progress) return;
|
|
1545
|
+
var label = progress.percent === null
|
|
1546
|
+
? 'Downloading...'
|
|
1547
|
+
: 'Downloading ' + progress.percent + '%...';
|
|
1548
|
+
setUpdateUi('downloading', label, updateInfo);
|
|
1549
|
+
});
|
|
1550
|
+
}
|
|
1124
1551
|
|
|
1125
1552
|
// --- Init ---
|
|
1126
1553
|
|