@acarmisc/backstage-plugin-litellm 0.3.2 → 0.4.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/UsageStats.d.ts +2 -1
- package/dist/index.cjs.js +124 -64
- package/dist/index.cjs.js.map +3 -3
- package/dist/index.esm.js +125 -62
- package/dist/index.esm.js.map +3 -3
- package/package.json +1 -1
package/dist/index.esm.js
CHANGED
|
@@ -105,7 +105,6 @@ var init_api = __esm({
|
|
|
105
105
|
// src/components/DashboardHeader.tsx
|
|
106
106
|
import React from "react";
|
|
107
107
|
import { Box, Typography, LinearProgress, Paper, Chip } from "@mui/material";
|
|
108
|
-
import { Warning } from "@mui/icons-material";
|
|
109
108
|
var DashboardHeader;
|
|
110
109
|
var init_DashboardHeader = __esm({
|
|
111
110
|
"src/components/DashboardHeader.tsx"() {
|
|
@@ -115,11 +114,6 @@ var init_DashboardHeader = __esm({
|
|
|
115
114
|
return /* @__PURE__ */ React.createElement(Paper, { sx: { p: 2, mb: 2 } }, /* @__PURE__ */ React.createElement(LinearProgress, null));
|
|
116
115
|
}
|
|
117
116
|
const displayName = userInfo.user_email ?? userInfo.email ?? userInfo.user_id;
|
|
118
|
-
const budget = userInfo.max_budget ?? 0;
|
|
119
|
-
const spend = userInfo.spend ?? userInfo.current_spend ?? 0;
|
|
120
|
-
const budgetPct = budget > 0 ? Math.min(spend / budget * 100, 100) : 0;
|
|
121
|
-
const isOver = budget > 0 && spend >= budget;
|
|
122
|
-
const isNear = budget > 0 && spend >= budget * 0.8 && !isOver;
|
|
123
117
|
return /* @__PURE__ */ React.createElement(Paper, { sx: { p: 2, mb: 2 } }, /* @__PURE__ */ React.createElement(Box, { display: "flex", alignItems: "flex-start", gap: 2, flexWrap: "wrap" }, /* @__PURE__ */ React.createElement(Box, { flexGrow: 1 }, /* @__PURE__ */ React.createElement(Typography, { variant: "h6" }, displayName), /* @__PURE__ */ React.createElement(Typography, { variant: "caption", color: "text.secondary" }, userInfo.user_id), teams.length > 0 && /* @__PURE__ */ React.createElement(Box, { display: "flex", gap: 0.75, flexWrap: "wrap", mt: 1 }, teams.map((team) => /* @__PURE__ */ React.createElement(
|
|
124
118
|
Chip,
|
|
125
119
|
{
|
|
@@ -129,15 +123,7 @@ var init_DashboardHeader = __esm({
|
|
|
129
123
|
variant: "outlined",
|
|
130
124
|
color: "primary"
|
|
131
125
|
}
|
|
132
|
-
))))
|
|
133
|
-
LinearProgress,
|
|
134
|
-
{
|
|
135
|
-
variant: "determinate",
|
|
136
|
-
value: budgetPct,
|
|
137
|
-
color: isOver ? "error" : isNear ? "warning" : "primary",
|
|
138
|
-
sx: { height: 8, borderRadius: 1 }
|
|
139
|
-
}
|
|
140
|
-
))));
|
|
126
|
+
))))));
|
|
141
127
|
};
|
|
142
128
|
}
|
|
143
129
|
});
|
|
@@ -252,7 +238,7 @@ var init_KeysTable = __esm({
|
|
|
252
238
|
if (!editingKey) return;
|
|
253
239
|
setEditSubmitting(true);
|
|
254
240
|
try {
|
|
255
|
-
await onUpdateKey(editingKey.key, editForm);
|
|
241
|
+
await onUpdateKey(editingKey.token ?? editingKey.key, editForm);
|
|
256
242
|
handleCloseEdit();
|
|
257
243
|
} catch (error) {
|
|
258
244
|
console.error("Failed to update key:", error);
|
|
@@ -290,7 +276,7 @@ var init_KeysTable = __esm({
|
|
|
290
276
|
}
|
|
291
277
|
},
|
|
292
278
|
shortKeyId(keyId)
|
|
293
|
-
), /* @__PURE__ */ React2.createElement(IconButton, { size: "small", onClick: () => copyToClipboard(keyId), title: "Copy Key ID" }, /* @__PURE__ */ React2.createElement(ContentCopy, { fontSize: "small" })))), /* @__PURE__ */ React2.createElement(TableCell, null, formatDate(key.created_at)), /* @__PURE__ */ React2.createElement(TableCell, null, "$", key.spend?.toFixed(4) || "0.00"), /* @__PURE__ */ React2.createElement(TableCell, null, key.max_budget ? `$${key.max_budget}` : "-"), /* @__PURE__ */ React2.createElement(TableCell, null, key.tpm_limit || "-"), /* @__PURE__ */ React2.createElement(TableCell, null, /* @__PURE__ */ React2.createElement(Box2, { display: "flex", gap: 0.5, flexWrap: "wrap" }, key.models?.slice(0, 2).map((model) => /* @__PURE__ */ React2.createElement(Chip2, { key: model, label: model, size: "small" })), (key.models?.length || 0) > 2 && /* @__PURE__ */ React2.createElement(Chip2, { label: `+${(key.models?.length || 0) - 2}`, size: "small", variant: "outlined" }))), /* @__PURE__ */ React2.createElement(TableCell, { align: "right" }, /* @__PURE__ */ React2.createElement(IconButton, { onClick: () => handleOpenEdit(key) }, /* @__PURE__ */ React2.createElement(Edit, { fontSize: "small" })), /* @__PURE__ */ React2.createElement(IconButton, { color: "error", onClick: () => onDeleteKey(
|
|
279
|
+
), /* @__PURE__ */ React2.createElement(IconButton, { size: "small", onClick: () => copyToClipboard(keyId), title: "Copy Key ID" }, /* @__PURE__ */ React2.createElement(ContentCopy, { fontSize: "small" })))), /* @__PURE__ */ React2.createElement(TableCell, null, formatDate(key.created_at)), /* @__PURE__ */ React2.createElement(TableCell, null, "$", key.spend?.toFixed(4) || "0.00"), /* @__PURE__ */ React2.createElement(TableCell, null, key.max_budget ? `$${key.max_budget}` : "-"), /* @__PURE__ */ React2.createElement(TableCell, null, key.tpm_limit || "-"), /* @__PURE__ */ React2.createElement(TableCell, null, /* @__PURE__ */ React2.createElement(Box2, { display: "flex", gap: 0.5, flexWrap: "wrap" }, key.models?.slice(0, 2).map((model) => /* @__PURE__ */ React2.createElement(Chip2, { key: model, label: model, size: "small" })), (key.models?.length || 0) > 2 && /* @__PURE__ */ React2.createElement(Chip2, { label: `+${(key.models?.length || 0) - 2}`, size: "small", variant: "outlined" }))), /* @__PURE__ */ React2.createElement(TableCell, { align: "right" }, /* @__PURE__ */ React2.createElement(IconButton, { onClick: () => handleOpenEdit(key) }, /* @__PURE__ */ React2.createElement(Edit, { fontSize: "small" })), /* @__PURE__ */ React2.createElement(IconButton, { color: "error", onClick: () => onDeleteKey(keyId) }, /* @__PURE__ */ React2.createElement(Delete, null))));
|
|
294
280
|
}))))), /* @__PURE__ */ React2.createElement(Dialog, { open: generateModalOpen, onClose: handleCloseModal, maxWidth: "sm", fullWidth: true }, /* @__PURE__ */ React2.createElement(DialogTitle, null, newKeyValue ? "Key Generated" : "Generate New Key"), /* @__PURE__ */ React2.createElement(DialogContent, null, newKeyValue ? /* @__PURE__ */ React2.createElement(Box2, null, /* @__PURE__ */ React2.createElement(Typography2, { variant: "body2", color: "text.secondary", gutterBottom: true }, "Copy this key now. You won't be able to see it again."), /* @__PURE__ */ React2.createElement(
|
|
295
281
|
Box2,
|
|
296
282
|
{
|
|
@@ -458,7 +444,6 @@ import {
|
|
|
458
444
|
Select,
|
|
459
445
|
MenuItem as MenuItem2,
|
|
460
446
|
Grid,
|
|
461
|
-
CircularProgress as CircularProgress2,
|
|
462
447
|
Tabs,
|
|
463
448
|
Tab,
|
|
464
449
|
Table as Table2,
|
|
@@ -468,42 +453,71 @@ import {
|
|
|
468
453
|
TableHead as TableHead2,
|
|
469
454
|
TableRow as TableRow2,
|
|
470
455
|
Chip as Chip3,
|
|
471
|
-
LinearProgress as LinearProgress2
|
|
456
|
+
LinearProgress as LinearProgress2,
|
|
457
|
+
Skeleton
|
|
472
458
|
} from "@mui/material";
|
|
473
459
|
import {
|
|
474
460
|
AreaChart,
|
|
475
461
|
Area,
|
|
476
462
|
BarChart,
|
|
477
463
|
Bar,
|
|
464
|
+
LineChart,
|
|
465
|
+
Line,
|
|
478
466
|
XAxis,
|
|
479
467
|
YAxis,
|
|
480
468
|
CartesianGrid,
|
|
481
469
|
Tooltip,
|
|
482
470
|
ResponsiveContainer,
|
|
483
|
-
Legend
|
|
471
|
+
Legend,
|
|
472
|
+
ReferenceLine
|
|
484
473
|
} from "recharts";
|
|
485
|
-
|
|
474
|
+
function modelColor(model) {
|
|
475
|
+
let h = 5381;
|
|
476
|
+
for (let i = 0; i < model.length; i++) h = (h << 5) + h + model.charCodeAt(i) >>> 0;
|
|
477
|
+
return MODEL_COLORS[h % MODEL_COLORS.length];
|
|
478
|
+
}
|
|
479
|
+
var TOP_N_MODELS, PERIOD_LS_KEY, MODEL_COLORS, fmtUsd, fmtInt, fmtPct, KpiCard, ChartSkeleton, EmptyChart, UsageStats;
|
|
486
480
|
var init_UsageStats = __esm({
|
|
487
481
|
"src/components/UsageStats.tsx"() {
|
|
488
482
|
"use strict";
|
|
483
|
+
TOP_N_MODELS = 6;
|
|
484
|
+
PERIOD_LS_KEY = "litellm_usage_period";
|
|
485
|
+
MODEL_COLORS = [
|
|
486
|
+
"#8884d8",
|
|
487
|
+
"#82ca9d",
|
|
488
|
+
"#ffc658",
|
|
489
|
+
"#ff7300",
|
|
490
|
+
"#0088fe",
|
|
491
|
+
"#00C49F",
|
|
492
|
+
"#FFBB28",
|
|
493
|
+
"#FF8042",
|
|
494
|
+
"#a4de6c",
|
|
495
|
+
"#d0ed57"
|
|
496
|
+
];
|
|
489
497
|
fmtUsd = (n) => `$${(n ?? 0).toFixed(n < 1 ? 4 : 2)}`;
|
|
490
498
|
fmtInt = (n) => (n ?? 0).toLocaleString();
|
|
491
499
|
fmtPct = (n) => `${(n * 100).toFixed(1)}%`;
|
|
492
500
|
KpiCard = ({ label, value, hint }) => /* @__PURE__ */ React3.createElement(Paper3, { variant: "outlined", sx: { p: 2, height: "100%" } }, /* @__PURE__ */ React3.createElement(Typography3, { variant: "caption", color: "text.secondary" }, label), /* @__PURE__ */ React3.createElement(Typography3, { variant: "h5", sx: { mt: 0.5 } }, value), hint ? /* @__PURE__ */ React3.createElement(Typography3, { variant: "caption", color: "text.secondary" }, hint) : null);
|
|
501
|
+
ChartSkeleton = ({ height = 260 }) => /* @__PURE__ */ React3.createElement(Skeleton, { variant: "rectangular", height, sx: { borderRadius: 1 } });
|
|
502
|
+
EmptyChart = ({
|
|
503
|
+
height = 260,
|
|
504
|
+
message = "No data for this period"
|
|
505
|
+
}) => /* @__PURE__ */ React3.createElement(Box3, { height, display: "flex", alignItems: "center", justifyContent: "center" }, /* @__PURE__ */ React3.createElement(Typography3, { color: "text.secondary", variant: "body2" }, message));
|
|
493
506
|
UsageStats = ({
|
|
494
507
|
usage,
|
|
495
508
|
models,
|
|
496
509
|
dateRange,
|
|
497
510
|
onDateRangeChange,
|
|
498
|
-
loading
|
|
511
|
+
loading,
|
|
512
|
+
userInfo
|
|
499
513
|
}) => {
|
|
500
514
|
const [selectedModel, setSelectedModel] = useState2("all");
|
|
501
515
|
const [tab, setTab] = useState2("costs");
|
|
502
516
|
const selectedPreset = useMemo(() => {
|
|
503
|
-
|
|
504
|
-
const
|
|
505
|
-
|
|
506
|
-
|
|
517
|
+
if (dateRange.start.toDateString() === dateRange.end.toDateString()) return "today";
|
|
518
|
+
const diffMs = dateRange.end.getTime() - dateRange.start.getTime();
|
|
519
|
+
const diffHours = diffMs / (1e3 * 60 * 60);
|
|
520
|
+
if (diffHours <= 26) return "24h";
|
|
507
521
|
const diffDays = Math.ceil(diffMs / (1e3 * 60 * 60 * 24));
|
|
508
522
|
if (diffDays <= 7) return "7d";
|
|
509
523
|
return "30d";
|
|
@@ -512,25 +526,15 @@ var init_UsageStats = __esm({
|
|
|
512
526
|
const end = /* @__PURE__ */ new Date();
|
|
513
527
|
const start = /* @__PURE__ */ new Date();
|
|
514
528
|
if (preset === "today") start.setHours(0, 0, 0, 0);
|
|
529
|
+
else if (preset === "24h") start.setHours(start.getHours() - 24);
|
|
515
530
|
else if (preset === "7d") start.setDate(start.getDate() - 7);
|
|
516
531
|
else if (preset === "30d") start.setDate(start.getDate() - 30);
|
|
532
|
+
try {
|
|
533
|
+
localStorage.setItem(PERIOD_LS_KEY, preset);
|
|
534
|
+
} catch {
|
|
535
|
+
}
|
|
517
536
|
onDateRangeChange({ start, end });
|
|
518
537
|
};
|
|
519
|
-
const todayRows = useMemo(() => {
|
|
520
|
-
const rows = usage?.daily_by_model ?? [];
|
|
521
|
-
if (rows.length === 0) return [];
|
|
522
|
-
const latestDate = rows.map((r) => r.date).sort().slice(-1)[0];
|
|
523
|
-
return rows.filter((r) => r.date === latestDate).map((r) => ({
|
|
524
|
-
model: r.model,
|
|
525
|
-
promptTokens: r.prompt_tokens,
|
|
526
|
-
completionTokens: r.completion_tokens
|
|
527
|
-
})).filter((r) => r.promptTokens + r.completionTokens > 0).sort((a, b) => b.promptTokens + b.completionTokens - (a.promptTokens + a.completionTokens));
|
|
528
|
-
}, [usage]);
|
|
529
|
-
const todayLabel = useMemo(() => {
|
|
530
|
-
const rows = usage?.daily_by_model ?? [];
|
|
531
|
-
if (rows.length === 0) return null;
|
|
532
|
-
return rows.map((r) => r.date).sort().slice(-1)[0];
|
|
533
|
-
}, [usage]);
|
|
534
538
|
const dailyData = useMemo(
|
|
535
539
|
() => (usage?.daily_usage ?? []).map((d) => ({
|
|
536
540
|
date: d.date,
|
|
@@ -544,6 +548,35 @@ var init_UsageStats = __esm({
|
|
|
544
548
|
})),
|
|
545
549
|
[usage]
|
|
546
550
|
);
|
|
551
|
+
const cumulativeData = useMemo(() => {
|
|
552
|
+
let cum = 0;
|
|
553
|
+
return dailyData.map((d) => {
|
|
554
|
+
cum += d.spend;
|
|
555
|
+
return { date: d.date, cumulative: cum };
|
|
556
|
+
});
|
|
557
|
+
}, [dailyData]);
|
|
558
|
+
const successRateData = useMemo(
|
|
559
|
+
() => dailyData.filter((d) => d.apiRequests > 0).map((d) => ({
|
|
560
|
+
date: d.date,
|
|
561
|
+
successRate: parseFloat((d.successfulRequests / d.apiRequests * 100).toFixed(1))
|
|
562
|
+
})),
|
|
563
|
+
[dailyData]
|
|
564
|
+
);
|
|
565
|
+
const { modelSpendByDate, topModels: topSpendModels } = useMemo(() => {
|
|
566
|
+
const rows = usage?.daily_by_model ?? [];
|
|
567
|
+
const modelTotals = {};
|
|
568
|
+
for (const r of rows) modelTotals[r.model] = (modelTotals[r.model] ?? 0) + r.spend;
|
|
569
|
+
const top = Object.entries(modelTotals).sort(([, a], [, b]) => b - a).slice(0, TOP_N_MODELS).map(([m]) => m);
|
|
570
|
+
const dateMap = {};
|
|
571
|
+
for (const r of rows) {
|
|
572
|
+
if (!dateMap[r.date]) dateMap[r.date] = {};
|
|
573
|
+
const bucket = top.includes(r.model) ? r.model : "Other";
|
|
574
|
+
dateMap[r.date][bucket] = (dateMap[r.date][bucket] ?? 0) + r.spend;
|
|
575
|
+
}
|
|
576
|
+
const sorted = Object.entries(dateMap).sort(([a], [b]) => a.localeCompare(b)).map(([date, m]) => ({ date, ...m }));
|
|
577
|
+
const hasOther = sorted.some((r) => r.Other > 0);
|
|
578
|
+
return { modelSpendByDate: sorted, topModels: hasOther ? [...top, "Other"] : top };
|
|
579
|
+
}, [usage]);
|
|
547
580
|
const modelRows = useMemo(() => {
|
|
548
581
|
const entries = Object.entries(usage?.usage_by_model ?? {}).map(([model, d]) => ({
|
|
549
582
|
model,
|
|
@@ -558,6 +591,10 @@ var init_UsageStats = __esm({
|
|
|
558
591
|
}));
|
|
559
592
|
return selectedModel === "all" ? entries : entries.filter((e) => e.model === selectedModel);
|
|
560
593
|
}, [usage, selectedModel]);
|
|
594
|
+
const topModelSpendBars = useMemo(
|
|
595
|
+
() => [...modelRows].sort((a, b) => b.spend - a.spend).slice(0, 10),
|
|
596
|
+
[modelRows]
|
|
597
|
+
);
|
|
561
598
|
const keyRows = useMemo(
|
|
562
599
|
() => Object.entries(usage?.usage_by_key ?? {}).map(([keyHash, d]) => ({
|
|
563
600
|
keyHash,
|
|
@@ -575,11 +612,14 @@ var init_UsageStats = __esm({
|
|
|
575
612
|
})),
|
|
576
613
|
[usage]
|
|
577
614
|
);
|
|
615
|
+
const topKeySpendBars = useMemo(
|
|
616
|
+
() => [...keyRows].sort((a, b) => b.spend - a.spend).slice(0, 10),
|
|
617
|
+
[keyRows]
|
|
618
|
+
);
|
|
578
619
|
const totalRequests = usage?.api_requests ?? 0;
|
|
579
620
|
const overallSuccessRate = totalRequests > 0 ? (usage?.successful_requests ?? 0) / totalRequests : 0;
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
}
|
|
621
|
+
const maxBudget = userInfo?.max_budget ?? 0;
|
|
622
|
+
const totalCumSpend = cumulativeData[cumulativeData.length - 1]?.cumulative ?? 0;
|
|
583
623
|
return /* @__PURE__ */ React3.createElement(Paper3, { sx: { p: 2 } }, /* @__PURE__ */ React3.createElement(Box3, { display: "flex", justifyContent: "space-between", alignItems: "center", mb: 2, flexWrap: "wrap", gap: 2 }, /* @__PURE__ */ React3.createElement(Typography3, { variant: "h6" }, "Usage Analytics"), /* @__PURE__ */ React3.createElement(Box3, { display: "flex", gap: 2, alignItems: "center", flexWrap: "wrap" }, /* @__PURE__ */ React3.createElement(FormControl, { size: "small", sx: { minWidth: 140 } }, /* @__PURE__ */ React3.createElement(InputLabel, null, "Period"), /* @__PURE__ */ React3.createElement(
|
|
584
624
|
Select,
|
|
585
625
|
{
|
|
@@ -588,6 +628,7 @@ var init_UsageStats = __esm({
|
|
|
588
628
|
onChange: (e) => handlePresetChange(e.target.value)
|
|
589
629
|
},
|
|
590
630
|
/* @__PURE__ */ React3.createElement(MenuItem2, { value: "today" }, "Today"),
|
|
631
|
+
/* @__PURE__ */ React3.createElement(MenuItem2, { value: "24h" }, "Last 24h"),
|
|
591
632
|
/* @__PURE__ */ React3.createElement(MenuItem2, { value: "7d" }, "Last 7 days"),
|
|
592
633
|
/* @__PURE__ */ React3.createElement(MenuItem2, { value: "30d" }, "Last 30 days")
|
|
593
634
|
)), tab === "models" && /* @__PURE__ */ React3.createElement(FormControl, { size: "small", sx: { minWidth: 180 } }, /* @__PURE__ */ React3.createElement(InputLabel, null, "Model"), /* @__PURE__ */ React3.createElement(
|
|
@@ -599,28 +640,39 @@ var init_UsageStats = __esm({
|
|
|
599
640
|
},
|
|
600
641
|
/* @__PURE__ */ React3.createElement(MenuItem2, { value: "all" }, "All Models"),
|
|
601
642
|
models.map((m) => /* @__PURE__ */ React3.createElement(MenuItem2, { key: m.model_name, value: m.model_name }, m.model_name))
|
|
602
|
-
)))), /* @__PURE__ */ React3.createElement(Grid, { container: true, spacing: 2, sx: { mb: 2 } }, /* @__PURE__ */ React3.createElement(Grid, { item: true, xs: 6, sm: 3 }, /* @__PURE__ */ React3.createElement(KpiCard, { label: "Total Spend", value: fmtUsd(usage?.total_spend ?? 0) })), /* @__PURE__ */ React3.createElement(Grid, { item: true, xs: 6, sm: 3 }, /* @__PURE__ */ React3.createElement(KpiCard, { label: "Total Requests", value: fmtInt(totalRequests), hint: `${fmtInt(usage?.failed_requests ?? 0)} failed` })), /* @__PURE__ */ React3.createElement(Grid, { item: true, xs: 6, sm: 3 }, /* @__PURE__ */ React3.createElement(KpiCard, { label: "Success Rate", value: totalRequests > 0 ? fmtPct(overallSuccessRate) : "\u2014" })), /* @__PURE__ */ React3.createElement(Grid, { item: true, xs: 6, sm: 3 }, /* @__PURE__ */ React3.createElement(
|
|
643
|
+
)))), /* @__PURE__ */ React3.createElement(Grid, { container: true, spacing: 2, sx: { mb: 2 } }, /* @__PURE__ */ React3.createElement(Grid, { item: true, xs: 6, sm: 3 }, loading ? /* @__PURE__ */ React3.createElement(ChartSkeleton, { height: 80 }) : /* @__PURE__ */ React3.createElement(KpiCard, { label: "Total Spend", value: fmtUsd(usage?.total_spend ?? 0) })), /* @__PURE__ */ React3.createElement(Grid, { item: true, xs: 6, sm: 3 }, loading ? /* @__PURE__ */ React3.createElement(ChartSkeleton, { height: 80 }) : /* @__PURE__ */ React3.createElement(KpiCard, { label: "Total Requests", value: fmtInt(totalRequests), hint: `${fmtInt(usage?.failed_requests ?? 0)} failed` })), /* @__PURE__ */ React3.createElement(Grid, { item: true, xs: 6, sm: 3 }, loading ? /* @__PURE__ */ React3.createElement(ChartSkeleton, { height: 80 }) : /* @__PURE__ */ React3.createElement(KpiCard, { label: "Success Rate", value: totalRequests > 0 ? fmtPct(overallSuccessRate) : "\u2014" })), /* @__PURE__ */ React3.createElement(Grid, { item: true, xs: 6, sm: 3 }, loading ? /* @__PURE__ */ React3.createElement(ChartSkeleton, { height: 80 }) : /* @__PURE__ */ React3.createElement(
|
|
603
644
|
KpiCard,
|
|
604
645
|
{
|
|
605
646
|
label: "Total Tokens",
|
|
606
647
|
value: fmtInt(usage?.total_tokens ?? 0),
|
|
607
648
|
hint: `${fmtInt(usage?.prompt_tokens ?? 0)} in \xB7 ${fmtInt(usage?.completion_tokens ?? 0)} out`
|
|
608
649
|
}
|
|
609
|
-
))), /* @__PURE__ */ React3.createElement(Tabs, { value: tab, onChange: (_, v) => setTab(v), sx: { mb: 2 } }, /* @__PURE__ */ React3.createElement(Tab, { value: "costs", label: "Costs" }), /* @__PURE__ */ React3.createElement(Tab, { value: "models", label: "Model Activity" }), /* @__PURE__ */ React3.createElement(Tab, { value: "keys", label: "Key Activity" })), tab === "costs" && /* @__PURE__ */ React3.createElement(Grid, { container: true, spacing: 3 }, /* @__PURE__ */ React3.createElement(Grid, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ React3.createElement(Typography3, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Daily Spend
|
|
650
|
+
))), /* @__PURE__ */ React3.createElement(Tabs, { value: tab, onChange: (_, v) => setTab(v), sx: { mb: 2 } }, /* @__PURE__ */ React3.createElement(Tab, { value: "costs", label: "Costs" }), /* @__PURE__ */ React3.createElement(Tab, { value: "models", label: "Model Activity" }), /* @__PURE__ */ React3.createElement(Tab, { value: "keys", label: "Key Activity" })), tab === "costs" && /* @__PURE__ */ React3.createElement(Grid, { container: true, spacing: 3 }, /* @__PURE__ */ React3.createElement(Grid, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ React3.createElement(Typography3, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Daily Spend by Model"), loading ? /* @__PURE__ */ React3.createElement(ChartSkeleton, null) : modelSpendByDate.length === 0 ? /* @__PURE__ */ React3.createElement(EmptyChart, null) : /* @__PURE__ */ React3.createElement(Box3, { height: 260 }, /* @__PURE__ */ React3.createElement(ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ React3.createElement(AreaChart, { data: modelSpendByDate }, /* @__PURE__ */ React3.createElement(CartesianGrid, { strokeDasharray: "3 3" }), /* @__PURE__ */ React3.createElement(XAxis, { dataKey: "date", tick: { fontSize: 12 } }), /* @__PURE__ */ React3.createElement(YAxis, { tick: { fontSize: 12 }, tickFormatter: (v) => `$${v.toFixed(2)}` }), /* @__PURE__ */ React3.createElement(Tooltip, { formatter: (v) => [`$${v.toFixed(4)}`, void 0] }), /* @__PURE__ */ React3.createElement(Legend, null), topSpendModels.map((m) => /* @__PURE__ */ React3.createElement(
|
|
651
|
+
Area,
|
|
652
|
+
{
|
|
653
|
+
key: m,
|
|
654
|
+
type: "monotone",
|
|
655
|
+
dataKey: m,
|
|
656
|
+
stackId: "spend",
|
|
657
|
+
stroke: modelColor(m),
|
|
658
|
+
fill: modelColor(m),
|
|
659
|
+
fillOpacity: 0.6
|
|
660
|
+
}
|
|
661
|
+
)))))), /* @__PURE__ */ React3.createElement(Grid, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ React3.createElement(Typography3, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Daily Token Usage"), loading ? /* @__PURE__ */ React3.createElement(ChartSkeleton, null) : dailyData.length === 0 ? /* @__PURE__ */ React3.createElement(EmptyChart, null) : /* @__PURE__ */ React3.createElement(Box3, { height: 260 }, /* @__PURE__ */ React3.createElement(ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ React3.createElement(AreaChart, { data: dailyData }, /* @__PURE__ */ React3.createElement(CartesianGrid, { strokeDasharray: "3 3" }), /* @__PURE__ */ React3.createElement(XAxis, { dataKey: "date", tick: { fontSize: 12 } }), /* @__PURE__ */ React3.createElement(YAxis, { tick: { fontSize: 12 }, tickFormatter: fmtInt }), /* @__PURE__ */ React3.createElement(Tooltip, { formatter: (v) => [fmtInt(v), void 0] }), /* @__PURE__ */ React3.createElement(Legend, null), /* @__PURE__ */ React3.createElement(Area, { type: "monotone", dataKey: "promptTokens", name: "Input (prompt)", stackId: "tok", stroke: "#8884d8", fill: "#8884d8", fillOpacity: 0.5 }), /* @__PURE__ */ React3.createElement(Area, { type: "monotone", dataKey: "completionTokens", name: "Output (completion)", stackId: "tok", stroke: "#82ca9d", fill: "#82ca9d", fillOpacity: 0.5 }))))), /* @__PURE__ */ React3.createElement(Grid, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ React3.createElement(Typography3, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Daily Requests"), loading ? /* @__PURE__ */ React3.createElement(ChartSkeleton, null) : dailyData.length === 0 ? /* @__PURE__ */ React3.createElement(EmptyChart, null) : /* @__PURE__ */ React3.createElement(Box3, { height: 260 }, /* @__PURE__ */ React3.createElement(ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ React3.createElement(BarChart, { data: dailyData }, /* @__PURE__ */ React3.createElement(CartesianGrid, { strokeDasharray: "3 3" }), /* @__PURE__ */ React3.createElement(XAxis, { dataKey: "date", tick: { fontSize: 12 } }), /* @__PURE__ */ React3.createElement(YAxis, { tick: { fontSize: 12 } }), /* @__PURE__ */ React3.createElement(Tooltip, null), /* @__PURE__ */ React3.createElement(Legend, null), /* @__PURE__ */ React3.createElement(Bar, { dataKey: "successfulRequests", name: "Successful", fill: "#82ca9d", stackId: "r" }), /* @__PURE__ */ React3.createElement(Bar, { dataKey: "failedRequests", name: "Failed", fill: "#e57373", stackId: "r" }))))), /* @__PURE__ */ React3.createElement(Grid, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ React3.createElement(Typography3, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Daily Success Rate"), loading ? /* @__PURE__ */ React3.createElement(ChartSkeleton, null) : successRateData.length === 0 ? /* @__PURE__ */ React3.createElement(EmptyChart, null) : /* @__PURE__ */ React3.createElement(Box3, { height: 260 }, /* @__PURE__ */ React3.createElement(ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ React3.createElement(LineChart, { data: successRateData }, /* @__PURE__ */ React3.createElement(CartesianGrid, { strokeDasharray: "3 3" }), /* @__PURE__ */ React3.createElement(XAxis, { dataKey: "date", tick: { fontSize: 12 } }), /* @__PURE__ */ React3.createElement(YAxis, { domain: [0, 100], tick: { fontSize: 12 }, tickFormatter: (v) => `${v}%` }), /* @__PURE__ */ React3.createElement(Tooltip, { formatter: (v) => [`${v}%`, "Success rate"] }), /* @__PURE__ */ React3.createElement(ReferenceLine, { y: 100, stroke: "#82ca9d", strokeDasharray: "4 2" }), /* @__PURE__ */ React3.createElement(Line, { type: "monotone", dataKey: "successRate", name: "Success rate", stroke: "#8884d8", dot: { r: 3 } }))))), (maxBudget > 0 || cumulativeData.length > 0) && /* @__PURE__ */ React3.createElement(Grid, { item: true, xs: 12 }, /* @__PURE__ */ React3.createElement(Typography3, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Cumulative Spend", maxBudget > 0 ? ` vs Budget (${fmtUsd(maxBudget)})` : "", maxBudget > 0 && /* @__PURE__ */ React3.createElement(Typography3, { component: "span", variant: "caption", color: "text.secondary", sx: { ml: 1 } }, fmtUsd(totalCumSpend), " used \xB7 ", fmtUsd(Math.max(0, maxBudget - totalCumSpend)), " remaining")), loading ? /* @__PURE__ */ React3.createElement(ChartSkeleton, { height: 180 }) : cumulativeData.length === 0 ? /* @__PURE__ */ React3.createElement(EmptyChart, { height: 180 }) : /* @__PURE__ */ React3.createElement(Box3, { height: 180 }, /* @__PURE__ */ React3.createElement(ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ React3.createElement(AreaChart, { data: cumulativeData }, /* @__PURE__ */ React3.createElement(CartesianGrid, { strokeDasharray: "3 3" }), /* @__PURE__ */ React3.createElement(XAxis, { dataKey: "date", tick: { fontSize: 12 } }), /* @__PURE__ */ React3.createElement(YAxis, { tick: { fontSize: 12 }, tickFormatter: (v) => `$${v.toFixed(2)}` }), /* @__PURE__ */ React3.createElement(Tooltip, { formatter: (v) => [`$${v.toFixed(4)}`, "Cumulative spend"] }), maxBudget > 0 && /* @__PURE__ */ React3.createElement(ReferenceLine, { y: maxBudget, stroke: "#e57373", strokeDasharray: "6 3", label: { value: `Budget ${fmtUsd(maxBudget)}`, position: "insideTopRight", fontSize: 11 } }), /* @__PURE__ */ React3.createElement(Area, { type: "monotone", dataKey: "cumulative", name: "Cumulative spend", stroke: "#ffc658", fill: "#ffc658", fillOpacity: 0.3 })))))), tab === "models" && /* @__PURE__ */ React3.createElement(Grid, { container: true, spacing: 3 }, /* @__PURE__ */ React3.createElement(Grid, { item: true, xs: 12 }, /* @__PURE__ */ React3.createElement(Typography3, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Spend by Model"), loading ? /* @__PURE__ */ React3.createElement(ChartSkeleton, null) : topModelSpendBars.length === 0 ? /* @__PURE__ */ React3.createElement(EmptyChart, null) : /* @__PURE__ */ React3.createElement(Box3, { height: Math.max(200, topModelSpendBars.length * 36) }, /* @__PURE__ */ React3.createElement(ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ React3.createElement(BarChart, { data: topModelSpendBars, layout: "vertical", margin: { left: 20 } }, /* @__PURE__ */ React3.createElement(CartesianGrid, { strokeDasharray: "3 3" }), /* @__PURE__ */ React3.createElement(XAxis, { type: "number", tick: { fontSize: 12 }, tickFormatter: (v) => `$${v.toFixed(2)}` }), /* @__PURE__ */ React3.createElement(YAxis, { type: "category", dataKey: "model", tick: { fontSize: 11 }, width: 200 }), /* @__PURE__ */ React3.createElement(Tooltip, { formatter: (v) => [fmtUsd(v), "Spend"] }), /* @__PURE__ */ React3.createElement(Bar, { dataKey: "spend", name: "Spend", radius: [0, 4, 4, 0] }, topModelSpendBars.map((r) => /* @__PURE__ */ React3.createElement("rect", { key: r.model, fill: modelColor(r.model) }))))))), /* @__PURE__ */ React3.createElement(Grid, { item: true, xs: 12 }, /* @__PURE__ */ React3.createElement(Typography3, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Tokens by Model"), loading ? /* @__PURE__ */ React3.createElement(ChartSkeleton, null) : modelRows.length === 0 ? /* @__PURE__ */ React3.createElement(EmptyChart, null) : /* @__PURE__ */ React3.createElement(Box3, { height: 260 }, /* @__PURE__ */ React3.createElement(ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ React3.createElement(BarChart, { data: modelRows }, /* @__PURE__ */ React3.createElement(CartesianGrid, { strokeDasharray: "3 3" }), /* @__PURE__ */ React3.createElement(XAxis, { dataKey: "model", tick: { fontSize: 10 }, interval: 0, angle: -15, textAnchor: "end", height: 60 }), /* @__PURE__ */ React3.createElement(YAxis, { tick: { fontSize: 12 } }), /* @__PURE__ */ React3.createElement(Tooltip, null), /* @__PURE__ */ React3.createElement(Legend, null), /* @__PURE__ */ React3.createElement(Bar, { dataKey: "promptTokens", name: "Prompt", fill: "#8884d8", stackId: "t" }), /* @__PURE__ */ React3.createElement(Bar, { dataKey: "completionTokens", name: "Completion", fill: "#82ca9d", stackId: "t" }))))), /* @__PURE__ */ React3.createElement(Grid, { item: true, xs: 12 }, /* @__PURE__ */ React3.createElement(TableContainer2, { component: Paper3, variant: "outlined" }, /* @__PURE__ */ React3.createElement(Table2, { size: "small" }, /* @__PURE__ */ React3.createElement(TableHead2, null, /* @__PURE__ */ React3.createElement(TableRow2, null, /* @__PURE__ */ React3.createElement(TableCell2, null, "Model"), /* @__PURE__ */ React3.createElement(TableCell2, { align: "right" }, "Spend"), /* @__PURE__ */ React3.createElement(TableCell2, { align: "right" }, "Requests"), /* @__PURE__ */ React3.createElement(TableCell2, { align: "right" }, "Success"), /* @__PURE__ */ React3.createElement(TableCell2, { align: "right" }, "Failed"), /* @__PURE__ */ React3.createElement(TableCell2, { align: "right" }, "Prompt"), /* @__PURE__ */ React3.createElement(TableCell2, { align: "right" }, "Completion"), /* @__PURE__ */ React3.createElement(TableCell2, { sx: { minWidth: 120 } }, "Success rate"))), /* @__PURE__ */ React3.createElement(TableBody2, null, loading ? /* @__PURE__ */ React3.createElement(TableRow2, null, /* @__PURE__ */ React3.createElement(TableCell2, { colSpan: 8 }, /* @__PURE__ */ React3.createElement(LinearProgress2, null))) : modelRows.length === 0 ? /* @__PURE__ */ React3.createElement(TableRow2, null, /* @__PURE__ */ React3.createElement(TableCell2, { colSpan: 8, align: "center" }, "No model activity")) : [...modelRows].sort((a, b) => b.spend - a.spend || b.totalTokens - a.totalTokens).map((r) => /* @__PURE__ */ React3.createElement(TableRow2, { key: r.model }, /* @__PURE__ */ React3.createElement(TableCell2, null, /* @__PURE__ */ React3.createElement(Box3, { display: "flex", alignItems: "center", gap: 0.75 }, /* @__PURE__ */ React3.createElement(Box3, { sx: { width: 10, height: 10, borderRadius: "50%", bgcolor: modelColor(r.model), flexShrink: 0 } }), r.model)), /* @__PURE__ */ React3.createElement(TableCell2, { align: "right" }, fmtUsd(r.spend)), /* @__PURE__ */ React3.createElement(TableCell2, { align: "right" }, fmtInt(r.apiRequests)), /* @__PURE__ */ React3.createElement(TableCell2, { align: "right" }, fmtInt(r.successfulRequests)), /* @__PURE__ */ React3.createElement(TableCell2, { align: "right" }, fmtInt(r.failedRequests)), /* @__PURE__ */ React3.createElement(TableCell2, { align: "right" }, fmtInt(r.promptTokens)), /* @__PURE__ */ React3.createElement(TableCell2, { align: "right" }, fmtInt(r.completionTokens)), /* @__PURE__ */ React3.createElement(TableCell2, null, r.apiRequests > 0 ? /* @__PURE__ */ React3.createElement(Box3, { display: "flex", alignItems: "center", gap: 1 }, /* @__PURE__ */ React3.createElement(
|
|
610
662
|
LinearProgress2,
|
|
611
663
|
{
|
|
612
664
|
variant: "determinate",
|
|
613
665
|
value: r.successRate * 100,
|
|
614
666
|
sx: { flex: 1, height: 6, borderRadius: 3 }
|
|
615
667
|
}
|
|
616
|
-
), /* @__PURE__ */ React3.createElement(Typography3, { variant: "caption" }, fmtPct(r.successRate))) : "\u2014")))))))), tab === "keys" && /* @__PURE__ */ React3.createElement(TableContainer2, { component: Paper3, variant: "outlined" }, /* @__PURE__ */ React3.createElement(Table2, { size: "small" }, /* @__PURE__ */ React3.createElement(TableHead2, null, /* @__PURE__ */ React3.createElement(TableRow2, null, /* @__PURE__ */ React3.createElement(TableCell2, null, "Key"), /* @__PURE__ */ React3.createElement(TableCell2, null, "Models"), /* @__PURE__ */ React3.createElement(TableCell2, { align: "right" }, "Spend"), /* @__PURE__ */ React3.createElement(TableCell2, { align: "right" }, "Requests"), /* @__PURE__ */ React3.createElement(TableCell2, { align: "right" }, "Tokens"), /* @__PURE__ */ React3.createElement(TableCell2, { align: "right" }, "Failed"), /* @__PURE__ */ React3.createElement(TableCell2, { sx: { minWidth: 120 } }, "Success rate"))), /* @__PURE__ */ React3.createElement(TableBody2, null, keyRows.length === 0 ? /* @__PURE__ */ React3.createElement(TableRow2, null, /* @__PURE__ */ React3.createElement(TableCell2, { colSpan: 7, align: "center" }, "No key activity")) : keyRows.sort((a, b) => b.spend - a.spend || b.apiRequests - a.apiRequests).map((r) => /* @__PURE__ */ React3.createElement(TableRow2, { key: r.keyHash }, /* @__PURE__ */ React3.createElement(TableCell2, null, /* @__PURE__ */ React3.createElement(Typography3, { variant: "body2" }, r.keyAlias), r.teamId ? /* @__PURE__ */ React3.createElement(Typography3, { variant: "caption", color: "text.secondary" }, "team: ", r.teamId) : null), /* @__PURE__ */ React3.createElement(TableCell2, null, /* @__PURE__ */ React3.createElement(Box3, { display: "flex", gap: 0.5, flexWrap: "wrap" }, r.models.map((m) => /* @__PURE__ */ React3.createElement(Chip3, { key: m, label: m, size: "small", variant: "outlined" })))), /* @__PURE__ */ React3.createElement(TableCell2, { align: "right" }, fmtUsd(r.spend)), /* @__PURE__ */ React3.createElement(TableCell2, { align: "right" }, fmtInt(r.apiRequests)), /* @__PURE__ */ React3.createElement(TableCell2, { align: "right" }, fmtInt(r.totalTokens)), /* @__PURE__ */ React3.createElement(TableCell2, { align: "right" }, fmtInt(r.failedRequests)), /* @__PURE__ */ React3.createElement(TableCell2, null, r.apiRequests > 0 ? /* @__PURE__ */ React3.createElement(Box3, { display: "flex", alignItems: "center", gap: 1 }, /* @__PURE__ */ React3.createElement(
|
|
668
|
+
), /* @__PURE__ */ React3.createElement(Typography3, { variant: "caption" }, fmtPct(r.successRate))) : "\u2014")))))))), tab === "keys" && /* @__PURE__ */ React3.createElement(Grid, { container: true, spacing: 3 }, (loading || topKeySpendBars.length > 0) && /* @__PURE__ */ React3.createElement(Grid, { item: true, xs: 12 }, /* @__PURE__ */ React3.createElement(Typography3, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Spend by Key"), loading ? /* @__PURE__ */ React3.createElement(ChartSkeleton, null) : /* @__PURE__ */ React3.createElement(Box3, { height: Math.max(160, topKeySpendBars.length * 36) }, /* @__PURE__ */ React3.createElement(ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ React3.createElement(BarChart, { data: topKeySpendBars, layout: "vertical", margin: { left: 20 } }, /* @__PURE__ */ React3.createElement(CartesianGrid, { strokeDasharray: "3 3" }), /* @__PURE__ */ React3.createElement(XAxis, { type: "number", tick: { fontSize: 12 }, tickFormatter: (v) => `$${v.toFixed(2)}` }), /* @__PURE__ */ React3.createElement(YAxis, { type: "category", dataKey: "keyAlias", tick: { fontSize: 11 }, width: 160 }), /* @__PURE__ */ React3.createElement(Tooltip, { formatter: (v) => [fmtUsd(v), "Spend"] }), /* @__PURE__ */ React3.createElement(Bar, { dataKey: "spend", name: "Spend", fill: "#8884d8", radius: [0, 4, 4, 0] }))))), /* @__PURE__ */ React3.createElement(Grid, { item: true, xs: 12 }, /* @__PURE__ */ React3.createElement(TableContainer2, { component: Paper3, variant: "outlined" }, /* @__PURE__ */ React3.createElement(Table2, { size: "small" }, /* @__PURE__ */ React3.createElement(TableHead2, null, /* @__PURE__ */ React3.createElement(TableRow2, null, /* @__PURE__ */ React3.createElement(TableCell2, null, "Key"), /* @__PURE__ */ React3.createElement(TableCell2, null, "Models"), /* @__PURE__ */ React3.createElement(TableCell2, { align: "right" }, "Spend"), /* @__PURE__ */ React3.createElement(TableCell2, { align: "right" }, "Requests"), /* @__PURE__ */ React3.createElement(TableCell2, { align: "right" }, "Tokens"), /* @__PURE__ */ React3.createElement(TableCell2, { align: "right" }, "Failed"), /* @__PURE__ */ React3.createElement(TableCell2, { sx: { minWidth: 120 } }, "Success rate"))), /* @__PURE__ */ React3.createElement(TableBody2, null, loading ? /* @__PURE__ */ React3.createElement(TableRow2, null, /* @__PURE__ */ React3.createElement(TableCell2, { colSpan: 7 }, /* @__PURE__ */ React3.createElement(LinearProgress2, null))) : keyRows.length === 0 ? /* @__PURE__ */ React3.createElement(TableRow2, null, /* @__PURE__ */ React3.createElement(TableCell2, { colSpan: 7, align: "center" }, "No key activity")) : [...keyRows].sort((a, b) => b.spend - a.spend || b.apiRequests - a.apiRequests).map((r) => /* @__PURE__ */ React3.createElement(TableRow2, { key: r.keyHash }, /* @__PURE__ */ React3.createElement(TableCell2, null, /* @__PURE__ */ React3.createElement(Typography3, { variant: "body2" }, r.keyAlias), r.teamId ? /* @__PURE__ */ React3.createElement(Typography3, { variant: "caption", color: "text.secondary" }, "team: ", r.teamId) : null), /* @__PURE__ */ React3.createElement(TableCell2, null, /* @__PURE__ */ React3.createElement(Box3, { display: "flex", gap: 0.5, flexWrap: "wrap" }, r.models.map((m) => /* @__PURE__ */ React3.createElement(Chip3, { key: m, label: m, size: "small", variant: "outlined" })))), /* @__PURE__ */ React3.createElement(TableCell2, { align: "right" }, fmtUsd(r.spend)), /* @__PURE__ */ React3.createElement(TableCell2, { align: "right" }, fmtInt(r.apiRequests)), /* @__PURE__ */ React3.createElement(TableCell2, { align: "right" }, fmtInt(r.totalTokens)), /* @__PURE__ */ React3.createElement(TableCell2, { align: "right" }, fmtInt(r.failedRequests)), /* @__PURE__ */ React3.createElement(TableCell2, null, r.apiRequests > 0 ? /* @__PURE__ */ React3.createElement(Box3, { display: "flex", alignItems: "center", gap: 1 }, /* @__PURE__ */ React3.createElement(
|
|
617
669
|
LinearProgress2,
|
|
618
670
|
{
|
|
619
671
|
variant: "determinate",
|
|
620
672
|
value: r.successRate * 100,
|
|
621
673
|
sx: { flex: 1, height: 6, borderRadius: 3 }
|
|
622
674
|
}
|
|
623
|
-
), /* @__PURE__ */ React3.createElement(Typography3, { variant: "caption" }, fmtPct(r.successRate))) : "\u2014")))))));
|
|
675
|
+
), /* @__PURE__ */ React3.createElement(Typography3, { variant: "caption" }, fmtPct(r.successRate))) : "\u2014")))))))));
|
|
624
676
|
};
|
|
625
677
|
}
|
|
626
678
|
});
|
|
@@ -641,7 +693,7 @@ import {
|
|
|
641
693
|
TableContainer as TableContainer3,
|
|
642
694
|
Collapse,
|
|
643
695
|
IconButton as IconButton2,
|
|
644
|
-
CircularProgress as
|
|
696
|
+
CircularProgress as CircularProgress2
|
|
645
697
|
} from "@mui/material";
|
|
646
698
|
import { ExpandMore, ExpandLess, Group } from "@mui/icons-material";
|
|
647
699
|
import {
|
|
@@ -673,7 +725,7 @@ var init_TeamUsage = __esm({
|
|
|
673
725
|
color: isOver ? "error" : isNear ? "warning" : "primary",
|
|
674
726
|
sx: { height: 6, borderRadius: 1 }
|
|
675
727
|
}
|
|
676
|
-
)), /* @__PURE__ */ React4.createElement(Collapse, { in: expanded }, /* @__PURE__ */ React4.createElement(Box4, { mt: 2 }, usageLoading ? /* @__PURE__ */ React4.createElement(Box4, { display: "flex", justifyContent: "center", p: 2 }, /* @__PURE__ */ React4.createElement(
|
|
728
|
+
)), /* @__PURE__ */ React4.createElement(Collapse, { in: expanded }, /* @__PURE__ */ React4.createElement(Box4, { mt: 2 }, usageLoading ? /* @__PURE__ */ React4.createElement(Box4, { display: "flex", justifyContent: "center", p: 2 }, /* @__PURE__ */ React4.createElement(CircularProgress2, { size: 24 })) : dailyData.length > 0 ? /* @__PURE__ */ React4.createElement(React4.Fragment, null, /* @__PURE__ */ React4.createElement(Typography4, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Daily Spend"), /* @__PURE__ */ React4.createElement(Box4, { height: 160 }, /* @__PURE__ */ React4.createElement(ResponsiveContainer2, { width: "100%", height: "100%" }, /* @__PURE__ */ React4.createElement(AreaChart2, { data: dailyData }, /* @__PURE__ */ React4.createElement(CartesianGrid2, { strokeDasharray: "3 3" }), /* @__PURE__ */ React4.createElement(XAxis2, { dataKey: "date", tick: { fontSize: 11 } }), /* @__PURE__ */ React4.createElement(YAxis2, { tick: { fontSize: 11 }, tickFormatter: (v) => `$${v.toFixed(2)}` }), /* @__PURE__ */ React4.createElement(Tooltip2, { formatter: (v) => [`$${v.toFixed(4)}`, "Spend"] }), /* @__PURE__ */ React4.createElement(Area2, { type: "monotone", dataKey: "spend", stroke: "#8884d8", fill: "#8884d8", fillOpacity: 0.3 }))))) : null, team.members_with_roles?.length ? /* @__PURE__ */ React4.createElement(Box4, { mt: 2 }, /* @__PURE__ */ React4.createElement(Typography4, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Members"), /* @__PURE__ */ React4.createElement(TableContainer3, null, /* @__PURE__ */ React4.createElement(Table3, { size: "small" }, /* @__PURE__ */ React4.createElement(TableHead3, null, /* @__PURE__ */ React4.createElement(TableRow3, null, /* @__PURE__ */ React4.createElement(TableCell3, null, "User"), /* @__PURE__ */ React4.createElement(TableCell3, null, "Role"))), /* @__PURE__ */ React4.createElement(TableBody3, null, team.members_with_roles.map((m) => /* @__PURE__ */ React4.createElement(TableRow3, { key: m.user_id }, /* @__PURE__ */ React4.createElement(TableCell3, { sx: { fontFamily: "monospace", fontSize: 12 } }, m.user_id), /* @__PURE__ */ React4.createElement(TableCell3, null, /* @__PURE__ */ React4.createElement(
|
|
677
729
|
Chip4,
|
|
678
730
|
{
|
|
679
731
|
label: m.role,
|
|
@@ -713,10 +765,24 @@ __export(LiteLLMPage_exports, {
|
|
|
713
765
|
LiteLLMPage: () => LiteLLMPage
|
|
714
766
|
});
|
|
715
767
|
import React5, { useState as useState4, useCallback, useMemo as useMemo2 } from "react";
|
|
716
|
-
import { Grid as Grid2, Box as Box5, Snackbar, Alert, CircularProgress as
|
|
768
|
+
import { Grid as Grid2, Box as Box5, Snackbar, Alert, CircularProgress as CircularProgress3, Typography as Typography5, Paper as Paper5 } from "@mui/material";
|
|
717
769
|
import { useAsync, useAsyncRetry } from "react-use";
|
|
718
770
|
import { useApi } from "@backstage/core-plugin-api";
|
|
719
|
-
|
|
771
|
+
function initDateRange() {
|
|
772
|
+
let preset = "7d";
|
|
773
|
+
try {
|
|
774
|
+
preset = localStorage.getItem(PERIOD_LS_KEY2) ?? "7d";
|
|
775
|
+
} catch {
|
|
776
|
+
}
|
|
777
|
+
const end = /* @__PURE__ */ new Date();
|
|
778
|
+
const start = /* @__PURE__ */ new Date();
|
|
779
|
+
if (preset === "today") start.setHours(0, 0, 0, 0);
|
|
780
|
+
else if (preset === "24h") start.setHours(start.getHours() - 24);
|
|
781
|
+
else if (preset === "30d") start.setDate(start.getDate() - 30);
|
|
782
|
+
else start.setDate(start.getDate() - 7);
|
|
783
|
+
return { start, end };
|
|
784
|
+
}
|
|
785
|
+
var PERIOD_LS_KEY2, LiteLLMPage;
|
|
720
786
|
var init_LiteLLMPage = __esm({
|
|
721
787
|
"src/components/LiteLLMPage.tsx"() {
|
|
722
788
|
"use strict";
|
|
@@ -725,14 +791,10 @@ var init_LiteLLMPage = __esm({
|
|
|
725
791
|
init_UsageStats();
|
|
726
792
|
init_TeamUsage();
|
|
727
793
|
init_api();
|
|
794
|
+
PERIOD_LS_KEY2 = "litellm_usage_period";
|
|
728
795
|
LiteLLMPage = () => {
|
|
729
796
|
const api = useApi(liteLlmApiRef);
|
|
730
|
-
const [dateRange, setDateRange] = useState4(
|
|
731
|
-
const end = /* @__PURE__ */ new Date();
|
|
732
|
-
const start = /* @__PURE__ */ new Date();
|
|
733
|
-
start.setDate(start.getDate() - 7);
|
|
734
|
-
return { start, end };
|
|
735
|
-
});
|
|
797
|
+
const [dateRange, setDateRange] = useState4(initDateRange);
|
|
736
798
|
const [snackbar, setSnackbar] = useState4(null);
|
|
737
799
|
const [teamUsageCache, setTeamUsageCache] = useState4({});
|
|
738
800
|
const [teamUsageLoading, setTeamUsageLoading] = useState4({});
|
|
@@ -855,7 +917,7 @@ var init_LiteLLMPage = __esm({
|
|
|
855
917
|
);
|
|
856
918
|
const isInitialLoading = userLoading && !userInfo;
|
|
857
919
|
if (isInitialLoading) {
|
|
858
|
-
return /* @__PURE__ */ React5.createElement(Box5, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: "50vh" }, /* @__PURE__ */ React5.createElement(
|
|
920
|
+
return /* @__PURE__ */ React5.createElement(Box5, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: "50vh" }, /* @__PURE__ */ React5.createElement(CircularProgress3, null));
|
|
859
921
|
}
|
|
860
922
|
if (userError || !userInfo) {
|
|
861
923
|
const isProvisioningEnabled = userError?.body?.provisioning === true;
|
|
@@ -892,7 +954,8 @@ var init_LiteLLMPage = __esm({
|
|
|
892
954
|
models: allModels ?? [],
|
|
893
955
|
dateRange,
|
|
894
956
|
onDateRangeChange: setDateRange,
|
|
895
|
-
loading: usageLoading
|
|
957
|
+
loading: usageLoading,
|
|
958
|
+
userInfo
|
|
896
959
|
}
|
|
897
960
|
))), /* @__PURE__ */ React5.createElement(
|
|
898
961
|
Snackbar,
|
|
@@ -959,7 +1022,7 @@ import {
|
|
|
959
1022
|
Select as Select2,
|
|
960
1023
|
MenuItem as MenuItem3,
|
|
961
1024
|
Grid as Grid3,
|
|
962
|
-
CircularProgress as
|
|
1025
|
+
CircularProgress as CircularProgress4,
|
|
963
1026
|
Alert as Alert2
|
|
964
1027
|
} from "@mui/material";
|
|
965
1028
|
import { AreaChart as AreaChart3, Area as Area3, ResponsiveContainer as ResponsiveContainer3 } from "recharts";
|
|
@@ -1027,7 +1090,7 @@ var LiteLLMHomeWidget = ({
|
|
|
1027
1090
|
/* @__PURE__ */ React7.createElement(MenuItem3, { value: "today" }, "Today"),
|
|
1028
1091
|
/* @__PURE__ */ React7.createElement(MenuItem3, { value: "7d" }, "7d"),
|
|
1029
1092
|
/* @__PURE__ */ React7.createElement(MenuItem3, { value: "30d" }, "30d")
|
|
1030
|
-
))), loading && /* @__PURE__ */ React7.createElement(Box6, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: 120 }, /* @__PURE__ */ React7.createElement(
|
|
1093
|
+
))), loading && /* @__PURE__ */ React7.createElement(Box6, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: 120 }, /* @__PURE__ */ React7.createElement(CircularProgress4, { size: 32 })), !loading && error && /* @__PURE__ */ React7.createElement(Alert2, { severity: "error", sx: { mt: 1 } }, error), !loading && !error && /* @__PURE__ */ React7.createElement(React7.Fragment, null, /* @__PURE__ */ React7.createElement(Grid3, { container: true, spacing: 2, sx: { mb: hasSparkline ? 1.5 : 0 } }, /* @__PURE__ */ React7.createElement(Grid3, { item: true, xs: 6 }, /* @__PURE__ */ React7.createElement(Kpi, { label: "USD Spent", value: fmtUsd2(usage?.total_spend ?? 0) })), /* @__PURE__ */ React7.createElement(Grid3, { item: true, xs: 6 }, /* @__PURE__ */ React7.createElement(Kpi, { label: "Tokens In", value: fmtInt2(usage?.prompt_tokens ?? 0) })), /* @__PURE__ */ React7.createElement(Grid3, { item: true, xs: 6 }, /* @__PURE__ */ React7.createElement(Kpi, { label: "Tokens Out", value: fmtInt2(usage?.completion_tokens ?? 0) })), /* @__PURE__ */ React7.createElement(Grid3, { item: true, xs: 6 }, /* @__PURE__ */ React7.createElement(Kpi, { label: "Keys", value: fmtInt2(keys.length) }))), hasSparkline && /* @__PURE__ */ React7.createElement(Box6, { height: 120 }, /* @__PURE__ */ React7.createElement(ResponsiveContainer3, { width: "100%", height: "100%" }, /* @__PURE__ */ React7.createElement(AreaChart3, { data: dailyData, margin: { top: 4, right: 0, bottom: 0, left: 0 } }, /* @__PURE__ */ React7.createElement(
|
|
1031
1094
|
Area3,
|
|
1032
1095
|
{
|
|
1033
1096
|
type: "monotone",
|