@lobehub/lobehub 2.0.0-next.14 → 2.0.0-next.16
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/.github/workflows/desktop-pr-build.yml +6 -6
- package/.github/workflows/release-desktop-beta.yml +4 -4
- package/.github/workflows/release.yml +1 -2
- package/.github/workflows/test.yml +4 -5
- package/.nvmrc +1 -1
- package/CHANGELOG.md +42 -0
- package/apps/desktop/tsconfig.json +0 -1
- package/changelog/v1.json +14 -0
- package/docs/self-hosting/advanced/feature-flags.mdx +0 -1
- package/docs/self-hosting/advanced/feature-flags.zh-CN.mdx +0 -1
- package/e2e/tsconfig.json +0 -1
- package/package.json +58 -58
- package/packages/types/src/serverConfig.ts +2 -6
- package/packages/web-crawler/tsconfig.json +0 -1
- package/src/app/[variants]/(auth)/signup/[[...signup]]/page.tsx +1 -8
- package/src/app/[variants]/(main)/(mobile)/me/(home)/features/UserBanner.tsx +3 -6
- package/src/app/[variants]/(main)/labs/components/LabCard.tsx +3 -1
- package/src/app/[variants]/(main)/settings/provider/detail/azure/index.tsx +5 -7
- package/src/components/InvalidAPIKey/APIKeyForm/Bedrock.tsx +8 -13
- package/src/config/featureFlags/schema.test.ts +0 -2
- package/src/config/featureFlags/schema.ts +0 -6
- package/src/config/modelProviders/ai21.ts +1 -16
- package/src/config/modelProviders/ai302.ts +1 -128
- package/src/config/modelProviders/ai360.ts +1 -32
- package/src/config/modelProviders/anthropic.ts +1 -71
- package/src/config/modelProviders/azure.ts +1 -51
- package/src/config/modelProviders/baichuan.ts +1 -57
- package/src/config/modelProviders/bedrock.ts +1 -276
- package/src/config/modelProviders/cloudflare.ts +1 -64
- package/src/config/modelProviders/deepseek.ts +1 -19
- package/src/config/modelProviders/fireworksai.ts +1 -174
- package/src/config/modelProviders/giteeai.ts +1 -135
- package/src/config/modelProviders/github.ts +1 -254
- package/src/config/modelProviders/google.ts +1 -130
- package/src/config/modelProviders/groq.ts +1 -119
- package/src/config/modelProviders/higress.ts +1 -1713
- package/src/config/modelProviders/huggingface.ts +1 -54
- package/src/config/modelProviders/hunyuan.ts +1 -83
- package/src/config/modelProviders/infiniai.ts +1 -74
- package/src/config/modelProviders/internlm.ts +1 -20
- package/src/config/modelProviders/mistral.ts +1 -95
- package/src/config/modelProviders/modelscope.ts +1 -27
- package/src/config/modelProviders/moonshot.ts +1 -29
- package/src/config/modelProviders/novita.ts +1 -105
- package/src/config/modelProviders/ollama.ts +1 -325
- package/src/config/modelProviders/openai.ts +1 -242
- package/src/config/modelProviders/openrouter.ts +1 -240
- package/src/config/modelProviders/perplexity.ts +1 -45
- package/src/config/modelProviders/ppio.ts +1 -152
- package/src/config/modelProviders/qiniu.ts +1 -18
- package/src/config/modelProviders/qwen.ts +1 -245
- package/src/config/modelProviders/search1api.ts +1 -34
- package/src/config/modelProviders/sensenova.ts +1 -69
- package/src/config/modelProviders/siliconcloud.ts +1 -417
- package/src/config/modelProviders/spark.ts +1 -59
- package/src/config/modelProviders/stepfun.ts +1 -98
- package/src/config/modelProviders/taichu.ts +1 -18
- package/src/config/modelProviders/togetherai.ts +1 -274
- package/src/config/modelProviders/upstage.ts +1 -28
- package/src/config/modelProviders/wenxin.ts +1 -140
- package/src/config/modelProviders/xai.ts +1 -38
- package/src/config/modelProviders/zeroone.ts +1 -81
- package/src/config/modelProviders/zhipu.ts +1 -108
- package/src/helpers/isCanUseFC.ts +0 -8
- package/src/hooks/useEnabledChatModels.ts +0 -8
- package/src/hooks/useModelContextWindowTokens.ts +0 -8
- package/src/hooks/useModelHasContextWindowToken.ts +1 -10
- package/src/hooks/useModelSupportFiles.ts +1 -11
- package/src/hooks/useModelSupportReasoning.ts +1 -11
- package/src/hooks/useModelSupportToolUse.ts +1 -11
- package/src/hooks/useModelSupportVision.ts +1 -11
- package/src/layout/AuthProvider/Clerk/index.tsx +2 -16
- package/src/server/globalConfig/index.ts +0 -23
- package/src/server/routers/lambda/config/__snapshots__/index.test.ts.snap +175 -12
- package/src/server/routers/lambda/config/index.test.ts +36 -28
- package/src/services/chat/chat.test.ts +12 -0
- package/src/services/chat/helper.ts +7 -31
- package/src/services/models.ts +2 -11
- package/src/store/chat/slices/aiChat/actions/generateAIChat.ts +41 -14
- package/src/store/global/store.ts +1 -7
- package/src/store/user/initialState.ts +1 -7
- package/src/store/user/selectors.ts +1 -5
- package/src/store/user/slices/common/action.ts +5 -4
- package/src/store/user/slices/settings/selectors/index.ts +1 -0
- package/src/store/user/slices/settings/selectors/keyVaults.ts +21 -0
- package/src/store/user/store.ts +0 -3
- package/src/tools/web-browsing/Render/Search/ConfigForm/Form.tsx +1 -1
- package/tsconfig.json +0 -1
- package/packages/utils/src/_deprecated/__snapshots__/parseModels.test.ts.snap +0 -104
- package/packages/utils/src/_deprecated/parseModels.test.ts +0 -287
- package/packages/utils/src/_deprecated/parseModels.ts +0 -165
- package/src/hooks/_header.ts +0 -23
- package/src/server/globalConfig/_deprecated.test.ts +0 -92
- package/src/server/globalConfig/_deprecated.ts +0 -41
- package/src/store/global/actions/clientDb.ts +0 -67
- package/src/store/user/slices/modelList/__snapshots__/action.test.ts.snap +0 -12
- package/src/store/user/slices/modelList/action.test.ts +0 -359
- package/src/store/user/slices/modelList/action.ts +0 -223
- package/src/store/user/slices/modelList/initialState.ts +0 -15
- package/src/store/user/slices/modelList/reducers/customModelCard.test.ts +0 -204
- package/src/store/user/slices/modelList/reducers/customModelCard.ts +0 -64
- package/src/store/user/slices/modelList/selectors/index.ts +0 -3
- package/src/store/user/slices/modelList/selectors/keyVaults.test.ts +0 -201
- package/src/store/user/slices/modelList/selectors/keyVaults.ts +0 -50
- package/src/store/user/slices/modelList/selectors/modelConfig.test.ts +0 -219
- package/src/store/user/slices/modelList/selectors/modelConfig.ts +0 -95
- package/src/store/user/slices/modelList/selectors/modelProvider.test.ts +0 -138
- package/src/store/user/slices/modelList/selectors/modelProvider.ts +0 -170
|
@@ -1,219 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
|
|
3
|
-
import { UserStore } from '@/store/user';
|
|
4
|
-
import { merge } from '@/utils/merge';
|
|
5
|
-
|
|
6
|
-
import { UserState } from '../../../initialState';
|
|
7
|
-
import { UserSettingsState, initialSettingsState } from '../../settings/initialState';
|
|
8
|
-
import { modelConfigSelectors } from './modelConfig';
|
|
9
|
-
|
|
10
|
-
describe('modelConfigSelectors', () => {
|
|
11
|
-
describe('isProviderEnabled', () => {
|
|
12
|
-
it('should return true if provider is enabled', () => {
|
|
13
|
-
const s = merge(initialSettingsState, {
|
|
14
|
-
settings: {
|
|
15
|
-
languageModel: {
|
|
16
|
-
ollama: { enabled: true },
|
|
17
|
-
},
|
|
18
|
-
},
|
|
19
|
-
} as UserSettingsState) as unknown as UserStore;
|
|
20
|
-
|
|
21
|
-
expect(modelConfigSelectors.isProviderEnabled('ollama')(s)).toBe(true);
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
it('should return false if provider is not enabled', () => {
|
|
25
|
-
const s = merge(initialSettingsState, {
|
|
26
|
-
settings: {
|
|
27
|
-
languageModel: {
|
|
28
|
-
perplexity: { enabled: false },
|
|
29
|
-
},
|
|
30
|
-
},
|
|
31
|
-
} as UserSettingsState) as unknown as UserStore;
|
|
32
|
-
|
|
33
|
-
expect(modelConfigSelectors.isProviderEnabled('perplexity')(s)).toBe(false);
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
it('should follow the user settings if provider is in the whitelist', () => {
|
|
37
|
-
const s = merge(initialSettingsState, {
|
|
38
|
-
settings: {
|
|
39
|
-
languageModel: {
|
|
40
|
-
ollama: { enabled: false },
|
|
41
|
-
},
|
|
42
|
-
},
|
|
43
|
-
} as UserSettingsState) as unknown as UserStore;
|
|
44
|
-
|
|
45
|
-
expect(modelConfigSelectors.isProviderEnabled('ollama')(s)).toBe(false);
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it('ollama should be enabled by default', () => {
|
|
49
|
-
const s = merge(initialSettingsState, {
|
|
50
|
-
settings: {
|
|
51
|
-
languageModel: {},
|
|
52
|
-
},
|
|
53
|
-
} as UserSettingsState) as unknown as UserStore;
|
|
54
|
-
expect(modelConfigSelectors.isProviderEnabled('ollama')(s)).toBe(true);
|
|
55
|
-
});
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
describe('isProviderFetchOnClient', () => {
|
|
59
|
-
// The next 4 case are base on the rules on https://github.com/lobehub/lobe-chat/pull/2753
|
|
60
|
-
it('client fetch should disabled on default', () => {
|
|
61
|
-
const s = merge(initialSettingsState, {
|
|
62
|
-
settings: {
|
|
63
|
-
keyVaults: {
|
|
64
|
-
azure: {
|
|
65
|
-
endpoint: 'endpoint',
|
|
66
|
-
apiKey: 'apikey',
|
|
67
|
-
},
|
|
68
|
-
},
|
|
69
|
-
},
|
|
70
|
-
} as UserSettingsState) as unknown as UserStore;
|
|
71
|
-
expect(modelConfigSelectors.isProviderFetchOnClient('azure')(s)).toBe(false);
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
it('client fetch should disabled if no apikey or endpoint provided even user set it enabled', () => {
|
|
75
|
-
const s = merge(initialSettingsState, {
|
|
76
|
-
settings: {
|
|
77
|
-
languageModel: {
|
|
78
|
-
azure: { fetchOnClient: true },
|
|
79
|
-
},
|
|
80
|
-
},
|
|
81
|
-
} as UserSettingsState) as unknown as UserStore;
|
|
82
|
-
expect(modelConfigSelectors.isProviderFetchOnClient('azure')(s)).toBe(false);
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
it('client fetch should enable if only endpoint provided', () => {
|
|
86
|
-
const s = merge(initialSettingsState, {
|
|
87
|
-
settings: {
|
|
88
|
-
languageModel: {
|
|
89
|
-
azure: { fetchOnClient: false },
|
|
90
|
-
},
|
|
91
|
-
keyVaults: {
|
|
92
|
-
azure: { endpoint: 'https://example.com' },
|
|
93
|
-
},
|
|
94
|
-
},
|
|
95
|
-
} as UserSettingsState) as unknown as UserStore;
|
|
96
|
-
expect(modelConfigSelectors.isProviderFetchOnClient('azure')(s)).toBe(true);
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
it('client fetch should control by user when a apikey or endpoint provided', () => {
|
|
100
|
-
const s = merge(initialSettingsState, {
|
|
101
|
-
settings: {
|
|
102
|
-
languageModel: {
|
|
103
|
-
azure: { fetchOnClient: true },
|
|
104
|
-
},
|
|
105
|
-
keyVaults: {
|
|
106
|
-
azure: { apiKey: 'some-key' },
|
|
107
|
-
},
|
|
108
|
-
},
|
|
109
|
-
} as UserSettingsState) as unknown as UserStore;
|
|
110
|
-
expect(modelConfigSelectors.isProviderFetchOnClient('azure')(s)).toBe(true);
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
// Qwen provider not work in browser request. Please skip this case if it work in future.
|
|
114
|
-
// Issue: https://github.com/lobehub/lobe-chat/issues/3108
|
|
115
|
-
// PR: https://github.com/lobehub/lobe-chat/pull/3133
|
|
116
|
-
it('client fecth should be disabled if provider is disable browser request', () => {
|
|
117
|
-
const s = merge(initialSettingsState, {
|
|
118
|
-
settings: {
|
|
119
|
-
languageModel: {
|
|
120
|
-
qwen: { fetchOnClient: true },
|
|
121
|
-
},
|
|
122
|
-
keyVaults: {
|
|
123
|
-
qwen: {
|
|
124
|
-
apiKey: 'apikey',
|
|
125
|
-
},
|
|
126
|
-
},
|
|
127
|
-
},
|
|
128
|
-
} as UserSettingsState) as unknown as UserStore;
|
|
129
|
-
expect(modelConfigSelectors.isAutoFetchModelsEnabled('qwen')(s)).toBe(false);
|
|
130
|
-
});
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
describe('getCustomModelCardById', () => {
|
|
134
|
-
it('should return the custom model card with the given id and provider', () => {
|
|
135
|
-
const s = merge(initialSettingsState, {
|
|
136
|
-
settings: {
|
|
137
|
-
languageModel: {
|
|
138
|
-
perplexity: {
|
|
139
|
-
customModelCards: [
|
|
140
|
-
{ id: 'custom-model-1', displayName: 'Custom Model 1' },
|
|
141
|
-
{ id: 'custom-model-2', displayName: 'Custom Model 2' },
|
|
142
|
-
],
|
|
143
|
-
},
|
|
144
|
-
},
|
|
145
|
-
},
|
|
146
|
-
} as UserSettingsState) as unknown as UserStore;
|
|
147
|
-
|
|
148
|
-
const customModelCard = modelConfigSelectors.getCustomModelCard({
|
|
149
|
-
id: 'custom-model-2',
|
|
150
|
-
provider: 'perplexity',
|
|
151
|
-
})(s);
|
|
152
|
-
|
|
153
|
-
expect(customModelCard).toEqual({ id: 'custom-model-2', displayName: 'Custom Model 2' });
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
it('should return undefined if no custom model card is found with the given id and provider', () => {
|
|
157
|
-
const s = merge(initialSettingsState, {
|
|
158
|
-
settings: {
|
|
159
|
-
languageModel: {
|
|
160
|
-
perplexity: {
|
|
161
|
-
customModelCards: [{ id: 'custom-model-1', displayName: 'Custom Model 1' }],
|
|
162
|
-
},
|
|
163
|
-
},
|
|
164
|
-
},
|
|
165
|
-
} as UserSettingsState) as unknown as UserStore;
|
|
166
|
-
|
|
167
|
-
const customModelCard = modelConfigSelectors.getCustomModelCard({
|
|
168
|
-
id: 'nonexistent-model',
|
|
169
|
-
provider: 'perplexity',
|
|
170
|
-
})(s);
|
|
171
|
-
|
|
172
|
-
expect(customModelCard).toBeUndefined();
|
|
173
|
-
});
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
describe('currentEditingCustomModelCard', () => {
|
|
177
|
-
it('should return the custom model card that is currently being edited', () => {
|
|
178
|
-
const s = merge(initialSettingsState, {
|
|
179
|
-
settings: {
|
|
180
|
-
languageModel: {
|
|
181
|
-
perplexity: {
|
|
182
|
-
customModelCards: [
|
|
183
|
-
{ id: 'custom-model-1', displayName: 'Custom Model 1' },
|
|
184
|
-
{ id: 'custom-model-2', displayName: 'Custom Model 2' },
|
|
185
|
-
],
|
|
186
|
-
},
|
|
187
|
-
},
|
|
188
|
-
},
|
|
189
|
-
editingCustomCardModel: {
|
|
190
|
-
id: 'custom-model-2',
|
|
191
|
-
provider: 'perplexity',
|
|
192
|
-
},
|
|
193
|
-
} as UserState) as unknown as UserStore;
|
|
194
|
-
|
|
195
|
-
const currentEditingModelCard = modelConfigSelectors.currentEditingCustomModelCard(s);
|
|
196
|
-
|
|
197
|
-
expect(currentEditingModelCard).toEqual({
|
|
198
|
-
id: 'custom-model-2',
|
|
199
|
-
displayName: 'Custom Model 2',
|
|
200
|
-
});
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
it('should return undefined if no custom model card is currently being edited', () => {
|
|
204
|
-
const s = merge(initialSettingsState, {
|
|
205
|
-
settings: {
|
|
206
|
-
languageModel: {
|
|
207
|
-
perplexity: {
|
|
208
|
-
customModelCards: [{ id: 'custom-model-1', displayName: 'Custom Model 1' }],
|
|
209
|
-
},
|
|
210
|
-
},
|
|
211
|
-
},
|
|
212
|
-
} as UserSettingsState) as unknown as UserStore;
|
|
213
|
-
|
|
214
|
-
const currentEditingModelCard = modelConfigSelectors.currentEditingCustomModelCard(s);
|
|
215
|
-
|
|
216
|
-
expect(currentEditingModelCard).toBeUndefined();
|
|
217
|
-
});
|
|
218
|
-
});
|
|
219
|
-
});
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
import { isProviderDisableBrowserRequest } from '@/config/modelProviders';
|
|
2
|
-
import { isDesktop } from '@/const/version';
|
|
3
|
-
import { UserStore } from '@/store/user';
|
|
4
|
-
import { GlobalLLMProviderKey } from '@/types/user/settings';
|
|
5
|
-
|
|
6
|
-
import { currentLLMSettings, getProviderConfigById } from '../../settings/selectors/settings';
|
|
7
|
-
import { keyVaultsConfigSelectors } from './keyVaults';
|
|
8
|
-
|
|
9
|
-
const isProviderEnabled = (provider: GlobalLLMProviderKey) => (s: UserStore) =>
|
|
10
|
-
getProviderConfigById(provider)(s)?.enabled || false;
|
|
11
|
-
|
|
12
|
-
const providerWhitelist = new Set(['ollama', 'lmstudio']);
|
|
13
|
-
/**
|
|
14
|
-
* @description The conditions to enable client fetch
|
|
15
|
-
* 1. If no baseUrl and apikey input, force on Server.
|
|
16
|
-
* 2. If only contains baseUrl, force on Client
|
|
17
|
-
* 3. Follow the user settings.
|
|
18
|
-
* 4. On Server, by default.
|
|
19
|
-
*/
|
|
20
|
-
const isProviderFetchOnClient = (provider: GlobalLLMProviderKey | string) => (s: UserStore) => {
|
|
21
|
-
const config = getProviderConfigById(provider)(s);
|
|
22
|
-
|
|
23
|
-
// if is desktop, force on Server.
|
|
24
|
-
if (isDesktop) return false;
|
|
25
|
-
|
|
26
|
-
// If the provider already disable browser request in model config, force on Server.
|
|
27
|
-
if (isProviderDisableBrowserRequest(provider)) return false;
|
|
28
|
-
|
|
29
|
-
// If the provider in the whitelist, follow the user settings
|
|
30
|
-
if (providerWhitelist.has(provider) && typeof config?.fetchOnClient !== 'undefined')
|
|
31
|
-
return config?.fetchOnClient;
|
|
32
|
-
|
|
33
|
-
// 1. If no baseUrl and apikey input, force on Server.
|
|
34
|
-
const isProviderEndpointNotEmpty =
|
|
35
|
-
keyVaultsConfigSelectors.isProviderEndpointNotEmpty(provider)(s);
|
|
36
|
-
const isProviderApiKeyNotEmpty = keyVaultsConfigSelectors.isProviderApiKeyNotEmpty(provider)(s);
|
|
37
|
-
if (!isProviderEndpointNotEmpty && !isProviderApiKeyNotEmpty) return false;
|
|
38
|
-
|
|
39
|
-
// 2. If only contains baseUrl, force on Client
|
|
40
|
-
if (isProviderEndpointNotEmpty && !isProviderApiKeyNotEmpty) return true;
|
|
41
|
-
|
|
42
|
-
// 3. Follow the user settings.
|
|
43
|
-
if (typeof config?.fetchOnClient !== 'undefined') return config?.fetchOnClient;
|
|
44
|
-
|
|
45
|
-
// 4. On Server, by default.
|
|
46
|
-
return false;
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
const getCustomModelCard =
|
|
50
|
-
({ id, provider }: { id?: string; provider?: string }) =>
|
|
51
|
-
(s: UserStore) => {
|
|
52
|
-
if (!provider) return;
|
|
53
|
-
|
|
54
|
-
const config = getProviderConfigById(provider)(s);
|
|
55
|
-
|
|
56
|
-
return config?.customModelCards?.find((m) => m.id === id);
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
const currentEditingCustomModelCard = (s: UserStore) => {
|
|
60
|
-
if (!s.editingCustomCardModel) return;
|
|
61
|
-
const { id, provider } = s.editingCustomCardModel;
|
|
62
|
-
|
|
63
|
-
return getCustomModelCard({ id, provider })(s);
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
const isAutoFetchModelsEnabled =
|
|
67
|
-
(provider: GlobalLLMProviderKey) =>
|
|
68
|
-
(s: UserStore): boolean => {
|
|
69
|
-
return getProviderConfigById(provider)(s)?.autoFetchModelLists || false;
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
const openAIConfig = (s: UserStore) => currentLLMSettings(s).openai;
|
|
73
|
-
const bedrockConfig = (s: UserStore) => currentLLMSettings(s).bedrock;
|
|
74
|
-
const ollamaConfig = (s: UserStore) => currentLLMSettings(s).ollama;
|
|
75
|
-
const azureConfig = (s: UserStore) => currentLLMSettings(s).azure;
|
|
76
|
-
const cloudflareConfig = (s: UserStore) => currentLLMSettings(s).cloudflare;
|
|
77
|
-
|
|
78
|
-
const isAzureEnabled = (s: UserStore) => currentLLMSettings(s).azure.enabled;
|
|
79
|
-
|
|
80
|
-
export const modelConfigSelectors = {
|
|
81
|
-
azureConfig,
|
|
82
|
-
bedrockConfig,
|
|
83
|
-
cloudflareConfig,
|
|
84
|
-
|
|
85
|
-
currentEditingCustomModelCard,
|
|
86
|
-
getCustomModelCard,
|
|
87
|
-
|
|
88
|
-
isAutoFetchModelsEnabled,
|
|
89
|
-
isAzureEnabled,
|
|
90
|
-
isProviderEnabled,
|
|
91
|
-
isProviderFetchOnClient,
|
|
92
|
-
|
|
93
|
-
ollamaConfig,
|
|
94
|
-
openAIConfig,
|
|
95
|
-
};
|
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
|
|
3
|
-
import { merge } from '@/utils/merge';
|
|
4
|
-
|
|
5
|
-
import { UserState, initialState } from '../../../initialState';
|
|
6
|
-
import { UserStore, useUserStore } from '../../../store';
|
|
7
|
-
import { getDefaultModeProviderById, modelProviderSelectors } from './modelProvider';
|
|
8
|
-
|
|
9
|
-
describe('modelProviderSelectors', () => {
|
|
10
|
-
describe('getDefaultModeProviderById', () => {
|
|
11
|
-
it('should return the correct ModelProviderCard when provider ID matches', () => {
|
|
12
|
-
const s = merge(initialState, {}) as unknown as UserStore;
|
|
13
|
-
|
|
14
|
-
const result = getDefaultModeProviderById('openai')(s);
|
|
15
|
-
expect(result).not.toBeUndefined();
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
it('should return undefined when provider ID does not exist', () => {
|
|
19
|
-
const s = merge(initialState, {}) as unknown as UserStore;
|
|
20
|
-
const result = getDefaultModeProviderById('nonExistingProvider')(s);
|
|
21
|
-
expect(result).toBeUndefined();
|
|
22
|
-
});
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
describe('getModelCardsById', () => {
|
|
26
|
-
it('should return model cards including custom model cards', () => {
|
|
27
|
-
const s = merge(initialState, {
|
|
28
|
-
settings: {
|
|
29
|
-
languageModel: {
|
|
30
|
-
perplexity: {
|
|
31
|
-
customModelCards: [{ id: 'custom-model', displayName: 'Custom Model' }],
|
|
32
|
-
},
|
|
33
|
-
},
|
|
34
|
-
},
|
|
35
|
-
} as UserState) as unknown as UserStore;
|
|
36
|
-
|
|
37
|
-
const modelCards = modelProviderSelectors.getModelCardsById('perplexity')(s);
|
|
38
|
-
|
|
39
|
-
expect(modelCards).toContainEqual({
|
|
40
|
-
id: 'custom-model',
|
|
41
|
-
displayName: 'Custom Model',
|
|
42
|
-
isCustom: true,
|
|
43
|
-
});
|
|
44
|
-
});
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
describe('defaultEnabledProviderModels', () => {
|
|
48
|
-
it('should return undefined for a non-existing provider', () => {
|
|
49
|
-
const s = merge(initialState, {}) as unknown as UserStore;
|
|
50
|
-
|
|
51
|
-
const result = modelProviderSelectors.getDefaultEnabledModelsById('nonExistingProvider')(s);
|
|
52
|
-
expect(result).toBeUndefined();
|
|
53
|
-
});
|
|
54
|
-
});
|
|
55
|
-
describe('modelEnabledVision', () => {
|
|
56
|
-
it('should return true if the model has vision ability', () => {
|
|
57
|
-
const hasAbility = modelProviderSelectors.isModelEnabledVision('gpt-4-vision-preview')(
|
|
58
|
-
useUserStore.getState(),
|
|
59
|
-
);
|
|
60
|
-
expect(hasAbility).toBeTruthy();
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it('should return false if the model does not have vision ability', () => {
|
|
64
|
-
const hasAbility = modelProviderSelectors.isModelEnabledVision('some-other-model')(
|
|
65
|
-
useUserStore.getState(),
|
|
66
|
-
);
|
|
67
|
-
|
|
68
|
-
expect(hasAbility).toBeFalsy();
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
it('should return false if the model include vision in id', () => {
|
|
72
|
-
const hasAbility = modelProviderSelectors.isModelEnabledVision('some-other-model-vision')(
|
|
73
|
-
useUserStore.getState(),
|
|
74
|
-
);
|
|
75
|
-
|
|
76
|
-
expect(hasAbility).toBeTruthy();
|
|
77
|
-
});
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
describe('modelEnabledFiles', () => {
|
|
81
|
-
it('should return false if the model does not have file ability', () => {
|
|
82
|
-
const enabledFiles = modelProviderSelectors.isModelEnabledFiles('gpt-4-vision-preview')(
|
|
83
|
-
useUserStore.getState(),
|
|
84
|
-
);
|
|
85
|
-
expect(enabledFiles).toBeFalsy();
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
it.skip('should return true if the model has file ability', () => {
|
|
89
|
-
const enabledFiles = modelProviderSelectors.isModelEnabledFiles('gpt-4-all')(
|
|
90
|
-
useUserStore.getState(),
|
|
91
|
-
);
|
|
92
|
-
expect(enabledFiles).toBeTruthy();
|
|
93
|
-
});
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
describe('modelHasMaxToken', () => {
|
|
97
|
-
it('should return true if the model is in the list of models that show tokens', () => {
|
|
98
|
-
const show = modelProviderSelectors.isModelHasMaxToken('gpt-3.5-turbo')(
|
|
99
|
-
useUserStore.getState(),
|
|
100
|
-
);
|
|
101
|
-
expect(show).toBeTruthy();
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
it('should return false if the model is not in the list of models that show tokens', () => {
|
|
105
|
-
const show = modelProviderSelectors.isModelHasMaxToken('some-other-model')(
|
|
106
|
-
useUserStore.getState(),
|
|
107
|
-
);
|
|
108
|
-
expect(show).toBe(false);
|
|
109
|
-
});
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
describe('modelMaxToken', () => {
|
|
113
|
-
it('should return the correct token count for a model with specified tokens', () => {
|
|
114
|
-
const model1Tokens = modelProviderSelectors.modelMaxToken('gpt-3.5-turbo')(
|
|
115
|
-
useUserStore.getState(),
|
|
116
|
-
);
|
|
117
|
-
|
|
118
|
-
expect(model1Tokens).toEqual(16385);
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
it('should return 0 for a model without a specified token count', () => {
|
|
122
|
-
// 测试未指定tokens属性的模型的tokens值,期望为0
|
|
123
|
-
const tokens = modelProviderSelectors.modelMaxToken('chat-bison-001')(
|
|
124
|
-
useUserStore.getState(),
|
|
125
|
-
);
|
|
126
|
-
expect(tokens).toEqual(0);
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
it('should return 0 for a non-existing model', () => {
|
|
130
|
-
// 测试一个不存在的模型的tokens值,期望为0
|
|
131
|
-
const tokens = modelProviderSelectors.modelMaxToken('nonExistingModel')(
|
|
132
|
-
useUserStore.getState(),
|
|
133
|
-
);
|
|
134
|
-
|
|
135
|
-
expect(tokens).toEqual(0);
|
|
136
|
-
});
|
|
137
|
-
});
|
|
138
|
-
});
|
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
ChatModelCard,
|
|
3
|
-
EnabledProviderWithModels,
|
|
4
|
-
GlobalLLMProviderKey,
|
|
5
|
-
ModelProviderCard,
|
|
6
|
-
} from '@lobechat/types';
|
|
7
|
-
import { ServerModelProviderConfig } from '@lobechat/types';
|
|
8
|
-
import { uniqBy } from 'lodash-es';
|
|
9
|
-
|
|
10
|
-
import { filterEnabledModels } from '@/config/modelProviders';
|
|
11
|
-
import type { UserStore } from '@/store/user';
|
|
12
|
-
|
|
13
|
-
import { currentSettings, getProviderConfigById } from '../../settings/selectors/settings';
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* get the server side model cards
|
|
17
|
-
*/
|
|
18
|
-
const serverProviderModelCards =
|
|
19
|
-
(provider: GlobalLLMProviderKey) =>
|
|
20
|
-
(s: UserStore): ChatModelCard[] | undefined => {
|
|
21
|
-
const config = s.serverLanguageModel?.[provider] as ServerModelProviderConfig | undefined;
|
|
22
|
-
|
|
23
|
-
if (!config) return;
|
|
24
|
-
|
|
25
|
-
return config.serverModelCards;
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
const remoteProviderModelCards =
|
|
29
|
-
(provider: GlobalLLMProviderKey) =>
|
|
30
|
-
(s: UserStore): ChatModelCard[] | undefined => {
|
|
31
|
-
const cards = currentSettings(s).languageModel?.[provider]?.remoteModelCards as
|
|
32
|
-
| ChatModelCard[]
|
|
33
|
-
| undefined;
|
|
34
|
-
|
|
35
|
-
if (!cards) return;
|
|
36
|
-
|
|
37
|
-
return cards;
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
const isProviderEnabled = (provider: GlobalLLMProviderKey) => (s: UserStore) =>
|
|
41
|
-
getProviderConfigById(provider)(s)?.enabled || false;
|
|
42
|
-
|
|
43
|
-
// Default Model Provider List
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* define all the model list of providers
|
|
47
|
-
*/
|
|
48
|
-
const defaultModelProviderList = (s: UserStore): ModelProviderCard[] => s.defaultModelProviderList;
|
|
49
|
-
|
|
50
|
-
export const getDefaultModeProviderById = (provider: string) => (s: UserStore) =>
|
|
51
|
-
defaultModelProviderList(s).find((s) => s.id === provider);
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* get the default enabled models for a provider
|
|
55
|
-
* it's a default enabled model list by Lobe Chat
|
|
56
|
-
* e.g. openai is ['gpt-4o-mini','gpt-4o','gpt-4-turbo']
|
|
57
|
-
*/
|
|
58
|
-
const getDefaultEnabledModelsById = (provider: string) => (s: UserStore) => {
|
|
59
|
-
const modelProvider = getDefaultModeProviderById(provider)(s);
|
|
60
|
-
|
|
61
|
-
if (modelProvider) return filterEnabledModels(modelProvider);
|
|
62
|
-
|
|
63
|
-
return undefined;
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
const getDefaultModelCardById = (id: string) => (s: UserStore) => {
|
|
67
|
-
const list = defaultModelProviderList(s);
|
|
68
|
-
|
|
69
|
-
return list.flatMap((i) => i.chatModels).find((m) => m.id === id);
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
// Model Provider List
|
|
73
|
-
|
|
74
|
-
const getModelCardsById =
|
|
75
|
-
(provider: string) =>
|
|
76
|
-
(s: UserStore): ChatModelCard[] => {
|
|
77
|
-
const builtinCards = getDefaultModeProviderById(provider)(s)?.chatModels || [];
|
|
78
|
-
|
|
79
|
-
const userCards = (getProviderConfigById(provider)(s)?.customModelCards || []).map((model) => ({
|
|
80
|
-
...model,
|
|
81
|
-
isCustom: true,
|
|
82
|
-
}));
|
|
83
|
-
|
|
84
|
-
return uniqBy([...userCards, ...builtinCards], 'id');
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
const getEnableModelsById = (provider: string) => (s: UserStore) => {
|
|
88
|
-
if (!getProviderConfigById(provider)(s)?.enabledModels) return;
|
|
89
|
-
|
|
90
|
-
return getProviderConfigById(provider)(s)?.enabledModels?.filter(Boolean);
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
const modelProviderList = (s: UserStore): ModelProviderCard[] => s.modelProviderList;
|
|
94
|
-
|
|
95
|
-
const modelProviderListForModelSelect = (s: UserStore): EnabledProviderWithModels[] =>
|
|
96
|
-
modelProviderList(s)
|
|
97
|
-
.filter((s) => s.enabled)
|
|
98
|
-
.map((provider) => ({
|
|
99
|
-
...provider,
|
|
100
|
-
children: provider.chatModels
|
|
101
|
-
.filter((model) => model.enabled)
|
|
102
|
-
.map((m) => ({
|
|
103
|
-
abilities: {
|
|
104
|
-
functionCall: m.functionCall,
|
|
105
|
-
vision: m.vision,
|
|
106
|
-
},
|
|
107
|
-
contextWindowTokens: m.contextWindowTokens,
|
|
108
|
-
displayName: m.displayName,
|
|
109
|
-
id: m.id,
|
|
110
|
-
})),
|
|
111
|
-
source: 'builtin',
|
|
112
|
-
}));
|
|
113
|
-
|
|
114
|
-
const getModelCardById = (id: string, provider?: GlobalLLMProviderKey) => (s: UserStore) => {
|
|
115
|
-
const list = modelProviderList(s);
|
|
116
|
-
|
|
117
|
-
return list
|
|
118
|
-
.filter((i) => !provider || i.id === provider)
|
|
119
|
-
.flatMap((i) => i.chatModels)
|
|
120
|
-
.find((m) => m.id === id);
|
|
121
|
-
};
|
|
122
|
-
|
|
123
|
-
const isModelEnabledFunctionCall = (id: string) => (s: UserStore) =>
|
|
124
|
-
getModelCardById(id)(s)?.functionCall || false;
|
|
125
|
-
|
|
126
|
-
// vision model white list, these models will change the content from string to array
|
|
127
|
-
// refs: https://github.com/lobehub/lobe-chat/issues/790
|
|
128
|
-
const isModelEnabledVision = (id: string) => (s: UserStore) =>
|
|
129
|
-
getModelCardById(id)(s)?.vision || id.includes('vision');
|
|
130
|
-
|
|
131
|
-
const isModelEnabledReasoning = (id: string) => (s: UserStore) =>
|
|
132
|
-
getModelCardById(id)(s)?.reasoning || false;
|
|
133
|
-
|
|
134
|
-
const isModelEnabledFiles = (id: string) => (s: UserStore) => getModelCardById(id)(s)?.files;
|
|
135
|
-
|
|
136
|
-
const isModelEnabledUpload = (id: string) => (s: UserStore) =>
|
|
137
|
-
isModelEnabledVision(id)(s) || isModelEnabledFiles(id)(s);
|
|
138
|
-
|
|
139
|
-
const isModelHasMaxToken = (id: string) => (s: UserStore) =>
|
|
140
|
-
typeof getModelCardById(id)(s)?.contextWindowTokens !== 'undefined';
|
|
141
|
-
|
|
142
|
-
const modelMaxToken = (id: string) => (s: UserStore) =>
|
|
143
|
-
getModelCardById(id)(s)?.contextWindowTokens || 0;
|
|
144
|
-
|
|
145
|
-
export const modelProviderSelectors = {
|
|
146
|
-
defaultModelProviderList,
|
|
147
|
-
getDefaultEnabledModelsById,
|
|
148
|
-
getDefaultModelCardById,
|
|
149
|
-
|
|
150
|
-
getEnableModelsById,
|
|
151
|
-
getModelCardById,
|
|
152
|
-
|
|
153
|
-
getModelCardsById,
|
|
154
|
-
isModelEnabledFiles,
|
|
155
|
-
isModelEnabledFunctionCall,
|
|
156
|
-
isModelEnabledReasoning,
|
|
157
|
-
isModelEnabledUpload,
|
|
158
|
-
isModelEnabledVision,
|
|
159
|
-
isModelHasMaxToken,
|
|
160
|
-
|
|
161
|
-
isProviderEnabled,
|
|
162
|
-
|
|
163
|
-
modelMaxToken,
|
|
164
|
-
modelProviderList,
|
|
165
|
-
|
|
166
|
-
modelProviderListForModelSelect,
|
|
167
|
-
|
|
168
|
-
remoteProviderModelCards,
|
|
169
|
-
serverProviderModelCards,
|
|
170
|
-
};
|