@lobehub/chat 1.60.1 → 1.60.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/CHANGELOG.md CHANGED
@@ -2,6 +2,31 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ### [Version 1.60.2](https://github.com/lobehub/lobe-chat/compare/v1.60.1...v1.60.2)
6
+
7
+ <sup>Released on **2025-02-17**</sup>
8
+
9
+ #### 🐛 Bug Fixes
10
+
11
+ - **misc**: Fix model list issue in client mode.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### What's fixed
19
+
20
+ - **misc**: Fix model list issue in client mode, closes [#6240](https://github.com/lobehub/lobe-chat/issues/6240) ([d6c6cda](https://github.com/lobehub/lobe-chat/commit/d6c6cda))
21
+
22
+ </details>
23
+
24
+ <div align="right">
25
+
26
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
27
+
28
+ </div>
29
+
5
30
  ### [Version 1.60.1](https://github.com/lobehub/lobe-chat/compare/v1.60.0...v1.60.1)
6
31
 
7
32
  <sup>Released on **2025-02-17**</sup>
package/changelog/v1.json CHANGED
@@ -1,4 +1,13 @@
1
1
  [
2
+ {
3
+ "children": {
4
+ "fixes": [
5
+ "Fix model list issue in client mode."
6
+ ]
7
+ },
8
+ "date": "2025-02-17",
9
+ "version": "1.60.2"
10
+ },
2
11
  {
3
12
  "children": {
4
13
  "improvements": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "1.60.1",
3
+ "version": "1.60.2",
4
4
  "description": "Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
5
5
  "keywords": [
6
6
  "framework",
@@ -24,7 +24,7 @@ const Page = async (props: PagePropsWithId) => {
24
24
  if (isServerMode) {
25
25
  const { userId } = await getUserAuth();
26
26
 
27
- const { aiProvider } = getServerGlobalConfig();
27
+ const { aiProvider } = await getServerGlobalConfig();
28
28
  const aiInfraRepos = new AiInfraRepos(
29
29
  serverDB,
30
30
  userId!,
@@ -4,31 +4,84 @@ const azureChatModels: AIChatModelCard[] = [
4
4
  {
5
5
  abilities: {
6
6
  functionCall: true,
7
+ reasoning: true,
7
8
  },
8
9
  config: {
9
- deploymentName: 'gpt-35-turbo',
10
+ deploymentName: 'o3-mini',
10
11
  },
11
- contextWindowTokens: 16_385,
12
+ contextWindowTokens: 200_000,
12
13
  description:
13
- 'GPT 3.5 Turbo,OpenAI提供的高效模型,适用于聊天和文本生成任务,支持并行函数调用。',
14
- displayName: 'GPT 3.5 Turbo',
14
+ 'o3-mini 是我们最新的小型推理模型,在与 o1-mini 相同的成本和延迟目标下提供高智能。',
15
+ displayName: 'OpenAI o3-mini',
16
+ id: 'o3-mini',
17
+ maxOutput: 100_000,
18
+ pricing: {
19
+ input: 1.1,
20
+ output: 4.4,
21
+ },
22
+ releasedAt: '2025-01-31',
23
+ type: 'chat',
24
+ },
25
+ {
26
+ abilities: {
27
+ reasoning: true,
28
+ },
29
+ config: {
30
+ deploymentName: 'o1-mini',
31
+ },
32
+ contextWindowTokens: 128_000,
33
+ description:
34
+ 'o1-mini是一款针对编程、数学和科学应用场景而设计的快速、经济高效的推理模型。该模型具有128K上下文和2023年10月的知识截止日期。',
35
+ displayName: 'OpenAI o1-mini',
15
36
  enabled: true,
16
- id: 'gpt-3.5-turbo',
17
- maxOutput: 4096,
37
+ id: 'o1-mini',
38
+ maxOutput: 65_536,
39
+ pricing: {
40
+ input: 1.1,
41
+ output: 4.4,
42
+ },
43
+ releasedAt: '2024-09-12',
18
44
  type: 'chat',
19
45
  },
20
46
  {
21
47
  abilities: {
22
- functionCall: true,
48
+ reasoning: true,
23
49
  },
24
50
  config: {
25
- deploymentName: 'gpt-35-turbo-16k',
51
+ deploymentName: 'o1',
26
52
  },
27
- contextWindowTokens: 16_384,
28
- description: 'GPT 3.5 Turbo 16k,高容量文本生成模型,适合复杂任务。',
29
- displayName: 'GPT 3.5 Turbo',
30
- id: 'gpt-3.5-turbo-16k',
31
- maxOutput: 4096,
53
+ contextWindowTokens: 200_000,
54
+ description:
55
+ 'o1是OpenAI新的推理模型,支持图文输入并输出文本,适用于需要广泛通用知识的复杂任务。该模型具有200K上下文和2023年10月的知识截止日期。',
56
+ displayName: 'OpenAI o1',
57
+ enabled: true,
58
+ id: 'o1',
59
+ maxOutput: 100_000,
60
+ pricing: {
61
+ input: 15,
62
+ output: 60,
63
+ },
64
+ releasedAt: '2024-12-17',
65
+ type: 'chat',
66
+ },
67
+ {
68
+ abilities: {
69
+ reasoning: true,
70
+ },
71
+ config: {
72
+ deploymentName: 'o1-preview',
73
+ },
74
+ contextWindowTokens: 128_000,
75
+ description:
76
+ 'o1是OpenAI新的推理模型,适用于需要广泛通用知识的复杂任务。该模型具有128K上下文和2023年10月的知识截止日期。',
77
+ displayName: 'OpenAI o1-preview',
78
+ id: 'o1-preview',
79
+ maxOutput: 32_768,
80
+ pricing: {
81
+ input: 15,
82
+ output: 60,
83
+ },
84
+ releasedAt: '2024-09-12',
32
85
  type: 'chat',
33
86
  },
34
87
  {
@@ -37,29 +90,34 @@ const azureChatModels: AIChatModelCard[] = [
37
90
  vision: true,
38
91
  },
39
92
  config: {
40
- deploymentName: 'gpt-4-turbo',
93
+ deploymentName: 'gpt-4o',
41
94
  },
42
95
  contextWindowTokens: 128_000,
43
- description: 'GPT 4 Turbo,多模态模型,提供杰出的语言理解和生成能力,同时支持图像输入。',
44
- displayName: 'GPT 4 Turbo',
96
+ description:
97
+ 'ChatGPT-4o 是一款动态模型,实时更新以保持当前最新版本。它结合了强大的语言理解与生成能力,适合于大规模应用场景,包括客户服务、教育和技术支持。',
98
+ displayName: 'GPT-4o',
45
99
  enabled: true,
46
- id: 'gpt-4',
47
- maxOutput: 4096,
100
+ id: 'gpt-4o',
101
+ pricing: {
102
+ input: 2.5,
103
+ output: 10,
104
+ },
105
+ releasedAt: '2024-05-13',
48
106
  type: 'chat',
49
107
  },
108
+
50
109
  {
51
110
  abilities: {
52
111
  functionCall: true,
53
112
  vision: true,
54
113
  },
55
114
  config: {
56
- deploymentName: 'gpt-4o-mini',
115
+ deploymentName: 'gpt-4-turbo',
57
116
  },
58
117
  contextWindowTokens: 128_000,
59
- description: 'GPT-4o Mini,小型高效模型,具备与GPT-4o相似的卓越性能。',
60
- displayName: 'GPT 4o Mini',
61
- enabled: true,
62
- id: 'gpt-4o-mini',
118
+ description: 'GPT 4 Turbo,多模态模型,提供杰出的语言理解和生成能力,同时支持图像输入。',
119
+ displayName: 'GPT 4 Turbo',
120
+ id: 'gpt-4',
63
121
  maxOutput: 4096,
64
122
  type: 'chat',
65
123
  },
@@ -69,13 +127,13 @@ const azureChatModels: AIChatModelCard[] = [
69
127
  vision: true,
70
128
  },
71
129
  config: {
72
- deploymentName: 'gpt-4o',
130
+ deploymentName: 'gpt-4o-mini',
73
131
  },
74
132
  contextWindowTokens: 128_000,
75
- description: 'GPT-4o 是最新的多模态模型,结合高级文本和图像处理能力。',
76
- displayName: 'GPT 4o',
133
+ description: 'GPT-4o Mini,小型高效模型,具备与GPT-4o相似的卓越性能。',
134
+ displayName: 'GPT 4o Mini',
77
135
  enabled: true,
78
- id: 'gpt-4o',
136
+ id: 'gpt-4o-mini',
79
137
  maxOutput: 4096,
80
138
  type: 'chat',
81
139
  },
@@ -22,7 +22,7 @@ export class AiInfraRepos {
22
22
  private userId: string;
23
23
  private db: LobeChatDatabase;
24
24
  aiProviderModel: AiProviderModel;
25
- private providerConfigs: Record<string, ProviderConfig>;
25
+ private readonly providerConfigs: Record<string, ProviderConfig>;
26
26
  aiModelModel: AiModelModel;
27
27
 
28
28
  constructor(
@@ -66,6 +66,9 @@ export class AiInfraRepos {
66
66
  });
67
67
  };
68
68
 
69
+ /**
70
+ * used in the chat page. to show the enabled providers
71
+ */
69
72
  getUserEnabledProviderList = async () => {
70
73
  const list = await this.getAiProviderList();
71
74
  return list
@@ -81,6 +84,9 @@ export class AiInfraRepos {
81
84
  );
82
85
  };
83
86
 
87
+ /**
88
+ * used in the chat page. to show the enabled models
89
+ */
84
90
  getEnabledModels = async () => {
85
91
  const providers = await this.getAiProviderList();
86
92
  const enabledProviders = providers.filter((item) => item.enabled);
@@ -129,15 +135,6 @@ export class AiInfraRepos {
129
135
  ) as EnabledAiModel[];
130
136
  };
131
137
 
132
- getAiProviderModelList = async (providerId: string) => {
133
- const aiModels = await this.aiModelModel.getModelListByProviderId(providerId);
134
-
135
- const defaultModels: AiProviderModelListItem[] =
136
- (await this.fetchBuiltinModels(providerId)) || [];
137
-
138
- return mergeArrayById(defaultModels, aiModels) as AiProviderModelListItem[];
139
- };
140
-
141
138
  getAiProviderRuntimeState = async (
142
139
  decryptor?: DecryptUserKeyVaults,
143
140
  ): Promise<AiProviderRuntimeState> => {
@@ -156,6 +153,18 @@ export class AiInfraRepos {
156
153
  return { enabledAiModels, enabledAiProviders, runtimeConfig };
157
154
  };
158
155
 
156
+ getAiProviderModelList = async (providerId: string) => {
157
+ const aiModels = await this.aiModelModel.getModelListByProviderId(providerId);
158
+
159
+ const defaultModels: AiProviderModelListItem[] =
160
+ (await this.fetchBuiltinModels(providerId)) || [];
161
+
162
+ return mergeArrayById(defaultModels, aiModels) as AiProviderModelListItem[];
163
+ };
164
+
165
+ /**
166
+ * use in the `/settings/provider/[id]` page
167
+ */
159
168
  getAiProviderDetail = async (id: string, decryptor?: DecryptUserKeyVaults) => {
160
169
  const config = await this.aiProviderModel.getAiProviderById(id, decryptor);
161
170
 
@@ -171,6 +180,7 @@ export class AiInfraRepos {
171
180
  try {
172
181
  const { default: providerModels } = await import(`@/config/aiModels/${providerId}`);
173
182
 
183
+ // use the serverModelLists as the defined server model list
174
184
  const presetList = this.providerConfigs[providerId]?.serverModelLists || providerModels;
175
185
  return (presetList as AIChatModelCard[]).map<AiProviderModelListItem>((m) => ({
176
186
  ...m,
@@ -22,8 +22,8 @@ exports[`LobeOpenAI > models > should get models 1`] = `
22
22
  },
23
23
  {
24
24
  "contextWindowTokens": 16385,
25
- "displayName": "GPT 3.5 Turbo",
26
- "enabled": true,
25
+ "displayName": "GPT-3.5 Turbo",
26
+ "enabled": false,
27
27
  "functionCall": true,
28
28
  "id": "gpt-3.5-turbo",
29
29
  "reasoning": false,
@@ -39,8 +39,8 @@ exports[`LobeOpenAI > models > should get models 1`] = `
39
39
  "vision": false,
40
40
  },
41
41
  {
42
- "contextWindowTokens": 16384,
43
- "displayName": "GPT 3.5 Turbo",
42
+ "contextWindowTokens": undefined,
43
+ "displayName": undefined,
44
44
  "enabled": false,
45
45
  "functionCall": true,
46
46
  "id": "gpt-3.5-turbo-16k",
@@ -221,7 +221,7 @@ exports[`LobeOpenAI > models > should get models 1`] = `
221
221
  {
222
222
  "contextWindowTokens": 128000,
223
223
  "displayName": "GPT 4 Turbo",
224
- "enabled": true,
224
+ "enabled": false,
225
225
  "functionCall": true,
226
226
  "id": "gpt-4",
227
227
  "reasoning": false,
@@ -12,7 +12,7 @@ import { genServerAiProvidersConfig } from './genServerAiProviderConfig';
12
12
  import { parseAgentConfig } from './parseDefaultAgent';
13
13
  import { parseFilesConfig } from './parseFilesConfig';
14
14
 
15
- export const getServerGlobalConfig = () => {
15
+ export const getServerGlobalConfig = async () => {
16
16
  const { ACCESS_CODES, DEFAULT_AGENT_CONFIG } = getAppConfig();
17
17
 
18
18
  const config: GlobalServerConfig = {
@@ -38,6 +38,9 @@ export const getServerGlobalConfig = () => {
38
38
  ollama: {
39
39
  fetchOnClient: !process.env.OLLAMA_PROXY_URL,
40
40
  },
41
+ volcengine: {
42
+ withDeploymentName: true,
43
+ },
41
44
  }),
42
45
  defaultAgent: {
43
46
  config: parseAgentConfig(DEFAULT_AGENT_CONFIG),
@@ -19,7 +19,7 @@ const aiModelProcedure = authedProcedure.use(async (opts) => {
19
19
  const { ctx } = opts;
20
20
 
21
21
  const gateKeeper = await KeyVaultsGateKeeper.initWithEnvKey();
22
- const { aiProvider } = getServerGlobalConfig();
22
+ const { aiProvider } = await getServerGlobalConfig();
23
23
 
24
24
  return opts.next({
25
25
  ctx: {
@@ -19,7 +19,7 @@ import { ProviderConfig } from '@/types/user/settings';
19
19
  const aiProviderProcedure = authedProcedure.use(async (opts) => {
20
20
  const { ctx } = opts;
21
21
 
22
- const { aiProvider } = getServerGlobalConfig();
22
+ const { aiProvider } = await getServerGlobalConfig();
23
23
 
24
24
  const gateKeeper = await KeyVaultsGateKeeper.initWithEnvKey();
25
25
  return opts.next({
@@ -10,7 +10,13 @@ export class ClientService extends BaseClientService implements IAiModelService
10
10
  return new AiModelModel(clientDB as any, this.userId);
11
11
  }
12
12
  private get aiInfraRepos(): AiInfraRepos {
13
- return new AiInfraRepos(clientDB as any, this.userId, {});
13
+ let config = {};
14
+
15
+ if (typeof window !== 'undefined') {
16
+ config = window.global_serverConfigStore.getState().serverConfig.aiProvider || {};
17
+ }
18
+
19
+ return new AiInfraRepos(clientDB as any, this.userId, config);
14
20
  }
15
21
 
16
22
  createAiModel: IAiModelService['createAiModel'] = async (params) => {
@@ -101,4 +101,241 @@ describe('mergeArrayById', () => {
101
101
  },
102
102
  ]);
103
103
  });
104
+
105
+ describe('empty array handling', () => {
106
+ it('should return empty array when both inputs are empty', () => {
107
+ const result = mergeArrayById([], []);
108
+ expect(result).toEqual([]);
109
+ });
110
+
111
+ it('should return all default items when user items is empty', () => {
112
+ const defaultItems = [
113
+ { id: '1', name: 'Default 1', value: 100 },
114
+ { id: '2', name: 'Default 2', value: 200 },
115
+ ];
116
+
117
+ const result = mergeArrayById(defaultItems, []);
118
+ expect(result).toEqual(defaultItems);
119
+ });
120
+
121
+ it('should return all user items when default items is empty', () => {
122
+ const userItems = [
123
+ { id: '1', name: 'User 1', value: 300 },
124
+ { id: '2', name: 'User 2', value: 400 },
125
+ ];
126
+
127
+ const result = mergeArrayById([], userItems);
128
+ expect(result).toEqual(userItems);
129
+ });
130
+ });
131
+
132
+ describe('ID matching scenarios', () => {
133
+ it('should handle user items with IDs not in default items', () => {
134
+ const defaultItems = [{ id: '1', name: 'Default 1', value: 100 }];
135
+ const userItems = [
136
+ { id: '1', name: 'User 1', value: 200 },
137
+ { id: '2', name: 'User 2', value: 300 }, // New ID
138
+ ];
139
+
140
+ const result = mergeArrayById(defaultItems, userItems);
141
+ expect(result).toHaveLength(2);
142
+ expect(result).toContainEqual({ id: '1', name: 'User 1', value: 200 });
143
+ expect(result).toContainEqual({ id: '2', name: 'User 2', value: 300 });
144
+ });
145
+
146
+ it('should merge multiple items correctly', () => {
147
+ const defaultItems = [
148
+ { id: '1', name: 'Default 1', value: 100, meta: { key: 'value' } },
149
+ { id: '2', name: 'Default 2', value: 200, meta: { key: 'value' } },
150
+ ];
151
+
152
+ const userItems = [
153
+ { id: '2', name: 'User 2', value: 300 },
154
+ { id: '1', name: 'User 1', value: 400 },
155
+ ];
156
+
157
+ const result = mergeArrayById(defaultItems, userItems);
158
+
159
+ expect(result).toHaveLength(2);
160
+ expect(result).toContainEqual({
161
+ id: '1',
162
+ name: 'User 1',
163
+ value: 400,
164
+ meta: { key: 'value' },
165
+ });
166
+ expect(result).toContainEqual({
167
+ id: '2',
168
+ name: 'User 2',
169
+ value: 300,
170
+ meta: { key: 'value' },
171
+ });
172
+ });
173
+ });
174
+
175
+ describe('special value handling', () => {
176
+ it('should handle undefined values by keeping default values', () => {
177
+ const defaultItems = [{ id: '1', name: 'Default', value: 100, meta: { key: 'value' } }];
178
+
179
+ const userItems = [{ id: '1', name: undefined, value: 200, meta: undefined }];
180
+
181
+ const result = mergeArrayById(defaultItems, userItems as any);
182
+
183
+ expect(result).toEqual([{ id: '1', name: 'Default', value: 200, meta: { key: 'value' } }]);
184
+ });
185
+
186
+ it('should handle nested objects correctly', () => {
187
+ const defaultItems = [
188
+ {
189
+ id: '1',
190
+ config: {
191
+ deep: {
192
+ value: 100,
193
+ keep: true,
194
+ },
195
+ surface: 'default',
196
+ },
197
+ },
198
+ ];
199
+
200
+ const userItems = [
201
+ {
202
+ id: '1',
203
+ config: {
204
+ deep: {
205
+ value: 200,
206
+ },
207
+ surface: 'changed',
208
+ },
209
+ },
210
+ ];
211
+
212
+ const result = mergeArrayById(defaultItems, userItems);
213
+
214
+ expect(result[0].config).toEqual({
215
+ deep: {
216
+ value: 200,
217
+ keep: true,
218
+ },
219
+ surface: 'changed',
220
+ });
221
+ });
222
+ });
223
+
224
+ describe('edge cases', () => {
225
+ it('should handle objects missing id property', () => {
226
+ const defaultItems = [{ id: '1', name: 'Default' }];
227
+
228
+ const userItems = [{ name: 'Invalid' }];
229
+
230
+ expect(mergeArrayById(defaultItems, userItems as any)).toEqual([
231
+ { name: 'Invalid' },
232
+ { id: '1', name: 'Default' },
233
+ ]);
234
+ });
235
+
236
+ it('should preserve the source objects (no mutation)', () => {
237
+ const defaultItems = [{ id: '1', name: 'Default', meta: { key: 'value' } }];
238
+ const userItems = [{ id: '1', name: 'User' }];
239
+
240
+ const defaultItemsClone = JSON.parse(JSON.stringify(defaultItems));
241
+ const userItemsClone = JSON.parse(JSON.stringify(userItems));
242
+
243
+ mergeArrayById(defaultItems, userItems);
244
+
245
+ expect(defaultItems).toEqual(defaultItemsClone);
246
+ expect(userItems).toEqual(userItemsClone);
247
+ });
248
+
249
+ it('should handle duplicate IDs in user items by using the last occurrence', () => {
250
+ const defaultItems = [{ id: '1', name: 'Default', value: 100 }];
251
+ const userItems = [
252
+ { id: '1', name: 'User 1', value: 200 },
253
+ { id: '1', name: 'User 2', value: 300 }, // Duplicate ID
254
+ ];
255
+
256
+ const result = mergeArrayById(defaultItems, userItems);
257
+
258
+ expect(result).toHaveLength(1);
259
+ expect(result[0]).toEqual({
260
+ id: '1',
261
+ name: 'User 2',
262
+ value: 300,
263
+ });
264
+ });
265
+ });
266
+
267
+ it('should merge data with not empty objects', () => {
268
+ const data = mergeArrayById(
269
+ [
270
+ {
271
+ abilities: {
272
+ reasoning: true,
273
+ functionCalling: true,
274
+ },
275
+ config: {
276
+ deploymentName: 'o1',
277
+ },
278
+ contextWindowTokens: 200000,
279
+ description:
280
+ 'o1是OpenAI新的推理模型,支持图文输入并输出文本,适用于需要广泛通用知识的复杂任务。该模型具有200K上下文和2023年10月的知识截止日期。',
281
+ displayName: 'OpenAI o1',
282
+ enabled: true,
283
+ id: 'o1',
284
+ maxOutput: 100000,
285
+ pricing: {
286
+ input: 15,
287
+ output: 60,
288
+ },
289
+ releasedAt: '2024-12-17',
290
+ type: 'chat',
291
+ source: 'builtin',
292
+ },
293
+ ],
294
+ [
295
+ {
296
+ abilities: {
297
+ reasoning: true,
298
+ },
299
+ config: {
300
+ deploymentName: 'ddd',
301
+ },
302
+ contextWindowTokens: 200000,
303
+ description:
304
+ 'o1是OpenAI新的推理模型,支持图文输入并输出文本,适用于需要广泛通用知识的复杂任务。该模型具有200K上下文和2023年10月的知识截止日期。',
305
+ displayName: 'OpenAI o1',
306
+ enabled: true,
307
+ id: 'o1',
308
+ maxOutput: 100000,
309
+ releasedAt: '2024-12-17',
310
+ type: 'chat',
311
+ },
312
+ ],
313
+ );
314
+
315
+ expect(data).toEqual([
316
+ {
317
+ abilities: {
318
+ functionCalling: true,
319
+ reasoning: true,
320
+ },
321
+ config: {
322
+ deploymentName: 'ddd',
323
+ },
324
+ contextWindowTokens: 200000,
325
+ description:
326
+ 'o1是OpenAI新的推理模型,支持图文输入并输出文本,适用于需要广泛通用知识的复杂任务。该模型具有200K上下文和2023年10月的知识截止日期。',
327
+ displayName: 'OpenAI o1',
328
+ enabled: true,
329
+ id: 'o1',
330
+ pricing: {
331
+ input: 15,
332
+ output: 60,
333
+ },
334
+ source: 'builtin',
335
+ maxOutput: 100000,
336
+ releasedAt: '2024-12-17',
337
+ type: 'chat',
338
+ },
339
+ ]);
340
+ });
104
341
  });
@@ -24,28 +24,39 @@ export const mergeArrayById = <T extends MergeableItem>(defaultItems: T[], userI
24
24
  // Create a map of default items for faster lookup
25
25
  const defaultItemsMap = new Map(defaultItems.map((item) => [item.id, item]));
26
26
 
27
+ // 使用 Map 存储合并结果,这样重复 ID 的后项会自然覆盖前项
28
+ const mergedItemsMap = new Map<string, T>();
29
+
27
30
  // Process user items with default metadata
28
- const mergedItems = userItems.map((userItem) => {
31
+ userItems.forEach((userItem) => {
29
32
  const defaultItem = defaultItemsMap.get(userItem.id);
30
- if (!defaultItem) return userItem;
33
+ if (!defaultItem) {
34
+ mergedItemsMap.set(userItem.id, userItem);
35
+ return;
36
+ }
31
37
 
32
- // Merge strategy: use default value when user value is null or undefined
33
38
  const mergedItem: T = { ...defaultItem };
34
39
  Object.entries(userItem).forEach(([key, value]) => {
35
- // Only use user value if it's not null and not undefined
36
- // and not empty object
37
40
  if (value !== null && value !== undefined && !(typeof value === 'object' && isEmpty(value))) {
38
41
  // @ts-expect-error
39
42
  mergedItem[key] = value;
40
43
  }
44
+
45
+ if (typeof value === 'object' && !isEmpty(value)) {
46
+ // @ts-expect-error
47
+ mergedItem[key] = merge(defaultItem[key], value);
48
+ }
41
49
  });
42
50
 
43
- return mergedItem;
51
+ mergedItemsMap.set(userItem.id, mergedItem);
44
52
  });
45
53
 
46
- // Add items that only exist in default configuration
47
- const userItemIds = new Set(userItems.map((item) => item.id));
48
- const onlyInDefaultItems = defaultItems.filter((item) => !userItemIds.has(item.id));
54
+ // 添加只在默认配置中存在的项
55
+ defaultItems.forEach((item) => {
56
+ if (!mergedItemsMap.has(item.id)) {
57
+ mergedItemsMap.set(item.id, item);
58
+ }
59
+ });
49
60
 
50
- return [...mergedItems, ...onlyInDefaultItems];
61
+ return Array.from(mergedItemsMap.values());
51
62
  };
@@ -301,6 +301,36 @@ describe('parseModelString', () => {
301
301
  },
302
302
  });
303
303
  });
304
+
305
+ it('should handle with multi deployName', () => {
306
+ const result = parseModelString(
307
+ 'gpt-4o->id1=GPT-4o,gpt-4o-mini->id2=gpt-4o-mini,o1-mini->id3=O1 mini',
308
+ true,
309
+ );
310
+ expect(result.add).toEqual([
311
+ {
312
+ abilities: {},
313
+ displayName: 'GPT-4o',
314
+ id: 'gpt-4o',
315
+ type: 'chat',
316
+ config: { deploymentName: 'id1' },
317
+ },
318
+ {
319
+ abilities: {},
320
+ displayName: 'gpt-4o-mini',
321
+ id: 'gpt-4o-mini',
322
+ type: 'chat',
323
+ config: { deploymentName: 'id2' },
324
+ },
325
+ {
326
+ abilities: {},
327
+ displayName: 'O1 mini',
328
+ id: 'o1-mini',
329
+ type: 'chat',
330
+ config: { deploymentName: 'id3' },
331
+ },
332
+ ]);
333
+ });
304
334
  });
305
335
  });
306
336
 
@@ -394,4 +424,154 @@ describe('transformToChatModelCards', () => {
394
424
 
395
425
  expect(result).toMatchSnapshot();
396
426
  });
427
+
428
+ it('should handle azure real case', () => {
429
+ const defaultChatModels = [
430
+ {
431
+ abilities: { functionCall: true, reasoning: true },
432
+ config: { deploymentName: 'o3-mini' },
433
+ contextWindowTokens: 200000,
434
+ description:
435
+ 'o3-mini 是我们最新的小型推理模型,在与 o1-mini 相同的成本和延迟目标下提供高智能。',
436
+ displayName: 'OpenAI o3-mini',
437
+ id: 'o3-mini',
438
+ maxOutput: 100000,
439
+ pricing: { input: 1.1, output: 4.4 },
440
+ releasedAt: '2025-01-31',
441
+ type: 'chat',
442
+ },
443
+ {
444
+ abilities: { reasoning: true },
445
+ config: { deploymentName: 'o1-mini' },
446
+ contextWindowTokens: 128000,
447
+ description:
448
+ 'o1-mini是一款针对编程、数学和科学应用场景而设计的快速、经济高效的推理模型。该模型具有128K上下文和2023年10月的知识截止日期。',
449
+ displayName: 'OpenAI o1-mini',
450
+ enabled: true,
451
+ id: 'o1-mini',
452
+ maxOutput: 65536,
453
+ pricing: { input: 1.1, output: 4.4 },
454
+ releasedAt: '2024-09-12',
455
+ type: 'chat',
456
+ },
457
+ {
458
+ abilities: { reasoning: true },
459
+ config: { deploymentName: 'o1' },
460
+ contextWindowTokens: 200000,
461
+ description:
462
+ 'o1是OpenAI新的推理模型,支持图文输入并输出文本,适用于需要广泛通用知识的复杂任务。该模型具有200K上下文和2023年10月的知识截止日期。',
463
+ displayName: 'OpenAI o1',
464
+ enabled: true,
465
+ id: 'o1',
466
+ maxOutput: 100000,
467
+ pricing: { input: 15, output: 60 },
468
+ releasedAt: '2024-12-17',
469
+ type: 'chat',
470
+ },
471
+ {
472
+ abilities: { reasoning: true },
473
+ config: { deploymentName: 'o1-preview' },
474
+ contextWindowTokens: 128000,
475
+ description:
476
+ 'o1是OpenAI新的推理模型,适用于需要广泛通用知识的复杂任务。该模型具有128K上下文和2023年10月的知识截止日期。',
477
+ displayName: 'OpenAI o1-preview',
478
+ id: 'o1-preview',
479
+ maxOutput: 32768,
480
+ pricing: { input: 15, output: 60 },
481
+ releasedAt: '2024-09-12',
482
+ type: 'chat',
483
+ },
484
+ {
485
+ abilities: { functionCall: true, vision: true },
486
+ config: { deploymentName: 'gpt-4o' },
487
+ contextWindowTokens: 128000,
488
+ description:
489
+ 'ChatGPT-4o 是一款动态模型,实时更新以保持当前最新版本。它结合了强大的语言理解与生成能力,适合于大规模应用场景,包括客户服务、教育和技术支持。',
490
+ displayName: 'GPT-4o',
491
+ enabled: true,
492
+ id: 'gpt-4o',
493
+ pricing: { input: 2.5, output: 10 },
494
+ releasedAt: '2024-05-13',
495
+ type: 'chat',
496
+ },
497
+ {
498
+ abilities: { functionCall: true, vision: true },
499
+ config: { deploymentName: 'gpt-4-turbo' },
500
+ contextWindowTokens: 128000,
501
+ description: 'GPT 4 Turbo,多模态模型,提供杰出的语言理解和生成能力,同时支持图像输入。',
502
+ displayName: 'GPT 4 Turbo',
503
+ id: 'gpt-4',
504
+ maxOutput: 4096,
505
+ type: 'chat',
506
+ },
507
+ {
508
+ abilities: { functionCall: true, vision: true },
509
+ config: { deploymentName: 'gpt-4o-mini' },
510
+ contextWindowTokens: 128000,
511
+ description: 'GPT-4o Mini,小型高效模型,具备与GPT-4o相似的卓越性能。',
512
+ displayName: 'GPT 4o Mini',
513
+ enabled: true,
514
+ id: 'gpt-4o-mini',
515
+ maxOutput: 4096,
516
+ type: 'chat',
517
+ },
518
+ ] as AiFullModelCard[];
519
+
520
+ const modelString =
521
+ '-all,gpt-4o->id1=GPT-4o,gpt-4o-mini->id2=GPT 4o Mini,o1-mini->id3=OpenAI o1-mini';
522
+
523
+ const data = transformToAiChatModelList({
524
+ modelString,
525
+ defaultChatModels,
526
+ providerId: 'azure',
527
+ withDeploymentName: true,
528
+ });
529
+
530
+ expect(data).toEqual([
531
+ {
532
+ abilities: { functionCall: true, vision: true },
533
+ config: { deploymentName: 'id1' },
534
+ contextWindowTokens: 128000,
535
+ description:
536
+ 'ChatGPT-4o 是一款动态模型,实时更新以保持当前最新版本。它结合了强大的语言理解与生成能力,适合于大规模应用场景,包括客户服务、教育和技术支持。',
537
+ displayName: 'GPT-4o',
538
+ enabled: true,
539
+ id: 'gpt-4o',
540
+ pricing: { input: 2.5, output: 10 },
541
+ providerId: 'azure',
542
+ releasedAt: '2024-05-13',
543
+ source: 'builtin',
544
+ type: 'chat',
545
+ },
546
+ {
547
+ abilities: { functionCall: true, vision: true },
548
+ config: { deploymentName: 'id2' },
549
+ contextWindowTokens: 128000,
550
+ description: 'GPT-4o Mini,小型高效模型,具备与GPT-4o相似的卓越性能。',
551
+ displayName: 'GPT 4o Mini',
552
+ providerId: 'azure',
553
+ source: 'builtin',
554
+ enabled: true,
555
+ id: 'gpt-4o-mini',
556
+ maxOutput: 4096,
557
+ type: 'chat',
558
+ },
559
+ {
560
+ abilities: { reasoning: true },
561
+ config: { deploymentName: 'id3' },
562
+ contextWindowTokens: 128000,
563
+ description:
564
+ 'o1-mini是一款针对编程、数学和科学应用场景而设计的快速、经济高效的推理模型。该模型具有128K上下文和2023年10月的知识截止日期。',
565
+ displayName: 'OpenAI o1-mini',
566
+ enabled: true,
567
+ providerId: 'azure',
568
+ source: 'builtin',
569
+ id: 'o1-mini',
570
+ maxOutput: 65536,
571
+ pricing: { input: 1.1, output: 4.4 },
572
+ releasedAt: '2024-09-12',
573
+ type: 'chat',
574
+ },
575
+ ]);
576
+ });
397
577
  });