@acarmisc/backstage-plugin-litellm 0.44.0 → 0.46.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
@@ -5084,47 +5084,70 @@ function budgetStatValue(hidden, budget) {
5084
5084
  if (budget > 0) return fmtUsd2(budget);
5085
5085
  return "Unlimited";
5086
5086
  }
5087
- var fmtUsd2, monoSx, MemberUsageTable, TeamKeyUsageTable, TeamCard, TeamUsage;
5087
+ var fmtUsd2, monoSx, fmtShare, MemberUsageCards, TeamKeyUsageTable, TeamCard, TeamUsage;
5088
5088
  var init_TeamUsage = __esm({
5089
5089
  "src/components/TeamUsage.tsx"() {
5090
5090
  "use strict";
5091
5091
  init_ui();
5092
5092
  fmtUsd2 = (n) => `$${(n ?? 0).toFixed(2)}`;
5093
5093
  monoSx = { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 11 };
5094
- MemberUsageTable = ({ usage }) => {
5094
+ fmtShare = (pct) => `${pct.toFixed(pct > 0 && pct < 1 ? 1 : 0)}%`;
5095
+ MemberUsageCards = ({ usage }) => {
5095
5096
  const hidden = usage.budget_hidden === true;
5096
5097
  if (!usage.members.length) {
5097
5098
  return /* @__PURE__ */ jsx15(Typography12, { variant: "body2", color: "text.secondary", children: "No members or activity in this period." });
5098
5099
  }
5099
- 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: [
5100
- /* @__PURE__ */ jsx15(TableHead4, { children: /* @__PURE__ */ jsxs14(TableRow4, { children: [
5101
- /* @__PURE__ */ jsx15(TableCell4, { children: "Member" }),
5102
- !hidden && /* @__PURE__ */ jsx15(TableCell4, { align: "right", children: "Spend" }),
5103
- /* @__PURE__ */ jsx15(TableCell4, { align: "right", children: "Share" }),
5104
- /* @__PURE__ */ jsx15(TableCell4, { align: "right", children: "Tokens" }),
5105
- /* @__PURE__ */ jsx15(TableCell4, { align: "right", children: "Requests" }),
5106
- /* @__PURE__ */ jsx15(TableCell4, { align: "right", children: "Success" }),
5107
- /* @__PURE__ */ jsx15(TableCell4, { align: "right", children: "Keys" })
5108
- ] }) }),
5109
- /* @__PURE__ */ jsx15(TableBody4, { children: usage.members.map((m) => {
5110
- const name = m.display_name ?? m.user_email ?? m.user_id;
5111
- let sub = null;
5112
- if (m.display_name) sub = m.user_email ?? m.user_id;
5113
- else if (m.user_email && m.user_id !== m.user_email) sub = m.user_id;
5114
- return /* @__PURE__ */ jsxs14(TableRow4, { children: [
5115
- /* @__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: [
5116
- /* @__PURE__ */ jsx15(Typography12, { variant: "body2", children: name }),
5117
- sub && /* @__PURE__ */ jsx15(Typography12, { variant: "caption", color: "text.secondary", sx: monoSx, children: sub })
5118
- ] }) }),
5119
- !hidden && /* @__PURE__ */ jsx15(TableCell4, { align: "right", children: fmtUsd2(m.spend) }),
5120
- /* @__PURE__ */ jsx15(TableCell4, { align: "right", children: `${m.spend_share_pct.toFixed(m.spend_share_pct > 0 && m.spend_share_pct < 1 ? 1 : 0)}%` }),
5121
- /* @__PURE__ */ jsx15(TableCell4, { align: "right", children: fmtCompact(m.total_tokens) }),
5122
- /* @__PURE__ */ jsx15(TableCell4, { align: "right", children: m.api_requests.toLocaleString() }),
5123
- /* @__PURE__ */ jsx15(TableCell4, { align: "right", children: m.success_rate === null ? "\u2014" : `${m.success_rate.toFixed(0)}%` }),
5124
- /* @__PURE__ */ jsx15(TableCell4, { align: "right", children: m.user_id === null ? "\u2014" : m.key_count })
5125
- ] }, m.user_id ?? "__unattributed");
5126
- }) })
5127
- ] }) });
5100
+ const people = usage.members.filter((m) => m.user_id !== null).length;
5101
+ return /* @__PURE__ */ jsxs14(Box14, { children: [
5102
+ !hidden && people > 0 && /* @__PURE__ */ jsx15(Typography12, { variant: "caption", color: "text.secondary", sx: { display: "block", mb: 1 }, children: `Team spend ${fmtUsd2(usage.total_spend)} \xB7 avg ${fmtUsd2(usage.total_spend / people)} per member` }),
5103
+ /* @__PURE__ */ jsx15(
5104
+ Box14,
5105
+ {
5106
+ role: "list",
5107
+ "aria-label": "Usage by member",
5108
+ sx: { display: "grid", gap: 1.5, gridTemplateColumns: "repeat(auto-fill, minmax(230px, 1fr))" },
5109
+ children: usage.members.map((m) => {
5110
+ const name = m.display_name ?? m.user_email ?? m.user_id;
5111
+ let sub = null;
5112
+ if (m.display_name) sub = m.user_email ?? m.user_id;
5113
+ else if (m.user_email && m.user_id !== m.user_email) sub = m.user_id;
5114
+ const stats = [
5115
+ ["Tokens", fmtCompact(m.total_tokens)],
5116
+ ["Requests", m.api_requests.toLocaleString()],
5117
+ ["Success", m.success_rate === null ? "\u2014" : `${m.success_rate.toFixed(0)}%`],
5118
+ ["Keys", m.user_id === null ? "\u2014" : String(m.key_count)]
5119
+ ];
5120
+ return /* @__PURE__ */ jsxs14(
5121
+ Paper8,
5122
+ {
5123
+ role: "listitem",
5124
+ variant: "outlined",
5125
+ sx: { borderRadius: 1.5, p: 1.5, minWidth: 0 },
5126
+ children: [
5127
+ /* @__PURE__ */ jsxs14(Box14, { display: "flex", justifyContent: "space-between", alignItems: "flex-start", gap: 1, children: [
5128
+ /* @__PURE__ */ jsx15(Box14, { minWidth: 0, 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: [
5129
+ /* @__PURE__ */ jsx15(Typography12, { variant: "body2", fontWeight: 600, noWrap: true, children: name }),
5130
+ sub && /* @__PURE__ */ jsx15(Typography12, { variant: "caption", color: "text.secondary", noWrap: true, sx: { ...monoSx, display: "block" }, children: sub })
5131
+ ] }) }),
5132
+ m.role && /* @__PURE__ */ jsx15(StatusPill, { label: m.role, tone: m.role === "admin" ? "accent" : "neutral", dot: false })
5133
+ ] }),
5134
+ /* @__PURE__ */ jsxs14(Box14, { mt: 1.5, display: "flex", alignItems: "baseline", justifyContent: "space-between", children: [
5135
+ /* @__PURE__ */ jsx15(Typography12, { variant: "h6", component: "span", sx: { lineHeight: 1.2 }, children: hidden ? fmtShare(m.spend_share_pct) : fmtUsd2(m.spend) }),
5136
+ /* @__PURE__ */ jsx15(Typography12, { variant: "caption", color: "text.secondary", children: hidden ? "of team usage" : `${fmtShare(m.spend_share_pct)} of team` })
5137
+ ] }),
5138
+ /* @__PURE__ */ jsx15(Box14, { mt: 0.75, children: /* @__PURE__ */ jsx15(Meter, { value: m.spend_share_pct, ariaLabel: `${name ?? "Unattributed"} share of team spend` }) }),
5139
+ /* @__PURE__ */ jsx15(Box14, { mt: 1.5, display: "grid", gridTemplateColumns: "repeat(4, 1fr)", gap: 0.5, children: stats.map(([label, value]) => /* @__PURE__ */ jsxs14(Box14, { children: [
5140
+ /* @__PURE__ */ jsx15(Typography12, { variant: "caption", color: "text.secondary", display: "block", children: label }),
5141
+ /* @__PURE__ */ jsx15(Typography12, { variant: "body2", children: value })
5142
+ ] }, label)) })
5143
+ ]
5144
+ },
5145
+ m.user_id ?? "__unattributed"
5146
+ );
5147
+ })
5148
+ }
5149
+ )
5150
+ ] });
5128
5151
  };
5129
5152
  TeamKeyUsageTable = ({ usage }) => {
5130
5153
  const hidden = usage.budget_hidden === true;
@@ -5166,7 +5189,7 @@ var init_TeamUsage = __esm({
5166
5189
  }) })
5167
5190
  ] }) });
5168
5191
  };
5169
- TeamCard = ({ team, usage, usageLoading, memberUsage, memberUsageLoading, keyUsage, keyUsageLoading, canManage, onEditTeam, canManageMembers, onManageMembers, onExpand, vectorStores, mcpServers }) => {
5192
+ TeamCard = ({ team, usage, usageLoading, memberUsage, memberUsageLoading, memberUsageFailed, keyUsage, keyUsageLoading, keyUsageFailed, canManage, onEditTeam, canManageMembers, onManageMembers, onExpand, vectorStores, mcpServers }) => {
5170
5193
  const [expanded, setExpanded] = useState9(false);
5171
5194
  const chart = useChartTheme();
5172
5195
  const handleExpandChange = (newExpanded) => {
@@ -5365,15 +5388,19 @@ var init_TeamUsage = __esm({
5365
5388
  renderNamedTags("Knowledge bases", team.object_permission?.vector_stores, vectorStores),
5366
5389
  renderNamedTags("MCP servers", team.object_permission?.mcp_servers, mcpServers),
5367
5390
  renderDailySpendSection(),
5368
- (memberUsageLoading || memberUsage) && /* @__PURE__ */ jsxs14(Box14, { mb: 2.5, children: [
5391
+ (memberUsageLoading || memberUsage || memberUsageFailed) && /* @__PURE__ */ jsxs14(Box14, { mb: 2.5, children: [
5369
5392
  sectionLabel("Usage by member"),
5370
- memberUsage ? /* @__PURE__ */ jsx15(MemberUsageTable, { usage: memberUsage }) : /* @__PURE__ */ jsx15(Box14, { display: "flex", justifyContent: "center", py: 2, children: /* @__PURE__ */ jsx15(CircularProgress4, { size: 22 }) })
5393
+ memberUsage && /* @__PURE__ */ jsx15(MemberUsageCards, { usage: memberUsage }),
5394
+ !memberUsage && memberUsageFailed && /* @__PURE__ */ jsx15(Typography12, { variant: "caption", color: "text.secondary", children: "Could not load per-member usage" }),
5395
+ !memberUsage && !memberUsageFailed && /* @__PURE__ */ jsx15(Box14, { display: "flex", justifyContent: "center", py: 2, children: /* @__PURE__ */ jsx15(CircularProgress4, { size: 22 }) })
5371
5396
  ] }),
5372
- (keyUsageLoading || keyUsage) && /* @__PURE__ */ jsxs14(Box14, { mb: 2.5, children: [
5397
+ (keyUsageLoading || keyUsage || keyUsageFailed) && /* @__PURE__ */ jsxs14(Box14, { mb: 2.5, children: [
5373
5398
  sectionLabel("Keys by member"),
5374
- keyUsage ? /* @__PURE__ */ jsx15(TeamKeyUsageTable, { usage: keyUsage }) : /* @__PURE__ */ jsx15(Box14, { display: "flex", justifyContent: "center", py: 2, children: /* @__PURE__ */ jsx15(CircularProgress4, { size: 22 }) })
5399
+ keyUsage && /* @__PURE__ */ jsx15(TeamKeyUsageTable, { usage: keyUsage }),
5400
+ !keyUsage && keyUsageFailed && /* @__PURE__ */ jsx15(Typography12, { variant: "caption", color: "text.secondary", children: "Could not load per-key usage" }),
5401
+ !keyUsage && !keyUsageFailed && /* @__PURE__ */ jsx15(Box14, { display: "flex", justifyContent: "center", py: 2, children: /* @__PURE__ */ jsx15(CircularProgress4, { size: 22 }) })
5375
5402
  ] }),
5376
- /* @__PURE__ */ jsxs14(Box14, { children: [
5403
+ !memberUsage && /* @__PURE__ */ jsxs14(Box14, { children: [
5377
5404
  sectionLabel("Members"),
5378
5405
  team.members_with_roles?.length ? /* @__PURE__ */ jsx15(
5379
5406
  TableContainer4,
@@ -5431,6 +5458,8 @@ var init_TeamUsage = __esm({
5431
5458
  getTeamMemberUsageLoading,
5432
5459
  getTeamKeyUsage,
5433
5460
  getTeamKeyUsageLoading,
5461
+ getTeamMemberUsageFailed,
5462
+ getTeamKeyUsageFailed,
5434
5463
  canManage,
5435
5464
  onEditTeam,
5436
5465
  canCreate,
@@ -5496,6 +5525,8 @@ var init_TeamUsage = __esm({
5496
5525
  memberUsageLoading: getTeamMemberUsageLoading?.(team.team_id) ?? false,
5497
5526
  keyUsage: getTeamKeyUsage?.(team.team_id) ?? null,
5498
5527
  keyUsageLoading: getTeamKeyUsageLoading?.(team.team_id) ?? false,
5528
+ memberUsageFailed: getTeamMemberUsageFailed?.(team.team_id) ?? false,
5529
+ keyUsageFailed: getTeamKeyUsageFailed?.(team.team_id) ?? false,
5499
5530
  canManage,
5500
5531
  onEditTeam,
5501
5532
  canManageMembers: canManageMembers?.(team),
@@ -6344,7 +6375,7 @@ function initDateRange() {
6344
6375
  else start.setDate(start.getDate() - 7);
6345
6376
  return { start, end };
6346
6377
  }
6347
- var PERIOD_LS_KEY2, PAGE_TABS, isPageTab, LiteLLMPage;
6378
+ var PERIOD_LS_KEY2, PAGE_TABS, isPageTab, isRefusal, LiteLLMPage;
6348
6379
  var init_LiteLLMPage = __esm({
6349
6380
  "src/components/LiteLLMPage.tsx"() {
6350
6381
  "use strict";
@@ -6365,6 +6396,7 @@ var init_LiteLLMPage = __esm({
6365
6396
  PERIOD_LS_KEY2 = "litellm_usage_period";
6366
6397
  PAGE_TABS = ["overview", "keys", "teams", "models", "insights"];
6367
6398
  isPageTab = (v) => !!v && PAGE_TABS.includes(v);
6399
+ isRefusal = (err) => err instanceof ApiError && (err.status === 403 || err.status === 404);
6368
6400
  LiteLLMPage = () => {
6369
6401
  const api = useApi4(liteLlmApiRef);
6370
6402
  const alertApi = useApi4(alertApiRef2);
@@ -6450,6 +6482,8 @@ var init_LiteLLMPage = __esm({
6450
6482
  const [memberUsageLoading, setMemberUsageLoading] = useState11({});
6451
6483
  const [keyUsageCache, setKeyUsageCache] = useState11({});
6452
6484
  const [keyUsageLoading, setKeyUsageLoading] = useState11({});
6485
+ const [memberUsageFailed, setMemberUsageFailed] = useState11({});
6486
+ const [keyUsageFailed, setKeyUsageFailed] = useState11({});
6453
6487
  const { value: userInfo, loading: userLoading, error: userError } = useAsync(
6454
6488
  () => api.getUserInfo(),
6455
6489
  [api]
@@ -6518,6 +6552,8 @@ var init_LiteLLMPage = __esm({
6518
6552
  setMemberUsageLoading({});
6519
6553
  setKeyUsageCache({});
6520
6554
  setKeyUsageLoading({});
6555
+ setMemberUsageFailed({});
6556
+ setKeyUsageFailed({});
6521
6557
  }, [setDateRange, setCurrentPreset]);
6522
6558
  const loadTeamUsage = useCallback4(async (teamId) => {
6523
6559
  if (teamUsageCache[teamId] !== void 0 || teamUsageLoading[teamId]) return;
@@ -6541,8 +6577,9 @@ var init_LiteLLMPage = __esm({
6541
6577
  const endDate = toLocalDay(dateRange.end);
6542
6578
  const data = await api.getTeamMemberUsage(teamId, startDate, endDate);
6543
6579
  setMemberUsageCache((prev) => ({ ...prev, [teamId]: data }));
6544
- } catch {
6580
+ } catch (err) {
6545
6581
  setMemberUsageCache((prev) => ({ ...prev, [teamId]: null }));
6582
+ setMemberUsageFailed((prev) => ({ ...prev, [teamId]: !isRefusal(err) }));
6546
6583
  } finally {
6547
6584
  setMemberUsageLoading((prev) => ({ ...prev, [teamId]: false }));
6548
6585
  }
@@ -6555,8 +6592,9 @@ var init_LiteLLMPage = __esm({
6555
6592
  const endDate = toLocalDay(dateRange.end);
6556
6593
  const data = await api.getTeamKeyUsage(teamId, startDate, endDate);
6557
6594
  setKeyUsageCache((prev) => ({ ...prev, [teamId]: data }));
6558
- } catch {
6595
+ } catch (err) {
6559
6596
  setKeyUsageCache((prev) => ({ ...prev, [teamId]: null }));
6597
+ setKeyUsageFailed((prev) => ({ ...prev, [teamId]: !isRefusal(err) }));
6560
6598
  } finally {
6561
6599
  setKeyUsageLoading((prev) => ({ ...prev, [teamId]: false }));
6562
6600
  }
@@ -6860,6 +6898,8 @@ var init_LiteLLMPage = __esm({
6860
6898
  getTeamMemberUsageLoading: (teamId) => memberUsageLoading[teamId] ?? false,
6861
6899
  getTeamKeyUsage: (teamId) => keyUsageCache[teamId] ?? null,
6862
6900
  getTeamKeyUsageLoading: (teamId) => keyUsageLoading[teamId] ?? false,
6901
+ getTeamMemberUsageFailed: (teamId) => memberUsageFailed[teamId] ?? false,
6902
+ getTeamKeyUsageFailed: (teamId) => keyUsageFailed[teamId] ?? false,
6863
6903
  canManage: teamMgmtEnabled && canManageTeam && !readOnly,
6864
6904
  onEditTeam: (t) => setManageTeam({ mode: "edit", team: t }),
6865
6905
  canCreate: teamMgmtEnabled && canCreateTeam && !readOnly,