librechat-data-provider 0.5.2 → 0.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.es.js +1 -1
- package/dist/index.es.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/react-query/index.es.js +1 -1
- package/dist/react-query/index.es.js.map +1 -1
- package/package.json +1 -1
- package/specs/generate.spec.ts +586 -0
- package/src/config.ts +19 -0
- package/src/data-service.ts +5 -3
- package/src/generate.ts +474 -0
- package/src/index.ts +1 -0
- package/src/react-query/react-query-service.ts +7 -6
- package/src/schemas.ts +109 -117
package/package.json
CHANGED
|
@@ -0,0 +1,586 @@
|
|
|
1
|
+
/* eslint-disable jest/no-conditional-expect */
|
|
2
|
+
import { ZodError, z } from 'zod';
|
|
3
|
+
import { generateDynamicSchema, validateSettingDefinitions, OptionTypes } from '../src/generate';
|
|
4
|
+
import type { SettingsConfiguration } from '../src/generate';
|
|
5
|
+
|
|
6
|
+
describe('generateDynamicSchema', () => {
|
|
7
|
+
it('should generate a schema for number settings with range', () => {
|
|
8
|
+
const settings: SettingsConfiguration = [
|
|
9
|
+
{
|
|
10
|
+
key: 'testNumber',
|
|
11
|
+
description: 'A test number setting',
|
|
12
|
+
type: 'number',
|
|
13
|
+
default: 5,
|
|
14
|
+
range: { min: 1, max: 10, step: 1 },
|
|
15
|
+
component: 'slider',
|
|
16
|
+
optionType: 'conversation',
|
|
17
|
+
columnSpan: 2,
|
|
18
|
+
label: 'Test Number Slider',
|
|
19
|
+
},
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
const schema = generateDynamicSchema(settings);
|
|
23
|
+
const result = schema.safeParse({ testNumber: 6 });
|
|
24
|
+
|
|
25
|
+
expect(result.success).toBeTruthy();
|
|
26
|
+
expect(result['data']).toEqual({ testNumber: 6 });
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('should generate a schema for boolean settings', () => {
|
|
30
|
+
const settings: SettingsConfiguration = [
|
|
31
|
+
{
|
|
32
|
+
key: 'testBoolean',
|
|
33
|
+
description: 'A test boolean setting',
|
|
34
|
+
type: 'boolean',
|
|
35
|
+
default: true,
|
|
36
|
+
component: 'switch',
|
|
37
|
+
optionType: 'model', // Only if relevant to your application's context
|
|
38
|
+
columnSpan: 1,
|
|
39
|
+
label: 'Test Boolean Switch',
|
|
40
|
+
},
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
const schema = generateDynamicSchema(settings);
|
|
44
|
+
const result = schema.safeParse({ testBoolean: false });
|
|
45
|
+
|
|
46
|
+
expect(result.success).toBeTruthy();
|
|
47
|
+
expect(result['data']).toEqual({ testBoolean: false });
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('should generate a schema for string settings', () => {
|
|
51
|
+
const settings: SettingsConfiguration = [
|
|
52
|
+
{
|
|
53
|
+
key: 'testString',
|
|
54
|
+
description: 'A test string setting',
|
|
55
|
+
type: 'string',
|
|
56
|
+
default: 'default value',
|
|
57
|
+
component: 'input',
|
|
58
|
+
optionType: 'model', // Optional and only if relevant
|
|
59
|
+
columnSpan: 3,
|
|
60
|
+
label: 'Test String Input',
|
|
61
|
+
placeholder: 'Enter text here...',
|
|
62
|
+
minText: 0, // Optional
|
|
63
|
+
maxText: 100, // Optional
|
|
64
|
+
},
|
|
65
|
+
];
|
|
66
|
+
|
|
67
|
+
const schema = generateDynamicSchema(settings);
|
|
68
|
+
const result = schema.safeParse({ testString: 'custom value' });
|
|
69
|
+
|
|
70
|
+
expect(result.success).toBeTruthy();
|
|
71
|
+
expect(result['data']).toEqual({ testString: 'custom value' });
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('should generate a schema for enum settings', () => {
|
|
75
|
+
const settings: SettingsConfiguration = [
|
|
76
|
+
{
|
|
77
|
+
key: 'testEnum',
|
|
78
|
+
description: 'A test enum setting',
|
|
79
|
+
type: 'enum',
|
|
80
|
+
default: 'option1',
|
|
81
|
+
options: ['option1', 'option2', 'option3'],
|
|
82
|
+
enumMappings: {
|
|
83
|
+
option1: 'First Option',
|
|
84
|
+
option2: 'Second Option',
|
|
85
|
+
option3: 'Third Option',
|
|
86
|
+
},
|
|
87
|
+
component: 'dropdown',
|
|
88
|
+
columnSpan: 2,
|
|
89
|
+
label: 'Test Enum Dropdown',
|
|
90
|
+
},
|
|
91
|
+
];
|
|
92
|
+
|
|
93
|
+
const schema = generateDynamicSchema(settings);
|
|
94
|
+
const result = schema.safeParse({ testEnum: 'option2' });
|
|
95
|
+
|
|
96
|
+
expect(result.success).toBeTruthy();
|
|
97
|
+
expect(result['data']).toEqual({ testEnum: 'option2' });
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('should fail for incorrect enum value', () => {
|
|
101
|
+
const settings: SettingsConfiguration = [
|
|
102
|
+
{
|
|
103
|
+
key: 'testEnum',
|
|
104
|
+
description: 'A test enum setting',
|
|
105
|
+
type: 'enum',
|
|
106
|
+
default: 'option1',
|
|
107
|
+
options: ['option1', 'option2', 'option3'],
|
|
108
|
+
component: 'dropdown',
|
|
109
|
+
},
|
|
110
|
+
];
|
|
111
|
+
|
|
112
|
+
const schema = generateDynamicSchema(settings);
|
|
113
|
+
const result = schema.safeParse({ testEnum: 'option4' }); // This option does not exist
|
|
114
|
+
|
|
115
|
+
expect(result.success).toBeFalsy();
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
describe('validateSettingDefinitions', () => {
|
|
120
|
+
test('should throw error for Conversation optionType', () => {
|
|
121
|
+
const validSettings: SettingsConfiguration = [
|
|
122
|
+
{
|
|
123
|
+
key: 'themeColor',
|
|
124
|
+
component: 'input',
|
|
125
|
+
type: 'string',
|
|
126
|
+
default: '#ffffff',
|
|
127
|
+
label: 'Theme Color',
|
|
128
|
+
columns: 2,
|
|
129
|
+
columnSpan: 1,
|
|
130
|
+
optionType: OptionTypes.Conversation,
|
|
131
|
+
},
|
|
132
|
+
];
|
|
133
|
+
|
|
134
|
+
expect(() => validateSettingDefinitions(validSettings)).toThrow();
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test('should throw error for Model optionType', () => {
|
|
138
|
+
const validSettings: SettingsConfiguration = [
|
|
139
|
+
{
|
|
140
|
+
key: 'themeColor',
|
|
141
|
+
component: 'input',
|
|
142
|
+
type: 'string',
|
|
143
|
+
default: '#ffffff',
|
|
144
|
+
label: 'Theme Color',
|
|
145
|
+
columns: 2,
|
|
146
|
+
columnSpan: 1,
|
|
147
|
+
optionType: OptionTypes.Model,
|
|
148
|
+
},
|
|
149
|
+
];
|
|
150
|
+
|
|
151
|
+
expect(() => validateSettingDefinitions(validSettings)).toThrow();
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test('should not throw error for valid settings', () => {
|
|
155
|
+
const validSettings: SettingsConfiguration = [
|
|
156
|
+
{
|
|
157
|
+
key: 'themeColor',
|
|
158
|
+
component: 'input',
|
|
159
|
+
type: 'string',
|
|
160
|
+
default: '#ffffff',
|
|
161
|
+
label: 'Theme Color',
|
|
162
|
+
columns: 2,
|
|
163
|
+
columnSpan: 1,
|
|
164
|
+
optionType: OptionTypes.Custom,
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
key: 'fontSize',
|
|
168
|
+
component: 'slider',
|
|
169
|
+
type: 'number',
|
|
170
|
+
range: { min: 8, max: 36 },
|
|
171
|
+
default: 14,
|
|
172
|
+
columnSpan: 2,
|
|
173
|
+
optionType: OptionTypes.Custom,
|
|
174
|
+
},
|
|
175
|
+
];
|
|
176
|
+
|
|
177
|
+
expect(() => validateSettingDefinitions(validSettings)).not.toThrow();
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
// Test for incorrectly configured columns
|
|
181
|
+
test('should throw error for invalid columns configuration', () => {
|
|
182
|
+
const invalidSettings: SettingsConfiguration = [
|
|
183
|
+
{
|
|
184
|
+
key: 'themeColor',
|
|
185
|
+
component: 'input',
|
|
186
|
+
type: 'string',
|
|
187
|
+
columns: 5,
|
|
188
|
+
},
|
|
189
|
+
];
|
|
190
|
+
|
|
191
|
+
expect(() => validateSettingDefinitions(invalidSettings)).toThrow(ZodError);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
test('should correctly handle columnSpan defaulting based on columns', () => {
|
|
195
|
+
const settingsWithColumnAdjustment: SettingsConfiguration = [
|
|
196
|
+
{
|
|
197
|
+
key: 'fontSize',
|
|
198
|
+
component: 'slider',
|
|
199
|
+
type: 'number',
|
|
200
|
+
columns: 4,
|
|
201
|
+
range: { min: 8, max: 14 },
|
|
202
|
+
default: 11,
|
|
203
|
+
optionType: OptionTypes.Custom,
|
|
204
|
+
},
|
|
205
|
+
];
|
|
206
|
+
|
|
207
|
+
expect(() => validateSettingDefinitions(settingsWithColumnAdjustment)).not.toThrow();
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
// Test for label defaulting to key if not provided
|
|
211
|
+
test('label should default to key if not explicitly set', () => {
|
|
212
|
+
const settingsWithDefaultLabel: SettingsConfiguration = [
|
|
213
|
+
{
|
|
214
|
+
key: 'fontWeight',
|
|
215
|
+
component: 'dropdown',
|
|
216
|
+
type: 'string',
|
|
217
|
+
options: ['normal', 'bold'],
|
|
218
|
+
optionType: OptionTypes.Custom,
|
|
219
|
+
},
|
|
220
|
+
];
|
|
221
|
+
|
|
222
|
+
expect(() => validateSettingDefinitions(settingsWithDefaultLabel)).not.toThrow();
|
|
223
|
+
expect(settingsWithDefaultLabel[0].label).toBe('fontWeight');
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
// Test for minText and maxText in input/textarea component
|
|
227
|
+
test('should throw error for negative minText or maxText', () => {
|
|
228
|
+
const settingsWithNegativeTextLimits: SettingsConfiguration = [
|
|
229
|
+
{ key: 'biography', component: 'textarea', type: 'string', minText: -1 },
|
|
230
|
+
];
|
|
231
|
+
|
|
232
|
+
expect(() => validateSettingDefinitions(settingsWithNegativeTextLimits)).toThrow(ZodError);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
// Validate optionType with tConversationSchema
|
|
236
|
+
test('should throw error for optionType "conversation" not matching schema', () => {
|
|
237
|
+
const settingsWithInvalidConversationOptionType: SettingsConfiguration = [
|
|
238
|
+
{ key: 'userAge', component: 'input', type: 'number', optionType: 'conversation' },
|
|
239
|
+
];
|
|
240
|
+
|
|
241
|
+
expect(() => validateSettingDefinitions(settingsWithInvalidConversationOptionType)).toThrow(
|
|
242
|
+
ZodError,
|
|
243
|
+
);
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
// Test for columnSpan defaulting and label defaulting to key
|
|
247
|
+
test('columnSpan defaults based on columns and label defaults to key if not set', () => {
|
|
248
|
+
const settings: SettingsConfiguration = [
|
|
249
|
+
{
|
|
250
|
+
key: 'textSize',
|
|
251
|
+
type: 'number',
|
|
252
|
+
component: 'slider',
|
|
253
|
+
range: { min: 10, max: 20 },
|
|
254
|
+
columns: 4,
|
|
255
|
+
optionType: OptionTypes.Custom,
|
|
256
|
+
},
|
|
257
|
+
];
|
|
258
|
+
|
|
259
|
+
validateSettingDefinitions(settings); // Perform validation which also mutates settings with default values
|
|
260
|
+
|
|
261
|
+
expect(settings[0].columnSpan).toBe(2); // Expects columnSpan to default based on columns
|
|
262
|
+
expect(settings[0].label).toBe('textSize'); // Expects label to default to key
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
// Test for errors thrown due to invalid columns value
|
|
266
|
+
test('throws error if columns value is out of range', () => {
|
|
267
|
+
const settings: SettingsConfiguration = [
|
|
268
|
+
{
|
|
269
|
+
key: 'themeMode',
|
|
270
|
+
type: 'string',
|
|
271
|
+
component: 'dropdown',
|
|
272
|
+
options: ['dark', 'light'],
|
|
273
|
+
columns: 5,
|
|
274
|
+
},
|
|
275
|
+
];
|
|
276
|
+
|
|
277
|
+
expect(() => validateSettingDefinitions(settings)).toThrow(ZodError);
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
// Test range validation for slider component
|
|
281
|
+
test('slider component range validation', () => {
|
|
282
|
+
const settings: SettingsConfiguration = [
|
|
283
|
+
{ key: 'volume', type: 'number', component: 'slider' }, // Missing range
|
|
284
|
+
];
|
|
285
|
+
|
|
286
|
+
expect(() => validateSettingDefinitions(settings)).toThrow(ZodError);
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
// Test options validation for enum type in slider component
|
|
290
|
+
test('slider component with enum type requires at least 2 options', () => {
|
|
291
|
+
const settings: SettingsConfiguration = [
|
|
292
|
+
{ key: 'color', type: 'enum', component: 'slider', options: ['red'] }, // Not enough options
|
|
293
|
+
];
|
|
294
|
+
|
|
295
|
+
expect(() => validateSettingDefinitions(settings)).toThrow(ZodError);
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
// Test checkbox component options validation
|
|
299
|
+
test('checkbox component must have 1-2 options if options are provided', () => {
|
|
300
|
+
const settings: SettingsConfiguration = [
|
|
301
|
+
{
|
|
302
|
+
key: 'agreeToTerms',
|
|
303
|
+
type: 'boolean',
|
|
304
|
+
component: 'checkbox',
|
|
305
|
+
options: ['Yes', 'No', 'Maybe'],
|
|
306
|
+
}, // Too many options
|
|
307
|
+
];
|
|
308
|
+
|
|
309
|
+
expect(() => validateSettingDefinitions(settings)).toThrow(ZodError);
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
// Test dropdown component options validation
|
|
313
|
+
test('dropdown component requires at least 2 options', () => {
|
|
314
|
+
const settings: SettingsConfiguration = [
|
|
315
|
+
{ key: 'country', type: 'enum', component: 'dropdown', options: ['USA'] }, // Not enough options
|
|
316
|
+
];
|
|
317
|
+
|
|
318
|
+
expect(() => validateSettingDefinitions(settings)).toThrow(ZodError);
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
// Validate minText and maxText constraints in input and textarea
|
|
322
|
+
test('validate minText and maxText constraints', () => {
|
|
323
|
+
const settings: SettingsConfiguration = [
|
|
324
|
+
{ key: 'biography', type: 'string', component: 'textarea', minText: 10, maxText: 5 }, // Incorrect minText and maxText
|
|
325
|
+
];
|
|
326
|
+
|
|
327
|
+
expect(() => validateSettingDefinitions(settings)).toThrow(ZodError);
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
// Validate optionType constraint with tConversationSchema
|
|
331
|
+
test('validate optionType constraint with tConversationSchema', () => {
|
|
332
|
+
const settings: SettingsConfiguration = [
|
|
333
|
+
{ key: 'userAge', type: 'number', component: 'input', optionType: 'conversation' }, // No corresponding schema in tConversationSchema
|
|
334
|
+
];
|
|
335
|
+
|
|
336
|
+
expect(() => validateSettingDefinitions(settings)).toThrow(ZodError);
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
// Validate correct handling of boolean settings with default values
|
|
340
|
+
test('correct handling of boolean settings with defaults', () => {
|
|
341
|
+
const settings: SettingsConfiguration = [
|
|
342
|
+
{
|
|
343
|
+
key: 'enableFeatureX',
|
|
344
|
+
type: 'boolean',
|
|
345
|
+
component: 'switch',
|
|
346
|
+
optionType: OptionTypes.Custom,
|
|
347
|
+
}, // Missing default, should default to false
|
|
348
|
+
];
|
|
349
|
+
|
|
350
|
+
validateSettingDefinitions(settings); // This would populate default values where missing
|
|
351
|
+
|
|
352
|
+
expect(settings[0].default).toBe(false); // Expects default to be false for boolean without explicit default
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
// Validate that number slider without default uses middle of range
|
|
356
|
+
test('number slider without default uses middle of range', () => {
|
|
357
|
+
const settings: SettingsConfiguration = [
|
|
358
|
+
{
|
|
359
|
+
key: 'brightness',
|
|
360
|
+
type: 'number',
|
|
361
|
+
component: 'slider',
|
|
362
|
+
range: { min: 0, max: 100 },
|
|
363
|
+
optionType: OptionTypes.Custom,
|
|
364
|
+
}, // Missing default
|
|
365
|
+
];
|
|
366
|
+
|
|
367
|
+
validateSettingDefinitions(settings); // This would populate default values where missing
|
|
368
|
+
|
|
369
|
+
expect(settings[0].default).toBe(50); // Expects default to be midpoint of range
|
|
370
|
+
});
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
const settingsConfiguration: SettingsConfiguration = [
|
|
374
|
+
{
|
|
375
|
+
key: 'temperature',
|
|
376
|
+
description:
|
|
377
|
+
'Higher values = more random, while lower values = more focused and deterministic. We recommend altering this or Top P but not both.',
|
|
378
|
+
type: 'number',
|
|
379
|
+
default: 1,
|
|
380
|
+
range: {
|
|
381
|
+
min: 0,
|
|
382
|
+
max: 2,
|
|
383
|
+
step: 0.01,
|
|
384
|
+
},
|
|
385
|
+
component: 'slider',
|
|
386
|
+
optionType: OptionTypes.Custom,
|
|
387
|
+
},
|
|
388
|
+
{
|
|
389
|
+
key: 'top_p',
|
|
390
|
+
description:
|
|
391
|
+
'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.',
|
|
392
|
+
type: 'number',
|
|
393
|
+
default: 1,
|
|
394
|
+
range: {
|
|
395
|
+
min: 0,
|
|
396
|
+
max: 1,
|
|
397
|
+
step: 0.01,
|
|
398
|
+
},
|
|
399
|
+
component: 'slider',
|
|
400
|
+
optionType: OptionTypes.Custom,
|
|
401
|
+
},
|
|
402
|
+
{
|
|
403
|
+
key: 'presence_penalty',
|
|
404
|
+
description:
|
|
405
|
+
'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.',
|
|
406
|
+
type: 'number',
|
|
407
|
+
default: 0,
|
|
408
|
+
range: {
|
|
409
|
+
min: -2,
|
|
410
|
+
max: 2,
|
|
411
|
+
step: 0.01,
|
|
412
|
+
},
|
|
413
|
+
component: 'slider',
|
|
414
|
+
optionType: OptionTypes.Custom,
|
|
415
|
+
},
|
|
416
|
+
{
|
|
417
|
+
key: 'frequency_penalty',
|
|
418
|
+
description:
|
|
419
|
+
'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.',
|
|
420
|
+
type: 'number',
|
|
421
|
+
default: 0,
|
|
422
|
+
range: {
|
|
423
|
+
min: -2,
|
|
424
|
+
max: 2,
|
|
425
|
+
step: 0.01,
|
|
426
|
+
},
|
|
427
|
+
component: 'slider',
|
|
428
|
+
optionType: OptionTypes.Custom,
|
|
429
|
+
},
|
|
430
|
+
{
|
|
431
|
+
key: 'resendFiles',
|
|
432
|
+
description:
|
|
433
|
+
'Resend all previously attached files. Note: this will increase token cost and you may experience errors with many attachments.',
|
|
434
|
+
type: 'boolean',
|
|
435
|
+
default: true,
|
|
436
|
+
component: 'switch',
|
|
437
|
+
optionType: OptionTypes.Custom,
|
|
438
|
+
},
|
|
439
|
+
{
|
|
440
|
+
key: 'imageDetail',
|
|
441
|
+
description:
|
|
442
|
+
'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.',
|
|
443
|
+
type: 'enum',
|
|
444
|
+
default: 'auto',
|
|
445
|
+
options: ['low', 'high', 'auto'],
|
|
446
|
+
component: 'slider',
|
|
447
|
+
optionType: OptionTypes.Custom,
|
|
448
|
+
},
|
|
449
|
+
{
|
|
450
|
+
key: 'promptPrefix',
|
|
451
|
+
type: 'string',
|
|
452
|
+
default: '',
|
|
453
|
+
component: 'input',
|
|
454
|
+
optionType: OptionTypes.Custom,
|
|
455
|
+
placeholder: 'Set custom instructions to include in System Message. Default: none',
|
|
456
|
+
},
|
|
457
|
+
{
|
|
458
|
+
key: 'chatGptLabel',
|
|
459
|
+
type: 'string',
|
|
460
|
+
default: '',
|
|
461
|
+
component: 'input',
|
|
462
|
+
optionType: OptionTypes.Custom,
|
|
463
|
+
placeholder: 'Set a custom name for your AI',
|
|
464
|
+
},
|
|
465
|
+
];
|
|
466
|
+
|
|
467
|
+
describe('Settings Validation and Schema Generation', () => {
|
|
468
|
+
// Test 1: Validate settings definitions do not throw for valid configuration
|
|
469
|
+
test('validateSettingDefinitions does not throw for valid configuration', () => {
|
|
470
|
+
expect(() => validateSettingDefinitions(settingsConfiguration)).not.toThrow();
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
test('validateSettingDefinitions throws for invalid type in settings', () => {
|
|
474
|
+
const settingsWithInvalidType = [
|
|
475
|
+
...settingsConfiguration,
|
|
476
|
+
{
|
|
477
|
+
key: 'newSetting',
|
|
478
|
+
description: 'A setting with an unsupported type',
|
|
479
|
+
type: 'unsupportedType', // Assuming 'unsupportedType' is not supported
|
|
480
|
+
component: 'input',
|
|
481
|
+
},
|
|
482
|
+
];
|
|
483
|
+
|
|
484
|
+
expect(() =>
|
|
485
|
+
validateSettingDefinitions(settingsWithInvalidType as SettingsConfiguration),
|
|
486
|
+
).toThrow();
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
test('validateSettingDefinitions throws for missing required fields', () => {
|
|
490
|
+
const settingsMissingRequiredField = [
|
|
491
|
+
...settingsConfiguration,
|
|
492
|
+
{
|
|
493
|
+
key: 'incompleteSetting',
|
|
494
|
+
type: 'number',
|
|
495
|
+
// Missing 'component',
|
|
496
|
+
},
|
|
497
|
+
];
|
|
498
|
+
|
|
499
|
+
expect(() =>
|
|
500
|
+
validateSettingDefinitions(settingsMissingRequiredField as SettingsConfiguration),
|
|
501
|
+
).toThrow();
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
test('validateSettingDefinitions throws for default value out of range', () => {
|
|
505
|
+
const settingsOutOfRange = [
|
|
506
|
+
...settingsConfiguration,
|
|
507
|
+
{
|
|
508
|
+
key: 'rangeTestSetting',
|
|
509
|
+
description: 'A setting with default value out of specified range',
|
|
510
|
+
type: 'number',
|
|
511
|
+
default: 5,
|
|
512
|
+
range: {
|
|
513
|
+
min: 0,
|
|
514
|
+
max: 1,
|
|
515
|
+
},
|
|
516
|
+
component: 'slider',
|
|
517
|
+
},
|
|
518
|
+
];
|
|
519
|
+
|
|
520
|
+
expect(() => validateSettingDefinitions(settingsOutOfRange as SettingsConfiguration)).toThrow();
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
test('validateSettingDefinitions throws for enum setting with incorrect default', () => {
|
|
524
|
+
const settingsWithIncorrectEnumDefault = [
|
|
525
|
+
...settingsConfiguration,
|
|
526
|
+
{
|
|
527
|
+
key: 'enumSetting',
|
|
528
|
+
description: 'Enum setting with a default not in options',
|
|
529
|
+
type: 'enum',
|
|
530
|
+
default: 'unlistedOption',
|
|
531
|
+
options: ['option1', 'option2'],
|
|
532
|
+
component: 'dropdown',
|
|
533
|
+
},
|
|
534
|
+
];
|
|
535
|
+
|
|
536
|
+
expect(() =>
|
|
537
|
+
validateSettingDefinitions(settingsWithIncorrectEnumDefault as SettingsConfiguration),
|
|
538
|
+
).toThrow();
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
// Test 2: Generate dynamic schema and validate correct input
|
|
542
|
+
test('generateDynamicSchema generates a schema that validates correct input', () => {
|
|
543
|
+
const schema = generateDynamicSchema(settingsConfiguration);
|
|
544
|
+
const validInput = {
|
|
545
|
+
temperature: 0.5,
|
|
546
|
+
top_p: 0.8,
|
|
547
|
+
presence_penalty: 1,
|
|
548
|
+
frequency_penalty: -1,
|
|
549
|
+
resendFiles: true,
|
|
550
|
+
imageDetail: 'high',
|
|
551
|
+
promptPrefix: 'Hello, AI.',
|
|
552
|
+
chatGptLabel: 'My Custom AI',
|
|
553
|
+
};
|
|
554
|
+
|
|
555
|
+
expect(schema.parse(validInput)).toEqual(validInput);
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
// Test 3: Generate dynamic schema and catch invalid input
|
|
559
|
+
test('generateDynamicSchema generates a schema that catches invalid input and provides detailed errors', async () => {
|
|
560
|
+
const schema = generateDynamicSchema(settingsConfiguration);
|
|
561
|
+
const invalidInput: z.infer<typeof schema> = {
|
|
562
|
+
temperature: 2.5, // Out of range
|
|
563
|
+
top_p: -0.5, // Out of range
|
|
564
|
+
presence_penalty: 3, // Out of range
|
|
565
|
+
frequency_penalty: -3, // Out of range
|
|
566
|
+
resendFiles: 'yes', // Wrong type
|
|
567
|
+
imageDetail: 'ultra', // Invalid option
|
|
568
|
+
promptPrefix: 123, // Wrong type
|
|
569
|
+
chatGptLabel: true, // Wrong type
|
|
570
|
+
};
|
|
571
|
+
|
|
572
|
+
const result = schema.safeParse(invalidInput);
|
|
573
|
+
expect(result.success).toBeFalsy();
|
|
574
|
+
if (!result.success) {
|
|
575
|
+
const errorPaths = result.error.issues.map((issue) => issue.path.join('.'));
|
|
576
|
+
expect(errorPaths).toContain('temperature');
|
|
577
|
+
expect(errorPaths).toContain('top_p');
|
|
578
|
+
expect(errorPaths).toContain('presence_penalty');
|
|
579
|
+
expect(errorPaths).toContain('frequency_penalty');
|
|
580
|
+
expect(errorPaths).toContain('resendFiles');
|
|
581
|
+
expect(errorPaths).toContain('imageDetail');
|
|
582
|
+
expect(errorPaths).toContain('promptPrefix');
|
|
583
|
+
expect(errorPaths).toContain('chatGptLabel');
|
|
584
|
+
}
|
|
585
|
+
});
|
|
586
|
+
});
|
package/src/config.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { z } from 'zod';
|
|
|
3
3
|
import { EModelEndpoint, eModelEndpointSchema } from './schemas';
|
|
4
4
|
import { fileConfigSchema } from './file-config';
|
|
5
5
|
import { FileSources } from './types/files';
|
|
6
|
+
import { TModelsConfig } from './types';
|
|
6
7
|
|
|
7
8
|
export const defaultSocialLogins = ['google', 'facebook', 'openid', 'github', 'discord'];
|
|
8
9
|
|
|
@@ -332,6 +333,24 @@ export const defaultModels = {
|
|
|
332
333
|
],
|
|
333
334
|
};
|
|
334
335
|
|
|
336
|
+
const fitlerAssistantModels = (str: string) => {
|
|
337
|
+
return /gpt-4|gpt-3\\.5/i.test(str) && !/vision|instruct/i.test(str);
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
const openAIModels = defaultModels[EModelEndpoint.openAI];
|
|
341
|
+
|
|
342
|
+
export const initialModelsConfig: TModelsConfig = {
|
|
343
|
+
initial: [],
|
|
344
|
+
[EModelEndpoint.openAI]: openAIModels,
|
|
345
|
+
[EModelEndpoint.assistants]: openAIModels.filter(fitlerAssistantModels),
|
|
346
|
+
[EModelEndpoint.gptPlugins]: openAIModels,
|
|
347
|
+
[EModelEndpoint.azureOpenAI]: openAIModels,
|
|
348
|
+
[EModelEndpoint.bingAI]: ['BingAI', 'Sydney'],
|
|
349
|
+
[EModelEndpoint.chatGPTBrowser]: ['text-davinci-002-render-sha'],
|
|
350
|
+
[EModelEndpoint.google]: defaultModels[EModelEndpoint.google],
|
|
351
|
+
[EModelEndpoint.anthropic]: defaultModels[EModelEndpoint.anthropic],
|
|
352
|
+
};
|
|
353
|
+
|
|
335
354
|
export const EndpointURLs: { [key in EModelEndpoint]: string } = {
|
|
336
355
|
[EModelEndpoint.openAI]: `/api/ask/${EModelEndpoint.openAI}`,
|
|
337
356
|
[EModelEndpoint.bingAI]: `/api/ask/${EModelEndpoint.bingAI}`,
|
package/src/data-service.ts
CHANGED
|
@@ -202,10 +202,12 @@ export const uploadAssistantAvatar = (data: m.AssistantAvatarVariables): Promise
|
|
|
202
202
|
);
|
|
203
203
|
};
|
|
204
204
|
|
|
205
|
-
export const getFileDownload = async (userId: string,
|
|
206
|
-
|
|
207
|
-
return request.getResponse(`${endpoints.files()}/download/${userId}/${encodedFilePath}`, {
|
|
205
|
+
export const getFileDownload = async (userId: string, file_id: string): Promise<AxiosResponse> => {
|
|
206
|
+
return request.getResponse(`${endpoints.files()}/download/${userId}/${file_id}`, {
|
|
208
207
|
responseType: 'blob',
|
|
208
|
+
headers: {
|
|
209
|
+
Accept: 'application/octet-stream',
|
|
210
|
+
},
|
|
209
211
|
});
|
|
210
212
|
};
|
|
211
213
|
|