@acarmisc/backstage-plugin-litellm 0.20.0 → 0.21.0

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