@acarmisc/backstage-plugin-litellm 0.19.0 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/budget.d.ts +54 -0
- package/dist/budget.test.d.ts +1 -0
- package/dist/components/LiteLLMBudgetWidget.d.ts +44 -0
- package/dist/index.cjs.js +591 -207
- package/dist/index.cjs.js.map +4 -4
- package/dist/index.d.ts +2 -0
- package/dist/index.esm.js +570 -186
- package/dist/index.esm.js.map +4 -4
- package/dist/types.d.ts +3 -0
- package/package.json +1 -1
package/dist/index.esm.js
CHANGED
|
@@ -2749,11 +2749,391 @@ 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 pcts = [
|
|
2806
|
+
...summary.keys.map((k) => k.pct),
|
|
2807
|
+
...summary.user ? [summary.user.pct] : [],
|
|
2808
|
+
...summary.teams.map((t) => t.pct)
|
|
2809
|
+
];
|
|
2810
|
+
const count = summary.keys.length + summary.hiddenBudgetedKeys + (summary.user ? 1 : 0) + summary.teams.length;
|
|
2811
|
+
return {
|
|
2812
|
+
count,
|
|
2813
|
+
closestPct: pcts.length ? Math.max(...pcts) : null
|
|
2814
|
+
};
|
|
2815
|
+
}
|
|
2816
|
+
function fmtBudgetDuration(duration) {
|
|
2817
|
+
if (!duration) return null;
|
|
2818
|
+
const m = /^(\d+)(s|m|h|d)$/.exec(duration);
|
|
2819
|
+
if (!m) return duration;
|
|
2820
|
+
const n = Number(m[1]);
|
|
2821
|
+
const unit = m[2];
|
|
2822
|
+
if (unit === "d") return n === 1 ? "daily" : `every ${n} days`;
|
|
2823
|
+
if (unit === "h") return n === 1 ? "hourly" : `every ${n} hours`;
|
|
2824
|
+
if (unit === "m") return n === 1 ? "every minute" : `every ${n} minutes`;
|
|
2825
|
+
return n === 1 ? "every second" : `every ${n} seconds`;
|
|
2826
|
+
}
|
|
2827
|
+
function budgetTone2(pct) {
|
|
2828
|
+
if (pct >= 100) return "danger";
|
|
2829
|
+
if (pct >= 80) return "warning";
|
|
2830
|
+
return "accent";
|
|
2831
|
+
}
|
|
2832
|
+
var init_budget = __esm({
|
|
2833
|
+
"src/budget.ts"() {
|
|
2834
|
+
"use strict";
|
|
2835
|
+
}
|
|
2836
|
+
});
|
|
2837
|
+
|
|
2838
|
+
// src/components/LiteLLMBudgetWidget.tsx
|
|
2839
|
+
import React7, { useEffect as useEffect3, useMemo as useMemo6, useState as useState5 } from "react";
|
|
2754
2840
|
import Paper5 from "@mui/material/Paper";
|
|
2755
2841
|
import Box7 from "@mui/material/Box";
|
|
2756
2842
|
import Typography7 from "@mui/material/Typography";
|
|
2843
|
+
import CircularProgress4 from "@mui/material/CircularProgress";
|
|
2844
|
+
import Alert3 from "@mui/material/Alert";
|
|
2845
|
+
import Collapse from "@mui/material/Collapse";
|
|
2846
|
+
import IconButton4 from "@mui/material/IconButton";
|
|
2847
|
+
import { ExpandMore as ExpandMore2 } from "@mui/icons-material";
|
|
2848
|
+
import { alpha as alpha4 } from "@mui/material/styles";
|
|
2849
|
+
import { useApi as useApi2 } from "@backstage/core-plugin-api";
|
|
2850
|
+
function levelToneColor(tone, theme) {
|
|
2851
|
+
switch (tone) {
|
|
2852
|
+
case "success":
|
|
2853
|
+
return theme.palette.success.main;
|
|
2854
|
+
case "warning":
|
|
2855
|
+
return theme.palette.warning.main;
|
|
2856
|
+
case "danger":
|
|
2857
|
+
return theme.palette.error.main;
|
|
2858
|
+
case "info":
|
|
2859
|
+
return theme.palette.info.main;
|
|
2860
|
+
default:
|
|
2861
|
+
return theme.palette.primary.main;
|
|
2862
|
+
}
|
|
2863
|
+
}
|
|
2864
|
+
var LevelFrame, EmptyLimitCard, LimitCard, TaggedLimit, FullBody, CompactBody, LiteLLMBudgetWidget;
|
|
2865
|
+
var init_LiteLLMBudgetWidget = __esm({
|
|
2866
|
+
"src/components/LiteLLMBudgetWidget.tsx"() {
|
|
2867
|
+
"use strict";
|
|
2868
|
+
init_api();
|
|
2869
|
+
init_format();
|
|
2870
|
+
init_ui();
|
|
2871
|
+
init_budget();
|
|
2872
|
+
LevelFrame = ({
|
|
2873
|
+
rank,
|
|
2874
|
+
name,
|
|
2875
|
+
tagline,
|
|
2876
|
+
note,
|
|
2877
|
+
tone,
|
|
2878
|
+
isLast,
|
|
2879
|
+
children
|
|
2880
|
+
}) => /* @__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(
|
|
2881
|
+
Box7,
|
|
2882
|
+
{
|
|
2883
|
+
sx: (theme) => ({
|
|
2884
|
+
width: 26,
|
|
2885
|
+
height: 26,
|
|
2886
|
+
borderRadius: "50%",
|
|
2887
|
+
display: "flex",
|
|
2888
|
+
alignItems: "center",
|
|
2889
|
+
justifyContent: "center",
|
|
2890
|
+
bgcolor: alpha4(levelToneColor(tone, theme), 0.14),
|
|
2891
|
+
color: levelToneColor(tone, theme),
|
|
2892
|
+
fontSize: 12,
|
|
2893
|
+
fontWeight: 700,
|
|
2894
|
+
flexShrink: 0
|
|
2895
|
+
})
|
|
2896
|
+
},
|
|
2897
|
+
rank
|
|
2898
|
+
), /* @__PURE__ */ React7.createElement(
|
|
2899
|
+
Box7,
|
|
2900
|
+
{
|
|
2901
|
+
sx: (theme) => ({
|
|
2902
|
+
flex: 1,
|
|
2903
|
+
width: 2,
|
|
2904
|
+
minHeight: 14,
|
|
2905
|
+
bgcolor: isLast ? "transparent" : alpha4(theme.palette.text.primary, 0.12)
|
|
2906
|
+
})
|
|
2907
|
+
}
|
|
2908
|
+
)), /* @__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(
|
|
2909
|
+
Typography7,
|
|
2910
|
+
{
|
|
2911
|
+
sx: (theme) => ({
|
|
2912
|
+
fontSize: 12.5,
|
|
2913
|
+
fontWeight: 700,
|
|
2914
|
+
letterSpacing: "0.08em",
|
|
2915
|
+
textTransform: "uppercase",
|
|
2916
|
+
color: levelToneColor(tone, theme)
|
|
2917
|
+
})
|
|
2918
|
+
},
|
|
2919
|
+
name
|
|
2920
|
+
), /* @__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)));
|
|
2921
|
+
EmptyLimitCard = ({ children }) => /* @__PURE__ */ React7.createElement(
|
|
2922
|
+
Paper5,
|
|
2923
|
+
{
|
|
2924
|
+
variant: "outlined",
|
|
2925
|
+
sx: (theme) => ({
|
|
2926
|
+
px: 1.5,
|
|
2927
|
+
py: 1.25,
|
|
2928
|
+
borderRadius: 1.5,
|
|
2929
|
+
bgcolor: alpha4(theme.palette.text.primary, theme.palette.mode === "dark" ? 0.03 : 0.015)
|
|
2930
|
+
})
|
|
2931
|
+
},
|
|
2932
|
+
/* @__PURE__ */ React7.createElement(Typography7, { variant: "body2", color: "text.secondary", sx: { fontSize: 13 } }, children)
|
|
2933
|
+
);
|
|
2934
|
+
LimitCard = ({ limit }) => {
|
|
2935
|
+
const tone = budgetTone2(limit.pct);
|
|
2936
|
+
const pct = Math.round(limit.pct);
|
|
2937
|
+
const closeTo = limit.softLimit !== void 0 && limit.spend >= limit.softLimit;
|
|
2938
|
+
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(
|
|
2939
|
+
Typography7,
|
|
2940
|
+
{
|
|
2941
|
+
variant: "body2",
|
|
2942
|
+
sx: { fontWeight: 600, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" },
|
|
2943
|
+
title: limit.sublabel ? `${limit.label} \xB7 ${limit.sublabel}` : limit.label
|
|
2944
|
+
},
|
|
2945
|
+
limit.label
|
|
2946
|
+
), limit.sublabel && /* @__PURE__ */ React7.createElement(
|
|
2947
|
+
Typography7,
|
|
2948
|
+
{
|
|
2949
|
+
variant: "caption",
|
|
2950
|
+
color: "text.secondary",
|
|
2951
|
+
sx: {
|
|
2952
|
+
fontSize: 10.5,
|
|
2953
|
+
fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
|
|
2954
|
+
display: "block",
|
|
2955
|
+
overflow: "hidden",
|
|
2956
|
+
textOverflow: "ellipsis",
|
|
2957
|
+
whiteSpace: "nowrap"
|
|
2958
|
+
}
|
|
2959
|
+
},
|
|
2960
|
+
limit.sublabel
|
|
2961
|
+
)), /* @__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(
|
|
2962
|
+
Box7,
|
|
2963
|
+
{
|
|
2964
|
+
sx: {
|
|
2965
|
+
display: "flex",
|
|
2966
|
+
justifyContent: "space-between",
|
|
2967
|
+
alignItems: "center",
|
|
2968
|
+
gap: 1,
|
|
2969
|
+
mt: 0.75
|
|
2970
|
+
}
|
|
2971
|
+
},
|
|
2972
|
+
/* @__PURE__ */ React7.createElement(Typography7, { variant: "caption", color: "text.secondary", sx: { fontVariantNumeric: "tabular-nums" } }, pct, "% of budget", closeTo && ` \xB7 soft-limit warning`),
|
|
2973
|
+
/* @__PURE__ */ React7.createElement(Typography7, { variant: "caption", color: "text.secondary" }, fmtBudgetDuration(limit.budgetDuration) ? `resets ${fmtBudgetDuration(limit.budgetDuration)}` : "never resets")
|
|
2974
|
+
));
|
|
2975
|
+
};
|
|
2976
|
+
TaggedLimit = ({ limit }) => {
|
|
2977
|
+
const tone = budgetTone2(limit.pct);
|
|
2978
|
+
return /* @__PURE__ */ React7.createElement(Box7, null, /* @__PURE__ */ React7.createElement(
|
|
2979
|
+
Typography7,
|
|
2980
|
+
{
|
|
2981
|
+
sx: (theme) => ({
|
|
2982
|
+
fontSize: 11,
|
|
2983
|
+
fontWeight: 700,
|
|
2984
|
+
letterSpacing: "0.08em",
|
|
2985
|
+
textTransform: "uppercase",
|
|
2986
|
+
color: tone === "accent" ? theme.palette.text.secondary : levelToneColor(tone, theme),
|
|
2987
|
+
mb: 0.5
|
|
2988
|
+
})
|
|
2989
|
+
},
|
|
2990
|
+
limit.kind
|
|
2991
|
+
), /* @__PURE__ */ React7.createElement(LimitCard, { limit }));
|
|
2992
|
+
};
|
|
2993
|
+
FullBody = ({ summary }) => {
|
|
2994
|
+
const hasBudgetedKeys = summary.keys.length + summary.hiddenBudgetedKeys > 0;
|
|
2995
|
+
return /* @__PURE__ */ React7.createElement(React7.Fragment, null, /* @__PURE__ */ React7.createElement(
|
|
2996
|
+
LevelFrame,
|
|
2997
|
+
{
|
|
2998
|
+
rank: 1,
|
|
2999
|
+
name: "Key",
|
|
3000
|
+
tagline: "per-key cap \xB7 highest priority",
|
|
3001
|
+
note: hasBudgetedKeys ? void 0 : "no key budgets set"
|
|
3002
|
+
},
|
|
3003
|
+
!hasBudgetedKeys && /* @__PURE__ */ React7.createElement(EmptyLimitCard, null, "No key has a budget, so no key-level cap applies."),
|
|
3004
|
+
summary.keys.map((k) => /* @__PURE__ */ React7.createElement(LimitCard, { key: k.sublabel ?? k.label, limit: k })),
|
|
3005
|
+
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")
|
|
3006
|
+
), /* @__PURE__ */ React7.createElement(
|
|
3007
|
+
LevelFrame,
|
|
3008
|
+
{
|
|
3009
|
+
rank: 2,
|
|
3010
|
+
name: "User",
|
|
3011
|
+
tagline: "cap on everything you spend with your own keys",
|
|
3012
|
+
note: "skipped for team-bound keys"
|
|
3013
|
+
},
|
|
3014
|
+
summary.user ? /* @__PURE__ */ React7.createElement(LimitCard, { limit: summary.user }) : /* @__PURE__ */ React7.createElement(EmptyLimitCard, null, "No personal budget is set on your account.")
|
|
3015
|
+
), /* @__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(
|
|
3016
|
+
Box7,
|
|
3017
|
+
{
|
|
3018
|
+
sx: (theme) => ({
|
|
3019
|
+
mt: 2,
|
|
3020
|
+
px: 1.5,
|
|
3021
|
+
py: 1.25,
|
|
3022
|
+
borderRadius: 1.5,
|
|
3023
|
+
border: "1px dashed",
|
|
3024
|
+
borderColor: alpha4(theme.palette.text.primary, 0.18)
|
|
3025
|
+
})
|
|
3026
|
+
},
|
|
3027
|
+
/* @__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.")
|
|
3028
|
+
));
|
|
3029
|
+
};
|
|
3030
|
+
CompactBody = ({ summary }) => {
|
|
3031
|
+
const { count } = budgetHeadline(summary);
|
|
3032
|
+
if (count === 0) {
|
|
3033
|
+
return /* @__PURE__ */ React7.createElement(Typography7, { variant: "body2", color: "text.secondary", sx: { fontSize: 13 } }, "No budget limits apply to you right now.");
|
|
3034
|
+
}
|
|
3035
|
+
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 })));
|
|
3036
|
+
};
|
|
3037
|
+
LiteLLMBudgetWidget = ({
|
|
3038
|
+
title = "Budget Policy",
|
|
3039
|
+
maxKeys = 3,
|
|
3040
|
+
compact = false,
|
|
3041
|
+
collapsible = false,
|
|
3042
|
+
defaultExpanded = true,
|
|
3043
|
+
action
|
|
3044
|
+
}) => {
|
|
3045
|
+
const api = useApi2(liteLlmApiRef);
|
|
3046
|
+
const [loading, setLoading] = useState5(true);
|
|
3047
|
+
const [error, setError] = useState5(null);
|
|
3048
|
+
const [user, setUser] = useState5(null);
|
|
3049
|
+
const [teams, setTeams] = useState5([]);
|
|
3050
|
+
const [keys, setKeys] = useState5([]);
|
|
3051
|
+
const [expanded, setExpanded] = useState5(defaultExpanded);
|
|
3052
|
+
useEffect3(() => {
|
|
3053
|
+
let cancelled = false;
|
|
3054
|
+
setLoading(true);
|
|
3055
|
+
setError(null);
|
|
3056
|
+
Promise.allSettled([api.getUserInfo(), api.getTeams(), api.listKeys()]).then(
|
|
3057
|
+
([userResult, teamsResult, keysResult]) => {
|
|
3058
|
+
if (cancelled) return;
|
|
3059
|
+
if (userResult.status === "fulfilled") {
|
|
3060
|
+
setUser(userResult.value);
|
|
3061
|
+
} else {
|
|
3062
|
+
setUser(null);
|
|
3063
|
+
setError(
|
|
3064
|
+
userResult.reason?.message ?? "Failed to load your LiteLLM profile"
|
|
3065
|
+
);
|
|
3066
|
+
}
|
|
3067
|
+
setTeams(teamsResult.status === "fulfilled" ? teamsResult.value : []);
|
|
3068
|
+
setKeys(keysResult.status === "fulfilled" ? keysResult.value : []);
|
|
3069
|
+
setLoading(false);
|
|
3070
|
+
}
|
|
3071
|
+
);
|
|
3072
|
+
return () => {
|
|
3073
|
+
cancelled = true;
|
|
3074
|
+
};
|
|
3075
|
+
}, [api]);
|
|
3076
|
+
const summary = useMemo6(
|
|
3077
|
+
() => buildBudgetSummary(user, teams, keys, maxKeys),
|
|
3078
|
+
[user, teams, keys, maxKeys]
|
|
3079
|
+
);
|
|
3080
|
+
const headline = useMemo6(() => budgetHeadline(summary), [summary]);
|
|
3081
|
+
const limitsWord = `${headline.count} limit${headline.count === 1 ? "" : "s"}`;
|
|
3082
|
+
const closestWord = headline.closestPct === null ? "" : ` \xB7 closest ${Math.round(headline.closestPct)}%`;
|
|
3083
|
+
const summaryText = headline.count === 0 ? "no limits apply" : `${limitsWord}${closestWord}`;
|
|
3084
|
+
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 })));
|
|
3085
|
+
return /* @__PURE__ */ React7.createElement(Paper5, { sx: { p: 2 } }, collapsible ? /* @__PURE__ */ React7.createElement(
|
|
3086
|
+
Box7,
|
|
3087
|
+
{
|
|
3088
|
+
onClick: () => setExpanded((e) => !e),
|
|
3089
|
+
sx: {
|
|
3090
|
+
display: "flex",
|
|
3091
|
+
alignItems: "center",
|
|
3092
|
+
gap: 1,
|
|
3093
|
+
cursor: "pointer",
|
|
3094
|
+
userSelect: "none",
|
|
3095
|
+
mb: expanded ? 1.5 : 0
|
|
3096
|
+
}
|
|
3097
|
+
},
|
|
3098
|
+
/* @__PURE__ */ React7.createElement(Box7, { sx: { minWidth: 0, flex: 1, display: "flex", alignItems: "center", gap: 1, flexWrap: "wrap" } }, /* @__PURE__ */ React7.createElement(Typography7, { variant: "h6", lineHeight: 1.2 }, title), !loading && !error && /* @__PURE__ */ React7.createElement(StatusPill, { label: summaryText, tone: budgetTone2(headline.closestPct ?? 0) })),
|
|
3099
|
+
/* @__PURE__ */ React7.createElement(
|
|
3100
|
+
IconButton4,
|
|
3101
|
+
{
|
|
3102
|
+
size: "small",
|
|
3103
|
+
"aria-label": expanded ? "Collapse budget policy" : "Expand budget policy",
|
|
3104
|
+
"aria-expanded": expanded,
|
|
3105
|
+
onClick: (e) => {
|
|
3106
|
+
e.stopPropagation();
|
|
3107
|
+
setExpanded((x) => !x);
|
|
3108
|
+
},
|
|
3109
|
+
sx: (theme) => ({
|
|
3110
|
+
color: theme.palette.text.secondary,
|
|
3111
|
+
transform: expanded ? "rotate(180deg)" : "none",
|
|
3112
|
+
transition: theme.transitions.create("transform")
|
|
3113
|
+
})
|
|
3114
|
+
},
|
|
3115
|
+
/* @__PURE__ */ React7.createElement(ExpandMore2, null)
|
|
3116
|
+
)
|
|
3117
|
+
) : /* @__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(
|
|
3118
|
+
Box7,
|
|
3119
|
+
{
|
|
3120
|
+
sx: (theme) => ({
|
|
3121
|
+
mt: 2,
|
|
3122
|
+
pt: 2,
|
|
3123
|
+
borderTop: `1px solid ${theme.palette.divider}`
|
|
3124
|
+
})
|
|
3125
|
+
},
|
|
3126
|
+
action
|
|
3127
|
+
));
|
|
3128
|
+
};
|
|
3129
|
+
}
|
|
3130
|
+
});
|
|
3131
|
+
|
|
3132
|
+
// src/components/TeamUsage.tsx
|
|
3133
|
+
import React8, { useState as useState6 } from "react";
|
|
3134
|
+
import Paper6 from "@mui/material/Paper";
|
|
3135
|
+
import Box8 from "@mui/material/Box";
|
|
3136
|
+
import Typography8 from "@mui/material/Typography";
|
|
2757
3137
|
import Skeleton4 from "@mui/material/Skeleton";
|
|
2758
3138
|
import Divider3 from "@mui/material/Divider";
|
|
2759
3139
|
import Table4 from "@mui/material/Table";
|
|
@@ -2762,12 +3142,12 @@ import TableCell4 from "@mui/material/TableCell";
|
|
|
2762
3142
|
import TableHead4 from "@mui/material/TableHead";
|
|
2763
3143
|
import TableRow4 from "@mui/material/TableRow";
|
|
2764
3144
|
import TableContainer4 from "@mui/material/TableContainer";
|
|
2765
|
-
import
|
|
2766
|
-
import
|
|
3145
|
+
import Collapse2 from "@mui/material/Collapse";
|
|
3146
|
+
import IconButton5 from "@mui/material/IconButton";
|
|
2767
3147
|
import Button5 from "@mui/material/Button";
|
|
2768
|
-
import
|
|
2769
|
-
import { alpha as
|
|
2770
|
-
import { ExpandMore as
|
|
3148
|
+
import CircularProgress5 from "@mui/material/CircularProgress";
|
|
3149
|
+
import { alpha as alpha5 } from "@mui/material/styles";
|
|
3150
|
+
import { ExpandMore as ExpandMore3, Group, Add as Add3 } from "@mui/icons-material";
|
|
2771
3151
|
import {
|
|
2772
3152
|
AreaChart as AreaChart2,
|
|
2773
3153
|
Area as Area2,
|
|
@@ -2777,7 +3157,7 @@ import {
|
|
|
2777
3157
|
Tooltip as Tooltip2,
|
|
2778
3158
|
ResponsiveContainer as ResponsiveContainer2
|
|
2779
3159
|
} from "recharts";
|
|
2780
|
-
function
|
|
3160
|
+
function budgetTone3(isOver, isNear) {
|
|
2781
3161
|
if (isOver) return "danger";
|
|
2782
3162
|
if (isNear) return "warning";
|
|
2783
3163
|
return "accent";
|
|
@@ -2789,7 +3169,7 @@ var init_TeamUsage = __esm({
|
|
|
2789
3169
|
init_ui();
|
|
2790
3170
|
fmtUsd2 = (n) => `$${(n ?? 0).toFixed(2)}`;
|
|
2791
3171
|
TeamCard = ({ team, usage, usageLoading, canManage, onEditTeam }) => {
|
|
2792
|
-
const [expanded, setExpanded] =
|
|
3172
|
+
const [expanded, setExpanded] = useState6(false);
|
|
2793
3173
|
const chart = useChartTheme();
|
|
2794
3174
|
const budget = team.max_budget ?? 0;
|
|
2795
3175
|
const spend = team.spend ?? 0;
|
|
@@ -2799,24 +3179,24 @@ var init_TeamUsage = __esm({
|
|
|
2799
3179
|
const dailyData = usage?.daily_usage?.map((d) => ({ date: d.date, spend: d.spend })) ?? [];
|
|
2800
3180
|
const renderDailySpendSection = () => {
|
|
2801
3181
|
if (usageLoading) {
|
|
2802
|
-
return /* @__PURE__ */
|
|
3182
|
+
return /* @__PURE__ */ React8.createElement(Box8, { display: "flex", justifyContent: "center", py: 3 }, /* @__PURE__ */ React8.createElement(CircularProgress5, { size: 22 }));
|
|
2803
3183
|
}
|
|
2804
3184
|
if (dailyData.length === 0) return null;
|
|
2805
|
-
return /* @__PURE__ */
|
|
2806
|
-
|
|
3185
|
+
return /* @__PURE__ */ React8.createElement(Box8, { mb: 2.5 }, /* @__PURE__ */ React8.createElement(
|
|
3186
|
+
Typography8,
|
|
2807
3187
|
{
|
|
2808
3188
|
variant: "caption",
|
|
2809
3189
|
color: "text.secondary",
|
|
2810
3190
|
sx: { display: "block", fontSize: 11, fontWeight: 700, letterSpacing: "0.07em", textTransform: "uppercase", mb: 1 }
|
|
2811
3191
|
},
|
|
2812
3192
|
"Daily spend"
|
|
2813
|
-
), /* @__PURE__ */
|
|
3193
|
+
), /* @__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
3194
|
Tooltip2,
|
|
2815
3195
|
{
|
|
2816
3196
|
cursor: chart.cursor,
|
|
2817
|
-
content: /* @__PURE__ */
|
|
3197
|
+
content: /* @__PURE__ */ React8.createElement(ChartTooltip, { valueFormatter: (v) => `$${v.toFixed(4)}` })
|
|
2818
3198
|
}
|
|
2819
|
-
), /* @__PURE__ */
|
|
3199
|
+
), /* @__PURE__ */ React8.createElement(
|
|
2820
3200
|
Area2,
|
|
2821
3201
|
{
|
|
2822
3202
|
type: "monotone",
|
|
@@ -2829,8 +3209,8 @@ var init_TeamUsage = __esm({
|
|
|
2829
3209
|
}
|
|
2830
3210
|
)))));
|
|
2831
3211
|
};
|
|
2832
|
-
const sectionLabel = (label) => /* @__PURE__ */
|
|
2833
|
-
|
|
3212
|
+
const sectionLabel = (label) => /* @__PURE__ */ React8.createElement(
|
|
3213
|
+
Typography8,
|
|
2834
3214
|
{
|
|
2835
3215
|
variant: "caption",
|
|
2836
3216
|
color: "text.secondary",
|
|
@@ -2838,8 +3218,8 @@ var init_TeamUsage = __esm({
|
|
|
2838
3218
|
},
|
|
2839
3219
|
label
|
|
2840
3220
|
);
|
|
2841
|
-
return /* @__PURE__ */
|
|
2842
|
-
|
|
3221
|
+
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(
|
|
3222
|
+
Box8,
|
|
2843
3223
|
{
|
|
2844
3224
|
sx: (theme) => ({
|
|
2845
3225
|
width: 36,
|
|
@@ -2849,20 +3229,20 @@ var init_TeamUsage = __esm({
|
|
|
2849
3229
|
display: "flex",
|
|
2850
3230
|
alignItems: "center",
|
|
2851
3231
|
justifyContent: "center",
|
|
2852
|
-
bgcolor:
|
|
3232
|
+
bgcolor: alpha5(theme.palette.primary.main, 0.1),
|
|
2853
3233
|
color: theme.palette.primary.main
|
|
2854
3234
|
})
|
|
2855
3235
|
},
|
|
2856
|
-
/* @__PURE__ */
|
|
2857
|
-
), /* @__PURE__ */
|
|
2858
|
-
|
|
3236
|
+
/* @__PURE__ */ React8.createElement(Group, { fontSize: "small" })
|
|
3237
|
+
), /* @__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(
|
|
3238
|
+
Typography8,
|
|
2859
3239
|
{
|
|
2860
3240
|
variant: "caption",
|
|
2861
3241
|
color: "text.secondary",
|
|
2862
3242
|
sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 11 }
|
|
2863
3243
|
},
|
|
2864
3244
|
team.team_id
|
|
2865
|
-
)), isOver && /* @__PURE__ */
|
|
3245
|
+
)), 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
3246
|
Button5,
|
|
2867
3247
|
{
|
|
2868
3248
|
size: "small",
|
|
@@ -2871,8 +3251,8 @@ var init_TeamUsage = __esm({
|
|
|
2871
3251
|
sx: { textTransform: "none" }
|
|
2872
3252
|
},
|
|
2873
3253
|
"Edit"
|
|
2874
|
-
), /* @__PURE__ */
|
|
2875
|
-
|
|
3254
|
+
), /* @__PURE__ */ React8.createElement(
|
|
3255
|
+
IconButton5,
|
|
2876
3256
|
{
|
|
2877
3257
|
size: "small",
|
|
2878
3258
|
onClick: () => setExpanded((e) => !e),
|
|
@@ -2884,9 +3264,9 @@ var init_TeamUsage = __esm({
|
|
|
2884
3264
|
transition: theme.transitions.create("transform")
|
|
2885
3265
|
})
|
|
2886
3266
|
},
|
|
2887
|
-
/* @__PURE__ */
|
|
2888
|
-
)), /* @__PURE__ */
|
|
2889
|
-
|
|
3267
|
+
/* @__PURE__ */ React8.createElement(ExpandMore3, null)
|
|
3268
|
+
)), /* @__PURE__ */ React8.createElement(
|
|
3269
|
+
Box8,
|
|
2890
3270
|
{
|
|
2891
3271
|
sx: {
|
|
2892
3272
|
display: "grid",
|
|
@@ -2896,35 +3276,35 @@ var init_TeamUsage = __esm({
|
|
|
2896
3276
|
maxWidth: 640
|
|
2897
3277
|
}
|
|
2898
3278
|
},
|
|
2899
|
-
/* @__PURE__ */
|
|
2900
|
-
/* @__PURE__ */
|
|
2901
|
-
/* @__PURE__ */
|
|
2902
|
-
/* @__PURE__ */
|
|
2903
|
-
/* @__PURE__ */
|
|
2904
|
-
/* @__PURE__ */
|
|
2905
|
-
), budget > 0 && /* @__PURE__ */
|
|
3279
|
+
/* @__PURE__ */ React8.createElement(Stat, { label: "Members", value: team.members_with_roles?.length ?? "\u2014" }),
|
|
3280
|
+
/* @__PURE__ */ React8.createElement(Stat, { label: "Models", value: team.models?.length ? team.models.length : "All" }),
|
|
3281
|
+
/* @__PURE__ */ React8.createElement(Stat, { label: "Budget", value: budget > 0 ? fmtUsd2(budget) : "Unlimited" }),
|
|
3282
|
+
/* @__PURE__ */ React8.createElement(Stat, { label: "Spend", value: fmtUsd2(spend) }),
|
|
3283
|
+
/* @__PURE__ */ React8.createElement(Stat, { label: "TPM", value: team.tpm_limit && team.tpm_limit > 0 ? team.tpm_limit.toLocaleString() : "\u2014" }),
|
|
3284
|
+
/* @__PURE__ */ React8.createElement(Stat, { label: "RPM", value: team.rpm_limit && team.rpm_limit > 0 ? team.rpm_limit.toLocaleString() : "\u2014" })
|
|
3285
|
+
), 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
3286
|
TableContainer4,
|
|
2907
3287
|
{
|
|
2908
|
-
component:
|
|
3288
|
+
component: Paper6,
|
|
2909
3289
|
variant: "outlined",
|
|
2910
3290
|
sx: { borderRadius: 1.5 }
|
|
2911
3291
|
},
|
|
2912
|
-
/* @__PURE__ */
|
|
2913
|
-
|
|
3292
|
+
/* @__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(
|
|
3293
|
+
Typography8,
|
|
2914
3294
|
{
|
|
2915
3295
|
variant: "caption",
|
|
2916
3296
|
color: "text.secondary",
|
|
2917
3297
|
sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 11 }
|
|
2918
3298
|
},
|
|
2919
3299
|
m.user_id
|
|
2920
|
-
)) : /* @__PURE__ */
|
|
2921
|
-
|
|
3300
|
+
)) : /* @__PURE__ */ React8.createElement(
|
|
3301
|
+
Typography8,
|
|
2922
3302
|
{
|
|
2923
3303
|
variant: "body2",
|
|
2924
3304
|
sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace" }
|
|
2925
3305
|
},
|
|
2926
3306
|
m.user_id
|
|
2927
|
-
)), /* @__PURE__ */
|
|
3307
|
+
)), /* @__PURE__ */ React8.createElement(TableCell4, { align: "right" }, /* @__PURE__ */ React8.createElement(
|
|
2928
3308
|
StatusPill,
|
|
2929
3309
|
{
|
|
2930
3310
|
label: m.role,
|
|
@@ -2932,7 +3312,7 @@ var init_TeamUsage = __esm({
|
|
|
2932
3312
|
dot: false
|
|
2933
3313
|
}
|
|
2934
3314
|
))))))
|
|
2935
|
-
) : /* @__PURE__ */
|
|
3315
|
+
) : /* @__PURE__ */ React8.createElement(Typography8, { variant: "body2", color: "text.secondary" }, "No members assigned."))));
|
|
2936
3316
|
};
|
|
2937
3317
|
TeamUsage = ({
|
|
2938
3318
|
teams,
|
|
@@ -2944,22 +3324,22 @@ var init_TeamUsage = __esm({
|
|
|
2944
3324
|
canCreate,
|
|
2945
3325
|
onCreateTeam
|
|
2946
3326
|
}) => {
|
|
2947
|
-
const createAction = canCreate && onCreateTeam ? /* @__PURE__ */
|
|
3327
|
+
const createAction = canCreate && onCreateTeam ? /* @__PURE__ */ React8.createElement(
|
|
2948
3328
|
Button5,
|
|
2949
3329
|
{
|
|
2950
3330
|
variant: "contained",
|
|
2951
3331
|
color: "primary",
|
|
2952
3332
|
disableElevation: true,
|
|
2953
|
-
startIcon: /* @__PURE__ */
|
|
3333
|
+
startIcon: /* @__PURE__ */ React8.createElement(Add3, null),
|
|
2954
3334
|
onClick: onCreateTeam
|
|
2955
3335
|
},
|
|
2956
3336
|
"Create Team"
|
|
2957
3337
|
) : void 0;
|
|
2958
3338
|
if (loading) {
|
|
2959
|
-
return /* @__PURE__ */
|
|
3339
|
+
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
3340
|
}
|
|
2961
3341
|
if (!teams.length) {
|
|
2962
|
-
return /* @__PURE__ */
|
|
3342
|
+
return /* @__PURE__ */ React8.createElement(SectionCard, { title: "Teams", actions: createAction }, /* @__PURE__ */ React8.createElement(
|
|
2963
3343
|
EmptyState,
|
|
2964
3344
|
{
|
|
2965
3345
|
message: "You're not a member of any LiteLLM team yet.",
|
|
@@ -2967,14 +3347,14 @@ var init_TeamUsage = __esm({
|
|
|
2967
3347
|
}
|
|
2968
3348
|
));
|
|
2969
3349
|
}
|
|
2970
|
-
return /* @__PURE__ */
|
|
3350
|
+
return /* @__PURE__ */ React8.createElement(
|
|
2971
3351
|
SectionCard,
|
|
2972
3352
|
{
|
|
2973
3353
|
title: "Teams",
|
|
2974
3354
|
subtitle: `${teams.length} team${teams.length === 1 ? "" : "s"}`,
|
|
2975
3355
|
actions: createAction
|
|
2976
3356
|
},
|
|
2977
|
-
/* @__PURE__ */
|
|
3357
|
+
/* @__PURE__ */ React8.createElement(Box8, { sx: { display: "flex", flexDirection: "column", gap: 2 } }, teams.map((team) => /* @__PURE__ */ React8.createElement(
|
|
2978
3358
|
TeamCard,
|
|
2979
3359
|
{
|
|
2980
3360
|
key: team.team_id,
|
|
@@ -2991,15 +3371,15 @@ var init_TeamUsage = __esm({
|
|
|
2991
3371
|
});
|
|
2992
3372
|
|
|
2993
3373
|
// src/components/ModelsTable.tsx
|
|
2994
|
-
import
|
|
2995
|
-
import
|
|
3374
|
+
import React9, { useState as useState7, useMemo as useMemo7, useCallback } from "react";
|
|
3375
|
+
import Box9 from "@mui/material/Box";
|
|
2996
3376
|
import Table5 from "@mui/material/Table";
|
|
2997
3377
|
import TableBody5 from "@mui/material/TableBody";
|
|
2998
3378
|
import TableCell5 from "@mui/material/TableCell";
|
|
2999
3379
|
import TableContainer5 from "@mui/material/TableContainer";
|
|
3000
3380
|
import TableHead5 from "@mui/material/TableHead";
|
|
3001
3381
|
import TableRow5 from "@mui/material/TableRow";
|
|
3002
|
-
import
|
|
3382
|
+
import Typography9 from "@mui/material/Typography";
|
|
3003
3383
|
import MenuItem4 from "@mui/material/MenuItem";
|
|
3004
3384
|
import Select from "@mui/material/Select";
|
|
3005
3385
|
import FormControl from "@mui/material/FormControl";
|
|
@@ -3010,8 +3390,8 @@ import { Search as Search2 } from "@mui/icons-material";
|
|
|
3010
3390
|
import ContentCopyIcon from "@mui/icons-material/ContentCopy";
|
|
3011
3391
|
import Tooltip3 from "@mui/material/Tooltip";
|
|
3012
3392
|
import Snackbar from "@mui/material/Snackbar";
|
|
3013
|
-
import
|
|
3014
|
-
import { alpha as
|
|
3393
|
+
import Alert4 from "@mui/material/Alert";
|
|
3394
|
+
import { alpha as alpha6, useTheme as useTheme2 } from "@mui/material/styles";
|
|
3015
3395
|
function isModelAllowedByTeam2(model, teamModels) {
|
|
3016
3396
|
if (!teamModels || teamModels.length === 0 || teamModels.includes(ALL_PROXY_MODELS2)) return true;
|
|
3017
3397
|
return teamModels.includes(model.model_name) || !!model.access_groups?.some((group) => teamModels.includes(group));
|
|
@@ -3037,18 +3417,18 @@ var init_ModelsTable = __esm({
|
|
|
3037
3417
|
ALL_PROXY_MODELS2 = "all-proxy-models";
|
|
3038
3418
|
ModelsTable = ({ allModels, teams, loading }) => {
|
|
3039
3419
|
const theme = useTheme2();
|
|
3040
|
-
const [selectedTeamId, setSelectedTeamId] =
|
|
3041
|
-
const [filterText, setFilterText] =
|
|
3042
|
-
const [copiedSnackbar, setCopiedSnackbar] =
|
|
3043
|
-
const selectedTeam =
|
|
3420
|
+
const [selectedTeamId, setSelectedTeamId] = useState7("");
|
|
3421
|
+
const [filterText, setFilterText] = useState7("");
|
|
3422
|
+
const [copiedSnackbar, setCopiedSnackbar] = useState7(false);
|
|
3423
|
+
const selectedTeam = useMemo7(
|
|
3044
3424
|
() => teams.find((t) => t.team_id === selectedTeamId) ?? null,
|
|
3045
3425
|
[teams, selectedTeamId]
|
|
3046
3426
|
);
|
|
3047
|
-
const teamModels =
|
|
3427
|
+
const teamModels = useMemo7(() => {
|
|
3048
3428
|
if (!selectedTeam) return [];
|
|
3049
3429
|
return allModels.filter((model) => isModelAllowedByTeam2(model, selectedTeam.models));
|
|
3050
3430
|
}, [allModels, selectedTeam]);
|
|
3051
|
-
const filteredModels =
|
|
3431
|
+
const filteredModels = useMemo7(() => {
|
|
3052
3432
|
const q = filterText.trim().toLowerCase();
|
|
3053
3433
|
if (!q) return teamModels;
|
|
3054
3434
|
return teamModels.filter(
|
|
@@ -3072,13 +3452,13 @@ var init_ModelsTable = __esm({
|
|
|
3072
3452
|
return theme.palette.info.main;
|
|
3073
3453
|
}
|
|
3074
3454
|
};
|
|
3075
|
-
return /* @__PURE__ */
|
|
3455
|
+
return /* @__PURE__ */ React9.createElement(
|
|
3076
3456
|
SectionCard,
|
|
3077
3457
|
{
|
|
3078
3458
|
title: "Available Models",
|
|
3079
3459
|
subtitle: selectedTeam ? `${filteredModels.length}${filterText.trim() ? ` of ${teamModels.length}` : ""} model${filteredModels.length === 1 ? "" : "s"}` : "Select a team to view its models"
|
|
3080
3460
|
},
|
|
3081
|
-
/* @__PURE__ */
|
|
3461
|
+
/* @__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
3462
|
Select,
|
|
3083
3463
|
{
|
|
3084
3464
|
labelId: "team-select-label",
|
|
@@ -3086,8 +3466,8 @@ var init_ModelsTable = __esm({
|
|
|
3086
3466
|
label: "Team",
|
|
3087
3467
|
onChange: (e) => setSelectedTeamId(e.target.value)
|
|
3088
3468
|
},
|
|
3089
|
-
teams.map((t) => /* @__PURE__ */
|
|
3090
|
-
)), /* @__PURE__ */
|
|
3469
|
+
teams.map((t) => /* @__PURE__ */ React9.createElement(MenuItem4, { key: t.team_id, value: t.team_id }, t.team_alias ?? t.team_id))
|
|
3470
|
+
)), /* @__PURE__ */ React9.createElement(
|
|
3091
3471
|
TextField5,
|
|
3092
3472
|
{
|
|
3093
3473
|
label: "Filter Models",
|
|
@@ -3097,33 +3477,33 @@ var init_ModelsTable = __esm({
|
|
|
3097
3477
|
disabled: !selectedTeamId,
|
|
3098
3478
|
sx: { minWidth: 240, mb: 2 },
|
|
3099
3479
|
InputProps: {
|
|
3100
|
-
startAdornment: /* @__PURE__ */
|
|
3480
|
+
startAdornment: /* @__PURE__ */ React9.createElement(InputAdornment2, { position: "start" }, /* @__PURE__ */ React9.createElement(Search2, { fontSize: "small" }))
|
|
3101
3481
|
}
|
|
3102
3482
|
}
|
|
3103
3483
|
)),
|
|
3104
|
-
loading && /* @__PURE__ */
|
|
3105
|
-
!loading && !selectedTeamId && /* @__PURE__ */
|
|
3484
|
+
loading && /* @__PURE__ */ React9.createElement(EmptyState, { message: "Loading models\u2026" }),
|
|
3485
|
+
!loading && !selectedTeamId && /* @__PURE__ */ React9.createElement(
|
|
3106
3486
|
EmptyState,
|
|
3107
3487
|
{
|
|
3108
3488
|
message: "Select a team to see available models",
|
|
3109
3489
|
hint: "Each team may have a different set of models assigned"
|
|
3110
3490
|
}
|
|
3111
3491
|
),
|
|
3112
|
-
!loading && selectedTeamId && teamModels.length === 0 && /* @__PURE__ */
|
|
3492
|
+
!loading && selectedTeamId && teamModels.length === 0 && /* @__PURE__ */ React9.createElement(
|
|
3113
3493
|
EmptyState,
|
|
3114
3494
|
{
|
|
3115
3495
|
message: "No models available for this team",
|
|
3116
3496
|
hint: allModels.length === 0 ? "No models are configured in the system" : "None of the team's assigned models were found in the catalog"
|
|
3117
3497
|
}
|
|
3118
3498
|
),
|
|
3119
|
-
!loading && selectedTeamId && teamModels.length > 0 && filteredModels.length === 0 && /* @__PURE__ */
|
|
3499
|
+
!loading && selectedTeamId && teamModels.length > 0 && filteredModels.length === 0 && /* @__PURE__ */ React9.createElement(
|
|
3120
3500
|
EmptyState,
|
|
3121
3501
|
{
|
|
3122
3502
|
message: "No models match your search",
|
|
3123
3503
|
hint: "Try a different search term"
|
|
3124
3504
|
}
|
|
3125
3505
|
),
|
|
3126
|
-
!loading && filteredModels.length > 0 && /* @__PURE__ */
|
|
3506
|
+
!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
3507
|
"span",
|
|
3128
3508
|
{
|
|
3129
3509
|
role: "button",
|
|
@@ -3133,9 +3513,9 @@ var init_ModelsTable = __esm({
|
|
|
3133
3513
|
onKeyDown: (e) => e.key === "Enter" && handleCopyModelId(m.model_name),
|
|
3134
3514
|
style: { cursor: "pointer", display: "inline-flex" }
|
|
3135
3515
|
},
|
|
3136
|
-
/* @__PURE__ */
|
|
3137
|
-
)), /* @__PURE__ */
|
|
3138
|
-
|
|
3516
|
+
/* @__PURE__ */ React9.createElement(TagChip, { label: m.model_name, title: "Copy model ID" })
|
|
3517
|
+
)), /* @__PURE__ */ React9.createElement(Tooltip3, { title: "Copy model ID" }, /* @__PURE__ */ React9.createElement(
|
|
3518
|
+
Box9,
|
|
3139
3519
|
{
|
|
3140
3520
|
component: "span",
|
|
3141
3521
|
role: "button",
|
|
@@ -3145,9 +3525,9 @@ var init_ModelsTable = __esm({
|
|
|
3145
3525
|
sx: quietIconButtonSx("accent"),
|
|
3146
3526
|
style: { cursor: "pointer", display: "inline-flex" }
|
|
3147
3527
|
},
|
|
3148
|
-
/* @__PURE__ */
|
|
3149
|
-
)))), /* @__PURE__ */
|
|
3150
|
-
|
|
3528
|
+
/* @__PURE__ */ React9.createElement(ContentCopyIcon, { sx: { fontSize: 14 } })
|
|
3529
|
+
)))), /* @__PURE__ */ React9.createElement(TableCell5, null, /* @__PURE__ */ React9.createElement(
|
|
3530
|
+
Box9,
|
|
3151
3531
|
{
|
|
3152
3532
|
component: "span",
|
|
3153
3533
|
sx: {
|
|
@@ -3158,13 +3538,13 @@ var init_ModelsTable = __esm({
|
|
|
3158
3538
|
fontSize: 11,
|
|
3159
3539
|
fontWeight: 600,
|
|
3160
3540
|
textTransform: "capitalize",
|
|
3161
|
-
bgcolor:
|
|
3541
|
+
bgcolor: alpha6(modeColor(m.mode), 0.1),
|
|
3162
3542
|
color: modeColor(m.mode)
|
|
3163
3543
|
}
|
|
3164
3544
|
},
|
|
3165
3545
|
m.mode
|
|
3166
|
-
)), /* @__PURE__ */
|
|
3167
|
-
/* @__PURE__ */
|
|
3546
|
+
)), /* @__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" })))))))),
|
|
3547
|
+
/* @__PURE__ */ React9.createElement(
|
|
3168
3548
|
Snackbar,
|
|
3169
3549
|
{
|
|
3170
3550
|
open: copiedSnackbar,
|
|
@@ -3172,7 +3552,7 @@ var init_ModelsTable = __esm({
|
|
|
3172
3552
|
onClose: () => setCopiedSnackbar(false),
|
|
3173
3553
|
anchorOrigin: { vertical: "bottom", horizontal: "center" }
|
|
3174
3554
|
},
|
|
3175
|
-
/* @__PURE__ */
|
|
3555
|
+
/* @__PURE__ */ React9.createElement(Alert4, { severity: "success", variant: "filled", sx: { fontSize: 13 } }, "Model ID copied to clipboard")
|
|
3176
3556
|
)
|
|
3177
3557
|
);
|
|
3178
3558
|
};
|
|
@@ -3180,9 +3560,9 @@ var init_ModelsTable = __esm({
|
|
|
3180
3560
|
});
|
|
3181
3561
|
|
|
3182
3562
|
// src/components/AuditLog.tsx
|
|
3183
|
-
import
|
|
3184
|
-
import
|
|
3185
|
-
import
|
|
3563
|
+
import React10, { useState as useState8, useCallback as useCallback2 } from "react";
|
|
3564
|
+
import Box10 from "@mui/material/Box";
|
|
3565
|
+
import Typography10 from "@mui/material/Typography";
|
|
3186
3566
|
import Table6 from "@mui/material/Table";
|
|
3187
3567
|
import TableBody6 from "@mui/material/TableBody";
|
|
3188
3568
|
import TableCell6 from "@mui/material/TableCell";
|
|
@@ -3193,10 +3573,10 @@ import TablePagination from "@mui/material/TablePagination";
|
|
|
3193
3573
|
import TextField6 from "@mui/material/TextField";
|
|
3194
3574
|
import MenuItem5 from "@mui/material/MenuItem";
|
|
3195
3575
|
import Skeleton5 from "@mui/material/Skeleton";
|
|
3196
|
-
import
|
|
3197
|
-
import
|
|
3198
|
-
import
|
|
3199
|
-
import { alpha as
|
|
3576
|
+
import Collapse3 from "@mui/material/Collapse";
|
|
3577
|
+
import IconButton6 from "@mui/material/IconButton";
|
|
3578
|
+
import Alert5 from "@mui/material/Alert";
|
|
3579
|
+
import { alpha as alpha7 } from "@mui/material/styles";
|
|
3200
3580
|
import { KeyboardArrowDown } from "@mui/icons-material";
|
|
3201
3581
|
import { useAsync as useAsync2 } from "react-use";
|
|
3202
3582
|
function actionTone(action) {
|
|
@@ -3219,10 +3599,10 @@ function formatDateTime(iso) {
|
|
|
3219
3599
|
}
|
|
3220
3600
|
function renderAuditLogBody(loading, entries) {
|
|
3221
3601
|
if (loading) {
|
|
3222
|
-
return /* @__PURE__ */
|
|
3602
|
+
return /* @__PURE__ */ React10.createElement(TableRow6, null, /* @__PURE__ */ React10.createElement(TableCell6, { colSpan: 6, sx: { py: 2 } }, /* @__PURE__ */ React10.createElement(Skeleton5, { variant: "rounded", height: 160 })));
|
|
3223
3603
|
}
|
|
3224
3604
|
if (entries.length === 0) {
|
|
3225
|
-
return /* @__PURE__ */
|
|
3605
|
+
return /* @__PURE__ */ React10.createElement(TableRow6, null, /* @__PURE__ */ React10.createElement(TableCell6, { colSpan: 6 }, /* @__PURE__ */ React10.createElement(
|
|
3226
3606
|
EmptyState,
|
|
3227
3607
|
{
|
|
3228
3608
|
message: "No audit events found",
|
|
@@ -3230,7 +3610,7 @@ function renderAuditLogBody(loading, entries) {
|
|
|
3230
3610
|
}
|
|
3231
3611
|
)));
|
|
3232
3612
|
}
|
|
3233
|
-
return entries.map((entry) => /* @__PURE__ */
|
|
3613
|
+
return entries.map((entry) => /* @__PURE__ */ React10.createElement(DetailRow, { key: entry.id, entry }));
|
|
3234
3614
|
}
|
|
3235
3615
|
var ACTION_TONES, TABLE_LABELS, DetailRow, AuditLog;
|
|
3236
3616
|
var init_AuditLog = __esm({
|
|
@@ -3252,10 +3632,10 @@ var init_AuditLog = __esm({
|
|
|
3252
3632
|
LiteLLM_UserTable: "User"
|
|
3253
3633
|
};
|
|
3254
3634
|
DetailRow = ({ entry }) => {
|
|
3255
|
-
const [open, setOpen] =
|
|
3635
|
+
const [open, setOpen] = useState8(false);
|
|
3256
3636
|
const hasDetail = entry.before_value || entry.updated_values;
|
|
3257
|
-
return /* @__PURE__ */
|
|
3258
|
-
|
|
3637
|
+
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(
|
|
3638
|
+
IconButton6,
|
|
3259
3639
|
{
|
|
3260
3640
|
size: "small",
|
|
3261
3641
|
onClick: () => setOpen((o) => !o),
|
|
@@ -3267,9 +3647,9 @@ var init_AuditLog = __esm({
|
|
|
3267
3647
|
transition: theme.transitions.create("transform")
|
|
3268
3648
|
})
|
|
3269
3649
|
},
|
|
3270
|
-
/* @__PURE__ */
|
|
3271
|
-
)), /* @__PURE__ */
|
|
3272
|
-
|
|
3650
|
+
/* @__PURE__ */ React10.createElement(KeyboardArrowDown, { fontSize: "small" })
|
|
3651
|
+
)), /* @__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(
|
|
3652
|
+
Typography10,
|
|
3273
3653
|
{
|
|
3274
3654
|
variant: "body2",
|
|
3275
3655
|
component: "code",
|
|
@@ -3278,8 +3658,8 @@ var init_AuditLog = __esm({
|
|
|
3278
3658
|
sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 12 }
|
|
3279
3659
|
},
|
|
3280
3660
|
entry.object_id ? entry.object_id.slice(0, 20) + (entry.object_id.length > 20 ? "\u2026" : "") : "\u2014"
|
|
3281
|
-
)), /* @__PURE__ */
|
|
3282
|
-
|
|
3661
|
+
)), /* @__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(
|
|
3662
|
+
Box10,
|
|
3283
3663
|
{
|
|
3284
3664
|
display: "flex",
|
|
3285
3665
|
gap: 2,
|
|
@@ -3288,11 +3668,11 @@ var init_AuditLog = __esm({
|
|
|
3288
3668
|
p: 2,
|
|
3289
3669
|
mb: 1.5,
|
|
3290
3670
|
borderRadius: 1.5,
|
|
3291
|
-
bgcolor:
|
|
3671
|
+
bgcolor: alpha7(theme.palette.text.primary, theme.palette.mode === "dark" ? 0.05 : 0.03)
|
|
3292
3672
|
})
|
|
3293
3673
|
},
|
|
3294
|
-
entry.before_value && /* @__PURE__ */
|
|
3295
|
-
|
|
3674
|
+
entry.before_value && /* @__PURE__ */ React10.createElement(Box10, { flex: 1, minWidth: 200 }, /* @__PURE__ */ React10.createElement(
|
|
3675
|
+
Typography10,
|
|
3296
3676
|
{
|
|
3297
3677
|
variant: "caption",
|
|
3298
3678
|
color: "text.secondary",
|
|
@@ -3301,9 +3681,9 @@ var init_AuditLog = __esm({
|
|
|
3301
3681
|
sx: { fontSize: 10.5, fontWeight: 700, letterSpacing: "0.07em", textTransform: "uppercase" }
|
|
3302
3682
|
},
|
|
3303
3683
|
"Before"
|
|
3304
|
-
), /* @__PURE__ */
|
|
3305
|
-
entry.updated_values && /* @__PURE__ */
|
|
3306
|
-
|
|
3684
|
+
), /* @__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))),
|
|
3685
|
+
entry.updated_values && /* @__PURE__ */ React10.createElement(Box10, { flex: 1, minWidth: 200 }, /* @__PURE__ */ React10.createElement(
|
|
3686
|
+
Typography10,
|
|
3307
3687
|
{
|
|
3308
3688
|
variant: "caption",
|
|
3309
3689
|
color: "text.secondary",
|
|
@@ -3312,13 +3692,13 @@ var init_AuditLog = __esm({
|
|
|
3312
3692
|
sx: { fontSize: 10.5, fontWeight: 700, letterSpacing: "0.07em", textTransform: "uppercase" }
|
|
3313
3693
|
},
|
|
3314
3694
|
"After"
|
|
3315
|
-
), /* @__PURE__ */
|
|
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.updated_values, null, 2)))
|
|
3316
3696
|
)))));
|
|
3317
3697
|
};
|
|
3318
3698
|
AuditLog = ({ api }) => {
|
|
3319
|
-
const [page, setPage] =
|
|
3320
|
-
const [pageSize, setPageSize] =
|
|
3321
|
-
const [filters, setFilters] =
|
|
3699
|
+
const [page, setPage] = useState8(0);
|
|
3700
|
+
const [pageSize, setPageSize] = useState8(25);
|
|
3701
|
+
const [filters, setFilters] = useState8({});
|
|
3322
3702
|
const fetchParams = useCallback2(
|
|
3323
3703
|
() => ({ page: page + 1, page_size: pageSize, ...filters }),
|
|
3324
3704
|
[page, pageSize, filters]
|
|
@@ -3329,13 +3709,13 @@ var init_AuditLog = __esm({
|
|
|
3329
3709
|
);
|
|
3330
3710
|
const entries = value?.audit_logs ?? [];
|
|
3331
3711
|
const total = value?.total ?? 0;
|
|
3332
|
-
return /* @__PURE__ */
|
|
3712
|
+
return /* @__PURE__ */ React10.createElement(
|
|
3333
3713
|
SectionCard,
|
|
3334
3714
|
{
|
|
3335
3715
|
title: "Audit Log",
|
|
3336
3716
|
subtitle: loading ? "Loading\u2026" : `${total.toLocaleString()} event${total === 1 ? "" : "s"}`,
|
|
3337
3717
|
flush: true,
|
|
3338
|
-
actions: /* @__PURE__ */
|
|
3718
|
+
actions: /* @__PURE__ */ React10.createElement(React10.Fragment, null, /* @__PURE__ */ React10.createElement(
|
|
3339
3719
|
TextField6,
|
|
3340
3720
|
{
|
|
3341
3721
|
size: "small",
|
|
@@ -3348,12 +3728,12 @@ var init_AuditLog = __esm({
|
|
|
3348
3728
|
},
|
|
3349
3729
|
sx: { minWidth: 150 }
|
|
3350
3730
|
},
|
|
3351
|
-
/* @__PURE__ */
|
|
3352
|
-
/* @__PURE__ */
|
|
3353
|
-
/* @__PURE__ */
|
|
3354
|
-
/* @__PURE__ */
|
|
3355
|
-
/* @__PURE__ */
|
|
3356
|
-
), /* @__PURE__ */
|
|
3731
|
+
/* @__PURE__ */ React10.createElement(MenuItem5, { value: "" }, "All actions"),
|
|
3732
|
+
/* @__PURE__ */ React10.createElement(MenuItem5, { value: "created" }, "Created"),
|
|
3733
|
+
/* @__PURE__ */ React10.createElement(MenuItem5, { value: "updated" }, "Updated"),
|
|
3734
|
+
/* @__PURE__ */ React10.createElement(MenuItem5, { value: "deleted" }, "Deleted"),
|
|
3735
|
+
/* @__PURE__ */ React10.createElement(MenuItem5, { value: "blocked" }, "Blocked")
|
|
3736
|
+
), /* @__PURE__ */ React10.createElement(
|
|
3357
3737
|
TextField6,
|
|
3358
3738
|
{
|
|
3359
3739
|
size: "small",
|
|
@@ -3366,13 +3746,13 @@ var init_AuditLog = __esm({
|
|
|
3366
3746
|
},
|
|
3367
3747
|
sx: { minWidth: 150 }
|
|
3368
3748
|
},
|
|
3369
|
-
/* @__PURE__ */
|
|
3370
|
-
/* @__PURE__ */
|
|
3371
|
-
/* @__PURE__ */
|
|
3372
|
-
/* @__PURE__ */
|
|
3373
|
-
/* @__PURE__ */
|
|
3374
|
-
/* @__PURE__ */
|
|
3375
|
-
), /* @__PURE__ */
|
|
3749
|
+
/* @__PURE__ */ React10.createElement(MenuItem5, { value: "" }, "All tables"),
|
|
3750
|
+
/* @__PURE__ */ React10.createElement(MenuItem5, { value: "LiteLLM_VerificationToken" }, "Key"),
|
|
3751
|
+
/* @__PURE__ */ React10.createElement(MenuItem5, { value: "LiteLLM_TeamTable" }, "Team"),
|
|
3752
|
+
/* @__PURE__ */ React10.createElement(MenuItem5, { value: "LiteLLM_TeamMembership" }, "Team member"),
|
|
3753
|
+
/* @__PURE__ */ React10.createElement(MenuItem5, { value: "LiteLLM_ObjectPermissionTable" }, "Team access (KB / MCP)"),
|
|
3754
|
+
/* @__PURE__ */ React10.createElement(MenuItem5, { value: "LiteLLM_UserTable" }, "User")
|
|
3755
|
+
), /* @__PURE__ */ React10.createElement(
|
|
3376
3756
|
TextField6,
|
|
3377
3757
|
{
|
|
3378
3758
|
size: "small",
|
|
@@ -3386,9 +3766,9 @@ var init_AuditLog = __esm({
|
|
|
3386
3766
|
}
|
|
3387
3767
|
))
|
|
3388
3768
|
},
|
|
3389
|
-
error && /* @__PURE__ */
|
|
3390
|
-
/* @__PURE__ */
|
|
3391
|
-
/* @__PURE__ */
|
|
3769
|
+
error && /* @__PURE__ */ React10.createElement(Box10, { px: 2.5, pb: 2 }, /* @__PURE__ */ React10.createElement(Alert5, { severity: "error" }, error.message)),
|
|
3770
|
+
/* @__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)))),
|
|
3771
|
+
/* @__PURE__ */ React10.createElement(
|
|
3392
3772
|
TablePagination,
|
|
3393
3773
|
{
|
|
3394
3774
|
component: "div",
|
|
@@ -3443,17 +3823,18 @@ var LiteLLMPage_exports = {};
|
|
|
3443
3823
|
__export(LiteLLMPage_exports, {
|
|
3444
3824
|
LiteLLMPage: () => LiteLLMPage
|
|
3445
3825
|
});
|
|
3446
|
-
import
|
|
3447
|
-
import
|
|
3826
|
+
import React11, { useState as useState9, useCallback as useCallback3, useMemo as useMemo8 } from "react";
|
|
3827
|
+
import Box11 from "@mui/material/Box";
|
|
3448
3828
|
import Snackbar2 from "@mui/material/Snackbar";
|
|
3449
|
-
import
|
|
3450
|
-
import
|
|
3451
|
-
import
|
|
3452
|
-
import
|
|
3829
|
+
import Alert6 from "@mui/material/Alert";
|
|
3830
|
+
import CircularProgress6 from "@mui/material/CircularProgress";
|
|
3831
|
+
import Typography11 from "@mui/material/Typography";
|
|
3832
|
+
import Paper7 from "@mui/material/Paper";
|
|
3453
3833
|
import Tabs2 from "@mui/material/Tabs";
|
|
3454
3834
|
import Tab2 from "@mui/material/Tab";
|
|
3835
|
+
import Grid2 from "@mui/material/Grid";
|
|
3455
3836
|
import { useAsync as useAsync3, useAsyncRetry } from "react-use";
|
|
3456
|
-
import { useApi as
|
|
3837
|
+
import { useApi as useApi3 } from "@backstage/core-plugin-api";
|
|
3457
3838
|
import { usePermission } from "@backstage/plugin-permission-react";
|
|
3458
3839
|
function initDateRange() {
|
|
3459
3840
|
let preset = "7d";
|
|
@@ -3478,6 +3859,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3478
3859
|
init_GenerateKeyDialog();
|
|
3479
3860
|
init_ManageTeamDialog();
|
|
3480
3861
|
init_UsageStats();
|
|
3862
|
+
init_LiteLLMBudgetWidget();
|
|
3481
3863
|
init_TeamUsage();
|
|
3482
3864
|
init_ModelsTable();
|
|
3483
3865
|
init_AuditLog();
|
|
@@ -3485,21 +3867,21 @@ var init_LiteLLMPage = __esm({
|
|
|
3485
3867
|
init_permissions();
|
|
3486
3868
|
PERIOD_LS_KEY2 = "litellm_usage_period";
|
|
3487
3869
|
LiteLLMPage = () => {
|
|
3488
|
-
const api =
|
|
3489
|
-
const [dateRange, setDateRange] =
|
|
3490
|
-
const [currentPreset, setCurrentPreset] =
|
|
3870
|
+
const api = useApi3(liteLlmApiRef);
|
|
3871
|
+
const [dateRange, setDateRange] = useState9(initDateRange);
|
|
3872
|
+
const [currentPreset, setCurrentPreset] = useState9(() => {
|
|
3491
3873
|
try {
|
|
3492
3874
|
return localStorage.getItem(PERIOD_LS_KEY2) ?? "7d";
|
|
3493
3875
|
} catch {
|
|
3494
3876
|
return "7d";
|
|
3495
3877
|
}
|
|
3496
3878
|
});
|
|
3497
|
-
const [activeTab, setActiveTab] =
|
|
3498
|
-
const [snackbar, setSnackbar] =
|
|
3499
|
-
const [generateDialogOpen, setGenerateDialogOpen] =
|
|
3500
|
-
const [manageTeam, setManageTeam] =
|
|
3501
|
-
const [teamUsageCache, setTeamUsageCache] =
|
|
3502
|
-
const [teamUsageLoading, setTeamUsageLoading] =
|
|
3879
|
+
const [activeTab, setActiveTab] = useState9("overview");
|
|
3880
|
+
const [snackbar, setSnackbar] = useState9(null);
|
|
3881
|
+
const [generateDialogOpen, setGenerateDialogOpen] = useState9(false);
|
|
3882
|
+
const [manageTeam, setManageTeam] = useState9(null);
|
|
3883
|
+
const [teamUsageCache, setTeamUsageCache] = useState9({});
|
|
3884
|
+
const [teamUsageLoading, setTeamUsageLoading] = useState9({});
|
|
3503
3885
|
const { value: userInfo, loading: userLoading, error: userError } = useAsync3(
|
|
3504
3886
|
() => api.getUserInfo(),
|
|
3505
3887
|
[api]
|
|
@@ -3543,7 +3925,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3543
3925
|
async () => objectPermsEnabled ? api.getMcpServers().catch(() => []) : [],
|
|
3544
3926
|
[api, objectPermsEnabled]
|
|
3545
3927
|
);
|
|
3546
|
-
const teams =
|
|
3928
|
+
const teams = useMemo8(() => {
|
|
3547
3929
|
if (!allTeams?.length) return [];
|
|
3548
3930
|
if (!userInfo) return allTeams;
|
|
3549
3931
|
const userId = userInfo.user_id;
|
|
@@ -3582,7 +3964,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3582
3964
|
setTeamUsageLoading((prev) => ({ ...prev, [teamId]: false }));
|
|
3583
3965
|
}
|
|
3584
3966
|
}, [api, dateRange, teamUsageCache, teamUsageLoading]);
|
|
3585
|
-
const allowedModels =
|
|
3967
|
+
const allowedModels = useMemo8(() => {
|
|
3586
3968
|
if (!allModels?.length) return [];
|
|
3587
3969
|
const userModels = userInfo?.models;
|
|
3588
3970
|
const teamModels = teams?.flatMap((t) => t.models ?? []);
|
|
@@ -3690,14 +4072,14 @@ var init_LiteLLMPage = __esm({
|
|
|
3690
4072
|
);
|
|
3691
4073
|
const isInitialLoading = userLoading && !userInfo;
|
|
3692
4074
|
if (isInitialLoading) {
|
|
3693
|
-
return /* @__PURE__ */
|
|
4075
|
+
return /* @__PURE__ */ React11.createElement(Box11, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: "50vh" }, /* @__PURE__ */ React11.createElement(CircularProgress6, null));
|
|
3694
4076
|
}
|
|
3695
4077
|
if (userError || !userInfo) {
|
|
3696
4078
|
const isProvisioningEnabled = userError?.body?.provisioning === true;
|
|
3697
4079
|
const hint = userError?.body?.hint;
|
|
3698
|
-
return /* @__PURE__ */
|
|
4080
|
+
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
4081
|
}
|
|
3700
|
-
const pageTabs = /* @__PURE__ */
|
|
4082
|
+
const pageTabs = /* @__PURE__ */ React11.createElement(
|
|
3701
4083
|
Tabs2,
|
|
3702
4084
|
{
|
|
3703
4085
|
value: activeTab,
|
|
@@ -3710,13 +4092,13 @@ var init_LiteLLMPage = __esm({
|
|
|
3710
4092
|
'& [class*="MuiTab-root"]': { minHeight: 44, textTransform: "none", fontSize: 14 }
|
|
3711
4093
|
}
|
|
3712
4094
|
},
|
|
3713
|
-
/* @__PURE__ */
|
|
3714
|
-
/* @__PURE__ */
|
|
3715
|
-
/* @__PURE__ */
|
|
3716
|
-
/* @__PURE__ */
|
|
3717
|
-
userInfo.can_view_audit && /* @__PURE__ */
|
|
4095
|
+
/* @__PURE__ */ React11.createElement(Tab2, { label: "Overview", value: "overview" }),
|
|
4096
|
+
/* @__PURE__ */ React11.createElement(Tab2, { label: "Keys", value: "keys" }),
|
|
4097
|
+
/* @__PURE__ */ React11.createElement(Tab2, { label: "Teams", value: "teams" }),
|
|
4098
|
+
/* @__PURE__ */ React11.createElement(Tab2, { label: "Models", value: "models" }),
|
|
4099
|
+
userInfo.can_view_audit && /* @__PURE__ */ React11.createElement(Tab2, { label: "Audit Log", value: "audit" })
|
|
3718
4100
|
);
|
|
3719
|
-
return /* @__PURE__ */
|
|
4101
|
+
return /* @__PURE__ */ React11.createElement(Box11, { sx: { p: 3, display: "flex", flexDirection: "column", gap: 2 } }, /* @__PURE__ */ React11.createElement(
|
|
3720
4102
|
DashboardHeader,
|
|
3721
4103
|
{
|
|
3722
4104
|
userInfo,
|
|
@@ -3726,7 +4108,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3726
4108
|
onGenerateKeyClick: () => setGenerateDialogOpen(true),
|
|
3727
4109
|
tabs: pageTabs
|
|
3728
4110
|
}
|
|
3729
|
-
), activeTab === "overview" && /* @__PURE__ */
|
|
4111
|
+
), 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
4112
|
UsageStats,
|
|
3731
4113
|
{
|
|
3732
4114
|
usage: usage ?? null,
|
|
@@ -3737,7 +4119,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3737
4119
|
loading: usageLoading,
|
|
3738
4120
|
userInfo
|
|
3739
4121
|
}
|
|
3740
|
-
), activeTab === "keys" && /* @__PURE__ */
|
|
4122
|
+
)), /* @__PURE__ */ React11.createElement(Grid2, { item: true, xs: 12, lg: 4 }, /* @__PURE__ */ React11.createElement(LiteLLMBudgetWidget, { compact: true, collapsible: true }))), activeTab === "keys" && /* @__PURE__ */ React11.createElement(
|
|
3741
4123
|
KeysTable,
|
|
3742
4124
|
{
|
|
3743
4125
|
keys: keys ?? [],
|
|
@@ -3756,7 +4138,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3756
4138
|
for (const t of managedTeams ?? []) teamsById.set(t.team_id, t);
|
|
3757
4139
|
for (const t of teams ?? []) teamsById.set(t.team_id, t);
|
|
3758
4140
|
const visibleTeams = Array.from(teamsById.values());
|
|
3759
|
-
return /* @__PURE__ */
|
|
4141
|
+
return /* @__PURE__ */ React11.createElement(
|
|
3760
4142
|
TeamUsage,
|
|
3761
4143
|
{
|
|
3762
4144
|
teams: visibleTeams,
|
|
@@ -3772,14 +4154,14 @@ var init_LiteLLMPage = __esm({
|
|
|
3772
4154
|
onCreateTeam: () => setManageTeam({ mode: "create" })
|
|
3773
4155
|
}
|
|
3774
4156
|
);
|
|
3775
|
-
})(), activeTab === "models" && /* @__PURE__ */
|
|
4157
|
+
})(), activeTab === "models" && /* @__PURE__ */ React11.createElement(
|
|
3776
4158
|
ModelsTable,
|
|
3777
4159
|
{
|
|
3778
4160
|
allModels: allModels ?? [],
|
|
3779
4161
|
teams: teams ?? [],
|
|
3780
4162
|
loading: modelsLoading
|
|
3781
4163
|
}
|
|
3782
|
-
), activeTab === "audit" && userInfo.can_view_audit && /* @__PURE__ */
|
|
4164
|
+
), activeTab === "audit" && userInfo.can_view_audit && /* @__PURE__ */ React11.createElement(AuditLog, { api }), /* @__PURE__ */ React11.createElement(
|
|
3783
4165
|
GenerateKeyDialog,
|
|
3784
4166
|
{
|
|
3785
4167
|
open: generateDialogOpen,
|
|
@@ -3792,7 +4174,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3792
4174
|
onGenerateKey: handleGenerateKey,
|
|
3793
4175
|
onGetConfig: () => api.getConfig()
|
|
3794
4176
|
}
|
|
3795
|
-
), /* @__PURE__ */
|
|
4177
|
+
), /* @__PURE__ */ React11.createElement(
|
|
3796
4178
|
ManageTeamDialog,
|
|
3797
4179
|
{
|
|
3798
4180
|
open: !!manageTeam,
|
|
@@ -3854,7 +4236,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3854
4236
|
refreshTeams();
|
|
3855
4237
|
}
|
|
3856
4238
|
}
|
|
3857
|
-
), /* @__PURE__ */
|
|
4239
|
+
), /* @__PURE__ */ React11.createElement(
|
|
3858
4240
|
Snackbar2,
|
|
3859
4241
|
{
|
|
3860
4242
|
open: !!snackbar,
|
|
@@ -3862,7 +4244,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3862
4244
|
onClose: () => setSnackbar(null),
|
|
3863
4245
|
anchorOrigin: { vertical: "bottom", horizontal: "right" }
|
|
3864
4246
|
},
|
|
3865
|
-
snackbar ? /* @__PURE__ */
|
|
4247
|
+
snackbar ? /* @__PURE__ */ React11.createElement(Alert6, { severity: snackbar.severity, onClose: () => setSnackbar(null) }, snackbar.message) : void 0
|
|
3866
4248
|
));
|
|
3867
4249
|
};
|
|
3868
4250
|
}
|
|
@@ -3870,7 +4252,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3870
4252
|
|
|
3871
4253
|
// src/plugin.tsx
|
|
3872
4254
|
init_api();
|
|
3873
|
-
import
|
|
4255
|
+
import React12 from "react";
|
|
3874
4256
|
import { TrendingUp as TrendingUpIcon } from "@mui/icons-material";
|
|
3875
4257
|
import {
|
|
3876
4258
|
createFrontendPlugin,
|
|
@@ -3889,10 +4271,10 @@ var liteLlmPage = PageBlueprint.make({
|
|
|
3889
4271
|
params: {
|
|
3890
4272
|
path: "/litellm",
|
|
3891
4273
|
title: "LiteLLM",
|
|
3892
|
-
icon: /* @__PURE__ */
|
|
4274
|
+
icon: /* @__PURE__ */ React12.createElement(TrendingUpIcon, null),
|
|
3893
4275
|
loader: async () => {
|
|
3894
4276
|
const { LiteLLMPage: LiteLLMPage2 } = await Promise.resolve().then(() => (init_LiteLLMPage(), LiteLLMPage_exports));
|
|
3895
|
-
return /* @__PURE__ */
|
|
4277
|
+
return /* @__PURE__ */ React12.createElement(LiteLLMPage2, null);
|
|
3896
4278
|
}
|
|
3897
4279
|
}
|
|
3898
4280
|
});
|
|
@@ -3911,18 +4293,18 @@ init_TeamUsage();
|
|
|
3911
4293
|
// src/components/LiteLLMHomeWidget.tsx
|
|
3912
4294
|
init_api();
|
|
3913
4295
|
init_format();
|
|
3914
|
-
import
|
|
3915
|
-
import
|
|
3916
|
-
import
|
|
3917
|
-
import
|
|
4296
|
+
import React13, { useState as useState10, useEffect as useEffect4 } from "react";
|
|
4297
|
+
import Paper8 from "@mui/material/Paper";
|
|
4298
|
+
import Box12 from "@mui/material/Box";
|
|
4299
|
+
import Typography12 from "@mui/material/Typography";
|
|
3918
4300
|
import FormControl2 from "@mui/material/FormControl";
|
|
3919
4301
|
import Select2 from "@mui/material/Select";
|
|
3920
4302
|
import MenuItem6 from "@mui/material/MenuItem";
|
|
3921
|
-
import
|
|
3922
|
-
import
|
|
3923
|
-
import
|
|
4303
|
+
import Grid3 from "@mui/material/Grid";
|
|
4304
|
+
import CircularProgress7 from "@mui/material/CircularProgress";
|
|
4305
|
+
import Alert7 from "@mui/material/Alert";
|
|
3924
4306
|
import { AreaChart as AreaChart3, Area as Area3, ResponsiveContainer as ResponsiveContainer3 } from "recharts";
|
|
3925
|
-
import { useApi as
|
|
4307
|
+
import { useApi as useApi4 } from "@backstage/core-plugin-api";
|
|
3926
4308
|
function presetToDateRange(preset) {
|
|
3927
4309
|
const end = /* @__PURE__ */ new Date();
|
|
3928
4310
|
const start = /* @__PURE__ */ new Date();
|
|
@@ -3935,19 +4317,19 @@ function presetToDateRange(preset) {
|
|
|
3935
4317
|
}
|
|
3936
4318
|
return { start, end };
|
|
3937
4319
|
}
|
|
3938
|
-
var Kpi = ({ label, value }) => /* @__PURE__ */
|
|
4320
|
+
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
4321
|
var LiteLLMHomeWidget = ({
|
|
3940
4322
|
defaultPeriod = "7d",
|
|
3941
4323
|
title = "LiteLLM Usage"
|
|
3942
4324
|
}) => {
|
|
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
|
-
|
|
4325
|
+
const api = useApi4(liteLlmApiRef);
|
|
4326
|
+
const [period, setPeriod] = useState10(defaultPeriod);
|
|
4327
|
+
const [loading, setLoading] = useState10(true);
|
|
4328
|
+
const [usageError, setUsageError] = useState10(null);
|
|
4329
|
+
const [keysError, setKeysError] = useState10(null);
|
|
4330
|
+
const [usage, setUsage] = useState10(null);
|
|
4331
|
+
const [keys, setKeys] = useState10([]);
|
|
4332
|
+
useEffect4(() => {
|
|
3951
4333
|
let cancelled = false;
|
|
3952
4334
|
setLoading(true);
|
|
3953
4335
|
setUsageError(null);
|
|
@@ -3984,17 +4366,17 @@ var LiteLLMHomeWidget = ({
|
|
|
3984
4366
|
spend: d.spend
|
|
3985
4367
|
}));
|
|
3986
4368
|
const hasSparkline = dailyData.length > 0;
|
|
3987
|
-
return /* @__PURE__ */
|
|
4369
|
+
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
4370
|
Select2,
|
|
3989
4371
|
{
|
|
3990
4372
|
value: period,
|
|
3991
4373
|
onChange: (e) => setPeriod(e.target.value),
|
|
3992
4374
|
displayEmpty: true
|
|
3993
4375
|
},
|
|
3994
|
-
/* @__PURE__ */
|
|
3995
|
-
/* @__PURE__ */
|
|
3996
|
-
/* @__PURE__ */
|
|
3997
|
-
))), loading && /* @__PURE__ */
|
|
4376
|
+
/* @__PURE__ */ React13.createElement(MenuItem6, { value: "today" }, "Today"),
|
|
4377
|
+
/* @__PURE__ */ React13.createElement(MenuItem6, { value: "7d" }, "7d"),
|
|
4378
|
+
/* @__PURE__ */ React13.createElement(MenuItem6, { value: "30d" }, "30d")
|
|
4379
|
+
))), 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
4380
|
Area3,
|
|
3999
4381
|
{
|
|
4000
4382
|
type: "monotone",
|
|
@@ -4009,6 +4391,7 @@ var LiteLLMHomeWidget = ({
|
|
|
4009
4391
|
};
|
|
4010
4392
|
|
|
4011
4393
|
// src/index.ts
|
|
4394
|
+
init_LiteLLMBudgetWidget();
|
|
4012
4395
|
init_GenerateKeyDialog();
|
|
4013
4396
|
init_ManageTeamDialog();
|
|
4014
4397
|
init_api();
|
|
@@ -4017,6 +4400,7 @@ export {
|
|
|
4017
4400
|
DashboardHeader,
|
|
4018
4401
|
GenerateKeyDialog,
|
|
4019
4402
|
KeysTable,
|
|
4403
|
+
LiteLLMBudgetWidget,
|
|
4020
4404
|
LiteLLMHomeWidget,
|
|
4021
4405
|
LiteLLMPage,
|
|
4022
4406
|
LiteLlmApi,
|