@acarmisc/backstage-plugin-litellm 0.25.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,
@@ -4121,7 +4083,7 @@ var init_LiteLLMPage = __esm({
4121
4083
  "use strict";
4122
4084
  init_DashboardHeader();
4123
4085
  init_KeysTable();
4124
- init_GenerateKeyDialog();
4086
+ init_KeyFormDialog();
4125
4087
  init_ManageTeamDialog();
4126
4088
  init_UsageStats();
4127
4089
  init_LiteLLMBudgetWidget();
@@ -4179,6 +4141,8 @@ var init_LiteLLMPage = __esm({
4179
4141
  );
4180
4142
  const [snackbar, setSnackbar] = useState9(null);
4181
4143
  const [manageTeam, setManageTeam] = useState9(null);
4144
+ const [keyToEdit, setKeyToEdit] = useState9(null);
4145
+ const keyFormOpen = generateDialogOpen || !!keyToEdit;
4182
4146
  const [teamUsageCache, setTeamUsageCache] = useState9({});
4183
4147
  const [teamUsageLoading, setTeamUsageLoading] = useState9({});
4184
4148
  const { value: userInfo, loading: userLoading, error: userError } = useAsync3(
@@ -4422,13 +4386,11 @@ var init_LiteLLMPage = __esm({
4422
4386
  KeysTable,
4423
4387
  {
4424
4388
  keys: keys ?? [],
4425
- models: allowedModels,
4426
4389
  loading: keysLoading || modelsLoading,
4427
4390
  onGenerateKeyClick: () => setGenerateDialogOpen(true),
4428
- onUpdateKey: handleUpdateKey,
4391
+ onEditKey: setKeyToEdit,
4429
4392
  onBlockKey: handleBlockKey,
4430
4393
  onUnblockKey: handleUnblockKey,
4431
- onResetKeySpend: handleResetKeySpend,
4432
4394
  onDeleteKey: handleDeleteKey,
4433
4395
  onPruneExpiredKeys: handlePruneExpiredKeys
4434
4396
  }
@@ -4461,16 +4423,23 @@ var init_LiteLLMPage = __esm({
4461
4423
  loading: modelsLoading
4462
4424
  }
4463
4425
  ), activeTab === "audit" && userInfo.can_view_audit && /* @__PURE__ */ React12.createElement(AuditLog, { api }), /* @__PURE__ */ React12.createElement(
4464
- GenerateKeyDialog,
4426
+ KeyFormDialog,
4465
4427
  {
4466
- open: generateDialogOpen,
4467
- onClose: () => setGenerateDialogOpen(false),
4428
+ open: keyFormOpen,
4429
+ onClose: () => {
4430
+ setGenerateDialogOpen(false);
4431
+ setKeyToEdit(null);
4432
+ },
4433
+ mode: keyToEdit ? "edit" : "create",
4434
+ keyToEdit,
4468
4435
  keys: keys ?? [],
4469
4436
  models: allowedModels,
4470
4437
  teams: teams ?? [],
4471
4438
  username: userInfo.user_id,
4472
4439
  keyGenerationSettings: liteLlmConfig?.keyGeneration,
4473
- onGenerateKey: handleGenerateKey,
4440
+ onCreateKey: handleGenerateKey,
4441
+ onUpdateKey: handleUpdateKey,
4442
+ onResetKeySpend: handleResetKeySpend,
4474
4443
  onGetConfig: () => api.getConfig()
4475
4444
  }
4476
4445
  ), /* @__PURE__ */ React12.createElement(
@@ -4992,13 +4961,37 @@ var LiteLLMBudgetGauges = ({
4992
4961
  };
4993
4962
 
4994
4963
  // src/index.ts
4995
- 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
4996
4988
  init_ManageTeamDialog();
4997
4989
  init_api();
4998
4990
  init_permissions();
4999
4991
  export {
5000
4992
  DashboardHeader,
5001
4993
  GenerateKeyDialog,
4994
+ KeyFormDialog,
5002
4995
  KeysTable,
5003
4996
  LiteLLMBudgetGauges,
5004
4997
  LiteLLMBudgetWidget,