@acarmisc/backstage-plugin-litellm 0.20.0 → 0.21.1

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