@acarmisc/backstage-plugin-litellm 0.23.0 → 0.24.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/budget.d.ts +22 -0
- package/dist/components/LiteLLMBudgetGauges.d.ts +27 -0
- package/dist/components/ui.d.ts +20 -0
- package/dist/index.cjs.js +311 -1
- package/dist/index.cjs.js.map +4 -4
- package/dist/index.d.ts +2 -0
- package/dist/index.esm.js +311 -1
- package/dist/index.esm.js.map +4 -4
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -8,6 +8,8 @@ export { LiteLLMHomeWidget } from './components/LiteLLMHomeWidget';
|
|
|
8
8
|
export type { LiteLLMHomeWidgetProps } from './components/LiteLLMHomeWidget';
|
|
9
9
|
export { LiteLLMBudgetWidget } from './components/LiteLLMBudgetWidget';
|
|
10
10
|
export type { LiteLLMBudgetWidgetProps } from './components/LiteLLMBudgetWidget';
|
|
11
|
+
export { LiteLLMBudgetGauges } from './components/LiteLLMBudgetGauges';
|
|
12
|
+
export type { LiteLLMBudgetGaugesProps } from './components/LiteLLMBudgetGauges';
|
|
11
13
|
export { GenerateKeyDialog } from './components/GenerateKeyDialog';
|
|
12
14
|
export { ManageTeamDialog } from './components/ManageTeamDialog';
|
|
13
15
|
export { LiteLlmApi, liteLlmApiRef } from './api';
|
package/dist/index.esm.js
CHANGED
|
@@ -319,7 +319,7 @@ function SegmentedControl({
|
|
|
319
319
|
})
|
|
320
320
|
);
|
|
321
321
|
}
|
|
322
|
-
var MONTHS, fmtDateShort, fmtCompact, fmtUsdCompact, CHART_COLORS, SERIES, OTHER_COLOR, ChartTooltip, SeriesLegend, StatusPill, TagChip, SectionCard, ChartCard, MetricStrip, Stat, Meter, dataTableSx, quietIconButtonSx, EmptyState;
|
|
322
|
+
var MONTHS, fmtDateShort, fmtCompact, fmtUsdCompact, CHART_COLORS, SERIES, OTHER_COLOR, ChartTooltip, SeriesLegend, StatusPill, TagChip, SectionCard, ChartCard, MetricStrip, Stat, Meter, Gauge, dataTableSx, quietIconButtonSx, EmptyState;
|
|
323
323
|
var init_ui = __esm({
|
|
324
324
|
"src/components/ui.tsx"() {
|
|
325
325
|
"use strict";
|
|
@@ -683,6 +683,102 @@ var init_ui = __esm({
|
|
|
683
683
|
}
|
|
684
684
|
)
|
|
685
685
|
);
|
|
686
|
+
Gauge = ({ value, tone = "accent", size = 72, thickness = 6, label, caption }) => {
|
|
687
|
+
const clamped = Math.max(0, Math.min(100, value ?? 0));
|
|
688
|
+
const r = (size - thickness) / 2;
|
|
689
|
+
const c = 2 * Math.PI * r;
|
|
690
|
+
const dash = clamped / 100 * c;
|
|
691
|
+
return /* @__PURE__ */ React.createElement(
|
|
692
|
+
Box,
|
|
693
|
+
{
|
|
694
|
+
sx: (theme) => ({
|
|
695
|
+
position: "relative",
|
|
696
|
+
width: size,
|
|
697
|
+
height: size,
|
|
698
|
+
flexShrink: 0,
|
|
699
|
+
color: toneColor(theme, tone)
|
|
700
|
+
})
|
|
701
|
+
},
|
|
702
|
+
/* @__PURE__ */ React.createElement(
|
|
703
|
+
Box,
|
|
704
|
+
{
|
|
705
|
+
component: "svg",
|
|
706
|
+
viewBox: `0 0 ${size} ${size}`,
|
|
707
|
+
width: size,
|
|
708
|
+
height: size,
|
|
709
|
+
sx: { display: "block", transform: "rotate(-90deg)" }
|
|
710
|
+
},
|
|
711
|
+
/* @__PURE__ */ React.createElement(
|
|
712
|
+
Box,
|
|
713
|
+
{
|
|
714
|
+
component: "circle",
|
|
715
|
+
cx: size / 2,
|
|
716
|
+
cy: size / 2,
|
|
717
|
+
r,
|
|
718
|
+
fill: "none",
|
|
719
|
+
strokeWidth: thickness,
|
|
720
|
+
sx: (theme) => ({
|
|
721
|
+
stroke: alpha(
|
|
722
|
+
theme.palette.text.primary,
|
|
723
|
+
theme.palette.mode === "dark" ? 0.14 : 0.09
|
|
724
|
+
)
|
|
725
|
+
})
|
|
726
|
+
}
|
|
727
|
+
),
|
|
728
|
+
/* @__PURE__ */ React.createElement(
|
|
729
|
+
Box,
|
|
730
|
+
{
|
|
731
|
+
component: "circle",
|
|
732
|
+
cx: size / 2,
|
|
733
|
+
cy: size / 2,
|
|
734
|
+
r,
|
|
735
|
+
fill: "none",
|
|
736
|
+
strokeWidth: thickness,
|
|
737
|
+
strokeLinecap: "round",
|
|
738
|
+
strokeDasharray: `${dash} ${c - dash}`,
|
|
739
|
+
sx: (theme) => ({
|
|
740
|
+
stroke: "currentColor",
|
|
741
|
+
transition: theme.transitions.create("stroke-dasharray")
|
|
742
|
+
})
|
|
743
|
+
}
|
|
744
|
+
)
|
|
745
|
+
),
|
|
746
|
+
/* @__PURE__ */ React.createElement(
|
|
747
|
+
Box,
|
|
748
|
+
{
|
|
749
|
+
sx: {
|
|
750
|
+
position: "absolute",
|
|
751
|
+
inset: 0,
|
|
752
|
+
display: "flex",
|
|
753
|
+
flexDirection: "column",
|
|
754
|
+
alignItems: "center",
|
|
755
|
+
justifyContent: "center",
|
|
756
|
+
lineHeight: 1
|
|
757
|
+
}
|
|
758
|
+
},
|
|
759
|
+
/* @__PURE__ */ React.createElement(
|
|
760
|
+
Typography,
|
|
761
|
+
{
|
|
762
|
+
sx: {
|
|
763
|
+
fontSize: size <= 64 ? 15 : 18,
|
|
764
|
+
fontWeight: 700,
|
|
765
|
+
fontVariantNumeric: "tabular-nums",
|
|
766
|
+
color: "text.primary"
|
|
767
|
+
}
|
|
768
|
+
},
|
|
769
|
+
label ?? `${Math.round(value)}%`
|
|
770
|
+
),
|
|
771
|
+
caption && /* @__PURE__ */ React.createElement(
|
|
772
|
+
Typography,
|
|
773
|
+
{
|
|
774
|
+
variant: "caption",
|
|
775
|
+
sx: { fontSize: 9.5, color: "text.secondary", mt: 0.25 }
|
|
776
|
+
},
|
|
777
|
+
caption
|
|
778
|
+
)
|
|
779
|
+
)
|
|
780
|
+
);
|
|
781
|
+
};
|
|
686
782
|
dataTableSx = (theme) => ({
|
|
687
783
|
// Element selectors, not `.MuiTableCell-head`: host apps may set a MUI
|
|
688
784
|
// classname prefix (Backstage ships `v5-`), which breaks global class targeting.
|
|
@@ -2828,6 +2924,22 @@ function buildBudgetSummary(user, teams, keys, maxKeys = 3) {
|
|
|
2828
2924
|
teams: teamLimits
|
|
2829
2925
|
};
|
|
2830
2926
|
}
|
|
2927
|
+
function buildBudgetGauges(summary) {
|
|
2928
|
+
const byLevel = {
|
|
2929
|
+
key: summary.keys,
|
|
2930
|
+
user: summary.user ? [summary.user] : [],
|
|
2931
|
+
team: summary.teams
|
|
2932
|
+
};
|
|
2933
|
+
return GAUGE_KINDS.map((kind) => {
|
|
2934
|
+
const limits = byLevel[kind];
|
|
2935
|
+
const count = kind === "key" ? limits.length + summary.hiddenBudgetedKeys : limits.length;
|
|
2936
|
+
return {
|
|
2937
|
+
kind,
|
|
2938
|
+
count,
|
|
2939
|
+
limit: limits.length ? limits.reduce((a, b) => b.pct > a.pct ? b : a) : void 0
|
|
2940
|
+
};
|
|
2941
|
+
});
|
|
2942
|
+
}
|
|
2831
2943
|
function budgetHeadline(summary) {
|
|
2832
2944
|
const all = [
|
|
2833
2945
|
...summary.keys,
|
|
@@ -2854,9 +2966,11 @@ function budgetTone2(pct) {
|
|
|
2854
2966
|
if (pct >= 80) return "warning";
|
|
2855
2967
|
return "accent";
|
|
2856
2968
|
}
|
|
2969
|
+
var GAUGE_KINDS;
|
|
2857
2970
|
var init_budget = __esm({
|
|
2858
2971
|
"src/budget.ts"() {
|
|
2859
2972
|
"use strict";
|
|
2973
|
+
GAUGE_KINDS = ["key", "user", "team"];
|
|
2860
2974
|
}
|
|
2861
2975
|
});
|
|
2862
2976
|
|
|
@@ -4516,6 +4630,201 @@ var LiteLLMHomeWidget = ({
|
|
|
4516
4630
|
|
|
4517
4631
|
// src/index.ts
|
|
4518
4632
|
init_LiteLLMBudgetWidget();
|
|
4633
|
+
|
|
4634
|
+
// src/components/LiteLLMBudgetGauges.tsx
|
|
4635
|
+
init_api();
|
|
4636
|
+
init_format();
|
|
4637
|
+
init_ui();
|
|
4638
|
+
init_budget();
|
|
4639
|
+
import React14, { useEffect as useEffect5, useMemo as useMemo10, useState as useState11 } from "react";
|
|
4640
|
+
import Paper9 from "@mui/material/Paper";
|
|
4641
|
+
import Box13 from "@mui/material/Box";
|
|
4642
|
+
import Typography13 from "@mui/material/Typography";
|
|
4643
|
+
import CircularProgress8 from "@mui/material/CircularProgress";
|
|
4644
|
+
import Alert8 from "@mui/material/Alert";
|
|
4645
|
+
import { useApi as useApi5 } from "@backstage/core-plugin-api";
|
|
4646
|
+
import { Link as Link2 } from "@backstage/core-components";
|
|
4647
|
+
var DEFAULT_KEYS_HREF = "/litellm?tab=keys";
|
|
4648
|
+
var LEVEL = {
|
|
4649
|
+
key: { name: "Key", none: "No key budget" },
|
|
4650
|
+
user: { name: "User", none: "No personal budget" },
|
|
4651
|
+
team: { name: "Team", none: "No team budget" }
|
|
4652
|
+
};
|
|
4653
|
+
function fmtUsdShort(n) {
|
|
4654
|
+
const v = n ?? 0;
|
|
4655
|
+
if (v >= 100 && Number.isInteger(v)) return `$${v}`;
|
|
4656
|
+
return fmtUsd(v);
|
|
4657
|
+
}
|
|
4658
|
+
function spendCaption(limit) {
|
|
4659
|
+
if (limit.hidden) return "hidden by admin";
|
|
4660
|
+
return `${fmtUsdShort(limit.spend)} / ${fmtUsdShort(limit.budget)}`;
|
|
4661
|
+
}
|
|
4662
|
+
function resetLabel(limit) {
|
|
4663
|
+
const window2 = fmtBudgetDuration(limit.budgetDuration);
|
|
4664
|
+
return window2 ? `resets ${window2}` : "never resets";
|
|
4665
|
+
}
|
|
4666
|
+
var LevelGauge = ({
|
|
4667
|
+
gauge,
|
|
4668
|
+
size,
|
|
4669
|
+
keysHref
|
|
4670
|
+
}) => {
|
|
4671
|
+
const { name, none } = LEVEL[gauge.kind];
|
|
4672
|
+
const limit = gauge.limit;
|
|
4673
|
+
const tone = limit ? budgetTone2(limit.pct) : "neutral";
|
|
4674
|
+
const extra = gauge.kind === "key" ? Math.max(0, gauge.count - 1) : 0;
|
|
4675
|
+
return /* @__PURE__ */ React14.createElement(
|
|
4676
|
+
Box13,
|
|
4677
|
+
{
|
|
4678
|
+
sx: {
|
|
4679
|
+
display: "flex",
|
|
4680
|
+
flexDirection: "column",
|
|
4681
|
+
alignItems: "center",
|
|
4682
|
+
minWidth: 0,
|
|
4683
|
+
flex: 1
|
|
4684
|
+
}
|
|
4685
|
+
},
|
|
4686
|
+
/* @__PURE__ */ React14.createElement(
|
|
4687
|
+
Gauge,
|
|
4688
|
+
{
|
|
4689
|
+
value: limit?.pct ?? 0,
|
|
4690
|
+
tone,
|
|
4691
|
+
size,
|
|
4692
|
+
label: limit ? `${Math.round(limit.pct)}%` : "\u2014"
|
|
4693
|
+
}
|
|
4694
|
+
),
|
|
4695
|
+
/* @__PURE__ */ React14.createElement(
|
|
4696
|
+
Typography13,
|
|
4697
|
+
{
|
|
4698
|
+
sx: (theme) => ({
|
|
4699
|
+
mt: 0.75,
|
|
4700
|
+
fontSize: 10.5,
|
|
4701
|
+
fontWeight: 700,
|
|
4702
|
+
letterSpacing: "0.08em",
|
|
4703
|
+
textTransform: "uppercase",
|
|
4704
|
+
color: tone === "neutral" ? theme.palette.text.secondary : void 0
|
|
4705
|
+
})
|
|
4706
|
+
},
|
|
4707
|
+
name
|
|
4708
|
+
),
|
|
4709
|
+
limit ? /* @__PURE__ */ React14.createElement(React14.Fragment, null, /* @__PURE__ */ React14.createElement(
|
|
4710
|
+
Typography13,
|
|
4711
|
+
{
|
|
4712
|
+
variant: "caption",
|
|
4713
|
+
title: limit.sublabel ? `${limit.label} \xB7 ${limit.sublabel}` : limit.label,
|
|
4714
|
+
sx: {
|
|
4715
|
+
mt: 0.25,
|
|
4716
|
+
fontSize: 11,
|
|
4717
|
+
fontWeight: 600,
|
|
4718
|
+
maxWidth: "100%",
|
|
4719
|
+
overflow: "hidden",
|
|
4720
|
+
textOverflow: "ellipsis",
|
|
4721
|
+
whiteSpace: "nowrap"
|
|
4722
|
+
}
|
|
4723
|
+
},
|
|
4724
|
+
limit.label
|
|
4725
|
+
), /* @__PURE__ */ React14.createElement(
|
|
4726
|
+
Typography13,
|
|
4727
|
+
{
|
|
4728
|
+
variant: "caption",
|
|
4729
|
+
color: "text.secondary",
|
|
4730
|
+
sx: {
|
|
4731
|
+
fontSize: 10.5,
|
|
4732
|
+
fontVariantNumeric: "tabular-nums",
|
|
4733
|
+
textAlign: "center"
|
|
4734
|
+
}
|
|
4735
|
+
},
|
|
4736
|
+
spendCaption(limit)
|
|
4737
|
+
), extra > 0 ? /* @__PURE__ */ React14.createElement(
|
|
4738
|
+
Typography13,
|
|
4739
|
+
{
|
|
4740
|
+
component: Link2,
|
|
4741
|
+
to: keysHref,
|
|
4742
|
+
variant: "caption",
|
|
4743
|
+
sx: {
|
|
4744
|
+
fontSize: 10.5,
|
|
4745
|
+
color: "primary.main",
|
|
4746
|
+
textDecoration: "none",
|
|
4747
|
+
"&:hover": { textDecoration: "underline" }
|
|
4748
|
+
}
|
|
4749
|
+
},
|
|
4750
|
+
"+",
|
|
4751
|
+
extra,
|
|
4752
|
+
" more"
|
|
4753
|
+
) : /* @__PURE__ */ React14.createElement(Typography13, { variant: "caption", color: "text.secondary", sx: { fontSize: 10.5 } }, resetLabel(limit))) : /* @__PURE__ */ React14.createElement(
|
|
4754
|
+
Typography13,
|
|
4755
|
+
{
|
|
4756
|
+
variant: "caption",
|
|
4757
|
+
color: "text.secondary",
|
|
4758
|
+
sx: { mt: 0.25, fontSize: 10.5, textAlign: "center" }
|
|
4759
|
+
},
|
|
4760
|
+
none
|
|
4761
|
+
)
|
|
4762
|
+
);
|
|
4763
|
+
};
|
|
4764
|
+
var LiteLLMBudgetGauges = ({
|
|
4765
|
+
title = "Budget",
|
|
4766
|
+
size = 72,
|
|
4767
|
+
keysHref = DEFAULT_KEYS_HREF,
|
|
4768
|
+
action
|
|
4769
|
+
}) => {
|
|
4770
|
+
const api = useApi5(liteLlmApiRef);
|
|
4771
|
+
const [loading, setLoading] = useState11(true);
|
|
4772
|
+
const [error, setError] = useState11(null);
|
|
4773
|
+
const [user, setUser] = useState11(null);
|
|
4774
|
+
const [teams, setTeams] = useState11([]);
|
|
4775
|
+
const [keys, setKeys] = useState11([]);
|
|
4776
|
+
useEffect5(() => {
|
|
4777
|
+
let cancelled = false;
|
|
4778
|
+
setLoading(true);
|
|
4779
|
+
setError(null);
|
|
4780
|
+
Promise.allSettled([api.getUserInfo(), api.getTeams(), api.listKeys()]).then(
|
|
4781
|
+
([userResult, teamsResult, keysResult]) => {
|
|
4782
|
+
if (cancelled) return;
|
|
4783
|
+
if (userResult.status === "fulfilled") {
|
|
4784
|
+
setUser(userResult.value);
|
|
4785
|
+
} else {
|
|
4786
|
+
setUser(null);
|
|
4787
|
+
setError(
|
|
4788
|
+
userResult.reason?.message ?? "Failed to load your LiteLLM profile"
|
|
4789
|
+
);
|
|
4790
|
+
}
|
|
4791
|
+
setTeams(teamsResult.status === "fulfilled" ? teamsResult.value : []);
|
|
4792
|
+
setKeys(keysResult.status === "fulfilled" ? keysResult.value : []);
|
|
4793
|
+
setLoading(false);
|
|
4794
|
+
}
|
|
4795
|
+
);
|
|
4796
|
+
return () => {
|
|
4797
|
+
cancelled = true;
|
|
4798
|
+
};
|
|
4799
|
+
}, [api]);
|
|
4800
|
+
const summary = useMemo10(
|
|
4801
|
+
() => buildBudgetSummary(user, teams, keys, keys.length),
|
|
4802
|
+
[user, teams, keys]
|
|
4803
|
+
);
|
|
4804
|
+
const gauges = useMemo10(() => buildBudgetGauges(summary), [summary]);
|
|
4805
|
+
const headline = useMemo10(() => budgetHeadline(summary), [summary]);
|
|
4806
|
+
const summaryText = headline.count === 0 ? "no limits apply" : `${headline.count} limit${headline.count === 1 ? "" : "s"}`;
|
|
4807
|
+
return /* @__PURE__ */ React14.createElement(Paper9, { sx: { p: 2 } }, /* @__PURE__ */ React14.createElement(Box13, { sx: { display: "flex", alignItems: "center", gap: 1, mb: 1.5, minWidth: 0 } }, /* @__PURE__ */ React14.createElement(Typography13, { variant: "h6", lineHeight: 1.2, sx: { minWidth: 0, flex: 1 } }, title), !loading && !error && /* @__PURE__ */ React14.createElement(
|
|
4808
|
+
StatusPill,
|
|
4809
|
+
{
|
|
4810
|
+
label: summaryText,
|
|
4811
|
+
tone: budgetTone2(headline.closest?.pct ?? 0),
|
|
4812
|
+
dot: headline.count > 0
|
|
4813
|
+
}
|
|
4814
|
+
)), loading && /* @__PURE__ */ React14.createElement(Box13, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: 110 }, /* @__PURE__ */ React14.createElement(CircularProgress8, { size: 28 })), !loading && error && /* @__PURE__ */ React14.createElement(Alert8, { severity: "error", sx: { mt: 0.5 } }, error), !loading && !error && /* @__PURE__ */ React14.createElement(Box13, { sx: { display: "flex", alignItems: "flex-start", gap: 1 } }, gauges.map((gauge) => /* @__PURE__ */ React14.createElement(LevelGauge, { key: gauge.kind, gauge, size, keysHref }))), action && /* @__PURE__ */ React14.createElement(
|
|
4815
|
+
Box13,
|
|
4816
|
+
{
|
|
4817
|
+
sx: (theme) => ({
|
|
4818
|
+
mt: 2,
|
|
4819
|
+
pt: 2,
|
|
4820
|
+
borderTop: `1px solid ${theme.palette.divider}`
|
|
4821
|
+
})
|
|
4822
|
+
},
|
|
4823
|
+
action
|
|
4824
|
+
));
|
|
4825
|
+
};
|
|
4826
|
+
|
|
4827
|
+
// src/index.ts
|
|
4519
4828
|
init_GenerateKeyDialog();
|
|
4520
4829
|
init_ManageTeamDialog();
|
|
4521
4830
|
init_api();
|
|
@@ -4524,6 +4833,7 @@ export {
|
|
|
4524
4833
|
DashboardHeader,
|
|
4525
4834
|
GenerateKeyDialog,
|
|
4526
4835
|
KeysTable,
|
|
4836
|
+
LiteLLMBudgetGauges,
|
|
4527
4837
|
LiteLLMBudgetWidget,
|
|
4528
4838
|
LiteLLMHomeWidget,
|
|
4529
4839
|
LiteLLMPage,
|