@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
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export interface LiteLLMHomeWidgetProps {
|
|
3
|
+
/** Default period when the widget mounts. Defaults to '7d'. */
|
|
4
|
+
defaultPeriod?: 'today' | '7d' | '30d';
|
|
5
|
+
/** Optional title override. Defaults to 'LiteLLM Usage'. */
|
|
6
|
+
title?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare const LiteLLMHomeWidget: React.FC<LiteLLMHomeWidgetProps>;
|
package/dist/index.cjs.js
CHANGED
|
@@ -165,7 +165,7 @@ var init_DashboardHeader = __esm({
|
|
|
165
165
|
});
|
|
166
166
|
|
|
167
167
|
// src/components/KeysTable.tsx
|
|
168
|
-
var import_react2, import_material2, import_icons_material2, maskKey, formatDate, emptyForm, keyToEditForm, KeysTable;
|
|
168
|
+
var import_react2, import_material2, import_icons_material2, maskKey, shortKeyId, formatDate, emptyForm, keyToEditForm, KeysTable;
|
|
169
169
|
var init_KeysTable = __esm({
|
|
170
170
|
"src/components/KeysTable.tsx"() {
|
|
171
171
|
"use strict";
|
|
@@ -176,6 +176,11 @@ var init_KeysTable = __esm({
|
|
|
176
176
|
if (key.length <= 8) return "***";
|
|
177
177
|
return `${key.slice(0, 4)}...${key.slice(-4)}`;
|
|
178
178
|
};
|
|
179
|
+
shortKeyId = (token) => {
|
|
180
|
+
if (!token) return "-";
|
|
181
|
+
if (token.length <= 16) return token;
|
|
182
|
+
return `${token.slice(0, 12)}\u2026`;
|
|
183
|
+
};
|
|
179
184
|
formatDate = (dateStr) => {
|
|
180
185
|
try {
|
|
181
186
|
return new Date(dateStr).toLocaleDateString();
|
|
@@ -209,7 +214,6 @@ var init_KeysTable = __esm({
|
|
|
209
214
|
onDeleteKey
|
|
210
215
|
}) => {
|
|
211
216
|
const [generateModalOpen, setGenerateModalOpen] = (0, import_react2.useState)(false);
|
|
212
|
-
const [showKeyValue, setShowKeyValue] = (0, import_react2.useState)(null);
|
|
213
217
|
const [newKeyValue, setNewKeyValue] = (0, import_react2.useState)(null);
|
|
214
218
|
const [formData, setFormData] = (0, import_react2.useState)(emptyForm());
|
|
215
219
|
const [submitting, setSubmitting] = (0, import_react2.useState)(false);
|
|
@@ -279,32 +283,26 @@ var init_KeysTable = __esm({
|
|
|
279
283
|
onClick: () => setGenerateModalOpen(true)
|
|
280
284
|
},
|
|
281
285
|
"Generate New Key"
|
|
282
|
-
)), /* @__PURE__ */ import_react2.default.createElement(import_material2.TableContainer, null, /* @__PURE__ */ import_react2.default.createElement(import_material2.Table, null, /* @__PURE__ */ import_react2.default.createElement(import_material2.TableHead, null, /* @__PURE__ */ import_react2.default.createElement(import_material2.TableRow, null, /* @__PURE__ */ import_react2.default.createElement(import_material2.TableCell, null, "Alias"), /* @__PURE__ */ import_react2.default.createElement(import_material2.TableCell, null, "Key"), /* @__PURE__ */ import_react2.default.createElement(import_material2.TableCell, null, "Created"), /* @__PURE__ */ import_react2.default.createElement(import_material2.TableCell, null, "Spend"), /* @__PURE__ */ import_react2.default.createElement(import_material2.TableCell, null, "Budget"), /* @__PURE__ */ import_react2.default.createElement(import_material2.TableCell, null, "TPM Limit"), /* @__PURE__ */ import_react2.default.createElement(import_material2.TableCell, null, "Models"), /* @__PURE__ */ import_react2.default.createElement(import_material2.TableCell, { align: "right" }, "Actions"))), /* @__PURE__ */ import_react2.default.createElement(import_material2.TableBody, null, loading ? /* @__PURE__ */ import_react2.default.createElement(import_material2.TableRow, null, /* @__PURE__ */ import_react2.default.createElement(import_material2.TableCell, { colSpan: 8, align: "center" }, /* @__PURE__ */ import_react2.default.createElement(import_material2.CircularProgress, { size: 24 }))) : keys.length === 0 ? /* @__PURE__ */ import_react2.default.createElement(import_material2.TableRow, null, /* @__PURE__ */ import_react2.default.createElement(import_material2.TableCell, { colSpan: 8, align: "center" }, /* @__PURE__ */ import_react2.default.createElement(import_material2.Typography, { color: "text.secondary" }, "No keys found"))) : keys.map((key) =>
|
|
283
|
-
|
|
284
|
-
{
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
{
|
|
303
|
-
size: "small",
|
|
304
|
-
onClick: () => setShowKeyValue(showKeyValue === key.key ? null : key.key)
|
|
305
|
-
},
|
|
306
|
-
showKeyValue === key.key ? /* @__PURE__ */ import_react2.default.createElement(import_icons_material2.VisibilityOff, null) : /* @__PURE__ */ import_react2.default.createElement(import_icons_material2.Visibility, null)
|
|
307
|
-
), /* @__PURE__ */ import_react2.default.createElement(import_material2.IconButton, { size: "small", onClick: () => copyToClipboard(key.key) }, /* @__PURE__ */ import_react2.default.createElement(import_icons_material2.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_material2.Edit, { fontSize: "small" })), /* @__PURE__ */ import_react2.default.createElement(import_material2.IconButton, { color: "error", onClick: () => onDeleteKey(key.key) }, /* @__PURE__ */ import_react2.default.createElement(import_icons_material2.Delete, null))))))))), /* @__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(
|
|
286
|
+
)), /* @__PURE__ */ import_react2.default.createElement(import_material2.TableContainer, null, /* @__PURE__ */ import_react2.default.createElement(import_material2.Table, null, /* @__PURE__ */ import_react2.default.createElement(import_material2.TableHead, null, /* @__PURE__ */ import_react2.default.createElement(import_material2.TableRow, null, /* @__PURE__ */ import_react2.default.createElement(import_material2.TableCell, null, "Alias"), /* @__PURE__ */ import_react2.default.createElement(import_material2.TableCell, null, "Key ID"), /* @__PURE__ */ import_react2.default.createElement(import_material2.TableCell, null, "Created"), /* @__PURE__ */ import_react2.default.createElement(import_material2.TableCell, null, "Spend"), /* @__PURE__ */ import_react2.default.createElement(import_material2.TableCell, null, "Budget"), /* @__PURE__ */ import_react2.default.createElement(import_material2.TableCell, null, "TPM Limit"), /* @__PURE__ */ import_react2.default.createElement(import_material2.TableCell, null, "Models"), /* @__PURE__ */ import_react2.default.createElement(import_material2.TableCell, { align: "right" }, "Actions"))), /* @__PURE__ */ import_react2.default.createElement(import_material2.TableBody, null, loading ? /* @__PURE__ */ import_react2.default.createElement(import_material2.TableRow, null, /* @__PURE__ */ import_react2.default.createElement(import_material2.TableCell, { colSpan: 8, align: "center" }, /* @__PURE__ */ import_react2.default.createElement(import_material2.CircularProgress, { size: 24 }))) : keys.length === 0 ? /* @__PURE__ */ import_react2.default.createElement(import_material2.TableRow, null, /* @__PURE__ */ import_react2.default.createElement(import_material2.TableCell, { colSpan: 8, align: "center" }, /* @__PURE__ */ import_react2.default.createElement(import_material2.Typography, { color: "text.secondary" }, "No keys found"))) : keys.map((key) => {
|
|
287
|
+
const keyId = key.token ?? key.key;
|
|
288
|
+
return /* @__PURE__ */ import_react2.default.createElement(import_material2.TableRow, { key: keyId }, /* @__PURE__ */ import_react2.default.createElement(import_material2.TableCell, null, key.key_alias || "-"), /* @__PURE__ */ import_react2.default.createElement(import_material2.TableCell, null, /* @__PURE__ */ import_react2.default.createElement(import_material2.Box, { display: "flex", alignItems: "center", gap: 0.5 }, /* @__PURE__ */ import_react2.default.createElement(
|
|
289
|
+
import_material2.Typography,
|
|
290
|
+
{
|
|
291
|
+
variant: "body2",
|
|
292
|
+
component: "code",
|
|
293
|
+
color: "text.secondary",
|
|
294
|
+
title: keyId,
|
|
295
|
+
sx: {
|
|
296
|
+
fontFamily: "monospace",
|
|
297
|
+
backgroundColor: "background.default",
|
|
298
|
+
px: 1,
|
|
299
|
+
py: 0.5,
|
|
300
|
+
borderRadius: 1
|
|
301
|
+
}
|
|
302
|
+
},
|
|
303
|
+
shortKeyId(keyId)
|
|
304
|
+
), /* @__PURE__ */ import_react2.default.createElement(import_material2.IconButton, { size: "small", onClick: () => copyToClipboard(keyId), title: "Copy Key ID" }, /* @__PURE__ */ import_react2.default.createElement(import_icons_material2.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_material2.Edit, { fontSize: "small" })), /* @__PURE__ */ import_react2.default.createElement(import_material2.IconButton, { color: "error", onClick: () => onDeleteKey(key.key) }, /* @__PURE__ */ import_react2.default.createElement(import_icons_material2.Delete, null))));
|
|
305
|
+
}))))), /* @__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(
|
|
308
306
|
import_material2.Box,
|
|
309
307
|
{
|
|
310
308
|
display: "flex",
|
|
@@ -868,6 +866,7 @@ var index_exports = {};
|
|
|
868
866
|
__export(index_exports, {
|
|
869
867
|
DashboardHeader: () => DashboardHeader,
|
|
870
868
|
KeysTable: () => KeysTable,
|
|
869
|
+
LiteLLMHomeWidget: () => LiteLLMHomeWidget,
|
|
871
870
|
LiteLLMPage: () => LiteLLMPage,
|
|
872
871
|
LiteLlmApi: () => LiteLlmApi,
|
|
873
872
|
TeamUsage: () => TeamUsage,
|
|
@@ -911,5 +910,90 @@ init_DashboardHeader();
|
|
|
911
910
|
init_KeysTable();
|
|
912
911
|
init_UsageStats();
|
|
913
912
|
init_TeamUsage();
|
|
913
|
+
|
|
914
|
+
// src/components/LiteLLMHomeWidget.tsx
|
|
915
|
+
var import_react7 = __toESM(require("react"));
|
|
916
|
+
var import_material6 = require("@mui/material");
|
|
917
|
+
var import_recharts3 = require("recharts");
|
|
918
|
+
var import_core_plugin_api3 = require("@backstage/core-plugin-api");
|
|
919
|
+
init_api();
|
|
920
|
+
var fmtUsd2 = (n) => `$${(n ?? 0).toFixed(n < 1 ? 4 : 2)}`;
|
|
921
|
+
var fmtInt2 = (n) => (n ?? 0).toLocaleString();
|
|
922
|
+
function presetToDateRange(preset) {
|
|
923
|
+
const end = /* @__PURE__ */ new Date();
|
|
924
|
+
const start = /* @__PURE__ */ new Date();
|
|
925
|
+
if (preset === "today") {
|
|
926
|
+
start.setHours(0, 0, 0, 0);
|
|
927
|
+
} else if (preset === "7d") {
|
|
928
|
+
start.setDate(start.getDate() - 7);
|
|
929
|
+
} else {
|
|
930
|
+
start.setDate(start.getDate() - 30);
|
|
931
|
+
}
|
|
932
|
+
return { start, end };
|
|
933
|
+
}
|
|
934
|
+
var Kpi = ({ label, value }) => /* @__PURE__ */ import_react7.default.createElement(import_material6.Box, null, /* @__PURE__ */ import_react7.default.createElement(import_material6.Typography, { variant: "caption", color: "text.secondary", display: "block" }, label), /* @__PURE__ */ import_react7.default.createElement(import_material6.Typography, { variant: "subtitle1", fontWeight: 600 }, value));
|
|
935
|
+
var LiteLLMHomeWidget = ({
|
|
936
|
+
defaultPeriod = "7d",
|
|
937
|
+
title = "LiteLLM Usage"
|
|
938
|
+
}) => {
|
|
939
|
+
const api = (0, import_core_plugin_api3.useApi)(liteLlmApiRef);
|
|
940
|
+
const [period, setPeriod] = (0, import_react7.useState)(defaultPeriod);
|
|
941
|
+
const [loading, setLoading] = (0, import_react7.useState)(true);
|
|
942
|
+
const [error, setError] = (0, import_react7.useState)(null);
|
|
943
|
+
const [usage, setUsage] = (0, import_react7.useState)(null);
|
|
944
|
+
const [keys, setKeys] = (0, import_react7.useState)([]);
|
|
945
|
+
(0, import_react7.useEffect)(() => {
|
|
946
|
+
let cancelled = false;
|
|
947
|
+
setLoading(true);
|
|
948
|
+
setError(null);
|
|
949
|
+
const { start, end } = presetToDateRange(period);
|
|
950
|
+
const startDate = start.toISOString().split("T")[0];
|
|
951
|
+
const endDate = end.toISOString().split("T")[0];
|
|
952
|
+
Promise.all([api.getUsage(startDate, endDate), api.listKeys()]).then(([usageData, keysData]) => {
|
|
953
|
+
if (!cancelled) {
|
|
954
|
+
setUsage(usageData);
|
|
955
|
+
setKeys(keysData);
|
|
956
|
+
setLoading(false);
|
|
957
|
+
}
|
|
958
|
+
}).catch((err) => {
|
|
959
|
+
if (!cancelled) {
|
|
960
|
+
setError(err.message ?? "Failed to load usage data");
|
|
961
|
+
setLoading(false);
|
|
962
|
+
}
|
|
963
|
+
});
|
|
964
|
+
return () => {
|
|
965
|
+
cancelled = true;
|
|
966
|
+
};
|
|
967
|
+
}, [api, period]);
|
|
968
|
+
const dailyData = (usage?.daily_usage ?? []).map((d) => ({
|
|
969
|
+
date: d.date,
|
|
970
|
+
spend: d.spend
|
|
971
|
+
}));
|
|
972
|
+
const hasSparkline = dailyData.length > 0;
|
|
973
|
+
return /* @__PURE__ */ import_react7.default.createElement(import_material6.Paper, { sx: { p: 2 } }, /* @__PURE__ */ import_react7.default.createElement(import_material6.Box, { display: "flex", justifyContent: "space-between", alignItems: "center", mb: 1.5 }, /* @__PURE__ */ import_react7.default.createElement(import_material6.Typography, { variant: "h6" }, title), /* @__PURE__ */ import_react7.default.createElement(import_material6.FormControl, { size: "small", sx: { minWidth: 90 } }, /* @__PURE__ */ import_react7.default.createElement(
|
|
974
|
+
import_material6.Select,
|
|
975
|
+
{
|
|
976
|
+
value: period,
|
|
977
|
+
onChange: (e) => setPeriod(e.target.value),
|
|
978
|
+
displayEmpty: true
|
|
979
|
+
},
|
|
980
|
+
/* @__PURE__ */ import_react7.default.createElement(import_material6.MenuItem, { value: "today" }, "Today"),
|
|
981
|
+
/* @__PURE__ */ import_react7.default.createElement(import_material6.MenuItem, { value: "7d" }, "7d"),
|
|
982
|
+
/* @__PURE__ */ import_react7.default.createElement(import_material6.MenuItem, { value: "30d" }, "30d")
|
|
983
|
+
))), loading && /* @__PURE__ */ import_react7.default.createElement(import_material6.Box, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: 120 }, /* @__PURE__ */ import_react7.default.createElement(import_material6.CircularProgress, { size: 32 })), !loading && error && /* @__PURE__ */ import_react7.default.createElement(import_material6.Alert, { severity: "error", sx: { mt: 1 } }, error), !loading && !error && /* @__PURE__ */ import_react7.default.createElement(import_react7.default.Fragment, null, /* @__PURE__ */ import_react7.default.createElement(import_material6.Grid, { container: true, spacing: 2, sx: { mb: hasSparkline ? 1.5 : 0 } }, /* @__PURE__ */ import_react7.default.createElement(import_material6.Grid, { item: true, xs: 6 }, /* @__PURE__ */ import_react7.default.createElement(Kpi, { label: "USD Spent", value: fmtUsd2(usage?.total_spend ?? 0) })), /* @__PURE__ */ import_react7.default.createElement(import_material6.Grid, { item: true, xs: 6 }, /* @__PURE__ */ import_react7.default.createElement(Kpi, { label: "Tokens In", value: fmtInt2(usage?.prompt_tokens ?? 0) })), /* @__PURE__ */ import_react7.default.createElement(import_material6.Grid, { item: true, xs: 6 }, /* @__PURE__ */ import_react7.default.createElement(Kpi, { label: "Tokens Out", value: fmtInt2(usage?.completion_tokens ?? 0) })), /* @__PURE__ */ import_react7.default.createElement(import_material6.Grid, { item: true, xs: 6 }, /* @__PURE__ */ import_react7.default.createElement(Kpi, { label: "Keys", value: fmtInt2(keys.length) }))), hasSparkline && /* @__PURE__ */ import_react7.default.createElement(import_material6.Box, { height: 120 }, /* @__PURE__ */ import_react7.default.createElement(import_recharts3.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react7.default.createElement(import_recharts3.AreaChart, { data: dailyData, margin: { top: 4, right: 0, bottom: 0, left: 0 } }, /* @__PURE__ */ import_react7.default.createElement(
|
|
984
|
+
import_recharts3.Area,
|
|
985
|
+
{
|
|
986
|
+
type: "monotone",
|
|
987
|
+
dataKey: "spend",
|
|
988
|
+
stroke: "#8884d8",
|
|
989
|
+
fill: "#8884d8",
|
|
990
|
+
fillOpacity: 0.3,
|
|
991
|
+
dot: false,
|
|
992
|
+
isAnimationActive: false
|
|
993
|
+
}
|
|
994
|
+
))))));
|
|
995
|
+
};
|
|
996
|
+
|
|
997
|
+
// src/index.ts
|
|
914
998
|
init_api();
|
|
915
999
|
//# sourceMappingURL=index.cjs.js.map
|