@acarmisc/backstage-plugin-litellm 0.12.3 → 0.12.4
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/dist/index.cjs.js +479 -267
- package/dist/index.cjs.js.map +3 -3
- package/dist/index.esm.js +358 -253
- package/dist/index.esm.js.map +2 -2
- package/package.json +2 -1
package/dist/index.esm.js
CHANGED
|
@@ -154,7 +154,13 @@ var init_api = __esm({
|
|
|
154
154
|
|
|
155
155
|
// src/components/DashboardHeader.tsx
|
|
156
156
|
import React, { useMemo } from "react";
|
|
157
|
-
import
|
|
157
|
+
import Box from "@mui/material/Box";
|
|
158
|
+
import Typography from "@mui/material/Typography";
|
|
159
|
+
import LinearProgress from "@mui/material/LinearProgress";
|
|
160
|
+
import Paper from "@mui/material/Paper";
|
|
161
|
+
import Chip from "@mui/material/Chip";
|
|
162
|
+
import Button from "@mui/material/Button";
|
|
163
|
+
import Stack from "@mui/material/Stack";
|
|
158
164
|
import { Add, Key, Warning, Schedule } from "@mui/icons-material";
|
|
159
165
|
var DashboardHeader;
|
|
160
166
|
var init_DashboardHeader = __esm({
|
|
@@ -218,42 +224,60 @@ var init_DashboardHeader = __esm({
|
|
|
218
224
|
|
|
219
225
|
// src/components/KeysTable.tsx
|
|
220
226
|
import React2, { useState, useMemo as useMemo2 } from "react";
|
|
221
|
-
import
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
InputAdornment
|
|
243
|
-
} from "@mui/material";
|
|
227
|
+
import Paper2 from "@mui/material/Paper";
|
|
228
|
+
import Table from "@mui/material/Table";
|
|
229
|
+
import TableBody from "@mui/material/TableBody";
|
|
230
|
+
import TableCell from "@mui/material/TableCell";
|
|
231
|
+
import TableContainer from "@mui/material/TableContainer";
|
|
232
|
+
import TableHead from "@mui/material/TableHead";
|
|
233
|
+
import TableRow from "@mui/material/TableRow";
|
|
234
|
+
import Button2 from "@mui/material/Button";
|
|
235
|
+
import IconButton from "@mui/material/IconButton";
|
|
236
|
+
import Box2 from "@mui/material/Box";
|
|
237
|
+
import Typography2 from "@mui/material/Typography";
|
|
238
|
+
import Dialog from "@mui/material/Dialog";
|
|
239
|
+
import DialogTitle from "@mui/material/DialogTitle";
|
|
240
|
+
import DialogContent from "@mui/material/DialogContent";
|
|
241
|
+
import DialogActions from "@mui/material/DialogActions";
|
|
242
|
+
import TextField from "@mui/material/TextField";
|
|
243
|
+
import Chip2 from "@mui/material/Chip";
|
|
244
|
+
import CircularProgress from "@mui/material/CircularProgress";
|
|
245
|
+
import Autocomplete from "@mui/material/Autocomplete";
|
|
246
|
+
import LinearProgress2 from "@mui/material/LinearProgress";
|
|
247
|
+
import InputAdornment from "@mui/material/InputAdornment";
|
|
244
248
|
import { ContentCopy, Delete, Add as Add2, Edit, Autorenew, Search, Warning as Warning2, Lock, LockOpen } from "@mui/icons-material";
|
|
249
|
+
function expiryChipLabel(status, expiresAt) {
|
|
250
|
+
if (status === "expired") return "Expired";
|
|
251
|
+
if (status === "soon") return `${Math.ceil((new Date(expiresAt).getTime() - Date.now()) / 864e5)}d left`;
|
|
252
|
+
return formatDate(expiresAt);
|
|
253
|
+
}
|
|
254
|
+
function expiryChipColor(status) {
|
|
255
|
+
if (status === "expired") return "error";
|
|
256
|
+
if (status === "soon") return "warning";
|
|
257
|
+
return "default";
|
|
258
|
+
}
|
|
245
259
|
function ExpiryChip({ expiresAt }) {
|
|
246
260
|
const status = expiryStatus(expiresAt);
|
|
247
261
|
if (!status) return /* @__PURE__ */ React2.createElement(Typography2, { variant: "body2", color: "text.secondary" }, "-");
|
|
248
|
-
const label = status
|
|
249
|
-
const color = status
|
|
262
|
+
const label = expiryChipLabel(status, expiresAt);
|
|
263
|
+
const color = expiryChipColor(status);
|
|
250
264
|
const icon = status === "expired" || status === "soon" ? /* @__PURE__ */ React2.createElement(Warning2, { fontSize: "small" }) : void 0;
|
|
251
265
|
return /* @__PURE__ */ React2.createElement(Chip2, { label, color, size: "small", icon });
|
|
252
266
|
}
|
|
267
|
+
function budgetColor(pct) {
|
|
268
|
+
if (pct >= 100) return "error";
|
|
269
|
+
if (pct >= 80) return "warning";
|
|
270
|
+
return "primary";
|
|
271
|
+
}
|
|
272
|
+
function keyBlockIcon(isBlocking, blocked) {
|
|
273
|
+
if (isBlocking) return /* @__PURE__ */ React2.createElement(CircularProgress, { size: 18 });
|
|
274
|
+
if (blocked) return /* @__PURE__ */ React2.createElement(LockOpen, { fontSize: "small" });
|
|
275
|
+
return /* @__PURE__ */ React2.createElement(Lock, { fontSize: "small" });
|
|
276
|
+
}
|
|
253
277
|
function BudgetCell({ spend, maxBudget }) {
|
|
254
278
|
if (!maxBudget) return /* @__PURE__ */ React2.createElement(Typography2, { variant: "body2", color: "text.secondary" }, "-");
|
|
255
279
|
const pct = Math.min(100, spend / maxBudget * 100);
|
|
256
|
-
const color = pct
|
|
280
|
+
const color = budgetColor(pct);
|
|
257
281
|
return /* @__PURE__ */ React2.createElement(Box2, { minWidth: 100 }, /* @__PURE__ */ React2.createElement(Box2, { display: "flex", justifyContent: "space-between" }, /* @__PURE__ */ React2.createElement(Typography2, { variant: "caption" }, "$", spend.toFixed(2)), /* @__PURE__ */ React2.createElement(Typography2, { variant: "caption", color: "text.secondary" }, "$", maxBudget)), /* @__PURE__ */ React2.createElement(LinearProgress2, { variant: "determinate", value: pct, color, sx: { height: 5, borderRadius: 1, mt: 0.25 } }));
|
|
258
282
|
}
|
|
259
283
|
function fmtCost(perToken) {
|
|
@@ -414,6 +438,62 @@ var init_KeysTable = __esm({
|
|
|
414
438
|
const ctx = formatContextWindow(m.max_input_tokens, m.max_output_tokens);
|
|
415
439
|
return /* @__PURE__ */ React2.createElement(Box2, null, /* @__PURE__ */ React2.createElement("span", null, m.model_name), m.supports_function_calling && " \u{1F527}", m.supports_vision && " \u{1F441}\uFE0F", ctx && /* @__PURE__ */ React2.createElement(Typography2, { variant: "caption", color: "text.secondary", sx: { ml: 1 } }, ctx), (inCost || outCost) && /* @__PURE__ */ React2.createElement(Typography2, { variant: "caption", color: "text.secondary", sx: { ml: 1 } }, inCost, " in \xB7 ", outCost, " out"));
|
|
416
440
|
};
|
|
441
|
+
const renderTableBody = () => {
|
|
442
|
+
if (loading) {
|
|
443
|
+
return /* @__PURE__ */ React2.createElement(TableRow, null, /* @__PURE__ */ React2.createElement(TableCell, { colSpan: 8, align: "center" }, /* @__PURE__ */ React2.createElement(CircularProgress, { size: 24 })));
|
|
444
|
+
}
|
|
445
|
+
if (filteredKeys.length === 0) {
|
|
446
|
+
return /* @__PURE__ */ React2.createElement(TableRow, null, /* @__PURE__ */ React2.createElement(TableCell, { colSpan: 8, align: "center", sx: { py: 4 } }, filterText ? /* @__PURE__ */ React2.createElement(Typography2, { color: "text.secondary" }, "No keys match filter") : /* @__PURE__ */ React2.createElement(Box2, null, /* @__PURE__ */ React2.createElement(Typography2, { color: "text.secondary", gutterBottom: true }, "No keys yet \u2014 generate your first key to start calling models."), /* @__PURE__ */ React2.createElement(
|
|
447
|
+
Button2,
|
|
448
|
+
{
|
|
449
|
+
variant: "contained",
|
|
450
|
+
color: "primary",
|
|
451
|
+
size: "small",
|
|
452
|
+
startIcon: /* @__PURE__ */ React2.createElement(Add2, null),
|
|
453
|
+
onClick: onGenerateKeyClick
|
|
454
|
+
},
|
|
455
|
+
"Generate Your First Key"
|
|
456
|
+
))));
|
|
457
|
+
}
|
|
458
|
+
return filteredKeys.map((key) => {
|
|
459
|
+
const keyId = key.token ?? key.key;
|
|
460
|
+
const isBlocking = blockingKeyId === keyId;
|
|
461
|
+
return /* @__PURE__ */ React2.createElement(TableRow, { key: keyId, sx: key.blocked ? { bgcolor: "action.disabledBackground" } : void 0 }, /* @__PURE__ */ React2.createElement(TableCell, null, /* @__PURE__ */ React2.createElement(Box2, { display: "flex", alignItems: "center", gap: 0.5 }, key.key_alias || "-", key.blocked && /* @__PURE__ */ React2.createElement(Chip2, { label: "Blocked", color: "error", size: "small" }))), /* @__PURE__ */ React2.createElement(TableCell, null, /* @__PURE__ */ React2.createElement(Box2, { display: "flex", alignItems: "center", gap: 0.5 }, /* @__PURE__ */ React2.createElement(
|
|
462
|
+
Typography2,
|
|
463
|
+
{
|
|
464
|
+
variant: "body2",
|
|
465
|
+
component: "code",
|
|
466
|
+
color: "text.secondary",
|
|
467
|
+
title: keyId,
|
|
468
|
+
sx: {
|
|
469
|
+
fontFamily: "monospace",
|
|
470
|
+
backgroundColor: "background.default",
|
|
471
|
+
px: 1,
|
|
472
|
+
py: 0.5,
|
|
473
|
+
borderRadius: 1
|
|
474
|
+
}
|
|
475
|
+
},
|
|
476
|
+
shortKeyId(keyId)
|
|
477
|
+
), /* @__PURE__ */ React2.createElement(IconButton, { size: "small", onClick: () => copyToClipboard(keyId), title: "Copy Key ID" }, /* @__PURE__ */ React2.createElement(ContentCopy, { fontSize: "small" })))), /* @__PURE__ */ React2.createElement(TableCell, null, formatDate(key.created_at)), /* @__PURE__ */ React2.createElement(TableCell, null, /* @__PURE__ */ React2.createElement(ExpiryChip, { expiresAt: key.expires_at })), /* @__PURE__ */ React2.createElement(TableCell, null, /* @__PURE__ */ React2.createElement(BudgetCell, { spend: key.spend ?? 0, maxBudget: key.max_budget })), /* @__PURE__ */ React2.createElement(TableCell, null, /* @__PURE__ */ React2.createElement(Typography2, { variant: "body2" }, key.tpm_limit ?? "-", " / ", key.rpm_limit ?? "-")), /* @__PURE__ */ React2.createElement(TableCell, null, /* @__PURE__ */ React2.createElement(Box2, { display: "flex", gap: 0.5, flexWrap: "wrap" }, key.models?.slice(0, 2).map((model) => /* @__PURE__ */ React2.createElement(Chip2, { key: model, label: model, size: "small" })), (key.models?.length || 0) > 2 && /* @__PURE__ */ React2.createElement(Chip2, { label: `+${(key.models?.length || 0) - 2}`, size: "small", variant: "outlined" }))), /* @__PURE__ */ React2.createElement(TableCell, { align: "right" }, /* @__PURE__ */ React2.createElement(IconButton, { onClick: () => handleOpenEdit(key), title: "Edit key" }, /* @__PURE__ */ React2.createElement(Edit, { fontSize: "small" })), /* @__PURE__ */ React2.createElement(
|
|
478
|
+
IconButton,
|
|
479
|
+
{
|
|
480
|
+
onClick: () => handleToggleBlock(key),
|
|
481
|
+
disabled: isBlocking,
|
|
482
|
+
color: key.blocked ? "warning" : "default",
|
|
483
|
+
title: key.blocked ? "Unblock key" : "Block key \u2014 suspends without revoking"
|
|
484
|
+
},
|
|
485
|
+
keyBlockIcon(isBlocking, key.blocked)
|
|
486
|
+
), /* @__PURE__ */ React2.createElement(
|
|
487
|
+
IconButton,
|
|
488
|
+
{
|
|
489
|
+
color: "error",
|
|
490
|
+
onClick: () => setDeleteConfirmId(keyId),
|
|
491
|
+
title: "Revoke key"
|
|
492
|
+
},
|
|
493
|
+
/* @__PURE__ */ React2.createElement(Delete, null)
|
|
494
|
+
)));
|
|
495
|
+
});
|
|
496
|
+
};
|
|
417
497
|
return /* @__PURE__ */ React2.createElement(React2.Fragment, null, /* @__PURE__ */ React2.createElement(Paper2, { sx: { mb: 2 } }, /* @__PURE__ */ React2.createElement(Box2, { display: "flex", justifyContent: "space-between", alignItems: "center", p: 2, gap: 2 }, /* @__PURE__ */ React2.createElement(Typography2, { variant: "h6" }, "Virtual Keys"), /* @__PURE__ */ React2.createElement(Box2, { display: "flex", gap: 1, alignItems: "center", flex: 1, justifyContent: "flex-end" }, /* @__PURE__ */ React2.createElement(
|
|
418
498
|
TextField,
|
|
419
499
|
{
|
|
@@ -446,54 +526,7 @@ var init_KeysTable = __esm({
|
|
|
446
526
|
onClick: onGenerateKeyClick
|
|
447
527
|
},
|
|
448
528
|
"Generate New Key"
|
|
449
|
-
))), /* @__PURE__ */ React2.createElement(TableContainer, null, /* @__PURE__ */ React2.createElement(Table, null, /* @__PURE__ */ React2.createElement(TableHead, null, /* @__PURE__ */ React2.createElement(TableRow, null, /* @__PURE__ */ React2.createElement(TableCell, null, "Alias"), /* @__PURE__ */ React2.createElement(TableCell, null, "Key ID"), /* @__PURE__ */ React2.createElement(TableCell, null, "Created"), /* @__PURE__ */ React2.createElement(TableCell, null, "Expires"), /* @__PURE__ */ React2.createElement(TableCell, null, "Budget"), /* @__PURE__ */ React2.createElement(TableCell, null, "TPM / RPM"), /* @__PURE__ */ React2.createElement(TableCell, null, "Models"), /* @__PURE__ */ React2.createElement(TableCell, { align: "right" }, "Actions"))), /* @__PURE__ */ React2.createElement(TableBody, null,
|
|
450
|
-
Button2,
|
|
451
|
-
{
|
|
452
|
-
variant: "contained",
|
|
453
|
-
color: "primary",
|
|
454
|
-
size: "small",
|
|
455
|
-
startIcon: /* @__PURE__ */ React2.createElement(Add2, null),
|
|
456
|
-
onClick: onGenerateKeyClick
|
|
457
|
-
},
|
|
458
|
-
"Generate Your First Key"
|
|
459
|
-
)))) : filteredKeys.map((key) => {
|
|
460
|
-
const keyId = key.token ?? key.key;
|
|
461
|
-
const isBlocking = blockingKeyId === keyId;
|
|
462
|
-
return /* @__PURE__ */ React2.createElement(TableRow, { key: keyId, sx: key.blocked ? { bgcolor: "action.disabledBackground" } : void 0 }, /* @__PURE__ */ React2.createElement(TableCell, null, /* @__PURE__ */ React2.createElement(Box2, { display: "flex", alignItems: "center", gap: 0.5 }, key.key_alias || "-", key.blocked && /* @__PURE__ */ React2.createElement(Chip2, { label: "Blocked", color: "error", size: "small" }))), /* @__PURE__ */ React2.createElement(TableCell, null, /* @__PURE__ */ React2.createElement(Box2, { display: "flex", alignItems: "center", gap: 0.5 }, /* @__PURE__ */ React2.createElement(
|
|
463
|
-
Typography2,
|
|
464
|
-
{
|
|
465
|
-
variant: "body2",
|
|
466
|
-
component: "code",
|
|
467
|
-
color: "text.secondary",
|
|
468
|
-
title: keyId,
|
|
469
|
-
sx: {
|
|
470
|
-
fontFamily: "monospace",
|
|
471
|
-
backgroundColor: "background.default",
|
|
472
|
-
px: 1,
|
|
473
|
-
py: 0.5,
|
|
474
|
-
borderRadius: 1
|
|
475
|
-
}
|
|
476
|
-
},
|
|
477
|
-
shortKeyId(keyId)
|
|
478
|
-
), /* @__PURE__ */ React2.createElement(IconButton, { size: "small", onClick: () => copyToClipboard(keyId), title: "Copy Key ID" }, /* @__PURE__ */ React2.createElement(ContentCopy, { fontSize: "small" })))), /* @__PURE__ */ React2.createElement(TableCell, null, formatDate(key.created_at)), /* @__PURE__ */ React2.createElement(TableCell, null, /* @__PURE__ */ React2.createElement(ExpiryChip, { expiresAt: key.expires_at })), /* @__PURE__ */ React2.createElement(TableCell, null, /* @__PURE__ */ React2.createElement(BudgetCell, { spend: key.spend ?? 0, maxBudget: key.max_budget })), /* @__PURE__ */ React2.createElement(TableCell, null, /* @__PURE__ */ React2.createElement(Typography2, { variant: "body2" }, key.tpm_limit ?? "-", " / ", key.rpm_limit ?? "-")), /* @__PURE__ */ React2.createElement(TableCell, null, /* @__PURE__ */ React2.createElement(Box2, { display: "flex", gap: 0.5, flexWrap: "wrap" }, key.models?.slice(0, 2).map((model) => /* @__PURE__ */ React2.createElement(Chip2, { key: model, label: model, size: "small" })), (key.models?.length || 0) > 2 && /* @__PURE__ */ React2.createElement(Chip2, { label: `+${(key.models?.length || 0) - 2}`, size: "small", variant: "outlined" }))), /* @__PURE__ */ React2.createElement(TableCell, { align: "right" }, /* @__PURE__ */ React2.createElement(IconButton, { onClick: () => handleOpenEdit(key), title: "Edit key" }, /* @__PURE__ */ React2.createElement(Edit, { fontSize: "small" })), /* @__PURE__ */ React2.createElement(
|
|
479
|
-
IconButton,
|
|
480
|
-
{
|
|
481
|
-
onClick: () => handleToggleBlock(key),
|
|
482
|
-
disabled: isBlocking,
|
|
483
|
-
color: key.blocked ? "warning" : "default",
|
|
484
|
-
title: key.blocked ? "Unblock key" : "Block key \u2014 suspends without revoking"
|
|
485
|
-
},
|
|
486
|
-
isBlocking ? /* @__PURE__ */ React2.createElement(CircularProgress, { size: 18 }) : key.blocked ? /* @__PURE__ */ React2.createElement(LockOpen, { fontSize: "small" }) : /* @__PURE__ */ React2.createElement(Lock, { fontSize: "small" })
|
|
487
|
-
), /* @__PURE__ */ React2.createElement(
|
|
488
|
-
IconButton,
|
|
489
|
-
{
|
|
490
|
-
color: "error",
|
|
491
|
-
onClick: () => setDeleteConfirmId(keyId),
|
|
492
|
-
title: "Revoke key"
|
|
493
|
-
},
|
|
494
|
-
/* @__PURE__ */ React2.createElement(Delete, null)
|
|
495
|
-
)));
|
|
496
|
-
}))))), /* @__PURE__ */ React2.createElement(Dialog, { open: !!editingKey, onClose: handleCloseEdit, maxWidth: "sm", fullWidth: true }, /* @__PURE__ */ React2.createElement(DialogTitle, null, "Edit Key"), /* @__PURE__ */ React2.createElement(DialogContent, null, editingKey && /* @__PURE__ */ React2.createElement(Box2, { display: "flex", flexDirection: "column", gap: 2, mt: 1 }, /* @__PURE__ */ React2.createElement(Typography2, { variant: "body2", color: "text.secondary" }, /* @__PURE__ */ React2.createElement("code", { style: { fontFamily: "monospace", color: "inherit" } }, maskKey(editingKey.key))), /* @__PURE__ */ React2.createElement(
|
|
529
|
+
))), /* @__PURE__ */ React2.createElement(TableContainer, null, /* @__PURE__ */ React2.createElement(Table, null, /* @__PURE__ */ React2.createElement(TableHead, null, /* @__PURE__ */ React2.createElement(TableRow, null, /* @__PURE__ */ React2.createElement(TableCell, null, "Alias"), /* @__PURE__ */ React2.createElement(TableCell, null, "Key ID"), /* @__PURE__ */ React2.createElement(TableCell, null, "Created"), /* @__PURE__ */ React2.createElement(TableCell, null, "Expires"), /* @__PURE__ */ React2.createElement(TableCell, null, "Budget"), /* @__PURE__ */ React2.createElement(TableCell, null, "TPM / RPM"), /* @__PURE__ */ React2.createElement(TableCell, null, "Models"), /* @__PURE__ */ React2.createElement(TableCell, { align: "right" }, "Actions"))), /* @__PURE__ */ React2.createElement(TableBody, null, renderTableBody())))), /* @__PURE__ */ React2.createElement(Dialog, { open: !!editingKey, onClose: handleCloseEdit, maxWidth: "sm", fullWidth: true }, /* @__PURE__ */ React2.createElement(DialogTitle, null, "Edit Key"), /* @__PURE__ */ React2.createElement(DialogContent, null, editingKey && /* @__PURE__ */ React2.createElement(Box2, { display: "flex", flexDirection: "column", gap: 2, mt: 1 }, /* @__PURE__ */ React2.createElement(Typography2, { variant: "body2", color: "text.secondary" }, /* @__PURE__ */ React2.createElement("code", { style: { fontFamily: "monospace", color: "inherit" } }, maskKey(editingKey.key))), /* @__PURE__ */ React2.createElement(
|
|
497
530
|
TextField,
|
|
498
531
|
{
|
|
499
532
|
label: "Alias",
|
|
@@ -600,25 +633,23 @@ var init_format = __esm({
|
|
|
600
633
|
|
|
601
634
|
// src/components/GenerateKeyDialog.tsx
|
|
602
635
|
import React3, { useState as useState2, useEffect, useMemo as useMemo3 } from "react";
|
|
603
|
-
import
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
Tab
|
|
621
|
-
} from "@mui/material";
|
|
636
|
+
import Box3 from "@mui/material/Box";
|
|
637
|
+
import Typography3 from "@mui/material/Typography";
|
|
638
|
+
import Dialog2 from "@mui/material/Dialog";
|
|
639
|
+
import DialogTitle2 from "@mui/material/DialogTitle";
|
|
640
|
+
import DialogContent2 from "@mui/material/DialogContent";
|
|
641
|
+
import DialogActions2 from "@mui/material/DialogActions";
|
|
642
|
+
import TextField2 from "@mui/material/TextField";
|
|
643
|
+
import MenuItem from "@mui/material/MenuItem";
|
|
644
|
+
import Button3 from "@mui/material/Button";
|
|
645
|
+
import IconButton2 from "@mui/material/IconButton";
|
|
646
|
+
import CircularProgress2 from "@mui/material/CircularProgress";
|
|
647
|
+
import Autocomplete2 from "@mui/material/Autocomplete";
|
|
648
|
+
import Checkbox from "@mui/material/Checkbox";
|
|
649
|
+
import FormControlLabel from "@mui/material/FormControlLabel";
|
|
650
|
+
import Alert from "@mui/material/Alert";
|
|
651
|
+
import Tabs from "@mui/material/Tabs";
|
|
652
|
+
import Tab from "@mui/material/Tab";
|
|
622
653
|
import { ContentCopy as ContentCopy2, Code } from "@mui/icons-material";
|
|
623
654
|
function trimSlash(url) {
|
|
624
655
|
return url.replace(/\/+$/, "");
|
|
@@ -647,6 +678,21 @@ response = client.chat.completions.create(
|
|
|
647
678
|
print(response.choices[0].message.content)`
|
|
648
679
|
};
|
|
649
680
|
}
|
|
681
|
+
function aliasHelperText(aliasError, aliasDuplicate) {
|
|
682
|
+
if (aliasError) return "Alias is required";
|
|
683
|
+
if (aliasDuplicate) return "This alias is already used by one of your keys \u2014 LiteLLM requires aliases to be unique across all keys";
|
|
684
|
+
return void 0;
|
|
685
|
+
}
|
|
686
|
+
function teamHelperText(teamError, teamRequired) {
|
|
687
|
+
if (teamError) return "Team is required";
|
|
688
|
+
if (teamRequired) return "Bind this key to a team for scoped access";
|
|
689
|
+
return "Optional: bind this key to a specific team for scoped access";
|
|
690
|
+
}
|
|
691
|
+
function budgetHelperText(budgetInvalid, budgetEstimate) {
|
|
692
|
+
if (budgetInvalid) return 'Enter a positive budget or tick "Unlimited"';
|
|
693
|
+
if (budgetEstimate !== null) return `\u2248 ${fmtInt(budgetEstimate)} tokens at the selected model's rate`;
|
|
694
|
+
return void 0;
|
|
695
|
+
}
|
|
650
696
|
function fmtCost2(perToken) {
|
|
651
697
|
if (!perToken) return null;
|
|
652
698
|
const per1k = perToken * 1e3;
|
|
@@ -667,7 +713,7 @@ function formatContextWindow2(maxInput, maxOutput) {
|
|
|
667
713
|
if (outPart) return `ctx ${outPart} out`;
|
|
668
714
|
return null;
|
|
669
715
|
}
|
|
670
|
-
var generateDefaultAlias, emptyForm,
|
|
716
|
+
var generateDefaultAlias, emptyForm, SnippetTabs, GenerateKeyDialog;
|
|
671
717
|
var init_GenerateKeyDialog = __esm({
|
|
672
718
|
"src/components/GenerateKeyDialog.tsx"() {
|
|
673
719
|
"use strict";
|
|
@@ -687,6 +733,44 @@ var init_GenerateKeyDialog = __esm({
|
|
|
687
733
|
team_id: void 0,
|
|
688
734
|
key_type: "llm_api"
|
|
689
735
|
});
|
|
736
|
+
SnippetTabs = ({ snippets, model, onCopy }) => {
|
|
737
|
+
const [tab, setTab] = useState2("curl");
|
|
738
|
+
const code = tab === "curl" ? snippets.curl : snippets.openai;
|
|
739
|
+
return /* @__PURE__ */ React3.createElement(Box3, null, /* @__PURE__ */ React3.createElement(Tabs, { value: tab, onChange: (_, v) => setTab(v), sx: { mb: 1 } }, /* @__PURE__ */ React3.createElement(Tab, { label: "curl", value: "curl" }), /* @__PURE__ */ React3.createElement(Tab, { label: "OpenAI SDK", value: "openai" })), /* @__PURE__ */ React3.createElement(
|
|
740
|
+
Box3,
|
|
741
|
+
{
|
|
742
|
+
position: "relative",
|
|
743
|
+
p: 1.5,
|
|
744
|
+
sx: { backgroundColor: "action.hover", border: "1px solid", borderColor: "divider", borderRadius: 1 }
|
|
745
|
+
},
|
|
746
|
+
/* @__PURE__ */ React3.createElement(
|
|
747
|
+
IconButton2,
|
|
748
|
+
{
|
|
749
|
+
size: "small",
|
|
750
|
+
onClick: () => onCopy(code),
|
|
751
|
+
title: "Copy snippet",
|
|
752
|
+
sx: { position: "absolute", top: 4, right: 4 }
|
|
753
|
+
},
|
|
754
|
+
/* @__PURE__ */ React3.createElement(ContentCopy2, { fontSize: "small" })
|
|
755
|
+
),
|
|
756
|
+
/* @__PURE__ */ React3.createElement(
|
|
757
|
+
Typography3,
|
|
758
|
+
{
|
|
759
|
+
component: "pre",
|
|
760
|
+
sx: {
|
|
761
|
+
fontFamily: "monospace",
|
|
762
|
+
fontSize: 12,
|
|
763
|
+
whiteSpace: "pre-wrap",
|
|
764
|
+
wordBreak: "break-all",
|
|
765
|
+
mb: 0,
|
|
766
|
+
pr: 4
|
|
767
|
+
}
|
|
768
|
+
},
|
|
769
|
+
code
|
|
770
|
+
),
|
|
771
|
+
model && /* @__PURE__ */ React3.createElement(Typography3, { variant: "caption", color: "text.secondary", display: "block", mt: 1 }, "Using model \u201C", model, "\u201D \u2014 swap it for any model you have access to.")
|
|
772
|
+
));
|
|
773
|
+
};
|
|
690
774
|
GenerateKeyDialog = ({
|
|
691
775
|
open,
|
|
692
776
|
onClose,
|
|
@@ -782,6 +866,38 @@ var init_GenerateKeyDialog = __esm({
|
|
|
782
866
|
const ctx = formatContextWindow2(m.max_input_tokens, m.max_output_tokens);
|
|
783
867
|
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"));
|
|
784
868
|
};
|
|
869
|
+
const renderTeamField = () => {
|
|
870
|
+
if (teams.length > 0) {
|
|
871
|
+
return /* @__PURE__ */ React3.createElement(
|
|
872
|
+
Autocomplete2,
|
|
873
|
+
{
|
|
874
|
+
options: teams,
|
|
875
|
+
getOptionLabel: (t) => t.team_alias || t.team_id,
|
|
876
|
+
value: selectedTeam,
|
|
877
|
+
onChange: (_e, team) => {
|
|
878
|
+
const teamModels = team?.models;
|
|
879
|
+
const restrictedModels = teamModels && teamModels.length > 0 ? (formData.models || []).filter((m) => teamModels.includes(m)) : formData.models;
|
|
880
|
+
setFormData({ ...formData, team_id: team?.team_id, models: restrictedModels });
|
|
881
|
+
},
|
|
882
|
+
renderInput: (params) => /* @__PURE__ */ React3.createElement(
|
|
883
|
+
TextField2,
|
|
884
|
+
{
|
|
885
|
+
...params,
|
|
886
|
+
label: "Team",
|
|
887
|
+
error: teamError,
|
|
888
|
+
helperText: teamHelperText(teamError, teamRequired),
|
|
889
|
+
required: teamRequired,
|
|
890
|
+
fullWidth: true
|
|
891
|
+
}
|
|
892
|
+
)
|
|
893
|
+
}
|
|
894
|
+
);
|
|
895
|
+
}
|
|
896
|
+
if (teamRequired) {
|
|
897
|
+
return /* @__PURE__ */ React3.createElement(Typography3, { variant: "body2", color: "error" }, "Team selection is required, but you don't belong to any team yet \u2014 contact your administrator.");
|
|
898
|
+
}
|
|
899
|
+
return null;
|
|
900
|
+
};
|
|
785
901
|
return /* @__PURE__ */ React3.createElement(Dialog2, { open, onClose: handleClose, maxWidth: "sm", fullWidth: true }, /* @__PURE__ */ React3.createElement(DialogTitle2, null, newKeyValue ? "Key Generated" : "Generate New Key"), /* @__PURE__ */ React3.createElement(DialogContent2, null, newKeyValue ? /* @__PURE__ */ React3.createElement(Box3, null, /* @__PURE__ */ React3.createElement(Typography3, { variant: "body2", color: "text.secondary", gutterBottom: true }, "Copy this key now. You won't be able to see it again."), /* @__PURE__ */ React3.createElement(
|
|
786
902
|
Box3,
|
|
787
903
|
{
|
|
@@ -818,7 +934,7 @@ var init_GenerateKeyDialog = __esm({
|
|
|
818
934
|
},
|
|
819
935
|
error: aliasError,
|
|
820
936
|
color: aliasDuplicate ? "warning" : void 0,
|
|
821
|
-
helperText: aliasError
|
|
937
|
+
helperText: aliasHelperText(aliasError, aliasDuplicate),
|
|
822
938
|
required: true,
|
|
823
939
|
fullWidth: true
|
|
824
940
|
}
|
|
@@ -836,30 +952,7 @@ var init_GenerateKeyDialog = __esm({
|
|
|
836
952
|
/* @__PURE__ */ React3.createElement(MenuItem, { value: "30d" }, "30 Days"),
|
|
837
953
|
/* @__PURE__ */ React3.createElement(MenuItem, { value: "90d" }, "90 Days"),
|
|
838
954
|
/* @__PURE__ */ React3.createElement(MenuItem, { value: "1y" }, "1 Year")
|
|
839
|
-
),
|
|
840
|
-
Autocomplete2,
|
|
841
|
-
{
|
|
842
|
-
options: teams,
|
|
843
|
-
getOptionLabel: (t) => t.team_alias || t.team_id,
|
|
844
|
-
value: selectedTeam,
|
|
845
|
-
onChange: (_e, team) => {
|
|
846
|
-
const teamModels = team?.models;
|
|
847
|
-
const restrictedModels = teamModels && teamModels.length > 0 ? (formData.models || []).filter((m) => teamModels.includes(m)) : formData.models;
|
|
848
|
-
setFormData({ ...formData, team_id: team?.team_id, models: restrictedModels });
|
|
849
|
-
},
|
|
850
|
-
renderInput: (params) => /* @__PURE__ */ React3.createElement(
|
|
851
|
-
TextField2,
|
|
852
|
-
{
|
|
853
|
-
...params,
|
|
854
|
-
label: "Team",
|
|
855
|
-
error: teamError,
|
|
856
|
-
helperText: teamError ? "Team is required" : teamRequired ? "Bind this key to a team for scoped access" : "Optional: bind this key to a specific team for scoped access",
|
|
857
|
-
required: teamRequired,
|
|
858
|
-
fullWidth: true
|
|
859
|
-
}
|
|
860
|
-
)
|
|
861
|
-
}
|
|
862
|
-
) : teamRequired ? /* @__PURE__ */ React3.createElement(Typography3, { variant: "body2", color: "error" }, "Team selection is required, but you don't belong to any team yet \u2014 contact your administrator.") : null, availableModels.length > 0 && /* @__PURE__ */ React3.createElement(
|
|
955
|
+
), renderTeamField(), availableModels.length > 0 && /* @__PURE__ */ React3.createElement(
|
|
863
956
|
Autocomplete2,
|
|
864
957
|
{
|
|
865
958
|
multiple: true,
|
|
@@ -906,7 +999,7 @@ var init_GenerateKeyDialog = __esm({
|
|
|
906
999
|
value: unlimitedBudget ? "" : formData.max_budget ?? "",
|
|
907
1000
|
onChange: (e) => setFormData({ ...formData, max_budget: e.target.value ? Number(e.target.value) : void 0 }),
|
|
908
1001
|
error: budgetInvalid,
|
|
909
|
-
helperText: budgetInvalid
|
|
1002
|
+
helperText: budgetHelperText(budgetInvalid, budgetEstimate),
|
|
910
1003
|
disabled: unlimitedBudget,
|
|
911
1004
|
required: true,
|
|
912
1005
|
fullWidth: true
|
|
@@ -942,71 +1035,31 @@ var init_GenerateKeyDialog = __esm({
|
|
|
942
1035
|
submitting ? /* @__PURE__ */ React3.createElement(CircularProgress2, { size: 24 }) : "Generate"
|
|
943
1036
|
))));
|
|
944
1037
|
};
|
|
945
|
-
SnippetTabs = ({ snippets, model, onCopy }) => {
|
|
946
|
-
const [tab, setTab] = useState2("curl");
|
|
947
|
-
const code = tab === "curl" ? snippets.curl : snippets.openai;
|
|
948
|
-
return /* @__PURE__ */ React3.createElement(Box3, null, /* @__PURE__ */ React3.createElement(Tabs, { value: tab, onChange: (_, v) => setTab(v), sx: { mb: 1 } }, /* @__PURE__ */ React3.createElement(Tab, { label: "curl", value: "curl" }), /* @__PURE__ */ React3.createElement(Tab, { label: "OpenAI SDK", value: "openai" })), /* @__PURE__ */ React3.createElement(
|
|
949
|
-
Box3,
|
|
950
|
-
{
|
|
951
|
-
position: "relative",
|
|
952
|
-
p: 1.5,
|
|
953
|
-
sx: { backgroundColor: "action.hover", border: "1px solid", borderColor: "divider", borderRadius: 1 }
|
|
954
|
-
},
|
|
955
|
-
/* @__PURE__ */ React3.createElement(
|
|
956
|
-
IconButton2,
|
|
957
|
-
{
|
|
958
|
-
size: "small",
|
|
959
|
-
onClick: () => onCopy(code),
|
|
960
|
-
title: "Copy snippet",
|
|
961
|
-
sx: { position: "absolute", top: 4, right: 4 }
|
|
962
|
-
},
|
|
963
|
-
/* @__PURE__ */ React3.createElement(ContentCopy2, { fontSize: "small" })
|
|
964
|
-
),
|
|
965
|
-
/* @__PURE__ */ React3.createElement(
|
|
966
|
-
Typography3,
|
|
967
|
-
{
|
|
968
|
-
component: "pre",
|
|
969
|
-
sx: {
|
|
970
|
-
fontFamily: "monospace",
|
|
971
|
-
fontSize: 12,
|
|
972
|
-
whiteSpace: "pre-wrap",
|
|
973
|
-
wordBreak: "break-all",
|
|
974
|
-
mb: 0,
|
|
975
|
-
pr: 4
|
|
976
|
-
}
|
|
977
|
-
},
|
|
978
|
-
code
|
|
979
|
-
),
|
|
980
|
-
model && /* @__PURE__ */ React3.createElement(Typography3, { variant: "caption", color: "text.secondary", display: "block", mt: 1 }, "Using model \u201C", model, "\u201D \u2014 swap it for any model you have access to.")
|
|
981
|
-
));
|
|
982
|
-
};
|
|
983
1038
|
}
|
|
984
1039
|
});
|
|
985
1040
|
|
|
986
1041
|
// src/components/UsageStats.tsx
|
|
987
1042
|
import React4, { useMemo as useMemo4, useState as useState3 } from "react";
|
|
988
|
-
import
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
useTheme
|
|
1009
|
-
} from "@mui/material";
|
|
1043
|
+
import Paper3 from "@mui/material/Paper";
|
|
1044
|
+
import Box4 from "@mui/material/Box";
|
|
1045
|
+
import Typography4 from "@mui/material/Typography";
|
|
1046
|
+
import FormControl from "@mui/material/FormControl";
|
|
1047
|
+
import InputLabel from "@mui/material/InputLabel";
|
|
1048
|
+
import Select from "@mui/material/Select";
|
|
1049
|
+
import MenuItem2 from "@mui/material/MenuItem";
|
|
1050
|
+
import Grid from "@mui/material/Grid";
|
|
1051
|
+
import Tabs2 from "@mui/material/Tabs";
|
|
1052
|
+
import Tab2 from "@mui/material/Tab";
|
|
1053
|
+
import Table2 from "@mui/material/Table";
|
|
1054
|
+
import TableBody2 from "@mui/material/TableBody";
|
|
1055
|
+
import TableCell2 from "@mui/material/TableCell";
|
|
1056
|
+
import TableContainer2 from "@mui/material/TableContainer";
|
|
1057
|
+
import TableHead2 from "@mui/material/TableHead";
|
|
1058
|
+
import TableRow2 from "@mui/material/TableRow";
|
|
1059
|
+
import Chip3 from "@mui/material/Chip";
|
|
1060
|
+
import LinearProgress3 from "@mui/material/LinearProgress";
|
|
1061
|
+
import Skeleton from "@mui/material/Skeleton";
|
|
1062
|
+
import { useTheme } from "@mui/material/styles";
|
|
1010
1063
|
import {
|
|
1011
1064
|
AreaChart,
|
|
1012
1065
|
Area,
|
|
@@ -1027,7 +1080,7 @@ function modelColor(model) {
|
|
|
1027
1080
|
for (let i = 0; i < model.length; i++) h = (h << 5) + h + model.charCodeAt(i) >>> 0;
|
|
1028
1081
|
return MODEL_COLORS[h % MODEL_COLORS.length];
|
|
1029
1082
|
}
|
|
1030
|
-
var TOP_N_MODELS, PERIOD_LS_KEY, MODEL_COLORS, fmtPct, KpiCard, ChartSkeleton, EmptyChart, UsageStats;
|
|
1083
|
+
var TOP_N_MODELS, PERIOD_LS_KEY, MODEL_COLORS, fmtPct, KpiCard, ChartSkeleton, EmptyChart, ChartOrFallback, UsageStats;
|
|
1031
1084
|
var init_UsageStats = __esm({
|
|
1032
1085
|
"src/components/UsageStats.tsx"() {
|
|
1033
1086
|
"use strict";
|
|
@@ -1053,6 +1106,16 @@ var init_UsageStats = __esm({
|
|
|
1053
1106
|
height = 260,
|
|
1054
1107
|
message = "No data for this period"
|
|
1055
1108
|
}) => /* @__PURE__ */ React4.createElement(Box4, { height, display: "flex", alignItems: "center", justifyContent: "center" }, /* @__PURE__ */ React4.createElement(Typography4, { color: "text.secondary", variant: "body2" }, message));
|
|
1109
|
+
ChartOrFallback = ({
|
|
1110
|
+
loading,
|
|
1111
|
+
empty,
|
|
1112
|
+
height,
|
|
1113
|
+
children
|
|
1114
|
+
}) => {
|
|
1115
|
+
if (loading) return /* @__PURE__ */ React4.createElement(ChartSkeleton, { height });
|
|
1116
|
+
if (empty) return /* @__PURE__ */ React4.createElement(EmptyChart, { height });
|
|
1117
|
+
return /* @__PURE__ */ React4.createElement(React4.Fragment, null, children);
|
|
1118
|
+
};
|
|
1056
1119
|
UsageStats = ({
|
|
1057
1120
|
usage,
|
|
1058
1121
|
models,
|
|
@@ -1176,6 +1239,38 @@ var init_UsageStats = __esm({
|
|
|
1176
1239
|
const overallSuccessRate = totalRequests > 0 ? (usage?.successful_requests ?? 0) / totalRequests : 0;
|
|
1177
1240
|
const maxBudget = userInfo?.max_budget ?? 0;
|
|
1178
1241
|
const totalCumSpend = cumulativeData[cumulativeData.length - 1]?.cumulative ?? 0;
|
|
1242
|
+
const renderKeyRows = () => {
|
|
1243
|
+
if (loading) {
|
|
1244
|
+
return /* @__PURE__ */ React4.createElement(TableRow2, null, /* @__PURE__ */ React4.createElement(TableCell2, { colSpan: 7 }, /* @__PURE__ */ React4.createElement(LinearProgress3, null)));
|
|
1245
|
+
}
|
|
1246
|
+
if (keyRows.length === 0) {
|
|
1247
|
+
return /* @__PURE__ */ React4.createElement(TableRow2, null, /* @__PURE__ */ React4.createElement(TableCell2, { colSpan: 7, align: "center" }, "No key activity"));
|
|
1248
|
+
}
|
|
1249
|
+
return [...keyRows].sort((a, b) => b.spend - a.spend || b.apiRequests - a.apiRequests).map((r) => /* @__PURE__ */ React4.createElement(TableRow2, { key: r.keyHash }, /* @__PURE__ */ React4.createElement(TableCell2, null, /* @__PURE__ */ React4.createElement(Typography4, { variant: "body2" }, r.keyAlias), r.teamId ? /* @__PURE__ */ React4.createElement(Typography4, { variant: "caption", color: "text.secondary" }, "team: ", r.teamId) : null), /* @__PURE__ */ React4.createElement(TableCell2, null, /* @__PURE__ */ React4.createElement(Box4, { display: "flex", gap: 0.5, flexWrap: "wrap" }, r.models.map((m) => /* @__PURE__ */ React4.createElement(Chip3, { key: m, label: m, size: "small", variant: "outlined" })))), /* @__PURE__ */ React4.createElement(TableCell2, { align: "right" }, fmtUsd(r.spend)), /* @__PURE__ */ React4.createElement(TableCell2, { align: "right" }, fmtInt(r.apiRequests)), /* @__PURE__ */ React4.createElement(TableCell2, { align: "right" }, fmtInt(r.totalTokens)), /* @__PURE__ */ React4.createElement(TableCell2, { align: "right" }, fmtInt(r.failedRequests)), /* @__PURE__ */ React4.createElement(TableCell2, null, r.apiRequests > 0 ? /* @__PURE__ */ React4.createElement(Box4, { display: "flex", alignItems: "center", gap: 1 }, /* @__PURE__ */ React4.createElement(
|
|
1250
|
+
LinearProgress3,
|
|
1251
|
+
{
|
|
1252
|
+
variant: "determinate",
|
|
1253
|
+
value: r.successRate * 100,
|
|
1254
|
+
sx: { flex: 1, height: 6, borderRadius: 3 }
|
|
1255
|
+
}
|
|
1256
|
+
), /* @__PURE__ */ React4.createElement(Typography4, { variant: "caption" }, fmtPct(r.successRate))) : "\u2014")));
|
|
1257
|
+
};
|
|
1258
|
+
const renderModelRows = () => {
|
|
1259
|
+
if (loading) {
|
|
1260
|
+
return /* @__PURE__ */ React4.createElement(TableRow2, null, /* @__PURE__ */ React4.createElement(TableCell2, { colSpan: 8 }, /* @__PURE__ */ React4.createElement(LinearProgress3, null)));
|
|
1261
|
+
}
|
|
1262
|
+
if (modelRows.length === 0) {
|
|
1263
|
+
return /* @__PURE__ */ React4.createElement(TableRow2, null, /* @__PURE__ */ React4.createElement(TableCell2, { colSpan: 8, align: "center" }, "No model activity"));
|
|
1264
|
+
}
|
|
1265
|
+
return [...modelRows].sort((a, b) => b.spend - a.spend || b.totalTokens - a.totalTokens).map((r) => /* @__PURE__ */ React4.createElement(TableRow2, { key: r.model }, /* @__PURE__ */ React4.createElement(TableCell2, null, /* @__PURE__ */ React4.createElement(Box4, { display: "flex", alignItems: "center", gap: 0.75 }, /* @__PURE__ */ React4.createElement(Box4, { sx: { width: 10, height: 10, borderRadius: "50%", bgcolor: modelColor(r.model), flexShrink: 0 } }), r.model)), /* @__PURE__ */ React4.createElement(TableCell2, { align: "right" }, fmtUsd(r.spend)), /* @__PURE__ */ React4.createElement(TableCell2, { align: "right" }, fmtInt(r.apiRequests)), /* @__PURE__ */ React4.createElement(TableCell2, { align: "right" }, fmtInt(r.successfulRequests)), /* @__PURE__ */ React4.createElement(TableCell2, { align: "right" }, fmtInt(r.failedRequests)), /* @__PURE__ */ React4.createElement(TableCell2, { align: "right" }, fmtInt(r.promptTokens)), /* @__PURE__ */ React4.createElement(TableCell2, { align: "right" }, fmtInt(r.completionTokens)), /* @__PURE__ */ React4.createElement(TableCell2, null, r.apiRequests > 0 ? /* @__PURE__ */ React4.createElement(Box4, { display: "flex", alignItems: "center", gap: 1 }, /* @__PURE__ */ React4.createElement(
|
|
1266
|
+
LinearProgress3,
|
|
1267
|
+
{
|
|
1268
|
+
variant: "determinate",
|
|
1269
|
+
value: r.successRate * 100,
|
|
1270
|
+
sx: { flex: 1, height: 6, borderRadius: 3 }
|
|
1271
|
+
}
|
|
1272
|
+
), /* @__PURE__ */ React4.createElement(Typography4, { variant: "caption" }, fmtPct(r.successRate))) : "\u2014")));
|
|
1273
|
+
};
|
|
1179
1274
|
return /* @__PURE__ */ React4.createElement(Paper3, { sx: { p: 2 } }, /* @__PURE__ */ React4.createElement(Box4, { display: "flex", justifyContent: "space-between", alignItems: "center", mb: 2, flexWrap: "wrap", gap: 2 }, /* @__PURE__ */ React4.createElement(Typography4, { variant: "h6" }, "Usage Analytics"), /* @__PURE__ */ React4.createElement(Box4, { display: "flex", gap: 2, alignItems: "center", flexWrap: "wrap" }, /* @__PURE__ */ React4.createElement(FormControl, { size: "small", sx: { minWidth: 140 } }, /* @__PURE__ */ React4.createElement(InputLabel, null, "Period"), /* @__PURE__ */ React4.createElement(
|
|
1180
1275
|
Select,
|
|
1181
1276
|
{
|
|
@@ -1203,7 +1298,7 @@ var init_UsageStats = __esm({
|
|
|
1203
1298
|
value: fmtInt(usage?.total_tokens ?? 0),
|
|
1204
1299
|
hint: `${fmtInt(usage?.prompt_tokens ?? 0)} in \xB7 ${fmtInt(usage?.completion_tokens ?? 0)} out`
|
|
1205
1300
|
}
|
|
1206
|
-
))), /* @__PURE__ */ React4.createElement(Tabs2, { value: tab, onChange: (_, v) => setTab(v), sx: { mb: 2 } }, /* @__PURE__ */ React4.createElement(Tab2, { value: "costs", label: "Costs" }), /* @__PURE__ */ React4.createElement(Tab2, { value: "models", label: "Model Activity" }), /* @__PURE__ */ React4.createElement(Tab2, { value: "keys", label: "Key Activity" })), tab === "costs" && /* @__PURE__ */ React4.createElement(Grid, { container: true, spacing: 3 }, /* @__PURE__ */ React4.createElement(Grid, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ React4.createElement(Typography4, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Daily Spend by Model"),
|
|
1301
|
+
))), /* @__PURE__ */ React4.createElement(Tabs2, { value: tab, onChange: (_, v) => setTab(v), sx: { mb: 2 } }, /* @__PURE__ */ React4.createElement(Tab2, { value: "costs", label: "Costs" }), /* @__PURE__ */ React4.createElement(Tab2, { value: "models", label: "Model Activity" }), /* @__PURE__ */ React4.createElement(Tab2, { value: "keys", label: "Key Activity" })), tab === "costs" && /* @__PURE__ */ React4.createElement(Grid, { container: true, spacing: 3 }, /* @__PURE__ */ React4.createElement(Grid, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ React4.createElement(Typography4, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Daily Spend by Model"), /* @__PURE__ */ React4.createElement(ChartOrFallback, { loading, empty: modelSpendByDate.length === 0 }, /* @__PURE__ */ React4.createElement(Box4, { height: 260 }, /* @__PURE__ */ React4.createElement(ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ React4.createElement(AreaChart, { data: modelSpendByDate }, /* @__PURE__ */ React4.createElement(CartesianGrid, { strokeDasharray: "3 3", stroke: gridStroke }), /* @__PURE__ */ React4.createElement(XAxis, { dataKey: "date", tick: tickStyle }), /* @__PURE__ */ React4.createElement(YAxis, { tick: tickStyle, tickFormatter: (v) => `$${v.toFixed(2)}` }), /* @__PURE__ */ React4.createElement(Tooltip, { formatter: (v) => [`$${v.toFixed(4)}`, void 0] }), /* @__PURE__ */ React4.createElement(Legend, null), topSpendModels.map((m) => /* @__PURE__ */ React4.createElement(
|
|
1207
1302
|
Area,
|
|
1208
1303
|
{
|
|
1209
1304
|
key: m,
|
|
@@ -1214,46 +1309,30 @@ var init_UsageStats = __esm({
|
|
|
1214
1309
|
fill: modelColor(m),
|
|
1215
1310
|
fillOpacity: 0.6
|
|
1216
1311
|
}
|
|
1217
|
-
)))))), /* @__PURE__ */ React4.createElement(Grid, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ React4.createElement(Typography4, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Daily Token Usage"), loading ? /* @__PURE__ */ React4.createElement(ChartSkeleton, null) : dailyData.length === 0 ? /* @__PURE__ */ React4.createElement(EmptyChart, null) : /* @__PURE__ */ React4.createElement(Box4, { height: 260 }, /* @__PURE__ */ React4.createElement(ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ React4.createElement(AreaChart, { data: dailyData }, /* @__PURE__ */ React4.createElement(CartesianGrid, { strokeDasharray: "3 3", stroke: gridStroke }), /* @__PURE__ */ React4.createElement(XAxis, { dataKey: "date", tick: tickStyle }), /* @__PURE__ */ React4.createElement(YAxis, { tick: tickStyle, tickFormatter: fmtInt }), /* @__PURE__ */ React4.createElement(Tooltip, { formatter: (v) => [fmtInt(v), void 0] }), /* @__PURE__ */ React4.createElement(Legend, null), /* @__PURE__ */ React4.createElement(Area, { type: "monotone", dataKey: "promptTokens", name: "Input (prompt)", stackId: "tok", stroke: "#8884d8", fill: "#8884d8", fillOpacity: 0.5 }), /* @__PURE__ */ React4.createElement(Area, { type: "monotone", dataKey: "completionTokens", name: "Output (completion)", stackId: "tok", stroke: "#82ca9d", fill: "#82ca9d", fillOpacity: 0.5 }))))), /* @__PURE__ */ React4.createElement(Grid, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ React4.createElement(Typography4, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Daily Requests"), loading ? /* @__PURE__ */ React4.createElement(ChartSkeleton, null) : dailyData.length === 0 ? /* @__PURE__ */ React4.createElement(EmptyChart, null) : /* @__PURE__ */ React4.createElement(Box4, { height: 260 }, /* @__PURE__ */ React4.createElement(ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ React4.createElement(BarChart, { data: dailyData }, /* @__PURE__ */ React4.createElement(CartesianGrid, { strokeDasharray: "3 3", stroke: gridStroke }), /* @__PURE__ */ React4.createElement(XAxis, { dataKey: "date", tick: tickStyle }), /* @__PURE__ */ React4.createElement(YAxis, { tick: tickStyle }), /* @__PURE__ */ React4.createElement(Tooltip, null), /* @__PURE__ */ React4.createElement(Legend, null), /* @__PURE__ */ React4.createElement(Bar, { dataKey: "successfulRequests", name: "Successful", fill: "#82ca9d", stackId: "r" }), /* @__PURE__ */ React4.createElement(Bar, { dataKey: "failedRequests", name: "Failed", fill: "#e57373", stackId: "r" }))))), /* @__PURE__ */ React4.createElement(Grid, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ React4.createElement(Typography4, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Daily Success Rate"), loading ? /* @__PURE__ */ React4.createElement(ChartSkeleton, null) : successRateData.length === 0 ? /* @__PURE__ */ React4.createElement(EmptyChart, null) : /* @__PURE__ */ React4.createElement(Box4, { height: 260 }, /* @__PURE__ */ React4.createElement(ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ React4.createElement(LineChart, { data: successRateData }, /* @__PURE__ */ React4.createElement(CartesianGrid, { strokeDasharray: "3 3", stroke: gridStroke }), /* @__PURE__ */ React4.createElement(XAxis, { dataKey: "date", tick: tickStyle }), /* @__PURE__ */ React4.createElement(YAxis, { domain: [0, 100], tick: tickStyle, tickFormatter: (v) => `${v}%` }), /* @__PURE__ */ React4.createElement(Tooltip, { formatter: (v) => [`${v}%`, "Success rate"] }), /* @__PURE__ */ React4.createElement(ReferenceLine, { y: 100, stroke: "#82ca9d", strokeDasharray: "4 2" }), /* @__PURE__ */ React4.createElement(Line, { type: "monotone", dataKey: "successRate", name: "Success rate", stroke: "#8884d8", dot: { r: 3 } }))))), (maxBudget > 0 || cumulativeData.length > 0) && /* @__PURE__ */ React4.createElement(Grid, { item: true, xs: 12 }, /* @__PURE__ */ React4.createElement(Typography4, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Cumulative Spend", maxBudget > 0 ? ` vs Budget (${fmtUsd(maxBudget)})` : "", maxBudget > 0 && /* @__PURE__ */ React4.createElement(Typography4, { component: "span", variant: "caption", color: "text.secondary", sx: { ml: 1 } }, fmtUsd(totalCumSpend), " used \xB7 ", fmtUsd(Math.max(0, maxBudget - totalCumSpend)), " remaining")), loading ? /* @__PURE__ */ React4.createElement(ChartSkeleton, { height: 180 }) : cumulativeData.length === 0 ? /* @__PURE__ */ React4.createElement(EmptyChart, { height: 180 }) : /* @__PURE__ */ React4.createElement(Box4, { height: 180 }, /* @__PURE__ */ React4.createElement(ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ React4.createElement(AreaChart, { data: cumulativeData }, /* @__PURE__ */ React4.createElement(CartesianGrid, { strokeDasharray: "3 3", stroke: gridStroke }), /* @__PURE__ */ React4.createElement(XAxis, { dataKey: "date", tick: tickStyle }), /* @__PURE__ */ React4.createElement(YAxis, { tick: tickStyle, tickFormatter: (v) => `$${v.toFixed(2)}` }), /* @__PURE__ */ React4.createElement(Tooltip, { formatter: (v) => [`$${v.toFixed(4)}`, "Cumulative spend"] }), maxBudget > 0 && /* @__PURE__ */ React4.createElement(ReferenceLine, { y: maxBudget, stroke: "#e57373", strokeDasharray: "6 3", label: { value: `Budget ${fmtUsd(maxBudget)}`, position: "insideTopRight", fontSize: 11 } }), /* @__PURE__ */ React4.createElement(Area, { type: "monotone", dataKey: "cumulative", name: "Cumulative spend", stroke: "#ffc658", fill: "#ffc658", fillOpacity: 0.3 })))))), tab === "models" && /* @__PURE__ */ React4.createElement(Grid, { container: true, spacing: 3 }, /* @__PURE__ */ React4.createElement(Grid, { item: true, xs: 12 }, /* @__PURE__ */ React4.createElement(Typography4, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Spend by Model"), loading ? /* @__PURE__ */ React4.createElement(ChartSkeleton, null) : topModelSpendBars.length === 0 ? /* @__PURE__ */ React4.createElement(EmptyChart, null) : /* @__PURE__ */ React4.createElement(Box4, { height: Math.max(200, topModelSpendBars.length * 36) }, /* @__PURE__ */ React4.createElement(ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ React4.createElement(BarChart, { data: topModelSpendBars, layout: "vertical", margin: { left: 20 } }, /* @__PURE__ */ React4.createElement(CartesianGrid, { strokeDasharray: "3 3", stroke: gridStroke }), /* @__PURE__ */ React4.createElement(XAxis, { type: "number", tick: tickStyle, tickFormatter: (v) => `$${v.toFixed(2)}` }), /* @__PURE__ */ React4.createElement(YAxis, { type: "category", dataKey: "model", tick: tickStyleSmall, width: 200 }), /* @__PURE__ */ React4.createElement(Tooltip, { formatter: (v) => [fmtUsd(v), "Spend"] }), /* @__PURE__ */ React4.createElement(Bar, { dataKey: "spend", name: "Spend", radius: [0, 4, 4, 0] }, topModelSpendBars.map((r) => /* @__PURE__ */ React4.createElement("rect", { key: r.model, fill: modelColor(r.model) }))))))), /* @__PURE__ */ React4.createElement(Grid, { item: true, xs: 12 }, /* @__PURE__ */ React4.createElement(Typography4, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Tokens by Model"), loading ? /* @__PURE__ */ React4.createElement(ChartSkeleton, null) : modelRows.length === 0 ? /* @__PURE__ */ React4.createElement(EmptyChart, null) : /* @__PURE__ */ React4.createElement(Box4, { height: 260 }, /* @__PURE__ */ React4.createElement(ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ React4.createElement(BarChart, { data: modelRows }, /* @__PURE__ */ React4.createElement(CartesianGrid, { strokeDasharray: "3 3", stroke: gridStroke }), /* @__PURE__ */ React4.createElement(XAxis, { dataKey: "model", tick: tickStyleTiny, interval: 0, angle: -15, textAnchor: "end", height: 60 }), /* @__PURE__ */ React4.createElement(YAxis, { tick: tickStyle }), /* @__PURE__ */ React4.createElement(Tooltip, null), /* @__PURE__ */ React4.createElement(Legend, null), /* @__PURE__ */ React4.createElement(Bar, { dataKey: "promptTokens", name: "Prompt", fill: "#8884d8", stackId: "t" }), /* @__PURE__ */ React4.createElement(Bar, { dataKey: "completionTokens", name: "Completion", fill: "#82ca9d", stackId: "t" }))))), /* @__PURE__ */ React4.createElement(Grid, { item: true, xs: 12 }, /* @__PURE__ */ React4.createElement(TableContainer2, { component: Paper3, variant: "outlined" }, /* @__PURE__ */ React4.createElement(Table2, { size: "small" }, /* @__PURE__ */ React4.createElement(TableHead2, null, /* @__PURE__ */ React4.createElement(TableRow2, null, /* @__PURE__ */ React4.createElement(TableCell2, null, "Model"), /* @__PURE__ */ React4.createElement(TableCell2, { align: "right" }, "Spend"), /* @__PURE__ */ React4.createElement(TableCell2, { align: "right" }, "Requests"), /* @__PURE__ */ React4.createElement(TableCell2, { align: "right" }, "Success"), /* @__PURE__ */ React4.createElement(TableCell2, { align: "right" }, "Failed"), /* @__PURE__ */ React4.createElement(TableCell2, { align: "right" }, "Prompt"), /* @__PURE__ */ React4.createElement(TableCell2, { align: "right" }, "Completion"), /* @__PURE__ */ React4.createElement(TableCell2, { sx: { minWidth: 120 } }, "Success rate"))), /* @__PURE__ */ React4.createElement(TableBody2, null, loading ? /* @__PURE__ */ React4.createElement(TableRow2, null, /* @__PURE__ */ React4.createElement(TableCell2, { colSpan: 8 }, /* @__PURE__ */ React4.createElement(LinearProgress3, null))) : modelRows.length === 0 ? /* @__PURE__ */ React4.createElement(TableRow2, null, /* @__PURE__ */ React4.createElement(TableCell2, { colSpan: 8, align: "center" }, "No model activity")) : [...modelRows].sort((a, b) => b.spend - a.spend || b.totalTokens - a.totalTokens).map((r) => /* @__PURE__ */ React4.createElement(TableRow2, { key: r.model }, /* @__PURE__ */ React4.createElement(TableCell2, null, /* @__PURE__ */ React4.createElement(Box4, { display: "flex", alignItems: "center", gap: 0.75 }, /* @__PURE__ */ React4.createElement(Box4, { sx: { width: 10, height: 10, borderRadius: "50%", bgcolor: modelColor(r.model), flexShrink: 0 } }), r.model)), /* @__PURE__ */ React4.createElement(TableCell2, { align: "right" }, fmtUsd(r.spend)), /* @__PURE__ */ React4.createElement(TableCell2, { align: "right" }, fmtInt(r.apiRequests)), /* @__PURE__ */ React4.createElement(TableCell2, { align: "right" }, fmtInt(r.successfulRequests)), /* @__PURE__ */ React4.createElement(TableCell2, { align: "right" }, fmtInt(r.failedRequests)), /* @__PURE__ */ React4.createElement(TableCell2, { align: "right" }, fmtInt(r.promptTokens)), /* @__PURE__ */ React4.createElement(TableCell2, { align: "right" }, fmtInt(r.completionTokens)), /* @__PURE__ */ React4.createElement(TableCell2, null, r.apiRequests > 0 ? /* @__PURE__ */ React4.createElement(Box4, { display: "flex", alignItems: "center", gap: 1 }, /* @__PURE__ */ React4.createElement(
|
|
1218
|
-
LinearProgress3,
|
|
1219
|
-
{
|
|
1220
|
-
variant: "determinate",
|
|
1221
|
-
value: r.successRate * 100,
|
|
1222
|
-
sx: { flex: 1, height: 6, borderRadius: 3 }
|
|
1223
|
-
}
|
|
1224
|
-
), /* @__PURE__ */ React4.createElement(Typography4, { variant: "caption" }, fmtPct(r.successRate))) : "\u2014")))))))), tab === "keys" && /* @__PURE__ */ React4.createElement(Grid, { container: true, spacing: 3 }, (loading || topKeySpendBars.length > 0) && /* @__PURE__ */ React4.createElement(Grid, { item: true, xs: 12 }, /* @__PURE__ */ React4.createElement(Typography4, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Spend by Key"), loading ? /* @__PURE__ */ React4.createElement(ChartSkeleton, null) : /* @__PURE__ */ React4.createElement(Box4, { height: Math.max(160, topKeySpendBars.length * 36) }, /* @__PURE__ */ React4.createElement(ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ React4.createElement(BarChart, { data: topKeySpendBars, layout: "vertical", margin: { left: 20 } }, /* @__PURE__ */ React4.createElement(CartesianGrid, { strokeDasharray: "3 3", stroke: gridStroke }), /* @__PURE__ */ React4.createElement(XAxis, { type: "number", tick: tickStyle, tickFormatter: (v) => `$${v.toFixed(2)}` }), /* @__PURE__ */ React4.createElement(YAxis, { type: "category", dataKey: "keyAlias", tick: tickStyleSmall, width: 160 }), /* @__PURE__ */ React4.createElement(Tooltip, { formatter: (v) => [fmtUsd(v), "Spend"] }), /* @__PURE__ */ React4.createElement(Bar, { dataKey: "spend", name: "Spend", fill: "#8884d8", radius: [0, 4, 4, 0] }))))), /* @__PURE__ */ React4.createElement(Grid, { item: true, xs: 12 }, /* @__PURE__ */ React4.createElement(TableContainer2, { component: Paper3, variant: "outlined" }, /* @__PURE__ */ React4.createElement(Table2, { size: "small" }, /* @__PURE__ */ React4.createElement(TableHead2, null, /* @__PURE__ */ React4.createElement(TableRow2, null, /* @__PURE__ */ React4.createElement(TableCell2, null, "Key"), /* @__PURE__ */ React4.createElement(TableCell2, null, "Models"), /* @__PURE__ */ React4.createElement(TableCell2, { align: "right" }, "Spend"), /* @__PURE__ */ React4.createElement(TableCell2, { align: "right" }, "Requests"), /* @__PURE__ */ React4.createElement(TableCell2, { align: "right" }, "Tokens"), /* @__PURE__ */ React4.createElement(TableCell2, { align: "right" }, "Failed"), /* @__PURE__ */ React4.createElement(TableCell2, { sx: { minWidth: 120 } }, "Success rate"))), /* @__PURE__ */ React4.createElement(TableBody2, null, loading ? /* @__PURE__ */ React4.createElement(TableRow2, null, /* @__PURE__ */ React4.createElement(TableCell2, { colSpan: 7 }, /* @__PURE__ */ React4.createElement(LinearProgress3, null))) : keyRows.length === 0 ? /* @__PURE__ */ React4.createElement(TableRow2, null, /* @__PURE__ */ React4.createElement(TableCell2, { colSpan: 7, align: "center" }, "No key activity")) : [...keyRows].sort((a, b) => b.spend - a.spend || b.apiRequests - a.apiRequests).map((r) => /* @__PURE__ */ React4.createElement(TableRow2, { key: r.keyHash }, /* @__PURE__ */ React4.createElement(TableCell2, null, /* @__PURE__ */ React4.createElement(Typography4, { variant: "body2" }, r.keyAlias), r.teamId ? /* @__PURE__ */ React4.createElement(Typography4, { variant: "caption", color: "text.secondary" }, "team: ", r.teamId) : null), /* @__PURE__ */ React4.createElement(TableCell2, null, /* @__PURE__ */ React4.createElement(Box4, { display: "flex", gap: 0.5, flexWrap: "wrap" }, r.models.map((m) => /* @__PURE__ */ React4.createElement(Chip3, { key: m, label: m, size: "small", variant: "outlined" })))), /* @__PURE__ */ React4.createElement(TableCell2, { align: "right" }, fmtUsd(r.spend)), /* @__PURE__ */ React4.createElement(TableCell2, { align: "right" }, fmtInt(r.apiRequests)), /* @__PURE__ */ React4.createElement(TableCell2, { align: "right" }, fmtInt(r.totalTokens)), /* @__PURE__ */ React4.createElement(TableCell2, { align: "right" }, fmtInt(r.failedRequests)), /* @__PURE__ */ React4.createElement(TableCell2, null, r.apiRequests > 0 ? /* @__PURE__ */ React4.createElement(Box4, { display: "flex", alignItems: "center", gap: 1 }, /* @__PURE__ */ React4.createElement(
|
|
1225
|
-
LinearProgress3,
|
|
1226
|
-
{
|
|
1227
|
-
variant: "determinate",
|
|
1228
|
-
value: r.successRate * 100,
|
|
1229
|
-
sx: { flex: 1, height: 6, borderRadius: 3 }
|
|
1230
|
-
}
|
|
1231
|
-
), /* @__PURE__ */ React4.createElement(Typography4, { variant: "caption" }, fmtPct(r.successRate))) : "\u2014")))))))));
|
|
1312
|
+
))))))), /* @__PURE__ */ React4.createElement(Grid, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ React4.createElement(Typography4, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Daily Token Usage"), /* @__PURE__ */ React4.createElement(ChartOrFallback, { loading, empty: dailyData.length === 0 }, /* @__PURE__ */ React4.createElement(Box4, { height: 260 }, /* @__PURE__ */ React4.createElement(ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ React4.createElement(AreaChart, { data: dailyData }, /* @__PURE__ */ React4.createElement(CartesianGrid, { strokeDasharray: "3 3", stroke: gridStroke }), /* @__PURE__ */ React4.createElement(XAxis, { dataKey: "date", tick: tickStyle }), /* @__PURE__ */ React4.createElement(YAxis, { tick: tickStyle, tickFormatter: fmtInt }), /* @__PURE__ */ React4.createElement(Tooltip, { formatter: (v) => [fmtInt(v), void 0] }), /* @__PURE__ */ React4.createElement(Legend, null), /* @__PURE__ */ React4.createElement(Area, { type: "monotone", dataKey: "promptTokens", name: "Input (prompt)", stackId: "tok", stroke: "#8884d8", fill: "#8884d8", fillOpacity: 0.5 }), /* @__PURE__ */ React4.createElement(Area, { type: "monotone", dataKey: "completionTokens", name: "Output (completion)", stackId: "tok", stroke: "#82ca9d", fill: "#82ca9d", fillOpacity: 0.5 })))))), /* @__PURE__ */ React4.createElement(Grid, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ React4.createElement(Typography4, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Daily Requests"), /* @__PURE__ */ React4.createElement(ChartOrFallback, { loading, empty: dailyData.length === 0 }, /* @__PURE__ */ React4.createElement(Box4, { height: 260 }, /* @__PURE__ */ React4.createElement(ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ React4.createElement(BarChart, { data: dailyData }, /* @__PURE__ */ React4.createElement(CartesianGrid, { strokeDasharray: "3 3", stroke: gridStroke }), /* @__PURE__ */ React4.createElement(XAxis, { dataKey: "date", tick: tickStyle }), /* @__PURE__ */ React4.createElement(YAxis, { tick: tickStyle }), /* @__PURE__ */ React4.createElement(Tooltip, null), /* @__PURE__ */ React4.createElement(Legend, null), /* @__PURE__ */ React4.createElement(Bar, { dataKey: "successfulRequests", name: "Successful", fill: "#82ca9d", stackId: "r" }), /* @__PURE__ */ React4.createElement(Bar, { dataKey: "failedRequests", name: "Failed", fill: "#e57373", stackId: "r" })))))), /* @__PURE__ */ React4.createElement(Grid, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ React4.createElement(Typography4, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Daily Success Rate"), /* @__PURE__ */ React4.createElement(ChartOrFallback, { loading, empty: successRateData.length === 0 }, /* @__PURE__ */ React4.createElement(Box4, { height: 260 }, /* @__PURE__ */ React4.createElement(ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ React4.createElement(LineChart, { data: successRateData }, /* @__PURE__ */ React4.createElement(CartesianGrid, { strokeDasharray: "3 3", stroke: gridStroke }), /* @__PURE__ */ React4.createElement(XAxis, { dataKey: "date", tick: tickStyle }), /* @__PURE__ */ React4.createElement(YAxis, { domain: [0, 100], tick: tickStyle, tickFormatter: (v) => `${v}%` }), /* @__PURE__ */ React4.createElement(Tooltip, { formatter: (v) => [`${v}%`, "Success rate"] }), /* @__PURE__ */ React4.createElement(ReferenceLine, { y: 100, stroke: "#82ca9d", strokeDasharray: "4 2" }), /* @__PURE__ */ React4.createElement(Line, { type: "monotone", dataKey: "successRate", name: "Success rate", stroke: "#8884d8", dot: { r: 3 } })))))), (maxBudget > 0 || cumulativeData.length > 0) && /* @__PURE__ */ React4.createElement(Grid, { item: true, xs: 12 }, /* @__PURE__ */ React4.createElement(Typography4, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Cumulative Spend", maxBudget > 0 ? ` vs Budget (${fmtUsd(maxBudget)})` : "", maxBudget > 0 && /* @__PURE__ */ React4.createElement(Typography4, { component: "span", variant: "caption", color: "text.secondary", sx: { ml: 1 } }, fmtUsd(totalCumSpend), " used \xB7 ", fmtUsd(Math.max(0, maxBudget - totalCumSpend)), " remaining")), /* @__PURE__ */ React4.createElement(ChartOrFallback, { loading, empty: cumulativeData.length === 0, height: 180 }, /* @__PURE__ */ React4.createElement(Box4, { height: 180 }, /* @__PURE__ */ React4.createElement(ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ React4.createElement(AreaChart, { data: cumulativeData }, /* @__PURE__ */ React4.createElement(CartesianGrid, { strokeDasharray: "3 3", stroke: gridStroke }), /* @__PURE__ */ React4.createElement(XAxis, { dataKey: "date", tick: tickStyle }), /* @__PURE__ */ React4.createElement(YAxis, { tick: tickStyle, tickFormatter: (v) => `$${v.toFixed(2)}` }), /* @__PURE__ */ React4.createElement(Tooltip, { formatter: (v) => [`$${v.toFixed(4)}`, "Cumulative spend"] }), maxBudget > 0 && /* @__PURE__ */ React4.createElement(ReferenceLine, { y: maxBudget, stroke: "#e57373", strokeDasharray: "6 3", label: { value: `Budget ${fmtUsd(maxBudget)}`, position: "insideTopRight", fontSize: 11 } }), /* @__PURE__ */ React4.createElement(Area, { type: "monotone", dataKey: "cumulative", name: "Cumulative spend", stroke: "#ffc658", fill: "#ffc658", fillOpacity: 0.3 }))))))), tab === "models" && /* @__PURE__ */ React4.createElement(Grid, { container: true, spacing: 3 }, /* @__PURE__ */ React4.createElement(Grid, { item: true, xs: 12 }, /* @__PURE__ */ React4.createElement(Typography4, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Spend by Model"), /* @__PURE__ */ React4.createElement(ChartOrFallback, { loading, empty: topModelSpendBars.length === 0 }, /* @__PURE__ */ React4.createElement(Box4, { height: Math.max(200, topModelSpendBars.length * 36) }, /* @__PURE__ */ React4.createElement(ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ React4.createElement(BarChart, { data: topModelSpendBars, layout: "vertical", margin: { left: 20 } }, /* @__PURE__ */ React4.createElement(CartesianGrid, { strokeDasharray: "3 3", stroke: gridStroke }), /* @__PURE__ */ React4.createElement(XAxis, { type: "number", tick: tickStyle, tickFormatter: (v) => `$${v.toFixed(2)}` }), /* @__PURE__ */ React4.createElement(YAxis, { type: "category", dataKey: "model", tick: tickStyleSmall, width: 200 }), /* @__PURE__ */ React4.createElement(Tooltip, { formatter: (v) => [fmtUsd(v), "Spend"] }), /* @__PURE__ */ React4.createElement(Bar, { dataKey: "spend", name: "Spend", radius: [0, 4, 4, 0] }, topModelSpendBars.map((r) => /* @__PURE__ */ React4.createElement("rect", { key: r.model, fill: modelColor(r.model) })))))))), /* @__PURE__ */ React4.createElement(Grid, { item: true, xs: 12 }, /* @__PURE__ */ React4.createElement(Typography4, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Tokens by Model"), /* @__PURE__ */ React4.createElement(ChartOrFallback, { loading, empty: modelRows.length === 0 }, /* @__PURE__ */ React4.createElement(Box4, { height: 260 }, /* @__PURE__ */ React4.createElement(ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ React4.createElement(BarChart, { data: modelRows }, /* @__PURE__ */ React4.createElement(CartesianGrid, { strokeDasharray: "3 3", stroke: gridStroke }), /* @__PURE__ */ React4.createElement(XAxis, { dataKey: "model", tick: tickStyleTiny, interval: 0, angle: -15, textAnchor: "end", height: 60 }), /* @__PURE__ */ React4.createElement(YAxis, { tick: tickStyle }), /* @__PURE__ */ React4.createElement(Tooltip, null), /* @__PURE__ */ React4.createElement(Legend, null), /* @__PURE__ */ React4.createElement(Bar, { dataKey: "promptTokens", name: "Prompt", fill: "#8884d8", stackId: "t" }), /* @__PURE__ */ React4.createElement(Bar, { dataKey: "completionTokens", name: "Completion", fill: "#82ca9d", stackId: "t" })))))), /* @__PURE__ */ React4.createElement(Grid, { item: true, xs: 12 }, /* @__PURE__ */ React4.createElement(TableContainer2, { component: Paper3, variant: "outlined" }, /* @__PURE__ */ React4.createElement(Table2, { size: "small" }, /* @__PURE__ */ React4.createElement(TableHead2, null, /* @__PURE__ */ React4.createElement(TableRow2, null, /* @__PURE__ */ React4.createElement(TableCell2, null, "Model"), /* @__PURE__ */ React4.createElement(TableCell2, { align: "right" }, "Spend"), /* @__PURE__ */ React4.createElement(TableCell2, { align: "right" }, "Requests"), /* @__PURE__ */ React4.createElement(TableCell2, { align: "right" }, "Success"), /* @__PURE__ */ React4.createElement(TableCell2, { align: "right" }, "Failed"), /* @__PURE__ */ React4.createElement(TableCell2, { align: "right" }, "Prompt"), /* @__PURE__ */ React4.createElement(TableCell2, { align: "right" }, "Completion"), /* @__PURE__ */ React4.createElement(TableCell2, { sx: { minWidth: 120 } }, "Success rate"))), /* @__PURE__ */ React4.createElement(TableBody2, null, renderModelRows()))))), tab === "keys" && /* @__PURE__ */ React4.createElement(Grid, { container: true, spacing: 3 }, (loading || topKeySpendBars.length > 0) && /* @__PURE__ */ React4.createElement(Grid, { item: true, xs: 12 }, /* @__PURE__ */ React4.createElement(Typography4, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Spend by Key"), loading ? /* @__PURE__ */ React4.createElement(ChartSkeleton, null) : /* @__PURE__ */ React4.createElement(Box4, { height: Math.max(160, topKeySpendBars.length * 36) }, /* @__PURE__ */ React4.createElement(ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ React4.createElement(BarChart, { data: topKeySpendBars, layout: "vertical", margin: { left: 20 } }, /* @__PURE__ */ React4.createElement(CartesianGrid, { strokeDasharray: "3 3", stroke: gridStroke }), /* @__PURE__ */ React4.createElement(XAxis, { type: "number", tick: tickStyle, tickFormatter: (v) => `$${v.toFixed(2)}` }), /* @__PURE__ */ React4.createElement(YAxis, { type: "category", dataKey: "keyAlias", tick: tickStyleSmall, width: 160 }), /* @__PURE__ */ React4.createElement(Tooltip, { formatter: (v) => [fmtUsd(v), "Spend"] }), /* @__PURE__ */ React4.createElement(Bar, { dataKey: "spend", name: "Spend", fill: "#8884d8", radius: [0, 4, 4, 0] }))))), /* @__PURE__ */ React4.createElement(Grid, { item: true, xs: 12 }, /* @__PURE__ */ React4.createElement(TableContainer2, { component: Paper3, variant: "outlined" }, /* @__PURE__ */ React4.createElement(Table2, { size: "small" }, /* @__PURE__ */ React4.createElement(TableHead2, null, /* @__PURE__ */ React4.createElement(TableRow2, null, /* @__PURE__ */ React4.createElement(TableCell2, null, "Key"), /* @__PURE__ */ React4.createElement(TableCell2, null, "Models"), /* @__PURE__ */ React4.createElement(TableCell2, { align: "right" }, "Spend"), /* @__PURE__ */ React4.createElement(TableCell2, { align: "right" }, "Requests"), /* @__PURE__ */ React4.createElement(TableCell2, { align: "right" }, "Tokens"), /* @__PURE__ */ React4.createElement(TableCell2, { align: "right" }, "Failed"), /* @__PURE__ */ React4.createElement(TableCell2, { sx: { minWidth: 120 } }, "Success rate"))), /* @__PURE__ */ React4.createElement(TableBody2, null, renderKeyRows()))))));
|
|
1232
1313
|
};
|
|
1233
1314
|
}
|
|
1234
1315
|
});
|
|
1235
1316
|
|
|
1236
1317
|
// src/components/TeamUsage.tsx
|
|
1237
1318
|
import React5, { useState as useState4 } from "react";
|
|
1238
|
-
import
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
useTheme as useTheme2
|
|
1256
|
-
} from "@mui/material";
|
|
1319
|
+
import Paper4 from "@mui/material/Paper";
|
|
1320
|
+
import Box5 from "@mui/material/Box";
|
|
1321
|
+
import Typography5 from "@mui/material/Typography";
|
|
1322
|
+
import LinearProgress4 from "@mui/material/LinearProgress";
|
|
1323
|
+
import Chip4 from "@mui/material/Chip";
|
|
1324
|
+
import Divider from "@mui/material/Divider";
|
|
1325
|
+
import Stack2 from "@mui/material/Stack";
|
|
1326
|
+
import Table3 from "@mui/material/Table";
|
|
1327
|
+
import TableBody3 from "@mui/material/TableBody";
|
|
1328
|
+
import TableCell3 from "@mui/material/TableCell";
|
|
1329
|
+
import TableHead3 from "@mui/material/TableHead";
|
|
1330
|
+
import TableRow3 from "@mui/material/TableRow";
|
|
1331
|
+
import TableContainer3 from "@mui/material/TableContainer";
|
|
1332
|
+
import Collapse from "@mui/material/Collapse";
|
|
1333
|
+
import IconButton3 from "@mui/material/IconButton";
|
|
1334
|
+
import CircularProgress3 from "@mui/material/CircularProgress";
|
|
1335
|
+
import { useTheme as useTheme2 } from "@mui/material/styles";
|
|
1257
1336
|
import { ExpandMore, ExpandLess, Group, Speed, Memory } from "@mui/icons-material";
|
|
1258
1337
|
import {
|
|
1259
1338
|
AreaChart as AreaChart2,
|
|
@@ -1264,6 +1343,11 @@ import {
|
|
|
1264
1343
|
Tooltip as Tooltip2,
|
|
1265
1344
|
ResponsiveContainer as ResponsiveContainer2
|
|
1266
1345
|
} from "recharts";
|
|
1346
|
+
function budgetBarColor(isOver, isNear) {
|
|
1347
|
+
if (isOver) return "error";
|
|
1348
|
+
if (isNear) return "warning";
|
|
1349
|
+
return "primary";
|
|
1350
|
+
}
|
|
1267
1351
|
var Stat, TeamCard, TeamUsage;
|
|
1268
1352
|
var init_TeamUsage = __esm({
|
|
1269
1353
|
"src/components/TeamUsage.tsx"() {
|
|
@@ -1280,6 +1364,15 @@ var init_TeamUsage = __esm({
|
|
|
1280
1364
|
const isOver = budget > 0 && spend >= budget;
|
|
1281
1365
|
const isNear = budget > 0 && spend >= budget * 0.8 && !isOver;
|
|
1282
1366
|
const dailyData = usage?.daily_usage?.map((d) => ({ date: d.date, spend: d.spend })) ?? [];
|
|
1367
|
+
const renderDailySpendSection = () => {
|
|
1368
|
+
if (usageLoading) {
|
|
1369
|
+
return /* @__PURE__ */ React5.createElement(Box5, { display: "flex", justifyContent: "center", p: 2 }, /* @__PURE__ */ React5.createElement(CircularProgress3, { size: 24 }));
|
|
1370
|
+
}
|
|
1371
|
+
if (dailyData.length > 0) {
|
|
1372
|
+
return /* @__PURE__ */ React5.createElement(React5.Fragment, null, /* @__PURE__ */ React5.createElement(Typography5, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Daily Spend"), /* @__PURE__ */ React5.createElement(Box5, { height: 160, mb: 2 }, /* @__PURE__ */ React5.createElement(ResponsiveContainer2, { width: "100%", height: "100%" }, /* @__PURE__ */ React5.createElement(AreaChart2, { data: dailyData }, /* @__PURE__ */ React5.createElement(CartesianGrid2, { strokeDasharray: "3 3", stroke: gridStroke }), /* @__PURE__ */ React5.createElement(XAxis2, { dataKey: "date", tick: { fontSize: 11, fill: tickFill } }), /* @__PURE__ */ React5.createElement(YAxis2, { tick: { fontSize: 11, fill: tickFill }, tickFormatter: (v) => `$${v.toFixed(2)}` }), /* @__PURE__ */ React5.createElement(Tooltip2, { formatter: (v) => [`$${v.toFixed(4)}`, "Spend"] }), /* @__PURE__ */ React5.createElement(Area2, { type: "monotone", dataKey: "spend", stroke: "#8884d8", fill: "#8884d8", fillOpacity: 0.3 })))), /* @__PURE__ */ React5.createElement(Divider, { sx: { mb: 2 } }));
|
|
1373
|
+
}
|
|
1374
|
+
return null;
|
|
1375
|
+
};
|
|
1283
1376
|
return /* @__PURE__ */ React5.createElement(Paper4, { variant: "outlined", sx: { p: 2, mb: 2 } }, /* @__PURE__ */ React5.createElement(Box5, { display: "flex", alignItems: "center", gap: 1 }, /* @__PURE__ */ React5.createElement(Group, { color: "action" }), /* @__PURE__ */ React5.createElement(Box5, { flexGrow: 1 }, /* @__PURE__ */ React5.createElement(Typography5, { variant: "subtitle1", fontWeight: 600 }, team.team_alias || "Untitled team"), /* @__PURE__ */ React5.createElement(Typography5, { variant: "caption", color: "text.secondary", sx: { fontFamily: "monospace" } }, team.team_id)), /* @__PURE__ */ React5.createElement(IconButton3, { size: "small", onClick: () => setExpanded((e) => !e), "aria-label": "toggle details" }, expanded ? /* @__PURE__ */ React5.createElement(ExpandLess, null) : /* @__PURE__ */ React5.createElement(ExpandMore, null))), /* @__PURE__ */ React5.createElement(Stack2, { direction: "row", spacing: 3, mt: 1.5, flexWrap: "wrap", useFlexGap: true, gap: 1.5 }, /* @__PURE__ */ React5.createElement(
|
|
1284
1377
|
Stat,
|
|
1285
1378
|
{
|
|
@@ -1308,23 +1401,23 @@ var init_TeamUsage = __esm({
|
|
|
1308
1401
|
Stat,
|
|
1309
1402
|
{
|
|
1310
1403
|
label: "TPM",
|
|
1311
|
-
value: team.tpm_limit
|
|
1404
|
+
value: typeof team.tpm_limit === "number" && team.tpm_limit > 0 ? String(team.tpm_limit) : "\u2014"
|
|
1312
1405
|
}
|
|
1313
1406
|
), /* @__PURE__ */ React5.createElement(
|
|
1314
1407
|
Stat,
|
|
1315
1408
|
{
|
|
1316
1409
|
label: "RPM",
|
|
1317
|
-
value: team.rpm_limit
|
|
1410
|
+
value: typeof team.rpm_limit === "number" && team.rpm_limit > 0 ? String(team.rpm_limit) : "\u2014"
|
|
1318
1411
|
}
|
|
1319
1412
|
)), budget > 0 && /* @__PURE__ */ React5.createElement(Box5, { mt: 1.5 }, /* @__PURE__ */ React5.createElement(Box5, { display: "flex", justifyContent: "space-between", mb: 0.5 }, /* @__PURE__ */ React5.createElement(Typography5, { variant: "body2" }, "$", spend.toFixed(2), " / $", budget.toFixed(2)), isOver && /* @__PURE__ */ React5.createElement(Chip4, { label: "Over Budget", size: "small", color: "error" }), isNear && /* @__PURE__ */ React5.createElement(Chip4, { label: "Near Limit", size: "small", color: "warning" })), /* @__PURE__ */ React5.createElement(
|
|
1320
1413
|
LinearProgress4,
|
|
1321
1414
|
{
|
|
1322
1415
|
variant: "determinate",
|
|
1323
1416
|
value: budgetPct,
|
|
1324
|
-
color: isOver
|
|
1417
|
+
color: budgetBarColor(isOver, isNear),
|
|
1325
1418
|
sx: { height: 6, borderRadius: 1 }
|
|
1326
1419
|
}
|
|
1327
|
-
)), /* @__PURE__ */ React5.createElement(Collapse, { in: expanded }, /* @__PURE__ */ React5.createElement(Box5, { mt: 2 }, /* @__PURE__ */ React5.createElement(Box5, { display: "flex", alignItems: "center", gap: 1, mb: 1 }, /* @__PURE__ */ React5.createElement(Speed, { fontSize: "small", color: "action" }), /* @__PURE__ */ React5.createElement(Typography5, { variant: "subtitle2", color: "text.secondary" }, "Rate limits")), /* @__PURE__ */ React5.createElement(Stack2, { direction: "row", spacing: 3, flexWrap: "wrap", useFlexGap: true, gap: 1.5, mb: 2 }, /* @__PURE__ */ React5.createElement(Stat, { label: "TPM limit", value: team.tpm_limit
|
|
1420
|
+
)), /* @__PURE__ */ React5.createElement(Collapse, { in: expanded }, /* @__PURE__ */ React5.createElement(Box5, { mt: 2 }, /* @__PURE__ */ React5.createElement(Box5, { display: "flex", alignItems: "center", gap: 1, mb: 1 }, /* @__PURE__ */ React5.createElement(Speed, { fontSize: "small", color: "action" }), /* @__PURE__ */ React5.createElement(Typography5, { variant: "subtitle2", color: "text.secondary" }, "Rate limits")), /* @__PURE__ */ React5.createElement(Stack2, { direction: "row", spacing: 3, flexWrap: "wrap", useFlexGap: true, gap: 1.5, mb: 2 }, /* @__PURE__ */ React5.createElement(Stat, { label: "TPM limit", value: typeof team.tpm_limit === "number" && team.tpm_limit > 0 ? String(team.tpm_limit) : "Unlimited" }), /* @__PURE__ */ React5.createElement(Stat, { label: "RPM limit", value: typeof team.rpm_limit === "number" && team.rpm_limit > 0 ? String(team.rpm_limit) : "Unlimited" }), /* @__PURE__ */ React5.createElement(Stat, { label: "Max budget", value: budget > 0 ? `$${budget.toFixed(2)}` : "Unlimited" })), /* @__PURE__ */ React5.createElement(Divider, { sx: { mb: 2 } }), /* @__PURE__ */ React5.createElement(Typography5, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Models"), team.models?.length ? /* @__PURE__ */ React5.createElement(Box5, { display: "flex", gap: 0.5, flexWrap: "wrap", mb: 2 }, team.models.map((m) => /* @__PURE__ */ React5.createElement(Chip4, { key: m, label: m, size: "small", variant: "outlined" }))) : /* @__PURE__ */ React5.createElement(Typography5, { variant: "body2", color: "text.secondary", mb: 2 }, "All models allowed"), /* @__PURE__ */ React5.createElement(Divider, { sx: { mb: 2 } }), renderDailySpendSection(), /* @__PURE__ */ React5.createElement(Box5, { display: "flex", alignItems: "center", gap: 1, mb: 1 }, /* @__PURE__ */ React5.createElement(Memory, { fontSize: "small", color: "action" }), /* @__PURE__ */ React5.createElement(Typography5, { variant: "subtitle2", color: "text.secondary" }, "Members")), team.members_with_roles?.length ? /* @__PURE__ */ React5.createElement(TableContainer3, null, /* @__PURE__ */ React5.createElement(Table3, { size: "small" }, /* @__PURE__ */ React5.createElement(TableHead3, null, /* @__PURE__ */ React5.createElement(TableRow3, null, /* @__PURE__ */ React5.createElement(TableCell3, null, "User"), /* @__PURE__ */ React5.createElement(TableCell3, null, "Role"))), /* @__PURE__ */ React5.createElement(TableBody3, null, team.members_with_roles.map((m) => /* @__PURE__ */ React5.createElement(TableRow3, { key: m.user_id }, /* @__PURE__ */ React5.createElement(TableCell3, null, m.user_email ? /* @__PURE__ */ React5.createElement(React5.Fragment, null, /* @__PURE__ */ React5.createElement(Typography5, { variant: "body2" }, m.user_email), /* @__PURE__ */ React5.createElement(
|
|
1328
1421
|
Typography5,
|
|
1329
1422
|
{
|
|
1330
1423
|
variant: "caption",
|
|
@@ -1368,25 +1461,23 @@ var init_TeamUsage = __esm({
|
|
|
1368
1461
|
|
|
1369
1462
|
// src/components/AuditLog.tsx
|
|
1370
1463
|
import React6, { useState as useState5, useCallback } from "react";
|
|
1371
|
-
import
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
Alert as Alert2
|
|
1389
|
-
} from "@mui/material";
|
|
1464
|
+
import Paper5 from "@mui/material/Paper";
|
|
1465
|
+
import Box6 from "@mui/material/Box";
|
|
1466
|
+
import Typography6 from "@mui/material/Typography";
|
|
1467
|
+
import Table4 from "@mui/material/Table";
|
|
1468
|
+
import TableBody4 from "@mui/material/TableBody";
|
|
1469
|
+
import TableCell4 from "@mui/material/TableCell";
|
|
1470
|
+
import TableContainer4 from "@mui/material/TableContainer";
|
|
1471
|
+
import TableHead4 from "@mui/material/TableHead";
|
|
1472
|
+
import TableRow4 from "@mui/material/TableRow";
|
|
1473
|
+
import TablePagination from "@mui/material/TablePagination";
|
|
1474
|
+
import TextField3 from "@mui/material/TextField";
|
|
1475
|
+
import MenuItem3 from "@mui/material/MenuItem";
|
|
1476
|
+
import Chip5 from "@mui/material/Chip";
|
|
1477
|
+
import CircularProgress4 from "@mui/material/CircularProgress";
|
|
1478
|
+
import Collapse2 from "@mui/material/Collapse";
|
|
1479
|
+
import IconButton4 from "@mui/material/IconButton";
|
|
1480
|
+
import Alert2 from "@mui/material/Alert";
|
|
1390
1481
|
import { KeyboardArrowDown, KeyboardArrowUp } from "@mui/icons-material";
|
|
1391
1482
|
import { useAsync } from "react-use";
|
|
1392
1483
|
function actionColor(action) {
|
|
@@ -1403,6 +1494,15 @@ function formatDateTime(iso) {
|
|
|
1403
1494
|
return iso;
|
|
1404
1495
|
}
|
|
1405
1496
|
}
|
|
1497
|
+
function renderAuditLogBody(loading, entries) {
|
|
1498
|
+
if (loading) {
|
|
1499
|
+
return /* @__PURE__ */ React6.createElement(TableRow4, null, /* @__PURE__ */ React6.createElement(TableCell4, { colSpan: 6, align: "center", sx: { py: 4 } }, /* @__PURE__ */ React6.createElement(CircularProgress4, { size: 24 })));
|
|
1500
|
+
}
|
|
1501
|
+
if (entries.length === 0) {
|
|
1502
|
+
return /* @__PURE__ */ React6.createElement(TableRow4, null, /* @__PURE__ */ React6.createElement(TableCell4, { colSpan: 6, align: "center", sx: { py: 4 } }, /* @__PURE__ */ React6.createElement(Typography6, { color: "text.secondary" }, "No audit events found")));
|
|
1503
|
+
}
|
|
1504
|
+
return entries.map((entry) => /* @__PURE__ */ React6.createElement(DetailRow, { key: entry.id, entry }));
|
|
1505
|
+
}
|
|
1406
1506
|
var ACTION_COLORS, DetailRow, AuditLog;
|
|
1407
1507
|
var init_AuditLog = __esm({
|
|
1408
1508
|
"src/components/AuditLog.tsx"() {
|
|
@@ -1480,7 +1580,7 @@ var init_AuditLog = __esm({
|
|
|
1480
1580
|
},
|
|
1481
1581
|
sx: { minWidth: 200 }
|
|
1482
1582
|
}
|
|
1483
|
-
)), error && /* @__PURE__ */ React6.createElement(Box6, { px: 2, pb: 2 }, /* @__PURE__ */ React6.createElement(Alert2, { severity: "error" }, error.message)), /* @__PURE__ */ React6.createElement(TableContainer4, null, /* @__PURE__ */ React6.createElement(Table4, { size: "small" }, /* @__PURE__ */ React6.createElement(TableHead4, null, /* @__PURE__ */ React6.createElement(TableRow4, null, /* @__PURE__ */ React6.createElement(TableCell4, { sx: { width: 40 } }), /* @__PURE__ */ React6.createElement(TableCell4, null, "Time"), /* @__PURE__ */ React6.createElement(TableCell4, null, "Action"), /* @__PURE__ */ React6.createElement(TableCell4, null, "Table"), /* @__PURE__ */ React6.createElement(TableCell4, null, "Object ID"), /* @__PURE__ */ React6.createElement(TableCell4, null, "Changed By"))), /* @__PURE__ */ React6.createElement(TableBody4, null, loading
|
|
1583
|
+
)), error && /* @__PURE__ */ React6.createElement(Box6, { px: 2, pb: 2 }, /* @__PURE__ */ React6.createElement(Alert2, { severity: "error" }, error.message)), /* @__PURE__ */ React6.createElement(TableContainer4, null, /* @__PURE__ */ React6.createElement(Table4, { size: "small" }, /* @__PURE__ */ React6.createElement(TableHead4, null, /* @__PURE__ */ React6.createElement(TableRow4, null, /* @__PURE__ */ React6.createElement(TableCell4, { sx: { width: 40 } }), /* @__PURE__ */ React6.createElement(TableCell4, null, "Time"), /* @__PURE__ */ React6.createElement(TableCell4, null, "Action"), /* @__PURE__ */ React6.createElement(TableCell4, null, "Table"), /* @__PURE__ */ React6.createElement(TableCell4, null, "Object ID"), /* @__PURE__ */ React6.createElement(TableCell4, null, "Changed By"))), /* @__PURE__ */ React6.createElement(TableBody4, null, renderAuditLogBody(loading, entries)))), /* @__PURE__ */ React6.createElement(
|
|
1484
1584
|
TablePagination,
|
|
1485
1585
|
{
|
|
1486
1586
|
component: "div",
|
|
@@ -1505,7 +1605,14 @@ __export(LiteLLMPage_exports, {
|
|
|
1505
1605
|
LiteLLMPage: () => LiteLLMPage
|
|
1506
1606
|
});
|
|
1507
1607
|
import React7, { useState as useState6, useCallback as useCallback2, useMemo as useMemo5 } from "react";
|
|
1508
|
-
import
|
|
1608
|
+
import Box7 from "@mui/material/Box";
|
|
1609
|
+
import Snackbar from "@mui/material/Snackbar";
|
|
1610
|
+
import Alert3 from "@mui/material/Alert";
|
|
1611
|
+
import CircularProgress5 from "@mui/material/CircularProgress";
|
|
1612
|
+
import Typography7 from "@mui/material/Typography";
|
|
1613
|
+
import Paper6 from "@mui/material/Paper";
|
|
1614
|
+
import Tabs3 from "@mui/material/Tabs";
|
|
1615
|
+
import Tab3 from "@mui/material/Tab";
|
|
1509
1616
|
import { useAsync as useAsync2, useAsyncRetry } from "react-use";
|
|
1510
1617
|
import { useApi } from "@backstage/core-plugin-api";
|
|
1511
1618
|
function initDateRange() {
|
|
@@ -1843,17 +1950,15 @@ init_TeamUsage();
|
|
|
1843
1950
|
init_api();
|
|
1844
1951
|
init_format();
|
|
1845
1952
|
import React9, { useState as useState7, useEffect as useEffect2 } from "react";
|
|
1846
|
-
import
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
Alert as Alert4
|
|
1856
|
-
} from "@mui/material";
|
|
1953
|
+
import Paper7 from "@mui/material/Paper";
|
|
1954
|
+
import Box8 from "@mui/material/Box";
|
|
1955
|
+
import Typography8 from "@mui/material/Typography";
|
|
1956
|
+
import FormControl2 from "@mui/material/FormControl";
|
|
1957
|
+
import Select2 from "@mui/material/Select";
|
|
1958
|
+
import MenuItem4 from "@mui/material/MenuItem";
|
|
1959
|
+
import Grid2 from "@mui/material/Grid";
|
|
1960
|
+
import CircularProgress6 from "@mui/material/CircularProgress";
|
|
1961
|
+
import Alert4 from "@mui/material/Alert";
|
|
1857
1962
|
import { AreaChart as AreaChart3, Area as Area3, ResponsiveContainer as ResponsiveContainer3 } from "recharts";
|
|
1858
1963
|
import { useApi as useApi2 } from "@backstage/core-plugin-api";
|
|
1859
1964
|
function presetToDateRange(preset) {
|