@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.esm.js
CHANGED
|
@@ -1003,7 +1003,6 @@ import DialogContent from "@mui/material/DialogContent";
|
|
|
1003
1003
|
import DialogActions from "@mui/material/DialogActions";
|
|
1004
1004
|
import TextField from "@mui/material/TextField";
|
|
1005
1005
|
import CircularProgress from "@mui/material/CircularProgress";
|
|
1006
|
-
import Autocomplete from "@mui/material/Autocomplete";
|
|
1007
1006
|
import Skeleton2 from "@mui/material/Skeleton";
|
|
1008
1007
|
import InputAdornment from "@mui/material/InputAdornment";
|
|
1009
1008
|
import { alpha as alpha3 } from "@mui/material/styles";
|
|
@@ -1052,26 +1051,6 @@ function BudgetCell({ spend, maxBudget }) {
|
|
|
1052
1051
|
const pct = Math.min(100, spend / maxBudget * 100);
|
|
1053
1052
|
return /* @__PURE__ */ React3.createElement(Box3, { minWidth: 104 }, /* @__PURE__ */ React3.createElement(Typography3, { variant: "body2", sx: { fontVariantNumeric: "tabular-nums", mb: 0.5 } }, "$", spend.toFixed(2), /* @__PURE__ */ React3.createElement(Typography3, { component: "span", variant: "caption", color: "text.secondary", sx: { ml: 0.5 } }, "/ $", maxBudget)), /* @__PURE__ */ React3.createElement(Meter, { value: pct, tone: budgetTone(pct), height: 4 }));
|
|
1054
1053
|
}
|
|
1055
|
-
function fmtCost(perToken) {
|
|
1056
|
-
if (!perToken) return null;
|
|
1057
|
-
const per1k = perToken * 1e3;
|
|
1058
|
-
return per1k < 0.01 ? `$${(perToken * 1e6).toFixed(2)}/M` : `$${per1k.toFixed(3)}/1K`;
|
|
1059
|
-
}
|
|
1060
|
-
function formatContextWindow(maxInput, maxOutput) {
|
|
1061
|
-
if (!maxInput && !maxOutput) return null;
|
|
1062
|
-
const fmt = (n) => {
|
|
1063
|
-
if (!n) return null;
|
|
1064
|
-
if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
|
|
1065
|
-
if (n >= 1e3) return `${Math.round(n / 1e3)}K`;
|
|
1066
|
-
return String(n);
|
|
1067
|
-
};
|
|
1068
|
-
const inPart = fmt(maxInput);
|
|
1069
|
-
const outPart = fmt(maxOutput);
|
|
1070
|
-
if (inPart && outPart) return `ctx ${inPart} in / ${outPart} out`;
|
|
1071
|
-
if (inPart) return `ctx ${inPart}`;
|
|
1072
|
-
if (outPart) return `ctx ${outPart} out`;
|
|
1073
|
-
return null;
|
|
1074
|
-
}
|
|
1075
1054
|
function compareKeys(a, b, sortKey) {
|
|
1076
1055
|
switch (sortKey) {
|
|
1077
1056
|
case "alias":
|
|
@@ -1090,16 +1069,12 @@ function compareKeys(a, b, sortKey) {
|
|
|
1090
1069
|
return 0;
|
|
1091
1070
|
}
|
|
1092
1071
|
}
|
|
1093
|
-
var
|
|
1072
|
+
var shortKeyId, formatDate, KeysTable;
|
|
1094
1073
|
var init_KeysTable = __esm({
|
|
1095
1074
|
"src/components/KeysTable.tsx"() {
|
|
1096
1075
|
"use strict";
|
|
1097
1076
|
init_api();
|
|
1098
1077
|
init_ui();
|
|
1099
|
-
maskKey = (key) => {
|
|
1100
|
-
if (key.length <= 8) return "***";
|
|
1101
|
-
return `${key.slice(0, 4)}...${key.slice(-4)}`;
|
|
1102
|
-
};
|
|
1103
1078
|
shortKeyId = (token) => {
|
|
1104
1079
|
if (!token) return "-";
|
|
1105
1080
|
if (token.length <= 16) return token;
|
|
@@ -1112,33 +1087,19 @@ var init_KeysTable = __esm({
|
|
|
1112
1087
|
return dateStr;
|
|
1113
1088
|
}
|
|
1114
1089
|
};
|
|
1115
|
-
keyToEditForm = (k) => ({
|
|
1116
|
-
key_alias: k.key_alias ?? "",
|
|
1117
|
-
models: k.models ?? [],
|
|
1118
|
-
max_budget: k.max_budget,
|
|
1119
|
-
tpm_limit: k.tpm_limit,
|
|
1120
|
-
rpm_limit: k.rpm_limit
|
|
1121
|
-
});
|
|
1122
1090
|
KeysTable = ({
|
|
1123
1091
|
keys,
|
|
1124
|
-
models,
|
|
1125
1092
|
loading,
|
|
1126
1093
|
onGenerateKeyClick,
|
|
1127
|
-
|
|
1094
|
+
onEditKey,
|
|
1128
1095
|
onBlockKey,
|
|
1129
1096
|
onUnblockKey,
|
|
1130
|
-
onResetKeySpend,
|
|
1131
1097
|
onDeleteKey,
|
|
1132
1098
|
onPruneExpiredKeys
|
|
1133
1099
|
}) => {
|
|
1134
|
-
const [editingKey, setEditingKey] = useState(null);
|
|
1135
|
-
const [editForm, setEditForm] = useState({});
|
|
1136
|
-
const [editSubmitting, setEditSubmitting] = useState(false);
|
|
1137
1100
|
const [blockingKeyId, setBlockingKeyId] = useState(null);
|
|
1138
1101
|
const [deleteConfirmId, setDeleteConfirmId] = useState(null);
|
|
1139
1102
|
const [deleteSubmitting, setDeleteSubmitting] = useState(false);
|
|
1140
|
-
const [resetSpendConfirm, setResetSpendConfirm] = useState(false);
|
|
1141
|
-
const [resetSpendSubmitting, setResetSpendSubmitting] = useState(false);
|
|
1142
1103
|
const [filterText, setFilterText] = useState("");
|
|
1143
1104
|
const [sortKey, setSortKey] = useState("created");
|
|
1144
1105
|
const [sortDirection, setSortDirection] = useState("desc");
|
|
@@ -1163,27 +1124,8 @@ var init_KeysTable = __esm({
|
|
|
1163
1124
|
return sortDirection === "asc" ? comparison : -comparison;
|
|
1164
1125
|
});
|
|
1165
1126
|
}, [keys, filterText, sortKey, sortDirection]);
|
|
1166
|
-
const editSelectedModels = models.filter((m) => (editForm.models || []).includes(m.model_name));
|
|
1167
1127
|
const handleOpenEdit = (k) => {
|
|
1168
|
-
|
|
1169
|
-
setEditForm(keyToEditForm(k));
|
|
1170
|
-
};
|
|
1171
|
-
const handleCloseEdit = () => {
|
|
1172
|
-
setEditingKey(null);
|
|
1173
|
-
setEditForm({});
|
|
1174
|
-
setResetSpendConfirm(false);
|
|
1175
|
-
};
|
|
1176
|
-
const handleUpdate = async () => {
|
|
1177
|
-
if (!editingKey) return;
|
|
1178
|
-
setEditSubmitting(true);
|
|
1179
|
-
try {
|
|
1180
|
-
await onUpdateKey(editingKey.token ?? editingKey.key, editForm);
|
|
1181
|
-
handleCloseEdit();
|
|
1182
|
-
} catch (error) {
|
|
1183
|
-
console.error("Failed to update key:", error);
|
|
1184
|
-
} finally {
|
|
1185
|
-
setEditSubmitting(false);
|
|
1186
|
-
}
|
|
1128
|
+
onEditKey(k);
|
|
1187
1129
|
};
|
|
1188
1130
|
const handleToggleBlock = async (key) => {
|
|
1189
1131
|
const keyId = key.token ?? key.key;
|
|
@@ -1198,17 +1140,6 @@ var init_KeysTable = __esm({
|
|
|
1198
1140
|
setBlockingKeyId(null);
|
|
1199
1141
|
}
|
|
1200
1142
|
};
|
|
1201
|
-
const handleResetSpend = async () => {
|
|
1202
|
-
if (!editingKey) return;
|
|
1203
|
-
setResetSpendSubmitting(true);
|
|
1204
|
-
try {
|
|
1205
|
-
await onResetKeySpend(editingKey.token ?? editingKey.key);
|
|
1206
|
-
setResetSpendConfirm(false);
|
|
1207
|
-
handleCloseEdit();
|
|
1208
|
-
} finally {
|
|
1209
|
-
setResetSpendSubmitting(false);
|
|
1210
|
-
}
|
|
1211
|
-
};
|
|
1212
1143
|
const handleConfirmDelete = async () => {
|
|
1213
1144
|
if (!deleteConfirmId) return;
|
|
1214
1145
|
setDeleteSubmitting(true);
|
|
@@ -1239,12 +1170,6 @@ var init_KeysTable = __esm({
|
|
|
1239
1170
|
setPruneConfirmCount(null);
|
|
1240
1171
|
}
|
|
1241
1172
|
};
|
|
1242
|
-
const modelOption = (m) => {
|
|
1243
|
-
const inCost = fmtCost(m.input_cost_per_token);
|
|
1244
|
-
const outCost = fmtCost(m.output_cost_per_token);
|
|
1245
|
-
const ctx = formatContextWindow(m.max_input_tokens, m.max_output_tokens);
|
|
1246
|
-
return /* @__PURE__ */ React3.createElement(Box3, null, /* @__PURE__ */ React3.createElement("span", null, m.model_name), m.supports_function_calling && " \u{1F527}", m.supports_vision && " \u{1F441}\uFE0F", ctx && /* @__PURE__ */ React3.createElement(Typography3, { variant: "caption", color: "text.secondary", sx: { ml: 1 } }, ctx), (inCost || outCost) && /* @__PURE__ */ React3.createElement(Typography3, { variant: "caption", color: "text.secondary", sx: { ml: 1 } }, inCost, " in \xB7 ", outCost, " out"));
|
|
1247
|
-
};
|
|
1248
1173
|
const renderTableBody = () => {
|
|
1249
1174
|
if (loading) {
|
|
1250
1175
|
return /* @__PURE__ */ React3.createElement(TableRow, null, /* @__PURE__ */ React3.createElement(TableCell, { colSpan: 8, sx: { py: 2 } }, /* @__PURE__ */ React3.createElement(Skeleton2, { variant: "rounded", height: 140 })));
|
|
@@ -1416,75 +1341,7 @@ var init_KeysTable = __esm({
|
|
|
1416
1341
|
))
|
|
1417
1342
|
},
|
|
1418
1343
|
/* @__PURE__ */ React3.createElement(TableContainer, null, /* @__PURE__ */ React3.createElement(Table, { size: "small", sx: dataTableSx }, /* @__PURE__ */ React3.createElement(TableHead, null, /* @__PURE__ */ React3.createElement(TableRow, null, /* @__PURE__ */ React3.createElement(TableCell, { sortDirection: sortKey === "alias" ? sortDirection : false }, /* @__PURE__ */ React3.createElement(TableSortLabel, { active: sortKey === "alias", direction: sortKey === "alias" ? sortDirection : "asc", onClick: () => handleSort("alias") }, "Alias")), /* @__PURE__ */ React3.createElement(TableCell, null, "Key ID"), /* @__PURE__ */ React3.createElement(TableCell, { sortDirection: sortKey === "created" ? sortDirection : false }, /* @__PURE__ */ React3.createElement(TableSortLabel, { active: sortKey === "created", direction: sortKey === "created" ? sortDirection : "asc", onClick: () => handleSort("created") }, "Created")), /* @__PURE__ */ React3.createElement(TableCell, { sortDirection: sortKey === "expires" ? sortDirection : false }, /* @__PURE__ */ React3.createElement(TableSortLabel, { active: sortKey === "expires", direction: sortKey === "expires" ? sortDirection : "asc", onClick: () => handleSort("expires") }, "Expires")), /* @__PURE__ */ React3.createElement(TableCell, { sortDirection: sortKey === "budget" ? sortDirection : false }, /* @__PURE__ */ React3.createElement(TableSortLabel, { active: sortKey === "budget", direction: sortKey === "budget" ? sortDirection : "asc", onClick: () => handleSort("budget") }, "Budget")), /* @__PURE__ */ React3.createElement(TableCell, { sortDirection: sortKey === "limits" ? sortDirection : false }, /* @__PURE__ */ React3.createElement(TableSortLabel, { active: sortKey === "limits", direction: sortKey === "limits" ? sortDirection : "asc", onClick: () => handleSort("limits") }, "TPM / RPM")), /* @__PURE__ */ React3.createElement(TableCell, { sortDirection: sortKey === "models" ? sortDirection : false }, /* @__PURE__ */ React3.createElement(TableSortLabel, { active: sortKey === "models", direction: sortKey === "models" ? sortDirection : "asc", onClick: () => handleSort("models") }, "Models")), /* @__PURE__ */ React3.createElement(TableCell, { align: "right" }, "Actions"))), /* @__PURE__ */ React3.createElement(TableBody, null, renderTableBody())))
|
|
1419
|
-
), /* @__PURE__ */ React3.createElement(Dialog, { open: !!
|
|
1420
|
-
TextField,
|
|
1421
|
-
{
|
|
1422
|
-
label: "Alias",
|
|
1423
|
-
value: editForm.key_alias || "",
|
|
1424
|
-
onChange: (e) => setEditForm({ ...editForm, key_alias: e.target.value }),
|
|
1425
|
-
fullWidth: true
|
|
1426
|
-
}
|
|
1427
|
-
), models.length > 0 && /* @__PURE__ */ React3.createElement(
|
|
1428
|
-
Autocomplete,
|
|
1429
|
-
{
|
|
1430
|
-
multiple: true,
|
|
1431
|
-
options: models,
|
|
1432
|
-
groupBy: (m) => m.mode || "other",
|
|
1433
|
-
getOptionLabel: (m) => m.model_name,
|
|
1434
|
-
value: editSelectedModels,
|
|
1435
|
-
onChange: (_e, selected) => setEditForm({ ...editForm, models: selected.map((m) => m.model_name) }),
|
|
1436
|
-
renderOption: (props, m) => /* @__PURE__ */ React3.createElement("li", { ...props }, modelOption(m)),
|
|
1437
|
-
renderInput: (params) => /* @__PURE__ */ React3.createElement(TextField, { ...params, label: "Models", fullWidth: true })
|
|
1438
|
-
}
|
|
1439
|
-
), /* @__PURE__ */ React3.createElement(
|
|
1440
|
-
TextField,
|
|
1441
|
-
{
|
|
1442
|
-
label: "Max Budget (USD)",
|
|
1443
|
-
type: "number",
|
|
1444
|
-
value: editForm.max_budget ?? "",
|
|
1445
|
-
onChange: (e) => setEditForm({ ...editForm, max_budget: e.target.value ? Number(e.target.value) : void 0 }),
|
|
1446
|
-
fullWidth: true
|
|
1447
|
-
}
|
|
1448
|
-
), /* @__PURE__ */ React3.createElement(
|
|
1449
|
-
TextField,
|
|
1450
|
-
{
|
|
1451
|
-
label: "TPM Limit",
|
|
1452
|
-
type: "number",
|
|
1453
|
-
value: editForm.tpm_limit ?? "",
|
|
1454
|
-
onChange: (e) => setEditForm({ ...editForm, tpm_limit: e.target.value ? Number(e.target.value) : void 0 }),
|
|
1455
|
-
helperText: "Max tokens per minute this key can consume across all models. Leave blank for no limit.",
|
|
1456
|
-
fullWidth: true
|
|
1457
|
-
}
|
|
1458
|
-
), /* @__PURE__ */ React3.createElement(
|
|
1459
|
-
TextField,
|
|
1460
|
-
{
|
|
1461
|
-
label: "RPM Limit",
|
|
1462
|
-
type: "number",
|
|
1463
|
-
value: editForm.rpm_limit ?? "",
|
|
1464
|
-
onChange: (e) => setEditForm({ ...editForm, rpm_limit: e.target.value ? Number(e.target.value) : void 0 }),
|
|
1465
|
-
helperText: "Max requests per minute this key can make across all models. Leave blank for no limit.",
|
|
1466
|
-
fullWidth: true
|
|
1467
|
-
}
|
|
1468
|
-
), /* @__PURE__ */ React3.createElement(Box3, { mt: 1, pt: 2, borderTop: "1px solid", sx: { borderColor: "divider" } }, /* @__PURE__ */ React3.createElement(Typography3, { variant: "caption", color: "text.secondary", display: "block", mb: 1 }, "Danger Zone"), !resetSpendConfirm ? /* @__PURE__ */ React3.createElement(
|
|
1469
|
-
Button2,
|
|
1470
|
-
{
|
|
1471
|
-
size: "small",
|
|
1472
|
-
color: "warning",
|
|
1473
|
-
variant: "outlined",
|
|
1474
|
-
onClick: () => setResetSpendConfirm(true)
|
|
1475
|
-
},
|
|
1476
|
-
"Reset Spend to $0"
|
|
1477
|
-
) : /* @__PURE__ */ React3.createElement(Box3, { display: "flex", alignItems: "center", gap: 1, flexWrap: "wrap" }, /* @__PURE__ */ React3.createElement(Typography3, { variant: "body2", color: "warning.main" }, "Zero out spend counter?"), /* @__PURE__ */ React3.createElement(
|
|
1478
|
-
Button2,
|
|
1479
|
-
{
|
|
1480
|
-
size: "small",
|
|
1481
|
-
color: "warning",
|
|
1482
|
-
variant: "contained",
|
|
1483
|
-
disabled: resetSpendSubmitting,
|
|
1484
|
-
onClick: handleResetSpend
|
|
1485
|
-
},
|
|
1486
|
-
resetSpendSubmitting ? /* @__PURE__ */ React3.createElement(CircularProgress, { size: 16 }) : "Confirm"
|
|
1487
|
-
), /* @__PURE__ */ React3.createElement(Button2, { size: "small", onClick: () => setResetSpendConfirm(false) }, "Cancel"))))), /* @__PURE__ */ React3.createElement(DialogActions, null, /* @__PURE__ */ React3.createElement(Button2, { onClick: handleCloseEdit }, "Cancel"), /* @__PURE__ */ React3.createElement(Button2, { onClick: handleUpdate, variant: "contained", color: "primary", disabled: editSubmitting }, editSubmitting ? /* @__PURE__ */ React3.createElement(CircularProgress, { size: 24 }) : "Save"))), /* @__PURE__ */ React3.createElement(Dialog, { open: !!deleteConfirmId, onClose: () => setDeleteConfirmId(null), maxWidth: "xs", fullWidth: true }, /* @__PURE__ */ React3.createElement(DialogTitle, null, "Revoke Key?"), /* @__PURE__ */ React3.createElement(DialogContent, null, /* @__PURE__ */ React3.createElement(Typography3, null, "This will permanently revoke the key. Any integrations using it will stop working immediately.")), /* @__PURE__ */ React3.createElement(DialogActions, null, /* @__PURE__ */ React3.createElement(Button2, { onClick: () => setDeleteConfirmId(null), disabled: deleteSubmitting }, "Cancel"), /* @__PURE__ */ React3.createElement(
|
|
1344
|
+
), /* @__PURE__ */ React3.createElement(Dialog, { open: !!deleteConfirmId, onClose: () => setDeleteConfirmId(null), maxWidth: "xs", fullWidth: true }, /* @__PURE__ */ React3.createElement(DialogTitle, null, "Revoke Key?"), /* @__PURE__ */ React3.createElement(DialogContent, null, /* @__PURE__ */ React3.createElement(Typography3, null, "This will permanently revoke the key. Any integrations using it will stop working immediately.")), /* @__PURE__ */ React3.createElement(DialogActions, null, /* @__PURE__ */ React3.createElement(Button2, { onClick: () => setDeleteConfirmId(null), disabled: deleteSubmitting }, "Cancel"), /* @__PURE__ */ React3.createElement(
|
|
1488
1345
|
Button2,
|
|
1489
1346
|
{
|
|
1490
1347
|
onClick: handleConfirmDelete,
|
|
@@ -1521,7 +1378,7 @@ var init_format = __esm({
|
|
|
1521
1378
|
}
|
|
1522
1379
|
});
|
|
1523
1380
|
|
|
1524
|
-
// src/components/
|
|
1381
|
+
// src/components/KeyFormDialog.tsx
|
|
1525
1382
|
import React4, { useState as useState2, useEffect, useMemo as useMemo3 } from "react";
|
|
1526
1383
|
import Box4 from "@mui/material/Box";
|
|
1527
1384
|
import Typography4 from "@mui/material/Typography";
|
|
@@ -1534,7 +1391,7 @@ import MenuItem from "@mui/material/MenuItem";
|
|
|
1534
1391
|
import Button3 from "@mui/material/Button";
|
|
1535
1392
|
import IconButton2 from "@mui/material/IconButton";
|
|
1536
1393
|
import CircularProgress2 from "@mui/material/CircularProgress";
|
|
1537
|
-
import
|
|
1394
|
+
import Autocomplete from "@mui/material/Autocomplete";
|
|
1538
1395
|
import Checkbox from "@mui/material/Checkbox";
|
|
1539
1396
|
import FormControlLabel from "@mui/material/FormControlLabel";
|
|
1540
1397
|
import Alert from "@mui/material/Alert";
|
|
@@ -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 generateDefaultAlias,
|
|
1665
|
-
var
|
|
1666
|
-
"src/components/
|
|
1521
|
+
var 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
|
init_format();
|
|
1669
1526
|
generateDefaultAlias = (username) => {
|
|
@@ -1671,8 +1528,8 @@ var init_GenerateKeyDialog = __esm({
|
|
|
1671
1528
|
const hash = Math.random().toString(36).slice(2, 8);
|
|
1672
1529
|
return `${base}-${hash}`;
|
|
1673
1530
|
};
|
|
1674
|
-
|
|
1675
|
-
alias:
|
|
1531
|
+
createForm = (username) => ({
|
|
1532
|
+
alias: generateDefaultAlias(username),
|
|
1676
1533
|
models: [],
|
|
1677
1534
|
duration: "30d",
|
|
1678
1535
|
max_budget: 100,
|
|
@@ -1681,6 +1538,13 @@ var init_GenerateKeyDialog = __esm({
|
|
|
1681
1538
|
team_id: void 0,
|
|
1682
1539
|
key_type: "llm_api"
|
|
1683
1540
|
});
|
|
1541
|
+
editForm = (k) => ({
|
|
1542
|
+
key_alias: k.key_alias ?? "",
|
|
1543
|
+
models: k.models ?? [],
|
|
1544
|
+
max_budget: k.max_budget,
|
|
1545
|
+
tpm_limit: k.tpm_limit,
|
|
1546
|
+
rpm_limit: k.rpm_limit
|
|
1547
|
+
});
|
|
1684
1548
|
ALL_PROXY_MODELS = "all-proxy-models";
|
|
1685
1549
|
SNIPPET_FILE_HINTS = {
|
|
1686
1550
|
opencode: "Add to ~/.config/opencode/opencode.json",
|
|
@@ -1727,46 +1591,79 @@ var init_GenerateKeyDialog = __esm({
|
|
|
1727
1591
|
model && /* @__PURE__ */ React4.createElement(Typography4, { variant: "caption", color: "text.secondary", display: "block", mt: 1 }, "Using model \u201C", model, "\u201D \u2014 swap it for any model you have access to.")
|
|
1728
1592
|
));
|
|
1729
1593
|
};
|
|
1730
|
-
|
|
1594
|
+
KeyFormDialog = ({
|
|
1731
1595
|
open,
|
|
1732
1596
|
onClose,
|
|
1597
|
+
mode,
|
|
1598
|
+
keyToEdit = null,
|
|
1733
1599
|
keys,
|
|
1734
1600
|
models,
|
|
1735
1601
|
teams,
|
|
1736
1602
|
username,
|
|
1737
1603
|
keyGenerationSettings,
|
|
1738
|
-
|
|
1604
|
+
onCreateKey,
|
|
1605
|
+
onUpdateKey,
|
|
1606
|
+
onResetKeySpend,
|
|
1739
1607
|
onGetConfig
|
|
1740
1608
|
}) => {
|
|
1741
|
-
const
|
|
1609
|
+
const isCreate = mode === "create";
|
|
1610
|
+
const [generateForm, setGenerateForm] = useState2(createForm(username));
|
|
1611
|
+
const [editFormState, setEditFormState] = useState2({});
|
|
1742
1612
|
const [unlimitedBudget, setUnlimitedBudget] = useState2(false);
|
|
1743
1613
|
const [submitting, setSubmitting] = useState2(false);
|
|
1744
1614
|
const [newKeyValue, setNewKeyValue] = useState2(null);
|
|
1745
1615
|
const [newKeySnippets, setNewKeySnippets] = useState2(null);
|
|
1746
1616
|
const [newKeyModel, setNewKeyModel] = useState2("");
|
|
1747
1617
|
const [generateError, setGenerateError] = useState2(null);
|
|
1618
|
+
const [editError, setEditError] = useState2(null);
|
|
1619
|
+
const [resetSpendConfirm, setResetSpendConfirm] = useState2(false);
|
|
1620
|
+
const [resetSpendSubmitting, setResetSpendSubmitting] = useState2(false);
|
|
1748
1621
|
useEffect(() => {
|
|
1749
1622
|
if (open) {
|
|
1750
|
-
|
|
1751
|
-
|
|
1623
|
+
if (isCreate) {
|
|
1624
|
+
setGenerateForm(createForm(username));
|
|
1625
|
+
} else if (keyToEdit) {
|
|
1626
|
+
setEditFormState(editForm(keyToEdit));
|
|
1627
|
+
setUnlimitedBudget(
|
|
1628
|
+
keyGenerationSettings?.allowUnlimitedBudget === true && (keyToEdit.max_budget === null || keyToEdit.max_budget === void 0)
|
|
1629
|
+
);
|
|
1630
|
+
} else {
|
|
1631
|
+
setEditFormState({});
|
|
1632
|
+
setUnlimitedBudget(false);
|
|
1633
|
+
}
|
|
1752
1634
|
setNewKeyValue(null);
|
|
1753
1635
|
setNewKeySnippets(null);
|
|
1754
1636
|
setNewKeyModel("");
|
|
1755
1637
|
setGenerateError(null);
|
|
1638
|
+
setEditError(null);
|
|
1639
|
+
setResetSpendConfirm(false);
|
|
1756
1640
|
}
|
|
1757
|
-
}, [open, username]);
|
|
1641
|
+
}, [open, isCreate, keyToEdit, username, keyGenerationSettings?.allowUnlimitedBudget]);
|
|
1758
1642
|
const allowUnlimitedBudget = keyGenerationSettings?.allowUnlimitedBudget ?? false;
|
|
1759
1643
|
const teamRequired = keyGenerationSettings?.teamRequired ?? true;
|
|
1760
|
-
const
|
|
1644
|
+
const formData = isCreate ? generateForm : editFormState;
|
|
1645
|
+
const setFormData = (patch) => {
|
|
1646
|
+
if (isCreate) {
|
|
1647
|
+
setGenerateForm((prev) => ({ ...prev, ...patch }));
|
|
1648
|
+
} else {
|
|
1649
|
+
setEditFormState((prev) => ({ ...prev, ...patch }));
|
|
1650
|
+
}
|
|
1651
|
+
};
|
|
1652
|
+
const currentAlias = (isCreate ? generateForm.alias : editFormState.key_alias) ?? "";
|
|
1653
|
+
const setAliasField = (value) => {
|
|
1654
|
+
if (isCreate) setFormData({ alias: value });
|
|
1655
|
+
else setFormData({ key_alias: value });
|
|
1656
|
+
};
|
|
1657
|
+
const selectedTeam = teams.find((t) => t.team_id === (isCreate ? generateForm.team_id : void 0)) ?? null;
|
|
1761
1658
|
const availableModels = useMemo3(() => {
|
|
1762
1659
|
return models.filter((m) => isModelAllowedByTeam(m, selectedTeam?.models));
|
|
1763
1660
|
}, [models, selectedTeam]);
|
|
1764
1661
|
const selectedModels = availableModels.filter((m) => (formData.models || []).includes(m.model_name));
|
|
1765
|
-
const aliasError = !
|
|
1766
|
-
const teamError = teamRequired && !
|
|
1662
|
+
const aliasError = !currentAlias.trim();
|
|
1663
|
+
const teamError = isCreate && teamRequired && !generateForm.team_id;
|
|
1767
1664
|
const budgetInvalid = !unlimitedBudget && (formData.max_budget === void 0 || formData.max_budget === null || formData.max_budget <= 0);
|
|
1768
|
-
const
|
|
1769
|
-
const aliasDuplicate = !!
|
|
1665
|
+
const canSubmit = !aliasError && !teamError && !budgetInvalid && !submitting;
|
|
1666
|
+
const aliasDuplicate = !!currentAlias && keys.some((k) => k.key_alias === currentAlias && k !== keyToEdit);
|
|
1770
1667
|
const budgetEstimate = useMemo3(() => {
|
|
1771
1668
|
if (unlimitedBudget || budgetInvalid) return null;
|
|
1772
1669
|
const inputCosts = selectedModels.map((m) => m.input_cost_per_token).filter((c) => typeof c === "number" && c > 0);
|
|
@@ -1779,11 +1676,11 @@ var init_GenerateKeyDialog = __esm({
|
|
|
1779
1676
|
setGenerateError(null);
|
|
1780
1677
|
try {
|
|
1781
1678
|
const request = {
|
|
1782
|
-
...
|
|
1783
|
-
max_budget: unlimitedBudget ? null :
|
|
1679
|
+
...generateForm,
|
|
1680
|
+
max_budget: unlimitedBudget ? null : generateForm.max_budget
|
|
1784
1681
|
};
|
|
1785
|
-
const response = await
|
|
1786
|
-
const model =
|
|
1682
|
+
const response = await onCreateKey(request);
|
|
1683
|
+
const model = generateForm.models?.[0] ?? availableModels[0]?.model_name ?? "";
|
|
1787
1684
|
setNewKeyValue(response.key);
|
|
1788
1685
|
setNewKeyModel(model);
|
|
1789
1686
|
setNewKeySnippets(null);
|
|
@@ -1792,7 +1689,7 @@ var init_GenerateKeyDialog = __esm({
|
|
|
1792
1689
|
setNewKeySnippets(buildSnippets(config.baseUrl, response.key, model));
|
|
1793
1690
|
} catch {
|
|
1794
1691
|
}
|
|
1795
|
-
|
|
1692
|
+
setGenerateForm(createForm(username));
|
|
1796
1693
|
setUnlimitedBudget(false);
|
|
1797
1694
|
} catch (error) {
|
|
1798
1695
|
setGenerateError(error?.message ?? "Failed to generate key");
|
|
@@ -1800,27 +1697,58 @@ var init_GenerateKeyDialog = __esm({
|
|
|
1800
1697
|
setSubmitting(false);
|
|
1801
1698
|
}
|
|
1802
1699
|
};
|
|
1700
|
+
const handleUpdate = async () => {
|
|
1701
|
+
if (!keyToEdit) return;
|
|
1702
|
+
setSubmitting(true);
|
|
1703
|
+
setEditError(null);
|
|
1704
|
+
try {
|
|
1705
|
+
const request = {
|
|
1706
|
+
...editFormState,
|
|
1707
|
+
max_budget: unlimitedBudget ? null : editFormState.max_budget
|
|
1708
|
+
};
|
|
1709
|
+
await onUpdateKey(keyToEdit.token ?? keyToEdit.key, request);
|
|
1710
|
+
onClose();
|
|
1711
|
+
} catch (error) {
|
|
1712
|
+
setEditError(error?.message ?? "Failed to update key");
|
|
1713
|
+
} finally {
|
|
1714
|
+
setSubmitting(false);
|
|
1715
|
+
}
|
|
1716
|
+
};
|
|
1717
|
+
const handleResetSpend = async () => {
|
|
1718
|
+
if (!keyToEdit || !onResetKeySpend) return;
|
|
1719
|
+
setResetSpendSubmitting(true);
|
|
1720
|
+
try {
|
|
1721
|
+
await onResetKeySpend(keyToEdit.token ?? keyToEdit.key);
|
|
1722
|
+
setResetSpendConfirm(false);
|
|
1723
|
+
onClose();
|
|
1724
|
+
} finally {
|
|
1725
|
+
setResetSpendSubmitting(false);
|
|
1726
|
+
}
|
|
1727
|
+
};
|
|
1803
1728
|
const handleClose = () => {
|
|
1804
1729
|
onClose();
|
|
1805
1730
|
setGenerateError(null);
|
|
1731
|
+
setEditError(null);
|
|
1806
1732
|
setNewKeyValue(null);
|
|
1807
1733
|
setNewKeySnippets(null);
|
|
1808
|
-
|
|
1734
|
+
setGenerateForm(createForm(username));
|
|
1735
|
+
setEditFormState({});
|
|
1809
1736
|
setUnlimitedBudget(false);
|
|
1737
|
+
setResetSpendConfirm(false);
|
|
1810
1738
|
};
|
|
1811
1739
|
const copyToClipboard = (text) => {
|
|
1812
1740
|
navigator.clipboard.writeText(text);
|
|
1813
1741
|
};
|
|
1814
1742
|
const modelOption = (m) => {
|
|
1815
|
-
const inCost =
|
|
1816
|
-
const outCost =
|
|
1817
|
-
const ctx =
|
|
1743
|
+
const inCost = fmtCost(m.input_cost_per_token);
|
|
1744
|
+
const outCost = fmtCost(m.output_cost_per_token);
|
|
1745
|
+
const ctx = formatContextWindow(m.max_input_tokens, m.max_output_tokens);
|
|
1818
1746
|
return /* @__PURE__ */ React4.createElement(Box4, null, /* @__PURE__ */ React4.createElement("span", null, m.model_name), m.supports_function_calling && " \u{1F527}", m.supports_vision && " \u{1F441}\uFE0F", ctx && /* @__PURE__ */ React4.createElement(Typography4, { variant: "caption", color: "text.secondary", sx: { ml: 1 } }, ctx), (inCost || outCost) && /* @__PURE__ */ React4.createElement(Typography4, { variant: "caption", color: "text.secondary", sx: { ml: 1 } }, inCost, " in \xB7 ", outCost, " out"));
|
|
1819
1747
|
};
|
|
1820
1748
|
const renderTeamField = () => {
|
|
1821
1749
|
if (teams.length > 0) {
|
|
1822
1750
|
return /* @__PURE__ */ React4.createElement(
|
|
1823
|
-
|
|
1751
|
+
Autocomplete,
|
|
1824
1752
|
{
|
|
1825
1753
|
options: teams,
|
|
1826
1754
|
getOptionLabel: (t) => t.team_alias || t.team_id,
|
|
@@ -1830,28 +1758,61 @@ var init_GenerateKeyDialog = __esm({
|
|
|
1830
1758
|
const model = models.find((m) => m.model_name === name);
|
|
1831
1759
|
return model ? isModelAllowedByTeam(model, team?.models) : false;
|
|
1832
1760
|
});
|
|
1833
|
-
setFormData({
|
|
1761
|
+
setFormData({ team_id: team?.team_id, models: restrictedModels });
|
|
1834
1762
|
},
|
|
1763
|
+
disabled: !isCreate,
|
|
1835
1764
|
renderInput: (params) => /* @__PURE__ */ React4.createElement(
|
|
1836
1765
|
TextField2,
|
|
1837
1766
|
{
|
|
1838
1767
|
...params,
|
|
1839
1768
|
label: "Team",
|
|
1840
1769
|
error: teamError,
|
|
1841
|
-
helperText: teamHelperText(teamError, teamRequired),
|
|
1842
|
-
required: teamRequired,
|
|
1770
|
+
helperText: isCreate ? teamHelperText(teamError, teamRequired) : "Team binding is fixed after creation \u2014 delete and recreate the key to change it",
|
|
1771
|
+
required: isCreate && teamRequired,
|
|
1843
1772
|
fullWidth: true
|
|
1844
1773
|
}
|
|
1845
1774
|
)
|
|
1846
1775
|
}
|
|
1847
1776
|
);
|
|
1848
1777
|
}
|
|
1849
|
-
if (teamRequired) {
|
|
1778
|
+
if (teamRequired && isCreate) {
|
|
1850
1779
|
return /* @__PURE__ */ React4.createElement(Typography4, { variant: "body2", color: "error" }, "Team selection is required, but you don't belong to any team yet \u2014 contact your administrator.");
|
|
1851
1780
|
}
|
|
1852
1781
|
return null;
|
|
1853
1782
|
};
|
|
1854
|
-
|
|
1783
|
+
const editKeyId = keyToEdit?.token ?? keyToEdit?.key ?? "";
|
|
1784
|
+
const renderDangerZone = () => {
|
|
1785
|
+
if (isCreate || !onResetKeySpend) return null;
|
|
1786
|
+
return /* @__PURE__ */ React4.createElement(Box4, { mt: 1, pt: 2, borderTop: "1px solid", sx: { borderColor: "divider" } }, /* @__PURE__ */ React4.createElement(Typography4, { variant: "caption", color: "text.secondary", display: "block", mb: 1 }, "Danger Zone"), !resetSpendConfirm ? /* @__PURE__ */ React4.createElement(
|
|
1787
|
+
Button3,
|
|
1788
|
+
{
|
|
1789
|
+
size: "small",
|
|
1790
|
+
color: "warning",
|
|
1791
|
+
variant: "outlined",
|
|
1792
|
+
onClick: () => setResetSpendConfirm(true)
|
|
1793
|
+
},
|
|
1794
|
+
"Reset Spend to $0"
|
|
1795
|
+
) : /* @__PURE__ */ React4.createElement(Box4, { display: "flex", alignItems: "center", gap: 1, flexWrap: "wrap" }, /* @__PURE__ */ React4.createElement(Typography4, { variant: "body2", color: "warning.main" }, "Zero out spend counter?"), /* @__PURE__ */ React4.createElement(
|
|
1796
|
+
Button3,
|
|
1797
|
+
{
|
|
1798
|
+
size: "small",
|
|
1799
|
+
color: "warning",
|
|
1800
|
+
variant: "contained",
|
|
1801
|
+
disabled: resetSpendSubmitting,
|
|
1802
|
+
onClick: handleResetSpend
|
|
1803
|
+
},
|
|
1804
|
+
resetSpendSubmitting ? /* @__PURE__ */ React4.createElement(CircularProgress2, { size: 16 }) : "Confirm"
|
|
1805
|
+
), /* @__PURE__ */ React4.createElement(Button3, { size: "small", onClick: () => setResetSpendConfirm(false) }, "Cancel")));
|
|
1806
|
+
};
|
|
1807
|
+
const maskKey = (key) => {
|
|
1808
|
+
if (key.length <= 8) return "***";
|
|
1809
|
+
return `${key.slice(0, 4)}...${key.slice(-4)}`;
|
|
1810
|
+
};
|
|
1811
|
+
let dialogTitle = "Edit Key";
|
|
1812
|
+
if (newKeyValue) dialogTitle = "Key Generated";
|
|
1813
|
+
else if (isCreate) dialogTitle = "Generate New Key";
|
|
1814
|
+
const submitLabel = isCreate ? "Generate" : "Save";
|
|
1815
|
+
return /* @__PURE__ */ React4.createElement(Dialog2, { open, onClose: handleClose, maxWidth: "sm", fullWidth: true }, /* @__PURE__ */ React4.createElement(DialogTitle2, null, dialogTitle), /* @__PURE__ */ React4.createElement(DialogContent2, null, newKeyValue ? /* @__PURE__ */ React4.createElement(Box4, null, /* @__PURE__ */ React4.createElement(Typography4, { variant: "body2", color: "text.secondary", gutterBottom: true }, "Copy this key now. You won't be able to see it again."), /* @__PURE__ */ React4.createElement(
|
|
1855
1816
|
Box4,
|
|
1856
1817
|
{
|
|
1857
1818
|
display: "flex",
|
|
@@ -1900,14 +1861,15 @@ var init_GenerateKeyDialog = __esm({
|
|
|
1900
1861
|
newKeySnippets.publicEndpoint
|
|
1901
1862
|
),
|
|
1902
1863
|
/* @__PURE__ */ React4.createElement(IconButton2, { size: "small", onClick: () => copyToClipboard(newKeySnippets.publicEndpoint) }, /* @__PURE__ */ React4.createElement(ContentCopy2, { fontSize: "small" }))
|
|
1903
|
-
)), newKeySnippets && /* @__PURE__ */ React4.createElement(Box4, { mt: 3 }, /* @__PURE__ */ React4.createElement(Box4, { display: "flex", alignItems: "center", gap: 1, mb: 1 }, /* @__PURE__ */ React4.createElement(Code, { fontSize: "small", color: "action" }), /* @__PURE__ */ React4.createElement(Typography4, { variant: "subtitle2" }, "Start calling the proxy \u2014 paste and run")), /* @__PURE__ */ React4.createElement(SnippetTabs, { snippets: newKeySnippets, model: newKeyModel, onCopy: copyToClipboard }))) : /* @__PURE__ */ React4.createElement(Box4, { display: "flex", flexDirection: "column", gap: 2, mt: 1 }, generateError && /* @__PURE__ */ React4.createElement(Alert, { severity: "error", onClose: () => setGenerateError(null) }, generateError), /* @__PURE__ */ React4.createElement(
|
|
1864
|
+
)), newKeySnippets && /* @__PURE__ */ React4.createElement(Box4, { mt: 3 }, /* @__PURE__ */ React4.createElement(Box4, { display: "flex", alignItems: "center", gap: 1, mb: 1 }, /* @__PURE__ */ React4.createElement(Code, { fontSize: "small", color: "action" }), /* @__PURE__ */ React4.createElement(Typography4, { variant: "subtitle2" }, "Start calling the proxy \u2014 paste and run")), /* @__PURE__ */ React4.createElement(SnippetTabs, { snippets: newKeySnippets, model: newKeyModel, onCopy: copyToClipboard }))) : /* @__PURE__ */ React4.createElement(Box4, { display: "flex", flexDirection: "column", gap: 2, mt: 1 }, !isCreate && /* @__PURE__ */ React4.createElement(Typography4, { variant: "body2", color: "text.secondary" }, /* @__PURE__ */ React4.createElement("code", { style: { fontFamily: "monospace", color: "inherit" } }, maskKey(editKeyId))), generateError && /* @__PURE__ */ React4.createElement(Alert, { severity: "error", onClose: () => setGenerateError(null) }, generateError), editError && /* @__PURE__ */ React4.createElement(Alert, { severity: "error", onClose: () => setEditError(null) }, editError), /* @__PURE__ */ React4.createElement(
|
|
1904
1865
|
TextField2,
|
|
1905
1866
|
{
|
|
1906
1867
|
label: "Alias",
|
|
1907
|
-
value:
|
|
1868
|
+
value: currentAlias,
|
|
1908
1869
|
onChange: (e) => {
|
|
1909
|
-
|
|
1870
|
+
setAliasField(e.target.value);
|
|
1910
1871
|
setGenerateError(null);
|
|
1872
|
+
setEditError(null);
|
|
1911
1873
|
},
|
|
1912
1874
|
error: aliasError,
|
|
1913
1875
|
color: aliasDuplicate ? "warning" : void 0,
|
|
@@ -1915,13 +1877,13 @@ var init_GenerateKeyDialog = __esm({
|
|
|
1915
1877
|
required: true,
|
|
1916
1878
|
fullWidth: true
|
|
1917
1879
|
}
|
|
1918
|
-
), /* @__PURE__ */ React4.createElement(
|
|
1880
|
+
), !isCreate ? null : /* @__PURE__ */ React4.createElement(
|
|
1919
1881
|
TextField2,
|
|
1920
1882
|
{
|
|
1921
1883
|
select: true,
|
|
1922
1884
|
label: "Duration",
|
|
1923
|
-
value:
|
|
1924
|
-
onChange: (e) => setFormData({
|
|
1885
|
+
value: generateForm.duration || "30d",
|
|
1886
|
+
onChange: (e) => setFormData({ duration: e.target.value }),
|
|
1925
1887
|
fullWidth: true
|
|
1926
1888
|
},
|
|
1927
1889
|
/* @__PURE__ */ React4.createElement(MenuItem, { value: "1d" }, "1 Day"),
|
|
@@ -1930,14 +1892,14 @@ var init_GenerateKeyDialog = __esm({
|
|
|
1930
1892
|
/* @__PURE__ */ React4.createElement(MenuItem, { value: "90d" }, "90 Days"),
|
|
1931
1893
|
/* @__PURE__ */ React4.createElement(MenuItem, { value: "1y" }, "1 Year")
|
|
1932
1894
|
), renderTeamField(), availableModels.length > 0 && /* @__PURE__ */ React4.createElement(
|
|
1933
|
-
|
|
1895
|
+
Autocomplete,
|
|
1934
1896
|
{
|
|
1935
1897
|
multiple: true,
|
|
1936
1898
|
options: availableModels,
|
|
1937
1899
|
groupBy: (m) => m.mode || "other",
|
|
1938
1900
|
getOptionLabel: (m) => m.model_name,
|
|
1939
1901
|
value: selectedModels,
|
|
1940
|
-
onChange: (_e, selected) => setFormData({
|
|
1902
|
+
onChange: (_e, selected) => setFormData({ models: selected.map((m) => m.model_name) }),
|
|
1941
1903
|
renderOption: (props, m) => /* @__PURE__ */ React4.createElement("li", { ...props }, modelOption(m)),
|
|
1942
1904
|
renderInput: (params) => /* @__PURE__ */ React4.createElement(
|
|
1943
1905
|
TextField2,
|
|
@@ -1959,9 +1921,9 @@ var init_GenerateKeyDialog = __esm({
|
|
|
1959
1921
|
onChange: (e) => {
|
|
1960
1922
|
setUnlimitedBudget(e.target.checked);
|
|
1961
1923
|
if (e.target.checked) {
|
|
1962
|
-
setFormData({
|
|
1924
|
+
setFormData({ max_budget: null });
|
|
1963
1925
|
} else {
|
|
1964
|
-
setFormData({
|
|
1926
|
+
setFormData({ max_budget: editFormState.max_budget ?? generateForm.max_budget ?? 100 });
|
|
1965
1927
|
}
|
|
1966
1928
|
}
|
|
1967
1929
|
}
|
|
@@ -1974,9 +1936,9 @@ var init_GenerateKeyDialog = __esm({
|
|
|
1974
1936
|
label: "Max Budget (USD)",
|
|
1975
1937
|
type: "number",
|
|
1976
1938
|
value: unlimitedBudget ? "" : formData.max_budget ?? "",
|
|
1977
|
-
onChange: (e) => setFormData({
|
|
1939
|
+
onChange: (e) => setFormData({ max_budget: e.target.value ? Number(e.target.value) : void 0 }),
|
|
1978
1940
|
error: budgetInvalid,
|
|
1979
|
-
helperText: budgetHelperText(budgetInvalid, budgetEstimate),
|
|
1941
|
+
helperText: budgetHelperText(budgetInvalid, budgetEstimate, unlimitedBudget),
|
|
1980
1942
|
disabled: unlimitedBudget,
|
|
1981
1943
|
required: true,
|
|
1982
1944
|
fullWidth: true
|
|
@@ -1987,7 +1949,7 @@ var init_GenerateKeyDialog = __esm({
|
|
|
1987
1949
|
label: "TPM Limit",
|
|
1988
1950
|
type: "number",
|
|
1989
1951
|
value: formData.tpm_limit ?? "",
|
|
1990
|
-
onChange: (e) => setFormData({
|
|
1952
|
+
onChange: (e) => setFormData({ tpm_limit: e.target.value ? Number(e.target.value) : void 0 }),
|
|
1991
1953
|
helperText: "Max tokens per minute this key can consume across all models. Leave blank for no limit.",
|
|
1992
1954
|
fullWidth: true
|
|
1993
1955
|
}
|
|
@@ -1997,19 +1959,19 @@ var init_GenerateKeyDialog = __esm({
|
|
|
1997
1959
|
label: "RPM Limit",
|
|
1998
1960
|
type: "number",
|
|
1999
1961
|
value: formData.rpm_limit ?? "",
|
|
2000
|
-
onChange: (e) => setFormData({
|
|
1962
|
+
onChange: (e) => setFormData({ rpm_limit: e.target.value ? Number(e.target.value) : void 0 }),
|
|
2001
1963
|
helperText: "Max requests per minute this key can make across all models. Leave blank for no limit.",
|
|
2002
1964
|
fullWidth: true
|
|
2003
1965
|
}
|
|
2004
|
-
))))), /* @__PURE__ */ React4.createElement(DialogActions2, null, newKeyValue ? /* @__PURE__ */ React4.createElement(Button3, { onClick: handleClose, variant: "contained", color: "success" }, "Done") : /* @__PURE__ */ React4.createElement(React4.Fragment, null, /* @__PURE__ */ React4.createElement(Button3, { onClick: handleClose }, "Cancel"), /* @__PURE__ */ React4.createElement(
|
|
1966
|
+
))), renderDangerZone())), /* @__PURE__ */ React4.createElement(DialogActions2, null, newKeyValue ? /* @__PURE__ */ React4.createElement(Button3, { onClick: handleClose, variant: "contained", color: "success" }, "Done") : /* @__PURE__ */ React4.createElement(React4.Fragment, null, /* @__PURE__ */ React4.createElement(Button3, { onClick: handleClose }, "Cancel"), /* @__PURE__ */ React4.createElement(
|
|
2005
1967
|
Button3,
|
|
2006
1968
|
{
|
|
2007
|
-
onClick: handleGenerate,
|
|
1969
|
+
onClick: isCreate ? handleGenerate : handleUpdate,
|
|
2008
1970
|
variant: "contained",
|
|
2009
1971
|
color: "primary",
|
|
2010
|
-
disabled: !
|
|
1972
|
+
disabled: !canSubmit
|
|
2011
1973
|
},
|
|
2012
|
-
submitting ? /* @__PURE__ */ React4.createElement(CircularProgress2, { size: 24 }) :
|
|
1974
|
+
submitting ? /* @__PURE__ */ React4.createElement(CircularProgress2, { size: 24 }) : submitLabel
|
|
2013
1975
|
))));
|
|
2014
1976
|
};
|
|
2015
1977
|
}
|
|
@@ -2026,7 +1988,7 @@ import Button4 from "@mui/material/Button";
|
|
|
2026
1988
|
import Alert2 from "@mui/material/Alert";
|
|
2027
1989
|
import Checkbox2 from "@mui/material/Checkbox";
|
|
2028
1990
|
import FormControlLabel2 from "@mui/material/FormControlLabel";
|
|
2029
|
-
import
|
|
1991
|
+
import Autocomplete2 from "@mui/material/Autocomplete";
|
|
2030
1992
|
import CircularProgress3 from "@mui/material/CircularProgress";
|
|
2031
1993
|
import MenuItem2 from "@mui/material/MenuItem";
|
|
2032
1994
|
import Box5 from "@mui/material/Box";
|
|
@@ -2295,7 +2257,7 @@ var init_ManageTeamDialog = __esm({
|
|
|
2295
2257
|
fullWidth: true
|
|
2296
2258
|
}
|
|
2297
2259
|
), /* @__PURE__ */ React5.createElement(
|
|
2298
|
-
|
|
2260
|
+
Autocomplete2,
|
|
2299
2261
|
{
|
|
2300
2262
|
multiple: true,
|
|
2301
2263
|
options: modelNames,
|
|
@@ -2368,7 +2330,7 @@ var init_ManageTeamDialog = __esm({
|
|
|
2368
2330
|
},
|
|
2369
2331
|
/* @__PURE__ */ React5.createElement(DeleteIcon, { fontSize: "small" })
|
|
2370
2332
|
))))))), /* @__PURE__ */ React5.createElement(Box5, { sx: { display: "flex", gap: 1, alignItems: "flex-start", flexWrap: "wrap" } }, /* @__PURE__ */ React5.createElement(
|
|
2371
|
-
|
|
2333
|
+
Autocomplete2,
|
|
2372
2334
|
{
|
|
2373
2335
|
freeSolo: true,
|
|
2374
2336
|
autoHighlight: true,
|
|
@@ -2427,7 +2389,7 @@ var init_ManageTeamDialog = __esm({
|
|
|
2427
2389
|
},
|
|
2428
2390
|
"Add"
|
|
2429
2391
|
))), showKnowledgeBases && /* @__PURE__ */ React5.createElement(Box5, null, /* @__PURE__ */ React5.createElement(Divider2, { sx: { my: 1 } }), /* @__PURE__ */ React5.createElement(Typography5, { variant: "subtitle2", sx: { mb: 1 } }, "Knowledge Bases"), /* @__PURE__ */ React5.createElement(Typography5, { variant: "body2", color: "text.secondary", sx: { mb: 1 } }, "Attaching a knowledge base exposes its documents to every key in this team."), kbError && /* @__PURE__ */ React5.createElement(Alert2, { severity: "error", sx: { mb: 1 }, onClose: () => setKbError(null) }, kbError), /* @__PURE__ */ React5.createElement(
|
|
2430
|
-
|
|
2392
|
+
Autocomplete2,
|
|
2431
2393
|
{
|
|
2432
2394
|
multiple: true,
|
|
2433
2395
|
options: kbOptions,
|
|
@@ -2446,7 +2408,7 @@ var init_ManageTeamDialog = __esm({
|
|
|
2446
2408
|
},
|
|
2447
2409
|
kbBusy ? "Saving\u2026" : "Save knowledge bases"
|
|
2448
2410
|
))), showMcpServers && /* @__PURE__ */ React5.createElement(Box5, null, /* @__PURE__ */ React5.createElement(Divider2, { sx: { my: 1 } }), /* @__PURE__ */ React5.createElement(Typography5, { variant: "subtitle2", sx: { mb: 1 } }, "MCP Servers"), /* @__PURE__ */ React5.createElement(Typography5, { variant: "body2", color: "text.secondary", sx: { mb: 1 } }, "Attaching an MCP server lets every key in this team invoke that server's tools through the model \u2014 including any side effects those tools have."), mcpError && /* @__PURE__ */ React5.createElement(Alert2, { severity: "error", sx: { mb: 1 }, onClose: () => setMcpError(null) }, mcpError), /* @__PURE__ */ React5.createElement(
|
|
2449
|
-
|
|
2411
|
+
Autocomplete2,
|
|
2450
2412
|
{
|
|
2451
2413
|
multiple: true,
|
|
2452
2414
|
options: mcpOptions,
|
|
@@ -2924,6 +2886,13 @@ function buildBudgetSummary(user, teams, keys, maxKeys = 3) {
|
|
|
2924
2886
|
teams: teamLimits
|
|
2925
2887
|
};
|
|
2926
2888
|
}
|
|
2889
|
+
function allBudgetLimits(summary) {
|
|
2890
|
+
return [
|
|
2891
|
+
...summary.keys,
|
|
2892
|
+
...summary.user ? [summary.user] : [],
|
|
2893
|
+
...summary.teams
|
|
2894
|
+
];
|
|
2895
|
+
}
|
|
2927
2896
|
function buildBudgetGauges(summary) {
|
|
2928
2897
|
const byLevel = {
|
|
2929
2898
|
key: summary.keys,
|
|
@@ -2974,18 +2943,12 @@ var init_budget = __esm({
|
|
|
2974
2943
|
}
|
|
2975
2944
|
});
|
|
2976
2945
|
|
|
2977
|
-
// src/components/
|
|
2978
|
-
import React7
|
|
2979
|
-
import Paper5 from "@mui/material/Paper";
|
|
2946
|
+
// src/components/BudgetLimitList.tsx
|
|
2947
|
+
import React7 from "react";
|
|
2980
2948
|
import Box7 from "@mui/material/Box";
|
|
2949
|
+
import Paper5 from "@mui/material/Paper";
|
|
2981
2950
|
import Typography7 from "@mui/material/Typography";
|
|
2982
|
-
import
|
|
2983
|
-
import Alert3 from "@mui/material/Alert";
|
|
2984
|
-
import Collapse from "@mui/material/Collapse";
|
|
2985
|
-
import IconButton4 from "@mui/material/IconButton";
|
|
2986
|
-
import { ExpandMore as ExpandMore2 } from "@mui/icons-material";
|
|
2987
|
-
import { alpha as alpha4 } from "@mui/material/styles";
|
|
2988
|
-
import { useApi as useApi2 } from "@backstage/core-plugin-api";
|
|
2951
|
+
import { alpha as alpha4, useTheme as useTheme2 } from "@mui/material/styles";
|
|
2989
2952
|
import { Link } from "@backstage/core-components";
|
|
2990
2953
|
function levelToneColor(tone, theme) {
|
|
2991
2954
|
switch (tone) {
|
|
@@ -3001,13 +2964,119 @@ function levelToneColor(tone, theme) {
|
|
|
3001
2964
|
return theme.palette.primary.main;
|
|
3002
2965
|
}
|
|
3003
2966
|
}
|
|
3004
|
-
var
|
|
2967
|
+
var LimitCard, TaggedLimit, MoreKeysNote, BudgetLimitList, LimitListPanel;
|
|
2968
|
+
var init_BudgetLimitList = __esm({
|
|
2969
|
+
"src/components/BudgetLimitList.tsx"() {
|
|
2970
|
+
"use strict";
|
|
2971
|
+
init_budget();
|
|
2972
|
+
init_format();
|
|
2973
|
+
init_ui();
|
|
2974
|
+
LimitCard = ({ limit }) => {
|
|
2975
|
+
const tone = budgetTone2(limit.pct);
|
|
2976
|
+
const pct = Math.round(limit.pct);
|
|
2977
|
+
const closeTo = limit.softLimit !== void 0 && limit.spend >= limit.softLimit;
|
|
2978
|
+
return /* @__PURE__ */ React7.createElement(Paper5, { variant: "outlined", sx: { px: 1.5, py: 1.25, borderRadius: 1.5 } }, /* @__PURE__ */ React7.createElement(Box7, { sx: { display: "flex", alignItems: "baseline", justifyContent: "space-between", gap: 1.5 } }, /* @__PURE__ */ React7.createElement(Box7, { sx: { minWidth: 0, flex: 1 } }, /* @__PURE__ */ React7.createElement(
|
|
2979
|
+
Typography7,
|
|
2980
|
+
{
|
|
2981
|
+
variant: "body2",
|
|
2982
|
+
sx: { fontWeight: 600, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" },
|
|
2983
|
+
title: limit.sublabel ? `${limit.label} \xB7 ${limit.sublabel}` : limit.label
|
|
2984
|
+
},
|
|
2985
|
+
limit.label
|
|
2986
|
+
), limit.sublabel && /* @__PURE__ */ React7.createElement(
|
|
2987
|
+
Typography7,
|
|
2988
|
+
{
|
|
2989
|
+
variant: "caption",
|
|
2990
|
+
color: "text.secondary",
|
|
2991
|
+
sx: {
|
|
2992
|
+
fontSize: 10.5,
|
|
2993
|
+
fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
|
|
2994
|
+
display: "block",
|
|
2995
|
+
overflow: "hidden",
|
|
2996
|
+
textOverflow: "ellipsis",
|
|
2997
|
+
whiteSpace: "nowrap"
|
|
2998
|
+
}
|
|
2999
|
+
},
|
|
3000
|
+
limit.sublabel
|
|
3001
|
+
)), limit.hidden ? /* @__PURE__ */ React7.createElement(Typography7, { variant: "body2", sx: { fontWeight: 600, fontVariantNumeric: "tabular-nums", whiteSpace: "nowrap" } }, pct, "% used") : /* @__PURE__ */ React7.createElement(Typography7, { variant: "body2", sx: { fontWeight: 600, fontVariantNumeric: "tabular-nums", whiteSpace: "nowrap" } }, fmtUsd(limit.spend), /* @__PURE__ */ React7.createElement(Box7, { component: "span", color: "text.secondary", sx: { fontWeight: 400 } }, " / ", fmtUsd(limit.budget)))), /* @__PURE__ */ React7.createElement(Box7, { sx: { mt: 1 } }, /* @__PURE__ */ React7.createElement(Meter, { value: limit.pct, tone, height: 6 })), /* @__PURE__ */ React7.createElement(
|
|
3002
|
+
Box7,
|
|
3003
|
+
{
|
|
3004
|
+
sx: {
|
|
3005
|
+
display: "flex",
|
|
3006
|
+
justifyContent: "space-between",
|
|
3007
|
+
alignItems: "center",
|
|
3008
|
+
gap: 1,
|
|
3009
|
+
mt: 0.75
|
|
3010
|
+
}
|
|
3011
|
+
},
|
|
3012
|
+
/* @__PURE__ */ React7.createElement(Typography7, { variant: "caption", color: "text.secondary", sx: { fontVariantNumeric: "tabular-nums" } }, limit.hidden ? /* @__PURE__ */ React7.createElement(React7.Fragment, null, "Hidden by admin") : /* @__PURE__ */ React7.createElement(React7.Fragment, null, pct, "% of budget", closeTo && ` \xB7 soft-limit warning`)),
|
|
3013
|
+
/* @__PURE__ */ React7.createElement(Typography7, { variant: "caption", color: "text.secondary" }, fmtBudgetDuration(limit.budgetDuration) ? `resets ${fmtBudgetDuration(limit.budgetDuration)}` : "never resets")
|
|
3014
|
+
));
|
|
3015
|
+
};
|
|
3016
|
+
TaggedLimit = ({ limit }) => {
|
|
3017
|
+
const theme = useTheme2();
|
|
3018
|
+
const tone = budgetTone2(limit.pct);
|
|
3019
|
+
return /* @__PURE__ */ React7.createElement(Box7, null, /* @__PURE__ */ React7.createElement(
|
|
3020
|
+
Typography7,
|
|
3021
|
+
{
|
|
3022
|
+
sx: {
|
|
3023
|
+
fontSize: 11,
|
|
3024
|
+
fontWeight: 700,
|
|
3025
|
+
letterSpacing: "0.08em",
|
|
3026
|
+
textTransform: "uppercase",
|
|
3027
|
+
color: tone === "accent" ? theme.palette.text.secondary : levelToneColor(tone, theme),
|
|
3028
|
+
mb: 0.5
|
|
3029
|
+
}
|
|
3030
|
+
},
|
|
3031
|
+
limit.kind
|
|
3032
|
+
), /* @__PURE__ */ React7.createElement(LimitCard, { limit }));
|
|
3033
|
+
};
|
|
3034
|
+
MoreKeysNote = ({
|
|
3035
|
+
count,
|
|
3036
|
+
href,
|
|
3037
|
+
label
|
|
3038
|
+
}) => {
|
|
3039
|
+
const text = label ?? `+${count} more budgeted key${count > 1 ? "s" : ""} further from the cap`;
|
|
3040
|
+
if (!href) {
|
|
3041
|
+
return /* @__PURE__ */ React7.createElement(Typography7, { variant: "caption", color: "text.secondary" }, text);
|
|
3042
|
+
}
|
|
3043
|
+
return /* @__PURE__ */ React7.createElement(Link, { to: href, variant: "caption" }, text, " \u2192");
|
|
3044
|
+
};
|
|
3045
|
+
BudgetLimitList = ({ limits, hiddenKeyCount = 0, keysHref }) => /* @__PURE__ */ React7.createElement(Box7, { sx: { display: "flex", flexDirection: "column", gap: 1.25 } }, limits.map((limit) => /* @__PURE__ */ React7.createElement(TaggedLimit, { key: `${limit.kind}:${limit.sublabel ?? limit.label}`, limit })), hiddenKeyCount > 0 && /* @__PURE__ */ React7.createElement(MoreKeysNote, { count: hiddenKeyCount, href: keysHref }));
|
|
3046
|
+
LimitListPanel = ({ children }) => /* @__PURE__ */ React7.createElement(
|
|
3047
|
+
Box7,
|
|
3048
|
+
{
|
|
3049
|
+
sx: (theme) => ({
|
|
3050
|
+
px: 1.5,
|
|
3051
|
+
py: 1.5,
|
|
3052
|
+
borderRadius: 1.5,
|
|
3053
|
+
bgcolor: alpha4(theme.palette.text.primary, theme.palette.mode === "dark" ? 0.03 : 0.02)
|
|
3054
|
+
})
|
|
3055
|
+
},
|
|
3056
|
+
children
|
|
3057
|
+
);
|
|
3058
|
+
}
|
|
3059
|
+
});
|
|
3060
|
+
|
|
3061
|
+
// src/components/LiteLLMBudgetWidget.tsx
|
|
3062
|
+
import React8, { useEffect as useEffect3, useMemo as useMemo6, useState as useState5 } from "react";
|
|
3063
|
+
import Paper6 from "@mui/material/Paper";
|
|
3064
|
+
import Box8 from "@mui/material/Box";
|
|
3065
|
+
import Typography8 from "@mui/material/Typography";
|
|
3066
|
+
import CircularProgress4 from "@mui/material/CircularProgress";
|
|
3067
|
+
import Alert3 from "@mui/material/Alert";
|
|
3068
|
+
import Collapse from "@mui/material/Collapse";
|
|
3069
|
+
import IconButton4 from "@mui/material/IconButton";
|
|
3070
|
+
import { ExpandMore as ExpandMore2 } from "@mui/icons-material";
|
|
3071
|
+
import { alpha as alpha5 } from "@mui/material/styles";
|
|
3072
|
+
import { useApi as useApi2 } from "@backstage/core-plugin-api";
|
|
3073
|
+
var LevelFrame, EmptyLimitCard, CLOSEST_LEVEL_LABEL, KEYS_PATH, HierarchyNote, FullBody, CompactBody, LiteLLMBudgetWidget;
|
|
3005
3074
|
var init_LiteLLMBudgetWidget = __esm({
|
|
3006
3075
|
"src/components/LiteLLMBudgetWidget.tsx"() {
|
|
3007
3076
|
"use strict";
|
|
3008
3077
|
init_api();
|
|
3009
|
-
init_format();
|
|
3010
3078
|
init_ui();
|
|
3079
|
+
init_BudgetLimitList();
|
|
3011
3080
|
init_budget();
|
|
3012
3081
|
LevelFrame = ({
|
|
3013
3082
|
rank,
|
|
@@ -3017,8 +3086,8 @@ var init_LiteLLMBudgetWidget = __esm({
|
|
|
3017
3086
|
tone,
|
|
3018
3087
|
isLast,
|
|
3019
3088
|
children
|
|
3020
|
-
}) => /* @__PURE__ */
|
|
3021
|
-
|
|
3089
|
+
}) => /* @__PURE__ */ React8.createElement(Box8, { sx: { display: "flex", gap: 1.5 } }, /* @__PURE__ */ React8.createElement(Box8, { sx: { display: "flex", flexDirection: "column", alignItems: "center", width: 26, flexShrink: 0 } }, /* @__PURE__ */ React8.createElement(
|
|
3090
|
+
Box8,
|
|
3022
3091
|
{
|
|
3023
3092
|
sx: (theme) => ({
|
|
3024
3093
|
width: 26,
|
|
@@ -3027,7 +3096,7 @@ var init_LiteLLMBudgetWidget = __esm({
|
|
|
3027
3096
|
display: "flex",
|
|
3028
3097
|
alignItems: "center",
|
|
3029
3098
|
justifyContent: "center",
|
|
3030
|
-
bgcolor:
|
|
3099
|
+
bgcolor: alpha5(levelToneColor(tone, theme), 0.14),
|
|
3031
3100
|
color: levelToneColor(tone, theme),
|
|
3032
3101
|
fontSize: 12,
|
|
3033
3102
|
fontWeight: 700,
|
|
@@ -3035,18 +3104,18 @@ var init_LiteLLMBudgetWidget = __esm({
|
|
|
3035
3104
|
})
|
|
3036
3105
|
},
|
|
3037
3106
|
rank
|
|
3038
|
-
), /* @__PURE__ */
|
|
3039
|
-
|
|
3107
|
+
), /* @__PURE__ */ React8.createElement(
|
|
3108
|
+
Box8,
|
|
3040
3109
|
{
|
|
3041
3110
|
sx: (theme) => ({
|
|
3042
3111
|
flex: 1,
|
|
3043
3112
|
width: 2,
|
|
3044
3113
|
minHeight: 14,
|
|
3045
|
-
bgcolor: isLast ? "transparent" :
|
|
3114
|
+
bgcolor: isLast ? "transparent" : alpha5(theme.palette.text.primary, 0.12)
|
|
3046
3115
|
})
|
|
3047
3116
|
}
|
|
3048
|
-
)), /* @__PURE__ */
|
|
3049
|
-
|
|
3117
|
+
)), /* @__PURE__ */ React8.createElement(Box8, { sx: { flex: 1, minWidth: 0, pb: isLast ? 0 : 2 } }, /* @__PURE__ */ React8.createElement(Box8, { sx: { display: "flex", alignItems: "baseline", gap: 1, flexWrap: "wrap", mb: 0.75 } }, /* @__PURE__ */ React8.createElement(
|
|
3118
|
+
Typography8,
|
|
3050
3119
|
{
|
|
3051
3120
|
sx: (theme) => ({
|
|
3052
3121
|
fontSize: 12.5,
|
|
@@ -3057,102 +3126,42 @@ var init_LiteLLMBudgetWidget = __esm({
|
|
|
3057
3126
|
})
|
|
3058
3127
|
},
|
|
3059
3128
|
name
|
|
3060
|
-
), /* @__PURE__ */
|
|
3061
|
-
EmptyLimitCard = ({ children }) => /* @__PURE__ */
|
|
3062
|
-
|
|
3129
|
+
), /* @__PURE__ */ React8.createElement(Typography8, { variant: "caption", color: "text.secondary", sx: { fontSize: 11.5 } }, tagline), note && /* @__PURE__ */ React8.createElement(StatusPill, { label: note, tone: "neutral", dot: false, sx: { ml: "auto" } })), /* @__PURE__ */ React8.createElement(Box8, { sx: { display: "flex", flexDirection: "column", gap: 1 } }, children)));
|
|
3130
|
+
EmptyLimitCard = ({ children }) => /* @__PURE__ */ React8.createElement(
|
|
3131
|
+
Paper6,
|
|
3063
3132
|
{
|
|
3064
3133
|
variant: "outlined",
|
|
3065
3134
|
sx: (theme) => ({
|
|
3066
3135
|
px: 1.5,
|
|
3067
3136
|
py: 1.25,
|
|
3068
3137
|
borderRadius: 1.5,
|
|
3069
|
-
bgcolor:
|
|
3138
|
+
bgcolor: alpha5(theme.palette.text.primary, theme.palette.mode === "dark" ? 0.03 : 0.015)
|
|
3070
3139
|
})
|
|
3071
3140
|
},
|
|
3072
|
-
/* @__PURE__ */
|
|
3141
|
+
/* @__PURE__ */ React8.createElement(Typography8, { variant: "body2", color: "text.secondary", sx: { fontSize: 13 } }, children)
|
|
3073
3142
|
);
|
|
3074
|
-
LimitCard = ({ limit }) => {
|
|
3075
|
-
const tone = budgetTone2(limit.pct);
|
|
3076
|
-
const pct = Math.round(limit.pct);
|
|
3077
|
-
const closeTo = limit.softLimit !== void 0 && limit.spend >= limit.softLimit;
|
|
3078
|
-
return /* @__PURE__ */ React7.createElement(Paper5, { variant: "outlined", sx: { px: 1.5, py: 1.25, borderRadius: 1.5 } }, /* @__PURE__ */ React7.createElement(Box7, { sx: { display: "flex", alignItems: "baseline", justifyContent: "space-between", gap: 1.5 } }, /* @__PURE__ */ React7.createElement(Box7, { sx: { minWidth: 0, flex: 1 } }, /* @__PURE__ */ React7.createElement(
|
|
3079
|
-
Typography7,
|
|
3080
|
-
{
|
|
3081
|
-
variant: "body2",
|
|
3082
|
-
sx: { fontWeight: 600, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" },
|
|
3083
|
-
title: limit.sublabel ? `${limit.label} \xB7 ${limit.sublabel}` : limit.label
|
|
3084
|
-
},
|
|
3085
|
-
limit.label
|
|
3086
|
-
), limit.sublabel && /* @__PURE__ */ React7.createElement(
|
|
3087
|
-
Typography7,
|
|
3088
|
-
{
|
|
3089
|
-
variant: "caption",
|
|
3090
|
-
color: "text.secondary",
|
|
3091
|
-
sx: {
|
|
3092
|
-
fontSize: 10.5,
|
|
3093
|
-
fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
|
|
3094
|
-
display: "block",
|
|
3095
|
-
overflow: "hidden",
|
|
3096
|
-
textOverflow: "ellipsis",
|
|
3097
|
-
whiteSpace: "nowrap"
|
|
3098
|
-
}
|
|
3099
|
-
},
|
|
3100
|
-
limit.sublabel
|
|
3101
|
-
)), limit.hidden ? /* @__PURE__ */ React7.createElement(Typography7, { variant: "body2", sx: { fontWeight: 600, fontVariantNumeric: "tabular-nums", whiteSpace: "nowrap" } }, pct, "% used") : /* @__PURE__ */ React7.createElement(Typography7, { variant: "body2", sx: { fontWeight: 600, fontVariantNumeric: "tabular-nums", whiteSpace: "nowrap" } }, fmtUsd(limit.spend), /* @__PURE__ */ React7.createElement(Box7, { component: "span", color: "text.secondary", sx: { fontWeight: 400 } }, " / ", fmtUsd(limit.budget)))), /* @__PURE__ */ React7.createElement(Box7, { sx: { mt: 1 } }, /* @__PURE__ */ React7.createElement(Meter, { value: limit.pct, tone, height: 6 })), /* @__PURE__ */ React7.createElement(
|
|
3102
|
-
Box7,
|
|
3103
|
-
{
|
|
3104
|
-
sx: {
|
|
3105
|
-
display: "flex",
|
|
3106
|
-
justifyContent: "space-between",
|
|
3107
|
-
alignItems: "center",
|
|
3108
|
-
gap: 1,
|
|
3109
|
-
mt: 0.75
|
|
3110
|
-
}
|
|
3111
|
-
},
|
|
3112
|
-
/* @__PURE__ */ React7.createElement(Typography7, { variant: "caption", color: "text.secondary", sx: { fontVariantNumeric: "tabular-nums" } }, limit.hidden ? /* @__PURE__ */ React7.createElement(React7.Fragment, null, "Hidden by admin") : /* @__PURE__ */ React7.createElement(React7.Fragment, null, pct, "% of budget", closeTo && ` \xB7 soft-limit warning`)),
|
|
3113
|
-
/* @__PURE__ */ React7.createElement(Typography7, { variant: "caption", color: "text.secondary" }, fmtBudgetDuration(limit.budgetDuration) ? `resets ${fmtBudgetDuration(limit.budgetDuration)}` : "never resets")
|
|
3114
|
-
));
|
|
3115
|
-
};
|
|
3116
3143
|
CLOSEST_LEVEL_LABEL = {
|
|
3117
3144
|
key: "key",
|
|
3118
3145
|
user: "personal",
|
|
3119
3146
|
team: "team"
|
|
3120
3147
|
};
|
|
3121
3148
|
KEYS_PATH = "/litellm?tab=keys";
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
Box7,
|
|
3149
|
+
HierarchyNote = ({ withReset }) => /* @__PURE__ */ React8.createElement(
|
|
3150
|
+
Box8,
|
|
3125
3151
|
{
|
|
3126
3152
|
sx: (theme) => ({
|
|
3127
3153
|
px: 1.5,
|
|
3128
3154
|
py: 1.25,
|
|
3129
3155
|
borderRadius: 1.5,
|
|
3130
3156
|
border: "1px dashed",
|
|
3131
|
-
borderColor:
|
|
3157
|
+
borderColor: alpha5(theme.palette.text.primary, 0.18)
|
|
3132
3158
|
})
|
|
3133
3159
|
},
|
|
3134
|
-
/* @__PURE__ */
|
|
3160
|
+
/* @__PURE__ */ React8.createElement(Typography8, { variant: "caption", color: "text.secondary", display: "block" }, /* @__PURE__ */ React8.createElement("b", null, "Order:"), " key \u2192 personal \u2192 team \u2192 global. The first cap you reach blocks the request; a key that belongs to a team uses the team's cap, not your personal one.", withReset && " A cap with a reset window drops to $0 when the window closes; without one it never resets.")
|
|
3135
3161
|
);
|
|
3136
|
-
TaggedLimit = ({ limit }) => {
|
|
3137
|
-
const tone = budgetTone2(limit.pct);
|
|
3138
|
-
return /* @__PURE__ */ React7.createElement(Box7, null, /* @__PURE__ */ React7.createElement(
|
|
3139
|
-
Typography7,
|
|
3140
|
-
{
|
|
3141
|
-
sx: (theme) => ({
|
|
3142
|
-
fontSize: 11,
|
|
3143
|
-
fontWeight: 700,
|
|
3144
|
-
letterSpacing: "0.08em",
|
|
3145
|
-
textTransform: "uppercase",
|
|
3146
|
-
color: tone === "accent" ? theme.palette.text.secondary : levelToneColor(tone, theme),
|
|
3147
|
-
mb: 0.5
|
|
3148
|
-
})
|
|
3149
|
-
},
|
|
3150
|
-
limit.kind
|
|
3151
|
-
), /* @__PURE__ */ React7.createElement(LimitCard, { limit }));
|
|
3152
|
-
};
|
|
3153
3162
|
FullBody = ({ summary }) => {
|
|
3154
3163
|
const hasBudgetedKeys = summary.keys.length + summary.hiddenBudgetedKeys > 0;
|
|
3155
|
-
return /* @__PURE__ */
|
|
3164
|
+
return /* @__PURE__ */ React8.createElement(React8.Fragment, null, /* @__PURE__ */ React8.createElement(
|
|
3156
3165
|
LevelFrame,
|
|
3157
3166
|
{
|
|
3158
3167
|
rank: 1,
|
|
@@ -3160,10 +3169,10 @@ var init_LiteLLMBudgetWidget = __esm({
|
|
|
3160
3169
|
tagline: "per-key cap \xB7 highest priority",
|
|
3161
3170
|
note: hasBudgetedKeys ? void 0 : "no key budgets set"
|
|
3162
3171
|
},
|
|
3163
|
-
!hasBudgetedKeys && /* @__PURE__ */
|
|
3164
|
-
summary.keys.map((k) => /* @__PURE__ */
|
|
3165
|
-
summary.hiddenBudgetedKeys > 0 && /* @__PURE__ */
|
|
3166
|
-
), /* @__PURE__ */
|
|
3172
|
+
!hasBudgetedKeys && /* @__PURE__ */ React8.createElement(EmptyLimitCard, null, "No key has a budget, so no key-level cap applies."),
|
|
3173
|
+
summary.keys.map((k) => /* @__PURE__ */ React8.createElement(LimitCard, { key: k.sublabel ?? k.label, limit: k })),
|
|
3174
|
+
summary.hiddenBudgetedKeys > 0 && /* @__PURE__ */ React8.createElement(MoreKeysNote, { count: summary.hiddenBudgetedKeys, href: KEYS_PATH })
|
|
3175
|
+
), /* @__PURE__ */ React8.createElement(
|
|
3167
3176
|
LevelFrame,
|
|
3168
3177
|
{
|
|
3169
3178
|
rank: 2,
|
|
@@ -3171,15 +3180,15 @@ var init_LiteLLMBudgetWidget = __esm({
|
|
|
3171
3180
|
tagline: "cap on everything you spend with your own keys",
|
|
3172
3181
|
note: "skipped for team-bound keys"
|
|
3173
3182
|
},
|
|
3174
|
-
summary.user ? /* @__PURE__ */
|
|
3175
|
-
), /* @__PURE__ */
|
|
3183
|
+
summary.user ? /* @__PURE__ */ React8.createElement(LimitCard, { limit: summary.user }) : /* @__PURE__ */ React8.createElement(EmptyLimitCard, null, "No personal budget is set on your account.")
|
|
3184
|
+
), /* @__PURE__ */ React8.createElement(LevelFrame, { rank: 3, name: "Team", tagline: "shared cap for all keys in a team (if any)" }, summary.teams.length > 0 ? summary.teams.map((t) => /* @__PURE__ */ React8.createElement(LimitCard, { key: t.sublabel ?? t.label, limit: t })) : /* @__PURE__ */ React8.createElement(EmptyLimitCard, null, "No team you belong to has a budget.")), /* @__PURE__ */ React8.createElement(LevelFrame, { rank: 4, name: "Global", tagline: "proxy-wide cap set by your admin", isLast: true }, /* @__PURE__ */ React8.createElement(EmptyLimitCard, null, "Configured by your admin in the proxy config \u2014 it isn't visible from this page. When set, it limits spend across every request on the proxy.")), /* @__PURE__ */ React8.createElement(Box8, { sx: { mt: 2 } }, /* @__PURE__ */ React8.createElement(HierarchyNote, { withReset: true })));
|
|
3176
3185
|
};
|
|
3177
3186
|
CompactBody = ({ summary }) => {
|
|
3178
3187
|
const { count } = budgetHeadline(summary);
|
|
3179
3188
|
if (count === 0) {
|
|
3180
|
-
return /* @__PURE__ */
|
|
3189
|
+
return /* @__PURE__ */ React8.createElement(Typography8, { variant: "body2", color: "text.secondary", sx: { fontSize: 13 } }, "No budget limits apply to you right now.");
|
|
3181
3190
|
}
|
|
3182
|
-
return /* @__PURE__ */
|
|
3191
|
+
return /* @__PURE__ */ React8.createElement(Box8, { sx: { display: "flex", flexDirection: "column", gap: 1.25 } }, /* @__PURE__ */ React8.createElement(HierarchyNote, null), summary.keys.map((k) => /* @__PURE__ */ React8.createElement(TaggedLimit, { key: k.sublabel ?? k.label, limit: k })), summary.hiddenBudgetedKeys > 0 && /* @__PURE__ */ React8.createElement(MoreKeysNote, { count: summary.hiddenBudgetedKeys, href: KEYS_PATH }), summary.user && /* @__PURE__ */ React8.createElement(TaggedLimit, { limit: summary.user }), summary.teams.map((t) => /* @__PURE__ */ React8.createElement(TaggedLimit, { key: t.sublabel ?? t.label, limit: t })));
|
|
3183
3192
|
};
|
|
3184
3193
|
LiteLLMBudgetWidget = ({
|
|
3185
3194
|
title = "Budget Policy",
|
|
@@ -3230,9 +3239,9 @@ var init_LiteLLMBudgetWidget = __esm({
|
|
|
3230
3239
|
headline.closest.pct
|
|
3231
3240
|
)}%`;
|
|
3232
3241
|
const summaryText = headline.count === 0 ? "no limits apply" : `${limitsWord}${closestWord}`;
|
|
3233
|
-
const body = /* @__PURE__ */
|
|
3234
|
-
return /* @__PURE__ */
|
|
3235
|
-
|
|
3242
|
+
const body = /* @__PURE__ */ React8.createElement(React8.Fragment, null, loading && /* @__PURE__ */ React8.createElement(Box8, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: 140 }, /* @__PURE__ */ React8.createElement(CircularProgress4, { size: 32 })), !loading && error && /* @__PURE__ */ React8.createElement(Alert3, { severity: "error", sx: { mt: 0.5 } }, error), !loading && !error && (compact ? /* @__PURE__ */ React8.createElement(CompactBody, { summary }) : /* @__PURE__ */ React8.createElement(FullBody, { summary })));
|
|
3243
|
+
return /* @__PURE__ */ React8.createElement(Paper6, { sx: { p: 2 } }, collapsible ? /* @__PURE__ */ React8.createElement(
|
|
3244
|
+
Box8,
|
|
3236
3245
|
{
|
|
3237
3246
|
onClick: () => setExpanded((e) => !e),
|
|
3238
3247
|
sx: {
|
|
@@ -3244,14 +3253,14 @@ var init_LiteLLMBudgetWidget = __esm({
|
|
|
3244
3253
|
mb: expanded ? 1.5 : 0
|
|
3245
3254
|
}
|
|
3246
3255
|
},
|
|
3247
|
-
/* @__PURE__ */
|
|
3256
|
+
/* @__PURE__ */ React8.createElement(Box8, { sx: { minWidth: 0, flex: 1 } }, /* @__PURE__ */ React8.createElement(Typography8, { variant: "h6", lineHeight: 1.2 }, title), !loading && !error && /* @__PURE__ */ React8.createElement(Box8, { sx: { mt: 0.5 } }, /* @__PURE__ */ React8.createElement(
|
|
3248
3257
|
StatusPill,
|
|
3249
3258
|
{
|
|
3250
3259
|
label: summaryText,
|
|
3251
3260
|
tone: budgetTone2(headline.closest?.pct ?? 0)
|
|
3252
3261
|
}
|
|
3253
3262
|
))),
|
|
3254
|
-
/* @__PURE__ */
|
|
3263
|
+
/* @__PURE__ */ React8.createElement(
|
|
3255
3264
|
IconButton4,
|
|
3256
3265
|
{
|
|
3257
3266
|
size: "small",
|
|
@@ -3267,10 +3276,10 @@ var init_LiteLLMBudgetWidget = __esm({
|
|
|
3267
3276
|
transition: theme.transitions.create("transform")
|
|
3268
3277
|
})
|
|
3269
3278
|
},
|
|
3270
|
-
/* @__PURE__ */
|
|
3279
|
+
/* @__PURE__ */ React8.createElement(ExpandMore2, null)
|
|
3271
3280
|
)
|
|
3272
|
-
) : /* @__PURE__ */
|
|
3273
|
-
|
|
3281
|
+
) : /* @__PURE__ */ React8.createElement(Box8, { sx: { mb: 1.5 } }, /* @__PURE__ */ React8.createElement(Typography8, { variant: "h6", lineHeight: 1.2 }, title), /* @__PURE__ */ React8.createElement(Typography8, { variant: "caption", color: "text.secondary" }, "How LiteLLM caps spend \u2014 and where you stand right now")), collapsible ? /* @__PURE__ */ React8.createElement(Collapse, { in: expanded, unmountOnExit: true }, body) : body, action && /* @__PURE__ */ React8.createElement(
|
|
3282
|
+
Box8,
|
|
3274
3283
|
{
|
|
3275
3284
|
sx: (theme) => ({
|
|
3276
3285
|
mt: 2,
|
|
@@ -3285,10 +3294,10 @@ var init_LiteLLMBudgetWidget = __esm({
|
|
|
3285
3294
|
});
|
|
3286
3295
|
|
|
3287
3296
|
// src/components/TeamUsage.tsx
|
|
3288
|
-
import
|
|
3289
|
-
import
|
|
3290
|
-
import
|
|
3291
|
-
import
|
|
3297
|
+
import React9, { useState as useState6 } from "react";
|
|
3298
|
+
import Paper7 from "@mui/material/Paper";
|
|
3299
|
+
import Box9 from "@mui/material/Box";
|
|
3300
|
+
import Typography9 from "@mui/material/Typography";
|
|
3292
3301
|
import Skeleton4 from "@mui/material/Skeleton";
|
|
3293
3302
|
import Divider3 from "@mui/material/Divider";
|
|
3294
3303
|
import Table4 from "@mui/material/Table";
|
|
@@ -3301,7 +3310,7 @@ import Collapse2 from "@mui/material/Collapse";
|
|
|
3301
3310
|
import IconButton5 from "@mui/material/IconButton";
|
|
3302
3311
|
import Button5 from "@mui/material/Button";
|
|
3303
3312
|
import CircularProgress5 from "@mui/material/CircularProgress";
|
|
3304
|
-
import { alpha as
|
|
3313
|
+
import { alpha as alpha6 } from "@mui/material/styles";
|
|
3305
3314
|
import { ExpandMore as ExpandMore3, Group, Add as Add3 } from "@mui/icons-material";
|
|
3306
3315
|
import {
|
|
3307
3316
|
AreaChart as AreaChart2,
|
|
@@ -3345,24 +3354,24 @@ var init_TeamUsage = __esm({
|
|
|
3345
3354
|
const renderDailySpendSection = () => {
|
|
3346
3355
|
if (hidden) return null;
|
|
3347
3356
|
if (usageLoading) {
|
|
3348
|
-
return /* @__PURE__ */
|
|
3357
|
+
return /* @__PURE__ */ React9.createElement(Box9, { display: "flex", justifyContent: "center", py: 3 }, /* @__PURE__ */ React9.createElement(CircularProgress5, { size: 22 }));
|
|
3349
3358
|
}
|
|
3350
3359
|
if (dailyData.length === 0) return null;
|
|
3351
|
-
return /* @__PURE__ */
|
|
3352
|
-
|
|
3360
|
+
return /* @__PURE__ */ React9.createElement(Box9, { mb: 2.5 }, /* @__PURE__ */ React9.createElement(
|
|
3361
|
+
Typography9,
|
|
3353
3362
|
{
|
|
3354
3363
|
variant: "caption",
|
|
3355
3364
|
color: "text.secondary",
|
|
3356
3365
|
sx: { display: "block", fontSize: 11, fontWeight: 700, letterSpacing: "0.07em", textTransform: "uppercase", mb: 1 }
|
|
3357
3366
|
},
|
|
3358
3367
|
"Daily spend"
|
|
3359
|
-
), /* @__PURE__ */
|
|
3368
|
+
), /* @__PURE__ */ React9.createElement(Box9, { height: 150 }, /* @__PURE__ */ React9.createElement(ResponsiveContainer2, { width: "100%", height: "100%" }, /* @__PURE__ */ React9.createElement(AreaChart2, { data: dailyData, margin: { top: 4, right: 8, left: 0, bottom: 0 } }, /* @__PURE__ */ React9.createElement(CartesianGrid2, { ...chart.grid }), /* @__PURE__ */ React9.createElement(XAxis2, { dataKey: "date", tickFormatter: fmtDateShort, ...chart.axis }), /* @__PURE__ */ React9.createElement(YAxis2, { tickFormatter: fmtUsdCompact, width: 52, ...chart.axis }), /* @__PURE__ */ React9.createElement(
|
|
3360
3369
|
Tooltip2,
|
|
3361
3370
|
{
|
|
3362
3371
|
cursor: chart.cursor,
|
|
3363
|
-
content: /* @__PURE__ */
|
|
3372
|
+
content: /* @__PURE__ */ React9.createElement(ChartTooltip, { valueFormatter: (v) => `$${v.toFixed(4)}` })
|
|
3364
3373
|
}
|
|
3365
|
-
), /* @__PURE__ */
|
|
3374
|
+
), /* @__PURE__ */ React9.createElement(
|
|
3366
3375
|
Area2,
|
|
3367
3376
|
{
|
|
3368
3377
|
type: "monotone",
|
|
@@ -3375,8 +3384,8 @@ var init_TeamUsage = __esm({
|
|
|
3375
3384
|
}
|
|
3376
3385
|
)))));
|
|
3377
3386
|
};
|
|
3378
|
-
const sectionLabel = (label) => /* @__PURE__ */
|
|
3379
|
-
|
|
3387
|
+
const sectionLabel = (label) => /* @__PURE__ */ React9.createElement(
|
|
3388
|
+
Typography9,
|
|
3380
3389
|
{
|
|
3381
3390
|
variant: "caption",
|
|
3382
3391
|
color: "text.secondary",
|
|
@@ -3384,8 +3393,8 @@ var init_TeamUsage = __esm({
|
|
|
3384
3393
|
},
|
|
3385
3394
|
label
|
|
3386
3395
|
);
|
|
3387
|
-
return /* @__PURE__ */
|
|
3388
|
-
|
|
3396
|
+
return /* @__PURE__ */ React9.createElement(Paper7, { variant: "outlined", sx: { borderRadius: 2, p: 2.5 } }, /* @__PURE__ */ React9.createElement(Box9, { display: "flex", alignItems: "center", gap: 1.5 }, /* @__PURE__ */ React9.createElement(
|
|
3397
|
+
Box9,
|
|
3389
3398
|
{
|
|
3390
3399
|
sx: (theme) => ({
|
|
3391
3400
|
width: 36,
|
|
@@ -3395,20 +3404,20 @@ var init_TeamUsage = __esm({
|
|
|
3395
3404
|
display: "flex",
|
|
3396
3405
|
alignItems: "center",
|
|
3397
3406
|
justifyContent: "center",
|
|
3398
|
-
bgcolor:
|
|
3407
|
+
bgcolor: alpha6(theme.palette.primary.main, 0.1),
|
|
3399
3408
|
color: theme.palette.primary.main
|
|
3400
3409
|
})
|
|
3401
3410
|
},
|
|
3402
|
-
/* @__PURE__ */
|
|
3403
|
-
), /* @__PURE__ */
|
|
3404
|
-
|
|
3411
|
+
/* @__PURE__ */ React9.createElement(Group, { fontSize: "small" })
|
|
3412
|
+
), /* @__PURE__ */ React9.createElement(Box9, { flexGrow: 1, minWidth: 0 }, /* @__PURE__ */ React9.createElement(Typography9, { variant: "subtitle1", sx: { fontWeight: 600, lineHeight: 1.3 } }, team.team_alias || "Untitled team"), /* @__PURE__ */ React9.createElement(
|
|
3413
|
+
Typography9,
|
|
3405
3414
|
{
|
|
3406
3415
|
variant: "caption",
|
|
3407
3416
|
color: "text.secondary",
|
|
3408
3417
|
sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 11 }
|
|
3409
3418
|
},
|
|
3410
3419
|
team.team_id
|
|
3411
|
-
)), isOver && /* @__PURE__ */
|
|
3420
|
+
)), isOver && /* @__PURE__ */ React9.createElement(StatusPill, { label: "Over budget", tone: "danger" }), isNear && /* @__PURE__ */ React9.createElement(StatusPill, { label: "Near limit", tone: "warning" }), canManage && onEditTeam && /* @__PURE__ */ React9.createElement(
|
|
3412
3421
|
Button5,
|
|
3413
3422
|
{
|
|
3414
3423
|
size: "small",
|
|
@@ -3417,7 +3426,7 @@ var init_TeamUsage = __esm({
|
|
|
3417
3426
|
sx: { textTransform: "none" }
|
|
3418
3427
|
},
|
|
3419
3428
|
"Edit"
|
|
3420
|
-
), /* @__PURE__ */
|
|
3429
|
+
), /* @__PURE__ */ React9.createElement(
|
|
3421
3430
|
IconButton5,
|
|
3422
3431
|
{
|
|
3423
3432
|
size: "small",
|
|
@@ -3430,9 +3439,9 @@ var init_TeamUsage = __esm({
|
|
|
3430
3439
|
transition: theme.transitions.create("transform")
|
|
3431
3440
|
})
|
|
3432
3441
|
},
|
|
3433
|
-
/* @__PURE__ */
|
|
3434
|
-
)), /* @__PURE__ */
|
|
3435
|
-
|
|
3442
|
+
/* @__PURE__ */ React9.createElement(ExpandMore3, null)
|
|
3443
|
+
)), /* @__PURE__ */ React9.createElement(
|
|
3444
|
+
Box9,
|
|
3436
3445
|
{
|
|
3437
3446
|
sx: {
|
|
3438
3447
|
display: "grid",
|
|
@@ -3442,35 +3451,35 @@ var init_TeamUsage = __esm({
|
|
|
3442
3451
|
maxWidth: 640
|
|
3443
3452
|
}
|
|
3444
3453
|
},
|
|
3445
|
-
/* @__PURE__ */
|
|
3446
|
-
/* @__PURE__ */
|
|
3447
|
-
/* @__PURE__ */
|
|
3448
|
-
/* @__PURE__ */
|
|
3449
|
-
/* @__PURE__ */
|
|
3450
|
-
/* @__PURE__ */
|
|
3451
|
-
), hidden && /* @__PURE__ */
|
|
3454
|
+
/* @__PURE__ */ React9.createElement(Stat, { label: "Members", value: team.members_with_roles?.length ?? "\u2014" }),
|
|
3455
|
+
/* @__PURE__ */ React9.createElement(Stat, { label: "Models", value: team.models?.length ? team.models.length : "All" }),
|
|
3456
|
+
/* @__PURE__ */ React9.createElement(Stat, { label: "Budget", value: budgetStatValue(hidden, budget) }),
|
|
3457
|
+
/* @__PURE__ */ React9.createElement(Stat, { label: "Spend", value: hidden ? "\u2014" : fmtUsd2(spend) }),
|
|
3458
|
+
/* @__PURE__ */ React9.createElement(Stat, { label: "TPM", value: team.tpm_limit && team.tpm_limit > 0 ? team.tpm_limit.toLocaleString() : "\u2014" }),
|
|
3459
|
+
/* @__PURE__ */ React9.createElement(Stat, { label: "RPM", value: team.rpm_limit && team.rpm_limit > 0 ? team.rpm_limit.toLocaleString() : "\u2014" })
|
|
3460
|
+
), hidden && /* @__PURE__ */ React9.createElement(Box9, { mt: 2, maxWidth: 640 }, /* @__PURE__ */ React9.createElement(Box9, { display: "flex", justifyContent: "space-between", alignItems: "baseline", mb: 0.75 }, /* @__PURE__ */ React9.createElement(Typography9, { variant: "caption", color: "text.secondary", sx: { fontVariantNumeric: "tabular-nums" } }, budgetPct.toFixed(0), "% used"), /* @__PURE__ */ React9.createElement(Typography9, { variant: "caption", color: "text.secondary" }, "Hidden by admin")), /* @__PURE__ */ React9.createElement(Meter, { value: budgetPct, tone: budgetTone3(isOver, isNear), height: 5 }), /* @__PURE__ */ React9.createElement(Typography9, { variant: "caption", color: "text.secondary", sx: { display: "block", mt: 0.75 } }, "Team budget is hidden \u2014 showing usage level only", team.budget_duration ? ` \xB7 resets every ${team.budget_duration}` : "", ".")), !hidden && budget > 0 && /* @__PURE__ */ React9.createElement(Box9, { mt: 2, maxWidth: 640 }, /* @__PURE__ */ React9.createElement(Box9, { display: "flex", justifyContent: "space-between", alignItems: "baseline", mb: 0.75 }, /* @__PURE__ */ React9.createElement(Typography9, { variant: "caption", color: "text.secondary", sx: { fontVariantNumeric: "tabular-nums" } }, fmtUsd2(spend), " of ", fmtUsd2(budget)), /* @__PURE__ */ React9.createElement(Typography9, { variant: "caption", color: "text.secondary", sx: { fontVariantNumeric: "tabular-nums" } }, budgetPct.toFixed(0), "%")), /* @__PURE__ */ React9.createElement(Meter, { value: budgetPct, tone: budgetTone3(isOver, isNear), height: 5 })), /* @__PURE__ */ React9.createElement(Collapse2, { in: expanded, unmountOnExit: true }, /* @__PURE__ */ React9.createElement(Divider3, { sx: { my: 2.5 } }), /* @__PURE__ */ React9.createElement(Box9, { mb: 2.5 }, sectionLabel("Models"), team.models?.length ? /* @__PURE__ */ React9.createElement(Box9, { display: "flex", gap: 0.5, flexWrap: "wrap" }, team.models.map((m) => /* @__PURE__ */ React9.createElement(TagChip, { key: m, label: m, title: m }))) : /* @__PURE__ */ React9.createElement(Typography9, { variant: "body2", color: "text.secondary" }, "All models allowed")), renderDailySpendSection(), /* @__PURE__ */ React9.createElement(Box9, null, sectionLabel("Members"), team.members_with_roles?.length ? /* @__PURE__ */ React9.createElement(
|
|
3452
3461
|
TableContainer4,
|
|
3453
3462
|
{
|
|
3454
|
-
component:
|
|
3463
|
+
component: Paper7,
|
|
3455
3464
|
variant: "outlined",
|
|
3456
3465
|
sx: { borderRadius: 1.5 }
|
|
3457
3466
|
},
|
|
3458
|
-
/* @__PURE__ */
|
|
3459
|
-
|
|
3467
|
+
/* @__PURE__ */ React9.createElement(Table4, { size: "small", sx: dataTableSx }, /* @__PURE__ */ React9.createElement(TableHead4, null, /* @__PURE__ */ React9.createElement(TableRow4, null, /* @__PURE__ */ React9.createElement(TableCell4, null, "User"), /* @__PURE__ */ React9.createElement(TableCell4, { align: "right" }, "Role"))), /* @__PURE__ */ React9.createElement(TableBody4, null, team.members_with_roles.map((m) => /* @__PURE__ */ React9.createElement(TableRow4, { key: m.user_id }, /* @__PURE__ */ React9.createElement(TableCell4, null, m.user_email ? /* @__PURE__ */ React9.createElement(React9.Fragment, null, /* @__PURE__ */ React9.createElement(Typography9, { variant: "body2" }, m.user_email), /* @__PURE__ */ React9.createElement(
|
|
3468
|
+
Typography9,
|
|
3460
3469
|
{
|
|
3461
3470
|
variant: "caption",
|
|
3462
3471
|
color: "text.secondary",
|
|
3463
3472
|
sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 11 }
|
|
3464
3473
|
},
|
|
3465
3474
|
m.user_id
|
|
3466
|
-
)) : /* @__PURE__ */
|
|
3467
|
-
|
|
3475
|
+
)) : /* @__PURE__ */ React9.createElement(
|
|
3476
|
+
Typography9,
|
|
3468
3477
|
{
|
|
3469
3478
|
variant: "body2",
|
|
3470
3479
|
sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace" }
|
|
3471
3480
|
},
|
|
3472
3481
|
m.user_id
|
|
3473
|
-
)), /* @__PURE__ */
|
|
3482
|
+
)), /* @__PURE__ */ React9.createElement(TableCell4, { align: "right" }, /* @__PURE__ */ React9.createElement(
|
|
3474
3483
|
StatusPill,
|
|
3475
3484
|
{
|
|
3476
3485
|
label: m.role,
|
|
@@ -3478,7 +3487,7 @@ var init_TeamUsage = __esm({
|
|
|
3478
3487
|
dot: false
|
|
3479
3488
|
}
|
|
3480
3489
|
))))))
|
|
3481
|
-
) : /* @__PURE__ */
|
|
3490
|
+
) : /* @__PURE__ */ React9.createElement(Typography9, { variant: "body2", color: "text.secondary" }, "No members assigned."))));
|
|
3482
3491
|
};
|
|
3483
3492
|
TeamUsage = ({
|
|
3484
3493
|
teams,
|
|
@@ -3490,22 +3499,22 @@ var init_TeamUsage = __esm({
|
|
|
3490
3499
|
canCreate,
|
|
3491
3500
|
onCreateTeam
|
|
3492
3501
|
}) => {
|
|
3493
|
-
const createAction = canCreate && onCreateTeam ? /* @__PURE__ */
|
|
3502
|
+
const createAction = canCreate && onCreateTeam ? /* @__PURE__ */ React9.createElement(
|
|
3494
3503
|
Button5,
|
|
3495
3504
|
{
|
|
3496
3505
|
variant: "contained",
|
|
3497
3506
|
color: "primary",
|
|
3498
3507
|
disableElevation: true,
|
|
3499
|
-
startIcon: /* @__PURE__ */
|
|
3508
|
+
startIcon: /* @__PURE__ */ React9.createElement(Add3, null),
|
|
3500
3509
|
onClick: onCreateTeam
|
|
3501
3510
|
},
|
|
3502
3511
|
"Create Team"
|
|
3503
3512
|
) : void 0;
|
|
3504
3513
|
if (loading) {
|
|
3505
|
-
return /* @__PURE__ */
|
|
3514
|
+
return /* @__PURE__ */ React9.createElement(SectionCard, { title: "Teams", actions: createAction }, /* @__PURE__ */ React9.createElement(Skeleton4, { variant: "rounded", height: 120, sx: { mb: 2 } }), /* @__PURE__ */ React9.createElement(Skeleton4, { variant: "rounded", height: 120 }));
|
|
3506
3515
|
}
|
|
3507
3516
|
if (!teams.length) {
|
|
3508
|
-
return /* @__PURE__ */
|
|
3517
|
+
return /* @__PURE__ */ React9.createElement(SectionCard, { title: "Teams", actions: createAction }, /* @__PURE__ */ React9.createElement(
|
|
3509
3518
|
EmptyState,
|
|
3510
3519
|
{
|
|
3511
3520
|
message: "You're not a member of any LiteLLM team yet.",
|
|
@@ -3513,14 +3522,14 @@ var init_TeamUsage = __esm({
|
|
|
3513
3522
|
}
|
|
3514
3523
|
));
|
|
3515
3524
|
}
|
|
3516
|
-
return /* @__PURE__ */
|
|
3525
|
+
return /* @__PURE__ */ React9.createElement(
|
|
3517
3526
|
SectionCard,
|
|
3518
3527
|
{
|
|
3519
3528
|
title: "Teams",
|
|
3520
3529
|
subtitle: `${teams.length} team${teams.length === 1 ? "" : "s"}`,
|
|
3521
3530
|
actions: createAction
|
|
3522
3531
|
},
|
|
3523
|
-
/* @__PURE__ */
|
|
3532
|
+
/* @__PURE__ */ React9.createElement(Box9, { sx: { display: "flex", flexDirection: "column", gap: 2 } }, teams.map((team) => /* @__PURE__ */ React9.createElement(
|
|
3524
3533
|
TeamCard,
|
|
3525
3534
|
{
|
|
3526
3535
|
key: team.team_id,
|
|
@@ -3537,8 +3546,8 @@ var init_TeamUsage = __esm({
|
|
|
3537
3546
|
});
|
|
3538
3547
|
|
|
3539
3548
|
// src/components/ModelsTable.tsx
|
|
3540
|
-
import
|
|
3541
|
-
import
|
|
3549
|
+
import React10, { useState as useState7, useMemo as useMemo7, useCallback } from "react";
|
|
3550
|
+
import Box10 from "@mui/material/Box";
|
|
3542
3551
|
import Table5 from "@mui/material/Table";
|
|
3543
3552
|
import TableBody5 from "@mui/material/TableBody";
|
|
3544
3553
|
import TableCell5 from "@mui/material/TableCell";
|
|
@@ -3546,7 +3555,7 @@ import TableContainer5 from "@mui/material/TableContainer";
|
|
|
3546
3555
|
import TableHead5 from "@mui/material/TableHead";
|
|
3547
3556
|
import TableRow5 from "@mui/material/TableRow";
|
|
3548
3557
|
import TableSortLabel2 from "@mui/material/TableSortLabel";
|
|
3549
|
-
import
|
|
3558
|
+
import Typography10 from "@mui/material/Typography";
|
|
3550
3559
|
import MenuItem4 from "@mui/material/MenuItem";
|
|
3551
3560
|
import Select from "@mui/material/Select";
|
|
3552
3561
|
import FormControl from "@mui/material/FormControl";
|
|
@@ -3558,7 +3567,7 @@ import ContentCopyIcon from "@mui/icons-material/ContentCopy";
|
|
|
3558
3567
|
import Tooltip3 from "@mui/material/Tooltip";
|
|
3559
3568
|
import Snackbar from "@mui/material/Snackbar";
|
|
3560
3569
|
import Alert4 from "@mui/material/Alert";
|
|
3561
|
-
import { alpha as
|
|
3570
|
+
import { alpha as alpha7, useTheme as useTheme3 } from "@mui/material/styles";
|
|
3562
3571
|
function isModelAllowedByTeam2(model, teamModels) {
|
|
3563
3572
|
if (!teamModels || teamModels.length === 0 || teamModels.includes(ALL_PROXY_MODELS2)) return true;
|
|
3564
3573
|
return teamModels.includes(model.model_name) || !!model.access_groups?.some((group) => teamModels.includes(group));
|
|
@@ -3583,7 +3592,7 @@ var init_ModelsTable = __esm({
|
|
|
3583
3592
|
init_ui();
|
|
3584
3593
|
ALL_PROXY_MODELS2 = "all-proxy-models";
|
|
3585
3594
|
ModelsTable = ({ allModels, teams, loading }) => {
|
|
3586
|
-
const theme =
|
|
3595
|
+
const theme = useTheme3();
|
|
3587
3596
|
const [selectedTeamId, setSelectedTeamId] = useState7("");
|
|
3588
3597
|
const [filterText, setFilterText] = useState7("");
|
|
3589
3598
|
const [copiedSnackbar, setCopiedSnackbar] = useState7(false);
|
|
@@ -3669,13 +3678,13 @@ var init_ModelsTable = __esm({
|
|
|
3669
3678
|
return theme.palette.info.main;
|
|
3670
3679
|
}
|
|
3671
3680
|
};
|
|
3672
|
-
return /* @__PURE__ */
|
|
3681
|
+
return /* @__PURE__ */ React10.createElement(
|
|
3673
3682
|
SectionCard,
|
|
3674
3683
|
{
|
|
3675
3684
|
title: "Available Models",
|
|
3676
3685
|
subtitle: selectedTeam ? `${filteredModels.length}${filterText.trim() ? ` of ${teamModels.length}` : ""} model${filteredModels.length === 1 ? "" : "s"}` : "Select a team to view its models"
|
|
3677
3686
|
},
|
|
3678
|
-
/* @__PURE__ */
|
|
3687
|
+
/* @__PURE__ */ React10.createElement(Box10, { sx: { px: 2.5, pb: 2.5, pt: 1, display: "flex", gap: 2, flexWrap: "wrap" } }, /* @__PURE__ */ React10.createElement(FormControl, { size: "small", sx: { minWidth: 240, mb: 2 } }, /* @__PURE__ */ React10.createElement(InputLabel, { id: "team-select-label" }, "Team"), /* @__PURE__ */ React10.createElement(
|
|
3679
3688
|
Select,
|
|
3680
3689
|
{
|
|
3681
3690
|
labelId: "team-select-label",
|
|
@@ -3683,8 +3692,8 @@ var init_ModelsTable = __esm({
|
|
|
3683
3692
|
label: "Team",
|
|
3684
3693
|
onChange: (e) => setSelectedTeamId(e.target.value)
|
|
3685
3694
|
},
|
|
3686
|
-
teams.map((t) => /* @__PURE__ */
|
|
3687
|
-
)), /* @__PURE__ */
|
|
3695
|
+
teams.map((t) => /* @__PURE__ */ React10.createElement(MenuItem4, { key: t.team_id, value: t.team_id }, t.team_alias ?? t.team_id))
|
|
3696
|
+
)), /* @__PURE__ */ React10.createElement(
|
|
3688
3697
|
TextField5,
|
|
3689
3698
|
{
|
|
3690
3699
|
label: "Filter Models",
|
|
@@ -3694,33 +3703,33 @@ var init_ModelsTable = __esm({
|
|
|
3694
3703
|
disabled: !selectedTeamId,
|
|
3695
3704
|
sx: { minWidth: 240, mb: 2 },
|
|
3696
3705
|
InputProps: {
|
|
3697
|
-
startAdornment: /* @__PURE__ */
|
|
3706
|
+
startAdornment: /* @__PURE__ */ React10.createElement(InputAdornment2, { position: "start" }, /* @__PURE__ */ React10.createElement(Search2, { fontSize: "small" }))
|
|
3698
3707
|
}
|
|
3699
3708
|
}
|
|
3700
3709
|
)),
|
|
3701
|
-
loading && /* @__PURE__ */
|
|
3702
|
-
!loading && !selectedTeamId && /* @__PURE__ */
|
|
3710
|
+
loading && /* @__PURE__ */ React10.createElement(EmptyState, { message: "Loading models\u2026" }),
|
|
3711
|
+
!loading && !selectedTeamId && /* @__PURE__ */ React10.createElement(
|
|
3703
3712
|
EmptyState,
|
|
3704
3713
|
{
|
|
3705
3714
|
message: "Select a team to see available models",
|
|
3706
3715
|
hint: "Each team may have a different set of models assigned"
|
|
3707
3716
|
}
|
|
3708
3717
|
),
|
|
3709
|
-
!loading && selectedTeamId && teamModels.length === 0 && /* @__PURE__ */
|
|
3718
|
+
!loading && selectedTeamId && teamModels.length === 0 && /* @__PURE__ */ React10.createElement(
|
|
3710
3719
|
EmptyState,
|
|
3711
3720
|
{
|
|
3712
3721
|
message: "No models available for this team",
|
|
3713
3722
|
hint: allModels.length === 0 ? "No models are configured in the system" : "None of the team's assigned models were found in the catalog"
|
|
3714
3723
|
}
|
|
3715
3724
|
),
|
|
3716
|
-
!loading && selectedTeamId && teamModels.length > 0 && filteredModels.length === 0 && /* @__PURE__ */
|
|
3725
|
+
!loading && selectedTeamId && teamModels.length > 0 && filteredModels.length === 0 && /* @__PURE__ */ React10.createElement(
|
|
3717
3726
|
EmptyState,
|
|
3718
3727
|
{
|
|
3719
3728
|
message: "No models match your search",
|
|
3720
3729
|
hint: "Try a different search term"
|
|
3721
3730
|
}
|
|
3722
3731
|
),
|
|
3723
|
-
!loading && filteredModels.length > 0 && /* @__PURE__ */
|
|
3732
|
+
!loading && filteredModels.length > 0 && /* @__PURE__ */ React10.createElement(TableContainer5, null, /* @__PURE__ */ React10.createElement(Table5, { size: "small", sx: dataTableSx }, /* @__PURE__ */ React10.createElement(TableHead5, null, /* @__PURE__ */ React10.createElement(TableRow5, null, /* @__PURE__ */ React10.createElement(TableCell5, { sortDirection: sortKey === "model_name" ? sortDirection : false }, /* @__PURE__ */ React10.createElement(TableSortLabel2, { active: sortKey === "model_name", direction: sortKey === "model_name" ? sortDirection : "asc", onClick: () => handleSort("model_name") }, "Model ID")), /* @__PURE__ */ React10.createElement(TableCell5, { sortDirection: sortKey === "mode" ? sortDirection : false }, /* @__PURE__ */ React10.createElement(TableSortLabel2, { active: sortKey === "mode", direction: sortKey === "mode" ? sortDirection : "asc", onClick: () => handleSort("mode") }, "Mode")), /* @__PURE__ */ React10.createElement(TableCell5, { align: "right", sortDirection: sortKey === "input_cost_per_token" ? sortDirection : false }, /* @__PURE__ */ React10.createElement(TableSortLabel2, { active: sortKey === "input_cost_per_token", direction: sortKey === "input_cost_per_token" ? sortDirection : "asc", onClick: () => handleSort("input_cost_per_token") }, "Input Cost")), /* @__PURE__ */ React10.createElement(TableCell5, { align: "right", sortDirection: sortKey === "output_cost_per_token" ? sortDirection : false }, /* @__PURE__ */ React10.createElement(TableSortLabel2, { active: sortKey === "output_cost_per_token", direction: sortKey === "output_cost_per_token" ? sortDirection : "asc", onClick: () => handleSort("output_cost_per_token") }, "Output Cost")), /* @__PURE__ */ React10.createElement(TableCell5, { align: "right", sortDirection: sortKey === "max_input_tokens" ? sortDirection : false }, /* @__PURE__ */ React10.createElement(TableSortLabel2, { active: sortKey === "max_input_tokens", direction: sortKey === "max_input_tokens" ? sortDirection : "asc", onClick: () => handleSort("max_input_tokens") }, "Max Input")), /* @__PURE__ */ React10.createElement(TableCell5, { align: "right", sortDirection: sortKey === "max_output_tokens" ? sortDirection : false }, /* @__PURE__ */ React10.createElement(TableSortLabel2, { active: sortKey === "max_output_tokens", direction: sortKey === "max_output_tokens" ? sortDirection : "asc", onClick: () => handleSort("max_output_tokens") }, "Max Output")), /* @__PURE__ */ React10.createElement(TableCell5, null, "Capabilities"))), /* @__PURE__ */ React10.createElement(TableBody5, null, filteredModels.map((m) => /* @__PURE__ */ React10.createElement(TableRow5, { key: m.model_name, hover: true }, /* @__PURE__ */ React10.createElement(TableCell5, null, /* @__PURE__ */ React10.createElement(Box10, { sx: { display: "flex", alignItems: "center", gap: 0.5 } }, /* @__PURE__ */ React10.createElement(Tooltip3, { title: m.model_name, placement: "top" }, /* @__PURE__ */ React10.createElement(
|
|
3724
3733
|
"span",
|
|
3725
3734
|
{
|
|
3726
3735
|
role: "button",
|
|
@@ -3730,9 +3739,9 @@ var init_ModelsTable = __esm({
|
|
|
3730
3739
|
onKeyDown: (e) => e.key === "Enter" && handleCopyModelId(m.model_name),
|
|
3731
3740
|
style: { cursor: "pointer", display: "inline-flex" }
|
|
3732
3741
|
},
|
|
3733
|
-
/* @__PURE__ */
|
|
3734
|
-
)), /* @__PURE__ */
|
|
3735
|
-
|
|
3742
|
+
/* @__PURE__ */ React10.createElement(TagChip, { label: m.model_name, title: "Copy model ID" })
|
|
3743
|
+
)), /* @__PURE__ */ React10.createElement(Tooltip3, { title: "Copy model ID" }, /* @__PURE__ */ React10.createElement(
|
|
3744
|
+
Box10,
|
|
3736
3745
|
{
|
|
3737
3746
|
component: "span",
|
|
3738
3747
|
role: "button",
|
|
@@ -3742,9 +3751,9 @@ var init_ModelsTable = __esm({
|
|
|
3742
3751
|
sx: quietIconButtonSx("accent"),
|
|
3743
3752
|
style: { cursor: "pointer", display: "inline-flex" }
|
|
3744
3753
|
},
|
|
3745
|
-
/* @__PURE__ */
|
|
3746
|
-
)))), /* @__PURE__ */
|
|
3747
|
-
|
|
3754
|
+
/* @__PURE__ */ React10.createElement(ContentCopyIcon, { sx: { fontSize: 14 } })
|
|
3755
|
+
)))), /* @__PURE__ */ React10.createElement(TableCell5, null, /* @__PURE__ */ React10.createElement(
|
|
3756
|
+
Box10,
|
|
3748
3757
|
{
|
|
3749
3758
|
component: "span",
|
|
3750
3759
|
sx: {
|
|
@@ -3755,13 +3764,13 @@ var init_ModelsTable = __esm({
|
|
|
3755
3764
|
fontSize: 11,
|
|
3756
3765
|
fontWeight: 600,
|
|
3757
3766
|
textTransform: "capitalize",
|
|
3758
|
-
bgcolor:
|
|
3767
|
+
bgcolor: alpha7(modeColor(m.mode), 0.1),
|
|
3759
3768
|
color: modeColor(m.mode)
|
|
3760
3769
|
}
|
|
3761
3770
|
},
|
|
3762
3771
|
m.mode
|
|
3763
|
-
)), /* @__PURE__ */
|
|
3764
|
-
/* @__PURE__ */
|
|
3772
|
+
)), /* @__PURE__ */ React10.createElement(TableCell5, { align: "right" }, /* @__PURE__ */ React10.createElement(Typography10, { variant: "body2", sx: { fontVariantNumeric: "tabular-nums" } }, fmtCostPerToken(m.input_cost_per_token))), /* @__PURE__ */ React10.createElement(TableCell5, { align: "right" }, /* @__PURE__ */ React10.createElement(Typography10, { variant: "body2", sx: { fontVariantNumeric: "tabular-nums" } }, fmtCostPerToken(m.output_cost_per_token))), /* @__PURE__ */ React10.createElement(TableCell5, { align: "right" }, /* @__PURE__ */ React10.createElement(Typography10, { variant: "body2", sx: { fontVariantNumeric: "tabular-nums" } }, fmtTokens(m.max_input_tokens))), /* @__PURE__ */ React10.createElement(TableCell5, { align: "right" }, /* @__PURE__ */ React10.createElement(Typography10, { variant: "body2", sx: { fontVariantNumeric: "tabular-nums" } }, fmtTokens(m.max_output_tokens))), /* @__PURE__ */ React10.createElement(TableCell5, null, /* @__PURE__ */ React10.createElement(Box10, { sx: { display: "flex", gap: 0.5, flexWrap: "wrap" } }, m.supports_function_calling && /* @__PURE__ */ React10.createElement(TagChip, { label: "Fn", title: "Function calling" }), m.supports_vision && /* @__PURE__ */ React10.createElement(TagChip, { label: "\u{1F441}", title: "Vision" })))))))),
|
|
3773
|
+
/* @__PURE__ */ React10.createElement(
|
|
3765
3774
|
Snackbar,
|
|
3766
3775
|
{
|
|
3767
3776
|
open: copiedSnackbar,
|
|
@@ -3769,7 +3778,7 @@ var init_ModelsTable = __esm({
|
|
|
3769
3778
|
onClose: () => setCopiedSnackbar(false),
|
|
3770
3779
|
anchorOrigin: { vertical: "bottom", horizontal: "center" }
|
|
3771
3780
|
},
|
|
3772
|
-
/* @__PURE__ */
|
|
3781
|
+
/* @__PURE__ */ React10.createElement(Alert4, { severity: "success", variant: "filled", sx: { fontSize: 13 } }, "Model ID copied to clipboard")
|
|
3773
3782
|
)
|
|
3774
3783
|
);
|
|
3775
3784
|
};
|
|
@@ -3777,9 +3786,9 @@ var init_ModelsTable = __esm({
|
|
|
3777
3786
|
});
|
|
3778
3787
|
|
|
3779
3788
|
// src/components/AuditLog.tsx
|
|
3780
|
-
import
|
|
3781
|
-
import
|
|
3782
|
-
import
|
|
3789
|
+
import React11, { useState as useState8, useMemo as useMemo8 } from "react";
|
|
3790
|
+
import Box11 from "@mui/material/Box";
|
|
3791
|
+
import Typography11 from "@mui/material/Typography";
|
|
3783
3792
|
import Table6 from "@mui/material/Table";
|
|
3784
3793
|
import TableBody6 from "@mui/material/TableBody";
|
|
3785
3794
|
import TableCell6 from "@mui/material/TableCell";
|
|
@@ -3793,7 +3802,7 @@ import Skeleton5 from "@mui/material/Skeleton";
|
|
|
3793
3802
|
import Collapse3 from "@mui/material/Collapse";
|
|
3794
3803
|
import IconButton6 from "@mui/material/IconButton";
|
|
3795
3804
|
import Alert5 from "@mui/material/Alert";
|
|
3796
|
-
import { alpha as
|
|
3805
|
+
import { alpha as alpha8 } from "@mui/material/styles";
|
|
3797
3806
|
import { KeyboardArrowDown } from "@mui/icons-material";
|
|
3798
3807
|
import { useAsync as useAsync2 } from "react-use";
|
|
3799
3808
|
function actionTone(action) {
|
|
@@ -3816,10 +3825,10 @@ function formatDateTime(iso) {
|
|
|
3816
3825
|
}
|
|
3817
3826
|
function renderAuditLogBody(loading, entries) {
|
|
3818
3827
|
if (loading) {
|
|
3819
|
-
return /* @__PURE__ */
|
|
3828
|
+
return /* @__PURE__ */ React11.createElement(TableRow6, null, /* @__PURE__ */ React11.createElement(TableCell6, { colSpan: 6, sx: { py: 2 } }, /* @__PURE__ */ React11.createElement(Skeleton5, { variant: "rounded", height: 160 })));
|
|
3820
3829
|
}
|
|
3821
3830
|
if (entries.length === 0) {
|
|
3822
|
-
return /* @__PURE__ */
|
|
3831
|
+
return /* @__PURE__ */ React11.createElement(TableRow6, null, /* @__PURE__ */ React11.createElement(TableCell6, { colSpan: 6 }, /* @__PURE__ */ React11.createElement(
|
|
3823
3832
|
EmptyState,
|
|
3824
3833
|
{
|
|
3825
3834
|
message: "No audit events found",
|
|
@@ -3827,7 +3836,7 @@ function renderAuditLogBody(loading, entries) {
|
|
|
3827
3836
|
}
|
|
3828
3837
|
)));
|
|
3829
3838
|
}
|
|
3830
|
-
return entries.map((entry) => /* @__PURE__ */
|
|
3839
|
+
return entries.map((entry) => /* @__PURE__ */ React11.createElement(DetailRow, { key: entry.id, entry }));
|
|
3831
3840
|
}
|
|
3832
3841
|
var ACTION_TONES, TABLE_LABELS, DetailRow, AuditLog;
|
|
3833
3842
|
var init_AuditLog = __esm({
|
|
@@ -3851,7 +3860,7 @@ var init_AuditLog = __esm({
|
|
|
3851
3860
|
DetailRow = ({ entry }) => {
|
|
3852
3861
|
const [open, setOpen] = useState8(false);
|
|
3853
3862
|
const hasDetail = entry.before_value || entry.updated_values;
|
|
3854
|
-
return /* @__PURE__ */
|
|
3863
|
+
return /* @__PURE__ */ React11.createElement(React11.Fragment, null, /* @__PURE__ */ React11.createElement(TableRow6, null, /* @__PURE__ */ React11.createElement(TableCell6, { sx: { width: 40, pr: 0 } }, hasDetail && /* @__PURE__ */ React11.createElement(
|
|
3855
3864
|
IconButton6,
|
|
3856
3865
|
{
|
|
3857
3866
|
size: "small",
|
|
@@ -3864,9 +3873,9 @@ var init_AuditLog = __esm({
|
|
|
3864
3873
|
transition: theme.transitions.create("transform")
|
|
3865
3874
|
})
|
|
3866
3875
|
},
|
|
3867
|
-
/* @__PURE__ */
|
|
3868
|
-
)), /* @__PURE__ */
|
|
3869
|
-
|
|
3876
|
+
/* @__PURE__ */ React11.createElement(KeyboardArrowDown, { fontSize: "small" })
|
|
3877
|
+
)), /* @__PURE__ */ React11.createElement(TableCell6, { sx: { whiteSpace: "nowrap" } }, /* @__PURE__ */ React11.createElement(Typography11, { variant: "body2", color: "text.secondary" }, formatDateTime(entry.updated_at))), /* @__PURE__ */ React11.createElement(TableCell6, null, entry.action && /* @__PURE__ */ React11.createElement(StatusPill, { label: entry.action, tone: actionTone(entry.action) })), /* @__PURE__ */ React11.createElement(TableCell6, null, /* @__PURE__ */ React11.createElement(Typography11, { variant: "body2" }, prettyTableName(entry.table_name))), /* @__PURE__ */ React11.createElement(TableCell6, null, /* @__PURE__ */ React11.createElement(
|
|
3878
|
+
Typography11,
|
|
3870
3879
|
{
|
|
3871
3880
|
variant: "body2",
|
|
3872
3881
|
component: "code",
|
|
@@ -3875,8 +3884,8 @@ var init_AuditLog = __esm({
|
|
|
3875
3884
|
sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 12 }
|
|
3876
3885
|
},
|
|
3877
3886
|
entry.object_id ? entry.object_id.slice(0, 20) + (entry.object_id.length > 20 ? "\u2026" : "") : "\u2014"
|
|
3878
|
-
)), /* @__PURE__ */
|
|
3879
|
-
|
|
3887
|
+
)), /* @__PURE__ */ React11.createElement(TableCell6, null, entry.changed_by ?? "\u2014")), hasDetail && /* @__PURE__ */ React11.createElement(TableRow6, { sx: { "&&:hover": { bgcolor: "transparent" } } }, /* @__PURE__ */ React11.createElement(TableCell6, { colSpan: 6, sx: { "&&": { py: 0, border: 0 } } }, /* @__PURE__ */ React11.createElement(Collapse3, { in: open, unmountOnExit: true }, /* @__PURE__ */ React11.createElement(
|
|
3888
|
+
Box11,
|
|
3880
3889
|
{
|
|
3881
3890
|
display: "flex",
|
|
3882
3891
|
gap: 2,
|
|
@@ -3885,11 +3894,11 @@ var init_AuditLog = __esm({
|
|
|
3885
3894
|
p: 2,
|
|
3886
3895
|
mb: 1.5,
|
|
3887
3896
|
borderRadius: 1.5,
|
|
3888
|
-
bgcolor:
|
|
3897
|
+
bgcolor: alpha8(theme.palette.text.primary, theme.palette.mode === "dark" ? 0.05 : 0.03)
|
|
3889
3898
|
})
|
|
3890
3899
|
},
|
|
3891
|
-
entry.before_value && /* @__PURE__ */
|
|
3892
|
-
|
|
3900
|
+
entry.before_value && /* @__PURE__ */ React11.createElement(Box11, { flex: 1, minWidth: 200 }, /* @__PURE__ */ React11.createElement(
|
|
3901
|
+
Typography11,
|
|
3893
3902
|
{
|
|
3894
3903
|
variant: "caption",
|
|
3895
3904
|
color: "text.secondary",
|
|
@@ -3898,9 +3907,9 @@ var init_AuditLog = __esm({
|
|
|
3898
3907
|
sx: { fontSize: 10.5, fontWeight: 700, letterSpacing: "0.07em", textTransform: "uppercase" }
|
|
3899
3908
|
},
|
|
3900
3909
|
"Before"
|
|
3901
|
-
), /* @__PURE__ */
|
|
3902
|
-
entry.updated_values && /* @__PURE__ */
|
|
3903
|
-
|
|
3910
|
+
), /* @__PURE__ */ React11.createElement(Typography11, { component: "pre", variant: "caption", sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", whiteSpace: "pre-wrap", wordBreak: "break-all", m: 0 } }, JSON.stringify(entry.before_value, null, 2))),
|
|
3911
|
+
entry.updated_values && /* @__PURE__ */ React11.createElement(Box11, { flex: 1, minWidth: 200 }, /* @__PURE__ */ React11.createElement(
|
|
3912
|
+
Typography11,
|
|
3904
3913
|
{
|
|
3905
3914
|
variant: "caption",
|
|
3906
3915
|
color: "text.secondary",
|
|
@@ -3909,7 +3918,7 @@ var init_AuditLog = __esm({
|
|
|
3909
3918
|
sx: { fontSize: 10.5, fontWeight: 700, letterSpacing: "0.07em", textTransform: "uppercase" }
|
|
3910
3919
|
},
|
|
3911
3920
|
"After"
|
|
3912
|
-
), /* @__PURE__ */
|
|
3921
|
+
), /* @__PURE__ */ React11.createElement(Typography11, { component: "pre", variant: "caption", sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", whiteSpace: "pre-wrap", wordBreak: "break-all", m: 0 } }, JSON.stringify(entry.updated_values, null, 2)))
|
|
3913
3922
|
)))));
|
|
3914
3923
|
};
|
|
3915
3924
|
AuditLog = ({ api }) => {
|
|
@@ -3926,13 +3935,13 @@ var init_AuditLog = __esm({
|
|
|
3926
3935
|
);
|
|
3927
3936
|
const entries = value?.audit_logs ?? [];
|
|
3928
3937
|
const total = value?.total ?? 0;
|
|
3929
|
-
return /* @__PURE__ */
|
|
3938
|
+
return /* @__PURE__ */ React11.createElement(
|
|
3930
3939
|
SectionCard,
|
|
3931
3940
|
{
|
|
3932
3941
|
title: "Audit Log",
|
|
3933
3942
|
subtitle: loading ? "Loading\u2026" : `${total.toLocaleString()} event${total === 1 ? "" : "s"}`,
|
|
3934
3943
|
flush: true,
|
|
3935
|
-
actions: /* @__PURE__ */
|
|
3944
|
+
actions: /* @__PURE__ */ React11.createElement(React11.Fragment, null, /* @__PURE__ */ React11.createElement(
|
|
3936
3945
|
TextField6,
|
|
3937
3946
|
{
|
|
3938
3947
|
size: "small",
|
|
@@ -3945,12 +3954,12 @@ var init_AuditLog = __esm({
|
|
|
3945
3954
|
},
|
|
3946
3955
|
sx: { minWidth: 150 }
|
|
3947
3956
|
},
|
|
3948
|
-
/* @__PURE__ */
|
|
3949
|
-
/* @__PURE__ */
|
|
3950
|
-
/* @__PURE__ */
|
|
3951
|
-
/* @__PURE__ */
|
|
3952
|
-
/* @__PURE__ */
|
|
3953
|
-
), /* @__PURE__ */
|
|
3957
|
+
/* @__PURE__ */ React11.createElement(MenuItem5, { value: "" }, "All actions"),
|
|
3958
|
+
/* @__PURE__ */ React11.createElement(MenuItem5, { value: "created" }, "Created"),
|
|
3959
|
+
/* @__PURE__ */ React11.createElement(MenuItem5, { value: "updated" }, "Updated"),
|
|
3960
|
+
/* @__PURE__ */ React11.createElement(MenuItem5, { value: "deleted" }, "Deleted"),
|
|
3961
|
+
/* @__PURE__ */ React11.createElement(MenuItem5, { value: "blocked" }, "Blocked")
|
|
3962
|
+
), /* @__PURE__ */ React11.createElement(
|
|
3954
3963
|
TextField6,
|
|
3955
3964
|
{
|
|
3956
3965
|
size: "small",
|
|
@@ -3963,13 +3972,13 @@ var init_AuditLog = __esm({
|
|
|
3963
3972
|
},
|
|
3964
3973
|
sx: { minWidth: 150 }
|
|
3965
3974
|
},
|
|
3966
|
-
/* @__PURE__ */
|
|
3967
|
-
/* @__PURE__ */
|
|
3968
|
-
/* @__PURE__ */
|
|
3969
|
-
/* @__PURE__ */
|
|
3970
|
-
/* @__PURE__ */
|
|
3971
|
-
/* @__PURE__ */
|
|
3972
|
-
), /* @__PURE__ */
|
|
3975
|
+
/* @__PURE__ */ React11.createElement(MenuItem5, { value: "" }, "All tables"),
|
|
3976
|
+
/* @__PURE__ */ React11.createElement(MenuItem5, { value: "LiteLLM_VerificationToken" }, "Key"),
|
|
3977
|
+
/* @__PURE__ */ React11.createElement(MenuItem5, { value: "LiteLLM_TeamTable" }, "Team"),
|
|
3978
|
+
/* @__PURE__ */ React11.createElement(MenuItem5, { value: "LiteLLM_TeamMembership" }, "Team member"),
|
|
3979
|
+
/* @__PURE__ */ React11.createElement(MenuItem5, { value: "LiteLLM_ObjectPermissionTable" }, "Team access (KB / MCP)"),
|
|
3980
|
+
/* @__PURE__ */ React11.createElement(MenuItem5, { value: "LiteLLM_UserTable" }, "User")
|
|
3981
|
+
), /* @__PURE__ */ React11.createElement(
|
|
3973
3982
|
TextField6,
|
|
3974
3983
|
{
|
|
3975
3984
|
size: "small",
|
|
@@ -3983,9 +3992,9 @@ var init_AuditLog = __esm({
|
|
|
3983
3992
|
}
|
|
3984
3993
|
))
|
|
3985
3994
|
},
|
|
3986
|
-
error && /* @__PURE__ */
|
|
3987
|
-
/* @__PURE__ */
|
|
3988
|
-
/* @__PURE__ */
|
|
3995
|
+
error && /* @__PURE__ */ React11.createElement(Box11, { px: 2.5, pb: 2 }, /* @__PURE__ */ React11.createElement(Alert5, { severity: "error" }, error.message)),
|
|
3996
|
+
/* @__PURE__ */ React11.createElement(TableContainer6, null, /* @__PURE__ */ React11.createElement(Table6, { size: "small", sx: dataTableSx }, /* @__PURE__ */ React11.createElement(TableHead6, null, /* @__PURE__ */ React11.createElement(TableRow6, null, /* @__PURE__ */ React11.createElement(TableCell6, { sx: { width: 40 } }), /* @__PURE__ */ React11.createElement(TableCell6, null, "Time"), /* @__PURE__ */ React11.createElement(TableCell6, null, "Action"), /* @__PURE__ */ React11.createElement(TableCell6, null, "Table"), /* @__PURE__ */ React11.createElement(TableCell6, null, "Object ID"), /* @__PURE__ */ React11.createElement(TableCell6, null, "Changed By"))), /* @__PURE__ */ React11.createElement(TableBody6, null, renderAuditLogBody(loading, entries)))),
|
|
3997
|
+
/* @__PURE__ */ React11.createElement(
|
|
3989
3998
|
TablePagination,
|
|
3990
3999
|
{
|
|
3991
4000
|
component: "div",
|
|
@@ -4040,13 +4049,13 @@ var LiteLLMPage_exports = {};
|
|
|
4040
4049
|
__export(LiteLLMPage_exports, {
|
|
4041
4050
|
LiteLLMPage: () => LiteLLMPage
|
|
4042
4051
|
});
|
|
4043
|
-
import
|
|
4044
|
-
import
|
|
4052
|
+
import React12, { useState as useState9, useCallback as useCallback2, useEffect as useEffect4, useMemo as useMemo9 } from "react";
|
|
4053
|
+
import Box12 from "@mui/material/Box";
|
|
4045
4054
|
import Snackbar2 from "@mui/material/Snackbar";
|
|
4046
4055
|
import Alert6 from "@mui/material/Alert";
|
|
4047
4056
|
import CircularProgress6 from "@mui/material/CircularProgress";
|
|
4048
|
-
import
|
|
4049
|
-
import
|
|
4057
|
+
import Typography12 from "@mui/material/Typography";
|
|
4058
|
+
import Paper8 from "@mui/material/Paper";
|
|
4050
4059
|
import Tabs2 from "@mui/material/Tabs";
|
|
4051
4060
|
import Tab2 from "@mui/material/Tab";
|
|
4052
4061
|
import Grid2 from "@mui/material/Grid";
|
|
@@ -4074,7 +4083,7 @@ var init_LiteLLMPage = __esm({
|
|
|
4074
4083
|
"use strict";
|
|
4075
4084
|
init_DashboardHeader();
|
|
4076
4085
|
init_KeysTable();
|
|
4077
|
-
|
|
4086
|
+
init_KeyFormDialog();
|
|
4078
4087
|
init_ManageTeamDialog();
|
|
4079
4088
|
init_UsageStats();
|
|
4080
4089
|
init_LiteLLMBudgetWidget();
|
|
@@ -4101,6 +4110,21 @@ var init_LiteLLMPage = __esm({
|
|
|
4101
4110
|
const t = searchParams.get("tab");
|
|
4102
4111
|
return isPageTab(t) ? t : "overview";
|
|
4103
4112
|
});
|
|
4113
|
+
const [generateDialogOpen, setGenerateDialogOpen] = useState9(
|
|
4114
|
+
() => searchParams.get("generate") === "1"
|
|
4115
|
+
);
|
|
4116
|
+
useEffect4(() => {
|
|
4117
|
+
if (searchParams.get("generate") !== "1") return;
|
|
4118
|
+
setGenerateDialogOpen(true);
|
|
4119
|
+
setSearchParams(
|
|
4120
|
+
(prev) => {
|
|
4121
|
+
const next = new URLSearchParams(prev);
|
|
4122
|
+
next.delete("generate");
|
|
4123
|
+
return next;
|
|
4124
|
+
},
|
|
4125
|
+
{ replace: true }
|
|
4126
|
+
);
|
|
4127
|
+
}, [searchParams, setSearchParams]);
|
|
4104
4128
|
const selectTab = useCallback2(
|
|
4105
4129
|
(tab) => {
|
|
4106
4130
|
setActiveTab(tab);
|
|
@@ -4116,8 +4140,9 @@ var init_LiteLLMPage = __esm({
|
|
|
4116
4140
|
[setSearchParams]
|
|
4117
4141
|
);
|
|
4118
4142
|
const [snackbar, setSnackbar] = useState9(null);
|
|
4119
|
-
const [generateDialogOpen, setGenerateDialogOpen] = useState9(false);
|
|
4120
4143
|
const [manageTeam, setManageTeam] = useState9(null);
|
|
4144
|
+
const [keyToEdit, setKeyToEdit] = useState9(null);
|
|
4145
|
+
const keyFormOpen = generateDialogOpen || !!keyToEdit;
|
|
4121
4146
|
const [teamUsageCache, setTeamUsageCache] = useState9({});
|
|
4122
4147
|
const [teamUsageLoading, setTeamUsageLoading] = useState9({});
|
|
4123
4148
|
const { value: userInfo, loading: userLoading, error: userError } = useAsync3(
|
|
@@ -4310,14 +4335,14 @@ var init_LiteLLMPage = __esm({
|
|
|
4310
4335
|
);
|
|
4311
4336
|
const isInitialLoading = userLoading && !userInfo;
|
|
4312
4337
|
if (isInitialLoading) {
|
|
4313
|
-
return /* @__PURE__ */
|
|
4338
|
+
return /* @__PURE__ */ React12.createElement(Box12, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: "50vh" }, /* @__PURE__ */ React12.createElement(CircularProgress6, null));
|
|
4314
4339
|
}
|
|
4315
4340
|
if (userError || !userInfo) {
|
|
4316
4341
|
const isProvisioningEnabled = userError?.body?.provisioning === true;
|
|
4317
4342
|
const hint = userError?.body?.hint;
|
|
4318
|
-
return /* @__PURE__ */
|
|
4343
|
+
return /* @__PURE__ */ React12.createElement(Box12, { p: 3 }, /* @__PURE__ */ React12.createElement(Paper8, { sx: { p: 3 } }, /* @__PURE__ */ React12.createElement(Typography12, { variant: "h6", gutterBottom: true }, "Account not provisioned"), /* @__PURE__ */ React12.createElement(Typography12, { color: "text.secondary", paragraph: true }, "Your Backstage account is not linked to a LiteLLM user."), hint ? /* @__PURE__ */ React12.createElement(Typography12, { variant: "body2", color: "text.secondary" }, hint) : /* @__PURE__ */ React12.createElement(Typography12, { variant: "body2", color: "text.secondary" }, isProvisioningEnabled ? "Auto-provisioning is enabled but failed. Check the backend logs." : "Set litellm.provisioning.enabled: true in app-config.yaml to enable auto-provisioning, or ask your administrator to create the account manually.")));
|
|
4319
4344
|
}
|
|
4320
|
-
const pageTabs = /* @__PURE__ */
|
|
4345
|
+
const pageTabs = /* @__PURE__ */ React12.createElement(
|
|
4321
4346
|
Tabs2,
|
|
4322
4347
|
{
|
|
4323
4348
|
value: activeTab,
|
|
@@ -4330,13 +4355,13 @@ var init_LiteLLMPage = __esm({
|
|
|
4330
4355
|
'& [class*="MuiTab-root"]': { minHeight: 44, textTransform: "none", fontSize: 14 }
|
|
4331
4356
|
}
|
|
4332
4357
|
},
|
|
4333
|
-
/* @__PURE__ */
|
|
4334
|
-
/* @__PURE__ */
|
|
4335
|
-
/* @__PURE__ */
|
|
4336
|
-
/* @__PURE__ */
|
|
4337
|
-
userInfo.can_view_audit && /* @__PURE__ */
|
|
4358
|
+
/* @__PURE__ */ React12.createElement(Tab2, { label: "Overview", value: "overview" }),
|
|
4359
|
+
/* @__PURE__ */ React12.createElement(Tab2, { label: "Keys", value: "keys" }),
|
|
4360
|
+
/* @__PURE__ */ React12.createElement(Tab2, { label: "Teams", value: "teams" }),
|
|
4361
|
+
/* @__PURE__ */ React12.createElement(Tab2, { label: "Models", value: "models" }),
|
|
4362
|
+
userInfo.can_view_audit && /* @__PURE__ */ React12.createElement(Tab2, { label: "Audit Log", value: "audit" })
|
|
4338
4363
|
);
|
|
4339
|
-
return /* @__PURE__ */
|
|
4364
|
+
return /* @__PURE__ */ React12.createElement(Box12, { sx: { p: 3, display: "flex", flexDirection: "column", gap: 2 } }, /* @__PURE__ */ React12.createElement(
|
|
4340
4365
|
DashboardHeader,
|
|
4341
4366
|
{
|
|
4342
4367
|
userInfo,
|
|
@@ -4346,7 +4371,7 @@ var init_LiteLLMPage = __esm({
|
|
|
4346
4371
|
onGenerateKeyClick: () => setGenerateDialogOpen(true),
|
|
4347
4372
|
tabs: pageTabs
|
|
4348
4373
|
}
|
|
4349
|
-
), activeTab === "overview" && /* @__PURE__ */
|
|
4374
|
+
), activeTab === "overview" && /* @__PURE__ */ React12.createElement(Grid2, { container: true, spacing: 2, alignItems: "flex-start" }, /* @__PURE__ */ React12.createElement(Grid2, { item: true, xs: 12, lg: 8 }, /* @__PURE__ */ React12.createElement(
|
|
4350
4375
|
UsageStats,
|
|
4351
4376
|
{
|
|
4352
4377
|
usage: usage ?? null,
|
|
@@ -4357,17 +4382,15 @@ var init_LiteLLMPage = __esm({
|
|
|
4357
4382
|
loading: usageLoading,
|
|
4358
4383
|
userInfo
|
|
4359
4384
|
}
|
|
4360
|
-
)), /* @__PURE__ */
|
|
4385
|
+
)), /* @__PURE__ */ React12.createElement(Grid2, { item: true, xs: 12, lg: 4 }, /* @__PURE__ */ React12.createElement(LiteLLMBudgetWidget, { compact: true, collapsible: true }))), activeTab === "keys" && /* @__PURE__ */ React12.createElement(
|
|
4361
4386
|
KeysTable,
|
|
4362
4387
|
{
|
|
4363
4388
|
keys: keys ?? [],
|
|
4364
|
-
models: allowedModels,
|
|
4365
4389
|
loading: keysLoading || modelsLoading,
|
|
4366
4390
|
onGenerateKeyClick: () => setGenerateDialogOpen(true),
|
|
4367
|
-
|
|
4391
|
+
onEditKey: setKeyToEdit,
|
|
4368
4392
|
onBlockKey: handleBlockKey,
|
|
4369
4393
|
onUnblockKey: handleUnblockKey,
|
|
4370
|
-
onResetKeySpend: handleResetKeySpend,
|
|
4371
4394
|
onDeleteKey: handleDeleteKey,
|
|
4372
4395
|
onPruneExpiredKeys: handlePruneExpiredKeys
|
|
4373
4396
|
}
|
|
@@ -4376,7 +4399,7 @@ var init_LiteLLMPage = __esm({
|
|
|
4376
4399
|
for (const t of managedTeams ?? []) teamsById.set(t.team_id, t);
|
|
4377
4400
|
for (const t of teams ?? []) teamsById.set(t.team_id, t);
|
|
4378
4401
|
const visibleTeams = Array.from(teamsById.values());
|
|
4379
|
-
return /* @__PURE__ */
|
|
4402
|
+
return /* @__PURE__ */ React12.createElement(
|
|
4380
4403
|
TeamUsage,
|
|
4381
4404
|
{
|
|
4382
4405
|
teams: visibleTeams,
|
|
@@ -4392,27 +4415,34 @@ var init_LiteLLMPage = __esm({
|
|
|
4392
4415
|
onCreateTeam: () => setManageTeam({ mode: "create" })
|
|
4393
4416
|
}
|
|
4394
4417
|
);
|
|
4395
|
-
})(), activeTab === "models" && /* @__PURE__ */
|
|
4418
|
+
})(), activeTab === "models" && /* @__PURE__ */ React12.createElement(
|
|
4396
4419
|
ModelsTable,
|
|
4397
4420
|
{
|
|
4398
4421
|
allModels: allModels ?? [],
|
|
4399
4422
|
teams: teams ?? [],
|
|
4400
4423
|
loading: modelsLoading
|
|
4401
4424
|
}
|
|
4402
|
-
), activeTab === "audit" && userInfo.can_view_audit && /* @__PURE__ */
|
|
4403
|
-
|
|
4425
|
+
), activeTab === "audit" && userInfo.can_view_audit && /* @__PURE__ */ React12.createElement(AuditLog, { api }), /* @__PURE__ */ React12.createElement(
|
|
4426
|
+
KeyFormDialog,
|
|
4404
4427
|
{
|
|
4405
|
-
open:
|
|
4406
|
-
onClose: () =>
|
|
4428
|
+
open: keyFormOpen,
|
|
4429
|
+
onClose: () => {
|
|
4430
|
+
setGenerateDialogOpen(false);
|
|
4431
|
+
setKeyToEdit(null);
|
|
4432
|
+
},
|
|
4433
|
+
mode: keyToEdit ? "edit" : "create",
|
|
4434
|
+
keyToEdit,
|
|
4407
4435
|
keys: keys ?? [],
|
|
4408
4436
|
models: allowedModels,
|
|
4409
4437
|
teams: teams ?? [],
|
|
4410
4438
|
username: userInfo.user_id,
|
|
4411
4439
|
keyGenerationSettings: liteLlmConfig?.keyGeneration,
|
|
4412
|
-
|
|
4440
|
+
onCreateKey: handleGenerateKey,
|
|
4441
|
+
onUpdateKey: handleUpdateKey,
|
|
4442
|
+
onResetKeySpend: handleResetKeySpend,
|
|
4413
4443
|
onGetConfig: () => api.getConfig()
|
|
4414
4444
|
}
|
|
4415
|
-
), /* @__PURE__ */
|
|
4445
|
+
), /* @__PURE__ */ React12.createElement(
|
|
4416
4446
|
ManageTeamDialog,
|
|
4417
4447
|
{
|
|
4418
4448
|
open: !!manageTeam,
|
|
@@ -4474,7 +4504,7 @@ var init_LiteLLMPage = __esm({
|
|
|
4474
4504
|
refreshTeams();
|
|
4475
4505
|
}
|
|
4476
4506
|
}
|
|
4477
|
-
), /* @__PURE__ */
|
|
4507
|
+
), /* @__PURE__ */ React12.createElement(
|
|
4478
4508
|
Snackbar2,
|
|
4479
4509
|
{
|
|
4480
4510
|
open: !!snackbar,
|
|
@@ -4482,7 +4512,7 @@ var init_LiteLLMPage = __esm({
|
|
|
4482
4512
|
onClose: () => setSnackbar(null),
|
|
4483
4513
|
anchorOrigin: { vertical: "bottom", horizontal: "right" }
|
|
4484
4514
|
},
|
|
4485
|
-
snackbar ? /* @__PURE__ */
|
|
4515
|
+
snackbar ? /* @__PURE__ */ React12.createElement(Alert6, { severity: snackbar.severity, onClose: () => setSnackbar(null) }, snackbar.message) : void 0
|
|
4486
4516
|
));
|
|
4487
4517
|
};
|
|
4488
4518
|
}
|
|
@@ -4490,7 +4520,7 @@ var init_LiteLLMPage = __esm({
|
|
|
4490
4520
|
|
|
4491
4521
|
// src/plugin.tsx
|
|
4492
4522
|
init_api();
|
|
4493
|
-
import
|
|
4523
|
+
import React13 from "react";
|
|
4494
4524
|
import { TrendingUp as TrendingUpIcon } from "@mui/icons-material";
|
|
4495
4525
|
import {
|
|
4496
4526
|
createFrontendPlugin,
|
|
@@ -4509,10 +4539,10 @@ var liteLlmPage = PageBlueprint.make({
|
|
|
4509
4539
|
params: {
|
|
4510
4540
|
path: "/litellm",
|
|
4511
4541
|
title: "LiteLLM",
|
|
4512
|
-
icon: /* @__PURE__ */
|
|
4542
|
+
icon: /* @__PURE__ */ React13.createElement(TrendingUpIcon, null),
|
|
4513
4543
|
loader: async () => {
|
|
4514
4544
|
const { LiteLLMPage: LiteLLMPage2 } = await Promise.resolve().then(() => (init_LiteLLMPage(), LiteLLMPage_exports));
|
|
4515
|
-
return /* @__PURE__ */
|
|
4545
|
+
return /* @__PURE__ */ React13.createElement(LiteLLMPage2, null);
|
|
4516
4546
|
}
|
|
4517
4547
|
}
|
|
4518
4548
|
});
|
|
@@ -4531,10 +4561,10 @@ init_TeamUsage();
|
|
|
4531
4561
|
// src/components/LiteLLMHomeWidget.tsx
|
|
4532
4562
|
init_api();
|
|
4533
4563
|
init_format();
|
|
4534
|
-
import
|
|
4535
|
-
import
|
|
4536
|
-
import
|
|
4537
|
-
import
|
|
4564
|
+
import React14, { useState as useState10, useEffect as useEffect5 } from "react";
|
|
4565
|
+
import Paper9 from "@mui/material/Paper";
|
|
4566
|
+
import Box13 from "@mui/material/Box";
|
|
4567
|
+
import Typography13 from "@mui/material/Typography";
|
|
4538
4568
|
import FormControl2 from "@mui/material/FormControl";
|
|
4539
4569
|
import Select2 from "@mui/material/Select";
|
|
4540
4570
|
import MenuItem6 from "@mui/material/MenuItem";
|
|
@@ -4555,7 +4585,7 @@ function presetToDateRange(preset) {
|
|
|
4555
4585
|
}
|
|
4556
4586
|
return { start, end };
|
|
4557
4587
|
}
|
|
4558
|
-
var Kpi = ({ label, value }) => /* @__PURE__ */
|
|
4588
|
+
var Kpi = ({ label, value }) => /* @__PURE__ */ React14.createElement(Box13, null, /* @__PURE__ */ React14.createElement(Typography13, { variant: "caption", color: "text.secondary", display: "block" }, label), /* @__PURE__ */ React14.createElement(Typography13, { variant: "subtitle1", fontWeight: 600 }, value));
|
|
4559
4589
|
var LiteLLMHomeWidget = ({
|
|
4560
4590
|
defaultPeriod = "7d",
|
|
4561
4591
|
title = "LiteLLM Usage"
|
|
@@ -4567,7 +4597,7 @@ var LiteLLMHomeWidget = ({
|
|
|
4567
4597
|
const [keysError, setKeysError] = useState10(null);
|
|
4568
4598
|
const [usage, setUsage] = useState10(null);
|
|
4569
4599
|
const [keys, setKeys] = useState10([]);
|
|
4570
|
-
|
|
4600
|
+
useEffect5(() => {
|
|
4571
4601
|
let cancelled = false;
|
|
4572
4602
|
setLoading(true);
|
|
4573
4603
|
setUsageError(null);
|
|
@@ -4604,17 +4634,17 @@ var LiteLLMHomeWidget = ({
|
|
|
4604
4634
|
spend: d.spend
|
|
4605
4635
|
}));
|
|
4606
4636
|
const hasSparkline = dailyData.length > 0;
|
|
4607
|
-
return /* @__PURE__ */
|
|
4637
|
+
return /* @__PURE__ */ React14.createElement(Paper9, { sx: { p: 2 } }, /* @__PURE__ */ React14.createElement(Box13, { display: "flex", justifyContent: "space-between", alignItems: "center", mb: 1.5 }, /* @__PURE__ */ React14.createElement(Typography13, { variant: "h6" }, title), /* @__PURE__ */ React14.createElement(FormControl2, { size: "small", sx: { minWidth: 90 } }, /* @__PURE__ */ React14.createElement(
|
|
4608
4638
|
Select2,
|
|
4609
4639
|
{
|
|
4610
4640
|
value: period,
|
|
4611
4641
|
onChange: (e) => setPeriod(e.target.value),
|
|
4612
4642
|
displayEmpty: true
|
|
4613
4643
|
},
|
|
4614
|
-
/* @__PURE__ */
|
|
4615
|
-
/* @__PURE__ */
|
|
4616
|
-
/* @__PURE__ */
|
|
4617
|
-
))), loading && /* @__PURE__ */
|
|
4644
|
+
/* @__PURE__ */ React14.createElement(MenuItem6, { value: "today" }, "Today"),
|
|
4645
|
+
/* @__PURE__ */ React14.createElement(MenuItem6, { value: "7d" }, "7d"),
|
|
4646
|
+
/* @__PURE__ */ React14.createElement(MenuItem6, { value: "30d" }, "30d")
|
|
4647
|
+
))), loading && /* @__PURE__ */ React14.createElement(Box13, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: 120 }, /* @__PURE__ */ React14.createElement(CircularProgress7, { size: 32 })), !loading && totalFailure && /* @__PURE__ */ React14.createElement(Alert7, { severity: "error", sx: { mt: 1 } }, usageError ?? "Failed to load usage data"), !loading && !totalFailure && /* @__PURE__ */ React14.createElement(React14.Fragment, null, partialFailure && /* @__PURE__ */ React14.createElement(Alert7, { severity: "warning", sx: { mt: 1, mb: 1 } }, usageError ? `Usage data unavailable (${usageError}).` : "", keysError ? ` Key list unavailable (${keysError}).` : "", " Showing what loaded."), /* @__PURE__ */ React14.createElement(Grid3, { container: true, spacing: 2, sx: { mb: hasSparkline ? 1.5 : 0 } }, /* @__PURE__ */ React14.createElement(Grid3, { item: true, xs: 6 }, /* @__PURE__ */ React14.createElement(Kpi, { label: "USD Spent", value: fmtUsd(usage?.total_spend ?? 0) })), /* @__PURE__ */ React14.createElement(Grid3, { item: true, xs: 6 }, /* @__PURE__ */ React14.createElement(Kpi, { label: "Tokens In", value: fmtInt(usage?.prompt_tokens ?? 0) })), /* @__PURE__ */ React14.createElement(Grid3, { item: true, xs: 6 }, /* @__PURE__ */ React14.createElement(Kpi, { label: "Tokens Out", value: fmtInt(usage?.completion_tokens ?? 0) })), /* @__PURE__ */ React14.createElement(Grid3, { item: true, xs: 6 }, /* @__PURE__ */ React14.createElement(Kpi, { label: "Keys", value: fmtInt(keys.length) }))), hasSparkline && /* @__PURE__ */ React14.createElement(Box13, { height: 120 }, /* @__PURE__ */ React14.createElement(ResponsiveContainer3, { width: "100%", height: "100%" }, /* @__PURE__ */ React14.createElement(AreaChart3, { data: dailyData, margin: { top: 4, right: 0, bottom: 0, left: 0 } }, /* @__PURE__ */ React14.createElement(
|
|
4618
4648
|
Area3,
|
|
4619
4649
|
{
|
|
4620
4650
|
type: "monotone",
|
|
@@ -4635,21 +4665,31 @@ init_LiteLLMBudgetWidget();
|
|
|
4635
4665
|
init_api();
|
|
4636
4666
|
init_format();
|
|
4637
4667
|
init_ui();
|
|
4668
|
+
init_BudgetLimitList();
|
|
4638
4669
|
init_budget();
|
|
4639
|
-
import
|
|
4640
|
-
import
|
|
4641
|
-
import
|
|
4642
|
-
import
|
|
4670
|
+
import React15, { useCallback as useCallback3, useEffect as useEffect6, useMemo as useMemo10, useState as useState11 } from "react";
|
|
4671
|
+
import Paper10 from "@mui/material/Paper";
|
|
4672
|
+
import Box14 from "@mui/material/Box";
|
|
4673
|
+
import Button6 from "@mui/material/Button";
|
|
4674
|
+
import Typography14 from "@mui/material/Typography";
|
|
4643
4675
|
import CircularProgress8 from "@mui/material/CircularProgress";
|
|
4644
4676
|
import Alert8 from "@mui/material/Alert";
|
|
4677
|
+
import Collapse4 from "@mui/material/Collapse";
|
|
4678
|
+
import { ExpandMore as ExpandMore4 } from "@mui/icons-material";
|
|
4645
4679
|
import { useApi as useApi5 } from "@backstage/core-plugin-api";
|
|
4646
4680
|
import { Link as Link2 } from "@backstage/core-components";
|
|
4647
|
-
var
|
|
4681
|
+
var MODULE_PATH = "/litellm";
|
|
4648
4682
|
var LEVEL = {
|
|
4649
4683
|
key: { name: "Key", none: "No key budget" },
|
|
4650
4684
|
user: { name: "User", none: "No personal budget" },
|
|
4651
4685
|
team: { name: "Team", none: "No team budget" }
|
|
4652
4686
|
};
|
|
4687
|
+
var DEFAULT_CTAS = ["new-key", "module", "all-limits"];
|
|
4688
|
+
var CTA_LABELS = {
|
|
4689
|
+
"new-key": "New key",
|
|
4690
|
+
module: "Open module",
|
|
4691
|
+
"all-limits": "All limits"
|
|
4692
|
+
};
|
|
4653
4693
|
function fmtUsdShort(n) {
|
|
4654
4694
|
const v = n ?? 0;
|
|
4655
4695
|
if (v >= 100 && Number.isInteger(v)) return `$${v}`;
|
|
@@ -4672,8 +4712,8 @@ var LevelGauge = ({
|
|
|
4672
4712
|
const limit = gauge.limit;
|
|
4673
4713
|
const tone = limit ? budgetTone2(limit.pct) : "neutral";
|
|
4674
4714
|
const extra = gauge.kind === "key" ? Math.max(0, gauge.count - 1) : 0;
|
|
4675
|
-
return /* @__PURE__ */
|
|
4676
|
-
|
|
4715
|
+
return /* @__PURE__ */ React15.createElement(
|
|
4716
|
+
Box14,
|
|
4677
4717
|
{
|
|
4678
4718
|
sx: {
|
|
4679
4719
|
display: "flex",
|
|
@@ -4683,7 +4723,7 @@ var LevelGauge = ({
|
|
|
4683
4723
|
flex: 1
|
|
4684
4724
|
}
|
|
4685
4725
|
},
|
|
4686
|
-
/* @__PURE__ */
|
|
4726
|
+
/* @__PURE__ */ React15.createElement(
|
|
4687
4727
|
Gauge,
|
|
4688
4728
|
{
|
|
4689
4729
|
value: limit?.pct ?? 0,
|
|
@@ -4692,8 +4732,8 @@ var LevelGauge = ({
|
|
|
4692
4732
|
label: limit ? `${Math.round(limit.pct)}%` : "\u2014"
|
|
4693
4733
|
}
|
|
4694
4734
|
),
|
|
4695
|
-
/* @__PURE__ */
|
|
4696
|
-
|
|
4735
|
+
/* @__PURE__ */ React15.createElement(
|
|
4736
|
+
Typography14,
|
|
4697
4737
|
{
|
|
4698
4738
|
sx: (theme) => ({
|
|
4699
4739
|
mt: 0.75,
|
|
@@ -4706,8 +4746,8 @@ var LevelGauge = ({
|
|
|
4706
4746
|
},
|
|
4707
4747
|
name
|
|
4708
4748
|
),
|
|
4709
|
-
limit ? /* @__PURE__ */
|
|
4710
|
-
|
|
4749
|
+
limit ? /* @__PURE__ */ React15.createElement(React15.Fragment, null, /* @__PURE__ */ React15.createElement(
|
|
4750
|
+
Typography14,
|
|
4711
4751
|
{
|
|
4712
4752
|
variant: "caption",
|
|
4713
4753
|
title: limit.sublabel ? `${limit.label} \xB7 ${limit.sublabel}` : limit.label,
|
|
@@ -4722,8 +4762,8 @@ var LevelGauge = ({
|
|
|
4722
4762
|
}
|
|
4723
4763
|
},
|
|
4724
4764
|
limit.label
|
|
4725
|
-
), /* @__PURE__ */
|
|
4726
|
-
|
|
4765
|
+
), /* @__PURE__ */ React15.createElement(
|
|
4766
|
+
Typography14,
|
|
4727
4767
|
{
|
|
4728
4768
|
variant: "caption",
|
|
4729
4769
|
color: "text.secondary",
|
|
@@ -4734,8 +4774,8 @@ var LevelGauge = ({
|
|
|
4734
4774
|
}
|
|
4735
4775
|
},
|
|
4736
4776
|
spendCaption(limit)
|
|
4737
|
-
), extra > 0 ? /* @__PURE__ */
|
|
4738
|
-
|
|
4777
|
+
), extra > 0 ? /* @__PURE__ */ React15.createElement(
|
|
4778
|
+
Typography14,
|
|
4739
4779
|
{
|
|
4740
4780
|
component: Link2,
|
|
4741
4781
|
to: keysHref,
|
|
@@ -4750,8 +4790,8 @@ var LevelGauge = ({
|
|
|
4750
4790
|
"+",
|
|
4751
4791
|
extra,
|
|
4752
4792
|
" more"
|
|
4753
|
-
) : /* @__PURE__ */
|
|
4754
|
-
|
|
4793
|
+
) : /* @__PURE__ */ React15.createElement(Typography14, { variant: "caption", color: "text.secondary", sx: { fontSize: 10.5 } }, resetLabel(limit))) : /* @__PURE__ */ React15.createElement(
|
|
4794
|
+
Typography14,
|
|
4755
4795
|
{
|
|
4756
4796
|
variant: "caption",
|
|
4757
4797
|
color: "text.secondary",
|
|
@@ -4764,7 +4804,14 @@ var LevelGauge = ({
|
|
|
4764
4804
|
var LiteLLMBudgetGauges = ({
|
|
4765
4805
|
title = "Budget",
|
|
4766
4806
|
size = 72,
|
|
4767
|
-
keysHref
|
|
4807
|
+
keysHref,
|
|
4808
|
+
moduleHref = MODULE_PATH,
|
|
4809
|
+
onCreateKey,
|
|
4810
|
+
ctas,
|
|
4811
|
+
maxExpandedKeys = 8,
|
|
4812
|
+
expanded,
|
|
4813
|
+
defaultExpanded = false,
|
|
4814
|
+
onExpandedChange,
|
|
4768
4815
|
action
|
|
4769
4816
|
}) => {
|
|
4770
4817
|
const api = useApi5(liteLlmApiRef);
|
|
@@ -4773,7 +4820,16 @@ var LiteLLMBudgetGauges = ({
|
|
|
4773
4820
|
const [user, setUser] = useState11(null);
|
|
4774
4821
|
const [teams, setTeams] = useState11([]);
|
|
4775
4822
|
const [keys, setKeys] = useState11([]);
|
|
4776
|
-
|
|
4823
|
+
const isControlled = expanded !== void 0;
|
|
4824
|
+
const [uncontrolledExpanded, setUncontrolledExpanded] = useState11(defaultExpanded);
|
|
4825
|
+
const isExpanded = isControlled ? expanded : uncontrolledExpanded;
|
|
4826
|
+
const toggleExpanded = useCallback3(() => {
|
|
4827
|
+
const next = !isExpanded;
|
|
4828
|
+
if (!isControlled) setUncontrolledExpanded(next);
|
|
4829
|
+
onExpandedChange?.(next);
|
|
4830
|
+
}, [isControlled, isExpanded, onExpandedChange]);
|
|
4831
|
+
const keysTabHref = keysHref ?? `${moduleHref}?tab=keys`;
|
|
4832
|
+
useEffect6(() => {
|
|
4777
4833
|
let cancelled = false;
|
|
4778
4834
|
setLoading(true);
|
|
4779
4835
|
setError(null);
|
|
@@ -4803,35 +4859,139 @@ var LiteLLMBudgetGauges = ({
|
|
|
4803
4859
|
);
|
|
4804
4860
|
const gauges = useMemo10(() => buildBudgetGauges(summary), [summary]);
|
|
4805
4861
|
const headline = useMemo10(() => budgetHeadline(summary), [summary]);
|
|
4862
|
+
const expandedSummary = useMemo10(
|
|
4863
|
+
() => buildBudgetSummary(user, teams, keys, maxExpandedKeys),
|
|
4864
|
+
[user, teams, keys, maxExpandedKeys]
|
|
4865
|
+
);
|
|
4866
|
+
const expandedLimits = useMemo10(
|
|
4867
|
+
() => allBudgetLimits(expandedSummary),
|
|
4868
|
+
[expandedSummary]
|
|
4869
|
+
);
|
|
4806
4870
|
const summaryText = headline.count === 0 ? "no limits apply" : `${headline.count} limit${headline.count === 1 ? "" : "s"}`;
|
|
4807
|
-
|
|
4871
|
+
const resolvedCtas = useMemo10(() => {
|
|
4872
|
+
const list = ctas ?? DEFAULT_CTAS;
|
|
4873
|
+
return list.map((cta) => typeof cta === "string" ? { kind: cta } : cta).filter((cta) => cta.kind !== "all-limits" || headline.count > 0);
|
|
4874
|
+
}, [ctas, headline.count]);
|
|
4875
|
+
const renderCta = (cta, index) => {
|
|
4876
|
+
const label = cta.label ?? CTA_LABELS[cta.kind];
|
|
4877
|
+
const key = `${cta.kind}-${index}`;
|
|
4878
|
+
switch (cta.kind) {
|
|
4879
|
+
case "new-key":
|
|
4880
|
+
return onCreateKey ? /* @__PURE__ */ React15.createElement(Button6, { key, size: "small", variant: "contained", onClick: onCreateKey }, label) : /* @__PURE__ */ React15.createElement(
|
|
4881
|
+
Button6,
|
|
4882
|
+
{
|
|
4883
|
+
key,
|
|
4884
|
+
size: "small",
|
|
4885
|
+
variant: "contained",
|
|
4886
|
+
component: Link2,
|
|
4887
|
+
to: `${moduleHref}?generate=1`
|
|
4888
|
+
},
|
|
4889
|
+
label
|
|
4890
|
+
);
|
|
4891
|
+
case "module":
|
|
4892
|
+
return /* @__PURE__ */ React15.createElement(Button6, { key, size: "small", variant: "outlined", component: Link2, to: moduleHref }, label);
|
|
4893
|
+
case "all-limits":
|
|
4894
|
+
return /* @__PURE__ */ React15.createElement(
|
|
4895
|
+
Button6,
|
|
4896
|
+
{
|
|
4897
|
+
key,
|
|
4898
|
+
size: "small",
|
|
4899
|
+
variant: "text",
|
|
4900
|
+
onClick: toggleExpanded,
|
|
4901
|
+
endIcon: /* @__PURE__ */ React15.createElement(
|
|
4902
|
+
ExpandMore4,
|
|
4903
|
+
{
|
|
4904
|
+
sx: {
|
|
4905
|
+
transform: isExpanded ? "rotate(180deg)" : "none",
|
|
4906
|
+
transition: (theme) => theme.transitions.create("transform")
|
|
4907
|
+
}
|
|
4908
|
+
}
|
|
4909
|
+
)
|
|
4910
|
+
},
|
|
4911
|
+
label
|
|
4912
|
+
);
|
|
4913
|
+
/* istanbul ignore next — exhaustiveness */
|
|
4914
|
+
default:
|
|
4915
|
+
return null;
|
|
4916
|
+
}
|
|
4917
|
+
};
|
|
4918
|
+
const hasFooter = resolvedCtas.length > 0 || !!action;
|
|
4919
|
+
return /* @__PURE__ */ React15.createElement(Paper10, { sx: { p: 2 } }, /* @__PURE__ */ React15.createElement(Box14, { sx: { display: "flex", alignItems: "center", gap: 1, mb: 1.5, minWidth: 0 } }, /* @__PURE__ */ React15.createElement(Typography14, { variant: "h6", lineHeight: 1.2, sx: { minWidth: 0, flex: 1 } }, title), !loading && !error && /* @__PURE__ */ React15.createElement(
|
|
4808
4920
|
StatusPill,
|
|
4809
4921
|
{
|
|
4810
4922
|
label: summaryText,
|
|
4811
4923
|
tone: budgetTone2(headline.closest?.pct ?? 0),
|
|
4812
4924
|
dot: headline.count > 0
|
|
4813
4925
|
}
|
|
4814
|
-
)), loading && /* @__PURE__ */
|
|
4815
|
-
|
|
4926
|
+
)), loading && /* @__PURE__ */ React15.createElement(Box14, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: 110 }, /* @__PURE__ */ React15.createElement(CircularProgress8, { size: 28 })), !loading && error && /* @__PURE__ */ React15.createElement(Alert8, { severity: "error", sx: { mt: 0.5 } }, error), !loading && !error && /* @__PURE__ */ React15.createElement(React15.Fragment, null, /* @__PURE__ */ React15.createElement(Box14, { sx: { display: "flex", alignItems: "flex-start", gap: 1 } }, gauges.map((gauge) => /* @__PURE__ */ React15.createElement(LevelGauge, { key: gauge.kind, gauge, size, keysHref: keysTabHref }))), /* @__PURE__ */ React15.createElement(Collapse4, { in: isExpanded, unmountOnExit: true }, /* @__PURE__ */ React15.createElement(
|
|
4927
|
+
Box14,
|
|
4928
|
+
{
|
|
4929
|
+
sx: {
|
|
4930
|
+
mt: 1.5,
|
|
4931
|
+
// Bound the expanded list so a user with many limits still
|
|
4932
|
+
// keeps the card compact and the toggle within reach.
|
|
4933
|
+
maxHeight: 300,
|
|
4934
|
+
overflowY: "auto"
|
|
4935
|
+
}
|
|
4936
|
+
},
|
|
4937
|
+
/* @__PURE__ */ React15.createElement(LimitListPanel, null, /* @__PURE__ */ React15.createElement(
|
|
4938
|
+
BudgetLimitList,
|
|
4939
|
+
{
|
|
4940
|
+
limits: expandedLimits,
|
|
4941
|
+
hiddenKeyCount: expandedSummary.hiddenBudgetedKeys,
|
|
4942
|
+
keysHref: keysTabHref
|
|
4943
|
+
}
|
|
4944
|
+
))
|
|
4945
|
+
))), hasFooter && /* @__PURE__ */ React15.createElement(
|
|
4946
|
+
Box14,
|
|
4816
4947
|
{
|
|
4817
4948
|
sx: (theme) => ({
|
|
4818
4949
|
mt: 2,
|
|
4819
4950
|
pt: 2,
|
|
4820
|
-
borderTop: `1px solid ${theme.palette.divider}
|
|
4951
|
+
borderTop: `1px solid ${theme.palette.divider}`,
|
|
4952
|
+
display: "flex",
|
|
4953
|
+
alignItems: "center",
|
|
4954
|
+
gap: 1,
|
|
4955
|
+
flexWrap: "wrap"
|
|
4821
4956
|
})
|
|
4822
4957
|
},
|
|
4958
|
+
resolvedCtas.map(renderCta),
|
|
4823
4959
|
action
|
|
4824
4960
|
));
|
|
4825
4961
|
};
|
|
4826
4962
|
|
|
4827
4963
|
// src/index.ts
|
|
4828
|
-
|
|
4964
|
+
init_KeyFormDialog();
|
|
4965
|
+
|
|
4966
|
+
// src/components/GenerateKeyDialog.tsx
|
|
4967
|
+
init_KeyFormDialog();
|
|
4968
|
+
import React16 from "react";
|
|
4969
|
+
var GenerateKeyDialog = (props) => /* @__PURE__ */ React16.createElement(
|
|
4970
|
+
KeyFormDialog,
|
|
4971
|
+
{
|
|
4972
|
+
open: props.open,
|
|
4973
|
+
onClose: props.onClose,
|
|
4974
|
+
mode: "create",
|
|
4975
|
+
keys: props.keys,
|
|
4976
|
+
models: props.models,
|
|
4977
|
+
teams: props.teams,
|
|
4978
|
+
username: props.username,
|
|
4979
|
+
keyGenerationSettings: props.keyGenerationSettings,
|
|
4980
|
+
onCreateKey: props.onGenerateKey,
|
|
4981
|
+
onUpdateKey: async () => {
|
|
4982
|
+
},
|
|
4983
|
+
onGetConfig: props.onGetConfig
|
|
4984
|
+
}
|
|
4985
|
+
);
|
|
4986
|
+
|
|
4987
|
+
// src/index.ts
|
|
4829
4988
|
init_ManageTeamDialog();
|
|
4830
4989
|
init_api();
|
|
4831
4990
|
init_permissions();
|
|
4832
4991
|
export {
|
|
4833
4992
|
DashboardHeader,
|
|
4834
4993
|
GenerateKeyDialog,
|
|
4994
|
+
KeyFormDialog,
|
|
4835
4995
|
KeysTable,
|
|
4836
4996
|
LiteLLMBudgetGauges,
|
|
4837
4997
|
LiteLLMBudgetWidget,
|