mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-17 17:00:15 +01:00
* chore: update @librechat/agents to v2.4.47 * WIP: temporary auto-toggle responses api for o1/o3-pro * feat: Enable Responses API for OpenAI models - Updated the OpenAI client initialization to check for the useResponsesApi parameter in model options. - Added translations for enabling the Responses API in the UI. - Introduced useResponsesApi parameter in data provider settings and schemas. - Updated relevant schemas to include useResponsesApi for conversation and preset configurations. * refactor: Remove useResponsesApi check from OpenAI client initialization and update translation for Responses API - Removed the check for useResponsesApi in the OpenAI client initialization. - Updated the translation for enabling the Responses API to clarify its functionality. * chore: update @librechat/agents dependency to version 2.4.48 * chore: update @librechat/agents dependency to version 2.4.49 * chore: linting * chore: linting * feat: Enhance DynamicSlider and validation for enumMappings - Added support for enumMappings in DynamicSlider to display values correctly based on enum settings. - Implemented validation for enumMappings in the generate function to ensure all options have corresponding mappings. - Added tests for handling empty string options and incomplete enumMappings in the generate.spec.ts file. * feat: Enhance DynamicSlider localization support - Added localization handling for mapped values in DynamicSlider when using enumMappings. - Updated the logic to check if the mapped value is a localization key and return the localized string if applicable. - Adjusted dependencies in useCallback hooks to include localize for proper functionality. * feat: Add reasoning summary and effort options to OpenAI configuration and UI * feat: Add enumMappings for ImageDetail options in parameter settings * style: Improve styling for DynamicSlider component labels and inputs * chore: Update reasoning effort description and parameter order for OpenAI params --------- Co-authored-by: Dustin Healy <dustinhealy1@gmail.com>
770 lines
25 KiB
TypeScript
770 lines
25 KiB
TypeScript
import { ZodError, z } from 'zod';
|
|
import { generateDynamicSchema, validateSettingDefinitions, OptionTypes } from '../src/generate';
|
|
import type { SettingsConfiguration } from '../src/generate';
|
|
|
|
describe('generateDynamicSchema', () => {
|
|
it('should generate a schema for number settings with range', () => {
|
|
const settings: SettingsConfiguration = [
|
|
{
|
|
key: 'testNumber',
|
|
description: 'A test number setting',
|
|
type: 'number',
|
|
default: 5,
|
|
range: { min: 1, max: 10, step: 1 },
|
|
component: 'slider',
|
|
optionType: 'conversation',
|
|
columnSpan: 2,
|
|
label: 'Test Number Slider',
|
|
},
|
|
];
|
|
|
|
const schema = generateDynamicSchema(settings);
|
|
const result = schema.safeParse({ testNumber: 6 });
|
|
|
|
expect(result.success).toBeTruthy();
|
|
expect(result['data']).toEqual({ testNumber: 6 });
|
|
});
|
|
|
|
it('should generate a schema for boolean settings', () => {
|
|
const settings: SettingsConfiguration = [
|
|
{
|
|
key: 'testBoolean',
|
|
description: 'A test boolean setting',
|
|
type: 'boolean',
|
|
default: true,
|
|
component: 'switch',
|
|
optionType: 'model', // Only if relevant to your application's context
|
|
columnSpan: 1,
|
|
label: 'Test Boolean Switch',
|
|
},
|
|
];
|
|
|
|
const schema = generateDynamicSchema(settings);
|
|
const result = schema.safeParse({ testBoolean: false });
|
|
|
|
expect(result.success).toBeTruthy();
|
|
expect(result['data']).toEqual({ testBoolean: false });
|
|
});
|
|
|
|
it('should generate a schema for string settings', () => {
|
|
const settings: SettingsConfiguration = [
|
|
{
|
|
key: 'testString',
|
|
description: 'A test string setting',
|
|
type: 'string',
|
|
default: 'default value',
|
|
component: 'input',
|
|
optionType: 'model', // Optional and only if relevant
|
|
columnSpan: 3,
|
|
label: 'Test String Input',
|
|
placeholder: 'Enter text here...',
|
|
minText: 0, // Optional
|
|
maxText: 100, // Optional
|
|
},
|
|
];
|
|
|
|
const schema = generateDynamicSchema(settings);
|
|
const result = schema.safeParse({ testString: 'custom value' });
|
|
|
|
expect(result.success).toBeTruthy();
|
|
expect(result['data']).toEqual({ testString: 'custom value' });
|
|
});
|
|
|
|
it('should generate a schema for enum settings', () => {
|
|
const settings: SettingsConfiguration = [
|
|
{
|
|
key: 'testEnum',
|
|
description: 'A test enum setting',
|
|
type: 'enum',
|
|
default: 'option1',
|
|
options: ['option1', 'option2', 'option3'],
|
|
enumMappings: {
|
|
option1: 'First Option',
|
|
option2: 'Second Option',
|
|
option3: 'Third Option',
|
|
},
|
|
component: 'dropdown',
|
|
columnSpan: 2,
|
|
label: 'Test Enum Dropdown',
|
|
},
|
|
];
|
|
|
|
const schema = generateDynamicSchema(settings);
|
|
const result = schema.safeParse({ testEnum: 'option2' });
|
|
|
|
expect(result.success).toBeTruthy();
|
|
expect(result['data']).toEqual({ testEnum: 'option2' });
|
|
});
|
|
|
|
it('should generate a schema for enum settings with empty string option', () => {
|
|
const settings: SettingsConfiguration = [
|
|
{
|
|
key: 'testEnumWithEmpty',
|
|
description: 'A test enum setting with empty string',
|
|
type: 'enum',
|
|
default: '',
|
|
options: ['', 'option1', 'option2'],
|
|
enumMappings: {
|
|
'': 'None',
|
|
option1: 'First Option',
|
|
option2: 'Second Option',
|
|
},
|
|
component: 'slider',
|
|
columnSpan: 2,
|
|
label: 'Test Enum with Empty String',
|
|
},
|
|
];
|
|
|
|
const schema = generateDynamicSchema(settings);
|
|
const result = schema.safeParse({ testEnumWithEmpty: '' });
|
|
|
|
expect(result.success).toBeTruthy();
|
|
expect(result['data']).toEqual({ testEnumWithEmpty: '' });
|
|
|
|
// Test with non-empty option
|
|
const result2 = schema.safeParse({ testEnumWithEmpty: 'option1' });
|
|
expect(result2.success).toBeTruthy();
|
|
expect(result2['data']).toEqual({ testEnumWithEmpty: 'option1' });
|
|
});
|
|
|
|
it('should fail for incorrect enum value', () => {
|
|
const settings: SettingsConfiguration = [
|
|
{
|
|
key: 'testEnum',
|
|
description: 'A test enum setting',
|
|
type: 'enum',
|
|
default: 'option1',
|
|
options: ['option1', 'option2', 'option3'],
|
|
component: 'dropdown',
|
|
},
|
|
];
|
|
|
|
const schema = generateDynamicSchema(settings);
|
|
const result = schema.safeParse({ testEnum: 'option4' }); // This option does not exist
|
|
|
|
expect(result.success).toBeFalsy();
|
|
});
|
|
|
|
it('should generate a schema for array settings', () => {
|
|
const settings: SettingsConfiguration = [
|
|
{
|
|
key: 'testArray',
|
|
description: 'A test array setting',
|
|
type: 'array',
|
|
default: ['default', 'values'],
|
|
component: 'tags', // Assuming 'tags' imply an array of strings
|
|
optionType: OptionTypes.Custom,
|
|
columnSpan: 3,
|
|
label: 'Test Array Tags',
|
|
minTags: 1, // Minimum number of tags
|
|
maxTags: 5, // Maximum number of tags
|
|
},
|
|
];
|
|
|
|
const schema = generateDynamicSchema(settings);
|
|
// Testing with right number of tags
|
|
let result = schema.safeParse({ testArray: ['value1', 'value2', 'value3'] });
|
|
|
|
expect(result.success).toBeTruthy();
|
|
expect(result?.['data']).toEqual({ testArray: ['value1', 'value2', 'value3'] });
|
|
|
|
// Testing with too few tags (should fail)
|
|
result = schema.safeParse({ testArray: [] }); // Assuming minTags is 1, empty array should fail
|
|
expect(result.success).toBeFalsy();
|
|
if (!result.success) {
|
|
// Additional check to ensure the failure is because of the minTags condition
|
|
const issues = result.error.issues.filter(
|
|
(issue) => issue.path.includes('testArray') && issue.code === 'too_small',
|
|
);
|
|
expect(issues.length).toBeGreaterThan(0); // Ensure there is at least one issue related to 'testArray' being too small
|
|
}
|
|
|
|
// Testing with too many tags (should fail)
|
|
result = schema.safeParse({
|
|
testArray: ['value1', 'value2', 'value3', 'value4', 'value5', 'value6'],
|
|
}); // Assuming maxTags is 5, this should fail
|
|
expect(result.success).toBeFalsy();
|
|
if (!result.success) {
|
|
// Additional check to ensure the failure is because of the maxTags condition
|
|
const issues = result.error.issues.filter(
|
|
(issue) => issue.path.includes('testArray') && issue.code === 'too_big',
|
|
);
|
|
expect(issues.length).toBeGreaterThan(0); // Ensure there is at least one issue related to 'testArray' being too big
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('validateSettingDefinitions', () => {
|
|
test('should throw error for Conversation optionType', () => {
|
|
const validSettings: SettingsConfiguration = [
|
|
{
|
|
key: 'themeColor',
|
|
component: 'input',
|
|
type: 'string',
|
|
default: '#ffffff',
|
|
label: 'Theme Color',
|
|
columns: 2,
|
|
columnSpan: 1,
|
|
optionType: OptionTypes.Conversation,
|
|
},
|
|
];
|
|
|
|
expect(() => validateSettingDefinitions(validSettings)).toThrow();
|
|
});
|
|
|
|
test('should throw error for Model optionType', () => {
|
|
const validSettings: SettingsConfiguration = [
|
|
{
|
|
key: 'themeColor',
|
|
component: 'input',
|
|
type: 'string',
|
|
default: '#ffffff',
|
|
label: 'Theme Color',
|
|
columns: 2,
|
|
columnSpan: 1,
|
|
optionType: OptionTypes.Model,
|
|
},
|
|
];
|
|
|
|
expect(() => validateSettingDefinitions(validSettings)).toThrow();
|
|
});
|
|
|
|
test('should not throw error for valid settings', () => {
|
|
const validSettings: SettingsConfiguration = [
|
|
{
|
|
key: 'themeColor',
|
|
component: 'input',
|
|
type: 'string',
|
|
default: '#ffffff',
|
|
label: 'Theme Color',
|
|
columns: 2,
|
|
columnSpan: 1,
|
|
optionType: OptionTypes.Custom,
|
|
},
|
|
{
|
|
key: 'fontSize',
|
|
component: 'slider',
|
|
type: 'number',
|
|
range: { min: 8, max: 36 },
|
|
default: 14,
|
|
columnSpan: 2,
|
|
optionType: OptionTypes.Custom,
|
|
},
|
|
];
|
|
|
|
expect(() => validateSettingDefinitions(validSettings)).not.toThrow();
|
|
});
|
|
|
|
// Test for incorrectly configured columns
|
|
test('should throw error for invalid columns configuration', () => {
|
|
const invalidSettings: SettingsConfiguration = [
|
|
{
|
|
key: 'themeColor',
|
|
component: 'input',
|
|
type: 'string',
|
|
columns: 5,
|
|
},
|
|
];
|
|
|
|
expect(() => validateSettingDefinitions(invalidSettings)).toThrow(ZodError);
|
|
});
|
|
|
|
test('should correctly handle columnSpan defaulting based on columns', () => {
|
|
const settingsWithColumnAdjustment: SettingsConfiguration = [
|
|
{
|
|
key: 'fontSize',
|
|
component: 'slider',
|
|
type: 'number',
|
|
columns: 4,
|
|
range: { min: 8, max: 14 },
|
|
default: 11,
|
|
optionType: OptionTypes.Custom,
|
|
},
|
|
];
|
|
|
|
expect(() => validateSettingDefinitions(settingsWithColumnAdjustment)).not.toThrow();
|
|
});
|
|
|
|
// Test for label defaulting to key if not provided
|
|
test('label should default to key if not explicitly set', () => {
|
|
const settingsWithDefaultLabel: SettingsConfiguration = [
|
|
{
|
|
key: 'fontWeight',
|
|
component: 'dropdown',
|
|
type: 'string',
|
|
options: ['normal', 'bold'],
|
|
optionType: OptionTypes.Custom,
|
|
},
|
|
];
|
|
|
|
expect(() => validateSettingDefinitions(settingsWithDefaultLabel)).not.toThrow();
|
|
expect(settingsWithDefaultLabel[0].label).toBe('fontWeight');
|
|
});
|
|
|
|
// Test for minText and maxText in input/textarea component
|
|
test('should throw error for negative minText or maxText', () => {
|
|
const settingsWithNegativeTextLimits: SettingsConfiguration = [
|
|
{ key: 'biography', component: 'textarea', type: 'string', minText: -1 },
|
|
];
|
|
|
|
expect(() => validateSettingDefinitions(settingsWithNegativeTextLimits)).toThrow(ZodError);
|
|
});
|
|
|
|
// Validate optionType with tConversationSchema
|
|
test('should throw error for optionType "conversation" not matching schema', () => {
|
|
const settingsWithInvalidConversationOptionType: SettingsConfiguration = [
|
|
{ key: 'userAge', component: 'input', type: 'number', optionType: 'conversation' },
|
|
];
|
|
|
|
expect(() => validateSettingDefinitions(settingsWithInvalidConversationOptionType)).toThrow(
|
|
ZodError,
|
|
);
|
|
});
|
|
|
|
// Test for columnSpan defaulting and label defaulting to key
|
|
test('columnSpan defaults based on columns and label defaults to key if not set', () => {
|
|
const settings: SettingsConfiguration = [
|
|
{
|
|
key: 'textSize',
|
|
type: 'number',
|
|
component: 'slider',
|
|
range: { min: 10, max: 20 },
|
|
columns: 4,
|
|
optionType: OptionTypes.Custom,
|
|
},
|
|
];
|
|
|
|
validateSettingDefinitions(settings); // Perform validation which also mutates settings with default values
|
|
|
|
expect(settings[0].columnSpan).toBe(2); // Expects columnSpan to default based on columns
|
|
expect(settings[0].label).toBe('textSize'); // Expects label to default to key
|
|
});
|
|
|
|
// Test for errors thrown due to invalid columns value
|
|
test('throws error if columns value is out of range', () => {
|
|
const settings: SettingsConfiguration = [
|
|
{
|
|
key: 'themeMode',
|
|
type: 'string',
|
|
component: 'dropdown',
|
|
options: ['dark', 'light'],
|
|
columns: 5,
|
|
},
|
|
];
|
|
|
|
expect(() => validateSettingDefinitions(settings)).toThrow(ZodError);
|
|
});
|
|
|
|
// Test range validation for slider component
|
|
test('slider component range validation', () => {
|
|
const settings: SettingsConfiguration = [
|
|
{ key: 'volume', type: 'number', component: 'slider' }, // Missing range
|
|
];
|
|
|
|
expect(() => validateSettingDefinitions(settings)).toThrow(ZodError);
|
|
});
|
|
|
|
// Test options validation for enum type in slider component
|
|
test('slider component with enum type requires at least 2 options', () => {
|
|
const settings: SettingsConfiguration = [
|
|
{ key: 'color', type: 'enum', component: 'slider', options: ['red'] }, // Not enough options
|
|
];
|
|
|
|
expect(() => validateSettingDefinitions(settings)).toThrow(ZodError);
|
|
});
|
|
|
|
// Test checkbox component options validation
|
|
test('checkbox component must have 1-2 options if options are provided', () => {
|
|
const settings: SettingsConfiguration = [
|
|
{
|
|
key: 'agreeToTerms',
|
|
type: 'boolean',
|
|
component: 'checkbox',
|
|
options: ['Yes', 'No', 'Maybe'],
|
|
}, // Too many options
|
|
];
|
|
|
|
expect(() => validateSettingDefinitions(settings)).toThrow(ZodError);
|
|
});
|
|
|
|
// Test dropdown component options validation
|
|
test('dropdown component requires at least 2 options', () => {
|
|
const settings: SettingsConfiguration = [
|
|
{ key: 'country', type: 'enum', component: 'dropdown', options: ['USA'] }, // Not enough options
|
|
];
|
|
|
|
expect(() => validateSettingDefinitions(settings)).toThrow(ZodError);
|
|
});
|
|
|
|
// Validate minText and maxText constraints in input and textarea
|
|
test('validate minText and maxText constraints', () => {
|
|
const settings: SettingsConfiguration = [
|
|
{ key: 'biography', type: 'string', component: 'textarea', minText: 10, maxText: 5 }, // Incorrect minText and maxText
|
|
];
|
|
|
|
expect(() => validateSettingDefinitions(settings)).toThrow(ZodError);
|
|
});
|
|
|
|
// Validate optionType constraint with tConversationSchema
|
|
test('validate optionType constraint with tConversationSchema', () => {
|
|
const settings: SettingsConfiguration = [
|
|
{ key: 'userAge', type: 'number', component: 'input', optionType: 'conversation' }, // No corresponding schema in tConversationSchema
|
|
];
|
|
|
|
expect(() => validateSettingDefinitions(settings)).toThrow(ZodError);
|
|
});
|
|
|
|
// Validate correct handling of boolean settings with default values
|
|
test('correct handling of boolean settings with defaults', () => {
|
|
const settings: SettingsConfiguration = [
|
|
{
|
|
key: 'enableFeatureX',
|
|
type: 'boolean',
|
|
component: 'switch',
|
|
optionType: OptionTypes.Custom,
|
|
}, // Missing default, should default to false
|
|
];
|
|
|
|
validateSettingDefinitions(settings); // This would populate default values where missing
|
|
|
|
expect(settings[0].default).toBe(false); // Expects default to be false for boolean without explicit default
|
|
});
|
|
|
|
// Validate that number slider without default uses middle of range
|
|
test('number slider without default uses middle of range', () => {
|
|
const settings: SettingsConfiguration = [
|
|
{
|
|
key: 'brightness',
|
|
type: 'number',
|
|
component: 'slider',
|
|
range: { min: 0, max: 100 },
|
|
optionType: OptionTypes.Custom,
|
|
}, // Missing default
|
|
];
|
|
|
|
validateSettingDefinitions(settings); // This would populate default values where missing
|
|
|
|
expect(settings[0].default).toBe(50); // Expects default to be midpoint of range
|
|
});
|
|
|
|
// Test for validating minTags and maxTags constraints
|
|
test('should validate minTags and maxTags constraints', () => {
|
|
const settingsWithTagsConstraints: SettingsConfiguration = [
|
|
{
|
|
key: 'selectedTags',
|
|
component: 'tags',
|
|
type: 'array',
|
|
default: ['tag1'], // Only one tag by default
|
|
minTags: 2, // Requires at least 2 tags, which should cause validation to fail
|
|
maxTags: 4,
|
|
optionType: OptionTypes.Custom,
|
|
},
|
|
];
|
|
|
|
expect(() => validateSettingDefinitions(settingsWithTagsConstraints)).toThrow(ZodError);
|
|
});
|
|
|
|
// Test for ensuring default values for tags are arrays
|
|
test('should ensure default values for tags are arrays', () => {
|
|
const settingsWithInvalidDefaultForTags: SettingsConfiguration = [
|
|
{
|
|
key: 'favoriteTags',
|
|
component: 'tags',
|
|
type: 'array',
|
|
default: 'notAnArray', // Incorrect default type
|
|
optionType: OptionTypes.Custom,
|
|
},
|
|
];
|
|
|
|
expect(() => validateSettingDefinitions(settingsWithInvalidDefaultForTags)).toThrow(ZodError);
|
|
});
|
|
|
|
// Test for array settings without default values should not throw if constraints are satisfied
|
|
test('array settings without defaults should not throw if constraints are met', () => {
|
|
const settingsWithNoDefaultButValidTags: SettingsConfiguration = [
|
|
{
|
|
key: 'userTags',
|
|
component: 'tags',
|
|
type: 'array',
|
|
minTags: 1, // Requires at least 1 tag
|
|
maxTags: 5, // Allows up to 5 tags
|
|
optionType: OptionTypes.Custom,
|
|
},
|
|
];
|
|
|
|
// No default is set, but since the constraints are potentially met (depends on user input), this should not throw
|
|
expect(() => validateSettingDefinitions(settingsWithNoDefaultButValidTags)).not.toThrow();
|
|
});
|
|
|
|
// Test for ensuring maxTags is respected in default array values
|
|
test('should ensure maxTags is respected for default array values', () => {
|
|
const settingsExceedingMaxTags: SettingsConfiguration = [
|
|
{
|
|
key: 'interestTags',
|
|
component: 'tags',
|
|
type: 'array',
|
|
default: ['music', 'movies', 'books', 'travel', 'cooking', 'sports'], // 6 tags
|
|
maxTags: 5, // Exceeds the maxTags limit
|
|
optionType: OptionTypes.Custom,
|
|
},
|
|
];
|
|
|
|
expect(() => validateSettingDefinitions(settingsExceedingMaxTags)).toThrow(ZodError);
|
|
});
|
|
|
|
// Test for incomplete enumMappings
|
|
test('should throw error for incomplete enumMappings', () => {
|
|
const settingsWithIncompleteEnumMappings: SettingsConfiguration = [
|
|
{
|
|
key: 'displayMode',
|
|
type: 'enum',
|
|
component: 'dropdown',
|
|
options: ['light', 'dark', 'auto'],
|
|
enumMappings: {
|
|
light: 'Light Mode',
|
|
dark: 'Dark Mode',
|
|
// Missing mapping for 'auto'
|
|
},
|
|
optionType: OptionTypes.Custom,
|
|
},
|
|
];
|
|
|
|
expect(() => validateSettingDefinitions(settingsWithIncompleteEnumMappings)).toThrow(ZodError);
|
|
});
|
|
|
|
// Test for complete enumMappings including empty string
|
|
test('should not throw error for complete enumMappings including empty string', () => {
|
|
const settingsWithCompleteEnumMappings: SettingsConfiguration = [
|
|
{
|
|
key: 'selectionMode',
|
|
type: 'enum',
|
|
component: 'slider',
|
|
options: ['', 'single', 'multiple'],
|
|
enumMappings: {
|
|
'': 'None',
|
|
single: 'Single Selection',
|
|
multiple: 'Multiple Selection',
|
|
},
|
|
default: '',
|
|
optionType: OptionTypes.Custom,
|
|
},
|
|
];
|
|
|
|
expect(() => validateSettingDefinitions(settingsWithCompleteEnumMappings)).not.toThrow();
|
|
});
|
|
});
|
|
|
|
const settingsConfiguration: SettingsConfiguration = [
|
|
{
|
|
key: 'temperature',
|
|
description:
|
|
'Higher values = more random, while lower values = more focused and deterministic. We recommend altering this or Top P but not both.',
|
|
type: 'number',
|
|
default: 1,
|
|
range: {
|
|
min: 0,
|
|
max: 2,
|
|
step: 0.01,
|
|
},
|
|
component: 'slider',
|
|
optionType: OptionTypes.Custom,
|
|
},
|
|
{
|
|
key: 'top_p',
|
|
description:
|
|
'An alternative to sampling with temperature, called nucleus sampling, where the model considers the results of the tokens with top_p probability mass. So 0.1 means only the tokens comprising the top 10% probability mass are considered. We recommend altering this or temperature but not both.',
|
|
type: 'number',
|
|
default: 1,
|
|
range: {
|
|
min: 0,
|
|
max: 1,
|
|
step: 0.01,
|
|
},
|
|
component: 'slider',
|
|
optionType: OptionTypes.Custom,
|
|
},
|
|
{
|
|
key: 'presence_penalty',
|
|
description:
|
|
"Number between -2.0 and 2.0. Positive values penalize new tokens based on whether they appear in the text so far, increasing the model's likelihood to talk about new topics.",
|
|
type: 'number',
|
|
default: 0,
|
|
range: {
|
|
min: -2,
|
|
max: 2,
|
|
step: 0.01,
|
|
},
|
|
component: 'slider',
|
|
optionType: OptionTypes.Custom,
|
|
},
|
|
{
|
|
key: 'frequency_penalty',
|
|
description:
|
|
"Number between -2.0 and 2.0. Positive values penalize new tokens based on their existing frequency in the text so far, decreasing the model's likelihood to repeat the same line verbatim.",
|
|
type: 'number',
|
|
default: 0,
|
|
range: {
|
|
min: -2,
|
|
max: 2,
|
|
step: 0.01,
|
|
},
|
|
component: 'slider',
|
|
optionType: OptionTypes.Custom,
|
|
},
|
|
{
|
|
key: 'resendFiles',
|
|
description:
|
|
'Resend all previously attached files. Note: this will increase token cost and you may experience errors with many attachments.',
|
|
type: 'boolean',
|
|
default: true,
|
|
component: 'switch',
|
|
optionType: OptionTypes.Custom,
|
|
},
|
|
{
|
|
key: 'imageDetail',
|
|
description:
|
|
'The resolution for Vision requests. "Low" is cheaper and faster, "High" is more detailed and expensive, and "Auto" will automatically choose between the two based on the image resolution.',
|
|
type: 'enum',
|
|
default: 'auto',
|
|
options: ['low', 'high', 'auto'],
|
|
component: 'slider',
|
|
optionType: OptionTypes.Custom,
|
|
},
|
|
{
|
|
key: 'promptPrefix',
|
|
type: 'string',
|
|
default: '',
|
|
component: 'input',
|
|
optionType: OptionTypes.Custom,
|
|
placeholder: 'Set custom instructions to include in System Message. Default: none',
|
|
},
|
|
{
|
|
key: 'chatGptLabel',
|
|
type: 'string',
|
|
default: '',
|
|
component: 'input',
|
|
optionType: OptionTypes.Custom,
|
|
placeholder: 'Set a custom name for your AI',
|
|
},
|
|
];
|
|
|
|
describe('Settings Validation and Schema Generation', () => {
|
|
// Test 1: Validate settings definitions do not throw for valid configuration
|
|
test('validateSettingDefinitions does not throw for valid configuration', () => {
|
|
expect(() => validateSettingDefinitions(settingsConfiguration)).not.toThrow();
|
|
});
|
|
|
|
test('validateSettingDefinitions throws for invalid type in settings', () => {
|
|
const settingsWithInvalidType = [
|
|
...settingsConfiguration,
|
|
{
|
|
key: 'newSetting',
|
|
description: 'A setting with an unsupported type',
|
|
type: 'unsupportedType', // Assuming 'unsupportedType' is not supported
|
|
component: 'input',
|
|
},
|
|
];
|
|
|
|
expect(() =>
|
|
validateSettingDefinitions(settingsWithInvalidType as SettingsConfiguration),
|
|
).toThrow();
|
|
});
|
|
|
|
test('validateSettingDefinitions throws for missing required fields', () => {
|
|
const settingsMissingRequiredField = [
|
|
...settingsConfiguration,
|
|
{
|
|
key: 'incompleteSetting',
|
|
type: 'number',
|
|
// Missing 'component',
|
|
},
|
|
];
|
|
|
|
expect(() =>
|
|
validateSettingDefinitions(settingsMissingRequiredField as SettingsConfiguration),
|
|
).toThrow();
|
|
});
|
|
|
|
test('validateSettingDefinitions throws for default value out of range', () => {
|
|
const settingsOutOfRange = [
|
|
...settingsConfiguration,
|
|
{
|
|
key: 'rangeTestSetting',
|
|
description: 'A setting with default value out of specified range',
|
|
type: 'number',
|
|
default: 5,
|
|
range: {
|
|
min: 0,
|
|
max: 1,
|
|
},
|
|
component: 'slider',
|
|
},
|
|
];
|
|
|
|
expect(() => validateSettingDefinitions(settingsOutOfRange as SettingsConfiguration)).toThrow();
|
|
});
|
|
|
|
test('validateSettingDefinitions throws for enum setting with incorrect default', () => {
|
|
const settingsWithIncorrectEnumDefault = [
|
|
...settingsConfiguration,
|
|
{
|
|
key: 'enumSetting',
|
|
description: 'Enum setting with a default not in options',
|
|
type: 'enum',
|
|
default: 'unlistedOption',
|
|
options: ['option1', 'option2'],
|
|
component: 'dropdown',
|
|
},
|
|
];
|
|
|
|
expect(() =>
|
|
validateSettingDefinitions(settingsWithIncorrectEnumDefault as SettingsConfiguration),
|
|
).toThrow();
|
|
});
|
|
|
|
// Test 2: Generate dynamic schema and validate correct input
|
|
test('generateDynamicSchema generates a schema that validates correct input', () => {
|
|
const schema = generateDynamicSchema(settingsConfiguration);
|
|
const validInput = {
|
|
temperature: 0.5,
|
|
top_p: 0.8,
|
|
presence_penalty: 1,
|
|
frequency_penalty: -1,
|
|
resendFiles: true,
|
|
imageDetail: 'high',
|
|
promptPrefix: 'Hello, AI.',
|
|
chatGptLabel: 'My Custom AI',
|
|
};
|
|
|
|
expect(schema.parse(validInput)).toEqual(validInput);
|
|
});
|
|
|
|
// Test 3: Generate dynamic schema and catch invalid input
|
|
test('generateDynamicSchema generates a schema that catches invalid input and provides detailed errors', async () => {
|
|
const schema = generateDynamicSchema(settingsConfiguration);
|
|
const invalidInput: z.infer<typeof schema> = {
|
|
temperature: 2.5, // Out of range
|
|
top_p: -0.5, // Out of range
|
|
presence_penalty: 3, // Out of range
|
|
frequency_penalty: -3, // Out of range
|
|
resendFiles: 'yes', // Wrong type
|
|
imageDetail: 'ultra', // Invalid option
|
|
promptPrefix: 123, // Wrong type
|
|
chatGptLabel: true, // Wrong type
|
|
};
|
|
|
|
const result = schema.safeParse(invalidInput);
|
|
expect(result.success).toBeFalsy();
|
|
if (!result.success) {
|
|
const errorPaths = result.error.issues.map((issue) => issue.path.join('.'));
|
|
expect(errorPaths).toContain('temperature');
|
|
expect(errorPaths).toContain('top_p');
|
|
expect(errorPaths).toContain('presence_penalty');
|
|
expect(errorPaths).toContain('frequency_penalty');
|
|
expect(errorPaths).toContain('resendFiles');
|
|
expect(errorPaths).toContain('imageDetail');
|
|
expect(errorPaths).toContain('promptPrefix');
|
|
expect(errorPaths).toContain('chatGptLabel');
|
|
}
|
|
});
|
|
});
|