@acarmisc/backstage-plugin-litellm 0.34.0 → 0.35.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.d.ts +4 -1
- package/dist/components/TeamUsage.d.ts +11 -1
- package/dist/index.cjs.js +76 -3
- package/dist/index.cjs.js.map +3 -3
- package/dist/index.esm.js +76 -3
- package/dist/index.esm.js.map +2 -2
- package/dist/types.d.ts +6 -1
- package/package.json +2 -2
package/dist/api.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type FetchApi, type DiscoveryApi } from '@backstage/frontend-plugin-api';
|
|
2
|
-
import { UserInfo, VirtualKey, ModelInfo, UsageMetrics, TeamInfo, GenerateKeyRequest, GenerateKeyResponse, UpdateKeyRequest, AuditLogsParams, PaginatedAuditLogs, LiteLlmConfig, VectorStoreInfo, McpServerInfo, CreateTeamRequest, CreateTeamResponse, UpdateTeamRequest } from './types';
|
|
2
|
+
import { UserInfo, VirtualKey, ModelInfo, UsageMetrics, TeamMemberUsage, TeamInfo, GenerateKeyRequest, GenerateKeyResponse, UpdateKeyRequest, AuditLogsParams, PaginatedAuditLogs, LiteLlmConfig, VectorStoreInfo, McpServerInfo, CreateTeamRequest, CreateTeamResponse, UpdateTeamRequest } from './types';
|
|
3
3
|
export declare class ApiError extends Error {
|
|
4
4
|
body: unknown;
|
|
5
5
|
status: number;
|
|
@@ -38,6 +38,8 @@ export interface LiteLlmApiInterface {
|
|
|
38
38
|
setTeamMcpServers(teamId: string, mcpServers: string[]): Promise<TeamInfo>;
|
|
39
39
|
getUsage(startDate: string, endDate: string): Promise<UsageMetrics>;
|
|
40
40
|
getTeamUsage(teamId: string, startDate: string, endDate: string): Promise<UsageMetrics>;
|
|
41
|
+
/** Per-member breakdown; rejects (403/404) when the caller may not see it. */
|
|
42
|
+
getTeamMemberUsage(teamId: string, startDate: string, endDate: string): Promise<TeamMemberUsage>;
|
|
41
43
|
getAuditLogs(params: AuditLogsParams): Promise<PaginatedAuditLogs>;
|
|
42
44
|
getConfig(): Promise<LiteLlmConfig>;
|
|
43
45
|
createTeam(request: CreateTeamRequest): Promise<CreateTeamResponse>;
|
|
@@ -96,6 +98,7 @@ export declare class LiteLlmApi implements LiteLlmApiInterface {
|
|
|
96
98
|
setTeamMcpServers(teamId: string, mcpServers: string[]): Promise<TeamInfo>;
|
|
97
99
|
getUsage(startDate: string, endDate: string): Promise<UsageMetrics>;
|
|
98
100
|
getTeamUsage(teamId: string, startDate: string, endDate: string): Promise<UsageMetrics>;
|
|
101
|
+
getTeamMemberUsage(teamId: string, startDate: string, endDate: string): Promise<TeamMemberUsage>;
|
|
99
102
|
getConfig(): Promise<LiteLlmConfig>;
|
|
100
103
|
createTeam(request: CreateTeamRequest): Promise<CreateTeamResponse>;
|
|
101
104
|
updateTeam(teamId: string, request: UpdateTeamRequest): Promise<TeamInfo>;
|
|
@@ -1,10 +1,20 @@
|
|
|
1
1
|
import { FC } from 'react';
|
|
2
|
-
import { TeamInfo, UsageMetrics } from '../types';
|
|
2
|
+
import { TeamInfo, TeamMemberUsage, UsageMetrics } from '../types';
|
|
3
|
+
/**
|
|
4
|
+
* Per-member usage for the period, sorted by spend. Dollars are replaced by
|
|
5
|
+
* the share of team spend when the backend hides them.
|
|
6
|
+
*/
|
|
7
|
+
export declare const MemberUsageTable: FC<{
|
|
8
|
+
usage: TeamMemberUsage;
|
|
9
|
+
}>;
|
|
3
10
|
interface TeamUsageProps {
|
|
4
11
|
teams: TeamInfo[];
|
|
5
12
|
loading: boolean;
|
|
6
13
|
getTeamUsage: (teamId: string) => UsageMetrics | null;
|
|
7
14
|
getTeamUsageLoading: (teamId: string) => boolean;
|
|
15
|
+
/** Per-member breakdown for a team; null hides the section. */
|
|
16
|
+
getTeamMemberUsage?: (teamId: string) => TeamMemberUsage | null;
|
|
17
|
+
getTeamMemberUsageLoading?: (teamId: string) => boolean;
|
|
8
18
|
canManage?: boolean;
|
|
9
19
|
onEditTeam?: (team: TeamInfo) => void;
|
|
10
20
|
/** When true (with `onCreateTeam`), a "Create Team" action renders in the section header. */
|
package/dist/index.cjs.js
CHANGED
|
@@ -307,6 +307,12 @@ var init_api = __esm({
|
|
|
307
307
|
end_date: endDate
|
|
308
308
|
});
|
|
309
309
|
}
|
|
310
|
+
async getTeamMemberUsage(teamId, startDate, endDate) {
|
|
311
|
+
return this.get(`/teams/${encodeURIComponent(teamId)}/usage/members`, {
|
|
312
|
+
start_date: startDate,
|
|
313
|
+
end_date: endDate
|
|
314
|
+
});
|
|
315
|
+
}
|
|
310
316
|
async getConfig() {
|
|
311
317
|
return this.get("/config");
|
|
312
318
|
}
|
|
@@ -4986,7 +4992,7 @@ function budgetStatValue(hidden, budget) {
|
|
|
4986
4992
|
if (budget > 0) return fmtUsd2(budget);
|
|
4987
4993
|
return "Unlimited";
|
|
4988
4994
|
}
|
|
4989
|
-
var import_react10, import_Paper8, import_Box14, import_Typography12, import_Skeleton5, import_Divider3, import_Table4, import_TableBody4, import_TableCell4, import_TableHead4, import_TableRow4, import_TableContainer4, import_Collapse2, import_IconButton6, import_Button6, import_CircularProgress4, import_styles10, import_Tooltip5, import_icons_material7, import_recharts2, import_jsx_runtime15, fmtUsd2, TeamCard, TeamUsage;
|
|
4995
|
+
var import_react10, import_Paper8, import_Box14, import_Typography12, import_Skeleton5, import_Divider3, import_Table4, import_TableBody4, import_TableCell4, import_TableHead4, import_TableRow4, import_TableContainer4, import_Collapse2, import_IconButton6, import_Button6, import_CircularProgress4, import_styles10, import_Tooltip5, import_icons_material7, import_recharts2, import_jsx_runtime15, fmtUsd2, monoSx, MemberUsageTable, TeamCard, TeamUsage;
|
|
4990
4996
|
var init_TeamUsage = __esm({
|
|
4991
4997
|
"src/components/TeamUsage.tsx"() {
|
|
4992
4998
|
"use strict";
|
|
@@ -5013,7 +5019,43 @@ var init_TeamUsage = __esm({
|
|
|
5013
5019
|
init_ui();
|
|
5014
5020
|
import_jsx_runtime15 = require("react/jsx-runtime");
|
|
5015
5021
|
fmtUsd2 = (n) => `$${(n ?? 0).toFixed(2)}`;
|
|
5016
|
-
|
|
5022
|
+
monoSx = { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 11 };
|
|
5023
|
+
MemberUsageTable = ({ usage }) => {
|
|
5024
|
+
const hidden = usage.budget_hidden === true;
|
|
5025
|
+
if (!usage.members.length) {
|
|
5026
|
+
return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_Typography12.default, { variant: "body2", color: "text.secondary", children: "No members or activity in this period." });
|
|
5027
|
+
}
|
|
5028
|
+
return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_TableContainer4.default, { component: import_Paper8.default, variant: "outlined", sx: { borderRadius: 1.5 }, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_Table4.default, { size: "small", sx: dataTableSx, "aria-label": "Usage by member", children: [
|
|
5029
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_TableHead4.default, { children: /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_TableRow4.default, { children: [
|
|
5030
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_TableCell4.default, { children: "Member" }),
|
|
5031
|
+
!hidden && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_TableCell4.default, { align: "right", children: "Spend" }),
|
|
5032
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_TableCell4.default, { align: "right", children: "Share" }),
|
|
5033
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_TableCell4.default, { align: "right", children: "Tokens" }),
|
|
5034
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_TableCell4.default, { align: "right", children: "Requests" }),
|
|
5035
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_TableCell4.default, { align: "right", children: "Success" }),
|
|
5036
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_TableCell4.default, { align: "right", children: "Keys" })
|
|
5037
|
+
] }) }),
|
|
5038
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_TableBody4.default, { children: usage.members.map((m) => {
|
|
5039
|
+
const name = m.display_name ?? m.user_email ?? m.user_id;
|
|
5040
|
+
let sub = null;
|
|
5041
|
+
if (m.display_name) sub = m.user_email ?? m.user_id;
|
|
5042
|
+
else if (m.user_email && m.user_id !== m.user_email) sub = m.user_id;
|
|
5043
|
+
return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_TableRow4.default, { children: [
|
|
5044
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_TableCell4.default, { children: m.user_id === null ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_Tooltip5.default, { title: "Activity of keys that were deleted or have no owner", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_Typography12.default, { variant: "body2", color: "text.secondary", sx: { fontStyle: "italic" }, children: "Unattributed" }) }) : /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_jsx_runtime15.Fragment, { children: [
|
|
5045
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_Typography12.default, { variant: "body2", children: name }),
|
|
5046
|
+
sub && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_Typography12.default, { variant: "caption", color: "text.secondary", sx: monoSx, children: sub })
|
|
5047
|
+
] }) }),
|
|
5048
|
+
!hidden && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_TableCell4.default, { align: "right", children: fmtUsd2(m.spend) }),
|
|
5049
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_TableCell4.default, { align: "right", children: `${m.spend_share_pct.toFixed(m.spend_share_pct > 0 && m.spend_share_pct < 1 ? 1 : 0)}%` }),
|
|
5050
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_TableCell4.default, { align: "right", children: fmtCompact(m.total_tokens) }),
|
|
5051
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_TableCell4.default, { align: "right", children: m.api_requests.toLocaleString() }),
|
|
5052
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_TableCell4.default, { align: "right", children: m.success_rate === null ? "\u2014" : `${m.success_rate.toFixed(0)}%` }),
|
|
5053
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_TableCell4.default, { align: "right", children: m.user_id === null ? "\u2014" : m.key_count })
|
|
5054
|
+
] }, m.user_id ?? "__unattributed");
|
|
5055
|
+
}) })
|
|
5056
|
+
] }) });
|
|
5057
|
+
};
|
|
5058
|
+
TeamCard = ({ team, usage, usageLoading, memberUsage, memberUsageLoading, canManage, onEditTeam, canManageMembers, onManageMembers, onExpand }) => {
|
|
5017
5059
|
const [expanded, setExpanded] = (0, import_react10.useState)(false);
|
|
5018
5060
|
const chart = useChartTheme();
|
|
5019
5061
|
const handleExpandChange = (newExpanded) => {
|
|
@@ -5206,6 +5248,10 @@ var init_TeamUsage = __esm({
|
|
|
5206
5248
|
team.models?.length ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_Box14.default, { display: "flex", gap: 0.5, flexWrap: "wrap", children: team.models.map((m) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(TagChip, { label: m, title: m }, m)) }) : /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_Typography12.default, { variant: "body2", color: "text.secondary", children: "All models allowed" })
|
|
5207
5249
|
] }),
|
|
5208
5250
|
renderDailySpendSection(),
|
|
5251
|
+
(memberUsageLoading || memberUsage) && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_Box14.default, { mb: 2.5, children: [
|
|
5252
|
+
sectionLabel("Usage by member"),
|
|
5253
|
+
memberUsage ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(MemberUsageTable, { usage: memberUsage }) : /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_Box14.default, { display: "flex", justifyContent: "center", py: 2, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_CircularProgress4.default, { size: 22 }) })
|
|
5254
|
+
] }),
|
|
5209
5255
|
/* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_Box14.default, { children: [
|
|
5210
5256
|
sectionLabel("Members"),
|
|
5211
5257
|
team.members_with_roles?.length ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
@@ -5260,6 +5306,8 @@ var init_TeamUsage = __esm({
|
|
|
5260
5306
|
loading,
|
|
5261
5307
|
getTeamUsage,
|
|
5262
5308
|
getTeamUsageLoading,
|
|
5309
|
+
getTeamMemberUsage,
|
|
5310
|
+
getTeamMemberUsageLoading,
|
|
5263
5311
|
canManage,
|
|
5264
5312
|
onEditTeam,
|
|
5265
5313
|
canCreate,
|
|
@@ -5319,6 +5367,8 @@ var init_TeamUsage = __esm({
|
|
|
5319
5367
|
team,
|
|
5320
5368
|
usage: getTeamUsage(team.team_id),
|
|
5321
5369
|
usageLoading: getTeamUsageLoading(team.team_id),
|
|
5370
|
+
memberUsage: getTeamMemberUsage?.(team.team_id) ?? null,
|
|
5371
|
+
memberUsageLoading: getTeamMemberUsageLoading?.(team.team_id) ?? false,
|
|
5322
5372
|
canManage,
|
|
5323
5373
|
onEditTeam,
|
|
5324
5374
|
canManageMembers: canManageMembers?.(team),
|
|
@@ -6125,6 +6175,8 @@ var init_LiteLLMPage = __esm({
|
|
|
6125
6175
|
const keyFormOpen = generateDialogOpen || !!keyToEdit;
|
|
6126
6176
|
const [teamUsageCache, setTeamUsageCache] = (0, import_react13.useState)({});
|
|
6127
6177
|
const [teamUsageLoading, setTeamUsageLoading] = (0, import_react13.useState)({});
|
|
6178
|
+
const [memberUsageCache, setMemberUsageCache] = (0, import_react13.useState)({});
|
|
6179
|
+
const [memberUsageLoading, setMemberUsageLoading] = (0, import_react13.useState)({});
|
|
6128
6180
|
const { value: userInfo, loading: userLoading, error: userError } = (0, import_react_use2.useAsync)(
|
|
6129
6181
|
() => api.getUserInfo(),
|
|
6130
6182
|
[api]
|
|
@@ -6185,6 +6237,8 @@ var init_LiteLLMPage = __esm({
|
|
|
6185
6237
|
}
|
|
6186
6238
|
setTeamUsageCache({});
|
|
6187
6239
|
setTeamUsageLoading({});
|
|
6240
|
+
setMemberUsageCache({});
|
|
6241
|
+
setMemberUsageLoading({});
|
|
6188
6242
|
}, [setDateRange, setCurrentPreset]);
|
|
6189
6243
|
const loadTeamUsage = (0, import_react13.useCallback)(async (teamId) => {
|
|
6190
6244
|
if (teamUsageCache[teamId] !== void 0 || teamUsageLoading[teamId]) return;
|
|
@@ -6200,6 +6254,20 @@ var init_LiteLLMPage = __esm({
|
|
|
6200
6254
|
setTeamUsageLoading((prev) => ({ ...prev, [teamId]: false }));
|
|
6201
6255
|
}
|
|
6202
6256
|
}, [api, dateRange, teamUsageCache, teamUsageLoading]);
|
|
6257
|
+
const loadMemberUsage = (0, import_react13.useCallback)(async (teamId) => {
|
|
6258
|
+
if (memberUsageCache[teamId] !== void 0 || memberUsageLoading[teamId]) return;
|
|
6259
|
+
setMemberUsageLoading((prev) => ({ ...prev, [teamId]: true }));
|
|
6260
|
+
try {
|
|
6261
|
+
const startDate = toLocalDay(dateRange.start);
|
|
6262
|
+
const endDate = toLocalDay(dateRange.end);
|
|
6263
|
+
const data = await api.getTeamMemberUsage(teamId, startDate, endDate);
|
|
6264
|
+
setMemberUsageCache((prev) => ({ ...prev, [teamId]: data }));
|
|
6265
|
+
} catch {
|
|
6266
|
+
setMemberUsageCache((prev) => ({ ...prev, [teamId]: null }));
|
|
6267
|
+
} finally {
|
|
6268
|
+
setMemberUsageLoading((prev) => ({ ...prev, [teamId]: false }));
|
|
6269
|
+
}
|
|
6270
|
+
}, [api, dateRange, memberUsageCache, memberUsageLoading]);
|
|
6203
6271
|
const allowedModels = (0, import_react13.useMemo)(() => {
|
|
6204
6272
|
if (!allModels?.length) return [];
|
|
6205
6273
|
const userModels = userInfo?.models;
|
|
@@ -6494,6 +6562,8 @@ var init_LiteLLMPage = __esm({
|
|
|
6494
6562
|
loading: teamsLoading,
|
|
6495
6563
|
getTeamUsage: (teamId) => teamUsageCache[teamId] ?? null,
|
|
6496
6564
|
getTeamUsageLoading: (teamId) => teamUsageLoading[teamId] ?? false,
|
|
6565
|
+
getTeamMemberUsage: (teamId) => memberUsageCache[teamId] ?? null,
|
|
6566
|
+
getTeamMemberUsageLoading: (teamId) => memberUsageLoading[teamId] ?? false,
|
|
6497
6567
|
canManage: teamMgmtEnabled && canManageTeam && !readOnly,
|
|
6498
6568
|
onEditTeam: (t) => setManageTeam({ mode: "edit", team: t }),
|
|
6499
6569
|
canCreate: teamMgmtEnabled && canCreateTeam && !readOnly,
|
|
@@ -6501,7 +6571,10 @@ var init_LiteLLMPage = __esm({
|
|
|
6501
6571
|
canManageMembers: (t) => teamMgmtEnabled && canManageMembers && isTeamMemberManager(t, userInfo?.user_id, memberManagerRoles, readOnly),
|
|
6502
6572
|
onManageMembers: (t) => setManageTeam({ mode: "members", team: t }),
|
|
6503
6573
|
readOnly,
|
|
6504
|
-
onTeamExpand: (team) =>
|
|
6574
|
+
onTeamExpand: (team) => {
|
|
6575
|
+
loadTeamUsage(team.team_id);
|
|
6576
|
+
if (liteLlmConfig?.teamUsage?.memberBreakdown) loadMemberUsage(team.team_id);
|
|
6577
|
+
}
|
|
6505
6578
|
}
|
|
6506
6579
|
);
|
|
6507
6580
|
})(),
|