@acarmisc/backstage-plugin-litellm 0.10.1 → 0.11.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/components/KeysTable.d.ts +7 -0
- package/dist/index.cjs.js +75 -18
- package/dist/index.cjs.js.map +2 -2
- package/dist/index.esm.js +75 -18
- package/dist/index.esm.js.map +2 -2
- package/dist/types.d.ts +8 -0
- package/package.json +1 -1
|
@@ -4,6 +4,13 @@ interface KeysTableProps {
|
|
|
4
4
|
keys: VirtualKey[];
|
|
5
5
|
models: ModelInfo[];
|
|
6
6
|
teams: TeamInfo[];
|
|
7
|
+
/** Current user's LiteLLM user id, used to pre-fill a default key alias. */
|
|
8
|
+
username?: string;
|
|
9
|
+
/** Controls for the "Generate New Key" form; see litellm.keyGeneration in app-config.yaml. */
|
|
10
|
+
keyGenerationSettings?: {
|
|
11
|
+
allowUnlimitedBudget: boolean;
|
|
12
|
+
teamRequired: boolean;
|
|
13
|
+
};
|
|
7
14
|
loading: boolean;
|
|
8
15
|
onGenerateKey: (request: GenerateKeyRequest) => Promise<GenerateKeyResponse>;
|
|
9
16
|
onUpdateKey: (keyId: string, request: UpdateKeyRequest) => 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
|
-
|
|
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) {
|
|
@@ -275,7 +276,7 @@ response = client.chat.completions.create(
|
|
|
275
276
|
print(response.choices[0].message.content)`
|
|
276
277
|
};
|
|
277
278
|
}
|
|
278
|
-
var import_react2, import_material2, import_icons_material, maskKey, shortKeyId, formatDate, emptyForm, keyToEditForm, KeysTable, SnippetTabs;
|
|
279
|
+
var import_react2, import_material2, import_icons_material, maskKey, generateDefaultAlias, shortKeyId, formatDate, emptyForm, keyToEditForm, KeysTable, SnippetTabs;
|
|
279
280
|
var init_KeysTable = __esm({
|
|
280
281
|
"src/components/KeysTable.tsx"() {
|
|
281
282
|
"use strict";
|
|
@@ -288,6 +289,11 @@ var init_KeysTable = __esm({
|
|
|
288
289
|
if (key.length <= 8) return "***";
|
|
289
290
|
return `${key.slice(0, 4)}...${key.slice(-4)}`;
|
|
290
291
|
};
|
|
292
|
+
generateDefaultAlias = (username) => {
|
|
293
|
+
const base = (username || "user").split("@")[0].toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "user";
|
|
294
|
+
const hash = Math.random().toString(36).slice(2, 8);
|
|
295
|
+
return `${base}-${hash}`;
|
|
296
|
+
};
|
|
291
297
|
shortKeyId = (token) => {
|
|
292
298
|
if (!token) return "-";
|
|
293
299
|
if (token.length <= 16) return token;
|
|
@@ -321,6 +327,8 @@ var init_KeysTable = __esm({
|
|
|
321
327
|
keys,
|
|
322
328
|
models,
|
|
323
329
|
teams,
|
|
330
|
+
username,
|
|
331
|
+
keyGenerationSettings,
|
|
324
332
|
loading,
|
|
325
333
|
onGenerateKey,
|
|
326
334
|
onUpdateKey,
|
|
@@ -354,12 +362,23 @@ var init_KeysTable = __esm({
|
|
|
354
362
|
(k) => (k.key_alias ?? "").toLowerCase().includes(q) || k.models?.some((m) => m.toLowerCase().includes(q))
|
|
355
363
|
);
|
|
356
364
|
}, [keys, filterText]);
|
|
357
|
-
const
|
|
365
|
+
const allowUnlimitedBudget = keyGenerationSettings?.allowUnlimitedBudget ?? false;
|
|
366
|
+
const teamRequired = keyGenerationSettings?.teamRequired ?? true;
|
|
358
367
|
const selectedTeam = teams.find((t) => t.team_id === formData.team_id) ?? null;
|
|
368
|
+
const availableModels = (0, import_react2.useMemo)(() => {
|
|
369
|
+
const teamModels = selectedTeam?.models;
|
|
370
|
+
if (teamModels && teamModels.length > 0) {
|
|
371
|
+
const allowed = new Set(teamModels);
|
|
372
|
+
return models.filter((m) => allowed.has(m.model_name));
|
|
373
|
+
}
|
|
374
|
+
return models;
|
|
375
|
+
}, [models, selectedTeam]);
|
|
376
|
+
const selectedModels = availableModels.filter((m) => (formData.models || []).includes(m.model_name));
|
|
359
377
|
const editSelectedModels = models.filter((m) => (editForm.models || []).includes(m.model_name));
|
|
360
378
|
const aliasError = !(formData.alias || "").trim();
|
|
379
|
+
const teamError = teamRequired && !formData.team_id;
|
|
361
380
|
const budgetInvalid = !unlimitedBudget && (formData.max_budget === void 0 || formData.max_budget === null || formData.max_budget <= 0);
|
|
362
|
-
const canGenerate = !aliasError && !budgetInvalid && !submitting;
|
|
381
|
+
const canGenerate = !aliasError && !teamError && !budgetInvalid && !submitting;
|
|
363
382
|
const budgetEstimate = (0, import_react2.useMemo)(() => {
|
|
364
383
|
if (unlimitedBudget || budgetInvalid) return null;
|
|
365
384
|
const inputCosts = selectedModels.map((m) => m.input_cost_per_token).filter((c) => typeof c === "number" && c > 0);
|
|
@@ -392,6 +411,10 @@ var init_KeysTable = __esm({
|
|
|
392
411
|
setSubmitting(false);
|
|
393
412
|
}
|
|
394
413
|
};
|
|
414
|
+
const handleOpenGenerate = () => {
|
|
415
|
+
setFormData({ ...emptyForm(), alias: generateDefaultAlias(username) });
|
|
416
|
+
setGenerateModalOpen(true);
|
|
417
|
+
};
|
|
395
418
|
const handleCloseModal = () => {
|
|
396
419
|
setGenerateModalOpen(false);
|
|
397
420
|
setNewKeyValue(null);
|
|
@@ -509,7 +532,7 @@ var init_KeysTable = __esm({
|
|
|
509
532
|
variant: "contained",
|
|
510
533
|
color: "primary",
|
|
511
534
|
startIcon: /* @__PURE__ */ import_react2.default.createElement(import_icons_material.Add, null),
|
|
512
|
-
onClick:
|
|
535
|
+
onClick: handleOpenGenerate
|
|
513
536
|
},
|
|
514
537
|
"Generate New Key"
|
|
515
538
|
))), /* @__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,7 +542,7 @@ var init_KeysTable = __esm({
|
|
|
519
542
|
color: "primary",
|
|
520
543
|
size: "small",
|
|
521
544
|
startIcon: /* @__PURE__ */ import_react2.default.createElement(import_icons_material.Add, null),
|
|
522
|
-
onClick:
|
|
545
|
+
onClick: handleOpenGenerate
|
|
523
546
|
},
|
|
524
547
|
"Generate Your First Key"
|
|
525
548
|
)))) : filteredKeys.map((key) => {
|
|
@@ -609,36 +632,50 @@ var init_KeysTable = __esm({
|
|
|
609
632
|
/* @__PURE__ */ import_react2.default.createElement(import_material2.MenuItem, { value: "30d" }, "30 Days"),
|
|
610
633
|
/* @__PURE__ */ import_react2.default.createElement(import_material2.MenuItem, { value: "90d" }, "90 Days"),
|
|
611
634
|
/* @__PURE__ */ import_react2.default.createElement(import_material2.MenuItem, { value: "1y" }, "1 Year")
|
|
612
|
-
), teams.length > 0
|
|
635
|
+
), teams.length > 0 ? /* @__PURE__ */ import_react2.default.createElement(
|
|
613
636
|
import_material2.Autocomplete,
|
|
614
637
|
{
|
|
615
638
|
options: teams,
|
|
616
639
|
getOptionLabel: (t) => t.team_alias || t.team_id,
|
|
617
640
|
value: selectedTeam,
|
|
618
|
-
onChange: (_e, team) =>
|
|
641
|
+
onChange: (_e, team) => {
|
|
642
|
+
const teamModels = team?.models;
|
|
643
|
+
const restrictedModels = teamModels && teamModels.length > 0 ? (formData.models || []).filter((m) => teamModels.includes(m)) : formData.models;
|
|
644
|
+
setFormData({ ...formData, team_id: team?.team_id, models: restrictedModels });
|
|
645
|
+
},
|
|
619
646
|
renderInput: (params) => /* @__PURE__ */ import_react2.default.createElement(
|
|
620
647
|
import_material2.TextField,
|
|
621
648
|
{
|
|
622
649
|
...params,
|
|
623
650
|
label: "Team",
|
|
624
|
-
|
|
651
|
+
error: teamError,
|
|
652
|
+
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",
|
|
653
|
+
required: teamRequired,
|
|
625
654
|
fullWidth: true
|
|
626
655
|
}
|
|
627
656
|
)
|
|
628
657
|
}
|
|
629
|
-
),
|
|
658
|
+
) : teamRequired ? /* @__PURE__ */ import_react2.default.createElement(import_material2.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_react2.default.createElement(
|
|
630
659
|
import_material2.Autocomplete,
|
|
631
660
|
{
|
|
632
661
|
multiple: true,
|
|
633
|
-
options:
|
|
662
|
+
options: availableModels,
|
|
634
663
|
groupBy: (m) => m.mode || "other",
|
|
635
664
|
getOptionLabel: (m) => m.model_name,
|
|
636
665
|
value: selectedModels,
|
|
637
666
|
onChange: (_e, selected) => setFormData({ ...formData, models: selected.map((m) => m.model_name) }),
|
|
638
667
|
renderOption: (props, m) => /* @__PURE__ */ import_react2.default.createElement("li", { ...props }, modelOption(m)),
|
|
639
|
-
renderInput: (params) => /* @__PURE__ */ import_react2.default.createElement(
|
|
668
|
+
renderInput: (params) => /* @__PURE__ */ import_react2.default.createElement(
|
|
669
|
+
import_material2.TextField,
|
|
670
|
+
{
|
|
671
|
+
...params,
|
|
672
|
+
label: "Models",
|
|
673
|
+
helperText: selectedTeam ? "Leave empty to allow all models available to this team" : "Leave empty to allow all models",
|
|
674
|
+
fullWidth: true
|
|
675
|
+
}
|
|
676
|
+
)
|
|
640
677
|
}
|
|
641
|
-
), /* @__PURE__ */ import_react2.default.createElement(
|
|
678
|
+
), allowUnlimitedBudget && /* @__PURE__ */ import_react2.default.createElement(
|
|
642
679
|
import_material2.FormControlLabel,
|
|
643
680
|
{
|
|
644
681
|
control: /* @__PURE__ */ import_react2.default.createElement(
|
|
@@ -677,6 +714,7 @@ var init_KeysTable = __esm({
|
|
|
677
714
|
type: "number",
|
|
678
715
|
value: formData.tpm_limit ?? "",
|
|
679
716
|
onChange: (e) => setFormData({ ...formData, tpm_limit: e.target.value ? Number(e.target.value) : void 0 }),
|
|
717
|
+
helperText: "Max tokens per minute this key can consume across all models. Leave blank for no limit.",
|
|
680
718
|
fullWidth: true
|
|
681
719
|
}
|
|
682
720
|
), /* @__PURE__ */ import_react2.default.createElement(
|
|
@@ -686,6 +724,7 @@ var init_KeysTable = __esm({
|
|
|
686
724
|
type: "number",
|
|
687
725
|
value: formData.rpm_limit ?? "",
|
|
688
726
|
onChange: (e) => setFormData({ ...formData, rpm_limit: e.target.value ? Number(e.target.value) : void 0 }),
|
|
727
|
+
helperText: "Max requests per minute this key can make across all models. Leave blank for no limit.",
|
|
689
728
|
fullWidth: true
|
|
690
729
|
}
|
|
691
730
|
))), /* @__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(
|
|
@@ -733,6 +772,7 @@ var init_KeysTable = __esm({
|
|
|
733
772
|
type: "number",
|
|
734
773
|
value: editForm.tpm_limit ?? "",
|
|
735
774
|
onChange: (e) => setEditForm({ ...editForm, tpm_limit: e.target.value ? Number(e.target.value) : void 0 }),
|
|
775
|
+
helperText: "Max tokens per minute this key can consume across all models. Leave blank for no limit.",
|
|
736
776
|
fullWidth: true
|
|
737
777
|
}
|
|
738
778
|
), /* @__PURE__ */ import_react2.default.createElement(
|
|
@@ -742,6 +782,7 @@ var init_KeysTable = __esm({
|
|
|
742
782
|
type: "number",
|
|
743
783
|
value: editForm.rpm_limit ?? "",
|
|
744
784
|
onChange: (e) => setEditForm({ ...editForm, rpm_limit: e.target.value ? Number(e.target.value) : void 0 }),
|
|
785
|
+
helperText: "Max requests per minute this key can make across all models. Leave blank for no limit.",
|
|
745
786
|
fullWidth: true
|
|
746
787
|
}
|
|
747
788
|
), /* @__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(
|
|
@@ -1104,7 +1145,15 @@ var init_TeamUsage = __esm({
|
|
|
1104
1145
|
color: isOver ? "error" : isNear ? "warning" : "primary",
|
|
1105
1146
|
sx: { height: 6, borderRadius: 1 }
|
|
1106
1147
|
}
|
|
1107
|
-
)), /* @__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,
|
|
1148
|
+
)), /* @__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(
|
|
1149
|
+
import_material4.Typography,
|
|
1150
|
+
{
|
|
1151
|
+
variant: "caption",
|
|
1152
|
+
color: "text.secondary",
|
|
1153
|
+
sx: { fontFamily: "monospace" }
|
|
1154
|
+
},
|
|
1155
|
+
m.user_id
|
|
1156
|
+
)) : /* @__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(
|
|
1108
1157
|
import_material4.Chip,
|
|
1109
1158
|
{
|
|
1110
1159
|
label: m.role,
|
|
@@ -1317,6 +1366,7 @@ var init_LiteLLMPage = __esm({
|
|
|
1317
1366
|
() => api.getTeams().catch(() => []),
|
|
1318
1367
|
[api]
|
|
1319
1368
|
);
|
|
1369
|
+
const { value: liteLlmConfig } = (0, import_react_use2.useAsync)(() => api.getConfig(), [api]);
|
|
1320
1370
|
const teams = (0, import_react6.useMemo)(() => {
|
|
1321
1371
|
if (!allTeams?.length) return [];
|
|
1322
1372
|
if (!userInfo) return allTeams;
|
|
@@ -1368,10 +1418,15 @@ var init_LiteLLMPage = __esm({
|
|
|
1368
1418
|
}, [allModels, userInfo, teams]);
|
|
1369
1419
|
const handleGenerateKey = (0, import_react6.useCallback)(
|
|
1370
1420
|
async (request) => {
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1421
|
+
try {
|
|
1422
|
+
const response = await api.generateKey(request);
|
|
1423
|
+
setSnackbar({ message: "Key generated successfully", severity: "success" });
|
|
1424
|
+
refreshKeys();
|
|
1425
|
+
return response;
|
|
1426
|
+
} catch (e) {
|
|
1427
|
+
setSnackbar({ message: `Failed to generate key: ${e.message}`, severity: "error" });
|
|
1428
|
+
throw e;
|
|
1429
|
+
}
|
|
1375
1430
|
},
|
|
1376
1431
|
[api, refreshKeys]
|
|
1377
1432
|
);
|
|
@@ -1490,6 +1545,8 @@ var init_LiteLLMPage = __esm({
|
|
|
1490
1545
|
keys: keys ?? [],
|
|
1491
1546
|
models: allowedModels,
|
|
1492
1547
|
teams: teams ?? [],
|
|
1548
|
+
username: userInfo.user_id,
|
|
1549
|
+
keyGenerationSettings: liteLlmConfig?.keyGeneration,
|
|
1493
1550
|
loading: keysLoading || modelsLoading,
|
|
1494
1551
|
onGenerateKey: handleGenerateKey,
|
|
1495
1552
|
onUpdateKey: handleUpdateKey,
|