@acarmisc/backstage-plugin-litellm 0.12.4 → 0.14.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/dist/components/DashboardHeader.d.ts +2 -0
- package/dist/components/ui.d.ts +186 -0
- package/dist/index.cjs.js +1613 -579
- package/dist/index.cjs.js.map +4 -4
- package/dist/index.esm.js +1550 -515
- package/dist/index.esm.js.map +4 -4
- package/dist/types.d.ts +2 -0
- package/package.json +1 -1
package/dist/index.esm.js
CHANGED
|
@@ -152,79 +152,682 @@ var init_api = __esm({
|
|
|
152
152
|
}
|
|
153
153
|
});
|
|
154
154
|
|
|
155
|
-
// src/components/
|
|
156
|
-
import React
|
|
155
|
+
// src/components/ui.tsx
|
|
156
|
+
import React from "react";
|
|
157
157
|
import Box from "@mui/material/Box";
|
|
158
|
-
import
|
|
159
|
-
import LinearProgress from "@mui/material/LinearProgress";
|
|
158
|
+
import ButtonBase from "@mui/material/ButtonBase";
|
|
160
159
|
import Paper from "@mui/material/Paper";
|
|
161
|
-
import
|
|
160
|
+
import Typography from "@mui/material/Typography";
|
|
161
|
+
import { alpha, useTheme } from "@mui/material/styles";
|
|
162
|
+
function chartColor(name) {
|
|
163
|
+
if (name === "Other") return OTHER_COLOR;
|
|
164
|
+
let h = 5381;
|
|
165
|
+
for (let i = 0; i < name.length; i++) h = (h << 5) + h + name.charCodeAt(i) >>> 0;
|
|
166
|
+
return CHART_COLORS[h % CHART_COLORS.length];
|
|
167
|
+
}
|
|
168
|
+
function useChartTheme() {
|
|
169
|
+
const theme = useTheme();
|
|
170
|
+
const tickFill = theme.palette.text.secondary;
|
|
171
|
+
return {
|
|
172
|
+
grid: {
|
|
173
|
+
stroke: alpha(theme.palette.text.primary, theme.palette.mode === "dark" ? 0.12 : 0.08),
|
|
174
|
+
strokeDasharray: "2 4",
|
|
175
|
+
vertical: false
|
|
176
|
+
},
|
|
177
|
+
axis: {
|
|
178
|
+
tick: { fontSize: 11, fill: tickFill },
|
|
179
|
+
tickLine: false,
|
|
180
|
+
axisLine: { stroke: alpha(theme.palette.text.primary, 0.12) }
|
|
181
|
+
},
|
|
182
|
+
legend: {
|
|
183
|
+
wrapperStyle: {
|
|
184
|
+
fontSize: 11,
|
|
185
|
+
color: tickFill,
|
|
186
|
+
paddingTop: 8
|
|
187
|
+
},
|
|
188
|
+
iconType: "circle",
|
|
189
|
+
iconSize: 8
|
|
190
|
+
},
|
|
191
|
+
cursor: { fill: alpha(theme.palette.text.primary, 0.05) }
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
function toneColor(theme, tone) {
|
|
195
|
+
switch (tone) {
|
|
196
|
+
case "success":
|
|
197
|
+
return theme.palette.success.main;
|
|
198
|
+
case "warning":
|
|
199
|
+
return theme.palette.warning.main;
|
|
200
|
+
case "danger":
|
|
201
|
+
return theme.palette.error.main;
|
|
202
|
+
case "info":
|
|
203
|
+
return theme.palette.info.main;
|
|
204
|
+
case "accent":
|
|
205
|
+
return theme.palette.primary.main;
|
|
206
|
+
default:
|
|
207
|
+
return theme.palette.text.secondary;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
function SegmentedControl({
|
|
211
|
+
value,
|
|
212
|
+
onChange,
|
|
213
|
+
options
|
|
214
|
+
}) {
|
|
215
|
+
return /* @__PURE__ */ React.createElement(
|
|
216
|
+
Box,
|
|
217
|
+
{
|
|
218
|
+
role: "tablist",
|
|
219
|
+
sx: (theme) => ({
|
|
220
|
+
display: "inline-flex",
|
|
221
|
+
gap: 0.25,
|
|
222
|
+
p: 0.375,
|
|
223
|
+
borderRadius: 1.5,
|
|
224
|
+
border: "1px solid",
|
|
225
|
+
borderColor: theme.palette.divider
|
|
226
|
+
})
|
|
227
|
+
},
|
|
228
|
+
options.map((opt) => {
|
|
229
|
+
const selected = opt.value === value;
|
|
230
|
+
return /* @__PURE__ */ React.createElement(
|
|
231
|
+
ButtonBase,
|
|
232
|
+
{
|
|
233
|
+
key: opt.value,
|
|
234
|
+
role: "tab",
|
|
235
|
+
"aria-selected": selected,
|
|
236
|
+
onClick: () => onChange(opt.value),
|
|
237
|
+
sx: (theme) => ({
|
|
238
|
+
px: 1.5,
|
|
239
|
+
height: 28,
|
|
240
|
+
borderRadius: 1,
|
|
241
|
+
fontSize: 13,
|
|
242
|
+
fontWeight: selected ? 700 : 500,
|
|
243
|
+
// Tint the selection with the accent rather than swapping in a
|
|
244
|
+
// surface colour: on a dark theme `background.paper` sits *behind*
|
|
245
|
+
// the track, so the selected chip would read as recessed.
|
|
246
|
+
color: selected ? theme.palette.primary.main : theme.palette.text.secondary,
|
|
247
|
+
bgcolor: selected ? alpha(theme.palette.primary.main, theme.palette.mode === "dark" ? 0.2 : 0.11) : "transparent",
|
|
248
|
+
transition: theme.transitions.create(["background-color", "color"]),
|
|
249
|
+
"&:hover": {
|
|
250
|
+
color: selected ? theme.palette.primary.main : theme.palette.text.primary,
|
|
251
|
+
bgcolor: selected ? alpha(theme.palette.primary.main, theme.palette.mode === "dark" ? 0.26 : 0.15) : alpha(theme.palette.text.primary, 0.05)
|
|
252
|
+
}
|
|
253
|
+
})
|
|
254
|
+
},
|
|
255
|
+
opt.label
|
|
256
|
+
);
|
|
257
|
+
})
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
var MONTHS, fmtDateShort, fmtCompact, fmtUsdCompact, CHART_COLORS, SERIES, OTHER_COLOR, ChartTooltip, SeriesLegend, StatusPill, TagChip, SectionCard, ChartCard, MetricStrip, Stat, Meter, dataTableSx, quietIconButtonSx, EmptyState;
|
|
261
|
+
var init_ui = __esm({
|
|
262
|
+
"src/components/ui.tsx"() {
|
|
263
|
+
"use strict";
|
|
264
|
+
MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
265
|
+
fmtDateShort = (value) => {
|
|
266
|
+
const m = /^(\d{4})-(\d{2})-(\d{2})/.exec(value ?? "");
|
|
267
|
+
if (!m) return value;
|
|
268
|
+
return `${MONTHS[Number(m[2]) - 1]} ${Number(m[3])}`;
|
|
269
|
+
};
|
|
270
|
+
fmtCompact = (n) => {
|
|
271
|
+
const v = n ?? 0;
|
|
272
|
+
const abs = Math.abs(v);
|
|
273
|
+
if (abs >= 1e9) return `${(v / 1e9).toFixed(abs >= 1e10 ? 0 : 1)}B`;
|
|
274
|
+
if (abs >= 1e6) return `${(v / 1e6).toFixed(abs >= 1e7 ? 0 : 1)}M`;
|
|
275
|
+
if (abs >= 1e3) return `${(v / 1e3).toFixed(abs >= 1e4 ? 0 : 1)}K`;
|
|
276
|
+
return String(Math.round(v));
|
|
277
|
+
};
|
|
278
|
+
fmtUsdCompact = (n) => {
|
|
279
|
+
const v = n ?? 0;
|
|
280
|
+
if (Math.abs(v) >= 1e3) return `$${fmtCompact(v)}`;
|
|
281
|
+
if (Math.abs(v) >= 10) return `$${v.toFixed(0)}`;
|
|
282
|
+
return `$${v.toFixed(2)}`;
|
|
283
|
+
};
|
|
284
|
+
CHART_COLORS = [
|
|
285
|
+
"#6366F1",
|
|
286
|
+
// indigo
|
|
287
|
+
"#14B8A6",
|
|
288
|
+
// teal
|
|
289
|
+
"#F59E0B",
|
|
290
|
+
// amber
|
|
291
|
+
"#EC4899",
|
|
292
|
+
// pink
|
|
293
|
+
"#38BDF8",
|
|
294
|
+
// sky
|
|
295
|
+
"#84CC16",
|
|
296
|
+
// lime
|
|
297
|
+
"#F97316",
|
|
298
|
+
// orange
|
|
299
|
+
"#A855F7"
|
|
300
|
+
// violet
|
|
301
|
+
];
|
|
302
|
+
SERIES = {
|
|
303
|
+
input: "#6366F1",
|
|
304
|
+
output: "#14B8A6",
|
|
305
|
+
success: "#10B981",
|
|
306
|
+
failure: "#EF4444",
|
|
307
|
+
spend: "#F59E0B",
|
|
308
|
+
budget: "#EF4444"
|
|
309
|
+
};
|
|
310
|
+
OTHER_COLOR = "#94A3B8";
|
|
311
|
+
ChartTooltip = ({
|
|
312
|
+
active,
|
|
313
|
+
label,
|
|
314
|
+
payload,
|
|
315
|
+
valueFormatter = (v) => v.toLocaleString(),
|
|
316
|
+
labelFormatter = fmtDateShort,
|
|
317
|
+
hideZero = false
|
|
318
|
+
}) => {
|
|
319
|
+
if (!active || !payload?.length) return null;
|
|
320
|
+
const rows = payload.filter(
|
|
321
|
+
(p) => p.value !== void 0 && p.value !== null && (!hideZero || p.value !== 0)
|
|
322
|
+
);
|
|
323
|
+
if (!rows.length) return null;
|
|
324
|
+
return /* @__PURE__ */ React.createElement(
|
|
325
|
+
Paper,
|
|
326
|
+
{
|
|
327
|
+
elevation: 8,
|
|
328
|
+
sx: {
|
|
329
|
+
px: 1.5,
|
|
330
|
+
py: 1,
|
|
331
|
+
borderRadius: 1.5,
|
|
332
|
+
border: "1px solid",
|
|
333
|
+
borderColor: "divider",
|
|
334
|
+
minWidth: 150,
|
|
335
|
+
pointerEvents: "none"
|
|
336
|
+
}
|
|
337
|
+
},
|
|
338
|
+
/* @__PURE__ */ React.createElement(
|
|
339
|
+
Typography,
|
|
340
|
+
{
|
|
341
|
+
variant: "caption",
|
|
342
|
+
sx: { display: "block", fontWeight: 700, mb: 0.75, letterSpacing: "0.02em" }
|
|
343
|
+
},
|
|
344
|
+
typeof label === "string" ? labelFormatter(label) : label
|
|
345
|
+
),
|
|
346
|
+
rows.map((row, i) => /* @__PURE__ */ React.createElement(
|
|
347
|
+
Box,
|
|
348
|
+
{
|
|
349
|
+
key: `${row.dataKey ?? row.name ?? i}`,
|
|
350
|
+
sx: { display: "flex", alignItems: "center", gap: 1, mt: i === 0 ? 0 : 0.5 }
|
|
351
|
+
},
|
|
352
|
+
/* @__PURE__ */ React.createElement(
|
|
353
|
+
Box,
|
|
354
|
+
{
|
|
355
|
+
sx: {
|
|
356
|
+
width: 8,
|
|
357
|
+
height: 8,
|
|
358
|
+
borderRadius: "50%",
|
|
359
|
+
bgcolor: row.color,
|
|
360
|
+
flexShrink: 0
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
),
|
|
364
|
+
/* @__PURE__ */ React.createElement(
|
|
365
|
+
Typography,
|
|
366
|
+
{
|
|
367
|
+
variant: "caption",
|
|
368
|
+
color: "text.secondary",
|
|
369
|
+
sx: { flex: 1, mr: 1.5, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }
|
|
370
|
+
},
|
|
371
|
+
row.name
|
|
372
|
+
),
|
|
373
|
+
/* @__PURE__ */ React.createElement(Typography, { variant: "caption", sx: { fontWeight: 700, fontVariantNumeric: "tabular-nums" } }, valueFormatter(row.value))
|
|
374
|
+
))
|
|
375
|
+
);
|
|
376
|
+
};
|
|
377
|
+
SeriesLegend = ({
|
|
378
|
+
series
|
|
379
|
+
}) => /* @__PURE__ */ React.createElement(
|
|
380
|
+
Box,
|
|
381
|
+
{
|
|
382
|
+
sx: {
|
|
383
|
+
display: "flex",
|
|
384
|
+
flexWrap: "wrap",
|
|
385
|
+
gap: 0.5,
|
|
386
|
+
columnGap: 2,
|
|
387
|
+
rowGap: 0.5,
|
|
388
|
+
mt: 1.5
|
|
389
|
+
}
|
|
390
|
+
},
|
|
391
|
+
series.map((s) => /* @__PURE__ */ React.createElement(Box, { key: s.name, sx: { display: "flex", alignItems: "center", gap: 0.75, minWidth: 0 } }, /* @__PURE__ */ React.createElement(
|
|
392
|
+
Box,
|
|
393
|
+
{
|
|
394
|
+
sx: { width: 8, height: 8, borderRadius: "50%", bgcolor: s.color, flexShrink: 0 }
|
|
395
|
+
}
|
|
396
|
+
), /* @__PURE__ */ React.createElement(
|
|
397
|
+
Typography,
|
|
398
|
+
{
|
|
399
|
+
variant: "caption",
|
|
400
|
+
color: "text.secondary",
|
|
401
|
+
title: s.name,
|
|
402
|
+
sx: {
|
|
403
|
+
fontSize: 11,
|
|
404
|
+
maxWidth: 190,
|
|
405
|
+
overflow: "hidden",
|
|
406
|
+
textOverflow: "ellipsis",
|
|
407
|
+
whiteSpace: "nowrap"
|
|
408
|
+
}
|
|
409
|
+
},
|
|
410
|
+
s.name
|
|
411
|
+
)))
|
|
412
|
+
);
|
|
413
|
+
StatusPill = ({ label, tone = "neutral", dot = true, title, sx }) => /* @__PURE__ */ React.createElement(
|
|
414
|
+
Box,
|
|
415
|
+
{
|
|
416
|
+
component: "span",
|
|
417
|
+
title,
|
|
418
|
+
sx: [
|
|
419
|
+
(theme) => {
|
|
420
|
+
const c = toneColor(theme, tone);
|
|
421
|
+
return {
|
|
422
|
+
display: "inline-flex",
|
|
423
|
+
alignItems: "center",
|
|
424
|
+
gap: 0.625,
|
|
425
|
+
height: 22,
|
|
426
|
+
px: 0.875,
|
|
427
|
+
borderRadius: "6px",
|
|
428
|
+
border: "1px solid",
|
|
429
|
+
borderColor: alpha(c, tone === "neutral" ? 0.25 : 0.35),
|
|
430
|
+
bgcolor: alpha(c, tone === "neutral" ? 0.06 : 0.12),
|
|
431
|
+
color: tone === "neutral" ? theme.palette.text.secondary : c,
|
|
432
|
+
fontSize: 11.5,
|
|
433
|
+
fontWeight: 600,
|
|
434
|
+
lineHeight: 1,
|
|
435
|
+
letterSpacing: "0.01em",
|
|
436
|
+
whiteSpace: "nowrap",
|
|
437
|
+
maxWidth: "100%"
|
|
438
|
+
};
|
|
439
|
+
},
|
|
440
|
+
...Array.isArray(sx) ? sx : [sx]
|
|
441
|
+
]
|
|
442
|
+
},
|
|
443
|
+
dot && /* @__PURE__ */ React.createElement(
|
|
444
|
+
Box,
|
|
445
|
+
{
|
|
446
|
+
component: "span",
|
|
447
|
+
sx: (theme) => ({
|
|
448
|
+
width: 6,
|
|
449
|
+
height: 6,
|
|
450
|
+
borderRadius: "50%",
|
|
451
|
+
bgcolor: toneColor(theme, tone),
|
|
452
|
+
flexShrink: 0
|
|
453
|
+
})
|
|
454
|
+
}
|
|
455
|
+
),
|
|
456
|
+
/* @__PURE__ */ React.createElement(Box, { component: "span", sx: { overflow: "hidden", textOverflow: "ellipsis" } }, label)
|
|
457
|
+
);
|
|
458
|
+
TagChip = ({
|
|
459
|
+
label,
|
|
460
|
+
title,
|
|
461
|
+
mono = true
|
|
462
|
+
}) => /* @__PURE__ */ React.createElement(
|
|
463
|
+
Box,
|
|
464
|
+
{
|
|
465
|
+
component: "span",
|
|
466
|
+
title,
|
|
467
|
+
sx: (theme) => ({
|
|
468
|
+
display: "inline-flex",
|
|
469
|
+
alignItems: "center",
|
|
470
|
+
height: 20,
|
|
471
|
+
px: 0.75,
|
|
472
|
+
borderRadius: "5px",
|
|
473
|
+
bgcolor: alpha(theme.palette.text.primary, theme.palette.mode === "dark" ? 0.08 : 0.05),
|
|
474
|
+
color: theme.palette.text.secondary,
|
|
475
|
+
fontFamily: mono ? "ui-monospace, SFMono-Regular, Menlo, monospace" : void 0,
|
|
476
|
+
fontSize: 11,
|
|
477
|
+
lineHeight: 1,
|
|
478
|
+
whiteSpace: "nowrap",
|
|
479
|
+
maxWidth: 220,
|
|
480
|
+
overflow: "hidden",
|
|
481
|
+
textOverflow: "ellipsis"
|
|
482
|
+
})
|
|
483
|
+
},
|
|
484
|
+
label
|
|
485
|
+
);
|
|
486
|
+
SectionCard = ({ title, subtitle, actions, flush = false, children }) => /* @__PURE__ */ React.createElement(Paper, { sx: { borderRadius: 2, overflow: "hidden" } }, (title || actions) && /* @__PURE__ */ React.createElement(
|
|
487
|
+
Box,
|
|
488
|
+
{
|
|
489
|
+
sx: {
|
|
490
|
+
display: "flex",
|
|
491
|
+
alignItems: "center",
|
|
492
|
+
justifyContent: "space-between",
|
|
493
|
+
gap: 2,
|
|
494
|
+
flexWrap: "wrap",
|
|
495
|
+
px: 2.5,
|
|
496
|
+
py: 2
|
|
497
|
+
}
|
|
498
|
+
},
|
|
499
|
+
/* @__PURE__ */ React.createElement(Box, { sx: { minWidth: 0 } }, /* @__PURE__ */ React.createElement(Typography, { variant: "h6", sx: { lineHeight: 1.2 } }, title), subtitle && /* @__PURE__ */ React.createElement(Typography, { variant: "caption", color: "text.secondary" }, subtitle)),
|
|
500
|
+
actions && /* @__PURE__ */ React.createElement(Box, { sx: { display: "flex", alignItems: "center", gap: 1, flexWrap: "wrap" } }, actions)
|
|
501
|
+
), /* @__PURE__ */ React.createElement(Box, { sx: flush ? void 0 : { px: 2.5, pb: 2.5, pt: title ? 0 : 2.5 } }, children));
|
|
502
|
+
ChartCard = ({ title, meta, height = 240, children }) => (
|
|
503
|
+
// The plot area must have an explicit height, never a flex-derived one:
|
|
504
|
+
// Recharts' ResponsiveContainer measures its parent, so a parent that sizes
|
|
505
|
+
// itself from its children instead spins in a measure/resize loop.
|
|
506
|
+
/* @__PURE__ */ React.createElement(Paper, { variant: "outlined", sx: { p: 2, borderRadius: 2, height: "100%" } }, /* @__PURE__ */ React.createElement(Box, { sx: { display: "flex", alignItems: "baseline", gap: 1, mb: 1.5, flexWrap: "wrap" } }, /* @__PURE__ */ React.createElement(
|
|
507
|
+
Typography,
|
|
508
|
+
{
|
|
509
|
+
variant: "subtitle2",
|
|
510
|
+
sx: { fontSize: 12, fontWeight: 700, letterSpacing: "0.06em", textTransform: "uppercase" }
|
|
511
|
+
},
|
|
512
|
+
title
|
|
513
|
+
), meta && /* @__PURE__ */ React.createElement(Typography, { variant: "caption", color: "text.secondary" }, meta)), /* @__PURE__ */ React.createElement(Box, { sx: { height, width: "100%" } }, children))
|
|
514
|
+
);
|
|
515
|
+
MetricStrip = ({ metrics }) => /* @__PURE__ */ React.createElement(
|
|
516
|
+
Paper,
|
|
517
|
+
{
|
|
518
|
+
variant: "outlined",
|
|
519
|
+
sx: {
|
|
520
|
+
borderRadius: 2,
|
|
521
|
+
display: "grid",
|
|
522
|
+
gridTemplateColumns: { xs: "1fr 1fr", md: `repeat(${metrics.length}, 1fr)` },
|
|
523
|
+
overflow: "hidden"
|
|
524
|
+
}
|
|
525
|
+
},
|
|
526
|
+
metrics.map((m, i) => /* @__PURE__ */ React.createElement(
|
|
527
|
+
Box,
|
|
528
|
+
{
|
|
529
|
+
key: m.label,
|
|
530
|
+
sx: (theme) => ({
|
|
531
|
+
px: 2.5,
|
|
532
|
+
py: 2,
|
|
533
|
+
borderLeft: i === 0 ? 0 : "1px solid",
|
|
534
|
+
borderTop: 0,
|
|
535
|
+
borderColor: "divider",
|
|
536
|
+
[theme.breakpoints.down("md")]: {
|
|
537
|
+
borderLeft: i % 2 === 0 ? 0 : "1px solid",
|
|
538
|
+
borderTop: i < 2 ? 0 : "1px solid",
|
|
539
|
+
borderColor: "divider"
|
|
540
|
+
}
|
|
541
|
+
})
|
|
542
|
+
},
|
|
543
|
+
/* @__PURE__ */ React.createElement(Box, { sx: { display: "flex", alignItems: "center", gap: 0.875, mb: 0.75 } }, /* @__PURE__ */ React.createElement(
|
|
544
|
+
Box,
|
|
545
|
+
{
|
|
546
|
+
sx: (theme) => ({
|
|
547
|
+
width: 6,
|
|
548
|
+
height: 6,
|
|
549
|
+
borderRadius: "50%",
|
|
550
|
+
bgcolor: toneColor(theme, m.tone ?? "accent"),
|
|
551
|
+
flexShrink: 0
|
|
552
|
+
})
|
|
553
|
+
}
|
|
554
|
+
), /* @__PURE__ */ React.createElement(
|
|
555
|
+
Typography,
|
|
556
|
+
{
|
|
557
|
+
variant: "caption",
|
|
558
|
+
color: "text.secondary",
|
|
559
|
+
sx: { fontSize: 11, fontWeight: 600, letterSpacing: "0.07em", textTransform: "uppercase" }
|
|
560
|
+
},
|
|
561
|
+
m.label
|
|
562
|
+
)),
|
|
563
|
+
/* @__PURE__ */ React.createElement(
|
|
564
|
+
Typography,
|
|
565
|
+
{
|
|
566
|
+
sx: { fontSize: 26, fontWeight: 700, lineHeight: 1.15, fontVariantNumeric: "tabular-nums" }
|
|
567
|
+
},
|
|
568
|
+
m.value
|
|
569
|
+
),
|
|
570
|
+
/* @__PURE__ */ React.createElement(
|
|
571
|
+
Typography,
|
|
572
|
+
{
|
|
573
|
+
variant: "caption",
|
|
574
|
+
color: "text.secondary",
|
|
575
|
+
sx: { display: "block", mt: 0.5, minHeight: 16 }
|
|
576
|
+
},
|
|
577
|
+
m.hint ?? "\xA0"
|
|
578
|
+
)
|
|
579
|
+
))
|
|
580
|
+
);
|
|
581
|
+
Stat = ({ label, value }) => /* @__PURE__ */ React.createElement(Box, { sx: { minWidth: 0 } }, /* @__PURE__ */ React.createElement(
|
|
582
|
+
Typography,
|
|
583
|
+
{
|
|
584
|
+
variant: "caption",
|
|
585
|
+
color: "text.secondary",
|
|
586
|
+
sx: { display: "block", fontSize: 10.5, fontWeight: 600, letterSpacing: "0.07em", textTransform: "uppercase" }
|
|
587
|
+
},
|
|
588
|
+
label
|
|
589
|
+
), /* @__PURE__ */ React.createElement(
|
|
590
|
+
Typography,
|
|
591
|
+
{
|
|
592
|
+
variant: "body2",
|
|
593
|
+
sx: { fontWeight: 600, fontVariantNumeric: "tabular-nums", mt: 0.25 }
|
|
594
|
+
},
|
|
595
|
+
value
|
|
596
|
+
));
|
|
597
|
+
Meter = ({
|
|
598
|
+
value,
|
|
599
|
+
tone = "accent",
|
|
600
|
+
height = 5
|
|
601
|
+
}) => /* @__PURE__ */ React.createElement(
|
|
602
|
+
Box,
|
|
603
|
+
{
|
|
604
|
+
sx: (theme) => ({
|
|
605
|
+
height,
|
|
606
|
+
borderRadius: height,
|
|
607
|
+
bgcolor: alpha(theme.palette.text.primary, theme.palette.mode === "dark" ? 0.12 : 0.08),
|
|
608
|
+
overflow: "hidden"
|
|
609
|
+
})
|
|
610
|
+
},
|
|
611
|
+
/* @__PURE__ */ React.createElement(
|
|
612
|
+
Box,
|
|
613
|
+
{
|
|
614
|
+
sx: (theme) => ({
|
|
615
|
+
height: "100%",
|
|
616
|
+
width: `${Math.max(0, Math.min(100, value))}%`,
|
|
617
|
+
borderRadius: height,
|
|
618
|
+
bgcolor: toneColor(theme, tone),
|
|
619
|
+
transition: theme.transitions.create("width")
|
|
620
|
+
})
|
|
621
|
+
}
|
|
622
|
+
)
|
|
623
|
+
);
|
|
624
|
+
dataTableSx = (theme) => ({
|
|
625
|
+
// Element selectors, not `.MuiTableCell-head`: host apps may set a MUI
|
|
626
|
+
// classname prefix (Backstage ships `v5-`), which breaks global class targeting.
|
|
627
|
+
"& thead th": {
|
|
628
|
+
fontSize: 11,
|
|
629
|
+
fontWeight: 700,
|
|
630
|
+
letterSpacing: "0.07em",
|
|
631
|
+
textTransform: "uppercase",
|
|
632
|
+
color: theme.palette.text.secondary,
|
|
633
|
+
backgroundColor: alpha(theme.palette.text.primary, theme.palette.mode === "dark" ? 0.04 : 0.02),
|
|
634
|
+
borderBottom: `1px solid ${theme.palette.divider}`,
|
|
635
|
+
whiteSpace: "nowrap",
|
|
636
|
+
paddingTop: theme.spacing(1.25),
|
|
637
|
+
paddingBottom: theme.spacing(1.25)
|
|
638
|
+
},
|
|
639
|
+
"& tbody td": {
|
|
640
|
+
borderBottom: `1px solid ${alpha(theme.palette.divider, 0.6)}`,
|
|
641
|
+
paddingTop: theme.spacing(1.25),
|
|
642
|
+
paddingBottom: theme.spacing(1.25),
|
|
643
|
+
fontVariantNumeric: "tabular-nums"
|
|
644
|
+
},
|
|
645
|
+
"& tbody tr:last-of-type td": {
|
|
646
|
+
borderBottom: 0
|
|
647
|
+
},
|
|
648
|
+
"& tbody tr:hover": {
|
|
649
|
+
backgroundColor: alpha(theme.palette.text.primary, 0.03)
|
|
650
|
+
}
|
|
651
|
+
});
|
|
652
|
+
quietIconButtonSx = (tone = "neutral") => (theme) => ({
|
|
653
|
+
color: theme.palette.text.secondary,
|
|
654
|
+
"&:hover": {
|
|
655
|
+
color: toneColor(theme, tone),
|
|
656
|
+
bgcolor: alpha(toneColor(theme, tone), 0.1)
|
|
657
|
+
}
|
|
658
|
+
});
|
|
659
|
+
EmptyState = ({ message, hint, height, action }) => /* @__PURE__ */ React.createElement(
|
|
660
|
+
Box,
|
|
661
|
+
{
|
|
662
|
+
sx: {
|
|
663
|
+
height,
|
|
664
|
+
minHeight: height ? void 0 : 120,
|
|
665
|
+
display: "flex",
|
|
666
|
+
flexDirection: "column",
|
|
667
|
+
alignItems: "center",
|
|
668
|
+
justifyContent: "center",
|
|
669
|
+
gap: 1,
|
|
670
|
+
textAlign: "center",
|
|
671
|
+
py: 3
|
|
672
|
+
}
|
|
673
|
+
},
|
|
674
|
+
/* @__PURE__ */ React.createElement(Typography, { variant: "body2", color: "text.secondary" }, message),
|
|
675
|
+
hint && /* @__PURE__ */ React.createElement(Typography, { variant: "caption", color: "text.secondary" }, hint),
|
|
676
|
+
action
|
|
677
|
+
);
|
|
678
|
+
}
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
// src/components/DashboardHeader.tsx
|
|
682
|
+
import React2, { useMemo } from "react";
|
|
683
|
+
import Box2 from "@mui/material/Box";
|
|
684
|
+
import Typography2 from "@mui/material/Typography";
|
|
685
|
+
import Skeleton from "@mui/material/Skeleton";
|
|
686
|
+
import Paper2 from "@mui/material/Paper";
|
|
162
687
|
import Button from "@mui/material/Button";
|
|
163
|
-
import
|
|
164
|
-
import {
|
|
165
|
-
|
|
688
|
+
import Divider from "@mui/material/Divider";
|
|
689
|
+
import { alpha as alpha2 } from "@mui/material/styles";
|
|
690
|
+
import { Add } from "@mui/icons-material";
|
|
691
|
+
function initials(name) {
|
|
692
|
+
const local = name.split("@")[0] ?? name;
|
|
693
|
+
const parts = local.split(/[.\-_\s]+/).filter(Boolean);
|
|
694
|
+
if (parts.length >= 2) return (parts[0][0] + parts[1][0]).toUpperCase();
|
|
695
|
+
return local.slice(0, 2).toUpperCase();
|
|
696
|
+
}
|
|
697
|
+
var CountStat, DashboardHeader;
|
|
166
698
|
var init_DashboardHeader = __esm({
|
|
167
699
|
"src/components/DashboardHeader.tsx"() {
|
|
168
700
|
"use strict";
|
|
169
701
|
init_api();
|
|
702
|
+
init_ui();
|
|
703
|
+
CountStat = ({ value, label, tone }) => {
|
|
704
|
+
const active = value > 0;
|
|
705
|
+
return /* @__PURE__ */ React2.createElement(Box2, { sx: { display: "flex", alignItems: "center", gap: 1 } }, /* @__PURE__ */ React2.createElement(
|
|
706
|
+
Box2,
|
|
707
|
+
{
|
|
708
|
+
sx: (theme) => ({
|
|
709
|
+
width: 7,
|
|
710
|
+
height: 7,
|
|
711
|
+
borderRadius: "50%",
|
|
712
|
+
flexShrink: 0,
|
|
713
|
+
bgcolor: active ? tone === "danger" && theme.palette.error.main || tone === "warning" && theme.palette.warning.main || theme.palette.primary.main : alpha2(theme.palette.text.primary, 0.25)
|
|
714
|
+
})
|
|
715
|
+
}
|
|
716
|
+
), /* @__PURE__ */ React2.createElement(
|
|
717
|
+
Typography2,
|
|
718
|
+
{
|
|
719
|
+
component: "span",
|
|
720
|
+
sx: { fontSize: 15, fontWeight: 700, lineHeight: 1, fontVariantNumeric: "tabular-nums" }
|
|
721
|
+
},
|
|
722
|
+
value
|
|
723
|
+
), /* @__PURE__ */ React2.createElement(
|
|
724
|
+
Typography2,
|
|
725
|
+
{
|
|
726
|
+
component: "span",
|
|
727
|
+
color: "text.secondary",
|
|
728
|
+
sx: { fontSize: 11, fontWeight: 600, letterSpacing: "0.07em", textTransform: "uppercase" }
|
|
729
|
+
},
|
|
730
|
+
label
|
|
731
|
+
));
|
|
732
|
+
};
|
|
170
733
|
DashboardHeader = ({
|
|
171
734
|
userInfo,
|
|
172
735
|
teams,
|
|
173
736
|
keys,
|
|
174
737
|
loading,
|
|
175
|
-
onGenerateKeyClick
|
|
738
|
+
onGenerateKeyClick,
|
|
739
|
+
tabs
|
|
176
740
|
}) => {
|
|
177
741
|
const counts = useMemo(() => {
|
|
178
742
|
const expired = keys.filter((k) => expiryStatus(k.expires_at) === "expired").length;
|
|
179
743
|
const expiringSoon = keys.filter((k) => expiryStatus(k.expires_at) === "soon").length;
|
|
180
744
|
return { total: keys.length, expired, expiringSoon };
|
|
181
745
|
}, [keys]);
|
|
182
|
-
if (loading) {
|
|
183
|
-
return /* @__PURE__ */ React.createElement(Paper, { sx: { p: 2, mb: 2 } }, /* @__PURE__ */ React.createElement(LinearProgress, null));
|
|
184
|
-
}
|
|
185
746
|
const displayName = userInfo.user_email ?? userInfo.email ?? userInfo.user_id;
|
|
186
|
-
return /* @__PURE__ */
|
|
187
|
-
|
|
188
|
-
{
|
|
189
|
-
key: team.team_id,
|
|
190
|
-
label: team.team_alias || team.team_id,
|
|
191
|
-
size: "small",
|
|
192
|
-
variant: "outlined",
|
|
193
|
-
color: "primary"
|
|
194
|
-
}
|
|
195
|
-
)))), /* @__PURE__ */ React.createElement(Box, { display: "flex", alignItems: "center", gap: 2, flexWrap: "wrap" }, /* @__PURE__ */ React.createElement(Stack, { direction: "row", spacing: 1 }, /* @__PURE__ */ React.createElement(Chip, { icon: /* @__PURE__ */ React.createElement(Key, { fontSize: "small" }), label: `${counts.total} keys`, size: "small" }), /* @__PURE__ */ React.createElement(
|
|
196
|
-
Chip,
|
|
197
|
-
{
|
|
198
|
-
icon: /* @__PURE__ */ React.createElement(Warning, { fontSize: "small" }),
|
|
199
|
-
label: `${counts.expired} expired`,
|
|
200
|
-
size: "small",
|
|
201
|
-
color: counts.expired > 0 ? "error" : "default"
|
|
202
|
-
}
|
|
203
|
-
), /* @__PURE__ */ React.createElement(
|
|
204
|
-
Chip,
|
|
747
|
+
return /* @__PURE__ */ React2.createElement(Paper2, { sx: { borderRadius: 2, overflow: "hidden" } }, /* @__PURE__ */ React2.createElement(
|
|
748
|
+
Box2,
|
|
205
749
|
{
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
750
|
+
sx: {
|
|
751
|
+
display: "flex",
|
|
752
|
+
alignItems: "flex-start",
|
|
753
|
+
gap: 2,
|
|
754
|
+
flexWrap: "wrap",
|
|
755
|
+
px: 2.5,
|
|
756
|
+
pt: 2.5,
|
|
757
|
+
pb: 2
|
|
758
|
+
}
|
|
759
|
+
},
|
|
760
|
+
/* @__PURE__ */ React2.createElement(
|
|
761
|
+
Box2,
|
|
762
|
+
{
|
|
763
|
+
sx: (theme) => ({
|
|
764
|
+
width: 44,
|
|
765
|
+
height: 44,
|
|
766
|
+
borderRadius: 1.5,
|
|
767
|
+
flexShrink: 0,
|
|
768
|
+
display: "flex",
|
|
769
|
+
alignItems: "center",
|
|
770
|
+
justifyContent: "center",
|
|
771
|
+
bgcolor: alpha2(theme.palette.primary.main, 0.12),
|
|
772
|
+
color: theme.palette.primary.main,
|
|
773
|
+
fontSize: 15,
|
|
774
|
+
fontWeight: 700,
|
|
775
|
+
letterSpacing: "0.02em"
|
|
776
|
+
})
|
|
777
|
+
},
|
|
778
|
+
initials(displayName)
|
|
779
|
+
),
|
|
780
|
+
/* @__PURE__ */ React2.createElement(Box2, { sx: { flexGrow: 1, minWidth: 220 } }, /* @__PURE__ */ React2.createElement(Typography2, { variant: "h6", sx: { lineHeight: 1.25, wordBreak: "break-word" } }, displayName), /* @__PURE__ */ React2.createElement(
|
|
781
|
+
Typography2,
|
|
782
|
+
{
|
|
783
|
+
variant: "caption",
|
|
784
|
+
color: "text.secondary",
|
|
785
|
+
sx: { display: "block", fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace" }
|
|
786
|
+
},
|
|
787
|
+
userInfo.user_id
|
|
788
|
+
), /* @__PURE__ */ React2.createElement(Box2, { sx: { display: "flex", gap: 0.75, flexWrap: "wrap", mt: 1, minHeight: 20 } }, loading ? /* @__PURE__ */ React2.createElement(Skeleton, { variant: "rounded", width: 140, height: 20 }) : teams.map((team) => /* @__PURE__ */ React2.createElement(
|
|
789
|
+
TagChip,
|
|
790
|
+
{
|
|
791
|
+
key: team.team_id,
|
|
792
|
+
label: team.team_alias || team.team_id,
|
|
793
|
+
title: team.team_id
|
|
794
|
+
}
|
|
795
|
+
)))),
|
|
796
|
+
/* @__PURE__ */ React2.createElement(
|
|
797
|
+
Button,
|
|
798
|
+
{
|
|
799
|
+
variant: "contained",
|
|
800
|
+
color: "primary",
|
|
801
|
+
disableElevation: true,
|
|
802
|
+
startIcon: /* @__PURE__ */ React2.createElement(Add, null),
|
|
803
|
+
onClick: onGenerateKeyClick
|
|
804
|
+
},
|
|
805
|
+
"Generate New Key"
|
|
806
|
+
)
|
|
807
|
+
), /* @__PURE__ */ React2.createElement(Divider, null), /* @__PURE__ */ React2.createElement(
|
|
808
|
+
Box2,
|
|
213
809
|
{
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
810
|
+
sx: {
|
|
811
|
+
display: "flex",
|
|
812
|
+
alignItems: "center",
|
|
813
|
+
gap: 2.5,
|
|
814
|
+
flexWrap: "wrap",
|
|
815
|
+
px: 2.5,
|
|
816
|
+
py: 1.5
|
|
817
|
+
}
|
|
218
818
|
},
|
|
219
|
-
"
|
|
220
|
-
|
|
819
|
+
/* @__PURE__ */ React2.createElement(CountStat, { value: counts.total, label: "keys", tone: "accent" }),
|
|
820
|
+
/* @__PURE__ */ React2.createElement(Divider, { orientation: "vertical", flexItem: true, sx: { my: 0.25 } }),
|
|
821
|
+
/* @__PURE__ */ React2.createElement(CountStat, { value: counts.expired, label: "expired", tone: "danger" }),
|
|
822
|
+
/* @__PURE__ */ React2.createElement(Divider, { orientation: "vertical", flexItem: true, sx: { my: 0.25 } }),
|
|
823
|
+
/* @__PURE__ */ React2.createElement(CountStat, { value: counts.expiringSoon, label: "expiring soon", tone: "warning" })
|
|
824
|
+
), tabs && /* @__PURE__ */ React2.createElement(React2.Fragment, null, /* @__PURE__ */ React2.createElement(Divider, null), /* @__PURE__ */ React2.createElement(Box2, { sx: { px: 1 } }, tabs)));
|
|
221
825
|
};
|
|
222
826
|
}
|
|
223
827
|
});
|
|
224
828
|
|
|
225
829
|
// src/components/KeysTable.tsx
|
|
226
|
-
import
|
|
227
|
-
import Paper2 from "@mui/material/Paper";
|
|
830
|
+
import React3, { useState, useMemo as useMemo2 } from "react";
|
|
228
831
|
import Table from "@mui/material/Table";
|
|
229
832
|
import TableBody from "@mui/material/TableBody";
|
|
230
833
|
import TableCell from "@mui/material/TableCell";
|
|
@@ -233,52 +836,62 @@ import TableHead from "@mui/material/TableHead";
|
|
|
233
836
|
import TableRow from "@mui/material/TableRow";
|
|
234
837
|
import Button2 from "@mui/material/Button";
|
|
235
838
|
import IconButton from "@mui/material/IconButton";
|
|
236
|
-
import
|
|
237
|
-
import
|
|
839
|
+
import Box3 from "@mui/material/Box";
|
|
840
|
+
import Typography3 from "@mui/material/Typography";
|
|
238
841
|
import Dialog from "@mui/material/Dialog";
|
|
239
842
|
import DialogTitle from "@mui/material/DialogTitle";
|
|
240
843
|
import DialogContent from "@mui/material/DialogContent";
|
|
241
844
|
import DialogActions from "@mui/material/DialogActions";
|
|
242
845
|
import TextField from "@mui/material/TextField";
|
|
243
|
-
import Chip2 from "@mui/material/Chip";
|
|
244
846
|
import CircularProgress from "@mui/material/CircularProgress";
|
|
245
847
|
import Autocomplete from "@mui/material/Autocomplete";
|
|
246
|
-
import
|
|
848
|
+
import Skeleton2 from "@mui/material/Skeleton";
|
|
247
849
|
import InputAdornment from "@mui/material/InputAdornment";
|
|
248
|
-
import {
|
|
850
|
+
import { alpha as alpha3 } from "@mui/material/styles";
|
|
851
|
+
import { ContentCopy, Delete, Add as Add2, Edit, Autorenew, Search, Lock, LockOpen } from "@mui/icons-material";
|
|
249
852
|
function expiryChipLabel(status, expiresAt) {
|
|
250
853
|
if (status === "expired") return "Expired";
|
|
251
854
|
if (status === "soon") return `${Math.ceil((new Date(expiresAt).getTime() - Date.now()) / 864e5)}d left`;
|
|
252
855
|
return formatDate(expiresAt);
|
|
253
856
|
}
|
|
254
|
-
function
|
|
255
|
-
if (status === "expired") return "
|
|
857
|
+
function expiryTone(status) {
|
|
858
|
+
if (status === "expired") return "danger";
|
|
256
859
|
if (status === "soon") return "warning";
|
|
257
|
-
return "
|
|
860
|
+
return "neutral";
|
|
258
861
|
}
|
|
259
|
-
function
|
|
862
|
+
function ExpiryCell({ expiresAt }) {
|
|
260
863
|
const status = expiryStatus(expiresAt);
|
|
261
|
-
if (!status)
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
864
|
+
if (!status) {
|
|
865
|
+
return /* @__PURE__ */ React3.createElement(Typography3, { variant: "body2", color: "text.secondary" }, "Never");
|
|
866
|
+
}
|
|
867
|
+
if (status === "ok") {
|
|
868
|
+
return /* @__PURE__ */ React3.createElement(Typography3, { variant: "body2", color: "text.secondary" }, formatDate(expiresAt));
|
|
869
|
+
}
|
|
870
|
+
return /* @__PURE__ */ React3.createElement(
|
|
871
|
+
StatusPill,
|
|
872
|
+
{
|
|
873
|
+
label: expiryChipLabel(status, expiresAt),
|
|
874
|
+
tone: expiryTone(status),
|
|
875
|
+
title: `Expires ${formatDate(expiresAt)}`
|
|
876
|
+
}
|
|
877
|
+
);
|
|
266
878
|
}
|
|
267
|
-
function
|
|
268
|
-
if (pct >= 100) return "
|
|
879
|
+
function budgetTone(pct) {
|
|
880
|
+
if (pct >= 100) return "danger";
|
|
269
881
|
if (pct >= 80) return "warning";
|
|
270
|
-
return "
|
|
882
|
+
return "accent";
|
|
271
883
|
}
|
|
272
884
|
function keyBlockIcon(isBlocking, blocked) {
|
|
273
|
-
if (isBlocking) return /* @__PURE__ */
|
|
274
|
-
if (blocked) return /* @__PURE__ */
|
|
275
|
-
return /* @__PURE__ */
|
|
885
|
+
if (isBlocking) return /* @__PURE__ */ React3.createElement(CircularProgress, { size: 18 });
|
|
886
|
+
if (blocked) return /* @__PURE__ */ React3.createElement(LockOpen, { fontSize: "small" });
|
|
887
|
+
return /* @__PURE__ */ React3.createElement(Lock, { fontSize: "small" });
|
|
276
888
|
}
|
|
277
889
|
function BudgetCell({ spend, maxBudget }) {
|
|
278
|
-
if (!maxBudget)
|
|
890
|
+
if (!maxBudget) {
|
|
891
|
+
return /* @__PURE__ */ React3.createElement(Typography3, { variant: "body2", color: "text.secondary", sx: { fontVariantNumeric: "tabular-nums" } }, "$", spend.toFixed(2), /* @__PURE__ */ React3.createElement(Typography3, { component: "span", variant: "caption", sx: { ml: 0.5, opacity: 0.7 } }, "/ \u221E"));
|
|
892
|
+
}
|
|
279
893
|
const pct = Math.min(100, spend / maxBudget * 100);
|
|
280
|
-
|
|
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 } }));
|
|
894
|
+
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 }));
|
|
282
895
|
}
|
|
283
896
|
function fmtCost(perToken) {
|
|
284
897
|
if (!perToken) return null;
|
|
@@ -305,6 +918,7 @@ var init_KeysTable = __esm({
|
|
|
305
918
|
"src/components/KeysTable.tsx"() {
|
|
306
919
|
"use strict";
|
|
307
920
|
init_api();
|
|
921
|
+
init_ui();
|
|
308
922
|
maskKey = (key) => {
|
|
309
923
|
if (key.length <= 8) return "***";
|
|
310
924
|
return `${key.slice(0, 4)}...${key.slice(-4)}`;
|
|
@@ -436,97 +1050,179 @@ var init_KeysTable = __esm({
|
|
|
436
1050
|
const inCost = fmtCost(m.input_cost_per_token);
|
|
437
1051
|
const outCost = fmtCost(m.output_cost_per_token);
|
|
438
1052
|
const ctx = formatContextWindow(m.max_input_tokens, m.max_output_tokens);
|
|
439
|
-
return /* @__PURE__ */
|
|
1053
|
+
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"));
|
|
440
1054
|
};
|
|
441
1055
|
const renderTableBody = () => {
|
|
442
1056
|
if (loading) {
|
|
443
|
-
return /* @__PURE__ */
|
|
1057
|
+
return /* @__PURE__ */ React3.createElement(TableRow, null, /* @__PURE__ */ React3.createElement(TableCell, { colSpan: 8, sx: { py: 2 } }, /* @__PURE__ */ React3.createElement(Skeleton2, { variant: "rounded", height: 140 })));
|
|
444
1058
|
}
|
|
445
1059
|
if (filteredKeys.length === 0) {
|
|
446
|
-
return /* @__PURE__ */
|
|
447
|
-
|
|
1060
|
+
return /* @__PURE__ */ React3.createElement(TableRow, null, /* @__PURE__ */ React3.createElement(TableCell, { colSpan: 8 }, filterText ? /* @__PURE__ */ React3.createElement(
|
|
1061
|
+
EmptyState,
|
|
448
1062
|
{
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
1063
|
+
message: "No keys match that filter",
|
|
1064
|
+
hint: "Try a different alias or model name."
|
|
1065
|
+
}
|
|
1066
|
+
) : /* @__PURE__ */ React3.createElement(
|
|
1067
|
+
EmptyState,
|
|
1068
|
+
{
|
|
1069
|
+
message: "No keys yet",
|
|
1070
|
+
hint: "Generate your first key to start calling models.",
|
|
1071
|
+
action: /* @__PURE__ */ React3.createElement(
|
|
1072
|
+
Button2,
|
|
1073
|
+
{
|
|
1074
|
+
variant: "contained",
|
|
1075
|
+
color: "primary",
|
|
1076
|
+
size: "small",
|
|
1077
|
+
disableElevation: true,
|
|
1078
|
+
startIcon: /* @__PURE__ */ React3.createElement(Add2, null),
|
|
1079
|
+
onClick: onGenerateKeyClick,
|
|
1080
|
+
sx: { mt: 1 }
|
|
1081
|
+
},
|
|
1082
|
+
"Generate Your First Key"
|
|
1083
|
+
)
|
|
1084
|
+
}
|
|
1085
|
+
)));
|
|
457
1086
|
}
|
|
458
1087
|
return filteredKeys.map((key) => {
|
|
459
1088
|
const keyId = key.token ?? key.key;
|
|
460
1089
|
const isBlocking = blockingKeyId === keyId;
|
|
461
|
-
|
|
462
|
-
|
|
1090
|
+
const keyModels = key.models ?? [];
|
|
1091
|
+
return /* @__PURE__ */ React3.createElement(
|
|
1092
|
+
TableRow,
|
|
463
1093
|
{
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
px: 1,
|
|
472
|
-
py: 0.5,
|
|
473
|
-
borderRadius: 1
|
|
474
|
-
}
|
|
1094
|
+
key: keyId,
|
|
1095
|
+
sx: key.blocked ? (theme) => ({
|
|
1096
|
+
// A left rule reads as "suspended" without greying the row into
|
|
1097
|
+
// illegibility the way a full background tint does.
|
|
1098
|
+
boxShadow: `inset 3px 0 0 0 ${theme.palette.error.main}`,
|
|
1099
|
+
"& td": { color: theme.palette.text.secondary }
|
|
1100
|
+
}) : void 0
|
|
475
1101
|
},
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
1102
|
+
/* @__PURE__ */ React3.createElement(TableCell, null, /* @__PURE__ */ React3.createElement(Box3, { display: "flex", alignItems: "center", gap: 1, flexWrap: "wrap" }, /* @__PURE__ */ React3.createElement(Typography3, { variant: "body2", sx: { fontWeight: 600 } }, key.key_alias || "\u2014"), key.blocked && /* @__PURE__ */ React3.createElement(StatusPill, { label: "Blocked", tone: "danger" }))),
|
|
1103
|
+
/* @__PURE__ */ React3.createElement(TableCell, null, /* @__PURE__ */ React3.createElement(Box3, { display: "flex", alignItems: "center", gap: 0.5 }, /* @__PURE__ */ React3.createElement(
|
|
1104
|
+
Typography3,
|
|
1105
|
+
{
|
|
1106
|
+
variant: "body2",
|
|
1107
|
+
component: "code",
|
|
1108
|
+
color: "text.secondary",
|
|
1109
|
+
title: keyId,
|
|
1110
|
+
sx: (theme) => ({
|
|
1111
|
+
fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
|
|
1112
|
+
fontSize: 12,
|
|
1113
|
+
bgcolor: alpha3(theme.palette.text.primary, theme.palette.mode === "dark" ? 0.08 : 0.05),
|
|
1114
|
+
px: 0.75,
|
|
1115
|
+
py: 0.375,
|
|
1116
|
+
borderRadius: "5px"
|
|
1117
|
+
})
|
|
1118
|
+
},
|
|
1119
|
+
shortKeyId(keyId)
|
|
1120
|
+
), /* @__PURE__ */ React3.createElement(
|
|
1121
|
+
IconButton,
|
|
1122
|
+
{
|
|
1123
|
+
size: "small",
|
|
1124
|
+
onClick: () => copyToClipboard(keyId),
|
|
1125
|
+
title: "Copy Key ID",
|
|
1126
|
+
sx: quietIconButtonSx("accent")
|
|
1127
|
+
},
|
|
1128
|
+
/* @__PURE__ */ React3.createElement(ContentCopy, { sx: { fontSize: 15 } })
|
|
1129
|
+
))),
|
|
1130
|
+
/* @__PURE__ */ React3.createElement(TableCell, null, /* @__PURE__ */ React3.createElement(Typography3, { variant: "body2", color: "text.secondary" }, formatDate(key.created_at))),
|
|
1131
|
+
/* @__PURE__ */ React3.createElement(TableCell, null, /* @__PURE__ */ React3.createElement(ExpiryCell, { expiresAt: key.expires_at })),
|
|
1132
|
+
/* @__PURE__ */ React3.createElement(TableCell, null, /* @__PURE__ */ React3.createElement(BudgetCell, { spend: key.spend ?? 0, maxBudget: key.max_budget })),
|
|
1133
|
+
/* @__PURE__ */ React3.createElement(TableCell, null, /* @__PURE__ */ React3.createElement(Typography3, { variant: "body2", color: "text.secondary", sx: { fontVariantNumeric: "tabular-nums" } }, key.tpm_limit ?? "\u221E", " / ", key.rpm_limit ?? "\u221E")),
|
|
1134
|
+
/* @__PURE__ */ React3.createElement(TableCell, null, keyModels.length === 0 ? /* @__PURE__ */ React3.createElement(Typography3, { variant: "body2", color: "text.secondary" }, "All models") : /* @__PURE__ */ React3.createElement(Box3, { display: "flex", gap: 0.5, flexWrap: "wrap" }, keyModels.slice(0, 2).map((model) => /* @__PURE__ */ React3.createElement(TagChip, { key: model, label: model, title: model })), keyModels.length > 2 && /* @__PURE__ */ React3.createElement(
|
|
1135
|
+
TagChip,
|
|
1136
|
+
{
|
|
1137
|
+
label: `+${keyModels.length - 2}`,
|
|
1138
|
+
mono: false,
|
|
1139
|
+
title: keyModels.slice(2).join(", ")
|
|
1140
|
+
}
|
|
1141
|
+
))),
|
|
1142
|
+
/* @__PURE__ */ React3.createElement(TableCell, { align: "right" }, /* @__PURE__ */ React3.createElement(Box3, { display: "flex", justifyContent: "flex-end", gap: 0.25 }, /* @__PURE__ */ React3.createElement(
|
|
1143
|
+
IconButton,
|
|
1144
|
+
{
|
|
1145
|
+
size: "small",
|
|
1146
|
+
onClick: () => handleOpenEdit(key),
|
|
1147
|
+
title: "Edit key",
|
|
1148
|
+
sx: quietIconButtonSx("accent")
|
|
1149
|
+
},
|
|
1150
|
+
/* @__PURE__ */ React3.createElement(Edit, { fontSize: "small" })
|
|
1151
|
+
), /* @__PURE__ */ React3.createElement(
|
|
1152
|
+
IconButton,
|
|
1153
|
+
{
|
|
1154
|
+
size: "small",
|
|
1155
|
+
onClick: () => handleToggleBlock(key),
|
|
1156
|
+
disabled: isBlocking,
|
|
1157
|
+
title: key.blocked ? "Unblock key" : "Block key \u2014 suspends without revoking",
|
|
1158
|
+
sx: quietIconButtonSx("warning")
|
|
1159
|
+
},
|
|
1160
|
+
keyBlockIcon(isBlocking, key.blocked)
|
|
1161
|
+
), /* @__PURE__ */ React3.createElement(
|
|
1162
|
+
IconButton,
|
|
1163
|
+
{
|
|
1164
|
+
size: "small",
|
|
1165
|
+
onClick: () => setDeleteConfirmId(keyId),
|
|
1166
|
+
title: "Revoke key",
|
|
1167
|
+
sx: quietIconButtonSx("danger")
|
|
1168
|
+
},
|
|
1169
|
+
/* @__PURE__ */ React3.createElement(Delete, { fontSize: "small" })
|
|
1170
|
+
)))
|
|
1171
|
+
);
|
|
1172
|
+
});
|
|
1173
|
+
};
|
|
1174
|
+
return /* @__PURE__ */ React3.createElement(React3.Fragment, null, /* @__PURE__ */ React3.createElement(
|
|
1175
|
+
SectionCard,
|
|
1176
|
+
{
|
|
1177
|
+
title: "Virtual Keys",
|
|
1178
|
+
subtitle: loading ? "Loading\u2026" : `${filteredKeys.length}${filterText ? ` of ${keys.length}` : ""} key${filteredKeys.length === 1 ? "" : "s"}`,
|
|
1179
|
+
flush: true,
|
|
1180
|
+
actions: /* @__PURE__ */ React3.createElement(React3.Fragment, null, /* @__PURE__ */ React3.createElement(
|
|
1181
|
+
TextField,
|
|
479
1182
|
{
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
1183
|
+
size: "small",
|
|
1184
|
+
placeholder: "Filter by alias or model\u2026",
|
|
1185
|
+
value: filterText,
|
|
1186
|
+
onChange: (e) => setFilterText(e.target.value),
|
|
1187
|
+
sx: { minWidth: 240 },
|
|
1188
|
+
InputProps: {
|
|
1189
|
+
startAdornment: /* @__PURE__ */ React3.createElement(InputAdornment, { position: "start" }, /* @__PURE__ */ React3.createElement(Search, { fontSize: "small", color: "disabled" }))
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
), expiredKeys.length > 0 && /* @__PURE__ */ React3.createElement(
|
|
1193
|
+
Button2,
|
|
1194
|
+
{
|
|
1195
|
+
variant: "outlined",
|
|
1196
|
+
color: "inherit",
|
|
1197
|
+
startIcon: /* @__PURE__ */ React3.createElement(Autorenew, null),
|
|
1198
|
+
onClick: () => setPruneConfirmCount(expiredKeys.length),
|
|
1199
|
+
sx: (theme) => ({
|
|
1200
|
+
color: theme.palette.text.secondary,
|
|
1201
|
+
borderColor: theme.palette.divider,
|
|
1202
|
+
"&:hover": {
|
|
1203
|
+
color: theme.palette.error.main,
|
|
1204
|
+
borderColor: alpha3(theme.palette.error.main, 0.5),
|
|
1205
|
+
bgcolor: alpha3(theme.palette.error.main, 0.06)
|
|
1206
|
+
}
|
|
1207
|
+
})
|
|
484
1208
|
},
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
1209
|
+
"Prune expired (",
|
|
1210
|
+
expiredKeys.length,
|
|
1211
|
+
")"
|
|
1212
|
+
), /* @__PURE__ */ React3.createElement(
|
|
1213
|
+
Button2,
|
|
488
1214
|
{
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
1215
|
+
variant: "contained",
|
|
1216
|
+
color: "primary",
|
|
1217
|
+
disableElevation: true,
|
|
1218
|
+
startIcon: /* @__PURE__ */ React3.createElement(Add2, null),
|
|
1219
|
+
onClick: onGenerateKeyClick
|
|
492
1220
|
},
|
|
493
|
-
|
|
494
|
-
))
|
|
495
|
-
});
|
|
496
|
-
};
|
|
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(
|
|
498
|
-
TextField,
|
|
499
|
-
{
|
|
500
|
-
size: "small",
|
|
501
|
-
placeholder: "Filter by alias or model\u2026",
|
|
502
|
-
value: filterText,
|
|
503
|
-
onChange: (e) => setFilterText(e.target.value),
|
|
504
|
-
sx: { minWidth: 240 },
|
|
505
|
-
InputProps: {
|
|
506
|
-
startAdornment: /* @__PURE__ */ React2.createElement(InputAdornment, { position: "start" }, /* @__PURE__ */ React2.createElement(Search, { fontSize: "small" }))
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
), expiredKeys.length > 0 && /* @__PURE__ */ React2.createElement(
|
|
510
|
-
Button2,
|
|
511
|
-
{
|
|
512
|
-
variant: "outlined",
|
|
513
|
-
color: "error",
|
|
514
|
-
startIcon: /* @__PURE__ */ React2.createElement(Autorenew, null),
|
|
515
|
-
onClick: () => setPruneConfirmCount(expiredKeys.length)
|
|
1221
|
+
"Generate New Key"
|
|
1222
|
+
))
|
|
516
1223
|
},
|
|
517
|
-
"
|
|
518
|
-
|
|
519
|
-
")"
|
|
520
|
-
), /* @__PURE__ */ React2.createElement(
|
|
521
|
-
Button2,
|
|
522
|
-
{
|
|
523
|
-
variant: "contained",
|
|
524
|
-
color: "primary",
|
|
525
|
-
startIcon: /* @__PURE__ */ React2.createElement(Add2, null),
|
|
526
|
-
onClick: onGenerateKeyClick
|
|
527
|
-
},
|
|
528
|
-
"Generate New Key"
|
|
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(
|
|
1224
|
+
/* @__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, null, "Alias"), /* @__PURE__ */ React3.createElement(TableCell, null, "Key ID"), /* @__PURE__ */ React3.createElement(TableCell, null, "Created"), /* @__PURE__ */ React3.createElement(TableCell, null, "Expires"), /* @__PURE__ */ React3.createElement(TableCell, null, "Budget"), /* @__PURE__ */ React3.createElement(TableCell, null, "TPM / RPM"), /* @__PURE__ */ React3.createElement(TableCell, null, "Models"), /* @__PURE__ */ React3.createElement(TableCell, { align: "right" }, "Actions"))), /* @__PURE__ */ React3.createElement(TableBody, null, renderTableBody())))
|
|
1225
|
+
), /* @__PURE__ */ React3.createElement(Dialog, { open: !!editingKey, onClose: handleCloseEdit, maxWidth: "sm", fullWidth: true }, /* @__PURE__ */ React3.createElement(DialogTitle, null, "Edit Key"), /* @__PURE__ */ React3.createElement(DialogContent, null, editingKey && /* @__PURE__ */ React3.createElement(Box3, { display: "flex", flexDirection: "column", gap: 2, mt: 1 }, /* @__PURE__ */ React3.createElement(Typography3, { variant: "body2", color: "text.secondary" }, /* @__PURE__ */ React3.createElement("code", { style: { fontFamily: "monospace", color: "inherit" } }, maskKey(editingKey.key))), /* @__PURE__ */ React3.createElement(
|
|
530
1226
|
TextField,
|
|
531
1227
|
{
|
|
532
1228
|
label: "Alias",
|
|
@@ -534,7 +1230,7 @@ var init_KeysTable = __esm({
|
|
|
534
1230
|
onChange: (e) => setEditForm({ ...editForm, key_alias: e.target.value }),
|
|
535
1231
|
fullWidth: true
|
|
536
1232
|
}
|
|
537
|
-
), models.length > 0 && /* @__PURE__ */
|
|
1233
|
+
), models.length > 0 && /* @__PURE__ */ React3.createElement(
|
|
538
1234
|
Autocomplete,
|
|
539
1235
|
{
|
|
540
1236
|
multiple: true,
|
|
@@ -543,10 +1239,10 @@ var init_KeysTable = __esm({
|
|
|
543
1239
|
getOptionLabel: (m) => m.model_name,
|
|
544
1240
|
value: editSelectedModels,
|
|
545
1241
|
onChange: (_e, selected) => setEditForm({ ...editForm, models: selected.map((m) => m.model_name) }),
|
|
546
|
-
renderOption: (props, m) => /* @__PURE__ */
|
|
547
|
-
renderInput: (params) => /* @__PURE__ */
|
|
1242
|
+
renderOption: (props, m) => /* @__PURE__ */ React3.createElement("li", { ...props }, modelOption(m)),
|
|
1243
|
+
renderInput: (params) => /* @__PURE__ */ React3.createElement(TextField, { ...params, label: "Models", fullWidth: true })
|
|
548
1244
|
}
|
|
549
|
-
), /* @__PURE__ */
|
|
1245
|
+
), /* @__PURE__ */ React3.createElement(
|
|
550
1246
|
TextField,
|
|
551
1247
|
{
|
|
552
1248
|
label: "Max Budget (USD)",
|
|
@@ -555,7 +1251,7 @@ var init_KeysTable = __esm({
|
|
|
555
1251
|
onChange: (e) => setEditForm({ ...editForm, max_budget: e.target.value ? Number(e.target.value) : void 0 }),
|
|
556
1252
|
fullWidth: true
|
|
557
1253
|
}
|
|
558
|
-
), /* @__PURE__ */
|
|
1254
|
+
), /* @__PURE__ */ React3.createElement(
|
|
559
1255
|
TextField,
|
|
560
1256
|
{
|
|
561
1257
|
label: "TPM Limit",
|
|
@@ -565,7 +1261,7 @@ var init_KeysTable = __esm({
|
|
|
565
1261
|
helperText: "Max tokens per minute this key can consume across all models. Leave blank for no limit.",
|
|
566
1262
|
fullWidth: true
|
|
567
1263
|
}
|
|
568
|
-
), /* @__PURE__ */
|
|
1264
|
+
), /* @__PURE__ */ React3.createElement(
|
|
569
1265
|
TextField,
|
|
570
1266
|
{
|
|
571
1267
|
label: "RPM Limit",
|
|
@@ -575,7 +1271,7 @@ var init_KeysTable = __esm({
|
|
|
575
1271
|
helperText: "Max requests per minute this key can make across all models. Leave blank for no limit.",
|
|
576
1272
|
fullWidth: true
|
|
577
1273
|
}
|
|
578
|
-
), /* @__PURE__ */
|
|
1274
|
+
), /* @__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(
|
|
579
1275
|
Button2,
|
|
580
1276
|
{
|
|
581
1277
|
size: "small",
|
|
@@ -584,7 +1280,7 @@ var init_KeysTable = __esm({
|
|
|
584
1280
|
onClick: () => setResetSpendConfirm(true)
|
|
585
1281
|
},
|
|
586
1282
|
"Reset Spend to $0"
|
|
587
|
-
) : /* @__PURE__ */
|
|
1283
|
+
) : /* @__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(
|
|
588
1284
|
Button2,
|
|
589
1285
|
{
|
|
590
1286
|
size: "small",
|
|
@@ -593,8 +1289,8 @@ var init_KeysTable = __esm({
|
|
|
593
1289
|
disabled: resetSpendSubmitting,
|
|
594
1290
|
onClick: handleResetSpend
|
|
595
1291
|
},
|
|
596
|
-
resetSpendSubmitting ? /* @__PURE__ */
|
|
597
|
-
), /* @__PURE__ */
|
|
1292
|
+
resetSpendSubmitting ? /* @__PURE__ */ React3.createElement(CircularProgress, { size: 16 }) : "Confirm"
|
|
1293
|
+
), /* @__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(
|
|
598
1294
|
Button2,
|
|
599
1295
|
{
|
|
600
1296
|
onClick: handleConfirmDelete,
|
|
@@ -602,8 +1298,8 @@ var init_KeysTable = __esm({
|
|
|
602
1298
|
color: "error",
|
|
603
1299
|
disabled: deleteSubmitting
|
|
604
1300
|
},
|
|
605
|
-
deleteSubmitting ? /* @__PURE__ */
|
|
606
|
-
))), /* @__PURE__ */
|
|
1301
|
+
deleteSubmitting ? /* @__PURE__ */ React3.createElement(CircularProgress, { size: 20 }) : "Revoke"
|
|
1302
|
+
))), /* @__PURE__ */ React3.createElement(Dialog, { open: pruneConfirmCount !== null, onClose: () => setPruneConfirmCount(null), maxWidth: "xs", fullWidth: true }, /* @__PURE__ */ React3.createElement(DialogTitle, null, "Prune Expired Keys?"), /* @__PURE__ */ React3.createElement(DialogContent, null, /* @__PURE__ */ React3.createElement(Typography3, null, "This will permanently delete ", pruneConfirmCount, " expired key", pruneConfirmCount !== 1 ? "s" : "", " from LiteLLM. Any integrations using them will stop working immediately.")), /* @__PURE__ */ React3.createElement(DialogActions, null, /* @__PURE__ */ React3.createElement(Button2, { onClick: () => setPruneConfirmCount(null), disabled: pruneSubmitting }, "Cancel"), /* @__PURE__ */ React3.createElement(
|
|
607
1303
|
Button2,
|
|
608
1304
|
{
|
|
609
1305
|
onClick: handlePruneExpired,
|
|
@@ -611,7 +1307,7 @@ var init_KeysTable = __esm({
|
|
|
611
1307
|
color: "error",
|
|
612
1308
|
disabled: pruneSubmitting
|
|
613
1309
|
},
|
|
614
|
-
pruneSubmitting ? /* @__PURE__ */
|
|
1310
|
+
pruneSubmitting ? /* @__PURE__ */ React3.createElement(CircularProgress, { size: 20 }) : "Prune"
|
|
615
1311
|
))));
|
|
616
1312
|
};
|
|
617
1313
|
}
|
|
@@ -632,9 +1328,9 @@ var init_format = __esm({
|
|
|
632
1328
|
});
|
|
633
1329
|
|
|
634
1330
|
// src/components/GenerateKeyDialog.tsx
|
|
635
|
-
import
|
|
636
|
-
import
|
|
637
|
-
import
|
|
1331
|
+
import React4, { useState as useState2, useEffect, useMemo as useMemo3 } from "react";
|
|
1332
|
+
import Box4 from "@mui/material/Box";
|
|
1333
|
+
import Typography4 from "@mui/material/Typography";
|
|
638
1334
|
import Dialog2 from "@mui/material/Dialog";
|
|
639
1335
|
import DialogTitle2 from "@mui/material/DialogTitle";
|
|
640
1336
|
import DialogContent2 from "@mui/material/DialogContent";
|
|
@@ -654,10 +1350,18 @@ import { ContentCopy as ContentCopy2, Code } from "@mui/icons-material";
|
|
|
654
1350
|
function trimSlash(url) {
|
|
655
1351
|
return url.replace(/\/+$/, "");
|
|
656
1352
|
}
|
|
1353
|
+
function isModelAllowedByTeam(model, teamModels) {
|
|
1354
|
+
if (!teamModels || teamModels.length === 0) return true;
|
|
1355
|
+
if (teamModels.includes(ALL_PROXY_MODELS)) return true;
|
|
1356
|
+
if (teamModels.includes(model.model_name)) return true;
|
|
1357
|
+
return !!model.access_groups?.some((group) => teamModels.includes(group));
|
|
1358
|
+
}
|
|
657
1359
|
function buildSnippets(baseUrl, key, model) {
|
|
658
1360
|
const base = trimSlash(baseUrl);
|
|
1361
|
+
const apiBase = `${base}/v1`;
|
|
659
1362
|
return {
|
|
660
|
-
|
|
1363
|
+
publicEndpoint: apiBase,
|
|
1364
|
+
curl: `curl ${apiBase}/chat/completions \\
|
|
661
1365
|
-H "Authorization: Bearer ${key}" \\
|
|
662
1366
|
-H "Content-Type: application/json" \\
|
|
663
1367
|
-d '{
|
|
@@ -668,14 +1372,40 @@ function buildSnippets(baseUrl, key, model) {
|
|
|
668
1372
|
|
|
669
1373
|
client = OpenAI(
|
|
670
1374
|
api_key="${key}",
|
|
671
|
-
base_url="${
|
|
1375
|
+
base_url="${apiBase}",
|
|
672
1376
|
)
|
|
673
1377
|
|
|
674
1378
|
response = client.chat.completions.create(
|
|
675
1379
|
model="${model}",
|
|
676
1380
|
messages=[{ "role": "user", "content": "Hello!" }],
|
|
677
1381
|
)
|
|
678
|
-
print(response.choices[0].message.content)
|
|
1382
|
+
print(response.choices[0].message.content)`,
|
|
1383
|
+
opencode: `{
|
|
1384
|
+
"$schema": "https://opencode.ai/config.json",
|
|
1385
|
+
"provider": {
|
|
1386
|
+
"litellm": {
|
|
1387
|
+
"npm": "@ai-sdk/openai-compatible",
|
|
1388
|
+
"name": "LiteLLM",
|
|
1389
|
+
"options": {
|
|
1390
|
+
"baseURL": "${apiBase}",
|
|
1391
|
+
"apiKey": "${key}"
|
|
1392
|
+
},
|
|
1393
|
+
"models": {
|
|
1394
|
+
"${model}": {}
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
}`,
|
|
1399
|
+
pi: `{
|
|
1400
|
+
"litellm": {
|
|
1401
|
+
"baseUrl": "${apiBase}",
|
|
1402
|
+
"apiKey": "${key}",
|
|
1403
|
+
"api": "openai-completions",
|
|
1404
|
+
"models": [
|
|
1405
|
+
{ "id": "${model}", "name": "${model}" }
|
|
1406
|
+
]
|
|
1407
|
+
}
|
|
1408
|
+
}`
|
|
679
1409
|
};
|
|
680
1410
|
}
|
|
681
1411
|
function aliasHelperText(aliasError, aliasDuplicate) {
|
|
@@ -713,7 +1443,7 @@ function formatContextWindow2(maxInput, maxOutput) {
|
|
|
713
1443
|
if (outPart) return `ctx ${outPart} out`;
|
|
714
1444
|
return null;
|
|
715
1445
|
}
|
|
716
|
-
var generateDefaultAlias, emptyForm, SnippetTabs, GenerateKeyDialog;
|
|
1446
|
+
var generateDefaultAlias, emptyForm, ALL_PROXY_MODELS, SNIPPET_FILE_HINTS, SnippetTabs, GenerateKeyDialog;
|
|
717
1447
|
var init_GenerateKeyDialog = __esm({
|
|
718
1448
|
"src/components/GenerateKeyDialog.tsx"() {
|
|
719
1449
|
"use strict";
|
|
@@ -733,17 +1463,23 @@ var init_GenerateKeyDialog = __esm({
|
|
|
733
1463
|
team_id: void 0,
|
|
734
1464
|
key_type: "llm_api"
|
|
735
1465
|
});
|
|
1466
|
+
ALL_PROXY_MODELS = "all-proxy-models";
|
|
1467
|
+
SNIPPET_FILE_HINTS = {
|
|
1468
|
+
opencode: "Add to ~/.config/opencode/opencode.json",
|
|
1469
|
+
pi: "Add to ~/.pi/agent/models.json"
|
|
1470
|
+
};
|
|
736
1471
|
SnippetTabs = ({ snippets, model, onCopy }) => {
|
|
737
1472
|
const [tab, setTab] = useState2("curl");
|
|
738
|
-
const code = tab
|
|
739
|
-
|
|
740
|
-
|
|
1473
|
+
const code = snippets[tab];
|
|
1474
|
+
const fileHint = SNIPPET_FILE_HINTS[tab];
|
|
1475
|
+
return /* @__PURE__ */ React4.createElement(Box4, null, /* @__PURE__ */ React4.createElement(Tabs, { value: tab, onChange: (_, v) => setTab(v), sx: { mb: 1 }, variant: "scrollable" }, /* @__PURE__ */ React4.createElement(Tab, { label: "curl", value: "curl" }), /* @__PURE__ */ React4.createElement(Tab, { label: "OpenAI SDK", value: "openai" }), /* @__PURE__ */ React4.createElement(Tab, { label: "opencode", value: "opencode" }), /* @__PURE__ */ React4.createElement(Tab, { label: "pi", value: "pi" })), fileHint && /* @__PURE__ */ React4.createElement(Typography4, { variant: "caption", color: "text.secondary", display: "block", mb: 0.5 }, fileHint), /* @__PURE__ */ React4.createElement(
|
|
1476
|
+
Box4,
|
|
741
1477
|
{
|
|
742
1478
|
position: "relative",
|
|
743
1479
|
p: 1.5,
|
|
744
1480
|
sx: { backgroundColor: "action.hover", border: "1px solid", borderColor: "divider", borderRadius: 1 }
|
|
745
1481
|
},
|
|
746
|
-
/* @__PURE__ */
|
|
1482
|
+
/* @__PURE__ */ React4.createElement(
|
|
747
1483
|
IconButton2,
|
|
748
1484
|
{
|
|
749
1485
|
size: "small",
|
|
@@ -751,10 +1487,10 @@ var init_GenerateKeyDialog = __esm({
|
|
|
751
1487
|
title: "Copy snippet",
|
|
752
1488
|
sx: { position: "absolute", top: 4, right: 4 }
|
|
753
1489
|
},
|
|
754
|
-
/* @__PURE__ */
|
|
1490
|
+
/* @__PURE__ */ React4.createElement(ContentCopy2, { fontSize: "small" })
|
|
755
1491
|
),
|
|
756
|
-
/* @__PURE__ */
|
|
757
|
-
|
|
1492
|
+
/* @__PURE__ */ React4.createElement(
|
|
1493
|
+
Typography4,
|
|
758
1494
|
{
|
|
759
1495
|
component: "pre",
|
|
760
1496
|
sx: {
|
|
@@ -768,7 +1504,7 @@ var init_GenerateKeyDialog = __esm({
|
|
|
768
1504
|
},
|
|
769
1505
|
code
|
|
770
1506
|
),
|
|
771
|
-
model && /* @__PURE__ */
|
|
1507
|
+
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.")
|
|
772
1508
|
));
|
|
773
1509
|
};
|
|
774
1510
|
GenerateKeyDialog = ({
|
|
@@ -803,12 +1539,7 @@ var init_GenerateKeyDialog = __esm({
|
|
|
803
1539
|
const teamRequired = keyGenerationSettings?.teamRequired ?? true;
|
|
804
1540
|
const selectedTeam = teams.find((t) => t.team_id === formData.team_id) ?? null;
|
|
805
1541
|
const availableModels = useMemo3(() => {
|
|
806
|
-
|
|
807
|
-
if (teamModels && teamModels.length > 0) {
|
|
808
|
-
const allowed = new Set(teamModels);
|
|
809
|
-
return models.filter((m) => allowed.has(m.model_name));
|
|
810
|
-
}
|
|
811
|
-
return models;
|
|
1542
|
+
return models.filter((m) => isModelAllowedByTeam(m, selectedTeam?.models));
|
|
812
1543
|
}, [models, selectedTeam]);
|
|
813
1544
|
const selectedModels = availableModels.filter((m) => (formData.models || []).includes(m.model_name));
|
|
814
1545
|
const aliasError = !(formData.alias || "").trim();
|
|
@@ -832,12 +1563,12 @@ var init_GenerateKeyDialog = __esm({
|
|
|
832
1563
|
max_budget: unlimitedBudget ? null : formData.max_budget
|
|
833
1564
|
};
|
|
834
1565
|
const response = await onGenerateKey(request);
|
|
1566
|
+
const model = formData.models?.[0] ?? availableModels[0]?.model_name ?? "";
|
|
835
1567
|
setNewKeyValue(response.key);
|
|
836
|
-
setNewKeyModel(
|
|
1568
|
+
setNewKeyModel(model);
|
|
837
1569
|
setNewKeySnippets(null);
|
|
838
1570
|
try {
|
|
839
1571
|
const config = await onGetConfig();
|
|
840
|
-
const model = formData.models?.[0] ?? "";
|
|
841
1572
|
setNewKeySnippets(buildSnippets(config.baseUrl, response.key, model));
|
|
842
1573
|
} catch {
|
|
843
1574
|
}
|
|
@@ -864,22 +1595,24 @@ var init_GenerateKeyDialog = __esm({
|
|
|
864
1595
|
const inCost = fmtCost2(m.input_cost_per_token);
|
|
865
1596
|
const outCost = fmtCost2(m.output_cost_per_token);
|
|
866
1597
|
const ctx = formatContextWindow2(m.max_input_tokens, m.max_output_tokens);
|
|
867
|
-
return /* @__PURE__ */
|
|
1598
|
+
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"));
|
|
868
1599
|
};
|
|
869
1600
|
const renderTeamField = () => {
|
|
870
1601
|
if (teams.length > 0) {
|
|
871
|
-
return /* @__PURE__ */
|
|
1602
|
+
return /* @__PURE__ */ React4.createElement(
|
|
872
1603
|
Autocomplete2,
|
|
873
1604
|
{
|
|
874
1605
|
options: teams,
|
|
875
1606
|
getOptionLabel: (t) => t.team_alias || t.team_id,
|
|
876
1607
|
value: selectedTeam,
|
|
877
1608
|
onChange: (_e, team) => {
|
|
878
|
-
const
|
|
879
|
-
|
|
1609
|
+
const restrictedModels = (formData.models || []).filter((name) => {
|
|
1610
|
+
const model = models.find((m) => m.model_name === name);
|
|
1611
|
+
return model ? isModelAllowedByTeam(model, team?.models) : false;
|
|
1612
|
+
});
|
|
880
1613
|
setFormData({ ...formData, team_id: team?.team_id, models: restrictedModels });
|
|
881
1614
|
},
|
|
882
|
-
renderInput: (params) => /* @__PURE__ */
|
|
1615
|
+
renderInput: (params) => /* @__PURE__ */ React4.createElement(
|
|
883
1616
|
TextField2,
|
|
884
1617
|
{
|
|
885
1618
|
...params,
|
|
@@ -894,12 +1627,12 @@ var init_GenerateKeyDialog = __esm({
|
|
|
894
1627
|
);
|
|
895
1628
|
}
|
|
896
1629
|
if (teamRequired) {
|
|
897
|
-
return /* @__PURE__ */
|
|
1630
|
+
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.");
|
|
898
1631
|
}
|
|
899
1632
|
return null;
|
|
900
1633
|
};
|
|
901
|
-
return /* @__PURE__ */
|
|
902
|
-
|
|
1634
|
+
return /* @__PURE__ */ React4.createElement(Dialog2, { open, onClose: handleClose, maxWidth: "sm", fullWidth: true }, /* @__PURE__ */ React4.createElement(DialogTitle2, null, newKeyValue ? "Key Generated" : "Generate New Key"), /* @__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(
|
|
1635
|
+
Box4,
|
|
903
1636
|
{
|
|
904
1637
|
display: "flex",
|
|
905
1638
|
alignItems: "center",
|
|
@@ -913,8 +1646,8 @@ var init_GenerateKeyDialog = __esm({
|
|
|
913
1646
|
borderRadius: 1
|
|
914
1647
|
}
|
|
915
1648
|
},
|
|
916
|
-
/* @__PURE__ */
|
|
917
|
-
|
|
1649
|
+
/* @__PURE__ */ React4.createElement(
|
|
1650
|
+
Typography4,
|
|
918
1651
|
{
|
|
919
1652
|
component: "code",
|
|
920
1653
|
color: "text.primary",
|
|
@@ -922,8 +1655,32 @@ var init_GenerateKeyDialog = __esm({
|
|
|
922
1655
|
},
|
|
923
1656
|
newKeyValue
|
|
924
1657
|
),
|
|
925
|
-
/* @__PURE__ */
|
|
926
|
-
), newKeySnippets && /* @__PURE__ */
|
|
1658
|
+
/* @__PURE__ */ React4.createElement(IconButton2, { onClick: () => copyToClipboard(newKeyValue) }, /* @__PURE__ */ React4.createElement(ContentCopy2, null))
|
|
1659
|
+
), newKeySnippets && /* @__PURE__ */ React4.createElement(Box4, { mt: 2 }, /* @__PURE__ */ React4.createElement(Typography4, { variant: "caption", color: "text.secondary", display: "block", gutterBottom: true }, "Public endpoint \u2014 paste into any tool's base URL / API base field"), /* @__PURE__ */ React4.createElement(
|
|
1660
|
+
Box4,
|
|
1661
|
+
{
|
|
1662
|
+
display: "flex",
|
|
1663
|
+
alignItems: "center",
|
|
1664
|
+
gap: 1,
|
|
1665
|
+
p: 1.5,
|
|
1666
|
+
sx: {
|
|
1667
|
+
backgroundColor: "action.hover",
|
|
1668
|
+
border: "1px solid",
|
|
1669
|
+
borderColor: "divider",
|
|
1670
|
+
borderRadius: 1
|
|
1671
|
+
}
|
|
1672
|
+
},
|
|
1673
|
+
/* @__PURE__ */ React4.createElement(
|
|
1674
|
+
Typography4,
|
|
1675
|
+
{
|
|
1676
|
+
component: "code",
|
|
1677
|
+
color: "text.primary",
|
|
1678
|
+
sx: { fontFamily: "monospace", fontSize: 13, wordBreak: "break-all", flex: 1 }
|
|
1679
|
+
},
|
|
1680
|
+
newKeySnippets.publicEndpoint
|
|
1681
|
+
),
|
|
1682
|
+
/* @__PURE__ */ React4.createElement(IconButton2, { size: "small", onClick: () => copyToClipboard(newKeySnippets.publicEndpoint) }, /* @__PURE__ */ React4.createElement(ContentCopy2, { fontSize: "small" }))
|
|
1683
|
+
)), 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(
|
|
927
1684
|
TextField2,
|
|
928
1685
|
{
|
|
929
1686
|
label: "Alias",
|
|
@@ -938,7 +1695,7 @@ var init_GenerateKeyDialog = __esm({
|
|
|
938
1695
|
required: true,
|
|
939
1696
|
fullWidth: true
|
|
940
1697
|
}
|
|
941
|
-
), /* @__PURE__ */
|
|
1698
|
+
), /* @__PURE__ */ React4.createElement(
|
|
942
1699
|
TextField2,
|
|
943
1700
|
{
|
|
944
1701
|
select: true,
|
|
@@ -947,12 +1704,12 @@ var init_GenerateKeyDialog = __esm({
|
|
|
947
1704
|
onChange: (e) => setFormData({ ...formData, duration: e.target.value }),
|
|
948
1705
|
fullWidth: true
|
|
949
1706
|
},
|
|
950
|
-
/* @__PURE__ */
|
|
951
|
-
/* @__PURE__ */
|
|
952
|
-
/* @__PURE__ */
|
|
953
|
-
/* @__PURE__ */
|
|
954
|
-
/* @__PURE__ */
|
|
955
|
-
), renderTeamField(), availableModels.length > 0 && /* @__PURE__ */
|
|
1707
|
+
/* @__PURE__ */ React4.createElement(MenuItem, { value: "1d" }, "1 Day"),
|
|
1708
|
+
/* @__PURE__ */ React4.createElement(MenuItem, { value: "7d" }, "7 Days"),
|
|
1709
|
+
/* @__PURE__ */ React4.createElement(MenuItem, { value: "30d" }, "30 Days"),
|
|
1710
|
+
/* @__PURE__ */ React4.createElement(MenuItem, { value: "90d" }, "90 Days"),
|
|
1711
|
+
/* @__PURE__ */ React4.createElement(MenuItem, { value: "1y" }, "1 Year")
|
|
1712
|
+
), renderTeamField(), availableModels.length > 0 && /* @__PURE__ */ React4.createElement(
|
|
956
1713
|
Autocomplete2,
|
|
957
1714
|
{
|
|
958
1715
|
multiple: true,
|
|
@@ -961,8 +1718,8 @@ var init_GenerateKeyDialog = __esm({
|
|
|
961
1718
|
getOptionLabel: (m) => m.model_name,
|
|
962
1719
|
value: selectedModels,
|
|
963
1720
|
onChange: (_e, selected) => setFormData({ ...formData, models: selected.map((m) => m.model_name) }),
|
|
964
|
-
renderOption: (props, m) => /* @__PURE__ */
|
|
965
|
-
renderInput: (params) => /* @__PURE__ */
|
|
1721
|
+
renderOption: (props, m) => /* @__PURE__ */ React4.createElement("li", { ...props }, modelOption(m)),
|
|
1722
|
+
renderInput: (params) => /* @__PURE__ */ React4.createElement(
|
|
966
1723
|
TextField2,
|
|
967
1724
|
{
|
|
968
1725
|
...params,
|
|
@@ -972,10 +1729,10 @@ var init_GenerateKeyDialog = __esm({
|
|
|
972
1729
|
}
|
|
973
1730
|
)
|
|
974
1731
|
}
|
|
975
|
-
), allowUnlimitedBudget && /* @__PURE__ */
|
|
1732
|
+
), allowUnlimitedBudget && /* @__PURE__ */ React4.createElement(
|
|
976
1733
|
FormControlLabel,
|
|
977
1734
|
{
|
|
978
|
-
control: /* @__PURE__ */
|
|
1735
|
+
control: /* @__PURE__ */ React4.createElement(
|
|
979
1736
|
Checkbox,
|
|
980
1737
|
{
|
|
981
1738
|
checked: unlimitedBudget,
|
|
@@ -991,7 +1748,7 @@ var init_GenerateKeyDialog = __esm({
|
|
|
991
1748
|
),
|
|
992
1749
|
label: "Unlimited budget"
|
|
993
1750
|
}
|
|
994
|
-
), /* @__PURE__ */
|
|
1751
|
+
), /* @__PURE__ */ React4.createElement(
|
|
995
1752
|
TextField2,
|
|
996
1753
|
{
|
|
997
1754
|
label: "Max Budget (USD)",
|
|
@@ -1004,7 +1761,7 @@ var init_GenerateKeyDialog = __esm({
|
|
|
1004
1761
|
required: true,
|
|
1005
1762
|
fullWidth: true
|
|
1006
1763
|
}
|
|
1007
|
-
), /* @__PURE__ */
|
|
1764
|
+
), /* @__PURE__ */ React4.createElement(
|
|
1008
1765
|
TextField2,
|
|
1009
1766
|
{
|
|
1010
1767
|
label: "TPM Limit",
|
|
@@ -1014,7 +1771,7 @@ var init_GenerateKeyDialog = __esm({
|
|
|
1014
1771
|
helperText: "Max tokens per minute this key can consume across all models. Leave blank for no limit.",
|
|
1015
1772
|
fullWidth: true
|
|
1016
1773
|
}
|
|
1017
|
-
), /* @__PURE__ */
|
|
1774
|
+
), /* @__PURE__ */ React4.createElement(
|
|
1018
1775
|
TextField2,
|
|
1019
1776
|
{
|
|
1020
1777
|
label: "RPM Limit",
|
|
@@ -1024,7 +1781,7 @@ var init_GenerateKeyDialog = __esm({
|
|
|
1024
1781
|
helperText: "Max requests per minute this key can make across all models. Leave blank for no limit.",
|
|
1025
1782
|
fullWidth: true
|
|
1026
1783
|
}
|
|
1027
|
-
))), /* @__PURE__ */
|
|
1784
|
+
))), /* @__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(
|
|
1028
1785
|
Button3,
|
|
1029
1786
|
{
|
|
1030
1787
|
onClick: handleGenerate,
|
|
@@ -1032,34 +1789,27 @@ var init_GenerateKeyDialog = __esm({
|
|
|
1032
1789
|
color: "primary",
|
|
1033
1790
|
disabled: !canGenerate
|
|
1034
1791
|
},
|
|
1035
|
-
submitting ? /* @__PURE__ */
|
|
1792
|
+
submitting ? /* @__PURE__ */ React4.createElement(CircularProgress2, { size: 24 }) : "Generate"
|
|
1036
1793
|
))));
|
|
1037
1794
|
};
|
|
1038
1795
|
}
|
|
1039
1796
|
});
|
|
1040
1797
|
|
|
1041
1798
|
// src/components/UsageStats.tsx
|
|
1042
|
-
import
|
|
1799
|
+
import React5, { useMemo as useMemo4, useState as useState3 } from "react";
|
|
1043
1800
|
import Paper3 from "@mui/material/Paper";
|
|
1044
|
-
import
|
|
1045
|
-
import
|
|
1046
|
-
import
|
|
1047
|
-
import InputLabel from "@mui/material/InputLabel";
|
|
1048
|
-
import Select from "@mui/material/Select";
|
|
1801
|
+
import Box5 from "@mui/material/Box";
|
|
1802
|
+
import Typography5 from "@mui/material/Typography";
|
|
1803
|
+
import TextField3 from "@mui/material/TextField";
|
|
1049
1804
|
import MenuItem2 from "@mui/material/MenuItem";
|
|
1050
1805
|
import Grid from "@mui/material/Grid";
|
|
1051
|
-
import Tabs2 from "@mui/material/Tabs";
|
|
1052
|
-
import Tab2 from "@mui/material/Tab";
|
|
1053
1806
|
import Table2 from "@mui/material/Table";
|
|
1054
1807
|
import TableBody2 from "@mui/material/TableBody";
|
|
1055
1808
|
import TableCell2 from "@mui/material/TableCell";
|
|
1056
1809
|
import TableContainer2 from "@mui/material/TableContainer";
|
|
1057
1810
|
import TableHead2 from "@mui/material/TableHead";
|
|
1058
1811
|
import TableRow2 from "@mui/material/TableRow";
|
|
1059
|
-
import
|
|
1060
|
-
import LinearProgress3 from "@mui/material/LinearProgress";
|
|
1061
|
-
import Skeleton from "@mui/material/Skeleton";
|
|
1062
|
-
import { useTheme } from "@mui/material/styles";
|
|
1812
|
+
import Skeleton3 from "@mui/material/Skeleton";
|
|
1063
1813
|
import {
|
|
1064
1814
|
AreaChart,
|
|
1065
1815
|
Area,
|
|
@@ -1073,48 +1823,46 @@ import {
|
|
|
1073
1823
|
Tooltip,
|
|
1074
1824
|
ResponsiveContainer,
|
|
1075
1825
|
Legend,
|
|
1076
|
-
ReferenceLine
|
|
1826
|
+
ReferenceLine,
|
|
1827
|
+
Cell
|
|
1077
1828
|
} from "recharts";
|
|
1078
|
-
function
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
return
|
|
1829
|
+
function rateTone(rate) {
|
|
1830
|
+
if (rate >= 0.99) return "success";
|
|
1831
|
+
if (rate >= 0.9) return "warning";
|
|
1832
|
+
return "danger";
|
|
1082
1833
|
}
|
|
1083
|
-
var TOP_N_MODELS, PERIOD_LS_KEY,
|
|
1834
|
+
var TOP_N_MODELS, PERIOD_LS_KEY, PRESET_LABELS, fmtPct, ChartSkeleton, ChartOrFallback, SuccessRateCell, UsageStats;
|
|
1084
1835
|
var init_UsageStats = __esm({
|
|
1085
1836
|
"src/components/UsageStats.tsx"() {
|
|
1086
1837
|
"use strict";
|
|
1087
1838
|
init_format();
|
|
1839
|
+
init_ui();
|
|
1088
1840
|
TOP_N_MODELS = 6;
|
|
1089
1841
|
PERIOD_LS_KEY = "litellm_usage_period";
|
|
1090
|
-
|
|
1091
|
-
"
|
|
1092
|
-
"
|
|
1093
|
-
"
|
|
1094
|
-
"
|
|
1095
|
-
|
|
1096
|
-
"#00C49F",
|
|
1097
|
-
"#FFBB28",
|
|
1098
|
-
"#FF8042",
|
|
1099
|
-
"#a4de6c",
|
|
1100
|
-
"#d0ed57"
|
|
1101
|
-
];
|
|
1842
|
+
PRESET_LABELS = {
|
|
1843
|
+
today: "Today",
|
|
1844
|
+
"24h": "Last 24 hours",
|
|
1845
|
+
"7d": "Last 7 days",
|
|
1846
|
+
"30d": "Last 30 days"
|
|
1847
|
+
};
|
|
1102
1848
|
fmtPct = (n) => `${(n * 100).toFixed(1)}%`;
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
}
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
height,
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1849
|
+
ChartSkeleton = ({ height = 240 }) => /* @__PURE__ */ React5.createElement(Skeleton3, { variant: "rounded", height });
|
|
1850
|
+
ChartOrFallback = ({ loading, empty, height = 240, children }) => {
|
|
1851
|
+
if (loading) return /* @__PURE__ */ React5.createElement(ChartSkeleton, { height });
|
|
1852
|
+
if (empty) return /* @__PURE__ */ React5.createElement(EmptyState, { message: "No data for this period", height });
|
|
1853
|
+
return /* @__PURE__ */ React5.createElement(React5.Fragment, null, children);
|
|
1854
|
+
};
|
|
1855
|
+
SuccessRateCell = ({ rate, requests }) => {
|
|
1856
|
+
if (requests === 0) return /* @__PURE__ */ React5.createElement(Typography5, { variant: "body2", color: "text.secondary" }, "\u2014");
|
|
1857
|
+
const tone = rateTone(rate);
|
|
1858
|
+
return /* @__PURE__ */ React5.createElement(Box5, { display: "flex", alignItems: "center", gap: 1 }, /* @__PURE__ */ React5.createElement(Box5, { width: 72, flexShrink: 0 }, /* @__PURE__ */ React5.createElement(Meter, { value: rate * 100, tone, height: 4 })), /* @__PURE__ */ React5.createElement(
|
|
1859
|
+
Typography5,
|
|
1860
|
+
{
|
|
1861
|
+
variant: "caption",
|
|
1862
|
+
sx: { fontVariantNumeric: "tabular-nums", minWidth: 44 }
|
|
1863
|
+
},
|
|
1864
|
+
fmtPct(rate)
|
|
1865
|
+
));
|
|
1118
1866
|
};
|
|
1119
1867
|
UsageStats = ({
|
|
1120
1868
|
usage,
|
|
@@ -1124,14 +1872,9 @@ var init_UsageStats = __esm({
|
|
|
1124
1872
|
loading,
|
|
1125
1873
|
userInfo
|
|
1126
1874
|
}) => {
|
|
1127
|
-
const
|
|
1875
|
+
const chart = useChartTheme();
|
|
1128
1876
|
const [selectedModel, setSelectedModel] = useState3("all");
|
|
1129
1877
|
const [tab, setTab] = useState3("costs");
|
|
1130
|
-
const gridStroke = theme.palette.divider;
|
|
1131
|
-
const tickFill = theme.palette.text.secondary;
|
|
1132
|
-
const tickStyle = { fontSize: 12, fill: tickFill };
|
|
1133
|
-
const tickStyleSmall = { fontSize: 11, fill: tickFill };
|
|
1134
|
-
const tickStyleTiny = { fontSize: 10, fill: tickFill };
|
|
1135
1878
|
const selectedPreset = useMemo4(() => {
|
|
1136
1879
|
if (dateRange.start.toDateString() === dateRange.end.toDateString()) return "today";
|
|
1137
1880
|
const diffMs = dateRange.end.getTime() - dateRange.start.getTime();
|
|
@@ -1239,90 +1982,225 @@ var init_UsageStats = __esm({
|
|
|
1239
1982
|
const overallSuccessRate = totalRequests > 0 ? (usage?.successful_requests ?? 0) / totalRequests : 0;
|
|
1240
1983
|
const maxBudget = userInfo?.max_budget ?? 0;
|
|
1241
1984
|
const totalCumSpend = cumulativeData[cumulativeData.length - 1]?.cumulative ?? 0;
|
|
1985
|
+
const cumulativeDomain = maxBudget > 0 ? [0, (dataMax) => Math.max(dataMax, maxBudget) * 1.05] : [0, "auto"];
|
|
1986
|
+
const successTone = totalRequests === 0 ? "neutral" : rateTone(overallSuccessRate);
|
|
1987
|
+
const metrics = [
|
|
1988
|
+
{
|
|
1989
|
+
label: "Total spend",
|
|
1990
|
+
value: fmtUsd(usage?.total_spend ?? 0),
|
|
1991
|
+
hint: maxBudget > 0 ? `of ${fmtUsd(maxBudget)} budget` : void 0,
|
|
1992
|
+
tone: "accent"
|
|
1993
|
+
},
|
|
1994
|
+
{
|
|
1995
|
+
label: "Requests",
|
|
1996
|
+
value: fmtInt(totalRequests),
|
|
1997
|
+
hint: `${fmtInt(usage?.failed_requests ?? 0)} failed`,
|
|
1998
|
+
tone: "info"
|
|
1999
|
+
},
|
|
2000
|
+
{
|
|
2001
|
+
label: "Success rate",
|
|
2002
|
+
value: totalRequests > 0 ? fmtPct(overallSuccessRate) : "\u2014",
|
|
2003
|
+
hint: totalRequests > 0 ? `${fmtInt(usage?.successful_requests ?? 0)} succeeded` : void 0,
|
|
2004
|
+
tone: successTone
|
|
2005
|
+
},
|
|
2006
|
+
{
|
|
2007
|
+
label: "Tokens",
|
|
2008
|
+
value: fmtCompact(usage?.total_tokens ?? 0),
|
|
2009
|
+
hint: `${fmtCompact(usage?.prompt_tokens ?? 0)} in \xB7 ${fmtCompact(usage?.completion_tokens ?? 0)} out`,
|
|
2010
|
+
tone: "success"
|
|
2011
|
+
}
|
|
2012
|
+
];
|
|
1242
2013
|
const renderKeyRows = () => {
|
|
1243
2014
|
if (loading) {
|
|
1244
|
-
return /* @__PURE__ */
|
|
2015
|
+
return /* @__PURE__ */ React5.createElement(TableRow2, null, /* @__PURE__ */ React5.createElement(TableCell2, { colSpan: 7, sx: { py: 3 } }, /* @__PURE__ */ React5.createElement(Skeleton3, { variant: "rounded", height: 80 })));
|
|
1245
2016
|
}
|
|
1246
2017
|
if (keyRows.length === 0) {
|
|
1247
|
-
return /* @__PURE__ */
|
|
2018
|
+
return /* @__PURE__ */ React5.createElement(TableRow2, null, /* @__PURE__ */ React5.createElement(TableCell2, { colSpan: 7 }, /* @__PURE__ */ React5.createElement(EmptyState, { message: "No key activity in this period" })));
|
|
1248
2019
|
}
|
|
1249
|
-
return [...keyRows].sort((a, b) => b.spend - a.spend || b.apiRequests - a.apiRequests).map((r) => /* @__PURE__ */
|
|
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")));
|
|
2020
|
+
return [...keyRows].sort((a, b) => b.spend - a.spend || b.apiRequests - a.apiRequests).map((r) => /* @__PURE__ */ React5.createElement(TableRow2, { key: r.keyHash }, /* @__PURE__ */ React5.createElement(TableCell2, null, /* @__PURE__ */ React5.createElement(Typography5, { variant: "body2", sx: { fontWeight: 600 } }, r.keyAlias), r.teamId ? /* @__PURE__ */ React5.createElement(Typography5, { variant: "caption", color: "text.secondary" }, "team: ", r.teamId) : null), /* @__PURE__ */ React5.createElement(TableCell2, null, /* @__PURE__ */ React5.createElement(Box5, { display: "flex", gap: 0.5, flexWrap: "wrap" }, r.models.slice(0, 3).map((m) => /* @__PURE__ */ React5.createElement(TagChip, { key: m, label: m, title: m })), r.models.length > 3 && /* @__PURE__ */ React5.createElement(TagChip, { label: `+${r.models.length - 3}`, mono: false }))), /* @__PURE__ */ React5.createElement(TableCell2, { align: "right" }, fmtUsd(r.spend)), /* @__PURE__ */ React5.createElement(TableCell2, { align: "right" }, fmtInt(r.apiRequests)), /* @__PURE__ */ React5.createElement(TableCell2, { align: "right" }, fmtCompact(r.totalTokens)), /* @__PURE__ */ React5.createElement(TableCell2, { align: "right" }, fmtInt(r.failedRequests)), /* @__PURE__ */ React5.createElement(TableCell2, null, /* @__PURE__ */ React5.createElement(SuccessRateCell, { rate: r.successRate, requests: r.apiRequests }))));
|
|
1257
2021
|
};
|
|
1258
2022
|
const renderModelRows = () => {
|
|
1259
2023
|
if (loading) {
|
|
1260
|
-
return /* @__PURE__ */
|
|
2024
|
+
return /* @__PURE__ */ React5.createElement(TableRow2, null, /* @__PURE__ */ React5.createElement(TableCell2, { colSpan: 8, sx: { py: 3 } }, /* @__PURE__ */ React5.createElement(Skeleton3, { variant: "rounded", height: 80 })));
|
|
1261
2025
|
}
|
|
1262
2026
|
if (modelRows.length === 0) {
|
|
1263
|
-
return /* @__PURE__ */
|
|
2027
|
+
return /* @__PURE__ */ React5.createElement(TableRow2, null, /* @__PURE__ */ React5.createElement(TableCell2, { colSpan: 8 }, /* @__PURE__ */ React5.createElement(EmptyState, { message: "No model activity in this period" })));
|
|
1264
2028
|
}
|
|
1265
|
-
return [...modelRows].sort((a, b) => b.spend - a.spend || b.totalTokens - a.totalTokens).map((r) => /* @__PURE__ */
|
|
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")));
|
|
2029
|
+
return [...modelRows].sort((a, b) => b.spend - a.spend || b.totalTokens - a.totalTokens).map((r) => /* @__PURE__ */ React5.createElement(TableRow2, { key: r.model }, /* @__PURE__ */ React5.createElement(TableCell2, null, /* @__PURE__ */ React5.createElement(Box5, { display: "flex", alignItems: "center", gap: 1 }, /* @__PURE__ */ React5.createElement(Box5, { sx: { width: 8, height: 8, borderRadius: "50%", bgcolor: chartColor(r.model), flexShrink: 0 } }), /* @__PURE__ */ React5.createElement(Typography5, { variant: "body2", sx: { fontWeight: 600 } }, r.model))), /* @__PURE__ */ React5.createElement(TableCell2, { align: "right" }, fmtUsd(r.spend)), /* @__PURE__ */ React5.createElement(TableCell2, { align: "right" }, fmtInt(r.apiRequests)), /* @__PURE__ */ React5.createElement(TableCell2, { align: "right" }, fmtInt(r.successfulRequests)), /* @__PURE__ */ React5.createElement(TableCell2, { align: "right" }, fmtInt(r.failedRequests)), /* @__PURE__ */ React5.createElement(TableCell2, { align: "right" }, fmtCompact(r.promptTokens)), /* @__PURE__ */ React5.createElement(TableCell2, { align: "right" }, fmtCompact(r.completionTokens)), /* @__PURE__ */ React5.createElement(TableCell2, null, /* @__PURE__ */ React5.createElement(SuccessRateCell, { rate: r.successRate, requests: r.apiRequests }))));
|
|
1273
2030
|
};
|
|
1274
|
-
|
|
1275
|
-
|
|
2031
|
+
const periodSelect = /* @__PURE__ */ React5.createElement(
|
|
2032
|
+
TextField3,
|
|
1276
2033
|
{
|
|
1277
|
-
|
|
2034
|
+
select: true,
|
|
2035
|
+
size: "small",
|
|
1278
2036
|
label: "Period",
|
|
1279
|
-
|
|
2037
|
+
value: selectedPreset,
|
|
2038
|
+
onChange: (e) => handlePresetChange(e.target.value),
|
|
2039
|
+
sx: { minWidth: 160 }
|
|
1280
2040
|
},
|
|
1281
|
-
/* @__PURE__ */
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
)), tab === "models" && /* @__PURE__ */ React4.createElement(FormControl, { size: "small", sx: { minWidth: 180 } }, /* @__PURE__ */ React4.createElement(InputLabel, null, "Model"), /* @__PURE__ */ React4.createElement(
|
|
1286
|
-
Select,
|
|
2041
|
+
Object.keys(PRESET_LABELS).map((p) => /* @__PURE__ */ React5.createElement(MenuItem2, { key: p, value: p }, PRESET_LABELS[p]))
|
|
2042
|
+
);
|
|
2043
|
+
return /* @__PURE__ */ React5.createElement(
|
|
2044
|
+
SectionCard,
|
|
1287
2045
|
{
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
2046
|
+
title: "Usage Analytics",
|
|
2047
|
+
subtitle: `${PRESET_LABELS[selectedPreset]} \xB7 ${dateRange.start.toLocaleDateString()} \u2013 ${dateRange.end.toLocaleDateString()}`,
|
|
2048
|
+
actions: periodSelect
|
|
1291
2049
|
},
|
|
1292
|
-
/* @__PURE__ */
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
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()))))));
|
|
2050
|
+
/* @__PURE__ */ React5.createElement(MetricStrip, { metrics }),
|
|
2051
|
+
/* @__PURE__ */ React5.createElement(
|
|
2052
|
+
Box5,
|
|
2053
|
+
{
|
|
2054
|
+
sx: {
|
|
2055
|
+
display: "flex",
|
|
2056
|
+
alignItems: "center",
|
|
2057
|
+
justifyContent: "space-between",
|
|
2058
|
+
gap: 2,
|
|
2059
|
+
flexWrap: "wrap",
|
|
2060
|
+
mt: 2.5,
|
|
2061
|
+
mb: 2
|
|
2062
|
+
}
|
|
2063
|
+
},
|
|
2064
|
+
/* @__PURE__ */ React5.createElement(
|
|
2065
|
+
SegmentedControl,
|
|
2066
|
+
{
|
|
2067
|
+
value: tab,
|
|
2068
|
+
onChange: setTab,
|
|
2069
|
+
options: [
|
|
2070
|
+
{ value: "costs", label: "Costs" },
|
|
2071
|
+
{ value: "models", label: "Model Activity" },
|
|
2072
|
+
{ value: "keys", label: "Key Activity" }
|
|
2073
|
+
]
|
|
2074
|
+
}
|
|
2075
|
+
),
|
|
2076
|
+
tab === "models" && /* @__PURE__ */ React5.createElement(
|
|
2077
|
+
TextField3,
|
|
2078
|
+
{
|
|
2079
|
+
select: true,
|
|
2080
|
+
size: "small",
|
|
2081
|
+
label: "Model",
|
|
2082
|
+
value: selectedModel,
|
|
2083
|
+
onChange: (e) => setSelectedModel(e.target.value),
|
|
2084
|
+
sx: { minWidth: 220 }
|
|
2085
|
+
},
|
|
2086
|
+
/* @__PURE__ */ React5.createElement(MenuItem2, { value: "all" }, "All models"),
|
|
2087
|
+
models.map((m) => /* @__PURE__ */ React5.createElement(MenuItem2, { key: m.model_name, value: m.model_name }, m.model_name))
|
|
2088
|
+
)
|
|
2089
|
+
),
|
|
2090
|
+
tab === "costs" && /* @__PURE__ */ React5.createElement(Grid, { container: true, spacing: 2 }, /* @__PURE__ */ React5.createElement(Grid, { item: true, xs: 12 }, /* @__PURE__ */ React5.createElement(ChartCard, { title: "Daily spend by model", height: 260 }, /* @__PURE__ */ React5.createElement(ChartOrFallback, { loading, empty: modelSpendByDate.length === 0, height: 260 }, /* @__PURE__ */ React5.createElement(ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ React5.createElement(AreaChart, { data: modelSpendByDate, margin: { top: 4, right: 8, left: 0, bottom: 0 } }, /* @__PURE__ */ React5.createElement(CartesianGrid, { ...chart.grid }), /* @__PURE__ */ React5.createElement(XAxis, { dataKey: "date", tickFormatter: fmtDateShort, ...chart.axis }), /* @__PURE__ */ React5.createElement(YAxis, { tickFormatter: fmtUsdCompact, width: 56, ...chart.axis }), /* @__PURE__ */ React5.createElement(
|
|
2091
|
+
Tooltip,
|
|
2092
|
+
{
|
|
2093
|
+
cursor: chart.cursor,
|
|
2094
|
+
content: /* @__PURE__ */ React5.createElement(ChartTooltip, { valueFormatter: fmtUsd, hideZero: true })
|
|
2095
|
+
}
|
|
2096
|
+
), topSpendModels.map((m) => /* @__PURE__ */ React5.createElement(
|
|
2097
|
+
Area,
|
|
2098
|
+
{
|
|
2099
|
+
key: m,
|
|
2100
|
+
type: "monotone",
|
|
2101
|
+
dataKey: m,
|
|
2102
|
+
stackId: "spend",
|
|
2103
|
+
stroke: chartColor(m),
|
|
2104
|
+
strokeWidth: 1.5,
|
|
2105
|
+
fill: chartColor(m),
|
|
2106
|
+
fillOpacity: 0.28
|
|
2107
|
+
}
|
|
2108
|
+
))))), !loading && topSpendModels.length > 0 && /* @__PURE__ */ React5.createElement(SeriesLegend, { series: topSpendModels.map((m) => ({ name: m, color: chartColor(m) })) }))), /* @__PURE__ */ React5.createElement(Grid, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ React5.createElement(ChartCard, { title: "Daily token usage" }, /* @__PURE__ */ React5.createElement(ChartOrFallback, { loading, empty: dailyData.length === 0 }, /* @__PURE__ */ React5.createElement(ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ React5.createElement(AreaChart, { data: dailyData, margin: { top: 4, right: 8, left: 0, bottom: 0 } }, /* @__PURE__ */ React5.createElement(CartesianGrid, { ...chart.grid }), /* @__PURE__ */ React5.createElement(XAxis, { dataKey: "date", tickFormatter: fmtDateShort, ...chart.axis }), /* @__PURE__ */ React5.createElement(YAxis, { tickFormatter: fmtCompact, width: 48, ...chart.axis }), /* @__PURE__ */ React5.createElement(Tooltip, { cursor: chart.cursor, content: /* @__PURE__ */ React5.createElement(ChartTooltip, { valueFormatter: fmtInt }) }), /* @__PURE__ */ React5.createElement(Legend, { ...chart.legend }), /* @__PURE__ */ React5.createElement(Area, { type: "monotone", dataKey: "promptTokens", name: "Input", stackId: "tok", stroke: SERIES.input, strokeWidth: 1.5, fill: SERIES.input, fillOpacity: 0.25 }), /* @__PURE__ */ React5.createElement(Area, { type: "monotone", dataKey: "completionTokens", name: "Output", stackId: "tok", stroke: SERIES.output, strokeWidth: 1.5, fill: SERIES.output, fillOpacity: 0.25 })))))), /* @__PURE__ */ React5.createElement(Grid, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ React5.createElement(ChartCard, { title: "Daily requests" }, /* @__PURE__ */ React5.createElement(ChartOrFallback, { loading, empty: dailyData.length === 0 }, /* @__PURE__ */ React5.createElement(ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ React5.createElement(BarChart, { data: dailyData, margin: { top: 4, right: 8, left: 0, bottom: 0 }, barCategoryGap: "30%" }, /* @__PURE__ */ React5.createElement(CartesianGrid, { ...chart.grid }), /* @__PURE__ */ React5.createElement(XAxis, { dataKey: "date", tickFormatter: fmtDateShort, ...chart.axis }), /* @__PURE__ */ React5.createElement(YAxis, { tickFormatter: fmtCompact, width: 48, ...chart.axis }), /* @__PURE__ */ React5.createElement(Tooltip, { cursor: chart.cursor, content: /* @__PURE__ */ React5.createElement(ChartTooltip, { valueFormatter: fmtInt }) }), /* @__PURE__ */ React5.createElement(Legend, { ...chart.legend }), /* @__PURE__ */ React5.createElement(Bar, { dataKey: "successfulRequests", name: "Successful", fill: SERIES.success, stackId: "r" }), /* @__PURE__ */ React5.createElement(Bar, { dataKey: "failedRequests", name: "Failed", fill: SERIES.failure, stackId: "r", radius: [3, 3, 0, 0] })))))), /* @__PURE__ */ React5.createElement(Grid, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ React5.createElement(ChartCard, { title: "Daily success rate" }, /* @__PURE__ */ React5.createElement(ChartOrFallback, { loading, empty: successRateData.length === 0 }, /* @__PURE__ */ React5.createElement(ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ React5.createElement(LineChart, { data: successRateData, margin: { top: 4, right: 8, left: 0, bottom: 0 } }, /* @__PURE__ */ React5.createElement(CartesianGrid, { ...chart.grid }), /* @__PURE__ */ React5.createElement(XAxis, { dataKey: "date", tickFormatter: fmtDateShort, ...chart.axis }), /* @__PURE__ */ React5.createElement(YAxis, { domain: [0, 100], tickFormatter: (v) => `${v}%`, width: 44, ...chart.axis }), /* @__PURE__ */ React5.createElement(
|
|
2109
|
+
Tooltip,
|
|
2110
|
+
{
|
|
2111
|
+
cursor: chart.cursor,
|
|
2112
|
+
content: /* @__PURE__ */ React5.createElement(ChartTooltip, { valueFormatter: (v) => `${v}%` })
|
|
2113
|
+
}
|
|
2114
|
+
), /* @__PURE__ */ React5.createElement(ReferenceLine, { y: 100, stroke: SERIES.success, strokeDasharray: "4 4", strokeOpacity: 0.5 }), /* @__PURE__ */ React5.createElement(
|
|
2115
|
+
Line,
|
|
2116
|
+
{
|
|
2117
|
+
type: "monotone",
|
|
2118
|
+
dataKey: "successRate",
|
|
2119
|
+
name: "Success rate",
|
|
2120
|
+
stroke: SERIES.input,
|
|
2121
|
+
strokeWidth: 2,
|
|
2122
|
+
dot: { r: 2.5, strokeWidth: 0, fill: SERIES.input },
|
|
2123
|
+
activeDot: { r: 4 }
|
|
2124
|
+
}
|
|
2125
|
+
)))))), /* @__PURE__ */ React5.createElement(Grid, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ React5.createElement(
|
|
2126
|
+
ChartCard,
|
|
2127
|
+
{
|
|
2128
|
+
title: maxBudget > 0 ? "Cumulative spend vs budget" : "Cumulative spend",
|
|
2129
|
+
meta: maxBudget > 0 ? `${fmtUsd(totalCumSpend)} used \xB7 ${fmtUsd(Math.max(0, maxBudget - totalCumSpend))} left` : void 0
|
|
2130
|
+
},
|
|
2131
|
+
/* @__PURE__ */ React5.createElement(ChartOrFallback, { loading, empty: cumulativeData.length === 0 }, /* @__PURE__ */ React5.createElement(ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ React5.createElement(AreaChart, { data: cumulativeData, margin: { top: 4, right: 8, left: 0, bottom: 0 } }, /* @__PURE__ */ React5.createElement(CartesianGrid, { ...chart.grid }), /* @__PURE__ */ React5.createElement(XAxis, { dataKey: "date", tickFormatter: fmtDateShort, ...chart.axis }), /* @__PURE__ */ React5.createElement(
|
|
2132
|
+
YAxis,
|
|
2133
|
+
{
|
|
2134
|
+
tickFormatter: fmtUsdCompact,
|
|
2135
|
+
width: 56,
|
|
2136
|
+
domain: cumulativeDomain,
|
|
2137
|
+
...chart.axis
|
|
2138
|
+
}
|
|
2139
|
+
), /* @__PURE__ */ React5.createElement(
|
|
2140
|
+
Tooltip,
|
|
2141
|
+
{
|
|
2142
|
+
cursor: chart.cursor,
|
|
2143
|
+
content: /* @__PURE__ */ React5.createElement(ChartTooltip, { valueFormatter: fmtUsd })
|
|
2144
|
+
}
|
|
2145
|
+
), maxBudget > 0 && /* @__PURE__ */ React5.createElement(
|
|
2146
|
+
ReferenceLine,
|
|
2147
|
+
{
|
|
2148
|
+
y: maxBudget,
|
|
2149
|
+
stroke: SERIES.budget,
|
|
2150
|
+
strokeDasharray: "5 4",
|
|
2151
|
+
label: { value: `Budget ${fmtUsd(maxBudget)}`, position: "insideTopRight", fontSize: 10, fill: SERIES.budget }
|
|
2152
|
+
}
|
|
2153
|
+
), /* @__PURE__ */ React5.createElement(
|
|
2154
|
+
Area,
|
|
2155
|
+
{
|
|
2156
|
+
type: "monotone",
|
|
2157
|
+
dataKey: "cumulative",
|
|
2158
|
+
name: "Cumulative spend",
|
|
2159
|
+
stroke: SERIES.spend,
|
|
2160
|
+
strokeWidth: 2,
|
|
2161
|
+
fill: SERIES.spend,
|
|
2162
|
+
fillOpacity: 0.18
|
|
2163
|
+
}
|
|
2164
|
+
))))
|
|
2165
|
+
))),
|
|
2166
|
+
tab === "models" && /* @__PURE__ */ React5.createElement(Grid, { container: true, spacing: 2 }, /* @__PURE__ */ React5.createElement(Grid, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ React5.createElement(ChartCard, { title: "Spend by model", height: Math.max(200, topModelSpendBars.length * 34) }, /* @__PURE__ */ React5.createElement(
|
|
2167
|
+
ChartOrFallback,
|
|
2168
|
+
{
|
|
2169
|
+
loading,
|
|
2170
|
+
empty: topModelSpendBars.length === 0,
|
|
2171
|
+
height: Math.max(200, topModelSpendBars.length * 34)
|
|
2172
|
+
},
|
|
2173
|
+
/* @__PURE__ */ React5.createElement(ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ React5.createElement(BarChart, { data: topModelSpendBars, layout: "vertical", margin: { top: 0, right: 12, left: 0, bottom: 0 } }, /* @__PURE__ */ React5.createElement(CartesianGrid, { ...chart.grid, vertical: true, horizontal: false }), /* @__PURE__ */ React5.createElement(XAxis, { type: "number", tickFormatter: fmtUsdCompact, ...chart.axis }), /* @__PURE__ */ React5.createElement(YAxis, { type: "category", dataKey: "model", width: 170, ...chart.axis, tick: { fontSize: 10.5, fill: chart.axis.tick.fill } }), /* @__PURE__ */ React5.createElement(Tooltip, { cursor: chart.cursor, content: /* @__PURE__ */ React5.createElement(ChartTooltip, { valueFormatter: fmtUsd, labelFormatter: (l) => l }) }), /* @__PURE__ */ React5.createElement(Bar, { dataKey: "spend", name: "Spend", radius: [0, 3, 3, 0], barSize: 14 }, topModelSpendBars.map((r) => /* @__PURE__ */ React5.createElement(Cell, { key: r.model, fill: chartColor(r.model) })))))
|
|
2174
|
+
))), /* @__PURE__ */ React5.createElement(Grid, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ React5.createElement(ChartCard, { title: "Tokens by model", height: Math.max(200, topModelSpendBars.length * 34) }, /* @__PURE__ */ React5.createElement(
|
|
2175
|
+
ChartOrFallback,
|
|
2176
|
+
{
|
|
2177
|
+
loading,
|
|
2178
|
+
empty: modelRows.length === 0,
|
|
2179
|
+
height: Math.max(200, topModelSpendBars.length * 34)
|
|
2180
|
+
},
|
|
2181
|
+
/* @__PURE__ */ React5.createElement(ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ React5.createElement(BarChart, { data: topModelSpendBars, layout: "vertical", margin: { top: 0, right: 12, left: 0, bottom: 0 } }, /* @__PURE__ */ React5.createElement(CartesianGrid, { ...chart.grid, vertical: true, horizontal: false }), /* @__PURE__ */ React5.createElement(XAxis, { type: "number", tickFormatter: fmtCompact, ...chart.axis }), /* @__PURE__ */ React5.createElement(YAxis, { type: "category", dataKey: "model", width: 170, ...chart.axis, tick: { fontSize: 10.5, fill: chart.axis.tick.fill } }), /* @__PURE__ */ React5.createElement(Tooltip, { cursor: chart.cursor, content: /* @__PURE__ */ React5.createElement(ChartTooltip, { valueFormatter: fmtInt, labelFormatter: (l) => l }) }), /* @__PURE__ */ React5.createElement(Legend, { ...chart.legend }), /* @__PURE__ */ React5.createElement(Bar, { dataKey: "promptTokens", name: "Input", fill: SERIES.input, stackId: "t", barSize: 14 }), /* @__PURE__ */ React5.createElement(Bar, { dataKey: "completionTokens", name: "Output", fill: SERIES.output, stackId: "t", barSize: 14, radius: [0, 3, 3, 0] })))
|
|
2182
|
+
))), /* @__PURE__ */ React5.createElement(Grid, { item: true, xs: 12 }, /* @__PURE__ */ React5.createElement(TableContainer2, { component: Paper3, variant: "outlined", sx: { borderRadius: 2 } }, /* @__PURE__ */ React5.createElement(Table2, { size: "small", sx: dataTableSx }, /* @__PURE__ */ React5.createElement(TableHead2, null, /* @__PURE__ */ React5.createElement(TableRow2, null, /* @__PURE__ */ React5.createElement(TableCell2, null, "Model"), /* @__PURE__ */ React5.createElement(TableCell2, { align: "right" }, "Spend"), /* @__PURE__ */ React5.createElement(TableCell2, { align: "right" }, "Requests"), /* @__PURE__ */ React5.createElement(TableCell2, { align: "right" }, "Success"), /* @__PURE__ */ React5.createElement(TableCell2, { align: "right" }, "Failed"), /* @__PURE__ */ React5.createElement(TableCell2, { align: "right" }, "Input"), /* @__PURE__ */ React5.createElement(TableCell2, { align: "right" }, "Output"), /* @__PURE__ */ React5.createElement(TableCell2, { sx: { width: 160 } }, "Success rate"))), /* @__PURE__ */ React5.createElement(TableBody2, null, renderModelRows()))))),
|
|
2183
|
+
tab === "keys" && /* @__PURE__ */ React5.createElement(Grid, { container: true, spacing: 2 }, (loading || topKeySpendBars.length > 0) && /* @__PURE__ */ React5.createElement(Grid, { item: true, xs: 12 }, /* @__PURE__ */ React5.createElement(ChartCard, { title: "Spend by key", height: Math.max(160, topKeySpendBars.length * 32) }, /* @__PURE__ */ React5.createElement(
|
|
2184
|
+
ChartOrFallback,
|
|
2185
|
+
{
|
|
2186
|
+
loading,
|
|
2187
|
+
empty: topKeySpendBars.length === 0,
|
|
2188
|
+
height: Math.max(160, topKeySpendBars.length * 32)
|
|
2189
|
+
},
|
|
2190
|
+
/* @__PURE__ */ React5.createElement(ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ React5.createElement(BarChart, { data: topKeySpendBars, layout: "vertical", margin: { top: 0, right: 12, left: 0, bottom: 0 } }, /* @__PURE__ */ React5.createElement(CartesianGrid, { ...chart.grid, vertical: true, horizontal: false }), /* @__PURE__ */ React5.createElement(XAxis, { type: "number", tickFormatter: fmtUsdCompact, ...chart.axis }), /* @__PURE__ */ React5.createElement(YAxis, { type: "category", dataKey: "keyAlias", width: 160, ...chart.axis, tick: { fontSize: 10.5, fill: chart.axis.tick.fill } }), /* @__PURE__ */ React5.createElement(Tooltip, { cursor: chart.cursor, content: /* @__PURE__ */ React5.createElement(ChartTooltip, { valueFormatter: fmtUsd, labelFormatter: (l) => l }) }), /* @__PURE__ */ React5.createElement(Bar, { dataKey: "spend", name: "Spend", fill: SERIES.input, radius: [0, 3, 3, 0], barSize: 14 })))
|
|
2191
|
+
))), /* @__PURE__ */ React5.createElement(Grid, { item: true, xs: 12 }, /* @__PURE__ */ React5.createElement(TableContainer2, { component: Paper3, variant: "outlined", sx: { borderRadius: 2 } }, /* @__PURE__ */ React5.createElement(Table2, { size: "small", sx: dataTableSx }, /* @__PURE__ */ React5.createElement(TableHead2, null, /* @__PURE__ */ React5.createElement(TableRow2, null, /* @__PURE__ */ React5.createElement(TableCell2, null, "Key"), /* @__PURE__ */ React5.createElement(TableCell2, null, "Models"), /* @__PURE__ */ React5.createElement(TableCell2, { align: "right" }, "Spend"), /* @__PURE__ */ React5.createElement(TableCell2, { align: "right" }, "Requests"), /* @__PURE__ */ React5.createElement(TableCell2, { align: "right" }, "Tokens"), /* @__PURE__ */ React5.createElement(TableCell2, { align: "right" }, "Failed"), /* @__PURE__ */ React5.createElement(TableCell2, { sx: { width: 160 } }, "Success rate"))), /* @__PURE__ */ React5.createElement(TableBody2, null, renderKeyRows())))))
|
|
2192
|
+
);
|
|
1313
2193
|
};
|
|
1314
2194
|
}
|
|
1315
2195
|
});
|
|
1316
2196
|
|
|
1317
2197
|
// src/components/TeamUsage.tsx
|
|
1318
|
-
import
|
|
2198
|
+
import React6, { useState as useState4 } from "react";
|
|
1319
2199
|
import Paper4 from "@mui/material/Paper";
|
|
1320
|
-
import
|
|
1321
|
-
import
|
|
1322
|
-
import
|
|
1323
|
-
import
|
|
1324
|
-
import Divider from "@mui/material/Divider";
|
|
1325
|
-
import Stack2 from "@mui/material/Stack";
|
|
2200
|
+
import Box6 from "@mui/material/Box";
|
|
2201
|
+
import Typography6 from "@mui/material/Typography";
|
|
2202
|
+
import Skeleton4 from "@mui/material/Skeleton";
|
|
2203
|
+
import Divider2 from "@mui/material/Divider";
|
|
1326
2204
|
import Table3 from "@mui/material/Table";
|
|
1327
2205
|
import TableBody3 from "@mui/material/TableBody";
|
|
1328
2206
|
import TableCell3 from "@mui/material/TableCell";
|
|
@@ -1332,8 +2210,8 @@ import TableContainer3 from "@mui/material/TableContainer";
|
|
|
1332
2210
|
import Collapse from "@mui/material/Collapse";
|
|
1333
2211
|
import IconButton3 from "@mui/material/IconButton";
|
|
1334
2212
|
import CircularProgress3 from "@mui/material/CircularProgress";
|
|
1335
|
-
import {
|
|
1336
|
-
import { ExpandMore,
|
|
2213
|
+
import { alpha as alpha4 } from "@mui/material/styles";
|
|
2214
|
+
import { ExpandMore, Group } from "@mui/icons-material";
|
|
1337
2215
|
import {
|
|
1338
2216
|
AreaChart as AreaChart2,
|
|
1339
2217
|
Area as Area2,
|
|
@@ -1343,21 +2221,20 @@ import {
|
|
|
1343
2221
|
Tooltip as Tooltip2,
|
|
1344
2222
|
ResponsiveContainer as ResponsiveContainer2
|
|
1345
2223
|
} from "recharts";
|
|
1346
|
-
function
|
|
1347
|
-
if (isOver) return "
|
|
2224
|
+
function budgetTone2(isOver, isNear) {
|
|
2225
|
+
if (isOver) return "danger";
|
|
1348
2226
|
if (isNear) return "warning";
|
|
1349
|
-
return "
|
|
2227
|
+
return "accent";
|
|
1350
2228
|
}
|
|
1351
|
-
var
|
|
2229
|
+
var fmtUsd2, TeamCard, TeamUsage;
|
|
1352
2230
|
var init_TeamUsage = __esm({
|
|
1353
2231
|
"src/components/TeamUsage.tsx"() {
|
|
1354
2232
|
"use strict";
|
|
1355
|
-
|
|
2233
|
+
init_ui();
|
|
2234
|
+
fmtUsd2 = (n) => `$${(n ?? 0).toFixed(2)}`;
|
|
1356
2235
|
TeamCard = ({ team, usage, usageLoading }) => {
|
|
1357
2236
|
const [expanded, setExpanded] = useState4(false);
|
|
1358
|
-
const
|
|
1359
|
-
const gridStroke = theme.palette.divider;
|
|
1360
|
-
const tickFill = theme.palette.text.secondary;
|
|
2237
|
+
const chart = useChartTheme();
|
|
1361
2238
|
const budget = team.max_budget ?? 0;
|
|
1362
2239
|
const spend = team.spend ?? 0;
|
|
1363
2240
|
const budgetPct = budget > 0 ? Math.min(spend / budget * 100, 100) : 0;
|
|
@@ -1366,73 +2243,131 @@ var init_TeamUsage = __esm({
|
|
|
1366
2243
|
const dailyData = usage?.daily_usage?.map((d) => ({ date: d.date, spend: d.spend })) ?? [];
|
|
1367
2244
|
const renderDailySpendSection = () => {
|
|
1368
2245
|
if (usageLoading) {
|
|
1369
|
-
return /* @__PURE__ */
|
|
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 } }));
|
|
2246
|
+
return /* @__PURE__ */ React6.createElement(Box6, { display: "flex", justifyContent: "center", py: 3 }, /* @__PURE__ */ React6.createElement(CircularProgress3, { size: 22 }));
|
|
1373
2247
|
}
|
|
1374
|
-
return null;
|
|
2248
|
+
if (dailyData.length === 0) return null;
|
|
2249
|
+
return /* @__PURE__ */ React6.createElement(Box6, { mb: 2.5 }, /* @__PURE__ */ React6.createElement(
|
|
2250
|
+
Typography6,
|
|
2251
|
+
{
|
|
2252
|
+
variant: "caption",
|
|
2253
|
+
color: "text.secondary",
|
|
2254
|
+
sx: { display: "block", fontSize: 11, fontWeight: 700, letterSpacing: "0.07em", textTransform: "uppercase", mb: 1 }
|
|
2255
|
+
},
|
|
2256
|
+
"Daily spend"
|
|
2257
|
+
), /* @__PURE__ */ React6.createElement(Box6, { height: 150 }, /* @__PURE__ */ React6.createElement(ResponsiveContainer2, { width: "100%", height: "100%" }, /* @__PURE__ */ React6.createElement(AreaChart2, { data: dailyData, margin: { top: 4, right: 8, left: 0, bottom: 0 } }, /* @__PURE__ */ React6.createElement(CartesianGrid2, { ...chart.grid }), /* @__PURE__ */ React6.createElement(XAxis2, { dataKey: "date", tickFormatter: fmtDateShort, ...chart.axis }), /* @__PURE__ */ React6.createElement(YAxis2, { tickFormatter: fmtUsdCompact, width: 52, ...chart.axis }), /* @__PURE__ */ React6.createElement(
|
|
2258
|
+
Tooltip2,
|
|
2259
|
+
{
|
|
2260
|
+
cursor: chart.cursor,
|
|
2261
|
+
content: /* @__PURE__ */ React6.createElement(ChartTooltip, { valueFormatter: (v) => `$${v.toFixed(4)}` })
|
|
2262
|
+
}
|
|
2263
|
+
), /* @__PURE__ */ React6.createElement(
|
|
2264
|
+
Area2,
|
|
2265
|
+
{
|
|
2266
|
+
type: "monotone",
|
|
2267
|
+
dataKey: "spend",
|
|
2268
|
+
name: "Spend",
|
|
2269
|
+
stroke: SERIES.spend,
|
|
2270
|
+
strokeWidth: 2,
|
|
2271
|
+
fill: SERIES.spend,
|
|
2272
|
+
fillOpacity: 0.18
|
|
2273
|
+
}
|
|
2274
|
+
)))));
|
|
1375
2275
|
};
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
{
|
|
1379
|
-
label: "Members",
|
|
1380
|
-
value: team.members_with_roles?.length ? String(team.members_with_roles.length) : "\u2014"
|
|
1381
|
-
}
|
|
1382
|
-
), /* @__PURE__ */ React5.createElement(
|
|
1383
|
-
Stat,
|
|
1384
|
-
{
|
|
1385
|
-
label: "Models",
|
|
1386
|
-
value: team.models?.length ? String(team.models.length) : "All"
|
|
1387
|
-
}
|
|
1388
|
-
), /* @__PURE__ */ React5.createElement(
|
|
1389
|
-
Stat,
|
|
1390
|
-
{
|
|
1391
|
-
label: "Budget",
|
|
1392
|
-
value: budget > 0 ? `$${budget.toFixed(2)}` : "Unlimited"
|
|
1393
|
-
}
|
|
1394
|
-
), /* @__PURE__ */ React5.createElement(
|
|
1395
|
-
Stat,
|
|
1396
|
-
{
|
|
1397
|
-
label: "Spend",
|
|
1398
|
-
value: `$${spend.toFixed(2)}`
|
|
1399
|
-
}
|
|
1400
|
-
), /* @__PURE__ */ React5.createElement(
|
|
1401
|
-
Stat,
|
|
1402
|
-
{
|
|
1403
|
-
label: "TPM",
|
|
1404
|
-
value: typeof team.tpm_limit === "number" && team.tpm_limit > 0 ? String(team.tpm_limit) : "\u2014"
|
|
1405
|
-
}
|
|
1406
|
-
), /* @__PURE__ */ React5.createElement(
|
|
1407
|
-
Stat,
|
|
2276
|
+
const sectionLabel = (label) => /* @__PURE__ */ React6.createElement(
|
|
2277
|
+
Typography6,
|
|
1408
2278
|
{
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
2279
|
+
variant: "caption",
|
|
2280
|
+
color: "text.secondary",
|
|
2281
|
+
sx: { display: "block", fontSize: 11, fontWeight: 700, letterSpacing: "0.07em", textTransform: "uppercase", mb: 1 }
|
|
2282
|
+
},
|
|
2283
|
+
label
|
|
2284
|
+
);
|
|
2285
|
+
return /* @__PURE__ */ React6.createElement(Paper4, { variant: "outlined", sx: { borderRadius: 2, p: 2.5 } }, /* @__PURE__ */ React6.createElement(Box6, { display: "flex", alignItems: "center", gap: 1.5 }, /* @__PURE__ */ React6.createElement(
|
|
2286
|
+
Box6,
|
|
1414
2287
|
{
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
2288
|
+
sx: (theme) => ({
|
|
2289
|
+
width: 36,
|
|
2290
|
+
height: 36,
|
|
2291
|
+
borderRadius: 1.5,
|
|
2292
|
+
flexShrink: 0,
|
|
2293
|
+
display: "flex",
|
|
2294
|
+
alignItems: "center",
|
|
2295
|
+
justifyContent: "center",
|
|
2296
|
+
bgcolor: alpha4(theme.palette.primary.main, 0.1),
|
|
2297
|
+
color: theme.palette.primary.main
|
|
2298
|
+
})
|
|
2299
|
+
},
|
|
2300
|
+
/* @__PURE__ */ React6.createElement(Group, { fontSize: "small" })
|
|
2301
|
+
), /* @__PURE__ */ React6.createElement(Box6, { flexGrow: 1, minWidth: 0 }, /* @__PURE__ */ React6.createElement(Typography6, { variant: "subtitle1", sx: { fontWeight: 600, lineHeight: 1.3 } }, team.team_alias || "Untitled team"), /* @__PURE__ */ React6.createElement(
|
|
2302
|
+
Typography6,
|
|
1422
2303
|
{
|
|
1423
2304
|
variant: "caption",
|
|
1424
2305
|
color: "text.secondary",
|
|
1425
|
-
sx: { fontFamily: "monospace" }
|
|
2306
|
+
sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 11 }
|
|
1426
2307
|
},
|
|
1427
|
-
|
|
1428
|
-
))
|
|
1429
|
-
|
|
2308
|
+
team.team_id
|
|
2309
|
+
)), isOver && /* @__PURE__ */ React6.createElement(StatusPill, { label: "Over budget", tone: "danger" }), isNear && /* @__PURE__ */ React6.createElement(StatusPill, { label: "Near limit", tone: "warning" }), /* @__PURE__ */ React6.createElement(
|
|
2310
|
+
IconButton3,
|
|
1430
2311
|
{
|
|
1431
|
-
label: m.role,
|
|
1432
2312
|
size: "small",
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
2313
|
+
onClick: () => setExpanded((e) => !e),
|
|
2314
|
+
"aria-label": expanded ? "Hide team details" : "Show team details",
|
|
2315
|
+
"aria-expanded": expanded,
|
|
2316
|
+
sx: (theme) => ({
|
|
2317
|
+
color: theme.palette.text.secondary,
|
|
2318
|
+
transform: expanded ? "rotate(180deg)" : "none",
|
|
2319
|
+
transition: theme.transitions.create("transform")
|
|
2320
|
+
})
|
|
2321
|
+
},
|
|
2322
|
+
/* @__PURE__ */ React6.createElement(ExpandMore, null)
|
|
2323
|
+
)), /* @__PURE__ */ React6.createElement(
|
|
2324
|
+
Box6,
|
|
2325
|
+
{
|
|
2326
|
+
sx: {
|
|
2327
|
+
display: "grid",
|
|
2328
|
+
gridTemplateColumns: { xs: "repeat(3, minmax(0, 1fr))", sm: "repeat(6, minmax(0, 1fr))" },
|
|
2329
|
+
gap: 2,
|
|
2330
|
+
mt: 2.5,
|
|
2331
|
+
maxWidth: 640
|
|
2332
|
+
}
|
|
2333
|
+
},
|
|
2334
|
+
/* @__PURE__ */ React6.createElement(Stat, { label: "Members", value: team.members_with_roles?.length ?? "\u2014" }),
|
|
2335
|
+
/* @__PURE__ */ React6.createElement(Stat, { label: "Models", value: team.models?.length ? team.models.length : "All" }),
|
|
2336
|
+
/* @__PURE__ */ React6.createElement(Stat, { label: "Budget", value: budget > 0 ? fmtUsd2(budget) : "Unlimited" }),
|
|
2337
|
+
/* @__PURE__ */ React6.createElement(Stat, { label: "Spend", value: fmtUsd2(spend) }),
|
|
2338
|
+
/* @__PURE__ */ React6.createElement(Stat, { label: "TPM", value: team.tpm_limit && team.tpm_limit > 0 ? team.tpm_limit.toLocaleString() : "\u2014" }),
|
|
2339
|
+
/* @__PURE__ */ React6.createElement(Stat, { label: "RPM", value: team.rpm_limit && team.rpm_limit > 0 ? team.rpm_limit.toLocaleString() : "\u2014" })
|
|
2340
|
+
), budget > 0 && /* @__PURE__ */ React6.createElement(Box6, { mt: 2, maxWidth: 640 }, /* @__PURE__ */ React6.createElement(Box6, { display: "flex", justifyContent: "space-between", alignItems: "baseline", mb: 0.75 }, /* @__PURE__ */ React6.createElement(Typography6, { variant: "caption", color: "text.secondary", sx: { fontVariantNumeric: "tabular-nums" } }, fmtUsd2(spend), " of ", fmtUsd2(budget)), /* @__PURE__ */ React6.createElement(Typography6, { variant: "caption", color: "text.secondary", sx: { fontVariantNumeric: "tabular-nums" } }, budgetPct.toFixed(0), "%")), /* @__PURE__ */ React6.createElement(Meter, { value: budgetPct, tone: budgetTone2(isOver, isNear), height: 5 })), /* @__PURE__ */ React6.createElement(Collapse, { in: expanded, unmountOnExit: true }, /* @__PURE__ */ React6.createElement(Divider2, { sx: { my: 2.5 } }), /* @__PURE__ */ React6.createElement(Box6, { mb: 2.5 }, sectionLabel("Models"), team.models?.length ? /* @__PURE__ */ React6.createElement(Box6, { display: "flex", gap: 0.5, flexWrap: "wrap" }, team.models.map((m) => /* @__PURE__ */ React6.createElement(TagChip, { key: m, label: m, title: m }))) : /* @__PURE__ */ React6.createElement(Typography6, { variant: "body2", color: "text.secondary" }, "All models allowed")), renderDailySpendSection(), /* @__PURE__ */ React6.createElement(Box6, null, sectionLabel("Members"), team.members_with_roles?.length ? /* @__PURE__ */ React6.createElement(
|
|
2341
|
+
TableContainer3,
|
|
2342
|
+
{
|
|
2343
|
+
component: Paper4,
|
|
2344
|
+
variant: "outlined",
|
|
2345
|
+
sx: { borderRadius: 1.5 }
|
|
2346
|
+
},
|
|
2347
|
+
/* @__PURE__ */ React6.createElement(Table3, { size: "small", sx: dataTableSx }, /* @__PURE__ */ React6.createElement(TableHead3, null, /* @__PURE__ */ React6.createElement(TableRow3, null, /* @__PURE__ */ React6.createElement(TableCell3, null, "User"), /* @__PURE__ */ React6.createElement(TableCell3, { align: "right" }, "Role"))), /* @__PURE__ */ React6.createElement(TableBody3, null, team.members_with_roles.map((m) => /* @__PURE__ */ React6.createElement(TableRow3, { key: m.user_id }, /* @__PURE__ */ React6.createElement(TableCell3, null, m.user_email ? /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(Typography6, { variant: "body2" }, m.user_email), /* @__PURE__ */ React6.createElement(
|
|
2348
|
+
Typography6,
|
|
2349
|
+
{
|
|
2350
|
+
variant: "caption",
|
|
2351
|
+
color: "text.secondary",
|
|
2352
|
+
sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 11 }
|
|
2353
|
+
},
|
|
2354
|
+
m.user_id
|
|
2355
|
+
)) : /* @__PURE__ */ React6.createElement(
|
|
2356
|
+
Typography6,
|
|
2357
|
+
{
|
|
2358
|
+
variant: "body2",
|
|
2359
|
+
sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace" }
|
|
2360
|
+
},
|
|
2361
|
+
m.user_id
|
|
2362
|
+
)), /* @__PURE__ */ React6.createElement(TableCell3, { align: "right" }, /* @__PURE__ */ React6.createElement(
|
|
2363
|
+
StatusPill,
|
|
2364
|
+
{
|
|
2365
|
+
label: m.role,
|
|
2366
|
+
tone: m.role === "admin" ? "accent" : "neutral",
|
|
2367
|
+
dot: false
|
|
2368
|
+
}
|
|
2369
|
+
))))))
|
|
2370
|
+
) : /* @__PURE__ */ React6.createElement(Typography6, { variant: "body2", color: "text.secondary" }, "No members assigned."))));
|
|
1436
2371
|
};
|
|
1437
2372
|
TeamUsage = ({
|
|
1438
2373
|
teams,
|
|
@@ -1441,12 +2376,18 @@ var init_TeamUsage = __esm({
|
|
|
1441
2376
|
getTeamUsageLoading
|
|
1442
2377
|
}) => {
|
|
1443
2378
|
if (loading) {
|
|
1444
|
-
return /* @__PURE__ */
|
|
2379
|
+
return /* @__PURE__ */ React6.createElement(SectionCard, { title: "Teams" }, /* @__PURE__ */ React6.createElement(Skeleton4, { variant: "rounded", height: 120, sx: { mb: 2 } }), /* @__PURE__ */ React6.createElement(Skeleton4, { variant: "rounded", height: 120 }));
|
|
1445
2380
|
}
|
|
1446
2381
|
if (!teams.length) {
|
|
1447
|
-
return /* @__PURE__ */
|
|
2382
|
+
return /* @__PURE__ */ React6.createElement(SectionCard, { title: "Teams" }, /* @__PURE__ */ React6.createElement(
|
|
2383
|
+
EmptyState,
|
|
2384
|
+
{
|
|
2385
|
+
message: "You're not a member of any LiteLLM team yet.",
|
|
2386
|
+
hint: "Your account is provisioned, so you can still generate personal keys and use models. Ask an admin to add you to a team if you need a shared budget."
|
|
2387
|
+
}
|
|
2388
|
+
));
|
|
1448
2389
|
}
|
|
1449
|
-
return /* @__PURE__ */
|
|
2390
|
+
return /* @__PURE__ */ React6.createElement(SectionCard, { title: "Teams", subtitle: `${teams.length} team${teams.length === 1 ? "" : "s"}` }, /* @__PURE__ */ React6.createElement(Box6, { sx: { display: "flex", flexDirection: "column", gap: 2 } }, teams.map((team) => /* @__PURE__ */ React6.createElement(
|
|
1450
2391
|
TeamCard,
|
|
1451
2392
|
{
|
|
1452
2393
|
key: team.team_id,
|
|
@@ -1454,16 +2395,15 @@ var init_TeamUsage = __esm({
|
|
|
1454
2395
|
usage: getTeamUsage(team.team_id),
|
|
1455
2396
|
usageLoading: getTeamUsageLoading(team.team_id)
|
|
1456
2397
|
}
|
|
1457
|
-
)));
|
|
2398
|
+
))));
|
|
1458
2399
|
};
|
|
1459
2400
|
}
|
|
1460
2401
|
});
|
|
1461
2402
|
|
|
1462
2403
|
// src/components/AuditLog.tsx
|
|
1463
|
-
import
|
|
1464
|
-
import
|
|
1465
|
-
import
|
|
1466
|
-
import Typography6 from "@mui/material/Typography";
|
|
2404
|
+
import React7, { useState as useState5, useCallback } from "react";
|
|
2405
|
+
import Box7 from "@mui/material/Box";
|
|
2406
|
+
import Typography7 from "@mui/material/Typography";
|
|
1467
2407
|
import Table4 from "@mui/material/Table";
|
|
1468
2408
|
import TableBody4 from "@mui/material/TableBody";
|
|
1469
2409
|
import TableCell4 from "@mui/material/TableCell";
|
|
@@ -1471,22 +2411,26 @@ import TableContainer4 from "@mui/material/TableContainer";
|
|
|
1471
2411
|
import TableHead4 from "@mui/material/TableHead";
|
|
1472
2412
|
import TableRow4 from "@mui/material/TableRow";
|
|
1473
2413
|
import TablePagination from "@mui/material/TablePagination";
|
|
1474
|
-
import
|
|
2414
|
+
import TextField4 from "@mui/material/TextField";
|
|
1475
2415
|
import MenuItem3 from "@mui/material/MenuItem";
|
|
1476
|
-
import
|
|
1477
|
-
import CircularProgress4 from "@mui/material/CircularProgress";
|
|
2416
|
+
import Skeleton5 from "@mui/material/Skeleton";
|
|
1478
2417
|
import Collapse2 from "@mui/material/Collapse";
|
|
1479
2418
|
import IconButton4 from "@mui/material/IconButton";
|
|
1480
2419
|
import Alert2 from "@mui/material/Alert";
|
|
1481
|
-
import {
|
|
2420
|
+
import { alpha as alpha5 } from "@mui/material/styles";
|
|
2421
|
+
import { KeyboardArrowDown } from "@mui/icons-material";
|
|
1482
2422
|
import { useAsync } from "react-use";
|
|
1483
|
-
function
|
|
1484
|
-
if (!action) return "
|
|
1485
|
-
for (const [key,
|
|
1486
|
-
if (action.toLowerCase().includes(key)) return
|
|
2423
|
+
function actionTone(action) {
|
|
2424
|
+
if (!action) return "neutral";
|
|
2425
|
+
for (const [key, tone] of Object.entries(ACTION_TONES)) {
|
|
2426
|
+
if (action.toLowerCase().includes(key)) return tone;
|
|
1487
2427
|
}
|
|
1488
2428
|
return "info";
|
|
1489
2429
|
}
|
|
2430
|
+
function prettyTableName(name) {
|
|
2431
|
+
if (!name) return "\u2014";
|
|
2432
|
+
return TABLE_LABELS[name] ?? name;
|
|
2433
|
+
}
|
|
1490
2434
|
function formatDateTime(iso) {
|
|
1491
2435
|
try {
|
|
1492
2436
|
return new Date(iso).toLocaleString();
|
|
@@ -1496,28 +2440,99 @@ function formatDateTime(iso) {
|
|
|
1496
2440
|
}
|
|
1497
2441
|
function renderAuditLogBody(loading, entries) {
|
|
1498
2442
|
if (loading) {
|
|
1499
|
-
return /* @__PURE__ */
|
|
2443
|
+
return /* @__PURE__ */ React7.createElement(TableRow4, null, /* @__PURE__ */ React7.createElement(TableCell4, { colSpan: 6, sx: { py: 2 } }, /* @__PURE__ */ React7.createElement(Skeleton5, { variant: "rounded", height: 160 })));
|
|
1500
2444
|
}
|
|
1501
2445
|
if (entries.length === 0) {
|
|
1502
|
-
return /* @__PURE__ */
|
|
2446
|
+
return /* @__PURE__ */ React7.createElement(TableRow4, null, /* @__PURE__ */ React7.createElement(TableCell4, { colSpan: 6 }, /* @__PURE__ */ React7.createElement(
|
|
2447
|
+
EmptyState,
|
|
2448
|
+
{
|
|
2449
|
+
message: "No audit events found",
|
|
2450
|
+
hint: "Try widening the filters or a different time window."
|
|
2451
|
+
}
|
|
2452
|
+
)));
|
|
1503
2453
|
}
|
|
1504
|
-
return entries.map((entry) => /* @__PURE__ */
|
|
2454
|
+
return entries.map((entry) => /* @__PURE__ */ React7.createElement(DetailRow, { key: entry.id, entry }));
|
|
1505
2455
|
}
|
|
1506
|
-
var
|
|
2456
|
+
var ACTION_TONES, TABLE_LABELS, DetailRow, AuditLog;
|
|
1507
2457
|
var init_AuditLog = __esm({
|
|
1508
2458
|
"src/components/AuditLog.tsx"() {
|
|
1509
2459
|
"use strict";
|
|
1510
|
-
|
|
2460
|
+
init_ui();
|
|
2461
|
+
ACTION_TONES = {
|
|
1511
2462
|
created: "success",
|
|
1512
|
-
deleted: "
|
|
2463
|
+
deleted: "danger",
|
|
1513
2464
|
updated: "warning",
|
|
1514
|
-
blocked: "
|
|
2465
|
+
blocked: "danger",
|
|
1515
2466
|
unblocked: "success"
|
|
1516
2467
|
};
|
|
2468
|
+
TABLE_LABELS = {
|
|
2469
|
+
LiteLLM_VerificationToken: "Key",
|
|
2470
|
+
LiteLLM_TeamTable: "Team",
|
|
2471
|
+
LiteLLM_UserTable: "User"
|
|
2472
|
+
};
|
|
1517
2473
|
DetailRow = ({ entry }) => {
|
|
1518
2474
|
const [open, setOpen] = useState5(false);
|
|
1519
2475
|
const hasDetail = entry.before_value || entry.updated_values;
|
|
1520
|
-
return /* @__PURE__ */
|
|
2476
|
+
return /* @__PURE__ */ React7.createElement(React7.Fragment, null, /* @__PURE__ */ React7.createElement(TableRow4, null, /* @__PURE__ */ React7.createElement(TableCell4, { sx: { width: 40, pr: 0 } }, hasDetail && /* @__PURE__ */ React7.createElement(
|
|
2477
|
+
IconButton4,
|
|
2478
|
+
{
|
|
2479
|
+
size: "small",
|
|
2480
|
+
onClick: () => setOpen((o) => !o),
|
|
2481
|
+
"aria-label": open ? "Hide changes" : "Show changes",
|
|
2482
|
+
"aria-expanded": open,
|
|
2483
|
+
sx: (theme) => ({
|
|
2484
|
+
color: theme.palette.text.secondary,
|
|
2485
|
+
transform: open ? "rotate(180deg)" : "none",
|
|
2486
|
+
transition: theme.transitions.create("transform")
|
|
2487
|
+
})
|
|
2488
|
+
},
|
|
2489
|
+
/* @__PURE__ */ React7.createElement(KeyboardArrowDown, { fontSize: "small" })
|
|
2490
|
+
)), /* @__PURE__ */ React7.createElement(TableCell4, { sx: { whiteSpace: "nowrap" } }, /* @__PURE__ */ React7.createElement(Typography7, { variant: "body2", color: "text.secondary" }, formatDateTime(entry.updated_at))), /* @__PURE__ */ React7.createElement(TableCell4, null, entry.action && /* @__PURE__ */ React7.createElement(StatusPill, { label: entry.action, tone: actionTone(entry.action) })), /* @__PURE__ */ React7.createElement(TableCell4, null, /* @__PURE__ */ React7.createElement(Typography7, { variant: "body2" }, prettyTableName(entry.table_name))), /* @__PURE__ */ React7.createElement(TableCell4, null, /* @__PURE__ */ React7.createElement(
|
|
2491
|
+
Typography7,
|
|
2492
|
+
{
|
|
2493
|
+
variant: "body2",
|
|
2494
|
+
component: "code",
|
|
2495
|
+
color: "text.secondary",
|
|
2496
|
+
title: entry.object_id ?? void 0,
|
|
2497
|
+
sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 12 }
|
|
2498
|
+
},
|
|
2499
|
+
entry.object_id ? entry.object_id.slice(0, 20) + (entry.object_id.length > 20 ? "\u2026" : "") : "\u2014"
|
|
2500
|
+
)), /* @__PURE__ */ React7.createElement(TableCell4, null, entry.changed_by ?? "\u2014")), hasDetail && /* @__PURE__ */ React7.createElement(TableRow4, { sx: { "&&:hover": { bgcolor: "transparent" } } }, /* @__PURE__ */ React7.createElement(TableCell4, { colSpan: 6, sx: { "&&": { py: 0, border: 0 } } }, /* @__PURE__ */ React7.createElement(Collapse2, { in: open, unmountOnExit: true }, /* @__PURE__ */ React7.createElement(
|
|
2501
|
+
Box7,
|
|
2502
|
+
{
|
|
2503
|
+
display: "flex",
|
|
2504
|
+
gap: 2,
|
|
2505
|
+
flexWrap: "wrap",
|
|
2506
|
+
sx: (theme) => ({
|
|
2507
|
+
p: 2,
|
|
2508
|
+
mb: 1.5,
|
|
2509
|
+
borderRadius: 1.5,
|
|
2510
|
+
bgcolor: alpha5(theme.palette.text.primary, theme.palette.mode === "dark" ? 0.05 : 0.03)
|
|
2511
|
+
})
|
|
2512
|
+
},
|
|
2513
|
+
entry.before_value && /* @__PURE__ */ React7.createElement(Box7, { flex: 1, minWidth: 200 }, /* @__PURE__ */ React7.createElement(
|
|
2514
|
+
Typography7,
|
|
2515
|
+
{
|
|
2516
|
+
variant: "caption",
|
|
2517
|
+
color: "text.secondary",
|
|
2518
|
+
display: "block",
|
|
2519
|
+
mb: 0.75,
|
|
2520
|
+
sx: { fontSize: 10.5, fontWeight: 700, letterSpacing: "0.07em", textTransform: "uppercase" }
|
|
2521
|
+
},
|
|
2522
|
+
"Before"
|
|
2523
|
+
), /* @__PURE__ */ React7.createElement(Typography7, { 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))),
|
|
2524
|
+
entry.updated_values && /* @__PURE__ */ React7.createElement(Box7, { flex: 1, minWidth: 200 }, /* @__PURE__ */ React7.createElement(
|
|
2525
|
+
Typography7,
|
|
2526
|
+
{
|
|
2527
|
+
variant: "caption",
|
|
2528
|
+
color: "text.secondary",
|
|
2529
|
+
display: "block",
|
|
2530
|
+
mb: 0.75,
|
|
2531
|
+
sx: { fontSize: 10.5, fontWeight: 700, letterSpacing: "0.07em", textTransform: "uppercase" }
|
|
2532
|
+
},
|
|
2533
|
+
"After"
|
|
2534
|
+
), /* @__PURE__ */ React7.createElement(Typography7, { 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)))
|
|
2535
|
+
)))));
|
|
1521
2536
|
};
|
|
1522
2537
|
AuditLog = ({ api }) => {
|
|
1523
2538
|
const [page, setPage] = useState5(0);
|
|
@@ -1533,68 +2548,80 @@ var init_AuditLog = __esm({
|
|
|
1533
2548
|
);
|
|
1534
2549
|
const entries = value?.audit_logs ?? [];
|
|
1535
2550
|
const total = value?.total ?? 0;
|
|
1536
|
-
return /* @__PURE__ */
|
|
1537
|
-
|
|
2551
|
+
return /* @__PURE__ */ React7.createElement(
|
|
2552
|
+
SectionCard,
|
|
1538
2553
|
{
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
2554
|
+
title: "Audit Log",
|
|
2555
|
+
subtitle: loading ? "Loading\u2026" : `${total.toLocaleString()} event${total === 1 ? "" : "s"}`,
|
|
2556
|
+
flush: true,
|
|
2557
|
+
actions: /* @__PURE__ */ React7.createElement(React7.Fragment, null, /* @__PURE__ */ React7.createElement(
|
|
2558
|
+
TextField4,
|
|
2559
|
+
{
|
|
2560
|
+
size: "small",
|
|
2561
|
+
label: "Action",
|
|
2562
|
+
select: true,
|
|
2563
|
+
value: filters.action ?? "",
|
|
2564
|
+
onChange: (e) => {
|
|
2565
|
+
setFilters((f) => ({ ...f, action: e.target.value || void 0 }));
|
|
2566
|
+
setPage(0);
|
|
2567
|
+
},
|
|
2568
|
+
sx: { minWidth: 150 }
|
|
2569
|
+
},
|
|
2570
|
+
/* @__PURE__ */ React7.createElement(MenuItem3, { value: "" }, "All actions"),
|
|
2571
|
+
/* @__PURE__ */ React7.createElement(MenuItem3, { value: "created" }, "Created"),
|
|
2572
|
+
/* @__PURE__ */ React7.createElement(MenuItem3, { value: "updated" }, "Updated"),
|
|
2573
|
+
/* @__PURE__ */ React7.createElement(MenuItem3, { value: "deleted" }, "Deleted"),
|
|
2574
|
+
/* @__PURE__ */ React7.createElement(MenuItem3, { value: "blocked" }, "Blocked")
|
|
2575
|
+
), /* @__PURE__ */ React7.createElement(
|
|
2576
|
+
TextField4,
|
|
2577
|
+
{
|
|
2578
|
+
size: "small",
|
|
2579
|
+
label: "Table",
|
|
2580
|
+
select: true,
|
|
2581
|
+
value: filters.table_name ?? "",
|
|
2582
|
+
onChange: (e) => {
|
|
2583
|
+
setFilters((f) => ({ ...f, table_name: e.target.value || void 0 }));
|
|
2584
|
+
setPage(0);
|
|
2585
|
+
},
|
|
2586
|
+
sx: { minWidth: 150 }
|
|
2587
|
+
},
|
|
2588
|
+
/* @__PURE__ */ React7.createElement(MenuItem3, { value: "" }, "All tables"),
|
|
2589
|
+
/* @__PURE__ */ React7.createElement(MenuItem3, { value: "LiteLLM_VerificationToken" }, "Key"),
|
|
2590
|
+
/* @__PURE__ */ React7.createElement(MenuItem3, { value: "LiteLLM_TeamTable" }, "Team"),
|
|
2591
|
+
/* @__PURE__ */ React7.createElement(MenuItem3, { value: "LiteLLM_UserTable" }, "User")
|
|
2592
|
+
), /* @__PURE__ */ React7.createElement(
|
|
2593
|
+
TextField4,
|
|
2594
|
+
{
|
|
2595
|
+
size: "small",
|
|
2596
|
+
label: "Changed by",
|
|
2597
|
+
value: filters.changed_by ?? "",
|
|
2598
|
+
onChange: (e) => {
|
|
2599
|
+
setFilters((f) => ({ ...f, changed_by: e.target.value || void 0 }));
|
|
2600
|
+
setPage(0);
|
|
2601
|
+
},
|
|
2602
|
+
sx: { minWidth: 180 }
|
|
2603
|
+
}
|
|
2604
|
+
))
|
|
1566
2605
|
},
|
|
1567
|
-
/* @__PURE__ */
|
|
1568
|
-
/* @__PURE__ */
|
|
1569
|
-
/* @__PURE__ */
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
component: "div",
|
|
1587
|
-
count: total,
|
|
1588
|
-
page,
|
|
1589
|
-
onPageChange: (_, p) => setPage(p),
|
|
1590
|
-
rowsPerPage: pageSize,
|
|
1591
|
-
onRowsPerPageChange: (e) => {
|
|
1592
|
-
setPageSize(Number(e.target.value));
|
|
1593
|
-
setPage(0);
|
|
1594
|
-
},
|
|
1595
|
-
rowsPerPageOptions: [10, 25, 50]
|
|
1596
|
-
}
|
|
1597
|
-
));
|
|
2606
|
+
error && /* @__PURE__ */ React7.createElement(Box7, { px: 2.5, pb: 2 }, /* @__PURE__ */ React7.createElement(Alert2, { severity: "error" }, error.message)),
|
|
2607
|
+
/* @__PURE__ */ React7.createElement(TableContainer4, null, /* @__PURE__ */ React7.createElement(Table4, { size: "small", sx: dataTableSx }, /* @__PURE__ */ React7.createElement(TableHead4, null, /* @__PURE__ */ React7.createElement(TableRow4, null, /* @__PURE__ */ React7.createElement(TableCell4, { sx: { width: 40 } }), /* @__PURE__ */ React7.createElement(TableCell4, null, "Time"), /* @__PURE__ */ React7.createElement(TableCell4, null, "Action"), /* @__PURE__ */ React7.createElement(TableCell4, null, "Table"), /* @__PURE__ */ React7.createElement(TableCell4, null, "Object ID"), /* @__PURE__ */ React7.createElement(TableCell4, null, "Changed By"))), /* @__PURE__ */ React7.createElement(TableBody4, null, renderAuditLogBody(loading, entries)))),
|
|
2608
|
+
/* @__PURE__ */ React7.createElement(
|
|
2609
|
+
TablePagination,
|
|
2610
|
+
{
|
|
2611
|
+
component: "div",
|
|
2612
|
+
count: total,
|
|
2613
|
+
page,
|
|
2614
|
+
onPageChange: (_, p) => setPage(p),
|
|
2615
|
+
rowsPerPage: pageSize,
|
|
2616
|
+
onRowsPerPageChange: (e) => {
|
|
2617
|
+
setPageSize(Number(e.target.value));
|
|
2618
|
+
setPage(0);
|
|
2619
|
+
},
|
|
2620
|
+
rowsPerPageOptions: [10, 25, 50],
|
|
2621
|
+
sx: (theme) => ({ borderTop: `1px solid ${theme.palette.divider}` })
|
|
2622
|
+
}
|
|
2623
|
+
)
|
|
2624
|
+
);
|
|
1598
2625
|
};
|
|
1599
2626
|
}
|
|
1600
2627
|
});
|
|
@@ -1604,15 +2631,15 @@ var LiteLLMPage_exports = {};
|
|
|
1604
2631
|
__export(LiteLLMPage_exports, {
|
|
1605
2632
|
LiteLLMPage: () => LiteLLMPage
|
|
1606
2633
|
});
|
|
1607
|
-
import
|
|
1608
|
-
import
|
|
2634
|
+
import React8, { useState as useState6, useCallback as useCallback2, useMemo as useMemo5 } from "react";
|
|
2635
|
+
import Box8 from "@mui/material/Box";
|
|
1609
2636
|
import Snackbar from "@mui/material/Snackbar";
|
|
1610
2637
|
import Alert3 from "@mui/material/Alert";
|
|
1611
|
-
import
|
|
1612
|
-
import
|
|
1613
|
-
import
|
|
1614
|
-
import
|
|
1615
|
-
import
|
|
2638
|
+
import CircularProgress4 from "@mui/material/CircularProgress";
|
|
2639
|
+
import Typography8 from "@mui/material/Typography";
|
|
2640
|
+
import Paper5 from "@mui/material/Paper";
|
|
2641
|
+
import Tabs2 from "@mui/material/Tabs";
|
|
2642
|
+
import Tab2 from "@mui/material/Tab";
|
|
1616
2643
|
import { useAsync as useAsync2, useAsyncRetry } from "react-use";
|
|
1617
2644
|
import { useApi } from "@backstage/core-plugin-api";
|
|
1618
2645
|
function initDateRange() {
|
|
@@ -1817,34 +2844,42 @@ var init_LiteLLMPage = __esm({
|
|
|
1817
2844
|
);
|
|
1818
2845
|
const isInitialLoading = userLoading && !userInfo;
|
|
1819
2846
|
if (isInitialLoading) {
|
|
1820
|
-
return /* @__PURE__ */
|
|
2847
|
+
return /* @__PURE__ */ React8.createElement(Box8, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: "50vh" }, /* @__PURE__ */ React8.createElement(CircularProgress4, null));
|
|
1821
2848
|
}
|
|
1822
2849
|
if (userError || !userInfo) {
|
|
1823
2850
|
const isProvisioningEnabled = userError?.body?.provisioning === true;
|
|
1824
2851
|
const hint = userError?.body?.hint;
|
|
1825
|
-
return /* @__PURE__ */
|
|
2852
|
+
return /* @__PURE__ */ React8.createElement(Box8, { p: 3 }, /* @__PURE__ */ React8.createElement(Paper5, { sx: { p: 3 } }, /* @__PURE__ */ React8.createElement(Typography8, { variant: "h6", gutterBottom: true }, "Account not provisioned"), /* @__PURE__ */ React8.createElement(Typography8, { color: "text.secondary", paragraph: true }, "Your Backstage account is not linked to a LiteLLM user."), hint ? /* @__PURE__ */ React8.createElement(Typography8, { variant: "body2", color: "text.secondary" }, hint) : /* @__PURE__ */ React8.createElement(Typography8, { 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.")));
|
|
1826
2853
|
}
|
|
1827
|
-
|
|
2854
|
+
const pageTabs = /* @__PURE__ */ React8.createElement(
|
|
2855
|
+
Tabs2,
|
|
2856
|
+
{
|
|
2857
|
+
value: activeTab,
|
|
2858
|
+
onChange: (_, v) => setActiveTab(v),
|
|
2859
|
+
variant: "scrollable",
|
|
2860
|
+
scrollButtons: "auto",
|
|
2861
|
+
sx: {
|
|
2862
|
+
minHeight: 44,
|
|
2863
|
+
'& [class*="MuiTabs-indicator"]': { height: 2, borderRadius: "2px 2px 0 0" },
|
|
2864
|
+
'& [class*="MuiTab-root"]': { minHeight: 44, textTransform: "none", fontSize: 14 }
|
|
2865
|
+
}
|
|
2866
|
+
},
|
|
2867
|
+
/* @__PURE__ */ React8.createElement(Tab2, { label: "Overview", value: "overview" }),
|
|
2868
|
+
/* @__PURE__ */ React8.createElement(Tab2, { label: "Keys", value: "keys" }),
|
|
2869
|
+
/* @__PURE__ */ React8.createElement(Tab2, { label: "Teams", value: "teams" }),
|
|
2870
|
+
userInfo.can_view_audit && /* @__PURE__ */ React8.createElement(Tab2, { label: "Audit Log", value: "audit" })
|
|
2871
|
+
);
|
|
2872
|
+
return /* @__PURE__ */ React8.createElement(Box8, { sx: { p: 3, display: "flex", flexDirection: "column", gap: 2 } }, /* @__PURE__ */ React8.createElement(
|
|
1828
2873
|
DashboardHeader,
|
|
1829
2874
|
{
|
|
1830
2875
|
userInfo,
|
|
1831
2876
|
teams: teams ?? [],
|
|
1832
2877
|
keys: keys ?? [],
|
|
1833
2878
|
loading: userLoading || teamsLoading,
|
|
1834
|
-
onGenerateKeyClick: () => setGenerateDialogOpen(true)
|
|
2879
|
+
onGenerateKeyClick: () => setGenerateDialogOpen(true),
|
|
2880
|
+
tabs: pageTabs
|
|
1835
2881
|
}
|
|
1836
|
-
)
|
|
1837
|
-
Tabs3,
|
|
1838
|
-
{
|
|
1839
|
-
value: activeTab,
|
|
1840
|
-
onChange: (_, v) => setActiveTab(v),
|
|
1841
|
-
sx: { mb: 2, borderBottom: 1, borderColor: "divider" }
|
|
1842
|
-
},
|
|
1843
|
-
/* @__PURE__ */ React7.createElement(Tab3, { label: "Overview", value: "overview" }),
|
|
1844
|
-
/* @__PURE__ */ React7.createElement(Tab3, { label: "Keys", value: "keys" }),
|
|
1845
|
-
/* @__PURE__ */ React7.createElement(Tab3, { label: "Teams", value: "teams" }),
|
|
1846
|
-
userInfo.can_view_audit && /* @__PURE__ */ React7.createElement(Tab3, { label: "Audit Log", value: "audit" })
|
|
1847
|
-
), activeTab === "overview" && /* @__PURE__ */ React7.createElement(
|
|
2882
|
+
), activeTab === "overview" && /* @__PURE__ */ React8.createElement(
|
|
1848
2883
|
UsageStats,
|
|
1849
2884
|
{
|
|
1850
2885
|
usage: usage ?? null,
|
|
@@ -1854,7 +2889,7 @@ var init_LiteLLMPage = __esm({
|
|
|
1854
2889
|
loading: usageLoading,
|
|
1855
2890
|
userInfo
|
|
1856
2891
|
}
|
|
1857
|
-
), activeTab === "keys" && /* @__PURE__ */
|
|
2892
|
+
), activeTab === "keys" && /* @__PURE__ */ React8.createElement(
|
|
1858
2893
|
KeysTable,
|
|
1859
2894
|
{
|
|
1860
2895
|
keys: keys ?? [],
|
|
@@ -1868,7 +2903,7 @@ var init_LiteLLMPage = __esm({
|
|
|
1868
2903
|
onDeleteKey: handleDeleteKey,
|
|
1869
2904
|
onPruneExpiredKeys: handlePruneExpiredKeys
|
|
1870
2905
|
}
|
|
1871
|
-
), activeTab === "teams" && /* @__PURE__ */
|
|
2906
|
+
), activeTab === "teams" && /* @__PURE__ */ React8.createElement(
|
|
1872
2907
|
TeamUsage,
|
|
1873
2908
|
{
|
|
1874
2909
|
teams: teams ?? [],
|
|
@@ -1879,7 +2914,7 @@ var init_LiteLLMPage = __esm({
|
|
|
1879
2914
|
},
|
|
1880
2915
|
getTeamUsageLoading: (teamId) => teamUsageLoading[teamId] ?? false
|
|
1881
2916
|
}
|
|
1882
|
-
), activeTab === "audit" && userInfo.can_view_audit && /* @__PURE__ */
|
|
2917
|
+
), activeTab === "audit" && userInfo.can_view_audit && /* @__PURE__ */ React8.createElement(AuditLog, { api }), /* @__PURE__ */ React8.createElement(
|
|
1883
2918
|
GenerateKeyDialog,
|
|
1884
2919
|
{
|
|
1885
2920
|
open: generateDialogOpen,
|
|
@@ -1892,7 +2927,7 @@ var init_LiteLLMPage = __esm({
|
|
|
1892
2927
|
onGenerateKey: handleGenerateKey,
|
|
1893
2928
|
onGetConfig: () => api.getConfig()
|
|
1894
2929
|
}
|
|
1895
|
-
), /* @__PURE__ */
|
|
2930
|
+
), /* @__PURE__ */ React8.createElement(
|
|
1896
2931
|
Snackbar,
|
|
1897
2932
|
{
|
|
1898
2933
|
open: !!snackbar,
|
|
@@ -1900,7 +2935,7 @@ var init_LiteLLMPage = __esm({
|
|
|
1900
2935
|
onClose: () => setSnackbar(null),
|
|
1901
2936
|
anchorOrigin: { vertical: "bottom", horizontal: "right" }
|
|
1902
2937
|
},
|
|
1903
|
-
snackbar ? /* @__PURE__ */
|
|
2938
|
+
snackbar ? /* @__PURE__ */ React8.createElement(Alert3, { severity: snackbar.severity, onClose: () => setSnackbar(null) }, snackbar.message) : void 0
|
|
1904
2939
|
));
|
|
1905
2940
|
};
|
|
1906
2941
|
}
|
|
@@ -1908,7 +2943,7 @@ var init_LiteLLMPage = __esm({
|
|
|
1908
2943
|
|
|
1909
2944
|
// src/plugin.tsx
|
|
1910
2945
|
init_api();
|
|
1911
|
-
import
|
|
2946
|
+
import React9 from "react";
|
|
1912
2947
|
import { TrendingUp as TrendingUpIcon } from "@mui/icons-material";
|
|
1913
2948
|
import {
|
|
1914
2949
|
createFrontendPlugin,
|
|
@@ -1927,10 +2962,10 @@ var liteLlmPage = PageBlueprint.make({
|
|
|
1927
2962
|
params: {
|
|
1928
2963
|
path: "/litellm",
|
|
1929
2964
|
title: "LiteLLM",
|
|
1930
|
-
icon: /* @__PURE__ */
|
|
2965
|
+
icon: /* @__PURE__ */ React9.createElement(TrendingUpIcon, null),
|
|
1931
2966
|
loader: async () => {
|
|
1932
2967
|
const { LiteLLMPage: LiteLLMPage2 } = await Promise.resolve().then(() => (init_LiteLLMPage(), LiteLLMPage_exports));
|
|
1933
|
-
return /* @__PURE__ */
|
|
2968
|
+
return /* @__PURE__ */ React9.createElement(LiteLLMPage2, null);
|
|
1934
2969
|
}
|
|
1935
2970
|
}
|
|
1936
2971
|
});
|
|
@@ -1949,15 +2984,15 @@ init_TeamUsage();
|
|
|
1949
2984
|
// src/components/LiteLLMHomeWidget.tsx
|
|
1950
2985
|
init_api();
|
|
1951
2986
|
init_format();
|
|
1952
|
-
import
|
|
1953
|
-
import
|
|
1954
|
-
import
|
|
1955
|
-
import
|
|
1956
|
-
import
|
|
1957
|
-
import
|
|
2987
|
+
import React10, { useState as useState7, useEffect as useEffect2 } from "react";
|
|
2988
|
+
import Paper6 from "@mui/material/Paper";
|
|
2989
|
+
import Box9 from "@mui/material/Box";
|
|
2990
|
+
import Typography9 from "@mui/material/Typography";
|
|
2991
|
+
import FormControl from "@mui/material/FormControl";
|
|
2992
|
+
import Select from "@mui/material/Select";
|
|
1958
2993
|
import MenuItem4 from "@mui/material/MenuItem";
|
|
1959
2994
|
import Grid2 from "@mui/material/Grid";
|
|
1960
|
-
import
|
|
2995
|
+
import CircularProgress5 from "@mui/material/CircularProgress";
|
|
1961
2996
|
import Alert4 from "@mui/material/Alert";
|
|
1962
2997
|
import { AreaChart as AreaChart3, Area as Area3, ResponsiveContainer as ResponsiveContainer3 } from "recharts";
|
|
1963
2998
|
import { useApi as useApi2 } from "@backstage/core-plugin-api";
|
|
@@ -1973,7 +3008,7 @@ function presetToDateRange(preset) {
|
|
|
1973
3008
|
}
|
|
1974
3009
|
return { start, end };
|
|
1975
3010
|
}
|
|
1976
|
-
var Kpi = ({ label, value }) => /* @__PURE__ */
|
|
3011
|
+
var Kpi = ({ label, value }) => /* @__PURE__ */ React10.createElement(Box9, null, /* @__PURE__ */ React10.createElement(Typography9, { variant: "caption", color: "text.secondary", display: "block" }, label), /* @__PURE__ */ React10.createElement(Typography9, { variant: "subtitle1", fontWeight: 600 }, value));
|
|
1977
3012
|
var LiteLLMHomeWidget = ({
|
|
1978
3013
|
defaultPeriod = "7d",
|
|
1979
3014
|
title = "LiteLLM Usage"
|
|
@@ -2022,17 +3057,17 @@ var LiteLLMHomeWidget = ({
|
|
|
2022
3057
|
spend: d.spend
|
|
2023
3058
|
}));
|
|
2024
3059
|
const hasSparkline = dailyData.length > 0;
|
|
2025
|
-
return /* @__PURE__ */
|
|
2026
|
-
|
|
3060
|
+
return /* @__PURE__ */ React10.createElement(Paper6, { sx: { p: 2 } }, /* @__PURE__ */ React10.createElement(Box9, { display: "flex", justifyContent: "space-between", alignItems: "center", mb: 1.5 }, /* @__PURE__ */ React10.createElement(Typography9, { variant: "h6" }, title), /* @__PURE__ */ React10.createElement(FormControl, { size: "small", sx: { minWidth: 90 } }, /* @__PURE__ */ React10.createElement(
|
|
3061
|
+
Select,
|
|
2027
3062
|
{
|
|
2028
3063
|
value: period,
|
|
2029
3064
|
onChange: (e) => setPeriod(e.target.value),
|
|
2030
3065
|
displayEmpty: true
|
|
2031
3066
|
},
|
|
2032
|
-
/* @__PURE__ */
|
|
2033
|
-
/* @__PURE__ */
|
|
2034
|
-
/* @__PURE__ */
|
|
2035
|
-
))), loading && /* @__PURE__ */
|
|
3067
|
+
/* @__PURE__ */ React10.createElement(MenuItem4, { value: "today" }, "Today"),
|
|
3068
|
+
/* @__PURE__ */ React10.createElement(MenuItem4, { value: "7d" }, "7d"),
|
|
3069
|
+
/* @__PURE__ */ React10.createElement(MenuItem4, { value: "30d" }, "30d")
|
|
3070
|
+
))), loading && /* @__PURE__ */ React10.createElement(Box9, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: 120 }, /* @__PURE__ */ React10.createElement(CircularProgress5, { size: 32 })), !loading && totalFailure && /* @__PURE__ */ React10.createElement(Alert4, { severity: "error", sx: { mt: 1 } }, usageError ?? "Failed to load usage data"), !loading && !totalFailure && /* @__PURE__ */ React10.createElement(React10.Fragment, null, partialFailure && /* @__PURE__ */ React10.createElement(Alert4, { severity: "warning", sx: { mt: 1, mb: 1 } }, usageError ? `Usage data unavailable (${usageError}).` : "", keysError ? ` Key list unavailable (${keysError}).` : "", " Showing what loaded."), /* @__PURE__ */ React10.createElement(Grid2, { container: true, spacing: 2, sx: { mb: hasSparkline ? 1.5 : 0 } }, /* @__PURE__ */ React10.createElement(Grid2, { item: true, xs: 6 }, /* @__PURE__ */ React10.createElement(Kpi, { label: "USD Spent", value: fmtUsd(usage?.total_spend ?? 0) })), /* @__PURE__ */ React10.createElement(Grid2, { item: true, xs: 6 }, /* @__PURE__ */ React10.createElement(Kpi, { label: "Tokens In", value: fmtInt(usage?.prompt_tokens ?? 0) })), /* @__PURE__ */ React10.createElement(Grid2, { item: true, xs: 6 }, /* @__PURE__ */ React10.createElement(Kpi, { label: "Tokens Out", value: fmtInt(usage?.completion_tokens ?? 0) })), /* @__PURE__ */ React10.createElement(Grid2, { item: true, xs: 6 }, /* @__PURE__ */ React10.createElement(Kpi, { label: "Keys", value: fmtInt(keys.length) }))), hasSparkline && /* @__PURE__ */ React10.createElement(Box9, { height: 120 }, /* @__PURE__ */ React10.createElement(ResponsiveContainer3, { width: "100%", height: "100%" }, /* @__PURE__ */ React10.createElement(AreaChart3, { data: dailyData, margin: { top: 4, right: 0, bottom: 0, left: 0 } }, /* @__PURE__ */ React10.createElement(
|
|
2036
3071
|
Area3,
|
|
2037
3072
|
{
|
|
2038
3073
|
type: "monotone",
|