diff --git a/__tests__/components/settings/llm-profiles/llm-settings-local-view.test.tsx b/__tests__/components/settings/llm-profiles/llm-settings-local-view.test.tsx index 0d8ea3feb..4a4a97c8d 100644 --- a/__tests__/components/settings/llm-profiles/llm-settings-local-view.test.tsx +++ b/__tests__/components/settings/llm-profiles/llm-settings-local-view.test.tsx @@ -3,7 +3,10 @@ import { AxiosError } from "axios"; import { screen, waitFor } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { renderWithProviders } from "test-utils"; -import { LlmSettingsLocalView } from "#/components/features/settings/llm-profiles/llm-settings-local-view"; +import { + LlmSettingsLocalView, + shouldReapplyProfileAfterSave, +} from "#/components/features/settings/llm-profiles/llm-settings-local-view"; import * as useLlmProfilesHook from "#/hooks/query/use-llm-profiles"; import * as useActivateLlmProfileHook from "#/hooks/mutation/use-activate-llm-profile"; import * as useSaveLlmProfileHook from "#/hooks/mutation/use-save-llm-profile"; @@ -487,8 +490,7 @@ describe("LlmSettingsLocalView", () => { message: "Profile renamed", }); - // Mock activateProfile - vi.mocked(ProfilesService.activateProfile).mockResolvedValue({ + mockActivateMutateAsync.mockResolvedValue({ name: "my-renamed-profile", message: "Profile activated", llm_applied: true, @@ -516,9 +518,9 @@ describe("LlmSettingsLocalView", () => { // Click save await user.click(screen.getByTestId("save-profile-btn")); - // Verify activateProfile was called after rename and save + // Verify activation mutation was called after rename and save await waitFor(() => { - expect(ProfilesService.activateProfile).toHaveBeenCalledWith( + expect(mockActivateMutateAsync).toHaveBeenCalledWith( "my-renamed-profile", ); }); @@ -631,3 +633,45 @@ describe("LlmSettingsLocalView", () => { }); }); }); + +describe("shouldReapplyProfileAfterSave", () => { + it("reapplies when saving the active profile without renaming", () => { + expect( + shouldReapplyProfileAfterSave({ + activeProfileName: "gpt-4-profile", + originalName: "gpt-4-profile", + savedName: "gpt-4-profile", + }), + ).toBe(true); + }); + + it("reapplies when the active profile was renamed", () => { + expect( + shouldReapplyProfileAfterSave({ + activeProfileName: "gpt-4-profile", + originalName: "gpt-4-profile", + savedName: "my-renamed-profile", + }), + ).toBe(true); + }); + + it("reapplies when creating a profile with the active profile name", () => { + expect( + shouldReapplyProfileAfterSave({ + activeProfileName: "gpt-4-profile", + originalName: null, + savedName: "gpt-4-profile", + }), + ).toBe(true); + }); + + it("does not reapply inactive profiles", () => { + expect( + shouldReapplyProfileAfterSave({ + activeProfileName: "claude-profile", + originalName: "gpt-4-profile", + savedName: "gpt-4-profile", + }), + ).toBe(false); + }); +}); diff --git a/src/components/features/settings/llm-profiles/llm-settings-local-view.tsx b/src/components/features/settings/llm-profiles/llm-settings-local-view.tsx index df98ccd07..daa909e61 100644 --- a/src/components/features/settings/llm-profiles/llm-settings-local-view.tsx +++ b/src/components/features/settings/llm-profiles/llm-settings-local-view.tsx @@ -11,6 +11,7 @@ import { ProfileNameInput } from "./profile-name-input"; import { BrandButton } from "#/components/features/settings/brand-button"; import { LlmSettingsScreen } from "#/routes/llm-settings"; import { useSaveLlmProfile } from "#/hooks/mutation/use-save-llm-profile"; +import { useActivateLlmProfile } from "#/hooks/mutation/use-activate-llm-profile"; import { useLlmProfiles } from "#/hooks/query/use-llm-profiles"; import { useSettings } from "#/hooks/query/use-settings"; import { useAgentSettingsSchema } from "#/hooks/query/use-agent-settings-schema"; @@ -49,6 +50,20 @@ interface EditingProfile { baseConfig: Record; } +export function shouldReapplyProfileAfterSave({ + activeProfileName, + originalName, + savedName, +}: { + activeProfileName: string | null | undefined; + originalName: string | null | undefined; + savedName: string; +}): boolean { + if (!activeProfileName) return false; + if (originalName) return activeProfileName === originalName; + return activeProfileName === savedName; +} + /** * LlmSettingsLocalView provides an integrated view for managing LLM profiles * in local agent-server mode. It supports listing, creating, and editing profiles. @@ -63,6 +78,7 @@ export function LlmSettingsLocalView() { const { t } = useTranslation("openhands"); const { setHideSectionHeader } = useSettingsSectionHeader(); const saveProfile = useSaveLlmProfile(); + const activateProfile = useActivateLlmProfile(); const { data: profilesData } = useLlmProfiles(); const { data: settings } = useSettings(); const { data: agentSchema } = useAgentSettingsSchema( @@ -269,7 +285,11 @@ export function LlmSettingsLocalView() { const originalName = editingProfile?.profile.name; const isRename = viewMode === "edit" && originalName && originalName !== trimmedName; - const wasActive = profilesData?.active_profile === originalName; + const shouldReapplyActiveProfile = shouldReapplyProfileAfterSave({ + activeProfileName: profilesData?.active_profile, + originalName, + savedName: trimmedName, + }); setIsSaving(true); try { @@ -290,10 +310,11 @@ export function LlmSettingsLocalView() { }, }); - // If the renamed profile was the active profile, re-activate it - // (the rename operation doesn't automatically update active_profile) - if (isRename && wasActive) { - await ProfilesService.activateProfile(trimmedName); + // Conversation start uses agent_settings.llm, not the profile row + // directly. Re-applying an active profile keeps those settings in sync + // after editing or recreating a profile with the active profile name. + if (shouldReapplyActiveProfile) { + await activateProfile.mutateAsync(trimmedName); } displaySuccessToast( @@ -316,6 +337,7 @@ export function LlmSettingsLocalView() { editingProfile, profilesData?.active_profile, saveProfile, + activateProfile, t, handleBackToList, ]);