@acarmisc/backstage-plugin-litellm 0.14.0 → 0.15.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.esm.js CHANGED
@@ -1405,7 +1405,28 @@ print(response.choices[0].message.content)`,
1405
1405
  { "id": "${model}", "name": "${model}" }
1406
1406
  ]
1407
1407
  }
1408
- }`
1408
+ }`,
1409
+ claudeCode: `# Option 1 \u2014 Static key (Method 1: Unified Endpoint)
1410
+ export ANTHROPIC_AUTH_TOKEN="${key}"
1411
+ export ANTHROPIC_BASE_URL="${base}"
1412
+ claude --model ${model}
1413
+
1414
+ # Option 2 \u2014 Dynamic key via helper script
1415
+ # 1. Create ~/bin/get-litellm-key.sh:
1416
+ cat > ~/bin/get-litellm-key.sh << 'SCRIPT'
1417
+ #!/bin/bash
1418
+ curl -s -X POST ${base}/key/generate \\
1419
+ -H "Authorization: Bearer ${key}" \\
1420
+ -H "Content-Type: application/json" \\
1421
+ -d '{}' | jq -r '.key'
1422
+ SCRIPT
1423
+ chmod +x ~/bin/get-litellm-key.sh
1424
+
1425
+ # 2. Add to ~/.claude/settings.json:
1426
+ # { "apiKeyHelper": "~/bin/get-litellm-key.sh" }
1427
+
1428
+ # 3. Set refresh interval (optional, default 1h):
1429
+ export CLAUDE_CODE_API_KEY_HELPER_TTL_MS=3600000`
1409
1430
  };
1410
1431
  }
1411
1432
  function aliasHelperText(aliasError, aliasDuplicate) {
@@ -1466,13 +1487,15 @@ var init_GenerateKeyDialog = __esm({
1466
1487
  ALL_PROXY_MODELS = "all-proxy-models";
1467
1488
  SNIPPET_FILE_HINTS = {
1468
1489
  opencode: "Add to ~/.config/opencode/opencode.json",
1469
- pi: "Add to ~/.pi/agent/models.json"
1490
+ pi: "Add to ~/.pi/agent/models.json",
1491
+ "claude-code": "Requires Claude Code CLI installed"
1470
1492
  };
1471
1493
  SnippetTabs = ({ snippets, model, onCopy }) => {
1472
1494
  const [tab, setTab] = useState2("curl");
1473
- const code = snippets[tab];
1495
+ const snippetKey = tab === "claude-code" ? "claudeCode" : tab;
1496
+ const code = snippets[snippetKey];
1474
1497
  const fileHint = SNIPPET_FILE_HINTS[tab];
1475
- return /* @__PURE__ */ React4.createElement(Box4, null, /* @__PURE__ */ React4.createElement(Tabs, { value: tab, onChange: (_, v) => setTab(v), sx: { mb: 1 }, variant: "scrollable" }, /* @__PURE__ */ React4.createElement(Tab, { label: "curl", value: "curl" }), /* @__PURE__ */ React4.createElement(Tab, { label: "OpenAI SDK", value: "openai" }), /* @__PURE__ */ React4.createElement(Tab, { label: "opencode", value: "opencode" }), /* @__PURE__ */ React4.createElement(Tab, { label: "pi", value: "pi" })), fileHint && /* @__PURE__ */ React4.createElement(Typography4, { variant: "caption", color: "text.secondary", display: "block", mb: 0.5 }, fileHint), /* @__PURE__ */ React4.createElement(
1498
+ return /* @__PURE__ */ React4.createElement(Box4, null, /* @__PURE__ */ React4.createElement(Tabs, { value: tab, onChange: (_, v) => setTab(v), sx: { mb: 1 }, variant: "scrollable" }, /* @__PURE__ */ React4.createElement(Tab, { label: "curl", value: "curl" }), /* @__PURE__ */ React4.createElement(Tab, { label: "OpenAI SDK", value: "openai" }), /* @__PURE__ */ React4.createElement(Tab, { label: "opencode", value: "opencode" }), /* @__PURE__ */ React4.createElement(Tab, { label: "pi", value: "pi" }), /* @__PURE__ */ React4.createElement(Tab, { label: "Claude Code", value: "claude-code" })), fileHint && /* @__PURE__ */ React4.createElement(Typography4, { variant: "caption", color: "text.secondary", display: "block", mb: 0.5 }, fileHint), /* @__PURE__ */ React4.createElement(
1476
1499
  Box4,
1477
1500
  {
1478
1501
  position: "relative",
@@ -2400,24 +2423,169 @@ var init_TeamUsage = __esm({
2400
2423
  }
2401
2424
  });
2402
2425
 
2403
- // src/components/AuditLog.tsx
2404
- import React7, { useState as useState5, useCallback } from "react";
2426
+ // src/components/ModelsTable.tsx
2427
+ import React7, { useState as useState5, useMemo as useMemo5, useCallback } from "react";
2405
2428
  import Box7 from "@mui/material/Box";
2406
- import Typography7 from "@mui/material/Typography";
2407
2429
  import Table4 from "@mui/material/Table";
2408
2430
  import TableBody4 from "@mui/material/TableBody";
2409
2431
  import TableCell4 from "@mui/material/TableCell";
2410
2432
  import TableContainer4 from "@mui/material/TableContainer";
2411
2433
  import TableHead4 from "@mui/material/TableHead";
2412
2434
  import TableRow4 from "@mui/material/TableRow";
2435
+ import Typography7 from "@mui/material/Typography";
2436
+ import MenuItem3 from "@mui/material/MenuItem";
2437
+ import Select from "@mui/material/Select";
2438
+ import FormControl from "@mui/material/FormControl";
2439
+ import InputLabel from "@mui/material/InputLabel";
2440
+ import ContentCopyIcon from "@mui/icons-material/ContentCopy";
2441
+ import Tooltip3 from "@mui/material/Tooltip";
2442
+ import Snackbar from "@mui/material/Snackbar";
2443
+ import Alert2 from "@mui/material/Alert";
2444
+ import { alpha as alpha5, useTheme as useTheme2 } from "@mui/material/styles";
2445
+ function fmtCostPerToken(cost) {
2446
+ if (cost === void 0 || cost === null) return "\u2014";
2447
+ if (cost === 0) return "$0";
2448
+ if (cost < 1e-6) return `$${(cost * 1e6).toFixed(2)}/M`;
2449
+ if (cost < 1e-3) return `$${(cost * 1e3).toFixed(3)}/K`;
2450
+ return `$${cost.toFixed(4)}`;
2451
+ }
2452
+ function fmtTokens(n) {
2453
+ if (n === void 0 || n === null) return "\u2014";
2454
+ if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
2455
+ if (n >= 1e3) return `${(n / 1e3).toFixed(0)}K`;
2456
+ return String(n);
2457
+ }
2458
+ var ModelsTable;
2459
+ var init_ModelsTable = __esm({
2460
+ "src/components/ModelsTable.tsx"() {
2461
+ "use strict";
2462
+ init_ui();
2463
+ ModelsTable = ({ allModels, teams, loading }) => {
2464
+ const theme = useTheme2();
2465
+ const [selectedTeamId, setSelectedTeamId] = useState5("");
2466
+ const [copiedSnackbar, setCopiedSnackbar] = useState5(false);
2467
+ const selectedTeam = useMemo5(
2468
+ () => teams.find((t) => t.team_id === selectedTeamId) ?? null,
2469
+ [teams, selectedTeamId]
2470
+ );
2471
+ const filteredModels = useMemo5(() => {
2472
+ if (!selectedTeam) return [];
2473
+ const teamModels = selectedTeam.models ?? [];
2474
+ if (teamModels.length === 0) return allModels;
2475
+ const allowed = new Set(teamModels);
2476
+ return allModels.filter((m) => allowed.has(m.model_name));
2477
+ }, [allModels, selectedTeam]);
2478
+ const handleCopyModelId = useCallback((modelId) => {
2479
+ navigator.clipboard.writeText(modelId).then(() => setCopiedSnackbar(true));
2480
+ }, []);
2481
+ const modeColor = (mode) => {
2482
+ switch (mode) {
2483
+ case "chat":
2484
+ return theme.palette.info.main;
2485
+ case "embedding":
2486
+ return theme.palette.success.main;
2487
+ case "completion":
2488
+ return theme.palette.primary.main;
2489
+ case "image":
2490
+ return theme.palette.warning.main;
2491
+ default:
2492
+ return theme.palette.info.main;
2493
+ }
2494
+ };
2495
+ return /* @__PURE__ */ React7.createElement(
2496
+ SectionCard,
2497
+ {
2498
+ title: "Available Models",
2499
+ subtitle: selectedTeam ? `${filteredModels.length} model${filteredModels.length !== 1 ? "s" : ""} in ${selectedTeam.team_alias ?? selectedTeam.team_id}` : "Select a team to view its models"
2500
+ },
2501
+ /* @__PURE__ */ React7.createElement(Box7, { sx: { px: 2.5, pb: 2.5, pt: 1 } }, /* @__PURE__ */ React7.createElement(FormControl, { size: "small", sx: { minWidth: 240, mb: 2 } }, /* @__PURE__ */ React7.createElement(InputLabel, { id: "team-select-label" }, "Team"), /* @__PURE__ */ React7.createElement(
2502
+ Select,
2503
+ {
2504
+ labelId: "team-select-label",
2505
+ value: selectedTeamId,
2506
+ label: "Team",
2507
+ onChange: (e) => setSelectedTeamId(e.target.value)
2508
+ },
2509
+ teams.map((t) => /* @__PURE__ */ React7.createElement(MenuItem3, { key: t.team_id, value: t.team_id }, t.team_alias ?? t.team_id))
2510
+ ))),
2511
+ loading && /* @__PURE__ */ React7.createElement(EmptyState, { message: "Loading models\u2026" }),
2512
+ !loading && !selectedTeamId && /* @__PURE__ */ React7.createElement(
2513
+ EmptyState,
2514
+ {
2515
+ message: "Select a team to see available models",
2516
+ hint: "Each team may have a different set of models assigned"
2517
+ }
2518
+ ),
2519
+ !loading && selectedTeamId && filteredModels.length === 0 && /* @__PURE__ */ React7.createElement(
2520
+ EmptyState,
2521
+ {
2522
+ message: "No models available for this team",
2523
+ hint: allModels.length === 0 ? "No models are configured in the system" : "None of the team's assigned models were found in the catalog"
2524
+ }
2525
+ ),
2526
+ !loading && filteredModels.length > 0 && /* @__PURE__ */ React7.createElement(TableContainer4, null, /* @__PURE__ */ React7.createElement(Table4, { size: "small", sx: dataTableSx }, /* @__PURE__ */ React7.createElement(TableHead4, null, /* @__PURE__ */ React7.createElement(TableRow4, null, /* @__PURE__ */ React7.createElement(TableCell4, null, "Model ID"), /* @__PURE__ */ React7.createElement(TableCell4, null, "Mode"), /* @__PURE__ */ React7.createElement(TableCell4, { align: "right" }, "Input Cost"), /* @__PURE__ */ React7.createElement(TableCell4, { align: "right" }, "Output Cost"), /* @__PURE__ */ React7.createElement(TableCell4, { align: "right" }, "Max Input"), /* @__PURE__ */ React7.createElement(TableCell4, { align: "right" }, "Max Output"), /* @__PURE__ */ React7.createElement(TableCell4, null, "Capabilities"))), /* @__PURE__ */ React7.createElement(TableBody4, null, filteredModels.map((m) => /* @__PURE__ */ React7.createElement(TableRow4, { key: m.model_name, hover: true }, /* @__PURE__ */ React7.createElement(TableCell4, null, /* @__PURE__ */ React7.createElement(Box7, { sx: { display: "flex", alignItems: "center", gap: 0.5 } }, /* @__PURE__ */ React7.createElement(TagChip, { label: m.model_name, title: m.model_name }), /* @__PURE__ */ React7.createElement(Tooltip3, { title: "Copy model ID" }, /* @__PURE__ */ React7.createElement(
2527
+ Box7,
2528
+ {
2529
+ component: "span",
2530
+ role: "button",
2531
+ tabIndex: 0,
2532
+ onClick: () => handleCopyModelId(m.model_name),
2533
+ onKeyDown: (e) => e.key === "Enter" && handleCopyModelId(m.model_name),
2534
+ sx: quietIconButtonSx("accent"),
2535
+ style: { cursor: "pointer", display: "inline-flex" }
2536
+ },
2537
+ /* @__PURE__ */ React7.createElement(ContentCopyIcon, { sx: { fontSize: 14 } })
2538
+ )))), /* @__PURE__ */ React7.createElement(TableCell4, null, /* @__PURE__ */ React7.createElement(
2539
+ Box7,
2540
+ {
2541
+ component: "span",
2542
+ sx: {
2543
+ display: "inline-block",
2544
+ px: 0.75,
2545
+ py: 0.25,
2546
+ borderRadius: 1,
2547
+ fontSize: 11,
2548
+ fontWeight: 600,
2549
+ textTransform: "capitalize",
2550
+ bgcolor: alpha5(modeColor(m.mode), 0.1),
2551
+ color: modeColor(m.mode)
2552
+ }
2553
+ },
2554
+ m.mode
2555
+ )), /* @__PURE__ */ React7.createElement(TableCell4, { align: "right" }, /* @__PURE__ */ React7.createElement(Typography7, { variant: "body2", sx: { fontVariantNumeric: "tabular-nums" } }, fmtCostPerToken(m.input_cost_per_token))), /* @__PURE__ */ React7.createElement(TableCell4, { align: "right" }, /* @__PURE__ */ React7.createElement(Typography7, { variant: "body2", sx: { fontVariantNumeric: "tabular-nums" } }, fmtCostPerToken(m.output_cost_per_token))), /* @__PURE__ */ React7.createElement(TableCell4, { align: "right" }, /* @__PURE__ */ React7.createElement(Typography7, { variant: "body2", sx: { fontVariantNumeric: "tabular-nums" } }, fmtTokens(m.max_input_tokens))), /* @__PURE__ */ React7.createElement(TableCell4, { align: "right" }, /* @__PURE__ */ React7.createElement(Typography7, { variant: "body2", sx: { fontVariantNumeric: "tabular-nums" } }, fmtTokens(m.max_output_tokens))), /* @__PURE__ */ React7.createElement(TableCell4, null, /* @__PURE__ */ React7.createElement(Box7, { sx: { display: "flex", gap: 0.5, flexWrap: "wrap" } }, m.supports_function_calling && /* @__PURE__ */ React7.createElement(TagChip, { label: "Fn", title: "Function calling" }), m.supports_vision && /* @__PURE__ */ React7.createElement(TagChip, { label: "\u{1F441}", title: "Vision" })))))))),
2556
+ /* @__PURE__ */ React7.createElement(
2557
+ Snackbar,
2558
+ {
2559
+ open: copiedSnackbar,
2560
+ autoHideDuration: 2e3,
2561
+ onClose: () => setCopiedSnackbar(false),
2562
+ anchorOrigin: { vertical: "bottom", horizontal: "center" }
2563
+ },
2564
+ /* @__PURE__ */ React7.createElement(Alert2, { severity: "success", variant: "filled", sx: { fontSize: 13 } }, "Model ID copied to clipboard")
2565
+ )
2566
+ );
2567
+ };
2568
+ }
2569
+ });
2570
+
2571
+ // src/components/AuditLog.tsx
2572
+ import React8, { useState as useState6, useCallback as useCallback2 } from "react";
2573
+ import Box8 from "@mui/material/Box";
2574
+ import Typography8 from "@mui/material/Typography";
2575
+ import Table5 from "@mui/material/Table";
2576
+ import TableBody5 from "@mui/material/TableBody";
2577
+ import TableCell5 from "@mui/material/TableCell";
2578
+ import TableContainer5 from "@mui/material/TableContainer";
2579
+ import TableHead5 from "@mui/material/TableHead";
2580
+ import TableRow5 from "@mui/material/TableRow";
2413
2581
  import TablePagination from "@mui/material/TablePagination";
2414
2582
  import TextField4 from "@mui/material/TextField";
2415
- import MenuItem3 from "@mui/material/MenuItem";
2583
+ import MenuItem4 from "@mui/material/MenuItem";
2416
2584
  import Skeleton5 from "@mui/material/Skeleton";
2417
2585
  import Collapse2 from "@mui/material/Collapse";
2418
2586
  import IconButton4 from "@mui/material/IconButton";
2419
- import Alert2 from "@mui/material/Alert";
2420
- import { alpha as alpha5 } from "@mui/material/styles";
2587
+ import Alert3 from "@mui/material/Alert";
2588
+ import { alpha as alpha6 } from "@mui/material/styles";
2421
2589
  import { KeyboardArrowDown } from "@mui/icons-material";
2422
2590
  import { useAsync } from "react-use";
2423
2591
  function actionTone(action) {
@@ -2440,10 +2608,10 @@ function formatDateTime(iso) {
2440
2608
  }
2441
2609
  function renderAuditLogBody(loading, entries) {
2442
2610
  if (loading) {
2443
- return /* @__PURE__ */ React7.createElement(TableRow4, null, /* @__PURE__ */ React7.createElement(TableCell4, { colSpan: 6, sx: { py: 2 } }, /* @__PURE__ */ React7.createElement(Skeleton5, { variant: "rounded", height: 160 })));
2611
+ return /* @__PURE__ */ React8.createElement(TableRow5, null, /* @__PURE__ */ React8.createElement(TableCell5, { colSpan: 6, sx: { py: 2 } }, /* @__PURE__ */ React8.createElement(Skeleton5, { variant: "rounded", height: 160 })));
2444
2612
  }
2445
2613
  if (entries.length === 0) {
2446
- return /* @__PURE__ */ React7.createElement(TableRow4, null, /* @__PURE__ */ React7.createElement(TableCell4, { colSpan: 6 }, /* @__PURE__ */ React7.createElement(
2614
+ return /* @__PURE__ */ React8.createElement(TableRow5, null, /* @__PURE__ */ React8.createElement(TableCell5, { colSpan: 6 }, /* @__PURE__ */ React8.createElement(
2447
2615
  EmptyState,
2448
2616
  {
2449
2617
  message: "No audit events found",
@@ -2451,7 +2619,7 @@ function renderAuditLogBody(loading, entries) {
2451
2619
  }
2452
2620
  )));
2453
2621
  }
2454
- return entries.map((entry) => /* @__PURE__ */ React7.createElement(DetailRow, { key: entry.id, entry }));
2622
+ return entries.map((entry) => /* @__PURE__ */ React8.createElement(DetailRow, { key: entry.id, entry }));
2455
2623
  }
2456
2624
  var ACTION_TONES, TABLE_LABELS, DetailRow, AuditLog;
2457
2625
  var init_AuditLog = __esm({
@@ -2471,9 +2639,9 @@ var init_AuditLog = __esm({
2471
2639
  LiteLLM_UserTable: "User"
2472
2640
  };
2473
2641
  DetailRow = ({ entry }) => {
2474
- const [open, setOpen] = useState5(false);
2642
+ const [open, setOpen] = useState6(false);
2475
2643
  const hasDetail = entry.before_value || entry.updated_values;
2476
- return /* @__PURE__ */ React7.createElement(React7.Fragment, null, /* @__PURE__ */ React7.createElement(TableRow4, null, /* @__PURE__ */ React7.createElement(TableCell4, { sx: { width: 40, pr: 0 } }, hasDetail && /* @__PURE__ */ React7.createElement(
2644
+ return /* @__PURE__ */ React8.createElement(React8.Fragment, null, /* @__PURE__ */ React8.createElement(TableRow5, null, /* @__PURE__ */ React8.createElement(TableCell5, { sx: { width: 40, pr: 0 } }, hasDetail && /* @__PURE__ */ React8.createElement(
2477
2645
  IconButton4,
2478
2646
  {
2479
2647
  size: "small",
@@ -2486,9 +2654,9 @@ var init_AuditLog = __esm({
2486
2654
  transition: theme.transitions.create("transform")
2487
2655
  })
2488
2656
  },
2489
- /* @__PURE__ */ React7.createElement(KeyboardArrowDown, { fontSize: "small" })
2490
- )), /* @__PURE__ */ React7.createElement(TableCell4, { sx: { whiteSpace: "nowrap" } }, /* @__PURE__ */ React7.createElement(Typography7, { variant: "body2", color: "text.secondary" }, formatDateTime(entry.updated_at))), /* @__PURE__ */ React7.createElement(TableCell4, null, entry.action && /* @__PURE__ */ React7.createElement(StatusPill, { label: entry.action, tone: actionTone(entry.action) })), /* @__PURE__ */ React7.createElement(TableCell4, null, /* @__PURE__ */ React7.createElement(Typography7, { variant: "body2" }, prettyTableName(entry.table_name))), /* @__PURE__ */ React7.createElement(TableCell4, null, /* @__PURE__ */ React7.createElement(
2491
- Typography7,
2657
+ /* @__PURE__ */ React8.createElement(KeyboardArrowDown, { fontSize: "small" })
2658
+ )), /* @__PURE__ */ React8.createElement(TableCell5, { sx: { whiteSpace: "nowrap" } }, /* @__PURE__ */ React8.createElement(Typography8, { variant: "body2", color: "text.secondary" }, formatDateTime(entry.updated_at))), /* @__PURE__ */ React8.createElement(TableCell5, null, entry.action && /* @__PURE__ */ React8.createElement(StatusPill, { label: entry.action, tone: actionTone(entry.action) })), /* @__PURE__ */ React8.createElement(TableCell5, null, /* @__PURE__ */ React8.createElement(Typography8, { variant: "body2" }, prettyTableName(entry.table_name))), /* @__PURE__ */ React8.createElement(TableCell5, null, /* @__PURE__ */ React8.createElement(
2659
+ Typography8,
2492
2660
  {
2493
2661
  variant: "body2",
2494
2662
  component: "code",
@@ -2497,8 +2665,8 @@ var init_AuditLog = __esm({
2497
2665
  sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 12 }
2498
2666
  },
2499
2667
  entry.object_id ? entry.object_id.slice(0, 20) + (entry.object_id.length > 20 ? "\u2026" : "") : "\u2014"
2500
- )), /* @__PURE__ */ React7.createElement(TableCell4, null, entry.changed_by ?? "\u2014")), hasDetail && /* @__PURE__ */ React7.createElement(TableRow4, { sx: { "&&:hover": { bgcolor: "transparent" } } }, /* @__PURE__ */ React7.createElement(TableCell4, { colSpan: 6, sx: { "&&": { py: 0, border: 0 } } }, /* @__PURE__ */ React7.createElement(Collapse2, { in: open, unmountOnExit: true }, /* @__PURE__ */ React7.createElement(
2501
- Box7,
2668
+ )), /* @__PURE__ */ React8.createElement(TableCell5, null, entry.changed_by ?? "\u2014")), hasDetail && /* @__PURE__ */ React8.createElement(TableRow5, { sx: { "&&:hover": { bgcolor: "transparent" } } }, /* @__PURE__ */ React8.createElement(TableCell5, { colSpan: 6, sx: { "&&": { py: 0, border: 0 } } }, /* @__PURE__ */ React8.createElement(Collapse2, { in: open, unmountOnExit: true }, /* @__PURE__ */ React8.createElement(
2669
+ Box8,
2502
2670
  {
2503
2671
  display: "flex",
2504
2672
  gap: 2,
@@ -2507,11 +2675,11 @@ var init_AuditLog = __esm({
2507
2675
  p: 2,
2508
2676
  mb: 1.5,
2509
2677
  borderRadius: 1.5,
2510
- bgcolor: alpha5(theme.palette.text.primary, theme.palette.mode === "dark" ? 0.05 : 0.03)
2678
+ bgcolor: alpha6(theme.palette.text.primary, theme.palette.mode === "dark" ? 0.05 : 0.03)
2511
2679
  })
2512
2680
  },
2513
- entry.before_value && /* @__PURE__ */ React7.createElement(Box7, { flex: 1, minWidth: 200 }, /* @__PURE__ */ React7.createElement(
2514
- Typography7,
2681
+ entry.before_value && /* @__PURE__ */ React8.createElement(Box8, { flex: 1, minWidth: 200 }, /* @__PURE__ */ React8.createElement(
2682
+ Typography8,
2515
2683
  {
2516
2684
  variant: "caption",
2517
2685
  color: "text.secondary",
@@ -2520,9 +2688,9 @@ var init_AuditLog = __esm({
2520
2688
  sx: { fontSize: 10.5, fontWeight: 700, letterSpacing: "0.07em", textTransform: "uppercase" }
2521
2689
  },
2522
2690
  "Before"
2523
- ), /* @__PURE__ */ React7.createElement(Typography7, { 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))),
2524
- entry.updated_values && /* @__PURE__ */ React7.createElement(Box7, { flex: 1, minWidth: 200 }, /* @__PURE__ */ React7.createElement(
2525
- Typography7,
2691
+ ), /* @__PURE__ */ React8.createElement(Typography8, { 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))),
2692
+ entry.updated_values && /* @__PURE__ */ React8.createElement(Box8, { flex: 1, minWidth: 200 }, /* @__PURE__ */ React8.createElement(
2693
+ Typography8,
2526
2694
  {
2527
2695
  variant: "caption",
2528
2696
  color: "text.secondary",
@@ -2531,14 +2699,14 @@ var init_AuditLog = __esm({
2531
2699
  sx: { fontSize: 10.5, fontWeight: 700, letterSpacing: "0.07em", textTransform: "uppercase" }
2532
2700
  },
2533
2701
  "After"
2534
- ), /* @__PURE__ */ React7.createElement(Typography7, { 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)))
2702
+ ), /* @__PURE__ */ React8.createElement(Typography8, { 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)))
2535
2703
  )))));
2536
2704
  };
2537
2705
  AuditLog = ({ api }) => {
2538
- const [page, setPage] = useState5(0);
2539
- const [pageSize, setPageSize] = useState5(25);
2540
- const [filters, setFilters] = useState5({});
2541
- const fetchParams = useCallback(
2706
+ const [page, setPage] = useState6(0);
2707
+ const [pageSize, setPageSize] = useState6(25);
2708
+ const [filters, setFilters] = useState6({});
2709
+ const fetchParams = useCallback2(
2542
2710
  () => ({ page: page + 1, page_size: pageSize, ...filters }),
2543
2711
  [page, pageSize, filters]
2544
2712
  )();
@@ -2548,13 +2716,13 @@ var init_AuditLog = __esm({
2548
2716
  );
2549
2717
  const entries = value?.audit_logs ?? [];
2550
2718
  const total = value?.total ?? 0;
2551
- return /* @__PURE__ */ React7.createElement(
2719
+ return /* @__PURE__ */ React8.createElement(
2552
2720
  SectionCard,
2553
2721
  {
2554
2722
  title: "Audit Log",
2555
2723
  subtitle: loading ? "Loading\u2026" : `${total.toLocaleString()} event${total === 1 ? "" : "s"}`,
2556
2724
  flush: true,
2557
- actions: /* @__PURE__ */ React7.createElement(React7.Fragment, null, /* @__PURE__ */ React7.createElement(
2725
+ actions: /* @__PURE__ */ React8.createElement(React8.Fragment, null, /* @__PURE__ */ React8.createElement(
2558
2726
  TextField4,
2559
2727
  {
2560
2728
  size: "small",
@@ -2567,12 +2735,12 @@ var init_AuditLog = __esm({
2567
2735
  },
2568
2736
  sx: { minWidth: 150 }
2569
2737
  },
2570
- /* @__PURE__ */ React7.createElement(MenuItem3, { value: "" }, "All actions"),
2571
- /* @__PURE__ */ React7.createElement(MenuItem3, { value: "created" }, "Created"),
2572
- /* @__PURE__ */ React7.createElement(MenuItem3, { value: "updated" }, "Updated"),
2573
- /* @__PURE__ */ React7.createElement(MenuItem3, { value: "deleted" }, "Deleted"),
2574
- /* @__PURE__ */ React7.createElement(MenuItem3, { value: "blocked" }, "Blocked")
2575
- ), /* @__PURE__ */ React7.createElement(
2738
+ /* @__PURE__ */ React8.createElement(MenuItem4, { value: "" }, "All actions"),
2739
+ /* @__PURE__ */ React8.createElement(MenuItem4, { value: "created" }, "Created"),
2740
+ /* @__PURE__ */ React8.createElement(MenuItem4, { value: "updated" }, "Updated"),
2741
+ /* @__PURE__ */ React8.createElement(MenuItem4, { value: "deleted" }, "Deleted"),
2742
+ /* @__PURE__ */ React8.createElement(MenuItem4, { value: "blocked" }, "Blocked")
2743
+ ), /* @__PURE__ */ React8.createElement(
2576
2744
  TextField4,
2577
2745
  {
2578
2746
  size: "small",
@@ -2585,11 +2753,11 @@ var init_AuditLog = __esm({
2585
2753
  },
2586
2754
  sx: { minWidth: 150 }
2587
2755
  },
2588
- /* @__PURE__ */ React7.createElement(MenuItem3, { value: "" }, "All tables"),
2589
- /* @__PURE__ */ React7.createElement(MenuItem3, { value: "LiteLLM_VerificationToken" }, "Key"),
2590
- /* @__PURE__ */ React7.createElement(MenuItem3, { value: "LiteLLM_TeamTable" }, "Team"),
2591
- /* @__PURE__ */ React7.createElement(MenuItem3, { value: "LiteLLM_UserTable" }, "User")
2592
- ), /* @__PURE__ */ React7.createElement(
2756
+ /* @__PURE__ */ React8.createElement(MenuItem4, { value: "" }, "All tables"),
2757
+ /* @__PURE__ */ React8.createElement(MenuItem4, { value: "LiteLLM_VerificationToken" }, "Key"),
2758
+ /* @__PURE__ */ React8.createElement(MenuItem4, { value: "LiteLLM_TeamTable" }, "Team"),
2759
+ /* @__PURE__ */ React8.createElement(MenuItem4, { value: "LiteLLM_UserTable" }, "User")
2760
+ ), /* @__PURE__ */ React8.createElement(
2593
2761
  TextField4,
2594
2762
  {
2595
2763
  size: "small",
@@ -2603,9 +2771,9 @@ var init_AuditLog = __esm({
2603
2771
  }
2604
2772
  ))
2605
2773
  },
2606
- error && /* @__PURE__ */ React7.createElement(Box7, { px: 2.5, pb: 2 }, /* @__PURE__ */ React7.createElement(Alert2, { severity: "error" }, error.message)),
2607
- /* @__PURE__ */ React7.createElement(TableContainer4, null, /* @__PURE__ */ React7.createElement(Table4, { size: "small", sx: dataTableSx }, /* @__PURE__ */ React7.createElement(TableHead4, null, /* @__PURE__ */ React7.createElement(TableRow4, null, /* @__PURE__ */ React7.createElement(TableCell4, { sx: { width: 40 } }), /* @__PURE__ */ React7.createElement(TableCell4, null, "Time"), /* @__PURE__ */ React7.createElement(TableCell4, null, "Action"), /* @__PURE__ */ React7.createElement(TableCell4, null, "Table"), /* @__PURE__ */ React7.createElement(TableCell4, null, "Object ID"), /* @__PURE__ */ React7.createElement(TableCell4, null, "Changed By"))), /* @__PURE__ */ React7.createElement(TableBody4, null, renderAuditLogBody(loading, entries)))),
2608
- /* @__PURE__ */ React7.createElement(
2774
+ error && /* @__PURE__ */ React8.createElement(Box8, { px: 2.5, pb: 2 }, /* @__PURE__ */ React8.createElement(Alert3, { severity: "error" }, error.message)),
2775
+ /* @__PURE__ */ React8.createElement(TableContainer5, null, /* @__PURE__ */ React8.createElement(Table5, { size: "small", sx: dataTableSx }, /* @__PURE__ */ React8.createElement(TableHead5, null, /* @__PURE__ */ React8.createElement(TableRow5, null, /* @__PURE__ */ React8.createElement(TableCell5, { sx: { width: 40 } }), /* @__PURE__ */ React8.createElement(TableCell5, null, "Time"), /* @__PURE__ */ React8.createElement(TableCell5, null, "Action"), /* @__PURE__ */ React8.createElement(TableCell5, null, "Table"), /* @__PURE__ */ React8.createElement(TableCell5, null, "Object ID"), /* @__PURE__ */ React8.createElement(TableCell5, null, "Changed By"))), /* @__PURE__ */ React8.createElement(TableBody5, null, renderAuditLogBody(loading, entries)))),
2776
+ /* @__PURE__ */ React8.createElement(
2609
2777
  TablePagination,
2610
2778
  {
2611
2779
  component: "div",
@@ -2631,12 +2799,12 @@ var LiteLLMPage_exports = {};
2631
2799
  __export(LiteLLMPage_exports, {
2632
2800
  LiteLLMPage: () => LiteLLMPage
2633
2801
  });
2634
- import React8, { useState as useState6, useCallback as useCallback2, useMemo as useMemo5 } from "react";
2635
- import Box8 from "@mui/material/Box";
2636
- import Snackbar from "@mui/material/Snackbar";
2637
- import Alert3 from "@mui/material/Alert";
2802
+ import React9, { useState as useState7, useCallback as useCallback3, useMemo as useMemo6 } from "react";
2803
+ import Box9 from "@mui/material/Box";
2804
+ import Snackbar2 from "@mui/material/Snackbar";
2805
+ import Alert4 from "@mui/material/Alert";
2638
2806
  import CircularProgress4 from "@mui/material/CircularProgress";
2639
- import Typography8 from "@mui/material/Typography";
2807
+ import Typography9 from "@mui/material/Typography";
2640
2808
  import Paper5 from "@mui/material/Paper";
2641
2809
  import Tabs2 from "@mui/material/Tabs";
2642
2810
  import Tab2 from "@mui/material/Tab";
@@ -2665,17 +2833,18 @@ var init_LiteLLMPage = __esm({
2665
2833
  init_GenerateKeyDialog();
2666
2834
  init_UsageStats();
2667
2835
  init_TeamUsage();
2836
+ init_ModelsTable();
2668
2837
  init_AuditLog();
2669
2838
  init_api();
2670
2839
  PERIOD_LS_KEY2 = "litellm_usage_period";
2671
2840
  LiteLLMPage = () => {
2672
2841
  const api = useApi(liteLlmApiRef);
2673
- const [dateRange, setDateRange] = useState6(initDateRange);
2674
- const [activeTab, setActiveTab] = useState6("overview");
2675
- const [snackbar, setSnackbar] = useState6(null);
2676
- const [generateDialogOpen, setGenerateDialogOpen] = useState6(false);
2677
- const [teamUsageCache, setTeamUsageCache] = useState6({});
2678
- const [teamUsageLoading, setTeamUsageLoading] = useState6({});
2842
+ const [dateRange, setDateRange] = useState7(initDateRange);
2843
+ const [activeTab, setActiveTab] = useState7("overview");
2844
+ const [snackbar, setSnackbar] = useState7(null);
2845
+ const [generateDialogOpen, setGenerateDialogOpen] = useState7(false);
2846
+ const [teamUsageCache, setTeamUsageCache] = useState7({});
2847
+ const [teamUsageLoading, setTeamUsageLoading] = useState7({});
2679
2848
  const { value: userInfo, loading: userLoading, error: userError } = useAsync2(
2680
2849
  () => api.getUserInfo(),
2681
2850
  [api]
@@ -2700,7 +2869,7 @@ var init_LiteLLMPage = __esm({
2700
2869
  [api]
2701
2870
  );
2702
2871
  const { value: liteLlmConfig } = useAsync2(() => api.getConfig(), [api]);
2703
- const teams = useMemo5(() => {
2872
+ const teams = useMemo6(() => {
2704
2873
  if (!allTeams?.length) return [];
2705
2874
  if (!userInfo) return allTeams;
2706
2875
  const userId = userInfo.user_id;
@@ -2717,12 +2886,12 @@ var init_LiteLLMPage = __esm({
2717
2886
  const endDate = dateRange.end.toISOString().split("T")[0];
2718
2887
  return api.getUsage(startDate, endDate);
2719
2888
  }, [api, dateRange]);
2720
- const handleDateRangeChange = useCallback2((range) => {
2889
+ const handleDateRangeChange = useCallback3((range) => {
2721
2890
  setDateRange(range);
2722
2891
  setTeamUsageCache({});
2723
2892
  setTeamUsageLoading({});
2724
2893
  }, [setDateRange]);
2725
- const loadTeamUsage = useCallback2(async (teamId) => {
2894
+ const loadTeamUsage = useCallback3(async (teamId) => {
2726
2895
  if (teamUsageCache[teamId] !== void 0 || teamUsageLoading[teamId]) return;
2727
2896
  setTeamUsageLoading((prev) => ({ ...prev, [teamId]: true }));
2728
2897
  try {
@@ -2736,7 +2905,7 @@ var init_LiteLLMPage = __esm({
2736
2905
  setTeamUsageLoading((prev) => ({ ...prev, [teamId]: false }));
2737
2906
  }
2738
2907
  }, [api, dateRange, teamUsageCache, teamUsageLoading]);
2739
- const allowedModels = useMemo5(() => {
2908
+ const allowedModels = useMemo6(() => {
2740
2909
  if (!allModels?.length) return [];
2741
2910
  const userModels = userInfo?.models;
2742
2911
  const teamModels = teams?.flatMap((t) => t.models ?? []);
@@ -2749,7 +2918,7 @@ var init_LiteLLMPage = __esm({
2749
2918
  ]);
2750
2919
  return allModels.filter((m) => allowed.has(m.model_name));
2751
2920
  }, [allModels, userInfo, teams]);
2752
- const handleGenerateKey = useCallback2(
2921
+ const handleGenerateKey = useCallback3(
2753
2922
  async (request) => {
2754
2923
  try {
2755
2924
  const response = await api.generateKey(request);
@@ -2763,7 +2932,7 @@ var init_LiteLLMPage = __esm({
2763
2932
  },
2764
2933
  [api, refreshKeys]
2765
2934
  );
2766
- const handleUpdateKey = useCallback2(
2935
+ const handleUpdateKey = useCallback3(
2767
2936
  async (keyId, request) => {
2768
2937
  try {
2769
2938
  await api.updateKey(keyId, request);
@@ -2776,7 +2945,7 @@ var init_LiteLLMPage = __esm({
2776
2945
  },
2777
2946
  [api, refreshKeys]
2778
2947
  );
2779
- const handleBlockKey = useCallback2(
2948
+ const handleBlockKey = useCallback3(
2780
2949
  async (keyId) => {
2781
2950
  try {
2782
2951
  await api.blockKey(keyId);
@@ -2788,7 +2957,7 @@ var init_LiteLLMPage = __esm({
2788
2957
  },
2789
2958
  [api, refreshKeys]
2790
2959
  );
2791
- const handleUnblockKey = useCallback2(
2960
+ const handleUnblockKey = useCallback3(
2792
2961
  async (keyId) => {
2793
2962
  try {
2794
2963
  await api.unblockKey(keyId);
@@ -2800,7 +2969,7 @@ var init_LiteLLMPage = __esm({
2800
2969
  },
2801
2970
  [api, refreshKeys]
2802
2971
  );
2803
- const handleResetKeySpend = useCallback2(
2972
+ const handleResetKeySpend = useCallback3(
2804
2973
  async (keyId) => {
2805
2974
  try {
2806
2975
  await api.resetKeySpend(keyId);
@@ -2812,7 +2981,7 @@ var init_LiteLLMPage = __esm({
2812
2981
  },
2813
2982
  [api, refreshKeys]
2814
2983
  );
2815
- const handleDeleteKey = useCallback2(
2984
+ const handleDeleteKey = useCallback3(
2816
2985
  async (keyId) => {
2817
2986
  try {
2818
2987
  await api.deleteKey(keyId);
@@ -2829,7 +2998,7 @@ var init_LiteLLMPage = __esm({
2829
2998
  },
2830
2999
  [api, refreshKeys]
2831
3000
  );
2832
- const handlePruneExpiredKeys = useCallback2(
3001
+ const handlePruneExpiredKeys = useCallback3(
2833
3002
  async () => {
2834
3003
  try {
2835
3004
  const result = await api.pruneExpiredKeys();
@@ -2844,14 +3013,14 @@ var init_LiteLLMPage = __esm({
2844
3013
  );
2845
3014
  const isInitialLoading = userLoading && !userInfo;
2846
3015
  if (isInitialLoading) {
2847
- return /* @__PURE__ */ React8.createElement(Box8, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: "50vh" }, /* @__PURE__ */ React8.createElement(CircularProgress4, null));
3016
+ return /* @__PURE__ */ React9.createElement(Box9, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: "50vh" }, /* @__PURE__ */ React9.createElement(CircularProgress4, null));
2848
3017
  }
2849
3018
  if (userError || !userInfo) {
2850
3019
  const isProvisioningEnabled = userError?.body?.provisioning === true;
2851
3020
  const hint = userError?.body?.hint;
2852
- return /* @__PURE__ */ React8.createElement(Box8, { p: 3 }, /* @__PURE__ */ React8.createElement(Paper5, { sx: { p: 3 } }, /* @__PURE__ */ React8.createElement(Typography8, { variant: "h6", gutterBottom: true }, "Account not provisioned"), /* @__PURE__ */ React8.createElement(Typography8, { color: "text.secondary", paragraph: true }, "Your Backstage account is not linked to a LiteLLM user."), hint ? /* @__PURE__ */ React8.createElement(Typography8, { variant: "body2", color: "text.secondary" }, hint) : /* @__PURE__ */ React8.createElement(Typography8, { 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.")));
3021
+ return /* @__PURE__ */ React9.createElement(Box9, { p: 3 }, /* @__PURE__ */ React9.createElement(Paper5, { sx: { p: 3 } }, /* @__PURE__ */ React9.createElement(Typography9, { variant: "h6", gutterBottom: true }, "Account not provisioned"), /* @__PURE__ */ React9.createElement(Typography9, { color: "text.secondary", paragraph: true }, "Your Backstage account is not linked to a LiteLLM user."), hint ? /* @__PURE__ */ React9.createElement(Typography9, { variant: "body2", color: "text.secondary" }, hint) : /* @__PURE__ */ React9.createElement(Typography9, { 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.")));
2853
3022
  }
2854
- const pageTabs = /* @__PURE__ */ React8.createElement(
3023
+ const pageTabs = /* @__PURE__ */ React9.createElement(
2855
3024
  Tabs2,
2856
3025
  {
2857
3026
  value: activeTab,
@@ -2864,12 +3033,13 @@ var init_LiteLLMPage = __esm({
2864
3033
  '& [class*="MuiTab-root"]': { minHeight: 44, textTransform: "none", fontSize: 14 }
2865
3034
  }
2866
3035
  },
2867
- /* @__PURE__ */ React8.createElement(Tab2, { label: "Overview", value: "overview" }),
2868
- /* @__PURE__ */ React8.createElement(Tab2, { label: "Keys", value: "keys" }),
2869
- /* @__PURE__ */ React8.createElement(Tab2, { label: "Teams", value: "teams" }),
2870
- userInfo.can_view_audit && /* @__PURE__ */ React8.createElement(Tab2, { label: "Audit Log", value: "audit" })
3036
+ /* @__PURE__ */ React9.createElement(Tab2, { label: "Overview", value: "overview" }),
3037
+ /* @__PURE__ */ React9.createElement(Tab2, { label: "Keys", value: "keys" }),
3038
+ /* @__PURE__ */ React9.createElement(Tab2, { label: "Teams", value: "teams" }),
3039
+ /* @__PURE__ */ React9.createElement(Tab2, { label: "Models", value: "models" }),
3040
+ userInfo.can_view_audit && /* @__PURE__ */ React9.createElement(Tab2, { label: "Audit Log", value: "audit" })
2871
3041
  );
2872
- return /* @__PURE__ */ React8.createElement(Box8, { sx: { p: 3, display: "flex", flexDirection: "column", gap: 2 } }, /* @__PURE__ */ React8.createElement(
3042
+ return /* @__PURE__ */ React9.createElement(Box9, { sx: { p: 3, display: "flex", flexDirection: "column", gap: 2 } }, /* @__PURE__ */ React9.createElement(
2873
3043
  DashboardHeader,
2874
3044
  {
2875
3045
  userInfo,
@@ -2879,7 +3049,7 @@ var init_LiteLLMPage = __esm({
2879
3049
  onGenerateKeyClick: () => setGenerateDialogOpen(true),
2880
3050
  tabs: pageTabs
2881
3051
  }
2882
- ), activeTab === "overview" && /* @__PURE__ */ React8.createElement(
3052
+ ), activeTab === "overview" && /* @__PURE__ */ React9.createElement(
2883
3053
  UsageStats,
2884
3054
  {
2885
3055
  usage: usage ?? null,
@@ -2889,7 +3059,7 @@ var init_LiteLLMPage = __esm({
2889
3059
  loading: usageLoading,
2890
3060
  userInfo
2891
3061
  }
2892
- ), activeTab === "keys" && /* @__PURE__ */ React8.createElement(
3062
+ ), activeTab === "keys" && /* @__PURE__ */ React9.createElement(
2893
3063
  KeysTable,
2894
3064
  {
2895
3065
  keys: keys ?? [],
@@ -2903,7 +3073,7 @@ var init_LiteLLMPage = __esm({
2903
3073
  onDeleteKey: handleDeleteKey,
2904
3074
  onPruneExpiredKeys: handlePruneExpiredKeys
2905
3075
  }
2906
- ), activeTab === "teams" && /* @__PURE__ */ React8.createElement(
3076
+ ), activeTab === "teams" && /* @__PURE__ */ React9.createElement(
2907
3077
  TeamUsage,
2908
3078
  {
2909
3079
  teams: teams ?? [],
@@ -2914,7 +3084,14 @@ var init_LiteLLMPage = __esm({
2914
3084
  },
2915
3085
  getTeamUsageLoading: (teamId) => teamUsageLoading[teamId] ?? false
2916
3086
  }
2917
- ), activeTab === "audit" && userInfo.can_view_audit && /* @__PURE__ */ React8.createElement(AuditLog, { api }), /* @__PURE__ */ React8.createElement(
3087
+ ), activeTab === "models" && /* @__PURE__ */ React9.createElement(
3088
+ ModelsTable,
3089
+ {
3090
+ allModels: allModels ?? [],
3091
+ teams: teams ?? [],
3092
+ loading: modelsLoading
3093
+ }
3094
+ ), activeTab === "audit" && userInfo.can_view_audit && /* @__PURE__ */ React9.createElement(AuditLog, { api }), /* @__PURE__ */ React9.createElement(
2918
3095
  GenerateKeyDialog,
2919
3096
  {
2920
3097
  open: generateDialogOpen,
@@ -2927,15 +3104,15 @@ var init_LiteLLMPage = __esm({
2927
3104
  onGenerateKey: handleGenerateKey,
2928
3105
  onGetConfig: () => api.getConfig()
2929
3106
  }
2930
- ), /* @__PURE__ */ React8.createElement(
2931
- Snackbar,
3107
+ ), /* @__PURE__ */ React9.createElement(
3108
+ Snackbar2,
2932
3109
  {
2933
3110
  open: !!snackbar,
2934
3111
  autoHideDuration: 5e3,
2935
3112
  onClose: () => setSnackbar(null),
2936
3113
  anchorOrigin: { vertical: "bottom", horizontal: "right" }
2937
3114
  },
2938
- snackbar ? /* @__PURE__ */ React8.createElement(Alert3, { severity: snackbar.severity, onClose: () => setSnackbar(null) }, snackbar.message) : void 0
3115
+ snackbar ? /* @__PURE__ */ React9.createElement(Alert4, { severity: snackbar.severity, onClose: () => setSnackbar(null) }, snackbar.message) : void 0
2939
3116
  ));
2940
3117
  };
2941
3118
  }
@@ -2943,7 +3120,7 @@ var init_LiteLLMPage = __esm({
2943
3120
 
2944
3121
  // src/plugin.tsx
2945
3122
  init_api();
2946
- import React9 from "react";
3123
+ import React10 from "react";
2947
3124
  import { TrendingUp as TrendingUpIcon } from "@mui/icons-material";
2948
3125
  import {
2949
3126
  createFrontendPlugin,
@@ -2962,10 +3139,10 @@ var liteLlmPage = PageBlueprint.make({
2962
3139
  params: {
2963
3140
  path: "/litellm",
2964
3141
  title: "LiteLLM",
2965
- icon: /* @__PURE__ */ React9.createElement(TrendingUpIcon, null),
3142
+ icon: /* @__PURE__ */ React10.createElement(TrendingUpIcon, null),
2966
3143
  loader: async () => {
2967
3144
  const { LiteLLMPage: LiteLLMPage2 } = await Promise.resolve().then(() => (init_LiteLLMPage(), LiteLLMPage_exports));
2968
- return /* @__PURE__ */ React9.createElement(LiteLLMPage2, null);
3145
+ return /* @__PURE__ */ React10.createElement(LiteLLMPage2, null);
2969
3146
  }
2970
3147
  }
2971
3148
  });
@@ -2984,16 +3161,16 @@ init_TeamUsage();
2984
3161
  // src/components/LiteLLMHomeWidget.tsx
2985
3162
  init_api();
2986
3163
  init_format();
2987
- import React10, { useState as useState7, useEffect as useEffect2 } from "react";
3164
+ import React11, { useState as useState8, useEffect as useEffect2 } from "react";
2988
3165
  import Paper6 from "@mui/material/Paper";
2989
- import Box9 from "@mui/material/Box";
2990
- import Typography9 from "@mui/material/Typography";
2991
- import FormControl from "@mui/material/FormControl";
2992
- import Select from "@mui/material/Select";
2993
- import MenuItem4 from "@mui/material/MenuItem";
3166
+ import Box10 from "@mui/material/Box";
3167
+ import Typography10 from "@mui/material/Typography";
3168
+ import FormControl2 from "@mui/material/FormControl";
3169
+ import Select2 from "@mui/material/Select";
3170
+ import MenuItem5 from "@mui/material/MenuItem";
2994
3171
  import Grid2 from "@mui/material/Grid";
2995
3172
  import CircularProgress5 from "@mui/material/CircularProgress";
2996
- import Alert4 from "@mui/material/Alert";
3173
+ import Alert5 from "@mui/material/Alert";
2997
3174
  import { AreaChart as AreaChart3, Area as Area3, ResponsiveContainer as ResponsiveContainer3 } from "recharts";
2998
3175
  import { useApi as useApi2 } from "@backstage/core-plugin-api";
2999
3176
  function presetToDateRange(preset) {
@@ -3008,18 +3185,18 @@ function presetToDateRange(preset) {
3008
3185
  }
3009
3186
  return { start, end };
3010
3187
  }
3011
- var Kpi = ({ label, value }) => /* @__PURE__ */ React10.createElement(Box9, null, /* @__PURE__ */ React10.createElement(Typography9, { variant: "caption", color: "text.secondary", display: "block" }, label), /* @__PURE__ */ React10.createElement(Typography9, { variant: "subtitle1", fontWeight: 600 }, value));
3188
+ var Kpi = ({ label, value }) => /* @__PURE__ */ React11.createElement(Box10, null, /* @__PURE__ */ React11.createElement(Typography10, { variant: "caption", color: "text.secondary", display: "block" }, label), /* @__PURE__ */ React11.createElement(Typography10, { variant: "subtitle1", fontWeight: 600 }, value));
3012
3189
  var LiteLLMHomeWidget = ({
3013
3190
  defaultPeriod = "7d",
3014
3191
  title = "LiteLLM Usage"
3015
3192
  }) => {
3016
3193
  const api = useApi2(liteLlmApiRef);
3017
- const [period, setPeriod] = useState7(defaultPeriod);
3018
- const [loading, setLoading] = useState7(true);
3019
- const [usageError, setUsageError] = useState7(null);
3020
- const [keysError, setKeysError] = useState7(null);
3021
- const [usage, setUsage] = useState7(null);
3022
- const [keys, setKeys] = useState7([]);
3194
+ const [period, setPeriod] = useState8(defaultPeriod);
3195
+ const [loading, setLoading] = useState8(true);
3196
+ const [usageError, setUsageError] = useState8(null);
3197
+ const [keysError, setKeysError] = useState8(null);
3198
+ const [usage, setUsage] = useState8(null);
3199
+ const [keys, setKeys] = useState8([]);
3023
3200
  useEffect2(() => {
3024
3201
  let cancelled = false;
3025
3202
  setLoading(true);
@@ -3057,17 +3234,17 @@ var LiteLLMHomeWidget = ({
3057
3234
  spend: d.spend
3058
3235
  }));
3059
3236
  const hasSparkline = dailyData.length > 0;
3060
- return /* @__PURE__ */ React10.createElement(Paper6, { sx: { p: 2 } }, /* @__PURE__ */ React10.createElement(Box9, { display: "flex", justifyContent: "space-between", alignItems: "center", mb: 1.5 }, /* @__PURE__ */ React10.createElement(Typography9, { variant: "h6" }, title), /* @__PURE__ */ React10.createElement(FormControl, { size: "small", sx: { minWidth: 90 } }, /* @__PURE__ */ React10.createElement(
3061
- Select,
3237
+ return /* @__PURE__ */ React11.createElement(Paper6, { sx: { p: 2 } }, /* @__PURE__ */ React11.createElement(Box10, { display: "flex", justifyContent: "space-between", alignItems: "center", mb: 1.5 }, /* @__PURE__ */ React11.createElement(Typography10, { variant: "h6" }, title), /* @__PURE__ */ React11.createElement(FormControl2, { size: "small", sx: { minWidth: 90 } }, /* @__PURE__ */ React11.createElement(
3238
+ Select2,
3062
3239
  {
3063
3240
  value: period,
3064
3241
  onChange: (e) => setPeriod(e.target.value),
3065
3242
  displayEmpty: true
3066
3243
  },
3067
- /* @__PURE__ */ React10.createElement(MenuItem4, { value: "today" }, "Today"),
3068
- /* @__PURE__ */ React10.createElement(MenuItem4, { value: "7d" }, "7d"),
3069
- /* @__PURE__ */ React10.createElement(MenuItem4, { value: "30d" }, "30d")
3070
- ))), loading && /* @__PURE__ */ React10.createElement(Box9, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: 120 }, /* @__PURE__ */ React10.createElement(CircularProgress5, { size: 32 })), !loading && totalFailure && /* @__PURE__ */ React10.createElement(Alert4, { severity: "error", sx: { mt: 1 } }, usageError ?? "Failed to load usage data"), !loading && !totalFailure && /* @__PURE__ */ React10.createElement(React10.Fragment, null, partialFailure && /* @__PURE__ */ React10.createElement(Alert4, { severity: "warning", sx: { mt: 1, mb: 1 } }, usageError ? `Usage data unavailable (${usageError}).` : "", keysError ? ` Key list unavailable (${keysError}).` : "", " Showing what loaded."), /* @__PURE__ */ React10.createElement(Grid2, { container: true, spacing: 2, sx: { mb: hasSparkline ? 1.5 : 0 } }, /* @__PURE__ */ React10.createElement(Grid2, { item: true, xs: 6 }, /* @__PURE__ */ React10.createElement(Kpi, { label: "USD Spent", value: fmtUsd(usage?.total_spend ?? 0) })), /* @__PURE__ */ React10.createElement(Grid2, { item: true, xs: 6 }, /* @__PURE__ */ React10.createElement(Kpi, { label: "Tokens In", value: fmtInt(usage?.prompt_tokens ?? 0) })), /* @__PURE__ */ React10.createElement(Grid2, { item: true, xs: 6 }, /* @__PURE__ */ React10.createElement(Kpi, { label: "Tokens Out", value: fmtInt(usage?.completion_tokens ?? 0) })), /* @__PURE__ */ React10.createElement(Grid2, { item: true, xs: 6 }, /* @__PURE__ */ React10.createElement(Kpi, { label: "Keys", value: fmtInt(keys.length) }))), hasSparkline && /* @__PURE__ */ React10.createElement(Box9, { height: 120 }, /* @__PURE__ */ React10.createElement(ResponsiveContainer3, { width: "100%", height: "100%" }, /* @__PURE__ */ React10.createElement(AreaChart3, { data: dailyData, margin: { top: 4, right: 0, bottom: 0, left: 0 } }, /* @__PURE__ */ React10.createElement(
3244
+ /* @__PURE__ */ React11.createElement(MenuItem5, { value: "today" }, "Today"),
3245
+ /* @__PURE__ */ React11.createElement(MenuItem5, { value: "7d" }, "7d"),
3246
+ /* @__PURE__ */ React11.createElement(MenuItem5, { value: "30d" }, "30d")
3247
+ ))), loading && /* @__PURE__ */ React11.createElement(Box10, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: 120 }, /* @__PURE__ */ React11.createElement(CircularProgress5, { size: 32 })), !loading && totalFailure && /* @__PURE__ */ React11.createElement(Alert5, { severity: "error", sx: { mt: 1 } }, usageError ?? "Failed to load usage data"), !loading && !totalFailure && /* @__PURE__ */ React11.createElement(React11.Fragment, null, partialFailure && /* @__PURE__ */ React11.createElement(Alert5, { severity: "warning", sx: { mt: 1, mb: 1 } }, usageError ? `Usage data unavailable (${usageError}).` : "", keysError ? ` Key list unavailable (${keysError}).` : "", " Showing what loaded."), /* @__PURE__ */ React11.createElement(Grid2, { container: true, spacing: 2, sx: { mb: hasSparkline ? 1.5 : 0 } }, /* @__PURE__ */ React11.createElement(Grid2, { item: true, xs: 6 }, /* @__PURE__ */ React11.createElement(Kpi, { label: "USD Spent", value: fmtUsd(usage?.total_spend ?? 0) })), /* @__PURE__ */ React11.createElement(Grid2, { item: true, xs: 6 }, /* @__PURE__ */ React11.createElement(Kpi, { label: "Tokens In", value: fmtInt(usage?.prompt_tokens ?? 0) })), /* @__PURE__ */ React11.createElement(Grid2, { item: true, xs: 6 }, /* @__PURE__ */ React11.createElement(Kpi, { label: "Tokens Out", value: fmtInt(usage?.completion_tokens ?? 0) })), /* @__PURE__ */ React11.createElement(Grid2, { item: true, xs: 6 }, /* @__PURE__ */ React11.createElement(Kpi, { label: "Keys", value: fmtInt(keys.length) }))), hasSparkline && /* @__PURE__ */ React11.createElement(Box10, { height: 120 }, /* @__PURE__ */ React11.createElement(ResponsiveContainer3, { width: "100%", height: "100%" }, /* @__PURE__ */ React11.createElement(AreaChart3, { data: dailyData, margin: { top: 4, right: 0, bottom: 0, left: 0 } }, /* @__PURE__ */ React11.createElement(
3071
3248
  Area3,
3072
3249
  {
3073
3250
  type: "monotone",