@acarmisc/backstage-plugin-litellm 0.12.4 → 0.13.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,
210
- {
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,
769
+ return /* @__PURE__ */ import_react2.default.createElement(import_Paper2.default, { sx: { borderRadius: 2, overflow: "hidden" } }, /* @__PURE__ */ import_react2.default.createElement(
770
+ import_Box2.default,
227
771
  {
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
  }
@@ -716,13 +1412,13 @@ function formatContextWindow2(maxInput, maxOutput) {
716
1412
  if (outPart) return `ctx ${outPart} out`;
717
1413
  return null;
718
1414
  }
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;
1415
+ 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, SnippetTabs, GenerateKeyDialog;
720
1416
  var init_GenerateKeyDialog = __esm({
721
1417
  "src/components/GenerateKeyDialog.tsx"() {
722
1418
  "use strict";
723
- import_react3 = __toESM(require("react"));
724
- import_Box3 = __toESM(require("@mui/material/Box"));
725
- import_Typography3 = __toESM(require("@mui/material/Typography"));
1419
+ import_react4 = __toESM(require("react"));
1420
+ import_Box4 = __toESM(require("@mui/material/Box"));
1421
+ import_Typography4 = __toESM(require("@mui/material/Typography"));
726
1422
  import_Dialog2 = __toESM(require("@mui/material/Dialog"));
727
1423
  import_DialogTitle2 = __toESM(require("@mui/material/DialogTitle"));
728
1424
  import_DialogContent2 = __toESM(require("@mui/material/DialogContent"));
@@ -756,16 +1452,16 @@ var init_GenerateKeyDialog = __esm({
756
1452
  key_type: "llm_api"
757
1453
  });
758
1454
  SnippetTabs = ({ snippets, model, onCopy }) => {
759
- const [tab, setTab] = (0, import_react3.useState)("curl");
1455
+ const [tab, setTab] = (0, import_react4.useState)("curl");
760
1456
  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,
1457
+ 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 } }, /* @__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(
1458
+ import_Box4.default,
763
1459
  {
764
1460
  position: "relative",
765
1461
  p: 1.5,
766
1462
  sx: { backgroundColor: "action.hover", border: "1px solid", borderColor: "divider", borderRadius: 1 }
767
1463
  },
768
- /* @__PURE__ */ import_react3.default.createElement(
1464
+ /* @__PURE__ */ import_react4.default.createElement(
769
1465
  import_IconButton2.default,
770
1466
  {
771
1467
  size: "small",
@@ -773,10 +1469,10 @@ var init_GenerateKeyDialog = __esm({
773
1469
  title: "Copy snippet",
774
1470
  sx: { position: "absolute", top: 4, right: 4 }
775
1471
  },
776
- /* @__PURE__ */ import_react3.default.createElement(import_icons_material3.ContentCopy, { fontSize: "small" })
1472
+ /* @__PURE__ */ import_react4.default.createElement(import_icons_material3.ContentCopy, { fontSize: "small" })
777
1473
  ),
778
- /* @__PURE__ */ import_react3.default.createElement(
779
- import_Typography3.default,
1474
+ /* @__PURE__ */ import_react4.default.createElement(
1475
+ import_Typography4.default,
780
1476
  {
781
1477
  component: "pre",
782
1478
  sx: {
@@ -790,7 +1486,7 @@ var init_GenerateKeyDialog = __esm({
790
1486
  },
791
1487
  code
792
1488
  ),
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.")
1489
+ 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
1490
  ));
795
1491
  };
796
1492
  GenerateKeyDialog = ({
@@ -804,14 +1500,14 @@ var init_GenerateKeyDialog = __esm({
804
1500
  onGenerateKey,
805
1501
  onGetConfig
806
1502
  }) => {
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)(() => {
1503
+ const [formData, setFormData] = (0, import_react4.useState)(emptyForm());
1504
+ const [unlimitedBudget, setUnlimitedBudget] = (0, import_react4.useState)(false);
1505
+ const [submitting, setSubmitting] = (0, import_react4.useState)(false);
1506
+ const [newKeyValue, setNewKeyValue] = (0, import_react4.useState)(null);
1507
+ const [newKeySnippets, setNewKeySnippets] = (0, import_react4.useState)(null);
1508
+ const [newKeyModel, setNewKeyModel] = (0, import_react4.useState)("");
1509
+ const [generateError, setGenerateError] = (0, import_react4.useState)(null);
1510
+ (0, import_react4.useEffect)(() => {
815
1511
  if (open) {
816
1512
  setFormData({ ...emptyForm(), alias: generateDefaultAlias(username) });
817
1513
  setUnlimitedBudget(false);
@@ -824,7 +1520,7 @@ var init_GenerateKeyDialog = __esm({
824
1520
  const allowUnlimitedBudget = keyGenerationSettings?.allowUnlimitedBudget ?? false;
825
1521
  const teamRequired = keyGenerationSettings?.teamRequired ?? true;
826
1522
  const selectedTeam = teams.find((t) => t.team_id === formData.team_id) ?? null;
827
- const availableModels = (0, import_react3.useMemo)(() => {
1523
+ const availableModels = (0, import_react4.useMemo)(() => {
828
1524
  const teamModels = selectedTeam?.models;
829
1525
  if (teamModels && teamModels.length > 0) {
830
1526
  const allowed = new Set(teamModels);
@@ -838,7 +1534,7 @@ var init_GenerateKeyDialog = __esm({
838
1534
  const budgetInvalid = !unlimitedBudget && (formData.max_budget === void 0 || formData.max_budget === null || formData.max_budget <= 0);
839
1535
  const canGenerate = !aliasError && !teamError && !budgetInvalid && !submitting;
840
1536
  const aliasDuplicate = !!formData.alias && keys.some((k) => k.key_alias === formData.alias);
841
- const budgetEstimate = (0, import_react3.useMemo)(() => {
1537
+ const budgetEstimate = (0, import_react4.useMemo)(() => {
842
1538
  if (unlimitedBudget || budgetInvalid) return null;
843
1539
  const inputCosts = selectedModels.map((m) => m.input_cost_per_token).filter((c) => typeof c === "number" && c > 0);
844
1540
  if (inputCosts.length === 0) return null;
@@ -886,11 +1582,11 @@ var init_GenerateKeyDialog = __esm({
886
1582
  const inCost = fmtCost2(m.input_cost_per_token);
887
1583
  const outCost = fmtCost2(m.output_cost_per_token);
888
1584
  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"));
1585
+ 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
1586
  };
891
1587
  const renderTeamField = () => {
892
1588
  if (teams.length > 0) {
893
- return /* @__PURE__ */ import_react3.default.createElement(
1589
+ return /* @__PURE__ */ import_react4.default.createElement(
894
1590
  import_Autocomplete2.default,
895
1591
  {
896
1592
  options: teams,
@@ -901,7 +1597,7 @@ var init_GenerateKeyDialog = __esm({
901
1597
  const restrictedModels = teamModels && teamModels.length > 0 ? (formData.models || []).filter((m) => teamModels.includes(m)) : formData.models;
902
1598
  setFormData({ ...formData, team_id: team?.team_id, models: restrictedModels });
903
1599
  },
904
- renderInput: (params) => /* @__PURE__ */ import_react3.default.createElement(
1600
+ renderInput: (params) => /* @__PURE__ */ import_react4.default.createElement(
905
1601
  import_TextField2.default,
906
1602
  {
907
1603
  ...params,
@@ -916,12 +1612,12 @@ var init_GenerateKeyDialog = __esm({
916
1612
  );
917
1613
  }
918
1614
  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.");
1615
+ 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
1616
  }
921
1617
  return null;
922
1618
  };
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,
1619
+ 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(
1620
+ import_Box4.default,
925
1621
  {
926
1622
  display: "flex",
927
1623
  alignItems: "center",
@@ -935,8 +1631,8 @@ var init_GenerateKeyDialog = __esm({
935
1631
  borderRadius: 1
936
1632
  }
937
1633
  },
938
- /* @__PURE__ */ import_react3.default.createElement(
939
- import_Typography3.default,
1634
+ /* @__PURE__ */ import_react4.default.createElement(
1635
+ import_Typography4.default,
940
1636
  {
941
1637
  component: "code",
942
1638
  color: "text.primary",
@@ -944,8 +1640,8 @@ var init_GenerateKeyDialog = __esm({
944
1640
  },
945
1641
  newKeyValue
946
1642
  ),
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(
1643
+ /* @__PURE__ */ import_react4.default.createElement(import_IconButton2.default, { onClick: () => copyToClipboard(newKeyValue) }, /* @__PURE__ */ import_react4.default.createElement(import_icons_material3.ContentCopy, null))
1644
+ ), 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
1645
  import_TextField2.default,
950
1646
  {
951
1647
  label: "Alias",
@@ -960,7 +1656,7 @@ var init_GenerateKeyDialog = __esm({
960
1656
  required: true,
961
1657
  fullWidth: true
962
1658
  }
963
- ), /* @__PURE__ */ import_react3.default.createElement(
1659
+ ), /* @__PURE__ */ import_react4.default.createElement(
964
1660
  import_TextField2.default,
965
1661
  {
966
1662
  select: true,
@@ -969,12 +1665,12 @@ var init_GenerateKeyDialog = __esm({
969
1665
  onChange: (e) => setFormData({ ...formData, duration: e.target.value }),
970
1666
  fullWidth: true
971
1667
  },
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(
1668
+ /* @__PURE__ */ import_react4.default.createElement(import_MenuItem.default, { value: "1d" }, "1 Day"),
1669
+ /* @__PURE__ */ import_react4.default.createElement(import_MenuItem.default, { value: "7d" }, "7 Days"),
1670
+ /* @__PURE__ */ import_react4.default.createElement(import_MenuItem.default, { value: "30d" }, "30 Days"),
1671
+ /* @__PURE__ */ import_react4.default.createElement(import_MenuItem.default, { value: "90d" }, "90 Days"),
1672
+ /* @__PURE__ */ import_react4.default.createElement(import_MenuItem.default, { value: "1y" }, "1 Year")
1673
+ ), renderTeamField(), availableModels.length > 0 && /* @__PURE__ */ import_react4.default.createElement(
978
1674
  import_Autocomplete2.default,
979
1675
  {
980
1676
  multiple: true,
@@ -983,8 +1679,8 @@ var init_GenerateKeyDialog = __esm({
983
1679
  getOptionLabel: (m) => m.model_name,
984
1680
  value: selectedModels,
985
1681
  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(
1682
+ renderOption: (props, m) => /* @__PURE__ */ import_react4.default.createElement("li", { ...props }, modelOption(m)),
1683
+ renderInput: (params) => /* @__PURE__ */ import_react4.default.createElement(
988
1684
  import_TextField2.default,
989
1685
  {
990
1686
  ...params,
@@ -994,10 +1690,10 @@ var init_GenerateKeyDialog = __esm({
994
1690
  }
995
1691
  )
996
1692
  }
997
- ), allowUnlimitedBudget && /* @__PURE__ */ import_react3.default.createElement(
1693
+ ), allowUnlimitedBudget && /* @__PURE__ */ import_react4.default.createElement(
998
1694
  import_FormControlLabel.default,
999
1695
  {
1000
- control: /* @__PURE__ */ import_react3.default.createElement(
1696
+ control: /* @__PURE__ */ import_react4.default.createElement(
1001
1697
  import_Checkbox.default,
1002
1698
  {
1003
1699
  checked: unlimitedBudget,
@@ -1013,7 +1709,7 @@ var init_GenerateKeyDialog = __esm({
1013
1709
  ),
1014
1710
  label: "Unlimited budget"
1015
1711
  }
1016
- ), /* @__PURE__ */ import_react3.default.createElement(
1712
+ ), /* @__PURE__ */ import_react4.default.createElement(
1017
1713
  import_TextField2.default,
1018
1714
  {
1019
1715
  label: "Max Budget (USD)",
@@ -1026,7 +1722,7 @@ var init_GenerateKeyDialog = __esm({
1026
1722
  required: true,
1027
1723
  fullWidth: true
1028
1724
  }
1029
- ), /* @__PURE__ */ import_react3.default.createElement(
1725
+ ), /* @__PURE__ */ import_react4.default.createElement(
1030
1726
  import_TextField2.default,
1031
1727
  {
1032
1728
  label: "TPM Limit",
@@ -1036,7 +1732,7 @@ var init_GenerateKeyDialog = __esm({
1036
1732
  helperText: "Max tokens per minute this key can consume across all models. Leave blank for no limit.",
1037
1733
  fullWidth: true
1038
1734
  }
1039
- ), /* @__PURE__ */ import_react3.default.createElement(
1735
+ ), /* @__PURE__ */ import_react4.default.createElement(
1040
1736
  import_TextField2.default,
1041
1737
  {
1042
1738
  label: "RPM Limit",
@@ -1046,7 +1742,7 @@ var init_GenerateKeyDialog = __esm({
1046
1742
  helperText: "Max requests per minute this key can make across all models. Leave blank for no limit.",
1047
1743
  fullWidth: true
1048
1744
  }
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(
1745
+ ))), /* @__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
1746
  import_Button3.default,
1051
1747
  {
1052
1748
  onClick: handleGenerate,
@@ -1054,75 +1750,65 @@ var init_GenerateKeyDialog = __esm({
1054
1750
  color: "primary",
1055
1751
  disabled: !canGenerate
1056
1752
  },
1057
- submitting ? /* @__PURE__ */ import_react3.default.createElement(import_CircularProgress2.default, { size: 24 }) : "Generate"
1753
+ submitting ? /* @__PURE__ */ import_react4.default.createElement(import_CircularProgress2.default, { size: 24 }) : "Generate"
1058
1754
  ))));
1059
1755
  };
1060
1756
  }
1061
1757
  });
1062
1758
 
1063
1759
  // 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];
1760
+ function rateTone(rate) {
1761
+ if (rate >= 0.99) return "success";
1762
+ if (rate >= 0.9) return "warning";
1763
+ return "danger";
1068
1764
  }
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;
1765
+ 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
1766
  var init_UsageStats = __esm({
1071
1767
  "src/components/UsageStats.tsx"() {
1072
1768
  "use strict";
1073
- import_react4 = __toESM(require("react"));
1769
+ import_react5 = __toESM(require("react"));
1074
1770
  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"));
1771
+ import_Box5 = __toESM(require("@mui/material/Box"));
1772
+ import_Typography5 = __toESM(require("@mui/material/Typography"));
1773
+ import_TextField3 = __toESM(require("@mui/material/TextField"));
1080
1774
  import_MenuItem2 = __toESM(require("@mui/material/MenuItem"));
1081
1775
  import_Grid = __toESM(require("@mui/material/Grid"));
1082
- import_Tabs2 = __toESM(require("@mui/material/Tabs"));
1083
- import_Tab2 = __toESM(require("@mui/material/Tab"));
1084
1776
  import_Table2 = __toESM(require("@mui/material/Table"));
1085
1777
  import_TableBody2 = __toESM(require("@mui/material/TableBody"));
1086
1778
  import_TableCell2 = __toESM(require("@mui/material/TableCell"));
1087
1779
  import_TableContainer2 = __toESM(require("@mui/material/TableContainer"));
1088
1780
  import_TableHead2 = __toESM(require("@mui/material/TableHead"));
1089
1781
  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");
1782
+ import_Skeleton3 = __toESM(require("@mui/material/Skeleton"));
1094
1783
  import_recharts = require("recharts");
1095
1784
  init_format();
1785
+ init_ui();
1096
1786
  TOP_N_MODELS = 6;
1097
1787
  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
- ];
1788
+ PRESET_LABELS = {
1789
+ today: "Today",
1790
+ "24h": "Last 24 hours",
1791
+ "7d": "Last 7 days",
1792
+ "30d": "Last 30 days"
1793
+ };
1110
1794
  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);
1795
+ ChartSkeleton = ({ height = 240 }) => /* @__PURE__ */ import_react5.default.createElement(import_Skeleton3.default, { variant: "rounded", height });
1796
+ ChartOrFallback = ({ loading, empty, height = 240, children }) => {
1797
+ if (loading) return /* @__PURE__ */ import_react5.default.createElement(ChartSkeleton, { height });
1798
+ if (empty) return /* @__PURE__ */ import_react5.default.createElement(EmptyState, { message: "No data for this period", height });
1799
+ return /* @__PURE__ */ import_react5.default.createElement(import_react5.default.Fragment, null, children);
1800
+ };
1801
+ SuccessRateCell = ({ rate, requests }) => {
1802
+ if (requests === 0) return /* @__PURE__ */ import_react5.default.createElement(import_Typography5.default, { variant: "body2", color: "text.secondary" }, "\u2014");
1803
+ const tone = rateTone(rate);
1804
+ 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(
1805
+ import_Typography5.default,
1806
+ {
1807
+ variant: "caption",
1808
+ sx: { fontVariantNumeric: "tabular-nums", minWidth: 44 }
1809
+ },
1810
+ fmtPct(rate)
1811
+ ));
1126
1812
  };
1127
1813
  UsageStats = ({
1128
1814
  usage,
@@ -1132,15 +1818,10 @@ var init_UsageStats = __esm({
1132
1818
  loading,
1133
1819
  userInfo
1134
1820
  }) => {
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)(() => {
1821
+ const chart = useChartTheme();
1822
+ const [selectedModel, setSelectedModel] = (0, import_react5.useState)("all");
1823
+ const [tab, setTab] = (0, import_react5.useState)("costs");
1824
+ const selectedPreset = (0, import_react5.useMemo)(() => {
1144
1825
  if (dateRange.start.toDateString() === dateRange.end.toDateString()) return "today";
1145
1826
  const diffMs = dateRange.end.getTime() - dateRange.start.getTime();
1146
1827
  const diffHours = diffMs / (1e3 * 60 * 60);
@@ -1162,7 +1843,7 @@ var init_UsageStats = __esm({
1162
1843
  }
1163
1844
  onDateRangeChange({ start, end });
1164
1845
  };
1165
- const dailyData = (0, import_react4.useMemo)(
1846
+ const dailyData = (0, import_react5.useMemo)(
1166
1847
  () => (usage?.daily_usage ?? []).map((d) => ({
1167
1848
  date: d.date,
1168
1849
  spend: d.spend,
@@ -1175,21 +1856,21 @@ var init_UsageStats = __esm({
1175
1856
  })),
1176
1857
  [usage]
1177
1858
  );
1178
- const cumulativeData = (0, import_react4.useMemo)(() => {
1859
+ const cumulativeData = (0, import_react5.useMemo)(() => {
1179
1860
  let cum = 0;
1180
1861
  return dailyData.map((d) => {
1181
1862
  cum += d.spend;
1182
1863
  return { date: d.date, cumulative: cum };
1183
1864
  });
1184
1865
  }, [dailyData]);
1185
- const successRateData = (0, import_react4.useMemo)(
1866
+ const successRateData = (0, import_react5.useMemo)(
1186
1867
  () => dailyData.filter((d) => d.apiRequests > 0).map((d) => ({
1187
1868
  date: d.date,
1188
1869
  successRate: parseFloat((d.successfulRequests / d.apiRequests * 100).toFixed(1))
1189
1870
  })),
1190
1871
  [dailyData]
1191
1872
  );
1192
- const { modelSpendByDate, topModels: topSpendModels } = (0, import_react4.useMemo)(() => {
1873
+ const { modelSpendByDate, topModels: topSpendModels } = (0, import_react5.useMemo)(() => {
1193
1874
  const rows = usage?.daily_by_model ?? [];
1194
1875
  const modelTotals = {};
1195
1876
  for (const r of rows) modelTotals[r.model] = (modelTotals[r.model] ?? 0) + r.spend;
@@ -1204,7 +1885,7 @@ var init_UsageStats = __esm({
1204
1885
  const hasOther = sorted.some((r) => r.Other > 0);
1205
1886
  return { modelSpendByDate: sorted, topModels: hasOther ? [...top, "Other"] : top };
1206
1887
  }, [usage]);
1207
- const modelRows = (0, import_react4.useMemo)(() => {
1888
+ const modelRows = (0, import_react5.useMemo)(() => {
1208
1889
  const entries = Object.entries(usage?.usage_by_model ?? {}).map(([model, d]) => ({
1209
1890
  model,
1210
1891
  spend: d.total_spend,
@@ -1218,11 +1899,11 @@ var init_UsageStats = __esm({
1218
1899
  }));
1219
1900
  return selectedModel === "all" ? entries : entries.filter((e) => e.model === selectedModel);
1220
1901
  }, [usage, selectedModel]);
1221
- const topModelSpendBars = (0, import_react4.useMemo)(
1902
+ const topModelSpendBars = (0, import_react5.useMemo)(
1222
1903
  () => [...modelRows].sort((a, b) => b.spend - a.spend).slice(0, 10),
1223
1904
  [modelRows]
1224
1905
  );
1225
- const keyRows = (0, import_react4.useMemo)(
1906
+ const keyRows = (0, import_react5.useMemo)(
1226
1907
  () => Object.entries(usage?.usage_by_key ?? {}).map(([keyHash, d]) => ({
1227
1908
  keyHash,
1228
1909
  keyAlias: d.key_alias ?? keyHash.slice(0, 8),
@@ -1239,7 +1920,7 @@ var init_UsageStats = __esm({
1239
1920
  })),
1240
1921
  [usage]
1241
1922
  );
1242
- const topKeySpendBars = (0, import_react4.useMemo)(
1923
+ const topKeySpendBars = (0, import_react5.useMemo)(
1243
1924
  () => [...keyRows].sort((a, b) => b.spend - a.spend).slice(0, 10),
1244
1925
  [keyRows]
1245
1926
  );
@@ -1247,99 +1928,234 @@ var init_UsageStats = __esm({
1247
1928
  const overallSuccessRate = totalRequests > 0 ? (usage?.successful_requests ?? 0) / totalRequests : 0;
1248
1929
  const maxBudget = userInfo?.max_budget ?? 0;
1249
1930
  const totalCumSpend = cumulativeData[cumulativeData.length - 1]?.cumulative ?? 0;
1931
+ const cumulativeDomain = maxBudget > 0 ? [0, (dataMax) => Math.max(dataMax, maxBudget) * 1.05] : [0, "auto"];
1932
+ const successTone = totalRequests === 0 ? "neutral" : rateTone(overallSuccessRate);
1933
+ const metrics = [
1934
+ {
1935
+ label: "Total spend",
1936
+ value: fmtUsd(usage?.total_spend ?? 0),
1937
+ hint: maxBudget > 0 ? `of ${fmtUsd(maxBudget)} budget` : void 0,
1938
+ tone: "accent"
1939
+ },
1940
+ {
1941
+ label: "Requests",
1942
+ value: fmtInt(totalRequests),
1943
+ hint: `${fmtInt(usage?.failed_requests ?? 0)} failed`,
1944
+ tone: "info"
1945
+ },
1946
+ {
1947
+ label: "Success rate",
1948
+ value: totalRequests > 0 ? fmtPct(overallSuccessRate) : "\u2014",
1949
+ hint: totalRequests > 0 ? `${fmtInt(usage?.successful_requests ?? 0)} succeeded` : void 0,
1950
+ tone: successTone
1951
+ },
1952
+ {
1953
+ label: "Tokens",
1954
+ value: fmtCompact(usage?.total_tokens ?? 0),
1955
+ hint: `${fmtCompact(usage?.prompt_tokens ?? 0)} in \xB7 ${fmtCompact(usage?.completion_tokens ?? 0)} out`,
1956
+ tone: "success"
1957
+ }
1958
+ ];
1250
1959
  const renderKeyRows = () => {
1251
1960
  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)));
1961
+ 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
1962
  }
1254
1963
  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"));
1964
+ 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
1965
  }
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")));
1966
+ 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
1967
  };
1266
1968
  const renderModelRows = () => {
1267
1969
  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)));
1970
+ 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
1971
  }
1270
1972
  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"));
1973
+ 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
1974
  }
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")));
1975
+ 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
1976
  };
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,
1977
+ const periodSelect = /* @__PURE__ */ import_react5.default.createElement(
1978
+ import_TextField3.default,
1284
1979
  {
1285
- value: selectedPreset,
1980
+ select: true,
1981
+ size: "small",
1286
1982
  label: "Period",
1287
- onChange: (e) => handlePresetChange(e.target.value)
1983
+ value: selectedPreset,
1984
+ onChange: (e) => handlePresetChange(e.target.value),
1985
+ sx: { minWidth: 160 }
1288
1986
  },
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,
1987
+ Object.keys(PRESET_LABELS).map((p) => /* @__PURE__ */ import_react5.default.createElement(import_MenuItem2.default, { key: p, value: p }, PRESET_LABELS[p]))
1988
+ );
1989
+ return /* @__PURE__ */ import_react5.default.createElement(
1990
+ SectionCard,
1295
1991
  {
1296
- value: selectedModel,
1297
- label: "Model",
1298
- onChange: (e) => setSelectedModel(e.target.value)
1992
+ title: "Usage Analytics",
1993
+ subtitle: `${PRESET_LABELS[selectedPreset]} \xB7 ${dateRange.start.toLocaleDateString()} \u2013 ${dateRange.end.toLocaleDateString()}`,
1994
+ actions: periodSelect
1299
1995
  },
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()))))));
1996
+ /* @__PURE__ */ import_react5.default.createElement(MetricStrip, { metrics }),
1997
+ /* @__PURE__ */ import_react5.default.createElement(
1998
+ import_Box5.default,
1999
+ {
2000
+ sx: {
2001
+ display: "flex",
2002
+ alignItems: "center",
2003
+ justifyContent: "space-between",
2004
+ gap: 2,
2005
+ flexWrap: "wrap",
2006
+ mt: 2.5,
2007
+ mb: 2
2008
+ }
2009
+ },
2010
+ /* @__PURE__ */ import_react5.default.createElement(
2011
+ SegmentedControl,
2012
+ {
2013
+ value: tab,
2014
+ onChange: setTab,
2015
+ options: [
2016
+ { value: "costs", label: "Costs" },
2017
+ { value: "models", label: "Model Activity" },
2018
+ { value: "keys", label: "Key Activity" }
2019
+ ]
2020
+ }
2021
+ ),
2022
+ tab === "models" && /* @__PURE__ */ import_react5.default.createElement(
2023
+ import_TextField3.default,
2024
+ {
2025
+ select: true,
2026
+ size: "small",
2027
+ label: "Model",
2028
+ value: selectedModel,
2029
+ onChange: (e) => setSelectedModel(e.target.value),
2030
+ sx: { minWidth: 220 }
2031
+ },
2032
+ /* @__PURE__ */ import_react5.default.createElement(import_MenuItem2.default, { value: "all" }, "All models"),
2033
+ models.map((m) => /* @__PURE__ */ import_react5.default.createElement(import_MenuItem2.default, { key: m.model_name, value: m.model_name }, m.model_name))
2034
+ )
2035
+ ),
2036
+ 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(
2037
+ import_recharts.Tooltip,
2038
+ {
2039
+ cursor: chart.cursor,
2040
+ content: /* @__PURE__ */ import_react5.default.createElement(ChartTooltip, { valueFormatter: fmtUsd, hideZero: true })
2041
+ }
2042
+ ), topSpendModels.map((m) => /* @__PURE__ */ import_react5.default.createElement(
2043
+ import_recharts.Area,
2044
+ {
2045
+ key: m,
2046
+ type: "monotone",
2047
+ dataKey: m,
2048
+ stackId: "spend",
2049
+ stroke: chartColor(m),
2050
+ strokeWidth: 1.5,
2051
+ fill: chartColor(m),
2052
+ fillOpacity: 0.28
2053
+ }
2054
+ ))))), !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(
2055
+ import_recharts.Tooltip,
2056
+ {
2057
+ cursor: chart.cursor,
2058
+ content: /* @__PURE__ */ import_react5.default.createElement(ChartTooltip, { valueFormatter: (v) => `${v}%` })
2059
+ }
2060
+ ), /* @__PURE__ */ import_react5.default.createElement(import_recharts.ReferenceLine, { y: 100, stroke: SERIES.success, strokeDasharray: "4 4", strokeOpacity: 0.5 }), /* @__PURE__ */ import_react5.default.createElement(
2061
+ import_recharts.Line,
2062
+ {
2063
+ type: "monotone",
2064
+ dataKey: "successRate",
2065
+ name: "Success rate",
2066
+ stroke: SERIES.input,
2067
+ strokeWidth: 2,
2068
+ dot: { r: 2.5, strokeWidth: 0, fill: SERIES.input },
2069
+ activeDot: { r: 4 }
2070
+ }
2071
+ )))))), /* @__PURE__ */ import_react5.default.createElement(import_Grid.default, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ import_react5.default.createElement(
2072
+ ChartCard,
2073
+ {
2074
+ title: maxBudget > 0 ? "Cumulative spend vs budget" : "Cumulative spend",
2075
+ meta: maxBudget > 0 ? `${fmtUsd(totalCumSpend)} used \xB7 ${fmtUsd(Math.max(0, maxBudget - totalCumSpend))} left` : void 0
2076
+ },
2077
+ /* @__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(
2078
+ import_recharts.YAxis,
2079
+ {
2080
+ tickFormatter: fmtUsdCompact,
2081
+ width: 56,
2082
+ domain: cumulativeDomain,
2083
+ ...chart.axis
2084
+ }
2085
+ ), /* @__PURE__ */ import_react5.default.createElement(
2086
+ import_recharts.Tooltip,
2087
+ {
2088
+ cursor: chart.cursor,
2089
+ content: /* @__PURE__ */ import_react5.default.createElement(ChartTooltip, { valueFormatter: fmtUsd })
2090
+ }
2091
+ ), maxBudget > 0 && /* @__PURE__ */ import_react5.default.createElement(
2092
+ import_recharts.ReferenceLine,
2093
+ {
2094
+ y: maxBudget,
2095
+ stroke: SERIES.budget,
2096
+ strokeDasharray: "5 4",
2097
+ label: { value: `Budget ${fmtUsd(maxBudget)}`, position: "insideTopRight", fontSize: 10, fill: SERIES.budget }
2098
+ }
2099
+ ), /* @__PURE__ */ import_react5.default.createElement(
2100
+ import_recharts.Area,
2101
+ {
2102
+ type: "monotone",
2103
+ dataKey: "cumulative",
2104
+ name: "Cumulative spend",
2105
+ stroke: SERIES.spend,
2106
+ strokeWidth: 2,
2107
+ fill: SERIES.spend,
2108
+ fillOpacity: 0.18
2109
+ }
2110
+ ))))
2111
+ ))),
2112
+ 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(
2113
+ ChartOrFallback,
2114
+ {
2115
+ loading,
2116
+ empty: topModelSpendBars.length === 0,
2117
+ height: Math.max(200, topModelSpendBars.length * 34)
2118
+ },
2119
+ /* @__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) })))))
2120
+ ))), /* @__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(
2121
+ ChartOrFallback,
2122
+ {
2123
+ loading,
2124
+ empty: modelRows.length === 0,
2125
+ height: Math.max(200, topModelSpendBars.length * 34)
2126
+ },
2127
+ /* @__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] })))
2128
+ ))), /* @__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()))))),
2129
+ 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(
2130
+ ChartOrFallback,
2131
+ {
2132
+ loading,
2133
+ empty: topKeySpendBars.length === 0,
2134
+ height: Math.max(160, topKeySpendBars.length * 32)
2135
+ },
2136
+ /* @__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 })))
2137
+ ))), /* @__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())))))
2138
+ );
1321
2139
  };
1322
2140
  }
1323
2141
  });
1324
2142
 
1325
2143
  // src/components/TeamUsage.tsx
1326
- function budgetBarColor(isOver, isNear) {
1327
- if (isOver) return "error";
2144
+ function budgetTone2(isOver, isNear) {
2145
+ if (isOver) return "danger";
1328
2146
  if (isNear) return "warning";
1329
- return "primary";
2147
+ return "accent";
1330
2148
  }
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;
2149
+ 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
2150
  var init_TeamUsage = __esm({
1333
2151
  "src/components/TeamUsage.tsx"() {
1334
2152
  "use strict";
1335
- import_react5 = __toESM(require("react"));
2153
+ import_react6 = __toESM(require("react"));
1336
2154
  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"));
2155
+ import_Box6 = __toESM(require("@mui/material/Box"));
2156
+ import_Typography6 = __toESM(require("@mui/material/Typography"));
2157
+ import_Skeleton4 = __toESM(require("@mui/material/Skeleton"));
2158
+ import_Divider2 = __toESM(require("@mui/material/Divider"));
1343
2159
  import_Table3 = __toESM(require("@mui/material/Table"));
1344
2160
  import_TableBody3 = __toESM(require("@mui/material/TableBody"));
1345
2161
  import_TableCell3 = __toESM(require("@mui/material/TableCell"));
@@ -1349,15 +2165,14 @@ var init_TeamUsage = __esm({
1349
2165
  import_Collapse = __toESM(require("@mui/material/Collapse"));
1350
2166
  import_IconButton3 = __toESM(require("@mui/material/IconButton"));
1351
2167
  import_CircularProgress3 = __toESM(require("@mui/material/CircularProgress"));
1352
- import_styles2 = require("@mui/material/styles");
2168
+ import_styles4 = require("@mui/material/styles");
1353
2169
  import_icons_material4 = require("@mui/icons-material");
1354
2170
  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));
2171
+ init_ui();
2172
+ fmtUsd2 = (n) => `$${(n ?? 0).toFixed(2)}`;
1356
2173
  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;
2174
+ const [expanded, setExpanded] = (0, import_react6.useState)(false);
2175
+ const chart = useChartTheme();
1361
2176
  const budget = team.max_budget ?? 0;
1362
2177
  const spend = team.spend ?? 0;
1363
2178
  const budgetPct = budget > 0 ? Math.min(spend / budget * 100, 100) : 0;
@@ -1366,73 +2181,131 @@ var init_TeamUsage = __esm({
1366
2181
  const dailyData = usage?.daily_usage?.map((d) => ({ date: d.date, spend: d.spend })) ?? [];
1367
2182
  const renderDailySpendSection = () => {
1368
2183
  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 } }));
2184
+ 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
2185
  }
1374
- return null;
2186
+ if (dailyData.length === 0) return null;
2187
+ return /* @__PURE__ */ import_react6.default.createElement(import_Box6.default, { mb: 2.5 }, /* @__PURE__ */ import_react6.default.createElement(
2188
+ import_Typography6.default,
2189
+ {
2190
+ variant: "caption",
2191
+ color: "text.secondary",
2192
+ sx: { display: "block", fontSize: 11, fontWeight: 700, letterSpacing: "0.07em", textTransform: "uppercase", mb: 1 }
2193
+ },
2194
+ "Daily spend"
2195
+ ), /* @__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(
2196
+ import_recharts2.Tooltip,
2197
+ {
2198
+ cursor: chart.cursor,
2199
+ content: /* @__PURE__ */ import_react6.default.createElement(ChartTooltip, { valueFormatter: (v) => `$${v.toFixed(4)}` })
2200
+ }
2201
+ ), /* @__PURE__ */ import_react6.default.createElement(
2202
+ import_recharts2.Area,
2203
+ {
2204
+ type: "monotone",
2205
+ dataKey: "spend",
2206
+ name: "Spend",
2207
+ stroke: SERIES.spend,
2208
+ strokeWidth: 2,
2209
+ fill: SERIES.spend,
2210
+ fillOpacity: 0.18
2211
+ }
2212
+ )))));
1375
2213
  };
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,
1384
- {
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,
2214
+ const sectionLabel = (label) => /* @__PURE__ */ import_react6.default.createElement(
2215
+ import_Typography6.default,
1402
2216
  {
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,
2217
+ variant: "caption",
2218
+ color: "text.secondary",
2219
+ sx: { display: "block", fontSize: 11, fontWeight: 700, letterSpacing: "0.07em", textTransform: "uppercase", mb: 1 }
2220
+ },
2221
+ label
2222
+ );
2223
+ 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(
2224
+ import_Box6.default,
1414
2225
  {
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,
2226
+ sx: (theme) => ({
2227
+ width: 36,
2228
+ height: 36,
2229
+ borderRadius: 1.5,
2230
+ flexShrink: 0,
2231
+ display: "flex",
2232
+ alignItems: "center",
2233
+ justifyContent: "center",
2234
+ bgcolor: (0, import_styles4.alpha)(theme.palette.primary.main, 0.1),
2235
+ color: theme.palette.primary.main
2236
+ })
2237
+ },
2238
+ /* @__PURE__ */ import_react6.default.createElement(import_icons_material4.Group, { fontSize: "small" })
2239
+ ), /* @__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(
2240
+ import_Typography6.default,
1422
2241
  {
1423
2242
  variant: "caption",
1424
2243
  color: "text.secondary",
1425
- sx: { fontFamily: "monospace" }
2244
+ sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 11 }
1426
2245
  },
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,
2246
+ team.team_id
2247
+ )), 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(
2248
+ import_IconButton3.default,
1430
2249
  {
1431
- label: m.role,
1432
2250
  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."))));
2251
+ onClick: () => setExpanded((e) => !e),
2252
+ "aria-label": expanded ? "Hide team details" : "Show team details",
2253
+ "aria-expanded": expanded,
2254
+ sx: (theme) => ({
2255
+ color: theme.palette.text.secondary,
2256
+ transform: expanded ? "rotate(180deg)" : "none",
2257
+ transition: theme.transitions.create("transform")
2258
+ })
2259
+ },
2260
+ /* @__PURE__ */ import_react6.default.createElement(import_icons_material4.ExpandMore, null)
2261
+ )), /* @__PURE__ */ import_react6.default.createElement(
2262
+ import_Box6.default,
2263
+ {
2264
+ sx: {
2265
+ display: "grid",
2266
+ gridTemplateColumns: { xs: "repeat(3, minmax(0, 1fr))", sm: "repeat(6, minmax(0, 1fr))" },
2267
+ gap: 2,
2268
+ mt: 2.5,
2269
+ maxWidth: 640
2270
+ }
2271
+ },
2272
+ /* @__PURE__ */ import_react6.default.createElement(Stat, { label: "Members", value: team.members_with_roles?.length ?? "\u2014" }),
2273
+ /* @__PURE__ */ import_react6.default.createElement(Stat, { label: "Models", value: team.models?.length ? team.models.length : "All" }),
2274
+ /* @__PURE__ */ import_react6.default.createElement(Stat, { label: "Budget", value: budget > 0 ? fmtUsd2(budget) : "Unlimited" }),
2275
+ /* @__PURE__ */ import_react6.default.createElement(Stat, { label: "Spend", value: fmtUsd2(spend) }),
2276
+ /* @__PURE__ */ import_react6.default.createElement(Stat, { label: "TPM", value: team.tpm_limit && team.tpm_limit > 0 ? team.tpm_limit.toLocaleString() : "\u2014" }),
2277
+ /* @__PURE__ */ import_react6.default.createElement(Stat, { label: "RPM", value: team.rpm_limit && team.rpm_limit > 0 ? team.rpm_limit.toLocaleString() : "\u2014" })
2278
+ ), 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(
2279
+ import_TableContainer3.default,
2280
+ {
2281
+ component: import_Paper4.default,
2282
+ variant: "outlined",
2283
+ sx: { borderRadius: 1.5 }
2284
+ },
2285
+ /* @__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(
2286
+ import_Typography6.default,
2287
+ {
2288
+ variant: "caption",
2289
+ color: "text.secondary",
2290
+ sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 11 }
2291
+ },
2292
+ m.user_id
2293
+ )) : /* @__PURE__ */ import_react6.default.createElement(
2294
+ import_Typography6.default,
2295
+ {
2296
+ variant: "body2",
2297
+ sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace" }
2298
+ },
2299
+ m.user_id
2300
+ )), /* @__PURE__ */ import_react6.default.createElement(import_TableCell3.default, { align: "right" }, /* @__PURE__ */ import_react6.default.createElement(
2301
+ StatusPill,
2302
+ {
2303
+ label: m.role,
2304
+ tone: m.role === "admin" ? "accent" : "neutral",
2305
+ dot: false
2306
+ }
2307
+ ))))))
2308
+ ) : /* @__PURE__ */ import_react6.default.createElement(import_Typography6.default, { variant: "body2", color: "text.secondary" }, "No members assigned."))));
1436
2309
  };
1437
2310
  TeamUsage = ({
1438
2311
  teams,
@@ -1441,12 +2314,18 @@ var init_TeamUsage = __esm({
1441
2314
  getTeamUsageLoading
1442
2315
  }) => {
1443
2316
  if (loading) {
1444
- return /* @__PURE__ */ import_react5.default.createElement(import_Paper4.default, { sx: { p: 2 } }, /* @__PURE__ */ import_react5.default.createElement(import_LinearProgress4.default, null));
2317
+ 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
2318
  }
1446
2319
  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."))));
2320
+ return /* @__PURE__ */ import_react6.default.createElement(SectionCard, { title: "Teams" }, /* @__PURE__ */ import_react6.default.createElement(
2321
+ EmptyState,
2322
+ {
2323
+ message: "You're not a member of any LiteLLM team yet.",
2324
+ 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."
2325
+ }
2326
+ ));
1448
2327
  }
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(
2328
+ 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
2329
  TeamCard,
1451
2330
  {
1452
2331
  key: team.team_id,
@@ -1454,19 +2333,23 @@ var init_TeamUsage = __esm({
1454
2333
  usage: getTeamUsage(team.team_id),
1455
2334
  usageLoading: getTeamUsageLoading(team.team_id)
1456
2335
  }
1457
- )));
2336
+ ))));
1458
2337
  };
1459
2338
  }
1460
2339
  });
1461
2340
 
1462
2341
  // 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;
2342
+ function actionTone(action) {
2343
+ if (!action) return "neutral";
2344
+ for (const [key, tone] of Object.entries(ACTION_TONES)) {
2345
+ if (action.toLowerCase().includes(key)) return tone;
1467
2346
  }
1468
2347
  return "info";
1469
2348
  }
2349
+ function prettyTableName(name) {
2350
+ if (!name) return "\u2014";
2351
+ return TABLE_LABELS[name] ?? name;
2352
+ }
1470
2353
  function formatDateTime(iso) {
1471
2354
  try {
1472
2355
  return new Date(iso).toLocaleString();
@@ -1476,21 +2359,26 @@ function formatDateTime(iso) {
1476
2359
  }
1477
2360
  function renderAuditLogBody(loading, entries) {
1478
2361
  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 })));
2362
+ 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
2363
  }
1481
2364
  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")));
2365
+ 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(
2366
+ EmptyState,
2367
+ {
2368
+ message: "No audit events found",
2369
+ hint: "Try widening the filters or a different time window."
2370
+ }
2371
+ )));
1483
2372
  }
1484
- return entries.map((entry) => /* @__PURE__ */ import_react6.default.createElement(DetailRow, { key: entry.id, entry }));
2373
+ return entries.map((entry) => /* @__PURE__ */ import_react7.default.createElement(DetailRow, { key: entry.id, entry }));
1485
2374
  }
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;
2375
+ 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
2376
  var init_AuditLog = __esm({
1488
2377
  "src/components/AuditLog.tsx"() {
1489
2378
  "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"));
2379
+ import_react7 = __toESM(require("react"));
2380
+ import_Box7 = __toESM(require("@mui/material/Box"));
2381
+ import_Typography7 = __toESM(require("@mui/material/Typography"));
1494
2382
  import_Table4 = __toESM(require("@mui/material/Table"));
1495
2383
  import_TableBody4 = __toESM(require("@mui/material/TableBody"));
1496
2384
  import_TableCell4 = __toESM(require("@mui/material/TableCell"));
@@ -1498,32 +2386,97 @@ var init_AuditLog = __esm({
1498
2386
  import_TableHead4 = __toESM(require("@mui/material/TableHead"));
1499
2387
  import_TableRow4 = __toESM(require("@mui/material/TableRow"));
1500
2388
  import_TablePagination = __toESM(require("@mui/material/TablePagination"));
1501
- import_TextField3 = __toESM(require("@mui/material/TextField"));
2389
+ import_TextField4 = __toESM(require("@mui/material/TextField"));
1502
2390
  import_MenuItem3 = __toESM(require("@mui/material/MenuItem"));
1503
- import_Chip5 = __toESM(require("@mui/material/Chip"));
1504
- import_CircularProgress4 = __toESM(require("@mui/material/CircularProgress"));
2391
+ import_Skeleton5 = __toESM(require("@mui/material/Skeleton"));
1505
2392
  import_Collapse2 = __toESM(require("@mui/material/Collapse"));
1506
2393
  import_IconButton4 = __toESM(require("@mui/material/IconButton"));
1507
2394
  import_Alert2 = __toESM(require("@mui/material/Alert"));
2395
+ import_styles5 = require("@mui/material/styles");
1508
2396
  import_icons_material5 = require("@mui/icons-material");
1509
2397
  import_react_use = require("react-use");
1510
- ACTION_COLORS = {
2398
+ init_ui();
2399
+ ACTION_TONES = {
1511
2400
  created: "success",
1512
- deleted: "error",
2401
+ deleted: "danger",
1513
2402
  updated: "warning",
1514
- blocked: "error",
2403
+ blocked: "danger",
1515
2404
  unblocked: "success"
1516
2405
  };
2406
+ TABLE_LABELS = {
2407
+ LiteLLM_VerificationToken: "Key",
2408
+ LiteLLM_TeamTable: "Team",
2409
+ LiteLLM_UserTable: "User"
2410
+ };
1517
2411
  DetailRow = ({ entry }) => {
1518
- const [open, setOpen] = (0, import_react6.useState)(false);
2412
+ const [open, setOpen] = (0, import_react7.useState)(false);
1519
2413
  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))))))));
2414
+ 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(
2415
+ import_IconButton4.default,
2416
+ {
2417
+ size: "small",
2418
+ onClick: () => setOpen((o) => !o),
2419
+ "aria-label": open ? "Hide changes" : "Show changes",
2420
+ "aria-expanded": open,
2421
+ sx: (theme) => ({
2422
+ color: theme.palette.text.secondary,
2423
+ transform: open ? "rotate(180deg)" : "none",
2424
+ transition: theme.transitions.create("transform")
2425
+ })
2426
+ },
2427
+ /* @__PURE__ */ import_react7.default.createElement(import_icons_material5.KeyboardArrowDown, { fontSize: "small" })
2428
+ )), /* @__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(
2429
+ import_Typography7.default,
2430
+ {
2431
+ variant: "body2",
2432
+ component: "code",
2433
+ color: "text.secondary",
2434
+ title: entry.object_id ?? void 0,
2435
+ sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 12 }
2436
+ },
2437
+ entry.object_id ? entry.object_id.slice(0, 20) + (entry.object_id.length > 20 ? "\u2026" : "") : "\u2014"
2438
+ )), /* @__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(
2439
+ import_Box7.default,
2440
+ {
2441
+ display: "flex",
2442
+ gap: 2,
2443
+ flexWrap: "wrap",
2444
+ sx: (theme) => ({
2445
+ p: 2,
2446
+ mb: 1.5,
2447
+ borderRadius: 1.5,
2448
+ bgcolor: (0, import_styles5.alpha)(theme.palette.text.primary, theme.palette.mode === "dark" ? 0.05 : 0.03)
2449
+ })
2450
+ },
2451
+ entry.before_value && /* @__PURE__ */ import_react7.default.createElement(import_Box7.default, { flex: 1, minWidth: 200 }, /* @__PURE__ */ import_react7.default.createElement(
2452
+ import_Typography7.default,
2453
+ {
2454
+ variant: "caption",
2455
+ color: "text.secondary",
2456
+ display: "block",
2457
+ mb: 0.75,
2458
+ sx: { fontSize: 10.5, fontWeight: 700, letterSpacing: "0.07em", textTransform: "uppercase" }
2459
+ },
2460
+ "Before"
2461
+ ), /* @__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))),
2462
+ entry.updated_values && /* @__PURE__ */ import_react7.default.createElement(import_Box7.default, { flex: 1, minWidth: 200 }, /* @__PURE__ */ import_react7.default.createElement(
2463
+ import_Typography7.default,
2464
+ {
2465
+ variant: "caption",
2466
+ color: "text.secondary",
2467
+ display: "block",
2468
+ mb: 0.75,
2469
+ sx: { fontSize: 10.5, fontWeight: 700, letterSpacing: "0.07em", textTransform: "uppercase" }
2470
+ },
2471
+ "After"
2472
+ ), /* @__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)))
2473
+ )))));
1521
2474
  };
1522
2475
  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)(
2476
+ const [page, setPage] = (0, import_react7.useState)(0);
2477
+ const [pageSize, setPageSize] = (0, import_react7.useState)(25);
2478
+ const [filters, setFilters] = (0, import_react7.useState)({});
2479
+ const fetchParams = (0, import_react7.useCallback)(
1527
2480
  () => ({ page: page + 1, page_size: pageSize, ...filters }),
1528
2481
  [page, pageSize, filters]
1529
2482
  )();
@@ -1533,68 +2486,80 @@ var init_AuditLog = __esm({
1533
2486
  );
1534
2487
  const entries = value?.audit_logs ?? [];
1535
2488
  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,
2489
+ return /* @__PURE__ */ import_react7.default.createElement(
2490
+ SectionCard,
1538
2491
  {
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 }
2492
+ title: "Audit Log",
2493
+ subtitle: loading ? "Loading\u2026" : `${total.toLocaleString()} event${total === 1 ? "" : "s"}`,
2494
+ flush: true,
2495
+ actions: /* @__PURE__ */ import_react7.default.createElement(import_react7.default.Fragment, null, /* @__PURE__ */ import_react7.default.createElement(
2496
+ import_TextField4.default,
2497
+ {
2498
+ size: "small",
2499
+ label: "Action",
2500
+ select: true,
2501
+ value: filters.action ?? "",
2502
+ onChange: (e) => {
2503
+ setFilters((f) => ({ ...f, action: e.target.value || void 0 }));
2504
+ setPage(0);
2505
+ },
2506
+ sx: { minWidth: 150 }
2507
+ },
2508
+ /* @__PURE__ */ import_react7.default.createElement(import_MenuItem3.default, { value: "" }, "All actions"),
2509
+ /* @__PURE__ */ import_react7.default.createElement(import_MenuItem3.default, { value: "created" }, "Created"),
2510
+ /* @__PURE__ */ import_react7.default.createElement(import_MenuItem3.default, { value: "updated" }, "Updated"),
2511
+ /* @__PURE__ */ import_react7.default.createElement(import_MenuItem3.default, { value: "deleted" }, "Deleted"),
2512
+ /* @__PURE__ */ import_react7.default.createElement(import_MenuItem3.default, { value: "blocked" }, "Blocked")
2513
+ ), /* @__PURE__ */ import_react7.default.createElement(
2514
+ import_TextField4.default,
2515
+ {
2516
+ size: "small",
2517
+ label: "Table",
2518
+ select: true,
2519
+ value: filters.table_name ?? "",
2520
+ onChange: (e) => {
2521
+ setFilters((f) => ({ ...f, table_name: e.target.value || void 0 }));
2522
+ setPage(0);
2523
+ },
2524
+ sx: { minWidth: 150 }
2525
+ },
2526
+ /* @__PURE__ */ import_react7.default.createElement(import_MenuItem3.default, { value: "" }, "All tables"),
2527
+ /* @__PURE__ */ import_react7.default.createElement(import_MenuItem3.default, { value: "LiteLLM_VerificationToken" }, "Key"),
2528
+ /* @__PURE__ */ import_react7.default.createElement(import_MenuItem3.default, { value: "LiteLLM_TeamTable" }, "Team"),
2529
+ /* @__PURE__ */ import_react7.default.createElement(import_MenuItem3.default, { value: "LiteLLM_UserTable" }, "User")
2530
+ ), /* @__PURE__ */ import_react7.default.createElement(
2531
+ import_TextField4.default,
2532
+ {
2533
+ size: "small",
2534
+ label: "Changed by",
2535
+ value: filters.changed_by ?? "",
2536
+ onChange: (e) => {
2537
+ setFilters((f) => ({ ...f, changed_by: e.target.value || void 0 }));
2538
+ setPage(0);
2539
+ },
2540
+ sx: { minWidth: 180 }
2541
+ }
2542
+ ))
1566
2543
  },
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
- ));
2544
+ 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)),
2545
+ /* @__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)))),
2546
+ /* @__PURE__ */ import_react7.default.createElement(
2547
+ import_TablePagination.default,
2548
+ {
2549
+ component: "div",
2550
+ count: total,
2551
+ page,
2552
+ onPageChange: (_, p) => setPage(p),
2553
+ rowsPerPage: pageSize,
2554
+ onRowsPerPageChange: (e) => {
2555
+ setPageSize(Number(e.target.value));
2556
+ setPage(0);
2557
+ },
2558
+ rowsPerPageOptions: [10, 25, 50],
2559
+ sx: (theme) => ({ borderTop: `1px solid ${theme.palette.divider}` })
2560
+ }
2561
+ )
2562
+ );
1598
2563
  };
1599
2564
  }
1600
2565
  });
@@ -1618,19 +2583,19 @@ function initDateRange() {
1618
2583
  else start.setDate(start.getDate() - 7);
1619
2584
  return { start, end };
1620
2585
  }
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;
2586
+ 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
2587
  var init_LiteLLMPage = __esm({
1623
2588
  "src/components/LiteLLMPage.tsx"() {
1624
2589
  "use strict";
1625
- import_react7 = __toESM(require("react"));
1626
- import_Box7 = __toESM(require("@mui/material/Box"));
2590
+ import_react8 = __toESM(require("react"));
2591
+ import_Box8 = __toESM(require("@mui/material/Box"));
1627
2592
  import_Snackbar = __toESM(require("@mui/material/Snackbar"));
1628
2593
  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"));
2594
+ import_CircularProgress4 = __toESM(require("@mui/material/CircularProgress"));
2595
+ import_Typography8 = __toESM(require("@mui/material/Typography"));
2596
+ import_Paper5 = __toESM(require("@mui/material/Paper"));
2597
+ import_Tabs2 = __toESM(require("@mui/material/Tabs"));
2598
+ import_Tab2 = __toESM(require("@mui/material/Tab"));
1634
2599
  import_react_use2 = require("react-use");
1635
2600
  import_core_plugin_api2 = require("@backstage/core-plugin-api");
1636
2601
  init_DashboardHeader();
@@ -1643,12 +2608,12 @@ var init_LiteLLMPage = __esm({
1643
2608
  PERIOD_LS_KEY2 = "litellm_usage_period";
1644
2609
  LiteLLMPage = () => {
1645
2610
  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)({});
2611
+ const [dateRange, setDateRange] = (0, import_react8.useState)(initDateRange);
2612
+ const [activeTab, setActiveTab] = (0, import_react8.useState)("overview");
2613
+ const [snackbar, setSnackbar] = (0, import_react8.useState)(null);
2614
+ const [generateDialogOpen, setGenerateDialogOpen] = (0, import_react8.useState)(false);
2615
+ const [teamUsageCache, setTeamUsageCache] = (0, import_react8.useState)({});
2616
+ const [teamUsageLoading, setTeamUsageLoading] = (0, import_react8.useState)({});
1652
2617
  const { value: userInfo, loading: userLoading, error: userError } = (0, import_react_use2.useAsync)(
1653
2618
  () => api.getUserInfo(),
1654
2619
  [api]
@@ -1673,7 +2638,7 @@ var init_LiteLLMPage = __esm({
1673
2638
  [api]
1674
2639
  );
1675
2640
  const { value: liteLlmConfig } = (0, import_react_use2.useAsync)(() => api.getConfig(), [api]);
1676
- const teams = (0, import_react7.useMemo)(() => {
2641
+ const teams = (0, import_react8.useMemo)(() => {
1677
2642
  if (!allTeams?.length) return [];
1678
2643
  if (!userInfo) return allTeams;
1679
2644
  const userId = userInfo.user_id;
@@ -1690,12 +2655,12 @@ var init_LiteLLMPage = __esm({
1690
2655
  const endDate = dateRange.end.toISOString().split("T")[0];
1691
2656
  return api.getUsage(startDate, endDate);
1692
2657
  }, [api, dateRange]);
1693
- const handleDateRangeChange = (0, import_react7.useCallback)((range) => {
2658
+ const handleDateRangeChange = (0, import_react8.useCallback)((range) => {
1694
2659
  setDateRange(range);
1695
2660
  setTeamUsageCache({});
1696
2661
  setTeamUsageLoading({});
1697
2662
  }, [setDateRange]);
1698
- const loadTeamUsage = (0, import_react7.useCallback)(async (teamId) => {
2663
+ const loadTeamUsage = (0, import_react8.useCallback)(async (teamId) => {
1699
2664
  if (teamUsageCache[teamId] !== void 0 || teamUsageLoading[teamId]) return;
1700
2665
  setTeamUsageLoading((prev) => ({ ...prev, [teamId]: true }));
1701
2666
  try {
@@ -1709,7 +2674,7 @@ var init_LiteLLMPage = __esm({
1709
2674
  setTeamUsageLoading((prev) => ({ ...prev, [teamId]: false }));
1710
2675
  }
1711
2676
  }, [api, dateRange, teamUsageCache, teamUsageLoading]);
1712
- const allowedModels = (0, import_react7.useMemo)(() => {
2677
+ const allowedModels = (0, import_react8.useMemo)(() => {
1713
2678
  if (!allModels?.length) return [];
1714
2679
  const userModels = userInfo?.models;
1715
2680
  const teamModels = teams?.flatMap((t) => t.models ?? []);
@@ -1722,7 +2687,7 @@ var init_LiteLLMPage = __esm({
1722
2687
  ]);
1723
2688
  return allModels.filter((m) => allowed.has(m.model_name));
1724
2689
  }, [allModels, userInfo, teams]);
1725
- const handleGenerateKey = (0, import_react7.useCallback)(
2690
+ const handleGenerateKey = (0, import_react8.useCallback)(
1726
2691
  async (request) => {
1727
2692
  try {
1728
2693
  const response = await api.generateKey(request);
@@ -1736,7 +2701,7 @@ var init_LiteLLMPage = __esm({
1736
2701
  },
1737
2702
  [api, refreshKeys]
1738
2703
  );
1739
- const handleUpdateKey = (0, import_react7.useCallback)(
2704
+ const handleUpdateKey = (0, import_react8.useCallback)(
1740
2705
  async (keyId, request) => {
1741
2706
  try {
1742
2707
  await api.updateKey(keyId, request);
@@ -1749,7 +2714,7 @@ var init_LiteLLMPage = __esm({
1749
2714
  },
1750
2715
  [api, refreshKeys]
1751
2716
  );
1752
- const handleBlockKey = (0, import_react7.useCallback)(
2717
+ const handleBlockKey = (0, import_react8.useCallback)(
1753
2718
  async (keyId) => {
1754
2719
  try {
1755
2720
  await api.blockKey(keyId);
@@ -1761,7 +2726,7 @@ var init_LiteLLMPage = __esm({
1761
2726
  },
1762
2727
  [api, refreshKeys]
1763
2728
  );
1764
- const handleUnblockKey = (0, import_react7.useCallback)(
2729
+ const handleUnblockKey = (0, import_react8.useCallback)(
1765
2730
  async (keyId) => {
1766
2731
  try {
1767
2732
  await api.unblockKey(keyId);
@@ -1773,7 +2738,7 @@ var init_LiteLLMPage = __esm({
1773
2738
  },
1774
2739
  [api, refreshKeys]
1775
2740
  );
1776
- const handleResetKeySpend = (0, import_react7.useCallback)(
2741
+ const handleResetKeySpend = (0, import_react8.useCallback)(
1777
2742
  async (keyId) => {
1778
2743
  try {
1779
2744
  await api.resetKeySpend(keyId);
@@ -1785,7 +2750,7 @@ var init_LiteLLMPage = __esm({
1785
2750
  },
1786
2751
  [api, refreshKeys]
1787
2752
  );
1788
- const handleDeleteKey = (0, import_react7.useCallback)(
2753
+ const handleDeleteKey = (0, import_react8.useCallback)(
1789
2754
  async (keyId) => {
1790
2755
  try {
1791
2756
  await api.deleteKey(keyId);
@@ -1802,7 +2767,7 @@ var init_LiteLLMPage = __esm({
1802
2767
  },
1803
2768
  [api, refreshKeys]
1804
2769
  );
1805
- const handlePruneExpiredKeys = (0, import_react7.useCallback)(
2770
+ const handlePruneExpiredKeys = (0, import_react8.useCallback)(
1806
2771
  async () => {
1807
2772
  try {
1808
2773
  const result = await api.pruneExpiredKeys();
@@ -1817,34 +2782,42 @@ var init_LiteLLMPage = __esm({
1817
2782
  );
1818
2783
  const isInitialLoading = userLoading && !userInfo;
1819
2784
  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));
2785
+ 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
2786
  }
1822
2787
  if (userError || !userInfo) {
1823
2788
  const isProvisioningEnabled = userError?.body?.provisioning === true;
1824
2789
  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.")));
2790
+ 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
2791
  }
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(
2792
+ const pageTabs = /* @__PURE__ */ import_react8.default.createElement(
2793
+ import_Tabs2.default,
2794
+ {
2795
+ value: activeTab,
2796
+ onChange: (_, v) => setActiveTab(v),
2797
+ variant: "scrollable",
2798
+ scrollButtons: "auto",
2799
+ sx: {
2800
+ minHeight: 44,
2801
+ '& [class*="MuiTabs-indicator"]': { height: 2, borderRadius: "2px 2px 0 0" },
2802
+ '& [class*="MuiTab-root"]': { minHeight: 44, textTransform: "none", fontSize: 14 }
2803
+ }
2804
+ },
2805
+ /* @__PURE__ */ import_react8.default.createElement(import_Tab2.default, { label: "Overview", value: "overview" }),
2806
+ /* @__PURE__ */ import_react8.default.createElement(import_Tab2.default, { label: "Keys", value: "keys" }),
2807
+ /* @__PURE__ */ import_react8.default.createElement(import_Tab2.default, { label: "Teams", value: "teams" }),
2808
+ userInfo.can_view_audit && /* @__PURE__ */ import_react8.default.createElement(import_Tab2.default, { label: "Audit Log", value: "audit" })
2809
+ );
2810
+ return /* @__PURE__ */ import_react8.default.createElement(import_Box8.default, { sx: { p: 3, display: "flex", flexDirection: "column", gap: 2 } }, /* @__PURE__ */ import_react8.default.createElement(
1828
2811
  DashboardHeader,
1829
2812
  {
1830
2813
  userInfo,
1831
2814
  teams: teams ?? [],
1832
2815
  keys: keys ?? [],
1833
2816
  loading: userLoading || teamsLoading,
1834
- onGenerateKeyClick: () => setGenerateDialogOpen(true)
2817
+ onGenerateKeyClick: () => setGenerateDialogOpen(true),
2818
+ tabs: pageTabs
1835
2819
  }
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(
2820
+ ), activeTab === "overview" && /* @__PURE__ */ import_react8.default.createElement(
1848
2821
  UsageStats,
1849
2822
  {
1850
2823
  usage: usage ?? null,
@@ -1854,7 +2827,7 @@ var init_LiteLLMPage = __esm({
1854
2827
  loading: usageLoading,
1855
2828
  userInfo
1856
2829
  }
1857
- ), activeTab === "keys" && /* @__PURE__ */ import_react7.default.createElement(
2830
+ ), activeTab === "keys" && /* @__PURE__ */ import_react8.default.createElement(
1858
2831
  KeysTable,
1859
2832
  {
1860
2833
  keys: keys ?? [],
@@ -1868,7 +2841,7 @@ var init_LiteLLMPage = __esm({
1868
2841
  onDeleteKey: handleDeleteKey,
1869
2842
  onPruneExpiredKeys: handlePruneExpiredKeys
1870
2843
  }
1871
- ), activeTab === "teams" && /* @__PURE__ */ import_react7.default.createElement(
2844
+ ), activeTab === "teams" && /* @__PURE__ */ import_react8.default.createElement(
1872
2845
  TeamUsage,
1873
2846
  {
1874
2847
  teams: teams ?? [],
@@ -1879,7 +2852,7 @@ var init_LiteLLMPage = __esm({
1879
2852
  },
1880
2853
  getTeamUsageLoading: (teamId) => teamUsageLoading[teamId] ?? false
1881
2854
  }
1882
- ), activeTab === "audit" && userInfo.can_view_audit && /* @__PURE__ */ import_react7.default.createElement(AuditLog, { api }), /* @__PURE__ */ import_react7.default.createElement(
2855
+ ), activeTab === "audit" && userInfo.can_view_audit && /* @__PURE__ */ import_react8.default.createElement(AuditLog, { api }), /* @__PURE__ */ import_react8.default.createElement(
1883
2856
  GenerateKeyDialog,
1884
2857
  {
1885
2858
  open: generateDialogOpen,
@@ -1892,7 +2865,7 @@ var init_LiteLLMPage = __esm({
1892
2865
  onGenerateKey: handleGenerateKey,
1893
2866
  onGetConfig: () => api.getConfig()
1894
2867
  }
1895
- ), /* @__PURE__ */ import_react7.default.createElement(
2868
+ ), /* @__PURE__ */ import_react8.default.createElement(
1896
2869
  import_Snackbar.default,
1897
2870
  {
1898
2871
  open: !!snackbar,
@@ -1900,7 +2873,7 @@ var init_LiteLLMPage = __esm({
1900
2873
  onClose: () => setSnackbar(null),
1901
2874
  anchorOrigin: { vertical: "bottom", horizontal: "right" }
1902
2875
  },
1903
- snackbar ? /* @__PURE__ */ import_react7.default.createElement(import_Alert3.default, { severity: snackbar.severity, onClose: () => setSnackbar(null) }, snackbar.message) : void 0
2876
+ snackbar ? /* @__PURE__ */ import_react8.default.createElement(import_Alert3.default, { severity: snackbar.severity, onClose: () => setSnackbar(null) }, snackbar.message) : void 0
1904
2877
  ));
1905
2878
  };
1906
2879
  }
@@ -1922,7 +2895,7 @@ __export(index_exports, {
1922
2895
  module.exports = __toCommonJS(index_exports);
1923
2896
 
1924
2897
  // src/plugin.tsx
1925
- var import_react8 = __toESM(require("react"));
2898
+ var import_react9 = __toESM(require("react"));
1926
2899
  var import_icons_material6 = require("@mui/icons-material");
1927
2900
  var import_frontend_plugin_api = require("@backstage/frontend-plugin-api");
1928
2901
  init_api();
@@ -1937,10 +2910,10 @@ var liteLlmPage = import_frontend_plugin_api.PageBlueprint.make({
1937
2910
  params: {
1938
2911
  path: "/litellm",
1939
2912
  title: "LiteLLM",
1940
- icon: /* @__PURE__ */ import_react8.default.createElement(import_icons_material6.TrendingUp, null),
2913
+ icon: /* @__PURE__ */ import_react9.default.createElement(import_icons_material6.TrendingUp, null),
1941
2914
  loader: async () => {
1942
2915
  const { LiteLLMPage: LiteLLMPage2 } = await Promise.resolve().then(() => (init_LiteLLMPage(), LiteLLMPage_exports));
1943
- return /* @__PURE__ */ import_react8.default.createElement(LiteLLMPage2, null);
2916
+ return /* @__PURE__ */ import_react9.default.createElement(LiteLLMPage2, null);
1944
2917
  }
1945
2918
  }
1946
2919
  });
@@ -1957,15 +2930,15 @@ init_UsageStats();
1957
2930
  init_TeamUsage();
1958
2931
 
1959
2932
  // 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"));
2933
+ var import_react10 = __toESM(require("react"));
2934
+ var import_Paper6 = __toESM(require("@mui/material/Paper"));
2935
+ var import_Box9 = __toESM(require("@mui/material/Box"));
2936
+ var import_Typography9 = __toESM(require("@mui/material/Typography"));
2937
+ var import_FormControl = __toESM(require("@mui/material/FormControl"));
2938
+ var import_Select = __toESM(require("@mui/material/Select"));
1966
2939
  var import_MenuItem4 = __toESM(require("@mui/material/MenuItem"));
1967
2940
  var import_Grid2 = __toESM(require("@mui/material/Grid"));
1968
- var import_CircularProgress6 = __toESM(require("@mui/material/CircularProgress"));
2941
+ var import_CircularProgress5 = __toESM(require("@mui/material/CircularProgress"));
1969
2942
  var import_Alert4 = __toESM(require("@mui/material/Alert"));
1970
2943
  var import_recharts3 = require("recharts");
1971
2944
  var import_core_plugin_api3 = require("@backstage/core-plugin-api");
@@ -1983,19 +2956,19 @@ function presetToDateRange(preset) {
1983
2956
  }
1984
2957
  return { start, end };
1985
2958
  }
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));
2959
+ 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
2960
  var LiteLLMHomeWidget = ({
1988
2961
  defaultPeriod = "7d",
1989
2962
  title = "LiteLLM Usage"
1990
2963
  }) => {
1991
2964
  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)(() => {
2965
+ const [period, setPeriod] = (0, import_react10.useState)(defaultPeriod);
2966
+ const [loading, setLoading] = (0, import_react10.useState)(true);
2967
+ const [usageError, setUsageError] = (0, import_react10.useState)(null);
2968
+ const [keysError, setKeysError] = (0, import_react10.useState)(null);
2969
+ const [usage, setUsage] = (0, import_react10.useState)(null);
2970
+ const [keys, setKeys] = (0, import_react10.useState)([]);
2971
+ (0, import_react10.useEffect)(() => {
1999
2972
  let cancelled = false;
2000
2973
  setLoading(true);
2001
2974
  setUsageError(null);
@@ -2032,17 +3005,17 @@ var LiteLLMHomeWidget = ({
2032
3005
  spend: d.spend
2033
3006
  }));
2034
3007
  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,
3008
+ 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(
3009
+ import_Select.default,
2037
3010
  {
2038
3011
  value: period,
2039
3012
  onChange: (e) => setPeriod(e.target.value),
2040
3013
  displayEmpty: true
2041
3014
  },
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(
3015
+ /* @__PURE__ */ import_react10.default.createElement(import_MenuItem4.default, { value: "today" }, "Today"),
3016
+ /* @__PURE__ */ import_react10.default.createElement(import_MenuItem4.default, { value: "7d" }, "7d"),
3017
+ /* @__PURE__ */ import_react10.default.createElement(import_MenuItem4.default, { value: "30d" }, "30d")
3018
+ ))), 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
3019
  import_recharts3.Area,
2047
3020
  {
2048
3021
  type: "monotone",