@acarmisc/backstage-plugin-litellm 0.15.1 → 0.15.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs.js CHANGED
@@ -913,7 +913,25 @@ function formatContextWindow(maxInput, maxOutput) {
913
913
  if (outPart) return `ctx ${outPart} out`;
914
914
  return null;
915
915
  }
916
- var import_react3, import_Table, import_TableBody, import_TableCell, import_TableContainer, import_TableHead, import_TableRow, import_Button2, import_IconButton, import_Box3, import_Typography3, import_Dialog, import_DialogTitle, import_DialogContent, import_DialogActions, import_TextField, import_CircularProgress, import_Autocomplete, import_Skeleton2, import_InputAdornment, import_styles3, import_icons_material2, maskKey, shortKeyId, formatDate, keyToEditForm, KeysTable;
916
+ function compareKeys(a, b, sortKey) {
917
+ switch (sortKey) {
918
+ case "alias":
919
+ return (a.key_alias ?? "").localeCompare(b.key_alias ?? "");
920
+ case "created":
921
+ return a.created_at.localeCompare(b.created_at);
922
+ case "expires":
923
+ return (a.expires_at ?? "").localeCompare(b.expires_at ?? "");
924
+ case "budget":
925
+ return (a.max_budget ?? Infinity) - (b.max_budget ?? Infinity);
926
+ case "limits":
927
+ return (a.tpm_limit ?? Infinity) - (b.tpm_limit ?? Infinity) || (a.rpm_limit ?? Infinity) - (b.rpm_limit ?? Infinity);
928
+ case "models":
929
+ return (a.models ?? []).join(",").localeCompare((b.models ?? []).join(","));
930
+ default:
931
+ return 0;
932
+ }
933
+ }
934
+ var import_react3, import_Table, import_TableBody, import_TableCell, import_TableContainer, import_TableHead, import_TableRow, import_TableSortLabel, import_Button2, import_IconButton, import_Box3, import_Typography3, import_Dialog, import_DialogTitle, import_DialogContent, import_DialogActions, import_TextField, import_CircularProgress, import_Autocomplete, import_Skeleton2, import_InputAdornment, import_styles3, import_icons_material2, maskKey, shortKeyId, formatDate, keyToEditForm, KeysTable;
917
935
  var init_KeysTable = __esm({
918
936
  "src/components/KeysTable.tsx"() {
919
937
  "use strict";
@@ -924,6 +942,7 @@ var init_KeysTable = __esm({
924
942
  import_TableContainer = __toESM(require("@mui/material/TableContainer"));
925
943
  import_TableHead = __toESM(require("@mui/material/TableHead"));
926
944
  import_TableRow = __toESM(require("@mui/material/TableRow"));
945
+ import_TableSortLabel = __toESM(require("@mui/material/TableSortLabel"));
927
946
  import_Button2 = __toESM(require("@mui/material/Button"));
928
947
  import_IconButton = __toESM(require("@mui/material/IconButton"));
929
948
  import_Box3 = __toESM(require("@mui/material/Box"));
@@ -985,13 +1004,29 @@ var init_KeysTable = __esm({
985
1004
  const [resetSpendConfirm, setResetSpendConfirm] = (0, import_react3.useState)(false);
986
1005
  const [resetSpendSubmitting, setResetSpendSubmitting] = (0, import_react3.useState)(false);
987
1006
  const [filterText, setFilterText] = (0, import_react3.useState)("");
1007
+ const [sortKey, setSortKey] = (0, import_react3.useState)("created");
1008
+ const [sortDirection, setSortDirection] = (0, import_react3.useState)("desc");
1009
+ const handleSort = (nextSortKey) => {
1010
+ if (sortKey === nextSortKey) {
1011
+ setSortDirection((current) => current === "asc" ? "desc" : "asc");
1012
+ } else {
1013
+ setSortKey(nextSortKey);
1014
+ setSortDirection("asc");
1015
+ }
1016
+ };
988
1017
  const filteredKeys = (0, import_react3.useMemo)(() => {
989
- if (!filterText.trim()) return keys;
990
- const q = filterText.toLowerCase();
991
- return keys.filter(
992
- (k) => (k.key_alias ?? "").toLowerCase().includes(q) || k.models?.some((m) => m.toLowerCase().includes(q))
993
- );
994
- }, [keys, filterText]);
1018
+ let result = keys;
1019
+ if (filterText.trim()) {
1020
+ const q = filterText.toLowerCase();
1021
+ result = keys.filter(
1022
+ (k) => (k.key_alias ?? "").toLowerCase().includes(q) || k.models?.some((m) => m.toLowerCase().includes(q))
1023
+ );
1024
+ }
1025
+ return [...result].sort((a, b) => {
1026
+ const comparison = compareKeys(a, b, sortKey);
1027
+ return sortDirection === "asc" ? comparison : -comparison;
1028
+ });
1029
+ }, [keys, filterText, sortKey, sortDirection]);
995
1030
  const editSelectedModels = models.filter((m) => (editForm.models || []).includes(m.model_name));
996
1031
  const handleOpenEdit = (k) => {
997
1032
  setEditingKey(k);
@@ -1121,7 +1156,7 @@ var init_KeysTable = __esm({
1121
1156
  "& td": { color: theme.palette.text.secondary }
1122
1157
  }) : void 0
1123
1158
  },
1124
- /* @__PURE__ */ import_react3.default.createElement(import_TableCell.default, null, /* @__PURE__ */ import_react3.default.createElement(import_Box3.default, { display: "flex", alignItems: "center", gap: 1, flexWrap: "wrap" }, /* @__PURE__ */ import_react3.default.createElement(import_Typography3.default, { variant: "body2", sx: { fontWeight: 600 } }, key.key_alias || "\u2014"), key.blocked && /* @__PURE__ */ import_react3.default.createElement(StatusPill, { label: "Blocked", tone: "danger" }))),
1159
+ /* @__PURE__ */ import_react3.default.createElement(import_TableCell.default, null, /* @__PURE__ */ import_react3.default.createElement(import_Box3.default, { display: "flex", alignItems: "center", gap: 1, flexWrap: "wrap" }, /* @__PURE__ */ import_react3.default.createElement(import_Typography3.default, { variant: "body2" }, key.key_alias || "\u2014"), key.blocked && /* @__PURE__ */ import_react3.default.createElement(StatusPill, { label: "Blocked", tone: "danger" }))),
1125
1160
  /* @__PURE__ */ import_react3.default.createElement(import_TableCell.default, null, /* @__PURE__ */ import_react3.default.createElement(import_Box3.default, { display: "flex", alignItems: "center", gap: 0.5 }, /* @__PURE__ */ import_react3.default.createElement(
1126
1161
  import_Typography3.default,
1127
1162
  {
@@ -1203,7 +1238,8 @@ var init_KeysTable = __esm({
1203
1238
  import_TextField.default,
1204
1239
  {
1205
1240
  size: "small",
1206
- placeholder: "Filter by alias or model\u2026",
1241
+ label: "Search alias or model",
1242
+ placeholder: "Search alias or model\u2026",
1207
1243
  value: filterText,
1208
1244
  onChange: (e) => setFilterText(e.target.value),
1209
1245
  sx: { minWidth: 240 },
@@ -1243,7 +1279,7 @@ var init_KeysTable = __esm({
1243
1279
  "Generate New Key"
1244
1280
  ))
1245
1281
  },
1246
- /* @__PURE__ */ import_react3.default.createElement(import_TableContainer.default, null, /* @__PURE__ */ import_react3.default.createElement(import_Table.default, { size: "small", sx: dataTableSx }, /* @__PURE__ */ import_react3.default.createElement(import_TableHead.default, null, /* @__PURE__ */ import_react3.default.createElement(import_TableRow.default, null, /* @__PURE__ */ import_react3.default.createElement(import_TableCell.default, null, "Alias"), /* @__PURE__ */ import_react3.default.createElement(import_TableCell.default, null, "Key ID"), /* @__PURE__ */ import_react3.default.createElement(import_TableCell.default, null, "Created"), /* @__PURE__ */ import_react3.default.createElement(import_TableCell.default, null, "Expires"), /* @__PURE__ */ import_react3.default.createElement(import_TableCell.default, null, "Budget"), /* @__PURE__ */ import_react3.default.createElement(import_TableCell.default, null, "TPM / RPM"), /* @__PURE__ */ import_react3.default.createElement(import_TableCell.default, null, "Models"), /* @__PURE__ */ import_react3.default.createElement(import_TableCell.default, { align: "right" }, "Actions"))), /* @__PURE__ */ import_react3.default.createElement(import_TableBody.default, null, renderTableBody())))
1282
+ /* @__PURE__ */ import_react3.default.createElement(import_TableContainer.default, null, /* @__PURE__ */ import_react3.default.createElement(import_Table.default, { size: "small", sx: dataTableSx }, /* @__PURE__ */ import_react3.default.createElement(import_TableHead.default, null, /* @__PURE__ */ import_react3.default.createElement(import_TableRow.default, null, /* @__PURE__ */ import_react3.default.createElement(import_TableCell.default, { sortDirection: sortKey === "alias" ? sortDirection : false }, /* @__PURE__ */ import_react3.default.createElement(import_TableSortLabel.default, { active: sortKey === "alias", direction: sortKey === "alias" ? sortDirection : "asc", onClick: () => handleSort("alias") }, "Alias")), /* @__PURE__ */ import_react3.default.createElement(import_TableCell.default, null, "Key ID"), /* @__PURE__ */ import_react3.default.createElement(import_TableCell.default, { sortDirection: sortKey === "created" ? sortDirection : false }, /* @__PURE__ */ import_react3.default.createElement(import_TableSortLabel.default, { active: sortKey === "created", direction: sortKey === "created" ? sortDirection : "asc", onClick: () => handleSort("created") }, "Created")), /* @__PURE__ */ import_react3.default.createElement(import_TableCell.default, { sortDirection: sortKey === "expires" ? sortDirection : false }, /* @__PURE__ */ import_react3.default.createElement(import_TableSortLabel.default, { active: sortKey === "expires", direction: sortKey === "expires" ? sortDirection : "asc", onClick: () => handleSort("expires") }, "Expires")), /* @__PURE__ */ import_react3.default.createElement(import_TableCell.default, { sortDirection: sortKey === "budget" ? sortDirection : false }, /* @__PURE__ */ import_react3.default.createElement(import_TableSortLabel.default, { active: sortKey === "budget", direction: sortKey === "budget" ? sortDirection : "asc", onClick: () => handleSort("budget") }, "Budget")), /* @__PURE__ */ import_react3.default.createElement(import_TableCell.default, { sortDirection: sortKey === "limits" ? sortDirection : false }, /* @__PURE__ */ import_react3.default.createElement(import_TableSortLabel.default, { active: sortKey === "limits", direction: sortKey === "limits" ? sortDirection : "asc", onClick: () => handleSort("limits") }, "TPM / RPM")), /* @__PURE__ */ import_react3.default.createElement(import_TableCell.default, { sortDirection: sortKey === "models" ? sortDirection : false }, /* @__PURE__ */ import_react3.default.createElement(import_TableSortLabel.default, { active: sortKey === "models", direction: sortKey === "models" ? sortDirection : "asc", onClick: () => handleSort("models") }, "Models")), /* @__PURE__ */ import_react3.default.createElement(import_TableCell.default, { align: "right" }, "Actions"))), /* @__PURE__ */ import_react3.default.createElement(import_TableBody.default, null, renderTableBody())))
1247
1283
  ), /* @__PURE__ */ import_react3.default.createElement(import_Dialog.default, { open: !!editingKey, onClose: handleCloseEdit, maxWidth: "sm", fullWidth: true }, /* @__PURE__ */ import_react3.default.createElement(import_DialogTitle.default, null, "Edit Key"), /* @__PURE__ */ import_react3.default.createElement(import_DialogContent.default, null, editingKey && /* @__PURE__ */ import_react3.default.createElement(import_Box3.default, { display: "flex", flexDirection: "column", gap: 2, mt: 1 }, /* @__PURE__ */ import_react3.default.createElement(import_Typography3.default, { variant: "body2", color: "text.secondary" }, /* @__PURE__ */ import_react3.default.createElement("code", { style: { fontFamily: "monospace", color: "inherit" } }, maskKey(editingKey.key))), /* @__PURE__ */ import_react3.default.createElement(
1248
1284
  import_TextField.default,
1249
1285
  {
@@ -1467,7 +1503,7 @@ function formatContextWindow2(maxInput, maxOutput) {
1467
1503
  if (outPart) return `ctx ${outPart} out`;
1468
1504
  return null;
1469
1505
  }
1470
- var import_react4, import_Box4, import_Typography4, import_Dialog2, import_DialogTitle2, import_DialogContent2, import_DialogActions2, import_TextField2, import_MenuItem, import_Button3, import_IconButton2, import_CircularProgress2, import_Autocomplete2, import_Checkbox, import_FormControlLabel, import_Alert, import_Tabs, import_Tab, import_icons_material3, generateDefaultAlias, emptyForm, ALL_PROXY_MODELS, SNIPPET_FILE_HINTS, SnippetTabs, GenerateKeyDialog;
1506
+ var import_react4, import_Box4, import_Typography4, import_Dialog2, import_DialogTitle2, import_DialogContent2, import_DialogActions2, import_TextField2, import_MenuItem, import_Button3, import_IconButton2, import_CircularProgress2, import_Autocomplete2, import_Checkbox, import_FormControlLabel, import_Alert, import_Accordion, import_AccordionSummary, import_AccordionDetails, import_Tabs, import_Tab, import_icons_material3, generateDefaultAlias, emptyForm, ALL_PROXY_MODELS, SNIPPET_FILE_HINTS, SnippetTabs, GenerateKeyDialog;
1471
1507
  var init_GenerateKeyDialog = __esm({
1472
1508
  "src/components/GenerateKeyDialog.tsx"() {
1473
1509
  "use strict";
@@ -1487,6 +1523,9 @@ var init_GenerateKeyDialog = __esm({
1487
1523
  import_Checkbox = __toESM(require("@mui/material/Checkbox"));
1488
1524
  import_FormControlLabel = __toESM(require("@mui/material/FormControlLabel"));
1489
1525
  import_Alert = __toESM(require("@mui/material/Alert"));
1526
+ import_Accordion = __toESM(require("@mui/material/Accordion"));
1527
+ import_AccordionSummary = __toESM(require("@mui/material/AccordionSummary"));
1528
+ import_AccordionDetails = __toESM(require("@mui/material/AccordionDetails"));
1490
1529
  import_Tabs = __toESM(require("@mui/material/Tabs"));
1491
1530
  import_Tab = __toESM(require("@mui/material/Tab"));
1492
1531
  import_icons_material3 = require("@mui/icons-material");
@@ -1806,7 +1845,7 @@ var init_GenerateKeyDialog = __esm({
1806
1845
  required: true,
1807
1846
  fullWidth: true
1808
1847
  }
1809
- ), /* @__PURE__ */ import_react4.default.createElement(
1848
+ ), /* @__PURE__ */ import_react4.default.createElement(import_Accordion.default, { disableGutters: true, elevation: 0, sx: { border: "1px solid", borderColor: "divider", "&:before": { display: "none" } } }, /* @__PURE__ */ import_react4.default.createElement(import_AccordionSummary.default, { expandIcon: /* @__PURE__ */ import_react4.default.createElement(import_icons_material3.ExpandMore, null) }, /* @__PURE__ */ import_react4.default.createElement(import_Typography4.default, { variant: "body2", fontWeight: 600 }, "Advanced")), /* @__PURE__ */ import_react4.default.createElement(import_AccordionDetails.default, { sx: { display: "flex", flexDirection: "column", gap: 2 } }, /* @__PURE__ */ import_react4.default.createElement(
1810
1849
  import_TextField2.default,
1811
1850
  {
1812
1851
  label: "TPM Limit",
@@ -1826,7 +1865,7 @@ var init_GenerateKeyDialog = __esm({
1826
1865
  helperText: "Max requests per minute this key can make across all models. Leave blank for no limit.",
1827
1866
  fullWidth: true
1828
1867
  }
1829
- ))), /* @__PURE__ */ import_react4.default.createElement(import_DialogActions2.default, null, newKeyValue ? /* @__PURE__ */ import_react4.default.createElement(import_Button3.default, { onClick: handleClose, variant: "contained", color: "success" }, "Done") : /* @__PURE__ */ import_react4.default.createElement(import_react4.default.Fragment, null, /* @__PURE__ */ import_react4.default.createElement(import_Button3.default, { onClick: handleClose }, "Cancel"), /* @__PURE__ */ import_react4.default.createElement(
1868
+ ))))), /* @__PURE__ */ import_react4.default.createElement(import_DialogActions2.default, null, newKeyValue ? /* @__PURE__ */ import_react4.default.createElement(import_Button3.default, { onClick: handleClose, variant: "contained", color: "success" }, "Done") : /* @__PURE__ */ import_react4.default.createElement(import_react4.default.Fragment, null, /* @__PURE__ */ import_react4.default.createElement(import_Button3.default, { onClick: handleClose }, "Cancel"), /* @__PURE__ */ import_react4.default.createElement(
1830
1869
  import_Button3.default,
1831
1870
  {
1832
1871
  onClick: handleGenerate,
@@ -2423,6 +2462,10 @@ var init_TeamUsage = __esm({
2423
2462
  });
2424
2463
 
2425
2464
  // src/components/ModelsTable.tsx
2465
+ function isModelAllowedByTeam2(model, teamModels) {
2466
+ if (!teamModels || teamModels.length === 0 || teamModels.includes(ALL_PROXY_MODELS2)) return true;
2467
+ return teamModels.includes(model.model_name) || !!model.access_groups?.some((group) => teamModels.includes(group));
2468
+ }
2426
2469
  function fmtCostPerToken(cost) {
2427
2470
  if (cost === void 0 || cost === null) return "\u2014";
2428
2471
  if (cost === 0) return "$0";
@@ -2436,7 +2479,7 @@ function fmtTokens(n) {
2436
2479
  if (n >= 1e3) return `${(n / 1e3).toFixed(0)}K`;
2437
2480
  return String(n);
2438
2481
  }
2439
- var import_react7, import_Box7, import_Table4, import_TableBody4, import_TableCell4, import_TableContainer4, import_TableHead4, import_TableRow4, import_Typography7, import_MenuItem3, import_Select, import_FormControl, import_InputLabel, import_ContentCopy, import_Tooltip, import_Snackbar, import_Alert2, import_styles5, ModelsTable;
2482
+ var import_react7, import_Box7, import_Table4, import_TableBody4, import_TableCell4, import_TableContainer4, import_TableHead4, import_TableRow4, import_Typography7, import_MenuItem3, import_Select, import_FormControl, import_InputLabel, import_ContentCopy, import_Tooltip, import_Snackbar, import_Alert2, import_styles5, ALL_PROXY_MODELS2, ModelsTable;
2440
2483
  var init_ModelsTable = __esm({
2441
2484
  "src/components/ModelsTable.tsx"() {
2442
2485
  "use strict";
@@ -2459,6 +2502,7 @@ var init_ModelsTable = __esm({
2459
2502
  import_Alert2 = __toESM(require("@mui/material/Alert"));
2460
2503
  init_ui();
2461
2504
  import_styles5 = require("@mui/material/styles");
2505
+ ALL_PROXY_MODELS2 = "all-proxy-models";
2462
2506
  ModelsTable = ({ allModels, teams, loading }) => {
2463
2507
  const theme = (0, import_styles5.useTheme)();
2464
2508
  const [selectedTeamId, setSelectedTeamId] = (0, import_react7.useState)("");
@@ -2469,10 +2513,7 @@ var init_ModelsTable = __esm({
2469
2513
  );
2470
2514
  const filteredModels = (0, import_react7.useMemo)(() => {
2471
2515
  if (!selectedTeam) return [];
2472
- const teamModels = selectedTeam.models ?? [];
2473
- if (teamModels.length === 0) return allModels;
2474
- const allowed = new Set(teamModels);
2475
- return allModels.filter((m) => allowed.has(m.model_name));
2516
+ return allModels.filter((model) => isModelAllowedByTeam2(model, selectedTeam.models));
2476
2517
  }, [allModels, selectedTeam]);
2477
2518
  const handleCopyModelId = (0, import_react7.useCallback)((modelId) => {
2478
2519
  navigator.clipboard.writeText(modelId).then(() => setCopiedSnackbar(true));