mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-01-01 16:18:51 +01:00
- Added `useUpdateSearchParams` hook to manage URL search parameters dynamically. - Updated `Landing`, `NewChat`, and various parameter components to utilize the new hook for better state management. - Enhanced styling for dark mode support in chat components. - Refactored click handlers in `NewChat` to create chat search parameters for navigation. - Improved accessibility and usability in `DynamicCombobox`, `DynamicInput`, `DynamicSlider`, `DynamicSwitch`, and `DynamicTags` components. - Introduced `createChatSearchParams` utility to streamline query parameter creation for chat sessions. remove icon borders in dark mode
310 lines
11 KiB
TypeScript
310 lines
11 KiB
TypeScript
import { EModelEndpoint } from 'librechat-data-provider';
|
|
import type { TConversation, TPreset } from 'librechat-data-provider';
|
|
import createChatSearchParams from './createChatSearchParams';
|
|
|
|
describe('createChatSearchParams', () => {
|
|
describe('conversation inputs', () => {
|
|
it('handles basic conversation properties', () => {
|
|
const conversation: Partial<TConversation> = {
|
|
endpoint: EModelEndpoint.openAI,
|
|
model: 'gpt-4',
|
|
temperature: 0.7,
|
|
};
|
|
|
|
const result = createChatSearchParams(conversation as TConversation);
|
|
expect(result.get('endpoint')).toBe(EModelEndpoint.openAI);
|
|
expect(result.get('model')).toBe('gpt-4');
|
|
expect(result.get('temperature')).toBe('0.7');
|
|
});
|
|
|
|
it('applies only the endpoint property when other conversation fields are absent', () => {
|
|
const endpointOnly = createChatSearchParams({
|
|
endpoint: EModelEndpoint.openAI,
|
|
} as TConversation);
|
|
expect(endpointOnly.get('endpoint')).toBe(EModelEndpoint.openAI);
|
|
expect(endpointOnly.has('model')).toBe(false);
|
|
expect(endpointOnly.has('endpoint')).toBe(true);
|
|
});
|
|
|
|
it('applies only the model property when other conversation fields are absent', () => {
|
|
const modelOnly = createChatSearchParams({ model: 'gpt-4' } as TConversation);
|
|
expect(modelOnly.has('endpoint')).toBe(false);
|
|
expect(modelOnly.get('model')).toBe('gpt-4');
|
|
expect(modelOnly.has('model')).toBe(true);
|
|
});
|
|
|
|
it('used to include endpoint and model with assistant_id but now only includes assistant_id', () => {
|
|
const withAssistantId = createChatSearchParams({
|
|
endpoint: EModelEndpoint.openAI,
|
|
model: 'gpt-4',
|
|
assistant_id: 'asst_123',
|
|
temperature: 0.7,
|
|
} as TConversation);
|
|
|
|
expect(withAssistantId.get('assistant_id')).toBe('asst_123');
|
|
expect(withAssistantId.has('endpoint')).toBe(false);
|
|
expect(withAssistantId.has('model')).toBe(false);
|
|
expect(withAssistantId.has('temperature')).toBe(false);
|
|
});
|
|
|
|
it('used to include endpoint and model with agent_id but now only includes agent_id', () => {
|
|
const withAgentId = createChatSearchParams({
|
|
endpoint: EModelEndpoint.openAI,
|
|
model: 'gpt-4',
|
|
agent_id: 'agent_123',
|
|
temperature: 0.7,
|
|
} as TConversation);
|
|
|
|
expect(withAgentId.get('agent_id')).toBe('agent_123');
|
|
expect(withAgentId.has('endpoint')).toBe(false);
|
|
expect(withAgentId.has('model')).toBe(false);
|
|
expect(withAgentId.has('temperature')).toBe(false);
|
|
});
|
|
|
|
it('when assistant_id is present, only include that param (excluding endpoint, model, etc)', () => {
|
|
const withAssistantId = createChatSearchParams({
|
|
endpoint: EModelEndpoint.openAI,
|
|
model: 'gpt-4',
|
|
assistant_id: 'asst_123',
|
|
temperature: 0.7,
|
|
} as TConversation);
|
|
|
|
expect(withAssistantId.get('assistant_id')).toBe('asst_123');
|
|
expect(withAssistantId.has('endpoint')).toBe(false);
|
|
expect(withAssistantId.has('model')).toBe(false);
|
|
expect(withAssistantId.has('temperature')).toBe(false);
|
|
expect([...withAssistantId.entries()].length).toBe(1);
|
|
});
|
|
|
|
it('when agent_id is present, only include that param (excluding endpoint, model, etc)', () => {
|
|
const withAgentId = createChatSearchParams({
|
|
endpoint: EModelEndpoint.openAI,
|
|
model: 'gpt-4',
|
|
agent_id: 'agent_123',
|
|
temperature: 0.7,
|
|
} as TConversation);
|
|
|
|
expect(withAgentId.get('agent_id')).toBe('agent_123');
|
|
expect(withAgentId.has('endpoint')).toBe(false);
|
|
expect(withAgentId.has('model')).toBe(false);
|
|
expect(withAgentId.has('temperature')).toBe(false);
|
|
expect([...withAgentId.entries()].length).toBe(1);
|
|
});
|
|
|
|
it('handles stop arrays correctly by joining with commas', () => {
|
|
const withStopArray = createChatSearchParams({
|
|
endpoint: EModelEndpoint.openAI,
|
|
model: 'gpt-4',
|
|
stop: ['stop1', 'stop2'],
|
|
} as TConversation);
|
|
|
|
expect(withStopArray.get('endpoint')).toBe(EModelEndpoint.openAI);
|
|
expect(withStopArray.get('model')).toBe('gpt-4');
|
|
expect(withStopArray.get('stop')).toBe('stop1,stop2');
|
|
});
|
|
|
|
it('filters out non-supported array properties', () => {
|
|
const withOtherArray = createChatSearchParams({
|
|
endpoint: EModelEndpoint.openAI,
|
|
model: 'gpt-4',
|
|
otherArrayProp: ['value1', 'value2'],
|
|
} as any);
|
|
|
|
expect(withOtherArray.get('endpoint')).toBe(EModelEndpoint.openAI);
|
|
expect(withOtherArray.get('model')).toBe('gpt-4');
|
|
expect(withOtherArray.has('otherArrayProp')).toBe(false);
|
|
});
|
|
|
|
it('includes empty arrays in output params', () => {
|
|
const result = createChatSearchParams({
|
|
endpoint: EModelEndpoint.openAI,
|
|
stop: [],
|
|
});
|
|
|
|
expect(result.get('endpoint')).toBe(EModelEndpoint.openAI);
|
|
expect(result.has('stop')).toBe(true);
|
|
expect(result.get('stop')).toBe('');
|
|
});
|
|
|
|
it('handles non-stop arrays correctly in paramMap', () => {
|
|
const conversation: any = {
|
|
endpoint: EModelEndpoint.openAI,
|
|
model: 'gpt-4',
|
|
top_p: ['0.7', '0.8'],
|
|
};
|
|
|
|
const result = createChatSearchParams(conversation);
|
|
|
|
const expectedJson = JSON.stringify(['0.7', '0.8']);
|
|
expect(result.get('top_p')).toBe(expectedJson);
|
|
expect(result.get('endpoint')).toBe(EModelEndpoint.openAI);
|
|
expect(result.get('model')).toBe('gpt-4');
|
|
});
|
|
|
|
it('includes empty non-stop arrays as serialized empty arrays', () => {
|
|
const result = createChatSearchParams({
|
|
endpoint: EModelEndpoint.openAI,
|
|
model: 'gpt-4',
|
|
temperature: 0.7,
|
|
top_p: [],
|
|
} as any);
|
|
|
|
expect(result.get('endpoint')).toBe(EModelEndpoint.openAI);
|
|
expect(result.get('model')).toBe('gpt-4');
|
|
expect(result.get('temperature')).toBe('0.7');
|
|
expect(result.has('top_p')).toBe(true);
|
|
expect(result.get('top_p')).toBe('[]');
|
|
});
|
|
|
|
it('excludes parameters with null or undefined values from the output', () => {
|
|
const result = createChatSearchParams({
|
|
endpoint: EModelEndpoint.openAI,
|
|
model: 'gpt-4',
|
|
temperature: 0.7,
|
|
top_p: undefined,
|
|
presence_penalty: undefined,
|
|
frequency_penalty: null,
|
|
} as any);
|
|
|
|
expect(result.get('endpoint')).toBe(EModelEndpoint.openAI);
|
|
expect(result.get('model')).toBe('gpt-4');
|
|
expect(result.get('temperature')).toBe('0.7');
|
|
expect(result.has('top_p')).toBe(false);
|
|
expect(result.has('presence_penalty')).toBe(false);
|
|
expect(result.has('frequency_penalty')).toBe(false);
|
|
expect(result).toBeDefined();
|
|
});
|
|
|
|
it('handles float parameter values correctly', () => {
|
|
const result = createChatSearchParams({
|
|
endpoint: EModelEndpoint.google,
|
|
model: 'gemini-pro',
|
|
frequency_penalty: 0.25,
|
|
temperature: 0.75,
|
|
});
|
|
|
|
expect(result.get('endpoint')).toBe(EModelEndpoint.google);
|
|
expect(result.get('model')).toBe('gemini-pro');
|
|
expect(result.get('frequency_penalty')).toBe('0.25');
|
|
expect(result.get('temperature')).toBe('0.75');
|
|
});
|
|
|
|
it('handles integer parameter values correctly', () => {
|
|
const result = createChatSearchParams({
|
|
endpoint: EModelEndpoint.google,
|
|
model: 'gemini-pro',
|
|
topK: 40,
|
|
maxOutputTokens: 2048,
|
|
});
|
|
|
|
expect(result.get('endpoint')).toBe(EModelEndpoint.google);
|
|
expect(result.get('model')).toBe('gemini-pro');
|
|
expect(result.get('topK')).toBe('40');
|
|
expect(result.get('maxOutputTokens')).toBe('2048');
|
|
});
|
|
});
|
|
|
|
describe('preset inputs', () => {
|
|
it('handles preset objects correctly', () => {
|
|
const preset: Partial<TPreset> = {
|
|
endpoint: EModelEndpoint.google,
|
|
model: 'gemini-pro',
|
|
temperature: 0.5,
|
|
topP: 0.8,
|
|
};
|
|
|
|
const result = createChatSearchParams(preset as TPreset);
|
|
expect(result.get('endpoint')).toBe(EModelEndpoint.google);
|
|
expect(result.get('model')).toBe('gemini-pro');
|
|
expect(result.get('temperature')).toBe('0.5');
|
|
expect(result.get('topP')).toBe('0.8');
|
|
});
|
|
});
|
|
|
|
describe('record inputs', () => {
|
|
it('includes allowed parameters from Record inputs', () => {
|
|
const record: Record<string, any> = {
|
|
endpoint: EModelEndpoint.anthropic,
|
|
model: 'claude-2',
|
|
temperature: '0.8',
|
|
top_p: '0.95',
|
|
extraParam: 'should-not-be-included',
|
|
invalidParam1: 'value1',
|
|
invalidParam2: 'value2',
|
|
};
|
|
|
|
const result = createChatSearchParams(record);
|
|
expect(result.get('endpoint')).toBe(EModelEndpoint.anthropic);
|
|
expect(result.get('model')).toBe('claude-2');
|
|
expect(result.get('temperature')).toBe('0.8');
|
|
expect(result.get('top_p')).toBe('0.95');
|
|
});
|
|
|
|
it('excludes disallowed parameters from Record inputs', () => {
|
|
const record: Record<string, any> = {
|
|
endpoint: EModelEndpoint.anthropic,
|
|
model: 'claude-2',
|
|
extraParam: 'should-not-be-included',
|
|
invalidParam1: 'value1',
|
|
invalidParam2: 'value2',
|
|
};
|
|
|
|
const result = createChatSearchParams(record);
|
|
expect(result.has('extraParam')).toBe(false);
|
|
expect(result.has('invalidParam1')).toBe(false);
|
|
expect(result.has('invalidParam2')).toBe(false);
|
|
expect(result.toString().includes('invalidParam')).toBe(false);
|
|
expect(result.toString().includes('extraParam')).toBe(false);
|
|
});
|
|
|
|
it('includes valid values from Record inputs', () => {
|
|
const record: Record<string, any> = {
|
|
temperature: '0.7',
|
|
top_p: null,
|
|
frequency_penalty: undefined,
|
|
};
|
|
|
|
const result = createChatSearchParams(record);
|
|
expect(result.get('temperature')).toBe('0.7');
|
|
});
|
|
|
|
it('excludes null or undefined values from Record inputs', () => {
|
|
const record: Record<string, any> = {
|
|
temperature: '0.7',
|
|
top_p: null,
|
|
frequency_penalty: undefined,
|
|
};
|
|
|
|
const result = createChatSearchParams(record);
|
|
expect(result.has('top_p')).toBe(false);
|
|
expect(result.has('frequency_penalty')).toBe(false);
|
|
});
|
|
|
|
it('handles generic object without endpoint or model properties', () => {
|
|
const customObject = {
|
|
temperature: '0.5',
|
|
top_p: '0.7',
|
|
customProperty: 'value',
|
|
};
|
|
|
|
const result = createChatSearchParams(customObject);
|
|
expect(result.get('temperature')).toBe('0.5');
|
|
expect(result.get('top_p')).toBe('0.7');
|
|
expect(result.has('customProperty')).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('edge cases', () => {
|
|
it('returns an empty URLSearchParams instance when input is null', () => {
|
|
const result = createChatSearchParams(null);
|
|
expect(result.toString()).toBe('');
|
|
expect(result instanceof URLSearchParams).toBe(true);
|
|
});
|
|
|
|
it('returns an empty URLSearchParams instance for an empty object input', () => {
|
|
const result = createChatSearchParams({});
|
|
expect(result.toString()).toBe('');
|
|
expect(result instanceof URLSearchParams).toBe(true);
|
|
});
|
|
});
|
|
});
|