@acarmisc/backstage-plugin-litellm 0.15.1 → 0.15.3
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/README.md +38 -1
- package/dist/components/UsageStats.d.ts +3 -1
- package/dist/index.cjs.js +76 -32
- package/dist/index.cjs.js.map +3 -3
- package/dist/index.esm.js +77 -33
- package/dist/index.esm.js.map +3 -3
- package/package.json +1 -1
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,
|
|
@@ -1891,6 +1930,7 @@ var init_UsageStats = __esm({
|
|
|
1891
1930
|
usage,
|
|
1892
1931
|
models,
|
|
1893
1932
|
dateRange,
|
|
1933
|
+
currentPreset,
|
|
1894
1934
|
onDateRangeChange,
|
|
1895
1935
|
loading,
|
|
1896
1936
|
userInfo
|
|
@@ -1898,15 +1938,6 @@ var init_UsageStats = __esm({
|
|
|
1898
1938
|
const chart = useChartTheme();
|
|
1899
1939
|
const [selectedModel, setSelectedModel] = useState3("all");
|
|
1900
1940
|
const [tab, setTab] = useState3("costs");
|
|
1901
|
-
const selectedPreset = useMemo4(() => {
|
|
1902
|
-
if (dateRange.start.toDateString() === dateRange.end.toDateString()) return "today";
|
|
1903
|
-
const diffMs = dateRange.end.getTime() - dateRange.start.getTime();
|
|
1904
|
-
const diffHours = diffMs / (1e3 * 60 * 60);
|
|
1905
|
-
if (diffHours <= 26) return "24h";
|
|
1906
|
-
const diffDays = Math.ceil(diffMs / (1e3 * 60 * 60 * 24));
|
|
1907
|
-
if (diffDays <= 7) return "7d";
|
|
1908
|
-
return "30d";
|
|
1909
|
-
}, [dateRange]);
|
|
1910
1941
|
const handlePresetChange = (preset) => {
|
|
1911
1942
|
const end = /* @__PURE__ */ new Date();
|
|
1912
1943
|
const start = /* @__PURE__ */ new Date();
|
|
@@ -1918,7 +1949,7 @@ var init_UsageStats = __esm({
|
|
|
1918
1949
|
localStorage.setItem(PERIOD_LS_KEY, preset);
|
|
1919
1950
|
} catch {
|
|
1920
1951
|
}
|
|
1921
|
-
onDateRangeChange({ start, end });
|
|
1952
|
+
onDateRangeChange({ start, end }, preset);
|
|
1922
1953
|
};
|
|
1923
1954
|
const dailyData = useMemo4(
|
|
1924
1955
|
() => (usage?.daily_usage ?? []).map((d) => ({
|
|
@@ -2057,7 +2088,7 @@ var init_UsageStats = __esm({
|
|
|
2057
2088
|
select: true,
|
|
2058
2089
|
size: "small",
|
|
2059
2090
|
label: "Period",
|
|
2060
|
-
value:
|
|
2091
|
+
value: currentPreset,
|
|
2061
2092
|
onChange: (e) => handlePresetChange(e.target.value),
|
|
2062
2093
|
sx: { minWidth: 160 }
|
|
2063
2094
|
},
|
|
@@ -2067,7 +2098,7 @@ var init_UsageStats = __esm({
|
|
|
2067
2098
|
SectionCard,
|
|
2068
2099
|
{
|
|
2069
2100
|
title: "Usage Analytics",
|
|
2070
|
-
subtitle: `${PRESET_LABELS[
|
|
2101
|
+
subtitle: `${PRESET_LABELS[currentPreset]} \xB7 ${dateRange.start.toLocaleDateString()} \u2013 ${dateRange.end.toLocaleDateString()}`,
|
|
2071
2102
|
actions: periodSelect
|
|
2072
2103
|
},
|
|
2073
2104
|
/* @__PURE__ */ React5.createElement(MetricStrip, { metrics }),
|
|
@@ -2234,7 +2265,7 @@ import Collapse from "@mui/material/Collapse";
|
|
|
2234
2265
|
import IconButton3 from "@mui/material/IconButton";
|
|
2235
2266
|
import CircularProgress3 from "@mui/material/CircularProgress";
|
|
2236
2267
|
import { alpha as alpha4 } from "@mui/material/styles";
|
|
2237
|
-
import { ExpandMore, Group } from "@mui/icons-material";
|
|
2268
|
+
import { ExpandMore as ExpandMore2, Group } from "@mui/icons-material";
|
|
2238
2269
|
import {
|
|
2239
2270
|
AreaChart as AreaChart2,
|
|
2240
2271
|
Area as Area2,
|
|
@@ -2342,7 +2373,7 @@ var init_TeamUsage = __esm({
|
|
|
2342
2373
|
transition: theme.transitions.create("transform")
|
|
2343
2374
|
})
|
|
2344
2375
|
},
|
|
2345
|
-
/* @__PURE__ */ React6.createElement(
|
|
2376
|
+
/* @__PURE__ */ React6.createElement(ExpandMore2, null)
|
|
2346
2377
|
)), /* @__PURE__ */ React6.createElement(
|
|
2347
2378
|
Box6,
|
|
2348
2379
|
{
|
|
@@ -2442,6 +2473,10 @@ import Tooltip3 from "@mui/material/Tooltip";
|
|
|
2442
2473
|
import Snackbar from "@mui/material/Snackbar";
|
|
2443
2474
|
import Alert2 from "@mui/material/Alert";
|
|
2444
2475
|
import { alpha as alpha5, useTheme as useTheme2 } from "@mui/material/styles";
|
|
2476
|
+
function isModelAllowedByTeam2(model, teamModels) {
|
|
2477
|
+
if (!teamModels || teamModels.length === 0 || teamModels.includes(ALL_PROXY_MODELS2)) return true;
|
|
2478
|
+
return teamModels.includes(model.model_name) || !!model.access_groups?.some((group) => teamModels.includes(group));
|
|
2479
|
+
}
|
|
2445
2480
|
function fmtCostPerToken(cost) {
|
|
2446
2481
|
if (cost === void 0 || cost === null) return "\u2014";
|
|
2447
2482
|
if (cost === 0) return "$0";
|
|
@@ -2455,11 +2490,12 @@ function fmtTokens(n) {
|
|
|
2455
2490
|
if (n >= 1e3) return `${(n / 1e3).toFixed(0)}K`;
|
|
2456
2491
|
return String(n);
|
|
2457
2492
|
}
|
|
2458
|
-
var ModelsTable;
|
|
2493
|
+
var ALL_PROXY_MODELS2, ModelsTable;
|
|
2459
2494
|
var init_ModelsTable = __esm({
|
|
2460
2495
|
"src/components/ModelsTable.tsx"() {
|
|
2461
2496
|
"use strict";
|
|
2462
2497
|
init_ui();
|
|
2498
|
+
ALL_PROXY_MODELS2 = "all-proxy-models";
|
|
2463
2499
|
ModelsTable = ({ allModels, teams, loading }) => {
|
|
2464
2500
|
const theme = useTheme2();
|
|
2465
2501
|
const [selectedTeamId, setSelectedTeamId] = useState5("");
|
|
@@ -2470,10 +2506,7 @@ var init_ModelsTable = __esm({
|
|
|
2470
2506
|
);
|
|
2471
2507
|
const filteredModels = useMemo5(() => {
|
|
2472
2508
|
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));
|
|
2509
|
+
return allModels.filter((model) => isModelAllowedByTeam2(model, selectedTeam.models));
|
|
2477
2510
|
}, [allModels, selectedTeam]);
|
|
2478
2511
|
const handleCopyModelId = useCallback((modelId) => {
|
|
2479
2512
|
navigator.clipboard.writeText(modelId).then(() => setCopiedSnackbar(true));
|
|
@@ -2840,6 +2873,13 @@ var init_LiteLLMPage = __esm({
|
|
|
2840
2873
|
LiteLLMPage = () => {
|
|
2841
2874
|
const api = useApi(liteLlmApiRef);
|
|
2842
2875
|
const [dateRange, setDateRange] = useState7(initDateRange);
|
|
2876
|
+
const [currentPreset, setCurrentPreset] = useState7(() => {
|
|
2877
|
+
try {
|
|
2878
|
+
return localStorage.getItem(PERIOD_LS_KEY2) ?? "7d";
|
|
2879
|
+
} catch {
|
|
2880
|
+
return "7d";
|
|
2881
|
+
}
|
|
2882
|
+
});
|
|
2843
2883
|
const [activeTab, setActiveTab] = useState7("overview");
|
|
2844
2884
|
const [snackbar, setSnackbar] = useState7(null);
|
|
2845
2885
|
const [generateDialogOpen, setGenerateDialogOpen] = useState7(false);
|
|
@@ -2886,11 +2926,14 @@ var init_LiteLLMPage = __esm({
|
|
|
2886
2926
|
const endDate = dateRange.end.toISOString().split("T")[0];
|
|
2887
2927
|
return api.getUsage(startDate, endDate);
|
|
2888
2928
|
}, [api, dateRange]);
|
|
2889
|
-
const handleDateRangeChange = useCallback3((range) => {
|
|
2929
|
+
const handleDateRangeChange = useCallback3((range, preset) => {
|
|
2890
2930
|
setDateRange(range);
|
|
2931
|
+
if (preset) {
|
|
2932
|
+
setCurrentPreset(preset);
|
|
2933
|
+
}
|
|
2891
2934
|
setTeamUsageCache({});
|
|
2892
2935
|
setTeamUsageLoading({});
|
|
2893
|
-
}, [setDateRange]);
|
|
2936
|
+
}, [setDateRange, setCurrentPreset]);
|
|
2894
2937
|
const loadTeamUsage = useCallback3(async (teamId) => {
|
|
2895
2938
|
if (teamUsageCache[teamId] !== void 0 || teamUsageLoading[teamId]) return;
|
|
2896
2939
|
setTeamUsageLoading((prev) => ({ ...prev, [teamId]: true }));
|
|
@@ -3055,6 +3098,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3055
3098
|
usage: usage ?? null,
|
|
3056
3099
|
models: allModels ?? [],
|
|
3057
3100
|
dateRange,
|
|
3101
|
+
currentPreset,
|
|
3058
3102
|
onDateRangeChange: handleDateRangeChange,
|
|
3059
3103
|
loading: usageLoading,
|
|
3060
3104
|
userInfo
|