@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.
Files changed (27) hide show
  1. package/.cursor/rules/testing-guide/testing-guide.mdc +18 -0
  2. package/CHANGELOG.md +25 -0
  3. package/changelog/v1.json +9 -0
  4. package/package.json +2 -2
  5. package/src/app/[variants]/(main)/settings/provider/features/ProviderConfig/Checker.tsx +15 -2
  6. package/src/app/[variants]/(main)/settings/provider/features/ProviderConfig/index.tsx +30 -3
  7. package/src/components/Analytics/LobeAnalyticsProvider.tsx +10 -13
  8. package/src/components/Analytics/LobeAnalyticsProviderWrapper.tsx +16 -4
  9. package/src/libs/model-runtime/RouterRuntime/createRuntime.test.ts +538 -0
  10. package/src/libs/model-runtime/RouterRuntime/createRuntime.ts +50 -13
  11. package/src/libs/model-runtime/RouterRuntime/index.ts +1 -1
  12. package/src/libs/model-runtime/aihubmix/index.ts +10 -5
  13. package/src/libs/model-runtime/ppio/index.test.ts +3 -6
  14. package/src/libs/model-runtime/utils/openaiCompatibleFactory/index.ts +8 -6
  15. package/src/server/globalConfig/genServerAiProviderConfig.test.ts +22 -25
  16. package/src/server/globalConfig/genServerAiProviderConfig.ts +34 -22
  17. package/src/server/globalConfig/index.ts +1 -1
  18. package/src/server/services/discover/index.ts +11 -2
  19. package/src/services/chat.ts +1 -1
  20. package/src/store/aiInfra/slices/aiProvider/__tests__/action.test.ts +211 -0
  21. package/src/store/aiInfra/slices/aiProvider/action.ts +46 -35
  22. package/src/store/user/slices/modelList/action.test.ts +5 -5
  23. package/src/store/user/slices/modelList/action.ts +4 -4
  24. package/src/utils/getFallbackModelProperty.test.ts +52 -45
  25. package/src/utils/getFallbackModelProperty.ts +4 -3
  26. package/src/utils/parseModels.test.ts +107 -98
  27. 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('test-provider', 'deepseek-r1=Deepseek R1<65536:reasoning>');
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('test-provider', 'qwen-max-latest=Qwen Max<32768:search>');
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('openai', '+gpt-4=Custom GPT-4,+claude-2=Custom Claude');
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',
@@ -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;