@acarmisc/backstage-plugin-litellm 0.16.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 +282 -167
- package/dist/index.cjs.js.map +3 -3
- package/dist/index.esm.js +292 -177
- package/dist/index.esm.js.map +3 -3
- package/dist/types.d.ts +3 -2
- 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,18 +1931,38 @@ 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";
|
|
1935
|
+
import MenuItem2 from "@mui/material/MenuItem";
|
|
1934
1936
|
import Box5 from "@mui/material/Box";
|
|
1935
1937
|
import Divider2 from "@mui/material/Divider";
|
|
1936
1938
|
import Typography5 from "@mui/material/Typography";
|
|
1937
1939
|
import IconButton3 from "@mui/material/IconButton";
|
|
1938
|
-
import
|
|
1939
|
-
import
|
|
1940
|
-
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";
|
|
1941
1947
|
import { Delete as DeleteIcon } from "@mui/icons-material";
|
|
1942
|
-
|
|
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";
|
|
1952
|
+
var BUDGET_DURATION_OPTIONS, DEFAULT_BUDGET_DURATION, FALLBACK_BUDGET_CEILING, ManageTeamDialog;
|
|
1943
1953
|
var init_ManageTeamDialog = __esm({
|
|
1944
1954
|
"src/components/ManageTeamDialog.tsx"() {
|
|
1945
1955
|
"use strict";
|
|
1956
|
+
init_ui();
|
|
1957
|
+
BUDGET_DURATION_OPTIONS = [
|
|
1958
|
+
{ value: "1d", label: "Daily (1d)" },
|
|
1959
|
+
{ value: "7d", label: "Weekly (7d)" },
|
|
1960
|
+
{ value: "30d", label: "Monthly (30d)" },
|
|
1961
|
+
{ value: "90d", label: "Quarterly (90d)" },
|
|
1962
|
+
{ value: "365d", label: "Yearly (365d)" }
|
|
1963
|
+
];
|
|
1964
|
+
DEFAULT_BUDGET_DURATION = "30d";
|
|
1965
|
+
FALLBACK_BUDGET_CEILING = 1e3;
|
|
1946
1966
|
ManageTeamDialog = ({
|
|
1947
1967
|
open,
|
|
1948
1968
|
onClose,
|
|
@@ -1968,6 +1988,7 @@ var init_ManageTeamDialog = __esm({
|
|
|
1968
1988
|
const [budgetDuration, setBudgetDuration] = useState3("");
|
|
1969
1989
|
const [submitting, setSubmitting] = useState3(false);
|
|
1970
1990
|
const [error, setError] = useState3(null);
|
|
1991
|
+
const [memberQuery, setMemberQuery] = useState3("");
|
|
1971
1992
|
const [memberRef, setMemberRef] = useState3("");
|
|
1972
1993
|
const [memberBudget, setMemberBudget] = useState3("");
|
|
1973
1994
|
const [memberBusy, setMemberBusy] = useState3(false);
|
|
@@ -1979,23 +2000,56 @@ var init_ManageTeamDialog = __esm({
|
|
|
1979
2000
|
const [mcpBusy, setMcpBusy] = useState3(false);
|
|
1980
2001
|
const [mcpError, setMcpError] = useState3(null);
|
|
1981
2002
|
const allowUnlimitedBudget = config?.teamManagement?.allowUnlimitedBudget ?? false;
|
|
1982
|
-
const maxBudgetCeiling = config?.teamManagement?.maxBudgetCeiling;
|
|
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]);
|
|
1983
2034
|
useEffect2(() => {
|
|
1984
2035
|
if (open) {
|
|
1985
2036
|
if (mode === "edit" && team) {
|
|
1986
2037
|
setAlias(team.team_alias ?? "");
|
|
1987
2038
|
setModels(team.models ?? []);
|
|
1988
|
-
setMaxBudget(
|
|
2039
|
+
setMaxBudget(
|
|
2040
|
+
team.max_budget ? String(team.max_budget) : String(maxBudgetCeiling)
|
|
2041
|
+
);
|
|
1989
2042
|
setUnlimited(allowUnlimitedBudget && !team.max_budget);
|
|
1990
|
-
setBudgetDuration(
|
|
2043
|
+
setBudgetDuration(team.budget_duration ?? DEFAULT_BUDGET_DURATION);
|
|
1991
2044
|
} else {
|
|
1992
2045
|
setAlias("");
|
|
1993
2046
|
setModels([]);
|
|
1994
|
-
setMaxBudget(
|
|
2047
|
+
setMaxBudget(String(maxBudgetCeiling));
|
|
1995
2048
|
setUnlimited(allowUnlimitedBudget);
|
|
1996
|
-
setBudgetDuration(
|
|
2049
|
+
setBudgetDuration(DEFAULT_BUDGET_DURATION);
|
|
1997
2050
|
}
|
|
1998
2051
|
setError(null);
|
|
2052
|
+
setMemberQuery("");
|
|
1999
2053
|
setMemberRef("");
|
|
2000
2054
|
setMemberBudget("");
|
|
2001
2055
|
setMemberError(null);
|
|
@@ -2004,7 +2058,7 @@ var init_ManageTeamDialog = __esm({
|
|
|
2004
2058
|
setMcpIds(team?.object_permission?.mcp_servers ?? []);
|
|
2005
2059
|
setMcpError(null);
|
|
2006
2060
|
}
|
|
2007
|
-
}, [open, mode, team, allowUnlimitedBudget]);
|
|
2061
|
+
}, [open, mode, team, allowUnlimitedBudget, maxBudgetCeiling]);
|
|
2008
2062
|
const handleSubmit = async () => {
|
|
2009
2063
|
setError(null);
|
|
2010
2064
|
if (!alias.trim()) {
|
|
@@ -2025,7 +2079,7 @@ var init_ManageTeamDialog = __esm({
|
|
|
2025
2079
|
setError("Budget must be a positive number");
|
|
2026
2080
|
return;
|
|
2027
2081
|
}
|
|
2028
|
-
if (
|
|
2082
|
+
if (budget > maxBudgetCeiling) {
|
|
2029
2083
|
setError(`Budget cannot exceed $${maxBudgetCeiling}`);
|
|
2030
2084
|
return;
|
|
2031
2085
|
}
|
|
@@ -2067,6 +2121,7 @@ var init_ManageTeamDialog = __esm({
|
|
|
2067
2121
|
try {
|
|
2068
2122
|
setMemberBusy(true);
|
|
2069
2123
|
await onAddMember(memberRef.trim(), budget);
|
|
2124
|
+
setMemberQuery("");
|
|
2070
2125
|
setMemberRef("");
|
|
2071
2126
|
setMemberBudget("");
|
|
2072
2127
|
} catch (err) {
|
|
@@ -2112,8 +2167,7 @@ var init_ManageTeamDialog = __esm({
|
|
|
2112
2167
|
}
|
|
2113
2168
|
};
|
|
2114
2169
|
const modelNames = allModels.map((m) => m.model_name);
|
|
2115
|
-
const
|
|
2116
|
-
const showMembers = mode === "edit" && !!canManageMembers;
|
|
2170
|
+
const durationOptions = BUDGET_DURATION_OPTIONS.some((o) => o.value === budgetDuration) ? BUDGET_DURATION_OPTIONS : [...BUDGET_DURATION_OPTIONS, { value: budgetDuration, label: budgetDuration }];
|
|
2117
2171
|
const showKnowledgeBases = mode === "edit" && !!canManageKnowledgeBases;
|
|
2118
2172
|
const kbOptions = (vectorStores ?? []).map((v) => v.id);
|
|
2119
2173
|
const kbLabel = (id) => (vectorStores ?? []).find((v) => v.id === id)?.name ?? id;
|
|
@@ -2137,7 +2191,15 @@ var init_ManageTeamDialog = __esm({
|
|
|
2137
2191
|
value: models,
|
|
2138
2192
|
onChange: (_, newValue) => setModels(newValue),
|
|
2139
2193
|
disabled: submitting,
|
|
2140
|
-
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
|
+
)
|
|
2141
2203
|
}
|
|
2142
2204
|
), /* @__PURE__ */ React5.createElement(
|
|
2143
2205
|
TextField3,
|
|
@@ -2147,7 +2209,8 @@ var init_ManageTeamDialog = __esm({
|
|
|
2147
2209
|
value: maxBudget,
|
|
2148
2210
|
onChange: (e) => setMaxBudget(e.target.value),
|
|
2149
2211
|
disabled: submitting || unlimited,
|
|
2150
|
-
|
|
2212
|
+
inputProps: { min: 0, max: maxBudgetCeiling },
|
|
2213
|
+
helperText: `Maximum: $${maxBudgetCeiling}`,
|
|
2151
2214
|
fullWidth: true
|
|
2152
2215
|
}
|
|
2153
2216
|
), allowUnlimitedBudget && /* @__PURE__ */ React5.createElement(
|
|
@@ -2159,41 +2222,78 @@ var init_ManageTeamDialog = __esm({
|
|
|
2159
2222
|
), /* @__PURE__ */ React5.createElement(
|
|
2160
2223
|
TextField3,
|
|
2161
2224
|
{
|
|
2225
|
+
select: true,
|
|
2162
2226
|
label: "Budget Duration",
|
|
2163
2227
|
value: budgetDuration,
|
|
2164
2228
|
onChange: (e) => setBudgetDuration(e.target.value),
|
|
2165
|
-
|
|
2166
|
-
|
|
2229
|
+
disabled: submitting || unlimited,
|
|
2230
|
+
helperText: "Spend-reset period for the team budget",
|
|
2167
2231
|
fullWidth: true
|
|
2168
|
-
}
|
|
2169
|
-
|
|
2170
|
-
|
|
2232
|
+
},
|
|
2233
|
+
durationOptions.map((o) => /* @__PURE__ */ React5.createElement(MenuItem2, { key: o.value, value: o.value }, o.label))
|
|
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,
|
|
2171
2236
|
{
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
IconButton3,
|
|
2176
|
-
{
|
|
2177
|
-
edge: "end",
|
|
2178
|
-
size: "small",
|
|
2179
|
-
"aria-label": `remove ${m.user_id}`,
|
|
2180
|
-
disabled: memberBusy,
|
|
2181
|
-
onClick: () => handleRemoveMember(m.user_id)
|
|
2182
|
-
},
|
|
2183
|
-
/* @__PURE__ */ React5.createElement(DeleteIcon, { fontSize: "small" })
|
|
2184
|
-
)
|
|
2237
|
+
variant: "caption",
|
|
2238
|
+
color: "text.secondary",
|
|
2239
|
+
sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 11 }
|
|
2185
2240
|
},
|
|
2186
|
-
|
|
2187
|
-
))
|
|
2188
|
-
|
|
2241
|
+
m.user_id
|
|
2242
|
+
)) : /* @__PURE__ */ React5.createElement(
|
|
2243
|
+
Typography5,
|
|
2189
2244
|
{
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
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",
|
|
2195
2253
|
size: "small",
|
|
2196
|
-
|
|
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
|
+
)
|
|
2197
2297
|
}
|
|
2198
2298
|
), /* @__PURE__ */ React5.createElement(
|
|
2199
2299
|
TextField3,
|
|
@@ -2259,19 +2359,19 @@ var init_ManageTeamDialog = __esm({
|
|
|
2259
2359
|
});
|
|
2260
2360
|
|
|
2261
2361
|
// src/components/UsageStats.tsx
|
|
2262
|
-
import React6, { useMemo as
|
|
2263
|
-
import
|
|
2362
|
+
import React6, { useMemo as useMemo5, useState as useState4 } from "react";
|
|
2363
|
+
import Paper4 from "@mui/material/Paper";
|
|
2264
2364
|
import Box6 from "@mui/material/Box";
|
|
2265
2365
|
import Typography6 from "@mui/material/Typography";
|
|
2266
2366
|
import TextField4 from "@mui/material/TextField";
|
|
2267
|
-
import
|
|
2367
|
+
import MenuItem3 from "@mui/material/MenuItem";
|
|
2268
2368
|
import Grid from "@mui/material/Grid";
|
|
2269
|
-
import
|
|
2270
|
-
import
|
|
2271
|
-
import
|
|
2272
|
-
import
|
|
2273
|
-
import
|
|
2274
|
-
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";
|
|
2275
2375
|
import Skeleton3 from "@mui/material/Skeleton";
|
|
2276
2376
|
import {
|
|
2277
2377
|
AreaChart,
|
|
@@ -2352,7 +2452,7 @@ var init_UsageStats = __esm({
|
|
|
2352
2452
|
}
|
|
2353
2453
|
onDateRangeChange({ start, end }, preset);
|
|
2354
2454
|
};
|
|
2355
|
-
const dailyData =
|
|
2455
|
+
const dailyData = useMemo5(
|
|
2356
2456
|
() => (usage?.daily_usage ?? []).map((d) => ({
|
|
2357
2457
|
date: d.date,
|
|
2358
2458
|
spend: d.spend,
|
|
@@ -2365,21 +2465,21 @@ var init_UsageStats = __esm({
|
|
|
2365
2465
|
})),
|
|
2366
2466
|
[usage]
|
|
2367
2467
|
);
|
|
2368
|
-
const cumulativeData =
|
|
2468
|
+
const cumulativeData = useMemo5(() => {
|
|
2369
2469
|
let cum = 0;
|
|
2370
2470
|
return dailyData.map((d) => {
|
|
2371
2471
|
cum += d.spend;
|
|
2372
2472
|
return { date: d.date, cumulative: cum };
|
|
2373
2473
|
});
|
|
2374
2474
|
}, [dailyData]);
|
|
2375
|
-
const successRateData =
|
|
2475
|
+
const successRateData = useMemo5(
|
|
2376
2476
|
() => dailyData.filter((d) => d.apiRequests > 0).map((d) => ({
|
|
2377
2477
|
date: d.date,
|
|
2378
2478
|
successRate: parseFloat((d.successfulRequests / d.apiRequests * 100).toFixed(1))
|
|
2379
2479
|
})),
|
|
2380
2480
|
[dailyData]
|
|
2381
2481
|
);
|
|
2382
|
-
const { modelSpendByDate, topModels: topSpendModels } =
|
|
2482
|
+
const { modelSpendByDate, topModels: topSpendModels } = useMemo5(() => {
|
|
2383
2483
|
const rows = usage?.daily_by_model ?? [];
|
|
2384
2484
|
const modelTotals = {};
|
|
2385
2485
|
for (const r of rows) modelTotals[r.model] = (modelTotals[r.model] ?? 0) + r.spend;
|
|
@@ -2394,7 +2494,7 @@ var init_UsageStats = __esm({
|
|
|
2394
2494
|
const hasOther = sorted.some((r) => r.Other > 0);
|
|
2395
2495
|
return { modelSpendByDate: sorted, topModels: hasOther ? [...top, "Other"] : top };
|
|
2396
2496
|
}, [usage]);
|
|
2397
|
-
const modelRows =
|
|
2497
|
+
const modelRows = useMemo5(() => {
|
|
2398
2498
|
const entries = Object.entries(usage?.usage_by_model ?? {}).map(([model, d]) => ({
|
|
2399
2499
|
model,
|
|
2400
2500
|
spend: d.total_spend,
|
|
@@ -2408,11 +2508,11 @@ var init_UsageStats = __esm({
|
|
|
2408
2508
|
}));
|
|
2409
2509
|
return selectedModel === "all" ? entries : entries.filter((e) => e.model === selectedModel);
|
|
2410
2510
|
}, [usage, selectedModel]);
|
|
2411
|
-
const topModelSpendBars =
|
|
2511
|
+
const topModelSpendBars = useMemo5(
|
|
2412
2512
|
() => [...modelRows].sort((a, b) => b.spend - a.spend).slice(0, 10),
|
|
2413
2513
|
[modelRows]
|
|
2414
2514
|
);
|
|
2415
|
-
const keyRows =
|
|
2515
|
+
const keyRows = useMemo5(
|
|
2416
2516
|
() => Object.entries(usage?.usage_by_key ?? {}).map(([keyHash, d]) => ({
|
|
2417
2517
|
keyHash,
|
|
2418
2518
|
keyAlias: d.key_alias ?? keyHash.slice(0, 8),
|
|
@@ -2429,7 +2529,7 @@ var init_UsageStats = __esm({
|
|
|
2429
2529
|
})),
|
|
2430
2530
|
[usage]
|
|
2431
2531
|
);
|
|
2432
|
-
const topKeySpendBars =
|
|
2532
|
+
const topKeySpendBars = useMemo5(
|
|
2433
2533
|
() => [...keyRows].sort((a, b) => b.spend - a.spend).slice(0, 10),
|
|
2434
2534
|
[keyRows]
|
|
2435
2535
|
);
|
|
@@ -2467,21 +2567,21 @@ var init_UsageStats = __esm({
|
|
|
2467
2567
|
];
|
|
2468
2568
|
const renderKeyRows = () => {
|
|
2469
2569
|
if (loading) {
|
|
2470
|
-
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 })));
|
|
2471
2571
|
}
|
|
2472
2572
|
if (keyRows.length === 0) {
|
|
2473
|
-
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" })));
|
|
2474
2574
|
}
|
|
2475
|
-
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 }))));
|
|
2476
2576
|
};
|
|
2477
2577
|
const renderModelRows = () => {
|
|
2478
2578
|
if (loading) {
|
|
2479
|
-
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 })));
|
|
2480
2580
|
}
|
|
2481
2581
|
if (modelRows.length === 0) {
|
|
2482
|
-
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" })));
|
|
2483
2583
|
}
|
|
2484
|
-
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 }))));
|
|
2485
2585
|
};
|
|
2486
2586
|
const periodSelect = /* @__PURE__ */ React6.createElement(
|
|
2487
2587
|
TextField4,
|
|
@@ -2493,7 +2593,7 @@ var init_UsageStats = __esm({
|
|
|
2493
2593
|
onChange: (e) => handlePresetChange(e.target.value),
|
|
2494
2594
|
sx: { minWidth: 160 }
|
|
2495
2595
|
},
|
|
2496
|
-
Object.keys(PRESET_LABELS).map((p) => /* @__PURE__ */ React6.createElement(
|
|
2596
|
+
Object.keys(PRESET_LABELS).map((p) => /* @__PURE__ */ React6.createElement(MenuItem3, { key: p, value: p }, PRESET_LABELS[p]))
|
|
2497
2597
|
);
|
|
2498
2598
|
return /* @__PURE__ */ React6.createElement(
|
|
2499
2599
|
SectionCard,
|
|
@@ -2538,8 +2638,8 @@ var init_UsageStats = __esm({
|
|
|
2538
2638
|
onChange: (e) => setSelectedModel(e.target.value),
|
|
2539
2639
|
sx: { minWidth: 220 }
|
|
2540
2640
|
},
|
|
2541
|
-
/* @__PURE__ */ React6.createElement(
|
|
2542
|
-
models.map((m) => /* @__PURE__ */ React6.createElement(
|
|
2641
|
+
/* @__PURE__ */ React6.createElement(MenuItem3, { value: "all" }, "All models"),
|
|
2642
|
+
models.map((m) => /* @__PURE__ */ React6.createElement(MenuItem3, { key: m.model_name, value: m.model_name }, m.model_name))
|
|
2543
2643
|
)
|
|
2544
2644
|
),
|
|
2545
2645
|
tab === "costs" && /* @__PURE__ */ React6.createElement(Grid, { container: true, spacing: 2 }, /* @__PURE__ */ React6.createElement(Grid, { item: true, xs: 12 }, /* @__PURE__ */ React6.createElement(ChartCard, { title: "Daily spend by model", height: 260 }, /* @__PURE__ */ React6.createElement(ChartOrFallback, { loading, empty: modelSpendByDate.length === 0, height: 260 }, /* @__PURE__ */ React6.createElement(ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ React6.createElement(AreaChart, { data: modelSpendByDate, margin: { top: 4, right: 8, left: 0, bottom: 0 } }, /* @__PURE__ */ React6.createElement(CartesianGrid, { ...chart.grid }), /* @__PURE__ */ React6.createElement(XAxis, { dataKey: "date", tickFormatter: fmtDateShort, ...chart.axis }), /* @__PURE__ */ React6.createElement(YAxis, { tickFormatter: fmtUsdCompact, width: 56, ...chart.axis }), /* @__PURE__ */ React6.createElement(
|
|
@@ -2634,7 +2734,7 @@ var init_UsageStats = __esm({
|
|
|
2634
2734
|
height: Math.max(200, topModelSpendBars.length * 34)
|
|
2635
2735
|
},
|
|
2636
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] })))
|
|
2637
|
-
))), /* @__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()))))),
|
|
2638
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(
|
|
2639
2739
|
ChartOrFallback,
|
|
2640
2740
|
{
|
|
@@ -2643,7 +2743,7 @@ var init_UsageStats = __esm({
|
|
|
2643
2743
|
height: Math.max(160, topKeySpendBars.length * 32)
|
|
2644
2744
|
},
|
|
2645
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 })))
|
|
2646
|
-
))), /* @__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())))))
|
|
2647
2747
|
);
|
|
2648
2748
|
};
|
|
2649
2749
|
}
|
|
@@ -2651,23 +2751,23 @@ var init_UsageStats = __esm({
|
|
|
2651
2751
|
|
|
2652
2752
|
// src/components/TeamUsage.tsx
|
|
2653
2753
|
import React7, { useState as useState5 } from "react";
|
|
2654
|
-
import
|
|
2754
|
+
import Paper5 from "@mui/material/Paper";
|
|
2655
2755
|
import Box7 from "@mui/material/Box";
|
|
2656
2756
|
import Typography7 from "@mui/material/Typography";
|
|
2657
2757
|
import Skeleton4 from "@mui/material/Skeleton";
|
|
2658
2758
|
import Divider3 from "@mui/material/Divider";
|
|
2659
|
-
import
|
|
2660
|
-
import
|
|
2661
|
-
import
|
|
2662
|
-
import
|
|
2663
|
-
import
|
|
2664
|
-
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";
|
|
2665
2765
|
import Collapse from "@mui/material/Collapse";
|
|
2666
2766
|
import IconButton4 from "@mui/material/IconButton";
|
|
2667
2767
|
import Button5 from "@mui/material/Button";
|
|
2668
|
-
import
|
|
2768
|
+
import CircularProgress4 from "@mui/material/CircularProgress";
|
|
2669
2769
|
import { alpha as alpha4 } from "@mui/material/styles";
|
|
2670
|
-
import { ExpandMore as ExpandMore2, Group } from "@mui/icons-material";
|
|
2770
|
+
import { ExpandMore as ExpandMore2, Group, Add as Add3 } from "@mui/icons-material";
|
|
2671
2771
|
import {
|
|
2672
2772
|
AreaChart as AreaChart2,
|
|
2673
2773
|
Area as Area2,
|
|
@@ -2699,7 +2799,7 @@ var init_TeamUsage = __esm({
|
|
|
2699
2799
|
const dailyData = usage?.daily_usage?.map((d) => ({ date: d.date, spend: d.spend })) ?? [];
|
|
2700
2800
|
const renderDailySpendSection = () => {
|
|
2701
2801
|
if (usageLoading) {
|
|
2702
|
-
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 }));
|
|
2703
2803
|
}
|
|
2704
2804
|
if (dailyData.length === 0) return null;
|
|
2705
2805
|
return /* @__PURE__ */ React7.createElement(Box7, { mb: 2.5 }, /* @__PURE__ */ React7.createElement(
|
|
@@ -2738,7 +2838,7 @@ var init_TeamUsage = __esm({
|
|
|
2738
2838
|
},
|
|
2739
2839
|
label
|
|
2740
2840
|
);
|
|
2741
|
-
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(
|
|
2742
2842
|
Box7,
|
|
2743
2843
|
{
|
|
2744
2844
|
sx: (theme) => ({
|
|
@@ -2803,13 +2903,13 @@ var init_TeamUsage = __esm({
|
|
|
2803
2903
|
/* @__PURE__ */ React7.createElement(Stat, { label: "TPM", value: team.tpm_limit && team.tpm_limit > 0 ? team.tpm_limit.toLocaleString() : "\u2014" }),
|
|
2804
2904
|
/* @__PURE__ */ React7.createElement(Stat, { label: "RPM", value: team.rpm_limit && team.rpm_limit > 0 ? team.rpm_limit.toLocaleString() : "\u2014" })
|
|
2805
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(
|
|
2806
|
-
|
|
2906
|
+
TableContainer4,
|
|
2807
2907
|
{
|
|
2808
|
-
component:
|
|
2908
|
+
component: Paper5,
|
|
2809
2909
|
variant: "outlined",
|
|
2810
2910
|
sx: { borderRadius: 1.5 }
|
|
2811
2911
|
},
|
|
2812
|
-
/* @__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(
|
|
2813
2913
|
Typography7,
|
|
2814
2914
|
{
|
|
2815
2915
|
variant: "caption",
|
|
@@ -2824,7 +2924,7 @@ var init_TeamUsage = __esm({
|
|
|
2824
2924
|
sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace" }
|
|
2825
2925
|
},
|
|
2826
2926
|
m.user_id
|
|
2827
|
-
)), /* @__PURE__ */ React7.createElement(
|
|
2927
|
+
)), /* @__PURE__ */ React7.createElement(TableCell4, { align: "right" }, /* @__PURE__ */ React7.createElement(
|
|
2828
2928
|
StatusPill,
|
|
2829
2929
|
{
|
|
2830
2930
|
label: m.role,
|
|
@@ -2840,13 +2940,26 @@ var init_TeamUsage = __esm({
|
|
|
2840
2940
|
getTeamUsage,
|
|
2841
2941
|
getTeamUsageLoading,
|
|
2842
2942
|
canManage,
|
|
2843
|
-
onEditTeam
|
|
2943
|
+
onEditTeam,
|
|
2944
|
+
canCreate,
|
|
2945
|
+
onCreateTeam
|
|
2844
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;
|
|
2845
2958
|
if (loading) {
|
|
2846
|
-
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 }));
|
|
2847
2960
|
}
|
|
2848
2961
|
if (!teams.length) {
|
|
2849
|
-
return /* @__PURE__ */ React7.createElement(SectionCard, { title: "Teams" }, /* @__PURE__ */ React7.createElement(
|
|
2962
|
+
return /* @__PURE__ */ React7.createElement(SectionCard, { title: "Teams", actions: createAction }, /* @__PURE__ */ React7.createElement(
|
|
2850
2963
|
EmptyState,
|
|
2851
2964
|
{
|
|
2852
2965
|
message: "You're not a member of any LiteLLM team yet.",
|
|
@@ -2854,32 +2967,40 @@ var init_TeamUsage = __esm({
|
|
|
2854
2967
|
}
|
|
2855
2968
|
));
|
|
2856
2969
|
}
|
|
2857
|
-
return /* @__PURE__ */ React7.createElement(
|
|
2858
|
-
|
|
2970
|
+
return /* @__PURE__ */ React7.createElement(
|
|
2971
|
+
SectionCard,
|
|
2859
2972
|
{
|
|
2860
|
-
|
|
2861
|
-
team
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
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
|
+
);
|
|
2868
2989
|
};
|
|
2869
2990
|
}
|
|
2870
2991
|
});
|
|
2871
2992
|
|
|
2872
2993
|
// src/components/ModelsTable.tsx
|
|
2873
|
-
import React8, { useState as useState6, useMemo as
|
|
2994
|
+
import React8, { useState as useState6, useMemo as useMemo6, useCallback } from "react";
|
|
2874
2995
|
import Box8 from "@mui/material/Box";
|
|
2875
|
-
import
|
|
2876
|
-
import
|
|
2877
|
-
import
|
|
2878
|
-
import
|
|
2879
|
-
import
|
|
2880
|
-
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";
|
|
2881
3002
|
import Typography8 from "@mui/material/Typography";
|
|
2882
|
-
import
|
|
3003
|
+
import MenuItem4 from "@mui/material/MenuItem";
|
|
2883
3004
|
import Select from "@mui/material/Select";
|
|
2884
3005
|
import FormControl from "@mui/material/FormControl";
|
|
2885
3006
|
import InputLabel from "@mui/material/InputLabel";
|
|
@@ -2915,11 +3036,11 @@ var init_ModelsTable = __esm({
|
|
|
2915
3036
|
const theme = useTheme2();
|
|
2916
3037
|
const [selectedTeamId, setSelectedTeamId] = useState6("");
|
|
2917
3038
|
const [copiedSnackbar, setCopiedSnackbar] = useState6(false);
|
|
2918
|
-
const selectedTeam =
|
|
3039
|
+
const selectedTeam = useMemo6(
|
|
2919
3040
|
() => teams.find((t) => t.team_id === selectedTeamId) ?? null,
|
|
2920
3041
|
[teams, selectedTeamId]
|
|
2921
3042
|
);
|
|
2922
|
-
const filteredModels =
|
|
3043
|
+
const filteredModels = useMemo6(() => {
|
|
2923
3044
|
if (!selectedTeam) return [];
|
|
2924
3045
|
return allModels.filter((model) => isModelAllowedByTeam2(model, selectedTeam.models));
|
|
2925
3046
|
}, [allModels, selectedTeam]);
|
|
@@ -2954,7 +3075,7 @@ var init_ModelsTable = __esm({
|
|
|
2954
3075
|
label: "Team",
|
|
2955
3076
|
onChange: (e) => setSelectedTeamId(e.target.value)
|
|
2956
3077
|
},
|
|
2957
|
-
teams.map((t) => /* @__PURE__ */ React8.createElement(
|
|
3078
|
+
teams.map((t) => /* @__PURE__ */ React8.createElement(MenuItem4, { key: t.team_id, value: t.team_id }, t.team_alias ?? t.team_id))
|
|
2958
3079
|
))),
|
|
2959
3080
|
loading && /* @__PURE__ */ React8.createElement(EmptyState, { message: "Loading models\u2026" }),
|
|
2960
3081
|
!loading && !selectedTeamId && /* @__PURE__ */ React8.createElement(
|
|
@@ -2971,7 +3092,7 @@ var init_ModelsTable = __esm({
|
|
|
2971
3092
|
hint: allModels.length === 0 ? "No models are configured in the system" : "None of the team's assigned models were found in the catalog"
|
|
2972
3093
|
}
|
|
2973
3094
|
),
|
|
2974
|
-
!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(
|
|
2975
3096
|
Box8,
|
|
2976
3097
|
{
|
|
2977
3098
|
component: "span",
|
|
@@ -2983,7 +3104,7 @@ var init_ModelsTable = __esm({
|
|
|
2983
3104
|
style: { cursor: "pointer", display: "inline-flex" }
|
|
2984
3105
|
},
|
|
2985
3106
|
/* @__PURE__ */ React8.createElement(ContentCopyIcon, { sx: { fontSize: 14 } })
|
|
2986
|
-
)))), /* @__PURE__ */ React8.createElement(
|
|
3107
|
+
)))), /* @__PURE__ */ React8.createElement(TableCell5, null, /* @__PURE__ */ React8.createElement(
|
|
2987
3108
|
Box8,
|
|
2988
3109
|
{
|
|
2989
3110
|
component: "span",
|
|
@@ -3000,7 +3121,7 @@ var init_ModelsTable = __esm({
|
|
|
3000
3121
|
}
|
|
3001
3122
|
},
|
|
3002
3123
|
m.mode
|
|
3003
|
-
)), /* @__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" })))))))),
|
|
3004
3125
|
/* @__PURE__ */ React8.createElement(
|
|
3005
3126
|
Snackbar,
|
|
3006
3127
|
{
|
|
@@ -3020,22 +3141,22 @@ var init_ModelsTable = __esm({
|
|
|
3020
3141
|
import React9, { useState as useState7, useCallback as useCallback2 } from "react";
|
|
3021
3142
|
import Box9 from "@mui/material/Box";
|
|
3022
3143
|
import Typography9 from "@mui/material/Typography";
|
|
3023
|
-
import
|
|
3024
|
-
import
|
|
3025
|
-
import
|
|
3026
|
-
import
|
|
3027
|
-
import
|
|
3028
|
-
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";
|
|
3029
3150
|
import TablePagination from "@mui/material/TablePagination";
|
|
3030
3151
|
import TextField5 from "@mui/material/TextField";
|
|
3031
|
-
import
|
|
3152
|
+
import MenuItem5 from "@mui/material/MenuItem";
|
|
3032
3153
|
import Skeleton5 from "@mui/material/Skeleton";
|
|
3033
3154
|
import Collapse2 from "@mui/material/Collapse";
|
|
3034
3155
|
import IconButton5 from "@mui/material/IconButton";
|
|
3035
3156
|
import Alert4 from "@mui/material/Alert";
|
|
3036
3157
|
import { alpha as alpha6 } from "@mui/material/styles";
|
|
3037
3158
|
import { KeyboardArrowDown } from "@mui/icons-material";
|
|
3038
|
-
import { useAsync } from "react-use";
|
|
3159
|
+
import { useAsync as useAsync2 } from "react-use";
|
|
3039
3160
|
function actionTone(action) {
|
|
3040
3161
|
if (!action) return "neutral";
|
|
3041
3162
|
for (const [key, tone] of Object.entries(ACTION_TONES)) {
|
|
@@ -3056,10 +3177,10 @@ function formatDateTime(iso) {
|
|
|
3056
3177
|
}
|
|
3057
3178
|
function renderAuditLogBody(loading, entries) {
|
|
3058
3179
|
if (loading) {
|
|
3059
|
-
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 })));
|
|
3060
3181
|
}
|
|
3061
3182
|
if (entries.length === 0) {
|
|
3062
|
-
return /* @__PURE__ */ React9.createElement(
|
|
3183
|
+
return /* @__PURE__ */ React9.createElement(TableRow6, null, /* @__PURE__ */ React9.createElement(TableCell6, { colSpan: 6 }, /* @__PURE__ */ React9.createElement(
|
|
3063
3184
|
EmptyState,
|
|
3064
3185
|
{
|
|
3065
3186
|
message: "No audit events found",
|
|
@@ -3091,7 +3212,7 @@ var init_AuditLog = __esm({
|
|
|
3091
3212
|
DetailRow = ({ entry }) => {
|
|
3092
3213
|
const [open, setOpen] = useState7(false);
|
|
3093
3214
|
const hasDetail = entry.before_value || entry.updated_values;
|
|
3094
|
-
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(
|
|
3095
3216
|
IconButton5,
|
|
3096
3217
|
{
|
|
3097
3218
|
size: "small",
|
|
@@ -3105,7 +3226,7 @@ var init_AuditLog = __esm({
|
|
|
3105
3226
|
})
|
|
3106
3227
|
},
|
|
3107
3228
|
/* @__PURE__ */ React9.createElement(KeyboardArrowDown, { fontSize: "small" })
|
|
3108
|
-
)), /* @__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(
|
|
3109
3230
|
Typography9,
|
|
3110
3231
|
{
|
|
3111
3232
|
variant: "body2",
|
|
@@ -3115,7 +3236,7 @@ var init_AuditLog = __esm({
|
|
|
3115
3236
|
sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 12 }
|
|
3116
3237
|
},
|
|
3117
3238
|
entry.object_id ? entry.object_id.slice(0, 20) + (entry.object_id.length > 20 ? "\u2026" : "") : "\u2014"
|
|
3118
|
-
)), /* @__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(
|
|
3119
3240
|
Box9,
|
|
3120
3241
|
{
|
|
3121
3242
|
display: "flex",
|
|
@@ -3160,7 +3281,7 @@ var init_AuditLog = __esm({
|
|
|
3160
3281
|
() => ({ page: page + 1, page_size: pageSize, ...filters }),
|
|
3161
3282
|
[page, pageSize, filters]
|
|
3162
3283
|
)();
|
|
3163
|
-
const { value, loading, error } =
|
|
3284
|
+
const { value, loading, error } = useAsync2(
|
|
3164
3285
|
() => api.getAuditLogs(fetchParams),
|
|
3165
3286
|
[api, fetchParams]
|
|
3166
3287
|
);
|
|
@@ -3185,11 +3306,11 @@ var init_AuditLog = __esm({
|
|
|
3185
3306
|
},
|
|
3186
3307
|
sx: { minWidth: 150 }
|
|
3187
3308
|
},
|
|
3188
|
-
/* @__PURE__ */ React9.createElement(
|
|
3189
|
-
/* @__PURE__ */ React9.createElement(
|
|
3190
|
-
/* @__PURE__ */ React9.createElement(
|
|
3191
|
-
/* @__PURE__ */ React9.createElement(
|
|
3192
|
-
/* @__PURE__ */ React9.createElement(
|
|
3309
|
+
/* @__PURE__ */ React9.createElement(MenuItem5, { value: "" }, "All actions"),
|
|
3310
|
+
/* @__PURE__ */ React9.createElement(MenuItem5, { value: "created" }, "Created"),
|
|
3311
|
+
/* @__PURE__ */ React9.createElement(MenuItem5, { value: "updated" }, "Updated"),
|
|
3312
|
+
/* @__PURE__ */ React9.createElement(MenuItem5, { value: "deleted" }, "Deleted"),
|
|
3313
|
+
/* @__PURE__ */ React9.createElement(MenuItem5, { value: "blocked" }, "Blocked")
|
|
3193
3314
|
), /* @__PURE__ */ React9.createElement(
|
|
3194
3315
|
TextField5,
|
|
3195
3316
|
{
|
|
@@ -3203,12 +3324,12 @@ var init_AuditLog = __esm({
|
|
|
3203
3324
|
},
|
|
3204
3325
|
sx: { minWidth: 150 }
|
|
3205
3326
|
},
|
|
3206
|
-
/* @__PURE__ */ React9.createElement(
|
|
3207
|
-
/* @__PURE__ */ React9.createElement(
|
|
3208
|
-
/* @__PURE__ */ React9.createElement(
|
|
3209
|
-
/* @__PURE__ */ React9.createElement(
|
|
3210
|
-
/* @__PURE__ */ React9.createElement(
|
|
3211
|
-
/* @__PURE__ */ React9.createElement(
|
|
3327
|
+
/* @__PURE__ */ React9.createElement(MenuItem5, { value: "" }, "All tables"),
|
|
3328
|
+
/* @__PURE__ */ React9.createElement(MenuItem5, { value: "LiteLLM_VerificationToken" }, "Key"),
|
|
3329
|
+
/* @__PURE__ */ React9.createElement(MenuItem5, { value: "LiteLLM_TeamTable" }, "Team"),
|
|
3330
|
+
/* @__PURE__ */ React9.createElement(MenuItem5, { value: "LiteLLM_TeamMembership" }, "Team member"),
|
|
3331
|
+
/* @__PURE__ */ React9.createElement(MenuItem5, { value: "LiteLLM_ObjectPermissionTable" }, "Team access (KB / MCP)"),
|
|
3332
|
+
/* @__PURE__ */ React9.createElement(MenuItem5, { value: "LiteLLM_UserTable" }, "User")
|
|
3212
3333
|
), /* @__PURE__ */ React9.createElement(
|
|
3213
3334
|
TextField5,
|
|
3214
3335
|
{
|
|
@@ -3224,7 +3345,7 @@ var init_AuditLog = __esm({
|
|
|
3224
3345
|
))
|
|
3225
3346
|
},
|
|
3226
3347
|
error && /* @__PURE__ */ React9.createElement(Box9, { px: 2.5, pb: 2 }, /* @__PURE__ */ React9.createElement(Alert4, { severity: "error" }, error.message)),
|
|
3227
|
-
/* @__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)))),
|
|
3228
3349
|
/* @__PURE__ */ React9.createElement(
|
|
3229
3350
|
TablePagination,
|
|
3230
3351
|
{
|
|
@@ -3280,18 +3401,17 @@ var LiteLLMPage_exports = {};
|
|
|
3280
3401
|
__export(LiteLLMPage_exports, {
|
|
3281
3402
|
LiteLLMPage: () => LiteLLMPage
|
|
3282
3403
|
});
|
|
3283
|
-
import React10, { useState as useState8, useCallback as useCallback3, useMemo as
|
|
3404
|
+
import React10, { useState as useState8, useCallback as useCallback3, useMemo as useMemo7 } from "react";
|
|
3284
3405
|
import Box10 from "@mui/material/Box";
|
|
3285
3406
|
import Snackbar2 from "@mui/material/Snackbar";
|
|
3286
3407
|
import Alert5 from "@mui/material/Alert";
|
|
3287
|
-
import
|
|
3408
|
+
import CircularProgress5 from "@mui/material/CircularProgress";
|
|
3288
3409
|
import Typography10 from "@mui/material/Typography";
|
|
3289
|
-
import
|
|
3410
|
+
import Paper6 from "@mui/material/Paper";
|
|
3290
3411
|
import Tabs2 from "@mui/material/Tabs";
|
|
3291
3412
|
import Tab2 from "@mui/material/Tab";
|
|
3292
|
-
import
|
|
3293
|
-
import {
|
|
3294
|
-
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";
|
|
3295
3415
|
import { usePermission } from "@backstage/plugin-permission-react";
|
|
3296
3416
|
function initDateRange() {
|
|
3297
3417
|
let preset = "7d";
|
|
@@ -3323,7 +3443,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3323
3443
|
init_permissions();
|
|
3324
3444
|
PERIOD_LS_KEY2 = "litellm_usage_period";
|
|
3325
3445
|
LiteLLMPage = () => {
|
|
3326
|
-
const api =
|
|
3446
|
+
const api = useApi2(liteLlmApiRef);
|
|
3327
3447
|
const [dateRange, setDateRange] = useState8(initDateRange);
|
|
3328
3448
|
const [currentPreset, setCurrentPreset] = useState8(() => {
|
|
3329
3449
|
try {
|
|
@@ -3338,7 +3458,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3338
3458
|
const [manageTeam, setManageTeam] = useState8(null);
|
|
3339
3459
|
const [teamUsageCache, setTeamUsageCache] = useState8({});
|
|
3340
3460
|
const [teamUsageLoading, setTeamUsageLoading] = useState8({});
|
|
3341
|
-
const { value: userInfo, loading: userLoading, error: userError } =
|
|
3461
|
+
const { value: userInfo, loading: userLoading, error: userError } = useAsync3(
|
|
3342
3462
|
() => api.getUserInfo(),
|
|
3343
3463
|
[api]
|
|
3344
3464
|
);
|
|
@@ -3353,7 +3473,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3353
3473
|
},
|
|
3354
3474
|
[api]
|
|
3355
3475
|
);
|
|
3356
|
-
const { value: allModels, loading: modelsLoading } =
|
|
3476
|
+
const { value: allModels, loading: modelsLoading } = useAsync3(
|
|
3357
3477
|
() => api.listModels().catch(() => []),
|
|
3358
3478
|
[api]
|
|
3359
3479
|
);
|
|
@@ -3361,8 +3481,8 @@ var init_LiteLLMPage = __esm({
|
|
|
3361
3481
|
() => api.getTeams().catch(() => []),
|
|
3362
3482
|
[api]
|
|
3363
3483
|
);
|
|
3364
|
-
const { value: liteLlmConfig } =
|
|
3365
|
-
const { value: managedTeams } =
|
|
3484
|
+
const { value: liteLlmConfig } = useAsync3(() => api.getConfig(), [api]);
|
|
3485
|
+
const { value: managedTeams } = useAsync3(
|
|
3366
3486
|
async () => liteLlmConfig?.teamManagement?.enabled ? api.getManagedTeams().catch(() => []) : [],
|
|
3367
3487
|
[api, liteLlmConfig]
|
|
3368
3488
|
);
|
|
@@ -3373,15 +3493,15 @@ var init_LiteLLMPage = __esm({
|
|
|
3373
3493
|
const { allowed: canManageMcpServers } = usePermission({ permission: litellmTeamMcpManagePermission });
|
|
3374
3494
|
const teamMgmtEnabled = liteLlmConfig?.teamManagement?.enabled ?? false;
|
|
3375
3495
|
const objectPermsEnabled = liteLlmConfig?.teamManagement?.objectPermissionsEnabled ?? false;
|
|
3376
|
-
const { value: vectorStores } =
|
|
3496
|
+
const { value: vectorStores } = useAsync3(
|
|
3377
3497
|
async () => objectPermsEnabled ? api.getVectorStores().catch(() => []) : [],
|
|
3378
3498
|
[api, objectPermsEnabled]
|
|
3379
3499
|
);
|
|
3380
|
-
const { value: mcpServers } =
|
|
3500
|
+
const { value: mcpServers } = useAsync3(
|
|
3381
3501
|
async () => objectPermsEnabled ? api.getMcpServers().catch(() => []) : [],
|
|
3382
3502
|
[api, objectPermsEnabled]
|
|
3383
3503
|
);
|
|
3384
|
-
const teams =
|
|
3504
|
+
const teams = useMemo7(() => {
|
|
3385
3505
|
if (!allTeams?.length) return [];
|
|
3386
3506
|
if (!userInfo) return allTeams;
|
|
3387
3507
|
const userId = userInfo.user_id;
|
|
@@ -3393,7 +3513,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3393
3513
|
);
|
|
3394
3514
|
return byMembership.length > 0 ? byMembership : allTeams;
|
|
3395
3515
|
}, [allTeams, userInfo]);
|
|
3396
|
-
const { value: usage, loading: usageLoading } =
|
|
3516
|
+
const { value: usage, loading: usageLoading } = useAsync3(async () => {
|
|
3397
3517
|
const startDate = dateRange.start.toISOString().split("T")[0];
|
|
3398
3518
|
const endDate = dateRange.end.toISOString().split("T")[0];
|
|
3399
3519
|
return api.getUsage(startDate, endDate);
|
|
@@ -3420,7 +3540,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3420
3540
|
setTeamUsageLoading((prev) => ({ ...prev, [teamId]: false }));
|
|
3421
3541
|
}
|
|
3422
3542
|
}, [api, dateRange, teamUsageCache, teamUsageLoading]);
|
|
3423
|
-
const allowedModels =
|
|
3543
|
+
const allowedModels = useMemo7(() => {
|
|
3424
3544
|
if (!allModels?.length) return [];
|
|
3425
3545
|
const userModels = userInfo?.models;
|
|
3426
3546
|
const teamModels = teams?.flatMap((t) => t.models ?? []);
|
|
@@ -3528,12 +3648,12 @@ var init_LiteLLMPage = __esm({
|
|
|
3528
3648
|
);
|
|
3529
3649
|
const isInitialLoading = userLoading && !userInfo;
|
|
3530
3650
|
if (isInitialLoading) {
|
|
3531
|
-
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));
|
|
3532
3652
|
}
|
|
3533
3653
|
if (userError || !userInfo) {
|
|
3534
3654
|
const isProvisioningEnabled = userError?.body?.provisioning === true;
|
|
3535
3655
|
const hint = userError?.body?.hint;
|
|
3536
|
-
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.")));
|
|
3537
3657
|
}
|
|
3538
3658
|
const pageTabs = /* @__PURE__ */ React10.createElement(
|
|
3539
3659
|
Tabs2,
|
|
@@ -3594,14 +3714,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3594
3714
|
for (const t of managedTeams ?? []) teamsById.set(t.team_id, t);
|
|
3595
3715
|
for (const t of teams ?? []) teamsById.set(t.team_id, t);
|
|
3596
3716
|
const visibleTeams = Array.from(teamsById.values());
|
|
3597
|
-
return /* @__PURE__ */ React10.createElement(
|
|
3598
|
-
Button6,
|
|
3599
|
-
{
|
|
3600
|
-
variant: "contained",
|
|
3601
|
-
onClick: () => setManageTeam({ mode: "create" })
|
|
3602
|
-
},
|
|
3603
|
-
"Create Team"
|
|
3604
|
-
)), /* @__PURE__ */ React10.createElement(
|
|
3717
|
+
return /* @__PURE__ */ React10.createElement(
|
|
3605
3718
|
TeamUsage,
|
|
3606
3719
|
{
|
|
3607
3720
|
teams: visibleTeams,
|
|
@@ -3612,9 +3725,11 @@ var init_LiteLLMPage = __esm({
|
|
|
3612
3725
|
},
|
|
3613
3726
|
getTeamUsageLoading: (teamId) => teamUsageLoading[teamId] ?? false,
|
|
3614
3727
|
canManage: teamMgmtEnabled && canManageTeam,
|
|
3615
|
-
onEditTeam: (t) => setManageTeam({ mode: "edit", team: t })
|
|
3728
|
+
onEditTeam: (t) => setManageTeam({ mode: "edit", team: t }),
|
|
3729
|
+
canCreate: teamMgmtEnabled && canCreateTeam,
|
|
3730
|
+
onCreateTeam: () => setManageTeam({ mode: "create" })
|
|
3616
3731
|
}
|
|
3617
|
-
)
|
|
3732
|
+
);
|
|
3618
3733
|
})(), activeTab === "models" && /* @__PURE__ */ React10.createElement(
|
|
3619
3734
|
ModelsTable,
|
|
3620
3735
|
{
|
|
@@ -3755,17 +3870,17 @@ init_TeamUsage();
|
|
|
3755
3870
|
init_api();
|
|
3756
3871
|
init_format();
|
|
3757
3872
|
import React12, { useState as useState9, useEffect as useEffect3 } from "react";
|
|
3758
|
-
import
|
|
3873
|
+
import Paper7 from "@mui/material/Paper";
|
|
3759
3874
|
import Box11 from "@mui/material/Box";
|
|
3760
3875
|
import Typography11 from "@mui/material/Typography";
|
|
3761
3876
|
import FormControl2 from "@mui/material/FormControl";
|
|
3762
3877
|
import Select2 from "@mui/material/Select";
|
|
3763
|
-
import
|
|
3878
|
+
import MenuItem6 from "@mui/material/MenuItem";
|
|
3764
3879
|
import Grid2 from "@mui/material/Grid";
|
|
3765
|
-
import
|
|
3880
|
+
import CircularProgress6 from "@mui/material/CircularProgress";
|
|
3766
3881
|
import Alert6 from "@mui/material/Alert";
|
|
3767
3882
|
import { AreaChart as AreaChart3, Area as Area3, ResponsiveContainer as ResponsiveContainer3 } from "recharts";
|
|
3768
|
-
import { useApi as
|
|
3883
|
+
import { useApi as useApi3 } from "@backstage/core-plugin-api";
|
|
3769
3884
|
function presetToDateRange(preset) {
|
|
3770
3885
|
const end = /* @__PURE__ */ new Date();
|
|
3771
3886
|
const start = /* @__PURE__ */ new Date();
|
|
@@ -3783,7 +3898,7 @@ var LiteLLMHomeWidget = ({
|
|
|
3783
3898
|
defaultPeriod = "7d",
|
|
3784
3899
|
title = "LiteLLM Usage"
|
|
3785
3900
|
}) => {
|
|
3786
|
-
const api =
|
|
3901
|
+
const api = useApi3(liteLlmApiRef);
|
|
3787
3902
|
const [period, setPeriod] = useState9(defaultPeriod);
|
|
3788
3903
|
const [loading, setLoading] = useState9(true);
|
|
3789
3904
|
const [usageError, setUsageError] = useState9(null);
|
|
@@ -3827,17 +3942,17 @@ var LiteLLMHomeWidget = ({
|
|
|
3827
3942
|
spend: d.spend
|
|
3828
3943
|
}));
|
|
3829
3944
|
const hasSparkline = dailyData.length > 0;
|
|
3830
|
-
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(
|
|
3831
3946
|
Select2,
|
|
3832
3947
|
{
|
|
3833
3948
|
value: period,
|
|
3834
3949
|
onChange: (e) => setPeriod(e.target.value),
|
|
3835
3950
|
displayEmpty: true
|
|
3836
3951
|
},
|
|
3837
|
-
/* @__PURE__ */ React12.createElement(
|
|
3838
|
-
/* @__PURE__ */ React12.createElement(
|
|
3839
|
-
/* @__PURE__ */ React12.createElement(
|
|
3840
|
-
))), loading && /* @__PURE__ */ React12.createElement(Box11, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: 120 }, /* @__PURE__ */ React12.createElement(
|
|
3952
|
+
/* @__PURE__ */ React12.createElement(MenuItem6, { value: "today" }, "Today"),
|
|
3953
|
+
/* @__PURE__ */ React12.createElement(MenuItem6, { value: "7d" }, "7d"),
|
|
3954
|
+
/* @__PURE__ */ React12.createElement(MenuItem6, { value: "30d" }, "30d")
|
|
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(
|
|
3841
3956
|
Area3,
|
|
3842
3957
|
{
|
|
3843
3958
|
type: "monotone",
|