@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
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { DateRange, UsageMetrics, ModelInfo } from '../types';
|
|
2
|
+
import { DateRange, UsageMetrics, ModelInfo, UserInfo } from '../types';
|
|
3
3
|
interface UsageStatsProps {
|
|
4
4
|
usage: UsageMetrics | null;
|
|
5
5
|
models: ModelInfo[];
|
|
6
6
|
dateRange: DateRange;
|
|
7
7
|
onDateRangeChange: (range: DateRange) => void;
|
|
8
8
|
loading: boolean;
|
|
9
|
+
userInfo?: UserInfo;
|
|
9
10
|
}
|
|
10
11
|
export declare const UsageStats: React.FC<UsageStatsProps>;
|
|
11
12
|
export {};
|
package/dist/index.cjs.js
CHANGED
|
@@ -125,23 +125,17 @@ var init_api = __esm({
|
|
|
125
125
|
});
|
|
126
126
|
|
|
127
127
|
// src/components/DashboardHeader.tsx
|
|
128
|
-
var import_react, import_material,
|
|
128
|
+
var import_react, import_material, DashboardHeader;
|
|
129
129
|
var init_DashboardHeader = __esm({
|
|
130
130
|
"src/components/DashboardHeader.tsx"() {
|
|
131
131
|
"use strict";
|
|
132
132
|
import_react = __toESM(require("react"));
|
|
133
133
|
import_material = require("@mui/material");
|
|
134
|
-
import_icons_material = require("@mui/icons-material");
|
|
135
134
|
DashboardHeader = ({ userInfo, teams, loading }) => {
|
|
136
135
|
if (loading) {
|
|
137
136
|
return /* @__PURE__ */ import_react.default.createElement(import_material.Paper, { sx: { p: 2, mb: 2 } }, /* @__PURE__ */ import_react.default.createElement(import_material.LinearProgress, null));
|
|
138
137
|
}
|
|
139
138
|
const displayName = userInfo.user_email ?? userInfo.email ?? userInfo.user_id;
|
|
140
|
-
const budget = userInfo.max_budget ?? 0;
|
|
141
|
-
const spend = userInfo.spend ?? userInfo.current_spend ?? 0;
|
|
142
|
-
const budgetPct = budget > 0 ? Math.min(spend / budget * 100, 100) : 0;
|
|
143
|
-
const isOver = budget > 0 && spend >= budget;
|
|
144
|
-
const isNear = budget > 0 && spend >= budget * 0.8 && !isOver;
|
|
145
139
|
return /* @__PURE__ */ import_react.default.createElement(import_material.Paper, { sx: { p: 2, mb: 2 } }, /* @__PURE__ */ import_react.default.createElement(import_material.Box, { display: "flex", alignItems: "flex-start", gap: 2, flexWrap: "wrap" }, /* @__PURE__ */ import_react.default.createElement(import_material.Box, { flexGrow: 1 }, /* @__PURE__ */ import_react.default.createElement(import_material.Typography, { variant: "h6" }, displayName), /* @__PURE__ */ import_react.default.createElement(import_material.Typography, { variant: "caption", color: "text.secondary" }, userInfo.user_id), teams.length > 0 && /* @__PURE__ */ import_react.default.createElement(import_material.Box, { display: "flex", gap: 0.75, flexWrap: "wrap", mt: 1 }, teams.map((team) => /* @__PURE__ */ import_react.default.createElement(
|
|
146
140
|
import_material.Chip,
|
|
147
141
|
{
|
|
@@ -151,27 +145,19 @@ var init_DashboardHeader = __esm({
|
|
|
151
145
|
variant: "outlined",
|
|
152
146
|
color: "primary"
|
|
153
147
|
}
|
|
154
|
-
))))
|
|
155
|
-
import_material.LinearProgress,
|
|
156
|
-
{
|
|
157
|
-
variant: "determinate",
|
|
158
|
-
value: budgetPct,
|
|
159
|
-
color: isOver ? "error" : isNear ? "warning" : "primary",
|
|
160
|
-
sx: { height: 8, borderRadius: 1 }
|
|
161
|
-
}
|
|
162
|
-
))));
|
|
148
|
+
))))));
|
|
163
149
|
};
|
|
164
150
|
}
|
|
165
151
|
});
|
|
166
152
|
|
|
167
153
|
// src/components/KeysTable.tsx
|
|
168
|
-
var import_react2, import_material2,
|
|
154
|
+
var import_react2, import_material2, import_icons_material, maskKey, shortKeyId, formatDate, emptyForm, keyToEditForm, KeysTable;
|
|
169
155
|
var init_KeysTable = __esm({
|
|
170
156
|
"src/components/KeysTable.tsx"() {
|
|
171
157
|
"use strict";
|
|
172
158
|
import_react2 = __toESM(require("react"));
|
|
173
159
|
import_material2 = require("@mui/material");
|
|
174
|
-
|
|
160
|
+
import_icons_material = require("@mui/icons-material");
|
|
175
161
|
maskKey = (key) => {
|
|
176
162
|
if (key.length <= 8) return "***";
|
|
177
163
|
return `${key.slice(0, 4)}...${key.slice(-4)}`;
|
|
@@ -253,7 +239,7 @@ var init_KeysTable = __esm({
|
|
|
253
239
|
if (!editingKey) return;
|
|
254
240
|
setEditSubmitting(true);
|
|
255
241
|
try {
|
|
256
|
-
await onUpdateKey(editingKey.key, editForm);
|
|
242
|
+
await onUpdateKey(editingKey.token ?? editingKey.key, editForm);
|
|
257
243
|
handleCloseEdit();
|
|
258
244
|
} catch (error) {
|
|
259
245
|
console.error("Failed to update key:", error);
|
|
@@ -269,7 +255,7 @@ var init_KeysTable = __esm({
|
|
|
269
255
|
{
|
|
270
256
|
variant: "contained",
|
|
271
257
|
color: "primary",
|
|
272
|
-
startIcon: /* @__PURE__ */ import_react2.default.createElement(
|
|
258
|
+
startIcon: /* @__PURE__ */ import_react2.default.createElement(import_icons_material.Add, null),
|
|
273
259
|
onClick: () => setGenerateModalOpen(true)
|
|
274
260
|
},
|
|
275
261
|
"Generate New Key"
|
|
@@ -291,7 +277,7 @@ var init_KeysTable = __esm({
|
|
|
291
277
|
}
|
|
292
278
|
},
|
|
293
279
|
shortKeyId(keyId)
|
|
294
|
-
), /* @__PURE__ */ import_react2.default.createElement(import_material2.IconButton, { size: "small", onClick: () => copyToClipboard(keyId), title: "Copy Key ID" }, /* @__PURE__ */ import_react2.default.createElement(
|
|
280
|
+
), /* @__PURE__ */ import_react2.default.createElement(import_material2.IconButton, { size: "small", onClick: () => copyToClipboard(keyId), title: "Copy Key ID" }, /* @__PURE__ */ import_react2.default.createElement(import_icons_material.ContentCopy, { fontSize: "small" })))), /* @__PURE__ */ import_react2.default.createElement(import_material2.TableCell, null, formatDate(key.created_at)), /* @__PURE__ */ import_react2.default.createElement(import_material2.TableCell, null, "$", key.spend?.toFixed(4) || "0.00"), /* @__PURE__ */ import_react2.default.createElement(import_material2.TableCell, null, key.max_budget ? `$${key.max_budget}` : "-"), /* @__PURE__ */ import_react2.default.createElement(import_material2.TableCell, null, key.tpm_limit || "-"), /* @__PURE__ */ import_react2.default.createElement(import_material2.TableCell, null, /* @__PURE__ */ import_react2.default.createElement(import_material2.Box, { display: "flex", gap: 0.5, flexWrap: "wrap" }, key.models?.slice(0, 2).map((model) => /* @__PURE__ */ import_react2.default.createElement(import_material2.Chip, { key: model, label: model, size: "small" })), (key.models?.length || 0) > 2 && /* @__PURE__ */ import_react2.default.createElement(import_material2.Chip, { label: `+${(key.models?.length || 0) - 2}`, size: "small", variant: "outlined" }))), /* @__PURE__ */ import_react2.default.createElement(import_material2.TableCell, { align: "right" }, /* @__PURE__ */ import_react2.default.createElement(import_material2.IconButton, { onClick: () => handleOpenEdit(key) }, /* @__PURE__ */ import_react2.default.createElement(import_icons_material.Edit, { fontSize: "small" })), /* @__PURE__ */ import_react2.default.createElement(import_material2.IconButton, { color: "error", onClick: () => onDeleteKey(keyId) }, /* @__PURE__ */ import_react2.default.createElement(import_icons_material.Delete, null))));
|
|
295
281
|
}))))), /* @__PURE__ */ import_react2.default.createElement(import_material2.Dialog, { open: generateModalOpen, onClose: handleCloseModal, maxWidth: "sm", fullWidth: true }, /* @__PURE__ */ import_react2.default.createElement(import_material2.DialogTitle, null, newKeyValue ? "Key Generated" : "Generate New Key"), /* @__PURE__ */ import_react2.default.createElement(import_material2.DialogContent, null, newKeyValue ? /* @__PURE__ */ import_react2.default.createElement(import_material2.Box, null, /* @__PURE__ */ import_react2.default.createElement(import_material2.Typography, { variant: "body2", color: "text.secondary", gutterBottom: true }, "Copy this key now. You won't be able to see it again."), /* @__PURE__ */ import_react2.default.createElement(
|
|
296
282
|
import_material2.Box,
|
|
297
283
|
{
|
|
@@ -316,7 +302,7 @@ var init_KeysTable = __esm({
|
|
|
316
302
|
},
|
|
317
303
|
newKeyValue
|
|
318
304
|
),
|
|
319
|
-
/* @__PURE__ */ import_react2.default.createElement(import_material2.IconButton, { onClick: () => copyToClipboard(newKeyValue) }, /* @__PURE__ */ import_react2.default.createElement(
|
|
305
|
+
/* @__PURE__ */ import_react2.default.createElement(import_material2.IconButton, { onClick: () => copyToClipboard(newKeyValue) }, /* @__PURE__ */ import_react2.default.createElement(import_icons_material.ContentCopy, null))
|
|
320
306
|
)) : /* @__PURE__ */ import_react2.default.createElement(import_material2.Box, { display: "flex", flexDirection: "column", gap: 2, mt: 1 }, /* @__PURE__ */ import_react2.default.createElement(
|
|
321
307
|
import_material2.TextField,
|
|
322
308
|
{
|
|
@@ -449,31 +435,56 @@ var init_KeysTable = __esm({
|
|
|
449
435
|
});
|
|
450
436
|
|
|
451
437
|
// src/components/UsageStats.tsx
|
|
452
|
-
|
|
438
|
+
function modelColor(model) {
|
|
439
|
+
let h = 5381;
|
|
440
|
+
for (let i = 0; i < model.length; i++) h = (h << 5) + h + model.charCodeAt(i) >>> 0;
|
|
441
|
+
return MODEL_COLORS[h % MODEL_COLORS.length];
|
|
442
|
+
}
|
|
443
|
+
var import_react3, import_material3, import_recharts, TOP_N_MODELS, PERIOD_LS_KEY, MODEL_COLORS, fmtUsd, fmtInt, fmtPct, KpiCard, ChartSkeleton, EmptyChart, UsageStats;
|
|
453
444
|
var init_UsageStats = __esm({
|
|
454
445
|
"src/components/UsageStats.tsx"() {
|
|
455
446
|
"use strict";
|
|
456
447
|
import_react3 = __toESM(require("react"));
|
|
457
448
|
import_material3 = require("@mui/material");
|
|
458
449
|
import_recharts = require("recharts");
|
|
450
|
+
TOP_N_MODELS = 6;
|
|
451
|
+
PERIOD_LS_KEY = "litellm_usage_period";
|
|
452
|
+
MODEL_COLORS = [
|
|
453
|
+
"#8884d8",
|
|
454
|
+
"#82ca9d",
|
|
455
|
+
"#ffc658",
|
|
456
|
+
"#ff7300",
|
|
457
|
+
"#0088fe",
|
|
458
|
+
"#00C49F",
|
|
459
|
+
"#FFBB28",
|
|
460
|
+
"#FF8042",
|
|
461
|
+
"#a4de6c",
|
|
462
|
+
"#d0ed57"
|
|
463
|
+
];
|
|
459
464
|
fmtUsd = (n) => `$${(n ?? 0).toFixed(n < 1 ? 4 : 2)}`;
|
|
460
465
|
fmtInt = (n) => (n ?? 0).toLocaleString();
|
|
461
466
|
fmtPct = (n) => `${(n * 100).toFixed(1)}%`;
|
|
462
467
|
KpiCard = ({ label, value, hint }) => /* @__PURE__ */ import_react3.default.createElement(import_material3.Paper, { variant: "outlined", sx: { p: 2, height: "100%" } }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "caption", color: "text.secondary" }, label), /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "h5", sx: { mt: 0.5 } }, value), hint ? /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "caption", color: "text.secondary" }, hint) : null);
|
|
468
|
+
ChartSkeleton = ({ height = 260 }) => /* @__PURE__ */ import_react3.default.createElement(import_material3.Skeleton, { variant: "rectangular", height, sx: { borderRadius: 1 } });
|
|
469
|
+
EmptyChart = ({
|
|
470
|
+
height = 260,
|
|
471
|
+
message = "No data for this period"
|
|
472
|
+
}) => /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { height, display: "flex", alignItems: "center", justifyContent: "center" }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { color: "text.secondary", variant: "body2" }, message));
|
|
463
473
|
UsageStats = ({
|
|
464
474
|
usage,
|
|
465
475
|
models,
|
|
466
476
|
dateRange,
|
|
467
477
|
onDateRangeChange,
|
|
468
|
-
loading
|
|
478
|
+
loading,
|
|
479
|
+
userInfo
|
|
469
480
|
}) => {
|
|
470
481
|
const [selectedModel, setSelectedModel] = (0, import_react3.useState)("all");
|
|
471
482
|
const [tab, setTab] = (0, import_react3.useState)("costs");
|
|
472
483
|
const selectedPreset = (0, import_react3.useMemo)(() => {
|
|
473
|
-
|
|
474
|
-
const
|
|
475
|
-
|
|
476
|
-
|
|
484
|
+
if (dateRange.start.toDateString() === dateRange.end.toDateString()) return "today";
|
|
485
|
+
const diffMs = dateRange.end.getTime() - dateRange.start.getTime();
|
|
486
|
+
const diffHours = diffMs / (1e3 * 60 * 60);
|
|
487
|
+
if (diffHours <= 26) return "24h";
|
|
477
488
|
const diffDays = Math.ceil(diffMs / (1e3 * 60 * 60 * 24));
|
|
478
489
|
if (diffDays <= 7) return "7d";
|
|
479
490
|
return "30d";
|
|
@@ -482,25 +493,15 @@ var init_UsageStats = __esm({
|
|
|
482
493
|
const end = /* @__PURE__ */ new Date();
|
|
483
494
|
const start = /* @__PURE__ */ new Date();
|
|
484
495
|
if (preset === "today") start.setHours(0, 0, 0, 0);
|
|
496
|
+
else if (preset === "24h") start.setHours(start.getHours() - 24);
|
|
485
497
|
else if (preset === "7d") start.setDate(start.getDate() - 7);
|
|
486
498
|
else if (preset === "30d") start.setDate(start.getDate() - 30);
|
|
499
|
+
try {
|
|
500
|
+
localStorage.setItem(PERIOD_LS_KEY, preset);
|
|
501
|
+
} catch {
|
|
502
|
+
}
|
|
487
503
|
onDateRangeChange({ start, end });
|
|
488
504
|
};
|
|
489
|
-
const todayRows = (0, import_react3.useMemo)(() => {
|
|
490
|
-
const rows = usage?.daily_by_model ?? [];
|
|
491
|
-
if (rows.length === 0) return [];
|
|
492
|
-
const latestDate = rows.map((r) => r.date).sort().slice(-1)[0];
|
|
493
|
-
return rows.filter((r) => r.date === latestDate).map((r) => ({
|
|
494
|
-
model: r.model,
|
|
495
|
-
promptTokens: r.prompt_tokens,
|
|
496
|
-
completionTokens: r.completion_tokens
|
|
497
|
-
})).filter((r) => r.promptTokens + r.completionTokens > 0).sort((a, b) => b.promptTokens + b.completionTokens - (a.promptTokens + a.completionTokens));
|
|
498
|
-
}, [usage]);
|
|
499
|
-
const todayLabel = (0, import_react3.useMemo)(() => {
|
|
500
|
-
const rows = usage?.daily_by_model ?? [];
|
|
501
|
-
if (rows.length === 0) return null;
|
|
502
|
-
return rows.map((r) => r.date).sort().slice(-1)[0];
|
|
503
|
-
}, [usage]);
|
|
504
505
|
const dailyData = (0, import_react3.useMemo)(
|
|
505
506
|
() => (usage?.daily_usage ?? []).map((d) => ({
|
|
506
507
|
date: d.date,
|
|
@@ -514,6 +515,35 @@ var init_UsageStats = __esm({
|
|
|
514
515
|
})),
|
|
515
516
|
[usage]
|
|
516
517
|
);
|
|
518
|
+
const cumulativeData = (0, import_react3.useMemo)(() => {
|
|
519
|
+
let cum = 0;
|
|
520
|
+
return dailyData.map((d) => {
|
|
521
|
+
cum += d.spend;
|
|
522
|
+
return { date: d.date, cumulative: cum };
|
|
523
|
+
});
|
|
524
|
+
}, [dailyData]);
|
|
525
|
+
const successRateData = (0, import_react3.useMemo)(
|
|
526
|
+
() => dailyData.filter((d) => d.apiRequests > 0).map((d) => ({
|
|
527
|
+
date: d.date,
|
|
528
|
+
successRate: parseFloat((d.successfulRequests / d.apiRequests * 100).toFixed(1))
|
|
529
|
+
})),
|
|
530
|
+
[dailyData]
|
|
531
|
+
);
|
|
532
|
+
const { modelSpendByDate, topModels: topSpendModels } = (0, import_react3.useMemo)(() => {
|
|
533
|
+
const rows = usage?.daily_by_model ?? [];
|
|
534
|
+
const modelTotals = {};
|
|
535
|
+
for (const r of rows) modelTotals[r.model] = (modelTotals[r.model] ?? 0) + r.spend;
|
|
536
|
+
const top = Object.entries(modelTotals).sort(([, a], [, b]) => b - a).slice(0, TOP_N_MODELS).map(([m]) => m);
|
|
537
|
+
const dateMap = {};
|
|
538
|
+
for (const r of rows) {
|
|
539
|
+
if (!dateMap[r.date]) dateMap[r.date] = {};
|
|
540
|
+
const bucket = top.includes(r.model) ? r.model : "Other";
|
|
541
|
+
dateMap[r.date][bucket] = (dateMap[r.date][bucket] ?? 0) + r.spend;
|
|
542
|
+
}
|
|
543
|
+
const sorted = Object.entries(dateMap).sort(([a], [b]) => a.localeCompare(b)).map(([date, m]) => ({ date, ...m }));
|
|
544
|
+
const hasOther = sorted.some((r) => r.Other > 0);
|
|
545
|
+
return { modelSpendByDate: sorted, topModels: hasOther ? [...top, "Other"] : top };
|
|
546
|
+
}, [usage]);
|
|
517
547
|
const modelRows = (0, import_react3.useMemo)(() => {
|
|
518
548
|
const entries = Object.entries(usage?.usage_by_model ?? {}).map(([model, d]) => ({
|
|
519
549
|
model,
|
|
@@ -528,6 +558,10 @@ var init_UsageStats = __esm({
|
|
|
528
558
|
}));
|
|
529
559
|
return selectedModel === "all" ? entries : entries.filter((e) => e.model === selectedModel);
|
|
530
560
|
}, [usage, selectedModel]);
|
|
561
|
+
const topModelSpendBars = (0, import_react3.useMemo)(
|
|
562
|
+
() => [...modelRows].sort((a, b) => b.spend - a.spend).slice(0, 10),
|
|
563
|
+
[modelRows]
|
|
564
|
+
);
|
|
531
565
|
const keyRows = (0, import_react3.useMemo)(
|
|
532
566
|
() => Object.entries(usage?.usage_by_key ?? {}).map(([keyHash, d]) => ({
|
|
533
567
|
keyHash,
|
|
@@ -545,11 +579,14 @@ var init_UsageStats = __esm({
|
|
|
545
579
|
})),
|
|
546
580
|
[usage]
|
|
547
581
|
);
|
|
582
|
+
const topKeySpendBars = (0, import_react3.useMemo)(
|
|
583
|
+
() => [...keyRows].sort((a, b) => b.spend - a.spend).slice(0, 10),
|
|
584
|
+
[keyRows]
|
|
585
|
+
);
|
|
548
586
|
const totalRequests = usage?.api_requests ?? 0;
|
|
549
587
|
const overallSuccessRate = totalRequests > 0 ? (usage?.successful_requests ?? 0) / totalRequests : 0;
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
}
|
|
588
|
+
const maxBudget = userInfo?.max_budget ?? 0;
|
|
589
|
+
const totalCumSpend = cumulativeData[cumulativeData.length - 1]?.cumulative ?? 0;
|
|
553
590
|
return /* @__PURE__ */ import_react3.default.createElement(import_material3.Paper, { sx: { p: 2 } }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { display: "flex", justifyContent: "space-between", alignItems: "center", mb: 2, flexWrap: "wrap", gap: 2 }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "h6" }, "Usage Analytics"), /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { display: "flex", gap: 2, alignItems: "center", flexWrap: "wrap" }, /* @__PURE__ */ import_react3.default.createElement(import_material3.FormControl, { size: "small", sx: { minWidth: 140 } }, /* @__PURE__ */ import_react3.default.createElement(import_material3.InputLabel, null, "Period"), /* @__PURE__ */ import_react3.default.createElement(
|
|
554
591
|
import_material3.Select,
|
|
555
592
|
{
|
|
@@ -558,6 +595,7 @@ var init_UsageStats = __esm({
|
|
|
558
595
|
onChange: (e) => handlePresetChange(e.target.value)
|
|
559
596
|
},
|
|
560
597
|
/* @__PURE__ */ import_react3.default.createElement(import_material3.MenuItem, { value: "today" }, "Today"),
|
|
598
|
+
/* @__PURE__ */ import_react3.default.createElement(import_material3.MenuItem, { value: "24h" }, "Last 24h"),
|
|
561
599
|
/* @__PURE__ */ import_react3.default.createElement(import_material3.MenuItem, { value: "7d" }, "Last 7 days"),
|
|
562
600
|
/* @__PURE__ */ import_react3.default.createElement(import_material3.MenuItem, { value: "30d" }, "Last 30 days")
|
|
563
601
|
)), tab === "models" && /* @__PURE__ */ import_react3.default.createElement(import_material3.FormControl, { size: "small", sx: { minWidth: 180 } }, /* @__PURE__ */ import_react3.default.createElement(import_material3.InputLabel, null, "Model"), /* @__PURE__ */ import_react3.default.createElement(
|
|
@@ -569,40 +607,51 @@ var init_UsageStats = __esm({
|
|
|
569
607
|
},
|
|
570
608
|
/* @__PURE__ */ import_react3.default.createElement(import_material3.MenuItem, { value: "all" }, "All Models"),
|
|
571
609
|
models.map((m) => /* @__PURE__ */ import_react3.default.createElement(import_material3.MenuItem, { key: m.model_name, value: m.model_name }, m.model_name))
|
|
572
|
-
)))), /* @__PURE__ */ import_react3.default.createElement(import_material3.Grid, { container: true, spacing: 2, sx: { mb: 2 } }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Grid, { item: true, xs: 6, sm: 3 }, /* @__PURE__ */ import_react3.default.createElement(KpiCard, { label: "Total Spend", value: fmtUsd(usage?.total_spend ?? 0) })), /* @__PURE__ */ import_react3.default.createElement(import_material3.Grid, { item: true, xs: 6, sm: 3 }, /* @__PURE__ */ import_react3.default.createElement(KpiCard, { label: "Total Requests", value: fmtInt(totalRequests), hint: `${fmtInt(usage?.failed_requests ?? 0)} failed` })), /* @__PURE__ */ import_react3.default.createElement(import_material3.Grid, { item: true, xs: 6, sm: 3 }, /* @__PURE__ */ import_react3.default.createElement(KpiCard, { label: "Success Rate", value: totalRequests > 0 ? fmtPct(overallSuccessRate) : "\u2014" })), /* @__PURE__ */ import_react3.default.createElement(import_material3.Grid, { item: true, xs: 6, sm: 3 }, /* @__PURE__ */ import_react3.default.createElement(
|
|
610
|
+
)))), /* @__PURE__ */ import_react3.default.createElement(import_material3.Grid, { container: true, spacing: 2, sx: { mb: 2 } }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Grid, { item: true, xs: 6, sm: 3 }, loading ? /* @__PURE__ */ import_react3.default.createElement(ChartSkeleton, { height: 80 }) : /* @__PURE__ */ import_react3.default.createElement(KpiCard, { label: "Total Spend", value: fmtUsd(usage?.total_spend ?? 0) })), /* @__PURE__ */ import_react3.default.createElement(import_material3.Grid, { item: true, xs: 6, sm: 3 }, loading ? /* @__PURE__ */ import_react3.default.createElement(ChartSkeleton, { height: 80 }) : /* @__PURE__ */ import_react3.default.createElement(KpiCard, { label: "Total Requests", value: fmtInt(totalRequests), hint: `${fmtInt(usage?.failed_requests ?? 0)} failed` })), /* @__PURE__ */ import_react3.default.createElement(import_material3.Grid, { item: true, xs: 6, sm: 3 }, loading ? /* @__PURE__ */ import_react3.default.createElement(ChartSkeleton, { height: 80 }) : /* @__PURE__ */ import_react3.default.createElement(KpiCard, { label: "Success Rate", value: totalRequests > 0 ? fmtPct(overallSuccessRate) : "\u2014" })), /* @__PURE__ */ import_react3.default.createElement(import_material3.Grid, { item: true, xs: 6, sm: 3 }, loading ? /* @__PURE__ */ import_react3.default.createElement(ChartSkeleton, { height: 80 }) : /* @__PURE__ */ import_react3.default.createElement(
|
|
573
611
|
KpiCard,
|
|
574
612
|
{
|
|
575
613
|
label: "Total Tokens",
|
|
576
614
|
value: fmtInt(usage?.total_tokens ?? 0),
|
|
577
615
|
hint: `${fmtInt(usage?.prompt_tokens ?? 0)} in \xB7 ${fmtInt(usage?.completion_tokens ?? 0)} out`
|
|
578
616
|
}
|
|
579
|
-
))), /* @__PURE__ */ import_react3.default.createElement(import_material3.Tabs, { value: tab, onChange: (_, v) => setTab(v), sx: { mb: 2 } }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Tab, { value: "costs", label: "Costs" }), /* @__PURE__ */ import_react3.default.createElement(import_material3.Tab, { value: "models", label: "Model Activity" }), /* @__PURE__ */ import_react3.default.createElement(import_material3.Tab, { value: "keys", label: "Key Activity" })), tab === "costs" && /* @__PURE__ */ import_react3.default.createElement(import_material3.Grid, { container: true, spacing: 3 }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Grid, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Daily Spend
|
|
617
|
+
))), /* @__PURE__ */ import_react3.default.createElement(import_material3.Tabs, { value: tab, onChange: (_, v) => setTab(v), sx: { mb: 2 } }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Tab, { value: "costs", label: "Costs" }), /* @__PURE__ */ import_react3.default.createElement(import_material3.Tab, { value: "models", label: "Model Activity" }), /* @__PURE__ */ import_react3.default.createElement(import_material3.Tab, { value: "keys", label: "Key Activity" })), tab === "costs" && /* @__PURE__ */ import_react3.default.createElement(import_material3.Grid, { container: true, spacing: 3 }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Grid, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Daily Spend by Model"), loading ? /* @__PURE__ */ import_react3.default.createElement(ChartSkeleton, null) : modelSpendByDate.length === 0 ? /* @__PURE__ */ import_react3.default.createElement(EmptyChart, null) : /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { height: 260 }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.AreaChart, { data: modelSpendByDate }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.CartesianGrid, { strokeDasharray: "3 3" }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.XAxis, { dataKey: "date", tick: { fontSize: 12 } }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.YAxis, { tick: { fontSize: 12 }, tickFormatter: (v) => `$${v.toFixed(2)}` }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Tooltip, { formatter: (v) => [`$${v.toFixed(4)}`, void 0] }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Legend, null), topSpendModels.map((m) => /* @__PURE__ */ import_react3.default.createElement(
|
|
618
|
+
import_recharts.Area,
|
|
619
|
+
{
|
|
620
|
+
key: m,
|
|
621
|
+
type: "monotone",
|
|
622
|
+
dataKey: m,
|
|
623
|
+
stackId: "spend",
|
|
624
|
+
stroke: modelColor(m),
|
|
625
|
+
fill: modelColor(m),
|
|
626
|
+
fillOpacity: 0.6
|
|
627
|
+
}
|
|
628
|
+
)))))), /* @__PURE__ */ import_react3.default.createElement(import_material3.Grid, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Daily Token Usage"), loading ? /* @__PURE__ */ import_react3.default.createElement(ChartSkeleton, null) : dailyData.length === 0 ? /* @__PURE__ */ import_react3.default.createElement(EmptyChart, null) : /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { height: 260 }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.AreaChart, { data: dailyData }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.CartesianGrid, { strokeDasharray: "3 3" }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.XAxis, { dataKey: "date", tick: { fontSize: 12 } }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.YAxis, { tick: { fontSize: 12 }, tickFormatter: fmtInt }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Tooltip, { formatter: (v) => [fmtInt(v), void 0] }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Legend, null), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Area, { type: "monotone", dataKey: "promptTokens", name: "Input (prompt)", stackId: "tok", stroke: "#8884d8", fill: "#8884d8", fillOpacity: 0.5 }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Area, { type: "monotone", dataKey: "completionTokens", name: "Output (completion)", stackId: "tok", stroke: "#82ca9d", fill: "#82ca9d", fillOpacity: 0.5 }))))), /* @__PURE__ */ import_react3.default.createElement(import_material3.Grid, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Daily Requests"), loading ? /* @__PURE__ */ import_react3.default.createElement(ChartSkeleton, null) : dailyData.length === 0 ? /* @__PURE__ */ import_react3.default.createElement(EmptyChart, null) : /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { height: 260 }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.BarChart, { data: dailyData }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.CartesianGrid, { strokeDasharray: "3 3" }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.XAxis, { dataKey: "date", tick: { fontSize: 12 } }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.YAxis, { tick: { fontSize: 12 } }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Tooltip, null), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Legend, null), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Bar, { dataKey: "successfulRequests", name: "Successful", fill: "#82ca9d", stackId: "r" }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Bar, { dataKey: "failedRequests", name: "Failed", fill: "#e57373", stackId: "r" }))))), /* @__PURE__ */ import_react3.default.createElement(import_material3.Grid, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Daily Success Rate"), loading ? /* @__PURE__ */ import_react3.default.createElement(ChartSkeleton, null) : successRateData.length === 0 ? /* @__PURE__ */ import_react3.default.createElement(EmptyChart, null) : /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { height: 260 }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.LineChart, { data: successRateData }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.CartesianGrid, { strokeDasharray: "3 3" }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.XAxis, { dataKey: "date", tick: { fontSize: 12 } }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.YAxis, { domain: [0, 100], tick: { fontSize: 12 }, tickFormatter: (v) => `${v}%` }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Tooltip, { formatter: (v) => [`${v}%`, "Success rate"] }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.ReferenceLine, { y: 100, stroke: "#82ca9d", strokeDasharray: "4 2" }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Line, { type: "monotone", dataKey: "successRate", name: "Success rate", stroke: "#8884d8", dot: { r: 3 } }))))), (maxBudget > 0 || cumulativeData.length > 0) && /* @__PURE__ */ import_react3.default.createElement(import_material3.Grid, { item: true, xs: 12 }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Cumulative Spend", maxBudget > 0 ? ` vs Budget (${fmtUsd(maxBudget)})` : "", maxBudget > 0 && /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { component: "span", variant: "caption", color: "text.secondary", sx: { ml: 1 } }, fmtUsd(totalCumSpend), " used \xB7 ", fmtUsd(Math.max(0, maxBudget - totalCumSpend)), " remaining")), loading ? /* @__PURE__ */ import_react3.default.createElement(ChartSkeleton, { height: 180 }) : cumulativeData.length === 0 ? /* @__PURE__ */ import_react3.default.createElement(EmptyChart, { height: 180 }) : /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { height: 180 }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.AreaChart, { data: cumulativeData }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.CartesianGrid, { strokeDasharray: "3 3" }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.XAxis, { dataKey: "date", tick: { fontSize: 12 } }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.YAxis, { tick: { fontSize: 12 }, tickFormatter: (v) => `$${v.toFixed(2)}` }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Tooltip, { formatter: (v) => [`$${v.toFixed(4)}`, "Cumulative spend"] }), maxBudget > 0 && /* @__PURE__ */ import_react3.default.createElement(import_recharts.ReferenceLine, { y: maxBudget, stroke: "#e57373", strokeDasharray: "6 3", label: { value: `Budget ${fmtUsd(maxBudget)}`, position: "insideTopRight", fontSize: 11 } }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Area, { type: "monotone", dataKey: "cumulative", name: "Cumulative spend", stroke: "#ffc658", fill: "#ffc658", fillOpacity: 0.3 })))))), tab === "models" && /* @__PURE__ */ import_react3.default.createElement(import_material3.Grid, { container: true, spacing: 3 }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Grid, { item: true, xs: 12 }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Spend by Model"), loading ? /* @__PURE__ */ import_react3.default.createElement(ChartSkeleton, null) : topModelSpendBars.length === 0 ? /* @__PURE__ */ import_react3.default.createElement(EmptyChart, null) : /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { height: Math.max(200, topModelSpendBars.length * 36) }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.BarChart, { data: topModelSpendBars, layout: "vertical", margin: { left: 20 } }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.CartesianGrid, { strokeDasharray: "3 3" }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.XAxis, { type: "number", tick: { fontSize: 12 }, tickFormatter: (v) => `$${v.toFixed(2)}` }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.YAxis, { type: "category", dataKey: "model", tick: { fontSize: 11 }, width: 200 }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Tooltip, { formatter: (v) => [fmtUsd(v), "Spend"] }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Bar, { dataKey: "spend", name: "Spend", radius: [0, 4, 4, 0] }, topModelSpendBars.map((r) => /* @__PURE__ */ import_react3.default.createElement("rect", { key: r.model, fill: modelColor(r.model) }))))))), /* @__PURE__ */ import_react3.default.createElement(import_material3.Grid, { item: true, xs: 12 }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Tokens by Model"), loading ? /* @__PURE__ */ import_react3.default.createElement(ChartSkeleton, null) : modelRows.length === 0 ? /* @__PURE__ */ import_react3.default.createElement(EmptyChart, null) : /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { height: 260 }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.BarChart, { data: modelRows }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.CartesianGrid, { strokeDasharray: "3 3" }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.XAxis, { dataKey: "model", tick: { fontSize: 10 }, interval: 0, angle: -15, textAnchor: "end", height: 60 }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.YAxis, { tick: { fontSize: 12 } }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Tooltip, null), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Legend, null), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Bar, { dataKey: "promptTokens", name: "Prompt", fill: "#8884d8", stackId: "t" }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Bar, { dataKey: "completionTokens", name: "Completion", fill: "#82ca9d", stackId: "t" }))))), /* @__PURE__ */ import_react3.default.createElement(import_material3.Grid, { item: true, xs: 12 }, /* @__PURE__ */ import_react3.default.createElement(import_material3.TableContainer, { component: import_material3.Paper, variant: "outlined" }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Table, { size: "small" }, /* @__PURE__ */ import_react3.default.createElement(import_material3.TableHead, null, /* @__PURE__ */ import_react3.default.createElement(import_material3.TableRow, null, /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, null, "Model"), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, "Spend"), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, "Requests"), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, "Success"), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, "Failed"), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, "Prompt"), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, "Completion"), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { sx: { minWidth: 120 } }, "Success rate"))), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableBody, null, loading ? /* @__PURE__ */ import_react3.default.createElement(import_material3.TableRow, null, /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { colSpan: 8 }, /* @__PURE__ */ import_react3.default.createElement(import_material3.LinearProgress, null))) : modelRows.length === 0 ? /* @__PURE__ */ import_react3.default.createElement(import_material3.TableRow, null, /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { colSpan: 8, align: "center" }, "No model activity")) : [...modelRows].sort((a, b) => b.spend - a.spend || b.totalTokens - a.totalTokens).map((r) => /* @__PURE__ */ import_react3.default.createElement(import_material3.TableRow, { key: r.model }, /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, null, /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { display: "flex", alignItems: "center", gap: 0.75 }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { sx: { width: 10, height: 10, borderRadius: "50%", bgcolor: modelColor(r.model), flexShrink: 0 } }), r.model)), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, fmtUsd(r.spend)), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, fmtInt(r.apiRequests)), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, fmtInt(r.successfulRequests)), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, fmtInt(r.failedRequests)), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, fmtInt(r.promptTokens)), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, fmtInt(r.completionTokens)), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, null, r.apiRequests > 0 ? /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { display: "flex", alignItems: "center", gap: 1 }, /* @__PURE__ */ import_react3.default.createElement(
|
|
580
629
|
import_material3.LinearProgress,
|
|
581
630
|
{
|
|
582
631
|
variant: "determinate",
|
|
583
632
|
value: r.successRate * 100,
|
|
584
633
|
sx: { flex: 1, height: 6, borderRadius: 3 }
|
|
585
634
|
}
|
|
586
|
-
), /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "caption" }, fmtPct(r.successRate))) : "\u2014")))))))), tab === "keys" && /* @__PURE__ */ import_react3.default.createElement(import_material3.TableContainer, { component: import_material3.Paper, variant: "outlined" }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Table, { size: "small" }, /* @__PURE__ */ import_react3.default.createElement(import_material3.TableHead, null, /* @__PURE__ */ import_react3.default.createElement(import_material3.TableRow, null, /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, null, "Key"), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, null, "Models"), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, "Spend"), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, "Requests"), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, "Tokens"), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, "Failed"), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { sx: { minWidth: 120 } }, "Success rate"))), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableBody, null, keyRows.length === 0 ? /* @__PURE__ */ import_react3.default.createElement(import_material3.TableRow, null, /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { colSpan: 7, align: "center" }, "No key activity")) : keyRows.sort((a, b) => b.spend - a.spend || b.apiRequests - a.apiRequests).map((r) => /* @__PURE__ */ import_react3.default.createElement(import_material3.TableRow, { key: r.keyHash }, /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, null, /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "body2" }, r.keyAlias), r.teamId ? /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "caption", color: "text.secondary" }, "team: ", r.teamId) : null), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, null, /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { display: "flex", gap: 0.5, flexWrap: "wrap" }, r.models.map((m) => /* @__PURE__ */ import_react3.default.createElement(import_material3.Chip, { key: m, label: m, size: "small", variant: "outlined" })))), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, fmtUsd(r.spend)), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, fmtInt(r.apiRequests)), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, fmtInt(r.totalTokens)), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, fmtInt(r.failedRequests)), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, null, r.apiRequests > 0 ? /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { display: "flex", alignItems: "center", gap: 1 }, /* @__PURE__ */ import_react3.default.createElement(
|
|
635
|
+
), /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "caption" }, fmtPct(r.successRate))) : "\u2014")))))))), tab === "keys" && /* @__PURE__ */ import_react3.default.createElement(import_material3.Grid, { container: true, spacing: 3 }, (loading || topKeySpendBars.length > 0) && /* @__PURE__ */ import_react3.default.createElement(import_material3.Grid, { item: true, xs: 12 }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Spend by Key"), loading ? /* @__PURE__ */ import_react3.default.createElement(ChartSkeleton, null) : /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { height: Math.max(160, topKeySpendBars.length * 36) }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.BarChart, { data: topKeySpendBars, layout: "vertical", margin: { left: 20 } }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.CartesianGrid, { strokeDasharray: "3 3" }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.XAxis, { type: "number", tick: { fontSize: 12 }, tickFormatter: (v) => `$${v.toFixed(2)}` }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.YAxis, { type: "category", dataKey: "keyAlias", tick: { fontSize: 11 }, width: 160 }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Tooltip, { formatter: (v) => [fmtUsd(v), "Spend"] }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Bar, { dataKey: "spend", name: "Spend", fill: "#8884d8", radius: [0, 4, 4, 0] }))))), /* @__PURE__ */ import_react3.default.createElement(import_material3.Grid, { item: true, xs: 12 }, /* @__PURE__ */ import_react3.default.createElement(import_material3.TableContainer, { component: import_material3.Paper, variant: "outlined" }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Table, { size: "small" }, /* @__PURE__ */ import_react3.default.createElement(import_material3.TableHead, null, /* @__PURE__ */ import_react3.default.createElement(import_material3.TableRow, null, /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, null, "Key"), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, null, "Models"), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, "Spend"), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, "Requests"), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, "Tokens"), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, "Failed"), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { sx: { minWidth: 120 } }, "Success rate"))), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableBody, null, loading ? /* @__PURE__ */ import_react3.default.createElement(import_material3.TableRow, null, /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { colSpan: 7 }, /* @__PURE__ */ import_react3.default.createElement(import_material3.LinearProgress, null))) : keyRows.length === 0 ? /* @__PURE__ */ import_react3.default.createElement(import_material3.TableRow, null, /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { colSpan: 7, align: "center" }, "No key activity")) : [...keyRows].sort((a, b) => b.spend - a.spend || b.apiRequests - a.apiRequests).map((r) => /* @__PURE__ */ import_react3.default.createElement(import_material3.TableRow, { key: r.keyHash }, /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, null, /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "body2" }, r.keyAlias), r.teamId ? /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "caption", color: "text.secondary" }, "team: ", r.teamId) : null), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, null, /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { display: "flex", gap: 0.5, flexWrap: "wrap" }, r.models.map((m) => /* @__PURE__ */ import_react3.default.createElement(import_material3.Chip, { key: m, label: m, size: "small", variant: "outlined" })))), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, fmtUsd(r.spend)), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, fmtInt(r.apiRequests)), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, fmtInt(r.totalTokens)), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, fmtInt(r.failedRequests)), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, null, r.apiRequests > 0 ? /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { display: "flex", alignItems: "center", gap: 1 }, /* @__PURE__ */ import_react3.default.createElement(
|
|
587
636
|
import_material3.LinearProgress,
|
|
588
637
|
{
|
|
589
638
|
variant: "determinate",
|
|
590
639
|
value: r.successRate * 100,
|
|
591
640
|
sx: { flex: 1, height: 6, borderRadius: 3 }
|
|
592
641
|
}
|
|
593
|
-
), /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "caption" }, fmtPct(r.successRate))) : "\u2014")))))));
|
|
642
|
+
), /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "caption" }, fmtPct(r.successRate))) : "\u2014")))))))));
|
|
594
643
|
};
|
|
595
644
|
}
|
|
596
645
|
});
|
|
597
646
|
|
|
598
647
|
// src/components/TeamUsage.tsx
|
|
599
|
-
var import_react4, import_material4,
|
|
648
|
+
var import_react4, import_material4, import_icons_material2, import_recharts2, TeamCard, TeamUsage;
|
|
600
649
|
var init_TeamUsage = __esm({
|
|
601
650
|
"src/components/TeamUsage.tsx"() {
|
|
602
651
|
"use strict";
|
|
603
652
|
import_react4 = __toESM(require("react"));
|
|
604
653
|
import_material4 = require("@mui/material");
|
|
605
|
-
|
|
654
|
+
import_icons_material2 = require("@mui/icons-material");
|
|
606
655
|
import_recharts2 = require("recharts");
|
|
607
656
|
TeamCard = ({ team, usage, usageLoading }) => {
|
|
608
657
|
const [expanded, setExpanded] = (0, import_react4.useState)(false);
|
|
@@ -612,7 +661,7 @@ var init_TeamUsage = __esm({
|
|
|
612
661
|
const isOver = budget > 0 && spend >= budget;
|
|
613
662
|
const isNear = budget > 0 && spend >= budget * 0.8 && !isOver;
|
|
614
663
|
const dailyData = usage?.daily_usage?.map((d) => ({ date: d.date, spend: d.spend })) ?? [];
|
|
615
|
-
return /* @__PURE__ */ import_react4.default.createElement(import_material4.Paper, { variant: "outlined", sx: { p: 2, mb: 2 } }, /* @__PURE__ */ import_react4.default.createElement(import_material4.Box, { display: "flex", alignItems: "center", gap: 1 }, /* @__PURE__ */ import_react4.default.createElement(
|
|
664
|
+
return /* @__PURE__ */ import_react4.default.createElement(import_material4.Paper, { variant: "outlined", sx: { p: 2, mb: 2 } }, /* @__PURE__ */ import_react4.default.createElement(import_material4.Box, { display: "flex", alignItems: "center", gap: 1 }, /* @__PURE__ */ import_react4.default.createElement(import_icons_material2.Group, { color: "action" }), /* @__PURE__ */ import_react4.default.createElement(import_material4.Box, { flexGrow: 1 }, /* @__PURE__ */ import_react4.default.createElement(import_material4.Typography, { variant: "subtitle1", fontWeight: 600 }, team.team_alias ?? team.team_id), team.models?.length ? /* @__PURE__ */ import_react4.default.createElement(import_material4.Box, { display: "flex", gap: 0.5, flexWrap: "wrap", mt: 0.5 }, team.models.map((m) => /* @__PURE__ */ import_react4.default.createElement(import_material4.Chip, { key: m, label: m, size: "small", variant: "outlined" }))) : /* @__PURE__ */ import_react4.default.createElement(import_material4.Typography, { variant: "caption", color: "text.secondary" }, "All models")), /* @__PURE__ */ import_react4.default.createElement(import_material4.IconButton, { size: "small", onClick: () => setExpanded((e) => !e) }, expanded ? /* @__PURE__ */ import_react4.default.createElement(import_icons_material2.ExpandLess, null) : /* @__PURE__ */ import_react4.default.createElement(import_icons_material2.ExpandMore, null))), budget > 0 && /* @__PURE__ */ import_react4.default.createElement(import_material4.Box, { mt: 1.5 }, /* @__PURE__ */ import_react4.default.createElement(import_material4.Box, { display: "flex", justifyContent: "space-between", mb: 0.5 }, /* @__PURE__ */ import_react4.default.createElement(import_material4.Typography, { variant: "body2" }, "$", spend.toFixed(2), " / $", budget.toFixed(2)), isOver && /* @__PURE__ */ import_react4.default.createElement(import_material4.Chip, { label: "Over Budget", size: "small", color: "error" }), isNear && /* @__PURE__ */ import_react4.default.createElement(import_material4.Chip, { label: "Near Limit", size: "small", color: "warning" })), /* @__PURE__ */ import_react4.default.createElement(
|
|
616
665
|
import_material4.LinearProgress,
|
|
617
666
|
{
|
|
618
667
|
variant: "determinate",
|
|
@@ -639,7 +688,7 @@ var init_TeamUsage = __esm({
|
|
|
639
688
|
return /* @__PURE__ */ import_react4.default.createElement(import_material4.Paper, { sx: { p: 2 } }, /* @__PURE__ */ import_react4.default.createElement(import_material4.LinearProgress, null));
|
|
640
689
|
}
|
|
641
690
|
if (!teams.length) {
|
|
642
|
-
return /* @__PURE__ */ import_react4.default.createElement(import_material4.Paper, { sx: { p: 2 } }, /* @__PURE__ */ import_react4.default.createElement(import_material4.Box, { display: "flex", alignItems: "center", gap: 1 }, /* @__PURE__ */ import_react4.default.createElement(
|
|
691
|
+
return /* @__PURE__ */ import_react4.default.createElement(import_material4.Paper, { sx: { p: 2 } }, /* @__PURE__ */ import_react4.default.createElement(import_material4.Box, { display: "flex", alignItems: "center", gap: 1 }, /* @__PURE__ */ import_react4.default.createElement(import_icons_material2.Group, { color: "disabled" }), /* @__PURE__ */ import_react4.default.createElement(import_material4.Typography, { color: "text.secondary", variant: "body2" }, "No team membership found in LiteLLM for this account.")));
|
|
643
692
|
}
|
|
644
693
|
return /* @__PURE__ */ import_react4.default.createElement(import_material4.Box, null, /* @__PURE__ */ import_react4.default.createElement(import_material4.Typography, { variant: "h6", mb: 1 }, "Teams"), teams.map((team) => /* @__PURE__ */ import_react4.default.createElement(
|
|
645
694
|
TeamCard,
|
|
@@ -659,7 +708,21 @@ var LiteLLMPage_exports = {};
|
|
|
659
708
|
__export(LiteLLMPage_exports, {
|
|
660
709
|
LiteLLMPage: () => LiteLLMPage
|
|
661
710
|
});
|
|
662
|
-
|
|
711
|
+
function initDateRange() {
|
|
712
|
+
let preset = "7d";
|
|
713
|
+
try {
|
|
714
|
+
preset = localStorage.getItem(PERIOD_LS_KEY2) ?? "7d";
|
|
715
|
+
} catch {
|
|
716
|
+
}
|
|
717
|
+
const end = /* @__PURE__ */ new Date();
|
|
718
|
+
const start = /* @__PURE__ */ new Date();
|
|
719
|
+
if (preset === "today") start.setHours(0, 0, 0, 0);
|
|
720
|
+
else if (preset === "24h") start.setHours(start.getHours() - 24);
|
|
721
|
+
else if (preset === "30d") start.setDate(start.getDate() - 30);
|
|
722
|
+
else start.setDate(start.getDate() - 7);
|
|
723
|
+
return { start, end };
|
|
724
|
+
}
|
|
725
|
+
var import_react5, import_material5, import_react_use, import_core_plugin_api2, PERIOD_LS_KEY2, LiteLLMPage;
|
|
663
726
|
var init_LiteLLMPage = __esm({
|
|
664
727
|
"src/components/LiteLLMPage.tsx"() {
|
|
665
728
|
"use strict";
|
|
@@ -672,14 +735,10 @@ var init_LiteLLMPage = __esm({
|
|
|
672
735
|
init_UsageStats();
|
|
673
736
|
init_TeamUsage();
|
|
674
737
|
init_api();
|
|
738
|
+
PERIOD_LS_KEY2 = "litellm_usage_period";
|
|
675
739
|
LiteLLMPage = () => {
|
|
676
740
|
const api = (0, import_core_plugin_api2.useApi)(liteLlmApiRef);
|
|
677
|
-
const [dateRange, setDateRange] = (0, import_react5.useState)(
|
|
678
|
-
const end = /* @__PURE__ */ new Date();
|
|
679
|
-
const start = /* @__PURE__ */ new Date();
|
|
680
|
-
start.setDate(start.getDate() - 7);
|
|
681
|
-
return { start, end };
|
|
682
|
-
});
|
|
741
|
+
const [dateRange, setDateRange] = (0, import_react5.useState)(initDateRange);
|
|
683
742
|
const [snackbar, setSnackbar] = (0, import_react5.useState)(null);
|
|
684
743
|
const [teamUsageCache, setTeamUsageCache] = (0, import_react5.useState)({});
|
|
685
744
|
const [teamUsageLoading, setTeamUsageLoading] = (0, import_react5.useState)({});
|
|
@@ -839,7 +898,8 @@ var init_LiteLLMPage = __esm({
|
|
|
839
898
|
models: allModels ?? [],
|
|
840
899
|
dateRange,
|
|
841
900
|
onDateRangeChange: setDateRange,
|
|
842
|
-
loading: usageLoading
|
|
901
|
+
loading: usageLoading,
|
|
902
|
+
userInfo
|
|
843
903
|
}
|
|
844
904
|
))), /* @__PURE__ */ import_react5.default.createElement(
|
|
845
905
|
import_material5.Snackbar,
|
|
@@ -872,7 +932,7 @@ module.exports = __toCommonJS(index_exports);
|
|
|
872
932
|
|
|
873
933
|
// src/plugin.tsx
|
|
874
934
|
var import_react6 = __toESM(require("react"));
|
|
875
|
-
var
|
|
935
|
+
var import_icons_material3 = require("@mui/icons-material");
|
|
876
936
|
var import_frontend_plugin_api = require("@backstage/frontend-plugin-api");
|
|
877
937
|
init_api();
|
|
878
938
|
var liteLlmApi = import_frontend_plugin_api.ApiBlueprint.make({
|
|
@@ -886,7 +946,7 @@ var liteLlmPage = import_frontend_plugin_api.PageBlueprint.make({
|
|
|
886
946
|
params: {
|
|
887
947
|
path: "/litellm",
|
|
888
948
|
title: "LiteLLM",
|
|
889
|
-
icon: /* @__PURE__ */ import_react6.default.createElement(
|
|
949
|
+
icon: /* @__PURE__ */ import_react6.default.createElement(import_icons_material3.TrendingUp, null),
|
|
890
950
|
loader: async () => {
|
|
891
951
|
const { LiteLLMPage: LiteLLMPage2 } = await Promise.resolve().then(() => (init_LiteLLMPage(), LiteLLMPage_exports));
|
|
892
952
|
return /* @__PURE__ */ import_react6.default.createElement(LiteLLMPage2, null);
|