@acarmisc/backstage-plugin-litellm 0.7.0 → 0.8.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/api.test.d.ts +1 -0
- package/dist/components/TeamUsage.d.ts +1 -2
- package/dist/format.d.ts +2 -0
- package/dist/format.test.d.ts +1 -0
- package/dist/index.cjs.js +55 -11
- package/dist/index.cjs.js.map +4 -4
- package/dist/index.esm.js +58 -12
- package/dist/index.esm.js.map +4 -4
- package/package.json +3 -1
package/dist/index.esm.js
CHANGED
|
@@ -708,6 +708,16 @@ var init_KeysTable = __esm({
|
|
|
708
708
|
}
|
|
709
709
|
});
|
|
710
710
|
|
|
711
|
+
// src/format.ts
|
|
712
|
+
var fmtUsd, fmtInt;
|
|
713
|
+
var init_format = __esm({
|
|
714
|
+
"src/format.ts"() {
|
|
715
|
+
"use strict";
|
|
716
|
+
fmtUsd = (n) => `$${(n ?? 0).toFixed(n < 1 ? 4 : 2)}`;
|
|
717
|
+
fmtInt = (n) => (n ?? 0).toLocaleString();
|
|
718
|
+
}
|
|
719
|
+
});
|
|
720
|
+
|
|
711
721
|
// src/components/UsageStats.tsx
|
|
712
722
|
import React3, { useMemo as useMemo2, useState as useState2 } from "react";
|
|
713
723
|
import {
|
|
@@ -751,10 +761,11 @@ function modelColor(model) {
|
|
|
751
761
|
for (let i = 0; i < model.length; i++) h = (h << 5) + h + model.charCodeAt(i) >>> 0;
|
|
752
762
|
return MODEL_COLORS[h % MODEL_COLORS.length];
|
|
753
763
|
}
|
|
754
|
-
var TOP_N_MODELS, PERIOD_LS_KEY, MODEL_COLORS,
|
|
764
|
+
var TOP_N_MODELS, PERIOD_LS_KEY, MODEL_COLORS, fmtPct, KpiCard, ChartSkeleton, EmptyChart, UsageStats;
|
|
755
765
|
var init_UsageStats = __esm({
|
|
756
766
|
"src/components/UsageStats.tsx"() {
|
|
757
767
|
"use strict";
|
|
768
|
+
init_format();
|
|
758
769
|
TOP_N_MODELS = 6;
|
|
759
770
|
PERIOD_LS_KEY = "litellm_usage_period";
|
|
760
771
|
MODEL_COLORS = [
|
|
@@ -769,8 +780,6 @@ var init_UsageStats = __esm({
|
|
|
769
780
|
"#a4de6c",
|
|
770
781
|
"#d0ed57"
|
|
771
782
|
];
|
|
772
|
-
fmtUsd = (n) => `$${(n ?? 0).toFixed(n < 1 ? 4 : 2)}`;
|
|
773
|
-
fmtInt = (n) => (n ?? 0).toLocaleString();
|
|
774
783
|
fmtPct = (n) => `${(n * 100).toFixed(1)}%`;
|
|
775
784
|
KpiCard = ({ label, value, hint }) => /* @__PURE__ */ React3.createElement(Paper3, { variant: "outlined", sx: { p: 2, height: "100%" } }, /* @__PURE__ */ React3.createElement(Typography3, { variant: "caption", color: "text.secondary" }, label), /* @__PURE__ */ React3.createElement(Typography3, { variant: "h5", sx: { mt: 0.5 } }, value), hint ? /* @__PURE__ */ React3.createElement(Typography3, { variant: "caption", color: "text.secondary" }, hint) : null);
|
|
776
785
|
ChartSkeleton = ({ height = 260 }) => /* @__PURE__ */ React3.createElement(Skeleton, { variant: "rectangular", height, sx: { borderRadius: 1 } });
|
|
@@ -960,6 +969,8 @@ import {
|
|
|
960
969
|
Typography as Typography4,
|
|
961
970
|
LinearProgress as LinearProgress4,
|
|
962
971
|
Chip as Chip4,
|
|
972
|
+
Divider,
|
|
973
|
+
Stack,
|
|
963
974
|
Table as Table3,
|
|
964
975
|
TableBody as TableBody3,
|
|
965
976
|
TableCell as TableCell3,
|
|
@@ -970,7 +981,7 @@ import {
|
|
|
970
981
|
IconButton as IconButton2,
|
|
971
982
|
CircularProgress as CircularProgress2
|
|
972
983
|
} from "@mui/material";
|
|
973
|
-
import { ExpandMore, ExpandLess, Group } from "@mui/icons-material";
|
|
984
|
+
import { ExpandMore, ExpandLess, Group, Speed, Memory } from "@mui/icons-material";
|
|
974
985
|
import {
|
|
975
986
|
AreaChart as AreaChart2,
|
|
976
987
|
Area as Area2,
|
|
@@ -980,10 +991,11 @@ import {
|
|
|
980
991
|
Tooltip as Tooltip2,
|
|
981
992
|
ResponsiveContainer as ResponsiveContainer2
|
|
982
993
|
} from "recharts";
|
|
983
|
-
var TeamCard, TeamUsage;
|
|
994
|
+
var Stat, TeamCard, TeamUsage;
|
|
984
995
|
var init_TeamUsage = __esm({
|
|
985
996
|
"src/components/TeamUsage.tsx"() {
|
|
986
997
|
"use strict";
|
|
998
|
+
Stat = ({ label, value }) => /* @__PURE__ */ React4.createElement(Box4, null, /* @__PURE__ */ React4.createElement(Typography4, { variant: "caption", color: "text.secondary", display: "block" }, label), /* @__PURE__ */ React4.createElement(Typography4, { variant: "body2", fontWeight: 600, sx: { fontFamily: "monospace" } }, value));
|
|
987
999
|
TeamCard = ({ team, usage, usageLoading }) => {
|
|
988
1000
|
const [expanded, setExpanded] = useState3(false);
|
|
989
1001
|
const budget = team.max_budget ?? 0;
|
|
@@ -992,7 +1004,43 @@ var init_TeamUsage = __esm({
|
|
|
992
1004
|
const isOver = budget > 0 && spend >= budget;
|
|
993
1005
|
const isNear = budget > 0 && spend >= budget * 0.8 && !isOver;
|
|
994
1006
|
const dailyData = usage?.daily_usage?.map((d) => ({ date: d.date, spend: d.spend })) ?? [];
|
|
995
|
-
return /* @__PURE__ */ React4.createElement(Paper4, { variant: "outlined", sx: { p: 2, mb: 2 } }, /* @__PURE__ */ React4.createElement(Box4, { display: "flex", alignItems: "center", gap: 1 }, /* @__PURE__ */ React4.createElement(Group, { color: "action" }), /* @__PURE__ */ React4.createElement(Box4, { flexGrow: 1 }, /* @__PURE__ */ React4.createElement(Typography4, { variant: "subtitle1", fontWeight: 600 }, team.team_alias
|
|
1007
|
+
return /* @__PURE__ */ React4.createElement(Paper4, { variant: "outlined", sx: { p: 2, mb: 2 } }, /* @__PURE__ */ React4.createElement(Box4, { display: "flex", alignItems: "center", gap: 1 }, /* @__PURE__ */ React4.createElement(Group, { color: "action" }), /* @__PURE__ */ React4.createElement(Box4, { flexGrow: 1 }, /* @__PURE__ */ React4.createElement(Typography4, { variant: "subtitle1", fontWeight: 600 }, team.team_alias || "Untitled team"), /* @__PURE__ */ React4.createElement(Typography4, { variant: "caption", color: "text.secondary", sx: { fontFamily: "monospace" } }, team.team_id)), /* @__PURE__ */ React4.createElement(IconButton2, { size: "small", onClick: () => setExpanded((e) => !e), "aria-label": "toggle details" }, expanded ? /* @__PURE__ */ React4.createElement(ExpandLess, null) : /* @__PURE__ */ React4.createElement(ExpandMore, null))), /* @__PURE__ */ React4.createElement(Stack, { direction: "row", spacing: 3, mt: 1.5, flexWrap: "wrap", useFlexGap: true, gap: 1.5 }, /* @__PURE__ */ React4.createElement(
|
|
1008
|
+
Stat,
|
|
1009
|
+
{
|
|
1010
|
+
label: "Members",
|
|
1011
|
+
value: team.members_with_roles?.length ? String(team.members_with_roles.length) : "\u2014"
|
|
1012
|
+
}
|
|
1013
|
+
), /* @__PURE__ */ React4.createElement(
|
|
1014
|
+
Stat,
|
|
1015
|
+
{
|
|
1016
|
+
label: "Models",
|
|
1017
|
+
value: team.models?.length ? String(team.models.length) : "All"
|
|
1018
|
+
}
|
|
1019
|
+
), /* @__PURE__ */ React4.createElement(
|
|
1020
|
+
Stat,
|
|
1021
|
+
{
|
|
1022
|
+
label: "Budget",
|
|
1023
|
+
value: budget > 0 ? `$${budget.toFixed(2)}` : "Unlimited"
|
|
1024
|
+
}
|
|
1025
|
+
), /* @__PURE__ */ React4.createElement(
|
|
1026
|
+
Stat,
|
|
1027
|
+
{
|
|
1028
|
+
label: "Spend",
|
|
1029
|
+
value: `$${spend.toFixed(2)}`
|
|
1030
|
+
}
|
|
1031
|
+
), /* @__PURE__ */ React4.createElement(
|
|
1032
|
+
Stat,
|
|
1033
|
+
{
|
|
1034
|
+
label: "TPM",
|
|
1035
|
+
value: team.tpm_limit != null && team.tpm_limit > 0 ? String(team.tpm_limit) : "\u2014"
|
|
1036
|
+
}
|
|
1037
|
+
), /* @__PURE__ */ React4.createElement(
|
|
1038
|
+
Stat,
|
|
1039
|
+
{
|
|
1040
|
+
label: "RPM",
|
|
1041
|
+
value: team.rpm_limit != null && team.rpm_limit > 0 ? String(team.rpm_limit) : "\u2014"
|
|
1042
|
+
}
|
|
1043
|
+
)), budget > 0 && /* @__PURE__ */ React4.createElement(Box4, { mt: 1.5 }, /* @__PURE__ */ React4.createElement(Box4, { display: "flex", justifyContent: "space-between", mb: 0.5 }, /* @__PURE__ */ React4.createElement(Typography4, { variant: "body2" }, "$", spend.toFixed(2), " / $", budget.toFixed(2)), isOver && /* @__PURE__ */ React4.createElement(Chip4, { label: "Over Budget", size: "small", color: "error" }), isNear && /* @__PURE__ */ React4.createElement(Chip4, { label: "Near Limit", size: "small", color: "warning" })), /* @__PURE__ */ React4.createElement(
|
|
996
1044
|
LinearProgress4,
|
|
997
1045
|
{
|
|
998
1046
|
variant: "determinate",
|
|
@@ -1000,14 +1048,14 @@ var init_TeamUsage = __esm({
|
|
|
1000
1048
|
color: isOver ? "error" : isNear ? "warning" : "primary",
|
|
1001
1049
|
sx: { height: 6, borderRadius: 1 }
|
|
1002
1050
|
}
|
|
1003
|
-
)), /* @__PURE__ */ React4.createElement(Collapse, { in: expanded }, /* @__PURE__ */ React4.createElement(Box4, { mt: 2 }, usageLoading ? /* @__PURE__ */ React4.createElement(Box4, { display: "flex", justifyContent: "center", p: 2 }, /* @__PURE__ */ React4.createElement(CircularProgress2, { size: 24 })) : dailyData.length > 0 ? /* @__PURE__ */ React4.createElement(React4.Fragment, null, /* @__PURE__ */ React4.createElement(Typography4, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Daily Spend"), /* @__PURE__ */ React4.createElement(Box4, { height: 160 }, /* @__PURE__ */ React4.createElement(ResponsiveContainer2, { width: "100%", height: "100%" }, /* @__PURE__ */ React4.createElement(AreaChart2, { data: dailyData }, /* @__PURE__ */ React4.createElement(CartesianGrid2, { strokeDasharray: "3 3" }), /* @__PURE__ */ React4.createElement(XAxis2, { dataKey: "date", tick: { fontSize: 11 } }), /* @__PURE__ */ React4.createElement(YAxis2, { tick: { fontSize: 11 }, tickFormatter: (v) => `$${v.toFixed(2)}` }), /* @__PURE__ */ React4.createElement(Tooltip2, { formatter: (v) => [`$${v.toFixed(4)}`, "Spend"] }), /* @__PURE__ */ React4.createElement(Area2, { type: "monotone", dataKey: "spend", stroke: "#8884d8", fill: "#8884d8", fillOpacity: 0.3 }))))) : null,
|
|
1051
|
+
)), /* @__PURE__ */ React4.createElement(Collapse, { in: expanded }, /* @__PURE__ */ React4.createElement(Box4, { mt: 2 }, /* @__PURE__ */ React4.createElement(Box4, { display: "flex", alignItems: "center", gap: 1, mb: 1 }, /* @__PURE__ */ React4.createElement(Speed, { fontSize: "small", color: "action" }), /* @__PURE__ */ React4.createElement(Typography4, { variant: "subtitle2", color: "text.secondary" }, "Rate limits")), /* @__PURE__ */ React4.createElement(Stack, { direction: "row", spacing: 3, flexWrap: "wrap", useFlexGap: true, gap: 1.5, mb: 2 }, /* @__PURE__ */ React4.createElement(Stat, { label: "TPM limit", value: team.tpm_limit != null && team.tpm_limit > 0 ? String(team.tpm_limit) : "Unlimited" }), /* @__PURE__ */ React4.createElement(Stat, { label: "RPM limit", value: team.rpm_limit != null && team.rpm_limit > 0 ? String(team.rpm_limit) : "Unlimited" }), /* @__PURE__ */ React4.createElement(Stat, { label: "Max budget", value: budget > 0 ? `$${budget.toFixed(2)}` : "Unlimited" })), /* @__PURE__ */ React4.createElement(Divider, { sx: { mb: 2 } }), /* @__PURE__ */ React4.createElement(Typography4, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Models"), team.models?.length ? /* @__PURE__ */ React4.createElement(Box4, { display: "flex", gap: 0.5, flexWrap: "wrap", mb: 2 }, team.models.map((m) => /* @__PURE__ */ React4.createElement(Chip4, { key: m, label: m, size: "small", variant: "outlined" }))) : /* @__PURE__ */ React4.createElement(Typography4, { variant: "body2", color: "text.secondary", mb: 2 }, "All models allowed"), /* @__PURE__ */ React4.createElement(Divider, { sx: { mb: 2 } }), usageLoading ? /* @__PURE__ */ React4.createElement(Box4, { display: "flex", justifyContent: "center", p: 2 }, /* @__PURE__ */ React4.createElement(CircularProgress2, { size: 24 })) : dailyData.length > 0 ? /* @__PURE__ */ React4.createElement(React4.Fragment, null, /* @__PURE__ */ React4.createElement(Typography4, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Daily Spend"), /* @__PURE__ */ React4.createElement(Box4, { height: 160, mb: 2 }, /* @__PURE__ */ React4.createElement(ResponsiveContainer2, { width: "100%", height: "100%" }, /* @__PURE__ */ React4.createElement(AreaChart2, { data: dailyData }, /* @__PURE__ */ React4.createElement(CartesianGrid2, { strokeDasharray: "3 3" }), /* @__PURE__ */ React4.createElement(XAxis2, { dataKey: "date", tick: { fontSize: 11 } }), /* @__PURE__ */ React4.createElement(YAxis2, { tick: { fontSize: 11 }, tickFormatter: (v) => `$${v.toFixed(2)}` }), /* @__PURE__ */ React4.createElement(Tooltip2, { formatter: (v) => [`$${v.toFixed(4)}`, "Spend"] }), /* @__PURE__ */ React4.createElement(Area2, { type: "monotone", dataKey: "spend", stroke: "#8884d8", fill: "#8884d8", fillOpacity: 0.3 })))), /* @__PURE__ */ React4.createElement(Divider, { sx: { mb: 2 } })) : null, /* @__PURE__ */ React4.createElement(Box4, { display: "flex", alignItems: "center", gap: 1, mb: 1 }, /* @__PURE__ */ React4.createElement(Memory, { fontSize: "small", color: "action" }), /* @__PURE__ */ React4.createElement(Typography4, { variant: "subtitle2", color: "text.secondary" }, "Members")), team.members_with_roles?.length ? /* @__PURE__ */ React4.createElement(TableContainer3, null, /* @__PURE__ */ React4.createElement(Table3, { size: "small" }, /* @__PURE__ */ React4.createElement(TableHead3, null, /* @__PURE__ */ React4.createElement(TableRow3, null, /* @__PURE__ */ React4.createElement(TableCell3, null, "User"), /* @__PURE__ */ React4.createElement(TableCell3, null, "Role"))), /* @__PURE__ */ React4.createElement(TableBody3, null, team.members_with_roles.map((m) => /* @__PURE__ */ React4.createElement(TableRow3, { key: m.user_id }, /* @__PURE__ */ React4.createElement(TableCell3, { sx: { fontFamily: "monospace", fontSize: 12 } }, m.user_id), /* @__PURE__ */ React4.createElement(TableCell3, null, /* @__PURE__ */ React4.createElement(
|
|
1004
1052
|
Chip4,
|
|
1005
1053
|
{
|
|
1006
1054
|
label: m.role,
|
|
1007
1055
|
size: "small",
|
|
1008
1056
|
color: m.role === "admin" ? "primary" : "default"
|
|
1009
1057
|
}
|
|
1010
|
-
)))))))
|
|
1058
|
+
))))))) : /* @__PURE__ */ React4.createElement(Typography4, { variant: "body2", color: "text.secondary" }, "No members assigned."))));
|
|
1011
1059
|
};
|
|
1012
1060
|
TeamUsage = ({
|
|
1013
1061
|
teams,
|
|
@@ -1428,7 +1476,6 @@ var init_LiteLLMPage = __esm({
|
|
|
1428
1476
|
{
|
|
1429
1477
|
teams: teams ?? [],
|
|
1430
1478
|
loading: teamsLoading,
|
|
1431
|
-
dateRange,
|
|
1432
1479
|
getTeamUsage: (teamId) => {
|
|
1433
1480
|
if (teamUsageCache[teamId] === void 0) loadTeamUsage(teamId);
|
|
1434
1481
|
return teamUsageCache[teamId] ?? null;
|
|
@@ -1491,6 +1538,7 @@ init_TeamUsage();
|
|
|
1491
1538
|
|
|
1492
1539
|
// src/components/LiteLLMHomeWidget.tsx
|
|
1493
1540
|
init_api();
|
|
1541
|
+
init_format();
|
|
1494
1542
|
import React8, { useState as useState6, useEffect } from "react";
|
|
1495
1543
|
import {
|
|
1496
1544
|
Paper as Paper7,
|
|
@@ -1505,8 +1553,6 @@ import {
|
|
|
1505
1553
|
} from "@mui/material";
|
|
1506
1554
|
import { AreaChart as AreaChart3, Area as Area3, ResponsiveContainer as ResponsiveContainer3 } from "recharts";
|
|
1507
1555
|
import { useApi as useApi2 } from "@backstage/core-plugin-api";
|
|
1508
|
-
var fmtUsd2 = (n) => `$${(n ?? 0).toFixed(n < 1 ? 4 : 2)}`;
|
|
1509
|
-
var fmtInt2 = (n) => (n ?? 0).toLocaleString();
|
|
1510
1556
|
function presetToDateRange(preset) {
|
|
1511
1557
|
const end = /* @__PURE__ */ new Date();
|
|
1512
1558
|
const start = /* @__PURE__ */ new Date();
|
|
@@ -1568,7 +1614,7 @@ var LiteLLMHomeWidget = ({
|
|
|
1568
1614
|
/* @__PURE__ */ React8.createElement(MenuItem4, { value: "today" }, "Today"),
|
|
1569
1615
|
/* @__PURE__ */ React8.createElement(MenuItem4, { value: "7d" }, "7d"),
|
|
1570
1616
|
/* @__PURE__ */ React8.createElement(MenuItem4, { value: "30d" }, "30d")
|
|
1571
|
-
))), loading && /* @__PURE__ */ React8.createElement(Box7, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: 120 }, /* @__PURE__ */ React8.createElement(CircularProgress5, { size: 32 })), !loading && error && /* @__PURE__ */ React8.createElement(Alert3, { severity: "error", sx: { mt: 1 } }, error), !loading && !error && /* @__PURE__ */ React8.createElement(React8.Fragment, null, /* @__PURE__ */ React8.createElement(Grid2, { container: true, spacing: 2, sx: { mb: hasSparkline ? 1.5 : 0 } }, /* @__PURE__ */ React8.createElement(Grid2, { item: true, xs: 6 }, /* @__PURE__ */ React8.createElement(Kpi, { label: "USD Spent", value:
|
|
1617
|
+
))), loading && /* @__PURE__ */ React8.createElement(Box7, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: 120 }, /* @__PURE__ */ React8.createElement(CircularProgress5, { size: 32 })), !loading && error && /* @__PURE__ */ React8.createElement(Alert3, { severity: "error", sx: { mt: 1 } }, error), !loading && !error && /* @__PURE__ */ React8.createElement(React8.Fragment, null, /* @__PURE__ */ React8.createElement(Grid2, { container: true, spacing: 2, sx: { mb: hasSparkline ? 1.5 : 0 } }, /* @__PURE__ */ React8.createElement(Grid2, { item: true, xs: 6 }, /* @__PURE__ */ React8.createElement(Kpi, { label: "USD Spent", value: fmtUsd(usage?.total_spend ?? 0) })), /* @__PURE__ */ React8.createElement(Grid2, { item: true, xs: 6 }, /* @__PURE__ */ React8.createElement(Kpi, { label: "Tokens In", value: fmtInt(usage?.prompt_tokens ?? 0) })), /* @__PURE__ */ React8.createElement(Grid2, { item: true, xs: 6 }, /* @__PURE__ */ React8.createElement(Kpi, { label: "Tokens Out", value: fmtInt(usage?.completion_tokens ?? 0) })), /* @__PURE__ */ React8.createElement(Grid2, { item: true, xs: 6 }, /* @__PURE__ */ React8.createElement(Kpi, { label: "Keys", value: fmtInt(keys.length) }))), hasSparkline && /* @__PURE__ */ React8.createElement(Box7, { height: 120 }, /* @__PURE__ */ React8.createElement(ResponsiveContainer3, { width: "100%", height: "100%" }, /* @__PURE__ */ React8.createElement(AreaChart3, { data: dailyData, margin: { top: 4, right: 0, bottom: 0, left: 0 } }, /* @__PURE__ */ React8.createElement(
|
|
1572
1618
|
Area3,
|
|
1573
1619
|
{
|
|
1574
1620
|
type: "monotone",
|