@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.cjs.js
CHANGED
|
@@ -858,6 +858,66 @@ var init_ui = __esm({
|
|
|
858
858
|
}
|
|
859
859
|
});
|
|
860
860
|
|
|
861
|
+
// src/components/GenerateKeyButton.tsx
|
|
862
|
+
var import_react2, import_Button, import_icons_material, import_core_components, GenerateKeyButton;
|
|
863
|
+
var init_GenerateKeyButton = __esm({
|
|
864
|
+
"src/components/GenerateKeyButton.tsx"() {
|
|
865
|
+
"use strict";
|
|
866
|
+
import_react2 = __toESM(require("react"));
|
|
867
|
+
import_Button = __toESM(require("@mui/material/Button"));
|
|
868
|
+
import_icons_material = require("@mui/icons-material");
|
|
869
|
+
import_core_components = require("@backstage/core-components");
|
|
870
|
+
GenerateKeyButton = ({
|
|
871
|
+
to,
|
|
872
|
+
onClick,
|
|
873
|
+
label = "Generate New Key",
|
|
874
|
+
size
|
|
875
|
+
}) => {
|
|
876
|
+
if (onClick) {
|
|
877
|
+
return /* @__PURE__ */ import_react2.default.createElement(
|
|
878
|
+
import_Button.default,
|
|
879
|
+
{
|
|
880
|
+
variant: "contained",
|
|
881
|
+
color: "primary",
|
|
882
|
+
disableElevation: true,
|
|
883
|
+
size,
|
|
884
|
+
startIcon: /* @__PURE__ */ import_react2.default.createElement(import_icons_material.Add, null),
|
|
885
|
+
onClick
|
|
886
|
+
},
|
|
887
|
+
label
|
|
888
|
+
);
|
|
889
|
+
}
|
|
890
|
+
if (to) {
|
|
891
|
+
return /* @__PURE__ */ import_react2.default.createElement(
|
|
892
|
+
import_Button.default,
|
|
893
|
+
{
|
|
894
|
+
variant: "contained",
|
|
895
|
+
color: "primary",
|
|
896
|
+
disableElevation: true,
|
|
897
|
+
size,
|
|
898
|
+
startIcon: /* @__PURE__ */ import_react2.default.createElement(import_icons_material.Add, null),
|
|
899
|
+
component: import_core_components.Link,
|
|
900
|
+
to
|
|
901
|
+
},
|
|
902
|
+
label
|
|
903
|
+
);
|
|
904
|
+
}
|
|
905
|
+
return /* @__PURE__ */ import_react2.default.createElement(
|
|
906
|
+
import_Button.default,
|
|
907
|
+
{
|
|
908
|
+
variant: "contained",
|
|
909
|
+
color: "primary",
|
|
910
|
+
disableElevation: true,
|
|
911
|
+
size,
|
|
912
|
+
startIcon: /* @__PURE__ */ import_react2.default.createElement(import_icons_material.Add, null),
|
|
913
|
+
disabled: true
|
|
914
|
+
},
|
|
915
|
+
label
|
|
916
|
+
);
|
|
917
|
+
};
|
|
918
|
+
}
|
|
919
|
+
});
|
|
920
|
+
|
|
861
921
|
// src/components/DashboardHeader.tsx
|
|
862
922
|
function initials(name) {
|
|
863
923
|
const local = name.split("@")[0] ?? name;
|
|
@@ -865,24 +925,23 @@ function initials(name) {
|
|
|
865
925
|
if (parts.length >= 2) return (parts[0][0] + parts[1][0]).toUpperCase();
|
|
866
926
|
return local.slice(0, 2).toUpperCase();
|
|
867
927
|
}
|
|
868
|
-
var
|
|
928
|
+
var import_react3, import_Box2, import_Typography2, import_Skeleton, import_Paper2, import_Divider, import_styles2, CountStat, DashboardHeader;
|
|
869
929
|
var init_DashboardHeader = __esm({
|
|
870
930
|
"src/components/DashboardHeader.tsx"() {
|
|
871
931
|
"use strict";
|
|
872
|
-
|
|
932
|
+
import_react3 = __toESM(require("react"));
|
|
873
933
|
import_Box2 = __toESM(require("@mui/material/Box"));
|
|
874
934
|
import_Typography2 = __toESM(require("@mui/material/Typography"));
|
|
875
935
|
import_Skeleton = __toESM(require("@mui/material/Skeleton"));
|
|
876
936
|
import_Paper2 = __toESM(require("@mui/material/Paper"));
|
|
877
|
-
import_Button = __toESM(require("@mui/material/Button"));
|
|
878
937
|
import_Divider = __toESM(require("@mui/material/Divider"));
|
|
879
938
|
import_styles2 = require("@mui/material/styles");
|
|
880
|
-
import_icons_material = require("@mui/icons-material");
|
|
881
939
|
init_api();
|
|
882
940
|
init_ui();
|
|
941
|
+
init_GenerateKeyButton();
|
|
883
942
|
CountStat = ({ value, label, tone }) => {
|
|
884
943
|
const active = value > 0;
|
|
885
|
-
return /* @__PURE__ */
|
|
944
|
+
return /* @__PURE__ */ import_react3.default.createElement(import_Box2.default, { sx: { display: "flex", alignItems: "center", gap: 1 } }, /* @__PURE__ */ import_react3.default.createElement(
|
|
886
945
|
import_Box2.default,
|
|
887
946
|
{
|
|
888
947
|
sx: (theme) => ({
|
|
@@ -893,14 +952,14 @@ var init_DashboardHeader = __esm({
|
|
|
893
952
|
bgcolor: active ? tone === "danger" && theme.palette.error.main || tone === "warning" && theme.palette.warning.main || theme.palette.primary.main : (0, import_styles2.alpha)(theme.palette.text.primary, 0.25)
|
|
894
953
|
})
|
|
895
954
|
}
|
|
896
|
-
), /* @__PURE__ */
|
|
955
|
+
), /* @__PURE__ */ import_react3.default.createElement(
|
|
897
956
|
import_Typography2.default,
|
|
898
957
|
{
|
|
899
958
|
component: "span",
|
|
900
959
|
sx: { fontSize: 15, fontWeight: 700, lineHeight: 1, fontVariantNumeric: "tabular-nums" }
|
|
901
960
|
},
|
|
902
961
|
value
|
|
903
|
-
), /* @__PURE__ */
|
|
962
|
+
), /* @__PURE__ */ import_react3.default.createElement(
|
|
904
963
|
import_Typography2.default,
|
|
905
964
|
{
|
|
906
965
|
component: "span",
|
|
@@ -918,13 +977,13 @@ var init_DashboardHeader = __esm({
|
|
|
918
977
|
onGenerateKeyClick,
|
|
919
978
|
tabs
|
|
920
979
|
}) => {
|
|
921
|
-
const counts = (0,
|
|
980
|
+
const counts = (0, import_react3.useMemo)(() => {
|
|
922
981
|
const expired = keys.filter((k) => expiryStatus(k.expires_at) === "expired").length;
|
|
923
982
|
const expiringSoon = keys.filter((k) => expiryStatus(k.expires_at) === "soon").length;
|
|
924
983
|
return { total: keys.length, expired, expiringSoon };
|
|
925
984
|
}, [keys]);
|
|
926
985
|
const displayName = userInfo.user_email ?? userInfo.email ?? userInfo.user_id;
|
|
927
|
-
return /* @__PURE__ */
|
|
986
|
+
return /* @__PURE__ */ import_react3.default.createElement(import_Paper2.default, { sx: { borderRadius: 2, overflow: "hidden" } }, /* @__PURE__ */ import_react3.default.createElement(
|
|
928
987
|
import_Box2.default,
|
|
929
988
|
{
|
|
930
989
|
sx: {
|
|
@@ -937,7 +996,7 @@ var init_DashboardHeader = __esm({
|
|
|
937
996
|
pb: 2
|
|
938
997
|
}
|
|
939
998
|
},
|
|
940
|
-
/* @__PURE__ */
|
|
999
|
+
/* @__PURE__ */ import_react3.default.createElement(
|
|
941
1000
|
import_Box2.default,
|
|
942
1001
|
{
|
|
943
1002
|
sx: (theme) => ({
|
|
@@ -957,7 +1016,7 @@ var init_DashboardHeader = __esm({
|
|
|
957
1016
|
},
|
|
958
1017
|
initials(displayName)
|
|
959
1018
|
),
|
|
960
|
-
/* @__PURE__ */
|
|
1019
|
+
/* @__PURE__ */ import_react3.default.createElement(import_Box2.default, { sx: { flexGrow: 1, minWidth: 220 } }, /* @__PURE__ */ import_react3.default.createElement(import_Typography2.default, { variant: "h6", sx: { lineHeight: 1.25, wordBreak: "break-word" } }, displayName), /* @__PURE__ */ import_react3.default.createElement(
|
|
961
1020
|
import_Typography2.default,
|
|
962
1021
|
{
|
|
963
1022
|
variant: "caption",
|
|
@@ -965,7 +1024,7 @@ var init_DashboardHeader = __esm({
|
|
|
965
1024
|
sx: { display: "block", fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace" }
|
|
966
1025
|
},
|
|
967
1026
|
userInfo.user_id
|
|
968
|
-
), /* @__PURE__ */
|
|
1027
|
+
), /* @__PURE__ */ import_react3.default.createElement(import_Box2.default, { sx: { display: "flex", gap: 0.75, flexWrap: "wrap", mt: 1, minHeight: 20 } }, loading ? /* @__PURE__ */ import_react3.default.createElement(import_Skeleton.default, { variant: "rounded", width: 140, height: 20 }) : teams.map((team) => /* @__PURE__ */ import_react3.default.createElement(
|
|
969
1028
|
TagChip,
|
|
970
1029
|
{
|
|
971
1030
|
key: team.team_id,
|
|
@@ -973,18 +1032,8 @@ var init_DashboardHeader = __esm({
|
|
|
973
1032
|
title: team.team_id
|
|
974
1033
|
}
|
|
975
1034
|
)))),
|
|
976
|
-
/* @__PURE__ */
|
|
977
|
-
|
|
978
|
-
{
|
|
979
|
-
variant: "contained",
|
|
980
|
-
color: "primary",
|
|
981
|
-
disableElevation: true,
|
|
982
|
-
startIcon: /* @__PURE__ */ import_react2.default.createElement(import_icons_material.Add, null),
|
|
983
|
-
onClick: onGenerateKeyClick
|
|
984
|
-
},
|
|
985
|
-
"Generate New Key"
|
|
986
|
-
)
|
|
987
|
-
), /* @__PURE__ */ import_react2.default.createElement(import_Divider.default, null), /* @__PURE__ */ import_react2.default.createElement(
|
|
1035
|
+
/* @__PURE__ */ import_react3.default.createElement(GenerateKeyButton, { onClick: onGenerateKeyClick })
|
|
1036
|
+
), /* @__PURE__ */ import_react3.default.createElement(import_Divider.default, null), /* @__PURE__ */ import_react3.default.createElement(
|
|
988
1037
|
import_Box2.default,
|
|
989
1038
|
{
|
|
990
1039
|
sx: {
|
|
@@ -996,12 +1045,12 @@ var init_DashboardHeader = __esm({
|
|
|
996
1045
|
py: 1.5
|
|
997
1046
|
}
|
|
998
1047
|
},
|
|
999
|
-
/* @__PURE__ */
|
|
1000
|
-
/* @__PURE__ */
|
|
1001
|
-
/* @__PURE__ */
|
|
1002
|
-
/* @__PURE__ */
|
|
1003
|
-
/* @__PURE__ */
|
|
1004
|
-
), tabs && /* @__PURE__ */
|
|
1048
|
+
/* @__PURE__ */ import_react3.default.createElement(CountStat, { value: counts.total, label: "keys", tone: "accent" }),
|
|
1049
|
+
/* @__PURE__ */ import_react3.default.createElement(import_Divider.default, { orientation: "vertical", flexItem: true, sx: { my: 0.25 } }),
|
|
1050
|
+
/* @__PURE__ */ import_react3.default.createElement(CountStat, { value: counts.expired, label: "expired", tone: "danger" }),
|
|
1051
|
+
/* @__PURE__ */ import_react3.default.createElement(import_Divider.default, { orientation: "vertical", flexItem: true, sx: { my: 0.25 } }),
|
|
1052
|
+
/* @__PURE__ */ import_react3.default.createElement(CountStat, { value: counts.expiringSoon, label: "expiring soon", tone: "warning" })
|
|
1053
|
+
), tabs && /* @__PURE__ */ import_react3.default.createElement(import_react3.default.Fragment, null, /* @__PURE__ */ import_react3.default.createElement(import_Divider.default, null), /* @__PURE__ */ import_react3.default.createElement(import_Box2.default, { sx: { px: 1 } }, tabs)));
|
|
1005
1054
|
};
|
|
1006
1055
|
}
|
|
1007
1056
|
});
|
|
@@ -1020,12 +1069,12 @@ function expiryTone(status) {
|
|
|
1020
1069
|
function ExpiryCell({ expiresAt }) {
|
|
1021
1070
|
const status = expiryStatus(expiresAt);
|
|
1022
1071
|
if (!status) {
|
|
1023
|
-
return /* @__PURE__ */
|
|
1072
|
+
return /* @__PURE__ */ import_react4.default.createElement(import_Typography3.default, { variant: "body2", color: "text.secondary" }, "Never");
|
|
1024
1073
|
}
|
|
1025
1074
|
if (status === "ok") {
|
|
1026
|
-
return /* @__PURE__ */
|
|
1075
|
+
return /* @__PURE__ */ import_react4.default.createElement(import_Typography3.default, { variant: "body2", color: "text.secondary" }, formatDate(expiresAt));
|
|
1027
1076
|
}
|
|
1028
|
-
return /* @__PURE__ */
|
|
1077
|
+
return /* @__PURE__ */ import_react4.default.createElement(
|
|
1029
1078
|
StatusPill,
|
|
1030
1079
|
{
|
|
1031
1080
|
label: expiryChipLabel(status, expiresAt),
|
|
@@ -1040,36 +1089,16 @@ function budgetTone(pct) {
|
|
|
1040
1089
|
return "accent";
|
|
1041
1090
|
}
|
|
1042
1091
|
function keyBlockIcon(isBlocking, blocked) {
|
|
1043
|
-
if (isBlocking) return /* @__PURE__ */
|
|
1044
|
-
if (blocked) return /* @__PURE__ */
|
|
1045
|
-
return /* @__PURE__ */
|
|
1092
|
+
if (isBlocking) return /* @__PURE__ */ import_react4.default.createElement(import_CircularProgress.default, { size: 18 });
|
|
1093
|
+
if (blocked) return /* @__PURE__ */ import_react4.default.createElement(import_icons_material2.LockOpen, { fontSize: "small" });
|
|
1094
|
+
return /* @__PURE__ */ import_react4.default.createElement(import_icons_material2.Lock, { fontSize: "small" });
|
|
1046
1095
|
}
|
|
1047
1096
|
function BudgetCell({ spend, maxBudget }) {
|
|
1048
1097
|
if (!maxBudget) {
|
|
1049
|
-
return /* @__PURE__ */
|
|
1098
|
+
return /* @__PURE__ */ import_react4.default.createElement(import_Typography3.default, { variant: "body2", color: "text.secondary", sx: { fontVariantNumeric: "tabular-nums" } }, "$", spend.toFixed(2), /* @__PURE__ */ import_react4.default.createElement(import_Typography3.default, { component: "span", variant: "caption", sx: { ml: 0.5, opacity: 0.7 } }, "/ \u221E"));
|
|
1050
1099
|
}
|
|
1051
1100
|
const pct = Math.min(100, spend / maxBudget * 100);
|
|
1052
|
-
return /* @__PURE__ */
|
|
1053
|
-
}
|
|
1054
|
-
function fmtCost(perToken) {
|
|
1055
|
-
if (!perToken) return null;
|
|
1056
|
-
const per1k = perToken * 1e3;
|
|
1057
|
-
return per1k < 0.01 ? `$${(perToken * 1e6).toFixed(2)}/M` : `$${per1k.toFixed(3)}/1K`;
|
|
1058
|
-
}
|
|
1059
|
-
function formatContextWindow(maxInput, maxOutput) {
|
|
1060
|
-
if (!maxInput && !maxOutput) return null;
|
|
1061
|
-
const fmt = (n) => {
|
|
1062
|
-
if (!n) return null;
|
|
1063
|
-
if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
|
|
1064
|
-
if (n >= 1e3) return `${Math.round(n / 1e3)}K`;
|
|
1065
|
-
return String(n);
|
|
1066
|
-
};
|
|
1067
|
-
const inPart = fmt(maxInput);
|
|
1068
|
-
const outPart = fmt(maxOutput);
|
|
1069
|
-
if (inPart && outPart) return `ctx ${inPart} in / ${outPart} out`;
|
|
1070
|
-
if (inPart) return `ctx ${inPart}`;
|
|
1071
|
-
if (outPart) return `ctx ${outPart} out`;
|
|
1072
|
-
return null;
|
|
1101
|
+
return /* @__PURE__ */ import_react4.default.createElement(import_Box3.default, { minWidth: 104 }, /* @__PURE__ */ import_react4.default.createElement(import_Typography3.default, { variant: "body2", sx: { fontVariantNumeric: "tabular-nums", mb: 0.5 } }, "$", spend.toFixed(2), /* @__PURE__ */ import_react4.default.createElement(import_Typography3.default, { component: "span", variant: "caption", color: "text.secondary", sx: { ml: 0.5 } }, "/ $", maxBudget)), /* @__PURE__ */ import_react4.default.createElement(Meter, { value: pct, tone: budgetTone(pct), height: 4 }));
|
|
1073
1102
|
}
|
|
1074
1103
|
function compareKeys(a, b, sortKey) {
|
|
1075
1104
|
switch (sortKey) {
|
|
@@ -1089,11 +1118,11 @@ function compareKeys(a, b, sortKey) {
|
|
|
1089
1118
|
return 0;
|
|
1090
1119
|
}
|
|
1091
1120
|
}
|
|
1092
|
-
var
|
|
1121
|
+
var import_react4, import_Table, import_TableBody, import_TableCell, import_TableContainer, import_TableHead, import_TableRow, import_TableSortLabel, import_Button2, import_IconButton, import_Box3, import_Typography3, import_Dialog, import_DialogTitle, import_DialogContent, import_DialogActions, import_TextField, import_CircularProgress, import_Skeleton2, import_InputAdornment, import_styles3, import_icons_material2, shortKeyId, formatDate, KeysTable;
|
|
1093
1122
|
var init_KeysTable = __esm({
|
|
1094
1123
|
"src/components/KeysTable.tsx"() {
|
|
1095
1124
|
"use strict";
|
|
1096
|
-
|
|
1125
|
+
import_react4 = __toESM(require("react"));
|
|
1097
1126
|
import_Table = __toESM(require("@mui/material/Table"));
|
|
1098
1127
|
import_TableBody = __toESM(require("@mui/material/TableBody"));
|
|
1099
1128
|
import_TableCell = __toESM(require("@mui/material/TableCell"));
|
|
@@ -1111,17 +1140,13 @@ var init_KeysTable = __esm({
|
|
|
1111
1140
|
import_DialogActions = __toESM(require("@mui/material/DialogActions"));
|
|
1112
1141
|
import_TextField = __toESM(require("@mui/material/TextField"));
|
|
1113
1142
|
import_CircularProgress = __toESM(require("@mui/material/CircularProgress"));
|
|
1114
|
-
import_Autocomplete = __toESM(require("@mui/material/Autocomplete"));
|
|
1115
1143
|
import_Skeleton2 = __toESM(require("@mui/material/Skeleton"));
|
|
1116
1144
|
import_InputAdornment = __toESM(require("@mui/material/InputAdornment"));
|
|
1117
1145
|
import_styles3 = require("@mui/material/styles");
|
|
1118
1146
|
import_icons_material2 = require("@mui/icons-material");
|
|
1119
1147
|
init_api();
|
|
1148
|
+
init_GenerateKeyButton();
|
|
1120
1149
|
init_ui();
|
|
1121
|
-
maskKey = (key) => {
|
|
1122
|
-
if (key.length <= 8) return "***";
|
|
1123
|
-
return `${key.slice(0, 4)}...${key.slice(-4)}`;
|
|
1124
|
-
};
|
|
1125
1150
|
shortKeyId = (token) => {
|
|
1126
1151
|
if (!token) return "-";
|
|
1127
1152
|
if (token.length <= 16) return token;
|
|
@@ -1134,36 +1159,22 @@ var init_KeysTable = __esm({
|
|
|
1134
1159
|
return dateStr;
|
|
1135
1160
|
}
|
|
1136
1161
|
};
|
|
1137
|
-
keyToEditForm = (k) => ({
|
|
1138
|
-
key_alias: k.key_alias ?? "",
|
|
1139
|
-
models: k.models ?? [],
|
|
1140
|
-
max_budget: k.max_budget,
|
|
1141
|
-
tpm_limit: k.tpm_limit,
|
|
1142
|
-
rpm_limit: k.rpm_limit
|
|
1143
|
-
});
|
|
1144
1162
|
KeysTable = ({
|
|
1145
1163
|
keys,
|
|
1146
|
-
models,
|
|
1147
1164
|
loading,
|
|
1148
1165
|
onGenerateKeyClick,
|
|
1149
|
-
|
|
1166
|
+
onEditKey,
|
|
1150
1167
|
onBlockKey,
|
|
1151
1168
|
onUnblockKey,
|
|
1152
|
-
onResetKeySpend,
|
|
1153
1169
|
onDeleteKey,
|
|
1154
1170
|
onPruneExpiredKeys
|
|
1155
1171
|
}) => {
|
|
1156
|
-
const [
|
|
1157
|
-
const [
|
|
1158
|
-
const [
|
|
1159
|
-
const [
|
|
1160
|
-
const [
|
|
1161
|
-
const [
|
|
1162
|
-
const [resetSpendConfirm, setResetSpendConfirm] = (0, import_react3.useState)(false);
|
|
1163
|
-
const [resetSpendSubmitting, setResetSpendSubmitting] = (0, import_react3.useState)(false);
|
|
1164
|
-
const [filterText, setFilterText] = (0, import_react3.useState)("");
|
|
1165
|
-
const [sortKey, setSortKey] = (0, import_react3.useState)("created");
|
|
1166
|
-
const [sortDirection, setSortDirection] = (0, import_react3.useState)("desc");
|
|
1172
|
+
const [blockingKeyId, setBlockingKeyId] = (0, import_react4.useState)(null);
|
|
1173
|
+
const [deleteConfirmId, setDeleteConfirmId] = (0, import_react4.useState)(null);
|
|
1174
|
+
const [deleteSubmitting, setDeleteSubmitting] = (0, import_react4.useState)(false);
|
|
1175
|
+
const [filterText, setFilterText] = (0, import_react4.useState)("");
|
|
1176
|
+
const [sortKey, setSortKey] = (0, import_react4.useState)("created");
|
|
1177
|
+
const [sortDirection, setSortDirection] = (0, import_react4.useState)("desc");
|
|
1167
1178
|
const handleSort = (nextSortKey) => {
|
|
1168
1179
|
if (sortKey === nextSortKey) {
|
|
1169
1180
|
setSortDirection((current) => current === "asc" ? "desc" : "asc");
|
|
@@ -1172,7 +1183,7 @@ var init_KeysTable = __esm({
|
|
|
1172
1183
|
setSortDirection("asc");
|
|
1173
1184
|
}
|
|
1174
1185
|
};
|
|
1175
|
-
const filteredKeys = (0,
|
|
1186
|
+
const filteredKeys = (0, import_react4.useMemo)(() => {
|
|
1176
1187
|
let result = keys;
|
|
1177
1188
|
if (filterText.trim()) {
|
|
1178
1189
|
const q = filterText.toLowerCase();
|
|
@@ -1185,27 +1196,8 @@ var init_KeysTable = __esm({
|
|
|
1185
1196
|
return sortDirection === "asc" ? comparison : -comparison;
|
|
1186
1197
|
});
|
|
1187
1198
|
}, [keys, filterText, sortKey, sortDirection]);
|
|
1188
|
-
const editSelectedModels = models.filter((m) => (editForm.models || []).includes(m.model_name));
|
|
1189
1199
|
const handleOpenEdit = (k) => {
|
|
1190
|
-
|
|
1191
|
-
setEditForm(keyToEditForm(k));
|
|
1192
|
-
};
|
|
1193
|
-
const handleCloseEdit = () => {
|
|
1194
|
-
setEditingKey(null);
|
|
1195
|
-
setEditForm({});
|
|
1196
|
-
setResetSpendConfirm(false);
|
|
1197
|
-
};
|
|
1198
|
-
const handleUpdate = async () => {
|
|
1199
|
-
if (!editingKey) return;
|
|
1200
|
-
setEditSubmitting(true);
|
|
1201
|
-
try {
|
|
1202
|
-
await onUpdateKey(editingKey.token ?? editingKey.key, editForm);
|
|
1203
|
-
handleCloseEdit();
|
|
1204
|
-
} catch (error) {
|
|
1205
|
-
console.error("Failed to update key:", error);
|
|
1206
|
-
} finally {
|
|
1207
|
-
setEditSubmitting(false);
|
|
1208
|
-
}
|
|
1200
|
+
onEditKey(k);
|
|
1209
1201
|
};
|
|
1210
1202
|
const handleToggleBlock = async (key) => {
|
|
1211
1203
|
const keyId = key.token ?? key.key;
|
|
@@ -1220,17 +1212,6 @@ var init_KeysTable = __esm({
|
|
|
1220
1212
|
setBlockingKeyId(null);
|
|
1221
1213
|
}
|
|
1222
1214
|
};
|
|
1223
|
-
const handleResetSpend = async () => {
|
|
1224
|
-
if (!editingKey) return;
|
|
1225
|
-
setResetSpendSubmitting(true);
|
|
1226
|
-
try {
|
|
1227
|
-
await onResetKeySpend(editingKey.token ?? editingKey.key);
|
|
1228
|
-
setResetSpendConfirm(false);
|
|
1229
|
-
handleCloseEdit();
|
|
1230
|
-
} finally {
|
|
1231
|
-
setResetSpendSubmitting(false);
|
|
1232
|
-
}
|
|
1233
|
-
};
|
|
1234
1215
|
const handleConfirmDelete = async () => {
|
|
1235
1216
|
if (!deleteConfirmId) return;
|
|
1236
1217
|
setDeleteSubmitting(true);
|
|
@@ -1244,9 +1225,9 @@ var init_KeysTable = __esm({
|
|
|
1244
1225
|
const copyToClipboard = (text) => {
|
|
1245
1226
|
navigator.clipboard.writeText(text);
|
|
1246
1227
|
};
|
|
1247
|
-
const [pruneConfirmCount, setPruneConfirmCount] = (0,
|
|
1248
|
-
const [pruneSubmitting, setPruneSubmitting] = (0,
|
|
1249
|
-
const expiredKeys = (0,
|
|
1228
|
+
const [pruneConfirmCount, setPruneConfirmCount] = (0, import_react4.useState)(null);
|
|
1229
|
+
const [pruneSubmitting, setPruneSubmitting] = (0, import_react4.useState)(false);
|
|
1230
|
+
const expiredKeys = (0, import_react4.useMemo)(() => {
|
|
1250
1231
|
return keys.filter((k) => expiryStatus(k.expires_at) === "expired");
|
|
1251
1232
|
}, [keys]);
|
|
1252
1233
|
const handlePruneExpired = async () => {
|
|
@@ -1261,40 +1242,29 @@ var init_KeysTable = __esm({
|
|
|
1261
1242
|
setPruneConfirmCount(null);
|
|
1262
1243
|
}
|
|
1263
1244
|
};
|
|
1264
|
-
const modelOption = (m) => {
|
|
1265
|
-
const inCost = fmtCost(m.input_cost_per_token);
|
|
1266
|
-
const outCost = fmtCost(m.output_cost_per_token);
|
|
1267
|
-
const ctx = formatContextWindow(m.max_input_tokens, m.max_output_tokens);
|
|
1268
|
-
return /* @__PURE__ */ import_react3.default.createElement(import_Box3.default, null, /* @__PURE__ */ import_react3.default.createElement("span", null, m.model_name), m.supports_function_calling && " \u{1F527}", m.supports_vision && " \u{1F441}\uFE0F", ctx && /* @__PURE__ */ import_react3.default.createElement(import_Typography3.default, { variant: "caption", color: "text.secondary", sx: { ml: 1 } }, ctx), (inCost || outCost) && /* @__PURE__ */ import_react3.default.createElement(import_Typography3.default, { variant: "caption", color: "text.secondary", sx: { ml: 1 } }, inCost, " in \xB7 ", outCost, " out"));
|
|
1269
|
-
};
|
|
1270
1245
|
const renderTableBody = () => {
|
|
1271
1246
|
if (loading) {
|
|
1272
|
-
return /* @__PURE__ */
|
|
1247
|
+
return /* @__PURE__ */ import_react4.default.createElement(import_TableRow.default, null, /* @__PURE__ */ import_react4.default.createElement(import_TableCell.default, { colSpan: 8, sx: { py: 2 } }, /* @__PURE__ */ import_react4.default.createElement(import_Skeleton2.default, { variant: "rounded", height: 140 })));
|
|
1273
1248
|
}
|
|
1274
1249
|
if (filteredKeys.length === 0) {
|
|
1275
|
-
return /* @__PURE__ */
|
|
1250
|
+
return /* @__PURE__ */ import_react4.default.createElement(import_TableRow.default, null, /* @__PURE__ */ import_react4.default.createElement(import_TableCell.default, { colSpan: 8 }, filterText ? /* @__PURE__ */ import_react4.default.createElement(
|
|
1276
1251
|
EmptyState,
|
|
1277
1252
|
{
|
|
1278
1253
|
message: "No keys match that filter",
|
|
1279
1254
|
hint: "Try a different alias or model name."
|
|
1280
1255
|
}
|
|
1281
|
-
) : /* @__PURE__ */
|
|
1256
|
+
) : /* @__PURE__ */ import_react4.default.createElement(
|
|
1282
1257
|
EmptyState,
|
|
1283
1258
|
{
|
|
1284
1259
|
message: "No keys yet",
|
|
1285
1260
|
hint: "Generate your first key to start calling models.",
|
|
1286
|
-
action: /* @__PURE__ */
|
|
1287
|
-
|
|
1261
|
+
action: /* @__PURE__ */ import_react4.default.createElement(
|
|
1262
|
+
GenerateKeyButton,
|
|
1288
1263
|
{
|
|
1289
|
-
variant: "contained",
|
|
1290
|
-
color: "primary",
|
|
1291
1264
|
size: "small",
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
sx: { mt: 1 }
|
|
1296
|
-
},
|
|
1297
|
-
"Generate Your First Key"
|
|
1265
|
+
label: "Generate Your First Key",
|
|
1266
|
+
onClick: onGenerateKeyClick
|
|
1267
|
+
}
|
|
1298
1268
|
)
|
|
1299
1269
|
}
|
|
1300
1270
|
)));
|
|
@@ -1303,7 +1273,7 @@ var init_KeysTable = __esm({
|
|
|
1303
1273
|
const keyId = key.token ?? key.key;
|
|
1304
1274
|
const isBlocking = blockingKeyId === keyId;
|
|
1305
1275
|
const keyModels = key.models ?? [];
|
|
1306
|
-
return /* @__PURE__ */
|
|
1276
|
+
return /* @__PURE__ */ import_react4.default.createElement(
|
|
1307
1277
|
import_TableRow.default,
|
|
1308
1278
|
{
|
|
1309
1279
|
key: keyId,
|
|
@@ -1314,8 +1284,8 @@ var init_KeysTable = __esm({
|
|
|
1314
1284
|
"& td": { color: theme.palette.text.secondary }
|
|
1315
1285
|
}) : void 0
|
|
1316
1286
|
},
|
|
1317
|
-
/* @__PURE__ */
|
|
1318
|
-
/* @__PURE__ */
|
|
1287
|
+
/* @__PURE__ */ import_react4.default.createElement(import_TableCell.default, null, /* @__PURE__ */ import_react4.default.createElement(import_Box3.default, { display: "flex", alignItems: "center", gap: 1, flexWrap: "wrap" }, /* @__PURE__ */ import_react4.default.createElement(import_Typography3.default, { variant: "body2" }, key.key_alias || "\u2014"), key.blocked && /* @__PURE__ */ import_react4.default.createElement(StatusPill, { label: "Blocked", tone: "danger" }))),
|
|
1288
|
+
/* @__PURE__ */ import_react4.default.createElement(import_TableCell.default, null, /* @__PURE__ */ import_react4.default.createElement(import_Box3.default, { display: "flex", alignItems: "center", gap: 0.5 }, /* @__PURE__ */ import_react4.default.createElement(
|
|
1319
1289
|
import_Typography3.default,
|
|
1320
1290
|
{
|
|
1321
1291
|
variant: "body2",
|
|
@@ -1332,7 +1302,7 @@ var init_KeysTable = __esm({
|
|
|
1332
1302
|
})
|
|
1333
1303
|
},
|
|
1334
1304
|
shortKeyId(keyId)
|
|
1335
|
-
), /* @__PURE__ */
|
|
1305
|
+
), /* @__PURE__ */ import_react4.default.createElement(
|
|
1336
1306
|
import_IconButton.default,
|
|
1337
1307
|
{
|
|
1338
1308
|
size: "small",
|
|
@@ -1340,13 +1310,13 @@ var init_KeysTable = __esm({
|
|
|
1340
1310
|
title: "Copy Key ID",
|
|
1341
1311
|
sx: quietIconButtonSx("accent")
|
|
1342
1312
|
},
|
|
1343
|
-
/* @__PURE__ */
|
|
1313
|
+
/* @__PURE__ */ import_react4.default.createElement(import_icons_material2.ContentCopy, { sx: { fontSize: 15 } })
|
|
1344
1314
|
))),
|
|
1345
|
-
/* @__PURE__ */
|
|
1346
|
-
/* @__PURE__ */
|
|
1347
|
-
/* @__PURE__ */
|
|
1348
|
-
/* @__PURE__ */
|
|
1349
|
-
/* @__PURE__ */
|
|
1315
|
+
/* @__PURE__ */ import_react4.default.createElement(import_TableCell.default, null, /* @__PURE__ */ import_react4.default.createElement(import_Typography3.default, { variant: "body2", color: "text.secondary" }, formatDate(key.created_at))),
|
|
1316
|
+
/* @__PURE__ */ import_react4.default.createElement(import_TableCell.default, null, /* @__PURE__ */ import_react4.default.createElement(ExpiryCell, { expiresAt: key.expires_at })),
|
|
1317
|
+
/* @__PURE__ */ import_react4.default.createElement(import_TableCell.default, null, /* @__PURE__ */ import_react4.default.createElement(BudgetCell, { spend: key.spend ?? 0, maxBudget: key.max_budget })),
|
|
1318
|
+
/* @__PURE__ */ import_react4.default.createElement(import_TableCell.default, null, /* @__PURE__ */ import_react4.default.createElement(import_Typography3.default, { variant: "body2", color: "text.secondary", sx: { fontVariantNumeric: "tabular-nums" } }, key.tpm_limit ?? "\u221E", " / ", key.rpm_limit ?? "\u221E")),
|
|
1319
|
+
/* @__PURE__ */ import_react4.default.createElement(import_TableCell.default, null, keyModels.length === 0 ? /* @__PURE__ */ import_react4.default.createElement(import_Typography3.default, { variant: "body2", color: "text.secondary" }, "All models") : /* @__PURE__ */ import_react4.default.createElement(import_Box3.default, { display: "flex", gap: 0.5, flexWrap: "wrap" }, keyModels.slice(0, 2).map((model) => /* @__PURE__ */ import_react4.default.createElement(TagChip, { key: model, label: model, title: model })), keyModels.length > 2 && /* @__PURE__ */ import_react4.default.createElement(
|
|
1350
1320
|
TagChip,
|
|
1351
1321
|
{
|
|
1352
1322
|
label: `+${keyModels.length - 2}`,
|
|
@@ -1354,7 +1324,7 @@ var init_KeysTable = __esm({
|
|
|
1354
1324
|
title: keyModels.slice(2).join(", ")
|
|
1355
1325
|
}
|
|
1356
1326
|
))),
|
|
1357
|
-
/* @__PURE__ */
|
|
1327
|
+
/* @__PURE__ */ import_react4.default.createElement(import_TableCell.default, { align: "right" }, /* @__PURE__ */ import_react4.default.createElement(import_Box3.default, { display: "flex", justifyContent: "flex-end", gap: 0.25 }, /* @__PURE__ */ import_react4.default.createElement(
|
|
1358
1328
|
import_IconButton.default,
|
|
1359
1329
|
{
|
|
1360
1330
|
size: "small",
|
|
@@ -1362,8 +1332,8 @@ var init_KeysTable = __esm({
|
|
|
1362
1332
|
title: "Edit key",
|
|
1363
1333
|
sx: quietIconButtonSx("accent")
|
|
1364
1334
|
},
|
|
1365
|
-
/* @__PURE__ */
|
|
1366
|
-
), /* @__PURE__ */
|
|
1335
|
+
/* @__PURE__ */ import_react4.default.createElement(import_icons_material2.Edit, { fontSize: "small" })
|
|
1336
|
+
), /* @__PURE__ */ import_react4.default.createElement(
|
|
1367
1337
|
import_IconButton.default,
|
|
1368
1338
|
{
|
|
1369
1339
|
size: "small",
|
|
@@ -1373,7 +1343,7 @@ var init_KeysTable = __esm({
|
|
|
1373
1343
|
sx: quietIconButtonSx("warning")
|
|
1374
1344
|
},
|
|
1375
1345
|
keyBlockIcon(isBlocking, key.blocked)
|
|
1376
|
-
), /* @__PURE__ */
|
|
1346
|
+
), /* @__PURE__ */ import_react4.default.createElement(
|
|
1377
1347
|
import_IconButton.default,
|
|
1378
1348
|
{
|
|
1379
1349
|
size: "small",
|
|
@@ -1381,18 +1351,18 @@ var init_KeysTable = __esm({
|
|
|
1381
1351
|
title: "Revoke key",
|
|
1382
1352
|
sx: quietIconButtonSx("danger")
|
|
1383
1353
|
},
|
|
1384
|
-
/* @__PURE__ */
|
|
1354
|
+
/* @__PURE__ */ import_react4.default.createElement(import_icons_material2.Delete, { fontSize: "small" })
|
|
1385
1355
|
)))
|
|
1386
1356
|
);
|
|
1387
1357
|
});
|
|
1388
1358
|
};
|
|
1389
|
-
return /* @__PURE__ */
|
|
1359
|
+
return /* @__PURE__ */ import_react4.default.createElement(import_react4.default.Fragment, null, /* @__PURE__ */ import_react4.default.createElement(
|
|
1390
1360
|
SectionCard,
|
|
1391
1361
|
{
|
|
1392
1362
|
title: "Virtual Keys",
|
|
1393
1363
|
subtitle: loading ? "Loading\u2026" : `${filteredKeys.length}${filterText ? ` of ${keys.length}` : ""} key${filteredKeys.length === 1 ? "" : "s"}`,
|
|
1394
1364
|
flush: true,
|
|
1395
|
-
actions: /* @__PURE__ */
|
|
1365
|
+
actions: /* @__PURE__ */ import_react4.default.createElement(import_react4.default.Fragment, null, /* @__PURE__ */ import_react4.default.createElement(
|
|
1396
1366
|
import_TextField.default,
|
|
1397
1367
|
{
|
|
1398
1368
|
size: "small",
|
|
@@ -1402,15 +1372,15 @@ var init_KeysTable = __esm({
|
|
|
1402
1372
|
onChange: (e) => setFilterText(e.target.value),
|
|
1403
1373
|
sx: { minWidth: 240 },
|
|
1404
1374
|
InputProps: {
|
|
1405
|
-
startAdornment: /* @__PURE__ */
|
|
1375
|
+
startAdornment: /* @__PURE__ */ import_react4.default.createElement(import_InputAdornment.default, { position: "start" }, /* @__PURE__ */ import_react4.default.createElement(import_icons_material2.Search, { fontSize: "small", color: "disabled" }))
|
|
1406
1376
|
}
|
|
1407
1377
|
}
|
|
1408
|
-
), expiredKeys.length > 0 && /* @__PURE__ */
|
|
1378
|
+
), expiredKeys.length > 0 && /* @__PURE__ */ import_react4.default.createElement(
|
|
1409
1379
|
import_Button2.default,
|
|
1410
1380
|
{
|
|
1411
1381
|
variant: "outlined",
|
|
1412
1382
|
color: "inherit",
|
|
1413
|
-
startIcon: /* @__PURE__ */
|
|
1383
|
+
startIcon: /* @__PURE__ */ import_react4.default.createElement(import_icons_material2.Autorenew, null),
|
|
1414
1384
|
onClick: () => setPruneConfirmCount(expiredKeys.length),
|
|
1415
1385
|
sx: (theme) => ({
|
|
1416
1386
|
color: theme.palette.text.secondary,
|
|
@@ -1425,88 +1395,10 @@ var init_KeysTable = __esm({
|
|
|
1425
1395
|
"Prune expired (",
|
|
1426
1396
|
expiredKeys.length,
|
|
1427
1397
|
")"
|
|
1428
|
-
), /* @__PURE__ */
|
|
1429
|
-
import_Button2.default,
|
|
1430
|
-
{
|
|
1431
|
-
variant: "contained",
|
|
1432
|
-
color: "primary",
|
|
1433
|
-
disableElevation: true,
|
|
1434
|
-
startIcon: /* @__PURE__ */ import_react3.default.createElement(import_icons_material2.Add, null),
|
|
1435
|
-
onClick: onGenerateKeyClick
|
|
1436
|
-
},
|
|
1437
|
-
"Generate New Key"
|
|
1438
|
-
))
|
|
1398
|
+
), /* @__PURE__ */ import_react4.default.createElement(GenerateKeyButton, { onClick: onGenerateKeyClick }))
|
|
1439
1399
|
},
|
|
1440
|
-
/* @__PURE__ */
|
|
1441
|
-
), /* @__PURE__ */
|
|
1442
|
-
import_TextField.default,
|
|
1443
|
-
{
|
|
1444
|
-
label: "Alias",
|
|
1445
|
-
value: editForm.key_alias || "",
|
|
1446
|
-
onChange: (e) => setEditForm({ ...editForm, key_alias: e.target.value }),
|
|
1447
|
-
fullWidth: true
|
|
1448
|
-
}
|
|
1449
|
-
), models.length > 0 && /* @__PURE__ */ import_react3.default.createElement(
|
|
1450
|
-
import_Autocomplete.default,
|
|
1451
|
-
{
|
|
1452
|
-
multiple: true,
|
|
1453
|
-
options: models,
|
|
1454
|
-
groupBy: (m) => m.mode || "other",
|
|
1455
|
-
getOptionLabel: (m) => m.model_name,
|
|
1456
|
-
value: editSelectedModels,
|
|
1457
|
-
onChange: (_e, selected) => setEditForm({ ...editForm, models: selected.map((m) => m.model_name) }),
|
|
1458
|
-
renderOption: (props, m) => /* @__PURE__ */ import_react3.default.createElement("li", { ...props }, modelOption(m)),
|
|
1459
|
-
renderInput: (params) => /* @__PURE__ */ import_react3.default.createElement(import_TextField.default, { ...params, label: "Models", fullWidth: true })
|
|
1460
|
-
}
|
|
1461
|
-
), /* @__PURE__ */ import_react3.default.createElement(
|
|
1462
|
-
import_TextField.default,
|
|
1463
|
-
{
|
|
1464
|
-
label: "Max Budget (USD)",
|
|
1465
|
-
type: "number",
|
|
1466
|
-
value: editForm.max_budget ?? "",
|
|
1467
|
-
onChange: (e) => setEditForm({ ...editForm, max_budget: e.target.value ? Number(e.target.value) : void 0 }),
|
|
1468
|
-
fullWidth: true
|
|
1469
|
-
}
|
|
1470
|
-
), /* @__PURE__ */ import_react3.default.createElement(
|
|
1471
|
-
import_TextField.default,
|
|
1472
|
-
{
|
|
1473
|
-
label: "TPM Limit",
|
|
1474
|
-
type: "number",
|
|
1475
|
-
value: editForm.tpm_limit ?? "",
|
|
1476
|
-
onChange: (e) => setEditForm({ ...editForm, tpm_limit: e.target.value ? Number(e.target.value) : void 0 }),
|
|
1477
|
-
helperText: "Max tokens per minute this key can consume across all models. Leave blank for no limit.",
|
|
1478
|
-
fullWidth: true
|
|
1479
|
-
}
|
|
1480
|
-
), /* @__PURE__ */ import_react3.default.createElement(
|
|
1481
|
-
import_TextField.default,
|
|
1482
|
-
{
|
|
1483
|
-
label: "RPM Limit",
|
|
1484
|
-
type: "number",
|
|
1485
|
-
value: editForm.rpm_limit ?? "",
|
|
1486
|
-
onChange: (e) => setEditForm({ ...editForm, rpm_limit: e.target.value ? Number(e.target.value) : void 0 }),
|
|
1487
|
-
helperText: "Max requests per minute this key can make across all models. Leave blank for no limit.",
|
|
1488
|
-
fullWidth: true
|
|
1489
|
-
}
|
|
1490
|
-
), /* @__PURE__ */ import_react3.default.createElement(import_Box3.default, { mt: 1, pt: 2, borderTop: "1px solid", sx: { borderColor: "divider" } }, /* @__PURE__ */ import_react3.default.createElement(import_Typography3.default, { variant: "caption", color: "text.secondary", display: "block", mb: 1 }, "Danger Zone"), !resetSpendConfirm ? /* @__PURE__ */ import_react3.default.createElement(
|
|
1491
|
-
import_Button2.default,
|
|
1492
|
-
{
|
|
1493
|
-
size: "small",
|
|
1494
|
-
color: "warning",
|
|
1495
|
-
variant: "outlined",
|
|
1496
|
-
onClick: () => setResetSpendConfirm(true)
|
|
1497
|
-
},
|
|
1498
|
-
"Reset Spend to $0"
|
|
1499
|
-
) : /* @__PURE__ */ import_react3.default.createElement(import_Box3.default, { display: "flex", alignItems: "center", gap: 1, flexWrap: "wrap" }, /* @__PURE__ */ import_react3.default.createElement(import_Typography3.default, { variant: "body2", color: "warning.main" }, "Zero out spend counter?"), /* @__PURE__ */ import_react3.default.createElement(
|
|
1500
|
-
import_Button2.default,
|
|
1501
|
-
{
|
|
1502
|
-
size: "small",
|
|
1503
|
-
color: "warning",
|
|
1504
|
-
variant: "contained",
|
|
1505
|
-
disabled: resetSpendSubmitting,
|
|
1506
|
-
onClick: handleResetSpend
|
|
1507
|
-
},
|
|
1508
|
-
resetSpendSubmitting ? /* @__PURE__ */ import_react3.default.createElement(import_CircularProgress.default, { size: 16 }) : "Confirm"
|
|
1509
|
-
), /* @__PURE__ */ import_react3.default.createElement(import_Button2.default, { size: "small", onClick: () => setResetSpendConfirm(false) }, "Cancel"))))), /* @__PURE__ */ import_react3.default.createElement(import_DialogActions.default, null, /* @__PURE__ */ import_react3.default.createElement(import_Button2.default, { onClick: handleCloseEdit }, "Cancel"), /* @__PURE__ */ import_react3.default.createElement(import_Button2.default, { onClick: handleUpdate, variant: "contained", color: "primary", disabled: editSubmitting }, editSubmitting ? /* @__PURE__ */ import_react3.default.createElement(import_CircularProgress.default, { size: 24 }) : "Save"))), /* @__PURE__ */ import_react3.default.createElement(import_Dialog.default, { open: !!deleteConfirmId, onClose: () => setDeleteConfirmId(null), maxWidth: "xs", fullWidth: true }, /* @__PURE__ */ import_react3.default.createElement(import_DialogTitle.default, null, "Revoke Key?"), /* @__PURE__ */ import_react3.default.createElement(import_DialogContent.default, null, /* @__PURE__ */ import_react3.default.createElement(import_Typography3.default, null, "This will permanently revoke the key. Any integrations using it will stop working immediately.")), /* @__PURE__ */ import_react3.default.createElement(import_DialogActions.default, null, /* @__PURE__ */ import_react3.default.createElement(import_Button2.default, { onClick: () => setDeleteConfirmId(null), disabled: deleteSubmitting }, "Cancel"), /* @__PURE__ */ import_react3.default.createElement(
|
|
1400
|
+
/* @__PURE__ */ import_react4.default.createElement(import_TableContainer.default, null, /* @__PURE__ */ import_react4.default.createElement(import_Table.default, { size: "small", sx: dataTableSx }, /* @__PURE__ */ import_react4.default.createElement(import_TableHead.default, null, /* @__PURE__ */ import_react4.default.createElement(import_TableRow.default, null, /* @__PURE__ */ import_react4.default.createElement(import_TableCell.default, { sortDirection: sortKey === "alias" ? sortDirection : false }, /* @__PURE__ */ import_react4.default.createElement(import_TableSortLabel.default, { active: sortKey === "alias", direction: sortKey === "alias" ? sortDirection : "asc", onClick: () => handleSort("alias") }, "Alias")), /* @__PURE__ */ import_react4.default.createElement(import_TableCell.default, null, "Key ID"), /* @__PURE__ */ import_react4.default.createElement(import_TableCell.default, { sortDirection: sortKey === "created" ? sortDirection : false }, /* @__PURE__ */ import_react4.default.createElement(import_TableSortLabel.default, { active: sortKey === "created", direction: sortKey === "created" ? sortDirection : "asc", onClick: () => handleSort("created") }, "Created")), /* @__PURE__ */ import_react4.default.createElement(import_TableCell.default, { sortDirection: sortKey === "expires" ? sortDirection : false }, /* @__PURE__ */ import_react4.default.createElement(import_TableSortLabel.default, { active: sortKey === "expires", direction: sortKey === "expires" ? sortDirection : "asc", onClick: () => handleSort("expires") }, "Expires")), /* @__PURE__ */ import_react4.default.createElement(import_TableCell.default, { sortDirection: sortKey === "budget" ? sortDirection : false }, /* @__PURE__ */ import_react4.default.createElement(import_TableSortLabel.default, { active: sortKey === "budget", direction: sortKey === "budget" ? sortDirection : "asc", onClick: () => handleSort("budget") }, "Budget")), /* @__PURE__ */ import_react4.default.createElement(import_TableCell.default, { sortDirection: sortKey === "limits" ? sortDirection : false }, /* @__PURE__ */ import_react4.default.createElement(import_TableSortLabel.default, { active: sortKey === "limits", direction: sortKey === "limits" ? sortDirection : "asc", onClick: () => handleSort("limits") }, "TPM / RPM")), /* @__PURE__ */ import_react4.default.createElement(import_TableCell.default, { sortDirection: sortKey === "models" ? sortDirection : false }, /* @__PURE__ */ import_react4.default.createElement(import_TableSortLabel.default, { active: sortKey === "models", direction: sortKey === "models" ? sortDirection : "asc", onClick: () => handleSort("models") }, "Models")), /* @__PURE__ */ import_react4.default.createElement(import_TableCell.default, { align: "right" }, "Actions"))), /* @__PURE__ */ import_react4.default.createElement(import_TableBody.default, null, renderTableBody())))
|
|
1401
|
+
), /* @__PURE__ */ import_react4.default.createElement(import_Dialog.default, { open: !!deleteConfirmId, onClose: () => setDeleteConfirmId(null), maxWidth: "xs", fullWidth: true }, /* @__PURE__ */ import_react4.default.createElement(import_DialogTitle.default, null, "Revoke Key?"), /* @__PURE__ */ import_react4.default.createElement(import_DialogContent.default, null, /* @__PURE__ */ import_react4.default.createElement(import_Typography3.default, null, "This will permanently revoke the key. Any integrations using it will stop working immediately.")), /* @__PURE__ */ import_react4.default.createElement(import_DialogActions.default, null, /* @__PURE__ */ import_react4.default.createElement(import_Button2.default, { onClick: () => setDeleteConfirmId(null), disabled: deleteSubmitting }, "Cancel"), /* @__PURE__ */ import_react4.default.createElement(
|
|
1510
1402
|
import_Button2.default,
|
|
1511
1403
|
{
|
|
1512
1404
|
onClick: handleConfirmDelete,
|
|
@@ -1514,8 +1406,8 @@ var init_KeysTable = __esm({
|
|
|
1514
1406
|
color: "error",
|
|
1515
1407
|
disabled: deleteSubmitting
|
|
1516
1408
|
},
|
|
1517
|
-
deleteSubmitting ? /* @__PURE__ */
|
|
1518
|
-
))), /* @__PURE__ */
|
|
1409
|
+
deleteSubmitting ? /* @__PURE__ */ import_react4.default.createElement(import_CircularProgress.default, { size: 20 }) : "Revoke"
|
|
1410
|
+
))), /* @__PURE__ */ import_react4.default.createElement(import_Dialog.default, { open: pruneConfirmCount !== null, onClose: () => setPruneConfirmCount(null), maxWidth: "xs", fullWidth: true }, /* @__PURE__ */ import_react4.default.createElement(import_DialogTitle.default, null, "Prune Expired Keys?"), /* @__PURE__ */ import_react4.default.createElement(import_DialogContent.default, null, /* @__PURE__ */ import_react4.default.createElement(import_Typography3.default, null, "This will permanently delete ", pruneConfirmCount, " expired key", pruneConfirmCount !== 1 ? "s" : "", " from LiteLLM. Any integrations using them will stop working immediately.")), /* @__PURE__ */ import_react4.default.createElement(import_DialogActions.default, null, /* @__PURE__ */ import_react4.default.createElement(import_Button2.default, { onClick: () => setPruneConfirmCount(null), disabled: pruneSubmitting }, "Cancel"), /* @__PURE__ */ import_react4.default.createElement(
|
|
1519
1411
|
import_Button2.default,
|
|
1520
1412
|
{
|
|
1521
1413
|
onClick: handlePruneExpired,
|
|
@@ -1523,7 +1415,7 @@ var init_KeysTable = __esm({
|
|
|
1523
1415
|
color: "error",
|
|
1524
1416
|
disabled: pruneSubmitting
|
|
1525
1417
|
},
|
|
1526
|
-
pruneSubmitting ? /* @__PURE__ */
|
|
1418
|
+
pruneSubmitting ? /* @__PURE__ */ import_react4.default.createElement(import_CircularProgress.default, { size: 20 }) : "Prune"
|
|
1527
1419
|
))));
|
|
1528
1420
|
};
|
|
1529
1421
|
}
|
|
@@ -1543,7 +1435,7 @@ var init_format = __esm({
|
|
|
1543
1435
|
}
|
|
1544
1436
|
});
|
|
1545
1437
|
|
|
1546
|
-
// src/components/
|
|
1438
|
+
// src/components/KeyFormDialog.tsx
|
|
1547
1439
|
function trimSlash(url) {
|
|
1548
1440
|
return url.replace(/\/+$/, "");
|
|
1549
1441
|
}
|
|
@@ -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,11 +1553,11 @@ function formatContextWindow2(maxInput, maxOutput) {
|
|
|
1661
1553
|
if (outPart) return `ctx ${outPart} out`;
|
|
1662
1554
|
return null;
|
|
1663
1555
|
}
|
|
1664
|
-
var
|
|
1665
|
-
var
|
|
1666
|
-
"src/components/
|
|
1556
|
+
var import_react5, import_Box4, import_Typography4, import_Dialog2, import_DialogTitle2, import_DialogContent2, import_DialogActions2, import_TextField2, import_MenuItem, import_Button3, import_IconButton2, import_CircularProgress2, import_Autocomplete, import_Checkbox, import_FormControlLabel, import_Alert, import_Accordion, import_AccordionSummary, import_AccordionDetails, import_Tabs, import_Tab, import_icons_material3, 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
|
+
import_react5 = __toESM(require("react"));
|
|
1669
1561
|
import_Box4 = __toESM(require("@mui/material/Box"));
|
|
1670
1562
|
import_Typography4 = __toESM(require("@mui/material/Typography"));
|
|
1671
1563
|
import_Dialog2 = __toESM(require("@mui/material/Dialog"));
|
|
@@ -1677,7 +1569,7 @@ var init_GenerateKeyDialog = __esm({
|
|
|
1677
1569
|
import_Button3 = __toESM(require("@mui/material/Button"));
|
|
1678
1570
|
import_IconButton2 = __toESM(require("@mui/material/IconButton"));
|
|
1679
1571
|
import_CircularProgress2 = __toESM(require("@mui/material/CircularProgress"));
|
|
1680
|
-
|
|
1572
|
+
import_Autocomplete = __toESM(require("@mui/material/Autocomplete"));
|
|
1681
1573
|
import_Checkbox = __toESM(require("@mui/material/Checkbox"));
|
|
1682
1574
|
import_FormControlLabel = __toESM(require("@mui/material/FormControlLabel"));
|
|
1683
1575
|
import_Alert = __toESM(require("@mui/material/Alert"));
|
|
@@ -1693,8 +1585,8 @@ var init_GenerateKeyDialog = __esm({
|
|
|
1693
1585
|
const hash = Math.random().toString(36).slice(2, 8);
|
|
1694
1586
|
return `${base}-${hash}`;
|
|
1695
1587
|
};
|
|
1696
|
-
|
|
1697
|
-
alias:
|
|
1588
|
+
createForm = (username) => ({
|
|
1589
|
+
alias: generateDefaultAlias(username),
|
|
1698
1590
|
models: [],
|
|
1699
1591
|
duration: "30d",
|
|
1700
1592
|
max_budget: 100,
|
|
@@ -1703,6 +1595,13 @@ var init_GenerateKeyDialog = __esm({
|
|
|
1703
1595
|
team_id: void 0,
|
|
1704
1596
|
key_type: "llm_api"
|
|
1705
1597
|
});
|
|
1598
|
+
editForm = (k) => ({
|
|
1599
|
+
key_alias: k.key_alias ?? "",
|
|
1600
|
+
models: k.models ?? [],
|
|
1601
|
+
max_budget: k.max_budget,
|
|
1602
|
+
tpm_limit: k.tpm_limit,
|
|
1603
|
+
rpm_limit: k.rpm_limit
|
|
1604
|
+
});
|
|
1706
1605
|
ALL_PROXY_MODELS = "all-proxy-models";
|
|
1707
1606
|
SNIPPET_FILE_HINTS = {
|
|
1708
1607
|
opencode: "Add to ~/.config/opencode/opencode.json",
|
|
@@ -1710,18 +1609,18 @@ var init_GenerateKeyDialog = __esm({
|
|
|
1710
1609
|
"claude-code": "Requires Claude Code CLI installed"
|
|
1711
1610
|
};
|
|
1712
1611
|
SnippetTabs = ({ snippets, model, onCopy }) => {
|
|
1713
|
-
const [tab, setTab] = (0,
|
|
1612
|
+
const [tab, setTab] = (0, import_react5.useState)("curl");
|
|
1714
1613
|
const snippetKey = tab === "claude-code" ? "claudeCode" : tab;
|
|
1715
1614
|
const code = snippets[snippetKey];
|
|
1716
1615
|
const fileHint = SNIPPET_FILE_HINTS[tab];
|
|
1717
|
-
return /* @__PURE__ */
|
|
1616
|
+
return /* @__PURE__ */ import_react5.default.createElement(import_Box4.default, null, /* @__PURE__ */ import_react5.default.createElement(import_Tabs.default, { value: tab, onChange: (_, v) => setTab(v), sx: { mb: 1 }, variant: "scrollable" }, /* @__PURE__ */ import_react5.default.createElement(import_Tab.default, { label: "curl", value: "curl" }), /* @__PURE__ */ import_react5.default.createElement(import_Tab.default, { label: "OpenAI SDK", value: "openai" }), /* @__PURE__ */ import_react5.default.createElement(import_Tab.default, { label: "opencode", value: "opencode" }), /* @__PURE__ */ import_react5.default.createElement(import_Tab.default, { label: "pi", value: "pi" }), /* @__PURE__ */ import_react5.default.createElement(import_Tab.default, { label: "Claude Code", value: "claude-code" })), fileHint && /* @__PURE__ */ import_react5.default.createElement(import_Typography4.default, { variant: "caption", color: "text.secondary", display: "block", mb: 0.5 }, fileHint), /* @__PURE__ */ import_react5.default.createElement(
|
|
1718
1617
|
import_Box4.default,
|
|
1719
1618
|
{
|
|
1720
1619
|
position: "relative",
|
|
1721
1620
|
p: 1.5,
|
|
1722
1621
|
sx: { backgroundColor: "action.hover", border: "1px solid", borderColor: "divider", borderRadius: 1 }
|
|
1723
1622
|
},
|
|
1724
|
-
/* @__PURE__ */
|
|
1623
|
+
/* @__PURE__ */ import_react5.default.createElement(
|
|
1725
1624
|
import_IconButton2.default,
|
|
1726
1625
|
{
|
|
1727
1626
|
size: "small",
|
|
@@ -1729,9 +1628,9 @@ var init_GenerateKeyDialog = __esm({
|
|
|
1729
1628
|
title: "Copy snippet",
|
|
1730
1629
|
sx: { position: "absolute", top: 4, right: 4 }
|
|
1731
1630
|
},
|
|
1732
|
-
/* @__PURE__ */
|
|
1631
|
+
/* @__PURE__ */ import_react5.default.createElement(import_icons_material3.ContentCopy, { fontSize: "small" })
|
|
1733
1632
|
),
|
|
1734
|
-
/* @__PURE__ */
|
|
1633
|
+
/* @__PURE__ */ import_react5.default.createElement(
|
|
1735
1634
|
import_Typography4.default,
|
|
1736
1635
|
{
|
|
1737
1636
|
component: "pre",
|
|
@@ -1746,50 +1645,83 @@ var init_GenerateKeyDialog = __esm({
|
|
|
1746
1645
|
},
|
|
1747
1646
|
code
|
|
1748
1647
|
),
|
|
1749
|
-
model && /* @__PURE__ */
|
|
1648
|
+
model && /* @__PURE__ */ import_react5.default.createElement(import_Typography4.default, { variant: "caption", color: "text.secondary", display: "block", mt: 1 }, "Using model \u201C", model, "\u201D \u2014 swap it for any model you have access to.")
|
|
1750
1649
|
));
|
|
1751
1650
|
};
|
|
1752
|
-
|
|
1651
|
+
KeyFormDialog = ({
|
|
1753
1652
|
open,
|
|
1754
1653
|
onClose,
|
|
1654
|
+
mode,
|
|
1655
|
+
keyToEdit = null,
|
|
1755
1656
|
keys,
|
|
1756
1657
|
models,
|
|
1757
1658
|
teams,
|
|
1758
1659
|
username,
|
|
1759
1660
|
keyGenerationSettings,
|
|
1760
|
-
|
|
1661
|
+
onCreateKey,
|
|
1662
|
+
onUpdateKey,
|
|
1663
|
+
onResetKeySpend,
|
|
1761
1664
|
onGetConfig
|
|
1762
1665
|
}) => {
|
|
1763
|
-
const
|
|
1764
|
-
const [
|
|
1765
|
-
const [
|
|
1766
|
-
const [
|
|
1767
|
-
const [
|
|
1768
|
-
const [
|
|
1769
|
-
const [
|
|
1770
|
-
(0,
|
|
1666
|
+
const isCreate = mode === "create";
|
|
1667
|
+
const [generateForm, setGenerateForm] = (0, import_react5.useState)(createForm(username));
|
|
1668
|
+
const [editFormState, setEditFormState] = (0, import_react5.useState)({});
|
|
1669
|
+
const [unlimitedBudget, setUnlimitedBudget] = (0, import_react5.useState)(false);
|
|
1670
|
+
const [submitting, setSubmitting] = (0, import_react5.useState)(false);
|
|
1671
|
+
const [newKeyValue, setNewKeyValue] = (0, import_react5.useState)(null);
|
|
1672
|
+
const [newKeySnippets, setNewKeySnippets] = (0, import_react5.useState)(null);
|
|
1673
|
+
const [newKeyModel, setNewKeyModel] = (0, import_react5.useState)("");
|
|
1674
|
+
const [generateError, setGenerateError] = (0, import_react5.useState)(null);
|
|
1675
|
+
const [editError, setEditError] = (0, import_react5.useState)(null);
|
|
1676
|
+
const [resetSpendConfirm, setResetSpendConfirm] = (0, import_react5.useState)(false);
|
|
1677
|
+
const [resetSpendSubmitting, setResetSpendSubmitting] = (0, import_react5.useState)(false);
|
|
1678
|
+
(0, import_react5.useEffect)(() => {
|
|
1771
1679
|
if (open) {
|
|
1772
|
-
|
|
1773
|
-
|
|
1680
|
+
if (isCreate) {
|
|
1681
|
+
setGenerateForm(createForm(username));
|
|
1682
|
+
} else if (keyToEdit) {
|
|
1683
|
+
setEditFormState(editForm(keyToEdit));
|
|
1684
|
+
setUnlimitedBudget(
|
|
1685
|
+
keyGenerationSettings?.allowUnlimitedBudget === true && (keyToEdit.max_budget === null || keyToEdit.max_budget === void 0)
|
|
1686
|
+
);
|
|
1687
|
+
} else {
|
|
1688
|
+
setEditFormState({});
|
|
1689
|
+
setUnlimitedBudget(false);
|
|
1690
|
+
}
|
|
1774
1691
|
setNewKeyValue(null);
|
|
1775
1692
|
setNewKeySnippets(null);
|
|
1776
1693
|
setNewKeyModel("");
|
|
1777
1694
|
setGenerateError(null);
|
|
1695
|
+
setEditError(null);
|
|
1696
|
+
setResetSpendConfirm(false);
|
|
1778
1697
|
}
|
|
1779
|
-
}, [open, username]);
|
|
1698
|
+
}, [open, isCreate, keyToEdit, username, keyGenerationSettings?.allowUnlimitedBudget]);
|
|
1780
1699
|
const allowUnlimitedBudget = keyGenerationSettings?.allowUnlimitedBudget ?? false;
|
|
1781
1700
|
const teamRequired = keyGenerationSettings?.teamRequired ?? true;
|
|
1782
|
-
const
|
|
1783
|
-
const
|
|
1701
|
+
const formData = isCreate ? generateForm : editFormState;
|
|
1702
|
+
const setFormData = (patch) => {
|
|
1703
|
+
if (isCreate) {
|
|
1704
|
+
setGenerateForm((prev) => ({ ...prev, ...patch }));
|
|
1705
|
+
} else {
|
|
1706
|
+
setEditFormState((prev) => ({ ...prev, ...patch }));
|
|
1707
|
+
}
|
|
1708
|
+
};
|
|
1709
|
+
const currentAlias = (isCreate ? generateForm.alias : editFormState.key_alias) ?? "";
|
|
1710
|
+
const setAliasField = (value) => {
|
|
1711
|
+
if (isCreate) setFormData({ alias: value });
|
|
1712
|
+
else setFormData({ key_alias: value });
|
|
1713
|
+
};
|
|
1714
|
+
const selectedTeam = teams.find((t) => t.team_id === (isCreate ? generateForm.team_id : void 0)) ?? null;
|
|
1715
|
+
const availableModels = (0, import_react5.useMemo)(() => {
|
|
1784
1716
|
return models.filter((m) => isModelAllowedByTeam(m, selectedTeam?.models));
|
|
1785
1717
|
}, [models, selectedTeam]);
|
|
1786
1718
|
const selectedModels = availableModels.filter((m) => (formData.models || []).includes(m.model_name));
|
|
1787
|
-
const aliasError = !
|
|
1788
|
-
const teamError = teamRequired && !
|
|
1719
|
+
const aliasError = !currentAlias.trim();
|
|
1720
|
+
const teamError = isCreate && teamRequired && !generateForm.team_id;
|
|
1789
1721
|
const budgetInvalid = !unlimitedBudget && (formData.max_budget === void 0 || formData.max_budget === null || formData.max_budget <= 0);
|
|
1790
|
-
const
|
|
1791
|
-
const aliasDuplicate = !!
|
|
1792
|
-
const budgetEstimate = (0,
|
|
1722
|
+
const canSubmit = !aliasError && !teamError && !budgetInvalid && !submitting;
|
|
1723
|
+
const aliasDuplicate = !!currentAlias && keys.some((k) => k.key_alias === currentAlias && k !== keyToEdit);
|
|
1724
|
+
const budgetEstimate = (0, import_react5.useMemo)(() => {
|
|
1793
1725
|
if (unlimitedBudget || budgetInvalid) return null;
|
|
1794
1726
|
const inputCosts = selectedModels.map((m) => m.input_cost_per_token).filter((c) => typeof c === "number" && c > 0);
|
|
1795
1727
|
if (inputCosts.length === 0) return null;
|
|
@@ -1801,11 +1733,11 @@ var init_GenerateKeyDialog = __esm({
|
|
|
1801
1733
|
setGenerateError(null);
|
|
1802
1734
|
try {
|
|
1803
1735
|
const request = {
|
|
1804
|
-
...
|
|
1805
|
-
max_budget: unlimitedBudget ? null :
|
|
1736
|
+
...generateForm,
|
|
1737
|
+
max_budget: unlimitedBudget ? null : generateForm.max_budget
|
|
1806
1738
|
};
|
|
1807
|
-
const response = await
|
|
1808
|
-
const model =
|
|
1739
|
+
const response = await onCreateKey(request);
|
|
1740
|
+
const model = generateForm.models?.[0] ?? availableModels[0]?.model_name ?? "";
|
|
1809
1741
|
setNewKeyValue(response.key);
|
|
1810
1742
|
setNewKeyModel(model);
|
|
1811
1743
|
setNewKeySnippets(null);
|
|
@@ -1814,7 +1746,7 @@ var init_GenerateKeyDialog = __esm({
|
|
|
1814
1746
|
setNewKeySnippets(buildSnippets(config.baseUrl, response.key, model));
|
|
1815
1747
|
} catch {
|
|
1816
1748
|
}
|
|
1817
|
-
|
|
1749
|
+
setGenerateForm(createForm(username));
|
|
1818
1750
|
setUnlimitedBudget(false);
|
|
1819
1751
|
} catch (error) {
|
|
1820
1752
|
setGenerateError(error?.message ?? "Failed to generate key");
|
|
@@ -1822,27 +1754,58 @@ var init_GenerateKeyDialog = __esm({
|
|
|
1822
1754
|
setSubmitting(false);
|
|
1823
1755
|
}
|
|
1824
1756
|
};
|
|
1757
|
+
const handleUpdate = async () => {
|
|
1758
|
+
if (!keyToEdit) return;
|
|
1759
|
+
setSubmitting(true);
|
|
1760
|
+
setEditError(null);
|
|
1761
|
+
try {
|
|
1762
|
+
const request = {
|
|
1763
|
+
...editFormState,
|
|
1764
|
+
max_budget: unlimitedBudget ? null : editFormState.max_budget
|
|
1765
|
+
};
|
|
1766
|
+
await onUpdateKey(keyToEdit.token ?? keyToEdit.key, request);
|
|
1767
|
+
onClose();
|
|
1768
|
+
} catch (error) {
|
|
1769
|
+
setEditError(error?.message ?? "Failed to update key");
|
|
1770
|
+
} finally {
|
|
1771
|
+
setSubmitting(false);
|
|
1772
|
+
}
|
|
1773
|
+
};
|
|
1774
|
+
const handleResetSpend = async () => {
|
|
1775
|
+
if (!keyToEdit || !onResetKeySpend) return;
|
|
1776
|
+
setResetSpendSubmitting(true);
|
|
1777
|
+
try {
|
|
1778
|
+
await onResetKeySpend(keyToEdit.token ?? keyToEdit.key);
|
|
1779
|
+
setResetSpendConfirm(false);
|
|
1780
|
+
onClose();
|
|
1781
|
+
} finally {
|
|
1782
|
+
setResetSpendSubmitting(false);
|
|
1783
|
+
}
|
|
1784
|
+
};
|
|
1825
1785
|
const handleClose = () => {
|
|
1826
1786
|
onClose();
|
|
1827
1787
|
setGenerateError(null);
|
|
1788
|
+
setEditError(null);
|
|
1828
1789
|
setNewKeyValue(null);
|
|
1829
1790
|
setNewKeySnippets(null);
|
|
1830
|
-
|
|
1791
|
+
setGenerateForm(createForm(username));
|
|
1792
|
+
setEditFormState({});
|
|
1831
1793
|
setUnlimitedBudget(false);
|
|
1794
|
+
setResetSpendConfirm(false);
|
|
1832
1795
|
};
|
|
1833
1796
|
const copyToClipboard = (text) => {
|
|
1834
1797
|
navigator.clipboard.writeText(text);
|
|
1835
1798
|
};
|
|
1836
1799
|
const modelOption = (m) => {
|
|
1837
|
-
const inCost =
|
|
1838
|
-
const outCost =
|
|
1839
|
-
const ctx =
|
|
1840
|
-
return /* @__PURE__ */
|
|
1800
|
+
const inCost = fmtCost(m.input_cost_per_token);
|
|
1801
|
+
const outCost = fmtCost(m.output_cost_per_token);
|
|
1802
|
+
const ctx = formatContextWindow(m.max_input_tokens, m.max_output_tokens);
|
|
1803
|
+
return /* @__PURE__ */ import_react5.default.createElement(import_Box4.default, null, /* @__PURE__ */ import_react5.default.createElement("span", null, m.model_name), m.supports_function_calling && " \u{1F527}", m.supports_vision && " \u{1F441}\uFE0F", ctx && /* @__PURE__ */ import_react5.default.createElement(import_Typography4.default, { variant: "caption", color: "text.secondary", sx: { ml: 1 } }, ctx), (inCost || outCost) && /* @__PURE__ */ import_react5.default.createElement(import_Typography4.default, { variant: "caption", color: "text.secondary", sx: { ml: 1 } }, inCost, " in \xB7 ", outCost, " out"));
|
|
1841
1804
|
};
|
|
1842
1805
|
const renderTeamField = () => {
|
|
1843
1806
|
if (teams.length > 0) {
|
|
1844
|
-
return /* @__PURE__ */
|
|
1845
|
-
|
|
1807
|
+
return /* @__PURE__ */ import_react5.default.createElement(
|
|
1808
|
+
import_Autocomplete.default,
|
|
1846
1809
|
{
|
|
1847
1810
|
options: teams,
|
|
1848
1811
|
getOptionLabel: (t) => t.team_alias || t.team_id,
|
|
@@ -1852,28 +1815,61 @@ var init_GenerateKeyDialog = __esm({
|
|
|
1852
1815
|
const model = models.find((m) => m.model_name === name);
|
|
1853
1816
|
return model ? isModelAllowedByTeam(model, team?.models) : false;
|
|
1854
1817
|
});
|
|
1855
|
-
setFormData({
|
|
1818
|
+
setFormData({ team_id: team?.team_id, models: restrictedModels });
|
|
1856
1819
|
},
|
|
1857
|
-
|
|
1820
|
+
disabled: !isCreate,
|
|
1821
|
+
renderInput: (params) => /* @__PURE__ */ import_react5.default.createElement(
|
|
1858
1822
|
import_TextField2.default,
|
|
1859
1823
|
{
|
|
1860
1824
|
...params,
|
|
1861
1825
|
label: "Team",
|
|
1862
1826
|
error: teamError,
|
|
1863
|
-
helperText: teamHelperText(teamError, teamRequired),
|
|
1864
|
-
required: teamRequired,
|
|
1827
|
+
helperText: isCreate ? teamHelperText(teamError, teamRequired) : "Team binding is fixed after creation \u2014 delete and recreate the key to change it",
|
|
1828
|
+
required: isCreate && teamRequired,
|
|
1865
1829
|
fullWidth: true
|
|
1866
1830
|
}
|
|
1867
1831
|
)
|
|
1868
1832
|
}
|
|
1869
1833
|
);
|
|
1870
1834
|
}
|
|
1871
|
-
if (teamRequired) {
|
|
1872
|
-
return /* @__PURE__ */
|
|
1835
|
+
if (teamRequired && isCreate) {
|
|
1836
|
+
return /* @__PURE__ */ import_react5.default.createElement(import_Typography4.default, { variant: "body2", color: "error" }, "Team selection is required, but you don't belong to any team yet \u2014 contact your administrator.");
|
|
1873
1837
|
}
|
|
1874
1838
|
return null;
|
|
1875
1839
|
};
|
|
1876
|
-
|
|
1840
|
+
const editKeyId = keyToEdit?.token ?? keyToEdit?.key ?? "";
|
|
1841
|
+
const renderDangerZone = () => {
|
|
1842
|
+
if (isCreate || !onResetKeySpend) return null;
|
|
1843
|
+
return /* @__PURE__ */ import_react5.default.createElement(import_Box4.default, { mt: 1, pt: 2, borderTop: "1px solid", sx: { borderColor: "divider" } }, /* @__PURE__ */ import_react5.default.createElement(import_Typography4.default, { variant: "caption", color: "text.secondary", display: "block", mb: 1 }, "Danger Zone"), !resetSpendConfirm ? /* @__PURE__ */ import_react5.default.createElement(
|
|
1844
|
+
import_Button3.default,
|
|
1845
|
+
{
|
|
1846
|
+
size: "small",
|
|
1847
|
+
color: "warning",
|
|
1848
|
+
variant: "outlined",
|
|
1849
|
+
onClick: () => setResetSpendConfirm(true)
|
|
1850
|
+
},
|
|
1851
|
+
"Reset Spend to $0"
|
|
1852
|
+
) : /* @__PURE__ */ import_react5.default.createElement(import_Box4.default, { display: "flex", alignItems: "center", gap: 1, flexWrap: "wrap" }, /* @__PURE__ */ import_react5.default.createElement(import_Typography4.default, { variant: "body2", color: "warning.main" }, "Zero out spend counter?"), /* @__PURE__ */ import_react5.default.createElement(
|
|
1853
|
+
import_Button3.default,
|
|
1854
|
+
{
|
|
1855
|
+
size: "small",
|
|
1856
|
+
color: "warning",
|
|
1857
|
+
variant: "contained",
|
|
1858
|
+
disabled: resetSpendSubmitting,
|
|
1859
|
+
onClick: handleResetSpend
|
|
1860
|
+
},
|
|
1861
|
+
resetSpendSubmitting ? /* @__PURE__ */ import_react5.default.createElement(import_CircularProgress2.default, { size: 16 }) : "Confirm"
|
|
1862
|
+
), /* @__PURE__ */ import_react5.default.createElement(import_Button3.default, { size: "small", onClick: () => setResetSpendConfirm(false) }, "Cancel")));
|
|
1863
|
+
};
|
|
1864
|
+
const maskKey = (key) => {
|
|
1865
|
+
if (key.length <= 8) return "***";
|
|
1866
|
+
return `${key.slice(0, 4)}...${key.slice(-4)}`;
|
|
1867
|
+
};
|
|
1868
|
+
let dialogTitle = "Edit Key";
|
|
1869
|
+
if (newKeyValue) dialogTitle = "Key Generated";
|
|
1870
|
+
else if (isCreate) dialogTitle = "Generate New Key";
|
|
1871
|
+
const submitLabel = isCreate ? "Generate" : "Save";
|
|
1872
|
+
return /* @__PURE__ */ import_react5.default.createElement(import_Dialog2.default, { open, onClose: handleClose, maxWidth: "sm", fullWidth: true }, /* @__PURE__ */ import_react5.default.createElement(import_DialogTitle2.default, null, dialogTitle), /* @__PURE__ */ import_react5.default.createElement(import_DialogContent2.default, null, newKeyValue ? /* @__PURE__ */ import_react5.default.createElement(import_Box4.default, null, /* @__PURE__ */ import_react5.default.createElement(import_Typography4.default, { variant: "body2", color: "text.secondary", gutterBottom: true }, "Copy this key now. You won't be able to see it again."), /* @__PURE__ */ import_react5.default.createElement(
|
|
1877
1873
|
import_Box4.default,
|
|
1878
1874
|
{
|
|
1879
1875
|
display: "flex",
|
|
@@ -1888,7 +1884,7 @@ var init_GenerateKeyDialog = __esm({
|
|
|
1888
1884
|
borderRadius: 1
|
|
1889
1885
|
}
|
|
1890
1886
|
},
|
|
1891
|
-
/* @__PURE__ */
|
|
1887
|
+
/* @__PURE__ */ import_react5.default.createElement(
|
|
1892
1888
|
import_Typography4.default,
|
|
1893
1889
|
{
|
|
1894
1890
|
component: "code",
|
|
@@ -1897,8 +1893,8 @@ var init_GenerateKeyDialog = __esm({
|
|
|
1897
1893
|
},
|
|
1898
1894
|
newKeyValue
|
|
1899
1895
|
),
|
|
1900
|
-
/* @__PURE__ */
|
|
1901
|
-
), newKeySnippets && /* @__PURE__ */
|
|
1896
|
+
/* @__PURE__ */ import_react5.default.createElement(import_IconButton2.default, { onClick: () => copyToClipboard(newKeyValue) }, /* @__PURE__ */ import_react5.default.createElement(import_icons_material3.ContentCopy, null))
|
|
1897
|
+
), newKeySnippets && /* @__PURE__ */ import_react5.default.createElement(import_Box4.default, { mt: 2 }, /* @__PURE__ */ import_react5.default.createElement(import_Typography4.default, { variant: "caption", color: "text.secondary", display: "block", gutterBottom: true }, "Public endpoint \u2014 paste into any tool's base URL / API base field"), /* @__PURE__ */ import_react5.default.createElement(
|
|
1902
1898
|
import_Box4.default,
|
|
1903
1899
|
{
|
|
1904
1900
|
display: "flex",
|
|
@@ -1912,7 +1908,7 @@ var init_GenerateKeyDialog = __esm({
|
|
|
1912
1908
|
borderRadius: 1
|
|
1913
1909
|
}
|
|
1914
1910
|
},
|
|
1915
|
-
/* @__PURE__ */
|
|
1911
|
+
/* @__PURE__ */ import_react5.default.createElement(
|
|
1916
1912
|
import_Typography4.default,
|
|
1917
1913
|
{
|
|
1918
1914
|
component: "code",
|
|
@@ -1921,15 +1917,16 @@ var init_GenerateKeyDialog = __esm({
|
|
|
1921
1917
|
},
|
|
1922
1918
|
newKeySnippets.publicEndpoint
|
|
1923
1919
|
),
|
|
1924
|
-
/* @__PURE__ */
|
|
1925
|
-
)), newKeySnippets && /* @__PURE__ */
|
|
1920
|
+
/* @__PURE__ */ import_react5.default.createElement(import_IconButton2.default, { size: "small", onClick: () => copyToClipboard(newKeySnippets.publicEndpoint) }, /* @__PURE__ */ import_react5.default.createElement(import_icons_material3.ContentCopy, { fontSize: "small" }))
|
|
1921
|
+
)), newKeySnippets && /* @__PURE__ */ import_react5.default.createElement(import_Box4.default, { mt: 3 }, /* @__PURE__ */ import_react5.default.createElement(import_Box4.default, { display: "flex", alignItems: "center", gap: 1, mb: 1 }, /* @__PURE__ */ import_react5.default.createElement(import_icons_material3.Code, { fontSize: "small", color: "action" }), /* @__PURE__ */ import_react5.default.createElement(import_Typography4.default, { variant: "subtitle2" }, "Start calling the proxy \u2014 paste and run")), /* @__PURE__ */ import_react5.default.createElement(SnippetTabs, { snippets: newKeySnippets, model: newKeyModel, onCopy: copyToClipboard }))) : /* @__PURE__ */ import_react5.default.createElement(import_Box4.default, { display: "flex", flexDirection: "column", gap: 2, mt: 1 }, !isCreate && /* @__PURE__ */ import_react5.default.createElement(import_Typography4.default, { variant: "body2", color: "text.secondary" }, /* @__PURE__ */ import_react5.default.createElement("code", { style: { fontFamily: "monospace", color: "inherit" } }, maskKey(editKeyId))), generateError && /* @__PURE__ */ import_react5.default.createElement(import_Alert.default, { severity: "error", onClose: () => setGenerateError(null) }, generateError), editError && /* @__PURE__ */ import_react5.default.createElement(import_Alert.default, { severity: "error", onClose: () => setEditError(null) }, editError), /* @__PURE__ */ import_react5.default.createElement(
|
|
1926
1922
|
import_TextField2.default,
|
|
1927
1923
|
{
|
|
1928
1924
|
label: "Alias",
|
|
1929
|
-
value:
|
|
1925
|
+
value: currentAlias,
|
|
1930
1926
|
onChange: (e) => {
|
|
1931
|
-
|
|
1927
|
+
setAliasField(e.target.value);
|
|
1932
1928
|
setGenerateError(null);
|
|
1929
|
+
setEditError(null);
|
|
1933
1930
|
},
|
|
1934
1931
|
error: aliasError,
|
|
1935
1932
|
color: aliasDuplicate ? "warning" : void 0,
|
|
@@ -1937,31 +1934,31 @@ var init_GenerateKeyDialog = __esm({
|
|
|
1937
1934
|
required: true,
|
|
1938
1935
|
fullWidth: true
|
|
1939
1936
|
}
|
|
1940
|
-
), /* @__PURE__ */
|
|
1937
|
+
), !isCreate ? null : /* @__PURE__ */ import_react5.default.createElement(
|
|
1941
1938
|
import_TextField2.default,
|
|
1942
1939
|
{
|
|
1943
1940
|
select: true,
|
|
1944
1941
|
label: "Duration",
|
|
1945
|
-
value:
|
|
1946
|
-
onChange: (e) => setFormData({
|
|
1942
|
+
value: generateForm.duration || "30d",
|
|
1943
|
+
onChange: (e) => setFormData({ duration: e.target.value }),
|
|
1947
1944
|
fullWidth: true
|
|
1948
1945
|
},
|
|
1949
|
-
/* @__PURE__ */
|
|
1950
|
-
/* @__PURE__ */
|
|
1951
|
-
/* @__PURE__ */
|
|
1952
|
-
/* @__PURE__ */
|
|
1953
|
-
/* @__PURE__ */
|
|
1954
|
-
), renderTeamField(), availableModels.length > 0 && /* @__PURE__ */
|
|
1955
|
-
|
|
1946
|
+
/* @__PURE__ */ import_react5.default.createElement(import_MenuItem.default, { value: "1d" }, "1 Day"),
|
|
1947
|
+
/* @__PURE__ */ import_react5.default.createElement(import_MenuItem.default, { value: "7d" }, "7 Days"),
|
|
1948
|
+
/* @__PURE__ */ import_react5.default.createElement(import_MenuItem.default, { value: "30d" }, "30 Days"),
|
|
1949
|
+
/* @__PURE__ */ import_react5.default.createElement(import_MenuItem.default, { value: "90d" }, "90 Days"),
|
|
1950
|
+
/* @__PURE__ */ import_react5.default.createElement(import_MenuItem.default, { value: "1y" }, "1 Year")
|
|
1951
|
+
), renderTeamField(), availableModels.length > 0 && /* @__PURE__ */ import_react5.default.createElement(
|
|
1952
|
+
import_Autocomplete.default,
|
|
1956
1953
|
{
|
|
1957
1954
|
multiple: true,
|
|
1958
1955
|
options: availableModels,
|
|
1959
1956
|
groupBy: (m) => m.mode || "other",
|
|
1960
1957
|
getOptionLabel: (m) => m.model_name,
|
|
1961
1958
|
value: selectedModels,
|
|
1962
|
-
onChange: (_e, selected) => setFormData({
|
|
1963
|
-
renderOption: (props, m) => /* @__PURE__ */
|
|
1964
|
-
renderInput: (params) => /* @__PURE__ */
|
|
1959
|
+
onChange: (_e, selected) => setFormData({ models: selected.map((m) => m.model_name) }),
|
|
1960
|
+
renderOption: (props, m) => /* @__PURE__ */ import_react5.default.createElement("li", { ...props }, modelOption(m)),
|
|
1961
|
+
renderInput: (params) => /* @__PURE__ */ import_react5.default.createElement(
|
|
1965
1962
|
import_TextField2.default,
|
|
1966
1963
|
{
|
|
1967
1964
|
...params,
|
|
@@ -1971,78 +1968,78 @@ var init_GenerateKeyDialog = __esm({
|
|
|
1971
1968
|
}
|
|
1972
1969
|
)
|
|
1973
1970
|
}
|
|
1974
|
-
), allowUnlimitedBudget && /* @__PURE__ */
|
|
1971
|
+
), allowUnlimitedBudget && /* @__PURE__ */ import_react5.default.createElement(
|
|
1975
1972
|
import_FormControlLabel.default,
|
|
1976
1973
|
{
|
|
1977
|
-
control: /* @__PURE__ */
|
|
1974
|
+
control: /* @__PURE__ */ import_react5.default.createElement(
|
|
1978
1975
|
import_Checkbox.default,
|
|
1979
1976
|
{
|
|
1980
1977
|
checked: unlimitedBudget,
|
|
1981
1978
|
onChange: (e) => {
|
|
1982
1979
|
setUnlimitedBudget(e.target.checked);
|
|
1983
1980
|
if (e.target.checked) {
|
|
1984
|
-
setFormData({
|
|
1981
|
+
setFormData({ max_budget: null });
|
|
1985
1982
|
} else {
|
|
1986
|
-
setFormData({
|
|
1983
|
+
setFormData({ max_budget: editFormState.max_budget ?? generateForm.max_budget ?? 100 });
|
|
1987
1984
|
}
|
|
1988
1985
|
}
|
|
1989
1986
|
}
|
|
1990
1987
|
),
|
|
1991
1988
|
label: "Unlimited budget"
|
|
1992
1989
|
}
|
|
1993
|
-
), /* @__PURE__ */
|
|
1990
|
+
), /* @__PURE__ */ import_react5.default.createElement(
|
|
1994
1991
|
import_TextField2.default,
|
|
1995
1992
|
{
|
|
1996
1993
|
label: "Max Budget (USD)",
|
|
1997
1994
|
type: "number",
|
|
1998
1995
|
value: unlimitedBudget ? "" : formData.max_budget ?? "",
|
|
1999
|
-
onChange: (e) => setFormData({
|
|
1996
|
+
onChange: (e) => setFormData({ max_budget: e.target.value ? Number(e.target.value) : void 0 }),
|
|
2000
1997
|
error: budgetInvalid,
|
|
2001
|
-
helperText: budgetHelperText(budgetInvalid, budgetEstimate),
|
|
1998
|
+
helperText: budgetHelperText(budgetInvalid, budgetEstimate, unlimitedBudget),
|
|
2002
1999
|
disabled: unlimitedBudget,
|
|
2003
2000
|
required: true,
|
|
2004
2001
|
fullWidth: true
|
|
2005
2002
|
}
|
|
2006
|
-
), /* @__PURE__ */
|
|
2003
|
+
), /* @__PURE__ */ import_react5.default.createElement(import_Accordion.default, { disableGutters: true, elevation: 0, sx: { border: "1px solid", borderColor: "divider", "&:before": { display: "none" } } }, /* @__PURE__ */ import_react5.default.createElement(import_AccordionSummary.default, { expandIcon: /* @__PURE__ */ import_react5.default.createElement(import_icons_material3.ExpandMore, null) }, /* @__PURE__ */ import_react5.default.createElement(import_Typography4.default, { variant: "body2", fontWeight: 600 }, "Advanced")), /* @__PURE__ */ import_react5.default.createElement(import_AccordionDetails.default, { sx: { display: "flex", flexDirection: "column", gap: 2 } }, /* @__PURE__ */ import_react5.default.createElement(
|
|
2007
2004
|
import_TextField2.default,
|
|
2008
2005
|
{
|
|
2009
2006
|
label: "TPM Limit",
|
|
2010
2007
|
type: "number",
|
|
2011
2008
|
value: formData.tpm_limit ?? "",
|
|
2012
|
-
onChange: (e) => setFormData({
|
|
2009
|
+
onChange: (e) => setFormData({ tpm_limit: e.target.value ? Number(e.target.value) : void 0 }),
|
|
2013
2010
|
helperText: "Max tokens per minute this key can consume across all models. Leave blank for no limit.",
|
|
2014
2011
|
fullWidth: true
|
|
2015
2012
|
}
|
|
2016
|
-
), /* @__PURE__ */
|
|
2013
|
+
), /* @__PURE__ */ import_react5.default.createElement(
|
|
2017
2014
|
import_TextField2.default,
|
|
2018
2015
|
{
|
|
2019
2016
|
label: "RPM Limit",
|
|
2020
2017
|
type: "number",
|
|
2021
2018
|
value: formData.rpm_limit ?? "",
|
|
2022
|
-
onChange: (e) => setFormData({
|
|
2019
|
+
onChange: (e) => setFormData({ rpm_limit: e.target.value ? Number(e.target.value) : void 0 }),
|
|
2023
2020
|
helperText: "Max requests per minute this key can make across all models. Leave blank for no limit.",
|
|
2024
2021
|
fullWidth: true
|
|
2025
2022
|
}
|
|
2026
|
-
))))), /* @__PURE__ */
|
|
2023
|
+
))), renderDangerZone())), /* @__PURE__ */ import_react5.default.createElement(import_DialogActions2.default, null, newKeyValue ? /* @__PURE__ */ import_react5.default.createElement(import_Button3.default, { onClick: handleClose, variant: "contained", color: "success" }, "Done") : /* @__PURE__ */ import_react5.default.createElement(import_react5.default.Fragment, null, /* @__PURE__ */ import_react5.default.createElement(import_Button3.default, { onClick: handleClose }, "Cancel"), /* @__PURE__ */ import_react5.default.createElement(
|
|
2027
2024
|
import_Button3.default,
|
|
2028
2025
|
{
|
|
2029
|
-
onClick: handleGenerate,
|
|
2026
|
+
onClick: isCreate ? handleGenerate : handleUpdate,
|
|
2030
2027
|
variant: "contained",
|
|
2031
2028
|
color: "primary",
|
|
2032
|
-
disabled: !
|
|
2029
|
+
disabled: !canSubmit
|
|
2033
2030
|
},
|
|
2034
|
-
submitting ? /* @__PURE__ */
|
|
2031
|
+
submitting ? /* @__PURE__ */ import_react5.default.createElement(import_CircularProgress2.default, { size: 24 }) : submitLabel
|
|
2035
2032
|
))));
|
|
2036
2033
|
};
|
|
2037
2034
|
}
|
|
2038
2035
|
});
|
|
2039
2036
|
|
|
2040
2037
|
// src/components/ManageTeamDialog.tsx
|
|
2041
|
-
var
|
|
2038
|
+
var import_react6, import_Dialog3, import_DialogTitle3, import_DialogContent3, import_DialogActions3, import_TextField3, import_Button4, import_Alert2, import_Checkbox2, import_FormControlLabel2, import_Autocomplete2, import_CircularProgress3, import_MenuItem2, import_Box5, import_Divider2, import_Typography5, import_IconButton3, import_Paper3, import_Table2, import_TableBody2, import_TableCell2, import_TableContainer2, import_TableHead2, import_TableRow2, import_icons_material4, import_core_plugin_api2, import_plugin_catalog_react, import_catalog_model, import_react_use, BUDGET_DURATION_OPTIONS, DEFAULT_BUDGET_DURATION, FALLBACK_BUDGET_CEILING, ManageTeamDialog;
|
|
2042
2039
|
var init_ManageTeamDialog = __esm({
|
|
2043
2040
|
"src/components/ManageTeamDialog.tsx"() {
|
|
2044
2041
|
"use strict";
|
|
2045
|
-
|
|
2042
|
+
import_react6 = __toESM(require("react"));
|
|
2046
2043
|
import_Dialog3 = __toESM(require("@mui/material/Dialog"));
|
|
2047
2044
|
import_DialogTitle3 = __toESM(require("@mui/material/DialogTitle"));
|
|
2048
2045
|
import_DialogContent3 = __toESM(require("@mui/material/DialogContent"));
|
|
@@ -2052,7 +2049,7 @@ var init_ManageTeamDialog = __esm({
|
|
|
2052
2049
|
import_Alert2 = __toESM(require("@mui/material/Alert"));
|
|
2053
2050
|
import_Checkbox2 = __toESM(require("@mui/material/Checkbox"));
|
|
2054
2051
|
import_FormControlLabel2 = __toESM(require("@mui/material/FormControlLabel"));
|
|
2055
|
-
|
|
2052
|
+
import_Autocomplete2 = __toESM(require("@mui/material/Autocomplete"));
|
|
2056
2053
|
import_CircularProgress3 = __toESM(require("@mui/material/CircularProgress"));
|
|
2057
2054
|
import_MenuItem2 = __toESM(require("@mui/material/MenuItem"));
|
|
2058
2055
|
import_Box5 = __toESM(require("@mui/material/Box"));
|
|
@@ -2099,29 +2096,29 @@ var init_ManageTeamDialog = __esm({
|
|
|
2099
2096
|
mcpServers,
|
|
2100
2097
|
onSaveMcpServers
|
|
2101
2098
|
}) => {
|
|
2102
|
-
const [alias, setAlias] = (0,
|
|
2103
|
-
const [models, setModels] = (0,
|
|
2104
|
-
const [maxBudget, setMaxBudget] = (0,
|
|
2105
|
-
const [unlimited, setUnlimited] = (0,
|
|
2106
|
-
const [budgetDuration, setBudgetDuration] = (0,
|
|
2107
|
-
const [submitting, setSubmitting] = (0,
|
|
2108
|
-
const [error, setError] = (0,
|
|
2109
|
-
const [memberQuery, setMemberQuery] = (0,
|
|
2110
|
-
const [memberRef, setMemberRef] = (0,
|
|
2111
|
-
const [memberBudget, setMemberBudget] = (0,
|
|
2112
|
-
const [memberBusy, setMemberBusy] = (0,
|
|
2113
|
-
const [memberError, setMemberError] = (0,
|
|
2114
|
-
const [kbIds, setKbIds] = (0,
|
|
2115
|
-
const [kbBusy, setKbBusy] = (0,
|
|
2116
|
-
const [kbError, setKbError] = (0,
|
|
2117
|
-
const [mcpIds, setMcpIds] = (0,
|
|
2118
|
-
const [mcpBusy, setMcpBusy] = (0,
|
|
2119
|
-
const [mcpError, setMcpError] = (0,
|
|
2099
|
+
const [alias, setAlias] = (0, import_react6.useState)("");
|
|
2100
|
+
const [models, setModels] = (0, import_react6.useState)([]);
|
|
2101
|
+
const [maxBudget, setMaxBudget] = (0, import_react6.useState)("");
|
|
2102
|
+
const [unlimited, setUnlimited] = (0, import_react6.useState)(false);
|
|
2103
|
+
const [budgetDuration, setBudgetDuration] = (0, import_react6.useState)("");
|
|
2104
|
+
const [submitting, setSubmitting] = (0, import_react6.useState)(false);
|
|
2105
|
+
const [error, setError] = (0, import_react6.useState)(null);
|
|
2106
|
+
const [memberQuery, setMemberQuery] = (0, import_react6.useState)("");
|
|
2107
|
+
const [memberRef, setMemberRef] = (0, import_react6.useState)("");
|
|
2108
|
+
const [memberBudget, setMemberBudget] = (0, import_react6.useState)("");
|
|
2109
|
+
const [memberBusy, setMemberBusy] = (0, import_react6.useState)(false);
|
|
2110
|
+
const [memberError, setMemberError] = (0, import_react6.useState)(null);
|
|
2111
|
+
const [kbIds, setKbIds] = (0, import_react6.useState)([]);
|
|
2112
|
+
const [kbBusy, setKbBusy] = (0, import_react6.useState)(false);
|
|
2113
|
+
const [kbError, setKbError] = (0, import_react6.useState)(null);
|
|
2114
|
+
const [mcpIds, setMcpIds] = (0, import_react6.useState)([]);
|
|
2115
|
+
const [mcpBusy, setMcpBusy] = (0, import_react6.useState)(false);
|
|
2116
|
+
const [mcpError, setMcpError] = (0, import_react6.useState)(null);
|
|
2120
2117
|
const allowUnlimitedBudget = config?.teamManagement?.allowUnlimitedBudget ?? false;
|
|
2121
2118
|
const maxBudgetCeiling = config?.teamManagement?.maxBudgetCeiling ?? FALLBACK_BUDGET_CEILING;
|
|
2122
2119
|
const hideBudgetForManagers = config?.display?.hideTeamBudgetForManagers ?? false;
|
|
2123
2120
|
const budgetWriteOnly = hideBudgetForManagers && mode === "edit";
|
|
2124
|
-
const members = (0,
|
|
2121
|
+
const members = (0, import_react6.useMemo)(() => team?.members_with_roles ?? [], [team]);
|
|
2125
2122
|
const showMembers = mode === "edit" && !!canManageMembers;
|
|
2126
2123
|
const catalogApi = (0, import_core_plugin_api2.useApi)(import_plugin_catalog_react.catalogApiRef);
|
|
2127
2124
|
const { value: catalogUsers, loading: usersLoading } = (0, import_react_use.useAsync)(async () => {
|
|
@@ -2136,7 +2133,7 @@ var init_ManageTeamDialog = __esm({
|
|
|
2136
2133
|
return [];
|
|
2137
2134
|
}
|
|
2138
2135
|
}, [open, showMembers, catalogApi]);
|
|
2139
|
-
const userOptions = (0,
|
|
2136
|
+
const userOptions = (0, import_react6.useMemo)(() => {
|
|
2140
2137
|
const existingEmails = new Set(
|
|
2141
2138
|
members.map((m) => m.user_email?.toLowerCase()).filter((e) => !!e)
|
|
2142
2139
|
);
|
|
@@ -2151,7 +2148,7 @@ var init_ManageTeamDialog = __esm({
|
|
|
2151
2148
|
};
|
|
2152
2149
|
}).filter((o) => !(o.email && existingEmails.has(o.email.toLowerCase()))).sort((a, b) => a.label.localeCompare(b.label));
|
|
2153
2150
|
}, [catalogUsers, members]);
|
|
2154
|
-
(0,
|
|
2151
|
+
(0, import_react6.useEffect)(() => {
|
|
2155
2152
|
if (open) {
|
|
2156
2153
|
if (mode === "edit" && team) {
|
|
2157
2154
|
setAlias(team.team_alias ?? "");
|
|
@@ -2307,7 +2304,7 @@ var init_ManageTeamDialog = __esm({
|
|
|
2307
2304
|
const showMcpServers = mode === "edit" && !!canManageMcpServers;
|
|
2308
2305
|
const mcpOptions = (mcpServers ?? []).map((s) => s.id);
|
|
2309
2306
|
const mcpLabel = (id) => (mcpServers ?? []).find((s) => s.id === id)?.name ?? id;
|
|
2310
|
-
return /* @__PURE__ */
|
|
2307
|
+
return /* @__PURE__ */ import_react6.default.createElement(import_Dialog3.default, { open, onClose, maxWidth: "sm", fullWidth: true }, /* @__PURE__ */ import_react6.default.createElement(import_DialogTitle3.default, null, mode === "create" ? "Create Team" : "Edit Team"), /* @__PURE__ */ import_react6.default.createElement(import_DialogContent3.default, { sx: { pt: 2, display: "flex", flexDirection: "column", gap: 2 } }, error && /* @__PURE__ */ import_react6.default.createElement(import_Alert2.default, { severity: "error" }, error), /* @__PURE__ */ import_react6.default.createElement(
|
|
2311
2308
|
import_TextField3.default,
|
|
2312
2309
|
{
|
|
2313
2310
|
label: "Team Alias",
|
|
@@ -2316,15 +2313,15 @@ var init_ManageTeamDialog = __esm({
|
|
|
2316
2313
|
disabled: submitting,
|
|
2317
2314
|
fullWidth: true
|
|
2318
2315
|
}
|
|
2319
|
-
), /* @__PURE__ */
|
|
2320
|
-
|
|
2316
|
+
), /* @__PURE__ */ import_react6.default.createElement(
|
|
2317
|
+
import_Autocomplete2.default,
|
|
2321
2318
|
{
|
|
2322
2319
|
multiple: true,
|
|
2323
2320
|
options: modelNames,
|
|
2324
2321
|
value: models,
|
|
2325
2322
|
onChange: (_, newValue) => setModels(newValue),
|
|
2326
2323
|
disabled: submitting,
|
|
2327
|
-
renderInput: (params) => /* @__PURE__ */
|
|
2324
|
+
renderInput: (params) => /* @__PURE__ */ import_react6.default.createElement(
|
|
2328
2325
|
import_TextField3.default,
|
|
2329
2326
|
{
|
|
2330
2327
|
...params,
|
|
@@ -2334,7 +2331,7 @@ var init_ManageTeamDialog = __esm({
|
|
|
2334
2331
|
}
|
|
2335
2332
|
)
|
|
2336
2333
|
}
|
|
2337
|
-
), /* @__PURE__ */
|
|
2334
|
+
), /* @__PURE__ */ import_react6.default.createElement(
|
|
2338
2335
|
import_TextField3.default,
|
|
2339
2336
|
{
|
|
2340
2337
|
label: budgetWriteOnly ? "New Max Budget ($)" : "Max Budget ($)",
|
|
@@ -2346,13 +2343,13 @@ var init_ManageTeamDialog = __esm({
|
|
|
2346
2343
|
helperText: budgetWriteOnly ? "Current budget is hidden \u2014 leave blank to keep it, or enter a new value" : `Maximum: $${maxBudgetCeiling}`,
|
|
2347
2344
|
fullWidth: true
|
|
2348
2345
|
}
|
|
2349
|
-
), allowUnlimitedBudget && /* @__PURE__ */
|
|
2346
|
+
), allowUnlimitedBudget && /* @__PURE__ */ import_react6.default.createElement(
|
|
2350
2347
|
import_FormControlLabel2.default,
|
|
2351
2348
|
{
|
|
2352
|
-
control: /* @__PURE__ */
|
|
2349
|
+
control: /* @__PURE__ */ import_react6.default.createElement(import_Checkbox2.default, { checked: unlimited, onChange: (e) => setUnlimited(e.target.checked), disabled: submitting }),
|
|
2353
2350
|
label: "Unlimited Budget"
|
|
2354
2351
|
}
|
|
2355
|
-
), /* @__PURE__ */
|
|
2352
|
+
), /* @__PURE__ */ import_react6.default.createElement(
|
|
2356
2353
|
import_TextField3.default,
|
|
2357
2354
|
{
|
|
2358
2355
|
select: true,
|
|
@@ -2363,8 +2360,8 @@ var init_ManageTeamDialog = __esm({
|
|
|
2363
2360
|
helperText: "Spend-reset period for the team budget",
|
|
2364
2361
|
fullWidth: true
|
|
2365
2362
|
},
|
|
2366
|
-
durationOptions.map((o) => /* @__PURE__ */
|
|
2367
|
-
), showMembers && /* @__PURE__ */
|
|
2363
|
+
durationOptions.map((o) => /* @__PURE__ */ import_react6.default.createElement(import_MenuItem2.default, { key: o.value, value: o.value }, o.label))
|
|
2364
|
+
), showMembers && /* @__PURE__ */ import_react6.default.createElement(import_Box5.default, null, /* @__PURE__ */ import_react6.default.createElement(import_Divider2.default, { sx: { my: 1 } }), /* @__PURE__ */ import_react6.default.createElement(import_Typography5.default, { variant: "subtitle2", sx: { mb: 1 } }, "Members"), memberError && /* @__PURE__ */ import_react6.default.createElement(import_Alert2.default, { severity: "error", sx: { mb: 1 }, onClose: () => setMemberError(null) }, memberError), members.length === 0 ? /* @__PURE__ */ import_react6.default.createElement(import_Typography5.default, { variant: "body2", color: "text.secondary", sx: { mb: 1 } }, "No members yet.") : /* @__PURE__ */ import_react6.default.createElement(import_TableContainer2.default, { component: import_Paper3.default, variant: "outlined", sx: { borderRadius: 1.5, mb: 1.5 } }, /* @__PURE__ */ import_react6.default.createElement(import_Table2.default, { size: "small", sx: dataTableSx }, /* @__PURE__ */ import_react6.default.createElement(import_TableHead2.default, null, /* @__PURE__ */ import_react6.default.createElement(import_TableRow2.default, null, /* @__PURE__ */ import_react6.default.createElement(import_TableCell2.default, null, "User"), /* @__PURE__ */ import_react6.default.createElement(import_TableCell2.default, { align: "right" }, "Role"), /* @__PURE__ */ import_react6.default.createElement(import_TableCell2.default, { align: "right", sx: { width: 40 } }))), /* @__PURE__ */ import_react6.default.createElement(import_TableBody2.default, null, members.map((m) => /* @__PURE__ */ import_react6.default.createElement(import_TableRow2.default, { key: m.user_id }, /* @__PURE__ */ import_react6.default.createElement(import_TableCell2.default, null, m.user_email ? /* @__PURE__ */ import_react6.default.createElement(import_react6.default.Fragment, null, /* @__PURE__ */ import_react6.default.createElement(import_Typography5.default, { variant: "body2" }, m.user_email), /* @__PURE__ */ import_react6.default.createElement(
|
|
2368
2365
|
import_Typography5.default,
|
|
2369
2366
|
{
|
|
2370
2367
|
variant: "caption",
|
|
@@ -2372,14 +2369,14 @@ var init_ManageTeamDialog = __esm({
|
|
|
2372
2369
|
sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 11 }
|
|
2373
2370
|
},
|
|
2374
2371
|
m.user_id
|
|
2375
|
-
)) : /* @__PURE__ */
|
|
2372
|
+
)) : /* @__PURE__ */ import_react6.default.createElement(
|
|
2376
2373
|
import_Typography5.default,
|
|
2377
2374
|
{
|
|
2378
2375
|
variant: "body2",
|
|
2379
2376
|
sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace" }
|
|
2380
2377
|
},
|
|
2381
2378
|
m.user_id
|
|
2382
|
-
)), /* @__PURE__ */
|
|
2379
|
+
)), /* @__PURE__ */ import_react6.default.createElement(import_TableCell2.default, { align: "right" }, /* @__PURE__ */ import_react6.default.createElement(StatusPill, { label: m.role, tone: m.role === "admin" ? "accent" : "neutral", dot: false })), /* @__PURE__ */ import_react6.default.createElement(import_TableCell2.default, { align: "right" }, /* @__PURE__ */ import_react6.default.createElement(
|
|
2383
2380
|
import_IconButton3.default,
|
|
2384
2381
|
{
|
|
2385
2382
|
edge: "end",
|
|
@@ -2388,9 +2385,9 @@ var init_ManageTeamDialog = __esm({
|
|
|
2388
2385
|
disabled: memberBusy,
|
|
2389
2386
|
onClick: () => handleRemoveMember(m.user_id)
|
|
2390
2387
|
},
|
|
2391
|
-
/* @__PURE__ */
|
|
2392
|
-
))))))), /* @__PURE__ */
|
|
2393
|
-
|
|
2388
|
+
/* @__PURE__ */ import_react6.default.createElement(import_icons_material4.Delete, { fontSize: "small" })
|
|
2389
|
+
))))))), /* @__PURE__ */ import_react6.default.createElement(import_Box5.default, { sx: { display: "flex", gap: 1, alignItems: "flex-start", flexWrap: "wrap" } }, /* @__PURE__ */ import_react6.default.createElement(
|
|
2390
|
+
import_Autocomplete2.default,
|
|
2394
2391
|
{
|
|
2395
2392
|
freeSolo: true,
|
|
2396
2393
|
autoHighlight: true,
|
|
@@ -2413,7 +2410,7 @@ var init_ManageTeamDialog = __esm({
|
|
|
2413
2410
|
isOptionEqualToValue: (o, v) => o.ref === (typeof v === "string" ? v : v.ref),
|
|
2414
2411
|
disabled: memberBusy,
|
|
2415
2412
|
sx: { flex: 1, minWidth: 260 },
|
|
2416
|
-
renderInput: (params) => /* @__PURE__ */
|
|
2413
|
+
renderInput: (params) => /* @__PURE__ */ import_react6.default.createElement(
|
|
2417
2414
|
import_TextField3.default,
|
|
2418
2415
|
{
|
|
2419
2416
|
...params,
|
|
@@ -2423,12 +2420,12 @@ var init_ManageTeamDialog = __esm({
|
|
|
2423
2420
|
helperText: "Pick a catalog user, or paste a user entity ref",
|
|
2424
2421
|
InputProps: {
|
|
2425
2422
|
...params.InputProps,
|
|
2426
|
-
endAdornment: /* @__PURE__ */
|
|
2423
|
+
endAdornment: /* @__PURE__ */ import_react6.default.createElement(import_react6.default.Fragment, null, usersLoading ? /* @__PURE__ */ import_react6.default.createElement(import_CircularProgress3.default, { color: "inherit", size: 14 }) : null, params.InputProps.endAdornment)
|
|
2427
2424
|
}
|
|
2428
2425
|
}
|
|
2429
2426
|
)
|
|
2430
2427
|
}
|
|
2431
|
-
), /* @__PURE__ */
|
|
2428
|
+
), /* @__PURE__ */ import_react6.default.createElement(
|
|
2432
2429
|
import_TextField3.default,
|
|
2433
2430
|
{
|
|
2434
2431
|
label: "Max budget in team ($)",
|
|
@@ -2439,7 +2436,7 @@ var init_ManageTeamDialog = __esm({
|
|
|
2439
2436
|
size: "small",
|
|
2440
2437
|
sx: { width: 160 }
|
|
2441
2438
|
}
|
|
2442
|
-
), /* @__PURE__ */
|
|
2439
|
+
), /* @__PURE__ */ import_react6.default.createElement(
|
|
2443
2440
|
import_Button4.default,
|
|
2444
2441
|
{
|
|
2445
2442
|
onClick: handleAddMember,
|
|
@@ -2448,8 +2445,8 @@ var init_ManageTeamDialog = __esm({
|
|
|
2448
2445
|
sx: { mt: 0.5 }
|
|
2449
2446
|
},
|
|
2450
2447
|
"Add"
|
|
2451
|
-
))), showKnowledgeBases && /* @__PURE__ */
|
|
2452
|
-
|
|
2448
|
+
))), showKnowledgeBases && /* @__PURE__ */ import_react6.default.createElement(import_Box5.default, null, /* @__PURE__ */ import_react6.default.createElement(import_Divider2.default, { sx: { my: 1 } }), /* @__PURE__ */ import_react6.default.createElement(import_Typography5.default, { variant: "subtitle2", sx: { mb: 1 } }, "Knowledge Bases"), /* @__PURE__ */ import_react6.default.createElement(import_Typography5.default, { variant: "body2", color: "text.secondary", sx: { mb: 1 } }, "Attaching a knowledge base exposes its documents to every key in this team."), kbError && /* @__PURE__ */ import_react6.default.createElement(import_Alert2.default, { severity: "error", sx: { mb: 1 }, onClose: () => setKbError(null) }, kbError), /* @__PURE__ */ import_react6.default.createElement(
|
|
2449
|
+
import_Autocomplete2.default,
|
|
2453
2450
|
{
|
|
2454
2451
|
multiple: true,
|
|
2455
2452
|
options: kbOptions,
|
|
@@ -2457,9 +2454,9 @@ var init_ManageTeamDialog = __esm({
|
|
|
2457
2454
|
getOptionLabel: kbLabel,
|
|
2458
2455
|
onChange: (_, v) => setKbIds(v),
|
|
2459
2456
|
disabled: kbBusy,
|
|
2460
|
-
renderInput: (params) => /* @__PURE__ */
|
|
2457
|
+
renderInput: (params) => /* @__PURE__ */ import_react6.default.createElement(import_TextField3.default, { ...params, label: "Attached knowledge bases" })
|
|
2461
2458
|
}
|
|
2462
|
-
), /* @__PURE__ */
|
|
2459
|
+
), /* @__PURE__ */ import_react6.default.createElement(import_Box5.default, { sx: { display: "flex", justifyContent: "flex-end", mt: 1 } }, /* @__PURE__ */ import_react6.default.createElement(
|
|
2463
2460
|
import_Button4.default,
|
|
2464
2461
|
{
|
|
2465
2462
|
onClick: handleSaveKnowledgeBases,
|
|
@@ -2467,8 +2464,8 @@ var init_ManageTeamDialog = __esm({
|
|
|
2467
2464
|
variant: "outlined"
|
|
2468
2465
|
},
|
|
2469
2466
|
kbBusy ? "Saving\u2026" : "Save knowledge bases"
|
|
2470
|
-
))), showMcpServers && /* @__PURE__ */
|
|
2471
|
-
|
|
2467
|
+
))), showMcpServers && /* @__PURE__ */ import_react6.default.createElement(import_Box5.default, null, /* @__PURE__ */ import_react6.default.createElement(import_Divider2.default, { sx: { my: 1 } }), /* @__PURE__ */ import_react6.default.createElement(import_Typography5.default, { variant: "subtitle2", sx: { mb: 1 } }, "MCP Servers"), /* @__PURE__ */ import_react6.default.createElement(import_Typography5.default, { 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__ */ import_react6.default.createElement(import_Alert2.default, { severity: "error", sx: { mb: 1 }, onClose: () => setMcpError(null) }, mcpError), /* @__PURE__ */ import_react6.default.createElement(
|
|
2468
|
+
import_Autocomplete2.default,
|
|
2472
2469
|
{
|
|
2473
2470
|
multiple: true,
|
|
2474
2471
|
options: mcpOptions,
|
|
@@ -2476,9 +2473,9 @@ var init_ManageTeamDialog = __esm({
|
|
|
2476
2473
|
getOptionLabel: mcpLabel,
|
|
2477
2474
|
onChange: (_, v) => setMcpIds(v),
|
|
2478
2475
|
disabled: mcpBusy,
|
|
2479
|
-
renderInput: (params) => /* @__PURE__ */
|
|
2476
|
+
renderInput: (params) => /* @__PURE__ */ import_react6.default.createElement(import_TextField3.default, { ...params, label: "Attached MCP servers" })
|
|
2480
2477
|
}
|
|
2481
|
-
), /* @__PURE__ */
|
|
2478
|
+
), /* @__PURE__ */ import_react6.default.createElement(import_Box5.default, { sx: { display: "flex", justifyContent: "flex-end", mt: 1 } }, /* @__PURE__ */ import_react6.default.createElement(
|
|
2482
2479
|
import_Button4.default,
|
|
2483
2480
|
{
|
|
2484
2481
|
onClick: handleSaveMcpServers,
|
|
@@ -2486,7 +2483,7 @@ var init_ManageTeamDialog = __esm({
|
|
|
2486
2483
|
variant: "outlined"
|
|
2487
2484
|
},
|
|
2488
2485
|
mcpBusy ? "Saving\u2026" : "Save MCP servers"
|
|
2489
|
-
)))), /* @__PURE__ */
|
|
2486
|
+
)))), /* @__PURE__ */ import_react6.default.createElement(import_DialogActions3.default, null, /* @__PURE__ */ import_react6.default.createElement(import_Button4.default, { onClick: onClose, disabled: submitting }, "Cancel"), /* @__PURE__ */ import_react6.default.createElement(import_Button4.default, { onClick: handleSubmit, disabled: submitting, variant: "contained" }, submitting ? "Saving..." : "Save")));
|
|
2490
2487
|
};
|
|
2491
2488
|
}
|
|
2492
2489
|
});
|
|
@@ -2497,11 +2494,11 @@ function rateTone(rate) {
|
|
|
2497
2494
|
if (rate >= 0.9) return "warning";
|
|
2498
2495
|
return "danger";
|
|
2499
2496
|
}
|
|
2500
|
-
var
|
|
2497
|
+
var import_react7, import_Paper4, import_Box6, import_Typography6, import_TextField4, import_MenuItem3, import_Grid, import_Table3, import_TableBody3, import_TableCell3, import_TableContainer3, import_TableHead3, import_TableRow3, import_Skeleton3, import_recharts, TOP_N_MODELS, PERIOD_LS_KEY, PRESET_LABELS, fmtPct, ChartSkeleton, ChartOrFallback, SuccessRateCell, UsageStats;
|
|
2501
2498
|
var init_UsageStats = __esm({
|
|
2502
2499
|
"src/components/UsageStats.tsx"() {
|
|
2503
2500
|
"use strict";
|
|
2504
|
-
|
|
2501
|
+
import_react7 = __toESM(require("react"));
|
|
2505
2502
|
import_Paper4 = __toESM(require("@mui/material/Paper"));
|
|
2506
2503
|
import_Box6 = __toESM(require("@mui/material/Box"));
|
|
2507
2504
|
import_Typography6 = __toESM(require("@mui/material/Typography"));
|
|
@@ -2527,16 +2524,16 @@ var init_UsageStats = __esm({
|
|
|
2527
2524
|
"30d": "Last 30 days"
|
|
2528
2525
|
};
|
|
2529
2526
|
fmtPct = (n) => `${(n * 100).toFixed(1)}%`;
|
|
2530
|
-
ChartSkeleton = ({ height = 240 }) => /* @__PURE__ */
|
|
2527
|
+
ChartSkeleton = ({ height = 240 }) => /* @__PURE__ */ import_react7.default.createElement(import_Skeleton3.default, { variant: "rounded", height });
|
|
2531
2528
|
ChartOrFallback = ({ loading, empty, height = 240, children }) => {
|
|
2532
|
-
if (loading) return /* @__PURE__ */
|
|
2533
|
-
if (empty) return /* @__PURE__ */
|
|
2534
|
-
return /* @__PURE__ */
|
|
2529
|
+
if (loading) return /* @__PURE__ */ import_react7.default.createElement(ChartSkeleton, { height });
|
|
2530
|
+
if (empty) return /* @__PURE__ */ import_react7.default.createElement(EmptyState, { message: "No data for this period", height });
|
|
2531
|
+
return /* @__PURE__ */ import_react7.default.createElement(import_react7.default.Fragment, null, children);
|
|
2535
2532
|
};
|
|
2536
2533
|
SuccessRateCell = ({ rate, requests }) => {
|
|
2537
|
-
if (requests === 0) return /* @__PURE__ */
|
|
2534
|
+
if (requests === 0) return /* @__PURE__ */ import_react7.default.createElement(import_Typography6.default, { variant: "body2", color: "text.secondary" }, "\u2014");
|
|
2538
2535
|
const tone = rateTone(rate);
|
|
2539
|
-
return /* @__PURE__ */
|
|
2536
|
+
return /* @__PURE__ */ import_react7.default.createElement(import_Box6.default, { display: "flex", alignItems: "center", gap: 1 }, /* @__PURE__ */ import_react7.default.createElement(import_Box6.default, { width: 72, flexShrink: 0 }, /* @__PURE__ */ import_react7.default.createElement(Meter, { value: rate * 100, tone, height: 4 })), /* @__PURE__ */ import_react7.default.createElement(
|
|
2540
2537
|
import_Typography6.default,
|
|
2541
2538
|
{
|
|
2542
2539
|
variant: "caption",
|
|
@@ -2555,8 +2552,8 @@ var init_UsageStats = __esm({
|
|
|
2555
2552
|
userInfo
|
|
2556
2553
|
}) => {
|
|
2557
2554
|
const chart = useChartTheme();
|
|
2558
|
-
const [selectedModel, setSelectedModel] = (0,
|
|
2559
|
-
const [tab, setTab] = (0,
|
|
2555
|
+
const [selectedModel, setSelectedModel] = (0, import_react7.useState)("all");
|
|
2556
|
+
const [tab, setTab] = (0, import_react7.useState)("costs");
|
|
2560
2557
|
const handlePresetChange = (preset) => {
|
|
2561
2558
|
const end = /* @__PURE__ */ new Date();
|
|
2562
2559
|
const start = /* @__PURE__ */ new Date();
|
|
@@ -2570,7 +2567,7 @@ var init_UsageStats = __esm({
|
|
|
2570
2567
|
}
|
|
2571
2568
|
onDateRangeChange({ start, end }, preset);
|
|
2572
2569
|
};
|
|
2573
|
-
const dailyData = (0,
|
|
2570
|
+
const dailyData = (0, import_react7.useMemo)(
|
|
2574
2571
|
() => (usage?.daily_usage ?? []).map((d) => ({
|
|
2575
2572
|
date: d.date,
|
|
2576
2573
|
spend: d.spend,
|
|
@@ -2583,21 +2580,21 @@ var init_UsageStats = __esm({
|
|
|
2583
2580
|
})),
|
|
2584
2581
|
[usage]
|
|
2585
2582
|
);
|
|
2586
|
-
const cumulativeData = (0,
|
|
2583
|
+
const cumulativeData = (0, import_react7.useMemo)(() => {
|
|
2587
2584
|
let cum = 0;
|
|
2588
2585
|
return dailyData.map((d) => {
|
|
2589
2586
|
cum += d.spend;
|
|
2590
2587
|
return { date: d.date, cumulative: cum };
|
|
2591
2588
|
});
|
|
2592
2589
|
}, [dailyData]);
|
|
2593
|
-
const successRateData = (0,
|
|
2590
|
+
const successRateData = (0, import_react7.useMemo)(
|
|
2594
2591
|
() => dailyData.filter((d) => d.apiRequests > 0).map((d) => ({
|
|
2595
2592
|
date: d.date,
|
|
2596
2593
|
successRate: parseFloat((d.successfulRequests / d.apiRequests * 100).toFixed(1))
|
|
2597
2594
|
})),
|
|
2598
2595
|
[dailyData]
|
|
2599
2596
|
);
|
|
2600
|
-
const { modelSpendByDate, topModels: topSpendModels } = (0,
|
|
2597
|
+
const { modelSpendByDate, topModels: topSpendModels } = (0, import_react7.useMemo)(() => {
|
|
2601
2598
|
const rows = usage?.daily_by_model ?? [];
|
|
2602
2599
|
const modelTotals = {};
|
|
2603
2600
|
for (const r of rows) modelTotals[r.model] = (modelTotals[r.model] ?? 0) + r.spend;
|
|
@@ -2612,7 +2609,7 @@ var init_UsageStats = __esm({
|
|
|
2612
2609
|
const hasOther = sorted.some((r) => r.Other > 0);
|
|
2613
2610
|
return { modelSpendByDate: sorted, topModels: hasOther ? [...top, "Other"] : top };
|
|
2614
2611
|
}, [usage]);
|
|
2615
|
-
const modelRows = (0,
|
|
2612
|
+
const modelRows = (0, import_react7.useMemo)(() => {
|
|
2616
2613
|
const entries = Object.entries(usage?.usage_by_model ?? {}).map(([model, d]) => ({
|
|
2617
2614
|
model,
|
|
2618
2615
|
spend: d.total_spend,
|
|
@@ -2626,11 +2623,11 @@ var init_UsageStats = __esm({
|
|
|
2626
2623
|
}));
|
|
2627
2624
|
return selectedModel === "all" ? entries : entries.filter((e) => e.model === selectedModel);
|
|
2628
2625
|
}, [usage, selectedModel]);
|
|
2629
|
-
const topModelSpendBars = (0,
|
|
2626
|
+
const topModelSpendBars = (0, import_react7.useMemo)(
|
|
2630
2627
|
() => [...modelRows].sort((a, b) => b.spend - a.spend).slice(0, 10),
|
|
2631
2628
|
[modelRows]
|
|
2632
2629
|
);
|
|
2633
|
-
const keyRows = (0,
|
|
2630
|
+
const keyRows = (0, import_react7.useMemo)(
|
|
2634
2631
|
() => Object.entries(usage?.usage_by_key ?? {}).map(([keyHash, d]) => ({
|
|
2635
2632
|
keyHash,
|
|
2636
2633
|
keyAlias: d.key_alias ?? keyHash.slice(0, 8),
|
|
@@ -2647,7 +2644,7 @@ var init_UsageStats = __esm({
|
|
|
2647
2644
|
})),
|
|
2648
2645
|
[usage]
|
|
2649
2646
|
);
|
|
2650
|
-
const topKeySpendBars = (0,
|
|
2647
|
+
const topKeySpendBars = (0, import_react7.useMemo)(
|
|
2651
2648
|
() => [...keyRows].sort((a, b) => b.spend - a.spend).slice(0, 10),
|
|
2652
2649
|
[keyRows]
|
|
2653
2650
|
);
|
|
@@ -2685,23 +2682,23 @@ var init_UsageStats = __esm({
|
|
|
2685
2682
|
];
|
|
2686
2683
|
const renderKeyRows = () => {
|
|
2687
2684
|
if (loading) {
|
|
2688
|
-
return /* @__PURE__ */
|
|
2685
|
+
return /* @__PURE__ */ import_react7.default.createElement(import_TableRow3.default, null, /* @__PURE__ */ import_react7.default.createElement(import_TableCell3.default, { colSpan: 7, sx: { py: 3 } }, /* @__PURE__ */ import_react7.default.createElement(import_Skeleton3.default, { variant: "rounded", height: 80 })));
|
|
2689
2686
|
}
|
|
2690
2687
|
if (keyRows.length === 0) {
|
|
2691
|
-
return /* @__PURE__ */
|
|
2688
|
+
return /* @__PURE__ */ import_react7.default.createElement(import_TableRow3.default, null, /* @__PURE__ */ import_react7.default.createElement(import_TableCell3.default, { colSpan: 7 }, /* @__PURE__ */ import_react7.default.createElement(EmptyState, { message: "No key activity in this period" })));
|
|
2692
2689
|
}
|
|
2693
|
-
return [...keyRows].sort((a, b) => b.spend - a.spend || b.apiRequests - a.apiRequests).map((r) => /* @__PURE__ */
|
|
2690
|
+
return [...keyRows].sort((a, b) => b.spend - a.spend || b.apiRequests - a.apiRequests).map((r) => /* @__PURE__ */ import_react7.default.createElement(import_TableRow3.default, { key: r.keyHash }, /* @__PURE__ */ import_react7.default.createElement(import_TableCell3.default, null, /* @__PURE__ */ import_react7.default.createElement(import_Typography6.default, { variant: "body2", sx: { fontWeight: 600 } }, r.keyAlias), r.teamId ? /* @__PURE__ */ import_react7.default.createElement(import_Typography6.default, { variant: "caption", color: "text.secondary" }, "team: ", r.teamId) : null), /* @__PURE__ */ import_react7.default.createElement(import_TableCell3.default, null, /* @__PURE__ */ import_react7.default.createElement(import_Box6.default, { display: "flex", gap: 0.5, flexWrap: "wrap" }, r.models.slice(0, 3).map((m) => /* @__PURE__ */ import_react7.default.createElement(TagChip, { key: m, label: m, title: m })), r.models.length > 3 && /* @__PURE__ */ import_react7.default.createElement(TagChip, { label: `+${r.models.length - 3}`, mono: false }))), /* @__PURE__ */ import_react7.default.createElement(import_TableCell3.default, { align: "right" }, fmtUsd(r.spend)), /* @__PURE__ */ import_react7.default.createElement(import_TableCell3.default, { align: "right" }, fmtInt(r.apiRequests)), /* @__PURE__ */ import_react7.default.createElement(import_TableCell3.default, { align: "right" }, fmtCompact(r.totalTokens)), /* @__PURE__ */ import_react7.default.createElement(import_TableCell3.default, { align: "right" }, fmtInt(r.failedRequests)), /* @__PURE__ */ import_react7.default.createElement(import_TableCell3.default, null, /* @__PURE__ */ import_react7.default.createElement(SuccessRateCell, { rate: r.successRate, requests: r.apiRequests }))));
|
|
2694
2691
|
};
|
|
2695
2692
|
const renderModelRows = () => {
|
|
2696
2693
|
if (loading) {
|
|
2697
|
-
return /* @__PURE__ */
|
|
2694
|
+
return /* @__PURE__ */ import_react7.default.createElement(import_TableRow3.default, null, /* @__PURE__ */ import_react7.default.createElement(import_TableCell3.default, { colSpan: 8, sx: { py: 3 } }, /* @__PURE__ */ import_react7.default.createElement(import_Skeleton3.default, { variant: "rounded", height: 80 })));
|
|
2698
2695
|
}
|
|
2699
2696
|
if (modelRows.length === 0) {
|
|
2700
|
-
return /* @__PURE__ */
|
|
2697
|
+
return /* @__PURE__ */ import_react7.default.createElement(import_TableRow3.default, null, /* @__PURE__ */ import_react7.default.createElement(import_TableCell3.default, { colSpan: 8 }, /* @__PURE__ */ import_react7.default.createElement(EmptyState, { message: "No model activity in this period" })));
|
|
2701
2698
|
}
|
|
2702
|
-
return [...modelRows].sort((a, b) => b.spend - a.spend || b.totalTokens - a.totalTokens).map((r) => /* @__PURE__ */
|
|
2699
|
+
return [...modelRows].sort((a, b) => b.spend - a.spend || b.totalTokens - a.totalTokens).map((r) => /* @__PURE__ */ import_react7.default.createElement(import_TableRow3.default, { key: r.model }, /* @__PURE__ */ import_react7.default.createElement(import_TableCell3.default, null, /* @__PURE__ */ import_react7.default.createElement(import_Box6.default, { display: "flex", alignItems: "center", gap: 1 }, /* @__PURE__ */ import_react7.default.createElement(import_Box6.default, { sx: { width: 8, height: 8, borderRadius: "50%", bgcolor: chartColor(r.model), flexShrink: 0 } }), /* @__PURE__ */ import_react7.default.createElement(import_Typography6.default, { variant: "body2", sx: { fontWeight: 600 } }, r.model))), /* @__PURE__ */ import_react7.default.createElement(import_TableCell3.default, { align: "right" }, fmtUsd(r.spend)), /* @__PURE__ */ import_react7.default.createElement(import_TableCell3.default, { align: "right" }, fmtInt(r.apiRequests)), /* @__PURE__ */ import_react7.default.createElement(import_TableCell3.default, { align: "right" }, fmtInt(r.successfulRequests)), /* @__PURE__ */ import_react7.default.createElement(import_TableCell3.default, { align: "right" }, fmtInt(r.failedRequests)), /* @__PURE__ */ import_react7.default.createElement(import_TableCell3.default, { align: "right" }, fmtCompact(r.promptTokens)), /* @__PURE__ */ import_react7.default.createElement(import_TableCell3.default, { align: "right" }, fmtCompact(r.completionTokens)), /* @__PURE__ */ import_react7.default.createElement(import_TableCell3.default, null, /* @__PURE__ */ import_react7.default.createElement(SuccessRateCell, { rate: r.successRate, requests: r.apiRequests }))));
|
|
2703
2700
|
};
|
|
2704
|
-
const periodSelect = /* @__PURE__ */
|
|
2701
|
+
const periodSelect = /* @__PURE__ */ import_react7.default.createElement(
|
|
2705
2702
|
import_TextField4.default,
|
|
2706
2703
|
{
|
|
2707
2704
|
select: true,
|
|
@@ -2711,17 +2708,17 @@ var init_UsageStats = __esm({
|
|
|
2711
2708
|
onChange: (e) => handlePresetChange(e.target.value),
|
|
2712
2709
|
sx: { minWidth: 160 }
|
|
2713
2710
|
},
|
|
2714
|
-
Object.keys(PRESET_LABELS).map((p) => /* @__PURE__ */
|
|
2711
|
+
Object.keys(PRESET_LABELS).map((p) => /* @__PURE__ */ import_react7.default.createElement(import_MenuItem3.default, { key: p, value: p }, PRESET_LABELS[p]))
|
|
2715
2712
|
);
|
|
2716
|
-
return /* @__PURE__ */
|
|
2713
|
+
return /* @__PURE__ */ import_react7.default.createElement(
|
|
2717
2714
|
SectionCard,
|
|
2718
2715
|
{
|
|
2719
2716
|
title: "Usage Analytics",
|
|
2720
2717
|
subtitle: `${PRESET_LABELS[currentPreset]} \xB7 ${dateRange.start.toLocaleDateString()} \u2013 ${dateRange.end.toLocaleDateString()}`,
|
|
2721
2718
|
actions: periodSelect
|
|
2722
2719
|
},
|
|
2723
|
-
/* @__PURE__ */
|
|
2724
|
-
/* @__PURE__ */
|
|
2720
|
+
/* @__PURE__ */ import_react7.default.createElement(MetricStrip, { metrics }),
|
|
2721
|
+
/* @__PURE__ */ import_react7.default.createElement(
|
|
2725
2722
|
import_Box6.default,
|
|
2726
2723
|
{
|
|
2727
2724
|
sx: {
|
|
@@ -2734,7 +2731,7 @@ var init_UsageStats = __esm({
|
|
|
2734
2731
|
mb: 2
|
|
2735
2732
|
}
|
|
2736
2733
|
},
|
|
2737
|
-
/* @__PURE__ */
|
|
2734
|
+
/* @__PURE__ */ import_react7.default.createElement(
|
|
2738
2735
|
SegmentedControl,
|
|
2739
2736
|
{
|
|
2740
2737
|
value: tab,
|
|
@@ -2746,7 +2743,7 @@ var init_UsageStats = __esm({
|
|
|
2746
2743
|
]
|
|
2747
2744
|
}
|
|
2748
2745
|
),
|
|
2749
|
-
tab === "models" && /* @__PURE__ */
|
|
2746
|
+
tab === "models" && /* @__PURE__ */ import_react7.default.createElement(
|
|
2750
2747
|
import_TextField4.default,
|
|
2751
2748
|
{
|
|
2752
2749
|
select: true,
|
|
@@ -2756,17 +2753,17 @@ var init_UsageStats = __esm({
|
|
|
2756
2753
|
onChange: (e) => setSelectedModel(e.target.value),
|
|
2757
2754
|
sx: { minWidth: 220 }
|
|
2758
2755
|
},
|
|
2759
|
-
/* @__PURE__ */
|
|
2760
|
-
models.map((m) => /* @__PURE__ */
|
|
2756
|
+
/* @__PURE__ */ import_react7.default.createElement(import_MenuItem3.default, { value: "all" }, "All models"),
|
|
2757
|
+
models.map((m) => /* @__PURE__ */ import_react7.default.createElement(import_MenuItem3.default, { key: m.model_name, value: m.model_name }, m.model_name))
|
|
2761
2758
|
)
|
|
2762
2759
|
),
|
|
2763
|
-
tab === "costs" && /* @__PURE__ */
|
|
2760
|
+
tab === "costs" && /* @__PURE__ */ import_react7.default.createElement(import_Grid.default, { container: true, spacing: 2 }, /* @__PURE__ */ import_react7.default.createElement(import_Grid.default, { item: true, xs: 12 }, /* @__PURE__ */ import_react7.default.createElement(ChartCard, { title: "Daily spend by model", height: 260 }, /* @__PURE__ */ import_react7.default.createElement(ChartOrFallback, { loading, empty: modelSpendByDate.length === 0, height: 260 }, /* @__PURE__ */ import_react7.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react7.default.createElement(import_recharts.AreaChart, { data: modelSpendByDate, margin: { top: 4, right: 8, left: 0, bottom: 0 } }, /* @__PURE__ */ import_react7.default.createElement(import_recharts.CartesianGrid, { ...chart.grid }), /* @__PURE__ */ import_react7.default.createElement(import_recharts.XAxis, { dataKey: "date", tickFormatter: fmtDateShort, ...chart.axis }), /* @__PURE__ */ import_react7.default.createElement(import_recharts.YAxis, { tickFormatter: fmtUsdCompact, width: 56, ...chart.axis }), /* @__PURE__ */ import_react7.default.createElement(
|
|
2764
2761
|
import_recharts.Tooltip,
|
|
2765
2762
|
{
|
|
2766
2763
|
cursor: chart.cursor,
|
|
2767
|
-
content: /* @__PURE__ */
|
|
2764
|
+
content: /* @__PURE__ */ import_react7.default.createElement(ChartTooltip, { valueFormatter: fmtUsd, hideZero: true })
|
|
2768
2765
|
}
|
|
2769
|
-
), topSpendModels.map((m) => /* @__PURE__ */
|
|
2766
|
+
), topSpendModels.map((m) => /* @__PURE__ */ import_react7.default.createElement(
|
|
2770
2767
|
import_recharts.Area,
|
|
2771
2768
|
{
|
|
2772
2769
|
key: m,
|
|
@@ -2778,13 +2775,13 @@ var init_UsageStats = __esm({
|
|
|
2778
2775
|
fill: chartColor(m),
|
|
2779
2776
|
fillOpacity: 0.28
|
|
2780
2777
|
}
|
|
2781
|
-
))))), !loading && topSpendModels.length > 0 && /* @__PURE__ */
|
|
2778
|
+
))))), !loading && topSpendModels.length > 0 && /* @__PURE__ */ import_react7.default.createElement(SeriesLegend, { series: topSpendModels.map((m) => ({ name: m, color: chartColor(m) })) }))), /* @__PURE__ */ import_react7.default.createElement(import_Grid.default, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ import_react7.default.createElement(ChartCard, { title: "Daily token usage" }, /* @__PURE__ */ import_react7.default.createElement(ChartOrFallback, { loading, empty: dailyData.length === 0 }, /* @__PURE__ */ import_react7.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react7.default.createElement(import_recharts.AreaChart, { data: dailyData, margin: { top: 4, right: 8, left: 0, bottom: 0 } }, /* @__PURE__ */ import_react7.default.createElement(import_recharts.CartesianGrid, { ...chart.grid }), /* @__PURE__ */ import_react7.default.createElement(import_recharts.XAxis, { dataKey: "date", tickFormatter: fmtDateShort, ...chart.axis }), /* @__PURE__ */ import_react7.default.createElement(import_recharts.YAxis, { tickFormatter: fmtCompact, width: 48, ...chart.axis }), /* @__PURE__ */ import_react7.default.createElement(import_recharts.Tooltip, { cursor: chart.cursor, content: /* @__PURE__ */ import_react7.default.createElement(ChartTooltip, { valueFormatter: fmtInt }) }), /* @__PURE__ */ import_react7.default.createElement(import_recharts.Legend, { ...chart.legend }), /* @__PURE__ */ import_react7.default.createElement(import_recharts.Area, { type: "monotone", dataKey: "promptTokens", name: "Input", stackId: "tok", stroke: SERIES.input, strokeWidth: 1.5, fill: SERIES.input, fillOpacity: 0.25 }), /* @__PURE__ */ import_react7.default.createElement(import_recharts.Area, { type: "monotone", dataKey: "completionTokens", name: "Output", stackId: "tok", stroke: SERIES.output, strokeWidth: 1.5, fill: SERIES.output, fillOpacity: 0.25 })))))), /* @__PURE__ */ import_react7.default.createElement(import_Grid.default, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ import_react7.default.createElement(ChartCard, { title: "Daily requests" }, /* @__PURE__ */ import_react7.default.createElement(ChartOrFallback, { loading, empty: dailyData.length === 0 }, /* @__PURE__ */ import_react7.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react7.default.createElement(import_recharts.BarChart, { data: dailyData, margin: { top: 4, right: 8, left: 0, bottom: 0 }, barCategoryGap: "30%" }, /* @__PURE__ */ import_react7.default.createElement(import_recharts.CartesianGrid, { ...chart.grid }), /* @__PURE__ */ import_react7.default.createElement(import_recharts.XAxis, { dataKey: "date", tickFormatter: fmtDateShort, ...chart.axis }), /* @__PURE__ */ import_react7.default.createElement(import_recharts.YAxis, { tickFormatter: fmtCompact, width: 48, ...chart.axis }), /* @__PURE__ */ import_react7.default.createElement(import_recharts.Tooltip, { cursor: chart.cursor, content: /* @__PURE__ */ import_react7.default.createElement(ChartTooltip, { valueFormatter: fmtInt }) }), /* @__PURE__ */ import_react7.default.createElement(import_recharts.Legend, { ...chart.legend }), /* @__PURE__ */ import_react7.default.createElement(import_recharts.Bar, { dataKey: "successfulRequests", name: "Successful", fill: SERIES.success, stackId: "r" }), /* @__PURE__ */ import_react7.default.createElement(import_recharts.Bar, { dataKey: "failedRequests", name: "Failed", fill: SERIES.failure, stackId: "r", radius: [3, 3, 0, 0] })))))), /* @__PURE__ */ import_react7.default.createElement(import_Grid.default, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ import_react7.default.createElement(ChartCard, { title: "Daily success rate" }, /* @__PURE__ */ import_react7.default.createElement(ChartOrFallback, { loading, empty: successRateData.length === 0 }, /* @__PURE__ */ import_react7.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react7.default.createElement(import_recharts.LineChart, { data: successRateData, margin: { top: 4, right: 8, left: 0, bottom: 0 } }, /* @__PURE__ */ import_react7.default.createElement(import_recharts.CartesianGrid, { ...chart.grid }), /* @__PURE__ */ import_react7.default.createElement(import_recharts.XAxis, { dataKey: "date", tickFormatter: fmtDateShort, ...chart.axis }), /* @__PURE__ */ import_react7.default.createElement(import_recharts.YAxis, { domain: [0, 100], tickFormatter: (v) => `${v}%`, width: 44, ...chart.axis }), /* @__PURE__ */ import_react7.default.createElement(
|
|
2782
2779
|
import_recharts.Tooltip,
|
|
2783
2780
|
{
|
|
2784
2781
|
cursor: chart.cursor,
|
|
2785
|
-
content: /* @__PURE__ */
|
|
2782
|
+
content: /* @__PURE__ */ import_react7.default.createElement(ChartTooltip, { valueFormatter: (v) => `${v}%` })
|
|
2786
2783
|
}
|
|
2787
|
-
), /* @__PURE__ */
|
|
2784
|
+
), /* @__PURE__ */ import_react7.default.createElement(import_recharts.ReferenceLine, { y: 100, stroke: SERIES.success, strokeDasharray: "4 4", strokeOpacity: 0.5 }), /* @__PURE__ */ import_react7.default.createElement(
|
|
2788
2785
|
import_recharts.Line,
|
|
2789
2786
|
{
|
|
2790
2787
|
type: "monotone",
|
|
@@ -2795,13 +2792,13 @@ var init_UsageStats = __esm({
|
|
|
2795
2792
|
dot: { r: 2.5, strokeWidth: 0, fill: SERIES.input },
|
|
2796
2793
|
activeDot: { r: 4 }
|
|
2797
2794
|
}
|
|
2798
|
-
)))))), /* @__PURE__ */
|
|
2795
|
+
)))))), /* @__PURE__ */ import_react7.default.createElement(import_Grid.default, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ import_react7.default.createElement(
|
|
2799
2796
|
ChartCard,
|
|
2800
2797
|
{
|
|
2801
2798
|
title: maxBudget > 0 ? "Cumulative spend vs budget" : "Cumulative spend",
|
|
2802
2799
|
meta: maxBudget > 0 ? `${fmtUsd(totalCumSpend)} used \xB7 ${fmtUsd(Math.max(0, maxBudget - totalCumSpend))} left` : void 0
|
|
2803
2800
|
},
|
|
2804
|
-
/* @__PURE__ */
|
|
2801
|
+
/* @__PURE__ */ import_react7.default.createElement(ChartOrFallback, { loading, empty: cumulativeData.length === 0 }, /* @__PURE__ */ import_react7.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react7.default.createElement(import_recharts.AreaChart, { data: cumulativeData, margin: { top: 4, right: 8, left: 0, bottom: 0 } }, /* @__PURE__ */ import_react7.default.createElement(import_recharts.CartesianGrid, { ...chart.grid }), /* @__PURE__ */ import_react7.default.createElement(import_recharts.XAxis, { dataKey: "date", tickFormatter: fmtDateShort, ...chart.axis }), /* @__PURE__ */ import_react7.default.createElement(
|
|
2805
2802
|
import_recharts.YAxis,
|
|
2806
2803
|
{
|
|
2807
2804
|
tickFormatter: fmtUsdCompact,
|
|
@@ -2809,13 +2806,13 @@ var init_UsageStats = __esm({
|
|
|
2809
2806
|
domain: cumulativeDomain,
|
|
2810
2807
|
...chart.axis
|
|
2811
2808
|
}
|
|
2812
|
-
), /* @__PURE__ */
|
|
2809
|
+
), /* @__PURE__ */ import_react7.default.createElement(
|
|
2813
2810
|
import_recharts.Tooltip,
|
|
2814
2811
|
{
|
|
2815
2812
|
cursor: chart.cursor,
|
|
2816
|
-
content: /* @__PURE__ */
|
|
2813
|
+
content: /* @__PURE__ */ import_react7.default.createElement(ChartTooltip, { valueFormatter: fmtUsd })
|
|
2817
2814
|
}
|
|
2818
|
-
), maxBudget > 0 && /* @__PURE__ */
|
|
2815
|
+
), maxBudget > 0 && /* @__PURE__ */ import_react7.default.createElement(
|
|
2819
2816
|
import_recharts.ReferenceLine,
|
|
2820
2817
|
{
|
|
2821
2818
|
y: maxBudget,
|
|
@@ -2823,7 +2820,7 @@ var init_UsageStats = __esm({
|
|
|
2823
2820
|
strokeDasharray: "5 4",
|
|
2824
2821
|
label: { value: `Budget ${fmtUsd(maxBudget)}`, position: "insideTopRight", fontSize: 10, fill: SERIES.budget }
|
|
2825
2822
|
}
|
|
2826
|
-
), /* @__PURE__ */
|
|
2823
|
+
), /* @__PURE__ */ import_react7.default.createElement(
|
|
2827
2824
|
import_recharts.Area,
|
|
2828
2825
|
{
|
|
2829
2826
|
type: "monotone",
|
|
@@ -2836,32 +2833,32 @@ var init_UsageStats = __esm({
|
|
|
2836
2833
|
}
|
|
2837
2834
|
))))
|
|
2838
2835
|
))),
|
|
2839
|
-
tab === "models" && /* @__PURE__ */
|
|
2836
|
+
tab === "models" && /* @__PURE__ */ import_react7.default.createElement(import_Grid.default, { container: true, spacing: 2 }, /* @__PURE__ */ import_react7.default.createElement(import_Grid.default, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ import_react7.default.createElement(ChartCard, { title: "Spend by model", height: Math.max(200, topModelSpendBars.length * 34) }, /* @__PURE__ */ import_react7.default.createElement(
|
|
2840
2837
|
ChartOrFallback,
|
|
2841
2838
|
{
|
|
2842
2839
|
loading,
|
|
2843
2840
|
empty: topModelSpendBars.length === 0,
|
|
2844
2841
|
height: Math.max(200, topModelSpendBars.length * 34)
|
|
2845
2842
|
},
|
|
2846
|
-
/* @__PURE__ */
|
|
2847
|
-
))), /* @__PURE__ */
|
|
2843
|
+
/* @__PURE__ */ import_react7.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react7.default.createElement(import_recharts.BarChart, { data: topModelSpendBars, layout: "vertical", margin: { top: 0, right: 12, left: 0, bottom: 0 } }, /* @__PURE__ */ import_react7.default.createElement(import_recharts.CartesianGrid, { ...chart.grid, vertical: true, horizontal: false }), /* @__PURE__ */ import_react7.default.createElement(import_recharts.XAxis, { type: "number", tickFormatter: fmtUsdCompact, ...chart.axis }), /* @__PURE__ */ import_react7.default.createElement(import_recharts.YAxis, { type: "category", dataKey: "model", width: 170, ...chart.axis, tick: { fontSize: 10.5, fill: chart.axis.tick.fill } }), /* @__PURE__ */ import_react7.default.createElement(import_recharts.Tooltip, { cursor: chart.cursor, content: /* @__PURE__ */ import_react7.default.createElement(ChartTooltip, { valueFormatter: fmtUsd, labelFormatter: (l) => l }) }), /* @__PURE__ */ import_react7.default.createElement(import_recharts.Bar, { dataKey: "spend", name: "Spend", radius: [0, 3, 3, 0], barSize: 14 }, topModelSpendBars.map((r) => /* @__PURE__ */ import_react7.default.createElement(import_recharts.Cell, { key: r.model, fill: chartColor(r.model) })))))
|
|
2844
|
+
))), /* @__PURE__ */ import_react7.default.createElement(import_Grid.default, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ import_react7.default.createElement(ChartCard, { title: "Tokens by model", height: Math.max(200, topModelSpendBars.length * 34) }, /* @__PURE__ */ import_react7.default.createElement(
|
|
2848
2845
|
ChartOrFallback,
|
|
2849
2846
|
{
|
|
2850
2847
|
loading,
|
|
2851
2848
|
empty: modelRows.length === 0,
|
|
2852
2849
|
height: Math.max(200, topModelSpendBars.length * 34)
|
|
2853
2850
|
},
|
|
2854
|
-
/* @__PURE__ */
|
|
2855
|
-
))), /* @__PURE__ */
|
|
2856
|
-
tab === "keys" && /* @__PURE__ */
|
|
2851
|
+
/* @__PURE__ */ import_react7.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react7.default.createElement(import_recharts.BarChart, { data: topModelSpendBars, layout: "vertical", margin: { top: 0, right: 12, left: 0, bottom: 0 } }, /* @__PURE__ */ import_react7.default.createElement(import_recharts.CartesianGrid, { ...chart.grid, vertical: true, horizontal: false }), /* @__PURE__ */ import_react7.default.createElement(import_recharts.XAxis, { type: "number", tickFormatter: fmtCompact, ...chart.axis }), /* @__PURE__ */ import_react7.default.createElement(import_recharts.YAxis, { type: "category", dataKey: "model", width: 170, ...chart.axis, tick: { fontSize: 10.5, fill: chart.axis.tick.fill } }), /* @__PURE__ */ import_react7.default.createElement(import_recharts.Tooltip, { cursor: chart.cursor, content: /* @__PURE__ */ import_react7.default.createElement(ChartTooltip, { valueFormatter: fmtInt, labelFormatter: (l) => l }) }), /* @__PURE__ */ import_react7.default.createElement(import_recharts.Legend, { ...chart.legend }), /* @__PURE__ */ import_react7.default.createElement(import_recharts.Bar, { dataKey: "promptTokens", name: "Input", fill: SERIES.input, stackId: "t", barSize: 14 }), /* @__PURE__ */ import_react7.default.createElement(import_recharts.Bar, { dataKey: "completionTokens", name: "Output", fill: SERIES.output, stackId: "t", barSize: 14, radius: [0, 3, 3, 0] })))
|
|
2852
|
+
))), /* @__PURE__ */ import_react7.default.createElement(import_Grid.default, { item: true, xs: 12 }, /* @__PURE__ */ import_react7.default.createElement(import_TableContainer3.default, { component: import_Paper4.default, variant: "outlined", sx: { borderRadius: 2 } }, /* @__PURE__ */ import_react7.default.createElement(import_Table3.default, { size: "small", sx: dataTableSx }, /* @__PURE__ */ import_react7.default.createElement(import_TableHead3.default, null, /* @__PURE__ */ import_react7.default.createElement(import_TableRow3.default, null, /* @__PURE__ */ import_react7.default.createElement(import_TableCell3.default, null, "Model"), /* @__PURE__ */ import_react7.default.createElement(import_TableCell3.default, { align: "right" }, "Spend"), /* @__PURE__ */ import_react7.default.createElement(import_TableCell3.default, { align: "right" }, "Requests"), /* @__PURE__ */ import_react7.default.createElement(import_TableCell3.default, { align: "right" }, "Success"), /* @__PURE__ */ import_react7.default.createElement(import_TableCell3.default, { align: "right" }, "Failed"), /* @__PURE__ */ import_react7.default.createElement(import_TableCell3.default, { align: "right" }, "Input"), /* @__PURE__ */ import_react7.default.createElement(import_TableCell3.default, { align: "right" }, "Output"), /* @__PURE__ */ import_react7.default.createElement(import_TableCell3.default, { sx: { width: 160 } }, "Success rate"))), /* @__PURE__ */ import_react7.default.createElement(import_TableBody3.default, null, renderModelRows()))))),
|
|
2853
|
+
tab === "keys" && /* @__PURE__ */ import_react7.default.createElement(import_Grid.default, { container: true, spacing: 2 }, (loading || topKeySpendBars.length > 0) && /* @__PURE__ */ import_react7.default.createElement(import_Grid.default, { item: true, xs: 12 }, /* @__PURE__ */ import_react7.default.createElement(ChartCard, { title: "Spend by key", height: Math.max(160, topKeySpendBars.length * 32) }, /* @__PURE__ */ import_react7.default.createElement(
|
|
2857
2854
|
ChartOrFallback,
|
|
2858
2855
|
{
|
|
2859
2856
|
loading,
|
|
2860
2857
|
empty: topKeySpendBars.length === 0,
|
|
2861
2858
|
height: Math.max(160, topKeySpendBars.length * 32)
|
|
2862
2859
|
},
|
|
2863
|
-
/* @__PURE__ */
|
|
2864
|
-
))), /* @__PURE__ */
|
|
2860
|
+
/* @__PURE__ */ import_react7.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react7.default.createElement(import_recharts.BarChart, { data: topKeySpendBars, layout: "vertical", margin: { top: 0, right: 12, left: 0, bottom: 0 } }, /* @__PURE__ */ import_react7.default.createElement(import_recharts.CartesianGrid, { ...chart.grid, vertical: true, horizontal: false }), /* @__PURE__ */ import_react7.default.createElement(import_recharts.XAxis, { type: "number", tickFormatter: fmtUsdCompact, ...chart.axis }), /* @__PURE__ */ import_react7.default.createElement(import_recharts.YAxis, { type: "category", dataKey: "keyAlias", width: 160, ...chart.axis, tick: { fontSize: 10.5, fill: chart.axis.tick.fill } }), /* @__PURE__ */ import_react7.default.createElement(import_recharts.Tooltip, { cursor: chart.cursor, content: /* @__PURE__ */ import_react7.default.createElement(ChartTooltip, { valueFormatter: fmtUsd, labelFormatter: (l) => l }) }), /* @__PURE__ */ import_react7.default.createElement(import_recharts.Bar, { dataKey: "spend", name: "Spend", fill: SERIES.input, radius: [0, 3, 3, 0], barSize: 14 })))
|
|
2861
|
+
))), /* @__PURE__ */ import_react7.default.createElement(import_Grid.default, { item: true, xs: 12 }, /* @__PURE__ */ import_react7.default.createElement(import_TableContainer3.default, { component: import_Paper4.default, variant: "outlined", sx: { borderRadius: 2 } }, /* @__PURE__ */ import_react7.default.createElement(import_Table3.default, { size: "small", sx: dataTableSx }, /* @__PURE__ */ import_react7.default.createElement(import_TableHead3.default, null, /* @__PURE__ */ import_react7.default.createElement(import_TableRow3.default, null, /* @__PURE__ */ import_react7.default.createElement(import_TableCell3.default, null, "Key"), /* @__PURE__ */ import_react7.default.createElement(import_TableCell3.default, null, "Models"), /* @__PURE__ */ import_react7.default.createElement(import_TableCell3.default, { align: "right" }, "Spend"), /* @__PURE__ */ import_react7.default.createElement(import_TableCell3.default, { align: "right" }, "Requests"), /* @__PURE__ */ import_react7.default.createElement(import_TableCell3.default, { align: "right" }, "Tokens"), /* @__PURE__ */ import_react7.default.createElement(import_TableCell3.default, { align: "right" }, "Failed"), /* @__PURE__ */ import_react7.default.createElement(import_TableCell3.default, { sx: { width: 160 } }, "Success rate"))), /* @__PURE__ */ import_react7.default.createElement(import_TableBody3.default, null, renderKeyRows())))))
|
|
2865
2862
|
);
|
|
2866
2863
|
};
|
|
2867
2864
|
}
|
|
@@ -3003,16 +3000,16 @@ function levelToneColor(tone, theme) {
|
|
|
3003
3000
|
return theme.palette.primary.main;
|
|
3004
3001
|
}
|
|
3005
3002
|
}
|
|
3006
|
-
var
|
|
3003
|
+
var import_react8, import_Box7, import_Paper5, import_Typography7, import_styles4, import_core_components2, LimitCard, TaggedLimit, MoreKeysNote, BudgetLimitList, LimitListPanel;
|
|
3007
3004
|
var init_BudgetLimitList = __esm({
|
|
3008
3005
|
"src/components/BudgetLimitList.tsx"() {
|
|
3009
3006
|
"use strict";
|
|
3010
|
-
|
|
3007
|
+
import_react8 = __toESM(require("react"));
|
|
3011
3008
|
import_Box7 = __toESM(require("@mui/material/Box"));
|
|
3012
3009
|
import_Paper5 = __toESM(require("@mui/material/Paper"));
|
|
3013
3010
|
import_Typography7 = __toESM(require("@mui/material/Typography"));
|
|
3014
3011
|
import_styles4 = require("@mui/material/styles");
|
|
3015
|
-
|
|
3012
|
+
import_core_components2 = require("@backstage/core-components");
|
|
3016
3013
|
init_budget();
|
|
3017
3014
|
init_format();
|
|
3018
3015
|
init_ui();
|
|
@@ -3020,7 +3017,7 @@ var init_BudgetLimitList = __esm({
|
|
|
3020
3017
|
const tone = budgetTone2(limit.pct);
|
|
3021
3018
|
const pct = Math.round(limit.pct);
|
|
3022
3019
|
const closeTo = limit.softLimit !== void 0 && limit.spend >= limit.softLimit;
|
|
3023
|
-
return /* @__PURE__ */
|
|
3020
|
+
return /* @__PURE__ */ import_react8.default.createElement(import_Paper5.default, { variant: "outlined", sx: { px: 1.5, py: 1.25, borderRadius: 1.5 } }, /* @__PURE__ */ import_react8.default.createElement(import_Box7.default, { sx: { display: "flex", alignItems: "baseline", justifyContent: "space-between", gap: 1.5 } }, /* @__PURE__ */ import_react8.default.createElement(import_Box7.default, { sx: { minWidth: 0, flex: 1 } }, /* @__PURE__ */ import_react8.default.createElement(
|
|
3024
3021
|
import_Typography7.default,
|
|
3025
3022
|
{
|
|
3026
3023
|
variant: "body2",
|
|
@@ -3028,7 +3025,7 @@ var init_BudgetLimitList = __esm({
|
|
|
3028
3025
|
title: limit.sublabel ? `${limit.label} \xB7 ${limit.sublabel}` : limit.label
|
|
3029
3026
|
},
|
|
3030
3027
|
limit.label
|
|
3031
|
-
), limit.sublabel && /* @__PURE__ */
|
|
3028
|
+
), limit.sublabel && /* @__PURE__ */ import_react8.default.createElement(
|
|
3032
3029
|
import_Typography7.default,
|
|
3033
3030
|
{
|
|
3034
3031
|
variant: "caption",
|
|
@@ -3043,7 +3040,7 @@ var init_BudgetLimitList = __esm({
|
|
|
3043
3040
|
}
|
|
3044
3041
|
},
|
|
3045
3042
|
limit.sublabel
|
|
3046
|
-
)), limit.hidden ? /* @__PURE__ */
|
|
3043
|
+
)), limit.hidden ? /* @__PURE__ */ import_react8.default.createElement(import_Typography7.default, { variant: "body2", sx: { fontWeight: 600, fontVariantNumeric: "tabular-nums", whiteSpace: "nowrap" } }, pct, "% used") : /* @__PURE__ */ import_react8.default.createElement(import_Typography7.default, { variant: "body2", sx: { fontWeight: 600, fontVariantNumeric: "tabular-nums", whiteSpace: "nowrap" } }, fmtUsd(limit.spend), /* @__PURE__ */ import_react8.default.createElement(import_Box7.default, { component: "span", color: "text.secondary", sx: { fontWeight: 400 } }, " / ", fmtUsd(limit.budget)))), /* @__PURE__ */ import_react8.default.createElement(import_Box7.default, { sx: { mt: 1 } }, /* @__PURE__ */ import_react8.default.createElement(Meter, { value: limit.pct, tone, height: 6 })), /* @__PURE__ */ import_react8.default.createElement(
|
|
3047
3044
|
import_Box7.default,
|
|
3048
3045
|
{
|
|
3049
3046
|
sx: {
|
|
@@ -3054,14 +3051,14 @@ var init_BudgetLimitList = __esm({
|
|
|
3054
3051
|
mt: 0.75
|
|
3055
3052
|
}
|
|
3056
3053
|
},
|
|
3057
|
-
/* @__PURE__ */
|
|
3058
|
-
/* @__PURE__ */
|
|
3054
|
+
/* @__PURE__ */ import_react8.default.createElement(import_Typography7.default, { variant: "caption", color: "text.secondary", sx: { fontVariantNumeric: "tabular-nums" } }, limit.hidden ? /* @__PURE__ */ import_react8.default.createElement(import_react8.default.Fragment, null, "Hidden by admin") : /* @__PURE__ */ import_react8.default.createElement(import_react8.default.Fragment, null, pct, "% of budget", closeTo && ` \xB7 soft-limit warning`)),
|
|
3055
|
+
/* @__PURE__ */ import_react8.default.createElement(import_Typography7.default, { variant: "caption", color: "text.secondary" }, fmtBudgetDuration(limit.budgetDuration) ? `resets ${fmtBudgetDuration(limit.budgetDuration)}` : "never resets")
|
|
3059
3056
|
));
|
|
3060
3057
|
};
|
|
3061
3058
|
TaggedLimit = ({ limit }) => {
|
|
3062
3059
|
const theme = (0, import_styles4.useTheme)();
|
|
3063
3060
|
const tone = budgetTone2(limit.pct);
|
|
3064
|
-
return /* @__PURE__ */
|
|
3061
|
+
return /* @__PURE__ */ import_react8.default.createElement(import_Box7.default, null, /* @__PURE__ */ import_react8.default.createElement(
|
|
3065
3062
|
import_Typography7.default,
|
|
3066
3063
|
{
|
|
3067
3064
|
sx: {
|
|
@@ -3074,7 +3071,7 @@ var init_BudgetLimitList = __esm({
|
|
|
3074
3071
|
}
|
|
3075
3072
|
},
|
|
3076
3073
|
limit.kind
|
|
3077
|
-
), /* @__PURE__ */
|
|
3074
|
+
), /* @__PURE__ */ import_react8.default.createElement(LimitCard, { limit }));
|
|
3078
3075
|
};
|
|
3079
3076
|
MoreKeysNote = ({
|
|
3080
3077
|
count,
|
|
@@ -3083,12 +3080,12 @@ var init_BudgetLimitList = __esm({
|
|
|
3083
3080
|
}) => {
|
|
3084
3081
|
const text = label ?? `+${count} more budgeted key${count > 1 ? "s" : ""} further from the cap`;
|
|
3085
3082
|
if (!href) {
|
|
3086
|
-
return /* @__PURE__ */
|
|
3083
|
+
return /* @__PURE__ */ import_react8.default.createElement(import_Typography7.default, { variant: "caption", color: "text.secondary" }, text);
|
|
3087
3084
|
}
|
|
3088
|
-
return /* @__PURE__ */
|
|
3085
|
+
return /* @__PURE__ */ import_react8.default.createElement(import_core_components2.Link, { to: href, variant: "caption" }, text, " \u2192");
|
|
3089
3086
|
};
|
|
3090
|
-
BudgetLimitList = ({ limits, hiddenKeyCount = 0, keysHref }) => /* @__PURE__ */
|
|
3091
|
-
LimitListPanel = ({ children }) => /* @__PURE__ */
|
|
3087
|
+
BudgetLimitList = ({ limits, hiddenKeyCount = 0, keysHref }) => /* @__PURE__ */ import_react8.default.createElement(import_Box7.default, { sx: { display: "flex", flexDirection: "column", gap: 1.25 } }, limits.map((limit) => /* @__PURE__ */ import_react8.default.createElement(TaggedLimit, { key: `${limit.kind}:${limit.sublabel ?? limit.label}`, limit })), hiddenKeyCount > 0 && /* @__PURE__ */ import_react8.default.createElement(MoreKeysNote, { count: hiddenKeyCount, href: keysHref }));
|
|
3088
|
+
LimitListPanel = ({ children }) => /* @__PURE__ */ import_react8.default.createElement(
|
|
3092
3089
|
import_Box7.default,
|
|
3093
3090
|
{
|
|
3094
3091
|
sx: (theme) => ({
|
|
@@ -3104,11 +3101,11 @@ var init_BudgetLimitList = __esm({
|
|
|
3104
3101
|
});
|
|
3105
3102
|
|
|
3106
3103
|
// src/components/LiteLLMBudgetWidget.tsx
|
|
3107
|
-
var
|
|
3104
|
+
var import_react9, import_Paper6, import_Box8, import_Typography8, import_CircularProgress4, import_Alert3, import_Collapse, import_IconButton4, import_icons_material5, import_styles5, import_core_plugin_api3, LevelFrame, EmptyLimitCard, CLOSEST_LEVEL_LABEL, KEYS_PATH, HierarchyNote, FullBody, CompactBody, LiteLLMBudgetWidget;
|
|
3108
3105
|
var init_LiteLLMBudgetWidget = __esm({
|
|
3109
3106
|
"src/components/LiteLLMBudgetWidget.tsx"() {
|
|
3110
3107
|
"use strict";
|
|
3111
|
-
|
|
3108
|
+
import_react9 = __toESM(require("react"));
|
|
3112
3109
|
import_Paper6 = __toESM(require("@mui/material/Paper"));
|
|
3113
3110
|
import_Box8 = __toESM(require("@mui/material/Box"));
|
|
3114
3111
|
import_Typography8 = __toESM(require("@mui/material/Typography"));
|
|
@@ -3131,7 +3128,7 @@ var init_LiteLLMBudgetWidget = __esm({
|
|
|
3131
3128
|
tone,
|
|
3132
3129
|
isLast,
|
|
3133
3130
|
children
|
|
3134
|
-
}) => /* @__PURE__ */
|
|
3131
|
+
}) => /* @__PURE__ */ import_react9.default.createElement(import_Box8.default, { sx: { display: "flex", gap: 1.5 } }, /* @__PURE__ */ import_react9.default.createElement(import_Box8.default, { sx: { display: "flex", flexDirection: "column", alignItems: "center", width: 26, flexShrink: 0 } }, /* @__PURE__ */ import_react9.default.createElement(
|
|
3135
3132
|
import_Box8.default,
|
|
3136
3133
|
{
|
|
3137
3134
|
sx: (theme) => ({
|
|
@@ -3149,7 +3146,7 @@ var init_LiteLLMBudgetWidget = __esm({
|
|
|
3149
3146
|
})
|
|
3150
3147
|
},
|
|
3151
3148
|
rank
|
|
3152
|
-
), /* @__PURE__ */
|
|
3149
|
+
), /* @__PURE__ */ import_react9.default.createElement(
|
|
3153
3150
|
import_Box8.default,
|
|
3154
3151
|
{
|
|
3155
3152
|
sx: (theme) => ({
|
|
@@ -3159,7 +3156,7 @@ var init_LiteLLMBudgetWidget = __esm({
|
|
|
3159
3156
|
bgcolor: isLast ? "transparent" : (0, import_styles5.alpha)(theme.palette.text.primary, 0.12)
|
|
3160
3157
|
})
|
|
3161
3158
|
}
|
|
3162
|
-
)), /* @__PURE__ */
|
|
3159
|
+
)), /* @__PURE__ */ import_react9.default.createElement(import_Box8.default, { sx: { flex: 1, minWidth: 0, pb: isLast ? 0 : 2 } }, /* @__PURE__ */ import_react9.default.createElement(import_Box8.default, { sx: { display: "flex", alignItems: "baseline", gap: 1, flexWrap: "wrap", mb: 0.75 } }, /* @__PURE__ */ import_react9.default.createElement(
|
|
3163
3160
|
import_Typography8.default,
|
|
3164
3161
|
{
|
|
3165
3162
|
sx: (theme) => ({
|
|
@@ -3171,8 +3168,8 @@ var init_LiteLLMBudgetWidget = __esm({
|
|
|
3171
3168
|
})
|
|
3172
3169
|
},
|
|
3173
3170
|
name
|
|
3174
|
-
), /* @__PURE__ */
|
|
3175
|
-
EmptyLimitCard = ({ children }) => /* @__PURE__ */
|
|
3171
|
+
), /* @__PURE__ */ import_react9.default.createElement(import_Typography8.default, { variant: "caption", color: "text.secondary", sx: { fontSize: 11.5 } }, tagline), note && /* @__PURE__ */ import_react9.default.createElement(StatusPill, { label: note, tone: "neutral", dot: false, sx: { ml: "auto" } })), /* @__PURE__ */ import_react9.default.createElement(import_Box8.default, { sx: { display: "flex", flexDirection: "column", gap: 1 } }, children)));
|
|
3172
|
+
EmptyLimitCard = ({ children }) => /* @__PURE__ */ import_react9.default.createElement(
|
|
3176
3173
|
import_Paper6.default,
|
|
3177
3174
|
{
|
|
3178
3175
|
variant: "outlined",
|
|
@@ -3183,7 +3180,7 @@ var init_LiteLLMBudgetWidget = __esm({
|
|
|
3183
3180
|
bgcolor: (0, import_styles5.alpha)(theme.palette.text.primary, theme.palette.mode === "dark" ? 0.03 : 0.015)
|
|
3184
3181
|
})
|
|
3185
3182
|
},
|
|
3186
|
-
/* @__PURE__ */
|
|
3183
|
+
/* @__PURE__ */ import_react9.default.createElement(import_Typography8.default, { variant: "body2", color: "text.secondary", sx: { fontSize: 13 } }, children)
|
|
3187
3184
|
);
|
|
3188
3185
|
CLOSEST_LEVEL_LABEL = {
|
|
3189
3186
|
key: "key",
|
|
@@ -3191,7 +3188,7 @@ var init_LiteLLMBudgetWidget = __esm({
|
|
|
3191
3188
|
team: "team"
|
|
3192
3189
|
};
|
|
3193
3190
|
KEYS_PATH = "/litellm?tab=keys";
|
|
3194
|
-
HierarchyNote = ({ withReset }) => /* @__PURE__ */
|
|
3191
|
+
HierarchyNote = ({ withReset }) => /* @__PURE__ */ import_react9.default.createElement(
|
|
3195
3192
|
import_Box8.default,
|
|
3196
3193
|
{
|
|
3197
3194
|
sx: (theme) => ({
|
|
@@ -3202,11 +3199,11 @@ var init_LiteLLMBudgetWidget = __esm({
|
|
|
3202
3199
|
borderColor: (0, import_styles5.alpha)(theme.palette.text.primary, 0.18)
|
|
3203
3200
|
})
|
|
3204
3201
|
},
|
|
3205
|
-
/* @__PURE__ */
|
|
3202
|
+
/* @__PURE__ */ import_react9.default.createElement(import_Typography8.default, { variant: "caption", color: "text.secondary", display: "block" }, /* @__PURE__ */ import_react9.default.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.")
|
|
3206
3203
|
);
|
|
3207
3204
|
FullBody = ({ summary }) => {
|
|
3208
3205
|
const hasBudgetedKeys = summary.keys.length + summary.hiddenBudgetedKeys > 0;
|
|
3209
|
-
return /* @__PURE__ */
|
|
3206
|
+
return /* @__PURE__ */ import_react9.default.createElement(import_react9.default.Fragment, null, /* @__PURE__ */ import_react9.default.createElement(
|
|
3210
3207
|
LevelFrame,
|
|
3211
3208
|
{
|
|
3212
3209
|
rank: 1,
|
|
@@ -3214,10 +3211,10 @@ var init_LiteLLMBudgetWidget = __esm({
|
|
|
3214
3211
|
tagline: "per-key cap \xB7 highest priority",
|
|
3215
3212
|
note: hasBudgetedKeys ? void 0 : "no key budgets set"
|
|
3216
3213
|
},
|
|
3217
|
-
!hasBudgetedKeys && /* @__PURE__ */
|
|
3218
|
-
summary.keys.map((k) => /* @__PURE__ */
|
|
3219
|
-
summary.hiddenBudgetedKeys > 0 && /* @__PURE__ */
|
|
3220
|
-
), /* @__PURE__ */
|
|
3214
|
+
!hasBudgetedKeys && /* @__PURE__ */ import_react9.default.createElement(EmptyLimitCard, null, "No key has a budget, so no key-level cap applies."),
|
|
3215
|
+
summary.keys.map((k) => /* @__PURE__ */ import_react9.default.createElement(LimitCard, { key: k.sublabel ?? k.label, limit: k })),
|
|
3216
|
+
summary.hiddenBudgetedKeys > 0 && /* @__PURE__ */ import_react9.default.createElement(MoreKeysNote, { count: summary.hiddenBudgetedKeys, href: KEYS_PATH })
|
|
3217
|
+
), /* @__PURE__ */ import_react9.default.createElement(
|
|
3221
3218
|
LevelFrame,
|
|
3222
3219
|
{
|
|
3223
3220
|
rank: 2,
|
|
@@ -3225,15 +3222,15 @@ var init_LiteLLMBudgetWidget = __esm({
|
|
|
3225
3222
|
tagline: "cap on everything you spend with your own keys",
|
|
3226
3223
|
note: "skipped for team-bound keys"
|
|
3227
3224
|
},
|
|
3228
|
-
summary.user ? /* @__PURE__ */
|
|
3229
|
-
), /* @__PURE__ */
|
|
3225
|
+
summary.user ? /* @__PURE__ */ import_react9.default.createElement(LimitCard, { limit: summary.user }) : /* @__PURE__ */ import_react9.default.createElement(EmptyLimitCard, null, "No personal budget is set on your account.")
|
|
3226
|
+
), /* @__PURE__ */ import_react9.default.createElement(LevelFrame, { rank: 3, name: "Team", tagline: "shared cap for all keys in a team (if any)" }, summary.teams.length > 0 ? summary.teams.map((t) => /* @__PURE__ */ import_react9.default.createElement(LimitCard, { key: t.sublabel ?? t.label, limit: t })) : /* @__PURE__ */ import_react9.default.createElement(EmptyLimitCard, null, "No team you belong to has a budget.")), /* @__PURE__ */ import_react9.default.createElement(LevelFrame, { rank: 4, name: "Global", tagline: "proxy-wide cap set by your admin", isLast: true }, /* @__PURE__ */ import_react9.default.createElement(EmptyLimitCard, null, "Configured by your admin in the proxy config \u2014 it isn't visible from this page. When set, it limits spend across every request on the proxy.")), /* @__PURE__ */ import_react9.default.createElement(import_Box8.default, { sx: { mt: 2 } }, /* @__PURE__ */ import_react9.default.createElement(HierarchyNote, { withReset: true })));
|
|
3230
3227
|
};
|
|
3231
3228
|
CompactBody = ({ summary }) => {
|
|
3232
3229
|
const { count } = budgetHeadline(summary);
|
|
3233
3230
|
if (count === 0) {
|
|
3234
|
-
return /* @__PURE__ */
|
|
3231
|
+
return /* @__PURE__ */ import_react9.default.createElement(import_Typography8.default, { variant: "body2", color: "text.secondary", sx: { fontSize: 13 } }, "No budget limits apply to you right now.");
|
|
3235
3232
|
}
|
|
3236
|
-
return /* @__PURE__ */
|
|
3233
|
+
return /* @__PURE__ */ import_react9.default.createElement(import_Box8.default, { sx: { display: "flex", flexDirection: "column", gap: 1.25 } }, /* @__PURE__ */ import_react9.default.createElement(HierarchyNote, null), summary.keys.map((k) => /* @__PURE__ */ import_react9.default.createElement(TaggedLimit, { key: k.sublabel ?? k.label, limit: k })), summary.hiddenBudgetedKeys > 0 && /* @__PURE__ */ import_react9.default.createElement(MoreKeysNote, { count: summary.hiddenBudgetedKeys, href: KEYS_PATH }), summary.user && /* @__PURE__ */ import_react9.default.createElement(TaggedLimit, { limit: summary.user }), summary.teams.map((t) => /* @__PURE__ */ import_react9.default.createElement(TaggedLimit, { key: t.sublabel ?? t.label, limit: t })));
|
|
3237
3234
|
};
|
|
3238
3235
|
LiteLLMBudgetWidget = ({
|
|
3239
3236
|
title = "Budget Policy",
|
|
@@ -3244,13 +3241,13 @@ var init_LiteLLMBudgetWidget = __esm({
|
|
|
3244
3241
|
action
|
|
3245
3242
|
}) => {
|
|
3246
3243
|
const api = (0, import_core_plugin_api3.useApi)(liteLlmApiRef);
|
|
3247
|
-
const [loading, setLoading] = (0,
|
|
3248
|
-
const [error, setError] = (0,
|
|
3249
|
-
const [user, setUser] = (0,
|
|
3250
|
-
const [teams, setTeams] = (0,
|
|
3251
|
-
const [keys, setKeys] = (0,
|
|
3252
|
-
const [expanded, setExpanded] = (0,
|
|
3253
|
-
(0,
|
|
3244
|
+
const [loading, setLoading] = (0, import_react9.useState)(true);
|
|
3245
|
+
const [error, setError] = (0, import_react9.useState)(null);
|
|
3246
|
+
const [user, setUser] = (0, import_react9.useState)(null);
|
|
3247
|
+
const [teams, setTeams] = (0, import_react9.useState)([]);
|
|
3248
|
+
const [keys, setKeys] = (0, import_react9.useState)([]);
|
|
3249
|
+
const [expanded, setExpanded] = (0, import_react9.useState)(defaultExpanded);
|
|
3250
|
+
(0, import_react9.useEffect)(() => {
|
|
3254
3251
|
let cancelled = false;
|
|
3255
3252
|
setLoading(true);
|
|
3256
3253
|
setError(null);
|
|
@@ -3274,18 +3271,18 @@ var init_LiteLLMBudgetWidget = __esm({
|
|
|
3274
3271
|
cancelled = true;
|
|
3275
3272
|
};
|
|
3276
3273
|
}, [api]);
|
|
3277
|
-
const summary = (0,
|
|
3274
|
+
const summary = (0, import_react9.useMemo)(
|
|
3278
3275
|
() => buildBudgetSummary(user, teams, keys, maxKeys),
|
|
3279
3276
|
[user, teams, keys, maxKeys]
|
|
3280
3277
|
);
|
|
3281
|
-
const headline = (0,
|
|
3278
|
+
const headline = (0, import_react9.useMemo)(() => budgetHeadline(summary), [summary]);
|
|
3282
3279
|
const limitsWord = `${headline.count} limit${headline.count === 1 ? "" : "s"}`;
|
|
3283
3280
|
const closestWord = headline.closest === null ? "" : ` \xB7 closest: ${CLOSEST_LEVEL_LABEL[headline.closest.kind]} ${Math.round(
|
|
3284
3281
|
headline.closest.pct
|
|
3285
3282
|
)}%`;
|
|
3286
3283
|
const summaryText = headline.count === 0 ? "no limits apply" : `${limitsWord}${closestWord}`;
|
|
3287
|
-
const body = /* @__PURE__ */
|
|
3288
|
-
return /* @__PURE__ */
|
|
3284
|
+
const body = /* @__PURE__ */ import_react9.default.createElement(import_react9.default.Fragment, null, loading && /* @__PURE__ */ import_react9.default.createElement(import_Box8.default, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: 140 }, /* @__PURE__ */ import_react9.default.createElement(import_CircularProgress4.default, { size: 32 })), !loading && error && /* @__PURE__ */ import_react9.default.createElement(import_Alert3.default, { severity: "error", sx: { mt: 0.5 } }, error), !loading && !error && (compact ? /* @__PURE__ */ import_react9.default.createElement(CompactBody, { summary }) : /* @__PURE__ */ import_react9.default.createElement(FullBody, { summary })));
|
|
3285
|
+
return /* @__PURE__ */ import_react9.default.createElement(import_Paper6.default, { sx: { p: 2 } }, collapsible ? /* @__PURE__ */ import_react9.default.createElement(
|
|
3289
3286
|
import_Box8.default,
|
|
3290
3287
|
{
|
|
3291
3288
|
onClick: () => setExpanded((e) => !e),
|
|
@@ -3298,14 +3295,14 @@ var init_LiteLLMBudgetWidget = __esm({
|
|
|
3298
3295
|
mb: expanded ? 1.5 : 0
|
|
3299
3296
|
}
|
|
3300
3297
|
},
|
|
3301
|
-
/* @__PURE__ */
|
|
3298
|
+
/* @__PURE__ */ import_react9.default.createElement(import_Box8.default, { sx: { minWidth: 0, flex: 1 } }, /* @__PURE__ */ import_react9.default.createElement(import_Typography8.default, { variant: "h6", lineHeight: 1.2 }, title), !loading && !error && /* @__PURE__ */ import_react9.default.createElement(import_Box8.default, { sx: { mt: 0.5 } }, /* @__PURE__ */ import_react9.default.createElement(
|
|
3302
3299
|
StatusPill,
|
|
3303
3300
|
{
|
|
3304
3301
|
label: summaryText,
|
|
3305
3302
|
tone: budgetTone2(headline.closest?.pct ?? 0)
|
|
3306
3303
|
}
|
|
3307
3304
|
))),
|
|
3308
|
-
/* @__PURE__ */
|
|
3305
|
+
/* @__PURE__ */ import_react9.default.createElement(
|
|
3309
3306
|
import_IconButton4.default,
|
|
3310
3307
|
{
|
|
3311
3308
|
size: "small",
|
|
@@ -3321,9 +3318,9 @@ var init_LiteLLMBudgetWidget = __esm({
|
|
|
3321
3318
|
transition: theme.transitions.create("transform")
|
|
3322
3319
|
})
|
|
3323
3320
|
},
|
|
3324
|
-
/* @__PURE__ */
|
|
3321
|
+
/* @__PURE__ */ import_react9.default.createElement(import_icons_material5.ExpandMore, null)
|
|
3325
3322
|
)
|
|
3326
|
-
) : /* @__PURE__ */
|
|
3323
|
+
) : /* @__PURE__ */ import_react9.default.createElement(import_Box8.default, { sx: { mb: 1.5 } }, /* @__PURE__ */ import_react9.default.createElement(import_Typography8.default, { variant: "h6", lineHeight: 1.2 }, title), /* @__PURE__ */ import_react9.default.createElement(import_Typography8.default, { variant: "caption", color: "text.secondary" }, "How LiteLLM caps spend \u2014 and where you stand right now")), collapsible ? /* @__PURE__ */ import_react9.default.createElement(import_Collapse.default, { in: expanded, unmountOnExit: true }, body) : body, action && /* @__PURE__ */ import_react9.default.createElement(
|
|
3327
3324
|
import_Box8.default,
|
|
3328
3325
|
{
|
|
3329
3326
|
sx: (theme) => ({
|
|
@@ -3353,11 +3350,11 @@ function budgetStatValue(hidden, budget) {
|
|
|
3353
3350
|
if (budget > 0) return fmtUsd2(budget);
|
|
3354
3351
|
return "Unlimited";
|
|
3355
3352
|
}
|
|
3356
|
-
var
|
|
3353
|
+
var import_react10, import_Paper7, import_Box9, import_Typography9, import_Skeleton4, import_Divider3, import_Table4, import_TableBody4, import_TableCell4, import_TableHead4, import_TableRow4, import_TableContainer4, import_Collapse2, import_IconButton5, import_Button5, import_CircularProgress5, import_styles6, import_icons_material6, import_recharts2, fmtUsd2, TeamCard, TeamUsage;
|
|
3357
3354
|
var init_TeamUsage = __esm({
|
|
3358
3355
|
"src/components/TeamUsage.tsx"() {
|
|
3359
3356
|
"use strict";
|
|
3360
|
-
|
|
3357
|
+
import_react10 = __toESM(require("react"));
|
|
3361
3358
|
import_Paper7 = __toESM(require("@mui/material/Paper"));
|
|
3362
3359
|
import_Box9 = __toESM(require("@mui/material/Box"));
|
|
3363
3360
|
import_Typography9 = __toESM(require("@mui/material/Typography"));
|
|
@@ -3379,7 +3376,7 @@ var init_TeamUsage = __esm({
|
|
|
3379
3376
|
init_ui();
|
|
3380
3377
|
fmtUsd2 = (n) => `$${(n ?? 0).toFixed(2)}`;
|
|
3381
3378
|
TeamCard = ({ team, usage, usageLoading, canManage, onEditTeam }) => {
|
|
3382
|
-
const [expanded, setExpanded] = (0,
|
|
3379
|
+
const [expanded, setExpanded] = (0, import_react10.useState)(false);
|
|
3383
3380
|
const chart = useChartTheme();
|
|
3384
3381
|
const hidden = team.budget_hidden === true;
|
|
3385
3382
|
const budget = hidden ? 0 : team.max_budget ?? 0;
|
|
@@ -3391,10 +3388,10 @@ var init_TeamUsage = __esm({
|
|
|
3391
3388
|
const renderDailySpendSection = () => {
|
|
3392
3389
|
if (hidden) return null;
|
|
3393
3390
|
if (usageLoading) {
|
|
3394
|
-
return /* @__PURE__ */
|
|
3391
|
+
return /* @__PURE__ */ import_react10.default.createElement(import_Box9.default, { display: "flex", justifyContent: "center", py: 3 }, /* @__PURE__ */ import_react10.default.createElement(import_CircularProgress5.default, { size: 22 }));
|
|
3395
3392
|
}
|
|
3396
3393
|
if (dailyData.length === 0) return null;
|
|
3397
|
-
return /* @__PURE__ */
|
|
3394
|
+
return /* @__PURE__ */ import_react10.default.createElement(import_Box9.default, { mb: 2.5 }, /* @__PURE__ */ import_react10.default.createElement(
|
|
3398
3395
|
import_Typography9.default,
|
|
3399
3396
|
{
|
|
3400
3397
|
variant: "caption",
|
|
@@ -3402,13 +3399,13 @@ var init_TeamUsage = __esm({
|
|
|
3402
3399
|
sx: { display: "block", fontSize: 11, fontWeight: 700, letterSpacing: "0.07em", textTransform: "uppercase", mb: 1 }
|
|
3403
3400
|
},
|
|
3404
3401
|
"Daily spend"
|
|
3405
|
-
), /* @__PURE__ */
|
|
3402
|
+
), /* @__PURE__ */ import_react10.default.createElement(import_Box9.default, { height: 150 }, /* @__PURE__ */ import_react10.default.createElement(import_recharts2.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react10.default.createElement(import_recharts2.AreaChart, { data: dailyData, margin: { top: 4, right: 8, left: 0, bottom: 0 } }, /* @__PURE__ */ import_react10.default.createElement(import_recharts2.CartesianGrid, { ...chart.grid }), /* @__PURE__ */ import_react10.default.createElement(import_recharts2.XAxis, { dataKey: "date", tickFormatter: fmtDateShort, ...chart.axis }), /* @__PURE__ */ import_react10.default.createElement(import_recharts2.YAxis, { tickFormatter: fmtUsdCompact, width: 52, ...chart.axis }), /* @__PURE__ */ import_react10.default.createElement(
|
|
3406
3403
|
import_recharts2.Tooltip,
|
|
3407
3404
|
{
|
|
3408
3405
|
cursor: chart.cursor,
|
|
3409
|
-
content: /* @__PURE__ */
|
|
3406
|
+
content: /* @__PURE__ */ import_react10.default.createElement(ChartTooltip, { valueFormatter: (v) => `$${v.toFixed(4)}` })
|
|
3410
3407
|
}
|
|
3411
|
-
), /* @__PURE__ */
|
|
3408
|
+
), /* @__PURE__ */ import_react10.default.createElement(
|
|
3412
3409
|
import_recharts2.Area,
|
|
3413
3410
|
{
|
|
3414
3411
|
type: "monotone",
|
|
@@ -3421,7 +3418,7 @@ var init_TeamUsage = __esm({
|
|
|
3421
3418
|
}
|
|
3422
3419
|
)))));
|
|
3423
3420
|
};
|
|
3424
|
-
const sectionLabel = (label) => /* @__PURE__ */
|
|
3421
|
+
const sectionLabel = (label) => /* @__PURE__ */ import_react10.default.createElement(
|
|
3425
3422
|
import_Typography9.default,
|
|
3426
3423
|
{
|
|
3427
3424
|
variant: "caption",
|
|
@@ -3430,7 +3427,7 @@ var init_TeamUsage = __esm({
|
|
|
3430
3427
|
},
|
|
3431
3428
|
label
|
|
3432
3429
|
);
|
|
3433
|
-
return /* @__PURE__ */
|
|
3430
|
+
return /* @__PURE__ */ import_react10.default.createElement(import_Paper7.default, { variant: "outlined", sx: { borderRadius: 2, p: 2.5 } }, /* @__PURE__ */ import_react10.default.createElement(import_Box9.default, { display: "flex", alignItems: "center", gap: 1.5 }, /* @__PURE__ */ import_react10.default.createElement(
|
|
3434
3431
|
import_Box9.default,
|
|
3435
3432
|
{
|
|
3436
3433
|
sx: (theme) => ({
|
|
@@ -3445,8 +3442,8 @@ var init_TeamUsage = __esm({
|
|
|
3445
3442
|
color: theme.palette.primary.main
|
|
3446
3443
|
})
|
|
3447
3444
|
},
|
|
3448
|
-
/* @__PURE__ */
|
|
3449
|
-
), /* @__PURE__ */
|
|
3445
|
+
/* @__PURE__ */ import_react10.default.createElement(import_icons_material6.Group, { fontSize: "small" })
|
|
3446
|
+
), /* @__PURE__ */ import_react10.default.createElement(import_Box9.default, { flexGrow: 1, minWidth: 0 }, /* @__PURE__ */ import_react10.default.createElement(import_Typography9.default, { variant: "subtitle1", sx: { fontWeight: 600, lineHeight: 1.3 } }, team.team_alias || "Untitled team"), /* @__PURE__ */ import_react10.default.createElement(
|
|
3450
3447
|
import_Typography9.default,
|
|
3451
3448
|
{
|
|
3452
3449
|
variant: "caption",
|
|
@@ -3454,7 +3451,7 @@ var init_TeamUsage = __esm({
|
|
|
3454
3451
|
sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 11 }
|
|
3455
3452
|
},
|
|
3456
3453
|
team.team_id
|
|
3457
|
-
)), isOver && /* @__PURE__ */
|
|
3454
|
+
)), isOver && /* @__PURE__ */ import_react10.default.createElement(StatusPill, { label: "Over budget", tone: "danger" }), isNear && /* @__PURE__ */ import_react10.default.createElement(StatusPill, { label: "Near limit", tone: "warning" }), canManage && onEditTeam && /* @__PURE__ */ import_react10.default.createElement(
|
|
3458
3455
|
import_Button5.default,
|
|
3459
3456
|
{
|
|
3460
3457
|
size: "small",
|
|
@@ -3463,7 +3460,7 @@ var init_TeamUsage = __esm({
|
|
|
3463
3460
|
sx: { textTransform: "none" }
|
|
3464
3461
|
},
|
|
3465
3462
|
"Edit"
|
|
3466
|
-
), /* @__PURE__ */
|
|
3463
|
+
), /* @__PURE__ */ import_react10.default.createElement(
|
|
3467
3464
|
import_IconButton5.default,
|
|
3468
3465
|
{
|
|
3469
3466
|
size: "small",
|
|
@@ -3476,8 +3473,8 @@ var init_TeamUsage = __esm({
|
|
|
3476
3473
|
transition: theme.transitions.create("transform")
|
|
3477
3474
|
})
|
|
3478
3475
|
},
|
|
3479
|
-
/* @__PURE__ */
|
|
3480
|
-
)), /* @__PURE__ */
|
|
3476
|
+
/* @__PURE__ */ import_react10.default.createElement(import_icons_material6.ExpandMore, null)
|
|
3477
|
+
)), /* @__PURE__ */ import_react10.default.createElement(
|
|
3481
3478
|
import_Box9.default,
|
|
3482
3479
|
{
|
|
3483
3480
|
sx: {
|
|
@@ -3488,20 +3485,20 @@ var init_TeamUsage = __esm({
|
|
|
3488
3485
|
maxWidth: 640
|
|
3489
3486
|
}
|
|
3490
3487
|
},
|
|
3491
|
-
/* @__PURE__ */
|
|
3492
|
-
/* @__PURE__ */
|
|
3493
|
-
/* @__PURE__ */
|
|
3494
|
-
/* @__PURE__ */
|
|
3495
|
-
/* @__PURE__ */
|
|
3496
|
-
/* @__PURE__ */
|
|
3497
|
-
), hidden && /* @__PURE__ */
|
|
3488
|
+
/* @__PURE__ */ import_react10.default.createElement(Stat, { label: "Members", value: team.members_with_roles?.length ?? "\u2014" }),
|
|
3489
|
+
/* @__PURE__ */ import_react10.default.createElement(Stat, { label: "Models", value: team.models?.length ? team.models.length : "All" }),
|
|
3490
|
+
/* @__PURE__ */ import_react10.default.createElement(Stat, { label: "Budget", value: budgetStatValue(hidden, budget) }),
|
|
3491
|
+
/* @__PURE__ */ import_react10.default.createElement(Stat, { label: "Spend", value: hidden ? "\u2014" : fmtUsd2(spend) }),
|
|
3492
|
+
/* @__PURE__ */ import_react10.default.createElement(Stat, { label: "TPM", value: team.tpm_limit && team.tpm_limit > 0 ? team.tpm_limit.toLocaleString() : "\u2014" }),
|
|
3493
|
+
/* @__PURE__ */ import_react10.default.createElement(Stat, { label: "RPM", value: team.rpm_limit && team.rpm_limit > 0 ? team.rpm_limit.toLocaleString() : "\u2014" })
|
|
3494
|
+
), hidden && /* @__PURE__ */ import_react10.default.createElement(import_Box9.default, { mt: 2, maxWidth: 640 }, /* @__PURE__ */ import_react10.default.createElement(import_Box9.default, { display: "flex", justifyContent: "space-between", alignItems: "baseline", mb: 0.75 }, /* @__PURE__ */ import_react10.default.createElement(import_Typography9.default, { variant: "caption", color: "text.secondary", sx: { fontVariantNumeric: "tabular-nums" } }, budgetPct.toFixed(0), "% used"), /* @__PURE__ */ import_react10.default.createElement(import_Typography9.default, { variant: "caption", color: "text.secondary" }, "Hidden by admin")), /* @__PURE__ */ import_react10.default.createElement(Meter, { value: budgetPct, tone: budgetTone3(isOver, isNear), height: 5 }), /* @__PURE__ */ import_react10.default.createElement(import_Typography9.default, { 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__ */ import_react10.default.createElement(import_Box9.default, { mt: 2, maxWidth: 640 }, /* @__PURE__ */ import_react10.default.createElement(import_Box9.default, { display: "flex", justifyContent: "space-between", alignItems: "baseline", mb: 0.75 }, /* @__PURE__ */ import_react10.default.createElement(import_Typography9.default, { variant: "caption", color: "text.secondary", sx: { fontVariantNumeric: "tabular-nums" } }, fmtUsd2(spend), " of ", fmtUsd2(budget)), /* @__PURE__ */ import_react10.default.createElement(import_Typography9.default, { variant: "caption", color: "text.secondary", sx: { fontVariantNumeric: "tabular-nums" } }, budgetPct.toFixed(0), "%")), /* @__PURE__ */ import_react10.default.createElement(Meter, { value: budgetPct, tone: budgetTone3(isOver, isNear), height: 5 })), /* @__PURE__ */ import_react10.default.createElement(import_Collapse2.default, { in: expanded, unmountOnExit: true }, /* @__PURE__ */ import_react10.default.createElement(import_Divider3.default, { sx: { my: 2.5 } }), /* @__PURE__ */ import_react10.default.createElement(import_Box9.default, { mb: 2.5 }, sectionLabel("Models"), team.models?.length ? /* @__PURE__ */ import_react10.default.createElement(import_Box9.default, { display: "flex", gap: 0.5, flexWrap: "wrap" }, team.models.map((m) => /* @__PURE__ */ import_react10.default.createElement(TagChip, { key: m, label: m, title: m }))) : /* @__PURE__ */ import_react10.default.createElement(import_Typography9.default, { variant: "body2", color: "text.secondary" }, "All models allowed")), renderDailySpendSection(), /* @__PURE__ */ import_react10.default.createElement(import_Box9.default, null, sectionLabel("Members"), team.members_with_roles?.length ? /* @__PURE__ */ import_react10.default.createElement(
|
|
3498
3495
|
import_TableContainer4.default,
|
|
3499
3496
|
{
|
|
3500
3497
|
component: import_Paper7.default,
|
|
3501
3498
|
variant: "outlined",
|
|
3502
3499
|
sx: { borderRadius: 1.5 }
|
|
3503
3500
|
},
|
|
3504
|
-
/* @__PURE__ */
|
|
3501
|
+
/* @__PURE__ */ import_react10.default.createElement(import_Table4.default, { size: "small", sx: dataTableSx }, /* @__PURE__ */ import_react10.default.createElement(import_TableHead4.default, null, /* @__PURE__ */ import_react10.default.createElement(import_TableRow4.default, null, /* @__PURE__ */ import_react10.default.createElement(import_TableCell4.default, null, "User"), /* @__PURE__ */ import_react10.default.createElement(import_TableCell4.default, { align: "right" }, "Role"))), /* @__PURE__ */ import_react10.default.createElement(import_TableBody4.default, null, team.members_with_roles.map((m) => /* @__PURE__ */ import_react10.default.createElement(import_TableRow4.default, { key: m.user_id }, /* @__PURE__ */ import_react10.default.createElement(import_TableCell4.default, null, m.user_email ? /* @__PURE__ */ import_react10.default.createElement(import_react10.default.Fragment, null, /* @__PURE__ */ import_react10.default.createElement(import_Typography9.default, { variant: "body2" }, m.user_email), /* @__PURE__ */ import_react10.default.createElement(
|
|
3505
3502
|
import_Typography9.default,
|
|
3506
3503
|
{
|
|
3507
3504
|
variant: "caption",
|
|
@@ -3509,14 +3506,14 @@ var init_TeamUsage = __esm({
|
|
|
3509
3506
|
sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 11 }
|
|
3510
3507
|
},
|
|
3511
3508
|
m.user_id
|
|
3512
|
-
)) : /* @__PURE__ */
|
|
3509
|
+
)) : /* @__PURE__ */ import_react10.default.createElement(
|
|
3513
3510
|
import_Typography9.default,
|
|
3514
3511
|
{
|
|
3515
3512
|
variant: "body2",
|
|
3516
3513
|
sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace" }
|
|
3517
3514
|
},
|
|
3518
3515
|
m.user_id
|
|
3519
|
-
)), /* @__PURE__ */
|
|
3516
|
+
)), /* @__PURE__ */ import_react10.default.createElement(import_TableCell4.default, { align: "right" }, /* @__PURE__ */ import_react10.default.createElement(
|
|
3520
3517
|
StatusPill,
|
|
3521
3518
|
{
|
|
3522
3519
|
label: m.role,
|
|
@@ -3524,7 +3521,7 @@ var init_TeamUsage = __esm({
|
|
|
3524
3521
|
dot: false
|
|
3525
3522
|
}
|
|
3526
3523
|
))))))
|
|
3527
|
-
) : /* @__PURE__ */
|
|
3524
|
+
) : /* @__PURE__ */ import_react10.default.createElement(import_Typography9.default, { variant: "body2", color: "text.secondary" }, "No members assigned."))));
|
|
3528
3525
|
};
|
|
3529
3526
|
TeamUsage = ({
|
|
3530
3527
|
teams,
|
|
@@ -3536,22 +3533,22 @@ var init_TeamUsage = __esm({
|
|
|
3536
3533
|
canCreate,
|
|
3537
3534
|
onCreateTeam
|
|
3538
3535
|
}) => {
|
|
3539
|
-
const createAction = canCreate && onCreateTeam ? /* @__PURE__ */
|
|
3536
|
+
const createAction = canCreate && onCreateTeam ? /* @__PURE__ */ import_react10.default.createElement(
|
|
3540
3537
|
import_Button5.default,
|
|
3541
3538
|
{
|
|
3542
3539
|
variant: "contained",
|
|
3543
3540
|
color: "primary",
|
|
3544
3541
|
disableElevation: true,
|
|
3545
|
-
startIcon: /* @__PURE__ */
|
|
3542
|
+
startIcon: /* @__PURE__ */ import_react10.default.createElement(import_icons_material6.Add, null),
|
|
3546
3543
|
onClick: onCreateTeam
|
|
3547
3544
|
},
|
|
3548
3545
|
"Create Team"
|
|
3549
3546
|
) : void 0;
|
|
3550
3547
|
if (loading) {
|
|
3551
|
-
return /* @__PURE__ */
|
|
3548
|
+
return /* @__PURE__ */ import_react10.default.createElement(SectionCard, { title: "Teams", actions: createAction }, /* @__PURE__ */ import_react10.default.createElement(import_Skeleton4.default, { variant: "rounded", height: 120, sx: { mb: 2 } }), /* @__PURE__ */ import_react10.default.createElement(import_Skeleton4.default, { variant: "rounded", height: 120 }));
|
|
3552
3549
|
}
|
|
3553
3550
|
if (!teams.length) {
|
|
3554
|
-
return /* @__PURE__ */
|
|
3551
|
+
return /* @__PURE__ */ import_react10.default.createElement(SectionCard, { title: "Teams", actions: createAction }, /* @__PURE__ */ import_react10.default.createElement(
|
|
3555
3552
|
EmptyState,
|
|
3556
3553
|
{
|
|
3557
3554
|
message: "You're not a member of any LiteLLM team yet.",
|
|
@@ -3559,14 +3556,14 @@ var init_TeamUsage = __esm({
|
|
|
3559
3556
|
}
|
|
3560
3557
|
));
|
|
3561
3558
|
}
|
|
3562
|
-
return /* @__PURE__ */
|
|
3559
|
+
return /* @__PURE__ */ import_react10.default.createElement(
|
|
3563
3560
|
SectionCard,
|
|
3564
3561
|
{
|
|
3565
3562
|
title: "Teams",
|
|
3566
3563
|
subtitle: `${teams.length} team${teams.length === 1 ? "" : "s"}`,
|
|
3567
3564
|
actions: createAction
|
|
3568
3565
|
},
|
|
3569
|
-
/* @__PURE__ */
|
|
3566
|
+
/* @__PURE__ */ import_react10.default.createElement(import_Box9.default, { sx: { display: "flex", flexDirection: "column", gap: 2 } }, teams.map((team) => /* @__PURE__ */ import_react10.default.createElement(
|
|
3570
3567
|
TeamCard,
|
|
3571
3568
|
{
|
|
3572
3569
|
key: team.team_id,
|
|
@@ -3600,11 +3597,11 @@ function fmtTokens(n) {
|
|
|
3600
3597
|
if (n >= 1e3) return `${(n / 1e3).toFixed(0)}K`;
|
|
3601
3598
|
return String(n);
|
|
3602
3599
|
}
|
|
3603
|
-
var
|
|
3600
|
+
var import_react11, import_Box10, import_Table5, import_TableBody5, import_TableCell5, import_TableContainer5, import_TableHead5, import_TableRow5, import_TableSortLabel2, import_Typography10, import_MenuItem4, import_Select, import_FormControl, import_InputLabel, import_TextField5, import_InputAdornment2, import_icons_material7, import_ContentCopy, import_Tooltip, import_Snackbar, import_Alert4, import_styles7, ALL_PROXY_MODELS2, ModelsTable;
|
|
3604
3601
|
var init_ModelsTable = __esm({
|
|
3605
3602
|
"src/components/ModelsTable.tsx"() {
|
|
3606
3603
|
"use strict";
|
|
3607
|
-
|
|
3604
|
+
import_react11 = __toESM(require("react"));
|
|
3608
3605
|
import_Box10 = __toESM(require("@mui/material/Box"));
|
|
3609
3606
|
import_Table5 = __toESM(require("@mui/material/Table"));
|
|
3610
3607
|
import_TableBody5 = __toESM(require("@mui/material/TableBody"));
|
|
@@ -3630,20 +3627,20 @@ var init_ModelsTable = __esm({
|
|
|
3630
3627
|
ALL_PROXY_MODELS2 = "all-proxy-models";
|
|
3631
3628
|
ModelsTable = ({ allModels, teams, loading }) => {
|
|
3632
3629
|
const theme = (0, import_styles7.useTheme)();
|
|
3633
|
-
const [selectedTeamId, setSelectedTeamId] = (0,
|
|
3634
|
-
const [filterText, setFilterText] = (0,
|
|
3635
|
-
const [copiedSnackbar, setCopiedSnackbar] = (0,
|
|
3636
|
-
const [sortKey, setSortKey] = (0,
|
|
3637
|
-
const [sortDirection, setSortDirection] = (0,
|
|
3638
|
-
const selectedTeam = (0,
|
|
3630
|
+
const [selectedTeamId, setSelectedTeamId] = (0, import_react11.useState)("");
|
|
3631
|
+
const [filterText, setFilterText] = (0, import_react11.useState)("");
|
|
3632
|
+
const [copiedSnackbar, setCopiedSnackbar] = (0, import_react11.useState)(false);
|
|
3633
|
+
const [sortKey, setSortKey] = (0, import_react11.useState)("model_name");
|
|
3634
|
+
const [sortDirection, setSortDirection] = (0, import_react11.useState)("asc");
|
|
3635
|
+
const selectedTeam = (0, import_react11.useMemo)(
|
|
3639
3636
|
() => teams.find((t) => t.team_id === selectedTeamId) ?? null,
|
|
3640
3637
|
[teams, selectedTeamId]
|
|
3641
3638
|
);
|
|
3642
|
-
const teamModels = (0,
|
|
3639
|
+
const teamModels = (0, import_react11.useMemo)(() => {
|
|
3643
3640
|
if (!selectedTeam) return [];
|
|
3644
3641
|
return allModels.filter((model) => isModelAllowedByTeam2(model, selectedTeam.models));
|
|
3645
3642
|
}, [allModels, selectedTeam]);
|
|
3646
|
-
const filteredModels = (0,
|
|
3643
|
+
const filteredModels = (0, import_react11.useMemo)(() => {
|
|
3647
3644
|
const q = filterText.trim().toLowerCase();
|
|
3648
3645
|
let models = teamModels;
|
|
3649
3646
|
if (q) {
|
|
@@ -3690,10 +3687,10 @@ var init_ModelsTable = __esm({
|
|
|
3690
3687
|
return sortDirection === "asc" ? aVal - bVal : bVal - aVal;
|
|
3691
3688
|
});
|
|
3692
3689
|
}, [teamModels, filterText, sortKey, sortDirection]);
|
|
3693
|
-
const handleCopyModelId = (0,
|
|
3690
|
+
const handleCopyModelId = (0, import_react11.useCallback)((modelId) => {
|
|
3694
3691
|
navigator.clipboard.writeText(modelId).then(() => setCopiedSnackbar(true));
|
|
3695
3692
|
}, []);
|
|
3696
|
-
const handleSort = (0,
|
|
3693
|
+
const handleSort = (0, import_react11.useCallback)((key) => {
|
|
3697
3694
|
if (sortKey === key) {
|
|
3698
3695
|
setSortDirection((prev) => prev === "asc" ? "desc" : "asc");
|
|
3699
3696
|
} else {
|
|
@@ -3715,13 +3712,13 @@ var init_ModelsTable = __esm({
|
|
|
3715
3712
|
return theme.palette.info.main;
|
|
3716
3713
|
}
|
|
3717
3714
|
};
|
|
3718
|
-
return /* @__PURE__ */
|
|
3715
|
+
return /* @__PURE__ */ import_react11.default.createElement(
|
|
3719
3716
|
SectionCard,
|
|
3720
3717
|
{
|
|
3721
3718
|
title: "Available Models",
|
|
3722
3719
|
subtitle: selectedTeam ? `${filteredModels.length}${filterText.trim() ? ` of ${teamModels.length}` : ""} model${filteredModels.length === 1 ? "" : "s"}` : "Select a team to view its models"
|
|
3723
3720
|
},
|
|
3724
|
-
/* @__PURE__ */
|
|
3721
|
+
/* @__PURE__ */ import_react11.default.createElement(import_Box10.default, { sx: { px: 2.5, pb: 2.5, pt: 1, display: "flex", gap: 2, flexWrap: "wrap" } }, /* @__PURE__ */ import_react11.default.createElement(import_FormControl.default, { size: "small", sx: { minWidth: 240, mb: 2 } }, /* @__PURE__ */ import_react11.default.createElement(import_InputLabel.default, { id: "team-select-label" }, "Team"), /* @__PURE__ */ import_react11.default.createElement(
|
|
3725
3722
|
import_Select.default,
|
|
3726
3723
|
{
|
|
3727
3724
|
labelId: "team-select-label",
|
|
@@ -3729,8 +3726,8 @@ var init_ModelsTable = __esm({
|
|
|
3729
3726
|
label: "Team",
|
|
3730
3727
|
onChange: (e) => setSelectedTeamId(e.target.value)
|
|
3731
3728
|
},
|
|
3732
|
-
teams.map((t) => /* @__PURE__ */
|
|
3733
|
-
)), /* @__PURE__ */
|
|
3729
|
+
teams.map((t) => /* @__PURE__ */ import_react11.default.createElement(import_MenuItem4.default, { key: t.team_id, value: t.team_id }, t.team_alias ?? t.team_id))
|
|
3730
|
+
)), /* @__PURE__ */ import_react11.default.createElement(
|
|
3734
3731
|
import_TextField5.default,
|
|
3735
3732
|
{
|
|
3736
3733
|
label: "Filter Models",
|
|
@@ -3740,33 +3737,33 @@ var init_ModelsTable = __esm({
|
|
|
3740
3737
|
disabled: !selectedTeamId,
|
|
3741
3738
|
sx: { minWidth: 240, mb: 2 },
|
|
3742
3739
|
InputProps: {
|
|
3743
|
-
startAdornment: /* @__PURE__ */
|
|
3740
|
+
startAdornment: /* @__PURE__ */ import_react11.default.createElement(import_InputAdornment2.default, { position: "start" }, /* @__PURE__ */ import_react11.default.createElement(import_icons_material7.Search, { fontSize: "small" }))
|
|
3744
3741
|
}
|
|
3745
3742
|
}
|
|
3746
3743
|
)),
|
|
3747
|
-
loading && /* @__PURE__ */
|
|
3748
|
-
!loading && !selectedTeamId && /* @__PURE__ */
|
|
3744
|
+
loading && /* @__PURE__ */ import_react11.default.createElement(EmptyState, { message: "Loading models\u2026" }),
|
|
3745
|
+
!loading && !selectedTeamId && /* @__PURE__ */ import_react11.default.createElement(
|
|
3749
3746
|
EmptyState,
|
|
3750
3747
|
{
|
|
3751
3748
|
message: "Select a team to see available models",
|
|
3752
3749
|
hint: "Each team may have a different set of models assigned"
|
|
3753
3750
|
}
|
|
3754
3751
|
),
|
|
3755
|
-
!loading && selectedTeamId && teamModels.length === 0 && /* @__PURE__ */
|
|
3752
|
+
!loading && selectedTeamId && teamModels.length === 0 && /* @__PURE__ */ import_react11.default.createElement(
|
|
3756
3753
|
EmptyState,
|
|
3757
3754
|
{
|
|
3758
3755
|
message: "No models available for this team",
|
|
3759
3756
|
hint: allModels.length === 0 ? "No models are configured in the system" : "None of the team's assigned models were found in the catalog"
|
|
3760
3757
|
}
|
|
3761
3758
|
),
|
|
3762
|
-
!loading && selectedTeamId && teamModels.length > 0 && filteredModels.length === 0 && /* @__PURE__ */
|
|
3759
|
+
!loading && selectedTeamId && teamModels.length > 0 && filteredModels.length === 0 && /* @__PURE__ */ import_react11.default.createElement(
|
|
3763
3760
|
EmptyState,
|
|
3764
3761
|
{
|
|
3765
3762
|
message: "No models match your search",
|
|
3766
3763
|
hint: "Try a different search term"
|
|
3767
3764
|
}
|
|
3768
3765
|
),
|
|
3769
|
-
!loading && filteredModels.length > 0 && /* @__PURE__ */
|
|
3766
|
+
!loading && filteredModels.length > 0 && /* @__PURE__ */ import_react11.default.createElement(import_TableContainer5.default, null, /* @__PURE__ */ import_react11.default.createElement(import_Table5.default, { size: "small", sx: dataTableSx }, /* @__PURE__ */ import_react11.default.createElement(import_TableHead5.default, null, /* @__PURE__ */ import_react11.default.createElement(import_TableRow5.default, null, /* @__PURE__ */ import_react11.default.createElement(import_TableCell5.default, { sortDirection: sortKey === "model_name" ? sortDirection : false }, /* @__PURE__ */ import_react11.default.createElement(import_TableSortLabel2.default, { active: sortKey === "model_name", direction: sortKey === "model_name" ? sortDirection : "asc", onClick: () => handleSort("model_name") }, "Model ID")), /* @__PURE__ */ import_react11.default.createElement(import_TableCell5.default, { sortDirection: sortKey === "mode" ? sortDirection : false }, /* @__PURE__ */ import_react11.default.createElement(import_TableSortLabel2.default, { active: sortKey === "mode", direction: sortKey === "mode" ? sortDirection : "asc", onClick: () => handleSort("mode") }, "Mode")), /* @__PURE__ */ import_react11.default.createElement(import_TableCell5.default, { align: "right", sortDirection: sortKey === "input_cost_per_token" ? sortDirection : false }, /* @__PURE__ */ import_react11.default.createElement(import_TableSortLabel2.default, { active: sortKey === "input_cost_per_token", direction: sortKey === "input_cost_per_token" ? sortDirection : "asc", onClick: () => handleSort("input_cost_per_token") }, "Input Cost")), /* @__PURE__ */ import_react11.default.createElement(import_TableCell5.default, { align: "right", sortDirection: sortKey === "output_cost_per_token" ? sortDirection : false }, /* @__PURE__ */ import_react11.default.createElement(import_TableSortLabel2.default, { active: sortKey === "output_cost_per_token", direction: sortKey === "output_cost_per_token" ? sortDirection : "asc", onClick: () => handleSort("output_cost_per_token") }, "Output Cost")), /* @__PURE__ */ import_react11.default.createElement(import_TableCell5.default, { align: "right", sortDirection: sortKey === "max_input_tokens" ? sortDirection : false }, /* @__PURE__ */ import_react11.default.createElement(import_TableSortLabel2.default, { active: sortKey === "max_input_tokens", direction: sortKey === "max_input_tokens" ? sortDirection : "asc", onClick: () => handleSort("max_input_tokens") }, "Max Input")), /* @__PURE__ */ import_react11.default.createElement(import_TableCell5.default, { align: "right", sortDirection: sortKey === "max_output_tokens" ? sortDirection : false }, /* @__PURE__ */ import_react11.default.createElement(import_TableSortLabel2.default, { active: sortKey === "max_output_tokens", direction: sortKey === "max_output_tokens" ? sortDirection : "asc", onClick: () => handleSort("max_output_tokens") }, "Max Output")), /* @__PURE__ */ import_react11.default.createElement(import_TableCell5.default, null, "Capabilities"))), /* @__PURE__ */ import_react11.default.createElement(import_TableBody5.default, null, filteredModels.map((m) => /* @__PURE__ */ import_react11.default.createElement(import_TableRow5.default, { key: m.model_name, hover: true }, /* @__PURE__ */ import_react11.default.createElement(import_TableCell5.default, null, /* @__PURE__ */ import_react11.default.createElement(import_Box10.default, { sx: { display: "flex", alignItems: "center", gap: 0.5 } }, /* @__PURE__ */ import_react11.default.createElement(import_Tooltip.default, { title: m.model_name, placement: "top" }, /* @__PURE__ */ import_react11.default.createElement(
|
|
3770
3767
|
"span",
|
|
3771
3768
|
{
|
|
3772
3769
|
role: "button",
|
|
@@ -3776,8 +3773,8 @@ var init_ModelsTable = __esm({
|
|
|
3776
3773
|
onKeyDown: (e) => e.key === "Enter" && handleCopyModelId(m.model_name),
|
|
3777
3774
|
style: { cursor: "pointer", display: "inline-flex" }
|
|
3778
3775
|
},
|
|
3779
|
-
/* @__PURE__ */
|
|
3780
|
-
)), /* @__PURE__ */
|
|
3776
|
+
/* @__PURE__ */ import_react11.default.createElement(TagChip, { label: m.model_name, title: "Copy model ID" })
|
|
3777
|
+
)), /* @__PURE__ */ import_react11.default.createElement(import_Tooltip.default, { title: "Copy model ID" }, /* @__PURE__ */ import_react11.default.createElement(
|
|
3781
3778
|
import_Box10.default,
|
|
3782
3779
|
{
|
|
3783
3780
|
component: "span",
|
|
@@ -3788,8 +3785,8 @@ var init_ModelsTable = __esm({
|
|
|
3788
3785
|
sx: quietIconButtonSx("accent"),
|
|
3789
3786
|
style: { cursor: "pointer", display: "inline-flex" }
|
|
3790
3787
|
},
|
|
3791
|
-
/* @__PURE__ */
|
|
3792
|
-
)))), /* @__PURE__ */
|
|
3788
|
+
/* @__PURE__ */ import_react11.default.createElement(import_ContentCopy.default, { sx: { fontSize: 14 } })
|
|
3789
|
+
)))), /* @__PURE__ */ import_react11.default.createElement(import_TableCell5.default, null, /* @__PURE__ */ import_react11.default.createElement(
|
|
3793
3790
|
import_Box10.default,
|
|
3794
3791
|
{
|
|
3795
3792
|
component: "span",
|
|
@@ -3806,8 +3803,8 @@ var init_ModelsTable = __esm({
|
|
|
3806
3803
|
}
|
|
3807
3804
|
},
|
|
3808
3805
|
m.mode
|
|
3809
|
-
)), /* @__PURE__ */
|
|
3810
|
-
/* @__PURE__ */
|
|
3806
|
+
)), /* @__PURE__ */ import_react11.default.createElement(import_TableCell5.default, { align: "right" }, /* @__PURE__ */ import_react11.default.createElement(import_Typography10.default, { variant: "body2", sx: { fontVariantNumeric: "tabular-nums" } }, fmtCostPerToken(m.input_cost_per_token))), /* @__PURE__ */ import_react11.default.createElement(import_TableCell5.default, { align: "right" }, /* @__PURE__ */ import_react11.default.createElement(import_Typography10.default, { variant: "body2", sx: { fontVariantNumeric: "tabular-nums" } }, fmtCostPerToken(m.output_cost_per_token))), /* @__PURE__ */ import_react11.default.createElement(import_TableCell5.default, { align: "right" }, /* @__PURE__ */ import_react11.default.createElement(import_Typography10.default, { variant: "body2", sx: { fontVariantNumeric: "tabular-nums" } }, fmtTokens(m.max_input_tokens))), /* @__PURE__ */ import_react11.default.createElement(import_TableCell5.default, { align: "right" }, /* @__PURE__ */ import_react11.default.createElement(import_Typography10.default, { variant: "body2", sx: { fontVariantNumeric: "tabular-nums" } }, fmtTokens(m.max_output_tokens))), /* @__PURE__ */ import_react11.default.createElement(import_TableCell5.default, null, /* @__PURE__ */ import_react11.default.createElement(import_Box10.default, { sx: { display: "flex", gap: 0.5, flexWrap: "wrap" } }, m.supports_function_calling && /* @__PURE__ */ import_react11.default.createElement(TagChip, { label: "Fn", title: "Function calling" }), m.supports_vision && /* @__PURE__ */ import_react11.default.createElement(TagChip, { label: "\u{1F441}", title: "Vision" })))))))),
|
|
3807
|
+
/* @__PURE__ */ import_react11.default.createElement(
|
|
3811
3808
|
import_Snackbar.default,
|
|
3812
3809
|
{
|
|
3813
3810
|
open: copiedSnackbar,
|
|
@@ -3815,7 +3812,7 @@ var init_ModelsTable = __esm({
|
|
|
3815
3812
|
onClose: () => setCopiedSnackbar(false),
|
|
3816
3813
|
anchorOrigin: { vertical: "bottom", horizontal: "center" }
|
|
3817
3814
|
},
|
|
3818
|
-
/* @__PURE__ */
|
|
3815
|
+
/* @__PURE__ */ import_react11.default.createElement(import_Alert4.default, { severity: "success", variant: "filled", sx: { fontSize: 13 } }, "Model ID copied to clipboard")
|
|
3819
3816
|
)
|
|
3820
3817
|
);
|
|
3821
3818
|
};
|
|
@@ -3843,10 +3840,10 @@ function formatDateTime(iso) {
|
|
|
3843
3840
|
}
|
|
3844
3841
|
function renderAuditLogBody(loading, entries) {
|
|
3845
3842
|
if (loading) {
|
|
3846
|
-
return /* @__PURE__ */
|
|
3843
|
+
return /* @__PURE__ */ import_react12.default.createElement(import_TableRow6.default, null, /* @__PURE__ */ import_react12.default.createElement(import_TableCell6.default, { colSpan: 6, sx: { py: 2 } }, /* @__PURE__ */ import_react12.default.createElement(import_Skeleton5.default, { variant: "rounded", height: 160 })));
|
|
3847
3844
|
}
|
|
3848
3845
|
if (entries.length === 0) {
|
|
3849
|
-
return /* @__PURE__ */
|
|
3846
|
+
return /* @__PURE__ */ import_react12.default.createElement(import_TableRow6.default, null, /* @__PURE__ */ import_react12.default.createElement(import_TableCell6.default, { colSpan: 6 }, /* @__PURE__ */ import_react12.default.createElement(
|
|
3850
3847
|
EmptyState,
|
|
3851
3848
|
{
|
|
3852
3849
|
message: "No audit events found",
|
|
@@ -3854,13 +3851,13 @@ function renderAuditLogBody(loading, entries) {
|
|
|
3854
3851
|
}
|
|
3855
3852
|
)));
|
|
3856
3853
|
}
|
|
3857
|
-
return entries.map((entry) => /* @__PURE__ */
|
|
3854
|
+
return entries.map((entry) => /* @__PURE__ */ import_react12.default.createElement(DetailRow, { key: entry.id, entry }));
|
|
3858
3855
|
}
|
|
3859
|
-
var
|
|
3856
|
+
var import_react12, import_Box11, import_Typography11, import_Table6, import_TableBody6, import_TableCell6, import_TableContainer6, import_TableHead6, import_TableRow6, import_TablePagination, import_TextField6, import_MenuItem5, import_Skeleton5, import_Collapse3, import_IconButton6, import_Alert5, import_styles8, import_icons_material8, import_react_use2, ACTION_TONES, TABLE_LABELS, DetailRow, AuditLog;
|
|
3860
3857
|
var init_AuditLog = __esm({
|
|
3861
3858
|
"src/components/AuditLog.tsx"() {
|
|
3862
3859
|
"use strict";
|
|
3863
|
-
|
|
3860
|
+
import_react12 = __toESM(require("react"));
|
|
3864
3861
|
import_Box11 = __toESM(require("@mui/material/Box"));
|
|
3865
3862
|
import_Typography11 = __toESM(require("@mui/material/Typography"));
|
|
3866
3863
|
import_Table6 = __toESM(require("@mui/material/Table"));
|
|
@@ -3895,9 +3892,9 @@ var init_AuditLog = __esm({
|
|
|
3895
3892
|
LiteLLM_UserTable: "User"
|
|
3896
3893
|
};
|
|
3897
3894
|
DetailRow = ({ entry }) => {
|
|
3898
|
-
const [open, setOpen] = (0,
|
|
3895
|
+
const [open, setOpen] = (0, import_react12.useState)(false);
|
|
3899
3896
|
const hasDetail = entry.before_value || entry.updated_values;
|
|
3900
|
-
return /* @__PURE__ */
|
|
3897
|
+
return /* @__PURE__ */ import_react12.default.createElement(import_react12.default.Fragment, null, /* @__PURE__ */ import_react12.default.createElement(import_TableRow6.default, null, /* @__PURE__ */ import_react12.default.createElement(import_TableCell6.default, { sx: { width: 40, pr: 0 } }, hasDetail && /* @__PURE__ */ import_react12.default.createElement(
|
|
3901
3898
|
import_IconButton6.default,
|
|
3902
3899
|
{
|
|
3903
3900
|
size: "small",
|
|
@@ -3910,8 +3907,8 @@ var init_AuditLog = __esm({
|
|
|
3910
3907
|
transition: theme.transitions.create("transform")
|
|
3911
3908
|
})
|
|
3912
3909
|
},
|
|
3913
|
-
/* @__PURE__ */
|
|
3914
|
-
)), /* @__PURE__ */
|
|
3910
|
+
/* @__PURE__ */ import_react12.default.createElement(import_icons_material8.KeyboardArrowDown, { fontSize: "small" })
|
|
3911
|
+
)), /* @__PURE__ */ import_react12.default.createElement(import_TableCell6.default, { sx: { whiteSpace: "nowrap" } }, /* @__PURE__ */ import_react12.default.createElement(import_Typography11.default, { variant: "body2", color: "text.secondary" }, formatDateTime(entry.updated_at))), /* @__PURE__ */ import_react12.default.createElement(import_TableCell6.default, null, entry.action && /* @__PURE__ */ import_react12.default.createElement(StatusPill, { label: entry.action, tone: actionTone(entry.action) })), /* @__PURE__ */ import_react12.default.createElement(import_TableCell6.default, null, /* @__PURE__ */ import_react12.default.createElement(import_Typography11.default, { variant: "body2" }, prettyTableName(entry.table_name))), /* @__PURE__ */ import_react12.default.createElement(import_TableCell6.default, null, /* @__PURE__ */ import_react12.default.createElement(
|
|
3915
3912
|
import_Typography11.default,
|
|
3916
3913
|
{
|
|
3917
3914
|
variant: "body2",
|
|
@@ -3921,7 +3918,7 @@ var init_AuditLog = __esm({
|
|
|
3921
3918
|
sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 12 }
|
|
3922
3919
|
},
|
|
3923
3920
|
entry.object_id ? entry.object_id.slice(0, 20) + (entry.object_id.length > 20 ? "\u2026" : "") : "\u2014"
|
|
3924
|
-
)), /* @__PURE__ */
|
|
3921
|
+
)), /* @__PURE__ */ import_react12.default.createElement(import_TableCell6.default, null, entry.changed_by ?? "\u2014")), hasDetail && /* @__PURE__ */ import_react12.default.createElement(import_TableRow6.default, { sx: { "&&:hover": { bgcolor: "transparent" } } }, /* @__PURE__ */ import_react12.default.createElement(import_TableCell6.default, { colSpan: 6, sx: { "&&": { py: 0, border: 0 } } }, /* @__PURE__ */ import_react12.default.createElement(import_Collapse3.default, { in: open, unmountOnExit: true }, /* @__PURE__ */ import_react12.default.createElement(
|
|
3925
3922
|
import_Box11.default,
|
|
3926
3923
|
{
|
|
3927
3924
|
display: "flex",
|
|
@@ -3934,7 +3931,7 @@ var init_AuditLog = __esm({
|
|
|
3934
3931
|
bgcolor: (0, import_styles8.alpha)(theme.palette.text.primary, theme.palette.mode === "dark" ? 0.05 : 0.03)
|
|
3935
3932
|
})
|
|
3936
3933
|
},
|
|
3937
|
-
entry.before_value && /* @__PURE__ */
|
|
3934
|
+
entry.before_value && /* @__PURE__ */ import_react12.default.createElement(import_Box11.default, { flex: 1, minWidth: 200 }, /* @__PURE__ */ import_react12.default.createElement(
|
|
3938
3935
|
import_Typography11.default,
|
|
3939
3936
|
{
|
|
3940
3937
|
variant: "caption",
|
|
@@ -3944,8 +3941,8 @@ var init_AuditLog = __esm({
|
|
|
3944
3941
|
sx: { fontSize: 10.5, fontWeight: 700, letterSpacing: "0.07em", textTransform: "uppercase" }
|
|
3945
3942
|
},
|
|
3946
3943
|
"Before"
|
|
3947
|
-
), /* @__PURE__ */
|
|
3948
|
-
entry.updated_values && /* @__PURE__ */
|
|
3944
|
+
), /* @__PURE__ */ import_react12.default.createElement(import_Typography11.default, { component: "pre", variant: "caption", sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", whiteSpace: "pre-wrap", wordBreak: "break-all", m: 0 } }, JSON.stringify(entry.before_value, null, 2))),
|
|
3945
|
+
entry.updated_values && /* @__PURE__ */ import_react12.default.createElement(import_Box11.default, { flex: 1, minWidth: 200 }, /* @__PURE__ */ import_react12.default.createElement(
|
|
3949
3946
|
import_Typography11.default,
|
|
3950
3947
|
{
|
|
3951
3948
|
variant: "caption",
|
|
@@ -3955,14 +3952,14 @@ var init_AuditLog = __esm({
|
|
|
3955
3952
|
sx: { fontSize: 10.5, fontWeight: 700, letterSpacing: "0.07em", textTransform: "uppercase" }
|
|
3956
3953
|
},
|
|
3957
3954
|
"After"
|
|
3958
|
-
), /* @__PURE__ */
|
|
3955
|
+
), /* @__PURE__ */ import_react12.default.createElement(import_Typography11.default, { component: "pre", variant: "caption", sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", whiteSpace: "pre-wrap", wordBreak: "break-all", m: 0 } }, JSON.stringify(entry.updated_values, null, 2)))
|
|
3959
3956
|
)))));
|
|
3960
3957
|
};
|
|
3961
3958
|
AuditLog = ({ api }) => {
|
|
3962
|
-
const [page, setPage] = (0,
|
|
3963
|
-
const [pageSize, setPageSize] = (0,
|
|
3964
|
-
const [filters, setFilters] = (0,
|
|
3965
|
-
const fetchParams = (0,
|
|
3959
|
+
const [page, setPage] = (0, import_react12.useState)(0);
|
|
3960
|
+
const [pageSize, setPageSize] = (0, import_react12.useState)(25);
|
|
3961
|
+
const [filters, setFilters] = (0, import_react12.useState)({});
|
|
3962
|
+
const fetchParams = (0, import_react12.useMemo)(
|
|
3966
3963
|
() => ({ page: page + 1, page_size: pageSize, ...filters }),
|
|
3967
3964
|
[page, pageSize, filters]
|
|
3968
3965
|
);
|
|
@@ -3972,13 +3969,13 @@ var init_AuditLog = __esm({
|
|
|
3972
3969
|
);
|
|
3973
3970
|
const entries = value?.audit_logs ?? [];
|
|
3974
3971
|
const total = value?.total ?? 0;
|
|
3975
|
-
return /* @__PURE__ */
|
|
3972
|
+
return /* @__PURE__ */ import_react12.default.createElement(
|
|
3976
3973
|
SectionCard,
|
|
3977
3974
|
{
|
|
3978
3975
|
title: "Audit Log",
|
|
3979
3976
|
subtitle: loading ? "Loading\u2026" : `${total.toLocaleString()} event${total === 1 ? "" : "s"}`,
|
|
3980
3977
|
flush: true,
|
|
3981
|
-
actions: /* @__PURE__ */
|
|
3978
|
+
actions: /* @__PURE__ */ import_react12.default.createElement(import_react12.default.Fragment, null, /* @__PURE__ */ import_react12.default.createElement(
|
|
3982
3979
|
import_TextField6.default,
|
|
3983
3980
|
{
|
|
3984
3981
|
size: "small",
|
|
@@ -3991,12 +3988,12 @@ var init_AuditLog = __esm({
|
|
|
3991
3988
|
},
|
|
3992
3989
|
sx: { minWidth: 150 }
|
|
3993
3990
|
},
|
|
3994
|
-
/* @__PURE__ */
|
|
3995
|
-
/* @__PURE__ */
|
|
3996
|
-
/* @__PURE__ */
|
|
3997
|
-
/* @__PURE__ */
|
|
3998
|
-
/* @__PURE__ */
|
|
3999
|
-
), /* @__PURE__ */
|
|
3991
|
+
/* @__PURE__ */ import_react12.default.createElement(import_MenuItem5.default, { value: "" }, "All actions"),
|
|
3992
|
+
/* @__PURE__ */ import_react12.default.createElement(import_MenuItem5.default, { value: "created" }, "Created"),
|
|
3993
|
+
/* @__PURE__ */ import_react12.default.createElement(import_MenuItem5.default, { value: "updated" }, "Updated"),
|
|
3994
|
+
/* @__PURE__ */ import_react12.default.createElement(import_MenuItem5.default, { value: "deleted" }, "Deleted"),
|
|
3995
|
+
/* @__PURE__ */ import_react12.default.createElement(import_MenuItem5.default, { value: "blocked" }, "Blocked")
|
|
3996
|
+
), /* @__PURE__ */ import_react12.default.createElement(
|
|
4000
3997
|
import_TextField6.default,
|
|
4001
3998
|
{
|
|
4002
3999
|
size: "small",
|
|
@@ -4009,13 +4006,13 @@ var init_AuditLog = __esm({
|
|
|
4009
4006
|
},
|
|
4010
4007
|
sx: { minWidth: 150 }
|
|
4011
4008
|
},
|
|
4012
|
-
/* @__PURE__ */
|
|
4013
|
-
/* @__PURE__ */
|
|
4014
|
-
/* @__PURE__ */
|
|
4015
|
-
/* @__PURE__ */
|
|
4016
|
-
/* @__PURE__ */
|
|
4017
|
-
/* @__PURE__ */
|
|
4018
|
-
), /* @__PURE__ */
|
|
4009
|
+
/* @__PURE__ */ import_react12.default.createElement(import_MenuItem5.default, { value: "" }, "All tables"),
|
|
4010
|
+
/* @__PURE__ */ import_react12.default.createElement(import_MenuItem5.default, { value: "LiteLLM_VerificationToken" }, "Key"),
|
|
4011
|
+
/* @__PURE__ */ import_react12.default.createElement(import_MenuItem5.default, { value: "LiteLLM_TeamTable" }, "Team"),
|
|
4012
|
+
/* @__PURE__ */ import_react12.default.createElement(import_MenuItem5.default, { value: "LiteLLM_TeamMembership" }, "Team member"),
|
|
4013
|
+
/* @__PURE__ */ import_react12.default.createElement(import_MenuItem5.default, { value: "LiteLLM_ObjectPermissionTable" }, "Team access (KB / MCP)"),
|
|
4014
|
+
/* @__PURE__ */ import_react12.default.createElement(import_MenuItem5.default, { value: "LiteLLM_UserTable" }, "User")
|
|
4015
|
+
), /* @__PURE__ */ import_react12.default.createElement(
|
|
4019
4016
|
import_TextField6.default,
|
|
4020
4017
|
{
|
|
4021
4018
|
size: "small",
|
|
@@ -4029,9 +4026,9 @@ var init_AuditLog = __esm({
|
|
|
4029
4026
|
}
|
|
4030
4027
|
))
|
|
4031
4028
|
},
|
|
4032
|
-
error && /* @__PURE__ */
|
|
4033
|
-
/* @__PURE__ */
|
|
4034
|
-
/* @__PURE__ */
|
|
4029
|
+
error && /* @__PURE__ */ import_react12.default.createElement(import_Box11.default, { px: 2.5, pb: 2 }, /* @__PURE__ */ import_react12.default.createElement(import_Alert5.default, { severity: "error" }, error.message)),
|
|
4030
|
+
/* @__PURE__ */ import_react12.default.createElement(import_TableContainer6.default, null, /* @__PURE__ */ import_react12.default.createElement(import_Table6.default, { size: "small", sx: dataTableSx }, /* @__PURE__ */ import_react12.default.createElement(import_TableHead6.default, null, /* @__PURE__ */ import_react12.default.createElement(import_TableRow6.default, null, /* @__PURE__ */ import_react12.default.createElement(import_TableCell6.default, { sx: { width: 40 } }), /* @__PURE__ */ import_react12.default.createElement(import_TableCell6.default, null, "Time"), /* @__PURE__ */ import_react12.default.createElement(import_TableCell6.default, null, "Action"), /* @__PURE__ */ import_react12.default.createElement(import_TableCell6.default, null, "Table"), /* @__PURE__ */ import_react12.default.createElement(import_TableCell6.default, null, "Object ID"), /* @__PURE__ */ import_react12.default.createElement(import_TableCell6.default, null, "Changed By"))), /* @__PURE__ */ import_react12.default.createElement(import_TableBody6.default, null, renderAuditLogBody(loading, entries)))),
|
|
4031
|
+
/* @__PURE__ */ import_react12.default.createElement(
|
|
4035
4032
|
import_TablePagination.default,
|
|
4036
4033
|
{
|
|
4037
4034
|
component: "div",
|
|
@@ -4100,11 +4097,11 @@ function initDateRange() {
|
|
|
4100
4097
|
else start.setDate(start.getDate() - 7);
|
|
4101
4098
|
return { start, end };
|
|
4102
4099
|
}
|
|
4103
|
-
var
|
|
4100
|
+
var import_react13, import_Box12, import_Snackbar2, import_Alert6, import_CircularProgress6, import_Typography12, import_Paper8, import_Tabs2, import_Tab2, import_Grid2, import_react_router_dom, import_react_use3, import_core_plugin_api4, import_plugin_permission_react, PERIOD_LS_KEY2, PAGE_TABS, isPageTab, LiteLLMPage;
|
|
4104
4101
|
var init_LiteLLMPage = __esm({
|
|
4105
4102
|
"src/components/LiteLLMPage.tsx"() {
|
|
4106
4103
|
"use strict";
|
|
4107
|
-
|
|
4104
|
+
import_react13 = __toESM(require("react"));
|
|
4108
4105
|
import_Box12 = __toESM(require("@mui/material/Box"));
|
|
4109
4106
|
import_Snackbar2 = __toESM(require("@mui/material/Snackbar"));
|
|
4110
4107
|
import_Alert6 = __toESM(require("@mui/material/Alert"));
|
|
@@ -4120,7 +4117,7 @@ var init_LiteLLMPage = __esm({
|
|
|
4120
4117
|
import_plugin_permission_react = require("@backstage/plugin-permission-react");
|
|
4121
4118
|
init_DashboardHeader();
|
|
4122
4119
|
init_KeysTable();
|
|
4123
|
-
|
|
4120
|
+
init_KeyFormDialog();
|
|
4124
4121
|
init_ManageTeamDialog();
|
|
4125
4122
|
init_UsageStats();
|
|
4126
4123
|
init_LiteLLMBudgetWidget();
|
|
@@ -4134,8 +4131,8 @@ var init_LiteLLMPage = __esm({
|
|
|
4134
4131
|
isPageTab = (v) => !!v && PAGE_TABS.includes(v);
|
|
4135
4132
|
LiteLLMPage = () => {
|
|
4136
4133
|
const api = (0, import_core_plugin_api4.useApi)(liteLlmApiRef);
|
|
4137
|
-
const [dateRange, setDateRange] = (0,
|
|
4138
|
-
const [currentPreset, setCurrentPreset] = (0,
|
|
4134
|
+
const [dateRange, setDateRange] = (0, import_react13.useState)(initDateRange);
|
|
4135
|
+
const [currentPreset, setCurrentPreset] = (0, import_react13.useState)(() => {
|
|
4139
4136
|
try {
|
|
4140
4137
|
return localStorage.getItem(PERIOD_LS_KEY2) ?? "7d";
|
|
4141
4138
|
} catch {
|
|
@@ -4143,14 +4140,14 @@ var init_LiteLLMPage = __esm({
|
|
|
4143
4140
|
}
|
|
4144
4141
|
});
|
|
4145
4142
|
const [searchParams, setSearchParams] = (0, import_react_router_dom.useSearchParams)();
|
|
4146
|
-
const [activeTab, setActiveTab] = (0,
|
|
4143
|
+
const [activeTab, setActiveTab] = (0, import_react13.useState)(() => {
|
|
4147
4144
|
const t = searchParams.get("tab");
|
|
4148
4145
|
return isPageTab(t) ? t : "overview";
|
|
4149
4146
|
});
|
|
4150
|
-
const [generateDialogOpen, setGenerateDialogOpen] = (0,
|
|
4147
|
+
const [generateDialogOpen, setGenerateDialogOpen] = (0, import_react13.useState)(
|
|
4151
4148
|
() => searchParams.get("generate") === "1"
|
|
4152
4149
|
);
|
|
4153
|
-
(0,
|
|
4150
|
+
(0, import_react13.useEffect)(() => {
|
|
4154
4151
|
if (searchParams.get("generate") !== "1") return;
|
|
4155
4152
|
setGenerateDialogOpen(true);
|
|
4156
4153
|
setSearchParams(
|
|
@@ -4162,7 +4159,7 @@ var init_LiteLLMPage = __esm({
|
|
|
4162
4159
|
{ replace: true }
|
|
4163
4160
|
);
|
|
4164
4161
|
}, [searchParams, setSearchParams]);
|
|
4165
|
-
const selectTab = (0,
|
|
4162
|
+
const selectTab = (0, import_react13.useCallback)(
|
|
4166
4163
|
(tab) => {
|
|
4167
4164
|
setActiveTab(tab);
|
|
4168
4165
|
setSearchParams(
|
|
@@ -4176,10 +4173,12 @@ var init_LiteLLMPage = __esm({
|
|
|
4176
4173
|
},
|
|
4177
4174
|
[setSearchParams]
|
|
4178
4175
|
);
|
|
4179
|
-
const [snackbar, setSnackbar] = (0,
|
|
4180
|
-
const [manageTeam, setManageTeam] = (0,
|
|
4181
|
-
const [
|
|
4182
|
-
const
|
|
4176
|
+
const [snackbar, setSnackbar] = (0, import_react13.useState)(null);
|
|
4177
|
+
const [manageTeam, setManageTeam] = (0, import_react13.useState)(null);
|
|
4178
|
+
const [keyToEdit, setKeyToEdit] = (0, import_react13.useState)(null);
|
|
4179
|
+
const keyFormOpen = generateDialogOpen || !!keyToEdit;
|
|
4180
|
+
const [teamUsageCache, setTeamUsageCache] = (0, import_react13.useState)({});
|
|
4181
|
+
const [teamUsageLoading, setTeamUsageLoading] = (0, import_react13.useState)({});
|
|
4183
4182
|
const { value: userInfo, loading: userLoading, error: userError } = (0, import_react_use3.useAsync)(
|
|
4184
4183
|
() => api.getUserInfo(),
|
|
4185
4184
|
[api]
|
|
@@ -4223,7 +4222,7 @@ var init_LiteLLMPage = __esm({
|
|
|
4223
4222
|
async () => objectPermsEnabled ? api.getMcpServers().catch(() => []) : [],
|
|
4224
4223
|
[api, objectPermsEnabled]
|
|
4225
4224
|
);
|
|
4226
|
-
const teams = (0,
|
|
4225
|
+
const teams = (0, import_react13.useMemo)(() => {
|
|
4227
4226
|
if (!allTeams?.length) return [];
|
|
4228
4227
|
if (!userInfo) return allTeams;
|
|
4229
4228
|
const userId = userInfo.user_id;
|
|
@@ -4240,7 +4239,7 @@ var init_LiteLLMPage = __esm({
|
|
|
4240
4239
|
const endDate = dateRange.end.toISOString().split("T")[0];
|
|
4241
4240
|
return api.getUsage(startDate, endDate);
|
|
4242
4241
|
}, [api, dateRange]);
|
|
4243
|
-
const handleDateRangeChange = (0,
|
|
4242
|
+
const handleDateRangeChange = (0, import_react13.useCallback)((range, preset) => {
|
|
4244
4243
|
setDateRange(range);
|
|
4245
4244
|
if (preset) {
|
|
4246
4245
|
setCurrentPreset(preset);
|
|
@@ -4248,7 +4247,7 @@ var init_LiteLLMPage = __esm({
|
|
|
4248
4247
|
setTeamUsageCache({});
|
|
4249
4248
|
setTeamUsageLoading({});
|
|
4250
4249
|
}, [setDateRange, setCurrentPreset]);
|
|
4251
|
-
const loadTeamUsage = (0,
|
|
4250
|
+
const loadTeamUsage = (0, import_react13.useCallback)(async (teamId) => {
|
|
4252
4251
|
if (teamUsageCache[teamId] !== void 0 || teamUsageLoading[teamId]) return;
|
|
4253
4252
|
setTeamUsageLoading((prev) => ({ ...prev, [teamId]: true }));
|
|
4254
4253
|
try {
|
|
@@ -4262,7 +4261,7 @@ var init_LiteLLMPage = __esm({
|
|
|
4262
4261
|
setTeamUsageLoading((prev) => ({ ...prev, [teamId]: false }));
|
|
4263
4262
|
}
|
|
4264
4263
|
}, [api, dateRange, teamUsageCache, teamUsageLoading]);
|
|
4265
|
-
const allowedModels = (0,
|
|
4264
|
+
const allowedModels = (0, import_react13.useMemo)(() => {
|
|
4266
4265
|
if (!allModels?.length) return [];
|
|
4267
4266
|
const userModels = userInfo?.models;
|
|
4268
4267
|
const teamModels = teams?.flatMap((t) => t.models ?? []);
|
|
@@ -4275,7 +4274,7 @@ var init_LiteLLMPage = __esm({
|
|
|
4275
4274
|
]);
|
|
4276
4275
|
return allModels.filter((m) => allowed.has(m.model_name));
|
|
4277
4276
|
}, [allModels, userInfo, teams]);
|
|
4278
|
-
const handleGenerateKey = (0,
|
|
4277
|
+
const handleGenerateKey = (0, import_react13.useCallback)(
|
|
4279
4278
|
async (request) => {
|
|
4280
4279
|
try {
|
|
4281
4280
|
const response = await api.generateKey(request);
|
|
@@ -4289,7 +4288,7 @@ var init_LiteLLMPage = __esm({
|
|
|
4289
4288
|
},
|
|
4290
4289
|
[api, refreshKeys]
|
|
4291
4290
|
);
|
|
4292
|
-
const handleUpdateKey = (0,
|
|
4291
|
+
const handleUpdateKey = (0, import_react13.useCallback)(
|
|
4293
4292
|
async (keyId, request) => {
|
|
4294
4293
|
try {
|
|
4295
4294
|
await api.updateKey(keyId, request);
|
|
@@ -4302,7 +4301,7 @@ var init_LiteLLMPage = __esm({
|
|
|
4302
4301
|
},
|
|
4303
4302
|
[api, refreshKeys]
|
|
4304
4303
|
);
|
|
4305
|
-
const handleBlockKey = (0,
|
|
4304
|
+
const handleBlockKey = (0, import_react13.useCallback)(
|
|
4306
4305
|
async (keyId) => {
|
|
4307
4306
|
try {
|
|
4308
4307
|
await api.blockKey(keyId);
|
|
@@ -4314,7 +4313,7 @@ var init_LiteLLMPage = __esm({
|
|
|
4314
4313
|
},
|
|
4315
4314
|
[api, refreshKeys]
|
|
4316
4315
|
);
|
|
4317
|
-
const handleUnblockKey = (0,
|
|
4316
|
+
const handleUnblockKey = (0, import_react13.useCallback)(
|
|
4318
4317
|
async (keyId) => {
|
|
4319
4318
|
try {
|
|
4320
4319
|
await api.unblockKey(keyId);
|
|
@@ -4326,7 +4325,7 @@ var init_LiteLLMPage = __esm({
|
|
|
4326
4325
|
},
|
|
4327
4326
|
[api, refreshKeys]
|
|
4328
4327
|
);
|
|
4329
|
-
const handleResetKeySpend = (0,
|
|
4328
|
+
const handleResetKeySpend = (0, import_react13.useCallback)(
|
|
4330
4329
|
async (keyId) => {
|
|
4331
4330
|
try {
|
|
4332
4331
|
await api.resetKeySpend(keyId);
|
|
@@ -4338,7 +4337,7 @@ var init_LiteLLMPage = __esm({
|
|
|
4338
4337
|
},
|
|
4339
4338
|
[api, refreshKeys]
|
|
4340
4339
|
);
|
|
4341
|
-
const handleDeleteKey = (0,
|
|
4340
|
+
const handleDeleteKey = (0, import_react13.useCallback)(
|
|
4342
4341
|
async (keyId) => {
|
|
4343
4342
|
try {
|
|
4344
4343
|
await api.deleteKey(keyId);
|
|
@@ -4355,7 +4354,7 @@ var init_LiteLLMPage = __esm({
|
|
|
4355
4354
|
},
|
|
4356
4355
|
[api, refreshKeys]
|
|
4357
4356
|
);
|
|
4358
|
-
const handlePruneExpiredKeys = (0,
|
|
4357
|
+
const handlePruneExpiredKeys = (0, import_react13.useCallback)(
|
|
4359
4358
|
async () => {
|
|
4360
4359
|
try {
|
|
4361
4360
|
const result = await api.pruneExpiredKeys();
|
|
@@ -4370,14 +4369,14 @@ var init_LiteLLMPage = __esm({
|
|
|
4370
4369
|
);
|
|
4371
4370
|
const isInitialLoading = userLoading && !userInfo;
|
|
4372
4371
|
if (isInitialLoading) {
|
|
4373
|
-
return /* @__PURE__ */
|
|
4372
|
+
return /* @__PURE__ */ import_react13.default.createElement(import_Box12.default, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: "50vh" }, /* @__PURE__ */ import_react13.default.createElement(import_CircularProgress6.default, null));
|
|
4374
4373
|
}
|
|
4375
4374
|
if (userError || !userInfo) {
|
|
4376
4375
|
const isProvisioningEnabled = userError?.body?.provisioning === true;
|
|
4377
4376
|
const hint = userError?.body?.hint;
|
|
4378
|
-
return /* @__PURE__ */
|
|
4377
|
+
return /* @__PURE__ */ import_react13.default.createElement(import_Box12.default, { p: 3 }, /* @__PURE__ */ import_react13.default.createElement(import_Paper8.default, { sx: { p: 3 } }, /* @__PURE__ */ import_react13.default.createElement(import_Typography12.default, { variant: "h6", gutterBottom: true }, "Account not provisioned"), /* @__PURE__ */ import_react13.default.createElement(import_Typography12.default, { color: "text.secondary", paragraph: true }, "Your Backstage account is not linked to a LiteLLM user."), hint ? /* @__PURE__ */ import_react13.default.createElement(import_Typography12.default, { variant: "body2", color: "text.secondary" }, hint) : /* @__PURE__ */ import_react13.default.createElement(import_Typography12.default, { variant: "body2", color: "text.secondary" }, isProvisioningEnabled ? "Auto-provisioning is enabled but failed. Check the backend logs." : "Set litellm.provisioning.enabled: true in app-config.yaml to enable auto-provisioning, or ask your administrator to create the account manually.")));
|
|
4379
4378
|
}
|
|
4380
|
-
const pageTabs = /* @__PURE__ */
|
|
4379
|
+
const pageTabs = /* @__PURE__ */ import_react13.default.createElement(
|
|
4381
4380
|
import_Tabs2.default,
|
|
4382
4381
|
{
|
|
4383
4382
|
value: activeTab,
|
|
@@ -4390,13 +4389,13 @@ var init_LiteLLMPage = __esm({
|
|
|
4390
4389
|
'& [class*="MuiTab-root"]': { minHeight: 44, textTransform: "none", fontSize: 14 }
|
|
4391
4390
|
}
|
|
4392
4391
|
},
|
|
4393
|
-
/* @__PURE__ */
|
|
4394
|
-
/* @__PURE__ */
|
|
4395
|
-
/* @__PURE__ */
|
|
4396
|
-
/* @__PURE__ */
|
|
4397
|
-
userInfo.can_view_audit && /* @__PURE__ */
|
|
4392
|
+
/* @__PURE__ */ import_react13.default.createElement(import_Tab2.default, { label: "Overview", value: "overview" }),
|
|
4393
|
+
/* @__PURE__ */ import_react13.default.createElement(import_Tab2.default, { label: "Keys", value: "keys" }),
|
|
4394
|
+
/* @__PURE__ */ import_react13.default.createElement(import_Tab2.default, { label: "Teams", value: "teams" }),
|
|
4395
|
+
/* @__PURE__ */ import_react13.default.createElement(import_Tab2.default, { label: "Models", value: "models" }),
|
|
4396
|
+
userInfo.can_view_audit && /* @__PURE__ */ import_react13.default.createElement(import_Tab2.default, { label: "Audit Log", value: "audit" })
|
|
4398
4397
|
);
|
|
4399
|
-
return /* @__PURE__ */
|
|
4398
|
+
return /* @__PURE__ */ import_react13.default.createElement(import_Box12.default, { sx: { p: 3, display: "flex", flexDirection: "column", gap: 2 } }, /* @__PURE__ */ import_react13.default.createElement(
|
|
4400
4399
|
DashboardHeader,
|
|
4401
4400
|
{
|
|
4402
4401
|
userInfo,
|
|
@@ -4406,7 +4405,7 @@ var init_LiteLLMPage = __esm({
|
|
|
4406
4405
|
onGenerateKeyClick: () => setGenerateDialogOpen(true),
|
|
4407
4406
|
tabs: pageTabs
|
|
4408
4407
|
}
|
|
4409
|
-
), activeTab === "overview" && /* @__PURE__ */
|
|
4408
|
+
), activeTab === "overview" && /* @__PURE__ */ import_react13.default.createElement(import_Grid2.default, { container: true, spacing: 2, alignItems: "flex-start" }, /* @__PURE__ */ import_react13.default.createElement(import_Grid2.default, { item: true, xs: 12, lg: 8 }, /* @__PURE__ */ import_react13.default.createElement(
|
|
4410
4409
|
UsageStats,
|
|
4411
4410
|
{
|
|
4412
4411
|
usage: usage ?? null,
|
|
@@ -4417,17 +4416,15 @@ var init_LiteLLMPage = __esm({
|
|
|
4417
4416
|
loading: usageLoading,
|
|
4418
4417
|
userInfo
|
|
4419
4418
|
}
|
|
4420
|
-
)), /* @__PURE__ */
|
|
4419
|
+
)), /* @__PURE__ */ import_react13.default.createElement(import_Grid2.default, { item: true, xs: 12, lg: 4 }, /* @__PURE__ */ import_react13.default.createElement(LiteLLMBudgetWidget, { compact: true, collapsible: true }))), activeTab === "keys" && /* @__PURE__ */ import_react13.default.createElement(
|
|
4421
4420
|
KeysTable,
|
|
4422
4421
|
{
|
|
4423
4422
|
keys: keys ?? [],
|
|
4424
|
-
models: allowedModels,
|
|
4425
4423
|
loading: keysLoading || modelsLoading,
|
|
4426
4424
|
onGenerateKeyClick: () => setGenerateDialogOpen(true),
|
|
4427
|
-
|
|
4425
|
+
onEditKey: setKeyToEdit,
|
|
4428
4426
|
onBlockKey: handleBlockKey,
|
|
4429
4427
|
onUnblockKey: handleUnblockKey,
|
|
4430
|
-
onResetKeySpend: handleResetKeySpend,
|
|
4431
4428
|
onDeleteKey: handleDeleteKey,
|
|
4432
4429
|
onPruneExpiredKeys: handlePruneExpiredKeys
|
|
4433
4430
|
}
|
|
@@ -4436,7 +4433,7 @@ var init_LiteLLMPage = __esm({
|
|
|
4436
4433
|
for (const t of managedTeams ?? []) teamsById.set(t.team_id, t);
|
|
4437
4434
|
for (const t of teams ?? []) teamsById.set(t.team_id, t);
|
|
4438
4435
|
const visibleTeams = Array.from(teamsById.values());
|
|
4439
|
-
return /* @__PURE__ */
|
|
4436
|
+
return /* @__PURE__ */ import_react13.default.createElement(
|
|
4440
4437
|
TeamUsage,
|
|
4441
4438
|
{
|
|
4442
4439
|
teams: visibleTeams,
|
|
@@ -4452,27 +4449,34 @@ var init_LiteLLMPage = __esm({
|
|
|
4452
4449
|
onCreateTeam: () => setManageTeam({ mode: "create" })
|
|
4453
4450
|
}
|
|
4454
4451
|
);
|
|
4455
|
-
})(), activeTab === "models" && /* @__PURE__ */
|
|
4452
|
+
})(), activeTab === "models" && /* @__PURE__ */ import_react13.default.createElement(
|
|
4456
4453
|
ModelsTable,
|
|
4457
4454
|
{
|
|
4458
4455
|
allModels: allModels ?? [],
|
|
4459
4456
|
teams: teams ?? [],
|
|
4460
4457
|
loading: modelsLoading
|
|
4461
4458
|
}
|
|
4462
|
-
), activeTab === "audit" && userInfo.can_view_audit && /* @__PURE__ */
|
|
4463
|
-
|
|
4459
|
+
), activeTab === "audit" && userInfo.can_view_audit && /* @__PURE__ */ import_react13.default.createElement(AuditLog, { api }), /* @__PURE__ */ import_react13.default.createElement(
|
|
4460
|
+
KeyFormDialog,
|
|
4464
4461
|
{
|
|
4465
|
-
open:
|
|
4466
|
-
onClose: () =>
|
|
4462
|
+
open: keyFormOpen,
|
|
4463
|
+
onClose: () => {
|
|
4464
|
+
setGenerateDialogOpen(false);
|
|
4465
|
+
setKeyToEdit(null);
|
|
4466
|
+
},
|
|
4467
|
+
mode: keyToEdit ? "edit" : "create",
|
|
4468
|
+
keyToEdit,
|
|
4467
4469
|
keys: keys ?? [],
|
|
4468
4470
|
models: allowedModels,
|
|
4469
4471
|
teams: teams ?? [],
|
|
4470
4472
|
username: userInfo.user_id,
|
|
4471
4473
|
keyGenerationSettings: liteLlmConfig?.keyGeneration,
|
|
4472
|
-
|
|
4474
|
+
onCreateKey: handleGenerateKey,
|
|
4475
|
+
onUpdateKey: handleUpdateKey,
|
|
4476
|
+
onResetKeySpend: handleResetKeySpend,
|
|
4473
4477
|
onGetConfig: () => api.getConfig()
|
|
4474
4478
|
}
|
|
4475
|
-
), /* @__PURE__ */
|
|
4479
|
+
), /* @__PURE__ */ import_react13.default.createElement(
|
|
4476
4480
|
ManageTeamDialog,
|
|
4477
4481
|
{
|
|
4478
4482
|
open: !!manageTeam,
|
|
@@ -4534,7 +4538,7 @@ var init_LiteLLMPage = __esm({
|
|
|
4534
4538
|
refreshTeams();
|
|
4535
4539
|
}
|
|
4536
4540
|
}
|
|
4537
|
-
), /* @__PURE__ */
|
|
4541
|
+
), /* @__PURE__ */ import_react13.default.createElement(
|
|
4538
4542
|
import_Snackbar2.default,
|
|
4539
4543
|
{
|
|
4540
4544
|
open: !!snackbar,
|
|
@@ -4542,7 +4546,7 @@ var init_LiteLLMPage = __esm({
|
|
|
4542
4546
|
onClose: () => setSnackbar(null),
|
|
4543
4547
|
anchorOrigin: { vertical: "bottom", horizontal: "right" }
|
|
4544
4548
|
},
|
|
4545
|
-
snackbar ? /* @__PURE__ */
|
|
4549
|
+
snackbar ? /* @__PURE__ */ import_react13.default.createElement(import_Alert6.default, { severity: snackbar.severity, onClose: () => setSnackbar(null) }, snackbar.message) : void 0
|
|
4546
4550
|
));
|
|
4547
4551
|
};
|
|
4548
4552
|
}
|
|
@@ -4552,7 +4556,9 @@ var init_LiteLLMPage = __esm({
|
|
|
4552
4556
|
var index_exports = {};
|
|
4553
4557
|
__export(index_exports, {
|
|
4554
4558
|
DashboardHeader: () => DashboardHeader,
|
|
4559
|
+
GenerateKeyButton: () => GenerateKeyButton,
|
|
4555
4560
|
GenerateKeyDialog: () => GenerateKeyDialog,
|
|
4561
|
+
KeyFormDialog: () => KeyFormDialog,
|
|
4556
4562
|
KeysTable: () => KeysTable,
|
|
4557
4563
|
LiteLLMBudgetGauges: () => LiteLLMBudgetGauges,
|
|
4558
4564
|
LiteLLMBudgetWidget: () => LiteLLMBudgetWidget,
|
|
@@ -4573,7 +4579,7 @@ __export(index_exports, {
|
|
|
4573
4579
|
module.exports = __toCommonJS(index_exports);
|
|
4574
4580
|
|
|
4575
4581
|
// src/plugin.tsx
|
|
4576
|
-
var
|
|
4582
|
+
var import_react14 = __toESM(require("react"));
|
|
4577
4583
|
var import_icons_material9 = require("@mui/icons-material");
|
|
4578
4584
|
var import_frontend_plugin_api = require("@backstage/frontend-plugin-api");
|
|
4579
4585
|
init_api();
|
|
@@ -4588,10 +4594,10 @@ var liteLlmPage = import_frontend_plugin_api.PageBlueprint.make({
|
|
|
4588
4594
|
params: {
|
|
4589
4595
|
path: "/litellm",
|
|
4590
4596
|
title: "LiteLLM",
|
|
4591
|
-
icon: /* @__PURE__ */
|
|
4597
|
+
icon: /* @__PURE__ */ import_react14.default.createElement(import_icons_material9.TrendingUp, null),
|
|
4592
4598
|
loader: async () => {
|
|
4593
4599
|
const { LiteLLMPage: LiteLLMPage2 } = await Promise.resolve().then(() => (init_LiteLLMPage(), LiteLLMPage_exports));
|
|
4594
|
-
return /* @__PURE__ */
|
|
4600
|
+
return /* @__PURE__ */ import_react14.default.createElement(LiteLLMPage2, null);
|
|
4595
4601
|
}
|
|
4596
4602
|
}
|
|
4597
4603
|
});
|
|
@@ -4608,7 +4614,7 @@ init_UsageStats();
|
|
|
4608
4614
|
init_TeamUsage();
|
|
4609
4615
|
|
|
4610
4616
|
// src/components/LiteLLMHomeWidget.tsx
|
|
4611
|
-
var
|
|
4617
|
+
var import_react15 = __toESM(require("react"));
|
|
4612
4618
|
var import_Paper9 = __toESM(require("@mui/material/Paper"));
|
|
4613
4619
|
var import_Box13 = __toESM(require("@mui/material/Box"));
|
|
4614
4620
|
var import_Typography13 = __toESM(require("@mui/material/Typography"));
|
|
@@ -4634,19 +4640,19 @@ function presetToDateRange(preset) {
|
|
|
4634
4640
|
}
|
|
4635
4641
|
return { start, end };
|
|
4636
4642
|
}
|
|
4637
|
-
var Kpi = ({ label, value }) => /* @__PURE__ */
|
|
4643
|
+
var Kpi = ({ label, value }) => /* @__PURE__ */ import_react15.default.createElement(import_Box13.default, null, /* @__PURE__ */ import_react15.default.createElement(import_Typography13.default, { variant: "caption", color: "text.secondary", display: "block" }, label), /* @__PURE__ */ import_react15.default.createElement(import_Typography13.default, { variant: "subtitle1", fontWeight: 600 }, value));
|
|
4638
4644
|
var LiteLLMHomeWidget = ({
|
|
4639
4645
|
defaultPeriod = "7d",
|
|
4640
4646
|
title = "LiteLLM Usage"
|
|
4641
4647
|
}) => {
|
|
4642
4648
|
const api = (0, import_core_plugin_api5.useApi)(liteLlmApiRef);
|
|
4643
|
-
const [period, setPeriod] = (0,
|
|
4644
|
-
const [loading, setLoading] = (0,
|
|
4645
|
-
const [usageError, setUsageError] = (0,
|
|
4646
|
-
const [keysError, setKeysError] = (0,
|
|
4647
|
-
const [usage, setUsage] = (0,
|
|
4648
|
-
const [keys, setKeys] = (0,
|
|
4649
|
-
(0,
|
|
4649
|
+
const [period, setPeriod] = (0, import_react15.useState)(defaultPeriod);
|
|
4650
|
+
const [loading, setLoading] = (0, import_react15.useState)(true);
|
|
4651
|
+
const [usageError, setUsageError] = (0, import_react15.useState)(null);
|
|
4652
|
+
const [keysError, setKeysError] = (0, import_react15.useState)(null);
|
|
4653
|
+
const [usage, setUsage] = (0, import_react15.useState)(null);
|
|
4654
|
+
const [keys, setKeys] = (0, import_react15.useState)([]);
|
|
4655
|
+
(0, import_react15.useEffect)(() => {
|
|
4650
4656
|
let cancelled = false;
|
|
4651
4657
|
setLoading(true);
|
|
4652
4658
|
setUsageError(null);
|
|
@@ -4683,17 +4689,17 @@ var LiteLLMHomeWidget = ({
|
|
|
4683
4689
|
spend: d.spend
|
|
4684
4690
|
}));
|
|
4685
4691
|
const hasSparkline = dailyData.length > 0;
|
|
4686
|
-
return /* @__PURE__ */
|
|
4692
|
+
return /* @__PURE__ */ import_react15.default.createElement(import_Paper9.default, { sx: { p: 2 } }, /* @__PURE__ */ import_react15.default.createElement(import_Box13.default, { display: "flex", justifyContent: "space-between", alignItems: "center", mb: 1.5 }, /* @__PURE__ */ import_react15.default.createElement(import_Typography13.default, { variant: "h6" }, title), /* @__PURE__ */ import_react15.default.createElement(import_FormControl2.default, { size: "small", sx: { minWidth: 90 } }, /* @__PURE__ */ import_react15.default.createElement(
|
|
4687
4693
|
import_Select2.default,
|
|
4688
4694
|
{
|
|
4689
4695
|
value: period,
|
|
4690
4696
|
onChange: (e) => setPeriod(e.target.value),
|
|
4691
4697
|
displayEmpty: true
|
|
4692
4698
|
},
|
|
4693
|
-
/* @__PURE__ */
|
|
4694
|
-
/* @__PURE__ */
|
|
4695
|
-
/* @__PURE__ */
|
|
4696
|
-
))), loading && /* @__PURE__ */
|
|
4699
|
+
/* @__PURE__ */ import_react15.default.createElement(import_MenuItem6.default, { value: "today" }, "Today"),
|
|
4700
|
+
/* @__PURE__ */ import_react15.default.createElement(import_MenuItem6.default, { value: "7d" }, "7d"),
|
|
4701
|
+
/* @__PURE__ */ import_react15.default.createElement(import_MenuItem6.default, { value: "30d" }, "30d")
|
|
4702
|
+
))), loading && /* @__PURE__ */ import_react15.default.createElement(import_Box13.default, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: 120 }, /* @__PURE__ */ import_react15.default.createElement(import_CircularProgress7.default, { size: 32 })), !loading && totalFailure && /* @__PURE__ */ import_react15.default.createElement(import_Alert7.default, { severity: "error", sx: { mt: 1 } }, usageError ?? "Failed to load usage data"), !loading && !totalFailure && /* @__PURE__ */ import_react15.default.createElement(import_react15.default.Fragment, null, partialFailure && /* @__PURE__ */ import_react15.default.createElement(import_Alert7.default, { severity: "warning", sx: { mt: 1, mb: 1 } }, usageError ? `Usage data unavailable (${usageError}).` : "", keysError ? ` Key list unavailable (${keysError}).` : "", " Showing what loaded."), /* @__PURE__ */ import_react15.default.createElement(import_Grid3.default, { container: true, spacing: 2, sx: { mb: hasSparkline ? 1.5 : 0 } }, /* @__PURE__ */ import_react15.default.createElement(import_Grid3.default, { item: true, xs: 6 }, /* @__PURE__ */ import_react15.default.createElement(Kpi, { label: "USD Spent", value: fmtUsd(usage?.total_spend ?? 0) })), /* @__PURE__ */ import_react15.default.createElement(import_Grid3.default, { item: true, xs: 6 }, /* @__PURE__ */ import_react15.default.createElement(Kpi, { label: "Tokens In", value: fmtInt(usage?.prompt_tokens ?? 0) })), /* @__PURE__ */ import_react15.default.createElement(import_Grid3.default, { item: true, xs: 6 }, /* @__PURE__ */ import_react15.default.createElement(Kpi, { label: "Tokens Out", value: fmtInt(usage?.completion_tokens ?? 0) })), /* @__PURE__ */ import_react15.default.createElement(import_Grid3.default, { item: true, xs: 6 }, /* @__PURE__ */ import_react15.default.createElement(Kpi, { label: "Keys", value: fmtInt(keys.length) }))), hasSparkline && /* @__PURE__ */ import_react15.default.createElement(import_Box13.default, { height: 120 }, /* @__PURE__ */ import_react15.default.createElement(import_recharts3.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react15.default.createElement(import_recharts3.AreaChart, { data: dailyData, margin: { top: 4, right: 0, bottom: 0, left: 0 } }, /* @__PURE__ */ import_react15.default.createElement(
|
|
4697
4703
|
import_recharts3.Area,
|
|
4698
4704
|
{
|
|
4699
4705
|
type: "monotone",
|
|
@@ -4711,7 +4717,7 @@ var LiteLLMHomeWidget = ({
|
|
|
4711
4717
|
init_LiteLLMBudgetWidget();
|
|
4712
4718
|
|
|
4713
4719
|
// src/components/LiteLLMBudgetGauges.tsx
|
|
4714
|
-
var
|
|
4720
|
+
var import_react16 = __toESM(require("react"));
|
|
4715
4721
|
var import_Paper10 = __toESM(require("@mui/material/Paper"));
|
|
4716
4722
|
var import_Box14 = __toESM(require("@mui/material/Box"));
|
|
4717
4723
|
var import_Button6 = __toESM(require("@mui/material/Button"));
|
|
@@ -4720,11 +4726,13 @@ var import_CircularProgress8 = __toESM(require("@mui/material/CircularProgress")
|
|
|
4720
4726
|
var import_Alert8 = __toESM(require("@mui/material/Alert"));
|
|
4721
4727
|
var import_Collapse4 = __toESM(require("@mui/material/Collapse"));
|
|
4722
4728
|
var import_icons_material10 = require("@mui/icons-material");
|
|
4729
|
+
var import_recharts4 = require("recharts");
|
|
4723
4730
|
var import_core_plugin_api6 = require("@backstage/core-plugin-api");
|
|
4724
|
-
var
|
|
4731
|
+
var import_core_components3 = require("@backstage/core-components");
|
|
4725
4732
|
init_api();
|
|
4726
4733
|
init_format();
|
|
4727
4734
|
init_ui();
|
|
4735
|
+
init_GenerateKeyButton();
|
|
4728
4736
|
init_BudgetLimitList();
|
|
4729
4737
|
init_budget();
|
|
4730
4738
|
var MODULE_PATH = "/litellm";
|
|
@@ -4735,7 +4743,7 @@ var LEVEL = {
|
|
|
4735
4743
|
};
|
|
4736
4744
|
var DEFAULT_CTAS = ["new-key", "module", "all-limits"];
|
|
4737
4745
|
var CTA_LABELS = {
|
|
4738
|
-
"new-key": "New
|
|
4746
|
+
"new-key": "Generate New Key",
|
|
4739
4747
|
module: "Open module",
|
|
4740
4748
|
"all-limits": "All limits"
|
|
4741
4749
|
};
|
|
@@ -4761,7 +4769,7 @@ var LevelGauge = ({
|
|
|
4761
4769
|
const limit = gauge.limit;
|
|
4762
4770
|
const tone = limit ? budgetTone2(limit.pct) : "neutral";
|
|
4763
4771
|
const extra = gauge.kind === "key" ? Math.max(0, gauge.count - 1) : 0;
|
|
4764
|
-
return /* @__PURE__ */
|
|
4772
|
+
return /* @__PURE__ */ import_react16.default.createElement(
|
|
4765
4773
|
import_Box14.default,
|
|
4766
4774
|
{
|
|
4767
4775
|
sx: {
|
|
@@ -4772,7 +4780,7 @@ var LevelGauge = ({
|
|
|
4772
4780
|
flex: 1
|
|
4773
4781
|
}
|
|
4774
4782
|
},
|
|
4775
|
-
/* @__PURE__ */
|
|
4783
|
+
/* @__PURE__ */ import_react16.default.createElement(
|
|
4776
4784
|
Gauge,
|
|
4777
4785
|
{
|
|
4778
4786
|
value: limit?.pct ?? 0,
|
|
@@ -4781,7 +4789,7 @@ var LevelGauge = ({
|
|
|
4781
4789
|
label: limit ? `${Math.round(limit.pct)}%` : "\u2014"
|
|
4782
4790
|
}
|
|
4783
4791
|
),
|
|
4784
|
-
/* @__PURE__ */
|
|
4792
|
+
/* @__PURE__ */ import_react16.default.createElement(
|
|
4785
4793
|
import_Typography14.default,
|
|
4786
4794
|
{
|
|
4787
4795
|
sx: (theme) => ({
|
|
@@ -4795,7 +4803,7 @@ var LevelGauge = ({
|
|
|
4795
4803
|
},
|
|
4796
4804
|
name
|
|
4797
4805
|
),
|
|
4798
|
-
limit ? /* @__PURE__ */
|
|
4806
|
+
limit ? /* @__PURE__ */ import_react16.default.createElement(import_react16.default.Fragment, null, /* @__PURE__ */ import_react16.default.createElement(
|
|
4799
4807
|
import_Typography14.default,
|
|
4800
4808
|
{
|
|
4801
4809
|
variant: "caption",
|
|
@@ -4811,7 +4819,7 @@ var LevelGauge = ({
|
|
|
4811
4819
|
}
|
|
4812
4820
|
},
|
|
4813
4821
|
limit.label
|
|
4814
|
-
), /* @__PURE__ */
|
|
4822
|
+
), /* @__PURE__ */ import_react16.default.createElement(
|
|
4815
4823
|
import_Typography14.default,
|
|
4816
4824
|
{
|
|
4817
4825
|
variant: "caption",
|
|
@@ -4823,10 +4831,10 @@ var LevelGauge = ({
|
|
|
4823
4831
|
}
|
|
4824
4832
|
},
|
|
4825
4833
|
spendCaption(limit)
|
|
4826
|
-
), extra > 0 ? /* @__PURE__ */
|
|
4834
|
+
), extra > 0 ? /* @__PURE__ */ import_react16.default.createElement(
|
|
4827
4835
|
import_Typography14.default,
|
|
4828
4836
|
{
|
|
4829
|
-
component:
|
|
4837
|
+
component: import_core_components3.Link,
|
|
4830
4838
|
to: keysHref,
|
|
4831
4839
|
variant: "caption",
|
|
4832
4840
|
sx: {
|
|
@@ -4839,7 +4847,7 @@ var LevelGauge = ({
|
|
|
4839
4847
|
"+",
|
|
4840
4848
|
extra,
|
|
4841
4849
|
" more"
|
|
4842
|
-
) : /* @__PURE__ */
|
|
4850
|
+
) : /* @__PURE__ */ import_react16.default.createElement(import_Typography14.default, { variant: "caption", color: "text.secondary", sx: { fontSize: 10.5 } }, resetLabel(limit))) : /* @__PURE__ */ import_react16.default.createElement(
|
|
4843
4851
|
import_Typography14.default,
|
|
4844
4852
|
{
|
|
4845
4853
|
variant: "caption",
|
|
@@ -4850,6 +4858,125 @@ var LevelGauge = ({
|
|
|
4850
4858
|
)
|
|
4851
4859
|
);
|
|
4852
4860
|
};
|
|
4861
|
+
function monthToDateRange(now = /* @__PURE__ */ new Date()) {
|
|
4862
|
+
const toDay = (d) => `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(
|
|
4863
|
+
d.getDate()
|
|
4864
|
+
).padStart(2, "0")}`;
|
|
4865
|
+
const start = new Date(now.getFullYear(), now.getMonth(), 1);
|
|
4866
|
+
return { startDate: toDay(start), endDate: toDay(now) };
|
|
4867
|
+
}
|
|
4868
|
+
var UsageMiniChart = ({ data, totalTokens, loading, height }) => {
|
|
4869
|
+
const renderPlot = () => {
|
|
4870
|
+
if (loading) {
|
|
4871
|
+
return /* @__PURE__ */ import_react16.default.createElement(import_Box14.default, { display: "flex", justifyContent: "center", alignItems: "center", height: "100%" }, /* @__PURE__ */ import_react16.default.createElement(import_CircularProgress8.default, { size: 20 }));
|
|
4872
|
+
}
|
|
4873
|
+
if (data.length === 0) {
|
|
4874
|
+
return /* @__PURE__ */ import_react16.default.createElement(
|
|
4875
|
+
import_Box14.default,
|
|
4876
|
+
{
|
|
4877
|
+
display: "flex",
|
|
4878
|
+
justifyContent: "center",
|
|
4879
|
+
alignItems: "center",
|
|
4880
|
+
height: "100%",
|
|
4881
|
+
sx: (theme) => ({ color: theme.palette.text.secondary, fontSize: 18 })
|
|
4882
|
+
},
|
|
4883
|
+
"\u2014"
|
|
4884
|
+
);
|
|
4885
|
+
}
|
|
4886
|
+
return /* @__PURE__ */ import_react16.default.createElement(import_recharts4.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react16.default.createElement(import_recharts4.AreaChart, { data, margin: { top: 4, right: 0, bottom: 0, left: 0 } }, /* @__PURE__ */ import_react16.default.createElement(import_recharts4.Tooltip, { content: /* @__PURE__ */ import_react16.default.createElement(ChartTooltip, { valueFormatter: fmtInt }) }), /* @__PURE__ */ import_react16.default.createElement(
|
|
4887
|
+
import_recharts4.Area,
|
|
4888
|
+
{
|
|
4889
|
+
type: "monotone",
|
|
4890
|
+
dataKey: "input",
|
|
4891
|
+
name: "Input",
|
|
4892
|
+
stackId: "tok",
|
|
4893
|
+
stroke: SERIES.input,
|
|
4894
|
+
strokeWidth: 1.5,
|
|
4895
|
+
fill: SERIES.input,
|
|
4896
|
+
fillOpacity: 0.25,
|
|
4897
|
+
dot: false,
|
|
4898
|
+
isAnimationActive: false
|
|
4899
|
+
}
|
|
4900
|
+
), /* @__PURE__ */ import_react16.default.createElement(
|
|
4901
|
+
import_recharts4.Area,
|
|
4902
|
+
{
|
|
4903
|
+
type: "monotone",
|
|
4904
|
+
dataKey: "output",
|
|
4905
|
+
name: "Output",
|
|
4906
|
+
stackId: "tok",
|
|
4907
|
+
stroke: SERIES.output,
|
|
4908
|
+
strokeWidth: 1.5,
|
|
4909
|
+
fill: SERIES.output,
|
|
4910
|
+
fillOpacity: 0.25,
|
|
4911
|
+
dot: false,
|
|
4912
|
+
isAnimationActive: false
|
|
4913
|
+
}
|
|
4914
|
+
)));
|
|
4915
|
+
};
|
|
4916
|
+
const renderCaption = () => {
|
|
4917
|
+
if (loading) {
|
|
4918
|
+
return /* @__PURE__ */ import_react16.default.createElement(import_Typography14.default, { variant: "caption", color: "text.secondary", sx: { mt: 0.25, fontSize: 11 } }, "\u2026");
|
|
4919
|
+
}
|
|
4920
|
+
if (data.length === 0) {
|
|
4921
|
+
return /* @__PURE__ */ import_react16.default.createElement(
|
|
4922
|
+
import_Typography14.default,
|
|
4923
|
+
{
|
|
4924
|
+
variant: "caption",
|
|
4925
|
+
color: "text.secondary",
|
|
4926
|
+
sx: { mt: 0.25, fontSize: 10.5, textAlign: "center" }
|
|
4927
|
+
},
|
|
4928
|
+
"No usage this month"
|
|
4929
|
+
);
|
|
4930
|
+
}
|
|
4931
|
+
return /* @__PURE__ */ import_react16.default.createElement(import_react16.default.Fragment, null, /* @__PURE__ */ import_react16.default.createElement(
|
|
4932
|
+
import_Typography14.default,
|
|
4933
|
+
{
|
|
4934
|
+
variant: "caption",
|
|
4935
|
+
title: `${fmtInt(totalTokens)} tokens month to date`,
|
|
4936
|
+
sx: {
|
|
4937
|
+
mt: 0.25,
|
|
4938
|
+
fontSize: 11,
|
|
4939
|
+
fontWeight: 600,
|
|
4940
|
+
maxWidth: "100%",
|
|
4941
|
+
overflow: "hidden",
|
|
4942
|
+
textOverflow: "ellipsis",
|
|
4943
|
+
whiteSpace: "nowrap",
|
|
4944
|
+
fontVariantNumeric: "tabular-nums"
|
|
4945
|
+
}
|
|
4946
|
+
},
|
|
4947
|
+
fmtCompact(totalTokens),
|
|
4948
|
+
" tokens"
|
|
4949
|
+
), /* @__PURE__ */ import_react16.default.createElement(import_Typography14.default, { variant: "caption", color: "text.secondary", sx: { fontSize: 10.5 } }, "month to date"));
|
|
4950
|
+
};
|
|
4951
|
+
return /* @__PURE__ */ import_react16.default.createElement(
|
|
4952
|
+
import_Box14.default,
|
|
4953
|
+
{
|
|
4954
|
+
sx: {
|
|
4955
|
+
display: "flex",
|
|
4956
|
+
flexDirection: "column",
|
|
4957
|
+
alignItems: "center",
|
|
4958
|
+
minWidth: 0,
|
|
4959
|
+
flex: 1
|
|
4960
|
+
}
|
|
4961
|
+
},
|
|
4962
|
+
/* @__PURE__ */ import_react16.default.createElement(import_Box14.default, { sx: { height, width: "100%" } }, renderPlot()),
|
|
4963
|
+
/* @__PURE__ */ import_react16.default.createElement(
|
|
4964
|
+
import_Typography14.default,
|
|
4965
|
+
{
|
|
4966
|
+
sx: (theme) => ({
|
|
4967
|
+
mt: 0.75,
|
|
4968
|
+
fontSize: 10.5,
|
|
4969
|
+
fontWeight: 700,
|
|
4970
|
+
letterSpacing: "0.08em",
|
|
4971
|
+
textTransform: "uppercase",
|
|
4972
|
+
color: theme.palette.text.secondary
|
|
4973
|
+
})
|
|
4974
|
+
},
|
|
4975
|
+
"Usage"
|
|
4976
|
+
),
|
|
4977
|
+
renderCaption()
|
|
4978
|
+
);
|
|
4979
|
+
};
|
|
4853
4980
|
var LiteLLMBudgetGauges = ({
|
|
4854
4981
|
title = "Budget",
|
|
4855
4982
|
size = 72,
|
|
@@ -4864,60 +4991,83 @@ var LiteLLMBudgetGauges = ({
|
|
|
4864
4991
|
action
|
|
4865
4992
|
}) => {
|
|
4866
4993
|
const api = (0, import_core_plugin_api6.useApi)(liteLlmApiRef);
|
|
4867
|
-
const [loading, setLoading] = (0,
|
|
4868
|
-
const [error, setError] = (0,
|
|
4869
|
-
const [user, setUser] = (0,
|
|
4870
|
-
const [teams, setTeams] = (0,
|
|
4871
|
-
const [keys, setKeys] = (0,
|
|
4994
|
+
const [loading, setLoading] = (0, import_react16.useState)(true);
|
|
4995
|
+
const [error, setError] = (0, import_react16.useState)(null);
|
|
4996
|
+
const [user, setUser] = (0, import_react16.useState)(null);
|
|
4997
|
+
const [teams, setTeams] = (0, import_react16.useState)([]);
|
|
4998
|
+
const [keys, setKeys] = (0, import_react16.useState)([]);
|
|
4999
|
+
const [usageLoading, setUsageLoading] = (0, import_react16.useState)(true);
|
|
5000
|
+
const [dailyTokens, setDailyTokens] = (0, import_react16.useState)([]);
|
|
5001
|
+
const [mtdTokens, setMtdTokens] = (0, import_react16.useState)(0);
|
|
4872
5002
|
const isControlled = expanded !== void 0;
|
|
4873
|
-
const [uncontrolledExpanded, setUncontrolledExpanded] = (0,
|
|
5003
|
+
const [uncontrolledExpanded, setUncontrolledExpanded] = (0, import_react16.useState)(defaultExpanded);
|
|
4874
5004
|
const isExpanded = isControlled ? expanded : uncontrolledExpanded;
|
|
4875
|
-
const toggleExpanded = (0,
|
|
5005
|
+
const toggleExpanded = (0, import_react16.useCallback)(() => {
|
|
4876
5006
|
const next = !isExpanded;
|
|
4877
5007
|
if (!isControlled) setUncontrolledExpanded(next);
|
|
4878
5008
|
onExpandedChange?.(next);
|
|
4879
5009
|
}, [isControlled, isExpanded, onExpandedChange]);
|
|
4880
5010
|
const keysTabHref = keysHref ?? `${moduleHref}?tab=keys`;
|
|
4881
|
-
(0,
|
|
5011
|
+
(0, import_react16.useEffect)(() => {
|
|
4882
5012
|
let cancelled = false;
|
|
4883
5013
|
setLoading(true);
|
|
5014
|
+
setUsageLoading(true);
|
|
4884
5015
|
setError(null);
|
|
4885
|
-
|
|
4886
|
-
|
|
4887
|
-
|
|
4888
|
-
|
|
4889
|
-
|
|
4890
|
-
|
|
4891
|
-
|
|
4892
|
-
|
|
4893
|
-
|
|
4894
|
-
|
|
4895
|
-
|
|
4896
|
-
|
|
4897
|
-
|
|
4898
|
-
|
|
5016
|
+
const { startDate, endDate } = monthToDateRange();
|
|
5017
|
+
Promise.allSettled([
|
|
5018
|
+
api.getUserInfo(),
|
|
5019
|
+
api.getTeams(),
|
|
5020
|
+
api.listKeys(),
|
|
5021
|
+
api.getUsage(startDate, endDate)
|
|
5022
|
+
]).then(([userResult, teamsResult, keysResult, usageResult]) => {
|
|
5023
|
+
if (cancelled) return;
|
|
5024
|
+
if (userResult.status === "fulfilled") {
|
|
5025
|
+
setUser(userResult.value);
|
|
5026
|
+
} else {
|
|
5027
|
+
setUser(null);
|
|
5028
|
+
setError(
|
|
5029
|
+
userResult.reason?.message ?? "Failed to load your LiteLLM profile"
|
|
5030
|
+
);
|
|
4899
5031
|
}
|
|
4900
|
-
|
|
5032
|
+
setTeams(teamsResult.status === "fulfilled" ? teamsResult.value : []);
|
|
5033
|
+
setKeys(keysResult.status === "fulfilled" ? keysResult.value : []);
|
|
5034
|
+
setLoading(false);
|
|
5035
|
+
if (usageResult.status === "fulfilled") {
|
|
5036
|
+
const daily = usageResult.value.daily_usage ?? [];
|
|
5037
|
+
setDailyTokens(
|
|
5038
|
+
daily.map((d) => ({
|
|
5039
|
+
date: d.date,
|
|
5040
|
+
input: d.prompt_tokens,
|
|
5041
|
+
output: d.completion_tokens
|
|
5042
|
+
}))
|
|
5043
|
+
);
|
|
5044
|
+
setMtdTokens(usageResult.value.total_tokens ?? 0);
|
|
5045
|
+
} else {
|
|
5046
|
+
setDailyTokens([]);
|
|
5047
|
+
setMtdTokens(0);
|
|
5048
|
+
}
|
|
5049
|
+
setUsageLoading(false);
|
|
5050
|
+
});
|
|
4901
5051
|
return () => {
|
|
4902
5052
|
cancelled = true;
|
|
4903
5053
|
};
|
|
4904
5054
|
}, [api]);
|
|
4905
|
-
const summary = (0,
|
|
5055
|
+
const summary = (0, import_react16.useMemo)(
|
|
4906
5056
|
() => buildBudgetSummary(user, teams, keys, keys.length),
|
|
4907
5057
|
[user, teams, keys]
|
|
4908
5058
|
);
|
|
4909
|
-
const gauges = (0,
|
|
4910
|
-
const headline = (0,
|
|
4911
|
-
const expandedSummary = (0,
|
|
5059
|
+
const gauges = (0, import_react16.useMemo)(() => buildBudgetGauges(summary), [summary]);
|
|
5060
|
+
const headline = (0, import_react16.useMemo)(() => budgetHeadline(summary), [summary]);
|
|
5061
|
+
const expandedSummary = (0, import_react16.useMemo)(
|
|
4912
5062
|
() => buildBudgetSummary(user, teams, keys, maxExpandedKeys),
|
|
4913
5063
|
[user, teams, keys, maxExpandedKeys]
|
|
4914
5064
|
);
|
|
4915
|
-
const expandedLimits = (0,
|
|
5065
|
+
const expandedLimits = (0, import_react16.useMemo)(
|
|
4916
5066
|
() => allBudgetLimits(expandedSummary),
|
|
4917
5067
|
[expandedSummary]
|
|
4918
5068
|
);
|
|
4919
5069
|
const summaryText = headline.count === 0 ? "no limits apply" : `${headline.count} limit${headline.count === 1 ? "" : "s"}`;
|
|
4920
|
-
const resolvedCtas = (0,
|
|
5070
|
+
const resolvedCtas = (0, import_react16.useMemo)(() => {
|
|
4921
5071
|
const list = ctas ?? DEFAULT_CTAS;
|
|
4922
5072
|
return list.map((cta) => typeof cta === "string" ? { kind: cta } : cta).filter((cta) => cta.kind !== "all-limits" || headline.count > 0);
|
|
4923
5073
|
}, [ctas, headline.count]);
|
|
@@ -4926,28 +5076,18 @@ var LiteLLMBudgetGauges = ({
|
|
|
4926
5076
|
const key = `${cta.kind}-${index}`;
|
|
4927
5077
|
switch (cta.kind) {
|
|
4928
5078
|
case "new-key":
|
|
4929
|
-
return onCreateKey ? /* @__PURE__ */
|
|
4930
|
-
import_Button6.default,
|
|
4931
|
-
{
|
|
4932
|
-
key,
|
|
4933
|
-
size: "small",
|
|
4934
|
-
variant: "contained",
|
|
4935
|
-
component: import_core_components2.Link,
|
|
4936
|
-
to: `${moduleHref}?generate=1`
|
|
4937
|
-
},
|
|
4938
|
-
label
|
|
4939
|
-
);
|
|
5079
|
+
return onCreateKey ? /* @__PURE__ */ import_react16.default.createElement(GenerateKeyButton, { key, size: "small", label, onClick: onCreateKey }) : /* @__PURE__ */ import_react16.default.createElement(GenerateKeyButton, { key, size: "small", label, to: `${moduleHref}?generate=1` });
|
|
4940
5080
|
case "module":
|
|
4941
|
-
return /* @__PURE__ */
|
|
5081
|
+
return /* @__PURE__ */ import_react16.default.createElement(import_Button6.default, { key, size: "small", variant: "outlined", component: import_core_components3.Link, to: moduleHref }, label);
|
|
4942
5082
|
case "all-limits":
|
|
4943
|
-
return /* @__PURE__ */
|
|
5083
|
+
return /* @__PURE__ */ import_react16.default.createElement(
|
|
4944
5084
|
import_Button6.default,
|
|
4945
5085
|
{
|
|
4946
5086
|
key,
|
|
4947
5087
|
size: "small",
|
|
4948
5088
|
variant: "text",
|
|
4949
5089
|
onClick: toggleExpanded,
|
|
4950
|
-
endIcon: /* @__PURE__ */
|
|
5090
|
+
endIcon: /* @__PURE__ */ import_react16.default.createElement(
|
|
4951
5091
|
import_icons_material10.ExpandMore,
|
|
4952
5092
|
{
|
|
4953
5093
|
sx: {
|
|
@@ -4965,14 +5105,22 @@ var LiteLLMBudgetGauges = ({
|
|
|
4965
5105
|
}
|
|
4966
5106
|
};
|
|
4967
5107
|
const hasFooter = resolvedCtas.length > 0 || !!action;
|
|
4968
|
-
return /* @__PURE__ */
|
|
5108
|
+
return /* @__PURE__ */ import_react16.default.createElement(import_Paper10.default, { sx: { p: 2 } }, /* @__PURE__ */ import_react16.default.createElement(import_Box14.default, { sx: { display: "flex", alignItems: "center", gap: 1, mb: 1.5, minWidth: 0 } }, /* @__PURE__ */ import_react16.default.createElement(import_Typography14.default, { variant: "h6", lineHeight: 1.2, sx: { minWidth: 0, flex: 1 } }, title), !loading && !error && /* @__PURE__ */ import_react16.default.createElement(
|
|
4969
5109
|
StatusPill,
|
|
4970
5110
|
{
|
|
4971
5111
|
label: summaryText,
|
|
4972
5112
|
tone: budgetTone2(headline.closest?.pct ?? 0),
|
|
4973
5113
|
dot: headline.count > 0
|
|
4974
5114
|
}
|
|
4975
|
-
)), loading && /* @__PURE__ */
|
|
5115
|
+
)), loading && /* @__PURE__ */ import_react16.default.createElement(import_Box14.default, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: 110 }, /* @__PURE__ */ import_react16.default.createElement(import_CircularProgress8.default, { size: 28 })), !loading && error && /* @__PURE__ */ import_react16.default.createElement(import_Alert8.default, { severity: "error", sx: { mt: 0.5 } }, error), !loading && !error && /* @__PURE__ */ import_react16.default.createElement(import_react16.default.Fragment, null, /* @__PURE__ */ import_react16.default.createElement(import_Box14.default, { sx: { display: "flex", alignItems: "flex-start", gap: 1 } }, gauges.map((gauge) => /* @__PURE__ */ import_react16.default.createElement(LevelGauge, { key: gauge.kind, gauge, size, keysHref: keysTabHref })), /* @__PURE__ */ import_react16.default.createElement(
|
|
5116
|
+
UsageMiniChart,
|
|
5117
|
+
{
|
|
5118
|
+
data: dailyTokens,
|
|
5119
|
+
totalTokens: mtdTokens,
|
|
5120
|
+
loading: usageLoading,
|
|
5121
|
+
height: size
|
|
5122
|
+
}
|
|
5123
|
+
)), /* @__PURE__ */ import_react16.default.createElement(import_Collapse4.default, { in: isExpanded, unmountOnExit: true }, /* @__PURE__ */ import_react16.default.createElement(
|
|
4976
5124
|
import_Box14.default,
|
|
4977
5125
|
{
|
|
4978
5126
|
sx: {
|
|
@@ -4983,7 +5131,7 @@ var LiteLLMBudgetGauges = ({
|
|
|
4983
5131
|
overflowY: "auto"
|
|
4984
5132
|
}
|
|
4985
5133
|
},
|
|
4986
|
-
/* @__PURE__ */
|
|
5134
|
+
/* @__PURE__ */ import_react16.default.createElement(LimitListPanel, null, /* @__PURE__ */ import_react16.default.createElement(
|
|
4987
5135
|
BudgetLimitList,
|
|
4988
5136
|
{
|
|
4989
5137
|
limits: expandedLimits,
|
|
@@ -4991,7 +5139,7 @@ var LiteLLMBudgetGauges = ({
|
|
|
4991
5139
|
keysHref: keysTabHref
|
|
4992
5140
|
}
|
|
4993
5141
|
))
|
|
4994
|
-
))), hasFooter && /* @__PURE__ */
|
|
5142
|
+
))), hasFooter && /* @__PURE__ */ import_react16.default.createElement(
|
|
4995
5143
|
import_Box14.default,
|
|
4996
5144
|
{
|
|
4997
5145
|
sx: (theme) => ({
|
|
@@ -5010,7 +5158,31 @@ var LiteLLMBudgetGauges = ({
|
|
|
5010
5158
|
};
|
|
5011
5159
|
|
|
5012
5160
|
// src/index.ts
|
|
5013
|
-
|
|
5161
|
+
init_KeyFormDialog();
|
|
5162
|
+
init_GenerateKeyButton();
|
|
5163
|
+
|
|
5164
|
+
// src/components/GenerateKeyDialog.tsx
|
|
5165
|
+
var import_react17 = __toESM(require("react"));
|
|
5166
|
+
init_KeyFormDialog();
|
|
5167
|
+
var GenerateKeyDialog = (props) => /* @__PURE__ */ import_react17.default.createElement(
|
|
5168
|
+
KeyFormDialog,
|
|
5169
|
+
{
|
|
5170
|
+
open: props.open,
|
|
5171
|
+
onClose: props.onClose,
|
|
5172
|
+
mode: "create",
|
|
5173
|
+
keys: props.keys,
|
|
5174
|
+
models: props.models,
|
|
5175
|
+
teams: props.teams,
|
|
5176
|
+
username: props.username,
|
|
5177
|
+
keyGenerationSettings: props.keyGenerationSettings,
|
|
5178
|
+
onCreateKey: props.onGenerateKey,
|
|
5179
|
+
onUpdateKey: async () => {
|
|
5180
|
+
},
|
|
5181
|
+
onGetConfig: props.onGetConfig
|
|
5182
|
+
}
|
|
5183
|
+
);
|
|
5184
|
+
|
|
5185
|
+
// src/index.ts
|
|
5014
5186
|
init_ManageTeamDialog();
|
|
5015
5187
|
init_api();
|
|
5016
5188
|
init_permissions();
|