From a20ef525609a721bbd4ddf042070f05b508555ac Mon Sep 17 00:00:00 2001 From: "ashlee@vellum.ai" Date: Wed, 3 Jun 2026 19:26:50 +0000 Subject: [PATCH] refactor(web): split manage-profiles-modal into focused modules (LUM-2226) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extract BlockedDeleteModal and ProfileListItem into dedicated files. Remove redundant manual optimistic updates from handleStatusToggle and handleReorder — useDaemonConfigMutation's onMutate/onError already handles both patch shapes via applyConfigPatch. Closes LUM-2226 Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../manage-profiles-blocked-delete-modal.tsx | 124 ++++++ .../settings/ai/manage-profiles-list-item.tsx | 180 ++++++++ .../settings/ai/manage-profiles-modal.tsx | 415 +++--------------- 3 files changed, 366 insertions(+), 353 deletions(-) create mode 100644 apps/web/src/domains/settings/ai/manage-profiles-blocked-delete-modal.tsx create mode 100644 apps/web/src/domains/settings/ai/manage-profiles-list-item.tsx diff --git a/apps/web/src/domains/settings/ai/manage-profiles-blocked-delete-modal.tsx b/apps/web/src/domains/settings/ai/manage-profiles-blocked-delete-modal.tsx new file mode 100644 index 00000000000..52daed0f1bc --- /dev/null +++ b/apps/web/src/domains/settings/ai/manage-profiles-blocked-delete-modal.tsx @@ -0,0 +1,124 @@ +import { Button } from "@vellum/design-library/components/button"; +import { Dropdown } from "@vellum/design-library/components/dropdown"; +import { Modal } from "@vellum/design-library/components/modal"; +import { Typography } from "@vellum/design-library/components/typography"; + +import type { ProfileWithName } from "@/domains/settings/ai/ai-types"; + +// --------------------------------------------------------------------------- +// Types +// --------------------------------------------------------------------------- + +export interface BlockedDeleteState { + name: string; + label: string; + isActive: boolean; + callSiteIds: string[]; +} + +// --------------------------------------------------------------------------- +// BlockedDeleteModal +// --------------------------------------------------------------------------- + +export function BlockedDeleteModal({ + blocked, + availableReplacements, + replacement, + onReplacementChange, + error, + saving, + onClose, + onConfirm, +}: { + blocked: BlockedDeleteState | null; + availableReplacements: ProfileWithName[]; + replacement: string; + onReplacementChange: (value: string) => void; + error: string | null; + saving: boolean; + onClose: () => void; + onConfirm: () => void; +}) { + let summary = ""; + if (blocked) { + const display = blocked.label || blocked.name; + if (blocked.isActive && blocked.callSiteIds.length > 0) { + summary = `"${display}" is the active profile and is used by ${blocked.callSiteIds.length} call site(s). Pick a replacement profile.`; + } else if (blocked.isActive) { + summary = `"${display}" is the active profile. Pick a different active profile before deleting, or select a replacement below.`; + } else { + summary = `"${display}" is used by ${blocked.callSiteIds.length} call site(s). Select a replacement profile to reassign them before deleting.`; + } + } + + return ( + { + if (!next) onClose(); + }} + > + + + Can't Delete Profile + + + + {summary} + + {blocked && blocked.callSiteIds.length > 0 && ( +
    + {blocked.callSiteIds.map((id) => ( +
  • + • {id} +
  • + ))} +
+ )} +
+ + ({ + value: p.name, + label: p.label ?? p.name, + })), + ]} + /> +
+ {error && ( + + {error} + + )} +
+ + + + +
+
+ ); +} diff --git a/apps/web/src/domains/settings/ai/manage-profiles-list-item.tsx b/apps/web/src/domains/settings/ai/manage-profiles-list-item.tsx new file mode 100644 index 00000000000..0b65c30059d --- /dev/null +++ b/apps/web/src/domains/settings/ai/manage-profiles-list-item.tsx @@ -0,0 +1,180 @@ +import { GripVertical, Trash2 } from "lucide-react"; + +import { Button } from "@vellum/design-library/components/button"; +import { Tag } from "@vellum/design-library/components/tag"; +import { Toggle } from "@vellum/design-library/components/toggle"; +import { Typography } from "@vellum/design-library/components/typography"; + +import type { ProfileWithName } from "@/domains/settings/ai/ai-types"; +import { AUTO_PROFILE_NAME } from "@/domains/settings/ai/profile-pickers"; + +// --------------------------------------------------------------------------- +// Types +// --------------------------------------------------------------------------- + +interface DropTarget { + name: string; + after: boolean; +} + +interface ProfileListItemProps { + profile: ProfileWithName; + isDragging: boolean; + dropTarget: DropTarget | null; + isDeleting: boolean; + deleteError: string | undefined; + isToggling: boolean; + onDragStart: (e: React.DragEvent) => void; + onDragEnd: () => void; + onDragOver: (e: React.DragEvent) => void; + onDragLeave: (e: React.DragEvent) => void; + onDrop: (e: React.DragEvent) => void; + onEditClick: () => void; + onDeleteClick: () => void; + onStatusToggle: (active: boolean) => void; +} + +// --------------------------------------------------------------------------- +// ProfileListItem +// --------------------------------------------------------------------------- + +export function ProfileListItem({ + profile, + isDragging, + dropTarget, + isDeleting, + deleteError, + isToggling, + onDragStart, + onDragEnd, + onDragOver, + onDragLeave, + onDrop, + onEditClick, + onDeleteClick, + onStatusToggle, +}: ProfileListItemProps) { + const isManaged = profile.source === "managed"; + const isActive = profile.status !== "disabled"; + const isAutoProfile = profile.name === AUTO_PROFILE_NAME; + + return ( +
+ {dropTarget?.name === profile.name && !dropTarget.after && ( +
+ )} +
+ {/* Grip icon — invisible for managed profiles to preserve alignment */} + + + {/* Label — dimmed when disabled */} +
+
+ + {profile.label ?? profile.name} + + {isManaged && profile.name !== AUTO_PROFILE_NAME && ( + + Platform + + )} +
+ {profile.description ? ( + + {profile.description} + + ) : null} + {(profile.model ?? profile.provider) ? ( + + {profile.model ?? profile.provider} + + ) : null} +
+ + {/* Actions */} +
+
+ onStatusToggle(next)} + disabled={isToggling} + aria-label={`${isActive ? "Disable" : "Enable"} ${profile.label ?? profile.name}`} + /> +
+
+ +
+
+
+ {dropTarget?.name === profile.name && dropTarget.after && ( +
+ )} + {deleteError ? ( + + {deleteError} + + ) : null} + {profile.name === AUTO_PROFILE_NAME && ( +
+ )} +
+ ); +} diff --git a/apps/web/src/domains/settings/ai/manage-profiles-modal.tsx b/apps/web/src/domains/settings/ai/manage-profiles-modal.tsx index 57f17369b8f..17fe7223bb6 100644 --- a/apps/web/src/domains/settings/ai/manage-profiles-modal.tsx +++ b/apps/web/src/domains/settings/ai/manage-profiles-modal.tsx @@ -1,38 +1,26 @@ -import { GripVertical, Trash2 } from "lucide-react"; import { useMemo, useRef, useState } from "react"; -import { useQuery, useQueryClient } from "@tanstack/react-query"; +import { useQuery } from "@tanstack/react-query"; import { Button } from "@vellum/design-library/components/button"; -import { Dropdown } from "@vellum/design-library/components/dropdown"; -import { Toggle } from "@vellum/design-library/components/toggle"; import { Modal } from "@vellum/design-library/components/modal"; -import { Tag } from "@vellum/design-library/components/tag"; import { Typography } from "@vellum/design-library/components/typography"; import { useAssistantFeatureFlagStore } from "@/stores/assistant-feature-flag-store"; import { captureError } from "@/lib/sentry/capture-error"; -import type { CallSiteOverrideDraft, DaemonConfig, DaemonConfigPatch, ProfileEntry, ProfileStatus, ProfileWithName } from "@/domains/settings/ai/ai-types"; +import type { CallSiteOverrideDraft, DaemonConfigPatch, ProfileEntry, ProfileStatus, ProfileWithName } from "@/domains/settings/ai/ai-types"; +import { BlockedDeleteModal } from "@/domains/settings/ai/manage-profiles-blocked-delete-modal"; +import type { BlockedDeleteState } from "@/domains/settings/ai/manage-profiles-blocked-delete-modal"; +import { ProfileListItem } from "@/domains/settings/ai/manage-profiles-list-item"; import { ProfileEditorModal } from "@/domains/settings/ai/profile-editor-modal"; -import { - AUTO_PROFILE_NAME, - gateAutoProfile, -} from "@/domains/settings/ai/profile-pickers"; +import { gateAutoProfile } from "@/domains/settings/ai/profile-pickers"; import { inferenceProviderconnectionsGetOptions } from "@/generated/daemon/@tanstack/react-query.gen"; import { filterFlaggedConnections } from "@/domains/settings/ai/provider-connections-client"; import { useDaemonConfigQuery, useDaemonConfigMutation } from "@/domains/settings/ai/use-daemon-config"; -import { assistantDaemonConfigQueryKey } from "@/lib/sync/query-tags"; // --------------------------------------------------------------------------- // Types // --------------------------------------------------------------------------- -interface BlockedDeleteState { - name: string; - label: string; - isActive: boolean; - callSiteIds: string[]; -} - interface ManageProfilesModalProps { isOpen: boolean; assistantId: string; @@ -149,7 +137,6 @@ export function ManageProfilesModal({ profileOrder={profileOrder} orderedProfiles={orderedProfiles} activeProfile={activeProfile} - assistantId={assistantId} callSiteOverrides={callSites} onClose={onClose} onEditClick={(profile) => { @@ -196,7 +183,6 @@ interface ManageProfilesModalInnerProps { profileOrder: string[]; orderedProfiles: ProfileWithName[]; activeProfile: string | null; - assistantId: string; callSiteOverrides: Record; onClose: () => void; onEditClick: (profile: ProfileWithName) => void; @@ -208,15 +194,12 @@ function ManageProfilesModalInner({ profileOrder, orderedProfiles, activeProfile, - assistantId, callSiteOverrides, onClose, onEditClick, onNewClick, }: ManageProfilesModalInnerProps) { const configMutation = useDaemonConfigMutation(); - const queryClient = useQueryClient(); - const queryKey = assistantDaemonConfigQueryKey(assistantId); const [deleteErrors, setDeleteErrors] = useState>({}); const [deleting, setDeleting] = useState>({}); @@ -227,7 +210,6 @@ function ManageProfilesModalInner({ const [draggingName, setDraggingName] = useState(null); const [dropTarget, setDropTarget] = useState<{ name: string; after: boolean } | null>(null); const [reorderError, setReorderError] = useState(null); - const lastConfirmedOrderRef = useRef(profileOrder); const draggingNameRef = useRef(null); const dropTargetRef = useRef<{ name: string; after: boolean } | null>(null); @@ -253,8 +235,7 @@ function ManageProfilesModalInner({ setToggleError(null); const wireStatus: ProfileStatus = active ? "active" : "disabled"; - const previousEntry = profiles[profile.name]; - if (!previousEntry) { + if (!profiles[profile.name]) { setTogglingNames((prev) => { const next = new Set(prev); next.delete(profile.name); @@ -263,25 +244,6 @@ function ManageProfilesModalInner({ return false; } - // Cancel in-flight refetches so they don't overwrite the optimistic update. - // See: https://tkdodo.eu/blog/concurrent-optimistic-updates-in-react-query - await queryClient.cancelQueries({ queryKey }); - - const previousStatus = previousEntry.status ?? "active"; - queryClient.setQueryData(queryKey, (old) => { - if (!old?.llm?.profiles?.[profile.name]) return old; - return { - ...old, - llm: { - ...old.llm, - profiles: { - ...old.llm.profiles, - [profile.name]: { ...old.llm.profiles[profile.name], status: wireStatus }, - }, - }, - }; - }); - try { await configMutation.mutateAsync({ llm: { profiles: { [profile.name]: { status: wireStatus } } }, @@ -289,20 +251,6 @@ function ManageProfilesModalInner({ return true; } catch (error) { captureError(error, { context: "settings-ai-profile-toggle" }); - // Rollback only the toggled field to avoid overwriting concurrent cache updates - queryClient.setQueryData(queryKey, (old) => { - if (!old?.llm?.profiles?.[profile.name]) return old; - return { - ...old, - llm: { - ...old.llm, - profiles: { - ...old.llm.profiles, - [profile.name]: { ...old.llm.profiles[profile.name], status: previousStatus }, - }, - }, - }; - }); setToggleError( `Couldn't update "${profile.label ?? profile.name}". Please try again.`, ); @@ -414,30 +362,10 @@ function ManageProfilesModalInner({ ...without.slice(insertAt), ]; - // Cancel in-flight refetches so they don't overwrite the optimistic update. - await queryClient.cancelQueries({ queryKey }); - - queryClient.setQueryData(queryKey, (old) => { - if (!old?.llm) return old; - return { - ...old, - llm: { ...old.llm, profileOrder: newOrder }, - }; - }); - try { await configMutation.mutateAsync({ llm: { profileOrder: newOrder } }); - lastConfirmedOrderRef.current = newOrder; } catch (error) { captureError(error, { context: "settings-ai-profile-reorder" }); - // Rollback to last confirmed server state - queryClient.setQueryData(queryKey, (old) => { - if (!old?.llm) return old; - return { - ...old, - llm: { ...old.llm, profileOrder: lastConfirmedOrderRef.current }, - }; - }); setReorderError("Failed to reorder profiles. Please try again."); } } @@ -472,173 +400,61 @@ function ManageProfilesModalInner({ ) : (
- {allOrderedProfiles.map((profile) => { - const isManaged = profile.source === "managed"; - const isDeleting = deleting[profile.name] ?? false; - const deleteError = deleteErrors[profile.name]; - - const isActive = profile.status !== "disabled"; - const isToggling = togglingNames.has(profile.name); - const isAutoProfile = profile.name === AUTO_PROFILE_NAME; - - return ( -
- {dropTarget?.name === profile.name && !dropTarget.after && ( -
- )} -
{ - draggingNameRef.current = profile.name; - setDraggingName(profile.name); - if (e.dataTransfer) { - e.dataTransfer.effectAllowed = "move"; - } - }} - onDragEnd={() => { - draggingNameRef.current = null; - dropTargetRef.current = null; - setDraggingName(null); - setDropTarget(null); - }} - onDragOver={(e) => { - e.preventDefault(); - const rect = e.currentTarget.getBoundingClientRect(); - const after = e.clientY > rect.top + rect.height / 2; - const t = { name: profile.name, after }; - dropTargetRef.current = t; - setDropTarget(t); - }} - onDragLeave={(e) => { - if (!e.currentTarget.contains(e.relatedTarget as Node | null)) { - dropTargetRef.current = null; - setDropTarget((prev) => - prev?.name === profile.name ? null : prev, - ); - } - }} - onDrop={(e) => { - e.preventDefault(); - const source = draggingNameRef.current; - const target = dropTargetRef.current; - draggingNameRef.current = null; - dropTargetRef.current = null; - setDraggingName(null); - setDropTarget(null); - if (source && target) { - void handleReorder(source, target); - } - }} - > - {/* Grip icon — invisible for managed profiles to preserve alignment */} - - - {/* Label — dimmed when disabled */} -
-
- - {profile.label ?? profile.name} - - {isManaged && profile.name !== AUTO_PROFILE_NAME && ( - - Platform - - )} -
- {profile.description ? ( - - {profile.description} - - ) : null} - {(profile.model ?? profile.provider) ? ( - - {profile.model ?? profile.provider} - - ) : null} -
- - {/* Actions */} -
-
- - void handleStatusToggle(profile, next) - } - disabled={isToggling} - aria-label={`${isActive ? "Disable" : "Enable"} ${profile.label ?? profile.name}`} - /> -
-
- -
-
-
- {dropTarget?.name === profile.name && dropTarget.after && ( -
- )} - {deleteError ? ( - - {deleteError} - - ) : null} - {profile.name === AUTO_PROFILE_NAME && ( -
- )} -
- ); - })} + {allOrderedProfiles.map((profile) => ( + { + draggingNameRef.current = profile.name; + setDraggingName(profile.name); + if (e.dataTransfer) { + e.dataTransfer.effectAllowed = "move"; + } + }} + onDragEnd={() => { + draggingNameRef.current = null; + dropTargetRef.current = null; + setDraggingName(null); + setDropTarget(null); + }} + onDragOver={(e) => { + e.preventDefault(); + const rect = e.currentTarget.getBoundingClientRect(); + const after = e.clientY > rect.top + rect.height / 2; + const t = { name: profile.name, after }; + dropTargetRef.current = t; + setDropTarget(t); + }} + onDragLeave={(e) => { + if (!e.currentTarget.contains(e.relatedTarget as Node | null)) { + dropTargetRef.current = null; + setDropTarget((prev) => + prev?.name === profile.name ? null : prev, + ); + } + }} + onDrop={(e) => { + e.preventDefault(); + const source = draggingNameRef.current; + const target = dropTargetRef.current; + draggingNameRef.current = null; + dropTargetRef.current = null; + setDraggingName(null); + setDropTarget(null); + if (source && target) { + void handleReorder(source, target); + } + }} + onEditClick={() => onEditClick(profile)} + onDeleteClick={() => handleDeleteClick(profile.name)} + onStatusToggle={(next) => void handleStatusToggle(profile, next)} + /> + ))}
)} {reorderError && ( @@ -687,110 +503,3 @@ function ManageProfilesModalInner({ ); } - -// --------------------------------------------------------------------------- -// BlockedDeleteModal -// --------------------------------------------------------------------------- - -function BlockedDeleteModal({ - blocked, - availableReplacements, - replacement, - onReplacementChange, - error, - saving, - onClose, - onConfirm, -}: { - blocked: BlockedDeleteState | null; - availableReplacements: ProfileWithName[]; - replacement: string; - onReplacementChange: (value: string) => void; - error: string | null; - saving: boolean; - onClose: () => void; - onConfirm: () => void; -}) { - let summary = ""; - if (blocked) { - const display = blocked.label || blocked.name; - if (blocked.isActive && blocked.callSiteIds.length > 0) { - summary = `"${display}" is the active profile and is used by ${blocked.callSiteIds.length} call site(s). Pick a replacement profile.`; - } else if (blocked.isActive) { - summary = `"${display}" is the active profile. Pick a different active profile before deleting, or select a replacement below.`; - } else { - summary = `"${display}" is used by ${blocked.callSiteIds.length} call site(s). Select a replacement profile to reassign them before deleting.`; - } - } - - return ( - { - if (!next) onClose(); - }} - > - - - Can't Delete Profile - - - - {summary} - - {blocked && blocked.callSiteIds.length > 0 && ( -
    - {blocked.callSiteIds.map((id) => ( -
  • - • {id} -
  • - ))} -
- )} -
- - ({ - value: p.name, - label: p.label ?? p.name, - })), - ]} - /> -
- {error && ( - - {error} - - )} -
- - - - -
-
- ); -}