@acarmisc/backstage-plugin-litellm 0.20.0 → 0.21.0
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 +9 -0
- package/dist/components/LiteLLMBudgetWidget.d.ts +22 -0
- package/dist/index.cjs.js +590 -481
- package/dist/index.cjs.js.map +4 -4
- package/dist/index.esm.js +569 -460
- package/dist/index.esm.js.map +4 -4
- package/package.json +1 -1
package/dist/index.cjs.js
CHANGED
|
@@ -2756,20 +2756,400 @@ 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 pcts = [
|
|
2813
|
+
...summary.keys.map((k) => k.pct),
|
|
2814
|
+
...summary.user ? [summary.user.pct] : [],
|
|
2815
|
+
...summary.teams.map((t) => t.pct)
|
|
2816
|
+
];
|
|
2817
|
+
const count = summary.keys.length + summary.hiddenBudgetedKeys + (summary.user ? 1 : 0) + summary.teams.length;
|
|
2818
|
+
return {
|
|
2819
|
+
count,
|
|
2820
|
+
closestPct: pcts.length ? Math.max(...pcts) : null
|
|
2821
|
+
};
|
|
2822
|
+
}
|
|
2823
|
+
function fmtBudgetDuration(duration) {
|
|
2824
|
+
if (!duration) return null;
|
|
2825
|
+
const m = /^(\d+)(s|m|h|d)$/.exec(duration);
|
|
2826
|
+
if (!m) return duration;
|
|
2827
|
+
const n = Number(m[1]);
|
|
2828
|
+
const unit = m[2];
|
|
2829
|
+
if (unit === "d") return n === 1 ? "daily" : `every ${n} days`;
|
|
2830
|
+
if (unit === "h") return n === 1 ? "hourly" : `every ${n} hours`;
|
|
2831
|
+
if (unit === "m") return n === 1 ? "every minute" : `every ${n} minutes`;
|
|
2832
|
+
return n === 1 ? "every second" : `every ${n} seconds`;
|
|
2833
|
+
}
|
|
2834
|
+
function budgetTone2(pct) {
|
|
2835
|
+
if (pct >= 100) return "danger";
|
|
2836
|
+
if (pct >= 80) return "warning";
|
|
2837
|
+
return "accent";
|
|
2838
|
+
}
|
|
2839
|
+
var init_budget = __esm({
|
|
2840
|
+
"src/budget.ts"() {
|
|
2841
|
+
"use strict";
|
|
2842
|
+
}
|
|
2843
|
+
});
|
|
2844
|
+
|
|
2845
|
+
// src/components/LiteLLMBudgetWidget.tsx
|
|
2846
|
+
function levelToneColor(tone, theme) {
|
|
2847
|
+
switch (tone) {
|
|
2848
|
+
case "success":
|
|
2849
|
+
return theme.palette.success.main;
|
|
2850
|
+
case "warning":
|
|
2851
|
+
return theme.palette.warning.main;
|
|
2852
|
+
case "danger":
|
|
2853
|
+
return theme.palette.error.main;
|
|
2854
|
+
case "info":
|
|
2855
|
+
return theme.palette.info.main;
|
|
2856
|
+
default:
|
|
2857
|
+
return theme.palette.primary.main;
|
|
2858
|
+
}
|
|
2859
|
+
}
|
|
2860
|
+
var import_react7, import_Paper5, import_Box7, import_Typography7, import_CircularProgress4, import_Alert3, import_Collapse, import_IconButton4, import_icons_material5, import_styles4, import_core_plugin_api3, LevelFrame, EmptyLimitCard, LimitCard, TaggedLimit, FullBody, CompactBody, LiteLLMBudgetWidget;
|
|
2861
|
+
var init_LiteLLMBudgetWidget = __esm({
|
|
2862
|
+
"src/components/LiteLLMBudgetWidget.tsx"() {
|
|
2863
|
+
"use strict";
|
|
2864
|
+
import_react7 = __toESM(require("react"));
|
|
2865
|
+
import_Paper5 = __toESM(require("@mui/material/Paper"));
|
|
2866
|
+
import_Box7 = __toESM(require("@mui/material/Box"));
|
|
2867
|
+
import_Typography7 = __toESM(require("@mui/material/Typography"));
|
|
2868
|
+
import_CircularProgress4 = __toESM(require("@mui/material/CircularProgress"));
|
|
2869
|
+
import_Alert3 = __toESM(require("@mui/material/Alert"));
|
|
2870
|
+
import_Collapse = __toESM(require("@mui/material/Collapse"));
|
|
2871
|
+
import_IconButton4 = __toESM(require("@mui/material/IconButton"));
|
|
2872
|
+
import_icons_material5 = require("@mui/icons-material");
|
|
2873
|
+
import_styles4 = require("@mui/material/styles");
|
|
2874
|
+
import_core_plugin_api3 = require("@backstage/core-plugin-api");
|
|
2875
|
+
init_api();
|
|
2876
|
+
init_format();
|
|
2877
|
+
init_ui();
|
|
2878
|
+
init_budget();
|
|
2879
|
+
LevelFrame = ({
|
|
2880
|
+
rank,
|
|
2881
|
+
name,
|
|
2882
|
+
tagline,
|
|
2883
|
+
note,
|
|
2884
|
+
tone,
|
|
2885
|
+
isLast,
|
|
2886
|
+
children
|
|
2887
|
+
}) => /* @__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(
|
|
2888
|
+
import_Box7.default,
|
|
2889
|
+
{
|
|
2890
|
+
sx: (theme) => ({
|
|
2891
|
+
width: 26,
|
|
2892
|
+
height: 26,
|
|
2893
|
+
borderRadius: "50%",
|
|
2894
|
+
display: "flex",
|
|
2895
|
+
alignItems: "center",
|
|
2896
|
+
justifyContent: "center",
|
|
2897
|
+
bgcolor: (0, import_styles4.alpha)(levelToneColor(tone, theme), 0.14),
|
|
2898
|
+
color: levelToneColor(tone, theme),
|
|
2899
|
+
fontSize: 12,
|
|
2900
|
+
fontWeight: 700,
|
|
2901
|
+
flexShrink: 0
|
|
2902
|
+
})
|
|
2903
|
+
},
|
|
2904
|
+
rank
|
|
2905
|
+
), /* @__PURE__ */ import_react7.default.createElement(
|
|
2906
|
+
import_Box7.default,
|
|
2907
|
+
{
|
|
2908
|
+
sx: (theme) => ({
|
|
2909
|
+
flex: 1,
|
|
2910
|
+
width: 2,
|
|
2911
|
+
minHeight: 14,
|
|
2912
|
+
bgcolor: isLast ? "transparent" : (0, import_styles4.alpha)(theme.palette.text.primary, 0.12)
|
|
2913
|
+
})
|
|
2914
|
+
}
|
|
2915
|
+
)), /* @__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(
|
|
2916
|
+
import_Typography7.default,
|
|
2917
|
+
{
|
|
2918
|
+
sx: (theme) => ({
|
|
2919
|
+
fontSize: 12.5,
|
|
2920
|
+
fontWeight: 700,
|
|
2921
|
+
letterSpacing: "0.08em",
|
|
2922
|
+
textTransform: "uppercase",
|
|
2923
|
+
color: levelToneColor(tone, theme)
|
|
2924
|
+
})
|
|
2925
|
+
},
|
|
2926
|
+
name
|
|
2927
|
+
), /* @__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)));
|
|
2928
|
+
EmptyLimitCard = ({ children }) => /* @__PURE__ */ import_react7.default.createElement(
|
|
2929
|
+
import_Paper5.default,
|
|
2930
|
+
{
|
|
2931
|
+
variant: "outlined",
|
|
2932
|
+
sx: (theme) => ({
|
|
2933
|
+
px: 1.5,
|
|
2934
|
+
py: 1.25,
|
|
2935
|
+
borderRadius: 1.5,
|
|
2936
|
+
bgcolor: (0, import_styles4.alpha)(theme.palette.text.primary, theme.palette.mode === "dark" ? 0.03 : 0.015)
|
|
2937
|
+
})
|
|
2938
|
+
},
|
|
2939
|
+
/* @__PURE__ */ import_react7.default.createElement(import_Typography7.default, { variant: "body2", color: "text.secondary", sx: { fontSize: 13 } }, children)
|
|
2940
|
+
);
|
|
2941
|
+
LimitCard = ({ limit }) => {
|
|
2942
|
+
const tone = budgetTone2(limit.pct);
|
|
2943
|
+
const pct = Math.round(limit.pct);
|
|
2944
|
+
const closeTo = limit.softLimit !== void 0 && limit.spend >= limit.softLimit;
|
|
2945
|
+
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(
|
|
2946
|
+
import_Typography7.default,
|
|
2947
|
+
{
|
|
2948
|
+
variant: "body2",
|
|
2949
|
+
sx: { fontWeight: 600, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" },
|
|
2950
|
+
title: limit.sublabel ? `${limit.label} \xB7 ${limit.sublabel}` : limit.label
|
|
2951
|
+
},
|
|
2952
|
+
limit.label
|
|
2953
|
+
), limit.sublabel && /* @__PURE__ */ import_react7.default.createElement(
|
|
2954
|
+
import_Typography7.default,
|
|
2955
|
+
{
|
|
2956
|
+
variant: "caption",
|
|
2957
|
+
color: "text.secondary",
|
|
2958
|
+
sx: {
|
|
2959
|
+
fontSize: 10.5,
|
|
2960
|
+
fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
|
|
2961
|
+
display: "block",
|
|
2962
|
+
overflow: "hidden",
|
|
2963
|
+
textOverflow: "ellipsis",
|
|
2964
|
+
whiteSpace: "nowrap"
|
|
2965
|
+
}
|
|
2966
|
+
},
|
|
2967
|
+
limit.sublabel
|
|
2968
|
+
)), /* @__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(
|
|
2969
|
+
import_Box7.default,
|
|
2970
|
+
{
|
|
2971
|
+
sx: {
|
|
2972
|
+
display: "flex",
|
|
2973
|
+
justifyContent: "space-between",
|
|
2974
|
+
alignItems: "center",
|
|
2975
|
+
gap: 1,
|
|
2976
|
+
mt: 0.75
|
|
2977
|
+
}
|
|
2978
|
+
},
|
|
2979
|
+
/* @__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`),
|
|
2980
|
+
/* @__PURE__ */ import_react7.default.createElement(import_Typography7.default, { variant: "caption", color: "text.secondary" }, fmtBudgetDuration(limit.budgetDuration) ? `resets ${fmtBudgetDuration(limit.budgetDuration)}` : "never resets")
|
|
2981
|
+
));
|
|
2982
|
+
};
|
|
2983
|
+
TaggedLimit = ({ limit }) => {
|
|
2984
|
+
const tone = budgetTone2(limit.pct);
|
|
2985
|
+
return /* @__PURE__ */ import_react7.default.createElement(import_Box7.default, null, /* @__PURE__ */ import_react7.default.createElement(
|
|
2986
|
+
import_Typography7.default,
|
|
2987
|
+
{
|
|
2988
|
+
sx: (theme) => ({
|
|
2989
|
+
fontSize: 11,
|
|
2990
|
+
fontWeight: 700,
|
|
2991
|
+
letterSpacing: "0.08em",
|
|
2992
|
+
textTransform: "uppercase",
|
|
2993
|
+
color: tone === "accent" ? theme.palette.text.secondary : levelToneColor(tone, theme),
|
|
2994
|
+
mb: 0.5
|
|
2995
|
+
})
|
|
2996
|
+
},
|
|
2997
|
+
limit.kind
|
|
2998
|
+
), /* @__PURE__ */ import_react7.default.createElement(LimitCard, { limit }));
|
|
2999
|
+
};
|
|
3000
|
+
FullBody = ({ summary }) => {
|
|
3001
|
+
const hasBudgetedKeys = summary.keys.length + summary.hiddenBudgetedKeys > 0;
|
|
3002
|
+
return /* @__PURE__ */ import_react7.default.createElement(import_react7.default.Fragment, null, /* @__PURE__ */ import_react7.default.createElement(
|
|
3003
|
+
LevelFrame,
|
|
3004
|
+
{
|
|
3005
|
+
rank: 1,
|
|
3006
|
+
name: "Key",
|
|
3007
|
+
tagline: "per-key cap \xB7 highest priority",
|
|
3008
|
+
note: hasBudgetedKeys ? void 0 : "no key budgets set"
|
|
3009
|
+
},
|
|
3010
|
+
!hasBudgetedKeys && /* @__PURE__ */ import_react7.default.createElement(EmptyLimitCard, null, "No key has a budget, so no key-level cap applies."),
|
|
3011
|
+
summary.keys.map((k) => /* @__PURE__ */ import_react7.default.createElement(LimitCard, { key: k.sublabel ?? k.label, limit: k })),
|
|
3012
|
+
summary.hiddenBudgetedKeys > 0 && /* @__PURE__ */ import_react7.default.createElement(import_Typography7.default, { variant: "caption", color: "text.secondary" }, "+", summary.hiddenBudgetedKeys, " more budgeted key", summary.hiddenBudgetedKeys > 1 ? "s" : "", " further from the cap")
|
|
3013
|
+
), /* @__PURE__ */ import_react7.default.createElement(
|
|
3014
|
+
LevelFrame,
|
|
3015
|
+
{
|
|
3016
|
+
rank: 2,
|
|
3017
|
+
name: "User",
|
|
3018
|
+
tagline: "cap on everything you spend with your own keys",
|
|
3019
|
+
note: "skipped for team-bound keys"
|
|
3020
|
+
},
|
|
3021
|
+
summary.user ? /* @__PURE__ */ import_react7.default.createElement(LimitCard, { limit: summary.user }) : /* @__PURE__ */ import_react7.default.createElement(EmptyLimitCard, null, "No personal budget is set on your account.")
|
|
3022
|
+
), /* @__PURE__ */ import_react7.default.createElement(LevelFrame, { rank: 3, name: "Team", tagline: "shared cap for all keys in a team (if any)" }, summary.teams.length > 0 ? summary.teams.map((t) => /* @__PURE__ */ import_react7.default.createElement(LimitCard, { key: t.sublabel ?? t.label, limit: t })) : /* @__PURE__ */ import_react7.default.createElement(EmptyLimitCard, null, "No team you belong to has a budget.")), /* @__PURE__ */ import_react7.default.createElement(LevelFrame, { rank: 4, name: "Global", tagline: "proxy-wide cap set by your admin", isLast: true }, /* @__PURE__ */ import_react7.default.createElement(EmptyLimitCard, null, "Configured by your admin in the proxy config \u2014 it isn't visible from this page. When set, it limits spend across every request on the proxy.")), /* @__PURE__ */ import_react7.default.createElement(
|
|
3023
|
+
import_Box7.default,
|
|
3024
|
+
{
|
|
3025
|
+
sx: (theme) => ({
|
|
3026
|
+
mt: 2,
|
|
3027
|
+
px: 1.5,
|
|
3028
|
+
py: 1.25,
|
|
3029
|
+
borderRadius: 1.5,
|
|
3030
|
+
border: "1px dashed",
|
|
3031
|
+
borderColor: (0, import_styles4.alpha)(theme.palette.text.primary, 0.18)
|
|
3032
|
+
})
|
|
3033
|
+
},
|
|
3034
|
+
/* @__PURE__ */ import_react7.default.createElement(import_Typography7.default, { variant: "caption", color: "text.secondary", display: "block" }, "The first cap a request runs into is the one that stops it. A limit with a reset window (e.g. daily or every 30 days) clears its spend to $0 when the window closes; without a window it never resets.")
|
|
3035
|
+
));
|
|
3036
|
+
};
|
|
3037
|
+
CompactBody = ({ summary }) => {
|
|
3038
|
+
const { count } = budgetHeadline(summary);
|
|
3039
|
+
if (count === 0) {
|
|
3040
|
+
return /* @__PURE__ */ import_react7.default.createElement(import_Typography7.default, { variant: "body2", color: "text.secondary", sx: { fontSize: 13 } }, "No budget limits apply to you right now.");
|
|
3041
|
+
}
|
|
3042
|
+
return /* @__PURE__ */ import_react7.default.createElement(import_Box7.default, { sx: { display: "flex", flexDirection: "column", gap: 1.25 } }, summary.keys.map((k) => /* @__PURE__ */ import_react7.default.createElement(TaggedLimit, { key: k.sublabel ?? k.label, limit: k })), summary.hiddenBudgetedKeys > 0 && /* @__PURE__ */ import_react7.default.createElement(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 })));
|
|
3043
|
+
};
|
|
3044
|
+
LiteLLMBudgetWidget = ({
|
|
3045
|
+
title = "Budget Policy",
|
|
3046
|
+
maxKeys = 3,
|
|
3047
|
+
compact = false,
|
|
3048
|
+
collapsible = false,
|
|
3049
|
+
defaultExpanded = true,
|
|
3050
|
+
action
|
|
3051
|
+
}) => {
|
|
3052
|
+
const api = (0, import_core_plugin_api3.useApi)(liteLlmApiRef);
|
|
3053
|
+
const [loading, setLoading] = (0, import_react7.useState)(true);
|
|
3054
|
+
const [error, setError] = (0, import_react7.useState)(null);
|
|
3055
|
+
const [user, setUser] = (0, import_react7.useState)(null);
|
|
3056
|
+
const [teams, setTeams] = (0, import_react7.useState)([]);
|
|
3057
|
+
const [keys, setKeys] = (0, import_react7.useState)([]);
|
|
3058
|
+
const [expanded, setExpanded] = (0, import_react7.useState)(defaultExpanded);
|
|
3059
|
+
(0, import_react7.useEffect)(() => {
|
|
3060
|
+
let cancelled = false;
|
|
3061
|
+
setLoading(true);
|
|
3062
|
+
setError(null);
|
|
3063
|
+
Promise.allSettled([api.getUserInfo(), api.getTeams(), api.listKeys()]).then(
|
|
3064
|
+
([userResult, teamsResult, keysResult]) => {
|
|
3065
|
+
if (cancelled) return;
|
|
3066
|
+
if (userResult.status === "fulfilled") {
|
|
3067
|
+
setUser(userResult.value);
|
|
3068
|
+
} else {
|
|
3069
|
+
setUser(null);
|
|
3070
|
+
setError(
|
|
3071
|
+
userResult.reason?.message ?? "Failed to load your LiteLLM profile"
|
|
3072
|
+
);
|
|
3073
|
+
}
|
|
3074
|
+
setTeams(teamsResult.status === "fulfilled" ? teamsResult.value : []);
|
|
3075
|
+
setKeys(keysResult.status === "fulfilled" ? keysResult.value : []);
|
|
3076
|
+
setLoading(false);
|
|
3077
|
+
}
|
|
3078
|
+
);
|
|
3079
|
+
return () => {
|
|
3080
|
+
cancelled = true;
|
|
3081
|
+
};
|
|
3082
|
+
}, [api]);
|
|
3083
|
+
const summary = (0, import_react7.useMemo)(
|
|
3084
|
+
() => buildBudgetSummary(user, teams, keys, maxKeys),
|
|
3085
|
+
[user, teams, keys, maxKeys]
|
|
3086
|
+
);
|
|
3087
|
+
const headline = (0, import_react7.useMemo)(() => budgetHeadline(summary), [summary]);
|
|
3088
|
+
const limitsWord = `${headline.count} limit${headline.count === 1 ? "" : "s"}`;
|
|
3089
|
+
const closestWord = headline.closestPct === null ? "" : ` \xB7 closest ${Math.round(headline.closestPct)}%`;
|
|
3090
|
+
const summaryText = headline.count === 0 ? "no limits apply" : `${limitsWord}${closestWord}`;
|
|
3091
|
+
const body = /* @__PURE__ */ import_react7.default.createElement(import_react7.default.Fragment, null, loading && /* @__PURE__ */ import_react7.default.createElement(import_Box7.default, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: 140 }, /* @__PURE__ */ import_react7.default.createElement(import_CircularProgress4.default, { size: 32 })), !loading && error && /* @__PURE__ */ import_react7.default.createElement(import_Alert3.default, { severity: "error", sx: { mt: 0.5 } }, error), !loading && !error && (compact ? /* @__PURE__ */ import_react7.default.createElement(CompactBody, { summary }) : /* @__PURE__ */ import_react7.default.createElement(FullBody, { summary })));
|
|
3092
|
+
return /* @__PURE__ */ import_react7.default.createElement(import_Paper5.default, { sx: { p: 2 } }, collapsible ? /* @__PURE__ */ import_react7.default.createElement(
|
|
3093
|
+
import_Box7.default,
|
|
3094
|
+
{
|
|
3095
|
+
onClick: () => setExpanded((e) => !e),
|
|
3096
|
+
sx: {
|
|
3097
|
+
display: "flex",
|
|
3098
|
+
alignItems: "center",
|
|
3099
|
+
gap: 1,
|
|
3100
|
+
cursor: "pointer",
|
|
3101
|
+
userSelect: "none",
|
|
3102
|
+
mb: expanded ? 1.5 : 0
|
|
3103
|
+
}
|
|
3104
|
+
},
|
|
3105
|
+
/* @__PURE__ */ import_react7.default.createElement(import_Box7.default, { sx: { minWidth: 0, flex: 1, display: "flex", alignItems: "center", gap: 1, flexWrap: "wrap" } }, /* @__PURE__ */ import_react7.default.createElement(import_Typography7.default, { variant: "h6", lineHeight: 1.2 }, title), !loading && !error && /* @__PURE__ */ import_react7.default.createElement(StatusPill, { label: summaryText, tone: budgetTone2(headline.closestPct ?? 0) })),
|
|
3106
|
+
/* @__PURE__ */ import_react7.default.createElement(
|
|
3107
|
+
import_IconButton4.default,
|
|
3108
|
+
{
|
|
3109
|
+
size: "small",
|
|
3110
|
+
"aria-label": expanded ? "Collapse budget policy" : "Expand budget policy",
|
|
3111
|
+
"aria-expanded": expanded,
|
|
3112
|
+
onClick: (e) => {
|
|
3113
|
+
e.stopPropagation();
|
|
3114
|
+
setExpanded((x) => !x);
|
|
3115
|
+
},
|
|
3116
|
+
sx: (theme) => ({
|
|
3117
|
+
color: theme.palette.text.secondary,
|
|
3118
|
+
transform: expanded ? "rotate(180deg)" : "none",
|
|
3119
|
+
transition: theme.transitions.create("transform")
|
|
3120
|
+
})
|
|
3121
|
+
},
|
|
3122
|
+
/* @__PURE__ */ import_react7.default.createElement(import_icons_material5.ExpandMore, null)
|
|
3123
|
+
)
|
|
3124
|
+
) : /* @__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(
|
|
3125
|
+
import_Box7.default,
|
|
3126
|
+
{
|
|
3127
|
+
sx: (theme) => ({
|
|
3128
|
+
mt: 2,
|
|
3129
|
+
pt: 2,
|
|
3130
|
+
borderTop: `1px solid ${theme.palette.divider}`
|
|
3131
|
+
})
|
|
3132
|
+
},
|
|
3133
|
+
action
|
|
3134
|
+
));
|
|
3135
|
+
};
|
|
3136
|
+
}
|
|
3137
|
+
});
|
|
3138
|
+
|
|
2759
3139
|
// src/components/TeamUsage.tsx
|
|
2760
|
-
function
|
|
3140
|
+
function budgetTone3(isOver, isNear) {
|
|
2761
3141
|
if (isOver) return "danger";
|
|
2762
3142
|
if (isNear) return "warning";
|
|
2763
3143
|
return "accent";
|
|
2764
3144
|
}
|
|
2765
|
-
var
|
|
3145
|
+
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
3146
|
var init_TeamUsage = __esm({
|
|
2767
3147
|
"src/components/TeamUsage.tsx"() {
|
|
2768
3148
|
"use strict";
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
3149
|
+
import_react8 = __toESM(require("react"));
|
|
3150
|
+
import_Paper6 = __toESM(require("@mui/material/Paper"));
|
|
3151
|
+
import_Box8 = __toESM(require("@mui/material/Box"));
|
|
3152
|
+
import_Typography8 = __toESM(require("@mui/material/Typography"));
|
|
2773
3153
|
import_Skeleton4 = __toESM(require("@mui/material/Skeleton"));
|
|
2774
3154
|
import_Divider3 = __toESM(require("@mui/material/Divider"));
|
|
2775
3155
|
import_Table4 = __toESM(require("@mui/material/Table"));
|
|
@@ -2778,17 +3158,17 @@ var init_TeamUsage = __esm({
|
|
|
2778
3158
|
import_TableHead4 = __toESM(require("@mui/material/TableHead"));
|
|
2779
3159
|
import_TableRow4 = __toESM(require("@mui/material/TableRow"));
|
|
2780
3160
|
import_TableContainer4 = __toESM(require("@mui/material/TableContainer"));
|
|
2781
|
-
|
|
2782
|
-
|
|
3161
|
+
import_Collapse2 = __toESM(require("@mui/material/Collapse"));
|
|
3162
|
+
import_IconButton5 = __toESM(require("@mui/material/IconButton"));
|
|
2783
3163
|
import_Button5 = __toESM(require("@mui/material/Button"));
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
3164
|
+
import_CircularProgress5 = __toESM(require("@mui/material/CircularProgress"));
|
|
3165
|
+
import_styles5 = require("@mui/material/styles");
|
|
3166
|
+
import_icons_material6 = require("@mui/icons-material");
|
|
2787
3167
|
import_recharts2 = require("recharts");
|
|
2788
3168
|
init_ui();
|
|
2789
3169
|
fmtUsd2 = (n) => `$${(n ?? 0).toFixed(2)}`;
|
|
2790
3170
|
TeamCard = ({ team, usage, usageLoading, canManage, onEditTeam }) => {
|
|
2791
|
-
const [expanded, setExpanded] = (0,
|
|
3171
|
+
const [expanded, setExpanded] = (0, import_react8.useState)(false);
|
|
2792
3172
|
const chart = useChartTheme();
|
|
2793
3173
|
const budget = team.max_budget ?? 0;
|
|
2794
3174
|
const spend = team.spend ?? 0;
|
|
@@ -2798,24 +3178,24 @@ var init_TeamUsage = __esm({
|
|
|
2798
3178
|
const dailyData = usage?.daily_usage?.map((d) => ({ date: d.date, spend: d.spend })) ?? [];
|
|
2799
3179
|
const renderDailySpendSection = () => {
|
|
2800
3180
|
if (usageLoading) {
|
|
2801
|
-
return /* @__PURE__ */
|
|
3181
|
+
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
3182
|
}
|
|
2803
3183
|
if (dailyData.length === 0) return null;
|
|
2804
|
-
return /* @__PURE__ */
|
|
2805
|
-
|
|
3184
|
+
return /* @__PURE__ */ import_react8.default.createElement(import_Box8.default, { mb: 2.5 }, /* @__PURE__ */ import_react8.default.createElement(
|
|
3185
|
+
import_Typography8.default,
|
|
2806
3186
|
{
|
|
2807
3187
|
variant: "caption",
|
|
2808
3188
|
color: "text.secondary",
|
|
2809
3189
|
sx: { display: "block", fontSize: 11, fontWeight: 700, letterSpacing: "0.07em", textTransform: "uppercase", mb: 1 }
|
|
2810
3190
|
},
|
|
2811
3191
|
"Daily spend"
|
|
2812
|
-
), /* @__PURE__ */
|
|
3192
|
+
), /* @__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
3193
|
import_recharts2.Tooltip,
|
|
2814
3194
|
{
|
|
2815
3195
|
cursor: chart.cursor,
|
|
2816
|
-
content: /* @__PURE__ */
|
|
3196
|
+
content: /* @__PURE__ */ import_react8.default.createElement(ChartTooltip, { valueFormatter: (v) => `$${v.toFixed(4)}` })
|
|
2817
3197
|
}
|
|
2818
|
-
), /* @__PURE__ */
|
|
3198
|
+
), /* @__PURE__ */ import_react8.default.createElement(
|
|
2819
3199
|
import_recharts2.Area,
|
|
2820
3200
|
{
|
|
2821
3201
|
type: "monotone",
|
|
@@ -2828,8 +3208,8 @@ var init_TeamUsage = __esm({
|
|
|
2828
3208
|
}
|
|
2829
3209
|
)))));
|
|
2830
3210
|
};
|
|
2831
|
-
const sectionLabel = (label) => /* @__PURE__ */
|
|
2832
|
-
|
|
3211
|
+
const sectionLabel = (label) => /* @__PURE__ */ import_react8.default.createElement(
|
|
3212
|
+
import_Typography8.default,
|
|
2833
3213
|
{
|
|
2834
3214
|
variant: "caption",
|
|
2835
3215
|
color: "text.secondary",
|
|
@@ -2837,8 +3217,8 @@ var init_TeamUsage = __esm({
|
|
|
2837
3217
|
},
|
|
2838
3218
|
label
|
|
2839
3219
|
);
|
|
2840
|
-
return /* @__PURE__ */
|
|
2841
|
-
|
|
3220
|
+
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(
|
|
3221
|
+
import_Box8.default,
|
|
2842
3222
|
{
|
|
2843
3223
|
sx: (theme) => ({
|
|
2844
3224
|
width: 36,
|
|
@@ -2848,20 +3228,20 @@ var init_TeamUsage = __esm({
|
|
|
2848
3228
|
display: "flex",
|
|
2849
3229
|
alignItems: "center",
|
|
2850
3230
|
justifyContent: "center",
|
|
2851
|
-
bgcolor: (0,
|
|
3231
|
+
bgcolor: (0, import_styles5.alpha)(theme.palette.primary.main, 0.1),
|
|
2852
3232
|
color: theme.palette.primary.main
|
|
2853
3233
|
})
|
|
2854
3234
|
},
|
|
2855
|
-
/* @__PURE__ */
|
|
2856
|
-
), /* @__PURE__ */
|
|
2857
|
-
|
|
3235
|
+
/* @__PURE__ */ import_react8.default.createElement(import_icons_material6.Group, { fontSize: "small" })
|
|
3236
|
+
), /* @__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(
|
|
3237
|
+
import_Typography8.default,
|
|
2858
3238
|
{
|
|
2859
3239
|
variant: "caption",
|
|
2860
3240
|
color: "text.secondary",
|
|
2861
3241
|
sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 11 }
|
|
2862
3242
|
},
|
|
2863
3243
|
team.team_id
|
|
2864
|
-
)), isOver && /* @__PURE__ */
|
|
3244
|
+
)), 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
3245
|
import_Button5.default,
|
|
2866
3246
|
{
|
|
2867
3247
|
size: "small",
|
|
@@ -2870,8 +3250,8 @@ var init_TeamUsage = __esm({
|
|
|
2870
3250
|
sx: { textTransform: "none" }
|
|
2871
3251
|
},
|
|
2872
3252
|
"Edit"
|
|
2873
|
-
), /* @__PURE__ */
|
|
2874
|
-
|
|
3253
|
+
), /* @__PURE__ */ import_react8.default.createElement(
|
|
3254
|
+
import_IconButton5.default,
|
|
2875
3255
|
{
|
|
2876
3256
|
size: "small",
|
|
2877
3257
|
onClick: () => setExpanded((e) => !e),
|
|
@@ -2883,9 +3263,9 @@ var init_TeamUsage = __esm({
|
|
|
2883
3263
|
transition: theme.transitions.create("transform")
|
|
2884
3264
|
})
|
|
2885
3265
|
},
|
|
2886
|
-
/* @__PURE__ */
|
|
2887
|
-
)), /* @__PURE__ */
|
|
2888
|
-
|
|
3266
|
+
/* @__PURE__ */ import_react8.default.createElement(import_icons_material6.ExpandMore, null)
|
|
3267
|
+
)), /* @__PURE__ */ import_react8.default.createElement(
|
|
3268
|
+
import_Box8.default,
|
|
2889
3269
|
{
|
|
2890
3270
|
sx: {
|
|
2891
3271
|
display: "grid",
|
|
@@ -2895,35 +3275,35 @@ var init_TeamUsage = __esm({
|
|
|
2895
3275
|
maxWidth: 640
|
|
2896
3276
|
}
|
|
2897
3277
|
},
|
|
2898
|
-
/* @__PURE__ */
|
|
2899
|
-
/* @__PURE__ */
|
|
2900
|
-
/* @__PURE__ */
|
|
2901
|
-
/* @__PURE__ */
|
|
2902
|
-
/* @__PURE__ */
|
|
2903
|
-
/* @__PURE__ */
|
|
2904
|
-
), budget > 0 && /* @__PURE__ */
|
|
3278
|
+
/* @__PURE__ */ import_react8.default.createElement(Stat, { label: "Members", value: team.members_with_roles?.length ?? "\u2014" }),
|
|
3279
|
+
/* @__PURE__ */ import_react8.default.createElement(Stat, { label: "Models", value: team.models?.length ? team.models.length : "All" }),
|
|
3280
|
+
/* @__PURE__ */ import_react8.default.createElement(Stat, { label: "Budget", value: budget > 0 ? fmtUsd2(budget) : "Unlimited" }),
|
|
3281
|
+
/* @__PURE__ */ import_react8.default.createElement(Stat, { label: "Spend", value: fmtUsd2(spend) }),
|
|
3282
|
+
/* @__PURE__ */ import_react8.default.createElement(Stat, { label: "TPM", value: team.tpm_limit && team.tpm_limit > 0 ? team.tpm_limit.toLocaleString() : "\u2014" }),
|
|
3283
|
+
/* @__PURE__ */ import_react8.default.createElement(Stat, { label: "RPM", value: team.rpm_limit && team.rpm_limit > 0 ? team.rpm_limit.toLocaleString() : "\u2014" })
|
|
3284
|
+
), 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
3285
|
import_TableContainer4.default,
|
|
2906
3286
|
{
|
|
2907
|
-
component:
|
|
3287
|
+
component: import_Paper6.default,
|
|
2908
3288
|
variant: "outlined",
|
|
2909
3289
|
sx: { borderRadius: 1.5 }
|
|
2910
3290
|
},
|
|
2911
|
-
/* @__PURE__ */
|
|
2912
|
-
|
|
3291
|
+
/* @__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(
|
|
3292
|
+
import_Typography8.default,
|
|
2913
3293
|
{
|
|
2914
3294
|
variant: "caption",
|
|
2915
3295
|
color: "text.secondary",
|
|
2916
3296
|
sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 11 }
|
|
2917
3297
|
},
|
|
2918
3298
|
m.user_id
|
|
2919
|
-
)) : /* @__PURE__ */
|
|
2920
|
-
|
|
3299
|
+
)) : /* @__PURE__ */ import_react8.default.createElement(
|
|
3300
|
+
import_Typography8.default,
|
|
2921
3301
|
{
|
|
2922
3302
|
variant: "body2",
|
|
2923
3303
|
sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace" }
|
|
2924
3304
|
},
|
|
2925
3305
|
m.user_id
|
|
2926
|
-
)), /* @__PURE__ */
|
|
3306
|
+
)), /* @__PURE__ */ import_react8.default.createElement(import_TableCell4.default, { align: "right" }, /* @__PURE__ */ import_react8.default.createElement(
|
|
2927
3307
|
StatusPill,
|
|
2928
3308
|
{
|
|
2929
3309
|
label: m.role,
|
|
@@ -2931,7 +3311,7 @@ var init_TeamUsage = __esm({
|
|
|
2931
3311
|
dot: false
|
|
2932
3312
|
}
|
|
2933
3313
|
))))))
|
|
2934
|
-
) : /* @__PURE__ */
|
|
3314
|
+
) : /* @__PURE__ */ import_react8.default.createElement(import_Typography8.default, { variant: "body2", color: "text.secondary" }, "No members assigned."))));
|
|
2935
3315
|
};
|
|
2936
3316
|
TeamUsage = ({
|
|
2937
3317
|
teams,
|
|
@@ -2943,22 +3323,22 @@ var init_TeamUsage = __esm({
|
|
|
2943
3323
|
canCreate,
|
|
2944
3324
|
onCreateTeam
|
|
2945
3325
|
}) => {
|
|
2946
|
-
const createAction = canCreate && onCreateTeam ? /* @__PURE__ */
|
|
3326
|
+
const createAction = canCreate && onCreateTeam ? /* @__PURE__ */ import_react8.default.createElement(
|
|
2947
3327
|
import_Button5.default,
|
|
2948
3328
|
{
|
|
2949
3329
|
variant: "contained",
|
|
2950
3330
|
color: "primary",
|
|
2951
3331
|
disableElevation: true,
|
|
2952
|
-
startIcon: /* @__PURE__ */
|
|
3332
|
+
startIcon: /* @__PURE__ */ import_react8.default.createElement(import_icons_material6.Add, null),
|
|
2953
3333
|
onClick: onCreateTeam
|
|
2954
3334
|
},
|
|
2955
3335
|
"Create Team"
|
|
2956
3336
|
) : void 0;
|
|
2957
3337
|
if (loading) {
|
|
2958
|
-
return /* @__PURE__ */
|
|
3338
|
+
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
3339
|
}
|
|
2960
3340
|
if (!teams.length) {
|
|
2961
|
-
return /* @__PURE__ */
|
|
3341
|
+
return /* @__PURE__ */ import_react8.default.createElement(SectionCard, { title: "Teams", actions: createAction }, /* @__PURE__ */ import_react8.default.createElement(
|
|
2962
3342
|
EmptyState,
|
|
2963
3343
|
{
|
|
2964
3344
|
message: "You're not a member of any LiteLLM team yet.",
|
|
@@ -2966,14 +3346,14 @@ var init_TeamUsage = __esm({
|
|
|
2966
3346
|
}
|
|
2967
3347
|
));
|
|
2968
3348
|
}
|
|
2969
|
-
return /* @__PURE__ */
|
|
3349
|
+
return /* @__PURE__ */ import_react8.default.createElement(
|
|
2970
3350
|
SectionCard,
|
|
2971
3351
|
{
|
|
2972
3352
|
title: "Teams",
|
|
2973
3353
|
subtitle: `${teams.length} team${teams.length === 1 ? "" : "s"}`,
|
|
2974
3354
|
actions: createAction
|
|
2975
3355
|
},
|
|
2976
|
-
/* @__PURE__ */
|
|
3356
|
+
/* @__PURE__ */ import_react8.default.createElement(import_Box8.default, { sx: { display: "flex", flexDirection: "column", gap: 2 } }, teams.map((team) => /* @__PURE__ */ import_react8.default.createElement(
|
|
2977
3357
|
TeamCard,
|
|
2978
3358
|
{
|
|
2979
3359
|
key: team.team_id,
|
|
@@ -3007,54 +3387,54 @@ function fmtTokens(n) {
|
|
|
3007
3387
|
if (n >= 1e3) return `${(n / 1e3).toFixed(0)}K`;
|
|
3008
3388
|
return String(n);
|
|
3009
3389
|
}
|
|
3010
|
-
var
|
|
3390
|
+
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
3391
|
var init_ModelsTable = __esm({
|
|
3012
3392
|
"src/components/ModelsTable.tsx"() {
|
|
3013
3393
|
"use strict";
|
|
3014
|
-
|
|
3015
|
-
|
|
3394
|
+
import_react9 = __toESM(require("react"));
|
|
3395
|
+
import_Box9 = __toESM(require("@mui/material/Box"));
|
|
3016
3396
|
import_Table5 = __toESM(require("@mui/material/Table"));
|
|
3017
3397
|
import_TableBody5 = __toESM(require("@mui/material/TableBody"));
|
|
3018
3398
|
import_TableCell5 = __toESM(require("@mui/material/TableCell"));
|
|
3019
3399
|
import_TableContainer5 = __toESM(require("@mui/material/TableContainer"));
|
|
3020
3400
|
import_TableHead5 = __toESM(require("@mui/material/TableHead"));
|
|
3021
3401
|
import_TableRow5 = __toESM(require("@mui/material/TableRow"));
|
|
3022
|
-
|
|
3402
|
+
import_Typography9 = __toESM(require("@mui/material/Typography"));
|
|
3023
3403
|
import_MenuItem4 = __toESM(require("@mui/material/MenuItem"));
|
|
3024
3404
|
import_Select = __toESM(require("@mui/material/Select"));
|
|
3025
3405
|
import_FormControl = __toESM(require("@mui/material/FormControl"));
|
|
3026
3406
|
import_InputLabel = __toESM(require("@mui/material/InputLabel"));
|
|
3027
3407
|
import_TextField5 = __toESM(require("@mui/material/TextField"));
|
|
3028
3408
|
import_InputAdornment2 = __toESM(require("@mui/material/InputAdornment"));
|
|
3029
|
-
|
|
3409
|
+
import_icons_material7 = require("@mui/icons-material");
|
|
3030
3410
|
import_ContentCopy = __toESM(require("@mui/icons-material/ContentCopy"));
|
|
3031
3411
|
import_Tooltip = __toESM(require("@mui/material/Tooltip"));
|
|
3032
3412
|
import_Snackbar = __toESM(require("@mui/material/Snackbar"));
|
|
3033
|
-
|
|
3413
|
+
import_Alert4 = __toESM(require("@mui/material/Alert"));
|
|
3034
3414
|
init_ui();
|
|
3035
|
-
|
|
3415
|
+
import_styles6 = require("@mui/material/styles");
|
|
3036
3416
|
ALL_PROXY_MODELS2 = "all-proxy-models";
|
|
3037
3417
|
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,
|
|
3418
|
+
const theme = (0, import_styles6.useTheme)();
|
|
3419
|
+
const [selectedTeamId, setSelectedTeamId] = (0, import_react9.useState)("");
|
|
3420
|
+
const [filterText, setFilterText] = (0, import_react9.useState)("");
|
|
3421
|
+
const [copiedSnackbar, setCopiedSnackbar] = (0, import_react9.useState)(false);
|
|
3422
|
+
const selectedTeam = (0, import_react9.useMemo)(
|
|
3043
3423
|
() => teams.find((t) => t.team_id === selectedTeamId) ?? null,
|
|
3044
3424
|
[teams, selectedTeamId]
|
|
3045
3425
|
);
|
|
3046
|
-
const teamModels = (0,
|
|
3426
|
+
const teamModels = (0, import_react9.useMemo)(() => {
|
|
3047
3427
|
if (!selectedTeam) return [];
|
|
3048
3428
|
return allModels.filter((model) => isModelAllowedByTeam2(model, selectedTeam.models));
|
|
3049
3429
|
}, [allModels, selectedTeam]);
|
|
3050
|
-
const filteredModels = (0,
|
|
3430
|
+
const filteredModels = (0, import_react9.useMemo)(() => {
|
|
3051
3431
|
const q = filterText.trim().toLowerCase();
|
|
3052
3432
|
if (!q) return teamModels;
|
|
3053
3433
|
return teamModels.filter(
|
|
3054
3434
|
(model) => model.model_name.toLowerCase().includes(q) || (model.access_groups || []).some((group) => group.toLowerCase().includes(q))
|
|
3055
3435
|
);
|
|
3056
3436
|
}, [teamModels, filterText]);
|
|
3057
|
-
const handleCopyModelId = (0,
|
|
3437
|
+
const handleCopyModelId = (0, import_react9.useCallback)((modelId) => {
|
|
3058
3438
|
navigator.clipboard.writeText(modelId).then(() => setCopiedSnackbar(true));
|
|
3059
3439
|
}, []);
|
|
3060
3440
|
const modeColor = (mode) => {
|
|
@@ -3071,13 +3451,13 @@ var init_ModelsTable = __esm({
|
|
|
3071
3451
|
return theme.palette.info.main;
|
|
3072
3452
|
}
|
|
3073
3453
|
};
|
|
3074
|
-
return /* @__PURE__ */
|
|
3454
|
+
return /* @__PURE__ */ import_react9.default.createElement(
|
|
3075
3455
|
SectionCard,
|
|
3076
3456
|
{
|
|
3077
3457
|
title: "Available Models",
|
|
3078
3458
|
subtitle: selectedTeam ? `${filteredModels.length}${filterText.trim() ? ` of ${teamModels.length}` : ""} model${filteredModels.length === 1 ? "" : "s"}` : "Select a team to view its models"
|
|
3079
3459
|
},
|
|
3080
|
-
/* @__PURE__ */
|
|
3460
|
+
/* @__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
3461
|
import_Select.default,
|
|
3082
3462
|
{
|
|
3083
3463
|
labelId: "team-select-label",
|
|
@@ -3085,8 +3465,8 @@ var init_ModelsTable = __esm({
|
|
|
3085
3465
|
label: "Team",
|
|
3086
3466
|
onChange: (e) => setSelectedTeamId(e.target.value)
|
|
3087
3467
|
},
|
|
3088
|
-
teams.map((t) => /* @__PURE__ */
|
|
3089
|
-
)), /* @__PURE__ */
|
|
3468
|
+
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))
|
|
3469
|
+
)), /* @__PURE__ */ import_react9.default.createElement(
|
|
3090
3470
|
import_TextField5.default,
|
|
3091
3471
|
{
|
|
3092
3472
|
label: "Filter Models",
|
|
@@ -3096,33 +3476,33 @@ var init_ModelsTable = __esm({
|
|
|
3096
3476
|
disabled: !selectedTeamId,
|
|
3097
3477
|
sx: { minWidth: 240, mb: 2 },
|
|
3098
3478
|
InputProps: {
|
|
3099
|
-
startAdornment: /* @__PURE__ */
|
|
3479
|
+
startAdornment: /* @__PURE__ */ import_react9.default.createElement(import_InputAdornment2.default, { position: "start" }, /* @__PURE__ */ import_react9.default.createElement(import_icons_material7.Search, { fontSize: "small" }))
|
|
3100
3480
|
}
|
|
3101
3481
|
}
|
|
3102
3482
|
)),
|
|
3103
|
-
loading && /* @__PURE__ */
|
|
3104
|
-
!loading && !selectedTeamId && /* @__PURE__ */
|
|
3483
|
+
loading && /* @__PURE__ */ import_react9.default.createElement(EmptyState, { message: "Loading models\u2026" }),
|
|
3484
|
+
!loading && !selectedTeamId && /* @__PURE__ */ import_react9.default.createElement(
|
|
3105
3485
|
EmptyState,
|
|
3106
3486
|
{
|
|
3107
3487
|
message: "Select a team to see available models",
|
|
3108
3488
|
hint: "Each team may have a different set of models assigned"
|
|
3109
3489
|
}
|
|
3110
3490
|
),
|
|
3111
|
-
!loading && selectedTeamId && teamModels.length === 0 && /* @__PURE__ */
|
|
3491
|
+
!loading && selectedTeamId && teamModels.length === 0 && /* @__PURE__ */ import_react9.default.createElement(
|
|
3112
3492
|
EmptyState,
|
|
3113
3493
|
{
|
|
3114
3494
|
message: "No models available for this team",
|
|
3115
3495
|
hint: allModels.length === 0 ? "No models are configured in the system" : "None of the team's assigned models were found in the catalog"
|
|
3116
3496
|
}
|
|
3117
3497
|
),
|
|
3118
|
-
!loading && selectedTeamId && teamModels.length > 0 && filteredModels.length === 0 && /* @__PURE__ */
|
|
3498
|
+
!loading && selectedTeamId && teamModels.length > 0 && filteredModels.length === 0 && /* @__PURE__ */ import_react9.default.createElement(
|
|
3119
3499
|
EmptyState,
|
|
3120
3500
|
{
|
|
3121
3501
|
message: "No models match your search",
|
|
3122
3502
|
hint: "Try a different search term"
|
|
3123
3503
|
}
|
|
3124
3504
|
),
|
|
3125
|
-
!loading && filteredModels.length > 0 && /* @__PURE__ */
|
|
3505
|
+
!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
3506
|
"span",
|
|
3127
3507
|
{
|
|
3128
3508
|
role: "button",
|
|
@@ -3132,9 +3512,9 @@ var init_ModelsTable = __esm({
|
|
|
3132
3512
|
onKeyDown: (e) => e.key === "Enter" && handleCopyModelId(m.model_name),
|
|
3133
3513
|
style: { cursor: "pointer", display: "inline-flex" }
|
|
3134
3514
|
},
|
|
3135
|
-
/* @__PURE__ */
|
|
3136
|
-
)), /* @__PURE__ */
|
|
3137
|
-
|
|
3515
|
+
/* @__PURE__ */ import_react9.default.createElement(TagChip, { label: m.model_name, title: "Copy model ID" })
|
|
3516
|
+
)), /* @__PURE__ */ import_react9.default.createElement(import_Tooltip.default, { title: "Copy model ID" }, /* @__PURE__ */ import_react9.default.createElement(
|
|
3517
|
+
import_Box9.default,
|
|
3138
3518
|
{
|
|
3139
3519
|
component: "span",
|
|
3140
3520
|
role: "button",
|
|
@@ -3144,9 +3524,9 @@ var init_ModelsTable = __esm({
|
|
|
3144
3524
|
sx: quietIconButtonSx("accent"),
|
|
3145
3525
|
style: { cursor: "pointer", display: "inline-flex" }
|
|
3146
3526
|
},
|
|
3147
|
-
/* @__PURE__ */
|
|
3148
|
-
)))), /* @__PURE__ */
|
|
3149
|
-
|
|
3527
|
+
/* @__PURE__ */ import_react9.default.createElement(import_ContentCopy.default, { sx: { fontSize: 14 } })
|
|
3528
|
+
)))), /* @__PURE__ */ import_react9.default.createElement(import_TableCell5.default, null, /* @__PURE__ */ import_react9.default.createElement(
|
|
3529
|
+
import_Box9.default,
|
|
3150
3530
|
{
|
|
3151
3531
|
component: "span",
|
|
3152
3532
|
sx: {
|
|
@@ -3157,13 +3537,13 @@ var init_ModelsTable = __esm({
|
|
|
3157
3537
|
fontSize: 11,
|
|
3158
3538
|
fontWeight: 600,
|
|
3159
3539
|
textTransform: "capitalize",
|
|
3160
|
-
bgcolor: (0,
|
|
3540
|
+
bgcolor: (0, import_styles6.alpha)(modeColor(m.mode), 0.1),
|
|
3161
3541
|
color: modeColor(m.mode)
|
|
3162
3542
|
}
|
|
3163
3543
|
},
|
|
3164
3544
|
m.mode
|
|
3165
|
-
)), /* @__PURE__ */
|
|
3166
|
-
/* @__PURE__ */
|
|
3545
|
+
)), /* @__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" })))))))),
|
|
3546
|
+
/* @__PURE__ */ import_react9.default.createElement(
|
|
3167
3547
|
import_Snackbar.default,
|
|
3168
3548
|
{
|
|
3169
3549
|
open: copiedSnackbar,
|
|
@@ -3171,7 +3551,7 @@ var init_ModelsTable = __esm({
|
|
|
3171
3551
|
onClose: () => setCopiedSnackbar(false),
|
|
3172
3552
|
anchorOrigin: { vertical: "bottom", horizontal: "center" }
|
|
3173
3553
|
},
|
|
3174
|
-
/* @__PURE__ */
|
|
3554
|
+
/* @__PURE__ */ import_react9.default.createElement(import_Alert4.default, { severity: "success", variant: "filled", sx: { fontSize: 13 } }, "Model ID copied to clipboard")
|
|
3175
3555
|
)
|
|
3176
3556
|
);
|
|
3177
3557
|
};
|
|
@@ -3199,10 +3579,10 @@ function formatDateTime(iso) {
|
|
|
3199
3579
|
}
|
|
3200
3580
|
function renderAuditLogBody(loading, entries) {
|
|
3201
3581
|
if (loading) {
|
|
3202
|
-
return /* @__PURE__ */
|
|
3582
|
+
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
3583
|
}
|
|
3204
3584
|
if (entries.length === 0) {
|
|
3205
|
-
return /* @__PURE__ */
|
|
3585
|
+
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
3586
|
EmptyState,
|
|
3207
3587
|
{
|
|
3208
3588
|
message: "No audit events found",
|
|
@@ -3210,15 +3590,15 @@ function renderAuditLogBody(loading, entries) {
|
|
|
3210
3590
|
}
|
|
3211
3591
|
)));
|
|
3212
3592
|
}
|
|
3213
|
-
return entries.map((entry) => /* @__PURE__ */
|
|
3593
|
+
return entries.map((entry) => /* @__PURE__ */ import_react10.default.createElement(DetailRow, { key: entry.id, entry }));
|
|
3214
3594
|
}
|
|
3215
|
-
var
|
|
3595
|
+
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
3596
|
var init_AuditLog = __esm({
|
|
3217
3597
|
"src/components/AuditLog.tsx"() {
|
|
3218
3598
|
"use strict";
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3599
|
+
import_react10 = __toESM(require("react"));
|
|
3600
|
+
import_Box10 = __toESM(require("@mui/material/Box"));
|
|
3601
|
+
import_Typography10 = __toESM(require("@mui/material/Typography"));
|
|
3222
3602
|
import_Table6 = __toESM(require("@mui/material/Table"));
|
|
3223
3603
|
import_TableBody6 = __toESM(require("@mui/material/TableBody"));
|
|
3224
3604
|
import_TableCell6 = __toESM(require("@mui/material/TableCell"));
|
|
@@ -3229,11 +3609,11 @@ var init_AuditLog = __esm({
|
|
|
3229
3609
|
import_TextField6 = __toESM(require("@mui/material/TextField"));
|
|
3230
3610
|
import_MenuItem5 = __toESM(require("@mui/material/MenuItem"));
|
|
3231
3611
|
import_Skeleton5 = __toESM(require("@mui/material/Skeleton"));
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3612
|
+
import_Collapse3 = __toESM(require("@mui/material/Collapse"));
|
|
3613
|
+
import_IconButton6 = __toESM(require("@mui/material/IconButton"));
|
|
3614
|
+
import_Alert5 = __toESM(require("@mui/material/Alert"));
|
|
3615
|
+
import_styles7 = require("@mui/material/styles");
|
|
3616
|
+
import_icons_material8 = require("@mui/icons-material");
|
|
3237
3617
|
import_react_use2 = require("react-use");
|
|
3238
3618
|
init_ui();
|
|
3239
3619
|
ACTION_TONES = {
|
|
@@ -3251,10 +3631,10 @@ var init_AuditLog = __esm({
|
|
|
3251
3631
|
LiteLLM_UserTable: "User"
|
|
3252
3632
|
};
|
|
3253
3633
|
DetailRow = ({ entry }) => {
|
|
3254
|
-
const [open, setOpen] = (0,
|
|
3634
|
+
const [open, setOpen] = (0, import_react10.useState)(false);
|
|
3255
3635
|
const hasDetail = entry.before_value || entry.updated_values;
|
|
3256
|
-
return /* @__PURE__ */
|
|
3257
|
-
|
|
3636
|
+
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(
|
|
3637
|
+
import_IconButton6.default,
|
|
3258
3638
|
{
|
|
3259
3639
|
size: "small",
|
|
3260
3640
|
onClick: () => setOpen((o) => !o),
|
|
@@ -3266,9 +3646,9 @@ var init_AuditLog = __esm({
|
|
|
3266
3646
|
transition: theme.transitions.create("transform")
|
|
3267
3647
|
})
|
|
3268
3648
|
},
|
|
3269
|
-
/* @__PURE__ */
|
|
3270
|
-
)), /* @__PURE__ */
|
|
3271
|
-
|
|
3649
|
+
/* @__PURE__ */ import_react10.default.createElement(import_icons_material8.KeyboardArrowDown, { fontSize: "small" })
|
|
3650
|
+
)), /* @__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(
|
|
3651
|
+
import_Typography10.default,
|
|
3272
3652
|
{
|
|
3273
3653
|
variant: "body2",
|
|
3274
3654
|
component: "code",
|
|
@@ -3277,8 +3657,8 @@ var init_AuditLog = __esm({
|
|
|
3277
3657
|
sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 12 }
|
|
3278
3658
|
},
|
|
3279
3659
|
entry.object_id ? entry.object_id.slice(0, 20) + (entry.object_id.length > 20 ? "\u2026" : "") : "\u2014"
|
|
3280
|
-
)), /* @__PURE__ */
|
|
3281
|
-
|
|
3660
|
+
)), /* @__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(
|
|
3661
|
+
import_Box10.default,
|
|
3282
3662
|
{
|
|
3283
3663
|
display: "flex",
|
|
3284
3664
|
gap: 2,
|
|
@@ -3287,11 +3667,11 @@ var init_AuditLog = __esm({
|
|
|
3287
3667
|
p: 2,
|
|
3288
3668
|
mb: 1.5,
|
|
3289
3669
|
borderRadius: 1.5,
|
|
3290
|
-
bgcolor: (0,
|
|
3670
|
+
bgcolor: (0, import_styles7.alpha)(theme.palette.text.primary, theme.palette.mode === "dark" ? 0.05 : 0.03)
|
|
3291
3671
|
})
|
|
3292
3672
|
},
|
|
3293
|
-
entry.before_value && /* @__PURE__ */
|
|
3294
|
-
|
|
3673
|
+
entry.before_value && /* @__PURE__ */ import_react10.default.createElement(import_Box10.default, { flex: 1, minWidth: 200 }, /* @__PURE__ */ import_react10.default.createElement(
|
|
3674
|
+
import_Typography10.default,
|
|
3295
3675
|
{
|
|
3296
3676
|
variant: "caption",
|
|
3297
3677
|
color: "text.secondary",
|
|
@@ -3300,9 +3680,9 @@ var init_AuditLog = __esm({
|
|
|
3300
3680
|
sx: { fontSize: 10.5, fontWeight: 700, letterSpacing: "0.07em", textTransform: "uppercase" }
|
|
3301
3681
|
},
|
|
3302
3682
|
"Before"
|
|
3303
|
-
), /* @__PURE__ */
|
|
3304
|
-
entry.updated_values && /* @__PURE__ */
|
|
3305
|
-
|
|
3683
|
+
), /* @__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))),
|
|
3684
|
+
entry.updated_values && /* @__PURE__ */ import_react10.default.createElement(import_Box10.default, { flex: 1, minWidth: 200 }, /* @__PURE__ */ import_react10.default.createElement(
|
|
3685
|
+
import_Typography10.default,
|
|
3306
3686
|
{
|
|
3307
3687
|
variant: "caption",
|
|
3308
3688
|
color: "text.secondary",
|
|
@@ -3311,14 +3691,14 @@ var init_AuditLog = __esm({
|
|
|
3311
3691
|
sx: { fontSize: 10.5, fontWeight: 700, letterSpacing: "0.07em", textTransform: "uppercase" }
|
|
3312
3692
|
},
|
|
3313
3693
|
"After"
|
|
3314
|
-
), /* @__PURE__ */
|
|
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.updated_values, null, 2)))
|
|
3315
3695
|
)))));
|
|
3316
3696
|
};
|
|
3317
3697
|
AuditLog = ({ api }) => {
|
|
3318
|
-
const [page, setPage] = (0,
|
|
3319
|
-
const [pageSize, setPageSize] = (0,
|
|
3320
|
-
const [filters, setFilters] = (0,
|
|
3321
|
-
const fetchParams = (0,
|
|
3698
|
+
const [page, setPage] = (0, import_react10.useState)(0);
|
|
3699
|
+
const [pageSize, setPageSize] = (0, import_react10.useState)(25);
|
|
3700
|
+
const [filters, setFilters] = (0, import_react10.useState)({});
|
|
3701
|
+
const fetchParams = (0, import_react10.useCallback)(
|
|
3322
3702
|
() => ({ page: page + 1, page_size: pageSize, ...filters }),
|
|
3323
3703
|
[page, pageSize, filters]
|
|
3324
3704
|
)();
|
|
@@ -3328,13 +3708,13 @@ var init_AuditLog = __esm({
|
|
|
3328
3708
|
);
|
|
3329
3709
|
const entries = value?.audit_logs ?? [];
|
|
3330
3710
|
const total = value?.total ?? 0;
|
|
3331
|
-
return /* @__PURE__ */
|
|
3711
|
+
return /* @__PURE__ */ import_react10.default.createElement(
|
|
3332
3712
|
SectionCard,
|
|
3333
3713
|
{
|
|
3334
3714
|
title: "Audit Log",
|
|
3335
3715
|
subtitle: loading ? "Loading\u2026" : `${total.toLocaleString()} event${total === 1 ? "" : "s"}`,
|
|
3336
3716
|
flush: true,
|
|
3337
|
-
actions: /* @__PURE__ */
|
|
3717
|
+
actions: /* @__PURE__ */ import_react10.default.createElement(import_react10.default.Fragment, null, /* @__PURE__ */ import_react10.default.createElement(
|
|
3338
3718
|
import_TextField6.default,
|
|
3339
3719
|
{
|
|
3340
3720
|
size: "small",
|
|
@@ -3347,12 +3727,12 @@ var init_AuditLog = __esm({
|
|
|
3347
3727
|
},
|
|
3348
3728
|
sx: { minWidth: 150 }
|
|
3349
3729
|
},
|
|
3350
|
-
/* @__PURE__ */
|
|
3351
|
-
/* @__PURE__ */
|
|
3352
|
-
/* @__PURE__ */
|
|
3353
|
-
/* @__PURE__ */
|
|
3354
|
-
/* @__PURE__ */
|
|
3355
|
-
), /* @__PURE__ */
|
|
3730
|
+
/* @__PURE__ */ import_react10.default.createElement(import_MenuItem5.default, { value: "" }, "All actions"),
|
|
3731
|
+
/* @__PURE__ */ import_react10.default.createElement(import_MenuItem5.default, { value: "created" }, "Created"),
|
|
3732
|
+
/* @__PURE__ */ import_react10.default.createElement(import_MenuItem5.default, { value: "updated" }, "Updated"),
|
|
3733
|
+
/* @__PURE__ */ import_react10.default.createElement(import_MenuItem5.default, { value: "deleted" }, "Deleted"),
|
|
3734
|
+
/* @__PURE__ */ import_react10.default.createElement(import_MenuItem5.default, { value: "blocked" }, "Blocked")
|
|
3735
|
+
), /* @__PURE__ */ import_react10.default.createElement(
|
|
3356
3736
|
import_TextField6.default,
|
|
3357
3737
|
{
|
|
3358
3738
|
size: "small",
|
|
@@ -3365,13 +3745,13 @@ var init_AuditLog = __esm({
|
|
|
3365
3745
|
},
|
|
3366
3746
|
sx: { minWidth: 150 }
|
|
3367
3747
|
},
|
|
3368
|
-
/* @__PURE__ */
|
|
3369
|
-
/* @__PURE__ */
|
|
3370
|
-
/* @__PURE__ */
|
|
3371
|
-
/* @__PURE__ */
|
|
3372
|
-
/* @__PURE__ */
|
|
3373
|
-
/* @__PURE__ */
|
|
3374
|
-
), /* @__PURE__ */
|
|
3748
|
+
/* @__PURE__ */ import_react10.default.createElement(import_MenuItem5.default, { value: "" }, "All tables"),
|
|
3749
|
+
/* @__PURE__ */ import_react10.default.createElement(import_MenuItem5.default, { value: "LiteLLM_VerificationToken" }, "Key"),
|
|
3750
|
+
/* @__PURE__ */ import_react10.default.createElement(import_MenuItem5.default, { value: "LiteLLM_TeamTable" }, "Team"),
|
|
3751
|
+
/* @__PURE__ */ import_react10.default.createElement(import_MenuItem5.default, { value: "LiteLLM_TeamMembership" }, "Team member"),
|
|
3752
|
+
/* @__PURE__ */ import_react10.default.createElement(import_MenuItem5.default, { value: "LiteLLM_ObjectPermissionTable" }, "Team access (KB / MCP)"),
|
|
3753
|
+
/* @__PURE__ */ import_react10.default.createElement(import_MenuItem5.default, { value: "LiteLLM_UserTable" }, "User")
|
|
3754
|
+
), /* @__PURE__ */ import_react10.default.createElement(
|
|
3375
3755
|
import_TextField6.default,
|
|
3376
3756
|
{
|
|
3377
3757
|
size: "small",
|
|
@@ -3385,9 +3765,9 @@ var init_AuditLog = __esm({
|
|
|
3385
3765
|
}
|
|
3386
3766
|
))
|
|
3387
3767
|
},
|
|
3388
|
-
error && /* @__PURE__ */
|
|
3389
|
-
/* @__PURE__ */
|
|
3390
|
-
/* @__PURE__ */
|
|
3768
|
+
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)),
|
|
3769
|
+
/* @__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)))),
|
|
3770
|
+
/* @__PURE__ */ import_react10.default.createElement(
|
|
3391
3771
|
import_TablePagination.default,
|
|
3392
3772
|
{
|
|
3393
3773
|
component: "div",
|
|
@@ -3456,27 +3836,29 @@ function initDateRange() {
|
|
|
3456
3836
|
else start.setDate(start.getDate() - 7);
|
|
3457
3837
|
return { start, end };
|
|
3458
3838
|
}
|
|
3459
|
-
var
|
|
3839
|
+
var import_react11, import_Box11, import_Snackbar2, import_Alert6, import_CircularProgress6, import_Typography11, import_Paper7, import_Tabs2, import_Tab2, import_Grid2, import_react_use3, import_core_plugin_api4, import_plugin_permission_react, PERIOD_LS_KEY2, LiteLLMPage;
|
|
3460
3840
|
var init_LiteLLMPage = __esm({
|
|
3461
3841
|
"src/components/LiteLLMPage.tsx"() {
|
|
3462
3842
|
"use strict";
|
|
3463
|
-
|
|
3464
|
-
|
|
3843
|
+
import_react11 = __toESM(require("react"));
|
|
3844
|
+
import_Box11 = __toESM(require("@mui/material/Box"));
|
|
3465
3845
|
import_Snackbar2 = __toESM(require("@mui/material/Snackbar"));
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3846
|
+
import_Alert6 = __toESM(require("@mui/material/Alert"));
|
|
3847
|
+
import_CircularProgress6 = __toESM(require("@mui/material/CircularProgress"));
|
|
3848
|
+
import_Typography11 = __toESM(require("@mui/material/Typography"));
|
|
3849
|
+
import_Paper7 = __toESM(require("@mui/material/Paper"));
|
|
3470
3850
|
import_Tabs2 = __toESM(require("@mui/material/Tabs"));
|
|
3471
3851
|
import_Tab2 = __toESM(require("@mui/material/Tab"));
|
|
3852
|
+
import_Grid2 = __toESM(require("@mui/material/Grid"));
|
|
3472
3853
|
import_react_use3 = require("react-use");
|
|
3473
|
-
|
|
3854
|
+
import_core_plugin_api4 = require("@backstage/core-plugin-api");
|
|
3474
3855
|
import_plugin_permission_react = require("@backstage/plugin-permission-react");
|
|
3475
3856
|
init_DashboardHeader();
|
|
3476
3857
|
init_KeysTable();
|
|
3477
3858
|
init_GenerateKeyDialog();
|
|
3478
3859
|
init_ManageTeamDialog();
|
|
3479
3860
|
init_UsageStats();
|
|
3861
|
+
init_LiteLLMBudgetWidget();
|
|
3480
3862
|
init_TeamUsage();
|
|
3481
3863
|
init_ModelsTable();
|
|
3482
3864
|
init_AuditLog();
|
|
@@ -3484,21 +3866,21 @@ var init_LiteLLMPage = __esm({
|
|
|
3484
3866
|
init_permissions();
|
|
3485
3867
|
PERIOD_LS_KEY2 = "litellm_usage_period";
|
|
3486
3868
|
LiteLLMPage = () => {
|
|
3487
|
-
const api = (0,
|
|
3488
|
-
const [dateRange, setDateRange] = (0,
|
|
3489
|
-
const [currentPreset, setCurrentPreset] = (0,
|
|
3869
|
+
const api = (0, import_core_plugin_api4.useApi)(liteLlmApiRef);
|
|
3870
|
+
const [dateRange, setDateRange] = (0, import_react11.useState)(initDateRange);
|
|
3871
|
+
const [currentPreset, setCurrentPreset] = (0, import_react11.useState)(() => {
|
|
3490
3872
|
try {
|
|
3491
3873
|
return localStorage.getItem(PERIOD_LS_KEY2) ?? "7d";
|
|
3492
3874
|
} catch {
|
|
3493
3875
|
return "7d";
|
|
3494
3876
|
}
|
|
3495
3877
|
});
|
|
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,
|
|
3878
|
+
const [activeTab, setActiveTab] = (0, import_react11.useState)("overview");
|
|
3879
|
+
const [snackbar, setSnackbar] = (0, import_react11.useState)(null);
|
|
3880
|
+
const [generateDialogOpen, setGenerateDialogOpen] = (0, import_react11.useState)(false);
|
|
3881
|
+
const [manageTeam, setManageTeam] = (0, import_react11.useState)(null);
|
|
3882
|
+
const [teamUsageCache, setTeamUsageCache] = (0, import_react11.useState)({});
|
|
3883
|
+
const [teamUsageLoading, setTeamUsageLoading] = (0, import_react11.useState)({});
|
|
3502
3884
|
const { value: userInfo, loading: userLoading, error: userError } = (0, import_react_use3.useAsync)(
|
|
3503
3885
|
() => api.getUserInfo(),
|
|
3504
3886
|
[api]
|
|
@@ -3542,7 +3924,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3542
3924
|
async () => objectPermsEnabled ? api.getMcpServers().catch(() => []) : [],
|
|
3543
3925
|
[api, objectPermsEnabled]
|
|
3544
3926
|
);
|
|
3545
|
-
const teams = (0,
|
|
3927
|
+
const teams = (0, import_react11.useMemo)(() => {
|
|
3546
3928
|
if (!allTeams?.length) return [];
|
|
3547
3929
|
if (!userInfo) return allTeams;
|
|
3548
3930
|
const userId = userInfo.user_id;
|
|
@@ -3559,7 +3941,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3559
3941
|
const endDate = dateRange.end.toISOString().split("T")[0];
|
|
3560
3942
|
return api.getUsage(startDate, endDate);
|
|
3561
3943
|
}, [api, dateRange]);
|
|
3562
|
-
const handleDateRangeChange = (0,
|
|
3944
|
+
const handleDateRangeChange = (0, import_react11.useCallback)((range, preset) => {
|
|
3563
3945
|
setDateRange(range);
|
|
3564
3946
|
if (preset) {
|
|
3565
3947
|
setCurrentPreset(preset);
|
|
@@ -3567,7 +3949,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3567
3949
|
setTeamUsageCache({});
|
|
3568
3950
|
setTeamUsageLoading({});
|
|
3569
3951
|
}, [setDateRange, setCurrentPreset]);
|
|
3570
|
-
const loadTeamUsage = (0,
|
|
3952
|
+
const loadTeamUsage = (0, import_react11.useCallback)(async (teamId) => {
|
|
3571
3953
|
if (teamUsageCache[teamId] !== void 0 || teamUsageLoading[teamId]) return;
|
|
3572
3954
|
setTeamUsageLoading((prev) => ({ ...prev, [teamId]: true }));
|
|
3573
3955
|
try {
|
|
@@ -3581,7 +3963,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3581
3963
|
setTeamUsageLoading((prev) => ({ ...prev, [teamId]: false }));
|
|
3582
3964
|
}
|
|
3583
3965
|
}, [api, dateRange, teamUsageCache, teamUsageLoading]);
|
|
3584
|
-
const allowedModels = (0,
|
|
3966
|
+
const allowedModels = (0, import_react11.useMemo)(() => {
|
|
3585
3967
|
if (!allModels?.length) return [];
|
|
3586
3968
|
const userModels = userInfo?.models;
|
|
3587
3969
|
const teamModels = teams?.flatMap((t) => t.models ?? []);
|
|
@@ -3594,7 +3976,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3594
3976
|
]);
|
|
3595
3977
|
return allModels.filter((m) => allowed.has(m.model_name));
|
|
3596
3978
|
}, [allModels, userInfo, teams]);
|
|
3597
|
-
const handleGenerateKey = (0,
|
|
3979
|
+
const handleGenerateKey = (0, import_react11.useCallback)(
|
|
3598
3980
|
async (request) => {
|
|
3599
3981
|
try {
|
|
3600
3982
|
const response = await api.generateKey(request);
|
|
@@ -3608,7 +3990,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3608
3990
|
},
|
|
3609
3991
|
[api, refreshKeys]
|
|
3610
3992
|
);
|
|
3611
|
-
const handleUpdateKey = (0,
|
|
3993
|
+
const handleUpdateKey = (0, import_react11.useCallback)(
|
|
3612
3994
|
async (keyId, request) => {
|
|
3613
3995
|
try {
|
|
3614
3996
|
await api.updateKey(keyId, request);
|
|
@@ -3621,7 +4003,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3621
4003
|
},
|
|
3622
4004
|
[api, refreshKeys]
|
|
3623
4005
|
);
|
|
3624
|
-
const handleBlockKey = (0,
|
|
4006
|
+
const handleBlockKey = (0, import_react11.useCallback)(
|
|
3625
4007
|
async (keyId) => {
|
|
3626
4008
|
try {
|
|
3627
4009
|
await api.blockKey(keyId);
|
|
@@ -3633,7 +4015,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3633
4015
|
},
|
|
3634
4016
|
[api, refreshKeys]
|
|
3635
4017
|
);
|
|
3636
|
-
const handleUnblockKey = (0,
|
|
4018
|
+
const handleUnblockKey = (0, import_react11.useCallback)(
|
|
3637
4019
|
async (keyId) => {
|
|
3638
4020
|
try {
|
|
3639
4021
|
await api.unblockKey(keyId);
|
|
@@ -3645,7 +4027,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3645
4027
|
},
|
|
3646
4028
|
[api, refreshKeys]
|
|
3647
4029
|
);
|
|
3648
|
-
const handleResetKeySpend = (0,
|
|
4030
|
+
const handleResetKeySpend = (0, import_react11.useCallback)(
|
|
3649
4031
|
async (keyId) => {
|
|
3650
4032
|
try {
|
|
3651
4033
|
await api.resetKeySpend(keyId);
|
|
@@ -3657,7 +4039,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3657
4039
|
},
|
|
3658
4040
|
[api, refreshKeys]
|
|
3659
4041
|
);
|
|
3660
|
-
const handleDeleteKey = (0,
|
|
4042
|
+
const handleDeleteKey = (0, import_react11.useCallback)(
|
|
3661
4043
|
async (keyId) => {
|
|
3662
4044
|
try {
|
|
3663
4045
|
await api.deleteKey(keyId);
|
|
@@ -3674,7 +4056,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3674
4056
|
},
|
|
3675
4057
|
[api, refreshKeys]
|
|
3676
4058
|
);
|
|
3677
|
-
const handlePruneExpiredKeys = (0,
|
|
4059
|
+
const handlePruneExpiredKeys = (0, import_react11.useCallback)(
|
|
3678
4060
|
async () => {
|
|
3679
4061
|
try {
|
|
3680
4062
|
const result = await api.pruneExpiredKeys();
|
|
@@ -3689,14 +4071,14 @@ var init_LiteLLMPage = __esm({
|
|
|
3689
4071
|
);
|
|
3690
4072
|
const isInitialLoading = userLoading && !userInfo;
|
|
3691
4073
|
if (isInitialLoading) {
|
|
3692
|
-
return /* @__PURE__ */
|
|
4074
|
+
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
4075
|
}
|
|
3694
4076
|
if (userError || !userInfo) {
|
|
3695
4077
|
const isProvisioningEnabled = userError?.body?.provisioning === true;
|
|
3696
4078
|
const hint = userError?.body?.hint;
|
|
3697
|
-
return /* @__PURE__ */
|
|
4079
|
+
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
4080
|
}
|
|
3699
|
-
const pageTabs = /* @__PURE__ */
|
|
4081
|
+
const pageTabs = /* @__PURE__ */ import_react11.default.createElement(
|
|
3700
4082
|
import_Tabs2.default,
|
|
3701
4083
|
{
|
|
3702
4084
|
value: activeTab,
|
|
@@ -3709,13 +4091,13 @@ var init_LiteLLMPage = __esm({
|
|
|
3709
4091
|
'& [class*="MuiTab-root"]': { minHeight: 44, textTransform: "none", fontSize: 14 }
|
|
3710
4092
|
}
|
|
3711
4093
|
},
|
|
3712
|
-
/* @__PURE__ */
|
|
3713
|
-
/* @__PURE__ */
|
|
3714
|
-
/* @__PURE__ */
|
|
3715
|
-
/* @__PURE__ */
|
|
3716
|
-
userInfo.can_view_audit && /* @__PURE__ */
|
|
4094
|
+
/* @__PURE__ */ import_react11.default.createElement(import_Tab2.default, { label: "Overview", value: "overview" }),
|
|
4095
|
+
/* @__PURE__ */ import_react11.default.createElement(import_Tab2.default, { label: "Keys", value: "keys" }),
|
|
4096
|
+
/* @__PURE__ */ import_react11.default.createElement(import_Tab2.default, { label: "Teams", value: "teams" }),
|
|
4097
|
+
/* @__PURE__ */ import_react11.default.createElement(import_Tab2.default, { label: "Models", value: "models" }),
|
|
4098
|
+
userInfo.can_view_audit && /* @__PURE__ */ import_react11.default.createElement(import_Tab2.default, { label: "Audit Log", value: "audit" })
|
|
3717
4099
|
);
|
|
3718
|
-
return /* @__PURE__ */
|
|
4100
|
+
return /* @__PURE__ */ import_react11.default.createElement(import_Box11.default, { sx: { p: 3, display: "flex", flexDirection: "column", gap: 2 } }, /* @__PURE__ */ import_react11.default.createElement(
|
|
3719
4101
|
DashboardHeader,
|
|
3720
4102
|
{
|
|
3721
4103
|
userInfo,
|
|
@@ -3725,7 +4107,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3725
4107
|
onGenerateKeyClick: () => setGenerateDialogOpen(true),
|
|
3726
4108
|
tabs: pageTabs
|
|
3727
4109
|
}
|
|
3728
|
-
), activeTab === "overview" && /* @__PURE__ */
|
|
4110
|
+
), 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
4111
|
UsageStats,
|
|
3730
4112
|
{
|
|
3731
4113
|
usage: usage ?? null,
|
|
@@ -3736,7 +4118,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3736
4118
|
loading: usageLoading,
|
|
3737
4119
|
userInfo
|
|
3738
4120
|
}
|
|
3739
|
-
), activeTab === "keys" && /* @__PURE__ */
|
|
4121
|
+
)), /* @__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
4122
|
KeysTable,
|
|
3741
4123
|
{
|
|
3742
4124
|
keys: keys ?? [],
|
|
@@ -3755,7 +4137,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3755
4137
|
for (const t of managedTeams ?? []) teamsById.set(t.team_id, t);
|
|
3756
4138
|
for (const t of teams ?? []) teamsById.set(t.team_id, t);
|
|
3757
4139
|
const visibleTeams = Array.from(teamsById.values());
|
|
3758
|
-
return /* @__PURE__ */
|
|
4140
|
+
return /* @__PURE__ */ import_react11.default.createElement(
|
|
3759
4141
|
TeamUsage,
|
|
3760
4142
|
{
|
|
3761
4143
|
teams: visibleTeams,
|
|
@@ -3771,14 +4153,14 @@ var init_LiteLLMPage = __esm({
|
|
|
3771
4153
|
onCreateTeam: () => setManageTeam({ mode: "create" })
|
|
3772
4154
|
}
|
|
3773
4155
|
);
|
|
3774
|
-
})(), activeTab === "models" && /* @__PURE__ */
|
|
4156
|
+
})(), activeTab === "models" && /* @__PURE__ */ import_react11.default.createElement(
|
|
3775
4157
|
ModelsTable,
|
|
3776
4158
|
{
|
|
3777
4159
|
allModels: allModels ?? [],
|
|
3778
4160
|
teams: teams ?? [],
|
|
3779
4161
|
loading: modelsLoading
|
|
3780
4162
|
}
|
|
3781
|
-
), activeTab === "audit" && userInfo.can_view_audit && /* @__PURE__ */
|
|
4163
|
+
), activeTab === "audit" && userInfo.can_view_audit && /* @__PURE__ */ import_react11.default.createElement(AuditLog, { api }), /* @__PURE__ */ import_react11.default.createElement(
|
|
3782
4164
|
GenerateKeyDialog,
|
|
3783
4165
|
{
|
|
3784
4166
|
open: generateDialogOpen,
|
|
@@ -3791,7 +4173,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3791
4173
|
onGenerateKey: handleGenerateKey,
|
|
3792
4174
|
onGetConfig: () => api.getConfig()
|
|
3793
4175
|
}
|
|
3794
|
-
), /* @__PURE__ */
|
|
4176
|
+
), /* @__PURE__ */ import_react11.default.createElement(
|
|
3795
4177
|
ManageTeamDialog,
|
|
3796
4178
|
{
|
|
3797
4179
|
open: !!manageTeam,
|
|
@@ -3853,7 +4235,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3853
4235
|
refreshTeams();
|
|
3854
4236
|
}
|
|
3855
4237
|
}
|
|
3856
|
-
), /* @__PURE__ */
|
|
4238
|
+
), /* @__PURE__ */ import_react11.default.createElement(
|
|
3857
4239
|
import_Snackbar2.default,
|
|
3858
4240
|
{
|
|
3859
4241
|
open: !!snackbar,
|
|
@@ -3861,7 +4243,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3861
4243
|
onClose: () => setSnackbar(null),
|
|
3862
4244
|
anchorOrigin: { vertical: "bottom", horizontal: "right" }
|
|
3863
4245
|
},
|
|
3864
|
-
snackbar ? /* @__PURE__ */
|
|
4246
|
+
snackbar ? /* @__PURE__ */ import_react11.default.createElement(import_Alert6.default, { severity: snackbar.severity, onClose: () => setSnackbar(null) }, snackbar.message) : void 0
|
|
3865
4247
|
));
|
|
3866
4248
|
};
|
|
3867
4249
|
}
|
|
@@ -3891,8 +4273,8 @@ __export(index_exports, {
|
|
|
3891
4273
|
module.exports = __toCommonJS(index_exports);
|
|
3892
4274
|
|
|
3893
4275
|
// src/plugin.tsx
|
|
3894
|
-
var
|
|
3895
|
-
var
|
|
4276
|
+
var import_react12 = __toESM(require("react"));
|
|
4277
|
+
var import_icons_material9 = require("@mui/icons-material");
|
|
3896
4278
|
var import_frontend_plugin_api = require("@backstage/frontend-plugin-api");
|
|
3897
4279
|
init_api();
|
|
3898
4280
|
var liteLlmApi = import_frontend_plugin_api.ApiBlueprint.make({
|
|
@@ -3906,10 +4288,10 @@ var liteLlmPage = import_frontend_plugin_api.PageBlueprint.make({
|
|
|
3906
4288
|
params: {
|
|
3907
4289
|
path: "/litellm",
|
|
3908
4290
|
title: "LiteLLM",
|
|
3909
|
-
icon: /* @__PURE__ */
|
|
4291
|
+
icon: /* @__PURE__ */ import_react12.default.createElement(import_icons_material9.TrendingUp, null),
|
|
3910
4292
|
loader: async () => {
|
|
3911
4293
|
const { LiteLLMPage: LiteLLMPage2 } = await Promise.resolve().then(() => (init_LiteLLMPage(), LiteLLMPage_exports));
|
|
3912
|
-
return /* @__PURE__ */
|
|
4294
|
+
return /* @__PURE__ */ import_react12.default.createElement(LiteLLMPage2, null);
|
|
3913
4295
|
}
|
|
3914
4296
|
}
|
|
3915
4297
|
});
|
|
@@ -3926,18 +4308,18 @@ init_UsageStats();
|
|
|
3926
4308
|
init_TeamUsage();
|
|
3927
4309
|
|
|
3928
4310
|
// src/components/LiteLLMHomeWidget.tsx
|
|
3929
|
-
var
|
|
3930
|
-
var
|
|
3931
|
-
var
|
|
3932
|
-
var
|
|
4311
|
+
var import_react13 = __toESM(require("react"));
|
|
4312
|
+
var import_Paper8 = __toESM(require("@mui/material/Paper"));
|
|
4313
|
+
var import_Box12 = __toESM(require("@mui/material/Box"));
|
|
4314
|
+
var import_Typography12 = __toESM(require("@mui/material/Typography"));
|
|
3933
4315
|
var import_FormControl2 = __toESM(require("@mui/material/FormControl"));
|
|
3934
4316
|
var import_Select2 = __toESM(require("@mui/material/Select"));
|
|
3935
4317
|
var import_MenuItem6 = __toESM(require("@mui/material/MenuItem"));
|
|
3936
|
-
var
|
|
3937
|
-
var
|
|
3938
|
-
var
|
|
4318
|
+
var import_Grid3 = __toESM(require("@mui/material/Grid"));
|
|
4319
|
+
var import_CircularProgress7 = __toESM(require("@mui/material/CircularProgress"));
|
|
4320
|
+
var import_Alert7 = __toESM(require("@mui/material/Alert"));
|
|
3939
4321
|
var import_recharts3 = require("recharts");
|
|
3940
|
-
var
|
|
4322
|
+
var import_core_plugin_api5 = require("@backstage/core-plugin-api");
|
|
3941
4323
|
init_api();
|
|
3942
4324
|
init_format();
|
|
3943
4325
|
function presetToDateRange(preset) {
|
|
@@ -3952,19 +4334,19 @@ function presetToDateRange(preset) {
|
|
|
3952
4334
|
}
|
|
3953
4335
|
return { start, end };
|
|
3954
4336
|
}
|
|
3955
|
-
var Kpi = ({ label, value }) => /* @__PURE__ */
|
|
4337
|
+
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
4338
|
var LiteLLMHomeWidget = ({
|
|
3957
4339
|
defaultPeriod = "7d",
|
|
3958
4340
|
title = "LiteLLM Usage"
|
|
3959
4341
|
}) => {
|
|
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,
|
|
4342
|
+
const api = (0, import_core_plugin_api5.useApi)(liteLlmApiRef);
|
|
4343
|
+
const [period, setPeriod] = (0, import_react13.useState)(defaultPeriod);
|
|
4344
|
+
const [loading, setLoading] = (0, import_react13.useState)(true);
|
|
4345
|
+
const [usageError, setUsageError] = (0, import_react13.useState)(null);
|
|
4346
|
+
const [keysError, setKeysError] = (0, import_react13.useState)(null);
|
|
4347
|
+
const [usage, setUsage] = (0, import_react13.useState)(null);
|
|
4348
|
+
const [keys, setKeys] = (0, import_react13.useState)([]);
|
|
4349
|
+
(0, import_react13.useEffect)(() => {
|
|
3968
4350
|
let cancelled = false;
|
|
3969
4351
|
setLoading(true);
|
|
3970
4352
|
setUsageError(null);
|
|
@@ -4001,17 +4383,17 @@ var LiteLLMHomeWidget = ({
|
|
|
4001
4383
|
spend: d.spend
|
|
4002
4384
|
}));
|
|
4003
4385
|
const hasSparkline = dailyData.length > 0;
|
|
4004
|
-
return /* @__PURE__ */
|
|
4386
|
+
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
4387
|
import_Select2.default,
|
|
4006
4388
|
{
|
|
4007
4389
|
value: period,
|
|
4008
4390
|
onChange: (e) => setPeriod(e.target.value),
|
|
4009
4391
|
displayEmpty: true
|
|
4010
4392
|
},
|
|
4011
|
-
/* @__PURE__ */
|
|
4012
|
-
/* @__PURE__ */
|
|
4013
|
-
/* @__PURE__ */
|
|
4014
|
-
))), loading && /* @__PURE__ */
|
|
4393
|
+
/* @__PURE__ */ import_react13.default.createElement(import_MenuItem6.default, { value: "today" }, "Today"),
|
|
4394
|
+
/* @__PURE__ */ import_react13.default.createElement(import_MenuItem6.default, { value: "7d" }, "7d"),
|
|
4395
|
+
/* @__PURE__ */ import_react13.default.createElement(import_MenuItem6.default, { value: "30d" }, "30d")
|
|
4396
|
+
))), 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
4397
|
import_recharts3.Area,
|
|
4016
4398
|
{
|
|
4017
4399
|
type: "monotone",
|
|
@@ -4025,281 +4407,8 @@ var LiteLLMHomeWidget = ({
|
|
|
4025
4407
|
))))));
|
|
4026
4408
|
};
|
|
4027
4409
|
|
|
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
4410
|
// src/index.ts
|
|
4411
|
+
init_LiteLLMBudgetWidget();
|
|
4303
4412
|
init_GenerateKeyDialog();
|
|
4304
4413
|
init_ManageTeamDialog();
|
|
4305
4414
|
init_api();
|