@acarmisc/backstage-plugin-litellm 0.20.0 → 0.21.1
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 +10 -0
- package/dist/components/LiteLLMBudgetWidget.d.ts +22 -0
- package/dist/index.cjs.js +601 -481
- package/dist/index.cjs.js.map +4 -4
- package/dist/index.esm.js +580 -460
- package/dist/index.esm.js.map +4 -4
- package/package.json +1 -1
package/dist/index.cjs.js
CHANGED
|
@@ -2756,20 +2756,411 @@ var init_UsageStats = __esm({
|
|
|
2756
2756
|
}
|
|
2757
2757
|
});
|
|
2758
2758
|
|
|
2759
|
+
// src/budget.ts
|
|
2760
|
+
function limitPct(spend, budget) {
|
|
2761
|
+
if (budget <= 0) return 0;
|
|
2762
|
+
return spend / budget * 100;
|
|
2763
|
+
}
|
|
2764
|
+
function buildBudgetSummary(user, teams, keys, maxKeys = 3) {
|
|
2765
|
+
const budgeted = keys.filter((k) => (k.max_budget ?? 0) > 0).map((k) => {
|
|
2766
|
+
const spend = k.spend ?? 0;
|
|
2767
|
+
return {
|
|
2768
|
+
kind: "key",
|
|
2769
|
+
label: k.key_alias || k.key || "Untitled key",
|
|
2770
|
+
sublabel: k.key,
|
|
2771
|
+
budget: k.max_budget,
|
|
2772
|
+
spend,
|
|
2773
|
+
budgetDuration: k.budget_duration,
|
|
2774
|
+
pct: limitPct(spend, k.max_budget)
|
|
2775
|
+
};
|
|
2776
|
+
}).sort((a, b) => b.pct - a.pct);
|
|
2777
|
+
const userBudget = user && (user.max_budget ?? user.hard_limit ?? 0) > 0 ? (() => {
|
|
2778
|
+
const budget = user.max_budget ?? user.hard_limit ?? 0;
|
|
2779
|
+
const spend = user.current_spend ?? user.spend ?? 0;
|
|
2780
|
+
return {
|
|
2781
|
+
kind: "user",
|
|
2782
|
+
label: "Personal budget",
|
|
2783
|
+
sublabel: user.user_email || user.user_id,
|
|
2784
|
+
budget,
|
|
2785
|
+
spend,
|
|
2786
|
+
budgetDuration: user.budget_duration,
|
|
2787
|
+
softLimit: user.soft_limit,
|
|
2788
|
+
pct: limitPct(spend, budget)
|
|
2789
|
+
};
|
|
2790
|
+
})() : void 0;
|
|
2791
|
+
const teamLimits = teams.filter((t) => (t.max_budget ?? 0) > 0).map((t) => {
|
|
2792
|
+
const spend = t.spend ?? 0;
|
|
2793
|
+
return {
|
|
2794
|
+
kind: "team",
|
|
2795
|
+
label: t.team_alias || "Untitled team",
|
|
2796
|
+
sublabel: t.team_id,
|
|
2797
|
+
budget: t.max_budget,
|
|
2798
|
+
spend,
|
|
2799
|
+
budgetDuration: t.budget_duration,
|
|
2800
|
+
pct: limitPct(spend, t.max_budget)
|
|
2801
|
+
};
|
|
2802
|
+
}).sort((a, b) => b.pct - a.pct);
|
|
2803
|
+
return {
|
|
2804
|
+
keys: budgeted.slice(0, maxKeys),
|
|
2805
|
+
hiddenBudgetedKeys: Math.max(0, budgeted.length - maxKeys),
|
|
2806
|
+
totalKeyCount: keys.length,
|
|
2807
|
+
user: userBudget,
|
|
2808
|
+
teams: teamLimits
|
|
2809
|
+
};
|
|
2810
|
+
}
|
|
2811
|
+
function budgetHeadline(summary) {
|
|
2812
|
+
const all = [
|
|
2813
|
+
...summary.keys,
|
|
2814
|
+
...summary.user ? [summary.user] : [],
|
|
2815
|
+
...summary.teams
|
|
2816
|
+
];
|
|
2817
|
+
const count = summary.keys.length + summary.hiddenBudgetedKeys + (summary.user ? 1 : 0) + summary.teams.length;
|
|
2818
|
+
const closest = all.length ? all.reduce((a, b) => b.pct > a.pct ? b : a) : null;
|
|
2819
|
+
return { count, closest };
|
|
2820
|
+
}
|
|
2821
|
+
function fmtBudgetDuration(duration) {
|
|
2822
|
+
if (!duration) return null;
|
|
2823
|
+
const m = /^(\d+)(s|m|h|d)$/.exec(duration);
|
|
2824
|
+
if (!m) return duration;
|
|
2825
|
+
const n = Number(m[1]);
|
|
2826
|
+
const unit = m[2];
|
|
2827
|
+
if (unit === "d") return n === 1 ? "daily" : `every ${n} days`;
|
|
2828
|
+
if (unit === "h") return n === 1 ? "hourly" : `every ${n} hours`;
|
|
2829
|
+
if (unit === "m") return n === 1 ? "every minute" : `every ${n} minutes`;
|
|
2830
|
+
return n === 1 ? "every second" : `every ${n} seconds`;
|
|
2831
|
+
}
|
|
2832
|
+
function budgetTone2(pct) {
|
|
2833
|
+
if (pct >= 100) return "danger";
|
|
2834
|
+
if (pct >= 80) return "warning";
|
|
2835
|
+
return "accent";
|
|
2836
|
+
}
|
|
2837
|
+
var init_budget = __esm({
|
|
2838
|
+
"src/budget.ts"() {
|
|
2839
|
+
"use strict";
|
|
2840
|
+
}
|
|
2841
|
+
});
|
|
2842
|
+
|
|
2843
|
+
// src/components/LiteLLMBudgetWidget.tsx
|
|
2844
|
+
function levelToneColor(tone, theme) {
|
|
2845
|
+
switch (tone) {
|
|
2846
|
+
case "success":
|
|
2847
|
+
return theme.palette.success.main;
|
|
2848
|
+
case "warning":
|
|
2849
|
+
return theme.palette.warning.main;
|
|
2850
|
+
case "danger":
|
|
2851
|
+
return theme.palette.error.main;
|
|
2852
|
+
case "info":
|
|
2853
|
+
return theme.palette.info.main;
|
|
2854
|
+
default:
|
|
2855
|
+
return theme.palette.primary.main;
|
|
2856
|
+
}
|
|
2857
|
+
}
|
|
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, LevelFrame, EmptyLimitCard, LimitCard, CLOSEST_LEVEL_LABEL, TaggedLimit, FullBody, CompactBody, LiteLLMBudgetWidget;
|
|
2859
|
+
var init_LiteLLMBudgetWidget = __esm({
|
|
2860
|
+
"src/components/LiteLLMBudgetWidget.tsx"() {
|
|
2861
|
+
"use strict";
|
|
2862
|
+
import_react7 = __toESM(require("react"));
|
|
2863
|
+
import_Paper5 = __toESM(require("@mui/material/Paper"));
|
|
2864
|
+
import_Box7 = __toESM(require("@mui/material/Box"));
|
|
2865
|
+
import_Typography7 = __toESM(require("@mui/material/Typography"));
|
|
2866
|
+
import_CircularProgress4 = __toESM(require("@mui/material/CircularProgress"));
|
|
2867
|
+
import_Alert3 = __toESM(require("@mui/material/Alert"));
|
|
2868
|
+
import_Collapse = __toESM(require("@mui/material/Collapse"));
|
|
2869
|
+
import_IconButton4 = __toESM(require("@mui/material/IconButton"));
|
|
2870
|
+
import_icons_material5 = require("@mui/icons-material");
|
|
2871
|
+
import_styles4 = require("@mui/material/styles");
|
|
2872
|
+
import_core_plugin_api3 = require("@backstage/core-plugin-api");
|
|
2873
|
+
init_api();
|
|
2874
|
+
init_format();
|
|
2875
|
+
init_ui();
|
|
2876
|
+
init_budget();
|
|
2877
|
+
LevelFrame = ({
|
|
2878
|
+
rank,
|
|
2879
|
+
name,
|
|
2880
|
+
tagline,
|
|
2881
|
+
note,
|
|
2882
|
+
tone,
|
|
2883
|
+
isLast,
|
|
2884
|
+
children
|
|
2885
|
+
}) => /* @__PURE__ */ import_react7.default.createElement(import_Box7.default, { sx: { display: "flex", gap: 1.5 } }, /* @__PURE__ */ import_react7.default.createElement(import_Box7.default, { sx: { display: "flex", flexDirection: "column", alignItems: "center", width: 26, flexShrink: 0 } }, /* @__PURE__ */ import_react7.default.createElement(
|
|
2886
|
+
import_Box7.default,
|
|
2887
|
+
{
|
|
2888
|
+
sx: (theme) => ({
|
|
2889
|
+
width: 26,
|
|
2890
|
+
height: 26,
|
|
2891
|
+
borderRadius: "50%",
|
|
2892
|
+
display: "flex",
|
|
2893
|
+
alignItems: "center",
|
|
2894
|
+
justifyContent: "center",
|
|
2895
|
+
bgcolor: (0, import_styles4.alpha)(levelToneColor(tone, theme), 0.14),
|
|
2896
|
+
color: levelToneColor(tone, theme),
|
|
2897
|
+
fontSize: 12,
|
|
2898
|
+
fontWeight: 700,
|
|
2899
|
+
flexShrink: 0
|
|
2900
|
+
})
|
|
2901
|
+
},
|
|
2902
|
+
rank
|
|
2903
|
+
), /* @__PURE__ */ import_react7.default.createElement(
|
|
2904
|
+
import_Box7.default,
|
|
2905
|
+
{
|
|
2906
|
+
sx: (theme) => ({
|
|
2907
|
+
flex: 1,
|
|
2908
|
+
width: 2,
|
|
2909
|
+
minHeight: 14,
|
|
2910
|
+
bgcolor: isLast ? "transparent" : (0, import_styles4.alpha)(theme.palette.text.primary, 0.12)
|
|
2911
|
+
})
|
|
2912
|
+
}
|
|
2913
|
+
)), /* @__PURE__ */ import_react7.default.createElement(import_Box7.default, { sx: { flex: 1, minWidth: 0, pb: isLast ? 0 : 2 } }, /* @__PURE__ */ import_react7.default.createElement(import_Box7.default, { sx: { display: "flex", alignItems: "baseline", gap: 1, flexWrap: "wrap", mb: 0.75 } }, /* @__PURE__ */ import_react7.default.createElement(
|
|
2914
|
+
import_Typography7.default,
|
|
2915
|
+
{
|
|
2916
|
+
sx: (theme) => ({
|
|
2917
|
+
fontSize: 12.5,
|
|
2918
|
+
fontWeight: 700,
|
|
2919
|
+
letterSpacing: "0.08em",
|
|
2920
|
+
textTransform: "uppercase",
|
|
2921
|
+
color: levelToneColor(tone, theme)
|
|
2922
|
+
})
|
|
2923
|
+
},
|
|
2924
|
+
name
|
|
2925
|
+
), /* @__PURE__ */ import_react7.default.createElement(import_Typography7.default, { variant: "caption", color: "text.secondary", sx: { fontSize: 11.5 } }, tagline), note && /* @__PURE__ */ import_react7.default.createElement(StatusPill, { label: note, tone: "neutral", dot: false, sx: { ml: "auto" } })), /* @__PURE__ */ import_react7.default.createElement(import_Box7.default, { sx: { display: "flex", flexDirection: "column", gap: 1 } }, children)));
|
|
2926
|
+
EmptyLimitCard = ({ children }) => /* @__PURE__ */ import_react7.default.createElement(
|
|
2927
|
+
import_Paper5.default,
|
|
2928
|
+
{
|
|
2929
|
+
variant: "outlined",
|
|
2930
|
+
sx: (theme) => ({
|
|
2931
|
+
px: 1.5,
|
|
2932
|
+
py: 1.25,
|
|
2933
|
+
borderRadius: 1.5,
|
|
2934
|
+
bgcolor: (0, import_styles4.alpha)(theme.palette.text.primary, theme.palette.mode === "dark" ? 0.03 : 0.015)
|
|
2935
|
+
})
|
|
2936
|
+
},
|
|
2937
|
+
/* @__PURE__ */ import_react7.default.createElement(import_Typography7.default, { variant: "body2", color: "text.secondary", sx: { fontSize: 13 } }, children)
|
|
2938
|
+
);
|
|
2939
|
+
LimitCard = ({ limit }) => {
|
|
2940
|
+
const tone = budgetTone2(limit.pct);
|
|
2941
|
+
const pct = Math.round(limit.pct);
|
|
2942
|
+
const closeTo = limit.softLimit !== void 0 && limit.spend >= limit.softLimit;
|
|
2943
|
+
return /* @__PURE__ */ import_react7.default.createElement(import_Paper5.default, { variant: "outlined", sx: { px: 1.5, py: 1.25, borderRadius: 1.5 } }, /* @__PURE__ */ import_react7.default.createElement(import_Box7.default, { sx: { display: "flex", alignItems: "baseline", justifyContent: "space-between", gap: 1.5 } }, /* @__PURE__ */ import_react7.default.createElement(import_Box7.default, { sx: { minWidth: 0, flex: 1 } }, /* @__PURE__ */ import_react7.default.createElement(
|
|
2944
|
+
import_Typography7.default,
|
|
2945
|
+
{
|
|
2946
|
+
variant: "body2",
|
|
2947
|
+
sx: { fontWeight: 600, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" },
|
|
2948
|
+
title: limit.sublabel ? `${limit.label} \xB7 ${limit.sublabel}` : limit.label
|
|
2949
|
+
},
|
|
2950
|
+
limit.label
|
|
2951
|
+
), limit.sublabel && /* @__PURE__ */ import_react7.default.createElement(
|
|
2952
|
+
import_Typography7.default,
|
|
2953
|
+
{
|
|
2954
|
+
variant: "caption",
|
|
2955
|
+
color: "text.secondary",
|
|
2956
|
+
sx: {
|
|
2957
|
+
fontSize: 10.5,
|
|
2958
|
+
fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
|
|
2959
|
+
display: "block",
|
|
2960
|
+
overflow: "hidden",
|
|
2961
|
+
textOverflow: "ellipsis",
|
|
2962
|
+
whiteSpace: "nowrap"
|
|
2963
|
+
}
|
|
2964
|
+
},
|
|
2965
|
+
limit.sublabel
|
|
2966
|
+
)), /* @__PURE__ */ import_react7.default.createElement(import_Typography7.default, { variant: "body2", sx: { fontWeight: 600, fontVariantNumeric: "tabular-nums", whiteSpace: "nowrap" } }, fmtUsd(limit.spend), /* @__PURE__ */ import_react7.default.createElement(import_Box7.default, { component: "span", color: "text.secondary", sx: { fontWeight: 400 } }, " / ", fmtUsd(limit.budget)))), /* @__PURE__ */ import_react7.default.createElement(import_Box7.default, { sx: { mt: 1 } }, /* @__PURE__ */ import_react7.default.createElement(Meter, { value: limit.pct, tone, height: 6 })), /* @__PURE__ */ import_react7.default.createElement(
|
|
2967
|
+
import_Box7.default,
|
|
2968
|
+
{
|
|
2969
|
+
sx: {
|
|
2970
|
+
display: "flex",
|
|
2971
|
+
justifyContent: "space-between",
|
|
2972
|
+
alignItems: "center",
|
|
2973
|
+
gap: 1,
|
|
2974
|
+
mt: 0.75
|
|
2975
|
+
}
|
|
2976
|
+
},
|
|
2977
|
+
/* @__PURE__ */ import_react7.default.createElement(import_Typography7.default, { variant: "caption", color: "text.secondary", sx: { fontVariantNumeric: "tabular-nums" } }, pct, "% of budget", closeTo && ` \xB7 soft-limit warning`),
|
|
2978
|
+
/* @__PURE__ */ import_react7.default.createElement(import_Typography7.default, { variant: "caption", color: "text.secondary" }, fmtBudgetDuration(limit.budgetDuration) ? `resets ${fmtBudgetDuration(limit.budgetDuration)}` : "never resets")
|
|
2979
|
+
));
|
|
2980
|
+
};
|
|
2981
|
+
CLOSEST_LEVEL_LABEL = {
|
|
2982
|
+
key: "key",
|
|
2983
|
+
user: "personal",
|
|
2984
|
+
team: "team"
|
|
2985
|
+
};
|
|
2986
|
+
TaggedLimit = ({ limit }) => {
|
|
2987
|
+
const tone = budgetTone2(limit.pct);
|
|
2988
|
+
return /* @__PURE__ */ import_react7.default.createElement(import_Box7.default, null, /* @__PURE__ */ import_react7.default.createElement(
|
|
2989
|
+
import_Typography7.default,
|
|
2990
|
+
{
|
|
2991
|
+
sx: (theme) => ({
|
|
2992
|
+
fontSize: 11,
|
|
2993
|
+
fontWeight: 700,
|
|
2994
|
+
letterSpacing: "0.08em",
|
|
2995
|
+
textTransform: "uppercase",
|
|
2996
|
+
color: tone === "accent" ? theme.palette.text.secondary : levelToneColor(tone, theme),
|
|
2997
|
+
mb: 0.5
|
|
2998
|
+
})
|
|
2999
|
+
},
|
|
3000
|
+
limit.kind
|
|
3001
|
+
), /* @__PURE__ */ import_react7.default.createElement(LimitCard, { limit }));
|
|
3002
|
+
};
|
|
3003
|
+
FullBody = ({ summary }) => {
|
|
3004
|
+
const hasBudgetedKeys = summary.keys.length + summary.hiddenBudgetedKeys > 0;
|
|
3005
|
+
return /* @__PURE__ */ import_react7.default.createElement(import_react7.default.Fragment, null, /* @__PURE__ */ import_react7.default.createElement(
|
|
3006
|
+
LevelFrame,
|
|
3007
|
+
{
|
|
3008
|
+
rank: 1,
|
|
3009
|
+
name: "Key",
|
|
3010
|
+
tagline: "per-key cap \xB7 highest priority",
|
|
3011
|
+
note: hasBudgetedKeys ? void 0 : "no key budgets set"
|
|
3012
|
+
},
|
|
3013
|
+
!hasBudgetedKeys && /* @__PURE__ */ import_react7.default.createElement(EmptyLimitCard, null, "No key has a budget, so no key-level cap applies."),
|
|
3014
|
+
summary.keys.map((k) => /* @__PURE__ */ import_react7.default.createElement(LimitCard, { key: k.sublabel ?? k.label, limit: k })),
|
|
3015
|
+
summary.hiddenBudgetedKeys > 0 && /* @__PURE__ */ import_react7.default.createElement(import_Typography7.default, { variant: "caption", color: "text.secondary" }, "+", summary.hiddenBudgetedKeys, " more budgeted key", summary.hiddenBudgetedKeys > 1 ? "s" : "", " further from the cap")
|
|
3016
|
+
), /* @__PURE__ */ import_react7.default.createElement(
|
|
3017
|
+
LevelFrame,
|
|
3018
|
+
{
|
|
3019
|
+
rank: 2,
|
|
3020
|
+
name: "User",
|
|
3021
|
+
tagline: "cap on everything you spend with your own keys",
|
|
3022
|
+
note: "skipped for team-bound keys"
|
|
3023
|
+
},
|
|
3024
|
+
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.")
|
|
3025
|
+
), /* @__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(
|
|
3026
|
+
import_Box7.default,
|
|
3027
|
+
{
|
|
3028
|
+
sx: (theme) => ({
|
|
3029
|
+
mt: 2,
|
|
3030
|
+
px: 1.5,
|
|
3031
|
+
py: 1.25,
|
|
3032
|
+
borderRadius: 1.5,
|
|
3033
|
+
border: "1px dashed",
|
|
3034
|
+
borderColor: (0, import_styles4.alpha)(theme.palette.text.primary, 0.18)
|
|
3035
|
+
})
|
|
3036
|
+
},
|
|
3037
|
+
/* @__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.")
|
|
3038
|
+
));
|
|
3039
|
+
};
|
|
3040
|
+
CompactBody = ({ summary }) => {
|
|
3041
|
+
const { count } = budgetHeadline(summary);
|
|
3042
|
+
if (count === 0) {
|
|
3043
|
+
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.");
|
|
3044
|
+
}
|
|
3045
|
+
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(import_Typography7.default, { variant: "caption", color: "text.secondary" }, "+", summary.hiddenBudgetedKeys, " more budgeted key", summary.hiddenBudgetedKeys > 1 ? "s" : "", " further from the cap"), 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 })));
|
|
3046
|
+
};
|
|
3047
|
+
LiteLLMBudgetWidget = ({
|
|
3048
|
+
title = "Budget Policy",
|
|
3049
|
+
maxKeys = 3,
|
|
3050
|
+
compact = false,
|
|
3051
|
+
collapsible = false,
|
|
3052
|
+
defaultExpanded = true,
|
|
3053
|
+
action
|
|
3054
|
+
}) => {
|
|
3055
|
+
const api = (0, import_core_plugin_api3.useApi)(liteLlmApiRef);
|
|
3056
|
+
const [loading, setLoading] = (0, import_react7.useState)(true);
|
|
3057
|
+
const [error, setError] = (0, import_react7.useState)(null);
|
|
3058
|
+
const [user, setUser] = (0, import_react7.useState)(null);
|
|
3059
|
+
const [teams, setTeams] = (0, import_react7.useState)([]);
|
|
3060
|
+
const [keys, setKeys] = (0, import_react7.useState)([]);
|
|
3061
|
+
const [expanded, setExpanded] = (0, import_react7.useState)(defaultExpanded);
|
|
3062
|
+
(0, import_react7.useEffect)(() => {
|
|
3063
|
+
let cancelled = false;
|
|
3064
|
+
setLoading(true);
|
|
3065
|
+
setError(null);
|
|
3066
|
+
Promise.allSettled([api.getUserInfo(), api.getTeams(), api.listKeys()]).then(
|
|
3067
|
+
([userResult, teamsResult, keysResult]) => {
|
|
3068
|
+
if (cancelled) return;
|
|
3069
|
+
if (userResult.status === "fulfilled") {
|
|
3070
|
+
setUser(userResult.value);
|
|
3071
|
+
} else {
|
|
3072
|
+
setUser(null);
|
|
3073
|
+
setError(
|
|
3074
|
+
userResult.reason?.message ?? "Failed to load your LiteLLM profile"
|
|
3075
|
+
);
|
|
3076
|
+
}
|
|
3077
|
+
setTeams(teamsResult.status === "fulfilled" ? teamsResult.value : []);
|
|
3078
|
+
setKeys(keysResult.status === "fulfilled" ? keysResult.value : []);
|
|
3079
|
+
setLoading(false);
|
|
3080
|
+
}
|
|
3081
|
+
);
|
|
3082
|
+
return () => {
|
|
3083
|
+
cancelled = true;
|
|
3084
|
+
};
|
|
3085
|
+
}, [api]);
|
|
3086
|
+
const summary = (0, import_react7.useMemo)(
|
|
3087
|
+
() => buildBudgetSummary(user, teams, keys, maxKeys),
|
|
3088
|
+
[user, teams, keys, maxKeys]
|
|
3089
|
+
);
|
|
3090
|
+
const headline = (0, import_react7.useMemo)(() => budgetHeadline(summary), [summary]);
|
|
3091
|
+
const limitsWord = `${headline.count} limit${headline.count === 1 ? "" : "s"}`;
|
|
3092
|
+
const closestWord = headline.closest === null ? "" : ` \xB7 closest: ${CLOSEST_LEVEL_LABEL[headline.closest.kind]} ${Math.round(
|
|
3093
|
+
headline.closest.pct
|
|
3094
|
+
)}%`;
|
|
3095
|
+
const summaryText = headline.count === 0 ? "no limits apply" : `${limitsWord}${closestWord}`;
|
|
3096
|
+
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 })));
|
|
3097
|
+
return /* @__PURE__ */ import_react7.default.createElement(import_Paper5.default, { sx: { p: 2 } }, collapsible ? /* @__PURE__ */ import_react7.default.createElement(
|
|
3098
|
+
import_Box7.default,
|
|
3099
|
+
{
|
|
3100
|
+
onClick: () => setExpanded((e) => !e),
|
|
3101
|
+
sx: {
|
|
3102
|
+
display: "flex",
|
|
3103
|
+
alignItems: "center",
|
|
3104
|
+
gap: 1,
|
|
3105
|
+
cursor: "pointer",
|
|
3106
|
+
userSelect: "none",
|
|
3107
|
+
mb: expanded ? 1.5 : 0
|
|
3108
|
+
}
|
|
3109
|
+
},
|
|
3110
|
+
/* @__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(
|
|
3111
|
+
StatusPill,
|
|
3112
|
+
{
|
|
3113
|
+
label: summaryText,
|
|
3114
|
+
tone: budgetTone2(headline.closest?.pct ?? 0)
|
|
3115
|
+
}
|
|
3116
|
+
))),
|
|
3117
|
+
/* @__PURE__ */ import_react7.default.createElement(
|
|
3118
|
+
import_IconButton4.default,
|
|
3119
|
+
{
|
|
3120
|
+
size: "small",
|
|
3121
|
+
"aria-label": expanded ? "Collapse budget policy" : "Expand budget policy",
|
|
3122
|
+
"aria-expanded": expanded,
|
|
3123
|
+
onClick: (e) => {
|
|
3124
|
+
e.stopPropagation();
|
|
3125
|
+
setExpanded((x) => !x);
|
|
3126
|
+
},
|
|
3127
|
+
sx: (theme) => ({
|
|
3128
|
+
color: theme.palette.text.secondary,
|
|
3129
|
+
transform: expanded ? "rotate(180deg)" : "none",
|
|
3130
|
+
transition: theme.transitions.create("transform")
|
|
3131
|
+
})
|
|
3132
|
+
},
|
|
3133
|
+
/* @__PURE__ */ import_react7.default.createElement(import_icons_material5.ExpandMore, null)
|
|
3134
|
+
)
|
|
3135
|
+
) : /* @__PURE__ */ import_react7.default.createElement(import_Box7.default, { sx: { mb: 1.5 } }, /* @__PURE__ */ import_react7.default.createElement(import_Typography7.default, { variant: "h6", lineHeight: 1.2 }, title), /* @__PURE__ */ import_react7.default.createElement(import_Typography7.default, { variant: "caption", color: "text.secondary" }, "How LiteLLM caps spend \u2014 and where you stand right now")), collapsible ? /* @__PURE__ */ import_react7.default.createElement(import_Collapse.default, { in: expanded, unmountOnExit: true }, body) : body, action && /* @__PURE__ */ import_react7.default.createElement(
|
|
3136
|
+
import_Box7.default,
|
|
3137
|
+
{
|
|
3138
|
+
sx: (theme) => ({
|
|
3139
|
+
mt: 2,
|
|
3140
|
+
pt: 2,
|
|
3141
|
+
borderTop: `1px solid ${theme.palette.divider}`
|
|
3142
|
+
})
|
|
3143
|
+
},
|
|
3144
|
+
action
|
|
3145
|
+
));
|
|
3146
|
+
};
|
|
3147
|
+
}
|
|
3148
|
+
});
|
|
3149
|
+
|
|
2759
3150
|
// src/components/TeamUsage.tsx
|
|
2760
|
-
function
|
|
3151
|
+
function budgetTone3(isOver, isNear) {
|
|
2761
3152
|
if (isOver) return "danger";
|
|
2762
3153
|
if (isNear) return "warning";
|
|
2763
3154
|
return "accent";
|
|
2764
3155
|
}
|
|
2765
|
-
var
|
|
3156
|
+
var import_react8, import_Paper6, import_Box8, import_Typography8, import_Skeleton4, import_Divider3, import_Table4, import_TableBody4, import_TableCell4, import_TableHead4, import_TableRow4, import_TableContainer4, import_Collapse2, import_IconButton5, import_Button5, import_CircularProgress5, import_styles5, import_icons_material6, import_recharts2, fmtUsd2, TeamCard, TeamUsage;
|
|
2766
3157
|
var init_TeamUsage = __esm({
|
|
2767
3158
|
"src/components/TeamUsage.tsx"() {
|
|
2768
3159
|
"use strict";
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
3160
|
+
import_react8 = __toESM(require("react"));
|
|
3161
|
+
import_Paper6 = __toESM(require("@mui/material/Paper"));
|
|
3162
|
+
import_Box8 = __toESM(require("@mui/material/Box"));
|
|
3163
|
+
import_Typography8 = __toESM(require("@mui/material/Typography"));
|
|
2773
3164
|
import_Skeleton4 = __toESM(require("@mui/material/Skeleton"));
|
|
2774
3165
|
import_Divider3 = __toESM(require("@mui/material/Divider"));
|
|
2775
3166
|
import_Table4 = __toESM(require("@mui/material/Table"));
|
|
@@ -2778,17 +3169,17 @@ var init_TeamUsage = __esm({
|
|
|
2778
3169
|
import_TableHead4 = __toESM(require("@mui/material/TableHead"));
|
|
2779
3170
|
import_TableRow4 = __toESM(require("@mui/material/TableRow"));
|
|
2780
3171
|
import_TableContainer4 = __toESM(require("@mui/material/TableContainer"));
|
|
2781
|
-
|
|
2782
|
-
|
|
3172
|
+
import_Collapse2 = __toESM(require("@mui/material/Collapse"));
|
|
3173
|
+
import_IconButton5 = __toESM(require("@mui/material/IconButton"));
|
|
2783
3174
|
import_Button5 = __toESM(require("@mui/material/Button"));
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
3175
|
+
import_CircularProgress5 = __toESM(require("@mui/material/CircularProgress"));
|
|
3176
|
+
import_styles5 = require("@mui/material/styles");
|
|
3177
|
+
import_icons_material6 = require("@mui/icons-material");
|
|
2787
3178
|
import_recharts2 = require("recharts");
|
|
2788
3179
|
init_ui();
|
|
2789
3180
|
fmtUsd2 = (n) => `$${(n ?? 0).toFixed(2)}`;
|
|
2790
3181
|
TeamCard = ({ team, usage, usageLoading, canManage, onEditTeam }) => {
|
|
2791
|
-
const [expanded, setExpanded] = (0,
|
|
3182
|
+
const [expanded, setExpanded] = (0, import_react8.useState)(false);
|
|
2792
3183
|
const chart = useChartTheme();
|
|
2793
3184
|
const budget = team.max_budget ?? 0;
|
|
2794
3185
|
const spend = team.spend ?? 0;
|
|
@@ -2798,24 +3189,24 @@ var init_TeamUsage = __esm({
|
|
|
2798
3189
|
const dailyData = usage?.daily_usage?.map((d) => ({ date: d.date, spend: d.spend })) ?? [];
|
|
2799
3190
|
const renderDailySpendSection = () => {
|
|
2800
3191
|
if (usageLoading) {
|
|
2801
|
-
return /* @__PURE__ */
|
|
3192
|
+
return /* @__PURE__ */ import_react8.default.createElement(import_Box8.default, { display: "flex", justifyContent: "center", py: 3 }, /* @__PURE__ */ import_react8.default.createElement(import_CircularProgress5.default, { size: 22 }));
|
|
2802
3193
|
}
|
|
2803
3194
|
if (dailyData.length === 0) return null;
|
|
2804
|
-
return /* @__PURE__ */
|
|
2805
|
-
|
|
3195
|
+
return /* @__PURE__ */ import_react8.default.createElement(import_Box8.default, { mb: 2.5 }, /* @__PURE__ */ import_react8.default.createElement(
|
|
3196
|
+
import_Typography8.default,
|
|
2806
3197
|
{
|
|
2807
3198
|
variant: "caption",
|
|
2808
3199
|
color: "text.secondary",
|
|
2809
3200
|
sx: { display: "block", fontSize: 11, fontWeight: 700, letterSpacing: "0.07em", textTransform: "uppercase", mb: 1 }
|
|
2810
3201
|
},
|
|
2811
3202
|
"Daily spend"
|
|
2812
|
-
), /* @__PURE__ */
|
|
3203
|
+
), /* @__PURE__ */ import_react8.default.createElement(import_Box8.default, { height: 150 }, /* @__PURE__ */ import_react8.default.createElement(import_recharts2.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react8.default.createElement(import_recharts2.AreaChart, { data: dailyData, margin: { top: 4, right: 8, left: 0, bottom: 0 } }, /* @__PURE__ */ import_react8.default.createElement(import_recharts2.CartesianGrid, { ...chart.grid }), /* @__PURE__ */ import_react8.default.createElement(import_recharts2.XAxis, { dataKey: "date", tickFormatter: fmtDateShort, ...chart.axis }), /* @__PURE__ */ import_react8.default.createElement(import_recharts2.YAxis, { tickFormatter: fmtUsdCompact, width: 52, ...chart.axis }), /* @__PURE__ */ import_react8.default.createElement(
|
|
2813
3204
|
import_recharts2.Tooltip,
|
|
2814
3205
|
{
|
|
2815
3206
|
cursor: chart.cursor,
|
|
2816
|
-
content: /* @__PURE__ */
|
|
3207
|
+
content: /* @__PURE__ */ import_react8.default.createElement(ChartTooltip, { valueFormatter: (v) => `$${v.toFixed(4)}` })
|
|
2817
3208
|
}
|
|
2818
|
-
), /* @__PURE__ */
|
|
3209
|
+
), /* @__PURE__ */ import_react8.default.createElement(
|
|
2819
3210
|
import_recharts2.Area,
|
|
2820
3211
|
{
|
|
2821
3212
|
type: "monotone",
|
|
@@ -2828,8 +3219,8 @@ var init_TeamUsage = __esm({
|
|
|
2828
3219
|
}
|
|
2829
3220
|
)))));
|
|
2830
3221
|
};
|
|
2831
|
-
const sectionLabel = (label) => /* @__PURE__ */
|
|
2832
|
-
|
|
3222
|
+
const sectionLabel = (label) => /* @__PURE__ */ import_react8.default.createElement(
|
|
3223
|
+
import_Typography8.default,
|
|
2833
3224
|
{
|
|
2834
3225
|
variant: "caption",
|
|
2835
3226
|
color: "text.secondary",
|
|
@@ -2837,8 +3228,8 @@ var init_TeamUsage = __esm({
|
|
|
2837
3228
|
},
|
|
2838
3229
|
label
|
|
2839
3230
|
);
|
|
2840
|
-
return /* @__PURE__ */
|
|
2841
|
-
|
|
3231
|
+
return /* @__PURE__ */ import_react8.default.createElement(import_Paper6.default, { variant: "outlined", sx: { borderRadius: 2, p: 2.5 } }, /* @__PURE__ */ import_react8.default.createElement(import_Box8.default, { display: "flex", alignItems: "center", gap: 1.5 }, /* @__PURE__ */ import_react8.default.createElement(
|
|
3232
|
+
import_Box8.default,
|
|
2842
3233
|
{
|
|
2843
3234
|
sx: (theme) => ({
|
|
2844
3235
|
width: 36,
|
|
@@ -2848,20 +3239,20 @@ var init_TeamUsage = __esm({
|
|
|
2848
3239
|
display: "flex",
|
|
2849
3240
|
alignItems: "center",
|
|
2850
3241
|
justifyContent: "center",
|
|
2851
|
-
bgcolor: (0,
|
|
3242
|
+
bgcolor: (0, import_styles5.alpha)(theme.palette.primary.main, 0.1),
|
|
2852
3243
|
color: theme.palette.primary.main
|
|
2853
3244
|
})
|
|
2854
3245
|
},
|
|
2855
|
-
/* @__PURE__ */
|
|
2856
|
-
), /* @__PURE__ */
|
|
2857
|
-
|
|
3246
|
+
/* @__PURE__ */ import_react8.default.createElement(import_icons_material6.Group, { fontSize: "small" })
|
|
3247
|
+
), /* @__PURE__ */ import_react8.default.createElement(import_Box8.default, { flexGrow: 1, minWidth: 0 }, /* @__PURE__ */ import_react8.default.createElement(import_Typography8.default, { variant: "subtitle1", sx: { fontWeight: 600, lineHeight: 1.3 } }, team.team_alias || "Untitled team"), /* @__PURE__ */ import_react8.default.createElement(
|
|
3248
|
+
import_Typography8.default,
|
|
2858
3249
|
{
|
|
2859
3250
|
variant: "caption",
|
|
2860
3251
|
color: "text.secondary",
|
|
2861
3252
|
sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 11 }
|
|
2862
3253
|
},
|
|
2863
3254
|
team.team_id
|
|
2864
|
-
)), isOver && /* @__PURE__ */
|
|
3255
|
+
)), isOver && /* @__PURE__ */ import_react8.default.createElement(StatusPill, { label: "Over budget", tone: "danger" }), isNear && /* @__PURE__ */ import_react8.default.createElement(StatusPill, { label: "Near limit", tone: "warning" }), canManage && onEditTeam && /* @__PURE__ */ import_react8.default.createElement(
|
|
2865
3256
|
import_Button5.default,
|
|
2866
3257
|
{
|
|
2867
3258
|
size: "small",
|
|
@@ -2870,8 +3261,8 @@ var init_TeamUsage = __esm({
|
|
|
2870
3261
|
sx: { textTransform: "none" }
|
|
2871
3262
|
},
|
|
2872
3263
|
"Edit"
|
|
2873
|
-
), /* @__PURE__ */
|
|
2874
|
-
|
|
3264
|
+
), /* @__PURE__ */ import_react8.default.createElement(
|
|
3265
|
+
import_IconButton5.default,
|
|
2875
3266
|
{
|
|
2876
3267
|
size: "small",
|
|
2877
3268
|
onClick: () => setExpanded((e) => !e),
|
|
@@ -2883,9 +3274,9 @@ var init_TeamUsage = __esm({
|
|
|
2883
3274
|
transition: theme.transitions.create("transform")
|
|
2884
3275
|
})
|
|
2885
3276
|
},
|
|
2886
|
-
/* @__PURE__ */
|
|
2887
|
-
)), /* @__PURE__ */
|
|
2888
|
-
|
|
3277
|
+
/* @__PURE__ */ import_react8.default.createElement(import_icons_material6.ExpandMore, null)
|
|
3278
|
+
)), /* @__PURE__ */ import_react8.default.createElement(
|
|
3279
|
+
import_Box8.default,
|
|
2889
3280
|
{
|
|
2890
3281
|
sx: {
|
|
2891
3282
|
display: "grid",
|
|
@@ -2895,35 +3286,35 @@ var init_TeamUsage = __esm({
|
|
|
2895
3286
|
maxWidth: 640
|
|
2896
3287
|
}
|
|
2897
3288
|
},
|
|
2898
|
-
/* @__PURE__ */
|
|
2899
|
-
/* @__PURE__ */
|
|
2900
|
-
/* @__PURE__ */
|
|
2901
|
-
/* @__PURE__ */
|
|
2902
|
-
/* @__PURE__ */
|
|
2903
|
-
/* @__PURE__ */
|
|
2904
|
-
), budget > 0 && /* @__PURE__ */
|
|
3289
|
+
/* @__PURE__ */ import_react8.default.createElement(Stat, { label: "Members", value: team.members_with_roles?.length ?? "\u2014" }),
|
|
3290
|
+
/* @__PURE__ */ import_react8.default.createElement(Stat, { label: "Models", value: team.models?.length ? team.models.length : "All" }),
|
|
3291
|
+
/* @__PURE__ */ import_react8.default.createElement(Stat, { label: "Budget", value: budget > 0 ? fmtUsd2(budget) : "Unlimited" }),
|
|
3292
|
+
/* @__PURE__ */ import_react8.default.createElement(Stat, { label: "Spend", value: fmtUsd2(spend) }),
|
|
3293
|
+
/* @__PURE__ */ import_react8.default.createElement(Stat, { label: "TPM", value: team.tpm_limit && team.tpm_limit > 0 ? team.tpm_limit.toLocaleString() : "\u2014" }),
|
|
3294
|
+
/* @__PURE__ */ import_react8.default.createElement(Stat, { label: "RPM", value: team.rpm_limit && team.rpm_limit > 0 ? team.rpm_limit.toLocaleString() : "\u2014" })
|
|
3295
|
+
), budget > 0 && /* @__PURE__ */ import_react8.default.createElement(import_Box8.default, { mt: 2, maxWidth: 640 }, /* @__PURE__ */ import_react8.default.createElement(import_Box8.default, { display: "flex", justifyContent: "space-between", alignItems: "baseline", mb: 0.75 }, /* @__PURE__ */ import_react8.default.createElement(import_Typography8.default, { variant: "caption", color: "text.secondary", sx: { fontVariantNumeric: "tabular-nums" } }, fmtUsd2(spend), " of ", fmtUsd2(budget)), /* @__PURE__ */ import_react8.default.createElement(import_Typography8.default, { variant: "caption", color: "text.secondary", sx: { fontVariantNumeric: "tabular-nums" } }, budgetPct.toFixed(0), "%")), /* @__PURE__ */ import_react8.default.createElement(Meter, { value: budgetPct, tone: budgetTone3(isOver, isNear), height: 5 })), /* @__PURE__ */ import_react8.default.createElement(import_Collapse2.default, { in: expanded, unmountOnExit: true }, /* @__PURE__ */ import_react8.default.createElement(import_Divider3.default, { sx: { my: 2.5 } }), /* @__PURE__ */ import_react8.default.createElement(import_Box8.default, { mb: 2.5 }, sectionLabel("Models"), team.models?.length ? /* @__PURE__ */ import_react8.default.createElement(import_Box8.default, { display: "flex", gap: 0.5, flexWrap: "wrap" }, team.models.map((m) => /* @__PURE__ */ import_react8.default.createElement(TagChip, { key: m, label: m, title: m }))) : /* @__PURE__ */ import_react8.default.createElement(import_Typography8.default, { variant: "body2", color: "text.secondary" }, "All models allowed")), renderDailySpendSection(), /* @__PURE__ */ import_react8.default.createElement(import_Box8.default, null, sectionLabel("Members"), team.members_with_roles?.length ? /* @__PURE__ */ import_react8.default.createElement(
|
|
2905
3296
|
import_TableContainer4.default,
|
|
2906
3297
|
{
|
|
2907
|
-
component:
|
|
3298
|
+
component: import_Paper6.default,
|
|
2908
3299
|
variant: "outlined",
|
|
2909
3300
|
sx: { borderRadius: 1.5 }
|
|
2910
3301
|
},
|
|
2911
|
-
/* @__PURE__ */
|
|
2912
|
-
|
|
3302
|
+
/* @__PURE__ */ import_react8.default.createElement(import_Table4.default, { size: "small", sx: dataTableSx }, /* @__PURE__ */ import_react8.default.createElement(import_TableHead4.default, null, /* @__PURE__ */ import_react8.default.createElement(import_TableRow4.default, null, /* @__PURE__ */ import_react8.default.createElement(import_TableCell4.default, null, "User"), /* @__PURE__ */ import_react8.default.createElement(import_TableCell4.default, { align: "right" }, "Role"))), /* @__PURE__ */ import_react8.default.createElement(import_TableBody4.default, null, team.members_with_roles.map((m) => /* @__PURE__ */ import_react8.default.createElement(import_TableRow4.default, { key: m.user_id }, /* @__PURE__ */ import_react8.default.createElement(import_TableCell4.default, null, m.user_email ? /* @__PURE__ */ import_react8.default.createElement(import_react8.default.Fragment, null, /* @__PURE__ */ import_react8.default.createElement(import_Typography8.default, { variant: "body2" }, m.user_email), /* @__PURE__ */ import_react8.default.createElement(
|
|
3303
|
+
import_Typography8.default,
|
|
2913
3304
|
{
|
|
2914
3305
|
variant: "caption",
|
|
2915
3306
|
color: "text.secondary",
|
|
2916
3307
|
sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 11 }
|
|
2917
3308
|
},
|
|
2918
3309
|
m.user_id
|
|
2919
|
-
)) : /* @__PURE__ */
|
|
2920
|
-
|
|
3310
|
+
)) : /* @__PURE__ */ import_react8.default.createElement(
|
|
3311
|
+
import_Typography8.default,
|
|
2921
3312
|
{
|
|
2922
3313
|
variant: "body2",
|
|
2923
3314
|
sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace" }
|
|
2924
3315
|
},
|
|
2925
3316
|
m.user_id
|
|
2926
|
-
)), /* @__PURE__ */
|
|
3317
|
+
)), /* @__PURE__ */ import_react8.default.createElement(import_TableCell4.default, { align: "right" }, /* @__PURE__ */ import_react8.default.createElement(
|
|
2927
3318
|
StatusPill,
|
|
2928
3319
|
{
|
|
2929
3320
|
label: m.role,
|
|
@@ -2931,7 +3322,7 @@ var init_TeamUsage = __esm({
|
|
|
2931
3322
|
dot: false
|
|
2932
3323
|
}
|
|
2933
3324
|
))))))
|
|
2934
|
-
) : /* @__PURE__ */
|
|
3325
|
+
) : /* @__PURE__ */ import_react8.default.createElement(import_Typography8.default, { variant: "body2", color: "text.secondary" }, "No members assigned."))));
|
|
2935
3326
|
};
|
|
2936
3327
|
TeamUsage = ({
|
|
2937
3328
|
teams,
|
|
@@ -2943,22 +3334,22 @@ var init_TeamUsage = __esm({
|
|
|
2943
3334
|
canCreate,
|
|
2944
3335
|
onCreateTeam
|
|
2945
3336
|
}) => {
|
|
2946
|
-
const createAction = canCreate && onCreateTeam ? /* @__PURE__ */
|
|
3337
|
+
const createAction = canCreate && onCreateTeam ? /* @__PURE__ */ import_react8.default.createElement(
|
|
2947
3338
|
import_Button5.default,
|
|
2948
3339
|
{
|
|
2949
3340
|
variant: "contained",
|
|
2950
3341
|
color: "primary",
|
|
2951
3342
|
disableElevation: true,
|
|
2952
|
-
startIcon: /* @__PURE__ */
|
|
3343
|
+
startIcon: /* @__PURE__ */ import_react8.default.createElement(import_icons_material6.Add, null),
|
|
2953
3344
|
onClick: onCreateTeam
|
|
2954
3345
|
},
|
|
2955
3346
|
"Create Team"
|
|
2956
3347
|
) : void 0;
|
|
2957
3348
|
if (loading) {
|
|
2958
|
-
return /* @__PURE__ */
|
|
3349
|
+
return /* @__PURE__ */ import_react8.default.createElement(SectionCard, { title: "Teams", actions: createAction }, /* @__PURE__ */ import_react8.default.createElement(import_Skeleton4.default, { variant: "rounded", height: 120, sx: { mb: 2 } }), /* @__PURE__ */ import_react8.default.createElement(import_Skeleton4.default, { variant: "rounded", height: 120 }));
|
|
2959
3350
|
}
|
|
2960
3351
|
if (!teams.length) {
|
|
2961
|
-
return /* @__PURE__ */
|
|
3352
|
+
return /* @__PURE__ */ import_react8.default.createElement(SectionCard, { title: "Teams", actions: createAction }, /* @__PURE__ */ import_react8.default.createElement(
|
|
2962
3353
|
EmptyState,
|
|
2963
3354
|
{
|
|
2964
3355
|
message: "You're not a member of any LiteLLM team yet.",
|
|
@@ -2966,14 +3357,14 @@ var init_TeamUsage = __esm({
|
|
|
2966
3357
|
}
|
|
2967
3358
|
));
|
|
2968
3359
|
}
|
|
2969
|
-
return /* @__PURE__ */
|
|
3360
|
+
return /* @__PURE__ */ import_react8.default.createElement(
|
|
2970
3361
|
SectionCard,
|
|
2971
3362
|
{
|
|
2972
3363
|
title: "Teams",
|
|
2973
3364
|
subtitle: `${teams.length} team${teams.length === 1 ? "" : "s"}`,
|
|
2974
3365
|
actions: createAction
|
|
2975
3366
|
},
|
|
2976
|
-
/* @__PURE__ */
|
|
3367
|
+
/* @__PURE__ */ import_react8.default.createElement(import_Box8.default, { sx: { display: "flex", flexDirection: "column", gap: 2 } }, teams.map((team) => /* @__PURE__ */ import_react8.default.createElement(
|
|
2977
3368
|
TeamCard,
|
|
2978
3369
|
{
|
|
2979
3370
|
key: team.team_id,
|
|
@@ -3007,54 +3398,54 @@ function fmtTokens(n) {
|
|
|
3007
3398
|
if (n >= 1e3) return `${(n / 1e3).toFixed(0)}K`;
|
|
3008
3399
|
return String(n);
|
|
3009
3400
|
}
|
|
3010
|
-
var
|
|
3401
|
+
var import_react9, import_Box9, import_Table5, import_TableBody5, import_TableCell5, import_TableContainer5, import_TableHead5, import_TableRow5, import_Typography9, import_MenuItem4, import_Select, import_FormControl, import_InputLabel, import_TextField5, import_InputAdornment2, import_icons_material7, import_ContentCopy, import_Tooltip, import_Snackbar, import_Alert4, import_styles6, ALL_PROXY_MODELS2, ModelsTable;
|
|
3011
3402
|
var init_ModelsTable = __esm({
|
|
3012
3403
|
"src/components/ModelsTable.tsx"() {
|
|
3013
3404
|
"use strict";
|
|
3014
|
-
|
|
3015
|
-
|
|
3405
|
+
import_react9 = __toESM(require("react"));
|
|
3406
|
+
import_Box9 = __toESM(require("@mui/material/Box"));
|
|
3016
3407
|
import_Table5 = __toESM(require("@mui/material/Table"));
|
|
3017
3408
|
import_TableBody5 = __toESM(require("@mui/material/TableBody"));
|
|
3018
3409
|
import_TableCell5 = __toESM(require("@mui/material/TableCell"));
|
|
3019
3410
|
import_TableContainer5 = __toESM(require("@mui/material/TableContainer"));
|
|
3020
3411
|
import_TableHead5 = __toESM(require("@mui/material/TableHead"));
|
|
3021
3412
|
import_TableRow5 = __toESM(require("@mui/material/TableRow"));
|
|
3022
|
-
|
|
3413
|
+
import_Typography9 = __toESM(require("@mui/material/Typography"));
|
|
3023
3414
|
import_MenuItem4 = __toESM(require("@mui/material/MenuItem"));
|
|
3024
3415
|
import_Select = __toESM(require("@mui/material/Select"));
|
|
3025
3416
|
import_FormControl = __toESM(require("@mui/material/FormControl"));
|
|
3026
3417
|
import_InputLabel = __toESM(require("@mui/material/InputLabel"));
|
|
3027
3418
|
import_TextField5 = __toESM(require("@mui/material/TextField"));
|
|
3028
3419
|
import_InputAdornment2 = __toESM(require("@mui/material/InputAdornment"));
|
|
3029
|
-
|
|
3420
|
+
import_icons_material7 = require("@mui/icons-material");
|
|
3030
3421
|
import_ContentCopy = __toESM(require("@mui/icons-material/ContentCopy"));
|
|
3031
3422
|
import_Tooltip = __toESM(require("@mui/material/Tooltip"));
|
|
3032
3423
|
import_Snackbar = __toESM(require("@mui/material/Snackbar"));
|
|
3033
|
-
|
|
3424
|
+
import_Alert4 = __toESM(require("@mui/material/Alert"));
|
|
3034
3425
|
init_ui();
|
|
3035
|
-
|
|
3426
|
+
import_styles6 = require("@mui/material/styles");
|
|
3036
3427
|
ALL_PROXY_MODELS2 = "all-proxy-models";
|
|
3037
3428
|
ModelsTable = ({ allModels, teams, loading }) => {
|
|
3038
|
-
const theme = (0,
|
|
3039
|
-
const [selectedTeamId, setSelectedTeamId] = (0,
|
|
3040
|
-
const [filterText, setFilterText] = (0,
|
|
3041
|
-
const [copiedSnackbar, setCopiedSnackbar] = (0,
|
|
3042
|
-
const selectedTeam = (0,
|
|
3429
|
+
const theme = (0, import_styles6.useTheme)();
|
|
3430
|
+
const [selectedTeamId, setSelectedTeamId] = (0, import_react9.useState)("");
|
|
3431
|
+
const [filterText, setFilterText] = (0, import_react9.useState)("");
|
|
3432
|
+
const [copiedSnackbar, setCopiedSnackbar] = (0, import_react9.useState)(false);
|
|
3433
|
+
const selectedTeam = (0, import_react9.useMemo)(
|
|
3043
3434
|
() => teams.find((t) => t.team_id === selectedTeamId) ?? null,
|
|
3044
3435
|
[teams, selectedTeamId]
|
|
3045
3436
|
);
|
|
3046
|
-
const teamModels = (0,
|
|
3437
|
+
const teamModels = (0, import_react9.useMemo)(() => {
|
|
3047
3438
|
if (!selectedTeam) return [];
|
|
3048
3439
|
return allModels.filter((model) => isModelAllowedByTeam2(model, selectedTeam.models));
|
|
3049
3440
|
}, [allModels, selectedTeam]);
|
|
3050
|
-
const filteredModels = (0,
|
|
3441
|
+
const filteredModels = (0, import_react9.useMemo)(() => {
|
|
3051
3442
|
const q = filterText.trim().toLowerCase();
|
|
3052
3443
|
if (!q) return teamModels;
|
|
3053
3444
|
return teamModels.filter(
|
|
3054
3445
|
(model) => model.model_name.toLowerCase().includes(q) || (model.access_groups || []).some((group) => group.toLowerCase().includes(q))
|
|
3055
3446
|
);
|
|
3056
3447
|
}, [teamModels, filterText]);
|
|
3057
|
-
const handleCopyModelId = (0,
|
|
3448
|
+
const handleCopyModelId = (0, import_react9.useCallback)((modelId) => {
|
|
3058
3449
|
navigator.clipboard.writeText(modelId).then(() => setCopiedSnackbar(true));
|
|
3059
3450
|
}, []);
|
|
3060
3451
|
const modeColor = (mode) => {
|
|
@@ -3071,13 +3462,13 @@ var init_ModelsTable = __esm({
|
|
|
3071
3462
|
return theme.palette.info.main;
|
|
3072
3463
|
}
|
|
3073
3464
|
};
|
|
3074
|
-
return /* @__PURE__ */
|
|
3465
|
+
return /* @__PURE__ */ import_react9.default.createElement(
|
|
3075
3466
|
SectionCard,
|
|
3076
3467
|
{
|
|
3077
3468
|
title: "Available Models",
|
|
3078
3469
|
subtitle: selectedTeam ? `${filteredModels.length}${filterText.trim() ? ` of ${teamModels.length}` : ""} model${filteredModels.length === 1 ? "" : "s"}` : "Select a team to view its models"
|
|
3079
3470
|
},
|
|
3080
|
-
/* @__PURE__ */
|
|
3471
|
+
/* @__PURE__ */ import_react9.default.createElement(import_Box9.default, { sx: { px: 2.5, pb: 2.5, pt: 1, display: "flex", gap: 2, flexWrap: "wrap" } }, /* @__PURE__ */ import_react9.default.createElement(import_FormControl.default, { size: "small", sx: { minWidth: 240, mb: 2 } }, /* @__PURE__ */ import_react9.default.createElement(import_InputLabel.default, { id: "team-select-label" }, "Team"), /* @__PURE__ */ import_react9.default.createElement(
|
|
3081
3472
|
import_Select.default,
|
|
3082
3473
|
{
|
|
3083
3474
|
labelId: "team-select-label",
|
|
@@ -3085,8 +3476,8 @@ var init_ModelsTable = __esm({
|
|
|
3085
3476
|
label: "Team",
|
|
3086
3477
|
onChange: (e) => setSelectedTeamId(e.target.value)
|
|
3087
3478
|
},
|
|
3088
|
-
teams.map((t) => /* @__PURE__ */
|
|
3089
|
-
)), /* @__PURE__ */
|
|
3479
|
+
teams.map((t) => /* @__PURE__ */ import_react9.default.createElement(import_MenuItem4.default, { key: t.team_id, value: t.team_id }, t.team_alias ?? t.team_id))
|
|
3480
|
+
)), /* @__PURE__ */ import_react9.default.createElement(
|
|
3090
3481
|
import_TextField5.default,
|
|
3091
3482
|
{
|
|
3092
3483
|
label: "Filter Models",
|
|
@@ -3096,33 +3487,33 @@ var init_ModelsTable = __esm({
|
|
|
3096
3487
|
disabled: !selectedTeamId,
|
|
3097
3488
|
sx: { minWidth: 240, mb: 2 },
|
|
3098
3489
|
InputProps: {
|
|
3099
|
-
startAdornment: /* @__PURE__ */
|
|
3490
|
+
startAdornment: /* @__PURE__ */ import_react9.default.createElement(import_InputAdornment2.default, { position: "start" }, /* @__PURE__ */ import_react9.default.createElement(import_icons_material7.Search, { fontSize: "small" }))
|
|
3100
3491
|
}
|
|
3101
3492
|
}
|
|
3102
3493
|
)),
|
|
3103
|
-
loading && /* @__PURE__ */
|
|
3104
|
-
!loading && !selectedTeamId && /* @__PURE__ */
|
|
3494
|
+
loading && /* @__PURE__ */ import_react9.default.createElement(EmptyState, { message: "Loading models\u2026" }),
|
|
3495
|
+
!loading && !selectedTeamId && /* @__PURE__ */ import_react9.default.createElement(
|
|
3105
3496
|
EmptyState,
|
|
3106
3497
|
{
|
|
3107
3498
|
message: "Select a team to see available models",
|
|
3108
3499
|
hint: "Each team may have a different set of models assigned"
|
|
3109
3500
|
}
|
|
3110
3501
|
),
|
|
3111
|
-
!loading && selectedTeamId && teamModels.length === 0 && /* @__PURE__ */
|
|
3502
|
+
!loading && selectedTeamId && teamModels.length === 0 && /* @__PURE__ */ import_react9.default.createElement(
|
|
3112
3503
|
EmptyState,
|
|
3113
3504
|
{
|
|
3114
3505
|
message: "No models available for this team",
|
|
3115
3506
|
hint: allModels.length === 0 ? "No models are configured in the system" : "None of the team's assigned models were found in the catalog"
|
|
3116
3507
|
}
|
|
3117
3508
|
),
|
|
3118
|
-
!loading && selectedTeamId && teamModels.length > 0 && filteredModels.length === 0 && /* @__PURE__ */
|
|
3509
|
+
!loading && selectedTeamId && teamModels.length > 0 && filteredModels.length === 0 && /* @__PURE__ */ import_react9.default.createElement(
|
|
3119
3510
|
EmptyState,
|
|
3120
3511
|
{
|
|
3121
3512
|
message: "No models match your search",
|
|
3122
3513
|
hint: "Try a different search term"
|
|
3123
3514
|
}
|
|
3124
3515
|
),
|
|
3125
|
-
!loading && filteredModels.length > 0 && /* @__PURE__ */
|
|
3516
|
+
!loading && filteredModels.length > 0 && /* @__PURE__ */ import_react9.default.createElement(import_TableContainer5.default, null, /* @__PURE__ */ import_react9.default.createElement(import_Table5.default, { size: "small", sx: dataTableSx }, /* @__PURE__ */ import_react9.default.createElement(import_TableHead5.default, null, /* @__PURE__ */ import_react9.default.createElement(import_TableRow5.default, null, /* @__PURE__ */ import_react9.default.createElement(import_TableCell5.default, null, "Model ID"), /* @__PURE__ */ import_react9.default.createElement(import_TableCell5.default, null, "Mode"), /* @__PURE__ */ import_react9.default.createElement(import_TableCell5.default, { align: "right" }, "Input Cost"), /* @__PURE__ */ import_react9.default.createElement(import_TableCell5.default, { align: "right" }, "Output Cost"), /* @__PURE__ */ import_react9.default.createElement(import_TableCell5.default, { align: "right" }, "Max Input"), /* @__PURE__ */ import_react9.default.createElement(import_TableCell5.default, { align: "right" }, "Max Output"), /* @__PURE__ */ import_react9.default.createElement(import_TableCell5.default, null, "Capabilities"))), /* @__PURE__ */ import_react9.default.createElement(import_TableBody5.default, null, filteredModels.map((m) => /* @__PURE__ */ import_react9.default.createElement(import_TableRow5.default, { key: m.model_name, hover: true }, /* @__PURE__ */ import_react9.default.createElement(import_TableCell5.default, null, /* @__PURE__ */ import_react9.default.createElement(import_Box9.default, { sx: { display: "flex", alignItems: "center", gap: 0.5 } }, /* @__PURE__ */ import_react9.default.createElement(import_Tooltip.default, { title: m.model_name, placement: "top" }, /* @__PURE__ */ import_react9.default.createElement(
|
|
3126
3517
|
"span",
|
|
3127
3518
|
{
|
|
3128
3519
|
role: "button",
|
|
@@ -3132,9 +3523,9 @@ var init_ModelsTable = __esm({
|
|
|
3132
3523
|
onKeyDown: (e) => e.key === "Enter" && handleCopyModelId(m.model_name),
|
|
3133
3524
|
style: { cursor: "pointer", display: "inline-flex" }
|
|
3134
3525
|
},
|
|
3135
|
-
/* @__PURE__ */
|
|
3136
|
-
)), /* @__PURE__ */
|
|
3137
|
-
|
|
3526
|
+
/* @__PURE__ */ import_react9.default.createElement(TagChip, { label: m.model_name, title: "Copy model ID" })
|
|
3527
|
+
)), /* @__PURE__ */ import_react9.default.createElement(import_Tooltip.default, { title: "Copy model ID" }, /* @__PURE__ */ import_react9.default.createElement(
|
|
3528
|
+
import_Box9.default,
|
|
3138
3529
|
{
|
|
3139
3530
|
component: "span",
|
|
3140
3531
|
role: "button",
|
|
@@ -3144,9 +3535,9 @@ var init_ModelsTable = __esm({
|
|
|
3144
3535
|
sx: quietIconButtonSx("accent"),
|
|
3145
3536
|
style: { cursor: "pointer", display: "inline-flex" }
|
|
3146
3537
|
},
|
|
3147
|
-
/* @__PURE__ */
|
|
3148
|
-
)))), /* @__PURE__ */
|
|
3149
|
-
|
|
3538
|
+
/* @__PURE__ */ import_react9.default.createElement(import_ContentCopy.default, { sx: { fontSize: 14 } })
|
|
3539
|
+
)))), /* @__PURE__ */ import_react9.default.createElement(import_TableCell5.default, null, /* @__PURE__ */ import_react9.default.createElement(
|
|
3540
|
+
import_Box9.default,
|
|
3150
3541
|
{
|
|
3151
3542
|
component: "span",
|
|
3152
3543
|
sx: {
|
|
@@ -3157,13 +3548,13 @@ var init_ModelsTable = __esm({
|
|
|
3157
3548
|
fontSize: 11,
|
|
3158
3549
|
fontWeight: 600,
|
|
3159
3550
|
textTransform: "capitalize",
|
|
3160
|
-
bgcolor: (0,
|
|
3551
|
+
bgcolor: (0, import_styles6.alpha)(modeColor(m.mode), 0.1),
|
|
3161
3552
|
color: modeColor(m.mode)
|
|
3162
3553
|
}
|
|
3163
3554
|
},
|
|
3164
3555
|
m.mode
|
|
3165
|
-
)), /* @__PURE__ */
|
|
3166
|
-
/* @__PURE__ */
|
|
3556
|
+
)), /* @__PURE__ */ import_react9.default.createElement(import_TableCell5.default, { align: "right" }, /* @__PURE__ */ import_react9.default.createElement(import_Typography9.default, { variant: "body2", sx: { fontVariantNumeric: "tabular-nums" } }, fmtCostPerToken(m.input_cost_per_token))), /* @__PURE__ */ import_react9.default.createElement(import_TableCell5.default, { align: "right" }, /* @__PURE__ */ import_react9.default.createElement(import_Typography9.default, { variant: "body2", sx: { fontVariantNumeric: "tabular-nums" } }, fmtCostPerToken(m.output_cost_per_token))), /* @__PURE__ */ import_react9.default.createElement(import_TableCell5.default, { align: "right" }, /* @__PURE__ */ import_react9.default.createElement(import_Typography9.default, { variant: "body2", sx: { fontVariantNumeric: "tabular-nums" } }, fmtTokens(m.max_input_tokens))), /* @__PURE__ */ import_react9.default.createElement(import_TableCell5.default, { align: "right" }, /* @__PURE__ */ import_react9.default.createElement(import_Typography9.default, { variant: "body2", sx: { fontVariantNumeric: "tabular-nums" } }, fmtTokens(m.max_output_tokens))), /* @__PURE__ */ import_react9.default.createElement(import_TableCell5.default, null, /* @__PURE__ */ import_react9.default.createElement(import_Box9.default, { sx: { display: "flex", gap: 0.5, flexWrap: "wrap" } }, m.supports_function_calling && /* @__PURE__ */ import_react9.default.createElement(TagChip, { label: "Fn", title: "Function calling" }), m.supports_vision && /* @__PURE__ */ import_react9.default.createElement(TagChip, { label: "\u{1F441}", title: "Vision" })))))))),
|
|
3557
|
+
/* @__PURE__ */ import_react9.default.createElement(
|
|
3167
3558
|
import_Snackbar.default,
|
|
3168
3559
|
{
|
|
3169
3560
|
open: copiedSnackbar,
|
|
@@ -3171,7 +3562,7 @@ var init_ModelsTable = __esm({
|
|
|
3171
3562
|
onClose: () => setCopiedSnackbar(false),
|
|
3172
3563
|
anchorOrigin: { vertical: "bottom", horizontal: "center" }
|
|
3173
3564
|
},
|
|
3174
|
-
/* @__PURE__ */
|
|
3565
|
+
/* @__PURE__ */ import_react9.default.createElement(import_Alert4.default, { severity: "success", variant: "filled", sx: { fontSize: 13 } }, "Model ID copied to clipboard")
|
|
3175
3566
|
)
|
|
3176
3567
|
);
|
|
3177
3568
|
};
|
|
@@ -3199,10 +3590,10 @@ function formatDateTime(iso) {
|
|
|
3199
3590
|
}
|
|
3200
3591
|
function renderAuditLogBody(loading, entries) {
|
|
3201
3592
|
if (loading) {
|
|
3202
|
-
return /* @__PURE__ */
|
|
3593
|
+
return /* @__PURE__ */ import_react10.default.createElement(import_TableRow6.default, null, /* @__PURE__ */ import_react10.default.createElement(import_TableCell6.default, { colSpan: 6, sx: { py: 2 } }, /* @__PURE__ */ import_react10.default.createElement(import_Skeleton5.default, { variant: "rounded", height: 160 })));
|
|
3203
3594
|
}
|
|
3204
3595
|
if (entries.length === 0) {
|
|
3205
|
-
return /* @__PURE__ */
|
|
3596
|
+
return /* @__PURE__ */ import_react10.default.createElement(import_TableRow6.default, null, /* @__PURE__ */ import_react10.default.createElement(import_TableCell6.default, { colSpan: 6 }, /* @__PURE__ */ import_react10.default.createElement(
|
|
3206
3597
|
EmptyState,
|
|
3207
3598
|
{
|
|
3208
3599
|
message: "No audit events found",
|
|
@@ -3210,15 +3601,15 @@ function renderAuditLogBody(loading, entries) {
|
|
|
3210
3601
|
}
|
|
3211
3602
|
)));
|
|
3212
3603
|
}
|
|
3213
|
-
return entries.map((entry) => /* @__PURE__ */
|
|
3604
|
+
return entries.map((entry) => /* @__PURE__ */ import_react10.default.createElement(DetailRow, { key: entry.id, entry }));
|
|
3214
3605
|
}
|
|
3215
|
-
var
|
|
3606
|
+
var import_react10, import_Box10, import_Typography10, import_Table6, import_TableBody6, import_TableCell6, import_TableContainer6, import_TableHead6, import_TableRow6, import_TablePagination, import_TextField6, import_MenuItem5, import_Skeleton5, import_Collapse3, import_IconButton6, import_Alert5, import_styles7, import_icons_material8, import_react_use2, ACTION_TONES, TABLE_LABELS, DetailRow, AuditLog;
|
|
3216
3607
|
var init_AuditLog = __esm({
|
|
3217
3608
|
"src/components/AuditLog.tsx"() {
|
|
3218
3609
|
"use strict";
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3610
|
+
import_react10 = __toESM(require("react"));
|
|
3611
|
+
import_Box10 = __toESM(require("@mui/material/Box"));
|
|
3612
|
+
import_Typography10 = __toESM(require("@mui/material/Typography"));
|
|
3222
3613
|
import_Table6 = __toESM(require("@mui/material/Table"));
|
|
3223
3614
|
import_TableBody6 = __toESM(require("@mui/material/TableBody"));
|
|
3224
3615
|
import_TableCell6 = __toESM(require("@mui/material/TableCell"));
|
|
@@ -3229,11 +3620,11 @@ var init_AuditLog = __esm({
|
|
|
3229
3620
|
import_TextField6 = __toESM(require("@mui/material/TextField"));
|
|
3230
3621
|
import_MenuItem5 = __toESM(require("@mui/material/MenuItem"));
|
|
3231
3622
|
import_Skeleton5 = __toESM(require("@mui/material/Skeleton"));
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3623
|
+
import_Collapse3 = __toESM(require("@mui/material/Collapse"));
|
|
3624
|
+
import_IconButton6 = __toESM(require("@mui/material/IconButton"));
|
|
3625
|
+
import_Alert5 = __toESM(require("@mui/material/Alert"));
|
|
3626
|
+
import_styles7 = require("@mui/material/styles");
|
|
3627
|
+
import_icons_material8 = require("@mui/icons-material");
|
|
3237
3628
|
import_react_use2 = require("react-use");
|
|
3238
3629
|
init_ui();
|
|
3239
3630
|
ACTION_TONES = {
|
|
@@ -3251,10 +3642,10 @@ var init_AuditLog = __esm({
|
|
|
3251
3642
|
LiteLLM_UserTable: "User"
|
|
3252
3643
|
};
|
|
3253
3644
|
DetailRow = ({ entry }) => {
|
|
3254
|
-
const [open, setOpen] = (0,
|
|
3645
|
+
const [open, setOpen] = (0, import_react10.useState)(false);
|
|
3255
3646
|
const hasDetail = entry.before_value || entry.updated_values;
|
|
3256
|
-
return /* @__PURE__ */
|
|
3257
|
-
|
|
3647
|
+
return /* @__PURE__ */ import_react10.default.createElement(import_react10.default.Fragment, null, /* @__PURE__ */ import_react10.default.createElement(import_TableRow6.default, null, /* @__PURE__ */ import_react10.default.createElement(import_TableCell6.default, { sx: { width: 40, pr: 0 } }, hasDetail && /* @__PURE__ */ import_react10.default.createElement(
|
|
3648
|
+
import_IconButton6.default,
|
|
3258
3649
|
{
|
|
3259
3650
|
size: "small",
|
|
3260
3651
|
onClick: () => setOpen((o) => !o),
|
|
@@ -3266,9 +3657,9 @@ var init_AuditLog = __esm({
|
|
|
3266
3657
|
transition: theme.transitions.create("transform")
|
|
3267
3658
|
})
|
|
3268
3659
|
},
|
|
3269
|
-
/* @__PURE__ */
|
|
3270
|
-
)), /* @__PURE__ */
|
|
3271
|
-
|
|
3660
|
+
/* @__PURE__ */ import_react10.default.createElement(import_icons_material8.KeyboardArrowDown, { fontSize: "small" })
|
|
3661
|
+
)), /* @__PURE__ */ import_react10.default.createElement(import_TableCell6.default, { sx: { whiteSpace: "nowrap" } }, /* @__PURE__ */ import_react10.default.createElement(import_Typography10.default, { variant: "body2", color: "text.secondary" }, formatDateTime(entry.updated_at))), /* @__PURE__ */ import_react10.default.createElement(import_TableCell6.default, null, entry.action && /* @__PURE__ */ import_react10.default.createElement(StatusPill, { label: entry.action, tone: actionTone(entry.action) })), /* @__PURE__ */ import_react10.default.createElement(import_TableCell6.default, null, /* @__PURE__ */ import_react10.default.createElement(import_Typography10.default, { variant: "body2" }, prettyTableName(entry.table_name))), /* @__PURE__ */ import_react10.default.createElement(import_TableCell6.default, null, /* @__PURE__ */ import_react10.default.createElement(
|
|
3662
|
+
import_Typography10.default,
|
|
3272
3663
|
{
|
|
3273
3664
|
variant: "body2",
|
|
3274
3665
|
component: "code",
|
|
@@ -3277,8 +3668,8 @@ var init_AuditLog = __esm({
|
|
|
3277
3668
|
sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 12 }
|
|
3278
3669
|
},
|
|
3279
3670
|
entry.object_id ? entry.object_id.slice(0, 20) + (entry.object_id.length > 20 ? "\u2026" : "") : "\u2014"
|
|
3280
|
-
)), /* @__PURE__ */
|
|
3281
|
-
|
|
3671
|
+
)), /* @__PURE__ */ import_react10.default.createElement(import_TableCell6.default, null, entry.changed_by ?? "\u2014")), hasDetail && /* @__PURE__ */ import_react10.default.createElement(import_TableRow6.default, { sx: { "&&:hover": { bgcolor: "transparent" } } }, /* @__PURE__ */ import_react10.default.createElement(import_TableCell6.default, { colSpan: 6, sx: { "&&": { py: 0, border: 0 } } }, /* @__PURE__ */ import_react10.default.createElement(import_Collapse3.default, { in: open, unmountOnExit: true }, /* @__PURE__ */ import_react10.default.createElement(
|
|
3672
|
+
import_Box10.default,
|
|
3282
3673
|
{
|
|
3283
3674
|
display: "flex",
|
|
3284
3675
|
gap: 2,
|
|
@@ -3287,11 +3678,11 @@ var init_AuditLog = __esm({
|
|
|
3287
3678
|
p: 2,
|
|
3288
3679
|
mb: 1.5,
|
|
3289
3680
|
borderRadius: 1.5,
|
|
3290
|
-
bgcolor: (0,
|
|
3681
|
+
bgcolor: (0, import_styles7.alpha)(theme.palette.text.primary, theme.palette.mode === "dark" ? 0.05 : 0.03)
|
|
3291
3682
|
})
|
|
3292
3683
|
},
|
|
3293
|
-
entry.before_value && /* @__PURE__ */
|
|
3294
|
-
|
|
3684
|
+
entry.before_value && /* @__PURE__ */ import_react10.default.createElement(import_Box10.default, { flex: 1, minWidth: 200 }, /* @__PURE__ */ import_react10.default.createElement(
|
|
3685
|
+
import_Typography10.default,
|
|
3295
3686
|
{
|
|
3296
3687
|
variant: "caption",
|
|
3297
3688
|
color: "text.secondary",
|
|
@@ -3300,9 +3691,9 @@ var init_AuditLog = __esm({
|
|
|
3300
3691
|
sx: { fontSize: 10.5, fontWeight: 700, letterSpacing: "0.07em", textTransform: "uppercase" }
|
|
3301
3692
|
},
|
|
3302
3693
|
"Before"
|
|
3303
|
-
), /* @__PURE__ */
|
|
3304
|
-
entry.updated_values && /* @__PURE__ */
|
|
3305
|
-
|
|
3694
|
+
), /* @__PURE__ */ import_react10.default.createElement(import_Typography10.default, { component: "pre", variant: "caption", sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", whiteSpace: "pre-wrap", wordBreak: "break-all", m: 0 } }, JSON.stringify(entry.before_value, null, 2))),
|
|
3695
|
+
entry.updated_values && /* @__PURE__ */ import_react10.default.createElement(import_Box10.default, { flex: 1, minWidth: 200 }, /* @__PURE__ */ import_react10.default.createElement(
|
|
3696
|
+
import_Typography10.default,
|
|
3306
3697
|
{
|
|
3307
3698
|
variant: "caption",
|
|
3308
3699
|
color: "text.secondary",
|
|
@@ -3311,14 +3702,14 @@ var init_AuditLog = __esm({
|
|
|
3311
3702
|
sx: { fontSize: 10.5, fontWeight: 700, letterSpacing: "0.07em", textTransform: "uppercase" }
|
|
3312
3703
|
},
|
|
3313
3704
|
"After"
|
|
3314
|
-
), /* @__PURE__ */
|
|
3705
|
+
), /* @__PURE__ */ import_react10.default.createElement(import_Typography10.default, { component: "pre", variant: "caption", sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", whiteSpace: "pre-wrap", wordBreak: "break-all", m: 0 } }, JSON.stringify(entry.updated_values, null, 2)))
|
|
3315
3706
|
)))));
|
|
3316
3707
|
};
|
|
3317
3708
|
AuditLog = ({ api }) => {
|
|
3318
|
-
const [page, setPage] = (0,
|
|
3319
|
-
const [pageSize, setPageSize] = (0,
|
|
3320
|
-
const [filters, setFilters] = (0,
|
|
3321
|
-
const fetchParams = (0,
|
|
3709
|
+
const [page, setPage] = (0, import_react10.useState)(0);
|
|
3710
|
+
const [pageSize, setPageSize] = (0, import_react10.useState)(25);
|
|
3711
|
+
const [filters, setFilters] = (0, import_react10.useState)({});
|
|
3712
|
+
const fetchParams = (0, import_react10.useCallback)(
|
|
3322
3713
|
() => ({ page: page + 1, page_size: pageSize, ...filters }),
|
|
3323
3714
|
[page, pageSize, filters]
|
|
3324
3715
|
)();
|
|
@@ -3328,13 +3719,13 @@ var init_AuditLog = __esm({
|
|
|
3328
3719
|
);
|
|
3329
3720
|
const entries = value?.audit_logs ?? [];
|
|
3330
3721
|
const total = value?.total ?? 0;
|
|
3331
|
-
return /* @__PURE__ */
|
|
3722
|
+
return /* @__PURE__ */ import_react10.default.createElement(
|
|
3332
3723
|
SectionCard,
|
|
3333
3724
|
{
|
|
3334
3725
|
title: "Audit Log",
|
|
3335
3726
|
subtitle: loading ? "Loading\u2026" : `${total.toLocaleString()} event${total === 1 ? "" : "s"}`,
|
|
3336
3727
|
flush: true,
|
|
3337
|
-
actions: /* @__PURE__ */
|
|
3728
|
+
actions: /* @__PURE__ */ import_react10.default.createElement(import_react10.default.Fragment, null, /* @__PURE__ */ import_react10.default.createElement(
|
|
3338
3729
|
import_TextField6.default,
|
|
3339
3730
|
{
|
|
3340
3731
|
size: "small",
|
|
@@ -3347,12 +3738,12 @@ var init_AuditLog = __esm({
|
|
|
3347
3738
|
},
|
|
3348
3739
|
sx: { minWidth: 150 }
|
|
3349
3740
|
},
|
|
3350
|
-
/* @__PURE__ */
|
|
3351
|
-
/* @__PURE__ */
|
|
3352
|
-
/* @__PURE__ */
|
|
3353
|
-
/* @__PURE__ */
|
|
3354
|
-
/* @__PURE__ */
|
|
3355
|
-
), /* @__PURE__ */
|
|
3741
|
+
/* @__PURE__ */ import_react10.default.createElement(import_MenuItem5.default, { value: "" }, "All actions"),
|
|
3742
|
+
/* @__PURE__ */ import_react10.default.createElement(import_MenuItem5.default, { value: "created" }, "Created"),
|
|
3743
|
+
/* @__PURE__ */ import_react10.default.createElement(import_MenuItem5.default, { value: "updated" }, "Updated"),
|
|
3744
|
+
/* @__PURE__ */ import_react10.default.createElement(import_MenuItem5.default, { value: "deleted" }, "Deleted"),
|
|
3745
|
+
/* @__PURE__ */ import_react10.default.createElement(import_MenuItem5.default, { value: "blocked" }, "Blocked")
|
|
3746
|
+
), /* @__PURE__ */ import_react10.default.createElement(
|
|
3356
3747
|
import_TextField6.default,
|
|
3357
3748
|
{
|
|
3358
3749
|
size: "small",
|
|
@@ -3365,13 +3756,13 @@ var init_AuditLog = __esm({
|
|
|
3365
3756
|
},
|
|
3366
3757
|
sx: { minWidth: 150 }
|
|
3367
3758
|
},
|
|
3368
|
-
/* @__PURE__ */
|
|
3369
|
-
/* @__PURE__ */
|
|
3370
|
-
/* @__PURE__ */
|
|
3371
|
-
/* @__PURE__ */
|
|
3372
|
-
/* @__PURE__ */
|
|
3373
|
-
/* @__PURE__ */
|
|
3374
|
-
), /* @__PURE__ */
|
|
3759
|
+
/* @__PURE__ */ import_react10.default.createElement(import_MenuItem5.default, { value: "" }, "All tables"),
|
|
3760
|
+
/* @__PURE__ */ import_react10.default.createElement(import_MenuItem5.default, { value: "LiteLLM_VerificationToken" }, "Key"),
|
|
3761
|
+
/* @__PURE__ */ import_react10.default.createElement(import_MenuItem5.default, { value: "LiteLLM_TeamTable" }, "Team"),
|
|
3762
|
+
/* @__PURE__ */ import_react10.default.createElement(import_MenuItem5.default, { value: "LiteLLM_TeamMembership" }, "Team member"),
|
|
3763
|
+
/* @__PURE__ */ import_react10.default.createElement(import_MenuItem5.default, { value: "LiteLLM_ObjectPermissionTable" }, "Team access (KB / MCP)"),
|
|
3764
|
+
/* @__PURE__ */ import_react10.default.createElement(import_MenuItem5.default, { value: "LiteLLM_UserTable" }, "User")
|
|
3765
|
+
), /* @__PURE__ */ import_react10.default.createElement(
|
|
3375
3766
|
import_TextField6.default,
|
|
3376
3767
|
{
|
|
3377
3768
|
size: "small",
|
|
@@ -3385,9 +3776,9 @@ var init_AuditLog = __esm({
|
|
|
3385
3776
|
}
|
|
3386
3777
|
))
|
|
3387
3778
|
},
|
|
3388
|
-
error && /* @__PURE__ */
|
|
3389
|
-
/* @__PURE__ */
|
|
3390
|
-
/* @__PURE__ */
|
|
3779
|
+
error && /* @__PURE__ */ import_react10.default.createElement(import_Box10.default, { px: 2.5, pb: 2 }, /* @__PURE__ */ import_react10.default.createElement(import_Alert5.default, { severity: "error" }, error.message)),
|
|
3780
|
+
/* @__PURE__ */ import_react10.default.createElement(import_TableContainer6.default, null, /* @__PURE__ */ import_react10.default.createElement(import_Table6.default, { size: "small", sx: dataTableSx }, /* @__PURE__ */ import_react10.default.createElement(import_TableHead6.default, null, /* @__PURE__ */ import_react10.default.createElement(import_TableRow6.default, null, /* @__PURE__ */ import_react10.default.createElement(import_TableCell6.default, { sx: { width: 40 } }), /* @__PURE__ */ import_react10.default.createElement(import_TableCell6.default, null, "Time"), /* @__PURE__ */ import_react10.default.createElement(import_TableCell6.default, null, "Action"), /* @__PURE__ */ import_react10.default.createElement(import_TableCell6.default, null, "Table"), /* @__PURE__ */ import_react10.default.createElement(import_TableCell6.default, null, "Object ID"), /* @__PURE__ */ import_react10.default.createElement(import_TableCell6.default, null, "Changed By"))), /* @__PURE__ */ import_react10.default.createElement(import_TableBody6.default, null, renderAuditLogBody(loading, entries)))),
|
|
3781
|
+
/* @__PURE__ */ import_react10.default.createElement(
|
|
3391
3782
|
import_TablePagination.default,
|
|
3392
3783
|
{
|
|
3393
3784
|
component: "div",
|
|
@@ -3456,27 +3847,29 @@ function initDateRange() {
|
|
|
3456
3847
|
else start.setDate(start.getDate() - 7);
|
|
3457
3848
|
return { start, end };
|
|
3458
3849
|
}
|
|
3459
|
-
var
|
|
3850
|
+
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;
|
|
3460
3851
|
var init_LiteLLMPage = __esm({
|
|
3461
3852
|
"src/components/LiteLLMPage.tsx"() {
|
|
3462
3853
|
"use strict";
|
|
3463
|
-
|
|
3464
|
-
|
|
3854
|
+
import_react11 = __toESM(require("react"));
|
|
3855
|
+
import_Box11 = __toESM(require("@mui/material/Box"));
|
|
3465
3856
|
import_Snackbar2 = __toESM(require("@mui/material/Snackbar"));
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3857
|
+
import_Alert6 = __toESM(require("@mui/material/Alert"));
|
|
3858
|
+
import_CircularProgress6 = __toESM(require("@mui/material/CircularProgress"));
|
|
3859
|
+
import_Typography11 = __toESM(require("@mui/material/Typography"));
|
|
3860
|
+
import_Paper7 = __toESM(require("@mui/material/Paper"));
|
|
3470
3861
|
import_Tabs2 = __toESM(require("@mui/material/Tabs"));
|
|
3471
3862
|
import_Tab2 = __toESM(require("@mui/material/Tab"));
|
|
3863
|
+
import_Grid2 = __toESM(require("@mui/material/Grid"));
|
|
3472
3864
|
import_react_use3 = require("react-use");
|
|
3473
|
-
|
|
3865
|
+
import_core_plugin_api4 = require("@backstage/core-plugin-api");
|
|
3474
3866
|
import_plugin_permission_react = require("@backstage/plugin-permission-react");
|
|
3475
3867
|
init_DashboardHeader();
|
|
3476
3868
|
init_KeysTable();
|
|
3477
3869
|
init_GenerateKeyDialog();
|
|
3478
3870
|
init_ManageTeamDialog();
|
|
3479
3871
|
init_UsageStats();
|
|
3872
|
+
init_LiteLLMBudgetWidget();
|
|
3480
3873
|
init_TeamUsage();
|
|
3481
3874
|
init_ModelsTable();
|
|
3482
3875
|
init_AuditLog();
|
|
@@ -3484,21 +3877,21 @@ var init_LiteLLMPage = __esm({
|
|
|
3484
3877
|
init_permissions();
|
|
3485
3878
|
PERIOD_LS_KEY2 = "litellm_usage_period";
|
|
3486
3879
|
LiteLLMPage = () => {
|
|
3487
|
-
const api = (0,
|
|
3488
|
-
const [dateRange, setDateRange] = (0,
|
|
3489
|
-
const [currentPreset, setCurrentPreset] = (0,
|
|
3880
|
+
const api = (0, import_core_plugin_api4.useApi)(liteLlmApiRef);
|
|
3881
|
+
const [dateRange, setDateRange] = (0, import_react11.useState)(initDateRange);
|
|
3882
|
+
const [currentPreset, setCurrentPreset] = (0, import_react11.useState)(() => {
|
|
3490
3883
|
try {
|
|
3491
3884
|
return localStorage.getItem(PERIOD_LS_KEY2) ?? "7d";
|
|
3492
3885
|
} catch {
|
|
3493
3886
|
return "7d";
|
|
3494
3887
|
}
|
|
3495
3888
|
});
|
|
3496
|
-
const [activeTab, setActiveTab] = (0,
|
|
3497
|
-
const [snackbar, setSnackbar] = (0,
|
|
3498
|
-
const [generateDialogOpen, setGenerateDialogOpen] = (0,
|
|
3499
|
-
const [manageTeam, setManageTeam] = (0,
|
|
3500
|
-
const [teamUsageCache, setTeamUsageCache] = (0,
|
|
3501
|
-
const [teamUsageLoading, setTeamUsageLoading] = (0,
|
|
3889
|
+
const [activeTab, setActiveTab] = (0, import_react11.useState)("overview");
|
|
3890
|
+
const [snackbar, setSnackbar] = (0, import_react11.useState)(null);
|
|
3891
|
+
const [generateDialogOpen, setGenerateDialogOpen] = (0, import_react11.useState)(false);
|
|
3892
|
+
const [manageTeam, setManageTeam] = (0, import_react11.useState)(null);
|
|
3893
|
+
const [teamUsageCache, setTeamUsageCache] = (0, import_react11.useState)({});
|
|
3894
|
+
const [teamUsageLoading, setTeamUsageLoading] = (0, import_react11.useState)({});
|
|
3502
3895
|
const { value: userInfo, loading: userLoading, error: userError } = (0, import_react_use3.useAsync)(
|
|
3503
3896
|
() => api.getUserInfo(),
|
|
3504
3897
|
[api]
|
|
@@ -3542,7 +3935,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3542
3935
|
async () => objectPermsEnabled ? api.getMcpServers().catch(() => []) : [],
|
|
3543
3936
|
[api, objectPermsEnabled]
|
|
3544
3937
|
);
|
|
3545
|
-
const teams = (0,
|
|
3938
|
+
const teams = (0, import_react11.useMemo)(() => {
|
|
3546
3939
|
if (!allTeams?.length) return [];
|
|
3547
3940
|
if (!userInfo) return allTeams;
|
|
3548
3941
|
const userId = userInfo.user_id;
|
|
@@ -3559,7 +3952,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3559
3952
|
const endDate = dateRange.end.toISOString().split("T")[0];
|
|
3560
3953
|
return api.getUsage(startDate, endDate);
|
|
3561
3954
|
}, [api, dateRange]);
|
|
3562
|
-
const handleDateRangeChange = (0,
|
|
3955
|
+
const handleDateRangeChange = (0, import_react11.useCallback)((range, preset) => {
|
|
3563
3956
|
setDateRange(range);
|
|
3564
3957
|
if (preset) {
|
|
3565
3958
|
setCurrentPreset(preset);
|
|
@@ -3567,7 +3960,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3567
3960
|
setTeamUsageCache({});
|
|
3568
3961
|
setTeamUsageLoading({});
|
|
3569
3962
|
}, [setDateRange, setCurrentPreset]);
|
|
3570
|
-
const loadTeamUsage = (0,
|
|
3963
|
+
const loadTeamUsage = (0, import_react11.useCallback)(async (teamId) => {
|
|
3571
3964
|
if (teamUsageCache[teamId] !== void 0 || teamUsageLoading[teamId]) return;
|
|
3572
3965
|
setTeamUsageLoading((prev) => ({ ...prev, [teamId]: true }));
|
|
3573
3966
|
try {
|
|
@@ -3581,7 +3974,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3581
3974
|
setTeamUsageLoading((prev) => ({ ...prev, [teamId]: false }));
|
|
3582
3975
|
}
|
|
3583
3976
|
}, [api, dateRange, teamUsageCache, teamUsageLoading]);
|
|
3584
|
-
const allowedModels = (0,
|
|
3977
|
+
const allowedModels = (0, import_react11.useMemo)(() => {
|
|
3585
3978
|
if (!allModels?.length) return [];
|
|
3586
3979
|
const userModels = userInfo?.models;
|
|
3587
3980
|
const teamModels = teams?.flatMap((t) => t.models ?? []);
|
|
@@ -3594,7 +3987,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3594
3987
|
]);
|
|
3595
3988
|
return allModels.filter((m) => allowed.has(m.model_name));
|
|
3596
3989
|
}, [allModels, userInfo, teams]);
|
|
3597
|
-
const handleGenerateKey = (0,
|
|
3990
|
+
const handleGenerateKey = (0, import_react11.useCallback)(
|
|
3598
3991
|
async (request) => {
|
|
3599
3992
|
try {
|
|
3600
3993
|
const response = await api.generateKey(request);
|
|
@@ -3608,7 +4001,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3608
4001
|
},
|
|
3609
4002
|
[api, refreshKeys]
|
|
3610
4003
|
);
|
|
3611
|
-
const handleUpdateKey = (0,
|
|
4004
|
+
const handleUpdateKey = (0, import_react11.useCallback)(
|
|
3612
4005
|
async (keyId, request) => {
|
|
3613
4006
|
try {
|
|
3614
4007
|
await api.updateKey(keyId, request);
|
|
@@ -3621,7 +4014,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3621
4014
|
},
|
|
3622
4015
|
[api, refreshKeys]
|
|
3623
4016
|
);
|
|
3624
|
-
const handleBlockKey = (0,
|
|
4017
|
+
const handleBlockKey = (0, import_react11.useCallback)(
|
|
3625
4018
|
async (keyId) => {
|
|
3626
4019
|
try {
|
|
3627
4020
|
await api.blockKey(keyId);
|
|
@@ -3633,7 +4026,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3633
4026
|
},
|
|
3634
4027
|
[api, refreshKeys]
|
|
3635
4028
|
);
|
|
3636
|
-
const handleUnblockKey = (0,
|
|
4029
|
+
const handleUnblockKey = (0, import_react11.useCallback)(
|
|
3637
4030
|
async (keyId) => {
|
|
3638
4031
|
try {
|
|
3639
4032
|
await api.unblockKey(keyId);
|
|
@@ -3645,7 +4038,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3645
4038
|
},
|
|
3646
4039
|
[api, refreshKeys]
|
|
3647
4040
|
);
|
|
3648
|
-
const handleResetKeySpend = (0,
|
|
4041
|
+
const handleResetKeySpend = (0, import_react11.useCallback)(
|
|
3649
4042
|
async (keyId) => {
|
|
3650
4043
|
try {
|
|
3651
4044
|
await api.resetKeySpend(keyId);
|
|
@@ -3657,7 +4050,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3657
4050
|
},
|
|
3658
4051
|
[api, refreshKeys]
|
|
3659
4052
|
);
|
|
3660
|
-
const handleDeleteKey = (0,
|
|
4053
|
+
const handleDeleteKey = (0, import_react11.useCallback)(
|
|
3661
4054
|
async (keyId) => {
|
|
3662
4055
|
try {
|
|
3663
4056
|
await api.deleteKey(keyId);
|
|
@@ -3674,7 +4067,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3674
4067
|
},
|
|
3675
4068
|
[api, refreshKeys]
|
|
3676
4069
|
);
|
|
3677
|
-
const handlePruneExpiredKeys = (0,
|
|
4070
|
+
const handlePruneExpiredKeys = (0, import_react11.useCallback)(
|
|
3678
4071
|
async () => {
|
|
3679
4072
|
try {
|
|
3680
4073
|
const result = await api.pruneExpiredKeys();
|
|
@@ -3689,14 +4082,14 @@ var init_LiteLLMPage = __esm({
|
|
|
3689
4082
|
);
|
|
3690
4083
|
const isInitialLoading = userLoading && !userInfo;
|
|
3691
4084
|
if (isInitialLoading) {
|
|
3692
|
-
return /* @__PURE__ */
|
|
4085
|
+
return /* @__PURE__ */ import_react11.default.createElement(import_Box11.default, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: "50vh" }, /* @__PURE__ */ import_react11.default.createElement(import_CircularProgress6.default, null));
|
|
3693
4086
|
}
|
|
3694
4087
|
if (userError || !userInfo) {
|
|
3695
4088
|
const isProvisioningEnabled = userError?.body?.provisioning === true;
|
|
3696
4089
|
const hint = userError?.body?.hint;
|
|
3697
|
-
return /* @__PURE__ */
|
|
4090
|
+
return /* @__PURE__ */ import_react11.default.createElement(import_Box11.default, { p: 3 }, /* @__PURE__ */ import_react11.default.createElement(import_Paper7.default, { sx: { p: 3 } }, /* @__PURE__ */ import_react11.default.createElement(import_Typography11.default, { variant: "h6", gutterBottom: true }, "Account not provisioned"), /* @__PURE__ */ import_react11.default.createElement(import_Typography11.default, { color: "text.secondary", paragraph: true }, "Your Backstage account is not linked to a LiteLLM user."), hint ? /* @__PURE__ */ import_react11.default.createElement(import_Typography11.default, { variant: "body2", color: "text.secondary" }, hint) : /* @__PURE__ */ import_react11.default.createElement(import_Typography11.default, { variant: "body2", color: "text.secondary" }, isProvisioningEnabled ? "Auto-provisioning is enabled but failed. Check the backend logs." : "Set litellm.provisioning.enabled: true in app-config.yaml to enable auto-provisioning, or ask your administrator to create the account manually.")));
|
|
3698
4091
|
}
|
|
3699
|
-
const pageTabs = /* @__PURE__ */
|
|
4092
|
+
const pageTabs = /* @__PURE__ */ import_react11.default.createElement(
|
|
3700
4093
|
import_Tabs2.default,
|
|
3701
4094
|
{
|
|
3702
4095
|
value: activeTab,
|
|
@@ -3709,13 +4102,13 @@ var init_LiteLLMPage = __esm({
|
|
|
3709
4102
|
'& [class*="MuiTab-root"]': { minHeight: 44, textTransform: "none", fontSize: 14 }
|
|
3710
4103
|
}
|
|
3711
4104
|
},
|
|
3712
|
-
/* @__PURE__ */
|
|
3713
|
-
/* @__PURE__ */
|
|
3714
|
-
/* @__PURE__ */
|
|
3715
|
-
/* @__PURE__ */
|
|
3716
|
-
userInfo.can_view_audit && /* @__PURE__ */
|
|
4105
|
+
/* @__PURE__ */ import_react11.default.createElement(import_Tab2.default, { label: "Overview", value: "overview" }),
|
|
4106
|
+
/* @__PURE__ */ import_react11.default.createElement(import_Tab2.default, { label: "Keys", value: "keys" }),
|
|
4107
|
+
/* @__PURE__ */ import_react11.default.createElement(import_Tab2.default, { label: "Teams", value: "teams" }),
|
|
4108
|
+
/* @__PURE__ */ import_react11.default.createElement(import_Tab2.default, { label: "Models", value: "models" }),
|
|
4109
|
+
userInfo.can_view_audit && /* @__PURE__ */ import_react11.default.createElement(import_Tab2.default, { label: "Audit Log", value: "audit" })
|
|
3717
4110
|
);
|
|
3718
|
-
return /* @__PURE__ */
|
|
4111
|
+
return /* @__PURE__ */ import_react11.default.createElement(import_Box11.default, { sx: { p: 3, display: "flex", flexDirection: "column", gap: 2 } }, /* @__PURE__ */ import_react11.default.createElement(
|
|
3719
4112
|
DashboardHeader,
|
|
3720
4113
|
{
|
|
3721
4114
|
userInfo,
|
|
@@ -3725,7 +4118,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3725
4118
|
onGenerateKeyClick: () => setGenerateDialogOpen(true),
|
|
3726
4119
|
tabs: pageTabs
|
|
3727
4120
|
}
|
|
3728
|
-
), activeTab === "overview" && /* @__PURE__ */
|
|
4121
|
+
), activeTab === "overview" && /* @__PURE__ */ import_react11.default.createElement(import_Grid2.default, { container: true, spacing: 2, alignItems: "flex-start" }, /* @__PURE__ */ import_react11.default.createElement(import_Grid2.default, { item: true, xs: 12, lg: 8 }, /* @__PURE__ */ import_react11.default.createElement(
|
|
3729
4122
|
UsageStats,
|
|
3730
4123
|
{
|
|
3731
4124
|
usage: usage ?? null,
|
|
@@ -3736,7 +4129,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3736
4129
|
loading: usageLoading,
|
|
3737
4130
|
userInfo
|
|
3738
4131
|
}
|
|
3739
|
-
), activeTab === "keys" && /* @__PURE__ */
|
|
4132
|
+
)), /* @__PURE__ */ import_react11.default.createElement(import_Grid2.default, { item: true, xs: 12, lg: 4 }, /* @__PURE__ */ import_react11.default.createElement(LiteLLMBudgetWidget, { compact: true, collapsible: true }))), activeTab === "keys" && /* @__PURE__ */ import_react11.default.createElement(
|
|
3740
4133
|
KeysTable,
|
|
3741
4134
|
{
|
|
3742
4135
|
keys: keys ?? [],
|
|
@@ -3755,7 +4148,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3755
4148
|
for (const t of managedTeams ?? []) teamsById.set(t.team_id, t);
|
|
3756
4149
|
for (const t of teams ?? []) teamsById.set(t.team_id, t);
|
|
3757
4150
|
const visibleTeams = Array.from(teamsById.values());
|
|
3758
|
-
return /* @__PURE__ */
|
|
4151
|
+
return /* @__PURE__ */ import_react11.default.createElement(
|
|
3759
4152
|
TeamUsage,
|
|
3760
4153
|
{
|
|
3761
4154
|
teams: visibleTeams,
|
|
@@ -3771,14 +4164,14 @@ var init_LiteLLMPage = __esm({
|
|
|
3771
4164
|
onCreateTeam: () => setManageTeam({ mode: "create" })
|
|
3772
4165
|
}
|
|
3773
4166
|
);
|
|
3774
|
-
})(), activeTab === "models" && /* @__PURE__ */
|
|
4167
|
+
})(), activeTab === "models" && /* @__PURE__ */ import_react11.default.createElement(
|
|
3775
4168
|
ModelsTable,
|
|
3776
4169
|
{
|
|
3777
4170
|
allModels: allModels ?? [],
|
|
3778
4171
|
teams: teams ?? [],
|
|
3779
4172
|
loading: modelsLoading
|
|
3780
4173
|
}
|
|
3781
|
-
), activeTab === "audit" && userInfo.can_view_audit && /* @__PURE__ */
|
|
4174
|
+
), activeTab === "audit" && userInfo.can_view_audit && /* @__PURE__ */ import_react11.default.createElement(AuditLog, { api }), /* @__PURE__ */ import_react11.default.createElement(
|
|
3782
4175
|
GenerateKeyDialog,
|
|
3783
4176
|
{
|
|
3784
4177
|
open: generateDialogOpen,
|
|
@@ -3791,7 +4184,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3791
4184
|
onGenerateKey: handleGenerateKey,
|
|
3792
4185
|
onGetConfig: () => api.getConfig()
|
|
3793
4186
|
}
|
|
3794
|
-
), /* @__PURE__ */
|
|
4187
|
+
), /* @__PURE__ */ import_react11.default.createElement(
|
|
3795
4188
|
ManageTeamDialog,
|
|
3796
4189
|
{
|
|
3797
4190
|
open: !!manageTeam,
|
|
@@ -3853,7 +4246,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3853
4246
|
refreshTeams();
|
|
3854
4247
|
}
|
|
3855
4248
|
}
|
|
3856
|
-
), /* @__PURE__ */
|
|
4249
|
+
), /* @__PURE__ */ import_react11.default.createElement(
|
|
3857
4250
|
import_Snackbar2.default,
|
|
3858
4251
|
{
|
|
3859
4252
|
open: !!snackbar,
|
|
@@ -3861,7 +4254,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3861
4254
|
onClose: () => setSnackbar(null),
|
|
3862
4255
|
anchorOrigin: { vertical: "bottom", horizontal: "right" }
|
|
3863
4256
|
},
|
|
3864
|
-
snackbar ? /* @__PURE__ */
|
|
4257
|
+
snackbar ? /* @__PURE__ */ import_react11.default.createElement(import_Alert6.default, { severity: snackbar.severity, onClose: () => setSnackbar(null) }, snackbar.message) : void 0
|
|
3865
4258
|
));
|
|
3866
4259
|
};
|
|
3867
4260
|
}
|
|
@@ -3891,8 +4284,8 @@ __export(index_exports, {
|
|
|
3891
4284
|
module.exports = __toCommonJS(index_exports);
|
|
3892
4285
|
|
|
3893
4286
|
// src/plugin.tsx
|
|
3894
|
-
var
|
|
3895
|
-
var
|
|
4287
|
+
var import_react12 = __toESM(require("react"));
|
|
4288
|
+
var import_icons_material9 = require("@mui/icons-material");
|
|
3896
4289
|
var import_frontend_plugin_api = require("@backstage/frontend-plugin-api");
|
|
3897
4290
|
init_api();
|
|
3898
4291
|
var liteLlmApi = import_frontend_plugin_api.ApiBlueprint.make({
|
|
@@ -3906,10 +4299,10 @@ var liteLlmPage = import_frontend_plugin_api.PageBlueprint.make({
|
|
|
3906
4299
|
params: {
|
|
3907
4300
|
path: "/litellm",
|
|
3908
4301
|
title: "LiteLLM",
|
|
3909
|
-
icon: /* @__PURE__ */
|
|
4302
|
+
icon: /* @__PURE__ */ import_react12.default.createElement(import_icons_material9.TrendingUp, null),
|
|
3910
4303
|
loader: async () => {
|
|
3911
4304
|
const { LiteLLMPage: LiteLLMPage2 } = await Promise.resolve().then(() => (init_LiteLLMPage(), LiteLLMPage_exports));
|
|
3912
|
-
return /* @__PURE__ */
|
|
4305
|
+
return /* @__PURE__ */ import_react12.default.createElement(LiteLLMPage2, null);
|
|
3913
4306
|
}
|
|
3914
4307
|
}
|
|
3915
4308
|
});
|
|
@@ -3926,18 +4319,18 @@ init_UsageStats();
|
|
|
3926
4319
|
init_TeamUsage();
|
|
3927
4320
|
|
|
3928
4321
|
// src/components/LiteLLMHomeWidget.tsx
|
|
3929
|
-
var
|
|
3930
|
-
var
|
|
3931
|
-
var
|
|
3932
|
-
var
|
|
4322
|
+
var import_react13 = __toESM(require("react"));
|
|
4323
|
+
var import_Paper8 = __toESM(require("@mui/material/Paper"));
|
|
4324
|
+
var import_Box12 = __toESM(require("@mui/material/Box"));
|
|
4325
|
+
var import_Typography12 = __toESM(require("@mui/material/Typography"));
|
|
3933
4326
|
var import_FormControl2 = __toESM(require("@mui/material/FormControl"));
|
|
3934
4327
|
var import_Select2 = __toESM(require("@mui/material/Select"));
|
|
3935
4328
|
var import_MenuItem6 = __toESM(require("@mui/material/MenuItem"));
|
|
3936
|
-
var
|
|
3937
|
-
var
|
|
3938
|
-
var
|
|
4329
|
+
var import_Grid3 = __toESM(require("@mui/material/Grid"));
|
|
4330
|
+
var import_CircularProgress7 = __toESM(require("@mui/material/CircularProgress"));
|
|
4331
|
+
var import_Alert7 = __toESM(require("@mui/material/Alert"));
|
|
3939
4332
|
var import_recharts3 = require("recharts");
|
|
3940
|
-
var
|
|
4333
|
+
var import_core_plugin_api5 = require("@backstage/core-plugin-api");
|
|
3941
4334
|
init_api();
|
|
3942
4335
|
init_format();
|
|
3943
4336
|
function presetToDateRange(preset) {
|
|
@@ -3952,19 +4345,19 @@ function presetToDateRange(preset) {
|
|
|
3952
4345
|
}
|
|
3953
4346
|
return { start, end };
|
|
3954
4347
|
}
|
|
3955
|
-
var Kpi = ({ label, value }) => /* @__PURE__ */
|
|
4348
|
+
var Kpi = ({ label, value }) => /* @__PURE__ */ import_react13.default.createElement(import_Box12.default, null, /* @__PURE__ */ import_react13.default.createElement(import_Typography12.default, { variant: "caption", color: "text.secondary", display: "block" }, label), /* @__PURE__ */ import_react13.default.createElement(import_Typography12.default, { variant: "subtitle1", fontWeight: 600 }, value));
|
|
3956
4349
|
var LiteLLMHomeWidget = ({
|
|
3957
4350
|
defaultPeriod = "7d",
|
|
3958
4351
|
title = "LiteLLM Usage"
|
|
3959
4352
|
}) => {
|
|
3960
|
-
const api = (0,
|
|
3961
|
-
const [period, setPeriod] = (0,
|
|
3962
|
-
const [loading, setLoading] = (0,
|
|
3963
|
-
const [usageError, setUsageError] = (0,
|
|
3964
|
-
const [keysError, setKeysError] = (0,
|
|
3965
|
-
const [usage, setUsage] = (0,
|
|
3966
|
-
const [keys, setKeys] = (0,
|
|
3967
|
-
(0,
|
|
4353
|
+
const api = (0, import_core_plugin_api5.useApi)(liteLlmApiRef);
|
|
4354
|
+
const [period, setPeriod] = (0, import_react13.useState)(defaultPeriod);
|
|
4355
|
+
const [loading, setLoading] = (0, import_react13.useState)(true);
|
|
4356
|
+
const [usageError, setUsageError] = (0, import_react13.useState)(null);
|
|
4357
|
+
const [keysError, setKeysError] = (0, import_react13.useState)(null);
|
|
4358
|
+
const [usage, setUsage] = (0, import_react13.useState)(null);
|
|
4359
|
+
const [keys, setKeys] = (0, import_react13.useState)([]);
|
|
4360
|
+
(0, import_react13.useEffect)(() => {
|
|
3968
4361
|
let cancelled = false;
|
|
3969
4362
|
setLoading(true);
|
|
3970
4363
|
setUsageError(null);
|
|
@@ -4001,17 +4394,17 @@ var LiteLLMHomeWidget = ({
|
|
|
4001
4394
|
spend: d.spend
|
|
4002
4395
|
}));
|
|
4003
4396
|
const hasSparkline = dailyData.length > 0;
|
|
4004
|
-
return /* @__PURE__ */
|
|
4397
|
+
return /* @__PURE__ */ import_react13.default.createElement(import_Paper8.default, { sx: { p: 2 } }, /* @__PURE__ */ import_react13.default.createElement(import_Box12.default, { display: "flex", justifyContent: "space-between", alignItems: "center", mb: 1.5 }, /* @__PURE__ */ import_react13.default.createElement(import_Typography12.default, { variant: "h6" }, title), /* @__PURE__ */ import_react13.default.createElement(import_FormControl2.default, { size: "small", sx: { minWidth: 90 } }, /* @__PURE__ */ import_react13.default.createElement(
|
|
4005
4398
|
import_Select2.default,
|
|
4006
4399
|
{
|
|
4007
4400
|
value: period,
|
|
4008
4401
|
onChange: (e) => setPeriod(e.target.value),
|
|
4009
4402
|
displayEmpty: true
|
|
4010
4403
|
},
|
|
4011
|
-
/* @__PURE__ */
|
|
4012
|
-
/* @__PURE__ */
|
|
4013
|
-
/* @__PURE__ */
|
|
4014
|
-
))), loading && /* @__PURE__ */
|
|
4404
|
+
/* @__PURE__ */ import_react13.default.createElement(import_MenuItem6.default, { value: "today" }, "Today"),
|
|
4405
|
+
/* @__PURE__ */ import_react13.default.createElement(import_MenuItem6.default, { value: "7d" }, "7d"),
|
|
4406
|
+
/* @__PURE__ */ import_react13.default.createElement(import_MenuItem6.default, { value: "30d" }, "30d")
|
|
4407
|
+
))), loading && /* @__PURE__ */ import_react13.default.createElement(import_Box12.default, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: 120 }, /* @__PURE__ */ import_react13.default.createElement(import_CircularProgress7.default, { size: 32 })), !loading && totalFailure && /* @__PURE__ */ import_react13.default.createElement(import_Alert7.default, { severity: "error", sx: { mt: 1 } }, usageError ?? "Failed to load usage data"), !loading && !totalFailure && /* @__PURE__ */ import_react13.default.createElement(import_react13.default.Fragment, null, partialFailure && /* @__PURE__ */ import_react13.default.createElement(import_Alert7.default, { severity: "warning", sx: { mt: 1, mb: 1 } }, usageError ? `Usage data unavailable (${usageError}).` : "", keysError ? ` Key list unavailable (${keysError}).` : "", " Showing what loaded."), /* @__PURE__ */ import_react13.default.createElement(import_Grid3.default, { container: true, spacing: 2, sx: { mb: hasSparkline ? 1.5 : 0 } }, /* @__PURE__ */ import_react13.default.createElement(import_Grid3.default, { item: true, xs: 6 }, /* @__PURE__ */ import_react13.default.createElement(Kpi, { label: "USD Spent", value: fmtUsd(usage?.total_spend ?? 0) })), /* @__PURE__ */ import_react13.default.createElement(import_Grid3.default, { item: true, xs: 6 }, /* @__PURE__ */ import_react13.default.createElement(Kpi, { label: "Tokens In", value: fmtInt(usage?.prompt_tokens ?? 0) })), /* @__PURE__ */ import_react13.default.createElement(import_Grid3.default, { item: true, xs: 6 }, /* @__PURE__ */ import_react13.default.createElement(Kpi, { label: "Tokens Out", value: fmtInt(usage?.completion_tokens ?? 0) })), /* @__PURE__ */ import_react13.default.createElement(import_Grid3.default, { item: true, xs: 6 }, /* @__PURE__ */ import_react13.default.createElement(Kpi, { label: "Keys", value: fmtInt(keys.length) }))), hasSparkline && /* @__PURE__ */ import_react13.default.createElement(import_Box12.default, { height: 120 }, /* @__PURE__ */ import_react13.default.createElement(import_recharts3.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react13.default.createElement(import_recharts3.AreaChart, { data: dailyData, margin: { top: 4, right: 0, bottom: 0, left: 0 } }, /* @__PURE__ */ import_react13.default.createElement(
|
|
4015
4408
|
import_recharts3.Area,
|
|
4016
4409
|
{
|
|
4017
4410
|
type: "monotone",
|
|
@@ -4025,281 +4418,8 @@ var LiteLLMHomeWidget = ({
|
|
|
4025
4418
|
))))));
|
|
4026
4419
|
};
|
|
4027
4420
|
|
|
4028
|
-
// src/components/LiteLLMBudgetWidget.tsx
|
|
4029
|
-
var import_react13 = __toESM(require("react"));
|
|
4030
|
-
var import_Paper8 = __toESM(require("@mui/material/Paper"));
|
|
4031
|
-
var import_Box12 = __toESM(require("@mui/material/Box"));
|
|
4032
|
-
var import_Typography12 = __toESM(require("@mui/material/Typography"));
|
|
4033
|
-
var import_CircularProgress7 = __toESM(require("@mui/material/CircularProgress"));
|
|
4034
|
-
var import_Alert7 = __toESM(require("@mui/material/Alert"));
|
|
4035
|
-
var import_styles7 = require("@mui/material/styles");
|
|
4036
|
-
var import_core_plugin_api5 = require("@backstage/core-plugin-api");
|
|
4037
|
-
init_api();
|
|
4038
|
-
init_format();
|
|
4039
|
-
init_ui();
|
|
4040
|
-
|
|
4041
|
-
// src/budget.ts
|
|
4042
|
-
function limitPct(spend, budget) {
|
|
4043
|
-
if (budget <= 0) return 0;
|
|
4044
|
-
return spend / budget * 100;
|
|
4045
|
-
}
|
|
4046
|
-
function buildBudgetSummary(user, teams, keys, maxKeys = 3) {
|
|
4047
|
-
const budgeted = keys.filter((k) => (k.max_budget ?? 0) > 0).map((k) => {
|
|
4048
|
-
const spend = k.spend ?? 0;
|
|
4049
|
-
return {
|
|
4050
|
-
kind: "key",
|
|
4051
|
-
label: k.key_alias || k.key || "Untitled key",
|
|
4052
|
-
sublabel: k.key,
|
|
4053
|
-
budget: k.max_budget,
|
|
4054
|
-
spend,
|
|
4055
|
-
budgetDuration: k.budget_duration,
|
|
4056
|
-
pct: limitPct(spend, k.max_budget)
|
|
4057
|
-
};
|
|
4058
|
-
}).sort((a, b) => b.pct - a.pct);
|
|
4059
|
-
const userBudget = user && (user.max_budget ?? user.hard_limit ?? 0) > 0 ? (() => {
|
|
4060
|
-
const budget = user.max_budget ?? user.hard_limit ?? 0;
|
|
4061
|
-
const spend = user.current_spend ?? user.spend ?? 0;
|
|
4062
|
-
return {
|
|
4063
|
-
kind: "user",
|
|
4064
|
-
label: "Personal budget",
|
|
4065
|
-
sublabel: user.user_email || user.user_id,
|
|
4066
|
-
budget,
|
|
4067
|
-
spend,
|
|
4068
|
-
budgetDuration: user.budget_duration,
|
|
4069
|
-
softLimit: user.soft_limit,
|
|
4070
|
-
pct: limitPct(spend, budget)
|
|
4071
|
-
};
|
|
4072
|
-
})() : void 0;
|
|
4073
|
-
const teamLimits = teams.filter((t) => (t.max_budget ?? 0) > 0).map((t) => {
|
|
4074
|
-
const spend = t.spend ?? 0;
|
|
4075
|
-
return {
|
|
4076
|
-
kind: "team",
|
|
4077
|
-
label: t.team_alias || "Untitled team",
|
|
4078
|
-
sublabel: t.team_id,
|
|
4079
|
-
budget: t.max_budget,
|
|
4080
|
-
spend,
|
|
4081
|
-
budgetDuration: t.budget_duration,
|
|
4082
|
-
pct: limitPct(spend, t.max_budget)
|
|
4083
|
-
};
|
|
4084
|
-
}).sort((a, b) => b.pct - a.pct);
|
|
4085
|
-
return {
|
|
4086
|
-
keys: budgeted.slice(0, maxKeys),
|
|
4087
|
-
hiddenBudgetedKeys: Math.max(0, budgeted.length - maxKeys),
|
|
4088
|
-
totalKeyCount: keys.length,
|
|
4089
|
-
user: userBudget,
|
|
4090
|
-
teams: teamLimits
|
|
4091
|
-
};
|
|
4092
|
-
}
|
|
4093
|
-
function fmtBudgetDuration(duration) {
|
|
4094
|
-
if (!duration) return null;
|
|
4095
|
-
const m = /^(\d+)(s|m|h|d)$/.exec(duration);
|
|
4096
|
-
if (!m) return duration;
|
|
4097
|
-
const n = Number(m[1]);
|
|
4098
|
-
const unit = m[2];
|
|
4099
|
-
if (unit === "d") return n === 1 ? "daily" : `every ${n} days`;
|
|
4100
|
-
if (unit === "h") return n === 1 ? "hourly" : `every ${n} hours`;
|
|
4101
|
-
if (unit === "m") return n === 1 ? "every minute" : `every ${n} minutes`;
|
|
4102
|
-
return n === 1 ? "every second" : `every ${n} seconds`;
|
|
4103
|
-
}
|
|
4104
|
-
function budgetTone3(pct) {
|
|
4105
|
-
if (pct >= 100) return "danger";
|
|
4106
|
-
if (pct >= 80) return "warning";
|
|
4107
|
-
return "accent";
|
|
4108
|
-
}
|
|
4109
|
-
|
|
4110
|
-
// src/components/LiteLLMBudgetWidget.tsx
|
|
4111
|
-
function levelToneColor(tone, theme) {
|
|
4112
|
-
switch (tone) {
|
|
4113
|
-
case "success":
|
|
4114
|
-
return theme.palette.success.main;
|
|
4115
|
-
case "warning":
|
|
4116
|
-
return theme.palette.warning.main;
|
|
4117
|
-
case "danger":
|
|
4118
|
-
return theme.palette.error.main;
|
|
4119
|
-
case "info":
|
|
4120
|
-
return theme.palette.info.main;
|
|
4121
|
-
default:
|
|
4122
|
-
return theme.palette.primary.main;
|
|
4123
|
-
}
|
|
4124
|
-
}
|
|
4125
|
-
var LevelFrame = ({
|
|
4126
|
-
rank,
|
|
4127
|
-
name,
|
|
4128
|
-
tagline,
|
|
4129
|
-
note,
|
|
4130
|
-
tone,
|
|
4131
|
-
isLast,
|
|
4132
|
-
children
|
|
4133
|
-
}) => /* @__PURE__ */ import_react13.default.createElement(import_Box12.default, { sx: { display: "flex", gap: 1.5 } }, /* @__PURE__ */ import_react13.default.createElement(import_Box12.default, { sx: { display: "flex", flexDirection: "column", alignItems: "center", width: 26, flexShrink: 0 } }, /* @__PURE__ */ import_react13.default.createElement(
|
|
4134
|
-
import_Box12.default,
|
|
4135
|
-
{
|
|
4136
|
-
sx: (theme) => ({
|
|
4137
|
-
width: 26,
|
|
4138
|
-
height: 26,
|
|
4139
|
-
borderRadius: "50%",
|
|
4140
|
-
display: "flex",
|
|
4141
|
-
alignItems: "center",
|
|
4142
|
-
justifyContent: "center",
|
|
4143
|
-
bgcolor: (0, import_styles7.alpha)(levelToneColor(tone, theme), 0.14),
|
|
4144
|
-
color: levelToneColor(tone, theme),
|
|
4145
|
-
fontSize: 12,
|
|
4146
|
-
fontWeight: 700,
|
|
4147
|
-
flexShrink: 0
|
|
4148
|
-
})
|
|
4149
|
-
},
|
|
4150
|
-
rank
|
|
4151
|
-
), /* @__PURE__ */ import_react13.default.createElement(
|
|
4152
|
-
import_Box12.default,
|
|
4153
|
-
{
|
|
4154
|
-
sx: (theme) => ({
|
|
4155
|
-
flex: 1,
|
|
4156
|
-
width: 2,
|
|
4157
|
-
minHeight: 14,
|
|
4158
|
-
bgcolor: isLast ? "transparent" : (0, import_styles7.alpha)(theme.palette.text.primary, 0.12)
|
|
4159
|
-
})
|
|
4160
|
-
}
|
|
4161
|
-
)), /* @__PURE__ */ import_react13.default.createElement(import_Box12.default, { sx: { flex: 1, minWidth: 0, pb: isLast ? 0 : 2 } }, /* @__PURE__ */ import_react13.default.createElement(import_Box12.default, { sx: { display: "flex", alignItems: "baseline", gap: 1, flexWrap: "wrap", mb: 0.75 } }, /* @__PURE__ */ import_react13.default.createElement(
|
|
4162
|
-
import_Typography12.default,
|
|
4163
|
-
{
|
|
4164
|
-
sx: (theme) => ({
|
|
4165
|
-
fontSize: 12.5,
|
|
4166
|
-
fontWeight: 700,
|
|
4167
|
-
letterSpacing: "0.08em",
|
|
4168
|
-
textTransform: "uppercase",
|
|
4169
|
-
color: levelToneColor(tone, theme)
|
|
4170
|
-
})
|
|
4171
|
-
},
|
|
4172
|
-
name
|
|
4173
|
-
), /* @__PURE__ */ import_react13.default.createElement(import_Typography12.default, { variant: "caption", color: "text.secondary", sx: { fontSize: 11.5 } }, tagline), note && /* @__PURE__ */ import_react13.default.createElement(StatusPill, { label: note, tone: "neutral", dot: false, sx: { ml: "auto" } })), /* @__PURE__ */ import_react13.default.createElement(import_Box12.default, { sx: { display: "flex", flexDirection: "column", gap: 1 } }, children)));
|
|
4174
|
-
var EmptyLimitCard = ({ children }) => /* @__PURE__ */ import_react13.default.createElement(
|
|
4175
|
-
import_Paper8.default,
|
|
4176
|
-
{
|
|
4177
|
-
variant: "outlined",
|
|
4178
|
-
sx: (theme) => ({
|
|
4179
|
-
px: 1.5,
|
|
4180
|
-
py: 1.25,
|
|
4181
|
-
borderRadius: 1.5,
|
|
4182
|
-
bgcolor: (0, import_styles7.alpha)(theme.palette.text.primary, theme.palette.mode === "dark" ? 0.03 : 0.015)
|
|
4183
|
-
})
|
|
4184
|
-
},
|
|
4185
|
-
/* @__PURE__ */ import_react13.default.createElement(import_Typography12.default, { variant: "body2", color: "text.secondary", sx: { fontSize: 13 } }, children)
|
|
4186
|
-
);
|
|
4187
|
-
var LimitCard = ({ limit }) => {
|
|
4188
|
-
const tone = budgetTone3(limit.pct);
|
|
4189
|
-
const pct = Math.round(limit.pct);
|
|
4190
|
-
const closeTo = limit.softLimit !== void 0 && limit.spend >= limit.softLimit;
|
|
4191
|
-
return /* @__PURE__ */ import_react13.default.createElement(import_Paper8.default, { variant: "outlined", sx: { px: 1.5, py: 1.25, borderRadius: 1.5 } }, /* @__PURE__ */ import_react13.default.createElement(import_Box12.default, { sx: { display: "flex", alignItems: "baseline", justifyContent: "space-between", gap: 1.5 } }, /* @__PURE__ */ import_react13.default.createElement(import_Box12.default, { sx: { minWidth: 0, flex: 1 } }, /* @__PURE__ */ import_react13.default.createElement(
|
|
4192
|
-
import_Typography12.default,
|
|
4193
|
-
{
|
|
4194
|
-
variant: "body2",
|
|
4195
|
-
sx: { fontWeight: 600, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" },
|
|
4196
|
-
title: limit.sublabel ? `${limit.label} \xB7 ${limit.sublabel}` : limit.label
|
|
4197
|
-
},
|
|
4198
|
-
limit.label
|
|
4199
|
-
), limit.sublabel && /* @__PURE__ */ import_react13.default.createElement(
|
|
4200
|
-
import_Typography12.default,
|
|
4201
|
-
{
|
|
4202
|
-
variant: "caption",
|
|
4203
|
-
color: "text.secondary",
|
|
4204
|
-
sx: {
|
|
4205
|
-
fontSize: 10.5,
|
|
4206
|
-
fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
|
|
4207
|
-
display: "block",
|
|
4208
|
-
overflow: "hidden",
|
|
4209
|
-
textOverflow: "ellipsis",
|
|
4210
|
-
whiteSpace: "nowrap"
|
|
4211
|
-
}
|
|
4212
|
-
},
|
|
4213
|
-
limit.sublabel
|
|
4214
|
-
)), /* @__PURE__ */ import_react13.default.createElement(import_Typography12.default, { variant: "body2", sx: { fontWeight: 600, fontVariantNumeric: "tabular-nums", whiteSpace: "nowrap" } }, fmtUsd(limit.spend), /* @__PURE__ */ import_react13.default.createElement(import_Box12.default, { component: "span", color: "text.secondary", sx: { fontWeight: 400 } }, " / ", fmtUsd(limit.budget)))), /* @__PURE__ */ import_react13.default.createElement(import_Box12.default, { sx: { mt: 1 } }, /* @__PURE__ */ import_react13.default.createElement(Meter, { value: limit.pct, tone, height: 6 })), /* @__PURE__ */ import_react13.default.createElement(
|
|
4215
|
-
import_Box12.default,
|
|
4216
|
-
{
|
|
4217
|
-
sx: {
|
|
4218
|
-
display: "flex",
|
|
4219
|
-
justifyContent: "space-between",
|
|
4220
|
-
alignItems: "center",
|
|
4221
|
-
gap: 1,
|
|
4222
|
-
mt: 0.75
|
|
4223
|
-
}
|
|
4224
|
-
},
|
|
4225
|
-
/* @__PURE__ */ import_react13.default.createElement(import_Typography12.default, { variant: "caption", color: "text.secondary", sx: { fontVariantNumeric: "tabular-nums" } }, pct, "% of budget", closeTo && ` \xB7 soft-limit warning`),
|
|
4226
|
-
/* @__PURE__ */ import_react13.default.createElement(import_Typography12.default, { variant: "caption", color: "text.secondary" }, fmtBudgetDuration(limit.budgetDuration) ? `resets ${fmtBudgetDuration(limit.budgetDuration)}` : "never resets")
|
|
4227
|
-
));
|
|
4228
|
-
};
|
|
4229
|
-
var LiteLLMBudgetWidget = ({
|
|
4230
|
-
title = "Budget Policy",
|
|
4231
|
-
maxKeys = 3
|
|
4232
|
-
}) => {
|
|
4233
|
-
const api = (0, import_core_plugin_api5.useApi)(liteLlmApiRef);
|
|
4234
|
-
const [loading, setLoading] = (0, import_react13.useState)(true);
|
|
4235
|
-
const [error, setError] = (0, import_react13.useState)(null);
|
|
4236
|
-
const [user, setUser] = (0, import_react13.useState)(null);
|
|
4237
|
-
const [teams, setTeams] = (0, import_react13.useState)([]);
|
|
4238
|
-
const [keys, setKeys] = (0, import_react13.useState)([]);
|
|
4239
|
-
(0, import_react13.useEffect)(() => {
|
|
4240
|
-
let cancelled = false;
|
|
4241
|
-
setLoading(true);
|
|
4242
|
-
setError(null);
|
|
4243
|
-
Promise.allSettled([api.getUserInfo(), api.getTeams(), api.listKeys()]).then(
|
|
4244
|
-
([userResult, teamsResult, keysResult]) => {
|
|
4245
|
-
if (cancelled) return;
|
|
4246
|
-
if (userResult.status === "fulfilled") {
|
|
4247
|
-
setUser(userResult.value);
|
|
4248
|
-
} else {
|
|
4249
|
-
setUser(null);
|
|
4250
|
-
setError(
|
|
4251
|
-
userResult.reason?.message ?? "Failed to load your LiteLLM profile"
|
|
4252
|
-
);
|
|
4253
|
-
}
|
|
4254
|
-
setTeams(teamsResult.status === "fulfilled" ? teamsResult.value : []);
|
|
4255
|
-
setKeys(keysResult.status === "fulfilled" ? keysResult.value : []);
|
|
4256
|
-
setLoading(false);
|
|
4257
|
-
}
|
|
4258
|
-
);
|
|
4259
|
-
return () => {
|
|
4260
|
-
cancelled = true;
|
|
4261
|
-
};
|
|
4262
|
-
}, [api]);
|
|
4263
|
-
const hasAnyLimit = !!user && (user.max_budget ?? user.hard_limit ?? 0) > 0;
|
|
4264
|
-
const summary = (0, import_react13.useMemo)(() => buildBudgetSummary(user, teams, keys, maxKeys), [user, teams, keys, maxKeys]);
|
|
4265
|
-
const hasBudgetedKeys = keys.some((k) => (k.max_budget ?? 0) > 0);
|
|
4266
|
-
return /* @__PURE__ */ import_react13.default.createElement(import_Paper8.default, { sx: { p: 2 } }, /* @__PURE__ */ import_react13.default.createElement(import_Box12.default, { sx: { mb: 1.5 } }, /* @__PURE__ */ import_react13.default.createElement(import_Typography12.default, { variant: "h6", lineHeight: 1.2 }, title), /* @__PURE__ */ import_react13.default.createElement(import_Typography12.default, { variant: "caption", color: "text.secondary" }, "How LiteLLM caps spend \u2014 and where you stand right now")), loading && /* @__PURE__ */ import_react13.default.createElement(import_Box12.default, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: 140 }, /* @__PURE__ */ import_react13.default.createElement(import_CircularProgress7.default, { size: 32 })), !loading && error && /* @__PURE__ */ import_react13.default.createElement(import_Alert7.default, { severity: "error", sx: { mt: 0.5 } }, error), !loading && !error && /* @__PURE__ */ import_react13.default.createElement(import_react13.default.Fragment, null, /* @__PURE__ */ import_react13.default.createElement(
|
|
4267
|
-
LevelFrame,
|
|
4268
|
-
{
|
|
4269
|
-
rank: 1,
|
|
4270
|
-
name: "Key",
|
|
4271
|
-
tagline: "per-key cap \xB7 highest priority",
|
|
4272
|
-
note: hasBudgetedKeys ? void 0 : "no key budgets set"
|
|
4273
|
-
},
|
|
4274
|
-
!hasBudgetedKeys && /* @__PURE__ */ import_react13.default.createElement(EmptyLimitCard, null, "No key has a budget, so no key-level cap applies."),
|
|
4275
|
-
summary.keys.map((k) => /* @__PURE__ */ import_react13.default.createElement(LimitCard, { key: k.sublabel ?? k.label, limit: k })),
|
|
4276
|
-
summary.hiddenBudgetedKeys > 0 && /* @__PURE__ */ import_react13.default.createElement(import_Typography12.default, { variant: "caption", color: "text.secondary" }, "+", summary.hiddenBudgetedKeys, " more budgeted key", summary.hiddenBudgetedKeys > 1 ? "s" : "", " further from the cap")
|
|
4277
|
-
), /* @__PURE__ */ import_react13.default.createElement(
|
|
4278
|
-
LevelFrame,
|
|
4279
|
-
{
|
|
4280
|
-
rank: 2,
|
|
4281
|
-
name: "User",
|
|
4282
|
-
tagline: "cap on everything you spend with your own keys",
|
|
4283
|
-
note: "skipped for team-bound keys"
|
|
4284
|
-
},
|
|
4285
|
-
hasAnyLimit && summary.user ? /* @__PURE__ */ import_react13.default.createElement(LimitCard, { limit: summary.user }) : /* @__PURE__ */ import_react13.default.createElement(EmptyLimitCard, null, "No personal budget is set on your account.")
|
|
4286
|
-
), /* @__PURE__ */ import_react13.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_react13.default.createElement(LimitCard, { key: t.sublabel ?? t.label, limit: t })) : /* @__PURE__ */ import_react13.default.createElement(EmptyLimitCard, null, "No team you belong to has a budget.")), /* @__PURE__ */ import_react13.default.createElement(LevelFrame, { rank: 4, name: "Global", tagline: "proxy-wide cap set by your admin", isLast: true }, /* @__PURE__ */ import_react13.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_react13.default.createElement(
|
|
4287
|
-
import_Box12.default,
|
|
4288
|
-
{
|
|
4289
|
-
sx: (theme) => ({
|
|
4290
|
-
mt: 2,
|
|
4291
|
-
px: 1.5,
|
|
4292
|
-
py: 1.25,
|
|
4293
|
-
borderRadius: 1.5,
|
|
4294
|
-
border: "1px dashed",
|
|
4295
|
-
borderColor: (0, import_styles7.alpha)(theme.palette.text.primary, 0.18)
|
|
4296
|
-
})
|
|
4297
|
-
},
|
|
4298
|
-
/* @__PURE__ */ import_react13.default.createElement(import_Typography12.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.")
|
|
4299
|
-
)));
|
|
4300
|
-
};
|
|
4301
|
-
|
|
4302
4421
|
// src/index.ts
|
|
4422
|
+
init_LiteLLMBudgetWidget();
|
|
4303
4423
|
init_GenerateKeyDialog();
|
|
4304
4424
|
init_ManageTeamDialog();
|
|
4305
4425
|
init_api();
|