@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/components/TeamUsage.d.ts +3 -0
- package/dist/index.cjs.js +233 -134
- package/dist/index.cjs.js.map +3 -3
- package/dist/index.esm.js +242 -143
- 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";
|
|
@@ -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 =
|
|
3039
|
+
const selectedTeam = useMemo6(
|
|
2935
3040
|
() => teams.find((t) => t.team_id === selectedTeamId) ?? null,
|
|
2936
3041
|
[teams, selectedTeamId]
|
|
2937
3042
|
);
|
|
2938
|
-
const filteredModels =
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
3040
|
-
import
|
|
3041
|
-
import
|
|
3042
|
-
import
|
|
3043
|
-
import
|
|
3044
|
-
import
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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 } =
|
|
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(
|
|
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
|
|
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
|
|
3408
|
+
import CircularProgress5 from "@mui/material/CircularProgress";
|
|
3304
3409
|
import Typography10 from "@mui/material/Typography";
|
|
3305
|
-
import
|
|
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
|
|
3309
|
-
import {
|
|
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 =
|
|
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 } =
|
|
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 } =
|
|
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 } =
|
|
3381
|
-
const { value: managedTeams } =
|
|
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 } =
|
|
3496
|
+
const { value: vectorStores } = useAsync3(
|
|
3393
3497
|
async () => objectPermsEnabled ? api.getVectorStores().catch(() => []) : [],
|
|
3394
3498
|
[api, objectPermsEnabled]
|
|
3395
3499
|
);
|
|
3396
|
-
const { value: mcpServers } =
|
|
3500
|
+
const { value: mcpServers } = useAsync3(
|
|
3397
3501
|
async () => objectPermsEnabled ? api.getMcpServers().catch(() => []) : [],
|
|
3398
3502
|
[api, objectPermsEnabled]
|
|
3399
3503
|
);
|
|
3400
|
-
const teams =
|
|
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 } =
|
|
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 =
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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(
|
|
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(
|
|
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",
|