@acarmisc/backstage-plugin-litellm 0.17.0 → 0.18.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/SearchInput.d.ts +1 -0
- package/dist/components/SearchInput.d.ts +9 -0
- package/dist/components/TeamUsage.d.ts +3 -0
- package/dist/index.cjs.js +267 -145
- package/dist/index.cjs.js.map +3 -3
- package/dist/index.esm.js +272 -150
- package/dist/index.esm.js.map +3 -3
- package/package.json +4 -1
package/dist/index.cjs.js
CHANGED
|
@@ -1942,7 +1942,7 @@ var init_GenerateKeyDialog = __esm({
|
|
|
1942
1942
|
});
|
|
1943
1943
|
|
|
1944
1944
|
// src/components/ManageTeamDialog.tsx
|
|
1945
|
-
var import_react5, import_Dialog3, import_DialogTitle3, import_DialogContent3, import_DialogActions3, import_TextField3, import_Button4, import_Alert2, import_Checkbox2, import_FormControlLabel2, import_Autocomplete3, import_MenuItem2, import_Box5, import_Divider2, import_Typography5, import_IconButton3,
|
|
1945
|
+
var import_react5, import_Dialog3, import_DialogTitle3, import_DialogContent3, import_DialogActions3, import_TextField3, import_Button4, import_Alert2, import_Checkbox2, import_FormControlLabel2, import_Autocomplete3, import_CircularProgress3, import_MenuItem2, import_Box5, import_Divider2, import_Typography5, import_IconButton3, import_Paper3, import_Table2, import_TableBody2, import_TableCell2, import_TableContainer2, import_TableHead2, import_TableRow2, import_icons_material4, import_core_plugin_api2, import_plugin_catalog_react, import_catalog_model, import_react_use, BUDGET_DURATION_OPTIONS, DEFAULT_BUDGET_DURATION, FALLBACK_BUDGET_CEILING, ManageTeamDialog;
|
|
1946
1946
|
var init_ManageTeamDialog = __esm({
|
|
1947
1947
|
"src/components/ManageTeamDialog.tsx"() {
|
|
1948
1948
|
"use strict";
|
|
@@ -1957,15 +1957,25 @@ var init_ManageTeamDialog = __esm({
|
|
|
1957
1957
|
import_Checkbox2 = __toESM(require("@mui/material/Checkbox"));
|
|
1958
1958
|
import_FormControlLabel2 = __toESM(require("@mui/material/FormControlLabel"));
|
|
1959
1959
|
import_Autocomplete3 = __toESM(require("@mui/material/Autocomplete"));
|
|
1960
|
+
import_CircularProgress3 = __toESM(require("@mui/material/CircularProgress"));
|
|
1960
1961
|
import_MenuItem2 = __toESM(require("@mui/material/MenuItem"));
|
|
1961
1962
|
import_Box5 = __toESM(require("@mui/material/Box"));
|
|
1962
1963
|
import_Divider2 = __toESM(require("@mui/material/Divider"));
|
|
1963
1964
|
import_Typography5 = __toESM(require("@mui/material/Typography"));
|
|
1964
1965
|
import_IconButton3 = __toESM(require("@mui/material/IconButton"));
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1966
|
+
import_Paper3 = __toESM(require("@mui/material/Paper"));
|
|
1967
|
+
import_Table2 = __toESM(require("@mui/material/Table"));
|
|
1968
|
+
import_TableBody2 = __toESM(require("@mui/material/TableBody"));
|
|
1969
|
+
import_TableCell2 = __toESM(require("@mui/material/TableCell"));
|
|
1970
|
+
import_TableContainer2 = __toESM(require("@mui/material/TableContainer"));
|
|
1971
|
+
import_TableHead2 = __toESM(require("@mui/material/TableHead"));
|
|
1972
|
+
import_TableRow2 = __toESM(require("@mui/material/TableRow"));
|
|
1968
1973
|
import_icons_material4 = require("@mui/icons-material");
|
|
1974
|
+
import_core_plugin_api2 = require("@backstage/core-plugin-api");
|
|
1975
|
+
import_plugin_catalog_react = require("@backstage/plugin-catalog-react");
|
|
1976
|
+
import_catalog_model = require("@backstage/catalog-model");
|
|
1977
|
+
import_react_use = require("react-use");
|
|
1978
|
+
init_ui();
|
|
1969
1979
|
BUDGET_DURATION_OPTIONS = [
|
|
1970
1980
|
{ value: "1d", label: "Daily (1d)" },
|
|
1971
1981
|
{ value: "7d", label: "Weekly (7d)" },
|
|
@@ -2000,6 +2010,7 @@ var init_ManageTeamDialog = __esm({
|
|
|
2000
2010
|
const [budgetDuration, setBudgetDuration] = (0, import_react5.useState)("");
|
|
2001
2011
|
const [submitting, setSubmitting] = (0, import_react5.useState)(false);
|
|
2002
2012
|
const [error, setError] = (0, import_react5.useState)(null);
|
|
2013
|
+
const [memberQuery, setMemberQuery] = (0, import_react5.useState)("");
|
|
2003
2014
|
const [memberRef, setMemberRef] = (0, import_react5.useState)("");
|
|
2004
2015
|
const [memberBudget, setMemberBudget] = (0, import_react5.useState)("");
|
|
2005
2016
|
const [memberBusy, setMemberBusy] = (0, import_react5.useState)(false);
|
|
@@ -2012,6 +2023,36 @@ var init_ManageTeamDialog = __esm({
|
|
|
2012
2023
|
const [mcpError, setMcpError] = (0, import_react5.useState)(null);
|
|
2013
2024
|
const allowUnlimitedBudget = config?.teamManagement?.allowUnlimitedBudget ?? false;
|
|
2014
2025
|
const maxBudgetCeiling = config?.teamManagement?.maxBudgetCeiling ?? FALLBACK_BUDGET_CEILING;
|
|
2026
|
+
const members = (0, import_react5.useMemo)(() => team?.members_with_roles ?? [], [team]);
|
|
2027
|
+
const showMembers = mode === "edit" && !!canManageMembers;
|
|
2028
|
+
const catalogApi = (0, import_core_plugin_api2.useApi)(import_plugin_catalog_react.catalogApiRef);
|
|
2029
|
+
const { value: catalogUsers, loading: usersLoading } = (0, import_react_use.useAsync)(async () => {
|
|
2030
|
+
if (!open || !showMembers) return [];
|
|
2031
|
+
try {
|
|
2032
|
+
const { items } = await catalogApi.getEntities({
|
|
2033
|
+
filter: { kind: "User" },
|
|
2034
|
+
fields: ["kind", "metadata.namespace", "metadata.name", "metadata.title", "spec.profile"]
|
|
2035
|
+
});
|
|
2036
|
+
return items;
|
|
2037
|
+
} catch {
|
|
2038
|
+
return [];
|
|
2039
|
+
}
|
|
2040
|
+
}, [open, showMembers, catalogApi]);
|
|
2041
|
+
const userOptions = (0, import_react5.useMemo)(() => {
|
|
2042
|
+
const existingEmails = new Set(
|
|
2043
|
+
members.map((m) => m.user_email?.toLowerCase()).filter((e) => !!e)
|
|
2044
|
+
);
|
|
2045
|
+
return (catalogUsers ?? []).map((e) => {
|
|
2046
|
+
const ref = (0, import_catalog_model.stringifyEntityRef)(e);
|
|
2047
|
+
const displayName = e.spec?.profile?.displayName || e.metadata.title || e.metadata.name;
|
|
2048
|
+
const email = e.spec?.profile?.email;
|
|
2049
|
+
return {
|
|
2050
|
+
ref,
|
|
2051
|
+
email,
|
|
2052
|
+
label: email ? `${displayName} (${email})` : displayName
|
|
2053
|
+
};
|
|
2054
|
+
}).filter((o) => !(o.email && existingEmails.has(o.email.toLowerCase()))).sort((a, b) => a.label.localeCompare(b.label));
|
|
2055
|
+
}, [catalogUsers, members]);
|
|
2015
2056
|
(0, import_react5.useEffect)(() => {
|
|
2016
2057
|
if (open) {
|
|
2017
2058
|
if (mode === "edit" && team) {
|
|
@@ -2030,6 +2071,7 @@ var init_ManageTeamDialog = __esm({
|
|
|
2030
2071
|
setBudgetDuration(DEFAULT_BUDGET_DURATION);
|
|
2031
2072
|
}
|
|
2032
2073
|
setError(null);
|
|
2074
|
+
setMemberQuery("");
|
|
2033
2075
|
setMemberRef("");
|
|
2034
2076
|
setMemberBudget("");
|
|
2035
2077
|
setMemberError(null);
|
|
@@ -2101,6 +2143,7 @@ var init_ManageTeamDialog = __esm({
|
|
|
2101
2143
|
try {
|
|
2102
2144
|
setMemberBusy(true);
|
|
2103
2145
|
await onAddMember(memberRef.trim(), budget);
|
|
2146
|
+
setMemberQuery("");
|
|
2104
2147
|
setMemberRef("");
|
|
2105
2148
|
setMemberBudget("");
|
|
2106
2149
|
} catch (err) {
|
|
@@ -2147,8 +2190,6 @@ var init_ManageTeamDialog = __esm({
|
|
|
2147
2190
|
};
|
|
2148
2191
|
const modelNames = allModels.map((m) => m.model_name);
|
|
2149
2192
|
const durationOptions = BUDGET_DURATION_OPTIONS.some((o) => o.value === budgetDuration) ? BUDGET_DURATION_OPTIONS : [...BUDGET_DURATION_OPTIONS, { value: budgetDuration, label: budgetDuration }];
|
|
2150
|
-
const members = team?.members_with_roles ?? [];
|
|
2151
|
-
const showMembers = mode === "edit" && !!canManageMembers;
|
|
2152
2193
|
const showKnowledgeBases = mode === "edit" && !!canManageKnowledgeBases;
|
|
2153
2194
|
const kbOptions = (vectorStores ?? []).map((v) => v.id);
|
|
2154
2195
|
const kbLabel = (id) => (vectorStores ?? []).find((v) => v.id === id)?.name ?? id;
|
|
@@ -2172,7 +2213,15 @@ var init_ManageTeamDialog = __esm({
|
|
|
2172
2213
|
value: models,
|
|
2173
2214
|
onChange: (_, newValue) => setModels(newValue),
|
|
2174
2215
|
disabled: submitting,
|
|
2175
|
-
renderInput: (params) => /* @__PURE__ */ import_react5.default.createElement(
|
|
2216
|
+
renderInput: (params) => /* @__PURE__ */ import_react5.default.createElement(
|
|
2217
|
+
import_TextField3.default,
|
|
2218
|
+
{
|
|
2219
|
+
...params,
|
|
2220
|
+
label: "Models",
|
|
2221
|
+
required: true,
|
|
2222
|
+
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.)"
|
|
2223
|
+
}
|
|
2224
|
+
)
|
|
2176
2225
|
}
|
|
2177
2226
|
), /* @__PURE__ */ import_react5.default.createElement(
|
|
2178
2227
|
import_TextField3.default,
|
|
@@ -2204,34 +2253,69 @@ var init_ManageTeamDialog = __esm({
|
|
|
2204
2253
|
fullWidth: true
|
|
2205
2254
|
},
|
|
2206
2255
|
durationOptions.map((o) => /* @__PURE__ */ import_react5.default.createElement(import_MenuItem2.default, { key: o.value, value: o.value }, o.label))
|
|
2207
|
-
), showMembers && /* @__PURE__ */ import_react5.default.createElement(import_Box5.default, null, /* @__PURE__ */ import_react5.default.createElement(import_Divider2.default, { sx: { my: 1 } }), /* @__PURE__ */ import_react5.default.createElement(import_Typography5.default, { variant: "subtitle2", sx: { mb: 1 } }, "Members"), memberError && /* @__PURE__ */ import_react5.default.createElement(import_Alert2.default, { severity: "error", sx: { mb: 1 }, onClose: () => setMemberError(null) }, memberError), members.length === 0 ? /* @__PURE__ */ import_react5.default.createElement(import_Typography5.default, { variant: "body2", color: "text.secondary" }, "No members yet.") : /* @__PURE__ */ import_react5.default.createElement(
|
|
2208
|
-
|
|
2256
|
+
), showMembers && /* @__PURE__ */ import_react5.default.createElement(import_Box5.default, null, /* @__PURE__ */ import_react5.default.createElement(import_Divider2.default, { sx: { my: 1 } }), /* @__PURE__ */ import_react5.default.createElement(import_Typography5.default, { variant: "subtitle2", sx: { mb: 1 } }, "Members"), memberError && /* @__PURE__ */ import_react5.default.createElement(import_Alert2.default, { severity: "error", sx: { mb: 1 }, onClose: () => setMemberError(null) }, memberError), members.length === 0 ? /* @__PURE__ */ import_react5.default.createElement(import_Typography5.default, { variant: "body2", color: "text.secondary", sx: { mb: 1 } }, "No members yet.") : /* @__PURE__ */ import_react5.default.createElement(import_TableContainer2.default, { component: import_Paper3.default, variant: "outlined", sx: { borderRadius: 1.5, mb: 1.5 } }, /* @__PURE__ */ import_react5.default.createElement(import_Table2.default, { size: "small", sx: dataTableSx }, /* @__PURE__ */ import_react5.default.createElement(import_TableHead2.default, null, /* @__PURE__ */ import_react5.default.createElement(import_TableRow2.default, null, /* @__PURE__ */ import_react5.default.createElement(import_TableCell2.default, null, "User"), /* @__PURE__ */ import_react5.default.createElement(import_TableCell2.default, { align: "right" }, "Role"), /* @__PURE__ */ import_react5.default.createElement(import_TableCell2.default, { align: "right", sx: { width: 40 } }))), /* @__PURE__ */ import_react5.default.createElement(import_TableBody2.default, null, members.map((m) => /* @__PURE__ */ import_react5.default.createElement(import_TableRow2.default, { key: m.user_id }, /* @__PURE__ */ import_react5.default.createElement(import_TableCell2.default, null, m.user_email ? /* @__PURE__ */ import_react5.default.createElement(import_react5.default.Fragment, null, /* @__PURE__ */ import_react5.default.createElement(import_Typography5.default, { variant: "body2" }, m.user_email), /* @__PURE__ */ import_react5.default.createElement(
|
|
2257
|
+
import_Typography5.default,
|
|
2209
2258
|
{
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
import_IconButton3.default,
|
|
2214
|
-
{
|
|
2215
|
-
edge: "end",
|
|
2216
|
-
size: "small",
|
|
2217
|
-
"aria-label": `remove ${m.user_id}`,
|
|
2218
|
-
disabled: memberBusy,
|
|
2219
|
-
onClick: () => handleRemoveMember(m.user_id)
|
|
2220
|
-
},
|
|
2221
|
-
/* @__PURE__ */ import_react5.default.createElement(import_icons_material4.Delete, { fontSize: "small" })
|
|
2222
|
-
)
|
|
2259
|
+
variant: "caption",
|
|
2260
|
+
color: "text.secondary",
|
|
2261
|
+
sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 11 }
|
|
2223
2262
|
},
|
|
2224
|
-
|
|
2225
|
-
))
|
|
2226
|
-
|
|
2263
|
+
m.user_id
|
|
2264
|
+
)) : /* @__PURE__ */ import_react5.default.createElement(
|
|
2265
|
+
import_Typography5.default,
|
|
2227
2266
|
{
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2267
|
+
variant: "body2",
|
|
2268
|
+
sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace" }
|
|
2269
|
+
},
|
|
2270
|
+
m.user_id
|
|
2271
|
+
)), /* @__PURE__ */ import_react5.default.createElement(import_TableCell2.default, { align: "right" }, /* @__PURE__ */ import_react5.default.createElement(StatusPill, { label: m.role, tone: m.role === "admin" ? "accent" : "neutral", dot: false })), /* @__PURE__ */ import_react5.default.createElement(import_TableCell2.default, { align: "right" }, /* @__PURE__ */ import_react5.default.createElement(
|
|
2272
|
+
import_IconButton3.default,
|
|
2273
|
+
{
|
|
2274
|
+
edge: "end",
|
|
2233
2275
|
size: "small",
|
|
2234
|
-
|
|
2276
|
+
"aria-label": `remove ${m.user_id}`,
|
|
2277
|
+
disabled: memberBusy,
|
|
2278
|
+
onClick: () => handleRemoveMember(m.user_id)
|
|
2279
|
+
},
|
|
2280
|
+
/* @__PURE__ */ import_react5.default.createElement(import_icons_material4.Delete, { fontSize: "small" })
|
|
2281
|
+
))))))), /* @__PURE__ */ import_react5.default.createElement(import_Box5.default, { sx: { display: "flex", gap: 1, alignItems: "flex-start", flexWrap: "wrap" } }, /* @__PURE__ */ import_react5.default.createElement(
|
|
2282
|
+
import_Autocomplete3.default,
|
|
2283
|
+
{
|
|
2284
|
+
freeSolo: true,
|
|
2285
|
+
autoHighlight: true,
|
|
2286
|
+
options: userOptions,
|
|
2287
|
+
loading: usersLoading,
|
|
2288
|
+
inputValue: memberQuery,
|
|
2289
|
+
onInputChange: (_, newInputValue, reason) => {
|
|
2290
|
+
setMemberQuery(newInputValue);
|
|
2291
|
+
if (reason === "input" || reason === "clear") {
|
|
2292
|
+
setMemberRef(newInputValue);
|
|
2293
|
+
}
|
|
2294
|
+
},
|
|
2295
|
+
onChange: (_, newValue) => {
|
|
2296
|
+
if (newValue && typeof newValue !== "string") {
|
|
2297
|
+
setMemberQuery(newValue.label);
|
|
2298
|
+
setMemberRef(newValue.ref);
|
|
2299
|
+
}
|
|
2300
|
+
},
|
|
2301
|
+
getOptionLabel: (o) => typeof o === "string" ? o : o.label,
|
|
2302
|
+
isOptionEqualToValue: (o, v) => o.ref === (typeof v === "string" ? v : v.ref),
|
|
2303
|
+
disabled: memberBusy,
|
|
2304
|
+
sx: { flex: 1, minWidth: 260 },
|
|
2305
|
+
renderInput: (params) => /* @__PURE__ */ import_react5.default.createElement(
|
|
2306
|
+
import_TextField3.default,
|
|
2307
|
+
{
|
|
2308
|
+
...params,
|
|
2309
|
+
label: "Add member",
|
|
2310
|
+
placeholder: "Search by name or email\u2026",
|
|
2311
|
+
size: "small",
|
|
2312
|
+
helperText: "Pick a catalog user, or paste a user entity ref",
|
|
2313
|
+
InputProps: {
|
|
2314
|
+
...params.InputProps,
|
|
2315
|
+
endAdornment: /* @__PURE__ */ import_react5.default.createElement(import_react5.default.Fragment, null, usersLoading ? /* @__PURE__ */ import_react5.default.createElement(import_CircularProgress3.default, { color: "inherit", size: 14 }) : null, params.InputProps.endAdornment)
|
|
2316
|
+
}
|
|
2317
|
+
}
|
|
2318
|
+
)
|
|
2235
2319
|
}
|
|
2236
2320
|
), /* @__PURE__ */ import_react5.default.createElement(
|
|
2237
2321
|
import_TextField3.default,
|
|
@@ -2302,23 +2386,23 @@ function rateTone(rate) {
|
|
|
2302
2386
|
if (rate >= 0.9) return "warning";
|
|
2303
2387
|
return "danger";
|
|
2304
2388
|
}
|
|
2305
|
-
var import_react6,
|
|
2389
|
+
var import_react6, import_Paper4, import_Box6, import_Typography6, import_TextField4, import_MenuItem3, import_Grid, import_Table3, import_TableBody3, import_TableCell3, import_TableContainer3, import_TableHead3, import_TableRow3, import_Skeleton3, import_recharts, TOP_N_MODELS, PERIOD_LS_KEY, PRESET_LABELS, fmtPct, ChartSkeleton, ChartOrFallback, SuccessRateCell, UsageStats;
|
|
2306
2390
|
var init_UsageStats = __esm({
|
|
2307
2391
|
"src/components/UsageStats.tsx"() {
|
|
2308
2392
|
"use strict";
|
|
2309
2393
|
import_react6 = __toESM(require("react"));
|
|
2310
|
-
|
|
2394
|
+
import_Paper4 = __toESM(require("@mui/material/Paper"));
|
|
2311
2395
|
import_Box6 = __toESM(require("@mui/material/Box"));
|
|
2312
2396
|
import_Typography6 = __toESM(require("@mui/material/Typography"));
|
|
2313
2397
|
import_TextField4 = __toESM(require("@mui/material/TextField"));
|
|
2314
2398
|
import_MenuItem3 = __toESM(require("@mui/material/MenuItem"));
|
|
2315
2399
|
import_Grid = __toESM(require("@mui/material/Grid"));
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2400
|
+
import_Table3 = __toESM(require("@mui/material/Table"));
|
|
2401
|
+
import_TableBody3 = __toESM(require("@mui/material/TableBody"));
|
|
2402
|
+
import_TableCell3 = __toESM(require("@mui/material/TableCell"));
|
|
2403
|
+
import_TableContainer3 = __toESM(require("@mui/material/TableContainer"));
|
|
2404
|
+
import_TableHead3 = __toESM(require("@mui/material/TableHead"));
|
|
2405
|
+
import_TableRow3 = __toESM(require("@mui/material/TableRow"));
|
|
2322
2406
|
import_Skeleton3 = __toESM(require("@mui/material/Skeleton"));
|
|
2323
2407
|
import_recharts = require("recharts");
|
|
2324
2408
|
init_format();
|
|
@@ -2490,21 +2574,21 @@ var init_UsageStats = __esm({
|
|
|
2490
2574
|
];
|
|
2491
2575
|
const renderKeyRows = () => {
|
|
2492
2576
|
if (loading) {
|
|
2493
|
-
return /* @__PURE__ */ import_react6.default.createElement(
|
|
2577
|
+
return /* @__PURE__ */ import_react6.default.createElement(import_TableRow3.default, null, /* @__PURE__ */ import_react6.default.createElement(import_TableCell3.default, { colSpan: 7, sx: { py: 3 } }, /* @__PURE__ */ import_react6.default.createElement(import_Skeleton3.default, { variant: "rounded", height: 80 })));
|
|
2494
2578
|
}
|
|
2495
2579
|
if (keyRows.length === 0) {
|
|
2496
|
-
return /* @__PURE__ */ import_react6.default.createElement(
|
|
2580
|
+
return /* @__PURE__ */ import_react6.default.createElement(import_TableRow3.default, null, /* @__PURE__ */ import_react6.default.createElement(import_TableCell3.default, { colSpan: 7 }, /* @__PURE__ */ import_react6.default.createElement(EmptyState, { message: "No key activity in this period" })));
|
|
2497
2581
|
}
|
|
2498
|
-
return [...keyRows].sort((a, b) => b.spend - a.spend || b.apiRequests - a.apiRequests).map((r) => /* @__PURE__ */ import_react6.default.createElement(
|
|
2582
|
+
return [...keyRows].sort((a, b) => b.spend - a.spend || b.apiRequests - a.apiRequests).map((r) => /* @__PURE__ */ import_react6.default.createElement(import_TableRow3.default, { key: r.keyHash }, /* @__PURE__ */ import_react6.default.createElement(import_TableCell3.default, null, /* @__PURE__ */ import_react6.default.createElement(import_Typography6.default, { variant: "body2", sx: { fontWeight: 600 } }, r.keyAlias), r.teamId ? /* @__PURE__ */ import_react6.default.createElement(import_Typography6.default, { variant: "caption", color: "text.secondary" }, "team: ", r.teamId) : null), /* @__PURE__ */ import_react6.default.createElement(import_TableCell3.default, null, /* @__PURE__ */ import_react6.default.createElement(import_Box6.default, { display: "flex", gap: 0.5, flexWrap: "wrap" }, r.models.slice(0, 3).map((m) => /* @__PURE__ */ import_react6.default.createElement(TagChip, { key: m, label: m, title: m })), r.models.length > 3 && /* @__PURE__ */ import_react6.default.createElement(TagChip, { label: `+${r.models.length - 3}`, mono: false }))), /* @__PURE__ */ import_react6.default.createElement(import_TableCell3.default, { align: "right" }, fmtUsd(r.spend)), /* @__PURE__ */ import_react6.default.createElement(import_TableCell3.default, { align: "right" }, fmtInt(r.apiRequests)), /* @__PURE__ */ import_react6.default.createElement(import_TableCell3.default, { align: "right" }, fmtCompact(r.totalTokens)), /* @__PURE__ */ import_react6.default.createElement(import_TableCell3.default, { align: "right" }, fmtInt(r.failedRequests)), /* @__PURE__ */ import_react6.default.createElement(import_TableCell3.default, null, /* @__PURE__ */ import_react6.default.createElement(SuccessRateCell, { rate: r.successRate, requests: r.apiRequests }))));
|
|
2499
2583
|
};
|
|
2500
2584
|
const renderModelRows = () => {
|
|
2501
2585
|
if (loading) {
|
|
2502
|
-
return /* @__PURE__ */ import_react6.default.createElement(
|
|
2586
|
+
return /* @__PURE__ */ import_react6.default.createElement(import_TableRow3.default, null, /* @__PURE__ */ import_react6.default.createElement(import_TableCell3.default, { colSpan: 8, sx: { py: 3 } }, /* @__PURE__ */ import_react6.default.createElement(import_Skeleton3.default, { variant: "rounded", height: 80 })));
|
|
2503
2587
|
}
|
|
2504
2588
|
if (modelRows.length === 0) {
|
|
2505
|
-
return /* @__PURE__ */ import_react6.default.createElement(
|
|
2589
|
+
return /* @__PURE__ */ import_react6.default.createElement(import_TableRow3.default, null, /* @__PURE__ */ import_react6.default.createElement(import_TableCell3.default, { colSpan: 8 }, /* @__PURE__ */ import_react6.default.createElement(EmptyState, { message: "No model activity in this period" })));
|
|
2506
2590
|
}
|
|
2507
|
-
return [...modelRows].sort((a, b) => b.spend - a.spend || b.totalTokens - a.totalTokens).map((r) => /* @__PURE__ */ import_react6.default.createElement(
|
|
2591
|
+
return [...modelRows].sort((a, b) => b.spend - a.spend || b.totalTokens - a.totalTokens).map((r) => /* @__PURE__ */ import_react6.default.createElement(import_TableRow3.default, { key: r.model }, /* @__PURE__ */ import_react6.default.createElement(import_TableCell3.default, null, /* @__PURE__ */ import_react6.default.createElement(import_Box6.default, { display: "flex", alignItems: "center", gap: 1 }, /* @__PURE__ */ import_react6.default.createElement(import_Box6.default, { sx: { width: 8, height: 8, borderRadius: "50%", bgcolor: chartColor(r.model), flexShrink: 0 } }), /* @__PURE__ */ import_react6.default.createElement(import_Typography6.default, { variant: "body2", sx: { fontWeight: 600 } }, r.model))), /* @__PURE__ */ import_react6.default.createElement(import_TableCell3.default, { align: "right" }, fmtUsd(r.spend)), /* @__PURE__ */ import_react6.default.createElement(import_TableCell3.default, { align: "right" }, fmtInt(r.apiRequests)), /* @__PURE__ */ import_react6.default.createElement(import_TableCell3.default, { align: "right" }, fmtInt(r.successfulRequests)), /* @__PURE__ */ import_react6.default.createElement(import_TableCell3.default, { align: "right" }, fmtInt(r.failedRequests)), /* @__PURE__ */ import_react6.default.createElement(import_TableCell3.default, { align: "right" }, fmtCompact(r.promptTokens)), /* @__PURE__ */ import_react6.default.createElement(import_TableCell3.default, { align: "right" }, fmtCompact(r.completionTokens)), /* @__PURE__ */ import_react6.default.createElement(import_TableCell3.default, null, /* @__PURE__ */ import_react6.default.createElement(SuccessRateCell, { rate: r.successRate, requests: r.apiRequests }))));
|
|
2508
2592
|
};
|
|
2509
2593
|
const periodSelect = /* @__PURE__ */ import_react6.default.createElement(
|
|
2510
2594
|
import_TextField4.default,
|
|
@@ -2657,7 +2741,7 @@ var init_UsageStats = __esm({
|
|
|
2657
2741
|
height: Math.max(200, topModelSpendBars.length * 34)
|
|
2658
2742
|
},
|
|
2659
2743
|
/* @__PURE__ */ import_react6.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react6.default.createElement(import_recharts.BarChart, { data: topModelSpendBars, layout: "vertical", margin: { top: 0, right: 12, left: 0, bottom: 0 } }, /* @__PURE__ */ import_react6.default.createElement(import_recharts.CartesianGrid, { ...chart.grid, vertical: true, horizontal: false }), /* @__PURE__ */ import_react6.default.createElement(import_recharts.XAxis, { type: "number", tickFormatter: fmtCompact, ...chart.axis }), /* @__PURE__ */ import_react6.default.createElement(import_recharts.YAxis, { type: "category", dataKey: "model", width: 170, ...chart.axis, tick: { fontSize: 10.5, fill: chart.axis.tick.fill } }), /* @__PURE__ */ import_react6.default.createElement(import_recharts.Tooltip, { cursor: chart.cursor, content: /* @__PURE__ */ import_react6.default.createElement(ChartTooltip, { valueFormatter: fmtInt, labelFormatter: (l) => l }) }), /* @__PURE__ */ import_react6.default.createElement(import_recharts.Legend, { ...chart.legend }), /* @__PURE__ */ import_react6.default.createElement(import_recharts.Bar, { dataKey: "promptTokens", name: "Input", fill: SERIES.input, stackId: "t", barSize: 14 }), /* @__PURE__ */ import_react6.default.createElement(import_recharts.Bar, { dataKey: "completionTokens", name: "Output", fill: SERIES.output, stackId: "t", barSize: 14, radius: [0, 3, 3, 0] })))
|
|
2660
|
-
))), /* @__PURE__ */ import_react6.default.createElement(import_Grid.default, { item: true, xs: 12 }, /* @__PURE__ */ import_react6.default.createElement(
|
|
2744
|
+
))), /* @__PURE__ */ import_react6.default.createElement(import_Grid.default, { item: true, xs: 12 }, /* @__PURE__ */ import_react6.default.createElement(import_TableContainer3.default, { component: import_Paper4.default, variant: "outlined", sx: { borderRadius: 2 } }, /* @__PURE__ */ import_react6.default.createElement(import_Table3.default, { size: "small", sx: dataTableSx }, /* @__PURE__ */ import_react6.default.createElement(import_TableHead3.default, null, /* @__PURE__ */ import_react6.default.createElement(import_TableRow3.default, null, /* @__PURE__ */ import_react6.default.createElement(import_TableCell3.default, null, "Model"), /* @__PURE__ */ import_react6.default.createElement(import_TableCell3.default, { align: "right" }, "Spend"), /* @__PURE__ */ import_react6.default.createElement(import_TableCell3.default, { align: "right" }, "Requests"), /* @__PURE__ */ import_react6.default.createElement(import_TableCell3.default, { align: "right" }, "Success"), /* @__PURE__ */ import_react6.default.createElement(import_TableCell3.default, { align: "right" }, "Failed"), /* @__PURE__ */ import_react6.default.createElement(import_TableCell3.default, { align: "right" }, "Input"), /* @__PURE__ */ import_react6.default.createElement(import_TableCell3.default, { align: "right" }, "Output"), /* @__PURE__ */ import_react6.default.createElement(import_TableCell3.default, { sx: { width: 160 } }, "Success rate"))), /* @__PURE__ */ import_react6.default.createElement(import_TableBody3.default, null, renderModelRows()))))),
|
|
2661
2745
|
tab === "keys" && /* @__PURE__ */ import_react6.default.createElement(import_Grid.default, { container: true, spacing: 2 }, (loading || topKeySpendBars.length > 0) && /* @__PURE__ */ import_react6.default.createElement(import_Grid.default, { item: true, xs: 12 }, /* @__PURE__ */ import_react6.default.createElement(ChartCard, { title: "Spend by key", height: Math.max(160, topKeySpendBars.length * 32) }, /* @__PURE__ */ import_react6.default.createElement(
|
|
2662
2746
|
ChartOrFallback,
|
|
2663
2747
|
{
|
|
@@ -2666,7 +2750,7 @@ var init_UsageStats = __esm({
|
|
|
2666
2750
|
height: Math.max(160, topKeySpendBars.length * 32)
|
|
2667
2751
|
},
|
|
2668
2752
|
/* @__PURE__ */ import_react6.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react6.default.createElement(import_recharts.BarChart, { data: topKeySpendBars, layout: "vertical", margin: { top: 0, right: 12, left: 0, bottom: 0 } }, /* @__PURE__ */ import_react6.default.createElement(import_recharts.CartesianGrid, { ...chart.grid, vertical: true, horizontal: false }), /* @__PURE__ */ import_react6.default.createElement(import_recharts.XAxis, { type: "number", tickFormatter: fmtUsdCompact, ...chart.axis }), /* @__PURE__ */ import_react6.default.createElement(import_recharts.YAxis, { type: "category", dataKey: "keyAlias", width: 160, ...chart.axis, tick: { fontSize: 10.5, fill: chart.axis.tick.fill } }), /* @__PURE__ */ import_react6.default.createElement(import_recharts.Tooltip, { cursor: chart.cursor, content: /* @__PURE__ */ import_react6.default.createElement(ChartTooltip, { valueFormatter: fmtUsd, labelFormatter: (l) => l }) }), /* @__PURE__ */ import_react6.default.createElement(import_recharts.Bar, { dataKey: "spend", name: "Spend", fill: SERIES.input, radius: [0, 3, 3, 0], barSize: 14 })))
|
|
2669
|
-
))), /* @__PURE__ */ import_react6.default.createElement(import_Grid.default, { item: true, xs: 12 }, /* @__PURE__ */ import_react6.default.createElement(
|
|
2753
|
+
))), /* @__PURE__ */ import_react6.default.createElement(import_Grid.default, { item: true, xs: 12 }, /* @__PURE__ */ import_react6.default.createElement(import_TableContainer3.default, { component: import_Paper4.default, variant: "outlined", sx: { borderRadius: 2 } }, /* @__PURE__ */ import_react6.default.createElement(import_Table3.default, { size: "small", sx: dataTableSx }, /* @__PURE__ */ import_react6.default.createElement(import_TableHead3.default, null, /* @__PURE__ */ import_react6.default.createElement(import_TableRow3.default, null, /* @__PURE__ */ import_react6.default.createElement(import_TableCell3.default, null, "Key"), /* @__PURE__ */ import_react6.default.createElement(import_TableCell3.default, null, "Models"), /* @__PURE__ */ import_react6.default.createElement(import_TableCell3.default, { align: "right" }, "Spend"), /* @__PURE__ */ import_react6.default.createElement(import_TableCell3.default, { align: "right" }, "Requests"), /* @__PURE__ */ import_react6.default.createElement(import_TableCell3.default, { align: "right" }, "Tokens"), /* @__PURE__ */ import_react6.default.createElement(import_TableCell3.default, { align: "right" }, "Failed"), /* @__PURE__ */ import_react6.default.createElement(import_TableCell3.default, { sx: { width: 160 } }, "Success rate"))), /* @__PURE__ */ import_react6.default.createElement(import_TableBody3.default, null, renderKeyRows())))))
|
|
2670
2754
|
);
|
|
2671
2755
|
};
|
|
2672
2756
|
}
|
|
@@ -2678,26 +2762,26 @@ function budgetTone2(isOver, isNear) {
|
|
|
2678
2762
|
if (isNear) return "warning";
|
|
2679
2763
|
return "accent";
|
|
2680
2764
|
}
|
|
2681
|
-
var import_react7,
|
|
2765
|
+
var import_react7, import_Paper5, import_Box7, import_Typography7, import_Skeleton4, import_Divider3, import_Table4, import_TableBody4, import_TableCell4, import_TableHead4, import_TableRow4, import_TableContainer4, import_Collapse, import_IconButton4, import_Button5, import_CircularProgress4, import_styles4, import_icons_material5, import_recharts2, fmtUsd2, TeamCard, TeamUsage;
|
|
2682
2766
|
var init_TeamUsage = __esm({
|
|
2683
2767
|
"src/components/TeamUsage.tsx"() {
|
|
2684
2768
|
"use strict";
|
|
2685
2769
|
import_react7 = __toESM(require("react"));
|
|
2686
|
-
|
|
2770
|
+
import_Paper5 = __toESM(require("@mui/material/Paper"));
|
|
2687
2771
|
import_Box7 = __toESM(require("@mui/material/Box"));
|
|
2688
2772
|
import_Typography7 = __toESM(require("@mui/material/Typography"));
|
|
2689
2773
|
import_Skeleton4 = __toESM(require("@mui/material/Skeleton"));
|
|
2690
2774
|
import_Divider3 = __toESM(require("@mui/material/Divider"));
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2775
|
+
import_Table4 = __toESM(require("@mui/material/Table"));
|
|
2776
|
+
import_TableBody4 = __toESM(require("@mui/material/TableBody"));
|
|
2777
|
+
import_TableCell4 = __toESM(require("@mui/material/TableCell"));
|
|
2778
|
+
import_TableHead4 = __toESM(require("@mui/material/TableHead"));
|
|
2779
|
+
import_TableRow4 = __toESM(require("@mui/material/TableRow"));
|
|
2780
|
+
import_TableContainer4 = __toESM(require("@mui/material/TableContainer"));
|
|
2697
2781
|
import_Collapse = __toESM(require("@mui/material/Collapse"));
|
|
2698
2782
|
import_IconButton4 = __toESM(require("@mui/material/IconButton"));
|
|
2699
2783
|
import_Button5 = __toESM(require("@mui/material/Button"));
|
|
2700
|
-
|
|
2784
|
+
import_CircularProgress4 = __toESM(require("@mui/material/CircularProgress"));
|
|
2701
2785
|
import_styles4 = require("@mui/material/styles");
|
|
2702
2786
|
import_icons_material5 = require("@mui/icons-material");
|
|
2703
2787
|
import_recharts2 = require("recharts");
|
|
@@ -2714,7 +2798,7 @@ var init_TeamUsage = __esm({
|
|
|
2714
2798
|
const dailyData = usage?.daily_usage?.map((d) => ({ date: d.date, spend: d.spend })) ?? [];
|
|
2715
2799
|
const renderDailySpendSection = () => {
|
|
2716
2800
|
if (usageLoading) {
|
|
2717
|
-
return /* @__PURE__ */ import_react7.default.createElement(import_Box7.default, { display: "flex", justifyContent: "center", py: 3 }, /* @__PURE__ */ import_react7.default.createElement(
|
|
2801
|
+
return /* @__PURE__ */ import_react7.default.createElement(import_Box7.default, { display: "flex", justifyContent: "center", py: 3 }, /* @__PURE__ */ import_react7.default.createElement(import_CircularProgress4.default, { size: 22 }));
|
|
2718
2802
|
}
|
|
2719
2803
|
if (dailyData.length === 0) return null;
|
|
2720
2804
|
return /* @__PURE__ */ import_react7.default.createElement(import_Box7.default, { mb: 2.5 }, /* @__PURE__ */ import_react7.default.createElement(
|
|
@@ -2753,7 +2837,7 @@ var init_TeamUsage = __esm({
|
|
|
2753
2837
|
},
|
|
2754
2838
|
label
|
|
2755
2839
|
);
|
|
2756
|
-
return /* @__PURE__ */ import_react7.default.createElement(
|
|
2840
|
+
return /* @__PURE__ */ import_react7.default.createElement(import_Paper5.default, { variant: "outlined", sx: { borderRadius: 2, p: 2.5 } }, /* @__PURE__ */ import_react7.default.createElement(import_Box7.default, { display: "flex", alignItems: "center", gap: 1.5 }, /* @__PURE__ */ import_react7.default.createElement(
|
|
2757
2841
|
import_Box7.default,
|
|
2758
2842
|
{
|
|
2759
2843
|
sx: (theme) => ({
|
|
@@ -2818,13 +2902,13 @@ var init_TeamUsage = __esm({
|
|
|
2818
2902
|
/* @__PURE__ */ import_react7.default.createElement(Stat, { label: "TPM", value: team.tpm_limit && team.tpm_limit > 0 ? team.tpm_limit.toLocaleString() : "\u2014" }),
|
|
2819
2903
|
/* @__PURE__ */ import_react7.default.createElement(Stat, { label: "RPM", value: team.rpm_limit && team.rpm_limit > 0 ? team.rpm_limit.toLocaleString() : "\u2014" })
|
|
2820
2904
|
), budget > 0 && /* @__PURE__ */ import_react7.default.createElement(import_Box7.default, { mt: 2, maxWidth: 640 }, /* @__PURE__ */ import_react7.default.createElement(import_Box7.default, { display: "flex", justifyContent: "space-between", alignItems: "baseline", mb: 0.75 }, /* @__PURE__ */ import_react7.default.createElement(import_Typography7.default, { variant: "caption", color: "text.secondary", sx: { fontVariantNumeric: "tabular-nums" } }, fmtUsd2(spend), " of ", fmtUsd2(budget)), /* @__PURE__ */ import_react7.default.createElement(import_Typography7.default, { variant: "caption", color: "text.secondary", sx: { fontVariantNumeric: "tabular-nums" } }, budgetPct.toFixed(0), "%")), /* @__PURE__ */ import_react7.default.createElement(Meter, { value: budgetPct, tone: budgetTone2(isOver, isNear), height: 5 })), /* @__PURE__ */ import_react7.default.createElement(import_Collapse.default, { in: expanded, unmountOnExit: true }, /* @__PURE__ */ import_react7.default.createElement(import_Divider3.default, { sx: { my: 2.5 } }), /* @__PURE__ */ import_react7.default.createElement(import_Box7.default, { mb: 2.5 }, sectionLabel("Models"), team.models?.length ? /* @__PURE__ */ import_react7.default.createElement(import_Box7.default, { display: "flex", gap: 0.5, flexWrap: "wrap" }, team.models.map((m) => /* @__PURE__ */ import_react7.default.createElement(TagChip, { key: m, label: m, title: m }))) : /* @__PURE__ */ import_react7.default.createElement(import_Typography7.default, { variant: "body2", color: "text.secondary" }, "All models allowed")), renderDailySpendSection(), /* @__PURE__ */ import_react7.default.createElement(import_Box7.default, null, sectionLabel("Members"), team.members_with_roles?.length ? /* @__PURE__ */ import_react7.default.createElement(
|
|
2821
|
-
|
|
2905
|
+
import_TableContainer4.default,
|
|
2822
2906
|
{
|
|
2823
|
-
component:
|
|
2907
|
+
component: import_Paper5.default,
|
|
2824
2908
|
variant: "outlined",
|
|
2825
2909
|
sx: { borderRadius: 1.5 }
|
|
2826
2910
|
},
|
|
2827
|
-
/* @__PURE__ */ import_react7.default.createElement(
|
|
2911
|
+
/* @__PURE__ */ import_react7.default.createElement(import_Table4.default, { size: "small", sx: dataTableSx }, /* @__PURE__ */ import_react7.default.createElement(import_TableHead4.default, null, /* @__PURE__ */ import_react7.default.createElement(import_TableRow4.default, null, /* @__PURE__ */ import_react7.default.createElement(import_TableCell4.default, null, "User"), /* @__PURE__ */ import_react7.default.createElement(import_TableCell4.default, { align: "right" }, "Role"))), /* @__PURE__ */ import_react7.default.createElement(import_TableBody4.default, null, team.members_with_roles.map((m) => /* @__PURE__ */ import_react7.default.createElement(import_TableRow4.default, { key: m.user_id }, /* @__PURE__ */ import_react7.default.createElement(import_TableCell4.default, null, m.user_email ? /* @__PURE__ */ import_react7.default.createElement(import_react7.default.Fragment, null, /* @__PURE__ */ import_react7.default.createElement(import_Typography7.default, { variant: "body2" }, m.user_email), /* @__PURE__ */ import_react7.default.createElement(
|
|
2828
2912
|
import_Typography7.default,
|
|
2829
2913
|
{
|
|
2830
2914
|
variant: "caption",
|
|
@@ -2839,7 +2923,7 @@ var init_TeamUsage = __esm({
|
|
|
2839
2923
|
sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace" }
|
|
2840
2924
|
},
|
|
2841
2925
|
m.user_id
|
|
2842
|
-
)), /* @__PURE__ */ import_react7.default.createElement(
|
|
2926
|
+
)), /* @__PURE__ */ import_react7.default.createElement(import_TableCell4.default, { align: "right" }, /* @__PURE__ */ import_react7.default.createElement(
|
|
2843
2927
|
StatusPill,
|
|
2844
2928
|
{
|
|
2845
2929
|
label: m.role,
|
|
@@ -2855,13 +2939,26 @@ var init_TeamUsage = __esm({
|
|
|
2855
2939
|
getTeamUsage,
|
|
2856
2940
|
getTeamUsageLoading,
|
|
2857
2941
|
canManage,
|
|
2858
|
-
onEditTeam
|
|
2942
|
+
onEditTeam,
|
|
2943
|
+
canCreate,
|
|
2944
|
+
onCreateTeam
|
|
2859
2945
|
}) => {
|
|
2946
|
+
const createAction = canCreate && onCreateTeam ? /* @__PURE__ */ import_react7.default.createElement(
|
|
2947
|
+
import_Button5.default,
|
|
2948
|
+
{
|
|
2949
|
+
variant: "contained",
|
|
2950
|
+
color: "primary",
|
|
2951
|
+
disableElevation: true,
|
|
2952
|
+
startIcon: /* @__PURE__ */ import_react7.default.createElement(import_icons_material5.Add, null),
|
|
2953
|
+
onClick: onCreateTeam
|
|
2954
|
+
},
|
|
2955
|
+
"Create Team"
|
|
2956
|
+
) : void 0;
|
|
2860
2957
|
if (loading) {
|
|
2861
|
-
return /* @__PURE__ */ import_react7.default.createElement(SectionCard, { title: "Teams" }, /* @__PURE__ */ import_react7.default.createElement(import_Skeleton4.default, { variant: "rounded", height: 120, sx: { mb: 2 } }), /* @__PURE__ */ import_react7.default.createElement(import_Skeleton4.default, { variant: "rounded", height: 120 }));
|
|
2958
|
+
return /* @__PURE__ */ import_react7.default.createElement(SectionCard, { title: "Teams", actions: createAction }, /* @__PURE__ */ import_react7.default.createElement(import_Skeleton4.default, { variant: "rounded", height: 120, sx: { mb: 2 } }), /* @__PURE__ */ import_react7.default.createElement(import_Skeleton4.default, { variant: "rounded", height: 120 }));
|
|
2862
2959
|
}
|
|
2863
2960
|
if (!teams.length) {
|
|
2864
|
-
return /* @__PURE__ */ import_react7.default.createElement(SectionCard, { title: "Teams" }, /* @__PURE__ */ import_react7.default.createElement(
|
|
2961
|
+
return /* @__PURE__ */ import_react7.default.createElement(SectionCard, { title: "Teams", actions: createAction }, /* @__PURE__ */ import_react7.default.createElement(
|
|
2865
2962
|
EmptyState,
|
|
2866
2963
|
{
|
|
2867
2964
|
message: "You're not a member of any LiteLLM team yet.",
|
|
@@ -2869,17 +2966,25 @@ var init_TeamUsage = __esm({
|
|
|
2869
2966
|
}
|
|
2870
2967
|
));
|
|
2871
2968
|
}
|
|
2872
|
-
return /* @__PURE__ */ import_react7.default.createElement(
|
|
2873
|
-
|
|
2969
|
+
return /* @__PURE__ */ import_react7.default.createElement(
|
|
2970
|
+
SectionCard,
|
|
2874
2971
|
{
|
|
2875
|
-
|
|
2876
|
-
team
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2972
|
+
title: "Teams",
|
|
2973
|
+
subtitle: `${teams.length} team${teams.length === 1 ? "" : "s"}`,
|
|
2974
|
+
actions: createAction
|
|
2975
|
+
},
|
|
2976
|
+
/* @__PURE__ */ import_react7.default.createElement(import_Box7.default, { sx: { display: "flex", flexDirection: "column", gap: 2 } }, teams.map((team) => /* @__PURE__ */ import_react7.default.createElement(
|
|
2977
|
+
TeamCard,
|
|
2978
|
+
{
|
|
2979
|
+
key: team.team_id,
|
|
2980
|
+
team,
|
|
2981
|
+
usage: getTeamUsage(team.team_id),
|
|
2982
|
+
usageLoading: getTeamUsageLoading(team.team_id),
|
|
2983
|
+
canManage,
|
|
2984
|
+
onEditTeam
|
|
2985
|
+
}
|
|
2986
|
+
)))
|
|
2987
|
+
);
|
|
2883
2988
|
};
|
|
2884
2989
|
}
|
|
2885
2990
|
});
|
|
@@ -2902,18 +3007,18 @@ function fmtTokens(n) {
|
|
|
2902
3007
|
if (n >= 1e3) return `${(n / 1e3).toFixed(0)}K`;
|
|
2903
3008
|
return String(n);
|
|
2904
3009
|
}
|
|
2905
|
-
var import_react8, import_Box8,
|
|
3010
|
+
var import_react8, import_Box8, import_Table5, import_TableBody5, import_TableCell5, import_TableContainer5, import_TableHead5, import_TableRow5, import_Typography8, import_MenuItem4, import_Select, import_FormControl, import_InputLabel, import_ContentCopy, import_Tooltip, import_Snackbar, import_Alert3, import_styles5, import_TextField5, import_InputAdornment2, import_icons_material6, ALL_PROXY_MODELS2, ModelsTable;
|
|
2906
3011
|
var init_ModelsTable = __esm({
|
|
2907
3012
|
"src/components/ModelsTable.tsx"() {
|
|
2908
3013
|
"use strict";
|
|
2909
3014
|
import_react8 = __toESM(require("react"));
|
|
2910
3015
|
import_Box8 = __toESM(require("@mui/material/Box"));
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
3016
|
+
import_Table5 = __toESM(require("@mui/material/Table"));
|
|
3017
|
+
import_TableBody5 = __toESM(require("@mui/material/TableBody"));
|
|
3018
|
+
import_TableCell5 = __toESM(require("@mui/material/TableCell"));
|
|
3019
|
+
import_TableContainer5 = __toESM(require("@mui/material/TableContainer"));
|
|
3020
|
+
import_TableHead5 = __toESM(require("@mui/material/TableHead"));
|
|
3021
|
+
import_TableRow5 = __toESM(require("@mui/material/TableRow"));
|
|
2917
3022
|
import_Typography8 = __toESM(require("@mui/material/Typography"));
|
|
2918
3023
|
import_MenuItem4 = __toESM(require("@mui/material/MenuItem"));
|
|
2919
3024
|
import_Select = __toESM(require("@mui/material/Select"));
|
|
@@ -2925,10 +3030,14 @@ var init_ModelsTable = __esm({
|
|
|
2925
3030
|
import_Alert3 = __toESM(require("@mui/material/Alert"));
|
|
2926
3031
|
init_ui();
|
|
2927
3032
|
import_styles5 = require("@mui/material/styles");
|
|
3033
|
+
import_TextField5 = __toESM(require("@mui/material/TextField"));
|
|
3034
|
+
import_InputAdornment2 = __toESM(require("@mui/material/InputAdornment"));
|
|
3035
|
+
import_icons_material6 = require("@mui/icons-material");
|
|
2928
3036
|
ALL_PROXY_MODELS2 = "all-proxy-models";
|
|
2929
3037
|
ModelsTable = ({ allModels, teams, loading }) => {
|
|
2930
3038
|
const theme = (0, import_styles5.useTheme)();
|
|
2931
3039
|
const [selectedTeamId, setSelectedTeamId] = (0, import_react8.useState)("");
|
|
3040
|
+
const [filterText, setFilterText] = (0, import_react8.useState)("");
|
|
2932
3041
|
const [copiedSnackbar, setCopiedSnackbar] = (0, import_react8.useState)(false);
|
|
2933
3042
|
const selectedTeam = (0, import_react8.useMemo)(
|
|
2934
3043
|
() => teams.find((t) => t.team_id === selectedTeamId) ?? null,
|
|
@@ -2936,8 +3045,15 @@ var init_ModelsTable = __esm({
|
|
|
2936
3045
|
);
|
|
2937
3046
|
const filteredModels = (0, import_react8.useMemo)(() => {
|
|
2938
3047
|
if (!selectedTeam) return [];
|
|
2939
|
-
|
|
2940
|
-
|
|
3048
|
+
let result = allModels.filter((model) => isModelAllowedByTeam2(model, selectedTeam.models));
|
|
3049
|
+
if (filterText.trim()) {
|
|
3050
|
+
const q = filterText.toLowerCase();
|
|
3051
|
+
result = result.filter(
|
|
3052
|
+
(model) => model.model_name.toLowerCase().includes(q) || (model.access_groups || []).some((group) => group.toLowerCase().includes(q))
|
|
3053
|
+
);
|
|
3054
|
+
}
|
|
3055
|
+
return result;
|
|
3056
|
+
}, [allModels, selectedTeam, filterText]);
|
|
2941
3057
|
const handleCopyModelId = (0, import_react8.useCallback)((modelId) => {
|
|
2942
3058
|
navigator.clipboard.writeText(modelId).then(() => setCopiedSnackbar(true));
|
|
2943
3059
|
}, []);
|
|
@@ -2970,7 +3086,19 @@ var init_ModelsTable = __esm({
|
|
|
2970
3086
|
onChange: (e) => setSelectedTeamId(e.target.value)
|
|
2971
3087
|
},
|
|
2972
3088
|
teams.map((t) => /* @__PURE__ */ import_react8.default.createElement(import_MenuItem4.default, { key: t.team_id, value: t.team_id }, t.team_alias ?? t.team_id))
|
|
2973
|
-
))
|
|
3089
|
+
)), /* @__PURE__ */ import_react8.default.createElement(
|
|
3090
|
+
import_TextField5.default,
|
|
3091
|
+
{
|
|
3092
|
+
label: "Filter Models",
|
|
3093
|
+
size: "small",
|
|
3094
|
+
value: filterText,
|
|
3095
|
+
onChange: (e) => setFilterText(e.target.value),
|
|
3096
|
+
sx: { minWidth: 240, mb: 2 },
|
|
3097
|
+
InputProps: {
|
|
3098
|
+
startAdornment: /* @__PURE__ */ import_react8.default.createElement(import_InputAdornment2.default, { position: "start" }, /* @__PURE__ */ import_react8.default.createElement(import_icons_material6.Search, { fontSize: "small" }))
|
|
3099
|
+
}
|
|
3100
|
+
}
|
|
3101
|
+
)),
|
|
2974
3102
|
loading && /* @__PURE__ */ import_react8.default.createElement(EmptyState, { message: "Loading models\u2026" }),
|
|
2975
3103
|
!loading && !selectedTeamId && /* @__PURE__ */ import_react8.default.createElement(
|
|
2976
3104
|
EmptyState,
|
|
@@ -2986,7 +3114,7 @@ var init_ModelsTable = __esm({
|
|
|
2986
3114
|
hint: allModels.length === 0 ? "No models are configured in the system" : "None of the team's assigned models were found in the catalog"
|
|
2987
3115
|
}
|
|
2988
3116
|
),
|
|
2989
|
-
!loading && filteredModels.length > 0 && /* @__PURE__ */ import_react8.default.createElement(
|
|
3117
|
+
!loading && filteredModels.length > 0 && /* @__PURE__ */ import_react8.default.createElement(import_TableContainer5.default, null, /* @__PURE__ */ import_react8.default.createElement(import_Table5.default, { size: "small", sx: dataTableSx }, /* @__PURE__ */ import_react8.default.createElement(import_TableHead5.default, null, /* @__PURE__ */ import_react8.default.createElement(import_TableRow5.default, null, /* @__PURE__ */ import_react8.default.createElement(import_TableCell5.default, null, "Model ID"), /* @__PURE__ */ import_react8.default.createElement(import_TableCell5.default, null, "Mode"), /* @__PURE__ */ import_react8.default.createElement(import_TableCell5.default, { align: "right" }, "Input Cost"), /* @__PURE__ */ import_react8.default.createElement(import_TableCell5.default, { align: "right" }, "Output Cost"), /* @__PURE__ */ import_react8.default.createElement(import_TableCell5.default, { align: "right" }, "Max Input"), /* @__PURE__ */ import_react8.default.createElement(import_TableCell5.default, { align: "right" }, "Max Output"), /* @__PURE__ */ import_react8.default.createElement(import_TableCell5.default, null, "Capabilities"))), /* @__PURE__ */ import_react8.default.createElement(import_TableBody5.default, null, filteredModels.map((m) => /* @__PURE__ */ import_react8.default.createElement(import_TableRow5.default, { key: m.model_name, hover: true }, /* @__PURE__ */ import_react8.default.createElement(import_TableCell5.default, null, /* @__PURE__ */ import_react8.default.createElement(import_Box8.default, { sx: { display: "flex", alignItems: "center", gap: 0.5 } }, /* @__PURE__ */ import_react8.default.createElement(TagChip, { label: m.model_name, title: m.model_name }), /* @__PURE__ */ import_react8.default.createElement(import_Tooltip.default, { title: "Copy model ID" }, /* @__PURE__ */ import_react8.default.createElement(
|
|
2990
3118
|
import_Box8.default,
|
|
2991
3119
|
{
|
|
2992
3120
|
component: "span",
|
|
@@ -2998,7 +3126,7 @@ var init_ModelsTable = __esm({
|
|
|
2998
3126
|
style: { cursor: "pointer", display: "inline-flex" }
|
|
2999
3127
|
},
|
|
3000
3128
|
/* @__PURE__ */ import_react8.default.createElement(import_ContentCopy.default, { sx: { fontSize: 14 } })
|
|
3001
|
-
)))), /* @__PURE__ */ import_react8.default.createElement(
|
|
3129
|
+
)))), /* @__PURE__ */ import_react8.default.createElement(import_TableCell5.default, null, /* @__PURE__ */ import_react8.default.createElement(
|
|
3002
3130
|
import_Box8.default,
|
|
3003
3131
|
{
|
|
3004
3132
|
component: "span",
|
|
@@ -3015,7 +3143,7 @@ var init_ModelsTable = __esm({
|
|
|
3015
3143
|
}
|
|
3016
3144
|
},
|
|
3017
3145
|
m.mode
|
|
3018
|
-
)), /* @__PURE__ */ import_react8.default.createElement(
|
|
3146
|
+
)), /* @__PURE__ */ import_react8.default.createElement(import_TableCell5.default, { align: "right" }, /* @__PURE__ */ import_react8.default.createElement(import_Typography8.default, { variant: "body2", sx: { fontVariantNumeric: "tabular-nums" } }, fmtCostPerToken(m.input_cost_per_token))), /* @__PURE__ */ import_react8.default.createElement(import_TableCell5.default, { align: "right" }, /* @__PURE__ */ import_react8.default.createElement(import_Typography8.default, { variant: "body2", sx: { fontVariantNumeric: "tabular-nums" } }, fmtCostPerToken(m.output_cost_per_token))), /* @__PURE__ */ import_react8.default.createElement(import_TableCell5.default, { align: "right" }, /* @__PURE__ */ import_react8.default.createElement(import_Typography8.default, { variant: "body2", sx: { fontVariantNumeric: "tabular-nums" } }, fmtTokens(m.max_input_tokens))), /* @__PURE__ */ import_react8.default.createElement(import_TableCell5.default, { align: "right" }, /* @__PURE__ */ import_react8.default.createElement(import_Typography8.default, { variant: "body2", sx: { fontVariantNumeric: "tabular-nums" } }, fmtTokens(m.max_output_tokens))), /* @__PURE__ */ import_react8.default.createElement(import_TableCell5.default, null, /* @__PURE__ */ import_react8.default.createElement(import_Box8.default, { sx: { display: "flex", gap: 0.5, flexWrap: "wrap" } }, m.supports_function_calling && /* @__PURE__ */ import_react8.default.createElement(TagChip, { label: "Fn", title: "Function calling" }), m.supports_vision && /* @__PURE__ */ import_react8.default.createElement(TagChip, { label: "\u{1F441}", title: "Vision" })))))))),
|
|
3019
3147
|
/* @__PURE__ */ import_react8.default.createElement(
|
|
3020
3148
|
import_Snackbar.default,
|
|
3021
3149
|
{
|
|
@@ -3052,10 +3180,10 @@ function formatDateTime(iso) {
|
|
|
3052
3180
|
}
|
|
3053
3181
|
function renderAuditLogBody(loading, entries) {
|
|
3054
3182
|
if (loading) {
|
|
3055
|
-
return /* @__PURE__ */ import_react9.default.createElement(
|
|
3183
|
+
return /* @__PURE__ */ import_react9.default.createElement(import_TableRow6.default, null, /* @__PURE__ */ import_react9.default.createElement(import_TableCell6.default, { colSpan: 6, sx: { py: 2 } }, /* @__PURE__ */ import_react9.default.createElement(import_Skeleton5.default, { variant: "rounded", height: 160 })));
|
|
3056
3184
|
}
|
|
3057
3185
|
if (entries.length === 0) {
|
|
3058
|
-
return /* @__PURE__ */ import_react9.default.createElement(
|
|
3186
|
+
return /* @__PURE__ */ import_react9.default.createElement(import_TableRow6.default, null, /* @__PURE__ */ import_react9.default.createElement(import_TableCell6.default, { colSpan: 6 }, /* @__PURE__ */ import_react9.default.createElement(
|
|
3059
3187
|
EmptyState,
|
|
3060
3188
|
{
|
|
3061
3189
|
message: "No audit events found",
|
|
@@ -3065,29 +3193,29 @@ function renderAuditLogBody(loading, entries) {
|
|
|
3065
3193
|
}
|
|
3066
3194
|
return entries.map((entry) => /* @__PURE__ */ import_react9.default.createElement(DetailRow, { key: entry.id, entry }));
|
|
3067
3195
|
}
|
|
3068
|
-
var import_react9, import_Box9, import_Typography9,
|
|
3196
|
+
var import_react9, import_Box9, import_Typography9, import_Table6, import_TableBody6, import_TableCell6, import_TableContainer6, import_TableHead6, import_TableRow6, import_TablePagination, import_TextField6, import_MenuItem5, import_Skeleton5, import_Collapse2, import_IconButton5, import_Alert4, import_styles6, import_icons_material7, import_react_use2, ACTION_TONES, TABLE_LABELS, DetailRow, AuditLog;
|
|
3069
3197
|
var init_AuditLog = __esm({
|
|
3070
3198
|
"src/components/AuditLog.tsx"() {
|
|
3071
3199
|
"use strict";
|
|
3072
3200
|
import_react9 = __toESM(require("react"));
|
|
3073
3201
|
import_Box9 = __toESM(require("@mui/material/Box"));
|
|
3074
3202
|
import_Typography9 = __toESM(require("@mui/material/Typography"));
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3203
|
+
import_Table6 = __toESM(require("@mui/material/Table"));
|
|
3204
|
+
import_TableBody6 = __toESM(require("@mui/material/TableBody"));
|
|
3205
|
+
import_TableCell6 = __toESM(require("@mui/material/TableCell"));
|
|
3206
|
+
import_TableContainer6 = __toESM(require("@mui/material/TableContainer"));
|
|
3207
|
+
import_TableHead6 = __toESM(require("@mui/material/TableHead"));
|
|
3208
|
+
import_TableRow6 = __toESM(require("@mui/material/TableRow"));
|
|
3081
3209
|
import_TablePagination = __toESM(require("@mui/material/TablePagination"));
|
|
3082
|
-
|
|
3210
|
+
import_TextField6 = __toESM(require("@mui/material/TextField"));
|
|
3083
3211
|
import_MenuItem5 = __toESM(require("@mui/material/MenuItem"));
|
|
3084
3212
|
import_Skeleton5 = __toESM(require("@mui/material/Skeleton"));
|
|
3085
3213
|
import_Collapse2 = __toESM(require("@mui/material/Collapse"));
|
|
3086
3214
|
import_IconButton5 = __toESM(require("@mui/material/IconButton"));
|
|
3087
3215
|
import_Alert4 = __toESM(require("@mui/material/Alert"));
|
|
3088
3216
|
import_styles6 = require("@mui/material/styles");
|
|
3089
|
-
|
|
3090
|
-
|
|
3217
|
+
import_icons_material7 = require("@mui/icons-material");
|
|
3218
|
+
import_react_use2 = require("react-use");
|
|
3091
3219
|
init_ui();
|
|
3092
3220
|
ACTION_TONES = {
|
|
3093
3221
|
created: "success",
|
|
@@ -3106,7 +3234,7 @@ var init_AuditLog = __esm({
|
|
|
3106
3234
|
DetailRow = ({ entry }) => {
|
|
3107
3235
|
const [open, setOpen] = (0, import_react9.useState)(false);
|
|
3108
3236
|
const hasDetail = entry.before_value || entry.updated_values;
|
|
3109
|
-
return /* @__PURE__ */ import_react9.default.createElement(import_react9.default.Fragment, null, /* @__PURE__ */ import_react9.default.createElement(
|
|
3237
|
+
return /* @__PURE__ */ import_react9.default.createElement(import_react9.default.Fragment, null, /* @__PURE__ */ import_react9.default.createElement(import_TableRow6.default, null, /* @__PURE__ */ import_react9.default.createElement(import_TableCell6.default, { sx: { width: 40, pr: 0 } }, hasDetail && /* @__PURE__ */ import_react9.default.createElement(
|
|
3110
3238
|
import_IconButton5.default,
|
|
3111
3239
|
{
|
|
3112
3240
|
size: "small",
|
|
@@ -3119,8 +3247,8 @@ var init_AuditLog = __esm({
|
|
|
3119
3247
|
transition: theme.transitions.create("transform")
|
|
3120
3248
|
})
|
|
3121
3249
|
},
|
|
3122
|
-
/* @__PURE__ */ import_react9.default.createElement(
|
|
3123
|
-
)), /* @__PURE__ */ import_react9.default.createElement(
|
|
3250
|
+
/* @__PURE__ */ import_react9.default.createElement(import_icons_material7.KeyboardArrowDown, { fontSize: "small" })
|
|
3251
|
+
)), /* @__PURE__ */ import_react9.default.createElement(import_TableCell6.default, { sx: { whiteSpace: "nowrap" } }, /* @__PURE__ */ import_react9.default.createElement(import_Typography9.default, { variant: "body2", color: "text.secondary" }, formatDateTime(entry.updated_at))), /* @__PURE__ */ import_react9.default.createElement(import_TableCell6.default, null, entry.action && /* @__PURE__ */ import_react9.default.createElement(StatusPill, { label: entry.action, tone: actionTone(entry.action) })), /* @__PURE__ */ import_react9.default.createElement(import_TableCell6.default, null, /* @__PURE__ */ import_react9.default.createElement(import_Typography9.default, { variant: "body2" }, prettyTableName(entry.table_name))), /* @__PURE__ */ import_react9.default.createElement(import_TableCell6.default, null, /* @__PURE__ */ import_react9.default.createElement(
|
|
3124
3252
|
import_Typography9.default,
|
|
3125
3253
|
{
|
|
3126
3254
|
variant: "body2",
|
|
@@ -3130,7 +3258,7 @@ var init_AuditLog = __esm({
|
|
|
3130
3258
|
sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 12 }
|
|
3131
3259
|
},
|
|
3132
3260
|
entry.object_id ? entry.object_id.slice(0, 20) + (entry.object_id.length > 20 ? "\u2026" : "") : "\u2014"
|
|
3133
|
-
)), /* @__PURE__ */ import_react9.default.createElement(
|
|
3261
|
+
)), /* @__PURE__ */ import_react9.default.createElement(import_TableCell6.default, null, entry.changed_by ?? "\u2014")), hasDetail && /* @__PURE__ */ import_react9.default.createElement(import_TableRow6.default, { sx: { "&&:hover": { bgcolor: "transparent" } } }, /* @__PURE__ */ import_react9.default.createElement(import_TableCell6.default, { colSpan: 6, sx: { "&&": { py: 0, border: 0 } } }, /* @__PURE__ */ import_react9.default.createElement(import_Collapse2.default, { in: open, unmountOnExit: true }, /* @__PURE__ */ import_react9.default.createElement(
|
|
3134
3262
|
import_Box9.default,
|
|
3135
3263
|
{
|
|
3136
3264
|
display: "flex",
|
|
@@ -3175,7 +3303,7 @@ var init_AuditLog = __esm({
|
|
|
3175
3303
|
() => ({ page: page + 1, page_size: pageSize, ...filters }),
|
|
3176
3304
|
[page, pageSize, filters]
|
|
3177
3305
|
)();
|
|
3178
|
-
const { value, loading, error } = (0,
|
|
3306
|
+
const { value, loading, error } = (0, import_react_use2.useAsync)(
|
|
3179
3307
|
() => api.getAuditLogs(fetchParams),
|
|
3180
3308
|
[api, fetchParams]
|
|
3181
3309
|
);
|
|
@@ -3188,7 +3316,7 @@ var init_AuditLog = __esm({
|
|
|
3188
3316
|
subtitle: loading ? "Loading\u2026" : `${total.toLocaleString()} event${total === 1 ? "" : "s"}`,
|
|
3189
3317
|
flush: true,
|
|
3190
3318
|
actions: /* @__PURE__ */ import_react9.default.createElement(import_react9.default.Fragment, null, /* @__PURE__ */ import_react9.default.createElement(
|
|
3191
|
-
|
|
3319
|
+
import_TextField6.default,
|
|
3192
3320
|
{
|
|
3193
3321
|
size: "small",
|
|
3194
3322
|
label: "Action",
|
|
@@ -3206,7 +3334,7 @@ var init_AuditLog = __esm({
|
|
|
3206
3334
|
/* @__PURE__ */ import_react9.default.createElement(import_MenuItem5.default, { value: "deleted" }, "Deleted"),
|
|
3207
3335
|
/* @__PURE__ */ import_react9.default.createElement(import_MenuItem5.default, { value: "blocked" }, "Blocked")
|
|
3208
3336
|
), /* @__PURE__ */ import_react9.default.createElement(
|
|
3209
|
-
|
|
3337
|
+
import_TextField6.default,
|
|
3210
3338
|
{
|
|
3211
3339
|
size: "small",
|
|
3212
3340
|
label: "Table",
|
|
@@ -3225,7 +3353,7 @@ var init_AuditLog = __esm({
|
|
|
3225
3353
|
/* @__PURE__ */ import_react9.default.createElement(import_MenuItem5.default, { value: "LiteLLM_ObjectPermissionTable" }, "Team access (KB / MCP)"),
|
|
3226
3354
|
/* @__PURE__ */ import_react9.default.createElement(import_MenuItem5.default, { value: "LiteLLM_UserTable" }, "User")
|
|
3227
3355
|
), /* @__PURE__ */ import_react9.default.createElement(
|
|
3228
|
-
|
|
3356
|
+
import_TextField6.default,
|
|
3229
3357
|
{
|
|
3230
3358
|
size: "small",
|
|
3231
3359
|
label: "Changed by",
|
|
@@ -3239,7 +3367,7 @@ var init_AuditLog = __esm({
|
|
|
3239
3367
|
))
|
|
3240
3368
|
},
|
|
3241
3369
|
error && /* @__PURE__ */ import_react9.default.createElement(import_Box9.default, { px: 2.5, pb: 2 }, /* @__PURE__ */ import_react9.default.createElement(import_Alert4.default, { severity: "error" }, error.message)),
|
|
3242
|
-
/* @__PURE__ */ import_react9.default.createElement(
|
|
3370
|
+
/* @__PURE__ */ import_react9.default.createElement(import_TableContainer6.default, null, /* @__PURE__ */ import_react9.default.createElement(import_Table6.default, { size: "small", sx: dataTableSx }, /* @__PURE__ */ import_react9.default.createElement(import_TableHead6.default, null, /* @__PURE__ */ import_react9.default.createElement(import_TableRow6.default, null, /* @__PURE__ */ import_react9.default.createElement(import_TableCell6.default, { sx: { width: 40 } }), /* @__PURE__ */ import_react9.default.createElement(import_TableCell6.default, null, "Time"), /* @__PURE__ */ import_react9.default.createElement(import_TableCell6.default, null, "Action"), /* @__PURE__ */ import_react9.default.createElement(import_TableCell6.default, null, "Table"), /* @__PURE__ */ import_react9.default.createElement(import_TableCell6.default, null, "Object ID"), /* @__PURE__ */ import_react9.default.createElement(import_TableCell6.default, null, "Changed By"))), /* @__PURE__ */ import_react9.default.createElement(import_TableBody6.default, null, renderAuditLogBody(loading, entries)))),
|
|
3243
3371
|
/* @__PURE__ */ import_react9.default.createElement(
|
|
3244
3372
|
import_TablePagination.default,
|
|
3245
3373
|
{
|
|
@@ -3309,7 +3437,7 @@ function initDateRange() {
|
|
|
3309
3437
|
else start.setDate(start.getDate() - 7);
|
|
3310
3438
|
return { start, end };
|
|
3311
3439
|
}
|
|
3312
|
-
var import_react10, import_Box10, import_Snackbar2, import_Alert5,
|
|
3440
|
+
var import_react10, import_Box10, import_Snackbar2, import_Alert5, import_CircularProgress5, import_Typography10, import_Paper6, import_Tabs2, import_Tab2, import_react_use3, import_core_plugin_api3, import_plugin_permission_react, PERIOD_LS_KEY2, LiteLLMPage;
|
|
3313
3441
|
var init_LiteLLMPage = __esm({
|
|
3314
3442
|
"src/components/LiteLLMPage.tsx"() {
|
|
3315
3443
|
"use strict";
|
|
@@ -3317,14 +3445,13 @@ var init_LiteLLMPage = __esm({
|
|
|
3317
3445
|
import_Box10 = __toESM(require("@mui/material/Box"));
|
|
3318
3446
|
import_Snackbar2 = __toESM(require("@mui/material/Snackbar"));
|
|
3319
3447
|
import_Alert5 = __toESM(require("@mui/material/Alert"));
|
|
3320
|
-
|
|
3448
|
+
import_CircularProgress5 = __toESM(require("@mui/material/CircularProgress"));
|
|
3321
3449
|
import_Typography10 = __toESM(require("@mui/material/Typography"));
|
|
3322
|
-
|
|
3450
|
+
import_Paper6 = __toESM(require("@mui/material/Paper"));
|
|
3323
3451
|
import_Tabs2 = __toESM(require("@mui/material/Tabs"));
|
|
3324
3452
|
import_Tab2 = __toESM(require("@mui/material/Tab"));
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
import_core_plugin_api2 = require("@backstage/core-plugin-api");
|
|
3453
|
+
import_react_use3 = require("react-use");
|
|
3454
|
+
import_core_plugin_api3 = require("@backstage/core-plugin-api");
|
|
3328
3455
|
import_plugin_permission_react = require("@backstage/plugin-permission-react");
|
|
3329
3456
|
init_DashboardHeader();
|
|
3330
3457
|
init_KeysTable();
|
|
@@ -3338,7 +3465,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3338
3465
|
init_permissions();
|
|
3339
3466
|
PERIOD_LS_KEY2 = "litellm_usage_period";
|
|
3340
3467
|
LiteLLMPage = () => {
|
|
3341
|
-
const api = (0,
|
|
3468
|
+
const api = (0, import_core_plugin_api3.useApi)(liteLlmApiRef);
|
|
3342
3469
|
const [dateRange, setDateRange] = (0, import_react10.useState)(initDateRange);
|
|
3343
3470
|
const [currentPreset, setCurrentPreset] = (0, import_react10.useState)(() => {
|
|
3344
3471
|
try {
|
|
@@ -3353,11 +3480,11 @@ var init_LiteLLMPage = __esm({
|
|
|
3353
3480
|
const [manageTeam, setManageTeam] = (0, import_react10.useState)(null);
|
|
3354
3481
|
const [teamUsageCache, setTeamUsageCache] = (0, import_react10.useState)({});
|
|
3355
3482
|
const [teamUsageLoading, setTeamUsageLoading] = (0, import_react10.useState)({});
|
|
3356
|
-
const { value: userInfo, loading: userLoading, error: userError } = (0,
|
|
3483
|
+
const { value: userInfo, loading: userLoading, error: userError } = (0, import_react_use3.useAsync)(
|
|
3357
3484
|
() => api.getUserInfo(),
|
|
3358
3485
|
[api]
|
|
3359
3486
|
);
|
|
3360
|
-
const { value: keys, loading: keysLoading, retry: refreshKeys } = (0,
|
|
3487
|
+
const { value: keys, loading: keysLoading, retry: refreshKeys } = (0, import_react_use3.useAsyncRetry)(
|
|
3361
3488
|
async () => {
|
|
3362
3489
|
try {
|
|
3363
3490
|
return await api.listKeys();
|
|
@@ -3368,16 +3495,16 @@ var init_LiteLLMPage = __esm({
|
|
|
3368
3495
|
},
|
|
3369
3496
|
[api]
|
|
3370
3497
|
);
|
|
3371
|
-
const { value: allModels, loading: modelsLoading } = (0,
|
|
3498
|
+
const { value: allModels, loading: modelsLoading } = (0, import_react_use3.useAsync)(
|
|
3372
3499
|
() => api.listModels().catch(() => []),
|
|
3373
3500
|
[api]
|
|
3374
3501
|
);
|
|
3375
|
-
const { value: allTeams, loading: teamsLoading, retry: refreshTeams } = (0,
|
|
3502
|
+
const { value: allTeams, loading: teamsLoading, retry: refreshTeams } = (0, import_react_use3.useAsyncRetry)(
|
|
3376
3503
|
() => api.getTeams().catch(() => []),
|
|
3377
3504
|
[api]
|
|
3378
3505
|
);
|
|
3379
|
-
const { value: liteLlmConfig } = (0,
|
|
3380
|
-
const { value: managedTeams } = (0,
|
|
3506
|
+
const { value: liteLlmConfig } = (0, import_react_use3.useAsync)(() => api.getConfig(), [api]);
|
|
3507
|
+
const { value: managedTeams } = (0, import_react_use3.useAsync)(
|
|
3381
3508
|
async () => liteLlmConfig?.teamManagement?.enabled ? api.getManagedTeams().catch(() => []) : [],
|
|
3382
3509
|
[api, liteLlmConfig]
|
|
3383
3510
|
);
|
|
@@ -3388,11 +3515,11 @@ var init_LiteLLMPage = __esm({
|
|
|
3388
3515
|
const { allowed: canManageMcpServers } = (0, import_plugin_permission_react.usePermission)({ permission: litellmTeamMcpManagePermission });
|
|
3389
3516
|
const teamMgmtEnabled = liteLlmConfig?.teamManagement?.enabled ?? false;
|
|
3390
3517
|
const objectPermsEnabled = liteLlmConfig?.teamManagement?.objectPermissionsEnabled ?? false;
|
|
3391
|
-
const { value: vectorStores } = (0,
|
|
3518
|
+
const { value: vectorStores } = (0, import_react_use3.useAsync)(
|
|
3392
3519
|
async () => objectPermsEnabled ? api.getVectorStores().catch(() => []) : [],
|
|
3393
3520
|
[api, objectPermsEnabled]
|
|
3394
3521
|
);
|
|
3395
|
-
const { value: mcpServers } = (0,
|
|
3522
|
+
const { value: mcpServers } = (0, import_react_use3.useAsync)(
|
|
3396
3523
|
async () => objectPermsEnabled ? api.getMcpServers().catch(() => []) : [],
|
|
3397
3524
|
[api, objectPermsEnabled]
|
|
3398
3525
|
);
|
|
@@ -3408,7 +3535,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3408
3535
|
);
|
|
3409
3536
|
return byMembership.length > 0 ? byMembership : allTeams;
|
|
3410
3537
|
}, [allTeams, userInfo]);
|
|
3411
|
-
const { value: usage, loading: usageLoading } = (0,
|
|
3538
|
+
const { value: usage, loading: usageLoading } = (0, import_react_use3.useAsync)(async () => {
|
|
3412
3539
|
const startDate = dateRange.start.toISOString().split("T")[0];
|
|
3413
3540
|
const endDate = dateRange.end.toISOString().split("T")[0];
|
|
3414
3541
|
return api.getUsage(startDate, endDate);
|
|
@@ -3543,12 +3670,12 @@ var init_LiteLLMPage = __esm({
|
|
|
3543
3670
|
);
|
|
3544
3671
|
const isInitialLoading = userLoading && !userInfo;
|
|
3545
3672
|
if (isInitialLoading) {
|
|
3546
|
-
return /* @__PURE__ */ import_react10.default.createElement(import_Box10.default, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: "50vh" }, /* @__PURE__ */ import_react10.default.createElement(
|
|
3673
|
+
return /* @__PURE__ */ import_react10.default.createElement(import_Box10.default, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: "50vh" }, /* @__PURE__ */ import_react10.default.createElement(import_CircularProgress5.default, null));
|
|
3547
3674
|
}
|
|
3548
3675
|
if (userError || !userInfo) {
|
|
3549
3676
|
const isProvisioningEnabled = userError?.body?.provisioning === true;
|
|
3550
3677
|
const hint = userError?.body?.hint;
|
|
3551
|
-
return /* @__PURE__ */ import_react10.default.createElement(import_Box10.default, { p: 3 }, /* @__PURE__ */ import_react10.default.createElement(
|
|
3678
|
+
return /* @__PURE__ */ import_react10.default.createElement(import_Box10.default, { p: 3 }, /* @__PURE__ */ import_react10.default.createElement(import_Paper6.default, { sx: { p: 3 } }, /* @__PURE__ */ import_react10.default.createElement(import_Typography10.default, { variant: "h6", gutterBottom: true }, "Account not provisioned"), /* @__PURE__ */ import_react10.default.createElement(import_Typography10.default, { color: "text.secondary", paragraph: true }, "Your Backstage account is not linked to a LiteLLM user."), hint ? /* @__PURE__ */ import_react10.default.createElement(import_Typography10.default, { variant: "body2", color: "text.secondary" }, hint) : /* @__PURE__ */ import_react10.default.createElement(import_Typography10.default, { 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.")));
|
|
3552
3679
|
}
|
|
3553
3680
|
const pageTabs = /* @__PURE__ */ import_react10.default.createElement(
|
|
3554
3681
|
import_Tabs2.default,
|
|
@@ -3609,14 +3736,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3609
3736
|
for (const t of managedTeams ?? []) teamsById.set(t.team_id, t);
|
|
3610
3737
|
for (const t of teams ?? []) teamsById.set(t.team_id, t);
|
|
3611
3738
|
const visibleTeams = Array.from(teamsById.values());
|
|
3612
|
-
return /* @__PURE__ */ import_react10.default.createElement(
|
|
3613
|
-
import_Button6.default,
|
|
3614
|
-
{
|
|
3615
|
-
variant: "contained",
|
|
3616
|
-
onClick: () => setManageTeam({ mode: "create" })
|
|
3617
|
-
},
|
|
3618
|
-
"Create Team"
|
|
3619
|
-
)), /* @__PURE__ */ import_react10.default.createElement(
|
|
3739
|
+
return /* @__PURE__ */ import_react10.default.createElement(
|
|
3620
3740
|
TeamUsage,
|
|
3621
3741
|
{
|
|
3622
3742
|
teams: visibleTeams,
|
|
@@ -3627,9 +3747,11 @@ var init_LiteLLMPage = __esm({
|
|
|
3627
3747
|
},
|
|
3628
3748
|
getTeamUsageLoading: (teamId) => teamUsageLoading[teamId] ?? false,
|
|
3629
3749
|
canManage: teamMgmtEnabled && canManageTeam,
|
|
3630
|
-
onEditTeam: (t) => setManageTeam({ mode: "edit", team: t })
|
|
3750
|
+
onEditTeam: (t) => setManageTeam({ mode: "edit", team: t }),
|
|
3751
|
+
canCreate: teamMgmtEnabled && canCreateTeam,
|
|
3752
|
+
onCreateTeam: () => setManageTeam({ mode: "create" })
|
|
3631
3753
|
}
|
|
3632
|
-
)
|
|
3754
|
+
);
|
|
3633
3755
|
})(), activeTab === "models" && /* @__PURE__ */ import_react10.default.createElement(
|
|
3634
3756
|
ModelsTable,
|
|
3635
3757
|
{
|
|
@@ -3750,7 +3872,7 @@ module.exports = __toCommonJS(index_exports);
|
|
|
3750
3872
|
|
|
3751
3873
|
// src/plugin.tsx
|
|
3752
3874
|
var import_react11 = __toESM(require("react"));
|
|
3753
|
-
var
|
|
3875
|
+
var import_icons_material8 = require("@mui/icons-material");
|
|
3754
3876
|
var import_frontend_plugin_api = require("@backstage/frontend-plugin-api");
|
|
3755
3877
|
init_api();
|
|
3756
3878
|
var liteLlmApi = import_frontend_plugin_api.ApiBlueprint.make({
|
|
@@ -3764,7 +3886,7 @@ var liteLlmPage = import_frontend_plugin_api.PageBlueprint.make({
|
|
|
3764
3886
|
params: {
|
|
3765
3887
|
path: "/litellm",
|
|
3766
3888
|
title: "LiteLLM",
|
|
3767
|
-
icon: /* @__PURE__ */ import_react11.default.createElement(
|
|
3889
|
+
icon: /* @__PURE__ */ import_react11.default.createElement(import_icons_material8.TrendingUp, null),
|
|
3768
3890
|
loader: async () => {
|
|
3769
3891
|
const { LiteLLMPage: LiteLLMPage2 } = await Promise.resolve().then(() => (init_LiteLLMPage(), LiteLLMPage_exports));
|
|
3770
3892
|
return /* @__PURE__ */ import_react11.default.createElement(LiteLLMPage2, null);
|
|
@@ -3785,17 +3907,17 @@ init_TeamUsage();
|
|
|
3785
3907
|
|
|
3786
3908
|
// src/components/LiteLLMHomeWidget.tsx
|
|
3787
3909
|
var import_react12 = __toESM(require("react"));
|
|
3788
|
-
var
|
|
3910
|
+
var import_Paper7 = __toESM(require("@mui/material/Paper"));
|
|
3789
3911
|
var import_Box11 = __toESM(require("@mui/material/Box"));
|
|
3790
3912
|
var import_Typography11 = __toESM(require("@mui/material/Typography"));
|
|
3791
3913
|
var import_FormControl2 = __toESM(require("@mui/material/FormControl"));
|
|
3792
3914
|
var import_Select2 = __toESM(require("@mui/material/Select"));
|
|
3793
3915
|
var import_MenuItem6 = __toESM(require("@mui/material/MenuItem"));
|
|
3794
3916
|
var import_Grid2 = __toESM(require("@mui/material/Grid"));
|
|
3795
|
-
var
|
|
3917
|
+
var import_CircularProgress6 = __toESM(require("@mui/material/CircularProgress"));
|
|
3796
3918
|
var import_Alert6 = __toESM(require("@mui/material/Alert"));
|
|
3797
3919
|
var import_recharts3 = require("recharts");
|
|
3798
|
-
var
|
|
3920
|
+
var import_core_plugin_api4 = require("@backstage/core-plugin-api");
|
|
3799
3921
|
init_api();
|
|
3800
3922
|
init_format();
|
|
3801
3923
|
function presetToDateRange(preset) {
|
|
@@ -3815,7 +3937,7 @@ var LiteLLMHomeWidget = ({
|
|
|
3815
3937
|
defaultPeriod = "7d",
|
|
3816
3938
|
title = "LiteLLM Usage"
|
|
3817
3939
|
}) => {
|
|
3818
|
-
const api = (0,
|
|
3940
|
+
const api = (0, import_core_plugin_api4.useApi)(liteLlmApiRef);
|
|
3819
3941
|
const [period, setPeriod] = (0, import_react12.useState)(defaultPeriod);
|
|
3820
3942
|
const [loading, setLoading] = (0, import_react12.useState)(true);
|
|
3821
3943
|
const [usageError, setUsageError] = (0, import_react12.useState)(null);
|
|
@@ -3859,7 +3981,7 @@ var LiteLLMHomeWidget = ({
|
|
|
3859
3981
|
spend: d.spend
|
|
3860
3982
|
}));
|
|
3861
3983
|
const hasSparkline = dailyData.length > 0;
|
|
3862
|
-
return /* @__PURE__ */ import_react12.default.createElement(
|
|
3984
|
+
return /* @__PURE__ */ import_react12.default.createElement(import_Paper7.default, { sx: { p: 2 } }, /* @__PURE__ */ import_react12.default.createElement(import_Box11.default, { display: "flex", justifyContent: "space-between", alignItems: "center", mb: 1.5 }, /* @__PURE__ */ import_react12.default.createElement(import_Typography11.default, { variant: "h6" }, title), /* @__PURE__ */ import_react12.default.createElement(import_FormControl2.default, { size: "small", sx: { minWidth: 90 } }, /* @__PURE__ */ import_react12.default.createElement(
|
|
3863
3985
|
import_Select2.default,
|
|
3864
3986
|
{
|
|
3865
3987
|
value: period,
|
|
@@ -3869,7 +3991,7 @@ var LiteLLMHomeWidget = ({
|
|
|
3869
3991
|
/* @__PURE__ */ import_react12.default.createElement(import_MenuItem6.default, { value: "today" }, "Today"),
|
|
3870
3992
|
/* @__PURE__ */ import_react12.default.createElement(import_MenuItem6.default, { value: "7d" }, "7d"),
|
|
3871
3993
|
/* @__PURE__ */ import_react12.default.createElement(import_MenuItem6.default, { value: "30d" }, "30d")
|
|
3872
|
-
))), loading && /* @__PURE__ */ import_react12.default.createElement(import_Box11.default, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: 120 }, /* @__PURE__ */ import_react12.default.createElement(
|
|
3994
|
+
))), loading && /* @__PURE__ */ import_react12.default.createElement(import_Box11.default, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: 120 }, /* @__PURE__ */ import_react12.default.createElement(import_CircularProgress6.default, { size: 32 })), !loading && totalFailure && /* @__PURE__ */ import_react12.default.createElement(import_Alert6.default, { severity: "error", sx: { mt: 1 } }, usageError ?? "Failed to load usage data"), !loading && !totalFailure && /* @__PURE__ */ import_react12.default.createElement(import_react12.default.Fragment, null, partialFailure && /* @__PURE__ */ import_react12.default.createElement(import_Alert6.default, { severity: "warning", sx: { mt: 1, mb: 1 } }, usageError ? `Usage data unavailable (${usageError}).` : "", keysError ? ` Key list unavailable (${keysError}).` : "", " Showing what loaded."), /* @__PURE__ */ import_react12.default.createElement(import_Grid2.default, { container: true, spacing: 2, sx: { mb: hasSparkline ? 1.5 : 0 } }, /* @__PURE__ */ import_react12.default.createElement(import_Grid2.default, { item: true, xs: 6 }, /* @__PURE__ */ import_react12.default.createElement(Kpi, { label: "USD Spent", value: fmtUsd(usage?.total_spend ?? 0) })), /* @__PURE__ */ import_react12.default.createElement(import_Grid2.default, { item: true, xs: 6 }, /* @__PURE__ */ import_react12.default.createElement(Kpi, { label: "Tokens In", value: fmtInt(usage?.prompt_tokens ?? 0) })), /* @__PURE__ */ import_react12.default.createElement(import_Grid2.default, { item: true, xs: 6 }, /* @__PURE__ */ import_react12.default.createElement(Kpi, { label: "Tokens Out", value: fmtInt(usage?.completion_tokens ?? 0) })), /* @__PURE__ */ import_react12.default.createElement(import_Grid2.default, { item: true, xs: 6 }, /* @__PURE__ */ import_react12.default.createElement(Kpi, { label: "Keys", value: fmtInt(keys.length) }))), hasSparkline && /* @__PURE__ */ import_react12.default.createElement(import_Box11.default, { height: 120 }, /* @__PURE__ */ import_react12.default.createElement(import_recharts3.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react12.default.createElement(import_recharts3.AreaChart, { data: dailyData, margin: { top: 4, right: 0, bottom: 0, left: 0 } }, /* @__PURE__ */ import_react12.default.createElement(
|
|
3873
3995
|
import_recharts3.Area,
|
|
3874
3996
|
{
|
|
3875
3997
|
type: "monotone",
|