@acarmisc/backstage-plugin-litellm 0.21.0 → 0.21.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/budget.d.ts +4 -3
- package/dist/index.cjs.js +65 -30
- package/dist/index.cjs.js.map +2 -2
- package/dist/index.esm.js +65 -30
- package/dist/index.esm.js.map +2 -2
- package/package.json +1 -1
package/dist/budget.d.ts
CHANGED
|
@@ -41,12 +41,13 @@ export interface BudgetSummary {
|
|
|
41
41
|
export declare function buildBudgetSummary(user: UserInfo | null, teams: TeamInfo[], keys: VirtualKey[], maxKeys?: number): BudgetSummary;
|
|
42
42
|
/**
|
|
43
43
|
* One-line summary of a `BudgetSummary`, for a collapsed/compact header:
|
|
44
|
-
* how many concrete limits the user is subject to, and
|
|
45
|
-
*
|
|
44
|
+
* how many concrete limits the user is subject to, and the one closest to
|
|
45
|
+
* its cap — kept as the whole `BudgetLimit` so the caller can name its
|
|
46
|
+
* level (key / personal / team). `closest` is null when there are no limits.
|
|
46
47
|
*/
|
|
47
48
|
export declare function budgetHeadline(summary: BudgetSummary): {
|
|
48
49
|
count: number;
|
|
49
|
-
|
|
50
|
+
closest: BudgetLimit | null;
|
|
50
51
|
};
|
|
51
52
|
/** "30d" → "every 30 days"; "1d" → "daily". Returns null when duration is unset. */
|
|
52
53
|
export declare function fmtBudgetDuration(duration?: string): string | null;
|
package/dist/index.cjs.js
CHANGED
|
@@ -2809,16 +2809,14 @@ function buildBudgetSummary(user, teams, keys, maxKeys = 3) {
|
|
|
2809
2809
|
};
|
|
2810
2810
|
}
|
|
2811
2811
|
function budgetHeadline(summary) {
|
|
2812
|
-
const
|
|
2813
|
-
...summary.keys
|
|
2814
|
-
...summary.user ? [summary.user
|
|
2815
|
-
...summary.teams
|
|
2812
|
+
const all = [
|
|
2813
|
+
...summary.keys,
|
|
2814
|
+
...summary.user ? [summary.user] : [],
|
|
2815
|
+
...summary.teams
|
|
2816
2816
|
];
|
|
2817
2817
|
const count = summary.keys.length + summary.hiddenBudgetedKeys + (summary.user ? 1 : 0) + summary.teams.length;
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
closestPct: pcts.length ? Math.max(...pcts) : null
|
|
2821
|
-
};
|
|
2818
|
+
const closest = all.length ? all.reduce((a, b) => b.pct > a.pct ? b : a) : null;
|
|
2819
|
+
return { count, closest };
|
|
2822
2820
|
}
|
|
2823
2821
|
function fmtBudgetDuration(duration) {
|
|
2824
2822
|
if (!duration) return null;
|
|
@@ -2857,7 +2855,7 @@ function levelToneColor(tone, theme) {
|
|
|
2857
2855
|
return theme.palette.primary.main;
|
|
2858
2856
|
}
|
|
2859
2857
|
}
|
|
2860
|
-
var import_react7, import_Paper5, import_Box7, import_Typography7, import_CircularProgress4, import_Alert3, import_Collapse, import_IconButton4, import_icons_material5, import_styles4, import_core_plugin_api3, LevelFrame, EmptyLimitCard, LimitCard, TaggedLimit, FullBody, CompactBody, LiteLLMBudgetWidget;
|
|
2858
|
+
var import_react7, import_Paper5, import_Box7, import_Typography7, import_CircularProgress4, import_Alert3, import_Collapse, import_IconButton4, import_icons_material5, import_styles4, import_core_plugin_api3, import_core_components, LevelFrame, EmptyLimitCard, LimitCard, CLOSEST_LEVEL_LABEL, KEYS_PATH, MoreKeysNote, HierarchyNote, TaggedLimit, FullBody, CompactBody, LiteLLMBudgetWidget;
|
|
2861
2859
|
var init_LiteLLMBudgetWidget = __esm({
|
|
2862
2860
|
"src/components/LiteLLMBudgetWidget.tsx"() {
|
|
2863
2861
|
"use strict";
|
|
@@ -2872,6 +2870,7 @@ var init_LiteLLMBudgetWidget = __esm({
|
|
|
2872
2870
|
import_icons_material5 = require("@mui/icons-material");
|
|
2873
2871
|
import_styles4 = require("@mui/material/styles");
|
|
2874
2872
|
import_core_plugin_api3 = require("@backstage/core-plugin-api");
|
|
2873
|
+
import_core_components = require("@backstage/core-components");
|
|
2875
2874
|
init_api();
|
|
2876
2875
|
init_format();
|
|
2877
2876
|
init_ui();
|
|
@@ -2980,6 +2979,26 @@ var init_LiteLLMBudgetWidget = __esm({
|
|
|
2980
2979
|
/* @__PURE__ */ import_react7.default.createElement(import_Typography7.default, { variant: "caption", color: "text.secondary" }, fmtBudgetDuration(limit.budgetDuration) ? `resets ${fmtBudgetDuration(limit.budgetDuration)}` : "never resets")
|
|
2981
2980
|
));
|
|
2982
2981
|
};
|
|
2982
|
+
CLOSEST_LEVEL_LABEL = {
|
|
2983
|
+
key: "key",
|
|
2984
|
+
user: "personal",
|
|
2985
|
+
team: "team"
|
|
2986
|
+
};
|
|
2987
|
+
KEYS_PATH = "/litellm?tab=keys";
|
|
2988
|
+
MoreKeysNote = ({ count }) => /* @__PURE__ */ import_react7.default.createElement(import_core_components.Link, { to: KEYS_PATH, variant: "caption" }, "+", count, " more budgeted key", count > 1 ? "s" : "", " further from the cap \u2192");
|
|
2989
|
+
HierarchyNote = ({ withReset }) => /* @__PURE__ */ import_react7.default.createElement(
|
|
2990
|
+
import_Box7.default,
|
|
2991
|
+
{
|
|
2992
|
+
sx: (theme) => ({
|
|
2993
|
+
px: 1.5,
|
|
2994
|
+
py: 1.25,
|
|
2995
|
+
borderRadius: 1.5,
|
|
2996
|
+
border: "1px dashed",
|
|
2997
|
+
borderColor: (0, import_styles4.alpha)(theme.palette.text.primary, 0.18)
|
|
2998
|
+
})
|
|
2999
|
+
},
|
|
3000
|
+
/* @__PURE__ */ import_react7.default.createElement(import_Typography7.default, { variant: "caption", color: "text.secondary", display: "block" }, /* @__PURE__ */ import_react7.default.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.")
|
|
3001
|
+
);
|
|
2983
3002
|
TaggedLimit = ({ limit }) => {
|
|
2984
3003
|
const tone = budgetTone2(limit.pct);
|
|
2985
3004
|
return /* @__PURE__ */ import_react7.default.createElement(import_Box7.default, null, /* @__PURE__ */ import_react7.default.createElement(
|
|
@@ -3009,7 +3028,7 @@ var init_LiteLLMBudgetWidget = __esm({
|
|
|
3009
3028
|
},
|
|
3010
3029
|
!hasBudgetedKeys && /* @__PURE__ */ import_react7.default.createElement(EmptyLimitCard, null, "No key has a budget, so no key-level cap applies."),
|
|
3011
3030
|
summary.keys.map((k) => /* @__PURE__ */ import_react7.default.createElement(LimitCard, { key: k.sublabel ?? k.label, limit: k })),
|
|
3012
|
-
summary.hiddenBudgetedKeys > 0 && /* @__PURE__ */ import_react7.default.createElement(
|
|
3031
|
+
summary.hiddenBudgetedKeys > 0 && /* @__PURE__ */ import_react7.default.createElement(MoreKeysNote, { count: summary.hiddenBudgetedKeys })
|
|
3013
3032
|
), /* @__PURE__ */ import_react7.default.createElement(
|
|
3014
3033
|
LevelFrame,
|
|
3015
3034
|
{
|
|
@@ -3019,27 +3038,14 @@ var init_LiteLLMBudgetWidget = __esm({
|
|
|
3019
3038
|
note: "skipped for team-bound keys"
|
|
3020
3039
|
},
|
|
3021
3040
|
summary.user ? /* @__PURE__ */ import_react7.default.createElement(LimitCard, { limit: summary.user }) : /* @__PURE__ */ import_react7.default.createElement(EmptyLimitCard, null, "No personal budget is set on your account.")
|
|
3022
|
-
), /* @__PURE__ */ import_react7.default.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__ */ import_react7.default.createElement(LimitCard, { key: t.sublabel ?? t.label, limit: t })) : /* @__PURE__ */ import_react7.default.createElement(EmptyLimitCard, null, "No team you belong to has a budget.")), /* @__PURE__ */ import_react7.default.createElement(LevelFrame, { rank: 4, name: "Global", tagline: "proxy-wide cap set by your admin", isLast: true }, /* @__PURE__ */ import_react7.default.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__ */ import_react7.default.createElement(
|
|
3023
|
-
import_Box7.default,
|
|
3024
|
-
{
|
|
3025
|
-
sx: (theme) => ({
|
|
3026
|
-
mt: 2,
|
|
3027
|
-
px: 1.5,
|
|
3028
|
-
py: 1.25,
|
|
3029
|
-
borderRadius: 1.5,
|
|
3030
|
-
border: "1px dashed",
|
|
3031
|
-
borderColor: (0, import_styles4.alpha)(theme.palette.text.primary, 0.18)
|
|
3032
|
-
})
|
|
3033
|
-
},
|
|
3034
|
-
/* @__PURE__ */ import_react7.default.createElement(import_Typography7.default, { 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.")
|
|
3035
|
-
));
|
|
3041
|
+
), /* @__PURE__ */ import_react7.default.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__ */ import_react7.default.createElement(LimitCard, { key: t.sublabel ?? t.label, limit: t })) : /* @__PURE__ */ import_react7.default.createElement(EmptyLimitCard, null, "No team you belong to has a budget.")), /* @__PURE__ */ import_react7.default.createElement(LevelFrame, { rank: 4, name: "Global", tagline: "proxy-wide cap set by your admin", isLast: true }, /* @__PURE__ */ import_react7.default.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__ */ import_react7.default.createElement(import_Box7.default, { sx: { mt: 2 } }, /* @__PURE__ */ import_react7.default.createElement(HierarchyNote, { withReset: true })));
|
|
3036
3042
|
};
|
|
3037
3043
|
CompactBody = ({ summary }) => {
|
|
3038
3044
|
const { count } = budgetHeadline(summary);
|
|
3039
3045
|
if (count === 0) {
|
|
3040
3046
|
return /* @__PURE__ */ import_react7.default.createElement(import_Typography7.default, { variant: "body2", color: "text.secondary", sx: { fontSize: 13 } }, "No budget limits apply to you right now.");
|
|
3041
3047
|
}
|
|
3042
|
-
return /* @__PURE__ */ import_react7.default.createElement(import_Box7.default, { sx: { display: "flex", flexDirection: "column", gap: 1.25 } }, summary.keys.map((k) => /* @__PURE__ */ import_react7.default.createElement(TaggedLimit, { key: k.sublabel ?? k.label, limit: k })), summary.hiddenBudgetedKeys > 0 && /* @__PURE__ */ import_react7.default.createElement(
|
|
3048
|
+
return /* @__PURE__ */ import_react7.default.createElement(import_Box7.default, { sx: { display: "flex", flexDirection: "column", gap: 1.25 } }, /* @__PURE__ */ import_react7.default.createElement(HierarchyNote, null), summary.keys.map((k) => /* @__PURE__ */ import_react7.default.createElement(TaggedLimit, { key: k.sublabel ?? k.label, limit: k })), summary.hiddenBudgetedKeys > 0 && /* @__PURE__ */ import_react7.default.createElement(MoreKeysNote, { count: summary.hiddenBudgetedKeys }), summary.user && /* @__PURE__ */ import_react7.default.createElement(TaggedLimit, { limit: summary.user }), summary.teams.map((t) => /* @__PURE__ */ import_react7.default.createElement(TaggedLimit, { key: t.sublabel ?? t.label, limit: t })));
|
|
3043
3049
|
};
|
|
3044
3050
|
LiteLLMBudgetWidget = ({
|
|
3045
3051
|
title = "Budget Policy",
|
|
@@ -3086,7 +3092,9 @@ var init_LiteLLMBudgetWidget = __esm({
|
|
|
3086
3092
|
);
|
|
3087
3093
|
const headline = (0, import_react7.useMemo)(() => budgetHeadline(summary), [summary]);
|
|
3088
3094
|
const limitsWord = `${headline.count} limit${headline.count === 1 ? "" : "s"}`;
|
|
3089
|
-
const closestWord = headline.
|
|
3095
|
+
const closestWord = headline.closest === null ? "" : ` \xB7 closest: ${CLOSEST_LEVEL_LABEL[headline.closest.kind]} ${Math.round(
|
|
3096
|
+
headline.closest.pct
|
|
3097
|
+
)}%`;
|
|
3090
3098
|
const summaryText = headline.count === 0 ? "no limits apply" : `${limitsWord}${closestWord}`;
|
|
3091
3099
|
const body = /* @__PURE__ */ import_react7.default.createElement(import_react7.default.Fragment, null, loading && /* @__PURE__ */ import_react7.default.createElement(import_Box7.default, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: 140 }, /* @__PURE__ */ import_react7.default.createElement(import_CircularProgress4.default, { size: 32 })), !loading && error && /* @__PURE__ */ import_react7.default.createElement(import_Alert3.default, { severity: "error", sx: { mt: 0.5 } }, error), !loading && !error && (compact ? /* @__PURE__ */ import_react7.default.createElement(CompactBody, { summary }) : /* @__PURE__ */ import_react7.default.createElement(FullBody, { summary })));
|
|
3092
3100
|
return /* @__PURE__ */ import_react7.default.createElement(import_Paper5.default, { sx: { p: 2 } }, collapsible ? /* @__PURE__ */ import_react7.default.createElement(
|
|
@@ -3102,7 +3110,13 @@ var init_LiteLLMBudgetWidget = __esm({
|
|
|
3102
3110
|
mb: expanded ? 1.5 : 0
|
|
3103
3111
|
}
|
|
3104
3112
|
},
|
|
3105
|
-
/* @__PURE__ */ import_react7.default.createElement(import_Box7.default, { sx: { minWidth: 0, flex: 1
|
|
3113
|
+
/* @__PURE__ */ import_react7.default.createElement(import_Box7.default, { sx: { minWidth: 0, flex: 1 } }, /* @__PURE__ */ import_react7.default.createElement(import_Typography7.default, { variant: "h6", lineHeight: 1.2 }, title), !loading && !error && /* @__PURE__ */ import_react7.default.createElement(import_Box7.default, { sx: { mt: 0.5 } }, /* @__PURE__ */ import_react7.default.createElement(
|
|
3114
|
+
StatusPill,
|
|
3115
|
+
{
|
|
3116
|
+
label: summaryText,
|
|
3117
|
+
tone: budgetTone2(headline.closest?.pct ?? 0)
|
|
3118
|
+
}
|
|
3119
|
+
))),
|
|
3106
3120
|
/* @__PURE__ */ import_react7.default.createElement(
|
|
3107
3121
|
import_IconButton4.default,
|
|
3108
3122
|
{
|
|
@@ -3836,7 +3850,7 @@ function initDateRange() {
|
|
|
3836
3850
|
else start.setDate(start.getDate() - 7);
|
|
3837
3851
|
return { start, end };
|
|
3838
3852
|
}
|
|
3839
|
-
var import_react11, import_Box11, import_Snackbar2, import_Alert6, import_CircularProgress6, import_Typography11, import_Paper7, import_Tabs2, import_Tab2, import_Grid2, import_react_use3, import_core_plugin_api4, import_plugin_permission_react, PERIOD_LS_KEY2, LiteLLMPage;
|
|
3853
|
+
var import_react11, import_Box11, import_Snackbar2, import_Alert6, import_CircularProgress6, import_Typography11, import_Paper7, import_Tabs2, import_Tab2, import_Grid2, import_react_router_dom, import_react_use3, import_core_plugin_api4, import_plugin_permission_react, PERIOD_LS_KEY2, PAGE_TABS, isPageTab, LiteLLMPage;
|
|
3840
3854
|
var init_LiteLLMPage = __esm({
|
|
3841
3855
|
"src/components/LiteLLMPage.tsx"() {
|
|
3842
3856
|
"use strict";
|
|
@@ -3850,6 +3864,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3850
3864
|
import_Tabs2 = __toESM(require("@mui/material/Tabs"));
|
|
3851
3865
|
import_Tab2 = __toESM(require("@mui/material/Tab"));
|
|
3852
3866
|
import_Grid2 = __toESM(require("@mui/material/Grid"));
|
|
3867
|
+
import_react_router_dom = require("react-router-dom");
|
|
3853
3868
|
import_react_use3 = require("react-use");
|
|
3854
3869
|
import_core_plugin_api4 = require("@backstage/core-plugin-api");
|
|
3855
3870
|
import_plugin_permission_react = require("@backstage/plugin-permission-react");
|
|
@@ -3865,6 +3880,8 @@ var init_LiteLLMPage = __esm({
|
|
|
3865
3880
|
init_api();
|
|
3866
3881
|
init_permissions();
|
|
3867
3882
|
PERIOD_LS_KEY2 = "litellm_usage_period";
|
|
3883
|
+
PAGE_TABS = ["overview", "keys", "teams", "models", "audit"];
|
|
3884
|
+
isPageTab = (v) => !!v && PAGE_TABS.includes(v);
|
|
3868
3885
|
LiteLLMPage = () => {
|
|
3869
3886
|
const api = (0, import_core_plugin_api4.useApi)(liteLlmApiRef);
|
|
3870
3887
|
const [dateRange, setDateRange] = (0, import_react11.useState)(initDateRange);
|
|
@@ -3875,7 +3892,25 @@ var init_LiteLLMPage = __esm({
|
|
|
3875
3892
|
return "7d";
|
|
3876
3893
|
}
|
|
3877
3894
|
});
|
|
3878
|
-
const [
|
|
3895
|
+
const [searchParams, setSearchParams] = (0, import_react_router_dom.useSearchParams)();
|
|
3896
|
+
const [activeTab, setActiveTab] = (0, import_react11.useState)(() => {
|
|
3897
|
+
const t = searchParams.get("tab");
|
|
3898
|
+
return isPageTab(t) ? t : "overview";
|
|
3899
|
+
});
|
|
3900
|
+
const selectTab = (0, import_react11.useCallback)(
|
|
3901
|
+
(tab) => {
|
|
3902
|
+
setActiveTab(tab);
|
|
3903
|
+
setSearchParams(
|
|
3904
|
+
(prev) => {
|
|
3905
|
+
const next = new URLSearchParams(prev);
|
|
3906
|
+
next.set("tab", tab);
|
|
3907
|
+
return next;
|
|
3908
|
+
},
|
|
3909
|
+
{ replace: true }
|
|
3910
|
+
);
|
|
3911
|
+
},
|
|
3912
|
+
[setSearchParams]
|
|
3913
|
+
);
|
|
3879
3914
|
const [snackbar, setSnackbar] = (0, import_react11.useState)(null);
|
|
3880
3915
|
const [generateDialogOpen, setGenerateDialogOpen] = (0, import_react11.useState)(false);
|
|
3881
3916
|
const [manageTeam, setManageTeam] = (0, import_react11.useState)(null);
|
|
@@ -4082,7 +4117,7 @@ var init_LiteLLMPage = __esm({
|
|
|
4082
4117
|
import_Tabs2.default,
|
|
4083
4118
|
{
|
|
4084
4119
|
value: activeTab,
|
|
4085
|
-
onChange: (_, v) =>
|
|
4120
|
+
onChange: (_, v) => selectTab(v),
|
|
4086
4121
|
variant: "scrollable",
|
|
4087
4122
|
scrollButtons: "auto",
|
|
4088
4123
|
sx: {
|