privateboard 0.1.4 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +1008 -284
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
- package/public/agent-overlay.js +2 -0
- package/public/agent-profile.js +7 -3
- package/public/app.js +143 -31
- package/public/avatars/chair.svg +1 -98
- package/public/avatars/first-principles.svg +1 -122
- package/public/avatars/historian.svg +1 -0
- package/public/avatars/long-horizon.svg +1 -147
- package/public/avatars/phenomenologist.svg +1 -130
- package/public/avatars/socrates.svg +1 -187
- package/public/avatars/user-empathy.svg +1 -117
- package/public/avatars/value-investor.svg +1 -117
- package/public/home.html +3 -3
- package/public/index.html +38 -1
- package/public/new-agent.js +5 -3
- package/public/report/spines/a16z-thesis.css +10 -5
- package/public/report/spines/anthropic-essay.css +11 -4
- package/public/report/spines/boardroom-dark.css +15 -5
- package/public/report/spines/gartner-note.css +8 -3
- package/public/report/spines/mckinsey-deck.css +8 -3
- package/public/report/spines/openai-paper.css +8 -3
- package/public/report.html +570 -141
- package/public/room-settings.js +2 -0
- package/public/user-settings.css +178 -0
- package/public/user-settings.js +172 -17
package/public/room-settings.js
CHANGED
package/public/user-settings.css
CHANGED
|
@@ -733,6 +733,184 @@
|
|
|
733
733
|
line-height: 1.55;
|
|
734
734
|
}
|
|
735
735
|
|
|
736
|
+
/* ─── Day picker · pill toggle above the 14-day chart ───
|
|
737
|
+
Two pills max: "All · cumulative" is always present; a date pill
|
|
738
|
+
appears only when the user has clicked into a specific bar. The
|
|
739
|
+
active pill carries a hairline ring + subtle accent background so
|
|
740
|
+
the current scope is unambiguous. */
|
|
741
|
+
.us-day-picker {
|
|
742
|
+
display: inline-flex;
|
|
743
|
+
gap: 8px;
|
|
744
|
+
flex-wrap: wrap;
|
|
745
|
+
}
|
|
746
|
+
.us-day-pill {
|
|
747
|
+
appearance: none;
|
|
748
|
+
background: transparent;
|
|
749
|
+
border: 0.5px solid var(--line-bright, #2A2A26);
|
|
750
|
+
color: var(--text-soft, #8E8B83);
|
|
751
|
+
font-family: var(--mono, "Inter", system-ui, sans-serif);
|
|
752
|
+
font-size: 10.5px;
|
|
753
|
+
letter-spacing: 0.12em;
|
|
754
|
+
text-transform: uppercase;
|
|
755
|
+
font-weight: 600;
|
|
756
|
+
padding: 6px 12px;
|
|
757
|
+
cursor: pointer;
|
|
758
|
+
transition: color 0.12s, border-color 0.12s, background 0.12s;
|
|
759
|
+
}
|
|
760
|
+
.us-day-pill:hover {
|
|
761
|
+
color: var(--text, #C8C5BE);
|
|
762
|
+
border-color: var(--text-faint, #3A382F);
|
|
763
|
+
}
|
|
764
|
+
.us-day-pill.active {
|
|
765
|
+
color: var(--lime, #6FB572);
|
|
766
|
+
border-color: var(--lime, #6FB572);
|
|
767
|
+
background: var(--panel-2, #1A1A18);
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
/* ─── 14-day stacked bar chart ───
|
|
771
|
+
Layout: a header strip with the window's total, then a flex row
|
|
772
|
+
of 14 bar buttons. Each button is a vertical column wrapping a
|
|
773
|
+
`.us-chart-stack` (the actual coloured stack, height ∈ [0, 100%])
|
|
774
|
+
and a `.us-chart-tick` (M·D label below). The bar's parent flexes
|
|
775
|
+
to share width — bars stay clickable down to ~16px. */
|
|
776
|
+
.us-chart-wrap {
|
|
777
|
+
display: flex;
|
|
778
|
+
flex-direction: column;
|
|
779
|
+
gap: 10px;
|
|
780
|
+
padding: 14px 12px 8px;
|
|
781
|
+
border: 0.5px solid var(--line-bright, #2A2A26);
|
|
782
|
+
background: var(--panel-2, #1A1A18);
|
|
783
|
+
}
|
|
784
|
+
.us-chart-meta {
|
|
785
|
+
display: inline-flex;
|
|
786
|
+
align-items: baseline;
|
|
787
|
+
gap: 10px;
|
|
788
|
+
font-family: var(--mono, "Inter", system-ui, sans-serif);
|
|
789
|
+
}
|
|
790
|
+
.us-chart-meta-label {
|
|
791
|
+
font-size: 9.5px;
|
|
792
|
+
letter-spacing: 0.18em;
|
|
793
|
+
text-transform: uppercase;
|
|
794
|
+
color: var(--text-faint, #3A382F);
|
|
795
|
+
font-weight: 600;
|
|
796
|
+
}
|
|
797
|
+
.us-chart-meta-value {
|
|
798
|
+
font-size: 13px;
|
|
799
|
+
color: var(--text, #C8C5BE);
|
|
800
|
+
font-weight: 700;
|
|
801
|
+
font-variant-numeric: tabular-nums;
|
|
802
|
+
letter-spacing: -0.01em;
|
|
803
|
+
}
|
|
804
|
+
.us-chart-bars {
|
|
805
|
+
display: flex;
|
|
806
|
+
align-items: flex-end;
|
|
807
|
+
gap: 4px;
|
|
808
|
+
height: 96px;
|
|
809
|
+
/* The stack within each bar lives at the bottom (flex-end) so
|
|
810
|
+
bar height grows upward like a real chart axis. */
|
|
811
|
+
}
|
|
812
|
+
.us-chart-bar {
|
|
813
|
+
appearance: none;
|
|
814
|
+
background: transparent;
|
|
815
|
+
border: none;
|
|
816
|
+
padding: 0;
|
|
817
|
+
flex: 1 1 0;
|
|
818
|
+
min-width: 14px;
|
|
819
|
+
display: flex;
|
|
820
|
+
flex-direction: column;
|
|
821
|
+
align-items: stretch;
|
|
822
|
+
justify-content: flex-end;
|
|
823
|
+
height: 100%;
|
|
824
|
+
cursor: pointer;
|
|
825
|
+
position: relative;
|
|
826
|
+
}
|
|
827
|
+
.us-chart-bar:hover .us-chart-stack { filter: brightness(1.15); }
|
|
828
|
+
.us-chart-bar.empty .us-chart-stack {
|
|
829
|
+
height: 1px !important;
|
|
830
|
+
background: var(--line-bright, #2A2A26);
|
|
831
|
+
}
|
|
832
|
+
.us-chart-bar.today::before {
|
|
833
|
+
/* Outline marker for today's bar — a hairline ring around the
|
|
834
|
+
full bar slot (including the empty space above the stack)
|
|
835
|
+
so the present is locatable even on a zero-token day. */
|
|
836
|
+
content: "";
|
|
837
|
+
position: absolute;
|
|
838
|
+
inset: 0 -1px 14px -1px;
|
|
839
|
+
border: 0.5px solid var(--text-faint, #3A382F);
|
|
840
|
+
pointer-events: none;
|
|
841
|
+
}
|
|
842
|
+
.us-chart-bar.active::before {
|
|
843
|
+
content: "";
|
|
844
|
+
position: absolute;
|
|
845
|
+
inset: 0 -1px 14px -1px;
|
|
846
|
+
border: 1px solid var(--lime, #6FB572);
|
|
847
|
+
pointer-events: none;
|
|
848
|
+
}
|
|
849
|
+
.us-chart-stack {
|
|
850
|
+
display: flex;
|
|
851
|
+
flex-direction: column-reverse; /* first segment at the bottom */
|
|
852
|
+
width: 100%;
|
|
853
|
+
background: transparent;
|
|
854
|
+
overflow: hidden;
|
|
855
|
+
transition: height 0.2s ease;
|
|
856
|
+
}
|
|
857
|
+
.us-chart-seg {
|
|
858
|
+
display: block;
|
|
859
|
+
width: 100%;
|
|
860
|
+
min-height: 1px;
|
|
861
|
+
}
|
|
862
|
+
.us-chart-tick {
|
|
863
|
+
display: block;
|
|
864
|
+
margin-top: 4px;
|
|
865
|
+
height: 10px;
|
|
866
|
+
font-family: var(--mono, "Inter", system-ui, sans-serif);
|
|
867
|
+
font-size: 8.5px;
|
|
868
|
+
letter-spacing: 0.06em;
|
|
869
|
+
color: var(--text-faint, #3A382F);
|
|
870
|
+
text-align: center;
|
|
871
|
+
font-variant-numeric: tabular-nums;
|
|
872
|
+
}
|
|
873
|
+
.us-chart-bar.today .us-chart-tick { color: var(--text-soft, #8E8B83); }
|
|
874
|
+
.us-chart-bar.active .us-chart-tick { color: var(--lime, #6FB572); }
|
|
875
|
+
|
|
876
|
+
/* ─── Day-empty placeholder · shown in the drill-down area when the
|
|
877
|
+
user clicks a bar with zero tokens. Mirrors `.us-usage-empty`'s
|
|
878
|
+
restraint, but tighter — the chart itself already gives plenty of
|
|
879
|
+
context. */
|
|
880
|
+
.us-day-empty {
|
|
881
|
+
display: flex;
|
|
882
|
+
flex-direction: column;
|
|
883
|
+
align-items: center;
|
|
884
|
+
gap: 10px;
|
|
885
|
+
padding: 28px 12px;
|
|
886
|
+
border: 0.5px dashed var(--line-bright, #2A2A26);
|
|
887
|
+
}
|
|
888
|
+
.us-day-empty-tag {
|
|
889
|
+
font-family: var(--mono, "Inter", system-ui, sans-serif);
|
|
890
|
+
font-size: 10.5px;
|
|
891
|
+
letter-spacing: 0.18em;
|
|
892
|
+
text-transform: uppercase;
|
|
893
|
+
color: var(--text-soft, #8E8B83);
|
|
894
|
+
font-weight: 700;
|
|
895
|
+
}
|
|
896
|
+
.us-day-empty-text {
|
|
897
|
+
font-size: 11.5px;
|
|
898
|
+
color: var(--text-faint, #3A382F);
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
/* Optional scope tag above the big total · "Cumulative since install"
|
|
902
|
+
or the long-form date for the selected day. Sits as a tiny mono
|
|
903
|
+
kicker so the user knows what scope the head numbers describe. */
|
|
904
|
+
.us-usage-total-scope {
|
|
905
|
+
font-family: var(--mono, "Inter", system-ui, sans-serif);
|
|
906
|
+
font-size: 9.5px;
|
|
907
|
+
letter-spacing: 0.18em;
|
|
908
|
+
text-transform: uppercase;
|
|
909
|
+
color: var(--text-faint, #3A382F);
|
|
910
|
+
font-weight: 600;
|
|
911
|
+
margin-bottom: 8px;
|
|
912
|
+
}
|
|
913
|
+
|
|
736
914
|
/* ─── Header strip · big total + meta column ─── */
|
|
737
915
|
.us-usage-head {
|
|
738
916
|
display: grid;
|
package/public/user-settings.js
CHANGED
|
@@ -290,9 +290,29 @@
|
|
|
290
290
|
`;
|
|
291
291
|
}
|
|
292
292
|
|
|
293
|
+
/* Usage-pane state · the cumulative summary fetched from /api/usage/summary
|
|
294
|
+
* + the currently-selected day for the drill-down panel. `null` selection
|
|
295
|
+
* means "All · cumulative" (the legacy view, default on open). */
|
|
296
|
+
let _usageSummary = null;
|
|
297
|
+
let _selectedDay = null;
|
|
298
|
+
|
|
299
|
+
function fmtDayLabel(dayStr) {
|
|
300
|
+
// 'YYYY-MM-DD' → 'M·D' for the bar's x-axis tick label.
|
|
301
|
+
const [, m, d] = dayStr.split("-");
|
|
302
|
+
return `${parseInt(m, 10)}·${parseInt(d, 10)}`;
|
|
303
|
+
}
|
|
304
|
+
function fmtDayLong(dayStr) {
|
|
305
|
+
// 'YYYY-MM-DD' → 'Apr 25' style for the drill-down header.
|
|
306
|
+
const d = new Date(dayStr + "T00:00:00");
|
|
307
|
+
if (isNaN(d.getTime())) return dayStr;
|
|
308
|
+
const months = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];
|
|
309
|
+
return `${months[d.getMonth()]} ${d.getDate()}`;
|
|
310
|
+
}
|
|
311
|
+
|
|
293
312
|
function renderUsagePane(s) {
|
|
294
313
|
const pane = paneEl.querySelector("[data-usage-pane]");
|
|
295
314
|
if (!pane) return;
|
|
315
|
+
_usageSummary = s;
|
|
296
316
|
if (!s || s.totalTokens === 0) {
|
|
297
317
|
pane.innerHTML = `
|
|
298
318
|
<div class="us-usage-empty">
|
|
@@ -302,15 +322,138 @@
|
|
|
302
322
|
`;
|
|
303
323
|
return;
|
|
304
324
|
}
|
|
325
|
+
pane.innerHTML = `
|
|
326
|
+
${renderDayPicker(s)}
|
|
327
|
+
${renderUsageChart(s)}
|
|
328
|
+
<div data-usage-detail>${renderUsageDetail(s, null)}</div>
|
|
329
|
+
`;
|
|
330
|
+
}
|
|
305
331
|
|
|
306
|
-
|
|
307
|
-
|
|
332
|
+
/* ─── Day picker · pill toggle above the chart ────────────────
|
|
333
|
+
Two pills: [All · cumulative] always present; the second appears
|
|
334
|
+
only when a specific day is selected and shows "May 8" (or the
|
|
335
|
+
localised date). Clicking either pill or any chart bar swaps
|
|
336
|
+
the detail body below. */
|
|
337
|
+
function renderDayPicker(s) {
|
|
338
|
+
const day = _selectedDay;
|
|
339
|
+
const allActive = day === null ? " active" : "";
|
|
340
|
+
const dayActive = day !== null ? " active" : "";
|
|
341
|
+
const dayPill = day !== null
|
|
342
|
+
? `<button type="button" class="us-day-pill${dayActive}" data-usage-day="${escape(day)}">${escape(fmtDayLong(day))}</button>`
|
|
343
|
+
: "";
|
|
344
|
+
return `
|
|
345
|
+
<div class="us-day-picker">
|
|
346
|
+
<button type="button" class="us-day-pill${allActive}" data-usage-day="all">All · cumulative</button>
|
|
347
|
+
${dayPill}
|
|
348
|
+
</div>
|
|
349
|
+
`;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/* ─── 14-day stacked bar chart · provider-coloured ───────────
|
|
353
|
+
Each bar is a vertical column flexing to fill the available
|
|
354
|
+
width; segments inside it stack by provider colour, weighted by
|
|
355
|
+
each provider's share of THAT day's tokens. Bar heights are
|
|
356
|
+
linear-scaled to the 14-day window's max — empty days render
|
|
357
|
+
as a 1px baseline tick that's still clickable so the user can
|
|
358
|
+
drill into "no usage on this day" without a separate empty
|
|
359
|
+
state. Today's bar carries an outline marker. */
|
|
360
|
+
function renderUsageChart(s) {
|
|
361
|
+
const days = Array.isArray(s.daily) ? s.daily : [];
|
|
362
|
+
if (days.length === 0) return "";
|
|
363
|
+
const max = days.reduce((m, d) => Math.max(m, d.totalTokens || 0), 0);
|
|
364
|
+
const last14Total = days.reduce((sum, d) => sum + (d.totalTokens || 0), 0);
|
|
365
|
+
const todayKey = days[days.length - 1]?.day;
|
|
366
|
+
const bars = days.map((d) => {
|
|
367
|
+
const total = d.totalTokens || 0;
|
|
368
|
+
// Linear height scaled to 14-day max. Below 1% of max we still
|
|
369
|
+
// give a 1px tick so empty days are clickable.
|
|
370
|
+
const heightPct = max > 0 ? Math.max(total > 0 ? (total / max) * 100 : 0, total > 0 ? 2 : 0) : 0;
|
|
371
|
+
// Provider sub-segments inside the bar · stacked bottom → top.
|
|
372
|
+
const segs = (d.byModel || [])
|
|
373
|
+
.reduce((map, m) => {
|
|
374
|
+
// Collapse models within the same provider into one stack
|
|
375
|
+
// segment · bar resolution stays per-provider; per-model
|
|
376
|
+
// detail lives in the drill-down below.
|
|
377
|
+
const cur = map.get(m.provider) || { tokens: 0, names: [] };
|
|
378
|
+
cur.tokens += m.tokens;
|
|
379
|
+
cur.names.push(`${m.displayName} ${fmtTokens(m.tokens)}`);
|
|
380
|
+
map.set(m.provider, cur);
|
|
381
|
+
return map;
|
|
382
|
+
}, new Map());
|
|
383
|
+
const segHtml = Array.from(segs.entries()).map(([provider, v]) => {
|
|
384
|
+
const segPct = total > 0 ? (v.tokens / total) * 100 : 0;
|
|
385
|
+
const color = PROVIDER_COLOR_VAR[provider] || PROVIDER_COLOR_VAR.unknown;
|
|
386
|
+
return `<span class="us-chart-seg" style="height:${segPct.toFixed(2)}%;background:var(${color})" title="${escape(v.names.join(' · '))}"></span>`;
|
|
387
|
+
}).join("");
|
|
388
|
+
const isToday = d.day === todayKey;
|
|
389
|
+
const isSelected = _selectedDay === d.day;
|
|
390
|
+
const cls = ["us-chart-bar"];
|
|
391
|
+
if (isToday) cls.push("today");
|
|
392
|
+
if (isSelected) cls.push("active");
|
|
393
|
+
if (total === 0) cls.push("empty");
|
|
394
|
+
const tooltip = total > 0
|
|
395
|
+
? `${fmtDayLong(d.day)} · ${fmtTokens(total)} tokens`
|
|
396
|
+
: `${fmtDayLong(d.day)} · no usage`;
|
|
397
|
+
return `
|
|
398
|
+
<button type="button" class="${cls.join(' ')}" data-usage-day="${escape(d.day)}" title="${escape(tooltip)}">
|
|
399
|
+
<span class="us-chart-stack" style="height:${heightPct.toFixed(2)}%">${segHtml}</span>
|
|
400
|
+
<span class="us-chart-tick">${escape(fmtDayLabel(d.day))}</span>
|
|
401
|
+
</button>
|
|
402
|
+
`;
|
|
403
|
+
}).join("");
|
|
404
|
+
return `
|
|
405
|
+
<div class="us-chart-wrap" aria-label="14-day token usage">
|
|
406
|
+
<div class="us-chart-meta">
|
|
407
|
+
<span class="us-chart-meta-label">Last 14 days</span>
|
|
408
|
+
<span class="us-chart-meta-value">${fmtTokens(last14Total)}</span>
|
|
409
|
+
</div>
|
|
410
|
+
<div class="us-chart-bars">${bars}</div>
|
|
411
|
+
</div>
|
|
412
|
+
`;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/* ─── Detail · the original "by model / top consumers" body,
|
|
416
|
+
parameterised on either the cumulative summary `s` (when
|
|
417
|
+
`dayKey === null`) or one day's rollup pulled from `s.daily`
|
|
418
|
+
(when `dayKey` matches a day). ────────────────────────────── */
|
|
419
|
+
function renderUsageDetail(s, dayKey) {
|
|
420
|
+
if (dayKey === null) {
|
|
421
|
+
return renderDetailBody({
|
|
422
|
+
total: s.totalTokens,
|
|
423
|
+
byModel: s.byModel,
|
|
424
|
+
byAgent: s.byAgent,
|
|
425
|
+
agentCount: s.agentCount,
|
|
426
|
+
retired: s.retired || { tokens: 0, agents: 0 },
|
|
427
|
+
scopeLabel: "Cumulative since install",
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
const d = (s.daily || []).find((x) => x.day === dayKey);
|
|
431
|
+
if (!d || d.totalTokens === 0) {
|
|
432
|
+
return `
|
|
433
|
+
<div class="us-day-empty">
|
|
434
|
+
<div class="us-day-empty-tag">${escape(fmtDayLong(dayKey))}</div>
|
|
435
|
+
<div class="us-day-empty-text">no usage on this day.</div>
|
|
436
|
+
</div>
|
|
437
|
+
`;
|
|
438
|
+
}
|
|
439
|
+
return renderDetailBody({
|
|
440
|
+
total: d.totalTokens,
|
|
441
|
+
byModel: d.byModel,
|
|
442
|
+
byAgent: d.byAgent,
|
|
443
|
+
agentCount: d.byAgent.length,
|
|
444
|
+
retired: { tokens: 0, agents: 0 },
|
|
445
|
+
scopeLabel: fmtDayLong(dayKey),
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
function renderDetailBody({ total, byModel, byAgent, agentCount, retired, scopeLabel }) {
|
|
450
|
+
const segments = byModel.map((m) => {
|
|
308
451
|
const pct = (m.tokens / total) * 100;
|
|
309
452
|
const color = PROVIDER_COLOR_VAR[m.provider] || PROVIDER_COLOR_VAR.unknown;
|
|
310
453
|
return `<span class="us-usage-seg" style="width:${pct.toFixed(2)}%;background:var(${color})" title="${escape(m.displayName)} · ${fmtTokens(m.tokens)}"></span>`;
|
|
311
454
|
}).join("");
|
|
312
455
|
|
|
313
|
-
const modelRows =
|
|
456
|
+
const modelRows = byModel.map((m) => {
|
|
314
457
|
const pct = (m.tokens / total) * 100;
|
|
315
458
|
const color = PROVIDER_COLOR_VAR[m.provider] || PROVIDER_COLOR_VAR.unknown;
|
|
316
459
|
return `
|
|
@@ -332,8 +475,7 @@
|
|
|
332
475
|
`;
|
|
333
476
|
}).join("");
|
|
334
477
|
|
|
335
|
-
|
|
336
|
-
const topAgents = s.byAgent.filter((a) => a.tokens > 0).slice(0, 6);
|
|
478
|
+
const topAgents = byAgent.filter((a) => a.tokens > 0).slice(0, 6);
|
|
337
479
|
const agentRows = topAgents.map((a) => {
|
|
338
480
|
const pct = (a.tokens / total) * 100;
|
|
339
481
|
const color = PROVIDER_COLOR_VAR[a.provider] || PROVIDER_COLOR_VAR.unknown;
|
|
@@ -353,17 +495,11 @@
|
|
|
353
495
|
`;
|
|
354
496
|
}).join("");
|
|
355
497
|
|
|
356
|
-
const silentCount =
|
|
498
|
+
const silentCount = byAgent.length - topAgents.length;
|
|
357
499
|
const silentNote = silentCount > 0
|
|
358
500
|
? `<div class="us-agent-silent">+ ${silentCount} agent${silentCount === 1 ? "" : "s"} not yet billed</div>`
|
|
359
501
|
: "";
|
|
360
502
|
|
|
361
|
-
// Retired-agents footer · custom directors that the user has
|
|
362
|
-
// deleted. Their per-agent identity is gone but the tokens were
|
|
363
|
-
// real, so we surface a small footer note acknowledging that the
|
|
364
|
-
// model-level totals above include their share. Hidden when no
|
|
365
|
-
// agents have ever been deleted with consumed tokens.
|
|
366
|
-
const retired = s.retired || { tokens: 0, agents: 0 };
|
|
367
503
|
const retiredNote = retired.tokens > 0
|
|
368
504
|
? `
|
|
369
505
|
<div class="us-usage-retired">
|
|
@@ -377,24 +513,25 @@
|
|
|
377
513
|
`
|
|
378
514
|
: "";
|
|
379
515
|
|
|
380
|
-
|
|
516
|
+
return `
|
|
381
517
|
<div class="us-usage-head">
|
|
382
518
|
<div class="us-usage-total">
|
|
519
|
+
<div class="us-usage-total-scope">${escape(scopeLabel)}</div>
|
|
383
520
|
<div class="us-usage-total-num">${fmtTokens(total)}</div>
|
|
384
521
|
<div class="us-usage-total-raw">${total.toLocaleString()} tokens</div>
|
|
385
522
|
</div>
|
|
386
523
|
<div class="us-usage-meta">
|
|
387
524
|
<div class="us-usage-meta-row">
|
|
388
525
|
<span class="us-usage-meta-label">Models</span>
|
|
389
|
-
<span class="us-usage-meta-value">${
|
|
526
|
+
<span class="us-usage-meta-value">${byModel.length}</span>
|
|
390
527
|
</div>
|
|
391
528
|
<div class="us-usage-meta-row">
|
|
392
529
|
<span class="us-usage-meta-label">Agents</span>
|
|
393
|
-
<span class="us-usage-meta-value">${
|
|
530
|
+
<span class="us-usage-meta-value">${agentCount}</span>
|
|
394
531
|
</div>
|
|
395
532
|
<div class="us-usage-meta-row">
|
|
396
533
|
<span class="us-usage-meta-label">Active</span>
|
|
397
|
-
<span class="us-usage-meta-value">${
|
|
534
|
+
<span class="us-usage-meta-value">${byAgent.filter((a) => a.tokens > 0).length}</span>
|
|
398
535
|
</div>
|
|
399
536
|
</div>
|
|
400
537
|
</div>
|
|
@@ -418,14 +555,32 @@
|
|
|
418
555
|
`;
|
|
419
556
|
}
|
|
420
557
|
|
|
558
|
+
/** Click handler · delegated on the pane. Bar OR pill click flips
|
|
559
|
+
* `_selectedDay` and re-renders chart + drill-down in place. We
|
|
560
|
+
* re-render the WHOLE pane (cheap; it's a small DOM) so the active-
|
|
561
|
+
* state classes on bars and pills both stay in sync. */
|
|
562
|
+
function onUsageClick(e) {
|
|
563
|
+
const trigger = e.target.closest("[data-usage-day]");
|
|
564
|
+
if (!trigger) return;
|
|
565
|
+
if (!_usageSummary) return;
|
|
566
|
+
const next = trigger.dataset.usageDay;
|
|
567
|
+
_selectedDay = (next === "all") ? null : next;
|
|
568
|
+
renderUsagePane(_usageSummary);
|
|
569
|
+
}
|
|
570
|
+
|
|
421
571
|
async function wireUsageSection() {
|
|
572
|
+
const pane = paneEl.querySelector("[data-usage-pane]");
|
|
573
|
+
if (pane && !pane.dataset.usageBound) {
|
|
574
|
+
pane.addEventListener("click", onUsageClick);
|
|
575
|
+
pane.dataset.usageBound = "1";
|
|
576
|
+
}
|
|
422
577
|
try {
|
|
423
578
|
const r = await fetch("/api/usage/summary");
|
|
424
579
|
if (!r.ok) throw new Error("HTTP " + r.status);
|
|
425
580
|
const s = await r.json();
|
|
581
|
+
_selectedDay = null; // reset to "All" on each pane open
|
|
426
582
|
renderUsagePane(s);
|
|
427
583
|
} catch (e) {
|
|
428
|
-
const pane = paneEl.querySelector("[data-usage-pane]");
|
|
429
584
|
if (pane) pane.innerHTML = `<div class="us-usage-empty"><div class="us-usage-empty-text">couldn't fetch usage stats. ${escape(String(e && e.message || e))}</div></div>`;
|
|
430
585
|
}
|
|
431
586
|
}
|