@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/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 import_react5, import_Paper3, import_Box5, import_Typography5, import_TextField3, 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;
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
- import_react5 = __toESM(require("react"));
2293
+ import_react6 = __toESM(require("react"));
1893
2294
  import_Paper3 = __toESM(require("@mui/material/Paper"));
1894
- import_Box5 = __toESM(require("@mui/material/Box"));
1895
- import_Typography5 = __toESM(require("@mui/material/Typography"));
1896
- import_TextField3 = __toESM(require("@mui/material/TextField"));
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__ */ import_react5.default.createElement(import_Skeleton3.default, { variant: "rounded", height });
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__ */ import_react5.default.createElement(ChartSkeleton, { height });
1921
- if (empty) return /* @__PURE__ */ import_react5.default.createElement(EmptyState, { message: "No data for this period", height });
1922
- return /* @__PURE__ */ import_react5.default.createElement(import_react5.default.Fragment, null, children);
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__ */ import_react5.default.createElement(import_Typography5.default, { variant: "body2", color: "text.secondary" }, "\u2014");
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__ */ import_react5.default.createElement(import_Box5.default, { display: "flex", alignItems: "center", gap: 1 }, /* @__PURE__ */ import_react5.default.createElement(import_Box5.default, { width: 72, flexShrink: 0 }, /* @__PURE__ */ import_react5.default.createElement(Meter, { value: rate * 100, tone, height: 4 })), /* @__PURE__ */ import_react5.default.createElement(
1928
- import_Typography5.default,
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, import_react5.useState)("all");
1947
- const [tab, setTab] = (0, import_react5.useState)("costs");
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, import_react5.useMemo)(
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, import_react5.useMemo)(() => {
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, import_react5.useMemo)(
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, import_react5.useMemo)(() => {
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, import_react5.useMemo)(() => {
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, import_react5.useMemo)(
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, import_react5.useMemo)(
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, import_react5.useMemo)(
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__ */ import_react5.default.createElement(import_TableRow2.default, null, /* @__PURE__ */ import_react5.default.createElement(import_TableCell2.default, { colSpan: 7, sx: { py: 3 } }, /* @__PURE__ */ import_react5.default.createElement(import_Skeleton3.default, { variant: "rounded", height: 80 })));
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__ */ import_react5.default.createElement(import_TableRow2.default, null, /* @__PURE__ */ import_react5.default.createElement(import_TableCell2.default, { colSpan: 7 }, /* @__PURE__ */ import_react5.default.createElement(EmptyState, { message: "No key activity in this period" })));
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__ */ import_react5.default.createElement(import_TableRow2.default, { key: r.keyHash }, /* @__PURE__ */ import_react5.default.createElement(import_TableCell2.default, null, /* @__PURE__ */ import_react5.default.createElement(import_Typography5.default, { variant: "body2", sx: { fontWeight: 600 } }, r.keyAlias), r.teamId ? /* @__PURE__ */ import_react5.default.createElement(import_Typography5.default, { variant: "caption", color: "text.secondary" }, "team: ", r.teamId) : null), /* @__PURE__ */ import_react5.default.createElement(import_TableCell2.default, null, /* @__PURE__ */ import_react5.default.createElement(import_Box5.default, { display: "flex", gap: 0.5, flexWrap: "wrap" }, r.models.slice(0, 3).map((m) => /* @__PURE__ */ import_react5.default.createElement(TagChip, { key: m, label: m, title: m })), r.models.length > 3 && /* @__PURE__ */ import_react5.default.createElement(TagChip, { label: `+${r.models.length - 3}`, mono: false }))), /* @__PURE__ */ import_react5.default.createElement(import_TableCell2.default, { align: "right" }, fmtUsd(r.spend)), /* @__PURE__ */ import_react5.default.createElement(import_TableCell2.default, { align: "right" }, fmtInt(r.apiRequests)), /* @__PURE__ */ import_react5.default.createElement(import_TableCell2.default, { align: "right" }, fmtCompact(r.totalTokens)), /* @__PURE__ */ import_react5.default.createElement(import_TableCell2.default, { align: "right" }, fmtInt(r.failedRequests)), /* @__PURE__ */ import_react5.default.createElement(import_TableCell2.default, null, /* @__PURE__ */ import_react5.default.createElement(SuccessRateCell, { rate: r.successRate, requests: r.apiRequests }))));
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__ */ import_react5.default.createElement(import_TableRow2.default, null, /* @__PURE__ */ import_react5.default.createElement(import_TableCell2.default, { colSpan: 8, sx: { py: 3 } }, /* @__PURE__ */ import_react5.default.createElement(import_Skeleton3.default, { variant: "rounded", height: 80 })));
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__ */ import_react5.default.createElement(import_TableRow2.default, null, /* @__PURE__ */ import_react5.default.createElement(import_TableCell2.default, { colSpan: 8 }, /* @__PURE__ */ import_react5.default.createElement(EmptyState, { message: "No model activity in this period" })));
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__ */ import_react5.default.createElement(import_TableRow2.default, { key: r.model }, /* @__PURE__ */ import_react5.default.createElement(import_TableCell2.default, null, /* @__PURE__ */ import_react5.default.createElement(import_Box5.default, { display: "flex", alignItems: "center", gap: 1 }, /* @__PURE__ */ import_react5.default.createElement(import_Box5.default, { sx: { width: 8, height: 8, borderRadius: "50%", bgcolor: chartColor(r.model), flexShrink: 0 } }), /* @__PURE__ */ import_react5.default.createElement(import_Typography5.default, { variant: "body2", sx: { fontWeight: 600 } }, r.model))), /* @__PURE__ */ import_react5.default.createElement(import_TableCell2.default, { align: "right" }, fmtUsd(r.spend)), /* @__PURE__ */ import_react5.default.createElement(import_TableCell2.default, { align: "right" }, fmtInt(r.apiRequests)), /* @__PURE__ */ import_react5.default.createElement(import_TableCell2.default, { align: "right" }, fmtInt(r.successfulRequests)), /* @__PURE__ */ import_react5.default.createElement(import_TableCell2.default, { align: "right" }, fmtInt(r.failedRequests)), /* @__PURE__ */ import_react5.default.createElement(import_TableCell2.default, { align: "right" }, fmtCompact(r.promptTokens)), /* @__PURE__ */ import_react5.default.createElement(import_TableCell2.default, { align: "right" }, fmtCompact(r.completionTokens)), /* @__PURE__ */ import_react5.default.createElement(import_TableCell2.default, null, /* @__PURE__ */ import_react5.default.createElement(SuccessRateCell, { rate: r.successRate, requests: r.apiRequests }))));
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__ */ import_react5.default.createElement(
2093
- import_TextField3.default,
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__ */ import_react5.default.createElement(import_MenuItem2.default, { key: p, value: p }, PRESET_LABELS[p]))
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__ */ import_react5.default.createElement(
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__ */ import_react5.default.createElement(MetricStrip, { metrics }),
2112
- /* @__PURE__ */ import_react5.default.createElement(
2113
- import_Box5.default,
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__ */ import_react5.default.createElement(
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__ */ import_react5.default.createElement(
2138
- import_TextField3.default,
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__ */ import_react5.default.createElement(import_MenuItem2.default, { value: "all" }, "All models"),
2148
- models.map((m) => /* @__PURE__ */ import_react5.default.createElement(import_MenuItem2.default, { key: m.model_name, value: m.model_name }, m.model_name))
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__ */ import_react5.default.createElement(import_Grid.default, { container: true, spacing: 2 }, /* @__PURE__ */ import_react5.default.createElement(import_Grid.default, { item: true, xs: 12 }, /* @__PURE__ */ import_react5.default.createElement(ChartCard, { title: "Daily spend by model", height: 260 }, /* @__PURE__ */ import_react5.default.createElement(ChartOrFallback, { loading, empty: modelSpendByDate.length === 0, height: 260 }, /* @__PURE__ */ import_react5.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react5.default.createElement(import_recharts.AreaChart, { data: modelSpendByDate, margin: { top: 4, right: 8, left: 0, bottom: 0 } }, /* @__PURE__ */ import_react5.default.createElement(import_recharts.CartesianGrid, { ...chart.grid }), /* @__PURE__ */ import_react5.default.createElement(import_recharts.XAxis, { dataKey: "date", tickFormatter: fmtDateShort, ...chart.axis }), /* @__PURE__ */ import_react5.default.createElement(import_recharts.YAxis, { tickFormatter: fmtUsdCompact, width: 56, ...chart.axis }), /* @__PURE__ */ import_react5.default.createElement(
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__ */ import_react5.default.createElement(ChartTooltip, { valueFormatter: fmtUsd, hideZero: true })
2556
+ content: /* @__PURE__ */ import_react6.default.createElement(ChartTooltip, { valueFormatter: fmtUsd, hideZero: true })
2156
2557
  }
2157
- ), topSpendModels.map((m) => /* @__PURE__ */ import_react5.default.createElement(
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__ */ import_react5.default.createElement(SeriesLegend, { series: topSpendModels.map((m) => ({ name: m, color: chartColor(m) })) }))), /* @__PURE__ */ import_react5.default.createElement(import_Grid.default, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ import_react5.default.createElement(ChartCard, { title: "Daily token usage" }, /* @__PURE__ */ import_react5.default.createElement(ChartOrFallback, { loading, empty: dailyData.length === 0 }, /* @__PURE__ */ import_react5.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react5.default.createElement(import_recharts.AreaChart, { data: dailyData, margin: { top: 4, right: 8, left: 0, bottom: 0 } }, /* @__PURE__ */ import_react5.default.createElement(import_recharts.CartesianGrid, { ...chart.grid }), /* @__PURE__ */ import_react5.default.createElement(import_recharts.XAxis, { dataKey: "date", tickFormatter: fmtDateShort, ...chart.axis }), /* @__PURE__ */ import_react5.default.createElement(import_recharts.YAxis, { tickFormatter: fmtCompact, width: 48, ...chart.axis }), /* @__PURE__ */ import_react5.default.createElement(import_recharts.Tooltip, { cursor: chart.cursor, content: /* @__PURE__ */ import_react5.default.createElement(ChartTooltip, { valueFormatter: fmtInt }) }), /* @__PURE__ */ import_react5.default.createElement(import_recharts.Legend, { ...chart.legend }), /* @__PURE__ */ import_react5.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_react5.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_react5.default.createElement(import_Grid.default, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ import_react5.default.createElement(ChartCard, { title: "Daily requests" }, /* @__PURE__ */ import_react5.default.createElement(ChartOrFallback, { loading, empty: dailyData.length === 0 }, /* @__PURE__ */ import_react5.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react5.default.createElement(import_recharts.BarChart, { data: dailyData, margin: { top: 4, right: 8, left: 0, bottom: 0 }, barCategoryGap: "30%" }, /* @__PURE__ */ import_react5.default.createElement(import_recharts.CartesianGrid, { ...chart.grid }), /* @__PURE__ */ import_react5.default.createElement(import_recharts.XAxis, { dataKey: "date", tickFormatter: fmtDateShort, ...chart.axis }), /* @__PURE__ */ import_react5.default.createElement(import_recharts.YAxis, { tickFormatter: fmtCompact, width: 48, ...chart.axis }), /* @__PURE__ */ import_react5.default.createElement(import_recharts.Tooltip, { cursor: chart.cursor, content: /* @__PURE__ */ import_react5.default.createElement(ChartTooltip, { valueFormatter: fmtInt }) }), /* @__PURE__ */ import_react5.default.createElement(import_recharts.Legend, { ...chart.legend }), /* @__PURE__ */ import_react5.default.createElement(import_recharts.Bar, { dataKey: "successfulRequests", name: "Successful", fill: SERIES.success, stackId: "r" }), /* @__PURE__ */ import_react5.default.createElement(import_recharts.Bar, { dataKey: "failedRequests", name: "Failed", fill: SERIES.failure, stackId: "r", radius: [3, 3, 0, 0] })))))), /* @__PURE__ */ import_react5.default.createElement(import_Grid.default, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ import_react5.default.createElement(ChartCard, { title: "Daily success rate" }, /* @__PURE__ */ import_react5.default.createElement(ChartOrFallback, { loading, empty: successRateData.length === 0 }, /* @__PURE__ */ import_react5.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react5.default.createElement(import_recharts.LineChart, { data: successRateData, margin: { top: 4, right: 8, left: 0, bottom: 0 } }, /* @__PURE__ */ import_react5.default.createElement(import_recharts.CartesianGrid, { ...chart.grid }), /* @__PURE__ */ import_react5.default.createElement(import_recharts.XAxis, { dataKey: "date", tickFormatter: fmtDateShort, ...chart.axis }), /* @__PURE__ */ import_react5.default.createElement(import_recharts.YAxis, { domain: [0, 100], tickFormatter: (v) => `${v}%`, width: 44, ...chart.axis }), /* @__PURE__ */ import_react5.default.createElement(
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__ */ import_react5.default.createElement(ChartTooltip, { valueFormatter: (v) => `${v}%` })
2574
+ content: /* @__PURE__ */ import_react6.default.createElement(ChartTooltip, { valueFormatter: (v) => `${v}%` })
2174
2575
  }
2175
- ), /* @__PURE__ */ import_react5.default.createElement(import_recharts.ReferenceLine, { y: 100, stroke: SERIES.success, strokeDasharray: "4 4", strokeOpacity: 0.5 }), /* @__PURE__ */ import_react5.default.createElement(
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__ */ import_react5.default.createElement(import_Grid.default, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ import_react5.default.createElement(
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__ */ import_react5.default.createElement(ChartOrFallback, { loading, empty: cumulativeData.length === 0 }, /* @__PURE__ */ import_react5.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react5.default.createElement(import_recharts.AreaChart, { data: cumulativeData, margin: { top: 4, right: 8, left: 0, bottom: 0 } }, /* @__PURE__ */ import_react5.default.createElement(import_recharts.CartesianGrid, { ...chart.grid }), /* @__PURE__ */ import_react5.default.createElement(import_recharts.XAxis, { dataKey: "date", tickFormatter: fmtDateShort, ...chart.axis }), /* @__PURE__ */ import_react5.default.createElement(
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__ */ import_react5.default.createElement(
2601
+ ), /* @__PURE__ */ import_react6.default.createElement(
2201
2602
  import_recharts.Tooltip,
2202
2603
  {
2203
2604
  cursor: chart.cursor,
2204
- content: /* @__PURE__ */ import_react5.default.createElement(ChartTooltip, { valueFormatter: fmtUsd })
2605
+ content: /* @__PURE__ */ import_react6.default.createElement(ChartTooltip, { valueFormatter: fmtUsd })
2205
2606
  }
2206
- ), maxBudget > 0 && /* @__PURE__ */ import_react5.default.createElement(
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__ */ import_react5.default.createElement(
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__ */ import_react5.default.createElement(import_Grid.default, { container: true, spacing: 2 }, /* @__PURE__ */ import_react5.default.createElement(import_Grid.default, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ import_react5.default.createElement(ChartCard, { title: "Spend by model", height: Math.max(200, topModelSpendBars.length * 34) }, /* @__PURE__ */ import_react5.default.createElement(
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__ */ import_react5.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react5.default.createElement(import_recharts.BarChart, { data: topModelSpendBars, layout: "vertical", margin: { top: 0, right: 12, left: 0, bottom: 0 } }, /* @__PURE__ */ import_react5.default.createElement(import_recharts.CartesianGrid, { ...chart.grid, vertical: true, horizontal: false }), /* @__PURE__ */ import_react5.default.createElement(import_recharts.XAxis, { type: "number", tickFormatter: fmtUsdCompact, ...chart.axis }), /* @__PURE__ */ import_react5.default.createElement(import_recharts.YAxis, { type: "category", dataKey: "model", width: 170, ...chart.axis, tick: { fontSize: 10.5, fill: chart.axis.tick.fill } }), /* @__PURE__ */ import_react5.default.createElement(import_recharts.Tooltip, { cursor: chart.cursor, content: /* @__PURE__ */ import_react5.default.createElement(ChartTooltip, { valueFormatter: fmtUsd, labelFormatter: (l) => l }) }), /* @__PURE__ */ import_react5.default.createElement(import_recharts.Bar, { dataKey: "spend", name: "Spend", radius: [0, 3, 3, 0], barSize: 14 }, topModelSpendBars.map((r) => /* @__PURE__ */ import_react5.default.createElement(import_recharts.Cell, { key: r.model, fill: chartColor(r.model) })))))
2235
- ))), /* @__PURE__ */ import_react5.default.createElement(import_Grid.default, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ import_react5.default.createElement(ChartCard, { title: "Tokens by model", height: Math.max(200, topModelSpendBars.length * 34) }, /* @__PURE__ */ import_react5.default.createElement(
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__ */ import_react5.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react5.default.createElement(import_recharts.BarChart, { data: topModelSpendBars, layout: "vertical", margin: { top: 0, right: 12, left: 0, bottom: 0 } }, /* @__PURE__ */ import_react5.default.createElement(import_recharts.CartesianGrid, { ...chart.grid, vertical: true, horizontal: false }), /* @__PURE__ */ import_react5.default.createElement(import_recharts.XAxis, { type: "number", tickFormatter: fmtCompact, ...chart.axis }), /* @__PURE__ */ import_react5.default.createElement(import_recharts.YAxis, { type: "category", dataKey: "model", width: 170, ...chart.axis, tick: { fontSize: 10.5, fill: chart.axis.tick.fill } }), /* @__PURE__ */ import_react5.default.createElement(import_recharts.Tooltip, { cursor: chart.cursor, content: /* @__PURE__ */ import_react5.default.createElement(ChartTooltip, { valueFormatter: fmtInt, labelFormatter: (l) => l }) }), /* @__PURE__ */ import_react5.default.createElement(import_recharts.Legend, { ...chart.legend }), /* @__PURE__ */ import_react5.default.createElement(import_recharts.Bar, { dataKey: "promptTokens", name: "Input", fill: SERIES.input, stackId: "t", barSize: 14 }), /* @__PURE__ */ import_react5.default.createElement(import_recharts.Bar, { dataKey: "completionTokens", name: "Output", fill: SERIES.output, stackId: "t", barSize: 14, radius: [0, 3, 3, 0] })))
2243
- ))), /* @__PURE__ */ import_react5.default.createElement(import_Grid.default, { item: true, xs: 12 }, /* @__PURE__ */ import_react5.default.createElement(import_TableContainer2.default, { component: import_Paper3.default, variant: "outlined", sx: { borderRadius: 2 } }, /* @__PURE__ */ import_react5.default.createElement(import_Table2.default, { size: "small", sx: dataTableSx }, /* @__PURE__ */ import_react5.default.createElement(import_TableHead2.default, null, /* @__PURE__ */ import_react5.default.createElement(import_TableRow2.default, null, /* @__PURE__ */ import_react5.default.createElement(import_TableCell2.default, null, "Model"), /* @__PURE__ */ import_react5.default.createElement(import_TableCell2.default, { align: "right" }, "Spend"), /* @__PURE__ */ import_react5.default.createElement(import_TableCell2.default, { align: "right" }, "Requests"), /* @__PURE__ */ import_react5.default.createElement(import_TableCell2.default, { align: "right" }, "Success"), /* @__PURE__ */ import_react5.default.createElement(import_TableCell2.default, { align: "right" }, "Failed"), /* @__PURE__ */ import_react5.default.createElement(import_TableCell2.default, { align: "right" }, "Input"), /* @__PURE__ */ import_react5.default.createElement(import_TableCell2.default, { align: "right" }, "Output"), /* @__PURE__ */ import_react5.default.createElement(import_TableCell2.default, { sx: { width: 160 } }, "Success rate"))), /* @__PURE__ */ import_react5.default.createElement(import_TableBody2.default, null, renderModelRows()))))),
2244
- tab === "keys" && /* @__PURE__ */ import_react5.default.createElement(import_Grid.default, { container: true, spacing: 2 }, (loading || topKeySpendBars.length > 0) && /* @__PURE__ */ import_react5.default.createElement(import_Grid.default, { item: true, xs: 12 }, /* @__PURE__ */ import_react5.default.createElement(ChartCard, { title: "Spend by key", height: Math.max(160, topKeySpendBars.length * 32) }, /* @__PURE__ */ import_react5.default.createElement(
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__ */ import_react5.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react5.default.createElement(import_recharts.BarChart, { data: topKeySpendBars, layout: "vertical", margin: { top: 0, right: 12, left: 0, bottom: 0 } }, /* @__PURE__ */ import_react5.default.createElement(import_recharts.CartesianGrid, { ...chart.grid, vertical: true, horizontal: false }), /* @__PURE__ */ import_react5.default.createElement(import_recharts.XAxis, { type: "number", tickFormatter: fmtUsdCompact, ...chart.axis }), /* @__PURE__ */ import_react5.default.createElement(import_recharts.YAxis, { type: "category", dataKey: "keyAlias", width: 160, ...chart.axis, tick: { fontSize: 10.5, fill: chart.axis.tick.fill } }), /* @__PURE__ */ import_react5.default.createElement(import_recharts.Tooltip, { cursor: chart.cursor, content: /* @__PURE__ */ import_react5.default.createElement(ChartTooltip, { valueFormatter: fmtUsd, labelFormatter: (l) => l }) }), /* @__PURE__ */ import_react5.default.createElement(import_recharts.Bar, { dataKey: "spend", name: "Spend", fill: SERIES.input, radius: [0, 3, 3, 0], barSize: 14 })))
2252
- ))), /* @__PURE__ */ import_react5.default.createElement(import_Grid.default, { item: true, xs: 12 }, /* @__PURE__ */ import_react5.default.createElement(import_TableContainer2.default, { component: import_Paper3.default, variant: "outlined", sx: { borderRadius: 2 } }, /* @__PURE__ */ import_react5.default.createElement(import_Table2.default, { size: "small", sx: dataTableSx }, /* @__PURE__ */ import_react5.default.createElement(import_TableHead2.default, null, /* @__PURE__ */ import_react5.default.createElement(import_TableRow2.default, null, /* @__PURE__ */ import_react5.default.createElement(import_TableCell2.default, null, "Key"), /* @__PURE__ */ import_react5.default.createElement(import_TableCell2.default, null, "Models"), /* @__PURE__ */ import_react5.default.createElement(import_TableCell2.default, { align: "right" }, "Spend"), /* @__PURE__ */ import_react5.default.createElement(import_TableCell2.default, { align: "right" }, "Requests"), /* @__PURE__ */ import_react5.default.createElement(import_TableCell2.default, { align: "right" }, "Tokens"), /* @__PURE__ */ import_react5.default.createElement(import_TableCell2.default, { align: "right" }, "Failed"), /* @__PURE__ */ import_react5.default.createElement(import_TableCell2.default, { sx: { width: 160 } }, "Success rate"))), /* @__PURE__ */ import_react5.default.createElement(import_TableBody2.default, null, renderKeyRows())))))
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 import_react6, import_Paper4, import_Box6, import_Typography6, import_Skeleton4, import_Divider2, import_Table3, import_TableBody3, import_TableCell3, import_TableHead3, import_TableRow3, import_TableContainer3, import_Collapse, import_IconButton3, import_CircularProgress3, import_styles4, import_icons_material4, import_recharts2, fmtUsd2, TeamCard, TeamUsage;
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
- import_react6 = __toESM(require("react"));
2669
+ import_react7 = __toESM(require("react"));
2269
2670
  import_Paper4 = __toESM(require("@mui/material/Paper"));
2270
- import_Box6 = __toESM(require("@mui/material/Box"));
2271
- import_Typography6 = __toESM(require("@mui/material/Typography"));
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
- import_Divider2 = __toESM(require("@mui/material/Divider"));
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
- import_IconButton3 = __toESM(require("@mui/material/IconButton"));
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
- import_icons_material4 = require("@mui/icons-material");
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, import_react6.useState)(false);
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__ */ import_react6.default.createElement(import_Box6.default, { display: "flex", justifyContent: "center", py: 3 }, /* @__PURE__ */ import_react6.default.createElement(import_CircularProgress3.default, { size: 22 }));
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__ */ import_react6.default.createElement(import_Box6.default, { mb: 2.5 }, /* @__PURE__ */ import_react6.default.createElement(
2303
- import_Typography6.default,
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__ */ import_react6.default.createElement(import_Box6.default, { height: 150 }, /* @__PURE__ */ import_react6.default.createElement(import_recharts2.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react6.default.createElement(import_recharts2.AreaChart, { data: dailyData, margin: { top: 4, right: 8, left: 0, bottom: 0 } }, /* @__PURE__ */ import_react6.default.createElement(import_recharts2.CartesianGrid, { ...chart.grid }), /* @__PURE__ */ import_react6.default.createElement(import_recharts2.XAxis, { dataKey: "date", tickFormatter: fmtDateShort, ...chart.axis }), /* @__PURE__ */ import_react6.default.createElement(import_recharts2.YAxis, { tickFormatter: fmtUsdCompact, width: 52, ...chart.axis }), /* @__PURE__ */ import_react6.default.createElement(
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__ */ import_react6.default.createElement(ChartTooltip, { valueFormatter: (v) => `$${v.toFixed(4)}` })
2716
+ content: /* @__PURE__ */ import_react7.default.createElement(ChartTooltip, { valueFormatter: (v) => `$${v.toFixed(4)}` })
2315
2717
  }
2316
- ), /* @__PURE__ */ import_react6.default.createElement(
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__ */ import_react6.default.createElement(
2330
- import_Typography6.default,
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__ */ import_react6.default.createElement(import_Paper4.default, { variant: "outlined", sx: { borderRadius: 2, p: 2.5 } }, /* @__PURE__ */ import_react6.default.createElement(import_Box6.default, { display: "flex", alignItems: "center", gap: 1.5 }, /* @__PURE__ */ import_react6.default.createElement(
2339
- import_Box6.default,
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__ */ import_react6.default.createElement(import_icons_material4.Group, { fontSize: "small" })
2354
- ), /* @__PURE__ */ import_react6.default.createElement(import_Box6.default, { flexGrow: 1, minWidth: 0 }, /* @__PURE__ */ import_react6.default.createElement(import_Typography6.default, { variant: "subtitle1", sx: { fontWeight: 600, lineHeight: 1.3 } }, team.team_alias || "Untitled team"), /* @__PURE__ */ import_react6.default.createElement(
2355
- import_Typography6.default,
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__ */ import_react6.default.createElement(StatusPill, { label: "Over budget", tone: "danger" }), isNear && /* @__PURE__ */ import_react6.default.createElement(StatusPill, { label: "Near limit", tone: "warning" }), /* @__PURE__ */ import_react6.default.createElement(
2363
- import_IconButton3.default,
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__ */ import_react6.default.createElement(import_icons_material4.ExpandMore, null)
2376
- )), /* @__PURE__ */ import_react6.default.createElement(
2377
- import_Box6.default,
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__ */ import_react6.default.createElement(Stat, { label: "Members", value: team.members_with_roles?.length ?? "\u2014" }),
2388
- /* @__PURE__ */ import_react6.default.createElement(Stat, { label: "Models", value: team.models?.length ? team.models.length : "All" }),
2389
- /* @__PURE__ */ import_react6.default.createElement(Stat, { label: "Budget", value: budget > 0 ? fmtUsd2(budget) : "Unlimited" }),
2390
- /* @__PURE__ */ import_react6.default.createElement(Stat, { label: "Spend", value: fmtUsd2(spend) }),
2391
- /* @__PURE__ */ import_react6.default.createElement(Stat, { label: "TPM", value: team.tpm_limit && team.tpm_limit > 0 ? team.tpm_limit.toLocaleString() : "\u2014" }),
2392
- /* @__PURE__ */ import_react6.default.createElement(Stat, { label: "RPM", value: team.rpm_limit && team.rpm_limit > 0 ? team.rpm_limit.toLocaleString() : "\u2014" })
2393
- ), budget > 0 && /* @__PURE__ */ import_react6.default.createElement(import_Box6.default, { mt: 2, maxWidth: 640 }, /* @__PURE__ */ import_react6.default.createElement(import_Box6.default, { display: "flex", justifyContent: "space-between", alignItems: "baseline", mb: 0.75 }, /* @__PURE__ */ import_react6.default.createElement(import_Typography6.default, { variant: "caption", color: "text.secondary", sx: { fontVariantNumeric: "tabular-nums" } }, fmtUsd2(spend), " of ", fmtUsd2(budget)), /* @__PURE__ */ import_react6.default.createElement(import_Typography6.default, { variant: "caption", color: "text.secondary", sx: { fontVariantNumeric: "tabular-nums" } }, budgetPct.toFixed(0), "%")), /* @__PURE__ */ import_react6.default.createElement(Meter, { value: budgetPct, tone: budgetTone2(isOver, isNear), height: 5 })), /* @__PURE__ */ import_react6.default.createElement(import_Collapse.default, { in: expanded, unmountOnExit: true }, /* @__PURE__ */ import_react6.default.createElement(import_Divider2.default, { sx: { my: 2.5 } }), /* @__PURE__ */ import_react6.default.createElement(import_Box6.default, { mb: 2.5 }, sectionLabel("Models"), team.models?.length ? /* @__PURE__ */ import_react6.default.createElement(import_Box6.default, { display: "flex", gap: 0.5, flexWrap: "wrap" }, team.models.map((m) => /* @__PURE__ */ import_react6.default.createElement(TagChip, { key: m, label: m, title: m }))) : /* @__PURE__ */ import_react6.default.createElement(import_Typography6.default, { variant: "body2", color: "text.secondary" }, "All models allowed")), renderDailySpendSection(), /* @__PURE__ */ import_react6.default.createElement(import_Box6.default, null, sectionLabel("Members"), team.members_with_roles?.length ? /* @__PURE__ */ import_react6.default.createElement(
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__ */ import_react6.default.createElement(import_Table3.default, { size: "small", sx: dataTableSx }, /* @__PURE__ */ import_react6.default.createElement(import_TableHead3.default, null, /* @__PURE__ */ import_react6.default.createElement(import_TableRow3.default, null, /* @__PURE__ */ import_react6.default.createElement(import_TableCell3.default, null, "User"), /* @__PURE__ */ import_react6.default.createElement(import_TableCell3.default, { align: "right" }, "Role"))), /* @__PURE__ */ import_react6.default.createElement(import_TableBody3.default, null, team.members_with_roles.map((m) => /* @__PURE__ */ import_react6.default.createElement(import_TableRow3.default, { key: m.user_id }, /* @__PURE__ */ import_react6.default.createElement(import_TableCell3.default, null, m.user_email ? /* @__PURE__ */ import_react6.default.createElement(import_react6.default.Fragment, null, /* @__PURE__ */ import_react6.default.createElement(import_Typography6.default, { variant: "body2" }, m.user_email), /* @__PURE__ */ import_react6.default.createElement(
2401
- import_Typography6.default,
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__ */ import_react6.default.createElement(
2409
- import_Typography6.default,
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__ */ import_react6.default.createElement(import_TableCell3.default, { align: "right" }, /* @__PURE__ */ import_react6.default.createElement(
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__ */ import_react6.default.createElement(import_Typography6.default, { variant: "body2", color: "text.secondary" }, "No members assigned."))));
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__ */ import_react6.default.createElement(SectionCard, { title: "Teams" }, /* @__PURE__ */ import_react6.default.createElement(import_Skeleton4.default, { variant: "rounded", height: 120, sx: { mb: 2 } }), /* @__PURE__ */ import_react6.default.createElement(import_Skeleton4.default, { variant: "rounded", height: 120 }));
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__ */ import_react6.default.createElement(SectionCard, { title: "Teams" }, /* @__PURE__ */ import_react6.default.createElement(
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__ */ import_react6.default.createElement(SectionCard, { title: "Teams", subtitle: `${teams.length} team${teams.length === 1 ? "" : "s"}` }, /* @__PURE__ */ import_react6.default.createElement(import_Box6.default, { sx: { display: "flex", flexDirection: "column", gap: 2 } }, teams.map((team) => /* @__PURE__ */ import_react6.default.createElement(
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 import_react7, import_Box7, import_Table4, import_TableBody4, import_TableCell4, import_TableContainer4, import_TableHead4, import_TableRow4, import_Typography7, import_MenuItem3, import_Select, import_FormControl, import_InputLabel, import_ContentCopy, import_Tooltip, import_Snackbar, import_Alert2, import_styles5, ALL_PROXY_MODELS2, ModelsTable;
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
- import_react7 = __toESM(require("react"));
2479
- import_Box7 = __toESM(require("@mui/material/Box"));
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
- import_Typography7 = __toESM(require("@mui/material/Typography"));
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
- import_Alert2 = __toESM(require("@mui/material/Alert"));
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, import_react7.useState)("");
2501
- const [copiedSnackbar, setCopiedSnackbar] = (0, import_react7.useState)(false);
2502
- const selectedTeam = (0, import_react7.useMemo)(
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, import_react7.useMemo)(() => {
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, import_react7.useCallback)((modelId) => {
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__ */ import_react7.default.createElement(
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__ */ import_react7.default.createElement(import_Box7.default, { sx: { px: 2.5, pb: 2.5, pt: 1 } }, /* @__PURE__ */ import_react7.default.createElement(import_FormControl.default, { size: "small", sx: { minWidth: 240, mb: 2 } }, /* @__PURE__ */ import_react7.default.createElement(import_InputLabel.default, { id: "team-select-label" }, "Team"), /* @__PURE__ */ import_react7.default.createElement(
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__ */ import_react7.default.createElement(import_MenuItem3.default, { key: t.team_id, value: t.team_id }, t.team_alias ?? t.team_id))
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__ */ import_react7.default.createElement(EmptyState, { message: "Loading models\u2026" }),
2544
- !loading && !selectedTeamId && /* @__PURE__ */ import_react7.default.createElement(
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__ */ import_react7.default.createElement(
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__ */ import_react7.default.createElement(import_TableContainer4.default, null, /* @__PURE__ */ import_react7.default.createElement(import_Table4.default, { size: "small", sx: dataTableSx }, /* @__PURE__ */ import_react7.default.createElement(import_TableHead4.default, null, /* @__PURE__ */ import_react7.default.createElement(import_TableRow4.default, null, /* @__PURE__ */ import_react7.default.createElement(import_TableCell4.default, null, "Model ID"), /* @__PURE__ */ import_react7.default.createElement(import_TableCell4.default, null, "Mode"), /* @__PURE__ */ import_react7.default.createElement(import_TableCell4.default, { align: "right" }, "Input Cost"), /* @__PURE__ */ import_react7.default.createElement(import_TableCell4.default, { align: "right" }, "Output Cost"), /* @__PURE__ */ import_react7.default.createElement(import_TableCell4.default, { align: "right" }, "Max Input"), /* @__PURE__ */ import_react7.default.createElement(import_TableCell4.default, { align: "right" }, "Max Output"), /* @__PURE__ */ import_react7.default.createElement(import_TableCell4.default, null, "Capabilities"))), /* @__PURE__ */ import_react7.default.createElement(import_TableBody4.default, null, filteredModels.map((m) => /* @__PURE__ */ import_react7.default.createElement(import_TableRow4.default, { key: m.model_name, hover: true }, /* @__PURE__ */ import_react7.default.createElement(import_TableCell4.default, null, /* @__PURE__ */ import_react7.default.createElement(import_Box7.default, { sx: { display: "flex", alignItems: "center", gap: 0.5 } }, /* @__PURE__ */ import_react7.default.createElement(TagChip, { label: m.model_name, title: m.model_name }), /* @__PURE__ */ import_react7.default.createElement(import_Tooltip.default, { title: "Copy model ID" }, /* @__PURE__ */ import_react7.default.createElement(
2559
- import_Box7.default,
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__ */ import_react7.default.createElement(import_ContentCopy.default, { sx: { fontSize: 14 } })
2570
- )))), /* @__PURE__ */ import_react7.default.createElement(import_TableCell4.default, null, /* @__PURE__ */ import_react7.default.createElement(
2571
- import_Box7.default,
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__ */ import_react7.default.createElement(import_TableCell4.default, { align: "right" }, /* @__PURE__ */ import_react7.default.createElement(import_Typography7.default, { variant: "body2", sx: { fontVariantNumeric: "tabular-nums" } }, fmtCostPerToken(m.input_cost_per_token))), /* @__PURE__ */ import_react7.default.createElement(import_TableCell4.default, { align: "right" }, /* @__PURE__ */ import_react7.default.createElement(import_Typography7.default, { variant: "body2", sx: { fontVariantNumeric: "tabular-nums" } }, fmtCostPerToken(m.output_cost_per_token))), /* @__PURE__ */ import_react7.default.createElement(import_TableCell4.default, { align: "right" }, /* @__PURE__ */ import_react7.default.createElement(import_Typography7.default, { variant: "body2", sx: { fontVariantNumeric: "tabular-nums" } }, fmtTokens(m.max_input_tokens))), /* @__PURE__ */ import_react7.default.createElement(import_TableCell4.default, { align: "right" }, /* @__PURE__ */ import_react7.default.createElement(import_Typography7.default, { variant: "body2", sx: { fontVariantNumeric: "tabular-nums" } }, fmtTokens(m.max_output_tokens))), /* @__PURE__ */ import_react7.default.createElement(import_TableCell4.default, null, /* @__PURE__ */ import_react7.default.createElement(import_Box7.default, { sx: { display: "flex", gap: 0.5, flexWrap: "wrap" } }, m.supports_function_calling && /* @__PURE__ */ import_react7.default.createElement(TagChip, { label: "Fn", title: "Function calling" }), m.supports_vision && /* @__PURE__ */ import_react7.default.createElement(TagChip, { label: "\u{1F441}", title: "Vision" })))))))),
2588
- /* @__PURE__ */ import_react7.default.createElement(
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__ */ import_react7.default.createElement(import_Alert2.default, { severity: "success", variant: "filled", sx: { fontSize: 13 } }, "Model ID copied to clipboard")
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__ */ import_react8.default.createElement(import_TableRow5.default, null, /* @__PURE__ */ import_react8.default.createElement(import_TableCell5.default, { colSpan: 6, sx: { py: 2 } }, /* @__PURE__ */ import_react8.default.createElement(import_Skeleton5.default, { variant: "rounded", height: 160 })));
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__ */ import_react8.default.createElement(import_TableRow5.default, null, /* @__PURE__ */ import_react8.default.createElement(import_TableCell5.default, { colSpan: 6 }, /* @__PURE__ */ import_react8.default.createElement(
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__ */ import_react8.default.createElement(DetailRow, { key: entry.id, entry }));
3050
+ return entries.map((entry) => /* @__PURE__ */ import_react9.default.createElement(DetailRow, { key: entry.id, entry }));
2636
3051
  }
2637
- var import_react8, import_Box8, import_Typography8, import_Table5, import_TableBody5, import_TableCell5, import_TableContainer5, import_TableHead5, import_TableRow5, import_TablePagination, import_TextField4, import_MenuItem4, import_Skeleton5, import_Collapse2, import_IconButton4, import_Alert3, import_styles6, import_icons_material5, import_react_use, ACTION_TONES, TABLE_LABELS, DetailRow, AuditLog;
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
- import_react8 = __toESM(require("react"));
2642
- import_Box8 = __toESM(require("@mui/material/Box"));
2643
- import_Typography8 = __toESM(require("@mui/material/Typography"));
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
- import_TextField4 = __toESM(require("@mui/material/TextField"));
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
- import_IconButton4 = __toESM(require("@mui/material/IconButton"));
2656
- import_Alert3 = __toESM(require("@mui/material/Alert"));
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
- import_icons_material5 = require("@mui/icons-material");
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, import_react8.useState)(false);
3091
+ const [open, setOpen] = (0, import_react9.useState)(false);
2675
3092
  const hasDetail = entry.before_value || entry.updated_values;
2676
- return /* @__PURE__ */ import_react8.default.createElement(import_react8.default.Fragment, null, /* @__PURE__ */ import_react8.default.createElement(import_TableRow5.default, null, /* @__PURE__ */ import_react8.default.createElement(import_TableCell5.default, { sx: { width: 40, pr: 0 } }, hasDetail && /* @__PURE__ */ import_react8.default.createElement(
2677
- import_IconButton4.default,
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__ */ import_react8.default.createElement(import_icons_material5.KeyboardArrowDown, { fontSize: "small" })
2690
- )), /* @__PURE__ */ import_react8.default.createElement(import_TableCell5.default, { sx: { whiteSpace: "nowrap" } }, /* @__PURE__ */ import_react8.default.createElement(import_Typography8.default, { variant: "body2", color: "text.secondary" }, formatDateTime(entry.updated_at))), /* @__PURE__ */ import_react8.default.createElement(import_TableCell5.default, null, entry.action && /* @__PURE__ */ import_react8.default.createElement(StatusPill, { label: entry.action, tone: actionTone(entry.action) })), /* @__PURE__ */ import_react8.default.createElement(import_TableCell5.default, null, /* @__PURE__ */ import_react8.default.createElement(import_Typography8.default, { variant: "body2" }, prettyTableName(entry.table_name))), /* @__PURE__ */ import_react8.default.createElement(import_TableCell5.default, null, /* @__PURE__ */ import_react8.default.createElement(
2691
- import_Typography8.default,
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__ */ import_react8.default.createElement(import_TableCell5.default, null, entry.changed_by ?? "\u2014")), hasDetail && /* @__PURE__ */ import_react8.default.createElement(import_TableRow5.default, { sx: { "&&:hover": { bgcolor: "transparent" } } }, /* @__PURE__ */ import_react8.default.createElement(import_TableCell5.default, { colSpan: 6, sx: { "&&": { py: 0, border: 0 } } }, /* @__PURE__ */ import_react8.default.createElement(import_Collapse2.default, { in: open, unmountOnExit: true }, /* @__PURE__ */ import_react8.default.createElement(
2701
- import_Box8.default,
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__ */ import_react8.default.createElement(import_Box8.default, { flex: 1, minWidth: 200 }, /* @__PURE__ */ import_react8.default.createElement(
2714
- import_Typography8.default,
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__ */ import_react8.default.createElement(import_Typography8.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))),
2724
- entry.updated_values && /* @__PURE__ */ import_react8.default.createElement(import_Box8.default, { flex: 1, minWidth: 200 }, /* @__PURE__ */ import_react8.default.createElement(
2725
- import_Typography8.default,
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__ */ import_react8.default.createElement(import_Typography8.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)))
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, import_react8.useState)(0);
2739
- const [pageSize, setPageSize] = (0, import_react8.useState)(25);
2740
- const [filters, setFilters] = (0, import_react8.useState)({});
2741
- const fetchParams = (0, import_react8.useCallback)(
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__ */ import_react8.default.createElement(
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__ */ import_react8.default.createElement(import_react8.default.Fragment, null, /* @__PURE__ */ import_react8.default.createElement(
2758
- import_TextField4.default,
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__ */ import_react8.default.createElement(import_MenuItem4.default, { value: "" }, "All actions"),
2771
- /* @__PURE__ */ import_react8.default.createElement(import_MenuItem4.default, { value: "created" }, "Created"),
2772
- /* @__PURE__ */ import_react8.default.createElement(import_MenuItem4.default, { value: "updated" }, "Updated"),
2773
- /* @__PURE__ */ import_react8.default.createElement(import_MenuItem4.default, { value: "deleted" }, "Deleted"),
2774
- /* @__PURE__ */ import_react8.default.createElement(import_MenuItem4.default, { value: "blocked" }, "Blocked")
2775
- ), /* @__PURE__ */ import_react8.default.createElement(
2776
- import_TextField4.default,
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__ */ import_react8.default.createElement(import_MenuItem4.default, { value: "" }, "All tables"),
2789
- /* @__PURE__ */ import_react8.default.createElement(import_MenuItem4.default, { value: "LiteLLM_VerificationToken" }, "Key"),
2790
- /* @__PURE__ */ import_react8.default.createElement(import_MenuItem4.default, { value: "LiteLLM_TeamTable" }, "Team"),
2791
- /* @__PURE__ */ import_react8.default.createElement(import_MenuItem4.default, { value: "LiteLLM_UserTable" }, "User")
2792
- ), /* @__PURE__ */ import_react8.default.createElement(
2793
- import_TextField4.default,
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__ */ import_react8.default.createElement(import_Box8.default, { px: 2.5, pb: 2 }, /* @__PURE__ */ import_react8.default.createElement(import_Alert3.default, { severity: "error" }, error.message)),
2807
- /* @__PURE__ */ import_react8.default.createElement(import_TableContainer5.default, null, /* @__PURE__ */ import_react8.default.createElement(import_Table5.default, { size: "small", sx: dataTableSx }, /* @__PURE__ */ import_react8.default.createElement(import_TableHead5.default, null, /* @__PURE__ */ import_react8.default.createElement(import_TableRow5.default, null, /* @__PURE__ */ import_react8.default.createElement(import_TableCell5.default, { sx: { width: 40 } }), /* @__PURE__ */ import_react8.default.createElement(import_TableCell5.default, null, "Time"), /* @__PURE__ */ import_react8.default.createElement(import_TableCell5.default, null, "Action"), /* @__PURE__ */ import_react8.default.createElement(import_TableCell5.default, null, "Table"), /* @__PURE__ */ import_react8.default.createElement(import_TableCell5.default, null, "Object ID"), /* @__PURE__ */ import_react8.default.createElement(import_TableCell5.default, null, "Changed By"))), /* @__PURE__ */ import_react8.default.createElement(import_TableBody5.default, null, renderAuditLogBody(loading, entries)))),
2808
- /* @__PURE__ */ import_react8.default.createElement(
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 import_react9, import_Box9, import_Snackbar2, import_Alert4, import_CircularProgress4, import_Typography9, import_Paper5, import_Tabs2, import_Tab2, import_react_use2, import_core_plugin_api2, PERIOD_LS_KEY2, LiteLLMPage;
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
- import_react9 = __toESM(require("react"));
2853
- import_Box9 = __toESM(require("@mui/material/Box"));
3300
+ import_react10 = __toESM(require("react"));
3301
+ import_Box10 = __toESM(require("@mui/material/Box"));
2854
3302
  import_Snackbar2 = __toESM(require("@mui/material/Snackbar"));
2855
- import_Alert4 = __toESM(require("@mui/material/Alert"));
3303
+ import_Alert5 = __toESM(require("@mui/material/Alert"));
2856
3304
  import_CircularProgress4 = __toESM(require("@mui/material/CircularProgress"));
2857
- import_Typography9 = __toESM(require("@mui/material/Typography"));
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, import_react9.useState)(initDateRange);
2875
- const [currentPreset, setCurrentPreset] = (0, import_react9.useState)(() => {
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, import_react9.useState)("overview");
2883
- const [snackbar, setSnackbar] = (0, import_react9.useState)(null);
2884
- const [generateDialogOpen, setGenerateDialogOpen] = (0, import_react9.useState)(false);
2885
- const [teamUsageCache, setTeamUsageCache] = (0, import_react9.useState)({});
2886
- const [teamUsageLoading, setTeamUsageLoading] = (0, import_react9.useState)({});
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.useAsync)(
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 teams = (0, import_react9.useMemo)(() => {
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, import_react9.useCallback)((range, preset) => {
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, import_react9.useCallback)(async (teamId) => {
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, import_react9.useMemo)(() => {
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, import_react9.useCallback)(
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, import_react9.useCallback)(
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, import_react9.useCallback)(
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, import_react9.useCallback)(
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, import_react9.useCallback)(
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, import_react9.useCallback)(
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, import_react9.useCallback)(
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__ */ import_react9.default.createElement(import_Box9.default, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: "50vh" }, /* @__PURE__ */ import_react9.default.createElement(import_CircularProgress4.default, null));
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__ */ import_react9.default.createElement(import_Box9.default, { p: 3 }, /* @__PURE__ */ import_react9.default.createElement(import_Paper5.default, { sx: { p: 3 } }, /* @__PURE__ */ import_react9.default.createElement(import_Typography9.default, { variant: "h6", gutterBottom: true }, "Account not provisioned"), /* @__PURE__ */ import_react9.default.createElement(import_Typography9.default, { color: "text.secondary", paragraph: true }, "Your Backstage account is not linked to a LiteLLM user."), hint ? /* @__PURE__ */ import_react9.default.createElement(import_Typography9.default, { variant: "body2", color: "text.secondary" }, hint) : /* @__PURE__ */ import_react9.default.createElement(import_Typography9.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.")));
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__ */ import_react9.default.createElement(
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__ */ import_react9.default.createElement(import_Tab2.default, { label: "Overview", value: "overview" }),
3079
- /* @__PURE__ */ import_react9.default.createElement(import_Tab2.default, { label: "Keys", value: "keys" }),
3080
- /* @__PURE__ */ import_react9.default.createElement(import_Tab2.default, { label: "Teams", value: "teams" }),
3081
- /* @__PURE__ */ import_react9.default.createElement(import_Tab2.default, { label: "Models", value: "models" }),
3082
- userInfo.can_view_audit && /* @__PURE__ */ import_react9.default.createElement(import_Tab2.default, { label: "Audit Log", value: "audit" })
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__ */ import_react9.default.createElement(import_Box9.default, { sx: { p: 3, display: "flex", flexDirection: "column", gap: 2 } }, /* @__PURE__ */ import_react9.default.createElement(
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__ */ import_react9.default.createElement(
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__ */ import_react9.default.createElement(
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" && /* @__PURE__ */ import_react9.default.createElement(
3120
- TeamUsage,
3121
- {
3122
- teams: teams ?? [],
3123
- loading: teamsLoading,
3124
- getTeamUsage: (teamId) => {
3125
- if (teamUsageCache[teamId] === void 0) loadTeamUsage(teamId);
3126
- return teamUsageCache[teamId] ?? null;
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
- getTeamUsageLoading: (teamId) => teamUsageLoading[teamId] ?? false
3129
- }
3130
- ), activeTab === "models" && /* @__PURE__ */ import_react9.default.createElement(
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__ */ import_react9.default.createElement(AuditLog, { api }), /* @__PURE__ */ import_react9.default.createElement(
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__ */ import_react9.default.createElement(
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__ */ import_react9.default.createElement(import_Alert4.default, { severity: snackbar.severity, onClose: () => setSnackbar(null) }, snackbar.message) : void 0
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 import_react10 = __toESM(require("react"));
3182
- var import_icons_material6 = require("@mui/icons-material");
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__ */ import_react10.default.createElement(import_icons_material6.TrendingUp, null),
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__ */ import_react10.default.createElement(LiteLLMPage2, null);
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 import_react11 = __toESM(require("react"));
3771
+ var import_react12 = __toESM(require("react"));
3217
3772
  var import_Paper6 = __toESM(require("@mui/material/Paper"));
3218
- var import_Box10 = __toESM(require("@mui/material/Box"));
3219
- var import_Typography10 = __toESM(require("@mui/material/Typography"));
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 import_Alert5 = __toESM(require("@mui/material/Alert"));
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__ */ import_react11.default.createElement(import_Box10.default, null, /* @__PURE__ */ import_react11.default.createElement(import_Typography10.default, { variant: "caption", color: "text.secondary", display: "block" }, label), /* @__PURE__ */ import_react11.default.createElement(import_Typography10.default, { variant: "subtitle1", fontWeight: 600 }, value));
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, import_react11.useState)(defaultPeriod);
3249
- const [loading, setLoading] = (0, import_react11.useState)(true);
3250
- const [usageError, setUsageError] = (0, import_react11.useState)(null);
3251
- const [keysError, setKeysError] = (0, import_react11.useState)(null);
3252
- const [usage, setUsage] = (0, import_react11.useState)(null);
3253
- const [keys, setKeys] = (0, import_react11.useState)([]);
3254
- (0, import_react11.useEffect)(() => {
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__ */ import_react11.default.createElement(import_Paper6.default, { sx: { p: 2 } }, /* @__PURE__ */ import_react11.default.createElement(import_Box10.default, { display: "flex", justifyContent: "space-between", alignItems: "center", mb: 1.5 }, /* @__PURE__ */ import_react11.default.createElement(import_Typography10.default, { variant: "h6" }, title), /* @__PURE__ */ import_react11.default.createElement(import_FormControl2.default, { size: "small", sx: { minWidth: 90 } }, /* @__PURE__ */ import_react11.default.createElement(
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__ */ import_react11.default.createElement(import_MenuItem5.default, { value: "today" }, "Today"),
3299
- /* @__PURE__ */ import_react11.default.createElement(import_MenuItem5.default, { value: "7d" }, "7d"),
3300
- /* @__PURE__ */ import_react11.default.createElement(import_MenuItem5.default, { value: "30d" }, "30d")
3301
- ))), loading && /* @__PURE__ */ import_react11.default.createElement(import_Box10.default, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: 120 }, /* @__PURE__ */ import_react11.default.createElement(import_CircularProgress5.default, { size: 32 })), !loading && totalFailure && /* @__PURE__ */ import_react11.default.createElement(import_Alert5.default, { severity: "error", sx: { mt: 1 } }, usageError ?? "Failed to load usage data"), !loading && !totalFailure && /* @__PURE__ */ import_react11.default.createElement(import_react11.default.Fragment, null, partialFailure && /* @__PURE__ */ import_react11.default.createElement(import_Alert5.default, { severity: "warning", sx: { mt: 1, mb: 1 } }, usageError ? `Usage data unavailable (${usageError}).` : "", keysError ? ` Key list unavailable (${keysError}).` : "", " Showing what loaded."), /* @__PURE__ */ import_react11.default.createElement(import_Grid2.default, { container: true, spacing: 2, sx: { mb: hasSparkline ? 1.5 : 0 } }, /* @__PURE__ */ import_react11.default.createElement(import_Grid2.default, { item: true, xs: 6 }, /* @__PURE__ */ import_react11.default.createElement(Kpi, { label: "USD Spent", value: fmtUsd(usage?.total_spend ?? 0) })), /* @__PURE__ */ import_react11.default.createElement(import_Grid2.default, { item: true, xs: 6 }, /* @__PURE__ */ import_react11.default.createElement(Kpi, { label: "Tokens In", value: fmtInt(usage?.prompt_tokens ?? 0) })), /* @__PURE__ */ import_react11.default.createElement(import_Grid2.default, { item: true, xs: 6 }, /* @__PURE__ */ import_react11.default.createElement(Kpi, { label: "Tokens Out", value: fmtInt(usage?.completion_tokens ?? 0) })), /* @__PURE__ */ import_react11.default.createElement(import_Grid2.default, { item: true, xs: 6 }, /* @__PURE__ */ import_react11.default.createElement(Kpi, { label: "Keys", value: fmtInt(keys.length) }))), hasSparkline && /* @__PURE__ */ import_react11.default.createElement(import_Box10.default, { height: 120 }, /* @__PURE__ */ import_react11.default.createElement(import_recharts3.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react11.default.createElement(import_recharts3.AreaChart, { data: dailyData, margin: { top: 4, right: 0, bottom: 0, left: 0 } }, /* @__PURE__ */ import_react11.default.createElement(
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