@acarmisc/backstage-plugin-litellm 0.17.0 → 0.18.1
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/SearchInput.d.ts +1 -0
- package/dist/components/SearchInput.d.ts +9 -0
- package/dist/components/TeamUsage.d.ts +3 -0
- package/dist/index.cjs.js +267 -145
- package/dist/index.cjs.js.map +3 -3
- package/dist/index.esm.js +272 -150
- package/dist/index.esm.js.map +3 -3
- package/package.json +4 -1
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
|
|
1940
|
-
import
|
|
1941
|
-
import
|
|
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(
|
|
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(
|
|
2186
|
-
|
|
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
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
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
|
-
|
|
2203
|
-
))
|
|
2204
|
-
|
|
2241
|
+
m.user_id
|
|
2242
|
+
)) : /* @__PURE__ */ React5.createElement(
|
|
2243
|
+
Typography5,
|
|
2205
2244
|
{
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
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
|
-
|
|
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
|
|
2279
|
-
import
|
|
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
|
|
2286
|
-
import
|
|
2287
|
-
import
|
|
2288
|
-
import
|
|
2289
|
-
import
|
|
2290
|
-
import
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 } =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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
|
|
2676
|
-
import
|
|
2677
|
-
import
|
|
2678
|
-
import
|
|
2679
|
-
import
|
|
2680
|
-
import
|
|
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
|
|
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(
|
|
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(
|
|
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
|
-
|
|
2906
|
+
TableContainer4,
|
|
2823
2907
|
{
|
|
2824
|
-
component:
|
|
2908
|
+
component: Paper5,
|
|
2825
2909
|
variant: "outlined",
|
|
2826
2910
|
sx: { borderRadius: 1.5 }
|
|
2827
2911
|
},
|
|
2828
|
-
/* @__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(
|
|
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(
|
|
2874
|
-
|
|
2970
|
+
return /* @__PURE__ */ React7.createElement(
|
|
2971
|
+
SectionCard,
|
|
2875
2972
|
{
|
|
2876
|
-
|
|
2877
|
-
team
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
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
|
|
2994
|
+
import React8, { useState as useState6, useMemo as useMemo6, useCallback } from "react";
|
|
2890
2995
|
import Box8 from "@mui/material/Box";
|
|
2891
|
-
import
|
|
2892
|
-
import
|
|
2893
|
-
import
|
|
2894
|
-
import
|
|
2895
|
-
import
|
|
2896
|
-
import
|
|
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";
|
|
@@ -2904,6 +3009,9 @@ import Tooltip3 from "@mui/material/Tooltip";
|
|
|
2904
3009
|
import Snackbar from "@mui/material/Snackbar";
|
|
2905
3010
|
import Alert3 from "@mui/material/Alert";
|
|
2906
3011
|
import { alpha as alpha5, useTheme as useTheme2 } from "@mui/material/styles";
|
|
3012
|
+
import TextField5 from "@mui/material/TextField";
|
|
3013
|
+
import InputAdornment2 from "@mui/material/InputAdornment";
|
|
3014
|
+
import { Search as Search2 } from "@mui/icons-material";
|
|
2907
3015
|
function isModelAllowedByTeam2(model, teamModels) {
|
|
2908
3016
|
if (!teamModels || teamModels.length === 0 || teamModels.includes(ALL_PROXY_MODELS2)) return true;
|
|
2909
3017
|
return teamModels.includes(model.model_name) || !!model.access_groups?.some((group) => teamModels.includes(group));
|
|
@@ -2930,15 +3038,23 @@ var init_ModelsTable = __esm({
|
|
|
2930
3038
|
ModelsTable = ({ allModels, teams, loading }) => {
|
|
2931
3039
|
const theme = useTheme2();
|
|
2932
3040
|
const [selectedTeamId, setSelectedTeamId] = useState6("");
|
|
3041
|
+
const [filterText, setFilterText] = useState6("");
|
|
2933
3042
|
const [copiedSnackbar, setCopiedSnackbar] = useState6(false);
|
|
2934
|
-
const selectedTeam =
|
|
3043
|
+
const selectedTeam = useMemo6(
|
|
2935
3044
|
() => teams.find((t) => t.team_id === selectedTeamId) ?? null,
|
|
2936
3045
|
[teams, selectedTeamId]
|
|
2937
3046
|
);
|
|
2938
|
-
const filteredModels =
|
|
3047
|
+
const filteredModels = useMemo6(() => {
|
|
2939
3048
|
if (!selectedTeam) return [];
|
|
2940
|
-
|
|
2941
|
-
|
|
3049
|
+
let result = allModels.filter((model) => isModelAllowedByTeam2(model, selectedTeam.models));
|
|
3050
|
+
if (filterText.trim()) {
|
|
3051
|
+
const q = filterText.toLowerCase();
|
|
3052
|
+
result = result.filter(
|
|
3053
|
+
(model) => model.model_name.toLowerCase().includes(q) || (model.access_groups || []).some((group) => group.toLowerCase().includes(q))
|
|
3054
|
+
);
|
|
3055
|
+
}
|
|
3056
|
+
return result;
|
|
3057
|
+
}, [allModels, selectedTeam, filterText]);
|
|
2942
3058
|
const handleCopyModelId = useCallback((modelId) => {
|
|
2943
3059
|
navigator.clipboard.writeText(modelId).then(() => setCopiedSnackbar(true));
|
|
2944
3060
|
}, []);
|
|
@@ -2971,7 +3087,19 @@ var init_ModelsTable = __esm({
|
|
|
2971
3087
|
onChange: (e) => setSelectedTeamId(e.target.value)
|
|
2972
3088
|
},
|
|
2973
3089
|
teams.map((t) => /* @__PURE__ */ React8.createElement(MenuItem4, { key: t.team_id, value: t.team_id }, t.team_alias ?? t.team_id))
|
|
2974
|
-
))
|
|
3090
|
+
)), /* @__PURE__ */ React8.createElement(
|
|
3091
|
+
TextField5,
|
|
3092
|
+
{
|
|
3093
|
+
label: "Filter Models",
|
|
3094
|
+
size: "small",
|
|
3095
|
+
value: filterText,
|
|
3096
|
+
onChange: (e) => setFilterText(e.target.value),
|
|
3097
|
+
sx: { minWidth: 240, mb: 2 },
|
|
3098
|
+
InputProps: {
|
|
3099
|
+
startAdornment: /* @__PURE__ */ React8.createElement(InputAdornment2, { position: "start" }, /* @__PURE__ */ React8.createElement(Search2, { fontSize: "small" }))
|
|
3100
|
+
}
|
|
3101
|
+
}
|
|
3102
|
+
)),
|
|
2975
3103
|
loading && /* @__PURE__ */ React8.createElement(EmptyState, { message: "Loading models\u2026" }),
|
|
2976
3104
|
!loading && !selectedTeamId && /* @__PURE__ */ React8.createElement(
|
|
2977
3105
|
EmptyState,
|
|
@@ -2987,7 +3115,7 @@ var init_ModelsTable = __esm({
|
|
|
2987
3115
|
hint: allModels.length === 0 ? "No models are configured in the system" : "None of the team's assigned models were found in the catalog"
|
|
2988
3116
|
}
|
|
2989
3117
|
),
|
|
2990
|
-
!loading && filteredModels.length > 0 && /* @__PURE__ */ React8.createElement(
|
|
3118
|
+
!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
3119
|
Box8,
|
|
2992
3120
|
{
|
|
2993
3121
|
component: "span",
|
|
@@ -2999,7 +3127,7 @@ var init_ModelsTable = __esm({
|
|
|
2999
3127
|
style: { cursor: "pointer", display: "inline-flex" }
|
|
3000
3128
|
},
|
|
3001
3129
|
/* @__PURE__ */ React8.createElement(ContentCopyIcon, { sx: { fontSize: 14 } })
|
|
3002
|
-
)))), /* @__PURE__ */ React8.createElement(
|
|
3130
|
+
)))), /* @__PURE__ */ React8.createElement(TableCell5, null, /* @__PURE__ */ React8.createElement(
|
|
3003
3131
|
Box8,
|
|
3004
3132
|
{
|
|
3005
3133
|
component: "span",
|
|
@@ -3016,7 +3144,7 @@ var init_ModelsTable = __esm({
|
|
|
3016
3144
|
}
|
|
3017
3145
|
},
|
|
3018
3146
|
m.mode
|
|
3019
|
-
)), /* @__PURE__ */ React8.createElement(
|
|
3147
|
+
)), /* @__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
3148
|
/* @__PURE__ */ React8.createElement(
|
|
3021
3149
|
Snackbar,
|
|
3022
3150
|
{
|
|
@@ -3036,14 +3164,14 @@ var init_ModelsTable = __esm({
|
|
|
3036
3164
|
import React9, { useState as useState7, useCallback as useCallback2 } from "react";
|
|
3037
3165
|
import Box9 from "@mui/material/Box";
|
|
3038
3166
|
import Typography9 from "@mui/material/Typography";
|
|
3039
|
-
import
|
|
3040
|
-
import
|
|
3041
|
-
import
|
|
3042
|
-
import
|
|
3043
|
-
import
|
|
3044
|
-
import
|
|
3167
|
+
import Table6 from "@mui/material/Table";
|
|
3168
|
+
import TableBody6 from "@mui/material/TableBody";
|
|
3169
|
+
import TableCell6 from "@mui/material/TableCell";
|
|
3170
|
+
import TableContainer6 from "@mui/material/TableContainer";
|
|
3171
|
+
import TableHead6 from "@mui/material/TableHead";
|
|
3172
|
+
import TableRow6 from "@mui/material/TableRow";
|
|
3045
3173
|
import TablePagination from "@mui/material/TablePagination";
|
|
3046
|
-
import
|
|
3174
|
+
import TextField6 from "@mui/material/TextField";
|
|
3047
3175
|
import MenuItem5 from "@mui/material/MenuItem";
|
|
3048
3176
|
import Skeleton5 from "@mui/material/Skeleton";
|
|
3049
3177
|
import Collapse2 from "@mui/material/Collapse";
|
|
@@ -3051,7 +3179,7 @@ import IconButton5 from "@mui/material/IconButton";
|
|
|
3051
3179
|
import Alert4 from "@mui/material/Alert";
|
|
3052
3180
|
import { alpha as alpha6 } from "@mui/material/styles";
|
|
3053
3181
|
import { KeyboardArrowDown } from "@mui/icons-material";
|
|
3054
|
-
import { useAsync } from "react-use";
|
|
3182
|
+
import { useAsync as useAsync2 } from "react-use";
|
|
3055
3183
|
function actionTone(action) {
|
|
3056
3184
|
if (!action) return "neutral";
|
|
3057
3185
|
for (const [key, tone] of Object.entries(ACTION_TONES)) {
|
|
@@ -3072,10 +3200,10 @@ function formatDateTime(iso) {
|
|
|
3072
3200
|
}
|
|
3073
3201
|
function renderAuditLogBody(loading, entries) {
|
|
3074
3202
|
if (loading) {
|
|
3075
|
-
return /* @__PURE__ */ React9.createElement(
|
|
3203
|
+
return /* @__PURE__ */ React9.createElement(TableRow6, null, /* @__PURE__ */ React9.createElement(TableCell6, { colSpan: 6, sx: { py: 2 } }, /* @__PURE__ */ React9.createElement(Skeleton5, { variant: "rounded", height: 160 })));
|
|
3076
3204
|
}
|
|
3077
3205
|
if (entries.length === 0) {
|
|
3078
|
-
return /* @__PURE__ */ React9.createElement(
|
|
3206
|
+
return /* @__PURE__ */ React9.createElement(TableRow6, null, /* @__PURE__ */ React9.createElement(TableCell6, { colSpan: 6 }, /* @__PURE__ */ React9.createElement(
|
|
3079
3207
|
EmptyState,
|
|
3080
3208
|
{
|
|
3081
3209
|
message: "No audit events found",
|
|
@@ -3107,7 +3235,7 @@ var init_AuditLog = __esm({
|
|
|
3107
3235
|
DetailRow = ({ entry }) => {
|
|
3108
3236
|
const [open, setOpen] = useState7(false);
|
|
3109
3237
|
const hasDetail = entry.before_value || entry.updated_values;
|
|
3110
|
-
return /* @__PURE__ */ React9.createElement(React9.Fragment, null, /* @__PURE__ */ React9.createElement(
|
|
3238
|
+
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
3239
|
IconButton5,
|
|
3112
3240
|
{
|
|
3113
3241
|
size: "small",
|
|
@@ -3121,7 +3249,7 @@ var init_AuditLog = __esm({
|
|
|
3121
3249
|
})
|
|
3122
3250
|
},
|
|
3123
3251
|
/* @__PURE__ */ React9.createElement(KeyboardArrowDown, { fontSize: "small" })
|
|
3124
|
-
)), /* @__PURE__ */ React9.createElement(
|
|
3252
|
+
)), /* @__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
3253
|
Typography9,
|
|
3126
3254
|
{
|
|
3127
3255
|
variant: "body2",
|
|
@@ -3131,7 +3259,7 @@ var init_AuditLog = __esm({
|
|
|
3131
3259
|
sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 12 }
|
|
3132
3260
|
},
|
|
3133
3261
|
entry.object_id ? entry.object_id.slice(0, 20) + (entry.object_id.length > 20 ? "\u2026" : "") : "\u2014"
|
|
3134
|
-
)), /* @__PURE__ */ React9.createElement(
|
|
3262
|
+
)), /* @__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
3263
|
Box9,
|
|
3136
3264
|
{
|
|
3137
3265
|
display: "flex",
|
|
@@ -3176,7 +3304,7 @@ var init_AuditLog = __esm({
|
|
|
3176
3304
|
() => ({ page: page + 1, page_size: pageSize, ...filters }),
|
|
3177
3305
|
[page, pageSize, filters]
|
|
3178
3306
|
)();
|
|
3179
|
-
const { value, loading, error } =
|
|
3307
|
+
const { value, loading, error } = useAsync2(
|
|
3180
3308
|
() => api.getAuditLogs(fetchParams),
|
|
3181
3309
|
[api, fetchParams]
|
|
3182
3310
|
);
|
|
@@ -3189,7 +3317,7 @@ var init_AuditLog = __esm({
|
|
|
3189
3317
|
subtitle: loading ? "Loading\u2026" : `${total.toLocaleString()} event${total === 1 ? "" : "s"}`,
|
|
3190
3318
|
flush: true,
|
|
3191
3319
|
actions: /* @__PURE__ */ React9.createElement(React9.Fragment, null, /* @__PURE__ */ React9.createElement(
|
|
3192
|
-
|
|
3320
|
+
TextField6,
|
|
3193
3321
|
{
|
|
3194
3322
|
size: "small",
|
|
3195
3323
|
label: "Action",
|
|
@@ -3207,7 +3335,7 @@ var init_AuditLog = __esm({
|
|
|
3207
3335
|
/* @__PURE__ */ React9.createElement(MenuItem5, { value: "deleted" }, "Deleted"),
|
|
3208
3336
|
/* @__PURE__ */ React9.createElement(MenuItem5, { value: "blocked" }, "Blocked")
|
|
3209
3337
|
), /* @__PURE__ */ React9.createElement(
|
|
3210
|
-
|
|
3338
|
+
TextField6,
|
|
3211
3339
|
{
|
|
3212
3340
|
size: "small",
|
|
3213
3341
|
label: "Table",
|
|
@@ -3226,7 +3354,7 @@ var init_AuditLog = __esm({
|
|
|
3226
3354
|
/* @__PURE__ */ React9.createElement(MenuItem5, { value: "LiteLLM_ObjectPermissionTable" }, "Team access (KB / MCP)"),
|
|
3227
3355
|
/* @__PURE__ */ React9.createElement(MenuItem5, { value: "LiteLLM_UserTable" }, "User")
|
|
3228
3356
|
), /* @__PURE__ */ React9.createElement(
|
|
3229
|
-
|
|
3357
|
+
TextField6,
|
|
3230
3358
|
{
|
|
3231
3359
|
size: "small",
|
|
3232
3360
|
label: "Changed by",
|
|
@@ -3240,7 +3368,7 @@ var init_AuditLog = __esm({
|
|
|
3240
3368
|
))
|
|
3241
3369
|
},
|
|
3242
3370
|
error && /* @__PURE__ */ React9.createElement(Box9, { px: 2.5, pb: 2 }, /* @__PURE__ */ React9.createElement(Alert4, { severity: "error" }, error.message)),
|
|
3243
|
-
/* @__PURE__ */ React9.createElement(
|
|
3371
|
+
/* @__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
3372
|
/* @__PURE__ */ React9.createElement(
|
|
3245
3373
|
TablePagination,
|
|
3246
3374
|
{
|
|
@@ -3296,18 +3424,17 @@ var LiteLLMPage_exports = {};
|
|
|
3296
3424
|
__export(LiteLLMPage_exports, {
|
|
3297
3425
|
LiteLLMPage: () => LiteLLMPage
|
|
3298
3426
|
});
|
|
3299
|
-
import React10, { useState as useState8, useCallback as useCallback3, useMemo as
|
|
3427
|
+
import React10, { useState as useState8, useCallback as useCallback3, useMemo as useMemo7 } from "react";
|
|
3300
3428
|
import Box10 from "@mui/material/Box";
|
|
3301
3429
|
import Snackbar2 from "@mui/material/Snackbar";
|
|
3302
3430
|
import Alert5 from "@mui/material/Alert";
|
|
3303
|
-
import
|
|
3431
|
+
import CircularProgress5 from "@mui/material/CircularProgress";
|
|
3304
3432
|
import Typography10 from "@mui/material/Typography";
|
|
3305
|
-
import
|
|
3433
|
+
import Paper6 from "@mui/material/Paper";
|
|
3306
3434
|
import Tabs2 from "@mui/material/Tabs";
|
|
3307
3435
|
import Tab2 from "@mui/material/Tab";
|
|
3308
|
-
import
|
|
3309
|
-
import {
|
|
3310
|
-
import { useApi } from "@backstage/core-plugin-api";
|
|
3436
|
+
import { useAsync as useAsync3, useAsyncRetry } from "react-use";
|
|
3437
|
+
import { useApi as useApi2 } from "@backstage/core-plugin-api";
|
|
3311
3438
|
import { usePermission } from "@backstage/plugin-permission-react";
|
|
3312
3439
|
function initDateRange() {
|
|
3313
3440
|
let preset = "7d";
|
|
@@ -3339,7 +3466,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3339
3466
|
init_permissions();
|
|
3340
3467
|
PERIOD_LS_KEY2 = "litellm_usage_period";
|
|
3341
3468
|
LiteLLMPage = () => {
|
|
3342
|
-
const api =
|
|
3469
|
+
const api = useApi2(liteLlmApiRef);
|
|
3343
3470
|
const [dateRange, setDateRange] = useState8(initDateRange);
|
|
3344
3471
|
const [currentPreset, setCurrentPreset] = useState8(() => {
|
|
3345
3472
|
try {
|
|
@@ -3354,7 +3481,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3354
3481
|
const [manageTeam, setManageTeam] = useState8(null);
|
|
3355
3482
|
const [teamUsageCache, setTeamUsageCache] = useState8({});
|
|
3356
3483
|
const [teamUsageLoading, setTeamUsageLoading] = useState8({});
|
|
3357
|
-
const { value: userInfo, loading: userLoading, error: userError } =
|
|
3484
|
+
const { value: userInfo, loading: userLoading, error: userError } = useAsync3(
|
|
3358
3485
|
() => api.getUserInfo(),
|
|
3359
3486
|
[api]
|
|
3360
3487
|
);
|
|
@@ -3369,7 +3496,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3369
3496
|
},
|
|
3370
3497
|
[api]
|
|
3371
3498
|
);
|
|
3372
|
-
const { value: allModels, loading: modelsLoading } =
|
|
3499
|
+
const { value: allModels, loading: modelsLoading } = useAsync3(
|
|
3373
3500
|
() => api.listModels().catch(() => []),
|
|
3374
3501
|
[api]
|
|
3375
3502
|
);
|
|
@@ -3377,8 +3504,8 @@ var init_LiteLLMPage = __esm({
|
|
|
3377
3504
|
() => api.getTeams().catch(() => []),
|
|
3378
3505
|
[api]
|
|
3379
3506
|
);
|
|
3380
|
-
const { value: liteLlmConfig } =
|
|
3381
|
-
const { value: managedTeams } =
|
|
3507
|
+
const { value: liteLlmConfig } = useAsync3(() => api.getConfig(), [api]);
|
|
3508
|
+
const { value: managedTeams } = useAsync3(
|
|
3382
3509
|
async () => liteLlmConfig?.teamManagement?.enabled ? api.getManagedTeams().catch(() => []) : [],
|
|
3383
3510
|
[api, liteLlmConfig]
|
|
3384
3511
|
);
|
|
@@ -3389,15 +3516,15 @@ var init_LiteLLMPage = __esm({
|
|
|
3389
3516
|
const { allowed: canManageMcpServers } = usePermission({ permission: litellmTeamMcpManagePermission });
|
|
3390
3517
|
const teamMgmtEnabled = liteLlmConfig?.teamManagement?.enabled ?? false;
|
|
3391
3518
|
const objectPermsEnabled = liteLlmConfig?.teamManagement?.objectPermissionsEnabled ?? false;
|
|
3392
|
-
const { value: vectorStores } =
|
|
3519
|
+
const { value: vectorStores } = useAsync3(
|
|
3393
3520
|
async () => objectPermsEnabled ? api.getVectorStores().catch(() => []) : [],
|
|
3394
3521
|
[api, objectPermsEnabled]
|
|
3395
3522
|
);
|
|
3396
|
-
const { value: mcpServers } =
|
|
3523
|
+
const { value: mcpServers } = useAsync3(
|
|
3397
3524
|
async () => objectPermsEnabled ? api.getMcpServers().catch(() => []) : [],
|
|
3398
3525
|
[api, objectPermsEnabled]
|
|
3399
3526
|
);
|
|
3400
|
-
const teams =
|
|
3527
|
+
const teams = useMemo7(() => {
|
|
3401
3528
|
if (!allTeams?.length) return [];
|
|
3402
3529
|
if (!userInfo) return allTeams;
|
|
3403
3530
|
const userId = userInfo.user_id;
|
|
@@ -3409,7 +3536,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3409
3536
|
);
|
|
3410
3537
|
return byMembership.length > 0 ? byMembership : allTeams;
|
|
3411
3538
|
}, [allTeams, userInfo]);
|
|
3412
|
-
const { value: usage, loading: usageLoading } =
|
|
3539
|
+
const { value: usage, loading: usageLoading } = useAsync3(async () => {
|
|
3413
3540
|
const startDate = dateRange.start.toISOString().split("T")[0];
|
|
3414
3541
|
const endDate = dateRange.end.toISOString().split("T")[0];
|
|
3415
3542
|
return api.getUsage(startDate, endDate);
|
|
@@ -3436,7 +3563,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3436
3563
|
setTeamUsageLoading((prev) => ({ ...prev, [teamId]: false }));
|
|
3437
3564
|
}
|
|
3438
3565
|
}, [api, dateRange, teamUsageCache, teamUsageLoading]);
|
|
3439
|
-
const allowedModels =
|
|
3566
|
+
const allowedModels = useMemo7(() => {
|
|
3440
3567
|
if (!allModels?.length) return [];
|
|
3441
3568
|
const userModels = userInfo?.models;
|
|
3442
3569
|
const teamModels = teams?.flatMap((t) => t.models ?? []);
|
|
@@ -3544,12 +3671,12 @@ var init_LiteLLMPage = __esm({
|
|
|
3544
3671
|
);
|
|
3545
3672
|
const isInitialLoading = userLoading && !userInfo;
|
|
3546
3673
|
if (isInitialLoading) {
|
|
3547
|
-
return /* @__PURE__ */ React10.createElement(Box10, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: "50vh" }, /* @__PURE__ */ React10.createElement(
|
|
3674
|
+
return /* @__PURE__ */ React10.createElement(Box10, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: "50vh" }, /* @__PURE__ */ React10.createElement(CircularProgress5, null));
|
|
3548
3675
|
}
|
|
3549
3676
|
if (userError || !userInfo) {
|
|
3550
3677
|
const isProvisioningEnabled = userError?.body?.provisioning === true;
|
|
3551
3678
|
const hint = userError?.body?.hint;
|
|
3552
|
-
return /* @__PURE__ */ React10.createElement(Box10, { p: 3 }, /* @__PURE__ */ React10.createElement(
|
|
3679
|
+
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
3680
|
}
|
|
3554
3681
|
const pageTabs = /* @__PURE__ */ React10.createElement(
|
|
3555
3682
|
Tabs2,
|
|
@@ -3610,14 +3737,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3610
3737
|
for (const t of managedTeams ?? []) teamsById.set(t.team_id, t);
|
|
3611
3738
|
for (const t of teams ?? []) teamsById.set(t.team_id, t);
|
|
3612
3739
|
const visibleTeams = Array.from(teamsById.values());
|
|
3613
|
-
return /* @__PURE__ */ React10.createElement(
|
|
3614
|
-
Button6,
|
|
3615
|
-
{
|
|
3616
|
-
variant: "contained",
|
|
3617
|
-
onClick: () => setManageTeam({ mode: "create" })
|
|
3618
|
-
},
|
|
3619
|
-
"Create Team"
|
|
3620
|
-
)), /* @__PURE__ */ React10.createElement(
|
|
3740
|
+
return /* @__PURE__ */ React10.createElement(
|
|
3621
3741
|
TeamUsage,
|
|
3622
3742
|
{
|
|
3623
3743
|
teams: visibleTeams,
|
|
@@ -3628,9 +3748,11 @@ var init_LiteLLMPage = __esm({
|
|
|
3628
3748
|
},
|
|
3629
3749
|
getTeamUsageLoading: (teamId) => teamUsageLoading[teamId] ?? false,
|
|
3630
3750
|
canManage: teamMgmtEnabled && canManageTeam,
|
|
3631
|
-
onEditTeam: (t) => setManageTeam({ mode: "edit", team: t })
|
|
3751
|
+
onEditTeam: (t) => setManageTeam({ mode: "edit", team: t }),
|
|
3752
|
+
canCreate: teamMgmtEnabled && canCreateTeam,
|
|
3753
|
+
onCreateTeam: () => setManageTeam({ mode: "create" })
|
|
3632
3754
|
}
|
|
3633
|
-
)
|
|
3755
|
+
);
|
|
3634
3756
|
})(), activeTab === "models" && /* @__PURE__ */ React10.createElement(
|
|
3635
3757
|
ModelsTable,
|
|
3636
3758
|
{
|
|
@@ -3771,17 +3893,17 @@ init_TeamUsage();
|
|
|
3771
3893
|
init_api();
|
|
3772
3894
|
init_format();
|
|
3773
3895
|
import React12, { useState as useState9, useEffect as useEffect3 } from "react";
|
|
3774
|
-
import
|
|
3896
|
+
import Paper7 from "@mui/material/Paper";
|
|
3775
3897
|
import Box11 from "@mui/material/Box";
|
|
3776
3898
|
import Typography11 from "@mui/material/Typography";
|
|
3777
3899
|
import FormControl2 from "@mui/material/FormControl";
|
|
3778
3900
|
import Select2 from "@mui/material/Select";
|
|
3779
3901
|
import MenuItem6 from "@mui/material/MenuItem";
|
|
3780
3902
|
import Grid2 from "@mui/material/Grid";
|
|
3781
|
-
import
|
|
3903
|
+
import CircularProgress6 from "@mui/material/CircularProgress";
|
|
3782
3904
|
import Alert6 from "@mui/material/Alert";
|
|
3783
3905
|
import { AreaChart as AreaChart3, Area as Area3, ResponsiveContainer as ResponsiveContainer3 } from "recharts";
|
|
3784
|
-
import { useApi as
|
|
3906
|
+
import { useApi as useApi3 } from "@backstage/core-plugin-api";
|
|
3785
3907
|
function presetToDateRange(preset) {
|
|
3786
3908
|
const end = /* @__PURE__ */ new Date();
|
|
3787
3909
|
const start = /* @__PURE__ */ new Date();
|
|
@@ -3799,7 +3921,7 @@ var LiteLLMHomeWidget = ({
|
|
|
3799
3921
|
defaultPeriod = "7d",
|
|
3800
3922
|
title = "LiteLLM Usage"
|
|
3801
3923
|
}) => {
|
|
3802
|
-
const api =
|
|
3924
|
+
const api = useApi3(liteLlmApiRef);
|
|
3803
3925
|
const [period, setPeriod] = useState9(defaultPeriod);
|
|
3804
3926
|
const [loading, setLoading] = useState9(true);
|
|
3805
3927
|
const [usageError, setUsageError] = useState9(null);
|
|
@@ -3843,7 +3965,7 @@ var LiteLLMHomeWidget = ({
|
|
|
3843
3965
|
spend: d.spend
|
|
3844
3966
|
}));
|
|
3845
3967
|
const hasSparkline = dailyData.length > 0;
|
|
3846
|
-
return /* @__PURE__ */ React12.createElement(
|
|
3968
|
+
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
3969
|
Select2,
|
|
3848
3970
|
{
|
|
3849
3971
|
value: period,
|
|
@@ -3853,7 +3975,7 @@ var LiteLLMHomeWidget = ({
|
|
|
3853
3975
|
/* @__PURE__ */ React12.createElement(MenuItem6, { value: "today" }, "Today"),
|
|
3854
3976
|
/* @__PURE__ */ React12.createElement(MenuItem6, { value: "7d" }, "7d"),
|
|
3855
3977
|
/* @__PURE__ */ React12.createElement(MenuItem6, { value: "30d" }, "30d")
|
|
3856
|
-
))), loading && /* @__PURE__ */ React12.createElement(Box11, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: 120 }, /* @__PURE__ */ React12.createElement(
|
|
3978
|
+
))), 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
3979
|
Area3,
|
|
3858
3980
|
{
|
|
3859
3981
|
type: "monotone",
|