@acarmisc/backstage-plugin-litellm 0.10.2 → 0.12.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
@@ -175,13 +175,26 @@ var init_api = __esm({
175
175
  });
176
176
 
177
177
  // src/components/DashboardHeader.tsx
178
- var import_react, import_material, DashboardHeader;
178
+ var import_react, import_material, import_icons_material, DashboardHeader;
179
179
  var init_DashboardHeader = __esm({
180
180
  "src/components/DashboardHeader.tsx"() {
181
181
  "use strict";
182
182
  import_react = __toESM(require("react"));
183
183
  import_material = require("@mui/material");
184
- DashboardHeader = ({ userInfo, teams, loading }) => {
184
+ import_icons_material = require("@mui/icons-material");
185
+ init_api();
186
+ DashboardHeader = ({
187
+ userInfo,
188
+ teams,
189
+ keys,
190
+ loading,
191
+ onGenerateKeyClick
192
+ }) => {
193
+ const counts = (0, import_react.useMemo)(() => {
194
+ const expired = keys.filter((k) => expiryStatus(k.expires_at) === "expired").length;
195
+ const expiringSoon = keys.filter((k) => expiryStatus(k.expires_at) === "soon").length;
196
+ return { total: keys.length, expired, expiringSoon };
197
+ }, [keys]);
185
198
  if (loading) {
186
199
  return /* @__PURE__ */ import_react.default.createElement(import_material.Paper, { sx: { p: 2, mb: 2 } }, /* @__PURE__ */ import_react.default.createElement(import_material.LinearProgress, null));
187
200
  }
@@ -195,21 +208,32 @@ var init_DashboardHeader = __esm({
195
208
  variant: "outlined",
196
209
  color: "primary"
197
210
  }
198
- ))))));
199
- };
200
- }
201
- });
202
-
203
- // src/format.ts
204
- var fmtUsd, fmtInt, estimateTokensFromBudget;
205
- var init_format = __esm({
206
- "src/format.ts"() {
207
- "use strict";
208
- fmtUsd = (n) => `$${(n ?? 0).toFixed(n < 1 ? 4 : 2)}`;
209
- fmtInt = (n) => (n ?? 0).toLocaleString();
210
- estimateTokensFromBudget = (budget, pricePerToken) => {
211
- if (!pricePerToken || pricePerToken <= 0 || !budget || budget <= 0) return null;
212
- return Math.floor(budget / pricePerToken);
211
+ )))), /* @__PURE__ */ import_react.default.createElement(import_material.Box, { display: "flex", alignItems: "center", gap: 2, flexWrap: "wrap" }, /* @__PURE__ */ import_react.default.createElement(import_material.Stack, { direction: "row", spacing: 1 }, /* @__PURE__ */ import_react.default.createElement(import_material.Chip, { icon: /* @__PURE__ */ import_react.default.createElement(import_icons_material.Key, { fontSize: "small" }), label: `${counts.total} keys`, size: "small" }), /* @__PURE__ */ import_react.default.createElement(
212
+ import_material.Chip,
213
+ {
214
+ icon: /* @__PURE__ */ import_react.default.createElement(import_icons_material.Warning, { fontSize: "small" }),
215
+ label: `${counts.expired} expired`,
216
+ size: "small",
217
+ color: counts.expired > 0 ? "error" : "default"
218
+ }
219
+ ), /* @__PURE__ */ import_react.default.createElement(
220
+ import_material.Chip,
221
+ {
222
+ icon: /* @__PURE__ */ import_react.default.createElement(import_icons_material.Schedule, { fontSize: "small" }),
223
+ label: `${counts.expiringSoon} expiring soon`,
224
+ size: "small",
225
+ color: counts.expiringSoon > 0 ? "warning" : "default"
226
+ }
227
+ )), /* @__PURE__ */ import_react.default.createElement(
228
+ import_material.Button,
229
+ {
230
+ variant: "contained",
231
+ color: "primary",
232
+ startIcon: /* @__PURE__ */ import_react.default.createElement(import_icons_material.Add, null),
233
+ onClick: onGenerateKeyClick
234
+ },
235
+ "Generate New Key"
236
+ ))));
213
237
  };
214
238
  }
215
239
  });
@@ -220,7 +244,7 @@ function ExpiryChip({ expiresAt }) {
220
244
  if (!status) return /* @__PURE__ */ import_react2.default.createElement(import_material2.Typography, { variant: "body2", color: "text.secondary" }, "-");
221
245
  const label = status === "expired" ? "Expired" : status === "soon" ? `${Math.ceil((new Date(expiresAt).getTime() - Date.now()) / 864e5)}d left` : formatDate(expiresAt);
222
246
  const color = status === "expired" ? "error" : status === "soon" ? "warning" : "default";
223
- const icon = status === "expired" || status === "soon" ? /* @__PURE__ */ import_react2.default.createElement(import_icons_material.Warning, { fontSize: "small" }) : void 0;
247
+ const icon = status === "expired" || status === "soon" ? /* @__PURE__ */ import_react2.default.createElement(import_icons_material2.Warning, { fontSize: "small" }) : void 0;
224
248
  return /* @__PURE__ */ import_react2.default.createElement(import_material2.Chip, { label, color, size: "small", icon });
225
249
  }
226
250
  function BudgetCell({ spend, maxBudget }) {
@@ -249,42 +273,14 @@ function formatContextWindow(maxInput, maxOutput) {
249
273
  if (outPart) return `ctx ${outPart} out`;
250
274
  return null;
251
275
  }
252
- function trimSlash(url) {
253
- return url.replace(/\/+$/, "");
254
- }
255
- function buildSnippets(baseUrl, key, model) {
256
- const base = trimSlash(baseUrl);
257
- return {
258
- curl: `curl ${base}/v1/chat/completions \\
259
- -H "Authorization: Bearer ${key}" \\
260
- -H "Content-Type: application/json" \\
261
- -d '{
262
- "model": "${model}",
263
- "messages": [{ "role": "user", "content": "Hello!" }]
264
- }'`,
265
- openai: `from openai import OpenAI
266
-
267
- client = OpenAI(
268
- api_key="${key}",
269
- base_url="${base}/v1",
270
- )
271
-
272
- response = client.chat.completions.create(
273
- model="${model}",
274
- messages=[{ "role": "user", "content": "Hello!" }],
275
- )
276
- print(response.choices[0].message.content)`
277
- };
278
- }
279
- var import_react2, import_material2, import_icons_material, maskKey, shortKeyId, formatDate, emptyForm, keyToEditForm, KeysTable, SnippetTabs;
276
+ var import_react2, import_material2, import_icons_material2, maskKey, shortKeyId, formatDate, keyToEditForm, KeysTable;
280
277
  var init_KeysTable = __esm({
281
278
  "src/components/KeysTable.tsx"() {
282
279
  "use strict";
283
280
  import_react2 = __toESM(require("react"));
284
281
  import_material2 = require("@mui/material");
285
- import_icons_material = require("@mui/icons-material");
282
+ import_icons_material2 = require("@mui/icons-material");
286
283
  init_api();
287
- init_format();
288
284
  maskKey = (key) => {
289
285
  if (key.length <= 8) return "***";
290
286
  return `${key.slice(0, 4)}...${key.slice(-4)}`;
@@ -301,16 +297,6 @@ var init_KeysTable = __esm({
301
297
  return dateStr;
302
298
  }
303
299
  };
304
- emptyForm = () => ({
305
- alias: "",
306
- models: [],
307
- duration: "30d",
308
- max_budget: 100,
309
- tpm_limit: void 0,
310
- rpm_limit: void 0,
311
- team_id: void 0,
312
- key_type: "llm_api"
313
- });
314
300
  keyToEditForm = (k) => ({
315
301
  key_alias: k.key_alias ?? "",
316
302
  models: k.models ?? [],
@@ -321,24 +307,15 @@ var init_KeysTable = __esm({
321
307
  KeysTable = ({
322
308
  keys,
323
309
  models,
324
- teams,
325
310
  loading,
326
- onGenerateKey,
311
+ onGenerateKeyClick,
327
312
  onUpdateKey,
328
313
  onBlockKey,
329
314
  onUnblockKey,
330
315
  onResetKeySpend,
331
316
  onDeleteKey,
332
- onPruneExpiredKeys,
333
- onGetConfig
317
+ onPruneExpiredKeys
334
318
  }) => {
335
- const [generateModalOpen, setGenerateModalOpen] = (0, import_react2.useState)(false);
336
- const [newKeyValue, setNewKeyValue] = (0, import_react2.useState)(null);
337
- const [newKeySnippets, setNewKeySnippets] = (0, import_react2.useState)(null);
338
- const [newKeyModel, setNewKeyModel] = (0, import_react2.useState)("");
339
- const [formData, setFormData] = (0, import_react2.useState)(emptyForm());
340
- const [unlimitedBudget, setUnlimitedBudget] = (0, import_react2.useState)(false);
341
- const [submitting, setSubmitting] = (0, import_react2.useState)(false);
342
319
  const [editingKey, setEditingKey] = (0, import_react2.useState)(null);
343
320
  const [editForm, setEditForm] = (0, import_react2.useState)({});
344
321
  const [editSubmitting, setEditSubmitting] = (0, import_react2.useState)(false);
@@ -355,51 +332,7 @@ var init_KeysTable = __esm({
355
332
  (k) => (k.key_alias ?? "").toLowerCase().includes(q) || k.models?.some((m) => m.toLowerCase().includes(q))
356
333
  );
357
334
  }, [keys, filterText]);
358
- const selectedModels = models.filter((m) => (formData.models || []).includes(m.model_name));
359
- const selectedTeam = teams.find((t) => t.team_id === formData.team_id) ?? null;
360
335
  const editSelectedModels = models.filter((m) => (editForm.models || []).includes(m.model_name));
361
- const aliasError = !(formData.alias || "").trim();
362
- const budgetInvalid = !unlimitedBudget && (formData.max_budget === void 0 || formData.max_budget === null || formData.max_budget <= 0);
363
- const canGenerate = !aliasError && !budgetInvalid && !submitting;
364
- const budgetEstimate = (0, import_react2.useMemo)(() => {
365
- if (unlimitedBudget || budgetInvalid) return null;
366
- const inputCosts = selectedModels.map((m) => m.input_cost_per_token).filter((c) => typeof c === "number" && c > 0);
367
- if (inputCosts.length === 0) return null;
368
- const pricePerToken = Math.max(...inputCosts);
369
- return estimateTokensFromBudget(formData.max_budget ?? 0, pricePerToken);
370
- }, [formData.max_budget, selectedModels, unlimitedBudget, budgetInvalid]);
371
- const handleGenerate = async () => {
372
- setSubmitting(true);
373
- try {
374
- const request = {
375
- ...formData,
376
- max_budget: unlimitedBudget ? null : formData.max_budget
377
- };
378
- const response = await onGenerateKey(request);
379
- setNewKeyValue(response.key);
380
- setNewKeyModel(formData.models?.[0] ?? "");
381
- setNewKeySnippets(null);
382
- try {
383
- const config = await onGetConfig();
384
- const model = formData.models?.[0] ?? "";
385
- setNewKeySnippets(buildSnippets(config.baseUrl, response.key, model));
386
- } catch {
387
- }
388
- setFormData(emptyForm());
389
- setUnlimitedBudget(false);
390
- } catch (error) {
391
- console.error("Failed to generate key:", error);
392
- } finally {
393
- setSubmitting(false);
394
- }
395
- };
396
- const handleCloseModal = () => {
397
- setGenerateModalOpen(false);
398
- setNewKeyValue(null);
399
- setNewKeySnippets(null);
400
- setFormData(emptyForm());
401
- setUnlimitedBudget(false);
402
- };
403
336
  const handleOpenEdit = (k) => {
404
337
  setEditingKey(k);
405
338
  setEditForm(keyToEditForm(k));
@@ -490,7 +423,7 @@ var init_KeysTable = __esm({
490
423
  onChange: (e) => setFilterText(e.target.value),
491
424
  sx: { minWidth: 240 },
492
425
  InputProps: {
493
- startAdornment: /* @__PURE__ */ import_react2.default.createElement(import_material2.InputAdornment, { position: "start" }, /* @__PURE__ */ import_react2.default.createElement(import_icons_material.Search, { fontSize: "small" }))
426
+ startAdornment: /* @__PURE__ */ import_react2.default.createElement(import_material2.InputAdornment, { position: "start" }, /* @__PURE__ */ import_react2.default.createElement(import_icons_material2.Search, { fontSize: "small" }))
494
427
  }
495
428
  }
496
429
  ), expiredKeys.length > 0 && /* @__PURE__ */ import_react2.default.createElement(
@@ -498,7 +431,7 @@ var init_KeysTable = __esm({
498
431
  {
499
432
  variant: "outlined",
500
433
  color: "error",
501
- startIcon: /* @__PURE__ */ import_react2.default.createElement(import_icons_material.Autorenew, null),
434
+ startIcon: /* @__PURE__ */ import_react2.default.createElement(import_icons_material2.Autorenew, null),
502
435
  onClick: () => setPruneConfirmCount(expiredKeys.length)
503
436
  },
504
437
  "Prune expired (",
@@ -509,8 +442,8 @@ var init_KeysTable = __esm({
509
442
  {
510
443
  variant: "contained",
511
444
  color: "primary",
512
- startIcon: /* @__PURE__ */ import_react2.default.createElement(import_icons_material.Add, null),
513
- onClick: () => setGenerateModalOpen(true)
445
+ startIcon: /* @__PURE__ */ import_react2.default.createElement(import_icons_material2.Add, null),
446
+ onClick: onGenerateKeyClick
514
447
  },
515
448
  "Generate New Key"
516
449
  ))), /* @__PURE__ */ import_react2.default.createElement(import_material2.TableContainer, null, /* @__PURE__ */ import_react2.default.createElement(import_material2.Table, null, /* @__PURE__ */ import_react2.default.createElement(import_material2.TableHead, null, /* @__PURE__ */ import_react2.default.createElement(import_material2.TableRow, null, /* @__PURE__ */ import_react2.default.createElement(import_material2.TableCell, null, "Alias"), /* @__PURE__ */ import_react2.default.createElement(import_material2.TableCell, null, "Key ID"), /* @__PURE__ */ import_react2.default.createElement(import_material2.TableCell, null, "Created"), /* @__PURE__ */ import_react2.default.createElement(import_material2.TableCell, null, "Expires"), /* @__PURE__ */ import_react2.default.createElement(import_material2.TableCell, null, "Budget"), /* @__PURE__ */ import_react2.default.createElement(import_material2.TableCell, null, "TPM / RPM"), /* @__PURE__ */ import_react2.default.createElement(import_material2.TableCell, null, "Models"), /* @__PURE__ */ import_react2.default.createElement(import_material2.TableCell, { align: "right" }, "Actions"))), /* @__PURE__ */ import_react2.default.createElement(import_material2.TableBody, null, loading ? /* @__PURE__ */ import_react2.default.createElement(import_material2.TableRow, null, /* @__PURE__ */ import_react2.default.createElement(import_material2.TableCell, { colSpan: 8, align: "center" }, /* @__PURE__ */ import_react2.default.createElement(import_material2.CircularProgress, { size: 24 }))) : filteredKeys.length === 0 ? /* @__PURE__ */ import_react2.default.createElement(import_material2.TableRow, null, /* @__PURE__ */ import_react2.default.createElement(import_material2.TableCell, { colSpan: 8, align: "center", sx: { py: 4 } }, filterText ? /* @__PURE__ */ import_react2.default.createElement(import_material2.Typography, { color: "text.secondary" }, "No keys match filter") : /* @__PURE__ */ import_react2.default.createElement(import_material2.Box, null, /* @__PURE__ */ import_react2.default.createElement(import_material2.Typography, { color: "text.secondary", gutterBottom: true }, "No keys yet \u2014 generate your first key to start calling models."), /* @__PURE__ */ import_react2.default.createElement(
@@ -519,8 +452,8 @@ var init_KeysTable = __esm({
519
452
  variant: "contained",
520
453
  color: "primary",
521
454
  size: "small",
522
- startIcon: /* @__PURE__ */ import_react2.default.createElement(import_icons_material.Add, null),
523
- onClick: () => setGenerateModalOpen(true)
455
+ startIcon: /* @__PURE__ */ import_react2.default.createElement(import_icons_material2.Add, null),
456
+ onClick: onGenerateKeyClick
524
457
  },
525
458
  "Generate Your First Key"
526
459
  )))) : filteredKeys.map((key) => {
@@ -542,7 +475,7 @@ var init_KeysTable = __esm({
542
475
  }
543
476
  },
544
477
  shortKeyId(keyId)
545
- ), /* @__PURE__ */ import_react2.default.createElement(import_material2.IconButton, { size: "small", onClick: () => copyToClipboard(keyId), title: "Copy Key ID" }, /* @__PURE__ */ import_react2.default.createElement(import_icons_material.ContentCopy, { fontSize: "small" })))), /* @__PURE__ */ import_react2.default.createElement(import_material2.TableCell, null, formatDate(key.created_at)), /* @__PURE__ */ import_react2.default.createElement(import_material2.TableCell, null, /* @__PURE__ */ import_react2.default.createElement(ExpiryChip, { expiresAt: key.expires_at })), /* @__PURE__ */ import_react2.default.createElement(import_material2.TableCell, null, /* @__PURE__ */ import_react2.default.createElement(BudgetCell, { spend: key.spend ?? 0, maxBudget: key.max_budget })), /* @__PURE__ */ import_react2.default.createElement(import_material2.TableCell, null, /* @__PURE__ */ import_react2.default.createElement(import_material2.Typography, { variant: "body2" }, key.tpm_limit ?? "-", " / ", key.rpm_limit ?? "-")), /* @__PURE__ */ import_react2.default.createElement(import_material2.TableCell, null, /* @__PURE__ */ import_react2.default.createElement(import_material2.Box, { display: "flex", gap: 0.5, flexWrap: "wrap" }, key.models?.slice(0, 2).map((model) => /* @__PURE__ */ import_react2.default.createElement(import_material2.Chip, { key: model, label: model, size: "small" })), (key.models?.length || 0) > 2 && /* @__PURE__ */ import_react2.default.createElement(import_material2.Chip, { label: `+${(key.models?.length || 0) - 2}`, size: "small", variant: "outlined" }))), /* @__PURE__ */ import_react2.default.createElement(import_material2.TableCell, { align: "right" }, /* @__PURE__ */ import_react2.default.createElement(import_material2.IconButton, { onClick: () => handleOpenEdit(key), title: "Edit key" }, /* @__PURE__ */ import_react2.default.createElement(import_icons_material.Edit, { fontSize: "small" })), /* @__PURE__ */ import_react2.default.createElement(
478
+ ), /* @__PURE__ */ import_react2.default.createElement(import_material2.IconButton, { size: "small", onClick: () => copyToClipboard(keyId), title: "Copy Key ID" }, /* @__PURE__ */ import_react2.default.createElement(import_icons_material2.ContentCopy, { fontSize: "small" })))), /* @__PURE__ */ import_react2.default.createElement(import_material2.TableCell, null, formatDate(key.created_at)), /* @__PURE__ */ import_react2.default.createElement(import_material2.TableCell, null, /* @__PURE__ */ import_react2.default.createElement(ExpiryChip, { expiresAt: key.expires_at })), /* @__PURE__ */ import_react2.default.createElement(import_material2.TableCell, null, /* @__PURE__ */ import_react2.default.createElement(BudgetCell, { spend: key.spend ?? 0, maxBudget: key.max_budget })), /* @__PURE__ */ import_react2.default.createElement(import_material2.TableCell, null, /* @__PURE__ */ import_react2.default.createElement(import_material2.Typography, { variant: "body2" }, key.tpm_limit ?? "-", " / ", key.rpm_limit ?? "-")), /* @__PURE__ */ import_react2.default.createElement(import_material2.TableCell, null, /* @__PURE__ */ import_react2.default.createElement(import_material2.Box, { display: "flex", gap: 0.5, flexWrap: "wrap" }, key.models?.slice(0, 2).map((model) => /* @__PURE__ */ import_react2.default.createElement(import_material2.Chip, { key: model, label: model, size: "small" })), (key.models?.length || 0) > 2 && /* @__PURE__ */ import_react2.default.createElement(import_material2.Chip, { label: `+${(key.models?.length || 0) - 2}`, size: "small", variant: "outlined" }))), /* @__PURE__ */ import_react2.default.createElement(import_material2.TableCell, { align: "right" }, /* @__PURE__ */ import_react2.default.createElement(import_material2.IconButton, { onClick: () => handleOpenEdit(key), title: "Edit key" }, /* @__PURE__ */ import_react2.default.createElement(import_icons_material2.Edit, { fontSize: "small" })), /* @__PURE__ */ import_react2.default.createElement(
546
479
  import_material2.IconButton,
547
480
  {
548
481
  onClick: () => handleToggleBlock(key),
@@ -550,7 +483,7 @@ var init_KeysTable = __esm({
550
483
  color: key.blocked ? "warning" : "default",
551
484
  title: key.blocked ? "Unblock key" : "Block key \u2014 suspends without revoking"
552
485
  },
553
- isBlocking ? /* @__PURE__ */ import_react2.default.createElement(import_material2.CircularProgress, { size: 18 }) : key.blocked ? /* @__PURE__ */ import_react2.default.createElement(import_icons_material.LockOpen, { fontSize: "small" }) : /* @__PURE__ */ import_react2.default.createElement(import_icons_material.Lock, { fontSize: "small" })
486
+ isBlocking ? /* @__PURE__ */ import_react2.default.createElement(import_material2.CircularProgress, { size: 18 }) : key.blocked ? /* @__PURE__ */ import_react2.default.createElement(import_icons_material2.LockOpen, { fontSize: "small" }) : /* @__PURE__ */ import_react2.default.createElement(import_icons_material2.Lock, { fontSize: "small" })
554
487
  ), /* @__PURE__ */ import_react2.default.createElement(
555
488
  import_material2.IconButton,
556
489
  {
@@ -558,10 +491,281 @@ var init_KeysTable = __esm({
558
491
  onClick: () => setDeleteConfirmId(keyId),
559
492
  title: "Revoke key"
560
493
  },
561
- /* @__PURE__ */ import_react2.default.createElement(import_icons_material.Delete, null)
494
+ /* @__PURE__ */ import_react2.default.createElement(import_icons_material2.Delete, null)
562
495
  )));
563
- }))))), /* @__PURE__ */ import_react2.default.createElement(import_material2.Dialog, { open: generateModalOpen, onClose: handleCloseModal, maxWidth: "sm", fullWidth: true }, /* @__PURE__ */ import_react2.default.createElement(import_material2.DialogTitle, null, newKeyValue ? "Key Generated" : "Generate New Key"), /* @__PURE__ */ import_react2.default.createElement(import_material2.DialogContent, null, newKeyValue ? /* @__PURE__ */ import_react2.default.createElement(import_material2.Box, null, /* @__PURE__ */ import_react2.default.createElement(import_material2.Typography, { variant: "body2", color: "text.secondary", gutterBottom: true }, "Copy this key now. You won't be able to see it again."), /* @__PURE__ */ import_react2.default.createElement(
564
- import_material2.Box,
496
+ }))))), /* @__PURE__ */ import_react2.default.createElement(import_material2.Dialog, { open: !!editingKey, onClose: handleCloseEdit, maxWidth: "sm", fullWidth: true }, /* @__PURE__ */ import_react2.default.createElement(import_material2.DialogTitle, null, "Edit Key"), /* @__PURE__ */ import_react2.default.createElement(import_material2.DialogContent, null, editingKey && /* @__PURE__ */ import_react2.default.createElement(import_material2.Box, { display: "flex", flexDirection: "column", gap: 2, mt: 1 }, /* @__PURE__ */ import_react2.default.createElement(import_material2.Typography, { variant: "body2", color: "text.secondary" }, /* @__PURE__ */ import_react2.default.createElement("code", { style: { fontFamily: "monospace", color: "inherit" } }, maskKey(editingKey.key))), /* @__PURE__ */ import_react2.default.createElement(
497
+ import_material2.TextField,
498
+ {
499
+ label: "Alias",
500
+ value: editForm.key_alias || "",
501
+ onChange: (e) => setEditForm({ ...editForm, key_alias: e.target.value }),
502
+ fullWidth: true
503
+ }
504
+ ), models.length > 0 && /* @__PURE__ */ import_react2.default.createElement(
505
+ import_material2.Autocomplete,
506
+ {
507
+ multiple: true,
508
+ options: models,
509
+ groupBy: (m) => m.mode || "other",
510
+ getOptionLabel: (m) => m.model_name,
511
+ value: editSelectedModels,
512
+ onChange: (_e, selected) => setEditForm({ ...editForm, models: selected.map((m) => m.model_name) }),
513
+ renderOption: (props, m) => /* @__PURE__ */ import_react2.default.createElement("li", { ...props }, modelOption(m)),
514
+ renderInput: (params) => /* @__PURE__ */ import_react2.default.createElement(import_material2.TextField, { ...params, label: "Models", fullWidth: true })
515
+ }
516
+ ), /* @__PURE__ */ import_react2.default.createElement(
517
+ import_material2.TextField,
518
+ {
519
+ label: "Max Budget (USD)",
520
+ type: "number",
521
+ value: editForm.max_budget ?? "",
522
+ onChange: (e) => setEditForm({ ...editForm, max_budget: e.target.value ? Number(e.target.value) : void 0 }),
523
+ fullWidth: true
524
+ }
525
+ ), /* @__PURE__ */ import_react2.default.createElement(
526
+ import_material2.TextField,
527
+ {
528
+ label: "TPM Limit",
529
+ type: "number",
530
+ value: editForm.tpm_limit ?? "",
531
+ onChange: (e) => setEditForm({ ...editForm, tpm_limit: e.target.value ? Number(e.target.value) : void 0 }),
532
+ helperText: "Max tokens per minute this key can consume across all models. Leave blank for no limit.",
533
+ fullWidth: true
534
+ }
535
+ ), /* @__PURE__ */ import_react2.default.createElement(
536
+ import_material2.TextField,
537
+ {
538
+ label: "RPM Limit",
539
+ type: "number",
540
+ value: editForm.rpm_limit ?? "",
541
+ onChange: (e) => setEditForm({ ...editForm, rpm_limit: e.target.value ? Number(e.target.value) : void 0 }),
542
+ helperText: "Max requests per minute this key can make across all models. Leave blank for no limit.",
543
+ fullWidth: true
544
+ }
545
+ ), /* @__PURE__ */ import_react2.default.createElement(import_material2.Box, { mt: 1, pt: 2, borderTop: "1px solid", sx: { borderColor: "divider" } }, /* @__PURE__ */ import_react2.default.createElement(import_material2.Typography, { variant: "caption", color: "text.secondary", display: "block", mb: 1 }, "Danger Zone"), !resetSpendConfirm ? /* @__PURE__ */ import_react2.default.createElement(
546
+ import_material2.Button,
547
+ {
548
+ size: "small",
549
+ color: "warning",
550
+ variant: "outlined",
551
+ onClick: () => setResetSpendConfirm(true)
552
+ },
553
+ "Reset Spend to $0"
554
+ ) : /* @__PURE__ */ import_react2.default.createElement(import_material2.Box, { display: "flex", alignItems: "center", gap: 1, flexWrap: "wrap" }, /* @__PURE__ */ import_react2.default.createElement(import_material2.Typography, { variant: "body2", color: "warning.main" }, "Zero out spend counter?"), /* @__PURE__ */ import_react2.default.createElement(
555
+ import_material2.Button,
556
+ {
557
+ size: "small",
558
+ color: "warning",
559
+ variant: "contained",
560
+ disabled: resetSpendSubmitting,
561
+ onClick: handleResetSpend
562
+ },
563
+ resetSpendSubmitting ? /* @__PURE__ */ import_react2.default.createElement(import_material2.CircularProgress, { size: 16 }) : "Confirm"
564
+ ), /* @__PURE__ */ import_react2.default.createElement(import_material2.Button, { size: "small", onClick: () => setResetSpendConfirm(false) }, "Cancel"))))), /* @__PURE__ */ import_react2.default.createElement(import_material2.DialogActions, null, /* @__PURE__ */ import_react2.default.createElement(import_material2.Button, { onClick: handleCloseEdit }, "Cancel"), /* @__PURE__ */ import_react2.default.createElement(import_material2.Button, { onClick: handleUpdate, variant: "contained", color: "primary", disabled: editSubmitting }, editSubmitting ? /* @__PURE__ */ import_react2.default.createElement(import_material2.CircularProgress, { size: 24 }) : "Save"))), /* @__PURE__ */ import_react2.default.createElement(import_material2.Dialog, { open: !!deleteConfirmId, onClose: () => setDeleteConfirmId(null), maxWidth: "xs", fullWidth: true }, /* @__PURE__ */ import_react2.default.createElement(import_material2.DialogTitle, null, "Revoke Key?"), /* @__PURE__ */ import_react2.default.createElement(import_material2.DialogContent, null, /* @__PURE__ */ import_react2.default.createElement(import_material2.Typography, null, "This will permanently revoke the key. Any integrations using it will stop working immediately.")), /* @__PURE__ */ import_react2.default.createElement(import_material2.DialogActions, null, /* @__PURE__ */ import_react2.default.createElement(import_material2.Button, { onClick: () => setDeleteConfirmId(null), disabled: deleteSubmitting }, "Cancel"), /* @__PURE__ */ import_react2.default.createElement(
565
+ import_material2.Button,
566
+ {
567
+ onClick: handleConfirmDelete,
568
+ variant: "contained",
569
+ color: "error",
570
+ disabled: deleteSubmitting
571
+ },
572
+ deleteSubmitting ? /* @__PURE__ */ import_react2.default.createElement(import_material2.CircularProgress, { size: 20 }) : "Revoke"
573
+ ))), /* @__PURE__ */ import_react2.default.createElement(import_material2.Dialog, { open: pruneConfirmCount !== null, onClose: () => setPruneConfirmCount(null), maxWidth: "xs", fullWidth: true }, /* @__PURE__ */ import_react2.default.createElement(import_material2.DialogTitle, null, "Prune Expired Keys?"), /* @__PURE__ */ import_react2.default.createElement(import_material2.DialogContent, null, /* @__PURE__ */ import_react2.default.createElement(import_material2.Typography, null, "This will permanently delete ", pruneConfirmCount, " expired key", pruneConfirmCount !== 1 ? "s" : "", " from LiteLLM. Any integrations using them will stop working immediately.")), /* @__PURE__ */ import_react2.default.createElement(import_material2.DialogActions, null, /* @__PURE__ */ import_react2.default.createElement(import_material2.Button, { onClick: () => setPruneConfirmCount(null), disabled: pruneSubmitting }, "Cancel"), /* @__PURE__ */ import_react2.default.createElement(
574
+ import_material2.Button,
575
+ {
576
+ onClick: handlePruneExpired,
577
+ variant: "contained",
578
+ color: "error",
579
+ disabled: pruneSubmitting
580
+ },
581
+ pruneSubmitting ? /* @__PURE__ */ import_react2.default.createElement(import_material2.CircularProgress, { size: 20 }) : "Prune"
582
+ ))));
583
+ };
584
+ }
585
+ });
586
+
587
+ // src/format.ts
588
+ var fmtUsd, fmtInt, estimateTokensFromBudget;
589
+ var init_format = __esm({
590
+ "src/format.ts"() {
591
+ "use strict";
592
+ fmtUsd = (n) => `$${(n ?? 0).toFixed(n < 1 ? 4 : 2)}`;
593
+ fmtInt = (n) => (n ?? 0).toLocaleString();
594
+ estimateTokensFromBudget = (budget, pricePerToken) => {
595
+ if (!pricePerToken || pricePerToken <= 0 || !budget || budget <= 0) return null;
596
+ return Math.floor(budget / pricePerToken);
597
+ };
598
+ }
599
+ });
600
+
601
+ // src/components/GenerateKeyDialog.tsx
602
+ function trimSlash(url) {
603
+ return url.replace(/\/+$/, "");
604
+ }
605
+ function buildSnippets(baseUrl, key, model) {
606
+ const base = trimSlash(baseUrl);
607
+ return {
608
+ curl: `curl ${base}/v1/chat/completions \\
609
+ -H "Authorization: Bearer ${key}" \\
610
+ -H "Content-Type: application/json" \\
611
+ -d '{
612
+ "model": "${model}",
613
+ "messages": [{ "role": "user", "content": "Hello!" }]
614
+ }'`,
615
+ openai: `from openai import OpenAI
616
+
617
+ client = OpenAI(
618
+ api_key="${key}",
619
+ base_url="${base}/v1",
620
+ )
621
+
622
+ response = client.chat.completions.create(
623
+ model="${model}",
624
+ messages=[{ "role": "user", "content": "Hello!" }],
625
+ )
626
+ print(response.choices[0].message.content)`
627
+ };
628
+ }
629
+ function fmtCost2(perToken) {
630
+ if (!perToken) return null;
631
+ const per1k = perToken * 1e3;
632
+ return per1k < 0.01 ? `$${(perToken * 1e6).toFixed(2)}/M` : `$${per1k.toFixed(3)}/1K`;
633
+ }
634
+ function formatContextWindow2(maxInput, maxOutput) {
635
+ if (!maxInput && !maxOutput) return null;
636
+ const fmt = (n) => {
637
+ if (!n) return null;
638
+ if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
639
+ if (n >= 1e3) return `${Math.round(n / 1e3)}K`;
640
+ return String(n);
641
+ };
642
+ const inPart = fmt(maxInput);
643
+ const outPart = fmt(maxOutput);
644
+ if (inPart && outPart) return `ctx ${inPart} in / ${outPart} out`;
645
+ if (inPart) return `ctx ${inPart}`;
646
+ if (outPart) return `ctx ${outPart} out`;
647
+ return null;
648
+ }
649
+ var import_react3, import_material3, import_icons_material3, generateDefaultAlias, emptyForm, GenerateKeyDialog, SnippetTabs;
650
+ var init_GenerateKeyDialog = __esm({
651
+ "src/components/GenerateKeyDialog.tsx"() {
652
+ "use strict";
653
+ import_react3 = __toESM(require("react"));
654
+ import_material3 = require("@mui/material");
655
+ import_icons_material3 = require("@mui/icons-material");
656
+ init_format();
657
+ generateDefaultAlias = (username) => {
658
+ const base = (username || "user").split("@")[0].toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "user";
659
+ const hash = Math.random().toString(36).slice(2, 8);
660
+ return `${base}-${hash}`;
661
+ };
662
+ emptyForm = () => ({
663
+ alias: "",
664
+ models: [],
665
+ duration: "30d",
666
+ max_budget: 100,
667
+ tpm_limit: void 0,
668
+ rpm_limit: void 0,
669
+ team_id: void 0,
670
+ key_type: "llm_api"
671
+ });
672
+ GenerateKeyDialog = ({
673
+ open,
674
+ onClose,
675
+ keys,
676
+ models,
677
+ teams,
678
+ username,
679
+ keyGenerationSettings,
680
+ onGenerateKey,
681
+ onGetConfig
682
+ }) => {
683
+ const [formData, setFormData] = (0, import_react3.useState)(emptyForm());
684
+ const [unlimitedBudget, setUnlimitedBudget] = (0, import_react3.useState)(false);
685
+ const [submitting, setSubmitting] = (0, import_react3.useState)(false);
686
+ const [newKeyValue, setNewKeyValue] = (0, import_react3.useState)(null);
687
+ const [newKeySnippets, setNewKeySnippets] = (0, import_react3.useState)(null);
688
+ const [newKeyModel, setNewKeyModel] = (0, import_react3.useState)("");
689
+ const [generateError, setGenerateError] = (0, import_react3.useState)(null);
690
+ (0, import_react3.useEffect)(() => {
691
+ if (open) {
692
+ setFormData({ ...emptyForm(), alias: generateDefaultAlias(username) });
693
+ setUnlimitedBudget(false);
694
+ setNewKeyValue(null);
695
+ setNewKeySnippets(null);
696
+ setNewKeyModel("");
697
+ setGenerateError(null);
698
+ }
699
+ }, [open, username]);
700
+ const allowUnlimitedBudget = keyGenerationSettings?.allowUnlimitedBudget ?? false;
701
+ const teamRequired = keyGenerationSettings?.teamRequired ?? true;
702
+ const selectedTeam = teams.find((t) => t.team_id === formData.team_id) ?? null;
703
+ const availableModels = (0, import_react3.useMemo)(() => {
704
+ const teamModels = selectedTeam?.models;
705
+ if (teamModels && teamModels.length > 0) {
706
+ const allowed = new Set(teamModels);
707
+ return models.filter((m) => allowed.has(m.model_name));
708
+ }
709
+ return models;
710
+ }, [models, selectedTeam]);
711
+ const selectedModels = availableModels.filter((m) => (formData.models || []).includes(m.model_name));
712
+ const aliasError = !(formData.alias || "").trim();
713
+ const teamError = teamRequired && !formData.team_id;
714
+ const budgetInvalid = !unlimitedBudget && (formData.max_budget === void 0 || formData.max_budget === null || formData.max_budget <= 0);
715
+ const canGenerate = !aliasError && !teamError && !budgetInvalid && !submitting;
716
+ const aliasDuplicate = !!formData.alias && keys.some((k) => k.key_alias === formData.alias);
717
+ const budgetEstimate = (0, import_react3.useMemo)(() => {
718
+ if (unlimitedBudget || budgetInvalid) return null;
719
+ const inputCosts = selectedModels.map((m) => m.input_cost_per_token).filter((c) => typeof c === "number" && c > 0);
720
+ if (inputCosts.length === 0) return null;
721
+ const pricePerToken = Math.max(...inputCosts);
722
+ return estimateTokensFromBudget(formData.max_budget ?? 0, pricePerToken);
723
+ }, [formData.max_budget, selectedModels, unlimitedBudget, budgetInvalid]);
724
+ const handleGenerate = async () => {
725
+ setSubmitting(true);
726
+ setGenerateError(null);
727
+ try {
728
+ const request = {
729
+ ...formData,
730
+ max_budget: unlimitedBudget ? null : formData.max_budget
731
+ };
732
+ const response = await onGenerateKey(request);
733
+ setNewKeyValue(response.key);
734
+ setNewKeyModel(formData.models?.[0] ?? "");
735
+ setNewKeySnippets(null);
736
+ try {
737
+ const config = await onGetConfig();
738
+ const model = formData.models?.[0] ?? "";
739
+ setNewKeySnippets(buildSnippets(config.baseUrl, response.key, model));
740
+ } catch {
741
+ }
742
+ setFormData(emptyForm());
743
+ setUnlimitedBudget(false);
744
+ } catch (error) {
745
+ setGenerateError(error?.message ?? "Failed to generate key");
746
+ } finally {
747
+ setSubmitting(false);
748
+ }
749
+ };
750
+ const handleClose = () => {
751
+ onClose();
752
+ setGenerateError(null);
753
+ setNewKeyValue(null);
754
+ setNewKeySnippets(null);
755
+ setFormData(emptyForm());
756
+ setUnlimitedBudget(false);
757
+ };
758
+ const copyToClipboard = (text) => {
759
+ navigator.clipboard.writeText(text);
760
+ };
761
+ const modelOption = (m) => {
762
+ const inCost = fmtCost2(m.input_cost_per_token);
763
+ const outCost = fmtCost2(m.output_cost_per_token);
764
+ const ctx = formatContextWindow2(m.max_input_tokens, m.max_output_tokens);
765
+ return /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, null, /* @__PURE__ */ import_react3.default.createElement("span", null, m.model_name), m.supports_function_calling && " \u{1F527}", m.supports_vision && " \u{1F441}\uFE0F", ctx && /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "caption", color: "text.secondary", sx: { ml: 1 } }, ctx), (inCost || outCost) && /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "caption", color: "text.secondary", sx: { ml: 1 } }, inCost, " in \xB7 ", outCost, " out"));
766
+ };
767
+ return /* @__PURE__ */ import_react3.default.createElement(import_material3.Dialog, { open, onClose: handleClose, maxWidth: "sm", fullWidth: true }, /* @__PURE__ */ import_react3.default.createElement(import_material3.DialogTitle, null, newKeyValue ? "Key Generated" : "Generate New Key"), /* @__PURE__ */ import_react3.default.createElement(import_material3.DialogContent, null, newKeyValue ? /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, null, /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "body2", color: "text.secondary", gutterBottom: true }, "Copy this key now. You won't be able to see it again."), /* @__PURE__ */ import_react3.default.createElement(
768
+ import_material3.Box,
565
769
  {
566
770
  display: "flex",
567
771
  alignItems: "center",
@@ -575,8 +779,8 @@ var init_KeysTable = __esm({
575
779
  borderRadius: 1
576
780
  }
577
781
  },
578
- /* @__PURE__ */ import_react2.default.createElement(
579
- import_material2.Typography,
782
+ /* @__PURE__ */ import_react3.default.createElement(
783
+ import_material3.Typography,
580
784
  {
581
785
  component: "code",
582
786
  color: "text.primary",
@@ -584,20 +788,24 @@ var init_KeysTable = __esm({
584
788
  },
585
789
  newKeyValue
586
790
  ),
587
- /* @__PURE__ */ import_react2.default.createElement(import_material2.IconButton, { onClick: () => copyToClipboard(newKeyValue) }, /* @__PURE__ */ import_react2.default.createElement(import_icons_material.ContentCopy, null))
588
- ), newKeySnippets && /* @__PURE__ */ import_react2.default.createElement(import_material2.Box, { mt: 3 }, /* @__PURE__ */ import_react2.default.createElement(import_material2.Box, { display: "flex", alignItems: "center", gap: 1, mb: 1 }, /* @__PURE__ */ import_react2.default.createElement(import_icons_material.Code, { fontSize: "small", color: "action" }), /* @__PURE__ */ import_react2.default.createElement(import_material2.Typography, { variant: "subtitle2" }, "Start calling the proxy \u2014 paste and run")), /* @__PURE__ */ import_react2.default.createElement(SnippetTabs, { snippets: newKeySnippets, model: newKeyModel, onCopy: copyToClipboard }))) : /* @__PURE__ */ import_react2.default.createElement(import_material2.Box, { display: "flex", flexDirection: "column", gap: 2, mt: 1 }, /* @__PURE__ */ import_react2.default.createElement(
589
- import_material2.TextField,
791
+ /* @__PURE__ */ import_react3.default.createElement(import_material3.IconButton, { onClick: () => copyToClipboard(newKeyValue) }, /* @__PURE__ */ import_react3.default.createElement(import_icons_material3.ContentCopy, null))
792
+ ), newKeySnippets && /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { mt: 3 }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { display: "flex", alignItems: "center", gap: 1, mb: 1 }, /* @__PURE__ */ import_react3.default.createElement(import_icons_material3.Code, { fontSize: "small", color: "action" }), /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "subtitle2" }, "Start calling the proxy \u2014 paste and run")), /* @__PURE__ */ import_react3.default.createElement(SnippetTabs, { snippets: newKeySnippets, model: newKeyModel, onCopy: copyToClipboard }))) : /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { display: "flex", flexDirection: "column", gap: 2, mt: 1 }, generateError && /* @__PURE__ */ import_react3.default.createElement(import_material3.Alert, { severity: "error", onClose: () => setGenerateError(null) }, generateError), /* @__PURE__ */ import_react3.default.createElement(
793
+ import_material3.TextField,
590
794
  {
591
795
  label: "Alias",
592
796
  value: formData.alias || "",
593
- onChange: (e) => setFormData({ ...formData, alias: e.target.value }),
797
+ onChange: (e) => {
798
+ setFormData({ ...formData, alias: e.target.value });
799
+ setGenerateError(null);
800
+ },
594
801
  error: aliasError,
595
- helperText: aliasError ? "Alias is required" : void 0,
802
+ color: aliasDuplicate ? "warning" : void 0,
803
+ helperText: aliasError ? "Alias is required" : aliasDuplicate ? "This alias is already used by one of your keys \u2014 LiteLLM requires aliases to be unique across all keys" : void 0,
596
804
  required: true,
597
805
  fullWidth: true
598
806
  }
599
- ), /* @__PURE__ */ import_react2.default.createElement(
600
- import_material2.TextField,
807
+ ), /* @__PURE__ */ import_react3.default.createElement(
808
+ import_material3.TextField,
601
809
  {
602
810
  select: true,
603
811
  label: "Duration",
@@ -605,45 +813,59 @@ var init_KeysTable = __esm({
605
813
  onChange: (e) => setFormData({ ...formData, duration: e.target.value }),
606
814
  fullWidth: true
607
815
  },
608
- /* @__PURE__ */ import_react2.default.createElement(import_material2.MenuItem, { value: "1d" }, "1 Day"),
609
- /* @__PURE__ */ import_react2.default.createElement(import_material2.MenuItem, { value: "7d" }, "7 Days"),
610
- /* @__PURE__ */ import_react2.default.createElement(import_material2.MenuItem, { value: "30d" }, "30 Days"),
611
- /* @__PURE__ */ import_react2.default.createElement(import_material2.MenuItem, { value: "90d" }, "90 Days"),
612
- /* @__PURE__ */ import_react2.default.createElement(import_material2.MenuItem, { value: "1y" }, "1 Year")
613
- ), teams.length > 0 && /* @__PURE__ */ import_react2.default.createElement(
614
- import_material2.Autocomplete,
816
+ /* @__PURE__ */ import_react3.default.createElement(import_material3.MenuItem, { value: "1d" }, "1 Day"),
817
+ /* @__PURE__ */ import_react3.default.createElement(import_material3.MenuItem, { value: "7d" }, "7 Days"),
818
+ /* @__PURE__ */ import_react3.default.createElement(import_material3.MenuItem, { value: "30d" }, "30 Days"),
819
+ /* @__PURE__ */ import_react3.default.createElement(import_material3.MenuItem, { value: "90d" }, "90 Days"),
820
+ /* @__PURE__ */ import_react3.default.createElement(import_material3.MenuItem, { value: "1y" }, "1 Year")
821
+ ), teams.length > 0 ? /* @__PURE__ */ import_react3.default.createElement(
822
+ import_material3.Autocomplete,
615
823
  {
616
824
  options: teams,
617
825
  getOptionLabel: (t) => t.team_alias || t.team_id,
618
826
  value: selectedTeam,
619
- onChange: (_e, team) => setFormData({ ...formData, team_id: team?.team_id }),
620
- renderInput: (params) => /* @__PURE__ */ import_react2.default.createElement(
621
- import_material2.TextField,
827
+ onChange: (_e, team) => {
828
+ const teamModels = team?.models;
829
+ const restrictedModels = teamModels && teamModels.length > 0 ? (formData.models || []).filter((m) => teamModels.includes(m)) : formData.models;
830
+ setFormData({ ...formData, team_id: team?.team_id, models: restrictedModels });
831
+ },
832
+ renderInput: (params) => /* @__PURE__ */ import_react3.default.createElement(
833
+ import_material3.TextField,
622
834
  {
623
835
  ...params,
624
836
  label: "Team",
625
- helperText: "Optional: bind this key to a specific team for scoped access",
837
+ error: teamError,
838
+ helperText: teamError ? "Team is required" : teamRequired ? "Bind this key to a team for scoped access" : "Optional: bind this key to a specific team for scoped access",
839
+ required: teamRequired,
626
840
  fullWidth: true
627
841
  }
628
842
  )
629
843
  }
630
- ), models.length > 0 && /* @__PURE__ */ import_react2.default.createElement(
631
- import_material2.Autocomplete,
844
+ ) : teamRequired ? /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "body2", color: "error" }, "Team selection is required, but you don't belong to any team yet \u2014 contact your administrator.") : null, availableModels.length > 0 && /* @__PURE__ */ import_react3.default.createElement(
845
+ import_material3.Autocomplete,
632
846
  {
633
847
  multiple: true,
634
- options: models,
848
+ options: availableModels,
635
849
  groupBy: (m) => m.mode || "other",
636
850
  getOptionLabel: (m) => m.model_name,
637
851
  value: selectedModels,
638
852
  onChange: (_e, selected) => setFormData({ ...formData, models: selected.map((m) => m.model_name) }),
639
- renderOption: (props, m) => /* @__PURE__ */ import_react2.default.createElement("li", { ...props }, modelOption(m)),
640
- renderInput: (params) => /* @__PURE__ */ import_react2.default.createElement(import_material2.TextField, { ...params, label: "Models", helperText: "Leave empty to allow all models", fullWidth: true })
853
+ renderOption: (props, m) => /* @__PURE__ */ import_react3.default.createElement("li", { ...props }, modelOption(m)),
854
+ renderInput: (params) => /* @__PURE__ */ import_react3.default.createElement(
855
+ import_material3.TextField,
856
+ {
857
+ ...params,
858
+ label: "Models",
859
+ helperText: selectedTeam ? "Leave empty to allow all models available to this team" : "Leave empty to allow all models",
860
+ fullWidth: true
861
+ }
862
+ )
641
863
  }
642
- ), /* @__PURE__ */ import_react2.default.createElement(
643
- import_material2.FormControlLabel,
864
+ ), allowUnlimitedBudget && /* @__PURE__ */ import_react3.default.createElement(
865
+ import_material3.FormControlLabel,
644
866
  {
645
- control: /* @__PURE__ */ import_react2.default.createElement(
646
- import_material2.Checkbox,
867
+ control: /* @__PURE__ */ import_react3.default.createElement(
868
+ import_material3.Checkbox,
647
869
  {
648
870
  checked: unlimitedBudget,
649
871
  onChange: (e) => {
@@ -658,8 +880,8 @@ var init_KeysTable = __esm({
658
880
  ),
659
881
  label: "Unlimited budget"
660
882
  }
661
- ), /* @__PURE__ */ import_react2.default.createElement(
662
- import_material2.TextField,
883
+ ), /* @__PURE__ */ import_react3.default.createElement(
884
+ import_material3.TextField,
663
885
  {
664
886
  label: "Max Budget (USD)",
665
887
  type: "number",
@@ -671,141 +893,59 @@ var init_KeysTable = __esm({
671
893
  required: true,
672
894
  fullWidth: true
673
895
  }
674
- ), /* @__PURE__ */ import_react2.default.createElement(
675
- import_material2.TextField,
896
+ ), /* @__PURE__ */ import_react3.default.createElement(
897
+ import_material3.TextField,
676
898
  {
677
899
  label: "TPM Limit",
678
900
  type: "number",
679
901
  value: formData.tpm_limit ?? "",
680
902
  onChange: (e) => setFormData({ ...formData, tpm_limit: e.target.value ? Number(e.target.value) : void 0 }),
903
+ helperText: "Max tokens per minute this key can consume across all models. Leave blank for no limit.",
681
904
  fullWidth: true
682
905
  }
683
- ), /* @__PURE__ */ import_react2.default.createElement(
684
- import_material2.TextField,
906
+ ), /* @__PURE__ */ import_react3.default.createElement(
907
+ import_material3.TextField,
685
908
  {
686
909
  label: "RPM Limit",
687
910
  type: "number",
688
911
  value: formData.rpm_limit ?? "",
689
912
  onChange: (e) => setFormData({ ...formData, rpm_limit: e.target.value ? Number(e.target.value) : void 0 }),
913
+ helperText: "Max requests per minute this key can make across all models. Leave blank for no limit.",
690
914
  fullWidth: true
691
915
  }
692
- ))), /* @__PURE__ */ import_react2.default.createElement(import_material2.DialogActions, null, newKeyValue ? /* @__PURE__ */ import_react2.default.createElement(import_material2.Button, { onClick: handleCloseModal, variant: "contained", color: "success" }, "Done") : /* @__PURE__ */ import_react2.default.createElement(import_react2.default.Fragment, null, /* @__PURE__ */ import_react2.default.createElement(import_material2.Button, { onClick: handleCloseModal }, "Cancel"), /* @__PURE__ */ import_react2.default.createElement(
693
- import_material2.Button,
916
+ ))), /* @__PURE__ */ import_react3.default.createElement(import_material3.DialogActions, null, newKeyValue ? /* @__PURE__ */ import_react3.default.createElement(import_material3.Button, { onClick: handleClose, variant: "contained", color: "success" }, "Done") : /* @__PURE__ */ import_react3.default.createElement(import_react3.default.Fragment, null, /* @__PURE__ */ import_react3.default.createElement(import_material3.Button, { onClick: handleClose }, "Cancel"), /* @__PURE__ */ import_react3.default.createElement(
917
+ import_material3.Button,
694
918
  {
695
919
  onClick: handleGenerate,
696
920
  variant: "contained",
697
921
  color: "primary",
698
922
  disabled: !canGenerate
699
923
  },
700
- submitting ? /* @__PURE__ */ import_react2.default.createElement(import_material2.CircularProgress, { size: 24 }) : "Generate"
701
- )))), /* @__PURE__ */ import_react2.default.createElement(import_material2.Dialog, { open: !!editingKey, onClose: handleCloseEdit, maxWidth: "sm", fullWidth: true }, /* @__PURE__ */ import_react2.default.createElement(import_material2.DialogTitle, null, "Edit Key"), /* @__PURE__ */ import_react2.default.createElement(import_material2.DialogContent, null, editingKey && /* @__PURE__ */ import_react2.default.createElement(import_material2.Box, { display: "flex", flexDirection: "column", gap: 2, mt: 1 }, /* @__PURE__ */ import_react2.default.createElement(import_material2.Typography, { variant: "body2", color: "text.secondary" }, /* @__PURE__ */ import_react2.default.createElement("code", { style: { fontFamily: "monospace", color: "inherit" } }, maskKey(editingKey.key))), /* @__PURE__ */ import_react2.default.createElement(
702
- import_material2.TextField,
703
- {
704
- label: "Alias",
705
- value: editForm.key_alias || "",
706
- onChange: (e) => setEditForm({ ...editForm, key_alias: e.target.value }),
707
- fullWidth: true
708
- }
709
- ), models.length > 0 && /* @__PURE__ */ import_react2.default.createElement(
710
- import_material2.Autocomplete,
711
- {
712
- multiple: true,
713
- options: models,
714
- groupBy: (m) => m.mode || "other",
715
- getOptionLabel: (m) => m.model_name,
716
- value: editSelectedModels,
717
- onChange: (_e, selected) => setEditForm({ ...editForm, models: selected.map((m) => m.model_name) }),
718
- renderOption: (props, m) => /* @__PURE__ */ import_react2.default.createElement("li", { ...props }, modelOption(m)),
719
- renderInput: (params) => /* @__PURE__ */ import_react2.default.createElement(import_material2.TextField, { ...params, label: "Models", fullWidth: true })
720
- }
721
- ), /* @__PURE__ */ import_react2.default.createElement(
722
- import_material2.TextField,
723
- {
724
- label: "Max Budget (USD)",
725
- type: "number",
726
- value: editForm.max_budget ?? "",
727
- onChange: (e) => setEditForm({ ...editForm, max_budget: e.target.value ? Number(e.target.value) : void 0 }),
728
- fullWidth: true
729
- }
730
- ), /* @__PURE__ */ import_react2.default.createElement(
731
- import_material2.TextField,
732
- {
733
- label: "TPM Limit",
734
- type: "number",
735
- value: editForm.tpm_limit ?? "",
736
- onChange: (e) => setEditForm({ ...editForm, tpm_limit: e.target.value ? Number(e.target.value) : void 0 }),
737
- fullWidth: true
738
- }
739
- ), /* @__PURE__ */ import_react2.default.createElement(
740
- import_material2.TextField,
741
- {
742
- label: "RPM Limit",
743
- type: "number",
744
- value: editForm.rpm_limit ?? "",
745
- onChange: (e) => setEditForm({ ...editForm, rpm_limit: e.target.value ? Number(e.target.value) : void 0 }),
746
- fullWidth: true
747
- }
748
- ), /* @__PURE__ */ import_react2.default.createElement(import_material2.Box, { mt: 1, pt: 2, borderTop: "1px solid", sx: { borderColor: "divider" } }, /* @__PURE__ */ import_react2.default.createElement(import_material2.Typography, { variant: "caption", color: "text.secondary", display: "block", mb: 1 }, "Danger Zone"), !resetSpendConfirm ? /* @__PURE__ */ import_react2.default.createElement(
749
- import_material2.Button,
750
- {
751
- size: "small",
752
- color: "warning",
753
- variant: "outlined",
754
- onClick: () => setResetSpendConfirm(true)
755
- },
756
- "Reset Spend to $0"
757
- ) : /* @__PURE__ */ import_react2.default.createElement(import_material2.Box, { display: "flex", alignItems: "center", gap: 1, flexWrap: "wrap" }, /* @__PURE__ */ import_react2.default.createElement(import_material2.Typography, { variant: "body2", color: "warning.main" }, "Zero out spend counter?"), /* @__PURE__ */ import_react2.default.createElement(
758
- import_material2.Button,
759
- {
760
- size: "small",
761
- color: "warning",
762
- variant: "contained",
763
- disabled: resetSpendSubmitting,
764
- onClick: handleResetSpend
765
- },
766
- resetSpendSubmitting ? /* @__PURE__ */ import_react2.default.createElement(import_material2.CircularProgress, { size: 16 }) : "Confirm"
767
- ), /* @__PURE__ */ import_react2.default.createElement(import_material2.Button, { size: "small", onClick: () => setResetSpendConfirm(false) }, "Cancel"))))), /* @__PURE__ */ import_react2.default.createElement(import_material2.DialogActions, null, /* @__PURE__ */ import_react2.default.createElement(import_material2.Button, { onClick: handleCloseEdit }, "Cancel"), /* @__PURE__ */ import_react2.default.createElement(import_material2.Button, { onClick: handleUpdate, variant: "contained", color: "primary", disabled: editSubmitting }, editSubmitting ? /* @__PURE__ */ import_react2.default.createElement(import_material2.CircularProgress, { size: 24 }) : "Save"))), /* @__PURE__ */ import_react2.default.createElement(import_material2.Dialog, { open: !!deleteConfirmId, onClose: () => setDeleteConfirmId(null), maxWidth: "xs", fullWidth: true }, /* @__PURE__ */ import_react2.default.createElement(import_material2.DialogTitle, null, "Revoke Key?"), /* @__PURE__ */ import_react2.default.createElement(import_material2.DialogContent, null, /* @__PURE__ */ import_react2.default.createElement(import_material2.Typography, null, "This will permanently revoke the key. Any integrations using it will stop working immediately.")), /* @__PURE__ */ import_react2.default.createElement(import_material2.DialogActions, null, /* @__PURE__ */ import_react2.default.createElement(import_material2.Button, { onClick: () => setDeleteConfirmId(null), disabled: deleteSubmitting }, "Cancel"), /* @__PURE__ */ import_react2.default.createElement(
768
- import_material2.Button,
769
- {
770
- onClick: handleConfirmDelete,
771
- variant: "contained",
772
- color: "error",
773
- disabled: deleteSubmitting
774
- },
775
- deleteSubmitting ? /* @__PURE__ */ import_react2.default.createElement(import_material2.CircularProgress, { size: 20 }) : "Revoke"
776
- ))), /* @__PURE__ */ import_react2.default.createElement(import_material2.Dialog, { open: pruneConfirmCount !== null, onClose: () => setPruneConfirmCount(null), maxWidth: "xs", fullWidth: true }, /* @__PURE__ */ import_react2.default.createElement(import_material2.DialogTitle, null, "Prune Expired Keys?"), /* @__PURE__ */ import_react2.default.createElement(import_material2.DialogContent, null, /* @__PURE__ */ import_react2.default.createElement(import_material2.Typography, null, "This will permanently delete ", pruneConfirmCount, " expired key", pruneConfirmCount !== 1 ? "s" : "", " from LiteLLM. Any integrations using them will stop working immediately.")), /* @__PURE__ */ import_react2.default.createElement(import_material2.DialogActions, null, /* @__PURE__ */ import_react2.default.createElement(import_material2.Button, { onClick: () => setPruneConfirmCount(null), disabled: pruneSubmitting }, "Cancel"), /* @__PURE__ */ import_react2.default.createElement(
777
- import_material2.Button,
778
- {
779
- onClick: handlePruneExpired,
780
- variant: "contained",
781
- color: "error",
782
- disabled: pruneSubmitting
783
- },
784
- pruneSubmitting ? /* @__PURE__ */ import_react2.default.createElement(import_material2.CircularProgress, { size: 20 }) : "Prune"
924
+ submitting ? /* @__PURE__ */ import_react3.default.createElement(import_material3.CircularProgress, { size: 24 }) : "Generate"
785
925
  ))));
786
926
  };
787
927
  SnippetTabs = ({ snippets, model, onCopy }) => {
788
- const [tab, setTab] = (0, import_react2.useState)("curl");
928
+ const [tab, setTab] = (0, import_react3.useState)("curl");
789
929
  const code = tab === "curl" ? snippets.curl : snippets.openai;
790
- return /* @__PURE__ */ import_react2.default.createElement(import_material2.Box, null, /* @__PURE__ */ import_react2.default.createElement(import_material2.Tabs, { value: tab, onChange: (_, v) => setTab(v), sx: { mb: 1 } }, /* @__PURE__ */ import_react2.default.createElement(import_material2.Tab, { label: "curl", value: "curl" }), /* @__PURE__ */ import_react2.default.createElement(import_material2.Tab, { label: "OpenAI SDK", value: "openai" })), /* @__PURE__ */ import_react2.default.createElement(
791
- import_material2.Box,
930
+ return /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, null, /* @__PURE__ */ import_react3.default.createElement(import_material3.Tabs, { value: tab, onChange: (_, v) => setTab(v), sx: { mb: 1 } }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Tab, { label: "curl", value: "curl" }), /* @__PURE__ */ import_react3.default.createElement(import_material3.Tab, { label: "OpenAI SDK", value: "openai" })), /* @__PURE__ */ import_react3.default.createElement(
931
+ import_material3.Box,
792
932
  {
793
933
  position: "relative",
794
934
  p: 1.5,
795
935
  sx: { backgroundColor: "action.hover", border: "1px solid", borderColor: "divider", borderRadius: 1 }
796
936
  },
797
- /* @__PURE__ */ import_react2.default.createElement(
798
- import_material2.IconButton,
937
+ /* @__PURE__ */ import_react3.default.createElement(
938
+ import_material3.IconButton,
799
939
  {
800
940
  size: "small",
801
941
  onClick: () => onCopy(code),
802
942
  title: "Copy snippet",
803
943
  sx: { position: "absolute", top: 4, right: 4 }
804
944
  },
805
- /* @__PURE__ */ import_react2.default.createElement(import_icons_material.ContentCopy, { fontSize: "small" })
945
+ /* @__PURE__ */ import_react3.default.createElement(import_icons_material3.ContentCopy, { fontSize: "small" })
806
946
  ),
807
- /* @__PURE__ */ import_react2.default.createElement(
808
- import_material2.Typography,
947
+ /* @__PURE__ */ import_react3.default.createElement(
948
+ import_material3.Typography,
809
949
  {
810
950
  component: "pre",
811
951
  sx: {
@@ -819,7 +959,7 @@ var init_KeysTable = __esm({
819
959
  },
820
960
  code
821
961
  ),
822
- model && /* @__PURE__ */ import_react2.default.createElement(import_material2.Typography, { variant: "caption", color: "text.secondary", display: "block", mt: 1 }, "Using model \u201C", model, "\u201D \u2014 swap it for any model you have access to.")
962
+ model && /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "caption", color: "text.secondary", display: "block", mt: 1 }, "Using model \u201C", model, "\u201D \u2014 swap it for any model you have access to.")
823
963
  ));
824
964
  };
825
965
  }
@@ -831,12 +971,12 @@ function modelColor(model) {
831
971
  for (let i = 0; i < model.length; i++) h = (h << 5) + h + model.charCodeAt(i) >>> 0;
832
972
  return MODEL_COLORS[h % MODEL_COLORS.length];
833
973
  }
834
- var import_react3, import_material3, import_recharts, TOP_N_MODELS, PERIOD_LS_KEY, MODEL_COLORS, fmtPct, KpiCard, ChartSkeleton, EmptyChart, UsageStats;
974
+ var import_react4, import_material4, import_recharts, TOP_N_MODELS, PERIOD_LS_KEY, MODEL_COLORS, fmtPct, KpiCard, ChartSkeleton, EmptyChart, UsageStats;
835
975
  var init_UsageStats = __esm({
836
976
  "src/components/UsageStats.tsx"() {
837
977
  "use strict";
838
- import_react3 = __toESM(require("react"));
839
- import_material3 = require("@mui/material");
978
+ import_react4 = __toESM(require("react"));
979
+ import_material4 = require("@mui/material");
840
980
  import_recharts = require("recharts");
841
981
  init_format();
842
982
  TOP_N_MODELS = 6;
@@ -854,12 +994,12 @@ var init_UsageStats = __esm({
854
994
  "#d0ed57"
855
995
  ];
856
996
  fmtPct = (n) => `${(n * 100).toFixed(1)}%`;
857
- KpiCard = ({ label, value, hint }) => /* @__PURE__ */ import_react3.default.createElement(import_material3.Paper, { variant: "outlined", sx: { p: 2, height: "100%" } }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "caption", color: "text.secondary" }, label), /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "h5", sx: { mt: 0.5 } }, value), hint ? /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "caption", color: "text.secondary" }, hint) : null);
858
- ChartSkeleton = ({ height = 260 }) => /* @__PURE__ */ import_react3.default.createElement(import_material3.Skeleton, { variant: "rectangular", height, sx: { borderRadius: 1 } });
997
+ KpiCard = ({ label, value, hint }) => /* @__PURE__ */ import_react4.default.createElement(import_material4.Paper, { variant: "outlined", sx: { p: 2, height: "100%" } }, /* @__PURE__ */ import_react4.default.createElement(import_material4.Typography, { variant: "caption", color: "text.secondary" }, label), /* @__PURE__ */ import_react4.default.createElement(import_material4.Typography, { variant: "h5", sx: { mt: 0.5 } }, value), hint ? /* @__PURE__ */ import_react4.default.createElement(import_material4.Typography, { variant: "caption", color: "text.secondary" }, hint) : null);
998
+ ChartSkeleton = ({ height = 260 }) => /* @__PURE__ */ import_react4.default.createElement(import_material4.Skeleton, { variant: "rectangular", height, sx: { borderRadius: 1 } });
859
999
  EmptyChart = ({
860
1000
  height = 260,
861
1001
  message = "No data for this period"
862
- }) => /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { height, display: "flex", alignItems: "center", justifyContent: "center" }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { color: "text.secondary", variant: "body2" }, message));
1002
+ }) => /* @__PURE__ */ import_react4.default.createElement(import_material4.Box, { height, display: "flex", alignItems: "center", justifyContent: "center" }, /* @__PURE__ */ import_react4.default.createElement(import_material4.Typography, { color: "text.secondary", variant: "body2" }, message));
863
1003
  UsageStats = ({
864
1004
  usage,
865
1005
  models,
@@ -868,15 +1008,15 @@ var init_UsageStats = __esm({
868
1008
  loading,
869
1009
  userInfo
870
1010
  }) => {
871
- const theme = (0, import_material3.useTheme)();
872
- const [selectedModel, setSelectedModel] = (0, import_react3.useState)("all");
873
- const [tab, setTab] = (0, import_react3.useState)("costs");
1011
+ const theme = (0, import_material4.useTheme)();
1012
+ const [selectedModel, setSelectedModel] = (0, import_react4.useState)("all");
1013
+ const [tab, setTab] = (0, import_react4.useState)("costs");
874
1014
  const gridStroke = theme.palette.divider;
875
1015
  const tickFill = theme.palette.text.secondary;
876
1016
  const tickStyle = { fontSize: 12, fill: tickFill };
877
1017
  const tickStyleSmall = { fontSize: 11, fill: tickFill };
878
1018
  const tickStyleTiny = { fontSize: 10, fill: tickFill };
879
- const selectedPreset = (0, import_react3.useMemo)(() => {
1019
+ const selectedPreset = (0, import_react4.useMemo)(() => {
880
1020
  if (dateRange.start.toDateString() === dateRange.end.toDateString()) return "today";
881
1021
  const diffMs = dateRange.end.getTime() - dateRange.start.getTime();
882
1022
  const diffHours = diffMs / (1e3 * 60 * 60);
@@ -898,7 +1038,7 @@ var init_UsageStats = __esm({
898
1038
  }
899
1039
  onDateRangeChange({ start, end });
900
1040
  };
901
- const dailyData = (0, import_react3.useMemo)(
1041
+ const dailyData = (0, import_react4.useMemo)(
902
1042
  () => (usage?.daily_usage ?? []).map((d) => ({
903
1043
  date: d.date,
904
1044
  spend: d.spend,
@@ -911,21 +1051,21 @@ var init_UsageStats = __esm({
911
1051
  })),
912
1052
  [usage]
913
1053
  );
914
- const cumulativeData = (0, import_react3.useMemo)(() => {
1054
+ const cumulativeData = (0, import_react4.useMemo)(() => {
915
1055
  let cum = 0;
916
1056
  return dailyData.map((d) => {
917
1057
  cum += d.spend;
918
1058
  return { date: d.date, cumulative: cum };
919
1059
  });
920
1060
  }, [dailyData]);
921
- const successRateData = (0, import_react3.useMemo)(
1061
+ const successRateData = (0, import_react4.useMemo)(
922
1062
  () => dailyData.filter((d) => d.apiRequests > 0).map((d) => ({
923
1063
  date: d.date,
924
1064
  successRate: parseFloat((d.successfulRequests / d.apiRequests * 100).toFixed(1))
925
1065
  })),
926
1066
  [dailyData]
927
1067
  );
928
- const { modelSpendByDate, topModels: topSpendModels } = (0, import_react3.useMemo)(() => {
1068
+ const { modelSpendByDate, topModels: topSpendModels } = (0, import_react4.useMemo)(() => {
929
1069
  const rows = usage?.daily_by_model ?? [];
930
1070
  const modelTotals = {};
931
1071
  for (const r of rows) modelTotals[r.model] = (modelTotals[r.model] ?? 0) + r.spend;
@@ -940,7 +1080,7 @@ var init_UsageStats = __esm({
940
1080
  const hasOther = sorted.some((r) => r.Other > 0);
941
1081
  return { modelSpendByDate: sorted, topModels: hasOther ? [...top, "Other"] : top };
942
1082
  }, [usage]);
943
- const modelRows = (0, import_react3.useMemo)(() => {
1083
+ const modelRows = (0, import_react4.useMemo)(() => {
944
1084
  const entries = Object.entries(usage?.usage_by_model ?? {}).map(([model, d]) => ({
945
1085
  model,
946
1086
  spend: d.total_spend,
@@ -954,11 +1094,11 @@ var init_UsageStats = __esm({
954
1094
  }));
955
1095
  return selectedModel === "all" ? entries : entries.filter((e) => e.model === selectedModel);
956
1096
  }, [usage, selectedModel]);
957
- const topModelSpendBars = (0, import_react3.useMemo)(
1097
+ const topModelSpendBars = (0, import_react4.useMemo)(
958
1098
  () => [...modelRows].sort((a, b) => b.spend - a.spend).slice(0, 10),
959
1099
  [modelRows]
960
1100
  );
961
- const keyRows = (0, import_react3.useMemo)(
1101
+ const keyRows = (0, import_react4.useMemo)(
962
1102
  () => Object.entries(usage?.usage_by_key ?? {}).map(([keyHash, d]) => ({
963
1103
  keyHash,
964
1104
  keyAlias: d.key_alias ?? keyHash.slice(0, 8),
@@ -975,7 +1115,7 @@ var init_UsageStats = __esm({
975
1115
  })),
976
1116
  [usage]
977
1117
  );
978
- const topKeySpendBars = (0, import_react3.useMemo)(
1118
+ const topKeySpendBars = (0, import_react4.useMemo)(
979
1119
  () => [...keyRows].sort((a, b) => b.spend - a.spend).slice(0, 10),
980
1120
  [keyRows]
981
1121
  );
@@ -983,34 +1123,34 @@ var init_UsageStats = __esm({
983
1123
  const overallSuccessRate = totalRequests > 0 ? (usage?.successful_requests ?? 0) / totalRequests : 0;
984
1124
  const maxBudget = userInfo?.max_budget ?? 0;
985
1125
  const totalCumSpend = cumulativeData[cumulativeData.length - 1]?.cumulative ?? 0;
986
- return /* @__PURE__ */ import_react3.default.createElement(import_material3.Paper, { sx: { p: 2 } }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { display: "flex", justifyContent: "space-between", alignItems: "center", mb: 2, flexWrap: "wrap", gap: 2 }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "h6" }, "Usage Analytics"), /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { display: "flex", gap: 2, alignItems: "center", flexWrap: "wrap" }, /* @__PURE__ */ import_react3.default.createElement(import_material3.FormControl, { size: "small", sx: { minWidth: 140 } }, /* @__PURE__ */ import_react3.default.createElement(import_material3.InputLabel, null, "Period"), /* @__PURE__ */ import_react3.default.createElement(
987
- import_material3.Select,
1126
+ return /* @__PURE__ */ import_react4.default.createElement(import_material4.Paper, { sx: { p: 2 } }, /* @__PURE__ */ import_react4.default.createElement(import_material4.Box, { display: "flex", justifyContent: "space-between", alignItems: "center", mb: 2, flexWrap: "wrap", gap: 2 }, /* @__PURE__ */ import_react4.default.createElement(import_material4.Typography, { variant: "h6" }, "Usage Analytics"), /* @__PURE__ */ import_react4.default.createElement(import_material4.Box, { display: "flex", gap: 2, alignItems: "center", flexWrap: "wrap" }, /* @__PURE__ */ import_react4.default.createElement(import_material4.FormControl, { size: "small", sx: { minWidth: 140 } }, /* @__PURE__ */ import_react4.default.createElement(import_material4.InputLabel, null, "Period"), /* @__PURE__ */ import_react4.default.createElement(
1127
+ import_material4.Select,
988
1128
  {
989
1129
  value: selectedPreset,
990
1130
  label: "Period",
991
1131
  onChange: (e) => handlePresetChange(e.target.value)
992
1132
  },
993
- /* @__PURE__ */ import_react3.default.createElement(import_material3.MenuItem, { value: "today" }, "Today"),
994
- /* @__PURE__ */ import_react3.default.createElement(import_material3.MenuItem, { value: "24h" }, "Last 24h"),
995
- /* @__PURE__ */ import_react3.default.createElement(import_material3.MenuItem, { value: "7d" }, "Last 7 days"),
996
- /* @__PURE__ */ import_react3.default.createElement(import_material3.MenuItem, { value: "30d" }, "Last 30 days")
997
- )), tab === "models" && /* @__PURE__ */ import_react3.default.createElement(import_material3.FormControl, { size: "small", sx: { minWidth: 180 } }, /* @__PURE__ */ import_react3.default.createElement(import_material3.InputLabel, null, "Model"), /* @__PURE__ */ import_react3.default.createElement(
998
- import_material3.Select,
1133
+ /* @__PURE__ */ import_react4.default.createElement(import_material4.MenuItem, { value: "today" }, "Today"),
1134
+ /* @__PURE__ */ import_react4.default.createElement(import_material4.MenuItem, { value: "24h" }, "Last 24h"),
1135
+ /* @__PURE__ */ import_react4.default.createElement(import_material4.MenuItem, { value: "7d" }, "Last 7 days"),
1136
+ /* @__PURE__ */ import_react4.default.createElement(import_material4.MenuItem, { value: "30d" }, "Last 30 days")
1137
+ )), tab === "models" && /* @__PURE__ */ import_react4.default.createElement(import_material4.FormControl, { size: "small", sx: { minWidth: 180 } }, /* @__PURE__ */ import_react4.default.createElement(import_material4.InputLabel, null, "Model"), /* @__PURE__ */ import_react4.default.createElement(
1138
+ import_material4.Select,
999
1139
  {
1000
1140
  value: selectedModel,
1001
1141
  label: "Model",
1002
1142
  onChange: (e) => setSelectedModel(e.target.value)
1003
1143
  },
1004
- /* @__PURE__ */ import_react3.default.createElement(import_material3.MenuItem, { value: "all" }, "All Models"),
1005
- models.map((m) => /* @__PURE__ */ import_react3.default.createElement(import_material3.MenuItem, { key: m.model_name, value: m.model_name }, m.model_name))
1006
- )))), /* @__PURE__ */ import_react3.default.createElement(import_material3.Grid, { container: true, spacing: 2, sx: { mb: 2 } }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Grid, { item: true, xs: 6, sm: 3 }, loading ? /* @__PURE__ */ import_react3.default.createElement(ChartSkeleton, { height: 80 }) : /* @__PURE__ */ import_react3.default.createElement(KpiCard, { label: "Total Spend", value: fmtUsd(usage?.total_spend ?? 0) })), /* @__PURE__ */ import_react3.default.createElement(import_material3.Grid, { item: true, xs: 6, sm: 3 }, loading ? /* @__PURE__ */ import_react3.default.createElement(ChartSkeleton, { height: 80 }) : /* @__PURE__ */ import_react3.default.createElement(KpiCard, { label: "Total Requests", value: fmtInt(totalRequests), hint: `${fmtInt(usage?.failed_requests ?? 0)} failed` })), /* @__PURE__ */ import_react3.default.createElement(import_material3.Grid, { item: true, xs: 6, sm: 3 }, loading ? /* @__PURE__ */ import_react3.default.createElement(ChartSkeleton, { height: 80 }) : /* @__PURE__ */ import_react3.default.createElement(KpiCard, { label: "Success Rate", value: totalRequests > 0 ? fmtPct(overallSuccessRate) : "\u2014" })), /* @__PURE__ */ import_react3.default.createElement(import_material3.Grid, { item: true, xs: 6, sm: 3 }, loading ? /* @__PURE__ */ import_react3.default.createElement(ChartSkeleton, { height: 80 }) : /* @__PURE__ */ import_react3.default.createElement(
1144
+ /* @__PURE__ */ import_react4.default.createElement(import_material4.MenuItem, { value: "all" }, "All Models"),
1145
+ models.map((m) => /* @__PURE__ */ import_react4.default.createElement(import_material4.MenuItem, { key: m.model_name, value: m.model_name }, m.model_name))
1146
+ )))), /* @__PURE__ */ import_react4.default.createElement(import_material4.Grid, { container: true, spacing: 2, sx: { mb: 2 } }, /* @__PURE__ */ import_react4.default.createElement(import_material4.Grid, { item: true, xs: 6, sm: 3 }, loading ? /* @__PURE__ */ import_react4.default.createElement(ChartSkeleton, { height: 80 }) : /* @__PURE__ */ import_react4.default.createElement(KpiCard, { label: "Total Spend", value: fmtUsd(usage?.total_spend ?? 0) })), /* @__PURE__ */ import_react4.default.createElement(import_material4.Grid, { item: true, xs: 6, sm: 3 }, loading ? /* @__PURE__ */ import_react4.default.createElement(ChartSkeleton, { height: 80 }) : /* @__PURE__ */ import_react4.default.createElement(KpiCard, { label: "Total Requests", value: fmtInt(totalRequests), hint: `${fmtInt(usage?.failed_requests ?? 0)} failed` })), /* @__PURE__ */ import_react4.default.createElement(import_material4.Grid, { item: true, xs: 6, sm: 3 }, loading ? /* @__PURE__ */ import_react4.default.createElement(ChartSkeleton, { height: 80 }) : /* @__PURE__ */ import_react4.default.createElement(KpiCard, { label: "Success Rate", value: totalRequests > 0 ? fmtPct(overallSuccessRate) : "\u2014" })), /* @__PURE__ */ import_react4.default.createElement(import_material4.Grid, { item: true, xs: 6, sm: 3 }, loading ? /* @__PURE__ */ import_react4.default.createElement(ChartSkeleton, { height: 80 }) : /* @__PURE__ */ import_react4.default.createElement(
1007
1147
  KpiCard,
1008
1148
  {
1009
1149
  label: "Total Tokens",
1010
1150
  value: fmtInt(usage?.total_tokens ?? 0),
1011
1151
  hint: `${fmtInt(usage?.prompt_tokens ?? 0)} in \xB7 ${fmtInt(usage?.completion_tokens ?? 0)} out`
1012
1152
  }
1013
- ))), /* @__PURE__ */ import_react3.default.createElement(import_material3.Tabs, { value: tab, onChange: (_, v) => setTab(v), sx: { mb: 2 } }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Tab, { value: "costs", label: "Costs" }), /* @__PURE__ */ import_react3.default.createElement(import_material3.Tab, { value: "models", label: "Model Activity" }), /* @__PURE__ */ import_react3.default.createElement(import_material3.Tab, { value: "keys", label: "Key Activity" })), tab === "costs" && /* @__PURE__ */ import_react3.default.createElement(import_material3.Grid, { container: true, spacing: 3 }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Grid, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Daily Spend by Model"), loading ? /* @__PURE__ */ import_react3.default.createElement(ChartSkeleton, null) : modelSpendByDate.length === 0 ? /* @__PURE__ */ import_react3.default.createElement(EmptyChart, null) : /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { height: 260 }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.AreaChart, { data: modelSpendByDate }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.CartesianGrid, { strokeDasharray: "3 3", stroke: gridStroke }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.XAxis, { dataKey: "date", tick: tickStyle }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.YAxis, { tick: tickStyle, tickFormatter: (v) => `$${v.toFixed(2)}` }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Tooltip, { formatter: (v) => [`$${v.toFixed(4)}`, void 0] }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Legend, null), topSpendModels.map((m) => /* @__PURE__ */ import_react3.default.createElement(
1153
+ ))), /* @__PURE__ */ import_react4.default.createElement(import_material4.Tabs, { value: tab, onChange: (_, v) => setTab(v), sx: { mb: 2 } }, /* @__PURE__ */ import_react4.default.createElement(import_material4.Tab, { value: "costs", label: "Costs" }), /* @__PURE__ */ import_react4.default.createElement(import_material4.Tab, { value: "models", label: "Model Activity" }), /* @__PURE__ */ import_react4.default.createElement(import_material4.Tab, { value: "keys", label: "Key Activity" })), tab === "costs" && /* @__PURE__ */ import_react4.default.createElement(import_material4.Grid, { container: true, spacing: 3 }, /* @__PURE__ */ import_react4.default.createElement(import_material4.Grid, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ import_react4.default.createElement(import_material4.Typography, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Daily Spend by Model"), loading ? /* @__PURE__ */ import_react4.default.createElement(ChartSkeleton, null) : modelSpendByDate.length === 0 ? /* @__PURE__ */ import_react4.default.createElement(EmptyChart, null) : /* @__PURE__ */ import_react4.default.createElement(import_material4.Box, { height: 260 }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.AreaChart, { data: modelSpendByDate }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.CartesianGrid, { strokeDasharray: "3 3", stroke: gridStroke }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.XAxis, { dataKey: "date", tick: tickStyle }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.YAxis, { tick: tickStyle, tickFormatter: (v) => `$${v.toFixed(2)}` }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.Tooltip, { formatter: (v) => [`$${v.toFixed(4)}`, void 0] }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.Legend, null), topSpendModels.map((m) => /* @__PURE__ */ import_react4.default.createElement(
1014
1154
  import_recharts.Area,
1015
1155
  {
1016
1156
  key: m,
@@ -1021,38 +1161,38 @@ var init_UsageStats = __esm({
1021
1161
  fill: modelColor(m),
1022
1162
  fillOpacity: 0.6
1023
1163
  }
1024
- )))))), /* @__PURE__ */ import_react3.default.createElement(import_material3.Grid, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Daily Token Usage"), loading ? /* @__PURE__ */ import_react3.default.createElement(ChartSkeleton, null) : dailyData.length === 0 ? /* @__PURE__ */ import_react3.default.createElement(EmptyChart, null) : /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { height: 260 }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.AreaChart, { data: dailyData }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.CartesianGrid, { strokeDasharray: "3 3", stroke: gridStroke }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.XAxis, { dataKey: "date", tick: tickStyle }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.YAxis, { tick: tickStyle, tickFormatter: fmtInt }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Tooltip, { formatter: (v) => [fmtInt(v), void 0] }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Legend, null), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Area, { type: "monotone", dataKey: "promptTokens", name: "Input (prompt)", stackId: "tok", stroke: "#8884d8", fill: "#8884d8", fillOpacity: 0.5 }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Area, { type: "monotone", dataKey: "completionTokens", name: "Output (completion)", stackId: "tok", stroke: "#82ca9d", fill: "#82ca9d", fillOpacity: 0.5 }))))), /* @__PURE__ */ import_react3.default.createElement(import_material3.Grid, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Daily Requests"), loading ? /* @__PURE__ */ import_react3.default.createElement(ChartSkeleton, null) : dailyData.length === 0 ? /* @__PURE__ */ import_react3.default.createElement(EmptyChart, null) : /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { height: 260 }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.BarChart, { data: dailyData }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.CartesianGrid, { strokeDasharray: "3 3", stroke: gridStroke }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.XAxis, { dataKey: "date", tick: tickStyle }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.YAxis, { tick: tickStyle }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Tooltip, null), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Legend, null), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Bar, { dataKey: "successfulRequests", name: "Successful", fill: "#82ca9d", stackId: "r" }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Bar, { dataKey: "failedRequests", name: "Failed", fill: "#e57373", stackId: "r" }))))), /* @__PURE__ */ import_react3.default.createElement(import_material3.Grid, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Daily Success Rate"), loading ? /* @__PURE__ */ import_react3.default.createElement(ChartSkeleton, null) : successRateData.length === 0 ? /* @__PURE__ */ import_react3.default.createElement(EmptyChart, null) : /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { height: 260 }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.LineChart, { data: successRateData }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.CartesianGrid, { strokeDasharray: "3 3", stroke: gridStroke }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.XAxis, { dataKey: "date", tick: tickStyle }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.YAxis, { domain: [0, 100], tick: tickStyle, tickFormatter: (v) => `${v}%` }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Tooltip, { formatter: (v) => [`${v}%`, "Success rate"] }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.ReferenceLine, { y: 100, stroke: "#82ca9d", strokeDasharray: "4 2" }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Line, { type: "monotone", dataKey: "successRate", name: "Success rate", stroke: "#8884d8", dot: { r: 3 } }))))), (maxBudget > 0 || cumulativeData.length > 0) && /* @__PURE__ */ import_react3.default.createElement(import_material3.Grid, { item: true, xs: 12 }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Cumulative Spend", maxBudget > 0 ? ` vs Budget (${fmtUsd(maxBudget)})` : "", maxBudget > 0 && /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { component: "span", variant: "caption", color: "text.secondary", sx: { ml: 1 } }, fmtUsd(totalCumSpend), " used \xB7 ", fmtUsd(Math.max(0, maxBudget - totalCumSpend)), " remaining")), loading ? /* @__PURE__ */ import_react3.default.createElement(ChartSkeleton, { height: 180 }) : cumulativeData.length === 0 ? /* @__PURE__ */ import_react3.default.createElement(EmptyChart, { height: 180 }) : /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { height: 180 }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.AreaChart, { data: cumulativeData }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.CartesianGrid, { strokeDasharray: "3 3", stroke: gridStroke }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.XAxis, { dataKey: "date", tick: tickStyle }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.YAxis, { tick: tickStyle, tickFormatter: (v) => `$${v.toFixed(2)}` }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Tooltip, { formatter: (v) => [`$${v.toFixed(4)}`, "Cumulative spend"] }), maxBudget > 0 && /* @__PURE__ */ import_react3.default.createElement(import_recharts.ReferenceLine, { y: maxBudget, stroke: "#e57373", strokeDasharray: "6 3", label: { value: `Budget ${fmtUsd(maxBudget)}`, position: "insideTopRight", fontSize: 11 } }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Area, { type: "monotone", dataKey: "cumulative", name: "Cumulative spend", stroke: "#ffc658", fill: "#ffc658", fillOpacity: 0.3 })))))), tab === "models" && /* @__PURE__ */ import_react3.default.createElement(import_material3.Grid, { container: true, spacing: 3 }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Grid, { item: true, xs: 12 }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Spend by Model"), loading ? /* @__PURE__ */ import_react3.default.createElement(ChartSkeleton, null) : topModelSpendBars.length === 0 ? /* @__PURE__ */ import_react3.default.createElement(EmptyChart, null) : /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { height: Math.max(200, topModelSpendBars.length * 36) }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.BarChart, { data: topModelSpendBars, layout: "vertical", margin: { left: 20 } }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.CartesianGrid, { strokeDasharray: "3 3", stroke: gridStroke }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.XAxis, { type: "number", tick: tickStyle, tickFormatter: (v) => `$${v.toFixed(2)}` }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.YAxis, { type: "category", dataKey: "model", tick: tickStyleSmall, width: 200 }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Tooltip, { formatter: (v) => [fmtUsd(v), "Spend"] }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Bar, { dataKey: "spend", name: "Spend", radius: [0, 4, 4, 0] }, topModelSpendBars.map((r) => /* @__PURE__ */ import_react3.default.createElement("rect", { key: r.model, fill: modelColor(r.model) }))))))), /* @__PURE__ */ import_react3.default.createElement(import_material3.Grid, { item: true, xs: 12 }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Tokens by Model"), loading ? /* @__PURE__ */ import_react3.default.createElement(ChartSkeleton, null) : modelRows.length === 0 ? /* @__PURE__ */ import_react3.default.createElement(EmptyChart, null) : /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { height: 260 }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.BarChart, { data: modelRows }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.CartesianGrid, { strokeDasharray: "3 3", stroke: gridStroke }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.XAxis, { dataKey: "model", tick: tickStyleTiny, interval: 0, angle: -15, textAnchor: "end", height: 60 }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.YAxis, { tick: tickStyle }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Tooltip, null), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Legend, null), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Bar, { dataKey: "promptTokens", name: "Prompt", fill: "#8884d8", stackId: "t" }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Bar, { dataKey: "completionTokens", name: "Completion", fill: "#82ca9d", stackId: "t" }))))), /* @__PURE__ */ import_react3.default.createElement(import_material3.Grid, { item: true, xs: 12 }, /* @__PURE__ */ import_react3.default.createElement(import_material3.TableContainer, { component: import_material3.Paper, variant: "outlined" }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Table, { size: "small" }, /* @__PURE__ */ import_react3.default.createElement(import_material3.TableHead, null, /* @__PURE__ */ import_react3.default.createElement(import_material3.TableRow, null, /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, null, "Model"), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, "Spend"), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, "Requests"), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, "Success"), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, "Failed"), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, "Prompt"), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, "Completion"), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { sx: { minWidth: 120 } }, "Success rate"))), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableBody, null, loading ? /* @__PURE__ */ import_react3.default.createElement(import_material3.TableRow, null, /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { colSpan: 8 }, /* @__PURE__ */ import_react3.default.createElement(import_material3.LinearProgress, null))) : modelRows.length === 0 ? /* @__PURE__ */ import_react3.default.createElement(import_material3.TableRow, null, /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { colSpan: 8, align: "center" }, "No model activity")) : [...modelRows].sort((a, b) => b.spend - a.spend || b.totalTokens - a.totalTokens).map((r) => /* @__PURE__ */ import_react3.default.createElement(import_material3.TableRow, { key: r.model }, /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, null, /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { display: "flex", alignItems: "center", gap: 0.75 }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { sx: { width: 10, height: 10, borderRadius: "50%", bgcolor: modelColor(r.model), flexShrink: 0 } }), r.model)), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, fmtUsd(r.spend)), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, fmtInt(r.apiRequests)), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, fmtInt(r.successfulRequests)), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, fmtInt(r.failedRequests)), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, fmtInt(r.promptTokens)), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, fmtInt(r.completionTokens)), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, null, r.apiRequests > 0 ? /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { display: "flex", alignItems: "center", gap: 1 }, /* @__PURE__ */ import_react3.default.createElement(
1025
- import_material3.LinearProgress,
1164
+ )))))), /* @__PURE__ */ import_react4.default.createElement(import_material4.Grid, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ import_react4.default.createElement(import_material4.Typography, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Daily Token Usage"), loading ? /* @__PURE__ */ import_react4.default.createElement(ChartSkeleton, null) : dailyData.length === 0 ? /* @__PURE__ */ import_react4.default.createElement(EmptyChart, null) : /* @__PURE__ */ import_react4.default.createElement(import_material4.Box, { height: 260 }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.AreaChart, { data: dailyData }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.CartesianGrid, { strokeDasharray: "3 3", stroke: gridStroke }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.XAxis, { dataKey: "date", tick: tickStyle }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.YAxis, { tick: tickStyle, tickFormatter: fmtInt }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.Tooltip, { formatter: (v) => [fmtInt(v), void 0] }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.Legend, null), /* @__PURE__ */ import_react4.default.createElement(import_recharts.Area, { type: "monotone", dataKey: "promptTokens", name: "Input (prompt)", stackId: "tok", stroke: "#8884d8", fill: "#8884d8", fillOpacity: 0.5 }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.Area, { type: "monotone", dataKey: "completionTokens", name: "Output (completion)", stackId: "tok", stroke: "#82ca9d", fill: "#82ca9d", fillOpacity: 0.5 }))))), /* @__PURE__ */ import_react4.default.createElement(import_material4.Grid, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ import_react4.default.createElement(import_material4.Typography, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Daily Requests"), loading ? /* @__PURE__ */ import_react4.default.createElement(ChartSkeleton, null) : dailyData.length === 0 ? /* @__PURE__ */ import_react4.default.createElement(EmptyChart, null) : /* @__PURE__ */ import_react4.default.createElement(import_material4.Box, { height: 260 }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.BarChart, { data: dailyData }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.CartesianGrid, { strokeDasharray: "3 3", stroke: gridStroke }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.XAxis, { dataKey: "date", tick: tickStyle }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.YAxis, { tick: tickStyle }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.Tooltip, null), /* @__PURE__ */ import_react4.default.createElement(import_recharts.Legend, null), /* @__PURE__ */ import_react4.default.createElement(import_recharts.Bar, { dataKey: "successfulRequests", name: "Successful", fill: "#82ca9d", stackId: "r" }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.Bar, { dataKey: "failedRequests", name: "Failed", fill: "#e57373", stackId: "r" }))))), /* @__PURE__ */ import_react4.default.createElement(import_material4.Grid, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ import_react4.default.createElement(import_material4.Typography, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Daily Success Rate"), loading ? /* @__PURE__ */ import_react4.default.createElement(ChartSkeleton, null) : successRateData.length === 0 ? /* @__PURE__ */ import_react4.default.createElement(EmptyChart, null) : /* @__PURE__ */ import_react4.default.createElement(import_material4.Box, { height: 260 }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.LineChart, { data: successRateData }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.CartesianGrid, { strokeDasharray: "3 3", stroke: gridStroke }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.XAxis, { dataKey: "date", tick: tickStyle }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.YAxis, { domain: [0, 100], tick: tickStyle, tickFormatter: (v) => `${v}%` }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.Tooltip, { formatter: (v) => [`${v}%`, "Success rate"] }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.ReferenceLine, { y: 100, stroke: "#82ca9d", strokeDasharray: "4 2" }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.Line, { type: "monotone", dataKey: "successRate", name: "Success rate", stroke: "#8884d8", dot: { r: 3 } }))))), (maxBudget > 0 || cumulativeData.length > 0) && /* @__PURE__ */ import_react4.default.createElement(import_material4.Grid, { item: true, xs: 12 }, /* @__PURE__ */ import_react4.default.createElement(import_material4.Typography, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Cumulative Spend", maxBudget > 0 ? ` vs Budget (${fmtUsd(maxBudget)})` : "", maxBudget > 0 && /* @__PURE__ */ import_react4.default.createElement(import_material4.Typography, { component: "span", variant: "caption", color: "text.secondary", sx: { ml: 1 } }, fmtUsd(totalCumSpend), " used \xB7 ", fmtUsd(Math.max(0, maxBudget - totalCumSpend)), " remaining")), loading ? /* @__PURE__ */ import_react4.default.createElement(ChartSkeleton, { height: 180 }) : cumulativeData.length === 0 ? /* @__PURE__ */ import_react4.default.createElement(EmptyChart, { height: 180 }) : /* @__PURE__ */ import_react4.default.createElement(import_material4.Box, { height: 180 }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.AreaChart, { data: cumulativeData }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.CartesianGrid, { strokeDasharray: "3 3", stroke: gridStroke }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.XAxis, { dataKey: "date", tick: tickStyle }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.YAxis, { tick: tickStyle, tickFormatter: (v) => `$${v.toFixed(2)}` }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.Tooltip, { formatter: (v) => [`$${v.toFixed(4)}`, "Cumulative spend"] }), maxBudget > 0 && /* @__PURE__ */ import_react4.default.createElement(import_recharts.ReferenceLine, { y: maxBudget, stroke: "#e57373", strokeDasharray: "6 3", label: { value: `Budget ${fmtUsd(maxBudget)}`, position: "insideTopRight", fontSize: 11 } }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.Area, { type: "monotone", dataKey: "cumulative", name: "Cumulative spend", stroke: "#ffc658", fill: "#ffc658", fillOpacity: 0.3 })))))), tab === "models" && /* @__PURE__ */ import_react4.default.createElement(import_material4.Grid, { container: true, spacing: 3 }, /* @__PURE__ */ import_react4.default.createElement(import_material4.Grid, { item: true, xs: 12 }, /* @__PURE__ */ import_react4.default.createElement(import_material4.Typography, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Spend by Model"), loading ? /* @__PURE__ */ import_react4.default.createElement(ChartSkeleton, null) : topModelSpendBars.length === 0 ? /* @__PURE__ */ import_react4.default.createElement(EmptyChart, null) : /* @__PURE__ */ import_react4.default.createElement(import_material4.Box, { height: Math.max(200, topModelSpendBars.length * 36) }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.BarChart, { data: topModelSpendBars, layout: "vertical", margin: { left: 20 } }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.CartesianGrid, { strokeDasharray: "3 3", stroke: gridStroke }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.XAxis, { type: "number", tick: tickStyle, tickFormatter: (v) => `$${v.toFixed(2)}` }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.YAxis, { type: "category", dataKey: "model", tick: tickStyleSmall, width: 200 }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.Tooltip, { formatter: (v) => [fmtUsd(v), "Spend"] }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.Bar, { dataKey: "spend", name: "Spend", radius: [0, 4, 4, 0] }, topModelSpendBars.map((r) => /* @__PURE__ */ import_react4.default.createElement("rect", { key: r.model, fill: modelColor(r.model) }))))))), /* @__PURE__ */ import_react4.default.createElement(import_material4.Grid, { item: true, xs: 12 }, /* @__PURE__ */ import_react4.default.createElement(import_material4.Typography, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Tokens by Model"), loading ? /* @__PURE__ */ import_react4.default.createElement(ChartSkeleton, null) : modelRows.length === 0 ? /* @__PURE__ */ import_react4.default.createElement(EmptyChart, null) : /* @__PURE__ */ import_react4.default.createElement(import_material4.Box, { height: 260 }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.BarChart, { data: modelRows }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.CartesianGrid, { strokeDasharray: "3 3", stroke: gridStroke }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.XAxis, { dataKey: "model", tick: tickStyleTiny, interval: 0, angle: -15, textAnchor: "end", height: 60 }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.YAxis, { tick: tickStyle }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.Tooltip, null), /* @__PURE__ */ import_react4.default.createElement(import_recharts.Legend, null), /* @__PURE__ */ import_react4.default.createElement(import_recharts.Bar, { dataKey: "promptTokens", name: "Prompt", fill: "#8884d8", stackId: "t" }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.Bar, { dataKey: "completionTokens", name: "Completion", fill: "#82ca9d", stackId: "t" }))))), /* @__PURE__ */ import_react4.default.createElement(import_material4.Grid, { item: true, xs: 12 }, /* @__PURE__ */ import_react4.default.createElement(import_material4.TableContainer, { component: import_material4.Paper, variant: "outlined" }, /* @__PURE__ */ import_react4.default.createElement(import_material4.Table, { size: "small" }, /* @__PURE__ */ import_react4.default.createElement(import_material4.TableHead, null, /* @__PURE__ */ import_react4.default.createElement(import_material4.TableRow, null, /* @__PURE__ */ import_react4.default.createElement(import_material4.TableCell, null, "Model"), /* @__PURE__ */ import_react4.default.createElement(import_material4.TableCell, { align: "right" }, "Spend"), /* @__PURE__ */ import_react4.default.createElement(import_material4.TableCell, { align: "right" }, "Requests"), /* @__PURE__ */ import_react4.default.createElement(import_material4.TableCell, { align: "right" }, "Success"), /* @__PURE__ */ import_react4.default.createElement(import_material4.TableCell, { align: "right" }, "Failed"), /* @__PURE__ */ import_react4.default.createElement(import_material4.TableCell, { align: "right" }, "Prompt"), /* @__PURE__ */ import_react4.default.createElement(import_material4.TableCell, { align: "right" }, "Completion"), /* @__PURE__ */ import_react4.default.createElement(import_material4.TableCell, { sx: { minWidth: 120 } }, "Success rate"))), /* @__PURE__ */ import_react4.default.createElement(import_material4.TableBody, null, loading ? /* @__PURE__ */ import_react4.default.createElement(import_material4.TableRow, null, /* @__PURE__ */ import_react4.default.createElement(import_material4.TableCell, { colSpan: 8 }, /* @__PURE__ */ import_react4.default.createElement(import_material4.LinearProgress, null))) : modelRows.length === 0 ? /* @__PURE__ */ import_react4.default.createElement(import_material4.TableRow, null, /* @__PURE__ */ import_react4.default.createElement(import_material4.TableCell, { colSpan: 8, align: "center" }, "No model activity")) : [...modelRows].sort((a, b) => b.spend - a.spend || b.totalTokens - a.totalTokens).map((r) => /* @__PURE__ */ import_react4.default.createElement(import_material4.TableRow, { key: r.model }, /* @__PURE__ */ import_react4.default.createElement(import_material4.TableCell, null, /* @__PURE__ */ import_react4.default.createElement(import_material4.Box, { display: "flex", alignItems: "center", gap: 0.75 }, /* @__PURE__ */ import_react4.default.createElement(import_material4.Box, { sx: { width: 10, height: 10, borderRadius: "50%", bgcolor: modelColor(r.model), flexShrink: 0 } }), r.model)), /* @__PURE__ */ import_react4.default.createElement(import_material4.TableCell, { align: "right" }, fmtUsd(r.spend)), /* @__PURE__ */ import_react4.default.createElement(import_material4.TableCell, { align: "right" }, fmtInt(r.apiRequests)), /* @__PURE__ */ import_react4.default.createElement(import_material4.TableCell, { align: "right" }, fmtInt(r.successfulRequests)), /* @__PURE__ */ import_react4.default.createElement(import_material4.TableCell, { align: "right" }, fmtInt(r.failedRequests)), /* @__PURE__ */ import_react4.default.createElement(import_material4.TableCell, { align: "right" }, fmtInt(r.promptTokens)), /* @__PURE__ */ import_react4.default.createElement(import_material4.TableCell, { align: "right" }, fmtInt(r.completionTokens)), /* @__PURE__ */ import_react4.default.createElement(import_material4.TableCell, null, r.apiRequests > 0 ? /* @__PURE__ */ import_react4.default.createElement(import_material4.Box, { display: "flex", alignItems: "center", gap: 1 }, /* @__PURE__ */ import_react4.default.createElement(
1165
+ import_material4.LinearProgress,
1026
1166
  {
1027
1167
  variant: "determinate",
1028
1168
  value: r.successRate * 100,
1029
1169
  sx: { flex: 1, height: 6, borderRadius: 3 }
1030
1170
  }
1031
- ), /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "caption" }, fmtPct(r.successRate))) : "\u2014")))))))), tab === "keys" && /* @__PURE__ */ import_react3.default.createElement(import_material3.Grid, { container: true, spacing: 3 }, (loading || topKeySpendBars.length > 0) && /* @__PURE__ */ import_react3.default.createElement(import_material3.Grid, { item: true, xs: 12 }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Spend by Key"), loading ? /* @__PURE__ */ import_react3.default.createElement(ChartSkeleton, null) : /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { height: Math.max(160, topKeySpendBars.length * 36) }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.BarChart, { data: topKeySpendBars, layout: "vertical", margin: { left: 20 } }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.CartesianGrid, { strokeDasharray: "3 3", stroke: gridStroke }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.XAxis, { type: "number", tick: tickStyle, tickFormatter: (v) => `$${v.toFixed(2)}` }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.YAxis, { type: "category", dataKey: "keyAlias", tick: tickStyleSmall, width: 160 }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Tooltip, { formatter: (v) => [fmtUsd(v), "Spend"] }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Bar, { dataKey: "spend", name: "Spend", fill: "#8884d8", radius: [0, 4, 4, 0] }))))), /* @__PURE__ */ import_react3.default.createElement(import_material3.Grid, { item: true, xs: 12 }, /* @__PURE__ */ import_react3.default.createElement(import_material3.TableContainer, { component: import_material3.Paper, variant: "outlined" }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Table, { size: "small" }, /* @__PURE__ */ import_react3.default.createElement(import_material3.TableHead, null, /* @__PURE__ */ import_react3.default.createElement(import_material3.TableRow, null, /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, null, "Key"), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, null, "Models"), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, "Spend"), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, "Requests"), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, "Tokens"), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, "Failed"), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { sx: { minWidth: 120 } }, "Success rate"))), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableBody, null, loading ? /* @__PURE__ */ import_react3.default.createElement(import_material3.TableRow, null, /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { colSpan: 7 }, /* @__PURE__ */ import_react3.default.createElement(import_material3.LinearProgress, null))) : keyRows.length === 0 ? /* @__PURE__ */ import_react3.default.createElement(import_material3.TableRow, null, /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { colSpan: 7, align: "center" }, "No key activity")) : [...keyRows].sort((a, b) => b.spend - a.spend || b.apiRequests - a.apiRequests).map((r) => /* @__PURE__ */ import_react3.default.createElement(import_material3.TableRow, { key: r.keyHash }, /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, null, /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "body2" }, r.keyAlias), r.teamId ? /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "caption", color: "text.secondary" }, "team: ", r.teamId) : null), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, null, /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { display: "flex", gap: 0.5, flexWrap: "wrap" }, r.models.map((m) => /* @__PURE__ */ import_react3.default.createElement(import_material3.Chip, { key: m, label: m, size: "small", variant: "outlined" })))), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, fmtUsd(r.spend)), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, fmtInt(r.apiRequests)), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, fmtInt(r.totalTokens)), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, fmtInt(r.failedRequests)), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, null, r.apiRequests > 0 ? /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { display: "flex", alignItems: "center", gap: 1 }, /* @__PURE__ */ import_react3.default.createElement(
1032
- import_material3.LinearProgress,
1171
+ ), /* @__PURE__ */ import_react4.default.createElement(import_material4.Typography, { variant: "caption" }, fmtPct(r.successRate))) : "\u2014")))))))), tab === "keys" && /* @__PURE__ */ import_react4.default.createElement(import_material4.Grid, { container: true, spacing: 3 }, (loading || topKeySpendBars.length > 0) && /* @__PURE__ */ import_react4.default.createElement(import_material4.Grid, { item: true, xs: 12 }, /* @__PURE__ */ import_react4.default.createElement(import_material4.Typography, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Spend by Key"), loading ? /* @__PURE__ */ import_react4.default.createElement(ChartSkeleton, null) : /* @__PURE__ */ import_react4.default.createElement(import_material4.Box, { height: Math.max(160, topKeySpendBars.length * 36) }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.BarChart, { data: topKeySpendBars, layout: "vertical", margin: { left: 20 } }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.CartesianGrid, { strokeDasharray: "3 3", stroke: gridStroke }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.XAxis, { type: "number", tick: tickStyle, tickFormatter: (v) => `$${v.toFixed(2)}` }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.YAxis, { type: "category", dataKey: "keyAlias", tick: tickStyleSmall, width: 160 }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.Tooltip, { formatter: (v) => [fmtUsd(v), "Spend"] }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.Bar, { dataKey: "spend", name: "Spend", fill: "#8884d8", radius: [0, 4, 4, 0] }))))), /* @__PURE__ */ import_react4.default.createElement(import_material4.Grid, { item: true, xs: 12 }, /* @__PURE__ */ import_react4.default.createElement(import_material4.TableContainer, { component: import_material4.Paper, variant: "outlined" }, /* @__PURE__ */ import_react4.default.createElement(import_material4.Table, { size: "small" }, /* @__PURE__ */ import_react4.default.createElement(import_material4.TableHead, null, /* @__PURE__ */ import_react4.default.createElement(import_material4.TableRow, null, /* @__PURE__ */ import_react4.default.createElement(import_material4.TableCell, null, "Key"), /* @__PURE__ */ import_react4.default.createElement(import_material4.TableCell, null, "Models"), /* @__PURE__ */ import_react4.default.createElement(import_material4.TableCell, { align: "right" }, "Spend"), /* @__PURE__ */ import_react4.default.createElement(import_material4.TableCell, { align: "right" }, "Requests"), /* @__PURE__ */ import_react4.default.createElement(import_material4.TableCell, { align: "right" }, "Tokens"), /* @__PURE__ */ import_react4.default.createElement(import_material4.TableCell, { align: "right" }, "Failed"), /* @__PURE__ */ import_react4.default.createElement(import_material4.TableCell, { sx: { minWidth: 120 } }, "Success rate"))), /* @__PURE__ */ import_react4.default.createElement(import_material4.TableBody, null, loading ? /* @__PURE__ */ import_react4.default.createElement(import_material4.TableRow, null, /* @__PURE__ */ import_react4.default.createElement(import_material4.TableCell, { colSpan: 7 }, /* @__PURE__ */ import_react4.default.createElement(import_material4.LinearProgress, null))) : keyRows.length === 0 ? /* @__PURE__ */ import_react4.default.createElement(import_material4.TableRow, null, /* @__PURE__ */ import_react4.default.createElement(import_material4.TableCell, { colSpan: 7, align: "center" }, "No key activity")) : [...keyRows].sort((a, b) => b.spend - a.spend || b.apiRequests - a.apiRequests).map((r) => /* @__PURE__ */ import_react4.default.createElement(import_material4.TableRow, { key: r.keyHash }, /* @__PURE__ */ import_react4.default.createElement(import_material4.TableCell, null, /* @__PURE__ */ import_react4.default.createElement(import_material4.Typography, { variant: "body2" }, r.keyAlias), r.teamId ? /* @__PURE__ */ import_react4.default.createElement(import_material4.Typography, { variant: "caption", color: "text.secondary" }, "team: ", r.teamId) : null), /* @__PURE__ */ import_react4.default.createElement(import_material4.TableCell, null, /* @__PURE__ */ import_react4.default.createElement(import_material4.Box, { display: "flex", gap: 0.5, flexWrap: "wrap" }, r.models.map((m) => /* @__PURE__ */ import_react4.default.createElement(import_material4.Chip, { key: m, label: m, size: "small", variant: "outlined" })))), /* @__PURE__ */ import_react4.default.createElement(import_material4.TableCell, { align: "right" }, fmtUsd(r.spend)), /* @__PURE__ */ import_react4.default.createElement(import_material4.TableCell, { align: "right" }, fmtInt(r.apiRequests)), /* @__PURE__ */ import_react4.default.createElement(import_material4.TableCell, { align: "right" }, fmtInt(r.totalTokens)), /* @__PURE__ */ import_react4.default.createElement(import_material4.TableCell, { align: "right" }, fmtInt(r.failedRequests)), /* @__PURE__ */ import_react4.default.createElement(import_material4.TableCell, null, r.apiRequests > 0 ? /* @__PURE__ */ import_react4.default.createElement(import_material4.Box, { display: "flex", alignItems: "center", gap: 1 }, /* @__PURE__ */ import_react4.default.createElement(
1172
+ import_material4.LinearProgress,
1033
1173
  {
1034
1174
  variant: "determinate",
1035
1175
  value: r.successRate * 100,
1036
1176
  sx: { flex: 1, height: 6, borderRadius: 3 }
1037
1177
  }
1038
- ), /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "caption" }, fmtPct(r.successRate))) : "\u2014")))))))));
1178
+ ), /* @__PURE__ */ import_react4.default.createElement(import_material4.Typography, { variant: "caption" }, fmtPct(r.successRate))) : "\u2014")))))))));
1039
1179
  };
1040
1180
  }
1041
1181
  });
1042
1182
 
1043
1183
  // src/components/TeamUsage.tsx
1044
- var import_react4, import_material4, import_icons_material2, import_recharts2, Stat, TeamCard, TeamUsage;
1184
+ var import_react5, import_material5, import_icons_material4, import_recharts2, Stat, TeamCard, TeamUsage;
1045
1185
  var init_TeamUsage = __esm({
1046
1186
  "src/components/TeamUsage.tsx"() {
1047
1187
  "use strict";
1048
- import_react4 = __toESM(require("react"));
1049
- import_material4 = require("@mui/material");
1050
- import_icons_material2 = require("@mui/icons-material");
1188
+ import_react5 = __toESM(require("react"));
1189
+ import_material5 = require("@mui/material");
1190
+ import_icons_material4 = require("@mui/icons-material");
1051
1191
  import_recharts2 = require("recharts");
1052
- Stat = ({ label, value }) => /* @__PURE__ */ import_react4.default.createElement(import_material4.Box, null, /* @__PURE__ */ import_react4.default.createElement(import_material4.Typography, { variant: "caption", color: "text.secondary", display: "block" }, label), /* @__PURE__ */ import_react4.default.createElement(import_material4.Typography, { variant: "body2", fontWeight: 600, sx: { fontFamily: "monospace" } }, value));
1192
+ Stat = ({ label, value }) => /* @__PURE__ */ import_react5.default.createElement(import_material5.Box, null, /* @__PURE__ */ import_react5.default.createElement(import_material5.Typography, { variant: "caption", color: "text.secondary", display: "block" }, label), /* @__PURE__ */ import_react5.default.createElement(import_material5.Typography, { variant: "body2", fontWeight: 600, sx: { fontFamily: "monospace" } }, value));
1053
1193
  TeamCard = ({ team, usage, usageLoading }) => {
1054
- const [expanded, setExpanded] = (0, import_react4.useState)(false);
1055
- const theme = (0, import_material4.useTheme)();
1194
+ const [expanded, setExpanded] = (0, import_react5.useState)(false);
1195
+ const theme = (0, import_material5.useTheme)();
1056
1196
  const gridStroke = theme.palette.divider;
1057
1197
  const tickFill = theme.palette.text.secondary;
1058
1198
  const budget = team.max_budget ?? 0;
@@ -1061,66 +1201,66 @@ var init_TeamUsage = __esm({
1061
1201
  const isOver = budget > 0 && spend >= budget;
1062
1202
  const isNear = budget > 0 && spend >= budget * 0.8 && !isOver;
1063
1203
  const dailyData = usage?.daily_usage?.map((d) => ({ date: d.date, spend: d.spend })) ?? [];
1064
- return /* @__PURE__ */ import_react4.default.createElement(import_material4.Paper, { variant: "outlined", sx: { p: 2, mb: 2 } }, /* @__PURE__ */ import_react4.default.createElement(import_material4.Box, { display: "flex", alignItems: "center", gap: 1 }, /* @__PURE__ */ import_react4.default.createElement(import_icons_material2.Group, { color: "action" }), /* @__PURE__ */ import_react4.default.createElement(import_material4.Box, { flexGrow: 1 }, /* @__PURE__ */ import_react4.default.createElement(import_material4.Typography, { variant: "subtitle1", fontWeight: 600 }, team.team_alias || "Untitled team"), /* @__PURE__ */ import_react4.default.createElement(import_material4.Typography, { variant: "caption", color: "text.secondary", sx: { fontFamily: "monospace" } }, team.team_id)), /* @__PURE__ */ import_react4.default.createElement(import_material4.IconButton, { size: "small", onClick: () => setExpanded((e) => !e), "aria-label": "toggle details" }, expanded ? /* @__PURE__ */ import_react4.default.createElement(import_icons_material2.ExpandLess, null) : /* @__PURE__ */ import_react4.default.createElement(import_icons_material2.ExpandMore, null))), /* @__PURE__ */ import_react4.default.createElement(import_material4.Stack, { direction: "row", spacing: 3, mt: 1.5, flexWrap: "wrap", useFlexGap: true, gap: 1.5 }, /* @__PURE__ */ import_react4.default.createElement(
1204
+ return /* @__PURE__ */ import_react5.default.createElement(import_material5.Paper, { variant: "outlined", sx: { p: 2, mb: 2 } }, /* @__PURE__ */ import_react5.default.createElement(import_material5.Box, { display: "flex", alignItems: "center", gap: 1 }, /* @__PURE__ */ import_react5.default.createElement(import_icons_material4.Group, { color: "action" }), /* @__PURE__ */ import_react5.default.createElement(import_material5.Box, { flexGrow: 1 }, /* @__PURE__ */ import_react5.default.createElement(import_material5.Typography, { variant: "subtitle1", fontWeight: 600 }, team.team_alias || "Untitled team"), /* @__PURE__ */ import_react5.default.createElement(import_material5.Typography, { variant: "caption", color: "text.secondary", sx: { fontFamily: "monospace" } }, team.team_id)), /* @__PURE__ */ import_react5.default.createElement(import_material5.IconButton, { size: "small", onClick: () => setExpanded((e) => !e), "aria-label": "toggle details" }, expanded ? /* @__PURE__ */ import_react5.default.createElement(import_icons_material4.ExpandLess, null) : /* @__PURE__ */ import_react5.default.createElement(import_icons_material4.ExpandMore, null))), /* @__PURE__ */ import_react5.default.createElement(import_material5.Stack, { direction: "row", spacing: 3, mt: 1.5, flexWrap: "wrap", useFlexGap: true, gap: 1.5 }, /* @__PURE__ */ import_react5.default.createElement(
1065
1205
  Stat,
1066
1206
  {
1067
1207
  label: "Members",
1068
1208
  value: team.members_with_roles?.length ? String(team.members_with_roles.length) : "\u2014"
1069
1209
  }
1070
- ), /* @__PURE__ */ import_react4.default.createElement(
1210
+ ), /* @__PURE__ */ import_react5.default.createElement(
1071
1211
  Stat,
1072
1212
  {
1073
1213
  label: "Models",
1074
1214
  value: team.models?.length ? String(team.models.length) : "All"
1075
1215
  }
1076
- ), /* @__PURE__ */ import_react4.default.createElement(
1216
+ ), /* @__PURE__ */ import_react5.default.createElement(
1077
1217
  Stat,
1078
1218
  {
1079
1219
  label: "Budget",
1080
1220
  value: budget > 0 ? `$${budget.toFixed(2)}` : "Unlimited"
1081
1221
  }
1082
- ), /* @__PURE__ */ import_react4.default.createElement(
1222
+ ), /* @__PURE__ */ import_react5.default.createElement(
1083
1223
  Stat,
1084
1224
  {
1085
1225
  label: "Spend",
1086
1226
  value: `$${spend.toFixed(2)}`
1087
1227
  }
1088
- ), /* @__PURE__ */ import_react4.default.createElement(
1228
+ ), /* @__PURE__ */ import_react5.default.createElement(
1089
1229
  Stat,
1090
1230
  {
1091
1231
  label: "TPM",
1092
1232
  value: team.tpm_limit != null && team.tpm_limit > 0 ? String(team.tpm_limit) : "\u2014"
1093
1233
  }
1094
- ), /* @__PURE__ */ import_react4.default.createElement(
1234
+ ), /* @__PURE__ */ import_react5.default.createElement(
1095
1235
  Stat,
1096
1236
  {
1097
1237
  label: "RPM",
1098
1238
  value: team.rpm_limit != null && team.rpm_limit > 0 ? String(team.rpm_limit) : "\u2014"
1099
1239
  }
1100
- )), budget > 0 && /* @__PURE__ */ import_react4.default.createElement(import_material4.Box, { mt: 1.5 }, /* @__PURE__ */ import_react4.default.createElement(import_material4.Box, { display: "flex", justifyContent: "space-between", mb: 0.5 }, /* @__PURE__ */ import_react4.default.createElement(import_material4.Typography, { variant: "body2" }, "$", spend.toFixed(2), " / $", budget.toFixed(2)), isOver && /* @__PURE__ */ import_react4.default.createElement(import_material4.Chip, { label: "Over Budget", size: "small", color: "error" }), isNear && /* @__PURE__ */ import_react4.default.createElement(import_material4.Chip, { label: "Near Limit", size: "small", color: "warning" })), /* @__PURE__ */ import_react4.default.createElement(
1101
- import_material4.LinearProgress,
1240
+ )), budget > 0 && /* @__PURE__ */ import_react5.default.createElement(import_material5.Box, { mt: 1.5 }, /* @__PURE__ */ import_react5.default.createElement(import_material5.Box, { display: "flex", justifyContent: "space-between", mb: 0.5 }, /* @__PURE__ */ import_react5.default.createElement(import_material5.Typography, { variant: "body2" }, "$", spend.toFixed(2), " / $", budget.toFixed(2)), isOver && /* @__PURE__ */ import_react5.default.createElement(import_material5.Chip, { label: "Over Budget", size: "small", color: "error" }), isNear && /* @__PURE__ */ import_react5.default.createElement(import_material5.Chip, { label: "Near Limit", size: "small", color: "warning" })), /* @__PURE__ */ import_react5.default.createElement(
1241
+ import_material5.LinearProgress,
1102
1242
  {
1103
1243
  variant: "determinate",
1104
1244
  value: budgetPct,
1105
1245
  color: isOver ? "error" : isNear ? "warning" : "primary",
1106
1246
  sx: { height: 6, borderRadius: 1 }
1107
1247
  }
1108
- )), /* @__PURE__ */ import_react4.default.createElement(import_material4.Collapse, { in: expanded }, /* @__PURE__ */ import_react4.default.createElement(import_material4.Box, { mt: 2 }, /* @__PURE__ */ import_react4.default.createElement(import_material4.Box, { display: "flex", alignItems: "center", gap: 1, mb: 1 }, /* @__PURE__ */ import_react4.default.createElement(import_icons_material2.Speed, { fontSize: "small", color: "action" }), /* @__PURE__ */ import_react4.default.createElement(import_material4.Typography, { variant: "subtitle2", color: "text.secondary" }, "Rate limits")), /* @__PURE__ */ import_react4.default.createElement(import_material4.Stack, { direction: "row", spacing: 3, flexWrap: "wrap", useFlexGap: true, gap: 1.5, mb: 2 }, /* @__PURE__ */ import_react4.default.createElement(Stat, { label: "TPM limit", value: team.tpm_limit != null && team.tpm_limit > 0 ? String(team.tpm_limit) : "Unlimited" }), /* @__PURE__ */ import_react4.default.createElement(Stat, { label: "RPM limit", value: team.rpm_limit != null && team.rpm_limit > 0 ? String(team.rpm_limit) : "Unlimited" }), /* @__PURE__ */ import_react4.default.createElement(Stat, { label: "Max budget", value: budget > 0 ? `$${budget.toFixed(2)}` : "Unlimited" })), /* @__PURE__ */ import_react4.default.createElement(import_material4.Divider, { sx: { mb: 2 } }), /* @__PURE__ */ import_react4.default.createElement(import_material4.Typography, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Models"), team.models?.length ? /* @__PURE__ */ import_react4.default.createElement(import_material4.Box, { display: "flex", gap: 0.5, flexWrap: "wrap", mb: 2 }, team.models.map((m) => /* @__PURE__ */ import_react4.default.createElement(import_material4.Chip, { key: m, label: m, size: "small", variant: "outlined" }))) : /* @__PURE__ */ import_react4.default.createElement(import_material4.Typography, { variant: "body2", color: "text.secondary", mb: 2 }, "All models allowed"), /* @__PURE__ */ import_react4.default.createElement(import_material4.Divider, { sx: { mb: 2 } }), usageLoading ? /* @__PURE__ */ import_react4.default.createElement(import_material4.Box, { display: "flex", justifyContent: "center", p: 2 }, /* @__PURE__ */ import_react4.default.createElement(import_material4.CircularProgress, { size: 24 })) : dailyData.length > 0 ? /* @__PURE__ */ import_react4.default.createElement(import_react4.default.Fragment, null, /* @__PURE__ */ import_react4.default.createElement(import_material4.Typography, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Daily Spend"), /* @__PURE__ */ import_react4.default.createElement(import_material4.Box, { height: 160, mb: 2 }, /* @__PURE__ */ import_react4.default.createElement(import_recharts2.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react4.default.createElement(import_recharts2.AreaChart, { data: dailyData }, /* @__PURE__ */ import_react4.default.createElement(import_recharts2.CartesianGrid, { strokeDasharray: "3 3", stroke: gridStroke }), /* @__PURE__ */ import_react4.default.createElement(import_recharts2.XAxis, { dataKey: "date", tick: { fontSize: 11, fill: tickFill } }), /* @__PURE__ */ import_react4.default.createElement(import_recharts2.YAxis, { tick: { fontSize: 11, fill: tickFill }, tickFormatter: (v) => `$${v.toFixed(2)}` }), /* @__PURE__ */ import_react4.default.createElement(import_recharts2.Tooltip, { formatter: (v) => [`$${v.toFixed(4)}`, "Spend"] }), /* @__PURE__ */ import_react4.default.createElement(import_recharts2.Area, { type: "monotone", dataKey: "spend", stroke: "#8884d8", fill: "#8884d8", fillOpacity: 0.3 })))), /* @__PURE__ */ import_react4.default.createElement(import_material4.Divider, { sx: { mb: 2 } })) : null, /* @__PURE__ */ import_react4.default.createElement(import_material4.Box, { display: "flex", alignItems: "center", gap: 1, mb: 1 }, /* @__PURE__ */ import_react4.default.createElement(import_icons_material2.Memory, { fontSize: "small", color: "action" }), /* @__PURE__ */ import_react4.default.createElement(import_material4.Typography, { variant: "subtitle2", color: "text.secondary" }, "Members")), team.members_with_roles?.length ? /* @__PURE__ */ import_react4.default.createElement(import_material4.TableContainer, null, /* @__PURE__ */ import_react4.default.createElement(import_material4.Table, { size: "small" }, /* @__PURE__ */ import_react4.default.createElement(import_material4.TableHead, null, /* @__PURE__ */ import_react4.default.createElement(import_material4.TableRow, null, /* @__PURE__ */ import_react4.default.createElement(import_material4.TableCell, null, "User"), /* @__PURE__ */ import_react4.default.createElement(import_material4.TableCell, null, "Role"))), /* @__PURE__ */ import_react4.default.createElement(import_material4.TableBody, null, team.members_with_roles.map((m) => /* @__PURE__ */ import_react4.default.createElement(import_material4.TableRow, { key: m.user_id }, /* @__PURE__ */ import_react4.default.createElement(import_material4.TableCell, null, m.user_email ? /* @__PURE__ */ import_react4.default.createElement(import_react4.default.Fragment, null, /* @__PURE__ */ import_react4.default.createElement(import_material4.Typography, { variant: "body2" }, m.user_email), /* @__PURE__ */ import_react4.default.createElement(
1109
- import_material4.Typography,
1248
+ )), /* @__PURE__ */ import_react5.default.createElement(import_material5.Collapse, { in: expanded }, /* @__PURE__ */ import_react5.default.createElement(import_material5.Box, { mt: 2 }, /* @__PURE__ */ import_react5.default.createElement(import_material5.Box, { display: "flex", alignItems: "center", gap: 1, mb: 1 }, /* @__PURE__ */ import_react5.default.createElement(import_icons_material4.Speed, { fontSize: "small", color: "action" }), /* @__PURE__ */ import_react5.default.createElement(import_material5.Typography, { variant: "subtitle2", color: "text.secondary" }, "Rate limits")), /* @__PURE__ */ import_react5.default.createElement(import_material5.Stack, { direction: "row", spacing: 3, flexWrap: "wrap", useFlexGap: true, gap: 1.5, mb: 2 }, /* @__PURE__ */ import_react5.default.createElement(Stat, { label: "TPM limit", value: team.tpm_limit != null && team.tpm_limit > 0 ? String(team.tpm_limit) : "Unlimited" }), /* @__PURE__ */ import_react5.default.createElement(Stat, { label: "RPM limit", value: team.rpm_limit != null && team.rpm_limit > 0 ? String(team.rpm_limit) : "Unlimited" }), /* @__PURE__ */ import_react5.default.createElement(Stat, { label: "Max budget", value: budget > 0 ? `$${budget.toFixed(2)}` : "Unlimited" })), /* @__PURE__ */ import_react5.default.createElement(import_material5.Divider, { sx: { mb: 2 } }), /* @__PURE__ */ import_react5.default.createElement(import_material5.Typography, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Models"), team.models?.length ? /* @__PURE__ */ import_react5.default.createElement(import_material5.Box, { display: "flex", gap: 0.5, flexWrap: "wrap", mb: 2 }, team.models.map((m) => /* @__PURE__ */ import_react5.default.createElement(import_material5.Chip, { key: m, label: m, size: "small", variant: "outlined" }))) : /* @__PURE__ */ import_react5.default.createElement(import_material5.Typography, { variant: "body2", color: "text.secondary", mb: 2 }, "All models allowed"), /* @__PURE__ */ import_react5.default.createElement(import_material5.Divider, { sx: { mb: 2 } }), usageLoading ? /* @__PURE__ */ import_react5.default.createElement(import_material5.Box, { display: "flex", justifyContent: "center", p: 2 }, /* @__PURE__ */ import_react5.default.createElement(import_material5.CircularProgress, { size: 24 })) : dailyData.length > 0 ? /* @__PURE__ */ import_react5.default.createElement(import_react5.default.Fragment, null, /* @__PURE__ */ import_react5.default.createElement(import_material5.Typography, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Daily Spend"), /* @__PURE__ */ import_react5.default.createElement(import_material5.Box, { height: 160, mb: 2 }, /* @__PURE__ */ import_react5.default.createElement(import_recharts2.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react5.default.createElement(import_recharts2.AreaChart, { data: dailyData }, /* @__PURE__ */ import_react5.default.createElement(import_recharts2.CartesianGrid, { strokeDasharray: "3 3", stroke: gridStroke }), /* @__PURE__ */ import_react5.default.createElement(import_recharts2.XAxis, { dataKey: "date", tick: { fontSize: 11, fill: tickFill } }), /* @__PURE__ */ import_react5.default.createElement(import_recharts2.YAxis, { tick: { fontSize: 11, fill: tickFill }, tickFormatter: (v) => `$${v.toFixed(2)}` }), /* @__PURE__ */ import_react5.default.createElement(import_recharts2.Tooltip, { formatter: (v) => [`$${v.toFixed(4)}`, "Spend"] }), /* @__PURE__ */ import_react5.default.createElement(import_recharts2.Area, { type: "monotone", dataKey: "spend", stroke: "#8884d8", fill: "#8884d8", fillOpacity: 0.3 })))), /* @__PURE__ */ import_react5.default.createElement(import_material5.Divider, { sx: { mb: 2 } })) : null, /* @__PURE__ */ import_react5.default.createElement(import_material5.Box, { display: "flex", alignItems: "center", gap: 1, mb: 1 }, /* @__PURE__ */ import_react5.default.createElement(import_icons_material4.Memory, { fontSize: "small", color: "action" }), /* @__PURE__ */ import_react5.default.createElement(import_material5.Typography, { variant: "subtitle2", color: "text.secondary" }, "Members")), team.members_with_roles?.length ? /* @__PURE__ */ import_react5.default.createElement(import_material5.TableContainer, null, /* @__PURE__ */ import_react5.default.createElement(import_material5.Table, { size: "small" }, /* @__PURE__ */ import_react5.default.createElement(import_material5.TableHead, null, /* @__PURE__ */ import_react5.default.createElement(import_material5.TableRow, null, /* @__PURE__ */ import_react5.default.createElement(import_material5.TableCell, null, "User"), /* @__PURE__ */ import_react5.default.createElement(import_material5.TableCell, null, "Role"))), /* @__PURE__ */ import_react5.default.createElement(import_material5.TableBody, null, team.members_with_roles.map((m) => /* @__PURE__ */ import_react5.default.createElement(import_material5.TableRow, { key: m.user_id }, /* @__PURE__ */ import_react5.default.createElement(import_material5.TableCell, null, m.user_email ? /* @__PURE__ */ import_react5.default.createElement(import_react5.default.Fragment, null, /* @__PURE__ */ import_react5.default.createElement(import_material5.Typography, { variant: "body2" }, m.user_email), /* @__PURE__ */ import_react5.default.createElement(
1249
+ import_material5.Typography,
1110
1250
  {
1111
1251
  variant: "caption",
1112
1252
  color: "text.secondary",
1113
1253
  sx: { fontFamily: "monospace" }
1114
1254
  },
1115
1255
  m.user_id
1116
- )) : /* @__PURE__ */ import_react4.default.createElement(import_material4.Typography, { variant: "body2", sx: { fontFamily: "monospace" } }, m.user_id)), /* @__PURE__ */ import_react4.default.createElement(import_material4.TableCell, null, /* @__PURE__ */ import_react4.default.createElement(
1117
- import_material4.Chip,
1256
+ )) : /* @__PURE__ */ import_react5.default.createElement(import_material5.Typography, { variant: "body2", sx: { fontFamily: "monospace" } }, m.user_id)), /* @__PURE__ */ import_react5.default.createElement(import_material5.TableCell, null, /* @__PURE__ */ import_react5.default.createElement(
1257
+ import_material5.Chip,
1118
1258
  {
1119
1259
  label: m.role,
1120
1260
  size: "small",
1121
1261
  color: m.role === "admin" ? "primary" : "default"
1122
1262
  }
1123
- ))))))) : /* @__PURE__ */ import_react4.default.createElement(import_material4.Typography, { variant: "body2", color: "text.secondary" }, "No members assigned."))));
1263
+ ))))))) : /* @__PURE__ */ import_react5.default.createElement(import_material5.Typography, { variant: "body2", color: "text.secondary" }, "No members assigned."))));
1124
1264
  };
1125
1265
  TeamUsage = ({
1126
1266
  teams,
@@ -1129,12 +1269,12 @@ var init_TeamUsage = __esm({
1129
1269
  getTeamUsageLoading
1130
1270
  }) => {
1131
1271
  if (loading) {
1132
- return /* @__PURE__ */ import_react4.default.createElement(import_material4.Paper, { sx: { p: 2 } }, /* @__PURE__ */ import_react4.default.createElement(import_material4.LinearProgress, null));
1272
+ return /* @__PURE__ */ import_react5.default.createElement(import_material5.Paper, { sx: { p: 2 } }, /* @__PURE__ */ import_react5.default.createElement(import_material5.LinearProgress, null));
1133
1273
  }
1134
1274
  if (!teams.length) {
1135
- return /* @__PURE__ */ import_react4.default.createElement(import_material4.Paper, { sx: { p: 2 } }, /* @__PURE__ */ import_react4.default.createElement(import_material4.Box, { display: "flex", alignItems: "flex-start", gap: 1.5 }, /* @__PURE__ */ import_react4.default.createElement(import_icons_material2.Group, { color: "disabled", sx: { mt: 0.5 } }), /* @__PURE__ */ import_react4.default.createElement(import_material4.Box, null, /* @__PURE__ */ import_react4.default.createElement(import_material4.Typography, { color: "text.secondary", variant: "body2" }, "You're not a member of any LiteLLM team yet."), /* @__PURE__ */ import_react4.default.createElement(import_material4.Typography, { color: "text.secondary", variant: "body2", mt: 0.5 }, "That's fine \u2014 your account is provisioned, so you can still generate personal keys and use models. If you need a shared budget with colleagues, ask an admin to add you to a team."))));
1275
+ return /* @__PURE__ */ import_react5.default.createElement(import_material5.Paper, { sx: { p: 2 } }, /* @__PURE__ */ import_react5.default.createElement(import_material5.Box, { display: "flex", alignItems: "flex-start", gap: 1.5 }, /* @__PURE__ */ import_react5.default.createElement(import_icons_material4.Group, { color: "disabled", sx: { mt: 0.5 } }), /* @__PURE__ */ import_react5.default.createElement(import_material5.Box, null, /* @__PURE__ */ import_react5.default.createElement(import_material5.Typography, { color: "text.secondary", variant: "body2" }, "You're not a member of any LiteLLM team yet."), /* @__PURE__ */ import_react5.default.createElement(import_material5.Typography, { color: "text.secondary", variant: "body2", mt: 0.5 }, "That's fine \u2014 your account is provisioned, so you can still generate personal keys and use models. If you need a shared budget with colleagues, ask an admin to add you to a team."))));
1136
1276
  }
1137
- return /* @__PURE__ */ import_react4.default.createElement(import_material4.Box, null, /* @__PURE__ */ import_react4.default.createElement(import_material4.Typography, { variant: "h6", mb: 1 }, "Teams"), teams.map((team) => /* @__PURE__ */ import_react4.default.createElement(
1277
+ return /* @__PURE__ */ import_react5.default.createElement(import_material5.Box, null, /* @__PURE__ */ import_react5.default.createElement(import_material5.Typography, { variant: "h6", mb: 1 }, "Teams"), teams.map((team) => /* @__PURE__ */ import_react5.default.createElement(
1138
1278
  TeamCard,
1139
1279
  {
1140
1280
  key: team.team_id,
@@ -1162,13 +1302,13 @@ function formatDateTime(iso) {
1162
1302
  return iso;
1163
1303
  }
1164
1304
  }
1165
- var import_react5, import_material5, import_icons_material3, import_react_use, ACTION_COLORS, DetailRow, AuditLog;
1305
+ var import_react6, import_material6, import_icons_material5, import_react_use, ACTION_COLORS, DetailRow, AuditLog;
1166
1306
  var init_AuditLog = __esm({
1167
1307
  "src/components/AuditLog.tsx"() {
1168
1308
  "use strict";
1169
- import_react5 = __toESM(require("react"));
1170
- import_material5 = require("@mui/material");
1171
- import_icons_material3 = require("@mui/icons-material");
1309
+ import_react6 = __toESM(require("react"));
1310
+ import_material6 = require("@mui/material");
1311
+ import_icons_material5 = require("@mui/icons-material");
1172
1312
  import_react_use = require("react-use");
1173
1313
  ACTION_COLORS = {
1174
1314
  created: "success",
@@ -1178,15 +1318,15 @@ var init_AuditLog = __esm({
1178
1318
  unblocked: "success"
1179
1319
  };
1180
1320
  DetailRow = ({ entry }) => {
1181
- const [open, setOpen] = (0, import_react5.useState)(false);
1321
+ const [open, setOpen] = (0, import_react6.useState)(false);
1182
1322
  const hasDetail = entry.before_value || entry.updated_values;
1183
- return /* @__PURE__ */ import_react5.default.createElement(import_react5.default.Fragment, null, /* @__PURE__ */ import_react5.default.createElement(import_material5.TableRow, { hover: true }, /* @__PURE__ */ import_react5.default.createElement(import_material5.TableCell, { sx: { width: 40, pr: 0 } }, hasDetail && /* @__PURE__ */ import_react5.default.createElement(import_material5.IconButton, { size: "small", onClick: () => setOpen((o) => !o) }, open ? /* @__PURE__ */ import_react5.default.createElement(import_icons_material3.KeyboardArrowUp, { fontSize: "small" }) : /* @__PURE__ */ import_react5.default.createElement(import_icons_material3.KeyboardArrowDown, { fontSize: "small" }))), /* @__PURE__ */ import_react5.default.createElement(import_material5.TableCell, { sx: { whiteSpace: "nowrap" } }, formatDateTime(entry.updated_at)), /* @__PURE__ */ import_react5.default.createElement(import_material5.TableCell, null, entry.action && /* @__PURE__ */ import_react5.default.createElement(import_material5.Chip, { label: entry.action, color: actionColor(entry.action), size: "small" })), /* @__PURE__ */ import_react5.default.createElement(import_material5.TableCell, null, entry.table_name ?? "-"), /* @__PURE__ */ import_react5.default.createElement(import_material5.TableCell, null, /* @__PURE__ */ import_react5.default.createElement(import_material5.Typography, { variant: "body2", component: "code", sx: { fontFamily: "monospace", fontSize: "0.75rem" } }, entry.object_id ? entry.object_id.slice(0, 20) + (entry.object_id.length > 20 ? "\u2026" : "") : "-")), /* @__PURE__ */ import_react5.default.createElement(import_material5.TableCell, null, entry.changed_by ?? "-")), hasDetail && /* @__PURE__ */ import_react5.default.createElement(import_material5.TableRow, null, /* @__PURE__ */ import_react5.default.createElement(import_material5.TableCell, { colSpan: 6, sx: { py: 0 } }, /* @__PURE__ */ import_react5.default.createElement(import_material5.Collapse, { in: open, unmountOnExit: true }, /* @__PURE__ */ import_react5.default.createElement(import_material5.Box, { p: 2, display: "flex", gap: 2, flexWrap: "wrap" }, entry.before_value && /* @__PURE__ */ import_react5.default.createElement(import_material5.Box, { flex: 1, minWidth: 200 }, /* @__PURE__ */ import_react5.default.createElement(import_material5.Typography, { variant: "caption", color: "text.secondary", display: "block", mb: 0.5 }, "Before"), /* @__PURE__ */ import_react5.default.createElement(import_material5.Typography, { component: "pre", variant: "caption", sx: { fontFamily: "monospace", whiteSpace: "pre-wrap", wordBreak: "break-all" } }, JSON.stringify(entry.before_value, null, 2))), entry.updated_values && /* @__PURE__ */ import_react5.default.createElement(import_material5.Box, { flex: 1, minWidth: 200 }, /* @__PURE__ */ import_react5.default.createElement(import_material5.Typography, { variant: "caption", color: "text.secondary", display: "block", mb: 0.5 }, "After"), /* @__PURE__ */ import_react5.default.createElement(import_material5.Typography, { component: "pre", variant: "caption", sx: { fontFamily: "monospace", whiteSpace: "pre-wrap", wordBreak: "break-all" } }, JSON.stringify(entry.updated_values, null, 2))))))));
1323
+ return /* @__PURE__ */ import_react6.default.createElement(import_react6.default.Fragment, null, /* @__PURE__ */ import_react6.default.createElement(import_material6.TableRow, { hover: true }, /* @__PURE__ */ import_react6.default.createElement(import_material6.TableCell, { sx: { width: 40, pr: 0 } }, hasDetail && /* @__PURE__ */ import_react6.default.createElement(import_material6.IconButton, { size: "small", onClick: () => setOpen((o) => !o) }, open ? /* @__PURE__ */ import_react6.default.createElement(import_icons_material5.KeyboardArrowUp, { fontSize: "small" }) : /* @__PURE__ */ import_react6.default.createElement(import_icons_material5.KeyboardArrowDown, { fontSize: "small" }))), /* @__PURE__ */ import_react6.default.createElement(import_material6.TableCell, { sx: { whiteSpace: "nowrap" } }, formatDateTime(entry.updated_at)), /* @__PURE__ */ import_react6.default.createElement(import_material6.TableCell, null, entry.action && /* @__PURE__ */ import_react6.default.createElement(import_material6.Chip, { label: entry.action, color: actionColor(entry.action), size: "small" })), /* @__PURE__ */ import_react6.default.createElement(import_material6.TableCell, null, entry.table_name ?? "-"), /* @__PURE__ */ import_react6.default.createElement(import_material6.TableCell, null, /* @__PURE__ */ import_react6.default.createElement(import_material6.Typography, { variant: "body2", component: "code", sx: { fontFamily: "monospace", fontSize: "0.75rem" } }, entry.object_id ? entry.object_id.slice(0, 20) + (entry.object_id.length > 20 ? "\u2026" : "") : "-")), /* @__PURE__ */ import_react6.default.createElement(import_material6.TableCell, null, entry.changed_by ?? "-")), hasDetail && /* @__PURE__ */ import_react6.default.createElement(import_material6.TableRow, null, /* @__PURE__ */ import_react6.default.createElement(import_material6.TableCell, { colSpan: 6, sx: { py: 0 } }, /* @__PURE__ */ import_react6.default.createElement(import_material6.Collapse, { in: open, unmountOnExit: true }, /* @__PURE__ */ import_react6.default.createElement(import_material6.Box, { p: 2, display: "flex", gap: 2, flexWrap: "wrap" }, entry.before_value && /* @__PURE__ */ import_react6.default.createElement(import_material6.Box, { flex: 1, minWidth: 200 }, /* @__PURE__ */ import_react6.default.createElement(import_material6.Typography, { variant: "caption", color: "text.secondary", display: "block", mb: 0.5 }, "Before"), /* @__PURE__ */ import_react6.default.createElement(import_material6.Typography, { component: "pre", variant: "caption", sx: { fontFamily: "monospace", whiteSpace: "pre-wrap", wordBreak: "break-all" } }, JSON.stringify(entry.before_value, null, 2))), entry.updated_values && /* @__PURE__ */ import_react6.default.createElement(import_material6.Box, { flex: 1, minWidth: 200 }, /* @__PURE__ */ import_react6.default.createElement(import_material6.Typography, { variant: "caption", color: "text.secondary", display: "block", mb: 0.5 }, "After"), /* @__PURE__ */ import_react6.default.createElement(import_material6.Typography, { component: "pre", variant: "caption", sx: { fontFamily: "monospace", whiteSpace: "pre-wrap", wordBreak: "break-all" } }, JSON.stringify(entry.updated_values, null, 2))))))));
1184
1324
  };
1185
1325
  AuditLog = ({ api }) => {
1186
- const [page, setPage] = (0, import_react5.useState)(0);
1187
- const [pageSize, setPageSize] = (0, import_react5.useState)(25);
1188
- const [filters, setFilters] = (0, import_react5.useState)({});
1189
- const fetchParams = (0, import_react5.useCallback)(
1326
+ const [page, setPage] = (0, import_react6.useState)(0);
1327
+ const [pageSize, setPageSize] = (0, import_react6.useState)(25);
1328
+ const [filters, setFilters] = (0, import_react6.useState)({});
1329
+ const fetchParams = (0, import_react6.useCallback)(
1190
1330
  () => ({ page: page + 1, page_size: pageSize, ...filters }),
1191
1331
  [page, pageSize, filters]
1192
1332
  )();
@@ -1196,8 +1336,8 @@ var init_AuditLog = __esm({
1196
1336
  );
1197
1337
  const entries = value?.audit_logs ?? [];
1198
1338
  const total = value?.total ?? 0;
1199
- return /* @__PURE__ */ import_react5.default.createElement(import_material5.Paper, null, /* @__PURE__ */ import_react5.default.createElement(import_material5.Box, { p: 2, display: "flex", gap: 2, flexWrap: "wrap", alignItems: "center" }, /* @__PURE__ */ import_react5.default.createElement(import_material5.Typography, { variant: "h6", sx: { flex: "0 0 auto", mr: 1 } }, "Audit Log"), /* @__PURE__ */ import_react5.default.createElement(
1200
- import_material5.TextField,
1339
+ return /* @__PURE__ */ import_react6.default.createElement(import_material6.Paper, null, /* @__PURE__ */ import_react6.default.createElement(import_material6.Box, { p: 2, display: "flex", gap: 2, flexWrap: "wrap", alignItems: "center" }, /* @__PURE__ */ import_react6.default.createElement(import_material6.Typography, { variant: "h6", sx: { flex: "0 0 auto", mr: 1 } }, "Audit Log"), /* @__PURE__ */ import_react6.default.createElement(
1340
+ import_material6.TextField,
1201
1341
  {
1202
1342
  size: "small",
1203
1343
  label: "Action",
@@ -1209,13 +1349,13 @@ var init_AuditLog = __esm({
1209
1349
  },
1210
1350
  sx: { minWidth: 160 }
1211
1351
  },
1212
- /* @__PURE__ */ import_react5.default.createElement(import_material5.MenuItem, { value: "" }, "All actions"),
1213
- /* @__PURE__ */ import_react5.default.createElement(import_material5.MenuItem, { value: "created" }, "Created"),
1214
- /* @__PURE__ */ import_react5.default.createElement(import_material5.MenuItem, { value: "updated" }, "Updated"),
1215
- /* @__PURE__ */ import_react5.default.createElement(import_material5.MenuItem, { value: "deleted" }, "Deleted"),
1216
- /* @__PURE__ */ import_react5.default.createElement(import_material5.MenuItem, { value: "blocked" }, "Blocked")
1217
- ), /* @__PURE__ */ import_react5.default.createElement(
1218
- import_material5.TextField,
1352
+ /* @__PURE__ */ import_react6.default.createElement(import_material6.MenuItem, { value: "" }, "All actions"),
1353
+ /* @__PURE__ */ import_react6.default.createElement(import_material6.MenuItem, { value: "created" }, "Created"),
1354
+ /* @__PURE__ */ import_react6.default.createElement(import_material6.MenuItem, { value: "updated" }, "Updated"),
1355
+ /* @__PURE__ */ import_react6.default.createElement(import_material6.MenuItem, { value: "deleted" }, "Deleted"),
1356
+ /* @__PURE__ */ import_react6.default.createElement(import_material6.MenuItem, { value: "blocked" }, "Blocked")
1357
+ ), /* @__PURE__ */ import_react6.default.createElement(
1358
+ import_material6.TextField,
1219
1359
  {
1220
1360
  size: "small",
1221
1361
  label: "Table",
@@ -1227,12 +1367,12 @@ var init_AuditLog = __esm({
1227
1367
  },
1228
1368
  sx: { minWidth: 160 }
1229
1369
  },
1230
- /* @__PURE__ */ import_react5.default.createElement(import_material5.MenuItem, { value: "" }, "All tables"),
1231
- /* @__PURE__ */ import_react5.default.createElement(import_material5.MenuItem, { value: "LiteLLM_VerificationToken" }, "Key"),
1232
- /* @__PURE__ */ import_react5.default.createElement(import_material5.MenuItem, { value: "LiteLLM_TeamTable" }, "Team"),
1233
- /* @__PURE__ */ import_react5.default.createElement(import_material5.MenuItem, { value: "LiteLLM_UserTable" }, "User")
1234
- ), /* @__PURE__ */ import_react5.default.createElement(
1235
- import_material5.TextField,
1370
+ /* @__PURE__ */ import_react6.default.createElement(import_material6.MenuItem, { value: "" }, "All tables"),
1371
+ /* @__PURE__ */ import_react6.default.createElement(import_material6.MenuItem, { value: "LiteLLM_VerificationToken" }, "Key"),
1372
+ /* @__PURE__ */ import_react6.default.createElement(import_material6.MenuItem, { value: "LiteLLM_TeamTable" }, "Team"),
1373
+ /* @__PURE__ */ import_react6.default.createElement(import_material6.MenuItem, { value: "LiteLLM_UserTable" }, "User")
1374
+ ), /* @__PURE__ */ import_react6.default.createElement(
1375
+ import_material6.TextField,
1236
1376
  {
1237
1377
  size: "small",
1238
1378
  label: "Changed by",
@@ -1243,8 +1383,8 @@ var init_AuditLog = __esm({
1243
1383
  },
1244
1384
  sx: { minWidth: 200 }
1245
1385
  }
1246
- )), error && /* @__PURE__ */ import_react5.default.createElement(import_material5.Box, { px: 2, pb: 2 }, /* @__PURE__ */ import_react5.default.createElement(import_material5.Alert, { severity: "error" }, error.message)), /* @__PURE__ */ import_react5.default.createElement(import_material5.TableContainer, null, /* @__PURE__ */ import_react5.default.createElement(import_material5.Table, { size: "small" }, /* @__PURE__ */ import_react5.default.createElement(import_material5.TableHead, null, /* @__PURE__ */ import_react5.default.createElement(import_material5.TableRow, null, /* @__PURE__ */ import_react5.default.createElement(import_material5.TableCell, { sx: { width: 40 } }), /* @__PURE__ */ import_react5.default.createElement(import_material5.TableCell, null, "Time"), /* @__PURE__ */ import_react5.default.createElement(import_material5.TableCell, null, "Action"), /* @__PURE__ */ import_react5.default.createElement(import_material5.TableCell, null, "Table"), /* @__PURE__ */ import_react5.default.createElement(import_material5.TableCell, null, "Object ID"), /* @__PURE__ */ import_react5.default.createElement(import_material5.TableCell, null, "Changed By"))), /* @__PURE__ */ import_react5.default.createElement(import_material5.TableBody, null, loading ? /* @__PURE__ */ import_react5.default.createElement(import_material5.TableRow, null, /* @__PURE__ */ import_react5.default.createElement(import_material5.TableCell, { colSpan: 6, align: "center", sx: { py: 4 } }, /* @__PURE__ */ import_react5.default.createElement(import_material5.CircularProgress, { size: 24 }))) : entries.length === 0 ? /* @__PURE__ */ import_react5.default.createElement(import_material5.TableRow, null, /* @__PURE__ */ import_react5.default.createElement(import_material5.TableCell, { colSpan: 6, align: "center", sx: { py: 4 } }, /* @__PURE__ */ import_react5.default.createElement(import_material5.Typography, { color: "text.secondary" }, "No audit events found"))) : entries.map((entry) => /* @__PURE__ */ import_react5.default.createElement(DetailRow, { key: entry.id, entry }))))), /* @__PURE__ */ import_react5.default.createElement(
1247
- import_material5.TablePagination,
1386
+ )), error && /* @__PURE__ */ import_react6.default.createElement(import_material6.Box, { px: 2, pb: 2 }, /* @__PURE__ */ import_react6.default.createElement(import_material6.Alert, { severity: "error" }, error.message)), /* @__PURE__ */ import_react6.default.createElement(import_material6.TableContainer, null, /* @__PURE__ */ import_react6.default.createElement(import_material6.Table, { size: "small" }, /* @__PURE__ */ import_react6.default.createElement(import_material6.TableHead, null, /* @__PURE__ */ import_react6.default.createElement(import_material6.TableRow, null, /* @__PURE__ */ import_react6.default.createElement(import_material6.TableCell, { sx: { width: 40 } }), /* @__PURE__ */ import_react6.default.createElement(import_material6.TableCell, null, "Time"), /* @__PURE__ */ import_react6.default.createElement(import_material6.TableCell, null, "Action"), /* @__PURE__ */ import_react6.default.createElement(import_material6.TableCell, null, "Table"), /* @__PURE__ */ import_react6.default.createElement(import_material6.TableCell, null, "Object ID"), /* @__PURE__ */ import_react6.default.createElement(import_material6.TableCell, null, "Changed By"))), /* @__PURE__ */ import_react6.default.createElement(import_material6.TableBody, null, loading ? /* @__PURE__ */ import_react6.default.createElement(import_material6.TableRow, null, /* @__PURE__ */ import_react6.default.createElement(import_material6.TableCell, { colSpan: 6, align: "center", sx: { py: 4 } }, /* @__PURE__ */ import_react6.default.createElement(import_material6.CircularProgress, { size: 24 }))) : entries.length === 0 ? /* @__PURE__ */ import_react6.default.createElement(import_material6.TableRow, null, /* @__PURE__ */ import_react6.default.createElement(import_material6.TableCell, { colSpan: 6, align: "center", sx: { py: 4 } }, /* @__PURE__ */ import_react6.default.createElement(import_material6.Typography, { color: "text.secondary" }, "No audit events found"))) : entries.map((entry) => /* @__PURE__ */ import_react6.default.createElement(DetailRow, { key: entry.id, entry }))))), /* @__PURE__ */ import_react6.default.createElement(
1387
+ import_material6.TablePagination,
1248
1388
  {
1249
1389
  component: "div",
1250
1390
  count: total,
@@ -1281,16 +1421,17 @@ function initDateRange() {
1281
1421
  else start.setDate(start.getDate() - 7);
1282
1422
  return { start, end };
1283
1423
  }
1284
- var import_react6, import_material6, import_react_use2, import_core_plugin_api2, PERIOD_LS_KEY2, LiteLLMPage;
1424
+ var import_react7, import_material7, import_react_use2, import_core_plugin_api2, PERIOD_LS_KEY2, LiteLLMPage;
1285
1425
  var init_LiteLLMPage = __esm({
1286
1426
  "src/components/LiteLLMPage.tsx"() {
1287
1427
  "use strict";
1288
- import_react6 = __toESM(require("react"));
1289
- import_material6 = require("@mui/material");
1428
+ import_react7 = __toESM(require("react"));
1429
+ import_material7 = require("@mui/material");
1290
1430
  import_react_use2 = require("react-use");
1291
1431
  import_core_plugin_api2 = require("@backstage/core-plugin-api");
1292
1432
  init_DashboardHeader();
1293
1433
  init_KeysTable();
1434
+ init_GenerateKeyDialog();
1294
1435
  init_UsageStats();
1295
1436
  init_TeamUsage();
1296
1437
  init_AuditLog();
@@ -1298,11 +1439,12 @@ var init_LiteLLMPage = __esm({
1298
1439
  PERIOD_LS_KEY2 = "litellm_usage_period";
1299
1440
  LiteLLMPage = () => {
1300
1441
  const api = (0, import_core_plugin_api2.useApi)(liteLlmApiRef);
1301
- const [dateRange, setDateRange] = (0, import_react6.useState)(initDateRange);
1302
- const [activeTab, setActiveTab] = (0, import_react6.useState)("overview");
1303
- const [snackbar, setSnackbar] = (0, import_react6.useState)(null);
1304
- const [teamUsageCache, setTeamUsageCache] = (0, import_react6.useState)({});
1305
- const [teamUsageLoading, setTeamUsageLoading] = (0, import_react6.useState)({});
1442
+ const [dateRange, setDateRange] = (0, import_react7.useState)(initDateRange);
1443
+ const [activeTab, setActiveTab] = (0, import_react7.useState)("overview");
1444
+ const [snackbar, setSnackbar] = (0, import_react7.useState)(null);
1445
+ const [generateDialogOpen, setGenerateDialogOpen] = (0, import_react7.useState)(false);
1446
+ const [teamUsageCache, setTeamUsageCache] = (0, import_react7.useState)({});
1447
+ const [teamUsageLoading, setTeamUsageLoading] = (0, import_react7.useState)({});
1306
1448
  const { value: userInfo, loading: userLoading, error: userError } = (0, import_react_use2.useAsync)(
1307
1449
  () => api.getUserInfo(),
1308
1450
  [api]
@@ -1326,7 +1468,8 @@ var init_LiteLLMPage = __esm({
1326
1468
  () => api.getTeams().catch(() => []),
1327
1469
  [api]
1328
1470
  );
1329
- const teams = (0, import_react6.useMemo)(() => {
1471
+ const { value: liteLlmConfig } = (0, import_react_use2.useAsync)(() => api.getConfig(), [api]);
1472
+ const teams = (0, import_react7.useMemo)(() => {
1330
1473
  if (!allTeams?.length) return [];
1331
1474
  if (!userInfo) return allTeams;
1332
1475
  const userId = userInfo.user_id;
@@ -1343,12 +1486,12 @@ var init_LiteLLMPage = __esm({
1343
1486
  const endDate = dateRange.end.toISOString().split("T")[0];
1344
1487
  return api.getUsage(startDate, endDate);
1345
1488
  }, [api, dateRange]);
1346
- const handleDateRangeChange = (0, import_react6.useCallback)((range) => {
1489
+ const handleDateRangeChange = (0, import_react7.useCallback)((range) => {
1347
1490
  setDateRange(range);
1348
1491
  setTeamUsageCache({});
1349
1492
  setTeamUsageLoading({});
1350
1493
  }, [setDateRange]);
1351
- const loadTeamUsage = (0, import_react6.useCallback)(async (teamId) => {
1494
+ const loadTeamUsage = (0, import_react7.useCallback)(async (teamId) => {
1352
1495
  if (teamUsageCache[teamId] !== void 0 || teamUsageLoading[teamId]) return;
1353
1496
  setTeamUsageLoading((prev) => ({ ...prev, [teamId]: true }));
1354
1497
  try {
@@ -1362,7 +1505,7 @@ var init_LiteLLMPage = __esm({
1362
1505
  setTeamUsageLoading((prev) => ({ ...prev, [teamId]: false }));
1363
1506
  }
1364
1507
  }, [api, dateRange, teamUsageCache, teamUsageLoading]);
1365
- const allowedModels = (0, import_react6.useMemo)(() => {
1508
+ const allowedModels = (0, import_react7.useMemo)(() => {
1366
1509
  if (!allModels?.length) return [];
1367
1510
  const userModels = userInfo?.models;
1368
1511
  const teamModels = teams?.flatMap((t) => t.models ?? []);
@@ -1375,7 +1518,7 @@ var init_LiteLLMPage = __esm({
1375
1518
  ]);
1376
1519
  return allModels.filter((m) => allowed.has(m.model_name));
1377
1520
  }, [allModels, userInfo, teams]);
1378
- const handleGenerateKey = (0, import_react6.useCallback)(
1521
+ const handleGenerateKey = (0, import_react7.useCallback)(
1379
1522
  async (request) => {
1380
1523
  try {
1381
1524
  const response = await api.generateKey(request);
@@ -1389,7 +1532,7 @@ var init_LiteLLMPage = __esm({
1389
1532
  },
1390
1533
  [api, refreshKeys]
1391
1534
  );
1392
- const handleUpdateKey = (0, import_react6.useCallback)(
1535
+ const handleUpdateKey = (0, import_react7.useCallback)(
1393
1536
  async (keyId, request) => {
1394
1537
  try {
1395
1538
  await api.updateKey(keyId, request);
@@ -1402,7 +1545,7 @@ var init_LiteLLMPage = __esm({
1402
1545
  },
1403
1546
  [api, refreshKeys]
1404
1547
  );
1405
- const handleBlockKey = (0, import_react6.useCallback)(
1548
+ const handleBlockKey = (0, import_react7.useCallback)(
1406
1549
  async (keyId) => {
1407
1550
  try {
1408
1551
  await api.blockKey(keyId);
@@ -1414,7 +1557,7 @@ var init_LiteLLMPage = __esm({
1414
1557
  },
1415
1558
  [api, refreshKeys]
1416
1559
  );
1417
- const handleUnblockKey = (0, import_react6.useCallback)(
1560
+ const handleUnblockKey = (0, import_react7.useCallback)(
1418
1561
  async (keyId) => {
1419
1562
  try {
1420
1563
  await api.unblockKey(keyId);
@@ -1426,7 +1569,7 @@ var init_LiteLLMPage = __esm({
1426
1569
  },
1427
1570
  [api, refreshKeys]
1428
1571
  );
1429
- const handleResetKeySpend = (0, import_react6.useCallback)(
1572
+ const handleResetKeySpend = (0, import_react7.useCallback)(
1430
1573
  async (keyId) => {
1431
1574
  try {
1432
1575
  await api.resetKeySpend(keyId);
@@ -1438,7 +1581,7 @@ var init_LiteLLMPage = __esm({
1438
1581
  },
1439
1582
  [api, refreshKeys]
1440
1583
  );
1441
- const handleDeleteKey = (0, import_react6.useCallback)(
1584
+ const handleDeleteKey = (0, import_react7.useCallback)(
1442
1585
  async (keyId) => {
1443
1586
  try {
1444
1587
  await api.deleteKey(keyId);
@@ -1455,7 +1598,7 @@ var init_LiteLLMPage = __esm({
1455
1598
  },
1456
1599
  [api, refreshKeys]
1457
1600
  );
1458
- const handlePruneExpiredKeys = (0, import_react6.useCallback)(
1601
+ const handlePruneExpiredKeys = (0, import_react7.useCallback)(
1459
1602
  async () => {
1460
1603
  try {
1461
1604
  const result = await api.pruneExpiredKeys();
@@ -1470,25 +1613,34 @@ var init_LiteLLMPage = __esm({
1470
1613
  );
1471
1614
  const isInitialLoading = userLoading && !userInfo;
1472
1615
  if (isInitialLoading) {
1473
- return /* @__PURE__ */ import_react6.default.createElement(import_material6.Box, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: "50vh" }, /* @__PURE__ */ import_react6.default.createElement(import_material6.CircularProgress, null));
1616
+ return /* @__PURE__ */ import_react7.default.createElement(import_material7.Box, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: "50vh" }, /* @__PURE__ */ import_react7.default.createElement(import_material7.CircularProgress, null));
1474
1617
  }
1475
1618
  if (userError || !userInfo) {
1476
1619
  const isProvisioningEnabled = userError?.body?.provisioning === true;
1477
1620
  const hint = userError?.body?.hint;
1478
- return /* @__PURE__ */ import_react6.default.createElement(import_material6.Box, { p: 3 }, /* @__PURE__ */ import_react6.default.createElement(import_material6.Paper, { sx: { p: 3 } }, /* @__PURE__ */ import_react6.default.createElement(import_material6.Typography, { variant: "h6", gutterBottom: true }, "Account not provisioned"), /* @__PURE__ */ import_react6.default.createElement(import_material6.Typography, { color: "text.secondary", paragraph: true }, "Your Backstage account is not linked to a LiteLLM user."), hint ? /* @__PURE__ */ import_react6.default.createElement(import_material6.Typography, { variant: "body2", color: "text.secondary" }, hint) : /* @__PURE__ */ import_react6.default.createElement(import_material6.Typography, { 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.")));
1621
+ return /* @__PURE__ */ import_react7.default.createElement(import_material7.Box, { p: 3 }, /* @__PURE__ */ import_react7.default.createElement(import_material7.Paper, { sx: { p: 3 } }, /* @__PURE__ */ import_react7.default.createElement(import_material7.Typography, { variant: "h6", gutterBottom: true }, "Account not provisioned"), /* @__PURE__ */ import_react7.default.createElement(import_material7.Typography, { color: "text.secondary", paragraph: true }, "Your Backstage account is not linked to a LiteLLM user."), hint ? /* @__PURE__ */ import_react7.default.createElement(import_material7.Typography, { variant: "body2", color: "text.secondary" }, hint) : /* @__PURE__ */ import_react7.default.createElement(import_material7.Typography, { 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.")));
1479
1622
  }
1480
- return /* @__PURE__ */ import_react6.default.createElement(import_material6.Box, { p: 3 }, /* @__PURE__ */ import_react6.default.createElement(import_material6.Box, { mb: 2 }, /* @__PURE__ */ import_react6.default.createElement(DashboardHeader, { userInfo, teams: teams ?? [], loading: userLoading || teamsLoading })), /* @__PURE__ */ import_react6.default.createElement(
1481
- import_material6.Tabs,
1623
+ return /* @__PURE__ */ import_react7.default.createElement(import_material7.Box, { p: 3 }, /* @__PURE__ */ import_react7.default.createElement(import_material7.Box, { mb: 2 }, /* @__PURE__ */ import_react7.default.createElement(
1624
+ DashboardHeader,
1625
+ {
1626
+ userInfo,
1627
+ teams: teams ?? [],
1628
+ keys: keys ?? [],
1629
+ loading: userLoading || teamsLoading,
1630
+ onGenerateKeyClick: () => setGenerateDialogOpen(true)
1631
+ }
1632
+ )), /* @__PURE__ */ import_react7.default.createElement(
1633
+ import_material7.Tabs,
1482
1634
  {
1483
1635
  value: activeTab,
1484
1636
  onChange: (_, v) => setActiveTab(v),
1485
1637
  sx: { mb: 2, borderBottom: 1, borderColor: "divider" }
1486
1638
  },
1487
- /* @__PURE__ */ import_react6.default.createElement(import_material6.Tab, { label: "Overview", value: "overview" }),
1488
- /* @__PURE__ */ import_react6.default.createElement(import_material6.Tab, { label: "Keys", value: "keys" }),
1489
- /* @__PURE__ */ import_react6.default.createElement(import_material6.Tab, { label: "Teams", value: "teams" }),
1490
- userInfo.can_view_audit && /* @__PURE__ */ import_react6.default.createElement(import_material6.Tab, { label: "Audit Log", value: "audit" })
1491
- ), activeTab === "overview" && /* @__PURE__ */ import_react6.default.createElement(
1639
+ /* @__PURE__ */ import_react7.default.createElement(import_material7.Tab, { label: "Overview", value: "overview" }),
1640
+ /* @__PURE__ */ import_react7.default.createElement(import_material7.Tab, { label: "Keys", value: "keys" }),
1641
+ /* @__PURE__ */ import_react7.default.createElement(import_material7.Tab, { label: "Teams", value: "teams" }),
1642
+ userInfo.can_view_audit && /* @__PURE__ */ import_react7.default.createElement(import_material7.Tab, { label: "Audit Log", value: "audit" })
1643
+ ), activeTab === "overview" && /* @__PURE__ */ import_react7.default.createElement(
1492
1644
  UsageStats,
1493
1645
  {
1494
1646
  usage: usage ?? null,
@@ -1498,23 +1650,21 @@ var init_LiteLLMPage = __esm({
1498
1650
  loading: usageLoading,
1499
1651
  userInfo
1500
1652
  }
1501
- ), activeTab === "keys" && /* @__PURE__ */ import_react6.default.createElement(
1653
+ ), activeTab === "keys" && /* @__PURE__ */ import_react7.default.createElement(
1502
1654
  KeysTable,
1503
1655
  {
1504
1656
  keys: keys ?? [],
1505
1657
  models: allowedModels,
1506
- teams: teams ?? [],
1507
1658
  loading: keysLoading || modelsLoading,
1508
- onGenerateKey: handleGenerateKey,
1659
+ onGenerateKeyClick: () => setGenerateDialogOpen(true),
1509
1660
  onUpdateKey: handleUpdateKey,
1510
1661
  onBlockKey: handleBlockKey,
1511
1662
  onUnblockKey: handleUnblockKey,
1512
1663
  onResetKeySpend: handleResetKeySpend,
1513
1664
  onDeleteKey: handleDeleteKey,
1514
- onPruneExpiredKeys: handlePruneExpiredKeys,
1515
- onGetConfig: () => api.getConfig()
1665
+ onPruneExpiredKeys: handlePruneExpiredKeys
1516
1666
  }
1517
- ), activeTab === "teams" && /* @__PURE__ */ import_react6.default.createElement(
1667
+ ), activeTab === "teams" && /* @__PURE__ */ import_react7.default.createElement(
1518
1668
  TeamUsage,
1519
1669
  {
1520
1670
  teams: teams ?? [],
@@ -1525,15 +1675,28 @@ var init_LiteLLMPage = __esm({
1525
1675
  },
1526
1676
  getTeamUsageLoading: (teamId) => teamUsageLoading[teamId] ?? false
1527
1677
  }
1528
- ), activeTab === "audit" && userInfo.can_view_audit && /* @__PURE__ */ import_react6.default.createElement(AuditLog, { api }), /* @__PURE__ */ import_react6.default.createElement(
1529
- import_material6.Snackbar,
1678
+ ), activeTab === "audit" && userInfo.can_view_audit && /* @__PURE__ */ import_react7.default.createElement(AuditLog, { api }), /* @__PURE__ */ import_react7.default.createElement(
1679
+ GenerateKeyDialog,
1680
+ {
1681
+ open: generateDialogOpen,
1682
+ onClose: () => setGenerateDialogOpen(false),
1683
+ keys: keys ?? [],
1684
+ models: allowedModels,
1685
+ teams: teams ?? [],
1686
+ username: userInfo.user_id,
1687
+ keyGenerationSettings: liteLlmConfig?.keyGeneration,
1688
+ onGenerateKey: handleGenerateKey,
1689
+ onGetConfig: () => api.getConfig()
1690
+ }
1691
+ ), /* @__PURE__ */ import_react7.default.createElement(
1692
+ import_material7.Snackbar,
1530
1693
  {
1531
1694
  open: !!snackbar,
1532
1695
  autoHideDuration: 5e3,
1533
1696
  onClose: () => setSnackbar(null),
1534
1697
  anchorOrigin: { vertical: "bottom", horizontal: "right" }
1535
1698
  },
1536
- snackbar ? /* @__PURE__ */ import_react6.default.createElement(import_material6.Alert, { severity: snackbar.severity, onClose: () => setSnackbar(null) }, snackbar.message) : void 0
1699
+ snackbar ? /* @__PURE__ */ import_react7.default.createElement(import_material7.Alert, { severity: snackbar.severity, onClose: () => setSnackbar(null) }, snackbar.message) : void 0
1537
1700
  ));
1538
1701
  };
1539
1702
  }
@@ -1555,8 +1718,8 @@ __export(index_exports, {
1555
1718
  module.exports = __toCommonJS(index_exports);
1556
1719
 
1557
1720
  // src/plugin.tsx
1558
- var import_react7 = __toESM(require("react"));
1559
- var import_icons_material4 = require("@mui/icons-material");
1721
+ var import_react8 = __toESM(require("react"));
1722
+ var import_icons_material6 = require("@mui/icons-material");
1560
1723
  var import_frontend_plugin_api = require("@backstage/frontend-plugin-api");
1561
1724
  init_api();
1562
1725
  var liteLlmApi = import_frontend_plugin_api.ApiBlueprint.make({
@@ -1570,10 +1733,10 @@ var liteLlmPage = import_frontend_plugin_api.PageBlueprint.make({
1570
1733
  params: {
1571
1734
  path: "/litellm",
1572
1735
  title: "LiteLLM",
1573
- icon: /* @__PURE__ */ import_react7.default.createElement(import_icons_material4.TrendingUp, null),
1736
+ icon: /* @__PURE__ */ import_react8.default.createElement(import_icons_material6.TrendingUp, null),
1574
1737
  loader: async () => {
1575
1738
  const { LiteLLMPage: LiteLLMPage2 } = await Promise.resolve().then(() => (init_LiteLLMPage(), LiteLLMPage_exports));
1576
- return /* @__PURE__ */ import_react7.default.createElement(LiteLLMPage2, null);
1739
+ return /* @__PURE__ */ import_react8.default.createElement(LiteLLMPage2, null);
1577
1740
  }
1578
1741
  }
1579
1742
  });
@@ -1590,8 +1753,8 @@ init_UsageStats();
1590
1753
  init_TeamUsage();
1591
1754
 
1592
1755
  // src/components/LiteLLMHomeWidget.tsx
1593
- var import_react8 = __toESM(require("react"));
1594
- var import_material7 = require("@mui/material");
1756
+ var import_react9 = __toESM(require("react"));
1757
+ var import_material8 = require("@mui/material");
1595
1758
  var import_recharts3 = require("recharts");
1596
1759
  var import_core_plugin_api3 = require("@backstage/core-plugin-api");
1597
1760
  init_api();
@@ -1608,19 +1771,19 @@ function presetToDateRange(preset) {
1608
1771
  }
1609
1772
  return { start, end };
1610
1773
  }
1611
- var Kpi = ({ label, value }) => /* @__PURE__ */ import_react8.default.createElement(import_material7.Box, null, /* @__PURE__ */ import_react8.default.createElement(import_material7.Typography, { variant: "caption", color: "text.secondary", display: "block" }, label), /* @__PURE__ */ import_react8.default.createElement(import_material7.Typography, { variant: "subtitle1", fontWeight: 600 }, value));
1774
+ var Kpi = ({ label, value }) => /* @__PURE__ */ import_react9.default.createElement(import_material8.Box, null, /* @__PURE__ */ import_react9.default.createElement(import_material8.Typography, { variant: "caption", color: "text.secondary", display: "block" }, label), /* @__PURE__ */ import_react9.default.createElement(import_material8.Typography, { variant: "subtitle1", fontWeight: 600 }, value));
1612
1775
  var LiteLLMHomeWidget = ({
1613
1776
  defaultPeriod = "7d",
1614
1777
  title = "LiteLLM Usage"
1615
1778
  }) => {
1616
1779
  const api = (0, import_core_plugin_api3.useApi)(liteLlmApiRef);
1617
- const [period, setPeriod] = (0, import_react8.useState)(defaultPeriod);
1618
- const [loading, setLoading] = (0, import_react8.useState)(true);
1619
- const [usageError, setUsageError] = (0, import_react8.useState)(null);
1620
- const [keysError, setKeysError] = (0, import_react8.useState)(null);
1621
- const [usage, setUsage] = (0, import_react8.useState)(null);
1622
- const [keys, setKeys] = (0, import_react8.useState)([]);
1623
- (0, import_react8.useEffect)(() => {
1780
+ const [period, setPeriod] = (0, import_react9.useState)(defaultPeriod);
1781
+ const [loading, setLoading] = (0, import_react9.useState)(true);
1782
+ const [usageError, setUsageError] = (0, import_react9.useState)(null);
1783
+ const [keysError, setKeysError] = (0, import_react9.useState)(null);
1784
+ const [usage, setUsage] = (0, import_react9.useState)(null);
1785
+ const [keys, setKeys] = (0, import_react9.useState)([]);
1786
+ (0, import_react9.useEffect)(() => {
1624
1787
  let cancelled = false;
1625
1788
  setLoading(true);
1626
1789
  setUsageError(null);
@@ -1657,17 +1820,17 @@ var LiteLLMHomeWidget = ({
1657
1820
  spend: d.spend
1658
1821
  }));
1659
1822
  const hasSparkline = dailyData.length > 0;
1660
- return /* @__PURE__ */ import_react8.default.createElement(import_material7.Paper, { sx: { p: 2 } }, /* @__PURE__ */ import_react8.default.createElement(import_material7.Box, { display: "flex", justifyContent: "space-between", alignItems: "center", mb: 1.5 }, /* @__PURE__ */ import_react8.default.createElement(import_material7.Typography, { variant: "h6" }, title), /* @__PURE__ */ import_react8.default.createElement(import_material7.FormControl, { size: "small", sx: { minWidth: 90 } }, /* @__PURE__ */ import_react8.default.createElement(
1661
- import_material7.Select,
1823
+ return /* @__PURE__ */ import_react9.default.createElement(import_material8.Paper, { sx: { p: 2 } }, /* @__PURE__ */ import_react9.default.createElement(import_material8.Box, { display: "flex", justifyContent: "space-between", alignItems: "center", mb: 1.5 }, /* @__PURE__ */ import_react9.default.createElement(import_material8.Typography, { variant: "h6" }, title), /* @__PURE__ */ import_react9.default.createElement(import_material8.FormControl, { size: "small", sx: { minWidth: 90 } }, /* @__PURE__ */ import_react9.default.createElement(
1824
+ import_material8.Select,
1662
1825
  {
1663
1826
  value: period,
1664
1827
  onChange: (e) => setPeriod(e.target.value),
1665
1828
  displayEmpty: true
1666
1829
  },
1667
- /* @__PURE__ */ import_react8.default.createElement(import_material7.MenuItem, { value: "today" }, "Today"),
1668
- /* @__PURE__ */ import_react8.default.createElement(import_material7.MenuItem, { value: "7d" }, "7d"),
1669
- /* @__PURE__ */ import_react8.default.createElement(import_material7.MenuItem, { value: "30d" }, "30d")
1670
- ))), loading && /* @__PURE__ */ import_react8.default.createElement(import_material7.Box, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: 120 }, /* @__PURE__ */ import_react8.default.createElement(import_material7.CircularProgress, { size: 32 })), !loading && totalFailure && /* @__PURE__ */ import_react8.default.createElement(import_material7.Alert, { severity: "error", sx: { mt: 1 } }, usageError ?? "Failed to load usage data"), !loading && !totalFailure && /* @__PURE__ */ import_react8.default.createElement(import_react8.default.Fragment, null, partialFailure && /* @__PURE__ */ import_react8.default.createElement(import_material7.Alert, { severity: "warning", sx: { mt: 1, mb: 1 } }, usageError ? `Usage data unavailable (${usageError}).` : "", keysError ? ` Key list unavailable (${keysError}).` : "", " Showing what loaded."), /* @__PURE__ */ import_react8.default.createElement(import_material7.Grid, { container: true, spacing: 2, sx: { mb: hasSparkline ? 1.5 : 0 } }, /* @__PURE__ */ import_react8.default.createElement(import_material7.Grid, { item: true, xs: 6 }, /* @__PURE__ */ import_react8.default.createElement(Kpi, { label: "USD Spent", value: fmtUsd(usage?.total_spend ?? 0) })), /* @__PURE__ */ import_react8.default.createElement(import_material7.Grid, { item: true, xs: 6 }, /* @__PURE__ */ import_react8.default.createElement(Kpi, { label: "Tokens In", value: fmtInt(usage?.prompt_tokens ?? 0) })), /* @__PURE__ */ import_react8.default.createElement(import_material7.Grid, { item: true, xs: 6 }, /* @__PURE__ */ import_react8.default.createElement(Kpi, { label: "Tokens Out", value: fmtInt(usage?.completion_tokens ?? 0) })), /* @__PURE__ */ import_react8.default.createElement(import_material7.Grid, { item: true, xs: 6 }, /* @__PURE__ */ import_react8.default.createElement(Kpi, { label: "Keys", value: fmtInt(keys.length) }))), hasSparkline && /* @__PURE__ */ import_react8.default.createElement(import_material7.Box, { height: 120 }, /* @__PURE__ */ import_react8.default.createElement(import_recharts3.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react8.default.createElement(import_recharts3.AreaChart, { data: dailyData, margin: { top: 4, right: 0, bottom: 0, left: 0 } }, /* @__PURE__ */ import_react8.default.createElement(
1830
+ /* @__PURE__ */ import_react9.default.createElement(import_material8.MenuItem, { value: "today" }, "Today"),
1831
+ /* @__PURE__ */ import_react9.default.createElement(import_material8.MenuItem, { value: "7d" }, "7d"),
1832
+ /* @__PURE__ */ import_react9.default.createElement(import_material8.MenuItem, { value: "30d" }, "30d")
1833
+ ))), loading && /* @__PURE__ */ import_react9.default.createElement(import_material8.Box, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: 120 }, /* @__PURE__ */ import_react9.default.createElement(import_material8.CircularProgress, { size: 32 })), !loading && totalFailure && /* @__PURE__ */ import_react9.default.createElement(import_material8.Alert, { severity: "error", sx: { mt: 1 } }, usageError ?? "Failed to load usage data"), !loading && !totalFailure && /* @__PURE__ */ import_react9.default.createElement(import_react9.default.Fragment, null, partialFailure && /* @__PURE__ */ import_react9.default.createElement(import_material8.Alert, { severity: "warning", sx: { mt: 1, mb: 1 } }, usageError ? `Usage data unavailable (${usageError}).` : "", keysError ? ` Key list unavailable (${keysError}).` : "", " Showing what loaded."), /* @__PURE__ */ import_react9.default.createElement(import_material8.Grid, { container: true, spacing: 2, sx: { mb: hasSparkline ? 1.5 : 0 } }, /* @__PURE__ */ import_react9.default.createElement(import_material8.Grid, { item: true, xs: 6 }, /* @__PURE__ */ import_react9.default.createElement(Kpi, { label: "USD Spent", value: fmtUsd(usage?.total_spend ?? 0) })), /* @__PURE__ */ import_react9.default.createElement(import_material8.Grid, { item: true, xs: 6 }, /* @__PURE__ */ import_react9.default.createElement(Kpi, { label: "Tokens In", value: fmtInt(usage?.prompt_tokens ?? 0) })), /* @__PURE__ */ import_react9.default.createElement(import_material8.Grid, { item: true, xs: 6 }, /* @__PURE__ */ import_react9.default.createElement(Kpi, { label: "Tokens Out", value: fmtInt(usage?.completion_tokens ?? 0) })), /* @__PURE__ */ import_react9.default.createElement(import_material8.Grid, { item: true, xs: 6 }, /* @__PURE__ */ import_react9.default.createElement(Kpi, { label: "Keys", value: fmtInt(keys.length) }))), hasSparkline && /* @__PURE__ */ import_react9.default.createElement(import_material8.Box, { height: 120 }, /* @__PURE__ */ import_react9.default.createElement(import_recharts3.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react9.default.createElement(import_recharts3.AreaChart, { data: dailyData, margin: { top: 4, right: 0, bottom: 0, left: 0 } }, /* @__PURE__ */ import_react9.default.createElement(
1671
1834
  import_recharts3.Area,
1672
1835
  {
1673
1836
  type: "monotone",