@acarmisc/backstage-plugin-litellm 0.32.0 → 0.33.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.
@@ -1,5 +1,10 @@
1
1
  import { FC } from 'react';
2
2
  import { TeamInfo, ModelInfo, LiteLlmConfig, CreateTeamRequest, UpdateTeamRequest, VectorStoreInfo, McpServerInfo } from '../types';
3
+ /** A catalog User entity flattened into what the member picker needs. */
4
+ export interface InitialMember {
5
+ userEntityRef: string;
6
+ maxBudgetInTeam?: number;
7
+ }
3
8
  interface ManageTeamDialogProps {
4
9
  open: boolean;
5
10
  onClose: () => void;
@@ -7,7 +12,11 @@ interface ManageTeamDialogProps {
7
12
  team?: TeamInfo;
8
13
  allModels: ModelInfo[];
9
14
  config?: LiteLlmConfig;
10
- onSubmit: (payload: CreateTeamRequest | UpdateTeamRequest) => Promise<void>;
15
+ /**
16
+ * Saves the team. In create mode, `initialMembers` lists the members picked
17
+ * in the dialog; the parent adds them once the team exists.
18
+ */
19
+ onSubmit: (payload: CreateTeamRequest | UpdateTeamRequest, initialMembers?: InitialMember[]) => Promise<void>;
11
20
  /** When true, the Members section (edit mode only) exposes add/remove controls. */
12
21
  canManageMembers?: boolean;
13
22
  /** Called to add a member; the parent is expected to refresh `team` on success. */
package/dist/index.cjs.js CHANGED
@@ -3204,6 +3204,7 @@ var init_ManageTeamDialog = __esm({
3204
3204
  const [memberBudget, setMemberBudget] = (0, import_react7.useState)("");
3205
3205
  const [memberBusy, setMemberBusy] = (0, import_react7.useState)(false);
3206
3206
  const [memberError, setMemberError] = (0, import_react7.useState)(null);
3207
+ const [pendingMembers, setPendingMembers] = (0, import_react7.useState)([]);
3207
3208
  const [kbIds, setKbIds] = (0, import_react7.useState)([]);
3208
3209
  const [kbBusy, setKbBusy] = (0, import_react7.useState)(false);
3209
3210
  const [kbError, setKbError] = (0, import_react7.useState)(null);
@@ -3214,8 +3215,17 @@ var init_ManageTeamDialog = __esm({
3214
3215
  const maxBudgetCeiling = config?.teamManagement?.maxBudgetCeiling ?? FALLBACK_BUDGET_CEILING;
3215
3216
  const hideBudgetForManagers = config?.display?.hideTeamBudgetForManagers ?? false;
3216
3217
  const budgetWriteOnly = hideBudgetForManagers && mode === "edit";
3217
- const members = (0, import_react7.useMemo)(() => team?.members_with_roles ?? [], [team]);
3218
- const showMembers = mode === "edit" && !!canManageMembers || membersOnly;
3218
+ const creating = mode === "create";
3219
+ const members = (0, import_react7.useMemo)(
3220
+ () => creating ? pendingMembers.map((p) => ({
3221
+ user_id: p.userEntityRef,
3222
+ user_email: p.label,
3223
+ role: "user"
3224
+ })) : team?.members_with_roles ?? [],
3225
+ [creating, pendingMembers, team]
3226
+ );
3227
+ const showMembers = !!canManageMembers || !!membersOnly;
3228
+ const wide = showMembers && !membersOnly;
3219
3229
  const catalogApi = (0, import_core_plugin_api4.useApi)(import_plugin_catalog_react.catalogApiRef);
3220
3230
  const [searchUsers, setSearchUsers] = (0, import_react7.useState)([]);
3221
3231
  const [usersLoading, setUsersLoading] = (0, import_react7.useState)(false);
@@ -3260,8 +3270,8 @@ var init_ManageTeamDialog = __esm({
3260
3270
  email,
3261
3271
  label: email ? `${displayName} (${email})` : displayName
3262
3272
  };
3263
- }).filter((o) => !(o.email && existingEmails.has(o.email.toLowerCase()))).sort((a, b) => a.label.localeCompare(b.label));
3264
- }, [searchUsers, members]);
3273
+ }).filter((o) => !(o.email && existingEmails.has(o.email.toLowerCase()))).filter((o) => !pendingMembers.some((p) => p.userEntityRef === o.ref)).sort((a, b) => a.label.localeCompare(b.label));
3274
+ }, [searchUsers, members, pendingMembers]);
3265
3275
  (0, import_react7.useEffect)(() => {
3266
3276
  return () => {
3267
3277
  if (debounceTimerRef.current) {
@@ -3295,6 +3305,7 @@ var init_ManageTeamDialog = __esm({
3295
3305
  setMemberRef("");
3296
3306
  setMemberBudget("");
3297
3307
  setMemberError(null);
3308
+ setPendingMembers([]);
3298
3309
  setKbIds(team?.object_permission?.vector_stores ?? []);
3299
3310
  setKbError(null);
3300
3311
  setMcpIds(team?.object_permission?.mcp_servers ?? []);
@@ -3353,7 +3364,13 @@ var init_ManageTeamDialog = __esm({
3353
3364
  ...budgetDuration && { budget_duration: budgetDuration }
3354
3365
  };
3355
3366
  }
3356
- await onSubmit(payload);
3367
+ await onSubmit(
3368
+ payload,
3369
+ creating ? pendingMembers.map(({ userEntityRef, maxBudgetInTeam }) => ({
3370
+ userEntityRef,
3371
+ ...maxBudgetInTeam !== void 0 && { maxBudgetInTeam }
3372
+ })) : void 0
3373
+ );
3357
3374
  onClose();
3358
3375
  } catch (err) {
3359
3376
  setError(err instanceof Error ? err.message : "An error occurred");
@@ -3362,13 +3379,26 @@ var init_ManageTeamDialog = __esm({
3362
3379
  }
3363
3380
  };
3364
3381
  const handleAddMember = async () => {
3365
- if (!onAddMember || !memberRef.trim()) return;
3382
+ if (!memberRef.trim() || !creating && !onAddMember) return;
3366
3383
  setMemberError(null);
3367
3384
  const budget = !membersOnly && memberBudget ? parseFloat(memberBudget) : void 0;
3368
3385
  if (budget !== void 0 && (isNaN(budget) || budget <= 0)) {
3369
3386
  setMemberError("Max budget in team must be a positive number");
3370
3387
  return;
3371
3388
  }
3389
+ if (creating) {
3390
+ const ref = memberRef.trim();
3391
+ if (!pendingMembers.some((p) => p.userEntityRef === ref)) {
3392
+ setPendingMembers((list) => [
3393
+ ...list,
3394
+ { userEntityRef: ref, label: memberQuery.trim() || ref, maxBudgetInTeam: budget }
3395
+ ]);
3396
+ }
3397
+ setMemberQuery("");
3398
+ setMemberRef("");
3399
+ setMemberBudget("");
3400
+ return;
3401
+ }
3372
3402
  try {
3373
3403
  setMemberBusy(true);
3374
3404
  await onAddMember(memberRef.trim(), budget);
@@ -3382,6 +3412,10 @@ var init_ManageTeamDialog = __esm({
3382
3412
  }
3383
3413
  };
3384
3414
  const handleRemoveMember = async (userId) => {
3415
+ if (creating) {
3416
+ setPendingMembers((list) => list.filter((p) => p.userEntityRef !== userId));
3417
+ return;
3418
+ }
3385
3419
  if (!onRemoveMember) return;
3386
3420
  setMemberError(null);
3387
3421
  try {
@@ -3435,13 +3469,14 @@ var init_ManageTeamDialog = __esm({
3435
3469
  }
3436
3470
  return { disabled: false };
3437
3471
  };
3472
+ const submitLabel = creating ? "Create team" : "Save";
3438
3473
  const getDialogTitle = () => {
3439
3474
  if (membersOnly) {
3440
3475
  return `Manage members \u2014 ${team?.team_alias || "Untitled team"}`;
3441
3476
  }
3442
3477
  return mode === "create" ? "Create Team" : "Edit Team";
3443
3478
  };
3444
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_Dialog3.default, { open, onClose, maxWidth: "sm", fullWidth: true, children: [
3479
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_Dialog3.default, { open, onClose, maxWidth: wide ? "md" : "sm", fullWidth: true, children: [
3445
3480
  /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_DialogTitle3.default, { children: getDialogTitle() }),
3446
3481
  /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_DialogContent3.default, { sx: { pt: 2, display: "flex", flexDirection: "column", gap: 2 }, children: [
3447
3482
  error && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_Alert2.default, { severity: "error", children: error }),
@@ -3450,186 +3485,215 @@ var init_ManageTeamDialog = __esm({
3450
3485
  team?.members_with_roles?.find((m) => m.user_id === currentUserId)?.role || "member",
3451
3486
  " you can add or remove members. Budgets and models are managed by your platform admins."
3452
3487
  ] }),
3453
- !membersOnly && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
3454
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3455
- import_TextField3.default,
3456
- {
3457
- label: "Team Alias",
3458
- value: alias,
3459
- onChange: (e) => setAlias(e.target.value),
3460
- disabled: submitting,
3461
- fullWidth: true
3462
- }
3463
- ),
3464
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3465
- import_Autocomplete2.default,
3466
- {
3467
- multiple: true,
3468
- options: modelNames,
3469
- value: models,
3470
- onChange: (_, newValue) => setModels(newValue),
3471
- disabled: submitting,
3472
- renderInput: (params) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3473
- import_TextField3.default,
3474
- {
3475
- ...params,
3476
- label: "Models",
3477
- required: true,
3478
- helperText: "At least one model is required here \u2014 this delegated flow only grants access to models your admin has allow-listed for team creation, not every proxy model. (Teams with no model restriction shown elsewhere were configured directly in LiteLLM.)"
3479
- }
3480
- )
3481
- }
3482
- ),
3483
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3484
- import_TextField3.default,
3485
- {
3486
- label: budgetWriteOnly ? "New Max Budget ($)" : "Max Budget ($)",
3487
- type: "number",
3488
- value: maxBudget,
3489
- onChange: (e) => setMaxBudget(e.target.value),
3490
- disabled: submitting || unlimited,
3491
- inputProps: { min: 0, max: maxBudgetCeiling },
3492
- helperText: budgetWriteOnly ? "Current budget is hidden \u2014 leave blank to keep it, or enter a new value" : `Maximum: $${maxBudgetCeiling}`,
3493
- fullWidth: true
3494
- }
3495
- ),
3496
- allowUnlimitedBudget && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3497
- import_FormControlLabel2.default,
3498
- {
3499
- control: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_Checkbox2.default, { checked: unlimited, onChange: (e) => setUnlimited(e.target.checked), disabled: submitting }),
3500
- label: "Unlimited Budget"
3501
- }
3502
- ),
3503
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3504
- import_TextField3.default,
3505
- {
3506
- select: true,
3507
- label: "Budget Duration",
3508
- value: budgetDuration,
3509
- onChange: (e) => setBudgetDuration(e.target.value),
3510
- disabled: submitting || unlimited,
3511
- helperText: "Spend-reset period for the team budget",
3512
- fullWidth: true,
3513
- children: durationOptions.map((o) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_MenuItem2.default, { value: o.value, children: o.label }, o.value))
3514
- }
3515
- )
3516
- ] }),
3517
- showMembers && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_Box10.default, { children: [
3518
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_Divider2.default, { sx: { my: 1 } }),
3519
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_Typography8.default, { variant: "subtitle2", sx: { mb: 1 }, children: "Members" }),
3520
- memberError && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_Alert2.default, { severity: "error", sx: { mb: 1 }, onClose: () => setMemberError(null), children: memberError }),
3521
- members.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_Typography8.default, { variant: "body2", color: "text.secondary", sx: { mb: 1 }, children: "No members yet." }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_TableContainer2.default, { component: import_Paper4.default, variant: "outlined", sx: { borderRadius: 1.5, mb: 1.5 }, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_Table2.default, { size: "small", sx: dataTableSx, children: [
3522
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_TableHead2.default, { children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_TableRow2.default, { children: [
3523
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_TableCell2.default, { children: "User" }),
3524
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_TableCell2.default, { align: "right", children: "Role" }),
3525
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_TableCell2.default, { align: "right", sx: { width: 40 } })
3526
- ] }) }),
3527
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_TableBody2.default, { children: members.map((m) => /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_TableRow2.default, { children: [
3528
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_TableCell2.default, { children: m.user_email ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
3529
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_Typography8.default, { variant: "body2", children: m.user_email }),
3488
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
3489
+ import_Box10.default,
3490
+ {
3491
+ sx: {
3492
+ display: "grid",
3493
+ gridTemplateColumns: {
3494
+ xs: "1fr",
3495
+ md: wide ? "minmax(0, 1fr) minmax(0, 1fr)" : "1fr"
3496
+ },
3497
+ gap: 3,
3498
+ alignItems: "start"
3499
+ },
3500
+ children: [
3501
+ !membersOnly && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_Box10.default, { sx: { display: "flex", flexDirection: "column", gap: 2 }, children: [
3502
+ wide && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_Typography8.default, { variant: "subtitle2", children: "Settings" }),
3530
3503
  /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3531
- import_Typography8.default,
3504
+ import_TextField3.default,
3532
3505
  {
3533
- variant: "caption",
3534
- color: "text.secondary",
3535
- sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 11 },
3536
- children: m.user_id
3506
+ label: "Team Alias",
3507
+ value: alias,
3508
+ onChange: (e) => setAlias(e.target.value),
3509
+ disabled: submitting,
3510
+ fullWidth: true
3537
3511
  }
3538
- )
3539
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3540
- import_Typography8.default,
3541
- {
3542
- variant: "body2",
3543
- sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace" },
3544
- children: m.user_id
3545
- }
3546
- ) }),
3547
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_TableCell2.default, { align: "right", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(StatusPill, { label: m.role, tone: m.role === "admin" ? "accent" : "neutral", dot: false }) }),
3548
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_TableCell2.default, { align: "right", children: (() => {
3549
- const { disabled, reason } = isRemovalDisabled(m.user_id, m.role);
3550
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_Tooltip4.default, { title: reason ?? "", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3551
- import_IconButton4.default,
3512
+ ),
3513
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3514
+ import_Autocomplete2.default,
3552
3515
  {
3553
- edge: "end",
3554
- size: "small",
3555
- "aria-label": `remove ${m.user_id}`,
3556
- disabled: memberBusy || disabled,
3557
- onClick: () => handleRemoveMember(m.user_id),
3558
- children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_icons_material5.Delete, { fontSize: "small" })
3559
- }
3560
- ) }) });
3561
- })() })
3562
- ] }, m.user_id)) })
3563
- ] }) }),
3564
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_Box10.default, { sx: { display: "flex", gap: 1, alignItems: "flex-start", flexWrap: "wrap" }, children: [
3565
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3566
- import_Autocomplete2.default,
3567
- {
3568
- freeSolo: true,
3569
- autoHighlight: true,
3570
- options: userOptions,
3571
- loading: usersLoading,
3572
- inputValue: memberQuery,
3573
- onInputChange: (_, newInputValue, reason) => {
3574
- setMemberQuery(newInputValue);
3575
- if (reason === "input" || reason === "clear") {
3576
- setMemberRef(newInputValue);
3577
- handleUserSearch(newInputValue);
3516
+ multiple: true,
3517
+ options: modelNames,
3518
+ value: models,
3519
+ onChange: (_, newValue) => setModels(newValue),
3520
+ disabled: submitting,
3521
+ renderInput: (params) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3522
+ import_TextField3.default,
3523
+ {
3524
+ ...params,
3525
+ label: "Models",
3526
+ required: true,
3527
+ helperText: "Only models your admin allow-listed for teams. At least one is required."
3528
+ }
3529
+ )
3578
3530
  }
3579
- },
3580
- onChange: (_, newValue) => {
3581
- if (newValue && typeof newValue !== "string") {
3582
- setMemberQuery(newValue.label);
3583
- setMemberRef(newValue.ref);
3531
+ ),
3532
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
3533
+ import_Box10.default,
3534
+ {
3535
+ sx: {
3536
+ display: "grid",
3537
+ gridTemplateColumns: { xs: "1fr", sm: "1fr 1fr" },
3538
+ gap: 2
3539
+ },
3540
+ children: [
3541
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3542
+ import_TextField3.default,
3543
+ {
3544
+ label: budgetWriteOnly ? "New Max Budget ($)" : "Max Budget ($)",
3545
+ type: "number",
3546
+ value: maxBudget,
3547
+ onChange: (e) => setMaxBudget(e.target.value),
3548
+ disabled: submitting || unlimited,
3549
+ inputProps: { min: 0, max: maxBudgetCeiling },
3550
+ helperText: budgetWriteOnly ? "Current budget is hidden \u2014 leave blank to keep it, or enter a new value" : `Maximum: $${maxBudgetCeiling}`,
3551
+ fullWidth: true
3552
+ }
3553
+ ),
3554
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3555
+ import_TextField3.default,
3556
+ {
3557
+ select: true,
3558
+ label: "Budget Duration",
3559
+ value: budgetDuration,
3560
+ onChange: (e) => setBudgetDuration(e.target.value),
3561
+ disabled: submitting || unlimited,
3562
+ helperText: "Spend-reset period for the team budget",
3563
+ fullWidth: true,
3564
+ children: durationOptions.map((o) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_MenuItem2.default, { value: o.value, children: o.label }, o.value))
3565
+ }
3566
+ )
3567
+ ]
3584
3568
  }
3585
- },
3586
- getOptionLabel: (o) => typeof o === "string" ? o : o.label,
3587
- isOptionEqualToValue: (o, v) => o.ref === (typeof v === "string" ? v : v.ref),
3588
- disabled: memberBusy,
3589
- sx: { flex: 1, minWidth: 260 },
3590
- renderInput: (params) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3591
- import_TextField3.default,
3569
+ ),
3570
+ allowUnlimitedBudget && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3571
+ import_FormControlLabel2.default,
3592
3572
  {
3593
- ...params,
3594
- label: "Add member",
3595
- placeholder: "Search by name or email\u2026",
3596
- size: "small",
3597
- helperText: "Pick a catalog user, or paste a user entity ref",
3598
- InputProps: {
3599
- ...params.InputProps,
3600
- endAdornment: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
3601
- usersLoading ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_CircularProgress3.default, { color: "inherit", size: 14 }) : null,
3602
- params.InputProps.endAdornment
3603
- ] })
3604
- }
3573
+ control: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_Checkbox2.default, { checked: unlimited, onChange: (e) => setUnlimited(e.target.checked), disabled: submitting }),
3574
+ label: "Unlimited Budget"
3605
3575
  }
3606
3576
  )
3607
- }
3608
- ),
3609
- !membersOnly && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3610
- import_TextField3.default,
3611
- {
3612
- label: "Max budget in team ($)",
3613
- type: "number",
3614
- value: memberBudget,
3615
- onChange: (e) => setMemberBudget(e.target.value),
3616
- disabled: memberBusy,
3617
- size: "small",
3618
- sx: { width: 160 }
3619
- }
3620
- ),
3621
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3622
- import_Button4.default,
3623
- {
3624
- onClick: handleAddMember,
3625
- disabled: memberBusy || !memberRef.trim(),
3626
- variant: "outlined",
3627
- sx: { mt: 0.5 },
3628
- children: "Add"
3629
- }
3630
- )
3631
- ] })
3632
- ] }),
3577
+ ] }),
3578
+ showMembers && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_Box10.default, { children: [
3579
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_Typography8.default, { variant: "subtitle2", sx: { mb: creating ? 0.25 : 1 }, children: creating ? "Initial members" : "Members" }),
3580
+ creating && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_Typography8.default, { variant: "body2", color: "text.secondary", sx: { mb: 1 }, children: "Added as team users right after the team is created." }),
3581
+ memberError && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_Alert2.default, { severity: "error", sx: { mb: 1 }, onClose: () => setMemberError(null), children: memberError }),
3582
+ members.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_Typography8.default, { variant: "body2", color: "text.secondary", sx: { mb: 1 }, children: creating ? "No members picked yet \u2014 you can also add them later." : "No members yet." }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_TableContainer2.default, { component: import_Paper4.default, variant: "outlined", sx: { borderRadius: 1.5, mb: 1.5 }, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_Table2.default, { size: "small", sx: dataTableSx, children: [
3583
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_TableHead2.default, { children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_TableRow2.default, { children: [
3584
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_TableCell2.default, { children: "User" }),
3585
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_TableCell2.default, { align: "right", children: "Role" }),
3586
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_TableCell2.default, { align: "right", sx: { width: 40 } })
3587
+ ] }) }),
3588
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_TableBody2.default, { children: members.map((m) => /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_TableRow2.default, { children: [
3589
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_TableCell2.default, { children: m.user_email ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
3590
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_Typography8.default, { variant: "body2", children: m.user_email }),
3591
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3592
+ import_Typography8.default,
3593
+ {
3594
+ variant: "caption",
3595
+ color: "text.secondary",
3596
+ sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 11 },
3597
+ children: m.user_id
3598
+ }
3599
+ )
3600
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3601
+ import_Typography8.default,
3602
+ {
3603
+ variant: "body2",
3604
+ sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace" },
3605
+ children: m.user_id
3606
+ }
3607
+ ) }),
3608
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_TableCell2.default, { align: "right", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(StatusPill, { label: m.role, tone: m.role === "admin" ? "accent" : "neutral", dot: false }) }),
3609
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_TableCell2.default, { align: "right", children: (() => {
3610
+ const { disabled, reason } = isRemovalDisabled(m.user_id, m.role);
3611
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_Tooltip4.default, { title: reason ?? "", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3612
+ import_IconButton4.default,
3613
+ {
3614
+ edge: "end",
3615
+ size: "small",
3616
+ "aria-label": `remove ${m.user_id}`,
3617
+ disabled: memberBusy || disabled,
3618
+ onClick: () => handleRemoveMember(m.user_id),
3619
+ children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_icons_material5.Delete, { fontSize: "small" })
3620
+ }
3621
+ ) }) });
3622
+ })() })
3623
+ ] }, m.user_id)) })
3624
+ ] }) }),
3625
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_Box10.default, { sx: { display: "flex", gap: 1, alignItems: "flex-start", flexWrap: "wrap" }, children: [
3626
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3627
+ import_Autocomplete2.default,
3628
+ {
3629
+ freeSolo: true,
3630
+ autoHighlight: true,
3631
+ options: userOptions,
3632
+ loading: usersLoading,
3633
+ inputValue: memberQuery,
3634
+ onInputChange: (_, newInputValue, reason) => {
3635
+ setMemberQuery(newInputValue);
3636
+ if (reason === "input" || reason === "clear") {
3637
+ setMemberRef(newInputValue);
3638
+ handleUserSearch(newInputValue);
3639
+ }
3640
+ },
3641
+ onChange: (_, newValue) => {
3642
+ if (newValue && typeof newValue !== "string") {
3643
+ setMemberQuery(newValue.label);
3644
+ setMemberRef(newValue.ref);
3645
+ }
3646
+ },
3647
+ getOptionLabel: (o) => typeof o === "string" ? o : o.label,
3648
+ isOptionEqualToValue: (o, v) => o.ref === (typeof v === "string" ? v : v.ref),
3649
+ disabled: memberBusy,
3650
+ sx: { flex: "1 1 240px" },
3651
+ renderInput: (params) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3652
+ import_TextField3.default,
3653
+ {
3654
+ ...params,
3655
+ label: "Add member",
3656
+ placeholder: "Search by name or email\u2026",
3657
+ size: "small",
3658
+ helperText: "Pick a catalog user, or paste a user entity ref",
3659
+ InputProps: {
3660
+ ...params.InputProps,
3661
+ endAdornment: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
3662
+ usersLoading ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_CircularProgress3.default, { color: "inherit", size: 14 }) : null,
3663
+ params.InputProps.endAdornment
3664
+ ] })
3665
+ }
3666
+ }
3667
+ )
3668
+ }
3669
+ ),
3670
+ !membersOnly && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3671
+ import_TextField3.default,
3672
+ {
3673
+ label: "Max budget in team ($)",
3674
+ type: "number",
3675
+ value: memberBudget,
3676
+ onChange: (e) => setMemberBudget(e.target.value),
3677
+ disabled: memberBusy,
3678
+ size: "small",
3679
+ sx: { width: 150 }
3680
+ }
3681
+ ),
3682
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3683
+ import_Button4.default,
3684
+ {
3685
+ onClick: handleAddMember,
3686
+ disabled: memberBusy || !memberRef.trim(),
3687
+ variant: "outlined",
3688
+ sx: { mt: 0.5 },
3689
+ children: "Add"
3690
+ }
3691
+ )
3692
+ ] })
3693
+ ] })
3694
+ ]
3695
+ }
3696
+ ),
3633
3697
  !membersOnly && showKnowledgeBases && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_Box10.default, { children: [
3634
3698
  /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_Divider2.default, { sx: { my: 1 } }),
3635
3699
  /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_Typography8.default, { variant: "subtitle2", sx: { mb: 1 }, children: "Knowledge Bases" }),
@@ -3687,7 +3751,7 @@ var init_ManageTeamDialog = __esm({
3687
3751
  ] }),
3688
3752
  /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_DialogActions3.default, { children: [
3689
3753
  /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_Button4.default, { onClick: onClose, disabled: submitting, children: membersOnly ? "Done" : "Cancel" }),
3690
- !membersOnly && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_Button4.default, { onClick: handleSubmit, disabled: submitting, variant: "contained", children: submitting ? "Saving..." : "Save" })
3754
+ !membersOnly && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_Button4.default, { onClick: handleSubmit, disabled: submitting, variant: "contained", children: submitting ? "Saving..." : submitLabel })
3691
3755
  ] })
3692
3756
  ] });
3693
3757
  };
@@ -6505,7 +6569,7 @@ var init_LiteLLMPage = __esm({
6505
6569
  membersOnly: manageTeam?.mode === "members",
6506
6570
  currentUserId: userInfo?.user_id,
6507
6571
  memberManagerRoles: liteLlmConfig?.teamManagement?.memberManagerRoles,
6508
- onSubmit: async (payload) => {
6572
+ onSubmit: async (payload, initialMembers) => {
6509
6573
  try {
6510
6574
  if (manageTeam?.mode === "edit" && manageTeam.team) {
6511
6575
  const loadedAt = manageTeam.team.metadata?.updated_at_iso;
@@ -6515,7 +6579,23 @@ var init_LiteLLMPage = __esm({
6515
6579
  ...typeof loadedAt === "string" ? { expectedUpdatedAtIso: loadedAt } : {}
6516
6580
  });
6517
6581
  } else {
6518
- await api.createTeam(payload);
6582
+ const created = await api.createTeam(payload);
6583
+ const failed = [];
6584
+ for (const m of initialMembers ?? []) {
6585
+ try {
6586
+ await api.addTeamMember(created.team_id, m);
6587
+ } catch (e) {
6588
+ failed.push(`${m.userEntityRef} (${e.message})`);
6589
+ }
6590
+ }
6591
+ if (failed.length) {
6592
+ alertApi.post({
6593
+ message: `Team created, but some members could not be added: ${failed.join(", ")}`,
6594
+ severity: "warning"
6595
+ });
6596
+ refreshTeams();
6597
+ return;
6598
+ }
6519
6599
  }
6520
6600
  const alert = toastFor("teamSaveSuccess");
6521
6601
  if (alert) alertApi.post(alert);