@acarmisc/backstage-plugin-litellm 0.10.0 → 0.10.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/api.d.ts CHANGED
@@ -8,7 +8,6 @@ export interface LiteLlmApiInterface {
8
8
  deleteKey(keyId: string): Promise<{
9
9
  success: boolean;
10
10
  }>;
11
- rotateKey(keyId: string): Promise<GenerateKeyResponse>;
12
11
  blockKey(keyId: string): Promise<void>;
13
12
  unblockKey(keyId: string): Promise<void>;
14
13
  resetKeySpend(keyId: string): Promise<void>;
@@ -40,7 +39,6 @@ export declare class LiteLlmApi implements LiteLlmApiInterface {
40
39
  deleteKey(keyId: string): Promise<{
41
40
  success: boolean;
42
41
  }>;
43
- rotateKey(keyId: string): Promise<GenerateKeyResponse>;
44
42
  blockKey(keyId: string): Promise<void>;
45
43
  unblockKey(keyId: string): Promise<void>;
46
44
  resetKeySpend(keyId: string): Promise<void>;
@@ -7,7 +7,6 @@ interface KeysTableProps {
7
7
  loading: boolean;
8
8
  onGenerateKey: (request: GenerateKeyRequest) => Promise<GenerateKeyResponse>;
9
9
  onUpdateKey: (keyId: string, request: UpdateKeyRequest) => Promise<void>;
10
- onRotateKey: (keyId: string) => Promise<GenerateKeyResponse>;
11
10
  onBlockKey: (keyId: string) => Promise<void>;
12
11
  onUnblockKey: (keyId: string) => Promise<void>;
13
12
  onResetKeySpend: (keyId: string) => Promise<void>;
package/dist/index.cjs.js CHANGED
@@ -72,7 +72,8 @@ var init_api = __esm({
72
72
  } catch {
73
73
  body = await response.text().catch(() => "");
74
74
  }
75
- throw new ApiError(`${response.status} ${response.statusText}`, response.status, body);
75
+ const message = (typeof body === "object" && body && typeof body.error === "string" ? body.error : void 0) ?? `${response.status} ${response.statusText}`;
76
+ throw new ApiError(message, response.status, body);
76
77
  }
77
78
  }
78
79
  async get(path, params) {
@@ -118,9 +119,6 @@ var init_api = __esm({
118
119
  async deleteKey(keyId) {
119
120
  return this.del(`/keys/${encodeURIComponent(keyId)}`);
120
121
  }
121
- async rotateKey(keyId) {
122
- return this.post(`/keys/${encodeURIComponent(keyId)}/regenerate`, {});
123
- }
124
122
  async blockKey(keyId) {
125
123
  await this.post(`/keys/${encodeURIComponent(keyId)}/block`, {});
126
124
  }
@@ -311,9 +309,7 @@ var init_KeysTable = __esm({
311
309
  tpm_limit: void 0,
312
310
  rpm_limit: void 0,
313
311
  team_id: void 0,
314
- key_type: "llm_api",
315
- auto_rotate: false,
316
- rotation_interval_days: void 0
312
+ key_type: "llm_api"
317
313
  });
318
314
  keyToEditForm = (k) => ({
319
315
  key_alias: k.key_alias ?? "",
@@ -329,7 +325,6 @@ var init_KeysTable = __esm({
329
325
  loading,
330
326
  onGenerateKey,
331
327
  onUpdateKey,
332
- onRotateKey,
333
328
  onBlockKey,
334
329
  onUnblockKey,
335
330
  onResetKeySpend,
@@ -347,8 +342,6 @@ var init_KeysTable = __esm({
347
342
  const [editingKey, setEditingKey] = (0, import_react2.useState)(null);
348
343
  const [editForm, setEditForm] = (0, import_react2.useState)({});
349
344
  const [editSubmitting, setEditSubmitting] = (0, import_react2.useState)(false);
350
- const [rotatingKeyId, setRotatingKeyId] = (0, import_react2.useState)(null);
351
- const [rotatedKeyValue, setRotatedKeyValue] = (0, import_react2.useState)(null);
352
345
  const [blockingKeyId, setBlockingKeyId] = (0, import_react2.useState)(null);
353
346
  const [deleteConfirmId, setDeleteConfirmId] = (0, import_react2.useState)(null);
354
347
  const [deleteSubmitting, setDeleteSubmitting] = (0, import_react2.useState)(false);
@@ -428,17 +421,6 @@ var init_KeysTable = __esm({
428
421
  setEditSubmitting(false);
429
422
  }
430
423
  };
431
- const handleRotate = async (keyId) => {
432
- setRotatingKeyId(keyId);
433
- try {
434
- const response = await onRotateKey(keyId);
435
- setRotatedKeyValue(response.key);
436
- } catch (error) {
437
- console.error("Failed to rotate key:", error);
438
- } finally {
439
- setRotatingKeyId(null);
440
- }
441
- };
442
424
  const handleToggleBlock = async (key) => {
443
425
  const keyId = key.token ?? key.key;
444
426
  setBlockingKeyId(keyId);
@@ -543,7 +525,6 @@ var init_KeysTable = __esm({
543
525
  "Generate Your First Key"
544
526
  )))) : filteredKeys.map((key) => {
545
527
  const keyId = key.token ?? key.key;
546
- const isRotating = rotatingKeyId === keyId;
547
528
  const isBlocking = blockingKeyId === keyId;
548
529
  return /* @__PURE__ */ import_react2.default.createElement(import_material2.TableRow, { key: keyId, sx: key.blocked ? { bgcolor: "action.disabledBackground" } : void 0 }, /* @__PURE__ */ import_react2.default.createElement(import_material2.TableCell, null, /* @__PURE__ */ import_react2.default.createElement(import_material2.Box, { display: "flex", alignItems: "center", gap: 0.5 }, key.key_alias || "-", key.blocked && /* @__PURE__ */ import_react2.default.createElement(import_material2.Chip, { label: "Blocked", color: "error", size: "small" }))), /* @__PURE__ */ import_react2.default.createElement(import_material2.TableCell, null, /* @__PURE__ */ import_react2.default.createElement(import_material2.Box, { display: "flex", alignItems: "center", gap: 0.5 }, /* @__PURE__ */ import_react2.default.createElement(
549
530
  import_material2.Typography,
@@ -562,14 +543,6 @@ var init_KeysTable = __esm({
562
543
  },
563
544
  shortKeyId(keyId)
564
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(
565
- import_material2.IconButton,
566
- {
567
- onClick: () => handleRotate(keyId),
568
- disabled: isRotating,
569
- title: "Rotate key \u2014 generates a new secret, same settings"
570
- },
571
- isRotating ? /* @__PURE__ */ import_react2.default.createElement(import_material2.CircularProgress, { size: 18 }) : /* @__PURE__ */ import_react2.default.createElement(import_icons_material.Autorenew, { fontSize: "small" })
572
- ), /* @__PURE__ */ import_react2.default.createElement(
573
546
  import_material2.IconButton,
574
547
  {
575
548
  onClick: () => handleToggleBlock(key),
@@ -716,36 +689,7 @@ var init_KeysTable = __esm({
716
689
  onChange: (e) => setFormData({ ...formData, rpm_limit: e.target.value ? Number(e.target.value) : void 0 }),
717
690
  fullWidth: true
718
691
  }
719
- ), /* @__PURE__ */ import_react2.default.createElement(
720
- import_material2.FormControlLabel,
721
- {
722
- control: /* @__PURE__ */ import_react2.default.createElement(
723
- import_material2.Checkbox,
724
- {
725
- checked: !!formData.auto_rotate,
726
- onChange: (e) => setFormData({
727
- ...formData,
728
- auto_rotate: e.target.checked,
729
- rotation_interval_days: e.target.checked ? formData.rotation_interval_days ?? 90 : void 0
730
- })
731
- }
732
- ),
733
- label: "Auto-rotate (LiteLLM rotates the secret on a schedule)"
734
- }
735
- ), formData.auto_rotate && /* @__PURE__ */ import_react2.default.createElement(
736
- import_material2.TextField,
737
- {
738
- label: "Rotate every (days)",
739
- type: "number",
740
- value: formData.rotation_interval_days ?? "",
741
- onChange: (e) => setFormData({
742
- ...formData,
743
- rotation_interval_days: e.target.value ? Number(e.target.value) : void 0
744
- }),
745
- helperText: "LiteLLM enforces this interval server-side; the old secret stops working the moment it rotates.",
746
- fullWidth: true
747
- }
748
- ))), /* @__PURE__ */ import_react2.default.createElement(import_material2.DialogActions, null, /* @__PURE__ */ import_react2.default.createElement(import_material2.Button, { onClick: handleCloseModal }, newKeyValue ? "Done" : "Cancel"), !newKeyValue && /* @__PURE__ */ import_react2.default.createElement(
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(
749
693
  import_material2.Button,
750
694
  {
751
695
  onClick: handleGenerate,
@@ -754,7 +698,7 @@ var init_KeysTable = __esm({
754
698
  disabled: !canGenerate
755
699
  },
756
700
  submitting ? /* @__PURE__ */ import_react2.default.createElement(import_material2.CircularProgress, { size: 24 }) : "Generate"
757
- ), newKeyValue && /* @__PURE__ */ import_react2.default.createElement(import_material2.Button, { onClick: handleCloseModal, variant: "contained", color: "success" }, "Done"))), /* @__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(
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(
758
702
  import_material2.TextField,
759
703
  {
760
704
  label: "Alias",
@@ -820,26 +764,7 @@ var init_KeysTable = __esm({
820
764
  onClick: handleResetSpend
821
765
  },
822
766
  resetSpendSubmitting ? /* @__PURE__ */ import_react2.default.createElement(import_material2.CircularProgress, { size: 16 }) : "Confirm"
823
- ), /* @__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: !!rotatedKeyValue, onClose: () => setRotatedKeyValue(null), maxWidth: "sm", fullWidth: true }, /* @__PURE__ */ import_react2.default.createElement(import_material2.DialogTitle, null, "Key Rotated"), /* @__PURE__ */ import_react2.default.createElement(import_material2.DialogContent, null, /* @__PURE__ */ import_react2.default.createElement(import_material2.Typography, { variant: "body2", color: "text.secondary", gutterBottom: true }, "The old secret is now invalid. Copy the new one \u2014 you won't see it again."), /* @__PURE__ */ import_react2.default.createElement(
824
- import_material2.Box,
825
- {
826
- display: "flex",
827
- alignItems: "center",
828
- gap: 1,
829
- mt: 2,
830
- p: 2,
831
- sx: { backgroundColor: "action.hover", border: "1px solid", borderColor: "divider", borderRadius: 1 }
832
- },
833
- /* @__PURE__ */ import_react2.default.createElement(
834
- import_material2.Typography,
835
- {
836
- component: "code",
837
- sx: { fontFamily: "monospace", wordBreak: "break-all", flex: 1 }
838
- },
839
- rotatedKeyValue
840
- ),
841
- /* @__PURE__ */ import_react2.default.createElement(import_material2.IconButton, { onClick: () => copyToClipboard(rotatedKeyValue) }, /* @__PURE__ */ import_react2.default.createElement(import_icons_material.ContentCopy, null))
842
- )), /* @__PURE__ */ import_react2.default.createElement(import_material2.DialogActions, null, /* @__PURE__ */ import_react2.default.createElement(import_material2.Button, { onClick: () => setRotatedKeyValue(null), variant: "contained", color: "success" }, "Done"))), /* @__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(
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(
843
768
  import_material2.Button,
844
769
  {
845
770
  onClick: handleConfirmDelete,
@@ -1180,7 +1105,15 @@ var init_TeamUsage = __esm({
1180
1105
  color: isOver ? "error" : isNear ? "warning" : "primary",
1181
1106
  sx: { height: 6, borderRadius: 1 }
1182
1107
  }
1183
- )), /* @__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, { sx: { fontFamily: "monospace", fontSize: 12 } }, m.user_id), /* @__PURE__ */ import_react4.default.createElement(import_material4.TableCell, null, /* @__PURE__ */ import_react4.default.createElement(
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,
1110
+ {
1111
+ variant: "caption",
1112
+ color: "text.secondary",
1113
+ sx: { fontFamily: "monospace" }
1114
+ },
1115
+ 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(
1184
1117
  import_material4.Chip,
1185
1118
  {
1186
1119
  label: m.role,
@@ -1444,10 +1377,15 @@ var init_LiteLLMPage = __esm({
1444
1377
  }, [allModels, userInfo, teams]);
1445
1378
  const handleGenerateKey = (0, import_react6.useCallback)(
1446
1379
  async (request) => {
1447
- const response = await api.generateKey(request);
1448
- setSnackbar({ message: "Key generated successfully", severity: "success" });
1449
- refreshKeys();
1450
- return response;
1380
+ try {
1381
+ const response = await api.generateKey(request);
1382
+ setSnackbar({ message: "Key generated successfully", severity: "success" });
1383
+ refreshKeys();
1384
+ return response;
1385
+ } catch (e) {
1386
+ setSnackbar({ message: `Failed to generate key: ${e.message}`, severity: "error" });
1387
+ throw e;
1388
+ }
1451
1389
  },
1452
1390
  [api, refreshKeys]
1453
1391
  );
@@ -1500,15 +1438,6 @@ var init_LiteLLMPage = __esm({
1500
1438
  },
1501
1439
  [api, refreshKeys]
1502
1440
  );
1503
- const handleRotateKey = (0, import_react6.useCallback)(
1504
- async (keyId) => {
1505
- const response = await api.rotateKey(keyId);
1506
- setSnackbar({ message: "Key rotated \u2014 copy the new secret now", severity: "success" });
1507
- refreshKeys();
1508
- return response;
1509
- },
1510
- [api, refreshKeys]
1511
- );
1512
1441
  const handleDeleteKey = (0, import_react6.useCallback)(
1513
1442
  async (keyId) => {
1514
1443
  try {
@@ -1578,7 +1507,6 @@ var init_LiteLLMPage = __esm({
1578
1507
  loading: keysLoading || modelsLoading,
1579
1508
  onGenerateKey: handleGenerateKey,
1580
1509
  onUpdateKey: handleUpdateKey,
1581
- onRotateKey: handleRotateKey,
1582
1510
  onBlockKey: handleBlockKey,
1583
1511
  onUnblockKey: handleUnblockKey,
1584
1512
  onResetKeySpend: handleResetKeySpend,