@acarmisc/backstage-plugin-litellm 0.21.1 → 0.21.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/dist/index.cjs.js +102 -27
- package/dist/index.cjs.js.map +3 -3
- package/dist/index.esm.js +101 -26
- package/dist/index.esm.js.map +3 -3
- package/package.json +1 -1
package/dist/index.esm.js
CHANGED
|
@@ -2845,6 +2845,7 @@ import IconButton4 from "@mui/material/IconButton";
|
|
|
2845
2845
|
import { ExpandMore as ExpandMore2 } from "@mui/icons-material";
|
|
2846
2846
|
import { alpha as alpha4 } from "@mui/material/styles";
|
|
2847
2847
|
import { useApi as useApi2 } from "@backstage/core-plugin-api";
|
|
2848
|
+
import { Link } from "@backstage/core-components";
|
|
2848
2849
|
function levelToneColor(tone, theme) {
|
|
2849
2850
|
switch (tone) {
|
|
2850
2851
|
case "success":
|
|
@@ -2859,7 +2860,7 @@ function levelToneColor(tone, theme) {
|
|
|
2859
2860
|
return theme.palette.primary.main;
|
|
2860
2861
|
}
|
|
2861
2862
|
}
|
|
2862
|
-
var LevelFrame, EmptyLimitCard, LimitCard, CLOSEST_LEVEL_LABEL, TaggedLimit, FullBody, CompactBody, LiteLLMBudgetWidget;
|
|
2863
|
+
var LevelFrame, EmptyLimitCard, LimitCard, CLOSEST_LEVEL_LABEL, KEYS_PATH, MoreKeysNote, HierarchyNote, TaggedLimit, FullBody, CompactBody, LiteLLMBudgetWidget;
|
|
2863
2864
|
var init_LiteLLMBudgetWidget = __esm({
|
|
2864
2865
|
"src/components/LiteLLMBudgetWidget.tsx"() {
|
|
2865
2866
|
"use strict";
|
|
@@ -2976,6 +2977,21 @@ var init_LiteLLMBudgetWidget = __esm({
|
|
|
2976
2977
|
user: "personal",
|
|
2977
2978
|
team: "team"
|
|
2978
2979
|
};
|
|
2980
|
+
KEYS_PATH = "/litellm?tab=keys";
|
|
2981
|
+
MoreKeysNote = ({ count }) => /* @__PURE__ */ React7.createElement(Link, { to: KEYS_PATH, variant: "caption" }, "+", count, " more budgeted key", count > 1 ? "s" : "", " further from the cap \u2192");
|
|
2982
|
+
HierarchyNote = ({ withReset }) => /* @__PURE__ */ React7.createElement(
|
|
2983
|
+
Box7,
|
|
2984
|
+
{
|
|
2985
|
+
sx: (theme) => ({
|
|
2986
|
+
px: 1.5,
|
|
2987
|
+
py: 1.25,
|
|
2988
|
+
borderRadius: 1.5,
|
|
2989
|
+
border: "1px dashed",
|
|
2990
|
+
borderColor: alpha4(theme.palette.text.primary, 0.18)
|
|
2991
|
+
})
|
|
2992
|
+
},
|
|
2993
|
+
/* @__PURE__ */ React7.createElement(Typography7, { variant: "caption", color: "text.secondary", display: "block" }, /* @__PURE__ */ React7.createElement("b", null, "Order:"), " key \u2192 personal \u2192 team \u2192 global. The first cap you reach blocks the request; a key that belongs to a team uses the team's cap, not your personal one.", withReset && " A cap with a reset window drops to $0 when the window closes; without one it never resets.")
|
|
2994
|
+
);
|
|
2979
2995
|
TaggedLimit = ({ limit }) => {
|
|
2980
2996
|
const tone = budgetTone2(limit.pct);
|
|
2981
2997
|
return /* @__PURE__ */ React7.createElement(Box7, null, /* @__PURE__ */ React7.createElement(
|
|
@@ -3005,7 +3021,7 @@ var init_LiteLLMBudgetWidget = __esm({
|
|
|
3005
3021
|
},
|
|
3006
3022
|
!hasBudgetedKeys && /* @__PURE__ */ React7.createElement(EmptyLimitCard, null, "No key has a budget, so no key-level cap applies."),
|
|
3007
3023
|
summary.keys.map((k) => /* @__PURE__ */ React7.createElement(LimitCard, { key: k.sublabel ?? k.label, limit: k })),
|
|
3008
|
-
summary.hiddenBudgetedKeys > 0 && /* @__PURE__ */ React7.createElement(
|
|
3024
|
+
summary.hiddenBudgetedKeys > 0 && /* @__PURE__ */ React7.createElement(MoreKeysNote, { count: summary.hiddenBudgetedKeys })
|
|
3009
3025
|
), /* @__PURE__ */ React7.createElement(
|
|
3010
3026
|
LevelFrame,
|
|
3011
3027
|
{
|
|
@@ -3015,27 +3031,14 @@ var init_LiteLLMBudgetWidget = __esm({
|
|
|
3015
3031
|
note: "skipped for team-bound keys"
|
|
3016
3032
|
},
|
|
3017
3033
|
summary.user ? /* @__PURE__ */ React7.createElement(LimitCard, { limit: summary.user }) : /* @__PURE__ */ React7.createElement(EmptyLimitCard, null, "No personal budget is set on your account.")
|
|
3018
|
-
), /* @__PURE__ */ React7.createElement(LevelFrame, { rank: 3, name: "Team", tagline: "shared cap for all keys in a team (if any)" }, summary.teams.length > 0 ? summary.teams.map((t) => /* @__PURE__ */ React7.createElement(LimitCard, { key: t.sublabel ?? t.label, limit: t })) : /* @__PURE__ */ React7.createElement(EmptyLimitCard, null, "No team you belong to has a budget.")), /* @__PURE__ */ React7.createElement(LevelFrame, { rank: 4, name: "Global", tagline: "proxy-wide cap set by your admin", isLast: true }, /* @__PURE__ */ React7.createElement(EmptyLimitCard, null, "Configured by your admin in the proxy config \u2014 it isn't visible from this page. When set, it limits spend across every request on the proxy.")), /* @__PURE__ */ React7.createElement(
|
|
3019
|
-
Box7,
|
|
3020
|
-
{
|
|
3021
|
-
sx: (theme) => ({
|
|
3022
|
-
mt: 2,
|
|
3023
|
-
px: 1.5,
|
|
3024
|
-
py: 1.25,
|
|
3025
|
-
borderRadius: 1.5,
|
|
3026
|
-
border: "1px dashed",
|
|
3027
|
-
borderColor: alpha4(theme.palette.text.primary, 0.18)
|
|
3028
|
-
})
|
|
3029
|
-
},
|
|
3030
|
-
/* @__PURE__ */ React7.createElement(Typography7, { variant: "caption", color: "text.secondary", display: "block" }, "The first cap a request runs into is the one that stops it. A limit with a reset window (e.g. daily or every 30 days) clears its spend to $0 when the window closes; without a window it never resets.")
|
|
3031
|
-
));
|
|
3034
|
+
), /* @__PURE__ */ React7.createElement(LevelFrame, { rank: 3, name: "Team", tagline: "shared cap for all keys in a team (if any)" }, summary.teams.length > 0 ? summary.teams.map((t) => /* @__PURE__ */ React7.createElement(LimitCard, { key: t.sublabel ?? t.label, limit: t })) : /* @__PURE__ */ React7.createElement(EmptyLimitCard, null, "No team you belong to has a budget.")), /* @__PURE__ */ React7.createElement(LevelFrame, { rank: 4, name: "Global", tagline: "proxy-wide cap set by your admin", isLast: true }, /* @__PURE__ */ React7.createElement(EmptyLimitCard, null, "Configured by your admin in the proxy config \u2014 it isn't visible from this page. When set, it limits spend across every request on the proxy.")), /* @__PURE__ */ React7.createElement(Box7, { sx: { mt: 2 } }, /* @__PURE__ */ React7.createElement(HierarchyNote, { withReset: true })));
|
|
3032
3035
|
};
|
|
3033
3036
|
CompactBody = ({ summary }) => {
|
|
3034
3037
|
const { count } = budgetHeadline(summary);
|
|
3035
3038
|
if (count === 0) {
|
|
3036
3039
|
return /* @__PURE__ */ React7.createElement(Typography7, { variant: "body2", color: "text.secondary", sx: { fontSize: 13 } }, "No budget limits apply to you right now.");
|
|
3037
3040
|
}
|
|
3038
|
-
return /* @__PURE__ */ React7.createElement(Box7, { sx: { display: "flex", flexDirection: "column", gap: 1.25 } }, summary.keys.map((k) => /* @__PURE__ */ React7.createElement(TaggedLimit, { key: k.sublabel ?? k.label, limit: k })), summary.hiddenBudgetedKeys > 0 && /* @__PURE__ */ React7.createElement(
|
|
3041
|
+
return /* @__PURE__ */ React7.createElement(Box7, { sx: { display: "flex", flexDirection: "column", gap: 1.25 } }, /* @__PURE__ */ React7.createElement(HierarchyNote, null), summary.keys.map((k) => /* @__PURE__ */ React7.createElement(TaggedLimit, { key: k.sublabel ?? k.label, limit: k })), summary.hiddenBudgetedKeys > 0 && /* @__PURE__ */ React7.createElement(MoreKeysNote, { count: summary.hiddenBudgetedKeys }), summary.user && /* @__PURE__ */ React7.createElement(TaggedLimit, { limit: summary.user }), summary.teams.map((t) => /* @__PURE__ */ React7.createElement(TaggedLimit, { key: t.sublabel ?? t.label, limit: t })));
|
|
3039
3042
|
};
|
|
3040
3043
|
LiteLLMBudgetWidget = ({
|
|
3041
3044
|
title = "Budget Policy",
|
|
@@ -3390,6 +3393,7 @@ import TableCell5 from "@mui/material/TableCell";
|
|
|
3390
3393
|
import TableContainer5 from "@mui/material/TableContainer";
|
|
3391
3394
|
import TableHead5 from "@mui/material/TableHead";
|
|
3392
3395
|
import TableRow5 from "@mui/material/TableRow";
|
|
3396
|
+
import TableSortLabel2 from "@mui/material/TableSortLabel";
|
|
3393
3397
|
import Typography9 from "@mui/material/Typography";
|
|
3394
3398
|
import MenuItem4 from "@mui/material/MenuItem";
|
|
3395
3399
|
import Select from "@mui/material/Select";
|
|
@@ -3431,6 +3435,8 @@ var init_ModelsTable = __esm({
|
|
|
3431
3435
|
const [selectedTeamId, setSelectedTeamId] = useState7("");
|
|
3432
3436
|
const [filterText, setFilterText] = useState7("");
|
|
3433
3437
|
const [copiedSnackbar, setCopiedSnackbar] = useState7(false);
|
|
3438
|
+
const [sortKey, setSortKey] = useState7("model_name");
|
|
3439
|
+
const [sortDirection, setSortDirection] = useState7("asc");
|
|
3434
3440
|
const selectedTeam = useMemo7(
|
|
3435
3441
|
() => teams.find((t) => t.team_id === selectedTeamId) ?? null,
|
|
3436
3442
|
[teams, selectedTeamId]
|
|
@@ -3441,14 +3447,62 @@ var init_ModelsTable = __esm({
|
|
|
3441
3447
|
}, [allModels, selectedTeam]);
|
|
3442
3448
|
const filteredModels = useMemo7(() => {
|
|
3443
3449
|
const q = filterText.trim().toLowerCase();
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3450
|
+
let models = teamModels;
|
|
3451
|
+
if (q) {
|
|
3452
|
+
models = teamModels.filter(
|
|
3453
|
+
(model) => model.model_name.toLowerCase().includes(q) || (model.access_groups || []).some((group) => group.toLowerCase().includes(q))
|
|
3454
|
+
);
|
|
3455
|
+
}
|
|
3456
|
+
return [...models].sort((a, b) => {
|
|
3457
|
+
let aVal;
|
|
3458
|
+
let bVal;
|
|
3459
|
+
switch (sortKey) {
|
|
3460
|
+
case "model_name":
|
|
3461
|
+
aVal = a.model_name;
|
|
3462
|
+
bVal = b.model_name;
|
|
3463
|
+
break;
|
|
3464
|
+
case "mode":
|
|
3465
|
+
aVal = a.mode;
|
|
3466
|
+
bVal = b.mode;
|
|
3467
|
+
break;
|
|
3468
|
+
case "input_cost_per_token":
|
|
3469
|
+
aVal = a.input_cost_per_token ?? 0;
|
|
3470
|
+
bVal = b.input_cost_per_token ?? 0;
|
|
3471
|
+
break;
|
|
3472
|
+
case "output_cost_per_token":
|
|
3473
|
+
aVal = a.output_cost_per_token ?? 0;
|
|
3474
|
+
bVal = b.output_cost_per_token ?? 0;
|
|
3475
|
+
break;
|
|
3476
|
+
case "max_input_tokens":
|
|
3477
|
+
aVal = a.max_input_tokens ?? 0;
|
|
3478
|
+
bVal = b.max_input_tokens ?? 0;
|
|
3479
|
+
break;
|
|
3480
|
+
case "max_output_tokens":
|
|
3481
|
+
aVal = a.max_output_tokens ?? 0;
|
|
3482
|
+
bVal = b.max_output_tokens ?? 0;
|
|
3483
|
+
break;
|
|
3484
|
+
default:
|
|
3485
|
+
return 0;
|
|
3486
|
+
}
|
|
3487
|
+
if (aVal === void 0 || aVal === null) aVal = "";
|
|
3488
|
+
if (bVal === void 0 || bVal === null) bVal = "";
|
|
3489
|
+
if (typeof aVal === "string" && typeof bVal === "string") {
|
|
3490
|
+
return sortDirection === "asc" ? aVal.localeCompare(bVal) : bVal.localeCompare(aVal);
|
|
3491
|
+
}
|
|
3492
|
+
return sortDirection === "asc" ? aVal - bVal : bVal - aVal;
|
|
3493
|
+
});
|
|
3494
|
+
}, [teamModels, filterText, sortKey, sortDirection]);
|
|
3449
3495
|
const handleCopyModelId = useCallback((modelId) => {
|
|
3450
3496
|
navigator.clipboard.writeText(modelId).then(() => setCopiedSnackbar(true));
|
|
3451
3497
|
}, []);
|
|
3498
|
+
const handleSort = useCallback((key) => {
|
|
3499
|
+
if (sortKey === key) {
|
|
3500
|
+
setSortDirection((prev) => prev === "asc" ? "desc" : "asc");
|
|
3501
|
+
} else {
|
|
3502
|
+
setSortKey(key);
|
|
3503
|
+
setSortDirection("asc");
|
|
3504
|
+
}
|
|
3505
|
+
}, [sortKey]);
|
|
3452
3506
|
const modeColor = (mode) => {
|
|
3453
3507
|
switch (mode) {
|
|
3454
3508
|
case "chat":
|
|
@@ -3514,7 +3568,7 @@ var init_ModelsTable = __esm({
|
|
|
3514
3568
|
hint: "Try a different search term"
|
|
3515
3569
|
}
|
|
3516
3570
|
),
|
|
3517
|
-
!loading && filteredModels.length > 0 && /* @__PURE__ */ React9.createElement(TableContainer5, null, /* @__PURE__ */ React9.createElement(Table5, { size: "small", sx: dataTableSx }, /* @__PURE__ */ React9.createElement(TableHead5, null, /* @__PURE__ */ React9.createElement(TableRow5, null, /* @__PURE__ */ React9.createElement(TableCell5,
|
|
3571
|
+
!loading && filteredModels.length > 0 && /* @__PURE__ */ React9.createElement(TableContainer5, null, /* @__PURE__ */ React9.createElement(Table5, { size: "small", sx: dataTableSx }, /* @__PURE__ */ React9.createElement(TableHead5, null, /* @__PURE__ */ React9.createElement(TableRow5, null, /* @__PURE__ */ React9.createElement(TableCell5, { sortDirection: sortKey === "model_name" ? sortDirection : false }, /* @__PURE__ */ React9.createElement(TableSortLabel2, { active: sortKey === "model_name", direction: sortKey === "model_name" ? sortDirection : "asc", onClick: () => handleSort("model_name") }, "Model ID")), /* @__PURE__ */ React9.createElement(TableCell5, { sortDirection: sortKey === "mode" ? sortDirection : false }, /* @__PURE__ */ React9.createElement(TableSortLabel2, { active: sortKey === "mode", direction: sortKey === "mode" ? sortDirection : "asc", onClick: () => handleSort("mode") }, "Mode")), /* @__PURE__ */ React9.createElement(TableCell5, { align: "right", sortDirection: sortKey === "input_cost_per_token" ? sortDirection : false }, /* @__PURE__ */ React9.createElement(TableSortLabel2, { active: sortKey === "input_cost_per_token", direction: sortKey === "input_cost_per_token" ? sortDirection : "asc", onClick: () => handleSort("input_cost_per_token") }, "Input Cost")), /* @__PURE__ */ React9.createElement(TableCell5, { align: "right", sortDirection: sortKey === "output_cost_per_token" ? sortDirection : false }, /* @__PURE__ */ React9.createElement(TableSortLabel2, { active: sortKey === "output_cost_per_token", direction: sortKey === "output_cost_per_token" ? sortDirection : "asc", onClick: () => handleSort("output_cost_per_token") }, "Output Cost")), /* @__PURE__ */ React9.createElement(TableCell5, { align: "right", sortDirection: sortKey === "max_input_tokens" ? sortDirection : false }, /* @__PURE__ */ React9.createElement(TableSortLabel2, { active: sortKey === "max_input_tokens", direction: sortKey === "max_input_tokens" ? sortDirection : "asc", onClick: () => handleSort("max_input_tokens") }, "Max Input")), /* @__PURE__ */ React9.createElement(TableCell5, { align: "right", sortDirection: sortKey === "max_output_tokens" ? sortDirection : false }, /* @__PURE__ */ React9.createElement(TableSortLabel2, { active: sortKey === "max_output_tokens", direction: sortKey === "max_output_tokens" ? sortDirection : "asc", onClick: () => handleSort("max_output_tokens") }, "Max Output")), /* @__PURE__ */ React9.createElement(TableCell5, null, "Capabilities"))), /* @__PURE__ */ React9.createElement(TableBody5, null, filteredModels.map((m) => /* @__PURE__ */ React9.createElement(TableRow5, { key: m.model_name, hover: true }, /* @__PURE__ */ React9.createElement(TableCell5, null, /* @__PURE__ */ React9.createElement(Box9, { sx: { display: "flex", alignItems: "center", gap: 0.5 } }, /* @__PURE__ */ React9.createElement(Tooltip3, { title: m.model_name, placement: "top" }, /* @__PURE__ */ React9.createElement(
|
|
3518
3572
|
"span",
|
|
3519
3573
|
{
|
|
3520
3574
|
role: "button",
|
|
@@ -3844,6 +3898,7 @@ import Paper7 from "@mui/material/Paper";
|
|
|
3844
3898
|
import Tabs2 from "@mui/material/Tabs";
|
|
3845
3899
|
import Tab2 from "@mui/material/Tab";
|
|
3846
3900
|
import Grid2 from "@mui/material/Grid";
|
|
3901
|
+
import { useSearchParams } from "react-router-dom";
|
|
3847
3902
|
import { useAsync as useAsync3, useAsyncRetry } from "react-use";
|
|
3848
3903
|
import { useApi as useApi3 } from "@backstage/core-plugin-api";
|
|
3849
3904
|
import { usePermission } from "@backstage/plugin-permission-react";
|
|
@@ -3861,7 +3916,7 @@ function initDateRange() {
|
|
|
3861
3916
|
else start.setDate(start.getDate() - 7);
|
|
3862
3917
|
return { start, end };
|
|
3863
3918
|
}
|
|
3864
|
-
var PERIOD_LS_KEY2, LiteLLMPage;
|
|
3919
|
+
var PERIOD_LS_KEY2, PAGE_TABS, isPageTab, LiteLLMPage;
|
|
3865
3920
|
var init_LiteLLMPage = __esm({
|
|
3866
3921
|
"src/components/LiteLLMPage.tsx"() {
|
|
3867
3922
|
"use strict";
|
|
@@ -3877,6 +3932,8 @@ var init_LiteLLMPage = __esm({
|
|
|
3877
3932
|
init_api();
|
|
3878
3933
|
init_permissions();
|
|
3879
3934
|
PERIOD_LS_KEY2 = "litellm_usage_period";
|
|
3935
|
+
PAGE_TABS = ["overview", "keys", "teams", "models", "audit"];
|
|
3936
|
+
isPageTab = (v) => !!v && PAGE_TABS.includes(v);
|
|
3880
3937
|
LiteLLMPage = () => {
|
|
3881
3938
|
const api = useApi3(liteLlmApiRef);
|
|
3882
3939
|
const [dateRange, setDateRange] = useState9(initDateRange);
|
|
@@ -3887,7 +3944,25 @@ var init_LiteLLMPage = __esm({
|
|
|
3887
3944
|
return "7d";
|
|
3888
3945
|
}
|
|
3889
3946
|
});
|
|
3890
|
-
const [
|
|
3947
|
+
const [searchParams, setSearchParams] = useSearchParams();
|
|
3948
|
+
const [activeTab, setActiveTab] = useState9(() => {
|
|
3949
|
+
const t = searchParams.get("tab");
|
|
3950
|
+
return isPageTab(t) ? t : "overview";
|
|
3951
|
+
});
|
|
3952
|
+
const selectTab = useCallback3(
|
|
3953
|
+
(tab) => {
|
|
3954
|
+
setActiveTab(tab);
|
|
3955
|
+
setSearchParams(
|
|
3956
|
+
(prev) => {
|
|
3957
|
+
const next = new URLSearchParams(prev);
|
|
3958
|
+
next.set("tab", tab);
|
|
3959
|
+
return next;
|
|
3960
|
+
},
|
|
3961
|
+
{ replace: true }
|
|
3962
|
+
);
|
|
3963
|
+
},
|
|
3964
|
+
[setSearchParams]
|
|
3965
|
+
);
|
|
3891
3966
|
const [snackbar, setSnackbar] = useState9(null);
|
|
3892
3967
|
const [generateDialogOpen, setGenerateDialogOpen] = useState9(false);
|
|
3893
3968
|
const [manageTeam, setManageTeam] = useState9(null);
|
|
@@ -4094,7 +4169,7 @@ var init_LiteLLMPage = __esm({
|
|
|
4094
4169
|
Tabs2,
|
|
4095
4170
|
{
|
|
4096
4171
|
value: activeTab,
|
|
4097
|
-
onChange: (_, v) =>
|
|
4172
|
+
onChange: (_, v) => selectTab(v),
|
|
4098
4173
|
variant: "scrollable",
|
|
4099
4174
|
scrollButtons: "auto",
|
|
4100
4175
|
sx: {
|