@acarmisc/backstage-plugin-litellm 0.20.0 → 0.21.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/budget.d.ts +10 -0
- package/dist/components/LiteLLMBudgetWidget.d.ts +22 -0
- package/dist/index.cjs.js +601 -481
- package/dist/index.cjs.js.map +4 -4
- package/dist/index.esm.js +580 -460
- package/dist/index.esm.js.map +4 -4
- package/package.json +1 -1
package/dist/index.esm.js
CHANGED
|
@@ -2749,11 +2749,402 @@ var init_UsageStats = __esm({
|
|
|
2749
2749
|
}
|
|
2750
2750
|
});
|
|
2751
2751
|
|
|
2752
|
-
// src/
|
|
2753
|
-
|
|
2752
|
+
// src/budget.ts
|
|
2753
|
+
function limitPct(spend, budget) {
|
|
2754
|
+
if (budget <= 0) return 0;
|
|
2755
|
+
return spend / budget * 100;
|
|
2756
|
+
}
|
|
2757
|
+
function buildBudgetSummary(user, teams, keys, maxKeys = 3) {
|
|
2758
|
+
const budgeted = keys.filter((k) => (k.max_budget ?? 0) > 0).map((k) => {
|
|
2759
|
+
const spend = k.spend ?? 0;
|
|
2760
|
+
return {
|
|
2761
|
+
kind: "key",
|
|
2762
|
+
label: k.key_alias || k.key || "Untitled key",
|
|
2763
|
+
sublabel: k.key,
|
|
2764
|
+
budget: k.max_budget,
|
|
2765
|
+
spend,
|
|
2766
|
+
budgetDuration: k.budget_duration,
|
|
2767
|
+
pct: limitPct(spend, k.max_budget)
|
|
2768
|
+
};
|
|
2769
|
+
}).sort((a, b) => b.pct - a.pct);
|
|
2770
|
+
const userBudget = user && (user.max_budget ?? user.hard_limit ?? 0) > 0 ? (() => {
|
|
2771
|
+
const budget = user.max_budget ?? user.hard_limit ?? 0;
|
|
2772
|
+
const spend = user.current_spend ?? user.spend ?? 0;
|
|
2773
|
+
return {
|
|
2774
|
+
kind: "user",
|
|
2775
|
+
label: "Personal budget",
|
|
2776
|
+
sublabel: user.user_email || user.user_id,
|
|
2777
|
+
budget,
|
|
2778
|
+
spend,
|
|
2779
|
+
budgetDuration: user.budget_duration,
|
|
2780
|
+
softLimit: user.soft_limit,
|
|
2781
|
+
pct: limitPct(spend, budget)
|
|
2782
|
+
};
|
|
2783
|
+
})() : void 0;
|
|
2784
|
+
const teamLimits = teams.filter((t) => (t.max_budget ?? 0) > 0).map((t) => {
|
|
2785
|
+
const spend = t.spend ?? 0;
|
|
2786
|
+
return {
|
|
2787
|
+
kind: "team",
|
|
2788
|
+
label: t.team_alias || "Untitled team",
|
|
2789
|
+
sublabel: t.team_id,
|
|
2790
|
+
budget: t.max_budget,
|
|
2791
|
+
spend,
|
|
2792
|
+
budgetDuration: t.budget_duration,
|
|
2793
|
+
pct: limitPct(spend, t.max_budget)
|
|
2794
|
+
};
|
|
2795
|
+
}).sort((a, b) => b.pct - a.pct);
|
|
2796
|
+
return {
|
|
2797
|
+
keys: budgeted.slice(0, maxKeys),
|
|
2798
|
+
hiddenBudgetedKeys: Math.max(0, budgeted.length - maxKeys),
|
|
2799
|
+
totalKeyCount: keys.length,
|
|
2800
|
+
user: userBudget,
|
|
2801
|
+
teams: teamLimits
|
|
2802
|
+
};
|
|
2803
|
+
}
|
|
2804
|
+
function budgetHeadline(summary) {
|
|
2805
|
+
const all = [
|
|
2806
|
+
...summary.keys,
|
|
2807
|
+
...summary.user ? [summary.user] : [],
|
|
2808
|
+
...summary.teams
|
|
2809
|
+
];
|
|
2810
|
+
const count = summary.keys.length + summary.hiddenBudgetedKeys + (summary.user ? 1 : 0) + summary.teams.length;
|
|
2811
|
+
const closest = all.length ? all.reduce((a, b) => b.pct > a.pct ? b : a) : null;
|
|
2812
|
+
return { count, closest };
|
|
2813
|
+
}
|
|
2814
|
+
function fmtBudgetDuration(duration) {
|
|
2815
|
+
if (!duration) return null;
|
|
2816
|
+
const m = /^(\d+)(s|m|h|d)$/.exec(duration);
|
|
2817
|
+
if (!m) return duration;
|
|
2818
|
+
const n = Number(m[1]);
|
|
2819
|
+
const unit = m[2];
|
|
2820
|
+
if (unit === "d") return n === 1 ? "daily" : `every ${n} days`;
|
|
2821
|
+
if (unit === "h") return n === 1 ? "hourly" : `every ${n} hours`;
|
|
2822
|
+
if (unit === "m") return n === 1 ? "every minute" : `every ${n} minutes`;
|
|
2823
|
+
return n === 1 ? "every second" : `every ${n} seconds`;
|
|
2824
|
+
}
|
|
2825
|
+
function budgetTone2(pct) {
|
|
2826
|
+
if (pct >= 100) return "danger";
|
|
2827
|
+
if (pct >= 80) return "warning";
|
|
2828
|
+
return "accent";
|
|
2829
|
+
}
|
|
2830
|
+
var init_budget = __esm({
|
|
2831
|
+
"src/budget.ts"() {
|
|
2832
|
+
"use strict";
|
|
2833
|
+
}
|
|
2834
|
+
});
|
|
2835
|
+
|
|
2836
|
+
// src/components/LiteLLMBudgetWidget.tsx
|
|
2837
|
+
import React7, { useEffect as useEffect3, useMemo as useMemo6, useState as useState5 } from "react";
|
|
2754
2838
|
import Paper5 from "@mui/material/Paper";
|
|
2755
2839
|
import Box7 from "@mui/material/Box";
|
|
2756
2840
|
import Typography7 from "@mui/material/Typography";
|
|
2841
|
+
import CircularProgress4 from "@mui/material/CircularProgress";
|
|
2842
|
+
import Alert3 from "@mui/material/Alert";
|
|
2843
|
+
import Collapse from "@mui/material/Collapse";
|
|
2844
|
+
import IconButton4 from "@mui/material/IconButton";
|
|
2845
|
+
import { ExpandMore as ExpandMore2 } from "@mui/icons-material";
|
|
2846
|
+
import { alpha as alpha4 } from "@mui/material/styles";
|
|
2847
|
+
import { useApi as useApi2 } from "@backstage/core-plugin-api";
|
|
2848
|
+
function levelToneColor(tone, theme) {
|
|
2849
|
+
switch (tone) {
|
|
2850
|
+
case "success":
|
|
2851
|
+
return theme.palette.success.main;
|
|
2852
|
+
case "warning":
|
|
2853
|
+
return theme.palette.warning.main;
|
|
2854
|
+
case "danger":
|
|
2855
|
+
return theme.palette.error.main;
|
|
2856
|
+
case "info":
|
|
2857
|
+
return theme.palette.info.main;
|
|
2858
|
+
default:
|
|
2859
|
+
return theme.palette.primary.main;
|
|
2860
|
+
}
|
|
2861
|
+
}
|
|
2862
|
+
var LevelFrame, EmptyLimitCard, LimitCard, CLOSEST_LEVEL_LABEL, TaggedLimit, FullBody, CompactBody, LiteLLMBudgetWidget;
|
|
2863
|
+
var init_LiteLLMBudgetWidget = __esm({
|
|
2864
|
+
"src/components/LiteLLMBudgetWidget.tsx"() {
|
|
2865
|
+
"use strict";
|
|
2866
|
+
init_api();
|
|
2867
|
+
init_format();
|
|
2868
|
+
init_ui();
|
|
2869
|
+
init_budget();
|
|
2870
|
+
LevelFrame = ({
|
|
2871
|
+
rank,
|
|
2872
|
+
name,
|
|
2873
|
+
tagline,
|
|
2874
|
+
note,
|
|
2875
|
+
tone,
|
|
2876
|
+
isLast,
|
|
2877
|
+
children
|
|
2878
|
+
}) => /* @__PURE__ */ React7.createElement(Box7, { sx: { display: "flex", gap: 1.5 } }, /* @__PURE__ */ React7.createElement(Box7, { sx: { display: "flex", flexDirection: "column", alignItems: "center", width: 26, flexShrink: 0 } }, /* @__PURE__ */ React7.createElement(
|
|
2879
|
+
Box7,
|
|
2880
|
+
{
|
|
2881
|
+
sx: (theme) => ({
|
|
2882
|
+
width: 26,
|
|
2883
|
+
height: 26,
|
|
2884
|
+
borderRadius: "50%",
|
|
2885
|
+
display: "flex",
|
|
2886
|
+
alignItems: "center",
|
|
2887
|
+
justifyContent: "center",
|
|
2888
|
+
bgcolor: alpha4(levelToneColor(tone, theme), 0.14),
|
|
2889
|
+
color: levelToneColor(tone, theme),
|
|
2890
|
+
fontSize: 12,
|
|
2891
|
+
fontWeight: 700,
|
|
2892
|
+
flexShrink: 0
|
|
2893
|
+
})
|
|
2894
|
+
},
|
|
2895
|
+
rank
|
|
2896
|
+
), /* @__PURE__ */ React7.createElement(
|
|
2897
|
+
Box7,
|
|
2898
|
+
{
|
|
2899
|
+
sx: (theme) => ({
|
|
2900
|
+
flex: 1,
|
|
2901
|
+
width: 2,
|
|
2902
|
+
minHeight: 14,
|
|
2903
|
+
bgcolor: isLast ? "transparent" : alpha4(theme.palette.text.primary, 0.12)
|
|
2904
|
+
})
|
|
2905
|
+
}
|
|
2906
|
+
)), /* @__PURE__ */ React7.createElement(Box7, { sx: { flex: 1, minWidth: 0, pb: isLast ? 0 : 2 } }, /* @__PURE__ */ React7.createElement(Box7, { sx: { display: "flex", alignItems: "baseline", gap: 1, flexWrap: "wrap", mb: 0.75 } }, /* @__PURE__ */ React7.createElement(
|
|
2907
|
+
Typography7,
|
|
2908
|
+
{
|
|
2909
|
+
sx: (theme) => ({
|
|
2910
|
+
fontSize: 12.5,
|
|
2911
|
+
fontWeight: 700,
|
|
2912
|
+
letterSpacing: "0.08em",
|
|
2913
|
+
textTransform: "uppercase",
|
|
2914
|
+
color: levelToneColor(tone, theme)
|
|
2915
|
+
})
|
|
2916
|
+
},
|
|
2917
|
+
name
|
|
2918
|
+
), /* @__PURE__ */ React7.createElement(Typography7, { variant: "caption", color: "text.secondary", sx: { fontSize: 11.5 } }, tagline), note && /* @__PURE__ */ React7.createElement(StatusPill, { label: note, tone: "neutral", dot: false, sx: { ml: "auto" } })), /* @__PURE__ */ React7.createElement(Box7, { sx: { display: "flex", flexDirection: "column", gap: 1 } }, children)));
|
|
2919
|
+
EmptyLimitCard = ({ children }) => /* @__PURE__ */ React7.createElement(
|
|
2920
|
+
Paper5,
|
|
2921
|
+
{
|
|
2922
|
+
variant: "outlined",
|
|
2923
|
+
sx: (theme) => ({
|
|
2924
|
+
px: 1.5,
|
|
2925
|
+
py: 1.25,
|
|
2926
|
+
borderRadius: 1.5,
|
|
2927
|
+
bgcolor: alpha4(theme.palette.text.primary, theme.palette.mode === "dark" ? 0.03 : 0.015)
|
|
2928
|
+
})
|
|
2929
|
+
},
|
|
2930
|
+
/* @__PURE__ */ React7.createElement(Typography7, { variant: "body2", color: "text.secondary", sx: { fontSize: 13 } }, children)
|
|
2931
|
+
);
|
|
2932
|
+
LimitCard = ({ limit }) => {
|
|
2933
|
+
const tone = budgetTone2(limit.pct);
|
|
2934
|
+
const pct = Math.round(limit.pct);
|
|
2935
|
+
const closeTo = limit.softLimit !== void 0 && limit.spend >= limit.softLimit;
|
|
2936
|
+
return /* @__PURE__ */ React7.createElement(Paper5, { variant: "outlined", sx: { px: 1.5, py: 1.25, borderRadius: 1.5 } }, /* @__PURE__ */ React7.createElement(Box7, { sx: { display: "flex", alignItems: "baseline", justifyContent: "space-between", gap: 1.5 } }, /* @__PURE__ */ React7.createElement(Box7, { sx: { minWidth: 0, flex: 1 } }, /* @__PURE__ */ React7.createElement(
|
|
2937
|
+
Typography7,
|
|
2938
|
+
{
|
|
2939
|
+
variant: "body2",
|
|
2940
|
+
sx: { fontWeight: 600, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" },
|
|
2941
|
+
title: limit.sublabel ? `${limit.label} \xB7 ${limit.sublabel}` : limit.label
|
|
2942
|
+
},
|
|
2943
|
+
limit.label
|
|
2944
|
+
), limit.sublabel && /* @__PURE__ */ React7.createElement(
|
|
2945
|
+
Typography7,
|
|
2946
|
+
{
|
|
2947
|
+
variant: "caption",
|
|
2948
|
+
color: "text.secondary",
|
|
2949
|
+
sx: {
|
|
2950
|
+
fontSize: 10.5,
|
|
2951
|
+
fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
|
|
2952
|
+
display: "block",
|
|
2953
|
+
overflow: "hidden",
|
|
2954
|
+
textOverflow: "ellipsis",
|
|
2955
|
+
whiteSpace: "nowrap"
|
|
2956
|
+
}
|
|
2957
|
+
},
|
|
2958
|
+
limit.sublabel
|
|
2959
|
+
)), /* @__PURE__ */ React7.createElement(Typography7, { variant: "body2", sx: { fontWeight: 600, fontVariantNumeric: "tabular-nums", whiteSpace: "nowrap" } }, fmtUsd(limit.spend), /* @__PURE__ */ React7.createElement(Box7, { component: "span", color: "text.secondary", sx: { fontWeight: 400 } }, " / ", fmtUsd(limit.budget)))), /* @__PURE__ */ React7.createElement(Box7, { sx: { mt: 1 } }, /* @__PURE__ */ React7.createElement(Meter, { value: limit.pct, tone, height: 6 })), /* @__PURE__ */ React7.createElement(
|
|
2960
|
+
Box7,
|
|
2961
|
+
{
|
|
2962
|
+
sx: {
|
|
2963
|
+
display: "flex",
|
|
2964
|
+
justifyContent: "space-between",
|
|
2965
|
+
alignItems: "center",
|
|
2966
|
+
gap: 1,
|
|
2967
|
+
mt: 0.75
|
|
2968
|
+
}
|
|
2969
|
+
},
|
|
2970
|
+
/* @__PURE__ */ React7.createElement(Typography7, { variant: "caption", color: "text.secondary", sx: { fontVariantNumeric: "tabular-nums" } }, pct, "% of budget", closeTo && ` \xB7 soft-limit warning`),
|
|
2971
|
+
/* @__PURE__ */ React7.createElement(Typography7, { variant: "caption", color: "text.secondary" }, fmtBudgetDuration(limit.budgetDuration) ? `resets ${fmtBudgetDuration(limit.budgetDuration)}` : "never resets")
|
|
2972
|
+
));
|
|
2973
|
+
};
|
|
2974
|
+
CLOSEST_LEVEL_LABEL = {
|
|
2975
|
+
key: "key",
|
|
2976
|
+
user: "personal",
|
|
2977
|
+
team: "team"
|
|
2978
|
+
};
|
|
2979
|
+
TaggedLimit = ({ limit }) => {
|
|
2980
|
+
const tone = budgetTone2(limit.pct);
|
|
2981
|
+
return /* @__PURE__ */ React7.createElement(Box7, null, /* @__PURE__ */ React7.createElement(
|
|
2982
|
+
Typography7,
|
|
2983
|
+
{
|
|
2984
|
+
sx: (theme) => ({
|
|
2985
|
+
fontSize: 11,
|
|
2986
|
+
fontWeight: 700,
|
|
2987
|
+
letterSpacing: "0.08em",
|
|
2988
|
+
textTransform: "uppercase",
|
|
2989
|
+
color: tone === "accent" ? theme.palette.text.secondary : levelToneColor(tone, theme),
|
|
2990
|
+
mb: 0.5
|
|
2991
|
+
})
|
|
2992
|
+
},
|
|
2993
|
+
limit.kind
|
|
2994
|
+
), /* @__PURE__ */ React7.createElement(LimitCard, { limit }));
|
|
2995
|
+
};
|
|
2996
|
+
FullBody = ({ summary }) => {
|
|
2997
|
+
const hasBudgetedKeys = summary.keys.length + summary.hiddenBudgetedKeys > 0;
|
|
2998
|
+
return /* @__PURE__ */ React7.createElement(React7.Fragment, null, /* @__PURE__ */ React7.createElement(
|
|
2999
|
+
LevelFrame,
|
|
3000
|
+
{
|
|
3001
|
+
rank: 1,
|
|
3002
|
+
name: "Key",
|
|
3003
|
+
tagline: "per-key cap \xB7 highest priority",
|
|
3004
|
+
note: hasBudgetedKeys ? void 0 : "no key budgets set"
|
|
3005
|
+
},
|
|
3006
|
+
!hasBudgetedKeys && /* @__PURE__ */ React7.createElement(EmptyLimitCard, null, "No key has a budget, so no key-level cap applies."),
|
|
3007
|
+
summary.keys.map((k) => /* @__PURE__ */ React7.createElement(LimitCard, { key: k.sublabel ?? k.label, limit: k })),
|
|
3008
|
+
summary.hiddenBudgetedKeys > 0 && /* @__PURE__ */ React7.createElement(Typography7, { variant: "caption", color: "text.secondary" }, "+", summary.hiddenBudgetedKeys, " more budgeted key", summary.hiddenBudgetedKeys > 1 ? "s" : "", " further from the cap")
|
|
3009
|
+
), /* @__PURE__ */ React7.createElement(
|
|
3010
|
+
LevelFrame,
|
|
3011
|
+
{
|
|
3012
|
+
rank: 2,
|
|
3013
|
+
name: "User",
|
|
3014
|
+
tagline: "cap on everything you spend with your own keys",
|
|
3015
|
+
note: "skipped for team-bound keys"
|
|
3016
|
+
},
|
|
3017
|
+
summary.user ? /* @__PURE__ */ React7.createElement(LimitCard, { limit: summary.user }) : /* @__PURE__ */ React7.createElement(EmptyLimitCard, null, "No personal budget is set on your account.")
|
|
3018
|
+
), /* @__PURE__ */ React7.createElement(LevelFrame, { rank: 3, name: "Team", tagline: "shared cap for all keys in a team (if any)" }, summary.teams.length > 0 ? summary.teams.map((t) => /* @__PURE__ */ React7.createElement(LimitCard, { key: t.sublabel ?? t.label, limit: t })) : /* @__PURE__ */ React7.createElement(EmptyLimitCard, null, "No team you belong to has a budget.")), /* @__PURE__ */ React7.createElement(LevelFrame, { rank: 4, name: "Global", tagline: "proxy-wide cap set by your admin", isLast: true }, /* @__PURE__ */ React7.createElement(EmptyLimitCard, null, "Configured by your admin in the proxy config \u2014 it isn't visible from this page. When set, it limits spend across every request on the proxy.")), /* @__PURE__ */ React7.createElement(
|
|
3019
|
+
Box7,
|
|
3020
|
+
{
|
|
3021
|
+
sx: (theme) => ({
|
|
3022
|
+
mt: 2,
|
|
3023
|
+
px: 1.5,
|
|
3024
|
+
py: 1.25,
|
|
3025
|
+
borderRadius: 1.5,
|
|
3026
|
+
border: "1px dashed",
|
|
3027
|
+
borderColor: alpha4(theme.palette.text.primary, 0.18)
|
|
3028
|
+
})
|
|
3029
|
+
},
|
|
3030
|
+
/* @__PURE__ */ React7.createElement(Typography7, { variant: "caption", color: "text.secondary", display: "block" }, "The first cap a request runs into is the one that stops it. A limit with a reset window (e.g. daily or every 30 days) clears its spend to $0 when the window closes; without a window it never resets.")
|
|
3031
|
+
));
|
|
3032
|
+
};
|
|
3033
|
+
CompactBody = ({ summary }) => {
|
|
3034
|
+
const { count } = budgetHeadline(summary);
|
|
3035
|
+
if (count === 0) {
|
|
3036
|
+
return /* @__PURE__ */ React7.createElement(Typography7, { variant: "body2", color: "text.secondary", sx: { fontSize: 13 } }, "No budget limits apply to you right now.");
|
|
3037
|
+
}
|
|
3038
|
+
return /* @__PURE__ */ React7.createElement(Box7, { sx: { display: "flex", flexDirection: "column", gap: 1.25 } }, summary.keys.map((k) => /* @__PURE__ */ React7.createElement(TaggedLimit, { key: k.sublabel ?? k.label, limit: k })), summary.hiddenBudgetedKeys > 0 && /* @__PURE__ */ React7.createElement(Typography7, { variant: "caption", color: "text.secondary" }, "+", summary.hiddenBudgetedKeys, " more budgeted key", summary.hiddenBudgetedKeys > 1 ? "s" : "", " further from the cap"), summary.user && /* @__PURE__ */ React7.createElement(TaggedLimit, { limit: summary.user }), summary.teams.map((t) => /* @__PURE__ */ React7.createElement(TaggedLimit, { key: t.sublabel ?? t.label, limit: t })));
|
|
3039
|
+
};
|
|
3040
|
+
LiteLLMBudgetWidget = ({
|
|
3041
|
+
title = "Budget Policy",
|
|
3042
|
+
maxKeys = 3,
|
|
3043
|
+
compact = false,
|
|
3044
|
+
collapsible = false,
|
|
3045
|
+
defaultExpanded = true,
|
|
3046
|
+
action
|
|
3047
|
+
}) => {
|
|
3048
|
+
const api = useApi2(liteLlmApiRef);
|
|
3049
|
+
const [loading, setLoading] = useState5(true);
|
|
3050
|
+
const [error, setError] = useState5(null);
|
|
3051
|
+
const [user, setUser] = useState5(null);
|
|
3052
|
+
const [teams, setTeams] = useState5([]);
|
|
3053
|
+
const [keys, setKeys] = useState5([]);
|
|
3054
|
+
const [expanded, setExpanded] = useState5(defaultExpanded);
|
|
3055
|
+
useEffect3(() => {
|
|
3056
|
+
let cancelled = false;
|
|
3057
|
+
setLoading(true);
|
|
3058
|
+
setError(null);
|
|
3059
|
+
Promise.allSettled([api.getUserInfo(), api.getTeams(), api.listKeys()]).then(
|
|
3060
|
+
([userResult, teamsResult, keysResult]) => {
|
|
3061
|
+
if (cancelled) return;
|
|
3062
|
+
if (userResult.status === "fulfilled") {
|
|
3063
|
+
setUser(userResult.value);
|
|
3064
|
+
} else {
|
|
3065
|
+
setUser(null);
|
|
3066
|
+
setError(
|
|
3067
|
+
userResult.reason?.message ?? "Failed to load your LiteLLM profile"
|
|
3068
|
+
);
|
|
3069
|
+
}
|
|
3070
|
+
setTeams(teamsResult.status === "fulfilled" ? teamsResult.value : []);
|
|
3071
|
+
setKeys(keysResult.status === "fulfilled" ? keysResult.value : []);
|
|
3072
|
+
setLoading(false);
|
|
3073
|
+
}
|
|
3074
|
+
);
|
|
3075
|
+
return () => {
|
|
3076
|
+
cancelled = true;
|
|
3077
|
+
};
|
|
3078
|
+
}, [api]);
|
|
3079
|
+
const summary = useMemo6(
|
|
3080
|
+
() => buildBudgetSummary(user, teams, keys, maxKeys),
|
|
3081
|
+
[user, teams, keys, maxKeys]
|
|
3082
|
+
);
|
|
3083
|
+
const headline = useMemo6(() => budgetHeadline(summary), [summary]);
|
|
3084
|
+
const limitsWord = `${headline.count} limit${headline.count === 1 ? "" : "s"}`;
|
|
3085
|
+
const closestWord = headline.closest === null ? "" : ` \xB7 closest: ${CLOSEST_LEVEL_LABEL[headline.closest.kind]} ${Math.round(
|
|
3086
|
+
headline.closest.pct
|
|
3087
|
+
)}%`;
|
|
3088
|
+
const summaryText = headline.count === 0 ? "no limits apply" : `${limitsWord}${closestWord}`;
|
|
3089
|
+
const body = /* @__PURE__ */ React7.createElement(React7.Fragment, null, loading && /* @__PURE__ */ React7.createElement(Box7, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: 140 }, /* @__PURE__ */ React7.createElement(CircularProgress4, { size: 32 })), !loading && error && /* @__PURE__ */ React7.createElement(Alert3, { severity: "error", sx: { mt: 0.5 } }, error), !loading && !error && (compact ? /* @__PURE__ */ React7.createElement(CompactBody, { summary }) : /* @__PURE__ */ React7.createElement(FullBody, { summary })));
|
|
3090
|
+
return /* @__PURE__ */ React7.createElement(Paper5, { sx: { p: 2 } }, collapsible ? /* @__PURE__ */ React7.createElement(
|
|
3091
|
+
Box7,
|
|
3092
|
+
{
|
|
3093
|
+
onClick: () => setExpanded((e) => !e),
|
|
3094
|
+
sx: {
|
|
3095
|
+
display: "flex",
|
|
3096
|
+
alignItems: "center",
|
|
3097
|
+
gap: 1,
|
|
3098
|
+
cursor: "pointer",
|
|
3099
|
+
userSelect: "none",
|
|
3100
|
+
mb: expanded ? 1.5 : 0
|
|
3101
|
+
}
|
|
3102
|
+
},
|
|
3103
|
+
/* @__PURE__ */ React7.createElement(Box7, { sx: { minWidth: 0, flex: 1 } }, /* @__PURE__ */ React7.createElement(Typography7, { variant: "h6", lineHeight: 1.2 }, title), !loading && !error && /* @__PURE__ */ React7.createElement(Box7, { sx: { mt: 0.5 } }, /* @__PURE__ */ React7.createElement(
|
|
3104
|
+
StatusPill,
|
|
3105
|
+
{
|
|
3106
|
+
label: summaryText,
|
|
3107
|
+
tone: budgetTone2(headline.closest?.pct ?? 0)
|
|
3108
|
+
}
|
|
3109
|
+
))),
|
|
3110
|
+
/* @__PURE__ */ React7.createElement(
|
|
3111
|
+
IconButton4,
|
|
3112
|
+
{
|
|
3113
|
+
size: "small",
|
|
3114
|
+
"aria-label": expanded ? "Collapse budget policy" : "Expand budget policy",
|
|
3115
|
+
"aria-expanded": expanded,
|
|
3116
|
+
onClick: (e) => {
|
|
3117
|
+
e.stopPropagation();
|
|
3118
|
+
setExpanded((x) => !x);
|
|
3119
|
+
},
|
|
3120
|
+
sx: (theme) => ({
|
|
3121
|
+
color: theme.palette.text.secondary,
|
|
3122
|
+
transform: expanded ? "rotate(180deg)" : "none",
|
|
3123
|
+
transition: theme.transitions.create("transform")
|
|
3124
|
+
})
|
|
3125
|
+
},
|
|
3126
|
+
/* @__PURE__ */ React7.createElement(ExpandMore2, null)
|
|
3127
|
+
)
|
|
3128
|
+
) : /* @__PURE__ */ React7.createElement(Box7, { sx: { mb: 1.5 } }, /* @__PURE__ */ React7.createElement(Typography7, { variant: "h6", lineHeight: 1.2 }, title), /* @__PURE__ */ React7.createElement(Typography7, { variant: "caption", color: "text.secondary" }, "How LiteLLM caps spend \u2014 and where you stand right now")), collapsible ? /* @__PURE__ */ React7.createElement(Collapse, { in: expanded, unmountOnExit: true }, body) : body, action && /* @__PURE__ */ React7.createElement(
|
|
3129
|
+
Box7,
|
|
3130
|
+
{
|
|
3131
|
+
sx: (theme) => ({
|
|
3132
|
+
mt: 2,
|
|
3133
|
+
pt: 2,
|
|
3134
|
+
borderTop: `1px solid ${theme.palette.divider}`
|
|
3135
|
+
})
|
|
3136
|
+
},
|
|
3137
|
+
action
|
|
3138
|
+
));
|
|
3139
|
+
};
|
|
3140
|
+
}
|
|
3141
|
+
});
|
|
3142
|
+
|
|
3143
|
+
// src/components/TeamUsage.tsx
|
|
3144
|
+
import React8, { useState as useState6 } from "react";
|
|
3145
|
+
import Paper6 from "@mui/material/Paper";
|
|
3146
|
+
import Box8 from "@mui/material/Box";
|
|
3147
|
+
import Typography8 from "@mui/material/Typography";
|
|
2757
3148
|
import Skeleton4 from "@mui/material/Skeleton";
|
|
2758
3149
|
import Divider3 from "@mui/material/Divider";
|
|
2759
3150
|
import Table4 from "@mui/material/Table";
|
|
@@ -2762,12 +3153,12 @@ import TableCell4 from "@mui/material/TableCell";
|
|
|
2762
3153
|
import TableHead4 from "@mui/material/TableHead";
|
|
2763
3154
|
import TableRow4 from "@mui/material/TableRow";
|
|
2764
3155
|
import TableContainer4 from "@mui/material/TableContainer";
|
|
2765
|
-
import
|
|
2766
|
-
import
|
|
3156
|
+
import Collapse2 from "@mui/material/Collapse";
|
|
3157
|
+
import IconButton5 from "@mui/material/IconButton";
|
|
2767
3158
|
import Button5 from "@mui/material/Button";
|
|
2768
|
-
import
|
|
2769
|
-
import { alpha as
|
|
2770
|
-
import { ExpandMore as
|
|
3159
|
+
import CircularProgress5 from "@mui/material/CircularProgress";
|
|
3160
|
+
import { alpha as alpha5 } from "@mui/material/styles";
|
|
3161
|
+
import { ExpandMore as ExpandMore3, Group, Add as Add3 } from "@mui/icons-material";
|
|
2771
3162
|
import {
|
|
2772
3163
|
AreaChart as AreaChart2,
|
|
2773
3164
|
Area as Area2,
|
|
@@ -2777,7 +3168,7 @@ import {
|
|
|
2777
3168
|
Tooltip as Tooltip2,
|
|
2778
3169
|
ResponsiveContainer as ResponsiveContainer2
|
|
2779
3170
|
} from "recharts";
|
|
2780
|
-
function
|
|
3171
|
+
function budgetTone3(isOver, isNear) {
|
|
2781
3172
|
if (isOver) return "danger";
|
|
2782
3173
|
if (isNear) return "warning";
|
|
2783
3174
|
return "accent";
|
|
@@ -2789,7 +3180,7 @@ var init_TeamUsage = __esm({
|
|
|
2789
3180
|
init_ui();
|
|
2790
3181
|
fmtUsd2 = (n) => `$${(n ?? 0).toFixed(2)}`;
|
|
2791
3182
|
TeamCard = ({ team, usage, usageLoading, canManage, onEditTeam }) => {
|
|
2792
|
-
const [expanded, setExpanded] =
|
|
3183
|
+
const [expanded, setExpanded] = useState6(false);
|
|
2793
3184
|
const chart = useChartTheme();
|
|
2794
3185
|
const budget = team.max_budget ?? 0;
|
|
2795
3186
|
const spend = team.spend ?? 0;
|
|
@@ -2799,24 +3190,24 @@ var init_TeamUsage = __esm({
|
|
|
2799
3190
|
const dailyData = usage?.daily_usage?.map((d) => ({ date: d.date, spend: d.spend })) ?? [];
|
|
2800
3191
|
const renderDailySpendSection = () => {
|
|
2801
3192
|
if (usageLoading) {
|
|
2802
|
-
return /* @__PURE__ */
|
|
3193
|
+
return /* @__PURE__ */ React8.createElement(Box8, { display: "flex", justifyContent: "center", py: 3 }, /* @__PURE__ */ React8.createElement(CircularProgress5, { size: 22 }));
|
|
2803
3194
|
}
|
|
2804
3195
|
if (dailyData.length === 0) return null;
|
|
2805
|
-
return /* @__PURE__ */
|
|
2806
|
-
|
|
3196
|
+
return /* @__PURE__ */ React8.createElement(Box8, { mb: 2.5 }, /* @__PURE__ */ React8.createElement(
|
|
3197
|
+
Typography8,
|
|
2807
3198
|
{
|
|
2808
3199
|
variant: "caption",
|
|
2809
3200
|
color: "text.secondary",
|
|
2810
3201
|
sx: { display: "block", fontSize: 11, fontWeight: 700, letterSpacing: "0.07em", textTransform: "uppercase", mb: 1 }
|
|
2811
3202
|
},
|
|
2812
3203
|
"Daily spend"
|
|
2813
|
-
), /* @__PURE__ */
|
|
3204
|
+
), /* @__PURE__ */ React8.createElement(Box8, { height: 150 }, /* @__PURE__ */ React8.createElement(ResponsiveContainer2, { width: "100%", height: "100%" }, /* @__PURE__ */ React8.createElement(AreaChart2, { data: dailyData, margin: { top: 4, right: 8, left: 0, bottom: 0 } }, /* @__PURE__ */ React8.createElement(CartesianGrid2, { ...chart.grid }), /* @__PURE__ */ React8.createElement(XAxis2, { dataKey: "date", tickFormatter: fmtDateShort, ...chart.axis }), /* @__PURE__ */ React8.createElement(YAxis2, { tickFormatter: fmtUsdCompact, width: 52, ...chart.axis }), /* @__PURE__ */ React8.createElement(
|
|
2814
3205
|
Tooltip2,
|
|
2815
3206
|
{
|
|
2816
3207
|
cursor: chart.cursor,
|
|
2817
|
-
content: /* @__PURE__ */
|
|
3208
|
+
content: /* @__PURE__ */ React8.createElement(ChartTooltip, { valueFormatter: (v) => `$${v.toFixed(4)}` })
|
|
2818
3209
|
}
|
|
2819
|
-
), /* @__PURE__ */
|
|
3210
|
+
), /* @__PURE__ */ React8.createElement(
|
|
2820
3211
|
Area2,
|
|
2821
3212
|
{
|
|
2822
3213
|
type: "monotone",
|
|
@@ -2829,8 +3220,8 @@ var init_TeamUsage = __esm({
|
|
|
2829
3220
|
}
|
|
2830
3221
|
)))));
|
|
2831
3222
|
};
|
|
2832
|
-
const sectionLabel = (label) => /* @__PURE__ */
|
|
2833
|
-
|
|
3223
|
+
const sectionLabel = (label) => /* @__PURE__ */ React8.createElement(
|
|
3224
|
+
Typography8,
|
|
2834
3225
|
{
|
|
2835
3226
|
variant: "caption",
|
|
2836
3227
|
color: "text.secondary",
|
|
@@ -2838,8 +3229,8 @@ var init_TeamUsage = __esm({
|
|
|
2838
3229
|
},
|
|
2839
3230
|
label
|
|
2840
3231
|
);
|
|
2841
|
-
return /* @__PURE__ */
|
|
2842
|
-
|
|
3232
|
+
return /* @__PURE__ */ React8.createElement(Paper6, { variant: "outlined", sx: { borderRadius: 2, p: 2.5 } }, /* @__PURE__ */ React8.createElement(Box8, { display: "flex", alignItems: "center", gap: 1.5 }, /* @__PURE__ */ React8.createElement(
|
|
3233
|
+
Box8,
|
|
2843
3234
|
{
|
|
2844
3235
|
sx: (theme) => ({
|
|
2845
3236
|
width: 36,
|
|
@@ -2849,20 +3240,20 @@ var init_TeamUsage = __esm({
|
|
|
2849
3240
|
display: "flex",
|
|
2850
3241
|
alignItems: "center",
|
|
2851
3242
|
justifyContent: "center",
|
|
2852
|
-
bgcolor:
|
|
3243
|
+
bgcolor: alpha5(theme.palette.primary.main, 0.1),
|
|
2853
3244
|
color: theme.palette.primary.main
|
|
2854
3245
|
})
|
|
2855
3246
|
},
|
|
2856
|
-
/* @__PURE__ */
|
|
2857
|
-
), /* @__PURE__ */
|
|
2858
|
-
|
|
3247
|
+
/* @__PURE__ */ React8.createElement(Group, { fontSize: "small" })
|
|
3248
|
+
), /* @__PURE__ */ React8.createElement(Box8, { flexGrow: 1, minWidth: 0 }, /* @__PURE__ */ React8.createElement(Typography8, { variant: "subtitle1", sx: { fontWeight: 600, lineHeight: 1.3 } }, team.team_alias || "Untitled team"), /* @__PURE__ */ React8.createElement(
|
|
3249
|
+
Typography8,
|
|
2859
3250
|
{
|
|
2860
3251
|
variant: "caption",
|
|
2861
3252
|
color: "text.secondary",
|
|
2862
3253
|
sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 11 }
|
|
2863
3254
|
},
|
|
2864
3255
|
team.team_id
|
|
2865
|
-
)), isOver && /* @__PURE__ */
|
|
3256
|
+
)), isOver && /* @__PURE__ */ React8.createElement(StatusPill, { label: "Over budget", tone: "danger" }), isNear && /* @__PURE__ */ React8.createElement(StatusPill, { label: "Near limit", tone: "warning" }), canManage && onEditTeam && /* @__PURE__ */ React8.createElement(
|
|
2866
3257
|
Button5,
|
|
2867
3258
|
{
|
|
2868
3259
|
size: "small",
|
|
@@ -2871,8 +3262,8 @@ var init_TeamUsage = __esm({
|
|
|
2871
3262
|
sx: { textTransform: "none" }
|
|
2872
3263
|
},
|
|
2873
3264
|
"Edit"
|
|
2874
|
-
), /* @__PURE__ */
|
|
2875
|
-
|
|
3265
|
+
), /* @__PURE__ */ React8.createElement(
|
|
3266
|
+
IconButton5,
|
|
2876
3267
|
{
|
|
2877
3268
|
size: "small",
|
|
2878
3269
|
onClick: () => setExpanded((e) => !e),
|
|
@@ -2884,9 +3275,9 @@ var init_TeamUsage = __esm({
|
|
|
2884
3275
|
transition: theme.transitions.create("transform")
|
|
2885
3276
|
})
|
|
2886
3277
|
},
|
|
2887
|
-
/* @__PURE__ */
|
|
2888
|
-
)), /* @__PURE__ */
|
|
2889
|
-
|
|
3278
|
+
/* @__PURE__ */ React8.createElement(ExpandMore3, null)
|
|
3279
|
+
)), /* @__PURE__ */ React8.createElement(
|
|
3280
|
+
Box8,
|
|
2890
3281
|
{
|
|
2891
3282
|
sx: {
|
|
2892
3283
|
display: "grid",
|
|
@@ -2896,35 +3287,35 @@ var init_TeamUsage = __esm({
|
|
|
2896
3287
|
maxWidth: 640
|
|
2897
3288
|
}
|
|
2898
3289
|
},
|
|
2899
|
-
/* @__PURE__ */
|
|
2900
|
-
/* @__PURE__ */
|
|
2901
|
-
/* @__PURE__ */
|
|
2902
|
-
/* @__PURE__ */
|
|
2903
|
-
/* @__PURE__ */
|
|
2904
|
-
/* @__PURE__ */
|
|
2905
|
-
), budget > 0 && /* @__PURE__ */
|
|
3290
|
+
/* @__PURE__ */ React8.createElement(Stat, { label: "Members", value: team.members_with_roles?.length ?? "\u2014" }),
|
|
3291
|
+
/* @__PURE__ */ React8.createElement(Stat, { label: "Models", value: team.models?.length ? team.models.length : "All" }),
|
|
3292
|
+
/* @__PURE__ */ React8.createElement(Stat, { label: "Budget", value: budget > 0 ? fmtUsd2(budget) : "Unlimited" }),
|
|
3293
|
+
/* @__PURE__ */ React8.createElement(Stat, { label: "Spend", value: fmtUsd2(spend) }),
|
|
3294
|
+
/* @__PURE__ */ React8.createElement(Stat, { label: "TPM", value: team.tpm_limit && team.tpm_limit > 0 ? team.tpm_limit.toLocaleString() : "\u2014" }),
|
|
3295
|
+
/* @__PURE__ */ React8.createElement(Stat, { label: "RPM", value: team.rpm_limit && team.rpm_limit > 0 ? team.rpm_limit.toLocaleString() : "\u2014" })
|
|
3296
|
+
), budget > 0 && /* @__PURE__ */ React8.createElement(Box8, { mt: 2, maxWidth: 640 }, /* @__PURE__ */ React8.createElement(Box8, { display: "flex", justifyContent: "space-between", alignItems: "baseline", mb: 0.75 }, /* @__PURE__ */ React8.createElement(Typography8, { variant: "caption", color: "text.secondary", sx: { fontVariantNumeric: "tabular-nums" } }, fmtUsd2(spend), " of ", fmtUsd2(budget)), /* @__PURE__ */ React8.createElement(Typography8, { variant: "caption", color: "text.secondary", sx: { fontVariantNumeric: "tabular-nums" } }, budgetPct.toFixed(0), "%")), /* @__PURE__ */ React8.createElement(Meter, { value: budgetPct, tone: budgetTone3(isOver, isNear), height: 5 })), /* @__PURE__ */ React8.createElement(Collapse2, { in: expanded, unmountOnExit: true }, /* @__PURE__ */ React8.createElement(Divider3, { sx: { my: 2.5 } }), /* @__PURE__ */ React8.createElement(Box8, { mb: 2.5 }, sectionLabel("Models"), team.models?.length ? /* @__PURE__ */ React8.createElement(Box8, { display: "flex", gap: 0.5, flexWrap: "wrap" }, team.models.map((m) => /* @__PURE__ */ React8.createElement(TagChip, { key: m, label: m, title: m }))) : /* @__PURE__ */ React8.createElement(Typography8, { variant: "body2", color: "text.secondary" }, "All models allowed")), renderDailySpendSection(), /* @__PURE__ */ React8.createElement(Box8, null, sectionLabel("Members"), team.members_with_roles?.length ? /* @__PURE__ */ React8.createElement(
|
|
2906
3297
|
TableContainer4,
|
|
2907
3298
|
{
|
|
2908
|
-
component:
|
|
3299
|
+
component: Paper6,
|
|
2909
3300
|
variant: "outlined",
|
|
2910
3301
|
sx: { borderRadius: 1.5 }
|
|
2911
3302
|
},
|
|
2912
|
-
/* @__PURE__ */
|
|
2913
|
-
|
|
3303
|
+
/* @__PURE__ */ React8.createElement(Table4, { size: "small", sx: dataTableSx }, /* @__PURE__ */ React8.createElement(TableHead4, null, /* @__PURE__ */ React8.createElement(TableRow4, null, /* @__PURE__ */ React8.createElement(TableCell4, null, "User"), /* @__PURE__ */ React8.createElement(TableCell4, { align: "right" }, "Role"))), /* @__PURE__ */ React8.createElement(TableBody4, null, team.members_with_roles.map((m) => /* @__PURE__ */ React8.createElement(TableRow4, { key: m.user_id }, /* @__PURE__ */ React8.createElement(TableCell4, null, m.user_email ? /* @__PURE__ */ React8.createElement(React8.Fragment, null, /* @__PURE__ */ React8.createElement(Typography8, { variant: "body2" }, m.user_email), /* @__PURE__ */ React8.createElement(
|
|
3304
|
+
Typography8,
|
|
2914
3305
|
{
|
|
2915
3306
|
variant: "caption",
|
|
2916
3307
|
color: "text.secondary",
|
|
2917
3308
|
sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 11 }
|
|
2918
3309
|
},
|
|
2919
3310
|
m.user_id
|
|
2920
|
-
)) : /* @__PURE__ */
|
|
2921
|
-
|
|
3311
|
+
)) : /* @__PURE__ */ React8.createElement(
|
|
3312
|
+
Typography8,
|
|
2922
3313
|
{
|
|
2923
3314
|
variant: "body2",
|
|
2924
3315
|
sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace" }
|
|
2925
3316
|
},
|
|
2926
3317
|
m.user_id
|
|
2927
|
-
)), /* @__PURE__ */
|
|
3318
|
+
)), /* @__PURE__ */ React8.createElement(TableCell4, { align: "right" }, /* @__PURE__ */ React8.createElement(
|
|
2928
3319
|
StatusPill,
|
|
2929
3320
|
{
|
|
2930
3321
|
label: m.role,
|
|
@@ -2932,7 +3323,7 @@ var init_TeamUsage = __esm({
|
|
|
2932
3323
|
dot: false
|
|
2933
3324
|
}
|
|
2934
3325
|
))))))
|
|
2935
|
-
) : /* @__PURE__ */
|
|
3326
|
+
) : /* @__PURE__ */ React8.createElement(Typography8, { variant: "body2", color: "text.secondary" }, "No members assigned."))));
|
|
2936
3327
|
};
|
|
2937
3328
|
TeamUsage = ({
|
|
2938
3329
|
teams,
|
|
@@ -2944,22 +3335,22 @@ var init_TeamUsage = __esm({
|
|
|
2944
3335
|
canCreate,
|
|
2945
3336
|
onCreateTeam
|
|
2946
3337
|
}) => {
|
|
2947
|
-
const createAction = canCreate && onCreateTeam ? /* @__PURE__ */
|
|
3338
|
+
const createAction = canCreate && onCreateTeam ? /* @__PURE__ */ React8.createElement(
|
|
2948
3339
|
Button5,
|
|
2949
3340
|
{
|
|
2950
3341
|
variant: "contained",
|
|
2951
3342
|
color: "primary",
|
|
2952
3343
|
disableElevation: true,
|
|
2953
|
-
startIcon: /* @__PURE__ */
|
|
3344
|
+
startIcon: /* @__PURE__ */ React8.createElement(Add3, null),
|
|
2954
3345
|
onClick: onCreateTeam
|
|
2955
3346
|
},
|
|
2956
3347
|
"Create Team"
|
|
2957
3348
|
) : void 0;
|
|
2958
3349
|
if (loading) {
|
|
2959
|
-
return /* @__PURE__ */
|
|
3350
|
+
return /* @__PURE__ */ React8.createElement(SectionCard, { title: "Teams", actions: createAction }, /* @__PURE__ */ React8.createElement(Skeleton4, { variant: "rounded", height: 120, sx: { mb: 2 } }), /* @__PURE__ */ React8.createElement(Skeleton4, { variant: "rounded", height: 120 }));
|
|
2960
3351
|
}
|
|
2961
3352
|
if (!teams.length) {
|
|
2962
|
-
return /* @__PURE__ */
|
|
3353
|
+
return /* @__PURE__ */ React8.createElement(SectionCard, { title: "Teams", actions: createAction }, /* @__PURE__ */ React8.createElement(
|
|
2963
3354
|
EmptyState,
|
|
2964
3355
|
{
|
|
2965
3356
|
message: "You're not a member of any LiteLLM team yet.",
|
|
@@ -2967,14 +3358,14 @@ var init_TeamUsage = __esm({
|
|
|
2967
3358
|
}
|
|
2968
3359
|
));
|
|
2969
3360
|
}
|
|
2970
|
-
return /* @__PURE__ */
|
|
3361
|
+
return /* @__PURE__ */ React8.createElement(
|
|
2971
3362
|
SectionCard,
|
|
2972
3363
|
{
|
|
2973
3364
|
title: "Teams",
|
|
2974
3365
|
subtitle: `${teams.length} team${teams.length === 1 ? "" : "s"}`,
|
|
2975
3366
|
actions: createAction
|
|
2976
3367
|
},
|
|
2977
|
-
/* @__PURE__ */
|
|
3368
|
+
/* @__PURE__ */ React8.createElement(Box8, { sx: { display: "flex", flexDirection: "column", gap: 2 } }, teams.map((team) => /* @__PURE__ */ React8.createElement(
|
|
2978
3369
|
TeamCard,
|
|
2979
3370
|
{
|
|
2980
3371
|
key: team.team_id,
|
|
@@ -2991,15 +3382,15 @@ var init_TeamUsage = __esm({
|
|
|
2991
3382
|
});
|
|
2992
3383
|
|
|
2993
3384
|
// src/components/ModelsTable.tsx
|
|
2994
|
-
import
|
|
2995
|
-
import
|
|
3385
|
+
import React9, { useState as useState7, useMemo as useMemo7, useCallback } from "react";
|
|
3386
|
+
import Box9 from "@mui/material/Box";
|
|
2996
3387
|
import Table5 from "@mui/material/Table";
|
|
2997
3388
|
import TableBody5 from "@mui/material/TableBody";
|
|
2998
3389
|
import TableCell5 from "@mui/material/TableCell";
|
|
2999
3390
|
import TableContainer5 from "@mui/material/TableContainer";
|
|
3000
3391
|
import TableHead5 from "@mui/material/TableHead";
|
|
3001
3392
|
import TableRow5 from "@mui/material/TableRow";
|
|
3002
|
-
import
|
|
3393
|
+
import Typography9 from "@mui/material/Typography";
|
|
3003
3394
|
import MenuItem4 from "@mui/material/MenuItem";
|
|
3004
3395
|
import Select from "@mui/material/Select";
|
|
3005
3396
|
import FormControl from "@mui/material/FormControl";
|
|
@@ -3010,8 +3401,8 @@ import { Search as Search2 } from "@mui/icons-material";
|
|
|
3010
3401
|
import ContentCopyIcon from "@mui/icons-material/ContentCopy";
|
|
3011
3402
|
import Tooltip3 from "@mui/material/Tooltip";
|
|
3012
3403
|
import Snackbar from "@mui/material/Snackbar";
|
|
3013
|
-
import
|
|
3014
|
-
import { alpha as
|
|
3404
|
+
import Alert4 from "@mui/material/Alert";
|
|
3405
|
+
import { alpha as alpha6, useTheme as useTheme2 } from "@mui/material/styles";
|
|
3015
3406
|
function isModelAllowedByTeam2(model, teamModels) {
|
|
3016
3407
|
if (!teamModels || teamModels.length === 0 || teamModels.includes(ALL_PROXY_MODELS2)) return true;
|
|
3017
3408
|
return teamModels.includes(model.model_name) || !!model.access_groups?.some((group) => teamModels.includes(group));
|
|
@@ -3037,18 +3428,18 @@ var init_ModelsTable = __esm({
|
|
|
3037
3428
|
ALL_PROXY_MODELS2 = "all-proxy-models";
|
|
3038
3429
|
ModelsTable = ({ allModels, teams, loading }) => {
|
|
3039
3430
|
const theme = useTheme2();
|
|
3040
|
-
const [selectedTeamId, setSelectedTeamId] =
|
|
3041
|
-
const [filterText, setFilterText] =
|
|
3042
|
-
const [copiedSnackbar, setCopiedSnackbar] =
|
|
3043
|
-
const selectedTeam =
|
|
3431
|
+
const [selectedTeamId, setSelectedTeamId] = useState7("");
|
|
3432
|
+
const [filterText, setFilterText] = useState7("");
|
|
3433
|
+
const [copiedSnackbar, setCopiedSnackbar] = useState7(false);
|
|
3434
|
+
const selectedTeam = useMemo7(
|
|
3044
3435
|
() => teams.find((t) => t.team_id === selectedTeamId) ?? null,
|
|
3045
3436
|
[teams, selectedTeamId]
|
|
3046
3437
|
);
|
|
3047
|
-
const teamModels =
|
|
3438
|
+
const teamModels = useMemo7(() => {
|
|
3048
3439
|
if (!selectedTeam) return [];
|
|
3049
3440
|
return allModels.filter((model) => isModelAllowedByTeam2(model, selectedTeam.models));
|
|
3050
3441
|
}, [allModels, selectedTeam]);
|
|
3051
|
-
const filteredModels =
|
|
3442
|
+
const filteredModels = useMemo7(() => {
|
|
3052
3443
|
const q = filterText.trim().toLowerCase();
|
|
3053
3444
|
if (!q) return teamModels;
|
|
3054
3445
|
return teamModels.filter(
|
|
@@ -3072,13 +3463,13 @@ var init_ModelsTable = __esm({
|
|
|
3072
3463
|
return theme.palette.info.main;
|
|
3073
3464
|
}
|
|
3074
3465
|
};
|
|
3075
|
-
return /* @__PURE__ */
|
|
3466
|
+
return /* @__PURE__ */ React9.createElement(
|
|
3076
3467
|
SectionCard,
|
|
3077
3468
|
{
|
|
3078
3469
|
title: "Available Models",
|
|
3079
3470
|
subtitle: selectedTeam ? `${filteredModels.length}${filterText.trim() ? ` of ${teamModels.length}` : ""} model${filteredModels.length === 1 ? "" : "s"}` : "Select a team to view its models"
|
|
3080
3471
|
},
|
|
3081
|
-
/* @__PURE__ */
|
|
3472
|
+
/* @__PURE__ */ React9.createElement(Box9, { sx: { px: 2.5, pb: 2.5, pt: 1, display: "flex", gap: 2, flexWrap: "wrap" } }, /* @__PURE__ */ React9.createElement(FormControl, { size: "small", sx: { minWidth: 240, mb: 2 } }, /* @__PURE__ */ React9.createElement(InputLabel, { id: "team-select-label" }, "Team"), /* @__PURE__ */ React9.createElement(
|
|
3082
3473
|
Select,
|
|
3083
3474
|
{
|
|
3084
3475
|
labelId: "team-select-label",
|
|
@@ -3086,8 +3477,8 @@ var init_ModelsTable = __esm({
|
|
|
3086
3477
|
label: "Team",
|
|
3087
3478
|
onChange: (e) => setSelectedTeamId(e.target.value)
|
|
3088
3479
|
},
|
|
3089
|
-
teams.map((t) => /* @__PURE__ */
|
|
3090
|
-
)), /* @__PURE__ */
|
|
3480
|
+
teams.map((t) => /* @__PURE__ */ React9.createElement(MenuItem4, { key: t.team_id, value: t.team_id }, t.team_alias ?? t.team_id))
|
|
3481
|
+
)), /* @__PURE__ */ React9.createElement(
|
|
3091
3482
|
TextField5,
|
|
3092
3483
|
{
|
|
3093
3484
|
label: "Filter Models",
|
|
@@ -3097,33 +3488,33 @@ var init_ModelsTable = __esm({
|
|
|
3097
3488
|
disabled: !selectedTeamId,
|
|
3098
3489
|
sx: { minWidth: 240, mb: 2 },
|
|
3099
3490
|
InputProps: {
|
|
3100
|
-
startAdornment: /* @__PURE__ */
|
|
3491
|
+
startAdornment: /* @__PURE__ */ React9.createElement(InputAdornment2, { position: "start" }, /* @__PURE__ */ React9.createElement(Search2, { fontSize: "small" }))
|
|
3101
3492
|
}
|
|
3102
3493
|
}
|
|
3103
3494
|
)),
|
|
3104
|
-
loading && /* @__PURE__ */
|
|
3105
|
-
!loading && !selectedTeamId && /* @__PURE__ */
|
|
3495
|
+
loading && /* @__PURE__ */ React9.createElement(EmptyState, { message: "Loading models\u2026" }),
|
|
3496
|
+
!loading && !selectedTeamId && /* @__PURE__ */ React9.createElement(
|
|
3106
3497
|
EmptyState,
|
|
3107
3498
|
{
|
|
3108
3499
|
message: "Select a team to see available models",
|
|
3109
3500
|
hint: "Each team may have a different set of models assigned"
|
|
3110
3501
|
}
|
|
3111
3502
|
),
|
|
3112
|
-
!loading && selectedTeamId && teamModels.length === 0 && /* @__PURE__ */
|
|
3503
|
+
!loading && selectedTeamId && teamModels.length === 0 && /* @__PURE__ */ React9.createElement(
|
|
3113
3504
|
EmptyState,
|
|
3114
3505
|
{
|
|
3115
3506
|
message: "No models available for this team",
|
|
3116
3507
|
hint: allModels.length === 0 ? "No models are configured in the system" : "None of the team's assigned models were found in the catalog"
|
|
3117
3508
|
}
|
|
3118
3509
|
),
|
|
3119
|
-
!loading && selectedTeamId && teamModels.length > 0 && filteredModels.length === 0 && /* @__PURE__ */
|
|
3510
|
+
!loading && selectedTeamId && teamModels.length > 0 && filteredModels.length === 0 && /* @__PURE__ */ React9.createElement(
|
|
3120
3511
|
EmptyState,
|
|
3121
3512
|
{
|
|
3122
3513
|
message: "No models match your search",
|
|
3123
3514
|
hint: "Try a different search term"
|
|
3124
3515
|
}
|
|
3125
3516
|
),
|
|
3126
|
-
!loading && filteredModels.length > 0 && /* @__PURE__ */
|
|
3517
|
+
!loading && filteredModels.length > 0 && /* @__PURE__ */ React9.createElement(TableContainer5, null, /* @__PURE__ */ React9.createElement(Table5, { size: "small", sx: dataTableSx }, /* @__PURE__ */ React9.createElement(TableHead5, null, /* @__PURE__ */ React9.createElement(TableRow5, null, /* @__PURE__ */ React9.createElement(TableCell5, null, "Model ID"), /* @__PURE__ */ React9.createElement(TableCell5, null, "Mode"), /* @__PURE__ */ React9.createElement(TableCell5, { align: "right" }, "Input Cost"), /* @__PURE__ */ React9.createElement(TableCell5, { align: "right" }, "Output Cost"), /* @__PURE__ */ React9.createElement(TableCell5, { align: "right" }, "Max Input"), /* @__PURE__ */ React9.createElement(TableCell5, { align: "right" }, "Max Output"), /* @__PURE__ */ React9.createElement(TableCell5, null, "Capabilities"))), /* @__PURE__ */ React9.createElement(TableBody5, null, filteredModels.map((m) => /* @__PURE__ */ React9.createElement(TableRow5, { key: m.model_name, hover: true }, /* @__PURE__ */ React9.createElement(TableCell5, null, /* @__PURE__ */ React9.createElement(Box9, { sx: { display: "flex", alignItems: "center", gap: 0.5 } }, /* @__PURE__ */ React9.createElement(Tooltip3, { title: m.model_name, placement: "top" }, /* @__PURE__ */ React9.createElement(
|
|
3127
3518
|
"span",
|
|
3128
3519
|
{
|
|
3129
3520
|
role: "button",
|
|
@@ -3133,9 +3524,9 @@ var init_ModelsTable = __esm({
|
|
|
3133
3524
|
onKeyDown: (e) => e.key === "Enter" && handleCopyModelId(m.model_name),
|
|
3134
3525
|
style: { cursor: "pointer", display: "inline-flex" }
|
|
3135
3526
|
},
|
|
3136
|
-
/* @__PURE__ */
|
|
3137
|
-
)), /* @__PURE__ */
|
|
3138
|
-
|
|
3527
|
+
/* @__PURE__ */ React9.createElement(TagChip, { label: m.model_name, title: "Copy model ID" })
|
|
3528
|
+
)), /* @__PURE__ */ React9.createElement(Tooltip3, { title: "Copy model ID" }, /* @__PURE__ */ React9.createElement(
|
|
3529
|
+
Box9,
|
|
3139
3530
|
{
|
|
3140
3531
|
component: "span",
|
|
3141
3532
|
role: "button",
|
|
@@ -3145,9 +3536,9 @@ var init_ModelsTable = __esm({
|
|
|
3145
3536
|
sx: quietIconButtonSx("accent"),
|
|
3146
3537
|
style: { cursor: "pointer", display: "inline-flex" }
|
|
3147
3538
|
},
|
|
3148
|
-
/* @__PURE__ */
|
|
3149
|
-
)))), /* @__PURE__ */
|
|
3150
|
-
|
|
3539
|
+
/* @__PURE__ */ React9.createElement(ContentCopyIcon, { sx: { fontSize: 14 } })
|
|
3540
|
+
)))), /* @__PURE__ */ React9.createElement(TableCell5, null, /* @__PURE__ */ React9.createElement(
|
|
3541
|
+
Box9,
|
|
3151
3542
|
{
|
|
3152
3543
|
component: "span",
|
|
3153
3544
|
sx: {
|
|
@@ -3158,13 +3549,13 @@ var init_ModelsTable = __esm({
|
|
|
3158
3549
|
fontSize: 11,
|
|
3159
3550
|
fontWeight: 600,
|
|
3160
3551
|
textTransform: "capitalize",
|
|
3161
|
-
bgcolor:
|
|
3552
|
+
bgcolor: alpha6(modeColor(m.mode), 0.1),
|
|
3162
3553
|
color: modeColor(m.mode)
|
|
3163
3554
|
}
|
|
3164
3555
|
},
|
|
3165
3556
|
m.mode
|
|
3166
|
-
)), /* @__PURE__ */
|
|
3167
|
-
/* @__PURE__ */
|
|
3557
|
+
)), /* @__PURE__ */ React9.createElement(TableCell5, { align: "right" }, /* @__PURE__ */ React9.createElement(Typography9, { variant: "body2", sx: { fontVariantNumeric: "tabular-nums" } }, fmtCostPerToken(m.input_cost_per_token))), /* @__PURE__ */ React9.createElement(TableCell5, { align: "right" }, /* @__PURE__ */ React9.createElement(Typography9, { variant: "body2", sx: { fontVariantNumeric: "tabular-nums" } }, fmtCostPerToken(m.output_cost_per_token))), /* @__PURE__ */ React9.createElement(TableCell5, { align: "right" }, /* @__PURE__ */ React9.createElement(Typography9, { variant: "body2", sx: { fontVariantNumeric: "tabular-nums" } }, fmtTokens(m.max_input_tokens))), /* @__PURE__ */ React9.createElement(TableCell5, { align: "right" }, /* @__PURE__ */ React9.createElement(Typography9, { variant: "body2", sx: { fontVariantNumeric: "tabular-nums" } }, fmtTokens(m.max_output_tokens))), /* @__PURE__ */ React9.createElement(TableCell5, null, /* @__PURE__ */ React9.createElement(Box9, { sx: { display: "flex", gap: 0.5, flexWrap: "wrap" } }, m.supports_function_calling && /* @__PURE__ */ React9.createElement(TagChip, { label: "Fn", title: "Function calling" }), m.supports_vision && /* @__PURE__ */ React9.createElement(TagChip, { label: "\u{1F441}", title: "Vision" })))))))),
|
|
3558
|
+
/* @__PURE__ */ React9.createElement(
|
|
3168
3559
|
Snackbar,
|
|
3169
3560
|
{
|
|
3170
3561
|
open: copiedSnackbar,
|
|
@@ -3172,7 +3563,7 @@ var init_ModelsTable = __esm({
|
|
|
3172
3563
|
onClose: () => setCopiedSnackbar(false),
|
|
3173
3564
|
anchorOrigin: { vertical: "bottom", horizontal: "center" }
|
|
3174
3565
|
},
|
|
3175
|
-
/* @__PURE__ */
|
|
3566
|
+
/* @__PURE__ */ React9.createElement(Alert4, { severity: "success", variant: "filled", sx: { fontSize: 13 } }, "Model ID copied to clipboard")
|
|
3176
3567
|
)
|
|
3177
3568
|
);
|
|
3178
3569
|
};
|
|
@@ -3180,9 +3571,9 @@ var init_ModelsTable = __esm({
|
|
|
3180
3571
|
});
|
|
3181
3572
|
|
|
3182
3573
|
// src/components/AuditLog.tsx
|
|
3183
|
-
import
|
|
3184
|
-
import
|
|
3185
|
-
import
|
|
3574
|
+
import React10, { useState as useState8, useCallback as useCallback2 } from "react";
|
|
3575
|
+
import Box10 from "@mui/material/Box";
|
|
3576
|
+
import Typography10 from "@mui/material/Typography";
|
|
3186
3577
|
import Table6 from "@mui/material/Table";
|
|
3187
3578
|
import TableBody6 from "@mui/material/TableBody";
|
|
3188
3579
|
import TableCell6 from "@mui/material/TableCell";
|
|
@@ -3193,10 +3584,10 @@ import TablePagination from "@mui/material/TablePagination";
|
|
|
3193
3584
|
import TextField6 from "@mui/material/TextField";
|
|
3194
3585
|
import MenuItem5 from "@mui/material/MenuItem";
|
|
3195
3586
|
import Skeleton5 from "@mui/material/Skeleton";
|
|
3196
|
-
import
|
|
3197
|
-
import
|
|
3198
|
-
import
|
|
3199
|
-
import { alpha as
|
|
3587
|
+
import Collapse3 from "@mui/material/Collapse";
|
|
3588
|
+
import IconButton6 from "@mui/material/IconButton";
|
|
3589
|
+
import Alert5 from "@mui/material/Alert";
|
|
3590
|
+
import { alpha as alpha7 } from "@mui/material/styles";
|
|
3200
3591
|
import { KeyboardArrowDown } from "@mui/icons-material";
|
|
3201
3592
|
import { useAsync as useAsync2 } from "react-use";
|
|
3202
3593
|
function actionTone(action) {
|
|
@@ -3219,10 +3610,10 @@ function formatDateTime(iso) {
|
|
|
3219
3610
|
}
|
|
3220
3611
|
function renderAuditLogBody(loading, entries) {
|
|
3221
3612
|
if (loading) {
|
|
3222
|
-
return /* @__PURE__ */
|
|
3613
|
+
return /* @__PURE__ */ React10.createElement(TableRow6, null, /* @__PURE__ */ React10.createElement(TableCell6, { colSpan: 6, sx: { py: 2 } }, /* @__PURE__ */ React10.createElement(Skeleton5, { variant: "rounded", height: 160 })));
|
|
3223
3614
|
}
|
|
3224
3615
|
if (entries.length === 0) {
|
|
3225
|
-
return /* @__PURE__ */
|
|
3616
|
+
return /* @__PURE__ */ React10.createElement(TableRow6, null, /* @__PURE__ */ React10.createElement(TableCell6, { colSpan: 6 }, /* @__PURE__ */ React10.createElement(
|
|
3226
3617
|
EmptyState,
|
|
3227
3618
|
{
|
|
3228
3619
|
message: "No audit events found",
|
|
@@ -3230,7 +3621,7 @@ function renderAuditLogBody(loading, entries) {
|
|
|
3230
3621
|
}
|
|
3231
3622
|
)));
|
|
3232
3623
|
}
|
|
3233
|
-
return entries.map((entry) => /* @__PURE__ */
|
|
3624
|
+
return entries.map((entry) => /* @__PURE__ */ React10.createElement(DetailRow, { key: entry.id, entry }));
|
|
3234
3625
|
}
|
|
3235
3626
|
var ACTION_TONES, TABLE_LABELS, DetailRow, AuditLog;
|
|
3236
3627
|
var init_AuditLog = __esm({
|
|
@@ -3252,10 +3643,10 @@ var init_AuditLog = __esm({
|
|
|
3252
3643
|
LiteLLM_UserTable: "User"
|
|
3253
3644
|
};
|
|
3254
3645
|
DetailRow = ({ entry }) => {
|
|
3255
|
-
const [open, setOpen] =
|
|
3646
|
+
const [open, setOpen] = useState8(false);
|
|
3256
3647
|
const hasDetail = entry.before_value || entry.updated_values;
|
|
3257
|
-
return /* @__PURE__ */
|
|
3258
|
-
|
|
3648
|
+
return /* @__PURE__ */ React10.createElement(React10.Fragment, null, /* @__PURE__ */ React10.createElement(TableRow6, null, /* @__PURE__ */ React10.createElement(TableCell6, { sx: { width: 40, pr: 0 } }, hasDetail && /* @__PURE__ */ React10.createElement(
|
|
3649
|
+
IconButton6,
|
|
3259
3650
|
{
|
|
3260
3651
|
size: "small",
|
|
3261
3652
|
onClick: () => setOpen((o) => !o),
|
|
@@ -3267,9 +3658,9 @@ var init_AuditLog = __esm({
|
|
|
3267
3658
|
transition: theme.transitions.create("transform")
|
|
3268
3659
|
})
|
|
3269
3660
|
},
|
|
3270
|
-
/* @__PURE__ */
|
|
3271
|
-
)), /* @__PURE__ */
|
|
3272
|
-
|
|
3661
|
+
/* @__PURE__ */ React10.createElement(KeyboardArrowDown, { fontSize: "small" })
|
|
3662
|
+
)), /* @__PURE__ */ React10.createElement(TableCell6, { sx: { whiteSpace: "nowrap" } }, /* @__PURE__ */ React10.createElement(Typography10, { variant: "body2", color: "text.secondary" }, formatDateTime(entry.updated_at))), /* @__PURE__ */ React10.createElement(TableCell6, null, entry.action && /* @__PURE__ */ React10.createElement(StatusPill, { label: entry.action, tone: actionTone(entry.action) })), /* @__PURE__ */ React10.createElement(TableCell6, null, /* @__PURE__ */ React10.createElement(Typography10, { variant: "body2" }, prettyTableName(entry.table_name))), /* @__PURE__ */ React10.createElement(TableCell6, null, /* @__PURE__ */ React10.createElement(
|
|
3663
|
+
Typography10,
|
|
3273
3664
|
{
|
|
3274
3665
|
variant: "body2",
|
|
3275
3666
|
component: "code",
|
|
@@ -3278,8 +3669,8 @@ var init_AuditLog = __esm({
|
|
|
3278
3669
|
sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 12 }
|
|
3279
3670
|
},
|
|
3280
3671
|
entry.object_id ? entry.object_id.slice(0, 20) + (entry.object_id.length > 20 ? "\u2026" : "") : "\u2014"
|
|
3281
|
-
)), /* @__PURE__ */
|
|
3282
|
-
|
|
3672
|
+
)), /* @__PURE__ */ React10.createElement(TableCell6, null, entry.changed_by ?? "\u2014")), hasDetail && /* @__PURE__ */ React10.createElement(TableRow6, { sx: { "&&:hover": { bgcolor: "transparent" } } }, /* @__PURE__ */ React10.createElement(TableCell6, { colSpan: 6, sx: { "&&": { py: 0, border: 0 } } }, /* @__PURE__ */ React10.createElement(Collapse3, { in: open, unmountOnExit: true }, /* @__PURE__ */ React10.createElement(
|
|
3673
|
+
Box10,
|
|
3283
3674
|
{
|
|
3284
3675
|
display: "flex",
|
|
3285
3676
|
gap: 2,
|
|
@@ -3288,11 +3679,11 @@ var init_AuditLog = __esm({
|
|
|
3288
3679
|
p: 2,
|
|
3289
3680
|
mb: 1.5,
|
|
3290
3681
|
borderRadius: 1.5,
|
|
3291
|
-
bgcolor:
|
|
3682
|
+
bgcolor: alpha7(theme.palette.text.primary, theme.palette.mode === "dark" ? 0.05 : 0.03)
|
|
3292
3683
|
})
|
|
3293
3684
|
},
|
|
3294
|
-
entry.before_value && /* @__PURE__ */
|
|
3295
|
-
|
|
3685
|
+
entry.before_value && /* @__PURE__ */ React10.createElement(Box10, { flex: 1, minWidth: 200 }, /* @__PURE__ */ React10.createElement(
|
|
3686
|
+
Typography10,
|
|
3296
3687
|
{
|
|
3297
3688
|
variant: "caption",
|
|
3298
3689
|
color: "text.secondary",
|
|
@@ -3301,9 +3692,9 @@ var init_AuditLog = __esm({
|
|
|
3301
3692
|
sx: { fontSize: 10.5, fontWeight: 700, letterSpacing: "0.07em", textTransform: "uppercase" }
|
|
3302
3693
|
},
|
|
3303
3694
|
"Before"
|
|
3304
|
-
), /* @__PURE__ */
|
|
3305
|
-
entry.updated_values && /* @__PURE__ */
|
|
3306
|
-
|
|
3695
|
+
), /* @__PURE__ */ React10.createElement(Typography10, { 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))),
|
|
3696
|
+
entry.updated_values && /* @__PURE__ */ React10.createElement(Box10, { flex: 1, minWidth: 200 }, /* @__PURE__ */ React10.createElement(
|
|
3697
|
+
Typography10,
|
|
3307
3698
|
{
|
|
3308
3699
|
variant: "caption",
|
|
3309
3700
|
color: "text.secondary",
|
|
@@ -3312,13 +3703,13 @@ var init_AuditLog = __esm({
|
|
|
3312
3703
|
sx: { fontSize: 10.5, fontWeight: 700, letterSpacing: "0.07em", textTransform: "uppercase" }
|
|
3313
3704
|
},
|
|
3314
3705
|
"After"
|
|
3315
|
-
), /* @__PURE__ */
|
|
3706
|
+
), /* @__PURE__ */ React10.createElement(Typography10, { 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)))
|
|
3316
3707
|
)))));
|
|
3317
3708
|
};
|
|
3318
3709
|
AuditLog = ({ api }) => {
|
|
3319
|
-
const [page, setPage] =
|
|
3320
|
-
const [pageSize, setPageSize] =
|
|
3321
|
-
const [filters, setFilters] =
|
|
3710
|
+
const [page, setPage] = useState8(0);
|
|
3711
|
+
const [pageSize, setPageSize] = useState8(25);
|
|
3712
|
+
const [filters, setFilters] = useState8({});
|
|
3322
3713
|
const fetchParams = useCallback2(
|
|
3323
3714
|
() => ({ page: page + 1, page_size: pageSize, ...filters }),
|
|
3324
3715
|
[page, pageSize, filters]
|
|
@@ -3329,13 +3720,13 @@ var init_AuditLog = __esm({
|
|
|
3329
3720
|
);
|
|
3330
3721
|
const entries = value?.audit_logs ?? [];
|
|
3331
3722
|
const total = value?.total ?? 0;
|
|
3332
|
-
return /* @__PURE__ */
|
|
3723
|
+
return /* @__PURE__ */ React10.createElement(
|
|
3333
3724
|
SectionCard,
|
|
3334
3725
|
{
|
|
3335
3726
|
title: "Audit Log",
|
|
3336
3727
|
subtitle: loading ? "Loading\u2026" : `${total.toLocaleString()} event${total === 1 ? "" : "s"}`,
|
|
3337
3728
|
flush: true,
|
|
3338
|
-
actions: /* @__PURE__ */
|
|
3729
|
+
actions: /* @__PURE__ */ React10.createElement(React10.Fragment, null, /* @__PURE__ */ React10.createElement(
|
|
3339
3730
|
TextField6,
|
|
3340
3731
|
{
|
|
3341
3732
|
size: "small",
|
|
@@ -3348,12 +3739,12 @@ var init_AuditLog = __esm({
|
|
|
3348
3739
|
},
|
|
3349
3740
|
sx: { minWidth: 150 }
|
|
3350
3741
|
},
|
|
3351
|
-
/* @__PURE__ */
|
|
3352
|
-
/* @__PURE__ */
|
|
3353
|
-
/* @__PURE__ */
|
|
3354
|
-
/* @__PURE__ */
|
|
3355
|
-
/* @__PURE__ */
|
|
3356
|
-
), /* @__PURE__ */
|
|
3742
|
+
/* @__PURE__ */ React10.createElement(MenuItem5, { value: "" }, "All actions"),
|
|
3743
|
+
/* @__PURE__ */ React10.createElement(MenuItem5, { value: "created" }, "Created"),
|
|
3744
|
+
/* @__PURE__ */ React10.createElement(MenuItem5, { value: "updated" }, "Updated"),
|
|
3745
|
+
/* @__PURE__ */ React10.createElement(MenuItem5, { value: "deleted" }, "Deleted"),
|
|
3746
|
+
/* @__PURE__ */ React10.createElement(MenuItem5, { value: "blocked" }, "Blocked")
|
|
3747
|
+
), /* @__PURE__ */ React10.createElement(
|
|
3357
3748
|
TextField6,
|
|
3358
3749
|
{
|
|
3359
3750
|
size: "small",
|
|
@@ -3366,13 +3757,13 @@ var init_AuditLog = __esm({
|
|
|
3366
3757
|
},
|
|
3367
3758
|
sx: { minWidth: 150 }
|
|
3368
3759
|
},
|
|
3369
|
-
/* @__PURE__ */
|
|
3370
|
-
/* @__PURE__ */
|
|
3371
|
-
/* @__PURE__ */
|
|
3372
|
-
/* @__PURE__ */
|
|
3373
|
-
/* @__PURE__ */
|
|
3374
|
-
/* @__PURE__ */
|
|
3375
|
-
), /* @__PURE__ */
|
|
3760
|
+
/* @__PURE__ */ React10.createElement(MenuItem5, { value: "" }, "All tables"),
|
|
3761
|
+
/* @__PURE__ */ React10.createElement(MenuItem5, { value: "LiteLLM_VerificationToken" }, "Key"),
|
|
3762
|
+
/* @__PURE__ */ React10.createElement(MenuItem5, { value: "LiteLLM_TeamTable" }, "Team"),
|
|
3763
|
+
/* @__PURE__ */ React10.createElement(MenuItem5, { value: "LiteLLM_TeamMembership" }, "Team member"),
|
|
3764
|
+
/* @__PURE__ */ React10.createElement(MenuItem5, { value: "LiteLLM_ObjectPermissionTable" }, "Team access (KB / MCP)"),
|
|
3765
|
+
/* @__PURE__ */ React10.createElement(MenuItem5, { value: "LiteLLM_UserTable" }, "User")
|
|
3766
|
+
), /* @__PURE__ */ React10.createElement(
|
|
3376
3767
|
TextField6,
|
|
3377
3768
|
{
|
|
3378
3769
|
size: "small",
|
|
@@ -3386,9 +3777,9 @@ var init_AuditLog = __esm({
|
|
|
3386
3777
|
}
|
|
3387
3778
|
))
|
|
3388
3779
|
},
|
|
3389
|
-
error && /* @__PURE__ */
|
|
3390
|
-
/* @__PURE__ */
|
|
3391
|
-
/* @__PURE__ */
|
|
3780
|
+
error && /* @__PURE__ */ React10.createElement(Box10, { px: 2.5, pb: 2 }, /* @__PURE__ */ React10.createElement(Alert5, { severity: "error" }, error.message)),
|
|
3781
|
+
/* @__PURE__ */ React10.createElement(TableContainer6, null, /* @__PURE__ */ React10.createElement(Table6, { size: "small", sx: dataTableSx }, /* @__PURE__ */ React10.createElement(TableHead6, null, /* @__PURE__ */ React10.createElement(TableRow6, null, /* @__PURE__ */ React10.createElement(TableCell6, { sx: { width: 40 } }), /* @__PURE__ */ React10.createElement(TableCell6, null, "Time"), /* @__PURE__ */ React10.createElement(TableCell6, null, "Action"), /* @__PURE__ */ React10.createElement(TableCell6, null, "Table"), /* @__PURE__ */ React10.createElement(TableCell6, null, "Object ID"), /* @__PURE__ */ React10.createElement(TableCell6, null, "Changed By"))), /* @__PURE__ */ React10.createElement(TableBody6, null, renderAuditLogBody(loading, entries)))),
|
|
3782
|
+
/* @__PURE__ */ React10.createElement(
|
|
3392
3783
|
TablePagination,
|
|
3393
3784
|
{
|
|
3394
3785
|
component: "div",
|
|
@@ -3443,17 +3834,18 @@ var LiteLLMPage_exports = {};
|
|
|
3443
3834
|
__export(LiteLLMPage_exports, {
|
|
3444
3835
|
LiteLLMPage: () => LiteLLMPage
|
|
3445
3836
|
});
|
|
3446
|
-
import
|
|
3447
|
-
import
|
|
3837
|
+
import React11, { useState as useState9, useCallback as useCallback3, useMemo as useMemo8 } from "react";
|
|
3838
|
+
import Box11 from "@mui/material/Box";
|
|
3448
3839
|
import Snackbar2 from "@mui/material/Snackbar";
|
|
3449
|
-
import
|
|
3450
|
-
import
|
|
3451
|
-
import
|
|
3452
|
-
import
|
|
3840
|
+
import Alert6 from "@mui/material/Alert";
|
|
3841
|
+
import CircularProgress6 from "@mui/material/CircularProgress";
|
|
3842
|
+
import Typography11 from "@mui/material/Typography";
|
|
3843
|
+
import Paper7 from "@mui/material/Paper";
|
|
3453
3844
|
import Tabs2 from "@mui/material/Tabs";
|
|
3454
3845
|
import Tab2 from "@mui/material/Tab";
|
|
3846
|
+
import Grid2 from "@mui/material/Grid";
|
|
3455
3847
|
import { useAsync as useAsync3, useAsyncRetry } from "react-use";
|
|
3456
|
-
import { useApi as
|
|
3848
|
+
import { useApi as useApi3 } from "@backstage/core-plugin-api";
|
|
3457
3849
|
import { usePermission } from "@backstage/plugin-permission-react";
|
|
3458
3850
|
function initDateRange() {
|
|
3459
3851
|
let preset = "7d";
|
|
@@ -3478,6 +3870,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3478
3870
|
init_GenerateKeyDialog();
|
|
3479
3871
|
init_ManageTeamDialog();
|
|
3480
3872
|
init_UsageStats();
|
|
3873
|
+
init_LiteLLMBudgetWidget();
|
|
3481
3874
|
init_TeamUsage();
|
|
3482
3875
|
init_ModelsTable();
|
|
3483
3876
|
init_AuditLog();
|
|
@@ -3485,21 +3878,21 @@ var init_LiteLLMPage = __esm({
|
|
|
3485
3878
|
init_permissions();
|
|
3486
3879
|
PERIOD_LS_KEY2 = "litellm_usage_period";
|
|
3487
3880
|
LiteLLMPage = () => {
|
|
3488
|
-
const api =
|
|
3489
|
-
const [dateRange, setDateRange] =
|
|
3490
|
-
const [currentPreset, setCurrentPreset] =
|
|
3881
|
+
const api = useApi3(liteLlmApiRef);
|
|
3882
|
+
const [dateRange, setDateRange] = useState9(initDateRange);
|
|
3883
|
+
const [currentPreset, setCurrentPreset] = useState9(() => {
|
|
3491
3884
|
try {
|
|
3492
3885
|
return localStorage.getItem(PERIOD_LS_KEY2) ?? "7d";
|
|
3493
3886
|
} catch {
|
|
3494
3887
|
return "7d";
|
|
3495
3888
|
}
|
|
3496
3889
|
});
|
|
3497
|
-
const [activeTab, setActiveTab] =
|
|
3498
|
-
const [snackbar, setSnackbar] =
|
|
3499
|
-
const [generateDialogOpen, setGenerateDialogOpen] =
|
|
3500
|
-
const [manageTeam, setManageTeam] =
|
|
3501
|
-
const [teamUsageCache, setTeamUsageCache] =
|
|
3502
|
-
const [teamUsageLoading, setTeamUsageLoading] =
|
|
3890
|
+
const [activeTab, setActiveTab] = useState9("overview");
|
|
3891
|
+
const [snackbar, setSnackbar] = useState9(null);
|
|
3892
|
+
const [generateDialogOpen, setGenerateDialogOpen] = useState9(false);
|
|
3893
|
+
const [manageTeam, setManageTeam] = useState9(null);
|
|
3894
|
+
const [teamUsageCache, setTeamUsageCache] = useState9({});
|
|
3895
|
+
const [teamUsageLoading, setTeamUsageLoading] = useState9({});
|
|
3503
3896
|
const { value: userInfo, loading: userLoading, error: userError } = useAsync3(
|
|
3504
3897
|
() => api.getUserInfo(),
|
|
3505
3898
|
[api]
|
|
@@ -3543,7 +3936,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3543
3936
|
async () => objectPermsEnabled ? api.getMcpServers().catch(() => []) : [],
|
|
3544
3937
|
[api, objectPermsEnabled]
|
|
3545
3938
|
);
|
|
3546
|
-
const teams =
|
|
3939
|
+
const teams = useMemo8(() => {
|
|
3547
3940
|
if (!allTeams?.length) return [];
|
|
3548
3941
|
if (!userInfo) return allTeams;
|
|
3549
3942
|
const userId = userInfo.user_id;
|
|
@@ -3582,7 +3975,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3582
3975
|
setTeamUsageLoading((prev) => ({ ...prev, [teamId]: false }));
|
|
3583
3976
|
}
|
|
3584
3977
|
}, [api, dateRange, teamUsageCache, teamUsageLoading]);
|
|
3585
|
-
const allowedModels =
|
|
3978
|
+
const allowedModels = useMemo8(() => {
|
|
3586
3979
|
if (!allModels?.length) return [];
|
|
3587
3980
|
const userModels = userInfo?.models;
|
|
3588
3981
|
const teamModels = teams?.flatMap((t) => t.models ?? []);
|
|
@@ -3690,14 +4083,14 @@ var init_LiteLLMPage = __esm({
|
|
|
3690
4083
|
);
|
|
3691
4084
|
const isInitialLoading = userLoading && !userInfo;
|
|
3692
4085
|
if (isInitialLoading) {
|
|
3693
|
-
return /* @__PURE__ */
|
|
4086
|
+
return /* @__PURE__ */ React11.createElement(Box11, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: "50vh" }, /* @__PURE__ */ React11.createElement(CircularProgress6, null));
|
|
3694
4087
|
}
|
|
3695
4088
|
if (userError || !userInfo) {
|
|
3696
4089
|
const isProvisioningEnabled = userError?.body?.provisioning === true;
|
|
3697
4090
|
const hint = userError?.body?.hint;
|
|
3698
|
-
return /* @__PURE__ */
|
|
4091
|
+
return /* @__PURE__ */ React11.createElement(Box11, { p: 3 }, /* @__PURE__ */ React11.createElement(Paper7, { sx: { p: 3 } }, /* @__PURE__ */ React11.createElement(Typography11, { variant: "h6", gutterBottom: true }, "Account not provisioned"), /* @__PURE__ */ React11.createElement(Typography11, { color: "text.secondary", paragraph: true }, "Your Backstage account is not linked to a LiteLLM user."), hint ? /* @__PURE__ */ React11.createElement(Typography11, { variant: "body2", color: "text.secondary" }, hint) : /* @__PURE__ */ React11.createElement(Typography11, { 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.")));
|
|
3699
4092
|
}
|
|
3700
|
-
const pageTabs = /* @__PURE__ */
|
|
4093
|
+
const pageTabs = /* @__PURE__ */ React11.createElement(
|
|
3701
4094
|
Tabs2,
|
|
3702
4095
|
{
|
|
3703
4096
|
value: activeTab,
|
|
@@ -3710,13 +4103,13 @@ var init_LiteLLMPage = __esm({
|
|
|
3710
4103
|
'& [class*="MuiTab-root"]': { minHeight: 44, textTransform: "none", fontSize: 14 }
|
|
3711
4104
|
}
|
|
3712
4105
|
},
|
|
3713
|
-
/* @__PURE__ */
|
|
3714
|
-
/* @__PURE__ */
|
|
3715
|
-
/* @__PURE__ */
|
|
3716
|
-
/* @__PURE__ */
|
|
3717
|
-
userInfo.can_view_audit && /* @__PURE__ */
|
|
4106
|
+
/* @__PURE__ */ React11.createElement(Tab2, { label: "Overview", value: "overview" }),
|
|
4107
|
+
/* @__PURE__ */ React11.createElement(Tab2, { label: "Keys", value: "keys" }),
|
|
4108
|
+
/* @__PURE__ */ React11.createElement(Tab2, { label: "Teams", value: "teams" }),
|
|
4109
|
+
/* @__PURE__ */ React11.createElement(Tab2, { label: "Models", value: "models" }),
|
|
4110
|
+
userInfo.can_view_audit && /* @__PURE__ */ React11.createElement(Tab2, { label: "Audit Log", value: "audit" })
|
|
3718
4111
|
);
|
|
3719
|
-
return /* @__PURE__ */
|
|
4112
|
+
return /* @__PURE__ */ React11.createElement(Box11, { sx: { p: 3, display: "flex", flexDirection: "column", gap: 2 } }, /* @__PURE__ */ React11.createElement(
|
|
3720
4113
|
DashboardHeader,
|
|
3721
4114
|
{
|
|
3722
4115
|
userInfo,
|
|
@@ -3726,7 +4119,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3726
4119
|
onGenerateKeyClick: () => setGenerateDialogOpen(true),
|
|
3727
4120
|
tabs: pageTabs
|
|
3728
4121
|
}
|
|
3729
|
-
), activeTab === "overview" && /* @__PURE__ */
|
|
4122
|
+
), activeTab === "overview" && /* @__PURE__ */ React11.createElement(Grid2, { container: true, spacing: 2, alignItems: "flex-start" }, /* @__PURE__ */ React11.createElement(Grid2, { item: true, xs: 12, lg: 8 }, /* @__PURE__ */ React11.createElement(
|
|
3730
4123
|
UsageStats,
|
|
3731
4124
|
{
|
|
3732
4125
|
usage: usage ?? null,
|
|
@@ -3737,7 +4130,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3737
4130
|
loading: usageLoading,
|
|
3738
4131
|
userInfo
|
|
3739
4132
|
}
|
|
3740
|
-
), activeTab === "keys" && /* @__PURE__ */
|
|
4133
|
+
)), /* @__PURE__ */ React11.createElement(Grid2, { item: true, xs: 12, lg: 4 }, /* @__PURE__ */ React11.createElement(LiteLLMBudgetWidget, { compact: true, collapsible: true }))), activeTab === "keys" && /* @__PURE__ */ React11.createElement(
|
|
3741
4134
|
KeysTable,
|
|
3742
4135
|
{
|
|
3743
4136
|
keys: keys ?? [],
|
|
@@ -3756,7 +4149,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3756
4149
|
for (const t of managedTeams ?? []) teamsById.set(t.team_id, t);
|
|
3757
4150
|
for (const t of teams ?? []) teamsById.set(t.team_id, t);
|
|
3758
4151
|
const visibleTeams = Array.from(teamsById.values());
|
|
3759
|
-
return /* @__PURE__ */
|
|
4152
|
+
return /* @__PURE__ */ React11.createElement(
|
|
3760
4153
|
TeamUsage,
|
|
3761
4154
|
{
|
|
3762
4155
|
teams: visibleTeams,
|
|
@@ -3772,14 +4165,14 @@ var init_LiteLLMPage = __esm({
|
|
|
3772
4165
|
onCreateTeam: () => setManageTeam({ mode: "create" })
|
|
3773
4166
|
}
|
|
3774
4167
|
);
|
|
3775
|
-
})(), activeTab === "models" && /* @__PURE__ */
|
|
4168
|
+
})(), activeTab === "models" && /* @__PURE__ */ React11.createElement(
|
|
3776
4169
|
ModelsTable,
|
|
3777
4170
|
{
|
|
3778
4171
|
allModels: allModels ?? [],
|
|
3779
4172
|
teams: teams ?? [],
|
|
3780
4173
|
loading: modelsLoading
|
|
3781
4174
|
}
|
|
3782
|
-
), activeTab === "audit" && userInfo.can_view_audit && /* @__PURE__ */
|
|
4175
|
+
), activeTab === "audit" && userInfo.can_view_audit && /* @__PURE__ */ React11.createElement(AuditLog, { api }), /* @__PURE__ */ React11.createElement(
|
|
3783
4176
|
GenerateKeyDialog,
|
|
3784
4177
|
{
|
|
3785
4178
|
open: generateDialogOpen,
|
|
@@ -3792,7 +4185,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3792
4185
|
onGenerateKey: handleGenerateKey,
|
|
3793
4186
|
onGetConfig: () => api.getConfig()
|
|
3794
4187
|
}
|
|
3795
|
-
), /* @__PURE__ */
|
|
4188
|
+
), /* @__PURE__ */ React11.createElement(
|
|
3796
4189
|
ManageTeamDialog,
|
|
3797
4190
|
{
|
|
3798
4191
|
open: !!manageTeam,
|
|
@@ -3854,7 +4247,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3854
4247
|
refreshTeams();
|
|
3855
4248
|
}
|
|
3856
4249
|
}
|
|
3857
|
-
), /* @__PURE__ */
|
|
4250
|
+
), /* @__PURE__ */ React11.createElement(
|
|
3858
4251
|
Snackbar2,
|
|
3859
4252
|
{
|
|
3860
4253
|
open: !!snackbar,
|
|
@@ -3862,7 +4255,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3862
4255
|
onClose: () => setSnackbar(null),
|
|
3863
4256
|
anchorOrigin: { vertical: "bottom", horizontal: "right" }
|
|
3864
4257
|
},
|
|
3865
|
-
snackbar ? /* @__PURE__ */
|
|
4258
|
+
snackbar ? /* @__PURE__ */ React11.createElement(Alert6, { severity: snackbar.severity, onClose: () => setSnackbar(null) }, snackbar.message) : void 0
|
|
3866
4259
|
));
|
|
3867
4260
|
};
|
|
3868
4261
|
}
|
|
@@ -3870,7 +4263,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3870
4263
|
|
|
3871
4264
|
// src/plugin.tsx
|
|
3872
4265
|
init_api();
|
|
3873
|
-
import
|
|
4266
|
+
import React12 from "react";
|
|
3874
4267
|
import { TrendingUp as TrendingUpIcon } from "@mui/icons-material";
|
|
3875
4268
|
import {
|
|
3876
4269
|
createFrontendPlugin,
|
|
@@ -3889,10 +4282,10 @@ var liteLlmPage = PageBlueprint.make({
|
|
|
3889
4282
|
params: {
|
|
3890
4283
|
path: "/litellm",
|
|
3891
4284
|
title: "LiteLLM",
|
|
3892
|
-
icon: /* @__PURE__ */
|
|
4285
|
+
icon: /* @__PURE__ */ React12.createElement(TrendingUpIcon, null),
|
|
3893
4286
|
loader: async () => {
|
|
3894
4287
|
const { LiteLLMPage: LiteLLMPage2 } = await Promise.resolve().then(() => (init_LiteLLMPage(), LiteLLMPage_exports));
|
|
3895
|
-
return /* @__PURE__ */
|
|
4288
|
+
return /* @__PURE__ */ React12.createElement(LiteLLMPage2, null);
|
|
3896
4289
|
}
|
|
3897
4290
|
}
|
|
3898
4291
|
});
|
|
@@ -3911,18 +4304,18 @@ init_TeamUsage();
|
|
|
3911
4304
|
// src/components/LiteLLMHomeWidget.tsx
|
|
3912
4305
|
init_api();
|
|
3913
4306
|
init_format();
|
|
3914
|
-
import
|
|
3915
|
-
import
|
|
3916
|
-
import
|
|
3917
|
-
import
|
|
4307
|
+
import React13, { useState as useState10, useEffect as useEffect4 } from "react";
|
|
4308
|
+
import Paper8 from "@mui/material/Paper";
|
|
4309
|
+
import Box12 from "@mui/material/Box";
|
|
4310
|
+
import Typography12 from "@mui/material/Typography";
|
|
3918
4311
|
import FormControl2 from "@mui/material/FormControl";
|
|
3919
4312
|
import Select2 from "@mui/material/Select";
|
|
3920
4313
|
import MenuItem6 from "@mui/material/MenuItem";
|
|
3921
|
-
import
|
|
3922
|
-
import
|
|
3923
|
-
import
|
|
4314
|
+
import Grid3 from "@mui/material/Grid";
|
|
4315
|
+
import CircularProgress7 from "@mui/material/CircularProgress";
|
|
4316
|
+
import Alert7 from "@mui/material/Alert";
|
|
3924
4317
|
import { AreaChart as AreaChart3, Area as Area3, ResponsiveContainer as ResponsiveContainer3 } from "recharts";
|
|
3925
|
-
import { useApi as
|
|
4318
|
+
import { useApi as useApi4 } from "@backstage/core-plugin-api";
|
|
3926
4319
|
function presetToDateRange(preset) {
|
|
3927
4320
|
const end = /* @__PURE__ */ new Date();
|
|
3928
4321
|
const start = /* @__PURE__ */ new Date();
|
|
@@ -3935,19 +4328,19 @@ function presetToDateRange(preset) {
|
|
|
3935
4328
|
}
|
|
3936
4329
|
return { start, end };
|
|
3937
4330
|
}
|
|
3938
|
-
var Kpi = ({ label, value }) => /* @__PURE__ */
|
|
4331
|
+
var Kpi = ({ label, value }) => /* @__PURE__ */ React13.createElement(Box12, null, /* @__PURE__ */ React13.createElement(Typography12, { variant: "caption", color: "text.secondary", display: "block" }, label), /* @__PURE__ */ React13.createElement(Typography12, { variant: "subtitle1", fontWeight: 600 }, value));
|
|
3939
4332
|
var LiteLLMHomeWidget = ({
|
|
3940
4333
|
defaultPeriod = "7d",
|
|
3941
4334
|
title = "LiteLLM Usage"
|
|
3942
4335
|
}) => {
|
|
3943
|
-
const api =
|
|
3944
|
-
const [period, setPeriod] =
|
|
3945
|
-
const [loading, setLoading] =
|
|
3946
|
-
const [usageError, setUsageError] =
|
|
3947
|
-
const [keysError, setKeysError] =
|
|
3948
|
-
const [usage, setUsage] =
|
|
3949
|
-
const [keys, setKeys] =
|
|
3950
|
-
|
|
4336
|
+
const api = useApi4(liteLlmApiRef);
|
|
4337
|
+
const [period, setPeriod] = useState10(defaultPeriod);
|
|
4338
|
+
const [loading, setLoading] = useState10(true);
|
|
4339
|
+
const [usageError, setUsageError] = useState10(null);
|
|
4340
|
+
const [keysError, setKeysError] = useState10(null);
|
|
4341
|
+
const [usage, setUsage] = useState10(null);
|
|
4342
|
+
const [keys, setKeys] = useState10([]);
|
|
4343
|
+
useEffect4(() => {
|
|
3951
4344
|
let cancelled = false;
|
|
3952
4345
|
setLoading(true);
|
|
3953
4346
|
setUsageError(null);
|
|
@@ -3984,17 +4377,17 @@ var LiteLLMHomeWidget = ({
|
|
|
3984
4377
|
spend: d.spend
|
|
3985
4378
|
}));
|
|
3986
4379
|
const hasSparkline = dailyData.length > 0;
|
|
3987
|
-
return /* @__PURE__ */
|
|
4380
|
+
return /* @__PURE__ */ React13.createElement(Paper8, { sx: { p: 2 } }, /* @__PURE__ */ React13.createElement(Box12, { display: "flex", justifyContent: "space-between", alignItems: "center", mb: 1.5 }, /* @__PURE__ */ React13.createElement(Typography12, { variant: "h6" }, title), /* @__PURE__ */ React13.createElement(FormControl2, { size: "small", sx: { minWidth: 90 } }, /* @__PURE__ */ React13.createElement(
|
|
3988
4381
|
Select2,
|
|
3989
4382
|
{
|
|
3990
4383
|
value: period,
|
|
3991
4384
|
onChange: (e) => setPeriod(e.target.value),
|
|
3992
4385
|
displayEmpty: true
|
|
3993
4386
|
},
|
|
3994
|
-
/* @__PURE__ */
|
|
3995
|
-
/* @__PURE__ */
|
|
3996
|
-
/* @__PURE__ */
|
|
3997
|
-
))), loading && /* @__PURE__ */
|
|
4387
|
+
/* @__PURE__ */ React13.createElement(MenuItem6, { value: "today" }, "Today"),
|
|
4388
|
+
/* @__PURE__ */ React13.createElement(MenuItem6, { value: "7d" }, "7d"),
|
|
4389
|
+
/* @__PURE__ */ React13.createElement(MenuItem6, { value: "30d" }, "30d")
|
|
4390
|
+
))), loading && /* @__PURE__ */ React13.createElement(Box12, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: 120 }, /* @__PURE__ */ React13.createElement(CircularProgress7, { size: 32 })), !loading && totalFailure && /* @__PURE__ */ React13.createElement(Alert7, { severity: "error", sx: { mt: 1 } }, usageError ?? "Failed to load usage data"), !loading && !totalFailure && /* @__PURE__ */ React13.createElement(React13.Fragment, null, partialFailure && /* @__PURE__ */ React13.createElement(Alert7, { severity: "warning", sx: { mt: 1, mb: 1 } }, usageError ? `Usage data unavailable (${usageError}).` : "", keysError ? ` Key list unavailable (${keysError}).` : "", " Showing what loaded."), /* @__PURE__ */ React13.createElement(Grid3, { container: true, spacing: 2, sx: { mb: hasSparkline ? 1.5 : 0 } }, /* @__PURE__ */ React13.createElement(Grid3, { item: true, xs: 6 }, /* @__PURE__ */ React13.createElement(Kpi, { label: "USD Spent", value: fmtUsd(usage?.total_spend ?? 0) })), /* @__PURE__ */ React13.createElement(Grid3, { item: true, xs: 6 }, /* @__PURE__ */ React13.createElement(Kpi, { label: "Tokens In", value: fmtInt(usage?.prompt_tokens ?? 0) })), /* @__PURE__ */ React13.createElement(Grid3, { item: true, xs: 6 }, /* @__PURE__ */ React13.createElement(Kpi, { label: "Tokens Out", value: fmtInt(usage?.completion_tokens ?? 0) })), /* @__PURE__ */ React13.createElement(Grid3, { item: true, xs: 6 }, /* @__PURE__ */ React13.createElement(Kpi, { label: "Keys", value: fmtInt(keys.length) }))), hasSparkline && /* @__PURE__ */ React13.createElement(Box12, { height: 120 }, /* @__PURE__ */ React13.createElement(ResponsiveContainer3, { width: "100%", height: "100%" }, /* @__PURE__ */ React13.createElement(AreaChart3, { data: dailyData, margin: { top: 4, right: 0, bottom: 0, left: 0 } }, /* @__PURE__ */ React13.createElement(
|
|
3998
4391
|
Area3,
|
|
3999
4392
|
{
|
|
4000
4393
|
type: "monotone",
|
|
@@ -4008,281 +4401,8 @@ var LiteLLMHomeWidget = ({
|
|
|
4008
4401
|
))))));
|
|
4009
4402
|
};
|
|
4010
4403
|
|
|
4011
|
-
// src/components/LiteLLMBudgetWidget.tsx
|
|
4012
|
-
init_api();
|
|
4013
|
-
init_format();
|
|
4014
|
-
init_ui();
|
|
4015
|
-
import React13, { useEffect as useEffect4, useMemo as useMemo8, useState as useState10 } from "react";
|
|
4016
|
-
import Paper8 from "@mui/material/Paper";
|
|
4017
|
-
import Box12 from "@mui/material/Box";
|
|
4018
|
-
import Typography12 from "@mui/material/Typography";
|
|
4019
|
-
import CircularProgress7 from "@mui/material/CircularProgress";
|
|
4020
|
-
import Alert7 from "@mui/material/Alert";
|
|
4021
|
-
import { alpha as alpha7 } from "@mui/material/styles";
|
|
4022
|
-
import { useApi as useApi4 } from "@backstage/core-plugin-api";
|
|
4023
|
-
|
|
4024
|
-
// src/budget.ts
|
|
4025
|
-
function limitPct(spend, budget) {
|
|
4026
|
-
if (budget <= 0) return 0;
|
|
4027
|
-
return spend / budget * 100;
|
|
4028
|
-
}
|
|
4029
|
-
function buildBudgetSummary(user, teams, keys, maxKeys = 3) {
|
|
4030
|
-
const budgeted = keys.filter((k) => (k.max_budget ?? 0) > 0).map((k) => {
|
|
4031
|
-
const spend = k.spend ?? 0;
|
|
4032
|
-
return {
|
|
4033
|
-
kind: "key",
|
|
4034
|
-
label: k.key_alias || k.key || "Untitled key",
|
|
4035
|
-
sublabel: k.key,
|
|
4036
|
-
budget: k.max_budget,
|
|
4037
|
-
spend,
|
|
4038
|
-
budgetDuration: k.budget_duration,
|
|
4039
|
-
pct: limitPct(spend, k.max_budget)
|
|
4040
|
-
};
|
|
4041
|
-
}).sort((a, b) => b.pct - a.pct);
|
|
4042
|
-
const userBudget = user && (user.max_budget ?? user.hard_limit ?? 0) > 0 ? (() => {
|
|
4043
|
-
const budget = user.max_budget ?? user.hard_limit ?? 0;
|
|
4044
|
-
const spend = user.current_spend ?? user.spend ?? 0;
|
|
4045
|
-
return {
|
|
4046
|
-
kind: "user",
|
|
4047
|
-
label: "Personal budget",
|
|
4048
|
-
sublabel: user.user_email || user.user_id,
|
|
4049
|
-
budget,
|
|
4050
|
-
spend,
|
|
4051
|
-
budgetDuration: user.budget_duration,
|
|
4052
|
-
softLimit: user.soft_limit,
|
|
4053
|
-
pct: limitPct(spend, budget)
|
|
4054
|
-
};
|
|
4055
|
-
})() : void 0;
|
|
4056
|
-
const teamLimits = teams.filter((t) => (t.max_budget ?? 0) > 0).map((t) => {
|
|
4057
|
-
const spend = t.spend ?? 0;
|
|
4058
|
-
return {
|
|
4059
|
-
kind: "team",
|
|
4060
|
-
label: t.team_alias || "Untitled team",
|
|
4061
|
-
sublabel: t.team_id,
|
|
4062
|
-
budget: t.max_budget,
|
|
4063
|
-
spend,
|
|
4064
|
-
budgetDuration: t.budget_duration,
|
|
4065
|
-
pct: limitPct(spend, t.max_budget)
|
|
4066
|
-
};
|
|
4067
|
-
}).sort((a, b) => b.pct - a.pct);
|
|
4068
|
-
return {
|
|
4069
|
-
keys: budgeted.slice(0, maxKeys),
|
|
4070
|
-
hiddenBudgetedKeys: Math.max(0, budgeted.length - maxKeys),
|
|
4071
|
-
totalKeyCount: keys.length,
|
|
4072
|
-
user: userBudget,
|
|
4073
|
-
teams: teamLimits
|
|
4074
|
-
};
|
|
4075
|
-
}
|
|
4076
|
-
function fmtBudgetDuration(duration) {
|
|
4077
|
-
if (!duration) return null;
|
|
4078
|
-
const m = /^(\d+)(s|m|h|d)$/.exec(duration);
|
|
4079
|
-
if (!m) return duration;
|
|
4080
|
-
const n = Number(m[1]);
|
|
4081
|
-
const unit = m[2];
|
|
4082
|
-
if (unit === "d") return n === 1 ? "daily" : `every ${n} days`;
|
|
4083
|
-
if (unit === "h") return n === 1 ? "hourly" : `every ${n} hours`;
|
|
4084
|
-
if (unit === "m") return n === 1 ? "every minute" : `every ${n} minutes`;
|
|
4085
|
-
return n === 1 ? "every second" : `every ${n} seconds`;
|
|
4086
|
-
}
|
|
4087
|
-
function budgetTone3(pct) {
|
|
4088
|
-
if (pct >= 100) return "danger";
|
|
4089
|
-
if (pct >= 80) return "warning";
|
|
4090
|
-
return "accent";
|
|
4091
|
-
}
|
|
4092
|
-
|
|
4093
|
-
// src/components/LiteLLMBudgetWidget.tsx
|
|
4094
|
-
function levelToneColor(tone, theme) {
|
|
4095
|
-
switch (tone) {
|
|
4096
|
-
case "success":
|
|
4097
|
-
return theme.palette.success.main;
|
|
4098
|
-
case "warning":
|
|
4099
|
-
return theme.palette.warning.main;
|
|
4100
|
-
case "danger":
|
|
4101
|
-
return theme.palette.error.main;
|
|
4102
|
-
case "info":
|
|
4103
|
-
return theme.palette.info.main;
|
|
4104
|
-
default:
|
|
4105
|
-
return theme.palette.primary.main;
|
|
4106
|
-
}
|
|
4107
|
-
}
|
|
4108
|
-
var LevelFrame = ({
|
|
4109
|
-
rank,
|
|
4110
|
-
name,
|
|
4111
|
-
tagline,
|
|
4112
|
-
note,
|
|
4113
|
-
tone,
|
|
4114
|
-
isLast,
|
|
4115
|
-
children
|
|
4116
|
-
}) => /* @__PURE__ */ React13.createElement(Box12, { sx: { display: "flex", gap: 1.5 } }, /* @__PURE__ */ React13.createElement(Box12, { sx: { display: "flex", flexDirection: "column", alignItems: "center", width: 26, flexShrink: 0 } }, /* @__PURE__ */ React13.createElement(
|
|
4117
|
-
Box12,
|
|
4118
|
-
{
|
|
4119
|
-
sx: (theme) => ({
|
|
4120
|
-
width: 26,
|
|
4121
|
-
height: 26,
|
|
4122
|
-
borderRadius: "50%",
|
|
4123
|
-
display: "flex",
|
|
4124
|
-
alignItems: "center",
|
|
4125
|
-
justifyContent: "center",
|
|
4126
|
-
bgcolor: alpha7(levelToneColor(tone, theme), 0.14),
|
|
4127
|
-
color: levelToneColor(tone, theme),
|
|
4128
|
-
fontSize: 12,
|
|
4129
|
-
fontWeight: 700,
|
|
4130
|
-
flexShrink: 0
|
|
4131
|
-
})
|
|
4132
|
-
},
|
|
4133
|
-
rank
|
|
4134
|
-
), /* @__PURE__ */ React13.createElement(
|
|
4135
|
-
Box12,
|
|
4136
|
-
{
|
|
4137
|
-
sx: (theme) => ({
|
|
4138
|
-
flex: 1,
|
|
4139
|
-
width: 2,
|
|
4140
|
-
minHeight: 14,
|
|
4141
|
-
bgcolor: isLast ? "transparent" : alpha7(theme.palette.text.primary, 0.12)
|
|
4142
|
-
})
|
|
4143
|
-
}
|
|
4144
|
-
)), /* @__PURE__ */ React13.createElement(Box12, { sx: { flex: 1, minWidth: 0, pb: isLast ? 0 : 2 } }, /* @__PURE__ */ React13.createElement(Box12, { sx: { display: "flex", alignItems: "baseline", gap: 1, flexWrap: "wrap", mb: 0.75 } }, /* @__PURE__ */ React13.createElement(
|
|
4145
|
-
Typography12,
|
|
4146
|
-
{
|
|
4147
|
-
sx: (theme) => ({
|
|
4148
|
-
fontSize: 12.5,
|
|
4149
|
-
fontWeight: 700,
|
|
4150
|
-
letterSpacing: "0.08em",
|
|
4151
|
-
textTransform: "uppercase",
|
|
4152
|
-
color: levelToneColor(tone, theme)
|
|
4153
|
-
})
|
|
4154
|
-
},
|
|
4155
|
-
name
|
|
4156
|
-
), /* @__PURE__ */ React13.createElement(Typography12, { variant: "caption", color: "text.secondary", sx: { fontSize: 11.5 } }, tagline), note && /* @__PURE__ */ React13.createElement(StatusPill, { label: note, tone: "neutral", dot: false, sx: { ml: "auto" } })), /* @__PURE__ */ React13.createElement(Box12, { sx: { display: "flex", flexDirection: "column", gap: 1 } }, children)));
|
|
4157
|
-
var EmptyLimitCard = ({ children }) => /* @__PURE__ */ React13.createElement(
|
|
4158
|
-
Paper8,
|
|
4159
|
-
{
|
|
4160
|
-
variant: "outlined",
|
|
4161
|
-
sx: (theme) => ({
|
|
4162
|
-
px: 1.5,
|
|
4163
|
-
py: 1.25,
|
|
4164
|
-
borderRadius: 1.5,
|
|
4165
|
-
bgcolor: alpha7(theme.palette.text.primary, theme.palette.mode === "dark" ? 0.03 : 0.015)
|
|
4166
|
-
})
|
|
4167
|
-
},
|
|
4168
|
-
/* @__PURE__ */ React13.createElement(Typography12, { variant: "body2", color: "text.secondary", sx: { fontSize: 13 } }, children)
|
|
4169
|
-
);
|
|
4170
|
-
var LimitCard = ({ limit }) => {
|
|
4171
|
-
const tone = budgetTone3(limit.pct);
|
|
4172
|
-
const pct = Math.round(limit.pct);
|
|
4173
|
-
const closeTo = limit.softLimit !== void 0 && limit.spend >= limit.softLimit;
|
|
4174
|
-
return /* @__PURE__ */ React13.createElement(Paper8, { variant: "outlined", sx: { px: 1.5, py: 1.25, borderRadius: 1.5 } }, /* @__PURE__ */ React13.createElement(Box12, { sx: { display: "flex", alignItems: "baseline", justifyContent: "space-between", gap: 1.5 } }, /* @__PURE__ */ React13.createElement(Box12, { sx: { minWidth: 0, flex: 1 } }, /* @__PURE__ */ React13.createElement(
|
|
4175
|
-
Typography12,
|
|
4176
|
-
{
|
|
4177
|
-
variant: "body2",
|
|
4178
|
-
sx: { fontWeight: 600, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" },
|
|
4179
|
-
title: limit.sublabel ? `${limit.label} \xB7 ${limit.sublabel}` : limit.label
|
|
4180
|
-
},
|
|
4181
|
-
limit.label
|
|
4182
|
-
), limit.sublabel && /* @__PURE__ */ React13.createElement(
|
|
4183
|
-
Typography12,
|
|
4184
|
-
{
|
|
4185
|
-
variant: "caption",
|
|
4186
|
-
color: "text.secondary",
|
|
4187
|
-
sx: {
|
|
4188
|
-
fontSize: 10.5,
|
|
4189
|
-
fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
|
|
4190
|
-
display: "block",
|
|
4191
|
-
overflow: "hidden",
|
|
4192
|
-
textOverflow: "ellipsis",
|
|
4193
|
-
whiteSpace: "nowrap"
|
|
4194
|
-
}
|
|
4195
|
-
},
|
|
4196
|
-
limit.sublabel
|
|
4197
|
-
)), /* @__PURE__ */ React13.createElement(Typography12, { variant: "body2", sx: { fontWeight: 600, fontVariantNumeric: "tabular-nums", whiteSpace: "nowrap" } }, fmtUsd(limit.spend), /* @__PURE__ */ React13.createElement(Box12, { component: "span", color: "text.secondary", sx: { fontWeight: 400 } }, " / ", fmtUsd(limit.budget)))), /* @__PURE__ */ React13.createElement(Box12, { sx: { mt: 1 } }, /* @__PURE__ */ React13.createElement(Meter, { value: limit.pct, tone, height: 6 })), /* @__PURE__ */ React13.createElement(
|
|
4198
|
-
Box12,
|
|
4199
|
-
{
|
|
4200
|
-
sx: {
|
|
4201
|
-
display: "flex",
|
|
4202
|
-
justifyContent: "space-between",
|
|
4203
|
-
alignItems: "center",
|
|
4204
|
-
gap: 1,
|
|
4205
|
-
mt: 0.75
|
|
4206
|
-
}
|
|
4207
|
-
},
|
|
4208
|
-
/* @__PURE__ */ React13.createElement(Typography12, { variant: "caption", color: "text.secondary", sx: { fontVariantNumeric: "tabular-nums" } }, pct, "% of budget", closeTo && ` \xB7 soft-limit warning`),
|
|
4209
|
-
/* @__PURE__ */ React13.createElement(Typography12, { variant: "caption", color: "text.secondary" }, fmtBudgetDuration(limit.budgetDuration) ? `resets ${fmtBudgetDuration(limit.budgetDuration)}` : "never resets")
|
|
4210
|
-
));
|
|
4211
|
-
};
|
|
4212
|
-
var LiteLLMBudgetWidget = ({
|
|
4213
|
-
title = "Budget Policy",
|
|
4214
|
-
maxKeys = 3
|
|
4215
|
-
}) => {
|
|
4216
|
-
const api = useApi4(liteLlmApiRef);
|
|
4217
|
-
const [loading, setLoading] = useState10(true);
|
|
4218
|
-
const [error, setError] = useState10(null);
|
|
4219
|
-
const [user, setUser] = useState10(null);
|
|
4220
|
-
const [teams, setTeams] = useState10([]);
|
|
4221
|
-
const [keys, setKeys] = useState10([]);
|
|
4222
|
-
useEffect4(() => {
|
|
4223
|
-
let cancelled = false;
|
|
4224
|
-
setLoading(true);
|
|
4225
|
-
setError(null);
|
|
4226
|
-
Promise.allSettled([api.getUserInfo(), api.getTeams(), api.listKeys()]).then(
|
|
4227
|
-
([userResult, teamsResult, keysResult]) => {
|
|
4228
|
-
if (cancelled) return;
|
|
4229
|
-
if (userResult.status === "fulfilled") {
|
|
4230
|
-
setUser(userResult.value);
|
|
4231
|
-
} else {
|
|
4232
|
-
setUser(null);
|
|
4233
|
-
setError(
|
|
4234
|
-
userResult.reason?.message ?? "Failed to load your LiteLLM profile"
|
|
4235
|
-
);
|
|
4236
|
-
}
|
|
4237
|
-
setTeams(teamsResult.status === "fulfilled" ? teamsResult.value : []);
|
|
4238
|
-
setKeys(keysResult.status === "fulfilled" ? keysResult.value : []);
|
|
4239
|
-
setLoading(false);
|
|
4240
|
-
}
|
|
4241
|
-
);
|
|
4242
|
-
return () => {
|
|
4243
|
-
cancelled = true;
|
|
4244
|
-
};
|
|
4245
|
-
}, [api]);
|
|
4246
|
-
const hasAnyLimit = !!user && (user.max_budget ?? user.hard_limit ?? 0) > 0;
|
|
4247
|
-
const summary = useMemo8(() => buildBudgetSummary(user, teams, keys, maxKeys), [user, teams, keys, maxKeys]);
|
|
4248
|
-
const hasBudgetedKeys = keys.some((k) => (k.max_budget ?? 0) > 0);
|
|
4249
|
-
return /* @__PURE__ */ React13.createElement(Paper8, { sx: { p: 2 } }, /* @__PURE__ */ React13.createElement(Box12, { sx: { mb: 1.5 } }, /* @__PURE__ */ React13.createElement(Typography12, { variant: "h6", lineHeight: 1.2 }, title), /* @__PURE__ */ React13.createElement(Typography12, { variant: "caption", color: "text.secondary" }, "How LiteLLM caps spend \u2014 and where you stand right now")), loading && /* @__PURE__ */ React13.createElement(Box12, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: 140 }, /* @__PURE__ */ React13.createElement(CircularProgress7, { size: 32 })), !loading && error && /* @__PURE__ */ React13.createElement(Alert7, { severity: "error", sx: { mt: 0.5 } }, error), !loading && !error && /* @__PURE__ */ React13.createElement(React13.Fragment, null, /* @__PURE__ */ React13.createElement(
|
|
4250
|
-
LevelFrame,
|
|
4251
|
-
{
|
|
4252
|
-
rank: 1,
|
|
4253
|
-
name: "Key",
|
|
4254
|
-
tagline: "per-key cap \xB7 highest priority",
|
|
4255
|
-
note: hasBudgetedKeys ? void 0 : "no key budgets set"
|
|
4256
|
-
},
|
|
4257
|
-
!hasBudgetedKeys && /* @__PURE__ */ React13.createElement(EmptyLimitCard, null, "No key has a budget, so no key-level cap applies."),
|
|
4258
|
-
summary.keys.map((k) => /* @__PURE__ */ React13.createElement(LimitCard, { key: k.sublabel ?? k.label, limit: k })),
|
|
4259
|
-
summary.hiddenBudgetedKeys > 0 && /* @__PURE__ */ React13.createElement(Typography12, { variant: "caption", color: "text.secondary" }, "+", summary.hiddenBudgetedKeys, " more budgeted key", summary.hiddenBudgetedKeys > 1 ? "s" : "", " further from the cap")
|
|
4260
|
-
), /* @__PURE__ */ React13.createElement(
|
|
4261
|
-
LevelFrame,
|
|
4262
|
-
{
|
|
4263
|
-
rank: 2,
|
|
4264
|
-
name: "User",
|
|
4265
|
-
tagline: "cap on everything you spend with your own keys",
|
|
4266
|
-
note: "skipped for team-bound keys"
|
|
4267
|
-
},
|
|
4268
|
-
hasAnyLimit && summary.user ? /* @__PURE__ */ React13.createElement(LimitCard, { limit: summary.user }) : /* @__PURE__ */ React13.createElement(EmptyLimitCard, null, "No personal budget is set on your account.")
|
|
4269
|
-
), /* @__PURE__ */ React13.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__ */ React13.createElement(LimitCard, { key: t.sublabel ?? t.label, limit: t })) : /* @__PURE__ */ React13.createElement(EmptyLimitCard, null, "No team you belong to has a budget.")), /* @__PURE__ */ React13.createElement(LevelFrame, { rank: 4, name: "Global", tagline: "proxy-wide cap set by your admin", isLast: true }, /* @__PURE__ */ React13.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__ */ React13.createElement(
|
|
4270
|
-
Box12,
|
|
4271
|
-
{
|
|
4272
|
-
sx: (theme) => ({
|
|
4273
|
-
mt: 2,
|
|
4274
|
-
px: 1.5,
|
|
4275
|
-
py: 1.25,
|
|
4276
|
-
borderRadius: 1.5,
|
|
4277
|
-
border: "1px dashed",
|
|
4278
|
-
borderColor: alpha7(theme.palette.text.primary, 0.18)
|
|
4279
|
-
})
|
|
4280
|
-
},
|
|
4281
|
-
/* @__PURE__ */ React13.createElement(Typography12, { 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.")
|
|
4282
|
-
)));
|
|
4283
|
-
};
|
|
4284
|
-
|
|
4285
4404
|
// src/index.ts
|
|
4405
|
+
init_LiteLLMBudgetWidget();
|
|
4286
4406
|
init_GenerateKeyDialog();
|
|
4287
4407
|
init_ManageTeamDialog();
|
|
4288
4408
|
init_api();
|