@acarmisc/backstage-plugin-litellm 0.12.4 → 0.14.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/index.cjs.js CHANGED
@@ -174,72 +174,676 @@ var init_api = __esm({
174
174
  }
175
175
  });
176
176
 
177
- // src/components/DashboardHeader.tsx
178
- var import_react, import_Box, import_Typography, import_LinearProgress, import_Paper, import_Chip, import_Button, import_Stack, import_icons_material, DashboardHeader;
179
- var init_DashboardHeader = __esm({
180
- "src/components/DashboardHeader.tsx"() {
177
+ // src/components/ui.tsx
178
+ function chartColor(name) {
179
+ if (name === "Other") return OTHER_COLOR;
180
+ let h = 5381;
181
+ for (let i = 0; i < name.length; i++) h = (h << 5) + h + name.charCodeAt(i) >>> 0;
182
+ return CHART_COLORS[h % CHART_COLORS.length];
183
+ }
184
+ function useChartTheme() {
185
+ const theme = (0, import_styles.useTheme)();
186
+ const tickFill = theme.palette.text.secondary;
187
+ return {
188
+ grid: {
189
+ stroke: (0, import_styles.alpha)(theme.palette.text.primary, theme.palette.mode === "dark" ? 0.12 : 0.08),
190
+ strokeDasharray: "2 4",
191
+ vertical: false
192
+ },
193
+ axis: {
194
+ tick: { fontSize: 11, fill: tickFill },
195
+ tickLine: false,
196
+ axisLine: { stroke: (0, import_styles.alpha)(theme.palette.text.primary, 0.12) }
197
+ },
198
+ legend: {
199
+ wrapperStyle: {
200
+ fontSize: 11,
201
+ color: tickFill,
202
+ paddingTop: 8
203
+ },
204
+ iconType: "circle",
205
+ iconSize: 8
206
+ },
207
+ cursor: { fill: (0, import_styles.alpha)(theme.palette.text.primary, 0.05) }
208
+ };
209
+ }
210
+ function toneColor(theme, tone) {
211
+ switch (tone) {
212
+ case "success":
213
+ return theme.palette.success.main;
214
+ case "warning":
215
+ return theme.palette.warning.main;
216
+ case "danger":
217
+ return theme.palette.error.main;
218
+ case "info":
219
+ return theme.palette.info.main;
220
+ case "accent":
221
+ return theme.palette.primary.main;
222
+ default:
223
+ return theme.palette.text.secondary;
224
+ }
225
+ }
226
+ function SegmentedControl({
227
+ value,
228
+ onChange,
229
+ options
230
+ }) {
231
+ return /* @__PURE__ */ import_react.default.createElement(
232
+ import_Box.default,
233
+ {
234
+ role: "tablist",
235
+ sx: (theme) => ({
236
+ display: "inline-flex",
237
+ gap: 0.25,
238
+ p: 0.375,
239
+ borderRadius: 1.5,
240
+ border: "1px solid",
241
+ borderColor: theme.palette.divider
242
+ })
243
+ },
244
+ options.map((opt) => {
245
+ const selected = opt.value === value;
246
+ return /* @__PURE__ */ import_react.default.createElement(
247
+ import_ButtonBase.default,
248
+ {
249
+ key: opt.value,
250
+ role: "tab",
251
+ "aria-selected": selected,
252
+ onClick: () => onChange(opt.value),
253
+ sx: (theme) => ({
254
+ px: 1.5,
255
+ height: 28,
256
+ borderRadius: 1,
257
+ fontSize: 13,
258
+ fontWeight: selected ? 700 : 500,
259
+ // Tint the selection with the accent rather than swapping in a
260
+ // surface colour: on a dark theme `background.paper` sits *behind*
261
+ // the track, so the selected chip would read as recessed.
262
+ color: selected ? theme.palette.primary.main : theme.palette.text.secondary,
263
+ bgcolor: selected ? (0, import_styles.alpha)(theme.palette.primary.main, theme.palette.mode === "dark" ? 0.2 : 0.11) : "transparent",
264
+ transition: theme.transitions.create(["background-color", "color"]),
265
+ "&:hover": {
266
+ color: selected ? theme.palette.primary.main : theme.palette.text.primary,
267
+ bgcolor: selected ? (0, import_styles.alpha)(theme.palette.primary.main, theme.palette.mode === "dark" ? 0.26 : 0.15) : (0, import_styles.alpha)(theme.palette.text.primary, 0.05)
268
+ }
269
+ })
270
+ },
271
+ opt.label
272
+ );
273
+ })
274
+ );
275
+ }
276
+ var import_react, import_Box, import_ButtonBase, import_Paper, import_Typography, import_styles, MONTHS, fmtDateShort, fmtCompact, fmtUsdCompact, CHART_COLORS, SERIES, OTHER_COLOR, ChartTooltip, SeriesLegend, StatusPill, TagChip, SectionCard, ChartCard, MetricStrip, Stat, Meter, dataTableSx, quietIconButtonSx, EmptyState;
277
+ var init_ui = __esm({
278
+ "src/components/ui.tsx"() {
181
279
  "use strict";
182
280
  import_react = __toESM(require("react"));
183
281
  import_Box = __toESM(require("@mui/material/Box"));
184
- import_Typography = __toESM(require("@mui/material/Typography"));
185
- import_LinearProgress = __toESM(require("@mui/material/LinearProgress"));
282
+ import_ButtonBase = __toESM(require("@mui/material/ButtonBase"));
186
283
  import_Paper = __toESM(require("@mui/material/Paper"));
187
- import_Chip = __toESM(require("@mui/material/Chip"));
284
+ import_Typography = __toESM(require("@mui/material/Typography"));
285
+ import_styles = require("@mui/material/styles");
286
+ MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
287
+ fmtDateShort = (value) => {
288
+ const m = /^(\d{4})-(\d{2})-(\d{2})/.exec(value ?? "");
289
+ if (!m) return value;
290
+ return `${MONTHS[Number(m[2]) - 1]} ${Number(m[3])}`;
291
+ };
292
+ fmtCompact = (n) => {
293
+ const v = n ?? 0;
294
+ const abs = Math.abs(v);
295
+ if (abs >= 1e9) return `${(v / 1e9).toFixed(abs >= 1e10 ? 0 : 1)}B`;
296
+ if (abs >= 1e6) return `${(v / 1e6).toFixed(abs >= 1e7 ? 0 : 1)}M`;
297
+ if (abs >= 1e3) return `${(v / 1e3).toFixed(abs >= 1e4 ? 0 : 1)}K`;
298
+ return String(Math.round(v));
299
+ };
300
+ fmtUsdCompact = (n) => {
301
+ const v = n ?? 0;
302
+ if (Math.abs(v) >= 1e3) return `$${fmtCompact(v)}`;
303
+ if (Math.abs(v) >= 10) return `$${v.toFixed(0)}`;
304
+ return `$${v.toFixed(2)}`;
305
+ };
306
+ CHART_COLORS = [
307
+ "#6366F1",
308
+ // indigo
309
+ "#14B8A6",
310
+ // teal
311
+ "#F59E0B",
312
+ // amber
313
+ "#EC4899",
314
+ // pink
315
+ "#38BDF8",
316
+ // sky
317
+ "#84CC16",
318
+ // lime
319
+ "#F97316",
320
+ // orange
321
+ "#A855F7"
322
+ // violet
323
+ ];
324
+ SERIES = {
325
+ input: "#6366F1",
326
+ output: "#14B8A6",
327
+ success: "#10B981",
328
+ failure: "#EF4444",
329
+ spend: "#F59E0B",
330
+ budget: "#EF4444"
331
+ };
332
+ OTHER_COLOR = "#94A3B8";
333
+ ChartTooltip = ({
334
+ active,
335
+ label,
336
+ payload,
337
+ valueFormatter = (v) => v.toLocaleString(),
338
+ labelFormatter = fmtDateShort,
339
+ hideZero = false
340
+ }) => {
341
+ if (!active || !payload?.length) return null;
342
+ const rows = payload.filter(
343
+ (p) => p.value !== void 0 && p.value !== null && (!hideZero || p.value !== 0)
344
+ );
345
+ if (!rows.length) return null;
346
+ return /* @__PURE__ */ import_react.default.createElement(
347
+ import_Paper.default,
348
+ {
349
+ elevation: 8,
350
+ sx: {
351
+ px: 1.5,
352
+ py: 1,
353
+ borderRadius: 1.5,
354
+ border: "1px solid",
355
+ borderColor: "divider",
356
+ minWidth: 150,
357
+ pointerEvents: "none"
358
+ }
359
+ },
360
+ /* @__PURE__ */ import_react.default.createElement(
361
+ import_Typography.default,
362
+ {
363
+ variant: "caption",
364
+ sx: { display: "block", fontWeight: 700, mb: 0.75, letterSpacing: "0.02em" }
365
+ },
366
+ typeof label === "string" ? labelFormatter(label) : label
367
+ ),
368
+ rows.map((row, i) => /* @__PURE__ */ import_react.default.createElement(
369
+ import_Box.default,
370
+ {
371
+ key: `${row.dataKey ?? row.name ?? i}`,
372
+ sx: { display: "flex", alignItems: "center", gap: 1, mt: i === 0 ? 0 : 0.5 }
373
+ },
374
+ /* @__PURE__ */ import_react.default.createElement(
375
+ import_Box.default,
376
+ {
377
+ sx: {
378
+ width: 8,
379
+ height: 8,
380
+ borderRadius: "50%",
381
+ bgcolor: row.color,
382
+ flexShrink: 0
383
+ }
384
+ }
385
+ ),
386
+ /* @__PURE__ */ import_react.default.createElement(
387
+ import_Typography.default,
388
+ {
389
+ variant: "caption",
390
+ color: "text.secondary",
391
+ sx: { flex: 1, mr: 1.5, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }
392
+ },
393
+ row.name
394
+ ),
395
+ /* @__PURE__ */ import_react.default.createElement(import_Typography.default, { variant: "caption", sx: { fontWeight: 700, fontVariantNumeric: "tabular-nums" } }, valueFormatter(row.value))
396
+ ))
397
+ );
398
+ };
399
+ SeriesLegend = ({
400
+ series
401
+ }) => /* @__PURE__ */ import_react.default.createElement(
402
+ import_Box.default,
403
+ {
404
+ sx: {
405
+ display: "flex",
406
+ flexWrap: "wrap",
407
+ gap: 0.5,
408
+ columnGap: 2,
409
+ rowGap: 0.5,
410
+ mt: 1.5
411
+ }
412
+ },
413
+ series.map((s) => /* @__PURE__ */ import_react.default.createElement(import_Box.default, { key: s.name, sx: { display: "flex", alignItems: "center", gap: 0.75, minWidth: 0 } }, /* @__PURE__ */ import_react.default.createElement(
414
+ import_Box.default,
415
+ {
416
+ sx: { width: 8, height: 8, borderRadius: "50%", bgcolor: s.color, flexShrink: 0 }
417
+ }
418
+ ), /* @__PURE__ */ import_react.default.createElement(
419
+ import_Typography.default,
420
+ {
421
+ variant: "caption",
422
+ color: "text.secondary",
423
+ title: s.name,
424
+ sx: {
425
+ fontSize: 11,
426
+ maxWidth: 190,
427
+ overflow: "hidden",
428
+ textOverflow: "ellipsis",
429
+ whiteSpace: "nowrap"
430
+ }
431
+ },
432
+ s.name
433
+ )))
434
+ );
435
+ StatusPill = ({ label, tone = "neutral", dot = true, title, sx }) => /* @__PURE__ */ import_react.default.createElement(
436
+ import_Box.default,
437
+ {
438
+ component: "span",
439
+ title,
440
+ sx: [
441
+ (theme) => {
442
+ const c = toneColor(theme, tone);
443
+ return {
444
+ display: "inline-flex",
445
+ alignItems: "center",
446
+ gap: 0.625,
447
+ height: 22,
448
+ px: 0.875,
449
+ borderRadius: "6px",
450
+ border: "1px solid",
451
+ borderColor: (0, import_styles.alpha)(c, tone === "neutral" ? 0.25 : 0.35),
452
+ bgcolor: (0, import_styles.alpha)(c, tone === "neutral" ? 0.06 : 0.12),
453
+ color: tone === "neutral" ? theme.palette.text.secondary : c,
454
+ fontSize: 11.5,
455
+ fontWeight: 600,
456
+ lineHeight: 1,
457
+ letterSpacing: "0.01em",
458
+ whiteSpace: "nowrap",
459
+ maxWidth: "100%"
460
+ };
461
+ },
462
+ ...Array.isArray(sx) ? sx : [sx]
463
+ ]
464
+ },
465
+ dot && /* @__PURE__ */ import_react.default.createElement(
466
+ import_Box.default,
467
+ {
468
+ component: "span",
469
+ sx: (theme) => ({
470
+ width: 6,
471
+ height: 6,
472
+ borderRadius: "50%",
473
+ bgcolor: toneColor(theme, tone),
474
+ flexShrink: 0
475
+ })
476
+ }
477
+ ),
478
+ /* @__PURE__ */ import_react.default.createElement(import_Box.default, { component: "span", sx: { overflow: "hidden", textOverflow: "ellipsis" } }, label)
479
+ );
480
+ TagChip = ({
481
+ label,
482
+ title,
483
+ mono = true
484
+ }) => /* @__PURE__ */ import_react.default.createElement(
485
+ import_Box.default,
486
+ {
487
+ component: "span",
488
+ title,
489
+ sx: (theme) => ({
490
+ display: "inline-flex",
491
+ alignItems: "center",
492
+ height: 20,
493
+ px: 0.75,
494
+ borderRadius: "5px",
495
+ bgcolor: (0, import_styles.alpha)(theme.palette.text.primary, theme.palette.mode === "dark" ? 0.08 : 0.05),
496
+ color: theme.palette.text.secondary,
497
+ fontFamily: mono ? "ui-monospace, SFMono-Regular, Menlo, monospace" : void 0,
498
+ fontSize: 11,
499
+ lineHeight: 1,
500
+ whiteSpace: "nowrap",
501
+ maxWidth: 220,
502
+ overflow: "hidden",
503
+ textOverflow: "ellipsis"
504
+ })
505
+ },
506
+ label
507
+ );
508
+ SectionCard = ({ title, subtitle, actions, flush = false, children }) => /* @__PURE__ */ import_react.default.createElement(import_Paper.default, { sx: { borderRadius: 2, overflow: "hidden" } }, (title || actions) && /* @__PURE__ */ import_react.default.createElement(
509
+ import_Box.default,
510
+ {
511
+ sx: {
512
+ display: "flex",
513
+ alignItems: "center",
514
+ justifyContent: "space-between",
515
+ gap: 2,
516
+ flexWrap: "wrap",
517
+ px: 2.5,
518
+ py: 2
519
+ }
520
+ },
521
+ /* @__PURE__ */ import_react.default.createElement(import_Box.default, { sx: { minWidth: 0 } }, /* @__PURE__ */ import_react.default.createElement(import_Typography.default, { variant: "h6", sx: { lineHeight: 1.2 } }, title), subtitle && /* @__PURE__ */ import_react.default.createElement(import_Typography.default, { variant: "caption", color: "text.secondary" }, subtitle)),
522
+ actions && /* @__PURE__ */ import_react.default.createElement(import_Box.default, { sx: { display: "flex", alignItems: "center", gap: 1, flexWrap: "wrap" } }, actions)
523
+ ), /* @__PURE__ */ import_react.default.createElement(import_Box.default, { sx: flush ? void 0 : { px: 2.5, pb: 2.5, pt: title ? 0 : 2.5 } }, children));
524
+ ChartCard = ({ title, meta, height = 240, children }) => (
525
+ // The plot area must have an explicit height, never a flex-derived one:
526
+ // Recharts' ResponsiveContainer measures its parent, so a parent that sizes
527
+ // itself from its children instead spins in a measure/resize loop.
528
+ /* @__PURE__ */ import_react.default.createElement(import_Paper.default, { variant: "outlined", sx: { p: 2, borderRadius: 2, height: "100%" } }, /* @__PURE__ */ import_react.default.createElement(import_Box.default, { sx: { display: "flex", alignItems: "baseline", gap: 1, mb: 1.5, flexWrap: "wrap" } }, /* @__PURE__ */ import_react.default.createElement(
529
+ import_Typography.default,
530
+ {
531
+ variant: "subtitle2",
532
+ sx: { fontSize: 12, fontWeight: 700, letterSpacing: "0.06em", textTransform: "uppercase" }
533
+ },
534
+ title
535
+ ), meta && /* @__PURE__ */ import_react.default.createElement(import_Typography.default, { variant: "caption", color: "text.secondary" }, meta)), /* @__PURE__ */ import_react.default.createElement(import_Box.default, { sx: { height, width: "100%" } }, children))
536
+ );
537
+ MetricStrip = ({ metrics }) => /* @__PURE__ */ import_react.default.createElement(
538
+ import_Paper.default,
539
+ {
540
+ variant: "outlined",
541
+ sx: {
542
+ borderRadius: 2,
543
+ display: "grid",
544
+ gridTemplateColumns: { xs: "1fr 1fr", md: `repeat(${metrics.length}, 1fr)` },
545
+ overflow: "hidden"
546
+ }
547
+ },
548
+ metrics.map((m, i) => /* @__PURE__ */ import_react.default.createElement(
549
+ import_Box.default,
550
+ {
551
+ key: m.label,
552
+ sx: (theme) => ({
553
+ px: 2.5,
554
+ py: 2,
555
+ borderLeft: i === 0 ? 0 : "1px solid",
556
+ borderTop: 0,
557
+ borderColor: "divider",
558
+ [theme.breakpoints.down("md")]: {
559
+ borderLeft: i % 2 === 0 ? 0 : "1px solid",
560
+ borderTop: i < 2 ? 0 : "1px solid",
561
+ borderColor: "divider"
562
+ }
563
+ })
564
+ },
565
+ /* @__PURE__ */ import_react.default.createElement(import_Box.default, { sx: { display: "flex", alignItems: "center", gap: 0.875, mb: 0.75 } }, /* @__PURE__ */ import_react.default.createElement(
566
+ import_Box.default,
567
+ {
568
+ sx: (theme) => ({
569
+ width: 6,
570
+ height: 6,
571
+ borderRadius: "50%",
572
+ bgcolor: toneColor(theme, m.tone ?? "accent"),
573
+ flexShrink: 0
574
+ })
575
+ }
576
+ ), /* @__PURE__ */ import_react.default.createElement(
577
+ import_Typography.default,
578
+ {
579
+ variant: "caption",
580
+ color: "text.secondary",
581
+ sx: { fontSize: 11, fontWeight: 600, letterSpacing: "0.07em", textTransform: "uppercase" }
582
+ },
583
+ m.label
584
+ )),
585
+ /* @__PURE__ */ import_react.default.createElement(
586
+ import_Typography.default,
587
+ {
588
+ sx: { fontSize: 26, fontWeight: 700, lineHeight: 1.15, fontVariantNumeric: "tabular-nums" }
589
+ },
590
+ m.value
591
+ ),
592
+ /* @__PURE__ */ import_react.default.createElement(
593
+ import_Typography.default,
594
+ {
595
+ variant: "caption",
596
+ color: "text.secondary",
597
+ sx: { display: "block", mt: 0.5, minHeight: 16 }
598
+ },
599
+ m.hint ?? "\xA0"
600
+ )
601
+ ))
602
+ );
603
+ Stat = ({ label, value }) => /* @__PURE__ */ import_react.default.createElement(import_Box.default, { sx: { minWidth: 0 } }, /* @__PURE__ */ import_react.default.createElement(
604
+ import_Typography.default,
605
+ {
606
+ variant: "caption",
607
+ color: "text.secondary",
608
+ sx: { display: "block", fontSize: 10.5, fontWeight: 600, letterSpacing: "0.07em", textTransform: "uppercase" }
609
+ },
610
+ label
611
+ ), /* @__PURE__ */ import_react.default.createElement(
612
+ import_Typography.default,
613
+ {
614
+ variant: "body2",
615
+ sx: { fontWeight: 600, fontVariantNumeric: "tabular-nums", mt: 0.25 }
616
+ },
617
+ value
618
+ ));
619
+ Meter = ({
620
+ value,
621
+ tone = "accent",
622
+ height = 5
623
+ }) => /* @__PURE__ */ import_react.default.createElement(
624
+ import_Box.default,
625
+ {
626
+ sx: (theme) => ({
627
+ height,
628
+ borderRadius: height,
629
+ bgcolor: (0, import_styles.alpha)(theme.palette.text.primary, theme.palette.mode === "dark" ? 0.12 : 0.08),
630
+ overflow: "hidden"
631
+ })
632
+ },
633
+ /* @__PURE__ */ import_react.default.createElement(
634
+ import_Box.default,
635
+ {
636
+ sx: (theme) => ({
637
+ height: "100%",
638
+ width: `${Math.max(0, Math.min(100, value))}%`,
639
+ borderRadius: height,
640
+ bgcolor: toneColor(theme, tone),
641
+ transition: theme.transitions.create("width")
642
+ })
643
+ }
644
+ )
645
+ );
646
+ dataTableSx = (theme) => ({
647
+ // Element selectors, not `.MuiTableCell-head`: host apps may set a MUI
648
+ // classname prefix (Backstage ships `v5-`), which breaks global class targeting.
649
+ "& thead th": {
650
+ fontSize: 11,
651
+ fontWeight: 700,
652
+ letterSpacing: "0.07em",
653
+ textTransform: "uppercase",
654
+ color: theme.palette.text.secondary,
655
+ backgroundColor: (0, import_styles.alpha)(theme.palette.text.primary, theme.palette.mode === "dark" ? 0.04 : 0.02),
656
+ borderBottom: `1px solid ${theme.palette.divider}`,
657
+ whiteSpace: "nowrap",
658
+ paddingTop: theme.spacing(1.25),
659
+ paddingBottom: theme.spacing(1.25)
660
+ },
661
+ "& tbody td": {
662
+ borderBottom: `1px solid ${(0, import_styles.alpha)(theme.palette.divider, 0.6)}`,
663
+ paddingTop: theme.spacing(1.25),
664
+ paddingBottom: theme.spacing(1.25),
665
+ fontVariantNumeric: "tabular-nums"
666
+ },
667
+ "& tbody tr:last-of-type td": {
668
+ borderBottom: 0
669
+ },
670
+ "& tbody tr:hover": {
671
+ backgroundColor: (0, import_styles.alpha)(theme.palette.text.primary, 0.03)
672
+ }
673
+ });
674
+ quietIconButtonSx = (tone = "neutral") => (theme) => ({
675
+ color: theme.palette.text.secondary,
676
+ "&:hover": {
677
+ color: toneColor(theme, tone),
678
+ bgcolor: (0, import_styles.alpha)(toneColor(theme, tone), 0.1)
679
+ }
680
+ });
681
+ EmptyState = ({ message, hint, height, action }) => /* @__PURE__ */ import_react.default.createElement(
682
+ import_Box.default,
683
+ {
684
+ sx: {
685
+ height,
686
+ minHeight: height ? void 0 : 120,
687
+ display: "flex",
688
+ flexDirection: "column",
689
+ alignItems: "center",
690
+ justifyContent: "center",
691
+ gap: 1,
692
+ textAlign: "center",
693
+ py: 3
694
+ }
695
+ },
696
+ /* @__PURE__ */ import_react.default.createElement(import_Typography.default, { variant: "body2", color: "text.secondary" }, message),
697
+ hint && /* @__PURE__ */ import_react.default.createElement(import_Typography.default, { variant: "caption", color: "text.secondary" }, hint),
698
+ action
699
+ );
700
+ }
701
+ });
702
+
703
+ // src/components/DashboardHeader.tsx
704
+ function initials(name) {
705
+ const local = name.split("@")[0] ?? name;
706
+ const parts = local.split(/[.\-_\s]+/).filter(Boolean);
707
+ if (parts.length >= 2) return (parts[0][0] + parts[1][0]).toUpperCase();
708
+ return local.slice(0, 2).toUpperCase();
709
+ }
710
+ var import_react2, import_Box2, import_Typography2, import_Skeleton, import_Paper2, import_Button, import_Divider, import_styles2, import_icons_material, CountStat, DashboardHeader;
711
+ var init_DashboardHeader = __esm({
712
+ "src/components/DashboardHeader.tsx"() {
713
+ "use strict";
714
+ import_react2 = __toESM(require("react"));
715
+ import_Box2 = __toESM(require("@mui/material/Box"));
716
+ import_Typography2 = __toESM(require("@mui/material/Typography"));
717
+ import_Skeleton = __toESM(require("@mui/material/Skeleton"));
718
+ import_Paper2 = __toESM(require("@mui/material/Paper"));
188
719
  import_Button = __toESM(require("@mui/material/Button"));
189
- import_Stack = __toESM(require("@mui/material/Stack"));
720
+ import_Divider = __toESM(require("@mui/material/Divider"));
721
+ import_styles2 = require("@mui/material/styles");
190
722
  import_icons_material = require("@mui/icons-material");
191
723
  init_api();
724
+ init_ui();
725
+ CountStat = ({ value, label, tone }) => {
726
+ const active = value > 0;
727
+ return /* @__PURE__ */ import_react2.default.createElement(import_Box2.default, { sx: { display: "flex", alignItems: "center", gap: 1 } }, /* @__PURE__ */ import_react2.default.createElement(
728
+ import_Box2.default,
729
+ {
730
+ sx: (theme) => ({
731
+ width: 7,
732
+ height: 7,
733
+ borderRadius: "50%",
734
+ flexShrink: 0,
735
+ bgcolor: active ? tone === "danger" && theme.palette.error.main || tone === "warning" && theme.palette.warning.main || theme.palette.primary.main : (0, import_styles2.alpha)(theme.palette.text.primary, 0.25)
736
+ })
737
+ }
738
+ ), /* @__PURE__ */ import_react2.default.createElement(
739
+ import_Typography2.default,
740
+ {
741
+ component: "span",
742
+ sx: { fontSize: 15, fontWeight: 700, lineHeight: 1, fontVariantNumeric: "tabular-nums" }
743
+ },
744
+ value
745
+ ), /* @__PURE__ */ import_react2.default.createElement(
746
+ import_Typography2.default,
747
+ {
748
+ component: "span",
749
+ color: "text.secondary",
750
+ sx: { fontSize: 11, fontWeight: 600, letterSpacing: "0.07em", textTransform: "uppercase" }
751
+ },
752
+ label
753
+ ));
754
+ };
192
755
  DashboardHeader = ({
193
756
  userInfo,
194
757
  teams,
195
758
  keys,
196
759
  loading,
197
- onGenerateKeyClick
760
+ onGenerateKeyClick,
761
+ tabs
198
762
  }) => {
199
- const counts = (0, import_react.useMemo)(() => {
763
+ const counts = (0, import_react2.useMemo)(() => {
200
764
  const expired = keys.filter((k) => expiryStatus(k.expires_at) === "expired").length;
201
765
  const expiringSoon = keys.filter((k) => expiryStatus(k.expires_at) === "soon").length;
202
766
  return { total: keys.length, expired, expiringSoon };
203
767
  }, [keys]);
204
- if (loading) {
205
- return /* @__PURE__ */ import_react.default.createElement(import_Paper.default, { sx: { p: 2, mb: 2 } }, /* @__PURE__ */ import_react.default.createElement(import_LinearProgress.default, null));
206
- }
207
768
  const displayName = userInfo.user_email ?? userInfo.email ?? userInfo.user_id;
208
- return /* @__PURE__ */ import_react.default.createElement(import_Paper.default, { sx: { p: 2, mb: 2 } }, /* @__PURE__ */ import_react.default.createElement(import_Box.default, { display: "flex", alignItems: "flex-start", gap: 2, flexWrap: "wrap" }, /* @__PURE__ */ import_react.default.createElement(import_Box.default, { flexGrow: 1 }, /* @__PURE__ */ import_react.default.createElement(import_Typography.default, { variant: "h6" }, displayName), /* @__PURE__ */ import_react.default.createElement(import_Typography.default, { variant: "caption", color: "text.secondary" }, userInfo.user_id), teams.length > 0 && /* @__PURE__ */ import_react.default.createElement(import_Box.default, { display: "flex", gap: 0.75, flexWrap: "wrap", mt: 1 }, teams.map((team) => /* @__PURE__ */ import_react.default.createElement(
209
- import_Chip.default,
769
+ return /* @__PURE__ */ import_react2.default.createElement(import_Paper2.default, { sx: { borderRadius: 2, overflow: "hidden" } }, /* @__PURE__ */ import_react2.default.createElement(
770
+ import_Box2.default,
210
771
  {
211
- key: team.team_id,
212
- label: team.team_alias || team.team_id,
213
- size: "small",
214
- variant: "outlined",
215
- color: "primary"
216
- }
217
- )))), /* @__PURE__ */ import_react.default.createElement(import_Box.default, { display: "flex", alignItems: "center", gap: 2, flexWrap: "wrap" }, /* @__PURE__ */ import_react.default.createElement(import_Stack.default, { direction: "row", spacing: 1 }, /* @__PURE__ */ import_react.default.createElement(import_Chip.default, { icon: /* @__PURE__ */ import_react.default.createElement(import_icons_material.Key, { fontSize: "small" }), label: `${counts.total} keys`, size: "small" }), /* @__PURE__ */ import_react.default.createElement(
218
- import_Chip.default,
219
- {
220
- icon: /* @__PURE__ */ import_react.default.createElement(import_icons_material.Warning, { fontSize: "small" }),
221
- label: `${counts.expired} expired`,
222
- size: "small",
223
- color: counts.expired > 0 ? "error" : "default"
224
- }
225
- ), /* @__PURE__ */ import_react.default.createElement(
226
- import_Chip.default,
227
- {
228
- icon: /* @__PURE__ */ import_react.default.createElement(import_icons_material.Schedule, { fontSize: "small" }),
229
- label: `${counts.expiringSoon} expiring soon`,
230
- size: "small",
231
- color: counts.expiringSoon > 0 ? "warning" : "default"
232
- }
233
- )), /* @__PURE__ */ import_react.default.createElement(
234
- import_Button.default,
772
+ sx: {
773
+ display: "flex",
774
+ alignItems: "flex-start",
775
+ gap: 2,
776
+ flexWrap: "wrap",
777
+ px: 2.5,
778
+ pt: 2.5,
779
+ pb: 2
780
+ }
781
+ },
782
+ /* @__PURE__ */ import_react2.default.createElement(
783
+ import_Box2.default,
784
+ {
785
+ sx: (theme) => ({
786
+ width: 44,
787
+ height: 44,
788
+ borderRadius: 1.5,
789
+ flexShrink: 0,
790
+ display: "flex",
791
+ alignItems: "center",
792
+ justifyContent: "center",
793
+ bgcolor: (0, import_styles2.alpha)(theme.palette.primary.main, 0.12),
794
+ color: theme.palette.primary.main,
795
+ fontSize: 15,
796
+ fontWeight: 700,
797
+ letterSpacing: "0.02em"
798
+ })
799
+ },
800
+ initials(displayName)
801
+ ),
802
+ /* @__PURE__ */ import_react2.default.createElement(import_Box2.default, { sx: { flexGrow: 1, minWidth: 220 } }, /* @__PURE__ */ import_react2.default.createElement(import_Typography2.default, { variant: "h6", sx: { lineHeight: 1.25, wordBreak: "break-word" } }, displayName), /* @__PURE__ */ import_react2.default.createElement(
803
+ import_Typography2.default,
804
+ {
805
+ variant: "caption",
806
+ color: "text.secondary",
807
+ sx: { display: "block", fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace" }
808
+ },
809
+ userInfo.user_id
810
+ ), /* @__PURE__ */ import_react2.default.createElement(import_Box2.default, { sx: { display: "flex", gap: 0.75, flexWrap: "wrap", mt: 1, minHeight: 20 } }, loading ? /* @__PURE__ */ import_react2.default.createElement(import_Skeleton.default, { variant: "rounded", width: 140, height: 20 }) : teams.map((team) => /* @__PURE__ */ import_react2.default.createElement(
811
+ TagChip,
812
+ {
813
+ key: team.team_id,
814
+ label: team.team_alias || team.team_id,
815
+ title: team.team_id
816
+ }
817
+ )))),
818
+ /* @__PURE__ */ import_react2.default.createElement(
819
+ import_Button.default,
820
+ {
821
+ variant: "contained",
822
+ color: "primary",
823
+ disableElevation: true,
824
+ startIcon: /* @__PURE__ */ import_react2.default.createElement(import_icons_material.Add, null),
825
+ onClick: onGenerateKeyClick
826
+ },
827
+ "Generate New Key"
828
+ )
829
+ ), /* @__PURE__ */ import_react2.default.createElement(import_Divider.default, null), /* @__PURE__ */ import_react2.default.createElement(
830
+ import_Box2.default,
235
831
  {
236
- variant: "contained",
237
- color: "primary",
238
- startIcon: /* @__PURE__ */ import_react.default.createElement(import_icons_material.Add, null),
239
- onClick: onGenerateKeyClick
832
+ sx: {
833
+ display: "flex",
834
+ alignItems: "center",
835
+ gap: 2.5,
836
+ flexWrap: "wrap",
837
+ px: 2.5,
838
+ py: 1.5
839
+ }
240
840
  },
241
- "Generate New Key"
242
- ))));
841
+ /* @__PURE__ */ import_react2.default.createElement(CountStat, { value: counts.total, label: "keys", tone: "accent" }),
842
+ /* @__PURE__ */ import_react2.default.createElement(import_Divider.default, { orientation: "vertical", flexItem: true, sx: { my: 0.25 } }),
843
+ /* @__PURE__ */ import_react2.default.createElement(CountStat, { value: counts.expired, label: "expired", tone: "danger" }),
844
+ /* @__PURE__ */ import_react2.default.createElement(import_Divider.default, { orientation: "vertical", flexItem: true, sx: { my: 0.25 } }),
845
+ /* @__PURE__ */ import_react2.default.createElement(CountStat, { value: counts.expiringSoon, label: "expiring soon", tone: "warning" })
846
+ ), tabs && /* @__PURE__ */ import_react2.default.createElement(import_react2.default.Fragment, null, /* @__PURE__ */ import_react2.default.createElement(import_Divider.default, null), /* @__PURE__ */ import_react2.default.createElement(import_Box2.default, { sx: { px: 1 } }, tabs)));
243
847
  };
244
848
  }
245
849
  });
@@ -250,34 +854,44 @@ function expiryChipLabel(status, expiresAt) {
250
854
  if (status === "soon") return `${Math.ceil((new Date(expiresAt).getTime() - Date.now()) / 864e5)}d left`;
251
855
  return formatDate(expiresAt);
252
856
  }
253
- function expiryChipColor(status) {
254
- if (status === "expired") return "error";
857
+ function expiryTone(status) {
858
+ if (status === "expired") return "danger";
255
859
  if (status === "soon") return "warning";
256
- return "default";
860
+ return "neutral";
257
861
  }
258
- function ExpiryChip({ expiresAt }) {
862
+ function ExpiryCell({ expiresAt }) {
259
863
  const status = expiryStatus(expiresAt);
260
- if (!status) return /* @__PURE__ */ import_react2.default.createElement(import_Typography2.default, { variant: "body2", color: "text.secondary" }, "-");
261
- const label = expiryChipLabel(status, expiresAt);
262
- const color = expiryChipColor(status);
263
- const icon = status === "expired" || status === "soon" ? /* @__PURE__ */ import_react2.default.createElement(import_icons_material2.Warning, { fontSize: "small" }) : void 0;
264
- return /* @__PURE__ */ import_react2.default.createElement(import_Chip2.default, { label, color, size: "small", icon });
864
+ if (!status) {
865
+ return /* @__PURE__ */ import_react3.default.createElement(import_Typography3.default, { variant: "body2", color: "text.secondary" }, "Never");
866
+ }
867
+ if (status === "ok") {
868
+ return /* @__PURE__ */ import_react3.default.createElement(import_Typography3.default, { variant: "body2", color: "text.secondary" }, formatDate(expiresAt));
869
+ }
870
+ return /* @__PURE__ */ import_react3.default.createElement(
871
+ StatusPill,
872
+ {
873
+ label: expiryChipLabel(status, expiresAt),
874
+ tone: expiryTone(status),
875
+ title: `Expires ${formatDate(expiresAt)}`
876
+ }
877
+ );
265
878
  }
266
- function budgetColor(pct) {
267
- if (pct >= 100) return "error";
879
+ function budgetTone(pct) {
880
+ if (pct >= 100) return "danger";
268
881
  if (pct >= 80) return "warning";
269
- return "primary";
882
+ return "accent";
270
883
  }
271
884
  function keyBlockIcon(isBlocking, blocked) {
272
- if (isBlocking) return /* @__PURE__ */ import_react2.default.createElement(import_CircularProgress.default, { size: 18 });
273
- if (blocked) return /* @__PURE__ */ import_react2.default.createElement(import_icons_material2.LockOpen, { fontSize: "small" });
274
- return /* @__PURE__ */ import_react2.default.createElement(import_icons_material2.Lock, { fontSize: "small" });
885
+ if (isBlocking) return /* @__PURE__ */ import_react3.default.createElement(import_CircularProgress.default, { size: 18 });
886
+ if (blocked) return /* @__PURE__ */ import_react3.default.createElement(import_icons_material2.LockOpen, { fontSize: "small" });
887
+ return /* @__PURE__ */ import_react3.default.createElement(import_icons_material2.Lock, { fontSize: "small" });
275
888
  }
276
889
  function BudgetCell({ spend, maxBudget }) {
277
- if (!maxBudget) return /* @__PURE__ */ import_react2.default.createElement(import_Typography2.default, { variant: "body2", color: "text.secondary" }, "-");
890
+ if (!maxBudget) {
891
+ return /* @__PURE__ */ import_react3.default.createElement(import_Typography3.default, { variant: "body2", color: "text.secondary", sx: { fontVariantNumeric: "tabular-nums" } }, "$", spend.toFixed(2), /* @__PURE__ */ import_react3.default.createElement(import_Typography3.default, { component: "span", variant: "caption", sx: { ml: 0.5, opacity: 0.7 } }, "/ \u221E"));
892
+ }
278
893
  const pct = Math.min(100, spend / maxBudget * 100);
279
- const color = budgetColor(pct);
280
- return /* @__PURE__ */ import_react2.default.createElement(import_Box2.default, { minWidth: 100 }, /* @__PURE__ */ import_react2.default.createElement(import_Box2.default, { display: "flex", justifyContent: "space-between" }, /* @__PURE__ */ import_react2.default.createElement(import_Typography2.default, { variant: "caption" }, "$", spend.toFixed(2)), /* @__PURE__ */ import_react2.default.createElement(import_Typography2.default, { variant: "caption", color: "text.secondary" }, "$", maxBudget)), /* @__PURE__ */ import_react2.default.createElement(import_LinearProgress2.default, { variant: "determinate", value: pct, color, sx: { height: 5, borderRadius: 1, mt: 0.25 } }));
894
+ return /* @__PURE__ */ import_react3.default.createElement(import_Box3.default, { minWidth: 104 }, /* @__PURE__ */ import_react3.default.createElement(import_Typography3.default, { variant: "body2", sx: { fontVariantNumeric: "tabular-nums", mb: 0.5 } }, "$", spend.toFixed(2), /* @__PURE__ */ import_react3.default.createElement(import_Typography3.default, { component: "span", variant: "caption", color: "text.secondary", sx: { ml: 0.5 } }, "/ $", maxBudget)), /* @__PURE__ */ import_react3.default.createElement(Meter, { value: pct, tone: budgetTone(pct), height: 4 }));
281
895
  }
282
896
  function fmtCost(perToken) {
283
897
  if (!perToken) return null;
@@ -299,12 +913,11 @@ function formatContextWindow(maxInput, maxOutput) {
299
913
  if (outPart) return `ctx ${outPart} out`;
300
914
  return null;
301
915
  }
302
- var import_react2, import_Paper2, import_Table, import_TableBody, import_TableCell, import_TableContainer, import_TableHead, import_TableRow, import_Button2, import_IconButton, import_Box2, import_Typography2, import_Dialog, import_DialogTitle, import_DialogContent, import_DialogActions, import_TextField, import_Chip2, import_CircularProgress, import_Autocomplete, import_LinearProgress2, import_InputAdornment, import_icons_material2, maskKey, shortKeyId, formatDate, keyToEditForm, KeysTable;
916
+ var import_react3, import_Table, import_TableBody, import_TableCell, import_TableContainer, import_TableHead, import_TableRow, import_Button2, import_IconButton, import_Box3, import_Typography3, import_Dialog, import_DialogTitle, import_DialogContent, import_DialogActions, import_TextField, import_CircularProgress, import_Autocomplete, import_Skeleton2, import_InputAdornment, import_styles3, import_icons_material2, maskKey, shortKeyId, formatDate, keyToEditForm, KeysTable;
303
917
  var init_KeysTable = __esm({
304
918
  "src/components/KeysTable.tsx"() {
305
919
  "use strict";
306
- import_react2 = __toESM(require("react"));
307
- import_Paper2 = __toESM(require("@mui/material/Paper"));
920
+ import_react3 = __toESM(require("react"));
308
921
  import_Table = __toESM(require("@mui/material/Table"));
309
922
  import_TableBody = __toESM(require("@mui/material/TableBody"));
310
923
  import_TableCell = __toESM(require("@mui/material/TableCell"));
@@ -313,20 +926,21 @@ var init_KeysTable = __esm({
313
926
  import_TableRow = __toESM(require("@mui/material/TableRow"));
314
927
  import_Button2 = __toESM(require("@mui/material/Button"));
315
928
  import_IconButton = __toESM(require("@mui/material/IconButton"));
316
- import_Box2 = __toESM(require("@mui/material/Box"));
317
- import_Typography2 = __toESM(require("@mui/material/Typography"));
929
+ import_Box3 = __toESM(require("@mui/material/Box"));
930
+ import_Typography3 = __toESM(require("@mui/material/Typography"));
318
931
  import_Dialog = __toESM(require("@mui/material/Dialog"));
319
932
  import_DialogTitle = __toESM(require("@mui/material/DialogTitle"));
320
933
  import_DialogContent = __toESM(require("@mui/material/DialogContent"));
321
934
  import_DialogActions = __toESM(require("@mui/material/DialogActions"));
322
935
  import_TextField = __toESM(require("@mui/material/TextField"));
323
- import_Chip2 = __toESM(require("@mui/material/Chip"));
324
936
  import_CircularProgress = __toESM(require("@mui/material/CircularProgress"));
325
937
  import_Autocomplete = __toESM(require("@mui/material/Autocomplete"));
326
- import_LinearProgress2 = __toESM(require("@mui/material/LinearProgress"));
938
+ import_Skeleton2 = __toESM(require("@mui/material/Skeleton"));
327
939
  import_InputAdornment = __toESM(require("@mui/material/InputAdornment"));
940
+ import_styles3 = require("@mui/material/styles");
328
941
  import_icons_material2 = require("@mui/icons-material");
329
942
  init_api();
943
+ init_ui();
330
944
  maskKey = (key) => {
331
945
  if (key.length <= 8) return "***";
332
946
  return `${key.slice(0, 4)}...${key.slice(-4)}`;
@@ -362,16 +976,16 @@ var init_KeysTable = __esm({
362
976
  onDeleteKey,
363
977
  onPruneExpiredKeys
364
978
  }) => {
365
- const [editingKey, setEditingKey] = (0, import_react2.useState)(null);
366
- const [editForm, setEditForm] = (0, import_react2.useState)({});
367
- const [editSubmitting, setEditSubmitting] = (0, import_react2.useState)(false);
368
- const [blockingKeyId, setBlockingKeyId] = (0, import_react2.useState)(null);
369
- const [deleteConfirmId, setDeleteConfirmId] = (0, import_react2.useState)(null);
370
- const [deleteSubmitting, setDeleteSubmitting] = (0, import_react2.useState)(false);
371
- const [resetSpendConfirm, setResetSpendConfirm] = (0, import_react2.useState)(false);
372
- const [resetSpendSubmitting, setResetSpendSubmitting] = (0, import_react2.useState)(false);
373
- const [filterText, setFilterText] = (0, import_react2.useState)("");
374
- const filteredKeys = (0, import_react2.useMemo)(() => {
979
+ const [editingKey, setEditingKey] = (0, import_react3.useState)(null);
980
+ const [editForm, setEditForm] = (0, import_react3.useState)({});
981
+ const [editSubmitting, setEditSubmitting] = (0, import_react3.useState)(false);
982
+ const [blockingKeyId, setBlockingKeyId] = (0, import_react3.useState)(null);
983
+ const [deleteConfirmId, setDeleteConfirmId] = (0, import_react3.useState)(null);
984
+ const [deleteSubmitting, setDeleteSubmitting] = (0, import_react3.useState)(false);
985
+ const [resetSpendConfirm, setResetSpendConfirm] = (0, import_react3.useState)(false);
986
+ const [resetSpendSubmitting, setResetSpendSubmitting] = (0, import_react3.useState)(false);
987
+ const [filterText, setFilterText] = (0, import_react3.useState)("");
988
+ const filteredKeys = (0, import_react3.useMemo)(() => {
375
989
  if (!filterText.trim()) return keys;
376
990
  const q = filterText.toLowerCase();
377
991
  return keys.filter(
@@ -437,9 +1051,9 @@ var init_KeysTable = __esm({
437
1051
  const copyToClipboard = (text) => {
438
1052
  navigator.clipboard.writeText(text);
439
1053
  };
440
- const [pruneConfirmCount, setPruneConfirmCount] = (0, import_react2.useState)(null);
441
- const [pruneSubmitting, setPruneSubmitting] = (0, import_react2.useState)(false);
442
- const expiredKeys = (0, import_react2.useMemo)(() => {
1054
+ const [pruneConfirmCount, setPruneConfirmCount] = (0, import_react3.useState)(null);
1055
+ const [pruneSubmitting, setPruneSubmitting] = (0, import_react3.useState)(false);
1056
+ const expiredKeys = (0, import_react3.useMemo)(() => {
443
1057
  return keys.filter((k) => expiryStatus(k.expires_at) === "expired");
444
1058
  }, [keys]);
445
1059
  const handlePruneExpired = async () => {
@@ -458,97 +1072,179 @@ var init_KeysTable = __esm({
458
1072
  const inCost = fmtCost(m.input_cost_per_token);
459
1073
  const outCost = fmtCost(m.output_cost_per_token);
460
1074
  const ctx = formatContextWindow(m.max_input_tokens, m.max_output_tokens);
461
- return /* @__PURE__ */ import_react2.default.createElement(import_Box2.default, null, /* @__PURE__ */ import_react2.default.createElement("span", null, m.model_name), m.supports_function_calling && " \u{1F527}", m.supports_vision && " \u{1F441}\uFE0F", ctx && /* @__PURE__ */ import_react2.default.createElement(import_Typography2.default, { variant: "caption", color: "text.secondary", sx: { ml: 1 } }, ctx), (inCost || outCost) && /* @__PURE__ */ import_react2.default.createElement(import_Typography2.default, { variant: "caption", color: "text.secondary", sx: { ml: 1 } }, inCost, " in \xB7 ", outCost, " out"));
1075
+ return /* @__PURE__ */ import_react3.default.createElement(import_Box3.default, null, /* @__PURE__ */ import_react3.default.createElement("span", null, m.model_name), m.supports_function_calling && " \u{1F527}", m.supports_vision && " \u{1F441}\uFE0F", ctx && /* @__PURE__ */ import_react3.default.createElement(import_Typography3.default, { variant: "caption", color: "text.secondary", sx: { ml: 1 } }, ctx), (inCost || outCost) && /* @__PURE__ */ import_react3.default.createElement(import_Typography3.default, { variant: "caption", color: "text.secondary", sx: { ml: 1 } }, inCost, " in \xB7 ", outCost, " out"));
462
1076
  };
463
1077
  const renderTableBody = () => {
464
1078
  if (loading) {
465
- return /* @__PURE__ */ import_react2.default.createElement(import_TableRow.default, null, /* @__PURE__ */ import_react2.default.createElement(import_TableCell.default, { colSpan: 8, align: "center" }, /* @__PURE__ */ import_react2.default.createElement(import_CircularProgress.default, { size: 24 })));
1079
+ return /* @__PURE__ */ import_react3.default.createElement(import_TableRow.default, null, /* @__PURE__ */ import_react3.default.createElement(import_TableCell.default, { colSpan: 8, sx: { py: 2 } }, /* @__PURE__ */ import_react3.default.createElement(import_Skeleton2.default, { variant: "rounded", height: 140 })));
466
1080
  }
467
1081
  if (filteredKeys.length === 0) {
468
- return /* @__PURE__ */ import_react2.default.createElement(import_TableRow.default, null, /* @__PURE__ */ import_react2.default.createElement(import_TableCell.default, { colSpan: 8, align: "center", sx: { py: 4 } }, filterText ? /* @__PURE__ */ import_react2.default.createElement(import_Typography2.default, { color: "text.secondary" }, "No keys match filter") : /* @__PURE__ */ import_react2.default.createElement(import_Box2.default, null, /* @__PURE__ */ import_react2.default.createElement(import_Typography2.default, { color: "text.secondary", gutterBottom: true }, "No keys yet \u2014 generate your first key to start calling models."), /* @__PURE__ */ import_react2.default.createElement(
469
- import_Button2.default,
1082
+ return /* @__PURE__ */ import_react3.default.createElement(import_TableRow.default, null, /* @__PURE__ */ import_react3.default.createElement(import_TableCell.default, { colSpan: 8 }, filterText ? /* @__PURE__ */ import_react3.default.createElement(
1083
+ EmptyState,
470
1084
  {
471
- variant: "contained",
472
- color: "primary",
473
- size: "small",
474
- startIcon: /* @__PURE__ */ import_react2.default.createElement(import_icons_material2.Add, null),
475
- onClick: onGenerateKeyClick
476
- },
477
- "Generate Your First Key"
478
- ))));
1085
+ message: "No keys match that filter",
1086
+ hint: "Try a different alias or model name."
1087
+ }
1088
+ ) : /* @__PURE__ */ import_react3.default.createElement(
1089
+ EmptyState,
1090
+ {
1091
+ message: "No keys yet",
1092
+ hint: "Generate your first key to start calling models.",
1093
+ action: /* @__PURE__ */ import_react3.default.createElement(
1094
+ import_Button2.default,
1095
+ {
1096
+ variant: "contained",
1097
+ color: "primary",
1098
+ size: "small",
1099
+ disableElevation: true,
1100
+ startIcon: /* @__PURE__ */ import_react3.default.createElement(import_icons_material2.Add, null),
1101
+ onClick: onGenerateKeyClick,
1102
+ sx: { mt: 1 }
1103
+ },
1104
+ "Generate Your First Key"
1105
+ )
1106
+ }
1107
+ )));
479
1108
  }
480
1109
  return filteredKeys.map((key) => {
481
1110
  const keyId = key.token ?? key.key;
482
1111
  const isBlocking = blockingKeyId === keyId;
483
- return /* @__PURE__ */ import_react2.default.createElement(import_TableRow.default, { key: keyId, sx: key.blocked ? { bgcolor: "action.disabledBackground" } : void 0 }, /* @__PURE__ */ import_react2.default.createElement(import_TableCell.default, null, /* @__PURE__ */ import_react2.default.createElement(import_Box2.default, { display: "flex", alignItems: "center", gap: 0.5 }, key.key_alias || "-", key.blocked && /* @__PURE__ */ import_react2.default.createElement(import_Chip2.default, { label: "Blocked", color: "error", size: "small" }))), /* @__PURE__ */ import_react2.default.createElement(import_TableCell.default, null, /* @__PURE__ */ import_react2.default.createElement(import_Box2.default, { display: "flex", alignItems: "center", gap: 0.5 }, /* @__PURE__ */ import_react2.default.createElement(
484
- import_Typography2.default,
1112
+ const keyModels = key.models ?? [];
1113
+ return /* @__PURE__ */ import_react3.default.createElement(
1114
+ import_TableRow.default,
485
1115
  {
486
- variant: "body2",
487
- component: "code",
488
- color: "text.secondary",
489
- title: keyId,
490
- sx: {
491
- fontFamily: "monospace",
492
- backgroundColor: "background.default",
493
- px: 1,
494
- py: 0.5,
495
- borderRadius: 1
496
- }
1116
+ key: keyId,
1117
+ sx: key.blocked ? (theme) => ({
1118
+ // A left rule reads as "suspended" without greying the row into
1119
+ // illegibility the way a full background tint does.
1120
+ boxShadow: `inset 3px 0 0 0 ${theme.palette.error.main}`,
1121
+ "& td": { color: theme.palette.text.secondary }
1122
+ }) : void 0
497
1123
  },
498
- shortKeyId(keyId)
499
- ), /* @__PURE__ */ import_react2.default.createElement(import_IconButton.default, { size: "small", onClick: () => copyToClipboard(keyId), title: "Copy Key ID" }, /* @__PURE__ */ import_react2.default.createElement(import_icons_material2.ContentCopy, { fontSize: "small" })))), /* @__PURE__ */ import_react2.default.createElement(import_TableCell.default, null, formatDate(key.created_at)), /* @__PURE__ */ import_react2.default.createElement(import_TableCell.default, null, /* @__PURE__ */ import_react2.default.createElement(ExpiryChip, { expiresAt: key.expires_at })), /* @__PURE__ */ import_react2.default.createElement(import_TableCell.default, null, /* @__PURE__ */ import_react2.default.createElement(BudgetCell, { spend: key.spend ?? 0, maxBudget: key.max_budget })), /* @__PURE__ */ import_react2.default.createElement(import_TableCell.default, null, /* @__PURE__ */ import_react2.default.createElement(import_Typography2.default, { variant: "body2" }, key.tpm_limit ?? "-", " / ", key.rpm_limit ?? "-")), /* @__PURE__ */ import_react2.default.createElement(import_TableCell.default, null, /* @__PURE__ */ import_react2.default.createElement(import_Box2.default, { display: "flex", gap: 0.5, flexWrap: "wrap" }, key.models?.slice(0, 2).map((model) => /* @__PURE__ */ import_react2.default.createElement(import_Chip2.default, { key: model, label: model, size: "small" })), (key.models?.length || 0) > 2 && /* @__PURE__ */ import_react2.default.createElement(import_Chip2.default, { label: `+${(key.models?.length || 0) - 2}`, size: "small", variant: "outlined" }))), /* @__PURE__ */ import_react2.default.createElement(import_TableCell.default, { align: "right" }, /* @__PURE__ */ import_react2.default.createElement(import_IconButton.default, { onClick: () => handleOpenEdit(key), title: "Edit key" }, /* @__PURE__ */ import_react2.default.createElement(import_icons_material2.Edit, { fontSize: "small" })), /* @__PURE__ */ import_react2.default.createElement(
500
- import_IconButton.default,
1124
+ /* @__PURE__ */ import_react3.default.createElement(import_TableCell.default, null, /* @__PURE__ */ import_react3.default.createElement(import_Box3.default, { display: "flex", alignItems: "center", gap: 1, flexWrap: "wrap" }, /* @__PURE__ */ import_react3.default.createElement(import_Typography3.default, { variant: "body2", sx: { fontWeight: 600 } }, key.key_alias || "\u2014"), key.blocked && /* @__PURE__ */ import_react3.default.createElement(StatusPill, { label: "Blocked", tone: "danger" }))),
1125
+ /* @__PURE__ */ import_react3.default.createElement(import_TableCell.default, null, /* @__PURE__ */ import_react3.default.createElement(import_Box3.default, { display: "flex", alignItems: "center", gap: 0.5 }, /* @__PURE__ */ import_react3.default.createElement(
1126
+ import_Typography3.default,
1127
+ {
1128
+ variant: "body2",
1129
+ component: "code",
1130
+ color: "text.secondary",
1131
+ title: keyId,
1132
+ sx: (theme) => ({
1133
+ fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
1134
+ fontSize: 12,
1135
+ bgcolor: (0, import_styles3.alpha)(theme.palette.text.primary, theme.palette.mode === "dark" ? 0.08 : 0.05),
1136
+ px: 0.75,
1137
+ py: 0.375,
1138
+ borderRadius: "5px"
1139
+ })
1140
+ },
1141
+ shortKeyId(keyId)
1142
+ ), /* @__PURE__ */ import_react3.default.createElement(
1143
+ import_IconButton.default,
1144
+ {
1145
+ size: "small",
1146
+ onClick: () => copyToClipboard(keyId),
1147
+ title: "Copy Key ID",
1148
+ sx: quietIconButtonSx("accent")
1149
+ },
1150
+ /* @__PURE__ */ import_react3.default.createElement(import_icons_material2.ContentCopy, { sx: { fontSize: 15 } })
1151
+ ))),
1152
+ /* @__PURE__ */ import_react3.default.createElement(import_TableCell.default, null, /* @__PURE__ */ import_react3.default.createElement(import_Typography3.default, { variant: "body2", color: "text.secondary" }, formatDate(key.created_at))),
1153
+ /* @__PURE__ */ import_react3.default.createElement(import_TableCell.default, null, /* @__PURE__ */ import_react3.default.createElement(ExpiryCell, { expiresAt: key.expires_at })),
1154
+ /* @__PURE__ */ import_react3.default.createElement(import_TableCell.default, null, /* @__PURE__ */ import_react3.default.createElement(BudgetCell, { spend: key.spend ?? 0, maxBudget: key.max_budget })),
1155
+ /* @__PURE__ */ import_react3.default.createElement(import_TableCell.default, null, /* @__PURE__ */ import_react3.default.createElement(import_Typography3.default, { variant: "body2", color: "text.secondary", sx: { fontVariantNumeric: "tabular-nums" } }, key.tpm_limit ?? "\u221E", " / ", key.rpm_limit ?? "\u221E")),
1156
+ /* @__PURE__ */ import_react3.default.createElement(import_TableCell.default, null, keyModels.length === 0 ? /* @__PURE__ */ import_react3.default.createElement(import_Typography3.default, { variant: "body2", color: "text.secondary" }, "All models") : /* @__PURE__ */ import_react3.default.createElement(import_Box3.default, { display: "flex", gap: 0.5, flexWrap: "wrap" }, keyModels.slice(0, 2).map((model) => /* @__PURE__ */ import_react3.default.createElement(TagChip, { key: model, label: model, title: model })), keyModels.length > 2 && /* @__PURE__ */ import_react3.default.createElement(
1157
+ TagChip,
1158
+ {
1159
+ label: `+${keyModels.length - 2}`,
1160
+ mono: false,
1161
+ title: keyModels.slice(2).join(", ")
1162
+ }
1163
+ ))),
1164
+ /* @__PURE__ */ import_react3.default.createElement(import_TableCell.default, { align: "right" }, /* @__PURE__ */ import_react3.default.createElement(import_Box3.default, { display: "flex", justifyContent: "flex-end", gap: 0.25 }, /* @__PURE__ */ import_react3.default.createElement(
1165
+ import_IconButton.default,
1166
+ {
1167
+ size: "small",
1168
+ onClick: () => handleOpenEdit(key),
1169
+ title: "Edit key",
1170
+ sx: quietIconButtonSx("accent")
1171
+ },
1172
+ /* @__PURE__ */ import_react3.default.createElement(import_icons_material2.Edit, { fontSize: "small" })
1173
+ ), /* @__PURE__ */ import_react3.default.createElement(
1174
+ import_IconButton.default,
1175
+ {
1176
+ size: "small",
1177
+ onClick: () => handleToggleBlock(key),
1178
+ disabled: isBlocking,
1179
+ title: key.blocked ? "Unblock key" : "Block key \u2014 suspends without revoking",
1180
+ sx: quietIconButtonSx("warning")
1181
+ },
1182
+ keyBlockIcon(isBlocking, key.blocked)
1183
+ ), /* @__PURE__ */ import_react3.default.createElement(
1184
+ import_IconButton.default,
1185
+ {
1186
+ size: "small",
1187
+ onClick: () => setDeleteConfirmId(keyId),
1188
+ title: "Revoke key",
1189
+ sx: quietIconButtonSx("danger")
1190
+ },
1191
+ /* @__PURE__ */ import_react3.default.createElement(import_icons_material2.Delete, { fontSize: "small" })
1192
+ )))
1193
+ );
1194
+ });
1195
+ };
1196
+ return /* @__PURE__ */ import_react3.default.createElement(import_react3.default.Fragment, null, /* @__PURE__ */ import_react3.default.createElement(
1197
+ SectionCard,
1198
+ {
1199
+ title: "Virtual Keys",
1200
+ subtitle: loading ? "Loading\u2026" : `${filteredKeys.length}${filterText ? ` of ${keys.length}` : ""} key${filteredKeys.length === 1 ? "" : "s"}`,
1201
+ flush: true,
1202
+ actions: /* @__PURE__ */ import_react3.default.createElement(import_react3.default.Fragment, null, /* @__PURE__ */ import_react3.default.createElement(
1203
+ import_TextField.default,
501
1204
  {
502
- onClick: () => handleToggleBlock(key),
503
- disabled: isBlocking,
504
- color: key.blocked ? "warning" : "default",
505
- title: key.blocked ? "Unblock key" : "Block key \u2014 suspends without revoking"
1205
+ size: "small",
1206
+ placeholder: "Filter by alias or model\u2026",
1207
+ value: filterText,
1208
+ onChange: (e) => setFilterText(e.target.value),
1209
+ sx: { minWidth: 240 },
1210
+ InputProps: {
1211
+ startAdornment: /* @__PURE__ */ import_react3.default.createElement(import_InputAdornment.default, { position: "start" }, /* @__PURE__ */ import_react3.default.createElement(import_icons_material2.Search, { fontSize: "small", color: "disabled" }))
1212
+ }
1213
+ }
1214
+ ), expiredKeys.length > 0 && /* @__PURE__ */ import_react3.default.createElement(
1215
+ import_Button2.default,
1216
+ {
1217
+ variant: "outlined",
1218
+ color: "inherit",
1219
+ startIcon: /* @__PURE__ */ import_react3.default.createElement(import_icons_material2.Autorenew, null),
1220
+ onClick: () => setPruneConfirmCount(expiredKeys.length),
1221
+ sx: (theme) => ({
1222
+ color: theme.palette.text.secondary,
1223
+ borderColor: theme.palette.divider,
1224
+ "&:hover": {
1225
+ color: theme.palette.error.main,
1226
+ borderColor: (0, import_styles3.alpha)(theme.palette.error.main, 0.5),
1227
+ bgcolor: (0, import_styles3.alpha)(theme.palette.error.main, 0.06)
1228
+ }
1229
+ })
506
1230
  },
507
- keyBlockIcon(isBlocking, key.blocked)
508
- ), /* @__PURE__ */ import_react2.default.createElement(
509
- import_IconButton.default,
1231
+ "Prune expired (",
1232
+ expiredKeys.length,
1233
+ ")"
1234
+ ), /* @__PURE__ */ import_react3.default.createElement(
1235
+ import_Button2.default,
510
1236
  {
511
- color: "error",
512
- onClick: () => setDeleteConfirmId(keyId),
513
- title: "Revoke key"
1237
+ variant: "contained",
1238
+ color: "primary",
1239
+ disableElevation: true,
1240
+ startIcon: /* @__PURE__ */ import_react3.default.createElement(import_icons_material2.Add, null),
1241
+ onClick: onGenerateKeyClick
514
1242
  },
515
- /* @__PURE__ */ import_react2.default.createElement(import_icons_material2.Delete, null)
516
- )));
517
- });
518
- };
519
- return /* @__PURE__ */ import_react2.default.createElement(import_react2.default.Fragment, null, /* @__PURE__ */ import_react2.default.createElement(import_Paper2.default, { sx: { mb: 2 } }, /* @__PURE__ */ import_react2.default.createElement(import_Box2.default, { display: "flex", justifyContent: "space-between", alignItems: "center", p: 2, gap: 2 }, /* @__PURE__ */ import_react2.default.createElement(import_Typography2.default, { variant: "h6" }, "Virtual Keys"), /* @__PURE__ */ import_react2.default.createElement(import_Box2.default, { display: "flex", gap: 1, alignItems: "center", flex: 1, justifyContent: "flex-end" }, /* @__PURE__ */ import_react2.default.createElement(
520
- import_TextField.default,
521
- {
522
- size: "small",
523
- placeholder: "Filter by alias or model\u2026",
524
- value: filterText,
525
- onChange: (e) => setFilterText(e.target.value),
526
- sx: { minWidth: 240 },
527
- InputProps: {
528
- startAdornment: /* @__PURE__ */ import_react2.default.createElement(import_InputAdornment.default, { position: "start" }, /* @__PURE__ */ import_react2.default.createElement(import_icons_material2.Search, { fontSize: "small" }))
529
- }
530
- }
531
- ), expiredKeys.length > 0 && /* @__PURE__ */ import_react2.default.createElement(
532
- import_Button2.default,
533
- {
534
- variant: "outlined",
535
- color: "error",
536
- startIcon: /* @__PURE__ */ import_react2.default.createElement(import_icons_material2.Autorenew, null),
537
- onClick: () => setPruneConfirmCount(expiredKeys.length)
1243
+ "Generate New Key"
1244
+ ))
538
1245
  },
539
- "Prune expired (",
540
- expiredKeys.length,
541
- ")"
542
- ), /* @__PURE__ */ import_react2.default.createElement(
543
- import_Button2.default,
544
- {
545
- variant: "contained",
546
- color: "primary",
547
- startIcon: /* @__PURE__ */ import_react2.default.createElement(import_icons_material2.Add, null),
548
- onClick: onGenerateKeyClick
549
- },
550
- "Generate New Key"
551
- ))), /* @__PURE__ */ import_react2.default.createElement(import_TableContainer.default, null, /* @__PURE__ */ import_react2.default.createElement(import_Table.default, null, /* @__PURE__ */ import_react2.default.createElement(import_TableHead.default, null, /* @__PURE__ */ import_react2.default.createElement(import_TableRow.default, null, /* @__PURE__ */ import_react2.default.createElement(import_TableCell.default, null, "Alias"), /* @__PURE__ */ import_react2.default.createElement(import_TableCell.default, null, "Key ID"), /* @__PURE__ */ import_react2.default.createElement(import_TableCell.default, null, "Created"), /* @__PURE__ */ import_react2.default.createElement(import_TableCell.default, null, "Expires"), /* @__PURE__ */ import_react2.default.createElement(import_TableCell.default, null, "Budget"), /* @__PURE__ */ import_react2.default.createElement(import_TableCell.default, null, "TPM / RPM"), /* @__PURE__ */ import_react2.default.createElement(import_TableCell.default, null, "Models"), /* @__PURE__ */ import_react2.default.createElement(import_TableCell.default, { align: "right" }, "Actions"))), /* @__PURE__ */ import_react2.default.createElement(import_TableBody.default, null, renderTableBody())))), /* @__PURE__ */ import_react2.default.createElement(import_Dialog.default, { open: !!editingKey, onClose: handleCloseEdit, maxWidth: "sm", fullWidth: true }, /* @__PURE__ */ import_react2.default.createElement(import_DialogTitle.default, null, "Edit Key"), /* @__PURE__ */ import_react2.default.createElement(import_DialogContent.default, null, editingKey && /* @__PURE__ */ import_react2.default.createElement(import_Box2.default, { display: "flex", flexDirection: "column", gap: 2, mt: 1 }, /* @__PURE__ */ import_react2.default.createElement(import_Typography2.default, { variant: "body2", color: "text.secondary" }, /* @__PURE__ */ import_react2.default.createElement("code", { style: { fontFamily: "monospace", color: "inherit" } }, maskKey(editingKey.key))), /* @__PURE__ */ import_react2.default.createElement(
1246
+ /* @__PURE__ */ import_react3.default.createElement(import_TableContainer.default, null, /* @__PURE__ */ import_react3.default.createElement(import_Table.default, { size: "small", sx: dataTableSx }, /* @__PURE__ */ import_react3.default.createElement(import_TableHead.default, null, /* @__PURE__ */ import_react3.default.createElement(import_TableRow.default, null, /* @__PURE__ */ import_react3.default.createElement(import_TableCell.default, null, "Alias"), /* @__PURE__ */ import_react3.default.createElement(import_TableCell.default, null, "Key ID"), /* @__PURE__ */ import_react3.default.createElement(import_TableCell.default, null, "Created"), /* @__PURE__ */ import_react3.default.createElement(import_TableCell.default, null, "Expires"), /* @__PURE__ */ import_react3.default.createElement(import_TableCell.default, null, "Budget"), /* @__PURE__ */ import_react3.default.createElement(import_TableCell.default, null, "TPM / RPM"), /* @__PURE__ */ import_react3.default.createElement(import_TableCell.default, null, "Models"), /* @__PURE__ */ import_react3.default.createElement(import_TableCell.default, { align: "right" }, "Actions"))), /* @__PURE__ */ import_react3.default.createElement(import_TableBody.default, null, renderTableBody())))
1247
+ ), /* @__PURE__ */ import_react3.default.createElement(import_Dialog.default, { open: !!editingKey, onClose: handleCloseEdit, maxWidth: "sm", fullWidth: true }, /* @__PURE__ */ import_react3.default.createElement(import_DialogTitle.default, null, "Edit Key"), /* @__PURE__ */ import_react3.default.createElement(import_DialogContent.default, null, editingKey && /* @__PURE__ */ import_react3.default.createElement(import_Box3.default, { display: "flex", flexDirection: "column", gap: 2, mt: 1 }, /* @__PURE__ */ import_react3.default.createElement(import_Typography3.default, { variant: "body2", color: "text.secondary" }, /* @__PURE__ */ import_react3.default.createElement("code", { style: { fontFamily: "monospace", color: "inherit" } }, maskKey(editingKey.key))), /* @__PURE__ */ import_react3.default.createElement(
552
1248
  import_TextField.default,
553
1249
  {
554
1250
  label: "Alias",
@@ -556,7 +1252,7 @@ var init_KeysTable = __esm({
556
1252
  onChange: (e) => setEditForm({ ...editForm, key_alias: e.target.value }),
557
1253
  fullWidth: true
558
1254
  }
559
- ), models.length > 0 && /* @__PURE__ */ import_react2.default.createElement(
1255
+ ), models.length > 0 && /* @__PURE__ */ import_react3.default.createElement(
560
1256
  import_Autocomplete.default,
561
1257
  {
562
1258
  multiple: true,
@@ -565,10 +1261,10 @@ var init_KeysTable = __esm({
565
1261
  getOptionLabel: (m) => m.model_name,
566
1262
  value: editSelectedModels,
567
1263
  onChange: (_e, selected) => setEditForm({ ...editForm, models: selected.map((m) => m.model_name) }),
568
- renderOption: (props, m) => /* @__PURE__ */ import_react2.default.createElement("li", { ...props }, modelOption(m)),
569
- renderInput: (params) => /* @__PURE__ */ import_react2.default.createElement(import_TextField.default, { ...params, label: "Models", fullWidth: true })
1264
+ renderOption: (props, m) => /* @__PURE__ */ import_react3.default.createElement("li", { ...props }, modelOption(m)),
1265
+ renderInput: (params) => /* @__PURE__ */ import_react3.default.createElement(import_TextField.default, { ...params, label: "Models", fullWidth: true })
570
1266
  }
571
- ), /* @__PURE__ */ import_react2.default.createElement(
1267
+ ), /* @__PURE__ */ import_react3.default.createElement(
572
1268
  import_TextField.default,
573
1269
  {
574
1270
  label: "Max Budget (USD)",
@@ -577,7 +1273,7 @@ var init_KeysTable = __esm({
577
1273
  onChange: (e) => setEditForm({ ...editForm, max_budget: e.target.value ? Number(e.target.value) : void 0 }),
578
1274
  fullWidth: true
579
1275
  }
580
- ), /* @__PURE__ */ import_react2.default.createElement(
1276
+ ), /* @__PURE__ */ import_react3.default.createElement(
581
1277
  import_TextField.default,
582
1278
  {
583
1279
  label: "TPM Limit",
@@ -587,7 +1283,7 @@ var init_KeysTable = __esm({
587
1283
  helperText: "Max tokens per minute this key can consume across all models. Leave blank for no limit.",
588
1284
  fullWidth: true
589
1285
  }
590
- ), /* @__PURE__ */ import_react2.default.createElement(
1286
+ ), /* @__PURE__ */ import_react3.default.createElement(
591
1287
  import_TextField.default,
592
1288
  {
593
1289
  label: "RPM Limit",
@@ -597,7 +1293,7 @@ var init_KeysTable = __esm({
597
1293
  helperText: "Max requests per minute this key can make across all models. Leave blank for no limit.",
598
1294
  fullWidth: true
599
1295
  }
600
- ), /* @__PURE__ */ import_react2.default.createElement(import_Box2.default, { mt: 1, pt: 2, borderTop: "1px solid", sx: { borderColor: "divider" } }, /* @__PURE__ */ import_react2.default.createElement(import_Typography2.default, { variant: "caption", color: "text.secondary", display: "block", mb: 1 }, "Danger Zone"), !resetSpendConfirm ? /* @__PURE__ */ import_react2.default.createElement(
1296
+ ), /* @__PURE__ */ import_react3.default.createElement(import_Box3.default, { mt: 1, pt: 2, borderTop: "1px solid", sx: { borderColor: "divider" } }, /* @__PURE__ */ import_react3.default.createElement(import_Typography3.default, { variant: "caption", color: "text.secondary", display: "block", mb: 1 }, "Danger Zone"), !resetSpendConfirm ? /* @__PURE__ */ import_react3.default.createElement(
601
1297
  import_Button2.default,
602
1298
  {
603
1299
  size: "small",
@@ -606,7 +1302,7 @@ var init_KeysTable = __esm({
606
1302
  onClick: () => setResetSpendConfirm(true)
607
1303
  },
608
1304
  "Reset Spend to $0"
609
- ) : /* @__PURE__ */ import_react2.default.createElement(import_Box2.default, { display: "flex", alignItems: "center", gap: 1, flexWrap: "wrap" }, /* @__PURE__ */ import_react2.default.createElement(import_Typography2.default, { variant: "body2", color: "warning.main" }, "Zero out spend counter?"), /* @__PURE__ */ import_react2.default.createElement(
1305
+ ) : /* @__PURE__ */ import_react3.default.createElement(import_Box3.default, { display: "flex", alignItems: "center", gap: 1, flexWrap: "wrap" }, /* @__PURE__ */ import_react3.default.createElement(import_Typography3.default, { variant: "body2", color: "warning.main" }, "Zero out spend counter?"), /* @__PURE__ */ import_react3.default.createElement(
610
1306
  import_Button2.default,
611
1307
  {
612
1308
  size: "small",
@@ -615,8 +1311,8 @@ var init_KeysTable = __esm({
615
1311
  disabled: resetSpendSubmitting,
616
1312
  onClick: handleResetSpend
617
1313
  },
618
- resetSpendSubmitting ? /* @__PURE__ */ import_react2.default.createElement(import_CircularProgress.default, { size: 16 }) : "Confirm"
619
- ), /* @__PURE__ */ import_react2.default.createElement(import_Button2.default, { size: "small", onClick: () => setResetSpendConfirm(false) }, "Cancel"))))), /* @__PURE__ */ import_react2.default.createElement(import_DialogActions.default, null, /* @__PURE__ */ import_react2.default.createElement(import_Button2.default, { onClick: handleCloseEdit }, "Cancel"), /* @__PURE__ */ import_react2.default.createElement(import_Button2.default, { onClick: handleUpdate, variant: "contained", color: "primary", disabled: editSubmitting }, editSubmitting ? /* @__PURE__ */ import_react2.default.createElement(import_CircularProgress.default, { size: 24 }) : "Save"))), /* @__PURE__ */ import_react2.default.createElement(import_Dialog.default, { open: !!deleteConfirmId, onClose: () => setDeleteConfirmId(null), maxWidth: "xs", fullWidth: true }, /* @__PURE__ */ import_react2.default.createElement(import_DialogTitle.default, null, "Revoke Key?"), /* @__PURE__ */ import_react2.default.createElement(import_DialogContent.default, null, /* @__PURE__ */ import_react2.default.createElement(import_Typography2.default, null, "This will permanently revoke the key. Any integrations using it will stop working immediately.")), /* @__PURE__ */ import_react2.default.createElement(import_DialogActions.default, null, /* @__PURE__ */ import_react2.default.createElement(import_Button2.default, { onClick: () => setDeleteConfirmId(null), disabled: deleteSubmitting }, "Cancel"), /* @__PURE__ */ import_react2.default.createElement(
1314
+ resetSpendSubmitting ? /* @__PURE__ */ import_react3.default.createElement(import_CircularProgress.default, { size: 16 }) : "Confirm"
1315
+ ), /* @__PURE__ */ import_react3.default.createElement(import_Button2.default, { size: "small", onClick: () => setResetSpendConfirm(false) }, "Cancel"))))), /* @__PURE__ */ import_react3.default.createElement(import_DialogActions.default, null, /* @__PURE__ */ import_react3.default.createElement(import_Button2.default, { onClick: handleCloseEdit }, "Cancel"), /* @__PURE__ */ import_react3.default.createElement(import_Button2.default, { onClick: handleUpdate, variant: "contained", color: "primary", disabled: editSubmitting }, editSubmitting ? /* @__PURE__ */ import_react3.default.createElement(import_CircularProgress.default, { size: 24 }) : "Save"))), /* @__PURE__ */ import_react3.default.createElement(import_Dialog.default, { open: !!deleteConfirmId, onClose: () => setDeleteConfirmId(null), maxWidth: "xs", fullWidth: true }, /* @__PURE__ */ import_react3.default.createElement(import_DialogTitle.default, null, "Revoke Key?"), /* @__PURE__ */ import_react3.default.createElement(import_DialogContent.default, null, /* @__PURE__ */ import_react3.default.createElement(import_Typography3.default, null, "This will permanently revoke the key. Any integrations using it will stop working immediately.")), /* @__PURE__ */ import_react3.default.createElement(import_DialogActions.default, null, /* @__PURE__ */ import_react3.default.createElement(import_Button2.default, { onClick: () => setDeleteConfirmId(null), disabled: deleteSubmitting }, "Cancel"), /* @__PURE__ */ import_react3.default.createElement(
620
1316
  import_Button2.default,
621
1317
  {
622
1318
  onClick: handleConfirmDelete,
@@ -624,8 +1320,8 @@ var init_KeysTable = __esm({
624
1320
  color: "error",
625
1321
  disabled: deleteSubmitting
626
1322
  },
627
- deleteSubmitting ? /* @__PURE__ */ import_react2.default.createElement(import_CircularProgress.default, { size: 20 }) : "Revoke"
628
- ))), /* @__PURE__ */ import_react2.default.createElement(import_Dialog.default, { open: pruneConfirmCount !== null, onClose: () => setPruneConfirmCount(null), maxWidth: "xs", fullWidth: true }, /* @__PURE__ */ import_react2.default.createElement(import_DialogTitle.default, null, "Prune Expired Keys?"), /* @__PURE__ */ import_react2.default.createElement(import_DialogContent.default, null, /* @__PURE__ */ import_react2.default.createElement(import_Typography2.default, null, "This will permanently delete ", pruneConfirmCount, " expired key", pruneConfirmCount !== 1 ? "s" : "", " from LiteLLM. Any integrations using them will stop working immediately.")), /* @__PURE__ */ import_react2.default.createElement(import_DialogActions.default, null, /* @__PURE__ */ import_react2.default.createElement(import_Button2.default, { onClick: () => setPruneConfirmCount(null), disabled: pruneSubmitting }, "Cancel"), /* @__PURE__ */ import_react2.default.createElement(
1323
+ deleteSubmitting ? /* @__PURE__ */ import_react3.default.createElement(import_CircularProgress.default, { size: 20 }) : "Revoke"
1324
+ ))), /* @__PURE__ */ import_react3.default.createElement(import_Dialog.default, { open: pruneConfirmCount !== null, onClose: () => setPruneConfirmCount(null), maxWidth: "xs", fullWidth: true }, /* @__PURE__ */ import_react3.default.createElement(import_DialogTitle.default, null, "Prune Expired Keys?"), /* @__PURE__ */ import_react3.default.createElement(import_DialogContent.default, null, /* @__PURE__ */ import_react3.default.createElement(import_Typography3.default, null, "This will permanently delete ", pruneConfirmCount, " expired key", pruneConfirmCount !== 1 ? "s" : "", " from LiteLLM. Any integrations using them will stop working immediately.")), /* @__PURE__ */ import_react3.default.createElement(import_DialogActions.default, null, /* @__PURE__ */ import_react3.default.createElement(import_Button2.default, { onClick: () => setPruneConfirmCount(null), disabled: pruneSubmitting }, "Cancel"), /* @__PURE__ */ import_react3.default.createElement(
629
1325
  import_Button2.default,
630
1326
  {
631
1327
  onClick: handlePruneExpired,
@@ -633,7 +1329,7 @@ var init_KeysTable = __esm({
633
1329
  color: "error",
634
1330
  disabled: pruneSubmitting
635
1331
  },
636
- pruneSubmitting ? /* @__PURE__ */ import_react2.default.createElement(import_CircularProgress.default, { size: 20 }) : "Prune"
1332
+ pruneSubmitting ? /* @__PURE__ */ import_react3.default.createElement(import_CircularProgress.default, { size: 20 }) : "Prune"
637
1333
  ))));
638
1334
  };
639
1335
  }
@@ -657,10 +1353,18 @@ var init_format = __esm({
657
1353
  function trimSlash(url) {
658
1354
  return url.replace(/\/+$/, "");
659
1355
  }
1356
+ function isModelAllowedByTeam(model, teamModels) {
1357
+ if (!teamModels || teamModels.length === 0) return true;
1358
+ if (teamModels.includes(ALL_PROXY_MODELS)) return true;
1359
+ if (teamModels.includes(model.model_name)) return true;
1360
+ return !!model.access_groups?.some((group) => teamModels.includes(group));
1361
+ }
660
1362
  function buildSnippets(baseUrl, key, model) {
661
1363
  const base = trimSlash(baseUrl);
1364
+ const apiBase = `${base}/v1`;
662
1365
  return {
663
- curl: `curl ${base}/v1/chat/completions \\
1366
+ publicEndpoint: apiBase,
1367
+ curl: `curl ${apiBase}/chat/completions \\
664
1368
  -H "Authorization: Bearer ${key}" \\
665
1369
  -H "Content-Type: application/json" \\
666
1370
  -d '{
@@ -671,14 +1375,40 @@ function buildSnippets(baseUrl, key, model) {
671
1375
 
672
1376
  client = OpenAI(
673
1377
  api_key="${key}",
674
- base_url="${base}/v1",
1378
+ base_url="${apiBase}",
675
1379
  )
676
1380
 
677
1381
  response = client.chat.completions.create(
678
1382
  model="${model}",
679
1383
  messages=[{ "role": "user", "content": "Hello!" }],
680
1384
  )
681
- print(response.choices[0].message.content)`
1385
+ print(response.choices[0].message.content)`,
1386
+ opencode: `{
1387
+ "$schema": "https://opencode.ai/config.json",
1388
+ "provider": {
1389
+ "litellm": {
1390
+ "npm": "@ai-sdk/openai-compatible",
1391
+ "name": "LiteLLM",
1392
+ "options": {
1393
+ "baseURL": "${apiBase}",
1394
+ "apiKey": "${key}"
1395
+ },
1396
+ "models": {
1397
+ "${model}": {}
1398
+ }
1399
+ }
1400
+ }
1401
+ }`,
1402
+ pi: `{
1403
+ "litellm": {
1404
+ "baseUrl": "${apiBase}",
1405
+ "apiKey": "${key}",
1406
+ "api": "openai-completions",
1407
+ "models": [
1408
+ { "id": "${model}", "name": "${model}" }
1409
+ ]
1410
+ }
1411
+ }`
682
1412
  };
683
1413
  }
684
1414
  function aliasHelperText(aliasError, aliasDuplicate) {
@@ -716,13 +1446,13 @@ function formatContextWindow2(maxInput, maxOutput) {
716
1446
  if (outPart) return `ctx ${outPart} out`;
717
1447
  return null;
718
1448
  }
719
- var import_react3, import_Box3, import_Typography3, import_Dialog2, import_DialogTitle2, import_DialogContent2, import_DialogActions2, import_TextField2, import_MenuItem, import_Button3, import_IconButton2, import_CircularProgress2, import_Autocomplete2, import_Checkbox, import_FormControlLabel, import_Alert, import_Tabs, import_Tab, import_icons_material3, generateDefaultAlias, emptyForm, SnippetTabs, GenerateKeyDialog;
1449
+ var import_react4, import_Box4, import_Typography4, import_Dialog2, import_DialogTitle2, import_DialogContent2, import_DialogActions2, import_TextField2, import_MenuItem, import_Button3, import_IconButton2, import_CircularProgress2, import_Autocomplete2, import_Checkbox, import_FormControlLabel, import_Alert, import_Tabs, import_Tab, import_icons_material3, generateDefaultAlias, emptyForm, ALL_PROXY_MODELS, SNIPPET_FILE_HINTS, SnippetTabs, GenerateKeyDialog;
720
1450
  var init_GenerateKeyDialog = __esm({
721
1451
  "src/components/GenerateKeyDialog.tsx"() {
722
1452
  "use strict";
723
- import_react3 = __toESM(require("react"));
724
- import_Box3 = __toESM(require("@mui/material/Box"));
725
- import_Typography3 = __toESM(require("@mui/material/Typography"));
1453
+ import_react4 = __toESM(require("react"));
1454
+ import_Box4 = __toESM(require("@mui/material/Box"));
1455
+ import_Typography4 = __toESM(require("@mui/material/Typography"));
726
1456
  import_Dialog2 = __toESM(require("@mui/material/Dialog"));
727
1457
  import_DialogTitle2 = __toESM(require("@mui/material/DialogTitle"));
728
1458
  import_DialogContent2 = __toESM(require("@mui/material/DialogContent"));
@@ -755,17 +1485,23 @@ var init_GenerateKeyDialog = __esm({
755
1485
  team_id: void 0,
756
1486
  key_type: "llm_api"
757
1487
  });
1488
+ ALL_PROXY_MODELS = "all-proxy-models";
1489
+ SNIPPET_FILE_HINTS = {
1490
+ opencode: "Add to ~/.config/opencode/opencode.json",
1491
+ pi: "Add to ~/.pi/agent/models.json"
1492
+ };
758
1493
  SnippetTabs = ({ snippets, model, onCopy }) => {
759
- const [tab, setTab] = (0, import_react3.useState)("curl");
760
- const code = tab === "curl" ? snippets.curl : snippets.openai;
761
- return /* @__PURE__ */ import_react3.default.createElement(import_Box3.default, null, /* @__PURE__ */ import_react3.default.createElement(import_Tabs.default, { value: tab, onChange: (_, v) => setTab(v), sx: { mb: 1 } }, /* @__PURE__ */ import_react3.default.createElement(import_Tab.default, { label: "curl", value: "curl" }), /* @__PURE__ */ import_react3.default.createElement(import_Tab.default, { label: "OpenAI SDK", value: "openai" })), /* @__PURE__ */ import_react3.default.createElement(
762
- import_Box3.default,
1494
+ const [tab, setTab] = (0, import_react4.useState)("curl");
1495
+ const code = snippets[tab];
1496
+ const fileHint = SNIPPET_FILE_HINTS[tab];
1497
+ return /* @__PURE__ */ import_react4.default.createElement(import_Box4.default, null, /* @__PURE__ */ import_react4.default.createElement(import_Tabs.default, { value: tab, onChange: (_, v) => setTab(v), sx: { mb: 1 }, variant: "scrollable" }, /* @__PURE__ */ import_react4.default.createElement(import_Tab.default, { label: "curl", value: "curl" }), /* @__PURE__ */ import_react4.default.createElement(import_Tab.default, { label: "OpenAI SDK", value: "openai" }), /* @__PURE__ */ import_react4.default.createElement(import_Tab.default, { label: "opencode", value: "opencode" }), /* @__PURE__ */ import_react4.default.createElement(import_Tab.default, { label: "pi", value: "pi" })), fileHint && /* @__PURE__ */ import_react4.default.createElement(import_Typography4.default, { variant: "caption", color: "text.secondary", display: "block", mb: 0.5 }, fileHint), /* @__PURE__ */ import_react4.default.createElement(
1498
+ import_Box4.default,
763
1499
  {
764
1500
  position: "relative",
765
1501
  p: 1.5,
766
1502
  sx: { backgroundColor: "action.hover", border: "1px solid", borderColor: "divider", borderRadius: 1 }
767
1503
  },
768
- /* @__PURE__ */ import_react3.default.createElement(
1504
+ /* @__PURE__ */ import_react4.default.createElement(
769
1505
  import_IconButton2.default,
770
1506
  {
771
1507
  size: "small",
@@ -773,10 +1509,10 @@ var init_GenerateKeyDialog = __esm({
773
1509
  title: "Copy snippet",
774
1510
  sx: { position: "absolute", top: 4, right: 4 }
775
1511
  },
776
- /* @__PURE__ */ import_react3.default.createElement(import_icons_material3.ContentCopy, { fontSize: "small" })
1512
+ /* @__PURE__ */ import_react4.default.createElement(import_icons_material3.ContentCopy, { fontSize: "small" })
777
1513
  ),
778
- /* @__PURE__ */ import_react3.default.createElement(
779
- import_Typography3.default,
1514
+ /* @__PURE__ */ import_react4.default.createElement(
1515
+ import_Typography4.default,
780
1516
  {
781
1517
  component: "pre",
782
1518
  sx: {
@@ -790,7 +1526,7 @@ var init_GenerateKeyDialog = __esm({
790
1526
  },
791
1527
  code
792
1528
  ),
793
- model && /* @__PURE__ */ import_react3.default.createElement(import_Typography3.default, { variant: "caption", color: "text.secondary", display: "block", mt: 1 }, "Using model \u201C", model, "\u201D \u2014 swap it for any model you have access to.")
1529
+ model && /* @__PURE__ */ import_react4.default.createElement(import_Typography4.default, { variant: "caption", color: "text.secondary", display: "block", mt: 1 }, "Using model \u201C", model, "\u201D \u2014 swap it for any model you have access to.")
794
1530
  ));
795
1531
  };
796
1532
  GenerateKeyDialog = ({
@@ -804,14 +1540,14 @@ var init_GenerateKeyDialog = __esm({
804
1540
  onGenerateKey,
805
1541
  onGetConfig
806
1542
  }) => {
807
- const [formData, setFormData] = (0, import_react3.useState)(emptyForm());
808
- const [unlimitedBudget, setUnlimitedBudget] = (0, import_react3.useState)(false);
809
- const [submitting, setSubmitting] = (0, import_react3.useState)(false);
810
- const [newKeyValue, setNewKeyValue] = (0, import_react3.useState)(null);
811
- const [newKeySnippets, setNewKeySnippets] = (0, import_react3.useState)(null);
812
- const [newKeyModel, setNewKeyModel] = (0, import_react3.useState)("");
813
- const [generateError, setGenerateError] = (0, import_react3.useState)(null);
814
- (0, import_react3.useEffect)(() => {
1543
+ const [formData, setFormData] = (0, import_react4.useState)(emptyForm());
1544
+ const [unlimitedBudget, setUnlimitedBudget] = (0, import_react4.useState)(false);
1545
+ const [submitting, setSubmitting] = (0, import_react4.useState)(false);
1546
+ const [newKeyValue, setNewKeyValue] = (0, import_react4.useState)(null);
1547
+ const [newKeySnippets, setNewKeySnippets] = (0, import_react4.useState)(null);
1548
+ const [newKeyModel, setNewKeyModel] = (0, import_react4.useState)("");
1549
+ const [generateError, setGenerateError] = (0, import_react4.useState)(null);
1550
+ (0, import_react4.useEffect)(() => {
815
1551
  if (open) {
816
1552
  setFormData({ ...emptyForm(), alias: generateDefaultAlias(username) });
817
1553
  setUnlimitedBudget(false);
@@ -824,13 +1560,8 @@ var init_GenerateKeyDialog = __esm({
824
1560
  const allowUnlimitedBudget = keyGenerationSettings?.allowUnlimitedBudget ?? false;
825
1561
  const teamRequired = keyGenerationSettings?.teamRequired ?? true;
826
1562
  const selectedTeam = teams.find((t) => t.team_id === formData.team_id) ?? null;
827
- const availableModels = (0, import_react3.useMemo)(() => {
828
- const teamModels = selectedTeam?.models;
829
- if (teamModels && teamModels.length > 0) {
830
- const allowed = new Set(teamModels);
831
- return models.filter((m) => allowed.has(m.model_name));
832
- }
833
- return models;
1563
+ const availableModels = (0, import_react4.useMemo)(() => {
1564
+ return models.filter((m) => isModelAllowedByTeam(m, selectedTeam?.models));
834
1565
  }, [models, selectedTeam]);
835
1566
  const selectedModels = availableModels.filter((m) => (formData.models || []).includes(m.model_name));
836
1567
  const aliasError = !(formData.alias || "").trim();
@@ -838,7 +1569,7 @@ var init_GenerateKeyDialog = __esm({
838
1569
  const budgetInvalid = !unlimitedBudget && (formData.max_budget === void 0 || formData.max_budget === null || formData.max_budget <= 0);
839
1570
  const canGenerate = !aliasError && !teamError && !budgetInvalid && !submitting;
840
1571
  const aliasDuplicate = !!formData.alias && keys.some((k) => k.key_alias === formData.alias);
841
- const budgetEstimate = (0, import_react3.useMemo)(() => {
1572
+ const budgetEstimate = (0, import_react4.useMemo)(() => {
842
1573
  if (unlimitedBudget || budgetInvalid) return null;
843
1574
  const inputCosts = selectedModels.map((m) => m.input_cost_per_token).filter((c) => typeof c === "number" && c > 0);
844
1575
  if (inputCosts.length === 0) return null;
@@ -854,12 +1585,12 @@ var init_GenerateKeyDialog = __esm({
854
1585
  max_budget: unlimitedBudget ? null : formData.max_budget
855
1586
  };
856
1587
  const response = await onGenerateKey(request);
1588
+ const model = formData.models?.[0] ?? availableModels[0]?.model_name ?? "";
857
1589
  setNewKeyValue(response.key);
858
- setNewKeyModel(formData.models?.[0] ?? "");
1590
+ setNewKeyModel(model);
859
1591
  setNewKeySnippets(null);
860
1592
  try {
861
1593
  const config = await onGetConfig();
862
- const model = formData.models?.[0] ?? "";
863
1594
  setNewKeySnippets(buildSnippets(config.baseUrl, response.key, model));
864
1595
  } catch {
865
1596
  }
@@ -886,22 +1617,24 @@ var init_GenerateKeyDialog = __esm({
886
1617
  const inCost = fmtCost2(m.input_cost_per_token);
887
1618
  const outCost = fmtCost2(m.output_cost_per_token);
888
1619
  const ctx = formatContextWindow2(m.max_input_tokens, m.max_output_tokens);
889
- return /* @__PURE__ */ import_react3.default.createElement(import_Box3.default, null, /* @__PURE__ */ import_react3.default.createElement("span", null, m.model_name), m.supports_function_calling && " \u{1F527}", m.supports_vision && " \u{1F441}\uFE0F", ctx && /* @__PURE__ */ import_react3.default.createElement(import_Typography3.default, { variant: "caption", color: "text.secondary", sx: { ml: 1 } }, ctx), (inCost || outCost) && /* @__PURE__ */ import_react3.default.createElement(import_Typography3.default, { variant: "caption", color: "text.secondary", sx: { ml: 1 } }, inCost, " in \xB7 ", outCost, " out"));
1620
+ return /* @__PURE__ */ import_react4.default.createElement(import_Box4.default, null, /* @__PURE__ */ import_react4.default.createElement("span", null, m.model_name), m.supports_function_calling && " \u{1F527}", m.supports_vision && " \u{1F441}\uFE0F", ctx && /* @__PURE__ */ import_react4.default.createElement(import_Typography4.default, { variant: "caption", color: "text.secondary", sx: { ml: 1 } }, ctx), (inCost || outCost) && /* @__PURE__ */ import_react4.default.createElement(import_Typography4.default, { variant: "caption", color: "text.secondary", sx: { ml: 1 } }, inCost, " in \xB7 ", outCost, " out"));
890
1621
  };
891
1622
  const renderTeamField = () => {
892
1623
  if (teams.length > 0) {
893
- return /* @__PURE__ */ import_react3.default.createElement(
1624
+ return /* @__PURE__ */ import_react4.default.createElement(
894
1625
  import_Autocomplete2.default,
895
1626
  {
896
1627
  options: teams,
897
1628
  getOptionLabel: (t) => t.team_alias || t.team_id,
898
1629
  value: selectedTeam,
899
1630
  onChange: (_e, team) => {
900
- const teamModels = team?.models;
901
- const restrictedModels = teamModels && teamModels.length > 0 ? (formData.models || []).filter((m) => teamModels.includes(m)) : formData.models;
1631
+ const restrictedModels = (formData.models || []).filter((name) => {
1632
+ const model = models.find((m) => m.model_name === name);
1633
+ return model ? isModelAllowedByTeam(model, team?.models) : false;
1634
+ });
902
1635
  setFormData({ ...formData, team_id: team?.team_id, models: restrictedModels });
903
1636
  },
904
- renderInput: (params) => /* @__PURE__ */ import_react3.default.createElement(
1637
+ renderInput: (params) => /* @__PURE__ */ import_react4.default.createElement(
905
1638
  import_TextField2.default,
906
1639
  {
907
1640
  ...params,
@@ -916,12 +1649,12 @@ var init_GenerateKeyDialog = __esm({
916
1649
  );
917
1650
  }
918
1651
  if (teamRequired) {
919
- return /* @__PURE__ */ import_react3.default.createElement(import_Typography3.default, { variant: "body2", color: "error" }, "Team selection is required, but you don't belong to any team yet \u2014 contact your administrator.");
1652
+ return /* @__PURE__ */ import_react4.default.createElement(import_Typography4.default, { variant: "body2", color: "error" }, "Team selection is required, but you don't belong to any team yet \u2014 contact your administrator.");
920
1653
  }
921
1654
  return null;
922
1655
  };
923
- return /* @__PURE__ */ import_react3.default.createElement(import_Dialog2.default, { open, onClose: handleClose, maxWidth: "sm", fullWidth: true }, /* @__PURE__ */ import_react3.default.createElement(import_DialogTitle2.default, null, newKeyValue ? "Key Generated" : "Generate New Key"), /* @__PURE__ */ import_react3.default.createElement(import_DialogContent2.default, null, newKeyValue ? /* @__PURE__ */ import_react3.default.createElement(import_Box3.default, null, /* @__PURE__ */ import_react3.default.createElement(import_Typography3.default, { variant: "body2", color: "text.secondary", gutterBottom: true }, "Copy this key now. You won't be able to see it again."), /* @__PURE__ */ import_react3.default.createElement(
924
- import_Box3.default,
1656
+ return /* @__PURE__ */ import_react4.default.createElement(import_Dialog2.default, { open, onClose: handleClose, maxWidth: "sm", fullWidth: true }, /* @__PURE__ */ import_react4.default.createElement(import_DialogTitle2.default, null, newKeyValue ? "Key Generated" : "Generate New Key"), /* @__PURE__ */ import_react4.default.createElement(import_DialogContent2.default, null, newKeyValue ? /* @__PURE__ */ import_react4.default.createElement(import_Box4.default, null, /* @__PURE__ */ import_react4.default.createElement(import_Typography4.default, { variant: "body2", color: "text.secondary", gutterBottom: true }, "Copy this key now. You won't be able to see it again."), /* @__PURE__ */ import_react4.default.createElement(
1657
+ import_Box4.default,
925
1658
  {
926
1659
  display: "flex",
927
1660
  alignItems: "center",
@@ -935,8 +1668,8 @@ var init_GenerateKeyDialog = __esm({
935
1668
  borderRadius: 1
936
1669
  }
937
1670
  },
938
- /* @__PURE__ */ import_react3.default.createElement(
939
- import_Typography3.default,
1671
+ /* @__PURE__ */ import_react4.default.createElement(
1672
+ import_Typography4.default,
940
1673
  {
941
1674
  component: "code",
942
1675
  color: "text.primary",
@@ -944,8 +1677,32 @@ var init_GenerateKeyDialog = __esm({
944
1677
  },
945
1678
  newKeyValue
946
1679
  ),
947
- /* @__PURE__ */ import_react3.default.createElement(import_IconButton2.default, { onClick: () => copyToClipboard(newKeyValue) }, /* @__PURE__ */ import_react3.default.createElement(import_icons_material3.ContentCopy, null))
948
- ), newKeySnippets && /* @__PURE__ */ import_react3.default.createElement(import_Box3.default, { mt: 3 }, /* @__PURE__ */ import_react3.default.createElement(import_Box3.default, { display: "flex", alignItems: "center", gap: 1, mb: 1 }, /* @__PURE__ */ import_react3.default.createElement(import_icons_material3.Code, { fontSize: "small", color: "action" }), /* @__PURE__ */ import_react3.default.createElement(import_Typography3.default, { variant: "subtitle2" }, "Start calling the proxy \u2014 paste and run")), /* @__PURE__ */ import_react3.default.createElement(SnippetTabs, { snippets: newKeySnippets, model: newKeyModel, onCopy: copyToClipboard }))) : /* @__PURE__ */ import_react3.default.createElement(import_Box3.default, { display: "flex", flexDirection: "column", gap: 2, mt: 1 }, generateError && /* @__PURE__ */ import_react3.default.createElement(import_Alert.default, { severity: "error", onClose: () => setGenerateError(null) }, generateError), /* @__PURE__ */ import_react3.default.createElement(
1680
+ /* @__PURE__ */ import_react4.default.createElement(import_IconButton2.default, { onClick: () => copyToClipboard(newKeyValue) }, /* @__PURE__ */ import_react4.default.createElement(import_icons_material3.ContentCopy, null))
1681
+ ), newKeySnippets && /* @__PURE__ */ import_react4.default.createElement(import_Box4.default, { mt: 2 }, /* @__PURE__ */ import_react4.default.createElement(import_Typography4.default, { variant: "caption", color: "text.secondary", display: "block", gutterBottom: true }, "Public endpoint \u2014 paste into any tool's base URL / API base field"), /* @__PURE__ */ import_react4.default.createElement(
1682
+ import_Box4.default,
1683
+ {
1684
+ display: "flex",
1685
+ alignItems: "center",
1686
+ gap: 1,
1687
+ p: 1.5,
1688
+ sx: {
1689
+ backgroundColor: "action.hover",
1690
+ border: "1px solid",
1691
+ borderColor: "divider",
1692
+ borderRadius: 1
1693
+ }
1694
+ },
1695
+ /* @__PURE__ */ import_react4.default.createElement(
1696
+ import_Typography4.default,
1697
+ {
1698
+ component: "code",
1699
+ color: "text.primary",
1700
+ sx: { fontFamily: "monospace", fontSize: 13, wordBreak: "break-all", flex: 1 }
1701
+ },
1702
+ newKeySnippets.publicEndpoint
1703
+ ),
1704
+ /* @__PURE__ */ import_react4.default.createElement(import_IconButton2.default, { size: "small", onClick: () => copyToClipboard(newKeySnippets.publicEndpoint) }, /* @__PURE__ */ import_react4.default.createElement(import_icons_material3.ContentCopy, { fontSize: "small" }))
1705
+ )), newKeySnippets && /* @__PURE__ */ import_react4.default.createElement(import_Box4.default, { mt: 3 }, /* @__PURE__ */ import_react4.default.createElement(import_Box4.default, { display: "flex", alignItems: "center", gap: 1, mb: 1 }, /* @__PURE__ */ import_react4.default.createElement(import_icons_material3.Code, { fontSize: "small", color: "action" }), /* @__PURE__ */ import_react4.default.createElement(import_Typography4.default, { variant: "subtitle2" }, "Start calling the proxy \u2014 paste and run")), /* @__PURE__ */ import_react4.default.createElement(SnippetTabs, { snippets: newKeySnippets, model: newKeyModel, onCopy: copyToClipboard }))) : /* @__PURE__ */ import_react4.default.createElement(import_Box4.default, { display: "flex", flexDirection: "column", gap: 2, mt: 1 }, generateError && /* @__PURE__ */ import_react4.default.createElement(import_Alert.default, { severity: "error", onClose: () => setGenerateError(null) }, generateError), /* @__PURE__ */ import_react4.default.createElement(
949
1706
  import_TextField2.default,
950
1707
  {
951
1708
  label: "Alias",
@@ -960,7 +1717,7 @@ var init_GenerateKeyDialog = __esm({
960
1717
  required: true,
961
1718
  fullWidth: true
962
1719
  }
963
- ), /* @__PURE__ */ import_react3.default.createElement(
1720
+ ), /* @__PURE__ */ import_react4.default.createElement(
964
1721
  import_TextField2.default,
965
1722
  {
966
1723
  select: true,
@@ -969,12 +1726,12 @@ var init_GenerateKeyDialog = __esm({
969
1726
  onChange: (e) => setFormData({ ...formData, duration: e.target.value }),
970
1727
  fullWidth: true
971
1728
  },
972
- /* @__PURE__ */ import_react3.default.createElement(import_MenuItem.default, { value: "1d" }, "1 Day"),
973
- /* @__PURE__ */ import_react3.default.createElement(import_MenuItem.default, { value: "7d" }, "7 Days"),
974
- /* @__PURE__ */ import_react3.default.createElement(import_MenuItem.default, { value: "30d" }, "30 Days"),
975
- /* @__PURE__ */ import_react3.default.createElement(import_MenuItem.default, { value: "90d" }, "90 Days"),
976
- /* @__PURE__ */ import_react3.default.createElement(import_MenuItem.default, { value: "1y" }, "1 Year")
977
- ), renderTeamField(), availableModels.length > 0 && /* @__PURE__ */ import_react3.default.createElement(
1729
+ /* @__PURE__ */ import_react4.default.createElement(import_MenuItem.default, { value: "1d" }, "1 Day"),
1730
+ /* @__PURE__ */ import_react4.default.createElement(import_MenuItem.default, { value: "7d" }, "7 Days"),
1731
+ /* @__PURE__ */ import_react4.default.createElement(import_MenuItem.default, { value: "30d" }, "30 Days"),
1732
+ /* @__PURE__ */ import_react4.default.createElement(import_MenuItem.default, { value: "90d" }, "90 Days"),
1733
+ /* @__PURE__ */ import_react4.default.createElement(import_MenuItem.default, { value: "1y" }, "1 Year")
1734
+ ), renderTeamField(), availableModels.length > 0 && /* @__PURE__ */ import_react4.default.createElement(
978
1735
  import_Autocomplete2.default,
979
1736
  {
980
1737
  multiple: true,
@@ -983,8 +1740,8 @@ var init_GenerateKeyDialog = __esm({
983
1740
  getOptionLabel: (m) => m.model_name,
984
1741
  value: selectedModels,
985
1742
  onChange: (_e, selected) => setFormData({ ...formData, models: selected.map((m) => m.model_name) }),
986
- renderOption: (props, m) => /* @__PURE__ */ import_react3.default.createElement("li", { ...props }, modelOption(m)),
987
- renderInput: (params) => /* @__PURE__ */ import_react3.default.createElement(
1743
+ renderOption: (props, m) => /* @__PURE__ */ import_react4.default.createElement("li", { ...props }, modelOption(m)),
1744
+ renderInput: (params) => /* @__PURE__ */ import_react4.default.createElement(
988
1745
  import_TextField2.default,
989
1746
  {
990
1747
  ...params,
@@ -994,10 +1751,10 @@ var init_GenerateKeyDialog = __esm({
994
1751
  }
995
1752
  )
996
1753
  }
997
- ), allowUnlimitedBudget && /* @__PURE__ */ import_react3.default.createElement(
1754
+ ), allowUnlimitedBudget && /* @__PURE__ */ import_react4.default.createElement(
998
1755
  import_FormControlLabel.default,
999
1756
  {
1000
- control: /* @__PURE__ */ import_react3.default.createElement(
1757
+ control: /* @__PURE__ */ import_react4.default.createElement(
1001
1758
  import_Checkbox.default,
1002
1759
  {
1003
1760
  checked: unlimitedBudget,
@@ -1013,7 +1770,7 @@ var init_GenerateKeyDialog = __esm({
1013
1770
  ),
1014
1771
  label: "Unlimited budget"
1015
1772
  }
1016
- ), /* @__PURE__ */ import_react3.default.createElement(
1773
+ ), /* @__PURE__ */ import_react4.default.createElement(
1017
1774
  import_TextField2.default,
1018
1775
  {
1019
1776
  label: "Max Budget (USD)",
@@ -1026,7 +1783,7 @@ var init_GenerateKeyDialog = __esm({
1026
1783
  required: true,
1027
1784
  fullWidth: true
1028
1785
  }
1029
- ), /* @__PURE__ */ import_react3.default.createElement(
1786
+ ), /* @__PURE__ */ import_react4.default.createElement(
1030
1787
  import_TextField2.default,
1031
1788
  {
1032
1789
  label: "TPM Limit",
@@ -1036,7 +1793,7 @@ var init_GenerateKeyDialog = __esm({
1036
1793
  helperText: "Max tokens per minute this key can consume across all models. Leave blank for no limit.",
1037
1794
  fullWidth: true
1038
1795
  }
1039
- ), /* @__PURE__ */ import_react3.default.createElement(
1796
+ ), /* @__PURE__ */ import_react4.default.createElement(
1040
1797
  import_TextField2.default,
1041
1798
  {
1042
1799
  label: "RPM Limit",
@@ -1046,7 +1803,7 @@ var init_GenerateKeyDialog = __esm({
1046
1803
  helperText: "Max requests per minute this key can make across all models. Leave blank for no limit.",
1047
1804
  fullWidth: true
1048
1805
  }
1049
- ))), /* @__PURE__ */ import_react3.default.createElement(import_DialogActions2.default, null, newKeyValue ? /* @__PURE__ */ import_react3.default.createElement(import_Button3.default, { onClick: handleClose, variant: "contained", color: "success" }, "Done") : /* @__PURE__ */ import_react3.default.createElement(import_react3.default.Fragment, null, /* @__PURE__ */ import_react3.default.createElement(import_Button3.default, { onClick: handleClose }, "Cancel"), /* @__PURE__ */ import_react3.default.createElement(
1806
+ ))), /* @__PURE__ */ import_react4.default.createElement(import_DialogActions2.default, null, newKeyValue ? /* @__PURE__ */ import_react4.default.createElement(import_Button3.default, { onClick: handleClose, variant: "contained", color: "success" }, "Done") : /* @__PURE__ */ import_react4.default.createElement(import_react4.default.Fragment, null, /* @__PURE__ */ import_react4.default.createElement(import_Button3.default, { onClick: handleClose }, "Cancel"), /* @__PURE__ */ import_react4.default.createElement(
1050
1807
  import_Button3.default,
1051
1808
  {
1052
1809
  onClick: handleGenerate,
@@ -1054,75 +1811,65 @@ var init_GenerateKeyDialog = __esm({
1054
1811
  color: "primary",
1055
1812
  disabled: !canGenerate
1056
1813
  },
1057
- submitting ? /* @__PURE__ */ import_react3.default.createElement(import_CircularProgress2.default, { size: 24 }) : "Generate"
1814
+ submitting ? /* @__PURE__ */ import_react4.default.createElement(import_CircularProgress2.default, { size: 24 }) : "Generate"
1058
1815
  ))));
1059
1816
  };
1060
1817
  }
1061
1818
  });
1062
1819
 
1063
1820
  // src/components/UsageStats.tsx
1064
- function modelColor(model) {
1065
- let h = 5381;
1066
- for (let i = 0; i < model.length; i++) h = (h << 5) + h + model.charCodeAt(i) >>> 0;
1067
- return MODEL_COLORS[h % MODEL_COLORS.length];
1821
+ function rateTone(rate) {
1822
+ if (rate >= 0.99) return "success";
1823
+ if (rate >= 0.9) return "warning";
1824
+ return "danger";
1068
1825
  }
1069
- var import_react4, import_Paper3, import_Box4, import_Typography4, import_FormControl, import_InputLabel, import_Select, import_MenuItem2, import_Grid, import_Tabs2, import_Tab2, import_Table2, import_TableBody2, import_TableCell2, import_TableContainer2, import_TableHead2, import_TableRow2, import_Chip3, import_LinearProgress3, import_Skeleton, import_styles, import_recharts, TOP_N_MODELS, PERIOD_LS_KEY, MODEL_COLORS, fmtPct, KpiCard, ChartSkeleton, EmptyChart, ChartOrFallback, UsageStats;
1826
+ var import_react5, import_Paper3, import_Box5, import_Typography5, import_TextField3, import_MenuItem2, import_Grid, import_Table2, import_TableBody2, import_TableCell2, import_TableContainer2, import_TableHead2, import_TableRow2, import_Skeleton3, import_recharts, TOP_N_MODELS, PERIOD_LS_KEY, PRESET_LABELS, fmtPct, ChartSkeleton, ChartOrFallback, SuccessRateCell, UsageStats;
1070
1827
  var init_UsageStats = __esm({
1071
1828
  "src/components/UsageStats.tsx"() {
1072
1829
  "use strict";
1073
- import_react4 = __toESM(require("react"));
1830
+ import_react5 = __toESM(require("react"));
1074
1831
  import_Paper3 = __toESM(require("@mui/material/Paper"));
1075
- import_Box4 = __toESM(require("@mui/material/Box"));
1076
- import_Typography4 = __toESM(require("@mui/material/Typography"));
1077
- import_FormControl = __toESM(require("@mui/material/FormControl"));
1078
- import_InputLabel = __toESM(require("@mui/material/InputLabel"));
1079
- import_Select = __toESM(require("@mui/material/Select"));
1832
+ import_Box5 = __toESM(require("@mui/material/Box"));
1833
+ import_Typography5 = __toESM(require("@mui/material/Typography"));
1834
+ import_TextField3 = __toESM(require("@mui/material/TextField"));
1080
1835
  import_MenuItem2 = __toESM(require("@mui/material/MenuItem"));
1081
1836
  import_Grid = __toESM(require("@mui/material/Grid"));
1082
- import_Tabs2 = __toESM(require("@mui/material/Tabs"));
1083
- import_Tab2 = __toESM(require("@mui/material/Tab"));
1084
1837
  import_Table2 = __toESM(require("@mui/material/Table"));
1085
1838
  import_TableBody2 = __toESM(require("@mui/material/TableBody"));
1086
1839
  import_TableCell2 = __toESM(require("@mui/material/TableCell"));
1087
1840
  import_TableContainer2 = __toESM(require("@mui/material/TableContainer"));
1088
1841
  import_TableHead2 = __toESM(require("@mui/material/TableHead"));
1089
1842
  import_TableRow2 = __toESM(require("@mui/material/TableRow"));
1090
- import_Chip3 = __toESM(require("@mui/material/Chip"));
1091
- import_LinearProgress3 = __toESM(require("@mui/material/LinearProgress"));
1092
- import_Skeleton = __toESM(require("@mui/material/Skeleton"));
1093
- import_styles = require("@mui/material/styles");
1843
+ import_Skeleton3 = __toESM(require("@mui/material/Skeleton"));
1094
1844
  import_recharts = require("recharts");
1095
1845
  init_format();
1846
+ init_ui();
1096
1847
  TOP_N_MODELS = 6;
1097
1848
  PERIOD_LS_KEY = "litellm_usage_period";
1098
- MODEL_COLORS = [
1099
- "#8884d8",
1100
- "#82ca9d",
1101
- "#ffc658",
1102
- "#ff7300",
1103
- "#0088fe",
1104
- "#00C49F",
1105
- "#FFBB28",
1106
- "#FF8042",
1107
- "#a4de6c",
1108
- "#d0ed57"
1109
- ];
1849
+ PRESET_LABELS = {
1850
+ today: "Today",
1851
+ "24h": "Last 24 hours",
1852
+ "7d": "Last 7 days",
1853
+ "30d": "Last 30 days"
1854
+ };
1110
1855
  fmtPct = (n) => `${(n * 100).toFixed(1)}%`;
1111
- KpiCard = ({ label, value, hint }) => /* @__PURE__ */ import_react4.default.createElement(import_Paper3.default, { variant: "outlined", sx: { p: 2, height: "100%" } }, /* @__PURE__ */ import_react4.default.createElement(import_Typography4.default, { variant: "caption", color: "text.secondary" }, label), /* @__PURE__ */ import_react4.default.createElement(import_Typography4.default, { variant: "h5", sx: { mt: 0.5 } }, value), hint ? /* @__PURE__ */ import_react4.default.createElement(import_Typography4.default, { variant: "caption", color: "text.secondary" }, hint) : null);
1112
- ChartSkeleton = ({ height = 260 }) => /* @__PURE__ */ import_react4.default.createElement(import_Skeleton.default, { variant: "rectangular", height, sx: { borderRadius: 1 } });
1113
- EmptyChart = ({
1114
- height = 260,
1115
- message = "No data for this period"
1116
- }) => /* @__PURE__ */ import_react4.default.createElement(import_Box4.default, { height, display: "flex", alignItems: "center", justifyContent: "center" }, /* @__PURE__ */ import_react4.default.createElement(import_Typography4.default, { color: "text.secondary", variant: "body2" }, message));
1117
- ChartOrFallback = ({
1118
- loading,
1119
- empty,
1120
- height,
1121
- children
1122
- }) => {
1123
- if (loading) return /* @__PURE__ */ import_react4.default.createElement(ChartSkeleton, { height });
1124
- if (empty) return /* @__PURE__ */ import_react4.default.createElement(EmptyChart, { height });
1125
- return /* @__PURE__ */ import_react4.default.createElement(import_react4.default.Fragment, null, children);
1856
+ ChartSkeleton = ({ height = 240 }) => /* @__PURE__ */ import_react5.default.createElement(import_Skeleton3.default, { variant: "rounded", height });
1857
+ ChartOrFallback = ({ loading, empty, height = 240, children }) => {
1858
+ if (loading) return /* @__PURE__ */ import_react5.default.createElement(ChartSkeleton, { height });
1859
+ if (empty) return /* @__PURE__ */ import_react5.default.createElement(EmptyState, { message: "No data for this period", height });
1860
+ return /* @__PURE__ */ import_react5.default.createElement(import_react5.default.Fragment, null, children);
1861
+ };
1862
+ SuccessRateCell = ({ rate, requests }) => {
1863
+ if (requests === 0) return /* @__PURE__ */ import_react5.default.createElement(import_Typography5.default, { variant: "body2", color: "text.secondary" }, "\u2014");
1864
+ const tone = rateTone(rate);
1865
+ return /* @__PURE__ */ import_react5.default.createElement(import_Box5.default, { display: "flex", alignItems: "center", gap: 1 }, /* @__PURE__ */ import_react5.default.createElement(import_Box5.default, { width: 72, flexShrink: 0 }, /* @__PURE__ */ import_react5.default.createElement(Meter, { value: rate * 100, tone, height: 4 })), /* @__PURE__ */ import_react5.default.createElement(
1866
+ import_Typography5.default,
1867
+ {
1868
+ variant: "caption",
1869
+ sx: { fontVariantNumeric: "tabular-nums", minWidth: 44 }
1870
+ },
1871
+ fmtPct(rate)
1872
+ ));
1126
1873
  };
1127
1874
  UsageStats = ({
1128
1875
  usage,
@@ -1132,15 +1879,10 @@ var init_UsageStats = __esm({
1132
1879
  loading,
1133
1880
  userInfo
1134
1881
  }) => {
1135
- const theme = (0, import_styles.useTheme)();
1136
- const [selectedModel, setSelectedModel] = (0, import_react4.useState)("all");
1137
- const [tab, setTab] = (0, import_react4.useState)("costs");
1138
- const gridStroke = theme.palette.divider;
1139
- const tickFill = theme.palette.text.secondary;
1140
- const tickStyle = { fontSize: 12, fill: tickFill };
1141
- const tickStyleSmall = { fontSize: 11, fill: tickFill };
1142
- const tickStyleTiny = { fontSize: 10, fill: tickFill };
1143
- const selectedPreset = (0, import_react4.useMemo)(() => {
1882
+ const chart = useChartTheme();
1883
+ const [selectedModel, setSelectedModel] = (0, import_react5.useState)("all");
1884
+ const [tab, setTab] = (0, import_react5.useState)("costs");
1885
+ const selectedPreset = (0, import_react5.useMemo)(() => {
1144
1886
  if (dateRange.start.toDateString() === dateRange.end.toDateString()) return "today";
1145
1887
  const diffMs = dateRange.end.getTime() - dateRange.start.getTime();
1146
1888
  const diffHours = diffMs / (1e3 * 60 * 60);
@@ -1162,7 +1904,7 @@ var init_UsageStats = __esm({
1162
1904
  }
1163
1905
  onDateRangeChange({ start, end });
1164
1906
  };
1165
- const dailyData = (0, import_react4.useMemo)(
1907
+ const dailyData = (0, import_react5.useMemo)(
1166
1908
  () => (usage?.daily_usage ?? []).map((d) => ({
1167
1909
  date: d.date,
1168
1910
  spend: d.spend,
@@ -1175,21 +1917,21 @@ var init_UsageStats = __esm({
1175
1917
  })),
1176
1918
  [usage]
1177
1919
  );
1178
- const cumulativeData = (0, import_react4.useMemo)(() => {
1920
+ const cumulativeData = (0, import_react5.useMemo)(() => {
1179
1921
  let cum = 0;
1180
1922
  return dailyData.map((d) => {
1181
1923
  cum += d.spend;
1182
1924
  return { date: d.date, cumulative: cum };
1183
1925
  });
1184
1926
  }, [dailyData]);
1185
- const successRateData = (0, import_react4.useMemo)(
1927
+ const successRateData = (0, import_react5.useMemo)(
1186
1928
  () => dailyData.filter((d) => d.apiRequests > 0).map((d) => ({
1187
1929
  date: d.date,
1188
1930
  successRate: parseFloat((d.successfulRequests / d.apiRequests * 100).toFixed(1))
1189
1931
  })),
1190
1932
  [dailyData]
1191
1933
  );
1192
- const { modelSpendByDate, topModels: topSpendModels } = (0, import_react4.useMemo)(() => {
1934
+ const { modelSpendByDate, topModels: topSpendModels } = (0, import_react5.useMemo)(() => {
1193
1935
  const rows = usage?.daily_by_model ?? [];
1194
1936
  const modelTotals = {};
1195
1937
  for (const r of rows) modelTotals[r.model] = (modelTotals[r.model] ?? 0) + r.spend;
@@ -1204,7 +1946,7 @@ var init_UsageStats = __esm({
1204
1946
  const hasOther = sorted.some((r) => r.Other > 0);
1205
1947
  return { modelSpendByDate: sorted, topModels: hasOther ? [...top, "Other"] : top };
1206
1948
  }, [usage]);
1207
- const modelRows = (0, import_react4.useMemo)(() => {
1949
+ const modelRows = (0, import_react5.useMemo)(() => {
1208
1950
  const entries = Object.entries(usage?.usage_by_model ?? {}).map(([model, d]) => ({
1209
1951
  model,
1210
1952
  spend: d.total_spend,
@@ -1218,11 +1960,11 @@ var init_UsageStats = __esm({
1218
1960
  }));
1219
1961
  return selectedModel === "all" ? entries : entries.filter((e) => e.model === selectedModel);
1220
1962
  }, [usage, selectedModel]);
1221
- const topModelSpendBars = (0, import_react4.useMemo)(
1963
+ const topModelSpendBars = (0, import_react5.useMemo)(
1222
1964
  () => [...modelRows].sort((a, b) => b.spend - a.spend).slice(0, 10),
1223
1965
  [modelRows]
1224
1966
  );
1225
- const keyRows = (0, import_react4.useMemo)(
1967
+ const keyRows = (0, import_react5.useMemo)(
1226
1968
  () => Object.entries(usage?.usage_by_key ?? {}).map(([keyHash, d]) => ({
1227
1969
  keyHash,
1228
1970
  keyAlias: d.key_alias ?? keyHash.slice(0, 8),
@@ -1239,7 +1981,7 @@ var init_UsageStats = __esm({
1239
1981
  })),
1240
1982
  [usage]
1241
1983
  );
1242
- const topKeySpendBars = (0, import_react4.useMemo)(
1984
+ const topKeySpendBars = (0, import_react5.useMemo)(
1243
1985
  () => [...keyRows].sort((a, b) => b.spend - a.spend).slice(0, 10),
1244
1986
  [keyRows]
1245
1987
  );
@@ -1247,99 +1989,234 @@ var init_UsageStats = __esm({
1247
1989
  const overallSuccessRate = totalRequests > 0 ? (usage?.successful_requests ?? 0) / totalRequests : 0;
1248
1990
  const maxBudget = userInfo?.max_budget ?? 0;
1249
1991
  const totalCumSpend = cumulativeData[cumulativeData.length - 1]?.cumulative ?? 0;
1992
+ const cumulativeDomain = maxBudget > 0 ? [0, (dataMax) => Math.max(dataMax, maxBudget) * 1.05] : [0, "auto"];
1993
+ const successTone = totalRequests === 0 ? "neutral" : rateTone(overallSuccessRate);
1994
+ const metrics = [
1995
+ {
1996
+ label: "Total spend",
1997
+ value: fmtUsd(usage?.total_spend ?? 0),
1998
+ hint: maxBudget > 0 ? `of ${fmtUsd(maxBudget)} budget` : void 0,
1999
+ tone: "accent"
2000
+ },
2001
+ {
2002
+ label: "Requests",
2003
+ value: fmtInt(totalRequests),
2004
+ hint: `${fmtInt(usage?.failed_requests ?? 0)} failed`,
2005
+ tone: "info"
2006
+ },
2007
+ {
2008
+ label: "Success rate",
2009
+ value: totalRequests > 0 ? fmtPct(overallSuccessRate) : "\u2014",
2010
+ hint: totalRequests > 0 ? `${fmtInt(usage?.successful_requests ?? 0)} succeeded` : void 0,
2011
+ tone: successTone
2012
+ },
2013
+ {
2014
+ label: "Tokens",
2015
+ value: fmtCompact(usage?.total_tokens ?? 0),
2016
+ hint: `${fmtCompact(usage?.prompt_tokens ?? 0)} in \xB7 ${fmtCompact(usage?.completion_tokens ?? 0)} out`,
2017
+ tone: "success"
2018
+ }
2019
+ ];
1250
2020
  const renderKeyRows = () => {
1251
2021
  if (loading) {
1252
- return /* @__PURE__ */ import_react4.default.createElement(import_TableRow2.default, null, /* @__PURE__ */ import_react4.default.createElement(import_TableCell2.default, { colSpan: 7 }, /* @__PURE__ */ import_react4.default.createElement(import_LinearProgress3.default, null)));
2022
+ return /* @__PURE__ */ import_react5.default.createElement(import_TableRow2.default, null, /* @__PURE__ */ import_react5.default.createElement(import_TableCell2.default, { colSpan: 7, sx: { py: 3 } }, /* @__PURE__ */ import_react5.default.createElement(import_Skeleton3.default, { variant: "rounded", height: 80 })));
1253
2023
  }
1254
2024
  if (keyRows.length === 0) {
1255
- return /* @__PURE__ */ import_react4.default.createElement(import_TableRow2.default, null, /* @__PURE__ */ import_react4.default.createElement(import_TableCell2.default, { colSpan: 7, align: "center" }, "No key activity"));
2025
+ return /* @__PURE__ */ import_react5.default.createElement(import_TableRow2.default, null, /* @__PURE__ */ import_react5.default.createElement(import_TableCell2.default, { colSpan: 7 }, /* @__PURE__ */ import_react5.default.createElement(EmptyState, { message: "No key activity in this period" })));
1256
2026
  }
1257
- return [...keyRows].sort((a, b) => b.spend - a.spend || b.apiRequests - a.apiRequests).map((r) => /* @__PURE__ */ import_react4.default.createElement(import_TableRow2.default, { key: r.keyHash }, /* @__PURE__ */ import_react4.default.createElement(import_TableCell2.default, null, /* @__PURE__ */ import_react4.default.createElement(import_Typography4.default, { variant: "body2" }, r.keyAlias), r.teamId ? /* @__PURE__ */ import_react4.default.createElement(import_Typography4.default, { variant: "caption", color: "text.secondary" }, "team: ", r.teamId) : null), /* @__PURE__ */ import_react4.default.createElement(import_TableCell2.default, null, /* @__PURE__ */ import_react4.default.createElement(import_Box4.default, { display: "flex", gap: 0.5, flexWrap: "wrap" }, r.models.map((m) => /* @__PURE__ */ import_react4.default.createElement(import_Chip3.default, { key: m, label: m, size: "small", variant: "outlined" })))), /* @__PURE__ */ import_react4.default.createElement(import_TableCell2.default, { align: "right" }, fmtUsd(r.spend)), /* @__PURE__ */ import_react4.default.createElement(import_TableCell2.default, { align: "right" }, fmtInt(r.apiRequests)), /* @__PURE__ */ import_react4.default.createElement(import_TableCell2.default, { align: "right" }, fmtInt(r.totalTokens)), /* @__PURE__ */ import_react4.default.createElement(import_TableCell2.default, { align: "right" }, fmtInt(r.failedRequests)), /* @__PURE__ */ import_react4.default.createElement(import_TableCell2.default, null, r.apiRequests > 0 ? /* @__PURE__ */ import_react4.default.createElement(import_Box4.default, { display: "flex", alignItems: "center", gap: 1 }, /* @__PURE__ */ import_react4.default.createElement(
1258
- import_LinearProgress3.default,
1259
- {
1260
- variant: "determinate",
1261
- value: r.successRate * 100,
1262
- sx: { flex: 1, height: 6, borderRadius: 3 }
1263
- }
1264
- ), /* @__PURE__ */ import_react4.default.createElement(import_Typography4.default, { variant: "caption" }, fmtPct(r.successRate))) : "\u2014")));
2027
+ return [...keyRows].sort((a, b) => b.spend - a.spend || b.apiRequests - a.apiRequests).map((r) => /* @__PURE__ */ import_react5.default.createElement(import_TableRow2.default, { key: r.keyHash }, /* @__PURE__ */ import_react5.default.createElement(import_TableCell2.default, null, /* @__PURE__ */ import_react5.default.createElement(import_Typography5.default, { variant: "body2", sx: { fontWeight: 600 } }, r.keyAlias), r.teamId ? /* @__PURE__ */ import_react5.default.createElement(import_Typography5.default, { variant: "caption", color: "text.secondary" }, "team: ", r.teamId) : null), /* @__PURE__ */ import_react5.default.createElement(import_TableCell2.default, null, /* @__PURE__ */ import_react5.default.createElement(import_Box5.default, { display: "flex", gap: 0.5, flexWrap: "wrap" }, r.models.slice(0, 3).map((m) => /* @__PURE__ */ import_react5.default.createElement(TagChip, { key: m, label: m, title: m })), r.models.length > 3 && /* @__PURE__ */ import_react5.default.createElement(TagChip, { label: `+${r.models.length - 3}`, mono: false }))), /* @__PURE__ */ import_react5.default.createElement(import_TableCell2.default, { align: "right" }, fmtUsd(r.spend)), /* @__PURE__ */ import_react5.default.createElement(import_TableCell2.default, { align: "right" }, fmtInt(r.apiRequests)), /* @__PURE__ */ import_react5.default.createElement(import_TableCell2.default, { align: "right" }, fmtCompact(r.totalTokens)), /* @__PURE__ */ import_react5.default.createElement(import_TableCell2.default, { align: "right" }, fmtInt(r.failedRequests)), /* @__PURE__ */ import_react5.default.createElement(import_TableCell2.default, null, /* @__PURE__ */ import_react5.default.createElement(SuccessRateCell, { rate: r.successRate, requests: r.apiRequests }))));
1265
2028
  };
1266
2029
  const renderModelRows = () => {
1267
2030
  if (loading) {
1268
- return /* @__PURE__ */ import_react4.default.createElement(import_TableRow2.default, null, /* @__PURE__ */ import_react4.default.createElement(import_TableCell2.default, { colSpan: 8 }, /* @__PURE__ */ import_react4.default.createElement(import_LinearProgress3.default, null)));
2031
+ return /* @__PURE__ */ import_react5.default.createElement(import_TableRow2.default, null, /* @__PURE__ */ import_react5.default.createElement(import_TableCell2.default, { colSpan: 8, sx: { py: 3 } }, /* @__PURE__ */ import_react5.default.createElement(import_Skeleton3.default, { variant: "rounded", height: 80 })));
1269
2032
  }
1270
2033
  if (modelRows.length === 0) {
1271
- return /* @__PURE__ */ import_react4.default.createElement(import_TableRow2.default, null, /* @__PURE__ */ import_react4.default.createElement(import_TableCell2.default, { colSpan: 8, align: "center" }, "No model activity"));
2034
+ return /* @__PURE__ */ import_react5.default.createElement(import_TableRow2.default, null, /* @__PURE__ */ import_react5.default.createElement(import_TableCell2.default, { colSpan: 8 }, /* @__PURE__ */ import_react5.default.createElement(EmptyState, { message: "No model activity in this period" })));
1272
2035
  }
1273
- return [...modelRows].sort((a, b) => b.spend - a.spend || b.totalTokens - a.totalTokens).map((r) => /* @__PURE__ */ import_react4.default.createElement(import_TableRow2.default, { key: r.model }, /* @__PURE__ */ import_react4.default.createElement(import_TableCell2.default, null, /* @__PURE__ */ import_react4.default.createElement(import_Box4.default, { display: "flex", alignItems: "center", gap: 0.75 }, /* @__PURE__ */ import_react4.default.createElement(import_Box4.default, { sx: { width: 10, height: 10, borderRadius: "50%", bgcolor: modelColor(r.model), flexShrink: 0 } }), r.model)), /* @__PURE__ */ import_react4.default.createElement(import_TableCell2.default, { align: "right" }, fmtUsd(r.spend)), /* @__PURE__ */ import_react4.default.createElement(import_TableCell2.default, { align: "right" }, fmtInt(r.apiRequests)), /* @__PURE__ */ import_react4.default.createElement(import_TableCell2.default, { align: "right" }, fmtInt(r.successfulRequests)), /* @__PURE__ */ import_react4.default.createElement(import_TableCell2.default, { align: "right" }, fmtInt(r.failedRequests)), /* @__PURE__ */ import_react4.default.createElement(import_TableCell2.default, { align: "right" }, fmtInt(r.promptTokens)), /* @__PURE__ */ import_react4.default.createElement(import_TableCell2.default, { align: "right" }, fmtInt(r.completionTokens)), /* @__PURE__ */ import_react4.default.createElement(import_TableCell2.default, null, r.apiRequests > 0 ? /* @__PURE__ */ import_react4.default.createElement(import_Box4.default, { display: "flex", alignItems: "center", gap: 1 }, /* @__PURE__ */ import_react4.default.createElement(
1274
- import_LinearProgress3.default,
1275
- {
1276
- variant: "determinate",
1277
- value: r.successRate * 100,
1278
- sx: { flex: 1, height: 6, borderRadius: 3 }
1279
- }
1280
- ), /* @__PURE__ */ import_react4.default.createElement(import_Typography4.default, { variant: "caption" }, fmtPct(r.successRate))) : "\u2014")));
2036
+ return [...modelRows].sort((a, b) => b.spend - a.spend || b.totalTokens - a.totalTokens).map((r) => /* @__PURE__ */ import_react5.default.createElement(import_TableRow2.default, { key: r.model }, /* @__PURE__ */ import_react5.default.createElement(import_TableCell2.default, null, /* @__PURE__ */ import_react5.default.createElement(import_Box5.default, { display: "flex", alignItems: "center", gap: 1 }, /* @__PURE__ */ import_react5.default.createElement(import_Box5.default, { sx: { width: 8, height: 8, borderRadius: "50%", bgcolor: chartColor(r.model), flexShrink: 0 } }), /* @__PURE__ */ import_react5.default.createElement(import_Typography5.default, { variant: "body2", sx: { fontWeight: 600 } }, r.model))), /* @__PURE__ */ import_react5.default.createElement(import_TableCell2.default, { align: "right" }, fmtUsd(r.spend)), /* @__PURE__ */ import_react5.default.createElement(import_TableCell2.default, { align: "right" }, fmtInt(r.apiRequests)), /* @__PURE__ */ import_react5.default.createElement(import_TableCell2.default, { align: "right" }, fmtInt(r.successfulRequests)), /* @__PURE__ */ import_react5.default.createElement(import_TableCell2.default, { align: "right" }, fmtInt(r.failedRequests)), /* @__PURE__ */ import_react5.default.createElement(import_TableCell2.default, { align: "right" }, fmtCompact(r.promptTokens)), /* @__PURE__ */ import_react5.default.createElement(import_TableCell2.default, { align: "right" }, fmtCompact(r.completionTokens)), /* @__PURE__ */ import_react5.default.createElement(import_TableCell2.default, null, /* @__PURE__ */ import_react5.default.createElement(SuccessRateCell, { rate: r.successRate, requests: r.apiRequests }))));
1281
2037
  };
1282
- return /* @__PURE__ */ import_react4.default.createElement(import_Paper3.default, { sx: { p: 2 } }, /* @__PURE__ */ import_react4.default.createElement(import_Box4.default, { display: "flex", justifyContent: "space-between", alignItems: "center", mb: 2, flexWrap: "wrap", gap: 2 }, /* @__PURE__ */ import_react4.default.createElement(import_Typography4.default, { variant: "h6" }, "Usage Analytics"), /* @__PURE__ */ import_react4.default.createElement(import_Box4.default, { display: "flex", gap: 2, alignItems: "center", flexWrap: "wrap" }, /* @__PURE__ */ import_react4.default.createElement(import_FormControl.default, { size: "small", sx: { minWidth: 140 } }, /* @__PURE__ */ import_react4.default.createElement(import_InputLabel.default, null, "Period"), /* @__PURE__ */ import_react4.default.createElement(
1283
- import_Select.default,
2038
+ const periodSelect = /* @__PURE__ */ import_react5.default.createElement(
2039
+ import_TextField3.default,
1284
2040
  {
1285
- value: selectedPreset,
2041
+ select: true,
2042
+ size: "small",
1286
2043
  label: "Period",
1287
- onChange: (e) => handlePresetChange(e.target.value)
2044
+ value: selectedPreset,
2045
+ onChange: (e) => handlePresetChange(e.target.value),
2046
+ sx: { minWidth: 160 }
1288
2047
  },
1289
- /* @__PURE__ */ import_react4.default.createElement(import_MenuItem2.default, { value: "today" }, "Today"),
1290
- /* @__PURE__ */ import_react4.default.createElement(import_MenuItem2.default, { value: "24h" }, "Last 24h"),
1291
- /* @__PURE__ */ import_react4.default.createElement(import_MenuItem2.default, { value: "7d" }, "Last 7 days"),
1292
- /* @__PURE__ */ import_react4.default.createElement(import_MenuItem2.default, { value: "30d" }, "Last 30 days")
1293
- )), tab === "models" && /* @__PURE__ */ import_react4.default.createElement(import_FormControl.default, { size: "small", sx: { minWidth: 180 } }, /* @__PURE__ */ import_react4.default.createElement(import_InputLabel.default, null, "Model"), /* @__PURE__ */ import_react4.default.createElement(
1294
- import_Select.default,
2048
+ Object.keys(PRESET_LABELS).map((p) => /* @__PURE__ */ import_react5.default.createElement(import_MenuItem2.default, { key: p, value: p }, PRESET_LABELS[p]))
2049
+ );
2050
+ return /* @__PURE__ */ import_react5.default.createElement(
2051
+ SectionCard,
1295
2052
  {
1296
- value: selectedModel,
1297
- label: "Model",
1298
- onChange: (e) => setSelectedModel(e.target.value)
2053
+ title: "Usage Analytics",
2054
+ subtitle: `${PRESET_LABELS[selectedPreset]} \xB7 ${dateRange.start.toLocaleDateString()} \u2013 ${dateRange.end.toLocaleDateString()}`,
2055
+ actions: periodSelect
1299
2056
  },
1300
- /* @__PURE__ */ import_react4.default.createElement(import_MenuItem2.default, { value: "all" }, "All Models"),
1301
- models.map((m) => /* @__PURE__ */ import_react4.default.createElement(import_MenuItem2.default, { key: m.model_name, value: m.model_name }, m.model_name))
1302
- )))), /* @__PURE__ */ import_react4.default.createElement(import_Grid.default, { container: true, spacing: 2, sx: { mb: 2 } }, /* @__PURE__ */ import_react4.default.createElement(import_Grid.default, { item: true, xs: 6, sm: 3 }, loading ? /* @__PURE__ */ import_react4.default.createElement(ChartSkeleton, { height: 80 }) : /* @__PURE__ */ import_react4.default.createElement(KpiCard, { label: "Total Spend", value: fmtUsd(usage?.total_spend ?? 0) })), /* @__PURE__ */ import_react4.default.createElement(import_Grid.default, { item: true, xs: 6, sm: 3 }, loading ? /* @__PURE__ */ import_react4.default.createElement(ChartSkeleton, { height: 80 }) : /* @__PURE__ */ import_react4.default.createElement(KpiCard, { label: "Total Requests", value: fmtInt(totalRequests), hint: `${fmtInt(usage?.failed_requests ?? 0)} failed` })), /* @__PURE__ */ import_react4.default.createElement(import_Grid.default, { item: true, xs: 6, sm: 3 }, loading ? /* @__PURE__ */ import_react4.default.createElement(ChartSkeleton, { height: 80 }) : /* @__PURE__ */ import_react4.default.createElement(KpiCard, { label: "Success Rate", value: totalRequests > 0 ? fmtPct(overallSuccessRate) : "\u2014" })), /* @__PURE__ */ import_react4.default.createElement(import_Grid.default, { item: true, xs: 6, sm: 3 }, loading ? /* @__PURE__ */ import_react4.default.createElement(ChartSkeleton, { height: 80 }) : /* @__PURE__ */ import_react4.default.createElement(
1303
- KpiCard,
1304
- {
1305
- label: "Total Tokens",
1306
- value: fmtInt(usage?.total_tokens ?? 0),
1307
- hint: `${fmtInt(usage?.prompt_tokens ?? 0)} in \xB7 ${fmtInt(usage?.completion_tokens ?? 0)} out`
1308
- }
1309
- ))), /* @__PURE__ */ import_react4.default.createElement(import_Tabs2.default, { value: tab, onChange: (_, v) => setTab(v), sx: { mb: 2 } }, /* @__PURE__ */ import_react4.default.createElement(import_Tab2.default, { value: "costs", label: "Costs" }), /* @__PURE__ */ import_react4.default.createElement(import_Tab2.default, { value: "models", label: "Model Activity" }), /* @__PURE__ */ import_react4.default.createElement(import_Tab2.default, { value: "keys", label: "Key Activity" })), tab === "costs" && /* @__PURE__ */ import_react4.default.createElement(import_Grid.default, { container: true, spacing: 3 }, /* @__PURE__ */ import_react4.default.createElement(import_Grid.default, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ import_react4.default.createElement(import_Typography4.default, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Daily Spend by Model"), /* @__PURE__ */ import_react4.default.createElement(ChartOrFallback, { loading, empty: modelSpendByDate.length === 0 }, /* @__PURE__ */ import_react4.default.createElement(import_Box4.default, { height: 260 }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.AreaChart, { data: modelSpendByDate }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.CartesianGrid, { strokeDasharray: "3 3", stroke: gridStroke }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.XAxis, { dataKey: "date", tick: tickStyle }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.YAxis, { tick: tickStyle, tickFormatter: (v) => `$${v.toFixed(2)}` }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.Tooltip, { formatter: (v) => [`$${v.toFixed(4)}`, void 0] }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.Legend, null), topSpendModels.map((m) => /* @__PURE__ */ import_react4.default.createElement(
1310
- import_recharts.Area,
1311
- {
1312
- key: m,
1313
- type: "monotone",
1314
- dataKey: m,
1315
- stackId: "spend",
1316
- stroke: modelColor(m),
1317
- fill: modelColor(m),
1318
- fillOpacity: 0.6
1319
- }
1320
- ))))))), /* @__PURE__ */ import_react4.default.createElement(import_Grid.default, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ import_react4.default.createElement(import_Typography4.default, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Daily Token Usage"), /* @__PURE__ */ import_react4.default.createElement(ChartOrFallback, { loading, empty: dailyData.length === 0 }, /* @__PURE__ */ import_react4.default.createElement(import_Box4.default, { height: 260 }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.AreaChart, { data: dailyData }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.CartesianGrid, { strokeDasharray: "3 3", stroke: gridStroke }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.XAxis, { dataKey: "date", tick: tickStyle }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.YAxis, { tick: tickStyle, tickFormatter: fmtInt }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.Tooltip, { formatter: (v) => [fmtInt(v), void 0] }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.Legend, null), /* @__PURE__ */ import_react4.default.createElement(import_recharts.Area, { type: "monotone", dataKey: "promptTokens", name: "Input (prompt)", stackId: "tok", stroke: "#8884d8", fill: "#8884d8", fillOpacity: 0.5 }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.Area, { type: "monotone", dataKey: "completionTokens", name: "Output (completion)", stackId: "tok", stroke: "#82ca9d", fill: "#82ca9d", fillOpacity: 0.5 })))))), /* @__PURE__ */ import_react4.default.createElement(import_Grid.default, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ import_react4.default.createElement(import_Typography4.default, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Daily Requests"), /* @__PURE__ */ import_react4.default.createElement(ChartOrFallback, { loading, empty: dailyData.length === 0 }, /* @__PURE__ */ import_react4.default.createElement(import_Box4.default, { height: 260 }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.BarChart, { data: dailyData }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.CartesianGrid, { strokeDasharray: "3 3", stroke: gridStroke }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.XAxis, { dataKey: "date", tick: tickStyle }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.YAxis, { tick: tickStyle }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.Tooltip, null), /* @__PURE__ */ import_react4.default.createElement(import_recharts.Legend, null), /* @__PURE__ */ import_react4.default.createElement(import_recharts.Bar, { dataKey: "successfulRequests", name: "Successful", fill: "#82ca9d", stackId: "r" }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.Bar, { dataKey: "failedRequests", name: "Failed", fill: "#e57373", stackId: "r" })))))), /* @__PURE__ */ import_react4.default.createElement(import_Grid.default, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ import_react4.default.createElement(import_Typography4.default, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Daily Success Rate"), /* @__PURE__ */ import_react4.default.createElement(ChartOrFallback, { loading, empty: successRateData.length === 0 }, /* @__PURE__ */ import_react4.default.createElement(import_Box4.default, { height: 260 }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.LineChart, { data: successRateData }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.CartesianGrid, { strokeDasharray: "3 3", stroke: gridStroke }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.XAxis, { dataKey: "date", tick: tickStyle }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.YAxis, { domain: [0, 100], tick: tickStyle, tickFormatter: (v) => `${v}%` }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.Tooltip, { formatter: (v) => [`${v}%`, "Success rate"] }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.ReferenceLine, { y: 100, stroke: "#82ca9d", strokeDasharray: "4 2" }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.Line, { type: "monotone", dataKey: "successRate", name: "Success rate", stroke: "#8884d8", dot: { r: 3 } })))))), (maxBudget > 0 || cumulativeData.length > 0) && /* @__PURE__ */ import_react4.default.createElement(import_Grid.default, { item: true, xs: 12 }, /* @__PURE__ */ import_react4.default.createElement(import_Typography4.default, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Cumulative Spend", maxBudget > 0 ? ` vs Budget (${fmtUsd(maxBudget)})` : "", maxBudget > 0 && /* @__PURE__ */ import_react4.default.createElement(import_Typography4.default, { component: "span", variant: "caption", color: "text.secondary", sx: { ml: 1 } }, fmtUsd(totalCumSpend), " used \xB7 ", fmtUsd(Math.max(0, maxBudget - totalCumSpend)), " remaining")), /* @__PURE__ */ import_react4.default.createElement(ChartOrFallback, { loading, empty: cumulativeData.length === 0, height: 180 }, /* @__PURE__ */ import_react4.default.createElement(import_Box4.default, { height: 180 }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.AreaChart, { data: cumulativeData }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.CartesianGrid, { strokeDasharray: "3 3", stroke: gridStroke }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.XAxis, { dataKey: "date", tick: tickStyle }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.YAxis, { tick: tickStyle, tickFormatter: (v) => `$${v.toFixed(2)}` }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.Tooltip, { formatter: (v) => [`$${v.toFixed(4)}`, "Cumulative spend"] }), maxBudget > 0 && /* @__PURE__ */ import_react4.default.createElement(import_recharts.ReferenceLine, { y: maxBudget, stroke: "#e57373", strokeDasharray: "6 3", label: { value: `Budget ${fmtUsd(maxBudget)}`, position: "insideTopRight", fontSize: 11 } }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.Area, { type: "monotone", dataKey: "cumulative", name: "Cumulative spend", stroke: "#ffc658", fill: "#ffc658", fillOpacity: 0.3 }))))))), tab === "models" && /* @__PURE__ */ import_react4.default.createElement(import_Grid.default, { container: true, spacing: 3 }, /* @__PURE__ */ import_react4.default.createElement(import_Grid.default, { item: true, xs: 12 }, /* @__PURE__ */ import_react4.default.createElement(import_Typography4.default, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Spend by Model"), /* @__PURE__ */ import_react4.default.createElement(ChartOrFallback, { loading, empty: topModelSpendBars.length === 0 }, /* @__PURE__ */ import_react4.default.createElement(import_Box4.default, { height: Math.max(200, topModelSpendBars.length * 36) }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.BarChart, { data: topModelSpendBars, layout: "vertical", margin: { left: 20 } }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.CartesianGrid, { strokeDasharray: "3 3", stroke: gridStroke }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.XAxis, { type: "number", tick: tickStyle, tickFormatter: (v) => `$${v.toFixed(2)}` }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.YAxis, { type: "category", dataKey: "model", tick: tickStyleSmall, width: 200 }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.Tooltip, { formatter: (v) => [fmtUsd(v), "Spend"] }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.Bar, { dataKey: "spend", name: "Spend", radius: [0, 4, 4, 0] }, topModelSpendBars.map((r) => /* @__PURE__ */ import_react4.default.createElement("rect", { key: r.model, fill: modelColor(r.model) })))))))), /* @__PURE__ */ import_react4.default.createElement(import_Grid.default, { item: true, xs: 12 }, /* @__PURE__ */ import_react4.default.createElement(import_Typography4.default, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Tokens by Model"), /* @__PURE__ */ import_react4.default.createElement(ChartOrFallback, { loading, empty: modelRows.length === 0 }, /* @__PURE__ */ import_react4.default.createElement(import_Box4.default, { height: 260 }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.BarChart, { data: modelRows }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.CartesianGrid, { strokeDasharray: "3 3", stroke: gridStroke }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.XAxis, { dataKey: "model", tick: tickStyleTiny, interval: 0, angle: -15, textAnchor: "end", height: 60 }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.YAxis, { tick: tickStyle }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.Tooltip, null), /* @__PURE__ */ import_react4.default.createElement(import_recharts.Legend, null), /* @__PURE__ */ import_react4.default.createElement(import_recharts.Bar, { dataKey: "promptTokens", name: "Prompt", fill: "#8884d8", stackId: "t" }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.Bar, { dataKey: "completionTokens", name: "Completion", fill: "#82ca9d", stackId: "t" })))))), /* @__PURE__ */ import_react4.default.createElement(import_Grid.default, { item: true, xs: 12 }, /* @__PURE__ */ import_react4.default.createElement(import_TableContainer2.default, { component: import_Paper3.default, variant: "outlined" }, /* @__PURE__ */ import_react4.default.createElement(import_Table2.default, { size: "small" }, /* @__PURE__ */ import_react4.default.createElement(import_TableHead2.default, null, /* @__PURE__ */ import_react4.default.createElement(import_TableRow2.default, null, /* @__PURE__ */ import_react4.default.createElement(import_TableCell2.default, null, "Model"), /* @__PURE__ */ import_react4.default.createElement(import_TableCell2.default, { align: "right" }, "Spend"), /* @__PURE__ */ import_react4.default.createElement(import_TableCell2.default, { align: "right" }, "Requests"), /* @__PURE__ */ import_react4.default.createElement(import_TableCell2.default, { align: "right" }, "Success"), /* @__PURE__ */ import_react4.default.createElement(import_TableCell2.default, { align: "right" }, "Failed"), /* @__PURE__ */ import_react4.default.createElement(import_TableCell2.default, { align: "right" }, "Prompt"), /* @__PURE__ */ import_react4.default.createElement(import_TableCell2.default, { align: "right" }, "Completion"), /* @__PURE__ */ import_react4.default.createElement(import_TableCell2.default, { sx: { minWidth: 120 } }, "Success rate"))), /* @__PURE__ */ import_react4.default.createElement(import_TableBody2.default, null, renderModelRows()))))), tab === "keys" && /* @__PURE__ */ import_react4.default.createElement(import_Grid.default, { container: true, spacing: 3 }, (loading || topKeySpendBars.length > 0) && /* @__PURE__ */ import_react4.default.createElement(import_Grid.default, { item: true, xs: 12 }, /* @__PURE__ */ import_react4.default.createElement(import_Typography4.default, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Spend by Key"), loading ? /* @__PURE__ */ import_react4.default.createElement(ChartSkeleton, null) : /* @__PURE__ */ import_react4.default.createElement(import_Box4.default, { height: Math.max(160, topKeySpendBars.length * 36) }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.BarChart, { data: topKeySpendBars, layout: "vertical", margin: { left: 20 } }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.CartesianGrid, { strokeDasharray: "3 3", stroke: gridStroke }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.XAxis, { type: "number", tick: tickStyle, tickFormatter: (v) => `$${v.toFixed(2)}` }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.YAxis, { type: "category", dataKey: "keyAlias", tick: tickStyleSmall, width: 160 }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.Tooltip, { formatter: (v) => [fmtUsd(v), "Spend"] }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.Bar, { dataKey: "spend", name: "Spend", fill: "#8884d8", radius: [0, 4, 4, 0] }))))), /* @__PURE__ */ import_react4.default.createElement(import_Grid.default, { item: true, xs: 12 }, /* @__PURE__ */ import_react4.default.createElement(import_TableContainer2.default, { component: import_Paper3.default, variant: "outlined" }, /* @__PURE__ */ import_react4.default.createElement(import_Table2.default, { size: "small" }, /* @__PURE__ */ import_react4.default.createElement(import_TableHead2.default, null, /* @__PURE__ */ import_react4.default.createElement(import_TableRow2.default, null, /* @__PURE__ */ import_react4.default.createElement(import_TableCell2.default, null, "Key"), /* @__PURE__ */ import_react4.default.createElement(import_TableCell2.default, null, "Models"), /* @__PURE__ */ import_react4.default.createElement(import_TableCell2.default, { align: "right" }, "Spend"), /* @__PURE__ */ import_react4.default.createElement(import_TableCell2.default, { align: "right" }, "Requests"), /* @__PURE__ */ import_react4.default.createElement(import_TableCell2.default, { align: "right" }, "Tokens"), /* @__PURE__ */ import_react4.default.createElement(import_TableCell2.default, { align: "right" }, "Failed"), /* @__PURE__ */ import_react4.default.createElement(import_TableCell2.default, { sx: { minWidth: 120 } }, "Success rate"))), /* @__PURE__ */ import_react4.default.createElement(import_TableBody2.default, null, renderKeyRows()))))));
2057
+ /* @__PURE__ */ import_react5.default.createElement(MetricStrip, { metrics }),
2058
+ /* @__PURE__ */ import_react5.default.createElement(
2059
+ import_Box5.default,
2060
+ {
2061
+ sx: {
2062
+ display: "flex",
2063
+ alignItems: "center",
2064
+ justifyContent: "space-between",
2065
+ gap: 2,
2066
+ flexWrap: "wrap",
2067
+ mt: 2.5,
2068
+ mb: 2
2069
+ }
2070
+ },
2071
+ /* @__PURE__ */ import_react5.default.createElement(
2072
+ SegmentedControl,
2073
+ {
2074
+ value: tab,
2075
+ onChange: setTab,
2076
+ options: [
2077
+ { value: "costs", label: "Costs" },
2078
+ { value: "models", label: "Model Activity" },
2079
+ { value: "keys", label: "Key Activity" }
2080
+ ]
2081
+ }
2082
+ ),
2083
+ tab === "models" && /* @__PURE__ */ import_react5.default.createElement(
2084
+ import_TextField3.default,
2085
+ {
2086
+ select: true,
2087
+ size: "small",
2088
+ label: "Model",
2089
+ value: selectedModel,
2090
+ onChange: (e) => setSelectedModel(e.target.value),
2091
+ sx: { minWidth: 220 }
2092
+ },
2093
+ /* @__PURE__ */ import_react5.default.createElement(import_MenuItem2.default, { value: "all" }, "All models"),
2094
+ models.map((m) => /* @__PURE__ */ import_react5.default.createElement(import_MenuItem2.default, { key: m.model_name, value: m.model_name }, m.model_name))
2095
+ )
2096
+ ),
2097
+ tab === "costs" && /* @__PURE__ */ import_react5.default.createElement(import_Grid.default, { container: true, spacing: 2 }, /* @__PURE__ */ import_react5.default.createElement(import_Grid.default, { item: true, xs: 12 }, /* @__PURE__ */ import_react5.default.createElement(ChartCard, { title: "Daily spend by model", height: 260 }, /* @__PURE__ */ import_react5.default.createElement(ChartOrFallback, { loading, empty: modelSpendByDate.length === 0, height: 260 }, /* @__PURE__ */ import_react5.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react5.default.createElement(import_recharts.AreaChart, { data: modelSpendByDate, margin: { top: 4, right: 8, left: 0, bottom: 0 } }, /* @__PURE__ */ import_react5.default.createElement(import_recharts.CartesianGrid, { ...chart.grid }), /* @__PURE__ */ import_react5.default.createElement(import_recharts.XAxis, { dataKey: "date", tickFormatter: fmtDateShort, ...chart.axis }), /* @__PURE__ */ import_react5.default.createElement(import_recharts.YAxis, { tickFormatter: fmtUsdCompact, width: 56, ...chart.axis }), /* @__PURE__ */ import_react5.default.createElement(
2098
+ import_recharts.Tooltip,
2099
+ {
2100
+ cursor: chart.cursor,
2101
+ content: /* @__PURE__ */ import_react5.default.createElement(ChartTooltip, { valueFormatter: fmtUsd, hideZero: true })
2102
+ }
2103
+ ), topSpendModels.map((m) => /* @__PURE__ */ import_react5.default.createElement(
2104
+ import_recharts.Area,
2105
+ {
2106
+ key: m,
2107
+ type: "monotone",
2108
+ dataKey: m,
2109
+ stackId: "spend",
2110
+ stroke: chartColor(m),
2111
+ strokeWidth: 1.5,
2112
+ fill: chartColor(m),
2113
+ fillOpacity: 0.28
2114
+ }
2115
+ ))))), !loading && topSpendModels.length > 0 && /* @__PURE__ */ import_react5.default.createElement(SeriesLegend, { series: topSpendModels.map((m) => ({ name: m, color: chartColor(m) })) }))), /* @__PURE__ */ import_react5.default.createElement(import_Grid.default, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ import_react5.default.createElement(ChartCard, { title: "Daily token usage" }, /* @__PURE__ */ import_react5.default.createElement(ChartOrFallback, { loading, empty: dailyData.length === 0 }, /* @__PURE__ */ import_react5.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react5.default.createElement(import_recharts.AreaChart, { data: dailyData, margin: { top: 4, right: 8, left: 0, bottom: 0 } }, /* @__PURE__ */ import_react5.default.createElement(import_recharts.CartesianGrid, { ...chart.grid }), /* @__PURE__ */ import_react5.default.createElement(import_recharts.XAxis, { dataKey: "date", tickFormatter: fmtDateShort, ...chart.axis }), /* @__PURE__ */ import_react5.default.createElement(import_recharts.YAxis, { tickFormatter: fmtCompact, width: 48, ...chart.axis }), /* @__PURE__ */ import_react5.default.createElement(import_recharts.Tooltip, { cursor: chart.cursor, content: /* @__PURE__ */ import_react5.default.createElement(ChartTooltip, { valueFormatter: fmtInt }) }), /* @__PURE__ */ import_react5.default.createElement(import_recharts.Legend, { ...chart.legend }), /* @__PURE__ */ import_react5.default.createElement(import_recharts.Area, { type: "monotone", dataKey: "promptTokens", name: "Input", stackId: "tok", stroke: SERIES.input, strokeWidth: 1.5, fill: SERIES.input, fillOpacity: 0.25 }), /* @__PURE__ */ import_react5.default.createElement(import_recharts.Area, { type: "monotone", dataKey: "completionTokens", name: "Output", stackId: "tok", stroke: SERIES.output, strokeWidth: 1.5, fill: SERIES.output, fillOpacity: 0.25 })))))), /* @__PURE__ */ import_react5.default.createElement(import_Grid.default, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ import_react5.default.createElement(ChartCard, { title: "Daily requests" }, /* @__PURE__ */ import_react5.default.createElement(ChartOrFallback, { loading, empty: dailyData.length === 0 }, /* @__PURE__ */ import_react5.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react5.default.createElement(import_recharts.BarChart, { data: dailyData, margin: { top: 4, right: 8, left: 0, bottom: 0 }, barCategoryGap: "30%" }, /* @__PURE__ */ import_react5.default.createElement(import_recharts.CartesianGrid, { ...chart.grid }), /* @__PURE__ */ import_react5.default.createElement(import_recharts.XAxis, { dataKey: "date", tickFormatter: fmtDateShort, ...chart.axis }), /* @__PURE__ */ import_react5.default.createElement(import_recharts.YAxis, { tickFormatter: fmtCompact, width: 48, ...chart.axis }), /* @__PURE__ */ import_react5.default.createElement(import_recharts.Tooltip, { cursor: chart.cursor, content: /* @__PURE__ */ import_react5.default.createElement(ChartTooltip, { valueFormatter: fmtInt }) }), /* @__PURE__ */ import_react5.default.createElement(import_recharts.Legend, { ...chart.legend }), /* @__PURE__ */ import_react5.default.createElement(import_recharts.Bar, { dataKey: "successfulRequests", name: "Successful", fill: SERIES.success, stackId: "r" }), /* @__PURE__ */ import_react5.default.createElement(import_recharts.Bar, { dataKey: "failedRequests", name: "Failed", fill: SERIES.failure, stackId: "r", radius: [3, 3, 0, 0] })))))), /* @__PURE__ */ import_react5.default.createElement(import_Grid.default, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ import_react5.default.createElement(ChartCard, { title: "Daily success rate" }, /* @__PURE__ */ import_react5.default.createElement(ChartOrFallback, { loading, empty: successRateData.length === 0 }, /* @__PURE__ */ import_react5.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react5.default.createElement(import_recharts.LineChart, { data: successRateData, margin: { top: 4, right: 8, left: 0, bottom: 0 } }, /* @__PURE__ */ import_react5.default.createElement(import_recharts.CartesianGrid, { ...chart.grid }), /* @__PURE__ */ import_react5.default.createElement(import_recharts.XAxis, { dataKey: "date", tickFormatter: fmtDateShort, ...chart.axis }), /* @__PURE__ */ import_react5.default.createElement(import_recharts.YAxis, { domain: [0, 100], tickFormatter: (v) => `${v}%`, width: 44, ...chart.axis }), /* @__PURE__ */ import_react5.default.createElement(
2116
+ import_recharts.Tooltip,
2117
+ {
2118
+ cursor: chart.cursor,
2119
+ content: /* @__PURE__ */ import_react5.default.createElement(ChartTooltip, { valueFormatter: (v) => `${v}%` })
2120
+ }
2121
+ ), /* @__PURE__ */ import_react5.default.createElement(import_recharts.ReferenceLine, { y: 100, stroke: SERIES.success, strokeDasharray: "4 4", strokeOpacity: 0.5 }), /* @__PURE__ */ import_react5.default.createElement(
2122
+ import_recharts.Line,
2123
+ {
2124
+ type: "monotone",
2125
+ dataKey: "successRate",
2126
+ name: "Success rate",
2127
+ stroke: SERIES.input,
2128
+ strokeWidth: 2,
2129
+ dot: { r: 2.5, strokeWidth: 0, fill: SERIES.input },
2130
+ activeDot: { r: 4 }
2131
+ }
2132
+ )))))), /* @__PURE__ */ import_react5.default.createElement(import_Grid.default, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ import_react5.default.createElement(
2133
+ ChartCard,
2134
+ {
2135
+ title: maxBudget > 0 ? "Cumulative spend vs budget" : "Cumulative spend",
2136
+ meta: maxBudget > 0 ? `${fmtUsd(totalCumSpend)} used \xB7 ${fmtUsd(Math.max(0, maxBudget - totalCumSpend))} left` : void 0
2137
+ },
2138
+ /* @__PURE__ */ import_react5.default.createElement(ChartOrFallback, { loading, empty: cumulativeData.length === 0 }, /* @__PURE__ */ import_react5.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react5.default.createElement(import_recharts.AreaChart, { data: cumulativeData, margin: { top: 4, right: 8, left: 0, bottom: 0 } }, /* @__PURE__ */ import_react5.default.createElement(import_recharts.CartesianGrid, { ...chart.grid }), /* @__PURE__ */ import_react5.default.createElement(import_recharts.XAxis, { dataKey: "date", tickFormatter: fmtDateShort, ...chart.axis }), /* @__PURE__ */ import_react5.default.createElement(
2139
+ import_recharts.YAxis,
2140
+ {
2141
+ tickFormatter: fmtUsdCompact,
2142
+ width: 56,
2143
+ domain: cumulativeDomain,
2144
+ ...chart.axis
2145
+ }
2146
+ ), /* @__PURE__ */ import_react5.default.createElement(
2147
+ import_recharts.Tooltip,
2148
+ {
2149
+ cursor: chart.cursor,
2150
+ content: /* @__PURE__ */ import_react5.default.createElement(ChartTooltip, { valueFormatter: fmtUsd })
2151
+ }
2152
+ ), maxBudget > 0 && /* @__PURE__ */ import_react5.default.createElement(
2153
+ import_recharts.ReferenceLine,
2154
+ {
2155
+ y: maxBudget,
2156
+ stroke: SERIES.budget,
2157
+ strokeDasharray: "5 4",
2158
+ label: { value: `Budget ${fmtUsd(maxBudget)}`, position: "insideTopRight", fontSize: 10, fill: SERIES.budget }
2159
+ }
2160
+ ), /* @__PURE__ */ import_react5.default.createElement(
2161
+ import_recharts.Area,
2162
+ {
2163
+ type: "monotone",
2164
+ dataKey: "cumulative",
2165
+ name: "Cumulative spend",
2166
+ stroke: SERIES.spend,
2167
+ strokeWidth: 2,
2168
+ fill: SERIES.spend,
2169
+ fillOpacity: 0.18
2170
+ }
2171
+ ))))
2172
+ ))),
2173
+ tab === "models" && /* @__PURE__ */ import_react5.default.createElement(import_Grid.default, { container: true, spacing: 2 }, /* @__PURE__ */ import_react5.default.createElement(import_Grid.default, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ import_react5.default.createElement(ChartCard, { title: "Spend by model", height: Math.max(200, topModelSpendBars.length * 34) }, /* @__PURE__ */ import_react5.default.createElement(
2174
+ ChartOrFallback,
2175
+ {
2176
+ loading,
2177
+ empty: topModelSpendBars.length === 0,
2178
+ height: Math.max(200, topModelSpendBars.length * 34)
2179
+ },
2180
+ /* @__PURE__ */ import_react5.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react5.default.createElement(import_recharts.BarChart, { data: topModelSpendBars, layout: "vertical", margin: { top: 0, right: 12, left: 0, bottom: 0 } }, /* @__PURE__ */ import_react5.default.createElement(import_recharts.CartesianGrid, { ...chart.grid, vertical: true, horizontal: false }), /* @__PURE__ */ import_react5.default.createElement(import_recharts.XAxis, { type: "number", tickFormatter: fmtUsdCompact, ...chart.axis }), /* @__PURE__ */ import_react5.default.createElement(import_recharts.YAxis, { type: "category", dataKey: "model", width: 170, ...chart.axis, tick: { fontSize: 10.5, fill: chart.axis.tick.fill } }), /* @__PURE__ */ import_react5.default.createElement(import_recharts.Tooltip, { cursor: chart.cursor, content: /* @__PURE__ */ import_react5.default.createElement(ChartTooltip, { valueFormatter: fmtUsd, labelFormatter: (l) => l }) }), /* @__PURE__ */ import_react5.default.createElement(import_recharts.Bar, { dataKey: "spend", name: "Spend", radius: [0, 3, 3, 0], barSize: 14 }, topModelSpendBars.map((r) => /* @__PURE__ */ import_react5.default.createElement(import_recharts.Cell, { key: r.model, fill: chartColor(r.model) })))))
2181
+ ))), /* @__PURE__ */ import_react5.default.createElement(import_Grid.default, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ import_react5.default.createElement(ChartCard, { title: "Tokens by model", height: Math.max(200, topModelSpendBars.length * 34) }, /* @__PURE__ */ import_react5.default.createElement(
2182
+ ChartOrFallback,
2183
+ {
2184
+ loading,
2185
+ empty: modelRows.length === 0,
2186
+ height: Math.max(200, topModelSpendBars.length * 34)
2187
+ },
2188
+ /* @__PURE__ */ import_react5.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react5.default.createElement(import_recharts.BarChart, { data: topModelSpendBars, layout: "vertical", margin: { top: 0, right: 12, left: 0, bottom: 0 } }, /* @__PURE__ */ import_react5.default.createElement(import_recharts.CartesianGrid, { ...chart.grid, vertical: true, horizontal: false }), /* @__PURE__ */ import_react5.default.createElement(import_recharts.XAxis, { type: "number", tickFormatter: fmtCompact, ...chart.axis }), /* @__PURE__ */ import_react5.default.createElement(import_recharts.YAxis, { type: "category", dataKey: "model", width: 170, ...chart.axis, tick: { fontSize: 10.5, fill: chart.axis.tick.fill } }), /* @__PURE__ */ import_react5.default.createElement(import_recharts.Tooltip, { cursor: chart.cursor, content: /* @__PURE__ */ import_react5.default.createElement(ChartTooltip, { valueFormatter: fmtInt, labelFormatter: (l) => l }) }), /* @__PURE__ */ import_react5.default.createElement(import_recharts.Legend, { ...chart.legend }), /* @__PURE__ */ import_react5.default.createElement(import_recharts.Bar, { dataKey: "promptTokens", name: "Input", fill: SERIES.input, stackId: "t", barSize: 14 }), /* @__PURE__ */ import_react5.default.createElement(import_recharts.Bar, { dataKey: "completionTokens", name: "Output", fill: SERIES.output, stackId: "t", barSize: 14, radius: [0, 3, 3, 0] })))
2189
+ ))), /* @__PURE__ */ import_react5.default.createElement(import_Grid.default, { item: true, xs: 12 }, /* @__PURE__ */ import_react5.default.createElement(import_TableContainer2.default, { component: import_Paper3.default, variant: "outlined", sx: { borderRadius: 2 } }, /* @__PURE__ */ import_react5.default.createElement(import_Table2.default, { size: "small", sx: dataTableSx }, /* @__PURE__ */ import_react5.default.createElement(import_TableHead2.default, null, /* @__PURE__ */ import_react5.default.createElement(import_TableRow2.default, null, /* @__PURE__ */ import_react5.default.createElement(import_TableCell2.default, null, "Model"), /* @__PURE__ */ import_react5.default.createElement(import_TableCell2.default, { align: "right" }, "Spend"), /* @__PURE__ */ import_react5.default.createElement(import_TableCell2.default, { align: "right" }, "Requests"), /* @__PURE__ */ import_react5.default.createElement(import_TableCell2.default, { align: "right" }, "Success"), /* @__PURE__ */ import_react5.default.createElement(import_TableCell2.default, { align: "right" }, "Failed"), /* @__PURE__ */ import_react5.default.createElement(import_TableCell2.default, { align: "right" }, "Input"), /* @__PURE__ */ import_react5.default.createElement(import_TableCell2.default, { align: "right" }, "Output"), /* @__PURE__ */ import_react5.default.createElement(import_TableCell2.default, { sx: { width: 160 } }, "Success rate"))), /* @__PURE__ */ import_react5.default.createElement(import_TableBody2.default, null, renderModelRows()))))),
2190
+ tab === "keys" && /* @__PURE__ */ import_react5.default.createElement(import_Grid.default, { container: true, spacing: 2 }, (loading || topKeySpendBars.length > 0) && /* @__PURE__ */ import_react5.default.createElement(import_Grid.default, { item: true, xs: 12 }, /* @__PURE__ */ import_react5.default.createElement(ChartCard, { title: "Spend by key", height: Math.max(160, topKeySpendBars.length * 32) }, /* @__PURE__ */ import_react5.default.createElement(
2191
+ ChartOrFallback,
2192
+ {
2193
+ loading,
2194
+ empty: topKeySpendBars.length === 0,
2195
+ height: Math.max(160, topKeySpendBars.length * 32)
2196
+ },
2197
+ /* @__PURE__ */ import_react5.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react5.default.createElement(import_recharts.BarChart, { data: topKeySpendBars, layout: "vertical", margin: { top: 0, right: 12, left: 0, bottom: 0 } }, /* @__PURE__ */ import_react5.default.createElement(import_recharts.CartesianGrid, { ...chart.grid, vertical: true, horizontal: false }), /* @__PURE__ */ import_react5.default.createElement(import_recharts.XAxis, { type: "number", tickFormatter: fmtUsdCompact, ...chart.axis }), /* @__PURE__ */ import_react5.default.createElement(import_recharts.YAxis, { type: "category", dataKey: "keyAlias", width: 160, ...chart.axis, tick: { fontSize: 10.5, fill: chart.axis.tick.fill } }), /* @__PURE__ */ import_react5.default.createElement(import_recharts.Tooltip, { cursor: chart.cursor, content: /* @__PURE__ */ import_react5.default.createElement(ChartTooltip, { valueFormatter: fmtUsd, labelFormatter: (l) => l }) }), /* @__PURE__ */ import_react5.default.createElement(import_recharts.Bar, { dataKey: "spend", name: "Spend", fill: SERIES.input, radius: [0, 3, 3, 0], barSize: 14 })))
2198
+ ))), /* @__PURE__ */ import_react5.default.createElement(import_Grid.default, { item: true, xs: 12 }, /* @__PURE__ */ import_react5.default.createElement(import_TableContainer2.default, { component: import_Paper3.default, variant: "outlined", sx: { borderRadius: 2 } }, /* @__PURE__ */ import_react5.default.createElement(import_Table2.default, { size: "small", sx: dataTableSx }, /* @__PURE__ */ import_react5.default.createElement(import_TableHead2.default, null, /* @__PURE__ */ import_react5.default.createElement(import_TableRow2.default, null, /* @__PURE__ */ import_react5.default.createElement(import_TableCell2.default, null, "Key"), /* @__PURE__ */ import_react5.default.createElement(import_TableCell2.default, null, "Models"), /* @__PURE__ */ import_react5.default.createElement(import_TableCell2.default, { align: "right" }, "Spend"), /* @__PURE__ */ import_react5.default.createElement(import_TableCell2.default, { align: "right" }, "Requests"), /* @__PURE__ */ import_react5.default.createElement(import_TableCell2.default, { align: "right" }, "Tokens"), /* @__PURE__ */ import_react5.default.createElement(import_TableCell2.default, { align: "right" }, "Failed"), /* @__PURE__ */ import_react5.default.createElement(import_TableCell2.default, { sx: { width: 160 } }, "Success rate"))), /* @__PURE__ */ import_react5.default.createElement(import_TableBody2.default, null, renderKeyRows())))))
2199
+ );
1321
2200
  };
1322
2201
  }
1323
2202
  });
1324
2203
 
1325
2204
  // src/components/TeamUsage.tsx
1326
- function budgetBarColor(isOver, isNear) {
1327
- if (isOver) return "error";
2205
+ function budgetTone2(isOver, isNear) {
2206
+ if (isOver) return "danger";
1328
2207
  if (isNear) return "warning";
1329
- return "primary";
2208
+ return "accent";
1330
2209
  }
1331
- var import_react5, import_Paper4, import_Box5, import_Typography5, import_LinearProgress4, import_Chip4, import_Divider, import_Stack2, import_Table3, import_TableBody3, import_TableCell3, import_TableHead3, import_TableRow3, import_TableContainer3, import_Collapse, import_IconButton3, import_CircularProgress3, import_styles2, import_icons_material4, import_recharts2, Stat, TeamCard, TeamUsage;
2210
+ var import_react6, import_Paper4, import_Box6, import_Typography6, import_Skeleton4, import_Divider2, import_Table3, import_TableBody3, import_TableCell3, import_TableHead3, import_TableRow3, import_TableContainer3, import_Collapse, import_IconButton3, import_CircularProgress3, import_styles4, import_icons_material4, import_recharts2, fmtUsd2, TeamCard, TeamUsage;
1332
2211
  var init_TeamUsage = __esm({
1333
2212
  "src/components/TeamUsage.tsx"() {
1334
2213
  "use strict";
1335
- import_react5 = __toESM(require("react"));
2214
+ import_react6 = __toESM(require("react"));
1336
2215
  import_Paper4 = __toESM(require("@mui/material/Paper"));
1337
- import_Box5 = __toESM(require("@mui/material/Box"));
1338
- import_Typography5 = __toESM(require("@mui/material/Typography"));
1339
- import_LinearProgress4 = __toESM(require("@mui/material/LinearProgress"));
1340
- import_Chip4 = __toESM(require("@mui/material/Chip"));
1341
- import_Divider = __toESM(require("@mui/material/Divider"));
1342
- import_Stack2 = __toESM(require("@mui/material/Stack"));
2216
+ import_Box6 = __toESM(require("@mui/material/Box"));
2217
+ import_Typography6 = __toESM(require("@mui/material/Typography"));
2218
+ import_Skeleton4 = __toESM(require("@mui/material/Skeleton"));
2219
+ import_Divider2 = __toESM(require("@mui/material/Divider"));
1343
2220
  import_Table3 = __toESM(require("@mui/material/Table"));
1344
2221
  import_TableBody3 = __toESM(require("@mui/material/TableBody"));
1345
2222
  import_TableCell3 = __toESM(require("@mui/material/TableCell"));
@@ -1349,15 +2226,14 @@ var init_TeamUsage = __esm({
1349
2226
  import_Collapse = __toESM(require("@mui/material/Collapse"));
1350
2227
  import_IconButton3 = __toESM(require("@mui/material/IconButton"));
1351
2228
  import_CircularProgress3 = __toESM(require("@mui/material/CircularProgress"));
1352
- import_styles2 = require("@mui/material/styles");
2229
+ import_styles4 = require("@mui/material/styles");
1353
2230
  import_icons_material4 = require("@mui/icons-material");
1354
2231
  import_recharts2 = require("recharts");
1355
- Stat = ({ label, value }) => /* @__PURE__ */ import_react5.default.createElement(import_Box5.default, null, /* @__PURE__ */ import_react5.default.createElement(import_Typography5.default, { variant: "caption", color: "text.secondary", display: "block" }, label), /* @__PURE__ */ import_react5.default.createElement(import_Typography5.default, { variant: "body2", fontWeight: 600, sx: { fontFamily: "monospace" } }, value));
2232
+ init_ui();
2233
+ fmtUsd2 = (n) => `$${(n ?? 0).toFixed(2)}`;
1356
2234
  TeamCard = ({ team, usage, usageLoading }) => {
1357
- const [expanded, setExpanded] = (0, import_react5.useState)(false);
1358
- const theme = (0, import_styles2.useTheme)();
1359
- const gridStroke = theme.palette.divider;
1360
- const tickFill = theme.palette.text.secondary;
2235
+ const [expanded, setExpanded] = (0, import_react6.useState)(false);
2236
+ const chart = useChartTheme();
1361
2237
  const budget = team.max_budget ?? 0;
1362
2238
  const spend = team.spend ?? 0;
1363
2239
  const budgetPct = budget > 0 ? Math.min(spend / budget * 100, 100) : 0;
@@ -1366,73 +2242,131 @@ var init_TeamUsage = __esm({
1366
2242
  const dailyData = usage?.daily_usage?.map((d) => ({ date: d.date, spend: d.spend })) ?? [];
1367
2243
  const renderDailySpendSection = () => {
1368
2244
  if (usageLoading) {
1369
- return /* @__PURE__ */ import_react5.default.createElement(import_Box5.default, { display: "flex", justifyContent: "center", p: 2 }, /* @__PURE__ */ import_react5.default.createElement(import_CircularProgress3.default, { size: 24 }));
1370
- }
1371
- if (dailyData.length > 0) {
1372
- return /* @__PURE__ */ import_react5.default.createElement(import_react5.default.Fragment, null, /* @__PURE__ */ import_react5.default.createElement(import_Typography5.default, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Daily Spend"), /* @__PURE__ */ import_react5.default.createElement(import_Box5.default, { height: 160, mb: 2 }, /* @__PURE__ */ import_react5.default.createElement(import_recharts2.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react5.default.createElement(import_recharts2.AreaChart, { data: dailyData }, /* @__PURE__ */ import_react5.default.createElement(import_recharts2.CartesianGrid, { strokeDasharray: "3 3", stroke: gridStroke }), /* @__PURE__ */ import_react5.default.createElement(import_recharts2.XAxis, { dataKey: "date", tick: { fontSize: 11, fill: tickFill } }), /* @__PURE__ */ import_react5.default.createElement(import_recharts2.YAxis, { tick: { fontSize: 11, fill: tickFill }, tickFormatter: (v) => `$${v.toFixed(2)}` }), /* @__PURE__ */ import_react5.default.createElement(import_recharts2.Tooltip, { formatter: (v) => [`$${v.toFixed(4)}`, "Spend"] }), /* @__PURE__ */ import_react5.default.createElement(import_recharts2.Area, { type: "monotone", dataKey: "spend", stroke: "#8884d8", fill: "#8884d8", fillOpacity: 0.3 })))), /* @__PURE__ */ import_react5.default.createElement(import_Divider.default, { sx: { mb: 2 } }));
2245
+ return /* @__PURE__ */ import_react6.default.createElement(import_Box6.default, { display: "flex", justifyContent: "center", py: 3 }, /* @__PURE__ */ import_react6.default.createElement(import_CircularProgress3.default, { size: 22 }));
1373
2246
  }
1374
- return null;
2247
+ if (dailyData.length === 0) return null;
2248
+ return /* @__PURE__ */ import_react6.default.createElement(import_Box6.default, { mb: 2.5 }, /* @__PURE__ */ import_react6.default.createElement(
2249
+ import_Typography6.default,
2250
+ {
2251
+ variant: "caption",
2252
+ color: "text.secondary",
2253
+ sx: { display: "block", fontSize: 11, fontWeight: 700, letterSpacing: "0.07em", textTransform: "uppercase", mb: 1 }
2254
+ },
2255
+ "Daily spend"
2256
+ ), /* @__PURE__ */ import_react6.default.createElement(import_Box6.default, { height: 150 }, /* @__PURE__ */ import_react6.default.createElement(import_recharts2.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react6.default.createElement(import_recharts2.AreaChart, { data: dailyData, margin: { top: 4, right: 8, left: 0, bottom: 0 } }, /* @__PURE__ */ import_react6.default.createElement(import_recharts2.CartesianGrid, { ...chart.grid }), /* @__PURE__ */ import_react6.default.createElement(import_recharts2.XAxis, { dataKey: "date", tickFormatter: fmtDateShort, ...chart.axis }), /* @__PURE__ */ import_react6.default.createElement(import_recharts2.YAxis, { tickFormatter: fmtUsdCompact, width: 52, ...chart.axis }), /* @__PURE__ */ import_react6.default.createElement(
2257
+ import_recharts2.Tooltip,
2258
+ {
2259
+ cursor: chart.cursor,
2260
+ content: /* @__PURE__ */ import_react6.default.createElement(ChartTooltip, { valueFormatter: (v) => `$${v.toFixed(4)}` })
2261
+ }
2262
+ ), /* @__PURE__ */ import_react6.default.createElement(
2263
+ import_recharts2.Area,
2264
+ {
2265
+ type: "monotone",
2266
+ dataKey: "spend",
2267
+ name: "Spend",
2268
+ stroke: SERIES.spend,
2269
+ strokeWidth: 2,
2270
+ fill: SERIES.spend,
2271
+ fillOpacity: 0.18
2272
+ }
2273
+ )))));
1375
2274
  };
1376
- return /* @__PURE__ */ import_react5.default.createElement(import_Paper4.default, { variant: "outlined", sx: { p: 2, mb: 2 } }, /* @__PURE__ */ import_react5.default.createElement(import_Box5.default, { display: "flex", alignItems: "center", gap: 1 }, /* @__PURE__ */ import_react5.default.createElement(import_icons_material4.Group, { color: "action" }), /* @__PURE__ */ import_react5.default.createElement(import_Box5.default, { flexGrow: 1 }, /* @__PURE__ */ import_react5.default.createElement(import_Typography5.default, { variant: "subtitle1", fontWeight: 600 }, team.team_alias || "Untitled team"), /* @__PURE__ */ import_react5.default.createElement(import_Typography5.default, { variant: "caption", color: "text.secondary", sx: { fontFamily: "monospace" } }, team.team_id)), /* @__PURE__ */ import_react5.default.createElement(import_IconButton3.default, { size: "small", onClick: () => setExpanded((e) => !e), "aria-label": "toggle details" }, expanded ? /* @__PURE__ */ import_react5.default.createElement(import_icons_material4.ExpandLess, null) : /* @__PURE__ */ import_react5.default.createElement(import_icons_material4.ExpandMore, null))), /* @__PURE__ */ import_react5.default.createElement(import_Stack2.default, { direction: "row", spacing: 3, mt: 1.5, flexWrap: "wrap", useFlexGap: true, gap: 1.5 }, /* @__PURE__ */ import_react5.default.createElement(
1377
- Stat,
1378
- {
1379
- label: "Members",
1380
- value: team.members_with_roles?.length ? String(team.members_with_roles.length) : "\u2014"
1381
- }
1382
- ), /* @__PURE__ */ import_react5.default.createElement(
1383
- Stat,
2275
+ const sectionLabel = (label) => /* @__PURE__ */ import_react6.default.createElement(
2276
+ import_Typography6.default,
1384
2277
  {
1385
- label: "Models",
1386
- value: team.models?.length ? String(team.models.length) : "All"
1387
- }
1388
- ), /* @__PURE__ */ import_react5.default.createElement(
1389
- Stat,
1390
- {
1391
- label: "Budget",
1392
- value: budget > 0 ? `$${budget.toFixed(2)}` : "Unlimited"
1393
- }
1394
- ), /* @__PURE__ */ import_react5.default.createElement(
1395
- Stat,
1396
- {
1397
- label: "Spend",
1398
- value: `$${spend.toFixed(2)}`
1399
- }
1400
- ), /* @__PURE__ */ import_react5.default.createElement(
1401
- Stat,
1402
- {
1403
- label: "TPM",
1404
- value: typeof team.tpm_limit === "number" && team.tpm_limit > 0 ? String(team.tpm_limit) : "\u2014"
1405
- }
1406
- ), /* @__PURE__ */ import_react5.default.createElement(
1407
- Stat,
1408
- {
1409
- label: "RPM",
1410
- value: typeof team.rpm_limit === "number" && team.rpm_limit > 0 ? String(team.rpm_limit) : "\u2014"
1411
- }
1412
- )), budget > 0 && /* @__PURE__ */ import_react5.default.createElement(import_Box5.default, { mt: 1.5 }, /* @__PURE__ */ import_react5.default.createElement(import_Box5.default, { display: "flex", justifyContent: "space-between", mb: 0.5 }, /* @__PURE__ */ import_react5.default.createElement(import_Typography5.default, { variant: "body2" }, "$", spend.toFixed(2), " / $", budget.toFixed(2)), isOver && /* @__PURE__ */ import_react5.default.createElement(import_Chip4.default, { label: "Over Budget", size: "small", color: "error" }), isNear && /* @__PURE__ */ import_react5.default.createElement(import_Chip4.default, { label: "Near Limit", size: "small", color: "warning" })), /* @__PURE__ */ import_react5.default.createElement(
1413
- import_LinearProgress4.default,
2278
+ variant: "caption",
2279
+ color: "text.secondary",
2280
+ sx: { display: "block", fontSize: 11, fontWeight: 700, letterSpacing: "0.07em", textTransform: "uppercase", mb: 1 }
2281
+ },
2282
+ label
2283
+ );
2284
+ return /* @__PURE__ */ import_react6.default.createElement(import_Paper4.default, { variant: "outlined", sx: { borderRadius: 2, p: 2.5 } }, /* @__PURE__ */ import_react6.default.createElement(import_Box6.default, { display: "flex", alignItems: "center", gap: 1.5 }, /* @__PURE__ */ import_react6.default.createElement(
2285
+ import_Box6.default,
1414
2286
  {
1415
- variant: "determinate",
1416
- value: budgetPct,
1417
- color: budgetBarColor(isOver, isNear),
1418
- sx: { height: 6, borderRadius: 1 }
1419
- }
1420
- )), /* @__PURE__ */ import_react5.default.createElement(import_Collapse.default, { in: expanded }, /* @__PURE__ */ import_react5.default.createElement(import_Box5.default, { mt: 2 }, /* @__PURE__ */ import_react5.default.createElement(import_Box5.default, { display: "flex", alignItems: "center", gap: 1, mb: 1 }, /* @__PURE__ */ import_react5.default.createElement(import_icons_material4.Speed, { fontSize: "small", color: "action" }), /* @__PURE__ */ import_react5.default.createElement(import_Typography5.default, { variant: "subtitle2", color: "text.secondary" }, "Rate limits")), /* @__PURE__ */ import_react5.default.createElement(import_Stack2.default, { direction: "row", spacing: 3, flexWrap: "wrap", useFlexGap: true, gap: 1.5, mb: 2 }, /* @__PURE__ */ import_react5.default.createElement(Stat, { label: "TPM limit", value: typeof team.tpm_limit === "number" && team.tpm_limit > 0 ? String(team.tpm_limit) : "Unlimited" }), /* @__PURE__ */ import_react5.default.createElement(Stat, { label: "RPM limit", value: typeof team.rpm_limit === "number" && team.rpm_limit > 0 ? String(team.rpm_limit) : "Unlimited" }), /* @__PURE__ */ import_react5.default.createElement(Stat, { label: "Max budget", value: budget > 0 ? `$${budget.toFixed(2)}` : "Unlimited" })), /* @__PURE__ */ import_react5.default.createElement(import_Divider.default, { sx: { mb: 2 } }), /* @__PURE__ */ import_react5.default.createElement(import_Typography5.default, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Models"), team.models?.length ? /* @__PURE__ */ import_react5.default.createElement(import_Box5.default, { display: "flex", gap: 0.5, flexWrap: "wrap", mb: 2 }, team.models.map((m) => /* @__PURE__ */ import_react5.default.createElement(import_Chip4.default, { key: m, label: m, size: "small", variant: "outlined" }))) : /* @__PURE__ */ import_react5.default.createElement(import_Typography5.default, { variant: "body2", color: "text.secondary", mb: 2 }, "All models allowed"), /* @__PURE__ */ import_react5.default.createElement(import_Divider.default, { sx: { mb: 2 } }), renderDailySpendSection(), /* @__PURE__ */ import_react5.default.createElement(import_Box5.default, { display: "flex", alignItems: "center", gap: 1, mb: 1 }, /* @__PURE__ */ import_react5.default.createElement(import_icons_material4.Memory, { fontSize: "small", color: "action" }), /* @__PURE__ */ import_react5.default.createElement(import_Typography5.default, { variant: "subtitle2", color: "text.secondary" }, "Members")), team.members_with_roles?.length ? /* @__PURE__ */ import_react5.default.createElement(import_TableContainer3.default, null, /* @__PURE__ */ import_react5.default.createElement(import_Table3.default, { size: "small" }, /* @__PURE__ */ import_react5.default.createElement(import_TableHead3.default, null, /* @__PURE__ */ import_react5.default.createElement(import_TableRow3.default, null, /* @__PURE__ */ import_react5.default.createElement(import_TableCell3.default, null, "User"), /* @__PURE__ */ import_react5.default.createElement(import_TableCell3.default, null, "Role"))), /* @__PURE__ */ import_react5.default.createElement(import_TableBody3.default, null, team.members_with_roles.map((m) => /* @__PURE__ */ import_react5.default.createElement(import_TableRow3.default, { key: m.user_id }, /* @__PURE__ */ import_react5.default.createElement(import_TableCell3.default, null, m.user_email ? /* @__PURE__ */ import_react5.default.createElement(import_react5.default.Fragment, null, /* @__PURE__ */ import_react5.default.createElement(import_Typography5.default, { variant: "body2" }, m.user_email), /* @__PURE__ */ import_react5.default.createElement(
1421
- import_Typography5.default,
2287
+ sx: (theme) => ({
2288
+ width: 36,
2289
+ height: 36,
2290
+ borderRadius: 1.5,
2291
+ flexShrink: 0,
2292
+ display: "flex",
2293
+ alignItems: "center",
2294
+ justifyContent: "center",
2295
+ bgcolor: (0, import_styles4.alpha)(theme.palette.primary.main, 0.1),
2296
+ color: theme.palette.primary.main
2297
+ })
2298
+ },
2299
+ /* @__PURE__ */ import_react6.default.createElement(import_icons_material4.Group, { fontSize: "small" })
2300
+ ), /* @__PURE__ */ import_react6.default.createElement(import_Box6.default, { flexGrow: 1, minWidth: 0 }, /* @__PURE__ */ import_react6.default.createElement(import_Typography6.default, { variant: "subtitle1", sx: { fontWeight: 600, lineHeight: 1.3 } }, team.team_alias || "Untitled team"), /* @__PURE__ */ import_react6.default.createElement(
2301
+ import_Typography6.default,
1422
2302
  {
1423
2303
  variant: "caption",
1424
2304
  color: "text.secondary",
1425
- sx: { fontFamily: "monospace" }
2305
+ sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 11 }
1426
2306
  },
1427
- m.user_id
1428
- )) : /* @__PURE__ */ import_react5.default.createElement(import_Typography5.default, { variant: "body2", sx: { fontFamily: "monospace" } }, m.user_id)), /* @__PURE__ */ import_react5.default.createElement(import_TableCell3.default, null, /* @__PURE__ */ import_react5.default.createElement(
1429
- import_Chip4.default,
2307
+ team.team_id
2308
+ )), isOver && /* @__PURE__ */ import_react6.default.createElement(StatusPill, { label: "Over budget", tone: "danger" }), isNear && /* @__PURE__ */ import_react6.default.createElement(StatusPill, { label: "Near limit", tone: "warning" }), /* @__PURE__ */ import_react6.default.createElement(
2309
+ import_IconButton3.default,
1430
2310
  {
1431
- label: m.role,
1432
2311
  size: "small",
1433
- color: m.role === "admin" ? "primary" : "default"
1434
- }
1435
- ))))))) : /* @__PURE__ */ import_react5.default.createElement(import_Typography5.default, { variant: "body2", color: "text.secondary" }, "No members assigned."))));
2312
+ onClick: () => setExpanded((e) => !e),
2313
+ "aria-label": expanded ? "Hide team details" : "Show team details",
2314
+ "aria-expanded": expanded,
2315
+ sx: (theme) => ({
2316
+ color: theme.palette.text.secondary,
2317
+ transform: expanded ? "rotate(180deg)" : "none",
2318
+ transition: theme.transitions.create("transform")
2319
+ })
2320
+ },
2321
+ /* @__PURE__ */ import_react6.default.createElement(import_icons_material4.ExpandMore, null)
2322
+ )), /* @__PURE__ */ import_react6.default.createElement(
2323
+ import_Box6.default,
2324
+ {
2325
+ sx: {
2326
+ display: "grid",
2327
+ gridTemplateColumns: { xs: "repeat(3, minmax(0, 1fr))", sm: "repeat(6, minmax(0, 1fr))" },
2328
+ gap: 2,
2329
+ mt: 2.5,
2330
+ maxWidth: 640
2331
+ }
2332
+ },
2333
+ /* @__PURE__ */ import_react6.default.createElement(Stat, { label: "Members", value: team.members_with_roles?.length ?? "\u2014" }),
2334
+ /* @__PURE__ */ import_react6.default.createElement(Stat, { label: "Models", value: team.models?.length ? team.models.length : "All" }),
2335
+ /* @__PURE__ */ import_react6.default.createElement(Stat, { label: "Budget", value: budget > 0 ? fmtUsd2(budget) : "Unlimited" }),
2336
+ /* @__PURE__ */ import_react6.default.createElement(Stat, { label: "Spend", value: fmtUsd2(spend) }),
2337
+ /* @__PURE__ */ import_react6.default.createElement(Stat, { label: "TPM", value: team.tpm_limit && team.tpm_limit > 0 ? team.tpm_limit.toLocaleString() : "\u2014" }),
2338
+ /* @__PURE__ */ import_react6.default.createElement(Stat, { label: "RPM", value: team.rpm_limit && team.rpm_limit > 0 ? team.rpm_limit.toLocaleString() : "\u2014" })
2339
+ ), budget > 0 && /* @__PURE__ */ import_react6.default.createElement(import_Box6.default, { mt: 2, maxWidth: 640 }, /* @__PURE__ */ import_react6.default.createElement(import_Box6.default, { display: "flex", justifyContent: "space-between", alignItems: "baseline", mb: 0.75 }, /* @__PURE__ */ import_react6.default.createElement(import_Typography6.default, { variant: "caption", color: "text.secondary", sx: { fontVariantNumeric: "tabular-nums" } }, fmtUsd2(spend), " of ", fmtUsd2(budget)), /* @__PURE__ */ import_react6.default.createElement(import_Typography6.default, { variant: "caption", color: "text.secondary", sx: { fontVariantNumeric: "tabular-nums" } }, budgetPct.toFixed(0), "%")), /* @__PURE__ */ import_react6.default.createElement(Meter, { value: budgetPct, tone: budgetTone2(isOver, isNear), height: 5 })), /* @__PURE__ */ import_react6.default.createElement(import_Collapse.default, { in: expanded, unmountOnExit: true }, /* @__PURE__ */ import_react6.default.createElement(import_Divider2.default, { sx: { my: 2.5 } }), /* @__PURE__ */ import_react6.default.createElement(import_Box6.default, { mb: 2.5 }, sectionLabel("Models"), team.models?.length ? /* @__PURE__ */ import_react6.default.createElement(import_Box6.default, { display: "flex", gap: 0.5, flexWrap: "wrap" }, team.models.map((m) => /* @__PURE__ */ import_react6.default.createElement(TagChip, { key: m, label: m, title: m }))) : /* @__PURE__ */ import_react6.default.createElement(import_Typography6.default, { variant: "body2", color: "text.secondary" }, "All models allowed")), renderDailySpendSection(), /* @__PURE__ */ import_react6.default.createElement(import_Box6.default, null, sectionLabel("Members"), team.members_with_roles?.length ? /* @__PURE__ */ import_react6.default.createElement(
2340
+ import_TableContainer3.default,
2341
+ {
2342
+ component: import_Paper4.default,
2343
+ variant: "outlined",
2344
+ sx: { borderRadius: 1.5 }
2345
+ },
2346
+ /* @__PURE__ */ import_react6.default.createElement(import_Table3.default, { size: "small", sx: dataTableSx }, /* @__PURE__ */ import_react6.default.createElement(import_TableHead3.default, null, /* @__PURE__ */ import_react6.default.createElement(import_TableRow3.default, null, /* @__PURE__ */ import_react6.default.createElement(import_TableCell3.default, null, "User"), /* @__PURE__ */ import_react6.default.createElement(import_TableCell3.default, { align: "right" }, "Role"))), /* @__PURE__ */ import_react6.default.createElement(import_TableBody3.default, null, team.members_with_roles.map((m) => /* @__PURE__ */ import_react6.default.createElement(import_TableRow3.default, { key: m.user_id }, /* @__PURE__ */ import_react6.default.createElement(import_TableCell3.default, null, m.user_email ? /* @__PURE__ */ import_react6.default.createElement(import_react6.default.Fragment, null, /* @__PURE__ */ import_react6.default.createElement(import_Typography6.default, { variant: "body2" }, m.user_email), /* @__PURE__ */ import_react6.default.createElement(
2347
+ import_Typography6.default,
2348
+ {
2349
+ variant: "caption",
2350
+ color: "text.secondary",
2351
+ sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 11 }
2352
+ },
2353
+ m.user_id
2354
+ )) : /* @__PURE__ */ import_react6.default.createElement(
2355
+ import_Typography6.default,
2356
+ {
2357
+ variant: "body2",
2358
+ sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace" }
2359
+ },
2360
+ m.user_id
2361
+ )), /* @__PURE__ */ import_react6.default.createElement(import_TableCell3.default, { align: "right" }, /* @__PURE__ */ import_react6.default.createElement(
2362
+ StatusPill,
2363
+ {
2364
+ label: m.role,
2365
+ tone: m.role === "admin" ? "accent" : "neutral",
2366
+ dot: false
2367
+ }
2368
+ ))))))
2369
+ ) : /* @__PURE__ */ import_react6.default.createElement(import_Typography6.default, { variant: "body2", color: "text.secondary" }, "No members assigned."))));
1436
2370
  };
1437
2371
  TeamUsage = ({
1438
2372
  teams,
@@ -1441,12 +2375,18 @@ var init_TeamUsage = __esm({
1441
2375
  getTeamUsageLoading
1442
2376
  }) => {
1443
2377
  if (loading) {
1444
- return /* @__PURE__ */ import_react5.default.createElement(import_Paper4.default, { sx: { p: 2 } }, /* @__PURE__ */ import_react5.default.createElement(import_LinearProgress4.default, null));
2378
+ return /* @__PURE__ */ import_react6.default.createElement(SectionCard, { title: "Teams" }, /* @__PURE__ */ import_react6.default.createElement(import_Skeleton4.default, { variant: "rounded", height: 120, sx: { mb: 2 } }), /* @__PURE__ */ import_react6.default.createElement(import_Skeleton4.default, { variant: "rounded", height: 120 }));
1445
2379
  }
1446
2380
  if (!teams.length) {
1447
- return /* @__PURE__ */ import_react5.default.createElement(import_Paper4.default, { sx: { p: 2 } }, /* @__PURE__ */ import_react5.default.createElement(import_Box5.default, { display: "flex", alignItems: "flex-start", gap: 1.5 }, /* @__PURE__ */ import_react5.default.createElement(import_icons_material4.Group, { color: "disabled", sx: { mt: 0.5 } }), /* @__PURE__ */ import_react5.default.createElement(import_Box5.default, null, /* @__PURE__ */ import_react5.default.createElement(import_Typography5.default, { color: "text.secondary", variant: "body2" }, "You're not a member of any LiteLLM team yet."), /* @__PURE__ */ import_react5.default.createElement(import_Typography5.default, { color: "text.secondary", variant: "body2", mt: 0.5 }, "That's fine \u2014 your account is provisioned, so you can still generate personal keys and use models. If you need a shared budget with colleagues, ask an admin to add you to a team."))));
2381
+ return /* @__PURE__ */ import_react6.default.createElement(SectionCard, { title: "Teams" }, /* @__PURE__ */ import_react6.default.createElement(
2382
+ EmptyState,
2383
+ {
2384
+ message: "You're not a member of any LiteLLM team yet.",
2385
+ hint: "Your account is provisioned, so you can still generate personal keys and use models. Ask an admin to add you to a team if you need a shared budget."
2386
+ }
2387
+ ));
1448
2388
  }
1449
- return /* @__PURE__ */ import_react5.default.createElement(import_Box5.default, null, /* @__PURE__ */ import_react5.default.createElement(import_Typography5.default, { variant: "h6", mb: 1 }, "Teams"), teams.map((team) => /* @__PURE__ */ import_react5.default.createElement(
2389
+ return /* @__PURE__ */ import_react6.default.createElement(SectionCard, { title: "Teams", subtitle: `${teams.length} team${teams.length === 1 ? "" : "s"}` }, /* @__PURE__ */ import_react6.default.createElement(import_Box6.default, { sx: { display: "flex", flexDirection: "column", gap: 2 } }, teams.map((team) => /* @__PURE__ */ import_react6.default.createElement(
1450
2390
  TeamCard,
1451
2391
  {
1452
2392
  key: team.team_id,
@@ -1454,19 +2394,23 @@ var init_TeamUsage = __esm({
1454
2394
  usage: getTeamUsage(team.team_id),
1455
2395
  usageLoading: getTeamUsageLoading(team.team_id)
1456
2396
  }
1457
- )));
2397
+ ))));
1458
2398
  };
1459
2399
  }
1460
2400
  });
1461
2401
 
1462
2402
  // src/components/AuditLog.tsx
1463
- function actionColor(action) {
1464
- if (!action) return "default";
1465
- for (const [key, color] of Object.entries(ACTION_COLORS)) {
1466
- if (action.toLowerCase().includes(key)) return color;
2403
+ function actionTone(action) {
2404
+ if (!action) return "neutral";
2405
+ for (const [key, tone] of Object.entries(ACTION_TONES)) {
2406
+ if (action.toLowerCase().includes(key)) return tone;
1467
2407
  }
1468
2408
  return "info";
1469
2409
  }
2410
+ function prettyTableName(name) {
2411
+ if (!name) return "\u2014";
2412
+ return TABLE_LABELS[name] ?? name;
2413
+ }
1470
2414
  function formatDateTime(iso) {
1471
2415
  try {
1472
2416
  return new Date(iso).toLocaleString();
@@ -1476,21 +2420,26 @@ function formatDateTime(iso) {
1476
2420
  }
1477
2421
  function renderAuditLogBody(loading, entries) {
1478
2422
  if (loading) {
1479
- return /* @__PURE__ */ import_react6.default.createElement(import_TableRow4.default, null, /* @__PURE__ */ import_react6.default.createElement(import_TableCell4.default, { colSpan: 6, align: "center", sx: { py: 4 } }, /* @__PURE__ */ import_react6.default.createElement(import_CircularProgress4.default, { size: 24 })));
2423
+ return /* @__PURE__ */ import_react7.default.createElement(import_TableRow4.default, null, /* @__PURE__ */ import_react7.default.createElement(import_TableCell4.default, { colSpan: 6, sx: { py: 2 } }, /* @__PURE__ */ import_react7.default.createElement(import_Skeleton5.default, { variant: "rounded", height: 160 })));
1480
2424
  }
1481
2425
  if (entries.length === 0) {
1482
- return /* @__PURE__ */ import_react6.default.createElement(import_TableRow4.default, null, /* @__PURE__ */ import_react6.default.createElement(import_TableCell4.default, { colSpan: 6, align: "center", sx: { py: 4 } }, /* @__PURE__ */ import_react6.default.createElement(import_Typography6.default, { color: "text.secondary" }, "No audit events found")));
2426
+ return /* @__PURE__ */ import_react7.default.createElement(import_TableRow4.default, null, /* @__PURE__ */ import_react7.default.createElement(import_TableCell4.default, { colSpan: 6 }, /* @__PURE__ */ import_react7.default.createElement(
2427
+ EmptyState,
2428
+ {
2429
+ message: "No audit events found",
2430
+ hint: "Try widening the filters or a different time window."
2431
+ }
2432
+ )));
1483
2433
  }
1484
- return entries.map((entry) => /* @__PURE__ */ import_react6.default.createElement(DetailRow, { key: entry.id, entry }));
2434
+ return entries.map((entry) => /* @__PURE__ */ import_react7.default.createElement(DetailRow, { key: entry.id, entry }));
1485
2435
  }
1486
- var import_react6, import_Paper5, import_Box6, import_Typography6, import_Table4, import_TableBody4, import_TableCell4, import_TableContainer4, import_TableHead4, import_TableRow4, import_TablePagination, import_TextField3, import_MenuItem3, import_Chip5, import_CircularProgress4, import_Collapse2, import_IconButton4, import_Alert2, import_icons_material5, import_react_use, ACTION_COLORS, DetailRow, AuditLog;
2436
+ var import_react7, import_Box7, import_Typography7, import_Table4, import_TableBody4, import_TableCell4, import_TableContainer4, import_TableHead4, import_TableRow4, import_TablePagination, import_TextField4, import_MenuItem3, import_Skeleton5, import_Collapse2, import_IconButton4, import_Alert2, import_styles5, import_icons_material5, import_react_use, ACTION_TONES, TABLE_LABELS, DetailRow, AuditLog;
1487
2437
  var init_AuditLog = __esm({
1488
2438
  "src/components/AuditLog.tsx"() {
1489
2439
  "use strict";
1490
- import_react6 = __toESM(require("react"));
1491
- import_Paper5 = __toESM(require("@mui/material/Paper"));
1492
- import_Box6 = __toESM(require("@mui/material/Box"));
1493
- import_Typography6 = __toESM(require("@mui/material/Typography"));
2440
+ import_react7 = __toESM(require("react"));
2441
+ import_Box7 = __toESM(require("@mui/material/Box"));
2442
+ import_Typography7 = __toESM(require("@mui/material/Typography"));
1494
2443
  import_Table4 = __toESM(require("@mui/material/Table"));
1495
2444
  import_TableBody4 = __toESM(require("@mui/material/TableBody"));
1496
2445
  import_TableCell4 = __toESM(require("@mui/material/TableCell"));
@@ -1498,32 +2447,97 @@ var init_AuditLog = __esm({
1498
2447
  import_TableHead4 = __toESM(require("@mui/material/TableHead"));
1499
2448
  import_TableRow4 = __toESM(require("@mui/material/TableRow"));
1500
2449
  import_TablePagination = __toESM(require("@mui/material/TablePagination"));
1501
- import_TextField3 = __toESM(require("@mui/material/TextField"));
2450
+ import_TextField4 = __toESM(require("@mui/material/TextField"));
1502
2451
  import_MenuItem3 = __toESM(require("@mui/material/MenuItem"));
1503
- import_Chip5 = __toESM(require("@mui/material/Chip"));
1504
- import_CircularProgress4 = __toESM(require("@mui/material/CircularProgress"));
2452
+ import_Skeleton5 = __toESM(require("@mui/material/Skeleton"));
1505
2453
  import_Collapse2 = __toESM(require("@mui/material/Collapse"));
1506
2454
  import_IconButton4 = __toESM(require("@mui/material/IconButton"));
1507
2455
  import_Alert2 = __toESM(require("@mui/material/Alert"));
2456
+ import_styles5 = require("@mui/material/styles");
1508
2457
  import_icons_material5 = require("@mui/icons-material");
1509
2458
  import_react_use = require("react-use");
1510
- ACTION_COLORS = {
2459
+ init_ui();
2460
+ ACTION_TONES = {
1511
2461
  created: "success",
1512
- deleted: "error",
2462
+ deleted: "danger",
1513
2463
  updated: "warning",
1514
- blocked: "error",
2464
+ blocked: "danger",
1515
2465
  unblocked: "success"
1516
2466
  };
2467
+ TABLE_LABELS = {
2468
+ LiteLLM_VerificationToken: "Key",
2469
+ LiteLLM_TeamTable: "Team",
2470
+ LiteLLM_UserTable: "User"
2471
+ };
1517
2472
  DetailRow = ({ entry }) => {
1518
- const [open, setOpen] = (0, import_react6.useState)(false);
2473
+ const [open, setOpen] = (0, import_react7.useState)(false);
1519
2474
  const hasDetail = entry.before_value || entry.updated_values;
1520
- return /* @__PURE__ */ import_react6.default.createElement(import_react6.default.Fragment, null, /* @__PURE__ */ import_react6.default.createElement(import_TableRow4.default, { hover: true }, /* @__PURE__ */ import_react6.default.createElement(import_TableCell4.default, { sx: { width: 40, pr: 0 } }, hasDetail && /* @__PURE__ */ import_react6.default.createElement(import_IconButton4.default, { size: "small", onClick: () => setOpen((o) => !o) }, open ? /* @__PURE__ */ import_react6.default.createElement(import_icons_material5.KeyboardArrowUp, { fontSize: "small" }) : /* @__PURE__ */ import_react6.default.createElement(import_icons_material5.KeyboardArrowDown, { fontSize: "small" }))), /* @__PURE__ */ import_react6.default.createElement(import_TableCell4.default, { sx: { whiteSpace: "nowrap" } }, formatDateTime(entry.updated_at)), /* @__PURE__ */ import_react6.default.createElement(import_TableCell4.default, null, entry.action && /* @__PURE__ */ import_react6.default.createElement(import_Chip5.default, { label: entry.action, color: actionColor(entry.action), size: "small" })), /* @__PURE__ */ import_react6.default.createElement(import_TableCell4.default, null, entry.table_name ?? "-"), /* @__PURE__ */ import_react6.default.createElement(import_TableCell4.default, null, /* @__PURE__ */ import_react6.default.createElement(import_Typography6.default, { variant: "body2", component: "code", sx: { fontFamily: "monospace", fontSize: "0.75rem" } }, entry.object_id ? entry.object_id.slice(0, 20) + (entry.object_id.length > 20 ? "\u2026" : "") : "-")), /* @__PURE__ */ import_react6.default.createElement(import_TableCell4.default, null, entry.changed_by ?? "-")), hasDetail && /* @__PURE__ */ import_react6.default.createElement(import_TableRow4.default, null, /* @__PURE__ */ import_react6.default.createElement(import_TableCell4.default, { colSpan: 6, sx: { py: 0 } }, /* @__PURE__ */ import_react6.default.createElement(import_Collapse2.default, { in: open, unmountOnExit: true }, /* @__PURE__ */ import_react6.default.createElement(import_Box6.default, { p: 2, display: "flex", gap: 2, flexWrap: "wrap" }, entry.before_value && /* @__PURE__ */ import_react6.default.createElement(import_Box6.default, { flex: 1, minWidth: 200 }, /* @__PURE__ */ import_react6.default.createElement(import_Typography6.default, { variant: "caption", color: "text.secondary", display: "block", mb: 0.5 }, "Before"), /* @__PURE__ */ import_react6.default.createElement(import_Typography6.default, { component: "pre", variant: "caption", sx: { fontFamily: "monospace", whiteSpace: "pre-wrap", wordBreak: "break-all" } }, JSON.stringify(entry.before_value, null, 2))), entry.updated_values && /* @__PURE__ */ import_react6.default.createElement(import_Box6.default, { flex: 1, minWidth: 200 }, /* @__PURE__ */ import_react6.default.createElement(import_Typography6.default, { variant: "caption", color: "text.secondary", display: "block", mb: 0.5 }, "After"), /* @__PURE__ */ import_react6.default.createElement(import_Typography6.default, { component: "pre", variant: "caption", sx: { fontFamily: "monospace", whiteSpace: "pre-wrap", wordBreak: "break-all" } }, JSON.stringify(entry.updated_values, null, 2))))))));
2475
+ return /* @__PURE__ */ import_react7.default.createElement(import_react7.default.Fragment, null, /* @__PURE__ */ import_react7.default.createElement(import_TableRow4.default, null, /* @__PURE__ */ import_react7.default.createElement(import_TableCell4.default, { sx: { width: 40, pr: 0 } }, hasDetail && /* @__PURE__ */ import_react7.default.createElement(
2476
+ import_IconButton4.default,
2477
+ {
2478
+ size: "small",
2479
+ onClick: () => setOpen((o) => !o),
2480
+ "aria-label": open ? "Hide changes" : "Show changes",
2481
+ "aria-expanded": open,
2482
+ sx: (theme) => ({
2483
+ color: theme.palette.text.secondary,
2484
+ transform: open ? "rotate(180deg)" : "none",
2485
+ transition: theme.transitions.create("transform")
2486
+ })
2487
+ },
2488
+ /* @__PURE__ */ import_react7.default.createElement(import_icons_material5.KeyboardArrowDown, { fontSize: "small" })
2489
+ )), /* @__PURE__ */ import_react7.default.createElement(import_TableCell4.default, { sx: { whiteSpace: "nowrap" } }, /* @__PURE__ */ import_react7.default.createElement(import_Typography7.default, { variant: "body2", color: "text.secondary" }, formatDateTime(entry.updated_at))), /* @__PURE__ */ import_react7.default.createElement(import_TableCell4.default, null, entry.action && /* @__PURE__ */ import_react7.default.createElement(StatusPill, { label: entry.action, tone: actionTone(entry.action) })), /* @__PURE__ */ import_react7.default.createElement(import_TableCell4.default, null, /* @__PURE__ */ import_react7.default.createElement(import_Typography7.default, { variant: "body2" }, prettyTableName(entry.table_name))), /* @__PURE__ */ import_react7.default.createElement(import_TableCell4.default, null, /* @__PURE__ */ import_react7.default.createElement(
2490
+ import_Typography7.default,
2491
+ {
2492
+ variant: "body2",
2493
+ component: "code",
2494
+ color: "text.secondary",
2495
+ title: entry.object_id ?? void 0,
2496
+ sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 12 }
2497
+ },
2498
+ entry.object_id ? entry.object_id.slice(0, 20) + (entry.object_id.length > 20 ? "\u2026" : "") : "\u2014"
2499
+ )), /* @__PURE__ */ import_react7.default.createElement(import_TableCell4.default, null, entry.changed_by ?? "\u2014")), hasDetail && /* @__PURE__ */ import_react7.default.createElement(import_TableRow4.default, { sx: { "&&:hover": { bgcolor: "transparent" } } }, /* @__PURE__ */ import_react7.default.createElement(import_TableCell4.default, { colSpan: 6, sx: { "&&": { py: 0, border: 0 } } }, /* @__PURE__ */ import_react7.default.createElement(import_Collapse2.default, { in: open, unmountOnExit: true }, /* @__PURE__ */ import_react7.default.createElement(
2500
+ import_Box7.default,
2501
+ {
2502
+ display: "flex",
2503
+ gap: 2,
2504
+ flexWrap: "wrap",
2505
+ sx: (theme) => ({
2506
+ p: 2,
2507
+ mb: 1.5,
2508
+ borderRadius: 1.5,
2509
+ bgcolor: (0, import_styles5.alpha)(theme.palette.text.primary, theme.palette.mode === "dark" ? 0.05 : 0.03)
2510
+ })
2511
+ },
2512
+ entry.before_value && /* @__PURE__ */ import_react7.default.createElement(import_Box7.default, { flex: 1, minWidth: 200 }, /* @__PURE__ */ import_react7.default.createElement(
2513
+ import_Typography7.default,
2514
+ {
2515
+ variant: "caption",
2516
+ color: "text.secondary",
2517
+ display: "block",
2518
+ mb: 0.75,
2519
+ sx: { fontSize: 10.5, fontWeight: 700, letterSpacing: "0.07em", textTransform: "uppercase" }
2520
+ },
2521
+ "Before"
2522
+ ), /* @__PURE__ */ import_react7.default.createElement(import_Typography7.default, { component: "pre", variant: "caption", sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", whiteSpace: "pre-wrap", wordBreak: "break-all", m: 0 } }, JSON.stringify(entry.before_value, null, 2))),
2523
+ entry.updated_values && /* @__PURE__ */ import_react7.default.createElement(import_Box7.default, { flex: 1, minWidth: 200 }, /* @__PURE__ */ import_react7.default.createElement(
2524
+ import_Typography7.default,
2525
+ {
2526
+ variant: "caption",
2527
+ color: "text.secondary",
2528
+ display: "block",
2529
+ mb: 0.75,
2530
+ sx: { fontSize: 10.5, fontWeight: 700, letterSpacing: "0.07em", textTransform: "uppercase" }
2531
+ },
2532
+ "After"
2533
+ ), /* @__PURE__ */ import_react7.default.createElement(import_Typography7.default, { component: "pre", variant: "caption", sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", whiteSpace: "pre-wrap", wordBreak: "break-all", m: 0 } }, JSON.stringify(entry.updated_values, null, 2)))
2534
+ )))));
1521
2535
  };
1522
2536
  AuditLog = ({ api }) => {
1523
- const [page, setPage] = (0, import_react6.useState)(0);
1524
- const [pageSize, setPageSize] = (0, import_react6.useState)(25);
1525
- const [filters, setFilters] = (0, import_react6.useState)({});
1526
- const fetchParams = (0, import_react6.useCallback)(
2537
+ const [page, setPage] = (0, import_react7.useState)(0);
2538
+ const [pageSize, setPageSize] = (0, import_react7.useState)(25);
2539
+ const [filters, setFilters] = (0, import_react7.useState)({});
2540
+ const fetchParams = (0, import_react7.useCallback)(
1527
2541
  () => ({ page: page + 1, page_size: pageSize, ...filters }),
1528
2542
  [page, pageSize, filters]
1529
2543
  )();
@@ -1533,68 +2547,80 @@ var init_AuditLog = __esm({
1533
2547
  );
1534
2548
  const entries = value?.audit_logs ?? [];
1535
2549
  const total = value?.total ?? 0;
1536
- return /* @__PURE__ */ import_react6.default.createElement(import_Paper5.default, null, /* @__PURE__ */ import_react6.default.createElement(import_Box6.default, { p: 2, display: "flex", gap: 2, flexWrap: "wrap", alignItems: "center" }, /* @__PURE__ */ import_react6.default.createElement(import_Typography6.default, { variant: "h6", sx: { flex: "0 0 auto", mr: 1 } }, "Audit Log"), /* @__PURE__ */ import_react6.default.createElement(
1537
- import_TextField3.default,
2550
+ return /* @__PURE__ */ import_react7.default.createElement(
2551
+ SectionCard,
1538
2552
  {
1539
- size: "small",
1540
- label: "Action",
1541
- select: true,
1542
- value: filters.action ?? "",
1543
- onChange: (e) => {
1544
- setFilters((f) => ({ ...f, action: e.target.value || void 0 }));
1545
- setPage(0);
1546
- },
1547
- sx: { minWidth: 160 }
1548
- },
1549
- /* @__PURE__ */ import_react6.default.createElement(import_MenuItem3.default, { value: "" }, "All actions"),
1550
- /* @__PURE__ */ import_react6.default.createElement(import_MenuItem3.default, { value: "created" }, "Created"),
1551
- /* @__PURE__ */ import_react6.default.createElement(import_MenuItem3.default, { value: "updated" }, "Updated"),
1552
- /* @__PURE__ */ import_react6.default.createElement(import_MenuItem3.default, { value: "deleted" }, "Deleted"),
1553
- /* @__PURE__ */ import_react6.default.createElement(import_MenuItem3.default, { value: "blocked" }, "Blocked")
1554
- ), /* @__PURE__ */ import_react6.default.createElement(
1555
- import_TextField3.default,
1556
- {
1557
- size: "small",
1558
- label: "Table",
1559
- select: true,
1560
- value: filters.table_name ?? "",
1561
- onChange: (e) => {
1562
- setFilters((f) => ({ ...f, table_name: e.target.value || void 0 }));
1563
- setPage(0);
1564
- },
1565
- sx: { minWidth: 160 }
2553
+ title: "Audit Log",
2554
+ subtitle: loading ? "Loading\u2026" : `${total.toLocaleString()} event${total === 1 ? "" : "s"}`,
2555
+ flush: true,
2556
+ actions: /* @__PURE__ */ import_react7.default.createElement(import_react7.default.Fragment, null, /* @__PURE__ */ import_react7.default.createElement(
2557
+ import_TextField4.default,
2558
+ {
2559
+ size: "small",
2560
+ label: "Action",
2561
+ select: true,
2562
+ value: filters.action ?? "",
2563
+ onChange: (e) => {
2564
+ setFilters((f) => ({ ...f, action: e.target.value || void 0 }));
2565
+ setPage(0);
2566
+ },
2567
+ sx: { minWidth: 150 }
2568
+ },
2569
+ /* @__PURE__ */ import_react7.default.createElement(import_MenuItem3.default, { value: "" }, "All actions"),
2570
+ /* @__PURE__ */ import_react7.default.createElement(import_MenuItem3.default, { value: "created" }, "Created"),
2571
+ /* @__PURE__ */ import_react7.default.createElement(import_MenuItem3.default, { value: "updated" }, "Updated"),
2572
+ /* @__PURE__ */ import_react7.default.createElement(import_MenuItem3.default, { value: "deleted" }, "Deleted"),
2573
+ /* @__PURE__ */ import_react7.default.createElement(import_MenuItem3.default, { value: "blocked" }, "Blocked")
2574
+ ), /* @__PURE__ */ import_react7.default.createElement(
2575
+ import_TextField4.default,
2576
+ {
2577
+ size: "small",
2578
+ label: "Table",
2579
+ select: true,
2580
+ value: filters.table_name ?? "",
2581
+ onChange: (e) => {
2582
+ setFilters((f) => ({ ...f, table_name: e.target.value || void 0 }));
2583
+ setPage(0);
2584
+ },
2585
+ sx: { minWidth: 150 }
2586
+ },
2587
+ /* @__PURE__ */ import_react7.default.createElement(import_MenuItem3.default, { value: "" }, "All tables"),
2588
+ /* @__PURE__ */ import_react7.default.createElement(import_MenuItem3.default, { value: "LiteLLM_VerificationToken" }, "Key"),
2589
+ /* @__PURE__ */ import_react7.default.createElement(import_MenuItem3.default, { value: "LiteLLM_TeamTable" }, "Team"),
2590
+ /* @__PURE__ */ import_react7.default.createElement(import_MenuItem3.default, { value: "LiteLLM_UserTable" }, "User")
2591
+ ), /* @__PURE__ */ import_react7.default.createElement(
2592
+ import_TextField4.default,
2593
+ {
2594
+ size: "small",
2595
+ label: "Changed by",
2596
+ value: filters.changed_by ?? "",
2597
+ onChange: (e) => {
2598
+ setFilters((f) => ({ ...f, changed_by: e.target.value || void 0 }));
2599
+ setPage(0);
2600
+ },
2601
+ sx: { minWidth: 180 }
2602
+ }
2603
+ ))
1566
2604
  },
1567
- /* @__PURE__ */ import_react6.default.createElement(import_MenuItem3.default, { value: "" }, "All tables"),
1568
- /* @__PURE__ */ import_react6.default.createElement(import_MenuItem3.default, { value: "LiteLLM_VerificationToken" }, "Key"),
1569
- /* @__PURE__ */ import_react6.default.createElement(import_MenuItem3.default, { value: "LiteLLM_TeamTable" }, "Team"),
1570
- /* @__PURE__ */ import_react6.default.createElement(import_MenuItem3.default, { value: "LiteLLM_UserTable" }, "User")
1571
- ), /* @__PURE__ */ import_react6.default.createElement(
1572
- import_TextField3.default,
1573
- {
1574
- size: "small",
1575
- label: "Changed by",
1576
- value: filters.changed_by ?? "",
1577
- onChange: (e) => {
1578
- setFilters((f) => ({ ...f, changed_by: e.target.value || void 0 }));
1579
- setPage(0);
1580
- },
1581
- sx: { minWidth: 200 }
1582
- }
1583
- )), error && /* @__PURE__ */ import_react6.default.createElement(import_Box6.default, { px: 2, pb: 2 }, /* @__PURE__ */ import_react6.default.createElement(import_Alert2.default, { severity: "error" }, error.message)), /* @__PURE__ */ import_react6.default.createElement(import_TableContainer4.default, null, /* @__PURE__ */ import_react6.default.createElement(import_Table4.default, { size: "small" }, /* @__PURE__ */ import_react6.default.createElement(import_TableHead4.default, null, /* @__PURE__ */ import_react6.default.createElement(import_TableRow4.default, null, /* @__PURE__ */ import_react6.default.createElement(import_TableCell4.default, { sx: { width: 40 } }), /* @__PURE__ */ import_react6.default.createElement(import_TableCell4.default, null, "Time"), /* @__PURE__ */ import_react6.default.createElement(import_TableCell4.default, null, "Action"), /* @__PURE__ */ import_react6.default.createElement(import_TableCell4.default, null, "Table"), /* @__PURE__ */ import_react6.default.createElement(import_TableCell4.default, null, "Object ID"), /* @__PURE__ */ import_react6.default.createElement(import_TableCell4.default, null, "Changed By"))), /* @__PURE__ */ import_react6.default.createElement(import_TableBody4.default, null, renderAuditLogBody(loading, entries)))), /* @__PURE__ */ import_react6.default.createElement(
1584
- import_TablePagination.default,
1585
- {
1586
- component: "div",
1587
- count: total,
1588
- page,
1589
- onPageChange: (_, p) => setPage(p),
1590
- rowsPerPage: pageSize,
1591
- onRowsPerPageChange: (e) => {
1592
- setPageSize(Number(e.target.value));
1593
- setPage(0);
1594
- },
1595
- rowsPerPageOptions: [10, 25, 50]
1596
- }
1597
- ));
2605
+ error && /* @__PURE__ */ import_react7.default.createElement(import_Box7.default, { px: 2.5, pb: 2 }, /* @__PURE__ */ import_react7.default.createElement(import_Alert2.default, { severity: "error" }, error.message)),
2606
+ /* @__PURE__ */ import_react7.default.createElement(import_TableContainer4.default, null, /* @__PURE__ */ import_react7.default.createElement(import_Table4.default, { size: "small", sx: dataTableSx }, /* @__PURE__ */ import_react7.default.createElement(import_TableHead4.default, null, /* @__PURE__ */ import_react7.default.createElement(import_TableRow4.default, null, /* @__PURE__ */ import_react7.default.createElement(import_TableCell4.default, { sx: { width: 40 } }), /* @__PURE__ */ import_react7.default.createElement(import_TableCell4.default, null, "Time"), /* @__PURE__ */ import_react7.default.createElement(import_TableCell4.default, null, "Action"), /* @__PURE__ */ import_react7.default.createElement(import_TableCell4.default, null, "Table"), /* @__PURE__ */ import_react7.default.createElement(import_TableCell4.default, null, "Object ID"), /* @__PURE__ */ import_react7.default.createElement(import_TableCell4.default, null, "Changed By"))), /* @__PURE__ */ import_react7.default.createElement(import_TableBody4.default, null, renderAuditLogBody(loading, entries)))),
2607
+ /* @__PURE__ */ import_react7.default.createElement(
2608
+ import_TablePagination.default,
2609
+ {
2610
+ component: "div",
2611
+ count: total,
2612
+ page,
2613
+ onPageChange: (_, p) => setPage(p),
2614
+ rowsPerPage: pageSize,
2615
+ onRowsPerPageChange: (e) => {
2616
+ setPageSize(Number(e.target.value));
2617
+ setPage(0);
2618
+ },
2619
+ rowsPerPageOptions: [10, 25, 50],
2620
+ sx: (theme) => ({ borderTop: `1px solid ${theme.palette.divider}` })
2621
+ }
2622
+ )
2623
+ );
1598
2624
  };
1599
2625
  }
1600
2626
  });
@@ -1618,19 +2644,19 @@ function initDateRange() {
1618
2644
  else start.setDate(start.getDate() - 7);
1619
2645
  return { start, end };
1620
2646
  }
1621
- var import_react7, import_Box7, import_Snackbar, import_Alert3, import_CircularProgress5, import_Typography7, import_Paper6, import_Tabs3, import_Tab3, import_react_use2, import_core_plugin_api2, PERIOD_LS_KEY2, LiteLLMPage;
2647
+ var import_react8, import_Box8, import_Snackbar, import_Alert3, import_CircularProgress4, import_Typography8, import_Paper5, import_Tabs2, import_Tab2, import_react_use2, import_core_plugin_api2, PERIOD_LS_KEY2, LiteLLMPage;
1622
2648
  var init_LiteLLMPage = __esm({
1623
2649
  "src/components/LiteLLMPage.tsx"() {
1624
2650
  "use strict";
1625
- import_react7 = __toESM(require("react"));
1626
- import_Box7 = __toESM(require("@mui/material/Box"));
2651
+ import_react8 = __toESM(require("react"));
2652
+ import_Box8 = __toESM(require("@mui/material/Box"));
1627
2653
  import_Snackbar = __toESM(require("@mui/material/Snackbar"));
1628
2654
  import_Alert3 = __toESM(require("@mui/material/Alert"));
1629
- import_CircularProgress5 = __toESM(require("@mui/material/CircularProgress"));
1630
- import_Typography7 = __toESM(require("@mui/material/Typography"));
1631
- import_Paper6 = __toESM(require("@mui/material/Paper"));
1632
- import_Tabs3 = __toESM(require("@mui/material/Tabs"));
1633
- import_Tab3 = __toESM(require("@mui/material/Tab"));
2655
+ import_CircularProgress4 = __toESM(require("@mui/material/CircularProgress"));
2656
+ import_Typography8 = __toESM(require("@mui/material/Typography"));
2657
+ import_Paper5 = __toESM(require("@mui/material/Paper"));
2658
+ import_Tabs2 = __toESM(require("@mui/material/Tabs"));
2659
+ import_Tab2 = __toESM(require("@mui/material/Tab"));
1634
2660
  import_react_use2 = require("react-use");
1635
2661
  import_core_plugin_api2 = require("@backstage/core-plugin-api");
1636
2662
  init_DashboardHeader();
@@ -1643,12 +2669,12 @@ var init_LiteLLMPage = __esm({
1643
2669
  PERIOD_LS_KEY2 = "litellm_usage_period";
1644
2670
  LiteLLMPage = () => {
1645
2671
  const api = (0, import_core_plugin_api2.useApi)(liteLlmApiRef);
1646
- const [dateRange, setDateRange] = (0, import_react7.useState)(initDateRange);
1647
- const [activeTab, setActiveTab] = (0, import_react7.useState)("overview");
1648
- const [snackbar, setSnackbar] = (0, import_react7.useState)(null);
1649
- const [generateDialogOpen, setGenerateDialogOpen] = (0, import_react7.useState)(false);
1650
- const [teamUsageCache, setTeamUsageCache] = (0, import_react7.useState)({});
1651
- const [teamUsageLoading, setTeamUsageLoading] = (0, import_react7.useState)({});
2672
+ const [dateRange, setDateRange] = (0, import_react8.useState)(initDateRange);
2673
+ const [activeTab, setActiveTab] = (0, import_react8.useState)("overview");
2674
+ const [snackbar, setSnackbar] = (0, import_react8.useState)(null);
2675
+ const [generateDialogOpen, setGenerateDialogOpen] = (0, import_react8.useState)(false);
2676
+ const [teamUsageCache, setTeamUsageCache] = (0, import_react8.useState)({});
2677
+ const [teamUsageLoading, setTeamUsageLoading] = (0, import_react8.useState)({});
1652
2678
  const { value: userInfo, loading: userLoading, error: userError } = (0, import_react_use2.useAsync)(
1653
2679
  () => api.getUserInfo(),
1654
2680
  [api]
@@ -1673,7 +2699,7 @@ var init_LiteLLMPage = __esm({
1673
2699
  [api]
1674
2700
  );
1675
2701
  const { value: liteLlmConfig } = (0, import_react_use2.useAsync)(() => api.getConfig(), [api]);
1676
- const teams = (0, import_react7.useMemo)(() => {
2702
+ const teams = (0, import_react8.useMemo)(() => {
1677
2703
  if (!allTeams?.length) return [];
1678
2704
  if (!userInfo) return allTeams;
1679
2705
  const userId = userInfo.user_id;
@@ -1690,12 +2716,12 @@ var init_LiteLLMPage = __esm({
1690
2716
  const endDate = dateRange.end.toISOString().split("T")[0];
1691
2717
  return api.getUsage(startDate, endDate);
1692
2718
  }, [api, dateRange]);
1693
- const handleDateRangeChange = (0, import_react7.useCallback)((range) => {
2719
+ const handleDateRangeChange = (0, import_react8.useCallback)((range) => {
1694
2720
  setDateRange(range);
1695
2721
  setTeamUsageCache({});
1696
2722
  setTeamUsageLoading({});
1697
2723
  }, [setDateRange]);
1698
- const loadTeamUsage = (0, import_react7.useCallback)(async (teamId) => {
2724
+ const loadTeamUsage = (0, import_react8.useCallback)(async (teamId) => {
1699
2725
  if (teamUsageCache[teamId] !== void 0 || teamUsageLoading[teamId]) return;
1700
2726
  setTeamUsageLoading((prev) => ({ ...prev, [teamId]: true }));
1701
2727
  try {
@@ -1709,7 +2735,7 @@ var init_LiteLLMPage = __esm({
1709
2735
  setTeamUsageLoading((prev) => ({ ...prev, [teamId]: false }));
1710
2736
  }
1711
2737
  }, [api, dateRange, teamUsageCache, teamUsageLoading]);
1712
- const allowedModels = (0, import_react7.useMemo)(() => {
2738
+ const allowedModels = (0, import_react8.useMemo)(() => {
1713
2739
  if (!allModels?.length) return [];
1714
2740
  const userModels = userInfo?.models;
1715
2741
  const teamModels = teams?.flatMap((t) => t.models ?? []);
@@ -1722,7 +2748,7 @@ var init_LiteLLMPage = __esm({
1722
2748
  ]);
1723
2749
  return allModels.filter((m) => allowed.has(m.model_name));
1724
2750
  }, [allModels, userInfo, teams]);
1725
- const handleGenerateKey = (0, import_react7.useCallback)(
2751
+ const handleGenerateKey = (0, import_react8.useCallback)(
1726
2752
  async (request) => {
1727
2753
  try {
1728
2754
  const response = await api.generateKey(request);
@@ -1736,7 +2762,7 @@ var init_LiteLLMPage = __esm({
1736
2762
  },
1737
2763
  [api, refreshKeys]
1738
2764
  );
1739
- const handleUpdateKey = (0, import_react7.useCallback)(
2765
+ const handleUpdateKey = (0, import_react8.useCallback)(
1740
2766
  async (keyId, request) => {
1741
2767
  try {
1742
2768
  await api.updateKey(keyId, request);
@@ -1749,7 +2775,7 @@ var init_LiteLLMPage = __esm({
1749
2775
  },
1750
2776
  [api, refreshKeys]
1751
2777
  );
1752
- const handleBlockKey = (0, import_react7.useCallback)(
2778
+ const handleBlockKey = (0, import_react8.useCallback)(
1753
2779
  async (keyId) => {
1754
2780
  try {
1755
2781
  await api.blockKey(keyId);
@@ -1761,7 +2787,7 @@ var init_LiteLLMPage = __esm({
1761
2787
  },
1762
2788
  [api, refreshKeys]
1763
2789
  );
1764
- const handleUnblockKey = (0, import_react7.useCallback)(
2790
+ const handleUnblockKey = (0, import_react8.useCallback)(
1765
2791
  async (keyId) => {
1766
2792
  try {
1767
2793
  await api.unblockKey(keyId);
@@ -1773,7 +2799,7 @@ var init_LiteLLMPage = __esm({
1773
2799
  },
1774
2800
  [api, refreshKeys]
1775
2801
  );
1776
- const handleResetKeySpend = (0, import_react7.useCallback)(
2802
+ const handleResetKeySpend = (0, import_react8.useCallback)(
1777
2803
  async (keyId) => {
1778
2804
  try {
1779
2805
  await api.resetKeySpend(keyId);
@@ -1785,7 +2811,7 @@ var init_LiteLLMPage = __esm({
1785
2811
  },
1786
2812
  [api, refreshKeys]
1787
2813
  );
1788
- const handleDeleteKey = (0, import_react7.useCallback)(
2814
+ const handleDeleteKey = (0, import_react8.useCallback)(
1789
2815
  async (keyId) => {
1790
2816
  try {
1791
2817
  await api.deleteKey(keyId);
@@ -1802,7 +2828,7 @@ var init_LiteLLMPage = __esm({
1802
2828
  },
1803
2829
  [api, refreshKeys]
1804
2830
  );
1805
- const handlePruneExpiredKeys = (0, import_react7.useCallback)(
2831
+ const handlePruneExpiredKeys = (0, import_react8.useCallback)(
1806
2832
  async () => {
1807
2833
  try {
1808
2834
  const result = await api.pruneExpiredKeys();
@@ -1817,34 +2843,42 @@ var init_LiteLLMPage = __esm({
1817
2843
  );
1818
2844
  const isInitialLoading = userLoading && !userInfo;
1819
2845
  if (isInitialLoading) {
1820
- return /* @__PURE__ */ import_react7.default.createElement(import_Box7.default, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: "50vh" }, /* @__PURE__ */ import_react7.default.createElement(import_CircularProgress5.default, null));
2846
+ return /* @__PURE__ */ import_react8.default.createElement(import_Box8.default, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: "50vh" }, /* @__PURE__ */ import_react8.default.createElement(import_CircularProgress4.default, null));
1821
2847
  }
1822
2848
  if (userError || !userInfo) {
1823
2849
  const isProvisioningEnabled = userError?.body?.provisioning === true;
1824
2850
  const hint = userError?.body?.hint;
1825
- return /* @__PURE__ */ import_react7.default.createElement(import_Box7.default, { p: 3 }, /* @__PURE__ */ import_react7.default.createElement(import_Paper6.default, { sx: { p: 3 } }, /* @__PURE__ */ import_react7.default.createElement(import_Typography7.default, { variant: "h6", gutterBottom: true }, "Account not provisioned"), /* @__PURE__ */ import_react7.default.createElement(import_Typography7.default, { color: "text.secondary", paragraph: true }, "Your Backstage account is not linked to a LiteLLM user."), hint ? /* @__PURE__ */ import_react7.default.createElement(import_Typography7.default, { variant: "body2", color: "text.secondary" }, hint) : /* @__PURE__ */ import_react7.default.createElement(import_Typography7.default, { variant: "body2", color: "text.secondary" }, isProvisioningEnabled ? "Auto-provisioning is enabled but failed. Check the backend logs." : "Set litellm.provisioning.enabled: true in app-config.yaml to enable auto-provisioning, or ask your administrator to create the account manually.")));
2851
+ return /* @__PURE__ */ import_react8.default.createElement(import_Box8.default, { p: 3 }, /* @__PURE__ */ import_react8.default.createElement(import_Paper5.default, { sx: { p: 3 } }, /* @__PURE__ */ import_react8.default.createElement(import_Typography8.default, { variant: "h6", gutterBottom: true }, "Account not provisioned"), /* @__PURE__ */ import_react8.default.createElement(import_Typography8.default, { color: "text.secondary", paragraph: true }, "Your Backstage account is not linked to a LiteLLM user."), hint ? /* @__PURE__ */ import_react8.default.createElement(import_Typography8.default, { variant: "body2", color: "text.secondary" }, hint) : /* @__PURE__ */ import_react8.default.createElement(import_Typography8.default, { variant: "body2", color: "text.secondary" }, isProvisioningEnabled ? "Auto-provisioning is enabled but failed. Check the backend logs." : "Set litellm.provisioning.enabled: true in app-config.yaml to enable auto-provisioning, or ask your administrator to create the account manually.")));
1826
2852
  }
1827
- return /* @__PURE__ */ import_react7.default.createElement(import_Box7.default, { p: 3 }, /* @__PURE__ */ import_react7.default.createElement(import_Box7.default, { mb: 2 }, /* @__PURE__ */ import_react7.default.createElement(
2853
+ const pageTabs = /* @__PURE__ */ import_react8.default.createElement(
2854
+ import_Tabs2.default,
2855
+ {
2856
+ value: activeTab,
2857
+ onChange: (_, v) => setActiveTab(v),
2858
+ variant: "scrollable",
2859
+ scrollButtons: "auto",
2860
+ sx: {
2861
+ minHeight: 44,
2862
+ '& [class*="MuiTabs-indicator"]': { height: 2, borderRadius: "2px 2px 0 0" },
2863
+ '& [class*="MuiTab-root"]': { minHeight: 44, textTransform: "none", fontSize: 14 }
2864
+ }
2865
+ },
2866
+ /* @__PURE__ */ import_react8.default.createElement(import_Tab2.default, { label: "Overview", value: "overview" }),
2867
+ /* @__PURE__ */ import_react8.default.createElement(import_Tab2.default, { label: "Keys", value: "keys" }),
2868
+ /* @__PURE__ */ import_react8.default.createElement(import_Tab2.default, { label: "Teams", value: "teams" }),
2869
+ userInfo.can_view_audit && /* @__PURE__ */ import_react8.default.createElement(import_Tab2.default, { label: "Audit Log", value: "audit" })
2870
+ );
2871
+ return /* @__PURE__ */ import_react8.default.createElement(import_Box8.default, { sx: { p: 3, display: "flex", flexDirection: "column", gap: 2 } }, /* @__PURE__ */ import_react8.default.createElement(
1828
2872
  DashboardHeader,
1829
2873
  {
1830
2874
  userInfo,
1831
2875
  teams: teams ?? [],
1832
2876
  keys: keys ?? [],
1833
2877
  loading: userLoading || teamsLoading,
1834
- onGenerateKeyClick: () => setGenerateDialogOpen(true)
2878
+ onGenerateKeyClick: () => setGenerateDialogOpen(true),
2879
+ tabs: pageTabs
1835
2880
  }
1836
- )), /* @__PURE__ */ import_react7.default.createElement(
1837
- import_Tabs3.default,
1838
- {
1839
- value: activeTab,
1840
- onChange: (_, v) => setActiveTab(v),
1841
- sx: { mb: 2, borderBottom: 1, borderColor: "divider" }
1842
- },
1843
- /* @__PURE__ */ import_react7.default.createElement(import_Tab3.default, { label: "Overview", value: "overview" }),
1844
- /* @__PURE__ */ import_react7.default.createElement(import_Tab3.default, { label: "Keys", value: "keys" }),
1845
- /* @__PURE__ */ import_react7.default.createElement(import_Tab3.default, { label: "Teams", value: "teams" }),
1846
- userInfo.can_view_audit && /* @__PURE__ */ import_react7.default.createElement(import_Tab3.default, { label: "Audit Log", value: "audit" })
1847
- ), activeTab === "overview" && /* @__PURE__ */ import_react7.default.createElement(
2881
+ ), activeTab === "overview" && /* @__PURE__ */ import_react8.default.createElement(
1848
2882
  UsageStats,
1849
2883
  {
1850
2884
  usage: usage ?? null,
@@ -1854,7 +2888,7 @@ var init_LiteLLMPage = __esm({
1854
2888
  loading: usageLoading,
1855
2889
  userInfo
1856
2890
  }
1857
- ), activeTab === "keys" && /* @__PURE__ */ import_react7.default.createElement(
2891
+ ), activeTab === "keys" && /* @__PURE__ */ import_react8.default.createElement(
1858
2892
  KeysTable,
1859
2893
  {
1860
2894
  keys: keys ?? [],
@@ -1868,7 +2902,7 @@ var init_LiteLLMPage = __esm({
1868
2902
  onDeleteKey: handleDeleteKey,
1869
2903
  onPruneExpiredKeys: handlePruneExpiredKeys
1870
2904
  }
1871
- ), activeTab === "teams" && /* @__PURE__ */ import_react7.default.createElement(
2905
+ ), activeTab === "teams" && /* @__PURE__ */ import_react8.default.createElement(
1872
2906
  TeamUsage,
1873
2907
  {
1874
2908
  teams: teams ?? [],
@@ -1879,7 +2913,7 @@ var init_LiteLLMPage = __esm({
1879
2913
  },
1880
2914
  getTeamUsageLoading: (teamId) => teamUsageLoading[teamId] ?? false
1881
2915
  }
1882
- ), activeTab === "audit" && userInfo.can_view_audit && /* @__PURE__ */ import_react7.default.createElement(AuditLog, { api }), /* @__PURE__ */ import_react7.default.createElement(
2916
+ ), activeTab === "audit" && userInfo.can_view_audit && /* @__PURE__ */ import_react8.default.createElement(AuditLog, { api }), /* @__PURE__ */ import_react8.default.createElement(
1883
2917
  GenerateKeyDialog,
1884
2918
  {
1885
2919
  open: generateDialogOpen,
@@ -1892,7 +2926,7 @@ var init_LiteLLMPage = __esm({
1892
2926
  onGenerateKey: handleGenerateKey,
1893
2927
  onGetConfig: () => api.getConfig()
1894
2928
  }
1895
- ), /* @__PURE__ */ import_react7.default.createElement(
2929
+ ), /* @__PURE__ */ import_react8.default.createElement(
1896
2930
  import_Snackbar.default,
1897
2931
  {
1898
2932
  open: !!snackbar,
@@ -1900,7 +2934,7 @@ var init_LiteLLMPage = __esm({
1900
2934
  onClose: () => setSnackbar(null),
1901
2935
  anchorOrigin: { vertical: "bottom", horizontal: "right" }
1902
2936
  },
1903
- snackbar ? /* @__PURE__ */ import_react7.default.createElement(import_Alert3.default, { severity: snackbar.severity, onClose: () => setSnackbar(null) }, snackbar.message) : void 0
2937
+ snackbar ? /* @__PURE__ */ import_react8.default.createElement(import_Alert3.default, { severity: snackbar.severity, onClose: () => setSnackbar(null) }, snackbar.message) : void 0
1904
2938
  ));
1905
2939
  };
1906
2940
  }
@@ -1922,7 +2956,7 @@ __export(index_exports, {
1922
2956
  module.exports = __toCommonJS(index_exports);
1923
2957
 
1924
2958
  // src/plugin.tsx
1925
- var import_react8 = __toESM(require("react"));
2959
+ var import_react9 = __toESM(require("react"));
1926
2960
  var import_icons_material6 = require("@mui/icons-material");
1927
2961
  var import_frontend_plugin_api = require("@backstage/frontend-plugin-api");
1928
2962
  init_api();
@@ -1937,10 +2971,10 @@ var liteLlmPage = import_frontend_plugin_api.PageBlueprint.make({
1937
2971
  params: {
1938
2972
  path: "/litellm",
1939
2973
  title: "LiteLLM",
1940
- icon: /* @__PURE__ */ import_react8.default.createElement(import_icons_material6.TrendingUp, null),
2974
+ icon: /* @__PURE__ */ import_react9.default.createElement(import_icons_material6.TrendingUp, null),
1941
2975
  loader: async () => {
1942
2976
  const { LiteLLMPage: LiteLLMPage2 } = await Promise.resolve().then(() => (init_LiteLLMPage(), LiteLLMPage_exports));
1943
- return /* @__PURE__ */ import_react8.default.createElement(LiteLLMPage2, null);
2977
+ return /* @__PURE__ */ import_react9.default.createElement(LiteLLMPage2, null);
1944
2978
  }
1945
2979
  }
1946
2980
  });
@@ -1957,15 +2991,15 @@ init_UsageStats();
1957
2991
  init_TeamUsage();
1958
2992
 
1959
2993
  // src/components/LiteLLMHomeWidget.tsx
1960
- var import_react9 = __toESM(require("react"));
1961
- var import_Paper7 = __toESM(require("@mui/material/Paper"));
1962
- var import_Box8 = __toESM(require("@mui/material/Box"));
1963
- var import_Typography8 = __toESM(require("@mui/material/Typography"));
1964
- var import_FormControl2 = __toESM(require("@mui/material/FormControl"));
1965
- var import_Select2 = __toESM(require("@mui/material/Select"));
2994
+ var import_react10 = __toESM(require("react"));
2995
+ var import_Paper6 = __toESM(require("@mui/material/Paper"));
2996
+ var import_Box9 = __toESM(require("@mui/material/Box"));
2997
+ var import_Typography9 = __toESM(require("@mui/material/Typography"));
2998
+ var import_FormControl = __toESM(require("@mui/material/FormControl"));
2999
+ var import_Select = __toESM(require("@mui/material/Select"));
1966
3000
  var import_MenuItem4 = __toESM(require("@mui/material/MenuItem"));
1967
3001
  var import_Grid2 = __toESM(require("@mui/material/Grid"));
1968
- var import_CircularProgress6 = __toESM(require("@mui/material/CircularProgress"));
3002
+ var import_CircularProgress5 = __toESM(require("@mui/material/CircularProgress"));
1969
3003
  var import_Alert4 = __toESM(require("@mui/material/Alert"));
1970
3004
  var import_recharts3 = require("recharts");
1971
3005
  var import_core_plugin_api3 = require("@backstage/core-plugin-api");
@@ -1983,19 +3017,19 @@ function presetToDateRange(preset) {
1983
3017
  }
1984
3018
  return { start, end };
1985
3019
  }
1986
- var Kpi = ({ label, value }) => /* @__PURE__ */ import_react9.default.createElement(import_Box8.default, null, /* @__PURE__ */ import_react9.default.createElement(import_Typography8.default, { variant: "caption", color: "text.secondary", display: "block" }, label), /* @__PURE__ */ import_react9.default.createElement(import_Typography8.default, { variant: "subtitle1", fontWeight: 600 }, value));
3020
+ var Kpi = ({ label, value }) => /* @__PURE__ */ import_react10.default.createElement(import_Box9.default, null, /* @__PURE__ */ import_react10.default.createElement(import_Typography9.default, { variant: "caption", color: "text.secondary", display: "block" }, label), /* @__PURE__ */ import_react10.default.createElement(import_Typography9.default, { variant: "subtitle1", fontWeight: 600 }, value));
1987
3021
  var LiteLLMHomeWidget = ({
1988
3022
  defaultPeriod = "7d",
1989
3023
  title = "LiteLLM Usage"
1990
3024
  }) => {
1991
3025
  const api = (0, import_core_plugin_api3.useApi)(liteLlmApiRef);
1992
- const [period, setPeriod] = (0, import_react9.useState)(defaultPeriod);
1993
- const [loading, setLoading] = (0, import_react9.useState)(true);
1994
- const [usageError, setUsageError] = (0, import_react9.useState)(null);
1995
- const [keysError, setKeysError] = (0, import_react9.useState)(null);
1996
- const [usage, setUsage] = (0, import_react9.useState)(null);
1997
- const [keys, setKeys] = (0, import_react9.useState)([]);
1998
- (0, import_react9.useEffect)(() => {
3026
+ const [period, setPeriod] = (0, import_react10.useState)(defaultPeriod);
3027
+ const [loading, setLoading] = (0, import_react10.useState)(true);
3028
+ const [usageError, setUsageError] = (0, import_react10.useState)(null);
3029
+ const [keysError, setKeysError] = (0, import_react10.useState)(null);
3030
+ const [usage, setUsage] = (0, import_react10.useState)(null);
3031
+ const [keys, setKeys] = (0, import_react10.useState)([]);
3032
+ (0, import_react10.useEffect)(() => {
1999
3033
  let cancelled = false;
2000
3034
  setLoading(true);
2001
3035
  setUsageError(null);
@@ -2032,17 +3066,17 @@ var LiteLLMHomeWidget = ({
2032
3066
  spend: d.spend
2033
3067
  }));
2034
3068
  const hasSparkline = dailyData.length > 0;
2035
- return /* @__PURE__ */ import_react9.default.createElement(import_Paper7.default, { sx: { p: 2 } }, /* @__PURE__ */ import_react9.default.createElement(import_Box8.default, { display: "flex", justifyContent: "space-between", alignItems: "center", mb: 1.5 }, /* @__PURE__ */ import_react9.default.createElement(import_Typography8.default, { variant: "h6" }, title), /* @__PURE__ */ import_react9.default.createElement(import_FormControl2.default, { size: "small", sx: { minWidth: 90 } }, /* @__PURE__ */ import_react9.default.createElement(
2036
- import_Select2.default,
3069
+ return /* @__PURE__ */ import_react10.default.createElement(import_Paper6.default, { sx: { p: 2 } }, /* @__PURE__ */ import_react10.default.createElement(import_Box9.default, { display: "flex", justifyContent: "space-between", alignItems: "center", mb: 1.5 }, /* @__PURE__ */ import_react10.default.createElement(import_Typography9.default, { variant: "h6" }, title), /* @__PURE__ */ import_react10.default.createElement(import_FormControl.default, { size: "small", sx: { minWidth: 90 } }, /* @__PURE__ */ import_react10.default.createElement(
3070
+ import_Select.default,
2037
3071
  {
2038
3072
  value: period,
2039
3073
  onChange: (e) => setPeriod(e.target.value),
2040
3074
  displayEmpty: true
2041
3075
  },
2042
- /* @__PURE__ */ import_react9.default.createElement(import_MenuItem4.default, { value: "today" }, "Today"),
2043
- /* @__PURE__ */ import_react9.default.createElement(import_MenuItem4.default, { value: "7d" }, "7d"),
2044
- /* @__PURE__ */ import_react9.default.createElement(import_MenuItem4.default, { value: "30d" }, "30d")
2045
- ))), loading && /* @__PURE__ */ import_react9.default.createElement(import_Box8.default, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: 120 }, /* @__PURE__ */ import_react9.default.createElement(import_CircularProgress6.default, { size: 32 })), !loading && totalFailure && /* @__PURE__ */ import_react9.default.createElement(import_Alert4.default, { severity: "error", sx: { mt: 1 } }, usageError ?? "Failed to load usage data"), !loading && !totalFailure && /* @__PURE__ */ import_react9.default.createElement(import_react9.default.Fragment, null, partialFailure && /* @__PURE__ */ import_react9.default.createElement(import_Alert4.default, { severity: "warning", sx: { mt: 1, mb: 1 } }, usageError ? `Usage data unavailable (${usageError}).` : "", keysError ? ` Key list unavailable (${keysError}).` : "", " Showing what loaded."), /* @__PURE__ */ import_react9.default.createElement(import_Grid2.default, { container: true, spacing: 2, sx: { mb: hasSparkline ? 1.5 : 0 } }, /* @__PURE__ */ import_react9.default.createElement(import_Grid2.default, { item: true, xs: 6 }, /* @__PURE__ */ import_react9.default.createElement(Kpi, { label: "USD Spent", value: fmtUsd(usage?.total_spend ?? 0) })), /* @__PURE__ */ import_react9.default.createElement(import_Grid2.default, { item: true, xs: 6 }, /* @__PURE__ */ import_react9.default.createElement(Kpi, { label: "Tokens In", value: fmtInt(usage?.prompt_tokens ?? 0) })), /* @__PURE__ */ import_react9.default.createElement(import_Grid2.default, { item: true, xs: 6 }, /* @__PURE__ */ import_react9.default.createElement(Kpi, { label: "Tokens Out", value: fmtInt(usage?.completion_tokens ?? 0) })), /* @__PURE__ */ import_react9.default.createElement(import_Grid2.default, { item: true, xs: 6 }, /* @__PURE__ */ import_react9.default.createElement(Kpi, { label: "Keys", value: fmtInt(keys.length) }))), hasSparkline && /* @__PURE__ */ import_react9.default.createElement(import_Box8.default, { height: 120 }, /* @__PURE__ */ import_react9.default.createElement(import_recharts3.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react9.default.createElement(import_recharts3.AreaChart, { data: dailyData, margin: { top: 4, right: 0, bottom: 0, left: 0 } }, /* @__PURE__ */ import_react9.default.createElement(
3076
+ /* @__PURE__ */ import_react10.default.createElement(import_MenuItem4.default, { value: "today" }, "Today"),
3077
+ /* @__PURE__ */ import_react10.default.createElement(import_MenuItem4.default, { value: "7d" }, "7d"),
3078
+ /* @__PURE__ */ import_react10.default.createElement(import_MenuItem4.default, { value: "30d" }, "30d")
3079
+ ))), loading && /* @__PURE__ */ import_react10.default.createElement(import_Box9.default, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: 120 }, /* @__PURE__ */ import_react10.default.createElement(import_CircularProgress5.default, { size: 32 })), !loading && totalFailure && /* @__PURE__ */ import_react10.default.createElement(import_Alert4.default, { severity: "error", sx: { mt: 1 } }, usageError ?? "Failed to load usage data"), !loading && !totalFailure && /* @__PURE__ */ import_react10.default.createElement(import_react10.default.Fragment, null, partialFailure && /* @__PURE__ */ import_react10.default.createElement(import_Alert4.default, { severity: "warning", sx: { mt: 1, mb: 1 } }, usageError ? `Usage data unavailable (${usageError}).` : "", keysError ? ` Key list unavailable (${keysError}).` : "", " Showing what loaded."), /* @__PURE__ */ import_react10.default.createElement(import_Grid2.default, { container: true, spacing: 2, sx: { mb: hasSparkline ? 1.5 : 0 } }, /* @__PURE__ */ import_react10.default.createElement(import_Grid2.default, { item: true, xs: 6 }, /* @__PURE__ */ import_react10.default.createElement(Kpi, { label: "USD Spent", value: fmtUsd(usage?.total_spend ?? 0) })), /* @__PURE__ */ import_react10.default.createElement(import_Grid2.default, { item: true, xs: 6 }, /* @__PURE__ */ import_react10.default.createElement(Kpi, { label: "Tokens In", value: fmtInt(usage?.prompt_tokens ?? 0) })), /* @__PURE__ */ import_react10.default.createElement(import_Grid2.default, { item: true, xs: 6 }, /* @__PURE__ */ import_react10.default.createElement(Kpi, { label: "Tokens Out", value: fmtInt(usage?.completion_tokens ?? 0) })), /* @__PURE__ */ import_react10.default.createElement(import_Grid2.default, { item: true, xs: 6 }, /* @__PURE__ */ import_react10.default.createElement(Kpi, { label: "Keys", value: fmtInt(keys.length) }))), hasSparkline && /* @__PURE__ */ import_react10.default.createElement(import_Box9.default, { height: 120 }, /* @__PURE__ */ import_react10.default.createElement(import_recharts3.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react10.default.createElement(import_recharts3.AreaChart, { data: dailyData, margin: { top: 4, right: 0, bottom: 0, left: 0 } }, /* @__PURE__ */ import_react10.default.createElement(
2046
3080
  import_recharts3.Area,
2047
3081
  {
2048
3082
  type: "monotone",