fix: Ignore programmatic agent form resets

This commit is contained in:
Danny Avila 2026-05-18 09:44:01 -04:00
parent 66614bbebc
commit 897cc50187
2 changed files with 48 additions and 6 deletions

View file

@ -11,7 +11,7 @@ import {
PermissionBits,
isAssistantsEndpoint,
} from 'librechat-data-provider';
import type { FieldNamesMarkedBoolean } from 'react-hook-form';
import type { FieldNamesMarkedBoolean, FieldPath } from 'react-hook-form';
import type { Agent } from 'librechat-data-provider';
import type { TranslationKeys } from '~/hooks/useLocalize';
import type { AgentForm, StringOption } from '~/common';
@ -280,6 +280,7 @@ export default function AgentPanel() {
reset,
watch,
getValues,
getFieldState,
setValue,
formState: { dirtyFields },
} = methods;
@ -316,6 +317,17 @@ export default function AgentPanel() {
const shouldPersistDraft = hasPersistableDirtyFields(dirtyFields);
const isDirtyPersistableDraftField = useCallback(
(name?: string): boolean => {
if (!isPersistableDraftField(name)) {
return false;
}
return getFieldState(name as FieldPath<AgentForm>).isDirty;
},
[getFieldState],
);
useEffect(() => {
if (currentAgentIdRef.current === current_agent_id) {
return;
@ -348,7 +360,7 @@ export default function AgentPanel() {
useEffect(() => {
const subscription = watch((_, { name }) => {
if (!shouldPersistDraftRef.current && !isPersistableDraftField(name)) {
if (!shouldPersistDraftRef.current && !isDirtyPersistableDraftField(name)) {
return;
}
@ -362,7 +374,7 @@ export default function AgentPanel() {
}
subscription.unsubscribe();
};
}, [getValues, persistAgentDraft, watch]);
}, [getValues, isDirtyPersistableDraftField, persistAgentDraft, watch]);
const uploadAvatarMutation = useUploadAgentAvatarMutation({
onSuccess: (updatedAgent) => {

View file

@ -2,7 +2,7 @@
* @jest-environment jsdom
*/
import * as React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { describe, it, expect, beforeEach, jest } from '@jest/globals';
import type { AgentForm, NavLink } from '~/common';
import { ActivePanelProvider, useActivePanel } from '~/Providers/ActivePanelContext';
@ -21,6 +21,7 @@ const SAVE_AGENT_LABEL = 'Save Agent';
const AGENTS_BUTTON_LABEL = 'Agents';
const FILES_BUTTON_LABEL = 'Files';
const FILES_PANEL_LABEL = 'Files panel';
const PROGRAMMATIC_UPDATE_LABEL = 'Programmatic agent update';
type MockButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> & {
size?: string;
@ -136,9 +137,24 @@ jest.mock('~/common', () => {
jest.mock('../AgentSelect', () => ({
__esModule: true,
default: ({ hasDraft }: MockAgentSelectProps) => {
default: function MockAgentSelect({ hasDraft }: MockAgentSelectProps) {
const { useFormContext } = jest.requireActual(
'react-hook-form',
) as typeof import('react-hook-form');
const { setValue } = useFormContext<AgentForm>();
mockLastAgentSelectHasDraft = hasDraft;
return <div>{AGENT_SELECT_LABEL}</div>;
return (
<>
<div>{AGENT_SELECT_LABEL}</div>
<button
type="button"
onClick={() => setValue('name', 'Saved from API', { shouldDirty: false })}
>
{PROGRAMMATIC_UPDATE_LABEL}
</button>
</>
);
},
}));
@ -283,4 +299,18 @@ describe('AgentPanel draft preservation', () => {
expect(getAgentDraft(undefined)).toBeUndefined();
expect(mockLastAgentSelectHasDraft).toBe(false);
});
it('does not save a draft for programmatic form updates', async () => {
mockCurrentAgentId = 'agent-123';
render(<Harness />);
fireEvent.click(screen.getByRole('button', { name: PROGRAMMATIC_UPDATE_LABEL }));
await waitFor(() => {
expect(screen.getByLabelText('Draft name')).toHaveValue('Saved from API');
});
expect(getAgentDraft('agent-123')).toBeUndefined();
expect(mockLastAgentSelectHasDraft).toBe(false);
});
});