@acarmisc/backstage-plugin-litellm 0.24.0 → 0.26.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/SearchInput.d.ts +1 -0
- package/dist/budget.d.ts +8 -0
- package/dist/components/BudgetLimitList.d.ts +41 -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 +34 -2
- package/dist/components/SearchInput.d.ts +9 -0
- package/dist/index.cjs.js +708 -548
- package/dist/index.cjs.js.map +4 -4
- package/dist/index.d.ts +3 -1
- package/dist/index.esm.js +649 -489
- 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
|
@@ -1051,26 +1051,6 @@ function BudgetCell({ spend, maxBudget }) {
|
|
|
1051
1051
|
const pct = Math.min(100, spend / maxBudget * 100);
|
|
1052
1052
|
return /* @__PURE__ */ import_react3.default.createElement(import_Box3.default, { minWidth: 104 }, /* @__PURE__ */ import_react3.default.createElement(import_Typography3.default, { variant: "body2", sx: { fontVariantNumeric: "tabular-nums", mb: 0.5 } }, "$", spend.toFixed(2), /* @__PURE__ */ import_react3.default.createElement(import_Typography3.default, { component: "span", variant: "caption", color: "text.secondary", sx: { ml: 0.5 } }, "/ $", maxBudget)), /* @__PURE__ */ import_react3.default.createElement(Meter, { value: pct, tone: budgetTone(pct), height: 4 }));
|
|
1053
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;
|
|
1073
|
-
}
|
|
1074
1054
|
function compareKeys(a, b, sortKey) {
|
|
1075
1055
|
switch (sortKey) {
|
|
1076
1056
|
case "alias":
|
|
@@ -1089,7 +1069,7 @@ function compareKeys(a, b, sortKey) {
|
|
|
1089
1069
|
return 0;
|
|
1090
1070
|
}
|
|
1091
1071
|
}
|
|
1092
|
-
var import_react3, 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,
|
|
1072
|
+
var import_react3, 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
1073
|
var init_KeysTable = __esm({
|
|
1094
1074
|
"src/components/KeysTable.tsx"() {
|
|
1095
1075
|
"use strict";
|
|
@@ -1111,17 +1091,12 @@ var init_KeysTable = __esm({
|
|
|
1111
1091
|
import_DialogActions = __toESM(require("@mui/material/DialogActions"));
|
|
1112
1092
|
import_TextField = __toESM(require("@mui/material/TextField"));
|
|
1113
1093
|
import_CircularProgress = __toESM(require("@mui/material/CircularProgress"));
|
|
1114
|
-
import_Autocomplete = __toESM(require("@mui/material/Autocomplete"));
|
|
1115
1094
|
import_Skeleton2 = __toESM(require("@mui/material/Skeleton"));
|
|
1116
1095
|
import_InputAdornment = __toESM(require("@mui/material/InputAdornment"));
|
|
1117
1096
|
import_styles3 = require("@mui/material/styles");
|
|
1118
1097
|
import_icons_material2 = require("@mui/icons-material");
|
|
1119
1098
|
init_api();
|
|
1120
1099
|
init_ui();
|
|
1121
|
-
maskKey = (key) => {
|
|
1122
|
-
if (key.length <= 8) return "***";
|
|
1123
|
-
return `${key.slice(0, 4)}...${key.slice(-4)}`;
|
|
1124
|
-
};
|
|
1125
1100
|
shortKeyId = (token) => {
|
|
1126
1101
|
if (!token) return "-";
|
|
1127
1102
|
if (token.length <= 16) return token;
|
|
@@ -1134,33 +1109,19 @@ var init_KeysTable = __esm({
|
|
|
1134
1109
|
return dateStr;
|
|
1135
1110
|
}
|
|
1136
1111
|
};
|
|
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
1112
|
KeysTable = ({
|
|
1145
1113
|
keys,
|
|
1146
|
-
models,
|
|
1147
1114
|
loading,
|
|
1148
1115
|
onGenerateKeyClick,
|
|
1149
|
-
|
|
1116
|
+
onEditKey,
|
|
1150
1117
|
onBlockKey,
|
|
1151
1118
|
onUnblockKey,
|
|
1152
|
-
onResetKeySpend,
|
|
1153
1119
|
onDeleteKey,
|
|
1154
1120
|
onPruneExpiredKeys
|
|
1155
1121
|
}) => {
|
|
1156
|
-
const [editingKey, setEditingKey] = (0, import_react3.useState)(null);
|
|
1157
|
-
const [editForm, setEditForm] = (0, import_react3.useState)({});
|
|
1158
|
-
const [editSubmitting, setEditSubmitting] = (0, import_react3.useState)(false);
|
|
1159
1122
|
const [blockingKeyId, setBlockingKeyId] = (0, import_react3.useState)(null);
|
|
1160
1123
|
const [deleteConfirmId, setDeleteConfirmId] = (0, import_react3.useState)(null);
|
|
1161
1124
|
const [deleteSubmitting, setDeleteSubmitting] = (0, import_react3.useState)(false);
|
|
1162
|
-
const [resetSpendConfirm, setResetSpendConfirm] = (0, import_react3.useState)(false);
|
|
1163
|
-
const [resetSpendSubmitting, setResetSpendSubmitting] = (0, import_react3.useState)(false);
|
|
1164
1125
|
const [filterText, setFilterText] = (0, import_react3.useState)("");
|
|
1165
1126
|
const [sortKey, setSortKey] = (0, import_react3.useState)("created");
|
|
1166
1127
|
const [sortDirection, setSortDirection] = (0, import_react3.useState)("desc");
|
|
@@ -1185,27 +1146,8 @@ var init_KeysTable = __esm({
|
|
|
1185
1146
|
return sortDirection === "asc" ? comparison : -comparison;
|
|
1186
1147
|
});
|
|
1187
1148
|
}, [keys, filterText, sortKey, sortDirection]);
|
|
1188
|
-
const editSelectedModels = models.filter((m) => (editForm.models || []).includes(m.model_name));
|
|
1189
1149
|
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
|
-
}
|
|
1150
|
+
onEditKey(k);
|
|
1209
1151
|
};
|
|
1210
1152
|
const handleToggleBlock = async (key) => {
|
|
1211
1153
|
const keyId = key.token ?? key.key;
|
|
@@ -1220,17 +1162,6 @@ var init_KeysTable = __esm({
|
|
|
1220
1162
|
setBlockingKeyId(null);
|
|
1221
1163
|
}
|
|
1222
1164
|
};
|
|
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
1165
|
const handleConfirmDelete = async () => {
|
|
1235
1166
|
if (!deleteConfirmId) return;
|
|
1236
1167
|
setDeleteSubmitting(true);
|
|
@@ -1261,12 +1192,6 @@ var init_KeysTable = __esm({
|
|
|
1261
1192
|
setPruneConfirmCount(null);
|
|
1262
1193
|
}
|
|
1263
1194
|
};
|
|
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
1195
|
const renderTableBody = () => {
|
|
1271
1196
|
if (loading) {
|
|
1272
1197
|
return /* @__PURE__ */ import_react3.default.createElement(import_TableRow.default, null, /* @__PURE__ */ import_react3.default.createElement(import_TableCell.default, { colSpan: 8, sx: { py: 2 } }, /* @__PURE__ */ import_react3.default.createElement(import_Skeleton2.default, { variant: "rounded", height: 140 })));
|
|
@@ -1438,75 +1363,7 @@ var init_KeysTable = __esm({
|
|
|
1438
1363
|
))
|
|
1439
1364
|
},
|
|
1440
1365
|
/* @__PURE__ */ import_react3.default.createElement(import_TableContainer.default, null, /* @__PURE__ */ import_react3.default.createElement(import_Table.default, { size: "small", sx: dataTableSx }, /* @__PURE__ */ import_react3.default.createElement(import_TableHead.default, null, /* @__PURE__ */ import_react3.default.createElement(import_TableRow.default, null, /* @__PURE__ */ import_react3.default.createElement(import_TableCell.default, { sortDirection: sortKey === "alias" ? sortDirection : false }, /* @__PURE__ */ import_react3.default.createElement(import_TableSortLabel.default, { active: sortKey === "alias", direction: sortKey === "alias" ? sortDirection : "asc", onClick: () => handleSort("alias") }, "Alias")), /* @__PURE__ */ import_react3.default.createElement(import_TableCell.default, null, "Key ID"), /* @__PURE__ */ import_react3.default.createElement(import_TableCell.default, { sortDirection: sortKey === "created" ? sortDirection : false }, /* @__PURE__ */ import_react3.default.createElement(import_TableSortLabel.default, { active: sortKey === "created", direction: sortKey === "created" ? sortDirection : "asc", onClick: () => handleSort("created") }, "Created")), /* @__PURE__ */ import_react3.default.createElement(import_TableCell.default, { sortDirection: sortKey === "expires" ? sortDirection : false }, /* @__PURE__ */ import_react3.default.createElement(import_TableSortLabel.default, { active: sortKey === "expires", direction: sortKey === "expires" ? sortDirection : "asc", onClick: () => handleSort("expires") }, "Expires")), /* @__PURE__ */ import_react3.default.createElement(import_TableCell.default, { sortDirection: sortKey === "budget" ? sortDirection : false }, /* @__PURE__ */ import_react3.default.createElement(import_TableSortLabel.default, { active: sortKey === "budget", direction: sortKey === "budget" ? sortDirection : "asc", onClick: () => handleSort("budget") }, "Budget")), /* @__PURE__ */ import_react3.default.createElement(import_TableCell.default, { sortDirection: sortKey === "limits" ? sortDirection : false }, /* @__PURE__ */ import_react3.default.createElement(import_TableSortLabel.default, { active: sortKey === "limits", direction: sortKey === "limits" ? sortDirection : "asc", onClick: () => handleSort("limits") }, "TPM / RPM")), /* @__PURE__ */ import_react3.default.createElement(import_TableCell.default, { sortDirection: sortKey === "models" ? sortDirection : false }, /* @__PURE__ */ import_react3.default.createElement(import_TableSortLabel.default, { active: sortKey === "models", direction: sortKey === "models" ? sortDirection : "asc", onClick: () => handleSort("models") }, "Models")), /* @__PURE__ */ import_react3.default.createElement(import_TableCell.default, { align: "right" }, "Actions"))), /* @__PURE__ */ import_react3.default.createElement(import_TableBody.default, null, renderTableBody())))
|
|
1441
|
-
), /* @__PURE__ */ import_react3.default.createElement(import_Dialog.default, { open: !!
|
|
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(
|
|
1366
|
+
), /* @__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(
|
|
1510
1367
|
import_Button2.default,
|
|
1511
1368
|
{
|
|
1512
1369
|
onClick: handleConfirmDelete,
|
|
@@ -1543,7 +1400,7 @@ var init_format = __esm({
|
|
|
1543
1400
|
}
|
|
1544
1401
|
});
|
|
1545
1402
|
|
|
1546
|
-
// src/components/
|
|
1403
|
+
// src/components/KeyFormDialog.tsx
|
|
1547
1404
|
function trimSlash(url) {
|
|
1548
1405
|
return url.replace(/\/+$/, "");
|
|
1549
1406
|
}
|
|
@@ -1636,17 +1493,17 @@ function teamHelperText(teamError, teamRequired) {
|
|
|
1636
1493
|
if (teamRequired) return "Bind this key to a team for scoped access";
|
|
1637
1494
|
return "Optional: bind this key to a specific team for scoped access";
|
|
1638
1495
|
}
|
|
1639
|
-
function budgetHelperText(budgetInvalid, budgetEstimate) {
|
|
1640
|
-
if (budgetInvalid) return 'Enter a positive budget or tick "Unlimited"';
|
|
1496
|
+
function budgetHelperText(budgetInvalid, budgetEstimate, unlimited) {
|
|
1497
|
+
if (budgetInvalid && !unlimited) return 'Enter a positive budget or tick "Unlimited"';
|
|
1641
1498
|
if (budgetEstimate !== null) return `\u2248 ${fmtInt(budgetEstimate)} tokens at the selected model's rate`;
|
|
1642
1499
|
return void 0;
|
|
1643
1500
|
}
|
|
1644
|
-
function
|
|
1501
|
+
function fmtCost(perToken) {
|
|
1645
1502
|
if (!perToken) return null;
|
|
1646
1503
|
const per1k = perToken * 1e3;
|
|
1647
1504
|
return per1k < 0.01 ? `$${(perToken * 1e6).toFixed(2)}/M` : `$${per1k.toFixed(3)}/1K`;
|
|
1648
1505
|
}
|
|
1649
|
-
function
|
|
1506
|
+
function formatContextWindow(maxInput, maxOutput) {
|
|
1650
1507
|
if (!maxInput && !maxOutput) return null;
|
|
1651
1508
|
const fmt = (n) => {
|
|
1652
1509
|
if (!n) return null;
|
|
@@ -1661,9 +1518,9 @@ function formatContextWindow2(maxInput, maxOutput) {
|
|
|
1661
1518
|
if (outPart) return `ctx ${outPart} out`;
|
|
1662
1519
|
return null;
|
|
1663
1520
|
}
|
|
1664
|
-
var import_react4, import_Box4, import_Typography4, import_Dialog2, import_DialogTitle2, import_DialogContent2, import_DialogActions2, import_TextField2, import_MenuItem, import_Button3, import_IconButton2, import_CircularProgress2,
|
|
1665
|
-
var
|
|
1666
|
-
"src/components/
|
|
1521
|
+
var import_react4, 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;
|
|
1522
|
+
var init_KeyFormDialog = __esm({
|
|
1523
|
+
"src/components/KeyFormDialog.tsx"() {
|
|
1667
1524
|
"use strict";
|
|
1668
1525
|
import_react4 = __toESM(require("react"));
|
|
1669
1526
|
import_Box4 = __toESM(require("@mui/material/Box"));
|
|
@@ -1677,7 +1534,7 @@ var init_GenerateKeyDialog = __esm({
|
|
|
1677
1534
|
import_Button3 = __toESM(require("@mui/material/Button"));
|
|
1678
1535
|
import_IconButton2 = __toESM(require("@mui/material/IconButton"));
|
|
1679
1536
|
import_CircularProgress2 = __toESM(require("@mui/material/CircularProgress"));
|
|
1680
|
-
|
|
1537
|
+
import_Autocomplete = __toESM(require("@mui/material/Autocomplete"));
|
|
1681
1538
|
import_Checkbox = __toESM(require("@mui/material/Checkbox"));
|
|
1682
1539
|
import_FormControlLabel = __toESM(require("@mui/material/FormControlLabel"));
|
|
1683
1540
|
import_Alert = __toESM(require("@mui/material/Alert"));
|
|
@@ -1693,8 +1550,8 @@ var init_GenerateKeyDialog = __esm({
|
|
|
1693
1550
|
const hash = Math.random().toString(36).slice(2, 8);
|
|
1694
1551
|
return `${base}-${hash}`;
|
|
1695
1552
|
};
|
|
1696
|
-
|
|
1697
|
-
alias:
|
|
1553
|
+
createForm = (username) => ({
|
|
1554
|
+
alias: generateDefaultAlias(username),
|
|
1698
1555
|
models: [],
|
|
1699
1556
|
duration: "30d",
|
|
1700
1557
|
max_budget: 100,
|
|
@@ -1703,6 +1560,13 @@ var init_GenerateKeyDialog = __esm({
|
|
|
1703
1560
|
team_id: void 0,
|
|
1704
1561
|
key_type: "llm_api"
|
|
1705
1562
|
});
|
|
1563
|
+
editForm = (k) => ({
|
|
1564
|
+
key_alias: k.key_alias ?? "",
|
|
1565
|
+
models: k.models ?? [],
|
|
1566
|
+
max_budget: k.max_budget,
|
|
1567
|
+
tpm_limit: k.tpm_limit,
|
|
1568
|
+
rpm_limit: k.rpm_limit
|
|
1569
|
+
});
|
|
1706
1570
|
ALL_PROXY_MODELS = "all-proxy-models";
|
|
1707
1571
|
SNIPPET_FILE_HINTS = {
|
|
1708
1572
|
opencode: "Add to ~/.config/opencode/opencode.json",
|
|
@@ -1749,46 +1613,79 @@ var init_GenerateKeyDialog = __esm({
|
|
|
1749
1613
|
model && /* @__PURE__ */ import_react4.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
1614
|
));
|
|
1751
1615
|
};
|
|
1752
|
-
|
|
1616
|
+
KeyFormDialog = ({
|
|
1753
1617
|
open,
|
|
1754
1618
|
onClose,
|
|
1619
|
+
mode,
|
|
1620
|
+
keyToEdit = null,
|
|
1755
1621
|
keys,
|
|
1756
1622
|
models,
|
|
1757
1623
|
teams,
|
|
1758
1624
|
username,
|
|
1759
1625
|
keyGenerationSettings,
|
|
1760
|
-
|
|
1626
|
+
onCreateKey,
|
|
1627
|
+
onUpdateKey,
|
|
1628
|
+
onResetKeySpend,
|
|
1761
1629
|
onGetConfig
|
|
1762
1630
|
}) => {
|
|
1763
|
-
const
|
|
1631
|
+
const isCreate = mode === "create";
|
|
1632
|
+
const [generateForm, setGenerateForm] = (0, import_react4.useState)(createForm(username));
|
|
1633
|
+
const [editFormState, setEditFormState] = (0, import_react4.useState)({});
|
|
1764
1634
|
const [unlimitedBudget, setUnlimitedBudget] = (0, import_react4.useState)(false);
|
|
1765
1635
|
const [submitting, setSubmitting] = (0, import_react4.useState)(false);
|
|
1766
1636
|
const [newKeyValue, setNewKeyValue] = (0, import_react4.useState)(null);
|
|
1767
1637
|
const [newKeySnippets, setNewKeySnippets] = (0, import_react4.useState)(null);
|
|
1768
1638
|
const [newKeyModel, setNewKeyModel] = (0, import_react4.useState)("");
|
|
1769
1639
|
const [generateError, setGenerateError] = (0, import_react4.useState)(null);
|
|
1640
|
+
const [editError, setEditError] = (0, import_react4.useState)(null);
|
|
1641
|
+
const [resetSpendConfirm, setResetSpendConfirm] = (0, import_react4.useState)(false);
|
|
1642
|
+
const [resetSpendSubmitting, setResetSpendSubmitting] = (0, import_react4.useState)(false);
|
|
1770
1643
|
(0, import_react4.useEffect)(() => {
|
|
1771
1644
|
if (open) {
|
|
1772
|
-
|
|
1773
|
-
|
|
1645
|
+
if (isCreate) {
|
|
1646
|
+
setGenerateForm(createForm(username));
|
|
1647
|
+
} else if (keyToEdit) {
|
|
1648
|
+
setEditFormState(editForm(keyToEdit));
|
|
1649
|
+
setUnlimitedBudget(
|
|
1650
|
+
keyGenerationSettings?.allowUnlimitedBudget === true && (keyToEdit.max_budget === null || keyToEdit.max_budget === void 0)
|
|
1651
|
+
);
|
|
1652
|
+
} else {
|
|
1653
|
+
setEditFormState({});
|
|
1654
|
+
setUnlimitedBudget(false);
|
|
1655
|
+
}
|
|
1774
1656
|
setNewKeyValue(null);
|
|
1775
1657
|
setNewKeySnippets(null);
|
|
1776
1658
|
setNewKeyModel("");
|
|
1777
1659
|
setGenerateError(null);
|
|
1660
|
+
setEditError(null);
|
|
1661
|
+
setResetSpendConfirm(false);
|
|
1778
1662
|
}
|
|
1779
|
-
}, [open, username]);
|
|
1663
|
+
}, [open, isCreate, keyToEdit, username, keyGenerationSettings?.allowUnlimitedBudget]);
|
|
1780
1664
|
const allowUnlimitedBudget = keyGenerationSettings?.allowUnlimitedBudget ?? false;
|
|
1781
1665
|
const teamRequired = keyGenerationSettings?.teamRequired ?? true;
|
|
1782
|
-
const
|
|
1666
|
+
const formData = isCreate ? generateForm : editFormState;
|
|
1667
|
+
const setFormData = (patch) => {
|
|
1668
|
+
if (isCreate) {
|
|
1669
|
+
setGenerateForm((prev) => ({ ...prev, ...patch }));
|
|
1670
|
+
} else {
|
|
1671
|
+
setEditFormState((prev) => ({ ...prev, ...patch }));
|
|
1672
|
+
}
|
|
1673
|
+
};
|
|
1674
|
+
const currentAlias = (isCreate ? generateForm.alias : editFormState.key_alias) ?? "";
|
|
1675
|
+
const setAliasField = (value) => {
|
|
1676
|
+
if (isCreate) setFormData({ alias: value });
|
|
1677
|
+
else setFormData({ key_alias: value });
|
|
1678
|
+
};
|
|
1679
|
+
const selectedTeam = teams.find((t) => t.team_id === (isCreate ? generateForm.team_id : void 0)) ?? null;
|
|
1783
1680
|
const availableModels = (0, import_react4.useMemo)(() => {
|
|
1784
1681
|
return models.filter((m) => isModelAllowedByTeam(m, selectedTeam?.models));
|
|
1785
1682
|
}, [models, selectedTeam]);
|
|
1786
1683
|
const selectedModels = availableModels.filter((m) => (formData.models || []).includes(m.model_name));
|
|
1787
|
-
const aliasError = !
|
|
1788
|
-
const teamError = teamRequired && !
|
|
1684
|
+
const aliasError = !currentAlias.trim();
|
|
1685
|
+
const teamError = isCreate && teamRequired && !generateForm.team_id;
|
|
1789
1686
|
const budgetInvalid = !unlimitedBudget && (formData.max_budget === void 0 || formData.max_budget === null || formData.max_budget <= 0);
|
|
1790
|
-
const
|
|
1791
|
-
const aliasDuplicate = !!
|
|
1687
|
+
const canSubmit = !aliasError && !teamError && !budgetInvalid && !submitting;
|
|
1688
|
+
const aliasDuplicate = !!currentAlias && keys.some((k) => k.key_alias === currentAlias && k !== keyToEdit);
|
|
1792
1689
|
const budgetEstimate = (0, import_react4.useMemo)(() => {
|
|
1793
1690
|
if (unlimitedBudget || budgetInvalid) return null;
|
|
1794
1691
|
const inputCosts = selectedModels.map((m) => m.input_cost_per_token).filter((c) => typeof c === "number" && c > 0);
|
|
@@ -1801,11 +1698,11 @@ var init_GenerateKeyDialog = __esm({
|
|
|
1801
1698
|
setGenerateError(null);
|
|
1802
1699
|
try {
|
|
1803
1700
|
const request = {
|
|
1804
|
-
...
|
|
1805
|
-
max_budget: unlimitedBudget ? null :
|
|
1701
|
+
...generateForm,
|
|
1702
|
+
max_budget: unlimitedBudget ? null : generateForm.max_budget
|
|
1806
1703
|
};
|
|
1807
|
-
const response = await
|
|
1808
|
-
const model =
|
|
1704
|
+
const response = await onCreateKey(request);
|
|
1705
|
+
const model = generateForm.models?.[0] ?? availableModels[0]?.model_name ?? "";
|
|
1809
1706
|
setNewKeyValue(response.key);
|
|
1810
1707
|
setNewKeyModel(model);
|
|
1811
1708
|
setNewKeySnippets(null);
|
|
@@ -1814,7 +1711,7 @@ var init_GenerateKeyDialog = __esm({
|
|
|
1814
1711
|
setNewKeySnippets(buildSnippets(config.baseUrl, response.key, model));
|
|
1815
1712
|
} catch {
|
|
1816
1713
|
}
|
|
1817
|
-
|
|
1714
|
+
setGenerateForm(createForm(username));
|
|
1818
1715
|
setUnlimitedBudget(false);
|
|
1819
1716
|
} catch (error) {
|
|
1820
1717
|
setGenerateError(error?.message ?? "Failed to generate key");
|
|
@@ -1822,27 +1719,58 @@ var init_GenerateKeyDialog = __esm({
|
|
|
1822
1719
|
setSubmitting(false);
|
|
1823
1720
|
}
|
|
1824
1721
|
};
|
|
1722
|
+
const handleUpdate = async () => {
|
|
1723
|
+
if (!keyToEdit) return;
|
|
1724
|
+
setSubmitting(true);
|
|
1725
|
+
setEditError(null);
|
|
1726
|
+
try {
|
|
1727
|
+
const request = {
|
|
1728
|
+
...editFormState,
|
|
1729
|
+
max_budget: unlimitedBudget ? null : editFormState.max_budget
|
|
1730
|
+
};
|
|
1731
|
+
await onUpdateKey(keyToEdit.token ?? keyToEdit.key, request);
|
|
1732
|
+
onClose();
|
|
1733
|
+
} catch (error) {
|
|
1734
|
+
setEditError(error?.message ?? "Failed to update key");
|
|
1735
|
+
} finally {
|
|
1736
|
+
setSubmitting(false);
|
|
1737
|
+
}
|
|
1738
|
+
};
|
|
1739
|
+
const handleResetSpend = async () => {
|
|
1740
|
+
if (!keyToEdit || !onResetKeySpend) return;
|
|
1741
|
+
setResetSpendSubmitting(true);
|
|
1742
|
+
try {
|
|
1743
|
+
await onResetKeySpend(keyToEdit.token ?? keyToEdit.key);
|
|
1744
|
+
setResetSpendConfirm(false);
|
|
1745
|
+
onClose();
|
|
1746
|
+
} finally {
|
|
1747
|
+
setResetSpendSubmitting(false);
|
|
1748
|
+
}
|
|
1749
|
+
};
|
|
1825
1750
|
const handleClose = () => {
|
|
1826
1751
|
onClose();
|
|
1827
1752
|
setGenerateError(null);
|
|
1753
|
+
setEditError(null);
|
|
1828
1754
|
setNewKeyValue(null);
|
|
1829
1755
|
setNewKeySnippets(null);
|
|
1830
|
-
|
|
1756
|
+
setGenerateForm(createForm(username));
|
|
1757
|
+
setEditFormState({});
|
|
1831
1758
|
setUnlimitedBudget(false);
|
|
1759
|
+
setResetSpendConfirm(false);
|
|
1832
1760
|
};
|
|
1833
1761
|
const copyToClipboard = (text) => {
|
|
1834
1762
|
navigator.clipboard.writeText(text);
|
|
1835
1763
|
};
|
|
1836
1764
|
const modelOption = (m) => {
|
|
1837
|
-
const inCost =
|
|
1838
|
-
const outCost =
|
|
1839
|
-
const ctx =
|
|
1765
|
+
const inCost = fmtCost(m.input_cost_per_token);
|
|
1766
|
+
const outCost = fmtCost(m.output_cost_per_token);
|
|
1767
|
+
const ctx = formatContextWindow(m.max_input_tokens, m.max_output_tokens);
|
|
1840
1768
|
return /* @__PURE__ */ import_react4.default.createElement(import_Box4.default, null, /* @__PURE__ */ import_react4.default.createElement("span", null, m.model_name), m.supports_function_calling && " \u{1F527}", m.supports_vision && " \u{1F441}\uFE0F", ctx && /* @__PURE__ */ import_react4.default.createElement(import_Typography4.default, { variant: "caption", color: "text.secondary", sx: { ml: 1 } }, ctx), (inCost || outCost) && /* @__PURE__ */ import_react4.default.createElement(import_Typography4.default, { variant: "caption", color: "text.secondary", sx: { ml: 1 } }, inCost, " in \xB7 ", outCost, " out"));
|
|
1841
1769
|
};
|
|
1842
1770
|
const renderTeamField = () => {
|
|
1843
1771
|
if (teams.length > 0) {
|
|
1844
1772
|
return /* @__PURE__ */ import_react4.default.createElement(
|
|
1845
|
-
|
|
1773
|
+
import_Autocomplete.default,
|
|
1846
1774
|
{
|
|
1847
1775
|
options: teams,
|
|
1848
1776
|
getOptionLabel: (t) => t.team_alias || t.team_id,
|
|
@@ -1852,28 +1780,61 @@ var init_GenerateKeyDialog = __esm({
|
|
|
1852
1780
|
const model = models.find((m) => m.model_name === name);
|
|
1853
1781
|
return model ? isModelAllowedByTeam(model, team?.models) : false;
|
|
1854
1782
|
});
|
|
1855
|
-
setFormData({
|
|
1783
|
+
setFormData({ team_id: team?.team_id, models: restrictedModels });
|
|
1856
1784
|
},
|
|
1785
|
+
disabled: !isCreate,
|
|
1857
1786
|
renderInput: (params) => /* @__PURE__ */ import_react4.default.createElement(
|
|
1858
1787
|
import_TextField2.default,
|
|
1859
1788
|
{
|
|
1860
1789
|
...params,
|
|
1861
1790
|
label: "Team",
|
|
1862
1791
|
error: teamError,
|
|
1863
|
-
helperText: teamHelperText(teamError, teamRequired),
|
|
1864
|
-
required: teamRequired,
|
|
1792
|
+
helperText: isCreate ? teamHelperText(teamError, teamRequired) : "Team binding is fixed after creation \u2014 delete and recreate the key to change it",
|
|
1793
|
+
required: isCreate && teamRequired,
|
|
1865
1794
|
fullWidth: true
|
|
1866
1795
|
}
|
|
1867
1796
|
)
|
|
1868
1797
|
}
|
|
1869
1798
|
);
|
|
1870
1799
|
}
|
|
1871
|
-
if (teamRequired) {
|
|
1800
|
+
if (teamRequired && isCreate) {
|
|
1872
1801
|
return /* @__PURE__ */ import_react4.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
1802
|
}
|
|
1874
1803
|
return null;
|
|
1875
1804
|
};
|
|
1876
|
-
|
|
1805
|
+
const editKeyId = keyToEdit?.token ?? keyToEdit?.key ?? "";
|
|
1806
|
+
const renderDangerZone = () => {
|
|
1807
|
+
if (isCreate || !onResetKeySpend) return null;
|
|
1808
|
+
return /* @__PURE__ */ import_react4.default.createElement(import_Box4.default, { mt: 1, pt: 2, borderTop: "1px solid", sx: { borderColor: "divider" } }, /* @__PURE__ */ import_react4.default.createElement(import_Typography4.default, { variant: "caption", color: "text.secondary", display: "block", mb: 1 }, "Danger Zone"), !resetSpendConfirm ? /* @__PURE__ */ import_react4.default.createElement(
|
|
1809
|
+
import_Button3.default,
|
|
1810
|
+
{
|
|
1811
|
+
size: "small",
|
|
1812
|
+
color: "warning",
|
|
1813
|
+
variant: "outlined",
|
|
1814
|
+
onClick: () => setResetSpendConfirm(true)
|
|
1815
|
+
},
|
|
1816
|
+
"Reset Spend to $0"
|
|
1817
|
+
) : /* @__PURE__ */ import_react4.default.createElement(import_Box4.default, { display: "flex", alignItems: "center", gap: 1, flexWrap: "wrap" }, /* @__PURE__ */ import_react4.default.createElement(import_Typography4.default, { variant: "body2", color: "warning.main" }, "Zero out spend counter?"), /* @__PURE__ */ import_react4.default.createElement(
|
|
1818
|
+
import_Button3.default,
|
|
1819
|
+
{
|
|
1820
|
+
size: "small",
|
|
1821
|
+
color: "warning",
|
|
1822
|
+
variant: "contained",
|
|
1823
|
+
disabled: resetSpendSubmitting,
|
|
1824
|
+
onClick: handleResetSpend
|
|
1825
|
+
},
|
|
1826
|
+
resetSpendSubmitting ? /* @__PURE__ */ import_react4.default.createElement(import_CircularProgress2.default, { size: 16 }) : "Confirm"
|
|
1827
|
+
), /* @__PURE__ */ import_react4.default.createElement(import_Button3.default, { size: "small", onClick: () => setResetSpendConfirm(false) }, "Cancel")));
|
|
1828
|
+
};
|
|
1829
|
+
const maskKey = (key) => {
|
|
1830
|
+
if (key.length <= 8) return "***";
|
|
1831
|
+
return `${key.slice(0, 4)}...${key.slice(-4)}`;
|
|
1832
|
+
};
|
|
1833
|
+
let dialogTitle = "Edit Key";
|
|
1834
|
+
if (newKeyValue) dialogTitle = "Key Generated";
|
|
1835
|
+
else if (isCreate) dialogTitle = "Generate New Key";
|
|
1836
|
+
const submitLabel = isCreate ? "Generate" : "Save";
|
|
1837
|
+
return /* @__PURE__ */ import_react4.default.createElement(import_Dialog2.default, { open, onClose: handleClose, maxWidth: "sm", fullWidth: true }, /* @__PURE__ */ import_react4.default.createElement(import_DialogTitle2.default, null, dialogTitle), /* @__PURE__ */ import_react4.default.createElement(import_DialogContent2.default, null, newKeyValue ? /* @__PURE__ */ import_react4.default.createElement(import_Box4.default, null, /* @__PURE__ */ import_react4.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_react4.default.createElement(
|
|
1877
1838
|
import_Box4.default,
|
|
1878
1839
|
{
|
|
1879
1840
|
display: "flex",
|
|
@@ -1922,14 +1883,15 @@ var init_GenerateKeyDialog = __esm({
|
|
|
1922
1883
|
newKeySnippets.publicEndpoint
|
|
1923
1884
|
),
|
|
1924
1885
|
/* @__PURE__ */ import_react4.default.createElement(import_IconButton2.default, { size: "small", onClick: () => copyToClipboard(newKeySnippets.publicEndpoint) }, /* @__PURE__ */ import_react4.default.createElement(import_icons_material3.ContentCopy, { fontSize: "small" }))
|
|
1925
|
-
)), newKeySnippets && /* @__PURE__ */ import_react4.default.createElement(import_Box4.default, { mt: 3 }, /* @__PURE__ */ import_react4.default.createElement(import_Box4.default, { display: "flex", alignItems: "center", gap: 1, mb: 1 }, /* @__PURE__ */ import_react4.default.createElement(import_icons_material3.Code, { fontSize: "small", color: "action" }), /* @__PURE__ */ import_react4.default.createElement(import_Typography4.default, { variant: "subtitle2" }, "Start calling the proxy \u2014 paste and run")), /* @__PURE__ */ import_react4.default.createElement(SnippetTabs, { snippets: newKeySnippets, model: newKeyModel, onCopy: copyToClipboard }))) : /* @__PURE__ */ import_react4.default.createElement(import_Box4.default, { display: "flex", flexDirection: "column", gap: 2, mt: 1 }, generateError && /* @__PURE__ */ import_react4.default.createElement(import_Alert.default, { severity: "error", onClose: () => setGenerateError(null) }, generateError), /* @__PURE__ */ import_react4.default.createElement(
|
|
1886
|
+
)), newKeySnippets && /* @__PURE__ */ import_react4.default.createElement(import_Box4.default, { mt: 3 }, /* @__PURE__ */ import_react4.default.createElement(import_Box4.default, { display: "flex", alignItems: "center", gap: 1, mb: 1 }, /* @__PURE__ */ import_react4.default.createElement(import_icons_material3.Code, { fontSize: "small", color: "action" }), /* @__PURE__ */ import_react4.default.createElement(import_Typography4.default, { variant: "subtitle2" }, "Start calling the proxy \u2014 paste and run")), /* @__PURE__ */ import_react4.default.createElement(SnippetTabs, { snippets: newKeySnippets, model: newKeyModel, onCopy: copyToClipboard }))) : /* @__PURE__ */ import_react4.default.createElement(import_Box4.default, { display: "flex", flexDirection: "column", gap: 2, mt: 1 }, !isCreate && /* @__PURE__ */ import_react4.default.createElement(import_Typography4.default, { variant: "body2", color: "text.secondary" }, /* @__PURE__ */ import_react4.default.createElement("code", { style: { fontFamily: "monospace", color: "inherit" } }, maskKey(editKeyId))), generateError && /* @__PURE__ */ import_react4.default.createElement(import_Alert.default, { severity: "error", onClose: () => setGenerateError(null) }, generateError), editError && /* @__PURE__ */ import_react4.default.createElement(import_Alert.default, { severity: "error", onClose: () => setEditError(null) }, editError), /* @__PURE__ */ import_react4.default.createElement(
|
|
1926
1887
|
import_TextField2.default,
|
|
1927
1888
|
{
|
|
1928
1889
|
label: "Alias",
|
|
1929
|
-
value:
|
|
1890
|
+
value: currentAlias,
|
|
1930
1891
|
onChange: (e) => {
|
|
1931
|
-
|
|
1892
|
+
setAliasField(e.target.value);
|
|
1932
1893
|
setGenerateError(null);
|
|
1894
|
+
setEditError(null);
|
|
1933
1895
|
},
|
|
1934
1896
|
error: aliasError,
|
|
1935
1897
|
color: aliasDuplicate ? "warning" : void 0,
|
|
@@ -1937,13 +1899,13 @@ var init_GenerateKeyDialog = __esm({
|
|
|
1937
1899
|
required: true,
|
|
1938
1900
|
fullWidth: true
|
|
1939
1901
|
}
|
|
1940
|
-
), /* @__PURE__ */ import_react4.default.createElement(
|
|
1902
|
+
), !isCreate ? null : /* @__PURE__ */ import_react4.default.createElement(
|
|
1941
1903
|
import_TextField2.default,
|
|
1942
1904
|
{
|
|
1943
1905
|
select: true,
|
|
1944
1906
|
label: "Duration",
|
|
1945
|
-
value:
|
|
1946
|
-
onChange: (e) => setFormData({
|
|
1907
|
+
value: generateForm.duration || "30d",
|
|
1908
|
+
onChange: (e) => setFormData({ duration: e.target.value }),
|
|
1947
1909
|
fullWidth: true
|
|
1948
1910
|
},
|
|
1949
1911
|
/* @__PURE__ */ import_react4.default.createElement(import_MenuItem.default, { value: "1d" }, "1 Day"),
|
|
@@ -1952,14 +1914,14 @@ var init_GenerateKeyDialog = __esm({
|
|
|
1952
1914
|
/* @__PURE__ */ import_react4.default.createElement(import_MenuItem.default, { value: "90d" }, "90 Days"),
|
|
1953
1915
|
/* @__PURE__ */ import_react4.default.createElement(import_MenuItem.default, { value: "1y" }, "1 Year")
|
|
1954
1916
|
), renderTeamField(), availableModels.length > 0 && /* @__PURE__ */ import_react4.default.createElement(
|
|
1955
|
-
|
|
1917
|
+
import_Autocomplete.default,
|
|
1956
1918
|
{
|
|
1957
1919
|
multiple: true,
|
|
1958
1920
|
options: availableModels,
|
|
1959
1921
|
groupBy: (m) => m.mode || "other",
|
|
1960
1922
|
getOptionLabel: (m) => m.model_name,
|
|
1961
1923
|
value: selectedModels,
|
|
1962
|
-
onChange: (_e, selected) => setFormData({
|
|
1924
|
+
onChange: (_e, selected) => setFormData({ models: selected.map((m) => m.model_name) }),
|
|
1963
1925
|
renderOption: (props, m) => /* @__PURE__ */ import_react4.default.createElement("li", { ...props }, modelOption(m)),
|
|
1964
1926
|
renderInput: (params) => /* @__PURE__ */ import_react4.default.createElement(
|
|
1965
1927
|
import_TextField2.default,
|
|
@@ -1981,9 +1943,9 @@ var init_GenerateKeyDialog = __esm({
|
|
|
1981
1943
|
onChange: (e) => {
|
|
1982
1944
|
setUnlimitedBudget(e.target.checked);
|
|
1983
1945
|
if (e.target.checked) {
|
|
1984
|
-
setFormData({
|
|
1946
|
+
setFormData({ max_budget: null });
|
|
1985
1947
|
} else {
|
|
1986
|
-
setFormData({
|
|
1948
|
+
setFormData({ max_budget: editFormState.max_budget ?? generateForm.max_budget ?? 100 });
|
|
1987
1949
|
}
|
|
1988
1950
|
}
|
|
1989
1951
|
}
|
|
@@ -1996,9 +1958,9 @@ var init_GenerateKeyDialog = __esm({
|
|
|
1996
1958
|
label: "Max Budget (USD)",
|
|
1997
1959
|
type: "number",
|
|
1998
1960
|
value: unlimitedBudget ? "" : formData.max_budget ?? "",
|
|
1999
|
-
onChange: (e) => setFormData({
|
|
1961
|
+
onChange: (e) => setFormData({ max_budget: e.target.value ? Number(e.target.value) : void 0 }),
|
|
2000
1962
|
error: budgetInvalid,
|
|
2001
|
-
helperText: budgetHelperText(budgetInvalid, budgetEstimate),
|
|
1963
|
+
helperText: budgetHelperText(budgetInvalid, budgetEstimate, unlimitedBudget),
|
|
2002
1964
|
disabled: unlimitedBudget,
|
|
2003
1965
|
required: true,
|
|
2004
1966
|
fullWidth: true
|
|
@@ -2009,7 +1971,7 @@ var init_GenerateKeyDialog = __esm({
|
|
|
2009
1971
|
label: "TPM Limit",
|
|
2010
1972
|
type: "number",
|
|
2011
1973
|
value: formData.tpm_limit ?? "",
|
|
2012
|
-
onChange: (e) => setFormData({
|
|
1974
|
+
onChange: (e) => setFormData({ tpm_limit: e.target.value ? Number(e.target.value) : void 0 }),
|
|
2013
1975
|
helperText: "Max tokens per minute this key can consume across all models. Leave blank for no limit.",
|
|
2014
1976
|
fullWidth: true
|
|
2015
1977
|
}
|
|
@@ -2019,26 +1981,26 @@ var init_GenerateKeyDialog = __esm({
|
|
|
2019
1981
|
label: "RPM Limit",
|
|
2020
1982
|
type: "number",
|
|
2021
1983
|
value: formData.rpm_limit ?? "",
|
|
2022
|
-
onChange: (e) => setFormData({
|
|
1984
|
+
onChange: (e) => setFormData({ rpm_limit: e.target.value ? Number(e.target.value) : void 0 }),
|
|
2023
1985
|
helperText: "Max requests per minute this key can make across all models. Leave blank for no limit.",
|
|
2024
1986
|
fullWidth: true
|
|
2025
1987
|
}
|
|
2026
|
-
))))), /* @__PURE__ */ import_react4.default.createElement(import_DialogActions2.default, null, newKeyValue ? /* @__PURE__ */ import_react4.default.createElement(import_Button3.default, { onClick: handleClose, variant: "contained", color: "success" }, "Done") : /* @__PURE__ */ import_react4.default.createElement(import_react4.default.Fragment, null, /* @__PURE__ */ import_react4.default.createElement(import_Button3.default, { onClick: handleClose }, "Cancel"), /* @__PURE__ */ import_react4.default.createElement(
|
|
1988
|
+
))), renderDangerZone())), /* @__PURE__ */ import_react4.default.createElement(import_DialogActions2.default, null, newKeyValue ? /* @__PURE__ */ import_react4.default.createElement(import_Button3.default, { onClick: handleClose, variant: "contained", color: "success" }, "Done") : /* @__PURE__ */ import_react4.default.createElement(import_react4.default.Fragment, null, /* @__PURE__ */ import_react4.default.createElement(import_Button3.default, { onClick: handleClose }, "Cancel"), /* @__PURE__ */ import_react4.default.createElement(
|
|
2027
1989
|
import_Button3.default,
|
|
2028
1990
|
{
|
|
2029
|
-
onClick: handleGenerate,
|
|
1991
|
+
onClick: isCreate ? handleGenerate : handleUpdate,
|
|
2030
1992
|
variant: "contained",
|
|
2031
1993
|
color: "primary",
|
|
2032
|
-
disabled: !
|
|
1994
|
+
disabled: !canSubmit
|
|
2033
1995
|
},
|
|
2034
|
-
submitting ? /* @__PURE__ */ import_react4.default.createElement(import_CircularProgress2.default, { size: 24 }) :
|
|
1996
|
+
submitting ? /* @__PURE__ */ import_react4.default.createElement(import_CircularProgress2.default, { size: 24 }) : submitLabel
|
|
2035
1997
|
))));
|
|
2036
1998
|
};
|
|
2037
1999
|
}
|
|
2038
2000
|
});
|
|
2039
2001
|
|
|
2040
2002
|
// src/components/ManageTeamDialog.tsx
|
|
2041
|
-
var import_react5, import_Dialog3, import_DialogTitle3, import_DialogContent3, import_DialogActions3, import_TextField3, import_Button4, import_Alert2, import_Checkbox2, import_FormControlLabel2,
|
|
2003
|
+
var import_react5, 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
2004
|
var init_ManageTeamDialog = __esm({
|
|
2043
2005
|
"src/components/ManageTeamDialog.tsx"() {
|
|
2044
2006
|
"use strict";
|
|
@@ -2052,7 +2014,7 @@ var init_ManageTeamDialog = __esm({
|
|
|
2052
2014
|
import_Alert2 = __toESM(require("@mui/material/Alert"));
|
|
2053
2015
|
import_Checkbox2 = __toESM(require("@mui/material/Checkbox"));
|
|
2054
2016
|
import_FormControlLabel2 = __toESM(require("@mui/material/FormControlLabel"));
|
|
2055
|
-
|
|
2017
|
+
import_Autocomplete2 = __toESM(require("@mui/material/Autocomplete"));
|
|
2056
2018
|
import_CircularProgress3 = __toESM(require("@mui/material/CircularProgress"));
|
|
2057
2019
|
import_MenuItem2 = __toESM(require("@mui/material/MenuItem"));
|
|
2058
2020
|
import_Box5 = __toESM(require("@mui/material/Box"));
|
|
@@ -2317,7 +2279,7 @@ var init_ManageTeamDialog = __esm({
|
|
|
2317
2279
|
fullWidth: true
|
|
2318
2280
|
}
|
|
2319
2281
|
), /* @__PURE__ */ import_react5.default.createElement(
|
|
2320
|
-
|
|
2282
|
+
import_Autocomplete2.default,
|
|
2321
2283
|
{
|
|
2322
2284
|
multiple: true,
|
|
2323
2285
|
options: modelNames,
|
|
@@ -2390,7 +2352,7 @@ var init_ManageTeamDialog = __esm({
|
|
|
2390
2352
|
},
|
|
2391
2353
|
/* @__PURE__ */ import_react5.default.createElement(import_icons_material4.Delete, { fontSize: "small" })
|
|
2392
2354
|
))))))), /* @__PURE__ */ import_react5.default.createElement(import_Box5.default, { sx: { display: "flex", gap: 1, alignItems: "flex-start", flexWrap: "wrap" } }, /* @__PURE__ */ import_react5.default.createElement(
|
|
2393
|
-
|
|
2355
|
+
import_Autocomplete2.default,
|
|
2394
2356
|
{
|
|
2395
2357
|
freeSolo: true,
|
|
2396
2358
|
autoHighlight: true,
|
|
@@ -2449,7 +2411,7 @@ var init_ManageTeamDialog = __esm({
|
|
|
2449
2411
|
},
|
|
2450
2412
|
"Add"
|
|
2451
2413
|
))), showKnowledgeBases && /* @__PURE__ */ import_react5.default.createElement(import_Box5.default, null, /* @__PURE__ */ import_react5.default.createElement(import_Divider2.default, { sx: { my: 1 } }), /* @__PURE__ */ import_react5.default.createElement(import_Typography5.default, { variant: "subtitle2", sx: { mb: 1 } }, "Knowledge Bases"), /* @__PURE__ */ import_react5.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_react5.default.createElement(import_Alert2.default, { severity: "error", sx: { mb: 1 }, onClose: () => setKbError(null) }, kbError), /* @__PURE__ */ import_react5.default.createElement(
|
|
2452
|
-
|
|
2414
|
+
import_Autocomplete2.default,
|
|
2453
2415
|
{
|
|
2454
2416
|
multiple: true,
|
|
2455
2417
|
options: kbOptions,
|
|
@@ -2468,7 +2430,7 @@ var init_ManageTeamDialog = __esm({
|
|
|
2468
2430
|
},
|
|
2469
2431
|
kbBusy ? "Saving\u2026" : "Save knowledge bases"
|
|
2470
2432
|
))), showMcpServers && /* @__PURE__ */ import_react5.default.createElement(import_Box5.default, null, /* @__PURE__ */ import_react5.default.createElement(import_Divider2.default, { sx: { my: 1 } }), /* @__PURE__ */ import_react5.default.createElement(import_Typography5.default, { variant: "subtitle2", sx: { mb: 1 } }, "MCP Servers"), /* @__PURE__ */ import_react5.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_react5.default.createElement(import_Alert2.default, { severity: "error", sx: { mb: 1 }, onClose: () => setMcpError(null) }, mcpError), /* @__PURE__ */ import_react5.default.createElement(
|
|
2471
|
-
|
|
2433
|
+
import_Autocomplete2.default,
|
|
2472
2434
|
{
|
|
2473
2435
|
multiple: true,
|
|
2474
2436
|
options: mcpOptions,
|
|
@@ -2931,6 +2893,13 @@ function buildBudgetSummary(user, teams, keys, maxKeys = 3) {
|
|
|
2931
2893
|
teams: teamLimits
|
|
2932
2894
|
};
|
|
2933
2895
|
}
|
|
2896
|
+
function allBudgetLimits(summary) {
|
|
2897
|
+
return [
|
|
2898
|
+
...summary.keys,
|
|
2899
|
+
...summary.user ? [summary.user] : [],
|
|
2900
|
+
...summary.teams
|
|
2901
|
+
];
|
|
2902
|
+
}
|
|
2934
2903
|
function buildBudgetGauges(summary) {
|
|
2935
2904
|
const byLevel = {
|
|
2936
2905
|
key: summary.keys,
|
|
@@ -2981,7 +2950,7 @@ var init_budget = __esm({
|
|
|
2981
2950
|
}
|
|
2982
2951
|
});
|
|
2983
2952
|
|
|
2984
|
-
// src/components/
|
|
2953
|
+
// src/components/BudgetLimitList.tsx
|
|
2985
2954
|
function levelToneColor(tone, theme) {
|
|
2986
2955
|
switch (tone) {
|
|
2987
2956
|
case "success":
|
|
@@ -2996,25 +2965,125 @@ function levelToneColor(tone, theme) {
|
|
|
2996
2965
|
return theme.palette.primary.main;
|
|
2997
2966
|
}
|
|
2998
2967
|
}
|
|
2999
|
-
var import_react7,
|
|
3000
|
-
var
|
|
3001
|
-
"src/components/
|
|
2968
|
+
var import_react7, import_Box7, import_Paper5, import_Typography7, import_styles4, import_core_components, LimitCard, TaggedLimit, MoreKeysNote, BudgetLimitList, LimitListPanel;
|
|
2969
|
+
var init_BudgetLimitList = __esm({
|
|
2970
|
+
"src/components/BudgetLimitList.tsx"() {
|
|
3002
2971
|
"use strict";
|
|
3003
2972
|
import_react7 = __toESM(require("react"));
|
|
3004
|
-
import_Paper5 = __toESM(require("@mui/material/Paper"));
|
|
3005
2973
|
import_Box7 = __toESM(require("@mui/material/Box"));
|
|
2974
|
+
import_Paper5 = __toESM(require("@mui/material/Paper"));
|
|
3006
2975
|
import_Typography7 = __toESM(require("@mui/material/Typography"));
|
|
2976
|
+
import_styles4 = require("@mui/material/styles");
|
|
2977
|
+
import_core_components = require("@backstage/core-components");
|
|
2978
|
+
init_budget();
|
|
2979
|
+
init_format();
|
|
2980
|
+
init_ui();
|
|
2981
|
+
LimitCard = ({ limit }) => {
|
|
2982
|
+
const tone = budgetTone2(limit.pct);
|
|
2983
|
+
const pct = Math.round(limit.pct);
|
|
2984
|
+
const closeTo = limit.softLimit !== void 0 && limit.spend >= limit.softLimit;
|
|
2985
|
+
return /* @__PURE__ */ import_react7.default.createElement(import_Paper5.default, { variant: "outlined", sx: { px: 1.5, py: 1.25, borderRadius: 1.5 } }, /* @__PURE__ */ import_react7.default.createElement(import_Box7.default, { sx: { display: "flex", alignItems: "baseline", justifyContent: "space-between", gap: 1.5 } }, /* @__PURE__ */ import_react7.default.createElement(import_Box7.default, { sx: { minWidth: 0, flex: 1 } }, /* @__PURE__ */ import_react7.default.createElement(
|
|
2986
|
+
import_Typography7.default,
|
|
2987
|
+
{
|
|
2988
|
+
variant: "body2",
|
|
2989
|
+
sx: { fontWeight: 600, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" },
|
|
2990
|
+
title: limit.sublabel ? `${limit.label} \xB7 ${limit.sublabel}` : limit.label
|
|
2991
|
+
},
|
|
2992
|
+
limit.label
|
|
2993
|
+
), limit.sublabel && /* @__PURE__ */ import_react7.default.createElement(
|
|
2994
|
+
import_Typography7.default,
|
|
2995
|
+
{
|
|
2996
|
+
variant: "caption",
|
|
2997
|
+
color: "text.secondary",
|
|
2998
|
+
sx: {
|
|
2999
|
+
fontSize: 10.5,
|
|
3000
|
+
fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
|
|
3001
|
+
display: "block",
|
|
3002
|
+
overflow: "hidden",
|
|
3003
|
+
textOverflow: "ellipsis",
|
|
3004
|
+
whiteSpace: "nowrap"
|
|
3005
|
+
}
|
|
3006
|
+
},
|
|
3007
|
+
limit.sublabel
|
|
3008
|
+
)), limit.hidden ? /* @__PURE__ */ import_react7.default.createElement(import_Typography7.default, { variant: "body2", sx: { fontWeight: 600, fontVariantNumeric: "tabular-nums", whiteSpace: "nowrap" } }, pct, "% used") : /* @__PURE__ */ import_react7.default.createElement(import_Typography7.default, { variant: "body2", sx: { fontWeight: 600, fontVariantNumeric: "tabular-nums", whiteSpace: "nowrap" } }, fmtUsd(limit.spend), /* @__PURE__ */ import_react7.default.createElement(import_Box7.default, { component: "span", color: "text.secondary", sx: { fontWeight: 400 } }, " / ", fmtUsd(limit.budget)))), /* @__PURE__ */ import_react7.default.createElement(import_Box7.default, { sx: { mt: 1 } }, /* @__PURE__ */ import_react7.default.createElement(Meter, { value: limit.pct, tone, height: 6 })), /* @__PURE__ */ import_react7.default.createElement(
|
|
3009
|
+
import_Box7.default,
|
|
3010
|
+
{
|
|
3011
|
+
sx: {
|
|
3012
|
+
display: "flex",
|
|
3013
|
+
justifyContent: "space-between",
|
|
3014
|
+
alignItems: "center",
|
|
3015
|
+
gap: 1,
|
|
3016
|
+
mt: 0.75
|
|
3017
|
+
}
|
|
3018
|
+
},
|
|
3019
|
+
/* @__PURE__ */ import_react7.default.createElement(import_Typography7.default, { variant: "caption", color: "text.secondary", sx: { fontVariantNumeric: "tabular-nums" } }, limit.hidden ? /* @__PURE__ */ import_react7.default.createElement(import_react7.default.Fragment, null, "Hidden by admin") : /* @__PURE__ */ import_react7.default.createElement(import_react7.default.Fragment, null, pct, "% of budget", closeTo && ` \xB7 soft-limit warning`)),
|
|
3020
|
+
/* @__PURE__ */ import_react7.default.createElement(import_Typography7.default, { variant: "caption", color: "text.secondary" }, fmtBudgetDuration(limit.budgetDuration) ? `resets ${fmtBudgetDuration(limit.budgetDuration)}` : "never resets")
|
|
3021
|
+
));
|
|
3022
|
+
};
|
|
3023
|
+
TaggedLimit = ({ limit }) => {
|
|
3024
|
+
const theme = (0, import_styles4.useTheme)();
|
|
3025
|
+
const tone = budgetTone2(limit.pct);
|
|
3026
|
+
return /* @__PURE__ */ import_react7.default.createElement(import_Box7.default, null, /* @__PURE__ */ import_react7.default.createElement(
|
|
3027
|
+
import_Typography7.default,
|
|
3028
|
+
{
|
|
3029
|
+
sx: {
|
|
3030
|
+
fontSize: 11,
|
|
3031
|
+
fontWeight: 700,
|
|
3032
|
+
letterSpacing: "0.08em",
|
|
3033
|
+
textTransform: "uppercase",
|
|
3034
|
+
color: tone === "accent" ? theme.palette.text.secondary : levelToneColor(tone, theme),
|
|
3035
|
+
mb: 0.5
|
|
3036
|
+
}
|
|
3037
|
+
},
|
|
3038
|
+
limit.kind
|
|
3039
|
+
), /* @__PURE__ */ import_react7.default.createElement(LimitCard, { limit }));
|
|
3040
|
+
};
|
|
3041
|
+
MoreKeysNote = ({
|
|
3042
|
+
count,
|
|
3043
|
+
href,
|
|
3044
|
+
label
|
|
3045
|
+
}) => {
|
|
3046
|
+
const text = label ?? `+${count} more budgeted key${count > 1 ? "s" : ""} further from the cap`;
|
|
3047
|
+
if (!href) {
|
|
3048
|
+
return /* @__PURE__ */ import_react7.default.createElement(import_Typography7.default, { variant: "caption", color: "text.secondary" }, text);
|
|
3049
|
+
}
|
|
3050
|
+
return /* @__PURE__ */ import_react7.default.createElement(import_core_components.Link, { to: href, variant: "caption" }, text, " \u2192");
|
|
3051
|
+
};
|
|
3052
|
+
BudgetLimitList = ({ limits, hiddenKeyCount = 0, keysHref }) => /* @__PURE__ */ import_react7.default.createElement(import_Box7.default, { sx: { display: "flex", flexDirection: "column", gap: 1.25 } }, limits.map((limit) => /* @__PURE__ */ import_react7.default.createElement(TaggedLimit, { key: `${limit.kind}:${limit.sublabel ?? limit.label}`, limit })), hiddenKeyCount > 0 && /* @__PURE__ */ import_react7.default.createElement(MoreKeysNote, { count: hiddenKeyCount, href: keysHref }));
|
|
3053
|
+
LimitListPanel = ({ children }) => /* @__PURE__ */ import_react7.default.createElement(
|
|
3054
|
+
import_Box7.default,
|
|
3055
|
+
{
|
|
3056
|
+
sx: (theme) => ({
|
|
3057
|
+
px: 1.5,
|
|
3058
|
+
py: 1.5,
|
|
3059
|
+
borderRadius: 1.5,
|
|
3060
|
+
bgcolor: (0, import_styles4.alpha)(theme.palette.text.primary, theme.palette.mode === "dark" ? 0.03 : 0.02)
|
|
3061
|
+
})
|
|
3062
|
+
},
|
|
3063
|
+
children
|
|
3064
|
+
);
|
|
3065
|
+
}
|
|
3066
|
+
});
|
|
3067
|
+
|
|
3068
|
+
// src/components/LiteLLMBudgetWidget.tsx
|
|
3069
|
+
var import_react8, 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;
|
|
3070
|
+
var init_LiteLLMBudgetWidget = __esm({
|
|
3071
|
+
"src/components/LiteLLMBudgetWidget.tsx"() {
|
|
3072
|
+
"use strict";
|
|
3073
|
+
import_react8 = __toESM(require("react"));
|
|
3074
|
+
import_Paper6 = __toESM(require("@mui/material/Paper"));
|
|
3075
|
+
import_Box8 = __toESM(require("@mui/material/Box"));
|
|
3076
|
+
import_Typography8 = __toESM(require("@mui/material/Typography"));
|
|
3007
3077
|
import_CircularProgress4 = __toESM(require("@mui/material/CircularProgress"));
|
|
3008
3078
|
import_Alert3 = __toESM(require("@mui/material/Alert"));
|
|
3009
3079
|
import_Collapse = __toESM(require("@mui/material/Collapse"));
|
|
3010
3080
|
import_IconButton4 = __toESM(require("@mui/material/IconButton"));
|
|
3011
3081
|
import_icons_material5 = require("@mui/icons-material");
|
|
3012
|
-
|
|
3082
|
+
import_styles5 = require("@mui/material/styles");
|
|
3013
3083
|
import_core_plugin_api3 = require("@backstage/core-plugin-api");
|
|
3014
|
-
import_core_components = require("@backstage/core-components");
|
|
3015
3084
|
init_api();
|
|
3016
|
-
init_format();
|
|
3017
3085
|
init_ui();
|
|
3086
|
+
init_BudgetLimitList();
|
|
3018
3087
|
init_budget();
|
|
3019
3088
|
LevelFrame = ({
|
|
3020
3089
|
rank,
|
|
@@ -3024,8 +3093,8 @@ var init_LiteLLMBudgetWidget = __esm({
|
|
|
3024
3093
|
tone,
|
|
3025
3094
|
isLast,
|
|
3026
3095
|
children
|
|
3027
|
-
}) => /* @__PURE__ */
|
|
3028
|
-
|
|
3096
|
+
}) => /* @__PURE__ */ import_react8.default.createElement(import_Box8.default, { sx: { display: "flex", gap: 1.5 } }, /* @__PURE__ */ import_react8.default.createElement(import_Box8.default, { sx: { display: "flex", flexDirection: "column", alignItems: "center", width: 26, flexShrink: 0 } }, /* @__PURE__ */ import_react8.default.createElement(
|
|
3097
|
+
import_Box8.default,
|
|
3029
3098
|
{
|
|
3030
3099
|
sx: (theme) => ({
|
|
3031
3100
|
width: 26,
|
|
@@ -3034,7 +3103,7 @@ var init_LiteLLMBudgetWidget = __esm({
|
|
|
3034
3103
|
display: "flex",
|
|
3035
3104
|
alignItems: "center",
|
|
3036
3105
|
justifyContent: "center",
|
|
3037
|
-
bgcolor: (0,
|
|
3106
|
+
bgcolor: (0, import_styles5.alpha)(levelToneColor(tone, theme), 0.14),
|
|
3038
3107
|
color: levelToneColor(tone, theme),
|
|
3039
3108
|
fontSize: 12,
|
|
3040
3109
|
fontWeight: 700,
|
|
@@ -3042,18 +3111,18 @@ var init_LiteLLMBudgetWidget = __esm({
|
|
|
3042
3111
|
})
|
|
3043
3112
|
},
|
|
3044
3113
|
rank
|
|
3045
|
-
), /* @__PURE__ */
|
|
3046
|
-
|
|
3114
|
+
), /* @__PURE__ */ import_react8.default.createElement(
|
|
3115
|
+
import_Box8.default,
|
|
3047
3116
|
{
|
|
3048
3117
|
sx: (theme) => ({
|
|
3049
3118
|
flex: 1,
|
|
3050
3119
|
width: 2,
|
|
3051
3120
|
minHeight: 14,
|
|
3052
|
-
bgcolor: isLast ? "transparent" : (0,
|
|
3121
|
+
bgcolor: isLast ? "transparent" : (0, import_styles5.alpha)(theme.palette.text.primary, 0.12)
|
|
3053
3122
|
})
|
|
3054
3123
|
}
|
|
3055
|
-
)), /* @__PURE__ */
|
|
3056
|
-
|
|
3124
|
+
)), /* @__PURE__ */ import_react8.default.createElement(import_Box8.default, { sx: { flex: 1, minWidth: 0, pb: isLast ? 0 : 2 } }, /* @__PURE__ */ import_react8.default.createElement(import_Box8.default, { sx: { display: "flex", alignItems: "baseline", gap: 1, flexWrap: "wrap", mb: 0.75 } }, /* @__PURE__ */ import_react8.default.createElement(
|
|
3125
|
+
import_Typography8.default,
|
|
3057
3126
|
{
|
|
3058
3127
|
sx: (theme) => ({
|
|
3059
3128
|
fontSize: 12.5,
|
|
@@ -3064,102 +3133,42 @@ var init_LiteLLMBudgetWidget = __esm({
|
|
|
3064
3133
|
})
|
|
3065
3134
|
},
|
|
3066
3135
|
name
|
|
3067
|
-
), /* @__PURE__ */
|
|
3068
|
-
EmptyLimitCard = ({ children }) => /* @__PURE__ */
|
|
3069
|
-
|
|
3136
|
+
), /* @__PURE__ */ import_react8.default.createElement(import_Typography8.default, { variant: "caption", color: "text.secondary", sx: { fontSize: 11.5 } }, tagline), note && /* @__PURE__ */ import_react8.default.createElement(StatusPill, { label: note, tone: "neutral", dot: false, sx: { ml: "auto" } })), /* @__PURE__ */ import_react8.default.createElement(import_Box8.default, { sx: { display: "flex", flexDirection: "column", gap: 1 } }, children)));
|
|
3137
|
+
EmptyLimitCard = ({ children }) => /* @__PURE__ */ import_react8.default.createElement(
|
|
3138
|
+
import_Paper6.default,
|
|
3070
3139
|
{
|
|
3071
3140
|
variant: "outlined",
|
|
3072
3141
|
sx: (theme) => ({
|
|
3073
3142
|
px: 1.5,
|
|
3074
3143
|
py: 1.25,
|
|
3075
3144
|
borderRadius: 1.5,
|
|
3076
|
-
bgcolor: (0,
|
|
3145
|
+
bgcolor: (0, import_styles5.alpha)(theme.palette.text.primary, theme.palette.mode === "dark" ? 0.03 : 0.015)
|
|
3077
3146
|
})
|
|
3078
3147
|
},
|
|
3079
|
-
/* @__PURE__ */
|
|
3148
|
+
/* @__PURE__ */ import_react8.default.createElement(import_Typography8.default, { variant: "body2", color: "text.secondary", sx: { fontSize: 13 } }, children)
|
|
3080
3149
|
);
|
|
3081
|
-
LimitCard = ({ limit }) => {
|
|
3082
|
-
const tone = budgetTone2(limit.pct);
|
|
3083
|
-
const pct = Math.round(limit.pct);
|
|
3084
|
-
const closeTo = limit.softLimit !== void 0 && limit.spend >= limit.softLimit;
|
|
3085
|
-
return /* @__PURE__ */ import_react7.default.createElement(import_Paper5.default, { variant: "outlined", sx: { px: 1.5, py: 1.25, borderRadius: 1.5 } }, /* @__PURE__ */ import_react7.default.createElement(import_Box7.default, { sx: { display: "flex", alignItems: "baseline", justifyContent: "space-between", gap: 1.5 } }, /* @__PURE__ */ import_react7.default.createElement(import_Box7.default, { sx: { minWidth: 0, flex: 1 } }, /* @__PURE__ */ import_react7.default.createElement(
|
|
3086
|
-
import_Typography7.default,
|
|
3087
|
-
{
|
|
3088
|
-
variant: "body2",
|
|
3089
|
-
sx: { fontWeight: 600, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" },
|
|
3090
|
-
title: limit.sublabel ? `${limit.label} \xB7 ${limit.sublabel}` : limit.label
|
|
3091
|
-
},
|
|
3092
|
-
limit.label
|
|
3093
|
-
), limit.sublabel && /* @__PURE__ */ import_react7.default.createElement(
|
|
3094
|
-
import_Typography7.default,
|
|
3095
|
-
{
|
|
3096
|
-
variant: "caption",
|
|
3097
|
-
color: "text.secondary",
|
|
3098
|
-
sx: {
|
|
3099
|
-
fontSize: 10.5,
|
|
3100
|
-
fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
|
|
3101
|
-
display: "block",
|
|
3102
|
-
overflow: "hidden",
|
|
3103
|
-
textOverflow: "ellipsis",
|
|
3104
|
-
whiteSpace: "nowrap"
|
|
3105
|
-
}
|
|
3106
|
-
},
|
|
3107
|
-
limit.sublabel
|
|
3108
|
-
)), limit.hidden ? /* @__PURE__ */ import_react7.default.createElement(import_Typography7.default, { variant: "body2", sx: { fontWeight: 600, fontVariantNumeric: "tabular-nums", whiteSpace: "nowrap" } }, pct, "% used") : /* @__PURE__ */ import_react7.default.createElement(import_Typography7.default, { variant: "body2", sx: { fontWeight: 600, fontVariantNumeric: "tabular-nums", whiteSpace: "nowrap" } }, fmtUsd(limit.spend), /* @__PURE__ */ import_react7.default.createElement(import_Box7.default, { component: "span", color: "text.secondary", sx: { fontWeight: 400 } }, " / ", fmtUsd(limit.budget)))), /* @__PURE__ */ import_react7.default.createElement(import_Box7.default, { sx: { mt: 1 } }, /* @__PURE__ */ import_react7.default.createElement(Meter, { value: limit.pct, tone, height: 6 })), /* @__PURE__ */ import_react7.default.createElement(
|
|
3109
|
-
import_Box7.default,
|
|
3110
|
-
{
|
|
3111
|
-
sx: {
|
|
3112
|
-
display: "flex",
|
|
3113
|
-
justifyContent: "space-between",
|
|
3114
|
-
alignItems: "center",
|
|
3115
|
-
gap: 1,
|
|
3116
|
-
mt: 0.75
|
|
3117
|
-
}
|
|
3118
|
-
},
|
|
3119
|
-
/* @__PURE__ */ import_react7.default.createElement(import_Typography7.default, { variant: "caption", color: "text.secondary", sx: { fontVariantNumeric: "tabular-nums" } }, limit.hidden ? /* @__PURE__ */ import_react7.default.createElement(import_react7.default.Fragment, null, "Hidden by admin") : /* @__PURE__ */ import_react7.default.createElement(import_react7.default.Fragment, null, pct, "% of budget", closeTo && ` \xB7 soft-limit warning`)),
|
|
3120
|
-
/* @__PURE__ */ import_react7.default.createElement(import_Typography7.default, { variant: "caption", color: "text.secondary" }, fmtBudgetDuration(limit.budgetDuration) ? `resets ${fmtBudgetDuration(limit.budgetDuration)}` : "never resets")
|
|
3121
|
-
));
|
|
3122
|
-
};
|
|
3123
3150
|
CLOSEST_LEVEL_LABEL = {
|
|
3124
3151
|
key: "key",
|
|
3125
3152
|
user: "personal",
|
|
3126
3153
|
team: "team"
|
|
3127
3154
|
};
|
|
3128
3155
|
KEYS_PATH = "/litellm?tab=keys";
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
import_Box7.default,
|
|
3156
|
+
HierarchyNote = ({ withReset }) => /* @__PURE__ */ import_react8.default.createElement(
|
|
3157
|
+
import_Box8.default,
|
|
3132
3158
|
{
|
|
3133
3159
|
sx: (theme) => ({
|
|
3134
3160
|
px: 1.5,
|
|
3135
3161
|
py: 1.25,
|
|
3136
3162
|
borderRadius: 1.5,
|
|
3137
3163
|
border: "1px dashed",
|
|
3138
|
-
borderColor: (0,
|
|
3164
|
+
borderColor: (0, import_styles5.alpha)(theme.palette.text.primary, 0.18)
|
|
3139
3165
|
})
|
|
3140
3166
|
},
|
|
3141
|
-
/* @__PURE__ */
|
|
3167
|
+
/* @__PURE__ */ import_react8.default.createElement(import_Typography8.default, { variant: "caption", color: "text.secondary", display: "block" }, /* @__PURE__ */ import_react8.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.")
|
|
3142
3168
|
);
|
|
3143
|
-
TaggedLimit = ({ limit }) => {
|
|
3144
|
-
const tone = budgetTone2(limit.pct);
|
|
3145
|
-
return /* @__PURE__ */ import_react7.default.createElement(import_Box7.default, null, /* @__PURE__ */ import_react7.default.createElement(
|
|
3146
|
-
import_Typography7.default,
|
|
3147
|
-
{
|
|
3148
|
-
sx: (theme) => ({
|
|
3149
|
-
fontSize: 11,
|
|
3150
|
-
fontWeight: 700,
|
|
3151
|
-
letterSpacing: "0.08em",
|
|
3152
|
-
textTransform: "uppercase",
|
|
3153
|
-
color: tone === "accent" ? theme.palette.text.secondary : levelToneColor(tone, theme),
|
|
3154
|
-
mb: 0.5
|
|
3155
|
-
})
|
|
3156
|
-
},
|
|
3157
|
-
limit.kind
|
|
3158
|
-
), /* @__PURE__ */ import_react7.default.createElement(LimitCard, { limit }));
|
|
3159
|
-
};
|
|
3160
3169
|
FullBody = ({ summary }) => {
|
|
3161
3170
|
const hasBudgetedKeys = summary.keys.length + summary.hiddenBudgetedKeys > 0;
|
|
3162
|
-
return /* @__PURE__ */
|
|
3171
|
+
return /* @__PURE__ */ import_react8.default.createElement(import_react8.default.Fragment, null, /* @__PURE__ */ import_react8.default.createElement(
|
|
3163
3172
|
LevelFrame,
|
|
3164
3173
|
{
|
|
3165
3174
|
rank: 1,
|
|
@@ -3167,10 +3176,10 @@ var init_LiteLLMBudgetWidget = __esm({
|
|
|
3167
3176
|
tagline: "per-key cap \xB7 highest priority",
|
|
3168
3177
|
note: hasBudgetedKeys ? void 0 : "no key budgets set"
|
|
3169
3178
|
},
|
|
3170
|
-
!hasBudgetedKeys && /* @__PURE__ */
|
|
3171
|
-
summary.keys.map((k) => /* @__PURE__ */
|
|
3172
|
-
summary.hiddenBudgetedKeys > 0 && /* @__PURE__ */
|
|
3173
|
-
), /* @__PURE__ */
|
|
3179
|
+
!hasBudgetedKeys && /* @__PURE__ */ import_react8.default.createElement(EmptyLimitCard, null, "No key has a budget, so no key-level cap applies."),
|
|
3180
|
+
summary.keys.map((k) => /* @__PURE__ */ import_react8.default.createElement(LimitCard, { key: k.sublabel ?? k.label, limit: k })),
|
|
3181
|
+
summary.hiddenBudgetedKeys > 0 && /* @__PURE__ */ import_react8.default.createElement(MoreKeysNote, { count: summary.hiddenBudgetedKeys, href: KEYS_PATH })
|
|
3182
|
+
), /* @__PURE__ */ import_react8.default.createElement(
|
|
3174
3183
|
LevelFrame,
|
|
3175
3184
|
{
|
|
3176
3185
|
rank: 2,
|
|
@@ -3178,15 +3187,15 @@ var init_LiteLLMBudgetWidget = __esm({
|
|
|
3178
3187
|
tagline: "cap on everything you spend with your own keys",
|
|
3179
3188
|
note: "skipped for team-bound keys"
|
|
3180
3189
|
},
|
|
3181
|
-
summary.user ? /* @__PURE__ */
|
|
3182
|
-
), /* @__PURE__ */
|
|
3190
|
+
summary.user ? /* @__PURE__ */ import_react8.default.createElement(LimitCard, { limit: summary.user }) : /* @__PURE__ */ import_react8.default.createElement(EmptyLimitCard, null, "No personal budget is set on your account.")
|
|
3191
|
+
), /* @__PURE__ */ import_react8.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_react8.default.createElement(LimitCard, { key: t.sublabel ?? t.label, limit: t })) : /* @__PURE__ */ import_react8.default.createElement(EmptyLimitCard, null, "No team you belong to has a budget.")), /* @__PURE__ */ import_react8.default.createElement(LevelFrame, { rank: 4, name: "Global", tagline: "proxy-wide cap set by your admin", isLast: true }, /* @__PURE__ */ import_react8.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_react8.default.createElement(import_Box8.default, { sx: { mt: 2 } }, /* @__PURE__ */ import_react8.default.createElement(HierarchyNote, { withReset: true })));
|
|
3183
3192
|
};
|
|
3184
3193
|
CompactBody = ({ summary }) => {
|
|
3185
3194
|
const { count } = budgetHeadline(summary);
|
|
3186
3195
|
if (count === 0) {
|
|
3187
|
-
return /* @__PURE__ */
|
|
3196
|
+
return /* @__PURE__ */ import_react8.default.createElement(import_Typography8.default, { variant: "body2", color: "text.secondary", sx: { fontSize: 13 } }, "No budget limits apply to you right now.");
|
|
3188
3197
|
}
|
|
3189
|
-
return /* @__PURE__ */
|
|
3198
|
+
return /* @__PURE__ */ import_react8.default.createElement(import_Box8.default, { sx: { display: "flex", flexDirection: "column", gap: 1.25 } }, /* @__PURE__ */ import_react8.default.createElement(HierarchyNote, null), summary.keys.map((k) => /* @__PURE__ */ import_react8.default.createElement(TaggedLimit, { key: k.sublabel ?? k.label, limit: k })), summary.hiddenBudgetedKeys > 0 && /* @__PURE__ */ import_react8.default.createElement(MoreKeysNote, { count: summary.hiddenBudgetedKeys, href: KEYS_PATH }), summary.user && /* @__PURE__ */ import_react8.default.createElement(TaggedLimit, { limit: summary.user }), summary.teams.map((t) => /* @__PURE__ */ import_react8.default.createElement(TaggedLimit, { key: t.sublabel ?? t.label, limit: t })));
|
|
3190
3199
|
};
|
|
3191
3200
|
LiteLLMBudgetWidget = ({
|
|
3192
3201
|
title = "Budget Policy",
|
|
@@ -3197,13 +3206,13 @@ var init_LiteLLMBudgetWidget = __esm({
|
|
|
3197
3206
|
action
|
|
3198
3207
|
}) => {
|
|
3199
3208
|
const api = (0, import_core_plugin_api3.useApi)(liteLlmApiRef);
|
|
3200
|
-
const [loading, setLoading] = (0,
|
|
3201
|
-
const [error, setError] = (0,
|
|
3202
|
-
const [user, setUser] = (0,
|
|
3203
|
-
const [teams, setTeams] = (0,
|
|
3204
|
-
const [keys, setKeys] = (0,
|
|
3205
|
-
const [expanded, setExpanded] = (0,
|
|
3206
|
-
(0,
|
|
3209
|
+
const [loading, setLoading] = (0, import_react8.useState)(true);
|
|
3210
|
+
const [error, setError] = (0, import_react8.useState)(null);
|
|
3211
|
+
const [user, setUser] = (0, import_react8.useState)(null);
|
|
3212
|
+
const [teams, setTeams] = (0, import_react8.useState)([]);
|
|
3213
|
+
const [keys, setKeys] = (0, import_react8.useState)([]);
|
|
3214
|
+
const [expanded, setExpanded] = (0, import_react8.useState)(defaultExpanded);
|
|
3215
|
+
(0, import_react8.useEffect)(() => {
|
|
3207
3216
|
let cancelled = false;
|
|
3208
3217
|
setLoading(true);
|
|
3209
3218
|
setError(null);
|
|
@@ -3227,19 +3236,19 @@ var init_LiteLLMBudgetWidget = __esm({
|
|
|
3227
3236
|
cancelled = true;
|
|
3228
3237
|
};
|
|
3229
3238
|
}, [api]);
|
|
3230
|
-
const summary = (0,
|
|
3239
|
+
const summary = (0, import_react8.useMemo)(
|
|
3231
3240
|
() => buildBudgetSummary(user, teams, keys, maxKeys),
|
|
3232
3241
|
[user, teams, keys, maxKeys]
|
|
3233
3242
|
);
|
|
3234
|
-
const headline = (0,
|
|
3243
|
+
const headline = (0, import_react8.useMemo)(() => budgetHeadline(summary), [summary]);
|
|
3235
3244
|
const limitsWord = `${headline.count} limit${headline.count === 1 ? "" : "s"}`;
|
|
3236
3245
|
const closestWord = headline.closest === null ? "" : ` \xB7 closest: ${CLOSEST_LEVEL_LABEL[headline.closest.kind]} ${Math.round(
|
|
3237
3246
|
headline.closest.pct
|
|
3238
3247
|
)}%`;
|
|
3239
3248
|
const summaryText = headline.count === 0 ? "no limits apply" : `${limitsWord}${closestWord}`;
|
|
3240
|
-
const body = /* @__PURE__ */
|
|
3241
|
-
return /* @__PURE__ */
|
|
3242
|
-
|
|
3249
|
+
const body = /* @__PURE__ */ import_react8.default.createElement(import_react8.default.Fragment, null, loading && /* @__PURE__ */ import_react8.default.createElement(import_Box8.default, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: 140 }, /* @__PURE__ */ import_react8.default.createElement(import_CircularProgress4.default, { size: 32 })), !loading && error && /* @__PURE__ */ import_react8.default.createElement(import_Alert3.default, { severity: "error", sx: { mt: 0.5 } }, error), !loading && !error && (compact ? /* @__PURE__ */ import_react8.default.createElement(CompactBody, { summary }) : /* @__PURE__ */ import_react8.default.createElement(FullBody, { summary })));
|
|
3250
|
+
return /* @__PURE__ */ import_react8.default.createElement(import_Paper6.default, { sx: { p: 2 } }, collapsible ? /* @__PURE__ */ import_react8.default.createElement(
|
|
3251
|
+
import_Box8.default,
|
|
3243
3252
|
{
|
|
3244
3253
|
onClick: () => setExpanded((e) => !e),
|
|
3245
3254
|
sx: {
|
|
@@ -3251,14 +3260,14 @@ var init_LiteLLMBudgetWidget = __esm({
|
|
|
3251
3260
|
mb: expanded ? 1.5 : 0
|
|
3252
3261
|
}
|
|
3253
3262
|
},
|
|
3254
|
-
/* @__PURE__ */
|
|
3263
|
+
/* @__PURE__ */ import_react8.default.createElement(import_Box8.default, { sx: { minWidth: 0, flex: 1 } }, /* @__PURE__ */ import_react8.default.createElement(import_Typography8.default, { variant: "h6", lineHeight: 1.2 }, title), !loading && !error && /* @__PURE__ */ import_react8.default.createElement(import_Box8.default, { sx: { mt: 0.5 } }, /* @__PURE__ */ import_react8.default.createElement(
|
|
3255
3264
|
StatusPill,
|
|
3256
3265
|
{
|
|
3257
3266
|
label: summaryText,
|
|
3258
3267
|
tone: budgetTone2(headline.closest?.pct ?? 0)
|
|
3259
3268
|
}
|
|
3260
3269
|
))),
|
|
3261
|
-
/* @__PURE__ */
|
|
3270
|
+
/* @__PURE__ */ import_react8.default.createElement(
|
|
3262
3271
|
import_IconButton4.default,
|
|
3263
3272
|
{
|
|
3264
3273
|
size: "small",
|
|
@@ -3274,10 +3283,10 @@ var init_LiteLLMBudgetWidget = __esm({
|
|
|
3274
3283
|
transition: theme.transitions.create("transform")
|
|
3275
3284
|
})
|
|
3276
3285
|
},
|
|
3277
|
-
/* @__PURE__ */
|
|
3286
|
+
/* @__PURE__ */ import_react8.default.createElement(import_icons_material5.ExpandMore, null)
|
|
3278
3287
|
)
|
|
3279
|
-
) : /* @__PURE__ */
|
|
3280
|
-
|
|
3288
|
+
) : /* @__PURE__ */ import_react8.default.createElement(import_Box8.default, { sx: { mb: 1.5 } }, /* @__PURE__ */ import_react8.default.createElement(import_Typography8.default, { variant: "h6", lineHeight: 1.2 }, title), /* @__PURE__ */ import_react8.default.createElement(import_Typography8.default, { variant: "caption", color: "text.secondary" }, "How LiteLLM caps spend \u2014 and where you stand right now")), collapsible ? /* @__PURE__ */ import_react8.default.createElement(import_Collapse.default, { in: expanded, unmountOnExit: true }, body) : body, action && /* @__PURE__ */ import_react8.default.createElement(
|
|
3289
|
+
import_Box8.default,
|
|
3281
3290
|
{
|
|
3282
3291
|
sx: (theme) => ({
|
|
3283
3292
|
mt: 2,
|
|
@@ -3306,14 +3315,14 @@ function budgetStatValue(hidden, budget) {
|
|
|
3306
3315
|
if (budget > 0) return fmtUsd2(budget);
|
|
3307
3316
|
return "Unlimited";
|
|
3308
3317
|
}
|
|
3309
|
-
var
|
|
3318
|
+
var import_react9, 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;
|
|
3310
3319
|
var init_TeamUsage = __esm({
|
|
3311
3320
|
"src/components/TeamUsage.tsx"() {
|
|
3312
3321
|
"use strict";
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3322
|
+
import_react9 = __toESM(require("react"));
|
|
3323
|
+
import_Paper7 = __toESM(require("@mui/material/Paper"));
|
|
3324
|
+
import_Box9 = __toESM(require("@mui/material/Box"));
|
|
3325
|
+
import_Typography9 = __toESM(require("@mui/material/Typography"));
|
|
3317
3326
|
import_Skeleton4 = __toESM(require("@mui/material/Skeleton"));
|
|
3318
3327
|
import_Divider3 = __toESM(require("@mui/material/Divider"));
|
|
3319
3328
|
import_Table4 = __toESM(require("@mui/material/Table"));
|
|
@@ -3326,13 +3335,13 @@ var init_TeamUsage = __esm({
|
|
|
3326
3335
|
import_IconButton5 = __toESM(require("@mui/material/IconButton"));
|
|
3327
3336
|
import_Button5 = __toESM(require("@mui/material/Button"));
|
|
3328
3337
|
import_CircularProgress5 = __toESM(require("@mui/material/CircularProgress"));
|
|
3329
|
-
|
|
3338
|
+
import_styles6 = require("@mui/material/styles");
|
|
3330
3339
|
import_icons_material6 = require("@mui/icons-material");
|
|
3331
3340
|
import_recharts2 = require("recharts");
|
|
3332
3341
|
init_ui();
|
|
3333
3342
|
fmtUsd2 = (n) => `$${(n ?? 0).toFixed(2)}`;
|
|
3334
3343
|
TeamCard = ({ team, usage, usageLoading, canManage, onEditTeam }) => {
|
|
3335
|
-
const [expanded, setExpanded] = (0,
|
|
3344
|
+
const [expanded, setExpanded] = (0, import_react9.useState)(false);
|
|
3336
3345
|
const chart = useChartTheme();
|
|
3337
3346
|
const hidden = team.budget_hidden === true;
|
|
3338
3347
|
const budget = hidden ? 0 : team.max_budget ?? 0;
|
|
@@ -3344,24 +3353,24 @@ var init_TeamUsage = __esm({
|
|
|
3344
3353
|
const renderDailySpendSection = () => {
|
|
3345
3354
|
if (hidden) return null;
|
|
3346
3355
|
if (usageLoading) {
|
|
3347
|
-
return /* @__PURE__ */
|
|
3356
|
+
return /* @__PURE__ */ import_react9.default.createElement(import_Box9.default, { display: "flex", justifyContent: "center", py: 3 }, /* @__PURE__ */ import_react9.default.createElement(import_CircularProgress5.default, { size: 22 }));
|
|
3348
3357
|
}
|
|
3349
3358
|
if (dailyData.length === 0) return null;
|
|
3350
|
-
return /* @__PURE__ */
|
|
3351
|
-
|
|
3359
|
+
return /* @__PURE__ */ import_react9.default.createElement(import_Box9.default, { mb: 2.5 }, /* @__PURE__ */ import_react9.default.createElement(
|
|
3360
|
+
import_Typography9.default,
|
|
3352
3361
|
{
|
|
3353
3362
|
variant: "caption",
|
|
3354
3363
|
color: "text.secondary",
|
|
3355
3364
|
sx: { display: "block", fontSize: 11, fontWeight: 700, letterSpacing: "0.07em", textTransform: "uppercase", mb: 1 }
|
|
3356
3365
|
},
|
|
3357
3366
|
"Daily spend"
|
|
3358
|
-
), /* @__PURE__ */
|
|
3367
|
+
), /* @__PURE__ */ import_react9.default.createElement(import_Box9.default, { height: 150 }, /* @__PURE__ */ import_react9.default.createElement(import_recharts2.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react9.default.createElement(import_recharts2.AreaChart, { data: dailyData, margin: { top: 4, right: 8, left: 0, bottom: 0 } }, /* @__PURE__ */ import_react9.default.createElement(import_recharts2.CartesianGrid, { ...chart.grid }), /* @__PURE__ */ import_react9.default.createElement(import_recharts2.XAxis, { dataKey: "date", tickFormatter: fmtDateShort, ...chart.axis }), /* @__PURE__ */ import_react9.default.createElement(import_recharts2.YAxis, { tickFormatter: fmtUsdCompact, width: 52, ...chart.axis }), /* @__PURE__ */ import_react9.default.createElement(
|
|
3359
3368
|
import_recharts2.Tooltip,
|
|
3360
3369
|
{
|
|
3361
3370
|
cursor: chart.cursor,
|
|
3362
|
-
content: /* @__PURE__ */
|
|
3371
|
+
content: /* @__PURE__ */ import_react9.default.createElement(ChartTooltip, { valueFormatter: (v) => `$${v.toFixed(4)}` })
|
|
3363
3372
|
}
|
|
3364
|
-
), /* @__PURE__ */
|
|
3373
|
+
), /* @__PURE__ */ import_react9.default.createElement(
|
|
3365
3374
|
import_recharts2.Area,
|
|
3366
3375
|
{
|
|
3367
3376
|
type: "monotone",
|
|
@@ -3374,8 +3383,8 @@ var init_TeamUsage = __esm({
|
|
|
3374
3383
|
}
|
|
3375
3384
|
)))));
|
|
3376
3385
|
};
|
|
3377
|
-
const sectionLabel = (label) => /* @__PURE__ */
|
|
3378
|
-
|
|
3386
|
+
const sectionLabel = (label) => /* @__PURE__ */ import_react9.default.createElement(
|
|
3387
|
+
import_Typography9.default,
|
|
3379
3388
|
{
|
|
3380
3389
|
variant: "caption",
|
|
3381
3390
|
color: "text.secondary",
|
|
@@ -3383,8 +3392,8 @@ var init_TeamUsage = __esm({
|
|
|
3383
3392
|
},
|
|
3384
3393
|
label
|
|
3385
3394
|
);
|
|
3386
|
-
return /* @__PURE__ */
|
|
3387
|
-
|
|
3395
|
+
return /* @__PURE__ */ import_react9.default.createElement(import_Paper7.default, { variant: "outlined", sx: { borderRadius: 2, p: 2.5 } }, /* @__PURE__ */ import_react9.default.createElement(import_Box9.default, { display: "flex", alignItems: "center", gap: 1.5 }, /* @__PURE__ */ import_react9.default.createElement(
|
|
3396
|
+
import_Box9.default,
|
|
3388
3397
|
{
|
|
3389
3398
|
sx: (theme) => ({
|
|
3390
3399
|
width: 36,
|
|
@@ -3394,20 +3403,20 @@ var init_TeamUsage = __esm({
|
|
|
3394
3403
|
display: "flex",
|
|
3395
3404
|
alignItems: "center",
|
|
3396
3405
|
justifyContent: "center",
|
|
3397
|
-
bgcolor: (0,
|
|
3406
|
+
bgcolor: (0, import_styles6.alpha)(theme.palette.primary.main, 0.1),
|
|
3398
3407
|
color: theme.palette.primary.main
|
|
3399
3408
|
})
|
|
3400
3409
|
},
|
|
3401
|
-
/* @__PURE__ */
|
|
3402
|
-
), /* @__PURE__ */
|
|
3403
|
-
|
|
3410
|
+
/* @__PURE__ */ import_react9.default.createElement(import_icons_material6.Group, { fontSize: "small" })
|
|
3411
|
+
), /* @__PURE__ */ import_react9.default.createElement(import_Box9.default, { flexGrow: 1, minWidth: 0 }, /* @__PURE__ */ import_react9.default.createElement(import_Typography9.default, { variant: "subtitle1", sx: { fontWeight: 600, lineHeight: 1.3 } }, team.team_alias || "Untitled team"), /* @__PURE__ */ import_react9.default.createElement(
|
|
3412
|
+
import_Typography9.default,
|
|
3404
3413
|
{
|
|
3405
3414
|
variant: "caption",
|
|
3406
3415
|
color: "text.secondary",
|
|
3407
3416
|
sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 11 }
|
|
3408
3417
|
},
|
|
3409
3418
|
team.team_id
|
|
3410
|
-
)), isOver && /* @__PURE__ */
|
|
3419
|
+
)), isOver && /* @__PURE__ */ import_react9.default.createElement(StatusPill, { label: "Over budget", tone: "danger" }), isNear && /* @__PURE__ */ import_react9.default.createElement(StatusPill, { label: "Near limit", tone: "warning" }), canManage && onEditTeam && /* @__PURE__ */ import_react9.default.createElement(
|
|
3411
3420
|
import_Button5.default,
|
|
3412
3421
|
{
|
|
3413
3422
|
size: "small",
|
|
@@ -3416,7 +3425,7 @@ var init_TeamUsage = __esm({
|
|
|
3416
3425
|
sx: { textTransform: "none" }
|
|
3417
3426
|
},
|
|
3418
3427
|
"Edit"
|
|
3419
|
-
), /* @__PURE__ */
|
|
3428
|
+
), /* @__PURE__ */ import_react9.default.createElement(
|
|
3420
3429
|
import_IconButton5.default,
|
|
3421
3430
|
{
|
|
3422
3431
|
size: "small",
|
|
@@ -3429,9 +3438,9 @@ var init_TeamUsage = __esm({
|
|
|
3429
3438
|
transition: theme.transitions.create("transform")
|
|
3430
3439
|
})
|
|
3431
3440
|
},
|
|
3432
|
-
/* @__PURE__ */
|
|
3433
|
-
)), /* @__PURE__ */
|
|
3434
|
-
|
|
3441
|
+
/* @__PURE__ */ import_react9.default.createElement(import_icons_material6.ExpandMore, null)
|
|
3442
|
+
)), /* @__PURE__ */ import_react9.default.createElement(
|
|
3443
|
+
import_Box9.default,
|
|
3435
3444
|
{
|
|
3436
3445
|
sx: {
|
|
3437
3446
|
display: "grid",
|
|
@@ -3441,35 +3450,35 @@ var init_TeamUsage = __esm({
|
|
|
3441
3450
|
maxWidth: 640
|
|
3442
3451
|
}
|
|
3443
3452
|
},
|
|
3444
|
-
/* @__PURE__ */
|
|
3445
|
-
/* @__PURE__ */
|
|
3446
|
-
/* @__PURE__ */
|
|
3447
|
-
/* @__PURE__ */
|
|
3448
|
-
/* @__PURE__ */
|
|
3449
|
-
/* @__PURE__ */
|
|
3450
|
-
), hidden && /* @__PURE__ */
|
|
3453
|
+
/* @__PURE__ */ import_react9.default.createElement(Stat, { label: "Members", value: team.members_with_roles?.length ?? "\u2014" }),
|
|
3454
|
+
/* @__PURE__ */ import_react9.default.createElement(Stat, { label: "Models", value: team.models?.length ? team.models.length : "All" }),
|
|
3455
|
+
/* @__PURE__ */ import_react9.default.createElement(Stat, { label: "Budget", value: budgetStatValue(hidden, budget) }),
|
|
3456
|
+
/* @__PURE__ */ import_react9.default.createElement(Stat, { label: "Spend", value: hidden ? "\u2014" : fmtUsd2(spend) }),
|
|
3457
|
+
/* @__PURE__ */ import_react9.default.createElement(Stat, { label: "TPM", value: team.tpm_limit && team.tpm_limit > 0 ? team.tpm_limit.toLocaleString() : "\u2014" }),
|
|
3458
|
+
/* @__PURE__ */ import_react9.default.createElement(Stat, { label: "RPM", value: team.rpm_limit && team.rpm_limit > 0 ? team.rpm_limit.toLocaleString() : "\u2014" })
|
|
3459
|
+
), hidden && /* @__PURE__ */ import_react9.default.createElement(import_Box9.default, { mt: 2, maxWidth: 640 }, /* @__PURE__ */ import_react9.default.createElement(import_Box9.default, { display: "flex", justifyContent: "space-between", alignItems: "baseline", mb: 0.75 }, /* @__PURE__ */ import_react9.default.createElement(import_Typography9.default, { variant: "caption", color: "text.secondary", sx: { fontVariantNumeric: "tabular-nums" } }, budgetPct.toFixed(0), "% used"), /* @__PURE__ */ import_react9.default.createElement(import_Typography9.default, { variant: "caption", color: "text.secondary" }, "Hidden by admin")), /* @__PURE__ */ import_react9.default.createElement(Meter, { value: budgetPct, tone: budgetTone3(isOver, isNear), height: 5 }), /* @__PURE__ */ import_react9.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_react9.default.createElement(import_Box9.default, { mt: 2, maxWidth: 640 }, /* @__PURE__ */ import_react9.default.createElement(import_Box9.default, { display: "flex", justifyContent: "space-between", alignItems: "baseline", mb: 0.75 }, /* @__PURE__ */ import_react9.default.createElement(import_Typography9.default, { variant: "caption", color: "text.secondary", sx: { fontVariantNumeric: "tabular-nums" } }, fmtUsd2(spend), " of ", fmtUsd2(budget)), /* @__PURE__ */ import_react9.default.createElement(import_Typography9.default, { variant: "caption", color: "text.secondary", sx: { fontVariantNumeric: "tabular-nums" } }, budgetPct.toFixed(0), "%")), /* @__PURE__ */ import_react9.default.createElement(Meter, { value: budgetPct, tone: budgetTone3(isOver, isNear), height: 5 })), /* @__PURE__ */ import_react9.default.createElement(import_Collapse2.default, { in: expanded, unmountOnExit: true }, /* @__PURE__ */ import_react9.default.createElement(import_Divider3.default, { sx: { my: 2.5 } }), /* @__PURE__ */ import_react9.default.createElement(import_Box9.default, { mb: 2.5 }, sectionLabel("Models"), team.models?.length ? /* @__PURE__ */ import_react9.default.createElement(import_Box9.default, { display: "flex", gap: 0.5, flexWrap: "wrap" }, team.models.map((m) => /* @__PURE__ */ import_react9.default.createElement(TagChip, { key: m, label: m, title: m }))) : /* @__PURE__ */ import_react9.default.createElement(import_Typography9.default, { variant: "body2", color: "text.secondary" }, "All models allowed")), renderDailySpendSection(), /* @__PURE__ */ import_react9.default.createElement(import_Box9.default, null, sectionLabel("Members"), team.members_with_roles?.length ? /* @__PURE__ */ import_react9.default.createElement(
|
|
3451
3460
|
import_TableContainer4.default,
|
|
3452
3461
|
{
|
|
3453
|
-
component:
|
|
3462
|
+
component: import_Paper7.default,
|
|
3454
3463
|
variant: "outlined",
|
|
3455
3464
|
sx: { borderRadius: 1.5 }
|
|
3456
3465
|
},
|
|
3457
|
-
/* @__PURE__ */
|
|
3458
|
-
|
|
3466
|
+
/* @__PURE__ */ import_react9.default.createElement(import_Table4.default, { size: "small", sx: dataTableSx }, /* @__PURE__ */ import_react9.default.createElement(import_TableHead4.default, null, /* @__PURE__ */ import_react9.default.createElement(import_TableRow4.default, null, /* @__PURE__ */ import_react9.default.createElement(import_TableCell4.default, null, "User"), /* @__PURE__ */ import_react9.default.createElement(import_TableCell4.default, { align: "right" }, "Role"))), /* @__PURE__ */ import_react9.default.createElement(import_TableBody4.default, null, team.members_with_roles.map((m) => /* @__PURE__ */ import_react9.default.createElement(import_TableRow4.default, { key: m.user_id }, /* @__PURE__ */ import_react9.default.createElement(import_TableCell4.default, null, m.user_email ? /* @__PURE__ */ import_react9.default.createElement(import_react9.default.Fragment, null, /* @__PURE__ */ import_react9.default.createElement(import_Typography9.default, { variant: "body2" }, m.user_email), /* @__PURE__ */ import_react9.default.createElement(
|
|
3467
|
+
import_Typography9.default,
|
|
3459
3468
|
{
|
|
3460
3469
|
variant: "caption",
|
|
3461
3470
|
color: "text.secondary",
|
|
3462
3471
|
sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 11 }
|
|
3463
3472
|
},
|
|
3464
3473
|
m.user_id
|
|
3465
|
-
)) : /* @__PURE__ */
|
|
3466
|
-
|
|
3474
|
+
)) : /* @__PURE__ */ import_react9.default.createElement(
|
|
3475
|
+
import_Typography9.default,
|
|
3467
3476
|
{
|
|
3468
3477
|
variant: "body2",
|
|
3469
3478
|
sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace" }
|
|
3470
3479
|
},
|
|
3471
3480
|
m.user_id
|
|
3472
|
-
)), /* @__PURE__ */
|
|
3481
|
+
)), /* @__PURE__ */ import_react9.default.createElement(import_TableCell4.default, { align: "right" }, /* @__PURE__ */ import_react9.default.createElement(
|
|
3473
3482
|
StatusPill,
|
|
3474
3483
|
{
|
|
3475
3484
|
label: m.role,
|
|
@@ -3477,7 +3486,7 @@ var init_TeamUsage = __esm({
|
|
|
3477
3486
|
dot: false
|
|
3478
3487
|
}
|
|
3479
3488
|
))))))
|
|
3480
|
-
) : /* @__PURE__ */
|
|
3489
|
+
) : /* @__PURE__ */ import_react9.default.createElement(import_Typography9.default, { variant: "body2", color: "text.secondary" }, "No members assigned."))));
|
|
3481
3490
|
};
|
|
3482
3491
|
TeamUsage = ({
|
|
3483
3492
|
teams,
|
|
@@ -3489,22 +3498,22 @@ var init_TeamUsage = __esm({
|
|
|
3489
3498
|
canCreate,
|
|
3490
3499
|
onCreateTeam
|
|
3491
3500
|
}) => {
|
|
3492
|
-
const createAction = canCreate && onCreateTeam ? /* @__PURE__ */
|
|
3501
|
+
const createAction = canCreate && onCreateTeam ? /* @__PURE__ */ import_react9.default.createElement(
|
|
3493
3502
|
import_Button5.default,
|
|
3494
3503
|
{
|
|
3495
3504
|
variant: "contained",
|
|
3496
3505
|
color: "primary",
|
|
3497
3506
|
disableElevation: true,
|
|
3498
|
-
startIcon: /* @__PURE__ */
|
|
3507
|
+
startIcon: /* @__PURE__ */ import_react9.default.createElement(import_icons_material6.Add, null),
|
|
3499
3508
|
onClick: onCreateTeam
|
|
3500
3509
|
},
|
|
3501
3510
|
"Create Team"
|
|
3502
3511
|
) : void 0;
|
|
3503
3512
|
if (loading) {
|
|
3504
|
-
return /* @__PURE__ */
|
|
3513
|
+
return /* @__PURE__ */ import_react9.default.createElement(SectionCard, { title: "Teams", actions: createAction }, /* @__PURE__ */ import_react9.default.createElement(import_Skeleton4.default, { variant: "rounded", height: 120, sx: { mb: 2 } }), /* @__PURE__ */ import_react9.default.createElement(import_Skeleton4.default, { variant: "rounded", height: 120 }));
|
|
3505
3514
|
}
|
|
3506
3515
|
if (!teams.length) {
|
|
3507
|
-
return /* @__PURE__ */
|
|
3516
|
+
return /* @__PURE__ */ import_react9.default.createElement(SectionCard, { title: "Teams", actions: createAction }, /* @__PURE__ */ import_react9.default.createElement(
|
|
3508
3517
|
EmptyState,
|
|
3509
3518
|
{
|
|
3510
3519
|
message: "You're not a member of any LiteLLM team yet.",
|
|
@@ -3512,14 +3521,14 @@ var init_TeamUsage = __esm({
|
|
|
3512
3521
|
}
|
|
3513
3522
|
));
|
|
3514
3523
|
}
|
|
3515
|
-
return /* @__PURE__ */
|
|
3524
|
+
return /* @__PURE__ */ import_react9.default.createElement(
|
|
3516
3525
|
SectionCard,
|
|
3517
3526
|
{
|
|
3518
3527
|
title: "Teams",
|
|
3519
3528
|
subtitle: `${teams.length} team${teams.length === 1 ? "" : "s"}`,
|
|
3520
3529
|
actions: createAction
|
|
3521
3530
|
},
|
|
3522
|
-
/* @__PURE__ */
|
|
3531
|
+
/* @__PURE__ */ import_react9.default.createElement(import_Box9.default, { sx: { display: "flex", flexDirection: "column", gap: 2 } }, teams.map((team) => /* @__PURE__ */ import_react9.default.createElement(
|
|
3523
3532
|
TeamCard,
|
|
3524
3533
|
{
|
|
3525
3534
|
key: team.team_id,
|
|
@@ -3553,12 +3562,12 @@ function fmtTokens(n) {
|
|
|
3553
3562
|
if (n >= 1e3) return `${(n / 1e3).toFixed(0)}K`;
|
|
3554
3563
|
return String(n);
|
|
3555
3564
|
}
|
|
3556
|
-
var
|
|
3565
|
+
var import_react10, 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;
|
|
3557
3566
|
var init_ModelsTable = __esm({
|
|
3558
3567
|
"src/components/ModelsTable.tsx"() {
|
|
3559
3568
|
"use strict";
|
|
3560
|
-
|
|
3561
|
-
|
|
3569
|
+
import_react10 = __toESM(require("react"));
|
|
3570
|
+
import_Box10 = __toESM(require("@mui/material/Box"));
|
|
3562
3571
|
import_Table5 = __toESM(require("@mui/material/Table"));
|
|
3563
3572
|
import_TableBody5 = __toESM(require("@mui/material/TableBody"));
|
|
3564
3573
|
import_TableCell5 = __toESM(require("@mui/material/TableCell"));
|
|
@@ -3566,7 +3575,7 @@ var init_ModelsTable = __esm({
|
|
|
3566
3575
|
import_TableHead5 = __toESM(require("@mui/material/TableHead"));
|
|
3567
3576
|
import_TableRow5 = __toESM(require("@mui/material/TableRow"));
|
|
3568
3577
|
import_TableSortLabel2 = __toESM(require("@mui/material/TableSortLabel"));
|
|
3569
|
-
|
|
3578
|
+
import_Typography10 = __toESM(require("@mui/material/Typography"));
|
|
3570
3579
|
import_MenuItem4 = __toESM(require("@mui/material/MenuItem"));
|
|
3571
3580
|
import_Select = __toESM(require("@mui/material/Select"));
|
|
3572
3581
|
import_FormControl = __toESM(require("@mui/material/FormControl"));
|
|
@@ -3579,24 +3588,24 @@ var init_ModelsTable = __esm({
|
|
|
3579
3588
|
import_Snackbar = __toESM(require("@mui/material/Snackbar"));
|
|
3580
3589
|
import_Alert4 = __toESM(require("@mui/material/Alert"));
|
|
3581
3590
|
init_ui();
|
|
3582
|
-
|
|
3591
|
+
import_styles7 = require("@mui/material/styles");
|
|
3583
3592
|
ALL_PROXY_MODELS2 = "all-proxy-models";
|
|
3584
3593
|
ModelsTable = ({ allModels, teams, loading }) => {
|
|
3585
|
-
const theme = (0,
|
|
3586
|
-
const [selectedTeamId, setSelectedTeamId] = (0,
|
|
3587
|
-
const [filterText, setFilterText] = (0,
|
|
3588
|
-
const [copiedSnackbar, setCopiedSnackbar] = (0,
|
|
3589
|
-
const [sortKey, setSortKey] = (0,
|
|
3590
|
-
const [sortDirection, setSortDirection] = (0,
|
|
3591
|
-
const selectedTeam = (0,
|
|
3594
|
+
const theme = (0, import_styles7.useTheme)();
|
|
3595
|
+
const [selectedTeamId, setSelectedTeamId] = (0, import_react10.useState)("");
|
|
3596
|
+
const [filterText, setFilterText] = (0, import_react10.useState)("");
|
|
3597
|
+
const [copiedSnackbar, setCopiedSnackbar] = (0, import_react10.useState)(false);
|
|
3598
|
+
const [sortKey, setSortKey] = (0, import_react10.useState)("model_name");
|
|
3599
|
+
const [sortDirection, setSortDirection] = (0, import_react10.useState)("asc");
|
|
3600
|
+
const selectedTeam = (0, import_react10.useMemo)(
|
|
3592
3601
|
() => teams.find((t) => t.team_id === selectedTeamId) ?? null,
|
|
3593
3602
|
[teams, selectedTeamId]
|
|
3594
3603
|
);
|
|
3595
|
-
const teamModels = (0,
|
|
3604
|
+
const teamModels = (0, import_react10.useMemo)(() => {
|
|
3596
3605
|
if (!selectedTeam) return [];
|
|
3597
3606
|
return allModels.filter((model) => isModelAllowedByTeam2(model, selectedTeam.models));
|
|
3598
3607
|
}, [allModels, selectedTeam]);
|
|
3599
|
-
const filteredModels = (0,
|
|
3608
|
+
const filteredModels = (0, import_react10.useMemo)(() => {
|
|
3600
3609
|
const q = filterText.trim().toLowerCase();
|
|
3601
3610
|
let models = teamModels;
|
|
3602
3611
|
if (q) {
|
|
@@ -3643,10 +3652,10 @@ var init_ModelsTable = __esm({
|
|
|
3643
3652
|
return sortDirection === "asc" ? aVal - bVal : bVal - aVal;
|
|
3644
3653
|
});
|
|
3645
3654
|
}, [teamModels, filterText, sortKey, sortDirection]);
|
|
3646
|
-
const handleCopyModelId = (0,
|
|
3655
|
+
const handleCopyModelId = (0, import_react10.useCallback)((modelId) => {
|
|
3647
3656
|
navigator.clipboard.writeText(modelId).then(() => setCopiedSnackbar(true));
|
|
3648
3657
|
}, []);
|
|
3649
|
-
const handleSort = (0,
|
|
3658
|
+
const handleSort = (0, import_react10.useCallback)((key) => {
|
|
3650
3659
|
if (sortKey === key) {
|
|
3651
3660
|
setSortDirection((prev) => prev === "asc" ? "desc" : "asc");
|
|
3652
3661
|
} else {
|
|
@@ -3668,13 +3677,13 @@ var init_ModelsTable = __esm({
|
|
|
3668
3677
|
return theme.palette.info.main;
|
|
3669
3678
|
}
|
|
3670
3679
|
};
|
|
3671
|
-
return /* @__PURE__ */
|
|
3680
|
+
return /* @__PURE__ */ import_react10.default.createElement(
|
|
3672
3681
|
SectionCard,
|
|
3673
3682
|
{
|
|
3674
3683
|
title: "Available Models",
|
|
3675
3684
|
subtitle: selectedTeam ? `${filteredModels.length}${filterText.trim() ? ` of ${teamModels.length}` : ""} model${filteredModels.length === 1 ? "" : "s"}` : "Select a team to view its models"
|
|
3676
3685
|
},
|
|
3677
|
-
/* @__PURE__ */
|
|
3686
|
+
/* @__PURE__ */ import_react10.default.createElement(import_Box10.default, { sx: { px: 2.5, pb: 2.5, pt: 1, display: "flex", gap: 2, flexWrap: "wrap" } }, /* @__PURE__ */ import_react10.default.createElement(import_FormControl.default, { size: "small", sx: { minWidth: 240, mb: 2 } }, /* @__PURE__ */ import_react10.default.createElement(import_InputLabel.default, { id: "team-select-label" }, "Team"), /* @__PURE__ */ import_react10.default.createElement(
|
|
3678
3687
|
import_Select.default,
|
|
3679
3688
|
{
|
|
3680
3689
|
labelId: "team-select-label",
|
|
@@ -3682,8 +3691,8 @@ var init_ModelsTable = __esm({
|
|
|
3682
3691
|
label: "Team",
|
|
3683
3692
|
onChange: (e) => setSelectedTeamId(e.target.value)
|
|
3684
3693
|
},
|
|
3685
|
-
teams.map((t) => /* @__PURE__ */
|
|
3686
|
-
)), /* @__PURE__ */
|
|
3694
|
+
teams.map((t) => /* @__PURE__ */ import_react10.default.createElement(import_MenuItem4.default, { key: t.team_id, value: t.team_id }, t.team_alias ?? t.team_id))
|
|
3695
|
+
)), /* @__PURE__ */ import_react10.default.createElement(
|
|
3687
3696
|
import_TextField5.default,
|
|
3688
3697
|
{
|
|
3689
3698
|
label: "Filter Models",
|
|
@@ -3693,33 +3702,33 @@ var init_ModelsTable = __esm({
|
|
|
3693
3702
|
disabled: !selectedTeamId,
|
|
3694
3703
|
sx: { minWidth: 240, mb: 2 },
|
|
3695
3704
|
InputProps: {
|
|
3696
|
-
startAdornment: /* @__PURE__ */
|
|
3705
|
+
startAdornment: /* @__PURE__ */ import_react10.default.createElement(import_InputAdornment2.default, { position: "start" }, /* @__PURE__ */ import_react10.default.createElement(import_icons_material7.Search, { fontSize: "small" }))
|
|
3697
3706
|
}
|
|
3698
3707
|
}
|
|
3699
3708
|
)),
|
|
3700
|
-
loading && /* @__PURE__ */
|
|
3701
|
-
!loading && !selectedTeamId && /* @__PURE__ */
|
|
3709
|
+
loading && /* @__PURE__ */ import_react10.default.createElement(EmptyState, { message: "Loading models\u2026" }),
|
|
3710
|
+
!loading && !selectedTeamId && /* @__PURE__ */ import_react10.default.createElement(
|
|
3702
3711
|
EmptyState,
|
|
3703
3712
|
{
|
|
3704
3713
|
message: "Select a team to see available models",
|
|
3705
3714
|
hint: "Each team may have a different set of models assigned"
|
|
3706
3715
|
}
|
|
3707
3716
|
),
|
|
3708
|
-
!loading && selectedTeamId && teamModels.length === 0 && /* @__PURE__ */
|
|
3717
|
+
!loading && selectedTeamId && teamModels.length === 0 && /* @__PURE__ */ import_react10.default.createElement(
|
|
3709
3718
|
EmptyState,
|
|
3710
3719
|
{
|
|
3711
3720
|
message: "No models available for this team",
|
|
3712
3721
|
hint: allModels.length === 0 ? "No models are configured in the system" : "None of the team's assigned models were found in the catalog"
|
|
3713
3722
|
}
|
|
3714
3723
|
),
|
|
3715
|
-
!loading && selectedTeamId && teamModels.length > 0 && filteredModels.length === 0 && /* @__PURE__ */
|
|
3724
|
+
!loading && selectedTeamId && teamModels.length > 0 && filteredModels.length === 0 && /* @__PURE__ */ import_react10.default.createElement(
|
|
3716
3725
|
EmptyState,
|
|
3717
3726
|
{
|
|
3718
3727
|
message: "No models match your search",
|
|
3719
3728
|
hint: "Try a different search term"
|
|
3720
3729
|
}
|
|
3721
3730
|
),
|
|
3722
|
-
!loading && filteredModels.length > 0 && /* @__PURE__ */
|
|
3731
|
+
!loading && filteredModels.length > 0 && /* @__PURE__ */ import_react10.default.createElement(import_TableContainer5.default, null, /* @__PURE__ */ import_react10.default.createElement(import_Table5.default, { size: "small", sx: dataTableSx }, /* @__PURE__ */ import_react10.default.createElement(import_TableHead5.default, null, /* @__PURE__ */ import_react10.default.createElement(import_TableRow5.default, null, /* @__PURE__ */ import_react10.default.createElement(import_TableCell5.default, { sortDirection: sortKey === "model_name" ? sortDirection : false }, /* @__PURE__ */ import_react10.default.createElement(import_TableSortLabel2.default, { active: sortKey === "model_name", direction: sortKey === "model_name" ? sortDirection : "asc", onClick: () => handleSort("model_name") }, "Model ID")), /* @__PURE__ */ import_react10.default.createElement(import_TableCell5.default, { sortDirection: sortKey === "mode" ? sortDirection : false }, /* @__PURE__ */ import_react10.default.createElement(import_TableSortLabel2.default, { active: sortKey === "mode", direction: sortKey === "mode" ? sortDirection : "asc", onClick: () => handleSort("mode") }, "Mode")), /* @__PURE__ */ import_react10.default.createElement(import_TableCell5.default, { align: "right", sortDirection: sortKey === "input_cost_per_token" ? sortDirection : false }, /* @__PURE__ */ import_react10.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_react10.default.createElement(import_TableCell5.default, { align: "right", sortDirection: sortKey === "output_cost_per_token" ? sortDirection : false }, /* @__PURE__ */ import_react10.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_react10.default.createElement(import_TableCell5.default, { align: "right", sortDirection: sortKey === "max_input_tokens" ? sortDirection : false }, /* @__PURE__ */ import_react10.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_react10.default.createElement(import_TableCell5.default, { align: "right", sortDirection: sortKey === "max_output_tokens" ? sortDirection : false }, /* @__PURE__ */ import_react10.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_react10.default.createElement(import_TableCell5.default, null, "Capabilities"))), /* @__PURE__ */ import_react10.default.createElement(import_TableBody5.default, null, filteredModels.map((m) => /* @__PURE__ */ import_react10.default.createElement(import_TableRow5.default, { key: m.model_name, hover: true }, /* @__PURE__ */ import_react10.default.createElement(import_TableCell5.default, null, /* @__PURE__ */ import_react10.default.createElement(import_Box10.default, { sx: { display: "flex", alignItems: "center", gap: 0.5 } }, /* @__PURE__ */ import_react10.default.createElement(import_Tooltip.default, { title: m.model_name, placement: "top" }, /* @__PURE__ */ import_react10.default.createElement(
|
|
3723
3732
|
"span",
|
|
3724
3733
|
{
|
|
3725
3734
|
role: "button",
|
|
@@ -3729,9 +3738,9 @@ var init_ModelsTable = __esm({
|
|
|
3729
3738
|
onKeyDown: (e) => e.key === "Enter" && handleCopyModelId(m.model_name),
|
|
3730
3739
|
style: { cursor: "pointer", display: "inline-flex" }
|
|
3731
3740
|
},
|
|
3732
|
-
/* @__PURE__ */
|
|
3733
|
-
)), /* @__PURE__ */
|
|
3734
|
-
|
|
3741
|
+
/* @__PURE__ */ import_react10.default.createElement(TagChip, { label: m.model_name, title: "Copy model ID" })
|
|
3742
|
+
)), /* @__PURE__ */ import_react10.default.createElement(import_Tooltip.default, { title: "Copy model ID" }, /* @__PURE__ */ import_react10.default.createElement(
|
|
3743
|
+
import_Box10.default,
|
|
3735
3744
|
{
|
|
3736
3745
|
component: "span",
|
|
3737
3746
|
role: "button",
|
|
@@ -3741,9 +3750,9 @@ var init_ModelsTable = __esm({
|
|
|
3741
3750
|
sx: quietIconButtonSx("accent"),
|
|
3742
3751
|
style: { cursor: "pointer", display: "inline-flex" }
|
|
3743
3752
|
},
|
|
3744
|
-
/* @__PURE__ */
|
|
3745
|
-
)))), /* @__PURE__ */
|
|
3746
|
-
|
|
3753
|
+
/* @__PURE__ */ import_react10.default.createElement(import_ContentCopy.default, { sx: { fontSize: 14 } })
|
|
3754
|
+
)))), /* @__PURE__ */ import_react10.default.createElement(import_TableCell5.default, null, /* @__PURE__ */ import_react10.default.createElement(
|
|
3755
|
+
import_Box10.default,
|
|
3747
3756
|
{
|
|
3748
3757
|
component: "span",
|
|
3749
3758
|
sx: {
|
|
@@ -3754,13 +3763,13 @@ var init_ModelsTable = __esm({
|
|
|
3754
3763
|
fontSize: 11,
|
|
3755
3764
|
fontWeight: 600,
|
|
3756
3765
|
textTransform: "capitalize",
|
|
3757
|
-
bgcolor: (0,
|
|
3766
|
+
bgcolor: (0, import_styles7.alpha)(modeColor(m.mode), 0.1),
|
|
3758
3767
|
color: modeColor(m.mode)
|
|
3759
3768
|
}
|
|
3760
3769
|
},
|
|
3761
3770
|
m.mode
|
|
3762
|
-
)), /* @__PURE__ */
|
|
3763
|
-
/* @__PURE__ */
|
|
3771
|
+
)), /* @__PURE__ */ import_react10.default.createElement(import_TableCell5.default, { align: "right" }, /* @__PURE__ */ import_react10.default.createElement(import_Typography10.default, { variant: "body2", sx: { fontVariantNumeric: "tabular-nums" } }, fmtCostPerToken(m.input_cost_per_token))), /* @__PURE__ */ import_react10.default.createElement(import_TableCell5.default, { align: "right" }, /* @__PURE__ */ import_react10.default.createElement(import_Typography10.default, { variant: "body2", sx: { fontVariantNumeric: "tabular-nums" } }, fmtCostPerToken(m.output_cost_per_token))), /* @__PURE__ */ import_react10.default.createElement(import_TableCell5.default, { align: "right" }, /* @__PURE__ */ import_react10.default.createElement(import_Typography10.default, { variant: "body2", sx: { fontVariantNumeric: "tabular-nums" } }, fmtTokens(m.max_input_tokens))), /* @__PURE__ */ import_react10.default.createElement(import_TableCell5.default, { align: "right" }, /* @__PURE__ */ import_react10.default.createElement(import_Typography10.default, { variant: "body2", sx: { fontVariantNumeric: "tabular-nums" } }, fmtTokens(m.max_output_tokens))), /* @__PURE__ */ import_react10.default.createElement(import_TableCell5.default, null, /* @__PURE__ */ import_react10.default.createElement(import_Box10.default, { sx: { display: "flex", gap: 0.5, flexWrap: "wrap" } }, m.supports_function_calling && /* @__PURE__ */ import_react10.default.createElement(TagChip, { label: "Fn", title: "Function calling" }), m.supports_vision && /* @__PURE__ */ import_react10.default.createElement(TagChip, { label: "\u{1F441}", title: "Vision" })))))))),
|
|
3772
|
+
/* @__PURE__ */ import_react10.default.createElement(
|
|
3764
3773
|
import_Snackbar.default,
|
|
3765
3774
|
{
|
|
3766
3775
|
open: copiedSnackbar,
|
|
@@ -3768,7 +3777,7 @@ var init_ModelsTable = __esm({
|
|
|
3768
3777
|
onClose: () => setCopiedSnackbar(false),
|
|
3769
3778
|
anchorOrigin: { vertical: "bottom", horizontal: "center" }
|
|
3770
3779
|
},
|
|
3771
|
-
/* @__PURE__ */
|
|
3780
|
+
/* @__PURE__ */ import_react10.default.createElement(import_Alert4.default, { severity: "success", variant: "filled", sx: { fontSize: 13 } }, "Model ID copied to clipboard")
|
|
3772
3781
|
)
|
|
3773
3782
|
);
|
|
3774
3783
|
};
|
|
@@ -3796,10 +3805,10 @@ function formatDateTime(iso) {
|
|
|
3796
3805
|
}
|
|
3797
3806
|
function renderAuditLogBody(loading, entries) {
|
|
3798
3807
|
if (loading) {
|
|
3799
|
-
return /* @__PURE__ */
|
|
3808
|
+
return /* @__PURE__ */ import_react11.default.createElement(import_TableRow6.default, null, /* @__PURE__ */ import_react11.default.createElement(import_TableCell6.default, { colSpan: 6, sx: { py: 2 } }, /* @__PURE__ */ import_react11.default.createElement(import_Skeleton5.default, { variant: "rounded", height: 160 })));
|
|
3800
3809
|
}
|
|
3801
3810
|
if (entries.length === 0) {
|
|
3802
|
-
return /* @__PURE__ */
|
|
3811
|
+
return /* @__PURE__ */ import_react11.default.createElement(import_TableRow6.default, null, /* @__PURE__ */ import_react11.default.createElement(import_TableCell6.default, { colSpan: 6 }, /* @__PURE__ */ import_react11.default.createElement(
|
|
3803
3812
|
EmptyState,
|
|
3804
3813
|
{
|
|
3805
3814
|
message: "No audit events found",
|
|
@@ -3807,15 +3816,15 @@ function renderAuditLogBody(loading, entries) {
|
|
|
3807
3816
|
}
|
|
3808
3817
|
)));
|
|
3809
3818
|
}
|
|
3810
|
-
return entries.map((entry) => /* @__PURE__ */
|
|
3819
|
+
return entries.map((entry) => /* @__PURE__ */ import_react11.default.createElement(DetailRow, { key: entry.id, entry }));
|
|
3811
3820
|
}
|
|
3812
|
-
var
|
|
3821
|
+
var import_react11, 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;
|
|
3813
3822
|
var init_AuditLog = __esm({
|
|
3814
3823
|
"src/components/AuditLog.tsx"() {
|
|
3815
3824
|
"use strict";
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
3825
|
+
import_react11 = __toESM(require("react"));
|
|
3826
|
+
import_Box11 = __toESM(require("@mui/material/Box"));
|
|
3827
|
+
import_Typography11 = __toESM(require("@mui/material/Typography"));
|
|
3819
3828
|
import_Table6 = __toESM(require("@mui/material/Table"));
|
|
3820
3829
|
import_TableBody6 = __toESM(require("@mui/material/TableBody"));
|
|
3821
3830
|
import_TableCell6 = __toESM(require("@mui/material/TableCell"));
|
|
@@ -3829,7 +3838,7 @@ var init_AuditLog = __esm({
|
|
|
3829
3838
|
import_Collapse3 = __toESM(require("@mui/material/Collapse"));
|
|
3830
3839
|
import_IconButton6 = __toESM(require("@mui/material/IconButton"));
|
|
3831
3840
|
import_Alert5 = __toESM(require("@mui/material/Alert"));
|
|
3832
|
-
|
|
3841
|
+
import_styles8 = require("@mui/material/styles");
|
|
3833
3842
|
import_icons_material8 = require("@mui/icons-material");
|
|
3834
3843
|
import_react_use2 = require("react-use");
|
|
3835
3844
|
init_ui();
|
|
@@ -3848,9 +3857,9 @@ var init_AuditLog = __esm({
|
|
|
3848
3857
|
LiteLLM_UserTable: "User"
|
|
3849
3858
|
};
|
|
3850
3859
|
DetailRow = ({ entry }) => {
|
|
3851
|
-
const [open, setOpen] = (0,
|
|
3860
|
+
const [open, setOpen] = (0, import_react11.useState)(false);
|
|
3852
3861
|
const hasDetail = entry.before_value || entry.updated_values;
|
|
3853
|
-
return /* @__PURE__ */
|
|
3862
|
+
return /* @__PURE__ */ import_react11.default.createElement(import_react11.default.Fragment, null, /* @__PURE__ */ import_react11.default.createElement(import_TableRow6.default, null, /* @__PURE__ */ import_react11.default.createElement(import_TableCell6.default, { sx: { width: 40, pr: 0 } }, hasDetail && /* @__PURE__ */ import_react11.default.createElement(
|
|
3854
3863
|
import_IconButton6.default,
|
|
3855
3864
|
{
|
|
3856
3865
|
size: "small",
|
|
@@ -3863,9 +3872,9 @@ var init_AuditLog = __esm({
|
|
|
3863
3872
|
transition: theme.transitions.create("transform")
|
|
3864
3873
|
})
|
|
3865
3874
|
},
|
|
3866
|
-
/* @__PURE__ */
|
|
3867
|
-
)), /* @__PURE__ */
|
|
3868
|
-
|
|
3875
|
+
/* @__PURE__ */ import_react11.default.createElement(import_icons_material8.KeyboardArrowDown, { fontSize: "small" })
|
|
3876
|
+
)), /* @__PURE__ */ import_react11.default.createElement(import_TableCell6.default, { sx: { whiteSpace: "nowrap" } }, /* @__PURE__ */ import_react11.default.createElement(import_Typography11.default, { variant: "body2", color: "text.secondary" }, formatDateTime(entry.updated_at))), /* @__PURE__ */ import_react11.default.createElement(import_TableCell6.default, null, entry.action && /* @__PURE__ */ import_react11.default.createElement(StatusPill, { label: entry.action, tone: actionTone(entry.action) })), /* @__PURE__ */ import_react11.default.createElement(import_TableCell6.default, null, /* @__PURE__ */ import_react11.default.createElement(import_Typography11.default, { variant: "body2" }, prettyTableName(entry.table_name))), /* @__PURE__ */ import_react11.default.createElement(import_TableCell6.default, null, /* @__PURE__ */ import_react11.default.createElement(
|
|
3877
|
+
import_Typography11.default,
|
|
3869
3878
|
{
|
|
3870
3879
|
variant: "body2",
|
|
3871
3880
|
component: "code",
|
|
@@ -3874,8 +3883,8 @@ var init_AuditLog = __esm({
|
|
|
3874
3883
|
sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 12 }
|
|
3875
3884
|
},
|
|
3876
3885
|
entry.object_id ? entry.object_id.slice(0, 20) + (entry.object_id.length > 20 ? "\u2026" : "") : "\u2014"
|
|
3877
|
-
)), /* @__PURE__ */
|
|
3878
|
-
|
|
3886
|
+
)), /* @__PURE__ */ import_react11.default.createElement(import_TableCell6.default, null, entry.changed_by ?? "\u2014")), hasDetail && /* @__PURE__ */ import_react11.default.createElement(import_TableRow6.default, { sx: { "&&:hover": { bgcolor: "transparent" } } }, /* @__PURE__ */ import_react11.default.createElement(import_TableCell6.default, { colSpan: 6, sx: { "&&": { py: 0, border: 0 } } }, /* @__PURE__ */ import_react11.default.createElement(import_Collapse3.default, { in: open, unmountOnExit: true }, /* @__PURE__ */ import_react11.default.createElement(
|
|
3887
|
+
import_Box11.default,
|
|
3879
3888
|
{
|
|
3880
3889
|
display: "flex",
|
|
3881
3890
|
gap: 2,
|
|
@@ -3884,11 +3893,11 @@ var init_AuditLog = __esm({
|
|
|
3884
3893
|
p: 2,
|
|
3885
3894
|
mb: 1.5,
|
|
3886
3895
|
borderRadius: 1.5,
|
|
3887
|
-
bgcolor: (0,
|
|
3896
|
+
bgcolor: (0, import_styles8.alpha)(theme.palette.text.primary, theme.palette.mode === "dark" ? 0.05 : 0.03)
|
|
3888
3897
|
})
|
|
3889
3898
|
},
|
|
3890
|
-
entry.before_value && /* @__PURE__ */
|
|
3891
|
-
|
|
3899
|
+
entry.before_value && /* @__PURE__ */ import_react11.default.createElement(import_Box11.default, { flex: 1, minWidth: 200 }, /* @__PURE__ */ import_react11.default.createElement(
|
|
3900
|
+
import_Typography11.default,
|
|
3892
3901
|
{
|
|
3893
3902
|
variant: "caption",
|
|
3894
3903
|
color: "text.secondary",
|
|
@@ -3897,9 +3906,9 @@ var init_AuditLog = __esm({
|
|
|
3897
3906
|
sx: { fontSize: 10.5, fontWeight: 700, letterSpacing: "0.07em", textTransform: "uppercase" }
|
|
3898
3907
|
},
|
|
3899
3908
|
"Before"
|
|
3900
|
-
), /* @__PURE__ */
|
|
3901
|
-
entry.updated_values && /* @__PURE__ */
|
|
3902
|
-
|
|
3909
|
+
), /* @__PURE__ */ import_react11.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))),
|
|
3910
|
+
entry.updated_values && /* @__PURE__ */ import_react11.default.createElement(import_Box11.default, { flex: 1, minWidth: 200 }, /* @__PURE__ */ import_react11.default.createElement(
|
|
3911
|
+
import_Typography11.default,
|
|
3903
3912
|
{
|
|
3904
3913
|
variant: "caption",
|
|
3905
3914
|
color: "text.secondary",
|
|
@@ -3908,14 +3917,14 @@ var init_AuditLog = __esm({
|
|
|
3908
3917
|
sx: { fontSize: 10.5, fontWeight: 700, letterSpacing: "0.07em", textTransform: "uppercase" }
|
|
3909
3918
|
},
|
|
3910
3919
|
"After"
|
|
3911
|
-
), /* @__PURE__ */
|
|
3920
|
+
), /* @__PURE__ */ import_react11.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)))
|
|
3912
3921
|
)))));
|
|
3913
3922
|
};
|
|
3914
3923
|
AuditLog = ({ api }) => {
|
|
3915
|
-
const [page, setPage] = (0,
|
|
3916
|
-
const [pageSize, setPageSize] = (0,
|
|
3917
|
-
const [filters, setFilters] = (0,
|
|
3918
|
-
const fetchParams = (0,
|
|
3924
|
+
const [page, setPage] = (0, import_react11.useState)(0);
|
|
3925
|
+
const [pageSize, setPageSize] = (0, import_react11.useState)(25);
|
|
3926
|
+
const [filters, setFilters] = (0, import_react11.useState)({});
|
|
3927
|
+
const fetchParams = (0, import_react11.useMemo)(
|
|
3919
3928
|
() => ({ page: page + 1, page_size: pageSize, ...filters }),
|
|
3920
3929
|
[page, pageSize, filters]
|
|
3921
3930
|
);
|
|
@@ -3925,13 +3934,13 @@ var init_AuditLog = __esm({
|
|
|
3925
3934
|
);
|
|
3926
3935
|
const entries = value?.audit_logs ?? [];
|
|
3927
3936
|
const total = value?.total ?? 0;
|
|
3928
|
-
return /* @__PURE__ */
|
|
3937
|
+
return /* @__PURE__ */ import_react11.default.createElement(
|
|
3929
3938
|
SectionCard,
|
|
3930
3939
|
{
|
|
3931
3940
|
title: "Audit Log",
|
|
3932
3941
|
subtitle: loading ? "Loading\u2026" : `${total.toLocaleString()} event${total === 1 ? "" : "s"}`,
|
|
3933
3942
|
flush: true,
|
|
3934
|
-
actions: /* @__PURE__ */
|
|
3943
|
+
actions: /* @__PURE__ */ import_react11.default.createElement(import_react11.default.Fragment, null, /* @__PURE__ */ import_react11.default.createElement(
|
|
3935
3944
|
import_TextField6.default,
|
|
3936
3945
|
{
|
|
3937
3946
|
size: "small",
|
|
@@ -3944,12 +3953,12 @@ var init_AuditLog = __esm({
|
|
|
3944
3953
|
},
|
|
3945
3954
|
sx: { minWidth: 150 }
|
|
3946
3955
|
},
|
|
3947
|
-
/* @__PURE__ */
|
|
3948
|
-
/* @__PURE__ */
|
|
3949
|
-
/* @__PURE__ */
|
|
3950
|
-
/* @__PURE__ */
|
|
3951
|
-
/* @__PURE__ */
|
|
3952
|
-
), /* @__PURE__ */
|
|
3956
|
+
/* @__PURE__ */ import_react11.default.createElement(import_MenuItem5.default, { value: "" }, "All actions"),
|
|
3957
|
+
/* @__PURE__ */ import_react11.default.createElement(import_MenuItem5.default, { value: "created" }, "Created"),
|
|
3958
|
+
/* @__PURE__ */ import_react11.default.createElement(import_MenuItem5.default, { value: "updated" }, "Updated"),
|
|
3959
|
+
/* @__PURE__ */ import_react11.default.createElement(import_MenuItem5.default, { value: "deleted" }, "Deleted"),
|
|
3960
|
+
/* @__PURE__ */ import_react11.default.createElement(import_MenuItem5.default, { value: "blocked" }, "Blocked")
|
|
3961
|
+
), /* @__PURE__ */ import_react11.default.createElement(
|
|
3953
3962
|
import_TextField6.default,
|
|
3954
3963
|
{
|
|
3955
3964
|
size: "small",
|
|
@@ -3962,13 +3971,13 @@ var init_AuditLog = __esm({
|
|
|
3962
3971
|
},
|
|
3963
3972
|
sx: { minWidth: 150 }
|
|
3964
3973
|
},
|
|
3965
|
-
/* @__PURE__ */
|
|
3966
|
-
/* @__PURE__ */
|
|
3967
|
-
/* @__PURE__ */
|
|
3968
|
-
/* @__PURE__ */
|
|
3969
|
-
/* @__PURE__ */
|
|
3970
|
-
/* @__PURE__ */
|
|
3971
|
-
), /* @__PURE__ */
|
|
3974
|
+
/* @__PURE__ */ import_react11.default.createElement(import_MenuItem5.default, { value: "" }, "All tables"),
|
|
3975
|
+
/* @__PURE__ */ import_react11.default.createElement(import_MenuItem5.default, { value: "LiteLLM_VerificationToken" }, "Key"),
|
|
3976
|
+
/* @__PURE__ */ import_react11.default.createElement(import_MenuItem5.default, { value: "LiteLLM_TeamTable" }, "Team"),
|
|
3977
|
+
/* @__PURE__ */ import_react11.default.createElement(import_MenuItem5.default, { value: "LiteLLM_TeamMembership" }, "Team member"),
|
|
3978
|
+
/* @__PURE__ */ import_react11.default.createElement(import_MenuItem5.default, { value: "LiteLLM_ObjectPermissionTable" }, "Team access (KB / MCP)"),
|
|
3979
|
+
/* @__PURE__ */ import_react11.default.createElement(import_MenuItem5.default, { value: "LiteLLM_UserTable" }, "User")
|
|
3980
|
+
), /* @__PURE__ */ import_react11.default.createElement(
|
|
3972
3981
|
import_TextField6.default,
|
|
3973
3982
|
{
|
|
3974
3983
|
size: "small",
|
|
@@ -3982,9 +3991,9 @@ var init_AuditLog = __esm({
|
|
|
3982
3991
|
}
|
|
3983
3992
|
))
|
|
3984
3993
|
},
|
|
3985
|
-
error && /* @__PURE__ */
|
|
3986
|
-
/* @__PURE__ */
|
|
3987
|
-
/* @__PURE__ */
|
|
3994
|
+
error && /* @__PURE__ */ import_react11.default.createElement(import_Box11.default, { px: 2.5, pb: 2 }, /* @__PURE__ */ import_react11.default.createElement(import_Alert5.default, { severity: "error" }, error.message)),
|
|
3995
|
+
/* @__PURE__ */ import_react11.default.createElement(import_TableContainer6.default, null, /* @__PURE__ */ import_react11.default.createElement(import_Table6.default, { size: "small", sx: dataTableSx }, /* @__PURE__ */ import_react11.default.createElement(import_TableHead6.default, null, /* @__PURE__ */ import_react11.default.createElement(import_TableRow6.default, null, /* @__PURE__ */ import_react11.default.createElement(import_TableCell6.default, { sx: { width: 40 } }), /* @__PURE__ */ import_react11.default.createElement(import_TableCell6.default, null, "Time"), /* @__PURE__ */ import_react11.default.createElement(import_TableCell6.default, null, "Action"), /* @__PURE__ */ import_react11.default.createElement(import_TableCell6.default, null, "Table"), /* @__PURE__ */ import_react11.default.createElement(import_TableCell6.default, null, "Object ID"), /* @__PURE__ */ import_react11.default.createElement(import_TableCell6.default, null, "Changed By"))), /* @__PURE__ */ import_react11.default.createElement(import_TableBody6.default, null, renderAuditLogBody(loading, entries)))),
|
|
3996
|
+
/* @__PURE__ */ import_react11.default.createElement(
|
|
3988
3997
|
import_TablePagination.default,
|
|
3989
3998
|
{
|
|
3990
3999
|
component: "div",
|
|
@@ -4053,17 +4062,17 @@ function initDateRange() {
|
|
|
4053
4062
|
else start.setDate(start.getDate() - 7);
|
|
4054
4063
|
return { start, end };
|
|
4055
4064
|
}
|
|
4056
|
-
var
|
|
4065
|
+
var import_react12, 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;
|
|
4057
4066
|
var init_LiteLLMPage = __esm({
|
|
4058
4067
|
"src/components/LiteLLMPage.tsx"() {
|
|
4059
4068
|
"use strict";
|
|
4060
|
-
|
|
4061
|
-
|
|
4069
|
+
import_react12 = __toESM(require("react"));
|
|
4070
|
+
import_Box12 = __toESM(require("@mui/material/Box"));
|
|
4062
4071
|
import_Snackbar2 = __toESM(require("@mui/material/Snackbar"));
|
|
4063
4072
|
import_Alert6 = __toESM(require("@mui/material/Alert"));
|
|
4064
4073
|
import_CircularProgress6 = __toESM(require("@mui/material/CircularProgress"));
|
|
4065
|
-
|
|
4066
|
-
|
|
4074
|
+
import_Typography12 = __toESM(require("@mui/material/Typography"));
|
|
4075
|
+
import_Paper8 = __toESM(require("@mui/material/Paper"));
|
|
4067
4076
|
import_Tabs2 = __toESM(require("@mui/material/Tabs"));
|
|
4068
4077
|
import_Tab2 = __toESM(require("@mui/material/Tab"));
|
|
4069
4078
|
import_Grid2 = __toESM(require("@mui/material/Grid"));
|
|
@@ -4073,7 +4082,7 @@ var init_LiteLLMPage = __esm({
|
|
|
4073
4082
|
import_plugin_permission_react = require("@backstage/plugin-permission-react");
|
|
4074
4083
|
init_DashboardHeader();
|
|
4075
4084
|
init_KeysTable();
|
|
4076
|
-
|
|
4085
|
+
init_KeyFormDialog();
|
|
4077
4086
|
init_ManageTeamDialog();
|
|
4078
4087
|
init_UsageStats();
|
|
4079
4088
|
init_LiteLLMBudgetWidget();
|
|
@@ -4087,8 +4096,8 @@ var init_LiteLLMPage = __esm({
|
|
|
4087
4096
|
isPageTab = (v) => !!v && PAGE_TABS.includes(v);
|
|
4088
4097
|
LiteLLMPage = () => {
|
|
4089
4098
|
const api = (0, import_core_plugin_api4.useApi)(liteLlmApiRef);
|
|
4090
|
-
const [dateRange, setDateRange] = (0,
|
|
4091
|
-
const [currentPreset, setCurrentPreset] = (0,
|
|
4099
|
+
const [dateRange, setDateRange] = (0, import_react12.useState)(initDateRange);
|
|
4100
|
+
const [currentPreset, setCurrentPreset] = (0, import_react12.useState)(() => {
|
|
4092
4101
|
try {
|
|
4093
4102
|
return localStorage.getItem(PERIOD_LS_KEY2) ?? "7d";
|
|
4094
4103
|
} catch {
|
|
@@ -4096,11 +4105,26 @@ var init_LiteLLMPage = __esm({
|
|
|
4096
4105
|
}
|
|
4097
4106
|
});
|
|
4098
4107
|
const [searchParams, setSearchParams] = (0, import_react_router_dom.useSearchParams)();
|
|
4099
|
-
const [activeTab, setActiveTab] = (0,
|
|
4108
|
+
const [activeTab, setActiveTab] = (0, import_react12.useState)(() => {
|
|
4100
4109
|
const t = searchParams.get("tab");
|
|
4101
4110
|
return isPageTab(t) ? t : "overview";
|
|
4102
4111
|
});
|
|
4103
|
-
const
|
|
4112
|
+
const [generateDialogOpen, setGenerateDialogOpen] = (0, import_react12.useState)(
|
|
4113
|
+
() => searchParams.get("generate") === "1"
|
|
4114
|
+
);
|
|
4115
|
+
(0, import_react12.useEffect)(() => {
|
|
4116
|
+
if (searchParams.get("generate") !== "1") return;
|
|
4117
|
+
setGenerateDialogOpen(true);
|
|
4118
|
+
setSearchParams(
|
|
4119
|
+
(prev) => {
|
|
4120
|
+
const next = new URLSearchParams(prev);
|
|
4121
|
+
next.delete("generate");
|
|
4122
|
+
return next;
|
|
4123
|
+
},
|
|
4124
|
+
{ replace: true }
|
|
4125
|
+
);
|
|
4126
|
+
}, [searchParams, setSearchParams]);
|
|
4127
|
+
const selectTab = (0, import_react12.useCallback)(
|
|
4104
4128
|
(tab) => {
|
|
4105
4129
|
setActiveTab(tab);
|
|
4106
4130
|
setSearchParams(
|
|
@@ -4114,11 +4138,12 @@ var init_LiteLLMPage = __esm({
|
|
|
4114
4138
|
},
|
|
4115
4139
|
[setSearchParams]
|
|
4116
4140
|
);
|
|
4117
|
-
const [snackbar, setSnackbar] = (0,
|
|
4118
|
-
const [
|
|
4119
|
-
const [
|
|
4120
|
-
const
|
|
4121
|
-
const [
|
|
4141
|
+
const [snackbar, setSnackbar] = (0, import_react12.useState)(null);
|
|
4142
|
+
const [manageTeam, setManageTeam] = (0, import_react12.useState)(null);
|
|
4143
|
+
const [keyToEdit, setKeyToEdit] = (0, import_react12.useState)(null);
|
|
4144
|
+
const keyFormOpen = generateDialogOpen || !!keyToEdit;
|
|
4145
|
+
const [teamUsageCache, setTeamUsageCache] = (0, import_react12.useState)({});
|
|
4146
|
+
const [teamUsageLoading, setTeamUsageLoading] = (0, import_react12.useState)({});
|
|
4122
4147
|
const { value: userInfo, loading: userLoading, error: userError } = (0, import_react_use3.useAsync)(
|
|
4123
4148
|
() => api.getUserInfo(),
|
|
4124
4149
|
[api]
|
|
@@ -4162,7 +4187,7 @@ var init_LiteLLMPage = __esm({
|
|
|
4162
4187
|
async () => objectPermsEnabled ? api.getMcpServers().catch(() => []) : [],
|
|
4163
4188
|
[api, objectPermsEnabled]
|
|
4164
4189
|
);
|
|
4165
|
-
const teams = (0,
|
|
4190
|
+
const teams = (0, import_react12.useMemo)(() => {
|
|
4166
4191
|
if (!allTeams?.length) return [];
|
|
4167
4192
|
if (!userInfo) return allTeams;
|
|
4168
4193
|
const userId = userInfo.user_id;
|
|
@@ -4179,7 +4204,7 @@ var init_LiteLLMPage = __esm({
|
|
|
4179
4204
|
const endDate = dateRange.end.toISOString().split("T")[0];
|
|
4180
4205
|
return api.getUsage(startDate, endDate);
|
|
4181
4206
|
}, [api, dateRange]);
|
|
4182
|
-
const handleDateRangeChange = (0,
|
|
4207
|
+
const handleDateRangeChange = (0, import_react12.useCallback)((range, preset) => {
|
|
4183
4208
|
setDateRange(range);
|
|
4184
4209
|
if (preset) {
|
|
4185
4210
|
setCurrentPreset(preset);
|
|
@@ -4187,7 +4212,7 @@ var init_LiteLLMPage = __esm({
|
|
|
4187
4212
|
setTeamUsageCache({});
|
|
4188
4213
|
setTeamUsageLoading({});
|
|
4189
4214
|
}, [setDateRange, setCurrentPreset]);
|
|
4190
|
-
const loadTeamUsage = (0,
|
|
4215
|
+
const loadTeamUsage = (0, import_react12.useCallback)(async (teamId) => {
|
|
4191
4216
|
if (teamUsageCache[teamId] !== void 0 || teamUsageLoading[teamId]) return;
|
|
4192
4217
|
setTeamUsageLoading((prev) => ({ ...prev, [teamId]: true }));
|
|
4193
4218
|
try {
|
|
@@ -4201,7 +4226,7 @@ var init_LiteLLMPage = __esm({
|
|
|
4201
4226
|
setTeamUsageLoading((prev) => ({ ...prev, [teamId]: false }));
|
|
4202
4227
|
}
|
|
4203
4228
|
}, [api, dateRange, teamUsageCache, teamUsageLoading]);
|
|
4204
|
-
const allowedModels = (0,
|
|
4229
|
+
const allowedModels = (0, import_react12.useMemo)(() => {
|
|
4205
4230
|
if (!allModels?.length) return [];
|
|
4206
4231
|
const userModels = userInfo?.models;
|
|
4207
4232
|
const teamModels = teams?.flatMap((t) => t.models ?? []);
|
|
@@ -4214,7 +4239,7 @@ var init_LiteLLMPage = __esm({
|
|
|
4214
4239
|
]);
|
|
4215
4240
|
return allModels.filter((m) => allowed.has(m.model_name));
|
|
4216
4241
|
}, [allModels, userInfo, teams]);
|
|
4217
|
-
const handleGenerateKey = (0,
|
|
4242
|
+
const handleGenerateKey = (0, import_react12.useCallback)(
|
|
4218
4243
|
async (request) => {
|
|
4219
4244
|
try {
|
|
4220
4245
|
const response = await api.generateKey(request);
|
|
@@ -4228,7 +4253,7 @@ var init_LiteLLMPage = __esm({
|
|
|
4228
4253
|
},
|
|
4229
4254
|
[api, refreshKeys]
|
|
4230
4255
|
);
|
|
4231
|
-
const handleUpdateKey = (0,
|
|
4256
|
+
const handleUpdateKey = (0, import_react12.useCallback)(
|
|
4232
4257
|
async (keyId, request) => {
|
|
4233
4258
|
try {
|
|
4234
4259
|
await api.updateKey(keyId, request);
|
|
@@ -4241,7 +4266,7 @@ var init_LiteLLMPage = __esm({
|
|
|
4241
4266
|
},
|
|
4242
4267
|
[api, refreshKeys]
|
|
4243
4268
|
);
|
|
4244
|
-
const handleBlockKey = (0,
|
|
4269
|
+
const handleBlockKey = (0, import_react12.useCallback)(
|
|
4245
4270
|
async (keyId) => {
|
|
4246
4271
|
try {
|
|
4247
4272
|
await api.blockKey(keyId);
|
|
@@ -4253,7 +4278,7 @@ var init_LiteLLMPage = __esm({
|
|
|
4253
4278
|
},
|
|
4254
4279
|
[api, refreshKeys]
|
|
4255
4280
|
);
|
|
4256
|
-
const handleUnblockKey = (0,
|
|
4281
|
+
const handleUnblockKey = (0, import_react12.useCallback)(
|
|
4257
4282
|
async (keyId) => {
|
|
4258
4283
|
try {
|
|
4259
4284
|
await api.unblockKey(keyId);
|
|
@@ -4265,7 +4290,7 @@ var init_LiteLLMPage = __esm({
|
|
|
4265
4290
|
},
|
|
4266
4291
|
[api, refreshKeys]
|
|
4267
4292
|
);
|
|
4268
|
-
const handleResetKeySpend = (0,
|
|
4293
|
+
const handleResetKeySpend = (0, import_react12.useCallback)(
|
|
4269
4294
|
async (keyId) => {
|
|
4270
4295
|
try {
|
|
4271
4296
|
await api.resetKeySpend(keyId);
|
|
@@ -4277,7 +4302,7 @@ var init_LiteLLMPage = __esm({
|
|
|
4277
4302
|
},
|
|
4278
4303
|
[api, refreshKeys]
|
|
4279
4304
|
);
|
|
4280
|
-
const handleDeleteKey = (0,
|
|
4305
|
+
const handleDeleteKey = (0, import_react12.useCallback)(
|
|
4281
4306
|
async (keyId) => {
|
|
4282
4307
|
try {
|
|
4283
4308
|
await api.deleteKey(keyId);
|
|
@@ -4294,7 +4319,7 @@ var init_LiteLLMPage = __esm({
|
|
|
4294
4319
|
},
|
|
4295
4320
|
[api, refreshKeys]
|
|
4296
4321
|
);
|
|
4297
|
-
const handlePruneExpiredKeys = (0,
|
|
4322
|
+
const handlePruneExpiredKeys = (0, import_react12.useCallback)(
|
|
4298
4323
|
async () => {
|
|
4299
4324
|
try {
|
|
4300
4325
|
const result = await api.pruneExpiredKeys();
|
|
@@ -4309,14 +4334,14 @@ var init_LiteLLMPage = __esm({
|
|
|
4309
4334
|
);
|
|
4310
4335
|
const isInitialLoading = userLoading && !userInfo;
|
|
4311
4336
|
if (isInitialLoading) {
|
|
4312
|
-
return /* @__PURE__ */
|
|
4337
|
+
return /* @__PURE__ */ import_react12.default.createElement(import_Box12.default, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: "50vh" }, /* @__PURE__ */ import_react12.default.createElement(import_CircularProgress6.default, null));
|
|
4313
4338
|
}
|
|
4314
4339
|
if (userError || !userInfo) {
|
|
4315
4340
|
const isProvisioningEnabled = userError?.body?.provisioning === true;
|
|
4316
4341
|
const hint = userError?.body?.hint;
|
|
4317
|
-
return /* @__PURE__ */
|
|
4342
|
+
return /* @__PURE__ */ import_react12.default.createElement(import_Box12.default, { p: 3 }, /* @__PURE__ */ import_react12.default.createElement(import_Paper8.default, { sx: { p: 3 } }, /* @__PURE__ */ import_react12.default.createElement(import_Typography12.default, { variant: "h6", gutterBottom: true }, "Account not provisioned"), /* @__PURE__ */ import_react12.default.createElement(import_Typography12.default, { color: "text.secondary", paragraph: true }, "Your Backstage account is not linked to a LiteLLM user."), hint ? /* @__PURE__ */ import_react12.default.createElement(import_Typography12.default, { variant: "body2", color: "text.secondary" }, hint) : /* @__PURE__ */ import_react12.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.")));
|
|
4318
4343
|
}
|
|
4319
|
-
const pageTabs = /* @__PURE__ */
|
|
4344
|
+
const pageTabs = /* @__PURE__ */ import_react12.default.createElement(
|
|
4320
4345
|
import_Tabs2.default,
|
|
4321
4346
|
{
|
|
4322
4347
|
value: activeTab,
|
|
@@ -4329,13 +4354,13 @@ var init_LiteLLMPage = __esm({
|
|
|
4329
4354
|
'& [class*="MuiTab-root"]': { minHeight: 44, textTransform: "none", fontSize: 14 }
|
|
4330
4355
|
}
|
|
4331
4356
|
},
|
|
4332
|
-
/* @__PURE__ */
|
|
4333
|
-
/* @__PURE__ */
|
|
4334
|
-
/* @__PURE__ */
|
|
4335
|
-
/* @__PURE__ */
|
|
4336
|
-
userInfo.can_view_audit && /* @__PURE__ */
|
|
4357
|
+
/* @__PURE__ */ import_react12.default.createElement(import_Tab2.default, { label: "Overview", value: "overview" }),
|
|
4358
|
+
/* @__PURE__ */ import_react12.default.createElement(import_Tab2.default, { label: "Keys", value: "keys" }),
|
|
4359
|
+
/* @__PURE__ */ import_react12.default.createElement(import_Tab2.default, { label: "Teams", value: "teams" }),
|
|
4360
|
+
/* @__PURE__ */ import_react12.default.createElement(import_Tab2.default, { label: "Models", value: "models" }),
|
|
4361
|
+
userInfo.can_view_audit && /* @__PURE__ */ import_react12.default.createElement(import_Tab2.default, { label: "Audit Log", value: "audit" })
|
|
4337
4362
|
);
|
|
4338
|
-
return /* @__PURE__ */
|
|
4363
|
+
return /* @__PURE__ */ import_react12.default.createElement(import_Box12.default, { sx: { p: 3, display: "flex", flexDirection: "column", gap: 2 } }, /* @__PURE__ */ import_react12.default.createElement(
|
|
4339
4364
|
DashboardHeader,
|
|
4340
4365
|
{
|
|
4341
4366
|
userInfo,
|
|
@@ -4345,7 +4370,7 @@ var init_LiteLLMPage = __esm({
|
|
|
4345
4370
|
onGenerateKeyClick: () => setGenerateDialogOpen(true),
|
|
4346
4371
|
tabs: pageTabs
|
|
4347
4372
|
}
|
|
4348
|
-
), activeTab === "overview" && /* @__PURE__ */
|
|
4373
|
+
), activeTab === "overview" && /* @__PURE__ */ import_react12.default.createElement(import_Grid2.default, { container: true, spacing: 2, alignItems: "flex-start" }, /* @__PURE__ */ import_react12.default.createElement(import_Grid2.default, { item: true, xs: 12, lg: 8 }, /* @__PURE__ */ import_react12.default.createElement(
|
|
4349
4374
|
UsageStats,
|
|
4350
4375
|
{
|
|
4351
4376
|
usage: usage ?? null,
|
|
@@ -4356,17 +4381,15 @@ var init_LiteLLMPage = __esm({
|
|
|
4356
4381
|
loading: usageLoading,
|
|
4357
4382
|
userInfo
|
|
4358
4383
|
}
|
|
4359
|
-
)), /* @__PURE__ */
|
|
4384
|
+
)), /* @__PURE__ */ import_react12.default.createElement(import_Grid2.default, { item: true, xs: 12, lg: 4 }, /* @__PURE__ */ import_react12.default.createElement(LiteLLMBudgetWidget, { compact: true, collapsible: true }))), activeTab === "keys" && /* @__PURE__ */ import_react12.default.createElement(
|
|
4360
4385
|
KeysTable,
|
|
4361
4386
|
{
|
|
4362
4387
|
keys: keys ?? [],
|
|
4363
|
-
models: allowedModels,
|
|
4364
4388
|
loading: keysLoading || modelsLoading,
|
|
4365
4389
|
onGenerateKeyClick: () => setGenerateDialogOpen(true),
|
|
4366
|
-
|
|
4390
|
+
onEditKey: setKeyToEdit,
|
|
4367
4391
|
onBlockKey: handleBlockKey,
|
|
4368
4392
|
onUnblockKey: handleUnblockKey,
|
|
4369
|
-
onResetKeySpend: handleResetKeySpend,
|
|
4370
4393
|
onDeleteKey: handleDeleteKey,
|
|
4371
4394
|
onPruneExpiredKeys: handlePruneExpiredKeys
|
|
4372
4395
|
}
|
|
@@ -4375,7 +4398,7 @@ var init_LiteLLMPage = __esm({
|
|
|
4375
4398
|
for (const t of managedTeams ?? []) teamsById.set(t.team_id, t);
|
|
4376
4399
|
for (const t of teams ?? []) teamsById.set(t.team_id, t);
|
|
4377
4400
|
const visibleTeams = Array.from(teamsById.values());
|
|
4378
|
-
return /* @__PURE__ */
|
|
4401
|
+
return /* @__PURE__ */ import_react12.default.createElement(
|
|
4379
4402
|
TeamUsage,
|
|
4380
4403
|
{
|
|
4381
4404
|
teams: visibleTeams,
|
|
@@ -4391,27 +4414,34 @@ var init_LiteLLMPage = __esm({
|
|
|
4391
4414
|
onCreateTeam: () => setManageTeam({ mode: "create" })
|
|
4392
4415
|
}
|
|
4393
4416
|
);
|
|
4394
|
-
})(), activeTab === "models" && /* @__PURE__ */
|
|
4417
|
+
})(), activeTab === "models" && /* @__PURE__ */ import_react12.default.createElement(
|
|
4395
4418
|
ModelsTable,
|
|
4396
4419
|
{
|
|
4397
4420
|
allModels: allModels ?? [],
|
|
4398
4421
|
teams: teams ?? [],
|
|
4399
4422
|
loading: modelsLoading
|
|
4400
4423
|
}
|
|
4401
|
-
), activeTab === "audit" && userInfo.can_view_audit && /* @__PURE__ */
|
|
4402
|
-
|
|
4424
|
+
), activeTab === "audit" && userInfo.can_view_audit && /* @__PURE__ */ import_react12.default.createElement(AuditLog, { api }), /* @__PURE__ */ import_react12.default.createElement(
|
|
4425
|
+
KeyFormDialog,
|
|
4403
4426
|
{
|
|
4404
|
-
open:
|
|
4405
|
-
onClose: () =>
|
|
4427
|
+
open: keyFormOpen,
|
|
4428
|
+
onClose: () => {
|
|
4429
|
+
setGenerateDialogOpen(false);
|
|
4430
|
+
setKeyToEdit(null);
|
|
4431
|
+
},
|
|
4432
|
+
mode: keyToEdit ? "edit" : "create",
|
|
4433
|
+
keyToEdit,
|
|
4406
4434
|
keys: keys ?? [],
|
|
4407
4435
|
models: allowedModels,
|
|
4408
4436
|
teams: teams ?? [],
|
|
4409
4437
|
username: userInfo.user_id,
|
|
4410
4438
|
keyGenerationSettings: liteLlmConfig?.keyGeneration,
|
|
4411
|
-
|
|
4439
|
+
onCreateKey: handleGenerateKey,
|
|
4440
|
+
onUpdateKey: handleUpdateKey,
|
|
4441
|
+
onResetKeySpend: handleResetKeySpend,
|
|
4412
4442
|
onGetConfig: () => api.getConfig()
|
|
4413
4443
|
}
|
|
4414
|
-
), /* @__PURE__ */
|
|
4444
|
+
), /* @__PURE__ */ import_react12.default.createElement(
|
|
4415
4445
|
ManageTeamDialog,
|
|
4416
4446
|
{
|
|
4417
4447
|
open: !!manageTeam,
|
|
@@ -4473,7 +4503,7 @@ var init_LiteLLMPage = __esm({
|
|
|
4473
4503
|
refreshTeams();
|
|
4474
4504
|
}
|
|
4475
4505
|
}
|
|
4476
|
-
), /* @__PURE__ */
|
|
4506
|
+
), /* @__PURE__ */ import_react12.default.createElement(
|
|
4477
4507
|
import_Snackbar2.default,
|
|
4478
4508
|
{
|
|
4479
4509
|
open: !!snackbar,
|
|
@@ -4481,7 +4511,7 @@ var init_LiteLLMPage = __esm({
|
|
|
4481
4511
|
onClose: () => setSnackbar(null),
|
|
4482
4512
|
anchorOrigin: { vertical: "bottom", horizontal: "right" }
|
|
4483
4513
|
},
|
|
4484
|
-
snackbar ? /* @__PURE__ */
|
|
4514
|
+
snackbar ? /* @__PURE__ */ import_react12.default.createElement(import_Alert6.default, { severity: snackbar.severity, onClose: () => setSnackbar(null) }, snackbar.message) : void 0
|
|
4485
4515
|
));
|
|
4486
4516
|
};
|
|
4487
4517
|
}
|
|
@@ -4492,6 +4522,7 @@ var index_exports = {};
|
|
|
4492
4522
|
__export(index_exports, {
|
|
4493
4523
|
DashboardHeader: () => DashboardHeader,
|
|
4494
4524
|
GenerateKeyDialog: () => GenerateKeyDialog,
|
|
4525
|
+
KeyFormDialog: () => KeyFormDialog,
|
|
4495
4526
|
KeysTable: () => KeysTable,
|
|
4496
4527
|
LiteLLMBudgetGauges: () => LiteLLMBudgetGauges,
|
|
4497
4528
|
LiteLLMBudgetWidget: () => LiteLLMBudgetWidget,
|
|
@@ -4512,7 +4543,7 @@ __export(index_exports, {
|
|
|
4512
4543
|
module.exports = __toCommonJS(index_exports);
|
|
4513
4544
|
|
|
4514
4545
|
// src/plugin.tsx
|
|
4515
|
-
var
|
|
4546
|
+
var import_react13 = __toESM(require("react"));
|
|
4516
4547
|
var import_icons_material9 = require("@mui/icons-material");
|
|
4517
4548
|
var import_frontend_plugin_api = require("@backstage/frontend-plugin-api");
|
|
4518
4549
|
init_api();
|
|
@@ -4527,10 +4558,10 @@ var liteLlmPage = import_frontend_plugin_api.PageBlueprint.make({
|
|
|
4527
4558
|
params: {
|
|
4528
4559
|
path: "/litellm",
|
|
4529
4560
|
title: "LiteLLM",
|
|
4530
|
-
icon: /* @__PURE__ */
|
|
4561
|
+
icon: /* @__PURE__ */ import_react13.default.createElement(import_icons_material9.TrendingUp, null),
|
|
4531
4562
|
loader: async () => {
|
|
4532
4563
|
const { LiteLLMPage: LiteLLMPage2 } = await Promise.resolve().then(() => (init_LiteLLMPage(), LiteLLMPage_exports));
|
|
4533
|
-
return /* @__PURE__ */
|
|
4564
|
+
return /* @__PURE__ */ import_react13.default.createElement(LiteLLMPage2, null);
|
|
4534
4565
|
}
|
|
4535
4566
|
}
|
|
4536
4567
|
});
|
|
@@ -4547,10 +4578,10 @@ init_UsageStats();
|
|
|
4547
4578
|
init_TeamUsage();
|
|
4548
4579
|
|
|
4549
4580
|
// src/components/LiteLLMHomeWidget.tsx
|
|
4550
|
-
var
|
|
4551
|
-
var
|
|
4552
|
-
var
|
|
4553
|
-
var
|
|
4581
|
+
var import_react14 = __toESM(require("react"));
|
|
4582
|
+
var import_Paper9 = __toESM(require("@mui/material/Paper"));
|
|
4583
|
+
var import_Box13 = __toESM(require("@mui/material/Box"));
|
|
4584
|
+
var import_Typography13 = __toESM(require("@mui/material/Typography"));
|
|
4554
4585
|
var import_FormControl2 = __toESM(require("@mui/material/FormControl"));
|
|
4555
4586
|
var import_Select2 = __toESM(require("@mui/material/Select"));
|
|
4556
4587
|
var import_MenuItem6 = __toESM(require("@mui/material/MenuItem"));
|
|
@@ -4573,19 +4604,19 @@ function presetToDateRange(preset) {
|
|
|
4573
4604
|
}
|
|
4574
4605
|
return { start, end };
|
|
4575
4606
|
}
|
|
4576
|
-
var Kpi = ({ label, value }) => /* @__PURE__ */
|
|
4607
|
+
var Kpi = ({ label, value }) => /* @__PURE__ */ import_react14.default.createElement(import_Box13.default, null, /* @__PURE__ */ import_react14.default.createElement(import_Typography13.default, { variant: "caption", color: "text.secondary", display: "block" }, label), /* @__PURE__ */ import_react14.default.createElement(import_Typography13.default, { variant: "subtitle1", fontWeight: 600 }, value));
|
|
4577
4608
|
var LiteLLMHomeWidget = ({
|
|
4578
4609
|
defaultPeriod = "7d",
|
|
4579
4610
|
title = "LiteLLM Usage"
|
|
4580
4611
|
}) => {
|
|
4581
4612
|
const api = (0, import_core_plugin_api5.useApi)(liteLlmApiRef);
|
|
4582
|
-
const [period, setPeriod] = (0,
|
|
4583
|
-
const [loading, setLoading] = (0,
|
|
4584
|
-
const [usageError, setUsageError] = (0,
|
|
4585
|
-
const [keysError, setKeysError] = (0,
|
|
4586
|
-
const [usage, setUsage] = (0,
|
|
4587
|
-
const [keys, setKeys] = (0,
|
|
4588
|
-
(0,
|
|
4613
|
+
const [period, setPeriod] = (0, import_react14.useState)(defaultPeriod);
|
|
4614
|
+
const [loading, setLoading] = (0, import_react14.useState)(true);
|
|
4615
|
+
const [usageError, setUsageError] = (0, import_react14.useState)(null);
|
|
4616
|
+
const [keysError, setKeysError] = (0, import_react14.useState)(null);
|
|
4617
|
+
const [usage, setUsage] = (0, import_react14.useState)(null);
|
|
4618
|
+
const [keys, setKeys] = (0, import_react14.useState)([]);
|
|
4619
|
+
(0, import_react14.useEffect)(() => {
|
|
4589
4620
|
let cancelled = false;
|
|
4590
4621
|
setLoading(true);
|
|
4591
4622
|
setUsageError(null);
|
|
@@ -4622,17 +4653,17 @@ var LiteLLMHomeWidget = ({
|
|
|
4622
4653
|
spend: d.spend
|
|
4623
4654
|
}));
|
|
4624
4655
|
const hasSparkline = dailyData.length > 0;
|
|
4625
|
-
return /* @__PURE__ */
|
|
4656
|
+
return /* @__PURE__ */ import_react14.default.createElement(import_Paper9.default, { sx: { p: 2 } }, /* @__PURE__ */ import_react14.default.createElement(import_Box13.default, { display: "flex", justifyContent: "space-between", alignItems: "center", mb: 1.5 }, /* @__PURE__ */ import_react14.default.createElement(import_Typography13.default, { variant: "h6" }, title), /* @__PURE__ */ import_react14.default.createElement(import_FormControl2.default, { size: "small", sx: { minWidth: 90 } }, /* @__PURE__ */ import_react14.default.createElement(
|
|
4626
4657
|
import_Select2.default,
|
|
4627
4658
|
{
|
|
4628
4659
|
value: period,
|
|
4629
4660
|
onChange: (e) => setPeriod(e.target.value),
|
|
4630
4661
|
displayEmpty: true
|
|
4631
4662
|
},
|
|
4632
|
-
/* @__PURE__ */
|
|
4633
|
-
/* @__PURE__ */
|
|
4634
|
-
/* @__PURE__ */
|
|
4635
|
-
))), loading && /* @__PURE__ */
|
|
4663
|
+
/* @__PURE__ */ import_react14.default.createElement(import_MenuItem6.default, { value: "today" }, "Today"),
|
|
4664
|
+
/* @__PURE__ */ import_react14.default.createElement(import_MenuItem6.default, { value: "7d" }, "7d"),
|
|
4665
|
+
/* @__PURE__ */ import_react14.default.createElement(import_MenuItem6.default, { value: "30d" }, "30d")
|
|
4666
|
+
))), loading && /* @__PURE__ */ import_react14.default.createElement(import_Box13.default, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: 120 }, /* @__PURE__ */ import_react14.default.createElement(import_CircularProgress7.default, { size: 32 })), !loading && totalFailure && /* @__PURE__ */ import_react14.default.createElement(import_Alert7.default, { severity: "error", sx: { mt: 1 } }, usageError ?? "Failed to load usage data"), !loading && !totalFailure && /* @__PURE__ */ import_react14.default.createElement(import_react14.default.Fragment, null, partialFailure && /* @__PURE__ */ import_react14.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_react14.default.createElement(import_Grid3.default, { container: true, spacing: 2, sx: { mb: hasSparkline ? 1.5 : 0 } }, /* @__PURE__ */ import_react14.default.createElement(import_Grid3.default, { item: true, xs: 6 }, /* @__PURE__ */ import_react14.default.createElement(Kpi, { label: "USD Spent", value: fmtUsd(usage?.total_spend ?? 0) })), /* @__PURE__ */ import_react14.default.createElement(import_Grid3.default, { item: true, xs: 6 }, /* @__PURE__ */ import_react14.default.createElement(Kpi, { label: "Tokens In", value: fmtInt(usage?.prompt_tokens ?? 0) })), /* @__PURE__ */ import_react14.default.createElement(import_Grid3.default, { item: true, xs: 6 }, /* @__PURE__ */ import_react14.default.createElement(Kpi, { label: "Tokens Out", value: fmtInt(usage?.completion_tokens ?? 0) })), /* @__PURE__ */ import_react14.default.createElement(import_Grid3.default, { item: true, xs: 6 }, /* @__PURE__ */ import_react14.default.createElement(Kpi, { label: "Keys", value: fmtInt(keys.length) }))), hasSparkline && /* @__PURE__ */ import_react14.default.createElement(import_Box13.default, { height: 120 }, /* @__PURE__ */ import_react14.default.createElement(import_recharts3.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react14.default.createElement(import_recharts3.AreaChart, { data: dailyData, margin: { top: 4, right: 0, bottom: 0, left: 0 } }, /* @__PURE__ */ import_react14.default.createElement(
|
|
4636
4667
|
import_recharts3.Area,
|
|
4637
4668
|
{
|
|
4638
4669
|
type: "monotone",
|
|
@@ -4650,24 +4681,34 @@ var LiteLLMHomeWidget = ({
|
|
|
4650
4681
|
init_LiteLLMBudgetWidget();
|
|
4651
4682
|
|
|
4652
4683
|
// src/components/LiteLLMBudgetGauges.tsx
|
|
4653
|
-
var
|
|
4654
|
-
var
|
|
4655
|
-
var
|
|
4656
|
-
var
|
|
4684
|
+
var import_react15 = __toESM(require("react"));
|
|
4685
|
+
var import_Paper10 = __toESM(require("@mui/material/Paper"));
|
|
4686
|
+
var import_Box14 = __toESM(require("@mui/material/Box"));
|
|
4687
|
+
var import_Button6 = __toESM(require("@mui/material/Button"));
|
|
4688
|
+
var import_Typography14 = __toESM(require("@mui/material/Typography"));
|
|
4657
4689
|
var import_CircularProgress8 = __toESM(require("@mui/material/CircularProgress"));
|
|
4658
4690
|
var import_Alert8 = __toESM(require("@mui/material/Alert"));
|
|
4691
|
+
var import_Collapse4 = __toESM(require("@mui/material/Collapse"));
|
|
4692
|
+
var import_icons_material10 = require("@mui/icons-material");
|
|
4659
4693
|
var import_core_plugin_api6 = require("@backstage/core-plugin-api");
|
|
4660
4694
|
var import_core_components2 = require("@backstage/core-components");
|
|
4661
4695
|
init_api();
|
|
4662
4696
|
init_format();
|
|
4663
4697
|
init_ui();
|
|
4698
|
+
init_BudgetLimitList();
|
|
4664
4699
|
init_budget();
|
|
4665
|
-
var
|
|
4700
|
+
var MODULE_PATH = "/litellm";
|
|
4666
4701
|
var LEVEL = {
|
|
4667
4702
|
key: { name: "Key", none: "No key budget" },
|
|
4668
4703
|
user: { name: "User", none: "No personal budget" },
|
|
4669
4704
|
team: { name: "Team", none: "No team budget" }
|
|
4670
4705
|
};
|
|
4706
|
+
var DEFAULT_CTAS = ["new-key", "module", "all-limits"];
|
|
4707
|
+
var CTA_LABELS = {
|
|
4708
|
+
"new-key": "New key",
|
|
4709
|
+
module: "Open module",
|
|
4710
|
+
"all-limits": "All limits"
|
|
4711
|
+
};
|
|
4671
4712
|
function fmtUsdShort(n) {
|
|
4672
4713
|
const v = n ?? 0;
|
|
4673
4714
|
if (v >= 100 && Number.isInteger(v)) return `$${v}`;
|
|
@@ -4690,8 +4731,8 @@ var LevelGauge = ({
|
|
|
4690
4731
|
const limit = gauge.limit;
|
|
4691
4732
|
const tone = limit ? budgetTone2(limit.pct) : "neutral";
|
|
4692
4733
|
const extra = gauge.kind === "key" ? Math.max(0, gauge.count - 1) : 0;
|
|
4693
|
-
return /* @__PURE__ */
|
|
4694
|
-
|
|
4734
|
+
return /* @__PURE__ */ import_react15.default.createElement(
|
|
4735
|
+
import_Box14.default,
|
|
4695
4736
|
{
|
|
4696
4737
|
sx: {
|
|
4697
4738
|
display: "flex",
|
|
@@ -4701,7 +4742,7 @@ var LevelGauge = ({
|
|
|
4701
4742
|
flex: 1
|
|
4702
4743
|
}
|
|
4703
4744
|
},
|
|
4704
|
-
/* @__PURE__ */
|
|
4745
|
+
/* @__PURE__ */ import_react15.default.createElement(
|
|
4705
4746
|
Gauge,
|
|
4706
4747
|
{
|
|
4707
4748
|
value: limit?.pct ?? 0,
|
|
@@ -4710,8 +4751,8 @@ var LevelGauge = ({
|
|
|
4710
4751
|
label: limit ? `${Math.round(limit.pct)}%` : "\u2014"
|
|
4711
4752
|
}
|
|
4712
4753
|
),
|
|
4713
|
-
/* @__PURE__ */
|
|
4714
|
-
|
|
4754
|
+
/* @__PURE__ */ import_react15.default.createElement(
|
|
4755
|
+
import_Typography14.default,
|
|
4715
4756
|
{
|
|
4716
4757
|
sx: (theme) => ({
|
|
4717
4758
|
mt: 0.75,
|
|
@@ -4724,8 +4765,8 @@ var LevelGauge = ({
|
|
|
4724
4765
|
},
|
|
4725
4766
|
name
|
|
4726
4767
|
),
|
|
4727
|
-
limit ? /* @__PURE__ */
|
|
4728
|
-
|
|
4768
|
+
limit ? /* @__PURE__ */ import_react15.default.createElement(import_react15.default.Fragment, null, /* @__PURE__ */ import_react15.default.createElement(
|
|
4769
|
+
import_Typography14.default,
|
|
4729
4770
|
{
|
|
4730
4771
|
variant: "caption",
|
|
4731
4772
|
title: limit.sublabel ? `${limit.label} \xB7 ${limit.sublabel}` : limit.label,
|
|
@@ -4740,8 +4781,8 @@ var LevelGauge = ({
|
|
|
4740
4781
|
}
|
|
4741
4782
|
},
|
|
4742
4783
|
limit.label
|
|
4743
|
-
), /* @__PURE__ */
|
|
4744
|
-
|
|
4784
|
+
), /* @__PURE__ */ import_react15.default.createElement(
|
|
4785
|
+
import_Typography14.default,
|
|
4745
4786
|
{
|
|
4746
4787
|
variant: "caption",
|
|
4747
4788
|
color: "text.secondary",
|
|
@@ -4752,8 +4793,8 @@ var LevelGauge = ({
|
|
|
4752
4793
|
}
|
|
4753
4794
|
},
|
|
4754
4795
|
spendCaption(limit)
|
|
4755
|
-
), extra > 0 ? /* @__PURE__ */
|
|
4756
|
-
|
|
4796
|
+
), extra > 0 ? /* @__PURE__ */ import_react15.default.createElement(
|
|
4797
|
+
import_Typography14.default,
|
|
4757
4798
|
{
|
|
4758
4799
|
component: import_core_components2.Link,
|
|
4759
4800
|
to: keysHref,
|
|
@@ -4768,8 +4809,8 @@ var LevelGauge = ({
|
|
|
4768
4809
|
"+",
|
|
4769
4810
|
extra,
|
|
4770
4811
|
" more"
|
|
4771
|
-
) : /* @__PURE__ */
|
|
4772
|
-
|
|
4812
|
+
) : /* @__PURE__ */ import_react15.default.createElement(import_Typography14.default, { variant: "caption", color: "text.secondary", sx: { fontSize: 10.5 } }, resetLabel(limit))) : /* @__PURE__ */ import_react15.default.createElement(
|
|
4813
|
+
import_Typography14.default,
|
|
4773
4814
|
{
|
|
4774
4815
|
variant: "caption",
|
|
4775
4816
|
color: "text.secondary",
|
|
@@ -4782,16 +4823,32 @@ var LevelGauge = ({
|
|
|
4782
4823
|
var LiteLLMBudgetGauges = ({
|
|
4783
4824
|
title = "Budget",
|
|
4784
4825
|
size = 72,
|
|
4785
|
-
keysHref
|
|
4826
|
+
keysHref,
|
|
4827
|
+
moduleHref = MODULE_PATH,
|
|
4828
|
+
onCreateKey,
|
|
4829
|
+
ctas,
|
|
4830
|
+
maxExpandedKeys = 8,
|
|
4831
|
+
expanded,
|
|
4832
|
+
defaultExpanded = false,
|
|
4833
|
+
onExpandedChange,
|
|
4786
4834
|
action
|
|
4787
4835
|
}) => {
|
|
4788
4836
|
const api = (0, import_core_plugin_api6.useApi)(liteLlmApiRef);
|
|
4789
|
-
const [loading, setLoading] = (0,
|
|
4790
|
-
const [error, setError] = (0,
|
|
4791
|
-
const [user, setUser] = (0,
|
|
4792
|
-
const [teams, setTeams] = (0,
|
|
4793
|
-
const [keys, setKeys] = (0,
|
|
4794
|
-
|
|
4837
|
+
const [loading, setLoading] = (0, import_react15.useState)(true);
|
|
4838
|
+
const [error, setError] = (0, import_react15.useState)(null);
|
|
4839
|
+
const [user, setUser] = (0, import_react15.useState)(null);
|
|
4840
|
+
const [teams, setTeams] = (0, import_react15.useState)([]);
|
|
4841
|
+
const [keys, setKeys] = (0, import_react15.useState)([]);
|
|
4842
|
+
const isControlled = expanded !== void 0;
|
|
4843
|
+
const [uncontrolledExpanded, setUncontrolledExpanded] = (0, import_react15.useState)(defaultExpanded);
|
|
4844
|
+
const isExpanded = isControlled ? expanded : uncontrolledExpanded;
|
|
4845
|
+
const toggleExpanded = (0, import_react15.useCallback)(() => {
|
|
4846
|
+
const next = !isExpanded;
|
|
4847
|
+
if (!isControlled) setUncontrolledExpanded(next);
|
|
4848
|
+
onExpandedChange?.(next);
|
|
4849
|
+
}, [isControlled, isExpanded, onExpandedChange]);
|
|
4850
|
+
const keysTabHref = keysHref ?? `${moduleHref}?tab=keys`;
|
|
4851
|
+
(0, import_react15.useEffect)(() => {
|
|
4795
4852
|
let cancelled = false;
|
|
4796
4853
|
setLoading(true);
|
|
4797
4854
|
setError(null);
|
|
@@ -4815,35 +4872,138 @@ var LiteLLMBudgetGauges = ({
|
|
|
4815
4872
|
cancelled = true;
|
|
4816
4873
|
};
|
|
4817
4874
|
}, [api]);
|
|
4818
|
-
const summary = (0,
|
|
4875
|
+
const summary = (0, import_react15.useMemo)(
|
|
4819
4876
|
() => buildBudgetSummary(user, teams, keys, keys.length),
|
|
4820
4877
|
[user, teams, keys]
|
|
4821
4878
|
);
|
|
4822
|
-
const gauges = (0,
|
|
4823
|
-
const headline = (0,
|
|
4879
|
+
const gauges = (0, import_react15.useMemo)(() => buildBudgetGauges(summary), [summary]);
|
|
4880
|
+
const headline = (0, import_react15.useMemo)(() => budgetHeadline(summary), [summary]);
|
|
4881
|
+
const expandedSummary = (0, import_react15.useMemo)(
|
|
4882
|
+
() => buildBudgetSummary(user, teams, keys, maxExpandedKeys),
|
|
4883
|
+
[user, teams, keys, maxExpandedKeys]
|
|
4884
|
+
);
|
|
4885
|
+
const expandedLimits = (0, import_react15.useMemo)(
|
|
4886
|
+
() => allBudgetLimits(expandedSummary),
|
|
4887
|
+
[expandedSummary]
|
|
4888
|
+
);
|
|
4824
4889
|
const summaryText = headline.count === 0 ? "no limits apply" : `${headline.count} limit${headline.count === 1 ? "" : "s"}`;
|
|
4825
|
-
|
|
4890
|
+
const resolvedCtas = (0, import_react15.useMemo)(() => {
|
|
4891
|
+
const list = ctas ?? DEFAULT_CTAS;
|
|
4892
|
+
return list.map((cta) => typeof cta === "string" ? { kind: cta } : cta).filter((cta) => cta.kind !== "all-limits" || headline.count > 0);
|
|
4893
|
+
}, [ctas, headline.count]);
|
|
4894
|
+
const renderCta = (cta, index) => {
|
|
4895
|
+
const label = cta.label ?? CTA_LABELS[cta.kind];
|
|
4896
|
+
const key = `${cta.kind}-${index}`;
|
|
4897
|
+
switch (cta.kind) {
|
|
4898
|
+
case "new-key":
|
|
4899
|
+
return onCreateKey ? /* @__PURE__ */ import_react15.default.createElement(import_Button6.default, { key, size: "small", variant: "contained", onClick: onCreateKey }, label) : /* @__PURE__ */ import_react15.default.createElement(
|
|
4900
|
+
import_Button6.default,
|
|
4901
|
+
{
|
|
4902
|
+
key,
|
|
4903
|
+
size: "small",
|
|
4904
|
+
variant: "contained",
|
|
4905
|
+
component: import_core_components2.Link,
|
|
4906
|
+
to: `${moduleHref}?generate=1`
|
|
4907
|
+
},
|
|
4908
|
+
label
|
|
4909
|
+
);
|
|
4910
|
+
case "module":
|
|
4911
|
+
return /* @__PURE__ */ import_react15.default.createElement(import_Button6.default, { key, size: "small", variant: "outlined", component: import_core_components2.Link, to: moduleHref }, label);
|
|
4912
|
+
case "all-limits":
|
|
4913
|
+
return /* @__PURE__ */ import_react15.default.createElement(
|
|
4914
|
+
import_Button6.default,
|
|
4915
|
+
{
|
|
4916
|
+
key,
|
|
4917
|
+
size: "small",
|
|
4918
|
+
variant: "text",
|
|
4919
|
+
onClick: toggleExpanded,
|
|
4920
|
+
endIcon: /* @__PURE__ */ import_react15.default.createElement(
|
|
4921
|
+
import_icons_material10.ExpandMore,
|
|
4922
|
+
{
|
|
4923
|
+
sx: {
|
|
4924
|
+
transform: isExpanded ? "rotate(180deg)" : "none",
|
|
4925
|
+
transition: (theme) => theme.transitions.create("transform")
|
|
4926
|
+
}
|
|
4927
|
+
}
|
|
4928
|
+
)
|
|
4929
|
+
},
|
|
4930
|
+
label
|
|
4931
|
+
);
|
|
4932
|
+
/* istanbul ignore next — exhaustiveness */
|
|
4933
|
+
default:
|
|
4934
|
+
return null;
|
|
4935
|
+
}
|
|
4936
|
+
};
|
|
4937
|
+
const hasFooter = resolvedCtas.length > 0 || !!action;
|
|
4938
|
+
return /* @__PURE__ */ import_react15.default.createElement(import_Paper10.default, { sx: { p: 2 } }, /* @__PURE__ */ import_react15.default.createElement(import_Box14.default, { sx: { display: "flex", alignItems: "center", gap: 1, mb: 1.5, minWidth: 0 } }, /* @__PURE__ */ import_react15.default.createElement(import_Typography14.default, { variant: "h6", lineHeight: 1.2, sx: { minWidth: 0, flex: 1 } }, title), !loading && !error && /* @__PURE__ */ import_react15.default.createElement(
|
|
4826
4939
|
StatusPill,
|
|
4827
4940
|
{
|
|
4828
4941
|
label: summaryText,
|
|
4829
4942
|
tone: budgetTone2(headline.closest?.pct ?? 0),
|
|
4830
4943
|
dot: headline.count > 0
|
|
4831
4944
|
}
|
|
4832
|
-
)), loading && /* @__PURE__ */
|
|
4833
|
-
|
|
4945
|
+
)), loading && /* @__PURE__ */ import_react15.default.createElement(import_Box14.default, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: 110 }, /* @__PURE__ */ import_react15.default.createElement(import_CircularProgress8.default, { size: 28 })), !loading && error && /* @__PURE__ */ import_react15.default.createElement(import_Alert8.default, { severity: "error", sx: { mt: 0.5 } }, error), !loading && !error && /* @__PURE__ */ import_react15.default.createElement(import_react15.default.Fragment, null, /* @__PURE__ */ import_react15.default.createElement(import_Box14.default, { sx: { display: "flex", alignItems: "flex-start", gap: 1 } }, gauges.map((gauge) => /* @__PURE__ */ import_react15.default.createElement(LevelGauge, { key: gauge.kind, gauge, size, keysHref: keysTabHref }))), /* @__PURE__ */ import_react15.default.createElement(import_Collapse4.default, { in: isExpanded, unmountOnExit: true }, /* @__PURE__ */ import_react15.default.createElement(
|
|
4946
|
+
import_Box14.default,
|
|
4947
|
+
{
|
|
4948
|
+
sx: {
|
|
4949
|
+
mt: 1.5,
|
|
4950
|
+
// Bound the expanded list so a user with many limits still
|
|
4951
|
+
// keeps the card compact and the toggle within reach.
|
|
4952
|
+
maxHeight: 300,
|
|
4953
|
+
overflowY: "auto"
|
|
4954
|
+
}
|
|
4955
|
+
},
|
|
4956
|
+
/* @__PURE__ */ import_react15.default.createElement(LimitListPanel, null, /* @__PURE__ */ import_react15.default.createElement(
|
|
4957
|
+
BudgetLimitList,
|
|
4958
|
+
{
|
|
4959
|
+
limits: expandedLimits,
|
|
4960
|
+
hiddenKeyCount: expandedSummary.hiddenBudgetedKeys,
|
|
4961
|
+
keysHref: keysTabHref
|
|
4962
|
+
}
|
|
4963
|
+
))
|
|
4964
|
+
))), hasFooter && /* @__PURE__ */ import_react15.default.createElement(
|
|
4965
|
+
import_Box14.default,
|
|
4834
4966
|
{
|
|
4835
4967
|
sx: (theme) => ({
|
|
4836
4968
|
mt: 2,
|
|
4837
4969
|
pt: 2,
|
|
4838
|
-
borderTop: `1px solid ${theme.palette.divider}
|
|
4970
|
+
borderTop: `1px solid ${theme.palette.divider}`,
|
|
4971
|
+
display: "flex",
|
|
4972
|
+
alignItems: "center",
|
|
4973
|
+
gap: 1,
|
|
4974
|
+
flexWrap: "wrap"
|
|
4839
4975
|
})
|
|
4840
4976
|
},
|
|
4977
|
+
resolvedCtas.map(renderCta),
|
|
4841
4978
|
action
|
|
4842
4979
|
));
|
|
4843
4980
|
};
|
|
4844
4981
|
|
|
4845
4982
|
// src/index.ts
|
|
4846
|
-
|
|
4983
|
+
init_KeyFormDialog();
|
|
4984
|
+
|
|
4985
|
+
// src/components/GenerateKeyDialog.tsx
|
|
4986
|
+
var import_react16 = __toESM(require("react"));
|
|
4987
|
+
init_KeyFormDialog();
|
|
4988
|
+
var GenerateKeyDialog = (props) => /* @__PURE__ */ import_react16.default.createElement(
|
|
4989
|
+
KeyFormDialog,
|
|
4990
|
+
{
|
|
4991
|
+
open: props.open,
|
|
4992
|
+
onClose: props.onClose,
|
|
4993
|
+
mode: "create",
|
|
4994
|
+
keys: props.keys,
|
|
4995
|
+
models: props.models,
|
|
4996
|
+
teams: props.teams,
|
|
4997
|
+
username: props.username,
|
|
4998
|
+
keyGenerationSettings: props.keyGenerationSettings,
|
|
4999
|
+
onCreateKey: props.onGenerateKey,
|
|
5000
|
+
onUpdateKey: async () => {
|
|
5001
|
+
},
|
|
5002
|
+
onGetConfig: props.onGetConfig
|
|
5003
|
+
}
|
|
5004
|
+
);
|
|
5005
|
+
|
|
5006
|
+
// src/index.ts
|
|
4847
5007
|
init_ManageTeamDialog();
|
|
4848
5008
|
init_api();
|
|
4849
5009
|
init_permissions();
|