@acarmisc/backstage-plugin-litellm 0.15.5 → 0.17.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/api.d.ts +27 -1
- package/dist/components/ManageTeamDialog.d.ts +31 -0
- package/dist/components/TeamUsage.d.ts +2 -0
- package/dist/index.cjs.js +825 -252
- package/dist/index.cjs.js.map +4 -4
- package/dist/index.d.ts +2 -0
- package/dist/index.esm.js +794 -221
- package/dist/index.esm.js.map +4 -4
- package/dist/permissions.d.ts +5 -0
- package/dist/permissions.test.d.ts +1 -0
- package/dist/types.d.ts +49 -0
- package/package.json +5 -1
package/dist/index.cjs.js
CHANGED
|
@@ -102,6 +102,24 @@ var init_api = __esm({
|
|
|
102
102
|
await this.throwIfNotOk(response);
|
|
103
103
|
return response.json();
|
|
104
104
|
}
|
|
105
|
+
async patch(path, body) {
|
|
106
|
+
const response = await this.fetchApi.fetch(`${this.basePath}${path}`, {
|
|
107
|
+
method: "PATCH",
|
|
108
|
+
headers: { "Content-Type": "application/json" },
|
|
109
|
+
body: JSON.stringify(body)
|
|
110
|
+
});
|
|
111
|
+
await this.throwIfNotOk(response);
|
|
112
|
+
return response.json();
|
|
113
|
+
}
|
|
114
|
+
async put(path, body) {
|
|
115
|
+
const response = await this.fetchApi.fetch(`${this.basePath}${path}`, {
|
|
116
|
+
method: "PUT",
|
|
117
|
+
headers: { "Content-Type": "application/json" },
|
|
118
|
+
body: JSON.stringify(body)
|
|
119
|
+
});
|
|
120
|
+
await this.throwIfNotOk(response);
|
|
121
|
+
return response.json();
|
|
122
|
+
}
|
|
105
123
|
// User identity is resolved server-side from the Backstage Bearer token.
|
|
106
124
|
// No user_id param needed on the frontend.
|
|
107
125
|
async getUserInfo() {
|
|
@@ -158,6 +176,44 @@ var init_api = __esm({
|
|
|
158
176
|
async getTeams() {
|
|
159
177
|
return this.get("/teams");
|
|
160
178
|
}
|
|
179
|
+
// Teams whose owning_group the caller administers (server-scoped). Distinct
|
|
180
|
+
// from getTeams(), which returns only the teams the caller is a member of.
|
|
181
|
+
async getManagedTeams() {
|
|
182
|
+
return this.get("/teams/managed");
|
|
183
|
+
}
|
|
184
|
+
async addTeamMember(teamId, body) {
|
|
185
|
+
return this.post(
|
|
186
|
+
`/teams/${encodeURIComponent(teamId)}/members`,
|
|
187
|
+
body
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
async removeTeamMember(teamId, userEntityRef) {
|
|
191
|
+
return this.del(
|
|
192
|
+
`/teams/${encodeURIComponent(teamId)}/members?userEntityRef=${encodeURIComponent(
|
|
193
|
+
userEntityRef
|
|
194
|
+
)}`
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
// Vector stores (knowledge bases) the caller may attach — already filtered
|
|
198
|
+
// server-side to the operator's allowlist.
|
|
199
|
+
async getVectorStores() {
|
|
200
|
+
return this.get("/vector-stores");
|
|
201
|
+
}
|
|
202
|
+
async setTeamKnowledgeBases(teamId, vectorStores) {
|
|
203
|
+
return this.put(
|
|
204
|
+
`/teams/${encodeURIComponent(teamId)}/knowledge-bases`,
|
|
205
|
+
{ vector_stores: vectorStores }
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
async getMcpServers() {
|
|
209
|
+
return this.get("/mcp-servers");
|
|
210
|
+
}
|
|
211
|
+
async setTeamMcpServers(teamId, mcpServers) {
|
|
212
|
+
return this.put(
|
|
213
|
+
`/teams/${encodeURIComponent(teamId)}/mcp-servers`,
|
|
214
|
+
{ mcp_servers: mcpServers }
|
|
215
|
+
);
|
|
216
|
+
}
|
|
161
217
|
async getUsage(startDate, endDate) {
|
|
162
218
|
return this.get("/usage", { start_date: startDate, end_date: endDate });
|
|
163
219
|
}
|
|
@@ -170,6 +226,12 @@ var init_api = __esm({
|
|
|
170
226
|
async getConfig() {
|
|
171
227
|
return this.get("/config");
|
|
172
228
|
}
|
|
229
|
+
async createTeam(request) {
|
|
230
|
+
return this.post("/teams", request);
|
|
231
|
+
}
|
|
232
|
+
async updateTeam(teamId, request) {
|
|
233
|
+
return this.patch(`/teams/${encodeURIComponent(teamId)}`, request);
|
|
234
|
+
}
|
|
173
235
|
};
|
|
174
236
|
}
|
|
175
237
|
});
|
|
@@ -1879,22 +1941,377 @@ var init_GenerateKeyDialog = __esm({
|
|
|
1879
1941
|
}
|
|
1880
1942
|
});
|
|
1881
1943
|
|
|
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, import_List, import_ListItem, import_ListItemText, import_icons_material4, BUDGET_DURATION_OPTIONS, DEFAULT_BUDGET_DURATION, FALLBACK_BUDGET_CEILING, ManageTeamDialog;
|
|
1946
|
+
var init_ManageTeamDialog = __esm({
|
|
1947
|
+
"src/components/ManageTeamDialog.tsx"() {
|
|
1948
|
+
"use strict";
|
|
1949
|
+
import_react5 = __toESM(require("react"));
|
|
1950
|
+
import_Dialog3 = __toESM(require("@mui/material/Dialog"));
|
|
1951
|
+
import_DialogTitle3 = __toESM(require("@mui/material/DialogTitle"));
|
|
1952
|
+
import_DialogContent3 = __toESM(require("@mui/material/DialogContent"));
|
|
1953
|
+
import_DialogActions3 = __toESM(require("@mui/material/DialogActions"));
|
|
1954
|
+
import_TextField3 = __toESM(require("@mui/material/TextField"));
|
|
1955
|
+
import_Button4 = __toESM(require("@mui/material/Button"));
|
|
1956
|
+
import_Alert2 = __toESM(require("@mui/material/Alert"));
|
|
1957
|
+
import_Checkbox2 = __toESM(require("@mui/material/Checkbox"));
|
|
1958
|
+
import_FormControlLabel2 = __toESM(require("@mui/material/FormControlLabel"));
|
|
1959
|
+
import_Autocomplete3 = __toESM(require("@mui/material/Autocomplete"));
|
|
1960
|
+
import_MenuItem2 = __toESM(require("@mui/material/MenuItem"));
|
|
1961
|
+
import_Box5 = __toESM(require("@mui/material/Box"));
|
|
1962
|
+
import_Divider2 = __toESM(require("@mui/material/Divider"));
|
|
1963
|
+
import_Typography5 = __toESM(require("@mui/material/Typography"));
|
|
1964
|
+
import_IconButton3 = __toESM(require("@mui/material/IconButton"));
|
|
1965
|
+
import_List = __toESM(require("@mui/material/List"));
|
|
1966
|
+
import_ListItem = __toESM(require("@mui/material/ListItem"));
|
|
1967
|
+
import_ListItemText = __toESM(require("@mui/material/ListItemText"));
|
|
1968
|
+
import_icons_material4 = require("@mui/icons-material");
|
|
1969
|
+
BUDGET_DURATION_OPTIONS = [
|
|
1970
|
+
{ value: "1d", label: "Daily (1d)" },
|
|
1971
|
+
{ value: "7d", label: "Weekly (7d)" },
|
|
1972
|
+
{ value: "30d", label: "Monthly (30d)" },
|
|
1973
|
+
{ value: "90d", label: "Quarterly (90d)" },
|
|
1974
|
+
{ value: "365d", label: "Yearly (365d)" }
|
|
1975
|
+
];
|
|
1976
|
+
DEFAULT_BUDGET_DURATION = "30d";
|
|
1977
|
+
FALLBACK_BUDGET_CEILING = 1e3;
|
|
1978
|
+
ManageTeamDialog = ({
|
|
1979
|
+
open,
|
|
1980
|
+
onClose,
|
|
1981
|
+
mode,
|
|
1982
|
+
team,
|
|
1983
|
+
allModels,
|
|
1984
|
+
config,
|
|
1985
|
+
onSubmit,
|
|
1986
|
+
canManageMembers,
|
|
1987
|
+
onAddMember,
|
|
1988
|
+
onRemoveMember,
|
|
1989
|
+
canManageKnowledgeBases,
|
|
1990
|
+
vectorStores,
|
|
1991
|
+
onSaveKnowledgeBases,
|
|
1992
|
+
canManageMcpServers,
|
|
1993
|
+
mcpServers,
|
|
1994
|
+
onSaveMcpServers
|
|
1995
|
+
}) => {
|
|
1996
|
+
const [alias, setAlias] = (0, import_react5.useState)("");
|
|
1997
|
+
const [models, setModels] = (0, import_react5.useState)([]);
|
|
1998
|
+
const [maxBudget, setMaxBudget] = (0, import_react5.useState)("");
|
|
1999
|
+
const [unlimited, setUnlimited] = (0, import_react5.useState)(false);
|
|
2000
|
+
const [budgetDuration, setBudgetDuration] = (0, import_react5.useState)("");
|
|
2001
|
+
const [submitting, setSubmitting] = (0, import_react5.useState)(false);
|
|
2002
|
+
const [error, setError] = (0, import_react5.useState)(null);
|
|
2003
|
+
const [memberRef, setMemberRef] = (0, import_react5.useState)("");
|
|
2004
|
+
const [memberBudget, setMemberBudget] = (0, import_react5.useState)("");
|
|
2005
|
+
const [memberBusy, setMemberBusy] = (0, import_react5.useState)(false);
|
|
2006
|
+
const [memberError, setMemberError] = (0, import_react5.useState)(null);
|
|
2007
|
+
const [kbIds, setKbIds] = (0, import_react5.useState)([]);
|
|
2008
|
+
const [kbBusy, setKbBusy] = (0, import_react5.useState)(false);
|
|
2009
|
+
const [kbError, setKbError] = (0, import_react5.useState)(null);
|
|
2010
|
+
const [mcpIds, setMcpIds] = (0, import_react5.useState)([]);
|
|
2011
|
+
const [mcpBusy, setMcpBusy] = (0, import_react5.useState)(false);
|
|
2012
|
+
const [mcpError, setMcpError] = (0, import_react5.useState)(null);
|
|
2013
|
+
const allowUnlimitedBudget = config?.teamManagement?.allowUnlimitedBudget ?? false;
|
|
2014
|
+
const maxBudgetCeiling = config?.teamManagement?.maxBudgetCeiling ?? FALLBACK_BUDGET_CEILING;
|
|
2015
|
+
(0, import_react5.useEffect)(() => {
|
|
2016
|
+
if (open) {
|
|
2017
|
+
if (mode === "edit" && team) {
|
|
2018
|
+
setAlias(team.team_alias ?? "");
|
|
2019
|
+
setModels(team.models ?? []);
|
|
2020
|
+
setMaxBudget(
|
|
2021
|
+
team.max_budget ? String(team.max_budget) : String(maxBudgetCeiling)
|
|
2022
|
+
);
|
|
2023
|
+
setUnlimited(allowUnlimitedBudget && !team.max_budget);
|
|
2024
|
+
setBudgetDuration(team.budget_duration ?? DEFAULT_BUDGET_DURATION);
|
|
2025
|
+
} else {
|
|
2026
|
+
setAlias("");
|
|
2027
|
+
setModels([]);
|
|
2028
|
+
setMaxBudget(String(maxBudgetCeiling));
|
|
2029
|
+
setUnlimited(allowUnlimitedBudget);
|
|
2030
|
+
setBudgetDuration(DEFAULT_BUDGET_DURATION);
|
|
2031
|
+
}
|
|
2032
|
+
setError(null);
|
|
2033
|
+
setMemberRef("");
|
|
2034
|
+
setMemberBudget("");
|
|
2035
|
+
setMemberError(null);
|
|
2036
|
+
setKbIds(team?.object_permission?.vector_stores ?? []);
|
|
2037
|
+
setKbError(null);
|
|
2038
|
+
setMcpIds(team?.object_permission?.mcp_servers ?? []);
|
|
2039
|
+
setMcpError(null);
|
|
2040
|
+
}
|
|
2041
|
+
}, [open, mode, team, allowUnlimitedBudget, maxBudgetCeiling]);
|
|
2042
|
+
const handleSubmit = async () => {
|
|
2043
|
+
setError(null);
|
|
2044
|
+
if (!alias.trim()) {
|
|
2045
|
+
setError("Team alias is required");
|
|
2046
|
+
return;
|
|
2047
|
+
}
|
|
2048
|
+
if (models.length === 0) {
|
|
2049
|
+
setError("At least one model is required");
|
|
2050
|
+
return;
|
|
2051
|
+
}
|
|
2052
|
+
if (!unlimited) {
|
|
2053
|
+
if (!maxBudget) {
|
|
2054
|
+
setError("Budget is required when unlimited budgets are disabled");
|
|
2055
|
+
return;
|
|
2056
|
+
}
|
|
2057
|
+
const budget = parseFloat(maxBudget);
|
|
2058
|
+
if (isNaN(budget) || budget <= 0) {
|
|
2059
|
+
setError("Budget must be a positive number");
|
|
2060
|
+
return;
|
|
2061
|
+
}
|
|
2062
|
+
if (budget > maxBudgetCeiling) {
|
|
2063
|
+
setError(`Budget cannot exceed $${maxBudgetCeiling}`);
|
|
2064
|
+
return;
|
|
2065
|
+
}
|
|
2066
|
+
}
|
|
2067
|
+
try {
|
|
2068
|
+
setSubmitting(true);
|
|
2069
|
+
let payload;
|
|
2070
|
+
if (mode === "create") {
|
|
2071
|
+
payload = {
|
|
2072
|
+
team_alias: alias.trim(),
|
|
2073
|
+
models,
|
|
2074
|
+
...unlimited ? { max_budget: null } : { max_budget: parseFloat(maxBudget) },
|
|
2075
|
+
...budgetDuration && { budget_duration: budgetDuration }
|
|
2076
|
+
};
|
|
2077
|
+
} else {
|
|
2078
|
+
payload = {
|
|
2079
|
+
team_alias: alias.trim(),
|
|
2080
|
+
models,
|
|
2081
|
+
...unlimited ? { max_budget: null } : { max_budget: parseFloat(maxBudget) },
|
|
2082
|
+
...budgetDuration && { budget_duration: budgetDuration }
|
|
2083
|
+
};
|
|
2084
|
+
}
|
|
2085
|
+
await onSubmit(payload);
|
|
2086
|
+
onClose();
|
|
2087
|
+
} catch (err) {
|
|
2088
|
+
setError(err instanceof Error ? err.message : "An error occurred");
|
|
2089
|
+
} finally {
|
|
2090
|
+
setSubmitting(false);
|
|
2091
|
+
}
|
|
2092
|
+
};
|
|
2093
|
+
const handleAddMember = async () => {
|
|
2094
|
+
if (!onAddMember || !memberRef.trim()) return;
|
|
2095
|
+
setMemberError(null);
|
|
2096
|
+
const budget = memberBudget ? parseFloat(memberBudget) : void 0;
|
|
2097
|
+
if (budget !== void 0 && (isNaN(budget) || budget <= 0)) {
|
|
2098
|
+
setMemberError("Max budget in team must be a positive number");
|
|
2099
|
+
return;
|
|
2100
|
+
}
|
|
2101
|
+
try {
|
|
2102
|
+
setMemberBusy(true);
|
|
2103
|
+
await onAddMember(memberRef.trim(), budget);
|
|
2104
|
+
setMemberRef("");
|
|
2105
|
+
setMemberBudget("");
|
|
2106
|
+
} catch (err) {
|
|
2107
|
+
setMemberError(err instanceof Error ? err.message : "Failed to add member");
|
|
2108
|
+
} finally {
|
|
2109
|
+
setMemberBusy(false);
|
|
2110
|
+
}
|
|
2111
|
+
};
|
|
2112
|
+
const handleRemoveMember = async (userId) => {
|
|
2113
|
+
if (!onRemoveMember) return;
|
|
2114
|
+
setMemberError(null);
|
|
2115
|
+
try {
|
|
2116
|
+
setMemberBusy(true);
|
|
2117
|
+
await onRemoveMember(userId);
|
|
2118
|
+
} catch (err) {
|
|
2119
|
+
setMemberError(err instanceof Error ? err.message : "Failed to remove member");
|
|
2120
|
+
} finally {
|
|
2121
|
+
setMemberBusy(false);
|
|
2122
|
+
}
|
|
2123
|
+
};
|
|
2124
|
+
const handleSaveKnowledgeBases = async () => {
|
|
2125
|
+
if (!onSaveKnowledgeBases) return;
|
|
2126
|
+
setKbError(null);
|
|
2127
|
+
try {
|
|
2128
|
+
setKbBusy(true);
|
|
2129
|
+
await onSaveKnowledgeBases(kbIds);
|
|
2130
|
+
} catch (err) {
|
|
2131
|
+
setKbError(err instanceof Error ? err.message : "Failed to save knowledge bases");
|
|
2132
|
+
} finally {
|
|
2133
|
+
setKbBusy(false);
|
|
2134
|
+
}
|
|
2135
|
+
};
|
|
2136
|
+
const handleSaveMcpServers = async () => {
|
|
2137
|
+
if (!onSaveMcpServers) return;
|
|
2138
|
+
setMcpError(null);
|
|
2139
|
+
try {
|
|
2140
|
+
setMcpBusy(true);
|
|
2141
|
+
await onSaveMcpServers(mcpIds);
|
|
2142
|
+
} catch (err) {
|
|
2143
|
+
setMcpError(err instanceof Error ? err.message : "Failed to save MCP servers");
|
|
2144
|
+
} finally {
|
|
2145
|
+
setMcpBusy(false);
|
|
2146
|
+
}
|
|
2147
|
+
};
|
|
2148
|
+
const modelNames = allModels.map((m) => m.model_name);
|
|
2149
|
+
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
|
+
const showKnowledgeBases = mode === "edit" && !!canManageKnowledgeBases;
|
|
2153
|
+
const kbOptions = (vectorStores ?? []).map((v) => v.id);
|
|
2154
|
+
const kbLabel = (id) => (vectorStores ?? []).find((v) => v.id === id)?.name ?? id;
|
|
2155
|
+
const showMcpServers = mode === "edit" && !!canManageMcpServers;
|
|
2156
|
+
const mcpOptions = (mcpServers ?? []).map((s) => s.id);
|
|
2157
|
+
const mcpLabel = (id) => (mcpServers ?? []).find((s) => s.id === id)?.name ?? id;
|
|
2158
|
+
return /* @__PURE__ */ import_react5.default.createElement(import_Dialog3.default, { open, onClose, maxWidth: "sm", fullWidth: true }, /* @__PURE__ */ import_react5.default.createElement(import_DialogTitle3.default, null, mode === "create" ? "Create Team" : "Edit Team"), /* @__PURE__ */ import_react5.default.createElement(import_DialogContent3.default, { sx: { pt: 2, display: "flex", flexDirection: "column", gap: 2 } }, error && /* @__PURE__ */ import_react5.default.createElement(import_Alert2.default, { severity: "error" }, error), /* @__PURE__ */ import_react5.default.createElement(
|
|
2159
|
+
import_TextField3.default,
|
|
2160
|
+
{
|
|
2161
|
+
label: "Team Alias",
|
|
2162
|
+
value: alias,
|
|
2163
|
+
onChange: (e) => setAlias(e.target.value),
|
|
2164
|
+
disabled: submitting,
|
|
2165
|
+
fullWidth: true
|
|
2166
|
+
}
|
|
2167
|
+
), /* @__PURE__ */ import_react5.default.createElement(
|
|
2168
|
+
import_Autocomplete3.default,
|
|
2169
|
+
{
|
|
2170
|
+
multiple: true,
|
|
2171
|
+
options: modelNames,
|
|
2172
|
+
value: models,
|
|
2173
|
+
onChange: (_, newValue) => setModels(newValue),
|
|
2174
|
+
disabled: submitting,
|
|
2175
|
+
renderInput: (params) => /* @__PURE__ */ import_react5.default.createElement(import_TextField3.default, { ...params, label: "Models" })
|
|
2176
|
+
}
|
|
2177
|
+
), /* @__PURE__ */ import_react5.default.createElement(
|
|
2178
|
+
import_TextField3.default,
|
|
2179
|
+
{
|
|
2180
|
+
label: "Max Budget ($)",
|
|
2181
|
+
type: "number",
|
|
2182
|
+
value: maxBudget,
|
|
2183
|
+
onChange: (e) => setMaxBudget(e.target.value),
|
|
2184
|
+
disabled: submitting || unlimited,
|
|
2185
|
+
inputProps: { min: 0, max: maxBudgetCeiling },
|
|
2186
|
+
helperText: `Maximum: $${maxBudgetCeiling}`,
|
|
2187
|
+
fullWidth: true
|
|
2188
|
+
}
|
|
2189
|
+
), allowUnlimitedBudget && /* @__PURE__ */ import_react5.default.createElement(
|
|
2190
|
+
import_FormControlLabel2.default,
|
|
2191
|
+
{
|
|
2192
|
+
control: /* @__PURE__ */ import_react5.default.createElement(import_Checkbox2.default, { checked: unlimited, onChange: (e) => setUnlimited(e.target.checked), disabled: submitting }),
|
|
2193
|
+
label: "Unlimited Budget"
|
|
2194
|
+
}
|
|
2195
|
+
), /* @__PURE__ */ import_react5.default.createElement(
|
|
2196
|
+
import_TextField3.default,
|
|
2197
|
+
{
|
|
2198
|
+
select: true,
|
|
2199
|
+
label: "Budget Duration",
|
|
2200
|
+
value: budgetDuration,
|
|
2201
|
+
onChange: (e) => setBudgetDuration(e.target.value),
|
|
2202
|
+
disabled: submitting || unlimited,
|
|
2203
|
+
helperText: "Spend-reset period for the team budget",
|
|
2204
|
+
fullWidth: true
|
|
2205
|
+
},
|
|
2206
|
+
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(import_List.default, { dense: true, disablePadding: true }, members.map((m) => /* @__PURE__ */ import_react5.default.createElement(
|
|
2208
|
+
import_ListItem.default,
|
|
2209
|
+
{
|
|
2210
|
+
key: m.user_id,
|
|
2211
|
+
disableGutters: true,
|
|
2212
|
+
secondaryAction: /* @__PURE__ */ import_react5.default.createElement(
|
|
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
|
+
)
|
|
2223
|
+
},
|
|
2224
|
+
/* @__PURE__ */ import_react5.default.createElement(import_ListItemText.default, { primary: m.user_id, secondary: m.role })
|
|
2225
|
+
))), /* @__PURE__ */ import_react5.default.createElement(import_Box5.default, { sx: { display: "flex", gap: 1, alignItems: "flex-start", mt: 1 } }, /* @__PURE__ */ import_react5.default.createElement(
|
|
2226
|
+
import_TextField3.default,
|
|
2227
|
+
{
|
|
2228
|
+
label: "Backstage user ref",
|
|
2229
|
+
placeholder: "user:default/alice",
|
|
2230
|
+
value: memberRef,
|
|
2231
|
+
onChange: (e) => setMemberRef(e.target.value),
|
|
2232
|
+
disabled: memberBusy,
|
|
2233
|
+
size: "small",
|
|
2234
|
+
sx: { flex: 1 }
|
|
2235
|
+
}
|
|
2236
|
+
), /* @__PURE__ */ import_react5.default.createElement(
|
|
2237
|
+
import_TextField3.default,
|
|
2238
|
+
{
|
|
2239
|
+
label: "Max budget in team ($)",
|
|
2240
|
+
type: "number",
|
|
2241
|
+
value: memberBudget,
|
|
2242
|
+
onChange: (e) => setMemberBudget(e.target.value),
|
|
2243
|
+
disabled: memberBusy,
|
|
2244
|
+
size: "small",
|
|
2245
|
+
sx: { width: 160 }
|
|
2246
|
+
}
|
|
2247
|
+
), /* @__PURE__ */ import_react5.default.createElement(
|
|
2248
|
+
import_Button4.default,
|
|
2249
|
+
{
|
|
2250
|
+
onClick: handleAddMember,
|
|
2251
|
+
disabled: memberBusy || !memberRef.trim(),
|
|
2252
|
+
variant: "outlined",
|
|
2253
|
+
sx: { mt: 0.5 }
|
|
2254
|
+
},
|
|
2255
|
+
"Add"
|
|
2256
|
+
))), showKnowledgeBases && /* @__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 } }, "Knowledge Bases"), /* @__PURE__ */ import_react5.default.createElement(import_Typography5.default, { variant: "body2", color: "text.secondary", sx: { mb: 1 } }, "Attaching a knowledge base exposes its documents to every key in this team."), kbError && /* @__PURE__ */ import_react5.default.createElement(import_Alert2.default, { severity: "error", sx: { mb: 1 }, onClose: () => setKbError(null) }, kbError), /* @__PURE__ */ import_react5.default.createElement(
|
|
2257
|
+
import_Autocomplete3.default,
|
|
2258
|
+
{
|
|
2259
|
+
multiple: true,
|
|
2260
|
+
options: kbOptions,
|
|
2261
|
+
value: kbIds,
|
|
2262
|
+
getOptionLabel: kbLabel,
|
|
2263
|
+
onChange: (_, v) => setKbIds(v),
|
|
2264
|
+
disabled: kbBusy,
|
|
2265
|
+
renderInput: (params) => /* @__PURE__ */ import_react5.default.createElement(import_TextField3.default, { ...params, label: "Attached knowledge bases" })
|
|
2266
|
+
}
|
|
2267
|
+
), /* @__PURE__ */ import_react5.default.createElement(import_Box5.default, { sx: { display: "flex", justifyContent: "flex-end", mt: 1 } }, /* @__PURE__ */ import_react5.default.createElement(
|
|
2268
|
+
import_Button4.default,
|
|
2269
|
+
{
|
|
2270
|
+
onClick: handleSaveKnowledgeBases,
|
|
2271
|
+
disabled: kbBusy,
|
|
2272
|
+
variant: "outlined"
|
|
2273
|
+
},
|
|
2274
|
+
kbBusy ? "Saving\u2026" : "Save knowledge bases"
|
|
2275
|
+
))), showMcpServers && /* @__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 } }, "MCP Servers"), /* @__PURE__ */ import_react5.default.createElement(import_Typography5.default, { variant: "body2", color: "text.secondary", sx: { mb: 1 } }, "Attaching an MCP server lets every key in this team invoke that server's tools through the model \u2014 including any side effects those tools have."), mcpError && /* @__PURE__ */ import_react5.default.createElement(import_Alert2.default, { severity: "error", sx: { mb: 1 }, onClose: () => setMcpError(null) }, mcpError), /* @__PURE__ */ import_react5.default.createElement(
|
|
2276
|
+
import_Autocomplete3.default,
|
|
2277
|
+
{
|
|
2278
|
+
multiple: true,
|
|
2279
|
+
options: mcpOptions,
|
|
2280
|
+
value: mcpIds,
|
|
2281
|
+
getOptionLabel: mcpLabel,
|
|
2282
|
+
onChange: (_, v) => setMcpIds(v),
|
|
2283
|
+
disabled: mcpBusy,
|
|
2284
|
+
renderInput: (params) => /* @__PURE__ */ import_react5.default.createElement(import_TextField3.default, { ...params, label: "Attached MCP servers" })
|
|
2285
|
+
}
|
|
2286
|
+
), /* @__PURE__ */ import_react5.default.createElement(import_Box5.default, { sx: { display: "flex", justifyContent: "flex-end", mt: 1 } }, /* @__PURE__ */ import_react5.default.createElement(
|
|
2287
|
+
import_Button4.default,
|
|
2288
|
+
{
|
|
2289
|
+
onClick: handleSaveMcpServers,
|
|
2290
|
+
disabled: mcpBusy,
|
|
2291
|
+
variant: "outlined"
|
|
2292
|
+
},
|
|
2293
|
+
mcpBusy ? "Saving\u2026" : "Save MCP servers"
|
|
2294
|
+
)))), /* @__PURE__ */ import_react5.default.createElement(import_DialogActions3.default, null, /* @__PURE__ */ import_react5.default.createElement(import_Button4.default, { onClick: onClose, disabled: submitting }, "Cancel"), /* @__PURE__ */ import_react5.default.createElement(import_Button4.default, { onClick: handleSubmit, disabled: submitting, variant: "contained" }, submitting ? "Saving..." : "Save")));
|
|
2295
|
+
};
|
|
2296
|
+
}
|
|
2297
|
+
});
|
|
2298
|
+
|
|
1882
2299
|
// src/components/UsageStats.tsx
|
|
1883
2300
|
function rateTone(rate) {
|
|
1884
2301
|
if (rate >= 0.99) return "success";
|
|
1885
2302
|
if (rate >= 0.9) return "warning";
|
|
1886
2303
|
return "danger";
|
|
1887
2304
|
}
|
|
1888
|
-
var
|
|
2305
|
+
var import_react6, import_Paper3, import_Box6, import_Typography6, import_TextField4, import_MenuItem3, import_Grid, import_Table2, import_TableBody2, import_TableCell2, import_TableContainer2, import_TableHead2, import_TableRow2, import_Skeleton3, import_recharts, TOP_N_MODELS, PERIOD_LS_KEY, PRESET_LABELS, fmtPct, ChartSkeleton, ChartOrFallback, SuccessRateCell, UsageStats;
|
|
1889
2306
|
var init_UsageStats = __esm({
|
|
1890
2307
|
"src/components/UsageStats.tsx"() {
|
|
1891
2308
|
"use strict";
|
|
1892
|
-
|
|
2309
|
+
import_react6 = __toESM(require("react"));
|
|
1893
2310
|
import_Paper3 = __toESM(require("@mui/material/Paper"));
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
2311
|
+
import_Box6 = __toESM(require("@mui/material/Box"));
|
|
2312
|
+
import_Typography6 = __toESM(require("@mui/material/Typography"));
|
|
2313
|
+
import_TextField4 = __toESM(require("@mui/material/TextField"));
|
|
2314
|
+
import_MenuItem3 = __toESM(require("@mui/material/MenuItem"));
|
|
1898
2315
|
import_Grid = __toESM(require("@mui/material/Grid"));
|
|
1899
2316
|
import_Table2 = __toESM(require("@mui/material/Table"));
|
|
1900
2317
|
import_TableBody2 = __toESM(require("@mui/material/TableBody"));
|
|
@@ -1915,17 +2332,17 @@ var init_UsageStats = __esm({
|
|
|
1915
2332
|
"30d": "Last 30 days"
|
|
1916
2333
|
};
|
|
1917
2334
|
fmtPct = (n) => `${(n * 100).toFixed(1)}%`;
|
|
1918
|
-
ChartSkeleton = ({ height = 240 }) => /* @__PURE__ */
|
|
2335
|
+
ChartSkeleton = ({ height = 240 }) => /* @__PURE__ */ import_react6.default.createElement(import_Skeleton3.default, { variant: "rounded", height });
|
|
1919
2336
|
ChartOrFallback = ({ loading, empty, height = 240, children }) => {
|
|
1920
|
-
if (loading) return /* @__PURE__ */
|
|
1921
|
-
if (empty) return /* @__PURE__ */
|
|
1922
|
-
return /* @__PURE__ */
|
|
2337
|
+
if (loading) return /* @__PURE__ */ import_react6.default.createElement(ChartSkeleton, { height });
|
|
2338
|
+
if (empty) return /* @__PURE__ */ import_react6.default.createElement(EmptyState, { message: "No data for this period", height });
|
|
2339
|
+
return /* @__PURE__ */ import_react6.default.createElement(import_react6.default.Fragment, null, children);
|
|
1923
2340
|
};
|
|
1924
2341
|
SuccessRateCell = ({ rate, requests }) => {
|
|
1925
|
-
if (requests === 0) return /* @__PURE__ */
|
|
2342
|
+
if (requests === 0) return /* @__PURE__ */ import_react6.default.createElement(import_Typography6.default, { variant: "body2", color: "text.secondary" }, "\u2014");
|
|
1926
2343
|
const tone = rateTone(rate);
|
|
1927
|
-
return /* @__PURE__ */
|
|
1928
|
-
|
|
2344
|
+
return /* @__PURE__ */ import_react6.default.createElement(import_Box6.default, { display: "flex", alignItems: "center", gap: 1 }, /* @__PURE__ */ import_react6.default.createElement(import_Box6.default, { width: 72, flexShrink: 0 }, /* @__PURE__ */ import_react6.default.createElement(Meter, { value: rate * 100, tone, height: 4 })), /* @__PURE__ */ import_react6.default.createElement(
|
|
2345
|
+
import_Typography6.default,
|
|
1929
2346
|
{
|
|
1930
2347
|
variant: "caption",
|
|
1931
2348
|
sx: { fontVariantNumeric: "tabular-nums", minWidth: 44 }
|
|
@@ -1943,8 +2360,8 @@ var init_UsageStats = __esm({
|
|
|
1943
2360
|
userInfo
|
|
1944
2361
|
}) => {
|
|
1945
2362
|
const chart = useChartTheme();
|
|
1946
|
-
const [selectedModel, setSelectedModel] = (0,
|
|
1947
|
-
const [tab, setTab] = (0,
|
|
2363
|
+
const [selectedModel, setSelectedModel] = (0, import_react6.useState)("all");
|
|
2364
|
+
const [tab, setTab] = (0, import_react6.useState)("costs");
|
|
1948
2365
|
const handlePresetChange = (preset) => {
|
|
1949
2366
|
const end = /* @__PURE__ */ new Date();
|
|
1950
2367
|
const start = /* @__PURE__ */ new Date();
|
|
@@ -1958,7 +2375,7 @@ var init_UsageStats = __esm({
|
|
|
1958
2375
|
}
|
|
1959
2376
|
onDateRangeChange({ start, end }, preset);
|
|
1960
2377
|
};
|
|
1961
|
-
const dailyData = (0,
|
|
2378
|
+
const dailyData = (0, import_react6.useMemo)(
|
|
1962
2379
|
() => (usage?.daily_usage ?? []).map((d) => ({
|
|
1963
2380
|
date: d.date,
|
|
1964
2381
|
spend: d.spend,
|
|
@@ -1971,21 +2388,21 @@ var init_UsageStats = __esm({
|
|
|
1971
2388
|
})),
|
|
1972
2389
|
[usage]
|
|
1973
2390
|
);
|
|
1974
|
-
const cumulativeData = (0,
|
|
2391
|
+
const cumulativeData = (0, import_react6.useMemo)(() => {
|
|
1975
2392
|
let cum = 0;
|
|
1976
2393
|
return dailyData.map((d) => {
|
|
1977
2394
|
cum += d.spend;
|
|
1978
2395
|
return { date: d.date, cumulative: cum };
|
|
1979
2396
|
});
|
|
1980
2397
|
}, [dailyData]);
|
|
1981
|
-
const successRateData = (0,
|
|
2398
|
+
const successRateData = (0, import_react6.useMemo)(
|
|
1982
2399
|
() => dailyData.filter((d) => d.apiRequests > 0).map((d) => ({
|
|
1983
2400
|
date: d.date,
|
|
1984
2401
|
successRate: parseFloat((d.successfulRequests / d.apiRequests * 100).toFixed(1))
|
|
1985
2402
|
})),
|
|
1986
2403
|
[dailyData]
|
|
1987
2404
|
);
|
|
1988
|
-
const { modelSpendByDate, topModels: topSpendModels } = (0,
|
|
2405
|
+
const { modelSpendByDate, topModels: topSpendModels } = (0, import_react6.useMemo)(() => {
|
|
1989
2406
|
const rows = usage?.daily_by_model ?? [];
|
|
1990
2407
|
const modelTotals = {};
|
|
1991
2408
|
for (const r of rows) modelTotals[r.model] = (modelTotals[r.model] ?? 0) + r.spend;
|
|
@@ -2000,7 +2417,7 @@ var init_UsageStats = __esm({
|
|
|
2000
2417
|
const hasOther = sorted.some((r) => r.Other > 0);
|
|
2001
2418
|
return { modelSpendByDate: sorted, topModels: hasOther ? [...top, "Other"] : top };
|
|
2002
2419
|
}, [usage]);
|
|
2003
|
-
const modelRows = (0,
|
|
2420
|
+
const modelRows = (0, import_react6.useMemo)(() => {
|
|
2004
2421
|
const entries = Object.entries(usage?.usage_by_model ?? {}).map(([model, d]) => ({
|
|
2005
2422
|
model,
|
|
2006
2423
|
spend: d.total_spend,
|
|
@@ -2014,11 +2431,11 @@ var init_UsageStats = __esm({
|
|
|
2014
2431
|
}));
|
|
2015
2432
|
return selectedModel === "all" ? entries : entries.filter((e) => e.model === selectedModel);
|
|
2016
2433
|
}, [usage, selectedModel]);
|
|
2017
|
-
const topModelSpendBars = (0,
|
|
2434
|
+
const topModelSpendBars = (0, import_react6.useMemo)(
|
|
2018
2435
|
() => [...modelRows].sort((a, b) => b.spend - a.spend).slice(0, 10),
|
|
2019
2436
|
[modelRows]
|
|
2020
2437
|
);
|
|
2021
|
-
const keyRows = (0,
|
|
2438
|
+
const keyRows = (0, import_react6.useMemo)(
|
|
2022
2439
|
() => Object.entries(usage?.usage_by_key ?? {}).map(([keyHash, d]) => ({
|
|
2023
2440
|
keyHash,
|
|
2024
2441
|
keyAlias: d.key_alias ?? keyHash.slice(0, 8),
|
|
@@ -2035,7 +2452,7 @@ var init_UsageStats = __esm({
|
|
|
2035
2452
|
})),
|
|
2036
2453
|
[usage]
|
|
2037
2454
|
);
|
|
2038
|
-
const topKeySpendBars = (0,
|
|
2455
|
+
const topKeySpendBars = (0, import_react6.useMemo)(
|
|
2039
2456
|
() => [...keyRows].sort((a, b) => b.spend - a.spend).slice(0, 10),
|
|
2040
2457
|
[keyRows]
|
|
2041
2458
|
);
|
|
@@ -2073,24 +2490,24 @@ var init_UsageStats = __esm({
|
|
|
2073
2490
|
];
|
|
2074
2491
|
const renderKeyRows = () => {
|
|
2075
2492
|
if (loading) {
|
|
2076
|
-
return /* @__PURE__ */
|
|
2493
|
+
return /* @__PURE__ */ import_react6.default.createElement(import_TableRow2.default, null, /* @__PURE__ */ import_react6.default.createElement(import_TableCell2.default, { colSpan: 7, sx: { py: 3 } }, /* @__PURE__ */ import_react6.default.createElement(import_Skeleton3.default, { variant: "rounded", height: 80 })));
|
|
2077
2494
|
}
|
|
2078
2495
|
if (keyRows.length === 0) {
|
|
2079
|
-
return /* @__PURE__ */
|
|
2496
|
+
return /* @__PURE__ */ import_react6.default.createElement(import_TableRow2.default, null, /* @__PURE__ */ import_react6.default.createElement(import_TableCell2.default, { colSpan: 7 }, /* @__PURE__ */ import_react6.default.createElement(EmptyState, { message: "No key activity in this period" })));
|
|
2080
2497
|
}
|
|
2081
|
-
return [...keyRows].sort((a, b) => b.spend - a.spend || b.apiRequests - a.apiRequests).map((r) => /* @__PURE__ */
|
|
2498
|
+
return [...keyRows].sort((a, b) => b.spend - a.spend || b.apiRequests - a.apiRequests).map((r) => /* @__PURE__ */ import_react6.default.createElement(import_TableRow2.default, { key: r.keyHash }, /* @__PURE__ */ import_react6.default.createElement(import_TableCell2.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_TableCell2.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_TableCell2.default, { align: "right" }, fmtUsd(r.spend)), /* @__PURE__ */ import_react6.default.createElement(import_TableCell2.default, { align: "right" }, fmtInt(r.apiRequests)), /* @__PURE__ */ import_react6.default.createElement(import_TableCell2.default, { align: "right" }, fmtCompact(r.totalTokens)), /* @__PURE__ */ import_react6.default.createElement(import_TableCell2.default, { align: "right" }, fmtInt(r.failedRequests)), /* @__PURE__ */ import_react6.default.createElement(import_TableCell2.default, null, /* @__PURE__ */ import_react6.default.createElement(SuccessRateCell, { rate: r.successRate, requests: r.apiRequests }))));
|
|
2082
2499
|
};
|
|
2083
2500
|
const renderModelRows = () => {
|
|
2084
2501
|
if (loading) {
|
|
2085
|
-
return /* @__PURE__ */
|
|
2502
|
+
return /* @__PURE__ */ import_react6.default.createElement(import_TableRow2.default, null, /* @__PURE__ */ import_react6.default.createElement(import_TableCell2.default, { colSpan: 8, sx: { py: 3 } }, /* @__PURE__ */ import_react6.default.createElement(import_Skeleton3.default, { variant: "rounded", height: 80 })));
|
|
2086
2503
|
}
|
|
2087
2504
|
if (modelRows.length === 0) {
|
|
2088
|
-
return /* @__PURE__ */
|
|
2505
|
+
return /* @__PURE__ */ import_react6.default.createElement(import_TableRow2.default, null, /* @__PURE__ */ import_react6.default.createElement(import_TableCell2.default, { colSpan: 8 }, /* @__PURE__ */ import_react6.default.createElement(EmptyState, { message: "No model activity in this period" })));
|
|
2089
2506
|
}
|
|
2090
|
-
return [...modelRows].sort((a, b) => b.spend - a.spend || b.totalTokens - a.totalTokens).map((r) => /* @__PURE__ */
|
|
2507
|
+
return [...modelRows].sort((a, b) => b.spend - a.spend || b.totalTokens - a.totalTokens).map((r) => /* @__PURE__ */ import_react6.default.createElement(import_TableRow2.default, { key: r.model }, /* @__PURE__ */ import_react6.default.createElement(import_TableCell2.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_TableCell2.default, { align: "right" }, fmtUsd(r.spend)), /* @__PURE__ */ import_react6.default.createElement(import_TableCell2.default, { align: "right" }, fmtInt(r.apiRequests)), /* @__PURE__ */ import_react6.default.createElement(import_TableCell2.default, { align: "right" }, fmtInt(r.successfulRequests)), /* @__PURE__ */ import_react6.default.createElement(import_TableCell2.default, { align: "right" }, fmtInt(r.failedRequests)), /* @__PURE__ */ import_react6.default.createElement(import_TableCell2.default, { align: "right" }, fmtCompact(r.promptTokens)), /* @__PURE__ */ import_react6.default.createElement(import_TableCell2.default, { align: "right" }, fmtCompact(r.completionTokens)), /* @__PURE__ */ import_react6.default.createElement(import_TableCell2.default, null, /* @__PURE__ */ import_react6.default.createElement(SuccessRateCell, { rate: r.successRate, requests: r.apiRequests }))));
|
|
2091
2508
|
};
|
|
2092
|
-
const periodSelect = /* @__PURE__ */
|
|
2093
|
-
|
|
2509
|
+
const periodSelect = /* @__PURE__ */ import_react6.default.createElement(
|
|
2510
|
+
import_TextField4.default,
|
|
2094
2511
|
{
|
|
2095
2512
|
select: true,
|
|
2096
2513
|
size: "small",
|
|
@@ -2099,18 +2516,18 @@ var init_UsageStats = __esm({
|
|
|
2099
2516
|
onChange: (e) => handlePresetChange(e.target.value),
|
|
2100
2517
|
sx: { minWidth: 160 }
|
|
2101
2518
|
},
|
|
2102
|
-
Object.keys(PRESET_LABELS).map((p) => /* @__PURE__ */
|
|
2519
|
+
Object.keys(PRESET_LABELS).map((p) => /* @__PURE__ */ import_react6.default.createElement(import_MenuItem3.default, { key: p, value: p }, PRESET_LABELS[p]))
|
|
2103
2520
|
);
|
|
2104
|
-
return /* @__PURE__ */
|
|
2521
|
+
return /* @__PURE__ */ import_react6.default.createElement(
|
|
2105
2522
|
SectionCard,
|
|
2106
2523
|
{
|
|
2107
2524
|
title: "Usage Analytics",
|
|
2108
2525
|
subtitle: `${PRESET_LABELS[currentPreset]} \xB7 ${dateRange.start.toLocaleDateString()} \u2013 ${dateRange.end.toLocaleDateString()}`,
|
|
2109
2526
|
actions: periodSelect
|
|
2110
2527
|
},
|
|
2111
|
-
/* @__PURE__ */
|
|
2112
|
-
/* @__PURE__ */
|
|
2113
|
-
|
|
2528
|
+
/* @__PURE__ */ import_react6.default.createElement(MetricStrip, { metrics }),
|
|
2529
|
+
/* @__PURE__ */ import_react6.default.createElement(
|
|
2530
|
+
import_Box6.default,
|
|
2114
2531
|
{
|
|
2115
2532
|
sx: {
|
|
2116
2533
|
display: "flex",
|
|
@@ -2122,7 +2539,7 @@ var init_UsageStats = __esm({
|
|
|
2122
2539
|
mb: 2
|
|
2123
2540
|
}
|
|
2124
2541
|
},
|
|
2125
|
-
/* @__PURE__ */
|
|
2542
|
+
/* @__PURE__ */ import_react6.default.createElement(
|
|
2126
2543
|
SegmentedControl,
|
|
2127
2544
|
{
|
|
2128
2545
|
value: tab,
|
|
@@ -2134,8 +2551,8 @@ var init_UsageStats = __esm({
|
|
|
2134
2551
|
]
|
|
2135
2552
|
}
|
|
2136
2553
|
),
|
|
2137
|
-
tab === "models" && /* @__PURE__ */
|
|
2138
|
-
|
|
2554
|
+
tab === "models" && /* @__PURE__ */ import_react6.default.createElement(
|
|
2555
|
+
import_TextField4.default,
|
|
2139
2556
|
{
|
|
2140
2557
|
select: true,
|
|
2141
2558
|
size: "small",
|
|
@@ -2144,17 +2561,17 @@ var init_UsageStats = __esm({
|
|
|
2144
2561
|
onChange: (e) => setSelectedModel(e.target.value),
|
|
2145
2562
|
sx: { minWidth: 220 }
|
|
2146
2563
|
},
|
|
2147
|
-
/* @__PURE__ */
|
|
2148
|
-
models.map((m) => /* @__PURE__ */
|
|
2564
|
+
/* @__PURE__ */ import_react6.default.createElement(import_MenuItem3.default, { value: "all" }, "All models"),
|
|
2565
|
+
models.map((m) => /* @__PURE__ */ import_react6.default.createElement(import_MenuItem3.default, { key: m.model_name, value: m.model_name }, m.model_name))
|
|
2149
2566
|
)
|
|
2150
2567
|
),
|
|
2151
|
-
tab === "costs" && /* @__PURE__ */
|
|
2568
|
+
tab === "costs" && /* @__PURE__ */ import_react6.default.createElement(import_Grid.default, { container: true, spacing: 2 }, /* @__PURE__ */ import_react6.default.createElement(import_Grid.default, { item: true, xs: 12 }, /* @__PURE__ */ import_react6.default.createElement(ChartCard, { title: "Daily spend by model", height: 260 }, /* @__PURE__ */ import_react6.default.createElement(ChartOrFallback, { loading, empty: modelSpendByDate.length === 0, height: 260 }, /* @__PURE__ */ import_react6.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react6.default.createElement(import_recharts.AreaChart, { data: modelSpendByDate, margin: { top: 4, right: 8, left: 0, bottom: 0 } }, /* @__PURE__ */ import_react6.default.createElement(import_recharts.CartesianGrid, { ...chart.grid }), /* @__PURE__ */ import_react6.default.createElement(import_recharts.XAxis, { dataKey: "date", tickFormatter: fmtDateShort, ...chart.axis }), /* @__PURE__ */ import_react6.default.createElement(import_recharts.YAxis, { tickFormatter: fmtUsdCompact, width: 56, ...chart.axis }), /* @__PURE__ */ import_react6.default.createElement(
|
|
2152
2569
|
import_recharts.Tooltip,
|
|
2153
2570
|
{
|
|
2154
2571
|
cursor: chart.cursor,
|
|
2155
|
-
content: /* @__PURE__ */
|
|
2572
|
+
content: /* @__PURE__ */ import_react6.default.createElement(ChartTooltip, { valueFormatter: fmtUsd, hideZero: true })
|
|
2156
2573
|
}
|
|
2157
|
-
), topSpendModels.map((m) => /* @__PURE__ */
|
|
2574
|
+
), topSpendModels.map((m) => /* @__PURE__ */ import_react6.default.createElement(
|
|
2158
2575
|
import_recharts.Area,
|
|
2159
2576
|
{
|
|
2160
2577
|
key: m,
|
|
@@ -2166,13 +2583,13 @@ var init_UsageStats = __esm({
|
|
|
2166
2583
|
fill: chartColor(m),
|
|
2167
2584
|
fillOpacity: 0.28
|
|
2168
2585
|
}
|
|
2169
|
-
))))), !loading && topSpendModels.length > 0 && /* @__PURE__ */
|
|
2586
|
+
))))), !loading && topSpendModels.length > 0 && /* @__PURE__ */ import_react6.default.createElement(SeriesLegend, { series: topSpendModels.map((m) => ({ name: m, color: chartColor(m) })) }))), /* @__PURE__ */ import_react6.default.createElement(import_Grid.default, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ import_react6.default.createElement(ChartCard, { title: "Daily token usage" }, /* @__PURE__ */ import_react6.default.createElement(ChartOrFallback, { loading, empty: dailyData.length === 0 }, /* @__PURE__ */ import_react6.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react6.default.createElement(import_recharts.AreaChart, { data: dailyData, margin: { top: 4, right: 8, left: 0, bottom: 0 } }, /* @__PURE__ */ import_react6.default.createElement(import_recharts.CartesianGrid, { ...chart.grid }), /* @__PURE__ */ import_react6.default.createElement(import_recharts.XAxis, { dataKey: "date", tickFormatter: fmtDateShort, ...chart.axis }), /* @__PURE__ */ import_react6.default.createElement(import_recharts.YAxis, { tickFormatter: fmtCompact, width: 48, ...chart.axis }), /* @__PURE__ */ import_react6.default.createElement(import_recharts.Tooltip, { cursor: chart.cursor, content: /* @__PURE__ */ import_react6.default.createElement(ChartTooltip, { valueFormatter: fmtInt }) }), /* @__PURE__ */ import_react6.default.createElement(import_recharts.Legend, { ...chart.legend }), /* @__PURE__ */ import_react6.default.createElement(import_recharts.Area, { type: "monotone", dataKey: "promptTokens", name: "Input", stackId: "tok", stroke: SERIES.input, strokeWidth: 1.5, fill: SERIES.input, fillOpacity: 0.25 }), /* @__PURE__ */ import_react6.default.createElement(import_recharts.Area, { type: "monotone", dataKey: "completionTokens", name: "Output", stackId: "tok", stroke: SERIES.output, strokeWidth: 1.5, fill: SERIES.output, fillOpacity: 0.25 })))))), /* @__PURE__ */ import_react6.default.createElement(import_Grid.default, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ import_react6.default.createElement(ChartCard, { title: "Daily requests" }, /* @__PURE__ */ import_react6.default.createElement(ChartOrFallback, { loading, empty: dailyData.length === 0 }, /* @__PURE__ */ import_react6.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react6.default.createElement(import_recharts.BarChart, { data: dailyData, margin: { top: 4, right: 8, left: 0, bottom: 0 }, barCategoryGap: "30%" }, /* @__PURE__ */ import_react6.default.createElement(import_recharts.CartesianGrid, { ...chart.grid }), /* @__PURE__ */ import_react6.default.createElement(import_recharts.XAxis, { dataKey: "date", tickFormatter: fmtDateShort, ...chart.axis }), /* @__PURE__ */ import_react6.default.createElement(import_recharts.YAxis, { tickFormatter: fmtCompact, width: 48, ...chart.axis }), /* @__PURE__ */ import_react6.default.createElement(import_recharts.Tooltip, { cursor: chart.cursor, content: /* @__PURE__ */ import_react6.default.createElement(ChartTooltip, { valueFormatter: fmtInt }) }), /* @__PURE__ */ import_react6.default.createElement(import_recharts.Legend, { ...chart.legend }), /* @__PURE__ */ import_react6.default.createElement(import_recharts.Bar, { dataKey: "successfulRequests", name: "Successful", fill: SERIES.success, stackId: "r" }), /* @__PURE__ */ import_react6.default.createElement(import_recharts.Bar, { dataKey: "failedRequests", name: "Failed", fill: SERIES.failure, stackId: "r", radius: [3, 3, 0, 0] })))))), /* @__PURE__ */ import_react6.default.createElement(import_Grid.default, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ import_react6.default.createElement(ChartCard, { title: "Daily success rate" }, /* @__PURE__ */ import_react6.default.createElement(ChartOrFallback, { loading, empty: successRateData.length === 0 }, /* @__PURE__ */ import_react6.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react6.default.createElement(import_recharts.LineChart, { data: successRateData, margin: { top: 4, right: 8, left: 0, bottom: 0 } }, /* @__PURE__ */ import_react6.default.createElement(import_recharts.CartesianGrid, { ...chart.grid }), /* @__PURE__ */ import_react6.default.createElement(import_recharts.XAxis, { dataKey: "date", tickFormatter: fmtDateShort, ...chart.axis }), /* @__PURE__ */ import_react6.default.createElement(import_recharts.YAxis, { domain: [0, 100], tickFormatter: (v) => `${v}%`, width: 44, ...chart.axis }), /* @__PURE__ */ import_react6.default.createElement(
|
|
2170
2587
|
import_recharts.Tooltip,
|
|
2171
2588
|
{
|
|
2172
2589
|
cursor: chart.cursor,
|
|
2173
|
-
content: /* @__PURE__ */
|
|
2590
|
+
content: /* @__PURE__ */ import_react6.default.createElement(ChartTooltip, { valueFormatter: (v) => `${v}%` })
|
|
2174
2591
|
}
|
|
2175
|
-
), /* @__PURE__ */
|
|
2592
|
+
), /* @__PURE__ */ import_react6.default.createElement(import_recharts.ReferenceLine, { y: 100, stroke: SERIES.success, strokeDasharray: "4 4", strokeOpacity: 0.5 }), /* @__PURE__ */ import_react6.default.createElement(
|
|
2176
2593
|
import_recharts.Line,
|
|
2177
2594
|
{
|
|
2178
2595
|
type: "monotone",
|
|
@@ -2183,13 +2600,13 @@ var init_UsageStats = __esm({
|
|
|
2183
2600
|
dot: { r: 2.5, strokeWidth: 0, fill: SERIES.input },
|
|
2184
2601
|
activeDot: { r: 4 }
|
|
2185
2602
|
}
|
|
2186
|
-
)))))), /* @__PURE__ */
|
|
2603
|
+
)))))), /* @__PURE__ */ import_react6.default.createElement(import_Grid.default, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ import_react6.default.createElement(
|
|
2187
2604
|
ChartCard,
|
|
2188
2605
|
{
|
|
2189
2606
|
title: maxBudget > 0 ? "Cumulative spend vs budget" : "Cumulative spend",
|
|
2190
2607
|
meta: maxBudget > 0 ? `${fmtUsd(totalCumSpend)} used \xB7 ${fmtUsd(Math.max(0, maxBudget - totalCumSpend))} left` : void 0
|
|
2191
2608
|
},
|
|
2192
|
-
/* @__PURE__ */
|
|
2609
|
+
/* @__PURE__ */ import_react6.default.createElement(ChartOrFallback, { loading, empty: cumulativeData.length === 0 }, /* @__PURE__ */ import_react6.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react6.default.createElement(import_recharts.AreaChart, { data: cumulativeData, margin: { top: 4, right: 8, left: 0, bottom: 0 } }, /* @__PURE__ */ import_react6.default.createElement(import_recharts.CartesianGrid, { ...chart.grid }), /* @__PURE__ */ import_react6.default.createElement(import_recharts.XAxis, { dataKey: "date", tickFormatter: fmtDateShort, ...chart.axis }), /* @__PURE__ */ import_react6.default.createElement(
|
|
2193
2610
|
import_recharts.YAxis,
|
|
2194
2611
|
{
|
|
2195
2612
|
tickFormatter: fmtUsdCompact,
|
|
@@ -2197,13 +2614,13 @@ var init_UsageStats = __esm({
|
|
|
2197
2614
|
domain: cumulativeDomain,
|
|
2198
2615
|
...chart.axis
|
|
2199
2616
|
}
|
|
2200
|
-
), /* @__PURE__ */
|
|
2617
|
+
), /* @__PURE__ */ import_react6.default.createElement(
|
|
2201
2618
|
import_recharts.Tooltip,
|
|
2202
2619
|
{
|
|
2203
2620
|
cursor: chart.cursor,
|
|
2204
|
-
content: /* @__PURE__ */
|
|
2621
|
+
content: /* @__PURE__ */ import_react6.default.createElement(ChartTooltip, { valueFormatter: fmtUsd })
|
|
2205
2622
|
}
|
|
2206
|
-
), maxBudget > 0 && /* @__PURE__ */
|
|
2623
|
+
), maxBudget > 0 && /* @__PURE__ */ import_react6.default.createElement(
|
|
2207
2624
|
import_recharts.ReferenceLine,
|
|
2208
2625
|
{
|
|
2209
2626
|
y: maxBudget,
|
|
@@ -2211,7 +2628,7 @@ var init_UsageStats = __esm({
|
|
|
2211
2628
|
strokeDasharray: "5 4",
|
|
2212
2629
|
label: { value: `Budget ${fmtUsd(maxBudget)}`, position: "insideTopRight", fontSize: 10, fill: SERIES.budget }
|
|
2213
2630
|
}
|
|
2214
|
-
), /* @__PURE__ */
|
|
2631
|
+
), /* @__PURE__ */ import_react6.default.createElement(
|
|
2215
2632
|
import_recharts.Area,
|
|
2216
2633
|
{
|
|
2217
2634
|
type: "monotone",
|
|
@@ -2224,32 +2641,32 @@ var init_UsageStats = __esm({
|
|
|
2224
2641
|
}
|
|
2225
2642
|
))))
|
|
2226
2643
|
))),
|
|
2227
|
-
tab === "models" && /* @__PURE__ */
|
|
2644
|
+
tab === "models" && /* @__PURE__ */ import_react6.default.createElement(import_Grid.default, { container: true, spacing: 2 }, /* @__PURE__ */ import_react6.default.createElement(import_Grid.default, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ import_react6.default.createElement(ChartCard, { title: "Spend by model", height: Math.max(200, topModelSpendBars.length * 34) }, /* @__PURE__ */ import_react6.default.createElement(
|
|
2228
2645
|
ChartOrFallback,
|
|
2229
2646
|
{
|
|
2230
2647
|
loading,
|
|
2231
2648
|
empty: topModelSpendBars.length === 0,
|
|
2232
2649
|
height: Math.max(200, topModelSpendBars.length * 34)
|
|
2233
2650
|
},
|
|
2234
|
-
/* @__PURE__ */
|
|
2235
|
-
))), /* @__PURE__ */
|
|
2651
|
+
/* @__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: fmtUsdCompact, ...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: fmtUsd, labelFormatter: (l) => l }) }), /* @__PURE__ */ import_react6.default.createElement(import_recharts.Bar, { dataKey: "spend", name: "Spend", radius: [0, 3, 3, 0], barSize: 14 }, topModelSpendBars.map((r) => /* @__PURE__ */ import_react6.default.createElement(import_recharts.Cell, { key: r.model, fill: chartColor(r.model) })))))
|
|
2652
|
+
))), /* @__PURE__ */ import_react6.default.createElement(import_Grid.default, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ import_react6.default.createElement(ChartCard, { title: "Tokens by model", height: Math.max(200, topModelSpendBars.length * 34) }, /* @__PURE__ */ import_react6.default.createElement(
|
|
2236
2653
|
ChartOrFallback,
|
|
2237
2654
|
{
|
|
2238
2655
|
loading,
|
|
2239
2656
|
empty: modelRows.length === 0,
|
|
2240
2657
|
height: Math.max(200, topModelSpendBars.length * 34)
|
|
2241
2658
|
},
|
|
2242
|
-
/* @__PURE__ */
|
|
2243
|
-
))), /* @__PURE__ */
|
|
2244
|
-
tab === "keys" && /* @__PURE__ */
|
|
2659
|
+
/* @__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(import_TableContainer2.default, { component: import_Paper3.default, variant: "outlined", sx: { borderRadius: 2 } }, /* @__PURE__ */ import_react6.default.createElement(import_Table2.default, { size: "small", sx: dataTableSx }, /* @__PURE__ */ import_react6.default.createElement(import_TableHead2.default, null, /* @__PURE__ */ import_react6.default.createElement(import_TableRow2.default, null, /* @__PURE__ */ import_react6.default.createElement(import_TableCell2.default, null, "Model"), /* @__PURE__ */ import_react6.default.createElement(import_TableCell2.default, { align: "right" }, "Spend"), /* @__PURE__ */ import_react6.default.createElement(import_TableCell2.default, { align: "right" }, "Requests"), /* @__PURE__ */ import_react6.default.createElement(import_TableCell2.default, { align: "right" }, "Success"), /* @__PURE__ */ import_react6.default.createElement(import_TableCell2.default, { align: "right" }, "Failed"), /* @__PURE__ */ import_react6.default.createElement(import_TableCell2.default, { align: "right" }, "Input"), /* @__PURE__ */ import_react6.default.createElement(import_TableCell2.default, { align: "right" }, "Output"), /* @__PURE__ */ import_react6.default.createElement(import_TableCell2.default, { sx: { width: 160 } }, "Success rate"))), /* @__PURE__ */ import_react6.default.createElement(import_TableBody2.default, null, renderModelRows()))))),
|
|
2661
|
+
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(
|
|
2245
2662
|
ChartOrFallback,
|
|
2246
2663
|
{
|
|
2247
2664
|
loading,
|
|
2248
2665
|
empty: topKeySpendBars.length === 0,
|
|
2249
2666
|
height: Math.max(160, topKeySpendBars.length * 32)
|
|
2250
2667
|
},
|
|
2251
|
-
/* @__PURE__ */
|
|
2252
|
-
))), /* @__PURE__ */
|
|
2668
|
+
/* @__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(import_TableContainer2.default, { component: import_Paper3.default, variant: "outlined", sx: { borderRadius: 2 } }, /* @__PURE__ */ import_react6.default.createElement(import_Table2.default, { size: "small", sx: dataTableSx }, /* @__PURE__ */ import_react6.default.createElement(import_TableHead2.default, null, /* @__PURE__ */ import_react6.default.createElement(import_TableRow2.default, null, /* @__PURE__ */ import_react6.default.createElement(import_TableCell2.default, null, "Key"), /* @__PURE__ */ import_react6.default.createElement(import_TableCell2.default, null, "Models"), /* @__PURE__ */ import_react6.default.createElement(import_TableCell2.default, { align: "right" }, "Spend"), /* @__PURE__ */ import_react6.default.createElement(import_TableCell2.default, { align: "right" }, "Requests"), /* @__PURE__ */ import_react6.default.createElement(import_TableCell2.default, { align: "right" }, "Tokens"), /* @__PURE__ */ import_react6.default.createElement(import_TableCell2.default, { align: "right" }, "Failed"), /* @__PURE__ */ import_react6.default.createElement(import_TableCell2.default, { sx: { width: 160 } }, "Success rate"))), /* @__PURE__ */ import_react6.default.createElement(import_TableBody2.default, null, renderKeyRows())))))
|
|
2253
2670
|
);
|
|
2254
2671
|
};
|
|
2255
2672
|
}
|
|
@@ -2261,16 +2678,16 @@ function budgetTone2(isOver, isNear) {
|
|
|
2261
2678
|
if (isNear) return "warning";
|
|
2262
2679
|
return "accent";
|
|
2263
2680
|
}
|
|
2264
|
-
var
|
|
2681
|
+
var import_react7, import_Paper4, import_Box7, import_Typography7, import_Skeleton4, import_Divider3, import_Table3, import_TableBody3, import_TableCell3, import_TableHead3, import_TableRow3, import_TableContainer3, import_Collapse, import_IconButton4, import_Button5, import_CircularProgress3, import_styles4, import_icons_material5, import_recharts2, fmtUsd2, TeamCard, TeamUsage;
|
|
2265
2682
|
var init_TeamUsage = __esm({
|
|
2266
2683
|
"src/components/TeamUsage.tsx"() {
|
|
2267
2684
|
"use strict";
|
|
2268
|
-
|
|
2685
|
+
import_react7 = __toESM(require("react"));
|
|
2269
2686
|
import_Paper4 = __toESM(require("@mui/material/Paper"));
|
|
2270
|
-
|
|
2271
|
-
|
|
2687
|
+
import_Box7 = __toESM(require("@mui/material/Box"));
|
|
2688
|
+
import_Typography7 = __toESM(require("@mui/material/Typography"));
|
|
2272
2689
|
import_Skeleton4 = __toESM(require("@mui/material/Skeleton"));
|
|
2273
|
-
|
|
2690
|
+
import_Divider3 = __toESM(require("@mui/material/Divider"));
|
|
2274
2691
|
import_Table3 = __toESM(require("@mui/material/Table"));
|
|
2275
2692
|
import_TableBody3 = __toESM(require("@mui/material/TableBody"));
|
|
2276
2693
|
import_TableCell3 = __toESM(require("@mui/material/TableCell"));
|
|
@@ -2278,15 +2695,16 @@ var init_TeamUsage = __esm({
|
|
|
2278
2695
|
import_TableRow3 = __toESM(require("@mui/material/TableRow"));
|
|
2279
2696
|
import_TableContainer3 = __toESM(require("@mui/material/TableContainer"));
|
|
2280
2697
|
import_Collapse = __toESM(require("@mui/material/Collapse"));
|
|
2281
|
-
|
|
2698
|
+
import_IconButton4 = __toESM(require("@mui/material/IconButton"));
|
|
2699
|
+
import_Button5 = __toESM(require("@mui/material/Button"));
|
|
2282
2700
|
import_CircularProgress3 = __toESM(require("@mui/material/CircularProgress"));
|
|
2283
2701
|
import_styles4 = require("@mui/material/styles");
|
|
2284
|
-
|
|
2702
|
+
import_icons_material5 = require("@mui/icons-material");
|
|
2285
2703
|
import_recharts2 = require("recharts");
|
|
2286
2704
|
init_ui();
|
|
2287
2705
|
fmtUsd2 = (n) => `$${(n ?? 0).toFixed(2)}`;
|
|
2288
|
-
TeamCard = ({ team, usage, usageLoading }) => {
|
|
2289
|
-
const [expanded, setExpanded] = (0,
|
|
2706
|
+
TeamCard = ({ team, usage, usageLoading, canManage, onEditTeam }) => {
|
|
2707
|
+
const [expanded, setExpanded] = (0, import_react7.useState)(false);
|
|
2290
2708
|
const chart = useChartTheme();
|
|
2291
2709
|
const budget = team.max_budget ?? 0;
|
|
2292
2710
|
const spend = team.spend ?? 0;
|
|
@@ -2296,24 +2714,24 @@ var init_TeamUsage = __esm({
|
|
|
2296
2714
|
const dailyData = usage?.daily_usage?.map((d) => ({ date: d.date, spend: d.spend })) ?? [];
|
|
2297
2715
|
const renderDailySpendSection = () => {
|
|
2298
2716
|
if (usageLoading) {
|
|
2299
|
-
return /* @__PURE__ */
|
|
2717
|
+
return /* @__PURE__ */ import_react7.default.createElement(import_Box7.default, { display: "flex", justifyContent: "center", py: 3 }, /* @__PURE__ */ import_react7.default.createElement(import_CircularProgress3.default, { size: 22 }));
|
|
2300
2718
|
}
|
|
2301
2719
|
if (dailyData.length === 0) return null;
|
|
2302
|
-
return /* @__PURE__ */
|
|
2303
|
-
|
|
2720
|
+
return /* @__PURE__ */ import_react7.default.createElement(import_Box7.default, { mb: 2.5 }, /* @__PURE__ */ import_react7.default.createElement(
|
|
2721
|
+
import_Typography7.default,
|
|
2304
2722
|
{
|
|
2305
2723
|
variant: "caption",
|
|
2306
2724
|
color: "text.secondary",
|
|
2307
2725
|
sx: { display: "block", fontSize: 11, fontWeight: 700, letterSpacing: "0.07em", textTransform: "uppercase", mb: 1 }
|
|
2308
2726
|
},
|
|
2309
2727
|
"Daily spend"
|
|
2310
|
-
), /* @__PURE__ */
|
|
2728
|
+
), /* @__PURE__ */ import_react7.default.createElement(import_Box7.default, { height: 150 }, /* @__PURE__ */ import_react7.default.createElement(import_recharts2.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react7.default.createElement(import_recharts2.AreaChart, { data: dailyData, margin: { top: 4, right: 8, left: 0, bottom: 0 } }, /* @__PURE__ */ import_react7.default.createElement(import_recharts2.CartesianGrid, { ...chart.grid }), /* @__PURE__ */ import_react7.default.createElement(import_recharts2.XAxis, { dataKey: "date", tickFormatter: fmtDateShort, ...chart.axis }), /* @__PURE__ */ import_react7.default.createElement(import_recharts2.YAxis, { tickFormatter: fmtUsdCompact, width: 52, ...chart.axis }), /* @__PURE__ */ import_react7.default.createElement(
|
|
2311
2729
|
import_recharts2.Tooltip,
|
|
2312
2730
|
{
|
|
2313
2731
|
cursor: chart.cursor,
|
|
2314
|
-
content: /* @__PURE__ */
|
|
2732
|
+
content: /* @__PURE__ */ import_react7.default.createElement(ChartTooltip, { valueFormatter: (v) => `$${v.toFixed(4)}` })
|
|
2315
2733
|
}
|
|
2316
|
-
), /* @__PURE__ */
|
|
2734
|
+
), /* @__PURE__ */ import_react7.default.createElement(
|
|
2317
2735
|
import_recharts2.Area,
|
|
2318
2736
|
{
|
|
2319
2737
|
type: "monotone",
|
|
@@ -2326,8 +2744,8 @@ var init_TeamUsage = __esm({
|
|
|
2326
2744
|
}
|
|
2327
2745
|
)))));
|
|
2328
2746
|
};
|
|
2329
|
-
const sectionLabel = (label) => /* @__PURE__ */
|
|
2330
|
-
|
|
2747
|
+
const sectionLabel = (label) => /* @__PURE__ */ import_react7.default.createElement(
|
|
2748
|
+
import_Typography7.default,
|
|
2331
2749
|
{
|
|
2332
2750
|
variant: "caption",
|
|
2333
2751
|
color: "text.secondary",
|
|
@@ -2335,8 +2753,8 @@ var init_TeamUsage = __esm({
|
|
|
2335
2753
|
},
|
|
2336
2754
|
label
|
|
2337
2755
|
);
|
|
2338
|
-
return /* @__PURE__ */
|
|
2339
|
-
|
|
2756
|
+
return /* @__PURE__ */ import_react7.default.createElement(import_Paper4.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
|
+
import_Box7.default,
|
|
2340
2758
|
{
|
|
2341
2759
|
sx: (theme) => ({
|
|
2342
2760
|
width: 36,
|
|
@@ -2350,17 +2768,26 @@ var init_TeamUsage = __esm({
|
|
|
2350
2768
|
color: theme.palette.primary.main
|
|
2351
2769
|
})
|
|
2352
2770
|
},
|
|
2353
|
-
/* @__PURE__ */
|
|
2354
|
-
), /* @__PURE__ */
|
|
2355
|
-
|
|
2771
|
+
/* @__PURE__ */ import_react7.default.createElement(import_icons_material5.Group, { fontSize: "small" })
|
|
2772
|
+
), /* @__PURE__ */ import_react7.default.createElement(import_Box7.default, { flexGrow: 1, minWidth: 0 }, /* @__PURE__ */ import_react7.default.createElement(import_Typography7.default, { variant: "subtitle1", sx: { fontWeight: 600, lineHeight: 1.3 } }, team.team_alias || "Untitled team"), /* @__PURE__ */ import_react7.default.createElement(
|
|
2773
|
+
import_Typography7.default,
|
|
2356
2774
|
{
|
|
2357
2775
|
variant: "caption",
|
|
2358
2776
|
color: "text.secondary",
|
|
2359
2777
|
sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 11 }
|
|
2360
2778
|
},
|
|
2361
2779
|
team.team_id
|
|
2362
|
-
)), isOver && /* @__PURE__ */
|
|
2363
|
-
|
|
2780
|
+
)), isOver && /* @__PURE__ */ import_react7.default.createElement(StatusPill, { label: "Over budget", tone: "danger" }), isNear && /* @__PURE__ */ import_react7.default.createElement(StatusPill, { label: "Near limit", tone: "warning" }), canManage && onEditTeam && /* @__PURE__ */ import_react7.default.createElement(
|
|
2781
|
+
import_Button5.default,
|
|
2782
|
+
{
|
|
2783
|
+
size: "small",
|
|
2784
|
+
variant: "text",
|
|
2785
|
+
onClick: () => onEditTeam(team),
|
|
2786
|
+
sx: { textTransform: "none" }
|
|
2787
|
+
},
|
|
2788
|
+
"Edit"
|
|
2789
|
+
), /* @__PURE__ */ import_react7.default.createElement(
|
|
2790
|
+
import_IconButton4.default,
|
|
2364
2791
|
{
|
|
2365
2792
|
size: "small",
|
|
2366
2793
|
onClick: () => setExpanded((e) => !e),
|
|
@@ -2372,9 +2799,9 @@ var init_TeamUsage = __esm({
|
|
|
2372
2799
|
transition: theme.transitions.create("transform")
|
|
2373
2800
|
})
|
|
2374
2801
|
},
|
|
2375
|
-
/* @__PURE__ */
|
|
2376
|
-
)), /* @__PURE__ */
|
|
2377
|
-
|
|
2802
|
+
/* @__PURE__ */ import_react7.default.createElement(import_icons_material5.ExpandMore, null)
|
|
2803
|
+
)), /* @__PURE__ */ import_react7.default.createElement(
|
|
2804
|
+
import_Box7.default,
|
|
2378
2805
|
{
|
|
2379
2806
|
sx: {
|
|
2380
2807
|
display: "grid",
|
|
@@ -2384,35 +2811,35 @@ var init_TeamUsage = __esm({
|
|
|
2384
2811
|
maxWidth: 640
|
|
2385
2812
|
}
|
|
2386
2813
|
},
|
|
2387
|
-
/* @__PURE__ */
|
|
2388
|
-
/* @__PURE__ */
|
|
2389
|
-
/* @__PURE__ */
|
|
2390
|
-
/* @__PURE__ */
|
|
2391
|
-
/* @__PURE__ */
|
|
2392
|
-
/* @__PURE__ */
|
|
2393
|
-
), budget > 0 && /* @__PURE__ */
|
|
2814
|
+
/* @__PURE__ */ import_react7.default.createElement(Stat, { label: "Members", value: team.members_with_roles?.length ?? "\u2014" }),
|
|
2815
|
+
/* @__PURE__ */ import_react7.default.createElement(Stat, { label: "Models", value: team.models?.length ? team.models.length : "All" }),
|
|
2816
|
+
/* @__PURE__ */ import_react7.default.createElement(Stat, { label: "Budget", value: budget > 0 ? fmtUsd2(budget) : "Unlimited" }),
|
|
2817
|
+
/* @__PURE__ */ import_react7.default.createElement(Stat, { label: "Spend", value: fmtUsd2(spend) }),
|
|
2818
|
+
/* @__PURE__ */ import_react7.default.createElement(Stat, { label: "TPM", value: team.tpm_limit && team.tpm_limit > 0 ? team.tpm_limit.toLocaleString() : "\u2014" }),
|
|
2819
|
+
/* @__PURE__ */ import_react7.default.createElement(Stat, { label: "RPM", value: team.rpm_limit && team.rpm_limit > 0 ? team.rpm_limit.toLocaleString() : "\u2014" })
|
|
2820
|
+
), 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(
|
|
2394
2821
|
import_TableContainer3.default,
|
|
2395
2822
|
{
|
|
2396
2823
|
component: import_Paper4.default,
|
|
2397
2824
|
variant: "outlined",
|
|
2398
2825
|
sx: { borderRadius: 1.5 }
|
|
2399
2826
|
},
|
|
2400
|
-
/* @__PURE__ */
|
|
2401
|
-
|
|
2827
|
+
/* @__PURE__ */ import_react7.default.createElement(import_Table3.default, { size: "small", sx: dataTableSx }, /* @__PURE__ */ import_react7.default.createElement(import_TableHead3.default, null, /* @__PURE__ */ import_react7.default.createElement(import_TableRow3.default, null, /* @__PURE__ */ import_react7.default.createElement(import_TableCell3.default, null, "User"), /* @__PURE__ */ import_react7.default.createElement(import_TableCell3.default, { align: "right" }, "Role"))), /* @__PURE__ */ import_react7.default.createElement(import_TableBody3.default, null, team.members_with_roles.map((m) => /* @__PURE__ */ import_react7.default.createElement(import_TableRow3.default, { key: m.user_id }, /* @__PURE__ */ import_react7.default.createElement(import_TableCell3.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
|
+
import_Typography7.default,
|
|
2402
2829
|
{
|
|
2403
2830
|
variant: "caption",
|
|
2404
2831
|
color: "text.secondary",
|
|
2405
2832
|
sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 11 }
|
|
2406
2833
|
},
|
|
2407
2834
|
m.user_id
|
|
2408
|
-
)) : /* @__PURE__ */
|
|
2409
|
-
|
|
2835
|
+
)) : /* @__PURE__ */ import_react7.default.createElement(
|
|
2836
|
+
import_Typography7.default,
|
|
2410
2837
|
{
|
|
2411
2838
|
variant: "body2",
|
|
2412
2839
|
sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace" }
|
|
2413
2840
|
},
|
|
2414
2841
|
m.user_id
|
|
2415
|
-
)), /* @__PURE__ */
|
|
2842
|
+
)), /* @__PURE__ */ import_react7.default.createElement(import_TableCell3.default, { align: "right" }, /* @__PURE__ */ import_react7.default.createElement(
|
|
2416
2843
|
StatusPill,
|
|
2417
2844
|
{
|
|
2418
2845
|
label: m.role,
|
|
@@ -2420,19 +2847,21 @@ var init_TeamUsage = __esm({
|
|
|
2420
2847
|
dot: false
|
|
2421
2848
|
}
|
|
2422
2849
|
))))))
|
|
2423
|
-
) : /* @__PURE__ */
|
|
2850
|
+
) : /* @__PURE__ */ import_react7.default.createElement(import_Typography7.default, { variant: "body2", color: "text.secondary" }, "No members assigned."))));
|
|
2424
2851
|
};
|
|
2425
2852
|
TeamUsage = ({
|
|
2426
2853
|
teams,
|
|
2427
2854
|
loading,
|
|
2428
2855
|
getTeamUsage,
|
|
2429
|
-
getTeamUsageLoading
|
|
2856
|
+
getTeamUsageLoading,
|
|
2857
|
+
canManage,
|
|
2858
|
+
onEditTeam
|
|
2430
2859
|
}) => {
|
|
2431
2860
|
if (loading) {
|
|
2432
|
-
return /* @__PURE__ */
|
|
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 }));
|
|
2433
2862
|
}
|
|
2434
2863
|
if (!teams.length) {
|
|
2435
|
-
return /* @__PURE__ */
|
|
2864
|
+
return /* @__PURE__ */ import_react7.default.createElement(SectionCard, { title: "Teams" }, /* @__PURE__ */ import_react7.default.createElement(
|
|
2436
2865
|
EmptyState,
|
|
2437
2866
|
{
|
|
2438
2867
|
message: "You're not a member of any LiteLLM team yet.",
|
|
@@ -2440,13 +2869,15 @@ var init_TeamUsage = __esm({
|
|
|
2440
2869
|
}
|
|
2441
2870
|
));
|
|
2442
2871
|
}
|
|
2443
|
-
return /* @__PURE__ */
|
|
2872
|
+
return /* @__PURE__ */ import_react7.default.createElement(SectionCard, { title: "Teams", subtitle: `${teams.length} team${teams.length === 1 ? "" : "s"}` }, /* @__PURE__ */ import_react7.default.createElement(import_Box7.default, { sx: { display: "flex", flexDirection: "column", gap: 2 } }, teams.map((team) => /* @__PURE__ */ import_react7.default.createElement(
|
|
2444
2873
|
TeamCard,
|
|
2445
2874
|
{
|
|
2446
2875
|
key: team.team_id,
|
|
2447
2876
|
team,
|
|
2448
2877
|
usage: getTeamUsage(team.team_id),
|
|
2449
|
-
usageLoading: getTeamUsageLoading(team.team_id)
|
|
2878
|
+
usageLoading: getTeamUsageLoading(team.team_id),
|
|
2879
|
+
canManage,
|
|
2880
|
+
onEditTeam
|
|
2450
2881
|
}
|
|
2451
2882
|
))));
|
|
2452
2883
|
};
|
|
@@ -2471,43 +2902,43 @@ function fmtTokens(n) {
|
|
|
2471
2902
|
if (n >= 1e3) return `${(n / 1e3).toFixed(0)}K`;
|
|
2472
2903
|
return String(n);
|
|
2473
2904
|
}
|
|
2474
|
-
var
|
|
2905
|
+
var import_react8, import_Box8, import_Table4, import_TableBody4, import_TableCell4, import_TableContainer4, import_TableHead4, import_TableRow4, import_Typography8, import_MenuItem4, import_Select, import_FormControl, import_InputLabel, import_ContentCopy, import_Tooltip, import_Snackbar, import_Alert3, import_styles5, ALL_PROXY_MODELS2, ModelsTable;
|
|
2475
2906
|
var init_ModelsTable = __esm({
|
|
2476
2907
|
"src/components/ModelsTable.tsx"() {
|
|
2477
2908
|
"use strict";
|
|
2478
|
-
|
|
2479
|
-
|
|
2909
|
+
import_react8 = __toESM(require("react"));
|
|
2910
|
+
import_Box8 = __toESM(require("@mui/material/Box"));
|
|
2480
2911
|
import_Table4 = __toESM(require("@mui/material/Table"));
|
|
2481
2912
|
import_TableBody4 = __toESM(require("@mui/material/TableBody"));
|
|
2482
2913
|
import_TableCell4 = __toESM(require("@mui/material/TableCell"));
|
|
2483
2914
|
import_TableContainer4 = __toESM(require("@mui/material/TableContainer"));
|
|
2484
2915
|
import_TableHead4 = __toESM(require("@mui/material/TableHead"));
|
|
2485
2916
|
import_TableRow4 = __toESM(require("@mui/material/TableRow"));
|
|
2486
|
-
|
|
2487
|
-
|
|
2917
|
+
import_Typography8 = __toESM(require("@mui/material/Typography"));
|
|
2918
|
+
import_MenuItem4 = __toESM(require("@mui/material/MenuItem"));
|
|
2488
2919
|
import_Select = __toESM(require("@mui/material/Select"));
|
|
2489
2920
|
import_FormControl = __toESM(require("@mui/material/FormControl"));
|
|
2490
2921
|
import_InputLabel = __toESM(require("@mui/material/InputLabel"));
|
|
2491
2922
|
import_ContentCopy = __toESM(require("@mui/icons-material/ContentCopy"));
|
|
2492
2923
|
import_Tooltip = __toESM(require("@mui/material/Tooltip"));
|
|
2493
2924
|
import_Snackbar = __toESM(require("@mui/material/Snackbar"));
|
|
2494
|
-
|
|
2925
|
+
import_Alert3 = __toESM(require("@mui/material/Alert"));
|
|
2495
2926
|
init_ui();
|
|
2496
2927
|
import_styles5 = require("@mui/material/styles");
|
|
2497
2928
|
ALL_PROXY_MODELS2 = "all-proxy-models";
|
|
2498
2929
|
ModelsTable = ({ allModels, teams, loading }) => {
|
|
2499
2930
|
const theme = (0, import_styles5.useTheme)();
|
|
2500
|
-
const [selectedTeamId, setSelectedTeamId] = (0,
|
|
2501
|
-
const [copiedSnackbar, setCopiedSnackbar] = (0,
|
|
2502
|
-
const selectedTeam = (0,
|
|
2931
|
+
const [selectedTeamId, setSelectedTeamId] = (0, import_react8.useState)("");
|
|
2932
|
+
const [copiedSnackbar, setCopiedSnackbar] = (0, import_react8.useState)(false);
|
|
2933
|
+
const selectedTeam = (0, import_react8.useMemo)(
|
|
2503
2934
|
() => teams.find((t) => t.team_id === selectedTeamId) ?? null,
|
|
2504
2935
|
[teams, selectedTeamId]
|
|
2505
2936
|
);
|
|
2506
|
-
const filteredModels = (0,
|
|
2937
|
+
const filteredModels = (0, import_react8.useMemo)(() => {
|
|
2507
2938
|
if (!selectedTeam) return [];
|
|
2508
2939
|
return allModels.filter((model) => isModelAllowedByTeam2(model, selectedTeam.models));
|
|
2509
2940
|
}, [allModels, selectedTeam]);
|
|
2510
|
-
const handleCopyModelId = (0,
|
|
2941
|
+
const handleCopyModelId = (0, import_react8.useCallback)((modelId) => {
|
|
2511
2942
|
navigator.clipboard.writeText(modelId).then(() => setCopiedSnackbar(true));
|
|
2512
2943
|
}, []);
|
|
2513
2944
|
const modeColor = (mode) => {
|
|
@@ -2524,13 +2955,13 @@ var init_ModelsTable = __esm({
|
|
|
2524
2955
|
return theme.palette.info.main;
|
|
2525
2956
|
}
|
|
2526
2957
|
};
|
|
2527
|
-
return /* @__PURE__ */
|
|
2958
|
+
return /* @__PURE__ */ import_react8.default.createElement(
|
|
2528
2959
|
SectionCard,
|
|
2529
2960
|
{
|
|
2530
2961
|
title: "Available Models",
|
|
2531
2962
|
subtitle: selectedTeam ? `${filteredModels.length} model${filteredModels.length !== 1 ? "s" : ""} in ${selectedTeam.team_alias ?? selectedTeam.team_id}` : "Select a team to view its models"
|
|
2532
2963
|
},
|
|
2533
|
-
/* @__PURE__ */
|
|
2964
|
+
/* @__PURE__ */ import_react8.default.createElement(import_Box8.default, { sx: { px: 2.5, pb: 2.5, pt: 1 } }, /* @__PURE__ */ import_react8.default.createElement(import_FormControl.default, { size: "small", sx: { minWidth: 240, mb: 2 } }, /* @__PURE__ */ import_react8.default.createElement(import_InputLabel.default, { id: "team-select-label" }, "Team"), /* @__PURE__ */ import_react8.default.createElement(
|
|
2534
2965
|
import_Select.default,
|
|
2535
2966
|
{
|
|
2536
2967
|
labelId: "team-select-label",
|
|
@@ -2538,25 +2969,25 @@ var init_ModelsTable = __esm({
|
|
|
2538
2969
|
label: "Team",
|
|
2539
2970
|
onChange: (e) => setSelectedTeamId(e.target.value)
|
|
2540
2971
|
},
|
|
2541
|
-
teams.map((t) => /* @__PURE__ */
|
|
2972
|
+
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))
|
|
2542
2973
|
))),
|
|
2543
|
-
loading && /* @__PURE__ */
|
|
2544
|
-
!loading && !selectedTeamId && /* @__PURE__ */
|
|
2974
|
+
loading && /* @__PURE__ */ import_react8.default.createElement(EmptyState, { message: "Loading models\u2026" }),
|
|
2975
|
+
!loading && !selectedTeamId && /* @__PURE__ */ import_react8.default.createElement(
|
|
2545
2976
|
EmptyState,
|
|
2546
2977
|
{
|
|
2547
2978
|
message: "Select a team to see available models",
|
|
2548
2979
|
hint: "Each team may have a different set of models assigned"
|
|
2549
2980
|
}
|
|
2550
2981
|
),
|
|
2551
|
-
!loading && selectedTeamId && filteredModels.length === 0 && /* @__PURE__ */
|
|
2982
|
+
!loading && selectedTeamId && filteredModels.length === 0 && /* @__PURE__ */ import_react8.default.createElement(
|
|
2552
2983
|
EmptyState,
|
|
2553
2984
|
{
|
|
2554
2985
|
message: "No models available for this team",
|
|
2555
2986
|
hint: allModels.length === 0 ? "No models are configured in the system" : "None of the team's assigned models were found in the catalog"
|
|
2556
2987
|
}
|
|
2557
2988
|
),
|
|
2558
|
-
!loading && filteredModels.length > 0 && /* @__PURE__ */
|
|
2559
|
-
|
|
2989
|
+
!loading && filteredModels.length > 0 && /* @__PURE__ */ import_react8.default.createElement(import_TableContainer4.default, null, /* @__PURE__ */ import_react8.default.createElement(import_Table4.default, { size: "small", sx: dataTableSx }, /* @__PURE__ */ import_react8.default.createElement(import_TableHead4.default, null, /* @__PURE__ */ import_react8.default.createElement(import_TableRow4.default, null, /* @__PURE__ */ import_react8.default.createElement(import_TableCell4.default, null, "Model ID"), /* @__PURE__ */ import_react8.default.createElement(import_TableCell4.default, null, "Mode"), /* @__PURE__ */ import_react8.default.createElement(import_TableCell4.default, { align: "right" }, "Input Cost"), /* @__PURE__ */ import_react8.default.createElement(import_TableCell4.default, { align: "right" }, "Output Cost"), /* @__PURE__ */ import_react8.default.createElement(import_TableCell4.default, { align: "right" }, "Max Input"), /* @__PURE__ */ import_react8.default.createElement(import_TableCell4.default, { align: "right" }, "Max Output"), /* @__PURE__ */ import_react8.default.createElement(import_TableCell4.default, null, "Capabilities"))), /* @__PURE__ */ import_react8.default.createElement(import_TableBody4.default, null, filteredModels.map((m) => /* @__PURE__ */ import_react8.default.createElement(import_TableRow4.default, { key: m.model_name, hover: true }, /* @__PURE__ */ import_react8.default.createElement(import_TableCell4.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
|
+
import_Box8.default,
|
|
2560
2991
|
{
|
|
2561
2992
|
component: "span",
|
|
2562
2993
|
role: "button",
|
|
@@ -2566,9 +2997,9 @@ var init_ModelsTable = __esm({
|
|
|
2566
2997
|
sx: quietIconButtonSx("accent"),
|
|
2567
2998
|
style: { cursor: "pointer", display: "inline-flex" }
|
|
2568
2999
|
},
|
|
2569
|
-
/* @__PURE__ */
|
|
2570
|
-
)))), /* @__PURE__ */
|
|
2571
|
-
|
|
3000
|
+
/* @__PURE__ */ import_react8.default.createElement(import_ContentCopy.default, { sx: { fontSize: 14 } })
|
|
3001
|
+
)))), /* @__PURE__ */ import_react8.default.createElement(import_TableCell4.default, null, /* @__PURE__ */ import_react8.default.createElement(
|
|
3002
|
+
import_Box8.default,
|
|
2572
3003
|
{
|
|
2573
3004
|
component: "span",
|
|
2574
3005
|
sx: {
|
|
@@ -2584,8 +3015,8 @@ var init_ModelsTable = __esm({
|
|
|
2584
3015
|
}
|
|
2585
3016
|
},
|
|
2586
3017
|
m.mode
|
|
2587
|
-
)), /* @__PURE__ */
|
|
2588
|
-
/* @__PURE__ */
|
|
3018
|
+
)), /* @__PURE__ */ import_react8.default.createElement(import_TableCell4.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_TableCell4.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_TableCell4.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_TableCell4.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_TableCell4.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
|
+
/* @__PURE__ */ import_react8.default.createElement(
|
|
2589
3020
|
import_Snackbar.default,
|
|
2590
3021
|
{
|
|
2591
3022
|
open: copiedSnackbar,
|
|
@@ -2593,7 +3024,7 @@ var init_ModelsTable = __esm({
|
|
|
2593
3024
|
onClose: () => setCopiedSnackbar(false),
|
|
2594
3025
|
anchorOrigin: { vertical: "bottom", horizontal: "center" }
|
|
2595
3026
|
},
|
|
2596
|
-
/* @__PURE__ */
|
|
3027
|
+
/* @__PURE__ */ import_react8.default.createElement(import_Alert3.default, { severity: "success", variant: "filled", sx: { fontSize: 13 } }, "Model ID copied to clipboard")
|
|
2597
3028
|
)
|
|
2598
3029
|
);
|
|
2599
3030
|
};
|
|
@@ -2621,10 +3052,10 @@ function formatDateTime(iso) {
|
|
|
2621
3052
|
}
|
|
2622
3053
|
function renderAuditLogBody(loading, entries) {
|
|
2623
3054
|
if (loading) {
|
|
2624
|
-
return /* @__PURE__ */
|
|
3055
|
+
return /* @__PURE__ */ import_react9.default.createElement(import_TableRow5.default, null, /* @__PURE__ */ import_react9.default.createElement(import_TableCell5.default, { colSpan: 6, sx: { py: 2 } }, /* @__PURE__ */ import_react9.default.createElement(import_Skeleton5.default, { variant: "rounded", height: 160 })));
|
|
2625
3056
|
}
|
|
2626
3057
|
if (entries.length === 0) {
|
|
2627
|
-
return /* @__PURE__ */
|
|
3058
|
+
return /* @__PURE__ */ import_react9.default.createElement(import_TableRow5.default, null, /* @__PURE__ */ import_react9.default.createElement(import_TableCell5.default, { colSpan: 6 }, /* @__PURE__ */ import_react9.default.createElement(
|
|
2628
3059
|
EmptyState,
|
|
2629
3060
|
{
|
|
2630
3061
|
message: "No audit events found",
|
|
@@ -2632,15 +3063,15 @@ function renderAuditLogBody(loading, entries) {
|
|
|
2632
3063
|
}
|
|
2633
3064
|
)));
|
|
2634
3065
|
}
|
|
2635
|
-
return entries.map((entry) => /* @__PURE__ */
|
|
3066
|
+
return entries.map((entry) => /* @__PURE__ */ import_react9.default.createElement(DetailRow, { key: entry.id, entry }));
|
|
2636
3067
|
}
|
|
2637
|
-
var
|
|
3068
|
+
var import_react9, import_Box9, import_Typography9, import_Table5, import_TableBody5, import_TableCell5, import_TableContainer5, import_TableHead5, import_TableRow5, import_TablePagination, import_TextField5, import_MenuItem5, import_Skeleton5, import_Collapse2, import_IconButton5, import_Alert4, import_styles6, import_icons_material6, import_react_use, ACTION_TONES, TABLE_LABELS, DetailRow, AuditLog;
|
|
2638
3069
|
var init_AuditLog = __esm({
|
|
2639
3070
|
"src/components/AuditLog.tsx"() {
|
|
2640
3071
|
"use strict";
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
3072
|
+
import_react9 = __toESM(require("react"));
|
|
3073
|
+
import_Box9 = __toESM(require("@mui/material/Box"));
|
|
3074
|
+
import_Typography9 = __toESM(require("@mui/material/Typography"));
|
|
2644
3075
|
import_Table5 = __toESM(require("@mui/material/Table"));
|
|
2645
3076
|
import_TableBody5 = __toESM(require("@mui/material/TableBody"));
|
|
2646
3077
|
import_TableCell5 = __toESM(require("@mui/material/TableCell"));
|
|
@@ -2648,14 +3079,14 @@ var init_AuditLog = __esm({
|
|
|
2648
3079
|
import_TableHead5 = __toESM(require("@mui/material/TableHead"));
|
|
2649
3080
|
import_TableRow5 = __toESM(require("@mui/material/TableRow"));
|
|
2650
3081
|
import_TablePagination = __toESM(require("@mui/material/TablePagination"));
|
|
2651
|
-
|
|
2652
|
-
|
|
3082
|
+
import_TextField5 = __toESM(require("@mui/material/TextField"));
|
|
3083
|
+
import_MenuItem5 = __toESM(require("@mui/material/MenuItem"));
|
|
2653
3084
|
import_Skeleton5 = __toESM(require("@mui/material/Skeleton"));
|
|
2654
3085
|
import_Collapse2 = __toESM(require("@mui/material/Collapse"));
|
|
2655
|
-
|
|
2656
|
-
|
|
3086
|
+
import_IconButton5 = __toESM(require("@mui/material/IconButton"));
|
|
3087
|
+
import_Alert4 = __toESM(require("@mui/material/Alert"));
|
|
2657
3088
|
import_styles6 = require("@mui/material/styles");
|
|
2658
|
-
|
|
3089
|
+
import_icons_material6 = require("@mui/icons-material");
|
|
2659
3090
|
import_react_use = require("react-use");
|
|
2660
3091
|
init_ui();
|
|
2661
3092
|
ACTION_TONES = {
|
|
@@ -2668,13 +3099,15 @@ var init_AuditLog = __esm({
|
|
|
2668
3099
|
TABLE_LABELS = {
|
|
2669
3100
|
LiteLLM_VerificationToken: "Key",
|
|
2670
3101
|
LiteLLM_TeamTable: "Team",
|
|
3102
|
+
LiteLLM_TeamMembership: "Team member",
|
|
3103
|
+
LiteLLM_ObjectPermissionTable: "Team access (KB / MCP)",
|
|
2671
3104
|
LiteLLM_UserTable: "User"
|
|
2672
3105
|
};
|
|
2673
3106
|
DetailRow = ({ entry }) => {
|
|
2674
|
-
const [open, setOpen] = (0,
|
|
3107
|
+
const [open, setOpen] = (0, import_react9.useState)(false);
|
|
2675
3108
|
const hasDetail = entry.before_value || entry.updated_values;
|
|
2676
|
-
return /* @__PURE__ */
|
|
2677
|
-
|
|
3109
|
+
return /* @__PURE__ */ import_react9.default.createElement(import_react9.default.Fragment, null, /* @__PURE__ */ import_react9.default.createElement(import_TableRow5.default, null, /* @__PURE__ */ import_react9.default.createElement(import_TableCell5.default, { sx: { width: 40, pr: 0 } }, hasDetail && /* @__PURE__ */ import_react9.default.createElement(
|
|
3110
|
+
import_IconButton5.default,
|
|
2678
3111
|
{
|
|
2679
3112
|
size: "small",
|
|
2680
3113
|
onClick: () => setOpen((o) => !o),
|
|
@@ -2686,9 +3119,9 @@ var init_AuditLog = __esm({
|
|
|
2686
3119
|
transition: theme.transitions.create("transform")
|
|
2687
3120
|
})
|
|
2688
3121
|
},
|
|
2689
|
-
/* @__PURE__ */
|
|
2690
|
-
)), /* @__PURE__ */
|
|
2691
|
-
|
|
3122
|
+
/* @__PURE__ */ import_react9.default.createElement(import_icons_material6.KeyboardArrowDown, { fontSize: "small" })
|
|
3123
|
+
)), /* @__PURE__ */ import_react9.default.createElement(import_TableCell5.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_TableCell5.default, null, entry.action && /* @__PURE__ */ import_react9.default.createElement(StatusPill, { label: entry.action, tone: actionTone(entry.action) })), /* @__PURE__ */ import_react9.default.createElement(import_TableCell5.default, null, /* @__PURE__ */ import_react9.default.createElement(import_Typography9.default, { variant: "body2" }, prettyTableName(entry.table_name))), /* @__PURE__ */ import_react9.default.createElement(import_TableCell5.default, null, /* @__PURE__ */ import_react9.default.createElement(
|
|
3124
|
+
import_Typography9.default,
|
|
2692
3125
|
{
|
|
2693
3126
|
variant: "body2",
|
|
2694
3127
|
component: "code",
|
|
@@ -2697,8 +3130,8 @@ var init_AuditLog = __esm({
|
|
|
2697
3130
|
sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 12 }
|
|
2698
3131
|
},
|
|
2699
3132
|
entry.object_id ? entry.object_id.slice(0, 20) + (entry.object_id.length > 20 ? "\u2026" : "") : "\u2014"
|
|
2700
|
-
)), /* @__PURE__ */
|
|
2701
|
-
|
|
3133
|
+
)), /* @__PURE__ */ import_react9.default.createElement(import_TableCell5.default, null, entry.changed_by ?? "\u2014")), hasDetail && /* @__PURE__ */ import_react9.default.createElement(import_TableRow5.default, { sx: { "&&:hover": { bgcolor: "transparent" } } }, /* @__PURE__ */ import_react9.default.createElement(import_TableCell5.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
|
+
import_Box9.default,
|
|
2702
3135
|
{
|
|
2703
3136
|
display: "flex",
|
|
2704
3137
|
gap: 2,
|
|
@@ -2710,8 +3143,8 @@ var init_AuditLog = __esm({
|
|
|
2710
3143
|
bgcolor: (0, import_styles6.alpha)(theme.palette.text.primary, theme.palette.mode === "dark" ? 0.05 : 0.03)
|
|
2711
3144
|
})
|
|
2712
3145
|
},
|
|
2713
|
-
entry.before_value && /* @__PURE__ */
|
|
2714
|
-
|
|
3146
|
+
entry.before_value && /* @__PURE__ */ import_react9.default.createElement(import_Box9.default, { flex: 1, minWidth: 200 }, /* @__PURE__ */ import_react9.default.createElement(
|
|
3147
|
+
import_Typography9.default,
|
|
2715
3148
|
{
|
|
2716
3149
|
variant: "caption",
|
|
2717
3150
|
color: "text.secondary",
|
|
@@ -2720,9 +3153,9 @@ var init_AuditLog = __esm({
|
|
|
2720
3153
|
sx: { fontSize: 10.5, fontWeight: 700, letterSpacing: "0.07em", textTransform: "uppercase" }
|
|
2721
3154
|
},
|
|
2722
3155
|
"Before"
|
|
2723
|
-
), /* @__PURE__ */
|
|
2724
|
-
entry.updated_values && /* @__PURE__ */
|
|
2725
|
-
|
|
3156
|
+
), /* @__PURE__ */ import_react9.default.createElement(import_Typography9.default, { component: "pre", variant: "caption", sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", whiteSpace: "pre-wrap", wordBreak: "break-all", m: 0 } }, JSON.stringify(entry.before_value, null, 2))),
|
|
3157
|
+
entry.updated_values && /* @__PURE__ */ import_react9.default.createElement(import_Box9.default, { flex: 1, minWidth: 200 }, /* @__PURE__ */ import_react9.default.createElement(
|
|
3158
|
+
import_Typography9.default,
|
|
2726
3159
|
{
|
|
2727
3160
|
variant: "caption",
|
|
2728
3161
|
color: "text.secondary",
|
|
@@ -2731,14 +3164,14 @@ var init_AuditLog = __esm({
|
|
|
2731
3164
|
sx: { fontSize: 10.5, fontWeight: 700, letterSpacing: "0.07em", textTransform: "uppercase" }
|
|
2732
3165
|
},
|
|
2733
3166
|
"After"
|
|
2734
|
-
), /* @__PURE__ */
|
|
3167
|
+
), /* @__PURE__ */ import_react9.default.createElement(import_Typography9.default, { component: "pre", variant: "caption", sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", whiteSpace: "pre-wrap", wordBreak: "break-all", m: 0 } }, JSON.stringify(entry.updated_values, null, 2)))
|
|
2735
3168
|
)))));
|
|
2736
3169
|
};
|
|
2737
3170
|
AuditLog = ({ api }) => {
|
|
2738
|
-
const [page, setPage] = (0,
|
|
2739
|
-
const [pageSize, setPageSize] = (0,
|
|
2740
|
-
const [filters, setFilters] = (0,
|
|
2741
|
-
const fetchParams = (0,
|
|
3171
|
+
const [page, setPage] = (0, import_react9.useState)(0);
|
|
3172
|
+
const [pageSize, setPageSize] = (0, import_react9.useState)(25);
|
|
3173
|
+
const [filters, setFilters] = (0, import_react9.useState)({});
|
|
3174
|
+
const fetchParams = (0, import_react9.useCallback)(
|
|
2742
3175
|
() => ({ page: page + 1, page_size: pageSize, ...filters }),
|
|
2743
3176
|
[page, pageSize, filters]
|
|
2744
3177
|
)();
|
|
@@ -2748,14 +3181,14 @@ var init_AuditLog = __esm({
|
|
|
2748
3181
|
);
|
|
2749
3182
|
const entries = value?.audit_logs ?? [];
|
|
2750
3183
|
const total = value?.total ?? 0;
|
|
2751
|
-
return /* @__PURE__ */
|
|
3184
|
+
return /* @__PURE__ */ import_react9.default.createElement(
|
|
2752
3185
|
SectionCard,
|
|
2753
3186
|
{
|
|
2754
3187
|
title: "Audit Log",
|
|
2755
3188
|
subtitle: loading ? "Loading\u2026" : `${total.toLocaleString()} event${total === 1 ? "" : "s"}`,
|
|
2756
3189
|
flush: true,
|
|
2757
|
-
actions: /* @__PURE__ */
|
|
2758
|
-
|
|
3190
|
+
actions: /* @__PURE__ */ import_react9.default.createElement(import_react9.default.Fragment, null, /* @__PURE__ */ import_react9.default.createElement(
|
|
3191
|
+
import_TextField5.default,
|
|
2759
3192
|
{
|
|
2760
3193
|
size: "small",
|
|
2761
3194
|
label: "Action",
|
|
@@ -2767,13 +3200,13 @@ var init_AuditLog = __esm({
|
|
|
2767
3200
|
},
|
|
2768
3201
|
sx: { minWidth: 150 }
|
|
2769
3202
|
},
|
|
2770
|
-
/* @__PURE__ */
|
|
2771
|
-
/* @__PURE__ */
|
|
2772
|
-
/* @__PURE__ */
|
|
2773
|
-
/* @__PURE__ */
|
|
2774
|
-
/* @__PURE__ */
|
|
2775
|
-
), /* @__PURE__ */
|
|
2776
|
-
|
|
3203
|
+
/* @__PURE__ */ import_react9.default.createElement(import_MenuItem5.default, { value: "" }, "All actions"),
|
|
3204
|
+
/* @__PURE__ */ import_react9.default.createElement(import_MenuItem5.default, { value: "created" }, "Created"),
|
|
3205
|
+
/* @__PURE__ */ import_react9.default.createElement(import_MenuItem5.default, { value: "updated" }, "Updated"),
|
|
3206
|
+
/* @__PURE__ */ import_react9.default.createElement(import_MenuItem5.default, { value: "deleted" }, "Deleted"),
|
|
3207
|
+
/* @__PURE__ */ import_react9.default.createElement(import_MenuItem5.default, { value: "blocked" }, "Blocked")
|
|
3208
|
+
), /* @__PURE__ */ import_react9.default.createElement(
|
|
3209
|
+
import_TextField5.default,
|
|
2777
3210
|
{
|
|
2778
3211
|
size: "small",
|
|
2779
3212
|
label: "Table",
|
|
@@ -2785,12 +3218,14 @@ var init_AuditLog = __esm({
|
|
|
2785
3218
|
},
|
|
2786
3219
|
sx: { minWidth: 150 }
|
|
2787
3220
|
},
|
|
2788
|
-
/* @__PURE__ */
|
|
2789
|
-
/* @__PURE__ */
|
|
2790
|
-
/* @__PURE__ */
|
|
2791
|
-
/* @__PURE__ */
|
|
2792
|
-
|
|
2793
|
-
|
|
3221
|
+
/* @__PURE__ */ import_react9.default.createElement(import_MenuItem5.default, { value: "" }, "All tables"),
|
|
3222
|
+
/* @__PURE__ */ import_react9.default.createElement(import_MenuItem5.default, { value: "LiteLLM_VerificationToken" }, "Key"),
|
|
3223
|
+
/* @__PURE__ */ import_react9.default.createElement(import_MenuItem5.default, { value: "LiteLLM_TeamTable" }, "Team"),
|
|
3224
|
+
/* @__PURE__ */ import_react9.default.createElement(import_MenuItem5.default, { value: "LiteLLM_TeamMembership" }, "Team member"),
|
|
3225
|
+
/* @__PURE__ */ import_react9.default.createElement(import_MenuItem5.default, { value: "LiteLLM_ObjectPermissionTable" }, "Team access (KB / MCP)"),
|
|
3226
|
+
/* @__PURE__ */ import_react9.default.createElement(import_MenuItem5.default, { value: "LiteLLM_UserTable" }, "User")
|
|
3227
|
+
), /* @__PURE__ */ import_react9.default.createElement(
|
|
3228
|
+
import_TextField5.default,
|
|
2794
3229
|
{
|
|
2795
3230
|
size: "small",
|
|
2796
3231
|
label: "Changed by",
|
|
@@ -2803,9 +3238,9 @@ var init_AuditLog = __esm({
|
|
|
2803
3238
|
}
|
|
2804
3239
|
))
|
|
2805
3240
|
},
|
|
2806
|
-
error && /* @__PURE__ */
|
|
2807
|
-
/* @__PURE__ */
|
|
2808
|
-
/* @__PURE__ */
|
|
3241
|
+
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(import_TableContainer5.default, null, /* @__PURE__ */ import_react9.default.createElement(import_Table5.default, { size: "small", sx: dataTableSx }, /* @__PURE__ */ import_react9.default.createElement(import_TableHead5.default, null, /* @__PURE__ */ import_react9.default.createElement(import_TableRow5.default, null, /* @__PURE__ */ import_react9.default.createElement(import_TableCell5.default, { sx: { width: 40 } }), /* @__PURE__ */ import_react9.default.createElement(import_TableCell5.default, null, "Time"), /* @__PURE__ */ import_react9.default.createElement(import_TableCell5.default, null, "Action"), /* @__PURE__ */ import_react9.default.createElement(import_TableCell5.default, null, "Table"), /* @__PURE__ */ import_react9.default.createElement(import_TableCell5.default, null, "Object ID"), /* @__PURE__ */ import_react9.default.createElement(import_TableCell5.default, null, "Changed By"))), /* @__PURE__ */ import_react9.default.createElement(import_TableBody5.default, null, renderAuditLogBody(loading, entries)))),
|
|
3243
|
+
/* @__PURE__ */ import_react9.default.createElement(
|
|
2809
3244
|
import_TablePagination.default,
|
|
2810
3245
|
{
|
|
2811
3246
|
component: "div",
|
|
@@ -2826,6 +3261,35 @@ var init_AuditLog = __esm({
|
|
|
2826
3261
|
}
|
|
2827
3262
|
});
|
|
2828
3263
|
|
|
3264
|
+
// src/permissions.ts
|
|
3265
|
+
var import_plugin_permission_common, litellmTeamCreatePermission, litellmTeamManagePermission, litellmTeamMembersManagePermission, litellmTeamKnowledgebaseManagePermission, litellmTeamMcpManagePermission;
|
|
3266
|
+
var init_permissions = __esm({
|
|
3267
|
+
"src/permissions.ts"() {
|
|
3268
|
+
"use strict";
|
|
3269
|
+
import_plugin_permission_common = require("@backstage/plugin-permission-common");
|
|
3270
|
+
litellmTeamCreatePermission = (0, import_plugin_permission_common.createPermission)({
|
|
3271
|
+
name: "litellm.team.create",
|
|
3272
|
+
attributes: { action: "create" }
|
|
3273
|
+
});
|
|
3274
|
+
litellmTeamManagePermission = (0, import_plugin_permission_common.createPermission)({
|
|
3275
|
+
name: "litellm.team.manage",
|
|
3276
|
+
attributes: { action: "update" }
|
|
3277
|
+
});
|
|
3278
|
+
litellmTeamMembersManagePermission = (0, import_plugin_permission_common.createPermission)({
|
|
3279
|
+
name: "litellm.team.members.manage",
|
|
3280
|
+
attributes: { action: "update" }
|
|
3281
|
+
});
|
|
3282
|
+
litellmTeamKnowledgebaseManagePermission = (0, import_plugin_permission_common.createPermission)({
|
|
3283
|
+
name: "litellm.team.knowledgebase.manage",
|
|
3284
|
+
attributes: { action: "update" }
|
|
3285
|
+
});
|
|
3286
|
+
litellmTeamMcpManagePermission = (0, import_plugin_permission_common.createPermission)({
|
|
3287
|
+
name: "litellm.team.mcp.manage",
|
|
3288
|
+
attributes: { action: "update" }
|
|
3289
|
+
});
|
|
3290
|
+
}
|
|
3291
|
+
});
|
|
3292
|
+
|
|
2829
3293
|
// src/components/LiteLLMPage.tsx
|
|
2830
3294
|
var LiteLLMPage_exports = {};
|
|
2831
3295
|
__export(LiteLLMPage_exports, {
|
|
@@ -2845,45 +3309,50 @@ function initDateRange() {
|
|
|
2845
3309
|
else start.setDate(start.getDate() - 7);
|
|
2846
3310
|
return { start, end };
|
|
2847
3311
|
}
|
|
2848
|
-
var
|
|
3312
|
+
var import_react10, import_Box10, import_Snackbar2, import_Alert5, import_CircularProgress4, import_Typography10, import_Paper5, import_Tabs2, import_Tab2, import_Button6, import_react_use2, import_core_plugin_api2, import_plugin_permission_react, PERIOD_LS_KEY2, LiteLLMPage;
|
|
2849
3313
|
var init_LiteLLMPage = __esm({
|
|
2850
3314
|
"src/components/LiteLLMPage.tsx"() {
|
|
2851
3315
|
"use strict";
|
|
2852
|
-
|
|
2853
|
-
|
|
3316
|
+
import_react10 = __toESM(require("react"));
|
|
3317
|
+
import_Box10 = __toESM(require("@mui/material/Box"));
|
|
2854
3318
|
import_Snackbar2 = __toESM(require("@mui/material/Snackbar"));
|
|
2855
|
-
|
|
3319
|
+
import_Alert5 = __toESM(require("@mui/material/Alert"));
|
|
2856
3320
|
import_CircularProgress4 = __toESM(require("@mui/material/CircularProgress"));
|
|
2857
|
-
|
|
3321
|
+
import_Typography10 = __toESM(require("@mui/material/Typography"));
|
|
2858
3322
|
import_Paper5 = __toESM(require("@mui/material/Paper"));
|
|
2859
3323
|
import_Tabs2 = __toESM(require("@mui/material/Tabs"));
|
|
2860
3324
|
import_Tab2 = __toESM(require("@mui/material/Tab"));
|
|
3325
|
+
import_Button6 = __toESM(require("@mui/material/Button"));
|
|
2861
3326
|
import_react_use2 = require("react-use");
|
|
2862
3327
|
import_core_plugin_api2 = require("@backstage/core-plugin-api");
|
|
3328
|
+
import_plugin_permission_react = require("@backstage/plugin-permission-react");
|
|
2863
3329
|
init_DashboardHeader();
|
|
2864
3330
|
init_KeysTable();
|
|
2865
3331
|
init_GenerateKeyDialog();
|
|
3332
|
+
init_ManageTeamDialog();
|
|
2866
3333
|
init_UsageStats();
|
|
2867
3334
|
init_TeamUsage();
|
|
2868
3335
|
init_ModelsTable();
|
|
2869
3336
|
init_AuditLog();
|
|
2870
3337
|
init_api();
|
|
3338
|
+
init_permissions();
|
|
2871
3339
|
PERIOD_LS_KEY2 = "litellm_usage_period";
|
|
2872
3340
|
LiteLLMPage = () => {
|
|
2873
3341
|
const api = (0, import_core_plugin_api2.useApi)(liteLlmApiRef);
|
|
2874
|
-
const [dateRange, setDateRange] = (0,
|
|
2875
|
-
const [currentPreset, setCurrentPreset] = (0,
|
|
3342
|
+
const [dateRange, setDateRange] = (0, import_react10.useState)(initDateRange);
|
|
3343
|
+
const [currentPreset, setCurrentPreset] = (0, import_react10.useState)(() => {
|
|
2876
3344
|
try {
|
|
2877
3345
|
return localStorage.getItem(PERIOD_LS_KEY2) ?? "7d";
|
|
2878
3346
|
} catch {
|
|
2879
3347
|
return "7d";
|
|
2880
3348
|
}
|
|
2881
3349
|
});
|
|
2882
|
-
const [activeTab, setActiveTab] = (0,
|
|
2883
|
-
const [snackbar, setSnackbar] = (0,
|
|
2884
|
-
const [generateDialogOpen, setGenerateDialogOpen] = (0,
|
|
2885
|
-
const [
|
|
2886
|
-
const [
|
|
3350
|
+
const [activeTab, setActiveTab] = (0, import_react10.useState)("overview");
|
|
3351
|
+
const [snackbar, setSnackbar] = (0, import_react10.useState)(null);
|
|
3352
|
+
const [generateDialogOpen, setGenerateDialogOpen] = (0, import_react10.useState)(false);
|
|
3353
|
+
const [manageTeam, setManageTeam] = (0, import_react10.useState)(null);
|
|
3354
|
+
const [teamUsageCache, setTeamUsageCache] = (0, import_react10.useState)({});
|
|
3355
|
+
const [teamUsageLoading, setTeamUsageLoading] = (0, import_react10.useState)({});
|
|
2887
3356
|
const { value: userInfo, loading: userLoading, error: userError } = (0, import_react_use2.useAsync)(
|
|
2888
3357
|
() => api.getUserInfo(),
|
|
2889
3358
|
[api]
|
|
@@ -2903,12 +3372,31 @@ var init_LiteLLMPage = __esm({
|
|
|
2903
3372
|
() => api.listModels().catch(() => []),
|
|
2904
3373
|
[api]
|
|
2905
3374
|
);
|
|
2906
|
-
const { value: allTeams, loading: teamsLoading } = (0, import_react_use2.
|
|
3375
|
+
const { value: allTeams, loading: teamsLoading, retry: refreshTeams } = (0, import_react_use2.useAsyncRetry)(
|
|
2907
3376
|
() => api.getTeams().catch(() => []),
|
|
2908
3377
|
[api]
|
|
2909
3378
|
);
|
|
2910
3379
|
const { value: liteLlmConfig } = (0, import_react_use2.useAsync)(() => api.getConfig(), [api]);
|
|
2911
|
-
const
|
|
3380
|
+
const { value: managedTeams } = (0, import_react_use2.useAsync)(
|
|
3381
|
+
async () => liteLlmConfig?.teamManagement?.enabled ? api.getManagedTeams().catch(() => []) : [],
|
|
3382
|
+
[api, liteLlmConfig]
|
|
3383
|
+
);
|
|
3384
|
+
const { allowed: canCreateTeam } = (0, import_plugin_permission_react.usePermission)({ permission: litellmTeamCreatePermission });
|
|
3385
|
+
const { allowed: canManageTeam } = (0, import_plugin_permission_react.usePermission)({ permission: litellmTeamManagePermission });
|
|
3386
|
+
const { allowed: canManageMembers } = (0, import_plugin_permission_react.usePermission)({ permission: litellmTeamMembersManagePermission });
|
|
3387
|
+
const { allowed: canManageKnowledgeBases } = (0, import_plugin_permission_react.usePermission)({ permission: litellmTeamKnowledgebaseManagePermission });
|
|
3388
|
+
const { allowed: canManageMcpServers } = (0, import_plugin_permission_react.usePermission)({ permission: litellmTeamMcpManagePermission });
|
|
3389
|
+
const teamMgmtEnabled = liteLlmConfig?.teamManagement?.enabled ?? false;
|
|
3390
|
+
const objectPermsEnabled = liteLlmConfig?.teamManagement?.objectPermissionsEnabled ?? false;
|
|
3391
|
+
const { value: vectorStores } = (0, import_react_use2.useAsync)(
|
|
3392
|
+
async () => objectPermsEnabled ? api.getVectorStores().catch(() => []) : [],
|
|
3393
|
+
[api, objectPermsEnabled]
|
|
3394
|
+
);
|
|
3395
|
+
const { value: mcpServers } = (0, import_react_use2.useAsync)(
|
|
3396
|
+
async () => objectPermsEnabled ? api.getMcpServers().catch(() => []) : [],
|
|
3397
|
+
[api, objectPermsEnabled]
|
|
3398
|
+
);
|
|
3399
|
+
const teams = (0, import_react10.useMemo)(() => {
|
|
2912
3400
|
if (!allTeams?.length) return [];
|
|
2913
3401
|
if (!userInfo) return allTeams;
|
|
2914
3402
|
const userId = userInfo.user_id;
|
|
@@ -2925,7 +3413,7 @@ var init_LiteLLMPage = __esm({
|
|
|
2925
3413
|
const endDate = dateRange.end.toISOString().split("T")[0];
|
|
2926
3414
|
return api.getUsage(startDate, endDate);
|
|
2927
3415
|
}, [api, dateRange]);
|
|
2928
|
-
const handleDateRangeChange = (0,
|
|
3416
|
+
const handleDateRangeChange = (0, import_react10.useCallback)((range, preset) => {
|
|
2929
3417
|
setDateRange(range);
|
|
2930
3418
|
if (preset) {
|
|
2931
3419
|
setCurrentPreset(preset);
|
|
@@ -2933,7 +3421,7 @@ var init_LiteLLMPage = __esm({
|
|
|
2933
3421
|
setTeamUsageCache({});
|
|
2934
3422
|
setTeamUsageLoading({});
|
|
2935
3423
|
}, [setDateRange, setCurrentPreset]);
|
|
2936
|
-
const loadTeamUsage = (0,
|
|
3424
|
+
const loadTeamUsage = (0, import_react10.useCallback)(async (teamId) => {
|
|
2937
3425
|
if (teamUsageCache[teamId] !== void 0 || teamUsageLoading[teamId]) return;
|
|
2938
3426
|
setTeamUsageLoading((prev) => ({ ...prev, [teamId]: true }));
|
|
2939
3427
|
try {
|
|
@@ -2947,7 +3435,7 @@ var init_LiteLLMPage = __esm({
|
|
|
2947
3435
|
setTeamUsageLoading((prev) => ({ ...prev, [teamId]: false }));
|
|
2948
3436
|
}
|
|
2949
3437
|
}, [api, dateRange, teamUsageCache, teamUsageLoading]);
|
|
2950
|
-
const allowedModels = (0,
|
|
3438
|
+
const allowedModels = (0, import_react10.useMemo)(() => {
|
|
2951
3439
|
if (!allModels?.length) return [];
|
|
2952
3440
|
const userModels = userInfo?.models;
|
|
2953
3441
|
const teamModels = teams?.flatMap((t) => t.models ?? []);
|
|
@@ -2960,7 +3448,7 @@ var init_LiteLLMPage = __esm({
|
|
|
2960
3448
|
]);
|
|
2961
3449
|
return allModels.filter((m) => allowed.has(m.model_name));
|
|
2962
3450
|
}, [allModels, userInfo, teams]);
|
|
2963
|
-
const handleGenerateKey = (0,
|
|
3451
|
+
const handleGenerateKey = (0, import_react10.useCallback)(
|
|
2964
3452
|
async (request) => {
|
|
2965
3453
|
try {
|
|
2966
3454
|
const response = await api.generateKey(request);
|
|
@@ -2974,7 +3462,7 @@ var init_LiteLLMPage = __esm({
|
|
|
2974
3462
|
},
|
|
2975
3463
|
[api, refreshKeys]
|
|
2976
3464
|
);
|
|
2977
|
-
const handleUpdateKey = (0,
|
|
3465
|
+
const handleUpdateKey = (0, import_react10.useCallback)(
|
|
2978
3466
|
async (keyId, request) => {
|
|
2979
3467
|
try {
|
|
2980
3468
|
await api.updateKey(keyId, request);
|
|
@@ -2987,7 +3475,7 @@ var init_LiteLLMPage = __esm({
|
|
|
2987
3475
|
},
|
|
2988
3476
|
[api, refreshKeys]
|
|
2989
3477
|
);
|
|
2990
|
-
const handleBlockKey = (0,
|
|
3478
|
+
const handleBlockKey = (0, import_react10.useCallback)(
|
|
2991
3479
|
async (keyId) => {
|
|
2992
3480
|
try {
|
|
2993
3481
|
await api.blockKey(keyId);
|
|
@@ -2999,7 +3487,7 @@ var init_LiteLLMPage = __esm({
|
|
|
2999
3487
|
},
|
|
3000
3488
|
[api, refreshKeys]
|
|
3001
3489
|
);
|
|
3002
|
-
const handleUnblockKey = (0,
|
|
3490
|
+
const handleUnblockKey = (0, import_react10.useCallback)(
|
|
3003
3491
|
async (keyId) => {
|
|
3004
3492
|
try {
|
|
3005
3493
|
await api.unblockKey(keyId);
|
|
@@ -3011,7 +3499,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3011
3499
|
},
|
|
3012
3500
|
[api, refreshKeys]
|
|
3013
3501
|
);
|
|
3014
|
-
const handleResetKeySpend = (0,
|
|
3502
|
+
const handleResetKeySpend = (0, import_react10.useCallback)(
|
|
3015
3503
|
async (keyId) => {
|
|
3016
3504
|
try {
|
|
3017
3505
|
await api.resetKeySpend(keyId);
|
|
@@ -3023,7 +3511,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3023
3511
|
},
|
|
3024
3512
|
[api, refreshKeys]
|
|
3025
3513
|
);
|
|
3026
|
-
const handleDeleteKey = (0,
|
|
3514
|
+
const handleDeleteKey = (0, import_react10.useCallback)(
|
|
3027
3515
|
async (keyId) => {
|
|
3028
3516
|
try {
|
|
3029
3517
|
await api.deleteKey(keyId);
|
|
@@ -3040,7 +3528,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3040
3528
|
},
|
|
3041
3529
|
[api, refreshKeys]
|
|
3042
3530
|
);
|
|
3043
|
-
const handlePruneExpiredKeys = (0,
|
|
3531
|
+
const handlePruneExpiredKeys = (0, import_react10.useCallback)(
|
|
3044
3532
|
async () => {
|
|
3045
3533
|
try {
|
|
3046
3534
|
const result = await api.pruneExpiredKeys();
|
|
@@ -3055,14 +3543,14 @@ var init_LiteLLMPage = __esm({
|
|
|
3055
3543
|
);
|
|
3056
3544
|
const isInitialLoading = userLoading && !userInfo;
|
|
3057
3545
|
if (isInitialLoading) {
|
|
3058
|
-
return /* @__PURE__ */
|
|
3546
|
+
return /* @__PURE__ */ import_react10.default.createElement(import_Box10.default, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: "50vh" }, /* @__PURE__ */ import_react10.default.createElement(import_CircularProgress4.default, null));
|
|
3059
3547
|
}
|
|
3060
3548
|
if (userError || !userInfo) {
|
|
3061
3549
|
const isProvisioningEnabled = userError?.body?.provisioning === true;
|
|
3062
3550
|
const hint = userError?.body?.hint;
|
|
3063
|
-
return /* @__PURE__ */
|
|
3551
|
+
return /* @__PURE__ */ import_react10.default.createElement(import_Box10.default, { p: 3 }, /* @__PURE__ */ import_react10.default.createElement(import_Paper5.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.")));
|
|
3064
3552
|
}
|
|
3065
|
-
const pageTabs = /* @__PURE__ */
|
|
3553
|
+
const pageTabs = /* @__PURE__ */ import_react10.default.createElement(
|
|
3066
3554
|
import_Tabs2.default,
|
|
3067
3555
|
{
|
|
3068
3556
|
value: activeTab,
|
|
@@ -3075,13 +3563,13 @@ var init_LiteLLMPage = __esm({
|
|
|
3075
3563
|
'& [class*="MuiTab-root"]': { minHeight: 44, textTransform: "none", fontSize: 14 }
|
|
3076
3564
|
}
|
|
3077
3565
|
},
|
|
3078
|
-
/* @__PURE__ */
|
|
3079
|
-
/* @__PURE__ */
|
|
3080
|
-
/* @__PURE__ */
|
|
3081
|
-
/* @__PURE__ */
|
|
3082
|
-
userInfo.can_view_audit && /* @__PURE__ */
|
|
3566
|
+
/* @__PURE__ */ import_react10.default.createElement(import_Tab2.default, { label: "Overview", value: "overview" }),
|
|
3567
|
+
/* @__PURE__ */ import_react10.default.createElement(import_Tab2.default, { label: "Keys", value: "keys" }),
|
|
3568
|
+
/* @__PURE__ */ import_react10.default.createElement(import_Tab2.default, { label: "Teams", value: "teams" }),
|
|
3569
|
+
/* @__PURE__ */ import_react10.default.createElement(import_Tab2.default, { label: "Models", value: "models" }),
|
|
3570
|
+
userInfo.can_view_audit && /* @__PURE__ */ import_react10.default.createElement(import_Tab2.default, { label: "Audit Log", value: "audit" })
|
|
3083
3571
|
);
|
|
3084
|
-
return /* @__PURE__ */
|
|
3572
|
+
return /* @__PURE__ */ import_react10.default.createElement(import_Box10.default, { sx: { p: 3, display: "flex", flexDirection: "column", gap: 2 } }, /* @__PURE__ */ import_react10.default.createElement(
|
|
3085
3573
|
DashboardHeader,
|
|
3086
3574
|
{
|
|
3087
3575
|
userInfo,
|
|
@@ -3091,7 +3579,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3091
3579
|
onGenerateKeyClick: () => setGenerateDialogOpen(true),
|
|
3092
3580
|
tabs: pageTabs
|
|
3093
3581
|
}
|
|
3094
|
-
), activeTab === "overview" && /* @__PURE__ */
|
|
3582
|
+
), activeTab === "overview" && /* @__PURE__ */ import_react10.default.createElement(
|
|
3095
3583
|
UsageStats,
|
|
3096
3584
|
{
|
|
3097
3585
|
usage: usage ?? null,
|
|
@@ -3102,7 +3590,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3102
3590
|
loading: usageLoading,
|
|
3103
3591
|
userInfo
|
|
3104
3592
|
}
|
|
3105
|
-
), activeTab === "keys" && /* @__PURE__ */
|
|
3593
|
+
), activeTab === "keys" && /* @__PURE__ */ import_react10.default.createElement(
|
|
3106
3594
|
KeysTable,
|
|
3107
3595
|
{
|
|
3108
3596
|
keys: keys ?? [],
|
|
@@ -3116,25 +3604,40 @@ var init_LiteLLMPage = __esm({
|
|
|
3116
3604
|
onDeleteKey: handleDeleteKey,
|
|
3117
3605
|
onPruneExpiredKeys: handlePruneExpiredKeys
|
|
3118
3606
|
}
|
|
3119
|
-
), activeTab === "teams" &&
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3607
|
+
), activeTab === "teams" && (() => {
|
|
3608
|
+
const teamsById = /* @__PURE__ */ new Map();
|
|
3609
|
+
for (const t of managedTeams ?? []) teamsById.set(t.team_id, t);
|
|
3610
|
+
for (const t of teams ?? []) teamsById.set(t.team_id, t);
|
|
3611
|
+
const visibleTeams = Array.from(teamsById.values());
|
|
3612
|
+
return /* @__PURE__ */ import_react10.default.createElement(import_Box10.default, { sx: { display: "flex", flexDirection: "column", gap: 2 } }, teamMgmtEnabled && canCreateTeam && /* @__PURE__ */ import_react10.default.createElement(import_Box10.default, { sx: { display: "flex", justifyContent: "flex-end" } }, /* @__PURE__ */ import_react10.default.createElement(
|
|
3613
|
+
import_Button6.default,
|
|
3614
|
+
{
|
|
3615
|
+
variant: "contained",
|
|
3616
|
+
onClick: () => setManageTeam({ mode: "create" })
|
|
3127
3617
|
},
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3618
|
+
"Create Team"
|
|
3619
|
+
)), /* @__PURE__ */ import_react10.default.createElement(
|
|
3620
|
+
TeamUsage,
|
|
3621
|
+
{
|
|
3622
|
+
teams: visibleTeams,
|
|
3623
|
+
loading: teamsLoading,
|
|
3624
|
+
getTeamUsage: (teamId) => {
|
|
3625
|
+
if (teamUsageCache[teamId] === void 0) loadTeamUsage(teamId);
|
|
3626
|
+
return teamUsageCache[teamId] ?? null;
|
|
3627
|
+
},
|
|
3628
|
+
getTeamUsageLoading: (teamId) => teamUsageLoading[teamId] ?? false,
|
|
3629
|
+
canManage: teamMgmtEnabled && canManageTeam,
|
|
3630
|
+
onEditTeam: (t) => setManageTeam({ mode: "edit", team: t })
|
|
3631
|
+
}
|
|
3632
|
+
));
|
|
3633
|
+
})(), activeTab === "models" && /* @__PURE__ */ import_react10.default.createElement(
|
|
3131
3634
|
ModelsTable,
|
|
3132
3635
|
{
|
|
3133
3636
|
allModels: allModels ?? [],
|
|
3134
3637
|
teams: teams ?? [],
|
|
3135
3638
|
loading: modelsLoading
|
|
3136
3639
|
}
|
|
3137
|
-
), activeTab === "audit" && userInfo.can_view_audit && /* @__PURE__ */
|
|
3640
|
+
), activeTab === "audit" && userInfo.can_view_audit && /* @__PURE__ */ import_react10.default.createElement(AuditLog, { api }), /* @__PURE__ */ import_react10.default.createElement(
|
|
3138
3641
|
GenerateKeyDialog,
|
|
3139
3642
|
{
|
|
3140
3643
|
open: generateDialogOpen,
|
|
@@ -3147,7 +3650,69 @@ var init_LiteLLMPage = __esm({
|
|
|
3147
3650
|
onGenerateKey: handleGenerateKey,
|
|
3148
3651
|
onGetConfig: () => api.getConfig()
|
|
3149
3652
|
}
|
|
3150
|
-
), /* @__PURE__ */
|
|
3653
|
+
), /* @__PURE__ */ import_react10.default.createElement(
|
|
3654
|
+
ManageTeamDialog,
|
|
3655
|
+
{
|
|
3656
|
+
open: !!manageTeam,
|
|
3657
|
+
onClose: () => setManageTeam(null),
|
|
3658
|
+
mode: manageTeam?.mode ?? "create",
|
|
3659
|
+
team: manageTeam?.team,
|
|
3660
|
+
allModels: allModels ?? [],
|
|
3661
|
+
config: liteLlmConfig,
|
|
3662
|
+
onSubmit: async (payload) => {
|
|
3663
|
+
if (manageTeam?.mode === "edit" && manageTeam.team) {
|
|
3664
|
+
await api.updateTeam(manageTeam.team.team_id, payload);
|
|
3665
|
+
} else {
|
|
3666
|
+
await api.createTeam(payload);
|
|
3667
|
+
}
|
|
3668
|
+
setSnackbar({ message: "Team saved", severity: "success" });
|
|
3669
|
+
refreshTeams();
|
|
3670
|
+
},
|
|
3671
|
+
canManageMembers: teamMgmtEnabled && canManageMembers,
|
|
3672
|
+
onAddMember: async (userEntityRef, maxBudgetInTeam) => {
|
|
3673
|
+
if (!manageTeam?.team) return;
|
|
3674
|
+
const updated = await api.addTeamMember(manageTeam.team.team_id, {
|
|
3675
|
+
userEntityRef,
|
|
3676
|
+
...maxBudgetInTeam ? { maxBudgetInTeam } : {}
|
|
3677
|
+
});
|
|
3678
|
+
setManageTeam((s) => s && s.team ? { ...s, team: updated } : s);
|
|
3679
|
+
refreshTeams();
|
|
3680
|
+
},
|
|
3681
|
+
onRemoveMember: async (userEntityRef) => {
|
|
3682
|
+
if (!manageTeam?.team) return;
|
|
3683
|
+
const updated = await api.removeTeamMember(
|
|
3684
|
+
manageTeam.team.team_id,
|
|
3685
|
+
userEntityRef
|
|
3686
|
+
);
|
|
3687
|
+
setManageTeam((s) => s && s.team ? { ...s, team: updated } : s);
|
|
3688
|
+
refreshTeams();
|
|
3689
|
+
},
|
|
3690
|
+
canManageKnowledgeBases: teamMgmtEnabled && objectPermsEnabled && canManageKnowledgeBases,
|
|
3691
|
+
vectorStores: vectorStores ?? [],
|
|
3692
|
+
onSaveKnowledgeBases: async (vectorStoreIds) => {
|
|
3693
|
+
if (!manageTeam?.team) return;
|
|
3694
|
+
const updated = await api.setTeamKnowledgeBases(
|
|
3695
|
+
manageTeam.team.team_id,
|
|
3696
|
+
vectorStoreIds
|
|
3697
|
+
);
|
|
3698
|
+
setManageTeam((s) => s && s.team ? { ...s, team: updated } : s);
|
|
3699
|
+
setSnackbar({ message: "Knowledge bases updated", severity: "success" });
|
|
3700
|
+
refreshTeams();
|
|
3701
|
+
},
|
|
3702
|
+
canManageMcpServers: teamMgmtEnabled && objectPermsEnabled && canManageMcpServers,
|
|
3703
|
+
mcpServers: mcpServers ?? [],
|
|
3704
|
+
onSaveMcpServers: async (mcpServerIds) => {
|
|
3705
|
+
if (!manageTeam?.team) return;
|
|
3706
|
+
const updated = await api.setTeamMcpServers(
|
|
3707
|
+
manageTeam.team.team_id,
|
|
3708
|
+
mcpServerIds
|
|
3709
|
+
);
|
|
3710
|
+
setManageTeam((s) => s && s.team ? { ...s, team: updated } : s);
|
|
3711
|
+
setSnackbar({ message: "MCP servers updated", severity: "success" });
|
|
3712
|
+
refreshTeams();
|
|
3713
|
+
}
|
|
3714
|
+
}
|
|
3715
|
+
), /* @__PURE__ */ import_react10.default.createElement(
|
|
3151
3716
|
import_Snackbar2.default,
|
|
3152
3717
|
{
|
|
3153
3718
|
open: !!snackbar,
|
|
@@ -3155,7 +3720,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3155
3720
|
onClose: () => setSnackbar(null),
|
|
3156
3721
|
anchorOrigin: { vertical: "bottom", horizontal: "right" }
|
|
3157
3722
|
},
|
|
3158
|
-
snackbar ? /* @__PURE__ */
|
|
3723
|
+
snackbar ? /* @__PURE__ */ import_react10.default.createElement(import_Alert5.default, { severity: snackbar.severity, onClose: () => setSnackbar(null) }, snackbar.message) : void 0
|
|
3159
3724
|
));
|
|
3160
3725
|
};
|
|
3161
3726
|
}
|
|
@@ -3170,16 +3735,22 @@ __export(index_exports, {
|
|
|
3170
3735
|
LiteLLMHomeWidget: () => LiteLLMHomeWidget,
|
|
3171
3736
|
LiteLLMPage: () => LiteLLMPage,
|
|
3172
3737
|
LiteLlmApi: () => LiteLlmApi,
|
|
3738
|
+
ManageTeamDialog: () => ManageTeamDialog,
|
|
3173
3739
|
TeamUsage: () => TeamUsage,
|
|
3174
3740
|
UsageStats: () => UsageStats,
|
|
3175
3741
|
liteLlmApiRef: () => liteLlmApiRef,
|
|
3176
|
-
litellmPlugin: () => litellmPlugin
|
|
3742
|
+
litellmPlugin: () => litellmPlugin,
|
|
3743
|
+
litellmTeamCreatePermission: () => litellmTeamCreatePermission,
|
|
3744
|
+
litellmTeamKnowledgebaseManagePermission: () => litellmTeamKnowledgebaseManagePermission,
|
|
3745
|
+
litellmTeamManagePermission: () => litellmTeamManagePermission,
|
|
3746
|
+
litellmTeamMcpManagePermission: () => litellmTeamMcpManagePermission,
|
|
3747
|
+
litellmTeamMembersManagePermission: () => litellmTeamMembersManagePermission
|
|
3177
3748
|
});
|
|
3178
3749
|
module.exports = __toCommonJS(index_exports);
|
|
3179
3750
|
|
|
3180
3751
|
// src/plugin.tsx
|
|
3181
|
-
var
|
|
3182
|
-
var
|
|
3752
|
+
var import_react11 = __toESM(require("react"));
|
|
3753
|
+
var import_icons_material7 = require("@mui/icons-material");
|
|
3183
3754
|
var import_frontend_plugin_api = require("@backstage/frontend-plugin-api");
|
|
3184
3755
|
init_api();
|
|
3185
3756
|
var liteLlmApi = import_frontend_plugin_api.ApiBlueprint.make({
|
|
@@ -3193,10 +3764,10 @@ var liteLlmPage = import_frontend_plugin_api.PageBlueprint.make({
|
|
|
3193
3764
|
params: {
|
|
3194
3765
|
path: "/litellm",
|
|
3195
3766
|
title: "LiteLLM",
|
|
3196
|
-
icon: /* @__PURE__ */
|
|
3767
|
+
icon: /* @__PURE__ */ import_react11.default.createElement(import_icons_material7.TrendingUp, null),
|
|
3197
3768
|
loader: async () => {
|
|
3198
3769
|
const { LiteLLMPage: LiteLLMPage2 } = await Promise.resolve().then(() => (init_LiteLLMPage(), LiteLLMPage_exports));
|
|
3199
|
-
return /* @__PURE__ */
|
|
3770
|
+
return /* @__PURE__ */ import_react11.default.createElement(LiteLLMPage2, null);
|
|
3200
3771
|
}
|
|
3201
3772
|
}
|
|
3202
3773
|
});
|
|
@@ -3213,16 +3784,16 @@ init_UsageStats();
|
|
|
3213
3784
|
init_TeamUsage();
|
|
3214
3785
|
|
|
3215
3786
|
// src/components/LiteLLMHomeWidget.tsx
|
|
3216
|
-
var
|
|
3787
|
+
var import_react12 = __toESM(require("react"));
|
|
3217
3788
|
var import_Paper6 = __toESM(require("@mui/material/Paper"));
|
|
3218
|
-
var
|
|
3219
|
-
var
|
|
3789
|
+
var import_Box11 = __toESM(require("@mui/material/Box"));
|
|
3790
|
+
var import_Typography11 = __toESM(require("@mui/material/Typography"));
|
|
3220
3791
|
var import_FormControl2 = __toESM(require("@mui/material/FormControl"));
|
|
3221
3792
|
var import_Select2 = __toESM(require("@mui/material/Select"));
|
|
3222
|
-
var
|
|
3793
|
+
var import_MenuItem6 = __toESM(require("@mui/material/MenuItem"));
|
|
3223
3794
|
var import_Grid2 = __toESM(require("@mui/material/Grid"));
|
|
3224
3795
|
var import_CircularProgress5 = __toESM(require("@mui/material/CircularProgress"));
|
|
3225
|
-
var
|
|
3796
|
+
var import_Alert6 = __toESM(require("@mui/material/Alert"));
|
|
3226
3797
|
var import_recharts3 = require("recharts");
|
|
3227
3798
|
var import_core_plugin_api3 = require("@backstage/core-plugin-api");
|
|
3228
3799
|
init_api();
|
|
@@ -3239,19 +3810,19 @@ function presetToDateRange(preset) {
|
|
|
3239
3810
|
}
|
|
3240
3811
|
return { start, end };
|
|
3241
3812
|
}
|
|
3242
|
-
var Kpi = ({ label, value }) => /* @__PURE__ */
|
|
3813
|
+
var Kpi = ({ label, value }) => /* @__PURE__ */ import_react12.default.createElement(import_Box11.default, null, /* @__PURE__ */ import_react12.default.createElement(import_Typography11.default, { variant: "caption", color: "text.secondary", display: "block" }, label), /* @__PURE__ */ import_react12.default.createElement(import_Typography11.default, { variant: "subtitle1", fontWeight: 600 }, value));
|
|
3243
3814
|
var LiteLLMHomeWidget = ({
|
|
3244
3815
|
defaultPeriod = "7d",
|
|
3245
3816
|
title = "LiteLLM Usage"
|
|
3246
3817
|
}) => {
|
|
3247
3818
|
const api = (0, import_core_plugin_api3.useApi)(liteLlmApiRef);
|
|
3248
|
-
const [period, setPeriod] = (0,
|
|
3249
|
-
const [loading, setLoading] = (0,
|
|
3250
|
-
const [usageError, setUsageError] = (0,
|
|
3251
|
-
const [keysError, setKeysError] = (0,
|
|
3252
|
-
const [usage, setUsage] = (0,
|
|
3253
|
-
const [keys, setKeys] = (0,
|
|
3254
|
-
(0,
|
|
3819
|
+
const [period, setPeriod] = (0, import_react12.useState)(defaultPeriod);
|
|
3820
|
+
const [loading, setLoading] = (0, import_react12.useState)(true);
|
|
3821
|
+
const [usageError, setUsageError] = (0, import_react12.useState)(null);
|
|
3822
|
+
const [keysError, setKeysError] = (0, import_react12.useState)(null);
|
|
3823
|
+
const [usage, setUsage] = (0, import_react12.useState)(null);
|
|
3824
|
+
const [keys, setKeys] = (0, import_react12.useState)([]);
|
|
3825
|
+
(0, import_react12.useEffect)(() => {
|
|
3255
3826
|
let cancelled = false;
|
|
3256
3827
|
setLoading(true);
|
|
3257
3828
|
setUsageError(null);
|
|
@@ -3288,17 +3859,17 @@ var LiteLLMHomeWidget = ({
|
|
|
3288
3859
|
spend: d.spend
|
|
3289
3860
|
}));
|
|
3290
3861
|
const hasSparkline = dailyData.length > 0;
|
|
3291
|
-
return /* @__PURE__ */
|
|
3862
|
+
return /* @__PURE__ */ import_react12.default.createElement(import_Paper6.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(
|
|
3292
3863
|
import_Select2.default,
|
|
3293
3864
|
{
|
|
3294
3865
|
value: period,
|
|
3295
3866
|
onChange: (e) => setPeriod(e.target.value),
|
|
3296
3867
|
displayEmpty: true
|
|
3297
3868
|
},
|
|
3298
|
-
/* @__PURE__ */
|
|
3299
|
-
/* @__PURE__ */
|
|
3300
|
-
/* @__PURE__ */
|
|
3301
|
-
))), loading && /* @__PURE__ */
|
|
3869
|
+
/* @__PURE__ */ import_react12.default.createElement(import_MenuItem6.default, { value: "today" }, "Today"),
|
|
3870
|
+
/* @__PURE__ */ import_react12.default.createElement(import_MenuItem6.default, { value: "7d" }, "7d"),
|
|
3871
|
+
/* @__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(import_CircularProgress5.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(
|
|
3302
3873
|
import_recharts3.Area,
|
|
3303
3874
|
{
|
|
3304
3875
|
type: "monotone",
|
|
@@ -3314,5 +3885,7 @@ var LiteLLMHomeWidget = ({
|
|
|
3314
3885
|
|
|
3315
3886
|
// src/index.ts
|
|
3316
3887
|
init_GenerateKeyDialog();
|
|
3888
|
+
init_ManageTeamDialog();
|
|
3317
3889
|
init_api();
|
|
3890
|
+
init_permissions();
|
|
3318
3891
|
//# sourceMappingURL=index.cjs.js.map
|