@lobehub/chat 1.60.0 → 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 +50 -0
- package/changelog/v1.json +18 -0
- package/package.json +1 -1
- package/src/app/[variants]/(main)/settings/provider/(detail)/[id]/page.tsx +1 -1
- package/src/config/aiModels/azure.ts +85 -27
- package/src/config/aiModels/jina.ts +1 -1
- package/src/config/modelProviders/jina.ts +1 -1
- package/src/database/repositories/aiInfra/index.ts +20 -10
- package/src/libs/agent-runtime/openai/__snapshots__/index.test.ts.snap +5 -5
- package/src/server/globalConfig/index.ts +4 -1
- package/src/server/routers/lambda/aiModel.ts +1 -1
- package/src/server/routers/lambda/aiProvider.ts +1 -1
- package/src/services/aiModel/client.ts +7 -1
- package/src/utils/merge.test.ts +237 -0
- package/src/utils/merge.ts +21 -10
- package/src/utils/parseModels.test.ts +180 -0
package/CHANGELOG.md
CHANGED
@@ -2,6 +2,56 @@
|
|
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
|
+
[](#readme-top)
|
27
|
+
|
28
|
+
</div>
|
29
|
+
|
30
|
+
### [Version 1.60.1](https://github.com/lobehub/lobe-chat/compare/v1.60.0...v1.60.1)
|
31
|
+
|
32
|
+
<sup>Released on **2025-02-17**</sup>
|
33
|
+
|
34
|
+
#### 💄 Styles
|
35
|
+
|
36
|
+
- **misc**: Update Jina AI Provider name & model info.
|
37
|
+
|
38
|
+
<br/>
|
39
|
+
|
40
|
+
<details>
|
41
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
42
|
+
|
43
|
+
#### Styles
|
44
|
+
|
45
|
+
- **misc**: Update Jina AI Provider name & model info, closes [#6243](https://github.com/lobehub/lobe-chat/issues/6243) ([ddbe482](https://github.com/lobehub/lobe-chat/commit/ddbe482))
|
46
|
+
|
47
|
+
</details>
|
48
|
+
|
49
|
+
<div align="right">
|
50
|
+
|
51
|
+
[](#readme-top)
|
52
|
+
|
53
|
+
</div>
|
54
|
+
|
5
55
|
## [Version 1.60.0](https://github.com/lobehub/lobe-chat/compare/v1.59.0...v1.60.0)
|
6
56
|
|
7
57
|
<sup>Released on **2025-02-17**</sup>
|
package/changelog/v1.json
CHANGED
@@ -1,4 +1,22 @@
|
|
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
|
+
},
|
11
|
+
{
|
12
|
+
"children": {
|
13
|
+
"improvements": [
|
14
|
+
"Update Jina AI Provider name & model info."
|
15
|
+
]
|
16
|
+
},
|
17
|
+
"date": "2025-02-17",
|
18
|
+
"version": "1.60.1"
|
19
|
+
},
|
2
20
|
{
|
3
21
|
"children": {
|
4
22
|
"features": [
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@lobehub/chat",
|
3
|
-
"version": "1.60.
|
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: '
|
10
|
+
deploymentName: 'o3-mini',
|
10
11
|
},
|
11
|
-
contextWindowTokens:
|
12
|
+
contextWindowTokens: 200_000,
|
12
13
|
description:
|
13
|
-
'
|
14
|
-
displayName: '
|
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: '
|
17
|
-
maxOutput:
|
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
|
-
|
48
|
+
reasoning: true,
|
23
49
|
},
|
24
50
|
config: {
|
25
|
-
deploymentName: '
|
51
|
+
deploymentName: 'o1',
|
26
52
|
},
|
27
|
-
contextWindowTokens:
|
28
|
-
description:
|
29
|
-
|
30
|
-
|
31
|
-
|
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-
|
93
|
+
deploymentName: 'gpt-4o',
|
41
94
|
},
|
42
95
|
contextWindowTokens: 128_000,
|
43
|
-
description:
|
44
|
-
|
96
|
+
description:
|
97
|
+
'ChatGPT-4o 是一款动态模型,实时更新以保持当前最新版本。它结合了强大的语言理解与生成能力,适合于大规模应用场景,包括客户服务、教育和技术支持。',
|
98
|
+
displayName: 'GPT-4o',
|
45
99
|
enabled: true,
|
46
|
-
id: 'gpt-
|
47
|
-
|
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-
|
115
|
+
deploymentName: 'gpt-4-turbo',
|
57
116
|
},
|
58
117
|
contextWindowTokens: 128_000,
|
59
|
-
description: 'GPT
|
60
|
-
displayName: 'GPT
|
61
|
-
|
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
|
},
|
@@ -5,7 +5,7 @@ const jinaChatModels: AIChatModelCard[] = [
|
|
5
5
|
abilities: {
|
6
6
|
reasoning: true,
|
7
7
|
},
|
8
|
-
contextWindowTokens:
|
8
|
+
contextWindowTokens: 1_000_000,
|
9
9
|
description: '深度搜索结合了网络搜索、阅读和推理,可进行全面调查。您可以将其视为一个代理,接受您的研究任务 - 它会进行广泛搜索并经过多次迭代,然后才能给出答案。这个过程涉及持续的研究、推理和从各个角度解决问题。这与直接从预训练数据生成答案的标准大模型以及依赖一次性表面搜索的传统 RAG 系统有着根本的不同。',
|
10
10
|
displayName: 'Jina DeepSearch v1',
|
11
11
|
enabled: true,
|
@@ -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
|
26
|
-
"enabled":
|
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":
|
43
|
-
"displayName":
|
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":
|
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
|
-
|
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) => {
|
package/src/utils/merge.test.ts
CHANGED
@@ -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
|
});
|
package/src/utils/merge.ts
CHANGED
@@ -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
|
-
|
31
|
+
userItems.forEach((userItem) => {
|
29
32
|
const defaultItem = defaultItemsMap.get(userItem.id);
|
30
|
-
if (!defaultItem)
|
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
|
-
|
51
|
+
mergedItemsMap.set(userItem.id, mergedItem);
|
44
52
|
});
|
45
53
|
|
46
|
-
//
|
47
|
-
|
48
|
-
|
54
|
+
// 添加只在默认配置中存在的项
|
55
|
+
defaultItems.forEach((item) => {
|
56
|
+
if (!mergedItemsMap.has(item.id)) {
|
57
|
+
mergedItemsMap.set(item.id, item);
|
58
|
+
}
|
59
|
+
});
|
49
60
|
|
50
|
-
return
|
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
|
});
|