@acarmisc/backstage-plugin-litellm 0.23.0 → 0.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.esm.js CHANGED
@@ -319,7 +319,7 @@ function SegmentedControl({
319
319
  })
320
320
  );
321
321
  }
322
- var MONTHS, fmtDateShort, fmtCompact, fmtUsdCompact, CHART_COLORS, SERIES, OTHER_COLOR, ChartTooltip, SeriesLegend, StatusPill, TagChip, SectionCard, ChartCard, MetricStrip, Stat, Meter, dataTableSx, quietIconButtonSx, EmptyState;
322
+ var MONTHS, fmtDateShort, fmtCompact, fmtUsdCompact, CHART_COLORS, SERIES, OTHER_COLOR, ChartTooltip, SeriesLegend, StatusPill, TagChip, SectionCard, ChartCard, MetricStrip, Stat, Meter, Gauge, dataTableSx, quietIconButtonSx, EmptyState;
323
323
  var init_ui = __esm({
324
324
  "src/components/ui.tsx"() {
325
325
  "use strict";
@@ -683,6 +683,102 @@ var init_ui = __esm({
683
683
  }
684
684
  )
685
685
  );
686
+ Gauge = ({ value, tone = "accent", size = 72, thickness = 6, label, caption }) => {
687
+ const clamped = Math.max(0, Math.min(100, value ?? 0));
688
+ const r = (size - thickness) / 2;
689
+ const c = 2 * Math.PI * r;
690
+ const dash = clamped / 100 * c;
691
+ return /* @__PURE__ */ React.createElement(
692
+ Box,
693
+ {
694
+ sx: (theme) => ({
695
+ position: "relative",
696
+ width: size,
697
+ height: size,
698
+ flexShrink: 0,
699
+ color: toneColor(theme, tone)
700
+ })
701
+ },
702
+ /* @__PURE__ */ React.createElement(
703
+ Box,
704
+ {
705
+ component: "svg",
706
+ viewBox: `0 0 ${size} ${size}`,
707
+ width: size,
708
+ height: size,
709
+ sx: { display: "block", transform: "rotate(-90deg)" }
710
+ },
711
+ /* @__PURE__ */ React.createElement(
712
+ Box,
713
+ {
714
+ component: "circle",
715
+ cx: size / 2,
716
+ cy: size / 2,
717
+ r,
718
+ fill: "none",
719
+ strokeWidth: thickness,
720
+ sx: (theme) => ({
721
+ stroke: alpha(
722
+ theme.palette.text.primary,
723
+ theme.palette.mode === "dark" ? 0.14 : 0.09
724
+ )
725
+ })
726
+ }
727
+ ),
728
+ /* @__PURE__ */ React.createElement(
729
+ Box,
730
+ {
731
+ component: "circle",
732
+ cx: size / 2,
733
+ cy: size / 2,
734
+ r,
735
+ fill: "none",
736
+ strokeWidth: thickness,
737
+ strokeLinecap: "round",
738
+ strokeDasharray: `${dash} ${c - dash}`,
739
+ sx: (theme) => ({
740
+ stroke: "currentColor",
741
+ transition: theme.transitions.create("stroke-dasharray")
742
+ })
743
+ }
744
+ )
745
+ ),
746
+ /* @__PURE__ */ React.createElement(
747
+ Box,
748
+ {
749
+ sx: {
750
+ position: "absolute",
751
+ inset: 0,
752
+ display: "flex",
753
+ flexDirection: "column",
754
+ alignItems: "center",
755
+ justifyContent: "center",
756
+ lineHeight: 1
757
+ }
758
+ },
759
+ /* @__PURE__ */ React.createElement(
760
+ Typography,
761
+ {
762
+ sx: {
763
+ fontSize: size <= 64 ? 15 : 18,
764
+ fontWeight: 700,
765
+ fontVariantNumeric: "tabular-nums",
766
+ color: "text.primary"
767
+ }
768
+ },
769
+ label ?? `${Math.round(value)}%`
770
+ ),
771
+ caption && /* @__PURE__ */ React.createElement(
772
+ Typography,
773
+ {
774
+ variant: "caption",
775
+ sx: { fontSize: 9.5, color: "text.secondary", mt: 0.25 }
776
+ },
777
+ caption
778
+ )
779
+ )
780
+ );
781
+ };
686
782
  dataTableSx = (theme) => ({
687
783
  // Element selectors, not `.MuiTableCell-head`: host apps may set a MUI
688
784
  // classname prefix (Backstage ships `v5-`), which breaks global class targeting.
@@ -2828,6 +2924,29 @@ function buildBudgetSummary(user, teams, keys, maxKeys = 3) {
2828
2924
  teams: teamLimits
2829
2925
  };
2830
2926
  }
2927
+ function allBudgetLimits(summary) {
2928
+ return [
2929
+ ...summary.keys,
2930
+ ...summary.user ? [summary.user] : [],
2931
+ ...summary.teams
2932
+ ];
2933
+ }
2934
+ function buildBudgetGauges(summary) {
2935
+ const byLevel = {
2936
+ key: summary.keys,
2937
+ user: summary.user ? [summary.user] : [],
2938
+ team: summary.teams
2939
+ };
2940
+ return GAUGE_KINDS.map((kind) => {
2941
+ const limits = byLevel[kind];
2942
+ const count = kind === "key" ? limits.length + summary.hiddenBudgetedKeys : limits.length;
2943
+ return {
2944
+ kind,
2945
+ count,
2946
+ limit: limits.length ? limits.reduce((a, b) => b.pct > a.pct ? b : a) : void 0
2947
+ };
2948
+ });
2949
+ }
2831
2950
  function budgetHeadline(summary) {
2832
2951
  const all = [
2833
2952
  ...summary.keys,
@@ -2854,24 +2973,20 @@ function budgetTone2(pct) {
2854
2973
  if (pct >= 80) return "warning";
2855
2974
  return "accent";
2856
2975
  }
2976
+ var GAUGE_KINDS;
2857
2977
  var init_budget = __esm({
2858
2978
  "src/budget.ts"() {
2859
2979
  "use strict";
2980
+ GAUGE_KINDS = ["key", "user", "team"];
2860
2981
  }
2861
2982
  });
2862
2983
 
2863
- // src/components/LiteLLMBudgetWidget.tsx
2864
- import React7, { useEffect as useEffect3, useMemo as useMemo6, useState as useState5 } from "react";
2865
- import Paper5 from "@mui/material/Paper";
2984
+ // src/components/BudgetLimitList.tsx
2985
+ import React7 from "react";
2866
2986
  import Box7 from "@mui/material/Box";
2987
+ import Paper5 from "@mui/material/Paper";
2867
2988
  import Typography7 from "@mui/material/Typography";
2868
- import CircularProgress4 from "@mui/material/CircularProgress";
2869
- import Alert3 from "@mui/material/Alert";
2870
- import Collapse from "@mui/material/Collapse";
2871
- import IconButton4 from "@mui/material/IconButton";
2872
- import { ExpandMore as ExpandMore2 } from "@mui/icons-material";
2873
- import { alpha as alpha4 } from "@mui/material/styles";
2874
- import { useApi as useApi2 } from "@backstage/core-plugin-api";
2989
+ import { alpha as alpha4, useTheme as useTheme2 } from "@mui/material/styles";
2875
2990
  import { Link } from "@backstage/core-components";
2876
2991
  function levelToneColor(tone, theme) {
2877
2992
  switch (tone) {
@@ -2887,13 +3002,119 @@ function levelToneColor(tone, theme) {
2887
3002
  return theme.palette.primary.main;
2888
3003
  }
2889
3004
  }
2890
- var LevelFrame, EmptyLimitCard, LimitCard, CLOSEST_LEVEL_LABEL, KEYS_PATH, MoreKeysNote, HierarchyNote, TaggedLimit, FullBody, CompactBody, LiteLLMBudgetWidget;
3005
+ var LimitCard, TaggedLimit, MoreKeysNote, BudgetLimitList, LimitListPanel;
3006
+ var init_BudgetLimitList = __esm({
3007
+ "src/components/BudgetLimitList.tsx"() {
3008
+ "use strict";
3009
+ init_budget();
3010
+ init_format();
3011
+ init_ui();
3012
+ LimitCard = ({ limit }) => {
3013
+ const tone = budgetTone2(limit.pct);
3014
+ const pct = Math.round(limit.pct);
3015
+ const closeTo = limit.softLimit !== void 0 && limit.spend >= limit.softLimit;
3016
+ return /* @__PURE__ */ React7.createElement(Paper5, { variant: "outlined", sx: { px: 1.5, py: 1.25, borderRadius: 1.5 } }, /* @__PURE__ */ React7.createElement(Box7, { sx: { display: "flex", alignItems: "baseline", justifyContent: "space-between", gap: 1.5 } }, /* @__PURE__ */ React7.createElement(Box7, { sx: { minWidth: 0, flex: 1 } }, /* @__PURE__ */ React7.createElement(
3017
+ Typography7,
3018
+ {
3019
+ variant: "body2",
3020
+ sx: { fontWeight: 600, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" },
3021
+ title: limit.sublabel ? `${limit.label} \xB7 ${limit.sublabel}` : limit.label
3022
+ },
3023
+ limit.label
3024
+ ), limit.sublabel && /* @__PURE__ */ React7.createElement(
3025
+ Typography7,
3026
+ {
3027
+ variant: "caption",
3028
+ color: "text.secondary",
3029
+ sx: {
3030
+ fontSize: 10.5,
3031
+ fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
3032
+ display: "block",
3033
+ overflow: "hidden",
3034
+ textOverflow: "ellipsis",
3035
+ whiteSpace: "nowrap"
3036
+ }
3037
+ },
3038
+ limit.sublabel
3039
+ )), limit.hidden ? /* @__PURE__ */ React7.createElement(Typography7, { variant: "body2", sx: { fontWeight: 600, fontVariantNumeric: "tabular-nums", whiteSpace: "nowrap" } }, pct, "% used") : /* @__PURE__ */ React7.createElement(Typography7, { variant: "body2", sx: { fontWeight: 600, fontVariantNumeric: "tabular-nums", whiteSpace: "nowrap" } }, fmtUsd(limit.spend), /* @__PURE__ */ React7.createElement(Box7, { component: "span", color: "text.secondary", sx: { fontWeight: 400 } }, " / ", fmtUsd(limit.budget)))), /* @__PURE__ */ React7.createElement(Box7, { sx: { mt: 1 } }, /* @__PURE__ */ React7.createElement(Meter, { value: limit.pct, tone, height: 6 })), /* @__PURE__ */ React7.createElement(
3040
+ Box7,
3041
+ {
3042
+ sx: {
3043
+ display: "flex",
3044
+ justifyContent: "space-between",
3045
+ alignItems: "center",
3046
+ gap: 1,
3047
+ mt: 0.75
3048
+ }
3049
+ },
3050
+ /* @__PURE__ */ React7.createElement(Typography7, { variant: "caption", color: "text.secondary", sx: { fontVariantNumeric: "tabular-nums" } }, limit.hidden ? /* @__PURE__ */ React7.createElement(React7.Fragment, null, "Hidden by admin") : /* @__PURE__ */ React7.createElement(React7.Fragment, null, pct, "% of budget", closeTo && ` \xB7 soft-limit warning`)),
3051
+ /* @__PURE__ */ React7.createElement(Typography7, { variant: "caption", color: "text.secondary" }, fmtBudgetDuration(limit.budgetDuration) ? `resets ${fmtBudgetDuration(limit.budgetDuration)}` : "never resets")
3052
+ ));
3053
+ };
3054
+ TaggedLimit = ({ limit }) => {
3055
+ const theme = useTheme2();
3056
+ const tone = budgetTone2(limit.pct);
3057
+ return /* @__PURE__ */ React7.createElement(Box7, null, /* @__PURE__ */ React7.createElement(
3058
+ Typography7,
3059
+ {
3060
+ sx: {
3061
+ fontSize: 11,
3062
+ fontWeight: 700,
3063
+ letterSpacing: "0.08em",
3064
+ textTransform: "uppercase",
3065
+ color: tone === "accent" ? theme.palette.text.secondary : levelToneColor(tone, theme),
3066
+ mb: 0.5
3067
+ }
3068
+ },
3069
+ limit.kind
3070
+ ), /* @__PURE__ */ React7.createElement(LimitCard, { limit }));
3071
+ };
3072
+ MoreKeysNote = ({
3073
+ count,
3074
+ href,
3075
+ label
3076
+ }) => {
3077
+ const text = label ?? `+${count} more budgeted key${count > 1 ? "s" : ""} further from the cap`;
3078
+ if (!href) {
3079
+ return /* @__PURE__ */ React7.createElement(Typography7, { variant: "caption", color: "text.secondary" }, text);
3080
+ }
3081
+ return /* @__PURE__ */ React7.createElement(Link, { to: href, variant: "caption" }, text, " \u2192");
3082
+ };
3083
+ BudgetLimitList = ({ limits, hiddenKeyCount = 0, keysHref }) => /* @__PURE__ */ React7.createElement(Box7, { sx: { display: "flex", flexDirection: "column", gap: 1.25 } }, limits.map((limit) => /* @__PURE__ */ React7.createElement(TaggedLimit, { key: `${limit.kind}:${limit.sublabel ?? limit.label}`, limit })), hiddenKeyCount > 0 && /* @__PURE__ */ React7.createElement(MoreKeysNote, { count: hiddenKeyCount, href: keysHref }));
3084
+ LimitListPanel = ({ children }) => /* @__PURE__ */ React7.createElement(
3085
+ Box7,
3086
+ {
3087
+ sx: (theme) => ({
3088
+ px: 1.5,
3089
+ py: 1.5,
3090
+ borderRadius: 1.5,
3091
+ bgcolor: alpha4(theme.palette.text.primary, theme.palette.mode === "dark" ? 0.03 : 0.02)
3092
+ })
3093
+ },
3094
+ children
3095
+ );
3096
+ }
3097
+ });
3098
+
3099
+ // src/components/LiteLLMBudgetWidget.tsx
3100
+ import React8, { useEffect as useEffect3, useMemo as useMemo6, useState as useState5 } from "react";
3101
+ import Paper6 from "@mui/material/Paper";
3102
+ import Box8 from "@mui/material/Box";
3103
+ import Typography8 from "@mui/material/Typography";
3104
+ import CircularProgress4 from "@mui/material/CircularProgress";
3105
+ import Alert3 from "@mui/material/Alert";
3106
+ import Collapse from "@mui/material/Collapse";
3107
+ import IconButton4 from "@mui/material/IconButton";
3108
+ import { ExpandMore as ExpandMore2 } from "@mui/icons-material";
3109
+ import { alpha as alpha5 } from "@mui/material/styles";
3110
+ import { useApi as useApi2 } from "@backstage/core-plugin-api";
3111
+ var LevelFrame, EmptyLimitCard, CLOSEST_LEVEL_LABEL, KEYS_PATH, HierarchyNote, FullBody, CompactBody, LiteLLMBudgetWidget;
2891
3112
  var init_LiteLLMBudgetWidget = __esm({
2892
3113
  "src/components/LiteLLMBudgetWidget.tsx"() {
2893
3114
  "use strict";
2894
3115
  init_api();
2895
- init_format();
2896
3116
  init_ui();
3117
+ init_BudgetLimitList();
2897
3118
  init_budget();
2898
3119
  LevelFrame = ({
2899
3120
  rank,
@@ -2903,8 +3124,8 @@ var init_LiteLLMBudgetWidget = __esm({
2903
3124
  tone,
2904
3125
  isLast,
2905
3126
  children
2906
- }) => /* @__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(
2907
- Box7,
3127
+ }) => /* @__PURE__ */ React8.createElement(Box8, { sx: { display: "flex", gap: 1.5 } }, /* @__PURE__ */ React8.createElement(Box8, { sx: { display: "flex", flexDirection: "column", alignItems: "center", width: 26, flexShrink: 0 } }, /* @__PURE__ */ React8.createElement(
3128
+ Box8,
2908
3129
  {
2909
3130
  sx: (theme) => ({
2910
3131
  width: 26,
@@ -2913,7 +3134,7 @@ var init_LiteLLMBudgetWidget = __esm({
2913
3134
  display: "flex",
2914
3135
  alignItems: "center",
2915
3136
  justifyContent: "center",
2916
- bgcolor: alpha4(levelToneColor(tone, theme), 0.14),
3137
+ bgcolor: alpha5(levelToneColor(tone, theme), 0.14),
2917
3138
  color: levelToneColor(tone, theme),
2918
3139
  fontSize: 12,
2919
3140
  fontWeight: 700,
@@ -2921,18 +3142,18 @@ var init_LiteLLMBudgetWidget = __esm({
2921
3142
  })
2922
3143
  },
2923
3144
  rank
2924
- ), /* @__PURE__ */ React7.createElement(
2925
- Box7,
3145
+ ), /* @__PURE__ */ React8.createElement(
3146
+ Box8,
2926
3147
  {
2927
3148
  sx: (theme) => ({
2928
3149
  flex: 1,
2929
3150
  width: 2,
2930
3151
  minHeight: 14,
2931
- bgcolor: isLast ? "transparent" : alpha4(theme.palette.text.primary, 0.12)
3152
+ bgcolor: isLast ? "transparent" : alpha5(theme.palette.text.primary, 0.12)
2932
3153
  })
2933
3154
  }
2934
- )), /* @__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(
2935
- Typography7,
3155
+ )), /* @__PURE__ */ React8.createElement(Box8, { sx: { flex: 1, minWidth: 0, pb: isLast ? 0 : 2 } }, /* @__PURE__ */ React8.createElement(Box8, { sx: { display: "flex", alignItems: "baseline", gap: 1, flexWrap: "wrap", mb: 0.75 } }, /* @__PURE__ */ React8.createElement(
3156
+ Typography8,
2936
3157
  {
2937
3158
  sx: (theme) => ({
2938
3159
  fontSize: 12.5,
@@ -2943,102 +3164,42 @@ var init_LiteLLMBudgetWidget = __esm({
2943
3164
  })
2944
3165
  },
2945
3166
  name
2946
- ), /* @__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)));
2947
- EmptyLimitCard = ({ children }) => /* @__PURE__ */ React7.createElement(
2948
- Paper5,
3167
+ ), /* @__PURE__ */ React8.createElement(Typography8, { variant: "caption", color: "text.secondary", sx: { fontSize: 11.5 } }, tagline), note && /* @__PURE__ */ React8.createElement(StatusPill, { label: note, tone: "neutral", dot: false, sx: { ml: "auto" } })), /* @__PURE__ */ React8.createElement(Box8, { sx: { display: "flex", flexDirection: "column", gap: 1 } }, children)));
3168
+ EmptyLimitCard = ({ children }) => /* @__PURE__ */ React8.createElement(
3169
+ Paper6,
2949
3170
  {
2950
3171
  variant: "outlined",
2951
3172
  sx: (theme) => ({
2952
3173
  px: 1.5,
2953
3174
  py: 1.25,
2954
3175
  borderRadius: 1.5,
2955
- bgcolor: alpha4(theme.palette.text.primary, theme.palette.mode === "dark" ? 0.03 : 0.015)
3176
+ bgcolor: alpha5(theme.palette.text.primary, theme.palette.mode === "dark" ? 0.03 : 0.015)
2956
3177
  })
2957
3178
  },
2958
- /* @__PURE__ */ React7.createElement(Typography7, { variant: "body2", color: "text.secondary", sx: { fontSize: 13 } }, children)
3179
+ /* @__PURE__ */ React8.createElement(Typography8, { variant: "body2", color: "text.secondary", sx: { fontSize: 13 } }, children)
2959
3180
  );
2960
- LimitCard = ({ limit }) => {
2961
- const tone = budgetTone2(limit.pct);
2962
- const pct = Math.round(limit.pct);
2963
- const closeTo = limit.softLimit !== void 0 && limit.spend >= limit.softLimit;
2964
- 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(
2965
- Typography7,
2966
- {
2967
- variant: "body2",
2968
- sx: { fontWeight: 600, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" },
2969
- title: limit.sublabel ? `${limit.label} \xB7 ${limit.sublabel}` : limit.label
2970
- },
2971
- limit.label
2972
- ), limit.sublabel && /* @__PURE__ */ React7.createElement(
2973
- Typography7,
2974
- {
2975
- variant: "caption",
2976
- color: "text.secondary",
2977
- sx: {
2978
- fontSize: 10.5,
2979
- fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
2980
- display: "block",
2981
- overflow: "hidden",
2982
- textOverflow: "ellipsis",
2983
- whiteSpace: "nowrap"
2984
- }
2985
- },
2986
- limit.sublabel
2987
- )), limit.hidden ? /* @__PURE__ */ React7.createElement(Typography7, { variant: "body2", sx: { fontWeight: 600, fontVariantNumeric: "tabular-nums", whiteSpace: "nowrap" } }, pct, "% used") : /* @__PURE__ */ React7.createElement(Typography7, { variant: "body2", sx: { fontWeight: 600, fontVariantNumeric: "tabular-nums", whiteSpace: "nowrap" } }, fmtUsd(limit.spend), /* @__PURE__ */ React7.createElement(Box7, { component: "span", color: "text.secondary", sx: { fontWeight: 400 } }, " / ", fmtUsd(limit.budget)))), /* @__PURE__ */ React7.createElement(Box7, { sx: { mt: 1 } }, /* @__PURE__ */ React7.createElement(Meter, { value: limit.pct, tone, height: 6 })), /* @__PURE__ */ React7.createElement(
2988
- Box7,
2989
- {
2990
- sx: {
2991
- display: "flex",
2992
- justifyContent: "space-between",
2993
- alignItems: "center",
2994
- gap: 1,
2995
- mt: 0.75
2996
- }
2997
- },
2998
- /* @__PURE__ */ React7.createElement(Typography7, { variant: "caption", color: "text.secondary", sx: { fontVariantNumeric: "tabular-nums" } }, limit.hidden ? /* @__PURE__ */ React7.createElement(React7.Fragment, null, "Hidden by admin") : /* @__PURE__ */ React7.createElement(React7.Fragment, null, pct, "% of budget", closeTo && ` \xB7 soft-limit warning`)),
2999
- /* @__PURE__ */ React7.createElement(Typography7, { variant: "caption", color: "text.secondary" }, fmtBudgetDuration(limit.budgetDuration) ? `resets ${fmtBudgetDuration(limit.budgetDuration)}` : "never resets")
3000
- ));
3001
- };
3002
3181
  CLOSEST_LEVEL_LABEL = {
3003
3182
  key: "key",
3004
3183
  user: "personal",
3005
3184
  team: "team"
3006
3185
  };
3007
3186
  KEYS_PATH = "/litellm?tab=keys";
3008
- MoreKeysNote = ({ count }) => /* @__PURE__ */ React7.createElement(Link, { to: KEYS_PATH, variant: "caption" }, "+", count, " more budgeted key", count > 1 ? "s" : "", " further from the cap \u2192");
3009
- HierarchyNote = ({ withReset }) => /* @__PURE__ */ React7.createElement(
3010
- Box7,
3187
+ HierarchyNote = ({ withReset }) => /* @__PURE__ */ React8.createElement(
3188
+ Box8,
3011
3189
  {
3012
3190
  sx: (theme) => ({
3013
3191
  px: 1.5,
3014
3192
  py: 1.25,
3015
3193
  borderRadius: 1.5,
3016
3194
  border: "1px dashed",
3017
- borderColor: alpha4(theme.palette.text.primary, 0.18)
3195
+ borderColor: alpha5(theme.palette.text.primary, 0.18)
3018
3196
  })
3019
3197
  },
3020
- /* @__PURE__ */ React7.createElement(Typography7, { variant: "caption", color: "text.secondary", display: "block" }, /* @__PURE__ */ React7.createElement("b", null, "Order:"), " key \u2192 personal \u2192 team \u2192 global. The first cap you reach blocks the request; a key that belongs to a team uses the team's cap, not your personal one.", withReset && " A cap with a reset window drops to $0 when the window closes; without one it never resets.")
3198
+ /* @__PURE__ */ React8.createElement(Typography8, { variant: "caption", color: "text.secondary", display: "block" }, /* @__PURE__ */ React8.createElement("b", null, "Order:"), " key \u2192 personal \u2192 team \u2192 global. The first cap you reach blocks the request; a key that belongs to a team uses the team's cap, not your personal one.", withReset && " A cap with a reset window drops to $0 when the window closes; without one it never resets.")
3021
3199
  );
3022
- TaggedLimit = ({ limit }) => {
3023
- const tone = budgetTone2(limit.pct);
3024
- return /* @__PURE__ */ React7.createElement(Box7, null, /* @__PURE__ */ React7.createElement(
3025
- Typography7,
3026
- {
3027
- sx: (theme) => ({
3028
- fontSize: 11,
3029
- fontWeight: 700,
3030
- letterSpacing: "0.08em",
3031
- textTransform: "uppercase",
3032
- color: tone === "accent" ? theme.palette.text.secondary : levelToneColor(tone, theme),
3033
- mb: 0.5
3034
- })
3035
- },
3036
- limit.kind
3037
- ), /* @__PURE__ */ React7.createElement(LimitCard, { limit }));
3038
- };
3039
3200
  FullBody = ({ summary }) => {
3040
3201
  const hasBudgetedKeys = summary.keys.length + summary.hiddenBudgetedKeys > 0;
3041
- return /* @__PURE__ */ React7.createElement(React7.Fragment, null, /* @__PURE__ */ React7.createElement(
3202
+ return /* @__PURE__ */ React8.createElement(React8.Fragment, null, /* @__PURE__ */ React8.createElement(
3042
3203
  LevelFrame,
3043
3204
  {
3044
3205
  rank: 1,
@@ -3046,10 +3207,10 @@ var init_LiteLLMBudgetWidget = __esm({
3046
3207
  tagline: "per-key cap \xB7 highest priority",
3047
3208
  note: hasBudgetedKeys ? void 0 : "no key budgets set"
3048
3209
  },
3049
- !hasBudgetedKeys && /* @__PURE__ */ React7.createElement(EmptyLimitCard, null, "No key has a budget, so no key-level cap applies."),
3050
- summary.keys.map((k) => /* @__PURE__ */ React7.createElement(LimitCard, { key: k.sublabel ?? k.label, limit: k })),
3051
- summary.hiddenBudgetedKeys > 0 && /* @__PURE__ */ React7.createElement(MoreKeysNote, { count: summary.hiddenBudgetedKeys })
3052
- ), /* @__PURE__ */ React7.createElement(
3210
+ !hasBudgetedKeys && /* @__PURE__ */ React8.createElement(EmptyLimitCard, null, "No key has a budget, so no key-level cap applies."),
3211
+ summary.keys.map((k) => /* @__PURE__ */ React8.createElement(LimitCard, { key: k.sublabel ?? k.label, limit: k })),
3212
+ summary.hiddenBudgetedKeys > 0 && /* @__PURE__ */ React8.createElement(MoreKeysNote, { count: summary.hiddenBudgetedKeys, href: KEYS_PATH })
3213
+ ), /* @__PURE__ */ React8.createElement(
3053
3214
  LevelFrame,
3054
3215
  {
3055
3216
  rank: 2,
@@ -3057,15 +3218,15 @@ var init_LiteLLMBudgetWidget = __esm({
3057
3218
  tagline: "cap on everything you spend with your own keys",
3058
3219
  note: "skipped for team-bound keys"
3059
3220
  },
3060
- summary.user ? /* @__PURE__ */ React7.createElement(LimitCard, { limit: summary.user }) : /* @__PURE__ */ React7.createElement(EmptyLimitCard, null, "No personal budget is set on your account.")
3061
- ), /* @__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(Box7, { sx: { mt: 2 } }, /* @__PURE__ */ React7.createElement(HierarchyNote, { withReset: true })));
3221
+ summary.user ? /* @__PURE__ */ React8.createElement(LimitCard, { limit: summary.user }) : /* @__PURE__ */ React8.createElement(EmptyLimitCard, null, "No personal budget is set on your account.")
3222
+ ), /* @__PURE__ */ React8.createElement(LevelFrame, { rank: 3, name: "Team", tagline: "shared cap for all keys in a team (if any)" }, summary.teams.length > 0 ? summary.teams.map((t) => /* @__PURE__ */ React8.createElement(LimitCard, { key: t.sublabel ?? t.label, limit: t })) : /* @__PURE__ */ React8.createElement(EmptyLimitCard, null, "No team you belong to has a budget.")), /* @__PURE__ */ React8.createElement(LevelFrame, { rank: 4, name: "Global", tagline: "proxy-wide cap set by your admin", isLast: true }, /* @__PURE__ */ React8.createElement(EmptyLimitCard, null, "Configured by your admin in the proxy config \u2014 it isn't visible from this page. When set, it limits spend across every request on the proxy.")), /* @__PURE__ */ React8.createElement(Box8, { sx: { mt: 2 } }, /* @__PURE__ */ React8.createElement(HierarchyNote, { withReset: true })));
3062
3223
  };
3063
3224
  CompactBody = ({ summary }) => {
3064
3225
  const { count } = budgetHeadline(summary);
3065
3226
  if (count === 0) {
3066
- return /* @__PURE__ */ React7.createElement(Typography7, { variant: "body2", color: "text.secondary", sx: { fontSize: 13 } }, "No budget limits apply to you right now.");
3227
+ return /* @__PURE__ */ React8.createElement(Typography8, { variant: "body2", color: "text.secondary", sx: { fontSize: 13 } }, "No budget limits apply to you right now.");
3067
3228
  }
3068
- return /* @__PURE__ */ React7.createElement(Box7, { sx: { display: "flex", flexDirection: "column", gap: 1.25 } }, /* @__PURE__ */ React7.createElement(HierarchyNote, null), summary.keys.map((k) => /* @__PURE__ */ React7.createElement(TaggedLimit, { key: k.sublabel ?? k.label, limit: k })), summary.hiddenBudgetedKeys > 0 && /* @__PURE__ */ React7.createElement(MoreKeysNote, { count: summary.hiddenBudgetedKeys }), summary.user && /* @__PURE__ */ React7.createElement(TaggedLimit, { limit: summary.user }), summary.teams.map((t) => /* @__PURE__ */ React7.createElement(TaggedLimit, { key: t.sublabel ?? t.label, limit: t })));
3229
+ return /* @__PURE__ */ React8.createElement(Box8, { sx: { display: "flex", flexDirection: "column", gap: 1.25 } }, /* @__PURE__ */ React8.createElement(HierarchyNote, null), summary.keys.map((k) => /* @__PURE__ */ React8.createElement(TaggedLimit, { key: k.sublabel ?? k.label, limit: k })), summary.hiddenBudgetedKeys > 0 && /* @__PURE__ */ React8.createElement(MoreKeysNote, { count: summary.hiddenBudgetedKeys, href: KEYS_PATH }), summary.user && /* @__PURE__ */ React8.createElement(TaggedLimit, { limit: summary.user }), summary.teams.map((t) => /* @__PURE__ */ React8.createElement(TaggedLimit, { key: t.sublabel ?? t.label, limit: t })));
3069
3230
  };
3070
3231
  LiteLLMBudgetWidget = ({
3071
3232
  title = "Budget Policy",
@@ -3116,9 +3277,9 @@ var init_LiteLLMBudgetWidget = __esm({
3116
3277
  headline.closest.pct
3117
3278
  )}%`;
3118
3279
  const summaryText = headline.count === 0 ? "no limits apply" : `${limitsWord}${closestWord}`;
3119
- 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 })));
3120
- return /* @__PURE__ */ React7.createElement(Paper5, { sx: { p: 2 } }, collapsible ? /* @__PURE__ */ React7.createElement(
3121
- Box7,
3280
+ const body = /* @__PURE__ */ React8.createElement(React8.Fragment, null, loading && /* @__PURE__ */ React8.createElement(Box8, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: 140 }, /* @__PURE__ */ React8.createElement(CircularProgress4, { size: 32 })), !loading && error && /* @__PURE__ */ React8.createElement(Alert3, { severity: "error", sx: { mt: 0.5 } }, error), !loading && !error && (compact ? /* @__PURE__ */ React8.createElement(CompactBody, { summary }) : /* @__PURE__ */ React8.createElement(FullBody, { summary })));
3281
+ return /* @__PURE__ */ React8.createElement(Paper6, { sx: { p: 2 } }, collapsible ? /* @__PURE__ */ React8.createElement(
3282
+ Box8,
3122
3283
  {
3123
3284
  onClick: () => setExpanded((e) => !e),
3124
3285
  sx: {
@@ -3130,14 +3291,14 @@ var init_LiteLLMBudgetWidget = __esm({
3130
3291
  mb: expanded ? 1.5 : 0
3131
3292
  }
3132
3293
  },
3133
- /* @__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(
3294
+ /* @__PURE__ */ React8.createElement(Box8, { sx: { minWidth: 0, flex: 1 } }, /* @__PURE__ */ React8.createElement(Typography8, { variant: "h6", lineHeight: 1.2 }, title), !loading && !error && /* @__PURE__ */ React8.createElement(Box8, { sx: { mt: 0.5 } }, /* @__PURE__ */ React8.createElement(
3134
3295
  StatusPill,
3135
3296
  {
3136
3297
  label: summaryText,
3137
3298
  tone: budgetTone2(headline.closest?.pct ?? 0)
3138
3299
  }
3139
3300
  ))),
3140
- /* @__PURE__ */ React7.createElement(
3301
+ /* @__PURE__ */ React8.createElement(
3141
3302
  IconButton4,
3142
3303
  {
3143
3304
  size: "small",
@@ -3153,10 +3314,10 @@ var init_LiteLLMBudgetWidget = __esm({
3153
3314
  transition: theme.transitions.create("transform")
3154
3315
  })
3155
3316
  },
3156
- /* @__PURE__ */ React7.createElement(ExpandMore2, null)
3317
+ /* @__PURE__ */ React8.createElement(ExpandMore2, null)
3157
3318
  )
3158
- ) : /* @__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(
3159
- Box7,
3319
+ ) : /* @__PURE__ */ React8.createElement(Box8, { sx: { mb: 1.5 } }, /* @__PURE__ */ React8.createElement(Typography8, { variant: "h6", lineHeight: 1.2 }, title), /* @__PURE__ */ React8.createElement(Typography8, { variant: "caption", color: "text.secondary" }, "How LiteLLM caps spend \u2014 and where you stand right now")), collapsible ? /* @__PURE__ */ React8.createElement(Collapse, { in: expanded, unmountOnExit: true }, body) : body, action && /* @__PURE__ */ React8.createElement(
3320
+ Box8,
3160
3321
  {
3161
3322
  sx: (theme) => ({
3162
3323
  mt: 2,
@@ -3171,10 +3332,10 @@ var init_LiteLLMBudgetWidget = __esm({
3171
3332
  });
3172
3333
 
3173
3334
  // src/components/TeamUsage.tsx
3174
- import React8, { useState as useState6 } from "react";
3175
- import Paper6 from "@mui/material/Paper";
3176
- import Box8 from "@mui/material/Box";
3177
- import Typography8 from "@mui/material/Typography";
3335
+ import React9, { useState as useState6 } from "react";
3336
+ import Paper7 from "@mui/material/Paper";
3337
+ import Box9 from "@mui/material/Box";
3338
+ import Typography9 from "@mui/material/Typography";
3178
3339
  import Skeleton4 from "@mui/material/Skeleton";
3179
3340
  import Divider3 from "@mui/material/Divider";
3180
3341
  import Table4 from "@mui/material/Table";
@@ -3187,7 +3348,7 @@ import Collapse2 from "@mui/material/Collapse";
3187
3348
  import IconButton5 from "@mui/material/IconButton";
3188
3349
  import Button5 from "@mui/material/Button";
3189
3350
  import CircularProgress5 from "@mui/material/CircularProgress";
3190
- import { alpha as alpha5 } from "@mui/material/styles";
3351
+ import { alpha as alpha6 } from "@mui/material/styles";
3191
3352
  import { ExpandMore as ExpandMore3, Group, Add as Add3 } from "@mui/icons-material";
3192
3353
  import {
3193
3354
  AreaChart as AreaChart2,
@@ -3231,24 +3392,24 @@ var init_TeamUsage = __esm({
3231
3392
  const renderDailySpendSection = () => {
3232
3393
  if (hidden) return null;
3233
3394
  if (usageLoading) {
3234
- return /* @__PURE__ */ React8.createElement(Box8, { display: "flex", justifyContent: "center", py: 3 }, /* @__PURE__ */ React8.createElement(CircularProgress5, { size: 22 }));
3395
+ return /* @__PURE__ */ React9.createElement(Box9, { display: "flex", justifyContent: "center", py: 3 }, /* @__PURE__ */ React9.createElement(CircularProgress5, { size: 22 }));
3235
3396
  }
3236
3397
  if (dailyData.length === 0) return null;
3237
- return /* @__PURE__ */ React8.createElement(Box8, { mb: 2.5 }, /* @__PURE__ */ React8.createElement(
3238
- Typography8,
3398
+ return /* @__PURE__ */ React9.createElement(Box9, { mb: 2.5 }, /* @__PURE__ */ React9.createElement(
3399
+ Typography9,
3239
3400
  {
3240
3401
  variant: "caption",
3241
3402
  color: "text.secondary",
3242
3403
  sx: { display: "block", fontSize: 11, fontWeight: 700, letterSpacing: "0.07em", textTransform: "uppercase", mb: 1 }
3243
3404
  },
3244
3405
  "Daily spend"
3245
- ), /* @__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(
3406
+ ), /* @__PURE__ */ React9.createElement(Box9, { height: 150 }, /* @__PURE__ */ React9.createElement(ResponsiveContainer2, { width: "100%", height: "100%" }, /* @__PURE__ */ React9.createElement(AreaChart2, { data: dailyData, margin: { top: 4, right: 8, left: 0, bottom: 0 } }, /* @__PURE__ */ React9.createElement(CartesianGrid2, { ...chart.grid }), /* @__PURE__ */ React9.createElement(XAxis2, { dataKey: "date", tickFormatter: fmtDateShort, ...chart.axis }), /* @__PURE__ */ React9.createElement(YAxis2, { tickFormatter: fmtUsdCompact, width: 52, ...chart.axis }), /* @__PURE__ */ React9.createElement(
3246
3407
  Tooltip2,
3247
3408
  {
3248
3409
  cursor: chart.cursor,
3249
- content: /* @__PURE__ */ React8.createElement(ChartTooltip, { valueFormatter: (v) => `$${v.toFixed(4)}` })
3410
+ content: /* @__PURE__ */ React9.createElement(ChartTooltip, { valueFormatter: (v) => `$${v.toFixed(4)}` })
3250
3411
  }
3251
- ), /* @__PURE__ */ React8.createElement(
3412
+ ), /* @__PURE__ */ React9.createElement(
3252
3413
  Area2,
3253
3414
  {
3254
3415
  type: "monotone",
@@ -3261,8 +3422,8 @@ var init_TeamUsage = __esm({
3261
3422
  }
3262
3423
  )))));
3263
3424
  };
3264
- const sectionLabel = (label) => /* @__PURE__ */ React8.createElement(
3265
- Typography8,
3425
+ const sectionLabel = (label) => /* @__PURE__ */ React9.createElement(
3426
+ Typography9,
3266
3427
  {
3267
3428
  variant: "caption",
3268
3429
  color: "text.secondary",
@@ -3270,8 +3431,8 @@ var init_TeamUsage = __esm({
3270
3431
  },
3271
3432
  label
3272
3433
  );
3273
- 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(
3274
- Box8,
3434
+ return /* @__PURE__ */ React9.createElement(Paper7, { variant: "outlined", sx: { borderRadius: 2, p: 2.5 } }, /* @__PURE__ */ React9.createElement(Box9, { display: "flex", alignItems: "center", gap: 1.5 }, /* @__PURE__ */ React9.createElement(
3435
+ Box9,
3275
3436
  {
3276
3437
  sx: (theme) => ({
3277
3438
  width: 36,
@@ -3281,20 +3442,20 @@ var init_TeamUsage = __esm({
3281
3442
  display: "flex",
3282
3443
  alignItems: "center",
3283
3444
  justifyContent: "center",
3284
- bgcolor: alpha5(theme.palette.primary.main, 0.1),
3445
+ bgcolor: alpha6(theme.palette.primary.main, 0.1),
3285
3446
  color: theme.palette.primary.main
3286
3447
  })
3287
3448
  },
3288
- /* @__PURE__ */ React8.createElement(Group, { fontSize: "small" })
3289
- ), /* @__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(
3290
- Typography8,
3449
+ /* @__PURE__ */ React9.createElement(Group, { fontSize: "small" })
3450
+ ), /* @__PURE__ */ React9.createElement(Box9, { flexGrow: 1, minWidth: 0 }, /* @__PURE__ */ React9.createElement(Typography9, { variant: "subtitle1", sx: { fontWeight: 600, lineHeight: 1.3 } }, team.team_alias || "Untitled team"), /* @__PURE__ */ React9.createElement(
3451
+ Typography9,
3291
3452
  {
3292
3453
  variant: "caption",
3293
3454
  color: "text.secondary",
3294
3455
  sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 11 }
3295
3456
  },
3296
3457
  team.team_id
3297
- )), 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(
3458
+ )), isOver && /* @__PURE__ */ React9.createElement(StatusPill, { label: "Over budget", tone: "danger" }), isNear && /* @__PURE__ */ React9.createElement(StatusPill, { label: "Near limit", tone: "warning" }), canManage && onEditTeam && /* @__PURE__ */ React9.createElement(
3298
3459
  Button5,
3299
3460
  {
3300
3461
  size: "small",
@@ -3303,7 +3464,7 @@ var init_TeamUsage = __esm({
3303
3464
  sx: { textTransform: "none" }
3304
3465
  },
3305
3466
  "Edit"
3306
- ), /* @__PURE__ */ React8.createElement(
3467
+ ), /* @__PURE__ */ React9.createElement(
3307
3468
  IconButton5,
3308
3469
  {
3309
3470
  size: "small",
@@ -3316,9 +3477,9 @@ var init_TeamUsage = __esm({
3316
3477
  transition: theme.transitions.create("transform")
3317
3478
  })
3318
3479
  },
3319
- /* @__PURE__ */ React8.createElement(ExpandMore3, null)
3320
- )), /* @__PURE__ */ React8.createElement(
3321
- Box8,
3480
+ /* @__PURE__ */ React9.createElement(ExpandMore3, null)
3481
+ )), /* @__PURE__ */ React9.createElement(
3482
+ Box9,
3322
3483
  {
3323
3484
  sx: {
3324
3485
  display: "grid",
@@ -3328,35 +3489,35 @@ var init_TeamUsage = __esm({
3328
3489
  maxWidth: 640
3329
3490
  }
3330
3491
  },
3331
- /* @__PURE__ */ React8.createElement(Stat, { label: "Members", value: team.members_with_roles?.length ?? "\u2014" }),
3332
- /* @__PURE__ */ React8.createElement(Stat, { label: "Models", value: team.models?.length ? team.models.length : "All" }),
3333
- /* @__PURE__ */ React8.createElement(Stat, { label: "Budget", value: budgetStatValue(hidden, budget) }),
3334
- /* @__PURE__ */ React8.createElement(Stat, { label: "Spend", value: hidden ? "\u2014" : fmtUsd2(spend) }),
3335
- /* @__PURE__ */ React8.createElement(Stat, { label: "TPM", value: team.tpm_limit && team.tpm_limit > 0 ? team.tpm_limit.toLocaleString() : "\u2014" }),
3336
- /* @__PURE__ */ React8.createElement(Stat, { label: "RPM", value: team.rpm_limit && team.rpm_limit > 0 ? team.rpm_limit.toLocaleString() : "\u2014" })
3337
- ), hidden && /* @__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" } }, budgetPct.toFixed(0), "% used"), /* @__PURE__ */ React8.createElement(Typography8, { variant: "caption", color: "text.secondary" }, "Hidden by admin")), /* @__PURE__ */ React8.createElement(Meter, { value: budgetPct, tone: budgetTone3(isOver, isNear), height: 5 }), /* @__PURE__ */ React8.createElement(Typography8, { variant: "caption", color: "text.secondary", sx: { display: "block", mt: 0.75 } }, "Team budget is hidden \u2014 showing usage level only", team.budget_duration ? ` \xB7 resets every ${team.budget_duration}` : "", ".")), !hidden && budget > 0 && /* @__PURE__ */ 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(
3492
+ /* @__PURE__ */ React9.createElement(Stat, { label: "Members", value: team.members_with_roles?.length ?? "\u2014" }),
3493
+ /* @__PURE__ */ React9.createElement(Stat, { label: "Models", value: team.models?.length ? team.models.length : "All" }),
3494
+ /* @__PURE__ */ React9.createElement(Stat, { label: "Budget", value: budgetStatValue(hidden, budget) }),
3495
+ /* @__PURE__ */ React9.createElement(Stat, { label: "Spend", value: hidden ? "\u2014" : fmtUsd2(spend) }),
3496
+ /* @__PURE__ */ React9.createElement(Stat, { label: "TPM", value: team.tpm_limit && team.tpm_limit > 0 ? team.tpm_limit.toLocaleString() : "\u2014" }),
3497
+ /* @__PURE__ */ React9.createElement(Stat, { label: "RPM", value: team.rpm_limit && team.rpm_limit > 0 ? team.rpm_limit.toLocaleString() : "\u2014" })
3498
+ ), hidden && /* @__PURE__ */ React9.createElement(Box9, { mt: 2, maxWidth: 640 }, /* @__PURE__ */ React9.createElement(Box9, { display: "flex", justifyContent: "space-between", alignItems: "baseline", mb: 0.75 }, /* @__PURE__ */ React9.createElement(Typography9, { variant: "caption", color: "text.secondary", sx: { fontVariantNumeric: "tabular-nums" } }, budgetPct.toFixed(0), "% used"), /* @__PURE__ */ React9.createElement(Typography9, { variant: "caption", color: "text.secondary" }, "Hidden by admin")), /* @__PURE__ */ React9.createElement(Meter, { value: budgetPct, tone: budgetTone3(isOver, isNear), height: 5 }), /* @__PURE__ */ React9.createElement(Typography9, { variant: "caption", color: "text.secondary", sx: { display: "block", mt: 0.75 } }, "Team budget is hidden \u2014 showing usage level only", team.budget_duration ? ` \xB7 resets every ${team.budget_duration}` : "", ".")), !hidden && budget > 0 && /* @__PURE__ */ React9.createElement(Box9, { mt: 2, maxWidth: 640 }, /* @__PURE__ */ React9.createElement(Box9, { display: "flex", justifyContent: "space-between", alignItems: "baseline", mb: 0.75 }, /* @__PURE__ */ React9.createElement(Typography9, { variant: "caption", color: "text.secondary", sx: { fontVariantNumeric: "tabular-nums" } }, fmtUsd2(spend), " of ", fmtUsd2(budget)), /* @__PURE__ */ React9.createElement(Typography9, { variant: "caption", color: "text.secondary", sx: { fontVariantNumeric: "tabular-nums" } }, budgetPct.toFixed(0), "%")), /* @__PURE__ */ React9.createElement(Meter, { value: budgetPct, tone: budgetTone3(isOver, isNear), height: 5 })), /* @__PURE__ */ React9.createElement(Collapse2, { in: expanded, unmountOnExit: true }, /* @__PURE__ */ React9.createElement(Divider3, { sx: { my: 2.5 } }), /* @__PURE__ */ React9.createElement(Box9, { mb: 2.5 }, sectionLabel("Models"), team.models?.length ? /* @__PURE__ */ React9.createElement(Box9, { display: "flex", gap: 0.5, flexWrap: "wrap" }, team.models.map((m) => /* @__PURE__ */ React9.createElement(TagChip, { key: m, label: m, title: m }))) : /* @__PURE__ */ React9.createElement(Typography9, { variant: "body2", color: "text.secondary" }, "All models allowed")), renderDailySpendSection(), /* @__PURE__ */ React9.createElement(Box9, null, sectionLabel("Members"), team.members_with_roles?.length ? /* @__PURE__ */ React9.createElement(
3338
3499
  TableContainer4,
3339
3500
  {
3340
- component: Paper6,
3501
+ component: Paper7,
3341
3502
  variant: "outlined",
3342
3503
  sx: { borderRadius: 1.5 }
3343
3504
  },
3344
- /* @__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(
3345
- Typography8,
3505
+ /* @__PURE__ */ React9.createElement(Table4, { size: "small", sx: dataTableSx }, /* @__PURE__ */ React9.createElement(TableHead4, null, /* @__PURE__ */ React9.createElement(TableRow4, null, /* @__PURE__ */ React9.createElement(TableCell4, null, "User"), /* @__PURE__ */ React9.createElement(TableCell4, { align: "right" }, "Role"))), /* @__PURE__ */ React9.createElement(TableBody4, null, team.members_with_roles.map((m) => /* @__PURE__ */ React9.createElement(TableRow4, { key: m.user_id }, /* @__PURE__ */ React9.createElement(TableCell4, null, m.user_email ? /* @__PURE__ */ React9.createElement(React9.Fragment, null, /* @__PURE__ */ React9.createElement(Typography9, { variant: "body2" }, m.user_email), /* @__PURE__ */ React9.createElement(
3506
+ Typography9,
3346
3507
  {
3347
3508
  variant: "caption",
3348
3509
  color: "text.secondary",
3349
3510
  sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 11 }
3350
3511
  },
3351
3512
  m.user_id
3352
- )) : /* @__PURE__ */ React8.createElement(
3353
- Typography8,
3513
+ )) : /* @__PURE__ */ React9.createElement(
3514
+ Typography9,
3354
3515
  {
3355
3516
  variant: "body2",
3356
3517
  sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace" }
3357
3518
  },
3358
3519
  m.user_id
3359
- )), /* @__PURE__ */ React8.createElement(TableCell4, { align: "right" }, /* @__PURE__ */ React8.createElement(
3520
+ )), /* @__PURE__ */ React9.createElement(TableCell4, { align: "right" }, /* @__PURE__ */ React9.createElement(
3360
3521
  StatusPill,
3361
3522
  {
3362
3523
  label: m.role,
@@ -3364,7 +3525,7 @@ var init_TeamUsage = __esm({
3364
3525
  dot: false
3365
3526
  }
3366
3527
  ))))))
3367
- ) : /* @__PURE__ */ React8.createElement(Typography8, { variant: "body2", color: "text.secondary" }, "No members assigned."))));
3528
+ ) : /* @__PURE__ */ React9.createElement(Typography9, { variant: "body2", color: "text.secondary" }, "No members assigned."))));
3368
3529
  };
3369
3530
  TeamUsage = ({
3370
3531
  teams,
@@ -3376,22 +3537,22 @@ var init_TeamUsage = __esm({
3376
3537
  canCreate,
3377
3538
  onCreateTeam
3378
3539
  }) => {
3379
- const createAction = canCreate && onCreateTeam ? /* @__PURE__ */ React8.createElement(
3540
+ const createAction = canCreate && onCreateTeam ? /* @__PURE__ */ React9.createElement(
3380
3541
  Button5,
3381
3542
  {
3382
3543
  variant: "contained",
3383
3544
  color: "primary",
3384
3545
  disableElevation: true,
3385
- startIcon: /* @__PURE__ */ React8.createElement(Add3, null),
3546
+ startIcon: /* @__PURE__ */ React9.createElement(Add3, null),
3386
3547
  onClick: onCreateTeam
3387
3548
  },
3388
3549
  "Create Team"
3389
3550
  ) : void 0;
3390
3551
  if (loading) {
3391
- 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 }));
3552
+ return /* @__PURE__ */ React9.createElement(SectionCard, { title: "Teams", actions: createAction }, /* @__PURE__ */ React9.createElement(Skeleton4, { variant: "rounded", height: 120, sx: { mb: 2 } }), /* @__PURE__ */ React9.createElement(Skeleton4, { variant: "rounded", height: 120 }));
3392
3553
  }
3393
3554
  if (!teams.length) {
3394
- return /* @__PURE__ */ React8.createElement(SectionCard, { title: "Teams", actions: createAction }, /* @__PURE__ */ React8.createElement(
3555
+ return /* @__PURE__ */ React9.createElement(SectionCard, { title: "Teams", actions: createAction }, /* @__PURE__ */ React9.createElement(
3395
3556
  EmptyState,
3396
3557
  {
3397
3558
  message: "You're not a member of any LiteLLM team yet.",
@@ -3399,14 +3560,14 @@ var init_TeamUsage = __esm({
3399
3560
  }
3400
3561
  ));
3401
3562
  }
3402
- return /* @__PURE__ */ React8.createElement(
3563
+ return /* @__PURE__ */ React9.createElement(
3403
3564
  SectionCard,
3404
3565
  {
3405
3566
  title: "Teams",
3406
3567
  subtitle: `${teams.length} team${teams.length === 1 ? "" : "s"}`,
3407
3568
  actions: createAction
3408
3569
  },
3409
- /* @__PURE__ */ React8.createElement(Box8, { sx: { display: "flex", flexDirection: "column", gap: 2 } }, teams.map((team) => /* @__PURE__ */ React8.createElement(
3570
+ /* @__PURE__ */ React9.createElement(Box9, { sx: { display: "flex", flexDirection: "column", gap: 2 } }, teams.map((team) => /* @__PURE__ */ React9.createElement(
3410
3571
  TeamCard,
3411
3572
  {
3412
3573
  key: team.team_id,
@@ -3423,8 +3584,8 @@ var init_TeamUsage = __esm({
3423
3584
  });
3424
3585
 
3425
3586
  // src/components/ModelsTable.tsx
3426
- import React9, { useState as useState7, useMemo as useMemo7, useCallback } from "react";
3427
- import Box9 from "@mui/material/Box";
3587
+ import React10, { useState as useState7, useMemo as useMemo7, useCallback } from "react";
3588
+ import Box10 from "@mui/material/Box";
3428
3589
  import Table5 from "@mui/material/Table";
3429
3590
  import TableBody5 from "@mui/material/TableBody";
3430
3591
  import TableCell5 from "@mui/material/TableCell";
@@ -3432,7 +3593,7 @@ import TableContainer5 from "@mui/material/TableContainer";
3432
3593
  import TableHead5 from "@mui/material/TableHead";
3433
3594
  import TableRow5 from "@mui/material/TableRow";
3434
3595
  import TableSortLabel2 from "@mui/material/TableSortLabel";
3435
- import Typography9 from "@mui/material/Typography";
3596
+ import Typography10 from "@mui/material/Typography";
3436
3597
  import MenuItem4 from "@mui/material/MenuItem";
3437
3598
  import Select from "@mui/material/Select";
3438
3599
  import FormControl from "@mui/material/FormControl";
@@ -3444,7 +3605,7 @@ import ContentCopyIcon from "@mui/icons-material/ContentCopy";
3444
3605
  import Tooltip3 from "@mui/material/Tooltip";
3445
3606
  import Snackbar from "@mui/material/Snackbar";
3446
3607
  import Alert4 from "@mui/material/Alert";
3447
- import { alpha as alpha6, useTheme as useTheme2 } from "@mui/material/styles";
3608
+ import { alpha as alpha7, useTheme as useTheme3 } from "@mui/material/styles";
3448
3609
  function isModelAllowedByTeam2(model, teamModels) {
3449
3610
  if (!teamModels || teamModels.length === 0 || teamModels.includes(ALL_PROXY_MODELS2)) return true;
3450
3611
  return teamModels.includes(model.model_name) || !!model.access_groups?.some((group) => teamModels.includes(group));
@@ -3469,7 +3630,7 @@ var init_ModelsTable = __esm({
3469
3630
  init_ui();
3470
3631
  ALL_PROXY_MODELS2 = "all-proxy-models";
3471
3632
  ModelsTable = ({ allModels, teams, loading }) => {
3472
- const theme = useTheme2();
3633
+ const theme = useTheme3();
3473
3634
  const [selectedTeamId, setSelectedTeamId] = useState7("");
3474
3635
  const [filterText, setFilterText] = useState7("");
3475
3636
  const [copiedSnackbar, setCopiedSnackbar] = useState7(false);
@@ -3555,13 +3716,13 @@ var init_ModelsTable = __esm({
3555
3716
  return theme.palette.info.main;
3556
3717
  }
3557
3718
  };
3558
- return /* @__PURE__ */ React9.createElement(
3719
+ return /* @__PURE__ */ React10.createElement(
3559
3720
  SectionCard,
3560
3721
  {
3561
3722
  title: "Available Models",
3562
3723
  subtitle: selectedTeam ? `${filteredModels.length}${filterText.trim() ? ` of ${teamModels.length}` : ""} model${filteredModels.length === 1 ? "" : "s"}` : "Select a team to view its models"
3563
3724
  },
3564
- /* @__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(
3725
+ /* @__PURE__ */ React10.createElement(Box10, { sx: { px: 2.5, pb: 2.5, pt: 1, display: "flex", gap: 2, flexWrap: "wrap" } }, /* @__PURE__ */ React10.createElement(FormControl, { size: "small", sx: { minWidth: 240, mb: 2 } }, /* @__PURE__ */ React10.createElement(InputLabel, { id: "team-select-label" }, "Team"), /* @__PURE__ */ React10.createElement(
3565
3726
  Select,
3566
3727
  {
3567
3728
  labelId: "team-select-label",
@@ -3569,8 +3730,8 @@ var init_ModelsTable = __esm({
3569
3730
  label: "Team",
3570
3731
  onChange: (e) => setSelectedTeamId(e.target.value)
3571
3732
  },
3572
- teams.map((t) => /* @__PURE__ */ React9.createElement(MenuItem4, { key: t.team_id, value: t.team_id }, t.team_alias ?? t.team_id))
3573
- )), /* @__PURE__ */ React9.createElement(
3733
+ teams.map((t) => /* @__PURE__ */ React10.createElement(MenuItem4, { key: t.team_id, value: t.team_id }, t.team_alias ?? t.team_id))
3734
+ )), /* @__PURE__ */ React10.createElement(
3574
3735
  TextField5,
3575
3736
  {
3576
3737
  label: "Filter Models",
@@ -3580,33 +3741,33 @@ var init_ModelsTable = __esm({
3580
3741
  disabled: !selectedTeamId,
3581
3742
  sx: { minWidth: 240, mb: 2 },
3582
3743
  InputProps: {
3583
- startAdornment: /* @__PURE__ */ React9.createElement(InputAdornment2, { position: "start" }, /* @__PURE__ */ React9.createElement(Search2, { fontSize: "small" }))
3744
+ startAdornment: /* @__PURE__ */ React10.createElement(InputAdornment2, { position: "start" }, /* @__PURE__ */ React10.createElement(Search2, { fontSize: "small" }))
3584
3745
  }
3585
3746
  }
3586
3747
  )),
3587
- loading && /* @__PURE__ */ React9.createElement(EmptyState, { message: "Loading models\u2026" }),
3588
- !loading && !selectedTeamId && /* @__PURE__ */ React9.createElement(
3748
+ loading && /* @__PURE__ */ React10.createElement(EmptyState, { message: "Loading models\u2026" }),
3749
+ !loading && !selectedTeamId && /* @__PURE__ */ React10.createElement(
3589
3750
  EmptyState,
3590
3751
  {
3591
3752
  message: "Select a team to see available models",
3592
3753
  hint: "Each team may have a different set of models assigned"
3593
3754
  }
3594
3755
  ),
3595
- !loading && selectedTeamId && teamModels.length === 0 && /* @__PURE__ */ React9.createElement(
3756
+ !loading && selectedTeamId && teamModels.length === 0 && /* @__PURE__ */ React10.createElement(
3596
3757
  EmptyState,
3597
3758
  {
3598
3759
  message: "No models available for this team",
3599
3760
  hint: allModels.length === 0 ? "No models are configured in the system" : "None of the team's assigned models were found in the catalog"
3600
3761
  }
3601
3762
  ),
3602
- !loading && selectedTeamId && teamModels.length > 0 && filteredModels.length === 0 && /* @__PURE__ */ React9.createElement(
3763
+ !loading && selectedTeamId && teamModels.length > 0 && filteredModels.length === 0 && /* @__PURE__ */ React10.createElement(
3603
3764
  EmptyState,
3604
3765
  {
3605
3766
  message: "No models match your search",
3606
3767
  hint: "Try a different search term"
3607
3768
  }
3608
3769
  ),
3609
- !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, { sortDirection: sortKey === "model_name" ? sortDirection : false }, /* @__PURE__ */ React9.createElement(TableSortLabel2, { active: sortKey === "model_name", direction: sortKey === "model_name" ? sortDirection : "asc", onClick: () => handleSort("model_name") }, "Model ID")), /* @__PURE__ */ React9.createElement(TableCell5, { sortDirection: sortKey === "mode" ? sortDirection : false }, /* @__PURE__ */ React9.createElement(TableSortLabel2, { active: sortKey === "mode", direction: sortKey === "mode" ? sortDirection : "asc", onClick: () => handleSort("mode") }, "Mode")), /* @__PURE__ */ React9.createElement(TableCell5, { align: "right", sortDirection: sortKey === "input_cost_per_token" ? sortDirection : false }, /* @__PURE__ */ React9.createElement(TableSortLabel2, { active: sortKey === "input_cost_per_token", direction: sortKey === "input_cost_per_token" ? sortDirection : "asc", onClick: () => handleSort("input_cost_per_token") }, "Input Cost")), /* @__PURE__ */ React9.createElement(TableCell5, { align: "right", sortDirection: sortKey === "output_cost_per_token" ? sortDirection : false }, /* @__PURE__ */ React9.createElement(TableSortLabel2, { active: sortKey === "output_cost_per_token", direction: sortKey === "output_cost_per_token" ? sortDirection : "asc", onClick: () => handleSort("output_cost_per_token") }, "Output Cost")), /* @__PURE__ */ React9.createElement(TableCell5, { align: "right", sortDirection: sortKey === "max_input_tokens" ? sortDirection : false }, /* @__PURE__ */ React9.createElement(TableSortLabel2, { active: sortKey === "max_input_tokens", direction: sortKey === "max_input_tokens" ? sortDirection : "asc", onClick: () => handleSort("max_input_tokens") }, "Max Input")), /* @__PURE__ */ React9.createElement(TableCell5, { align: "right", sortDirection: sortKey === "max_output_tokens" ? sortDirection : false }, /* @__PURE__ */ React9.createElement(TableSortLabel2, { active: sortKey === "max_output_tokens", direction: sortKey === "max_output_tokens" ? sortDirection : "asc", onClick: () => handleSort("max_output_tokens") }, "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(
3770
+ !loading && filteredModels.length > 0 && /* @__PURE__ */ React10.createElement(TableContainer5, null, /* @__PURE__ */ React10.createElement(Table5, { size: "small", sx: dataTableSx }, /* @__PURE__ */ React10.createElement(TableHead5, null, /* @__PURE__ */ React10.createElement(TableRow5, null, /* @__PURE__ */ React10.createElement(TableCell5, { sortDirection: sortKey === "model_name" ? sortDirection : false }, /* @__PURE__ */ React10.createElement(TableSortLabel2, { active: sortKey === "model_name", direction: sortKey === "model_name" ? sortDirection : "asc", onClick: () => handleSort("model_name") }, "Model ID")), /* @__PURE__ */ React10.createElement(TableCell5, { sortDirection: sortKey === "mode" ? sortDirection : false }, /* @__PURE__ */ React10.createElement(TableSortLabel2, { active: sortKey === "mode", direction: sortKey === "mode" ? sortDirection : "asc", onClick: () => handleSort("mode") }, "Mode")), /* @__PURE__ */ React10.createElement(TableCell5, { align: "right", sortDirection: sortKey === "input_cost_per_token" ? sortDirection : false }, /* @__PURE__ */ React10.createElement(TableSortLabel2, { active: sortKey === "input_cost_per_token", direction: sortKey === "input_cost_per_token" ? sortDirection : "asc", onClick: () => handleSort("input_cost_per_token") }, "Input Cost")), /* @__PURE__ */ React10.createElement(TableCell5, { align: "right", sortDirection: sortKey === "output_cost_per_token" ? sortDirection : false }, /* @__PURE__ */ React10.createElement(TableSortLabel2, { active: sortKey === "output_cost_per_token", direction: sortKey === "output_cost_per_token" ? sortDirection : "asc", onClick: () => handleSort("output_cost_per_token") }, "Output Cost")), /* @__PURE__ */ React10.createElement(TableCell5, { align: "right", sortDirection: sortKey === "max_input_tokens" ? sortDirection : false }, /* @__PURE__ */ React10.createElement(TableSortLabel2, { active: sortKey === "max_input_tokens", direction: sortKey === "max_input_tokens" ? sortDirection : "asc", onClick: () => handleSort("max_input_tokens") }, "Max Input")), /* @__PURE__ */ React10.createElement(TableCell5, { align: "right", sortDirection: sortKey === "max_output_tokens" ? sortDirection : false }, /* @__PURE__ */ React10.createElement(TableSortLabel2, { active: sortKey === "max_output_tokens", direction: sortKey === "max_output_tokens" ? sortDirection : "asc", onClick: () => handleSort("max_output_tokens") }, "Max Output")), /* @__PURE__ */ React10.createElement(TableCell5, null, "Capabilities"))), /* @__PURE__ */ React10.createElement(TableBody5, null, filteredModels.map((m) => /* @__PURE__ */ React10.createElement(TableRow5, { key: m.model_name, hover: true }, /* @__PURE__ */ React10.createElement(TableCell5, null, /* @__PURE__ */ React10.createElement(Box10, { sx: { display: "flex", alignItems: "center", gap: 0.5 } }, /* @__PURE__ */ React10.createElement(Tooltip3, { title: m.model_name, placement: "top" }, /* @__PURE__ */ React10.createElement(
3610
3771
  "span",
3611
3772
  {
3612
3773
  role: "button",
@@ -3616,9 +3777,9 @@ var init_ModelsTable = __esm({
3616
3777
  onKeyDown: (e) => e.key === "Enter" && handleCopyModelId(m.model_name),
3617
3778
  style: { cursor: "pointer", display: "inline-flex" }
3618
3779
  },
3619
- /* @__PURE__ */ React9.createElement(TagChip, { label: m.model_name, title: "Copy model ID" })
3620
- )), /* @__PURE__ */ React9.createElement(Tooltip3, { title: "Copy model ID" }, /* @__PURE__ */ React9.createElement(
3621
- Box9,
3780
+ /* @__PURE__ */ React10.createElement(TagChip, { label: m.model_name, title: "Copy model ID" })
3781
+ )), /* @__PURE__ */ React10.createElement(Tooltip3, { title: "Copy model ID" }, /* @__PURE__ */ React10.createElement(
3782
+ Box10,
3622
3783
  {
3623
3784
  component: "span",
3624
3785
  role: "button",
@@ -3628,9 +3789,9 @@ var init_ModelsTable = __esm({
3628
3789
  sx: quietIconButtonSx("accent"),
3629
3790
  style: { cursor: "pointer", display: "inline-flex" }
3630
3791
  },
3631
- /* @__PURE__ */ React9.createElement(ContentCopyIcon, { sx: { fontSize: 14 } })
3632
- )))), /* @__PURE__ */ React9.createElement(TableCell5, null, /* @__PURE__ */ React9.createElement(
3633
- Box9,
3792
+ /* @__PURE__ */ React10.createElement(ContentCopyIcon, { sx: { fontSize: 14 } })
3793
+ )))), /* @__PURE__ */ React10.createElement(TableCell5, null, /* @__PURE__ */ React10.createElement(
3794
+ Box10,
3634
3795
  {
3635
3796
  component: "span",
3636
3797
  sx: {
@@ -3641,13 +3802,13 @@ var init_ModelsTable = __esm({
3641
3802
  fontSize: 11,
3642
3803
  fontWeight: 600,
3643
3804
  textTransform: "capitalize",
3644
- bgcolor: alpha6(modeColor(m.mode), 0.1),
3805
+ bgcolor: alpha7(modeColor(m.mode), 0.1),
3645
3806
  color: modeColor(m.mode)
3646
3807
  }
3647
3808
  },
3648
3809
  m.mode
3649
- )), /* @__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" })))))))),
3650
- /* @__PURE__ */ React9.createElement(
3810
+ )), /* @__PURE__ */ React10.createElement(TableCell5, { align: "right" }, /* @__PURE__ */ React10.createElement(Typography10, { variant: "body2", sx: { fontVariantNumeric: "tabular-nums" } }, fmtCostPerToken(m.input_cost_per_token))), /* @__PURE__ */ React10.createElement(TableCell5, { align: "right" }, /* @__PURE__ */ React10.createElement(Typography10, { variant: "body2", sx: { fontVariantNumeric: "tabular-nums" } }, fmtCostPerToken(m.output_cost_per_token))), /* @__PURE__ */ React10.createElement(TableCell5, { align: "right" }, /* @__PURE__ */ React10.createElement(Typography10, { variant: "body2", sx: { fontVariantNumeric: "tabular-nums" } }, fmtTokens(m.max_input_tokens))), /* @__PURE__ */ React10.createElement(TableCell5, { align: "right" }, /* @__PURE__ */ React10.createElement(Typography10, { variant: "body2", sx: { fontVariantNumeric: "tabular-nums" } }, fmtTokens(m.max_output_tokens))), /* @__PURE__ */ React10.createElement(TableCell5, null, /* @__PURE__ */ React10.createElement(Box10, { sx: { display: "flex", gap: 0.5, flexWrap: "wrap" } }, m.supports_function_calling && /* @__PURE__ */ React10.createElement(TagChip, { label: "Fn", title: "Function calling" }), m.supports_vision && /* @__PURE__ */ React10.createElement(TagChip, { label: "\u{1F441}", title: "Vision" })))))))),
3811
+ /* @__PURE__ */ React10.createElement(
3651
3812
  Snackbar,
3652
3813
  {
3653
3814
  open: copiedSnackbar,
@@ -3655,7 +3816,7 @@ var init_ModelsTable = __esm({
3655
3816
  onClose: () => setCopiedSnackbar(false),
3656
3817
  anchorOrigin: { vertical: "bottom", horizontal: "center" }
3657
3818
  },
3658
- /* @__PURE__ */ React9.createElement(Alert4, { severity: "success", variant: "filled", sx: { fontSize: 13 } }, "Model ID copied to clipboard")
3819
+ /* @__PURE__ */ React10.createElement(Alert4, { severity: "success", variant: "filled", sx: { fontSize: 13 } }, "Model ID copied to clipboard")
3659
3820
  )
3660
3821
  );
3661
3822
  };
@@ -3663,9 +3824,9 @@ var init_ModelsTable = __esm({
3663
3824
  });
3664
3825
 
3665
3826
  // src/components/AuditLog.tsx
3666
- import React10, { useState as useState8, useMemo as useMemo8 } from "react";
3667
- import Box10 from "@mui/material/Box";
3668
- import Typography10 from "@mui/material/Typography";
3827
+ import React11, { useState as useState8, useMemo as useMemo8 } from "react";
3828
+ import Box11 from "@mui/material/Box";
3829
+ import Typography11 from "@mui/material/Typography";
3669
3830
  import Table6 from "@mui/material/Table";
3670
3831
  import TableBody6 from "@mui/material/TableBody";
3671
3832
  import TableCell6 from "@mui/material/TableCell";
@@ -3679,7 +3840,7 @@ import Skeleton5 from "@mui/material/Skeleton";
3679
3840
  import Collapse3 from "@mui/material/Collapse";
3680
3841
  import IconButton6 from "@mui/material/IconButton";
3681
3842
  import Alert5 from "@mui/material/Alert";
3682
- import { alpha as alpha7 } from "@mui/material/styles";
3843
+ import { alpha as alpha8 } from "@mui/material/styles";
3683
3844
  import { KeyboardArrowDown } from "@mui/icons-material";
3684
3845
  import { useAsync as useAsync2 } from "react-use";
3685
3846
  function actionTone(action) {
@@ -3702,10 +3863,10 @@ function formatDateTime(iso) {
3702
3863
  }
3703
3864
  function renderAuditLogBody(loading, entries) {
3704
3865
  if (loading) {
3705
- return /* @__PURE__ */ React10.createElement(TableRow6, null, /* @__PURE__ */ React10.createElement(TableCell6, { colSpan: 6, sx: { py: 2 } }, /* @__PURE__ */ React10.createElement(Skeleton5, { variant: "rounded", height: 160 })));
3866
+ return /* @__PURE__ */ React11.createElement(TableRow6, null, /* @__PURE__ */ React11.createElement(TableCell6, { colSpan: 6, sx: { py: 2 } }, /* @__PURE__ */ React11.createElement(Skeleton5, { variant: "rounded", height: 160 })));
3706
3867
  }
3707
3868
  if (entries.length === 0) {
3708
- return /* @__PURE__ */ React10.createElement(TableRow6, null, /* @__PURE__ */ React10.createElement(TableCell6, { colSpan: 6 }, /* @__PURE__ */ React10.createElement(
3869
+ return /* @__PURE__ */ React11.createElement(TableRow6, null, /* @__PURE__ */ React11.createElement(TableCell6, { colSpan: 6 }, /* @__PURE__ */ React11.createElement(
3709
3870
  EmptyState,
3710
3871
  {
3711
3872
  message: "No audit events found",
@@ -3713,7 +3874,7 @@ function renderAuditLogBody(loading, entries) {
3713
3874
  }
3714
3875
  )));
3715
3876
  }
3716
- return entries.map((entry) => /* @__PURE__ */ React10.createElement(DetailRow, { key: entry.id, entry }));
3877
+ return entries.map((entry) => /* @__PURE__ */ React11.createElement(DetailRow, { key: entry.id, entry }));
3717
3878
  }
3718
3879
  var ACTION_TONES, TABLE_LABELS, DetailRow, AuditLog;
3719
3880
  var init_AuditLog = __esm({
@@ -3737,7 +3898,7 @@ var init_AuditLog = __esm({
3737
3898
  DetailRow = ({ entry }) => {
3738
3899
  const [open, setOpen] = useState8(false);
3739
3900
  const hasDetail = entry.before_value || entry.updated_values;
3740
- 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(
3901
+ return /* @__PURE__ */ React11.createElement(React11.Fragment, null, /* @__PURE__ */ React11.createElement(TableRow6, null, /* @__PURE__ */ React11.createElement(TableCell6, { sx: { width: 40, pr: 0 } }, hasDetail && /* @__PURE__ */ React11.createElement(
3741
3902
  IconButton6,
3742
3903
  {
3743
3904
  size: "small",
@@ -3750,9 +3911,9 @@ var init_AuditLog = __esm({
3750
3911
  transition: theme.transitions.create("transform")
3751
3912
  })
3752
3913
  },
3753
- /* @__PURE__ */ React10.createElement(KeyboardArrowDown, { fontSize: "small" })
3754
- )), /* @__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(
3755
- Typography10,
3914
+ /* @__PURE__ */ React11.createElement(KeyboardArrowDown, { fontSize: "small" })
3915
+ )), /* @__PURE__ */ React11.createElement(TableCell6, { sx: { whiteSpace: "nowrap" } }, /* @__PURE__ */ React11.createElement(Typography11, { variant: "body2", color: "text.secondary" }, formatDateTime(entry.updated_at))), /* @__PURE__ */ React11.createElement(TableCell6, null, entry.action && /* @__PURE__ */ React11.createElement(StatusPill, { label: entry.action, tone: actionTone(entry.action) })), /* @__PURE__ */ React11.createElement(TableCell6, null, /* @__PURE__ */ React11.createElement(Typography11, { variant: "body2" }, prettyTableName(entry.table_name))), /* @__PURE__ */ React11.createElement(TableCell6, null, /* @__PURE__ */ React11.createElement(
3916
+ Typography11,
3756
3917
  {
3757
3918
  variant: "body2",
3758
3919
  component: "code",
@@ -3761,8 +3922,8 @@ var init_AuditLog = __esm({
3761
3922
  sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 12 }
3762
3923
  },
3763
3924
  entry.object_id ? entry.object_id.slice(0, 20) + (entry.object_id.length > 20 ? "\u2026" : "") : "\u2014"
3764
- )), /* @__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(
3765
- Box10,
3925
+ )), /* @__PURE__ */ React11.createElement(TableCell6, null, entry.changed_by ?? "\u2014")), hasDetail && /* @__PURE__ */ React11.createElement(TableRow6, { sx: { "&&:hover": { bgcolor: "transparent" } } }, /* @__PURE__ */ React11.createElement(TableCell6, { colSpan: 6, sx: { "&&": { py: 0, border: 0 } } }, /* @__PURE__ */ React11.createElement(Collapse3, { in: open, unmountOnExit: true }, /* @__PURE__ */ React11.createElement(
3926
+ Box11,
3766
3927
  {
3767
3928
  display: "flex",
3768
3929
  gap: 2,
@@ -3771,11 +3932,11 @@ var init_AuditLog = __esm({
3771
3932
  p: 2,
3772
3933
  mb: 1.5,
3773
3934
  borderRadius: 1.5,
3774
- bgcolor: alpha7(theme.palette.text.primary, theme.palette.mode === "dark" ? 0.05 : 0.03)
3935
+ bgcolor: alpha8(theme.palette.text.primary, theme.palette.mode === "dark" ? 0.05 : 0.03)
3775
3936
  })
3776
3937
  },
3777
- entry.before_value && /* @__PURE__ */ React10.createElement(Box10, { flex: 1, minWidth: 200 }, /* @__PURE__ */ React10.createElement(
3778
- Typography10,
3938
+ entry.before_value && /* @__PURE__ */ React11.createElement(Box11, { flex: 1, minWidth: 200 }, /* @__PURE__ */ React11.createElement(
3939
+ Typography11,
3779
3940
  {
3780
3941
  variant: "caption",
3781
3942
  color: "text.secondary",
@@ -3784,9 +3945,9 @@ var init_AuditLog = __esm({
3784
3945
  sx: { fontSize: 10.5, fontWeight: 700, letterSpacing: "0.07em", textTransform: "uppercase" }
3785
3946
  },
3786
3947
  "Before"
3787
- ), /* @__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))),
3788
- entry.updated_values && /* @__PURE__ */ React10.createElement(Box10, { flex: 1, minWidth: 200 }, /* @__PURE__ */ React10.createElement(
3789
- Typography10,
3948
+ ), /* @__PURE__ */ React11.createElement(Typography11, { component: "pre", variant: "caption", sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", whiteSpace: "pre-wrap", wordBreak: "break-all", m: 0 } }, JSON.stringify(entry.before_value, null, 2))),
3949
+ entry.updated_values && /* @__PURE__ */ React11.createElement(Box11, { flex: 1, minWidth: 200 }, /* @__PURE__ */ React11.createElement(
3950
+ Typography11,
3790
3951
  {
3791
3952
  variant: "caption",
3792
3953
  color: "text.secondary",
@@ -3795,7 +3956,7 @@ var init_AuditLog = __esm({
3795
3956
  sx: { fontSize: 10.5, fontWeight: 700, letterSpacing: "0.07em", textTransform: "uppercase" }
3796
3957
  },
3797
3958
  "After"
3798
- ), /* @__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)))
3959
+ ), /* @__PURE__ */ React11.createElement(Typography11, { component: "pre", variant: "caption", sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", whiteSpace: "pre-wrap", wordBreak: "break-all", m: 0 } }, JSON.stringify(entry.updated_values, null, 2)))
3799
3960
  )))));
3800
3961
  };
3801
3962
  AuditLog = ({ api }) => {
@@ -3812,13 +3973,13 @@ var init_AuditLog = __esm({
3812
3973
  );
3813
3974
  const entries = value?.audit_logs ?? [];
3814
3975
  const total = value?.total ?? 0;
3815
- return /* @__PURE__ */ React10.createElement(
3976
+ return /* @__PURE__ */ React11.createElement(
3816
3977
  SectionCard,
3817
3978
  {
3818
3979
  title: "Audit Log",
3819
3980
  subtitle: loading ? "Loading\u2026" : `${total.toLocaleString()} event${total === 1 ? "" : "s"}`,
3820
3981
  flush: true,
3821
- actions: /* @__PURE__ */ React10.createElement(React10.Fragment, null, /* @__PURE__ */ React10.createElement(
3982
+ actions: /* @__PURE__ */ React11.createElement(React11.Fragment, null, /* @__PURE__ */ React11.createElement(
3822
3983
  TextField6,
3823
3984
  {
3824
3985
  size: "small",
@@ -3831,12 +3992,12 @@ var init_AuditLog = __esm({
3831
3992
  },
3832
3993
  sx: { minWidth: 150 }
3833
3994
  },
3834
- /* @__PURE__ */ React10.createElement(MenuItem5, { value: "" }, "All actions"),
3835
- /* @__PURE__ */ React10.createElement(MenuItem5, { value: "created" }, "Created"),
3836
- /* @__PURE__ */ React10.createElement(MenuItem5, { value: "updated" }, "Updated"),
3837
- /* @__PURE__ */ React10.createElement(MenuItem5, { value: "deleted" }, "Deleted"),
3838
- /* @__PURE__ */ React10.createElement(MenuItem5, { value: "blocked" }, "Blocked")
3839
- ), /* @__PURE__ */ React10.createElement(
3995
+ /* @__PURE__ */ React11.createElement(MenuItem5, { value: "" }, "All actions"),
3996
+ /* @__PURE__ */ React11.createElement(MenuItem5, { value: "created" }, "Created"),
3997
+ /* @__PURE__ */ React11.createElement(MenuItem5, { value: "updated" }, "Updated"),
3998
+ /* @__PURE__ */ React11.createElement(MenuItem5, { value: "deleted" }, "Deleted"),
3999
+ /* @__PURE__ */ React11.createElement(MenuItem5, { value: "blocked" }, "Blocked")
4000
+ ), /* @__PURE__ */ React11.createElement(
3840
4001
  TextField6,
3841
4002
  {
3842
4003
  size: "small",
@@ -3849,13 +4010,13 @@ var init_AuditLog = __esm({
3849
4010
  },
3850
4011
  sx: { minWidth: 150 }
3851
4012
  },
3852
- /* @__PURE__ */ React10.createElement(MenuItem5, { value: "" }, "All tables"),
3853
- /* @__PURE__ */ React10.createElement(MenuItem5, { value: "LiteLLM_VerificationToken" }, "Key"),
3854
- /* @__PURE__ */ React10.createElement(MenuItem5, { value: "LiteLLM_TeamTable" }, "Team"),
3855
- /* @__PURE__ */ React10.createElement(MenuItem5, { value: "LiteLLM_TeamMembership" }, "Team member"),
3856
- /* @__PURE__ */ React10.createElement(MenuItem5, { value: "LiteLLM_ObjectPermissionTable" }, "Team access (KB / MCP)"),
3857
- /* @__PURE__ */ React10.createElement(MenuItem5, { value: "LiteLLM_UserTable" }, "User")
3858
- ), /* @__PURE__ */ React10.createElement(
4013
+ /* @__PURE__ */ React11.createElement(MenuItem5, { value: "" }, "All tables"),
4014
+ /* @__PURE__ */ React11.createElement(MenuItem5, { value: "LiteLLM_VerificationToken" }, "Key"),
4015
+ /* @__PURE__ */ React11.createElement(MenuItem5, { value: "LiteLLM_TeamTable" }, "Team"),
4016
+ /* @__PURE__ */ React11.createElement(MenuItem5, { value: "LiteLLM_TeamMembership" }, "Team member"),
4017
+ /* @__PURE__ */ React11.createElement(MenuItem5, { value: "LiteLLM_ObjectPermissionTable" }, "Team access (KB / MCP)"),
4018
+ /* @__PURE__ */ React11.createElement(MenuItem5, { value: "LiteLLM_UserTable" }, "User")
4019
+ ), /* @__PURE__ */ React11.createElement(
3859
4020
  TextField6,
3860
4021
  {
3861
4022
  size: "small",
@@ -3869,9 +4030,9 @@ var init_AuditLog = __esm({
3869
4030
  }
3870
4031
  ))
3871
4032
  },
3872
- error && /* @__PURE__ */ React10.createElement(Box10, { px: 2.5, pb: 2 }, /* @__PURE__ */ React10.createElement(Alert5, { severity: "error" }, error.message)),
3873
- /* @__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)))),
3874
- /* @__PURE__ */ React10.createElement(
4033
+ error && /* @__PURE__ */ React11.createElement(Box11, { px: 2.5, pb: 2 }, /* @__PURE__ */ React11.createElement(Alert5, { severity: "error" }, error.message)),
4034
+ /* @__PURE__ */ React11.createElement(TableContainer6, null, /* @__PURE__ */ React11.createElement(Table6, { size: "small", sx: dataTableSx }, /* @__PURE__ */ React11.createElement(TableHead6, null, /* @__PURE__ */ React11.createElement(TableRow6, null, /* @__PURE__ */ React11.createElement(TableCell6, { sx: { width: 40 } }), /* @__PURE__ */ React11.createElement(TableCell6, null, "Time"), /* @__PURE__ */ React11.createElement(TableCell6, null, "Action"), /* @__PURE__ */ React11.createElement(TableCell6, null, "Table"), /* @__PURE__ */ React11.createElement(TableCell6, null, "Object ID"), /* @__PURE__ */ React11.createElement(TableCell6, null, "Changed By"))), /* @__PURE__ */ React11.createElement(TableBody6, null, renderAuditLogBody(loading, entries)))),
4035
+ /* @__PURE__ */ React11.createElement(
3875
4036
  TablePagination,
3876
4037
  {
3877
4038
  component: "div",
@@ -3926,13 +4087,13 @@ var LiteLLMPage_exports = {};
3926
4087
  __export(LiteLLMPage_exports, {
3927
4088
  LiteLLMPage: () => LiteLLMPage
3928
4089
  });
3929
- import React11, { useState as useState9, useCallback as useCallback2, useMemo as useMemo9 } from "react";
3930
- import Box11 from "@mui/material/Box";
4090
+ import React12, { useState as useState9, useCallback as useCallback2, useEffect as useEffect4, useMemo as useMemo9 } from "react";
4091
+ import Box12 from "@mui/material/Box";
3931
4092
  import Snackbar2 from "@mui/material/Snackbar";
3932
4093
  import Alert6 from "@mui/material/Alert";
3933
4094
  import CircularProgress6 from "@mui/material/CircularProgress";
3934
- import Typography11 from "@mui/material/Typography";
3935
- import Paper7 from "@mui/material/Paper";
4095
+ import Typography12 from "@mui/material/Typography";
4096
+ import Paper8 from "@mui/material/Paper";
3936
4097
  import Tabs2 from "@mui/material/Tabs";
3937
4098
  import Tab2 from "@mui/material/Tab";
3938
4099
  import Grid2 from "@mui/material/Grid";
@@ -3987,6 +4148,21 @@ var init_LiteLLMPage = __esm({
3987
4148
  const t = searchParams.get("tab");
3988
4149
  return isPageTab(t) ? t : "overview";
3989
4150
  });
4151
+ const [generateDialogOpen, setGenerateDialogOpen] = useState9(
4152
+ () => searchParams.get("generate") === "1"
4153
+ );
4154
+ useEffect4(() => {
4155
+ if (searchParams.get("generate") !== "1") return;
4156
+ setGenerateDialogOpen(true);
4157
+ setSearchParams(
4158
+ (prev) => {
4159
+ const next = new URLSearchParams(prev);
4160
+ next.delete("generate");
4161
+ return next;
4162
+ },
4163
+ { replace: true }
4164
+ );
4165
+ }, [searchParams, setSearchParams]);
3990
4166
  const selectTab = useCallback2(
3991
4167
  (tab) => {
3992
4168
  setActiveTab(tab);
@@ -4002,7 +4178,6 @@ var init_LiteLLMPage = __esm({
4002
4178
  [setSearchParams]
4003
4179
  );
4004
4180
  const [snackbar, setSnackbar] = useState9(null);
4005
- const [generateDialogOpen, setGenerateDialogOpen] = useState9(false);
4006
4181
  const [manageTeam, setManageTeam] = useState9(null);
4007
4182
  const [teamUsageCache, setTeamUsageCache] = useState9({});
4008
4183
  const [teamUsageLoading, setTeamUsageLoading] = useState9({});
@@ -4196,14 +4371,14 @@ var init_LiteLLMPage = __esm({
4196
4371
  );
4197
4372
  const isInitialLoading = userLoading && !userInfo;
4198
4373
  if (isInitialLoading) {
4199
- return /* @__PURE__ */ React11.createElement(Box11, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: "50vh" }, /* @__PURE__ */ React11.createElement(CircularProgress6, null));
4374
+ return /* @__PURE__ */ React12.createElement(Box12, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: "50vh" }, /* @__PURE__ */ React12.createElement(CircularProgress6, null));
4200
4375
  }
4201
4376
  if (userError || !userInfo) {
4202
4377
  const isProvisioningEnabled = userError?.body?.provisioning === true;
4203
4378
  const hint = userError?.body?.hint;
4204
- 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.")));
4379
+ return /* @__PURE__ */ React12.createElement(Box12, { p: 3 }, /* @__PURE__ */ React12.createElement(Paper8, { sx: { p: 3 } }, /* @__PURE__ */ React12.createElement(Typography12, { variant: "h6", gutterBottom: true }, "Account not provisioned"), /* @__PURE__ */ React12.createElement(Typography12, { color: "text.secondary", paragraph: true }, "Your Backstage account is not linked to a LiteLLM user."), hint ? /* @__PURE__ */ React12.createElement(Typography12, { variant: "body2", color: "text.secondary" }, hint) : /* @__PURE__ */ React12.createElement(Typography12, { variant: "body2", color: "text.secondary" }, isProvisioningEnabled ? "Auto-provisioning is enabled but failed. Check the backend logs." : "Set litellm.provisioning.enabled: true in app-config.yaml to enable auto-provisioning, or ask your administrator to create the account manually.")));
4205
4380
  }
4206
- const pageTabs = /* @__PURE__ */ React11.createElement(
4381
+ const pageTabs = /* @__PURE__ */ React12.createElement(
4207
4382
  Tabs2,
4208
4383
  {
4209
4384
  value: activeTab,
@@ -4216,13 +4391,13 @@ var init_LiteLLMPage = __esm({
4216
4391
  '& [class*="MuiTab-root"]': { minHeight: 44, textTransform: "none", fontSize: 14 }
4217
4392
  }
4218
4393
  },
4219
- /* @__PURE__ */ React11.createElement(Tab2, { label: "Overview", value: "overview" }),
4220
- /* @__PURE__ */ React11.createElement(Tab2, { label: "Keys", value: "keys" }),
4221
- /* @__PURE__ */ React11.createElement(Tab2, { label: "Teams", value: "teams" }),
4222
- /* @__PURE__ */ React11.createElement(Tab2, { label: "Models", value: "models" }),
4223
- userInfo.can_view_audit && /* @__PURE__ */ React11.createElement(Tab2, { label: "Audit Log", value: "audit" })
4394
+ /* @__PURE__ */ React12.createElement(Tab2, { label: "Overview", value: "overview" }),
4395
+ /* @__PURE__ */ React12.createElement(Tab2, { label: "Keys", value: "keys" }),
4396
+ /* @__PURE__ */ React12.createElement(Tab2, { label: "Teams", value: "teams" }),
4397
+ /* @__PURE__ */ React12.createElement(Tab2, { label: "Models", value: "models" }),
4398
+ userInfo.can_view_audit && /* @__PURE__ */ React12.createElement(Tab2, { label: "Audit Log", value: "audit" })
4224
4399
  );
4225
- return /* @__PURE__ */ React11.createElement(Box11, { sx: { p: 3, display: "flex", flexDirection: "column", gap: 2 } }, /* @__PURE__ */ React11.createElement(
4400
+ return /* @__PURE__ */ React12.createElement(Box12, { sx: { p: 3, display: "flex", flexDirection: "column", gap: 2 } }, /* @__PURE__ */ React12.createElement(
4226
4401
  DashboardHeader,
4227
4402
  {
4228
4403
  userInfo,
@@ -4232,7 +4407,7 @@ var init_LiteLLMPage = __esm({
4232
4407
  onGenerateKeyClick: () => setGenerateDialogOpen(true),
4233
4408
  tabs: pageTabs
4234
4409
  }
4235
- ), 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(
4410
+ ), activeTab === "overview" && /* @__PURE__ */ React12.createElement(Grid2, { container: true, spacing: 2, alignItems: "flex-start" }, /* @__PURE__ */ React12.createElement(Grid2, { item: true, xs: 12, lg: 8 }, /* @__PURE__ */ React12.createElement(
4236
4411
  UsageStats,
4237
4412
  {
4238
4413
  usage: usage ?? null,
@@ -4243,7 +4418,7 @@ var init_LiteLLMPage = __esm({
4243
4418
  loading: usageLoading,
4244
4419
  userInfo
4245
4420
  }
4246
- )), /* @__PURE__ */ React11.createElement(Grid2, { item: true, xs: 12, lg: 4 }, /* @__PURE__ */ React11.createElement(LiteLLMBudgetWidget, { compact: true, collapsible: true }))), activeTab === "keys" && /* @__PURE__ */ React11.createElement(
4421
+ )), /* @__PURE__ */ React12.createElement(Grid2, { item: true, xs: 12, lg: 4 }, /* @__PURE__ */ React12.createElement(LiteLLMBudgetWidget, { compact: true, collapsible: true }))), activeTab === "keys" && /* @__PURE__ */ React12.createElement(
4247
4422
  KeysTable,
4248
4423
  {
4249
4424
  keys: keys ?? [],
@@ -4262,7 +4437,7 @@ var init_LiteLLMPage = __esm({
4262
4437
  for (const t of managedTeams ?? []) teamsById.set(t.team_id, t);
4263
4438
  for (const t of teams ?? []) teamsById.set(t.team_id, t);
4264
4439
  const visibleTeams = Array.from(teamsById.values());
4265
- return /* @__PURE__ */ React11.createElement(
4440
+ return /* @__PURE__ */ React12.createElement(
4266
4441
  TeamUsage,
4267
4442
  {
4268
4443
  teams: visibleTeams,
@@ -4278,14 +4453,14 @@ var init_LiteLLMPage = __esm({
4278
4453
  onCreateTeam: () => setManageTeam({ mode: "create" })
4279
4454
  }
4280
4455
  );
4281
- })(), activeTab === "models" && /* @__PURE__ */ React11.createElement(
4456
+ })(), activeTab === "models" && /* @__PURE__ */ React12.createElement(
4282
4457
  ModelsTable,
4283
4458
  {
4284
4459
  allModels: allModels ?? [],
4285
4460
  teams: teams ?? [],
4286
4461
  loading: modelsLoading
4287
4462
  }
4288
- ), activeTab === "audit" && userInfo.can_view_audit && /* @__PURE__ */ React11.createElement(AuditLog, { api }), /* @__PURE__ */ React11.createElement(
4463
+ ), activeTab === "audit" && userInfo.can_view_audit && /* @__PURE__ */ React12.createElement(AuditLog, { api }), /* @__PURE__ */ React12.createElement(
4289
4464
  GenerateKeyDialog,
4290
4465
  {
4291
4466
  open: generateDialogOpen,
@@ -4298,7 +4473,7 @@ var init_LiteLLMPage = __esm({
4298
4473
  onGenerateKey: handleGenerateKey,
4299
4474
  onGetConfig: () => api.getConfig()
4300
4475
  }
4301
- ), /* @__PURE__ */ React11.createElement(
4476
+ ), /* @__PURE__ */ React12.createElement(
4302
4477
  ManageTeamDialog,
4303
4478
  {
4304
4479
  open: !!manageTeam,
@@ -4360,7 +4535,7 @@ var init_LiteLLMPage = __esm({
4360
4535
  refreshTeams();
4361
4536
  }
4362
4537
  }
4363
- ), /* @__PURE__ */ React11.createElement(
4538
+ ), /* @__PURE__ */ React12.createElement(
4364
4539
  Snackbar2,
4365
4540
  {
4366
4541
  open: !!snackbar,
@@ -4368,7 +4543,7 @@ var init_LiteLLMPage = __esm({
4368
4543
  onClose: () => setSnackbar(null),
4369
4544
  anchorOrigin: { vertical: "bottom", horizontal: "right" }
4370
4545
  },
4371
- snackbar ? /* @__PURE__ */ React11.createElement(Alert6, { severity: snackbar.severity, onClose: () => setSnackbar(null) }, snackbar.message) : void 0
4546
+ snackbar ? /* @__PURE__ */ React12.createElement(Alert6, { severity: snackbar.severity, onClose: () => setSnackbar(null) }, snackbar.message) : void 0
4372
4547
  ));
4373
4548
  };
4374
4549
  }
@@ -4376,7 +4551,7 @@ var init_LiteLLMPage = __esm({
4376
4551
 
4377
4552
  // src/plugin.tsx
4378
4553
  init_api();
4379
- import React12 from "react";
4554
+ import React13 from "react";
4380
4555
  import { TrendingUp as TrendingUpIcon } from "@mui/icons-material";
4381
4556
  import {
4382
4557
  createFrontendPlugin,
@@ -4395,10 +4570,10 @@ var liteLlmPage = PageBlueprint.make({
4395
4570
  params: {
4396
4571
  path: "/litellm",
4397
4572
  title: "LiteLLM",
4398
- icon: /* @__PURE__ */ React12.createElement(TrendingUpIcon, null),
4573
+ icon: /* @__PURE__ */ React13.createElement(TrendingUpIcon, null),
4399
4574
  loader: async () => {
4400
4575
  const { LiteLLMPage: LiteLLMPage2 } = await Promise.resolve().then(() => (init_LiteLLMPage(), LiteLLMPage_exports));
4401
- return /* @__PURE__ */ React12.createElement(LiteLLMPage2, null);
4576
+ return /* @__PURE__ */ React13.createElement(LiteLLMPage2, null);
4402
4577
  }
4403
4578
  }
4404
4579
  });
@@ -4417,10 +4592,10 @@ init_TeamUsage();
4417
4592
  // src/components/LiteLLMHomeWidget.tsx
4418
4593
  init_api();
4419
4594
  init_format();
4420
- import React13, { useState as useState10, useEffect as useEffect4 } from "react";
4421
- import Paper8 from "@mui/material/Paper";
4422
- import Box12 from "@mui/material/Box";
4423
- import Typography12 from "@mui/material/Typography";
4595
+ import React14, { useState as useState10, useEffect as useEffect5 } from "react";
4596
+ import Paper9 from "@mui/material/Paper";
4597
+ import Box13 from "@mui/material/Box";
4598
+ import Typography13 from "@mui/material/Typography";
4424
4599
  import FormControl2 from "@mui/material/FormControl";
4425
4600
  import Select2 from "@mui/material/Select";
4426
4601
  import MenuItem6 from "@mui/material/MenuItem";
@@ -4441,7 +4616,7 @@ function presetToDateRange(preset) {
4441
4616
  }
4442
4617
  return { start, end };
4443
4618
  }
4444
- 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));
4619
+ var Kpi = ({ label, value }) => /* @__PURE__ */ React14.createElement(Box13, null, /* @__PURE__ */ React14.createElement(Typography13, { variant: "caption", color: "text.secondary", display: "block" }, label), /* @__PURE__ */ React14.createElement(Typography13, { variant: "subtitle1", fontWeight: 600 }, value));
4445
4620
  var LiteLLMHomeWidget = ({
4446
4621
  defaultPeriod = "7d",
4447
4622
  title = "LiteLLM Usage"
@@ -4453,7 +4628,7 @@ var LiteLLMHomeWidget = ({
4453
4628
  const [keysError, setKeysError] = useState10(null);
4454
4629
  const [usage, setUsage] = useState10(null);
4455
4630
  const [keys, setKeys] = useState10([]);
4456
- useEffect4(() => {
4631
+ useEffect5(() => {
4457
4632
  let cancelled = false;
4458
4633
  setLoading(true);
4459
4634
  setUsageError(null);
@@ -4490,17 +4665,17 @@ var LiteLLMHomeWidget = ({
4490
4665
  spend: d.spend
4491
4666
  }));
4492
4667
  const hasSparkline = dailyData.length > 0;
4493
- 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(
4668
+ return /* @__PURE__ */ React14.createElement(Paper9, { sx: { p: 2 } }, /* @__PURE__ */ React14.createElement(Box13, { display: "flex", justifyContent: "space-between", alignItems: "center", mb: 1.5 }, /* @__PURE__ */ React14.createElement(Typography13, { variant: "h6" }, title), /* @__PURE__ */ React14.createElement(FormControl2, { size: "small", sx: { minWidth: 90 } }, /* @__PURE__ */ React14.createElement(
4494
4669
  Select2,
4495
4670
  {
4496
4671
  value: period,
4497
4672
  onChange: (e) => setPeriod(e.target.value),
4498
4673
  displayEmpty: true
4499
4674
  },
4500
- /* @__PURE__ */ React13.createElement(MenuItem6, { value: "today" }, "Today"),
4501
- /* @__PURE__ */ React13.createElement(MenuItem6, { value: "7d" }, "7d"),
4502
- /* @__PURE__ */ React13.createElement(MenuItem6, { value: "30d" }, "30d")
4503
- ))), 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(
4675
+ /* @__PURE__ */ React14.createElement(MenuItem6, { value: "today" }, "Today"),
4676
+ /* @__PURE__ */ React14.createElement(MenuItem6, { value: "7d" }, "7d"),
4677
+ /* @__PURE__ */ React14.createElement(MenuItem6, { value: "30d" }, "30d")
4678
+ ))), loading && /* @__PURE__ */ React14.createElement(Box13, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: 120 }, /* @__PURE__ */ React14.createElement(CircularProgress7, { size: 32 })), !loading && totalFailure && /* @__PURE__ */ React14.createElement(Alert7, { severity: "error", sx: { mt: 1 } }, usageError ?? "Failed to load usage data"), !loading && !totalFailure && /* @__PURE__ */ React14.createElement(React14.Fragment, null, partialFailure && /* @__PURE__ */ React14.createElement(Alert7, { severity: "warning", sx: { mt: 1, mb: 1 } }, usageError ? `Usage data unavailable (${usageError}).` : "", keysError ? ` Key list unavailable (${keysError}).` : "", " Showing what loaded."), /* @__PURE__ */ React14.createElement(Grid3, { container: true, spacing: 2, sx: { mb: hasSparkline ? 1.5 : 0 } }, /* @__PURE__ */ React14.createElement(Grid3, { item: true, xs: 6 }, /* @__PURE__ */ React14.createElement(Kpi, { label: "USD Spent", value: fmtUsd(usage?.total_spend ?? 0) })), /* @__PURE__ */ React14.createElement(Grid3, { item: true, xs: 6 }, /* @__PURE__ */ React14.createElement(Kpi, { label: "Tokens In", value: fmtInt(usage?.prompt_tokens ?? 0) })), /* @__PURE__ */ React14.createElement(Grid3, { item: true, xs: 6 }, /* @__PURE__ */ React14.createElement(Kpi, { label: "Tokens Out", value: fmtInt(usage?.completion_tokens ?? 0) })), /* @__PURE__ */ React14.createElement(Grid3, { item: true, xs: 6 }, /* @__PURE__ */ React14.createElement(Kpi, { label: "Keys", value: fmtInt(keys.length) }))), hasSparkline && /* @__PURE__ */ React14.createElement(Box13, { height: 120 }, /* @__PURE__ */ React14.createElement(ResponsiveContainer3, { width: "100%", height: "100%" }, /* @__PURE__ */ React14.createElement(AreaChart3, { data: dailyData, margin: { top: 4, right: 0, bottom: 0, left: 0 } }, /* @__PURE__ */ React14.createElement(
4504
4679
  Area3,
4505
4680
  {
4506
4681
  type: "monotone",
@@ -4516,6 +4691,307 @@ var LiteLLMHomeWidget = ({
4516
4691
 
4517
4692
  // src/index.ts
4518
4693
  init_LiteLLMBudgetWidget();
4694
+
4695
+ // src/components/LiteLLMBudgetGauges.tsx
4696
+ init_api();
4697
+ init_format();
4698
+ init_ui();
4699
+ init_BudgetLimitList();
4700
+ init_budget();
4701
+ import React15, { useCallback as useCallback3, useEffect as useEffect6, useMemo as useMemo10, useState as useState11 } from "react";
4702
+ import Paper10 from "@mui/material/Paper";
4703
+ import Box14 from "@mui/material/Box";
4704
+ import Button6 from "@mui/material/Button";
4705
+ import Typography14 from "@mui/material/Typography";
4706
+ import CircularProgress8 from "@mui/material/CircularProgress";
4707
+ import Alert8 from "@mui/material/Alert";
4708
+ import Collapse4 from "@mui/material/Collapse";
4709
+ import { ExpandMore as ExpandMore4 } from "@mui/icons-material";
4710
+ import { useApi as useApi5 } from "@backstage/core-plugin-api";
4711
+ import { Link as Link2 } from "@backstage/core-components";
4712
+ var MODULE_PATH = "/litellm";
4713
+ var LEVEL = {
4714
+ key: { name: "Key", none: "No key budget" },
4715
+ user: { name: "User", none: "No personal budget" },
4716
+ team: { name: "Team", none: "No team budget" }
4717
+ };
4718
+ var DEFAULT_CTAS = ["new-key", "module", "all-limits"];
4719
+ var CTA_LABELS = {
4720
+ "new-key": "New key",
4721
+ module: "Open module",
4722
+ "all-limits": "All limits"
4723
+ };
4724
+ function fmtUsdShort(n) {
4725
+ const v = n ?? 0;
4726
+ if (v >= 100 && Number.isInteger(v)) return `$${v}`;
4727
+ return fmtUsd(v);
4728
+ }
4729
+ function spendCaption(limit) {
4730
+ if (limit.hidden) return "hidden by admin";
4731
+ return `${fmtUsdShort(limit.spend)} / ${fmtUsdShort(limit.budget)}`;
4732
+ }
4733
+ function resetLabel(limit) {
4734
+ const window2 = fmtBudgetDuration(limit.budgetDuration);
4735
+ return window2 ? `resets ${window2}` : "never resets";
4736
+ }
4737
+ var LevelGauge = ({
4738
+ gauge,
4739
+ size,
4740
+ keysHref
4741
+ }) => {
4742
+ const { name, none } = LEVEL[gauge.kind];
4743
+ const limit = gauge.limit;
4744
+ const tone = limit ? budgetTone2(limit.pct) : "neutral";
4745
+ const extra = gauge.kind === "key" ? Math.max(0, gauge.count - 1) : 0;
4746
+ return /* @__PURE__ */ React15.createElement(
4747
+ Box14,
4748
+ {
4749
+ sx: {
4750
+ display: "flex",
4751
+ flexDirection: "column",
4752
+ alignItems: "center",
4753
+ minWidth: 0,
4754
+ flex: 1
4755
+ }
4756
+ },
4757
+ /* @__PURE__ */ React15.createElement(
4758
+ Gauge,
4759
+ {
4760
+ value: limit?.pct ?? 0,
4761
+ tone,
4762
+ size,
4763
+ label: limit ? `${Math.round(limit.pct)}%` : "\u2014"
4764
+ }
4765
+ ),
4766
+ /* @__PURE__ */ React15.createElement(
4767
+ Typography14,
4768
+ {
4769
+ sx: (theme) => ({
4770
+ mt: 0.75,
4771
+ fontSize: 10.5,
4772
+ fontWeight: 700,
4773
+ letterSpacing: "0.08em",
4774
+ textTransform: "uppercase",
4775
+ color: tone === "neutral" ? theme.palette.text.secondary : void 0
4776
+ })
4777
+ },
4778
+ name
4779
+ ),
4780
+ limit ? /* @__PURE__ */ React15.createElement(React15.Fragment, null, /* @__PURE__ */ React15.createElement(
4781
+ Typography14,
4782
+ {
4783
+ variant: "caption",
4784
+ title: limit.sublabel ? `${limit.label} \xB7 ${limit.sublabel}` : limit.label,
4785
+ sx: {
4786
+ mt: 0.25,
4787
+ fontSize: 11,
4788
+ fontWeight: 600,
4789
+ maxWidth: "100%",
4790
+ overflow: "hidden",
4791
+ textOverflow: "ellipsis",
4792
+ whiteSpace: "nowrap"
4793
+ }
4794
+ },
4795
+ limit.label
4796
+ ), /* @__PURE__ */ React15.createElement(
4797
+ Typography14,
4798
+ {
4799
+ variant: "caption",
4800
+ color: "text.secondary",
4801
+ sx: {
4802
+ fontSize: 10.5,
4803
+ fontVariantNumeric: "tabular-nums",
4804
+ textAlign: "center"
4805
+ }
4806
+ },
4807
+ spendCaption(limit)
4808
+ ), extra > 0 ? /* @__PURE__ */ React15.createElement(
4809
+ Typography14,
4810
+ {
4811
+ component: Link2,
4812
+ to: keysHref,
4813
+ variant: "caption",
4814
+ sx: {
4815
+ fontSize: 10.5,
4816
+ color: "primary.main",
4817
+ textDecoration: "none",
4818
+ "&:hover": { textDecoration: "underline" }
4819
+ }
4820
+ },
4821
+ "+",
4822
+ extra,
4823
+ " more"
4824
+ ) : /* @__PURE__ */ React15.createElement(Typography14, { variant: "caption", color: "text.secondary", sx: { fontSize: 10.5 } }, resetLabel(limit))) : /* @__PURE__ */ React15.createElement(
4825
+ Typography14,
4826
+ {
4827
+ variant: "caption",
4828
+ color: "text.secondary",
4829
+ sx: { mt: 0.25, fontSize: 10.5, textAlign: "center" }
4830
+ },
4831
+ none
4832
+ )
4833
+ );
4834
+ };
4835
+ var LiteLLMBudgetGauges = ({
4836
+ title = "Budget",
4837
+ size = 72,
4838
+ keysHref,
4839
+ moduleHref = MODULE_PATH,
4840
+ onCreateKey,
4841
+ ctas,
4842
+ maxExpandedKeys = 8,
4843
+ expanded,
4844
+ defaultExpanded = false,
4845
+ onExpandedChange,
4846
+ action
4847
+ }) => {
4848
+ const api = useApi5(liteLlmApiRef);
4849
+ const [loading, setLoading] = useState11(true);
4850
+ const [error, setError] = useState11(null);
4851
+ const [user, setUser] = useState11(null);
4852
+ const [teams, setTeams] = useState11([]);
4853
+ const [keys, setKeys] = useState11([]);
4854
+ const isControlled = expanded !== void 0;
4855
+ const [uncontrolledExpanded, setUncontrolledExpanded] = useState11(defaultExpanded);
4856
+ const isExpanded = isControlled ? expanded : uncontrolledExpanded;
4857
+ const toggleExpanded = useCallback3(() => {
4858
+ const next = !isExpanded;
4859
+ if (!isControlled) setUncontrolledExpanded(next);
4860
+ onExpandedChange?.(next);
4861
+ }, [isControlled, isExpanded, onExpandedChange]);
4862
+ const keysTabHref = keysHref ?? `${moduleHref}?tab=keys`;
4863
+ useEffect6(() => {
4864
+ let cancelled = false;
4865
+ setLoading(true);
4866
+ setError(null);
4867
+ Promise.allSettled([api.getUserInfo(), api.getTeams(), api.listKeys()]).then(
4868
+ ([userResult, teamsResult, keysResult]) => {
4869
+ if (cancelled) return;
4870
+ if (userResult.status === "fulfilled") {
4871
+ setUser(userResult.value);
4872
+ } else {
4873
+ setUser(null);
4874
+ setError(
4875
+ userResult.reason?.message ?? "Failed to load your LiteLLM profile"
4876
+ );
4877
+ }
4878
+ setTeams(teamsResult.status === "fulfilled" ? teamsResult.value : []);
4879
+ setKeys(keysResult.status === "fulfilled" ? keysResult.value : []);
4880
+ setLoading(false);
4881
+ }
4882
+ );
4883
+ return () => {
4884
+ cancelled = true;
4885
+ };
4886
+ }, [api]);
4887
+ const summary = useMemo10(
4888
+ () => buildBudgetSummary(user, teams, keys, keys.length),
4889
+ [user, teams, keys]
4890
+ );
4891
+ const gauges = useMemo10(() => buildBudgetGauges(summary), [summary]);
4892
+ const headline = useMemo10(() => budgetHeadline(summary), [summary]);
4893
+ const expandedSummary = useMemo10(
4894
+ () => buildBudgetSummary(user, teams, keys, maxExpandedKeys),
4895
+ [user, teams, keys, maxExpandedKeys]
4896
+ );
4897
+ const expandedLimits = useMemo10(
4898
+ () => allBudgetLimits(expandedSummary),
4899
+ [expandedSummary]
4900
+ );
4901
+ const summaryText = headline.count === 0 ? "no limits apply" : `${headline.count} limit${headline.count === 1 ? "" : "s"}`;
4902
+ const resolvedCtas = useMemo10(() => {
4903
+ const list = ctas ?? DEFAULT_CTAS;
4904
+ return list.map((cta) => typeof cta === "string" ? { kind: cta } : cta).filter((cta) => cta.kind !== "all-limits" || headline.count > 0);
4905
+ }, [ctas, headline.count]);
4906
+ const renderCta = (cta, index) => {
4907
+ const label = cta.label ?? CTA_LABELS[cta.kind];
4908
+ const key = `${cta.kind}-${index}`;
4909
+ switch (cta.kind) {
4910
+ case "new-key":
4911
+ return onCreateKey ? /* @__PURE__ */ React15.createElement(Button6, { key, size: "small", variant: "contained", onClick: onCreateKey }, label) : /* @__PURE__ */ React15.createElement(
4912
+ Button6,
4913
+ {
4914
+ key,
4915
+ size: "small",
4916
+ variant: "contained",
4917
+ component: Link2,
4918
+ to: `${moduleHref}?generate=1`
4919
+ },
4920
+ label
4921
+ );
4922
+ case "module":
4923
+ return /* @__PURE__ */ React15.createElement(Button6, { key, size: "small", variant: "outlined", component: Link2, to: moduleHref }, label);
4924
+ case "all-limits":
4925
+ return /* @__PURE__ */ React15.createElement(
4926
+ Button6,
4927
+ {
4928
+ key,
4929
+ size: "small",
4930
+ variant: "text",
4931
+ onClick: toggleExpanded,
4932
+ endIcon: /* @__PURE__ */ React15.createElement(
4933
+ ExpandMore4,
4934
+ {
4935
+ sx: {
4936
+ transform: isExpanded ? "rotate(180deg)" : "none",
4937
+ transition: (theme) => theme.transitions.create("transform")
4938
+ }
4939
+ }
4940
+ )
4941
+ },
4942
+ label
4943
+ );
4944
+ /* istanbul ignore next — exhaustiveness */
4945
+ default:
4946
+ return null;
4947
+ }
4948
+ };
4949
+ const hasFooter = resolvedCtas.length > 0 || !!action;
4950
+ return /* @__PURE__ */ React15.createElement(Paper10, { sx: { p: 2 } }, /* @__PURE__ */ React15.createElement(Box14, { sx: { display: "flex", alignItems: "center", gap: 1, mb: 1.5, minWidth: 0 } }, /* @__PURE__ */ React15.createElement(Typography14, { variant: "h6", lineHeight: 1.2, sx: { minWidth: 0, flex: 1 } }, title), !loading && !error && /* @__PURE__ */ React15.createElement(
4951
+ StatusPill,
4952
+ {
4953
+ label: summaryText,
4954
+ tone: budgetTone2(headline.closest?.pct ?? 0),
4955
+ dot: headline.count > 0
4956
+ }
4957
+ )), loading && /* @__PURE__ */ React15.createElement(Box14, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: 110 }, /* @__PURE__ */ React15.createElement(CircularProgress8, { size: 28 })), !loading && error && /* @__PURE__ */ React15.createElement(Alert8, { severity: "error", sx: { mt: 0.5 } }, error), !loading && !error && /* @__PURE__ */ React15.createElement(React15.Fragment, null, /* @__PURE__ */ React15.createElement(Box14, { sx: { display: "flex", alignItems: "flex-start", gap: 1 } }, gauges.map((gauge) => /* @__PURE__ */ React15.createElement(LevelGauge, { key: gauge.kind, gauge, size, keysHref: keysTabHref }))), /* @__PURE__ */ React15.createElement(Collapse4, { in: isExpanded, unmountOnExit: true }, /* @__PURE__ */ React15.createElement(
4958
+ Box14,
4959
+ {
4960
+ sx: {
4961
+ mt: 1.5,
4962
+ // Bound the expanded list so a user with many limits still
4963
+ // keeps the card compact and the toggle within reach.
4964
+ maxHeight: 300,
4965
+ overflowY: "auto"
4966
+ }
4967
+ },
4968
+ /* @__PURE__ */ React15.createElement(LimitListPanel, null, /* @__PURE__ */ React15.createElement(
4969
+ BudgetLimitList,
4970
+ {
4971
+ limits: expandedLimits,
4972
+ hiddenKeyCount: expandedSummary.hiddenBudgetedKeys,
4973
+ keysHref: keysTabHref
4974
+ }
4975
+ ))
4976
+ ))), hasFooter && /* @__PURE__ */ React15.createElement(
4977
+ Box14,
4978
+ {
4979
+ sx: (theme) => ({
4980
+ mt: 2,
4981
+ pt: 2,
4982
+ borderTop: `1px solid ${theme.palette.divider}`,
4983
+ display: "flex",
4984
+ alignItems: "center",
4985
+ gap: 1,
4986
+ flexWrap: "wrap"
4987
+ })
4988
+ },
4989
+ resolvedCtas.map(renderCta),
4990
+ action
4991
+ ));
4992
+ };
4993
+
4994
+ // src/index.ts
4519
4995
  init_GenerateKeyDialog();
4520
4996
  init_ManageTeamDialog();
4521
4997
  init_api();
@@ -4524,6 +5000,7 @@ export {
4524
5000
  DashboardHeader,
4525
5001
  GenerateKeyDialog,
4526
5002
  KeysTable,
5003
+ LiteLLMBudgetGauges,
4527
5004
  LiteLLMBudgetWidget,
4528
5005
  LiteLLMHomeWidget,
4529
5006
  LiteLLMPage,