@acarmisc/backstage-plugin-litellm 0.25.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/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/SearchInput.d.ts +9 -0
- package/dist/index.cjs.js +210 -217
- package/dist/index.cjs.js.map +4 -4
- package/dist/index.d.ts +2 -0
- package/dist/index.esm.js +209 -216
- 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,
|
|
@@ -4120,7 +4082,7 @@ var init_LiteLLMPage = __esm({
|
|
|
4120
4082
|
import_plugin_permission_react = require("@backstage/plugin-permission-react");
|
|
4121
4083
|
init_DashboardHeader();
|
|
4122
4084
|
init_KeysTable();
|
|
4123
|
-
|
|
4085
|
+
init_KeyFormDialog();
|
|
4124
4086
|
init_ManageTeamDialog();
|
|
4125
4087
|
init_UsageStats();
|
|
4126
4088
|
init_LiteLLMBudgetWidget();
|
|
@@ -4178,6 +4140,8 @@ var init_LiteLLMPage = __esm({
|
|
|
4178
4140
|
);
|
|
4179
4141
|
const [snackbar, setSnackbar] = (0, import_react12.useState)(null);
|
|
4180
4142
|
const [manageTeam, setManageTeam] = (0, import_react12.useState)(null);
|
|
4143
|
+
const [keyToEdit, setKeyToEdit] = (0, import_react12.useState)(null);
|
|
4144
|
+
const keyFormOpen = generateDialogOpen || !!keyToEdit;
|
|
4181
4145
|
const [teamUsageCache, setTeamUsageCache] = (0, import_react12.useState)({});
|
|
4182
4146
|
const [teamUsageLoading, setTeamUsageLoading] = (0, import_react12.useState)({});
|
|
4183
4147
|
const { value: userInfo, loading: userLoading, error: userError } = (0, import_react_use3.useAsync)(
|
|
@@ -4421,13 +4385,11 @@ var init_LiteLLMPage = __esm({
|
|
|
4421
4385
|
KeysTable,
|
|
4422
4386
|
{
|
|
4423
4387
|
keys: keys ?? [],
|
|
4424
|
-
models: allowedModels,
|
|
4425
4388
|
loading: keysLoading || modelsLoading,
|
|
4426
4389
|
onGenerateKeyClick: () => setGenerateDialogOpen(true),
|
|
4427
|
-
|
|
4390
|
+
onEditKey: setKeyToEdit,
|
|
4428
4391
|
onBlockKey: handleBlockKey,
|
|
4429
4392
|
onUnblockKey: handleUnblockKey,
|
|
4430
|
-
onResetKeySpend: handleResetKeySpend,
|
|
4431
4393
|
onDeleteKey: handleDeleteKey,
|
|
4432
4394
|
onPruneExpiredKeys: handlePruneExpiredKeys
|
|
4433
4395
|
}
|
|
@@ -4460,16 +4422,23 @@ var init_LiteLLMPage = __esm({
|
|
|
4460
4422
|
loading: modelsLoading
|
|
4461
4423
|
}
|
|
4462
4424
|
), activeTab === "audit" && userInfo.can_view_audit && /* @__PURE__ */ import_react12.default.createElement(AuditLog, { api }), /* @__PURE__ */ import_react12.default.createElement(
|
|
4463
|
-
|
|
4425
|
+
KeyFormDialog,
|
|
4464
4426
|
{
|
|
4465
|
-
open:
|
|
4466
|
-
onClose: () =>
|
|
4427
|
+
open: keyFormOpen,
|
|
4428
|
+
onClose: () => {
|
|
4429
|
+
setGenerateDialogOpen(false);
|
|
4430
|
+
setKeyToEdit(null);
|
|
4431
|
+
},
|
|
4432
|
+
mode: keyToEdit ? "edit" : "create",
|
|
4433
|
+
keyToEdit,
|
|
4467
4434
|
keys: keys ?? [],
|
|
4468
4435
|
models: allowedModels,
|
|
4469
4436
|
teams: teams ?? [],
|
|
4470
4437
|
username: userInfo.user_id,
|
|
4471
4438
|
keyGenerationSettings: liteLlmConfig?.keyGeneration,
|
|
4472
|
-
|
|
4439
|
+
onCreateKey: handleGenerateKey,
|
|
4440
|
+
onUpdateKey: handleUpdateKey,
|
|
4441
|
+
onResetKeySpend: handleResetKeySpend,
|
|
4473
4442
|
onGetConfig: () => api.getConfig()
|
|
4474
4443
|
}
|
|
4475
4444
|
), /* @__PURE__ */ import_react12.default.createElement(
|
|
@@ -4553,6 +4522,7 @@ var index_exports = {};
|
|
|
4553
4522
|
__export(index_exports, {
|
|
4554
4523
|
DashboardHeader: () => DashboardHeader,
|
|
4555
4524
|
GenerateKeyDialog: () => GenerateKeyDialog,
|
|
4525
|
+
KeyFormDialog: () => KeyFormDialog,
|
|
4556
4526
|
KeysTable: () => KeysTable,
|
|
4557
4527
|
LiteLLMBudgetGauges: () => LiteLLMBudgetGauges,
|
|
4558
4528
|
LiteLLMBudgetWidget: () => LiteLLMBudgetWidget,
|
|
@@ -5010,7 +4980,30 @@ var LiteLLMBudgetGauges = ({
|
|
|
5010
4980
|
};
|
|
5011
4981
|
|
|
5012
4982
|
// src/index.ts
|
|
5013
|
-
|
|
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
|
|
5014
5007
|
init_ManageTeamDialog();
|
|
5015
5008
|
init_api();
|
|
5016
5009
|
init_permissions();
|