@acarmisc/backstage-plugin-litellm 0.20.0 → 0.21.1

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