@lobehub/chat 1.70.2 → 1.70.4
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/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/changelog/v1.json +14 -0
- package/docs/self-hosting/advanced/model-list.mdx +3 -3
- package/docs/self-hosting/advanced/model-list.zh-CN.mdx +3 -3
- package/docs/self-hosting/environment-variables/model-provider.mdx +17 -3
- package/docs/self-hosting/environment-variables/model-provider.zh-CN.mdx +16 -2
- package/locales/fa-IR/setting.json +1 -1
- package/locales/zh-CN/setting.json +1 -1
- package/locales/zh-TW/setting.json +1 -1
- package/package.json +1 -1
- package/packages/web-crawler/src/crawImpl/naive.ts +11 -3
- package/packages/web-crawler/src/urlRules.ts +6 -0
- package/src/components/Branding/ProductLogo/Custom.tsx +4 -0
- package/src/config/modelProviders/index.ts +1 -1
- package/src/config/modelProviders/openrouter.ts +7 -0
- package/src/database/schemas/user.ts +3 -3
- package/src/database/server/models/__tests__/plugin.test.ts +8 -8
- package/src/database/server/models/plugin.ts +20 -20
- package/src/libs/agent-runtime/openrouter/index.test.ts +10 -1
- package/src/locales/default/setting.ts +1 -1
- package/src/services/plugin/client.test.ts +21 -21
- package/src/store/aiInfra/slices/aiProvider/__tests__/selectors.test.ts +249 -0
- package/src/store/aiInfra/slices/aiProvider/selectors.ts +3 -3
- package/src/store/user/slices/modelList/selectors/modelConfig.test.ts +2 -2
- package/src/store/user/slices/modelList/selectors/modelConfig.ts +3 -3
package/CHANGELOG.md
CHANGED
@@ -2,6 +2,56 @@
|
|
2
2
|
|
3
3
|
# Changelog
|
4
4
|
|
5
|
+
### [Version 1.70.4](https://github.com/lobehub/lobe-chat/compare/v1.70.3...v1.70.4)
|
6
|
+
|
7
|
+
<sup>Released on **2025-03-11**</sup>
|
8
|
+
|
9
|
+
#### 💄 Styles
|
10
|
+
|
11
|
+
- **misc**: Support OpenRouter custom BaseURL.
|
12
|
+
|
13
|
+
<br/>
|
14
|
+
|
15
|
+
<details>
|
16
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
17
|
+
|
18
|
+
#### Styles
|
19
|
+
|
20
|
+
- **misc**: Support OpenRouter custom BaseURL ([a8089ed](https://github.com/lobehub/lobe-chat/commit/a8089ed))
|
21
|
+
|
22
|
+
</details>
|
23
|
+
|
24
|
+
<div align="right">
|
25
|
+
|
26
|
+
[](#readme-top)
|
27
|
+
|
28
|
+
</div>
|
29
|
+
|
30
|
+
### [Version 1.70.3](https://github.com/lobehub/lobe-chat/compare/v1.70.2...v1.70.3)
|
31
|
+
|
32
|
+
<sup>Released on **2025-03-11**</sup>
|
33
|
+
|
34
|
+
#### 💄 Styles
|
35
|
+
|
36
|
+
- **spelling**: Correct "broswer" to "browser" across codebase.
|
37
|
+
|
38
|
+
<br/>
|
39
|
+
|
40
|
+
<details>
|
41
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
42
|
+
|
43
|
+
#### Styles
|
44
|
+
|
45
|
+
- **spelling**: Correct "broswer" to "browser" across codebase, closes [#6876](https://github.com/lobehub/lobe-chat/issues/6876) ([8d677a2](https://github.com/lobehub/lobe-chat/commit/8d677a2))
|
46
|
+
|
47
|
+
</details>
|
48
|
+
|
49
|
+
<div align="right">
|
50
|
+
|
51
|
+
[](#readme-top)
|
52
|
+
|
53
|
+
</div>
|
54
|
+
|
5
55
|
### [Version 1.70.2](https://github.com/lobehub/lobe-chat/compare/v1.70.1...v1.70.2)
|
6
56
|
|
7
57
|
<sup>Released on **2025-03-10**</sup>
|
package/README.md
CHANGED
@@ -330,7 +330,7 @@ In addition, these plugins are not limited to news aggregation, but can also ext
|
|
330
330
|
| [Bing_websearch](https://lobechat.com/discover/plugin/Bingsearch-identifier)<br/><sup>By **FineHow** on **2024-12-22**</sup> | Search for information from the internet base BingApi<br/>`bingsearch` |
|
331
331
|
| [PortfolioMeta](https://lobechat.com/discover/plugin/StockData)<br/><sup>By **portfoliometa** on **2024-12-22**</sup> | Analyze stocks and get comprehensive real-time investment data and analytics.<br/>`stock` |
|
332
332
|
|
333
|
-
> 📊 Total plugins: [<kbd>**
|
333
|
+
> 📊 Total plugins: [<kbd>**46**</kbd>](https://lobechat.com/discover/plugins)
|
334
334
|
|
335
335
|
<!-- PLUGIN LIST -->
|
336
336
|
|
package/README.zh-CN.md
CHANGED
@@ -323,7 +323,7 @@ LobeChat 的插件生态系统是其核心功能的重要扩展,它极大地
|
|
323
323
|
| [必应网页搜索](https://lobechat.com/discover/plugin/Bingsearch-identifier)<br/><sup>By **FineHow** on **2024-12-22**</sup> | 通过 BingApi 搜索互联网上的信息<br/>`bingsearch` |
|
324
324
|
| [PortfolioMeta](https://lobechat.com/discover/plugin/StockData)<br/><sup>By **portfoliometa** on **2024-12-22**</sup> | 分析股票并获取全面的实时投资数据和分析。<br/>`股票` |
|
325
325
|
|
326
|
-
> 📊 Total plugins: [<kbd>**
|
326
|
+
> 📊 Total plugins: [<kbd>**46**</kbd>](https://lobechat.com/discover/plugins)
|
327
327
|
|
328
328
|
<!-- PLUGIN LIST -->
|
329
329
|
|
package/changelog/v1.json
CHANGED
@@ -1,4 +1,18 @@
|
|
1
1
|
[
|
2
|
+
{
|
3
|
+
"children": {
|
4
|
+
"improvements": [
|
5
|
+
"Support OpenRouter custom BaseURL."
|
6
|
+
]
|
7
|
+
},
|
8
|
+
"date": "2025-03-11",
|
9
|
+
"version": "1.70.4"
|
10
|
+
},
|
11
|
+
{
|
12
|
+
"children": {},
|
13
|
+
"date": "2025-03-11",
|
14
|
+
"version": "1.70.3"
|
15
|
+
},
|
2
16
|
{
|
3
17
|
"children": {
|
4
18
|
"fixes": [
|
@@ -14,10 +14,10 @@ tags:
|
|
14
14
|
|
15
15
|
LobeChat supports customizing the model list during deployment. This configuration is done in the environment for each [model provider](/docs/self-hosting/environment-variables/model-provider).
|
16
16
|
|
17
|
-
You can use `+` to add a model, `-` to hide a model, and use `model name=display name<extension configuration>` to customize the display name of a model, separated by English commas. The basic syntax is as follows:
|
17
|
+
You can use `+` to add a model, `-` to hide a model, and use `model name->deploymentName=display name<extension configuration>` to customize the display name of a model, separated by English commas. The basic syntax is as follows:
|
18
18
|
|
19
19
|
```text
|
20
|
-
id=displayName<maxToken:vision:reasoning:search:fc:file>,model2,model3
|
20
|
+
id->deploymentName=displayName<maxToken:vision:reasoning:search:fc:file>,model2,model3
|
21
21
|
```
|
22
22
|
|
23
23
|
For example: `+qwen-7b-chat,+glm-6b,-gpt-3.5-turbo,gpt-4-0125-preview=gpt-4-turbo`
|
@@ -29,7 +29,7 @@ In the above example, it adds `qwen-7b-chat` and `glm-6b` to the model list, rem
|
|
29
29
|
Considering the diversity of model capabilities, we started to add extension configuration in version `0.147.8`, with the following rules:
|
30
30
|
|
31
31
|
```shell
|
32
|
-
id=displayName<maxToken:vision:reasoning:search:fc:file>
|
32
|
+
id->deploymentName=displayName<maxToken:vision:reasoning:search:fc:file>
|
33
33
|
```
|
34
34
|
|
35
35
|
The first value in angle brackets is designated as the `maxToken` for this model. The second value and beyond are the model's extension capabilities, separated by colons `:`, and the order is not important.
|
@@ -13,10 +13,10 @@ tags:
|
|
13
13
|
|
14
14
|
LobeChat 支持在部署时自定义模型列表,详情请参考 [模型提供商](/zh/docs/self-hosting/environment-variables/model-provider) 。
|
15
15
|
|
16
|
-
你可以使用 `+` 增加一个模型,使用 `-` 来隐藏一个模型,使用
|
16
|
+
你可以使用 `+` 增加一个模型,使用 `-` 来隐藏一个模型,使用 `模型名->部署名=展示名<扩展配置>` 来自定义模型的展示名,用英文逗号隔开。通过 `<>` 来添加扩展配置。基本语法如下:
|
17
17
|
|
18
18
|
```text
|
19
|
-
id=displayName<maxToken:vision:reasoning:search:fc:file>,model2,model3
|
19
|
+
id->deploymentName=displayName<maxToken:vision:reasoning:search:fc:file>,model2,model3
|
20
20
|
```
|
21
21
|
|
22
22
|
例如: `+qwen-7b-chat,+glm-6b,-gpt-3.5-turbo,gpt-4-0125-preview=gpt-4-turbo`
|
@@ -28,7 +28,7 @@ id=displayName<maxToken:vision:reasoning:search:fc:file>,model2,model3
|
|
28
28
|
考虑到模型的能力多样性,我们在 `0.147.8` 版本开始增加扩展性配置,它的规则如下:
|
29
29
|
|
30
30
|
```shell
|
31
|
-
id=displayName<maxToken:vision:reasoning:search:fc:file>
|
31
|
+
id->deploymentName=displayName<maxToken:vision:reasoning:search:fc:file>
|
32
32
|
```
|
33
33
|
|
34
34
|
尖括号第一个值约定为这个模型的 `maxToken` 。第二个及以后作为模型的扩展能力,能力与能力之间用冒号 `:` 作为分隔符,顺序不重要。
|
@@ -94,7 +94,7 @@ If you need to use Azure OpenAI to provide model services, you can refer to the
|
|
94
94
|
### `AZURE_MODEL_LIST`
|
95
95
|
|
96
96
|
- Type: Optional
|
97
|
-
- Description: Used to control the model list, use `+` to add a model, use `-` to hide a model, use `id->
|
97
|
+
- Description: Used to control the model list, use `+` to add a model, use `-` to hide a model, use `id->deploymentName=displayName` to customize the display name of a model, separated by commas. Definition syntax rules see [model-list][model-list]
|
98
98
|
- Default: `-`
|
99
99
|
- Example: `gpt-35-turbo->my-deploy=GPT 3.5 Turbo` 或 `gpt-4-turbo->my-gpt4=GPT 4 Turbo<128000:vision:fc>`
|
100
100
|
|
@@ -183,6 +183,13 @@ If you need to use Azure OpenAI to provide model services, you can refer to the
|
|
183
183
|
- Default: -
|
184
184
|
- Example: `sk-xxxxxx...xxxxxx`
|
185
185
|
|
186
|
+
### `DEEPSEEK_MODEL_LIST`
|
187
|
+
|
188
|
+
- Type: Optional
|
189
|
+
- Description: Used to control the model list, use `+` to add a model, use `-` to hide a model, use `model_name=displayName` to customize the display name of a model, separated by commas. Definition syntax rules see [model-list][model-list]
|
190
|
+
- Default: `-`
|
191
|
+
- Example: `-all,+deepseek-reasoner`
|
192
|
+
|
186
193
|
## XAI
|
187
194
|
|
188
195
|
### `XAI_API_KEY`
|
@@ -425,6 +432,13 @@ If you need to use Azure OpenAI to provide model services, you can refer to the
|
|
425
432
|
- Default: `-`
|
426
433
|
- Example: `-all,+qwen-turbo-latest,+qwen-plus-latest`
|
427
434
|
|
435
|
+
### `QWEN_PROXY_URL`
|
436
|
+
|
437
|
+
- Type: Optional
|
438
|
+
- Description: If you manually configure the Qwen API proxy, you can use this configuration item to override the default Qwen API request base URL
|
439
|
+
- Default: `https://dashscope.aliyuncs.com/compatible-mode/v1`
|
440
|
+
- Example: `https://my-qwen-proxy.com/v1`
|
441
|
+
|
428
442
|
## Stepfun AI
|
429
443
|
|
430
444
|
### `STEPFUN_API_KEY`
|
@@ -555,9 +569,9 @@ If you need to use Azure OpenAI to provide model services, you can refer to the
|
|
555
569
|
### `VOLCENGINE_MODEL_LIST`
|
556
570
|
|
557
571
|
- Type: Optional
|
558
|
-
- Description: Used to control the model list, use `+` to add a model, use `-` to hide a model, use `model_name=display_name` to customize the display name of a model, separated by commas. Definition syntax rules see [model-list][model-list]
|
572
|
+
- Description: Used to control the model list, use `+` to add a model, use `-` to hide a model, use `model_name->deploymentName=display_name` to customize the display name of a model, separated by commas. Definition syntax rules see [model-list][model-list]
|
559
573
|
- Default: `-`
|
560
|
-
- Example: `-all,+deepseek-r1-250120,+deepseek-v3-241226,+doubao-1-5-pro-256k-250115,+doubao-1-5-pro-32k-250115,+doubao-1-5-lite-32k-250115`
|
574
|
+
- Example: `-all,+deepseek-r1->deepseek-r1-250120,+deepseek-v3->deepseek-v3-241226,+doubao-1.5-pro-256k->doubao-1-5-pro-256k-250115,+doubao-1.5-pro-32k->doubao-1-5-pro-32k-250115,+doubao-1.5-lite-32k->doubao-1-5-lite-32k-250115`
|
561
575
|
|
562
576
|
|
563
577
|
[model-list]: /docs/self-hosting/advanced/model-list
|
@@ -181,6 +181,13 @@ LobeChat 在部署时提供了丰富的模型服务商相关的环境变量,
|
|
181
181
|
- 默认值:-
|
182
182
|
- 示例:`sk-xxxxxx...xxxxxx`
|
183
183
|
|
184
|
+
### `DEEPSEEK_MODEL_LIST`
|
185
|
+
|
186
|
+
- 类型:可选
|
187
|
+
- 描述:用来控制模型列表,使用 `+` 增加一个模型,使用 `-` 来隐藏一个模型,使用 `模型名=展示名<扩展配置>` 来自定义模型的展示名,用英文逗号隔开。模型定义语法规则见 [模型列表][model-list]
|
188
|
+
- 默认值:`-`
|
189
|
+
- 示例:`-all,+deepseek-reasoner`
|
190
|
+
|
184
191
|
## XAI
|
185
192
|
|
186
193
|
### `XAI_API_KEY`
|
@@ -423,6 +430,13 @@ LobeChat 在部署时提供了丰富的模型服务商相关的环境变量,
|
|
423
430
|
- 默认值:`-`
|
424
431
|
- 示例:`-all,+qwen-turbo-latest,+qwen-plus-latest`
|
425
432
|
|
433
|
+
### `QWEN_PROXY_URL`
|
434
|
+
|
435
|
+
- 类型:可选
|
436
|
+
- 描述:如果你手动配置了 Qwen 接口代理,可以使用此配置项来覆盖默认的 Qwen API 请求基础 URL
|
437
|
+
- 默认值:`https://dashscope.aliyuncs.com/compatible-mode/v1`
|
438
|
+
- 示例:`https://my-qwen-proxy.com/v1`
|
439
|
+
|
426
440
|
## Stepfun AI
|
427
441
|
|
428
442
|
### `STEPFUN_API_KEY`
|
@@ -553,8 +567,8 @@ LobeChat 在部署时提供了丰富的模型服务商相关的环境变量,
|
|
553
567
|
### `VOLCENGINE_MODEL_LIST`
|
554
568
|
|
555
569
|
- 类型:可选
|
556
|
-
- 描述:用来控制模型列表,使用 `+` 增加一个模型,使用 `-` 来隐藏一个模型,使用
|
570
|
+
- 描述:用来控制模型列表,使用 `+` 增加一个模型,使用 `-` 来隐藏一个模型,使用 `模型名->部署名=展示名<扩展配置>` 来自定义模型的展示名,用英文逗号隔开。模型定义语法规则见 [模型列表][model-list]
|
557
571
|
- 默认值:`-`
|
558
|
-
- 示例:`-all,+deepseek-r1-250120,+deepseek-v3-241226,+doubao-1-5-pro-256k-250115,+doubao-1-5-pro-32k-250115,+doubao-1-5-lite-32k-250115`
|
572
|
+
- 示例:`-all,+deepseek-r1->deepseek-r1-250120,+deepseek-v3->deepseek-v3-241226,+doubao-1.5-pro-256k->doubao-1-5-pro-256k-250115,+doubao-1.5-pro-32k->doubao-1-5-pro-32k-250115,+doubao-1.5-lite-32k->doubao-1-5-lite-32k-250115`
|
559
573
|
|
560
574
|
[model-list]: /zh/docs/self-hosting/advanced/model-list
|
@@ -289,7 +289,7 @@
|
|
289
289
|
"title": "زبان تشخیص گفتار"
|
290
290
|
},
|
291
291
|
"sttService": {
|
292
|
-
"desc": "در این میان،
|
292
|
+
"desc": "در این میان، browser به سرویس تشخیص گفتار بومی مرورگر اشاره دارد",
|
293
293
|
"title": "سرویس تشخیص گفتار"
|
294
294
|
},
|
295
295
|
"title": "سرویسهای گفتاری",
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@lobehub/chat",
|
3
|
-
"version": "1.70.
|
3
|
+
"version": "1.70.4",
|
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",
|
@@ -75,11 +75,19 @@ export const naive: CrawlImpl = async (url, { filterOptions }) => {
|
|
75
75
|
const type = res.headers.get('content-type');
|
76
76
|
|
77
77
|
if (type?.includes('application/json')) {
|
78
|
-
|
78
|
+
let content: string;
|
79
|
+
|
80
|
+
try {
|
81
|
+
const json = await res.clone().json();
|
82
|
+
content = JSON.stringify(json, null, 2);
|
83
|
+
} catch {
|
84
|
+
content = await res.text();
|
85
|
+
}
|
86
|
+
|
79
87
|
return {
|
80
|
-
content:
|
88
|
+
content: content,
|
81
89
|
contentType: 'json',
|
82
|
-
length:
|
90
|
+
length: content.length,
|
83
91
|
url,
|
84
92
|
} satisfies CrawlSuccessResult;
|
85
93
|
}
|
@@ -72,4 +72,10 @@ export const crawUrlRules: CrawlUrlRule[] = [
|
|
72
72
|
impls: ['jina'],
|
73
73
|
urlPattern: 'https://cvpr.thecvf.com(.*)',
|
74
74
|
},
|
75
|
+
// 飞书用 jina
|
76
|
+
// https://github.com/lobehub/lobe-chat/issues/6879
|
77
|
+
{
|
78
|
+
impls: ['jina'],
|
79
|
+
urlPattern: 'https://(.*).feishu.cn/(.*)',
|
80
|
+
},
|
75
81
|
];
|
@@ -104,6 +104,10 @@ const CustomLogo = memo<LobeChatProps>(({ extra, size = 32, className, style, ty
|
|
104
104
|
|
105
105
|
break;
|
106
106
|
}
|
107
|
+
default: {
|
108
|
+
logoComponent = <CustomImageLogo size={size} style={style} {...rest} />;
|
109
|
+
break;
|
110
|
+
}
|
107
111
|
}
|
108
112
|
|
109
113
|
if (!extra) return logoComponent;
|
@@ -151,7 +151,7 @@ export const filterEnabledModels = (provider: ModelProviderCard) => {
|
|
151
151
|
return provider.chatModels.filter((v) => v.enabled).map((m) => m.id);
|
152
152
|
};
|
153
153
|
|
154
|
-
export const
|
154
|
+
export const isProviderDisableBrowserRequest = (id: string) => {
|
155
155
|
const provider = DEFAULT_MODEL_PROVIDER_LIST.find((v) => v.id === id && v.disableBrowserRequest);
|
156
156
|
return !!provider;
|
157
157
|
};
|
@@ -326,10 +326,17 @@ const OpenRouter: ModelProviderCard = {
|
|
326
326
|
modelList: { showModelFetcher: true },
|
327
327
|
modelsUrl: 'https://openrouter.ai/models',
|
328
328
|
name: 'OpenRouter',
|
329
|
+
proxyUrl: {
|
330
|
+
placeholder: 'https://openrouter.ai/api/v1',
|
331
|
+
},
|
329
332
|
settings: {
|
330
333
|
// OpenRouter don't support browser request
|
331
334
|
// https://github.com/lobehub/lobe-chat/issues/5900
|
332
335
|
disableBrowserRequest: true,
|
336
|
+
|
337
|
+
proxyUrl: {
|
338
|
+
placeholder: 'https://openrouter.ai/api/v1',
|
339
|
+
},
|
333
340
|
sdkType: 'openai',
|
334
341
|
searchMode: 'params',
|
335
342
|
showModelFetcher: true,
|
@@ -51,7 +51,7 @@ export const userSettings = pgTable('user_settings', {
|
|
51
51
|
});
|
52
52
|
export type UserSettingsItem = typeof userSettings.$inferSelect;
|
53
53
|
|
54
|
-
export const
|
54
|
+
export const userInstalledPlugins = pgTable(
|
55
55
|
'user_installed_plugins',
|
56
56
|
{
|
57
57
|
userId: text('user_id')
|
@@ -71,5 +71,5 @@ export const installedPlugins = pgTable(
|
|
71
71
|
}),
|
72
72
|
);
|
73
73
|
|
74
|
-
export type NewInstalledPlugin = typeof
|
75
|
-
export type InstalledPluginItem = typeof
|
74
|
+
export type NewInstalledPlugin = typeof userInstalledPlugins.$inferInsert;
|
75
|
+
export type InstalledPluginItem = typeof userInstalledPlugins.$inferSelect;
|
@@ -3,7 +3,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
3
3
|
|
4
4
|
import { getTestDBInstance } from '@/database/server/core/dbForTest';
|
5
5
|
|
6
|
-
import { NewInstalledPlugin,
|
6
|
+
import { NewInstalledPlugin, userInstalledPlugins, users } from '../../../schemas';
|
7
7
|
import { PluginModel } from '../plugin';
|
8
8
|
|
9
9
|
let serverDB = await getTestDBInstance();
|
@@ -44,7 +44,7 @@ describe('PluginModel', () => {
|
|
44
44
|
|
45
45
|
describe('delete', () => {
|
46
46
|
it('should delete an installed plugin by identifier', async () => {
|
47
|
-
await serverDB.insert(
|
47
|
+
await serverDB.insert(userInstalledPlugins).values({
|
48
48
|
userId,
|
49
49
|
type: 'plugin',
|
50
50
|
identifier: 'test-plugin',
|
@@ -53,14 +53,14 @@ describe('PluginModel', () => {
|
|
53
53
|
|
54
54
|
await pluginModel.delete('test-plugin');
|
55
55
|
|
56
|
-
const result = await serverDB.select().from(
|
56
|
+
const result = await serverDB.select().from(userInstalledPlugins);
|
57
57
|
expect(result).toHaveLength(0);
|
58
58
|
});
|
59
59
|
});
|
60
60
|
|
61
61
|
describe('deleteAll', () => {
|
62
62
|
it('should delete all installed plugins for the user', async () => {
|
63
|
-
await serverDB.insert(
|
63
|
+
await serverDB.insert(userInstalledPlugins).values([
|
64
64
|
{
|
65
65
|
userId,
|
66
66
|
type: 'plugin',
|
@@ -83,7 +83,7 @@ describe('PluginModel', () => {
|
|
83
83
|
|
84
84
|
await pluginModel.deleteAll();
|
85
85
|
|
86
|
-
const result = await serverDB.select().from(
|
86
|
+
const result = await serverDB.select().from(userInstalledPlugins);
|
87
87
|
expect(result).toHaveLength(1);
|
88
88
|
expect(result[0].userId).toBe('456');
|
89
89
|
});
|
@@ -91,7 +91,7 @@ describe('PluginModel', () => {
|
|
91
91
|
|
92
92
|
describe('query', () => {
|
93
93
|
it('should query installed plugins for the user', async () => {
|
94
|
-
await serverDB.insert(
|
94
|
+
await serverDB.insert(userInstalledPlugins).values([
|
95
95
|
{
|
96
96
|
userId,
|
97
97
|
type: 'plugin',
|
@@ -125,7 +125,7 @@ describe('PluginModel', () => {
|
|
125
125
|
|
126
126
|
describe('findById', () => {
|
127
127
|
it('should find an installed plugin by identifier', async () => {
|
128
|
-
await serverDB.insert(
|
128
|
+
await serverDB.insert(userInstalledPlugins).values([
|
129
129
|
{
|
130
130
|
userId,
|
131
131
|
type: 'plugin',
|
@@ -149,7 +149,7 @@ describe('PluginModel', () => {
|
|
149
149
|
|
150
150
|
describe('update', () => {
|
151
151
|
it('should update an installed plugin', async () => {
|
152
|
-
await serverDB.insert(
|
152
|
+
await serverDB.insert(userInstalledPlugins).values({
|
153
153
|
userId,
|
154
154
|
type: 'plugin',
|
155
155
|
identifier: 'test-plugin',
|
@@ -2,7 +2,7 @@ import { and, desc, eq } from 'drizzle-orm/expressions';
|
|
2
2
|
|
3
3
|
import { LobeChatDatabase } from '@/database/type';
|
4
4
|
|
5
|
-
import { InstalledPluginItem, NewInstalledPlugin,
|
5
|
+
import { InstalledPluginItem, NewInstalledPlugin, userInstalledPlugins } from '../../schemas';
|
6
6
|
|
7
7
|
export class PluginModel {
|
8
8
|
private userId: string;
|
@@ -17,11 +17,11 @@ export class PluginModel {
|
|
17
17
|
params: Pick<NewInstalledPlugin, 'type' | 'identifier' | 'manifest' | 'customParams'>,
|
18
18
|
) => {
|
19
19
|
const [result] = await this.db
|
20
|
-
.insert(
|
20
|
+
.insert(userInstalledPlugins)
|
21
21
|
.values({ ...params, createdAt: new Date(), updatedAt: new Date(), userId: this.userId })
|
22
22
|
.onConflictDoUpdate({
|
23
23
|
set: { ...params, updatedAt: new Date() },
|
24
|
-
target: [
|
24
|
+
target: [userInstalledPlugins.identifier, userInstalledPlugins.userId],
|
25
25
|
})
|
26
26
|
.returning();
|
27
27
|
|
@@ -30,40 +30,40 @@ export class PluginModel {
|
|
30
30
|
|
31
31
|
delete = async (id: string) => {
|
32
32
|
return this.db
|
33
|
-
.delete(
|
34
|
-
.where(and(eq(
|
33
|
+
.delete(userInstalledPlugins)
|
34
|
+
.where(and(eq(userInstalledPlugins.identifier, id), eq(userInstalledPlugins.userId, this.userId)));
|
35
35
|
};
|
36
36
|
|
37
37
|
deleteAll = async () => {
|
38
|
-
return this.db.delete(
|
38
|
+
return this.db.delete(userInstalledPlugins).where(eq(userInstalledPlugins.userId, this.userId));
|
39
39
|
};
|
40
40
|
|
41
41
|
query = async () => {
|
42
42
|
return this.db
|
43
43
|
.select({
|
44
|
-
createdAt:
|
45
|
-
customParams:
|
46
|
-
identifier:
|
47
|
-
manifest:
|
48
|
-
settings:
|
49
|
-
type:
|
50
|
-
updatedAt:
|
44
|
+
createdAt: userInstalledPlugins.createdAt,
|
45
|
+
customParams: userInstalledPlugins.customParams,
|
46
|
+
identifier: userInstalledPlugins.identifier,
|
47
|
+
manifest: userInstalledPlugins.manifest,
|
48
|
+
settings: userInstalledPlugins.settings,
|
49
|
+
type: userInstalledPlugins.type,
|
50
|
+
updatedAt: userInstalledPlugins.updatedAt,
|
51
51
|
})
|
52
|
-
.from(
|
53
|
-
.where(eq(
|
54
|
-
.orderBy(desc(
|
52
|
+
.from(userInstalledPlugins)
|
53
|
+
.where(eq(userInstalledPlugins.userId, this.userId))
|
54
|
+
.orderBy(desc(userInstalledPlugins.createdAt));
|
55
55
|
};
|
56
56
|
|
57
57
|
findById = async (id: string) => {
|
58
|
-
return this.db.query.
|
59
|
-
where: and(eq(
|
58
|
+
return this.db.query.userInstalledPlugins.findFirst({
|
59
|
+
where: and(eq(userInstalledPlugins.identifier, id), eq(userInstalledPlugins.userId, this.userId)),
|
60
60
|
});
|
61
61
|
};
|
62
62
|
|
63
63
|
update = async (id: string, value: Partial<InstalledPluginItem>) => {
|
64
64
|
return this.db
|
65
|
-
.update(
|
65
|
+
.update(userInstalledPlugins)
|
66
66
|
.set({ ...value, updatedAt: new Date() })
|
67
|
-
.where(and(eq(
|
67
|
+
.where(and(eq(userInstalledPlugins.identifier, id), eq(userInstalledPlugins.userId, this.userId)));
|
68
68
|
};
|
69
69
|
}
|
@@ -30,7 +30,7 @@ beforeEach(() => {
|
|
30
30
|
});
|
31
31
|
|
32
32
|
afterEach(() => {
|
33
|
-
vi.
|
33
|
+
vi.restoreAllMocks();
|
34
34
|
});
|
35
35
|
|
36
36
|
describe('LobeOpenRouterAI', () => {
|
@@ -40,6 +40,15 @@ describe('LobeOpenRouterAI', () => {
|
|
40
40
|
expect(instance).toBeInstanceOf(LobeOpenRouterAI);
|
41
41
|
expect(instance.baseURL).toEqual(defaultBaseURL);
|
42
42
|
});
|
43
|
+
|
44
|
+
it('should correctly initialize with a custom base URL', async () => {
|
45
|
+
const instance = new LobeOpenRouterAI({
|
46
|
+
apiKey: 'test_api_key',
|
47
|
+
baseURL: 'https://api.abc.com/v1',
|
48
|
+
});
|
49
|
+
expect(instance).toBeInstanceOf(LobeOpenRouterAI);
|
50
|
+
expect(instance.baseURL).toEqual('https://api.abc.com/v1');
|
51
|
+
});
|
43
52
|
});
|
44
53
|
|
45
54
|
describe('chat', () => {
|
@@ -3,7 +3,7 @@ import { eq } from 'drizzle-orm';
|
|
3
3
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
4
4
|
|
5
5
|
import { clientDB, initializeDB } from '@/database/client/db';
|
6
|
-
import {
|
6
|
+
import { userInstalledPlugins, users } from '@/database/schemas';
|
7
7
|
import { LobeTool } from '@/types/tool';
|
8
8
|
import { LobeToolCustomPlugin } from '@/types/tool/plugin';
|
9
9
|
|
@@ -40,8 +40,8 @@ describe('PluginService', () => {
|
|
40
40
|
await pluginService.installPlugin(fakePlugin);
|
41
41
|
|
42
42
|
// Assert
|
43
|
-
const result = await clientDB.query.
|
44
|
-
where: eq(
|
43
|
+
const result = await clientDB.query.userInstalledPlugins.findFirst({
|
44
|
+
where: eq(userInstalledPlugins.identifier, fakePlugin.identifier),
|
45
45
|
});
|
46
46
|
expect(result).toMatchObject(fakePlugin);
|
47
47
|
});
|
@@ -52,7 +52,7 @@ describe('PluginService', () => {
|
|
52
52
|
// Arrange
|
53
53
|
const fakePlugins = [{ identifier: 'test-plugin', type: 'plugin' }] as LobeTool[];
|
54
54
|
await clientDB
|
55
|
-
.insert(
|
55
|
+
.insert(userInstalledPlugins)
|
56
56
|
.values([{ identifier: 'test-plugin', type: 'plugin', userId }]);
|
57
57
|
// Act
|
58
58
|
const data = await pluginService.getInstalledPlugins();
|
@@ -66,14 +66,14 @@ describe('PluginService', () => {
|
|
66
66
|
it('should uninstall a plugin', async () => {
|
67
67
|
// Arrange
|
68
68
|
const identifier = 'test-plugin';
|
69
|
-
await clientDB.insert(
|
69
|
+
await clientDB.insert(userInstalledPlugins).values([{ identifier, type: 'plugin', userId }]);
|
70
70
|
|
71
71
|
// Act
|
72
72
|
await pluginService.uninstallPlugin(identifier);
|
73
73
|
|
74
74
|
// Assert
|
75
|
-
const result = await clientDB.query.
|
76
|
-
where: eq(
|
75
|
+
const result = await clientDB.query.userInstalledPlugins.findFirst({
|
76
|
+
where: eq(userInstalledPlugins.identifier, identifier),
|
77
77
|
});
|
78
78
|
expect(result).toBe(undefined);
|
79
79
|
});
|
@@ -92,8 +92,8 @@ describe('PluginService', () => {
|
|
92
92
|
await pluginService.createCustomPlugin(customPlugin);
|
93
93
|
|
94
94
|
// Assert
|
95
|
-
const result = await clientDB.query.
|
96
|
-
where: eq(
|
95
|
+
const result = await clientDB.query.userInstalledPlugins.findFirst({
|
96
|
+
where: eq(userInstalledPlugins.identifier, customPlugin.identifier),
|
97
97
|
});
|
98
98
|
expect(result).toMatchObject(customPlugin);
|
99
99
|
});
|
@@ -104,14 +104,14 @@ describe('PluginService', () => {
|
|
104
104
|
// Arrange
|
105
105
|
const identifier = 'plugin-id';
|
106
106
|
const value = { customParams: { ab: '1' } } as unknown as LobeToolCustomPlugin;
|
107
|
-
await clientDB.insert(
|
107
|
+
await clientDB.insert(userInstalledPlugins).values([{ identifier, type: 'plugin', userId }]);
|
108
108
|
|
109
109
|
// Act
|
110
110
|
await pluginService.updatePlugin(identifier, value);
|
111
111
|
|
112
112
|
// Assert
|
113
|
-
const result = await clientDB.query.
|
114
|
-
where: eq(
|
113
|
+
const result = await clientDB.query.userInstalledPlugins.findFirst({
|
114
|
+
where: eq(userInstalledPlugins.identifier, identifier),
|
115
115
|
});
|
116
116
|
expect(result).toMatchObject(value);
|
117
117
|
});
|
@@ -122,14 +122,14 @@ describe('PluginService', () => {
|
|
122
122
|
// Arrange
|
123
123
|
const identifier = 'plugin-id';
|
124
124
|
const manifest = { name: 'NewPluginManifest' } as unknown as LobeChatPluginManifest;
|
125
|
-
await clientDB.insert(
|
125
|
+
await clientDB.insert(userInstalledPlugins).values([{ identifier, type: 'plugin', userId }]);
|
126
126
|
|
127
127
|
// Act
|
128
128
|
await pluginService.updatePluginManifest(identifier, manifest);
|
129
129
|
|
130
130
|
// Assert
|
131
|
-
const result = await clientDB.query.
|
132
|
-
where: eq(
|
131
|
+
const result = await clientDB.query.userInstalledPlugins.findFirst({
|
132
|
+
where: eq(userInstalledPlugins.identifier, identifier),
|
133
133
|
});
|
134
134
|
expect(result).toMatchObject({ manifest });
|
135
135
|
});
|
@@ -138,7 +138,7 @@ describe('PluginService', () => {
|
|
138
138
|
describe('removeAllPlugins', () => {
|
139
139
|
it('should remove all plugins', async () => {
|
140
140
|
// Arrange
|
141
|
-
await clientDB.insert(
|
141
|
+
await clientDB.insert(userInstalledPlugins).values([
|
142
142
|
{ identifier: '123', type: 'plugin', userId },
|
143
143
|
{ identifier: '234', type: 'plugin', userId },
|
144
144
|
]);
|
@@ -147,8 +147,8 @@ describe('PluginService', () => {
|
|
147
147
|
await pluginService.removeAllPlugins();
|
148
148
|
|
149
149
|
// Assert
|
150
|
-
const result = await clientDB.query.
|
151
|
-
where: eq(
|
150
|
+
const result = await clientDB.query.userInstalledPlugins.findMany({
|
151
|
+
where: eq(userInstalledPlugins.userId, userId),
|
152
152
|
});
|
153
153
|
expect(result.length).toEqual(0);
|
154
154
|
});
|
@@ -159,14 +159,14 @@ describe('PluginService', () => {
|
|
159
159
|
// Arrange
|
160
160
|
const id = 'plugin-id';
|
161
161
|
const settings = { color: 'blue' };
|
162
|
-
await clientDB.insert(
|
162
|
+
await clientDB.insert(userInstalledPlugins).values([{ identifier: id, type: 'plugin', userId }]);
|
163
163
|
|
164
164
|
// Act
|
165
165
|
await pluginService.updatePluginSettings(id, settings);
|
166
166
|
|
167
167
|
// Assert
|
168
|
-
const result = await clientDB.query.
|
169
|
-
where: eq(
|
168
|
+
const result = await clientDB.query.userInstalledPlugins.findFirst({
|
169
|
+
where: eq(userInstalledPlugins.identifier, id),
|
170
170
|
});
|
171
171
|
|
172
172
|
expect(result).toMatchObject({ settings });
|
@@ -0,0 +1,249 @@
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
2
|
+
|
3
|
+
import { aiProviderSelectors } from '../selectors';
|
4
|
+
|
5
|
+
describe('aiProviderSelectors', () => {
|
6
|
+
const mockState: any = {
|
7
|
+
aiProviderList: [
|
8
|
+
{ id: 'provider1', enabled: true, sort: 1 },
|
9
|
+
{ id: 'provider2', enabled: false, sort: 2 },
|
10
|
+
{ id: 'provider3', enabled: true, sort: 0 },
|
11
|
+
],
|
12
|
+
aiProviderDetail: {
|
13
|
+
id: 'provider1',
|
14
|
+
keyVaults: {
|
15
|
+
baseURL: 'https://api.example.com',
|
16
|
+
apiKey: 'test-key',
|
17
|
+
},
|
18
|
+
},
|
19
|
+
aiProviderLoadingIds: ['loading-provider'],
|
20
|
+
aiProviderConfigUpdatingIds: ['updating-provider'],
|
21
|
+
activeAiProvider: 'provider1',
|
22
|
+
aiProviderRuntimeConfig: {
|
23
|
+
provider1: {
|
24
|
+
keyVaults: {
|
25
|
+
baseURL: 'https://api.example.com',
|
26
|
+
apiKey: 'test-key',
|
27
|
+
},
|
28
|
+
settings: {
|
29
|
+
searchMode: 'internal',
|
30
|
+
},
|
31
|
+
fetchOnClient: true,
|
32
|
+
},
|
33
|
+
provider2: {
|
34
|
+
keyVaults: {
|
35
|
+
baseURL: 'https://api2.example.com',
|
36
|
+
},
|
37
|
+
settings: {},
|
38
|
+
},
|
39
|
+
ollama: {
|
40
|
+
keyVaults: {},
|
41
|
+
settings: {},
|
42
|
+
fetchOnClient: true,
|
43
|
+
},
|
44
|
+
},
|
45
|
+
// Required by AIProviderStoreState
|
46
|
+
activeProviderModelList: [],
|
47
|
+
initAiProviderList: [],
|
48
|
+
providerSearchKeyword: '',
|
49
|
+
aiModelLoadingIds: [],
|
50
|
+
modelFetchingStatus: {},
|
51
|
+
modelRuntimeConfig: {},
|
52
|
+
modelSearchKeyword: '',
|
53
|
+
};
|
54
|
+
|
55
|
+
describe('enabledAiProviderList', () => {
|
56
|
+
it('should return enabled providers sorted by sort', () => {
|
57
|
+
const result = aiProviderSelectors.enabledAiProviderList(mockState);
|
58
|
+
expect(result).toEqual([
|
59
|
+
{ id: 'provider3', enabled: true, sort: 0 },
|
60
|
+
{ id: 'provider1', enabled: true, sort: 1 },
|
61
|
+
]);
|
62
|
+
});
|
63
|
+
});
|
64
|
+
|
65
|
+
describe('disabledAiProviderList', () => {
|
66
|
+
it('should return disabled providers', () => {
|
67
|
+
const result = aiProviderSelectors.disabledAiProviderList(mockState);
|
68
|
+
expect(result).toEqual([{ id: 'provider2', enabled: false, sort: 2 }]);
|
69
|
+
});
|
70
|
+
});
|
71
|
+
|
72
|
+
describe('isProviderEnabled', () => {
|
73
|
+
it('should return true for enabled provider', () => {
|
74
|
+
expect(aiProviderSelectors.isProviderEnabled('provider1')(mockState)).toBe(true);
|
75
|
+
});
|
76
|
+
|
77
|
+
it('should return false for disabled provider', () => {
|
78
|
+
expect(aiProviderSelectors.isProviderEnabled('provider2')(mockState)).toBe(false);
|
79
|
+
});
|
80
|
+
});
|
81
|
+
|
82
|
+
describe('isProviderLoading', () => {
|
83
|
+
it('should return true for loading provider', () => {
|
84
|
+
expect(aiProviderSelectors.isProviderLoading('loading-provider')(mockState)).toBe(true);
|
85
|
+
});
|
86
|
+
|
87
|
+
it('should return false for non-loading provider', () => {
|
88
|
+
expect(aiProviderSelectors.isProviderLoading('provider1')(mockState)).toBe(false);
|
89
|
+
});
|
90
|
+
});
|
91
|
+
|
92
|
+
describe('activeProviderConfig', () => {
|
93
|
+
it('should return active provider config', () => {
|
94
|
+
expect(aiProviderSelectors.activeProviderConfig(mockState)).toEqual(
|
95
|
+
mockState.aiProviderDetail,
|
96
|
+
);
|
97
|
+
});
|
98
|
+
});
|
99
|
+
|
100
|
+
describe('isAiProviderConfigLoading', () => {
|
101
|
+
it('should return true if provider id does not match active provider', () => {
|
102
|
+
expect(aiProviderSelectors.isAiProviderConfigLoading('provider2')(mockState)).toBe(true);
|
103
|
+
});
|
104
|
+
|
105
|
+
it('should return false if provider id matches active provider', () => {
|
106
|
+
expect(aiProviderSelectors.isAiProviderConfigLoading('provider1')(mockState)).toBe(false);
|
107
|
+
});
|
108
|
+
});
|
109
|
+
|
110
|
+
describe('isActiveProviderEndpointNotEmpty', () => {
|
111
|
+
it('should return true when baseURL exists', () => {
|
112
|
+
expect(aiProviderSelectors.isActiveProviderEndpointNotEmpty(mockState)).toBe(true);
|
113
|
+
});
|
114
|
+
|
115
|
+
it('should return false when no endpoint info exists', () => {
|
116
|
+
const stateWithoutEndpoint = {
|
117
|
+
...mockState,
|
118
|
+
aiProviderDetail: { keyVaults: {} },
|
119
|
+
};
|
120
|
+
expect(aiProviderSelectors.isActiveProviderEndpointNotEmpty(stateWithoutEndpoint)).toBe(
|
121
|
+
false,
|
122
|
+
);
|
123
|
+
});
|
124
|
+
});
|
125
|
+
|
126
|
+
describe('isActiveProviderApiKeyNotEmpty', () => {
|
127
|
+
it('should return true when apiKey exists', () => {
|
128
|
+
expect(aiProviderSelectors.isActiveProviderApiKeyNotEmpty(mockState)).toBe(true);
|
129
|
+
});
|
130
|
+
|
131
|
+
it('should return false when no api key exists', () => {
|
132
|
+
const stateWithoutApiKey = {
|
133
|
+
...mockState,
|
134
|
+
aiProviderDetail: { keyVaults: {} },
|
135
|
+
};
|
136
|
+
expect(aiProviderSelectors.isActiveProviderApiKeyNotEmpty(stateWithoutApiKey)).toBe(false);
|
137
|
+
});
|
138
|
+
});
|
139
|
+
|
140
|
+
describe('providerConfigById', () => {
|
141
|
+
it('should return config for existing provider', () => {
|
142
|
+
expect(aiProviderSelectors.providerConfigById('provider1')(mockState)).toEqual(
|
143
|
+
mockState.aiProviderRuntimeConfig.provider1,
|
144
|
+
);
|
145
|
+
});
|
146
|
+
|
147
|
+
it('should return undefined for non-existing provider', () => {
|
148
|
+
expect(aiProviderSelectors.providerConfigById('non-existing')(mockState)).toBeUndefined();
|
149
|
+
});
|
150
|
+
|
151
|
+
it('should return undefined for empty id', () => {
|
152
|
+
expect(aiProviderSelectors.providerConfigById('')(mockState)).toBeUndefined();
|
153
|
+
});
|
154
|
+
});
|
155
|
+
|
156
|
+
describe('isProviderConfigUpdating', () => {
|
157
|
+
it('should return true for updating provider', () => {
|
158
|
+
expect(aiProviderSelectors.isProviderConfigUpdating('updating-provider')(mockState)).toBe(
|
159
|
+
true,
|
160
|
+
);
|
161
|
+
});
|
162
|
+
|
163
|
+
it('should return false for non-updating provider', () => {
|
164
|
+
expect(aiProviderSelectors.isProviderConfigUpdating('provider1')(mockState)).toBe(false);
|
165
|
+
});
|
166
|
+
});
|
167
|
+
|
168
|
+
describe('isProviderFetchOnClient', () => {
|
169
|
+
it('should return false if provider is in disable browser request list', () => {
|
170
|
+
expect(
|
171
|
+
aiProviderSelectors.isProviderFetchOnClient('provider-with-disabled-browser')(mockState),
|
172
|
+
).toBe(false);
|
173
|
+
});
|
174
|
+
|
175
|
+
it('should follow user settings for whitelisted providers', () => {
|
176
|
+
expect(aiProviderSelectors.isProviderFetchOnClient('ollama')(mockState)).toBe(true);
|
177
|
+
});
|
178
|
+
|
179
|
+
it('should return false if no endpoint and api key', () => {
|
180
|
+
const state = {
|
181
|
+
...mockState,
|
182
|
+
aiProviderRuntimeConfig: {
|
183
|
+
test: {
|
184
|
+
keyVaults: {},
|
185
|
+
settings: {},
|
186
|
+
},
|
187
|
+
},
|
188
|
+
};
|
189
|
+
expect(aiProviderSelectors.isProviderFetchOnClient('test')(state)).toBe(false);
|
190
|
+
});
|
191
|
+
|
192
|
+
it('should return true if only baseURL exists', () => {
|
193
|
+
const state = {
|
194
|
+
...mockState,
|
195
|
+
aiProviderRuntimeConfig: {
|
196
|
+
test: {
|
197
|
+
keyVaults: { baseURL: 'http://test.com' },
|
198
|
+
settings: {},
|
199
|
+
},
|
200
|
+
},
|
201
|
+
};
|
202
|
+
expect(aiProviderSelectors.isProviderFetchOnClient('test')(state)).toBe(true);
|
203
|
+
});
|
204
|
+
|
205
|
+
it('should follow user settings if both endpoint and api key exist', () => {
|
206
|
+
expect(aiProviderSelectors.isProviderFetchOnClient('provider1')(mockState)).toBe(true);
|
207
|
+
});
|
208
|
+
});
|
209
|
+
|
210
|
+
describe('providerKeyVaults', () => {
|
211
|
+
it('should return key vaults for existing provider', () => {
|
212
|
+
expect(aiProviderSelectors.providerKeyVaults('provider1')(mockState)).toEqual(
|
213
|
+
mockState.aiProviderRuntimeConfig.provider1.keyVaults,
|
214
|
+
);
|
215
|
+
});
|
216
|
+
|
217
|
+
it('should return undefined for undefined provider', () => {
|
218
|
+
expect(aiProviderSelectors.providerKeyVaults(undefined)(mockState)).toBeUndefined();
|
219
|
+
});
|
220
|
+
|
221
|
+
it('should return undefined for non-existing provider', () => {
|
222
|
+
expect(aiProviderSelectors.providerKeyVaults('non-existing')(mockState)).toBeUndefined();
|
223
|
+
});
|
224
|
+
});
|
225
|
+
|
226
|
+
describe('isProviderHasBuiltinSearch', () => {
|
227
|
+
it('should return true if provider has search mode', () => {
|
228
|
+
expect(aiProviderSelectors.isProviderHasBuiltinSearch('provider1')(mockState)).toBe(true);
|
229
|
+
});
|
230
|
+
|
231
|
+
it('should return false if provider has no search mode', () => {
|
232
|
+
expect(aiProviderSelectors.isProviderHasBuiltinSearch('provider2')(mockState)).toBe(false);
|
233
|
+
});
|
234
|
+
});
|
235
|
+
|
236
|
+
describe('isProviderHasBuiltinSearchConfig', () => {
|
237
|
+
it('should return false if search mode is internal', () => {
|
238
|
+
expect(aiProviderSelectors.isProviderHasBuiltinSearchConfig('provider1')(mockState)).toBe(
|
239
|
+
false,
|
240
|
+
);
|
241
|
+
});
|
242
|
+
|
243
|
+
it('should return false if no search mode exists', () => {
|
244
|
+
expect(aiProviderSelectors.isProviderHasBuiltinSearchConfig('provider2')(mockState)).toBe(
|
245
|
+
false,
|
246
|
+
);
|
247
|
+
});
|
248
|
+
});
|
249
|
+
});
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import {
|
1
|
+
import { isProviderDisableBrowserRequest } from '@/config/modelProviders';
|
2
2
|
import { AIProviderStoreState } from '@/store/aiInfra/initialState';
|
3
3
|
import { AiProviderRuntimeConfig } from '@/types/aiProvider';
|
4
4
|
import { GlobalLLMProviderKey } from '@/types/user/settings';
|
@@ -59,8 +59,8 @@ const isProviderFetchOnClient =
|
|
59
59
|
(provider: GlobalLLMProviderKey | string) => (s: AIProviderStoreState) => {
|
60
60
|
const config = providerConfigById(provider)(s);
|
61
61
|
|
62
|
-
// If the provider already disable
|
63
|
-
if (
|
62
|
+
// If the provider already disable browser request in model config, force on Server.
|
63
|
+
if (isProviderDisableBrowserRequest(provider)) return false;
|
64
64
|
|
65
65
|
// If the provider in the whitelist, follow the user settings
|
66
66
|
if (providerWhitelist.has(provider) && typeof config?.fetchOnClient !== 'undefined')
|
@@ -110,10 +110,10 @@ describe('modelConfigSelectors', () => {
|
|
110
110
|
expect(modelConfigSelectors.isProviderFetchOnClient('azure')(s)).toBe(true);
|
111
111
|
});
|
112
112
|
|
113
|
-
// Qwen provider not work in
|
113
|
+
// Qwen provider not work in browser request. Please skip this case if it work in future.
|
114
114
|
// Issue: https://github.com/lobehub/lobe-chat/issues/3108
|
115
115
|
// PR: https://github.com/lobehub/lobe-chat/pull/3133
|
116
|
-
it('client fecth should be disabled if provider is disable
|
116
|
+
it('client fecth should be disabled if provider is disable browser request', () => {
|
117
117
|
const s = merge(initialSettingsState, {
|
118
118
|
settings: {
|
119
119
|
languageModel: {
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import {
|
1
|
+
import { isProviderDisableBrowserRequest } from '@/config/modelProviders';
|
2
2
|
import { UserStore } from '@/store/user';
|
3
3
|
import { GlobalLLMProviderKey } from '@/types/user/settings';
|
4
4
|
|
@@ -19,8 +19,8 @@ const providerWhitelist = new Set(['ollama']);
|
|
19
19
|
const isProviderFetchOnClient = (provider: GlobalLLMProviderKey | string) => (s: UserStore) => {
|
20
20
|
const config = getProviderConfigById(provider)(s);
|
21
21
|
|
22
|
-
// If the provider already disable
|
23
|
-
if (
|
22
|
+
// If the provider already disable browser request in model config, force on Server.
|
23
|
+
if (isProviderDisableBrowserRequest(provider)) return false;
|
24
24
|
|
25
25
|
// If the provider in the whitelist, follow the user settings
|
26
26
|
if (providerWhitelist.has(provider) && typeof config?.fetchOnClient !== 'undefined')
|