@acarmisc/backstage-plugin-litellm 0.24.0 → 0.26.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
@@ -1003,7 +1003,6 @@ import DialogContent from "@mui/material/DialogContent";
1003
1003
  import DialogActions from "@mui/material/DialogActions";
1004
1004
  import TextField from "@mui/material/TextField";
1005
1005
  import CircularProgress from "@mui/material/CircularProgress";
1006
- import Autocomplete from "@mui/material/Autocomplete";
1007
1006
  import Skeleton2 from "@mui/material/Skeleton";
1008
1007
  import InputAdornment from "@mui/material/InputAdornment";
1009
1008
  import { alpha as alpha3 } from "@mui/material/styles";
@@ -1052,26 +1051,6 @@ function BudgetCell({ spend, maxBudget }) {
1052
1051
  const pct = Math.min(100, spend / maxBudget * 100);
1053
1052
  return /* @__PURE__ */ React3.createElement(Box3, { minWidth: 104 }, /* @__PURE__ */ React3.createElement(Typography3, { variant: "body2", sx: { fontVariantNumeric: "tabular-nums", mb: 0.5 } }, "$", spend.toFixed(2), /* @__PURE__ */ React3.createElement(Typography3, { component: "span", variant: "caption", color: "text.secondary", sx: { ml: 0.5 } }, "/ $", maxBudget)), /* @__PURE__ */ React3.createElement(Meter, { value: pct, tone: budgetTone(pct), height: 4 }));
1054
1053
  }
1055
- function fmtCost(perToken) {
1056
- if (!perToken) return null;
1057
- const per1k = perToken * 1e3;
1058
- return per1k < 0.01 ? `$${(perToken * 1e6).toFixed(2)}/M` : `$${per1k.toFixed(3)}/1K`;
1059
- }
1060
- function formatContextWindow(maxInput, maxOutput) {
1061
- if (!maxInput && !maxOutput) return null;
1062
- const fmt = (n) => {
1063
- if (!n) return null;
1064
- if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
1065
- if (n >= 1e3) return `${Math.round(n / 1e3)}K`;
1066
- return String(n);
1067
- };
1068
- const inPart = fmt(maxInput);
1069
- const outPart = fmt(maxOutput);
1070
- if (inPart && outPart) return `ctx ${inPart} in / ${outPart} out`;
1071
- if (inPart) return `ctx ${inPart}`;
1072
- if (outPart) return `ctx ${outPart} out`;
1073
- return null;
1074
- }
1075
1054
  function compareKeys(a, b, sortKey) {
1076
1055
  switch (sortKey) {
1077
1056
  case "alias":
@@ -1090,16 +1069,12 @@ function compareKeys(a, b, sortKey) {
1090
1069
  return 0;
1091
1070
  }
1092
1071
  }
1093
- var maskKey, shortKeyId, formatDate, keyToEditForm, KeysTable;
1072
+ var shortKeyId, formatDate, KeysTable;
1094
1073
  var init_KeysTable = __esm({
1095
1074
  "src/components/KeysTable.tsx"() {
1096
1075
  "use strict";
1097
1076
  init_api();
1098
1077
  init_ui();
1099
- maskKey = (key) => {
1100
- if (key.length <= 8) return "***";
1101
- return `${key.slice(0, 4)}...${key.slice(-4)}`;
1102
- };
1103
1078
  shortKeyId = (token) => {
1104
1079
  if (!token) return "-";
1105
1080
  if (token.length <= 16) return token;
@@ -1112,33 +1087,19 @@ var init_KeysTable = __esm({
1112
1087
  return dateStr;
1113
1088
  }
1114
1089
  };
1115
- keyToEditForm = (k) => ({
1116
- key_alias: k.key_alias ?? "",
1117
- models: k.models ?? [],
1118
- max_budget: k.max_budget,
1119
- tpm_limit: k.tpm_limit,
1120
- rpm_limit: k.rpm_limit
1121
- });
1122
1090
  KeysTable = ({
1123
1091
  keys,
1124
- models,
1125
1092
  loading,
1126
1093
  onGenerateKeyClick,
1127
- onUpdateKey,
1094
+ onEditKey,
1128
1095
  onBlockKey,
1129
1096
  onUnblockKey,
1130
- onResetKeySpend,
1131
1097
  onDeleteKey,
1132
1098
  onPruneExpiredKeys
1133
1099
  }) => {
1134
- const [editingKey, setEditingKey] = useState(null);
1135
- const [editForm, setEditForm] = useState({});
1136
- const [editSubmitting, setEditSubmitting] = useState(false);
1137
1100
  const [blockingKeyId, setBlockingKeyId] = useState(null);
1138
1101
  const [deleteConfirmId, setDeleteConfirmId] = useState(null);
1139
1102
  const [deleteSubmitting, setDeleteSubmitting] = useState(false);
1140
- const [resetSpendConfirm, setResetSpendConfirm] = useState(false);
1141
- const [resetSpendSubmitting, setResetSpendSubmitting] = useState(false);
1142
1103
  const [filterText, setFilterText] = useState("");
1143
1104
  const [sortKey, setSortKey] = useState("created");
1144
1105
  const [sortDirection, setSortDirection] = useState("desc");
@@ -1163,27 +1124,8 @@ var init_KeysTable = __esm({
1163
1124
  return sortDirection === "asc" ? comparison : -comparison;
1164
1125
  });
1165
1126
  }, [keys, filterText, sortKey, sortDirection]);
1166
- const editSelectedModels = models.filter((m) => (editForm.models || []).includes(m.model_name));
1167
1127
  const handleOpenEdit = (k) => {
1168
- setEditingKey(k);
1169
- setEditForm(keyToEditForm(k));
1170
- };
1171
- const handleCloseEdit = () => {
1172
- setEditingKey(null);
1173
- setEditForm({});
1174
- setResetSpendConfirm(false);
1175
- };
1176
- const handleUpdate = async () => {
1177
- if (!editingKey) return;
1178
- setEditSubmitting(true);
1179
- try {
1180
- await onUpdateKey(editingKey.token ?? editingKey.key, editForm);
1181
- handleCloseEdit();
1182
- } catch (error) {
1183
- console.error("Failed to update key:", error);
1184
- } finally {
1185
- setEditSubmitting(false);
1186
- }
1128
+ onEditKey(k);
1187
1129
  };
1188
1130
  const handleToggleBlock = async (key) => {
1189
1131
  const keyId = key.token ?? key.key;
@@ -1198,17 +1140,6 @@ var init_KeysTable = __esm({
1198
1140
  setBlockingKeyId(null);
1199
1141
  }
1200
1142
  };
1201
- const handleResetSpend = async () => {
1202
- if (!editingKey) return;
1203
- setResetSpendSubmitting(true);
1204
- try {
1205
- await onResetKeySpend(editingKey.token ?? editingKey.key);
1206
- setResetSpendConfirm(false);
1207
- handleCloseEdit();
1208
- } finally {
1209
- setResetSpendSubmitting(false);
1210
- }
1211
- };
1212
1143
  const handleConfirmDelete = async () => {
1213
1144
  if (!deleteConfirmId) return;
1214
1145
  setDeleteSubmitting(true);
@@ -1239,12 +1170,6 @@ var init_KeysTable = __esm({
1239
1170
  setPruneConfirmCount(null);
1240
1171
  }
1241
1172
  };
1242
- const modelOption = (m) => {
1243
- const inCost = fmtCost(m.input_cost_per_token);
1244
- const outCost = fmtCost(m.output_cost_per_token);
1245
- const ctx = formatContextWindow(m.max_input_tokens, m.max_output_tokens);
1246
- return /* @__PURE__ */ React3.createElement(Box3, null, /* @__PURE__ */ React3.createElement("span", null, m.model_name), m.supports_function_calling && " \u{1F527}", m.supports_vision && " \u{1F441}\uFE0F", ctx && /* @__PURE__ */ React3.createElement(Typography3, { variant: "caption", color: "text.secondary", sx: { ml: 1 } }, ctx), (inCost || outCost) && /* @__PURE__ */ React3.createElement(Typography3, { variant: "caption", color: "text.secondary", sx: { ml: 1 } }, inCost, " in \xB7 ", outCost, " out"));
1247
- };
1248
1173
  const renderTableBody = () => {
1249
1174
  if (loading) {
1250
1175
  return /* @__PURE__ */ React3.createElement(TableRow, null, /* @__PURE__ */ React3.createElement(TableCell, { colSpan: 8, sx: { py: 2 } }, /* @__PURE__ */ React3.createElement(Skeleton2, { variant: "rounded", height: 140 })));
@@ -1416,75 +1341,7 @@ var init_KeysTable = __esm({
1416
1341
  ))
1417
1342
  },
1418
1343
  /* @__PURE__ */ React3.createElement(TableContainer, null, /* @__PURE__ */ React3.createElement(Table, { size: "small", sx: dataTableSx }, /* @__PURE__ */ React3.createElement(TableHead, null, /* @__PURE__ */ React3.createElement(TableRow, null, /* @__PURE__ */ React3.createElement(TableCell, { sortDirection: sortKey === "alias" ? sortDirection : false }, /* @__PURE__ */ React3.createElement(TableSortLabel, { active: sortKey === "alias", direction: sortKey === "alias" ? sortDirection : "asc", onClick: () => handleSort("alias") }, "Alias")), /* @__PURE__ */ React3.createElement(TableCell, null, "Key ID"), /* @__PURE__ */ React3.createElement(TableCell, { sortDirection: sortKey === "created" ? sortDirection : false }, /* @__PURE__ */ React3.createElement(TableSortLabel, { active: sortKey === "created", direction: sortKey === "created" ? sortDirection : "asc", onClick: () => handleSort("created") }, "Created")), /* @__PURE__ */ React3.createElement(TableCell, { sortDirection: sortKey === "expires" ? sortDirection : false }, /* @__PURE__ */ React3.createElement(TableSortLabel, { active: sortKey === "expires", direction: sortKey === "expires" ? sortDirection : "asc", onClick: () => handleSort("expires") }, "Expires")), /* @__PURE__ */ React3.createElement(TableCell, { sortDirection: sortKey === "budget" ? sortDirection : false }, /* @__PURE__ */ React3.createElement(TableSortLabel, { active: sortKey === "budget", direction: sortKey === "budget" ? sortDirection : "asc", onClick: () => handleSort("budget") }, "Budget")), /* @__PURE__ */ React3.createElement(TableCell, { sortDirection: sortKey === "limits" ? sortDirection : false }, /* @__PURE__ */ React3.createElement(TableSortLabel, { active: sortKey === "limits", direction: sortKey === "limits" ? sortDirection : "asc", onClick: () => handleSort("limits") }, "TPM / RPM")), /* @__PURE__ */ React3.createElement(TableCell, { sortDirection: sortKey === "models" ? sortDirection : false }, /* @__PURE__ */ React3.createElement(TableSortLabel, { active: sortKey === "models", direction: sortKey === "models" ? sortDirection : "asc", onClick: () => handleSort("models") }, "Models")), /* @__PURE__ */ React3.createElement(TableCell, { align: "right" }, "Actions"))), /* @__PURE__ */ React3.createElement(TableBody, null, renderTableBody())))
1419
- ), /* @__PURE__ */ React3.createElement(Dialog, { open: !!editingKey, onClose: handleCloseEdit, maxWidth: "sm", fullWidth: true }, /* @__PURE__ */ React3.createElement(DialogTitle, null, "Edit Key"), /* @__PURE__ */ React3.createElement(DialogContent, null, editingKey && /* @__PURE__ */ React3.createElement(Box3, { display: "flex", flexDirection: "column", gap: 2, mt: 1 }, /* @__PURE__ */ React3.createElement(Typography3, { variant: "body2", color: "text.secondary" }, /* @__PURE__ */ React3.createElement("code", { style: { fontFamily: "monospace", color: "inherit" } }, maskKey(editingKey.key))), /* @__PURE__ */ React3.createElement(
1420
- TextField,
1421
- {
1422
- label: "Alias",
1423
- value: editForm.key_alias || "",
1424
- onChange: (e) => setEditForm({ ...editForm, key_alias: e.target.value }),
1425
- fullWidth: true
1426
- }
1427
- ), models.length > 0 && /* @__PURE__ */ React3.createElement(
1428
- Autocomplete,
1429
- {
1430
- multiple: true,
1431
- options: models,
1432
- groupBy: (m) => m.mode || "other",
1433
- getOptionLabel: (m) => m.model_name,
1434
- value: editSelectedModels,
1435
- onChange: (_e, selected) => setEditForm({ ...editForm, models: selected.map((m) => m.model_name) }),
1436
- renderOption: (props, m) => /* @__PURE__ */ React3.createElement("li", { ...props }, modelOption(m)),
1437
- renderInput: (params) => /* @__PURE__ */ React3.createElement(TextField, { ...params, label: "Models", fullWidth: true })
1438
- }
1439
- ), /* @__PURE__ */ React3.createElement(
1440
- TextField,
1441
- {
1442
- label: "Max Budget (USD)",
1443
- type: "number",
1444
- value: editForm.max_budget ?? "",
1445
- onChange: (e) => setEditForm({ ...editForm, max_budget: e.target.value ? Number(e.target.value) : void 0 }),
1446
- fullWidth: true
1447
- }
1448
- ), /* @__PURE__ */ React3.createElement(
1449
- TextField,
1450
- {
1451
- label: "TPM Limit",
1452
- type: "number",
1453
- value: editForm.tpm_limit ?? "",
1454
- onChange: (e) => setEditForm({ ...editForm, tpm_limit: e.target.value ? Number(e.target.value) : void 0 }),
1455
- helperText: "Max tokens per minute this key can consume across all models. Leave blank for no limit.",
1456
- fullWidth: true
1457
- }
1458
- ), /* @__PURE__ */ React3.createElement(
1459
- TextField,
1460
- {
1461
- label: "RPM Limit",
1462
- type: "number",
1463
- value: editForm.rpm_limit ?? "",
1464
- onChange: (e) => setEditForm({ ...editForm, rpm_limit: e.target.value ? Number(e.target.value) : void 0 }),
1465
- helperText: "Max requests per minute this key can make across all models. Leave blank for no limit.",
1466
- fullWidth: true
1467
- }
1468
- ), /* @__PURE__ */ React3.createElement(Box3, { mt: 1, pt: 2, borderTop: "1px solid", sx: { borderColor: "divider" } }, /* @__PURE__ */ React3.createElement(Typography3, { variant: "caption", color: "text.secondary", display: "block", mb: 1 }, "Danger Zone"), !resetSpendConfirm ? /* @__PURE__ */ React3.createElement(
1469
- Button2,
1470
- {
1471
- size: "small",
1472
- color: "warning",
1473
- variant: "outlined",
1474
- onClick: () => setResetSpendConfirm(true)
1475
- },
1476
- "Reset Spend to $0"
1477
- ) : /* @__PURE__ */ React3.createElement(Box3, { display: "flex", alignItems: "center", gap: 1, flexWrap: "wrap" }, /* @__PURE__ */ React3.createElement(Typography3, { variant: "body2", color: "warning.main" }, "Zero out spend counter?"), /* @__PURE__ */ React3.createElement(
1478
- Button2,
1479
- {
1480
- size: "small",
1481
- color: "warning",
1482
- variant: "contained",
1483
- disabled: resetSpendSubmitting,
1484
- onClick: handleResetSpend
1485
- },
1486
- resetSpendSubmitting ? /* @__PURE__ */ React3.createElement(CircularProgress, { size: 16 }) : "Confirm"
1487
- ), /* @__PURE__ */ React3.createElement(Button2, { size: "small", onClick: () => setResetSpendConfirm(false) }, "Cancel"))))), /* @__PURE__ */ React3.createElement(DialogActions, null, /* @__PURE__ */ React3.createElement(Button2, { onClick: handleCloseEdit }, "Cancel"), /* @__PURE__ */ React3.createElement(Button2, { onClick: handleUpdate, variant: "contained", color: "primary", disabled: editSubmitting }, editSubmitting ? /* @__PURE__ */ React3.createElement(CircularProgress, { size: 24 }) : "Save"))), /* @__PURE__ */ React3.createElement(Dialog, { open: !!deleteConfirmId, onClose: () => setDeleteConfirmId(null), maxWidth: "xs", fullWidth: true }, /* @__PURE__ */ React3.createElement(DialogTitle, null, "Revoke Key?"), /* @__PURE__ */ React3.createElement(DialogContent, null, /* @__PURE__ */ React3.createElement(Typography3, null, "This will permanently revoke the key. Any integrations using it will stop working immediately.")), /* @__PURE__ */ React3.createElement(DialogActions, null, /* @__PURE__ */ React3.createElement(Button2, { onClick: () => setDeleteConfirmId(null), disabled: deleteSubmitting }, "Cancel"), /* @__PURE__ */ React3.createElement(
1344
+ ), /* @__PURE__ */ React3.createElement(Dialog, { open: !!deleteConfirmId, onClose: () => setDeleteConfirmId(null), maxWidth: "xs", fullWidth: true }, /* @__PURE__ */ React3.createElement(DialogTitle, null, "Revoke Key?"), /* @__PURE__ */ React3.createElement(DialogContent, null, /* @__PURE__ */ React3.createElement(Typography3, null, "This will permanently revoke the key. Any integrations using it will stop working immediately.")), /* @__PURE__ */ React3.createElement(DialogActions, null, /* @__PURE__ */ React3.createElement(Button2, { onClick: () => setDeleteConfirmId(null), disabled: deleteSubmitting }, "Cancel"), /* @__PURE__ */ React3.createElement(
1488
1345
  Button2,
1489
1346
  {
1490
1347
  onClick: handleConfirmDelete,
@@ -1521,7 +1378,7 @@ var init_format = __esm({
1521
1378
  }
1522
1379
  });
1523
1380
 
1524
- // src/components/GenerateKeyDialog.tsx
1381
+ // src/components/KeyFormDialog.tsx
1525
1382
  import React4, { useState as useState2, useEffect, useMemo as useMemo3 } from "react";
1526
1383
  import Box4 from "@mui/material/Box";
1527
1384
  import Typography4 from "@mui/material/Typography";
@@ -1534,7 +1391,7 @@ import MenuItem from "@mui/material/MenuItem";
1534
1391
  import Button3 from "@mui/material/Button";
1535
1392
  import IconButton2 from "@mui/material/IconButton";
1536
1393
  import CircularProgress2 from "@mui/material/CircularProgress";
1537
- import Autocomplete2 from "@mui/material/Autocomplete";
1394
+ import Autocomplete from "@mui/material/Autocomplete";
1538
1395
  import Checkbox from "@mui/material/Checkbox";
1539
1396
  import FormControlLabel from "@mui/material/FormControlLabel";
1540
1397
  import Alert from "@mui/material/Alert";
@@ -1636,17 +1493,17 @@ function teamHelperText(teamError, teamRequired) {
1636
1493
  if (teamRequired) return "Bind this key to a team for scoped access";
1637
1494
  return "Optional: bind this key to a specific team for scoped access";
1638
1495
  }
1639
- function budgetHelperText(budgetInvalid, budgetEstimate) {
1640
- if (budgetInvalid) return 'Enter a positive budget or tick "Unlimited"';
1496
+ function budgetHelperText(budgetInvalid, budgetEstimate, unlimited) {
1497
+ if (budgetInvalid && !unlimited) return 'Enter a positive budget or tick "Unlimited"';
1641
1498
  if (budgetEstimate !== null) return `\u2248 ${fmtInt(budgetEstimate)} tokens at the selected model's rate`;
1642
1499
  return void 0;
1643
1500
  }
1644
- function fmtCost2(perToken) {
1501
+ function fmtCost(perToken) {
1645
1502
  if (!perToken) return null;
1646
1503
  const per1k = perToken * 1e3;
1647
1504
  return per1k < 0.01 ? `$${(perToken * 1e6).toFixed(2)}/M` : `$${per1k.toFixed(3)}/1K`;
1648
1505
  }
1649
- function formatContextWindow2(maxInput, maxOutput) {
1506
+ function formatContextWindow(maxInput, maxOutput) {
1650
1507
  if (!maxInput && !maxOutput) return null;
1651
1508
  const fmt = (n) => {
1652
1509
  if (!n) return null;
@@ -1661,9 +1518,9 @@ function formatContextWindow2(maxInput, maxOutput) {
1661
1518
  if (outPart) return `ctx ${outPart} out`;
1662
1519
  return null;
1663
1520
  }
1664
- var generateDefaultAlias, emptyForm, ALL_PROXY_MODELS, SNIPPET_FILE_HINTS, SnippetTabs, GenerateKeyDialog;
1665
- var init_GenerateKeyDialog = __esm({
1666
- "src/components/GenerateKeyDialog.tsx"() {
1521
+ var generateDefaultAlias, createForm, editForm, ALL_PROXY_MODELS, SNIPPET_FILE_HINTS, SnippetTabs, KeyFormDialog;
1522
+ var init_KeyFormDialog = __esm({
1523
+ "src/components/KeyFormDialog.tsx"() {
1667
1524
  "use strict";
1668
1525
  init_format();
1669
1526
  generateDefaultAlias = (username) => {
@@ -1671,8 +1528,8 @@ var init_GenerateKeyDialog = __esm({
1671
1528
  const hash = Math.random().toString(36).slice(2, 8);
1672
1529
  return `${base}-${hash}`;
1673
1530
  };
1674
- emptyForm = () => ({
1675
- alias: "",
1531
+ createForm = (username) => ({
1532
+ alias: generateDefaultAlias(username),
1676
1533
  models: [],
1677
1534
  duration: "30d",
1678
1535
  max_budget: 100,
@@ -1681,6 +1538,13 @@ var init_GenerateKeyDialog = __esm({
1681
1538
  team_id: void 0,
1682
1539
  key_type: "llm_api"
1683
1540
  });
1541
+ editForm = (k) => ({
1542
+ key_alias: k.key_alias ?? "",
1543
+ models: k.models ?? [],
1544
+ max_budget: k.max_budget,
1545
+ tpm_limit: k.tpm_limit,
1546
+ rpm_limit: k.rpm_limit
1547
+ });
1684
1548
  ALL_PROXY_MODELS = "all-proxy-models";
1685
1549
  SNIPPET_FILE_HINTS = {
1686
1550
  opencode: "Add to ~/.config/opencode/opencode.json",
@@ -1727,46 +1591,79 @@ var init_GenerateKeyDialog = __esm({
1727
1591
  model && /* @__PURE__ */ React4.createElement(Typography4, { variant: "caption", color: "text.secondary", display: "block", mt: 1 }, "Using model \u201C", model, "\u201D \u2014 swap it for any model you have access to.")
1728
1592
  ));
1729
1593
  };
1730
- GenerateKeyDialog = ({
1594
+ KeyFormDialog = ({
1731
1595
  open,
1732
1596
  onClose,
1597
+ mode,
1598
+ keyToEdit = null,
1733
1599
  keys,
1734
1600
  models,
1735
1601
  teams,
1736
1602
  username,
1737
1603
  keyGenerationSettings,
1738
- onGenerateKey,
1604
+ onCreateKey,
1605
+ onUpdateKey,
1606
+ onResetKeySpend,
1739
1607
  onGetConfig
1740
1608
  }) => {
1741
- const [formData, setFormData] = useState2(emptyForm());
1609
+ const isCreate = mode === "create";
1610
+ const [generateForm, setGenerateForm] = useState2(createForm(username));
1611
+ const [editFormState, setEditFormState] = useState2({});
1742
1612
  const [unlimitedBudget, setUnlimitedBudget] = useState2(false);
1743
1613
  const [submitting, setSubmitting] = useState2(false);
1744
1614
  const [newKeyValue, setNewKeyValue] = useState2(null);
1745
1615
  const [newKeySnippets, setNewKeySnippets] = useState2(null);
1746
1616
  const [newKeyModel, setNewKeyModel] = useState2("");
1747
1617
  const [generateError, setGenerateError] = useState2(null);
1618
+ const [editError, setEditError] = useState2(null);
1619
+ const [resetSpendConfirm, setResetSpendConfirm] = useState2(false);
1620
+ const [resetSpendSubmitting, setResetSpendSubmitting] = useState2(false);
1748
1621
  useEffect(() => {
1749
1622
  if (open) {
1750
- setFormData({ ...emptyForm(), alias: generateDefaultAlias(username) });
1751
- setUnlimitedBudget(false);
1623
+ if (isCreate) {
1624
+ setGenerateForm(createForm(username));
1625
+ } else if (keyToEdit) {
1626
+ setEditFormState(editForm(keyToEdit));
1627
+ setUnlimitedBudget(
1628
+ keyGenerationSettings?.allowUnlimitedBudget === true && (keyToEdit.max_budget === null || keyToEdit.max_budget === void 0)
1629
+ );
1630
+ } else {
1631
+ setEditFormState({});
1632
+ setUnlimitedBudget(false);
1633
+ }
1752
1634
  setNewKeyValue(null);
1753
1635
  setNewKeySnippets(null);
1754
1636
  setNewKeyModel("");
1755
1637
  setGenerateError(null);
1638
+ setEditError(null);
1639
+ setResetSpendConfirm(false);
1756
1640
  }
1757
- }, [open, username]);
1641
+ }, [open, isCreate, keyToEdit, username, keyGenerationSettings?.allowUnlimitedBudget]);
1758
1642
  const allowUnlimitedBudget = keyGenerationSettings?.allowUnlimitedBudget ?? false;
1759
1643
  const teamRequired = keyGenerationSettings?.teamRequired ?? true;
1760
- const selectedTeam = teams.find((t) => t.team_id === formData.team_id) ?? null;
1644
+ const formData = isCreate ? generateForm : editFormState;
1645
+ const setFormData = (patch) => {
1646
+ if (isCreate) {
1647
+ setGenerateForm((prev) => ({ ...prev, ...patch }));
1648
+ } else {
1649
+ setEditFormState((prev) => ({ ...prev, ...patch }));
1650
+ }
1651
+ };
1652
+ const currentAlias = (isCreate ? generateForm.alias : editFormState.key_alias) ?? "";
1653
+ const setAliasField = (value) => {
1654
+ if (isCreate) setFormData({ alias: value });
1655
+ else setFormData({ key_alias: value });
1656
+ };
1657
+ const selectedTeam = teams.find((t) => t.team_id === (isCreate ? generateForm.team_id : void 0)) ?? null;
1761
1658
  const availableModels = useMemo3(() => {
1762
1659
  return models.filter((m) => isModelAllowedByTeam(m, selectedTeam?.models));
1763
1660
  }, [models, selectedTeam]);
1764
1661
  const selectedModels = availableModels.filter((m) => (formData.models || []).includes(m.model_name));
1765
- const aliasError = !(formData.alias || "").trim();
1766
- const teamError = teamRequired && !formData.team_id;
1662
+ const aliasError = !currentAlias.trim();
1663
+ const teamError = isCreate && teamRequired && !generateForm.team_id;
1767
1664
  const budgetInvalid = !unlimitedBudget && (formData.max_budget === void 0 || formData.max_budget === null || formData.max_budget <= 0);
1768
- const canGenerate = !aliasError && !teamError && !budgetInvalid && !submitting;
1769
- const aliasDuplicate = !!formData.alias && keys.some((k) => k.key_alias === formData.alias);
1665
+ const canSubmit = !aliasError && !teamError && !budgetInvalid && !submitting;
1666
+ const aliasDuplicate = !!currentAlias && keys.some((k) => k.key_alias === currentAlias && k !== keyToEdit);
1770
1667
  const budgetEstimate = useMemo3(() => {
1771
1668
  if (unlimitedBudget || budgetInvalid) return null;
1772
1669
  const inputCosts = selectedModels.map((m) => m.input_cost_per_token).filter((c) => typeof c === "number" && c > 0);
@@ -1779,11 +1676,11 @@ var init_GenerateKeyDialog = __esm({
1779
1676
  setGenerateError(null);
1780
1677
  try {
1781
1678
  const request = {
1782
- ...formData,
1783
- max_budget: unlimitedBudget ? null : formData.max_budget
1679
+ ...generateForm,
1680
+ max_budget: unlimitedBudget ? null : generateForm.max_budget
1784
1681
  };
1785
- const response = await onGenerateKey(request);
1786
- const model = formData.models?.[0] ?? availableModels[0]?.model_name ?? "";
1682
+ const response = await onCreateKey(request);
1683
+ const model = generateForm.models?.[0] ?? availableModels[0]?.model_name ?? "";
1787
1684
  setNewKeyValue(response.key);
1788
1685
  setNewKeyModel(model);
1789
1686
  setNewKeySnippets(null);
@@ -1792,7 +1689,7 @@ var init_GenerateKeyDialog = __esm({
1792
1689
  setNewKeySnippets(buildSnippets(config.baseUrl, response.key, model));
1793
1690
  } catch {
1794
1691
  }
1795
- setFormData(emptyForm());
1692
+ setGenerateForm(createForm(username));
1796
1693
  setUnlimitedBudget(false);
1797
1694
  } catch (error) {
1798
1695
  setGenerateError(error?.message ?? "Failed to generate key");
@@ -1800,27 +1697,58 @@ var init_GenerateKeyDialog = __esm({
1800
1697
  setSubmitting(false);
1801
1698
  }
1802
1699
  };
1700
+ const handleUpdate = async () => {
1701
+ if (!keyToEdit) return;
1702
+ setSubmitting(true);
1703
+ setEditError(null);
1704
+ try {
1705
+ const request = {
1706
+ ...editFormState,
1707
+ max_budget: unlimitedBudget ? null : editFormState.max_budget
1708
+ };
1709
+ await onUpdateKey(keyToEdit.token ?? keyToEdit.key, request);
1710
+ onClose();
1711
+ } catch (error) {
1712
+ setEditError(error?.message ?? "Failed to update key");
1713
+ } finally {
1714
+ setSubmitting(false);
1715
+ }
1716
+ };
1717
+ const handleResetSpend = async () => {
1718
+ if (!keyToEdit || !onResetKeySpend) return;
1719
+ setResetSpendSubmitting(true);
1720
+ try {
1721
+ await onResetKeySpend(keyToEdit.token ?? keyToEdit.key);
1722
+ setResetSpendConfirm(false);
1723
+ onClose();
1724
+ } finally {
1725
+ setResetSpendSubmitting(false);
1726
+ }
1727
+ };
1803
1728
  const handleClose = () => {
1804
1729
  onClose();
1805
1730
  setGenerateError(null);
1731
+ setEditError(null);
1806
1732
  setNewKeyValue(null);
1807
1733
  setNewKeySnippets(null);
1808
- setFormData(emptyForm());
1734
+ setGenerateForm(createForm(username));
1735
+ setEditFormState({});
1809
1736
  setUnlimitedBudget(false);
1737
+ setResetSpendConfirm(false);
1810
1738
  };
1811
1739
  const copyToClipboard = (text) => {
1812
1740
  navigator.clipboard.writeText(text);
1813
1741
  };
1814
1742
  const modelOption = (m) => {
1815
- const inCost = fmtCost2(m.input_cost_per_token);
1816
- const outCost = fmtCost2(m.output_cost_per_token);
1817
- const ctx = formatContextWindow2(m.max_input_tokens, m.max_output_tokens);
1743
+ const inCost = fmtCost(m.input_cost_per_token);
1744
+ const outCost = fmtCost(m.output_cost_per_token);
1745
+ const ctx = formatContextWindow(m.max_input_tokens, m.max_output_tokens);
1818
1746
  return /* @__PURE__ */ React4.createElement(Box4, null, /* @__PURE__ */ React4.createElement("span", null, m.model_name), m.supports_function_calling && " \u{1F527}", m.supports_vision && " \u{1F441}\uFE0F", ctx && /* @__PURE__ */ React4.createElement(Typography4, { variant: "caption", color: "text.secondary", sx: { ml: 1 } }, ctx), (inCost || outCost) && /* @__PURE__ */ React4.createElement(Typography4, { variant: "caption", color: "text.secondary", sx: { ml: 1 } }, inCost, " in \xB7 ", outCost, " out"));
1819
1747
  };
1820
1748
  const renderTeamField = () => {
1821
1749
  if (teams.length > 0) {
1822
1750
  return /* @__PURE__ */ React4.createElement(
1823
- Autocomplete2,
1751
+ Autocomplete,
1824
1752
  {
1825
1753
  options: teams,
1826
1754
  getOptionLabel: (t) => t.team_alias || t.team_id,
@@ -1830,28 +1758,61 @@ var init_GenerateKeyDialog = __esm({
1830
1758
  const model = models.find((m) => m.model_name === name);
1831
1759
  return model ? isModelAllowedByTeam(model, team?.models) : false;
1832
1760
  });
1833
- setFormData({ ...formData, team_id: team?.team_id, models: restrictedModels });
1761
+ setFormData({ team_id: team?.team_id, models: restrictedModels });
1834
1762
  },
1763
+ disabled: !isCreate,
1835
1764
  renderInput: (params) => /* @__PURE__ */ React4.createElement(
1836
1765
  TextField2,
1837
1766
  {
1838
1767
  ...params,
1839
1768
  label: "Team",
1840
1769
  error: teamError,
1841
- helperText: teamHelperText(teamError, teamRequired),
1842
- required: teamRequired,
1770
+ helperText: isCreate ? teamHelperText(teamError, teamRequired) : "Team binding is fixed after creation \u2014 delete and recreate the key to change it",
1771
+ required: isCreate && teamRequired,
1843
1772
  fullWidth: true
1844
1773
  }
1845
1774
  )
1846
1775
  }
1847
1776
  );
1848
1777
  }
1849
- if (teamRequired) {
1778
+ if (teamRequired && isCreate) {
1850
1779
  return /* @__PURE__ */ React4.createElement(Typography4, { variant: "body2", color: "error" }, "Team selection is required, but you don't belong to any team yet \u2014 contact your administrator.");
1851
1780
  }
1852
1781
  return null;
1853
1782
  };
1854
- return /* @__PURE__ */ React4.createElement(Dialog2, { open, onClose: handleClose, maxWidth: "sm", fullWidth: true }, /* @__PURE__ */ React4.createElement(DialogTitle2, null, newKeyValue ? "Key Generated" : "Generate New Key"), /* @__PURE__ */ React4.createElement(DialogContent2, null, newKeyValue ? /* @__PURE__ */ React4.createElement(Box4, null, /* @__PURE__ */ React4.createElement(Typography4, { variant: "body2", color: "text.secondary", gutterBottom: true }, "Copy this key now. You won't be able to see it again."), /* @__PURE__ */ React4.createElement(
1783
+ const editKeyId = keyToEdit?.token ?? keyToEdit?.key ?? "";
1784
+ const renderDangerZone = () => {
1785
+ if (isCreate || !onResetKeySpend) return null;
1786
+ return /* @__PURE__ */ React4.createElement(Box4, { mt: 1, pt: 2, borderTop: "1px solid", sx: { borderColor: "divider" } }, /* @__PURE__ */ React4.createElement(Typography4, { variant: "caption", color: "text.secondary", display: "block", mb: 1 }, "Danger Zone"), !resetSpendConfirm ? /* @__PURE__ */ React4.createElement(
1787
+ Button3,
1788
+ {
1789
+ size: "small",
1790
+ color: "warning",
1791
+ variant: "outlined",
1792
+ onClick: () => setResetSpendConfirm(true)
1793
+ },
1794
+ "Reset Spend to $0"
1795
+ ) : /* @__PURE__ */ React4.createElement(Box4, { display: "flex", alignItems: "center", gap: 1, flexWrap: "wrap" }, /* @__PURE__ */ React4.createElement(Typography4, { variant: "body2", color: "warning.main" }, "Zero out spend counter?"), /* @__PURE__ */ React4.createElement(
1796
+ Button3,
1797
+ {
1798
+ size: "small",
1799
+ color: "warning",
1800
+ variant: "contained",
1801
+ disabled: resetSpendSubmitting,
1802
+ onClick: handleResetSpend
1803
+ },
1804
+ resetSpendSubmitting ? /* @__PURE__ */ React4.createElement(CircularProgress2, { size: 16 }) : "Confirm"
1805
+ ), /* @__PURE__ */ React4.createElement(Button3, { size: "small", onClick: () => setResetSpendConfirm(false) }, "Cancel")));
1806
+ };
1807
+ const maskKey = (key) => {
1808
+ if (key.length <= 8) return "***";
1809
+ return `${key.slice(0, 4)}...${key.slice(-4)}`;
1810
+ };
1811
+ let dialogTitle = "Edit Key";
1812
+ if (newKeyValue) dialogTitle = "Key Generated";
1813
+ else if (isCreate) dialogTitle = "Generate New Key";
1814
+ const submitLabel = isCreate ? "Generate" : "Save";
1815
+ return /* @__PURE__ */ React4.createElement(Dialog2, { open, onClose: handleClose, maxWidth: "sm", fullWidth: true }, /* @__PURE__ */ React4.createElement(DialogTitle2, null, dialogTitle), /* @__PURE__ */ React4.createElement(DialogContent2, null, newKeyValue ? /* @__PURE__ */ React4.createElement(Box4, null, /* @__PURE__ */ React4.createElement(Typography4, { variant: "body2", color: "text.secondary", gutterBottom: true }, "Copy this key now. You won't be able to see it again."), /* @__PURE__ */ React4.createElement(
1855
1816
  Box4,
1856
1817
  {
1857
1818
  display: "flex",
@@ -1900,14 +1861,15 @@ var init_GenerateKeyDialog = __esm({
1900
1861
  newKeySnippets.publicEndpoint
1901
1862
  ),
1902
1863
  /* @__PURE__ */ React4.createElement(IconButton2, { size: "small", onClick: () => copyToClipboard(newKeySnippets.publicEndpoint) }, /* @__PURE__ */ React4.createElement(ContentCopy2, { fontSize: "small" }))
1903
- )), newKeySnippets && /* @__PURE__ */ React4.createElement(Box4, { mt: 3 }, /* @__PURE__ */ React4.createElement(Box4, { display: "flex", alignItems: "center", gap: 1, mb: 1 }, /* @__PURE__ */ React4.createElement(Code, { fontSize: "small", color: "action" }), /* @__PURE__ */ React4.createElement(Typography4, { variant: "subtitle2" }, "Start calling the proxy \u2014 paste and run")), /* @__PURE__ */ React4.createElement(SnippetTabs, { snippets: newKeySnippets, model: newKeyModel, onCopy: copyToClipboard }))) : /* @__PURE__ */ React4.createElement(Box4, { display: "flex", flexDirection: "column", gap: 2, mt: 1 }, generateError && /* @__PURE__ */ React4.createElement(Alert, { severity: "error", onClose: () => setGenerateError(null) }, generateError), /* @__PURE__ */ React4.createElement(
1864
+ )), newKeySnippets && /* @__PURE__ */ React4.createElement(Box4, { mt: 3 }, /* @__PURE__ */ React4.createElement(Box4, { display: "flex", alignItems: "center", gap: 1, mb: 1 }, /* @__PURE__ */ React4.createElement(Code, { fontSize: "small", color: "action" }), /* @__PURE__ */ React4.createElement(Typography4, { variant: "subtitle2" }, "Start calling the proxy \u2014 paste and run")), /* @__PURE__ */ React4.createElement(SnippetTabs, { snippets: newKeySnippets, model: newKeyModel, onCopy: copyToClipboard }))) : /* @__PURE__ */ React4.createElement(Box4, { display: "flex", flexDirection: "column", gap: 2, mt: 1 }, !isCreate && /* @__PURE__ */ React4.createElement(Typography4, { variant: "body2", color: "text.secondary" }, /* @__PURE__ */ React4.createElement("code", { style: { fontFamily: "monospace", color: "inherit" } }, maskKey(editKeyId))), generateError && /* @__PURE__ */ React4.createElement(Alert, { severity: "error", onClose: () => setGenerateError(null) }, generateError), editError && /* @__PURE__ */ React4.createElement(Alert, { severity: "error", onClose: () => setEditError(null) }, editError), /* @__PURE__ */ React4.createElement(
1904
1865
  TextField2,
1905
1866
  {
1906
1867
  label: "Alias",
1907
- value: formData.alias || "",
1868
+ value: currentAlias,
1908
1869
  onChange: (e) => {
1909
- setFormData({ ...formData, alias: e.target.value });
1870
+ setAliasField(e.target.value);
1910
1871
  setGenerateError(null);
1872
+ setEditError(null);
1911
1873
  },
1912
1874
  error: aliasError,
1913
1875
  color: aliasDuplicate ? "warning" : void 0,
@@ -1915,13 +1877,13 @@ var init_GenerateKeyDialog = __esm({
1915
1877
  required: true,
1916
1878
  fullWidth: true
1917
1879
  }
1918
- ), /* @__PURE__ */ React4.createElement(
1880
+ ), !isCreate ? null : /* @__PURE__ */ React4.createElement(
1919
1881
  TextField2,
1920
1882
  {
1921
1883
  select: true,
1922
1884
  label: "Duration",
1923
- value: formData.duration || "30d",
1924
- onChange: (e) => setFormData({ ...formData, duration: e.target.value }),
1885
+ value: generateForm.duration || "30d",
1886
+ onChange: (e) => setFormData({ duration: e.target.value }),
1925
1887
  fullWidth: true
1926
1888
  },
1927
1889
  /* @__PURE__ */ React4.createElement(MenuItem, { value: "1d" }, "1 Day"),
@@ -1930,14 +1892,14 @@ var init_GenerateKeyDialog = __esm({
1930
1892
  /* @__PURE__ */ React4.createElement(MenuItem, { value: "90d" }, "90 Days"),
1931
1893
  /* @__PURE__ */ React4.createElement(MenuItem, { value: "1y" }, "1 Year")
1932
1894
  ), renderTeamField(), availableModels.length > 0 && /* @__PURE__ */ React4.createElement(
1933
- Autocomplete2,
1895
+ Autocomplete,
1934
1896
  {
1935
1897
  multiple: true,
1936
1898
  options: availableModels,
1937
1899
  groupBy: (m) => m.mode || "other",
1938
1900
  getOptionLabel: (m) => m.model_name,
1939
1901
  value: selectedModels,
1940
- onChange: (_e, selected) => setFormData({ ...formData, models: selected.map((m) => m.model_name) }),
1902
+ onChange: (_e, selected) => setFormData({ models: selected.map((m) => m.model_name) }),
1941
1903
  renderOption: (props, m) => /* @__PURE__ */ React4.createElement("li", { ...props }, modelOption(m)),
1942
1904
  renderInput: (params) => /* @__PURE__ */ React4.createElement(
1943
1905
  TextField2,
@@ -1959,9 +1921,9 @@ var init_GenerateKeyDialog = __esm({
1959
1921
  onChange: (e) => {
1960
1922
  setUnlimitedBudget(e.target.checked);
1961
1923
  if (e.target.checked) {
1962
- setFormData({ ...formData, max_budget: null });
1924
+ setFormData({ max_budget: null });
1963
1925
  } else {
1964
- setFormData({ ...formData, max_budget: 100 });
1926
+ setFormData({ max_budget: editFormState.max_budget ?? generateForm.max_budget ?? 100 });
1965
1927
  }
1966
1928
  }
1967
1929
  }
@@ -1974,9 +1936,9 @@ var init_GenerateKeyDialog = __esm({
1974
1936
  label: "Max Budget (USD)",
1975
1937
  type: "number",
1976
1938
  value: unlimitedBudget ? "" : formData.max_budget ?? "",
1977
- onChange: (e) => setFormData({ ...formData, max_budget: e.target.value ? Number(e.target.value) : void 0 }),
1939
+ onChange: (e) => setFormData({ max_budget: e.target.value ? Number(e.target.value) : void 0 }),
1978
1940
  error: budgetInvalid,
1979
- helperText: budgetHelperText(budgetInvalid, budgetEstimate),
1941
+ helperText: budgetHelperText(budgetInvalid, budgetEstimate, unlimitedBudget),
1980
1942
  disabled: unlimitedBudget,
1981
1943
  required: true,
1982
1944
  fullWidth: true
@@ -1987,7 +1949,7 @@ var init_GenerateKeyDialog = __esm({
1987
1949
  label: "TPM Limit",
1988
1950
  type: "number",
1989
1951
  value: formData.tpm_limit ?? "",
1990
- onChange: (e) => setFormData({ ...formData, tpm_limit: e.target.value ? Number(e.target.value) : void 0 }),
1952
+ onChange: (e) => setFormData({ tpm_limit: e.target.value ? Number(e.target.value) : void 0 }),
1991
1953
  helperText: "Max tokens per minute this key can consume across all models. Leave blank for no limit.",
1992
1954
  fullWidth: true
1993
1955
  }
@@ -1997,19 +1959,19 @@ var init_GenerateKeyDialog = __esm({
1997
1959
  label: "RPM Limit",
1998
1960
  type: "number",
1999
1961
  value: formData.rpm_limit ?? "",
2000
- onChange: (e) => setFormData({ ...formData, rpm_limit: e.target.value ? Number(e.target.value) : void 0 }),
1962
+ onChange: (e) => setFormData({ rpm_limit: e.target.value ? Number(e.target.value) : void 0 }),
2001
1963
  helperText: "Max requests per minute this key can make across all models. Leave blank for no limit.",
2002
1964
  fullWidth: true
2003
1965
  }
2004
- ))))), /* @__PURE__ */ React4.createElement(DialogActions2, null, newKeyValue ? /* @__PURE__ */ React4.createElement(Button3, { onClick: handleClose, variant: "contained", color: "success" }, "Done") : /* @__PURE__ */ React4.createElement(React4.Fragment, null, /* @__PURE__ */ React4.createElement(Button3, { onClick: handleClose }, "Cancel"), /* @__PURE__ */ React4.createElement(
1966
+ ))), renderDangerZone())), /* @__PURE__ */ React4.createElement(DialogActions2, null, newKeyValue ? /* @__PURE__ */ React4.createElement(Button3, { onClick: handleClose, variant: "contained", color: "success" }, "Done") : /* @__PURE__ */ React4.createElement(React4.Fragment, null, /* @__PURE__ */ React4.createElement(Button3, { onClick: handleClose }, "Cancel"), /* @__PURE__ */ React4.createElement(
2005
1967
  Button3,
2006
1968
  {
2007
- onClick: handleGenerate,
1969
+ onClick: isCreate ? handleGenerate : handleUpdate,
2008
1970
  variant: "contained",
2009
1971
  color: "primary",
2010
- disabled: !canGenerate
1972
+ disabled: !canSubmit
2011
1973
  },
2012
- submitting ? /* @__PURE__ */ React4.createElement(CircularProgress2, { size: 24 }) : "Generate"
1974
+ submitting ? /* @__PURE__ */ React4.createElement(CircularProgress2, { size: 24 }) : submitLabel
2013
1975
  ))));
2014
1976
  };
2015
1977
  }
@@ -2026,7 +1988,7 @@ import Button4 from "@mui/material/Button";
2026
1988
  import Alert2 from "@mui/material/Alert";
2027
1989
  import Checkbox2 from "@mui/material/Checkbox";
2028
1990
  import FormControlLabel2 from "@mui/material/FormControlLabel";
2029
- import Autocomplete3 from "@mui/material/Autocomplete";
1991
+ import Autocomplete2 from "@mui/material/Autocomplete";
2030
1992
  import CircularProgress3 from "@mui/material/CircularProgress";
2031
1993
  import MenuItem2 from "@mui/material/MenuItem";
2032
1994
  import Box5 from "@mui/material/Box";
@@ -2295,7 +2257,7 @@ var init_ManageTeamDialog = __esm({
2295
2257
  fullWidth: true
2296
2258
  }
2297
2259
  ), /* @__PURE__ */ React5.createElement(
2298
- Autocomplete3,
2260
+ Autocomplete2,
2299
2261
  {
2300
2262
  multiple: true,
2301
2263
  options: modelNames,
@@ -2368,7 +2330,7 @@ var init_ManageTeamDialog = __esm({
2368
2330
  },
2369
2331
  /* @__PURE__ */ React5.createElement(DeleteIcon, { fontSize: "small" })
2370
2332
  ))))))), /* @__PURE__ */ React5.createElement(Box5, { sx: { display: "flex", gap: 1, alignItems: "flex-start", flexWrap: "wrap" } }, /* @__PURE__ */ React5.createElement(
2371
- Autocomplete3,
2333
+ Autocomplete2,
2372
2334
  {
2373
2335
  freeSolo: true,
2374
2336
  autoHighlight: true,
@@ -2427,7 +2389,7 @@ var init_ManageTeamDialog = __esm({
2427
2389
  },
2428
2390
  "Add"
2429
2391
  ))), showKnowledgeBases && /* @__PURE__ */ React5.createElement(Box5, null, /* @__PURE__ */ React5.createElement(Divider2, { sx: { my: 1 } }), /* @__PURE__ */ React5.createElement(Typography5, { variant: "subtitle2", sx: { mb: 1 } }, "Knowledge Bases"), /* @__PURE__ */ React5.createElement(Typography5, { variant: "body2", color: "text.secondary", sx: { mb: 1 } }, "Attaching a knowledge base exposes its documents to every key in this team."), kbError && /* @__PURE__ */ React5.createElement(Alert2, { severity: "error", sx: { mb: 1 }, onClose: () => setKbError(null) }, kbError), /* @__PURE__ */ React5.createElement(
2430
- Autocomplete3,
2392
+ Autocomplete2,
2431
2393
  {
2432
2394
  multiple: true,
2433
2395
  options: kbOptions,
@@ -2446,7 +2408,7 @@ var init_ManageTeamDialog = __esm({
2446
2408
  },
2447
2409
  kbBusy ? "Saving\u2026" : "Save knowledge bases"
2448
2410
  ))), showMcpServers && /* @__PURE__ */ React5.createElement(Box5, null, /* @__PURE__ */ React5.createElement(Divider2, { sx: { my: 1 } }), /* @__PURE__ */ React5.createElement(Typography5, { variant: "subtitle2", sx: { mb: 1 } }, "MCP Servers"), /* @__PURE__ */ React5.createElement(Typography5, { variant: "body2", color: "text.secondary", sx: { mb: 1 } }, "Attaching an MCP server lets every key in this team invoke that server's tools through the model \u2014 including any side effects those tools have."), mcpError && /* @__PURE__ */ React5.createElement(Alert2, { severity: "error", sx: { mb: 1 }, onClose: () => setMcpError(null) }, mcpError), /* @__PURE__ */ React5.createElement(
2449
- Autocomplete3,
2411
+ Autocomplete2,
2450
2412
  {
2451
2413
  multiple: true,
2452
2414
  options: mcpOptions,
@@ -2924,6 +2886,13 @@ function buildBudgetSummary(user, teams, keys, maxKeys = 3) {
2924
2886
  teams: teamLimits
2925
2887
  };
2926
2888
  }
2889
+ function allBudgetLimits(summary) {
2890
+ return [
2891
+ ...summary.keys,
2892
+ ...summary.user ? [summary.user] : [],
2893
+ ...summary.teams
2894
+ ];
2895
+ }
2927
2896
  function buildBudgetGauges(summary) {
2928
2897
  const byLevel = {
2929
2898
  key: summary.keys,
@@ -2974,18 +2943,12 @@ var init_budget = __esm({
2974
2943
  }
2975
2944
  });
2976
2945
 
2977
- // src/components/LiteLLMBudgetWidget.tsx
2978
- import React7, { useEffect as useEffect3, useMemo as useMemo6, useState as useState5 } from "react";
2979
- import Paper5 from "@mui/material/Paper";
2946
+ // src/components/BudgetLimitList.tsx
2947
+ import React7 from "react";
2980
2948
  import Box7 from "@mui/material/Box";
2949
+ import Paper5 from "@mui/material/Paper";
2981
2950
  import Typography7 from "@mui/material/Typography";
2982
- import CircularProgress4 from "@mui/material/CircularProgress";
2983
- import Alert3 from "@mui/material/Alert";
2984
- import Collapse from "@mui/material/Collapse";
2985
- import IconButton4 from "@mui/material/IconButton";
2986
- import { ExpandMore as ExpandMore2 } from "@mui/icons-material";
2987
- import { alpha as alpha4 } from "@mui/material/styles";
2988
- import { useApi as useApi2 } from "@backstage/core-plugin-api";
2951
+ import { alpha as alpha4, useTheme as useTheme2 } from "@mui/material/styles";
2989
2952
  import { Link } from "@backstage/core-components";
2990
2953
  function levelToneColor(tone, theme) {
2991
2954
  switch (tone) {
@@ -3001,13 +2964,119 @@ function levelToneColor(tone, theme) {
3001
2964
  return theme.palette.primary.main;
3002
2965
  }
3003
2966
  }
3004
- var LevelFrame, EmptyLimitCard, LimitCard, CLOSEST_LEVEL_LABEL, KEYS_PATH, MoreKeysNote, HierarchyNote, TaggedLimit, FullBody, CompactBody, LiteLLMBudgetWidget;
2967
+ var LimitCard, TaggedLimit, MoreKeysNote, BudgetLimitList, LimitListPanel;
2968
+ var init_BudgetLimitList = __esm({
2969
+ "src/components/BudgetLimitList.tsx"() {
2970
+ "use strict";
2971
+ init_budget();
2972
+ init_format();
2973
+ init_ui();
2974
+ LimitCard = ({ limit }) => {
2975
+ const tone = budgetTone2(limit.pct);
2976
+ const pct = Math.round(limit.pct);
2977
+ const closeTo = limit.softLimit !== void 0 && limit.spend >= limit.softLimit;
2978
+ return /* @__PURE__ */ React7.createElement(Paper5, { variant: "outlined", sx: { px: 1.5, py: 1.25, borderRadius: 1.5 } }, /* @__PURE__ */ React7.createElement(Box7, { sx: { display: "flex", alignItems: "baseline", justifyContent: "space-between", gap: 1.5 } }, /* @__PURE__ */ React7.createElement(Box7, { sx: { minWidth: 0, flex: 1 } }, /* @__PURE__ */ React7.createElement(
2979
+ Typography7,
2980
+ {
2981
+ variant: "body2",
2982
+ sx: { fontWeight: 600, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" },
2983
+ title: limit.sublabel ? `${limit.label} \xB7 ${limit.sublabel}` : limit.label
2984
+ },
2985
+ limit.label
2986
+ ), limit.sublabel && /* @__PURE__ */ React7.createElement(
2987
+ Typography7,
2988
+ {
2989
+ variant: "caption",
2990
+ color: "text.secondary",
2991
+ sx: {
2992
+ fontSize: 10.5,
2993
+ fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
2994
+ display: "block",
2995
+ overflow: "hidden",
2996
+ textOverflow: "ellipsis",
2997
+ whiteSpace: "nowrap"
2998
+ }
2999
+ },
3000
+ limit.sublabel
3001
+ )), limit.hidden ? /* @__PURE__ */ React7.createElement(Typography7, { variant: "body2", sx: { fontWeight: 600, fontVariantNumeric: "tabular-nums", whiteSpace: "nowrap" } }, pct, "% used") : /* @__PURE__ */ React7.createElement(Typography7, { variant: "body2", sx: { fontWeight: 600, fontVariantNumeric: "tabular-nums", whiteSpace: "nowrap" } }, fmtUsd(limit.spend), /* @__PURE__ */ React7.createElement(Box7, { component: "span", color: "text.secondary", sx: { fontWeight: 400 } }, " / ", fmtUsd(limit.budget)))), /* @__PURE__ */ React7.createElement(Box7, { sx: { mt: 1 } }, /* @__PURE__ */ React7.createElement(Meter, { value: limit.pct, tone, height: 6 })), /* @__PURE__ */ React7.createElement(
3002
+ Box7,
3003
+ {
3004
+ sx: {
3005
+ display: "flex",
3006
+ justifyContent: "space-between",
3007
+ alignItems: "center",
3008
+ gap: 1,
3009
+ mt: 0.75
3010
+ }
3011
+ },
3012
+ /* @__PURE__ */ React7.createElement(Typography7, { variant: "caption", color: "text.secondary", sx: { fontVariantNumeric: "tabular-nums" } }, limit.hidden ? /* @__PURE__ */ React7.createElement(React7.Fragment, null, "Hidden by admin") : /* @__PURE__ */ React7.createElement(React7.Fragment, null, pct, "% of budget", closeTo && ` \xB7 soft-limit warning`)),
3013
+ /* @__PURE__ */ React7.createElement(Typography7, { variant: "caption", color: "text.secondary" }, fmtBudgetDuration(limit.budgetDuration) ? `resets ${fmtBudgetDuration(limit.budgetDuration)}` : "never resets")
3014
+ ));
3015
+ };
3016
+ TaggedLimit = ({ limit }) => {
3017
+ const theme = useTheme2();
3018
+ const tone = budgetTone2(limit.pct);
3019
+ return /* @__PURE__ */ React7.createElement(Box7, null, /* @__PURE__ */ React7.createElement(
3020
+ Typography7,
3021
+ {
3022
+ sx: {
3023
+ fontSize: 11,
3024
+ fontWeight: 700,
3025
+ letterSpacing: "0.08em",
3026
+ textTransform: "uppercase",
3027
+ color: tone === "accent" ? theme.palette.text.secondary : levelToneColor(tone, theme),
3028
+ mb: 0.5
3029
+ }
3030
+ },
3031
+ limit.kind
3032
+ ), /* @__PURE__ */ React7.createElement(LimitCard, { limit }));
3033
+ };
3034
+ MoreKeysNote = ({
3035
+ count,
3036
+ href,
3037
+ label
3038
+ }) => {
3039
+ const text = label ?? `+${count} more budgeted key${count > 1 ? "s" : ""} further from the cap`;
3040
+ if (!href) {
3041
+ return /* @__PURE__ */ React7.createElement(Typography7, { variant: "caption", color: "text.secondary" }, text);
3042
+ }
3043
+ return /* @__PURE__ */ React7.createElement(Link, { to: href, variant: "caption" }, text, " \u2192");
3044
+ };
3045
+ BudgetLimitList = ({ limits, hiddenKeyCount = 0, keysHref }) => /* @__PURE__ */ React7.createElement(Box7, { sx: { display: "flex", flexDirection: "column", gap: 1.25 } }, limits.map((limit) => /* @__PURE__ */ React7.createElement(TaggedLimit, { key: `${limit.kind}:${limit.sublabel ?? limit.label}`, limit })), hiddenKeyCount > 0 && /* @__PURE__ */ React7.createElement(MoreKeysNote, { count: hiddenKeyCount, href: keysHref }));
3046
+ LimitListPanel = ({ children }) => /* @__PURE__ */ React7.createElement(
3047
+ Box7,
3048
+ {
3049
+ sx: (theme) => ({
3050
+ px: 1.5,
3051
+ py: 1.5,
3052
+ borderRadius: 1.5,
3053
+ bgcolor: alpha4(theme.palette.text.primary, theme.palette.mode === "dark" ? 0.03 : 0.02)
3054
+ })
3055
+ },
3056
+ children
3057
+ );
3058
+ }
3059
+ });
3060
+
3061
+ // src/components/LiteLLMBudgetWidget.tsx
3062
+ import React8, { useEffect as useEffect3, useMemo as useMemo6, useState as useState5 } from "react";
3063
+ import Paper6 from "@mui/material/Paper";
3064
+ import Box8 from "@mui/material/Box";
3065
+ import Typography8 from "@mui/material/Typography";
3066
+ import CircularProgress4 from "@mui/material/CircularProgress";
3067
+ import Alert3 from "@mui/material/Alert";
3068
+ import Collapse from "@mui/material/Collapse";
3069
+ import IconButton4 from "@mui/material/IconButton";
3070
+ import { ExpandMore as ExpandMore2 } from "@mui/icons-material";
3071
+ import { alpha as alpha5 } from "@mui/material/styles";
3072
+ import { useApi as useApi2 } from "@backstage/core-plugin-api";
3073
+ var LevelFrame, EmptyLimitCard, CLOSEST_LEVEL_LABEL, KEYS_PATH, HierarchyNote, FullBody, CompactBody, LiteLLMBudgetWidget;
3005
3074
  var init_LiteLLMBudgetWidget = __esm({
3006
3075
  "src/components/LiteLLMBudgetWidget.tsx"() {
3007
3076
  "use strict";
3008
3077
  init_api();
3009
- init_format();
3010
3078
  init_ui();
3079
+ init_BudgetLimitList();
3011
3080
  init_budget();
3012
3081
  LevelFrame = ({
3013
3082
  rank,
@@ -3017,8 +3086,8 @@ var init_LiteLLMBudgetWidget = __esm({
3017
3086
  tone,
3018
3087
  isLast,
3019
3088
  children
3020
- }) => /* @__PURE__ */ React7.createElement(Box7, { sx: { display: "flex", gap: 1.5 } }, /* @__PURE__ */ React7.createElement(Box7, { sx: { display: "flex", flexDirection: "column", alignItems: "center", width: 26, flexShrink: 0 } }, /* @__PURE__ */ React7.createElement(
3021
- Box7,
3089
+ }) => /* @__PURE__ */ React8.createElement(Box8, { sx: { display: "flex", gap: 1.5 } }, /* @__PURE__ */ React8.createElement(Box8, { sx: { display: "flex", flexDirection: "column", alignItems: "center", width: 26, flexShrink: 0 } }, /* @__PURE__ */ React8.createElement(
3090
+ Box8,
3022
3091
  {
3023
3092
  sx: (theme) => ({
3024
3093
  width: 26,
@@ -3027,7 +3096,7 @@ var init_LiteLLMBudgetWidget = __esm({
3027
3096
  display: "flex",
3028
3097
  alignItems: "center",
3029
3098
  justifyContent: "center",
3030
- bgcolor: alpha4(levelToneColor(tone, theme), 0.14),
3099
+ bgcolor: alpha5(levelToneColor(tone, theme), 0.14),
3031
3100
  color: levelToneColor(tone, theme),
3032
3101
  fontSize: 12,
3033
3102
  fontWeight: 700,
@@ -3035,18 +3104,18 @@ var init_LiteLLMBudgetWidget = __esm({
3035
3104
  })
3036
3105
  },
3037
3106
  rank
3038
- ), /* @__PURE__ */ React7.createElement(
3039
- Box7,
3107
+ ), /* @__PURE__ */ React8.createElement(
3108
+ Box8,
3040
3109
  {
3041
3110
  sx: (theme) => ({
3042
3111
  flex: 1,
3043
3112
  width: 2,
3044
3113
  minHeight: 14,
3045
- bgcolor: isLast ? "transparent" : alpha4(theme.palette.text.primary, 0.12)
3114
+ bgcolor: isLast ? "transparent" : alpha5(theme.palette.text.primary, 0.12)
3046
3115
  })
3047
3116
  }
3048
- )), /* @__PURE__ */ React7.createElement(Box7, { sx: { flex: 1, minWidth: 0, pb: isLast ? 0 : 2 } }, /* @__PURE__ */ React7.createElement(Box7, { sx: { display: "flex", alignItems: "baseline", gap: 1, flexWrap: "wrap", mb: 0.75 } }, /* @__PURE__ */ React7.createElement(
3049
- Typography7,
3117
+ )), /* @__PURE__ */ React8.createElement(Box8, { sx: { flex: 1, minWidth: 0, pb: isLast ? 0 : 2 } }, /* @__PURE__ */ React8.createElement(Box8, { sx: { display: "flex", alignItems: "baseline", gap: 1, flexWrap: "wrap", mb: 0.75 } }, /* @__PURE__ */ React8.createElement(
3118
+ Typography8,
3050
3119
  {
3051
3120
  sx: (theme) => ({
3052
3121
  fontSize: 12.5,
@@ -3057,102 +3126,42 @@ var init_LiteLLMBudgetWidget = __esm({
3057
3126
  })
3058
3127
  },
3059
3128
  name
3060
- ), /* @__PURE__ */ React7.createElement(Typography7, { variant: "caption", color: "text.secondary", sx: { fontSize: 11.5 } }, tagline), note && /* @__PURE__ */ React7.createElement(StatusPill, { label: note, tone: "neutral", dot: false, sx: { ml: "auto" } })), /* @__PURE__ */ React7.createElement(Box7, { sx: { display: "flex", flexDirection: "column", gap: 1 } }, children)));
3061
- EmptyLimitCard = ({ children }) => /* @__PURE__ */ React7.createElement(
3062
- Paper5,
3129
+ ), /* @__PURE__ */ React8.createElement(Typography8, { variant: "caption", color: "text.secondary", sx: { fontSize: 11.5 } }, tagline), note && /* @__PURE__ */ React8.createElement(StatusPill, { label: note, tone: "neutral", dot: false, sx: { ml: "auto" } })), /* @__PURE__ */ React8.createElement(Box8, { sx: { display: "flex", flexDirection: "column", gap: 1 } }, children)));
3130
+ EmptyLimitCard = ({ children }) => /* @__PURE__ */ React8.createElement(
3131
+ Paper6,
3063
3132
  {
3064
3133
  variant: "outlined",
3065
3134
  sx: (theme) => ({
3066
3135
  px: 1.5,
3067
3136
  py: 1.25,
3068
3137
  borderRadius: 1.5,
3069
- bgcolor: alpha4(theme.palette.text.primary, theme.palette.mode === "dark" ? 0.03 : 0.015)
3138
+ bgcolor: alpha5(theme.palette.text.primary, theme.palette.mode === "dark" ? 0.03 : 0.015)
3070
3139
  })
3071
3140
  },
3072
- /* @__PURE__ */ React7.createElement(Typography7, { variant: "body2", color: "text.secondary", sx: { fontSize: 13 } }, children)
3141
+ /* @__PURE__ */ React8.createElement(Typography8, { variant: "body2", color: "text.secondary", sx: { fontSize: 13 } }, children)
3073
3142
  );
3074
- LimitCard = ({ limit }) => {
3075
- const tone = budgetTone2(limit.pct);
3076
- const pct = Math.round(limit.pct);
3077
- const closeTo = limit.softLimit !== void 0 && limit.spend >= limit.softLimit;
3078
- return /* @__PURE__ */ React7.createElement(Paper5, { variant: "outlined", sx: { px: 1.5, py: 1.25, borderRadius: 1.5 } }, /* @__PURE__ */ React7.createElement(Box7, { sx: { display: "flex", alignItems: "baseline", justifyContent: "space-between", gap: 1.5 } }, /* @__PURE__ */ React7.createElement(Box7, { sx: { minWidth: 0, flex: 1 } }, /* @__PURE__ */ React7.createElement(
3079
- Typography7,
3080
- {
3081
- variant: "body2",
3082
- sx: { fontWeight: 600, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" },
3083
- title: limit.sublabel ? `${limit.label} \xB7 ${limit.sublabel}` : limit.label
3084
- },
3085
- limit.label
3086
- ), limit.sublabel && /* @__PURE__ */ React7.createElement(
3087
- Typography7,
3088
- {
3089
- variant: "caption",
3090
- color: "text.secondary",
3091
- sx: {
3092
- fontSize: 10.5,
3093
- fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
3094
- display: "block",
3095
- overflow: "hidden",
3096
- textOverflow: "ellipsis",
3097
- whiteSpace: "nowrap"
3098
- }
3099
- },
3100
- limit.sublabel
3101
- )), limit.hidden ? /* @__PURE__ */ React7.createElement(Typography7, { variant: "body2", sx: { fontWeight: 600, fontVariantNumeric: "tabular-nums", whiteSpace: "nowrap" } }, pct, "% used") : /* @__PURE__ */ React7.createElement(Typography7, { variant: "body2", sx: { fontWeight: 600, fontVariantNumeric: "tabular-nums", whiteSpace: "nowrap" } }, fmtUsd(limit.spend), /* @__PURE__ */ React7.createElement(Box7, { component: "span", color: "text.secondary", sx: { fontWeight: 400 } }, " / ", fmtUsd(limit.budget)))), /* @__PURE__ */ React7.createElement(Box7, { sx: { mt: 1 } }, /* @__PURE__ */ React7.createElement(Meter, { value: limit.pct, tone, height: 6 })), /* @__PURE__ */ React7.createElement(
3102
- Box7,
3103
- {
3104
- sx: {
3105
- display: "flex",
3106
- justifyContent: "space-between",
3107
- alignItems: "center",
3108
- gap: 1,
3109
- mt: 0.75
3110
- }
3111
- },
3112
- /* @__PURE__ */ React7.createElement(Typography7, { variant: "caption", color: "text.secondary", sx: { fontVariantNumeric: "tabular-nums" } }, limit.hidden ? /* @__PURE__ */ React7.createElement(React7.Fragment, null, "Hidden by admin") : /* @__PURE__ */ React7.createElement(React7.Fragment, null, pct, "% of budget", closeTo && ` \xB7 soft-limit warning`)),
3113
- /* @__PURE__ */ React7.createElement(Typography7, { variant: "caption", color: "text.secondary" }, fmtBudgetDuration(limit.budgetDuration) ? `resets ${fmtBudgetDuration(limit.budgetDuration)}` : "never resets")
3114
- ));
3115
- };
3116
3143
  CLOSEST_LEVEL_LABEL = {
3117
3144
  key: "key",
3118
3145
  user: "personal",
3119
3146
  team: "team"
3120
3147
  };
3121
3148
  KEYS_PATH = "/litellm?tab=keys";
3122
- MoreKeysNote = ({ count }) => /* @__PURE__ */ React7.createElement(Link, { to: KEYS_PATH, variant: "caption" }, "+", count, " more budgeted key", count > 1 ? "s" : "", " further from the cap \u2192");
3123
- HierarchyNote = ({ withReset }) => /* @__PURE__ */ React7.createElement(
3124
- Box7,
3149
+ HierarchyNote = ({ withReset }) => /* @__PURE__ */ React8.createElement(
3150
+ Box8,
3125
3151
  {
3126
3152
  sx: (theme) => ({
3127
3153
  px: 1.5,
3128
3154
  py: 1.25,
3129
3155
  borderRadius: 1.5,
3130
3156
  border: "1px dashed",
3131
- borderColor: alpha4(theme.palette.text.primary, 0.18)
3157
+ borderColor: alpha5(theme.palette.text.primary, 0.18)
3132
3158
  })
3133
3159
  },
3134
- /* @__PURE__ */ React7.createElement(Typography7, { variant: "caption", color: "text.secondary", display: "block" }, /* @__PURE__ */ React7.createElement("b", null, "Order:"), " key \u2192 personal \u2192 team \u2192 global. The first cap you reach blocks the request; a key that belongs to a team uses the team's cap, not your personal one.", withReset && " A cap with a reset window drops to $0 when the window closes; without one it never resets.")
3160
+ /* @__PURE__ */ React8.createElement(Typography8, { variant: "caption", color: "text.secondary", display: "block" }, /* @__PURE__ */ React8.createElement("b", null, "Order:"), " key \u2192 personal \u2192 team \u2192 global. The first cap you reach blocks the request; a key that belongs to a team uses the team's cap, not your personal one.", withReset && " A cap with a reset window drops to $0 when the window closes; without one it never resets.")
3135
3161
  );
3136
- TaggedLimit = ({ limit }) => {
3137
- const tone = budgetTone2(limit.pct);
3138
- return /* @__PURE__ */ React7.createElement(Box7, null, /* @__PURE__ */ React7.createElement(
3139
- Typography7,
3140
- {
3141
- sx: (theme) => ({
3142
- fontSize: 11,
3143
- fontWeight: 700,
3144
- letterSpacing: "0.08em",
3145
- textTransform: "uppercase",
3146
- color: tone === "accent" ? theme.palette.text.secondary : levelToneColor(tone, theme),
3147
- mb: 0.5
3148
- })
3149
- },
3150
- limit.kind
3151
- ), /* @__PURE__ */ React7.createElement(LimitCard, { limit }));
3152
- };
3153
3162
  FullBody = ({ summary }) => {
3154
3163
  const hasBudgetedKeys = summary.keys.length + summary.hiddenBudgetedKeys > 0;
3155
- return /* @__PURE__ */ React7.createElement(React7.Fragment, null, /* @__PURE__ */ React7.createElement(
3164
+ return /* @__PURE__ */ React8.createElement(React8.Fragment, null, /* @__PURE__ */ React8.createElement(
3156
3165
  LevelFrame,
3157
3166
  {
3158
3167
  rank: 1,
@@ -3160,10 +3169,10 @@ var init_LiteLLMBudgetWidget = __esm({
3160
3169
  tagline: "per-key cap \xB7 highest priority",
3161
3170
  note: hasBudgetedKeys ? void 0 : "no key budgets set"
3162
3171
  },
3163
- !hasBudgetedKeys && /* @__PURE__ */ React7.createElement(EmptyLimitCard, null, "No key has a budget, so no key-level cap applies."),
3164
- summary.keys.map((k) => /* @__PURE__ */ React7.createElement(LimitCard, { key: k.sublabel ?? k.label, limit: k })),
3165
- summary.hiddenBudgetedKeys > 0 && /* @__PURE__ */ React7.createElement(MoreKeysNote, { count: summary.hiddenBudgetedKeys })
3166
- ), /* @__PURE__ */ React7.createElement(
3172
+ !hasBudgetedKeys && /* @__PURE__ */ React8.createElement(EmptyLimitCard, null, "No key has a budget, so no key-level cap applies."),
3173
+ summary.keys.map((k) => /* @__PURE__ */ React8.createElement(LimitCard, { key: k.sublabel ?? k.label, limit: k })),
3174
+ summary.hiddenBudgetedKeys > 0 && /* @__PURE__ */ React8.createElement(MoreKeysNote, { count: summary.hiddenBudgetedKeys, href: KEYS_PATH })
3175
+ ), /* @__PURE__ */ React8.createElement(
3167
3176
  LevelFrame,
3168
3177
  {
3169
3178
  rank: 2,
@@ -3171,15 +3180,15 @@ var init_LiteLLMBudgetWidget = __esm({
3171
3180
  tagline: "cap on everything you spend with your own keys",
3172
3181
  note: "skipped for team-bound keys"
3173
3182
  },
3174
- summary.user ? /* @__PURE__ */ React7.createElement(LimitCard, { limit: summary.user }) : /* @__PURE__ */ React7.createElement(EmptyLimitCard, null, "No personal budget is set on your account.")
3175
- ), /* @__PURE__ */ React7.createElement(LevelFrame, { rank: 3, name: "Team", tagline: "shared cap for all keys in a team (if any)" }, summary.teams.length > 0 ? summary.teams.map((t) => /* @__PURE__ */ React7.createElement(LimitCard, { key: t.sublabel ?? t.label, limit: t })) : /* @__PURE__ */ React7.createElement(EmptyLimitCard, null, "No team you belong to has a budget.")), /* @__PURE__ */ React7.createElement(LevelFrame, { rank: 4, name: "Global", tagline: "proxy-wide cap set by your admin", isLast: true }, /* @__PURE__ */ React7.createElement(EmptyLimitCard, null, "Configured by your admin in the proxy config \u2014 it isn't visible from this page. When set, it limits spend across every request on the proxy.")), /* @__PURE__ */ React7.createElement(Box7, { sx: { mt: 2 } }, /* @__PURE__ */ React7.createElement(HierarchyNote, { withReset: true })));
3183
+ summary.user ? /* @__PURE__ */ React8.createElement(LimitCard, { limit: summary.user }) : /* @__PURE__ */ React8.createElement(EmptyLimitCard, null, "No personal budget is set on your account.")
3184
+ ), /* @__PURE__ */ React8.createElement(LevelFrame, { rank: 3, name: "Team", tagline: "shared cap for all keys in a team (if any)" }, summary.teams.length > 0 ? summary.teams.map((t) => /* @__PURE__ */ React8.createElement(LimitCard, { key: t.sublabel ?? t.label, limit: t })) : /* @__PURE__ */ React8.createElement(EmptyLimitCard, null, "No team you belong to has a budget.")), /* @__PURE__ */ React8.createElement(LevelFrame, { rank: 4, name: "Global", tagline: "proxy-wide cap set by your admin", isLast: true }, /* @__PURE__ */ React8.createElement(EmptyLimitCard, null, "Configured by your admin in the proxy config \u2014 it isn't visible from this page. When set, it limits spend across every request on the proxy.")), /* @__PURE__ */ React8.createElement(Box8, { sx: { mt: 2 } }, /* @__PURE__ */ React8.createElement(HierarchyNote, { withReset: true })));
3176
3185
  };
3177
3186
  CompactBody = ({ summary }) => {
3178
3187
  const { count } = budgetHeadline(summary);
3179
3188
  if (count === 0) {
3180
- return /* @__PURE__ */ React7.createElement(Typography7, { variant: "body2", color: "text.secondary", sx: { fontSize: 13 } }, "No budget limits apply to you right now.");
3189
+ return /* @__PURE__ */ React8.createElement(Typography8, { variant: "body2", color: "text.secondary", sx: { fontSize: 13 } }, "No budget limits apply to you right now.");
3181
3190
  }
3182
- return /* @__PURE__ */ React7.createElement(Box7, { sx: { display: "flex", flexDirection: "column", gap: 1.25 } }, /* @__PURE__ */ React7.createElement(HierarchyNote, null), summary.keys.map((k) => /* @__PURE__ */ React7.createElement(TaggedLimit, { key: k.sublabel ?? k.label, limit: k })), summary.hiddenBudgetedKeys > 0 && /* @__PURE__ */ React7.createElement(MoreKeysNote, { count: summary.hiddenBudgetedKeys }), summary.user && /* @__PURE__ */ React7.createElement(TaggedLimit, { limit: summary.user }), summary.teams.map((t) => /* @__PURE__ */ React7.createElement(TaggedLimit, { key: t.sublabel ?? t.label, limit: t })));
3191
+ return /* @__PURE__ */ React8.createElement(Box8, { sx: { display: "flex", flexDirection: "column", gap: 1.25 } }, /* @__PURE__ */ React8.createElement(HierarchyNote, null), summary.keys.map((k) => /* @__PURE__ */ React8.createElement(TaggedLimit, { key: k.sublabel ?? k.label, limit: k })), summary.hiddenBudgetedKeys > 0 && /* @__PURE__ */ React8.createElement(MoreKeysNote, { count: summary.hiddenBudgetedKeys, href: KEYS_PATH }), summary.user && /* @__PURE__ */ React8.createElement(TaggedLimit, { limit: summary.user }), summary.teams.map((t) => /* @__PURE__ */ React8.createElement(TaggedLimit, { key: t.sublabel ?? t.label, limit: t })));
3183
3192
  };
3184
3193
  LiteLLMBudgetWidget = ({
3185
3194
  title = "Budget Policy",
@@ -3230,9 +3239,9 @@ var init_LiteLLMBudgetWidget = __esm({
3230
3239
  headline.closest.pct
3231
3240
  )}%`;
3232
3241
  const summaryText = headline.count === 0 ? "no limits apply" : `${limitsWord}${closestWord}`;
3233
- const body = /* @__PURE__ */ React7.createElement(React7.Fragment, null, loading && /* @__PURE__ */ React7.createElement(Box7, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: 140 }, /* @__PURE__ */ React7.createElement(CircularProgress4, { size: 32 })), !loading && error && /* @__PURE__ */ React7.createElement(Alert3, { severity: "error", sx: { mt: 0.5 } }, error), !loading && !error && (compact ? /* @__PURE__ */ React7.createElement(CompactBody, { summary }) : /* @__PURE__ */ React7.createElement(FullBody, { summary })));
3234
- return /* @__PURE__ */ React7.createElement(Paper5, { sx: { p: 2 } }, collapsible ? /* @__PURE__ */ React7.createElement(
3235
- Box7,
3242
+ const body = /* @__PURE__ */ React8.createElement(React8.Fragment, null, loading && /* @__PURE__ */ React8.createElement(Box8, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: 140 }, /* @__PURE__ */ React8.createElement(CircularProgress4, { size: 32 })), !loading && error && /* @__PURE__ */ React8.createElement(Alert3, { severity: "error", sx: { mt: 0.5 } }, error), !loading && !error && (compact ? /* @__PURE__ */ React8.createElement(CompactBody, { summary }) : /* @__PURE__ */ React8.createElement(FullBody, { summary })));
3243
+ return /* @__PURE__ */ React8.createElement(Paper6, { sx: { p: 2 } }, collapsible ? /* @__PURE__ */ React8.createElement(
3244
+ Box8,
3236
3245
  {
3237
3246
  onClick: () => setExpanded((e) => !e),
3238
3247
  sx: {
@@ -3244,14 +3253,14 @@ var init_LiteLLMBudgetWidget = __esm({
3244
3253
  mb: expanded ? 1.5 : 0
3245
3254
  }
3246
3255
  },
3247
- /* @__PURE__ */ React7.createElement(Box7, { sx: { minWidth: 0, flex: 1 } }, /* @__PURE__ */ React7.createElement(Typography7, { variant: "h6", lineHeight: 1.2 }, title), !loading && !error && /* @__PURE__ */ React7.createElement(Box7, { sx: { mt: 0.5 } }, /* @__PURE__ */ React7.createElement(
3256
+ /* @__PURE__ */ React8.createElement(Box8, { sx: { minWidth: 0, flex: 1 } }, /* @__PURE__ */ React8.createElement(Typography8, { variant: "h6", lineHeight: 1.2 }, title), !loading && !error && /* @__PURE__ */ React8.createElement(Box8, { sx: { mt: 0.5 } }, /* @__PURE__ */ React8.createElement(
3248
3257
  StatusPill,
3249
3258
  {
3250
3259
  label: summaryText,
3251
3260
  tone: budgetTone2(headline.closest?.pct ?? 0)
3252
3261
  }
3253
3262
  ))),
3254
- /* @__PURE__ */ React7.createElement(
3263
+ /* @__PURE__ */ React8.createElement(
3255
3264
  IconButton4,
3256
3265
  {
3257
3266
  size: "small",
@@ -3267,10 +3276,10 @@ var init_LiteLLMBudgetWidget = __esm({
3267
3276
  transition: theme.transitions.create("transform")
3268
3277
  })
3269
3278
  },
3270
- /* @__PURE__ */ React7.createElement(ExpandMore2, null)
3279
+ /* @__PURE__ */ React8.createElement(ExpandMore2, null)
3271
3280
  )
3272
- ) : /* @__PURE__ */ React7.createElement(Box7, { sx: { mb: 1.5 } }, /* @__PURE__ */ React7.createElement(Typography7, { variant: "h6", lineHeight: 1.2 }, title), /* @__PURE__ */ React7.createElement(Typography7, { variant: "caption", color: "text.secondary" }, "How LiteLLM caps spend \u2014 and where you stand right now")), collapsible ? /* @__PURE__ */ React7.createElement(Collapse, { in: expanded, unmountOnExit: true }, body) : body, action && /* @__PURE__ */ React7.createElement(
3273
- Box7,
3281
+ ) : /* @__PURE__ */ React8.createElement(Box8, { sx: { mb: 1.5 } }, /* @__PURE__ */ React8.createElement(Typography8, { variant: "h6", lineHeight: 1.2 }, title), /* @__PURE__ */ React8.createElement(Typography8, { variant: "caption", color: "text.secondary" }, "How LiteLLM caps spend \u2014 and where you stand right now")), collapsible ? /* @__PURE__ */ React8.createElement(Collapse, { in: expanded, unmountOnExit: true }, body) : body, action && /* @__PURE__ */ React8.createElement(
3282
+ Box8,
3274
3283
  {
3275
3284
  sx: (theme) => ({
3276
3285
  mt: 2,
@@ -3285,10 +3294,10 @@ var init_LiteLLMBudgetWidget = __esm({
3285
3294
  });
3286
3295
 
3287
3296
  // src/components/TeamUsage.tsx
3288
- import React8, { useState as useState6 } from "react";
3289
- import Paper6 from "@mui/material/Paper";
3290
- import Box8 from "@mui/material/Box";
3291
- import Typography8 from "@mui/material/Typography";
3297
+ import React9, { useState as useState6 } from "react";
3298
+ import Paper7 from "@mui/material/Paper";
3299
+ import Box9 from "@mui/material/Box";
3300
+ import Typography9 from "@mui/material/Typography";
3292
3301
  import Skeleton4 from "@mui/material/Skeleton";
3293
3302
  import Divider3 from "@mui/material/Divider";
3294
3303
  import Table4 from "@mui/material/Table";
@@ -3301,7 +3310,7 @@ import Collapse2 from "@mui/material/Collapse";
3301
3310
  import IconButton5 from "@mui/material/IconButton";
3302
3311
  import Button5 from "@mui/material/Button";
3303
3312
  import CircularProgress5 from "@mui/material/CircularProgress";
3304
- import { alpha as alpha5 } from "@mui/material/styles";
3313
+ import { alpha as alpha6 } from "@mui/material/styles";
3305
3314
  import { ExpandMore as ExpandMore3, Group, Add as Add3 } from "@mui/icons-material";
3306
3315
  import {
3307
3316
  AreaChart as AreaChart2,
@@ -3345,24 +3354,24 @@ var init_TeamUsage = __esm({
3345
3354
  const renderDailySpendSection = () => {
3346
3355
  if (hidden) return null;
3347
3356
  if (usageLoading) {
3348
- return /* @__PURE__ */ React8.createElement(Box8, { display: "flex", justifyContent: "center", py: 3 }, /* @__PURE__ */ React8.createElement(CircularProgress5, { size: 22 }));
3357
+ return /* @__PURE__ */ React9.createElement(Box9, { display: "flex", justifyContent: "center", py: 3 }, /* @__PURE__ */ React9.createElement(CircularProgress5, { size: 22 }));
3349
3358
  }
3350
3359
  if (dailyData.length === 0) return null;
3351
- return /* @__PURE__ */ React8.createElement(Box8, { mb: 2.5 }, /* @__PURE__ */ React8.createElement(
3352
- Typography8,
3360
+ return /* @__PURE__ */ React9.createElement(Box9, { mb: 2.5 }, /* @__PURE__ */ React9.createElement(
3361
+ Typography9,
3353
3362
  {
3354
3363
  variant: "caption",
3355
3364
  color: "text.secondary",
3356
3365
  sx: { display: "block", fontSize: 11, fontWeight: 700, letterSpacing: "0.07em", textTransform: "uppercase", mb: 1 }
3357
3366
  },
3358
3367
  "Daily spend"
3359
- ), /* @__PURE__ */ React8.createElement(Box8, { height: 150 }, /* @__PURE__ */ React8.createElement(ResponsiveContainer2, { width: "100%", height: "100%" }, /* @__PURE__ */ React8.createElement(AreaChart2, { data: dailyData, margin: { top: 4, right: 8, left: 0, bottom: 0 } }, /* @__PURE__ */ React8.createElement(CartesianGrid2, { ...chart.grid }), /* @__PURE__ */ React8.createElement(XAxis2, { dataKey: "date", tickFormatter: fmtDateShort, ...chart.axis }), /* @__PURE__ */ React8.createElement(YAxis2, { tickFormatter: fmtUsdCompact, width: 52, ...chart.axis }), /* @__PURE__ */ React8.createElement(
3368
+ ), /* @__PURE__ */ React9.createElement(Box9, { height: 150 }, /* @__PURE__ */ React9.createElement(ResponsiveContainer2, { width: "100%", height: "100%" }, /* @__PURE__ */ React9.createElement(AreaChart2, { data: dailyData, margin: { top: 4, right: 8, left: 0, bottom: 0 } }, /* @__PURE__ */ React9.createElement(CartesianGrid2, { ...chart.grid }), /* @__PURE__ */ React9.createElement(XAxis2, { dataKey: "date", tickFormatter: fmtDateShort, ...chart.axis }), /* @__PURE__ */ React9.createElement(YAxis2, { tickFormatter: fmtUsdCompact, width: 52, ...chart.axis }), /* @__PURE__ */ React9.createElement(
3360
3369
  Tooltip2,
3361
3370
  {
3362
3371
  cursor: chart.cursor,
3363
- content: /* @__PURE__ */ React8.createElement(ChartTooltip, { valueFormatter: (v) => `$${v.toFixed(4)}` })
3372
+ content: /* @__PURE__ */ React9.createElement(ChartTooltip, { valueFormatter: (v) => `$${v.toFixed(4)}` })
3364
3373
  }
3365
- ), /* @__PURE__ */ React8.createElement(
3374
+ ), /* @__PURE__ */ React9.createElement(
3366
3375
  Area2,
3367
3376
  {
3368
3377
  type: "monotone",
@@ -3375,8 +3384,8 @@ var init_TeamUsage = __esm({
3375
3384
  }
3376
3385
  )))));
3377
3386
  };
3378
- const sectionLabel = (label) => /* @__PURE__ */ React8.createElement(
3379
- Typography8,
3387
+ const sectionLabel = (label) => /* @__PURE__ */ React9.createElement(
3388
+ Typography9,
3380
3389
  {
3381
3390
  variant: "caption",
3382
3391
  color: "text.secondary",
@@ -3384,8 +3393,8 @@ var init_TeamUsage = __esm({
3384
3393
  },
3385
3394
  label
3386
3395
  );
3387
- return /* @__PURE__ */ React8.createElement(Paper6, { variant: "outlined", sx: { borderRadius: 2, p: 2.5 } }, /* @__PURE__ */ React8.createElement(Box8, { display: "flex", alignItems: "center", gap: 1.5 }, /* @__PURE__ */ React8.createElement(
3388
- Box8,
3396
+ return /* @__PURE__ */ React9.createElement(Paper7, { variant: "outlined", sx: { borderRadius: 2, p: 2.5 } }, /* @__PURE__ */ React9.createElement(Box9, { display: "flex", alignItems: "center", gap: 1.5 }, /* @__PURE__ */ React9.createElement(
3397
+ Box9,
3389
3398
  {
3390
3399
  sx: (theme) => ({
3391
3400
  width: 36,
@@ -3395,20 +3404,20 @@ var init_TeamUsage = __esm({
3395
3404
  display: "flex",
3396
3405
  alignItems: "center",
3397
3406
  justifyContent: "center",
3398
- bgcolor: alpha5(theme.palette.primary.main, 0.1),
3407
+ bgcolor: alpha6(theme.palette.primary.main, 0.1),
3399
3408
  color: theme.palette.primary.main
3400
3409
  })
3401
3410
  },
3402
- /* @__PURE__ */ React8.createElement(Group, { fontSize: "small" })
3403
- ), /* @__PURE__ */ React8.createElement(Box8, { flexGrow: 1, minWidth: 0 }, /* @__PURE__ */ React8.createElement(Typography8, { variant: "subtitle1", sx: { fontWeight: 600, lineHeight: 1.3 } }, team.team_alias || "Untitled team"), /* @__PURE__ */ React8.createElement(
3404
- Typography8,
3411
+ /* @__PURE__ */ React9.createElement(Group, { fontSize: "small" })
3412
+ ), /* @__PURE__ */ React9.createElement(Box9, { flexGrow: 1, minWidth: 0 }, /* @__PURE__ */ React9.createElement(Typography9, { variant: "subtitle1", sx: { fontWeight: 600, lineHeight: 1.3 } }, team.team_alias || "Untitled team"), /* @__PURE__ */ React9.createElement(
3413
+ Typography9,
3405
3414
  {
3406
3415
  variant: "caption",
3407
3416
  color: "text.secondary",
3408
3417
  sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 11 }
3409
3418
  },
3410
3419
  team.team_id
3411
- )), isOver && /* @__PURE__ */ React8.createElement(StatusPill, { label: "Over budget", tone: "danger" }), isNear && /* @__PURE__ */ React8.createElement(StatusPill, { label: "Near limit", tone: "warning" }), canManage && onEditTeam && /* @__PURE__ */ React8.createElement(
3420
+ )), isOver && /* @__PURE__ */ React9.createElement(StatusPill, { label: "Over budget", tone: "danger" }), isNear && /* @__PURE__ */ React9.createElement(StatusPill, { label: "Near limit", tone: "warning" }), canManage && onEditTeam && /* @__PURE__ */ React9.createElement(
3412
3421
  Button5,
3413
3422
  {
3414
3423
  size: "small",
@@ -3417,7 +3426,7 @@ var init_TeamUsage = __esm({
3417
3426
  sx: { textTransform: "none" }
3418
3427
  },
3419
3428
  "Edit"
3420
- ), /* @__PURE__ */ React8.createElement(
3429
+ ), /* @__PURE__ */ React9.createElement(
3421
3430
  IconButton5,
3422
3431
  {
3423
3432
  size: "small",
@@ -3430,9 +3439,9 @@ var init_TeamUsage = __esm({
3430
3439
  transition: theme.transitions.create("transform")
3431
3440
  })
3432
3441
  },
3433
- /* @__PURE__ */ React8.createElement(ExpandMore3, null)
3434
- )), /* @__PURE__ */ React8.createElement(
3435
- Box8,
3442
+ /* @__PURE__ */ React9.createElement(ExpandMore3, null)
3443
+ )), /* @__PURE__ */ React9.createElement(
3444
+ Box9,
3436
3445
  {
3437
3446
  sx: {
3438
3447
  display: "grid",
@@ -3442,35 +3451,35 @@ var init_TeamUsage = __esm({
3442
3451
  maxWidth: 640
3443
3452
  }
3444
3453
  },
3445
- /* @__PURE__ */ React8.createElement(Stat, { label: "Members", value: team.members_with_roles?.length ?? "\u2014" }),
3446
- /* @__PURE__ */ React8.createElement(Stat, { label: "Models", value: team.models?.length ? team.models.length : "All" }),
3447
- /* @__PURE__ */ React8.createElement(Stat, { label: "Budget", value: budgetStatValue(hidden, budget) }),
3448
- /* @__PURE__ */ React8.createElement(Stat, { label: "Spend", value: hidden ? "\u2014" : fmtUsd2(spend) }),
3449
- /* @__PURE__ */ React8.createElement(Stat, { label: "TPM", value: team.tpm_limit && team.tpm_limit > 0 ? team.tpm_limit.toLocaleString() : "\u2014" }),
3450
- /* @__PURE__ */ React8.createElement(Stat, { label: "RPM", value: team.rpm_limit && team.rpm_limit > 0 ? team.rpm_limit.toLocaleString() : "\u2014" })
3451
- ), hidden && /* @__PURE__ */ React8.createElement(Box8, { mt: 2, maxWidth: 640 }, /* @__PURE__ */ React8.createElement(Box8, { display: "flex", justifyContent: "space-between", alignItems: "baseline", mb: 0.75 }, /* @__PURE__ */ React8.createElement(Typography8, { variant: "caption", color: "text.secondary", sx: { fontVariantNumeric: "tabular-nums" } }, budgetPct.toFixed(0), "% used"), /* @__PURE__ */ React8.createElement(Typography8, { variant: "caption", color: "text.secondary" }, "Hidden by admin")), /* @__PURE__ */ React8.createElement(Meter, { value: budgetPct, tone: budgetTone3(isOver, isNear), height: 5 }), /* @__PURE__ */ React8.createElement(Typography8, { variant: "caption", color: "text.secondary", sx: { display: "block", mt: 0.75 } }, "Team budget is hidden \u2014 showing usage level only", team.budget_duration ? ` \xB7 resets every ${team.budget_duration}` : "", ".")), !hidden && budget > 0 && /* @__PURE__ */ React8.createElement(Box8, { mt: 2, maxWidth: 640 }, /* @__PURE__ */ React8.createElement(Box8, { display: "flex", justifyContent: "space-between", alignItems: "baseline", mb: 0.75 }, /* @__PURE__ */ React8.createElement(Typography8, { variant: "caption", color: "text.secondary", sx: { fontVariantNumeric: "tabular-nums" } }, fmtUsd2(spend), " of ", fmtUsd2(budget)), /* @__PURE__ */ React8.createElement(Typography8, { variant: "caption", color: "text.secondary", sx: { fontVariantNumeric: "tabular-nums" } }, budgetPct.toFixed(0), "%")), /* @__PURE__ */ React8.createElement(Meter, { value: budgetPct, tone: budgetTone3(isOver, isNear), height: 5 })), /* @__PURE__ */ React8.createElement(Collapse2, { in: expanded, unmountOnExit: true }, /* @__PURE__ */ React8.createElement(Divider3, { sx: { my: 2.5 } }), /* @__PURE__ */ React8.createElement(Box8, { mb: 2.5 }, sectionLabel("Models"), team.models?.length ? /* @__PURE__ */ React8.createElement(Box8, { display: "flex", gap: 0.5, flexWrap: "wrap" }, team.models.map((m) => /* @__PURE__ */ React8.createElement(TagChip, { key: m, label: m, title: m }))) : /* @__PURE__ */ React8.createElement(Typography8, { variant: "body2", color: "text.secondary" }, "All models allowed")), renderDailySpendSection(), /* @__PURE__ */ React8.createElement(Box8, null, sectionLabel("Members"), team.members_with_roles?.length ? /* @__PURE__ */ React8.createElement(
3454
+ /* @__PURE__ */ React9.createElement(Stat, { label: "Members", value: team.members_with_roles?.length ?? "\u2014" }),
3455
+ /* @__PURE__ */ React9.createElement(Stat, { label: "Models", value: team.models?.length ? team.models.length : "All" }),
3456
+ /* @__PURE__ */ React9.createElement(Stat, { label: "Budget", value: budgetStatValue(hidden, budget) }),
3457
+ /* @__PURE__ */ React9.createElement(Stat, { label: "Spend", value: hidden ? "\u2014" : fmtUsd2(spend) }),
3458
+ /* @__PURE__ */ React9.createElement(Stat, { label: "TPM", value: team.tpm_limit && team.tpm_limit > 0 ? team.tpm_limit.toLocaleString() : "\u2014" }),
3459
+ /* @__PURE__ */ React9.createElement(Stat, { label: "RPM", value: team.rpm_limit && team.rpm_limit > 0 ? team.rpm_limit.toLocaleString() : "\u2014" })
3460
+ ), hidden && /* @__PURE__ */ React9.createElement(Box9, { mt: 2, maxWidth: 640 }, /* @__PURE__ */ React9.createElement(Box9, { display: "flex", justifyContent: "space-between", alignItems: "baseline", mb: 0.75 }, /* @__PURE__ */ React9.createElement(Typography9, { variant: "caption", color: "text.secondary", sx: { fontVariantNumeric: "tabular-nums" } }, budgetPct.toFixed(0), "% used"), /* @__PURE__ */ React9.createElement(Typography9, { variant: "caption", color: "text.secondary" }, "Hidden by admin")), /* @__PURE__ */ React9.createElement(Meter, { value: budgetPct, tone: budgetTone3(isOver, isNear), height: 5 }), /* @__PURE__ */ React9.createElement(Typography9, { variant: "caption", color: "text.secondary", sx: { display: "block", mt: 0.75 } }, "Team budget is hidden \u2014 showing usage level only", team.budget_duration ? ` \xB7 resets every ${team.budget_duration}` : "", ".")), !hidden && budget > 0 && /* @__PURE__ */ React9.createElement(Box9, { mt: 2, maxWidth: 640 }, /* @__PURE__ */ React9.createElement(Box9, { display: "flex", justifyContent: "space-between", alignItems: "baseline", mb: 0.75 }, /* @__PURE__ */ React9.createElement(Typography9, { variant: "caption", color: "text.secondary", sx: { fontVariantNumeric: "tabular-nums" } }, fmtUsd2(spend), " of ", fmtUsd2(budget)), /* @__PURE__ */ React9.createElement(Typography9, { variant: "caption", color: "text.secondary", sx: { fontVariantNumeric: "tabular-nums" } }, budgetPct.toFixed(0), "%")), /* @__PURE__ */ React9.createElement(Meter, { value: budgetPct, tone: budgetTone3(isOver, isNear), height: 5 })), /* @__PURE__ */ React9.createElement(Collapse2, { in: expanded, unmountOnExit: true }, /* @__PURE__ */ React9.createElement(Divider3, { sx: { my: 2.5 } }), /* @__PURE__ */ React9.createElement(Box9, { mb: 2.5 }, sectionLabel("Models"), team.models?.length ? /* @__PURE__ */ React9.createElement(Box9, { display: "flex", gap: 0.5, flexWrap: "wrap" }, team.models.map((m) => /* @__PURE__ */ React9.createElement(TagChip, { key: m, label: m, title: m }))) : /* @__PURE__ */ React9.createElement(Typography9, { variant: "body2", color: "text.secondary" }, "All models allowed")), renderDailySpendSection(), /* @__PURE__ */ React9.createElement(Box9, null, sectionLabel("Members"), team.members_with_roles?.length ? /* @__PURE__ */ React9.createElement(
3452
3461
  TableContainer4,
3453
3462
  {
3454
- component: Paper6,
3463
+ component: Paper7,
3455
3464
  variant: "outlined",
3456
3465
  sx: { borderRadius: 1.5 }
3457
3466
  },
3458
- /* @__PURE__ */ React8.createElement(Table4, { size: "small", sx: dataTableSx }, /* @__PURE__ */ React8.createElement(TableHead4, null, /* @__PURE__ */ React8.createElement(TableRow4, null, /* @__PURE__ */ React8.createElement(TableCell4, null, "User"), /* @__PURE__ */ React8.createElement(TableCell4, { align: "right" }, "Role"))), /* @__PURE__ */ React8.createElement(TableBody4, null, team.members_with_roles.map((m) => /* @__PURE__ */ React8.createElement(TableRow4, { key: m.user_id }, /* @__PURE__ */ React8.createElement(TableCell4, null, m.user_email ? /* @__PURE__ */ React8.createElement(React8.Fragment, null, /* @__PURE__ */ React8.createElement(Typography8, { variant: "body2" }, m.user_email), /* @__PURE__ */ React8.createElement(
3459
- Typography8,
3467
+ /* @__PURE__ */ React9.createElement(Table4, { size: "small", sx: dataTableSx }, /* @__PURE__ */ React9.createElement(TableHead4, null, /* @__PURE__ */ React9.createElement(TableRow4, null, /* @__PURE__ */ React9.createElement(TableCell4, null, "User"), /* @__PURE__ */ React9.createElement(TableCell4, { align: "right" }, "Role"))), /* @__PURE__ */ React9.createElement(TableBody4, null, team.members_with_roles.map((m) => /* @__PURE__ */ React9.createElement(TableRow4, { key: m.user_id }, /* @__PURE__ */ React9.createElement(TableCell4, null, m.user_email ? /* @__PURE__ */ React9.createElement(React9.Fragment, null, /* @__PURE__ */ React9.createElement(Typography9, { variant: "body2" }, m.user_email), /* @__PURE__ */ React9.createElement(
3468
+ Typography9,
3460
3469
  {
3461
3470
  variant: "caption",
3462
3471
  color: "text.secondary",
3463
3472
  sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 11 }
3464
3473
  },
3465
3474
  m.user_id
3466
- )) : /* @__PURE__ */ React8.createElement(
3467
- Typography8,
3475
+ )) : /* @__PURE__ */ React9.createElement(
3476
+ Typography9,
3468
3477
  {
3469
3478
  variant: "body2",
3470
3479
  sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace" }
3471
3480
  },
3472
3481
  m.user_id
3473
- )), /* @__PURE__ */ React8.createElement(TableCell4, { align: "right" }, /* @__PURE__ */ React8.createElement(
3482
+ )), /* @__PURE__ */ React9.createElement(TableCell4, { align: "right" }, /* @__PURE__ */ React9.createElement(
3474
3483
  StatusPill,
3475
3484
  {
3476
3485
  label: m.role,
@@ -3478,7 +3487,7 @@ var init_TeamUsage = __esm({
3478
3487
  dot: false
3479
3488
  }
3480
3489
  ))))))
3481
- ) : /* @__PURE__ */ React8.createElement(Typography8, { variant: "body2", color: "text.secondary" }, "No members assigned."))));
3490
+ ) : /* @__PURE__ */ React9.createElement(Typography9, { variant: "body2", color: "text.secondary" }, "No members assigned."))));
3482
3491
  };
3483
3492
  TeamUsage = ({
3484
3493
  teams,
@@ -3490,22 +3499,22 @@ var init_TeamUsage = __esm({
3490
3499
  canCreate,
3491
3500
  onCreateTeam
3492
3501
  }) => {
3493
- const createAction = canCreate && onCreateTeam ? /* @__PURE__ */ React8.createElement(
3502
+ const createAction = canCreate && onCreateTeam ? /* @__PURE__ */ React9.createElement(
3494
3503
  Button5,
3495
3504
  {
3496
3505
  variant: "contained",
3497
3506
  color: "primary",
3498
3507
  disableElevation: true,
3499
- startIcon: /* @__PURE__ */ React8.createElement(Add3, null),
3508
+ startIcon: /* @__PURE__ */ React9.createElement(Add3, null),
3500
3509
  onClick: onCreateTeam
3501
3510
  },
3502
3511
  "Create Team"
3503
3512
  ) : void 0;
3504
3513
  if (loading) {
3505
- return /* @__PURE__ */ React8.createElement(SectionCard, { title: "Teams", actions: createAction }, /* @__PURE__ */ React8.createElement(Skeleton4, { variant: "rounded", height: 120, sx: { mb: 2 } }), /* @__PURE__ */ React8.createElement(Skeleton4, { variant: "rounded", height: 120 }));
3514
+ return /* @__PURE__ */ React9.createElement(SectionCard, { title: "Teams", actions: createAction }, /* @__PURE__ */ React9.createElement(Skeleton4, { variant: "rounded", height: 120, sx: { mb: 2 } }), /* @__PURE__ */ React9.createElement(Skeleton4, { variant: "rounded", height: 120 }));
3506
3515
  }
3507
3516
  if (!teams.length) {
3508
- return /* @__PURE__ */ React8.createElement(SectionCard, { title: "Teams", actions: createAction }, /* @__PURE__ */ React8.createElement(
3517
+ return /* @__PURE__ */ React9.createElement(SectionCard, { title: "Teams", actions: createAction }, /* @__PURE__ */ React9.createElement(
3509
3518
  EmptyState,
3510
3519
  {
3511
3520
  message: "You're not a member of any LiteLLM team yet.",
@@ -3513,14 +3522,14 @@ var init_TeamUsage = __esm({
3513
3522
  }
3514
3523
  ));
3515
3524
  }
3516
- return /* @__PURE__ */ React8.createElement(
3525
+ return /* @__PURE__ */ React9.createElement(
3517
3526
  SectionCard,
3518
3527
  {
3519
3528
  title: "Teams",
3520
3529
  subtitle: `${teams.length} team${teams.length === 1 ? "" : "s"}`,
3521
3530
  actions: createAction
3522
3531
  },
3523
- /* @__PURE__ */ React8.createElement(Box8, { sx: { display: "flex", flexDirection: "column", gap: 2 } }, teams.map((team) => /* @__PURE__ */ React8.createElement(
3532
+ /* @__PURE__ */ React9.createElement(Box9, { sx: { display: "flex", flexDirection: "column", gap: 2 } }, teams.map((team) => /* @__PURE__ */ React9.createElement(
3524
3533
  TeamCard,
3525
3534
  {
3526
3535
  key: team.team_id,
@@ -3537,8 +3546,8 @@ var init_TeamUsage = __esm({
3537
3546
  });
3538
3547
 
3539
3548
  // src/components/ModelsTable.tsx
3540
- import React9, { useState as useState7, useMemo as useMemo7, useCallback } from "react";
3541
- import Box9 from "@mui/material/Box";
3549
+ import React10, { useState as useState7, useMemo as useMemo7, useCallback } from "react";
3550
+ import Box10 from "@mui/material/Box";
3542
3551
  import Table5 from "@mui/material/Table";
3543
3552
  import TableBody5 from "@mui/material/TableBody";
3544
3553
  import TableCell5 from "@mui/material/TableCell";
@@ -3546,7 +3555,7 @@ import TableContainer5 from "@mui/material/TableContainer";
3546
3555
  import TableHead5 from "@mui/material/TableHead";
3547
3556
  import TableRow5 from "@mui/material/TableRow";
3548
3557
  import TableSortLabel2 from "@mui/material/TableSortLabel";
3549
- import Typography9 from "@mui/material/Typography";
3558
+ import Typography10 from "@mui/material/Typography";
3550
3559
  import MenuItem4 from "@mui/material/MenuItem";
3551
3560
  import Select from "@mui/material/Select";
3552
3561
  import FormControl from "@mui/material/FormControl";
@@ -3558,7 +3567,7 @@ import ContentCopyIcon from "@mui/icons-material/ContentCopy";
3558
3567
  import Tooltip3 from "@mui/material/Tooltip";
3559
3568
  import Snackbar from "@mui/material/Snackbar";
3560
3569
  import Alert4 from "@mui/material/Alert";
3561
- import { alpha as alpha6, useTheme as useTheme2 } from "@mui/material/styles";
3570
+ import { alpha as alpha7, useTheme as useTheme3 } from "@mui/material/styles";
3562
3571
  function isModelAllowedByTeam2(model, teamModels) {
3563
3572
  if (!teamModels || teamModels.length === 0 || teamModels.includes(ALL_PROXY_MODELS2)) return true;
3564
3573
  return teamModels.includes(model.model_name) || !!model.access_groups?.some((group) => teamModels.includes(group));
@@ -3583,7 +3592,7 @@ var init_ModelsTable = __esm({
3583
3592
  init_ui();
3584
3593
  ALL_PROXY_MODELS2 = "all-proxy-models";
3585
3594
  ModelsTable = ({ allModels, teams, loading }) => {
3586
- const theme = useTheme2();
3595
+ const theme = useTheme3();
3587
3596
  const [selectedTeamId, setSelectedTeamId] = useState7("");
3588
3597
  const [filterText, setFilterText] = useState7("");
3589
3598
  const [copiedSnackbar, setCopiedSnackbar] = useState7(false);
@@ -3669,13 +3678,13 @@ var init_ModelsTable = __esm({
3669
3678
  return theme.palette.info.main;
3670
3679
  }
3671
3680
  };
3672
- return /* @__PURE__ */ React9.createElement(
3681
+ return /* @__PURE__ */ React10.createElement(
3673
3682
  SectionCard,
3674
3683
  {
3675
3684
  title: "Available Models",
3676
3685
  subtitle: selectedTeam ? `${filteredModels.length}${filterText.trim() ? ` of ${teamModels.length}` : ""} model${filteredModels.length === 1 ? "" : "s"}` : "Select a team to view its models"
3677
3686
  },
3678
- /* @__PURE__ */ React9.createElement(Box9, { sx: { px: 2.5, pb: 2.5, pt: 1, display: "flex", gap: 2, flexWrap: "wrap" } }, /* @__PURE__ */ React9.createElement(FormControl, { size: "small", sx: { minWidth: 240, mb: 2 } }, /* @__PURE__ */ React9.createElement(InputLabel, { id: "team-select-label" }, "Team"), /* @__PURE__ */ React9.createElement(
3687
+ /* @__PURE__ */ React10.createElement(Box10, { sx: { px: 2.5, pb: 2.5, pt: 1, display: "flex", gap: 2, flexWrap: "wrap" } }, /* @__PURE__ */ React10.createElement(FormControl, { size: "small", sx: { minWidth: 240, mb: 2 } }, /* @__PURE__ */ React10.createElement(InputLabel, { id: "team-select-label" }, "Team"), /* @__PURE__ */ React10.createElement(
3679
3688
  Select,
3680
3689
  {
3681
3690
  labelId: "team-select-label",
@@ -3683,8 +3692,8 @@ var init_ModelsTable = __esm({
3683
3692
  label: "Team",
3684
3693
  onChange: (e) => setSelectedTeamId(e.target.value)
3685
3694
  },
3686
- teams.map((t) => /* @__PURE__ */ React9.createElement(MenuItem4, { key: t.team_id, value: t.team_id }, t.team_alias ?? t.team_id))
3687
- )), /* @__PURE__ */ React9.createElement(
3695
+ teams.map((t) => /* @__PURE__ */ React10.createElement(MenuItem4, { key: t.team_id, value: t.team_id }, t.team_alias ?? t.team_id))
3696
+ )), /* @__PURE__ */ React10.createElement(
3688
3697
  TextField5,
3689
3698
  {
3690
3699
  label: "Filter Models",
@@ -3694,33 +3703,33 @@ var init_ModelsTable = __esm({
3694
3703
  disabled: !selectedTeamId,
3695
3704
  sx: { minWidth: 240, mb: 2 },
3696
3705
  InputProps: {
3697
- startAdornment: /* @__PURE__ */ React9.createElement(InputAdornment2, { position: "start" }, /* @__PURE__ */ React9.createElement(Search2, { fontSize: "small" }))
3706
+ startAdornment: /* @__PURE__ */ React10.createElement(InputAdornment2, { position: "start" }, /* @__PURE__ */ React10.createElement(Search2, { fontSize: "small" }))
3698
3707
  }
3699
3708
  }
3700
3709
  )),
3701
- loading && /* @__PURE__ */ React9.createElement(EmptyState, { message: "Loading models\u2026" }),
3702
- !loading && !selectedTeamId && /* @__PURE__ */ React9.createElement(
3710
+ loading && /* @__PURE__ */ React10.createElement(EmptyState, { message: "Loading models\u2026" }),
3711
+ !loading && !selectedTeamId && /* @__PURE__ */ React10.createElement(
3703
3712
  EmptyState,
3704
3713
  {
3705
3714
  message: "Select a team to see available models",
3706
3715
  hint: "Each team may have a different set of models assigned"
3707
3716
  }
3708
3717
  ),
3709
- !loading && selectedTeamId && teamModels.length === 0 && /* @__PURE__ */ React9.createElement(
3718
+ !loading && selectedTeamId && teamModels.length === 0 && /* @__PURE__ */ React10.createElement(
3710
3719
  EmptyState,
3711
3720
  {
3712
3721
  message: "No models available for this team",
3713
3722
  hint: allModels.length === 0 ? "No models are configured in the system" : "None of the team's assigned models were found in the catalog"
3714
3723
  }
3715
3724
  ),
3716
- !loading && selectedTeamId && teamModels.length > 0 && filteredModels.length === 0 && /* @__PURE__ */ React9.createElement(
3725
+ !loading && selectedTeamId && teamModels.length > 0 && filteredModels.length === 0 && /* @__PURE__ */ React10.createElement(
3717
3726
  EmptyState,
3718
3727
  {
3719
3728
  message: "No models match your search",
3720
3729
  hint: "Try a different search term"
3721
3730
  }
3722
3731
  ),
3723
- !loading && filteredModels.length > 0 && /* @__PURE__ */ React9.createElement(TableContainer5, null, /* @__PURE__ */ React9.createElement(Table5, { size: "small", sx: dataTableSx }, /* @__PURE__ */ React9.createElement(TableHead5, null, /* @__PURE__ */ React9.createElement(TableRow5, null, /* @__PURE__ */ React9.createElement(TableCell5, { sortDirection: sortKey === "model_name" ? sortDirection : false }, /* @__PURE__ */ React9.createElement(TableSortLabel2, { active: sortKey === "model_name", direction: sortKey === "model_name" ? sortDirection : "asc", onClick: () => handleSort("model_name") }, "Model ID")), /* @__PURE__ */ React9.createElement(TableCell5, { sortDirection: sortKey === "mode" ? sortDirection : false }, /* @__PURE__ */ React9.createElement(TableSortLabel2, { active: sortKey === "mode", direction: sortKey === "mode" ? sortDirection : "asc", onClick: () => handleSort("mode") }, "Mode")), /* @__PURE__ */ React9.createElement(TableCell5, { align: "right", sortDirection: sortKey === "input_cost_per_token" ? sortDirection : false }, /* @__PURE__ */ React9.createElement(TableSortLabel2, { active: sortKey === "input_cost_per_token", direction: sortKey === "input_cost_per_token" ? sortDirection : "asc", onClick: () => handleSort("input_cost_per_token") }, "Input Cost")), /* @__PURE__ */ React9.createElement(TableCell5, { align: "right", sortDirection: sortKey === "output_cost_per_token" ? sortDirection : false }, /* @__PURE__ */ React9.createElement(TableSortLabel2, { active: sortKey === "output_cost_per_token", direction: sortKey === "output_cost_per_token" ? sortDirection : "asc", onClick: () => handleSort("output_cost_per_token") }, "Output Cost")), /* @__PURE__ */ React9.createElement(TableCell5, { align: "right", sortDirection: sortKey === "max_input_tokens" ? sortDirection : false }, /* @__PURE__ */ React9.createElement(TableSortLabel2, { active: sortKey === "max_input_tokens", direction: sortKey === "max_input_tokens" ? sortDirection : "asc", onClick: () => handleSort("max_input_tokens") }, "Max Input")), /* @__PURE__ */ React9.createElement(TableCell5, { align: "right", sortDirection: sortKey === "max_output_tokens" ? sortDirection : false }, /* @__PURE__ */ React9.createElement(TableSortLabel2, { active: sortKey === "max_output_tokens", direction: sortKey === "max_output_tokens" ? sortDirection : "asc", onClick: () => handleSort("max_output_tokens") }, "Max Output")), /* @__PURE__ */ React9.createElement(TableCell5, null, "Capabilities"))), /* @__PURE__ */ React9.createElement(TableBody5, null, filteredModels.map((m) => /* @__PURE__ */ React9.createElement(TableRow5, { key: m.model_name, hover: true }, /* @__PURE__ */ React9.createElement(TableCell5, null, /* @__PURE__ */ React9.createElement(Box9, { sx: { display: "flex", alignItems: "center", gap: 0.5 } }, /* @__PURE__ */ React9.createElement(Tooltip3, { title: m.model_name, placement: "top" }, /* @__PURE__ */ React9.createElement(
3732
+ !loading && filteredModels.length > 0 && /* @__PURE__ */ React10.createElement(TableContainer5, null, /* @__PURE__ */ React10.createElement(Table5, { size: "small", sx: dataTableSx }, /* @__PURE__ */ React10.createElement(TableHead5, null, /* @__PURE__ */ React10.createElement(TableRow5, null, /* @__PURE__ */ React10.createElement(TableCell5, { sortDirection: sortKey === "model_name" ? sortDirection : false }, /* @__PURE__ */ React10.createElement(TableSortLabel2, { active: sortKey === "model_name", direction: sortKey === "model_name" ? sortDirection : "asc", onClick: () => handleSort("model_name") }, "Model ID")), /* @__PURE__ */ React10.createElement(TableCell5, { sortDirection: sortKey === "mode" ? sortDirection : false }, /* @__PURE__ */ React10.createElement(TableSortLabel2, { active: sortKey === "mode", direction: sortKey === "mode" ? sortDirection : "asc", onClick: () => handleSort("mode") }, "Mode")), /* @__PURE__ */ React10.createElement(TableCell5, { align: "right", sortDirection: sortKey === "input_cost_per_token" ? sortDirection : false }, /* @__PURE__ */ React10.createElement(TableSortLabel2, { active: sortKey === "input_cost_per_token", direction: sortKey === "input_cost_per_token" ? sortDirection : "asc", onClick: () => handleSort("input_cost_per_token") }, "Input Cost")), /* @__PURE__ */ React10.createElement(TableCell5, { align: "right", sortDirection: sortKey === "output_cost_per_token" ? sortDirection : false }, /* @__PURE__ */ React10.createElement(TableSortLabel2, { active: sortKey === "output_cost_per_token", direction: sortKey === "output_cost_per_token" ? sortDirection : "asc", onClick: () => handleSort("output_cost_per_token") }, "Output Cost")), /* @__PURE__ */ React10.createElement(TableCell5, { align: "right", sortDirection: sortKey === "max_input_tokens" ? sortDirection : false }, /* @__PURE__ */ React10.createElement(TableSortLabel2, { active: sortKey === "max_input_tokens", direction: sortKey === "max_input_tokens" ? sortDirection : "asc", onClick: () => handleSort("max_input_tokens") }, "Max Input")), /* @__PURE__ */ React10.createElement(TableCell5, { align: "right", sortDirection: sortKey === "max_output_tokens" ? sortDirection : false }, /* @__PURE__ */ React10.createElement(TableSortLabel2, { active: sortKey === "max_output_tokens", direction: sortKey === "max_output_tokens" ? sortDirection : "asc", onClick: () => handleSort("max_output_tokens") }, "Max Output")), /* @__PURE__ */ React10.createElement(TableCell5, null, "Capabilities"))), /* @__PURE__ */ React10.createElement(TableBody5, null, filteredModels.map((m) => /* @__PURE__ */ React10.createElement(TableRow5, { key: m.model_name, hover: true }, /* @__PURE__ */ React10.createElement(TableCell5, null, /* @__PURE__ */ React10.createElement(Box10, { sx: { display: "flex", alignItems: "center", gap: 0.5 } }, /* @__PURE__ */ React10.createElement(Tooltip3, { title: m.model_name, placement: "top" }, /* @__PURE__ */ React10.createElement(
3724
3733
  "span",
3725
3734
  {
3726
3735
  role: "button",
@@ -3730,9 +3739,9 @@ var init_ModelsTable = __esm({
3730
3739
  onKeyDown: (e) => e.key === "Enter" && handleCopyModelId(m.model_name),
3731
3740
  style: { cursor: "pointer", display: "inline-flex" }
3732
3741
  },
3733
- /* @__PURE__ */ React9.createElement(TagChip, { label: m.model_name, title: "Copy model ID" })
3734
- )), /* @__PURE__ */ React9.createElement(Tooltip3, { title: "Copy model ID" }, /* @__PURE__ */ React9.createElement(
3735
- Box9,
3742
+ /* @__PURE__ */ React10.createElement(TagChip, { label: m.model_name, title: "Copy model ID" })
3743
+ )), /* @__PURE__ */ React10.createElement(Tooltip3, { title: "Copy model ID" }, /* @__PURE__ */ React10.createElement(
3744
+ Box10,
3736
3745
  {
3737
3746
  component: "span",
3738
3747
  role: "button",
@@ -3742,9 +3751,9 @@ var init_ModelsTable = __esm({
3742
3751
  sx: quietIconButtonSx("accent"),
3743
3752
  style: { cursor: "pointer", display: "inline-flex" }
3744
3753
  },
3745
- /* @__PURE__ */ React9.createElement(ContentCopyIcon, { sx: { fontSize: 14 } })
3746
- )))), /* @__PURE__ */ React9.createElement(TableCell5, null, /* @__PURE__ */ React9.createElement(
3747
- Box9,
3754
+ /* @__PURE__ */ React10.createElement(ContentCopyIcon, { sx: { fontSize: 14 } })
3755
+ )))), /* @__PURE__ */ React10.createElement(TableCell5, null, /* @__PURE__ */ React10.createElement(
3756
+ Box10,
3748
3757
  {
3749
3758
  component: "span",
3750
3759
  sx: {
@@ -3755,13 +3764,13 @@ var init_ModelsTable = __esm({
3755
3764
  fontSize: 11,
3756
3765
  fontWeight: 600,
3757
3766
  textTransform: "capitalize",
3758
- bgcolor: alpha6(modeColor(m.mode), 0.1),
3767
+ bgcolor: alpha7(modeColor(m.mode), 0.1),
3759
3768
  color: modeColor(m.mode)
3760
3769
  }
3761
3770
  },
3762
3771
  m.mode
3763
- )), /* @__PURE__ */ React9.createElement(TableCell5, { align: "right" }, /* @__PURE__ */ React9.createElement(Typography9, { variant: "body2", sx: { fontVariantNumeric: "tabular-nums" } }, fmtCostPerToken(m.input_cost_per_token))), /* @__PURE__ */ React9.createElement(TableCell5, { align: "right" }, /* @__PURE__ */ React9.createElement(Typography9, { variant: "body2", sx: { fontVariantNumeric: "tabular-nums" } }, fmtCostPerToken(m.output_cost_per_token))), /* @__PURE__ */ React9.createElement(TableCell5, { align: "right" }, /* @__PURE__ */ React9.createElement(Typography9, { variant: "body2", sx: { fontVariantNumeric: "tabular-nums" } }, fmtTokens(m.max_input_tokens))), /* @__PURE__ */ React9.createElement(TableCell5, { align: "right" }, /* @__PURE__ */ React9.createElement(Typography9, { variant: "body2", sx: { fontVariantNumeric: "tabular-nums" } }, fmtTokens(m.max_output_tokens))), /* @__PURE__ */ React9.createElement(TableCell5, null, /* @__PURE__ */ React9.createElement(Box9, { sx: { display: "flex", gap: 0.5, flexWrap: "wrap" } }, m.supports_function_calling && /* @__PURE__ */ React9.createElement(TagChip, { label: "Fn", title: "Function calling" }), m.supports_vision && /* @__PURE__ */ React9.createElement(TagChip, { label: "\u{1F441}", title: "Vision" })))))))),
3764
- /* @__PURE__ */ React9.createElement(
3772
+ )), /* @__PURE__ */ React10.createElement(TableCell5, { align: "right" }, /* @__PURE__ */ React10.createElement(Typography10, { variant: "body2", sx: { fontVariantNumeric: "tabular-nums" } }, fmtCostPerToken(m.input_cost_per_token))), /* @__PURE__ */ React10.createElement(TableCell5, { align: "right" }, /* @__PURE__ */ React10.createElement(Typography10, { variant: "body2", sx: { fontVariantNumeric: "tabular-nums" } }, fmtCostPerToken(m.output_cost_per_token))), /* @__PURE__ */ React10.createElement(TableCell5, { align: "right" }, /* @__PURE__ */ React10.createElement(Typography10, { variant: "body2", sx: { fontVariantNumeric: "tabular-nums" } }, fmtTokens(m.max_input_tokens))), /* @__PURE__ */ React10.createElement(TableCell5, { align: "right" }, /* @__PURE__ */ React10.createElement(Typography10, { variant: "body2", sx: { fontVariantNumeric: "tabular-nums" } }, fmtTokens(m.max_output_tokens))), /* @__PURE__ */ React10.createElement(TableCell5, null, /* @__PURE__ */ React10.createElement(Box10, { sx: { display: "flex", gap: 0.5, flexWrap: "wrap" } }, m.supports_function_calling && /* @__PURE__ */ React10.createElement(TagChip, { label: "Fn", title: "Function calling" }), m.supports_vision && /* @__PURE__ */ React10.createElement(TagChip, { label: "\u{1F441}", title: "Vision" })))))))),
3773
+ /* @__PURE__ */ React10.createElement(
3765
3774
  Snackbar,
3766
3775
  {
3767
3776
  open: copiedSnackbar,
@@ -3769,7 +3778,7 @@ var init_ModelsTable = __esm({
3769
3778
  onClose: () => setCopiedSnackbar(false),
3770
3779
  anchorOrigin: { vertical: "bottom", horizontal: "center" }
3771
3780
  },
3772
- /* @__PURE__ */ React9.createElement(Alert4, { severity: "success", variant: "filled", sx: { fontSize: 13 } }, "Model ID copied to clipboard")
3781
+ /* @__PURE__ */ React10.createElement(Alert4, { severity: "success", variant: "filled", sx: { fontSize: 13 } }, "Model ID copied to clipboard")
3773
3782
  )
3774
3783
  );
3775
3784
  };
@@ -3777,9 +3786,9 @@ var init_ModelsTable = __esm({
3777
3786
  });
3778
3787
 
3779
3788
  // src/components/AuditLog.tsx
3780
- import React10, { useState as useState8, useMemo as useMemo8 } from "react";
3781
- import Box10 from "@mui/material/Box";
3782
- import Typography10 from "@mui/material/Typography";
3789
+ import React11, { useState as useState8, useMemo as useMemo8 } from "react";
3790
+ import Box11 from "@mui/material/Box";
3791
+ import Typography11 from "@mui/material/Typography";
3783
3792
  import Table6 from "@mui/material/Table";
3784
3793
  import TableBody6 from "@mui/material/TableBody";
3785
3794
  import TableCell6 from "@mui/material/TableCell";
@@ -3793,7 +3802,7 @@ import Skeleton5 from "@mui/material/Skeleton";
3793
3802
  import Collapse3 from "@mui/material/Collapse";
3794
3803
  import IconButton6 from "@mui/material/IconButton";
3795
3804
  import Alert5 from "@mui/material/Alert";
3796
- import { alpha as alpha7 } from "@mui/material/styles";
3805
+ import { alpha as alpha8 } from "@mui/material/styles";
3797
3806
  import { KeyboardArrowDown } from "@mui/icons-material";
3798
3807
  import { useAsync as useAsync2 } from "react-use";
3799
3808
  function actionTone(action) {
@@ -3816,10 +3825,10 @@ function formatDateTime(iso) {
3816
3825
  }
3817
3826
  function renderAuditLogBody(loading, entries) {
3818
3827
  if (loading) {
3819
- return /* @__PURE__ */ React10.createElement(TableRow6, null, /* @__PURE__ */ React10.createElement(TableCell6, { colSpan: 6, sx: { py: 2 } }, /* @__PURE__ */ React10.createElement(Skeleton5, { variant: "rounded", height: 160 })));
3828
+ return /* @__PURE__ */ React11.createElement(TableRow6, null, /* @__PURE__ */ React11.createElement(TableCell6, { colSpan: 6, sx: { py: 2 } }, /* @__PURE__ */ React11.createElement(Skeleton5, { variant: "rounded", height: 160 })));
3820
3829
  }
3821
3830
  if (entries.length === 0) {
3822
- return /* @__PURE__ */ React10.createElement(TableRow6, null, /* @__PURE__ */ React10.createElement(TableCell6, { colSpan: 6 }, /* @__PURE__ */ React10.createElement(
3831
+ return /* @__PURE__ */ React11.createElement(TableRow6, null, /* @__PURE__ */ React11.createElement(TableCell6, { colSpan: 6 }, /* @__PURE__ */ React11.createElement(
3823
3832
  EmptyState,
3824
3833
  {
3825
3834
  message: "No audit events found",
@@ -3827,7 +3836,7 @@ function renderAuditLogBody(loading, entries) {
3827
3836
  }
3828
3837
  )));
3829
3838
  }
3830
- return entries.map((entry) => /* @__PURE__ */ React10.createElement(DetailRow, { key: entry.id, entry }));
3839
+ return entries.map((entry) => /* @__PURE__ */ React11.createElement(DetailRow, { key: entry.id, entry }));
3831
3840
  }
3832
3841
  var ACTION_TONES, TABLE_LABELS, DetailRow, AuditLog;
3833
3842
  var init_AuditLog = __esm({
@@ -3851,7 +3860,7 @@ var init_AuditLog = __esm({
3851
3860
  DetailRow = ({ entry }) => {
3852
3861
  const [open, setOpen] = useState8(false);
3853
3862
  const hasDetail = entry.before_value || entry.updated_values;
3854
- return /* @__PURE__ */ React10.createElement(React10.Fragment, null, /* @__PURE__ */ React10.createElement(TableRow6, null, /* @__PURE__ */ React10.createElement(TableCell6, { sx: { width: 40, pr: 0 } }, hasDetail && /* @__PURE__ */ React10.createElement(
3863
+ return /* @__PURE__ */ React11.createElement(React11.Fragment, null, /* @__PURE__ */ React11.createElement(TableRow6, null, /* @__PURE__ */ React11.createElement(TableCell6, { sx: { width: 40, pr: 0 } }, hasDetail && /* @__PURE__ */ React11.createElement(
3855
3864
  IconButton6,
3856
3865
  {
3857
3866
  size: "small",
@@ -3864,9 +3873,9 @@ var init_AuditLog = __esm({
3864
3873
  transition: theme.transitions.create("transform")
3865
3874
  })
3866
3875
  },
3867
- /* @__PURE__ */ React10.createElement(KeyboardArrowDown, { fontSize: "small" })
3868
- )), /* @__PURE__ */ React10.createElement(TableCell6, { sx: { whiteSpace: "nowrap" } }, /* @__PURE__ */ React10.createElement(Typography10, { variant: "body2", color: "text.secondary" }, formatDateTime(entry.updated_at))), /* @__PURE__ */ React10.createElement(TableCell6, null, entry.action && /* @__PURE__ */ React10.createElement(StatusPill, { label: entry.action, tone: actionTone(entry.action) })), /* @__PURE__ */ React10.createElement(TableCell6, null, /* @__PURE__ */ React10.createElement(Typography10, { variant: "body2" }, prettyTableName(entry.table_name))), /* @__PURE__ */ React10.createElement(TableCell6, null, /* @__PURE__ */ React10.createElement(
3869
- Typography10,
3876
+ /* @__PURE__ */ React11.createElement(KeyboardArrowDown, { fontSize: "small" })
3877
+ )), /* @__PURE__ */ React11.createElement(TableCell6, { sx: { whiteSpace: "nowrap" } }, /* @__PURE__ */ React11.createElement(Typography11, { variant: "body2", color: "text.secondary" }, formatDateTime(entry.updated_at))), /* @__PURE__ */ React11.createElement(TableCell6, null, entry.action && /* @__PURE__ */ React11.createElement(StatusPill, { label: entry.action, tone: actionTone(entry.action) })), /* @__PURE__ */ React11.createElement(TableCell6, null, /* @__PURE__ */ React11.createElement(Typography11, { variant: "body2" }, prettyTableName(entry.table_name))), /* @__PURE__ */ React11.createElement(TableCell6, null, /* @__PURE__ */ React11.createElement(
3878
+ Typography11,
3870
3879
  {
3871
3880
  variant: "body2",
3872
3881
  component: "code",
@@ -3875,8 +3884,8 @@ var init_AuditLog = __esm({
3875
3884
  sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 12 }
3876
3885
  },
3877
3886
  entry.object_id ? entry.object_id.slice(0, 20) + (entry.object_id.length > 20 ? "\u2026" : "") : "\u2014"
3878
- )), /* @__PURE__ */ React10.createElement(TableCell6, null, entry.changed_by ?? "\u2014")), hasDetail && /* @__PURE__ */ React10.createElement(TableRow6, { sx: { "&&:hover": { bgcolor: "transparent" } } }, /* @__PURE__ */ React10.createElement(TableCell6, { colSpan: 6, sx: { "&&": { py: 0, border: 0 } } }, /* @__PURE__ */ React10.createElement(Collapse3, { in: open, unmountOnExit: true }, /* @__PURE__ */ React10.createElement(
3879
- Box10,
3887
+ )), /* @__PURE__ */ React11.createElement(TableCell6, null, entry.changed_by ?? "\u2014")), hasDetail && /* @__PURE__ */ React11.createElement(TableRow6, { sx: { "&&:hover": { bgcolor: "transparent" } } }, /* @__PURE__ */ React11.createElement(TableCell6, { colSpan: 6, sx: { "&&": { py: 0, border: 0 } } }, /* @__PURE__ */ React11.createElement(Collapse3, { in: open, unmountOnExit: true }, /* @__PURE__ */ React11.createElement(
3888
+ Box11,
3880
3889
  {
3881
3890
  display: "flex",
3882
3891
  gap: 2,
@@ -3885,11 +3894,11 @@ var init_AuditLog = __esm({
3885
3894
  p: 2,
3886
3895
  mb: 1.5,
3887
3896
  borderRadius: 1.5,
3888
- bgcolor: alpha7(theme.palette.text.primary, theme.palette.mode === "dark" ? 0.05 : 0.03)
3897
+ bgcolor: alpha8(theme.palette.text.primary, theme.palette.mode === "dark" ? 0.05 : 0.03)
3889
3898
  })
3890
3899
  },
3891
- entry.before_value && /* @__PURE__ */ React10.createElement(Box10, { flex: 1, minWidth: 200 }, /* @__PURE__ */ React10.createElement(
3892
- Typography10,
3900
+ entry.before_value && /* @__PURE__ */ React11.createElement(Box11, { flex: 1, minWidth: 200 }, /* @__PURE__ */ React11.createElement(
3901
+ Typography11,
3893
3902
  {
3894
3903
  variant: "caption",
3895
3904
  color: "text.secondary",
@@ -3898,9 +3907,9 @@ var init_AuditLog = __esm({
3898
3907
  sx: { fontSize: 10.5, fontWeight: 700, letterSpacing: "0.07em", textTransform: "uppercase" }
3899
3908
  },
3900
3909
  "Before"
3901
- ), /* @__PURE__ */ React10.createElement(Typography10, { 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))),
3902
- entry.updated_values && /* @__PURE__ */ React10.createElement(Box10, { flex: 1, minWidth: 200 }, /* @__PURE__ */ React10.createElement(
3903
- Typography10,
3910
+ ), /* @__PURE__ */ React11.createElement(Typography11, { 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))),
3911
+ entry.updated_values && /* @__PURE__ */ React11.createElement(Box11, { flex: 1, minWidth: 200 }, /* @__PURE__ */ React11.createElement(
3912
+ Typography11,
3904
3913
  {
3905
3914
  variant: "caption",
3906
3915
  color: "text.secondary",
@@ -3909,7 +3918,7 @@ var init_AuditLog = __esm({
3909
3918
  sx: { fontSize: 10.5, fontWeight: 700, letterSpacing: "0.07em", textTransform: "uppercase" }
3910
3919
  },
3911
3920
  "After"
3912
- ), /* @__PURE__ */ React10.createElement(Typography10, { 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)))
3921
+ ), /* @__PURE__ */ React11.createElement(Typography11, { 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)))
3913
3922
  )))));
3914
3923
  };
3915
3924
  AuditLog = ({ api }) => {
@@ -3926,13 +3935,13 @@ var init_AuditLog = __esm({
3926
3935
  );
3927
3936
  const entries = value?.audit_logs ?? [];
3928
3937
  const total = value?.total ?? 0;
3929
- return /* @__PURE__ */ React10.createElement(
3938
+ return /* @__PURE__ */ React11.createElement(
3930
3939
  SectionCard,
3931
3940
  {
3932
3941
  title: "Audit Log",
3933
3942
  subtitle: loading ? "Loading\u2026" : `${total.toLocaleString()} event${total === 1 ? "" : "s"}`,
3934
3943
  flush: true,
3935
- actions: /* @__PURE__ */ React10.createElement(React10.Fragment, null, /* @__PURE__ */ React10.createElement(
3944
+ actions: /* @__PURE__ */ React11.createElement(React11.Fragment, null, /* @__PURE__ */ React11.createElement(
3936
3945
  TextField6,
3937
3946
  {
3938
3947
  size: "small",
@@ -3945,12 +3954,12 @@ var init_AuditLog = __esm({
3945
3954
  },
3946
3955
  sx: { minWidth: 150 }
3947
3956
  },
3948
- /* @__PURE__ */ React10.createElement(MenuItem5, { value: "" }, "All actions"),
3949
- /* @__PURE__ */ React10.createElement(MenuItem5, { value: "created" }, "Created"),
3950
- /* @__PURE__ */ React10.createElement(MenuItem5, { value: "updated" }, "Updated"),
3951
- /* @__PURE__ */ React10.createElement(MenuItem5, { value: "deleted" }, "Deleted"),
3952
- /* @__PURE__ */ React10.createElement(MenuItem5, { value: "blocked" }, "Blocked")
3953
- ), /* @__PURE__ */ React10.createElement(
3957
+ /* @__PURE__ */ React11.createElement(MenuItem5, { value: "" }, "All actions"),
3958
+ /* @__PURE__ */ React11.createElement(MenuItem5, { value: "created" }, "Created"),
3959
+ /* @__PURE__ */ React11.createElement(MenuItem5, { value: "updated" }, "Updated"),
3960
+ /* @__PURE__ */ React11.createElement(MenuItem5, { value: "deleted" }, "Deleted"),
3961
+ /* @__PURE__ */ React11.createElement(MenuItem5, { value: "blocked" }, "Blocked")
3962
+ ), /* @__PURE__ */ React11.createElement(
3954
3963
  TextField6,
3955
3964
  {
3956
3965
  size: "small",
@@ -3963,13 +3972,13 @@ var init_AuditLog = __esm({
3963
3972
  },
3964
3973
  sx: { minWidth: 150 }
3965
3974
  },
3966
- /* @__PURE__ */ React10.createElement(MenuItem5, { value: "" }, "All tables"),
3967
- /* @__PURE__ */ React10.createElement(MenuItem5, { value: "LiteLLM_VerificationToken" }, "Key"),
3968
- /* @__PURE__ */ React10.createElement(MenuItem5, { value: "LiteLLM_TeamTable" }, "Team"),
3969
- /* @__PURE__ */ React10.createElement(MenuItem5, { value: "LiteLLM_TeamMembership" }, "Team member"),
3970
- /* @__PURE__ */ React10.createElement(MenuItem5, { value: "LiteLLM_ObjectPermissionTable" }, "Team access (KB / MCP)"),
3971
- /* @__PURE__ */ React10.createElement(MenuItem5, { value: "LiteLLM_UserTable" }, "User")
3972
- ), /* @__PURE__ */ React10.createElement(
3975
+ /* @__PURE__ */ React11.createElement(MenuItem5, { value: "" }, "All tables"),
3976
+ /* @__PURE__ */ React11.createElement(MenuItem5, { value: "LiteLLM_VerificationToken" }, "Key"),
3977
+ /* @__PURE__ */ React11.createElement(MenuItem5, { value: "LiteLLM_TeamTable" }, "Team"),
3978
+ /* @__PURE__ */ React11.createElement(MenuItem5, { value: "LiteLLM_TeamMembership" }, "Team member"),
3979
+ /* @__PURE__ */ React11.createElement(MenuItem5, { value: "LiteLLM_ObjectPermissionTable" }, "Team access (KB / MCP)"),
3980
+ /* @__PURE__ */ React11.createElement(MenuItem5, { value: "LiteLLM_UserTable" }, "User")
3981
+ ), /* @__PURE__ */ React11.createElement(
3973
3982
  TextField6,
3974
3983
  {
3975
3984
  size: "small",
@@ -3983,9 +3992,9 @@ var init_AuditLog = __esm({
3983
3992
  }
3984
3993
  ))
3985
3994
  },
3986
- error && /* @__PURE__ */ React10.createElement(Box10, { px: 2.5, pb: 2 }, /* @__PURE__ */ React10.createElement(Alert5, { severity: "error" }, error.message)),
3987
- /* @__PURE__ */ React10.createElement(TableContainer6, null, /* @__PURE__ */ React10.createElement(Table6, { size: "small", sx: dataTableSx }, /* @__PURE__ */ React10.createElement(TableHead6, null, /* @__PURE__ */ React10.createElement(TableRow6, null, /* @__PURE__ */ React10.createElement(TableCell6, { sx: { width: 40 } }), /* @__PURE__ */ React10.createElement(TableCell6, null, "Time"), /* @__PURE__ */ React10.createElement(TableCell6, null, "Action"), /* @__PURE__ */ React10.createElement(TableCell6, null, "Table"), /* @__PURE__ */ React10.createElement(TableCell6, null, "Object ID"), /* @__PURE__ */ React10.createElement(TableCell6, null, "Changed By"))), /* @__PURE__ */ React10.createElement(TableBody6, null, renderAuditLogBody(loading, entries)))),
3988
- /* @__PURE__ */ React10.createElement(
3995
+ error && /* @__PURE__ */ React11.createElement(Box11, { px: 2.5, pb: 2 }, /* @__PURE__ */ React11.createElement(Alert5, { severity: "error" }, error.message)),
3996
+ /* @__PURE__ */ React11.createElement(TableContainer6, null, /* @__PURE__ */ React11.createElement(Table6, { size: "small", sx: dataTableSx }, /* @__PURE__ */ React11.createElement(TableHead6, null, /* @__PURE__ */ React11.createElement(TableRow6, null, /* @__PURE__ */ React11.createElement(TableCell6, { sx: { width: 40 } }), /* @__PURE__ */ React11.createElement(TableCell6, null, "Time"), /* @__PURE__ */ React11.createElement(TableCell6, null, "Action"), /* @__PURE__ */ React11.createElement(TableCell6, null, "Table"), /* @__PURE__ */ React11.createElement(TableCell6, null, "Object ID"), /* @__PURE__ */ React11.createElement(TableCell6, null, "Changed By"))), /* @__PURE__ */ React11.createElement(TableBody6, null, renderAuditLogBody(loading, entries)))),
3997
+ /* @__PURE__ */ React11.createElement(
3989
3998
  TablePagination,
3990
3999
  {
3991
4000
  component: "div",
@@ -4040,13 +4049,13 @@ var LiteLLMPage_exports = {};
4040
4049
  __export(LiteLLMPage_exports, {
4041
4050
  LiteLLMPage: () => LiteLLMPage
4042
4051
  });
4043
- import React11, { useState as useState9, useCallback as useCallback2, useMemo as useMemo9 } from "react";
4044
- import Box11 from "@mui/material/Box";
4052
+ import React12, { useState as useState9, useCallback as useCallback2, useEffect as useEffect4, useMemo as useMemo9 } from "react";
4053
+ import Box12 from "@mui/material/Box";
4045
4054
  import Snackbar2 from "@mui/material/Snackbar";
4046
4055
  import Alert6 from "@mui/material/Alert";
4047
4056
  import CircularProgress6 from "@mui/material/CircularProgress";
4048
- import Typography11 from "@mui/material/Typography";
4049
- import Paper7 from "@mui/material/Paper";
4057
+ import Typography12 from "@mui/material/Typography";
4058
+ import Paper8 from "@mui/material/Paper";
4050
4059
  import Tabs2 from "@mui/material/Tabs";
4051
4060
  import Tab2 from "@mui/material/Tab";
4052
4061
  import Grid2 from "@mui/material/Grid";
@@ -4074,7 +4083,7 @@ var init_LiteLLMPage = __esm({
4074
4083
  "use strict";
4075
4084
  init_DashboardHeader();
4076
4085
  init_KeysTable();
4077
- init_GenerateKeyDialog();
4086
+ init_KeyFormDialog();
4078
4087
  init_ManageTeamDialog();
4079
4088
  init_UsageStats();
4080
4089
  init_LiteLLMBudgetWidget();
@@ -4101,6 +4110,21 @@ var init_LiteLLMPage = __esm({
4101
4110
  const t = searchParams.get("tab");
4102
4111
  return isPageTab(t) ? t : "overview";
4103
4112
  });
4113
+ const [generateDialogOpen, setGenerateDialogOpen] = useState9(
4114
+ () => searchParams.get("generate") === "1"
4115
+ );
4116
+ useEffect4(() => {
4117
+ if (searchParams.get("generate") !== "1") return;
4118
+ setGenerateDialogOpen(true);
4119
+ setSearchParams(
4120
+ (prev) => {
4121
+ const next = new URLSearchParams(prev);
4122
+ next.delete("generate");
4123
+ return next;
4124
+ },
4125
+ { replace: true }
4126
+ );
4127
+ }, [searchParams, setSearchParams]);
4104
4128
  const selectTab = useCallback2(
4105
4129
  (tab) => {
4106
4130
  setActiveTab(tab);
@@ -4116,8 +4140,9 @@ var init_LiteLLMPage = __esm({
4116
4140
  [setSearchParams]
4117
4141
  );
4118
4142
  const [snackbar, setSnackbar] = useState9(null);
4119
- const [generateDialogOpen, setGenerateDialogOpen] = useState9(false);
4120
4143
  const [manageTeam, setManageTeam] = useState9(null);
4144
+ const [keyToEdit, setKeyToEdit] = useState9(null);
4145
+ const keyFormOpen = generateDialogOpen || !!keyToEdit;
4121
4146
  const [teamUsageCache, setTeamUsageCache] = useState9({});
4122
4147
  const [teamUsageLoading, setTeamUsageLoading] = useState9({});
4123
4148
  const { value: userInfo, loading: userLoading, error: userError } = useAsync3(
@@ -4310,14 +4335,14 @@ var init_LiteLLMPage = __esm({
4310
4335
  );
4311
4336
  const isInitialLoading = userLoading && !userInfo;
4312
4337
  if (isInitialLoading) {
4313
- return /* @__PURE__ */ React11.createElement(Box11, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: "50vh" }, /* @__PURE__ */ React11.createElement(CircularProgress6, null));
4338
+ return /* @__PURE__ */ React12.createElement(Box12, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: "50vh" }, /* @__PURE__ */ React12.createElement(CircularProgress6, null));
4314
4339
  }
4315
4340
  if (userError || !userInfo) {
4316
4341
  const isProvisioningEnabled = userError?.body?.provisioning === true;
4317
4342
  const hint = userError?.body?.hint;
4318
- return /* @__PURE__ */ React11.createElement(Box11, { p: 3 }, /* @__PURE__ */ React11.createElement(Paper7, { sx: { p: 3 } }, /* @__PURE__ */ React11.createElement(Typography11, { variant: "h6", gutterBottom: true }, "Account not provisioned"), /* @__PURE__ */ React11.createElement(Typography11, { color: "text.secondary", paragraph: true }, "Your Backstage account is not linked to a LiteLLM user."), hint ? /* @__PURE__ */ React11.createElement(Typography11, { variant: "body2", color: "text.secondary" }, hint) : /* @__PURE__ */ React11.createElement(Typography11, { 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.")));
4343
+ return /* @__PURE__ */ React12.createElement(Box12, { p: 3 }, /* @__PURE__ */ React12.createElement(Paper8, { sx: { p: 3 } }, /* @__PURE__ */ React12.createElement(Typography12, { variant: "h6", gutterBottom: true }, "Account not provisioned"), /* @__PURE__ */ React12.createElement(Typography12, { color: "text.secondary", paragraph: true }, "Your Backstage account is not linked to a LiteLLM user."), hint ? /* @__PURE__ */ React12.createElement(Typography12, { variant: "body2", color: "text.secondary" }, hint) : /* @__PURE__ */ React12.createElement(Typography12, { 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.")));
4319
4344
  }
4320
- const pageTabs = /* @__PURE__ */ React11.createElement(
4345
+ const pageTabs = /* @__PURE__ */ React12.createElement(
4321
4346
  Tabs2,
4322
4347
  {
4323
4348
  value: activeTab,
@@ -4330,13 +4355,13 @@ var init_LiteLLMPage = __esm({
4330
4355
  '& [class*="MuiTab-root"]': { minHeight: 44, textTransform: "none", fontSize: 14 }
4331
4356
  }
4332
4357
  },
4333
- /* @__PURE__ */ React11.createElement(Tab2, { label: "Overview", value: "overview" }),
4334
- /* @__PURE__ */ React11.createElement(Tab2, { label: "Keys", value: "keys" }),
4335
- /* @__PURE__ */ React11.createElement(Tab2, { label: "Teams", value: "teams" }),
4336
- /* @__PURE__ */ React11.createElement(Tab2, { label: "Models", value: "models" }),
4337
- userInfo.can_view_audit && /* @__PURE__ */ React11.createElement(Tab2, { label: "Audit Log", value: "audit" })
4358
+ /* @__PURE__ */ React12.createElement(Tab2, { label: "Overview", value: "overview" }),
4359
+ /* @__PURE__ */ React12.createElement(Tab2, { label: "Keys", value: "keys" }),
4360
+ /* @__PURE__ */ React12.createElement(Tab2, { label: "Teams", value: "teams" }),
4361
+ /* @__PURE__ */ React12.createElement(Tab2, { label: "Models", value: "models" }),
4362
+ userInfo.can_view_audit && /* @__PURE__ */ React12.createElement(Tab2, { label: "Audit Log", value: "audit" })
4338
4363
  );
4339
- return /* @__PURE__ */ React11.createElement(Box11, { sx: { p: 3, display: "flex", flexDirection: "column", gap: 2 } }, /* @__PURE__ */ React11.createElement(
4364
+ return /* @__PURE__ */ React12.createElement(Box12, { sx: { p: 3, display: "flex", flexDirection: "column", gap: 2 } }, /* @__PURE__ */ React12.createElement(
4340
4365
  DashboardHeader,
4341
4366
  {
4342
4367
  userInfo,
@@ -4346,7 +4371,7 @@ var init_LiteLLMPage = __esm({
4346
4371
  onGenerateKeyClick: () => setGenerateDialogOpen(true),
4347
4372
  tabs: pageTabs
4348
4373
  }
4349
- ), activeTab === "overview" && /* @__PURE__ */ React11.createElement(Grid2, { container: true, spacing: 2, alignItems: "flex-start" }, /* @__PURE__ */ React11.createElement(Grid2, { item: true, xs: 12, lg: 8 }, /* @__PURE__ */ React11.createElement(
4374
+ ), activeTab === "overview" && /* @__PURE__ */ React12.createElement(Grid2, { container: true, spacing: 2, alignItems: "flex-start" }, /* @__PURE__ */ React12.createElement(Grid2, { item: true, xs: 12, lg: 8 }, /* @__PURE__ */ React12.createElement(
4350
4375
  UsageStats,
4351
4376
  {
4352
4377
  usage: usage ?? null,
@@ -4357,17 +4382,15 @@ var init_LiteLLMPage = __esm({
4357
4382
  loading: usageLoading,
4358
4383
  userInfo
4359
4384
  }
4360
- )), /* @__PURE__ */ React11.createElement(Grid2, { item: true, xs: 12, lg: 4 }, /* @__PURE__ */ React11.createElement(LiteLLMBudgetWidget, { compact: true, collapsible: true }))), activeTab === "keys" && /* @__PURE__ */ React11.createElement(
4385
+ )), /* @__PURE__ */ React12.createElement(Grid2, { item: true, xs: 12, lg: 4 }, /* @__PURE__ */ React12.createElement(LiteLLMBudgetWidget, { compact: true, collapsible: true }))), activeTab === "keys" && /* @__PURE__ */ React12.createElement(
4361
4386
  KeysTable,
4362
4387
  {
4363
4388
  keys: keys ?? [],
4364
- models: allowedModels,
4365
4389
  loading: keysLoading || modelsLoading,
4366
4390
  onGenerateKeyClick: () => setGenerateDialogOpen(true),
4367
- onUpdateKey: handleUpdateKey,
4391
+ onEditKey: setKeyToEdit,
4368
4392
  onBlockKey: handleBlockKey,
4369
4393
  onUnblockKey: handleUnblockKey,
4370
- onResetKeySpend: handleResetKeySpend,
4371
4394
  onDeleteKey: handleDeleteKey,
4372
4395
  onPruneExpiredKeys: handlePruneExpiredKeys
4373
4396
  }
@@ -4376,7 +4399,7 @@ var init_LiteLLMPage = __esm({
4376
4399
  for (const t of managedTeams ?? []) teamsById.set(t.team_id, t);
4377
4400
  for (const t of teams ?? []) teamsById.set(t.team_id, t);
4378
4401
  const visibleTeams = Array.from(teamsById.values());
4379
- return /* @__PURE__ */ React11.createElement(
4402
+ return /* @__PURE__ */ React12.createElement(
4380
4403
  TeamUsage,
4381
4404
  {
4382
4405
  teams: visibleTeams,
@@ -4392,27 +4415,34 @@ var init_LiteLLMPage = __esm({
4392
4415
  onCreateTeam: () => setManageTeam({ mode: "create" })
4393
4416
  }
4394
4417
  );
4395
- })(), activeTab === "models" && /* @__PURE__ */ React11.createElement(
4418
+ })(), activeTab === "models" && /* @__PURE__ */ React12.createElement(
4396
4419
  ModelsTable,
4397
4420
  {
4398
4421
  allModels: allModels ?? [],
4399
4422
  teams: teams ?? [],
4400
4423
  loading: modelsLoading
4401
4424
  }
4402
- ), activeTab === "audit" && userInfo.can_view_audit && /* @__PURE__ */ React11.createElement(AuditLog, { api }), /* @__PURE__ */ React11.createElement(
4403
- GenerateKeyDialog,
4425
+ ), activeTab === "audit" && userInfo.can_view_audit && /* @__PURE__ */ React12.createElement(AuditLog, { api }), /* @__PURE__ */ React12.createElement(
4426
+ KeyFormDialog,
4404
4427
  {
4405
- open: generateDialogOpen,
4406
- onClose: () => setGenerateDialogOpen(false),
4428
+ open: keyFormOpen,
4429
+ onClose: () => {
4430
+ setGenerateDialogOpen(false);
4431
+ setKeyToEdit(null);
4432
+ },
4433
+ mode: keyToEdit ? "edit" : "create",
4434
+ keyToEdit,
4407
4435
  keys: keys ?? [],
4408
4436
  models: allowedModels,
4409
4437
  teams: teams ?? [],
4410
4438
  username: userInfo.user_id,
4411
4439
  keyGenerationSettings: liteLlmConfig?.keyGeneration,
4412
- onGenerateKey: handleGenerateKey,
4440
+ onCreateKey: handleGenerateKey,
4441
+ onUpdateKey: handleUpdateKey,
4442
+ onResetKeySpend: handleResetKeySpend,
4413
4443
  onGetConfig: () => api.getConfig()
4414
4444
  }
4415
- ), /* @__PURE__ */ React11.createElement(
4445
+ ), /* @__PURE__ */ React12.createElement(
4416
4446
  ManageTeamDialog,
4417
4447
  {
4418
4448
  open: !!manageTeam,
@@ -4474,7 +4504,7 @@ var init_LiteLLMPage = __esm({
4474
4504
  refreshTeams();
4475
4505
  }
4476
4506
  }
4477
- ), /* @__PURE__ */ React11.createElement(
4507
+ ), /* @__PURE__ */ React12.createElement(
4478
4508
  Snackbar2,
4479
4509
  {
4480
4510
  open: !!snackbar,
@@ -4482,7 +4512,7 @@ var init_LiteLLMPage = __esm({
4482
4512
  onClose: () => setSnackbar(null),
4483
4513
  anchorOrigin: { vertical: "bottom", horizontal: "right" }
4484
4514
  },
4485
- snackbar ? /* @__PURE__ */ React11.createElement(Alert6, { severity: snackbar.severity, onClose: () => setSnackbar(null) }, snackbar.message) : void 0
4515
+ snackbar ? /* @__PURE__ */ React12.createElement(Alert6, { severity: snackbar.severity, onClose: () => setSnackbar(null) }, snackbar.message) : void 0
4486
4516
  ));
4487
4517
  };
4488
4518
  }
@@ -4490,7 +4520,7 @@ var init_LiteLLMPage = __esm({
4490
4520
 
4491
4521
  // src/plugin.tsx
4492
4522
  init_api();
4493
- import React12 from "react";
4523
+ import React13 from "react";
4494
4524
  import { TrendingUp as TrendingUpIcon } from "@mui/icons-material";
4495
4525
  import {
4496
4526
  createFrontendPlugin,
@@ -4509,10 +4539,10 @@ var liteLlmPage = PageBlueprint.make({
4509
4539
  params: {
4510
4540
  path: "/litellm",
4511
4541
  title: "LiteLLM",
4512
- icon: /* @__PURE__ */ React12.createElement(TrendingUpIcon, null),
4542
+ icon: /* @__PURE__ */ React13.createElement(TrendingUpIcon, null),
4513
4543
  loader: async () => {
4514
4544
  const { LiteLLMPage: LiteLLMPage2 } = await Promise.resolve().then(() => (init_LiteLLMPage(), LiteLLMPage_exports));
4515
- return /* @__PURE__ */ React12.createElement(LiteLLMPage2, null);
4545
+ return /* @__PURE__ */ React13.createElement(LiteLLMPage2, null);
4516
4546
  }
4517
4547
  }
4518
4548
  });
@@ -4531,10 +4561,10 @@ init_TeamUsage();
4531
4561
  // src/components/LiteLLMHomeWidget.tsx
4532
4562
  init_api();
4533
4563
  init_format();
4534
- import React13, { useState as useState10, useEffect as useEffect4 } from "react";
4535
- import Paper8 from "@mui/material/Paper";
4536
- import Box12 from "@mui/material/Box";
4537
- import Typography12 from "@mui/material/Typography";
4564
+ import React14, { useState as useState10, useEffect as useEffect5 } from "react";
4565
+ import Paper9 from "@mui/material/Paper";
4566
+ import Box13 from "@mui/material/Box";
4567
+ import Typography13 from "@mui/material/Typography";
4538
4568
  import FormControl2 from "@mui/material/FormControl";
4539
4569
  import Select2 from "@mui/material/Select";
4540
4570
  import MenuItem6 from "@mui/material/MenuItem";
@@ -4555,7 +4585,7 @@ function presetToDateRange(preset) {
4555
4585
  }
4556
4586
  return { start, end };
4557
4587
  }
4558
- var Kpi = ({ label, value }) => /* @__PURE__ */ React13.createElement(Box12, null, /* @__PURE__ */ React13.createElement(Typography12, { variant: "caption", color: "text.secondary", display: "block" }, label), /* @__PURE__ */ React13.createElement(Typography12, { variant: "subtitle1", fontWeight: 600 }, value));
4588
+ var Kpi = ({ label, value }) => /* @__PURE__ */ React14.createElement(Box13, null, /* @__PURE__ */ React14.createElement(Typography13, { variant: "caption", color: "text.secondary", display: "block" }, label), /* @__PURE__ */ React14.createElement(Typography13, { variant: "subtitle1", fontWeight: 600 }, value));
4559
4589
  var LiteLLMHomeWidget = ({
4560
4590
  defaultPeriod = "7d",
4561
4591
  title = "LiteLLM Usage"
@@ -4567,7 +4597,7 @@ var LiteLLMHomeWidget = ({
4567
4597
  const [keysError, setKeysError] = useState10(null);
4568
4598
  const [usage, setUsage] = useState10(null);
4569
4599
  const [keys, setKeys] = useState10([]);
4570
- useEffect4(() => {
4600
+ useEffect5(() => {
4571
4601
  let cancelled = false;
4572
4602
  setLoading(true);
4573
4603
  setUsageError(null);
@@ -4604,17 +4634,17 @@ var LiteLLMHomeWidget = ({
4604
4634
  spend: d.spend
4605
4635
  }));
4606
4636
  const hasSparkline = dailyData.length > 0;
4607
- return /* @__PURE__ */ React13.createElement(Paper8, { sx: { p: 2 } }, /* @__PURE__ */ React13.createElement(Box12, { display: "flex", justifyContent: "space-between", alignItems: "center", mb: 1.5 }, /* @__PURE__ */ React13.createElement(Typography12, { variant: "h6" }, title), /* @__PURE__ */ React13.createElement(FormControl2, { size: "small", sx: { minWidth: 90 } }, /* @__PURE__ */ React13.createElement(
4637
+ return /* @__PURE__ */ React14.createElement(Paper9, { sx: { p: 2 } }, /* @__PURE__ */ React14.createElement(Box13, { display: "flex", justifyContent: "space-between", alignItems: "center", mb: 1.5 }, /* @__PURE__ */ React14.createElement(Typography13, { variant: "h6" }, title), /* @__PURE__ */ React14.createElement(FormControl2, { size: "small", sx: { minWidth: 90 } }, /* @__PURE__ */ React14.createElement(
4608
4638
  Select2,
4609
4639
  {
4610
4640
  value: period,
4611
4641
  onChange: (e) => setPeriod(e.target.value),
4612
4642
  displayEmpty: true
4613
4643
  },
4614
- /* @__PURE__ */ React13.createElement(MenuItem6, { value: "today" }, "Today"),
4615
- /* @__PURE__ */ React13.createElement(MenuItem6, { value: "7d" }, "7d"),
4616
- /* @__PURE__ */ React13.createElement(MenuItem6, { value: "30d" }, "30d")
4617
- ))), loading && /* @__PURE__ */ React13.createElement(Box12, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: 120 }, /* @__PURE__ */ React13.createElement(CircularProgress7, { size: 32 })), !loading && totalFailure && /* @__PURE__ */ React13.createElement(Alert7, { severity: "error", sx: { mt: 1 } }, usageError ?? "Failed to load usage data"), !loading && !totalFailure && /* @__PURE__ */ React13.createElement(React13.Fragment, null, partialFailure && /* @__PURE__ */ React13.createElement(Alert7, { severity: "warning", sx: { mt: 1, mb: 1 } }, usageError ? `Usage data unavailable (${usageError}).` : "", keysError ? ` Key list unavailable (${keysError}).` : "", " Showing what loaded."), /* @__PURE__ */ React13.createElement(Grid3, { container: true, spacing: 2, sx: { mb: hasSparkline ? 1.5 : 0 } }, /* @__PURE__ */ React13.createElement(Grid3, { item: true, xs: 6 }, /* @__PURE__ */ React13.createElement(Kpi, { label: "USD Spent", value: fmtUsd(usage?.total_spend ?? 0) })), /* @__PURE__ */ React13.createElement(Grid3, { item: true, xs: 6 }, /* @__PURE__ */ React13.createElement(Kpi, { label: "Tokens In", value: fmtInt(usage?.prompt_tokens ?? 0) })), /* @__PURE__ */ React13.createElement(Grid3, { item: true, xs: 6 }, /* @__PURE__ */ React13.createElement(Kpi, { label: "Tokens Out", value: fmtInt(usage?.completion_tokens ?? 0) })), /* @__PURE__ */ React13.createElement(Grid3, { item: true, xs: 6 }, /* @__PURE__ */ React13.createElement(Kpi, { label: "Keys", value: fmtInt(keys.length) }))), hasSparkline && /* @__PURE__ */ React13.createElement(Box12, { height: 120 }, /* @__PURE__ */ React13.createElement(ResponsiveContainer3, { width: "100%", height: "100%" }, /* @__PURE__ */ React13.createElement(AreaChart3, { data: dailyData, margin: { top: 4, right: 0, bottom: 0, left: 0 } }, /* @__PURE__ */ React13.createElement(
4644
+ /* @__PURE__ */ React14.createElement(MenuItem6, { value: "today" }, "Today"),
4645
+ /* @__PURE__ */ React14.createElement(MenuItem6, { value: "7d" }, "7d"),
4646
+ /* @__PURE__ */ React14.createElement(MenuItem6, { value: "30d" }, "30d")
4647
+ ))), loading && /* @__PURE__ */ React14.createElement(Box13, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: 120 }, /* @__PURE__ */ React14.createElement(CircularProgress7, { size: 32 })), !loading && totalFailure && /* @__PURE__ */ React14.createElement(Alert7, { severity: "error", sx: { mt: 1 } }, usageError ?? "Failed to load usage data"), !loading && !totalFailure && /* @__PURE__ */ React14.createElement(React14.Fragment, null, partialFailure && /* @__PURE__ */ React14.createElement(Alert7, { severity: "warning", sx: { mt: 1, mb: 1 } }, usageError ? `Usage data unavailable (${usageError}).` : "", keysError ? ` Key list unavailable (${keysError}).` : "", " Showing what loaded."), /* @__PURE__ */ React14.createElement(Grid3, { container: true, spacing: 2, sx: { mb: hasSparkline ? 1.5 : 0 } }, /* @__PURE__ */ React14.createElement(Grid3, { item: true, xs: 6 }, /* @__PURE__ */ React14.createElement(Kpi, { label: "USD Spent", value: fmtUsd(usage?.total_spend ?? 0) })), /* @__PURE__ */ React14.createElement(Grid3, { item: true, xs: 6 }, /* @__PURE__ */ React14.createElement(Kpi, { label: "Tokens In", value: fmtInt(usage?.prompt_tokens ?? 0) })), /* @__PURE__ */ React14.createElement(Grid3, { item: true, xs: 6 }, /* @__PURE__ */ React14.createElement(Kpi, { label: "Tokens Out", value: fmtInt(usage?.completion_tokens ?? 0) })), /* @__PURE__ */ React14.createElement(Grid3, { item: true, xs: 6 }, /* @__PURE__ */ React14.createElement(Kpi, { label: "Keys", value: fmtInt(keys.length) }))), hasSparkline && /* @__PURE__ */ React14.createElement(Box13, { height: 120 }, /* @__PURE__ */ React14.createElement(ResponsiveContainer3, { width: "100%", height: "100%" }, /* @__PURE__ */ React14.createElement(AreaChart3, { data: dailyData, margin: { top: 4, right: 0, bottom: 0, left: 0 } }, /* @__PURE__ */ React14.createElement(
4618
4648
  Area3,
4619
4649
  {
4620
4650
  type: "monotone",
@@ -4635,21 +4665,31 @@ init_LiteLLMBudgetWidget();
4635
4665
  init_api();
4636
4666
  init_format();
4637
4667
  init_ui();
4668
+ init_BudgetLimitList();
4638
4669
  init_budget();
4639
- import React14, { useEffect as useEffect5, useMemo as useMemo10, useState as useState11 } from "react";
4640
- import Paper9 from "@mui/material/Paper";
4641
- import Box13 from "@mui/material/Box";
4642
- import Typography13 from "@mui/material/Typography";
4670
+ import React15, { useCallback as useCallback3, useEffect as useEffect6, useMemo as useMemo10, useState as useState11 } from "react";
4671
+ import Paper10 from "@mui/material/Paper";
4672
+ import Box14 from "@mui/material/Box";
4673
+ import Button6 from "@mui/material/Button";
4674
+ import Typography14 from "@mui/material/Typography";
4643
4675
  import CircularProgress8 from "@mui/material/CircularProgress";
4644
4676
  import Alert8 from "@mui/material/Alert";
4677
+ import Collapse4 from "@mui/material/Collapse";
4678
+ import { ExpandMore as ExpandMore4 } from "@mui/icons-material";
4645
4679
  import { useApi as useApi5 } from "@backstage/core-plugin-api";
4646
4680
  import { Link as Link2 } from "@backstage/core-components";
4647
- var DEFAULT_KEYS_HREF = "/litellm?tab=keys";
4681
+ var MODULE_PATH = "/litellm";
4648
4682
  var LEVEL = {
4649
4683
  key: { name: "Key", none: "No key budget" },
4650
4684
  user: { name: "User", none: "No personal budget" },
4651
4685
  team: { name: "Team", none: "No team budget" }
4652
4686
  };
4687
+ var DEFAULT_CTAS = ["new-key", "module", "all-limits"];
4688
+ var CTA_LABELS = {
4689
+ "new-key": "New key",
4690
+ module: "Open module",
4691
+ "all-limits": "All limits"
4692
+ };
4653
4693
  function fmtUsdShort(n) {
4654
4694
  const v = n ?? 0;
4655
4695
  if (v >= 100 && Number.isInteger(v)) return `$${v}`;
@@ -4672,8 +4712,8 @@ var LevelGauge = ({
4672
4712
  const limit = gauge.limit;
4673
4713
  const tone = limit ? budgetTone2(limit.pct) : "neutral";
4674
4714
  const extra = gauge.kind === "key" ? Math.max(0, gauge.count - 1) : 0;
4675
- return /* @__PURE__ */ React14.createElement(
4676
- Box13,
4715
+ return /* @__PURE__ */ React15.createElement(
4716
+ Box14,
4677
4717
  {
4678
4718
  sx: {
4679
4719
  display: "flex",
@@ -4683,7 +4723,7 @@ var LevelGauge = ({
4683
4723
  flex: 1
4684
4724
  }
4685
4725
  },
4686
- /* @__PURE__ */ React14.createElement(
4726
+ /* @__PURE__ */ React15.createElement(
4687
4727
  Gauge,
4688
4728
  {
4689
4729
  value: limit?.pct ?? 0,
@@ -4692,8 +4732,8 @@ var LevelGauge = ({
4692
4732
  label: limit ? `${Math.round(limit.pct)}%` : "\u2014"
4693
4733
  }
4694
4734
  ),
4695
- /* @__PURE__ */ React14.createElement(
4696
- Typography13,
4735
+ /* @__PURE__ */ React15.createElement(
4736
+ Typography14,
4697
4737
  {
4698
4738
  sx: (theme) => ({
4699
4739
  mt: 0.75,
@@ -4706,8 +4746,8 @@ var LevelGauge = ({
4706
4746
  },
4707
4747
  name
4708
4748
  ),
4709
- limit ? /* @__PURE__ */ React14.createElement(React14.Fragment, null, /* @__PURE__ */ React14.createElement(
4710
- Typography13,
4749
+ limit ? /* @__PURE__ */ React15.createElement(React15.Fragment, null, /* @__PURE__ */ React15.createElement(
4750
+ Typography14,
4711
4751
  {
4712
4752
  variant: "caption",
4713
4753
  title: limit.sublabel ? `${limit.label} \xB7 ${limit.sublabel}` : limit.label,
@@ -4722,8 +4762,8 @@ var LevelGauge = ({
4722
4762
  }
4723
4763
  },
4724
4764
  limit.label
4725
- ), /* @__PURE__ */ React14.createElement(
4726
- Typography13,
4765
+ ), /* @__PURE__ */ React15.createElement(
4766
+ Typography14,
4727
4767
  {
4728
4768
  variant: "caption",
4729
4769
  color: "text.secondary",
@@ -4734,8 +4774,8 @@ var LevelGauge = ({
4734
4774
  }
4735
4775
  },
4736
4776
  spendCaption(limit)
4737
- ), extra > 0 ? /* @__PURE__ */ React14.createElement(
4738
- Typography13,
4777
+ ), extra > 0 ? /* @__PURE__ */ React15.createElement(
4778
+ Typography14,
4739
4779
  {
4740
4780
  component: Link2,
4741
4781
  to: keysHref,
@@ -4750,8 +4790,8 @@ var LevelGauge = ({
4750
4790
  "+",
4751
4791
  extra,
4752
4792
  " more"
4753
- ) : /* @__PURE__ */ React14.createElement(Typography13, { variant: "caption", color: "text.secondary", sx: { fontSize: 10.5 } }, resetLabel(limit))) : /* @__PURE__ */ React14.createElement(
4754
- Typography13,
4793
+ ) : /* @__PURE__ */ React15.createElement(Typography14, { variant: "caption", color: "text.secondary", sx: { fontSize: 10.5 } }, resetLabel(limit))) : /* @__PURE__ */ React15.createElement(
4794
+ Typography14,
4755
4795
  {
4756
4796
  variant: "caption",
4757
4797
  color: "text.secondary",
@@ -4764,7 +4804,14 @@ var LevelGauge = ({
4764
4804
  var LiteLLMBudgetGauges = ({
4765
4805
  title = "Budget",
4766
4806
  size = 72,
4767
- keysHref = DEFAULT_KEYS_HREF,
4807
+ keysHref,
4808
+ moduleHref = MODULE_PATH,
4809
+ onCreateKey,
4810
+ ctas,
4811
+ maxExpandedKeys = 8,
4812
+ expanded,
4813
+ defaultExpanded = false,
4814
+ onExpandedChange,
4768
4815
  action
4769
4816
  }) => {
4770
4817
  const api = useApi5(liteLlmApiRef);
@@ -4773,7 +4820,16 @@ var LiteLLMBudgetGauges = ({
4773
4820
  const [user, setUser] = useState11(null);
4774
4821
  const [teams, setTeams] = useState11([]);
4775
4822
  const [keys, setKeys] = useState11([]);
4776
- useEffect5(() => {
4823
+ const isControlled = expanded !== void 0;
4824
+ const [uncontrolledExpanded, setUncontrolledExpanded] = useState11(defaultExpanded);
4825
+ const isExpanded = isControlled ? expanded : uncontrolledExpanded;
4826
+ const toggleExpanded = useCallback3(() => {
4827
+ const next = !isExpanded;
4828
+ if (!isControlled) setUncontrolledExpanded(next);
4829
+ onExpandedChange?.(next);
4830
+ }, [isControlled, isExpanded, onExpandedChange]);
4831
+ const keysTabHref = keysHref ?? `${moduleHref}?tab=keys`;
4832
+ useEffect6(() => {
4777
4833
  let cancelled = false;
4778
4834
  setLoading(true);
4779
4835
  setError(null);
@@ -4803,35 +4859,139 @@ var LiteLLMBudgetGauges = ({
4803
4859
  );
4804
4860
  const gauges = useMemo10(() => buildBudgetGauges(summary), [summary]);
4805
4861
  const headline = useMemo10(() => budgetHeadline(summary), [summary]);
4862
+ const expandedSummary = useMemo10(
4863
+ () => buildBudgetSummary(user, teams, keys, maxExpandedKeys),
4864
+ [user, teams, keys, maxExpandedKeys]
4865
+ );
4866
+ const expandedLimits = useMemo10(
4867
+ () => allBudgetLimits(expandedSummary),
4868
+ [expandedSummary]
4869
+ );
4806
4870
  const summaryText = headline.count === 0 ? "no limits apply" : `${headline.count} limit${headline.count === 1 ? "" : "s"}`;
4807
- return /* @__PURE__ */ React14.createElement(Paper9, { sx: { p: 2 } }, /* @__PURE__ */ React14.createElement(Box13, { sx: { display: "flex", alignItems: "center", gap: 1, mb: 1.5, minWidth: 0 } }, /* @__PURE__ */ React14.createElement(Typography13, { variant: "h6", lineHeight: 1.2, sx: { minWidth: 0, flex: 1 } }, title), !loading && !error && /* @__PURE__ */ React14.createElement(
4871
+ const resolvedCtas = useMemo10(() => {
4872
+ const list = ctas ?? DEFAULT_CTAS;
4873
+ return list.map((cta) => typeof cta === "string" ? { kind: cta } : cta).filter((cta) => cta.kind !== "all-limits" || headline.count > 0);
4874
+ }, [ctas, headline.count]);
4875
+ const renderCta = (cta, index) => {
4876
+ const label = cta.label ?? CTA_LABELS[cta.kind];
4877
+ const key = `${cta.kind}-${index}`;
4878
+ switch (cta.kind) {
4879
+ case "new-key":
4880
+ return onCreateKey ? /* @__PURE__ */ React15.createElement(Button6, { key, size: "small", variant: "contained", onClick: onCreateKey }, label) : /* @__PURE__ */ React15.createElement(
4881
+ Button6,
4882
+ {
4883
+ key,
4884
+ size: "small",
4885
+ variant: "contained",
4886
+ component: Link2,
4887
+ to: `${moduleHref}?generate=1`
4888
+ },
4889
+ label
4890
+ );
4891
+ case "module":
4892
+ return /* @__PURE__ */ React15.createElement(Button6, { key, size: "small", variant: "outlined", component: Link2, to: moduleHref }, label);
4893
+ case "all-limits":
4894
+ return /* @__PURE__ */ React15.createElement(
4895
+ Button6,
4896
+ {
4897
+ key,
4898
+ size: "small",
4899
+ variant: "text",
4900
+ onClick: toggleExpanded,
4901
+ endIcon: /* @__PURE__ */ React15.createElement(
4902
+ ExpandMore4,
4903
+ {
4904
+ sx: {
4905
+ transform: isExpanded ? "rotate(180deg)" : "none",
4906
+ transition: (theme) => theme.transitions.create("transform")
4907
+ }
4908
+ }
4909
+ )
4910
+ },
4911
+ label
4912
+ );
4913
+ /* istanbul ignore next — exhaustiveness */
4914
+ default:
4915
+ return null;
4916
+ }
4917
+ };
4918
+ const hasFooter = resolvedCtas.length > 0 || !!action;
4919
+ return /* @__PURE__ */ React15.createElement(Paper10, { sx: { p: 2 } }, /* @__PURE__ */ React15.createElement(Box14, { sx: { display: "flex", alignItems: "center", gap: 1, mb: 1.5, minWidth: 0 } }, /* @__PURE__ */ React15.createElement(Typography14, { variant: "h6", lineHeight: 1.2, sx: { minWidth: 0, flex: 1 } }, title), !loading && !error && /* @__PURE__ */ React15.createElement(
4808
4920
  StatusPill,
4809
4921
  {
4810
4922
  label: summaryText,
4811
4923
  tone: budgetTone2(headline.closest?.pct ?? 0),
4812
4924
  dot: headline.count > 0
4813
4925
  }
4814
- )), loading && /* @__PURE__ */ React14.createElement(Box13, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: 110 }, /* @__PURE__ */ React14.createElement(CircularProgress8, { size: 28 })), !loading && error && /* @__PURE__ */ React14.createElement(Alert8, { severity: "error", sx: { mt: 0.5 } }, error), !loading && !error && /* @__PURE__ */ React14.createElement(Box13, { sx: { display: "flex", alignItems: "flex-start", gap: 1 } }, gauges.map((gauge) => /* @__PURE__ */ React14.createElement(LevelGauge, { key: gauge.kind, gauge, size, keysHref }))), action && /* @__PURE__ */ React14.createElement(
4815
- Box13,
4926
+ )), loading && /* @__PURE__ */ React15.createElement(Box14, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: 110 }, /* @__PURE__ */ React15.createElement(CircularProgress8, { size: 28 })), !loading && error && /* @__PURE__ */ React15.createElement(Alert8, { severity: "error", sx: { mt: 0.5 } }, error), !loading && !error && /* @__PURE__ */ React15.createElement(React15.Fragment, null, /* @__PURE__ */ React15.createElement(Box14, { sx: { display: "flex", alignItems: "flex-start", gap: 1 } }, gauges.map((gauge) => /* @__PURE__ */ React15.createElement(LevelGauge, { key: gauge.kind, gauge, size, keysHref: keysTabHref }))), /* @__PURE__ */ React15.createElement(Collapse4, { in: isExpanded, unmountOnExit: true }, /* @__PURE__ */ React15.createElement(
4927
+ Box14,
4928
+ {
4929
+ sx: {
4930
+ mt: 1.5,
4931
+ // Bound the expanded list so a user with many limits still
4932
+ // keeps the card compact and the toggle within reach.
4933
+ maxHeight: 300,
4934
+ overflowY: "auto"
4935
+ }
4936
+ },
4937
+ /* @__PURE__ */ React15.createElement(LimitListPanel, null, /* @__PURE__ */ React15.createElement(
4938
+ BudgetLimitList,
4939
+ {
4940
+ limits: expandedLimits,
4941
+ hiddenKeyCount: expandedSummary.hiddenBudgetedKeys,
4942
+ keysHref: keysTabHref
4943
+ }
4944
+ ))
4945
+ ))), hasFooter && /* @__PURE__ */ React15.createElement(
4946
+ Box14,
4816
4947
  {
4817
4948
  sx: (theme) => ({
4818
4949
  mt: 2,
4819
4950
  pt: 2,
4820
- borderTop: `1px solid ${theme.palette.divider}`
4951
+ borderTop: `1px solid ${theme.palette.divider}`,
4952
+ display: "flex",
4953
+ alignItems: "center",
4954
+ gap: 1,
4955
+ flexWrap: "wrap"
4821
4956
  })
4822
4957
  },
4958
+ resolvedCtas.map(renderCta),
4823
4959
  action
4824
4960
  ));
4825
4961
  };
4826
4962
 
4827
4963
  // src/index.ts
4828
- init_GenerateKeyDialog();
4964
+ init_KeyFormDialog();
4965
+
4966
+ // src/components/GenerateKeyDialog.tsx
4967
+ init_KeyFormDialog();
4968
+ import React16 from "react";
4969
+ var GenerateKeyDialog = (props) => /* @__PURE__ */ React16.createElement(
4970
+ KeyFormDialog,
4971
+ {
4972
+ open: props.open,
4973
+ onClose: props.onClose,
4974
+ mode: "create",
4975
+ keys: props.keys,
4976
+ models: props.models,
4977
+ teams: props.teams,
4978
+ username: props.username,
4979
+ keyGenerationSettings: props.keyGenerationSettings,
4980
+ onCreateKey: props.onGenerateKey,
4981
+ onUpdateKey: async () => {
4982
+ },
4983
+ onGetConfig: props.onGetConfig
4984
+ }
4985
+ );
4986
+
4987
+ // src/index.ts
4829
4988
  init_ManageTeamDialog();
4830
4989
  init_api();
4831
4990
  init_permissions();
4832
4991
  export {
4833
4992
  DashboardHeader,
4834
4993
  GenerateKeyDialog,
4994
+ KeyFormDialog,
4835
4995
  KeysTable,
4836
4996
  LiteLLMBudgetGauges,
4837
4997
  LiteLLMBudgetWidget,