@acarmisc/backstage-plugin-litellm 0.17.0 → 0.18.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
@@ -1920,7 +1920,7 @@ var init_GenerateKeyDialog = __esm({
1920
1920
  });
1921
1921
 
1922
1922
  // src/components/ManageTeamDialog.tsx
1923
- import React5, { useState as useState3, useEffect as useEffect2 } from "react";
1923
+ import React5, { useState as useState3, useEffect as useEffect2, useMemo as useMemo4 } from "react";
1924
1924
  import Dialog3 from "@mui/material/Dialog";
1925
1925
  import DialogTitle3 from "@mui/material/DialogTitle";
1926
1926
  import DialogContent3 from "@mui/material/DialogContent";
@@ -1931,19 +1931,29 @@ import Alert2 from "@mui/material/Alert";
1931
1931
  import Checkbox2 from "@mui/material/Checkbox";
1932
1932
  import FormControlLabel2 from "@mui/material/FormControlLabel";
1933
1933
  import Autocomplete3 from "@mui/material/Autocomplete";
1934
+ import CircularProgress3 from "@mui/material/CircularProgress";
1934
1935
  import MenuItem2 from "@mui/material/MenuItem";
1935
1936
  import Box5 from "@mui/material/Box";
1936
1937
  import Divider2 from "@mui/material/Divider";
1937
1938
  import Typography5 from "@mui/material/Typography";
1938
1939
  import IconButton3 from "@mui/material/IconButton";
1939
- import List from "@mui/material/List";
1940
- import ListItem from "@mui/material/ListItem";
1941
- import ListItemText from "@mui/material/ListItemText";
1940
+ import Paper3 from "@mui/material/Paper";
1941
+ import Table2 from "@mui/material/Table";
1942
+ import TableBody2 from "@mui/material/TableBody";
1943
+ import TableCell2 from "@mui/material/TableCell";
1944
+ import TableContainer2 from "@mui/material/TableContainer";
1945
+ import TableHead2 from "@mui/material/TableHead";
1946
+ import TableRow2 from "@mui/material/TableRow";
1942
1947
  import { Delete as DeleteIcon } from "@mui/icons-material";
1948
+ import { useApi } from "@backstage/core-plugin-api";
1949
+ import { catalogApiRef } from "@backstage/plugin-catalog-react";
1950
+ import { stringifyEntityRef } from "@backstage/catalog-model";
1951
+ import { useAsync } from "react-use";
1943
1952
  var BUDGET_DURATION_OPTIONS, DEFAULT_BUDGET_DURATION, FALLBACK_BUDGET_CEILING, ManageTeamDialog;
1944
1953
  var init_ManageTeamDialog = __esm({
1945
1954
  "src/components/ManageTeamDialog.tsx"() {
1946
1955
  "use strict";
1956
+ init_ui();
1947
1957
  BUDGET_DURATION_OPTIONS = [
1948
1958
  { value: "1d", label: "Daily (1d)" },
1949
1959
  { value: "7d", label: "Weekly (7d)" },
@@ -1978,6 +1988,7 @@ var init_ManageTeamDialog = __esm({
1978
1988
  const [budgetDuration, setBudgetDuration] = useState3("");
1979
1989
  const [submitting, setSubmitting] = useState3(false);
1980
1990
  const [error, setError] = useState3(null);
1991
+ const [memberQuery, setMemberQuery] = useState3("");
1981
1992
  const [memberRef, setMemberRef] = useState3("");
1982
1993
  const [memberBudget, setMemberBudget] = useState3("");
1983
1994
  const [memberBusy, setMemberBusy] = useState3(false);
@@ -1990,6 +2001,36 @@ var init_ManageTeamDialog = __esm({
1990
2001
  const [mcpError, setMcpError] = useState3(null);
1991
2002
  const allowUnlimitedBudget = config?.teamManagement?.allowUnlimitedBudget ?? false;
1992
2003
  const maxBudgetCeiling = config?.teamManagement?.maxBudgetCeiling ?? FALLBACK_BUDGET_CEILING;
2004
+ const members = useMemo4(() => team?.members_with_roles ?? [], [team]);
2005
+ const showMembers = mode === "edit" && !!canManageMembers;
2006
+ const catalogApi = useApi(catalogApiRef);
2007
+ const { value: catalogUsers, loading: usersLoading } = useAsync(async () => {
2008
+ if (!open || !showMembers) return [];
2009
+ try {
2010
+ const { items } = await catalogApi.getEntities({
2011
+ filter: { kind: "User" },
2012
+ fields: ["kind", "metadata.namespace", "metadata.name", "metadata.title", "spec.profile"]
2013
+ });
2014
+ return items;
2015
+ } catch {
2016
+ return [];
2017
+ }
2018
+ }, [open, showMembers, catalogApi]);
2019
+ const userOptions = useMemo4(() => {
2020
+ const existingEmails = new Set(
2021
+ members.map((m) => m.user_email?.toLowerCase()).filter((e) => !!e)
2022
+ );
2023
+ return (catalogUsers ?? []).map((e) => {
2024
+ const ref = stringifyEntityRef(e);
2025
+ const displayName = e.spec?.profile?.displayName || e.metadata.title || e.metadata.name;
2026
+ const email = e.spec?.profile?.email;
2027
+ return {
2028
+ ref,
2029
+ email,
2030
+ label: email ? `${displayName} (${email})` : displayName
2031
+ };
2032
+ }).filter((o) => !(o.email && existingEmails.has(o.email.toLowerCase()))).sort((a, b) => a.label.localeCompare(b.label));
2033
+ }, [catalogUsers, members]);
1993
2034
  useEffect2(() => {
1994
2035
  if (open) {
1995
2036
  if (mode === "edit" && team) {
@@ -2008,6 +2049,7 @@ var init_ManageTeamDialog = __esm({
2008
2049
  setBudgetDuration(DEFAULT_BUDGET_DURATION);
2009
2050
  }
2010
2051
  setError(null);
2052
+ setMemberQuery("");
2011
2053
  setMemberRef("");
2012
2054
  setMemberBudget("");
2013
2055
  setMemberError(null);
@@ -2079,6 +2121,7 @@ var init_ManageTeamDialog = __esm({
2079
2121
  try {
2080
2122
  setMemberBusy(true);
2081
2123
  await onAddMember(memberRef.trim(), budget);
2124
+ setMemberQuery("");
2082
2125
  setMemberRef("");
2083
2126
  setMemberBudget("");
2084
2127
  } catch (err) {
@@ -2125,8 +2168,6 @@ var init_ManageTeamDialog = __esm({
2125
2168
  };
2126
2169
  const modelNames = allModels.map((m) => m.model_name);
2127
2170
  const durationOptions = BUDGET_DURATION_OPTIONS.some((o) => o.value === budgetDuration) ? BUDGET_DURATION_OPTIONS : [...BUDGET_DURATION_OPTIONS, { value: budgetDuration, label: budgetDuration }];
2128
- const members = team?.members_with_roles ?? [];
2129
- const showMembers = mode === "edit" && !!canManageMembers;
2130
2171
  const showKnowledgeBases = mode === "edit" && !!canManageKnowledgeBases;
2131
2172
  const kbOptions = (vectorStores ?? []).map((v) => v.id);
2132
2173
  const kbLabel = (id) => (vectorStores ?? []).find((v) => v.id === id)?.name ?? id;
@@ -2150,7 +2191,15 @@ var init_ManageTeamDialog = __esm({
2150
2191
  value: models,
2151
2192
  onChange: (_, newValue) => setModels(newValue),
2152
2193
  disabled: submitting,
2153
- renderInput: (params) => /* @__PURE__ */ React5.createElement(TextField3, { ...params, label: "Models" })
2194
+ renderInput: (params) => /* @__PURE__ */ React5.createElement(
2195
+ TextField3,
2196
+ {
2197
+ ...params,
2198
+ label: "Models",
2199
+ required: true,
2200
+ helperText: "At least one model is required here \u2014 this delegated flow only grants access to models your admin has allow-listed for team creation, not every proxy model. (Teams with no model restriction shown elsewhere were configured directly in LiteLLM.)"
2201
+ }
2202
+ )
2154
2203
  }
2155
2204
  ), /* @__PURE__ */ React5.createElement(
2156
2205
  TextField3,
@@ -2182,34 +2231,69 @@ var init_ManageTeamDialog = __esm({
2182
2231
  fullWidth: true
2183
2232
  },
2184
2233
  durationOptions.map((o) => /* @__PURE__ */ React5.createElement(MenuItem2, { key: o.value, value: o.value }, o.label))
2185
- ), showMembers && /* @__PURE__ */ React5.createElement(Box5, null, /* @__PURE__ */ React5.createElement(Divider2, { sx: { my: 1 } }), /* @__PURE__ */ React5.createElement(Typography5, { variant: "subtitle2", sx: { mb: 1 } }, "Members"), memberError && /* @__PURE__ */ React5.createElement(Alert2, { severity: "error", sx: { mb: 1 }, onClose: () => setMemberError(null) }, memberError), members.length === 0 ? /* @__PURE__ */ React5.createElement(Typography5, { variant: "body2", color: "text.secondary" }, "No members yet.") : /* @__PURE__ */ React5.createElement(List, { dense: true, disablePadding: true }, members.map((m) => /* @__PURE__ */ React5.createElement(
2186
- ListItem,
2234
+ ), showMembers && /* @__PURE__ */ React5.createElement(Box5, null, /* @__PURE__ */ React5.createElement(Divider2, { sx: { my: 1 } }), /* @__PURE__ */ React5.createElement(Typography5, { variant: "subtitle2", sx: { mb: 1 } }, "Members"), memberError && /* @__PURE__ */ React5.createElement(Alert2, { severity: "error", sx: { mb: 1 }, onClose: () => setMemberError(null) }, memberError), members.length === 0 ? /* @__PURE__ */ React5.createElement(Typography5, { variant: "body2", color: "text.secondary", sx: { mb: 1 } }, "No members yet.") : /* @__PURE__ */ React5.createElement(TableContainer2, { component: Paper3, variant: "outlined", sx: { borderRadius: 1.5, mb: 1.5 } }, /* @__PURE__ */ React5.createElement(Table2, { size: "small", sx: dataTableSx }, /* @__PURE__ */ React5.createElement(TableHead2, null, /* @__PURE__ */ React5.createElement(TableRow2, null, /* @__PURE__ */ React5.createElement(TableCell2, null, "User"), /* @__PURE__ */ React5.createElement(TableCell2, { align: "right" }, "Role"), /* @__PURE__ */ React5.createElement(TableCell2, { align: "right", sx: { width: 40 } }))), /* @__PURE__ */ React5.createElement(TableBody2, null, members.map((m) => /* @__PURE__ */ React5.createElement(TableRow2, { key: m.user_id }, /* @__PURE__ */ React5.createElement(TableCell2, null, m.user_email ? /* @__PURE__ */ React5.createElement(React5.Fragment, null, /* @__PURE__ */ React5.createElement(Typography5, { variant: "body2" }, m.user_email), /* @__PURE__ */ React5.createElement(
2235
+ Typography5,
2187
2236
  {
2188
- key: m.user_id,
2189
- disableGutters: true,
2190
- secondaryAction: /* @__PURE__ */ React5.createElement(
2191
- IconButton3,
2192
- {
2193
- edge: "end",
2194
- size: "small",
2195
- "aria-label": `remove ${m.user_id}`,
2196
- disabled: memberBusy,
2197
- onClick: () => handleRemoveMember(m.user_id)
2198
- },
2199
- /* @__PURE__ */ React5.createElement(DeleteIcon, { fontSize: "small" })
2200
- )
2237
+ variant: "caption",
2238
+ color: "text.secondary",
2239
+ sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 11 }
2201
2240
  },
2202
- /* @__PURE__ */ React5.createElement(ListItemText, { primary: m.user_id, secondary: m.role })
2203
- ))), /* @__PURE__ */ React5.createElement(Box5, { sx: { display: "flex", gap: 1, alignItems: "flex-start", mt: 1 } }, /* @__PURE__ */ React5.createElement(
2204
- TextField3,
2241
+ m.user_id
2242
+ )) : /* @__PURE__ */ React5.createElement(
2243
+ Typography5,
2205
2244
  {
2206
- label: "Backstage user ref",
2207
- placeholder: "user:default/alice",
2208
- value: memberRef,
2209
- onChange: (e) => setMemberRef(e.target.value),
2210
- disabled: memberBusy,
2245
+ variant: "body2",
2246
+ sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace" }
2247
+ },
2248
+ m.user_id
2249
+ )), /* @__PURE__ */ React5.createElement(TableCell2, { align: "right" }, /* @__PURE__ */ React5.createElement(StatusPill, { label: m.role, tone: m.role === "admin" ? "accent" : "neutral", dot: false })), /* @__PURE__ */ React5.createElement(TableCell2, { align: "right" }, /* @__PURE__ */ React5.createElement(
2250
+ IconButton3,
2251
+ {
2252
+ edge: "end",
2211
2253
  size: "small",
2212
- sx: { flex: 1 }
2254
+ "aria-label": `remove ${m.user_id}`,
2255
+ disabled: memberBusy,
2256
+ onClick: () => handleRemoveMember(m.user_id)
2257
+ },
2258
+ /* @__PURE__ */ React5.createElement(DeleteIcon, { fontSize: "small" })
2259
+ ))))))), /* @__PURE__ */ React5.createElement(Box5, { sx: { display: "flex", gap: 1, alignItems: "flex-start", flexWrap: "wrap" } }, /* @__PURE__ */ React5.createElement(
2260
+ Autocomplete3,
2261
+ {
2262
+ freeSolo: true,
2263
+ autoHighlight: true,
2264
+ options: userOptions,
2265
+ loading: usersLoading,
2266
+ inputValue: memberQuery,
2267
+ onInputChange: (_, newInputValue, reason) => {
2268
+ setMemberQuery(newInputValue);
2269
+ if (reason === "input" || reason === "clear") {
2270
+ setMemberRef(newInputValue);
2271
+ }
2272
+ },
2273
+ onChange: (_, newValue) => {
2274
+ if (newValue && typeof newValue !== "string") {
2275
+ setMemberQuery(newValue.label);
2276
+ setMemberRef(newValue.ref);
2277
+ }
2278
+ },
2279
+ getOptionLabel: (o) => typeof o === "string" ? o : o.label,
2280
+ isOptionEqualToValue: (o, v) => o.ref === (typeof v === "string" ? v : v.ref),
2281
+ disabled: memberBusy,
2282
+ sx: { flex: 1, minWidth: 260 },
2283
+ renderInput: (params) => /* @__PURE__ */ React5.createElement(
2284
+ TextField3,
2285
+ {
2286
+ ...params,
2287
+ label: "Add member",
2288
+ placeholder: "Search by name or email\u2026",
2289
+ size: "small",
2290
+ helperText: "Pick a catalog user, or paste a user entity ref",
2291
+ InputProps: {
2292
+ ...params.InputProps,
2293
+ endAdornment: /* @__PURE__ */ React5.createElement(React5.Fragment, null, usersLoading ? /* @__PURE__ */ React5.createElement(CircularProgress3, { color: "inherit", size: 14 }) : null, params.InputProps.endAdornment)
2294
+ }
2295
+ }
2296
+ )
2213
2297
  }
2214
2298
  ), /* @__PURE__ */ React5.createElement(
2215
2299
  TextField3,
@@ -2275,19 +2359,19 @@ var init_ManageTeamDialog = __esm({
2275
2359
  });
2276
2360
 
2277
2361
  // src/components/UsageStats.tsx
2278
- import React6, { useMemo as useMemo4, useState as useState4 } from "react";
2279
- import Paper3 from "@mui/material/Paper";
2362
+ import React6, { useMemo as useMemo5, useState as useState4 } from "react";
2363
+ import Paper4 from "@mui/material/Paper";
2280
2364
  import Box6 from "@mui/material/Box";
2281
2365
  import Typography6 from "@mui/material/Typography";
2282
2366
  import TextField4 from "@mui/material/TextField";
2283
2367
  import MenuItem3 from "@mui/material/MenuItem";
2284
2368
  import Grid from "@mui/material/Grid";
2285
- import Table2 from "@mui/material/Table";
2286
- import TableBody2 from "@mui/material/TableBody";
2287
- import TableCell2 from "@mui/material/TableCell";
2288
- import TableContainer2 from "@mui/material/TableContainer";
2289
- import TableHead2 from "@mui/material/TableHead";
2290
- import TableRow2 from "@mui/material/TableRow";
2369
+ import Table3 from "@mui/material/Table";
2370
+ import TableBody3 from "@mui/material/TableBody";
2371
+ import TableCell3 from "@mui/material/TableCell";
2372
+ import TableContainer3 from "@mui/material/TableContainer";
2373
+ import TableHead3 from "@mui/material/TableHead";
2374
+ import TableRow3 from "@mui/material/TableRow";
2291
2375
  import Skeleton3 from "@mui/material/Skeleton";
2292
2376
  import {
2293
2377
  AreaChart,
@@ -2368,7 +2452,7 @@ var init_UsageStats = __esm({
2368
2452
  }
2369
2453
  onDateRangeChange({ start, end }, preset);
2370
2454
  };
2371
- const dailyData = useMemo4(
2455
+ const dailyData = useMemo5(
2372
2456
  () => (usage?.daily_usage ?? []).map((d) => ({
2373
2457
  date: d.date,
2374
2458
  spend: d.spend,
@@ -2381,21 +2465,21 @@ var init_UsageStats = __esm({
2381
2465
  })),
2382
2466
  [usage]
2383
2467
  );
2384
- const cumulativeData = useMemo4(() => {
2468
+ const cumulativeData = useMemo5(() => {
2385
2469
  let cum = 0;
2386
2470
  return dailyData.map((d) => {
2387
2471
  cum += d.spend;
2388
2472
  return { date: d.date, cumulative: cum };
2389
2473
  });
2390
2474
  }, [dailyData]);
2391
- const successRateData = useMemo4(
2475
+ const successRateData = useMemo5(
2392
2476
  () => dailyData.filter((d) => d.apiRequests > 0).map((d) => ({
2393
2477
  date: d.date,
2394
2478
  successRate: parseFloat((d.successfulRequests / d.apiRequests * 100).toFixed(1))
2395
2479
  })),
2396
2480
  [dailyData]
2397
2481
  );
2398
- const { modelSpendByDate, topModels: topSpendModels } = useMemo4(() => {
2482
+ const { modelSpendByDate, topModels: topSpendModels } = useMemo5(() => {
2399
2483
  const rows = usage?.daily_by_model ?? [];
2400
2484
  const modelTotals = {};
2401
2485
  for (const r of rows) modelTotals[r.model] = (modelTotals[r.model] ?? 0) + r.spend;
@@ -2410,7 +2494,7 @@ var init_UsageStats = __esm({
2410
2494
  const hasOther = sorted.some((r) => r.Other > 0);
2411
2495
  return { modelSpendByDate: sorted, topModels: hasOther ? [...top, "Other"] : top };
2412
2496
  }, [usage]);
2413
- const modelRows = useMemo4(() => {
2497
+ const modelRows = useMemo5(() => {
2414
2498
  const entries = Object.entries(usage?.usage_by_model ?? {}).map(([model, d]) => ({
2415
2499
  model,
2416
2500
  spend: d.total_spend,
@@ -2424,11 +2508,11 @@ var init_UsageStats = __esm({
2424
2508
  }));
2425
2509
  return selectedModel === "all" ? entries : entries.filter((e) => e.model === selectedModel);
2426
2510
  }, [usage, selectedModel]);
2427
- const topModelSpendBars = useMemo4(
2511
+ const topModelSpendBars = useMemo5(
2428
2512
  () => [...modelRows].sort((a, b) => b.spend - a.spend).slice(0, 10),
2429
2513
  [modelRows]
2430
2514
  );
2431
- const keyRows = useMemo4(
2515
+ const keyRows = useMemo5(
2432
2516
  () => Object.entries(usage?.usage_by_key ?? {}).map(([keyHash, d]) => ({
2433
2517
  keyHash,
2434
2518
  keyAlias: d.key_alias ?? keyHash.slice(0, 8),
@@ -2445,7 +2529,7 @@ var init_UsageStats = __esm({
2445
2529
  })),
2446
2530
  [usage]
2447
2531
  );
2448
- const topKeySpendBars = useMemo4(
2532
+ const topKeySpendBars = useMemo5(
2449
2533
  () => [...keyRows].sort((a, b) => b.spend - a.spend).slice(0, 10),
2450
2534
  [keyRows]
2451
2535
  );
@@ -2483,21 +2567,21 @@ var init_UsageStats = __esm({
2483
2567
  ];
2484
2568
  const renderKeyRows = () => {
2485
2569
  if (loading) {
2486
- return /* @__PURE__ */ React6.createElement(TableRow2, null, /* @__PURE__ */ React6.createElement(TableCell2, { colSpan: 7, sx: { py: 3 } }, /* @__PURE__ */ React6.createElement(Skeleton3, { variant: "rounded", height: 80 })));
2570
+ return /* @__PURE__ */ React6.createElement(TableRow3, null, /* @__PURE__ */ React6.createElement(TableCell3, { colSpan: 7, sx: { py: 3 } }, /* @__PURE__ */ React6.createElement(Skeleton3, { variant: "rounded", height: 80 })));
2487
2571
  }
2488
2572
  if (keyRows.length === 0) {
2489
- return /* @__PURE__ */ React6.createElement(TableRow2, null, /* @__PURE__ */ React6.createElement(TableCell2, { colSpan: 7 }, /* @__PURE__ */ React6.createElement(EmptyState, { message: "No key activity in this period" })));
2573
+ return /* @__PURE__ */ React6.createElement(TableRow3, null, /* @__PURE__ */ React6.createElement(TableCell3, { colSpan: 7 }, /* @__PURE__ */ React6.createElement(EmptyState, { message: "No key activity in this period" })));
2490
2574
  }
2491
- return [...keyRows].sort((a, b) => b.spend - a.spend || b.apiRequests - a.apiRequests).map((r) => /* @__PURE__ */ React6.createElement(TableRow2, { key: r.keyHash }, /* @__PURE__ */ React6.createElement(TableCell2, null, /* @__PURE__ */ React6.createElement(Typography6, { variant: "body2", sx: { fontWeight: 600 } }, r.keyAlias), r.teamId ? /* @__PURE__ */ React6.createElement(Typography6, { variant: "caption", color: "text.secondary" }, "team: ", r.teamId) : null), /* @__PURE__ */ React6.createElement(TableCell2, null, /* @__PURE__ */ React6.createElement(Box6, { display: "flex", gap: 0.5, flexWrap: "wrap" }, r.models.slice(0, 3).map((m) => /* @__PURE__ */ React6.createElement(TagChip, { key: m, label: m, title: m })), r.models.length > 3 && /* @__PURE__ */ React6.createElement(TagChip, { label: `+${r.models.length - 3}`, mono: false }))), /* @__PURE__ */ React6.createElement(TableCell2, { align: "right" }, fmtUsd(r.spend)), /* @__PURE__ */ React6.createElement(TableCell2, { align: "right" }, fmtInt(r.apiRequests)), /* @__PURE__ */ React6.createElement(TableCell2, { align: "right" }, fmtCompact(r.totalTokens)), /* @__PURE__ */ React6.createElement(TableCell2, { align: "right" }, fmtInt(r.failedRequests)), /* @__PURE__ */ React6.createElement(TableCell2, null, /* @__PURE__ */ React6.createElement(SuccessRateCell, { rate: r.successRate, requests: r.apiRequests }))));
2575
+ return [...keyRows].sort((a, b) => b.spend - a.spend || b.apiRequests - a.apiRequests).map((r) => /* @__PURE__ */ React6.createElement(TableRow3, { key: r.keyHash }, /* @__PURE__ */ React6.createElement(TableCell3, null, /* @__PURE__ */ React6.createElement(Typography6, { variant: "body2", sx: { fontWeight: 600 } }, r.keyAlias), r.teamId ? /* @__PURE__ */ React6.createElement(Typography6, { variant: "caption", color: "text.secondary" }, "team: ", r.teamId) : null), /* @__PURE__ */ React6.createElement(TableCell3, null, /* @__PURE__ */ React6.createElement(Box6, { display: "flex", gap: 0.5, flexWrap: "wrap" }, r.models.slice(0, 3).map((m) => /* @__PURE__ */ React6.createElement(TagChip, { key: m, label: m, title: m })), r.models.length > 3 && /* @__PURE__ */ React6.createElement(TagChip, { label: `+${r.models.length - 3}`, mono: false }))), /* @__PURE__ */ React6.createElement(TableCell3, { align: "right" }, fmtUsd(r.spend)), /* @__PURE__ */ React6.createElement(TableCell3, { align: "right" }, fmtInt(r.apiRequests)), /* @__PURE__ */ React6.createElement(TableCell3, { align: "right" }, fmtCompact(r.totalTokens)), /* @__PURE__ */ React6.createElement(TableCell3, { align: "right" }, fmtInt(r.failedRequests)), /* @__PURE__ */ React6.createElement(TableCell3, null, /* @__PURE__ */ React6.createElement(SuccessRateCell, { rate: r.successRate, requests: r.apiRequests }))));
2492
2576
  };
2493
2577
  const renderModelRows = () => {
2494
2578
  if (loading) {
2495
- return /* @__PURE__ */ React6.createElement(TableRow2, null, /* @__PURE__ */ React6.createElement(TableCell2, { colSpan: 8, sx: { py: 3 } }, /* @__PURE__ */ React6.createElement(Skeleton3, { variant: "rounded", height: 80 })));
2579
+ return /* @__PURE__ */ React6.createElement(TableRow3, null, /* @__PURE__ */ React6.createElement(TableCell3, { colSpan: 8, sx: { py: 3 } }, /* @__PURE__ */ React6.createElement(Skeleton3, { variant: "rounded", height: 80 })));
2496
2580
  }
2497
2581
  if (modelRows.length === 0) {
2498
- return /* @__PURE__ */ React6.createElement(TableRow2, null, /* @__PURE__ */ React6.createElement(TableCell2, { colSpan: 8 }, /* @__PURE__ */ React6.createElement(EmptyState, { message: "No model activity in this period" })));
2582
+ return /* @__PURE__ */ React6.createElement(TableRow3, null, /* @__PURE__ */ React6.createElement(TableCell3, { colSpan: 8 }, /* @__PURE__ */ React6.createElement(EmptyState, { message: "No model activity in this period" })));
2499
2583
  }
2500
- return [...modelRows].sort((a, b) => b.spend - a.spend || b.totalTokens - a.totalTokens).map((r) => /* @__PURE__ */ React6.createElement(TableRow2, { key: r.model }, /* @__PURE__ */ React6.createElement(TableCell2, null, /* @__PURE__ */ React6.createElement(Box6, { display: "flex", alignItems: "center", gap: 1 }, /* @__PURE__ */ React6.createElement(Box6, { sx: { width: 8, height: 8, borderRadius: "50%", bgcolor: chartColor(r.model), flexShrink: 0 } }), /* @__PURE__ */ React6.createElement(Typography6, { variant: "body2", sx: { fontWeight: 600 } }, r.model))), /* @__PURE__ */ React6.createElement(TableCell2, { align: "right" }, fmtUsd(r.spend)), /* @__PURE__ */ React6.createElement(TableCell2, { align: "right" }, fmtInt(r.apiRequests)), /* @__PURE__ */ React6.createElement(TableCell2, { align: "right" }, fmtInt(r.successfulRequests)), /* @__PURE__ */ React6.createElement(TableCell2, { align: "right" }, fmtInt(r.failedRequests)), /* @__PURE__ */ React6.createElement(TableCell2, { align: "right" }, fmtCompact(r.promptTokens)), /* @__PURE__ */ React6.createElement(TableCell2, { align: "right" }, fmtCompact(r.completionTokens)), /* @__PURE__ */ React6.createElement(TableCell2, null, /* @__PURE__ */ React6.createElement(SuccessRateCell, { rate: r.successRate, requests: r.apiRequests }))));
2584
+ return [...modelRows].sort((a, b) => b.spend - a.spend || b.totalTokens - a.totalTokens).map((r) => /* @__PURE__ */ React6.createElement(TableRow3, { key: r.model }, /* @__PURE__ */ React6.createElement(TableCell3, null, /* @__PURE__ */ React6.createElement(Box6, { display: "flex", alignItems: "center", gap: 1 }, /* @__PURE__ */ React6.createElement(Box6, { sx: { width: 8, height: 8, borderRadius: "50%", bgcolor: chartColor(r.model), flexShrink: 0 } }), /* @__PURE__ */ React6.createElement(Typography6, { variant: "body2", sx: { fontWeight: 600 } }, r.model))), /* @__PURE__ */ React6.createElement(TableCell3, { align: "right" }, fmtUsd(r.spend)), /* @__PURE__ */ React6.createElement(TableCell3, { align: "right" }, fmtInt(r.apiRequests)), /* @__PURE__ */ React6.createElement(TableCell3, { align: "right" }, fmtInt(r.successfulRequests)), /* @__PURE__ */ React6.createElement(TableCell3, { align: "right" }, fmtInt(r.failedRequests)), /* @__PURE__ */ React6.createElement(TableCell3, { align: "right" }, fmtCompact(r.promptTokens)), /* @__PURE__ */ React6.createElement(TableCell3, { align: "right" }, fmtCompact(r.completionTokens)), /* @__PURE__ */ React6.createElement(TableCell3, null, /* @__PURE__ */ React6.createElement(SuccessRateCell, { rate: r.successRate, requests: r.apiRequests }))));
2501
2585
  };
2502
2586
  const periodSelect = /* @__PURE__ */ React6.createElement(
2503
2587
  TextField4,
@@ -2650,7 +2734,7 @@ var init_UsageStats = __esm({
2650
2734
  height: Math.max(200, topModelSpendBars.length * 34)
2651
2735
  },
2652
2736
  /* @__PURE__ */ React6.createElement(ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ React6.createElement(BarChart, { data: topModelSpendBars, layout: "vertical", margin: { top: 0, right: 12, left: 0, bottom: 0 } }, /* @__PURE__ */ React6.createElement(CartesianGrid, { ...chart.grid, vertical: true, horizontal: false }), /* @__PURE__ */ React6.createElement(XAxis, { type: "number", tickFormatter: fmtCompact, ...chart.axis }), /* @__PURE__ */ React6.createElement(YAxis, { type: "category", dataKey: "model", width: 170, ...chart.axis, tick: { fontSize: 10.5, fill: chart.axis.tick.fill } }), /* @__PURE__ */ React6.createElement(Tooltip, { cursor: chart.cursor, content: /* @__PURE__ */ React6.createElement(ChartTooltip, { valueFormatter: fmtInt, labelFormatter: (l) => l }) }), /* @__PURE__ */ React6.createElement(Legend, { ...chart.legend }), /* @__PURE__ */ React6.createElement(Bar, { dataKey: "promptTokens", name: "Input", fill: SERIES.input, stackId: "t", barSize: 14 }), /* @__PURE__ */ React6.createElement(Bar, { dataKey: "completionTokens", name: "Output", fill: SERIES.output, stackId: "t", barSize: 14, radius: [0, 3, 3, 0] })))
2653
- ))), /* @__PURE__ */ React6.createElement(Grid, { item: true, xs: 12 }, /* @__PURE__ */ React6.createElement(TableContainer2, { component: Paper3, variant: "outlined", sx: { borderRadius: 2 } }, /* @__PURE__ */ React6.createElement(Table2, { size: "small", sx: dataTableSx }, /* @__PURE__ */ React6.createElement(TableHead2, null, /* @__PURE__ */ React6.createElement(TableRow2, null, /* @__PURE__ */ React6.createElement(TableCell2, null, "Model"), /* @__PURE__ */ React6.createElement(TableCell2, { align: "right" }, "Spend"), /* @__PURE__ */ React6.createElement(TableCell2, { align: "right" }, "Requests"), /* @__PURE__ */ React6.createElement(TableCell2, { align: "right" }, "Success"), /* @__PURE__ */ React6.createElement(TableCell2, { align: "right" }, "Failed"), /* @__PURE__ */ React6.createElement(TableCell2, { align: "right" }, "Input"), /* @__PURE__ */ React6.createElement(TableCell2, { align: "right" }, "Output"), /* @__PURE__ */ React6.createElement(TableCell2, { sx: { width: 160 } }, "Success rate"))), /* @__PURE__ */ React6.createElement(TableBody2, null, renderModelRows()))))),
2737
+ ))), /* @__PURE__ */ React6.createElement(Grid, { item: true, xs: 12 }, /* @__PURE__ */ React6.createElement(TableContainer3, { component: Paper4, variant: "outlined", sx: { borderRadius: 2 } }, /* @__PURE__ */ React6.createElement(Table3, { size: "small", sx: dataTableSx }, /* @__PURE__ */ React6.createElement(TableHead3, null, /* @__PURE__ */ React6.createElement(TableRow3, null, /* @__PURE__ */ React6.createElement(TableCell3, null, "Model"), /* @__PURE__ */ React6.createElement(TableCell3, { align: "right" }, "Spend"), /* @__PURE__ */ React6.createElement(TableCell3, { align: "right" }, "Requests"), /* @__PURE__ */ React6.createElement(TableCell3, { align: "right" }, "Success"), /* @__PURE__ */ React6.createElement(TableCell3, { align: "right" }, "Failed"), /* @__PURE__ */ React6.createElement(TableCell3, { align: "right" }, "Input"), /* @__PURE__ */ React6.createElement(TableCell3, { align: "right" }, "Output"), /* @__PURE__ */ React6.createElement(TableCell3, { sx: { width: 160 } }, "Success rate"))), /* @__PURE__ */ React6.createElement(TableBody3, null, renderModelRows()))))),
2654
2738
  tab === "keys" && /* @__PURE__ */ React6.createElement(Grid, { container: true, spacing: 2 }, (loading || topKeySpendBars.length > 0) && /* @__PURE__ */ React6.createElement(Grid, { item: true, xs: 12 }, /* @__PURE__ */ React6.createElement(ChartCard, { title: "Spend by key", height: Math.max(160, topKeySpendBars.length * 32) }, /* @__PURE__ */ React6.createElement(
2655
2739
  ChartOrFallback,
2656
2740
  {
@@ -2659,7 +2743,7 @@ var init_UsageStats = __esm({
2659
2743
  height: Math.max(160, topKeySpendBars.length * 32)
2660
2744
  },
2661
2745
  /* @__PURE__ */ React6.createElement(ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ React6.createElement(BarChart, { data: topKeySpendBars, layout: "vertical", margin: { top: 0, right: 12, left: 0, bottom: 0 } }, /* @__PURE__ */ React6.createElement(CartesianGrid, { ...chart.grid, vertical: true, horizontal: false }), /* @__PURE__ */ React6.createElement(XAxis, { type: "number", tickFormatter: fmtUsdCompact, ...chart.axis }), /* @__PURE__ */ React6.createElement(YAxis, { type: "category", dataKey: "keyAlias", width: 160, ...chart.axis, tick: { fontSize: 10.5, fill: chart.axis.tick.fill } }), /* @__PURE__ */ React6.createElement(Tooltip, { cursor: chart.cursor, content: /* @__PURE__ */ React6.createElement(ChartTooltip, { valueFormatter: fmtUsd, labelFormatter: (l) => l }) }), /* @__PURE__ */ React6.createElement(Bar, { dataKey: "spend", name: "Spend", fill: SERIES.input, radius: [0, 3, 3, 0], barSize: 14 })))
2662
- ))), /* @__PURE__ */ React6.createElement(Grid, { item: true, xs: 12 }, /* @__PURE__ */ React6.createElement(TableContainer2, { component: Paper3, variant: "outlined", sx: { borderRadius: 2 } }, /* @__PURE__ */ React6.createElement(Table2, { size: "small", sx: dataTableSx }, /* @__PURE__ */ React6.createElement(TableHead2, null, /* @__PURE__ */ React6.createElement(TableRow2, null, /* @__PURE__ */ React6.createElement(TableCell2, null, "Key"), /* @__PURE__ */ React6.createElement(TableCell2, null, "Models"), /* @__PURE__ */ React6.createElement(TableCell2, { align: "right" }, "Spend"), /* @__PURE__ */ React6.createElement(TableCell2, { align: "right" }, "Requests"), /* @__PURE__ */ React6.createElement(TableCell2, { align: "right" }, "Tokens"), /* @__PURE__ */ React6.createElement(TableCell2, { align: "right" }, "Failed"), /* @__PURE__ */ React6.createElement(TableCell2, { sx: { width: 160 } }, "Success rate"))), /* @__PURE__ */ React6.createElement(TableBody2, null, renderKeyRows())))))
2746
+ ))), /* @__PURE__ */ React6.createElement(Grid, { item: true, xs: 12 }, /* @__PURE__ */ React6.createElement(TableContainer3, { component: Paper4, variant: "outlined", sx: { borderRadius: 2 } }, /* @__PURE__ */ React6.createElement(Table3, { size: "small", sx: dataTableSx }, /* @__PURE__ */ React6.createElement(TableHead3, null, /* @__PURE__ */ React6.createElement(TableRow3, null, /* @__PURE__ */ React6.createElement(TableCell3, null, "Key"), /* @__PURE__ */ React6.createElement(TableCell3, null, "Models"), /* @__PURE__ */ React6.createElement(TableCell3, { align: "right" }, "Spend"), /* @__PURE__ */ React6.createElement(TableCell3, { align: "right" }, "Requests"), /* @__PURE__ */ React6.createElement(TableCell3, { align: "right" }, "Tokens"), /* @__PURE__ */ React6.createElement(TableCell3, { align: "right" }, "Failed"), /* @__PURE__ */ React6.createElement(TableCell3, { sx: { width: 160 } }, "Success rate"))), /* @__PURE__ */ React6.createElement(TableBody3, null, renderKeyRows())))))
2663
2747
  );
2664
2748
  };
2665
2749
  }
@@ -2667,23 +2751,23 @@ var init_UsageStats = __esm({
2667
2751
 
2668
2752
  // src/components/TeamUsage.tsx
2669
2753
  import React7, { useState as useState5 } from "react";
2670
- import Paper4 from "@mui/material/Paper";
2754
+ import Paper5 from "@mui/material/Paper";
2671
2755
  import Box7 from "@mui/material/Box";
2672
2756
  import Typography7 from "@mui/material/Typography";
2673
2757
  import Skeleton4 from "@mui/material/Skeleton";
2674
2758
  import Divider3 from "@mui/material/Divider";
2675
- import Table3 from "@mui/material/Table";
2676
- import TableBody3 from "@mui/material/TableBody";
2677
- import TableCell3 from "@mui/material/TableCell";
2678
- import TableHead3 from "@mui/material/TableHead";
2679
- import TableRow3 from "@mui/material/TableRow";
2680
- import TableContainer3 from "@mui/material/TableContainer";
2759
+ import Table4 from "@mui/material/Table";
2760
+ import TableBody4 from "@mui/material/TableBody";
2761
+ import TableCell4 from "@mui/material/TableCell";
2762
+ import TableHead4 from "@mui/material/TableHead";
2763
+ import TableRow4 from "@mui/material/TableRow";
2764
+ import TableContainer4 from "@mui/material/TableContainer";
2681
2765
  import Collapse from "@mui/material/Collapse";
2682
2766
  import IconButton4 from "@mui/material/IconButton";
2683
2767
  import Button5 from "@mui/material/Button";
2684
- import CircularProgress3 from "@mui/material/CircularProgress";
2768
+ import CircularProgress4 from "@mui/material/CircularProgress";
2685
2769
  import { alpha as alpha4 } from "@mui/material/styles";
2686
- import { ExpandMore as ExpandMore2, Group } from "@mui/icons-material";
2770
+ import { ExpandMore as ExpandMore2, Group, Add as Add3 } from "@mui/icons-material";
2687
2771
  import {
2688
2772
  AreaChart as AreaChart2,
2689
2773
  Area as Area2,
@@ -2715,7 +2799,7 @@ var init_TeamUsage = __esm({
2715
2799
  const dailyData = usage?.daily_usage?.map((d) => ({ date: d.date, spend: d.spend })) ?? [];
2716
2800
  const renderDailySpendSection = () => {
2717
2801
  if (usageLoading) {
2718
- return /* @__PURE__ */ React7.createElement(Box7, { display: "flex", justifyContent: "center", py: 3 }, /* @__PURE__ */ React7.createElement(CircularProgress3, { size: 22 }));
2802
+ return /* @__PURE__ */ React7.createElement(Box7, { display: "flex", justifyContent: "center", py: 3 }, /* @__PURE__ */ React7.createElement(CircularProgress4, { size: 22 }));
2719
2803
  }
2720
2804
  if (dailyData.length === 0) return null;
2721
2805
  return /* @__PURE__ */ React7.createElement(Box7, { mb: 2.5 }, /* @__PURE__ */ React7.createElement(
@@ -2754,7 +2838,7 @@ var init_TeamUsage = __esm({
2754
2838
  },
2755
2839
  label
2756
2840
  );
2757
- return /* @__PURE__ */ React7.createElement(Paper4, { variant: "outlined", sx: { borderRadius: 2, p: 2.5 } }, /* @__PURE__ */ React7.createElement(Box7, { display: "flex", alignItems: "center", gap: 1.5 }, /* @__PURE__ */ React7.createElement(
2841
+ return /* @__PURE__ */ React7.createElement(Paper5, { variant: "outlined", sx: { borderRadius: 2, p: 2.5 } }, /* @__PURE__ */ React7.createElement(Box7, { display: "flex", alignItems: "center", gap: 1.5 }, /* @__PURE__ */ React7.createElement(
2758
2842
  Box7,
2759
2843
  {
2760
2844
  sx: (theme) => ({
@@ -2819,13 +2903,13 @@ var init_TeamUsage = __esm({
2819
2903
  /* @__PURE__ */ React7.createElement(Stat, { label: "TPM", value: team.tpm_limit && team.tpm_limit > 0 ? team.tpm_limit.toLocaleString() : "\u2014" }),
2820
2904
  /* @__PURE__ */ React7.createElement(Stat, { label: "RPM", value: team.rpm_limit && team.rpm_limit > 0 ? team.rpm_limit.toLocaleString() : "\u2014" })
2821
2905
  ), budget > 0 && /* @__PURE__ */ React7.createElement(Box7, { mt: 2, maxWidth: 640 }, /* @__PURE__ */ React7.createElement(Box7, { display: "flex", justifyContent: "space-between", alignItems: "baseline", mb: 0.75 }, /* @__PURE__ */ React7.createElement(Typography7, { variant: "caption", color: "text.secondary", sx: { fontVariantNumeric: "tabular-nums" } }, fmtUsd2(spend), " of ", fmtUsd2(budget)), /* @__PURE__ */ React7.createElement(Typography7, { variant: "caption", color: "text.secondary", sx: { fontVariantNumeric: "tabular-nums" } }, budgetPct.toFixed(0), "%")), /* @__PURE__ */ React7.createElement(Meter, { value: budgetPct, tone: budgetTone2(isOver, isNear), height: 5 })), /* @__PURE__ */ React7.createElement(Collapse, { in: expanded, unmountOnExit: true }, /* @__PURE__ */ React7.createElement(Divider3, { sx: { my: 2.5 } }), /* @__PURE__ */ React7.createElement(Box7, { mb: 2.5 }, sectionLabel("Models"), team.models?.length ? /* @__PURE__ */ React7.createElement(Box7, { display: "flex", gap: 0.5, flexWrap: "wrap" }, team.models.map((m) => /* @__PURE__ */ React7.createElement(TagChip, { key: m, label: m, title: m }))) : /* @__PURE__ */ React7.createElement(Typography7, { variant: "body2", color: "text.secondary" }, "All models allowed")), renderDailySpendSection(), /* @__PURE__ */ React7.createElement(Box7, null, sectionLabel("Members"), team.members_with_roles?.length ? /* @__PURE__ */ React7.createElement(
2822
- TableContainer3,
2906
+ TableContainer4,
2823
2907
  {
2824
- component: Paper4,
2908
+ component: Paper5,
2825
2909
  variant: "outlined",
2826
2910
  sx: { borderRadius: 1.5 }
2827
2911
  },
2828
- /* @__PURE__ */ React7.createElement(Table3, { size: "small", sx: dataTableSx }, /* @__PURE__ */ React7.createElement(TableHead3, null, /* @__PURE__ */ React7.createElement(TableRow3, null, /* @__PURE__ */ React7.createElement(TableCell3, null, "User"), /* @__PURE__ */ React7.createElement(TableCell3, { align: "right" }, "Role"))), /* @__PURE__ */ React7.createElement(TableBody3, null, team.members_with_roles.map((m) => /* @__PURE__ */ React7.createElement(TableRow3, { key: m.user_id }, /* @__PURE__ */ React7.createElement(TableCell3, null, m.user_email ? /* @__PURE__ */ React7.createElement(React7.Fragment, null, /* @__PURE__ */ React7.createElement(Typography7, { variant: "body2" }, m.user_email), /* @__PURE__ */ React7.createElement(
2912
+ /* @__PURE__ */ React7.createElement(Table4, { size: "small", sx: dataTableSx }, /* @__PURE__ */ React7.createElement(TableHead4, null, /* @__PURE__ */ React7.createElement(TableRow4, null, /* @__PURE__ */ React7.createElement(TableCell4, null, "User"), /* @__PURE__ */ React7.createElement(TableCell4, { align: "right" }, "Role"))), /* @__PURE__ */ React7.createElement(TableBody4, null, team.members_with_roles.map((m) => /* @__PURE__ */ React7.createElement(TableRow4, { key: m.user_id }, /* @__PURE__ */ React7.createElement(TableCell4, null, m.user_email ? /* @__PURE__ */ React7.createElement(React7.Fragment, null, /* @__PURE__ */ React7.createElement(Typography7, { variant: "body2" }, m.user_email), /* @__PURE__ */ React7.createElement(
2829
2913
  Typography7,
2830
2914
  {
2831
2915
  variant: "caption",
@@ -2840,7 +2924,7 @@ var init_TeamUsage = __esm({
2840
2924
  sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace" }
2841
2925
  },
2842
2926
  m.user_id
2843
- )), /* @__PURE__ */ React7.createElement(TableCell3, { align: "right" }, /* @__PURE__ */ React7.createElement(
2927
+ )), /* @__PURE__ */ React7.createElement(TableCell4, { align: "right" }, /* @__PURE__ */ React7.createElement(
2844
2928
  StatusPill,
2845
2929
  {
2846
2930
  label: m.role,
@@ -2856,13 +2940,26 @@ var init_TeamUsage = __esm({
2856
2940
  getTeamUsage,
2857
2941
  getTeamUsageLoading,
2858
2942
  canManage,
2859
- onEditTeam
2943
+ onEditTeam,
2944
+ canCreate,
2945
+ onCreateTeam
2860
2946
  }) => {
2947
+ const createAction = canCreate && onCreateTeam ? /* @__PURE__ */ React7.createElement(
2948
+ Button5,
2949
+ {
2950
+ variant: "contained",
2951
+ color: "primary",
2952
+ disableElevation: true,
2953
+ startIcon: /* @__PURE__ */ React7.createElement(Add3, null),
2954
+ onClick: onCreateTeam
2955
+ },
2956
+ "Create Team"
2957
+ ) : void 0;
2861
2958
  if (loading) {
2862
- return /* @__PURE__ */ React7.createElement(SectionCard, { title: "Teams" }, /* @__PURE__ */ React7.createElement(Skeleton4, { variant: "rounded", height: 120, sx: { mb: 2 } }), /* @__PURE__ */ React7.createElement(Skeleton4, { variant: "rounded", height: 120 }));
2959
+ return /* @__PURE__ */ React7.createElement(SectionCard, { title: "Teams", actions: createAction }, /* @__PURE__ */ React7.createElement(Skeleton4, { variant: "rounded", height: 120, sx: { mb: 2 } }), /* @__PURE__ */ React7.createElement(Skeleton4, { variant: "rounded", height: 120 }));
2863
2960
  }
2864
2961
  if (!teams.length) {
2865
- return /* @__PURE__ */ React7.createElement(SectionCard, { title: "Teams" }, /* @__PURE__ */ React7.createElement(
2962
+ return /* @__PURE__ */ React7.createElement(SectionCard, { title: "Teams", actions: createAction }, /* @__PURE__ */ React7.createElement(
2866
2963
  EmptyState,
2867
2964
  {
2868
2965
  message: "You're not a member of any LiteLLM team yet.",
@@ -2870,30 +2967,38 @@ var init_TeamUsage = __esm({
2870
2967
  }
2871
2968
  ));
2872
2969
  }
2873
- return /* @__PURE__ */ React7.createElement(SectionCard, { title: "Teams", subtitle: `${teams.length} team${teams.length === 1 ? "" : "s"}` }, /* @__PURE__ */ React7.createElement(Box7, { sx: { display: "flex", flexDirection: "column", gap: 2 } }, teams.map((team) => /* @__PURE__ */ React7.createElement(
2874
- TeamCard,
2970
+ return /* @__PURE__ */ React7.createElement(
2971
+ SectionCard,
2875
2972
  {
2876
- key: team.team_id,
2877
- team,
2878
- usage: getTeamUsage(team.team_id),
2879
- usageLoading: getTeamUsageLoading(team.team_id),
2880
- canManage,
2881
- onEditTeam
2882
- }
2883
- ))));
2973
+ title: "Teams",
2974
+ subtitle: `${teams.length} team${teams.length === 1 ? "" : "s"}`,
2975
+ actions: createAction
2976
+ },
2977
+ /* @__PURE__ */ React7.createElement(Box7, { sx: { display: "flex", flexDirection: "column", gap: 2 } }, teams.map((team) => /* @__PURE__ */ React7.createElement(
2978
+ TeamCard,
2979
+ {
2980
+ key: team.team_id,
2981
+ team,
2982
+ usage: getTeamUsage(team.team_id),
2983
+ usageLoading: getTeamUsageLoading(team.team_id),
2984
+ canManage,
2985
+ onEditTeam
2986
+ }
2987
+ )))
2988
+ );
2884
2989
  };
2885
2990
  }
2886
2991
  });
2887
2992
 
2888
2993
  // src/components/ModelsTable.tsx
2889
- import React8, { useState as useState6, useMemo as useMemo5, useCallback } from "react";
2994
+ import React8, { useState as useState6, useMemo as useMemo6, useCallback } from "react";
2890
2995
  import Box8 from "@mui/material/Box";
2891
- import Table4 from "@mui/material/Table";
2892
- import TableBody4 from "@mui/material/TableBody";
2893
- import TableCell4 from "@mui/material/TableCell";
2894
- import TableContainer4 from "@mui/material/TableContainer";
2895
- import TableHead4 from "@mui/material/TableHead";
2896
- import TableRow4 from "@mui/material/TableRow";
2996
+ import Table5 from "@mui/material/Table";
2997
+ import TableBody5 from "@mui/material/TableBody";
2998
+ import TableCell5 from "@mui/material/TableCell";
2999
+ import TableContainer5 from "@mui/material/TableContainer";
3000
+ import TableHead5 from "@mui/material/TableHead";
3001
+ import TableRow5 from "@mui/material/TableRow";
2897
3002
  import Typography8 from "@mui/material/Typography";
2898
3003
  import MenuItem4 from "@mui/material/MenuItem";
2899
3004
  import Select from "@mui/material/Select";
@@ -2931,11 +3036,11 @@ var init_ModelsTable = __esm({
2931
3036
  const theme = useTheme2();
2932
3037
  const [selectedTeamId, setSelectedTeamId] = useState6("");
2933
3038
  const [copiedSnackbar, setCopiedSnackbar] = useState6(false);
2934
- const selectedTeam = useMemo5(
3039
+ const selectedTeam = useMemo6(
2935
3040
  () => teams.find((t) => t.team_id === selectedTeamId) ?? null,
2936
3041
  [teams, selectedTeamId]
2937
3042
  );
2938
- const filteredModels = useMemo5(() => {
3043
+ const filteredModels = useMemo6(() => {
2939
3044
  if (!selectedTeam) return [];
2940
3045
  return allModels.filter((model) => isModelAllowedByTeam2(model, selectedTeam.models));
2941
3046
  }, [allModels, selectedTeam]);
@@ -2987,7 +3092,7 @@ var init_ModelsTable = __esm({
2987
3092
  hint: allModels.length === 0 ? "No models are configured in the system" : "None of the team's assigned models were found in the catalog"
2988
3093
  }
2989
3094
  ),
2990
- !loading && filteredModels.length > 0 && /* @__PURE__ */ React8.createElement(TableContainer4, null, /* @__PURE__ */ React8.createElement(Table4, { size: "small", sx: dataTableSx }, /* @__PURE__ */ React8.createElement(TableHead4, null, /* @__PURE__ */ React8.createElement(TableRow4, null, /* @__PURE__ */ React8.createElement(TableCell4, null, "Model ID"), /* @__PURE__ */ React8.createElement(TableCell4, null, "Mode"), /* @__PURE__ */ React8.createElement(TableCell4, { align: "right" }, "Input Cost"), /* @__PURE__ */ React8.createElement(TableCell4, { align: "right" }, "Output Cost"), /* @__PURE__ */ React8.createElement(TableCell4, { align: "right" }, "Max Input"), /* @__PURE__ */ React8.createElement(TableCell4, { align: "right" }, "Max Output"), /* @__PURE__ */ React8.createElement(TableCell4, null, "Capabilities"))), /* @__PURE__ */ React8.createElement(TableBody4, null, filteredModels.map((m) => /* @__PURE__ */ React8.createElement(TableRow4, { key: m.model_name, hover: true }, /* @__PURE__ */ React8.createElement(TableCell4, null, /* @__PURE__ */ React8.createElement(Box8, { sx: { display: "flex", alignItems: "center", gap: 0.5 } }, /* @__PURE__ */ React8.createElement(TagChip, { label: m.model_name, title: m.model_name }), /* @__PURE__ */ React8.createElement(Tooltip3, { title: "Copy model ID" }, /* @__PURE__ */ React8.createElement(
3095
+ !loading && filteredModels.length > 0 && /* @__PURE__ */ React8.createElement(TableContainer5, null, /* @__PURE__ */ React8.createElement(Table5, { size: "small", sx: dataTableSx }, /* @__PURE__ */ React8.createElement(TableHead5, null, /* @__PURE__ */ React8.createElement(TableRow5, null, /* @__PURE__ */ React8.createElement(TableCell5, null, "Model ID"), /* @__PURE__ */ React8.createElement(TableCell5, null, "Mode"), /* @__PURE__ */ React8.createElement(TableCell5, { align: "right" }, "Input Cost"), /* @__PURE__ */ React8.createElement(TableCell5, { align: "right" }, "Output Cost"), /* @__PURE__ */ React8.createElement(TableCell5, { align: "right" }, "Max Input"), /* @__PURE__ */ React8.createElement(TableCell5, { align: "right" }, "Max Output"), /* @__PURE__ */ React8.createElement(TableCell5, null, "Capabilities"))), /* @__PURE__ */ React8.createElement(TableBody5, null, filteredModels.map((m) => /* @__PURE__ */ React8.createElement(TableRow5, { key: m.model_name, hover: true }, /* @__PURE__ */ React8.createElement(TableCell5, null, /* @__PURE__ */ React8.createElement(Box8, { sx: { display: "flex", alignItems: "center", gap: 0.5 } }, /* @__PURE__ */ React8.createElement(TagChip, { label: m.model_name, title: m.model_name }), /* @__PURE__ */ React8.createElement(Tooltip3, { title: "Copy model ID" }, /* @__PURE__ */ React8.createElement(
2991
3096
  Box8,
2992
3097
  {
2993
3098
  component: "span",
@@ -2999,7 +3104,7 @@ var init_ModelsTable = __esm({
2999
3104
  style: { cursor: "pointer", display: "inline-flex" }
3000
3105
  },
3001
3106
  /* @__PURE__ */ React8.createElement(ContentCopyIcon, { sx: { fontSize: 14 } })
3002
- )))), /* @__PURE__ */ React8.createElement(TableCell4, null, /* @__PURE__ */ React8.createElement(
3107
+ )))), /* @__PURE__ */ React8.createElement(TableCell5, null, /* @__PURE__ */ React8.createElement(
3003
3108
  Box8,
3004
3109
  {
3005
3110
  component: "span",
@@ -3016,7 +3121,7 @@ var init_ModelsTable = __esm({
3016
3121
  }
3017
3122
  },
3018
3123
  m.mode
3019
- )), /* @__PURE__ */ React8.createElement(TableCell4, { align: "right" }, /* @__PURE__ */ React8.createElement(Typography8, { variant: "body2", sx: { fontVariantNumeric: "tabular-nums" } }, fmtCostPerToken(m.input_cost_per_token))), /* @__PURE__ */ React8.createElement(TableCell4, { align: "right" }, /* @__PURE__ */ React8.createElement(Typography8, { variant: "body2", sx: { fontVariantNumeric: "tabular-nums" } }, fmtCostPerToken(m.output_cost_per_token))), /* @__PURE__ */ React8.createElement(TableCell4, { align: "right" }, /* @__PURE__ */ React8.createElement(Typography8, { variant: "body2", sx: { fontVariantNumeric: "tabular-nums" } }, fmtTokens(m.max_input_tokens))), /* @__PURE__ */ React8.createElement(TableCell4, { align: "right" }, /* @__PURE__ */ React8.createElement(Typography8, { variant: "body2", sx: { fontVariantNumeric: "tabular-nums" } }, fmtTokens(m.max_output_tokens))), /* @__PURE__ */ React8.createElement(TableCell4, null, /* @__PURE__ */ React8.createElement(Box8, { sx: { display: "flex", gap: 0.5, flexWrap: "wrap" } }, m.supports_function_calling && /* @__PURE__ */ React8.createElement(TagChip, { label: "Fn", title: "Function calling" }), m.supports_vision && /* @__PURE__ */ React8.createElement(TagChip, { label: "\u{1F441}", title: "Vision" })))))))),
3124
+ )), /* @__PURE__ */ React8.createElement(TableCell5, { align: "right" }, /* @__PURE__ */ React8.createElement(Typography8, { variant: "body2", sx: { fontVariantNumeric: "tabular-nums" } }, fmtCostPerToken(m.input_cost_per_token))), /* @__PURE__ */ React8.createElement(TableCell5, { align: "right" }, /* @__PURE__ */ React8.createElement(Typography8, { variant: "body2", sx: { fontVariantNumeric: "tabular-nums" } }, fmtCostPerToken(m.output_cost_per_token))), /* @__PURE__ */ React8.createElement(TableCell5, { align: "right" }, /* @__PURE__ */ React8.createElement(Typography8, { variant: "body2", sx: { fontVariantNumeric: "tabular-nums" } }, fmtTokens(m.max_input_tokens))), /* @__PURE__ */ React8.createElement(TableCell5, { align: "right" }, /* @__PURE__ */ React8.createElement(Typography8, { variant: "body2", sx: { fontVariantNumeric: "tabular-nums" } }, fmtTokens(m.max_output_tokens))), /* @__PURE__ */ React8.createElement(TableCell5, null, /* @__PURE__ */ React8.createElement(Box8, { sx: { display: "flex", gap: 0.5, flexWrap: "wrap" } }, m.supports_function_calling && /* @__PURE__ */ React8.createElement(TagChip, { label: "Fn", title: "Function calling" }), m.supports_vision && /* @__PURE__ */ React8.createElement(TagChip, { label: "\u{1F441}", title: "Vision" })))))))),
3020
3125
  /* @__PURE__ */ React8.createElement(
3021
3126
  Snackbar,
3022
3127
  {
@@ -3036,12 +3141,12 @@ var init_ModelsTable = __esm({
3036
3141
  import React9, { useState as useState7, useCallback as useCallback2 } from "react";
3037
3142
  import Box9 from "@mui/material/Box";
3038
3143
  import Typography9 from "@mui/material/Typography";
3039
- import Table5 from "@mui/material/Table";
3040
- import TableBody5 from "@mui/material/TableBody";
3041
- import TableCell5 from "@mui/material/TableCell";
3042
- import TableContainer5 from "@mui/material/TableContainer";
3043
- import TableHead5 from "@mui/material/TableHead";
3044
- import TableRow5 from "@mui/material/TableRow";
3144
+ import Table6 from "@mui/material/Table";
3145
+ import TableBody6 from "@mui/material/TableBody";
3146
+ import TableCell6 from "@mui/material/TableCell";
3147
+ import TableContainer6 from "@mui/material/TableContainer";
3148
+ import TableHead6 from "@mui/material/TableHead";
3149
+ import TableRow6 from "@mui/material/TableRow";
3045
3150
  import TablePagination from "@mui/material/TablePagination";
3046
3151
  import TextField5 from "@mui/material/TextField";
3047
3152
  import MenuItem5 from "@mui/material/MenuItem";
@@ -3051,7 +3156,7 @@ import IconButton5 from "@mui/material/IconButton";
3051
3156
  import Alert4 from "@mui/material/Alert";
3052
3157
  import { alpha as alpha6 } from "@mui/material/styles";
3053
3158
  import { KeyboardArrowDown } from "@mui/icons-material";
3054
- import { useAsync } from "react-use";
3159
+ import { useAsync as useAsync2 } from "react-use";
3055
3160
  function actionTone(action) {
3056
3161
  if (!action) return "neutral";
3057
3162
  for (const [key, tone] of Object.entries(ACTION_TONES)) {
@@ -3072,10 +3177,10 @@ function formatDateTime(iso) {
3072
3177
  }
3073
3178
  function renderAuditLogBody(loading, entries) {
3074
3179
  if (loading) {
3075
- return /* @__PURE__ */ React9.createElement(TableRow5, null, /* @__PURE__ */ React9.createElement(TableCell5, { colSpan: 6, sx: { py: 2 } }, /* @__PURE__ */ React9.createElement(Skeleton5, { variant: "rounded", height: 160 })));
3180
+ return /* @__PURE__ */ React9.createElement(TableRow6, null, /* @__PURE__ */ React9.createElement(TableCell6, { colSpan: 6, sx: { py: 2 } }, /* @__PURE__ */ React9.createElement(Skeleton5, { variant: "rounded", height: 160 })));
3076
3181
  }
3077
3182
  if (entries.length === 0) {
3078
- return /* @__PURE__ */ React9.createElement(TableRow5, null, /* @__PURE__ */ React9.createElement(TableCell5, { colSpan: 6 }, /* @__PURE__ */ React9.createElement(
3183
+ return /* @__PURE__ */ React9.createElement(TableRow6, null, /* @__PURE__ */ React9.createElement(TableCell6, { colSpan: 6 }, /* @__PURE__ */ React9.createElement(
3079
3184
  EmptyState,
3080
3185
  {
3081
3186
  message: "No audit events found",
@@ -3107,7 +3212,7 @@ var init_AuditLog = __esm({
3107
3212
  DetailRow = ({ entry }) => {
3108
3213
  const [open, setOpen] = useState7(false);
3109
3214
  const hasDetail = entry.before_value || entry.updated_values;
3110
- return /* @__PURE__ */ React9.createElement(React9.Fragment, null, /* @__PURE__ */ React9.createElement(TableRow5, null, /* @__PURE__ */ React9.createElement(TableCell5, { sx: { width: 40, pr: 0 } }, hasDetail && /* @__PURE__ */ React9.createElement(
3215
+ return /* @__PURE__ */ React9.createElement(React9.Fragment, null, /* @__PURE__ */ React9.createElement(TableRow6, null, /* @__PURE__ */ React9.createElement(TableCell6, { sx: { width: 40, pr: 0 } }, hasDetail && /* @__PURE__ */ React9.createElement(
3111
3216
  IconButton5,
3112
3217
  {
3113
3218
  size: "small",
@@ -3121,7 +3226,7 @@ var init_AuditLog = __esm({
3121
3226
  })
3122
3227
  },
3123
3228
  /* @__PURE__ */ React9.createElement(KeyboardArrowDown, { fontSize: "small" })
3124
- )), /* @__PURE__ */ React9.createElement(TableCell5, { sx: { whiteSpace: "nowrap" } }, /* @__PURE__ */ React9.createElement(Typography9, { variant: "body2", color: "text.secondary" }, formatDateTime(entry.updated_at))), /* @__PURE__ */ React9.createElement(TableCell5, null, entry.action && /* @__PURE__ */ React9.createElement(StatusPill, { label: entry.action, tone: actionTone(entry.action) })), /* @__PURE__ */ React9.createElement(TableCell5, null, /* @__PURE__ */ React9.createElement(Typography9, { variant: "body2" }, prettyTableName(entry.table_name))), /* @__PURE__ */ React9.createElement(TableCell5, null, /* @__PURE__ */ React9.createElement(
3229
+ )), /* @__PURE__ */ React9.createElement(TableCell6, { sx: { whiteSpace: "nowrap" } }, /* @__PURE__ */ React9.createElement(Typography9, { variant: "body2", color: "text.secondary" }, formatDateTime(entry.updated_at))), /* @__PURE__ */ React9.createElement(TableCell6, null, entry.action && /* @__PURE__ */ React9.createElement(StatusPill, { label: entry.action, tone: actionTone(entry.action) })), /* @__PURE__ */ React9.createElement(TableCell6, null, /* @__PURE__ */ React9.createElement(Typography9, { variant: "body2" }, prettyTableName(entry.table_name))), /* @__PURE__ */ React9.createElement(TableCell6, null, /* @__PURE__ */ React9.createElement(
3125
3230
  Typography9,
3126
3231
  {
3127
3232
  variant: "body2",
@@ -3131,7 +3236,7 @@ var init_AuditLog = __esm({
3131
3236
  sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 12 }
3132
3237
  },
3133
3238
  entry.object_id ? entry.object_id.slice(0, 20) + (entry.object_id.length > 20 ? "\u2026" : "") : "\u2014"
3134
- )), /* @__PURE__ */ React9.createElement(TableCell5, null, entry.changed_by ?? "\u2014")), hasDetail && /* @__PURE__ */ React9.createElement(TableRow5, { sx: { "&&:hover": { bgcolor: "transparent" } } }, /* @__PURE__ */ React9.createElement(TableCell5, { colSpan: 6, sx: { "&&": { py: 0, border: 0 } } }, /* @__PURE__ */ React9.createElement(Collapse2, { in: open, unmountOnExit: true }, /* @__PURE__ */ React9.createElement(
3239
+ )), /* @__PURE__ */ React9.createElement(TableCell6, null, entry.changed_by ?? "\u2014")), hasDetail && /* @__PURE__ */ React9.createElement(TableRow6, { sx: { "&&:hover": { bgcolor: "transparent" } } }, /* @__PURE__ */ React9.createElement(TableCell6, { colSpan: 6, sx: { "&&": { py: 0, border: 0 } } }, /* @__PURE__ */ React9.createElement(Collapse2, { in: open, unmountOnExit: true }, /* @__PURE__ */ React9.createElement(
3135
3240
  Box9,
3136
3241
  {
3137
3242
  display: "flex",
@@ -3176,7 +3281,7 @@ var init_AuditLog = __esm({
3176
3281
  () => ({ page: page + 1, page_size: pageSize, ...filters }),
3177
3282
  [page, pageSize, filters]
3178
3283
  )();
3179
- const { value, loading, error } = useAsync(
3284
+ const { value, loading, error } = useAsync2(
3180
3285
  () => api.getAuditLogs(fetchParams),
3181
3286
  [api, fetchParams]
3182
3287
  );
@@ -3240,7 +3345,7 @@ var init_AuditLog = __esm({
3240
3345
  ))
3241
3346
  },
3242
3347
  error && /* @__PURE__ */ React9.createElement(Box9, { px: 2.5, pb: 2 }, /* @__PURE__ */ React9.createElement(Alert4, { severity: "error" }, error.message)),
3243
- /* @__PURE__ */ React9.createElement(TableContainer5, null, /* @__PURE__ */ React9.createElement(Table5, { size: "small", sx: dataTableSx }, /* @__PURE__ */ React9.createElement(TableHead5, null, /* @__PURE__ */ React9.createElement(TableRow5, null, /* @__PURE__ */ React9.createElement(TableCell5, { sx: { width: 40 } }), /* @__PURE__ */ React9.createElement(TableCell5, null, "Time"), /* @__PURE__ */ React9.createElement(TableCell5, null, "Action"), /* @__PURE__ */ React9.createElement(TableCell5, null, "Table"), /* @__PURE__ */ React9.createElement(TableCell5, null, "Object ID"), /* @__PURE__ */ React9.createElement(TableCell5, null, "Changed By"))), /* @__PURE__ */ React9.createElement(TableBody5, null, renderAuditLogBody(loading, entries)))),
3348
+ /* @__PURE__ */ React9.createElement(TableContainer6, null, /* @__PURE__ */ React9.createElement(Table6, { size: "small", sx: dataTableSx }, /* @__PURE__ */ React9.createElement(TableHead6, null, /* @__PURE__ */ React9.createElement(TableRow6, null, /* @__PURE__ */ React9.createElement(TableCell6, { sx: { width: 40 } }), /* @__PURE__ */ React9.createElement(TableCell6, null, "Time"), /* @__PURE__ */ React9.createElement(TableCell6, null, "Action"), /* @__PURE__ */ React9.createElement(TableCell6, null, "Table"), /* @__PURE__ */ React9.createElement(TableCell6, null, "Object ID"), /* @__PURE__ */ React9.createElement(TableCell6, null, "Changed By"))), /* @__PURE__ */ React9.createElement(TableBody6, null, renderAuditLogBody(loading, entries)))),
3244
3349
  /* @__PURE__ */ React9.createElement(
3245
3350
  TablePagination,
3246
3351
  {
@@ -3296,18 +3401,17 @@ var LiteLLMPage_exports = {};
3296
3401
  __export(LiteLLMPage_exports, {
3297
3402
  LiteLLMPage: () => LiteLLMPage
3298
3403
  });
3299
- import React10, { useState as useState8, useCallback as useCallback3, useMemo as useMemo6 } from "react";
3404
+ import React10, { useState as useState8, useCallback as useCallback3, useMemo as useMemo7 } from "react";
3300
3405
  import Box10 from "@mui/material/Box";
3301
3406
  import Snackbar2 from "@mui/material/Snackbar";
3302
3407
  import Alert5 from "@mui/material/Alert";
3303
- import CircularProgress4 from "@mui/material/CircularProgress";
3408
+ import CircularProgress5 from "@mui/material/CircularProgress";
3304
3409
  import Typography10 from "@mui/material/Typography";
3305
- import Paper5 from "@mui/material/Paper";
3410
+ import Paper6 from "@mui/material/Paper";
3306
3411
  import Tabs2 from "@mui/material/Tabs";
3307
3412
  import Tab2 from "@mui/material/Tab";
3308
- import Button6 from "@mui/material/Button";
3309
- import { useAsync as useAsync2, useAsyncRetry } from "react-use";
3310
- import { useApi } from "@backstage/core-plugin-api";
3413
+ import { useAsync as useAsync3, useAsyncRetry } from "react-use";
3414
+ import { useApi as useApi2 } from "@backstage/core-plugin-api";
3311
3415
  import { usePermission } from "@backstage/plugin-permission-react";
3312
3416
  function initDateRange() {
3313
3417
  let preset = "7d";
@@ -3339,7 +3443,7 @@ var init_LiteLLMPage = __esm({
3339
3443
  init_permissions();
3340
3444
  PERIOD_LS_KEY2 = "litellm_usage_period";
3341
3445
  LiteLLMPage = () => {
3342
- const api = useApi(liteLlmApiRef);
3446
+ const api = useApi2(liteLlmApiRef);
3343
3447
  const [dateRange, setDateRange] = useState8(initDateRange);
3344
3448
  const [currentPreset, setCurrentPreset] = useState8(() => {
3345
3449
  try {
@@ -3354,7 +3458,7 @@ var init_LiteLLMPage = __esm({
3354
3458
  const [manageTeam, setManageTeam] = useState8(null);
3355
3459
  const [teamUsageCache, setTeamUsageCache] = useState8({});
3356
3460
  const [teamUsageLoading, setTeamUsageLoading] = useState8({});
3357
- const { value: userInfo, loading: userLoading, error: userError } = useAsync2(
3461
+ const { value: userInfo, loading: userLoading, error: userError } = useAsync3(
3358
3462
  () => api.getUserInfo(),
3359
3463
  [api]
3360
3464
  );
@@ -3369,7 +3473,7 @@ var init_LiteLLMPage = __esm({
3369
3473
  },
3370
3474
  [api]
3371
3475
  );
3372
- const { value: allModels, loading: modelsLoading } = useAsync2(
3476
+ const { value: allModels, loading: modelsLoading } = useAsync3(
3373
3477
  () => api.listModels().catch(() => []),
3374
3478
  [api]
3375
3479
  );
@@ -3377,8 +3481,8 @@ var init_LiteLLMPage = __esm({
3377
3481
  () => api.getTeams().catch(() => []),
3378
3482
  [api]
3379
3483
  );
3380
- const { value: liteLlmConfig } = useAsync2(() => api.getConfig(), [api]);
3381
- const { value: managedTeams } = useAsync2(
3484
+ const { value: liteLlmConfig } = useAsync3(() => api.getConfig(), [api]);
3485
+ const { value: managedTeams } = useAsync3(
3382
3486
  async () => liteLlmConfig?.teamManagement?.enabled ? api.getManagedTeams().catch(() => []) : [],
3383
3487
  [api, liteLlmConfig]
3384
3488
  );
@@ -3389,15 +3493,15 @@ var init_LiteLLMPage = __esm({
3389
3493
  const { allowed: canManageMcpServers } = usePermission({ permission: litellmTeamMcpManagePermission });
3390
3494
  const teamMgmtEnabled = liteLlmConfig?.teamManagement?.enabled ?? false;
3391
3495
  const objectPermsEnabled = liteLlmConfig?.teamManagement?.objectPermissionsEnabled ?? false;
3392
- const { value: vectorStores } = useAsync2(
3496
+ const { value: vectorStores } = useAsync3(
3393
3497
  async () => objectPermsEnabled ? api.getVectorStores().catch(() => []) : [],
3394
3498
  [api, objectPermsEnabled]
3395
3499
  );
3396
- const { value: mcpServers } = useAsync2(
3500
+ const { value: mcpServers } = useAsync3(
3397
3501
  async () => objectPermsEnabled ? api.getMcpServers().catch(() => []) : [],
3398
3502
  [api, objectPermsEnabled]
3399
3503
  );
3400
- const teams = useMemo6(() => {
3504
+ const teams = useMemo7(() => {
3401
3505
  if (!allTeams?.length) return [];
3402
3506
  if (!userInfo) return allTeams;
3403
3507
  const userId = userInfo.user_id;
@@ -3409,7 +3513,7 @@ var init_LiteLLMPage = __esm({
3409
3513
  );
3410
3514
  return byMembership.length > 0 ? byMembership : allTeams;
3411
3515
  }, [allTeams, userInfo]);
3412
- const { value: usage, loading: usageLoading } = useAsync2(async () => {
3516
+ const { value: usage, loading: usageLoading } = useAsync3(async () => {
3413
3517
  const startDate = dateRange.start.toISOString().split("T")[0];
3414
3518
  const endDate = dateRange.end.toISOString().split("T")[0];
3415
3519
  return api.getUsage(startDate, endDate);
@@ -3436,7 +3540,7 @@ var init_LiteLLMPage = __esm({
3436
3540
  setTeamUsageLoading((prev) => ({ ...prev, [teamId]: false }));
3437
3541
  }
3438
3542
  }, [api, dateRange, teamUsageCache, teamUsageLoading]);
3439
- const allowedModels = useMemo6(() => {
3543
+ const allowedModels = useMemo7(() => {
3440
3544
  if (!allModels?.length) return [];
3441
3545
  const userModels = userInfo?.models;
3442
3546
  const teamModels = teams?.flatMap((t) => t.models ?? []);
@@ -3544,12 +3648,12 @@ var init_LiteLLMPage = __esm({
3544
3648
  );
3545
3649
  const isInitialLoading = userLoading && !userInfo;
3546
3650
  if (isInitialLoading) {
3547
- return /* @__PURE__ */ React10.createElement(Box10, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: "50vh" }, /* @__PURE__ */ React10.createElement(CircularProgress4, null));
3651
+ return /* @__PURE__ */ React10.createElement(Box10, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: "50vh" }, /* @__PURE__ */ React10.createElement(CircularProgress5, null));
3548
3652
  }
3549
3653
  if (userError || !userInfo) {
3550
3654
  const isProvisioningEnabled = userError?.body?.provisioning === true;
3551
3655
  const hint = userError?.body?.hint;
3552
- return /* @__PURE__ */ React10.createElement(Box10, { p: 3 }, /* @__PURE__ */ React10.createElement(Paper5, { sx: { p: 3 } }, /* @__PURE__ */ React10.createElement(Typography10, { variant: "h6", gutterBottom: true }, "Account not provisioned"), /* @__PURE__ */ React10.createElement(Typography10, { color: "text.secondary", paragraph: true }, "Your Backstage account is not linked to a LiteLLM user."), hint ? /* @__PURE__ */ React10.createElement(Typography10, { variant: "body2", color: "text.secondary" }, hint) : /* @__PURE__ */ React10.createElement(Typography10, { variant: "body2", color: "text.secondary" }, isProvisioningEnabled ? "Auto-provisioning is enabled but failed. Check the backend logs." : "Set litellm.provisioning.enabled: true in app-config.yaml to enable auto-provisioning, or ask your administrator to create the account manually.")));
3656
+ return /* @__PURE__ */ React10.createElement(Box10, { p: 3 }, /* @__PURE__ */ React10.createElement(Paper6, { sx: { p: 3 } }, /* @__PURE__ */ React10.createElement(Typography10, { variant: "h6", gutterBottom: true }, "Account not provisioned"), /* @__PURE__ */ React10.createElement(Typography10, { color: "text.secondary", paragraph: true }, "Your Backstage account is not linked to a LiteLLM user."), hint ? /* @__PURE__ */ React10.createElement(Typography10, { variant: "body2", color: "text.secondary" }, hint) : /* @__PURE__ */ React10.createElement(Typography10, { variant: "body2", color: "text.secondary" }, isProvisioningEnabled ? "Auto-provisioning is enabled but failed. Check the backend logs." : "Set litellm.provisioning.enabled: true in app-config.yaml to enable auto-provisioning, or ask your administrator to create the account manually.")));
3553
3657
  }
3554
3658
  const pageTabs = /* @__PURE__ */ React10.createElement(
3555
3659
  Tabs2,
@@ -3610,14 +3714,7 @@ var init_LiteLLMPage = __esm({
3610
3714
  for (const t of managedTeams ?? []) teamsById.set(t.team_id, t);
3611
3715
  for (const t of teams ?? []) teamsById.set(t.team_id, t);
3612
3716
  const visibleTeams = Array.from(teamsById.values());
3613
- return /* @__PURE__ */ React10.createElement(Box10, { sx: { display: "flex", flexDirection: "column", gap: 2 } }, teamMgmtEnabled && canCreateTeam && /* @__PURE__ */ React10.createElement(Box10, { sx: { display: "flex", justifyContent: "flex-end" } }, /* @__PURE__ */ React10.createElement(
3614
- Button6,
3615
- {
3616
- variant: "contained",
3617
- onClick: () => setManageTeam({ mode: "create" })
3618
- },
3619
- "Create Team"
3620
- )), /* @__PURE__ */ React10.createElement(
3717
+ return /* @__PURE__ */ React10.createElement(
3621
3718
  TeamUsage,
3622
3719
  {
3623
3720
  teams: visibleTeams,
@@ -3628,9 +3725,11 @@ var init_LiteLLMPage = __esm({
3628
3725
  },
3629
3726
  getTeamUsageLoading: (teamId) => teamUsageLoading[teamId] ?? false,
3630
3727
  canManage: teamMgmtEnabled && canManageTeam,
3631
- onEditTeam: (t) => setManageTeam({ mode: "edit", team: t })
3728
+ onEditTeam: (t) => setManageTeam({ mode: "edit", team: t }),
3729
+ canCreate: teamMgmtEnabled && canCreateTeam,
3730
+ onCreateTeam: () => setManageTeam({ mode: "create" })
3632
3731
  }
3633
- ));
3732
+ );
3634
3733
  })(), activeTab === "models" && /* @__PURE__ */ React10.createElement(
3635
3734
  ModelsTable,
3636
3735
  {
@@ -3771,17 +3870,17 @@ init_TeamUsage();
3771
3870
  init_api();
3772
3871
  init_format();
3773
3872
  import React12, { useState as useState9, useEffect as useEffect3 } from "react";
3774
- import Paper6 from "@mui/material/Paper";
3873
+ import Paper7 from "@mui/material/Paper";
3775
3874
  import Box11 from "@mui/material/Box";
3776
3875
  import Typography11 from "@mui/material/Typography";
3777
3876
  import FormControl2 from "@mui/material/FormControl";
3778
3877
  import Select2 from "@mui/material/Select";
3779
3878
  import MenuItem6 from "@mui/material/MenuItem";
3780
3879
  import Grid2 from "@mui/material/Grid";
3781
- import CircularProgress5 from "@mui/material/CircularProgress";
3880
+ import CircularProgress6 from "@mui/material/CircularProgress";
3782
3881
  import Alert6 from "@mui/material/Alert";
3783
3882
  import { AreaChart as AreaChart3, Area as Area3, ResponsiveContainer as ResponsiveContainer3 } from "recharts";
3784
- import { useApi as useApi2 } from "@backstage/core-plugin-api";
3883
+ import { useApi as useApi3 } from "@backstage/core-plugin-api";
3785
3884
  function presetToDateRange(preset) {
3786
3885
  const end = /* @__PURE__ */ new Date();
3787
3886
  const start = /* @__PURE__ */ new Date();
@@ -3799,7 +3898,7 @@ var LiteLLMHomeWidget = ({
3799
3898
  defaultPeriod = "7d",
3800
3899
  title = "LiteLLM Usage"
3801
3900
  }) => {
3802
- const api = useApi2(liteLlmApiRef);
3901
+ const api = useApi3(liteLlmApiRef);
3803
3902
  const [period, setPeriod] = useState9(defaultPeriod);
3804
3903
  const [loading, setLoading] = useState9(true);
3805
3904
  const [usageError, setUsageError] = useState9(null);
@@ -3843,7 +3942,7 @@ var LiteLLMHomeWidget = ({
3843
3942
  spend: d.spend
3844
3943
  }));
3845
3944
  const hasSparkline = dailyData.length > 0;
3846
- return /* @__PURE__ */ React12.createElement(Paper6, { sx: { p: 2 } }, /* @__PURE__ */ React12.createElement(Box11, { display: "flex", justifyContent: "space-between", alignItems: "center", mb: 1.5 }, /* @__PURE__ */ React12.createElement(Typography11, { variant: "h6" }, title), /* @__PURE__ */ React12.createElement(FormControl2, { size: "small", sx: { minWidth: 90 } }, /* @__PURE__ */ React12.createElement(
3945
+ return /* @__PURE__ */ React12.createElement(Paper7, { sx: { p: 2 } }, /* @__PURE__ */ React12.createElement(Box11, { display: "flex", justifyContent: "space-between", alignItems: "center", mb: 1.5 }, /* @__PURE__ */ React12.createElement(Typography11, { variant: "h6" }, title), /* @__PURE__ */ React12.createElement(FormControl2, { size: "small", sx: { minWidth: 90 } }, /* @__PURE__ */ React12.createElement(
3847
3946
  Select2,
3848
3947
  {
3849
3948
  value: period,
@@ -3853,7 +3952,7 @@ var LiteLLMHomeWidget = ({
3853
3952
  /* @__PURE__ */ React12.createElement(MenuItem6, { value: "today" }, "Today"),
3854
3953
  /* @__PURE__ */ React12.createElement(MenuItem6, { value: "7d" }, "7d"),
3855
3954
  /* @__PURE__ */ React12.createElement(MenuItem6, { value: "30d" }, "30d")
3856
- ))), loading && /* @__PURE__ */ React12.createElement(Box11, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: 120 }, /* @__PURE__ */ React12.createElement(CircularProgress5, { size: 32 })), !loading && totalFailure && /* @__PURE__ */ React12.createElement(Alert6, { severity: "error", sx: { mt: 1 } }, usageError ?? "Failed to load usage data"), !loading && !totalFailure && /* @__PURE__ */ React12.createElement(React12.Fragment, null, partialFailure && /* @__PURE__ */ React12.createElement(Alert6, { severity: "warning", sx: { mt: 1, mb: 1 } }, usageError ? `Usage data unavailable (${usageError}).` : "", keysError ? ` Key list unavailable (${keysError}).` : "", " Showing what loaded."), /* @__PURE__ */ React12.createElement(Grid2, { container: true, spacing: 2, sx: { mb: hasSparkline ? 1.5 : 0 } }, /* @__PURE__ */ React12.createElement(Grid2, { item: true, xs: 6 }, /* @__PURE__ */ React12.createElement(Kpi, { label: "USD Spent", value: fmtUsd(usage?.total_spend ?? 0) })), /* @__PURE__ */ React12.createElement(Grid2, { item: true, xs: 6 }, /* @__PURE__ */ React12.createElement(Kpi, { label: "Tokens In", value: fmtInt(usage?.prompt_tokens ?? 0) })), /* @__PURE__ */ React12.createElement(Grid2, { item: true, xs: 6 }, /* @__PURE__ */ React12.createElement(Kpi, { label: "Tokens Out", value: fmtInt(usage?.completion_tokens ?? 0) })), /* @__PURE__ */ React12.createElement(Grid2, { item: true, xs: 6 }, /* @__PURE__ */ React12.createElement(Kpi, { label: "Keys", value: fmtInt(keys.length) }))), hasSparkline && /* @__PURE__ */ React12.createElement(Box11, { height: 120 }, /* @__PURE__ */ React12.createElement(ResponsiveContainer3, { width: "100%", height: "100%" }, /* @__PURE__ */ React12.createElement(AreaChart3, { data: dailyData, margin: { top: 4, right: 0, bottom: 0, left: 0 } }, /* @__PURE__ */ React12.createElement(
3955
+ ))), loading && /* @__PURE__ */ React12.createElement(Box11, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: 120 }, /* @__PURE__ */ React12.createElement(CircularProgress6, { size: 32 })), !loading && totalFailure && /* @__PURE__ */ React12.createElement(Alert6, { severity: "error", sx: { mt: 1 } }, usageError ?? "Failed to load usage data"), !loading && !totalFailure && /* @__PURE__ */ React12.createElement(React12.Fragment, null, partialFailure && /* @__PURE__ */ React12.createElement(Alert6, { severity: "warning", sx: { mt: 1, mb: 1 } }, usageError ? `Usage data unavailable (${usageError}).` : "", keysError ? ` Key list unavailable (${keysError}).` : "", " Showing what loaded."), /* @__PURE__ */ React12.createElement(Grid2, { container: true, spacing: 2, sx: { mb: hasSparkline ? 1.5 : 0 } }, /* @__PURE__ */ React12.createElement(Grid2, { item: true, xs: 6 }, /* @__PURE__ */ React12.createElement(Kpi, { label: "USD Spent", value: fmtUsd(usage?.total_spend ?? 0) })), /* @__PURE__ */ React12.createElement(Grid2, { item: true, xs: 6 }, /* @__PURE__ */ React12.createElement(Kpi, { label: "Tokens In", value: fmtInt(usage?.prompt_tokens ?? 0) })), /* @__PURE__ */ React12.createElement(Grid2, { item: true, xs: 6 }, /* @__PURE__ */ React12.createElement(Kpi, { label: "Tokens Out", value: fmtInt(usage?.completion_tokens ?? 0) })), /* @__PURE__ */ React12.createElement(Grid2, { item: true, xs: 6 }, /* @__PURE__ */ React12.createElement(Kpi, { label: "Keys", value: fmtInt(keys.length) }))), hasSparkline && /* @__PURE__ */ React12.createElement(Box11, { height: 120 }, /* @__PURE__ */ React12.createElement(ResponsiveContainer3, { width: "100%", height: "100%" }, /* @__PURE__ */ React12.createElement(AreaChart3, { data: dailyData, margin: { top: 4, right: 0, bottom: 0, left: 0 } }, /* @__PURE__ */ React12.createElement(
3857
3956
  Area3,
3858
3957
  {
3859
3958
  type: "monotone",