@acarmisc/backstage-plugin-litellm 0.15.5 → 0.17.0

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