@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/README.md +3 -2
- package/dist/components/GenerateKeyButton.d.ts +18 -0
- package/dist/components/GenerateKeyDialog.d.ts +8 -7
- package/dist/components/KeyFormDialog.d.ts +42 -0
- package/dist/components/KeysTable.d.ts +4 -5
- package/dist/components/LiteLLMBudgetGauges.d.ts +23 -10
- package/dist/index.cjs.js +884 -712
- package/dist/index.cjs.js.map +4 -4
- package/dist/index.d.ts +4 -0
- package/dist/index.esm.js +758 -586
- package/dist/index.esm.js.map +4 -4
- package/dist/types.d.ts +4 -1
- package/package.json +1 -1
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
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
955
|
-
|
|
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__ */
|
|
978
|
-
/* @__PURE__ */
|
|
979
|
-
/* @__PURE__ */
|
|
980
|
-
/* @__PURE__ */
|
|
981
|
-
/* @__PURE__ */
|
|
982
|
-
), tabs && /* @__PURE__ */
|
|
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
|
|
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,
|
|
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__ */
|
|
1072
|
+
return /* @__PURE__ */ React4.createElement(Typography3, { variant: "body2", color: "text.secondary" }, "Never");
|
|
1025
1073
|
}
|
|
1026
1074
|
if (status === "ok") {
|
|
1027
|
-
return /* @__PURE__ */
|
|
1075
|
+
return /* @__PURE__ */ React4.createElement(Typography3, { variant: "body2", color: "text.secondary" }, formatDate(expiresAt));
|
|
1028
1076
|
}
|
|
1029
|
-
return /* @__PURE__ */
|
|
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__ */
|
|
1045
|
-
if (blocked) return /* @__PURE__ */
|
|
1046
|
-
return /* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
1265
|
-
|
|
1239
|
+
action: /* @__PURE__ */ React4.createElement(
|
|
1240
|
+
GenerateKeyButton,
|
|
1266
1241
|
{
|
|
1267
|
-
variant: "contained",
|
|
1268
|
-
color: "primary",
|
|
1269
1242
|
size: "small",
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
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__ */
|
|
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__ */
|
|
1296
|
-
/* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
1291
|
+
/* @__PURE__ */ React4.createElement(ContentCopy, { sx: { fontSize: 15 } })
|
|
1322
1292
|
))),
|
|
1323
|
-
/* @__PURE__ */
|
|
1324
|
-
/* @__PURE__ */
|
|
1325
|
-
/* @__PURE__ */
|
|
1326
|
-
/* @__PURE__ */
|
|
1327
|
-
/* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
1344
|
-
), /* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
1332
|
+
/* @__PURE__ */ React4.createElement(Delete, { fontSize: "small" })
|
|
1363
1333
|
)))
|
|
1364
1334
|
);
|
|
1365
1335
|
});
|
|
1366
1336
|
};
|
|
1367
|
-
return /* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
1356
|
+
), expiredKeys.length > 0 && /* @__PURE__ */ React4.createElement(
|
|
1387
1357
|
Button2,
|
|
1388
1358
|
{
|
|
1389
1359
|
variant: "outlined",
|
|
1390
1360
|
color: "inherit",
|
|
1391
|
-
startIcon: /* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
1419
|
-
), /* @__PURE__ */
|
|
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__ */
|
|
1496
|
-
))), /* @__PURE__ */
|
|
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__ */
|
|
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/
|
|
1525
|
-
import
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
1665
|
-
var
|
|
1666
|
-
"src/components/
|
|
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
|
-
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
1609
|
+
/* @__PURE__ */ React5.createElement(ContentCopy2, { fontSize: "small" })
|
|
1711
1610
|
),
|
|
1712
|
-
/* @__PURE__ */
|
|
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__ */
|
|
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
|
-
|
|
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
|
-
|
|
1639
|
+
onCreateKey,
|
|
1640
|
+
onUpdateKey,
|
|
1641
|
+
onResetKeySpend,
|
|
1739
1642
|
onGetConfig
|
|
1740
1643
|
}) => {
|
|
1741
|
-
const
|
|
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
|
-
|
|
1751
|
-
|
|
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
|
|
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 = !
|
|
1766
|
-
const teamError = teamRequired && !
|
|
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
|
|
1769
|
-
const aliasDuplicate = !!
|
|
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
|
-
...
|
|
1783
|
-
max_budget: unlimitedBudget ? null :
|
|
1714
|
+
...generateForm,
|
|
1715
|
+
max_budget: unlimitedBudget ? null : generateForm.max_budget
|
|
1784
1716
|
};
|
|
1785
|
-
const response = await
|
|
1786
|
-
const model =
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
1816
|
-
const outCost =
|
|
1817
|
-
const ctx =
|
|
1818
|
-
return /* @__PURE__ */
|
|
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__ */
|
|
1823
|
-
|
|
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({
|
|
1796
|
+
setFormData({ team_id: team?.team_id, models: restrictedModels });
|
|
1834
1797
|
},
|
|
1835
|
-
|
|
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__ */
|
|
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
|
-
|
|
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__ */
|
|
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__ */
|
|
1879
|
-
), newKeySnippets && /* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
1903
|
-
)), newKeySnippets && /* @__PURE__ */
|
|
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:
|
|
1903
|
+
value: currentAlias,
|
|
1908
1904
|
onChange: (e) => {
|
|
1909
|
-
|
|
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__ */
|
|
1915
|
+
), !isCreate ? null : /* @__PURE__ */ React5.createElement(
|
|
1919
1916
|
TextField2,
|
|
1920
1917
|
{
|
|
1921
1918
|
select: true,
|
|
1922
1919
|
label: "Duration",
|
|
1923
|
-
value:
|
|
1924
|
-
onChange: (e) => setFormData({
|
|
1920
|
+
value: generateForm.duration || "30d",
|
|
1921
|
+
onChange: (e) => setFormData({ duration: e.target.value }),
|
|
1925
1922
|
fullWidth: true
|
|
1926
1923
|
},
|
|
1927
|
-
/* @__PURE__ */
|
|
1928
|
-
/* @__PURE__ */
|
|
1929
|
-
/* @__PURE__ */
|
|
1930
|
-
/* @__PURE__ */
|
|
1931
|
-
/* @__PURE__ */
|
|
1932
|
-
), renderTeamField(), availableModels.length > 0 && /* @__PURE__ */
|
|
1933
|
-
|
|
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({
|
|
1941
|
-
renderOption: (props, m) => /* @__PURE__ */
|
|
1942
|
-
renderInput: (params) => /* @__PURE__ */
|
|
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__ */
|
|
1949
|
+
), allowUnlimitedBudget && /* @__PURE__ */ React5.createElement(
|
|
1953
1950
|
FormControlLabel,
|
|
1954
1951
|
{
|
|
1955
|
-
control: /* @__PURE__ */
|
|
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({
|
|
1959
|
+
setFormData({ max_budget: null });
|
|
1963
1960
|
} else {
|
|
1964
|
-
setFormData({
|
|
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__ */
|
|
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({
|
|
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__ */
|
|
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({
|
|
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__ */
|
|
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({
|
|
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__ */
|
|
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: !
|
|
2007
|
+
disabled: !canSubmit
|
|
2011
2008
|
},
|
|
2012
|
-
submitting ? /* @__PURE__ */
|
|
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
|
|
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
|
|
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__ */
|
|
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__ */
|
|
2298
|
-
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
2324
|
+
), allowUnlimitedBudget && /* @__PURE__ */ React6.createElement(
|
|
2328
2325
|
FormControlLabel2,
|
|
2329
2326
|
{
|
|
2330
|
-
control: /* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
2345
|
-
), showMembers && /* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
2370
|
-
))))))), /* @__PURE__ */
|
|
2371
|
-
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
2430
|
-
|
|
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__ */
|
|
2435
|
+
renderInput: (params) => /* @__PURE__ */ React6.createElement(TextField3, { ...params, label: "Attached knowledge bases" })
|
|
2439
2436
|
}
|
|
2440
|
-
), /* @__PURE__ */
|
|
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__ */
|
|
2449
|
-
|
|
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__ */
|
|
2454
|
+
renderInput: (params) => /* @__PURE__ */ React6.createElement(TextField3, { ...params, label: "Attached MCP servers" })
|
|
2458
2455
|
}
|
|
2459
|
-
), /* @__PURE__ */
|
|
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__ */
|
|
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
|
|
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__ */
|
|
2520
|
+
ChartSkeleton = ({ height = 240 }) => /* @__PURE__ */ React7.createElement(Skeleton3, { variant: "rounded", height });
|
|
2524
2521
|
ChartOrFallback = ({ loading, empty, height = 240, children }) => {
|
|
2525
|
-
if (loading) return /* @__PURE__ */
|
|
2526
|
-
if (empty) return /* @__PURE__ */
|
|
2527
|
-
return /* @__PURE__ */
|
|
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__ */
|
|
2527
|
+
if (requests === 0) return /* @__PURE__ */ React7.createElement(Typography6, { variant: "body2", color: "text.secondary" }, "\u2014");
|
|
2531
2528
|
const tone = rateTone(rate);
|
|
2532
|
-
return /* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
2704
|
+
Object.keys(PRESET_LABELS).map((p) => /* @__PURE__ */ React7.createElement(MenuItem3, { key: p, value: p }, PRESET_LABELS[p]))
|
|
2708
2705
|
);
|
|
2709
|
-
return /* @__PURE__ */
|
|
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__ */
|
|
2717
|
-
/* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
2753
|
-
models.map((m) => /* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
2757
|
+
content: /* @__PURE__ */ React7.createElement(ChartTooltip, { valueFormatter: fmtUsd, hideZero: true })
|
|
2761
2758
|
}
|
|
2762
|
-
), topSpendModels.map((m) => /* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
2775
|
+
content: /* @__PURE__ */ React7.createElement(ChartTooltip, { valueFormatter: (v) => `${v}%` })
|
|
2779
2776
|
}
|
|
2780
|
-
), /* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
2802
|
+
), /* @__PURE__ */ React7.createElement(
|
|
2806
2803
|
Tooltip,
|
|
2807
2804
|
{
|
|
2808
2805
|
cursor: chart.cursor,
|
|
2809
|
-
content: /* @__PURE__ */
|
|
2806
|
+
content: /* @__PURE__ */ React7.createElement(ChartTooltip, { valueFormatter: fmtUsd })
|
|
2810
2807
|
}
|
|
2811
|
-
), maxBudget > 0 && /* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
2840
|
-
))), /* @__PURE__ */
|
|
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__ */
|
|
2848
|
-
))), /* @__PURE__ */
|
|
2849
|
-
tab === "keys" && /* @__PURE__ */
|
|
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__ */
|
|
2857
|
-
))), /* @__PURE__ */
|
|
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
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
3051
|
-
/* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
3076
|
+
return /* @__PURE__ */ React8.createElement(Typography7, { variant: "caption", color: "text.secondary" }, text);
|
|
3080
3077
|
}
|
|
3081
|
-
return /* @__PURE__ */
|
|
3078
|
+
return /* @__PURE__ */ React8.createElement(Link2, { to: href, variant: "caption" }, text, " \u2192");
|
|
3082
3079
|
};
|
|
3083
|
-
BudgetLimitList = ({ limits, hiddenKeyCount = 0, keysHref }) => /* @__PURE__ */
|
|
3084
|
-
LimitListPanel = ({ children }) => /* @__PURE__ */
|
|
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
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
3168
|
-
EmptyLimitCard = ({ children }) => /* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
3211
|
-
summary.keys.map((k) => /* @__PURE__ */
|
|
3212
|
-
summary.hiddenBudgetedKeys > 0 && /* @__PURE__ */
|
|
3213
|
-
), /* @__PURE__ */
|
|
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__ */
|
|
3222
|
-
), /* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
3281
|
-
return /* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
3314
|
+
/* @__PURE__ */ React9.createElement(ExpandMore2, null)
|
|
3318
3315
|
)
|
|
3319
|
-
) : /* @__PURE__ */
|
|
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
|
|
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
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
3407
|
+
content: /* @__PURE__ */ React10.createElement(ChartTooltip, { valueFormatter: (v) => `$${v.toFixed(4)}` })
|
|
3411
3408
|
}
|
|
3412
|
-
), /* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
3450
|
-
), /* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
3481
|
-
)), /* @__PURE__ */
|
|
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__ */
|
|
3493
|
-
/* @__PURE__ */
|
|
3494
|
-
/* @__PURE__ */
|
|
3495
|
-
/* @__PURE__ */
|
|
3496
|
-
/* @__PURE__ */
|
|
3497
|
-
/* @__PURE__ */
|
|
3498
|
-
), hidden && /* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
3734
|
-
)), /* @__PURE__ */
|
|
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__ */
|
|
3741
|
+
startAdornment: /* @__PURE__ */ React11.createElement(InputAdornment2, { position: "start" }, /* @__PURE__ */ React11.createElement(Search2, { fontSize: "small" }))
|
|
3745
3742
|
}
|
|
3746
3743
|
}
|
|
3747
3744
|
)),
|
|
3748
|
-
loading && /* @__PURE__ */
|
|
3749
|
-
!loading && !selectedTeamId && /* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
3781
|
-
)), /* @__PURE__ */
|
|
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__ */
|
|
3793
|
-
)))), /* @__PURE__ */
|
|
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__ */
|
|
3811
|
-
/* @__PURE__ */
|
|
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__ */
|
|
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
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
3915
|
-
)), /* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
3949
|
-
entry.updated_values && /* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
3996
|
-
/* @__PURE__ */
|
|
3997
|
-
/* @__PURE__ */
|
|
3998
|
-
/* @__PURE__ */
|
|
3999
|
-
/* @__PURE__ */
|
|
4000
|
-
), /* @__PURE__ */
|
|
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__ */
|
|
4014
|
-
/* @__PURE__ */
|
|
4015
|
-
/* @__PURE__ */
|
|
4016
|
-
/* @__PURE__ */
|
|
4017
|
-
/* @__PURE__ */
|
|
4018
|
-
/* @__PURE__ */
|
|
4019
|
-
), /* @__PURE__ */
|
|
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__ */
|
|
4034
|
-
/* @__PURE__ */
|
|
4035
|
-
/* @__PURE__ */
|
|
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
|
|
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
|
-
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
4395
|
-
/* @__PURE__ */
|
|
4396
|
-
/* @__PURE__ */
|
|
4397
|
-
/* @__PURE__ */
|
|
4398
|
-
userInfo.can_view_audit && /* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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
|
-
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
4464
|
-
|
|
4460
|
+
), activeTab === "audit" && userInfo.can_view_audit && /* @__PURE__ */ React13.createElement(AuditLog, { api }), /* @__PURE__ */ React13.createElement(
|
|
4461
|
+
KeyFormDialog,
|
|
4465
4462
|
{
|
|
4466
|
-
open:
|
|
4467
|
-
onClose: () =>
|
|
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
|
-
|
|
4475
|
+
onCreateKey: handleGenerateKey,
|
|
4476
|
+
onUpdateKey: handleUpdateKey,
|
|
4477
|
+
onResetKeySpend: handleResetKeySpend,
|
|
4474
4478
|
onGetConfig: () => api.getConfig()
|
|
4475
4479
|
}
|
|
4476
|
-
), /* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
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
|
|
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__ */
|
|
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__ */
|
|
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
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
4676
|
-
/* @__PURE__ */
|
|
4677
|
-
/* @__PURE__ */
|
|
4678
|
-
))), loading && /* @__PURE__ */
|
|
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
|
|
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
|
|
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
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
4814
|
+
), extra > 0 ? /* @__PURE__ */ React16.createElement(
|
|
4809
4815
|
Typography14,
|
|
4810
4816
|
{
|
|
4811
|
-
component:
|
|
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__ */
|
|
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
|
-
|
|
4868
|
-
|
|
4869
|
-
|
|
4870
|
-
|
|
4871
|
-
|
|
4872
|
-
|
|
4873
|
-
|
|
4874
|
-
|
|
4875
|
-
|
|
4876
|
-
|
|
4877
|
-
|
|
4878
|
-
|
|
4879
|
-
|
|
4880
|
-
|
|
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__ */
|
|
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__ */
|
|
5061
|
+
return /* @__PURE__ */ React16.createElement(Button6, { key, size: "small", variant: "outlined", component: Link3, to: moduleHref }, label);
|
|
4924
5062
|
case "all-limits":
|
|
4925
|
-
return /* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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
|
-
|
|
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,
|