@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/index.esm.js CHANGED
@@ -285,6 +285,12 @@ var init_api = __esm({
285
285
  end_date: endDate
286
286
  });
287
287
  }
288
+ async getTeamMemberUsage(teamId, startDate, endDate) {
289
+ return this.get(`/teams/${encodeURIComponent(teamId)}/usage/members`, {
290
+ start_date: startDate,
291
+ end_date: endDate
292
+ });
293
+ }
288
294
  async getConfig() {
289
295
  return this.get("/config");
290
296
  }
@@ -5010,13 +5016,49 @@ function budgetStatValue(hidden, budget) {
5010
5016
  if (budget > 0) return fmtUsd2(budget);
5011
5017
  return "Unlimited";
5012
5018
  }
5013
- var fmtUsd2, TeamCard, TeamUsage;
5019
+ var fmtUsd2, monoSx, MemberUsageTable, TeamCard, TeamUsage;
5014
5020
  var init_TeamUsage = __esm({
5015
5021
  "src/components/TeamUsage.tsx"() {
5016
5022
  "use strict";
5017
5023
  init_ui();
5018
5024
  fmtUsd2 = (n) => `$${(n ?? 0).toFixed(2)}`;
5019
- TeamCard = ({ team, usage, usageLoading, canManage, onEditTeam, canManageMembers, onManageMembers, onExpand }) => {
5025
+ monoSx = { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 11 };
5026
+ MemberUsageTable = ({ usage }) => {
5027
+ const hidden = usage.budget_hidden === true;
5028
+ if (!usage.members.length) {
5029
+ return /* @__PURE__ */ jsx15(Typography12, { variant: "body2", color: "text.secondary", children: "No members or activity in this period." });
5030
+ }
5031
+ return /* @__PURE__ */ jsx15(TableContainer4, { component: Paper8, variant: "outlined", sx: { borderRadius: 1.5 }, children: /* @__PURE__ */ jsxs14(Table4, { size: "small", sx: dataTableSx, "aria-label": "Usage by member", children: [
5032
+ /* @__PURE__ */ jsx15(TableHead4, { children: /* @__PURE__ */ jsxs14(TableRow4, { children: [
5033
+ /* @__PURE__ */ jsx15(TableCell4, { children: "Member" }),
5034
+ !hidden && /* @__PURE__ */ jsx15(TableCell4, { align: "right", children: "Spend" }),
5035
+ /* @__PURE__ */ jsx15(TableCell4, { align: "right", children: "Share" }),
5036
+ /* @__PURE__ */ jsx15(TableCell4, { align: "right", children: "Tokens" }),
5037
+ /* @__PURE__ */ jsx15(TableCell4, { align: "right", children: "Requests" }),
5038
+ /* @__PURE__ */ jsx15(TableCell4, { align: "right", children: "Success" }),
5039
+ /* @__PURE__ */ jsx15(TableCell4, { align: "right", children: "Keys" })
5040
+ ] }) }),
5041
+ /* @__PURE__ */ jsx15(TableBody4, { children: usage.members.map((m) => {
5042
+ const name = m.display_name ?? m.user_email ?? m.user_id;
5043
+ let sub = null;
5044
+ if (m.display_name) sub = m.user_email ?? m.user_id;
5045
+ else if (m.user_email && m.user_id !== m.user_email) sub = m.user_id;
5046
+ return /* @__PURE__ */ jsxs14(TableRow4, { children: [
5047
+ /* @__PURE__ */ jsx15(TableCell4, { children: m.user_id === null ? /* @__PURE__ */ jsx15(MuiTooltip, { title: "Activity of keys that were deleted or have no owner", children: /* @__PURE__ */ jsx15(Typography12, { variant: "body2", color: "text.secondary", sx: { fontStyle: "italic" }, children: "Unattributed" }) }) : /* @__PURE__ */ jsxs14(Fragment8, { children: [
5048
+ /* @__PURE__ */ jsx15(Typography12, { variant: "body2", children: name }),
5049
+ sub && /* @__PURE__ */ jsx15(Typography12, { variant: "caption", color: "text.secondary", sx: monoSx, children: sub })
5050
+ ] }) }),
5051
+ !hidden && /* @__PURE__ */ jsx15(TableCell4, { align: "right", children: fmtUsd2(m.spend) }),
5052
+ /* @__PURE__ */ jsx15(TableCell4, { align: "right", children: `${m.spend_share_pct.toFixed(m.spend_share_pct > 0 && m.spend_share_pct < 1 ? 1 : 0)}%` }),
5053
+ /* @__PURE__ */ jsx15(TableCell4, { align: "right", children: fmtCompact(m.total_tokens) }),
5054
+ /* @__PURE__ */ jsx15(TableCell4, { align: "right", children: m.api_requests.toLocaleString() }),
5055
+ /* @__PURE__ */ jsx15(TableCell4, { align: "right", children: m.success_rate === null ? "\u2014" : `${m.success_rate.toFixed(0)}%` }),
5056
+ /* @__PURE__ */ jsx15(TableCell4, { align: "right", children: m.user_id === null ? "\u2014" : m.key_count })
5057
+ ] }, m.user_id ?? "__unattributed");
5058
+ }) })
5059
+ ] }) });
5060
+ };
5061
+ TeamCard = ({ team, usage, usageLoading, memberUsage, memberUsageLoading, canManage, onEditTeam, canManageMembers, onManageMembers, onExpand }) => {
5020
5062
  const [expanded, setExpanded] = useState9(false);
5021
5063
  const chart = useChartTheme();
5022
5064
  const handleExpandChange = (newExpanded) => {
@@ -5209,6 +5251,10 @@ var init_TeamUsage = __esm({
5209
5251
  team.models?.length ? /* @__PURE__ */ jsx15(Box14, { display: "flex", gap: 0.5, flexWrap: "wrap", children: team.models.map((m) => /* @__PURE__ */ jsx15(TagChip, { label: m, title: m }, m)) }) : /* @__PURE__ */ jsx15(Typography12, { variant: "body2", color: "text.secondary", children: "All models allowed" })
5210
5252
  ] }),
5211
5253
  renderDailySpendSection(),
5254
+ (memberUsageLoading || memberUsage) && /* @__PURE__ */ jsxs14(Box14, { mb: 2.5, children: [
5255
+ sectionLabel("Usage by member"),
5256
+ memberUsage ? /* @__PURE__ */ jsx15(MemberUsageTable, { usage: memberUsage }) : /* @__PURE__ */ jsx15(Box14, { display: "flex", justifyContent: "center", py: 2, children: /* @__PURE__ */ jsx15(CircularProgress4, { size: 22 }) })
5257
+ ] }),
5212
5258
  /* @__PURE__ */ jsxs14(Box14, { children: [
5213
5259
  sectionLabel("Members"),
5214
5260
  team.members_with_roles?.length ? /* @__PURE__ */ jsx15(
@@ -5263,6 +5309,8 @@ var init_TeamUsage = __esm({
5263
5309
  loading,
5264
5310
  getTeamUsage,
5265
5311
  getTeamUsageLoading,
5312
+ getTeamMemberUsage,
5313
+ getTeamMemberUsageLoading,
5266
5314
  canManage,
5267
5315
  onEditTeam,
5268
5316
  canCreate,
@@ -5322,6 +5370,8 @@ var init_TeamUsage = __esm({
5322
5370
  team,
5323
5371
  usage: getTeamUsage(team.team_id),
5324
5372
  usageLoading: getTeamUsageLoading(team.team_id),
5373
+ memberUsage: getTeamMemberUsage?.(team.team_id) ?? null,
5374
+ memberUsageLoading: getTeamMemberUsageLoading?.(team.team_id) ?? false,
5325
5375
  canManage,
5326
5376
  onEditTeam,
5327
5377
  canManageMembers: canManageMembers?.(team),
@@ -6134,6 +6184,8 @@ var init_LiteLLMPage = __esm({
6134
6184
  const keyFormOpen = generateDialogOpen || !!keyToEdit;
6135
6185
  const [teamUsageCache, setTeamUsageCache] = useState12({});
6136
6186
  const [teamUsageLoading, setTeamUsageLoading] = useState12({});
6187
+ const [memberUsageCache, setMemberUsageCache] = useState12({});
6188
+ const [memberUsageLoading, setMemberUsageLoading] = useState12({});
6137
6189
  const { value: userInfo, loading: userLoading, error: userError } = useAsync2(
6138
6190
  () => api.getUserInfo(),
6139
6191
  [api]
@@ -6194,6 +6246,8 @@ var init_LiteLLMPage = __esm({
6194
6246
  }
6195
6247
  setTeamUsageCache({});
6196
6248
  setTeamUsageLoading({});
6249
+ setMemberUsageCache({});
6250
+ setMemberUsageLoading({});
6197
6251
  }, [setDateRange, setCurrentPreset]);
6198
6252
  const loadTeamUsage = useCallback4(async (teamId) => {
6199
6253
  if (teamUsageCache[teamId] !== void 0 || teamUsageLoading[teamId]) return;
@@ -6209,6 +6263,20 @@ var init_LiteLLMPage = __esm({
6209
6263
  setTeamUsageLoading((prev) => ({ ...prev, [teamId]: false }));
6210
6264
  }
6211
6265
  }, [api, dateRange, teamUsageCache, teamUsageLoading]);
6266
+ const loadMemberUsage = useCallback4(async (teamId) => {
6267
+ if (memberUsageCache[teamId] !== void 0 || memberUsageLoading[teamId]) return;
6268
+ setMemberUsageLoading((prev) => ({ ...prev, [teamId]: true }));
6269
+ try {
6270
+ const startDate = toLocalDay(dateRange.start);
6271
+ const endDate = toLocalDay(dateRange.end);
6272
+ const data = await api.getTeamMemberUsage(teamId, startDate, endDate);
6273
+ setMemberUsageCache((prev) => ({ ...prev, [teamId]: data }));
6274
+ } catch {
6275
+ setMemberUsageCache((prev) => ({ ...prev, [teamId]: null }));
6276
+ } finally {
6277
+ setMemberUsageLoading((prev) => ({ ...prev, [teamId]: false }));
6278
+ }
6279
+ }, [api, dateRange, memberUsageCache, memberUsageLoading]);
6212
6280
  const allowedModels = useMemo9(() => {
6213
6281
  if (!allModels?.length) return [];
6214
6282
  const userModels = userInfo?.models;
@@ -6503,6 +6571,8 @@ var init_LiteLLMPage = __esm({
6503
6571
  loading: teamsLoading,
6504
6572
  getTeamUsage: (teamId) => teamUsageCache[teamId] ?? null,
6505
6573
  getTeamUsageLoading: (teamId) => teamUsageLoading[teamId] ?? false,
6574
+ getTeamMemberUsage: (teamId) => memberUsageCache[teamId] ?? null,
6575
+ getTeamMemberUsageLoading: (teamId) => memberUsageLoading[teamId] ?? false,
6506
6576
  canManage: teamMgmtEnabled && canManageTeam && !readOnly,
6507
6577
  onEditTeam: (t) => setManageTeam({ mode: "edit", team: t }),
6508
6578
  canCreate: teamMgmtEnabled && canCreateTeam && !readOnly,
@@ -6510,7 +6580,10 @@ var init_LiteLLMPage = __esm({
6510
6580
  canManageMembers: (t) => teamMgmtEnabled && canManageMembers && isTeamMemberManager(t, userInfo?.user_id, memberManagerRoles, readOnly),
6511
6581
  onManageMembers: (t) => setManageTeam({ mode: "members", team: t }),
6512
6582
  readOnly,
6513
- onTeamExpand: (team) => loadTeamUsage(team.team_id)
6583
+ onTeamExpand: (team) => {
6584
+ loadTeamUsage(team.team_id);
6585
+ if (liteLlmConfig?.teamUsage?.memberBreakdown) loadMemberUsage(team.team_id);
6586
+ }
6514
6587
  }
6515
6588
  );
6516
6589
  })(),