@acarmisc/backstage-plugin-litellm 0.10.2 → 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 +55 -12
- package/dist/index.cjs.js.map +2 -2
- package/dist/index.esm.js +55 -12
- package/dist/index.esm.js.map +2 -2
- package/dist/types.d.ts +7 -0
- package/package.json +1 -1
package/dist/index.esm.js
CHANGED
|
@@ -284,7 +284,7 @@ response = client.chat.completions.create(
|
|
|
284
284
|
print(response.choices[0].message.content)`
|
|
285
285
|
};
|
|
286
286
|
}
|
|
287
|
-
var maskKey, shortKeyId, formatDate, emptyForm, keyToEditForm, KeysTable, SnippetTabs;
|
|
287
|
+
var maskKey, generateDefaultAlias, shortKeyId, formatDate, emptyForm, keyToEditForm, KeysTable, SnippetTabs;
|
|
288
288
|
var init_KeysTable = __esm({
|
|
289
289
|
"src/components/KeysTable.tsx"() {
|
|
290
290
|
"use strict";
|
|
@@ -294,6 +294,11 @@ var init_KeysTable = __esm({
|
|
|
294
294
|
if (key.length <= 8) return "***";
|
|
295
295
|
return `${key.slice(0, 4)}...${key.slice(-4)}`;
|
|
296
296
|
};
|
|
297
|
+
generateDefaultAlias = (username) => {
|
|
298
|
+
const base = (username || "user").split("@")[0].toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "user";
|
|
299
|
+
const hash = Math.random().toString(36).slice(2, 8);
|
|
300
|
+
return `${base}-${hash}`;
|
|
301
|
+
};
|
|
297
302
|
shortKeyId = (token) => {
|
|
298
303
|
if (!token) return "-";
|
|
299
304
|
if (token.length <= 16) return token;
|
|
@@ -327,6 +332,8 @@ var init_KeysTable = __esm({
|
|
|
327
332
|
keys,
|
|
328
333
|
models,
|
|
329
334
|
teams,
|
|
335
|
+
username,
|
|
336
|
+
keyGenerationSettings,
|
|
330
337
|
loading,
|
|
331
338
|
onGenerateKey,
|
|
332
339
|
onUpdateKey,
|
|
@@ -360,12 +367,23 @@ var init_KeysTable = __esm({
|
|
|
360
367
|
(k) => (k.key_alias ?? "").toLowerCase().includes(q) || k.models?.some((m) => m.toLowerCase().includes(q))
|
|
361
368
|
);
|
|
362
369
|
}, [keys, filterText]);
|
|
363
|
-
const
|
|
370
|
+
const allowUnlimitedBudget = keyGenerationSettings?.allowUnlimitedBudget ?? false;
|
|
371
|
+
const teamRequired = keyGenerationSettings?.teamRequired ?? true;
|
|
364
372
|
const selectedTeam = teams.find((t) => t.team_id === formData.team_id) ?? null;
|
|
373
|
+
const availableModels = useMemo(() => {
|
|
374
|
+
const teamModels = selectedTeam?.models;
|
|
375
|
+
if (teamModels && teamModels.length > 0) {
|
|
376
|
+
const allowed = new Set(teamModels);
|
|
377
|
+
return models.filter((m) => allowed.has(m.model_name));
|
|
378
|
+
}
|
|
379
|
+
return models;
|
|
380
|
+
}, [models, selectedTeam]);
|
|
381
|
+
const selectedModels = availableModels.filter((m) => (formData.models || []).includes(m.model_name));
|
|
365
382
|
const editSelectedModels = models.filter((m) => (editForm.models || []).includes(m.model_name));
|
|
366
383
|
const aliasError = !(formData.alias || "").trim();
|
|
384
|
+
const teamError = teamRequired && !formData.team_id;
|
|
367
385
|
const budgetInvalid = !unlimitedBudget && (formData.max_budget === void 0 || formData.max_budget === null || formData.max_budget <= 0);
|
|
368
|
-
const canGenerate = !aliasError && !budgetInvalid && !submitting;
|
|
386
|
+
const canGenerate = !aliasError && !teamError && !budgetInvalid && !submitting;
|
|
369
387
|
const budgetEstimate = useMemo(() => {
|
|
370
388
|
if (unlimitedBudget || budgetInvalid) return null;
|
|
371
389
|
const inputCosts = selectedModels.map((m) => m.input_cost_per_token).filter((c) => typeof c === "number" && c > 0);
|
|
@@ -398,6 +416,10 @@ var init_KeysTable = __esm({
|
|
|
398
416
|
setSubmitting(false);
|
|
399
417
|
}
|
|
400
418
|
};
|
|
419
|
+
const handleOpenGenerate = () => {
|
|
420
|
+
setFormData({ ...emptyForm(), alias: generateDefaultAlias(username) });
|
|
421
|
+
setGenerateModalOpen(true);
|
|
422
|
+
};
|
|
401
423
|
const handleCloseModal = () => {
|
|
402
424
|
setGenerateModalOpen(false);
|
|
403
425
|
setNewKeyValue(null);
|
|
@@ -515,7 +537,7 @@ var init_KeysTable = __esm({
|
|
|
515
537
|
variant: "contained",
|
|
516
538
|
color: "primary",
|
|
517
539
|
startIcon: /* @__PURE__ */ React2.createElement(Add, null),
|
|
518
|
-
onClick:
|
|
540
|
+
onClick: handleOpenGenerate
|
|
519
541
|
},
|
|
520
542
|
"Generate New Key"
|
|
521
543
|
))), /* @__PURE__ */ React2.createElement(TableContainer, null, /* @__PURE__ */ React2.createElement(Table, null, /* @__PURE__ */ React2.createElement(TableHead, null, /* @__PURE__ */ React2.createElement(TableRow, null, /* @__PURE__ */ React2.createElement(TableCell, null, "Alias"), /* @__PURE__ */ React2.createElement(TableCell, null, "Key ID"), /* @__PURE__ */ React2.createElement(TableCell, null, "Created"), /* @__PURE__ */ React2.createElement(TableCell, null, "Expires"), /* @__PURE__ */ React2.createElement(TableCell, null, "Budget"), /* @__PURE__ */ React2.createElement(TableCell, null, "TPM / RPM"), /* @__PURE__ */ React2.createElement(TableCell, null, "Models"), /* @__PURE__ */ React2.createElement(TableCell, { align: "right" }, "Actions"))), /* @__PURE__ */ React2.createElement(TableBody, null, loading ? /* @__PURE__ */ React2.createElement(TableRow, null, /* @__PURE__ */ React2.createElement(TableCell, { colSpan: 8, align: "center" }, /* @__PURE__ */ React2.createElement(CircularProgress, { size: 24 }))) : filteredKeys.length === 0 ? /* @__PURE__ */ React2.createElement(TableRow, null, /* @__PURE__ */ React2.createElement(TableCell, { colSpan: 8, align: "center", sx: { py: 4 } }, filterText ? /* @__PURE__ */ React2.createElement(Typography2, { color: "text.secondary" }, "No keys match filter") : /* @__PURE__ */ React2.createElement(Box2, null, /* @__PURE__ */ React2.createElement(Typography2, { color: "text.secondary", gutterBottom: true }, "No keys yet \u2014 generate your first key to start calling models."), /* @__PURE__ */ React2.createElement(
|
|
@@ -525,7 +547,7 @@ var init_KeysTable = __esm({
|
|
|
525
547
|
color: "primary",
|
|
526
548
|
size: "small",
|
|
527
549
|
startIcon: /* @__PURE__ */ React2.createElement(Add, null),
|
|
528
|
-
onClick:
|
|
550
|
+
onClick: handleOpenGenerate
|
|
529
551
|
},
|
|
530
552
|
"Generate Your First Key"
|
|
531
553
|
)))) : filteredKeys.map((key) => {
|
|
@@ -615,36 +637,50 @@ var init_KeysTable = __esm({
|
|
|
615
637
|
/* @__PURE__ */ React2.createElement(MenuItem, { value: "30d" }, "30 Days"),
|
|
616
638
|
/* @__PURE__ */ React2.createElement(MenuItem, { value: "90d" }, "90 Days"),
|
|
617
639
|
/* @__PURE__ */ React2.createElement(MenuItem, { value: "1y" }, "1 Year")
|
|
618
|
-
), teams.length > 0
|
|
640
|
+
), teams.length > 0 ? /* @__PURE__ */ React2.createElement(
|
|
619
641
|
Autocomplete,
|
|
620
642
|
{
|
|
621
643
|
options: teams,
|
|
622
644
|
getOptionLabel: (t) => t.team_alias || t.team_id,
|
|
623
645
|
value: selectedTeam,
|
|
624
|
-
onChange: (_e, team) =>
|
|
646
|
+
onChange: (_e, team) => {
|
|
647
|
+
const teamModels = team?.models;
|
|
648
|
+
const restrictedModels = teamModels && teamModels.length > 0 ? (formData.models || []).filter((m) => teamModels.includes(m)) : formData.models;
|
|
649
|
+
setFormData({ ...formData, team_id: team?.team_id, models: restrictedModels });
|
|
650
|
+
},
|
|
625
651
|
renderInput: (params) => /* @__PURE__ */ React2.createElement(
|
|
626
652
|
TextField,
|
|
627
653
|
{
|
|
628
654
|
...params,
|
|
629
655
|
label: "Team",
|
|
630
|
-
|
|
656
|
+
error: teamError,
|
|
657
|
+
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",
|
|
658
|
+
required: teamRequired,
|
|
631
659
|
fullWidth: true
|
|
632
660
|
}
|
|
633
661
|
)
|
|
634
662
|
}
|
|
635
|
-
),
|
|
663
|
+
) : teamRequired ? /* @__PURE__ */ React2.createElement(Typography2, { 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__ */ React2.createElement(
|
|
636
664
|
Autocomplete,
|
|
637
665
|
{
|
|
638
666
|
multiple: true,
|
|
639
|
-
options:
|
|
667
|
+
options: availableModels,
|
|
640
668
|
groupBy: (m) => m.mode || "other",
|
|
641
669
|
getOptionLabel: (m) => m.model_name,
|
|
642
670
|
value: selectedModels,
|
|
643
671
|
onChange: (_e, selected) => setFormData({ ...formData, models: selected.map((m) => m.model_name) }),
|
|
644
672
|
renderOption: (props, m) => /* @__PURE__ */ React2.createElement("li", { ...props }, modelOption(m)),
|
|
645
|
-
renderInput: (params) => /* @__PURE__ */ React2.createElement(
|
|
673
|
+
renderInput: (params) => /* @__PURE__ */ React2.createElement(
|
|
674
|
+
TextField,
|
|
675
|
+
{
|
|
676
|
+
...params,
|
|
677
|
+
label: "Models",
|
|
678
|
+
helperText: selectedTeam ? "Leave empty to allow all models available to this team" : "Leave empty to allow all models",
|
|
679
|
+
fullWidth: true
|
|
680
|
+
}
|
|
681
|
+
)
|
|
646
682
|
}
|
|
647
|
-
), /* @__PURE__ */ React2.createElement(
|
|
683
|
+
), allowUnlimitedBudget && /* @__PURE__ */ React2.createElement(
|
|
648
684
|
FormControlLabel,
|
|
649
685
|
{
|
|
650
686
|
control: /* @__PURE__ */ React2.createElement(
|
|
@@ -683,6 +719,7 @@ var init_KeysTable = __esm({
|
|
|
683
719
|
type: "number",
|
|
684
720
|
value: formData.tpm_limit ?? "",
|
|
685
721
|
onChange: (e) => setFormData({ ...formData, tpm_limit: e.target.value ? Number(e.target.value) : void 0 }),
|
|
722
|
+
helperText: "Max tokens per minute this key can consume across all models. Leave blank for no limit.",
|
|
686
723
|
fullWidth: true
|
|
687
724
|
}
|
|
688
725
|
), /* @__PURE__ */ React2.createElement(
|
|
@@ -692,6 +729,7 @@ var init_KeysTable = __esm({
|
|
|
692
729
|
type: "number",
|
|
693
730
|
value: formData.rpm_limit ?? "",
|
|
694
731
|
onChange: (e) => setFormData({ ...formData, rpm_limit: e.target.value ? Number(e.target.value) : void 0 }),
|
|
732
|
+
helperText: "Max requests per minute this key can make across all models. Leave blank for no limit.",
|
|
695
733
|
fullWidth: true
|
|
696
734
|
}
|
|
697
735
|
))), /* @__PURE__ */ React2.createElement(DialogActions, null, newKeyValue ? /* @__PURE__ */ React2.createElement(Button, { onClick: handleCloseModal, variant: "contained", color: "success" }, "Done") : /* @__PURE__ */ React2.createElement(React2.Fragment, null, /* @__PURE__ */ React2.createElement(Button, { onClick: handleCloseModal }, "Cancel"), /* @__PURE__ */ React2.createElement(
|
|
@@ -739,6 +777,7 @@ var init_KeysTable = __esm({
|
|
|
739
777
|
type: "number",
|
|
740
778
|
value: editForm.tpm_limit ?? "",
|
|
741
779
|
onChange: (e) => setEditForm({ ...editForm, tpm_limit: e.target.value ? Number(e.target.value) : void 0 }),
|
|
780
|
+
helperText: "Max tokens per minute this key can consume across all models. Leave blank for no limit.",
|
|
742
781
|
fullWidth: true
|
|
743
782
|
}
|
|
744
783
|
), /* @__PURE__ */ React2.createElement(
|
|
@@ -748,6 +787,7 @@ var init_KeysTable = __esm({
|
|
|
748
787
|
type: "number",
|
|
749
788
|
value: editForm.rpm_limit ?? "",
|
|
750
789
|
onChange: (e) => setEditForm({ ...editForm, rpm_limit: e.target.value ? Number(e.target.value) : void 0 }),
|
|
790
|
+
helperText: "Max requests per minute this key can make across all models. Leave blank for no limit.",
|
|
751
791
|
fullWidth: true
|
|
752
792
|
}
|
|
753
793
|
), /* @__PURE__ */ React2.createElement(Box2, { mt: 1, pt: 2, borderTop: "1px solid", sx: { borderColor: "divider" } }, /* @__PURE__ */ React2.createElement(Typography2, { variant: "caption", color: "text.secondary", display: "block", mb: 1 }, "Danger Zone"), !resetSpendConfirm ? /* @__PURE__ */ React2.createElement(
|
|
@@ -1410,6 +1450,7 @@ var init_LiteLLMPage = __esm({
|
|
|
1410
1450
|
() => api.getTeams().catch(() => []),
|
|
1411
1451
|
[api]
|
|
1412
1452
|
);
|
|
1453
|
+
const { value: liteLlmConfig } = useAsync2(() => api.getConfig(), [api]);
|
|
1413
1454
|
const teams = useMemo3(() => {
|
|
1414
1455
|
if (!allTeams?.length) return [];
|
|
1415
1456
|
if (!userInfo) return allTeams;
|
|
@@ -1588,6 +1629,8 @@ var init_LiteLLMPage = __esm({
|
|
|
1588
1629
|
keys: keys ?? [],
|
|
1589
1630
|
models: allowedModels,
|
|
1590
1631
|
teams: teams ?? [],
|
|
1632
|
+
username: userInfo.user_id,
|
|
1633
|
+
keyGenerationSettings: liteLlmConfig?.keyGeneration,
|
|
1591
1634
|
loading: keysLoading || modelsLoading,
|
|
1592
1635
|
onGenerateKey: handleGenerateKey,
|
|
1593
1636
|
onUpdateKey: handleUpdateKey,
|