@acarmisc/backstage-plugin-litellm 0.15.0 → 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 +61 -18
- package/dist/index.cjs.js.map +3 -3
- package/dist/index.d.ts +1 -0
- package/dist/index.esm.js +62 -19
- package/dist/index.esm.js.map +3 -3
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ export { UsageStats } from './components/UsageStats';
|
|
|
6
6
|
export { TeamUsage } from './components/TeamUsage';
|
|
7
7
|
export { LiteLLMHomeWidget } from './components/LiteLLMHomeWidget';
|
|
8
8
|
export type { LiteLLMHomeWidgetProps } from './components/LiteLLMHomeWidget';
|
|
9
|
+
export { GenerateKeyDialog } from './components/GenerateKeyDialog';
|
|
9
10
|
export { LiteLlmApi, liteLlmApiRef } from './api';
|
|
10
11
|
export type { LiteLlmApiInterface } from './api';
|
|
11
12
|
export * from './types';
|
package/dist/index.esm.js
CHANGED
|
@@ -834,6 +834,7 @@ import TableCell from "@mui/material/TableCell";
|
|
|
834
834
|
import TableContainer from "@mui/material/TableContainer";
|
|
835
835
|
import TableHead from "@mui/material/TableHead";
|
|
836
836
|
import TableRow from "@mui/material/TableRow";
|
|
837
|
+
import TableSortLabel from "@mui/material/TableSortLabel";
|
|
837
838
|
import Button2 from "@mui/material/Button";
|
|
838
839
|
import IconButton from "@mui/material/IconButton";
|
|
839
840
|
import Box3 from "@mui/material/Box";
|
|
@@ -913,6 +914,24 @@ function formatContextWindow(maxInput, maxOutput) {
|
|
|
913
914
|
if (outPart) return `ctx ${outPart} out`;
|
|
914
915
|
return null;
|
|
915
916
|
}
|
|
917
|
+
function compareKeys(a, b, sortKey) {
|
|
918
|
+
switch (sortKey) {
|
|
919
|
+
case "alias":
|
|
920
|
+
return (a.key_alias ?? "").localeCompare(b.key_alias ?? "");
|
|
921
|
+
case "created":
|
|
922
|
+
return a.created_at.localeCompare(b.created_at);
|
|
923
|
+
case "expires":
|
|
924
|
+
return (a.expires_at ?? "").localeCompare(b.expires_at ?? "");
|
|
925
|
+
case "budget":
|
|
926
|
+
return (a.max_budget ?? Infinity) - (b.max_budget ?? Infinity);
|
|
927
|
+
case "limits":
|
|
928
|
+
return (a.tpm_limit ?? Infinity) - (b.tpm_limit ?? Infinity) || (a.rpm_limit ?? Infinity) - (b.rpm_limit ?? Infinity);
|
|
929
|
+
case "models":
|
|
930
|
+
return (a.models ?? []).join(",").localeCompare((b.models ?? []).join(","));
|
|
931
|
+
default:
|
|
932
|
+
return 0;
|
|
933
|
+
}
|
|
934
|
+
}
|
|
916
935
|
var maskKey, shortKeyId, formatDate, keyToEditForm, KeysTable;
|
|
917
936
|
var init_KeysTable = __esm({
|
|
918
937
|
"src/components/KeysTable.tsx"() {
|
|
@@ -963,13 +982,29 @@ var init_KeysTable = __esm({
|
|
|
963
982
|
const [resetSpendConfirm, setResetSpendConfirm] = useState(false);
|
|
964
983
|
const [resetSpendSubmitting, setResetSpendSubmitting] = useState(false);
|
|
965
984
|
const [filterText, setFilterText] = useState("");
|
|
985
|
+
const [sortKey, setSortKey] = useState("created");
|
|
986
|
+
const [sortDirection, setSortDirection] = useState("desc");
|
|
987
|
+
const handleSort = (nextSortKey) => {
|
|
988
|
+
if (sortKey === nextSortKey) {
|
|
989
|
+
setSortDirection((current) => current === "asc" ? "desc" : "asc");
|
|
990
|
+
} else {
|
|
991
|
+
setSortKey(nextSortKey);
|
|
992
|
+
setSortDirection("asc");
|
|
993
|
+
}
|
|
994
|
+
};
|
|
966
995
|
const filteredKeys = useMemo2(() => {
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
996
|
+
let result = keys;
|
|
997
|
+
if (filterText.trim()) {
|
|
998
|
+
const q = filterText.toLowerCase();
|
|
999
|
+
result = keys.filter(
|
|
1000
|
+
(k) => (k.key_alias ?? "").toLowerCase().includes(q) || k.models?.some((m) => m.toLowerCase().includes(q))
|
|
1001
|
+
);
|
|
1002
|
+
}
|
|
1003
|
+
return [...result].sort((a, b) => {
|
|
1004
|
+
const comparison = compareKeys(a, b, sortKey);
|
|
1005
|
+
return sortDirection === "asc" ? comparison : -comparison;
|
|
1006
|
+
});
|
|
1007
|
+
}, [keys, filterText, sortKey, sortDirection]);
|
|
973
1008
|
const editSelectedModels = models.filter((m) => (editForm.models || []).includes(m.model_name));
|
|
974
1009
|
const handleOpenEdit = (k) => {
|
|
975
1010
|
setEditingKey(k);
|
|
@@ -1099,7 +1134,7 @@ var init_KeysTable = __esm({
|
|
|
1099
1134
|
"& td": { color: theme.palette.text.secondary }
|
|
1100
1135
|
}) : void 0
|
|
1101
1136
|
},
|
|
1102
|
-
/* @__PURE__ */ React3.createElement(TableCell, null, /* @__PURE__ */ React3.createElement(Box3, { display: "flex", alignItems: "center", gap: 1, flexWrap: "wrap" }, /* @__PURE__ */ React3.createElement(Typography3, { variant: "body2"
|
|
1137
|
+
/* @__PURE__ */ React3.createElement(TableCell, null, /* @__PURE__ */ React3.createElement(Box3, { display: "flex", alignItems: "center", gap: 1, flexWrap: "wrap" }, /* @__PURE__ */ React3.createElement(Typography3, { variant: "body2" }, key.key_alias || "\u2014"), key.blocked && /* @__PURE__ */ React3.createElement(StatusPill, { label: "Blocked", tone: "danger" }))),
|
|
1103
1138
|
/* @__PURE__ */ React3.createElement(TableCell, null, /* @__PURE__ */ React3.createElement(Box3, { display: "flex", alignItems: "center", gap: 0.5 }, /* @__PURE__ */ React3.createElement(
|
|
1104
1139
|
Typography3,
|
|
1105
1140
|
{
|
|
@@ -1181,7 +1216,8 @@ var init_KeysTable = __esm({
|
|
|
1181
1216
|
TextField,
|
|
1182
1217
|
{
|
|
1183
1218
|
size: "small",
|
|
1184
|
-
|
|
1219
|
+
label: "Search alias or model",
|
|
1220
|
+
placeholder: "Search alias or model\u2026",
|
|
1185
1221
|
value: filterText,
|
|
1186
1222
|
onChange: (e) => setFilterText(e.target.value),
|
|
1187
1223
|
sx: { minWidth: 240 },
|
|
@@ -1221,7 +1257,7 @@ var init_KeysTable = __esm({
|
|
|
1221
1257
|
"Generate New Key"
|
|
1222
1258
|
))
|
|
1223
1259
|
},
|
|
1224
|
-
/* @__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,
|
|
1260
|
+
/* @__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())))
|
|
1225
1261
|
), /* @__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(
|
|
1226
1262
|
TextField,
|
|
1227
1263
|
{
|
|
@@ -1344,9 +1380,12 @@ import Autocomplete2 from "@mui/material/Autocomplete";
|
|
|
1344
1380
|
import Checkbox from "@mui/material/Checkbox";
|
|
1345
1381
|
import FormControlLabel from "@mui/material/FormControlLabel";
|
|
1346
1382
|
import Alert from "@mui/material/Alert";
|
|
1383
|
+
import Accordion from "@mui/material/Accordion";
|
|
1384
|
+
import AccordionSummary from "@mui/material/AccordionSummary";
|
|
1385
|
+
import AccordionDetails from "@mui/material/AccordionDetails";
|
|
1347
1386
|
import Tabs from "@mui/material/Tabs";
|
|
1348
1387
|
import Tab from "@mui/material/Tab";
|
|
1349
|
-
import { ContentCopy as ContentCopy2, Code } from "@mui/icons-material";
|
|
1388
|
+
import { ContentCopy as ContentCopy2, Code, ExpandMore } from "@mui/icons-material";
|
|
1350
1389
|
function trimSlash(url) {
|
|
1351
1390
|
return url.replace(/\/+$/, "");
|
|
1352
1391
|
}
|
|
@@ -1784,7 +1823,7 @@ var init_GenerateKeyDialog = __esm({
|
|
|
1784
1823
|
required: true,
|
|
1785
1824
|
fullWidth: true
|
|
1786
1825
|
}
|
|
1787
|
-
), /* @__PURE__ */ React4.createElement(
|
|
1826
|
+
), /* @__PURE__ */ React4.createElement(Accordion, { disableGutters: true, elevation: 0, sx: { border: "1px solid", borderColor: "divider", "&:before": { display: "none" } } }, /* @__PURE__ */ React4.createElement(AccordionSummary, { expandIcon: /* @__PURE__ */ React4.createElement(ExpandMore, null) }, /* @__PURE__ */ React4.createElement(Typography4, { variant: "body2", fontWeight: 600 }, "Advanced")), /* @__PURE__ */ React4.createElement(AccordionDetails, { sx: { display: "flex", flexDirection: "column", gap: 2 } }, /* @__PURE__ */ React4.createElement(
|
|
1788
1827
|
TextField2,
|
|
1789
1828
|
{
|
|
1790
1829
|
label: "TPM Limit",
|
|
@@ -1804,7 +1843,7 @@ var init_GenerateKeyDialog = __esm({
|
|
|
1804
1843
|
helperText: "Max requests per minute this key can make across all models. Leave blank for no limit.",
|
|
1805
1844
|
fullWidth: true
|
|
1806
1845
|
}
|
|
1807
|
-
))), /* @__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(
|
|
1846
|
+
))))), /* @__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(
|
|
1808
1847
|
Button3,
|
|
1809
1848
|
{
|
|
1810
1849
|
onClick: handleGenerate,
|
|
@@ -2234,7 +2273,7 @@ import Collapse from "@mui/material/Collapse";
|
|
|
2234
2273
|
import IconButton3 from "@mui/material/IconButton";
|
|
2235
2274
|
import CircularProgress3 from "@mui/material/CircularProgress";
|
|
2236
2275
|
import { alpha as alpha4 } from "@mui/material/styles";
|
|
2237
|
-
import { ExpandMore, Group } from "@mui/icons-material";
|
|
2276
|
+
import { ExpandMore as ExpandMore2, Group } from "@mui/icons-material";
|
|
2238
2277
|
import {
|
|
2239
2278
|
AreaChart as AreaChart2,
|
|
2240
2279
|
Area as Area2,
|
|
@@ -2342,7 +2381,7 @@ var init_TeamUsage = __esm({
|
|
|
2342
2381
|
transition: theme.transitions.create("transform")
|
|
2343
2382
|
})
|
|
2344
2383
|
},
|
|
2345
|
-
/* @__PURE__ */ React6.createElement(
|
|
2384
|
+
/* @__PURE__ */ React6.createElement(ExpandMore2, null)
|
|
2346
2385
|
)), /* @__PURE__ */ React6.createElement(
|
|
2347
2386
|
Box6,
|
|
2348
2387
|
{
|
|
@@ -2442,6 +2481,10 @@ import Tooltip3 from "@mui/material/Tooltip";
|
|
|
2442
2481
|
import Snackbar from "@mui/material/Snackbar";
|
|
2443
2482
|
import Alert2 from "@mui/material/Alert";
|
|
2444
2483
|
import { alpha as alpha5, useTheme as useTheme2 } from "@mui/material/styles";
|
|
2484
|
+
function isModelAllowedByTeam2(model, teamModels) {
|
|
2485
|
+
if (!teamModels || teamModels.length === 0 || teamModels.includes(ALL_PROXY_MODELS2)) return true;
|
|
2486
|
+
return teamModels.includes(model.model_name) || !!model.access_groups?.some((group) => teamModels.includes(group));
|
|
2487
|
+
}
|
|
2445
2488
|
function fmtCostPerToken(cost) {
|
|
2446
2489
|
if (cost === void 0 || cost === null) return "\u2014";
|
|
2447
2490
|
if (cost === 0) return "$0";
|
|
@@ -2455,11 +2498,12 @@ function fmtTokens(n) {
|
|
|
2455
2498
|
if (n >= 1e3) return `${(n / 1e3).toFixed(0)}K`;
|
|
2456
2499
|
return String(n);
|
|
2457
2500
|
}
|
|
2458
|
-
var ModelsTable;
|
|
2501
|
+
var ALL_PROXY_MODELS2, ModelsTable;
|
|
2459
2502
|
var init_ModelsTable = __esm({
|
|
2460
2503
|
"src/components/ModelsTable.tsx"() {
|
|
2461
2504
|
"use strict";
|
|
2462
2505
|
init_ui();
|
|
2506
|
+
ALL_PROXY_MODELS2 = "all-proxy-models";
|
|
2463
2507
|
ModelsTable = ({ allModels, teams, loading }) => {
|
|
2464
2508
|
const theme = useTheme2();
|
|
2465
2509
|
const [selectedTeamId, setSelectedTeamId] = useState5("");
|
|
@@ -2470,10 +2514,7 @@ var init_ModelsTable = __esm({
|
|
|
2470
2514
|
);
|
|
2471
2515
|
const filteredModels = useMemo5(() => {
|
|
2472
2516
|
if (!selectedTeam) return [];
|
|
2473
|
-
|
|
2474
|
-
if (teamModels.length === 0) return allModels;
|
|
2475
|
-
const allowed = new Set(teamModels);
|
|
2476
|
-
return allModels.filter((m) => allowed.has(m.model_name));
|
|
2517
|
+
return allModels.filter((model) => isModelAllowedByTeam2(model, selectedTeam.models));
|
|
2477
2518
|
}, [allModels, selectedTeam]);
|
|
2478
2519
|
const handleCopyModelId = useCallback((modelId) => {
|
|
2479
2520
|
navigator.clipboard.writeText(modelId).then(() => setCopiedSnackbar(true));
|
|
@@ -3259,9 +3300,11 @@ var LiteLLMHomeWidget = ({
|
|
|
3259
3300
|
};
|
|
3260
3301
|
|
|
3261
3302
|
// src/index.ts
|
|
3303
|
+
init_GenerateKeyDialog();
|
|
3262
3304
|
init_api();
|
|
3263
3305
|
export {
|
|
3264
3306
|
DashboardHeader,
|
|
3307
|
+
GenerateKeyDialog,
|
|
3265
3308
|
KeysTable,
|
|
3266
3309
|
LiteLLMHomeWidget,
|
|
3267
3310
|
LiteLLMPage,
|