@acarmisc/backstage-plugin-litellm 0.25.0 → 0.27.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
@@ -836,16 +836,74 @@ var init_ui = __esm({
836
836
  }
837
837
  });
838
838
 
839
+ // src/components/GenerateKeyButton.tsx
840
+ import React2 from "react";
841
+ import Button from "@mui/material/Button";
842
+ import { Add } from "@mui/icons-material";
843
+ import { Link } from "@backstage/core-components";
844
+ var GenerateKeyButton;
845
+ var init_GenerateKeyButton = __esm({
846
+ "src/components/GenerateKeyButton.tsx"() {
847
+ "use strict";
848
+ GenerateKeyButton = ({
849
+ to,
850
+ onClick,
851
+ label = "Generate New Key",
852
+ size
853
+ }) => {
854
+ if (onClick) {
855
+ return /* @__PURE__ */ React2.createElement(
856
+ Button,
857
+ {
858
+ variant: "contained",
859
+ color: "primary",
860
+ disableElevation: true,
861
+ size,
862
+ startIcon: /* @__PURE__ */ React2.createElement(Add, null),
863
+ onClick
864
+ },
865
+ label
866
+ );
867
+ }
868
+ if (to) {
869
+ return /* @__PURE__ */ React2.createElement(
870
+ Button,
871
+ {
872
+ variant: "contained",
873
+ color: "primary",
874
+ disableElevation: true,
875
+ size,
876
+ startIcon: /* @__PURE__ */ React2.createElement(Add, null),
877
+ component: Link,
878
+ to
879
+ },
880
+ label
881
+ );
882
+ }
883
+ return /* @__PURE__ */ React2.createElement(
884
+ Button,
885
+ {
886
+ variant: "contained",
887
+ color: "primary",
888
+ disableElevation: true,
889
+ size,
890
+ startIcon: /* @__PURE__ */ React2.createElement(Add, null),
891
+ disabled: true
892
+ },
893
+ label
894
+ );
895
+ };
896
+ }
897
+ });
898
+
839
899
  // src/components/DashboardHeader.tsx
840
- import React2, { useMemo } from "react";
900
+ import React3, { useMemo } from "react";
841
901
  import Box2 from "@mui/material/Box";
842
902
  import Typography2 from "@mui/material/Typography";
843
903
  import Skeleton from "@mui/material/Skeleton";
844
904
  import Paper2 from "@mui/material/Paper";
845
- import Button from "@mui/material/Button";
846
905
  import Divider from "@mui/material/Divider";
847
906
  import { alpha as alpha2 } from "@mui/material/styles";
848
- import { Add } from "@mui/icons-material";
849
907
  function initials(name) {
850
908
  const local = name.split("@")[0] ?? name;
851
909
  const parts = local.split(/[.\-_\s]+/).filter(Boolean);
@@ -858,9 +916,10 @@ var init_DashboardHeader = __esm({
858
916
  "use strict";
859
917
  init_api();
860
918
  init_ui();
919
+ init_GenerateKeyButton();
861
920
  CountStat = ({ value, label, tone }) => {
862
921
  const active = value > 0;
863
- return /* @__PURE__ */ React2.createElement(Box2, { sx: { display: "flex", alignItems: "center", gap: 1 } }, /* @__PURE__ */ React2.createElement(
922
+ return /* @__PURE__ */ React3.createElement(Box2, { sx: { display: "flex", alignItems: "center", gap: 1 } }, /* @__PURE__ */ React3.createElement(
864
923
  Box2,
865
924
  {
866
925
  sx: (theme) => ({
@@ -871,14 +930,14 @@ var init_DashboardHeader = __esm({
871
930
  bgcolor: active ? tone === "danger" && theme.palette.error.main || tone === "warning" && theme.palette.warning.main || theme.palette.primary.main : alpha2(theme.palette.text.primary, 0.25)
872
931
  })
873
932
  }
874
- ), /* @__PURE__ */ React2.createElement(
933
+ ), /* @__PURE__ */ React3.createElement(
875
934
  Typography2,
876
935
  {
877
936
  component: "span",
878
937
  sx: { fontSize: 15, fontWeight: 700, lineHeight: 1, fontVariantNumeric: "tabular-nums" }
879
938
  },
880
939
  value
881
- ), /* @__PURE__ */ React2.createElement(
940
+ ), /* @__PURE__ */ React3.createElement(
882
941
  Typography2,
883
942
  {
884
943
  component: "span",
@@ -902,7 +961,7 @@ var init_DashboardHeader = __esm({
902
961
  return { total: keys.length, expired, expiringSoon };
903
962
  }, [keys]);
904
963
  const displayName = userInfo.user_email ?? userInfo.email ?? userInfo.user_id;
905
- return /* @__PURE__ */ React2.createElement(Paper2, { sx: { borderRadius: 2, overflow: "hidden" } }, /* @__PURE__ */ React2.createElement(
964
+ return /* @__PURE__ */ React3.createElement(Paper2, { sx: { borderRadius: 2, overflow: "hidden" } }, /* @__PURE__ */ React3.createElement(
906
965
  Box2,
907
966
  {
908
967
  sx: {
@@ -915,7 +974,7 @@ var init_DashboardHeader = __esm({
915
974
  pb: 2
916
975
  }
917
976
  },
918
- /* @__PURE__ */ React2.createElement(
977
+ /* @__PURE__ */ React3.createElement(
919
978
  Box2,
920
979
  {
921
980
  sx: (theme) => ({
@@ -935,7 +994,7 @@ var init_DashboardHeader = __esm({
935
994
  },
936
995
  initials(displayName)
937
996
  ),
938
- /* @__PURE__ */ React2.createElement(Box2, { sx: { flexGrow: 1, minWidth: 220 } }, /* @__PURE__ */ React2.createElement(Typography2, { variant: "h6", sx: { lineHeight: 1.25, wordBreak: "break-word" } }, displayName), /* @__PURE__ */ React2.createElement(
997
+ /* @__PURE__ */ React3.createElement(Box2, { sx: { flexGrow: 1, minWidth: 220 } }, /* @__PURE__ */ React3.createElement(Typography2, { variant: "h6", sx: { lineHeight: 1.25, wordBreak: "break-word" } }, displayName), /* @__PURE__ */ React3.createElement(
939
998
  Typography2,
940
999
  {
941
1000
  variant: "caption",
@@ -943,7 +1002,7 @@ var init_DashboardHeader = __esm({
943
1002
  sx: { display: "block", fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace" }
944
1003
  },
945
1004
  userInfo.user_id
946
- ), /* @__PURE__ */ React2.createElement(Box2, { sx: { display: "flex", gap: 0.75, flexWrap: "wrap", mt: 1, minHeight: 20 } }, loading ? /* @__PURE__ */ React2.createElement(Skeleton, { variant: "rounded", width: 140, height: 20 }) : teams.map((team) => /* @__PURE__ */ React2.createElement(
1005
+ ), /* @__PURE__ */ React3.createElement(Box2, { sx: { display: "flex", gap: 0.75, flexWrap: "wrap", mt: 1, minHeight: 20 } }, loading ? /* @__PURE__ */ React3.createElement(Skeleton, { variant: "rounded", width: 140, height: 20 }) : teams.map((team) => /* @__PURE__ */ React3.createElement(
947
1006
  TagChip,
948
1007
  {
949
1008
  key: team.team_id,
@@ -951,18 +1010,8 @@ var init_DashboardHeader = __esm({
951
1010
  title: team.team_id
952
1011
  }
953
1012
  )))),
954
- /* @__PURE__ */ React2.createElement(
955
- Button,
956
- {
957
- variant: "contained",
958
- color: "primary",
959
- disableElevation: true,
960
- startIcon: /* @__PURE__ */ React2.createElement(Add, null),
961
- onClick: onGenerateKeyClick
962
- },
963
- "Generate New Key"
964
- )
965
- ), /* @__PURE__ */ React2.createElement(Divider, null), /* @__PURE__ */ React2.createElement(
1013
+ /* @__PURE__ */ React3.createElement(GenerateKeyButton, { onClick: onGenerateKeyClick })
1014
+ ), /* @__PURE__ */ React3.createElement(Divider, null), /* @__PURE__ */ React3.createElement(
966
1015
  Box2,
967
1016
  {
968
1017
  sx: {
@@ -974,18 +1023,18 @@ var init_DashboardHeader = __esm({
974
1023
  py: 1.5
975
1024
  }
976
1025
  },
977
- /* @__PURE__ */ React2.createElement(CountStat, { value: counts.total, label: "keys", tone: "accent" }),
978
- /* @__PURE__ */ React2.createElement(Divider, { orientation: "vertical", flexItem: true, sx: { my: 0.25 } }),
979
- /* @__PURE__ */ React2.createElement(CountStat, { value: counts.expired, label: "expired", tone: "danger" }),
980
- /* @__PURE__ */ React2.createElement(Divider, { orientation: "vertical", flexItem: true, sx: { my: 0.25 } }),
981
- /* @__PURE__ */ React2.createElement(CountStat, { value: counts.expiringSoon, label: "expiring soon", tone: "warning" })
982
- ), tabs && /* @__PURE__ */ React2.createElement(React2.Fragment, null, /* @__PURE__ */ React2.createElement(Divider, null), /* @__PURE__ */ React2.createElement(Box2, { sx: { px: 1 } }, tabs)));
1026
+ /* @__PURE__ */ React3.createElement(CountStat, { value: counts.total, label: "keys", tone: "accent" }),
1027
+ /* @__PURE__ */ React3.createElement(Divider, { orientation: "vertical", flexItem: true, sx: { my: 0.25 } }),
1028
+ /* @__PURE__ */ React3.createElement(CountStat, { value: counts.expired, label: "expired", tone: "danger" }),
1029
+ /* @__PURE__ */ React3.createElement(Divider, { orientation: "vertical", flexItem: true, sx: { my: 0.25 } }),
1030
+ /* @__PURE__ */ React3.createElement(CountStat, { value: counts.expiringSoon, label: "expiring soon", tone: "warning" })
1031
+ ), tabs && /* @__PURE__ */ React3.createElement(React3.Fragment, null, /* @__PURE__ */ React3.createElement(Divider, null), /* @__PURE__ */ React3.createElement(Box2, { sx: { px: 1 } }, tabs)));
983
1032
  };
984
1033
  }
985
1034
  });
986
1035
 
987
1036
  // src/components/KeysTable.tsx
988
- import React3, { useState, useMemo as useMemo2 } from "react";
1037
+ import React4, { useState, useMemo as useMemo2 } from "react";
989
1038
  import Table from "@mui/material/Table";
990
1039
  import TableBody from "@mui/material/TableBody";
991
1040
  import TableCell from "@mui/material/TableCell";
@@ -1003,11 +1052,10 @@ import DialogContent from "@mui/material/DialogContent";
1003
1052
  import DialogActions from "@mui/material/DialogActions";
1004
1053
  import TextField from "@mui/material/TextField";
1005
1054
  import CircularProgress from "@mui/material/CircularProgress";
1006
- import Autocomplete from "@mui/material/Autocomplete";
1007
1055
  import Skeleton2 from "@mui/material/Skeleton";
1008
1056
  import InputAdornment from "@mui/material/InputAdornment";
1009
1057
  import { alpha as alpha3 } from "@mui/material/styles";
1010
- import { ContentCopy, Delete, Add as Add2, Edit, Autorenew, Search, Lock, LockOpen } from "@mui/icons-material";
1058
+ import { ContentCopy, Delete, Edit, Autorenew, Search, Lock, LockOpen } from "@mui/icons-material";
1011
1059
  function expiryChipLabel(status, expiresAt) {
1012
1060
  if (status === "expired") return "Expired";
1013
1061
  if (status === "soon") return `${Math.ceil((new Date(expiresAt).getTime() - Date.now()) / 864e5)}d left`;
@@ -1021,12 +1069,12 @@ function expiryTone(status) {
1021
1069
  function ExpiryCell({ expiresAt }) {
1022
1070
  const status = expiryStatus(expiresAt);
1023
1071
  if (!status) {
1024
- return /* @__PURE__ */ React3.createElement(Typography3, { variant: "body2", color: "text.secondary" }, "Never");
1072
+ return /* @__PURE__ */ React4.createElement(Typography3, { variant: "body2", color: "text.secondary" }, "Never");
1025
1073
  }
1026
1074
  if (status === "ok") {
1027
- return /* @__PURE__ */ React3.createElement(Typography3, { variant: "body2", color: "text.secondary" }, formatDate(expiresAt));
1075
+ return /* @__PURE__ */ React4.createElement(Typography3, { variant: "body2", color: "text.secondary" }, formatDate(expiresAt));
1028
1076
  }
1029
- return /* @__PURE__ */ React3.createElement(
1077
+ return /* @__PURE__ */ React4.createElement(
1030
1078
  StatusPill,
1031
1079
  {
1032
1080
  label: expiryChipLabel(status, expiresAt),
@@ -1041,36 +1089,16 @@ function budgetTone(pct) {
1041
1089
  return "accent";
1042
1090
  }
1043
1091
  function keyBlockIcon(isBlocking, blocked) {
1044
- if (isBlocking) return /* @__PURE__ */ React3.createElement(CircularProgress, { size: 18 });
1045
- if (blocked) return /* @__PURE__ */ React3.createElement(LockOpen, { fontSize: "small" });
1046
- return /* @__PURE__ */ React3.createElement(Lock, { fontSize: "small" });
1092
+ if (isBlocking) return /* @__PURE__ */ React4.createElement(CircularProgress, { size: 18 });
1093
+ if (blocked) return /* @__PURE__ */ React4.createElement(LockOpen, { fontSize: "small" });
1094
+ return /* @__PURE__ */ React4.createElement(Lock, { fontSize: "small" });
1047
1095
  }
1048
1096
  function BudgetCell({ spend, maxBudget }) {
1049
1097
  if (!maxBudget) {
1050
- return /* @__PURE__ */ React3.createElement(Typography3, { variant: "body2", color: "text.secondary", sx: { fontVariantNumeric: "tabular-nums" } }, "$", spend.toFixed(2), /* @__PURE__ */ React3.createElement(Typography3, { component: "span", variant: "caption", sx: { ml: 0.5, opacity: 0.7 } }, "/ \u221E"));
1098
+ return /* @__PURE__ */ React4.createElement(Typography3, { variant: "body2", color: "text.secondary", sx: { fontVariantNumeric: "tabular-nums" } }, "$", spend.toFixed(2), /* @__PURE__ */ React4.createElement(Typography3, { component: "span", variant: "caption", sx: { ml: 0.5, opacity: 0.7 } }, "/ \u221E"));
1051
1099
  }
1052
1100
  const pct = Math.min(100, spend / maxBudget * 100);
1053
- return /* @__PURE__ */ React3.createElement(Box3, { minWidth: 104 }, /* @__PURE__ */ React3.createElement(Typography3, { variant: "body2", sx: { fontVariantNumeric: "tabular-nums", mb: 0.5 } }, "$", spend.toFixed(2), /* @__PURE__ */ React3.createElement(Typography3, { component: "span", variant: "caption", color: "text.secondary", sx: { ml: 0.5 } }, "/ $", maxBudget)), /* @__PURE__ */ React3.createElement(Meter, { value: pct, tone: budgetTone(pct), height: 4 }));
1054
- }
1055
- function fmtCost(perToken) {
1056
- if (!perToken) return null;
1057
- const per1k = perToken * 1e3;
1058
- return per1k < 0.01 ? `$${(perToken * 1e6).toFixed(2)}/M` : `$${per1k.toFixed(3)}/1K`;
1059
- }
1060
- function formatContextWindow(maxInput, maxOutput) {
1061
- if (!maxInput && !maxOutput) return null;
1062
- const fmt = (n) => {
1063
- if (!n) return null;
1064
- if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
1065
- if (n >= 1e3) return `${Math.round(n / 1e3)}K`;
1066
- return String(n);
1067
- };
1068
- const inPart = fmt(maxInput);
1069
- const outPart = fmt(maxOutput);
1070
- if (inPart && outPart) return `ctx ${inPart} in / ${outPart} out`;
1071
- if (inPart) return `ctx ${inPart}`;
1072
- if (outPart) return `ctx ${outPart} out`;
1073
- return null;
1101
+ return /* @__PURE__ */ React4.createElement(Box3, { minWidth: 104 }, /* @__PURE__ */ React4.createElement(Typography3, { variant: "body2", sx: { fontVariantNumeric: "tabular-nums", mb: 0.5 } }, "$", spend.toFixed(2), /* @__PURE__ */ React4.createElement(Typography3, { component: "span", variant: "caption", color: "text.secondary", sx: { ml: 0.5 } }, "/ $", maxBudget)), /* @__PURE__ */ React4.createElement(Meter, { value: pct, tone: budgetTone(pct), height: 4 }));
1074
1102
  }
1075
1103
  function compareKeys(a, b, sortKey) {
1076
1104
  switch (sortKey) {
@@ -1090,16 +1118,13 @@ function compareKeys(a, b, sortKey) {
1090
1118
  return 0;
1091
1119
  }
1092
1120
  }
1093
- var maskKey, shortKeyId, formatDate, keyToEditForm, KeysTable;
1121
+ var shortKeyId, formatDate, KeysTable;
1094
1122
  var init_KeysTable = __esm({
1095
1123
  "src/components/KeysTable.tsx"() {
1096
1124
  "use strict";
1097
1125
  init_api();
1126
+ init_GenerateKeyButton();
1098
1127
  init_ui();
1099
- maskKey = (key) => {
1100
- if (key.length <= 8) return "***";
1101
- return `${key.slice(0, 4)}...${key.slice(-4)}`;
1102
- };
1103
1128
  shortKeyId = (token) => {
1104
1129
  if (!token) return "-";
1105
1130
  if (token.length <= 16) return token;
@@ -1112,33 +1137,19 @@ var init_KeysTable = __esm({
1112
1137
  return dateStr;
1113
1138
  }
1114
1139
  };
1115
- keyToEditForm = (k) => ({
1116
- key_alias: k.key_alias ?? "",
1117
- models: k.models ?? [],
1118
- max_budget: k.max_budget,
1119
- tpm_limit: k.tpm_limit,
1120
- rpm_limit: k.rpm_limit
1121
- });
1122
1140
  KeysTable = ({
1123
1141
  keys,
1124
- models,
1125
1142
  loading,
1126
1143
  onGenerateKeyClick,
1127
- onUpdateKey,
1144
+ onEditKey,
1128
1145
  onBlockKey,
1129
1146
  onUnblockKey,
1130
- onResetKeySpend,
1131
1147
  onDeleteKey,
1132
1148
  onPruneExpiredKeys
1133
1149
  }) => {
1134
- const [editingKey, setEditingKey] = useState(null);
1135
- const [editForm, setEditForm] = useState({});
1136
- const [editSubmitting, setEditSubmitting] = useState(false);
1137
1150
  const [blockingKeyId, setBlockingKeyId] = useState(null);
1138
1151
  const [deleteConfirmId, setDeleteConfirmId] = useState(null);
1139
1152
  const [deleteSubmitting, setDeleteSubmitting] = useState(false);
1140
- const [resetSpendConfirm, setResetSpendConfirm] = useState(false);
1141
- const [resetSpendSubmitting, setResetSpendSubmitting] = useState(false);
1142
1153
  const [filterText, setFilterText] = useState("");
1143
1154
  const [sortKey, setSortKey] = useState("created");
1144
1155
  const [sortDirection, setSortDirection] = useState("desc");
@@ -1163,27 +1174,8 @@ var init_KeysTable = __esm({
1163
1174
  return sortDirection === "asc" ? comparison : -comparison;
1164
1175
  });
1165
1176
  }, [keys, filterText, sortKey, sortDirection]);
1166
- const editSelectedModels = models.filter((m) => (editForm.models || []).includes(m.model_name));
1167
1177
  const handleOpenEdit = (k) => {
1168
- setEditingKey(k);
1169
- setEditForm(keyToEditForm(k));
1170
- };
1171
- const handleCloseEdit = () => {
1172
- setEditingKey(null);
1173
- setEditForm({});
1174
- setResetSpendConfirm(false);
1175
- };
1176
- const handleUpdate = async () => {
1177
- if (!editingKey) return;
1178
- setEditSubmitting(true);
1179
- try {
1180
- await onUpdateKey(editingKey.token ?? editingKey.key, editForm);
1181
- handleCloseEdit();
1182
- } catch (error) {
1183
- console.error("Failed to update key:", error);
1184
- } finally {
1185
- setEditSubmitting(false);
1186
- }
1178
+ onEditKey(k);
1187
1179
  };
1188
1180
  const handleToggleBlock = async (key) => {
1189
1181
  const keyId = key.token ?? key.key;
@@ -1198,17 +1190,6 @@ var init_KeysTable = __esm({
1198
1190
  setBlockingKeyId(null);
1199
1191
  }
1200
1192
  };
1201
- const handleResetSpend = async () => {
1202
- if (!editingKey) return;
1203
- setResetSpendSubmitting(true);
1204
- try {
1205
- await onResetKeySpend(editingKey.token ?? editingKey.key);
1206
- setResetSpendConfirm(false);
1207
- handleCloseEdit();
1208
- } finally {
1209
- setResetSpendSubmitting(false);
1210
- }
1211
- };
1212
1193
  const handleConfirmDelete = async () => {
1213
1194
  if (!deleteConfirmId) return;
1214
1195
  setDeleteSubmitting(true);
@@ -1239,40 +1220,29 @@ var init_KeysTable = __esm({
1239
1220
  setPruneConfirmCount(null);
1240
1221
  }
1241
1222
  };
1242
- const modelOption = (m) => {
1243
- const inCost = fmtCost(m.input_cost_per_token);
1244
- const outCost = fmtCost(m.output_cost_per_token);
1245
- const ctx = formatContextWindow(m.max_input_tokens, m.max_output_tokens);
1246
- return /* @__PURE__ */ React3.createElement(Box3, null, /* @__PURE__ */ React3.createElement("span", null, m.model_name), m.supports_function_calling && " \u{1F527}", m.supports_vision && " \u{1F441}\uFE0F", ctx && /* @__PURE__ */ React3.createElement(Typography3, { variant: "caption", color: "text.secondary", sx: { ml: 1 } }, ctx), (inCost || outCost) && /* @__PURE__ */ React3.createElement(Typography3, { variant: "caption", color: "text.secondary", sx: { ml: 1 } }, inCost, " in \xB7 ", outCost, " out"));
1247
- };
1248
1223
  const renderTableBody = () => {
1249
1224
  if (loading) {
1250
- return /* @__PURE__ */ React3.createElement(TableRow, null, /* @__PURE__ */ React3.createElement(TableCell, { colSpan: 8, sx: { py: 2 } }, /* @__PURE__ */ React3.createElement(Skeleton2, { variant: "rounded", height: 140 })));
1225
+ return /* @__PURE__ */ React4.createElement(TableRow, null, /* @__PURE__ */ React4.createElement(TableCell, { colSpan: 8, sx: { py: 2 } }, /* @__PURE__ */ React4.createElement(Skeleton2, { variant: "rounded", height: 140 })));
1251
1226
  }
1252
1227
  if (filteredKeys.length === 0) {
1253
- return /* @__PURE__ */ React3.createElement(TableRow, null, /* @__PURE__ */ React3.createElement(TableCell, { colSpan: 8 }, filterText ? /* @__PURE__ */ React3.createElement(
1228
+ return /* @__PURE__ */ React4.createElement(TableRow, null, /* @__PURE__ */ React4.createElement(TableCell, { colSpan: 8 }, filterText ? /* @__PURE__ */ React4.createElement(
1254
1229
  EmptyState,
1255
1230
  {
1256
1231
  message: "No keys match that filter",
1257
1232
  hint: "Try a different alias or model name."
1258
1233
  }
1259
- ) : /* @__PURE__ */ React3.createElement(
1234
+ ) : /* @__PURE__ */ React4.createElement(
1260
1235
  EmptyState,
1261
1236
  {
1262
1237
  message: "No keys yet",
1263
1238
  hint: "Generate your first key to start calling models.",
1264
- action: /* @__PURE__ */ React3.createElement(
1265
- Button2,
1239
+ action: /* @__PURE__ */ React4.createElement(
1240
+ GenerateKeyButton,
1266
1241
  {
1267
- variant: "contained",
1268
- color: "primary",
1269
1242
  size: "small",
1270
- disableElevation: true,
1271
- startIcon: /* @__PURE__ */ React3.createElement(Add2, null),
1272
- onClick: onGenerateKeyClick,
1273
- sx: { mt: 1 }
1274
- },
1275
- "Generate Your First Key"
1243
+ label: "Generate Your First Key",
1244
+ onClick: onGenerateKeyClick
1245
+ }
1276
1246
  )
1277
1247
  }
1278
1248
  )));
@@ -1281,7 +1251,7 @@ var init_KeysTable = __esm({
1281
1251
  const keyId = key.token ?? key.key;
1282
1252
  const isBlocking = blockingKeyId === keyId;
1283
1253
  const keyModels = key.models ?? [];
1284
- return /* @__PURE__ */ React3.createElement(
1254
+ return /* @__PURE__ */ React4.createElement(
1285
1255
  TableRow,
1286
1256
  {
1287
1257
  key: keyId,
@@ -1292,8 +1262,8 @@ var init_KeysTable = __esm({
1292
1262
  "& td": { color: theme.palette.text.secondary }
1293
1263
  }) : void 0
1294
1264
  },
1295
- /* @__PURE__ */ React3.createElement(TableCell, null, /* @__PURE__ */ React3.createElement(Box3, { display: "flex", alignItems: "center", gap: 1, flexWrap: "wrap" }, /* @__PURE__ */ React3.createElement(Typography3, { variant: "body2" }, key.key_alias || "\u2014"), key.blocked && /* @__PURE__ */ React3.createElement(StatusPill, { label: "Blocked", tone: "danger" }))),
1296
- /* @__PURE__ */ React3.createElement(TableCell, null, /* @__PURE__ */ React3.createElement(Box3, { display: "flex", alignItems: "center", gap: 0.5 }, /* @__PURE__ */ React3.createElement(
1265
+ /* @__PURE__ */ React4.createElement(TableCell, null, /* @__PURE__ */ React4.createElement(Box3, { display: "flex", alignItems: "center", gap: 1, flexWrap: "wrap" }, /* @__PURE__ */ React4.createElement(Typography3, { variant: "body2" }, key.key_alias || "\u2014"), key.blocked && /* @__PURE__ */ React4.createElement(StatusPill, { label: "Blocked", tone: "danger" }))),
1266
+ /* @__PURE__ */ React4.createElement(TableCell, null, /* @__PURE__ */ React4.createElement(Box3, { display: "flex", alignItems: "center", gap: 0.5 }, /* @__PURE__ */ React4.createElement(
1297
1267
  Typography3,
1298
1268
  {
1299
1269
  variant: "body2",
@@ -1310,7 +1280,7 @@ var init_KeysTable = __esm({
1310
1280
  })
1311
1281
  },
1312
1282
  shortKeyId(keyId)
1313
- ), /* @__PURE__ */ React3.createElement(
1283
+ ), /* @__PURE__ */ React4.createElement(
1314
1284
  IconButton,
1315
1285
  {
1316
1286
  size: "small",
@@ -1318,13 +1288,13 @@ var init_KeysTable = __esm({
1318
1288
  title: "Copy Key ID",
1319
1289
  sx: quietIconButtonSx("accent")
1320
1290
  },
1321
- /* @__PURE__ */ React3.createElement(ContentCopy, { sx: { fontSize: 15 } })
1291
+ /* @__PURE__ */ React4.createElement(ContentCopy, { sx: { fontSize: 15 } })
1322
1292
  ))),
1323
- /* @__PURE__ */ React3.createElement(TableCell, null, /* @__PURE__ */ React3.createElement(Typography3, { variant: "body2", color: "text.secondary" }, formatDate(key.created_at))),
1324
- /* @__PURE__ */ React3.createElement(TableCell, null, /* @__PURE__ */ React3.createElement(ExpiryCell, { expiresAt: key.expires_at })),
1325
- /* @__PURE__ */ React3.createElement(TableCell, null, /* @__PURE__ */ React3.createElement(BudgetCell, { spend: key.spend ?? 0, maxBudget: key.max_budget })),
1326
- /* @__PURE__ */ React3.createElement(TableCell, null, /* @__PURE__ */ React3.createElement(Typography3, { variant: "body2", color: "text.secondary", sx: { fontVariantNumeric: "tabular-nums" } }, key.tpm_limit ?? "\u221E", " / ", key.rpm_limit ?? "\u221E")),
1327
- /* @__PURE__ */ React3.createElement(TableCell, null, keyModels.length === 0 ? /* @__PURE__ */ React3.createElement(Typography3, { variant: "body2", color: "text.secondary" }, "All models") : /* @__PURE__ */ React3.createElement(Box3, { display: "flex", gap: 0.5, flexWrap: "wrap" }, keyModels.slice(0, 2).map((model) => /* @__PURE__ */ React3.createElement(TagChip, { key: model, label: model, title: model })), keyModels.length > 2 && /* @__PURE__ */ React3.createElement(
1293
+ /* @__PURE__ */ React4.createElement(TableCell, null, /* @__PURE__ */ React4.createElement(Typography3, { variant: "body2", color: "text.secondary" }, formatDate(key.created_at))),
1294
+ /* @__PURE__ */ React4.createElement(TableCell, null, /* @__PURE__ */ React4.createElement(ExpiryCell, { expiresAt: key.expires_at })),
1295
+ /* @__PURE__ */ React4.createElement(TableCell, null, /* @__PURE__ */ React4.createElement(BudgetCell, { spend: key.spend ?? 0, maxBudget: key.max_budget })),
1296
+ /* @__PURE__ */ React4.createElement(TableCell, null, /* @__PURE__ */ React4.createElement(Typography3, { variant: "body2", color: "text.secondary", sx: { fontVariantNumeric: "tabular-nums" } }, key.tpm_limit ?? "\u221E", " / ", key.rpm_limit ?? "\u221E")),
1297
+ /* @__PURE__ */ React4.createElement(TableCell, null, keyModels.length === 0 ? /* @__PURE__ */ React4.createElement(Typography3, { variant: "body2", color: "text.secondary" }, "All models") : /* @__PURE__ */ React4.createElement(Box3, { display: "flex", gap: 0.5, flexWrap: "wrap" }, keyModels.slice(0, 2).map((model) => /* @__PURE__ */ React4.createElement(TagChip, { key: model, label: model, title: model })), keyModels.length > 2 && /* @__PURE__ */ React4.createElement(
1328
1298
  TagChip,
1329
1299
  {
1330
1300
  label: `+${keyModels.length - 2}`,
@@ -1332,7 +1302,7 @@ var init_KeysTable = __esm({
1332
1302
  title: keyModels.slice(2).join(", ")
1333
1303
  }
1334
1304
  ))),
1335
- /* @__PURE__ */ React3.createElement(TableCell, { align: "right" }, /* @__PURE__ */ React3.createElement(Box3, { display: "flex", justifyContent: "flex-end", gap: 0.25 }, /* @__PURE__ */ React3.createElement(
1305
+ /* @__PURE__ */ React4.createElement(TableCell, { align: "right" }, /* @__PURE__ */ React4.createElement(Box3, { display: "flex", justifyContent: "flex-end", gap: 0.25 }, /* @__PURE__ */ React4.createElement(
1336
1306
  IconButton,
1337
1307
  {
1338
1308
  size: "small",
@@ -1340,8 +1310,8 @@ var init_KeysTable = __esm({
1340
1310
  title: "Edit key",
1341
1311
  sx: quietIconButtonSx("accent")
1342
1312
  },
1343
- /* @__PURE__ */ React3.createElement(Edit, { fontSize: "small" })
1344
- ), /* @__PURE__ */ React3.createElement(
1313
+ /* @__PURE__ */ React4.createElement(Edit, { fontSize: "small" })
1314
+ ), /* @__PURE__ */ React4.createElement(
1345
1315
  IconButton,
1346
1316
  {
1347
1317
  size: "small",
@@ -1351,7 +1321,7 @@ var init_KeysTable = __esm({
1351
1321
  sx: quietIconButtonSx("warning")
1352
1322
  },
1353
1323
  keyBlockIcon(isBlocking, key.blocked)
1354
- ), /* @__PURE__ */ React3.createElement(
1324
+ ), /* @__PURE__ */ React4.createElement(
1355
1325
  IconButton,
1356
1326
  {
1357
1327
  size: "small",
@@ -1359,18 +1329,18 @@ var init_KeysTable = __esm({
1359
1329
  title: "Revoke key",
1360
1330
  sx: quietIconButtonSx("danger")
1361
1331
  },
1362
- /* @__PURE__ */ React3.createElement(Delete, { fontSize: "small" })
1332
+ /* @__PURE__ */ React4.createElement(Delete, { fontSize: "small" })
1363
1333
  )))
1364
1334
  );
1365
1335
  });
1366
1336
  };
1367
- return /* @__PURE__ */ React3.createElement(React3.Fragment, null, /* @__PURE__ */ React3.createElement(
1337
+ return /* @__PURE__ */ React4.createElement(React4.Fragment, null, /* @__PURE__ */ React4.createElement(
1368
1338
  SectionCard,
1369
1339
  {
1370
1340
  title: "Virtual Keys",
1371
1341
  subtitle: loading ? "Loading\u2026" : `${filteredKeys.length}${filterText ? ` of ${keys.length}` : ""} key${filteredKeys.length === 1 ? "" : "s"}`,
1372
1342
  flush: true,
1373
- actions: /* @__PURE__ */ React3.createElement(React3.Fragment, null, /* @__PURE__ */ React3.createElement(
1343
+ actions: /* @__PURE__ */ React4.createElement(React4.Fragment, null, /* @__PURE__ */ React4.createElement(
1374
1344
  TextField,
1375
1345
  {
1376
1346
  size: "small",
@@ -1380,15 +1350,15 @@ var init_KeysTable = __esm({
1380
1350
  onChange: (e) => setFilterText(e.target.value),
1381
1351
  sx: { minWidth: 240 },
1382
1352
  InputProps: {
1383
- startAdornment: /* @__PURE__ */ React3.createElement(InputAdornment, { position: "start" }, /* @__PURE__ */ React3.createElement(Search, { fontSize: "small", color: "disabled" }))
1353
+ startAdornment: /* @__PURE__ */ React4.createElement(InputAdornment, { position: "start" }, /* @__PURE__ */ React4.createElement(Search, { fontSize: "small", color: "disabled" }))
1384
1354
  }
1385
1355
  }
1386
- ), expiredKeys.length > 0 && /* @__PURE__ */ React3.createElement(
1356
+ ), expiredKeys.length > 0 && /* @__PURE__ */ React4.createElement(
1387
1357
  Button2,
1388
1358
  {
1389
1359
  variant: "outlined",
1390
1360
  color: "inherit",
1391
- startIcon: /* @__PURE__ */ React3.createElement(Autorenew, null),
1361
+ startIcon: /* @__PURE__ */ React4.createElement(Autorenew, null),
1392
1362
  onClick: () => setPruneConfirmCount(expiredKeys.length),
1393
1363
  sx: (theme) => ({
1394
1364
  color: theme.palette.text.secondary,
@@ -1403,88 +1373,10 @@ var init_KeysTable = __esm({
1403
1373
  "Prune expired (",
1404
1374
  expiredKeys.length,
1405
1375
  ")"
1406
- ), /* @__PURE__ */ React3.createElement(
1407
- Button2,
1408
- {
1409
- variant: "contained",
1410
- color: "primary",
1411
- disableElevation: true,
1412
- startIcon: /* @__PURE__ */ React3.createElement(Add2, null),
1413
- onClick: onGenerateKeyClick
1414
- },
1415
- "Generate New Key"
1416
- ))
1376
+ ), /* @__PURE__ */ React4.createElement(GenerateKeyButton, { onClick: onGenerateKeyClick }))
1417
1377
  },
1418
- /* @__PURE__ */ React3.createElement(TableContainer, null, /* @__PURE__ */ React3.createElement(Table, { size: "small", sx: dataTableSx }, /* @__PURE__ */ React3.createElement(TableHead, null, /* @__PURE__ */ React3.createElement(TableRow, null, /* @__PURE__ */ React3.createElement(TableCell, { sortDirection: sortKey === "alias" ? sortDirection : false }, /* @__PURE__ */ React3.createElement(TableSortLabel, { active: sortKey === "alias", direction: sortKey === "alias" ? sortDirection : "asc", onClick: () => handleSort("alias") }, "Alias")), /* @__PURE__ */ React3.createElement(TableCell, null, "Key ID"), /* @__PURE__ */ React3.createElement(TableCell, { sortDirection: sortKey === "created" ? sortDirection : false }, /* @__PURE__ */ React3.createElement(TableSortLabel, { active: sortKey === "created", direction: sortKey === "created" ? sortDirection : "asc", onClick: () => handleSort("created") }, "Created")), /* @__PURE__ */ React3.createElement(TableCell, { sortDirection: sortKey === "expires" ? sortDirection : false }, /* @__PURE__ */ React3.createElement(TableSortLabel, { active: sortKey === "expires", direction: sortKey === "expires" ? sortDirection : "asc", onClick: () => handleSort("expires") }, "Expires")), /* @__PURE__ */ React3.createElement(TableCell, { sortDirection: sortKey === "budget" ? sortDirection : false }, /* @__PURE__ */ React3.createElement(TableSortLabel, { active: sortKey === "budget", direction: sortKey === "budget" ? sortDirection : "asc", onClick: () => handleSort("budget") }, "Budget")), /* @__PURE__ */ React3.createElement(TableCell, { sortDirection: sortKey === "limits" ? sortDirection : false }, /* @__PURE__ */ React3.createElement(TableSortLabel, { active: sortKey === "limits", direction: sortKey === "limits" ? sortDirection : "asc", onClick: () => handleSort("limits") }, "TPM / RPM")), /* @__PURE__ */ React3.createElement(TableCell, { sortDirection: sortKey === "models" ? sortDirection : false }, /* @__PURE__ */ React3.createElement(TableSortLabel, { active: sortKey === "models", direction: sortKey === "models" ? sortDirection : "asc", onClick: () => handleSort("models") }, "Models")), /* @__PURE__ */ React3.createElement(TableCell, { align: "right" }, "Actions"))), /* @__PURE__ */ React3.createElement(TableBody, null, renderTableBody())))
1419
- ), /* @__PURE__ */ React3.createElement(Dialog, { open: !!editingKey, onClose: handleCloseEdit, maxWidth: "sm", fullWidth: true }, /* @__PURE__ */ React3.createElement(DialogTitle, null, "Edit Key"), /* @__PURE__ */ React3.createElement(DialogContent, null, editingKey && /* @__PURE__ */ React3.createElement(Box3, { display: "flex", flexDirection: "column", gap: 2, mt: 1 }, /* @__PURE__ */ React3.createElement(Typography3, { variant: "body2", color: "text.secondary" }, /* @__PURE__ */ React3.createElement("code", { style: { fontFamily: "monospace", color: "inherit" } }, maskKey(editingKey.key))), /* @__PURE__ */ React3.createElement(
1420
- TextField,
1421
- {
1422
- label: "Alias",
1423
- value: editForm.key_alias || "",
1424
- onChange: (e) => setEditForm({ ...editForm, key_alias: e.target.value }),
1425
- fullWidth: true
1426
- }
1427
- ), models.length > 0 && /* @__PURE__ */ React3.createElement(
1428
- Autocomplete,
1429
- {
1430
- multiple: true,
1431
- options: models,
1432
- groupBy: (m) => m.mode || "other",
1433
- getOptionLabel: (m) => m.model_name,
1434
- value: editSelectedModels,
1435
- onChange: (_e, selected) => setEditForm({ ...editForm, models: selected.map((m) => m.model_name) }),
1436
- renderOption: (props, m) => /* @__PURE__ */ React3.createElement("li", { ...props }, modelOption(m)),
1437
- renderInput: (params) => /* @__PURE__ */ React3.createElement(TextField, { ...params, label: "Models", fullWidth: true })
1438
- }
1439
- ), /* @__PURE__ */ React3.createElement(
1440
- TextField,
1441
- {
1442
- label: "Max Budget (USD)",
1443
- type: "number",
1444
- value: editForm.max_budget ?? "",
1445
- onChange: (e) => setEditForm({ ...editForm, max_budget: e.target.value ? Number(e.target.value) : void 0 }),
1446
- fullWidth: true
1447
- }
1448
- ), /* @__PURE__ */ React3.createElement(
1449
- TextField,
1450
- {
1451
- label: "TPM Limit",
1452
- type: "number",
1453
- value: editForm.tpm_limit ?? "",
1454
- onChange: (e) => setEditForm({ ...editForm, tpm_limit: e.target.value ? Number(e.target.value) : void 0 }),
1455
- helperText: "Max tokens per minute this key can consume across all models. Leave blank for no limit.",
1456
- fullWidth: true
1457
- }
1458
- ), /* @__PURE__ */ React3.createElement(
1459
- TextField,
1460
- {
1461
- label: "RPM Limit",
1462
- type: "number",
1463
- value: editForm.rpm_limit ?? "",
1464
- onChange: (e) => setEditForm({ ...editForm, rpm_limit: e.target.value ? Number(e.target.value) : void 0 }),
1465
- helperText: "Max requests per minute this key can make across all models. Leave blank for no limit.",
1466
- fullWidth: true
1467
- }
1468
- ), /* @__PURE__ */ React3.createElement(Box3, { mt: 1, pt: 2, borderTop: "1px solid", sx: { borderColor: "divider" } }, /* @__PURE__ */ React3.createElement(Typography3, { variant: "caption", color: "text.secondary", display: "block", mb: 1 }, "Danger Zone"), !resetSpendConfirm ? /* @__PURE__ */ React3.createElement(
1469
- Button2,
1470
- {
1471
- size: "small",
1472
- color: "warning",
1473
- variant: "outlined",
1474
- onClick: () => setResetSpendConfirm(true)
1475
- },
1476
- "Reset Spend to $0"
1477
- ) : /* @__PURE__ */ React3.createElement(Box3, { display: "flex", alignItems: "center", gap: 1, flexWrap: "wrap" }, /* @__PURE__ */ React3.createElement(Typography3, { variant: "body2", color: "warning.main" }, "Zero out spend counter?"), /* @__PURE__ */ React3.createElement(
1478
- Button2,
1479
- {
1480
- size: "small",
1481
- color: "warning",
1482
- variant: "contained",
1483
- disabled: resetSpendSubmitting,
1484
- onClick: handleResetSpend
1485
- },
1486
- resetSpendSubmitting ? /* @__PURE__ */ React3.createElement(CircularProgress, { size: 16 }) : "Confirm"
1487
- ), /* @__PURE__ */ React3.createElement(Button2, { size: "small", onClick: () => setResetSpendConfirm(false) }, "Cancel"))))), /* @__PURE__ */ React3.createElement(DialogActions, null, /* @__PURE__ */ React3.createElement(Button2, { onClick: handleCloseEdit }, "Cancel"), /* @__PURE__ */ React3.createElement(Button2, { onClick: handleUpdate, variant: "contained", color: "primary", disabled: editSubmitting }, editSubmitting ? /* @__PURE__ */ React3.createElement(CircularProgress, { size: 24 }) : "Save"))), /* @__PURE__ */ React3.createElement(Dialog, { open: !!deleteConfirmId, onClose: () => setDeleteConfirmId(null), maxWidth: "xs", fullWidth: true }, /* @__PURE__ */ React3.createElement(DialogTitle, null, "Revoke Key?"), /* @__PURE__ */ React3.createElement(DialogContent, null, /* @__PURE__ */ React3.createElement(Typography3, null, "This will permanently revoke the key. Any integrations using it will stop working immediately.")), /* @__PURE__ */ React3.createElement(DialogActions, null, /* @__PURE__ */ React3.createElement(Button2, { onClick: () => setDeleteConfirmId(null), disabled: deleteSubmitting }, "Cancel"), /* @__PURE__ */ React3.createElement(
1378
+ /* @__PURE__ */ React4.createElement(TableContainer, null, /* @__PURE__ */ React4.createElement(Table, { size: "small", sx: dataTableSx }, /* @__PURE__ */ React4.createElement(TableHead, null, /* @__PURE__ */ React4.createElement(TableRow, null, /* @__PURE__ */ React4.createElement(TableCell, { sortDirection: sortKey === "alias" ? sortDirection : false }, /* @__PURE__ */ React4.createElement(TableSortLabel, { active: sortKey === "alias", direction: sortKey === "alias" ? sortDirection : "asc", onClick: () => handleSort("alias") }, "Alias")), /* @__PURE__ */ React4.createElement(TableCell, null, "Key ID"), /* @__PURE__ */ React4.createElement(TableCell, { sortDirection: sortKey === "created" ? sortDirection : false }, /* @__PURE__ */ React4.createElement(TableSortLabel, { active: sortKey === "created", direction: sortKey === "created" ? sortDirection : "asc", onClick: () => handleSort("created") }, "Created")), /* @__PURE__ */ React4.createElement(TableCell, { sortDirection: sortKey === "expires" ? sortDirection : false }, /* @__PURE__ */ React4.createElement(TableSortLabel, { active: sortKey === "expires", direction: sortKey === "expires" ? sortDirection : "asc", onClick: () => handleSort("expires") }, "Expires")), /* @__PURE__ */ React4.createElement(TableCell, { sortDirection: sortKey === "budget" ? sortDirection : false }, /* @__PURE__ */ React4.createElement(TableSortLabel, { active: sortKey === "budget", direction: sortKey === "budget" ? sortDirection : "asc", onClick: () => handleSort("budget") }, "Budget")), /* @__PURE__ */ React4.createElement(TableCell, { sortDirection: sortKey === "limits" ? sortDirection : false }, /* @__PURE__ */ React4.createElement(TableSortLabel, { active: sortKey === "limits", direction: sortKey === "limits" ? sortDirection : "asc", onClick: () => handleSort("limits") }, "TPM / RPM")), /* @__PURE__ */ React4.createElement(TableCell, { sortDirection: sortKey === "models" ? sortDirection : false }, /* @__PURE__ */ React4.createElement(TableSortLabel, { active: sortKey === "models", direction: sortKey === "models" ? sortDirection : "asc", onClick: () => handleSort("models") }, "Models")), /* @__PURE__ */ React4.createElement(TableCell, { align: "right" }, "Actions"))), /* @__PURE__ */ React4.createElement(TableBody, null, renderTableBody())))
1379
+ ), /* @__PURE__ */ React4.createElement(Dialog, { open: !!deleteConfirmId, onClose: () => setDeleteConfirmId(null), maxWidth: "xs", fullWidth: true }, /* @__PURE__ */ React4.createElement(DialogTitle, null, "Revoke Key?"), /* @__PURE__ */ React4.createElement(DialogContent, null, /* @__PURE__ */ React4.createElement(Typography3, null, "This will permanently revoke the key. Any integrations using it will stop working immediately.")), /* @__PURE__ */ React4.createElement(DialogActions, null, /* @__PURE__ */ React4.createElement(Button2, { onClick: () => setDeleteConfirmId(null), disabled: deleteSubmitting }, "Cancel"), /* @__PURE__ */ React4.createElement(
1488
1380
  Button2,
1489
1381
  {
1490
1382
  onClick: handleConfirmDelete,
@@ -1492,8 +1384,8 @@ var init_KeysTable = __esm({
1492
1384
  color: "error",
1493
1385
  disabled: deleteSubmitting
1494
1386
  },
1495
- deleteSubmitting ? /* @__PURE__ */ React3.createElement(CircularProgress, { size: 20 }) : "Revoke"
1496
- ))), /* @__PURE__ */ React3.createElement(Dialog, { open: pruneConfirmCount !== null, onClose: () => setPruneConfirmCount(null), maxWidth: "xs", fullWidth: true }, /* @__PURE__ */ React3.createElement(DialogTitle, null, "Prune Expired Keys?"), /* @__PURE__ */ React3.createElement(DialogContent, null, /* @__PURE__ */ React3.createElement(Typography3, null, "This will permanently delete ", pruneConfirmCount, " expired key", pruneConfirmCount !== 1 ? "s" : "", " from LiteLLM. Any integrations using them will stop working immediately.")), /* @__PURE__ */ React3.createElement(DialogActions, null, /* @__PURE__ */ React3.createElement(Button2, { onClick: () => setPruneConfirmCount(null), disabled: pruneSubmitting }, "Cancel"), /* @__PURE__ */ React3.createElement(
1387
+ deleteSubmitting ? /* @__PURE__ */ React4.createElement(CircularProgress, { size: 20 }) : "Revoke"
1388
+ ))), /* @__PURE__ */ React4.createElement(Dialog, { open: pruneConfirmCount !== null, onClose: () => setPruneConfirmCount(null), maxWidth: "xs", fullWidth: true }, /* @__PURE__ */ React4.createElement(DialogTitle, null, "Prune Expired Keys?"), /* @__PURE__ */ React4.createElement(DialogContent, null, /* @__PURE__ */ React4.createElement(Typography3, null, "This will permanently delete ", pruneConfirmCount, " expired key", pruneConfirmCount !== 1 ? "s" : "", " from LiteLLM. Any integrations using them will stop working immediately.")), /* @__PURE__ */ React4.createElement(DialogActions, null, /* @__PURE__ */ React4.createElement(Button2, { onClick: () => setPruneConfirmCount(null), disabled: pruneSubmitting }, "Cancel"), /* @__PURE__ */ React4.createElement(
1497
1389
  Button2,
1498
1390
  {
1499
1391
  onClick: handlePruneExpired,
@@ -1501,7 +1393,7 @@ var init_KeysTable = __esm({
1501
1393
  color: "error",
1502
1394
  disabled: pruneSubmitting
1503
1395
  },
1504
- pruneSubmitting ? /* @__PURE__ */ React3.createElement(CircularProgress, { size: 20 }) : "Prune"
1396
+ pruneSubmitting ? /* @__PURE__ */ React4.createElement(CircularProgress, { size: 20 }) : "Prune"
1505
1397
  ))));
1506
1398
  };
1507
1399
  }
@@ -1521,8 +1413,8 @@ var init_format = __esm({
1521
1413
  }
1522
1414
  });
1523
1415
 
1524
- // src/components/GenerateKeyDialog.tsx
1525
- import React4, { useState as useState2, useEffect, useMemo as useMemo3 } from "react";
1416
+ // src/components/KeyFormDialog.tsx
1417
+ import React5, { useState as useState2, useEffect, useMemo as useMemo3 } from "react";
1526
1418
  import Box4 from "@mui/material/Box";
1527
1419
  import Typography4 from "@mui/material/Typography";
1528
1420
  import Dialog2 from "@mui/material/Dialog";
@@ -1534,7 +1426,7 @@ import MenuItem from "@mui/material/MenuItem";
1534
1426
  import Button3 from "@mui/material/Button";
1535
1427
  import IconButton2 from "@mui/material/IconButton";
1536
1428
  import CircularProgress2 from "@mui/material/CircularProgress";
1537
- import Autocomplete2 from "@mui/material/Autocomplete";
1429
+ import Autocomplete from "@mui/material/Autocomplete";
1538
1430
  import Checkbox from "@mui/material/Checkbox";
1539
1431
  import FormControlLabel from "@mui/material/FormControlLabel";
1540
1432
  import Alert from "@mui/material/Alert";
@@ -1636,17 +1528,17 @@ function teamHelperText(teamError, teamRequired) {
1636
1528
  if (teamRequired) return "Bind this key to a team for scoped access";
1637
1529
  return "Optional: bind this key to a specific team for scoped access";
1638
1530
  }
1639
- function budgetHelperText(budgetInvalid, budgetEstimate) {
1640
- if (budgetInvalid) return 'Enter a positive budget or tick "Unlimited"';
1531
+ function budgetHelperText(budgetInvalid, budgetEstimate, unlimited) {
1532
+ if (budgetInvalid && !unlimited) return 'Enter a positive budget or tick "Unlimited"';
1641
1533
  if (budgetEstimate !== null) return `\u2248 ${fmtInt(budgetEstimate)} tokens at the selected model's rate`;
1642
1534
  return void 0;
1643
1535
  }
1644
- function fmtCost2(perToken) {
1536
+ function fmtCost(perToken) {
1645
1537
  if (!perToken) return null;
1646
1538
  const per1k = perToken * 1e3;
1647
1539
  return per1k < 0.01 ? `$${(perToken * 1e6).toFixed(2)}/M` : `$${per1k.toFixed(3)}/1K`;
1648
1540
  }
1649
- function formatContextWindow2(maxInput, maxOutput) {
1541
+ function formatContextWindow(maxInput, maxOutput) {
1650
1542
  if (!maxInput && !maxOutput) return null;
1651
1543
  const fmt = (n) => {
1652
1544
  if (!n) return null;
@@ -1661,9 +1553,9 @@ function formatContextWindow2(maxInput, maxOutput) {
1661
1553
  if (outPart) return `ctx ${outPart} out`;
1662
1554
  return null;
1663
1555
  }
1664
- var generateDefaultAlias, emptyForm, ALL_PROXY_MODELS, SNIPPET_FILE_HINTS, SnippetTabs, GenerateKeyDialog;
1665
- var init_GenerateKeyDialog = __esm({
1666
- "src/components/GenerateKeyDialog.tsx"() {
1556
+ var generateDefaultAlias, createForm, editForm, ALL_PROXY_MODELS, SNIPPET_FILE_HINTS, SnippetTabs, KeyFormDialog;
1557
+ var init_KeyFormDialog = __esm({
1558
+ "src/components/KeyFormDialog.tsx"() {
1667
1559
  "use strict";
1668
1560
  init_format();
1669
1561
  generateDefaultAlias = (username) => {
@@ -1671,8 +1563,8 @@ var init_GenerateKeyDialog = __esm({
1671
1563
  const hash = Math.random().toString(36).slice(2, 8);
1672
1564
  return `${base}-${hash}`;
1673
1565
  };
1674
- emptyForm = () => ({
1675
- alias: "",
1566
+ createForm = (username) => ({
1567
+ alias: generateDefaultAlias(username),
1676
1568
  models: [],
1677
1569
  duration: "30d",
1678
1570
  max_budget: 100,
@@ -1681,6 +1573,13 @@ var init_GenerateKeyDialog = __esm({
1681
1573
  team_id: void 0,
1682
1574
  key_type: "llm_api"
1683
1575
  });
1576
+ editForm = (k) => ({
1577
+ key_alias: k.key_alias ?? "",
1578
+ models: k.models ?? [],
1579
+ max_budget: k.max_budget,
1580
+ tpm_limit: k.tpm_limit,
1581
+ rpm_limit: k.rpm_limit
1582
+ });
1684
1583
  ALL_PROXY_MODELS = "all-proxy-models";
1685
1584
  SNIPPET_FILE_HINTS = {
1686
1585
  opencode: "Add to ~/.config/opencode/opencode.json",
@@ -1692,14 +1591,14 @@ var init_GenerateKeyDialog = __esm({
1692
1591
  const snippetKey = tab === "claude-code" ? "claudeCode" : tab;
1693
1592
  const code = snippets[snippetKey];
1694
1593
  const fileHint = SNIPPET_FILE_HINTS[tab];
1695
- return /* @__PURE__ */ React4.createElement(Box4, null, /* @__PURE__ */ React4.createElement(Tabs, { value: tab, onChange: (_, v) => setTab(v), sx: { mb: 1 }, variant: "scrollable" }, /* @__PURE__ */ React4.createElement(Tab, { label: "curl", value: "curl" }), /* @__PURE__ */ React4.createElement(Tab, { label: "OpenAI SDK", value: "openai" }), /* @__PURE__ */ React4.createElement(Tab, { label: "opencode", value: "opencode" }), /* @__PURE__ */ React4.createElement(Tab, { label: "pi", value: "pi" }), /* @__PURE__ */ React4.createElement(Tab, { label: "Claude Code", value: "claude-code" })), fileHint && /* @__PURE__ */ React4.createElement(Typography4, { variant: "caption", color: "text.secondary", display: "block", mb: 0.5 }, fileHint), /* @__PURE__ */ React4.createElement(
1594
+ return /* @__PURE__ */ React5.createElement(Box4, null, /* @__PURE__ */ React5.createElement(Tabs, { value: tab, onChange: (_, v) => setTab(v), sx: { mb: 1 }, variant: "scrollable" }, /* @__PURE__ */ React5.createElement(Tab, { label: "curl", value: "curl" }), /* @__PURE__ */ React5.createElement(Tab, { label: "OpenAI SDK", value: "openai" }), /* @__PURE__ */ React5.createElement(Tab, { label: "opencode", value: "opencode" }), /* @__PURE__ */ React5.createElement(Tab, { label: "pi", value: "pi" }), /* @__PURE__ */ React5.createElement(Tab, { label: "Claude Code", value: "claude-code" })), fileHint && /* @__PURE__ */ React5.createElement(Typography4, { variant: "caption", color: "text.secondary", display: "block", mb: 0.5 }, fileHint), /* @__PURE__ */ React5.createElement(
1696
1595
  Box4,
1697
1596
  {
1698
1597
  position: "relative",
1699
1598
  p: 1.5,
1700
1599
  sx: { backgroundColor: "action.hover", border: "1px solid", borderColor: "divider", borderRadius: 1 }
1701
1600
  },
1702
- /* @__PURE__ */ React4.createElement(
1601
+ /* @__PURE__ */ React5.createElement(
1703
1602
  IconButton2,
1704
1603
  {
1705
1604
  size: "small",
@@ -1707,9 +1606,9 @@ var init_GenerateKeyDialog = __esm({
1707
1606
  title: "Copy snippet",
1708
1607
  sx: { position: "absolute", top: 4, right: 4 }
1709
1608
  },
1710
- /* @__PURE__ */ React4.createElement(ContentCopy2, { fontSize: "small" })
1609
+ /* @__PURE__ */ React5.createElement(ContentCopy2, { fontSize: "small" })
1711
1610
  ),
1712
- /* @__PURE__ */ React4.createElement(
1611
+ /* @__PURE__ */ React5.createElement(
1713
1612
  Typography4,
1714
1613
  {
1715
1614
  component: "pre",
@@ -1724,49 +1623,82 @@ var init_GenerateKeyDialog = __esm({
1724
1623
  },
1725
1624
  code
1726
1625
  ),
1727
- model && /* @__PURE__ */ React4.createElement(Typography4, { variant: "caption", color: "text.secondary", display: "block", mt: 1 }, "Using model \u201C", model, "\u201D \u2014 swap it for any model you have access to.")
1626
+ model && /* @__PURE__ */ React5.createElement(Typography4, { variant: "caption", color: "text.secondary", display: "block", mt: 1 }, "Using model \u201C", model, "\u201D \u2014 swap it for any model you have access to.")
1728
1627
  ));
1729
1628
  };
1730
- GenerateKeyDialog = ({
1629
+ KeyFormDialog = ({
1731
1630
  open,
1732
1631
  onClose,
1632
+ mode,
1633
+ keyToEdit = null,
1733
1634
  keys,
1734
1635
  models,
1735
1636
  teams,
1736
1637
  username,
1737
1638
  keyGenerationSettings,
1738
- onGenerateKey,
1639
+ onCreateKey,
1640
+ onUpdateKey,
1641
+ onResetKeySpend,
1739
1642
  onGetConfig
1740
1643
  }) => {
1741
- const [formData, setFormData] = useState2(emptyForm());
1644
+ const isCreate = mode === "create";
1645
+ const [generateForm, setGenerateForm] = useState2(createForm(username));
1646
+ const [editFormState, setEditFormState] = useState2({});
1742
1647
  const [unlimitedBudget, setUnlimitedBudget] = useState2(false);
1743
1648
  const [submitting, setSubmitting] = useState2(false);
1744
1649
  const [newKeyValue, setNewKeyValue] = useState2(null);
1745
1650
  const [newKeySnippets, setNewKeySnippets] = useState2(null);
1746
1651
  const [newKeyModel, setNewKeyModel] = useState2("");
1747
1652
  const [generateError, setGenerateError] = useState2(null);
1653
+ const [editError, setEditError] = useState2(null);
1654
+ const [resetSpendConfirm, setResetSpendConfirm] = useState2(false);
1655
+ const [resetSpendSubmitting, setResetSpendSubmitting] = useState2(false);
1748
1656
  useEffect(() => {
1749
1657
  if (open) {
1750
- setFormData({ ...emptyForm(), alias: generateDefaultAlias(username) });
1751
- setUnlimitedBudget(false);
1658
+ if (isCreate) {
1659
+ setGenerateForm(createForm(username));
1660
+ } else if (keyToEdit) {
1661
+ setEditFormState(editForm(keyToEdit));
1662
+ setUnlimitedBudget(
1663
+ keyGenerationSettings?.allowUnlimitedBudget === true && (keyToEdit.max_budget === null || keyToEdit.max_budget === void 0)
1664
+ );
1665
+ } else {
1666
+ setEditFormState({});
1667
+ setUnlimitedBudget(false);
1668
+ }
1752
1669
  setNewKeyValue(null);
1753
1670
  setNewKeySnippets(null);
1754
1671
  setNewKeyModel("");
1755
1672
  setGenerateError(null);
1673
+ setEditError(null);
1674
+ setResetSpendConfirm(false);
1756
1675
  }
1757
- }, [open, username]);
1676
+ }, [open, isCreate, keyToEdit, username, keyGenerationSettings?.allowUnlimitedBudget]);
1758
1677
  const allowUnlimitedBudget = keyGenerationSettings?.allowUnlimitedBudget ?? false;
1759
1678
  const teamRequired = keyGenerationSettings?.teamRequired ?? true;
1760
- const selectedTeam = teams.find((t) => t.team_id === formData.team_id) ?? null;
1679
+ const formData = isCreate ? generateForm : editFormState;
1680
+ const setFormData = (patch) => {
1681
+ if (isCreate) {
1682
+ setGenerateForm((prev) => ({ ...prev, ...patch }));
1683
+ } else {
1684
+ setEditFormState((prev) => ({ ...prev, ...patch }));
1685
+ }
1686
+ };
1687
+ const currentAlias = (isCreate ? generateForm.alias : editFormState.key_alias) ?? "";
1688
+ const setAliasField = (value) => {
1689
+ if (isCreate) setFormData({ alias: value });
1690
+ else setFormData({ key_alias: value });
1691
+ };
1692
+ const selectedTeam = teams.find((t) => t.team_id === (isCreate ? generateForm.team_id : void 0)) ?? null;
1761
1693
  const availableModels = useMemo3(() => {
1762
1694
  return models.filter((m) => isModelAllowedByTeam(m, selectedTeam?.models));
1763
1695
  }, [models, selectedTeam]);
1764
1696
  const selectedModels = availableModels.filter((m) => (formData.models || []).includes(m.model_name));
1765
- const aliasError = !(formData.alias || "").trim();
1766
- const teamError = teamRequired && !formData.team_id;
1697
+ const aliasError = !currentAlias.trim();
1698
+ const teamError = isCreate && teamRequired && !generateForm.team_id;
1767
1699
  const budgetInvalid = !unlimitedBudget && (formData.max_budget === void 0 || formData.max_budget === null || formData.max_budget <= 0);
1768
- const canGenerate = !aliasError && !teamError && !budgetInvalid && !submitting;
1769
- const aliasDuplicate = !!formData.alias && keys.some((k) => k.key_alias === formData.alias);
1700
+ const canSubmit = !aliasError && !teamError && !budgetInvalid && !submitting;
1701
+ const aliasDuplicate = !!currentAlias && keys.some((k) => k.key_alias === currentAlias && k !== keyToEdit);
1770
1702
  const budgetEstimate = useMemo3(() => {
1771
1703
  if (unlimitedBudget || budgetInvalid) return null;
1772
1704
  const inputCosts = selectedModels.map((m) => m.input_cost_per_token).filter((c) => typeof c === "number" && c > 0);
@@ -1779,11 +1711,11 @@ var init_GenerateKeyDialog = __esm({
1779
1711
  setGenerateError(null);
1780
1712
  try {
1781
1713
  const request = {
1782
- ...formData,
1783
- max_budget: unlimitedBudget ? null : formData.max_budget
1714
+ ...generateForm,
1715
+ max_budget: unlimitedBudget ? null : generateForm.max_budget
1784
1716
  };
1785
- const response = await onGenerateKey(request);
1786
- const model = formData.models?.[0] ?? availableModels[0]?.model_name ?? "";
1717
+ const response = await onCreateKey(request);
1718
+ const model = generateForm.models?.[0] ?? availableModels[0]?.model_name ?? "";
1787
1719
  setNewKeyValue(response.key);
1788
1720
  setNewKeyModel(model);
1789
1721
  setNewKeySnippets(null);
@@ -1792,7 +1724,7 @@ var init_GenerateKeyDialog = __esm({
1792
1724
  setNewKeySnippets(buildSnippets(config.baseUrl, response.key, model));
1793
1725
  } catch {
1794
1726
  }
1795
- setFormData(emptyForm());
1727
+ setGenerateForm(createForm(username));
1796
1728
  setUnlimitedBudget(false);
1797
1729
  } catch (error) {
1798
1730
  setGenerateError(error?.message ?? "Failed to generate key");
@@ -1800,27 +1732,58 @@ var init_GenerateKeyDialog = __esm({
1800
1732
  setSubmitting(false);
1801
1733
  }
1802
1734
  };
1735
+ const handleUpdate = async () => {
1736
+ if (!keyToEdit) return;
1737
+ setSubmitting(true);
1738
+ setEditError(null);
1739
+ try {
1740
+ const request = {
1741
+ ...editFormState,
1742
+ max_budget: unlimitedBudget ? null : editFormState.max_budget
1743
+ };
1744
+ await onUpdateKey(keyToEdit.token ?? keyToEdit.key, request);
1745
+ onClose();
1746
+ } catch (error) {
1747
+ setEditError(error?.message ?? "Failed to update key");
1748
+ } finally {
1749
+ setSubmitting(false);
1750
+ }
1751
+ };
1752
+ const handleResetSpend = async () => {
1753
+ if (!keyToEdit || !onResetKeySpend) return;
1754
+ setResetSpendSubmitting(true);
1755
+ try {
1756
+ await onResetKeySpend(keyToEdit.token ?? keyToEdit.key);
1757
+ setResetSpendConfirm(false);
1758
+ onClose();
1759
+ } finally {
1760
+ setResetSpendSubmitting(false);
1761
+ }
1762
+ };
1803
1763
  const handleClose = () => {
1804
1764
  onClose();
1805
1765
  setGenerateError(null);
1766
+ setEditError(null);
1806
1767
  setNewKeyValue(null);
1807
1768
  setNewKeySnippets(null);
1808
- setFormData(emptyForm());
1769
+ setGenerateForm(createForm(username));
1770
+ setEditFormState({});
1809
1771
  setUnlimitedBudget(false);
1772
+ setResetSpendConfirm(false);
1810
1773
  };
1811
1774
  const copyToClipboard = (text) => {
1812
1775
  navigator.clipboard.writeText(text);
1813
1776
  };
1814
1777
  const modelOption = (m) => {
1815
- const inCost = fmtCost2(m.input_cost_per_token);
1816
- const outCost = fmtCost2(m.output_cost_per_token);
1817
- const ctx = formatContextWindow2(m.max_input_tokens, m.max_output_tokens);
1818
- return /* @__PURE__ */ React4.createElement(Box4, null, /* @__PURE__ */ React4.createElement("span", null, m.model_name), m.supports_function_calling && " \u{1F527}", m.supports_vision && " \u{1F441}\uFE0F", ctx && /* @__PURE__ */ React4.createElement(Typography4, { variant: "caption", color: "text.secondary", sx: { ml: 1 } }, ctx), (inCost || outCost) && /* @__PURE__ */ React4.createElement(Typography4, { variant: "caption", color: "text.secondary", sx: { ml: 1 } }, inCost, " in \xB7 ", outCost, " out"));
1778
+ const inCost = fmtCost(m.input_cost_per_token);
1779
+ const outCost = fmtCost(m.output_cost_per_token);
1780
+ const ctx = formatContextWindow(m.max_input_tokens, m.max_output_tokens);
1781
+ return /* @__PURE__ */ React5.createElement(Box4, null, /* @__PURE__ */ React5.createElement("span", null, m.model_name), m.supports_function_calling && " \u{1F527}", m.supports_vision && " \u{1F441}\uFE0F", ctx && /* @__PURE__ */ React5.createElement(Typography4, { variant: "caption", color: "text.secondary", sx: { ml: 1 } }, ctx), (inCost || outCost) && /* @__PURE__ */ React5.createElement(Typography4, { variant: "caption", color: "text.secondary", sx: { ml: 1 } }, inCost, " in \xB7 ", outCost, " out"));
1819
1782
  };
1820
1783
  const renderTeamField = () => {
1821
1784
  if (teams.length > 0) {
1822
- return /* @__PURE__ */ React4.createElement(
1823
- Autocomplete2,
1785
+ return /* @__PURE__ */ React5.createElement(
1786
+ Autocomplete,
1824
1787
  {
1825
1788
  options: teams,
1826
1789
  getOptionLabel: (t) => t.team_alias || t.team_id,
@@ -1830,28 +1793,61 @@ var init_GenerateKeyDialog = __esm({
1830
1793
  const model = models.find((m) => m.model_name === name);
1831
1794
  return model ? isModelAllowedByTeam(model, team?.models) : false;
1832
1795
  });
1833
- setFormData({ ...formData, team_id: team?.team_id, models: restrictedModels });
1796
+ setFormData({ team_id: team?.team_id, models: restrictedModels });
1834
1797
  },
1835
- renderInput: (params) => /* @__PURE__ */ React4.createElement(
1798
+ disabled: !isCreate,
1799
+ renderInput: (params) => /* @__PURE__ */ React5.createElement(
1836
1800
  TextField2,
1837
1801
  {
1838
1802
  ...params,
1839
1803
  label: "Team",
1840
1804
  error: teamError,
1841
- helperText: teamHelperText(teamError, teamRequired),
1842
- required: teamRequired,
1805
+ helperText: isCreate ? teamHelperText(teamError, teamRequired) : "Team binding is fixed after creation \u2014 delete and recreate the key to change it",
1806
+ required: isCreate && teamRequired,
1843
1807
  fullWidth: true
1844
1808
  }
1845
1809
  )
1846
1810
  }
1847
1811
  );
1848
1812
  }
1849
- if (teamRequired) {
1850
- return /* @__PURE__ */ React4.createElement(Typography4, { variant: "body2", color: "error" }, "Team selection is required, but you don't belong to any team yet \u2014 contact your administrator.");
1813
+ if (teamRequired && isCreate) {
1814
+ return /* @__PURE__ */ React5.createElement(Typography4, { variant: "body2", color: "error" }, "Team selection is required, but you don't belong to any team yet \u2014 contact your administrator.");
1851
1815
  }
1852
1816
  return null;
1853
1817
  };
1854
- return /* @__PURE__ */ React4.createElement(Dialog2, { open, onClose: handleClose, maxWidth: "sm", fullWidth: true }, /* @__PURE__ */ React4.createElement(DialogTitle2, null, newKeyValue ? "Key Generated" : "Generate New Key"), /* @__PURE__ */ React4.createElement(DialogContent2, null, newKeyValue ? /* @__PURE__ */ React4.createElement(Box4, null, /* @__PURE__ */ React4.createElement(Typography4, { variant: "body2", color: "text.secondary", gutterBottom: true }, "Copy this key now. You won't be able to see it again."), /* @__PURE__ */ React4.createElement(
1818
+ const editKeyId = keyToEdit?.token ?? keyToEdit?.key ?? "";
1819
+ const renderDangerZone = () => {
1820
+ if (isCreate || !onResetKeySpend) return null;
1821
+ return /* @__PURE__ */ React5.createElement(Box4, { mt: 1, pt: 2, borderTop: "1px solid", sx: { borderColor: "divider" } }, /* @__PURE__ */ React5.createElement(Typography4, { variant: "caption", color: "text.secondary", display: "block", mb: 1 }, "Danger Zone"), !resetSpendConfirm ? /* @__PURE__ */ React5.createElement(
1822
+ Button3,
1823
+ {
1824
+ size: "small",
1825
+ color: "warning",
1826
+ variant: "outlined",
1827
+ onClick: () => setResetSpendConfirm(true)
1828
+ },
1829
+ "Reset Spend to $0"
1830
+ ) : /* @__PURE__ */ React5.createElement(Box4, { display: "flex", alignItems: "center", gap: 1, flexWrap: "wrap" }, /* @__PURE__ */ React5.createElement(Typography4, { variant: "body2", color: "warning.main" }, "Zero out spend counter?"), /* @__PURE__ */ React5.createElement(
1831
+ Button3,
1832
+ {
1833
+ size: "small",
1834
+ color: "warning",
1835
+ variant: "contained",
1836
+ disabled: resetSpendSubmitting,
1837
+ onClick: handleResetSpend
1838
+ },
1839
+ resetSpendSubmitting ? /* @__PURE__ */ React5.createElement(CircularProgress2, { size: 16 }) : "Confirm"
1840
+ ), /* @__PURE__ */ React5.createElement(Button3, { size: "small", onClick: () => setResetSpendConfirm(false) }, "Cancel")));
1841
+ };
1842
+ const maskKey = (key) => {
1843
+ if (key.length <= 8) return "***";
1844
+ return `${key.slice(0, 4)}...${key.slice(-4)}`;
1845
+ };
1846
+ let dialogTitle = "Edit Key";
1847
+ if (newKeyValue) dialogTitle = "Key Generated";
1848
+ else if (isCreate) dialogTitle = "Generate New Key";
1849
+ const submitLabel = isCreate ? "Generate" : "Save";
1850
+ return /* @__PURE__ */ React5.createElement(Dialog2, { open, onClose: handleClose, maxWidth: "sm", fullWidth: true }, /* @__PURE__ */ React5.createElement(DialogTitle2, null, dialogTitle), /* @__PURE__ */ React5.createElement(DialogContent2, null, newKeyValue ? /* @__PURE__ */ React5.createElement(Box4, null, /* @__PURE__ */ React5.createElement(Typography4, { variant: "body2", color: "text.secondary", gutterBottom: true }, "Copy this key now. You won't be able to see it again."), /* @__PURE__ */ React5.createElement(
1855
1851
  Box4,
1856
1852
  {
1857
1853
  display: "flex",
@@ -1866,7 +1862,7 @@ var init_GenerateKeyDialog = __esm({
1866
1862
  borderRadius: 1
1867
1863
  }
1868
1864
  },
1869
- /* @__PURE__ */ React4.createElement(
1865
+ /* @__PURE__ */ React5.createElement(
1870
1866
  Typography4,
1871
1867
  {
1872
1868
  component: "code",
@@ -1875,8 +1871,8 @@ var init_GenerateKeyDialog = __esm({
1875
1871
  },
1876
1872
  newKeyValue
1877
1873
  ),
1878
- /* @__PURE__ */ React4.createElement(IconButton2, { onClick: () => copyToClipboard(newKeyValue) }, /* @__PURE__ */ React4.createElement(ContentCopy2, null))
1879
- ), newKeySnippets && /* @__PURE__ */ React4.createElement(Box4, { mt: 2 }, /* @__PURE__ */ React4.createElement(Typography4, { variant: "caption", color: "text.secondary", display: "block", gutterBottom: true }, "Public endpoint \u2014 paste into any tool's base URL / API base field"), /* @__PURE__ */ React4.createElement(
1874
+ /* @__PURE__ */ React5.createElement(IconButton2, { onClick: () => copyToClipboard(newKeyValue) }, /* @__PURE__ */ React5.createElement(ContentCopy2, null))
1875
+ ), newKeySnippets && /* @__PURE__ */ React5.createElement(Box4, { mt: 2 }, /* @__PURE__ */ React5.createElement(Typography4, { variant: "caption", color: "text.secondary", display: "block", gutterBottom: true }, "Public endpoint \u2014 paste into any tool's base URL / API base field"), /* @__PURE__ */ React5.createElement(
1880
1876
  Box4,
1881
1877
  {
1882
1878
  display: "flex",
@@ -1890,7 +1886,7 @@ var init_GenerateKeyDialog = __esm({
1890
1886
  borderRadius: 1
1891
1887
  }
1892
1888
  },
1893
- /* @__PURE__ */ React4.createElement(
1889
+ /* @__PURE__ */ React5.createElement(
1894
1890
  Typography4,
1895
1891
  {
1896
1892
  component: "code",
@@ -1899,15 +1895,16 @@ var init_GenerateKeyDialog = __esm({
1899
1895
  },
1900
1896
  newKeySnippets.publicEndpoint
1901
1897
  ),
1902
- /* @__PURE__ */ React4.createElement(IconButton2, { size: "small", onClick: () => copyToClipboard(newKeySnippets.publicEndpoint) }, /* @__PURE__ */ React4.createElement(ContentCopy2, { fontSize: "small" }))
1903
- )), newKeySnippets && /* @__PURE__ */ React4.createElement(Box4, { mt: 3 }, /* @__PURE__ */ React4.createElement(Box4, { display: "flex", alignItems: "center", gap: 1, mb: 1 }, /* @__PURE__ */ React4.createElement(Code, { fontSize: "small", color: "action" }), /* @__PURE__ */ React4.createElement(Typography4, { variant: "subtitle2" }, "Start calling the proxy \u2014 paste and run")), /* @__PURE__ */ React4.createElement(SnippetTabs, { snippets: newKeySnippets, model: newKeyModel, onCopy: copyToClipboard }))) : /* @__PURE__ */ React4.createElement(Box4, { display: "flex", flexDirection: "column", gap: 2, mt: 1 }, generateError && /* @__PURE__ */ React4.createElement(Alert, { severity: "error", onClose: () => setGenerateError(null) }, generateError), /* @__PURE__ */ React4.createElement(
1898
+ /* @__PURE__ */ React5.createElement(IconButton2, { size: "small", onClick: () => copyToClipboard(newKeySnippets.publicEndpoint) }, /* @__PURE__ */ React5.createElement(ContentCopy2, { fontSize: "small" }))
1899
+ )), newKeySnippets && /* @__PURE__ */ React5.createElement(Box4, { mt: 3 }, /* @__PURE__ */ React5.createElement(Box4, { display: "flex", alignItems: "center", gap: 1, mb: 1 }, /* @__PURE__ */ React5.createElement(Code, { fontSize: "small", color: "action" }), /* @__PURE__ */ React5.createElement(Typography4, { variant: "subtitle2" }, "Start calling the proxy \u2014 paste and run")), /* @__PURE__ */ React5.createElement(SnippetTabs, { snippets: newKeySnippets, model: newKeyModel, onCopy: copyToClipboard }))) : /* @__PURE__ */ React5.createElement(Box4, { display: "flex", flexDirection: "column", gap: 2, mt: 1 }, !isCreate && /* @__PURE__ */ React5.createElement(Typography4, { variant: "body2", color: "text.secondary" }, /* @__PURE__ */ React5.createElement("code", { style: { fontFamily: "monospace", color: "inherit" } }, maskKey(editKeyId))), generateError && /* @__PURE__ */ React5.createElement(Alert, { severity: "error", onClose: () => setGenerateError(null) }, generateError), editError && /* @__PURE__ */ React5.createElement(Alert, { severity: "error", onClose: () => setEditError(null) }, editError), /* @__PURE__ */ React5.createElement(
1904
1900
  TextField2,
1905
1901
  {
1906
1902
  label: "Alias",
1907
- value: formData.alias || "",
1903
+ value: currentAlias,
1908
1904
  onChange: (e) => {
1909
- setFormData({ ...formData, alias: e.target.value });
1905
+ setAliasField(e.target.value);
1910
1906
  setGenerateError(null);
1907
+ setEditError(null);
1911
1908
  },
1912
1909
  error: aliasError,
1913
1910
  color: aliasDuplicate ? "warning" : void 0,
@@ -1915,31 +1912,31 @@ var init_GenerateKeyDialog = __esm({
1915
1912
  required: true,
1916
1913
  fullWidth: true
1917
1914
  }
1918
- ), /* @__PURE__ */ React4.createElement(
1915
+ ), !isCreate ? null : /* @__PURE__ */ React5.createElement(
1919
1916
  TextField2,
1920
1917
  {
1921
1918
  select: true,
1922
1919
  label: "Duration",
1923
- value: formData.duration || "30d",
1924
- onChange: (e) => setFormData({ ...formData, duration: e.target.value }),
1920
+ value: generateForm.duration || "30d",
1921
+ onChange: (e) => setFormData({ duration: e.target.value }),
1925
1922
  fullWidth: true
1926
1923
  },
1927
- /* @__PURE__ */ React4.createElement(MenuItem, { value: "1d" }, "1 Day"),
1928
- /* @__PURE__ */ React4.createElement(MenuItem, { value: "7d" }, "7 Days"),
1929
- /* @__PURE__ */ React4.createElement(MenuItem, { value: "30d" }, "30 Days"),
1930
- /* @__PURE__ */ React4.createElement(MenuItem, { value: "90d" }, "90 Days"),
1931
- /* @__PURE__ */ React4.createElement(MenuItem, { value: "1y" }, "1 Year")
1932
- ), renderTeamField(), availableModels.length > 0 && /* @__PURE__ */ React4.createElement(
1933
- Autocomplete2,
1924
+ /* @__PURE__ */ React5.createElement(MenuItem, { value: "1d" }, "1 Day"),
1925
+ /* @__PURE__ */ React5.createElement(MenuItem, { value: "7d" }, "7 Days"),
1926
+ /* @__PURE__ */ React5.createElement(MenuItem, { value: "30d" }, "30 Days"),
1927
+ /* @__PURE__ */ React5.createElement(MenuItem, { value: "90d" }, "90 Days"),
1928
+ /* @__PURE__ */ React5.createElement(MenuItem, { value: "1y" }, "1 Year")
1929
+ ), renderTeamField(), availableModels.length > 0 && /* @__PURE__ */ React5.createElement(
1930
+ Autocomplete,
1934
1931
  {
1935
1932
  multiple: true,
1936
1933
  options: availableModels,
1937
1934
  groupBy: (m) => m.mode || "other",
1938
1935
  getOptionLabel: (m) => m.model_name,
1939
1936
  value: selectedModels,
1940
- onChange: (_e, selected) => setFormData({ ...formData, models: selected.map((m) => m.model_name) }),
1941
- renderOption: (props, m) => /* @__PURE__ */ React4.createElement("li", { ...props }, modelOption(m)),
1942
- renderInput: (params) => /* @__PURE__ */ React4.createElement(
1937
+ onChange: (_e, selected) => setFormData({ models: selected.map((m) => m.model_name) }),
1938
+ renderOption: (props, m) => /* @__PURE__ */ React5.createElement("li", { ...props }, modelOption(m)),
1939
+ renderInput: (params) => /* @__PURE__ */ React5.createElement(
1943
1940
  TextField2,
1944
1941
  {
1945
1942
  ...params,
@@ -1949,74 +1946,74 @@ var init_GenerateKeyDialog = __esm({
1949
1946
  }
1950
1947
  )
1951
1948
  }
1952
- ), allowUnlimitedBudget && /* @__PURE__ */ React4.createElement(
1949
+ ), allowUnlimitedBudget && /* @__PURE__ */ React5.createElement(
1953
1950
  FormControlLabel,
1954
1951
  {
1955
- control: /* @__PURE__ */ React4.createElement(
1952
+ control: /* @__PURE__ */ React5.createElement(
1956
1953
  Checkbox,
1957
1954
  {
1958
1955
  checked: unlimitedBudget,
1959
1956
  onChange: (e) => {
1960
1957
  setUnlimitedBudget(e.target.checked);
1961
1958
  if (e.target.checked) {
1962
- setFormData({ ...formData, max_budget: null });
1959
+ setFormData({ max_budget: null });
1963
1960
  } else {
1964
- setFormData({ ...formData, max_budget: 100 });
1961
+ setFormData({ max_budget: editFormState.max_budget ?? generateForm.max_budget ?? 100 });
1965
1962
  }
1966
1963
  }
1967
1964
  }
1968
1965
  ),
1969
1966
  label: "Unlimited budget"
1970
1967
  }
1971
- ), /* @__PURE__ */ React4.createElement(
1968
+ ), /* @__PURE__ */ React5.createElement(
1972
1969
  TextField2,
1973
1970
  {
1974
1971
  label: "Max Budget (USD)",
1975
1972
  type: "number",
1976
1973
  value: unlimitedBudget ? "" : formData.max_budget ?? "",
1977
- onChange: (e) => setFormData({ ...formData, max_budget: e.target.value ? Number(e.target.value) : void 0 }),
1974
+ onChange: (e) => setFormData({ max_budget: e.target.value ? Number(e.target.value) : void 0 }),
1978
1975
  error: budgetInvalid,
1979
- helperText: budgetHelperText(budgetInvalid, budgetEstimate),
1976
+ helperText: budgetHelperText(budgetInvalid, budgetEstimate, unlimitedBudget),
1980
1977
  disabled: unlimitedBudget,
1981
1978
  required: true,
1982
1979
  fullWidth: true
1983
1980
  }
1984
- ), /* @__PURE__ */ React4.createElement(Accordion, { disableGutters: true, elevation: 0, sx: { border: "1px solid", borderColor: "divider", "&:before": { display: "none" } } }, /* @__PURE__ */ React4.createElement(AccordionSummary, { expandIcon: /* @__PURE__ */ React4.createElement(ExpandMore, null) }, /* @__PURE__ */ React4.createElement(Typography4, { variant: "body2", fontWeight: 600 }, "Advanced")), /* @__PURE__ */ React4.createElement(AccordionDetails, { sx: { display: "flex", flexDirection: "column", gap: 2 } }, /* @__PURE__ */ React4.createElement(
1981
+ ), /* @__PURE__ */ React5.createElement(Accordion, { disableGutters: true, elevation: 0, sx: { border: "1px solid", borderColor: "divider", "&:before": { display: "none" } } }, /* @__PURE__ */ React5.createElement(AccordionSummary, { expandIcon: /* @__PURE__ */ React5.createElement(ExpandMore, null) }, /* @__PURE__ */ React5.createElement(Typography4, { variant: "body2", fontWeight: 600 }, "Advanced")), /* @__PURE__ */ React5.createElement(AccordionDetails, { sx: { display: "flex", flexDirection: "column", gap: 2 } }, /* @__PURE__ */ React5.createElement(
1985
1982
  TextField2,
1986
1983
  {
1987
1984
  label: "TPM Limit",
1988
1985
  type: "number",
1989
1986
  value: formData.tpm_limit ?? "",
1990
- onChange: (e) => setFormData({ ...formData, tpm_limit: e.target.value ? Number(e.target.value) : void 0 }),
1987
+ onChange: (e) => setFormData({ tpm_limit: e.target.value ? Number(e.target.value) : void 0 }),
1991
1988
  helperText: "Max tokens per minute this key can consume across all models. Leave blank for no limit.",
1992
1989
  fullWidth: true
1993
1990
  }
1994
- ), /* @__PURE__ */ React4.createElement(
1991
+ ), /* @__PURE__ */ React5.createElement(
1995
1992
  TextField2,
1996
1993
  {
1997
1994
  label: "RPM Limit",
1998
1995
  type: "number",
1999
1996
  value: formData.rpm_limit ?? "",
2000
- onChange: (e) => setFormData({ ...formData, rpm_limit: e.target.value ? Number(e.target.value) : void 0 }),
1997
+ onChange: (e) => setFormData({ rpm_limit: e.target.value ? Number(e.target.value) : void 0 }),
2001
1998
  helperText: "Max requests per minute this key can make across all models. Leave blank for no limit.",
2002
1999
  fullWidth: true
2003
2000
  }
2004
- ))))), /* @__PURE__ */ React4.createElement(DialogActions2, null, newKeyValue ? /* @__PURE__ */ React4.createElement(Button3, { onClick: handleClose, variant: "contained", color: "success" }, "Done") : /* @__PURE__ */ React4.createElement(React4.Fragment, null, /* @__PURE__ */ React4.createElement(Button3, { onClick: handleClose }, "Cancel"), /* @__PURE__ */ React4.createElement(
2001
+ ))), renderDangerZone())), /* @__PURE__ */ React5.createElement(DialogActions2, null, newKeyValue ? /* @__PURE__ */ React5.createElement(Button3, { onClick: handleClose, variant: "contained", color: "success" }, "Done") : /* @__PURE__ */ React5.createElement(React5.Fragment, null, /* @__PURE__ */ React5.createElement(Button3, { onClick: handleClose }, "Cancel"), /* @__PURE__ */ React5.createElement(
2005
2002
  Button3,
2006
2003
  {
2007
- onClick: handleGenerate,
2004
+ onClick: isCreate ? handleGenerate : handleUpdate,
2008
2005
  variant: "contained",
2009
2006
  color: "primary",
2010
- disabled: !canGenerate
2007
+ disabled: !canSubmit
2011
2008
  },
2012
- submitting ? /* @__PURE__ */ React4.createElement(CircularProgress2, { size: 24 }) : "Generate"
2009
+ submitting ? /* @__PURE__ */ React5.createElement(CircularProgress2, { size: 24 }) : submitLabel
2013
2010
  ))));
2014
2011
  };
2015
2012
  }
2016
2013
  });
2017
2014
 
2018
2015
  // src/components/ManageTeamDialog.tsx
2019
- import React5, { useState as useState3, useEffect as useEffect2, useMemo as useMemo4 } from "react";
2016
+ import React6, { useState as useState3, useEffect as useEffect2, useMemo as useMemo4 } from "react";
2020
2017
  import Dialog3 from "@mui/material/Dialog";
2021
2018
  import DialogTitle3 from "@mui/material/DialogTitle";
2022
2019
  import DialogContent3 from "@mui/material/DialogContent";
@@ -2026,7 +2023,7 @@ import Button4 from "@mui/material/Button";
2026
2023
  import Alert2 from "@mui/material/Alert";
2027
2024
  import Checkbox2 from "@mui/material/Checkbox";
2028
2025
  import FormControlLabel2 from "@mui/material/FormControlLabel";
2029
- import Autocomplete3 from "@mui/material/Autocomplete";
2026
+ import Autocomplete2 from "@mui/material/Autocomplete";
2030
2027
  import CircularProgress3 from "@mui/material/CircularProgress";
2031
2028
  import MenuItem2 from "@mui/material/MenuItem";
2032
2029
  import Box5 from "@mui/material/Box";
@@ -2285,7 +2282,7 @@ var init_ManageTeamDialog = __esm({
2285
2282
  const showMcpServers = mode === "edit" && !!canManageMcpServers;
2286
2283
  const mcpOptions = (mcpServers ?? []).map((s) => s.id);
2287
2284
  const mcpLabel = (id) => (mcpServers ?? []).find((s) => s.id === id)?.name ?? id;
2288
- return /* @__PURE__ */ React5.createElement(Dialog3, { open, onClose, maxWidth: "sm", fullWidth: true }, /* @__PURE__ */ React5.createElement(DialogTitle3, null, mode === "create" ? "Create Team" : "Edit Team"), /* @__PURE__ */ React5.createElement(DialogContent3, { sx: { pt: 2, display: "flex", flexDirection: "column", gap: 2 } }, error && /* @__PURE__ */ React5.createElement(Alert2, { severity: "error" }, error), /* @__PURE__ */ React5.createElement(
2285
+ return /* @__PURE__ */ React6.createElement(Dialog3, { open, onClose, maxWidth: "sm", fullWidth: true }, /* @__PURE__ */ React6.createElement(DialogTitle3, null, mode === "create" ? "Create Team" : "Edit Team"), /* @__PURE__ */ React6.createElement(DialogContent3, { sx: { pt: 2, display: "flex", flexDirection: "column", gap: 2 } }, error && /* @__PURE__ */ React6.createElement(Alert2, { severity: "error" }, error), /* @__PURE__ */ React6.createElement(
2289
2286
  TextField3,
2290
2287
  {
2291
2288
  label: "Team Alias",
@@ -2294,15 +2291,15 @@ var init_ManageTeamDialog = __esm({
2294
2291
  disabled: submitting,
2295
2292
  fullWidth: true
2296
2293
  }
2297
- ), /* @__PURE__ */ React5.createElement(
2298
- Autocomplete3,
2294
+ ), /* @__PURE__ */ React6.createElement(
2295
+ Autocomplete2,
2299
2296
  {
2300
2297
  multiple: true,
2301
2298
  options: modelNames,
2302
2299
  value: models,
2303
2300
  onChange: (_, newValue) => setModels(newValue),
2304
2301
  disabled: submitting,
2305
- renderInput: (params) => /* @__PURE__ */ React5.createElement(
2302
+ renderInput: (params) => /* @__PURE__ */ React6.createElement(
2306
2303
  TextField3,
2307
2304
  {
2308
2305
  ...params,
@@ -2312,7 +2309,7 @@ var init_ManageTeamDialog = __esm({
2312
2309
  }
2313
2310
  )
2314
2311
  }
2315
- ), /* @__PURE__ */ React5.createElement(
2312
+ ), /* @__PURE__ */ React6.createElement(
2316
2313
  TextField3,
2317
2314
  {
2318
2315
  label: budgetWriteOnly ? "New Max Budget ($)" : "Max Budget ($)",
@@ -2324,13 +2321,13 @@ var init_ManageTeamDialog = __esm({
2324
2321
  helperText: budgetWriteOnly ? "Current budget is hidden \u2014 leave blank to keep it, or enter a new value" : `Maximum: $${maxBudgetCeiling}`,
2325
2322
  fullWidth: true
2326
2323
  }
2327
- ), allowUnlimitedBudget && /* @__PURE__ */ React5.createElement(
2324
+ ), allowUnlimitedBudget && /* @__PURE__ */ React6.createElement(
2328
2325
  FormControlLabel2,
2329
2326
  {
2330
- control: /* @__PURE__ */ React5.createElement(Checkbox2, { checked: unlimited, onChange: (e) => setUnlimited(e.target.checked), disabled: submitting }),
2327
+ control: /* @__PURE__ */ React6.createElement(Checkbox2, { checked: unlimited, onChange: (e) => setUnlimited(e.target.checked), disabled: submitting }),
2331
2328
  label: "Unlimited Budget"
2332
2329
  }
2333
- ), /* @__PURE__ */ React5.createElement(
2330
+ ), /* @__PURE__ */ React6.createElement(
2334
2331
  TextField3,
2335
2332
  {
2336
2333
  select: true,
@@ -2341,8 +2338,8 @@ var init_ManageTeamDialog = __esm({
2341
2338
  helperText: "Spend-reset period for the team budget",
2342
2339
  fullWidth: true
2343
2340
  },
2344
- durationOptions.map((o) => /* @__PURE__ */ React5.createElement(MenuItem2, { key: o.value, value: o.value }, o.label))
2345
- ), showMembers && /* @__PURE__ */ React5.createElement(Box5, null, /* @__PURE__ */ React5.createElement(Divider2, { sx: { my: 1 } }), /* @__PURE__ */ React5.createElement(Typography5, { variant: "subtitle2", sx: { mb: 1 } }, "Members"), memberError && /* @__PURE__ */ React5.createElement(Alert2, { severity: "error", sx: { mb: 1 }, onClose: () => setMemberError(null) }, memberError), members.length === 0 ? /* @__PURE__ */ React5.createElement(Typography5, { variant: "body2", color: "text.secondary", sx: { mb: 1 } }, "No members yet.") : /* @__PURE__ */ React5.createElement(TableContainer2, { component: Paper3, variant: "outlined", sx: { borderRadius: 1.5, mb: 1.5 } }, /* @__PURE__ */ React5.createElement(Table2, { size: "small", sx: dataTableSx }, /* @__PURE__ */ React5.createElement(TableHead2, null, /* @__PURE__ */ React5.createElement(TableRow2, null, /* @__PURE__ */ React5.createElement(TableCell2, null, "User"), /* @__PURE__ */ React5.createElement(TableCell2, { align: "right" }, "Role"), /* @__PURE__ */ React5.createElement(TableCell2, { align: "right", sx: { width: 40 } }))), /* @__PURE__ */ React5.createElement(TableBody2, null, members.map((m) => /* @__PURE__ */ React5.createElement(TableRow2, { key: m.user_id }, /* @__PURE__ */ React5.createElement(TableCell2, null, m.user_email ? /* @__PURE__ */ React5.createElement(React5.Fragment, null, /* @__PURE__ */ React5.createElement(Typography5, { variant: "body2" }, m.user_email), /* @__PURE__ */ React5.createElement(
2341
+ durationOptions.map((o) => /* @__PURE__ */ React6.createElement(MenuItem2, { key: o.value, value: o.value }, o.label))
2342
+ ), showMembers && /* @__PURE__ */ React6.createElement(Box5, null, /* @__PURE__ */ React6.createElement(Divider2, { sx: { my: 1 } }), /* @__PURE__ */ React6.createElement(Typography5, { variant: "subtitle2", sx: { mb: 1 } }, "Members"), memberError && /* @__PURE__ */ React6.createElement(Alert2, { severity: "error", sx: { mb: 1 }, onClose: () => setMemberError(null) }, memberError), members.length === 0 ? /* @__PURE__ */ React6.createElement(Typography5, { variant: "body2", color: "text.secondary", sx: { mb: 1 } }, "No members yet.") : /* @__PURE__ */ React6.createElement(TableContainer2, { component: Paper3, variant: "outlined", sx: { borderRadius: 1.5, mb: 1.5 } }, /* @__PURE__ */ React6.createElement(Table2, { size: "small", sx: dataTableSx }, /* @__PURE__ */ React6.createElement(TableHead2, null, /* @__PURE__ */ React6.createElement(TableRow2, null, /* @__PURE__ */ React6.createElement(TableCell2, null, "User"), /* @__PURE__ */ React6.createElement(TableCell2, { align: "right" }, "Role"), /* @__PURE__ */ React6.createElement(TableCell2, { align: "right", sx: { width: 40 } }))), /* @__PURE__ */ React6.createElement(TableBody2, null, members.map((m) => /* @__PURE__ */ React6.createElement(TableRow2, { key: m.user_id }, /* @__PURE__ */ React6.createElement(TableCell2, null, m.user_email ? /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(Typography5, { variant: "body2" }, m.user_email), /* @__PURE__ */ React6.createElement(
2346
2343
  Typography5,
2347
2344
  {
2348
2345
  variant: "caption",
@@ -2350,14 +2347,14 @@ var init_ManageTeamDialog = __esm({
2350
2347
  sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 11 }
2351
2348
  },
2352
2349
  m.user_id
2353
- )) : /* @__PURE__ */ React5.createElement(
2350
+ )) : /* @__PURE__ */ React6.createElement(
2354
2351
  Typography5,
2355
2352
  {
2356
2353
  variant: "body2",
2357
2354
  sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace" }
2358
2355
  },
2359
2356
  m.user_id
2360
- )), /* @__PURE__ */ React5.createElement(TableCell2, { align: "right" }, /* @__PURE__ */ React5.createElement(StatusPill, { label: m.role, tone: m.role === "admin" ? "accent" : "neutral", dot: false })), /* @__PURE__ */ React5.createElement(TableCell2, { align: "right" }, /* @__PURE__ */ React5.createElement(
2357
+ )), /* @__PURE__ */ React6.createElement(TableCell2, { align: "right" }, /* @__PURE__ */ React6.createElement(StatusPill, { label: m.role, tone: m.role === "admin" ? "accent" : "neutral", dot: false })), /* @__PURE__ */ React6.createElement(TableCell2, { align: "right" }, /* @__PURE__ */ React6.createElement(
2361
2358
  IconButton3,
2362
2359
  {
2363
2360
  edge: "end",
@@ -2366,9 +2363,9 @@ var init_ManageTeamDialog = __esm({
2366
2363
  disabled: memberBusy,
2367
2364
  onClick: () => handleRemoveMember(m.user_id)
2368
2365
  },
2369
- /* @__PURE__ */ React5.createElement(DeleteIcon, { fontSize: "small" })
2370
- ))))))), /* @__PURE__ */ React5.createElement(Box5, { sx: { display: "flex", gap: 1, alignItems: "flex-start", flexWrap: "wrap" } }, /* @__PURE__ */ React5.createElement(
2371
- Autocomplete3,
2366
+ /* @__PURE__ */ React6.createElement(DeleteIcon, { fontSize: "small" })
2367
+ ))))))), /* @__PURE__ */ React6.createElement(Box5, { sx: { display: "flex", gap: 1, alignItems: "flex-start", flexWrap: "wrap" } }, /* @__PURE__ */ React6.createElement(
2368
+ Autocomplete2,
2372
2369
  {
2373
2370
  freeSolo: true,
2374
2371
  autoHighlight: true,
@@ -2391,7 +2388,7 @@ var init_ManageTeamDialog = __esm({
2391
2388
  isOptionEqualToValue: (o, v) => o.ref === (typeof v === "string" ? v : v.ref),
2392
2389
  disabled: memberBusy,
2393
2390
  sx: { flex: 1, minWidth: 260 },
2394
- renderInput: (params) => /* @__PURE__ */ React5.createElement(
2391
+ renderInput: (params) => /* @__PURE__ */ React6.createElement(
2395
2392
  TextField3,
2396
2393
  {
2397
2394
  ...params,
@@ -2401,12 +2398,12 @@ var init_ManageTeamDialog = __esm({
2401
2398
  helperText: "Pick a catalog user, or paste a user entity ref",
2402
2399
  InputProps: {
2403
2400
  ...params.InputProps,
2404
- endAdornment: /* @__PURE__ */ React5.createElement(React5.Fragment, null, usersLoading ? /* @__PURE__ */ React5.createElement(CircularProgress3, { color: "inherit", size: 14 }) : null, params.InputProps.endAdornment)
2401
+ endAdornment: /* @__PURE__ */ React6.createElement(React6.Fragment, null, usersLoading ? /* @__PURE__ */ React6.createElement(CircularProgress3, { color: "inherit", size: 14 }) : null, params.InputProps.endAdornment)
2405
2402
  }
2406
2403
  }
2407
2404
  )
2408
2405
  }
2409
- ), /* @__PURE__ */ React5.createElement(
2406
+ ), /* @__PURE__ */ React6.createElement(
2410
2407
  TextField3,
2411
2408
  {
2412
2409
  label: "Max budget in team ($)",
@@ -2417,7 +2414,7 @@ var init_ManageTeamDialog = __esm({
2417
2414
  size: "small",
2418
2415
  sx: { width: 160 }
2419
2416
  }
2420
- ), /* @__PURE__ */ React5.createElement(
2417
+ ), /* @__PURE__ */ React6.createElement(
2421
2418
  Button4,
2422
2419
  {
2423
2420
  onClick: handleAddMember,
@@ -2426,8 +2423,8 @@ var init_ManageTeamDialog = __esm({
2426
2423
  sx: { mt: 0.5 }
2427
2424
  },
2428
2425
  "Add"
2429
- ))), showKnowledgeBases && /* @__PURE__ */ React5.createElement(Box5, null, /* @__PURE__ */ React5.createElement(Divider2, { sx: { my: 1 } }), /* @__PURE__ */ React5.createElement(Typography5, { variant: "subtitle2", sx: { mb: 1 } }, "Knowledge Bases"), /* @__PURE__ */ React5.createElement(Typography5, { variant: "body2", color: "text.secondary", sx: { mb: 1 } }, "Attaching a knowledge base exposes its documents to every key in this team."), kbError && /* @__PURE__ */ React5.createElement(Alert2, { severity: "error", sx: { mb: 1 }, onClose: () => setKbError(null) }, kbError), /* @__PURE__ */ React5.createElement(
2430
- Autocomplete3,
2426
+ ))), showKnowledgeBases && /* @__PURE__ */ React6.createElement(Box5, null, /* @__PURE__ */ React6.createElement(Divider2, { sx: { my: 1 } }), /* @__PURE__ */ React6.createElement(Typography5, { variant: "subtitle2", sx: { mb: 1 } }, "Knowledge Bases"), /* @__PURE__ */ React6.createElement(Typography5, { variant: "body2", color: "text.secondary", sx: { mb: 1 } }, "Attaching a knowledge base exposes its documents to every key in this team."), kbError && /* @__PURE__ */ React6.createElement(Alert2, { severity: "error", sx: { mb: 1 }, onClose: () => setKbError(null) }, kbError), /* @__PURE__ */ React6.createElement(
2427
+ Autocomplete2,
2431
2428
  {
2432
2429
  multiple: true,
2433
2430
  options: kbOptions,
@@ -2435,9 +2432,9 @@ var init_ManageTeamDialog = __esm({
2435
2432
  getOptionLabel: kbLabel,
2436
2433
  onChange: (_, v) => setKbIds(v),
2437
2434
  disabled: kbBusy,
2438
- renderInput: (params) => /* @__PURE__ */ React5.createElement(TextField3, { ...params, label: "Attached knowledge bases" })
2435
+ renderInput: (params) => /* @__PURE__ */ React6.createElement(TextField3, { ...params, label: "Attached knowledge bases" })
2439
2436
  }
2440
- ), /* @__PURE__ */ React5.createElement(Box5, { sx: { display: "flex", justifyContent: "flex-end", mt: 1 } }, /* @__PURE__ */ React5.createElement(
2437
+ ), /* @__PURE__ */ React6.createElement(Box5, { sx: { display: "flex", justifyContent: "flex-end", mt: 1 } }, /* @__PURE__ */ React6.createElement(
2441
2438
  Button4,
2442
2439
  {
2443
2440
  onClick: handleSaveKnowledgeBases,
@@ -2445,8 +2442,8 @@ var init_ManageTeamDialog = __esm({
2445
2442
  variant: "outlined"
2446
2443
  },
2447
2444
  kbBusy ? "Saving\u2026" : "Save knowledge bases"
2448
- ))), showMcpServers && /* @__PURE__ */ React5.createElement(Box5, null, /* @__PURE__ */ React5.createElement(Divider2, { sx: { my: 1 } }), /* @__PURE__ */ React5.createElement(Typography5, { variant: "subtitle2", sx: { mb: 1 } }, "MCP Servers"), /* @__PURE__ */ React5.createElement(Typography5, { variant: "body2", color: "text.secondary", sx: { mb: 1 } }, "Attaching an MCP server lets every key in this team invoke that server's tools through the model \u2014 including any side effects those tools have."), mcpError && /* @__PURE__ */ React5.createElement(Alert2, { severity: "error", sx: { mb: 1 }, onClose: () => setMcpError(null) }, mcpError), /* @__PURE__ */ React5.createElement(
2449
- Autocomplete3,
2445
+ ))), showMcpServers && /* @__PURE__ */ React6.createElement(Box5, null, /* @__PURE__ */ React6.createElement(Divider2, { sx: { my: 1 } }), /* @__PURE__ */ React6.createElement(Typography5, { variant: "subtitle2", sx: { mb: 1 } }, "MCP Servers"), /* @__PURE__ */ React6.createElement(Typography5, { variant: "body2", color: "text.secondary", sx: { mb: 1 } }, "Attaching an MCP server lets every key in this team invoke that server's tools through the model \u2014 including any side effects those tools have."), mcpError && /* @__PURE__ */ React6.createElement(Alert2, { severity: "error", sx: { mb: 1 }, onClose: () => setMcpError(null) }, mcpError), /* @__PURE__ */ React6.createElement(
2446
+ Autocomplete2,
2450
2447
  {
2451
2448
  multiple: true,
2452
2449
  options: mcpOptions,
@@ -2454,9 +2451,9 @@ var init_ManageTeamDialog = __esm({
2454
2451
  getOptionLabel: mcpLabel,
2455
2452
  onChange: (_, v) => setMcpIds(v),
2456
2453
  disabled: mcpBusy,
2457
- renderInput: (params) => /* @__PURE__ */ React5.createElement(TextField3, { ...params, label: "Attached MCP servers" })
2454
+ renderInput: (params) => /* @__PURE__ */ React6.createElement(TextField3, { ...params, label: "Attached MCP servers" })
2458
2455
  }
2459
- ), /* @__PURE__ */ React5.createElement(Box5, { sx: { display: "flex", justifyContent: "flex-end", mt: 1 } }, /* @__PURE__ */ React5.createElement(
2456
+ ), /* @__PURE__ */ React6.createElement(Box5, { sx: { display: "flex", justifyContent: "flex-end", mt: 1 } }, /* @__PURE__ */ React6.createElement(
2460
2457
  Button4,
2461
2458
  {
2462
2459
  onClick: handleSaveMcpServers,
@@ -2464,13 +2461,13 @@ var init_ManageTeamDialog = __esm({
2464
2461
  variant: "outlined"
2465
2462
  },
2466
2463
  mcpBusy ? "Saving\u2026" : "Save MCP servers"
2467
- )))), /* @__PURE__ */ React5.createElement(DialogActions3, null, /* @__PURE__ */ React5.createElement(Button4, { onClick: onClose, disabled: submitting }, "Cancel"), /* @__PURE__ */ React5.createElement(Button4, { onClick: handleSubmit, disabled: submitting, variant: "contained" }, submitting ? "Saving..." : "Save")));
2464
+ )))), /* @__PURE__ */ React6.createElement(DialogActions3, null, /* @__PURE__ */ React6.createElement(Button4, { onClick: onClose, disabled: submitting }, "Cancel"), /* @__PURE__ */ React6.createElement(Button4, { onClick: handleSubmit, disabled: submitting, variant: "contained" }, submitting ? "Saving..." : "Save")));
2468
2465
  };
2469
2466
  }
2470
2467
  });
2471
2468
 
2472
2469
  // src/components/UsageStats.tsx
2473
- import React6, { useMemo as useMemo5, useState as useState4 } from "react";
2470
+ import React7, { useMemo as useMemo5, useState as useState4 } from "react";
2474
2471
  import Paper4 from "@mui/material/Paper";
2475
2472
  import Box6 from "@mui/material/Box";
2476
2473
  import Typography6 from "@mui/material/Typography";
@@ -2520,16 +2517,16 @@ var init_UsageStats = __esm({
2520
2517
  "30d": "Last 30 days"
2521
2518
  };
2522
2519
  fmtPct = (n) => `${(n * 100).toFixed(1)}%`;
2523
- ChartSkeleton = ({ height = 240 }) => /* @__PURE__ */ React6.createElement(Skeleton3, { variant: "rounded", height });
2520
+ ChartSkeleton = ({ height = 240 }) => /* @__PURE__ */ React7.createElement(Skeleton3, { variant: "rounded", height });
2524
2521
  ChartOrFallback = ({ loading, empty, height = 240, children }) => {
2525
- if (loading) return /* @__PURE__ */ React6.createElement(ChartSkeleton, { height });
2526
- if (empty) return /* @__PURE__ */ React6.createElement(EmptyState, { message: "No data for this period", height });
2527
- return /* @__PURE__ */ React6.createElement(React6.Fragment, null, children);
2522
+ if (loading) return /* @__PURE__ */ React7.createElement(ChartSkeleton, { height });
2523
+ if (empty) return /* @__PURE__ */ React7.createElement(EmptyState, { message: "No data for this period", height });
2524
+ return /* @__PURE__ */ React7.createElement(React7.Fragment, null, children);
2528
2525
  };
2529
2526
  SuccessRateCell = ({ rate, requests }) => {
2530
- if (requests === 0) return /* @__PURE__ */ React6.createElement(Typography6, { variant: "body2", color: "text.secondary" }, "\u2014");
2527
+ if (requests === 0) return /* @__PURE__ */ React7.createElement(Typography6, { variant: "body2", color: "text.secondary" }, "\u2014");
2531
2528
  const tone = rateTone(rate);
2532
- return /* @__PURE__ */ React6.createElement(Box6, { display: "flex", alignItems: "center", gap: 1 }, /* @__PURE__ */ React6.createElement(Box6, { width: 72, flexShrink: 0 }, /* @__PURE__ */ React6.createElement(Meter, { value: rate * 100, tone, height: 4 })), /* @__PURE__ */ React6.createElement(
2529
+ return /* @__PURE__ */ React7.createElement(Box6, { display: "flex", alignItems: "center", gap: 1 }, /* @__PURE__ */ React7.createElement(Box6, { width: 72, flexShrink: 0 }, /* @__PURE__ */ React7.createElement(Meter, { value: rate * 100, tone, height: 4 })), /* @__PURE__ */ React7.createElement(
2533
2530
  Typography6,
2534
2531
  {
2535
2532
  variant: "caption",
@@ -2678,23 +2675,23 @@ var init_UsageStats = __esm({
2678
2675
  ];
2679
2676
  const renderKeyRows = () => {
2680
2677
  if (loading) {
2681
- return /* @__PURE__ */ React6.createElement(TableRow3, null, /* @__PURE__ */ React6.createElement(TableCell3, { colSpan: 7, sx: { py: 3 } }, /* @__PURE__ */ React6.createElement(Skeleton3, { variant: "rounded", height: 80 })));
2678
+ return /* @__PURE__ */ React7.createElement(TableRow3, null, /* @__PURE__ */ React7.createElement(TableCell3, { colSpan: 7, sx: { py: 3 } }, /* @__PURE__ */ React7.createElement(Skeleton3, { variant: "rounded", height: 80 })));
2682
2679
  }
2683
2680
  if (keyRows.length === 0) {
2684
- return /* @__PURE__ */ React6.createElement(TableRow3, null, /* @__PURE__ */ React6.createElement(TableCell3, { colSpan: 7 }, /* @__PURE__ */ React6.createElement(EmptyState, { message: "No key activity in this period" })));
2681
+ return /* @__PURE__ */ React7.createElement(TableRow3, null, /* @__PURE__ */ React7.createElement(TableCell3, { colSpan: 7 }, /* @__PURE__ */ React7.createElement(EmptyState, { message: "No key activity in this period" })));
2685
2682
  }
2686
- return [...keyRows].sort((a, b) => b.spend - a.spend || b.apiRequests - a.apiRequests).map((r) => /* @__PURE__ */ React6.createElement(TableRow3, { key: r.keyHash }, /* @__PURE__ */ React6.createElement(TableCell3, null, /* @__PURE__ */ React6.createElement(Typography6, { variant: "body2", sx: { fontWeight: 600 } }, r.keyAlias), r.teamId ? /* @__PURE__ */ React6.createElement(Typography6, { variant: "caption", color: "text.secondary" }, "team: ", r.teamId) : null), /* @__PURE__ */ React6.createElement(TableCell3, null, /* @__PURE__ */ React6.createElement(Box6, { display: "flex", gap: 0.5, flexWrap: "wrap" }, r.models.slice(0, 3).map((m) => /* @__PURE__ */ React6.createElement(TagChip, { key: m, label: m, title: m })), r.models.length > 3 && /* @__PURE__ */ React6.createElement(TagChip, { label: `+${r.models.length - 3}`, mono: false }))), /* @__PURE__ */ React6.createElement(TableCell3, { align: "right" }, fmtUsd(r.spend)), /* @__PURE__ */ React6.createElement(TableCell3, { align: "right" }, fmtInt(r.apiRequests)), /* @__PURE__ */ React6.createElement(TableCell3, { align: "right" }, fmtCompact(r.totalTokens)), /* @__PURE__ */ React6.createElement(TableCell3, { align: "right" }, fmtInt(r.failedRequests)), /* @__PURE__ */ React6.createElement(TableCell3, null, /* @__PURE__ */ React6.createElement(SuccessRateCell, { rate: r.successRate, requests: r.apiRequests }))));
2683
+ return [...keyRows].sort((a, b) => b.spend - a.spend || b.apiRequests - a.apiRequests).map((r) => /* @__PURE__ */ React7.createElement(TableRow3, { key: r.keyHash }, /* @__PURE__ */ React7.createElement(TableCell3, null, /* @__PURE__ */ React7.createElement(Typography6, { variant: "body2", sx: { fontWeight: 600 } }, r.keyAlias), r.teamId ? /* @__PURE__ */ React7.createElement(Typography6, { variant: "caption", color: "text.secondary" }, "team: ", r.teamId) : null), /* @__PURE__ */ React7.createElement(TableCell3, null, /* @__PURE__ */ React7.createElement(Box6, { display: "flex", gap: 0.5, flexWrap: "wrap" }, r.models.slice(0, 3).map((m) => /* @__PURE__ */ React7.createElement(TagChip, { key: m, label: m, title: m })), r.models.length > 3 && /* @__PURE__ */ React7.createElement(TagChip, { label: `+${r.models.length - 3}`, mono: false }))), /* @__PURE__ */ React7.createElement(TableCell3, { align: "right" }, fmtUsd(r.spend)), /* @__PURE__ */ React7.createElement(TableCell3, { align: "right" }, fmtInt(r.apiRequests)), /* @__PURE__ */ React7.createElement(TableCell3, { align: "right" }, fmtCompact(r.totalTokens)), /* @__PURE__ */ React7.createElement(TableCell3, { align: "right" }, fmtInt(r.failedRequests)), /* @__PURE__ */ React7.createElement(TableCell3, null, /* @__PURE__ */ React7.createElement(SuccessRateCell, { rate: r.successRate, requests: r.apiRequests }))));
2687
2684
  };
2688
2685
  const renderModelRows = () => {
2689
2686
  if (loading) {
2690
- return /* @__PURE__ */ React6.createElement(TableRow3, null, /* @__PURE__ */ React6.createElement(TableCell3, { colSpan: 8, sx: { py: 3 } }, /* @__PURE__ */ React6.createElement(Skeleton3, { variant: "rounded", height: 80 })));
2687
+ return /* @__PURE__ */ React7.createElement(TableRow3, null, /* @__PURE__ */ React7.createElement(TableCell3, { colSpan: 8, sx: { py: 3 } }, /* @__PURE__ */ React7.createElement(Skeleton3, { variant: "rounded", height: 80 })));
2691
2688
  }
2692
2689
  if (modelRows.length === 0) {
2693
- return /* @__PURE__ */ React6.createElement(TableRow3, null, /* @__PURE__ */ React6.createElement(TableCell3, { colSpan: 8 }, /* @__PURE__ */ React6.createElement(EmptyState, { message: "No model activity in this period" })));
2690
+ return /* @__PURE__ */ React7.createElement(TableRow3, null, /* @__PURE__ */ React7.createElement(TableCell3, { colSpan: 8 }, /* @__PURE__ */ React7.createElement(EmptyState, { message: "No model activity in this period" })));
2694
2691
  }
2695
- return [...modelRows].sort((a, b) => b.spend - a.spend || b.totalTokens - a.totalTokens).map((r) => /* @__PURE__ */ React6.createElement(TableRow3, { key: r.model }, /* @__PURE__ */ React6.createElement(TableCell3, null, /* @__PURE__ */ React6.createElement(Box6, { display: "flex", alignItems: "center", gap: 1 }, /* @__PURE__ */ React6.createElement(Box6, { sx: { width: 8, height: 8, borderRadius: "50%", bgcolor: chartColor(r.model), flexShrink: 0 } }), /* @__PURE__ */ React6.createElement(Typography6, { variant: "body2", sx: { fontWeight: 600 } }, r.model))), /* @__PURE__ */ React6.createElement(TableCell3, { align: "right" }, fmtUsd(r.spend)), /* @__PURE__ */ React6.createElement(TableCell3, { align: "right" }, fmtInt(r.apiRequests)), /* @__PURE__ */ React6.createElement(TableCell3, { align: "right" }, fmtInt(r.successfulRequests)), /* @__PURE__ */ React6.createElement(TableCell3, { align: "right" }, fmtInt(r.failedRequests)), /* @__PURE__ */ React6.createElement(TableCell3, { align: "right" }, fmtCompact(r.promptTokens)), /* @__PURE__ */ React6.createElement(TableCell3, { align: "right" }, fmtCompact(r.completionTokens)), /* @__PURE__ */ React6.createElement(TableCell3, null, /* @__PURE__ */ React6.createElement(SuccessRateCell, { rate: r.successRate, requests: r.apiRequests }))));
2692
+ return [...modelRows].sort((a, b) => b.spend - a.spend || b.totalTokens - a.totalTokens).map((r) => /* @__PURE__ */ React7.createElement(TableRow3, { key: r.model }, /* @__PURE__ */ React7.createElement(TableCell3, null, /* @__PURE__ */ React7.createElement(Box6, { display: "flex", alignItems: "center", gap: 1 }, /* @__PURE__ */ React7.createElement(Box6, { sx: { width: 8, height: 8, borderRadius: "50%", bgcolor: chartColor(r.model), flexShrink: 0 } }), /* @__PURE__ */ React7.createElement(Typography6, { variant: "body2", sx: { fontWeight: 600 } }, r.model))), /* @__PURE__ */ React7.createElement(TableCell3, { align: "right" }, fmtUsd(r.spend)), /* @__PURE__ */ React7.createElement(TableCell3, { align: "right" }, fmtInt(r.apiRequests)), /* @__PURE__ */ React7.createElement(TableCell3, { align: "right" }, fmtInt(r.successfulRequests)), /* @__PURE__ */ React7.createElement(TableCell3, { align: "right" }, fmtInt(r.failedRequests)), /* @__PURE__ */ React7.createElement(TableCell3, { align: "right" }, fmtCompact(r.promptTokens)), /* @__PURE__ */ React7.createElement(TableCell3, { align: "right" }, fmtCompact(r.completionTokens)), /* @__PURE__ */ React7.createElement(TableCell3, null, /* @__PURE__ */ React7.createElement(SuccessRateCell, { rate: r.successRate, requests: r.apiRequests }))));
2696
2693
  };
2697
- const periodSelect = /* @__PURE__ */ React6.createElement(
2694
+ const periodSelect = /* @__PURE__ */ React7.createElement(
2698
2695
  TextField4,
2699
2696
  {
2700
2697
  select: true,
@@ -2704,17 +2701,17 @@ var init_UsageStats = __esm({
2704
2701
  onChange: (e) => handlePresetChange(e.target.value),
2705
2702
  sx: { minWidth: 160 }
2706
2703
  },
2707
- Object.keys(PRESET_LABELS).map((p) => /* @__PURE__ */ React6.createElement(MenuItem3, { key: p, value: p }, PRESET_LABELS[p]))
2704
+ Object.keys(PRESET_LABELS).map((p) => /* @__PURE__ */ React7.createElement(MenuItem3, { key: p, value: p }, PRESET_LABELS[p]))
2708
2705
  );
2709
- return /* @__PURE__ */ React6.createElement(
2706
+ return /* @__PURE__ */ React7.createElement(
2710
2707
  SectionCard,
2711
2708
  {
2712
2709
  title: "Usage Analytics",
2713
2710
  subtitle: `${PRESET_LABELS[currentPreset]} \xB7 ${dateRange.start.toLocaleDateString()} \u2013 ${dateRange.end.toLocaleDateString()}`,
2714
2711
  actions: periodSelect
2715
2712
  },
2716
- /* @__PURE__ */ React6.createElement(MetricStrip, { metrics }),
2717
- /* @__PURE__ */ React6.createElement(
2713
+ /* @__PURE__ */ React7.createElement(MetricStrip, { metrics }),
2714
+ /* @__PURE__ */ React7.createElement(
2718
2715
  Box6,
2719
2716
  {
2720
2717
  sx: {
@@ -2727,7 +2724,7 @@ var init_UsageStats = __esm({
2727
2724
  mb: 2
2728
2725
  }
2729
2726
  },
2730
- /* @__PURE__ */ React6.createElement(
2727
+ /* @__PURE__ */ React7.createElement(
2731
2728
  SegmentedControl,
2732
2729
  {
2733
2730
  value: tab,
@@ -2739,7 +2736,7 @@ var init_UsageStats = __esm({
2739
2736
  ]
2740
2737
  }
2741
2738
  ),
2742
- tab === "models" && /* @__PURE__ */ React6.createElement(
2739
+ tab === "models" && /* @__PURE__ */ React7.createElement(
2743
2740
  TextField4,
2744
2741
  {
2745
2742
  select: true,
@@ -2749,17 +2746,17 @@ var init_UsageStats = __esm({
2749
2746
  onChange: (e) => setSelectedModel(e.target.value),
2750
2747
  sx: { minWidth: 220 }
2751
2748
  },
2752
- /* @__PURE__ */ React6.createElement(MenuItem3, { value: "all" }, "All models"),
2753
- models.map((m) => /* @__PURE__ */ React6.createElement(MenuItem3, { key: m.model_name, value: m.model_name }, m.model_name))
2749
+ /* @__PURE__ */ React7.createElement(MenuItem3, { value: "all" }, "All models"),
2750
+ models.map((m) => /* @__PURE__ */ React7.createElement(MenuItem3, { key: m.model_name, value: m.model_name }, m.model_name))
2754
2751
  )
2755
2752
  ),
2756
- tab === "costs" && /* @__PURE__ */ React6.createElement(Grid, { container: true, spacing: 2 }, /* @__PURE__ */ React6.createElement(Grid, { item: true, xs: 12 }, /* @__PURE__ */ React6.createElement(ChartCard, { title: "Daily spend by model", height: 260 }, /* @__PURE__ */ React6.createElement(ChartOrFallback, { loading, empty: modelSpendByDate.length === 0, height: 260 }, /* @__PURE__ */ React6.createElement(ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ React6.createElement(AreaChart, { data: modelSpendByDate, margin: { top: 4, right: 8, left: 0, bottom: 0 } }, /* @__PURE__ */ React6.createElement(CartesianGrid, { ...chart.grid }), /* @__PURE__ */ React6.createElement(XAxis, { dataKey: "date", tickFormatter: fmtDateShort, ...chart.axis }), /* @__PURE__ */ React6.createElement(YAxis, { tickFormatter: fmtUsdCompact, width: 56, ...chart.axis }), /* @__PURE__ */ React6.createElement(
2753
+ tab === "costs" && /* @__PURE__ */ React7.createElement(Grid, { container: true, spacing: 2 }, /* @__PURE__ */ React7.createElement(Grid, { item: true, xs: 12 }, /* @__PURE__ */ React7.createElement(ChartCard, { title: "Daily spend by model", height: 260 }, /* @__PURE__ */ React7.createElement(ChartOrFallback, { loading, empty: modelSpendByDate.length === 0, height: 260 }, /* @__PURE__ */ React7.createElement(ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ React7.createElement(AreaChart, { data: modelSpendByDate, margin: { top: 4, right: 8, left: 0, bottom: 0 } }, /* @__PURE__ */ React7.createElement(CartesianGrid, { ...chart.grid }), /* @__PURE__ */ React7.createElement(XAxis, { dataKey: "date", tickFormatter: fmtDateShort, ...chart.axis }), /* @__PURE__ */ React7.createElement(YAxis, { tickFormatter: fmtUsdCompact, width: 56, ...chart.axis }), /* @__PURE__ */ React7.createElement(
2757
2754
  Tooltip,
2758
2755
  {
2759
2756
  cursor: chart.cursor,
2760
- content: /* @__PURE__ */ React6.createElement(ChartTooltip, { valueFormatter: fmtUsd, hideZero: true })
2757
+ content: /* @__PURE__ */ React7.createElement(ChartTooltip, { valueFormatter: fmtUsd, hideZero: true })
2761
2758
  }
2762
- ), topSpendModels.map((m) => /* @__PURE__ */ React6.createElement(
2759
+ ), topSpendModels.map((m) => /* @__PURE__ */ React7.createElement(
2763
2760
  Area,
2764
2761
  {
2765
2762
  key: m,
@@ -2771,13 +2768,13 @@ var init_UsageStats = __esm({
2771
2768
  fill: chartColor(m),
2772
2769
  fillOpacity: 0.28
2773
2770
  }
2774
- ))))), !loading && topSpendModels.length > 0 && /* @__PURE__ */ React6.createElement(SeriesLegend, { series: topSpendModels.map((m) => ({ name: m, color: chartColor(m) })) }))), /* @__PURE__ */ React6.createElement(Grid, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ React6.createElement(ChartCard, { title: "Daily token usage" }, /* @__PURE__ */ React6.createElement(ChartOrFallback, { loading, empty: dailyData.length === 0 }, /* @__PURE__ */ React6.createElement(ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ React6.createElement(AreaChart, { data: dailyData, margin: { top: 4, right: 8, left: 0, bottom: 0 } }, /* @__PURE__ */ React6.createElement(CartesianGrid, { ...chart.grid }), /* @__PURE__ */ React6.createElement(XAxis, { dataKey: "date", tickFormatter: fmtDateShort, ...chart.axis }), /* @__PURE__ */ React6.createElement(YAxis, { tickFormatter: fmtCompact, width: 48, ...chart.axis }), /* @__PURE__ */ React6.createElement(Tooltip, { cursor: chart.cursor, content: /* @__PURE__ */ React6.createElement(ChartTooltip, { valueFormatter: fmtInt }) }), /* @__PURE__ */ React6.createElement(Legend, { ...chart.legend }), /* @__PURE__ */ React6.createElement(Area, { type: "monotone", dataKey: "promptTokens", name: "Input", stackId: "tok", stroke: SERIES.input, strokeWidth: 1.5, fill: SERIES.input, fillOpacity: 0.25 }), /* @__PURE__ */ React6.createElement(Area, { type: "monotone", dataKey: "completionTokens", name: "Output", stackId: "tok", stroke: SERIES.output, strokeWidth: 1.5, fill: SERIES.output, fillOpacity: 0.25 })))))), /* @__PURE__ */ React6.createElement(Grid, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ React6.createElement(ChartCard, { title: "Daily requests" }, /* @__PURE__ */ React6.createElement(ChartOrFallback, { loading, empty: dailyData.length === 0 }, /* @__PURE__ */ React6.createElement(ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ React6.createElement(BarChart, { data: dailyData, margin: { top: 4, right: 8, left: 0, bottom: 0 }, barCategoryGap: "30%" }, /* @__PURE__ */ React6.createElement(CartesianGrid, { ...chart.grid }), /* @__PURE__ */ React6.createElement(XAxis, { dataKey: "date", tickFormatter: fmtDateShort, ...chart.axis }), /* @__PURE__ */ React6.createElement(YAxis, { tickFormatter: fmtCompact, width: 48, ...chart.axis }), /* @__PURE__ */ React6.createElement(Tooltip, { cursor: chart.cursor, content: /* @__PURE__ */ React6.createElement(ChartTooltip, { valueFormatter: fmtInt }) }), /* @__PURE__ */ React6.createElement(Legend, { ...chart.legend }), /* @__PURE__ */ React6.createElement(Bar, { dataKey: "successfulRequests", name: "Successful", fill: SERIES.success, stackId: "r" }), /* @__PURE__ */ React6.createElement(Bar, { dataKey: "failedRequests", name: "Failed", fill: SERIES.failure, stackId: "r", radius: [3, 3, 0, 0] })))))), /* @__PURE__ */ React6.createElement(Grid, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ React6.createElement(ChartCard, { title: "Daily success rate" }, /* @__PURE__ */ React6.createElement(ChartOrFallback, { loading, empty: successRateData.length === 0 }, /* @__PURE__ */ React6.createElement(ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ React6.createElement(LineChart, { data: successRateData, margin: { top: 4, right: 8, left: 0, bottom: 0 } }, /* @__PURE__ */ React6.createElement(CartesianGrid, { ...chart.grid }), /* @__PURE__ */ React6.createElement(XAxis, { dataKey: "date", tickFormatter: fmtDateShort, ...chart.axis }), /* @__PURE__ */ React6.createElement(YAxis, { domain: [0, 100], tickFormatter: (v) => `${v}%`, width: 44, ...chart.axis }), /* @__PURE__ */ React6.createElement(
2771
+ ))))), !loading && topSpendModels.length > 0 && /* @__PURE__ */ React7.createElement(SeriesLegend, { series: topSpendModels.map((m) => ({ name: m, color: chartColor(m) })) }))), /* @__PURE__ */ React7.createElement(Grid, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ React7.createElement(ChartCard, { title: "Daily token usage" }, /* @__PURE__ */ React7.createElement(ChartOrFallback, { loading, empty: dailyData.length === 0 }, /* @__PURE__ */ React7.createElement(ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ React7.createElement(AreaChart, { data: dailyData, margin: { top: 4, right: 8, left: 0, bottom: 0 } }, /* @__PURE__ */ React7.createElement(CartesianGrid, { ...chart.grid }), /* @__PURE__ */ React7.createElement(XAxis, { dataKey: "date", tickFormatter: fmtDateShort, ...chart.axis }), /* @__PURE__ */ React7.createElement(YAxis, { tickFormatter: fmtCompact, width: 48, ...chart.axis }), /* @__PURE__ */ React7.createElement(Tooltip, { cursor: chart.cursor, content: /* @__PURE__ */ React7.createElement(ChartTooltip, { valueFormatter: fmtInt }) }), /* @__PURE__ */ React7.createElement(Legend, { ...chart.legend }), /* @__PURE__ */ React7.createElement(Area, { type: "monotone", dataKey: "promptTokens", name: "Input", stackId: "tok", stroke: SERIES.input, strokeWidth: 1.5, fill: SERIES.input, fillOpacity: 0.25 }), /* @__PURE__ */ React7.createElement(Area, { type: "monotone", dataKey: "completionTokens", name: "Output", stackId: "tok", stroke: SERIES.output, strokeWidth: 1.5, fill: SERIES.output, fillOpacity: 0.25 })))))), /* @__PURE__ */ React7.createElement(Grid, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ React7.createElement(ChartCard, { title: "Daily requests" }, /* @__PURE__ */ React7.createElement(ChartOrFallback, { loading, empty: dailyData.length === 0 }, /* @__PURE__ */ React7.createElement(ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ React7.createElement(BarChart, { data: dailyData, margin: { top: 4, right: 8, left: 0, bottom: 0 }, barCategoryGap: "30%" }, /* @__PURE__ */ React7.createElement(CartesianGrid, { ...chart.grid }), /* @__PURE__ */ React7.createElement(XAxis, { dataKey: "date", tickFormatter: fmtDateShort, ...chart.axis }), /* @__PURE__ */ React7.createElement(YAxis, { tickFormatter: fmtCompact, width: 48, ...chart.axis }), /* @__PURE__ */ React7.createElement(Tooltip, { cursor: chart.cursor, content: /* @__PURE__ */ React7.createElement(ChartTooltip, { valueFormatter: fmtInt }) }), /* @__PURE__ */ React7.createElement(Legend, { ...chart.legend }), /* @__PURE__ */ React7.createElement(Bar, { dataKey: "successfulRequests", name: "Successful", fill: SERIES.success, stackId: "r" }), /* @__PURE__ */ React7.createElement(Bar, { dataKey: "failedRequests", name: "Failed", fill: SERIES.failure, stackId: "r", radius: [3, 3, 0, 0] })))))), /* @__PURE__ */ React7.createElement(Grid, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ React7.createElement(ChartCard, { title: "Daily success rate" }, /* @__PURE__ */ React7.createElement(ChartOrFallback, { loading, empty: successRateData.length === 0 }, /* @__PURE__ */ React7.createElement(ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ React7.createElement(LineChart, { data: successRateData, margin: { top: 4, right: 8, left: 0, bottom: 0 } }, /* @__PURE__ */ React7.createElement(CartesianGrid, { ...chart.grid }), /* @__PURE__ */ React7.createElement(XAxis, { dataKey: "date", tickFormatter: fmtDateShort, ...chart.axis }), /* @__PURE__ */ React7.createElement(YAxis, { domain: [0, 100], tickFormatter: (v) => `${v}%`, width: 44, ...chart.axis }), /* @__PURE__ */ React7.createElement(
2775
2772
  Tooltip,
2776
2773
  {
2777
2774
  cursor: chart.cursor,
2778
- content: /* @__PURE__ */ React6.createElement(ChartTooltip, { valueFormatter: (v) => `${v}%` })
2775
+ content: /* @__PURE__ */ React7.createElement(ChartTooltip, { valueFormatter: (v) => `${v}%` })
2779
2776
  }
2780
- ), /* @__PURE__ */ React6.createElement(ReferenceLine, { y: 100, stroke: SERIES.success, strokeDasharray: "4 4", strokeOpacity: 0.5 }), /* @__PURE__ */ React6.createElement(
2777
+ ), /* @__PURE__ */ React7.createElement(ReferenceLine, { y: 100, stroke: SERIES.success, strokeDasharray: "4 4", strokeOpacity: 0.5 }), /* @__PURE__ */ React7.createElement(
2781
2778
  Line,
2782
2779
  {
2783
2780
  type: "monotone",
@@ -2788,13 +2785,13 @@ var init_UsageStats = __esm({
2788
2785
  dot: { r: 2.5, strokeWidth: 0, fill: SERIES.input },
2789
2786
  activeDot: { r: 4 }
2790
2787
  }
2791
- )))))), /* @__PURE__ */ React6.createElement(Grid, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ React6.createElement(
2788
+ )))))), /* @__PURE__ */ React7.createElement(Grid, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ React7.createElement(
2792
2789
  ChartCard,
2793
2790
  {
2794
2791
  title: maxBudget > 0 ? "Cumulative spend vs budget" : "Cumulative spend",
2795
2792
  meta: maxBudget > 0 ? `${fmtUsd(totalCumSpend)} used \xB7 ${fmtUsd(Math.max(0, maxBudget - totalCumSpend))} left` : void 0
2796
2793
  },
2797
- /* @__PURE__ */ React6.createElement(ChartOrFallback, { loading, empty: cumulativeData.length === 0 }, /* @__PURE__ */ React6.createElement(ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ React6.createElement(AreaChart, { data: cumulativeData, margin: { top: 4, right: 8, left: 0, bottom: 0 } }, /* @__PURE__ */ React6.createElement(CartesianGrid, { ...chart.grid }), /* @__PURE__ */ React6.createElement(XAxis, { dataKey: "date", tickFormatter: fmtDateShort, ...chart.axis }), /* @__PURE__ */ React6.createElement(
2794
+ /* @__PURE__ */ React7.createElement(ChartOrFallback, { loading, empty: cumulativeData.length === 0 }, /* @__PURE__ */ React7.createElement(ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ React7.createElement(AreaChart, { data: cumulativeData, margin: { top: 4, right: 8, left: 0, bottom: 0 } }, /* @__PURE__ */ React7.createElement(CartesianGrid, { ...chart.grid }), /* @__PURE__ */ React7.createElement(XAxis, { dataKey: "date", tickFormatter: fmtDateShort, ...chart.axis }), /* @__PURE__ */ React7.createElement(
2798
2795
  YAxis,
2799
2796
  {
2800
2797
  tickFormatter: fmtUsdCompact,
@@ -2802,13 +2799,13 @@ var init_UsageStats = __esm({
2802
2799
  domain: cumulativeDomain,
2803
2800
  ...chart.axis
2804
2801
  }
2805
- ), /* @__PURE__ */ React6.createElement(
2802
+ ), /* @__PURE__ */ React7.createElement(
2806
2803
  Tooltip,
2807
2804
  {
2808
2805
  cursor: chart.cursor,
2809
- content: /* @__PURE__ */ React6.createElement(ChartTooltip, { valueFormatter: fmtUsd })
2806
+ content: /* @__PURE__ */ React7.createElement(ChartTooltip, { valueFormatter: fmtUsd })
2810
2807
  }
2811
- ), maxBudget > 0 && /* @__PURE__ */ React6.createElement(
2808
+ ), maxBudget > 0 && /* @__PURE__ */ React7.createElement(
2812
2809
  ReferenceLine,
2813
2810
  {
2814
2811
  y: maxBudget,
@@ -2816,7 +2813,7 @@ var init_UsageStats = __esm({
2816
2813
  strokeDasharray: "5 4",
2817
2814
  label: { value: `Budget ${fmtUsd(maxBudget)}`, position: "insideTopRight", fontSize: 10, fill: SERIES.budget }
2818
2815
  }
2819
- ), /* @__PURE__ */ React6.createElement(
2816
+ ), /* @__PURE__ */ React7.createElement(
2820
2817
  Area,
2821
2818
  {
2822
2819
  type: "monotone",
@@ -2829,32 +2826,32 @@ var init_UsageStats = __esm({
2829
2826
  }
2830
2827
  ))))
2831
2828
  ))),
2832
- tab === "models" && /* @__PURE__ */ React6.createElement(Grid, { container: true, spacing: 2 }, /* @__PURE__ */ React6.createElement(Grid, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ React6.createElement(ChartCard, { title: "Spend by model", height: Math.max(200, topModelSpendBars.length * 34) }, /* @__PURE__ */ React6.createElement(
2829
+ tab === "models" && /* @__PURE__ */ React7.createElement(Grid, { container: true, spacing: 2 }, /* @__PURE__ */ React7.createElement(Grid, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ React7.createElement(ChartCard, { title: "Spend by model", height: Math.max(200, topModelSpendBars.length * 34) }, /* @__PURE__ */ React7.createElement(
2833
2830
  ChartOrFallback,
2834
2831
  {
2835
2832
  loading,
2836
2833
  empty: topModelSpendBars.length === 0,
2837
2834
  height: Math.max(200, topModelSpendBars.length * 34)
2838
2835
  },
2839
- /* @__PURE__ */ React6.createElement(ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ React6.createElement(BarChart, { data: topModelSpendBars, layout: "vertical", margin: { top: 0, right: 12, left: 0, bottom: 0 } }, /* @__PURE__ */ React6.createElement(CartesianGrid, { ...chart.grid, vertical: true, horizontal: false }), /* @__PURE__ */ React6.createElement(XAxis, { type: "number", tickFormatter: fmtUsdCompact, ...chart.axis }), /* @__PURE__ */ React6.createElement(YAxis, { type: "category", dataKey: "model", width: 170, ...chart.axis, tick: { fontSize: 10.5, fill: chart.axis.tick.fill } }), /* @__PURE__ */ React6.createElement(Tooltip, { cursor: chart.cursor, content: /* @__PURE__ */ React6.createElement(ChartTooltip, { valueFormatter: fmtUsd, labelFormatter: (l) => l }) }), /* @__PURE__ */ React6.createElement(Bar, { dataKey: "spend", name: "Spend", radius: [0, 3, 3, 0], barSize: 14 }, topModelSpendBars.map((r) => /* @__PURE__ */ React6.createElement(Cell, { key: r.model, fill: chartColor(r.model) })))))
2840
- ))), /* @__PURE__ */ React6.createElement(Grid, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ React6.createElement(ChartCard, { title: "Tokens by model", height: Math.max(200, topModelSpendBars.length * 34) }, /* @__PURE__ */ React6.createElement(
2836
+ /* @__PURE__ */ React7.createElement(ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ React7.createElement(BarChart, { data: topModelSpendBars, layout: "vertical", margin: { top: 0, right: 12, left: 0, bottom: 0 } }, /* @__PURE__ */ React7.createElement(CartesianGrid, { ...chart.grid, vertical: true, horizontal: false }), /* @__PURE__ */ React7.createElement(XAxis, { type: "number", tickFormatter: fmtUsdCompact, ...chart.axis }), /* @__PURE__ */ React7.createElement(YAxis, { type: "category", dataKey: "model", width: 170, ...chart.axis, tick: { fontSize: 10.5, fill: chart.axis.tick.fill } }), /* @__PURE__ */ React7.createElement(Tooltip, { cursor: chart.cursor, content: /* @__PURE__ */ React7.createElement(ChartTooltip, { valueFormatter: fmtUsd, labelFormatter: (l) => l }) }), /* @__PURE__ */ React7.createElement(Bar, { dataKey: "spend", name: "Spend", radius: [0, 3, 3, 0], barSize: 14 }, topModelSpendBars.map((r) => /* @__PURE__ */ React7.createElement(Cell, { key: r.model, fill: chartColor(r.model) })))))
2837
+ ))), /* @__PURE__ */ React7.createElement(Grid, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ React7.createElement(ChartCard, { title: "Tokens by model", height: Math.max(200, topModelSpendBars.length * 34) }, /* @__PURE__ */ React7.createElement(
2841
2838
  ChartOrFallback,
2842
2839
  {
2843
2840
  loading,
2844
2841
  empty: modelRows.length === 0,
2845
2842
  height: Math.max(200, topModelSpendBars.length * 34)
2846
2843
  },
2847
- /* @__PURE__ */ React6.createElement(ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ React6.createElement(BarChart, { data: topModelSpendBars, layout: "vertical", margin: { top: 0, right: 12, left: 0, bottom: 0 } }, /* @__PURE__ */ React6.createElement(CartesianGrid, { ...chart.grid, vertical: true, horizontal: false }), /* @__PURE__ */ React6.createElement(XAxis, { type: "number", tickFormatter: fmtCompact, ...chart.axis }), /* @__PURE__ */ React6.createElement(YAxis, { type: "category", dataKey: "model", width: 170, ...chart.axis, tick: { fontSize: 10.5, fill: chart.axis.tick.fill } }), /* @__PURE__ */ React6.createElement(Tooltip, { cursor: chart.cursor, content: /* @__PURE__ */ React6.createElement(ChartTooltip, { valueFormatter: fmtInt, labelFormatter: (l) => l }) }), /* @__PURE__ */ React6.createElement(Legend, { ...chart.legend }), /* @__PURE__ */ React6.createElement(Bar, { dataKey: "promptTokens", name: "Input", fill: SERIES.input, stackId: "t", barSize: 14 }), /* @__PURE__ */ React6.createElement(Bar, { dataKey: "completionTokens", name: "Output", fill: SERIES.output, stackId: "t", barSize: 14, radius: [0, 3, 3, 0] })))
2848
- ))), /* @__PURE__ */ React6.createElement(Grid, { item: true, xs: 12 }, /* @__PURE__ */ React6.createElement(TableContainer3, { component: Paper4, variant: "outlined", sx: { borderRadius: 2 } }, /* @__PURE__ */ React6.createElement(Table3, { size: "small", sx: dataTableSx }, /* @__PURE__ */ React6.createElement(TableHead3, null, /* @__PURE__ */ React6.createElement(TableRow3, null, /* @__PURE__ */ React6.createElement(TableCell3, null, "Model"), /* @__PURE__ */ React6.createElement(TableCell3, { align: "right" }, "Spend"), /* @__PURE__ */ React6.createElement(TableCell3, { align: "right" }, "Requests"), /* @__PURE__ */ React6.createElement(TableCell3, { align: "right" }, "Success"), /* @__PURE__ */ React6.createElement(TableCell3, { align: "right" }, "Failed"), /* @__PURE__ */ React6.createElement(TableCell3, { align: "right" }, "Input"), /* @__PURE__ */ React6.createElement(TableCell3, { align: "right" }, "Output"), /* @__PURE__ */ React6.createElement(TableCell3, { sx: { width: 160 } }, "Success rate"))), /* @__PURE__ */ React6.createElement(TableBody3, null, renderModelRows()))))),
2849
- tab === "keys" && /* @__PURE__ */ React6.createElement(Grid, { container: true, spacing: 2 }, (loading || topKeySpendBars.length > 0) && /* @__PURE__ */ React6.createElement(Grid, { item: true, xs: 12 }, /* @__PURE__ */ React6.createElement(ChartCard, { title: "Spend by key", height: Math.max(160, topKeySpendBars.length * 32) }, /* @__PURE__ */ React6.createElement(
2844
+ /* @__PURE__ */ React7.createElement(ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ React7.createElement(BarChart, { data: topModelSpendBars, layout: "vertical", margin: { top: 0, right: 12, left: 0, bottom: 0 } }, /* @__PURE__ */ React7.createElement(CartesianGrid, { ...chart.grid, vertical: true, horizontal: false }), /* @__PURE__ */ React7.createElement(XAxis, { type: "number", tickFormatter: fmtCompact, ...chart.axis }), /* @__PURE__ */ React7.createElement(YAxis, { type: "category", dataKey: "model", width: 170, ...chart.axis, tick: { fontSize: 10.5, fill: chart.axis.tick.fill } }), /* @__PURE__ */ React7.createElement(Tooltip, { cursor: chart.cursor, content: /* @__PURE__ */ React7.createElement(ChartTooltip, { valueFormatter: fmtInt, labelFormatter: (l) => l }) }), /* @__PURE__ */ React7.createElement(Legend, { ...chart.legend }), /* @__PURE__ */ React7.createElement(Bar, { dataKey: "promptTokens", name: "Input", fill: SERIES.input, stackId: "t", barSize: 14 }), /* @__PURE__ */ React7.createElement(Bar, { dataKey: "completionTokens", name: "Output", fill: SERIES.output, stackId: "t", barSize: 14, radius: [0, 3, 3, 0] })))
2845
+ ))), /* @__PURE__ */ React7.createElement(Grid, { item: true, xs: 12 }, /* @__PURE__ */ React7.createElement(TableContainer3, { component: Paper4, variant: "outlined", sx: { borderRadius: 2 } }, /* @__PURE__ */ React7.createElement(Table3, { size: "small", sx: dataTableSx }, /* @__PURE__ */ React7.createElement(TableHead3, null, /* @__PURE__ */ React7.createElement(TableRow3, null, /* @__PURE__ */ React7.createElement(TableCell3, null, "Model"), /* @__PURE__ */ React7.createElement(TableCell3, { align: "right" }, "Spend"), /* @__PURE__ */ React7.createElement(TableCell3, { align: "right" }, "Requests"), /* @__PURE__ */ React7.createElement(TableCell3, { align: "right" }, "Success"), /* @__PURE__ */ React7.createElement(TableCell3, { align: "right" }, "Failed"), /* @__PURE__ */ React7.createElement(TableCell3, { align: "right" }, "Input"), /* @__PURE__ */ React7.createElement(TableCell3, { align: "right" }, "Output"), /* @__PURE__ */ React7.createElement(TableCell3, { sx: { width: 160 } }, "Success rate"))), /* @__PURE__ */ React7.createElement(TableBody3, null, renderModelRows()))))),
2846
+ tab === "keys" && /* @__PURE__ */ React7.createElement(Grid, { container: true, spacing: 2 }, (loading || topKeySpendBars.length > 0) && /* @__PURE__ */ React7.createElement(Grid, { item: true, xs: 12 }, /* @__PURE__ */ React7.createElement(ChartCard, { title: "Spend by key", height: Math.max(160, topKeySpendBars.length * 32) }, /* @__PURE__ */ React7.createElement(
2850
2847
  ChartOrFallback,
2851
2848
  {
2852
2849
  loading,
2853
2850
  empty: topKeySpendBars.length === 0,
2854
2851
  height: Math.max(160, topKeySpendBars.length * 32)
2855
2852
  },
2856
- /* @__PURE__ */ React6.createElement(ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ React6.createElement(BarChart, { data: topKeySpendBars, layout: "vertical", margin: { top: 0, right: 12, left: 0, bottom: 0 } }, /* @__PURE__ */ React6.createElement(CartesianGrid, { ...chart.grid, vertical: true, horizontal: false }), /* @__PURE__ */ React6.createElement(XAxis, { type: "number", tickFormatter: fmtUsdCompact, ...chart.axis }), /* @__PURE__ */ React6.createElement(YAxis, { type: "category", dataKey: "keyAlias", width: 160, ...chart.axis, tick: { fontSize: 10.5, fill: chart.axis.tick.fill } }), /* @__PURE__ */ React6.createElement(Tooltip, { cursor: chart.cursor, content: /* @__PURE__ */ React6.createElement(ChartTooltip, { valueFormatter: fmtUsd, labelFormatter: (l) => l }) }), /* @__PURE__ */ React6.createElement(Bar, { dataKey: "spend", name: "Spend", fill: SERIES.input, radius: [0, 3, 3, 0], barSize: 14 })))
2857
- ))), /* @__PURE__ */ React6.createElement(Grid, { item: true, xs: 12 }, /* @__PURE__ */ React6.createElement(TableContainer3, { component: Paper4, variant: "outlined", sx: { borderRadius: 2 } }, /* @__PURE__ */ React6.createElement(Table3, { size: "small", sx: dataTableSx }, /* @__PURE__ */ React6.createElement(TableHead3, null, /* @__PURE__ */ React6.createElement(TableRow3, null, /* @__PURE__ */ React6.createElement(TableCell3, null, "Key"), /* @__PURE__ */ React6.createElement(TableCell3, null, "Models"), /* @__PURE__ */ React6.createElement(TableCell3, { align: "right" }, "Spend"), /* @__PURE__ */ React6.createElement(TableCell3, { align: "right" }, "Requests"), /* @__PURE__ */ React6.createElement(TableCell3, { align: "right" }, "Tokens"), /* @__PURE__ */ React6.createElement(TableCell3, { align: "right" }, "Failed"), /* @__PURE__ */ React6.createElement(TableCell3, { sx: { width: 160 } }, "Success rate"))), /* @__PURE__ */ React6.createElement(TableBody3, null, renderKeyRows())))))
2853
+ /* @__PURE__ */ React7.createElement(ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ React7.createElement(BarChart, { data: topKeySpendBars, layout: "vertical", margin: { top: 0, right: 12, left: 0, bottom: 0 } }, /* @__PURE__ */ React7.createElement(CartesianGrid, { ...chart.grid, vertical: true, horizontal: false }), /* @__PURE__ */ React7.createElement(XAxis, { type: "number", tickFormatter: fmtUsdCompact, ...chart.axis }), /* @__PURE__ */ React7.createElement(YAxis, { type: "category", dataKey: "keyAlias", width: 160, ...chart.axis, tick: { fontSize: 10.5, fill: chart.axis.tick.fill } }), /* @__PURE__ */ React7.createElement(Tooltip, { cursor: chart.cursor, content: /* @__PURE__ */ React7.createElement(ChartTooltip, { valueFormatter: fmtUsd, labelFormatter: (l) => l }) }), /* @__PURE__ */ React7.createElement(Bar, { dataKey: "spend", name: "Spend", fill: SERIES.input, radius: [0, 3, 3, 0], barSize: 14 })))
2854
+ ))), /* @__PURE__ */ React7.createElement(Grid, { item: true, xs: 12 }, /* @__PURE__ */ React7.createElement(TableContainer3, { component: Paper4, variant: "outlined", sx: { borderRadius: 2 } }, /* @__PURE__ */ React7.createElement(Table3, { size: "small", sx: dataTableSx }, /* @__PURE__ */ React7.createElement(TableHead3, null, /* @__PURE__ */ React7.createElement(TableRow3, null, /* @__PURE__ */ React7.createElement(TableCell3, null, "Key"), /* @__PURE__ */ React7.createElement(TableCell3, null, "Models"), /* @__PURE__ */ React7.createElement(TableCell3, { align: "right" }, "Spend"), /* @__PURE__ */ React7.createElement(TableCell3, { align: "right" }, "Requests"), /* @__PURE__ */ React7.createElement(TableCell3, { align: "right" }, "Tokens"), /* @__PURE__ */ React7.createElement(TableCell3, { align: "right" }, "Failed"), /* @__PURE__ */ React7.createElement(TableCell3, { sx: { width: 160 } }, "Success rate"))), /* @__PURE__ */ React7.createElement(TableBody3, null, renderKeyRows())))))
2858
2855
  );
2859
2856
  };
2860
2857
  }
@@ -2982,12 +2979,12 @@ var init_budget = __esm({
2982
2979
  });
2983
2980
 
2984
2981
  // src/components/BudgetLimitList.tsx
2985
- import React7 from "react";
2982
+ import React8 from "react";
2986
2983
  import Box7 from "@mui/material/Box";
2987
2984
  import Paper5 from "@mui/material/Paper";
2988
2985
  import Typography7 from "@mui/material/Typography";
2989
2986
  import { alpha as alpha4, useTheme as useTheme2 } from "@mui/material/styles";
2990
- import { Link } from "@backstage/core-components";
2987
+ import { Link as Link2 } from "@backstage/core-components";
2991
2988
  function levelToneColor(tone, theme) {
2992
2989
  switch (tone) {
2993
2990
  case "success":
@@ -3013,7 +3010,7 @@ var init_BudgetLimitList = __esm({
3013
3010
  const tone = budgetTone2(limit.pct);
3014
3011
  const pct = Math.round(limit.pct);
3015
3012
  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(
3013
+ return /* @__PURE__ */ React8.createElement(Paper5, { variant: "outlined", sx: { px: 1.5, py: 1.25, borderRadius: 1.5 } }, /* @__PURE__ */ React8.createElement(Box7, { sx: { display: "flex", alignItems: "baseline", justifyContent: "space-between", gap: 1.5 } }, /* @__PURE__ */ React8.createElement(Box7, { sx: { minWidth: 0, flex: 1 } }, /* @__PURE__ */ React8.createElement(
3017
3014
  Typography7,
3018
3015
  {
3019
3016
  variant: "body2",
@@ -3021,7 +3018,7 @@ var init_BudgetLimitList = __esm({
3021
3018
  title: limit.sublabel ? `${limit.label} \xB7 ${limit.sublabel}` : limit.label
3022
3019
  },
3023
3020
  limit.label
3024
- ), limit.sublabel && /* @__PURE__ */ React7.createElement(
3021
+ ), limit.sublabel && /* @__PURE__ */ React8.createElement(
3025
3022
  Typography7,
3026
3023
  {
3027
3024
  variant: "caption",
@@ -3036,7 +3033,7 @@ var init_BudgetLimitList = __esm({
3036
3033
  }
3037
3034
  },
3038
3035
  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(
3036
+ )), limit.hidden ? /* @__PURE__ */ React8.createElement(Typography7, { variant: "body2", sx: { fontWeight: 600, fontVariantNumeric: "tabular-nums", whiteSpace: "nowrap" } }, pct, "% used") : /* @__PURE__ */ React8.createElement(Typography7, { variant: "body2", sx: { fontWeight: 600, fontVariantNumeric: "tabular-nums", whiteSpace: "nowrap" } }, fmtUsd(limit.spend), /* @__PURE__ */ React8.createElement(Box7, { component: "span", color: "text.secondary", sx: { fontWeight: 400 } }, " / ", fmtUsd(limit.budget)))), /* @__PURE__ */ React8.createElement(Box7, { sx: { mt: 1 } }, /* @__PURE__ */ React8.createElement(Meter, { value: limit.pct, tone, height: 6 })), /* @__PURE__ */ React8.createElement(
3040
3037
  Box7,
3041
3038
  {
3042
3039
  sx: {
@@ -3047,14 +3044,14 @@ var init_BudgetLimitList = __esm({
3047
3044
  mt: 0.75
3048
3045
  }
3049
3046
  },
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")
3047
+ /* @__PURE__ */ React8.createElement(Typography7, { variant: "caption", color: "text.secondary", sx: { fontVariantNumeric: "tabular-nums" } }, limit.hidden ? /* @__PURE__ */ React8.createElement(React8.Fragment, null, "Hidden by admin") : /* @__PURE__ */ React8.createElement(React8.Fragment, null, pct, "% of budget", closeTo && ` \xB7 soft-limit warning`)),
3048
+ /* @__PURE__ */ React8.createElement(Typography7, { variant: "caption", color: "text.secondary" }, fmtBudgetDuration(limit.budgetDuration) ? `resets ${fmtBudgetDuration(limit.budgetDuration)}` : "never resets")
3052
3049
  ));
3053
3050
  };
3054
3051
  TaggedLimit = ({ limit }) => {
3055
3052
  const theme = useTheme2();
3056
3053
  const tone = budgetTone2(limit.pct);
3057
- return /* @__PURE__ */ React7.createElement(Box7, null, /* @__PURE__ */ React7.createElement(
3054
+ return /* @__PURE__ */ React8.createElement(Box7, null, /* @__PURE__ */ React8.createElement(
3058
3055
  Typography7,
3059
3056
  {
3060
3057
  sx: {
@@ -3067,7 +3064,7 @@ var init_BudgetLimitList = __esm({
3067
3064
  }
3068
3065
  },
3069
3066
  limit.kind
3070
- ), /* @__PURE__ */ React7.createElement(LimitCard, { limit }));
3067
+ ), /* @__PURE__ */ React8.createElement(LimitCard, { limit }));
3071
3068
  };
3072
3069
  MoreKeysNote = ({
3073
3070
  count,
@@ -3076,12 +3073,12 @@ var init_BudgetLimitList = __esm({
3076
3073
  }) => {
3077
3074
  const text = label ?? `+${count} more budgeted key${count > 1 ? "s" : ""} further from the cap`;
3078
3075
  if (!href) {
3079
- return /* @__PURE__ */ React7.createElement(Typography7, { variant: "caption", color: "text.secondary" }, text);
3076
+ return /* @__PURE__ */ React8.createElement(Typography7, { variant: "caption", color: "text.secondary" }, text);
3080
3077
  }
3081
- return /* @__PURE__ */ React7.createElement(Link, { to: href, variant: "caption" }, text, " \u2192");
3078
+ return /* @__PURE__ */ React8.createElement(Link2, { to: href, variant: "caption" }, text, " \u2192");
3082
3079
  };
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(
3080
+ BudgetLimitList = ({ limits, hiddenKeyCount = 0, keysHref }) => /* @__PURE__ */ React8.createElement(Box7, { sx: { display: "flex", flexDirection: "column", gap: 1.25 } }, limits.map((limit) => /* @__PURE__ */ React8.createElement(TaggedLimit, { key: `${limit.kind}:${limit.sublabel ?? limit.label}`, limit })), hiddenKeyCount > 0 && /* @__PURE__ */ React8.createElement(MoreKeysNote, { count: hiddenKeyCount, href: keysHref }));
3081
+ LimitListPanel = ({ children }) => /* @__PURE__ */ React8.createElement(
3085
3082
  Box7,
3086
3083
  {
3087
3084
  sx: (theme) => ({
@@ -3097,7 +3094,7 @@ var init_BudgetLimitList = __esm({
3097
3094
  });
3098
3095
 
3099
3096
  // src/components/LiteLLMBudgetWidget.tsx
3100
- import React8, { useEffect as useEffect3, useMemo as useMemo6, useState as useState5 } from "react";
3097
+ import React9, { useEffect as useEffect3, useMemo as useMemo6, useState as useState5 } from "react";
3101
3098
  import Paper6 from "@mui/material/Paper";
3102
3099
  import Box8 from "@mui/material/Box";
3103
3100
  import Typography8 from "@mui/material/Typography";
@@ -3124,7 +3121,7 @@ var init_LiteLLMBudgetWidget = __esm({
3124
3121
  tone,
3125
3122
  isLast,
3126
3123
  children
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(
3124
+ }) => /* @__PURE__ */ React9.createElement(Box8, { sx: { display: "flex", gap: 1.5 } }, /* @__PURE__ */ React9.createElement(Box8, { sx: { display: "flex", flexDirection: "column", alignItems: "center", width: 26, flexShrink: 0 } }, /* @__PURE__ */ React9.createElement(
3128
3125
  Box8,
3129
3126
  {
3130
3127
  sx: (theme) => ({
@@ -3142,7 +3139,7 @@ var init_LiteLLMBudgetWidget = __esm({
3142
3139
  })
3143
3140
  },
3144
3141
  rank
3145
- ), /* @__PURE__ */ React8.createElement(
3142
+ ), /* @__PURE__ */ React9.createElement(
3146
3143
  Box8,
3147
3144
  {
3148
3145
  sx: (theme) => ({
@@ -3152,7 +3149,7 @@ var init_LiteLLMBudgetWidget = __esm({
3152
3149
  bgcolor: isLast ? "transparent" : alpha5(theme.palette.text.primary, 0.12)
3153
3150
  })
3154
3151
  }
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(
3152
+ )), /* @__PURE__ */ React9.createElement(Box8, { sx: { flex: 1, minWidth: 0, pb: isLast ? 0 : 2 } }, /* @__PURE__ */ React9.createElement(Box8, { sx: { display: "flex", alignItems: "baseline", gap: 1, flexWrap: "wrap", mb: 0.75 } }, /* @__PURE__ */ React9.createElement(
3156
3153
  Typography8,
3157
3154
  {
3158
3155
  sx: (theme) => ({
@@ -3164,8 +3161,8 @@ var init_LiteLLMBudgetWidget = __esm({
3164
3161
  })
3165
3162
  },
3166
3163
  name
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(
3164
+ ), /* @__PURE__ */ React9.createElement(Typography8, { variant: "caption", color: "text.secondary", sx: { fontSize: 11.5 } }, tagline), note && /* @__PURE__ */ React9.createElement(StatusPill, { label: note, tone: "neutral", dot: false, sx: { ml: "auto" } })), /* @__PURE__ */ React9.createElement(Box8, { sx: { display: "flex", flexDirection: "column", gap: 1 } }, children)));
3165
+ EmptyLimitCard = ({ children }) => /* @__PURE__ */ React9.createElement(
3169
3166
  Paper6,
3170
3167
  {
3171
3168
  variant: "outlined",
@@ -3176,7 +3173,7 @@ var init_LiteLLMBudgetWidget = __esm({
3176
3173
  bgcolor: alpha5(theme.palette.text.primary, theme.palette.mode === "dark" ? 0.03 : 0.015)
3177
3174
  })
3178
3175
  },
3179
- /* @__PURE__ */ React8.createElement(Typography8, { variant: "body2", color: "text.secondary", sx: { fontSize: 13 } }, children)
3176
+ /* @__PURE__ */ React9.createElement(Typography8, { variant: "body2", color: "text.secondary", sx: { fontSize: 13 } }, children)
3180
3177
  );
3181
3178
  CLOSEST_LEVEL_LABEL = {
3182
3179
  key: "key",
@@ -3184,7 +3181,7 @@ var init_LiteLLMBudgetWidget = __esm({
3184
3181
  team: "team"
3185
3182
  };
3186
3183
  KEYS_PATH = "/litellm?tab=keys";
3187
- HierarchyNote = ({ withReset }) => /* @__PURE__ */ React8.createElement(
3184
+ HierarchyNote = ({ withReset }) => /* @__PURE__ */ React9.createElement(
3188
3185
  Box8,
3189
3186
  {
3190
3187
  sx: (theme) => ({
@@ -3195,11 +3192,11 @@ var init_LiteLLMBudgetWidget = __esm({
3195
3192
  borderColor: alpha5(theme.palette.text.primary, 0.18)
3196
3193
  })
3197
3194
  },
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.")
3195
+ /* @__PURE__ */ React9.createElement(Typography8, { variant: "caption", color: "text.secondary", display: "block" }, /* @__PURE__ */ React9.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.")
3199
3196
  );
3200
3197
  FullBody = ({ summary }) => {
3201
3198
  const hasBudgetedKeys = summary.keys.length + summary.hiddenBudgetedKeys > 0;
3202
- return /* @__PURE__ */ React8.createElement(React8.Fragment, null, /* @__PURE__ */ React8.createElement(
3199
+ return /* @__PURE__ */ React9.createElement(React9.Fragment, null, /* @__PURE__ */ React9.createElement(
3203
3200
  LevelFrame,
3204
3201
  {
3205
3202
  rank: 1,
@@ -3207,10 +3204,10 @@ var init_LiteLLMBudgetWidget = __esm({
3207
3204
  tagline: "per-key cap \xB7 highest priority",
3208
3205
  note: hasBudgetedKeys ? void 0 : "no key budgets set"
3209
3206
  },
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(
3207
+ !hasBudgetedKeys && /* @__PURE__ */ React9.createElement(EmptyLimitCard, null, "No key has a budget, so no key-level cap applies."),
3208
+ summary.keys.map((k) => /* @__PURE__ */ React9.createElement(LimitCard, { key: k.sublabel ?? k.label, limit: k })),
3209
+ summary.hiddenBudgetedKeys > 0 && /* @__PURE__ */ React9.createElement(MoreKeysNote, { count: summary.hiddenBudgetedKeys, href: KEYS_PATH })
3210
+ ), /* @__PURE__ */ React9.createElement(
3214
3211
  LevelFrame,
3215
3212
  {
3216
3213
  rank: 2,
@@ -3218,15 +3215,15 @@ var init_LiteLLMBudgetWidget = __esm({
3218
3215
  tagline: "cap on everything you spend with your own keys",
3219
3216
  note: "skipped for team-bound keys"
3220
3217
  },
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 })));
3218
+ summary.user ? /* @__PURE__ */ React9.createElement(LimitCard, { limit: summary.user }) : /* @__PURE__ */ React9.createElement(EmptyLimitCard, null, "No personal budget is set on your account.")
3219
+ ), /* @__PURE__ */ React9.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__ */ React9.createElement(LimitCard, { key: t.sublabel ?? t.label, limit: t })) : /* @__PURE__ */ React9.createElement(EmptyLimitCard, null, "No team you belong to has a budget.")), /* @__PURE__ */ React9.createElement(LevelFrame, { rank: 4, name: "Global", tagline: "proxy-wide cap set by your admin", isLast: true }, /* @__PURE__ */ React9.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__ */ React9.createElement(Box8, { sx: { mt: 2 } }, /* @__PURE__ */ React9.createElement(HierarchyNote, { withReset: true })));
3223
3220
  };
3224
3221
  CompactBody = ({ summary }) => {
3225
3222
  const { count } = budgetHeadline(summary);
3226
3223
  if (count === 0) {
3227
- return /* @__PURE__ */ React8.createElement(Typography8, { variant: "body2", color: "text.secondary", sx: { fontSize: 13 } }, "No budget limits apply to you right now.");
3224
+ return /* @__PURE__ */ React9.createElement(Typography8, { variant: "body2", color: "text.secondary", sx: { fontSize: 13 } }, "No budget limits apply to you right now.");
3228
3225
  }
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 })));
3226
+ return /* @__PURE__ */ React9.createElement(Box8, { sx: { display: "flex", flexDirection: "column", gap: 1.25 } }, /* @__PURE__ */ React9.createElement(HierarchyNote, null), summary.keys.map((k) => /* @__PURE__ */ React9.createElement(TaggedLimit, { key: k.sublabel ?? k.label, limit: k })), summary.hiddenBudgetedKeys > 0 && /* @__PURE__ */ React9.createElement(MoreKeysNote, { count: summary.hiddenBudgetedKeys, href: KEYS_PATH }), summary.user && /* @__PURE__ */ React9.createElement(TaggedLimit, { limit: summary.user }), summary.teams.map((t) => /* @__PURE__ */ React9.createElement(TaggedLimit, { key: t.sublabel ?? t.label, limit: t })));
3230
3227
  };
3231
3228
  LiteLLMBudgetWidget = ({
3232
3229
  title = "Budget Policy",
@@ -3277,8 +3274,8 @@ var init_LiteLLMBudgetWidget = __esm({
3277
3274
  headline.closest.pct
3278
3275
  )}%`;
3279
3276
  const summaryText = headline.count === 0 ? "no limits apply" : `${limitsWord}${closestWord}`;
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(
3277
+ const body = /* @__PURE__ */ React9.createElement(React9.Fragment, null, loading && /* @__PURE__ */ React9.createElement(Box8, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: 140 }, /* @__PURE__ */ React9.createElement(CircularProgress4, { size: 32 })), !loading && error && /* @__PURE__ */ React9.createElement(Alert3, { severity: "error", sx: { mt: 0.5 } }, error), !loading && !error && (compact ? /* @__PURE__ */ React9.createElement(CompactBody, { summary }) : /* @__PURE__ */ React9.createElement(FullBody, { summary })));
3278
+ return /* @__PURE__ */ React9.createElement(Paper6, { sx: { p: 2 } }, collapsible ? /* @__PURE__ */ React9.createElement(
3282
3279
  Box8,
3283
3280
  {
3284
3281
  onClick: () => setExpanded((e) => !e),
@@ -3291,14 +3288,14 @@ var init_LiteLLMBudgetWidget = __esm({
3291
3288
  mb: expanded ? 1.5 : 0
3292
3289
  }
3293
3290
  },
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(
3291
+ /* @__PURE__ */ React9.createElement(Box8, { sx: { minWidth: 0, flex: 1 } }, /* @__PURE__ */ React9.createElement(Typography8, { variant: "h6", lineHeight: 1.2 }, title), !loading && !error && /* @__PURE__ */ React9.createElement(Box8, { sx: { mt: 0.5 } }, /* @__PURE__ */ React9.createElement(
3295
3292
  StatusPill,
3296
3293
  {
3297
3294
  label: summaryText,
3298
3295
  tone: budgetTone2(headline.closest?.pct ?? 0)
3299
3296
  }
3300
3297
  ))),
3301
- /* @__PURE__ */ React8.createElement(
3298
+ /* @__PURE__ */ React9.createElement(
3302
3299
  IconButton4,
3303
3300
  {
3304
3301
  size: "small",
@@ -3314,9 +3311,9 @@ var init_LiteLLMBudgetWidget = __esm({
3314
3311
  transition: theme.transitions.create("transform")
3315
3312
  })
3316
3313
  },
3317
- /* @__PURE__ */ React8.createElement(ExpandMore2, null)
3314
+ /* @__PURE__ */ React9.createElement(ExpandMore2, null)
3318
3315
  )
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(
3316
+ ) : /* @__PURE__ */ React9.createElement(Box8, { sx: { mb: 1.5 } }, /* @__PURE__ */ React9.createElement(Typography8, { variant: "h6", lineHeight: 1.2 }, title), /* @__PURE__ */ React9.createElement(Typography8, { variant: "caption", color: "text.secondary" }, "How LiteLLM caps spend \u2014 and where you stand right now")), collapsible ? /* @__PURE__ */ React9.createElement(Collapse, { in: expanded, unmountOnExit: true }, body) : body, action && /* @__PURE__ */ React9.createElement(
3320
3317
  Box8,
3321
3318
  {
3322
3319
  sx: (theme) => ({
@@ -3332,7 +3329,7 @@ var init_LiteLLMBudgetWidget = __esm({
3332
3329
  });
3333
3330
 
3334
3331
  // src/components/TeamUsage.tsx
3335
- import React9, { useState as useState6 } from "react";
3332
+ import React10, { useState as useState6 } from "react";
3336
3333
  import Paper7 from "@mui/material/Paper";
3337
3334
  import Box9 from "@mui/material/Box";
3338
3335
  import Typography9 from "@mui/material/Typography";
@@ -3349,7 +3346,7 @@ import IconButton5 from "@mui/material/IconButton";
3349
3346
  import Button5 from "@mui/material/Button";
3350
3347
  import CircularProgress5 from "@mui/material/CircularProgress";
3351
3348
  import { alpha as alpha6 } from "@mui/material/styles";
3352
- import { ExpandMore as ExpandMore3, Group, Add as Add3 } from "@mui/icons-material";
3349
+ import { ExpandMore as ExpandMore3, Group, Add as Add2 } from "@mui/icons-material";
3353
3350
  import {
3354
3351
  AreaChart as AreaChart2,
3355
3352
  Area as Area2,
@@ -3392,10 +3389,10 @@ var init_TeamUsage = __esm({
3392
3389
  const renderDailySpendSection = () => {
3393
3390
  if (hidden) return null;
3394
3391
  if (usageLoading) {
3395
- return /* @__PURE__ */ React9.createElement(Box9, { display: "flex", justifyContent: "center", py: 3 }, /* @__PURE__ */ React9.createElement(CircularProgress5, { size: 22 }));
3392
+ return /* @__PURE__ */ React10.createElement(Box9, { display: "flex", justifyContent: "center", py: 3 }, /* @__PURE__ */ React10.createElement(CircularProgress5, { size: 22 }));
3396
3393
  }
3397
3394
  if (dailyData.length === 0) return null;
3398
- return /* @__PURE__ */ React9.createElement(Box9, { mb: 2.5 }, /* @__PURE__ */ React9.createElement(
3395
+ return /* @__PURE__ */ React10.createElement(Box9, { mb: 2.5 }, /* @__PURE__ */ React10.createElement(
3399
3396
  Typography9,
3400
3397
  {
3401
3398
  variant: "caption",
@@ -3403,13 +3400,13 @@ var init_TeamUsage = __esm({
3403
3400
  sx: { display: "block", fontSize: 11, fontWeight: 700, letterSpacing: "0.07em", textTransform: "uppercase", mb: 1 }
3404
3401
  },
3405
3402
  "Daily spend"
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(
3403
+ ), /* @__PURE__ */ React10.createElement(Box9, { height: 150 }, /* @__PURE__ */ React10.createElement(ResponsiveContainer2, { width: "100%", height: "100%" }, /* @__PURE__ */ React10.createElement(AreaChart2, { data: dailyData, margin: { top: 4, right: 8, left: 0, bottom: 0 } }, /* @__PURE__ */ React10.createElement(CartesianGrid2, { ...chart.grid }), /* @__PURE__ */ React10.createElement(XAxis2, { dataKey: "date", tickFormatter: fmtDateShort, ...chart.axis }), /* @__PURE__ */ React10.createElement(YAxis2, { tickFormatter: fmtUsdCompact, width: 52, ...chart.axis }), /* @__PURE__ */ React10.createElement(
3407
3404
  Tooltip2,
3408
3405
  {
3409
3406
  cursor: chart.cursor,
3410
- content: /* @__PURE__ */ React9.createElement(ChartTooltip, { valueFormatter: (v) => `$${v.toFixed(4)}` })
3407
+ content: /* @__PURE__ */ React10.createElement(ChartTooltip, { valueFormatter: (v) => `$${v.toFixed(4)}` })
3411
3408
  }
3412
- ), /* @__PURE__ */ React9.createElement(
3409
+ ), /* @__PURE__ */ React10.createElement(
3413
3410
  Area2,
3414
3411
  {
3415
3412
  type: "monotone",
@@ -3422,7 +3419,7 @@ var init_TeamUsage = __esm({
3422
3419
  }
3423
3420
  )))));
3424
3421
  };
3425
- const sectionLabel = (label) => /* @__PURE__ */ React9.createElement(
3422
+ const sectionLabel = (label) => /* @__PURE__ */ React10.createElement(
3426
3423
  Typography9,
3427
3424
  {
3428
3425
  variant: "caption",
@@ -3431,7 +3428,7 @@ var init_TeamUsage = __esm({
3431
3428
  },
3432
3429
  label
3433
3430
  );
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(
3431
+ return /* @__PURE__ */ React10.createElement(Paper7, { variant: "outlined", sx: { borderRadius: 2, p: 2.5 } }, /* @__PURE__ */ React10.createElement(Box9, { display: "flex", alignItems: "center", gap: 1.5 }, /* @__PURE__ */ React10.createElement(
3435
3432
  Box9,
3436
3433
  {
3437
3434
  sx: (theme) => ({
@@ -3446,8 +3443,8 @@ var init_TeamUsage = __esm({
3446
3443
  color: theme.palette.primary.main
3447
3444
  })
3448
3445
  },
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(
3446
+ /* @__PURE__ */ React10.createElement(Group, { fontSize: "small" })
3447
+ ), /* @__PURE__ */ React10.createElement(Box9, { flexGrow: 1, minWidth: 0 }, /* @__PURE__ */ React10.createElement(Typography9, { variant: "subtitle1", sx: { fontWeight: 600, lineHeight: 1.3 } }, team.team_alias || "Untitled team"), /* @__PURE__ */ React10.createElement(
3451
3448
  Typography9,
3452
3449
  {
3453
3450
  variant: "caption",
@@ -3455,7 +3452,7 @@ var init_TeamUsage = __esm({
3455
3452
  sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 11 }
3456
3453
  },
3457
3454
  team.team_id
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(
3455
+ )), isOver && /* @__PURE__ */ React10.createElement(StatusPill, { label: "Over budget", tone: "danger" }), isNear && /* @__PURE__ */ React10.createElement(StatusPill, { label: "Near limit", tone: "warning" }), canManage && onEditTeam && /* @__PURE__ */ React10.createElement(
3459
3456
  Button5,
3460
3457
  {
3461
3458
  size: "small",
@@ -3464,7 +3461,7 @@ var init_TeamUsage = __esm({
3464
3461
  sx: { textTransform: "none" }
3465
3462
  },
3466
3463
  "Edit"
3467
- ), /* @__PURE__ */ React9.createElement(
3464
+ ), /* @__PURE__ */ React10.createElement(
3468
3465
  IconButton5,
3469
3466
  {
3470
3467
  size: "small",
@@ -3477,8 +3474,8 @@ var init_TeamUsage = __esm({
3477
3474
  transition: theme.transitions.create("transform")
3478
3475
  })
3479
3476
  },
3480
- /* @__PURE__ */ React9.createElement(ExpandMore3, null)
3481
- )), /* @__PURE__ */ React9.createElement(
3477
+ /* @__PURE__ */ React10.createElement(ExpandMore3, null)
3478
+ )), /* @__PURE__ */ React10.createElement(
3482
3479
  Box9,
3483
3480
  {
3484
3481
  sx: {
@@ -3489,20 +3486,20 @@ var init_TeamUsage = __esm({
3489
3486
  maxWidth: 640
3490
3487
  }
3491
3488
  },
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(
3489
+ /* @__PURE__ */ React10.createElement(Stat, { label: "Members", value: team.members_with_roles?.length ?? "\u2014" }),
3490
+ /* @__PURE__ */ React10.createElement(Stat, { label: "Models", value: team.models?.length ? team.models.length : "All" }),
3491
+ /* @__PURE__ */ React10.createElement(Stat, { label: "Budget", value: budgetStatValue(hidden, budget) }),
3492
+ /* @__PURE__ */ React10.createElement(Stat, { label: "Spend", value: hidden ? "\u2014" : fmtUsd2(spend) }),
3493
+ /* @__PURE__ */ React10.createElement(Stat, { label: "TPM", value: team.tpm_limit && team.tpm_limit > 0 ? team.tpm_limit.toLocaleString() : "\u2014" }),
3494
+ /* @__PURE__ */ React10.createElement(Stat, { label: "RPM", value: team.rpm_limit && team.rpm_limit > 0 ? team.rpm_limit.toLocaleString() : "\u2014" })
3495
+ ), hidden && /* @__PURE__ */ React10.createElement(Box9, { mt: 2, maxWidth: 640 }, /* @__PURE__ */ React10.createElement(Box9, { display: "flex", justifyContent: "space-between", alignItems: "baseline", mb: 0.75 }, /* @__PURE__ */ React10.createElement(Typography9, { variant: "caption", color: "text.secondary", sx: { fontVariantNumeric: "tabular-nums" } }, budgetPct.toFixed(0), "% used"), /* @__PURE__ */ React10.createElement(Typography9, { variant: "caption", color: "text.secondary" }, "Hidden by admin")), /* @__PURE__ */ React10.createElement(Meter, { value: budgetPct, tone: budgetTone3(isOver, isNear), height: 5 }), /* @__PURE__ */ React10.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__ */ React10.createElement(Box9, { mt: 2, maxWidth: 640 }, /* @__PURE__ */ React10.createElement(Box9, { display: "flex", justifyContent: "space-between", alignItems: "baseline", mb: 0.75 }, /* @__PURE__ */ React10.createElement(Typography9, { variant: "caption", color: "text.secondary", sx: { fontVariantNumeric: "tabular-nums" } }, fmtUsd2(spend), " of ", fmtUsd2(budget)), /* @__PURE__ */ React10.createElement(Typography9, { variant: "caption", color: "text.secondary", sx: { fontVariantNumeric: "tabular-nums" } }, budgetPct.toFixed(0), "%")), /* @__PURE__ */ React10.createElement(Meter, { value: budgetPct, tone: budgetTone3(isOver, isNear), height: 5 })), /* @__PURE__ */ React10.createElement(Collapse2, { in: expanded, unmountOnExit: true }, /* @__PURE__ */ React10.createElement(Divider3, { sx: { my: 2.5 } }), /* @__PURE__ */ React10.createElement(Box9, { mb: 2.5 }, sectionLabel("Models"), team.models?.length ? /* @__PURE__ */ React10.createElement(Box9, { display: "flex", gap: 0.5, flexWrap: "wrap" }, team.models.map((m) => /* @__PURE__ */ React10.createElement(TagChip, { key: m, label: m, title: m }))) : /* @__PURE__ */ React10.createElement(Typography9, { variant: "body2", color: "text.secondary" }, "All models allowed")), renderDailySpendSection(), /* @__PURE__ */ React10.createElement(Box9, null, sectionLabel("Members"), team.members_with_roles?.length ? /* @__PURE__ */ React10.createElement(
3499
3496
  TableContainer4,
3500
3497
  {
3501
3498
  component: Paper7,
3502
3499
  variant: "outlined",
3503
3500
  sx: { borderRadius: 1.5 }
3504
3501
  },
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(
3502
+ /* @__PURE__ */ React10.createElement(Table4, { size: "small", sx: dataTableSx }, /* @__PURE__ */ React10.createElement(TableHead4, null, /* @__PURE__ */ React10.createElement(TableRow4, null, /* @__PURE__ */ React10.createElement(TableCell4, null, "User"), /* @__PURE__ */ React10.createElement(TableCell4, { align: "right" }, "Role"))), /* @__PURE__ */ React10.createElement(TableBody4, null, team.members_with_roles.map((m) => /* @__PURE__ */ React10.createElement(TableRow4, { key: m.user_id }, /* @__PURE__ */ React10.createElement(TableCell4, null, m.user_email ? /* @__PURE__ */ React10.createElement(React10.Fragment, null, /* @__PURE__ */ React10.createElement(Typography9, { variant: "body2" }, m.user_email), /* @__PURE__ */ React10.createElement(
3506
3503
  Typography9,
3507
3504
  {
3508
3505
  variant: "caption",
@@ -3510,14 +3507,14 @@ var init_TeamUsage = __esm({
3510
3507
  sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 11 }
3511
3508
  },
3512
3509
  m.user_id
3513
- )) : /* @__PURE__ */ React9.createElement(
3510
+ )) : /* @__PURE__ */ React10.createElement(
3514
3511
  Typography9,
3515
3512
  {
3516
3513
  variant: "body2",
3517
3514
  sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace" }
3518
3515
  },
3519
3516
  m.user_id
3520
- )), /* @__PURE__ */ React9.createElement(TableCell4, { align: "right" }, /* @__PURE__ */ React9.createElement(
3517
+ )), /* @__PURE__ */ React10.createElement(TableCell4, { align: "right" }, /* @__PURE__ */ React10.createElement(
3521
3518
  StatusPill,
3522
3519
  {
3523
3520
  label: m.role,
@@ -3525,7 +3522,7 @@ var init_TeamUsage = __esm({
3525
3522
  dot: false
3526
3523
  }
3527
3524
  ))))))
3528
- ) : /* @__PURE__ */ React9.createElement(Typography9, { variant: "body2", color: "text.secondary" }, "No members assigned."))));
3525
+ ) : /* @__PURE__ */ React10.createElement(Typography9, { variant: "body2", color: "text.secondary" }, "No members assigned."))));
3529
3526
  };
3530
3527
  TeamUsage = ({
3531
3528
  teams,
@@ -3537,22 +3534,22 @@ var init_TeamUsage = __esm({
3537
3534
  canCreate,
3538
3535
  onCreateTeam
3539
3536
  }) => {
3540
- const createAction = canCreate && onCreateTeam ? /* @__PURE__ */ React9.createElement(
3537
+ const createAction = canCreate && onCreateTeam ? /* @__PURE__ */ React10.createElement(
3541
3538
  Button5,
3542
3539
  {
3543
3540
  variant: "contained",
3544
3541
  color: "primary",
3545
3542
  disableElevation: true,
3546
- startIcon: /* @__PURE__ */ React9.createElement(Add3, null),
3543
+ startIcon: /* @__PURE__ */ React10.createElement(Add2, null),
3547
3544
  onClick: onCreateTeam
3548
3545
  },
3549
3546
  "Create Team"
3550
3547
  ) : void 0;
3551
3548
  if (loading) {
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 }));
3549
+ return /* @__PURE__ */ React10.createElement(SectionCard, { title: "Teams", actions: createAction }, /* @__PURE__ */ React10.createElement(Skeleton4, { variant: "rounded", height: 120, sx: { mb: 2 } }), /* @__PURE__ */ React10.createElement(Skeleton4, { variant: "rounded", height: 120 }));
3553
3550
  }
3554
3551
  if (!teams.length) {
3555
- return /* @__PURE__ */ React9.createElement(SectionCard, { title: "Teams", actions: createAction }, /* @__PURE__ */ React9.createElement(
3552
+ return /* @__PURE__ */ React10.createElement(SectionCard, { title: "Teams", actions: createAction }, /* @__PURE__ */ React10.createElement(
3556
3553
  EmptyState,
3557
3554
  {
3558
3555
  message: "You're not a member of any LiteLLM team yet.",
@@ -3560,14 +3557,14 @@ var init_TeamUsage = __esm({
3560
3557
  }
3561
3558
  ));
3562
3559
  }
3563
- return /* @__PURE__ */ React9.createElement(
3560
+ return /* @__PURE__ */ React10.createElement(
3564
3561
  SectionCard,
3565
3562
  {
3566
3563
  title: "Teams",
3567
3564
  subtitle: `${teams.length} team${teams.length === 1 ? "" : "s"}`,
3568
3565
  actions: createAction
3569
3566
  },
3570
- /* @__PURE__ */ React9.createElement(Box9, { sx: { display: "flex", flexDirection: "column", gap: 2 } }, teams.map((team) => /* @__PURE__ */ React9.createElement(
3567
+ /* @__PURE__ */ React10.createElement(Box9, { sx: { display: "flex", flexDirection: "column", gap: 2 } }, teams.map((team) => /* @__PURE__ */ React10.createElement(
3571
3568
  TeamCard,
3572
3569
  {
3573
3570
  key: team.team_id,
@@ -3584,7 +3581,7 @@ var init_TeamUsage = __esm({
3584
3581
  });
3585
3582
 
3586
3583
  // src/components/ModelsTable.tsx
3587
- import React10, { useState as useState7, useMemo as useMemo7, useCallback } from "react";
3584
+ import React11, { useState as useState7, useMemo as useMemo7, useCallback } from "react";
3588
3585
  import Box10 from "@mui/material/Box";
3589
3586
  import Table5 from "@mui/material/Table";
3590
3587
  import TableBody5 from "@mui/material/TableBody";
@@ -3716,13 +3713,13 @@ var init_ModelsTable = __esm({
3716
3713
  return theme.palette.info.main;
3717
3714
  }
3718
3715
  };
3719
- return /* @__PURE__ */ React10.createElement(
3716
+ return /* @__PURE__ */ React11.createElement(
3720
3717
  SectionCard,
3721
3718
  {
3722
3719
  title: "Available Models",
3723
3720
  subtitle: selectedTeam ? `${filteredModels.length}${filterText.trim() ? ` of ${teamModels.length}` : ""} model${filteredModels.length === 1 ? "" : "s"}` : "Select a team to view its models"
3724
3721
  },
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(
3722
+ /* @__PURE__ */ React11.createElement(Box10, { sx: { px: 2.5, pb: 2.5, pt: 1, display: "flex", gap: 2, flexWrap: "wrap" } }, /* @__PURE__ */ React11.createElement(FormControl, { size: "small", sx: { minWidth: 240, mb: 2 } }, /* @__PURE__ */ React11.createElement(InputLabel, { id: "team-select-label" }, "Team"), /* @__PURE__ */ React11.createElement(
3726
3723
  Select,
3727
3724
  {
3728
3725
  labelId: "team-select-label",
@@ -3730,8 +3727,8 @@ var init_ModelsTable = __esm({
3730
3727
  label: "Team",
3731
3728
  onChange: (e) => setSelectedTeamId(e.target.value)
3732
3729
  },
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(
3730
+ teams.map((t) => /* @__PURE__ */ React11.createElement(MenuItem4, { key: t.team_id, value: t.team_id }, t.team_alias ?? t.team_id))
3731
+ )), /* @__PURE__ */ React11.createElement(
3735
3732
  TextField5,
3736
3733
  {
3737
3734
  label: "Filter Models",
@@ -3741,33 +3738,33 @@ var init_ModelsTable = __esm({
3741
3738
  disabled: !selectedTeamId,
3742
3739
  sx: { minWidth: 240, mb: 2 },
3743
3740
  InputProps: {
3744
- startAdornment: /* @__PURE__ */ React10.createElement(InputAdornment2, { position: "start" }, /* @__PURE__ */ React10.createElement(Search2, { fontSize: "small" }))
3741
+ startAdornment: /* @__PURE__ */ React11.createElement(InputAdornment2, { position: "start" }, /* @__PURE__ */ React11.createElement(Search2, { fontSize: "small" }))
3745
3742
  }
3746
3743
  }
3747
3744
  )),
3748
- loading && /* @__PURE__ */ React10.createElement(EmptyState, { message: "Loading models\u2026" }),
3749
- !loading && !selectedTeamId && /* @__PURE__ */ React10.createElement(
3745
+ loading && /* @__PURE__ */ React11.createElement(EmptyState, { message: "Loading models\u2026" }),
3746
+ !loading && !selectedTeamId && /* @__PURE__ */ React11.createElement(
3750
3747
  EmptyState,
3751
3748
  {
3752
3749
  message: "Select a team to see available models",
3753
3750
  hint: "Each team may have a different set of models assigned"
3754
3751
  }
3755
3752
  ),
3756
- !loading && selectedTeamId && teamModels.length === 0 && /* @__PURE__ */ React10.createElement(
3753
+ !loading && selectedTeamId && teamModels.length === 0 && /* @__PURE__ */ React11.createElement(
3757
3754
  EmptyState,
3758
3755
  {
3759
3756
  message: "No models available for this team",
3760
3757
  hint: allModels.length === 0 ? "No models are configured in the system" : "None of the team's assigned models were found in the catalog"
3761
3758
  }
3762
3759
  ),
3763
- !loading && selectedTeamId && teamModels.length > 0 && filteredModels.length === 0 && /* @__PURE__ */ React10.createElement(
3760
+ !loading && selectedTeamId && teamModels.length > 0 && filteredModels.length === 0 && /* @__PURE__ */ React11.createElement(
3764
3761
  EmptyState,
3765
3762
  {
3766
3763
  message: "No models match your search",
3767
3764
  hint: "Try a different search term"
3768
3765
  }
3769
3766
  ),
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(
3767
+ !loading && filteredModels.length > 0 && /* @__PURE__ */ React11.createElement(TableContainer5, null, /* @__PURE__ */ React11.createElement(Table5, { size: "small", sx: dataTableSx }, /* @__PURE__ */ React11.createElement(TableHead5, null, /* @__PURE__ */ React11.createElement(TableRow5, null, /* @__PURE__ */ React11.createElement(TableCell5, { sortDirection: sortKey === "model_name" ? sortDirection : false }, /* @__PURE__ */ React11.createElement(TableSortLabel2, { active: sortKey === "model_name", direction: sortKey === "model_name" ? sortDirection : "asc", onClick: () => handleSort("model_name") }, "Model ID")), /* @__PURE__ */ React11.createElement(TableCell5, { sortDirection: sortKey === "mode" ? sortDirection : false }, /* @__PURE__ */ React11.createElement(TableSortLabel2, { active: sortKey === "mode", direction: sortKey === "mode" ? sortDirection : "asc", onClick: () => handleSort("mode") }, "Mode")), /* @__PURE__ */ React11.createElement(TableCell5, { align: "right", sortDirection: sortKey === "input_cost_per_token" ? sortDirection : false }, /* @__PURE__ */ React11.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__ */ React11.createElement(TableCell5, { align: "right", sortDirection: sortKey === "output_cost_per_token" ? sortDirection : false }, /* @__PURE__ */ React11.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__ */ React11.createElement(TableCell5, { align: "right", sortDirection: sortKey === "max_input_tokens" ? sortDirection : false }, /* @__PURE__ */ React11.createElement(TableSortLabel2, { active: sortKey === "max_input_tokens", direction: sortKey === "max_input_tokens" ? sortDirection : "asc", onClick: () => handleSort("max_input_tokens") }, "Max Input")), /* @__PURE__ */ React11.createElement(TableCell5, { align: "right", sortDirection: sortKey === "max_output_tokens" ? sortDirection : false }, /* @__PURE__ */ React11.createElement(TableSortLabel2, { active: sortKey === "max_output_tokens", direction: sortKey === "max_output_tokens" ? sortDirection : "asc", onClick: () => handleSort("max_output_tokens") }, "Max Output")), /* @__PURE__ */ React11.createElement(TableCell5, null, "Capabilities"))), /* @__PURE__ */ React11.createElement(TableBody5, null, filteredModels.map((m) => /* @__PURE__ */ React11.createElement(TableRow5, { key: m.model_name, hover: true }, /* @__PURE__ */ React11.createElement(TableCell5, null, /* @__PURE__ */ React11.createElement(Box10, { sx: { display: "flex", alignItems: "center", gap: 0.5 } }, /* @__PURE__ */ React11.createElement(Tooltip3, { title: m.model_name, placement: "top" }, /* @__PURE__ */ React11.createElement(
3771
3768
  "span",
3772
3769
  {
3773
3770
  role: "button",
@@ -3777,8 +3774,8 @@ var init_ModelsTable = __esm({
3777
3774
  onKeyDown: (e) => e.key === "Enter" && handleCopyModelId(m.model_name),
3778
3775
  style: { cursor: "pointer", display: "inline-flex" }
3779
3776
  },
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(
3777
+ /* @__PURE__ */ React11.createElement(TagChip, { label: m.model_name, title: "Copy model ID" })
3778
+ )), /* @__PURE__ */ React11.createElement(Tooltip3, { title: "Copy model ID" }, /* @__PURE__ */ React11.createElement(
3782
3779
  Box10,
3783
3780
  {
3784
3781
  component: "span",
@@ -3789,8 +3786,8 @@ var init_ModelsTable = __esm({
3789
3786
  sx: quietIconButtonSx("accent"),
3790
3787
  style: { cursor: "pointer", display: "inline-flex" }
3791
3788
  },
3792
- /* @__PURE__ */ React10.createElement(ContentCopyIcon, { sx: { fontSize: 14 } })
3793
- )))), /* @__PURE__ */ React10.createElement(TableCell5, null, /* @__PURE__ */ React10.createElement(
3789
+ /* @__PURE__ */ React11.createElement(ContentCopyIcon, { sx: { fontSize: 14 } })
3790
+ )))), /* @__PURE__ */ React11.createElement(TableCell5, null, /* @__PURE__ */ React11.createElement(
3794
3791
  Box10,
3795
3792
  {
3796
3793
  component: "span",
@@ -3807,8 +3804,8 @@ var init_ModelsTable = __esm({
3807
3804
  }
3808
3805
  },
3809
3806
  m.mode
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(
3807
+ )), /* @__PURE__ */ React11.createElement(TableCell5, { align: "right" }, /* @__PURE__ */ React11.createElement(Typography10, { variant: "body2", sx: { fontVariantNumeric: "tabular-nums" } }, fmtCostPerToken(m.input_cost_per_token))), /* @__PURE__ */ React11.createElement(TableCell5, { align: "right" }, /* @__PURE__ */ React11.createElement(Typography10, { variant: "body2", sx: { fontVariantNumeric: "tabular-nums" } }, fmtCostPerToken(m.output_cost_per_token))), /* @__PURE__ */ React11.createElement(TableCell5, { align: "right" }, /* @__PURE__ */ React11.createElement(Typography10, { variant: "body2", sx: { fontVariantNumeric: "tabular-nums" } }, fmtTokens(m.max_input_tokens))), /* @__PURE__ */ React11.createElement(TableCell5, { align: "right" }, /* @__PURE__ */ React11.createElement(Typography10, { variant: "body2", sx: { fontVariantNumeric: "tabular-nums" } }, fmtTokens(m.max_output_tokens))), /* @__PURE__ */ React11.createElement(TableCell5, null, /* @__PURE__ */ React11.createElement(Box10, { sx: { display: "flex", gap: 0.5, flexWrap: "wrap" } }, m.supports_function_calling && /* @__PURE__ */ React11.createElement(TagChip, { label: "Fn", title: "Function calling" }), m.supports_vision && /* @__PURE__ */ React11.createElement(TagChip, { label: "\u{1F441}", title: "Vision" })))))))),
3808
+ /* @__PURE__ */ React11.createElement(
3812
3809
  Snackbar,
3813
3810
  {
3814
3811
  open: copiedSnackbar,
@@ -3816,7 +3813,7 @@ var init_ModelsTable = __esm({
3816
3813
  onClose: () => setCopiedSnackbar(false),
3817
3814
  anchorOrigin: { vertical: "bottom", horizontal: "center" }
3818
3815
  },
3819
- /* @__PURE__ */ React10.createElement(Alert4, { severity: "success", variant: "filled", sx: { fontSize: 13 } }, "Model ID copied to clipboard")
3816
+ /* @__PURE__ */ React11.createElement(Alert4, { severity: "success", variant: "filled", sx: { fontSize: 13 } }, "Model ID copied to clipboard")
3820
3817
  )
3821
3818
  );
3822
3819
  };
@@ -3824,7 +3821,7 @@ var init_ModelsTable = __esm({
3824
3821
  });
3825
3822
 
3826
3823
  // src/components/AuditLog.tsx
3827
- import React11, { useState as useState8, useMemo as useMemo8 } from "react";
3824
+ import React12, { useState as useState8, useMemo as useMemo8 } from "react";
3828
3825
  import Box11 from "@mui/material/Box";
3829
3826
  import Typography11 from "@mui/material/Typography";
3830
3827
  import Table6 from "@mui/material/Table";
@@ -3863,10 +3860,10 @@ function formatDateTime(iso) {
3863
3860
  }
3864
3861
  function renderAuditLogBody(loading, entries) {
3865
3862
  if (loading) {
3866
- return /* @__PURE__ */ React11.createElement(TableRow6, null, /* @__PURE__ */ React11.createElement(TableCell6, { colSpan: 6, sx: { py: 2 } }, /* @__PURE__ */ React11.createElement(Skeleton5, { variant: "rounded", height: 160 })));
3863
+ return /* @__PURE__ */ React12.createElement(TableRow6, null, /* @__PURE__ */ React12.createElement(TableCell6, { colSpan: 6, sx: { py: 2 } }, /* @__PURE__ */ React12.createElement(Skeleton5, { variant: "rounded", height: 160 })));
3867
3864
  }
3868
3865
  if (entries.length === 0) {
3869
- return /* @__PURE__ */ React11.createElement(TableRow6, null, /* @__PURE__ */ React11.createElement(TableCell6, { colSpan: 6 }, /* @__PURE__ */ React11.createElement(
3866
+ return /* @__PURE__ */ React12.createElement(TableRow6, null, /* @__PURE__ */ React12.createElement(TableCell6, { colSpan: 6 }, /* @__PURE__ */ React12.createElement(
3870
3867
  EmptyState,
3871
3868
  {
3872
3869
  message: "No audit events found",
@@ -3874,7 +3871,7 @@ function renderAuditLogBody(loading, entries) {
3874
3871
  }
3875
3872
  )));
3876
3873
  }
3877
- return entries.map((entry) => /* @__PURE__ */ React11.createElement(DetailRow, { key: entry.id, entry }));
3874
+ return entries.map((entry) => /* @__PURE__ */ React12.createElement(DetailRow, { key: entry.id, entry }));
3878
3875
  }
3879
3876
  var ACTION_TONES, TABLE_LABELS, DetailRow, AuditLog;
3880
3877
  var init_AuditLog = __esm({
@@ -3898,7 +3895,7 @@ var init_AuditLog = __esm({
3898
3895
  DetailRow = ({ entry }) => {
3899
3896
  const [open, setOpen] = useState8(false);
3900
3897
  const hasDetail = entry.before_value || entry.updated_values;
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(
3898
+ return /* @__PURE__ */ React12.createElement(React12.Fragment, null, /* @__PURE__ */ React12.createElement(TableRow6, null, /* @__PURE__ */ React12.createElement(TableCell6, { sx: { width: 40, pr: 0 } }, hasDetail && /* @__PURE__ */ React12.createElement(
3902
3899
  IconButton6,
3903
3900
  {
3904
3901
  size: "small",
@@ -3911,8 +3908,8 @@ var init_AuditLog = __esm({
3911
3908
  transition: theme.transitions.create("transform")
3912
3909
  })
3913
3910
  },
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(
3911
+ /* @__PURE__ */ React12.createElement(KeyboardArrowDown, { fontSize: "small" })
3912
+ )), /* @__PURE__ */ React12.createElement(TableCell6, { sx: { whiteSpace: "nowrap" } }, /* @__PURE__ */ React12.createElement(Typography11, { variant: "body2", color: "text.secondary" }, formatDateTime(entry.updated_at))), /* @__PURE__ */ React12.createElement(TableCell6, null, entry.action && /* @__PURE__ */ React12.createElement(StatusPill, { label: entry.action, tone: actionTone(entry.action) })), /* @__PURE__ */ React12.createElement(TableCell6, null, /* @__PURE__ */ React12.createElement(Typography11, { variant: "body2" }, prettyTableName(entry.table_name))), /* @__PURE__ */ React12.createElement(TableCell6, null, /* @__PURE__ */ React12.createElement(
3916
3913
  Typography11,
3917
3914
  {
3918
3915
  variant: "body2",
@@ -3922,7 +3919,7 @@ var init_AuditLog = __esm({
3922
3919
  sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 12 }
3923
3920
  },
3924
3921
  entry.object_id ? entry.object_id.slice(0, 20) + (entry.object_id.length > 20 ? "\u2026" : "") : "\u2014"
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(
3922
+ )), /* @__PURE__ */ React12.createElement(TableCell6, null, entry.changed_by ?? "\u2014")), hasDetail && /* @__PURE__ */ React12.createElement(TableRow6, { sx: { "&&:hover": { bgcolor: "transparent" } } }, /* @__PURE__ */ React12.createElement(TableCell6, { colSpan: 6, sx: { "&&": { py: 0, border: 0 } } }, /* @__PURE__ */ React12.createElement(Collapse3, { in: open, unmountOnExit: true }, /* @__PURE__ */ React12.createElement(
3926
3923
  Box11,
3927
3924
  {
3928
3925
  display: "flex",
@@ -3935,7 +3932,7 @@ var init_AuditLog = __esm({
3935
3932
  bgcolor: alpha8(theme.palette.text.primary, theme.palette.mode === "dark" ? 0.05 : 0.03)
3936
3933
  })
3937
3934
  },
3938
- entry.before_value && /* @__PURE__ */ React11.createElement(Box11, { flex: 1, minWidth: 200 }, /* @__PURE__ */ React11.createElement(
3935
+ entry.before_value && /* @__PURE__ */ React12.createElement(Box11, { flex: 1, minWidth: 200 }, /* @__PURE__ */ React12.createElement(
3939
3936
  Typography11,
3940
3937
  {
3941
3938
  variant: "caption",
@@ -3945,8 +3942,8 @@ var init_AuditLog = __esm({
3945
3942
  sx: { fontSize: 10.5, fontWeight: 700, letterSpacing: "0.07em", textTransform: "uppercase" }
3946
3943
  },
3947
3944
  "Before"
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(
3945
+ ), /* @__PURE__ */ React12.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))),
3946
+ entry.updated_values && /* @__PURE__ */ React12.createElement(Box11, { flex: 1, minWidth: 200 }, /* @__PURE__ */ React12.createElement(
3950
3947
  Typography11,
3951
3948
  {
3952
3949
  variant: "caption",
@@ -3956,7 +3953,7 @@ var init_AuditLog = __esm({
3956
3953
  sx: { fontSize: 10.5, fontWeight: 700, letterSpacing: "0.07em", textTransform: "uppercase" }
3957
3954
  },
3958
3955
  "After"
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)))
3956
+ ), /* @__PURE__ */ React12.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)))
3960
3957
  )))));
3961
3958
  };
3962
3959
  AuditLog = ({ api }) => {
@@ -3973,13 +3970,13 @@ var init_AuditLog = __esm({
3973
3970
  );
3974
3971
  const entries = value?.audit_logs ?? [];
3975
3972
  const total = value?.total ?? 0;
3976
- return /* @__PURE__ */ React11.createElement(
3973
+ return /* @__PURE__ */ React12.createElement(
3977
3974
  SectionCard,
3978
3975
  {
3979
3976
  title: "Audit Log",
3980
3977
  subtitle: loading ? "Loading\u2026" : `${total.toLocaleString()} event${total === 1 ? "" : "s"}`,
3981
3978
  flush: true,
3982
- actions: /* @__PURE__ */ React11.createElement(React11.Fragment, null, /* @__PURE__ */ React11.createElement(
3979
+ actions: /* @__PURE__ */ React12.createElement(React12.Fragment, null, /* @__PURE__ */ React12.createElement(
3983
3980
  TextField6,
3984
3981
  {
3985
3982
  size: "small",
@@ -3992,12 +3989,12 @@ var init_AuditLog = __esm({
3992
3989
  },
3993
3990
  sx: { minWidth: 150 }
3994
3991
  },
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(
3992
+ /* @__PURE__ */ React12.createElement(MenuItem5, { value: "" }, "All actions"),
3993
+ /* @__PURE__ */ React12.createElement(MenuItem5, { value: "created" }, "Created"),
3994
+ /* @__PURE__ */ React12.createElement(MenuItem5, { value: "updated" }, "Updated"),
3995
+ /* @__PURE__ */ React12.createElement(MenuItem5, { value: "deleted" }, "Deleted"),
3996
+ /* @__PURE__ */ React12.createElement(MenuItem5, { value: "blocked" }, "Blocked")
3997
+ ), /* @__PURE__ */ React12.createElement(
4001
3998
  TextField6,
4002
3999
  {
4003
4000
  size: "small",
@@ -4010,13 +4007,13 @@ var init_AuditLog = __esm({
4010
4007
  },
4011
4008
  sx: { minWidth: 150 }
4012
4009
  },
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(
4010
+ /* @__PURE__ */ React12.createElement(MenuItem5, { value: "" }, "All tables"),
4011
+ /* @__PURE__ */ React12.createElement(MenuItem5, { value: "LiteLLM_VerificationToken" }, "Key"),
4012
+ /* @__PURE__ */ React12.createElement(MenuItem5, { value: "LiteLLM_TeamTable" }, "Team"),
4013
+ /* @__PURE__ */ React12.createElement(MenuItem5, { value: "LiteLLM_TeamMembership" }, "Team member"),
4014
+ /* @__PURE__ */ React12.createElement(MenuItem5, { value: "LiteLLM_ObjectPermissionTable" }, "Team access (KB / MCP)"),
4015
+ /* @__PURE__ */ React12.createElement(MenuItem5, { value: "LiteLLM_UserTable" }, "User")
4016
+ ), /* @__PURE__ */ React12.createElement(
4020
4017
  TextField6,
4021
4018
  {
4022
4019
  size: "small",
@@ -4030,9 +4027,9 @@ var init_AuditLog = __esm({
4030
4027
  }
4031
4028
  ))
4032
4029
  },
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(
4030
+ error && /* @__PURE__ */ React12.createElement(Box11, { px: 2.5, pb: 2 }, /* @__PURE__ */ React12.createElement(Alert5, { severity: "error" }, error.message)),
4031
+ /* @__PURE__ */ React12.createElement(TableContainer6, null, /* @__PURE__ */ React12.createElement(Table6, { size: "small", sx: dataTableSx }, /* @__PURE__ */ React12.createElement(TableHead6, null, /* @__PURE__ */ React12.createElement(TableRow6, null, /* @__PURE__ */ React12.createElement(TableCell6, { sx: { width: 40 } }), /* @__PURE__ */ React12.createElement(TableCell6, null, "Time"), /* @__PURE__ */ React12.createElement(TableCell6, null, "Action"), /* @__PURE__ */ React12.createElement(TableCell6, null, "Table"), /* @__PURE__ */ React12.createElement(TableCell6, null, "Object ID"), /* @__PURE__ */ React12.createElement(TableCell6, null, "Changed By"))), /* @__PURE__ */ React12.createElement(TableBody6, null, renderAuditLogBody(loading, entries)))),
4032
+ /* @__PURE__ */ React12.createElement(
4036
4033
  TablePagination,
4037
4034
  {
4038
4035
  component: "div",
@@ -4087,7 +4084,7 @@ var LiteLLMPage_exports = {};
4087
4084
  __export(LiteLLMPage_exports, {
4088
4085
  LiteLLMPage: () => LiteLLMPage
4089
4086
  });
4090
- import React12, { useState as useState9, useCallback as useCallback2, useEffect as useEffect4, useMemo as useMemo9 } from "react";
4087
+ import React13, { useState as useState9, useCallback as useCallback2, useEffect as useEffect4, useMemo as useMemo9 } from "react";
4091
4088
  import Box12 from "@mui/material/Box";
4092
4089
  import Snackbar2 from "@mui/material/Snackbar";
4093
4090
  import Alert6 from "@mui/material/Alert";
@@ -4121,7 +4118,7 @@ var init_LiteLLMPage = __esm({
4121
4118
  "use strict";
4122
4119
  init_DashboardHeader();
4123
4120
  init_KeysTable();
4124
- init_GenerateKeyDialog();
4121
+ init_KeyFormDialog();
4125
4122
  init_ManageTeamDialog();
4126
4123
  init_UsageStats();
4127
4124
  init_LiteLLMBudgetWidget();
@@ -4179,6 +4176,8 @@ var init_LiteLLMPage = __esm({
4179
4176
  );
4180
4177
  const [snackbar, setSnackbar] = useState9(null);
4181
4178
  const [manageTeam, setManageTeam] = useState9(null);
4179
+ const [keyToEdit, setKeyToEdit] = useState9(null);
4180
+ const keyFormOpen = generateDialogOpen || !!keyToEdit;
4182
4181
  const [teamUsageCache, setTeamUsageCache] = useState9({});
4183
4182
  const [teamUsageLoading, setTeamUsageLoading] = useState9({});
4184
4183
  const { value: userInfo, loading: userLoading, error: userError } = useAsync3(
@@ -4371,14 +4370,14 @@ var init_LiteLLMPage = __esm({
4371
4370
  );
4372
4371
  const isInitialLoading = userLoading && !userInfo;
4373
4372
  if (isInitialLoading) {
4374
- return /* @__PURE__ */ React12.createElement(Box12, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: "50vh" }, /* @__PURE__ */ React12.createElement(CircularProgress6, null));
4373
+ return /* @__PURE__ */ React13.createElement(Box12, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: "50vh" }, /* @__PURE__ */ React13.createElement(CircularProgress6, null));
4375
4374
  }
4376
4375
  if (userError || !userInfo) {
4377
4376
  const isProvisioningEnabled = userError?.body?.provisioning === true;
4378
4377
  const hint = userError?.body?.hint;
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.")));
4378
+ return /* @__PURE__ */ React13.createElement(Box12, { p: 3 }, /* @__PURE__ */ React13.createElement(Paper8, { sx: { p: 3 } }, /* @__PURE__ */ React13.createElement(Typography12, { variant: "h6", gutterBottom: true }, "Account not provisioned"), /* @__PURE__ */ React13.createElement(Typography12, { color: "text.secondary", paragraph: true }, "Your Backstage account is not linked to a LiteLLM user."), hint ? /* @__PURE__ */ React13.createElement(Typography12, { variant: "body2", color: "text.secondary" }, hint) : /* @__PURE__ */ React13.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.")));
4380
4379
  }
4381
- const pageTabs = /* @__PURE__ */ React12.createElement(
4380
+ const pageTabs = /* @__PURE__ */ React13.createElement(
4382
4381
  Tabs2,
4383
4382
  {
4384
4383
  value: activeTab,
@@ -4391,13 +4390,13 @@ var init_LiteLLMPage = __esm({
4391
4390
  '& [class*="MuiTab-root"]': { minHeight: 44, textTransform: "none", fontSize: 14 }
4392
4391
  }
4393
4392
  },
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" })
4393
+ /* @__PURE__ */ React13.createElement(Tab2, { label: "Overview", value: "overview" }),
4394
+ /* @__PURE__ */ React13.createElement(Tab2, { label: "Keys", value: "keys" }),
4395
+ /* @__PURE__ */ React13.createElement(Tab2, { label: "Teams", value: "teams" }),
4396
+ /* @__PURE__ */ React13.createElement(Tab2, { label: "Models", value: "models" }),
4397
+ userInfo.can_view_audit && /* @__PURE__ */ React13.createElement(Tab2, { label: "Audit Log", value: "audit" })
4399
4398
  );
4400
- return /* @__PURE__ */ React12.createElement(Box12, { sx: { p: 3, display: "flex", flexDirection: "column", gap: 2 } }, /* @__PURE__ */ React12.createElement(
4399
+ return /* @__PURE__ */ React13.createElement(Box12, { sx: { p: 3, display: "flex", flexDirection: "column", gap: 2 } }, /* @__PURE__ */ React13.createElement(
4401
4400
  DashboardHeader,
4402
4401
  {
4403
4402
  userInfo,
@@ -4407,7 +4406,7 @@ var init_LiteLLMPage = __esm({
4407
4406
  onGenerateKeyClick: () => setGenerateDialogOpen(true),
4408
4407
  tabs: pageTabs
4409
4408
  }
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(
4409
+ ), activeTab === "overview" && /* @__PURE__ */ React13.createElement(Grid2, { container: true, spacing: 2, alignItems: "flex-start" }, /* @__PURE__ */ React13.createElement(Grid2, { item: true, xs: 12, lg: 8 }, /* @__PURE__ */ React13.createElement(
4411
4410
  UsageStats,
4412
4411
  {
4413
4412
  usage: usage ?? null,
@@ -4418,17 +4417,15 @@ var init_LiteLLMPage = __esm({
4418
4417
  loading: usageLoading,
4419
4418
  userInfo
4420
4419
  }
4421
- )), /* @__PURE__ */ React12.createElement(Grid2, { item: true, xs: 12, lg: 4 }, /* @__PURE__ */ React12.createElement(LiteLLMBudgetWidget, { compact: true, collapsible: true }))), activeTab === "keys" && /* @__PURE__ */ React12.createElement(
4420
+ )), /* @__PURE__ */ React13.createElement(Grid2, { item: true, xs: 12, lg: 4 }, /* @__PURE__ */ React13.createElement(LiteLLMBudgetWidget, { compact: true, collapsible: true }))), activeTab === "keys" && /* @__PURE__ */ React13.createElement(
4422
4421
  KeysTable,
4423
4422
  {
4424
4423
  keys: keys ?? [],
4425
- models: allowedModels,
4426
4424
  loading: keysLoading || modelsLoading,
4427
4425
  onGenerateKeyClick: () => setGenerateDialogOpen(true),
4428
- onUpdateKey: handleUpdateKey,
4426
+ onEditKey: setKeyToEdit,
4429
4427
  onBlockKey: handleBlockKey,
4430
4428
  onUnblockKey: handleUnblockKey,
4431
- onResetKeySpend: handleResetKeySpend,
4432
4429
  onDeleteKey: handleDeleteKey,
4433
4430
  onPruneExpiredKeys: handlePruneExpiredKeys
4434
4431
  }
@@ -4437,7 +4434,7 @@ var init_LiteLLMPage = __esm({
4437
4434
  for (const t of managedTeams ?? []) teamsById.set(t.team_id, t);
4438
4435
  for (const t of teams ?? []) teamsById.set(t.team_id, t);
4439
4436
  const visibleTeams = Array.from(teamsById.values());
4440
- return /* @__PURE__ */ React12.createElement(
4437
+ return /* @__PURE__ */ React13.createElement(
4441
4438
  TeamUsage,
4442
4439
  {
4443
4440
  teams: visibleTeams,
@@ -4453,27 +4450,34 @@ var init_LiteLLMPage = __esm({
4453
4450
  onCreateTeam: () => setManageTeam({ mode: "create" })
4454
4451
  }
4455
4452
  );
4456
- })(), activeTab === "models" && /* @__PURE__ */ React12.createElement(
4453
+ })(), activeTab === "models" && /* @__PURE__ */ React13.createElement(
4457
4454
  ModelsTable,
4458
4455
  {
4459
4456
  allModels: allModels ?? [],
4460
4457
  teams: teams ?? [],
4461
4458
  loading: modelsLoading
4462
4459
  }
4463
- ), activeTab === "audit" && userInfo.can_view_audit && /* @__PURE__ */ React12.createElement(AuditLog, { api }), /* @__PURE__ */ React12.createElement(
4464
- GenerateKeyDialog,
4460
+ ), activeTab === "audit" && userInfo.can_view_audit && /* @__PURE__ */ React13.createElement(AuditLog, { api }), /* @__PURE__ */ React13.createElement(
4461
+ KeyFormDialog,
4465
4462
  {
4466
- open: generateDialogOpen,
4467
- onClose: () => setGenerateDialogOpen(false),
4463
+ open: keyFormOpen,
4464
+ onClose: () => {
4465
+ setGenerateDialogOpen(false);
4466
+ setKeyToEdit(null);
4467
+ },
4468
+ mode: keyToEdit ? "edit" : "create",
4469
+ keyToEdit,
4468
4470
  keys: keys ?? [],
4469
4471
  models: allowedModels,
4470
4472
  teams: teams ?? [],
4471
4473
  username: userInfo.user_id,
4472
4474
  keyGenerationSettings: liteLlmConfig?.keyGeneration,
4473
- onGenerateKey: handleGenerateKey,
4475
+ onCreateKey: handleGenerateKey,
4476
+ onUpdateKey: handleUpdateKey,
4477
+ onResetKeySpend: handleResetKeySpend,
4474
4478
  onGetConfig: () => api.getConfig()
4475
4479
  }
4476
- ), /* @__PURE__ */ React12.createElement(
4480
+ ), /* @__PURE__ */ React13.createElement(
4477
4481
  ManageTeamDialog,
4478
4482
  {
4479
4483
  open: !!manageTeam,
@@ -4535,7 +4539,7 @@ var init_LiteLLMPage = __esm({
4535
4539
  refreshTeams();
4536
4540
  }
4537
4541
  }
4538
- ), /* @__PURE__ */ React12.createElement(
4542
+ ), /* @__PURE__ */ React13.createElement(
4539
4543
  Snackbar2,
4540
4544
  {
4541
4545
  open: !!snackbar,
@@ -4543,7 +4547,7 @@ var init_LiteLLMPage = __esm({
4543
4547
  onClose: () => setSnackbar(null),
4544
4548
  anchorOrigin: { vertical: "bottom", horizontal: "right" }
4545
4549
  },
4546
- snackbar ? /* @__PURE__ */ React12.createElement(Alert6, { severity: snackbar.severity, onClose: () => setSnackbar(null) }, snackbar.message) : void 0
4550
+ snackbar ? /* @__PURE__ */ React13.createElement(Alert6, { severity: snackbar.severity, onClose: () => setSnackbar(null) }, snackbar.message) : void 0
4547
4551
  ));
4548
4552
  };
4549
4553
  }
@@ -4551,7 +4555,7 @@ var init_LiteLLMPage = __esm({
4551
4555
 
4552
4556
  // src/plugin.tsx
4553
4557
  init_api();
4554
- import React13 from "react";
4558
+ import React14 from "react";
4555
4559
  import { TrendingUp as TrendingUpIcon } from "@mui/icons-material";
4556
4560
  import {
4557
4561
  createFrontendPlugin,
@@ -4570,10 +4574,10 @@ var liteLlmPage = PageBlueprint.make({
4570
4574
  params: {
4571
4575
  path: "/litellm",
4572
4576
  title: "LiteLLM",
4573
- icon: /* @__PURE__ */ React13.createElement(TrendingUpIcon, null),
4577
+ icon: /* @__PURE__ */ React14.createElement(TrendingUpIcon, null),
4574
4578
  loader: async () => {
4575
4579
  const { LiteLLMPage: LiteLLMPage2 } = await Promise.resolve().then(() => (init_LiteLLMPage(), LiteLLMPage_exports));
4576
- return /* @__PURE__ */ React13.createElement(LiteLLMPage2, null);
4580
+ return /* @__PURE__ */ React14.createElement(LiteLLMPage2, null);
4577
4581
  }
4578
4582
  }
4579
4583
  });
@@ -4592,7 +4596,7 @@ init_TeamUsage();
4592
4596
  // src/components/LiteLLMHomeWidget.tsx
4593
4597
  init_api();
4594
4598
  init_format();
4595
- import React14, { useState as useState10, useEffect as useEffect5 } from "react";
4599
+ import React15, { useState as useState10, useEffect as useEffect5 } from "react";
4596
4600
  import Paper9 from "@mui/material/Paper";
4597
4601
  import Box13 from "@mui/material/Box";
4598
4602
  import Typography13 from "@mui/material/Typography";
@@ -4616,7 +4620,7 @@ function presetToDateRange(preset) {
4616
4620
  }
4617
4621
  return { start, end };
4618
4622
  }
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));
4623
+ var Kpi = ({ label, value }) => /* @__PURE__ */ React15.createElement(Box13, null, /* @__PURE__ */ React15.createElement(Typography13, { variant: "caption", color: "text.secondary", display: "block" }, label), /* @__PURE__ */ React15.createElement(Typography13, { variant: "subtitle1", fontWeight: 600 }, value));
4620
4624
  var LiteLLMHomeWidget = ({
4621
4625
  defaultPeriod = "7d",
4622
4626
  title = "LiteLLM Usage"
@@ -4665,17 +4669,17 @@ var LiteLLMHomeWidget = ({
4665
4669
  spend: d.spend
4666
4670
  }));
4667
4671
  const hasSparkline = dailyData.length > 0;
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(
4672
+ return /* @__PURE__ */ React15.createElement(Paper9, { sx: { p: 2 } }, /* @__PURE__ */ React15.createElement(Box13, { display: "flex", justifyContent: "space-between", alignItems: "center", mb: 1.5 }, /* @__PURE__ */ React15.createElement(Typography13, { variant: "h6" }, title), /* @__PURE__ */ React15.createElement(FormControl2, { size: "small", sx: { minWidth: 90 } }, /* @__PURE__ */ React15.createElement(
4669
4673
  Select2,
4670
4674
  {
4671
4675
  value: period,
4672
4676
  onChange: (e) => setPeriod(e.target.value),
4673
4677
  displayEmpty: true
4674
4678
  },
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(
4679
+ /* @__PURE__ */ React15.createElement(MenuItem6, { value: "today" }, "Today"),
4680
+ /* @__PURE__ */ React15.createElement(MenuItem6, { value: "7d" }, "7d"),
4681
+ /* @__PURE__ */ React15.createElement(MenuItem6, { value: "30d" }, "30d")
4682
+ ))), loading && /* @__PURE__ */ React15.createElement(Box13, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: 120 }, /* @__PURE__ */ React15.createElement(CircularProgress7, { size: 32 })), !loading && totalFailure && /* @__PURE__ */ React15.createElement(Alert7, { severity: "error", sx: { mt: 1 } }, usageError ?? "Failed to load usage data"), !loading && !totalFailure && /* @__PURE__ */ React15.createElement(React15.Fragment, null, partialFailure && /* @__PURE__ */ React15.createElement(Alert7, { severity: "warning", sx: { mt: 1, mb: 1 } }, usageError ? `Usage data unavailable (${usageError}).` : "", keysError ? ` Key list unavailable (${keysError}).` : "", " Showing what loaded."), /* @__PURE__ */ React15.createElement(Grid3, { container: true, spacing: 2, sx: { mb: hasSparkline ? 1.5 : 0 } }, /* @__PURE__ */ React15.createElement(Grid3, { item: true, xs: 6 }, /* @__PURE__ */ React15.createElement(Kpi, { label: "USD Spent", value: fmtUsd(usage?.total_spend ?? 0) })), /* @__PURE__ */ React15.createElement(Grid3, { item: true, xs: 6 }, /* @__PURE__ */ React15.createElement(Kpi, { label: "Tokens In", value: fmtInt(usage?.prompt_tokens ?? 0) })), /* @__PURE__ */ React15.createElement(Grid3, { item: true, xs: 6 }, /* @__PURE__ */ React15.createElement(Kpi, { label: "Tokens Out", value: fmtInt(usage?.completion_tokens ?? 0) })), /* @__PURE__ */ React15.createElement(Grid3, { item: true, xs: 6 }, /* @__PURE__ */ React15.createElement(Kpi, { label: "Keys", value: fmtInt(keys.length) }))), hasSparkline && /* @__PURE__ */ React15.createElement(Box13, { height: 120 }, /* @__PURE__ */ React15.createElement(ResponsiveContainer3, { width: "100%", height: "100%" }, /* @__PURE__ */ React15.createElement(AreaChart3, { data: dailyData, margin: { top: 4, right: 0, bottom: 0, left: 0 } }, /* @__PURE__ */ React15.createElement(
4679
4683
  Area3,
4680
4684
  {
4681
4685
  type: "monotone",
@@ -4696,9 +4700,10 @@ init_LiteLLMBudgetWidget();
4696
4700
  init_api();
4697
4701
  init_format();
4698
4702
  init_ui();
4703
+ init_GenerateKeyButton();
4699
4704
  init_BudgetLimitList();
4700
4705
  init_budget();
4701
- import React15, { useCallback as useCallback3, useEffect as useEffect6, useMemo as useMemo10, useState as useState11 } from "react";
4706
+ import React16, { useCallback as useCallback3, useEffect as useEffect6, useMemo as useMemo10, useState as useState11 } from "react";
4702
4707
  import Paper10 from "@mui/material/Paper";
4703
4708
  import Box14 from "@mui/material/Box";
4704
4709
  import Button6 from "@mui/material/Button";
@@ -4707,8 +4712,9 @@ import CircularProgress8 from "@mui/material/CircularProgress";
4707
4712
  import Alert8 from "@mui/material/Alert";
4708
4713
  import Collapse4 from "@mui/material/Collapse";
4709
4714
  import { ExpandMore as ExpandMore4 } from "@mui/icons-material";
4715
+ import { AreaChart as AreaChart4, Area as Area4, ResponsiveContainer as ResponsiveContainer4, Tooltip as Tooltip4 } from "recharts";
4710
4716
  import { useApi as useApi5 } from "@backstage/core-plugin-api";
4711
- import { Link as Link2 } from "@backstage/core-components";
4717
+ import { Link as Link3 } from "@backstage/core-components";
4712
4718
  var MODULE_PATH = "/litellm";
4713
4719
  var LEVEL = {
4714
4720
  key: { name: "Key", none: "No key budget" },
@@ -4717,7 +4723,7 @@ var LEVEL = {
4717
4723
  };
4718
4724
  var DEFAULT_CTAS = ["new-key", "module", "all-limits"];
4719
4725
  var CTA_LABELS = {
4720
- "new-key": "New key",
4726
+ "new-key": "Generate New Key",
4721
4727
  module: "Open module",
4722
4728
  "all-limits": "All limits"
4723
4729
  };
@@ -4743,7 +4749,7 @@ var LevelGauge = ({
4743
4749
  const limit = gauge.limit;
4744
4750
  const tone = limit ? budgetTone2(limit.pct) : "neutral";
4745
4751
  const extra = gauge.kind === "key" ? Math.max(0, gauge.count - 1) : 0;
4746
- return /* @__PURE__ */ React15.createElement(
4752
+ return /* @__PURE__ */ React16.createElement(
4747
4753
  Box14,
4748
4754
  {
4749
4755
  sx: {
@@ -4754,7 +4760,7 @@ var LevelGauge = ({
4754
4760
  flex: 1
4755
4761
  }
4756
4762
  },
4757
- /* @__PURE__ */ React15.createElement(
4763
+ /* @__PURE__ */ React16.createElement(
4758
4764
  Gauge,
4759
4765
  {
4760
4766
  value: limit?.pct ?? 0,
@@ -4763,7 +4769,7 @@ var LevelGauge = ({
4763
4769
  label: limit ? `${Math.round(limit.pct)}%` : "\u2014"
4764
4770
  }
4765
4771
  ),
4766
- /* @__PURE__ */ React15.createElement(
4772
+ /* @__PURE__ */ React16.createElement(
4767
4773
  Typography14,
4768
4774
  {
4769
4775
  sx: (theme) => ({
@@ -4777,7 +4783,7 @@ var LevelGauge = ({
4777
4783
  },
4778
4784
  name
4779
4785
  ),
4780
- limit ? /* @__PURE__ */ React15.createElement(React15.Fragment, null, /* @__PURE__ */ React15.createElement(
4786
+ limit ? /* @__PURE__ */ React16.createElement(React16.Fragment, null, /* @__PURE__ */ React16.createElement(
4781
4787
  Typography14,
4782
4788
  {
4783
4789
  variant: "caption",
@@ -4793,7 +4799,7 @@ var LevelGauge = ({
4793
4799
  }
4794
4800
  },
4795
4801
  limit.label
4796
- ), /* @__PURE__ */ React15.createElement(
4802
+ ), /* @__PURE__ */ React16.createElement(
4797
4803
  Typography14,
4798
4804
  {
4799
4805
  variant: "caption",
@@ -4805,10 +4811,10 @@ var LevelGauge = ({
4805
4811
  }
4806
4812
  },
4807
4813
  spendCaption(limit)
4808
- ), extra > 0 ? /* @__PURE__ */ React15.createElement(
4814
+ ), extra > 0 ? /* @__PURE__ */ React16.createElement(
4809
4815
  Typography14,
4810
4816
  {
4811
- component: Link2,
4817
+ component: Link3,
4812
4818
  to: keysHref,
4813
4819
  variant: "caption",
4814
4820
  sx: {
@@ -4821,7 +4827,7 @@ var LevelGauge = ({
4821
4827
  "+",
4822
4828
  extra,
4823
4829
  " more"
4824
- ) : /* @__PURE__ */ React15.createElement(Typography14, { variant: "caption", color: "text.secondary", sx: { fontSize: 10.5 } }, resetLabel(limit))) : /* @__PURE__ */ React15.createElement(
4830
+ ) : /* @__PURE__ */ React16.createElement(Typography14, { variant: "caption", color: "text.secondary", sx: { fontSize: 10.5 } }, resetLabel(limit))) : /* @__PURE__ */ React16.createElement(
4825
4831
  Typography14,
4826
4832
  {
4827
4833
  variant: "caption",
@@ -4832,6 +4838,125 @@ var LevelGauge = ({
4832
4838
  )
4833
4839
  );
4834
4840
  };
4841
+ function monthToDateRange(now = /* @__PURE__ */ new Date()) {
4842
+ const toDay = (d) => `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(
4843
+ d.getDate()
4844
+ ).padStart(2, "0")}`;
4845
+ const start = new Date(now.getFullYear(), now.getMonth(), 1);
4846
+ return { startDate: toDay(start), endDate: toDay(now) };
4847
+ }
4848
+ var UsageMiniChart = ({ data, totalTokens, loading, height }) => {
4849
+ const renderPlot = () => {
4850
+ if (loading) {
4851
+ return /* @__PURE__ */ React16.createElement(Box14, { display: "flex", justifyContent: "center", alignItems: "center", height: "100%" }, /* @__PURE__ */ React16.createElement(CircularProgress8, { size: 20 }));
4852
+ }
4853
+ if (data.length === 0) {
4854
+ return /* @__PURE__ */ React16.createElement(
4855
+ Box14,
4856
+ {
4857
+ display: "flex",
4858
+ justifyContent: "center",
4859
+ alignItems: "center",
4860
+ height: "100%",
4861
+ sx: (theme) => ({ color: theme.palette.text.secondary, fontSize: 18 })
4862
+ },
4863
+ "\u2014"
4864
+ );
4865
+ }
4866
+ return /* @__PURE__ */ React16.createElement(ResponsiveContainer4, { width: "100%", height: "100%" }, /* @__PURE__ */ React16.createElement(AreaChart4, { data, margin: { top: 4, right: 0, bottom: 0, left: 0 } }, /* @__PURE__ */ React16.createElement(Tooltip4, { content: /* @__PURE__ */ React16.createElement(ChartTooltip, { valueFormatter: fmtInt }) }), /* @__PURE__ */ React16.createElement(
4867
+ Area4,
4868
+ {
4869
+ type: "monotone",
4870
+ dataKey: "input",
4871
+ name: "Input",
4872
+ stackId: "tok",
4873
+ stroke: SERIES.input,
4874
+ strokeWidth: 1.5,
4875
+ fill: SERIES.input,
4876
+ fillOpacity: 0.25,
4877
+ dot: false,
4878
+ isAnimationActive: false
4879
+ }
4880
+ ), /* @__PURE__ */ React16.createElement(
4881
+ Area4,
4882
+ {
4883
+ type: "monotone",
4884
+ dataKey: "output",
4885
+ name: "Output",
4886
+ stackId: "tok",
4887
+ stroke: SERIES.output,
4888
+ strokeWidth: 1.5,
4889
+ fill: SERIES.output,
4890
+ fillOpacity: 0.25,
4891
+ dot: false,
4892
+ isAnimationActive: false
4893
+ }
4894
+ )));
4895
+ };
4896
+ const renderCaption = () => {
4897
+ if (loading) {
4898
+ return /* @__PURE__ */ React16.createElement(Typography14, { variant: "caption", color: "text.secondary", sx: { mt: 0.25, fontSize: 11 } }, "\u2026");
4899
+ }
4900
+ if (data.length === 0) {
4901
+ return /* @__PURE__ */ React16.createElement(
4902
+ Typography14,
4903
+ {
4904
+ variant: "caption",
4905
+ color: "text.secondary",
4906
+ sx: { mt: 0.25, fontSize: 10.5, textAlign: "center" }
4907
+ },
4908
+ "No usage this month"
4909
+ );
4910
+ }
4911
+ return /* @__PURE__ */ React16.createElement(React16.Fragment, null, /* @__PURE__ */ React16.createElement(
4912
+ Typography14,
4913
+ {
4914
+ variant: "caption",
4915
+ title: `${fmtInt(totalTokens)} tokens month to date`,
4916
+ sx: {
4917
+ mt: 0.25,
4918
+ fontSize: 11,
4919
+ fontWeight: 600,
4920
+ maxWidth: "100%",
4921
+ overflow: "hidden",
4922
+ textOverflow: "ellipsis",
4923
+ whiteSpace: "nowrap",
4924
+ fontVariantNumeric: "tabular-nums"
4925
+ }
4926
+ },
4927
+ fmtCompact(totalTokens),
4928
+ " tokens"
4929
+ ), /* @__PURE__ */ React16.createElement(Typography14, { variant: "caption", color: "text.secondary", sx: { fontSize: 10.5 } }, "month to date"));
4930
+ };
4931
+ return /* @__PURE__ */ React16.createElement(
4932
+ Box14,
4933
+ {
4934
+ sx: {
4935
+ display: "flex",
4936
+ flexDirection: "column",
4937
+ alignItems: "center",
4938
+ minWidth: 0,
4939
+ flex: 1
4940
+ }
4941
+ },
4942
+ /* @__PURE__ */ React16.createElement(Box14, { sx: { height, width: "100%" } }, renderPlot()),
4943
+ /* @__PURE__ */ React16.createElement(
4944
+ Typography14,
4945
+ {
4946
+ sx: (theme) => ({
4947
+ mt: 0.75,
4948
+ fontSize: 10.5,
4949
+ fontWeight: 700,
4950
+ letterSpacing: "0.08em",
4951
+ textTransform: "uppercase",
4952
+ color: theme.palette.text.secondary
4953
+ })
4954
+ },
4955
+ "Usage"
4956
+ ),
4957
+ renderCaption()
4958
+ );
4959
+ };
4835
4960
  var LiteLLMBudgetGauges = ({
4836
4961
  title = "Budget",
4837
4962
  size = 72,
@@ -4851,6 +4976,9 @@ var LiteLLMBudgetGauges = ({
4851
4976
  const [user, setUser] = useState11(null);
4852
4977
  const [teams, setTeams] = useState11([]);
4853
4978
  const [keys, setKeys] = useState11([]);
4979
+ const [usageLoading, setUsageLoading] = useState11(true);
4980
+ const [dailyTokens, setDailyTokens] = useState11([]);
4981
+ const [mtdTokens, setMtdTokens] = useState11(0);
4854
4982
  const isControlled = expanded !== void 0;
4855
4983
  const [uncontrolledExpanded, setUncontrolledExpanded] = useState11(defaultExpanded);
4856
4984
  const isExpanded = isControlled ? expanded : uncontrolledExpanded;
@@ -4863,23 +4991,43 @@ var LiteLLMBudgetGauges = ({
4863
4991
  useEffect6(() => {
4864
4992
  let cancelled = false;
4865
4993
  setLoading(true);
4994
+ setUsageLoading(true);
4866
4995
  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);
4996
+ const { startDate, endDate } = monthToDateRange();
4997
+ Promise.allSettled([
4998
+ api.getUserInfo(),
4999
+ api.getTeams(),
5000
+ api.listKeys(),
5001
+ api.getUsage(startDate, endDate)
5002
+ ]).then(([userResult, teamsResult, keysResult, usageResult]) => {
5003
+ if (cancelled) return;
5004
+ if (userResult.status === "fulfilled") {
5005
+ setUser(userResult.value);
5006
+ } else {
5007
+ setUser(null);
5008
+ setError(
5009
+ userResult.reason?.message ?? "Failed to load your LiteLLM profile"
5010
+ );
4881
5011
  }
4882
- );
5012
+ setTeams(teamsResult.status === "fulfilled" ? teamsResult.value : []);
5013
+ setKeys(keysResult.status === "fulfilled" ? keysResult.value : []);
5014
+ setLoading(false);
5015
+ if (usageResult.status === "fulfilled") {
5016
+ const daily = usageResult.value.daily_usage ?? [];
5017
+ setDailyTokens(
5018
+ daily.map((d) => ({
5019
+ date: d.date,
5020
+ input: d.prompt_tokens,
5021
+ output: d.completion_tokens
5022
+ }))
5023
+ );
5024
+ setMtdTokens(usageResult.value.total_tokens ?? 0);
5025
+ } else {
5026
+ setDailyTokens([]);
5027
+ setMtdTokens(0);
5028
+ }
5029
+ setUsageLoading(false);
5030
+ });
4883
5031
  return () => {
4884
5032
  cancelled = true;
4885
5033
  };
@@ -4908,28 +5056,18 @@ var LiteLLMBudgetGauges = ({
4908
5056
  const key = `${cta.kind}-${index}`;
4909
5057
  switch (cta.kind) {
4910
5058
  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
- );
5059
+ return onCreateKey ? /* @__PURE__ */ React16.createElement(GenerateKeyButton, { key, size: "small", label, onClick: onCreateKey }) : /* @__PURE__ */ React16.createElement(GenerateKeyButton, { key, size: "small", label, to: `${moduleHref}?generate=1` });
4922
5060
  case "module":
4923
- return /* @__PURE__ */ React15.createElement(Button6, { key, size: "small", variant: "outlined", component: Link2, to: moduleHref }, label);
5061
+ return /* @__PURE__ */ React16.createElement(Button6, { key, size: "small", variant: "outlined", component: Link3, to: moduleHref }, label);
4924
5062
  case "all-limits":
4925
- return /* @__PURE__ */ React15.createElement(
5063
+ return /* @__PURE__ */ React16.createElement(
4926
5064
  Button6,
4927
5065
  {
4928
5066
  key,
4929
5067
  size: "small",
4930
5068
  variant: "text",
4931
5069
  onClick: toggleExpanded,
4932
- endIcon: /* @__PURE__ */ React15.createElement(
5070
+ endIcon: /* @__PURE__ */ React16.createElement(
4933
5071
  ExpandMore4,
4934
5072
  {
4935
5073
  sx: {
@@ -4947,14 +5085,22 @@ var LiteLLMBudgetGauges = ({
4947
5085
  }
4948
5086
  };
4949
5087
  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(
5088
+ return /* @__PURE__ */ React16.createElement(Paper10, { sx: { p: 2 } }, /* @__PURE__ */ React16.createElement(Box14, { sx: { display: "flex", alignItems: "center", gap: 1, mb: 1.5, minWidth: 0 } }, /* @__PURE__ */ React16.createElement(Typography14, { variant: "h6", lineHeight: 1.2, sx: { minWidth: 0, flex: 1 } }, title), !loading && !error && /* @__PURE__ */ React16.createElement(
4951
5089
  StatusPill,
4952
5090
  {
4953
5091
  label: summaryText,
4954
5092
  tone: budgetTone2(headline.closest?.pct ?? 0),
4955
5093
  dot: headline.count > 0
4956
5094
  }
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(
5095
+ )), loading && /* @__PURE__ */ React16.createElement(Box14, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: 110 }, /* @__PURE__ */ React16.createElement(CircularProgress8, { size: 28 })), !loading && error && /* @__PURE__ */ React16.createElement(Alert8, { severity: "error", sx: { mt: 0.5 } }, error), !loading && !error && /* @__PURE__ */ React16.createElement(React16.Fragment, null, /* @__PURE__ */ React16.createElement(Box14, { sx: { display: "flex", alignItems: "flex-start", gap: 1 } }, gauges.map((gauge) => /* @__PURE__ */ React16.createElement(LevelGauge, { key: gauge.kind, gauge, size, keysHref: keysTabHref })), /* @__PURE__ */ React16.createElement(
5096
+ UsageMiniChart,
5097
+ {
5098
+ data: dailyTokens,
5099
+ totalTokens: mtdTokens,
5100
+ loading: usageLoading,
5101
+ height: size
5102
+ }
5103
+ )), /* @__PURE__ */ React16.createElement(Collapse4, { in: isExpanded, unmountOnExit: true }, /* @__PURE__ */ React16.createElement(
4958
5104
  Box14,
4959
5105
  {
4960
5106
  sx: {
@@ -4965,7 +5111,7 @@ var LiteLLMBudgetGauges = ({
4965
5111
  overflowY: "auto"
4966
5112
  }
4967
5113
  },
4968
- /* @__PURE__ */ React15.createElement(LimitListPanel, null, /* @__PURE__ */ React15.createElement(
5114
+ /* @__PURE__ */ React16.createElement(LimitListPanel, null, /* @__PURE__ */ React16.createElement(
4969
5115
  BudgetLimitList,
4970
5116
  {
4971
5117
  limits: expandedLimits,
@@ -4973,7 +5119,7 @@ var LiteLLMBudgetGauges = ({
4973
5119
  keysHref: keysTabHref
4974
5120
  }
4975
5121
  ))
4976
- ))), hasFooter && /* @__PURE__ */ React15.createElement(
5122
+ ))), hasFooter && /* @__PURE__ */ React16.createElement(
4977
5123
  Box14,
4978
5124
  {
4979
5125
  sx: (theme) => ({
@@ -4992,13 +5138,39 @@ var LiteLLMBudgetGauges = ({
4992
5138
  };
4993
5139
 
4994
5140
  // src/index.ts
4995
- init_GenerateKeyDialog();
5141
+ init_KeyFormDialog();
5142
+ init_GenerateKeyButton();
5143
+
5144
+ // src/components/GenerateKeyDialog.tsx
5145
+ init_KeyFormDialog();
5146
+ import React17 from "react";
5147
+ var GenerateKeyDialog = (props) => /* @__PURE__ */ React17.createElement(
5148
+ KeyFormDialog,
5149
+ {
5150
+ open: props.open,
5151
+ onClose: props.onClose,
5152
+ mode: "create",
5153
+ keys: props.keys,
5154
+ models: props.models,
5155
+ teams: props.teams,
5156
+ username: props.username,
5157
+ keyGenerationSettings: props.keyGenerationSettings,
5158
+ onCreateKey: props.onGenerateKey,
5159
+ onUpdateKey: async () => {
5160
+ },
5161
+ onGetConfig: props.onGetConfig
5162
+ }
5163
+ );
5164
+
5165
+ // src/index.ts
4996
5166
  init_ManageTeamDialog();
4997
5167
  init_api();
4998
5168
  init_permissions();
4999
5169
  export {
5000
5170
  DashboardHeader,
5171
+ GenerateKeyButton,
5001
5172
  GenerateKeyDialog,
5173
+ KeyFormDialog,
5002
5174
  KeysTable,
5003
5175
  LiteLLMBudgetGauges,
5004
5176
  LiteLLMBudgetWidget,