@acarmisc/backstage-plugin-litellm 0.2.1 → 0.3.1
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/LiteLLMHomeWidget.d.ts +8 -0
- package/dist/index.cjs.js +112 -28
- package/dist/index.cjs.js.map +4 -4
- package/dist/index.d.ts +2 -0
- package/dist/index.esm.js +123 -29
- package/dist/index.esm.js.map +4 -4
- package/dist/types.d.ts +1 -0
- package/package.json +1 -1
package/dist/index.esm.js
CHANGED
|
@@ -166,8 +166,8 @@ import {
|
|
|
166
166
|
CircularProgress,
|
|
167
167
|
Autocomplete
|
|
168
168
|
} from "@mui/material";
|
|
169
|
-
import { ContentCopy, Delete, Add, Edit
|
|
170
|
-
var maskKey, formatDate, emptyForm, keyToEditForm, KeysTable;
|
|
169
|
+
import { ContentCopy, Delete, Add, Edit } from "@mui/icons-material";
|
|
170
|
+
var maskKey, shortKeyId, formatDate, emptyForm, keyToEditForm, KeysTable;
|
|
171
171
|
var init_KeysTable = __esm({
|
|
172
172
|
"src/components/KeysTable.tsx"() {
|
|
173
173
|
"use strict";
|
|
@@ -175,6 +175,11 @@ var init_KeysTable = __esm({
|
|
|
175
175
|
if (key.length <= 8) return "***";
|
|
176
176
|
return `${key.slice(0, 4)}...${key.slice(-4)}`;
|
|
177
177
|
};
|
|
178
|
+
shortKeyId = (token) => {
|
|
179
|
+
if (!token) return "-";
|
|
180
|
+
if (token.length <= 16) return token;
|
|
181
|
+
return `${token.slice(0, 12)}\u2026`;
|
|
182
|
+
};
|
|
178
183
|
formatDate = (dateStr) => {
|
|
179
184
|
try {
|
|
180
185
|
return new Date(dateStr).toLocaleDateString();
|
|
@@ -208,7 +213,6 @@ var init_KeysTable = __esm({
|
|
|
208
213
|
onDeleteKey
|
|
209
214
|
}) => {
|
|
210
215
|
const [generateModalOpen, setGenerateModalOpen] = useState(false);
|
|
211
|
-
const [showKeyValue, setShowKeyValue] = useState(null);
|
|
212
216
|
const [newKeyValue, setNewKeyValue] = useState(null);
|
|
213
217
|
const [formData, setFormData] = useState(emptyForm());
|
|
214
218
|
const [submitting, setSubmitting] = useState(false);
|
|
@@ -278,32 +282,26 @@ var init_KeysTable = __esm({
|
|
|
278
282
|
onClick: () => setGenerateModalOpen(true)
|
|
279
283
|
},
|
|
280
284
|
"Generate New Key"
|
|
281
|
-
)), /* @__PURE__ */ React2.createElement(TableContainer, null, /* @__PURE__ */ React2.createElement(Table, null, /* @__PURE__ */ React2.createElement(TableHead, null, /* @__PURE__ */ React2.createElement(TableRow, null, /* @__PURE__ */ React2.createElement(TableCell, null, "Alias"), /* @__PURE__ */ React2.createElement(TableCell, null, "Key"), /* @__PURE__ */ React2.createElement(TableCell, null, "Created"), /* @__PURE__ */ React2.createElement(TableCell, null, "Spend"), /* @__PURE__ */ React2.createElement(TableCell, null, "Budget"), /* @__PURE__ */ React2.createElement(TableCell, null, "TPM Limit"), /* @__PURE__ */ React2.createElement(TableCell, null, "Models"), /* @__PURE__ */ React2.createElement(TableCell, { align: "right" }, "Actions"))), /* @__PURE__ */ React2.createElement(TableBody, null, loading ? /* @__PURE__ */ React2.createElement(TableRow, null, /* @__PURE__ */ React2.createElement(TableCell, { colSpan: 8, align: "center" }, /* @__PURE__ */ React2.createElement(CircularProgress, { size: 24 }))) : keys.length === 0 ? /* @__PURE__ */ React2.createElement(TableRow, null, /* @__PURE__ */ React2.createElement(TableCell, { colSpan: 8, align: "center" }, /* @__PURE__ */ React2.createElement(Typography2, { color: "text.secondary" }, "No keys found"))) : keys.map((key) =>
|
|
282
|
-
|
|
283
|
-
{
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
{
|
|
302
|
-
size: "small",
|
|
303
|
-
onClick: () => setShowKeyValue(showKeyValue === key.key ? null : key.key)
|
|
304
|
-
},
|
|
305
|
-
showKeyValue === key.key ? /* @__PURE__ */ React2.createElement(VisibilityOff, null) : /* @__PURE__ */ React2.createElement(Visibility, null)
|
|
306
|
-
), /* @__PURE__ */ React2.createElement(IconButton, { size: "small", onClick: () => copyToClipboard(key.key) }, /* @__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(key.key) }, /* @__PURE__ */ React2.createElement(Delete, null))))))))), /* @__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(
|
|
285
|
+
)), /* @__PURE__ */ React2.createElement(TableContainer, null, /* @__PURE__ */ React2.createElement(Table, null, /* @__PURE__ */ React2.createElement(TableHead, null, /* @__PURE__ */ React2.createElement(TableRow, null, /* @__PURE__ */ React2.createElement(TableCell, null, "Alias"), /* @__PURE__ */ React2.createElement(TableCell, null, "Key ID"), /* @__PURE__ */ React2.createElement(TableCell, null, "Created"), /* @__PURE__ */ React2.createElement(TableCell, null, "Spend"), /* @__PURE__ */ React2.createElement(TableCell, null, "Budget"), /* @__PURE__ */ React2.createElement(TableCell, null, "TPM Limit"), /* @__PURE__ */ React2.createElement(TableCell, null, "Models"), /* @__PURE__ */ React2.createElement(TableCell, { align: "right" }, "Actions"))), /* @__PURE__ */ React2.createElement(TableBody, null, loading ? /* @__PURE__ */ React2.createElement(TableRow, null, /* @__PURE__ */ React2.createElement(TableCell, { colSpan: 8, align: "center" }, /* @__PURE__ */ React2.createElement(CircularProgress, { size: 24 }))) : keys.length === 0 ? /* @__PURE__ */ React2.createElement(TableRow, null, /* @__PURE__ */ React2.createElement(TableCell, { colSpan: 8, align: "center" }, /* @__PURE__ */ React2.createElement(Typography2, { color: "text.secondary" }, "No keys found"))) : keys.map((key) => {
|
|
286
|
+
const keyId = key.token ?? key.key;
|
|
287
|
+
return /* @__PURE__ */ React2.createElement(TableRow, { key: keyId }, /* @__PURE__ */ React2.createElement(TableCell, null, key.key_alias || "-"), /* @__PURE__ */ React2.createElement(TableCell, null, /* @__PURE__ */ React2.createElement(Box2, { display: "flex", alignItems: "center", gap: 0.5 }, /* @__PURE__ */ React2.createElement(
|
|
288
|
+
Typography2,
|
|
289
|
+
{
|
|
290
|
+
variant: "body2",
|
|
291
|
+
component: "code",
|
|
292
|
+
color: "text.secondary",
|
|
293
|
+
title: keyId,
|
|
294
|
+
sx: {
|
|
295
|
+
fontFamily: "monospace",
|
|
296
|
+
backgroundColor: "background.default",
|
|
297
|
+
px: 1,
|
|
298
|
+
py: 0.5,
|
|
299
|
+
borderRadius: 1
|
|
300
|
+
}
|
|
301
|
+
},
|
|
302
|
+
shortKeyId(keyId)
|
|
303
|
+
), /* @__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(key.key) }, /* @__PURE__ */ React2.createElement(Delete, null))));
|
|
304
|
+
}))))), /* @__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(
|
|
307
305
|
Box2,
|
|
308
306
|
{
|
|
309
307
|
display: "flex",
|
|
@@ -955,10 +953,106 @@ init_DashboardHeader();
|
|
|
955
953
|
init_KeysTable();
|
|
956
954
|
init_UsageStats();
|
|
957
955
|
init_TeamUsage();
|
|
956
|
+
|
|
957
|
+
// src/components/LiteLLMHomeWidget.tsx
|
|
958
|
+
init_api();
|
|
959
|
+
import React7, { useState as useState5, useEffect as useEffect2 } from "react";
|
|
960
|
+
import {
|
|
961
|
+
Paper as Paper6,
|
|
962
|
+
Box as Box6,
|
|
963
|
+
Typography as Typography6,
|
|
964
|
+
FormControl as FormControl2,
|
|
965
|
+
Select as Select2,
|
|
966
|
+
MenuItem as MenuItem3,
|
|
967
|
+
Grid as Grid3,
|
|
968
|
+
CircularProgress as CircularProgress5,
|
|
969
|
+
Alert as Alert2
|
|
970
|
+
} from "@mui/material";
|
|
971
|
+
import { AreaChart as AreaChart3, Area as Area3, ResponsiveContainer as ResponsiveContainer3 } from "recharts";
|
|
972
|
+
import { useApi as useApi2 } from "@backstage/core-plugin-api";
|
|
973
|
+
var fmtUsd2 = (n) => `$${(n ?? 0).toFixed(n < 1 ? 4 : 2)}`;
|
|
974
|
+
var fmtInt2 = (n) => (n ?? 0).toLocaleString();
|
|
975
|
+
function presetToDateRange(preset) {
|
|
976
|
+
const end = /* @__PURE__ */ new Date();
|
|
977
|
+
const start = /* @__PURE__ */ new Date();
|
|
978
|
+
if (preset === "today") {
|
|
979
|
+
start.setHours(0, 0, 0, 0);
|
|
980
|
+
} else if (preset === "7d") {
|
|
981
|
+
start.setDate(start.getDate() - 7);
|
|
982
|
+
} else {
|
|
983
|
+
start.setDate(start.getDate() - 30);
|
|
984
|
+
}
|
|
985
|
+
return { start, end };
|
|
986
|
+
}
|
|
987
|
+
var Kpi = ({ label, value }) => /* @__PURE__ */ React7.createElement(Box6, null, /* @__PURE__ */ React7.createElement(Typography6, { variant: "caption", color: "text.secondary", display: "block" }, label), /* @__PURE__ */ React7.createElement(Typography6, { variant: "subtitle1", fontWeight: 600 }, value));
|
|
988
|
+
var LiteLLMHomeWidget = ({
|
|
989
|
+
defaultPeriod = "7d",
|
|
990
|
+
title = "LiteLLM Usage"
|
|
991
|
+
}) => {
|
|
992
|
+
const api = useApi2(liteLlmApiRef);
|
|
993
|
+
const [period, setPeriod] = useState5(defaultPeriod);
|
|
994
|
+
const [loading, setLoading] = useState5(true);
|
|
995
|
+
const [error, setError] = useState5(null);
|
|
996
|
+
const [usage, setUsage] = useState5(null);
|
|
997
|
+
const [keys, setKeys] = useState5([]);
|
|
998
|
+
useEffect2(() => {
|
|
999
|
+
let cancelled = false;
|
|
1000
|
+
setLoading(true);
|
|
1001
|
+
setError(null);
|
|
1002
|
+
const { start, end } = presetToDateRange(period);
|
|
1003
|
+
const startDate = start.toISOString().split("T")[0];
|
|
1004
|
+
const endDate = end.toISOString().split("T")[0];
|
|
1005
|
+
Promise.all([api.getUsage(startDate, endDate), api.listKeys()]).then(([usageData, keysData]) => {
|
|
1006
|
+
if (!cancelled) {
|
|
1007
|
+
setUsage(usageData);
|
|
1008
|
+
setKeys(keysData);
|
|
1009
|
+
setLoading(false);
|
|
1010
|
+
}
|
|
1011
|
+
}).catch((err) => {
|
|
1012
|
+
if (!cancelled) {
|
|
1013
|
+
setError(err.message ?? "Failed to load usage data");
|
|
1014
|
+
setLoading(false);
|
|
1015
|
+
}
|
|
1016
|
+
});
|
|
1017
|
+
return () => {
|
|
1018
|
+
cancelled = true;
|
|
1019
|
+
};
|
|
1020
|
+
}, [api, period]);
|
|
1021
|
+
const dailyData = (usage?.daily_usage ?? []).map((d) => ({
|
|
1022
|
+
date: d.date,
|
|
1023
|
+
spend: d.spend
|
|
1024
|
+
}));
|
|
1025
|
+
const hasSparkline = dailyData.length > 0;
|
|
1026
|
+
return /* @__PURE__ */ React7.createElement(Paper6, { sx: { p: 2 } }, /* @__PURE__ */ React7.createElement(Box6, { display: "flex", justifyContent: "space-between", alignItems: "center", mb: 1.5 }, /* @__PURE__ */ React7.createElement(Typography6, { variant: "h6" }, title), /* @__PURE__ */ React7.createElement(FormControl2, { size: "small", sx: { minWidth: 90 } }, /* @__PURE__ */ React7.createElement(
|
|
1027
|
+
Select2,
|
|
1028
|
+
{
|
|
1029
|
+
value: period,
|
|
1030
|
+
onChange: (e) => setPeriod(e.target.value),
|
|
1031
|
+
displayEmpty: true
|
|
1032
|
+
},
|
|
1033
|
+
/* @__PURE__ */ React7.createElement(MenuItem3, { value: "today" }, "Today"),
|
|
1034
|
+
/* @__PURE__ */ React7.createElement(MenuItem3, { value: "7d" }, "7d"),
|
|
1035
|
+
/* @__PURE__ */ React7.createElement(MenuItem3, { value: "30d" }, "30d")
|
|
1036
|
+
))), loading && /* @__PURE__ */ React7.createElement(Box6, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: 120 }, /* @__PURE__ */ React7.createElement(CircularProgress5, { 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(
|
|
1037
|
+
Area3,
|
|
1038
|
+
{
|
|
1039
|
+
type: "monotone",
|
|
1040
|
+
dataKey: "spend",
|
|
1041
|
+
stroke: "#8884d8",
|
|
1042
|
+
fill: "#8884d8",
|
|
1043
|
+
fillOpacity: 0.3,
|
|
1044
|
+
dot: false,
|
|
1045
|
+
isAnimationActive: false
|
|
1046
|
+
}
|
|
1047
|
+
))))));
|
|
1048
|
+
};
|
|
1049
|
+
|
|
1050
|
+
// src/index.ts
|
|
958
1051
|
init_api();
|
|
959
1052
|
export {
|
|
960
1053
|
DashboardHeader,
|
|
961
1054
|
KeysTable,
|
|
1055
|
+
LiteLLMHomeWidget,
|
|
962
1056
|
LiteLLMPage,
|
|
963
1057
|
LiteLlmApi,
|
|
964
1058
|
TeamUsage,
|