@acarmisc/backstage-plugin-litellm 0.15.4 → 0.16.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 +805 -248
- package/dist/index.cjs.js.map +4 -4
- package/dist/index.d.ts +2 -0
- package/dist/index.esm.js +774 -217
- 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 +48 -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,21 +1941,360 @@ 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_Box5, import_Divider2, import_Typography5, import_IconButton3, import_List, import_ListItem, import_ListItemText, import_icons_material4, 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_Box5 = __toESM(require("@mui/material/Box"));
|
|
1961
|
+
import_Divider2 = __toESM(require("@mui/material/Divider"));
|
|
1962
|
+
import_Typography5 = __toESM(require("@mui/material/Typography"));
|
|
1963
|
+
import_IconButton3 = __toESM(require("@mui/material/IconButton"));
|
|
1964
|
+
import_List = __toESM(require("@mui/material/List"));
|
|
1965
|
+
import_ListItem = __toESM(require("@mui/material/ListItem"));
|
|
1966
|
+
import_ListItemText = __toESM(require("@mui/material/ListItemText"));
|
|
1967
|
+
import_icons_material4 = require("@mui/icons-material");
|
|
1968
|
+
ManageTeamDialog = ({
|
|
1969
|
+
open,
|
|
1970
|
+
onClose,
|
|
1971
|
+
mode,
|
|
1972
|
+
team,
|
|
1973
|
+
allModels,
|
|
1974
|
+
config,
|
|
1975
|
+
onSubmit,
|
|
1976
|
+
canManageMembers,
|
|
1977
|
+
onAddMember,
|
|
1978
|
+
onRemoveMember,
|
|
1979
|
+
canManageKnowledgeBases,
|
|
1980
|
+
vectorStores,
|
|
1981
|
+
onSaveKnowledgeBases,
|
|
1982
|
+
canManageMcpServers,
|
|
1983
|
+
mcpServers,
|
|
1984
|
+
onSaveMcpServers
|
|
1985
|
+
}) => {
|
|
1986
|
+
const [alias, setAlias] = (0, import_react5.useState)("");
|
|
1987
|
+
const [models, setModels] = (0, import_react5.useState)([]);
|
|
1988
|
+
const [maxBudget, setMaxBudget] = (0, import_react5.useState)("");
|
|
1989
|
+
const [unlimited, setUnlimited] = (0, import_react5.useState)(false);
|
|
1990
|
+
const [budgetDuration, setBudgetDuration] = (0, import_react5.useState)("");
|
|
1991
|
+
const [submitting, setSubmitting] = (0, import_react5.useState)(false);
|
|
1992
|
+
const [error, setError] = (0, import_react5.useState)(null);
|
|
1993
|
+
const [memberRef, setMemberRef] = (0, import_react5.useState)("");
|
|
1994
|
+
const [memberBudget, setMemberBudget] = (0, import_react5.useState)("");
|
|
1995
|
+
const [memberBusy, setMemberBusy] = (0, import_react5.useState)(false);
|
|
1996
|
+
const [memberError, setMemberError] = (0, import_react5.useState)(null);
|
|
1997
|
+
const [kbIds, setKbIds] = (0, import_react5.useState)([]);
|
|
1998
|
+
const [kbBusy, setKbBusy] = (0, import_react5.useState)(false);
|
|
1999
|
+
const [kbError, setKbError] = (0, import_react5.useState)(null);
|
|
2000
|
+
const [mcpIds, setMcpIds] = (0, import_react5.useState)([]);
|
|
2001
|
+
const [mcpBusy, setMcpBusy] = (0, import_react5.useState)(false);
|
|
2002
|
+
const [mcpError, setMcpError] = (0, import_react5.useState)(null);
|
|
2003
|
+
const allowUnlimitedBudget = config?.teamManagement?.allowUnlimitedBudget ?? false;
|
|
2004
|
+
const maxBudgetCeiling = config?.teamManagement?.maxBudgetCeiling;
|
|
2005
|
+
(0, import_react5.useEffect)(() => {
|
|
2006
|
+
if (open) {
|
|
2007
|
+
if (mode === "edit" && team) {
|
|
2008
|
+
setAlias(team.team_alias ?? "");
|
|
2009
|
+
setModels(team.models ?? []);
|
|
2010
|
+
setMaxBudget(team.max_budget ? String(team.max_budget) : "");
|
|
2011
|
+
setUnlimited(allowUnlimitedBudget && !team.max_budget);
|
|
2012
|
+
setBudgetDuration("");
|
|
2013
|
+
} else {
|
|
2014
|
+
setAlias("");
|
|
2015
|
+
setModels([]);
|
|
2016
|
+
setMaxBudget("");
|
|
2017
|
+
setUnlimited(allowUnlimitedBudget);
|
|
2018
|
+
setBudgetDuration("");
|
|
2019
|
+
}
|
|
2020
|
+
setError(null);
|
|
2021
|
+
setMemberRef("");
|
|
2022
|
+
setMemberBudget("");
|
|
2023
|
+
setMemberError(null);
|
|
2024
|
+
setKbIds(team?.object_permission?.vector_stores ?? []);
|
|
2025
|
+
setKbError(null);
|
|
2026
|
+
setMcpIds(team?.object_permission?.mcp_servers ?? []);
|
|
2027
|
+
setMcpError(null);
|
|
2028
|
+
}
|
|
2029
|
+
}, [open, mode, team, allowUnlimitedBudget]);
|
|
2030
|
+
const handleSubmit = async () => {
|
|
2031
|
+
setError(null);
|
|
2032
|
+
if (!alias.trim()) {
|
|
2033
|
+
setError("Team alias is required");
|
|
2034
|
+
return;
|
|
2035
|
+
}
|
|
2036
|
+
if (models.length === 0) {
|
|
2037
|
+
setError("At least one model is required");
|
|
2038
|
+
return;
|
|
2039
|
+
}
|
|
2040
|
+
if (!unlimited) {
|
|
2041
|
+
if (!maxBudget) {
|
|
2042
|
+
setError("Budget is required when unlimited budgets are disabled");
|
|
2043
|
+
return;
|
|
2044
|
+
}
|
|
2045
|
+
const budget = parseFloat(maxBudget);
|
|
2046
|
+
if (isNaN(budget) || budget <= 0) {
|
|
2047
|
+
setError("Budget must be a positive number");
|
|
2048
|
+
return;
|
|
2049
|
+
}
|
|
2050
|
+
if (maxBudgetCeiling !== null && maxBudgetCeiling !== void 0 && budget > maxBudgetCeiling) {
|
|
2051
|
+
setError(`Budget cannot exceed $${maxBudgetCeiling}`);
|
|
2052
|
+
return;
|
|
2053
|
+
}
|
|
2054
|
+
}
|
|
2055
|
+
try {
|
|
2056
|
+
setSubmitting(true);
|
|
2057
|
+
let payload;
|
|
2058
|
+
if (mode === "create") {
|
|
2059
|
+
payload = {
|
|
2060
|
+
team_alias: alias.trim(),
|
|
2061
|
+
models,
|
|
2062
|
+
...unlimited ? { max_budget: null } : { max_budget: parseFloat(maxBudget) },
|
|
2063
|
+
...budgetDuration && { budget_duration: budgetDuration }
|
|
2064
|
+
};
|
|
2065
|
+
} else {
|
|
2066
|
+
payload = {
|
|
2067
|
+
team_alias: alias.trim(),
|
|
2068
|
+
models,
|
|
2069
|
+
...unlimited ? { max_budget: null } : { max_budget: parseFloat(maxBudget) },
|
|
2070
|
+
...budgetDuration && { budget_duration: budgetDuration }
|
|
2071
|
+
};
|
|
2072
|
+
}
|
|
2073
|
+
await onSubmit(payload);
|
|
2074
|
+
onClose();
|
|
2075
|
+
} catch (err) {
|
|
2076
|
+
setError(err instanceof Error ? err.message : "An error occurred");
|
|
2077
|
+
} finally {
|
|
2078
|
+
setSubmitting(false);
|
|
2079
|
+
}
|
|
2080
|
+
};
|
|
2081
|
+
const handleAddMember = async () => {
|
|
2082
|
+
if (!onAddMember || !memberRef.trim()) return;
|
|
2083
|
+
setMemberError(null);
|
|
2084
|
+
const budget = memberBudget ? parseFloat(memberBudget) : void 0;
|
|
2085
|
+
if (budget !== void 0 && (isNaN(budget) || budget <= 0)) {
|
|
2086
|
+
setMemberError("Max budget in team must be a positive number");
|
|
2087
|
+
return;
|
|
2088
|
+
}
|
|
2089
|
+
try {
|
|
2090
|
+
setMemberBusy(true);
|
|
2091
|
+
await onAddMember(memberRef.trim(), budget);
|
|
2092
|
+
setMemberRef("");
|
|
2093
|
+
setMemberBudget("");
|
|
2094
|
+
} catch (err) {
|
|
2095
|
+
setMemberError(err instanceof Error ? err.message : "Failed to add member");
|
|
2096
|
+
} finally {
|
|
2097
|
+
setMemberBusy(false);
|
|
2098
|
+
}
|
|
2099
|
+
};
|
|
2100
|
+
const handleRemoveMember = async (userId) => {
|
|
2101
|
+
if (!onRemoveMember) return;
|
|
2102
|
+
setMemberError(null);
|
|
2103
|
+
try {
|
|
2104
|
+
setMemberBusy(true);
|
|
2105
|
+
await onRemoveMember(userId);
|
|
2106
|
+
} catch (err) {
|
|
2107
|
+
setMemberError(err instanceof Error ? err.message : "Failed to remove member");
|
|
2108
|
+
} finally {
|
|
2109
|
+
setMemberBusy(false);
|
|
2110
|
+
}
|
|
2111
|
+
};
|
|
2112
|
+
const handleSaveKnowledgeBases = async () => {
|
|
2113
|
+
if (!onSaveKnowledgeBases) return;
|
|
2114
|
+
setKbError(null);
|
|
2115
|
+
try {
|
|
2116
|
+
setKbBusy(true);
|
|
2117
|
+
await onSaveKnowledgeBases(kbIds);
|
|
2118
|
+
} catch (err) {
|
|
2119
|
+
setKbError(err instanceof Error ? err.message : "Failed to save knowledge bases");
|
|
2120
|
+
} finally {
|
|
2121
|
+
setKbBusy(false);
|
|
2122
|
+
}
|
|
2123
|
+
};
|
|
2124
|
+
const handleSaveMcpServers = async () => {
|
|
2125
|
+
if (!onSaveMcpServers) return;
|
|
2126
|
+
setMcpError(null);
|
|
2127
|
+
try {
|
|
2128
|
+
setMcpBusy(true);
|
|
2129
|
+
await onSaveMcpServers(mcpIds);
|
|
2130
|
+
} catch (err) {
|
|
2131
|
+
setMcpError(err instanceof Error ? err.message : "Failed to save MCP servers");
|
|
2132
|
+
} finally {
|
|
2133
|
+
setMcpBusy(false);
|
|
2134
|
+
}
|
|
2135
|
+
};
|
|
2136
|
+
const modelNames = allModels.map((m) => m.model_name);
|
|
2137
|
+
const members = team?.members_with_roles ?? [];
|
|
2138
|
+
const showMembers = mode === "edit" && !!canManageMembers;
|
|
2139
|
+
const showKnowledgeBases = mode === "edit" && !!canManageKnowledgeBases;
|
|
2140
|
+
const kbOptions = (vectorStores ?? []).map((v) => v.id);
|
|
2141
|
+
const kbLabel = (id) => (vectorStores ?? []).find((v) => v.id === id)?.name ?? id;
|
|
2142
|
+
const showMcpServers = mode === "edit" && !!canManageMcpServers;
|
|
2143
|
+
const mcpOptions = (mcpServers ?? []).map((s) => s.id);
|
|
2144
|
+
const mcpLabel = (id) => (mcpServers ?? []).find((s) => s.id === id)?.name ?? id;
|
|
2145
|
+
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(
|
|
2146
|
+
import_TextField3.default,
|
|
2147
|
+
{
|
|
2148
|
+
label: "Team Alias",
|
|
2149
|
+
value: alias,
|
|
2150
|
+
onChange: (e) => setAlias(e.target.value),
|
|
2151
|
+
disabled: submitting,
|
|
2152
|
+
fullWidth: true
|
|
2153
|
+
}
|
|
2154
|
+
), /* @__PURE__ */ import_react5.default.createElement(
|
|
2155
|
+
import_Autocomplete3.default,
|
|
2156
|
+
{
|
|
2157
|
+
multiple: true,
|
|
2158
|
+
options: modelNames,
|
|
2159
|
+
value: models,
|
|
2160
|
+
onChange: (_, newValue) => setModels(newValue),
|
|
2161
|
+
disabled: submitting,
|
|
2162
|
+
renderInput: (params) => /* @__PURE__ */ import_react5.default.createElement(import_TextField3.default, { ...params, label: "Models" })
|
|
2163
|
+
}
|
|
2164
|
+
), /* @__PURE__ */ import_react5.default.createElement(
|
|
2165
|
+
import_TextField3.default,
|
|
2166
|
+
{
|
|
2167
|
+
label: "Max Budget ($)",
|
|
2168
|
+
type: "number",
|
|
2169
|
+
value: maxBudget,
|
|
2170
|
+
onChange: (e) => setMaxBudget(e.target.value),
|
|
2171
|
+
disabled: submitting || unlimited,
|
|
2172
|
+
helperText: maxBudgetCeiling !== null && maxBudgetCeiling !== void 0 ? `Maximum: $${maxBudgetCeiling}` : void 0,
|
|
2173
|
+
fullWidth: true
|
|
2174
|
+
}
|
|
2175
|
+
), allowUnlimitedBudget && /* @__PURE__ */ import_react5.default.createElement(
|
|
2176
|
+
import_FormControlLabel2.default,
|
|
2177
|
+
{
|
|
2178
|
+
control: /* @__PURE__ */ import_react5.default.createElement(import_Checkbox2.default, { checked: unlimited, onChange: (e) => setUnlimited(e.target.checked), disabled: submitting }),
|
|
2179
|
+
label: "Unlimited Budget"
|
|
2180
|
+
}
|
|
2181
|
+
), /* @__PURE__ */ import_react5.default.createElement(
|
|
2182
|
+
import_TextField3.default,
|
|
2183
|
+
{
|
|
2184
|
+
label: "Budget Duration",
|
|
2185
|
+
value: budgetDuration,
|
|
2186
|
+
onChange: (e) => setBudgetDuration(e.target.value),
|
|
2187
|
+
placeholder: "30d",
|
|
2188
|
+
disabled: submitting,
|
|
2189
|
+
fullWidth: true
|
|
2190
|
+
}
|
|
2191
|
+
), 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(
|
|
2192
|
+
import_ListItem.default,
|
|
2193
|
+
{
|
|
2194
|
+
key: m.user_id,
|
|
2195
|
+
disableGutters: true,
|
|
2196
|
+
secondaryAction: /* @__PURE__ */ import_react5.default.createElement(
|
|
2197
|
+
import_IconButton3.default,
|
|
2198
|
+
{
|
|
2199
|
+
edge: "end",
|
|
2200
|
+
size: "small",
|
|
2201
|
+
"aria-label": `remove ${m.user_id}`,
|
|
2202
|
+
disabled: memberBusy,
|
|
2203
|
+
onClick: () => handleRemoveMember(m.user_id)
|
|
2204
|
+
},
|
|
2205
|
+
/* @__PURE__ */ import_react5.default.createElement(import_icons_material4.Delete, { fontSize: "small" })
|
|
2206
|
+
)
|
|
2207
|
+
},
|
|
2208
|
+
/* @__PURE__ */ import_react5.default.createElement(import_ListItemText.default, { primary: m.user_id, secondary: m.role })
|
|
2209
|
+
))), /* @__PURE__ */ import_react5.default.createElement(import_Box5.default, { sx: { display: "flex", gap: 1, alignItems: "flex-start", mt: 1 } }, /* @__PURE__ */ import_react5.default.createElement(
|
|
2210
|
+
import_TextField3.default,
|
|
2211
|
+
{
|
|
2212
|
+
label: "Backstage user ref",
|
|
2213
|
+
placeholder: "user:default/alice",
|
|
2214
|
+
value: memberRef,
|
|
2215
|
+
onChange: (e) => setMemberRef(e.target.value),
|
|
2216
|
+
disabled: memberBusy,
|
|
2217
|
+
size: "small",
|
|
2218
|
+
sx: { flex: 1 }
|
|
2219
|
+
}
|
|
2220
|
+
), /* @__PURE__ */ import_react5.default.createElement(
|
|
2221
|
+
import_TextField3.default,
|
|
2222
|
+
{
|
|
2223
|
+
label: "Max budget in team ($)",
|
|
2224
|
+
type: "number",
|
|
2225
|
+
value: memberBudget,
|
|
2226
|
+
onChange: (e) => setMemberBudget(e.target.value),
|
|
2227
|
+
disabled: memberBusy,
|
|
2228
|
+
size: "small",
|
|
2229
|
+
sx: { width: 160 }
|
|
2230
|
+
}
|
|
2231
|
+
), /* @__PURE__ */ import_react5.default.createElement(
|
|
2232
|
+
import_Button4.default,
|
|
2233
|
+
{
|
|
2234
|
+
onClick: handleAddMember,
|
|
2235
|
+
disabled: memberBusy || !memberRef.trim(),
|
|
2236
|
+
variant: "outlined",
|
|
2237
|
+
sx: { mt: 0.5 }
|
|
2238
|
+
},
|
|
2239
|
+
"Add"
|
|
2240
|
+
))), 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(
|
|
2241
|
+
import_Autocomplete3.default,
|
|
2242
|
+
{
|
|
2243
|
+
multiple: true,
|
|
2244
|
+
options: kbOptions,
|
|
2245
|
+
value: kbIds,
|
|
2246
|
+
getOptionLabel: kbLabel,
|
|
2247
|
+
onChange: (_, v) => setKbIds(v),
|
|
2248
|
+
disabled: kbBusy,
|
|
2249
|
+
renderInput: (params) => /* @__PURE__ */ import_react5.default.createElement(import_TextField3.default, { ...params, label: "Attached knowledge bases" })
|
|
2250
|
+
}
|
|
2251
|
+
), /* @__PURE__ */ import_react5.default.createElement(import_Box5.default, { sx: { display: "flex", justifyContent: "flex-end", mt: 1 } }, /* @__PURE__ */ import_react5.default.createElement(
|
|
2252
|
+
import_Button4.default,
|
|
2253
|
+
{
|
|
2254
|
+
onClick: handleSaveKnowledgeBases,
|
|
2255
|
+
disabled: kbBusy,
|
|
2256
|
+
variant: "outlined"
|
|
2257
|
+
},
|
|
2258
|
+
kbBusy ? "Saving\u2026" : "Save knowledge bases"
|
|
2259
|
+
))), 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(
|
|
2260
|
+
import_Autocomplete3.default,
|
|
2261
|
+
{
|
|
2262
|
+
multiple: true,
|
|
2263
|
+
options: mcpOptions,
|
|
2264
|
+
value: mcpIds,
|
|
2265
|
+
getOptionLabel: mcpLabel,
|
|
2266
|
+
onChange: (_, v) => setMcpIds(v),
|
|
2267
|
+
disabled: mcpBusy,
|
|
2268
|
+
renderInput: (params) => /* @__PURE__ */ import_react5.default.createElement(import_TextField3.default, { ...params, label: "Attached MCP servers" })
|
|
2269
|
+
}
|
|
2270
|
+
), /* @__PURE__ */ import_react5.default.createElement(import_Box5.default, { sx: { display: "flex", justifyContent: "flex-end", mt: 1 } }, /* @__PURE__ */ import_react5.default.createElement(
|
|
2271
|
+
import_Button4.default,
|
|
2272
|
+
{
|
|
2273
|
+
onClick: handleSaveMcpServers,
|
|
2274
|
+
disabled: mcpBusy,
|
|
2275
|
+
variant: "outlined"
|
|
2276
|
+
},
|
|
2277
|
+
mcpBusy ? "Saving\u2026" : "Save MCP servers"
|
|
2278
|
+
)))), /* @__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")));
|
|
2279
|
+
};
|
|
2280
|
+
}
|
|
2281
|
+
});
|
|
2282
|
+
|
|
1882
2283
|
// src/components/UsageStats.tsx
|
|
1883
2284
|
function rateTone(rate) {
|
|
1884
2285
|
if (rate >= 0.99) return "success";
|
|
1885
2286
|
if (rate >= 0.9) return "warning";
|
|
1886
2287
|
return "danger";
|
|
1887
2288
|
}
|
|
1888
|
-
var
|
|
2289
|
+
var import_react6, import_Paper3, import_Box6, import_Typography6, import_TextField4, import_MenuItem2, 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
2290
|
var init_UsageStats = __esm({
|
|
1890
2291
|
"src/components/UsageStats.tsx"() {
|
|
1891
2292
|
"use strict";
|
|
1892
|
-
|
|
2293
|
+
import_react6 = __toESM(require("react"));
|
|
1893
2294
|
import_Paper3 = __toESM(require("@mui/material/Paper"));
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
2295
|
+
import_Box6 = __toESM(require("@mui/material/Box"));
|
|
2296
|
+
import_Typography6 = __toESM(require("@mui/material/Typography"));
|
|
2297
|
+
import_TextField4 = __toESM(require("@mui/material/TextField"));
|
|
1897
2298
|
import_MenuItem2 = __toESM(require("@mui/material/MenuItem"));
|
|
1898
2299
|
import_Grid = __toESM(require("@mui/material/Grid"));
|
|
1899
2300
|
import_Table2 = __toESM(require("@mui/material/Table"));
|
|
@@ -1915,17 +2316,17 @@ var init_UsageStats = __esm({
|
|
|
1915
2316
|
"30d": "Last 30 days"
|
|
1916
2317
|
};
|
|
1917
2318
|
fmtPct = (n) => `${(n * 100).toFixed(1)}%`;
|
|
1918
|
-
ChartSkeleton = ({ height = 240 }) => /* @__PURE__ */
|
|
2319
|
+
ChartSkeleton = ({ height = 240 }) => /* @__PURE__ */ import_react6.default.createElement(import_Skeleton3.default, { variant: "rounded", height });
|
|
1919
2320
|
ChartOrFallback = ({ loading, empty, height = 240, children }) => {
|
|
1920
|
-
if (loading) return /* @__PURE__ */
|
|
1921
|
-
if (empty) return /* @__PURE__ */
|
|
1922
|
-
return /* @__PURE__ */
|
|
2321
|
+
if (loading) return /* @__PURE__ */ import_react6.default.createElement(ChartSkeleton, { height });
|
|
2322
|
+
if (empty) return /* @__PURE__ */ import_react6.default.createElement(EmptyState, { message: "No data for this period", height });
|
|
2323
|
+
return /* @__PURE__ */ import_react6.default.createElement(import_react6.default.Fragment, null, children);
|
|
1923
2324
|
};
|
|
1924
2325
|
SuccessRateCell = ({ rate, requests }) => {
|
|
1925
|
-
if (requests === 0) return /* @__PURE__ */
|
|
2326
|
+
if (requests === 0) return /* @__PURE__ */ import_react6.default.createElement(import_Typography6.default, { variant: "body2", color: "text.secondary" }, "\u2014");
|
|
1926
2327
|
const tone = rateTone(rate);
|
|
1927
|
-
return /* @__PURE__ */
|
|
1928
|
-
|
|
2328
|
+
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(
|
|
2329
|
+
import_Typography6.default,
|
|
1929
2330
|
{
|
|
1930
2331
|
variant: "caption",
|
|
1931
2332
|
sx: { fontVariantNumeric: "tabular-nums", minWidth: 44 }
|
|
@@ -1943,8 +2344,8 @@ var init_UsageStats = __esm({
|
|
|
1943
2344
|
userInfo
|
|
1944
2345
|
}) => {
|
|
1945
2346
|
const chart = useChartTheme();
|
|
1946
|
-
const [selectedModel, setSelectedModel] = (0,
|
|
1947
|
-
const [tab, setTab] = (0,
|
|
2347
|
+
const [selectedModel, setSelectedModel] = (0, import_react6.useState)("all");
|
|
2348
|
+
const [tab, setTab] = (0, import_react6.useState)("costs");
|
|
1948
2349
|
const handlePresetChange = (preset) => {
|
|
1949
2350
|
const end = /* @__PURE__ */ new Date();
|
|
1950
2351
|
const start = /* @__PURE__ */ new Date();
|
|
@@ -1958,7 +2359,7 @@ var init_UsageStats = __esm({
|
|
|
1958
2359
|
}
|
|
1959
2360
|
onDateRangeChange({ start, end }, preset);
|
|
1960
2361
|
};
|
|
1961
|
-
const dailyData = (0,
|
|
2362
|
+
const dailyData = (0, import_react6.useMemo)(
|
|
1962
2363
|
() => (usage?.daily_usage ?? []).map((d) => ({
|
|
1963
2364
|
date: d.date,
|
|
1964
2365
|
spend: d.spend,
|
|
@@ -1971,21 +2372,21 @@ var init_UsageStats = __esm({
|
|
|
1971
2372
|
})),
|
|
1972
2373
|
[usage]
|
|
1973
2374
|
);
|
|
1974
|
-
const cumulativeData = (0,
|
|
2375
|
+
const cumulativeData = (0, import_react6.useMemo)(() => {
|
|
1975
2376
|
let cum = 0;
|
|
1976
2377
|
return dailyData.map((d) => {
|
|
1977
2378
|
cum += d.spend;
|
|
1978
2379
|
return { date: d.date, cumulative: cum };
|
|
1979
2380
|
});
|
|
1980
2381
|
}, [dailyData]);
|
|
1981
|
-
const successRateData = (0,
|
|
2382
|
+
const successRateData = (0, import_react6.useMemo)(
|
|
1982
2383
|
() => dailyData.filter((d) => d.apiRequests > 0).map((d) => ({
|
|
1983
2384
|
date: d.date,
|
|
1984
2385
|
successRate: parseFloat((d.successfulRequests / d.apiRequests * 100).toFixed(1))
|
|
1985
2386
|
})),
|
|
1986
2387
|
[dailyData]
|
|
1987
2388
|
);
|
|
1988
|
-
const { modelSpendByDate, topModels: topSpendModels } = (0,
|
|
2389
|
+
const { modelSpendByDate, topModels: topSpendModels } = (0, import_react6.useMemo)(() => {
|
|
1989
2390
|
const rows = usage?.daily_by_model ?? [];
|
|
1990
2391
|
const modelTotals = {};
|
|
1991
2392
|
for (const r of rows) modelTotals[r.model] = (modelTotals[r.model] ?? 0) + r.spend;
|
|
@@ -2000,7 +2401,7 @@ var init_UsageStats = __esm({
|
|
|
2000
2401
|
const hasOther = sorted.some((r) => r.Other > 0);
|
|
2001
2402
|
return { modelSpendByDate: sorted, topModels: hasOther ? [...top, "Other"] : top };
|
|
2002
2403
|
}, [usage]);
|
|
2003
|
-
const modelRows = (0,
|
|
2404
|
+
const modelRows = (0, import_react6.useMemo)(() => {
|
|
2004
2405
|
const entries = Object.entries(usage?.usage_by_model ?? {}).map(([model, d]) => ({
|
|
2005
2406
|
model,
|
|
2006
2407
|
spend: d.total_spend,
|
|
@@ -2014,11 +2415,11 @@ var init_UsageStats = __esm({
|
|
|
2014
2415
|
}));
|
|
2015
2416
|
return selectedModel === "all" ? entries : entries.filter((e) => e.model === selectedModel);
|
|
2016
2417
|
}, [usage, selectedModel]);
|
|
2017
|
-
const topModelSpendBars = (0,
|
|
2418
|
+
const topModelSpendBars = (0, import_react6.useMemo)(
|
|
2018
2419
|
() => [...modelRows].sort((a, b) => b.spend - a.spend).slice(0, 10),
|
|
2019
2420
|
[modelRows]
|
|
2020
2421
|
);
|
|
2021
|
-
const keyRows = (0,
|
|
2422
|
+
const keyRows = (0, import_react6.useMemo)(
|
|
2022
2423
|
() => Object.entries(usage?.usage_by_key ?? {}).map(([keyHash, d]) => ({
|
|
2023
2424
|
keyHash,
|
|
2024
2425
|
keyAlias: d.key_alias ?? keyHash.slice(0, 8),
|
|
@@ -2035,7 +2436,7 @@ var init_UsageStats = __esm({
|
|
|
2035
2436
|
})),
|
|
2036
2437
|
[usage]
|
|
2037
2438
|
);
|
|
2038
|
-
const topKeySpendBars = (0,
|
|
2439
|
+
const topKeySpendBars = (0, import_react6.useMemo)(
|
|
2039
2440
|
() => [...keyRows].sort((a, b) => b.spend - a.spend).slice(0, 10),
|
|
2040
2441
|
[keyRows]
|
|
2041
2442
|
);
|
|
@@ -2073,24 +2474,24 @@ var init_UsageStats = __esm({
|
|
|
2073
2474
|
];
|
|
2074
2475
|
const renderKeyRows = () => {
|
|
2075
2476
|
if (loading) {
|
|
2076
|
-
return /* @__PURE__ */
|
|
2477
|
+
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
2478
|
}
|
|
2078
2479
|
if (keyRows.length === 0) {
|
|
2079
|
-
return /* @__PURE__ */
|
|
2480
|
+
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
2481
|
}
|
|
2081
|
-
return [...keyRows].sort((a, b) => b.spend - a.spend || b.apiRequests - a.apiRequests).map((r) => /* @__PURE__ */
|
|
2482
|
+
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
2483
|
};
|
|
2083
2484
|
const renderModelRows = () => {
|
|
2084
2485
|
if (loading) {
|
|
2085
|
-
return /* @__PURE__ */
|
|
2486
|
+
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
2487
|
}
|
|
2087
2488
|
if (modelRows.length === 0) {
|
|
2088
|
-
return /* @__PURE__ */
|
|
2489
|
+
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
2490
|
}
|
|
2090
|
-
return [...modelRows].sort((a, b) => b.spend - a.spend || b.totalTokens - a.totalTokens).map((r) => /* @__PURE__ */
|
|
2491
|
+
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
2492
|
};
|
|
2092
|
-
const periodSelect = /* @__PURE__ */
|
|
2093
|
-
|
|
2493
|
+
const periodSelect = /* @__PURE__ */ import_react6.default.createElement(
|
|
2494
|
+
import_TextField4.default,
|
|
2094
2495
|
{
|
|
2095
2496
|
select: true,
|
|
2096
2497
|
size: "small",
|
|
@@ -2099,18 +2500,18 @@ var init_UsageStats = __esm({
|
|
|
2099
2500
|
onChange: (e) => handlePresetChange(e.target.value),
|
|
2100
2501
|
sx: { minWidth: 160 }
|
|
2101
2502
|
},
|
|
2102
|
-
Object.keys(PRESET_LABELS).map((p) => /* @__PURE__ */
|
|
2503
|
+
Object.keys(PRESET_LABELS).map((p) => /* @__PURE__ */ import_react6.default.createElement(import_MenuItem2.default, { key: p, value: p }, PRESET_LABELS[p]))
|
|
2103
2504
|
);
|
|
2104
|
-
return /* @__PURE__ */
|
|
2505
|
+
return /* @__PURE__ */ import_react6.default.createElement(
|
|
2105
2506
|
SectionCard,
|
|
2106
2507
|
{
|
|
2107
2508
|
title: "Usage Analytics",
|
|
2108
2509
|
subtitle: `${PRESET_LABELS[currentPreset]} \xB7 ${dateRange.start.toLocaleDateString()} \u2013 ${dateRange.end.toLocaleDateString()}`,
|
|
2109
2510
|
actions: periodSelect
|
|
2110
2511
|
},
|
|
2111
|
-
/* @__PURE__ */
|
|
2112
|
-
/* @__PURE__ */
|
|
2113
|
-
|
|
2512
|
+
/* @__PURE__ */ import_react6.default.createElement(MetricStrip, { metrics }),
|
|
2513
|
+
/* @__PURE__ */ import_react6.default.createElement(
|
|
2514
|
+
import_Box6.default,
|
|
2114
2515
|
{
|
|
2115
2516
|
sx: {
|
|
2116
2517
|
display: "flex",
|
|
@@ -2122,7 +2523,7 @@ var init_UsageStats = __esm({
|
|
|
2122
2523
|
mb: 2
|
|
2123
2524
|
}
|
|
2124
2525
|
},
|
|
2125
|
-
/* @__PURE__ */
|
|
2526
|
+
/* @__PURE__ */ import_react6.default.createElement(
|
|
2126
2527
|
SegmentedControl,
|
|
2127
2528
|
{
|
|
2128
2529
|
value: tab,
|
|
@@ -2134,8 +2535,8 @@ var init_UsageStats = __esm({
|
|
|
2134
2535
|
]
|
|
2135
2536
|
}
|
|
2136
2537
|
),
|
|
2137
|
-
tab === "models" && /* @__PURE__ */
|
|
2138
|
-
|
|
2538
|
+
tab === "models" && /* @__PURE__ */ import_react6.default.createElement(
|
|
2539
|
+
import_TextField4.default,
|
|
2139
2540
|
{
|
|
2140
2541
|
select: true,
|
|
2141
2542
|
size: "small",
|
|
@@ -2144,17 +2545,17 @@ var init_UsageStats = __esm({
|
|
|
2144
2545
|
onChange: (e) => setSelectedModel(e.target.value),
|
|
2145
2546
|
sx: { minWidth: 220 }
|
|
2146
2547
|
},
|
|
2147
|
-
/* @__PURE__ */
|
|
2148
|
-
models.map((m) => /* @__PURE__ */
|
|
2548
|
+
/* @__PURE__ */ import_react6.default.createElement(import_MenuItem2.default, { value: "all" }, "All models"),
|
|
2549
|
+
models.map((m) => /* @__PURE__ */ import_react6.default.createElement(import_MenuItem2.default, { key: m.model_name, value: m.model_name }, m.model_name))
|
|
2149
2550
|
)
|
|
2150
2551
|
),
|
|
2151
|
-
tab === "costs" && /* @__PURE__ */
|
|
2552
|
+
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
2553
|
import_recharts.Tooltip,
|
|
2153
2554
|
{
|
|
2154
2555
|
cursor: chart.cursor,
|
|
2155
|
-
content: /* @__PURE__ */
|
|
2556
|
+
content: /* @__PURE__ */ import_react6.default.createElement(ChartTooltip, { valueFormatter: fmtUsd, hideZero: true })
|
|
2156
2557
|
}
|
|
2157
|
-
), topSpendModels.map((m) => /* @__PURE__ */
|
|
2558
|
+
), topSpendModels.map((m) => /* @__PURE__ */ import_react6.default.createElement(
|
|
2158
2559
|
import_recharts.Area,
|
|
2159
2560
|
{
|
|
2160
2561
|
key: m,
|
|
@@ -2166,13 +2567,13 @@ var init_UsageStats = __esm({
|
|
|
2166
2567
|
fill: chartColor(m),
|
|
2167
2568
|
fillOpacity: 0.28
|
|
2168
2569
|
}
|
|
2169
|
-
))))), !loading && topSpendModels.length > 0 && /* @__PURE__ */
|
|
2570
|
+
))))), !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
2571
|
import_recharts.Tooltip,
|
|
2171
2572
|
{
|
|
2172
2573
|
cursor: chart.cursor,
|
|
2173
|
-
content: /* @__PURE__ */
|
|
2574
|
+
content: /* @__PURE__ */ import_react6.default.createElement(ChartTooltip, { valueFormatter: (v) => `${v}%` })
|
|
2174
2575
|
}
|
|
2175
|
-
), /* @__PURE__ */
|
|
2576
|
+
), /* @__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
2577
|
import_recharts.Line,
|
|
2177
2578
|
{
|
|
2178
2579
|
type: "monotone",
|
|
@@ -2183,13 +2584,13 @@ var init_UsageStats = __esm({
|
|
|
2183
2584
|
dot: { r: 2.5, strokeWidth: 0, fill: SERIES.input },
|
|
2184
2585
|
activeDot: { r: 4 }
|
|
2185
2586
|
}
|
|
2186
|
-
)))))), /* @__PURE__ */
|
|
2587
|
+
)))))), /* @__PURE__ */ import_react6.default.createElement(import_Grid.default, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ import_react6.default.createElement(
|
|
2187
2588
|
ChartCard,
|
|
2188
2589
|
{
|
|
2189
2590
|
title: maxBudget > 0 ? "Cumulative spend vs budget" : "Cumulative spend",
|
|
2190
2591
|
meta: maxBudget > 0 ? `${fmtUsd(totalCumSpend)} used \xB7 ${fmtUsd(Math.max(0, maxBudget - totalCumSpend))} left` : void 0
|
|
2191
2592
|
},
|
|
2192
|
-
/* @__PURE__ */
|
|
2593
|
+
/* @__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
2594
|
import_recharts.YAxis,
|
|
2194
2595
|
{
|
|
2195
2596
|
tickFormatter: fmtUsdCompact,
|
|
@@ -2197,13 +2598,13 @@ var init_UsageStats = __esm({
|
|
|
2197
2598
|
domain: cumulativeDomain,
|
|
2198
2599
|
...chart.axis
|
|
2199
2600
|
}
|
|
2200
|
-
), /* @__PURE__ */
|
|
2601
|
+
), /* @__PURE__ */ import_react6.default.createElement(
|
|
2201
2602
|
import_recharts.Tooltip,
|
|
2202
2603
|
{
|
|
2203
2604
|
cursor: chart.cursor,
|
|
2204
|
-
content: /* @__PURE__ */
|
|
2605
|
+
content: /* @__PURE__ */ import_react6.default.createElement(ChartTooltip, { valueFormatter: fmtUsd })
|
|
2205
2606
|
}
|
|
2206
|
-
), maxBudget > 0 && /* @__PURE__ */
|
|
2607
|
+
), maxBudget > 0 && /* @__PURE__ */ import_react6.default.createElement(
|
|
2207
2608
|
import_recharts.ReferenceLine,
|
|
2208
2609
|
{
|
|
2209
2610
|
y: maxBudget,
|
|
@@ -2211,7 +2612,7 @@ var init_UsageStats = __esm({
|
|
|
2211
2612
|
strokeDasharray: "5 4",
|
|
2212
2613
|
label: { value: `Budget ${fmtUsd(maxBudget)}`, position: "insideTopRight", fontSize: 10, fill: SERIES.budget }
|
|
2213
2614
|
}
|
|
2214
|
-
), /* @__PURE__ */
|
|
2615
|
+
), /* @__PURE__ */ import_react6.default.createElement(
|
|
2215
2616
|
import_recharts.Area,
|
|
2216
2617
|
{
|
|
2217
2618
|
type: "monotone",
|
|
@@ -2224,32 +2625,32 @@ var init_UsageStats = __esm({
|
|
|
2224
2625
|
}
|
|
2225
2626
|
))))
|
|
2226
2627
|
))),
|
|
2227
|
-
tab === "models" && /* @__PURE__ */
|
|
2628
|
+
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
2629
|
ChartOrFallback,
|
|
2229
2630
|
{
|
|
2230
2631
|
loading,
|
|
2231
2632
|
empty: topModelSpendBars.length === 0,
|
|
2232
2633
|
height: Math.max(200, topModelSpendBars.length * 34)
|
|
2233
2634
|
},
|
|
2234
|
-
/* @__PURE__ */
|
|
2235
|
-
))), /* @__PURE__ */
|
|
2635
|
+
/* @__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) })))))
|
|
2636
|
+
))), /* @__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
2637
|
ChartOrFallback,
|
|
2237
2638
|
{
|
|
2238
2639
|
loading,
|
|
2239
2640
|
empty: modelRows.length === 0,
|
|
2240
2641
|
height: Math.max(200, topModelSpendBars.length * 34)
|
|
2241
2642
|
},
|
|
2242
|
-
/* @__PURE__ */
|
|
2243
|
-
))), /* @__PURE__ */
|
|
2244
|
-
tab === "keys" && /* @__PURE__ */
|
|
2643
|
+
/* @__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] })))
|
|
2644
|
+
))), /* @__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()))))),
|
|
2645
|
+
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
2646
|
ChartOrFallback,
|
|
2246
2647
|
{
|
|
2247
2648
|
loading,
|
|
2248
2649
|
empty: topKeySpendBars.length === 0,
|
|
2249
2650
|
height: Math.max(160, topKeySpendBars.length * 32)
|
|
2250
2651
|
},
|
|
2251
|
-
/* @__PURE__ */
|
|
2252
|
-
))), /* @__PURE__ */
|
|
2652
|
+
/* @__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 })))
|
|
2653
|
+
))), /* @__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
2654
|
);
|
|
2254
2655
|
};
|
|
2255
2656
|
}
|
|
@@ -2261,16 +2662,16 @@ function budgetTone2(isOver, isNear) {
|
|
|
2261
2662
|
if (isNear) return "warning";
|
|
2262
2663
|
return "accent";
|
|
2263
2664
|
}
|
|
2264
|
-
var
|
|
2665
|
+
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
2666
|
var init_TeamUsage = __esm({
|
|
2266
2667
|
"src/components/TeamUsage.tsx"() {
|
|
2267
2668
|
"use strict";
|
|
2268
|
-
|
|
2669
|
+
import_react7 = __toESM(require("react"));
|
|
2269
2670
|
import_Paper4 = __toESM(require("@mui/material/Paper"));
|
|
2270
|
-
|
|
2271
|
-
|
|
2671
|
+
import_Box7 = __toESM(require("@mui/material/Box"));
|
|
2672
|
+
import_Typography7 = __toESM(require("@mui/material/Typography"));
|
|
2272
2673
|
import_Skeleton4 = __toESM(require("@mui/material/Skeleton"));
|
|
2273
|
-
|
|
2674
|
+
import_Divider3 = __toESM(require("@mui/material/Divider"));
|
|
2274
2675
|
import_Table3 = __toESM(require("@mui/material/Table"));
|
|
2275
2676
|
import_TableBody3 = __toESM(require("@mui/material/TableBody"));
|
|
2276
2677
|
import_TableCell3 = __toESM(require("@mui/material/TableCell"));
|
|
@@ -2278,15 +2679,16 @@ var init_TeamUsage = __esm({
|
|
|
2278
2679
|
import_TableRow3 = __toESM(require("@mui/material/TableRow"));
|
|
2279
2680
|
import_TableContainer3 = __toESM(require("@mui/material/TableContainer"));
|
|
2280
2681
|
import_Collapse = __toESM(require("@mui/material/Collapse"));
|
|
2281
|
-
|
|
2682
|
+
import_IconButton4 = __toESM(require("@mui/material/IconButton"));
|
|
2683
|
+
import_Button5 = __toESM(require("@mui/material/Button"));
|
|
2282
2684
|
import_CircularProgress3 = __toESM(require("@mui/material/CircularProgress"));
|
|
2283
2685
|
import_styles4 = require("@mui/material/styles");
|
|
2284
|
-
|
|
2686
|
+
import_icons_material5 = require("@mui/icons-material");
|
|
2285
2687
|
import_recharts2 = require("recharts");
|
|
2286
2688
|
init_ui();
|
|
2287
2689
|
fmtUsd2 = (n) => `$${(n ?? 0).toFixed(2)}`;
|
|
2288
|
-
TeamCard = ({ team, usage, usageLoading }) => {
|
|
2289
|
-
const [expanded, setExpanded] = (0,
|
|
2690
|
+
TeamCard = ({ team, usage, usageLoading, canManage, onEditTeam }) => {
|
|
2691
|
+
const [expanded, setExpanded] = (0, import_react7.useState)(false);
|
|
2290
2692
|
const chart = useChartTheme();
|
|
2291
2693
|
const budget = team.max_budget ?? 0;
|
|
2292
2694
|
const spend = team.spend ?? 0;
|
|
@@ -2296,24 +2698,24 @@ var init_TeamUsage = __esm({
|
|
|
2296
2698
|
const dailyData = usage?.daily_usage?.map((d) => ({ date: d.date, spend: d.spend })) ?? [];
|
|
2297
2699
|
const renderDailySpendSection = () => {
|
|
2298
2700
|
if (usageLoading) {
|
|
2299
|
-
return /* @__PURE__ */
|
|
2701
|
+
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
2702
|
}
|
|
2301
2703
|
if (dailyData.length === 0) return null;
|
|
2302
|
-
return /* @__PURE__ */
|
|
2303
|
-
|
|
2704
|
+
return /* @__PURE__ */ import_react7.default.createElement(import_Box7.default, { mb: 2.5 }, /* @__PURE__ */ import_react7.default.createElement(
|
|
2705
|
+
import_Typography7.default,
|
|
2304
2706
|
{
|
|
2305
2707
|
variant: "caption",
|
|
2306
2708
|
color: "text.secondary",
|
|
2307
2709
|
sx: { display: "block", fontSize: 11, fontWeight: 700, letterSpacing: "0.07em", textTransform: "uppercase", mb: 1 }
|
|
2308
2710
|
},
|
|
2309
2711
|
"Daily spend"
|
|
2310
|
-
), /* @__PURE__ */
|
|
2712
|
+
), /* @__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
2713
|
import_recharts2.Tooltip,
|
|
2312
2714
|
{
|
|
2313
2715
|
cursor: chart.cursor,
|
|
2314
|
-
content: /* @__PURE__ */
|
|
2716
|
+
content: /* @__PURE__ */ import_react7.default.createElement(ChartTooltip, { valueFormatter: (v) => `$${v.toFixed(4)}` })
|
|
2315
2717
|
}
|
|
2316
|
-
), /* @__PURE__ */
|
|
2718
|
+
), /* @__PURE__ */ import_react7.default.createElement(
|
|
2317
2719
|
import_recharts2.Area,
|
|
2318
2720
|
{
|
|
2319
2721
|
type: "monotone",
|
|
@@ -2326,8 +2728,8 @@ var init_TeamUsage = __esm({
|
|
|
2326
2728
|
}
|
|
2327
2729
|
)))));
|
|
2328
2730
|
};
|
|
2329
|
-
const sectionLabel = (label) => /* @__PURE__ */
|
|
2330
|
-
|
|
2731
|
+
const sectionLabel = (label) => /* @__PURE__ */ import_react7.default.createElement(
|
|
2732
|
+
import_Typography7.default,
|
|
2331
2733
|
{
|
|
2332
2734
|
variant: "caption",
|
|
2333
2735
|
color: "text.secondary",
|
|
@@ -2335,8 +2737,8 @@ var init_TeamUsage = __esm({
|
|
|
2335
2737
|
},
|
|
2336
2738
|
label
|
|
2337
2739
|
);
|
|
2338
|
-
return /* @__PURE__ */
|
|
2339
|
-
|
|
2740
|
+
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(
|
|
2741
|
+
import_Box7.default,
|
|
2340
2742
|
{
|
|
2341
2743
|
sx: (theme) => ({
|
|
2342
2744
|
width: 36,
|
|
@@ -2350,17 +2752,26 @@ var init_TeamUsage = __esm({
|
|
|
2350
2752
|
color: theme.palette.primary.main
|
|
2351
2753
|
})
|
|
2352
2754
|
},
|
|
2353
|
-
/* @__PURE__ */
|
|
2354
|
-
), /* @__PURE__ */
|
|
2355
|
-
|
|
2755
|
+
/* @__PURE__ */ import_react7.default.createElement(import_icons_material5.Group, { fontSize: "small" })
|
|
2756
|
+
), /* @__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(
|
|
2757
|
+
import_Typography7.default,
|
|
2356
2758
|
{
|
|
2357
2759
|
variant: "caption",
|
|
2358
2760
|
color: "text.secondary",
|
|
2359
2761
|
sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 11 }
|
|
2360
2762
|
},
|
|
2361
2763
|
team.team_id
|
|
2362
|
-
)), isOver && /* @__PURE__ */
|
|
2363
|
-
|
|
2764
|
+
)), 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(
|
|
2765
|
+
import_Button5.default,
|
|
2766
|
+
{
|
|
2767
|
+
size: "small",
|
|
2768
|
+
variant: "text",
|
|
2769
|
+
onClick: () => onEditTeam(team),
|
|
2770
|
+
sx: { textTransform: "none" }
|
|
2771
|
+
},
|
|
2772
|
+
"Edit"
|
|
2773
|
+
), /* @__PURE__ */ import_react7.default.createElement(
|
|
2774
|
+
import_IconButton4.default,
|
|
2364
2775
|
{
|
|
2365
2776
|
size: "small",
|
|
2366
2777
|
onClick: () => setExpanded((e) => !e),
|
|
@@ -2372,9 +2783,9 @@ var init_TeamUsage = __esm({
|
|
|
2372
2783
|
transition: theme.transitions.create("transform")
|
|
2373
2784
|
})
|
|
2374
2785
|
},
|
|
2375
|
-
/* @__PURE__ */
|
|
2376
|
-
)), /* @__PURE__ */
|
|
2377
|
-
|
|
2786
|
+
/* @__PURE__ */ import_react7.default.createElement(import_icons_material5.ExpandMore, null)
|
|
2787
|
+
)), /* @__PURE__ */ import_react7.default.createElement(
|
|
2788
|
+
import_Box7.default,
|
|
2378
2789
|
{
|
|
2379
2790
|
sx: {
|
|
2380
2791
|
display: "grid",
|
|
@@ -2384,35 +2795,35 @@ var init_TeamUsage = __esm({
|
|
|
2384
2795
|
maxWidth: 640
|
|
2385
2796
|
}
|
|
2386
2797
|
},
|
|
2387
|
-
/* @__PURE__ */
|
|
2388
|
-
/* @__PURE__ */
|
|
2389
|
-
/* @__PURE__ */
|
|
2390
|
-
/* @__PURE__ */
|
|
2391
|
-
/* @__PURE__ */
|
|
2392
|
-
/* @__PURE__ */
|
|
2393
|
-
), budget > 0 && /* @__PURE__ */
|
|
2798
|
+
/* @__PURE__ */ import_react7.default.createElement(Stat, { label: "Members", value: team.members_with_roles?.length ?? "\u2014" }),
|
|
2799
|
+
/* @__PURE__ */ import_react7.default.createElement(Stat, { label: "Models", value: team.models?.length ? team.models.length : "All" }),
|
|
2800
|
+
/* @__PURE__ */ import_react7.default.createElement(Stat, { label: "Budget", value: budget > 0 ? fmtUsd2(budget) : "Unlimited" }),
|
|
2801
|
+
/* @__PURE__ */ import_react7.default.createElement(Stat, { label: "Spend", value: fmtUsd2(spend) }),
|
|
2802
|
+
/* @__PURE__ */ import_react7.default.createElement(Stat, { label: "TPM", value: team.tpm_limit && team.tpm_limit > 0 ? team.tpm_limit.toLocaleString() : "\u2014" }),
|
|
2803
|
+
/* @__PURE__ */ import_react7.default.createElement(Stat, { label: "RPM", value: team.rpm_limit && team.rpm_limit > 0 ? team.rpm_limit.toLocaleString() : "\u2014" })
|
|
2804
|
+
), 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
2805
|
import_TableContainer3.default,
|
|
2395
2806
|
{
|
|
2396
2807
|
component: import_Paper4.default,
|
|
2397
2808
|
variant: "outlined",
|
|
2398
2809
|
sx: { borderRadius: 1.5 }
|
|
2399
2810
|
},
|
|
2400
|
-
/* @__PURE__ */
|
|
2401
|
-
|
|
2811
|
+
/* @__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(
|
|
2812
|
+
import_Typography7.default,
|
|
2402
2813
|
{
|
|
2403
2814
|
variant: "caption",
|
|
2404
2815
|
color: "text.secondary",
|
|
2405
2816
|
sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 11 }
|
|
2406
2817
|
},
|
|
2407
2818
|
m.user_id
|
|
2408
|
-
)) : /* @__PURE__ */
|
|
2409
|
-
|
|
2819
|
+
)) : /* @__PURE__ */ import_react7.default.createElement(
|
|
2820
|
+
import_Typography7.default,
|
|
2410
2821
|
{
|
|
2411
2822
|
variant: "body2",
|
|
2412
2823
|
sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace" }
|
|
2413
2824
|
},
|
|
2414
2825
|
m.user_id
|
|
2415
|
-
)), /* @__PURE__ */
|
|
2826
|
+
)), /* @__PURE__ */ import_react7.default.createElement(import_TableCell3.default, { align: "right" }, /* @__PURE__ */ import_react7.default.createElement(
|
|
2416
2827
|
StatusPill,
|
|
2417
2828
|
{
|
|
2418
2829
|
label: m.role,
|
|
@@ -2420,19 +2831,21 @@ var init_TeamUsage = __esm({
|
|
|
2420
2831
|
dot: false
|
|
2421
2832
|
}
|
|
2422
2833
|
))))))
|
|
2423
|
-
) : /* @__PURE__ */
|
|
2834
|
+
) : /* @__PURE__ */ import_react7.default.createElement(import_Typography7.default, { variant: "body2", color: "text.secondary" }, "No members assigned."))));
|
|
2424
2835
|
};
|
|
2425
2836
|
TeamUsage = ({
|
|
2426
2837
|
teams,
|
|
2427
2838
|
loading,
|
|
2428
2839
|
getTeamUsage,
|
|
2429
|
-
getTeamUsageLoading
|
|
2840
|
+
getTeamUsageLoading,
|
|
2841
|
+
canManage,
|
|
2842
|
+
onEditTeam
|
|
2430
2843
|
}) => {
|
|
2431
2844
|
if (loading) {
|
|
2432
|
-
return /* @__PURE__ */
|
|
2845
|
+
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
2846
|
}
|
|
2434
2847
|
if (!teams.length) {
|
|
2435
|
-
return /* @__PURE__ */
|
|
2848
|
+
return /* @__PURE__ */ import_react7.default.createElement(SectionCard, { title: "Teams" }, /* @__PURE__ */ import_react7.default.createElement(
|
|
2436
2849
|
EmptyState,
|
|
2437
2850
|
{
|
|
2438
2851
|
message: "You're not a member of any LiteLLM team yet.",
|
|
@@ -2440,13 +2853,15 @@ var init_TeamUsage = __esm({
|
|
|
2440
2853
|
}
|
|
2441
2854
|
));
|
|
2442
2855
|
}
|
|
2443
|
-
return /* @__PURE__ */
|
|
2856
|
+
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
2857
|
TeamCard,
|
|
2445
2858
|
{
|
|
2446
2859
|
key: team.team_id,
|
|
2447
2860
|
team,
|
|
2448
2861
|
usage: getTeamUsage(team.team_id),
|
|
2449
|
-
usageLoading: getTeamUsageLoading(team.team_id)
|
|
2862
|
+
usageLoading: getTeamUsageLoading(team.team_id),
|
|
2863
|
+
canManage,
|
|
2864
|
+
onEditTeam
|
|
2450
2865
|
}
|
|
2451
2866
|
))));
|
|
2452
2867
|
};
|
|
@@ -2471,19 +2886,19 @@ function fmtTokens(n) {
|
|
|
2471
2886
|
if (n >= 1e3) return `${(n / 1e3).toFixed(0)}K`;
|
|
2472
2887
|
return String(n);
|
|
2473
2888
|
}
|
|
2474
|
-
var
|
|
2889
|
+
var import_react8, import_Box8, import_Table4, import_TableBody4, import_TableCell4, import_TableContainer4, import_TableHead4, import_TableRow4, import_Typography8, import_MenuItem3, import_Select, import_FormControl, import_InputLabel, import_ContentCopy, import_Tooltip, import_Snackbar, import_Alert3, import_styles5, ALL_PROXY_MODELS2, ModelsTable;
|
|
2475
2890
|
var init_ModelsTable = __esm({
|
|
2476
2891
|
"src/components/ModelsTable.tsx"() {
|
|
2477
2892
|
"use strict";
|
|
2478
|
-
|
|
2479
|
-
|
|
2893
|
+
import_react8 = __toESM(require("react"));
|
|
2894
|
+
import_Box8 = __toESM(require("@mui/material/Box"));
|
|
2480
2895
|
import_Table4 = __toESM(require("@mui/material/Table"));
|
|
2481
2896
|
import_TableBody4 = __toESM(require("@mui/material/TableBody"));
|
|
2482
2897
|
import_TableCell4 = __toESM(require("@mui/material/TableCell"));
|
|
2483
2898
|
import_TableContainer4 = __toESM(require("@mui/material/TableContainer"));
|
|
2484
2899
|
import_TableHead4 = __toESM(require("@mui/material/TableHead"));
|
|
2485
2900
|
import_TableRow4 = __toESM(require("@mui/material/TableRow"));
|
|
2486
|
-
|
|
2901
|
+
import_Typography8 = __toESM(require("@mui/material/Typography"));
|
|
2487
2902
|
import_MenuItem3 = __toESM(require("@mui/material/MenuItem"));
|
|
2488
2903
|
import_Select = __toESM(require("@mui/material/Select"));
|
|
2489
2904
|
import_FormControl = __toESM(require("@mui/material/FormControl"));
|
|
@@ -2491,23 +2906,23 @@ var init_ModelsTable = __esm({
|
|
|
2491
2906
|
import_ContentCopy = __toESM(require("@mui/icons-material/ContentCopy"));
|
|
2492
2907
|
import_Tooltip = __toESM(require("@mui/material/Tooltip"));
|
|
2493
2908
|
import_Snackbar = __toESM(require("@mui/material/Snackbar"));
|
|
2494
|
-
|
|
2909
|
+
import_Alert3 = __toESM(require("@mui/material/Alert"));
|
|
2495
2910
|
init_ui();
|
|
2496
2911
|
import_styles5 = require("@mui/material/styles");
|
|
2497
2912
|
ALL_PROXY_MODELS2 = "all-proxy-models";
|
|
2498
2913
|
ModelsTable = ({ allModels, teams, loading }) => {
|
|
2499
2914
|
const theme = (0, import_styles5.useTheme)();
|
|
2500
|
-
const [selectedTeamId, setSelectedTeamId] = (0,
|
|
2501
|
-
const [copiedSnackbar, setCopiedSnackbar] = (0,
|
|
2502
|
-
const selectedTeam = (0,
|
|
2915
|
+
const [selectedTeamId, setSelectedTeamId] = (0, import_react8.useState)("");
|
|
2916
|
+
const [copiedSnackbar, setCopiedSnackbar] = (0, import_react8.useState)(false);
|
|
2917
|
+
const selectedTeam = (0, import_react8.useMemo)(
|
|
2503
2918
|
() => teams.find((t) => t.team_id === selectedTeamId) ?? null,
|
|
2504
2919
|
[teams, selectedTeamId]
|
|
2505
2920
|
);
|
|
2506
|
-
const filteredModels = (0,
|
|
2921
|
+
const filteredModels = (0, import_react8.useMemo)(() => {
|
|
2507
2922
|
if (!selectedTeam) return [];
|
|
2508
2923
|
return allModels.filter((model) => isModelAllowedByTeam2(model, selectedTeam.models));
|
|
2509
2924
|
}, [allModels, selectedTeam]);
|
|
2510
|
-
const handleCopyModelId = (0,
|
|
2925
|
+
const handleCopyModelId = (0, import_react8.useCallback)((modelId) => {
|
|
2511
2926
|
navigator.clipboard.writeText(modelId).then(() => setCopiedSnackbar(true));
|
|
2512
2927
|
}, []);
|
|
2513
2928
|
const modeColor = (mode) => {
|
|
@@ -2524,13 +2939,13 @@ var init_ModelsTable = __esm({
|
|
|
2524
2939
|
return theme.palette.info.main;
|
|
2525
2940
|
}
|
|
2526
2941
|
};
|
|
2527
|
-
return /* @__PURE__ */
|
|
2942
|
+
return /* @__PURE__ */ import_react8.default.createElement(
|
|
2528
2943
|
SectionCard,
|
|
2529
2944
|
{
|
|
2530
2945
|
title: "Available Models",
|
|
2531
2946
|
subtitle: selectedTeam ? `${filteredModels.length} model${filteredModels.length !== 1 ? "s" : ""} in ${selectedTeam.team_alias ?? selectedTeam.team_id}` : "Select a team to view its models"
|
|
2532
2947
|
},
|
|
2533
|
-
/* @__PURE__ */
|
|
2948
|
+
/* @__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
2949
|
import_Select.default,
|
|
2535
2950
|
{
|
|
2536
2951
|
labelId: "team-select-label",
|
|
@@ -2538,25 +2953,25 @@ var init_ModelsTable = __esm({
|
|
|
2538
2953
|
label: "Team",
|
|
2539
2954
|
onChange: (e) => setSelectedTeamId(e.target.value)
|
|
2540
2955
|
},
|
|
2541
|
-
teams.map((t) => /* @__PURE__ */
|
|
2956
|
+
teams.map((t) => /* @__PURE__ */ import_react8.default.createElement(import_MenuItem3.default, { key: t.team_id, value: t.team_id }, t.team_alias ?? t.team_id))
|
|
2542
2957
|
))),
|
|
2543
|
-
loading && /* @__PURE__ */
|
|
2544
|
-
!loading && !selectedTeamId && /* @__PURE__ */
|
|
2958
|
+
loading && /* @__PURE__ */ import_react8.default.createElement(EmptyState, { message: "Loading models\u2026" }),
|
|
2959
|
+
!loading && !selectedTeamId && /* @__PURE__ */ import_react8.default.createElement(
|
|
2545
2960
|
EmptyState,
|
|
2546
2961
|
{
|
|
2547
2962
|
message: "Select a team to see available models",
|
|
2548
2963
|
hint: "Each team may have a different set of models assigned"
|
|
2549
2964
|
}
|
|
2550
2965
|
),
|
|
2551
|
-
!loading && selectedTeamId && filteredModels.length === 0 && /* @__PURE__ */
|
|
2966
|
+
!loading && selectedTeamId && filteredModels.length === 0 && /* @__PURE__ */ import_react8.default.createElement(
|
|
2552
2967
|
EmptyState,
|
|
2553
2968
|
{
|
|
2554
2969
|
message: "No models available for this team",
|
|
2555
2970
|
hint: allModels.length === 0 ? "No models are configured in the system" : "None of the team's assigned models were found in the catalog"
|
|
2556
2971
|
}
|
|
2557
2972
|
),
|
|
2558
|
-
!loading && filteredModels.length > 0 && /* @__PURE__ */
|
|
2559
|
-
|
|
2973
|
+
!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(
|
|
2974
|
+
import_Box8.default,
|
|
2560
2975
|
{
|
|
2561
2976
|
component: "span",
|
|
2562
2977
|
role: "button",
|
|
@@ -2566,9 +2981,9 @@ var init_ModelsTable = __esm({
|
|
|
2566
2981
|
sx: quietIconButtonSx("accent"),
|
|
2567
2982
|
style: { cursor: "pointer", display: "inline-flex" }
|
|
2568
2983
|
},
|
|
2569
|
-
/* @__PURE__ */
|
|
2570
|
-
)))), /* @__PURE__ */
|
|
2571
|
-
|
|
2984
|
+
/* @__PURE__ */ import_react8.default.createElement(import_ContentCopy.default, { sx: { fontSize: 14 } })
|
|
2985
|
+
)))), /* @__PURE__ */ import_react8.default.createElement(import_TableCell4.default, null, /* @__PURE__ */ import_react8.default.createElement(
|
|
2986
|
+
import_Box8.default,
|
|
2572
2987
|
{
|
|
2573
2988
|
component: "span",
|
|
2574
2989
|
sx: {
|
|
@@ -2584,8 +2999,8 @@ var init_ModelsTable = __esm({
|
|
|
2584
2999
|
}
|
|
2585
3000
|
},
|
|
2586
3001
|
m.mode
|
|
2587
|
-
)), /* @__PURE__ */
|
|
2588
|
-
/* @__PURE__ */
|
|
3002
|
+
)), /* @__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" })))))))),
|
|
3003
|
+
/* @__PURE__ */ import_react8.default.createElement(
|
|
2589
3004
|
import_Snackbar.default,
|
|
2590
3005
|
{
|
|
2591
3006
|
open: copiedSnackbar,
|
|
@@ -2593,7 +3008,7 @@ var init_ModelsTable = __esm({
|
|
|
2593
3008
|
onClose: () => setCopiedSnackbar(false),
|
|
2594
3009
|
anchorOrigin: { vertical: "bottom", horizontal: "center" }
|
|
2595
3010
|
},
|
|
2596
|
-
/* @__PURE__ */
|
|
3011
|
+
/* @__PURE__ */ import_react8.default.createElement(import_Alert3.default, { severity: "success", variant: "filled", sx: { fontSize: 13 } }, "Model ID copied to clipboard")
|
|
2597
3012
|
)
|
|
2598
3013
|
);
|
|
2599
3014
|
};
|
|
@@ -2621,10 +3036,10 @@ function formatDateTime(iso) {
|
|
|
2621
3036
|
}
|
|
2622
3037
|
function renderAuditLogBody(loading, entries) {
|
|
2623
3038
|
if (loading) {
|
|
2624
|
-
return /* @__PURE__ */
|
|
3039
|
+
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
3040
|
}
|
|
2626
3041
|
if (entries.length === 0) {
|
|
2627
|
-
return /* @__PURE__ */
|
|
3042
|
+
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
3043
|
EmptyState,
|
|
2629
3044
|
{
|
|
2630
3045
|
message: "No audit events found",
|
|
@@ -2632,15 +3047,15 @@ function renderAuditLogBody(loading, entries) {
|
|
|
2632
3047
|
}
|
|
2633
3048
|
)));
|
|
2634
3049
|
}
|
|
2635
|
-
return entries.map((entry) => /* @__PURE__ */
|
|
3050
|
+
return entries.map((entry) => /* @__PURE__ */ import_react9.default.createElement(DetailRow, { key: entry.id, entry }));
|
|
2636
3051
|
}
|
|
2637
|
-
var
|
|
3052
|
+
var import_react9, import_Box9, import_Typography9, import_Table5, import_TableBody5, import_TableCell5, import_TableContainer5, import_TableHead5, import_TableRow5, import_TablePagination, import_TextField5, import_MenuItem4, import_Skeleton5, import_Collapse2, import_IconButton5, import_Alert4, import_styles6, import_icons_material6, import_react_use, ACTION_TONES, TABLE_LABELS, DetailRow, AuditLog;
|
|
2638
3053
|
var init_AuditLog = __esm({
|
|
2639
3054
|
"src/components/AuditLog.tsx"() {
|
|
2640
3055
|
"use strict";
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
3056
|
+
import_react9 = __toESM(require("react"));
|
|
3057
|
+
import_Box9 = __toESM(require("@mui/material/Box"));
|
|
3058
|
+
import_Typography9 = __toESM(require("@mui/material/Typography"));
|
|
2644
3059
|
import_Table5 = __toESM(require("@mui/material/Table"));
|
|
2645
3060
|
import_TableBody5 = __toESM(require("@mui/material/TableBody"));
|
|
2646
3061
|
import_TableCell5 = __toESM(require("@mui/material/TableCell"));
|
|
@@ -2648,14 +3063,14 @@ var init_AuditLog = __esm({
|
|
|
2648
3063
|
import_TableHead5 = __toESM(require("@mui/material/TableHead"));
|
|
2649
3064
|
import_TableRow5 = __toESM(require("@mui/material/TableRow"));
|
|
2650
3065
|
import_TablePagination = __toESM(require("@mui/material/TablePagination"));
|
|
2651
|
-
|
|
3066
|
+
import_TextField5 = __toESM(require("@mui/material/TextField"));
|
|
2652
3067
|
import_MenuItem4 = __toESM(require("@mui/material/MenuItem"));
|
|
2653
3068
|
import_Skeleton5 = __toESM(require("@mui/material/Skeleton"));
|
|
2654
3069
|
import_Collapse2 = __toESM(require("@mui/material/Collapse"));
|
|
2655
|
-
|
|
2656
|
-
|
|
3070
|
+
import_IconButton5 = __toESM(require("@mui/material/IconButton"));
|
|
3071
|
+
import_Alert4 = __toESM(require("@mui/material/Alert"));
|
|
2657
3072
|
import_styles6 = require("@mui/material/styles");
|
|
2658
|
-
|
|
3073
|
+
import_icons_material6 = require("@mui/icons-material");
|
|
2659
3074
|
import_react_use = require("react-use");
|
|
2660
3075
|
init_ui();
|
|
2661
3076
|
ACTION_TONES = {
|
|
@@ -2668,13 +3083,15 @@ var init_AuditLog = __esm({
|
|
|
2668
3083
|
TABLE_LABELS = {
|
|
2669
3084
|
LiteLLM_VerificationToken: "Key",
|
|
2670
3085
|
LiteLLM_TeamTable: "Team",
|
|
3086
|
+
LiteLLM_TeamMembership: "Team member",
|
|
3087
|
+
LiteLLM_ObjectPermissionTable: "Team access (KB / MCP)",
|
|
2671
3088
|
LiteLLM_UserTable: "User"
|
|
2672
3089
|
};
|
|
2673
3090
|
DetailRow = ({ entry }) => {
|
|
2674
|
-
const [open, setOpen] = (0,
|
|
3091
|
+
const [open, setOpen] = (0, import_react9.useState)(false);
|
|
2675
3092
|
const hasDetail = entry.before_value || entry.updated_values;
|
|
2676
|
-
return /* @__PURE__ */
|
|
2677
|
-
|
|
3093
|
+
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(
|
|
3094
|
+
import_IconButton5.default,
|
|
2678
3095
|
{
|
|
2679
3096
|
size: "small",
|
|
2680
3097
|
onClick: () => setOpen((o) => !o),
|
|
@@ -2686,9 +3103,9 @@ var init_AuditLog = __esm({
|
|
|
2686
3103
|
transition: theme.transitions.create("transform")
|
|
2687
3104
|
})
|
|
2688
3105
|
},
|
|
2689
|
-
/* @__PURE__ */
|
|
2690
|
-
)), /* @__PURE__ */
|
|
2691
|
-
|
|
3106
|
+
/* @__PURE__ */ import_react9.default.createElement(import_icons_material6.KeyboardArrowDown, { fontSize: "small" })
|
|
3107
|
+
)), /* @__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(
|
|
3108
|
+
import_Typography9.default,
|
|
2692
3109
|
{
|
|
2693
3110
|
variant: "body2",
|
|
2694
3111
|
component: "code",
|
|
@@ -2697,8 +3114,8 @@ var init_AuditLog = __esm({
|
|
|
2697
3114
|
sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 12 }
|
|
2698
3115
|
},
|
|
2699
3116
|
entry.object_id ? entry.object_id.slice(0, 20) + (entry.object_id.length > 20 ? "\u2026" : "") : "\u2014"
|
|
2700
|
-
)), /* @__PURE__ */
|
|
2701
|
-
|
|
3117
|
+
)), /* @__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(
|
|
3118
|
+
import_Box9.default,
|
|
2702
3119
|
{
|
|
2703
3120
|
display: "flex",
|
|
2704
3121
|
gap: 2,
|
|
@@ -2710,8 +3127,8 @@ var init_AuditLog = __esm({
|
|
|
2710
3127
|
bgcolor: (0, import_styles6.alpha)(theme.palette.text.primary, theme.palette.mode === "dark" ? 0.05 : 0.03)
|
|
2711
3128
|
})
|
|
2712
3129
|
},
|
|
2713
|
-
entry.before_value && /* @__PURE__ */
|
|
2714
|
-
|
|
3130
|
+
entry.before_value && /* @__PURE__ */ import_react9.default.createElement(import_Box9.default, { flex: 1, minWidth: 200 }, /* @__PURE__ */ import_react9.default.createElement(
|
|
3131
|
+
import_Typography9.default,
|
|
2715
3132
|
{
|
|
2716
3133
|
variant: "caption",
|
|
2717
3134
|
color: "text.secondary",
|
|
@@ -2720,9 +3137,9 @@ var init_AuditLog = __esm({
|
|
|
2720
3137
|
sx: { fontSize: 10.5, fontWeight: 700, letterSpacing: "0.07em", textTransform: "uppercase" }
|
|
2721
3138
|
},
|
|
2722
3139
|
"Before"
|
|
2723
|
-
), /* @__PURE__ */
|
|
2724
|
-
entry.updated_values && /* @__PURE__ */
|
|
2725
|
-
|
|
3140
|
+
), /* @__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))),
|
|
3141
|
+
entry.updated_values && /* @__PURE__ */ import_react9.default.createElement(import_Box9.default, { flex: 1, minWidth: 200 }, /* @__PURE__ */ import_react9.default.createElement(
|
|
3142
|
+
import_Typography9.default,
|
|
2726
3143
|
{
|
|
2727
3144
|
variant: "caption",
|
|
2728
3145
|
color: "text.secondary",
|
|
@@ -2731,14 +3148,14 @@ var init_AuditLog = __esm({
|
|
|
2731
3148
|
sx: { fontSize: 10.5, fontWeight: 700, letterSpacing: "0.07em", textTransform: "uppercase" }
|
|
2732
3149
|
},
|
|
2733
3150
|
"After"
|
|
2734
|
-
), /* @__PURE__ */
|
|
3151
|
+
), /* @__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
3152
|
)))));
|
|
2736
3153
|
};
|
|
2737
3154
|
AuditLog = ({ api }) => {
|
|
2738
|
-
const [page, setPage] = (0,
|
|
2739
|
-
const [pageSize, setPageSize] = (0,
|
|
2740
|
-
const [filters, setFilters] = (0,
|
|
2741
|
-
const fetchParams = (0,
|
|
3155
|
+
const [page, setPage] = (0, import_react9.useState)(0);
|
|
3156
|
+
const [pageSize, setPageSize] = (0, import_react9.useState)(25);
|
|
3157
|
+
const [filters, setFilters] = (0, import_react9.useState)({});
|
|
3158
|
+
const fetchParams = (0, import_react9.useCallback)(
|
|
2742
3159
|
() => ({ page: page + 1, page_size: pageSize, ...filters }),
|
|
2743
3160
|
[page, pageSize, filters]
|
|
2744
3161
|
)();
|
|
@@ -2748,14 +3165,14 @@ var init_AuditLog = __esm({
|
|
|
2748
3165
|
);
|
|
2749
3166
|
const entries = value?.audit_logs ?? [];
|
|
2750
3167
|
const total = value?.total ?? 0;
|
|
2751
|
-
return /* @__PURE__ */
|
|
3168
|
+
return /* @__PURE__ */ import_react9.default.createElement(
|
|
2752
3169
|
SectionCard,
|
|
2753
3170
|
{
|
|
2754
3171
|
title: "Audit Log",
|
|
2755
3172
|
subtitle: loading ? "Loading\u2026" : `${total.toLocaleString()} event${total === 1 ? "" : "s"}`,
|
|
2756
3173
|
flush: true,
|
|
2757
|
-
actions: /* @__PURE__ */
|
|
2758
|
-
|
|
3174
|
+
actions: /* @__PURE__ */ import_react9.default.createElement(import_react9.default.Fragment, null, /* @__PURE__ */ import_react9.default.createElement(
|
|
3175
|
+
import_TextField5.default,
|
|
2759
3176
|
{
|
|
2760
3177
|
size: "small",
|
|
2761
3178
|
label: "Action",
|
|
@@ -2767,13 +3184,13 @@ var init_AuditLog = __esm({
|
|
|
2767
3184
|
},
|
|
2768
3185
|
sx: { minWidth: 150 }
|
|
2769
3186
|
},
|
|
2770
|
-
/* @__PURE__ */
|
|
2771
|
-
/* @__PURE__ */
|
|
2772
|
-
/* @__PURE__ */
|
|
2773
|
-
/* @__PURE__ */
|
|
2774
|
-
/* @__PURE__ */
|
|
2775
|
-
), /* @__PURE__ */
|
|
2776
|
-
|
|
3187
|
+
/* @__PURE__ */ import_react9.default.createElement(import_MenuItem4.default, { value: "" }, "All actions"),
|
|
3188
|
+
/* @__PURE__ */ import_react9.default.createElement(import_MenuItem4.default, { value: "created" }, "Created"),
|
|
3189
|
+
/* @__PURE__ */ import_react9.default.createElement(import_MenuItem4.default, { value: "updated" }, "Updated"),
|
|
3190
|
+
/* @__PURE__ */ import_react9.default.createElement(import_MenuItem4.default, { value: "deleted" }, "Deleted"),
|
|
3191
|
+
/* @__PURE__ */ import_react9.default.createElement(import_MenuItem4.default, { value: "blocked" }, "Blocked")
|
|
3192
|
+
), /* @__PURE__ */ import_react9.default.createElement(
|
|
3193
|
+
import_TextField5.default,
|
|
2777
3194
|
{
|
|
2778
3195
|
size: "small",
|
|
2779
3196
|
label: "Table",
|
|
@@ -2785,12 +3202,14 @@ var init_AuditLog = __esm({
|
|
|
2785
3202
|
},
|
|
2786
3203
|
sx: { minWidth: 150 }
|
|
2787
3204
|
},
|
|
2788
|
-
/* @__PURE__ */
|
|
2789
|
-
/* @__PURE__ */
|
|
2790
|
-
/* @__PURE__ */
|
|
2791
|
-
/* @__PURE__ */
|
|
2792
|
-
|
|
2793
|
-
|
|
3205
|
+
/* @__PURE__ */ import_react9.default.createElement(import_MenuItem4.default, { value: "" }, "All tables"),
|
|
3206
|
+
/* @__PURE__ */ import_react9.default.createElement(import_MenuItem4.default, { value: "LiteLLM_VerificationToken" }, "Key"),
|
|
3207
|
+
/* @__PURE__ */ import_react9.default.createElement(import_MenuItem4.default, { value: "LiteLLM_TeamTable" }, "Team"),
|
|
3208
|
+
/* @__PURE__ */ import_react9.default.createElement(import_MenuItem4.default, { value: "LiteLLM_TeamMembership" }, "Team member"),
|
|
3209
|
+
/* @__PURE__ */ import_react9.default.createElement(import_MenuItem4.default, { value: "LiteLLM_ObjectPermissionTable" }, "Team access (KB / MCP)"),
|
|
3210
|
+
/* @__PURE__ */ import_react9.default.createElement(import_MenuItem4.default, { value: "LiteLLM_UserTable" }, "User")
|
|
3211
|
+
), /* @__PURE__ */ import_react9.default.createElement(
|
|
3212
|
+
import_TextField5.default,
|
|
2794
3213
|
{
|
|
2795
3214
|
size: "small",
|
|
2796
3215
|
label: "Changed by",
|
|
@@ -2803,9 +3222,9 @@ var init_AuditLog = __esm({
|
|
|
2803
3222
|
}
|
|
2804
3223
|
))
|
|
2805
3224
|
},
|
|
2806
|
-
error && /* @__PURE__ */
|
|
2807
|
-
/* @__PURE__ */
|
|
2808
|
-
/* @__PURE__ */
|
|
3225
|
+
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)),
|
|
3226
|
+
/* @__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)))),
|
|
3227
|
+
/* @__PURE__ */ import_react9.default.createElement(
|
|
2809
3228
|
import_TablePagination.default,
|
|
2810
3229
|
{
|
|
2811
3230
|
component: "div",
|
|
@@ -2826,6 +3245,35 @@ var init_AuditLog = __esm({
|
|
|
2826
3245
|
}
|
|
2827
3246
|
});
|
|
2828
3247
|
|
|
3248
|
+
// src/permissions.ts
|
|
3249
|
+
var import_plugin_permission_common, litellmTeamCreatePermission, litellmTeamManagePermission, litellmTeamMembersManagePermission, litellmTeamKnowledgebaseManagePermission, litellmTeamMcpManagePermission;
|
|
3250
|
+
var init_permissions = __esm({
|
|
3251
|
+
"src/permissions.ts"() {
|
|
3252
|
+
"use strict";
|
|
3253
|
+
import_plugin_permission_common = require("@backstage/plugin-permission-common");
|
|
3254
|
+
litellmTeamCreatePermission = (0, import_plugin_permission_common.createPermission)({
|
|
3255
|
+
name: "litellm.team.create",
|
|
3256
|
+
attributes: { action: "create" }
|
|
3257
|
+
});
|
|
3258
|
+
litellmTeamManagePermission = (0, import_plugin_permission_common.createPermission)({
|
|
3259
|
+
name: "litellm.team.manage",
|
|
3260
|
+
attributes: { action: "update" }
|
|
3261
|
+
});
|
|
3262
|
+
litellmTeamMembersManagePermission = (0, import_plugin_permission_common.createPermission)({
|
|
3263
|
+
name: "litellm.team.members.manage",
|
|
3264
|
+
attributes: { action: "update" }
|
|
3265
|
+
});
|
|
3266
|
+
litellmTeamKnowledgebaseManagePermission = (0, import_plugin_permission_common.createPermission)({
|
|
3267
|
+
name: "litellm.team.knowledgebase.manage",
|
|
3268
|
+
attributes: { action: "update" }
|
|
3269
|
+
});
|
|
3270
|
+
litellmTeamMcpManagePermission = (0, import_plugin_permission_common.createPermission)({
|
|
3271
|
+
name: "litellm.team.mcp.manage",
|
|
3272
|
+
attributes: { action: "update" }
|
|
3273
|
+
});
|
|
3274
|
+
}
|
|
3275
|
+
});
|
|
3276
|
+
|
|
2829
3277
|
// src/components/LiteLLMPage.tsx
|
|
2830
3278
|
var LiteLLMPage_exports = {};
|
|
2831
3279
|
__export(LiteLLMPage_exports, {
|
|
@@ -2845,45 +3293,50 @@ function initDateRange() {
|
|
|
2845
3293
|
else start.setDate(start.getDate() - 7);
|
|
2846
3294
|
return { start, end };
|
|
2847
3295
|
}
|
|
2848
|
-
var
|
|
3296
|
+
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
3297
|
var init_LiteLLMPage = __esm({
|
|
2850
3298
|
"src/components/LiteLLMPage.tsx"() {
|
|
2851
3299
|
"use strict";
|
|
2852
|
-
|
|
2853
|
-
|
|
3300
|
+
import_react10 = __toESM(require("react"));
|
|
3301
|
+
import_Box10 = __toESM(require("@mui/material/Box"));
|
|
2854
3302
|
import_Snackbar2 = __toESM(require("@mui/material/Snackbar"));
|
|
2855
|
-
|
|
3303
|
+
import_Alert5 = __toESM(require("@mui/material/Alert"));
|
|
2856
3304
|
import_CircularProgress4 = __toESM(require("@mui/material/CircularProgress"));
|
|
2857
|
-
|
|
3305
|
+
import_Typography10 = __toESM(require("@mui/material/Typography"));
|
|
2858
3306
|
import_Paper5 = __toESM(require("@mui/material/Paper"));
|
|
2859
3307
|
import_Tabs2 = __toESM(require("@mui/material/Tabs"));
|
|
2860
3308
|
import_Tab2 = __toESM(require("@mui/material/Tab"));
|
|
3309
|
+
import_Button6 = __toESM(require("@mui/material/Button"));
|
|
2861
3310
|
import_react_use2 = require("react-use");
|
|
2862
3311
|
import_core_plugin_api2 = require("@backstage/core-plugin-api");
|
|
3312
|
+
import_plugin_permission_react = require("@backstage/plugin-permission-react");
|
|
2863
3313
|
init_DashboardHeader();
|
|
2864
3314
|
init_KeysTable();
|
|
2865
3315
|
init_GenerateKeyDialog();
|
|
3316
|
+
init_ManageTeamDialog();
|
|
2866
3317
|
init_UsageStats();
|
|
2867
3318
|
init_TeamUsage();
|
|
2868
3319
|
init_ModelsTable();
|
|
2869
3320
|
init_AuditLog();
|
|
2870
3321
|
init_api();
|
|
3322
|
+
init_permissions();
|
|
2871
3323
|
PERIOD_LS_KEY2 = "litellm_usage_period";
|
|
2872
3324
|
LiteLLMPage = () => {
|
|
2873
3325
|
const api = (0, import_core_plugin_api2.useApi)(liteLlmApiRef);
|
|
2874
|
-
const [dateRange, setDateRange] = (0,
|
|
2875
|
-
const [currentPreset, setCurrentPreset] = (0,
|
|
3326
|
+
const [dateRange, setDateRange] = (0, import_react10.useState)(initDateRange);
|
|
3327
|
+
const [currentPreset, setCurrentPreset] = (0, import_react10.useState)(() => {
|
|
2876
3328
|
try {
|
|
2877
3329
|
return localStorage.getItem(PERIOD_LS_KEY2) ?? "7d";
|
|
2878
3330
|
} catch {
|
|
2879
3331
|
return "7d";
|
|
2880
3332
|
}
|
|
2881
3333
|
});
|
|
2882
|
-
const [activeTab, setActiveTab] = (0,
|
|
2883
|
-
const [snackbar, setSnackbar] = (0,
|
|
2884
|
-
const [generateDialogOpen, setGenerateDialogOpen] = (0,
|
|
2885
|
-
const [
|
|
2886
|
-
const [
|
|
3334
|
+
const [activeTab, setActiveTab] = (0, import_react10.useState)("overview");
|
|
3335
|
+
const [snackbar, setSnackbar] = (0, import_react10.useState)(null);
|
|
3336
|
+
const [generateDialogOpen, setGenerateDialogOpen] = (0, import_react10.useState)(false);
|
|
3337
|
+
const [manageTeam, setManageTeam] = (0, import_react10.useState)(null);
|
|
3338
|
+
const [teamUsageCache, setTeamUsageCache] = (0, import_react10.useState)({});
|
|
3339
|
+
const [teamUsageLoading, setTeamUsageLoading] = (0, import_react10.useState)({});
|
|
2887
3340
|
const { value: userInfo, loading: userLoading, error: userError } = (0, import_react_use2.useAsync)(
|
|
2888
3341
|
() => api.getUserInfo(),
|
|
2889
3342
|
[api]
|
|
@@ -2903,12 +3356,31 @@ var init_LiteLLMPage = __esm({
|
|
|
2903
3356
|
() => api.listModels().catch(() => []),
|
|
2904
3357
|
[api]
|
|
2905
3358
|
);
|
|
2906
|
-
const { value: allTeams, loading: teamsLoading } = (0, import_react_use2.
|
|
3359
|
+
const { value: allTeams, loading: teamsLoading, retry: refreshTeams } = (0, import_react_use2.useAsyncRetry)(
|
|
2907
3360
|
() => api.getTeams().catch(() => []),
|
|
2908
3361
|
[api]
|
|
2909
3362
|
);
|
|
2910
3363
|
const { value: liteLlmConfig } = (0, import_react_use2.useAsync)(() => api.getConfig(), [api]);
|
|
2911
|
-
const
|
|
3364
|
+
const { value: managedTeams } = (0, import_react_use2.useAsync)(
|
|
3365
|
+
async () => liteLlmConfig?.teamManagement?.enabled ? api.getManagedTeams().catch(() => []) : [],
|
|
3366
|
+
[api, liteLlmConfig]
|
|
3367
|
+
);
|
|
3368
|
+
const { allowed: canCreateTeam } = (0, import_plugin_permission_react.usePermission)({ permission: litellmTeamCreatePermission });
|
|
3369
|
+
const { allowed: canManageTeam } = (0, import_plugin_permission_react.usePermission)({ permission: litellmTeamManagePermission });
|
|
3370
|
+
const { allowed: canManageMembers } = (0, import_plugin_permission_react.usePermission)({ permission: litellmTeamMembersManagePermission });
|
|
3371
|
+
const { allowed: canManageKnowledgeBases } = (0, import_plugin_permission_react.usePermission)({ permission: litellmTeamKnowledgebaseManagePermission });
|
|
3372
|
+
const { allowed: canManageMcpServers } = (0, import_plugin_permission_react.usePermission)({ permission: litellmTeamMcpManagePermission });
|
|
3373
|
+
const teamMgmtEnabled = liteLlmConfig?.teamManagement?.enabled ?? false;
|
|
3374
|
+
const objectPermsEnabled = liteLlmConfig?.teamManagement?.objectPermissionsEnabled ?? false;
|
|
3375
|
+
const { value: vectorStores } = (0, import_react_use2.useAsync)(
|
|
3376
|
+
async () => objectPermsEnabled ? api.getVectorStores().catch(() => []) : [],
|
|
3377
|
+
[api, objectPermsEnabled]
|
|
3378
|
+
);
|
|
3379
|
+
const { value: mcpServers } = (0, import_react_use2.useAsync)(
|
|
3380
|
+
async () => objectPermsEnabled ? api.getMcpServers().catch(() => []) : [],
|
|
3381
|
+
[api, objectPermsEnabled]
|
|
3382
|
+
);
|
|
3383
|
+
const teams = (0, import_react10.useMemo)(() => {
|
|
2912
3384
|
if (!allTeams?.length) return [];
|
|
2913
3385
|
if (!userInfo) return allTeams;
|
|
2914
3386
|
const userId = userInfo.user_id;
|
|
@@ -2925,7 +3397,7 @@ var init_LiteLLMPage = __esm({
|
|
|
2925
3397
|
const endDate = dateRange.end.toISOString().split("T")[0];
|
|
2926
3398
|
return api.getUsage(startDate, endDate);
|
|
2927
3399
|
}, [api, dateRange]);
|
|
2928
|
-
const handleDateRangeChange = (0,
|
|
3400
|
+
const handleDateRangeChange = (0, import_react10.useCallback)((range, preset) => {
|
|
2929
3401
|
setDateRange(range);
|
|
2930
3402
|
if (preset) {
|
|
2931
3403
|
setCurrentPreset(preset);
|
|
@@ -2933,7 +3405,7 @@ var init_LiteLLMPage = __esm({
|
|
|
2933
3405
|
setTeamUsageCache({});
|
|
2934
3406
|
setTeamUsageLoading({});
|
|
2935
3407
|
}, [setDateRange, setCurrentPreset]);
|
|
2936
|
-
const loadTeamUsage = (0,
|
|
3408
|
+
const loadTeamUsage = (0, import_react10.useCallback)(async (teamId) => {
|
|
2937
3409
|
if (teamUsageCache[teamId] !== void 0 || teamUsageLoading[teamId]) return;
|
|
2938
3410
|
setTeamUsageLoading((prev) => ({ ...prev, [teamId]: true }));
|
|
2939
3411
|
try {
|
|
@@ -2947,7 +3419,7 @@ var init_LiteLLMPage = __esm({
|
|
|
2947
3419
|
setTeamUsageLoading((prev) => ({ ...prev, [teamId]: false }));
|
|
2948
3420
|
}
|
|
2949
3421
|
}, [api, dateRange, teamUsageCache, teamUsageLoading]);
|
|
2950
|
-
const allowedModels = (0,
|
|
3422
|
+
const allowedModels = (0, import_react10.useMemo)(() => {
|
|
2951
3423
|
if (!allModels?.length) return [];
|
|
2952
3424
|
const userModels = userInfo?.models;
|
|
2953
3425
|
const teamModels = teams?.flatMap((t) => t.models ?? []);
|
|
@@ -2960,7 +3432,7 @@ var init_LiteLLMPage = __esm({
|
|
|
2960
3432
|
]);
|
|
2961
3433
|
return allModels.filter((m) => allowed.has(m.model_name));
|
|
2962
3434
|
}, [allModels, userInfo, teams]);
|
|
2963
|
-
const handleGenerateKey = (0,
|
|
3435
|
+
const handleGenerateKey = (0, import_react10.useCallback)(
|
|
2964
3436
|
async (request) => {
|
|
2965
3437
|
try {
|
|
2966
3438
|
const response = await api.generateKey(request);
|
|
@@ -2974,7 +3446,7 @@ var init_LiteLLMPage = __esm({
|
|
|
2974
3446
|
},
|
|
2975
3447
|
[api, refreshKeys]
|
|
2976
3448
|
);
|
|
2977
|
-
const handleUpdateKey = (0,
|
|
3449
|
+
const handleUpdateKey = (0, import_react10.useCallback)(
|
|
2978
3450
|
async (keyId, request) => {
|
|
2979
3451
|
try {
|
|
2980
3452
|
await api.updateKey(keyId, request);
|
|
@@ -2987,7 +3459,7 @@ var init_LiteLLMPage = __esm({
|
|
|
2987
3459
|
},
|
|
2988
3460
|
[api, refreshKeys]
|
|
2989
3461
|
);
|
|
2990
|
-
const handleBlockKey = (0,
|
|
3462
|
+
const handleBlockKey = (0, import_react10.useCallback)(
|
|
2991
3463
|
async (keyId) => {
|
|
2992
3464
|
try {
|
|
2993
3465
|
await api.blockKey(keyId);
|
|
@@ -2999,7 +3471,7 @@ var init_LiteLLMPage = __esm({
|
|
|
2999
3471
|
},
|
|
3000
3472
|
[api, refreshKeys]
|
|
3001
3473
|
);
|
|
3002
|
-
const handleUnblockKey = (0,
|
|
3474
|
+
const handleUnblockKey = (0, import_react10.useCallback)(
|
|
3003
3475
|
async (keyId) => {
|
|
3004
3476
|
try {
|
|
3005
3477
|
await api.unblockKey(keyId);
|
|
@@ -3011,7 +3483,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3011
3483
|
},
|
|
3012
3484
|
[api, refreshKeys]
|
|
3013
3485
|
);
|
|
3014
|
-
const handleResetKeySpend = (0,
|
|
3486
|
+
const handleResetKeySpend = (0, import_react10.useCallback)(
|
|
3015
3487
|
async (keyId) => {
|
|
3016
3488
|
try {
|
|
3017
3489
|
await api.resetKeySpend(keyId);
|
|
@@ -3023,7 +3495,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3023
3495
|
},
|
|
3024
3496
|
[api, refreshKeys]
|
|
3025
3497
|
);
|
|
3026
|
-
const handleDeleteKey = (0,
|
|
3498
|
+
const handleDeleteKey = (0, import_react10.useCallback)(
|
|
3027
3499
|
async (keyId) => {
|
|
3028
3500
|
try {
|
|
3029
3501
|
await api.deleteKey(keyId);
|
|
@@ -3040,7 +3512,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3040
3512
|
},
|
|
3041
3513
|
[api, refreshKeys]
|
|
3042
3514
|
);
|
|
3043
|
-
const handlePruneExpiredKeys = (0,
|
|
3515
|
+
const handlePruneExpiredKeys = (0, import_react10.useCallback)(
|
|
3044
3516
|
async () => {
|
|
3045
3517
|
try {
|
|
3046
3518
|
const result = await api.pruneExpiredKeys();
|
|
@@ -3055,14 +3527,14 @@ var init_LiteLLMPage = __esm({
|
|
|
3055
3527
|
);
|
|
3056
3528
|
const isInitialLoading = userLoading && !userInfo;
|
|
3057
3529
|
if (isInitialLoading) {
|
|
3058
|
-
return /* @__PURE__ */
|
|
3530
|
+
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
3531
|
}
|
|
3060
3532
|
if (userError || !userInfo) {
|
|
3061
3533
|
const isProvisioningEnabled = userError?.body?.provisioning === true;
|
|
3062
3534
|
const hint = userError?.body?.hint;
|
|
3063
|
-
return /* @__PURE__ */
|
|
3535
|
+
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
3536
|
}
|
|
3065
|
-
const pageTabs = /* @__PURE__ */
|
|
3537
|
+
const pageTabs = /* @__PURE__ */ import_react10.default.createElement(
|
|
3066
3538
|
import_Tabs2.default,
|
|
3067
3539
|
{
|
|
3068
3540
|
value: activeTab,
|
|
@@ -3075,13 +3547,13 @@ var init_LiteLLMPage = __esm({
|
|
|
3075
3547
|
'& [class*="MuiTab-root"]': { minHeight: 44, textTransform: "none", fontSize: 14 }
|
|
3076
3548
|
}
|
|
3077
3549
|
},
|
|
3078
|
-
/* @__PURE__ */
|
|
3079
|
-
/* @__PURE__ */
|
|
3080
|
-
/* @__PURE__ */
|
|
3081
|
-
/* @__PURE__ */
|
|
3082
|
-
userInfo.can_view_audit && /* @__PURE__ */
|
|
3550
|
+
/* @__PURE__ */ import_react10.default.createElement(import_Tab2.default, { label: "Overview", value: "overview" }),
|
|
3551
|
+
/* @__PURE__ */ import_react10.default.createElement(import_Tab2.default, { label: "Keys", value: "keys" }),
|
|
3552
|
+
/* @__PURE__ */ import_react10.default.createElement(import_Tab2.default, { label: "Teams", value: "teams" }),
|
|
3553
|
+
/* @__PURE__ */ import_react10.default.createElement(import_Tab2.default, { label: "Models", value: "models" }),
|
|
3554
|
+
userInfo.can_view_audit && /* @__PURE__ */ import_react10.default.createElement(import_Tab2.default, { label: "Audit Log", value: "audit" })
|
|
3083
3555
|
);
|
|
3084
|
-
return /* @__PURE__ */
|
|
3556
|
+
return /* @__PURE__ */ import_react10.default.createElement(import_Box10.default, { sx: { p: 3, display: "flex", flexDirection: "column", gap: 2 } }, /* @__PURE__ */ import_react10.default.createElement(
|
|
3085
3557
|
DashboardHeader,
|
|
3086
3558
|
{
|
|
3087
3559
|
userInfo,
|
|
@@ -3091,7 +3563,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3091
3563
|
onGenerateKeyClick: () => setGenerateDialogOpen(true),
|
|
3092
3564
|
tabs: pageTabs
|
|
3093
3565
|
}
|
|
3094
|
-
), activeTab === "overview" && /* @__PURE__ */
|
|
3566
|
+
), activeTab === "overview" && /* @__PURE__ */ import_react10.default.createElement(
|
|
3095
3567
|
UsageStats,
|
|
3096
3568
|
{
|
|
3097
3569
|
usage: usage ?? null,
|
|
@@ -3102,7 +3574,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3102
3574
|
loading: usageLoading,
|
|
3103
3575
|
userInfo
|
|
3104
3576
|
}
|
|
3105
|
-
), activeTab === "keys" && /* @__PURE__ */
|
|
3577
|
+
), activeTab === "keys" && /* @__PURE__ */ import_react10.default.createElement(
|
|
3106
3578
|
KeysTable,
|
|
3107
3579
|
{
|
|
3108
3580
|
keys: keys ?? [],
|
|
@@ -3116,25 +3588,40 @@ var init_LiteLLMPage = __esm({
|
|
|
3116
3588
|
onDeleteKey: handleDeleteKey,
|
|
3117
3589
|
onPruneExpiredKeys: handlePruneExpiredKeys
|
|
3118
3590
|
}
|
|
3119
|
-
), activeTab === "teams" &&
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3591
|
+
), activeTab === "teams" && (() => {
|
|
3592
|
+
const teamsById = /* @__PURE__ */ new Map();
|
|
3593
|
+
for (const t of managedTeams ?? []) teamsById.set(t.team_id, t);
|
|
3594
|
+
for (const t of teams ?? []) teamsById.set(t.team_id, t);
|
|
3595
|
+
const visibleTeams = Array.from(teamsById.values());
|
|
3596
|
+
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(
|
|
3597
|
+
import_Button6.default,
|
|
3598
|
+
{
|
|
3599
|
+
variant: "contained",
|
|
3600
|
+
onClick: () => setManageTeam({ mode: "create" })
|
|
3127
3601
|
},
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3602
|
+
"Create Team"
|
|
3603
|
+
)), /* @__PURE__ */ import_react10.default.createElement(
|
|
3604
|
+
TeamUsage,
|
|
3605
|
+
{
|
|
3606
|
+
teams: visibleTeams,
|
|
3607
|
+
loading: teamsLoading,
|
|
3608
|
+
getTeamUsage: (teamId) => {
|
|
3609
|
+
if (teamUsageCache[teamId] === void 0) loadTeamUsage(teamId);
|
|
3610
|
+
return teamUsageCache[teamId] ?? null;
|
|
3611
|
+
},
|
|
3612
|
+
getTeamUsageLoading: (teamId) => teamUsageLoading[teamId] ?? false,
|
|
3613
|
+
canManage: teamMgmtEnabled && canManageTeam,
|
|
3614
|
+
onEditTeam: (t) => setManageTeam({ mode: "edit", team: t })
|
|
3615
|
+
}
|
|
3616
|
+
));
|
|
3617
|
+
})(), activeTab === "models" && /* @__PURE__ */ import_react10.default.createElement(
|
|
3131
3618
|
ModelsTable,
|
|
3132
3619
|
{
|
|
3133
3620
|
allModels: allModels ?? [],
|
|
3134
3621
|
teams: teams ?? [],
|
|
3135
3622
|
loading: modelsLoading
|
|
3136
3623
|
}
|
|
3137
|
-
), activeTab === "audit" && userInfo.can_view_audit && /* @__PURE__ */
|
|
3624
|
+
), activeTab === "audit" && userInfo.can_view_audit && /* @__PURE__ */ import_react10.default.createElement(AuditLog, { api }), /* @__PURE__ */ import_react10.default.createElement(
|
|
3138
3625
|
GenerateKeyDialog,
|
|
3139
3626
|
{
|
|
3140
3627
|
open: generateDialogOpen,
|
|
@@ -3147,7 +3634,69 @@ var init_LiteLLMPage = __esm({
|
|
|
3147
3634
|
onGenerateKey: handleGenerateKey,
|
|
3148
3635
|
onGetConfig: () => api.getConfig()
|
|
3149
3636
|
}
|
|
3150
|
-
), /* @__PURE__ */
|
|
3637
|
+
), /* @__PURE__ */ import_react10.default.createElement(
|
|
3638
|
+
ManageTeamDialog,
|
|
3639
|
+
{
|
|
3640
|
+
open: !!manageTeam,
|
|
3641
|
+
onClose: () => setManageTeam(null),
|
|
3642
|
+
mode: manageTeam?.mode ?? "create",
|
|
3643
|
+
team: manageTeam?.team,
|
|
3644
|
+
allModels: allModels ?? [],
|
|
3645
|
+
config: liteLlmConfig,
|
|
3646
|
+
onSubmit: async (payload) => {
|
|
3647
|
+
if (manageTeam?.mode === "edit" && manageTeam.team) {
|
|
3648
|
+
await api.updateTeam(manageTeam.team.team_id, payload);
|
|
3649
|
+
} else {
|
|
3650
|
+
await api.createTeam(payload);
|
|
3651
|
+
}
|
|
3652
|
+
setSnackbar({ message: "Team saved", severity: "success" });
|
|
3653
|
+
refreshTeams();
|
|
3654
|
+
},
|
|
3655
|
+
canManageMembers: teamMgmtEnabled && canManageMembers,
|
|
3656
|
+
onAddMember: async (userEntityRef, maxBudgetInTeam) => {
|
|
3657
|
+
if (!manageTeam?.team) return;
|
|
3658
|
+
const updated = await api.addTeamMember(manageTeam.team.team_id, {
|
|
3659
|
+
userEntityRef,
|
|
3660
|
+
...maxBudgetInTeam ? { maxBudgetInTeam } : {}
|
|
3661
|
+
});
|
|
3662
|
+
setManageTeam((s) => s && s.team ? { ...s, team: updated } : s);
|
|
3663
|
+
refreshTeams();
|
|
3664
|
+
},
|
|
3665
|
+
onRemoveMember: async (userEntityRef) => {
|
|
3666
|
+
if (!manageTeam?.team) return;
|
|
3667
|
+
const updated = await api.removeTeamMember(
|
|
3668
|
+
manageTeam.team.team_id,
|
|
3669
|
+
userEntityRef
|
|
3670
|
+
);
|
|
3671
|
+
setManageTeam((s) => s && s.team ? { ...s, team: updated } : s);
|
|
3672
|
+
refreshTeams();
|
|
3673
|
+
},
|
|
3674
|
+
canManageKnowledgeBases: teamMgmtEnabled && objectPermsEnabled && canManageKnowledgeBases,
|
|
3675
|
+
vectorStores: vectorStores ?? [],
|
|
3676
|
+
onSaveKnowledgeBases: async (vectorStoreIds) => {
|
|
3677
|
+
if (!manageTeam?.team) return;
|
|
3678
|
+
const updated = await api.setTeamKnowledgeBases(
|
|
3679
|
+
manageTeam.team.team_id,
|
|
3680
|
+
vectorStoreIds
|
|
3681
|
+
);
|
|
3682
|
+
setManageTeam((s) => s && s.team ? { ...s, team: updated } : s);
|
|
3683
|
+
setSnackbar({ message: "Knowledge bases updated", severity: "success" });
|
|
3684
|
+
refreshTeams();
|
|
3685
|
+
},
|
|
3686
|
+
canManageMcpServers: teamMgmtEnabled && objectPermsEnabled && canManageMcpServers,
|
|
3687
|
+
mcpServers: mcpServers ?? [],
|
|
3688
|
+
onSaveMcpServers: async (mcpServerIds) => {
|
|
3689
|
+
if (!manageTeam?.team) return;
|
|
3690
|
+
const updated = await api.setTeamMcpServers(
|
|
3691
|
+
manageTeam.team.team_id,
|
|
3692
|
+
mcpServerIds
|
|
3693
|
+
);
|
|
3694
|
+
setManageTeam((s) => s && s.team ? { ...s, team: updated } : s);
|
|
3695
|
+
setSnackbar({ message: "MCP servers updated", severity: "success" });
|
|
3696
|
+
refreshTeams();
|
|
3697
|
+
}
|
|
3698
|
+
}
|
|
3699
|
+
), /* @__PURE__ */ import_react10.default.createElement(
|
|
3151
3700
|
import_Snackbar2.default,
|
|
3152
3701
|
{
|
|
3153
3702
|
open: !!snackbar,
|
|
@@ -3155,7 +3704,7 @@ var init_LiteLLMPage = __esm({
|
|
|
3155
3704
|
onClose: () => setSnackbar(null),
|
|
3156
3705
|
anchorOrigin: { vertical: "bottom", horizontal: "right" }
|
|
3157
3706
|
},
|
|
3158
|
-
snackbar ? /* @__PURE__ */
|
|
3707
|
+
snackbar ? /* @__PURE__ */ import_react10.default.createElement(import_Alert5.default, { severity: snackbar.severity, onClose: () => setSnackbar(null) }, snackbar.message) : void 0
|
|
3159
3708
|
));
|
|
3160
3709
|
};
|
|
3161
3710
|
}
|
|
@@ -3170,16 +3719,22 @@ __export(index_exports, {
|
|
|
3170
3719
|
LiteLLMHomeWidget: () => LiteLLMHomeWidget,
|
|
3171
3720
|
LiteLLMPage: () => LiteLLMPage,
|
|
3172
3721
|
LiteLlmApi: () => LiteLlmApi,
|
|
3722
|
+
ManageTeamDialog: () => ManageTeamDialog,
|
|
3173
3723
|
TeamUsage: () => TeamUsage,
|
|
3174
3724
|
UsageStats: () => UsageStats,
|
|
3175
3725
|
liteLlmApiRef: () => liteLlmApiRef,
|
|
3176
|
-
litellmPlugin: () => litellmPlugin
|
|
3726
|
+
litellmPlugin: () => litellmPlugin,
|
|
3727
|
+
litellmTeamCreatePermission: () => litellmTeamCreatePermission,
|
|
3728
|
+
litellmTeamKnowledgebaseManagePermission: () => litellmTeamKnowledgebaseManagePermission,
|
|
3729
|
+
litellmTeamManagePermission: () => litellmTeamManagePermission,
|
|
3730
|
+
litellmTeamMcpManagePermission: () => litellmTeamMcpManagePermission,
|
|
3731
|
+
litellmTeamMembersManagePermission: () => litellmTeamMembersManagePermission
|
|
3177
3732
|
});
|
|
3178
3733
|
module.exports = __toCommonJS(index_exports);
|
|
3179
3734
|
|
|
3180
3735
|
// src/plugin.tsx
|
|
3181
|
-
var
|
|
3182
|
-
var
|
|
3736
|
+
var import_react11 = __toESM(require("react"));
|
|
3737
|
+
var import_icons_material7 = require("@mui/icons-material");
|
|
3183
3738
|
var import_frontend_plugin_api = require("@backstage/frontend-plugin-api");
|
|
3184
3739
|
init_api();
|
|
3185
3740
|
var liteLlmApi = import_frontend_plugin_api.ApiBlueprint.make({
|
|
@@ -3193,10 +3748,10 @@ var liteLlmPage = import_frontend_plugin_api.PageBlueprint.make({
|
|
|
3193
3748
|
params: {
|
|
3194
3749
|
path: "/litellm",
|
|
3195
3750
|
title: "LiteLLM",
|
|
3196
|
-
icon: /* @__PURE__ */
|
|
3751
|
+
icon: /* @__PURE__ */ import_react11.default.createElement(import_icons_material7.TrendingUp, null),
|
|
3197
3752
|
loader: async () => {
|
|
3198
3753
|
const { LiteLLMPage: LiteLLMPage2 } = await Promise.resolve().then(() => (init_LiteLLMPage(), LiteLLMPage_exports));
|
|
3199
|
-
return /* @__PURE__ */
|
|
3754
|
+
return /* @__PURE__ */ import_react11.default.createElement(LiteLLMPage2, null);
|
|
3200
3755
|
}
|
|
3201
3756
|
}
|
|
3202
3757
|
});
|
|
@@ -3213,16 +3768,16 @@ init_UsageStats();
|
|
|
3213
3768
|
init_TeamUsage();
|
|
3214
3769
|
|
|
3215
3770
|
// src/components/LiteLLMHomeWidget.tsx
|
|
3216
|
-
var
|
|
3771
|
+
var import_react12 = __toESM(require("react"));
|
|
3217
3772
|
var import_Paper6 = __toESM(require("@mui/material/Paper"));
|
|
3218
|
-
var
|
|
3219
|
-
var
|
|
3773
|
+
var import_Box11 = __toESM(require("@mui/material/Box"));
|
|
3774
|
+
var import_Typography11 = __toESM(require("@mui/material/Typography"));
|
|
3220
3775
|
var import_FormControl2 = __toESM(require("@mui/material/FormControl"));
|
|
3221
3776
|
var import_Select2 = __toESM(require("@mui/material/Select"));
|
|
3222
3777
|
var import_MenuItem5 = __toESM(require("@mui/material/MenuItem"));
|
|
3223
3778
|
var import_Grid2 = __toESM(require("@mui/material/Grid"));
|
|
3224
3779
|
var import_CircularProgress5 = __toESM(require("@mui/material/CircularProgress"));
|
|
3225
|
-
var
|
|
3780
|
+
var import_Alert6 = __toESM(require("@mui/material/Alert"));
|
|
3226
3781
|
var import_recharts3 = require("recharts");
|
|
3227
3782
|
var import_core_plugin_api3 = require("@backstage/core-plugin-api");
|
|
3228
3783
|
init_api();
|
|
@@ -3239,19 +3794,19 @@ function presetToDateRange(preset) {
|
|
|
3239
3794
|
}
|
|
3240
3795
|
return { start, end };
|
|
3241
3796
|
}
|
|
3242
|
-
var Kpi = ({ label, value }) => /* @__PURE__ */
|
|
3797
|
+
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
3798
|
var LiteLLMHomeWidget = ({
|
|
3244
3799
|
defaultPeriod = "7d",
|
|
3245
3800
|
title = "LiteLLM Usage"
|
|
3246
3801
|
}) => {
|
|
3247
3802
|
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,
|
|
3803
|
+
const [period, setPeriod] = (0, import_react12.useState)(defaultPeriod);
|
|
3804
|
+
const [loading, setLoading] = (0, import_react12.useState)(true);
|
|
3805
|
+
const [usageError, setUsageError] = (0, import_react12.useState)(null);
|
|
3806
|
+
const [keysError, setKeysError] = (0, import_react12.useState)(null);
|
|
3807
|
+
const [usage, setUsage] = (0, import_react12.useState)(null);
|
|
3808
|
+
const [keys, setKeys] = (0, import_react12.useState)([]);
|
|
3809
|
+
(0, import_react12.useEffect)(() => {
|
|
3255
3810
|
let cancelled = false;
|
|
3256
3811
|
setLoading(true);
|
|
3257
3812
|
setUsageError(null);
|
|
@@ -3288,17 +3843,17 @@ var LiteLLMHomeWidget = ({
|
|
|
3288
3843
|
spend: d.spend
|
|
3289
3844
|
}));
|
|
3290
3845
|
const hasSparkline = dailyData.length > 0;
|
|
3291
|
-
return /* @__PURE__ */
|
|
3846
|
+
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
3847
|
import_Select2.default,
|
|
3293
3848
|
{
|
|
3294
3849
|
value: period,
|
|
3295
3850
|
onChange: (e) => setPeriod(e.target.value),
|
|
3296
3851
|
displayEmpty: true
|
|
3297
3852
|
},
|
|
3298
|
-
/* @__PURE__ */
|
|
3299
|
-
/* @__PURE__ */
|
|
3300
|
-
/* @__PURE__ */
|
|
3301
|
-
))), loading && /* @__PURE__ */
|
|
3853
|
+
/* @__PURE__ */ import_react12.default.createElement(import_MenuItem5.default, { value: "today" }, "Today"),
|
|
3854
|
+
/* @__PURE__ */ import_react12.default.createElement(import_MenuItem5.default, { value: "7d" }, "7d"),
|
|
3855
|
+
/* @__PURE__ */ import_react12.default.createElement(import_MenuItem5.default, { value: "30d" }, "30d")
|
|
3856
|
+
))), 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
3857
|
import_recharts3.Area,
|
|
3303
3858
|
{
|
|
3304
3859
|
type: "monotone",
|
|
@@ -3314,5 +3869,7 @@ var LiteLLMHomeWidget = ({
|
|
|
3314
3869
|
|
|
3315
3870
|
// src/index.ts
|
|
3316
3871
|
init_GenerateKeyDialog();
|
|
3872
|
+
init_ManageTeamDialog();
|
|
3317
3873
|
init_api();
|
|
3874
|
+
init_permissions();
|
|
3318
3875
|
//# sourceMappingURL=index.cjs.js.map
|