@lobehub/chat 1.108.1 → 1.108.2
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/.cursor/rules/testing-guide/testing-guide.mdc +18 -0
- package/CHANGELOG.md +25 -0
- package/changelog/v1.json +9 -0
- package/package.json +2 -2
- package/src/app/[variants]/(main)/settings/provider/features/ProviderConfig/Checker.tsx +15 -2
- package/src/app/[variants]/(main)/settings/provider/features/ProviderConfig/index.tsx +30 -3
- package/src/components/Analytics/LobeAnalyticsProvider.tsx +10 -13
- package/src/components/Analytics/LobeAnalyticsProviderWrapper.tsx +16 -4
- package/src/libs/model-runtime/RouterRuntime/createRuntime.test.ts +538 -0
- package/src/libs/model-runtime/RouterRuntime/createRuntime.ts +50 -13
- package/src/libs/model-runtime/RouterRuntime/index.ts +1 -1
- package/src/libs/model-runtime/aihubmix/index.ts +10 -5
- package/src/libs/model-runtime/ppio/index.test.ts +3 -6
- package/src/libs/model-runtime/utils/openaiCompatibleFactory/index.ts +8 -6
- package/src/server/globalConfig/genServerAiProviderConfig.test.ts +22 -25
- package/src/server/globalConfig/genServerAiProviderConfig.ts +34 -22
- package/src/server/globalConfig/index.ts +1 -1
- package/src/server/services/discover/index.ts +11 -2
- package/src/services/chat.ts +1 -1
- package/src/store/aiInfra/slices/aiProvider/__tests__/action.test.ts +211 -0
- package/src/store/aiInfra/slices/aiProvider/action.ts +46 -35
- package/src/store/user/slices/modelList/action.test.ts +5 -5
- package/src/store/user/slices/modelList/action.ts +4 -4
- package/src/utils/getFallbackModelProperty.test.ts +52 -45
- package/src/utils/getFallbackModelProperty.ts +4 -3
- package/src/utils/parseModels.test.ts +107 -98
- package/src/utils/parseModels.ts +10 -8
@@ -7,8 +7,8 @@ import { AiFullModelCard } from '@/types/aiModel';
|
|
7
7
|
import { extractEnabledModels, parseModelString, transformToAiModelList } from './parseModels';
|
8
8
|
|
9
9
|
describe('parseModelString', () => {
|
10
|
-
it('custom deletion, addition, and renaming of models', () => {
|
11
|
-
const result = parseModelString(
|
10
|
+
it('custom deletion, addition, and renaming of models', async () => {
|
11
|
+
const result = await parseModelString(
|
12
12
|
'test-provider',
|
13
13
|
'-all,+llama,+claude-2,-gpt-3.5-turbo,gpt-4-1106-preview=gpt-4-turbo,gpt-4-1106-preview=gpt-4-32k',
|
14
14
|
);
|
@@ -16,22 +16,22 @@ describe('parseModelString', () => {
|
|
16
16
|
expect(result).toMatchSnapshot();
|
17
17
|
});
|
18
18
|
|
19
|
-
it('duplicate naming model', () => {
|
20
|
-
const result = parseModelString(
|
19
|
+
it('duplicate naming model', async () => {
|
20
|
+
const result = await parseModelString(
|
21
21
|
'test-provider',
|
22
22
|
'gpt-4-1106-preview=gpt-4-turbo,gpt-4-1106-preview=gpt-4-32k',
|
23
23
|
);
|
24
24
|
expect(result).toMatchSnapshot();
|
25
25
|
});
|
26
26
|
|
27
|
-
it('only add the model', () => {
|
28
|
-
const result = parseModelString('test-provider', 'model1,model2,model3,model4');
|
27
|
+
it('only add the model', async () => {
|
28
|
+
const result = await parseModelString('test-provider', 'model1,model2,model3,model4');
|
29
29
|
|
30
30
|
expect(result).toMatchSnapshot();
|
31
31
|
});
|
32
32
|
|
33
|
-
it('empty string model', () => {
|
34
|
-
const result = parseModelString(
|
33
|
+
it('empty string model', async () => {
|
34
|
+
const result = await parseModelString(
|
35
35
|
'test-provider',
|
36
36
|
'gpt-4-1106-preview=gpt-4-turbo,, ,\n ,+claude-2',
|
37
37
|
);
|
@@ -39,8 +39,8 @@ describe('parseModelString', () => {
|
|
39
39
|
});
|
40
40
|
|
41
41
|
describe('extension capabilities', () => {
|
42
|
-
it('with token', () => {
|
43
|
-
const result = parseModelString('test-provider', 'chatglm-6b=ChatGLM 6B<4096>');
|
42
|
+
it('with token', async () => {
|
43
|
+
const result = await parseModelString('test-provider', 'chatglm-6b=ChatGLM 6B<4096>');
|
44
44
|
|
45
45
|
expect(result.add[0]).toEqual({
|
46
46
|
displayName: 'ChatGLM 6B',
|
@@ -51,8 +51,8 @@ describe('parseModelString', () => {
|
|
51
51
|
});
|
52
52
|
});
|
53
53
|
|
54
|
-
it('token and function calling', () => {
|
55
|
-
const result = parseModelString('test-provider', 'spark-v3.5=讯飞星火 v3.5<8192:fc>');
|
54
|
+
it('token and function calling', async () => {
|
55
|
+
const result = await parseModelString('test-provider', 'spark-v3.5=讯飞星火 v3.5<8192:fc>');
|
56
56
|
|
57
57
|
expect(result.add[0]).toEqual({
|
58
58
|
displayName: '讯飞星火 v3.5',
|
@@ -65,8 +65,11 @@ describe('parseModelString', () => {
|
|
65
65
|
});
|
66
66
|
});
|
67
67
|
|
68
|
-
it('token and reasoning', () => {
|
69
|
-
const result = parseModelString(
|
68
|
+
it('token and reasoning', async () => {
|
69
|
+
const result = await parseModelString(
|
70
|
+
'test-provider',
|
71
|
+
'deepseek-r1=Deepseek R1<65536:reasoning>',
|
72
|
+
);
|
70
73
|
|
71
74
|
expect(result.add[0]).toEqual({
|
72
75
|
displayName: 'Deepseek R1',
|
@@ -79,8 +82,11 @@ describe('parseModelString', () => {
|
|
79
82
|
});
|
80
83
|
});
|
81
84
|
|
82
|
-
it('token and search', () => {
|
83
|
-
const result = parseModelString(
|
85
|
+
it('token and search', async () => {
|
86
|
+
const result = await parseModelString(
|
87
|
+
'test-provider',
|
88
|
+
'qwen-max-latest=Qwen Max<32768:search>',
|
89
|
+
);
|
84
90
|
|
85
91
|
expect(result.add[0]).toEqual({
|
86
92
|
displayName: 'Qwen Max',
|
@@ -93,8 +99,8 @@ describe('parseModelString', () => {
|
|
93
99
|
});
|
94
100
|
});
|
95
101
|
|
96
|
-
it('token and image output', () => {
|
97
|
-
const result = parseModelString(
|
102
|
+
it('token and image output', async () => {
|
103
|
+
const result = await parseModelString(
|
98
104
|
'test-provider',
|
99
105
|
'gemini-2.0-flash-exp-image-generation=Gemini 2.0 Flash (Image Generation) Experimental<32768:imageOutput>',
|
100
106
|
);
|
@@ -110,8 +116,8 @@ describe('parseModelString', () => {
|
|
110
116
|
});
|
111
117
|
});
|
112
118
|
|
113
|
-
it('multi models', () => {
|
114
|
-
const result = parseModelString(
|
119
|
+
it('multi models', async () => {
|
120
|
+
const result = await parseModelString(
|
115
121
|
'test-provider',
|
116
122
|
'gemini-1.5-flash-latest=Gemini 1.5 Flash<16000:vision>,gpt-4-all=ChatGPT Plus<128000:fc:vision:file>',
|
117
123
|
);
|
@@ -140,8 +146,8 @@ describe('parseModelString', () => {
|
|
140
146
|
]);
|
141
147
|
});
|
142
148
|
|
143
|
-
it('should have file with builtin models like gpt-4-0125-preview', () => {
|
144
|
-
const result = parseModelString(
|
149
|
+
it('should have file with builtin models like gpt-4-0125-preview', async () => {
|
150
|
+
const result = await parseModelString(
|
145
151
|
'openai',
|
146
152
|
'-all,+gpt-4-0125-preview=ChatGPT-4<128000:fc:file>,+gpt-4-turbo-2024-04-09=ChatGPT-4 Vision<128000:fc:vision:file>',
|
147
153
|
);
|
@@ -170,8 +176,8 @@ describe('parseModelString', () => {
|
|
170
176
|
]);
|
171
177
|
});
|
172
178
|
|
173
|
-
it('should handle empty extension capability value', () => {
|
174
|
-
const result = parseModelString('test-provider', 'model1<1024:>');
|
179
|
+
it('should handle empty extension capability value', async () => {
|
180
|
+
const result = await parseModelString('test-provider', 'model1<1024:>');
|
175
181
|
expect(result.add[0]).toEqual({
|
176
182
|
abilities: {},
|
177
183
|
type: 'chat',
|
@@ -180,8 +186,8 @@ describe('parseModelString', () => {
|
|
180
186
|
});
|
181
187
|
});
|
182
188
|
|
183
|
-
it('should handle empty extension capability name', () => {
|
184
|
-
const result = parseModelString('test-provider', 'model1<1024::file>');
|
189
|
+
it('should handle empty extension capability name', async () => {
|
190
|
+
const result = await parseModelString('test-provider', 'model1<1024::file>');
|
185
191
|
expect(result.add[0]).toEqual({
|
186
192
|
id: 'model1',
|
187
193
|
contextWindowTokens: 1024,
|
@@ -192,8 +198,8 @@ describe('parseModelString', () => {
|
|
192
198
|
});
|
193
199
|
});
|
194
200
|
|
195
|
-
it('should handle duplicate extension capabilities', () => {
|
196
|
-
const result = parseModelString('test-provider', 'model1<1024:vision:vision>');
|
201
|
+
it('should handle duplicate extension capabilities', async () => {
|
202
|
+
const result = await parseModelString('test-provider', 'model1<1024:vision:vision>');
|
197
203
|
expect(result.add[0]).toEqual({
|
198
204
|
id: 'model1',
|
199
205
|
contextWindowTokens: 1024,
|
@@ -204,8 +210,8 @@ describe('parseModelString', () => {
|
|
204
210
|
});
|
205
211
|
});
|
206
212
|
|
207
|
-
it('should handle case-sensitive extension capability names', () => {
|
208
|
-
const result = parseModelString('test-provider', 'model1<1024:VISION:FC:file>');
|
213
|
+
it('should handle case-sensitive extension capability names', async () => {
|
214
|
+
const result = await parseModelString('test-provider', 'model1<1024:VISION:FC:file>');
|
209
215
|
expect(result.add[0]).toEqual({
|
210
216
|
id: 'model1',
|
211
217
|
contextWindowTokens: 1024,
|
@@ -216,8 +222,8 @@ describe('parseModelString', () => {
|
|
216
222
|
});
|
217
223
|
});
|
218
224
|
|
219
|
-
it('should handle case-sensitive extension capability values', () => {
|
220
|
-
const result = parseModelString('test-provider', 'model1<1024:vision:Fc:File>');
|
225
|
+
it('should handle case-sensitive extension capability values', async () => {
|
226
|
+
const result = await parseModelString('test-provider', 'model1<1024:vision:Fc:File>');
|
221
227
|
expect(result.add[0]).toEqual({
|
222
228
|
id: 'model1',
|
223
229
|
contextWindowTokens: 1024,
|
@@ -228,44 +234,44 @@ describe('parseModelString', () => {
|
|
228
234
|
});
|
229
235
|
});
|
230
236
|
|
231
|
-
it('should handle empty angle brackets', () => {
|
232
|
-
const result = parseModelString('test-provider', 'model1<>');
|
237
|
+
it('should handle empty angle brackets', async () => {
|
238
|
+
const result = await parseModelString('test-provider', 'model1<>');
|
233
239
|
expect(result.add[0]).toEqual({ id: 'model1', abilities: {}, type: 'chat' });
|
234
240
|
});
|
235
241
|
|
236
|
-
it('should handle not close angle brackets', () => {
|
237
|
-
const result = parseModelString('test-provider', 'model1<,model2');
|
242
|
+
it('should handle not close angle brackets', async () => {
|
243
|
+
const result = await parseModelString('test-provider', 'model1<,model2');
|
238
244
|
expect(result.add).toEqual([
|
239
245
|
{ id: 'model1', abilities: {}, type: 'chat' },
|
240
246
|
{ id: 'model2', abilities: {}, type: 'chat' },
|
241
247
|
]);
|
242
248
|
});
|
243
249
|
|
244
|
-
it('should handle multi close angle brackets', () => {
|
245
|
-
const result = parseModelString('test-provider', 'model1<>>,model2');
|
250
|
+
it('should handle multi close angle brackets', async () => {
|
251
|
+
const result = await parseModelString('test-provider', 'model1<>>,model2');
|
246
252
|
expect(result.add).toEqual([
|
247
253
|
{ id: 'model1', abilities: {}, type: 'chat' },
|
248
254
|
{ id: 'model2', abilities: {}, type: 'chat' },
|
249
255
|
]);
|
250
256
|
});
|
251
257
|
|
252
|
-
it('should handle only colon inside angle brackets', () => {
|
253
|
-
const result = parseModelString('test-provider', 'model1<:>');
|
258
|
+
it('should handle only colon inside angle brackets', async () => {
|
259
|
+
const result = await parseModelString('test-provider', 'model1<:>');
|
254
260
|
expect(result.add[0]).toEqual({ id: 'model1', abilities: {}, type: 'chat' });
|
255
261
|
});
|
256
262
|
|
257
|
-
it('should handle only non-digit characters inside angle brackets', () => {
|
258
|
-
const result = parseModelString('test-provider', 'model1<abc>');
|
263
|
+
it('should handle only non-digit characters inside angle brackets', async () => {
|
264
|
+
const result = await parseModelString('test-provider', 'model1<abc>');
|
259
265
|
expect(result.add[0]).toEqual({ id: 'model1', abilities: {}, type: 'chat' });
|
260
266
|
});
|
261
267
|
|
262
|
-
it('should handle non-digit characters followed by digits inside angle brackets', () => {
|
263
|
-
const result = parseModelString('test-provider', 'model1<abc123>');
|
268
|
+
it('should handle non-digit characters followed by digits inside angle brackets', async () => {
|
269
|
+
const result = await parseModelString('test-provider', 'model1<abc123>');
|
264
270
|
expect(result.add[0]).toEqual({ id: 'model1', abilities: {}, type: 'chat' });
|
265
271
|
});
|
266
272
|
|
267
|
-
it('should handle digits followed by non-colon characters inside angle brackets', () => {
|
268
|
-
const result = parseModelString('test-provider', 'model1<1024abc>');
|
273
|
+
it('should handle digits followed by non-colon characters inside angle brackets', async () => {
|
274
|
+
const result = await parseModelString('test-provider', 'model1<1024abc>');
|
269
275
|
expect(result.add[0]).toEqual({
|
270
276
|
id: 'model1',
|
271
277
|
contextWindowTokens: 1024,
|
@@ -274,8 +280,8 @@ describe('parseModelString', () => {
|
|
274
280
|
});
|
275
281
|
});
|
276
282
|
|
277
|
-
it('should handle digits followed by multiple colons inside angle brackets', () => {
|
278
|
-
const result = parseModelString('test-provider', 'model1<1024::>');
|
283
|
+
it('should handle digits followed by multiple colons inside angle brackets', async () => {
|
284
|
+
const result = await parseModelString('test-provider', 'model1<1024::>');
|
279
285
|
expect(result.add[0]).toEqual({
|
280
286
|
id: 'model1',
|
281
287
|
contextWindowTokens: 1024,
|
@@ -284,8 +290,8 @@ describe('parseModelString', () => {
|
|
284
290
|
});
|
285
291
|
});
|
286
292
|
|
287
|
-
it('should handle digits followed by a colon and non-letter characters inside angle brackets', () => {
|
288
|
-
const result = parseModelString('test-provider', 'model1<1024:123>');
|
293
|
+
it('should handle digits followed by a colon and non-letter characters inside angle brackets', async () => {
|
294
|
+
const result = await parseModelString('test-provider', 'model1<1024:123>');
|
289
295
|
expect(result.add[0]).toEqual({
|
290
296
|
id: 'model1',
|
291
297
|
contextWindowTokens: 1024,
|
@@ -294,8 +300,8 @@ describe('parseModelString', () => {
|
|
294
300
|
});
|
295
301
|
});
|
296
302
|
|
297
|
-
it('should handle digits followed by a colon and spaces inside angle brackets', () => {
|
298
|
-
const result = parseModelString('test-provider', 'model1<1024: vision>');
|
303
|
+
it('should handle digits followed by a colon and spaces inside angle brackets', async () => {
|
304
|
+
const result = await parseModelString('test-provider', 'model1<1024: vision>');
|
299
305
|
expect(result.add[0]).toEqual({
|
300
306
|
id: 'model1',
|
301
307
|
contextWindowTokens: 1024,
|
@@ -304,8 +310,8 @@ describe('parseModelString', () => {
|
|
304
310
|
});
|
305
311
|
});
|
306
312
|
|
307
|
-
it('should handle digits followed by multiple colons and spaces inside angle brackets', () => {
|
308
|
-
const result = parseModelString('test-provider', 'model1<1024: : vision>');
|
313
|
+
it('should handle digits followed by multiple colons and spaces inside angle brackets', async () => {
|
314
|
+
const result = await parseModelString('test-provider', 'model1<1024: : vision>');
|
309
315
|
expect(result.add[0]).toEqual({
|
310
316
|
id: 'model1',
|
311
317
|
contextWindowTokens: 1024,
|
@@ -316,8 +322,8 @@ describe('parseModelString', () => {
|
|
316
322
|
});
|
317
323
|
|
318
324
|
describe('FAL image models', () => {
|
319
|
-
it('should correctly parse FAL image model ids with slash and custom display names', () => {
|
320
|
-
const result = parseModelString(
|
325
|
+
it('should correctly parse FAL image model ids with slash and custom display names', async () => {
|
326
|
+
const result = await parseModelString(
|
321
327
|
'fal',
|
322
328
|
'-all,+flux-kontext/dev=KontextDev,+flux-pro/kontext=KontextPro,+flux/schnell=Schnell,+imagen4/preview=Imagen4',
|
323
329
|
);
|
@@ -351,8 +357,8 @@ describe('parseModelString', () => {
|
|
351
357
|
expect(result.removed).toEqual(['all']);
|
352
358
|
});
|
353
359
|
|
354
|
-
it('should correctly parse FAL image model ids with slash (no displayName)', () => {
|
355
|
-
const result = parseModelString('fal', '-all,+flux-kontext/dev,+flux-pro/kontext');
|
360
|
+
it('should correctly parse FAL image model ids with slash (no displayName)', async () => {
|
361
|
+
const result = await parseModelString('fal', '-all,+flux-kontext/dev,+flux-pro/kontext');
|
356
362
|
expect(result.add).toEqual([
|
357
363
|
{
|
358
364
|
id: 'flux-kontext/dev',
|
@@ -371,8 +377,8 @@ describe('parseModelString', () => {
|
|
371
377
|
});
|
372
378
|
|
373
379
|
describe('deployment name', () => {
|
374
|
-
it('should have no deployment name', () => {
|
375
|
-
const result = parseModelString('test-provider', 'model1=Model 1', true);
|
380
|
+
it('should have no deployment name', async () => {
|
381
|
+
const result = await parseModelString('test-provider', 'model1=Model 1', true);
|
376
382
|
expect(result.add[0]).toEqual({
|
377
383
|
id: 'model1',
|
378
384
|
displayName: 'Model 1',
|
@@ -381,8 +387,8 @@ describe('parseModelString', () => {
|
|
381
387
|
});
|
382
388
|
});
|
383
389
|
|
384
|
-
it('should have diff deployment name as id', () => {
|
385
|
-
const result = parseModelString('azure', 'gpt-35-turbo->my-deploy=GPT 3.5 Turbo', true);
|
390
|
+
it('should have diff deployment name as id', async () => {
|
391
|
+
const result = await parseModelString('azure', 'gpt-35-turbo->my-deploy=GPT 3.5 Turbo', true);
|
386
392
|
expect(result.add[0]).toEqual({
|
387
393
|
id: 'gpt-35-turbo',
|
388
394
|
displayName: 'GPT 3.5 Turbo',
|
@@ -394,8 +400,8 @@ describe('parseModelString', () => {
|
|
394
400
|
});
|
395
401
|
});
|
396
402
|
|
397
|
-
it('should handle with multi deployName', () => {
|
398
|
-
const result = parseModelString(
|
403
|
+
it('should handle with multi deployName', async () => {
|
404
|
+
const result = await parseModelString(
|
399
405
|
'azure',
|
400
406
|
'gpt-4o->id1=GPT-4o,gpt-4o-mini->id2=gpt-4o-mini,o1-mini->id3=O1 mini',
|
401
407
|
true,
|
@@ -428,28 +434,28 @@ describe('parseModelString', () => {
|
|
428
434
|
});
|
429
435
|
|
430
436
|
describe('extractEnabledModels', () => {
|
431
|
-
it('should return undefined when no models are added', () => {
|
432
|
-
const result = extractEnabledModels('test-provider', '-all');
|
437
|
+
it('should return undefined when no models are added', async () => {
|
438
|
+
const result = await extractEnabledModels('test-provider', '-all');
|
433
439
|
expect(result).toBeUndefined();
|
434
440
|
});
|
435
441
|
|
436
|
-
it('should return undefined when modelString is empty', () => {
|
437
|
-
const result = extractEnabledModels('test-provider', '');
|
442
|
+
it('should return undefined when modelString is empty', async () => {
|
443
|
+
const result = await extractEnabledModels('test-provider', '');
|
438
444
|
expect(result).toBeUndefined();
|
439
445
|
});
|
440
446
|
|
441
|
-
it('should return array of model IDs when models are added', () => {
|
442
|
-
const result = extractEnabledModels('test-provider', '+model1,+model2,+model3');
|
447
|
+
it('should return array of model IDs when models are added', async () => {
|
448
|
+
const result = await extractEnabledModels('test-provider', '+model1,+model2,+model3');
|
443
449
|
expect(result).toEqual(['model1', 'model2', 'model3']);
|
444
450
|
});
|
445
451
|
|
446
|
-
it('should handle mixed add/remove operations and return only added models', () => {
|
447
|
-
const result = extractEnabledModels('test-provider', '+model1,-model2,+model3');
|
452
|
+
it('should handle mixed add/remove operations and return only added models', async () => {
|
453
|
+
const result = await extractEnabledModels('test-provider', '+model1,-model2,+model3');
|
448
454
|
expect(result).toEqual(['model1', 'model3']);
|
449
455
|
});
|
450
456
|
|
451
|
-
it('should handle deployment names when withDeploymentName is true', () => {
|
452
|
-
const result = extractEnabledModels(
|
457
|
+
it('should handle deployment names when withDeploymentName is true', async () => {
|
458
|
+
const result = await extractEnabledModels(
|
453
459
|
'azure',
|
454
460
|
'+gpt-4->deployment1,+gpt-35-turbo->deployment2',
|
455
461
|
true,
|
@@ -457,8 +463,11 @@ describe('extractEnabledModels', () => {
|
|
457
463
|
expect(result).toEqual(['gpt-4', 'gpt-35-turbo']);
|
458
464
|
});
|
459
465
|
|
460
|
-
it('should handle complex model strings with custom names', () => {
|
461
|
-
const result = extractEnabledModels(
|
466
|
+
it('should handle complex model strings with custom names', async () => {
|
467
|
+
const result = await extractEnabledModels(
|
468
|
+
'openai',
|
469
|
+
'+gpt-4=Custom GPT-4,+claude-2=Custom Claude',
|
470
|
+
);
|
462
471
|
expect(result).toEqual(['gpt-4', 'claude-2']);
|
463
472
|
});
|
464
473
|
});
|
@@ -469,8 +478,8 @@ describe('transformToChatModelCards', () => {
|
|
469
478
|
{ id: 'model2', displayName: 'Model 2', enabled: false, type: 'chat' },
|
470
479
|
];
|
471
480
|
|
472
|
-
it('should return undefined when modelString is empty', () => {
|
473
|
-
const result = transformToAiModelList({
|
481
|
+
it('should return undefined when modelString is empty', async () => {
|
482
|
+
const result = await transformToAiModelList({
|
474
483
|
modelString: '',
|
475
484
|
defaultModels: defaultChatModels,
|
476
485
|
providerId: 'openai',
|
@@ -478,8 +487,8 @@ describe('transformToChatModelCards', () => {
|
|
478
487
|
expect(result).toBeUndefined();
|
479
488
|
});
|
480
489
|
|
481
|
-
it('should remove all models when removeAll is true', () => {
|
482
|
-
const result = transformToAiModelList({
|
490
|
+
it('should remove all models when removeAll is true', async () => {
|
491
|
+
const result = await transformToAiModelList({
|
483
492
|
modelString: '-all',
|
484
493
|
defaultModels: defaultChatModels,
|
485
494
|
providerId: 'openai',
|
@@ -487,8 +496,8 @@ describe('transformToChatModelCards', () => {
|
|
487
496
|
expect(result).toEqual([]);
|
488
497
|
});
|
489
498
|
|
490
|
-
it('should remove specified models', () => {
|
491
|
-
const result = transformToAiModelList({
|
499
|
+
it('should remove specified models', async () => {
|
500
|
+
const result = await transformToAiModelList({
|
492
501
|
modelString: '-model1',
|
493
502
|
defaultModels: defaultChatModels,
|
494
503
|
providerId: 'openai',
|
@@ -498,9 +507,9 @@ describe('transformToChatModelCards', () => {
|
|
498
507
|
]);
|
499
508
|
});
|
500
509
|
|
501
|
-
it('should add a new known model', () => {
|
510
|
+
it('should add a new known model', async () => {
|
502
511
|
const knownModel = LOBE_DEFAULT_MODEL_LIST.find((m) => m.providerId === 'ai21')!;
|
503
|
-
const result = transformToAiModelList({
|
512
|
+
const result = await transformToAiModelList({
|
504
513
|
modelString: `${knownModel.id}`,
|
505
514
|
defaultModels: defaultChatModels,
|
506
515
|
providerId: 'ai21',
|
@@ -513,9 +522,9 @@ describe('transformToChatModelCards', () => {
|
|
513
522
|
});
|
514
523
|
});
|
515
524
|
|
516
|
-
it('should update an existing known model', () => {
|
525
|
+
it('should update an existing known model', async () => {
|
517
526
|
const knownModel = LOBE_DEFAULT_MODEL_LIST.find((m) => m.providerId === 'openai')!;
|
518
|
-
const result = transformToAiModelList({
|
527
|
+
const result = await transformToAiModelList({
|
519
528
|
modelString: `+${knownModel.id}=Updated Model`,
|
520
529
|
defaultModels: [knownModel],
|
521
530
|
providerId: 'openai',
|
@@ -528,8 +537,8 @@ describe('transformToChatModelCards', () => {
|
|
528
537
|
});
|
529
538
|
});
|
530
539
|
|
531
|
-
it('should add a new custom model', () => {
|
532
|
-
const result = transformToAiModelList({
|
540
|
+
it('should add a new custom model', async () => {
|
541
|
+
const result = await transformToAiModelList({
|
533
542
|
modelString: '+custom_model=Custom Model',
|
534
543
|
defaultModels: defaultChatModels,
|
535
544
|
providerId: 'openai',
|
@@ -543,8 +552,8 @@ describe('transformToChatModelCards', () => {
|
|
543
552
|
});
|
544
553
|
});
|
545
554
|
|
546
|
-
it('should have file with builtin models like gpt-4-0125-preview', () => {
|
547
|
-
const result = transformToAiModelList({
|
555
|
+
it('should have file with builtin models like gpt-4-0125-preview', async () => {
|
556
|
+
const result = await transformToAiModelList({
|
548
557
|
modelString:
|
549
558
|
'-all,+gpt-4-0125-preview=ChatGPT-4<128000:fc:file>,+gpt-4-turbo-2024-04-09=ChatGPT-4 Vision<128000:fc:vision:file>',
|
550
559
|
defaultModels: openaiChatModels,
|
@@ -554,12 +563,12 @@ describe('transformToChatModelCards', () => {
|
|
554
563
|
expect(result).toMatchSnapshot();
|
555
564
|
});
|
556
565
|
|
557
|
-
it('should use default deploymentName from known model when not specified in string (VolcEngine case)', () => {
|
566
|
+
it('should use default deploymentName from known model when not specified in string (VolcEngine case)', async () => {
|
558
567
|
const knownModel = LOBE_DEFAULT_MODEL_LIST.find(
|
559
568
|
(m) => m.id === 'deepseek-r1' && m.providerId === 'volcengine',
|
560
569
|
);
|
561
570
|
const defaultChatModels: AiFullModelCard[] = [];
|
562
|
-
const result = transformToAiModelList({
|
571
|
+
const result = await transformToAiModelList({
|
563
572
|
modelString: '+deepseek-r1',
|
564
573
|
defaultModels: defaultChatModels,
|
565
574
|
providerId: 'volcengine',
|
@@ -571,12 +580,12 @@ describe('transformToChatModelCards', () => {
|
|
571
580
|
});
|
572
581
|
});
|
573
582
|
|
574
|
-
it('should use deploymentName from modelString when specified (VolcEngine case)', () => {
|
583
|
+
it('should use deploymentName from modelString when specified (VolcEngine case)', async () => {
|
575
584
|
const defaultChatModels: AiFullModelCard[] = [];
|
576
585
|
const knownModel = LOBE_DEFAULT_MODEL_LIST.find(
|
577
586
|
(m) => m.id === 'deepseek-r1' && m.providerId === 'volcengine',
|
578
587
|
);
|
579
|
-
const result = transformToAiModelList({
|
588
|
+
const result = await transformToAiModelList({
|
580
589
|
modelString: `+deepseek-r1->my-custom-deploy`,
|
581
590
|
defaultModels: defaultChatModels,
|
582
591
|
providerId: 'volcengine',
|
@@ -589,9 +598,9 @@ describe('transformToChatModelCards', () => {
|
|
589
598
|
});
|
590
599
|
});
|
591
600
|
|
592
|
-
it('should set both id and deploymentName to the full string when no -> is used and withDeploymentName is true', () => {
|
601
|
+
it('should set both id and deploymentName to the full string when no -> is used and withDeploymentName is true', async () => {
|
593
602
|
const defaultChatModels: AiFullModelCard[] = [];
|
594
|
-
const result = transformToAiModelList({
|
603
|
+
const result = await transformToAiModelList({
|
595
604
|
modelString: `+my_model`,
|
596
605
|
defaultModels: defaultChatModels,
|
597
606
|
providerId: 'volcengine',
|
@@ -609,7 +618,7 @@ describe('transformToChatModelCards', () => {
|
|
609
618
|
});
|
610
619
|
});
|
611
620
|
|
612
|
-
it('should handle azure real case', () => {
|
621
|
+
it('should handle azure real case', async () => {
|
613
622
|
const defaultChatModels = [
|
614
623
|
{
|
615
624
|
abilities: { functionCall: true, reasoning: true },
|
@@ -704,7 +713,7 @@ describe('transformToChatModelCards', () => {
|
|
704
713
|
const modelString =
|
705
714
|
'-all,gpt-4o->id1=GPT-4o,gpt-4o-mini->id2=GPT 4o Mini,o1-mini->id3=OpenAI o1-mini';
|
706
715
|
|
707
|
-
const data = transformToAiModelList({
|
716
|
+
const data = await transformToAiModelList({
|
708
717
|
modelString,
|
709
718
|
defaultModels: defaultChatModels,
|
710
719
|
providerId: 'azure',
|
package/src/utils/parseModels.ts
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
import { produce } from 'immer';
|
2
2
|
|
3
|
-
import { LOBE_DEFAULT_MODEL_LIST } from '@/config/aiModels';
|
4
3
|
import { AiFullModelCard, AiModelType } from '@/types/aiModel';
|
5
4
|
import { getModelPropertyWithFallback } from '@/utils/getFallbackModelProperty';
|
6
5
|
import { merge } from '@/utils/merge';
|
@@ -8,7 +7,7 @@ import { merge } from '@/utils/merge';
|
|
8
7
|
/**
|
9
8
|
* Parse model string to add or remove models.
|
10
9
|
*/
|
11
|
-
export const parseModelString = (
|
10
|
+
export const parseModelString = async (
|
12
11
|
providerId: string,
|
13
12
|
modelString: string = '',
|
14
13
|
withDeploymentName = false,
|
@@ -52,7 +51,7 @@ export const parseModelString = (
|
|
52
51
|
}
|
53
52
|
|
54
53
|
// Use new type lookup function, prioritizing same provider first, then fallback to other providers
|
55
|
-
const modelType: AiModelType = getModelPropertyWithFallback<AiModelType>(
|
54
|
+
const modelType: AiModelType = await getModelPropertyWithFallback<AiModelType>(
|
56
55
|
id,
|
57
56
|
'type',
|
58
57
|
providerId,
|
@@ -119,7 +118,7 @@ export const parseModelString = (
|
|
119
118
|
/**
|
120
119
|
* Extract a special method to process chatModels
|
121
120
|
*/
|
122
|
-
export const transformToAiModelList = ({
|
121
|
+
export const transformToAiModelList = async ({
|
123
122
|
modelString = '',
|
124
123
|
defaultModels,
|
125
124
|
providerId,
|
@@ -129,10 +128,10 @@ export const transformToAiModelList = ({
|
|
129
128
|
modelString?: string;
|
130
129
|
providerId: string;
|
131
130
|
withDeploymentName?: boolean;
|
132
|
-
}): AiFullModelCard[] | undefined => {
|
131
|
+
}): Promise<AiFullModelCard[] | undefined> => {
|
133
132
|
if (!modelString) return undefined;
|
134
133
|
|
135
|
-
const modelConfig = parseModelString(providerId, modelString, withDeploymentName);
|
134
|
+
const modelConfig = await parseModelString(providerId, modelString, withDeploymentName);
|
136
135
|
let chatModels = modelConfig.removeAll ? [] : defaultModels;
|
137
136
|
|
138
137
|
// 处理移除逻辑
|
@@ -140,6 +139,9 @@ export const transformToAiModelList = ({
|
|
140
139
|
chatModels = chatModels.filter((m) => !modelConfig.removed.includes(m.id));
|
141
140
|
}
|
142
141
|
|
142
|
+
// 异步获取配置
|
143
|
+
const { LOBE_DEFAULT_MODEL_LIST } = await import('@/config/aiModels');
|
144
|
+
|
143
145
|
return produce(chatModels, (draft) => {
|
144
146
|
// 处理添加或替换逻辑
|
145
147
|
for (const toAddModel of modelConfig.add) {
|
@@ -193,12 +195,12 @@ export const transformToAiModelList = ({
|
|
193
195
|
});
|
194
196
|
};
|
195
197
|
|
196
|
-
export const extractEnabledModels = (
|
198
|
+
export const extractEnabledModels = async (
|
197
199
|
providerId: string,
|
198
200
|
modelString: string = '',
|
199
201
|
withDeploymentName = false,
|
200
202
|
) => {
|
201
|
-
const modelConfig = parseModelString(providerId, modelString, withDeploymentName);
|
203
|
+
const modelConfig = await parseModelString(providerId, modelString, withDeploymentName);
|
202
204
|
const list = modelConfig.add.map((m) => m.id);
|
203
205
|
|
204
206
|
if (list.length === 0) return;
|