@lobehub/chat 1.70.3 โ†’ 1.70.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/README.md +1 -1
  3. package/README.zh-CN.md +1 -1
  4. package/changelog/v1.json +18 -0
  5. package/docs/self-hosting/advanced/model-list.mdx +3 -3
  6. package/docs/self-hosting/advanced/model-list.zh-CN.mdx +3 -3
  7. package/docs/self-hosting/environment-variables/model-provider.mdx +17 -3
  8. package/docs/self-hosting/environment-variables/model-provider.zh-CN.mdx +16 -2
  9. package/package.json +1 -1
  10. package/packages/web-crawler/src/crawImpl/naive.ts +11 -3
  11. package/packages/web-crawler/src/urlRules.ts +6 -0
  12. package/src/app/[variants]/(main)/(mobile)/me/(home)/features/Header.tsx +2 -2
  13. package/src/app/[variants]/(main)/settings/common/features/Theme/index.tsx +5 -3
  14. package/src/components/Branding/ProductLogo/Custom.tsx +4 -0
  15. package/src/config/modelProviders/openrouter.ts +7 -0
  16. package/src/const/settings/common.ts +0 -1
  17. package/src/database/schemas/user.ts +3 -3
  18. package/src/database/server/models/__tests__/plugin.test.ts +8 -8
  19. package/src/database/server/models/plugin.ts +20 -20
  20. package/src/features/User/UserPanel/ThemeButton.tsx +4 -4
  21. package/src/layout/GlobalProvider/AppTheme.tsx +5 -9
  22. package/src/libs/agent-runtime/openrouter/index.test.ts +10 -1
  23. package/src/services/plugin/client.test.ts +21 -21
  24. package/src/services/user/_deprecated.test.ts +1 -1
  25. package/src/services/user/client.test.ts +1 -1
  26. package/src/store/aiInfra/slices/aiProvider/__tests__/selectors.test.ts +249 -0
  27. package/src/store/global/action.test.ts +15 -0
  28. package/src/store/global/actions/__tests__/general.test.ts +221 -0
  29. package/src/store/global/actions/general.ts +10 -1
  30. package/src/store/global/initialState.ts +6 -0
  31. package/src/store/global/selectors/systemStatus.test.ts +209 -0
  32. package/src/store/global/selectors/systemStatus.ts +22 -21
  33. package/src/store/user/slices/settings/action.test.ts +1 -19
  34. package/src/store/user/slices/settings/action.ts +0 -5
  35. package/src/store/user/slices/settings/selectors/general.test.ts +5 -15
  36. package/src/store/user/slices/settings/selectors/general.ts +0 -6
  37. package/src/types/user/settings/general.ts +0 -2
package/CHANGELOG.md CHANGED
@@ -2,6 +2,56 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ### [Version 1.70.5](https://github.com/lobehub/lobe-chat/compare/v1.70.4...v1.70.5)
6
+
7
+ <sup>Released on **2025-03-11**</sup>
8
+
9
+ #### ๐Ÿ› Bug Fixes
10
+
11
+ - **misc**: Refactor the theme implement.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### What's fixed
19
+
20
+ - **misc**: Refactor the theme implement, closes [#6844](https://github.com/lobehub/lobe-chat/issues/6844) ([e5c2161](https://github.com/lobehub/lobe-chat/commit/e5c2161))
21
+
22
+ </details>
23
+
24
+ <div align="right">
25
+
26
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
27
+
28
+ </div>
29
+
30
+ ### [Version 1.70.4](https://github.com/lobehub/lobe-chat/compare/v1.70.3...v1.70.4)
31
+
32
+ <sup>Released on **2025-03-11**</sup>
33
+
34
+ #### ๐Ÿ’„ Styles
35
+
36
+ - **misc**: Support OpenRouter custom BaseURL.
37
+
38
+ <br/>
39
+
40
+ <details>
41
+ <summary><kbd>Improvements and Fixes</kbd></summary>
42
+
43
+ #### Styles
44
+
45
+ - **misc**: Support OpenRouter custom BaseURL ([a8089ed](https://github.com/lobehub/lobe-chat/commit/a8089ed))
46
+
47
+ </details>
48
+
49
+ <div align="right">
50
+
51
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
52
+
53
+ </div>
54
+
5
55
  ### [Version 1.70.3](https://github.com/lobehub/lobe-chat/compare/v1.70.2...v1.70.3)
6
56
 
7
57
  <sup>Released on **2025-03-11**</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>**47**</kbd>](https://lobechat.com/discover/plugins)
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>**47**</kbd>](https://lobechat.com/discover/plugins)
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,22 @@
1
1
  [
2
+ {
3
+ "children": {
4
+ "fixes": [
5
+ "Refactor the theme implement."
6
+ ]
7
+ },
8
+ "date": "2025-03-11",
9
+ "version": "1.70.5"
10
+ },
11
+ {
12
+ "children": {
13
+ "improvements": [
14
+ "Support OpenRouter custom BaseURL."
15
+ ]
16
+ },
17
+ "date": "2025-03-11",
18
+ "version": "1.70.4"
19
+ },
2
20
  {
3
21
  "children": {},
4
22
  "date": "2025-03-11",
@@ -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->deplymentName=displayName` to customize the display name of a model, separated by commas. Definition syntax rules see [model-list][model-list]
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
- - ๆ่ฟฐ๏ผš็”จๆฅๆŽงๅˆถๆจกๅž‹ๅˆ—่กจ๏ผŒไฝฟ็”จ `+` ๅขžๅŠ ไธ€ไธชๆจกๅž‹๏ผŒไฝฟ็”จ `-` ๆฅ้š่—ไธ€ไธชๆจกๅž‹๏ผŒไฝฟ็”จ `ๆจกๅž‹ๅ=ๅฑ•็คบๅ<ๆ‰ฉๅฑ•้…็ฝฎ>` ๆฅ่‡ชๅฎšไน‰ๆจกๅž‹็š„ๅฑ•็คบๅ๏ผŒ็”จ่‹ฑๆ–‡้€—ๅท้š”ๅผ€ใ€‚ๆจกๅž‹ๅฎšไน‰่ฏญๆณ•่ง„ๅˆ™่ง [ๆจกๅž‹ๅˆ—่กจ][model-list]
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "1.70.3",
3
+ "version": "1.70.5",
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
- const json = await res.json();
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: JSON.stringify(json, null, 2),
88
+ content: content,
81
89
  contentType: 'json',
82
- length: json.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
  ];
@@ -7,12 +7,12 @@ import { Moon, Sun } from 'lucide-react';
7
7
  import { memo } from 'react';
8
8
 
9
9
  import { MOBILE_HEADER_ICON_SIZE } from '@/const/layoutTokens';
10
- import { useUserStore } from '@/store/user';
10
+ import { useGlobalStore } from '@/store/global';
11
11
  import { mobileHeaderSticky } from '@/styles/mobileHeader';
12
12
 
13
13
  const Header = memo(() => {
14
14
  const theme = useTheme();
15
- const switchThemeMode = useUserStore((s) => s.switchThemeMode);
15
+ const switchThemeMode = useGlobalStore((s) => s.switchThemeMode);
16
16
 
17
17
  return (
18
18
  <MobileNavBar
@@ -12,8 +12,9 @@ import { FORM_STYLE } from '@/const/layoutTokens';
12
12
  import { imageUrl } from '@/const/url';
13
13
  import { Locales, localeOptions } from '@/locales/resources';
14
14
  import { useGlobalStore } from '@/store/global';
15
+ import { systemStatusSelectors } from '@/store/global/selectors';
15
16
  import { useUserStore } from '@/store/user';
16
- import { settingsSelectors, userGeneralSettingsSelectors } from '@/store/user/selectors';
17
+ import { settingsSelectors } from '@/store/user/selectors';
17
18
 
18
19
  import { ThemeSwatchesNeutral, ThemeSwatchesPrimary } from './ThemeSwatches';
19
20
 
@@ -24,8 +25,9 @@ const Theme = memo(() => {
24
25
 
25
26
  const [form] = Form.useForm();
26
27
  const settings = useUserStore(settingsSelectors.currentSettings, isEqual);
27
- const themeMode = useUserStore(userGeneralSettingsSelectors.currentThemeMode);
28
- const [setThemeMode, setSettings] = useUserStore((s) => [s.switchThemeMode, s.setSettings]);
28
+ const themeMode = useGlobalStore(systemStatusSelectors.themeMode);
29
+ const [setSettings] = useUserStore((s) => [s.setSettings]);
30
+ const [setThemeMode] = useGlobalStore((s) => [s.switchThemeMode]);
29
31
 
30
32
  useSyncSettings(form);
31
33
  const [switchLocale] = useGlobalStore((s) => [s.switchLocale]);
@@ -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;
@@ -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,
@@ -2,5 +2,4 @@ import { UserGeneralConfig } from '@/types/user/settings';
2
2
 
3
3
  export const DEFAULT_COMMON_SETTINGS: UserGeneralConfig = {
4
4
  fontSize: 14,
5
- themeMode: 'auto',
6
5
  };
@@ -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 installedPlugins = pgTable(
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 installedPlugins.$inferInsert;
75
- export type InstalledPluginItem = typeof installedPlugins.$inferSelect;
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, installedPlugins, users } from '../../../schemas';
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(installedPlugins).values({
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(installedPlugins);
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(installedPlugins).values([
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(installedPlugins);
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(installedPlugins).values([
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(installedPlugins).values([
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(installedPlugins).values({
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, installedPlugins } from '../../schemas';
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(installedPlugins)
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: [installedPlugins.identifier, installedPlugins.userId],
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(installedPlugins)
34
- .where(and(eq(installedPlugins.identifier, id), eq(installedPlugins.userId, this.userId)));
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(installedPlugins).where(eq(installedPlugins.userId, this.userId));
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: installedPlugins.createdAt,
45
- customParams: installedPlugins.customParams,
46
- identifier: installedPlugins.identifier,
47
- manifest: installedPlugins.manifest,
48
- settings: installedPlugins.settings,
49
- type: installedPlugins.type,
50
- updatedAt: installedPlugins.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(installedPlugins)
53
- .where(eq(installedPlugins.userId, this.userId))
54
- .orderBy(desc(installedPlugins.createdAt));
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.installedPlugins.findFirst({
59
- where: and(eq(installedPlugins.identifier, id), eq(installedPlugins.userId, this.userId)),
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(installedPlugins)
65
+ .update(userInstalledPlugins)
66
66
  .set({ ...value, updatedAt: new Date() })
67
- .where(and(eq(installedPlugins.identifier, id), eq(installedPlugins.userId, this.userId)));
67
+ .where(and(eq(userInstalledPlugins.identifier, id), eq(userInstalledPlugins.userId, this.userId)));
68
68
  };
69
69
  }
@@ -6,8 +6,8 @@ import { memo, useMemo } from 'react';
6
6
  import { useTranslation } from 'react-i18next';
7
7
 
8
8
  import Menu, { type MenuProps } from '@/components/Menu';
9
- import { useUserStore } from '@/store/user';
10
- import { userGeneralSettingsSelectors } from '@/store/user/selectors';
9
+ import { useGlobalStore } from '@/store/global';
10
+ import { systemStatusSelectors } from '@/store/global/selectors';
11
11
 
12
12
  const themeIcons = {
13
13
  auto: Monitor,
@@ -17,8 +17,8 @@ const themeIcons = {
17
17
 
18
18
  const ThemeButton = memo<{ placement?: PopoverProps['placement'] }>(({ placement = 'right' }) => {
19
19
  const theme = useTheme();
20
- const [themeMode, switchThemeMode] = useUserStore((s) => [
21
- userGeneralSettingsSelectors.currentThemeMode(s),
20
+ const [themeMode, switchThemeMode] = useGlobalStore((s) => [
21
+ systemStatusSelectors.themeMode(s),
22
22
  s.switchThemeMode,
23
23
  ]);
24
24
 
@@ -14,11 +14,9 @@ import Link from 'next/link';
14
14
  import { ReactNode, memo, useEffect } from 'react';
15
15
 
16
16
  import AntdStaticMethods from '@/components/AntdStaticMethods';
17
- import {
18
- LOBE_THEME_APPEARANCE,
19
- LOBE_THEME_NEUTRAL_COLOR,
20
- LOBE_THEME_PRIMARY_COLOR,
21
- } from '@/const/theme';
17
+ import { LOBE_THEME_NEUTRAL_COLOR, LOBE_THEME_PRIMARY_COLOR } from '@/const/theme';
18
+ import { useGlobalStore } from '@/store/global';
19
+ import { systemStatusSelectors } from '@/store/global/selectors';
22
20
  import { useUserStore } from '@/store/user';
23
21
  import { userGeneralSettingsSelectors } from '@/store/user/selectors';
24
22
  import { GlobalStyle } from '@/styles';
@@ -103,7 +101,7 @@ const AppTheme = memo<AppThemeProps>(
103
101
  // console.debug('server:appearance', defaultAppearance);
104
102
  // console.debug('server:primaryColor', defaultPrimaryColor);
105
103
  // console.debug('server:neutralColor', defaultNeutralColor);
106
- const themeMode = useUserStore(userGeneralSettingsSelectors.currentThemeMode);
104
+ const themeMode = useGlobalStore(systemStatusSelectors.themeMode);
107
105
  const { styles, cx, theme } = useStyles();
108
106
  const [primaryColor, neutralColor] = useUserStore((s) => [
109
107
  userGeneralSettingsSelectors.primaryColor(s),
@@ -120,15 +118,13 @@ const AppTheme = memo<AppThemeProps>(
120
118
 
121
119
  return (
122
120
  <ThemeProvider
121
+ appearance={themeMode !== 'auto' ? themeMode : undefined}
123
122
  className={cx(styles.app, styles.scrollbar, styles.scrollbarPolyfill)}
124
123
  customTheme={{
125
124
  neutralColor: neutralColor ?? defaultNeutralColor,
126
125
  primaryColor: primaryColor ?? defaultPrimaryColor,
127
126
  }}
128
127
  defaultAppearance={defaultAppearance}
129
- onAppearanceChange={(appearance) => {
130
- setCookie(LOBE_THEME_APPEARANCE, appearance);
131
- }}
132
128
  theme={{
133
129
  cssVar: true,
134
130
  token: {
@@ -30,7 +30,7 @@ beforeEach(() => {
30
30
  });
31
31
 
32
32
  afterEach(() => {
33
- vi.clearAllMocks();
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', () => {