@acarmisc/backstage-plugin-litellm 0.24.0 → 0.25.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 +8 -0
- package/dist/components/BudgetLimitList.d.ts +41 -0
- package/dist/components/LiteLLMBudgetGauges.d.ts +34 -2
- package/dist/index.cjs.js +498 -331
- package/dist/index.cjs.js.map +4 -4
- package/dist/index.d.ts +1 -1
- package/dist/index.esm.js +440 -273
- package/dist/index.esm.js.map +4 -4
- package/package.json +1 -1
package/dist/index.esm.js
CHANGED
|
@@ -2924,6 +2924,13 @@ function buildBudgetSummary(user, teams, keys, maxKeys = 3) {
|
|
|
2924
2924
|
teams: teamLimits
|
|
2925
2925
|
};
|
|
2926
2926
|
}
|
|
2927
|
+
function allBudgetLimits(summary) {
|
|
2928
|
+
return [
|
|
2929
|
+
...summary.keys,
|
|
2930
|
+
...summary.user ? [summary.user] : [],
|
|
2931
|
+
...summary.teams
|
|
2932
|
+
];
|
|
2933
|
+
}
|
|
2927
2934
|
function buildBudgetGauges(summary) {
|
|
2928
2935
|
const byLevel = {
|
|
2929
2936
|
key: summary.keys,
|
|
@@ -2974,18 +2981,12 @@ var init_budget = __esm({
|
|
|
2974
2981
|
}
|
|
2975
2982
|
});
|
|
2976
2983
|
|
|
2977
|
-
// src/components/
|
|
2978
|
-
import React7
|
|
2979
|
-
import Paper5 from "@mui/material/Paper";
|
|
2984
|
+
// src/components/BudgetLimitList.tsx
|
|
2985
|
+
import React7 from "react";
|
|
2980
2986
|
import Box7 from "@mui/material/Box";
|
|
2987
|
+
import Paper5 from "@mui/material/Paper";
|
|
2981
2988
|
import Typography7 from "@mui/material/Typography";
|
|
2982
|
-
import
|
|
2983
|
-
import Alert3 from "@mui/material/Alert";
|
|
2984
|
-
import Collapse from "@mui/material/Collapse";
|
|
2985
|
-
import IconButton4 from "@mui/material/IconButton";
|
|
2986
|
-
import { ExpandMore as ExpandMore2 } from "@mui/icons-material";
|
|
2987
|
-
import { alpha as alpha4 } from "@mui/material/styles";
|
|
2988
|
-
import { useApi as useApi2 } from "@backstage/core-plugin-api";
|
|
2989
|
+
import { alpha as alpha4, useTheme as useTheme2 } from "@mui/material/styles";
|
|
2989
2990
|
import { Link } from "@backstage/core-components";
|
|
2990
2991
|
function levelToneColor(tone, theme) {
|
|
2991
2992
|
switch (tone) {
|
|
@@ -3001,13 +3002,119 @@ function levelToneColor(tone, theme) {
|
|
|
3001
3002
|
return theme.palette.primary.main;
|
|
3002
3003
|
}
|
|
3003
3004
|
}
|
|
3004
|
-
var
|
|
3005
|
+
var LimitCard, TaggedLimit, MoreKeysNote, BudgetLimitList, LimitListPanel;
|
|
3006
|
+
var init_BudgetLimitList = __esm({
|
|
3007
|
+
"src/components/BudgetLimitList.tsx"() {
|
|
3008
|
+
"use strict";
|
|
3009
|
+
init_budget();
|
|
3010
|
+
init_format();
|
|
3011
|
+
init_ui();
|
|
3012
|
+
LimitCard = ({ limit }) => {
|
|
3013
|
+
const tone = budgetTone2(limit.pct);
|
|
3014
|
+
const pct = Math.round(limit.pct);
|
|
3015
|
+
const closeTo = limit.softLimit !== void 0 && limit.spend >= limit.softLimit;
|
|
3016
|
+
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(
|
|
3017
|
+
Typography7,
|
|
3018
|
+
{
|
|
3019
|
+
variant: "body2",
|
|
3020
|
+
sx: { fontWeight: 600, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" },
|
|
3021
|
+
title: limit.sublabel ? `${limit.label} \xB7 ${limit.sublabel}` : limit.label
|
|
3022
|
+
},
|
|
3023
|
+
limit.label
|
|
3024
|
+
), limit.sublabel && /* @__PURE__ */ React7.createElement(
|
|
3025
|
+
Typography7,
|
|
3026
|
+
{
|
|
3027
|
+
variant: "caption",
|
|
3028
|
+
color: "text.secondary",
|
|
3029
|
+
sx: {
|
|
3030
|
+
fontSize: 10.5,
|
|
3031
|
+
fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
|
|
3032
|
+
display: "block",
|
|
3033
|
+
overflow: "hidden",
|
|
3034
|
+
textOverflow: "ellipsis",
|
|
3035
|
+
whiteSpace: "nowrap"
|
|
3036
|
+
}
|
|
3037
|
+
},
|
|
3038
|
+
limit.sublabel
|
|
3039
|
+
)), limit.hidden ? /* @__PURE__ */ React7.createElement(Typography7, { variant: "body2", sx: { fontWeight: 600, fontVariantNumeric: "tabular-nums", whiteSpace: "nowrap" } }, pct, "% used") : /* @__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(
|
|
3040
|
+
Box7,
|
|
3041
|
+
{
|
|
3042
|
+
sx: {
|
|
3043
|
+
display: "flex",
|
|
3044
|
+
justifyContent: "space-between",
|
|
3045
|
+
alignItems: "center",
|
|
3046
|
+
gap: 1,
|
|
3047
|
+
mt: 0.75
|
|
3048
|
+
}
|
|
3049
|
+
},
|
|
3050
|
+
/* @__PURE__ */ React7.createElement(Typography7, { variant: "caption", color: "text.secondary", sx: { fontVariantNumeric: "tabular-nums" } }, limit.hidden ? /* @__PURE__ */ React7.createElement(React7.Fragment, null, "Hidden by admin") : /* @__PURE__ */ React7.createElement(React7.Fragment, null, pct, "% of budget", closeTo && ` \xB7 soft-limit warning`)),
|
|
3051
|
+
/* @__PURE__ */ React7.createElement(Typography7, { variant: "caption", color: "text.secondary" }, fmtBudgetDuration(limit.budgetDuration) ? `resets ${fmtBudgetDuration(limit.budgetDuration)}` : "never resets")
|
|
3052
|
+
));
|
|
3053
|
+
};
|
|
3054
|
+
TaggedLimit = ({ limit }) => {
|
|
3055
|
+
const theme = useTheme2();
|
|
3056
|
+
const tone = budgetTone2(limit.pct);
|
|
3057
|
+
return /* @__PURE__ */ React7.createElement(Box7, null, /* @__PURE__ */ React7.createElement(
|
|
3058
|
+
Typography7,
|
|
3059
|
+
{
|
|
3060
|
+
sx: {
|
|
3061
|
+
fontSize: 11,
|
|
3062
|
+
fontWeight: 700,
|
|
3063
|
+
letterSpacing: "0.08em",
|
|
3064
|
+
textTransform: "uppercase",
|
|
3065
|
+
color: tone === "accent" ? theme.palette.text.secondary : levelToneColor(tone, theme),
|
|
3066
|
+
mb: 0.5
|
|
3067
|
+
}
|
|
3068
|
+
},
|
|
3069
|
+
limit.kind
|
|
3070
|
+
), /* @__PURE__ */ React7.createElement(LimitCard, { limit }));
|
|
3071
|
+
};
|
|
3072
|
+
MoreKeysNote = ({
|
|
3073
|
+
count,
|
|
3074
|
+
href,
|
|
3075
|
+
label
|
|
3076
|
+
}) => {
|
|
3077
|
+
const text = label ?? `+${count} more budgeted key${count > 1 ? "s" : ""} further from the cap`;
|
|
3078
|
+
if (!href) {
|
|
3079
|
+
return /* @__PURE__ */ React7.createElement(Typography7, { variant: "caption", color: "text.secondary" }, text);
|
|
3080
|
+
}
|
|
3081
|
+
return /* @__PURE__ */ React7.createElement(Link, { to: href, variant: "caption" }, text, " \u2192");
|
|
3082
|
+
};
|
|
3083
|
+
BudgetLimitList = ({ limits, hiddenKeyCount = 0, keysHref }) => /* @__PURE__ */ React7.createElement(Box7, { sx: { display: "flex", flexDirection: "column", gap: 1.25 } }, limits.map((limit) => /* @__PURE__ */ React7.createElement(TaggedLimit, { key: `${limit.kind}:${limit.sublabel ?? limit.label}`, limit })), hiddenKeyCount > 0 && /* @__PURE__ */ React7.createElement(MoreKeysNote, { count: hiddenKeyCount, href: keysHref }));
|
|
3084
|
+
LimitListPanel = ({ children }) => /* @__PURE__ */ React7.createElement(
|
|
3085
|
+
Box7,
|
|
3086
|
+
{
|
|
3087
|
+
sx: (theme) => ({
|
|
3088
|
+
px: 1.5,
|
|
3089
|
+
py: 1.5,
|
|
3090
|
+
borderRadius: 1.5,
|
|
3091
|
+
bgcolor: alpha4(theme.palette.text.primary, theme.palette.mode === "dark" ? 0.03 : 0.02)
|
|
3092
|
+
})
|
|
3093
|
+
},
|
|
3094
|
+
children
|
|
3095
|
+
);
|
|
3096
|
+
}
|
|
3097
|
+
});
|
|
3098
|
+
|
|
3099
|
+
// src/components/LiteLLMBudgetWidget.tsx
|
|
3100
|
+
import React8, { useEffect as useEffect3, useMemo as useMemo6, useState as useState5 } from "react";
|
|
3101
|
+
import Paper6 from "@mui/material/Paper";
|
|
3102
|
+
import Box8 from "@mui/material/Box";
|
|
3103
|
+
import Typography8 from "@mui/material/Typography";
|
|
3104
|
+
import CircularProgress4 from "@mui/material/CircularProgress";
|
|
3105
|
+
import Alert3 from "@mui/material/Alert";
|
|
3106
|
+
import Collapse from "@mui/material/Collapse";
|
|
3107
|
+
import IconButton4 from "@mui/material/IconButton";
|
|
3108
|
+
import { ExpandMore as ExpandMore2 } from "@mui/icons-material";
|
|
3109
|
+
import { alpha as alpha5 } from "@mui/material/styles";
|
|
3110
|
+
import { useApi as useApi2 } from "@backstage/core-plugin-api";
|
|
3111
|
+
var LevelFrame, EmptyLimitCard, CLOSEST_LEVEL_LABEL, KEYS_PATH, HierarchyNote, FullBody, CompactBody, LiteLLMBudgetWidget;
|
|
3005
3112
|
var init_LiteLLMBudgetWidget = __esm({
|
|
3006
3113
|
"src/components/LiteLLMBudgetWidget.tsx"() {
|
|
3007
3114
|
"use strict";
|
|
3008
3115
|
init_api();
|
|
3009
|
-
init_format();
|
|
3010
3116
|
init_ui();
|
|
3117
|
+
init_BudgetLimitList();
|
|
3011
3118
|
init_budget();
|
|
3012
3119
|
LevelFrame = ({
|
|
3013
3120
|
rank,
|
|
@@ -3017,8 +3124,8 @@ var init_LiteLLMBudgetWidget = __esm({
|
|
|
3017
3124
|
tone,
|
|
3018
3125
|
isLast,
|
|
3019
3126
|
children
|
|
3020
|
-
}) => /* @__PURE__ */
|
|
3021
|
-
|
|
3127
|
+
}) => /* @__PURE__ */ React8.createElement(Box8, { sx: { display: "flex", gap: 1.5 } }, /* @__PURE__ */ React8.createElement(Box8, { sx: { display: "flex", flexDirection: "column", alignItems: "center", width: 26, flexShrink: 0 } }, /* @__PURE__ */ React8.createElement(
|
|
3128
|
+
Box8,
|
|
3022
3129
|
{
|
|
3023
3130
|
sx: (theme) => ({
|
|
3024
3131
|
width: 26,
|
|
@@ -3027,7 +3134,7 @@ var init_LiteLLMBudgetWidget = __esm({
|
|
|
3027
3134
|
display: "flex",
|
|
3028
3135
|
alignItems: "center",
|
|
3029
3136
|
justifyContent: "center",
|
|
3030
|
-
bgcolor:
|
|
3137
|
+
bgcolor: alpha5(levelToneColor(tone, theme), 0.14),
|
|
3031
3138
|
color: levelToneColor(tone, theme),
|
|
3032
3139
|
fontSize: 12,
|
|
3033
3140
|
fontWeight: 700,
|
|
@@ -3035,18 +3142,18 @@ var init_LiteLLMBudgetWidget = __esm({
|
|
|
3035
3142
|
})
|
|
3036
3143
|
},
|
|
3037
3144
|
rank
|
|
3038
|
-
), /* @__PURE__ */
|
|
3039
|
-
|
|
3145
|
+
), /* @__PURE__ */ React8.createElement(
|
|
3146
|
+
Box8,
|
|
3040
3147
|
{
|
|
3041
3148
|
sx: (theme) => ({
|
|
3042
3149
|
flex: 1,
|
|
3043
3150
|
width: 2,
|
|
3044
3151
|
minHeight: 14,
|
|
3045
|
-
bgcolor: isLast ? "transparent" :
|
|
3152
|
+
bgcolor: isLast ? "transparent" : alpha5(theme.palette.text.primary, 0.12)
|
|
3046
3153
|
})
|
|
3047
3154
|
}
|
|
3048
|
-
)), /* @__PURE__ */
|
|
3049
|
-
|
|
3155
|
+
)), /* @__PURE__ */ React8.createElement(Box8, { sx: { flex: 1, minWidth: 0, pb: isLast ? 0 : 2 } }, /* @__PURE__ */ React8.createElement(Box8, { sx: { display: "flex", alignItems: "baseline", gap: 1, flexWrap: "wrap", mb: 0.75 } }, /* @__PURE__ */ React8.createElement(
|
|
3156
|
+
Typography8,
|
|
3050
3157
|
{
|
|
3051
3158
|
sx: (theme) => ({
|
|
3052
3159
|
fontSize: 12.5,
|
|
@@ -3057,102 +3164,42 @@ var init_LiteLLMBudgetWidget = __esm({
|
|
|
3057
3164
|
})
|
|
3058
3165
|
},
|
|
3059
3166
|
name
|
|
3060
|
-
), /* @__PURE__ */
|
|
3061
|
-
EmptyLimitCard = ({ children }) => /* @__PURE__ */
|
|
3062
|
-
|
|
3167
|
+
), /* @__PURE__ */ React8.createElement(Typography8, { variant: "caption", color: "text.secondary", sx: { fontSize: 11.5 } }, tagline), note && /* @__PURE__ */ React8.createElement(StatusPill, { label: note, tone: "neutral", dot: false, sx: { ml: "auto" } })), /* @__PURE__ */ React8.createElement(Box8, { sx: { display: "flex", flexDirection: "column", gap: 1 } }, children)));
|
|
3168
|
+
EmptyLimitCard = ({ children }) => /* @__PURE__ */ React8.createElement(
|
|
3169
|
+
Paper6,
|
|
3063
3170
|
{
|
|
3064
3171
|
variant: "outlined",
|
|
3065
3172
|
sx: (theme) => ({
|
|
3066
3173
|
px: 1.5,
|
|
3067
3174
|
py: 1.25,
|
|
3068
3175
|
borderRadius: 1.5,
|
|
3069
|
-
bgcolor:
|
|
3176
|
+
bgcolor: alpha5(theme.palette.text.primary, theme.palette.mode === "dark" ? 0.03 : 0.015)
|
|
3070
3177
|
})
|
|
3071
3178
|
},
|
|
3072
|
-
/* @__PURE__ */
|
|
3179
|
+
/* @__PURE__ */ React8.createElement(Typography8, { variant: "body2", color: "text.secondary", sx: { fontSize: 13 } }, children)
|
|
3073
3180
|
);
|
|
3074
|
-
LimitCard = ({ limit }) => {
|
|
3075
|
-
const tone = budgetTone2(limit.pct);
|
|
3076
|
-
const pct = Math.round(limit.pct);
|
|
3077
|
-
const closeTo = limit.softLimit !== void 0 && limit.spend >= limit.softLimit;
|
|
3078
|
-
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(
|
|
3079
|
-
Typography7,
|
|
3080
|
-
{
|
|
3081
|
-
variant: "body2",
|
|
3082
|
-
sx: { fontWeight: 600, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" },
|
|
3083
|
-
title: limit.sublabel ? `${limit.label} \xB7 ${limit.sublabel}` : limit.label
|
|
3084
|
-
},
|
|
3085
|
-
limit.label
|
|
3086
|
-
), limit.sublabel && /* @__PURE__ */ React7.createElement(
|
|
3087
|
-
Typography7,
|
|
3088
|
-
{
|
|
3089
|
-
variant: "caption",
|
|
3090
|
-
color: "text.secondary",
|
|
3091
|
-
sx: {
|
|
3092
|
-
fontSize: 10.5,
|
|
3093
|
-
fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
|
|
3094
|
-
display: "block",
|
|
3095
|
-
overflow: "hidden",
|
|
3096
|
-
textOverflow: "ellipsis",
|
|
3097
|
-
whiteSpace: "nowrap"
|
|
3098
|
-
}
|
|
3099
|
-
},
|
|
3100
|
-
limit.sublabel
|
|
3101
|
-
)), limit.hidden ? /* @__PURE__ */ React7.createElement(Typography7, { variant: "body2", sx: { fontWeight: 600, fontVariantNumeric: "tabular-nums", whiteSpace: "nowrap" } }, pct, "% used") : /* @__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(
|
|
3102
|
-
Box7,
|
|
3103
|
-
{
|
|
3104
|
-
sx: {
|
|
3105
|
-
display: "flex",
|
|
3106
|
-
justifyContent: "space-between",
|
|
3107
|
-
alignItems: "center",
|
|
3108
|
-
gap: 1,
|
|
3109
|
-
mt: 0.75
|
|
3110
|
-
}
|
|
3111
|
-
},
|
|
3112
|
-
/* @__PURE__ */ React7.createElement(Typography7, { variant: "caption", color: "text.secondary", sx: { fontVariantNumeric: "tabular-nums" } }, limit.hidden ? /* @__PURE__ */ React7.createElement(React7.Fragment, null, "Hidden by admin") : /* @__PURE__ */ React7.createElement(React7.Fragment, null, pct, "% of budget", closeTo && ` \xB7 soft-limit warning`)),
|
|
3113
|
-
/* @__PURE__ */ React7.createElement(Typography7, { variant: "caption", color: "text.secondary" }, fmtBudgetDuration(limit.budgetDuration) ? `resets ${fmtBudgetDuration(limit.budgetDuration)}` : "never resets")
|
|
3114
|
-
));
|
|
3115
|
-
};
|
|
3116
3181
|
CLOSEST_LEVEL_LABEL = {
|
|
3117
3182
|
key: "key",
|
|
3118
3183
|
user: "personal",
|
|
3119
3184
|
team: "team"
|
|
3120
3185
|
};
|
|
3121
3186
|
KEYS_PATH = "/litellm?tab=keys";
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
Box7,
|
|
3187
|
+
HierarchyNote = ({ withReset }) => /* @__PURE__ */ React8.createElement(
|
|
3188
|
+
Box8,
|
|
3125
3189
|
{
|
|
3126
3190
|
sx: (theme) => ({
|
|
3127
3191
|
px: 1.5,
|
|
3128
3192
|
py: 1.25,
|
|
3129
3193
|
borderRadius: 1.5,
|
|
3130
3194
|
border: "1px dashed",
|
|
3131
|
-
borderColor:
|
|
3195
|
+
borderColor: alpha5(theme.palette.text.primary, 0.18)
|
|
3132
3196
|
})
|
|
3133
3197
|
},
|
|
3134
|
-
/* @__PURE__ */
|
|
3198
|
+
/* @__PURE__ */ React8.createElement(Typography8, { variant: "caption", color: "text.secondary", display: "block" }, /* @__PURE__ */ React8.createElement("b", null, "Order:"), " key \u2192 personal \u2192 team \u2192 global. The first cap you reach blocks the request; a key that belongs to a team uses the team's cap, not your personal one.", withReset && " A cap with a reset window drops to $0 when the window closes; without one it never resets.")
|
|
3135
3199
|
);
|
|
3136
|
-
TaggedLimit = ({ limit }) => {
|
|
3137
|
-
const tone = budgetTone2(limit.pct);
|
|
3138
|
-
return /* @__PURE__ */ React7.createElement(Box7, null, /* @__PURE__ */ React7.createElement(
|
|
3139
|
-
Typography7,
|
|
3140
|
-
{
|
|
3141
|
-
sx: (theme) => ({
|
|
3142
|
-
fontSize: 11,
|
|
3143
|
-
fontWeight: 700,
|
|
3144
|
-
letterSpacing: "0.08em",
|
|
3145
|
-
textTransform: "uppercase",
|
|
3146
|
-
color: tone === "accent" ? theme.palette.text.secondary : levelToneColor(tone, theme),
|
|
3147
|
-
mb: 0.5
|
|
3148
|
-
})
|
|
3149
|
-
},
|
|
3150
|
-
limit.kind
|
|
3151
|
-
), /* @__PURE__ */ React7.createElement(LimitCard, { limit }));
|
|
3152
|
-
};
|
|
3153
3200
|
FullBody = ({ summary }) => {
|
|
3154
3201
|
const hasBudgetedKeys = summary.keys.length + summary.hiddenBudgetedKeys > 0;
|
|
3155
|
-
return /* @__PURE__ */
|
|
3202
|
+
return /* @__PURE__ */ React8.createElement(React8.Fragment, null, /* @__PURE__ */ React8.createElement(
|
|
3156
3203
|
LevelFrame,
|
|
3157
3204
|
{
|
|
3158
3205
|
rank: 1,
|
|
@@ -3160,10 +3207,10 @@ var init_LiteLLMBudgetWidget = __esm({
|
|
|
3160
3207
|
tagline: "per-key cap \xB7 highest priority",
|
|
3161
3208
|
note: hasBudgetedKeys ? void 0 : "no key budgets set"
|
|
3162
3209
|
},
|
|
3163
|
-
!hasBudgetedKeys && /* @__PURE__ */
|
|
3164
|
-
summary.keys.map((k) => /* @__PURE__ */
|
|
3165
|
-
summary.hiddenBudgetedKeys > 0 && /* @__PURE__ */
|
|
3166
|
-
), /* @__PURE__ */
|
|
3210
|
+
!hasBudgetedKeys && /* @__PURE__ */ React8.createElement(EmptyLimitCard, null, "No key has a budget, so no key-level cap applies."),
|
|
3211
|
+
summary.keys.map((k) => /* @__PURE__ */ React8.createElement(LimitCard, { key: k.sublabel ?? k.label, limit: k })),
|
|
3212
|
+
summary.hiddenBudgetedKeys > 0 && /* @__PURE__ */ React8.createElement(MoreKeysNote, { count: summary.hiddenBudgetedKeys, href: KEYS_PATH })
|
|
3213
|
+
), /* @__PURE__ */ React8.createElement(
|
|
3167
3214
|
LevelFrame,
|
|
3168
3215
|
{
|
|
3169
3216
|
rank: 2,
|
|
@@ -3171,15 +3218,15 @@ var init_LiteLLMBudgetWidget = __esm({
|
|
|
3171
3218
|
tagline: "cap on everything you spend with your own keys",
|
|
3172
3219
|
note: "skipped for team-bound keys"
|
|
3173
3220
|
},
|
|
3174
|
-
summary.user ? /* @__PURE__ */
|
|
3175
|
-
), /* @__PURE__ */
|
|
3221
|
+
summary.user ? /* @__PURE__ */ React8.createElement(LimitCard, { limit: summary.user }) : /* @__PURE__ */ React8.createElement(EmptyLimitCard, null, "No personal budget is set on your account.")
|
|
3222
|
+
), /* @__PURE__ */ React8.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__ */ React8.createElement(LimitCard, { key: t.sublabel ?? t.label, limit: t })) : /* @__PURE__ */ React8.createElement(EmptyLimitCard, null, "No team you belong to has a budget.")), /* @__PURE__ */ React8.createElement(LevelFrame, { rank: 4, name: "Global", tagline: "proxy-wide cap set by your admin", isLast: true }, /* @__PURE__ */ React8.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__ */ React8.createElement(Box8, { sx: { mt: 2 } }, /* @__PURE__ */ React8.createElement(HierarchyNote, { withReset: true })));
|
|
3176
3223
|
};
|
|
3177
3224
|
CompactBody = ({ summary }) => {
|
|
3178
3225
|
const { count } = budgetHeadline(summary);
|
|
3179
3226
|
if (count === 0) {
|
|
3180
|
-
return /* @__PURE__ */
|
|
3227
|
+
return /* @__PURE__ */ React8.createElement(Typography8, { variant: "body2", color: "text.secondary", sx: { fontSize: 13 } }, "No budget limits apply to you right now.");
|
|
3181
3228
|
}
|
|
3182
|
-
return /* @__PURE__ */
|
|
3229
|
+
return /* @__PURE__ */ React8.createElement(Box8, { sx: { display: "flex", flexDirection: "column", gap: 1.25 } }, /* @__PURE__ */ React8.createElement(HierarchyNote, null), summary.keys.map((k) => /* @__PURE__ */ React8.createElement(TaggedLimit, { key: k.sublabel ?? k.label, limit: k })), summary.hiddenBudgetedKeys > 0 && /* @__PURE__ */ React8.createElement(MoreKeysNote, { count: summary.hiddenBudgetedKeys, href: KEYS_PATH }), summary.user && /* @__PURE__ */ React8.createElement(TaggedLimit, { limit: summary.user }), summary.teams.map((t) => /* @__PURE__ */ React8.createElement(TaggedLimit, { key: t.sublabel ?? t.label, limit: t })));
|
|
3183
3230
|
};
|
|
3184
3231
|
LiteLLMBudgetWidget = ({
|
|
3185
3232
|
title = "Budget Policy",
|
|
@@ -3230,9 +3277,9 @@ var init_LiteLLMBudgetWidget = __esm({
|
|
|
3230
3277
|
headline.closest.pct
|
|
3231
3278
|
)}%`;
|
|
3232
3279
|
const summaryText = headline.count === 0 ? "no limits apply" : `${limitsWord}${closestWord}`;
|
|
3233
|
-
const body = /* @__PURE__ */
|
|
3234
|
-
return /* @__PURE__ */
|
|
3235
|
-
|
|
3280
|
+
const body = /* @__PURE__ */ React8.createElement(React8.Fragment, null, loading && /* @__PURE__ */ React8.createElement(Box8, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: 140 }, /* @__PURE__ */ React8.createElement(CircularProgress4, { size: 32 })), !loading && error && /* @__PURE__ */ React8.createElement(Alert3, { severity: "error", sx: { mt: 0.5 } }, error), !loading && !error && (compact ? /* @__PURE__ */ React8.createElement(CompactBody, { summary }) : /* @__PURE__ */ React8.createElement(FullBody, { summary })));
|
|
3281
|
+
return /* @__PURE__ */ React8.createElement(Paper6, { sx: { p: 2 } }, collapsible ? /* @__PURE__ */ React8.createElement(
|
|
3282
|
+
Box8,
|
|
3236
3283
|
{
|
|
3237
3284
|
onClick: () => setExpanded((e) => !e),
|
|
3238
3285
|
sx: {
|
|
@@ -3244,14 +3291,14 @@ var init_LiteLLMBudgetWidget = __esm({
|
|
|
3244
3291
|
mb: expanded ? 1.5 : 0
|
|
3245
3292
|
}
|
|
3246
3293
|
},
|
|
3247
|
-
/* @__PURE__ */
|
|
3294
|
+
/* @__PURE__ */ React8.createElement(Box8, { sx: { minWidth: 0, flex: 1 } }, /* @__PURE__ */ React8.createElement(Typography8, { variant: "h6", lineHeight: 1.2 }, title), !loading && !error && /* @__PURE__ */ React8.createElement(Box8, { sx: { mt: 0.5 } }, /* @__PURE__ */ React8.createElement(
|
|
3248
3295
|
StatusPill,
|
|
3249
3296
|
{
|
|
3250
3297
|
label: summaryText,
|
|
3251
3298
|
tone: budgetTone2(headline.closest?.pct ?? 0)
|
|
3252
3299
|
}
|
|
3253
3300
|
))),
|
|
3254
|
-
/* @__PURE__ */
|
|
3301
|
+
/* @__PURE__ */ React8.createElement(
|
|
3255
3302
|
IconButton4,
|
|
3256
3303
|
{
|
|
3257
3304
|
size: "small",
|
|
@@ -3267,10 +3314,10 @@ var init_LiteLLMBudgetWidget = __esm({
|
|
|
3267
3314
|
transition: theme.transitions.create("transform")
|
|
3268
3315
|
})
|
|
3269
3316
|
},
|
|
3270
|
-
/* @__PURE__ */
|
|
3317
|
+
/* @__PURE__ */ React8.createElement(ExpandMore2, null)
|
|
3271
3318
|
)
|
|
3272
|
-
) : /* @__PURE__ */
|
|
3273
|
-
|
|
3319
|
+
) : /* @__PURE__ */ React8.createElement(Box8, { sx: { mb: 1.5 } }, /* @__PURE__ */ React8.createElement(Typography8, { variant: "h6", lineHeight: 1.2 }, title), /* @__PURE__ */ React8.createElement(Typography8, { variant: "caption", color: "text.secondary" }, "How LiteLLM caps spend \u2014 and where you stand right now")), collapsible ? /* @__PURE__ */ React8.createElement(Collapse, { in: expanded, unmountOnExit: true }, body) : body, action && /* @__PURE__ */ React8.createElement(
|
|
3320
|
+
Box8,
|
|
3274
3321
|
{
|
|
3275
3322
|
sx: (theme) => ({
|
|
3276
3323
|
mt: 2,
|
|
@@ -3285,10 +3332,10 @@ var init_LiteLLMBudgetWidget = __esm({
|
|
|
3285
3332
|
});
|
|
3286
3333
|
|
|
3287
3334
|
// src/components/TeamUsage.tsx
|
|
3288
|
-
import
|
|
3289
|
-
import
|
|
3290
|
-
import
|
|
3291
|
-
import
|
|
3335
|
+
import React9, { useState as useState6 } from "react";
|
|
3336
|
+
import Paper7 from "@mui/material/Paper";
|
|
3337
|
+
import Box9 from "@mui/material/Box";
|
|
3338
|
+
import Typography9 from "@mui/material/Typography";
|
|
3292
3339
|
import Skeleton4 from "@mui/material/Skeleton";
|
|
3293
3340
|
import Divider3 from "@mui/material/Divider";
|
|
3294
3341
|
import Table4 from "@mui/material/Table";
|
|
@@ -3301,7 +3348,7 @@ import Collapse2 from "@mui/material/Collapse";
|
|
|
3301
3348
|
import IconButton5 from "@mui/material/IconButton";
|
|
3302
3349
|
import Button5 from "@mui/material/Button";
|
|
3303
3350
|
import CircularProgress5 from "@mui/material/CircularProgress";
|
|
3304
|
-
import { alpha as
|
|
3351
|
+
import { alpha as alpha6 } from "@mui/material/styles";
|
|
3305
3352
|
import { ExpandMore as ExpandMore3, Group, Add as Add3 } from "@mui/icons-material";
|
|
3306
3353
|
import {
|
|
3307
3354
|
AreaChart as AreaChart2,
|
|
@@ -3345,24 +3392,24 @@ var init_TeamUsage = __esm({
|
|
|
3345
3392
|
const renderDailySpendSection = () => {
|
|
3346
3393
|
if (hidden) return null;
|
|
3347
3394
|
if (usageLoading) {
|
|
3348
|
-
return /* @__PURE__ */
|
|
3395
|
+
return /* @__PURE__ */ React9.createElement(Box9, { display: "flex", justifyContent: "center", py: 3 }, /* @__PURE__ */ React9.createElement(CircularProgress5, { size: 22 }));
|
|
3349
3396
|
}
|
|
3350
3397
|
if (dailyData.length === 0) return null;
|
|
3351
|
-
return /* @__PURE__ */
|
|
3352
|
-
|
|
3398
|
+
return /* @__PURE__ */ React9.createElement(Box9, { mb: 2.5 }, /* @__PURE__ */ React9.createElement(
|
|
3399
|
+
Typography9,
|
|
3353
3400
|
{
|
|
3354
3401
|
variant: "caption",
|
|
3355
3402
|
color: "text.secondary",
|
|
3356
3403
|
sx: { display: "block", fontSize: 11, fontWeight: 700, letterSpacing: "0.07em", textTransform: "uppercase", mb: 1 }
|
|
3357
3404
|
},
|
|
3358
3405
|
"Daily spend"
|
|
3359
|
-
), /* @__PURE__ */
|
|
3406
|
+
), /* @__PURE__ */ React9.createElement(Box9, { height: 150 }, /* @__PURE__ */ React9.createElement(ResponsiveContainer2, { width: "100%", height: "100%" }, /* @__PURE__ */ React9.createElement(AreaChart2, { data: dailyData, margin: { top: 4, right: 8, left: 0, bottom: 0 } }, /* @__PURE__ */ React9.createElement(CartesianGrid2, { ...chart.grid }), /* @__PURE__ */ React9.createElement(XAxis2, { dataKey: "date", tickFormatter: fmtDateShort, ...chart.axis }), /* @__PURE__ */ React9.createElement(YAxis2, { tickFormatter: fmtUsdCompact, width: 52, ...chart.axis }), /* @__PURE__ */ React9.createElement(
|
|
3360
3407
|
Tooltip2,
|
|
3361
3408
|
{
|
|
3362
3409
|
cursor: chart.cursor,
|
|
3363
|
-
content: /* @__PURE__ */
|
|
3410
|
+
content: /* @__PURE__ */ React9.createElement(ChartTooltip, { valueFormatter: (v) => `$${v.toFixed(4)}` })
|
|
3364
3411
|
}
|
|
3365
|
-
), /* @__PURE__ */
|
|
3412
|
+
), /* @__PURE__ */ React9.createElement(
|
|
3366
3413
|
Area2,
|
|
3367
3414
|
{
|
|
3368
3415
|
type: "monotone",
|
|
@@ -3375,8 +3422,8 @@ var init_TeamUsage = __esm({
|
|
|
3375
3422
|
}
|
|
3376
3423
|
)))));
|
|
3377
3424
|
};
|
|
3378
|
-
const sectionLabel = (label) => /* @__PURE__ */
|
|
3379
|
-
|
|
3425
|
+
const sectionLabel = (label) => /* @__PURE__ */ React9.createElement(
|
|
3426
|
+
Typography9,
|
|
3380
3427
|
{
|
|
3381
3428
|
variant: "caption",
|
|
3382
3429
|
color: "text.secondary",
|
|
@@ -3384,8 +3431,8 @@ var init_TeamUsage = __esm({
|
|
|
3384
3431
|
},
|
|
3385
3432
|
label
|
|
3386
3433
|
);
|
|
3387
|
-
return /* @__PURE__ */
|
|
3388
|
-
|
|
3434
|
+
return /* @__PURE__ */ React9.createElement(Paper7, { variant: "outlined", sx: { borderRadius: 2, p: 2.5 } }, /* @__PURE__ */ React9.createElement(Box9, { display: "flex", alignItems: "center", gap: 1.5 }, /* @__PURE__ */ React9.createElement(
|
|
3435
|
+
Box9,
|
|
3389
3436
|
{
|
|
3390
3437
|
sx: (theme) => ({
|
|
3391
3438
|
width: 36,
|
|
@@ -3395,20 +3442,20 @@ var init_TeamUsage = __esm({
|
|
|
3395
3442
|
display: "flex",
|
|
3396
3443
|
alignItems: "center",
|
|
3397
3444
|
justifyContent: "center",
|
|
3398
|
-
bgcolor:
|
|
3445
|
+
bgcolor: alpha6(theme.palette.primary.main, 0.1),
|
|
3399
3446
|
color: theme.palette.primary.main
|
|
3400
3447
|
})
|
|
3401
3448
|
},
|
|
3402
|
-
/* @__PURE__ */
|
|
3403
|
-
), /* @__PURE__ */
|
|
3404
|
-
|
|
3449
|
+
/* @__PURE__ */ React9.createElement(Group, { fontSize: "small" })
|
|
3450
|
+
), /* @__PURE__ */ React9.createElement(Box9, { flexGrow: 1, minWidth: 0 }, /* @__PURE__ */ React9.createElement(Typography9, { variant: "subtitle1", sx: { fontWeight: 600, lineHeight: 1.3 } }, team.team_alias || "Untitled team"), /* @__PURE__ */ React9.createElement(
|
|
3451
|
+
Typography9,
|
|
3405
3452
|
{
|
|
3406
3453
|
variant: "caption",
|
|
3407
3454
|
color: "text.secondary",
|
|
3408
3455
|
sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 11 }
|
|
3409
3456
|
},
|
|
3410
3457
|
team.team_id
|
|
3411
|
-
)), isOver && /* @__PURE__ */
|
|
3458
|
+
)), isOver && /* @__PURE__ */ React9.createElement(StatusPill, { label: "Over budget", tone: "danger" }), isNear && /* @__PURE__ */ React9.createElement(StatusPill, { label: "Near limit", tone: "warning" }), canManage && onEditTeam && /* @__PURE__ */ React9.createElement(
|
|
3412
3459
|
Button5,
|
|
3413
3460
|
{
|
|
3414
3461
|
size: "small",
|
|
@@ -3417,7 +3464,7 @@ var init_TeamUsage = __esm({
|
|
|
3417
3464
|
sx: { textTransform: "none" }
|
|
3418
3465
|
},
|
|
3419
3466
|
"Edit"
|
|
3420
|
-
), /* @__PURE__ */
|
|
3467
|
+
), /* @__PURE__ */ React9.createElement(
|
|
3421
3468
|
IconButton5,
|
|
3422
3469
|
{
|
|
3423
3470
|
size: "small",
|
|
@@ -3430,9 +3477,9 @@ var init_TeamUsage = __esm({
|
|
|
3430
3477
|
transition: theme.transitions.create("transform")
|
|
3431
3478
|
})
|
|
3432
3479
|
},
|
|
3433
|
-
/* @__PURE__ */
|
|
3434
|
-
)), /* @__PURE__ */
|
|
3435
|
-
|
|
3480
|
+
/* @__PURE__ */ React9.createElement(ExpandMore3, null)
|
|
3481
|
+
)), /* @__PURE__ */ React9.createElement(
|
|
3482
|
+
Box9,
|
|
3436
3483
|
{
|
|
3437
3484
|
sx: {
|
|
3438
3485
|
display: "grid",
|
|
@@ -3442,35 +3489,35 @@ var init_TeamUsage = __esm({
|
|
|
3442
3489
|
maxWidth: 640
|
|
3443
3490
|
}
|
|
3444
3491
|
},
|
|
3445
|
-
/* @__PURE__ */
|
|
3446
|
-
/* @__PURE__ */
|
|
3447
|
-
/* @__PURE__ */
|
|
3448
|
-
/* @__PURE__ */
|
|
3449
|
-
/* @__PURE__ */
|
|
3450
|
-
/* @__PURE__ */
|
|
3451
|
-
), hidden && /* @__PURE__ */
|
|
3492
|
+
/* @__PURE__ */ React9.createElement(Stat, { label: "Members", value: team.members_with_roles?.length ?? "\u2014" }),
|
|
3493
|
+
/* @__PURE__ */ React9.createElement(Stat, { label: "Models", value: team.models?.length ? team.models.length : "All" }),
|
|
3494
|
+
/* @__PURE__ */ React9.createElement(Stat, { label: "Budget", value: budgetStatValue(hidden, budget) }),
|
|
3495
|
+
/* @__PURE__ */ React9.createElement(Stat, { label: "Spend", value: hidden ? "\u2014" : fmtUsd2(spend) }),
|
|
3496
|
+
/* @__PURE__ */ React9.createElement(Stat, { label: "TPM", value: team.tpm_limit && team.tpm_limit > 0 ? team.tpm_limit.toLocaleString() : "\u2014" }),
|
|
3497
|
+
/* @__PURE__ */ React9.createElement(Stat, { label: "RPM", value: team.rpm_limit && team.rpm_limit > 0 ? team.rpm_limit.toLocaleString() : "\u2014" })
|
|
3498
|
+
), hidden && /* @__PURE__ */ React9.createElement(Box9, { mt: 2, maxWidth: 640 }, /* @__PURE__ */ React9.createElement(Box9, { display: "flex", justifyContent: "space-between", alignItems: "baseline", mb: 0.75 }, /* @__PURE__ */ React9.createElement(Typography9, { variant: "caption", color: "text.secondary", sx: { fontVariantNumeric: "tabular-nums" } }, budgetPct.toFixed(0), "% used"), /* @__PURE__ */ React9.createElement(Typography9, { variant: "caption", color: "text.secondary" }, "Hidden by admin")), /* @__PURE__ */ React9.createElement(Meter, { value: budgetPct, tone: budgetTone3(isOver, isNear), height: 5 }), /* @__PURE__ */ React9.createElement(Typography9, { variant: "caption", color: "text.secondary", sx: { display: "block", mt: 0.75 } }, "Team budget is hidden \u2014 showing usage level only", team.budget_duration ? ` \xB7 resets every ${team.budget_duration}` : "", ".")), !hidden && budget > 0 && /* @__PURE__ */ React9.createElement(Box9, { mt: 2, maxWidth: 640 }, /* @__PURE__ */ React9.createElement(Box9, { display: "flex", justifyContent: "space-between", alignItems: "baseline", mb: 0.75 }, /* @__PURE__ */ React9.createElement(Typography9, { variant: "caption", color: "text.secondary", sx: { fontVariantNumeric: "tabular-nums" } }, fmtUsd2(spend), " of ", fmtUsd2(budget)), /* @__PURE__ */ React9.createElement(Typography9, { variant: "caption", color: "text.secondary", sx: { fontVariantNumeric: "tabular-nums" } }, budgetPct.toFixed(0), "%")), /* @__PURE__ */ React9.createElement(Meter, { value: budgetPct, tone: budgetTone3(isOver, isNear), height: 5 })), /* @__PURE__ */ React9.createElement(Collapse2, { in: expanded, unmountOnExit: true }, /* @__PURE__ */ React9.createElement(Divider3, { sx: { my: 2.5 } }), /* @__PURE__ */ React9.createElement(Box9, { mb: 2.5 }, sectionLabel("Models"), team.models?.length ? /* @__PURE__ */ React9.createElement(Box9, { display: "flex", gap: 0.5, flexWrap: "wrap" }, team.models.map((m) => /* @__PURE__ */ React9.createElement(TagChip, { key: m, label: m, title: m }))) : /* @__PURE__ */ React9.createElement(Typography9, { variant: "body2", color: "text.secondary" }, "All models allowed")), renderDailySpendSection(), /* @__PURE__ */ React9.createElement(Box9, null, sectionLabel("Members"), team.members_with_roles?.length ? /* @__PURE__ */ React9.createElement(
|
|
3452
3499
|
TableContainer4,
|
|
3453
3500
|
{
|
|
3454
|
-
component:
|
|
3501
|
+
component: Paper7,
|
|
3455
3502
|
variant: "outlined",
|
|
3456
3503
|
sx: { borderRadius: 1.5 }
|
|
3457
3504
|
},
|
|
3458
|
-
/* @__PURE__ */
|
|
3459
|
-
|
|
3505
|
+
/* @__PURE__ */ React9.createElement(Table4, { size: "small", sx: dataTableSx }, /* @__PURE__ */ React9.createElement(TableHead4, null, /* @__PURE__ */ React9.createElement(TableRow4, null, /* @__PURE__ */ React9.createElement(TableCell4, null, "User"), /* @__PURE__ */ React9.createElement(TableCell4, { align: "right" }, "Role"))), /* @__PURE__ */ React9.createElement(TableBody4, null, team.members_with_roles.map((m) => /* @__PURE__ */ React9.createElement(TableRow4, { key: m.user_id }, /* @__PURE__ */ React9.createElement(TableCell4, null, m.user_email ? /* @__PURE__ */ React9.createElement(React9.Fragment, null, /* @__PURE__ */ React9.createElement(Typography9, { variant: "body2" }, m.user_email), /* @__PURE__ */ React9.createElement(
|
|
3506
|
+
Typography9,
|
|
3460
3507
|
{
|
|
3461
3508
|
variant: "caption",
|
|
3462
3509
|
color: "text.secondary",
|
|
3463
3510
|
sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 11 }
|
|
3464
3511
|
},
|
|
3465
3512
|
m.user_id
|
|
3466
|
-
)) : /* @__PURE__ */
|
|
3467
|
-
|
|
3513
|
+
)) : /* @__PURE__ */ React9.createElement(
|
|
3514
|
+
Typography9,
|
|
3468
3515
|
{
|
|
3469
3516
|
variant: "body2",
|
|
3470
3517
|
sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace" }
|
|
3471
3518
|
},
|
|
3472
3519
|
m.user_id
|
|
3473
|
-
)), /* @__PURE__ */
|
|
3520
|
+
)), /* @__PURE__ */ React9.createElement(TableCell4, { align: "right" }, /* @__PURE__ */ React9.createElement(
|
|
3474
3521
|
StatusPill,
|
|
3475
3522
|
{
|
|
3476
3523
|
label: m.role,
|
|
@@ -3478,7 +3525,7 @@ var init_TeamUsage = __esm({
|
|
|
3478
3525
|
dot: false
|
|
3479
3526
|
}
|
|
3480
3527
|
))))))
|
|
3481
|
-
) : /* @__PURE__ */
|
|
3528
|
+
) : /* @__PURE__ */ React9.createElement(Typography9, { variant: "body2", color: "text.secondary" }, "No members assigned."))));
|
|
3482
3529
|
};
|
|
3483
3530
|
TeamUsage = ({
|
|
3484
3531
|
teams,
|
|
@@ -3490,22 +3537,22 @@ var init_TeamUsage = __esm({
|
|
|
3490
3537
|
canCreate,
|
|
3491
3538
|
onCreateTeam
|
|
3492
3539
|
}) => {
|
|
3493
|
-
const createAction = canCreate && onCreateTeam ? /* @__PURE__ */
|
|
3540
|
+
const createAction = canCreate && onCreateTeam ? /* @__PURE__ */ React9.createElement(
|
|
3494
3541
|
Button5,
|
|
3495
3542
|
{
|
|
3496
3543
|
variant: "contained",
|
|
3497
3544
|
color: "primary",
|
|
3498
3545
|
disableElevation: true,
|
|
3499
|
-
startIcon: /* @__PURE__ */
|
|
3546
|
+
startIcon: /* @__PURE__ */ React9.createElement(Add3, null),
|
|
3500
3547
|
onClick: onCreateTeam
|
|
3501
3548
|
},
|
|
3502
3549
|
"Create Team"
|
|
3503
3550
|
) : void 0;
|
|
3504
3551
|
if (loading) {
|
|
3505
|
-
return /* @__PURE__ */
|
|
3552
|
+
return /* @__PURE__ */ React9.createElement(SectionCard, { title: "Teams", actions: createAction }, /* @__PURE__ */ React9.createElement(Skeleton4, { variant: "rounded", height: 120, sx: { mb: 2 } }), /* @__PURE__ */ React9.createElement(Skeleton4, { variant: "rounded", height: 120 }));
|
|
3506
3553
|
}
|
|
3507
3554
|
if (!teams.length) {
|
|
3508
|
-
return /* @__PURE__ */
|
|
3555
|
+
return /* @__PURE__ */ React9.createElement(SectionCard, { title: "Teams", actions: createAction }, /* @__PURE__ */ React9.createElement(
|
|
3509
3556
|
EmptyState,
|
|
3510
3557
|
{
|
|
3511
3558
|
message: "You're not a member of any LiteLLM team yet.",
|
|
@@ -3513,14 +3560,14 @@ var init_TeamUsage = __esm({
|
|
|
3513
3560
|
}
|
|
3514
3561
|
));
|
|
3515
3562
|
}
|
|
3516
|
-
return /* @__PURE__ */
|
|
3563
|
+
return /* @__PURE__ */ React9.createElement(
|
|
3517
3564
|
SectionCard,
|
|
3518
3565
|
{
|
|
3519
3566
|
title: "Teams",
|
|
3520
3567
|
subtitle: `${teams.length} team${teams.length === 1 ? "" : "s"}`,
|
|
3521
3568
|
actions: createAction
|
|
3522
3569
|
},
|
|
3523
|
-
/* @__PURE__ */
|
|
3570
|
+
/* @__PURE__ */ React9.createElement(Box9, { sx: { display: "flex", flexDirection: "column", gap: 2 } }, teams.map((team) => /* @__PURE__ */ React9.createElement(
|
|
3524
3571
|
TeamCard,
|
|
3525
3572
|
{
|
|
3526
3573
|
key: team.team_id,
|
|
@@ -3537,8 +3584,8 @@ var init_TeamUsage = __esm({
|
|
|
3537
3584
|
});
|
|
3538
3585
|
|
|
3539
3586
|
// src/components/ModelsTable.tsx
|
|
3540
|
-
import
|
|
3541
|
-
import
|
|
3587
|
+
import React10, { useState as useState7, useMemo as useMemo7, useCallback } from "react";
|
|
3588
|
+
import Box10 from "@mui/material/Box";
|
|
3542
3589
|
import Table5 from "@mui/material/Table";
|
|
3543
3590
|
import TableBody5 from "@mui/material/TableBody";
|
|
3544
3591
|
import TableCell5 from "@mui/material/TableCell";
|
|
@@ -3546,7 +3593,7 @@ import TableContainer5 from "@mui/material/TableContainer";
|
|
|
3546
3593
|
import TableHead5 from "@mui/material/TableHead";
|
|
3547
3594
|
import TableRow5 from "@mui/material/TableRow";
|
|
3548
3595
|
import TableSortLabel2 from "@mui/material/TableSortLabel";
|
|
3549
|
-
import
|
|
3596
|
+
import Typography10 from "@mui/material/Typography";
|
|
3550
3597
|
import MenuItem4 from "@mui/material/MenuItem";
|
|
3551
3598
|
import Select from "@mui/material/Select";
|
|
3552
3599
|
import FormControl from "@mui/material/FormControl";
|
|
@@ -3558,7 +3605,7 @@ import ContentCopyIcon from "@mui/icons-material/ContentCopy";
|
|
|
3558
3605
|
import Tooltip3 from "@mui/material/Tooltip";
|
|
3559
3606
|
import Snackbar from "@mui/material/Snackbar";
|
|
3560
3607
|
import Alert4 from "@mui/material/Alert";
|
|
3561
|
-
import { alpha as
|
|
3608
|
+
import { alpha as alpha7, useTheme as useTheme3 } from "@mui/material/styles";
|
|
3562
3609
|
function isModelAllowedByTeam2(model, teamModels) {
|
|
3563
3610
|
if (!teamModels || teamModels.length === 0 || teamModels.includes(ALL_PROXY_MODELS2)) return true;
|
|
3564
3611
|
return teamModels.includes(model.model_name) || !!model.access_groups?.some((group) => teamModels.includes(group));
|
|
@@ -3583,7 +3630,7 @@ var init_ModelsTable = __esm({
|
|
|
3583
3630
|
init_ui();
|
|
3584
3631
|
ALL_PROXY_MODELS2 = "all-proxy-models";
|
|
3585
3632
|
ModelsTable = ({ allModels, teams, loading }) => {
|
|
3586
|
-
const theme =
|
|
3633
|
+
const theme = useTheme3();
|
|
3587
3634
|
const [selectedTeamId, setSelectedTeamId] = useState7("");
|
|
3588
3635
|
const [filterText, setFilterText] = useState7("");
|
|
3589
3636
|
const [copiedSnackbar, setCopiedSnackbar] = useState7(false);
|
|
@@ -3669,13 +3716,13 @@ var init_ModelsTable = __esm({
|
|
|
3669
3716
|
return theme.palette.info.main;
|
|
3670
3717
|
}
|
|
3671
3718
|
};
|
|
3672
|
-
return /* @__PURE__ */
|
|
3719
|
+
return /* @__PURE__ */ React10.createElement(
|
|
3673
3720
|
SectionCard,
|
|
3674
3721
|
{
|
|
3675
3722
|
title: "Available Models",
|
|
3676
3723
|
subtitle: selectedTeam ? `${filteredModels.length}${filterText.trim() ? ` of ${teamModels.length}` : ""} model${filteredModels.length === 1 ? "" : "s"}` : "Select a team to view its models"
|
|
3677
3724
|
},
|
|
3678
|
-
/* @__PURE__ */
|
|
3725
|
+
/* @__PURE__ */ React10.createElement(Box10, { sx: { px: 2.5, pb: 2.5, pt: 1, display: "flex", gap: 2, flexWrap: "wrap" } }, /* @__PURE__ */ React10.createElement(FormControl, { size: "small", sx: { minWidth: 240, mb: 2 } }, /* @__PURE__ */ React10.createElement(InputLabel, { id: "team-select-label" }, "Team"), /* @__PURE__ */ React10.createElement(
|
|
3679
3726
|
Select,
|
|
3680
3727
|
{
|
|
3681
3728
|
labelId: "team-select-label",
|
|
@@ -3683,8 +3730,8 @@ var init_ModelsTable = __esm({
|
|
|
3683
3730
|
label: "Team",
|
|
3684
3731
|
onChange: (e) => setSelectedTeamId(e.target.value)
|
|
3685
3732
|
},
|
|
3686
|
-
teams.map((t) => /* @__PURE__ */
|
|
3687
|
-
)), /* @__PURE__ */
|
|
3733
|
+
teams.map((t) => /* @__PURE__ */ React10.createElement(MenuItem4, { key: t.team_id, value: t.team_id }, t.team_alias ?? t.team_id))
|
|
3734
|
+
)), /* @__PURE__ */ React10.createElement(
|
|
3688
3735
|
TextField5,
|
|
3689
3736
|
{
|
|
3690
3737
|
label: "Filter Models",
|
|
@@ -3694,33 +3741,33 @@ var init_ModelsTable = __esm({
|
|
|
3694
3741
|
disabled: !selectedTeamId,
|
|
3695
3742
|
sx: { minWidth: 240, mb: 2 },
|
|
3696
3743
|
InputProps: {
|
|
3697
|
-
startAdornment: /* @__PURE__ */
|
|
3744
|
+
startAdornment: /* @__PURE__ */ React10.createElement(InputAdornment2, { position: "start" }, /* @__PURE__ */ React10.createElement(Search2, { fontSize: "small" }))
|
|
3698
3745
|
}
|
|
3699
3746
|
}
|
|
3700
3747
|
)),
|
|
3701
|
-
loading && /* @__PURE__ */
|
|
3702
|
-
!loading && !selectedTeamId && /* @__PURE__ */
|
|
3748
|
+
loading && /* @__PURE__ */ React10.createElement(EmptyState, { message: "Loading models\u2026" }),
|
|
3749
|
+
!loading && !selectedTeamId && /* @__PURE__ */ React10.createElement(
|
|
3703
3750
|
EmptyState,
|
|
3704
3751
|
{
|
|
3705
3752
|
message: "Select a team to see available models",
|
|
3706
3753
|
hint: "Each team may have a different set of models assigned"
|
|
3707
3754
|
}
|
|
3708
3755
|
),
|
|
3709
|
-
!loading && selectedTeamId && teamModels.length === 0 && /* @__PURE__ */
|
|
3756
|
+
!loading && selectedTeamId && teamModels.length === 0 && /* @__PURE__ */ React10.createElement(
|
|
3710
3757
|
EmptyState,
|
|
3711
3758
|
{
|
|
3712
3759
|
message: "No models available for this team",
|
|
3713
3760
|
hint: allModels.length === 0 ? "No models are configured in the system" : "None of the team's assigned models were found in the catalog"
|
|
3714
3761
|
}
|
|
3715
3762
|
),
|
|
3716
|
-
!loading && selectedTeamId && teamModels.length > 0 && filteredModels.length === 0 && /* @__PURE__ */
|
|
3763
|
+
!loading && selectedTeamId && teamModels.length > 0 && filteredModels.length === 0 && /* @__PURE__ */ React10.createElement(
|
|
3717
3764
|
EmptyState,
|
|
3718
3765
|
{
|
|
3719
3766
|
message: "No models match your search",
|
|
3720
3767
|
hint: "Try a different search term"
|
|
3721
3768
|
}
|
|
3722
3769
|
),
|
|
3723
|
-
!loading && filteredModels.length > 0 && /* @__PURE__ */
|
|
3770
|
+
!loading && filteredModels.length > 0 && /* @__PURE__ */ React10.createElement(TableContainer5, null, /* @__PURE__ */ React10.createElement(Table5, { size: "small", sx: dataTableSx }, /* @__PURE__ */ React10.createElement(TableHead5, null, /* @__PURE__ */ React10.createElement(TableRow5, null, /* @__PURE__ */ React10.createElement(TableCell5, { sortDirection: sortKey === "model_name" ? sortDirection : false }, /* @__PURE__ */ React10.createElement(TableSortLabel2, { active: sortKey === "model_name", direction: sortKey === "model_name" ? sortDirection : "asc", onClick: () => handleSort("model_name") }, "Model ID")), /* @__PURE__ */ React10.createElement(TableCell5, { sortDirection: sortKey === "mode" ? sortDirection : false }, /* @__PURE__ */ React10.createElement(TableSortLabel2, { active: sortKey === "mode", direction: sortKey === "mode" ? sortDirection : "asc", onClick: () => handleSort("mode") }, "Mode")), /* @__PURE__ */ React10.createElement(TableCell5, { align: "right", sortDirection: sortKey === "input_cost_per_token" ? sortDirection : false }, /* @__PURE__ */ React10.createElement(TableSortLabel2, { active: sortKey === "input_cost_per_token", direction: sortKey === "input_cost_per_token" ? sortDirection : "asc", onClick: () => handleSort("input_cost_per_token") }, "Input Cost")), /* @__PURE__ */ React10.createElement(TableCell5, { align: "right", sortDirection: sortKey === "output_cost_per_token" ? sortDirection : false }, /* @__PURE__ */ React10.createElement(TableSortLabel2, { active: sortKey === "output_cost_per_token", direction: sortKey === "output_cost_per_token" ? sortDirection : "asc", onClick: () => handleSort("output_cost_per_token") }, "Output Cost")), /* @__PURE__ */ React10.createElement(TableCell5, { align: "right", sortDirection: sortKey === "max_input_tokens" ? sortDirection : false }, /* @__PURE__ */ React10.createElement(TableSortLabel2, { active: sortKey === "max_input_tokens", direction: sortKey === "max_input_tokens" ? sortDirection : "asc", onClick: () => handleSort("max_input_tokens") }, "Max Input")), /* @__PURE__ */ React10.createElement(TableCell5, { align: "right", sortDirection: sortKey === "max_output_tokens" ? sortDirection : false }, /* @__PURE__ */ React10.createElement(TableSortLabel2, { active: sortKey === "max_output_tokens", direction: sortKey === "max_output_tokens" ? sortDirection : "asc", onClick: () => handleSort("max_output_tokens") }, "Max Output")), /* @__PURE__ */ React10.createElement(TableCell5, null, "Capabilities"))), /* @__PURE__ */ React10.createElement(TableBody5, null, filteredModels.map((m) => /* @__PURE__ */ React10.createElement(TableRow5, { key: m.model_name, hover: true }, /* @__PURE__ */ React10.createElement(TableCell5, null, /* @__PURE__ */ React10.createElement(Box10, { sx: { display: "flex", alignItems: "center", gap: 0.5 } }, /* @__PURE__ */ React10.createElement(Tooltip3, { title: m.model_name, placement: "top" }, /* @__PURE__ */ React10.createElement(
|
|
3724
3771
|
"span",
|
|
3725
3772
|
{
|
|
3726
3773
|
role: "button",
|
|
@@ -3730,9 +3777,9 @@ var init_ModelsTable = __esm({
|
|
|
3730
3777
|
onKeyDown: (e) => e.key === "Enter" && handleCopyModelId(m.model_name),
|
|
3731
3778
|
style: { cursor: "pointer", display: "inline-flex" }
|
|
3732
3779
|
},
|
|
3733
|
-
/* @__PURE__ */
|
|
3734
|
-
)), /* @__PURE__ */
|
|
3735
|
-
|
|
3780
|
+
/* @__PURE__ */ React10.createElement(TagChip, { label: m.model_name, title: "Copy model ID" })
|
|
3781
|
+
)), /* @__PURE__ */ React10.createElement(Tooltip3, { title: "Copy model ID" }, /* @__PURE__ */ React10.createElement(
|
|
3782
|
+
Box10,
|
|
3736
3783
|
{
|
|
3737
3784
|
component: "span",
|
|
3738
3785
|
role: "button",
|
|
@@ -3742,9 +3789,9 @@ var init_ModelsTable = __esm({
|
|
|
3742
3789
|
sx: quietIconButtonSx("accent"),
|
|
3743
3790
|
style: { cursor: "pointer", display: "inline-flex" }
|
|
3744
3791
|
},
|
|
3745
|
-
/* @__PURE__ */
|
|
3746
|
-
)))), /* @__PURE__ */
|
|
3747
|
-
|
|
3792
|
+
/* @__PURE__ */ React10.createElement(ContentCopyIcon, { sx: { fontSize: 14 } })
|
|
3793
|
+
)))), /* @__PURE__ */ React10.createElement(TableCell5, null, /* @__PURE__ */ React10.createElement(
|
|
3794
|
+
Box10,
|
|
3748
3795
|
{
|
|
3749
3796
|
component: "span",
|
|
3750
3797
|
sx: {
|
|
@@ -3755,13 +3802,13 @@ var init_ModelsTable = __esm({
|
|
|
3755
3802
|
fontSize: 11,
|
|
3756
3803
|
fontWeight: 600,
|
|
3757
3804
|
textTransform: "capitalize",
|
|
3758
|
-
bgcolor:
|
|
3805
|
+
bgcolor: alpha7(modeColor(m.mode), 0.1),
|
|
3759
3806
|
color: modeColor(m.mode)
|
|
3760
3807
|
}
|
|
3761
3808
|
},
|
|
3762
3809
|
m.mode
|
|
3763
|
-
)), /* @__PURE__ */
|
|
3764
|
-
/* @__PURE__ */
|
|
3810
|
+
)), /* @__PURE__ */ React10.createElement(TableCell5, { align: "right" }, /* @__PURE__ */ React10.createElement(Typography10, { variant: "body2", sx: { fontVariantNumeric: "tabular-nums" } }, fmtCostPerToken(m.input_cost_per_token))), /* @__PURE__ */ React10.createElement(TableCell5, { align: "right" }, /* @__PURE__ */ React10.createElement(Typography10, { variant: "body2", sx: { fontVariantNumeric: "tabular-nums" } }, fmtCostPerToken(m.output_cost_per_token))), /* @__PURE__ */ React10.createElement(TableCell5, { align: "right" }, /* @__PURE__ */ React10.createElement(Typography10, { variant: "body2", sx: { fontVariantNumeric: "tabular-nums" } }, fmtTokens(m.max_input_tokens))), /* @__PURE__ */ React10.createElement(TableCell5, { align: "right" }, /* @__PURE__ */ React10.createElement(Typography10, { variant: "body2", sx: { fontVariantNumeric: "tabular-nums" } }, fmtTokens(m.max_output_tokens))), /* @__PURE__ */ React10.createElement(TableCell5, null, /* @__PURE__ */ React10.createElement(Box10, { sx: { display: "flex", gap: 0.5, flexWrap: "wrap" } }, m.supports_function_calling && /* @__PURE__ */ React10.createElement(TagChip, { label: "Fn", title: "Function calling" }), m.supports_vision && /* @__PURE__ */ React10.createElement(TagChip, { label: "\u{1F441}", title: "Vision" })))))))),
|
|
3811
|
+
/* @__PURE__ */ React10.createElement(
|
|
3765
3812
|
Snackbar,
|
|
3766
3813
|
{
|
|
3767
3814
|
open: copiedSnackbar,
|
|
@@ -3769,7 +3816,7 @@ var init_ModelsTable = __esm({
|
|
|
3769
3816
|
onClose: () => setCopiedSnackbar(false),
|
|
3770
3817
|
anchorOrigin: { vertical: "bottom", horizontal: "center" }
|
|
3771
3818
|
},
|
|
3772
|
-
/* @__PURE__ */
|
|
3819
|
+
/* @__PURE__ */ React10.createElement(Alert4, { severity: "success", variant: "filled", sx: { fontSize: 13 } }, "Model ID copied to clipboard")
|
|
3773
3820
|
)
|
|
3774
3821
|
);
|
|
3775
3822
|
};
|
|
@@ -3777,9 +3824,9 @@ var init_ModelsTable = __esm({
|
|
|
3777
3824
|
});
|
|
3778
3825
|
|
|
3779
3826
|
// src/components/AuditLog.tsx
|
|
3780
|
-
import
|
|
3781
|
-
import
|
|
3782
|
-
import
|
|
3827
|
+
import React11, { useState as useState8, useMemo as useMemo8 } from "react";
|
|
3828
|
+
import Box11 from "@mui/material/Box";
|
|
3829
|
+
import Typography11 from "@mui/material/Typography";
|
|
3783
3830
|
import Table6 from "@mui/material/Table";
|
|
3784
3831
|
import TableBody6 from "@mui/material/TableBody";
|
|
3785
3832
|
import TableCell6 from "@mui/material/TableCell";
|
|
@@ -3793,7 +3840,7 @@ import Skeleton5 from "@mui/material/Skeleton";
|
|
|
3793
3840
|
import Collapse3 from "@mui/material/Collapse";
|
|
3794
3841
|
import IconButton6 from "@mui/material/IconButton";
|
|
3795
3842
|
import Alert5 from "@mui/material/Alert";
|
|
3796
|
-
import { alpha as
|
|
3843
|
+
import { alpha as alpha8 } from "@mui/material/styles";
|
|
3797
3844
|
import { KeyboardArrowDown } from "@mui/icons-material";
|
|
3798
3845
|
import { useAsync as useAsync2 } from "react-use";
|
|
3799
3846
|
function actionTone(action) {
|
|
@@ -3816,10 +3863,10 @@ function formatDateTime(iso) {
|
|
|
3816
3863
|
}
|
|
3817
3864
|
function renderAuditLogBody(loading, entries) {
|
|
3818
3865
|
if (loading) {
|
|
3819
|
-
return /* @__PURE__ */
|
|
3866
|
+
return /* @__PURE__ */ React11.createElement(TableRow6, null, /* @__PURE__ */ React11.createElement(TableCell6, { colSpan: 6, sx: { py: 2 } }, /* @__PURE__ */ React11.createElement(Skeleton5, { variant: "rounded", height: 160 })));
|
|
3820
3867
|
}
|
|
3821
3868
|
if (entries.length === 0) {
|
|
3822
|
-
return /* @__PURE__ */
|
|
3869
|
+
return /* @__PURE__ */ React11.createElement(TableRow6, null, /* @__PURE__ */ React11.createElement(TableCell6, { colSpan: 6 }, /* @__PURE__ */ React11.createElement(
|
|
3823
3870
|
EmptyState,
|
|
3824
3871
|
{
|
|
3825
3872
|
message: "No audit events found",
|
|
@@ -3827,7 +3874,7 @@ function renderAuditLogBody(loading, entries) {
|
|
|
3827
3874
|
}
|
|
3828
3875
|
)));
|
|
3829
3876
|
}
|
|
3830
|
-
return entries.map((entry) => /* @__PURE__ */
|
|
3877
|
+
return entries.map((entry) => /* @__PURE__ */ React11.createElement(DetailRow, { key: entry.id, entry }));
|
|
3831
3878
|
}
|
|
3832
3879
|
var ACTION_TONES, TABLE_LABELS, DetailRow, AuditLog;
|
|
3833
3880
|
var init_AuditLog = __esm({
|
|
@@ -3851,7 +3898,7 @@ var init_AuditLog = __esm({
|
|
|
3851
3898
|
DetailRow = ({ entry }) => {
|
|
3852
3899
|
const [open, setOpen] = useState8(false);
|
|
3853
3900
|
const hasDetail = entry.before_value || entry.updated_values;
|
|
3854
|
-
return /* @__PURE__ */
|
|
3901
|
+
return /* @__PURE__ */ React11.createElement(React11.Fragment, null, /* @__PURE__ */ React11.createElement(TableRow6, null, /* @__PURE__ */ React11.createElement(TableCell6, { sx: { width: 40, pr: 0 } }, hasDetail && /* @__PURE__ */ React11.createElement(
|
|
3855
3902
|
IconButton6,
|
|
3856
3903
|
{
|
|
3857
3904
|
size: "small",
|
|
@@ -3864,9 +3911,9 @@ var init_AuditLog = __esm({
|
|
|
3864
3911
|
transition: theme.transitions.create("transform")
|
|
3865
3912
|
})
|
|
3866
3913
|
},
|
|
3867
|
-
/* @__PURE__ */
|
|
3868
|
-
)), /* @__PURE__ */
|
|
3869
|
-
|
|
3914
|
+
/* @__PURE__ */ React11.createElement(KeyboardArrowDown, { fontSize: "small" })
|
|
3915
|
+
)), /* @__PURE__ */ React11.createElement(TableCell6, { sx: { whiteSpace: "nowrap" } }, /* @__PURE__ */ React11.createElement(Typography11, { variant: "body2", color: "text.secondary" }, formatDateTime(entry.updated_at))), /* @__PURE__ */ React11.createElement(TableCell6, null, entry.action && /* @__PURE__ */ React11.createElement(StatusPill, { label: entry.action, tone: actionTone(entry.action) })), /* @__PURE__ */ React11.createElement(TableCell6, null, /* @__PURE__ */ React11.createElement(Typography11, { variant: "body2" }, prettyTableName(entry.table_name))), /* @__PURE__ */ React11.createElement(TableCell6, null, /* @__PURE__ */ React11.createElement(
|
|
3916
|
+
Typography11,
|
|
3870
3917
|
{
|
|
3871
3918
|
variant: "body2",
|
|
3872
3919
|
component: "code",
|
|
@@ -3875,8 +3922,8 @@ var init_AuditLog = __esm({
|
|
|
3875
3922
|
sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 12 }
|
|
3876
3923
|
},
|
|
3877
3924
|
entry.object_id ? entry.object_id.slice(0, 20) + (entry.object_id.length > 20 ? "\u2026" : "") : "\u2014"
|
|
3878
|
-
)), /* @__PURE__ */
|
|
3879
|
-
|
|
3925
|
+
)), /* @__PURE__ */ React11.createElement(TableCell6, null, entry.changed_by ?? "\u2014")), hasDetail && /* @__PURE__ */ React11.createElement(TableRow6, { sx: { "&&:hover": { bgcolor: "transparent" } } }, /* @__PURE__ */ React11.createElement(TableCell6, { colSpan: 6, sx: { "&&": { py: 0, border: 0 } } }, /* @__PURE__ */ React11.createElement(Collapse3, { in: open, unmountOnExit: true }, /* @__PURE__ */ React11.createElement(
|
|
3926
|
+
Box11,
|
|
3880
3927
|
{
|
|
3881
3928
|
display: "flex",
|
|
3882
3929
|
gap: 2,
|
|
@@ -3885,11 +3932,11 @@ var init_AuditLog = __esm({
|
|
|
3885
3932
|
p: 2,
|
|
3886
3933
|
mb: 1.5,
|
|
3887
3934
|
borderRadius: 1.5,
|
|
3888
|
-
bgcolor:
|
|
3935
|
+
bgcolor: alpha8(theme.palette.text.primary, theme.palette.mode === "dark" ? 0.05 : 0.03)
|
|
3889
3936
|
})
|
|
3890
3937
|
},
|
|
3891
|
-
entry.before_value && /* @__PURE__ */
|
|
3892
|
-
|
|
3938
|
+
entry.before_value && /* @__PURE__ */ React11.createElement(Box11, { flex: 1, minWidth: 200 }, /* @__PURE__ */ React11.createElement(
|
|
3939
|
+
Typography11,
|
|
3893
3940
|
{
|
|
3894
3941
|
variant: "caption",
|
|
3895
3942
|
color: "text.secondary",
|
|
@@ -3898,9 +3945,9 @@ var init_AuditLog = __esm({
|
|
|
3898
3945
|
sx: { fontSize: 10.5, fontWeight: 700, letterSpacing: "0.07em", textTransform: "uppercase" }
|
|
3899
3946
|
},
|
|
3900
3947
|
"Before"
|
|
3901
|
-
), /* @__PURE__ */
|
|
3902
|
-
entry.updated_values && /* @__PURE__ */
|
|
3903
|
-
|
|
3948
|
+
), /* @__PURE__ */ React11.createElement(Typography11, { 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))),
|
|
3949
|
+
entry.updated_values && /* @__PURE__ */ React11.createElement(Box11, { flex: 1, minWidth: 200 }, /* @__PURE__ */ React11.createElement(
|
|
3950
|
+
Typography11,
|
|
3904
3951
|
{
|
|
3905
3952
|
variant: "caption",
|
|
3906
3953
|
color: "text.secondary",
|
|
@@ -3909,7 +3956,7 @@ var init_AuditLog = __esm({
|
|
|
3909
3956
|
sx: { fontSize: 10.5, fontWeight: 700, letterSpacing: "0.07em", textTransform: "uppercase" }
|
|
3910
3957
|
},
|
|
3911
3958
|
"After"
|
|
3912
|
-
), /* @__PURE__ */
|
|
3959
|
+
), /* @__PURE__ */ React11.createElement(Typography11, { 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)))
|
|
3913
3960
|
)))));
|
|
3914
3961
|
};
|
|
3915
3962
|
AuditLog = ({ api }) => {
|
|
@@ -3926,13 +3973,13 @@ var init_AuditLog = __esm({
|
|
|
3926
3973
|
);
|
|
3927
3974
|
const entries = value?.audit_logs ?? [];
|
|
3928
3975
|
const total = value?.total ?? 0;
|
|
3929
|
-
return /* @__PURE__ */
|
|
3976
|
+
return /* @__PURE__ */ React11.createElement(
|
|
3930
3977
|
SectionCard,
|
|
3931
3978
|
{
|
|
3932
3979
|
title: "Audit Log",
|
|
3933
3980
|
subtitle: loading ? "Loading\u2026" : `${total.toLocaleString()} event${total === 1 ? "" : "s"}`,
|
|
3934
3981
|
flush: true,
|
|
3935
|
-
actions: /* @__PURE__ */
|
|
3982
|
+
actions: /* @__PURE__ */ React11.createElement(React11.Fragment, null, /* @__PURE__ */ React11.createElement(
|
|
3936
3983
|
TextField6,
|
|
3937
3984
|
{
|
|
3938
3985
|
size: "small",
|
|
@@ -3945,12 +3992,12 @@ var init_AuditLog = __esm({
|
|
|
3945
3992
|
},
|
|
3946
3993
|
sx: { minWidth: 150 }
|
|
3947
3994
|
},
|
|
3948
|
-
/* @__PURE__ */
|
|
3949
|
-
/* @__PURE__ */
|
|
3950
|
-
/* @__PURE__ */
|
|
3951
|
-
/* @__PURE__ */
|
|
3952
|
-
/* @__PURE__ */
|
|
3953
|
-
), /* @__PURE__ */
|
|
3995
|
+
/* @__PURE__ */ React11.createElement(MenuItem5, { value: "" }, "All actions"),
|
|
3996
|
+
/* @__PURE__ */ React11.createElement(MenuItem5, { value: "created" }, "Created"),
|
|
3997
|
+
/* @__PURE__ */ React11.createElement(MenuItem5, { value: "updated" }, "Updated"),
|
|
3998
|
+
/* @__PURE__ */ React11.createElement(MenuItem5, { value: "deleted" }, "Deleted"),
|
|
3999
|
+
/* @__PURE__ */ React11.createElement(MenuItem5, { value: "blocked" }, "Blocked")
|
|
4000
|
+
), /* @__PURE__ */ React11.createElement(
|
|
3954
4001
|
TextField6,
|
|
3955
4002
|
{
|
|
3956
4003
|
size: "small",
|
|
@@ -3963,13 +4010,13 @@ var init_AuditLog = __esm({
|
|
|
3963
4010
|
},
|
|
3964
4011
|
sx: { minWidth: 150 }
|
|
3965
4012
|
},
|
|
3966
|
-
/* @__PURE__ */
|
|
3967
|
-
/* @__PURE__ */
|
|
3968
|
-
/* @__PURE__ */
|
|
3969
|
-
/* @__PURE__ */
|
|
3970
|
-
/* @__PURE__ */
|
|
3971
|
-
/* @__PURE__ */
|
|
3972
|
-
), /* @__PURE__ */
|
|
4013
|
+
/* @__PURE__ */ React11.createElement(MenuItem5, { value: "" }, "All tables"),
|
|
4014
|
+
/* @__PURE__ */ React11.createElement(MenuItem5, { value: "LiteLLM_VerificationToken" }, "Key"),
|
|
4015
|
+
/* @__PURE__ */ React11.createElement(MenuItem5, { value: "LiteLLM_TeamTable" }, "Team"),
|
|
4016
|
+
/* @__PURE__ */ React11.createElement(MenuItem5, { value: "LiteLLM_TeamMembership" }, "Team member"),
|
|
4017
|
+
/* @__PURE__ */ React11.createElement(MenuItem5, { value: "LiteLLM_ObjectPermissionTable" }, "Team access (KB / MCP)"),
|
|
4018
|
+
/* @__PURE__ */ React11.createElement(MenuItem5, { value: "LiteLLM_UserTable" }, "User")
|
|
4019
|
+
), /* @__PURE__ */ React11.createElement(
|
|
3973
4020
|
TextField6,
|
|
3974
4021
|
{
|
|
3975
4022
|
size: "small",
|
|
@@ -3983,9 +4030,9 @@ var init_AuditLog = __esm({
|
|
|
3983
4030
|
}
|
|
3984
4031
|
))
|
|
3985
4032
|
},
|
|
3986
|
-
error && /* @__PURE__ */
|
|
3987
|
-
/* @__PURE__ */
|
|
3988
|
-
/* @__PURE__ */
|
|
4033
|
+
error && /* @__PURE__ */ React11.createElement(Box11, { px: 2.5, pb: 2 }, /* @__PURE__ */ React11.createElement(Alert5, { severity: "error" }, error.message)),
|
|
4034
|
+
/* @__PURE__ */ React11.createElement(TableContainer6, null, /* @__PURE__ */ React11.createElement(Table6, { size: "small", sx: dataTableSx }, /* @__PURE__ */ React11.createElement(TableHead6, null, /* @__PURE__ */ React11.createElement(TableRow6, null, /* @__PURE__ */ React11.createElement(TableCell6, { sx: { width: 40 } }), /* @__PURE__ */ React11.createElement(TableCell6, null, "Time"), /* @__PURE__ */ React11.createElement(TableCell6, null, "Action"), /* @__PURE__ */ React11.createElement(TableCell6, null, "Table"), /* @__PURE__ */ React11.createElement(TableCell6, null, "Object ID"), /* @__PURE__ */ React11.createElement(TableCell6, null, "Changed By"))), /* @__PURE__ */ React11.createElement(TableBody6, null, renderAuditLogBody(loading, entries)))),
|
|
4035
|
+
/* @__PURE__ */ React11.createElement(
|
|
3989
4036
|
TablePagination,
|
|
3990
4037
|
{
|
|
3991
4038
|
component: "div",
|
|
@@ -4040,13 +4087,13 @@ var LiteLLMPage_exports = {};
|
|
|
4040
4087
|
__export(LiteLLMPage_exports, {
|
|
4041
4088
|
LiteLLMPage: () => LiteLLMPage
|
|
4042
4089
|
});
|
|
4043
|
-
import
|
|
4044
|
-
import
|
|
4090
|
+
import React12, { useState as useState9, useCallback as useCallback2, useEffect as useEffect4, useMemo as useMemo9 } from "react";
|
|
4091
|
+
import Box12 from "@mui/material/Box";
|
|
4045
4092
|
import Snackbar2 from "@mui/material/Snackbar";
|
|
4046
4093
|
import Alert6 from "@mui/material/Alert";
|
|
4047
4094
|
import CircularProgress6 from "@mui/material/CircularProgress";
|
|
4048
|
-
import
|
|
4049
|
-
import
|
|
4095
|
+
import Typography12 from "@mui/material/Typography";
|
|
4096
|
+
import Paper8 from "@mui/material/Paper";
|
|
4050
4097
|
import Tabs2 from "@mui/material/Tabs";
|
|
4051
4098
|
import Tab2 from "@mui/material/Tab";
|
|
4052
4099
|
import Grid2 from "@mui/material/Grid";
|
|
@@ -4101,6 +4148,21 @@ var init_LiteLLMPage = __esm({
|
|
|
4101
4148
|
const t = searchParams.get("tab");
|
|
4102
4149
|
return isPageTab(t) ? t : "overview";
|
|
4103
4150
|
});
|
|
4151
|
+
const [generateDialogOpen, setGenerateDialogOpen] = useState9(
|
|
4152
|
+
() => searchParams.get("generate") === "1"
|
|
4153
|
+
);
|
|
4154
|
+
useEffect4(() => {
|
|
4155
|
+
if (searchParams.get("generate") !== "1") return;
|
|
4156
|
+
setGenerateDialogOpen(true);
|
|
4157
|
+
setSearchParams(
|
|
4158
|
+
(prev) => {
|
|
4159
|
+
const next = new URLSearchParams(prev);
|
|
4160
|
+
next.delete("generate");
|
|
4161
|
+
return next;
|
|
4162
|
+
},
|
|
4163
|
+
{ replace: true }
|
|
4164
|
+
);
|
|
4165
|
+
}, [searchParams, setSearchParams]);
|
|
4104
4166
|
const selectTab = useCallback2(
|
|
4105
4167
|
(tab) => {
|
|
4106
4168
|
setActiveTab(tab);
|
|
@@ -4116,7 +4178,6 @@ var init_LiteLLMPage = __esm({
|
|
|
4116
4178
|
[setSearchParams]
|
|
4117
4179
|
);
|
|
4118
4180
|
const [snackbar, setSnackbar] = useState9(null);
|
|
4119
|
-
const [generateDialogOpen, setGenerateDialogOpen] = useState9(false);
|
|
4120
4181
|
const [manageTeam, setManageTeam] = useState9(null);
|
|
4121
4182
|
const [teamUsageCache, setTeamUsageCache] = useState9({});
|
|
4122
4183
|
const [teamUsageLoading, setTeamUsageLoading] = useState9({});
|
|
@@ -4310,14 +4371,14 @@ var init_LiteLLMPage = __esm({
|
|
|
4310
4371
|
);
|
|
4311
4372
|
const isInitialLoading = userLoading && !userInfo;
|
|
4312
4373
|
if (isInitialLoading) {
|
|
4313
|
-
return /* @__PURE__ */
|
|
4374
|
+
return /* @__PURE__ */ React12.createElement(Box12, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: "50vh" }, /* @__PURE__ */ React12.createElement(CircularProgress6, null));
|
|
4314
4375
|
}
|
|
4315
4376
|
if (userError || !userInfo) {
|
|
4316
4377
|
const isProvisioningEnabled = userError?.body?.provisioning === true;
|
|
4317
4378
|
const hint = userError?.body?.hint;
|
|
4318
|
-
return /* @__PURE__ */
|
|
4379
|
+
return /* @__PURE__ */ React12.createElement(Box12, { p: 3 }, /* @__PURE__ */ React12.createElement(Paper8, { sx: { p: 3 } }, /* @__PURE__ */ React12.createElement(Typography12, { variant: "h6", gutterBottom: true }, "Account not provisioned"), /* @__PURE__ */ React12.createElement(Typography12, { color: "text.secondary", paragraph: true }, "Your Backstage account is not linked to a LiteLLM user."), hint ? /* @__PURE__ */ React12.createElement(Typography12, { variant: "body2", color: "text.secondary" }, hint) : /* @__PURE__ */ React12.createElement(Typography12, { 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.")));
|
|
4319
4380
|
}
|
|
4320
|
-
const pageTabs = /* @__PURE__ */
|
|
4381
|
+
const pageTabs = /* @__PURE__ */ React12.createElement(
|
|
4321
4382
|
Tabs2,
|
|
4322
4383
|
{
|
|
4323
4384
|
value: activeTab,
|
|
@@ -4330,13 +4391,13 @@ var init_LiteLLMPage = __esm({
|
|
|
4330
4391
|
'& [class*="MuiTab-root"]': { minHeight: 44, textTransform: "none", fontSize: 14 }
|
|
4331
4392
|
}
|
|
4332
4393
|
},
|
|
4333
|
-
/* @__PURE__ */
|
|
4334
|
-
/* @__PURE__ */
|
|
4335
|
-
/* @__PURE__ */
|
|
4336
|
-
/* @__PURE__ */
|
|
4337
|
-
userInfo.can_view_audit && /* @__PURE__ */
|
|
4394
|
+
/* @__PURE__ */ React12.createElement(Tab2, { label: "Overview", value: "overview" }),
|
|
4395
|
+
/* @__PURE__ */ React12.createElement(Tab2, { label: "Keys", value: "keys" }),
|
|
4396
|
+
/* @__PURE__ */ React12.createElement(Tab2, { label: "Teams", value: "teams" }),
|
|
4397
|
+
/* @__PURE__ */ React12.createElement(Tab2, { label: "Models", value: "models" }),
|
|
4398
|
+
userInfo.can_view_audit && /* @__PURE__ */ React12.createElement(Tab2, { label: "Audit Log", value: "audit" })
|
|
4338
4399
|
);
|
|
4339
|
-
return /* @__PURE__ */
|
|
4400
|
+
return /* @__PURE__ */ React12.createElement(Box12, { sx: { p: 3, display: "flex", flexDirection: "column", gap: 2 } }, /* @__PURE__ */ React12.createElement(
|
|
4340
4401
|
DashboardHeader,
|
|
4341
4402
|
{
|
|
4342
4403
|
userInfo,
|
|
@@ -4346,7 +4407,7 @@ var init_LiteLLMPage = __esm({
|
|
|
4346
4407
|
onGenerateKeyClick: () => setGenerateDialogOpen(true),
|
|
4347
4408
|
tabs: pageTabs
|
|
4348
4409
|
}
|
|
4349
|
-
), activeTab === "overview" && /* @__PURE__ */
|
|
4410
|
+
), activeTab === "overview" && /* @__PURE__ */ React12.createElement(Grid2, { container: true, spacing: 2, alignItems: "flex-start" }, /* @__PURE__ */ React12.createElement(Grid2, { item: true, xs: 12, lg: 8 }, /* @__PURE__ */ React12.createElement(
|
|
4350
4411
|
UsageStats,
|
|
4351
4412
|
{
|
|
4352
4413
|
usage: usage ?? null,
|
|
@@ -4357,7 +4418,7 @@ var init_LiteLLMPage = __esm({
|
|
|
4357
4418
|
loading: usageLoading,
|
|
4358
4419
|
userInfo
|
|
4359
4420
|
}
|
|
4360
|
-
)), /* @__PURE__ */
|
|
4421
|
+
)), /* @__PURE__ */ React12.createElement(Grid2, { item: true, xs: 12, lg: 4 }, /* @__PURE__ */ React12.createElement(LiteLLMBudgetWidget, { compact: true, collapsible: true }))), activeTab === "keys" && /* @__PURE__ */ React12.createElement(
|
|
4361
4422
|
KeysTable,
|
|
4362
4423
|
{
|
|
4363
4424
|
keys: keys ?? [],
|
|
@@ -4376,7 +4437,7 @@ var init_LiteLLMPage = __esm({
|
|
|
4376
4437
|
for (const t of managedTeams ?? []) teamsById.set(t.team_id, t);
|
|
4377
4438
|
for (const t of teams ?? []) teamsById.set(t.team_id, t);
|
|
4378
4439
|
const visibleTeams = Array.from(teamsById.values());
|
|
4379
|
-
return /* @__PURE__ */
|
|
4440
|
+
return /* @__PURE__ */ React12.createElement(
|
|
4380
4441
|
TeamUsage,
|
|
4381
4442
|
{
|
|
4382
4443
|
teams: visibleTeams,
|
|
@@ -4392,14 +4453,14 @@ var init_LiteLLMPage = __esm({
|
|
|
4392
4453
|
onCreateTeam: () => setManageTeam({ mode: "create" })
|
|
4393
4454
|
}
|
|
4394
4455
|
);
|
|
4395
|
-
})(), activeTab === "models" && /* @__PURE__ */
|
|
4456
|
+
})(), activeTab === "models" && /* @__PURE__ */ React12.createElement(
|
|
4396
4457
|
ModelsTable,
|
|
4397
4458
|
{
|
|
4398
4459
|
allModels: allModels ?? [],
|
|
4399
4460
|
teams: teams ?? [],
|
|
4400
4461
|
loading: modelsLoading
|
|
4401
4462
|
}
|
|
4402
|
-
), activeTab === "audit" && userInfo.can_view_audit && /* @__PURE__ */
|
|
4463
|
+
), activeTab === "audit" && userInfo.can_view_audit && /* @__PURE__ */ React12.createElement(AuditLog, { api }), /* @__PURE__ */ React12.createElement(
|
|
4403
4464
|
GenerateKeyDialog,
|
|
4404
4465
|
{
|
|
4405
4466
|
open: generateDialogOpen,
|
|
@@ -4412,7 +4473,7 @@ var init_LiteLLMPage = __esm({
|
|
|
4412
4473
|
onGenerateKey: handleGenerateKey,
|
|
4413
4474
|
onGetConfig: () => api.getConfig()
|
|
4414
4475
|
}
|
|
4415
|
-
), /* @__PURE__ */
|
|
4476
|
+
), /* @__PURE__ */ React12.createElement(
|
|
4416
4477
|
ManageTeamDialog,
|
|
4417
4478
|
{
|
|
4418
4479
|
open: !!manageTeam,
|
|
@@ -4474,7 +4535,7 @@ var init_LiteLLMPage = __esm({
|
|
|
4474
4535
|
refreshTeams();
|
|
4475
4536
|
}
|
|
4476
4537
|
}
|
|
4477
|
-
), /* @__PURE__ */
|
|
4538
|
+
), /* @__PURE__ */ React12.createElement(
|
|
4478
4539
|
Snackbar2,
|
|
4479
4540
|
{
|
|
4480
4541
|
open: !!snackbar,
|
|
@@ -4482,7 +4543,7 @@ var init_LiteLLMPage = __esm({
|
|
|
4482
4543
|
onClose: () => setSnackbar(null),
|
|
4483
4544
|
anchorOrigin: { vertical: "bottom", horizontal: "right" }
|
|
4484
4545
|
},
|
|
4485
|
-
snackbar ? /* @__PURE__ */
|
|
4546
|
+
snackbar ? /* @__PURE__ */ React12.createElement(Alert6, { severity: snackbar.severity, onClose: () => setSnackbar(null) }, snackbar.message) : void 0
|
|
4486
4547
|
));
|
|
4487
4548
|
};
|
|
4488
4549
|
}
|
|
@@ -4490,7 +4551,7 @@ var init_LiteLLMPage = __esm({
|
|
|
4490
4551
|
|
|
4491
4552
|
// src/plugin.tsx
|
|
4492
4553
|
init_api();
|
|
4493
|
-
import
|
|
4554
|
+
import React13 from "react";
|
|
4494
4555
|
import { TrendingUp as TrendingUpIcon } from "@mui/icons-material";
|
|
4495
4556
|
import {
|
|
4496
4557
|
createFrontendPlugin,
|
|
@@ -4509,10 +4570,10 @@ var liteLlmPage = PageBlueprint.make({
|
|
|
4509
4570
|
params: {
|
|
4510
4571
|
path: "/litellm",
|
|
4511
4572
|
title: "LiteLLM",
|
|
4512
|
-
icon: /* @__PURE__ */
|
|
4573
|
+
icon: /* @__PURE__ */ React13.createElement(TrendingUpIcon, null),
|
|
4513
4574
|
loader: async () => {
|
|
4514
4575
|
const { LiteLLMPage: LiteLLMPage2 } = await Promise.resolve().then(() => (init_LiteLLMPage(), LiteLLMPage_exports));
|
|
4515
|
-
return /* @__PURE__ */
|
|
4576
|
+
return /* @__PURE__ */ React13.createElement(LiteLLMPage2, null);
|
|
4516
4577
|
}
|
|
4517
4578
|
}
|
|
4518
4579
|
});
|
|
@@ -4531,10 +4592,10 @@ init_TeamUsage();
|
|
|
4531
4592
|
// src/components/LiteLLMHomeWidget.tsx
|
|
4532
4593
|
init_api();
|
|
4533
4594
|
init_format();
|
|
4534
|
-
import
|
|
4535
|
-
import
|
|
4536
|
-
import
|
|
4537
|
-
import
|
|
4595
|
+
import React14, { useState as useState10, useEffect as useEffect5 } from "react";
|
|
4596
|
+
import Paper9 from "@mui/material/Paper";
|
|
4597
|
+
import Box13 from "@mui/material/Box";
|
|
4598
|
+
import Typography13 from "@mui/material/Typography";
|
|
4538
4599
|
import FormControl2 from "@mui/material/FormControl";
|
|
4539
4600
|
import Select2 from "@mui/material/Select";
|
|
4540
4601
|
import MenuItem6 from "@mui/material/MenuItem";
|
|
@@ -4555,7 +4616,7 @@ function presetToDateRange(preset) {
|
|
|
4555
4616
|
}
|
|
4556
4617
|
return { start, end };
|
|
4557
4618
|
}
|
|
4558
|
-
var Kpi = ({ label, value }) => /* @__PURE__ */
|
|
4619
|
+
var Kpi = ({ label, value }) => /* @__PURE__ */ React14.createElement(Box13, null, /* @__PURE__ */ React14.createElement(Typography13, { variant: "caption", color: "text.secondary", display: "block" }, label), /* @__PURE__ */ React14.createElement(Typography13, { variant: "subtitle1", fontWeight: 600 }, value));
|
|
4559
4620
|
var LiteLLMHomeWidget = ({
|
|
4560
4621
|
defaultPeriod = "7d",
|
|
4561
4622
|
title = "LiteLLM Usage"
|
|
@@ -4567,7 +4628,7 @@ var LiteLLMHomeWidget = ({
|
|
|
4567
4628
|
const [keysError, setKeysError] = useState10(null);
|
|
4568
4629
|
const [usage, setUsage] = useState10(null);
|
|
4569
4630
|
const [keys, setKeys] = useState10([]);
|
|
4570
|
-
|
|
4631
|
+
useEffect5(() => {
|
|
4571
4632
|
let cancelled = false;
|
|
4572
4633
|
setLoading(true);
|
|
4573
4634
|
setUsageError(null);
|
|
@@ -4604,17 +4665,17 @@ var LiteLLMHomeWidget = ({
|
|
|
4604
4665
|
spend: d.spend
|
|
4605
4666
|
}));
|
|
4606
4667
|
const hasSparkline = dailyData.length > 0;
|
|
4607
|
-
return /* @__PURE__ */
|
|
4668
|
+
return /* @__PURE__ */ React14.createElement(Paper9, { sx: { p: 2 } }, /* @__PURE__ */ React14.createElement(Box13, { display: "flex", justifyContent: "space-between", alignItems: "center", mb: 1.5 }, /* @__PURE__ */ React14.createElement(Typography13, { variant: "h6" }, title), /* @__PURE__ */ React14.createElement(FormControl2, { size: "small", sx: { minWidth: 90 } }, /* @__PURE__ */ React14.createElement(
|
|
4608
4669
|
Select2,
|
|
4609
4670
|
{
|
|
4610
4671
|
value: period,
|
|
4611
4672
|
onChange: (e) => setPeriod(e.target.value),
|
|
4612
4673
|
displayEmpty: true
|
|
4613
4674
|
},
|
|
4614
|
-
/* @__PURE__ */
|
|
4615
|
-
/* @__PURE__ */
|
|
4616
|
-
/* @__PURE__ */
|
|
4617
|
-
))), loading && /* @__PURE__ */
|
|
4675
|
+
/* @__PURE__ */ React14.createElement(MenuItem6, { value: "today" }, "Today"),
|
|
4676
|
+
/* @__PURE__ */ React14.createElement(MenuItem6, { value: "7d" }, "7d"),
|
|
4677
|
+
/* @__PURE__ */ React14.createElement(MenuItem6, { value: "30d" }, "30d")
|
|
4678
|
+
))), loading && /* @__PURE__ */ React14.createElement(Box13, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: 120 }, /* @__PURE__ */ React14.createElement(CircularProgress7, { size: 32 })), !loading && totalFailure && /* @__PURE__ */ React14.createElement(Alert7, { severity: "error", sx: { mt: 1 } }, usageError ?? "Failed to load usage data"), !loading && !totalFailure && /* @__PURE__ */ React14.createElement(React14.Fragment, null, partialFailure && /* @__PURE__ */ React14.createElement(Alert7, { severity: "warning", sx: { mt: 1, mb: 1 } }, usageError ? `Usage data unavailable (${usageError}).` : "", keysError ? ` Key list unavailable (${keysError}).` : "", " Showing what loaded."), /* @__PURE__ */ React14.createElement(Grid3, { container: true, spacing: 2, sx: { mb: hasSparkline ? 1.5 : 0 } }, /* @__PURE__ */ React14.createElement(Grid3, { item: true, xs: 6 }, /* @__PURE__ */ React14.createElement(Kpi, { label: "USD Spent", value: fmtUsd(usage?.total_spend ?? 0) })), /* @__PURE__ */ React14.createElement(Grid3, { item: true, xs: 6 }, /* @__PURE__ */ React14.createElement(Kpi, { label: "Tokens In", value: fmtInt(usage?.prompt_tokens ?? 0) })), /* @__PURE__ */ React14.createElement(Grid3, { item: true, xs: 6 }, /* @__PURE__ */ React14.createElement(Kpi, { label: "Tokens Out", value: fmtInt(usage?.completion_tokens ?? 0) })), /* @__PURE__ */ React14.createElement(Grid3, { item: true, xs: 6 }, /* @__PURE__ */ React14.createElement(Kpi, { label: "Keys", value: fmtInt(keys.length) }))), hasSparkline && /* @__PURE__ */ React14.createElement(Box13, { height: 120 }, /* @__PURE__ */ React14.createElement(ResponsiveContainer3, { width: "100%", height: "100%" }, /* @__PURE__ */ React14.createElement(AreaChart3, { data: dailyData, margin: { top: 4, right: 0, bottom: 0, left: 0 } }, /* @__PURE__ */ React14.createElement(
|
|
4618
4679
|
Area3,
|
|
4619
4680
|
{
|
|
4620
4681
|
type: "monotone",
|
|
@@ -4635,21 +4696,31 @@ init_LiteLLMBudgetWidget();
|
|
|
4635
4696
|
init_api();
|
|
4636
4697
|
init_format();
|
|
4637
4698
|
init_ui();
|
|
4699
|
+
init_BudgetLimitList();
|
|
4638
4700
|
init_budget();
|
|
4639
|
-
import
|
|
4640
|
-
import
|
|
4641
|
-
import
|
|
4642
|
-
import
|
|
4701
|
+
import React15, { useCallback as useCallback3, useEffect as useEffect6, useMemo as useMemo10, useState as useState11 } from "react";
|
|
4702
|
+
import Paper10 from "@mui/material/Paper";
|
|
4703
|
+
import Box14 from "@mui/material/Box";
|
|
4704
|
+
import Button6 from "@mui/material/Button";
|
|
4705
|
+
import Typography14 from "@mui/material/Typography";
|
|
4643
4706
|
import CircularProgress8 from "@mui/material/CircularProgress";
|
|
4644
4707
|
import Alert8 from "@mui/material/Alert";
|
|
4708
|
+
import Collapse4 from "@mui/material/Collapse";
|
|
4709
|
+
import { ExpandMore as ExpandMore4 } from "@mui/icons-material";
|
|
4645
4710
|
import { useApi as useApi5 } from "@backstage/core-plugin-api";
|
|
4646
4711
|
import { Link as Link2 } from "@backstage/core-components";
|
|
4647
|
-
var
|
|
4712
|
+
var MODULE_PATH = "/litellm";
|
|
4648
4713
|
var LEVEL = {
|
|
4649
4714
|
key: { name: "Key", none: "No key budget" },
|
|
4650
4715
|
user: { name: "User", none: "No personal budget" },
|
|
4651
4716
|
team: { name: "Team", none: "No team budget" }
|
|
4652
4717
|
};
|
|
4718
|
+
var DEFAULT_CTAS = ["new-key", "module", "all-limits"];
|
|
4719
|
+
var CTA_LABELS = {
|
|
4720
|
+
"new-key": "New key",
|
|
4721
|
+
module: "Open module",
|
|
4722
|
+
"all-limits": "All limits"
|
|
4723
|
+
};
|
|
4653
4724
|
function fmtUsdShort(n) {
|
|
4654
4725
|
const v = n ?? 0;
|
|
4655
4726
|
if (v >= 100 && Number.isInteger(v)) return `$${v}`;
|
|
@@ -4672,8 +4743,8 @@ var LevelGauge = ({
|
|
|
4672
4743
|
const limit = gauge.limit;
|
|
4673
4744
|
const tone = limit ? budgetTone2(limit.pct) : "neutral";
|
|
4674
4745
|
const extra = gauge.kind === "key" ? Math.max(0, gauge.count - 1) : 0;
|
|
4675
|
-
return /* @__PURE__ */
|
|
4676
|
-
|
|
4746
|
+
return /* @__PURE__ */ React15.createElement(
|
|
4747
|
+
Box14,
|
|
4677
4748
|
{
|
|
4678
4749
|
sx: {
|
|
4679
4750
|
display: "flex",
|
|
@@ -4683,7 +4754,7 @@ var LevelGauge = ({
|
|
|
4683
4754
|
flex: 1
|
|
4684
4755
|
}
|
|
4685
4756
|
},
|
|
4686
|
-
/* @__PURE__ */
|
|
4757
|
+
/* @__PURE__ */ React15.createElement(
|
|
4687
4758
|
Gauge,
|
|
4688
4759
|
{
|
|
4689
4760
|
value: limit?.pct ?? 0,
|
|
@@ -4692,8 +4763,8 @@ var LevelGauge = ({
|
|
|
4692
4763
|
label: limit ? `${Math.round(limit.pct)}%` : "\u2014"
|
|
4693
4764
|
}
|
|
4694
4765
|
),
|
|
4695
|
-
/* @__PURE__ */
|
|
4696
|
-
|
|
4766
|
+
/* @__PURE__ */ React15.createElement(
|
|
4767
|
+
Typography14,
|
|
4697
4768
|
{
|
|
4698
4769
|
sx: (theme) => ({
|
|
4699
4770
|
mt: 0.75,
|
|
@@ -4706,8 +4777,8 @@ var LevelGauge = ({
|
|
|
4706
4777
|
},
|
|
4707
4778
|
name
|
|
4708
4779
|
),
|
|
4709
|
-
limit ? /* @__PURE__ */
|
|
4710
|
-
|
|
4780
|
+
limit ? /* @__PURE__ */ React15.createElement(React15.Fragment, null, /* @__PURE__ */ React15.createElement(
|
|
4781
|
+
Typography14,
|
|
4711
4782
|
{
|
|
4712
4783
|
variant: "caption",
|
|
4713
4784
|
title: limit.sublabel ? `${limit.label} \xB7 ${limit.sublabel}` : limit.label,
|
|
@@ -4722,8 +4793,8 @@ var LevelGauge = ({
|
|
|
4722
4793
|
}
|
|
4723
4794
|
},
|
|
4724
4795
|
limit.label
|
|
4725
|
-
), /* @__PURE__ */
|
|
4726
|
-
|
|
4796
|
+
), /* @__PURE__ */ React15.createElement(
|
|
4797
|
+
Typography14,
|
|
4727
4798
|
{
|
|
4728
4799
|
variant: "caption",
|
|
4729
4800
|
color: "text.secondary",
|
|
@@ -4734,8 +4805,8 @@ var LevelGauge = ({
|
|
|
4734
4805
|
}
|
|
4735
4806
|
},
|
|
4736
4807
|
spendCaption(limit)
|
|
4737
|
-
), extra > 0 ? /* @__PURE__ */
|
|
4738
|
-
|
|
4808
|
+
), extra > 0 ? /* @__PURE__ */ React15.createElement(
|
|
4809
|
+
Typography14,
|
|
4739
4810
|
{
|
|
4740
4811
|
component: Link2,
|
|
4741
4812
|
to: keysHref,
|
|
@@ -4750,8 +4821,8 @@ var LevelGauge = ({
|
|
|
4750
4821
|
"+",
|
|
4751
4822
|
extra,
|
|
4752
4823
|
" more"
|
|
4753
|
-
) : /* @__PURE__ */
|
|
4754
|
-
|
|
4824
|
+
) : /* @__PURE__ */ React15.createElement(Typography14, { variant: "caption", color: "text.secondary", sx: { fontSize: 10.5 } }, resetLabel(limit))) : /* @__PURE__ */ React15.createElement(
|
|
4825
|
+
Typography14,
|
|
4755
4826
|
{
|
|
4756
4827
|
variant: "caption",
|
|
4757
4828
|
color: "text.secondary",
|
|
@@ -4764,7 +4835,14 @@ var LevelGauge = ({
|
|
|
4764
4835
|
var LiteLLMBudgetGauges = ({
|
|
4765
4836
|
title = "Budget",
|
|
4766
4837
|
size = 72,
|
|
4767
|
-
keysHref
|
|
4838
|
+
keysHref,
|
|
4839
|
+
moduleHref = MODULE_PATH,
|
|
4840
|
+
onCreateKey,
|
|
4841
|
+
ctas,
|
|
4842
|
+
maxExpandedKeys = 8,
|
|
4843
|
+
expanded,
|
|
4844
|
+
defaultExpanded = false,
|
|
4845
|
+
onExpandedChange,
|
|
4768
4846
|
action
|
|
4769
4847
|
}) => {
|
|
4770
4848
|
const api = useApi5(liteLlmApiRef);
|
|
@@ -4773,7 +4851,16 @@ var LiteLLMBudgetGauges = ({
|
|
|
4773
4851
|
const [user, setUser] = useState11(null);
|
|
4774
4852
|
const [teams, setTeams] = useState11([]);
|
|
4775
4853
|
const [keys, setKeys] = useState11([]);
|
|
4776
|
-
|
|
4854
|
+
const isControlled = expanded !== void 0;
|
|
4855
|
+
const [uncontrolledExpanded, setUncontrolledExpanded] = useState11(defaultExpanded);
|
|
4856
|
+
const isExpanded = isControlled ? expanded : uncontrolledExpanded;
|
|
4857
|
+
const toggleExpanded = useCallback3(() => {
|
|
4858
|
+
const next = !isExpanded;
|
|
4859
|
+
if (!isControlled) setUncontrolledExpanded(next);
|
|
4860
|
+
onExpandedChange?.(next);
|
|
4861
|
+
}, [isControlled, isExpanded, onExpandedChange]);
|
|
4862
|
+
const keysTabHref = keysHref ?? `${moduleHref}?tab=keys`;
|
|
4863
|
+
useEffect6(() => {
|
|
4777
4864
|
let cancelled = false;
|
|
4778
4865
|
setLoading(true);
|
|
4779
4866
|
setError(null);
|
|
@@ -4803,23 +4890,103 @@ var LiteLLMBudgetGauges = ({
|
|
|
4803
4890
|
);
|
|
4804
4891
|
const gauges = useMemo10(() => buildBudgetGauges(summary), [summary]);
|
|
4805
4892
|
const headline = useMemo10(() => budgetHeadline(summary), [summary]);
|
|
4893
|
+
const expandedSummary = useMemo10(
|
|
4894
|
+
() => buildBudgetSummary(user, teams, keys, maxExpandedKeys),
|
|
4895
|
+
[user, teams, keys, maxExpandedKeys]
|
|
4896
|
+
);
|
|
4897
|
+
const expandedLimits = useMemo10(
|
|
4898
|
+
() => allBudgetLimits(expandedSummary),
|
|
4899
|
+
[expandedSummary]
|
|
4900
|
+
);
|
|
4806
4901
|
const summaryText = headline.count === 0 ? "no limits apply" : `${headline.count} limit${headline.count === 1 ? "" : "s"}`;
|
|
4807
|
-
|
|
4902
|
+
const resolvedCtas = useMemo10(() => {
|
|
4903
|
+
const list = ctas ?? DEFAULT_CTAS;
|
|
4904
|
+
return list.map((cta) => typeof cta === "string" ? { kind: cta } : cta).filter((cta) => cta.kind !== "all-limits" || headline.count > 0);
|
|
4905
|
+
}, [ctas, headline.count]);
|
|
4906
|
+
const renderCta = (cta, index) => {
|
|
4907
|
+
const label = cta.label ?? CTA_LABELS[cta.kind];
|
|
4908
|
+
const key = `${cta.kind}-${index}`;
|
|
4909
|
+
switch (cta.kind) {
|
|
4910
|
+
case "new-key":
|
|
4911
|
+
return onCreateKey ? /* @__PURE__ */ React15.createElement(Button6, { key, size: "small", variant: "contained", onClick: onCreateKey }, label) : /* @__PURE__ */ React15.createElement(
|
|
4912
|
+
Button6,
|
|
4913
|
+
{
|
|
4914
|
+
key,
|
|
4915
|
+
size: "small",
|
|
4916
|
+
variant: "contained",
|
|
4917
|
+
component: Link2,
|
|
4918
|
+
to: `${moduleHref}?generate=1`
|
|
4919
|
+
},
|
|
4920
|
+
label
|
|
4921
|
+
);
|
|
4922
|
+
case "module":
|
|
4923
|
+
return /* @__PURE__ */ React15.createElement(Button6, { key, size: "small", variant: "outlined", component: Link2, to: moduleHref }, label);
|
|
4924
|
+
case "all-limits":
|
|
4925
|
+
return /* @__PURE__ */ React15.createElement(
|
|
4926
|
+
Button6,
|
|
4927
|
+
{
|
|
4928
|
+
key,
|
|
4929
|
+
size: "small",
|
|
4930
|
+
variant: "text",
|
|
4931
|
+
onClick: toggleExpanded,
|
|
4932
|
+
endIcon: /* @__PURE__ */ React15.createElement(
|
|
4933
|
+
ExpandMore4,
|
|
4934
|
+
{
|
|
4935
|
+
sx: {
|
|
4936
|
+
transform: isExpanded ? "rotate(180deg)" : "none",
|
|
4937
|
+
transition: (theme) => theme.transitions.create("transform")
|
|
4938
|
+
}
|
|
4939
|
+
}
|
|
4940
|
+
)
|
|
4941
|
+
},
|
|
4942
|
+
label
|
|
4943
|
+
);
|
|
4944
|
+
/* istanbul ignore next — exhaustiveness */
|
|
4945
|
+
default:
|
|
4946
|
+
return null;
|
|
4947
|
+
}
|
|
4948
|
+
};
|
|
4949
|
+
const hasFooter = resolvedCtas.length > 0 || !!action;
|
|
4950
|
+
return /* @__PURE__ */ React15.createElement(Paper10, { sx: { p: 2 } }, /* @__PURE__ */ React15.createElement(Box14, { sx: { display: "flex", alignItems: "center", gap: 1, mb: 1.5, minWidth: 0 } }, /* @__PURE__ */ React15.createElement(Typography14, { variant: "h6", lineHeight: 1.2, sx: { minWidth: 0, flex: 1 } }, title), !loading && !error && /* @__PURE__ */ React15.createElement(
|
|
4808
4951
|
StatusPill,
|
|
4809
4952
|
{
|
|
4810
4953
|
label: summaryText,
|
|
4811
4954
|
tone: budgetTone2(headline.closest?.pct ?? 0),
|
|
4812
4955
|
dot: headline.count > 0
|
|
4813
4956
|
}
|
|
4814
|
-
)), loading && /* @__PURE__ */
|
|
4815
|
-
|
|
4957
|
+
)), loading && /* @__PURE__ */ React15.createElement(Box14, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: 110 }, /* @__PURE__ */ React15.createElement(CircularProgress8, { size: 28 })), !loading && error && /* @__PURE__ */ React15.createElement(Alert8, { severity: "error", sx: { mt: 0.5 } }, error), !loading && !error && /* @__PURE__ */ React15.createElement(React15.Fragment, null, /* @__PURE__ */ React15.createElement(Box14, { sx: { display: "flex", alignItems: "flex-start", gap: 1 } }, gauges.map((gauge) => /* @__PURE__ */ React15.createElement(LevelGauge, { key: gauge.kind, gauge, size, keysHref: keysTabHref }))), /* @__PURE__ */ React15.createElement(Collapse4, { in: isExpanded, unmountOnExit: true }, /* @__PURE__ */ React15.createElement(
|
|
4958
|
+
Box14,
|
|
4959
|
+
{
|
|
4960
|
+
sx: {
|
|
4961
|
+
mt: 1.5,
|
|
4962
|
+
// Bound the expanded list so a user with many limits still
|
|
4963
|
+
// keeps the card compact and the toggle within reach.
|
|
4964
|
+
maxHeight: 300,
|
|
4965
|
+
overflowY: "auto"
|
|
4966
|
+
}
|
|
4967
|
+
},
|
|
4968
|
+
/* @__PURE__ */ React15.createElement(LimitListPanel, null, /* @__PURE__ */ React15.createElement(
|
|
4969
|
+
BudgetLimitList,
|
|
4970
|
+
{
|
|
4971
|
+
limits: expandedLimits,
|
|
4972
|
+
hiddenKeyCount: expandedSummary.hiddenBudgetedKeys,
|
|
4973
|
+
keysHref: keysTabHref
|
|
4974
|
+
}
|
|
4975
|
+
))
|
|
4976
|
+
))), hasFooter && /* @__PURE__ */ React15.createElement(
|
|
4977
|
+
Box14,
|
|
4816
4978
|
{
|
|
4817
4979
|
sx: (theme) => ({
|
|
4818
4980
|
mt: 2,
|
|
4819
4981
|
pt: 2,
|
|
4820
|
-
borderTop: `1px solid ${theme.palette.divider}
|
|
4982
|
+
borderTop: `1px solid ${theme.palette.divider}`,
|
|
4983
|
+
display: "flex",
|
|
4984
|
+
alignItems: "center",
|
|
4985
|
+
gap: 1,
|
|
4986
|
+
flexWrap: "wrap"
|
|
4821
4987
|
})
|
|
4822
4988
|
},
|
|
4989
|
+
resolvedCtas.map(renderCta),
|
|
4823
4990
|
action
|
|
4824
4991
|
));
|
|
4825
4992
|
};
|