mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-10-22 01:53:33 +00:00
🧼 fix: Clear Model Spec Fields on Configured Selections (#15544)
This commit is contained in:
parent
2d794a0439
commit
ac55b5fad4
8 changed files with 228 additions and 11 deletions
|
|
@ -17,8 +17,8 @@ import {
|
|||
AgentListResponse,
|
||||
} from 'librechat-data-provider';
|
||||
import type t from 'librechat-data-provider';
|
||||
import { renderAgentAvatar, clearMessagesCache, specDisplayFieldReset } from '~/utils';
|
||||
import { useLocalize, useDefaultConvo, useFavorites } from '~/hooks';
|
||||
import { renderAgentAvatar, clearMessagesCache } from '~/utils';
|
||||
import { useChatContext } from '~/Providers';
|
||||
|
||||
interface SupportContact {
|
||||
|
|
@ -79,6 +79,7 @@ const AgentDetail: React.FC<AgentDetailProps> = ({ agent, isOpen, onClose }) =>
|
|||
endpoint: EModelEndpoint.agents,
|
||||
agent_id: agent.id,
|
||||
title: localize('com_agents_chat_with', { name: agent.name || localize('com_ui_agent') }),
|
||||
...specDisplayFieldReset,
|
||||
};
|
||||
|
||||
const currentConvo = getDefaultConversation({
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@ import {
|
|||
AgentListResponse,
|
||||
} from 'librechat-data-provider';
|
||||
import type t from 'librechat-data-provider';
|
||||
import { renderAgentAvatar, clearMessagesCache, specDisplayFieldReset } from '~/utils';
|
||||
import { useLocalize, useDefaultConvo, useFavorites } from '~/hooks';
|
||||
import { renderAgentAvatar, clearMessagesCache } from '~/utils';
|
||||
import { useChatContext } from '~/Providers';
|
||||
import AgentContact from './AgentContact';
|
||||
|
||||
|
|
@ -64,6 +64,7 @@ const AgentDetailContent: React.FC<AgentDetailContentProps> = ({ agent }) => {
|
|||
endpoint: EModelEndpoint.agents,
|
||||
agent_id: agent.id,
|
||||
title: localize('com_agents_chat_with', { name: agent.name || localize('com_ui_agent') }),
|
||||
...specDisplayFieldReset,
|
||||
};
|
||||
|
||||
const currentConvo = getDefaultConversation({
|
||||
|
|
|
|||
|
|
@ -49,6 +49,15 @@ jest.mock('~/utils/agents', () => ({
|
|||
)),
|
||||
}));
|
||||
|
||||
jest.mock('~/utils/endpoints', () => ({
|
||||
specDisplayFieldReset: {
|
||||
spec: null,
|
||||
iconURL: null,
|
||||
modelLabel: null,
|
||||
greeting: undefined,
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('~/Providers', () => ({
|
||||
useChatContext: jest.fn(),
|
||||
}));
|
||||
|
|
@ -260,10 +269,52 @@ describe('AgentDetail', () => {
|
|||
endpoint: EModelEndpoint.agents,
|
||||
agent_id: 'test-agent-id',
|
||||
title: 'Chat with Test Agent',
|
||||
spec: null,
|
||||
iconURL: null,
|
||||
modelLabel: null,
|
||||
greeting: undefined,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should clear model spec display fields when starting an agent chat', async () => {
|
||||
const user = userEvent.setup();
|
||||
const mockNewConversation = jest.fn();
|
||||
const { useChatContext } = require('~/Providers');
|
||||
(useChatContext as jest.Mock).mockReturnValue({
|
||||
conversation: {
|
||||
conversationId: 'test-convo-id',
|
||||
spec: 'ClickHouse Agent',
|
||||
iconURL: '/images/clickhouse.svg',
|
||||
modelLabel: 'ClickHouse Agent',
|
||||
},
|
||||
newConversation: mockNewConversation,
|
||||
});
|
||||
const { useDefaultConvo } = require('~/hooks');
|
||||
(useDefaultConvo as jest.Mock).mockReturnValue(({ conversation, preset }) => ({
|
||||
...conversation,
|
||||
...preset,
|
||||
}));
|
||||
|
||||
renderWithProviders(<AgentDetail {...defaultProps} />);
|
||||
await user.click(screen.getByRole('button', { name: 'com_agents_start_chat' }));
|
||||
|
||||
expect(mockNewConversation).toHaveBeenCalledWith({
|
||||
template: expect.objectContaining({
|
||||
endpoint: EModelEndpoint.agents,
|
||||
agent_id: 'test-agent-id',
|
||||
spec: null,
|
||||
iconURL: null,
|
||||
modelLabel: null,
|
||||
}),
|
||||
preset: expect.objectContaining({
|
||||
spec: null,
|
||||
iconURL: null,
|
||||
modelLabel: null,
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
it('should not navigate when agent is null', async () => {
|
||||
const user = userEvent.setup();
|
||||
renderWithProviders(<AgentDetail {...defaultProps} agent={null as any} />);
|
||||
|
|
|
|||
|
|
@ -1,9 +1,14 @@
|
|||
import React from 'react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import type { TConversation } from 'librechat-data-provider';
|
||||
import '@testing-library/jest-dom';
|
||||
import AgentDetailContent from '../AgentDetailContent';
|
||||
|
||||
const mockNewConversation = jest.fn();
|
||||
let mockConversation: Partial<TConversation> | undefined;
|
||||
|
||||
jest.mock('librechat-data-provider', () => ({
|
||||
QueryKeys: {
|
||||
agents: 'agents',
|
||||
|
|
@ -62,8 +67,8 @@ jest.mock('~/hooks', () => ({
|
|||
|
||||
jest.mock('~/Providers', () => ({
|
||||
useChatContext: () => ({
|
||||
conversation: undefined,
|
||||
newConversation: jest.fn(),
|
||||
conversation: mockConversation,
|
||||
newConversation: mockNewConversation,
|
||||
}),
|
||||
}));
|
||||
|
||||
|
|
@ -71,6 +76,12 @@ jest.mock('~/utils', () => ({
|
|||
cn: (...classes: string[]) => classes.filter(Boolean).join(' '),
|
||||
clearMessagesCache: jest.fn(),
|
||||
renderAgentAvatar: () => <div data-testid="agent-avatar" />,
|
||||
specDisplayFieldReset: {
|
||||
spec: null,
|
||||
iconURL: null,
|
||||
modelLabel: null,
|
||||
greeting: undefined,
|
||||
},
|
||||
}));
|
||||
|
||||
const renderWithClient = (children: React.ReactNode) => {
|
||||
|
|
@ -88,9 +99,14 @@ const baseAgent = {
|
|||
provider: 'openai',
|
||||
model: 'gpt-4',
|
||||
model_parameters: {},
|
||||
};
|
||||
} as React.ComponentProps<typeof AgentDetailContent>['agent'];
|
||||
|
||||
describe('AgentDetailContent', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockConversation = undefined;
|
||||
});
|
||||
|
||||
it('renders support contact with mailto link', () => {
|
||||
renderWithClient(
|
||||
<AgentDetailContent
|
||||
|
|
@ -127,4 +143,32 @@ describe('AgentDetailContent', () => {
|
|||
expect(screen.getByText('Owner User')).toBeInTheDocument();
|
||||
expect(screen.queryByRole('link', { name: 'Owner User' })).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('clears model spec display fields when starting an agent chat', async () => {
|
||||
const user = userEvent.setup();
|
||||
mockConversation = {
|
||||
conversationId: 'existing-conversation',
|
||||
spec: 'ClickHouse Agent',
|
||||
iconURL: '/images/clickhouse.svg',
|
||||
modelLabel: 'ClickHouse Agent',
|
||||
};
|
||||
|
||||
renderWithClient(<AgentDetailContent agent={baseAgent} />);
|
||||
await user.click(screen.getByRole('button', { name: 'Start chat' }));
|
||||
|
||||
expect(mockNewConversation).toHaveBeenCalledWith({
|
||||
template: expect.objectContaining({
|
||||
endpoint: 'agents',
|
||||
agent_id: 'agent-1',
|
||||
spec: null,
|
||||
iconURL: null,
|
||||
modelLabel: null,
|
||||
}),
|
||||
preset: expect.objectContaining({
|
||||
spec: null,
|
||||
iconURL: null,
|
||||
modelLabel: null,
|
||||
}),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -30,7 +30,15 @@ jest.mock('~/Providers/AgentsMapContext', () => ({
|
|||
useAgentsMapContext: jest.fn(() => ({ 'agent-1': { id: 'agent-1', name: 'Agent' } })),
|
||||
}));
|
||||
|
||||
jest.mock('~/utils', () => ({ logger: { log: jest.fn() } }));
|
||||
jest.mock('~/utils', () => ({
|
||||
logger: { log: jest.fn() },
|
||||
specDisplayFieldReset: {
|
||||
spec: null,
|
||||
iconURL: null,
|
||||
modelLabel: null,
|
||||
greeting: undefined,
|
||||
},
|
||||
}));
|
||||
|
||||
import useSelectAgent from '../useSelectAgent';
|
||||
|
||||
|
|
@ -39,7 +47,16 @@ describe('useSelectAgent', () => {
|
|||
jest.clearAllMocks();
|
||||
mockGetConversation.mockResolvedValue({ endpoint: EModelEndpoint.agents } as TConversation);
|
||||
mockGetDefaultConversation.mockImplementation(
|
||||
({ conversation }: { conversation: Partial<TConversation> }) => conversation,
|
||||
({
|
||||
conversation,
|
||||
preset,
|
||||
}: {
|
||||
conversation: Partial<TConversation>;
|
||||
preset: Partial<TConversation>;
|
||||
}) => ({
|
||||
...conversation,
|
||||
...preset,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
|
|
@ -85,4 +102,28 @@ describe('useSelectAgent', () => {
|
|||
expect(mockNewConversation.mock.calls[0][0].keepComposerState).toBe(false);
|
||||
expect(mockNewConversation.mock.calls[1][0].keepComposerState).toBe(true);
|
||||
});
|
||||
|
||||
it('clears model spec display fields when selecting an agent', async () => {
|
||||
mockGetConversation.mockResolvedValue({
|
||||
endpoint: EModelEndpoint.openAI,
|
||||
spec: 'ClickHouse Agent',
|
||||
iconURL: EModelEndpoint.bedrock,
|
||||
modelLabel: 'ClickHouse Agent',
|
||||
} as TConversation);
|
||||
mockFetchQuery.mockResolvedValue({ id: 'agent-1', name: 'Full Agent' });
|
||||
const { result } = renderHook(() => useSelectAgent());
|
||||
|
||||
await act(async () => {
|
||||
await result.current.onSelect('agent-1');
|
||||
});
|
||||
|
||||
const firstConversation = mockNewConversation.mock.calls[0][0].template;
|
||||
expect(firstConversation).toMatchObject({
|
||||
endpoint: EModelEndpoint.agents,
|
||||
agent_id: 'agent-1',
|
||||
spec: null,
|
||||
iconURL: null,
|
||||
modelLabel: null,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@ import type { TConversation, TPreset, Agent } from 'librechat-data-provider';
|
|||
import useGetConversation from '~/hooks/Conversations/useGetConversation';
|
||||
import useDefaultConvo from '~/hooks/Conversations/useDefaultConvo';
|
||||
import { useAgentsMapContext } from '~/Providers/AgentsMapContext';
|
||||
import { logger, specDisplayFieldReset } from '~/utils';
|
||||
import useNewConvo from '~/hooks/useNewConvo';
|
||||
import { logger } from '~/utils';
|
||||
|
||||
export default function useSelectAgent() {
|
||||
const queryClient = useQueryClient();
|
||||
|
|
@ -40,7 +40,7 @@ export default function useSelectAgent() {
|
|||
return;
|
||||
}
|
||||
const currentConvo = getDefaultConversation({
|
||||
conversation: { ...(conversation ?? {}), agent_id: agent.id },
|
||||
conversation: { ...(conversation ?? {}), agent_id: agent.id, ...specDisplayFieldReset },
|
||||
preset: template,
|
||||
});
|
||||
newConversation({
|
||||
|
|
@ -63,6 +63,7 @@ export default function useSelectAgent() {
|
|||
endpoint: EModelEndpoint.agents,
|
||||
agent_id: agent.id,
|
||||
conversationId: Constants.NEW_CONVO as string,
|
||||
...specDisplayFieldReset,
|
||||
};
|
||||
|
||||
await updateConversation({ id: agent.id }, template);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,77 @@
|
|||
import { renderHook, act } from '@testing-library/react';
|
||||
import { EModelEndpoint } from 'librechat-data-provider';
|
||||
|
||||
const mockNewConversation = jest.fn();
|
||||
const mockGetDefaultConversation = jest.fn();
|
||||
|
||||
jest.mock('~/Providers/ChatContext', () => ({
|
||||
useChatContext: jest.fn(() => ({
|
||||
conversation: {
|
||||
endpoint: 'assistants',
|
||||
spec: 'Assistant Spec',
|
||||
iconURL: '/images/assistant.svg',
|
||||
modelLabel: 'Assistant Spec',
|
||||
},
|
||||
newConversation: mockNewConversation,
|
||||
})),
|
||||
}));
|
||||
|
||||
jest.mock('~/hooks/Conversations/useDefaultConvo', () => ({
|
||||
__esModule: true,
|
||||
default: jest.fn(() => mockGetDefaultConversation),
|
||||
}));
|
||||
|
||||
jest.mock('../useAssistantListMap', () => ({
|
||||
__esModule: true,
|
||||
default: jest.fn(() => ({
|
||||
assistants: {
|
||||
'assistant-1': { id: 'assistant-1', model: 'gpt-4' },
|
||||
},
|
||||
})),
|
||||
}));
|
||||
|
||||
jest.mock('~/utils', () => ({
|
||||
logger: { log: jest.fn() },
|
||||
mapAssistants: jest.fn(),
|
||||
specDisplayFieldReset: {
|
||||
spec: null,
|
||||
iconURL: null,
|
||||
modelLabel: null,
|
||||
greeting: undefined,
|
||||
},
|
||||
}));
|
||||
|
||||
import useSelectAssistant from '../useSelectAssistant';
|
||||
|
||||
describe('useSelectAssistant', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockGetDefaultConversation.mockImplementation(({ conversation, preset }) => ({
|
||||
...conversation,
|
||||
...preset,
|
||||
}));
|
||||
});
|
||||
|
||||
it('clears model spec display fields when selecting an assistant', () => {
|
||||
const { result } = renderHook(() => useSelectAssistant(EModelEndpoint.assistants));
|
||||
|
||||
act(() => {
|
||||
result.current.onSelect('assistant-1');
|
||||
});
|
||||
|
||||
expect(mockNewConversation).toHaveBeenCalledWith({
|
||||
template: expect.objectContaining({
|
||||
endpoint: EModelEndpoint.assistants,
|
||||
assistant_id: 'assistant-1',
|
||||
spec: null,
|
||||
iconURL: null,
|
||||
modelLabel: null,
|
||||
}),
|
||||
preset: expect.objectContaining({
|
||||
spec: null,
|
||||
iconURL: null,
|
||||
modelLabel: null,
|
||||
}),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
import { useCallback } from 'react';
|
||||
import { isAssistantsEndpoint } from 'librechat-data-provider';
|
||||
import type { AssistantsEndpoint, TConversation, TPreset } from 'librechat-data-provider';
|
||||
import { mapAssistants, logger, specDisplayFieldReset } from '~/utils';
|
||||
import useDefaultConvo from '~/hooks/Conversations/useDefaultConvo';
|
||||
import { useChatContext } from '~/Providers/ChatContext';
|
||||
import useAssistantListMap from './useAssistantListMap';
|
||||
import { mapAssistants, logger } from '~/utils';
|
||||
|
||||
export default function useSelectAssistant(endpoint: AssistantsEndpoint) {
|
||||
const getDefaultConversation = useDefaultConvo();
|
||||
|
|
@ -22,12 +22,13 @@ export default function useSelectAssistant(endpoint: AssistantsEndpoint) {
|
|||
assistant_id: assistant.id,
|
||||
model: assistant.model,
|
||||
conversationId: 'new',
|
||||
...specDisplayFieldReset,
|
||||
};
|
||||
|
||||
logger.log('conversation', 'Updating conversation with assistant', assistant);
|
||||
if (isAssistantsEndpoint(conversation?.endpoint)) {
|
||||
const currentConvo = getDefaultConversation({
|
||||
conversation: { ...(conversation ?? {}) },
|
||||
conversation: { ...(conversation ?? {}), ...specDisplayFieldReset },
|
||||
preset: template,
|
||||
});
|
||||
newConversation({
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue