@acarmisc/backstage-plugin-litellm 0.15.2 → 0.15.3
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/README.md +38 -1
- package/dist/components/UsageStats.d.ts +3 -1
- package/dist/index.cjs.js +17 -14
- package/dist/index.cjs.js.map +2 -2
- package/dist/index.esm.js +17 -14
- package/dist/index.esm.js.map +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,6 +8,17 @@ Pair this package with the backend, [`@acarmisc/backstage-plugin-litellm-backend
|
|
|
8
8
|
|
|
9
9
|
Full setup docs, configuration reference, and architecture notes live in the [project README](https://github.com/acarmisc/backstage-plugin-litellm-govai#readme).
|
|
10
10
|
|
|
11
|
+
## Screenshots
|
|
12
|
+
|
|
13
|
+
| View | Preview |
|
|
14
|
+
|------|---------|
|
|
15
|
+
| Home widget — `LiteLLMHomeWidget` |  |
|
|
16
|
+
| Overview / Usage Analytics |  |
|
|
17
|
+
| Keys tab — virtual key inventory |  |
|
|
18
|
+
| Generate New Key dialog |  |
|
|
19
|
+
| Models tab |  |
|
|
20
|
+
| Compact "Create Key" card |  |
|
|
21
|
+
|
|
11
22
|
## Installation
|
|
12
23
|
|
|
13
24
|
This plugin is designed to be used **within a Backstage monorepo**.
|
|
@@ -22,6 +33,8 @@ You'll also need the backend package installed and configured — see its [READM
|
|
|
22
33
|
|
|
23
34
|
### Full page
|
|
24
35
|
|
|
36
|
+
The `LiteLLMPage` component renders the four-tab governance view: **Overview** (usage KPIs + charts), **Keys** (virtual key inventory), **Teams** (team-scoped usage), and **Models** (proxy model catalog).
|
|
37
|
+
|
|
25
38
|
```tsx
|
|
26
39
|
import { litellmPlugin, LiteLLMPage } from '@acarmisc/backstage-plugin-litellm';
|
|
27
40
|
|
|
@@ -29,9 +42,11 @@ import { litellmPlugin, LiteLLMPage } from '@acarmisc/backstage-plugin-litellm';
|
|
|
29
42
|
<Route path="/litellm" element={<LiteLLMPage />} />
|
|
30
43
|
```
|
|
31
44
|
|
|
45
|
+

|
|
46
|
+
|
|
32
47
|
### Home page widget
|
|
33
48
|
|
|
34
|
-
`LiteLLMHomeWidget` is a compact card for the Backstage homepage. It shows the signed-in user's own usage — identity is resolved server-side from the Backstage token, so no `userId` prop is required.
|
|
49
|
+
`LiteLLMHomeWidget` is a compact card for the Backstage homepage. It shows the signed-in user's own usage — identity is resolved server-side from the Backstage token, so no `userId` prop is required. The header includes a `Today` / `7d` / `30d` period selector and a daily-spend sparkline.
|
|
35
50
|
|
|
36
51
|
```tsx
|
|
37
52
|
import { LiteLLMHomeWidget } from '@acarmisc/backstage-plugin-litellm';
|
|
@@ -44,6 +59,28 @@ import { LiteLLMHomeWidget } from '@acarmisc/backstage-plugin-litellm';
|
|
|
44
59
|
| `defaultPeriod` | `'today' \| '7d' \| '30d'` | `'7d'` | Period shown on first render |
|
|
45
60
|
| `title` | `string` | `'LiteLLM Usage'` | Card title override |
|
|
46
61
|
|
|
62
|
+
A slimmer **Create Key** card variant is available for surfaces that only need a one-click shortcut into the key-mint flow:
|
|
63
|
+
|
|
64
|
+

|
|
65
|
+
|
|
66
|
+
### Keys tab
|
|
67
|
+
|
|
68
|
+
The **Keys** tab lists every virtual key you own with its alias, key ID, creation and expiry dates, budget bar (`$spent / $limit`), TPM/RPM, and model scope. Each row exposes inline actions: edit, block/unblock, reset spend, and delete.
|
|
69
|
+
|
|
70
|
+

|
|
71
|
+
|
|
72
|
+
### Generate New Key dialog
|
|
73
|
+
|
|
74
|
+
The dialog walks the user through aliasing the key, picking a duration, binding a team, restricting model access, setting a max budget, and configuring per-key TPM/RPM caps. Failures (e.g. duplicate alias) surface inline with the upstream error code preserved.
|
|
75
|
+
|
|
76
|
+

|
|
77
|
+
|
|
78
|
+
### Models tab
|
|
79
|
+
|
|
80
|
+
The **Models** tab lists every model the LiteLLM proxy exposes, with per-model input/output cost, max input/output token limits, and capabilities. The team selector scopes the list to the models the bound team is allowed to call.
|
|
81
|
+
|
|
82
|
+

|
|
83
|
+
|
|
47
84
|
## Exports
|
|
48
85
|
|
|
49
86
|
- `litellmPlugin` — the frontend plugin instance
|
|
@@ -4,9 +4,11 @@ interface UsageStatsProps {
|
|
|
4
4
|
usage: UsageMetrics | null;
|
|
5
5
|
models: ModelInfo[];
|
|
6
6
|
dateRange: DateRange;
|
|
7
|
-
|
|
7
|
+
currentPreset: DatePreset;
|
|
8
|
+
onDateRangeChange: (range: DateRange, preset?: DatePreset) => void;
|
|
8
9
|
loading: boolean;
|
|
9
10
|
userInfo?: UserInfo;
|
|
10
11
|
}
|
|
12
|
+
type DatePreset = 'today' | '24h' | '7d' | '30d';
|
|
11
13
|
export declare const UsageStats: React.FC<UsageStatsProps>;
|
|
12
14
|
export {};
|
package/dist/index.cjs.js
CHANGED
|
@@ -1937,6 +1937,7 @@ var init_UsageStats = __esm({
|
|
|
1937
1937
|
usage,
|
|
1938
1938
|
models,
|
|
1939
1939
|
dateRange,
|
|
1940
|
+
currentPreset,
|
|
1940
1941
|
onDateRangeChange,
|
|
1941
1942
|
loading,
|
|
1942
1943
|
userInfo
|
|
@@ -1944,15 +1945,6 @@ var init_UsageStats = __esm({
|
|
|
1944
1945
|
const chart = useChartTheme();
|
|
1945
1946
|
const [selectedModel, setSelectedModel] = (0, import_react5.useState)("all");
|
|
1946
1947
|
const [tab, setTab] = (0, import_react5.useState)("costs");
|
|
1947
|
-
const selectedPreset = (0, import_react5.useMemo)(() => {
|
|
1948
|
-
if (dateRange.start.toDateString() === dateRange.end.toDateString()) return "today";
|
|
1949
|
-
const diffMs = dateRange.end.getTime() - dateRange.start.getTime();
|
|
1950
|
-
const diffHours = diffMs / (1e3 * 60 * 60);
|
|
1951
|
-
if (diffHours <= 26) return "24h";
|
|
1952
|
-
const diffDays = Math.ceil(diffMs / (1e3 * 60 * 60 * 24));
|
|
1953
|
-
if (diffDays <= 7) return "7d";
|
|
1954
|
-
return "30d";
|
|
1955
|
-
}, [dateRange]);
|
|
1956
1948
|
const handlePresetChange = (preset) => {
|
|
1957
1949
|
const end = /* @__PURE__ */ new Date();
|
|
1958
1950
|
const start = /* @__PURE__ */ new Date();
|
|
@@ -1964,7 +1956,7 @@ var init_UsageStats = __esm({
|
|
|
1964
1956
|
localStorage.setItem(PERIOD_LS_KEY, preset);
|
|
1965
1957
|
} catch {
|
|
1966
1958
|
}
|
|
1967
|
-
onDateRangeChange({ start, end });
|
|
1959
|
+
onDateRangeChange({ start, end }, preset);
|
|
1968
1960
|
};
|
|
1969
1961
|
const dailyData = (0, import_react5.useMemo)(
|
|
1970
1962
|
() => (usage?.daily_usage ?? []).map((d) => ({
|
|
@@ -2103,7 +2095,7 @@ var init_UsageStats = __esm({
|
|
|
2103
2095
|
select: true,
|
|
2104
2096
|
size: "small",
|
|
2105
2097
|
label: "Period",
|
|
2106
|
-
value:
|
|
2098
|
+
value: currentPreset,
|
|
2107
2099
|
onChange: (e) => handlePresetChange(e.target.value),
|
|
2108
2100
|
sx: { minWidth: 160 }
|
|
2109
2101
|
},
|
|
@@ -2113,7 +2105,7 @@ var init_UsageStats = __esm({
|
|
|
2113
2105
|
SectionCard,
|
|
2114
2106
|
{
|
|
2115
2107
|
title: "Usage Analytics",
|
|
2116
|
-
subtitle: `${PRESET_LABELS[
|
|
2108
|
+
subtitle: `${PRESET_LABELS[currentPreset]} \xB7 ${dateRange.start.toLocaleDateString()} \u2013 ${dateRange.end.toLocaleDateString()}`,
|
|
2117
2109
|
actions: periodSelect
|
|
2118
2110
|
},
|
|
2119
2111
|
/* @__PURE__ */ import_react5.default.createElement(MetricStrip, { metrics }),
|
|
@@ -2880,6 +2872,13 @@ var init_LiteLLMPage = __esm({
|
|
|
2880
2872
|
LiteLLMPage = () => {
|
|
2881
2873
|
const api = (0, import_core_plugin_api2.useApi)(liteLlmApiRef);
|
|
2882
2874
|
const [dateRange, setDateRange] = (0, import_react9.useState)(initDateRange);
|
|
2875
|
+
const [currentPreset, setCurrentPreset] = (0, import_react9.useState)(() => {
|
|
2876
|
+
try {
|
|
2877
|
+
return localStorage.getItem(PERIOD_LS_KEY2) ?? "7d";
|
|
2878
|
+
} catch {
|
|
2879
|
+
return "7d";
|
|
2880
|
+
}
|
|
2881
|
+
});
|
|
2883
2882
|
const [activeTab, setActiveTab] = (0, import_react9.useState)("overview");
|
|
2884
2883
|
const [snackbar, setSnackbar] = (0, import_react9.useState)(null);
|
|
2885
2884
|
const [generateDialogOpen, setGenerateDialogOpen] = (0, import_react9.useState)(false);
|
|
@@ -2926,11 +2925,14 @@ var init_LiteLLMPage = __esm({
|
|
|
2926
2925
|
const endDate = dateRange.end.toISOString().split("T")[0];
|
|
2927
2926
|
return api.getUsage(startDate, endDate);
|
|
2928
2927
|
}, [api, dateRange]);
|
|
2929
|
-
const handleDateRangeChange = (0, import_react9.useCallback)((range) => {
|
|
2928
|
+
const handleDateRangeChange = (0, import_react9.useCallback)((range, preset) => {
|
|
2930
2929
|
setDateRange(range);
|
|
2930
|
+
if (preset) {
|
|
2931
|
+
setCurrentPreset(preset);
|
|
2932
|
+
}
|
|
2931
2933
|
setTeamUsageCache({});
|
|
2932
2934
|
setTeamUsageLoading({});
|
|
2933
|
-
}, [setDateRange]);
|
|
2935
|
+
}, [setDateRange, setCurrentPreset]);
|
|
2934
2936
|
const loadTeamUsage = (0, import_react9.useCallback)(async (teamId) => {
|
|
2935
2937
|
if (teamUsageCache[teamId] !== void 0 || teamUsageLoading[teamId]) return;
|
|
2936
2938
|
setTeamUsageLoading((prev) => ({ ...prev, [teamId]: true }));
|
|
@@ -3095,6 +3097,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3095
3097
|
usage: usage ?? null,
|
|
3096
3098
|
models: allModels ?? [],
|
|
3097
3099
|
dateRange,
|
|
3100
|
+
currentPreset,
|
|
3098
3101
|
onDateRangeChange: handleDateRangeChange,
|
|
3099
3102
|
loading: usageLoading,
|
|
3100
3103
|
userInfo
|