@lobehub/chat 0.127.1 → 0.128.0
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/.eslintrc.js +2 -0
- package/CHANGELOG.md +50 -0
- package/README.md +8 -8
- package/README.zh-CN.md +8 -8
- package/docs/Deployment/Environment-Variable.md +26 -0
- package/docs/Deployment/Environment-Variable.zh-CN.md +26 -0
- package/package.json +1 -1
- package/src/app/api/config/parseDefaultAgent.test.ts +181 -0
- package/src/app/api/config/parseDefaultAgent.ts +42 -0
- package/src/app/api/config/route.ts +6 -0
- package/src/app/chat/(desktop)/layout.desktop.tsx +1 -3
- package/src/app/chat/(mobile)/index.tsx +2 -5
- package/src/app/chat/features/ChatHeader/SettingButton.tsx +1 -1
- package/src/app/home/Redirect.tsx +0 -4
- package/src/app/market/(desktop)/layout.desktop.tsx +1 -4
- package/src/app/market/(mobile)/layout.mobile.tsx +1 -4
- package/src/app/market/features/AgentDetailContent/AgentInfo/Header.tsx +0 -4
- package/src/app/settings/(desktop)/layout.desktop.tsx +1 -4
- package/src/app/settings/(mobile)/layout.mobile.tsx +1 -3
- package/src/config/server/app.ts +4 -0
- package/src/features/AgentSetting/AgentConfig/index.tsx +1 -1
- package/src/features/MobileTabBar/index.tsx +7 -14
- package/src/features/SideBar/BottomActions.tsx +2 -7
- package/src/features/SideBar/TopActions.tsx +2 -7
- package/src/features/SideBar/index.tsx +7 -5
- package/src/layout/AppLayout.desktop.tsx +8 -3
- package/src/layout/AppLayout.mobile.tsx +4 -2
- package/src/store/global/hooks/index.ts +1 -1
- package/src/store/global/slices/common/action.test.ts +0 -12
- package/src/store/global/slices/common/action.ts +1 -6
- package/src/store/global/hooks/useSwitchSideBarOnInit.ts +0 -13
package/.eslintrc.js
CHANGED
|
@@ -15,5 +15,7 @@ config.rules['react/no-unknown-property'] = 0;
|
|
|
15
15
|
config.rules['unicorn/prefer-ternary'] = 0;
|
|
16
16
|
config.rules['unicorn/prefer-spread'] = 0;
|
|
17
17
|
config.rules['unicorn/catch-error-name'] = 0;
|
|
18
|
+
config.rules['unicorn/no-array-for-each'] = 0;
|
|
19
|
+
config.rules['unicorn/prefer-number-properties'] = 0;
|
|
18
20
|
|
|
19
21
|
module.exports = config;
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,56 @@
|
|
|
2
2
|
|
|
3
3
|
# Changelog
|
|
4
4
|
|
|
5
|
+
## [Version 0.128.0](https://github.com/lobehub/lobe-chat/compare/v0.127.2...v0.128.0)
|
|
6
|
+
|
|
7
|
+
<sup>Released on **2024-02-14**</sup>
|
|
8
|
+
|
|
9
|
+
#### ✨ Features
|
|
10
|
+
|
|
11
|
+
- **misc**: Support define default agent config with `DEFAULT_AGENT_CONFIG` ENV.
|
|
12
|
+
|
|
13
|
+
<br/>
|
|
14
|
+
|
|
15
|
+
<details>
|
|
16
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
|
17
|
+
|
|
18
|
+
#### What's improved
|
|
19
|
+
|
|
20
|
+
- **misc**: Support define default agent config with `DEFAULT_AGENT_CONFIG` ENV, closes [#1291](https://github.com/lobehub/lobe-chat/issues/1291) ([c7c096e](https://github.com/lobehub/lobe-chat/commit/c7c096e))
|
|
21
|
+
|
|
22
|
+
</details>
|
|
23
|
+
|
|
24
|
+
<div align="right">
|
|
25
|
+
|
|
26
|
+
[](#readme-top)
|
|
27
|
+
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
### [Version 0.127.2](https://github.com/lobehub/lobe-chat/compare/v0.127.1...v0.127.2)
|
|
31
|
+
|
|
32
|
+
<sup>Released on **2024-02-14**</sup>
|
|
33
|
+
|
|
34
|
+
#### ♻ Code Refactoring
|
|
35
|
+
|
|
36
|
+
- **misc**: Refactor the sidebar to fix first render state.
|
|
37
|
+
|
|
38
|
+
<br/>
|
|
39
|
+
|
|
40
|
+
<details>
|
|
41
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
|
42
|
+
|
|
43
|
+
#### Code refactoring
|
|
44
|
+
|
|
45
|
+
- **misc**: Refactor the sidebar to fix first render state, closes [#1301](https://github.com/lobehub/lobe-chat/issues/1301) ([c477491](https://github.com/lobehub/lobe-chat/commit/c477491))
|
|
46
|
+
|
|
47
|
+
</details>
|
|
48
|
+
|
|
49
|
+
<div align="right">
|
|
50
|
+
|
|
51
|
+
[](#readme-top)
|
|
52
|
+
|
|
53
|
+
</div>
|
|
54
|
+
|
|
5
55
|
### [Version 0.127.1](https://github.com/lobehub/lobe-chat/compare/v0.127.0...v0.127.1)
|
|
6
56
|
|
|
7
57
|
<sup>Released on **2024-02-14**</sup>
|
package/README.md
CHANGED
|
@@ -225,14 +225,14 @@ Our marketplace is not just a showcase platform but also a collaborative space.
|
|
|
225
225
|
|
|
226
226
|
<!-- AGENT LIST -->
|
|
227
227
|
|
|
228
|
-
| Recent Submits
|
|
229
|
-
|
|
|
230
|
-
| [Product
|
|
231
|
-
| [
|
|
232
|
-
| [
|
|
233
|
-
| [
|
|
234
|
-
|
|
235
|
-
> 📊 Total agents: [<kbd>**
|
|
228
|
+
| Recent Submits | Description |
|
|
229
|
+
| --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
230
|
+
| [Product Copywriting](https://chat-preview.lobehub.com/market?agent=copywriting)<br/><sup>By **[pllz7](https://github.com/pllz7)** on **2024-02-14**</sup> | Expert in persuasive copywriting and consumer psychology<br/>`ecommerce` |
|
|
231
|
+
| [Private Domain Operations Expert](https://chat-preview.lobehub.com/market?agent=gl-syyy)<br/><sup>By **[guling-io](https://github.com/guling-io)** on **2024-02-14**</sup> | Specializing in private domain operations, traffic acquisition, user retention, conversion, and content planning. Familiar with marketing theories and related classic works.<br/>`private-domain-operations` `traffic-acquisition` `user-retention` `conversion` `content-planning` |
|
|
232
|
+
| [Self-Media Operation Expert](https://chat-preview.lobehub.com/market?agent=gl-zmtyy)<br/><sup>By **[guling-io](https://github.com/guling-io)** on **2024-02-14**</sup> | Specialized in self-media operation and content creation<br/>`self-media-operation` `social-media` `content-creation` `fan-growth` `brand-promotion` |
|
|
233
|
+
| [Product Description](https://chat-preview.lobehub.com/market?agent=product-description)<br/><sup>By **[pllz7](https://github.com/pllz7)** on **2024-02-14**</sup> | Craft compelling product descriptions that boost e-commerce sales<br/>`ecommerce` |
|
|
234
|
+
|
|
235
|
+
> 📊 Total agents: [<kbd>**177**</kbd> ](https://github.com/lobehub/lobe-chat-agents)
|
|
236
236
|
|
|
237
237
|
<!-- AGENT LIST -->
|
|
238
238
|
|
package/README.zh-CN.md
CHANGED
|
@@ -205,14 +205,14 @@ LobeChat 的插件生态系统是其核心功能的重要扩展,它极大地
|
|
|
205
205
|
|
|
206
206
|
<!-- AGENT LIST -->
|
|
207
207
|
|
|
208
|
-
| 最近新增
|
|
209
|
-
|
|
|
210
|
-
| [
|
|
211
|
-
| [
|
|
212
|
-
| [
|
|
213
|
-
| [
|
|
214
|
-
|
|
215
|
-
> 📊 Total agents: [<kbd>**
|
|
208
|
+
| 最近新增 | 助手说明 |
|
|
209
|
+
| ---------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
|
|
210
|
+
| [产品文案撰写](https://chat-preview.lobehub.com/market?agent=copywriting)<br/><sup>By **[pllz7](https://github.com/pllz7)** on **2024-02-14**</sup> | 精通有说服力的文案撰写和消费者心理学<br/>`电子商务` |
|
|
211
|
+
| [私域运营专家](https://chat-preview.lobehub.com/market?agent=gl-syyy)<br/><sup>By **[guling-io](https://github.com/guling-io)** on **2024-02-14**</sup> | 擅长私域运营、引流、承接、转化和内容策划,熟悉营销理论和相关经典著作。<br/>`私域运营` `引流` `承接` `转化` `内容策划` |
|
|
212
|
+
| [自媒体运营专家](https://chat-preview.lobehub.com/market?agent=gl-zmtyy)<br/><sup>By **[guling-io](https://github.com/guling-io)** on **2024-02-14**</sup> | 擅长自媒体运营与内容创作<br/>`自媒体运营` `社交媒体` `内容创作` `粉丝增长` `品牌推广` |
|
|
213
|
+
| [产品描述](https://chat-preview.lobehub.com/market?agent=product-description)<br/><sup>By **[pllz7](https://github.com/pllz7)** on **2024-02-14**</sup> | 打造引人入胜的产品描述,提升电子商务销售业绩<br/>`电子商务` |
|
|
214
|
+
|
|
215
|
+
> 📊 Total agents: [<kbd>**177**</kbd> ](https://github.com/lobehub/lobe-chat-agents)
|
|
216
216
|
|
|
217
217
|
<!-- AGENT LIST -->
|
|
218
218
|
|
|
@@ -52,6 +52,32 @@ LobeChat provides additional configuration options during deployment, which can
|
|
|
52
52
|
- Default: `-`
|
|
53
53
|
- Example: `/test`
|
|
54
54
|
|
|
55
|
+
#### `DEFAULT_AGENT_CONFIG`
|
|
56
|
+
|
|
57
|
+
- Type: Optional
|
|
58
|
+
- Description: Used to configure the default configuration of the LobeChat default assistant. It supports various data types and structures, including key-value pairs, nested fields, array values, etc.
|
|
59
|
+
- Default Value: `-`
|
|
60
|
+
- Example: `'model=gpt-4-1106-preview;params.max_tokens=300;plugins=search-engine,lobe-image-designer`
|
|
61
|
+
|
|
62
|
+
`DEFAULT_AGENT_CONFIG` is used to configure the default configuration of the LobeChat default agent. It supports various data types and structures, including key-value pairs, nested fields, array values, etc. The table below provides detailed explanations of the configuration options, examples, and corresponding explanations for the `DEFAULT_AGENT_CONFIG` environment variable:
|
|
63
|
+
|
|
64
|
+
| Configuration Type | Example | Explanation |
|
|
65
|
+
| ----------------------- | -------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
|
|
66
|
+
| Basic Key-Value Pair | `model=gpt-4` | Set the model to `gpt-4`. |
|
|
67
|
+
| Nested Field | `tts.sttLocale=en-US` | Set the language region for the text-to-speech service to `en-US`. |
|
|
68
|
+
| Array | `plugins=search-engine,lobe-image-designer` | Enable the `search-engine` and `lobe-image-designer` plugins. |
|
|
69
|
+
| Chinese Comma | `plugins=search-engine,lobe-image-designer` | The same as above, demonstrating support for Chinese comma separation. |
|
|
70
|
+
| Multiple Configurations | `model=glm-4;provider=zhipu` | Set the model to `glm-4` and the model provider to `zhipu`. |
|
|
71
|
+
| Numeric Value | `params.max_tokens=300` | Set the maximum number of tokens to `300`. |
|
|
72
|
+
| Boolean Value | `enableAutoCreateTopic=true` | Enable automatic topic creation. |
|
|
73
|
+
| Special Characters | `inputTemplate="Hello; I am a bot;"` | Set the input template to `Hello; I am a bot;`. |
|
|
74
|
+
| Error Handling | `model=gpt-4;maxToken` | Ignore the invalid entry `maxToken` and only parse out `model=gpt-4`. |
|
|
75
|
+
| Value Overriding | `model=gpt-4;model=gpt-4-1106-preview` | If the key is duplicated, use the value that appears last, in this case, the value of `model` is `gpt-4-1106-preview`. |
|
|
76
|
+
|
|
77
|
+
Related discussions:
|
|
78
|
+
|
|
79
|
+
- [\[RFC\] 022 - Default Helper Parameters for Environment Variable Configuration](https://github.com/lobehub/lobe-chat/discussions/913)
|
|
80
|
+
|
|
55
81
|
## Authentication Service Providers
|
|
56
82
|
|
|
57
83
|
### Common Settings
|
|
@@ -52,6 +52,32 @@ LobeChat 在部署时提供了一些额外的配置项,使用环境变量进
|
|
|
52
52
|
- 默认值: `-`
|
|
53
53
|
- 示例: `/test`
|
|
54
54
|
|
|
55
|
+
#### `DEFAULT_AGENT_CONFIG`
|
|
56
|
+
|
|
57
|
+
- 类型:可选
|
|
58
|
+
- 描述:用于配置 LobeChat 默认助理的默认配置。它支持多种数据类型和结构,包括键值对、嵌套字段、数组值等。
|
|
59
|
+
- 默认值:`-`
|
|
60
|
+
- 示例:`'model=gpt-4-1106-preview;params.max_tokens=300;plugins=search-engine,lobe-image-designer`
|
|
61
|
+
|
|
62
|
+
`DEFAULT_AGENT_CONFIG` 用于配置 LobeChat 默认助理的默认配置。它支持多种数据类型和结构,包括键值对、嵌套字段、数组值等。下表详细说明了 `DEFAULT_AGENT_CONFIG` 环境变量的配置项、示例以及相应解释:
|
|
63
|
+
|
|
64
|
+
| 配置项类型 | 示例 | 解释 |
|
|
65
|
+
| ---------- | -------------------------------------------- | ---------------------------------------------------------------------------- |
|
|
66
|
+
| 基本键值对 | `model=gpt-4` | 设置模型为 `gpt-4`。 |
|
|
67
|
+
| 嵌套字段 | `tts.sttLocale=en-US` | 设置文本到语音服务的语言区域为 `en-US`。 |
|
|
68
|
+
| 数组 | `plugins=search-engine,lobe-image-designer` | 启用 `search-engine` 和 `lobe-image-designer` 插件。 |
|
|
69
|
+
| 中文逗号 | `plugins=search-engine,lobe-image-designer` | 同上,演示支持中文逗号分隔。 |
|
|
70
|
+
| 多个配置项 | `model=glm-4;provider=zhipu` | 设置模型为 `glm-4` 且模型服务商为 `zhipu`。 |
|
|
71
|
+
| 数字值 | `params.max_tokens=300` | 设置最大令牌数为 `300`。 |
|
|
72
|
+
| 布尔值 | `enableAutoCreateTopic=true` | 启用自动创建主题。 |
|
|
73
|
+
| 特殊字符 | `inputTemplate="Hello; I am a bot;"` | 设置输入模板为 `Hello; I am a bot;`。 |
|
|
74
|
+
| 错误处理 | `model=gpt-4;maxToken` | 忽略无效条目 `maxToken`,仅解析出 `model=gpt-4`。 |
|
|
75
|
+
| 值覆盖 | `model=gpt-4;model=gpt-4-1106-preview` | 如果键重复,使用最后一次出现的值,此处 `model` 的值为 `gpt-4-1106-preview`。 |
|
|
76
|
+
|
|
77
|
+
相关阅读:
|
|
78
|
+
|
|
79
|
+
- [\[RFC\] 022 - 环境变量配置默认助手参数](https://github.com/lobehub/lobe-chat/discussions/913)
|
|
80
|
+
|
|
55
81
|
## 身份验证服务
|
|
56
82
|
|
|
57
83
|
### 通用设置
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lobehub/chat",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.128.0",
|
|
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",
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { parseAgentConfig } from './parseDefaultAgent';
|
|
4
|
+
|
|
5
|
+
describe('parseAgentConfig', () => {
|
|
6
|
+
describe('single functional feature', () => {
|
|
7
|
+
it('parses single key-value pair correctly', () => {
|
|
8
|
+
const envStr = 'model=gpt-4';
|
|
9
|
+
const expected = { model: 'gpt-4' };
|
|
10
|
+
expect(parseAgentConfig(envStr)).toEqual(expected);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('parses nested fields correctly', () => {
|
|
14
|
+
const envStr = 'tts.sttLocale=en-US';
|
|
15
|
+
const expected = { tts: { sttLocale: 'en-US' } };
|
|
16
|
+
expect(parseAgentConfig(envStr)).toEqual(expected);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('parses array values with commas correctly', () => {
|
|
20
|
+
const envStr = 'plugins=search-engine,lobe-image-designer';
|
|
21
|
+
const expected = { plugins: ['search-engine', 'lobe-image-designer'] };
|
|
22
|
+
expect(parseAgentConfig(envStr)).toEqual(expected);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('parses array values with Chinese commas correctly', () => {
|
|
26
|
+
const envStr = 'plugins=search-engine,lobe-image-designer';
|
|
27
|
+
const expected = { plugins: ['search-engine', 'lobe-image-designer'] };
|
|
28
|
+
expect(parseAgentConfig(envStr)).toEqual(expected);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('parses multiple key-value pairs correctly', () => {
|
|
32
|
+
const envStr = 'model=gpt-4;version=1.0.0';
|
|
33
|
+
const expected = { model: 'gpt-4', version: '1.0.0' };
|
|
34
|
+
expect(parseAgentConfig(envStr)).toEqual(expected);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// 测试数字值是否被正确解析为数字
|
|
38
|
+
it('parses numerical values correctly', () => {
|
|
39
|
+
const envStr = 'params.max_tokens=300';
|
|
40
|
+
const expected = { params: { max_tokens: 300 } };
|
|
41
|
+
expect(parseAgentConfig(envStr)).toEqual(expected);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// 测试多级嵌套属性是否被正确解析
|
|
45
|
+
it('parses deeply nested fields correctly', () => {
|
|
46
|
+
const envStr = 'tts.voice.openai=english-voice';
|
|
47
|
+
const expected = { tts: { voice: { openai: 'english-voice' } } };
|
|
48
|
+
expect(parseAgentConfig(envStr)).toEqual(expected);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('parses boolean values correctly', () => {
|
|
52
|
+
const envStr = 'enableAutoCreateTopic=true;enableCompressThreshold=false';
|
|
53
|
+
const expected = {
|
|
54
|
+
enableAutoCreateTopic: true,
|
|
55
|
+
enableCompressThreshold: false,
|
|
56
|
+
};
|
|
57
|
+
expect(parseAgentConfig(envStr)).toEqual(expected);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('parses fewShots array with cascading keys correctly', () => {
|
|
61
|
+
const envStr =
|
|
62
|
+
'fewShots.0.content=Hello;fewShots.0.role=user;fewShots.1.content=Hi;fewShots.1.role=system';
|
|
63
|
+
const expected = {
|
|
64
|
+
fewShots: [
|
|
65
|
+
{ content: 'Hello', role: 'user' },
|
|
66
|
+
{ content: 'Hi', role: 'system' },
|
|
67
|
+
],
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// Assuming parseAgentConfig function has been implemented to understand and correctly parse the cascading keys format for fewShots
|
|
71
|
+
expect(parseAgentConfig(envStr)).toEqual(expected);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('parses tts voice configuration correctly', () => {
|
|
75
|
+
const envStr = 'tts.voice.openai=english-voice;tts.voice.microsoft=spanish-voice';
|
|
76
|
+
const expected = {
|
|
77
|
+
tts: { voice: { openai: 'english-voice', microsoft: 'spanish-voice' } },
|
|
78
|
+
};
|
|
79
|
+
expect(parseAgentConfig(envStr)).toEqual(expected);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('parses inputTemplate with special characters correctly', () => {
|
|
83
|
+
const envStr = 'inputTemplate="Hello, I am {name}"';
|
|
84
|
+
const expected = { inputTemplate: 'Hello, I am {name}' };
|
|
85
|
+
expect(parseAgentConfig(envStr)).toEqual(expected);
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe('complex environment', () => {
|
|
90
|
+
it.skip('parses a complete environment variable string correctly', () => {
|
|
91
|
+
const envStr =
|
|
92
|
+
'model=gpt-4-1106-preview;params.max_tokens=300;plugins=search-engine,lobe-image-designer';
|
|
93
|
+
const expected = {
|
|
94
|
+
model: 'gpt-4-1106-preview',
|
|
95
|
+
params: { max_tokens: 300 },
|
|
96
|
+
plugins: ['search-engine', 'lobe-image-designer'],
|
|
97
|
+
};
|
|
98
|
+
expect(parseAgentConfig(envStr)).toEqual(expected);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// 测试当配置字符串包含所有可能字段时的行为
|
|
102
|
+
it('parses a complex environment variable string correctly', () => {
|
|
103
|
+
const envStr =
|
|
104
|
+
'model=gpt-4-1106-preview;params.max_tokens=300;params.temperature=0.7;plugins=search-engine,lobe-image-designer;tts.voice.openai=english-voice';
|
|
105
|
+
const expected = {
|
|
106
|
+
model: 'gpt-4-1106-preview',
|
|
107
|
+
params: { max_tokens: 300, temperature: 0.7 },
|
|
108
|
+
plugins: ['search-engine', 'lobe-image-designer'],
|
|
109
|
+
tts: { voice: { openai: 'english-voice' } },
|
|
110
|
+
};
|
|
111
|
+
expect(parseAgentConfig(envStr)).toEqual(expected);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
describe('Error Boundary', () => {
|
|
116
|
+
it('handles empty string input', () => {
|
|
117
|
+
const envStr = '';
|
|
118
|
+
const expected = {};
|
|
119
|
+
expect(parseAgentConfig(envStr)).toEqual(expected);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('ignores entries without an equal sign', () => {
|
|
123
|
+
const envStr = 'model=gpt-4;invalidentry';
|
|
124
|
+
const expected = { model: 'gpt-4' };
|
|
125
|
+
expect(parseAgentConfig(envStr)).toEqual(expected);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('handles entries with missing value', () => {
|
|
129
|
+
const envStr = 'model=gpt-4;version=';
|
|
130
|
+
const expected = { model: 'gpt-4', version: undefined };
|
|
131
|
+
expect(parseAgentConfig(envStr)).toEqual(expected);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('handles entries with missing key', () => {
|
|
135
|
+
const envStr = '=gpt-4;version=1.0.0';
|
|
136
|
+
const expected = { version: '1.0.0' }; // Assuming the parser ignores entries with no key
|
|
137
|
+
expect(parseAgentConfig(envStr)).toEqual(expected);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('handles multiple consecutive semicolons', () => {
|
|
141
|
+
const envStr = 'model=gpt-4;;version=1.0.0';
|
|
142
|
+
const expected = { model: 'gpt-4', version: '1.0.0' };
|
|
143
|
+
expect(parseAgentConfig(envStr)).toEqual(expected);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
// 测试键重复时的覆盖行为
|
|
147
|
+
it('overrides duplicate keys with the last occurrence', () => {
|
|
148
|
+
const envStr = 'model=gpt-4;model=gpt-4-1106-preview';
|
|
149
|
+
const expected = { model: 'gpt-4-1106-preview' };
|
|
150
|
+
expect(parseAgentConfig(envStr)).toEqual(expected);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// 测试未提供的数组值是否返回空数组
|
|
154
|
+
it('parses missing array values as undefined', () => {
|
|
155
|
+
const envStr = 'plugins=';
|
|
156
|
+
const expected = {};
|
|
157
|
+
expect(parseAgentConfig(envStr)).toEqual(expected);
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
// 测试值中包含分号的情况
|
|
162
|
+
it('handles values with semicolons correctly', () => {
|
|
163
|
+
const envStr = 'inputTemplate="Hello; I am a bot;"';
|
|
164
|
+
const expected = { inputTemplate: 'Hello; I am a bot;' };
|
|
165
|
+
expect(parseAgentConfig(envStr)).toEqual(expected);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// 测试值中包含等号的情况
|
|
169
|
+
it('handles values with equals signs correctly', () => {
|
|
170
|
+
const envStr = 'inputTemplate="Hello=world"';
|
|
171
|
+
const expected = { inputTemplate: 'Hello=world' };
|
|
172
|
+
expect(parseAgentConfig(envStr)).toEqual(expected);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
// 测试空值是否返回undefined或空字符串
|
|
176
|
+
it('parses empty values as undefined or empty string', () => {
|
|
177
|
+
const envStr = 'model=';
|
|
178
|
+
const expected = { model: undefined }; // 或 { model: '' },取决于应用逻辑
|
|
179
|
+
expect(parseAgentConfig(envStr)).toEqual(expected);
|
|
180
|
+
});
|
|
181
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { set } from 'lodash-es';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Improved parsing function that handles numbers, booleans, semicolons, and equals signs in values.
|
|
5
|
+
* @param {string} envStr - The environment variable string to be parsed.
|
|
6
|
+
*/
|
|
7
|
+
export const parseAgentConfig = (envStr: string) => {
|
|
8
|
+
const config = {};
|
|
9
|
+
// use regex to match key-value pairs, considering the possibility of semicolons in values
|
|
10
|
+
const regex = /([^;=]+)=("[^"]+"|[^;]+)/g;
|
|
11
|
+
let match;
|
|
12
|
+
|
|
13
|
+
while ((match = regex.exec(envStr)) !== null) {
|
|
14
|
+
const key = match[1].trim();
|
|
15
|
+
let value = match[2].trim();
|
|
16
|
+
if (!key || !value) return;
|
|
17
|
+
|
|
18
|
+
let finalValue: any = value;
|
|
19
|
+
|
|
20
|
+
// Handle string value
|
|
21
|
+
if (value.startsWith('"') && value.endsWith('"')) {
|
|
22
|
+
finalValue = value.slice(1, -1);
|
|
23
|
+
}
|
|
24
|
+
// Handle numeric values
|
|
25
|
+
else if (!isNaN(value as any)) {
|
|
26
|
+
finalValue = Number(value);
|
|
27
|
+
}
|
|
28
|
+
// Handle boolean values
|
|
29
|
+
else if (value.toLowerCase() === 'true' || value.toLowerCase() === 'false') {
|
|
30
|
+
finalValue = value.toLowerCase() === 'true';
|
|
31
|
+
}
|
|
32
|
+
// Handle arrays
|
|
33
|
+
else if (value.includes(',') || value.includes(',')) {
|
|
34
|
+
const array = value.replaceAll(',', ',').split(',');
|
|
35
|
+
finalValue = array.map((item) => (isNaN(item as any) ? item : Number(item)));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
set(config, key, finalValue);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return config;
|
|
42
|
+
};
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { getServerConfig } from '@/config/server';
|
|
2
2
|
import { GlobalServerConfig } from '@/types/settings';
|
|
3
3
|
|
|
4
|
+
import { parseAgentConfig } from './parseDefaultAgent';
|
|
5
|
+
|
|
4
6
|
export const runtime = 'edge';
|
|
5
7
|
|
|
6
8
|
/**
|
|
@@ -15,10 +17,14 @@ export const GET = async () => {
|
|
|
15
17
|
ENABLED_GOOGLE,
|
|
16
18
|
ENABLE_OAUTH_SSO,
|
|
17
19
|
ENABLE_OLLAMA,
|
|
20
|
+
DEFAULT_AGENT_CONFIG,
|
|
18
21
|
} = getServerConfig();
|
|
19
22
|
|
|
20
23
|
const config: GlobalServerConfig = {
|
|
21
24
|
customModelName: CUSTOM_MODELS,
|
|
25
|
+
defaultAgent: {
|
|
26
|
+
config: parseAgentConfig(DEFAULT_AGENT_CONFIG),
|
|
27
|
+
},
|
|
22
28
|
enabledOAuthSSO: ENABLE_OAUTH_SSO,
|
|
23
29
|
languageModel: {
|
|
24
30
|
bedrock: { enabled: ENABLED_AWS_BEDROCK },
|
|
@@ -4,15 +4,13 @@ import { PropsWithChildren, memo } from 'react';
|
|
|
4
4
|
import { Flexbox } from 'react-layout-kit';
|
|
5
5
|
|
|
6
6
|
import AppLayoutDesktop from '@/layout/AppLayout.desktop';
|
|
7
|
-
import { useSwitchSideBarOnInit } from '@/store/global';
|
|
8
7
|
import { SidebarTabKey } from '@/store/global/initialState';
|
|
9
8
|
|
|
10
9
|
import ResponsiveSessionList from './features/SessionList';
|
|
11
10
|
|
|
12
11
|
export default memo(({ children }: PropsWithChildren) => {
|
|
13
|
-
useSwitchSideBarOnInit(SidebarTabKey.Chat);
|
|
14
12
|
return (
|
|
15
|
-
<AppLayoutDesktop>
|
|
13
|
+
<AppLayoutDesktop sidebarKey={SidebarTabKey.Chat}>
|
|
16
14
|
<ResponsiveSessionList />
|
|
17
15
|
<Flexbox
|
|
18
16
|
flex={1}
|
|
@@ -4,16 +4,13 @@ import { useRouter } from 'next/navigation';
|
|
|
4
4
|
import { memo, useEffect } from 'react';
|
|
5
5
|
|
|
6
6
|
import AppLayoutMobile from '@/layout/AppLayout.mobile';
|
|
7
|
-
import {
|
|
8
|
-
import { SidebarTabKey } from '@/store/global/slices/common/initialState';
|
|
7
|
+
import { SidebarTabKey } from '@/store/global/initialState';
|
|
9
8
|
|
|
10
9
|
import PageTitle from '../features/PageTitle';
|
|
11
10
|
import SessionHeader from './features/SessionHeader';
|
|
12
11
|
import SessionList from './features/SessionList';
|
|
13
12
|
|
|
14
13
|
const ChatMobilePage = memo(() => {
|
|
15
|
-
useSwitchSideBarOnInit(SidebarTabKey.Chat);
|
|
16
|
-
|
|
17
14
|
const router = useRouter();
|
|
18
15
|
useEffect(() => {
|
|
19
16
|
router.prefetch('/chat/mobile');
|
|
@@ -21,7 +18,7 @@ const ChatMobilePage = memo(() => {
|
|
|
21
18
|
}, []);
|
|
22
19
|
|
|
23
20
|
return (
|
|
24
|
-
<AppLayoutMobile navBar={<SessionHeader />} showTabBar>
|
|
21
|
+
<AppLayoutMobile navBar={<SessionHeader />} showTabBar tabBarKey={SidebarTabKey.Chat}>
|
|
25
22
|
<PageTitle />
|
|
26
23
|
<SessionList />
|
|
27
24
|
</AppLayoutMobile>
|
|
@@ -6,7 +6,7 @@ import { useTranslation } from 'react-i18next';
|
|
|
6
6
|
|
|
7
7
|
import { DESKTOP_HEADER_ICON_SIZE, MOBILE_HEADER_ICON_SIZE } from '@/const/layoutTokens';
|
|
8
8
|
import { useGlobalStore } from '@/store/global';
|
|
9
|
-
import { SidebarTabKey } from '@/store/global/
|
|
9
|
+
import { SidebarTabKey } from '@/store/global/initialState';
|
|
10
10
|
import { useSessionStore } from '@/store/session';
|
|
11
11
|
import { sessionSelectors } from '@/store/session/selectors';
|
|
12
12
|
import { pathString } from '@/utils/url';
|
|
@@ -5,8 +5,6 @@ import { memo, useEffect } from 'react';
|
|
|
5
5
|
|
|
6
6
|
import { messageService } from '@/services/message';
|
|
7
7
|
import { sessionService } from '@/services/session';
|
|
8
|
-
import { useGlobalStore } from '@/store/global';
|
|
9
|
-
import { SidebarTabKey } from '@/store/global/initialState';
|
|
10
8
|
import { useSessionStore } from '@/store/session';
|
|
11
9
|
|
|
12
10
|
const checkHasConversation = async () => {
|
|
@@ -21,8 +19,6 @@ const Redirect = memo(() => {
|
|
|
21
19
|
|
|
22
20
|
useEffect(() => {
|
|
23
21
|
checkHasConversation().then((hasData) => {
|
|
24
|
-
useGlobalStore.getState().switchSideBar(SidebarTabKey.Chat);
|
|
25
|
-
|
|
26
22
|
if (hasData) {
|
|
27
23
|
router.replace('/chat');
|
|
28
24
|
|
|
@@ -7,7 +7,6 @@ import { Center, Flexbox } from 'react-layout-kit';
|
|
|
7
7
|
import SafeSpacing from '@/components/SafeSpacing';
|
|
8
8
|
import { MAX_WIDTH } from '@/const/layoutTokens';
|
|
9
9
|
import AppLayoutDesktop from '@/layout/AppLayout.desktop';
|
|
10
|
-
import { useSwitchSideBarOnInit } from '@/store/global';
|
|
11
10
|
import { SidebarTabKey } from '@/store/global/initialState';
|
|
12
11
|
|
|
13
12
|
import Header from './features/Header';
|
|
@@ -30,10 +29,8 @@ const useStyles = createStyles(({ css }) => ({
|
|
|
30
29
|
const MarketLayout = memo<PropsWithChildren>(({ children }) => {
|
|
31
30
|
const { theme, styles } = useStyles();
|
|
32
31
|
|
|
33
|
-
useSwitchSideBarOnInit(SidebarTabKey.Market);
|
|
34
|
-
|
|
35
32
|
return (
|
|
36
|
-
<AppLayoutDesktop>
|
|
33
|
+
<AppLayoutDesktop sidebarKey={SidebarTabKey.Market}>
|
|
37
34
|
<Flexbox
|
|
38
35
|
flex={1}
|
|
39
36
|
height={'100%'}
|
|
@@ -5,7 +5,6 @@ import { PropsWithChildren } from 'react';
|
|
|
5
5
|
import { Flexbox } from 'react-layout-kit';
|
|
6
6
|
|
|
7
7
|
import AppLayoutMobile from '@/layout/AppLayout.mobile';
|
|
8
|
-
import { useSwitchSideBarOnInit } from '@/store/global';
|
|
9
8
|
import { SidebarTabKey } from '@/store/global/initialState';
|
|
10
9
|
|
|
11
10
|
import Header from './features/Header';
|
|
@@ -13,10 +12,8 @@ import Header from './features/Header';
|
|
|
13
12
|
const DetailModal = dynamic(() => import('./features/AgentDetail'));
|
|
14
13
|
|
|
15
14
|
const MarketLayout = ({ children }: PropsWithChildren) => {
|
|
16
|
-
useSwitchSideBarOnInit(SidebarTabKey.Market);
|
|
17
|
-
|
|
18
15
|
return (
|
|
19
|
-
<AppLayoutMobile navBar={<Header />} showTabBar>
|
|
16
|
+
<AppLayoutMobile navBar={<Header />} showTabBar tabBarKey={SidebarTabKey.Market}>
|
|
20
17
|
<Flexbox flex={1} gap={16} style={{ padding: 16 }}>
|
|
21
18
|
{children}
|
|
22
19
|
</Flexbox>
|
|
@@ -5,8 +5,6 @@ import { memo } from 'react';
|
|
|
5
5
|
import { useTranslation } from 'react-i18next';
|
|
6
6
|
import { Center } from 'react-layout-kit';
|
|
7
7
|
|
|
8
|
-
import { useGlobalStore } from '@/store/global';
|
|
9
|
-
import { SidebarTabKey } from '@/store/global/initialState';
|
|
10
8
|
import { agentMarketSelectors, useMarketStore } from '@/store/market';
|
|
11
9
|
import { useSessionStore } from '@/store/session';
|
|
12
10
|
|
|
@@ -18,7 +16,6 @@ const Header = memo(() => {
|
|
|
18
16
|
const { t } = useTranslation('market');
|
|
19
17
|
const { styles, theme } = useStyles();
|
|
20
18
|
const createSession = useSessionStore((s) => s.createSession);
|
|
21
|
-
const switchSideBar = useGlobalStore((s) => s.switchSideBar);
|
|
22
19
|
const agentItem = useMarketStore(agentMarketSelectors.currentAgentItem);
|
|
23
20
|
const { message } = App.useApp();
|
|
24
21
|
|
|
@@ -56,7 +53,6 @@ const Header = memo(() => {
|
|
|
56
53
|
if (!agentItem) return;
|
|
57
54
|
|
|
58
55
|
createSession({ config, meta });
|
|
59
|
-
switchSideBar(SidebarTabKey.Chat);
|
|
60
56
|
}}
|
|
61
57
|
type={'primary'}
|
|
62
58
|
>
|
|
@@ -5,7 +5,6 @@ import { Center, Flexbox } from 'react-layout-kit';
|
|
|
5
5
|
|
|
6
6
|
import SafeSpacing from '@/components/SafeSpacing';
|
|
7
7
|
import AppLayoutDesktop from '@/layout/AppLayout.desktop';
|
|
8
|
-
import { useSwitchSideBarOnInit } from '@/store/global';
|
|
9
8
|
import { SettingsTabs, SidebarTabKey } from '@/store/global/initialState';
|
|
10
9
|
|
|
11
10
|
import Header from './features/Header';
|
|
@@ -17,10 +16,8 @@ export interface DesktopLayoutProps {
|
|
|
17
16
|
}
|
|
18
17
|
|
|
19
18
|
const DesktopLayout = memo<DesktopLayoutProps>(({ children, activeTab }) => {
|
|
20
|
-
useSwitchSideBarOnInit(SidebarTabKey.Setting);
|
|
21
|
-
|
|
22
19
|
return (
|
|
23
|
-
<AppLayoutDesktop>
|
|
20
|
+
<AppLayoutDesktop sidebarKey={SidebarTabKey.Setting}>
|
|
24
21
|
<SideBar activeTab={activeTab} />
|
|
25
22
|
<Flexbox flex={1} height={'100%'} style={{ position: 'relative' }}>
|
|
26
23
|
<Header activeTab={activeTab} />
|
|
@@ -4,7 +4,6 @@ import { ReactNode, memo } from 'react';
|
|
|
4
4
|
import { Flexbox } from 'react-layout-kit';
|
|
5
5
|
|
|
6
6
|
import AppLayoutMobile from '@/layout/AppLayout.mobile';
|
|
7
|
-
import { useSwitchSideBarOnInit } from '@/store/global';
|
|
8
7
|
import { SettingsTabs, SidebarTabKey } from '@/store/global/initialState';
|
|
9
8
|
|
|
10
9
|
import Header from './features/Header';
|
|
@@ -14,9 +13,8 @@ export interface SettingLayoutProps {
|
|
|
14
13
|
children: ReactNode;
|
|
15
14
|
}
|
|
16
15
|
export default memo(({ children, activeTab }: SettingLayoutProps) => {
|
|
17
|
-
useSwitchSideBarOnInit(SidebarTabKey.Setting);
|
|
18
16
|
return (
|
|
19
|
-
<AppLayoutMobile navBar={<Header activeTab={activeTab} />}>
|
|
17
|
+
<AppLayoutMobile navBar={<Header activeTab={activeTab} />} tabBarKey={SidebarTabKey.Setting}>
|
|
20
18
|
<Flexbox style={{ overflow: 'auto' }}>{children}</Flexbox>
|
|
21
19
|
</AppLayoutMobile>
|
|
22
20
|
);
|
package/src/config/server/app.ts
CHANGED
|
@@ -14,6 +14,8 @@ declare global {
|
|
|
14
14
|
|
|
15
15
|
PLUGINS_INDEX_URL?: string;
|
|
16
16
|
PLUGIN_SETTINGS?: string;
|
|
17
|
+
|
|
18
|
+
DEFAULT_AGENT_CONFIG?: string;
|
|
17
19
|
}
|
|
18
20
|
}
|
|
19
21
|
}
|
|
@@ -32,6 +34,8 @@ export const getAppConfig = () => {
|
|
|
32
34
|
return {
|
|
33
35
|
ACCESS_CODES,
|
|
34
36
|
|
|
37
|
+
DEFAULT_AGENT_CONFIG: process.env.DEFAULT_AGENT_CONFIG || '',
|
|
38
|
+
|
|
35
39
|
SHOW_ACCESS_CODE_CONFIG: !!ACCESS_CODES.length,
|
|
36
40
|
|
|
37
41
|
METADATA_BASE_URL: process.env.METADATA_BASE_URL,
|
|
@@ -6,7 +6,6 @@ import { rgba } from 'polished';
|
|
|
6
6
|
import { memo, useMemo } from 'react';
|
|
7
7
|
import { useTranslation } from 'react-i18next';
|
|
8
8
|
|
|
9
|
-
import { useGlobalStore } from '@/store/global';
|
|
10
9
|
import { SidebarTabKey } from '@/store/global/initialState';
|
|
11
10
|
|
|
12
11
|
const useStyles = createStyles(({ css, token }) => ({
|
|
@@ -17,8 +16,12 @@ const useStyles = createStyles(({ css, token }) => ({
|
|
|
17
16
|
`,
|
|
18
17
|
}));
|
|
19
18
|
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
interface Props {
|
|
20
|
+
className?: string;
|
|
21
|
+
tabBarKey?: SidebarTabKey;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export default memo<Props>(({ className, tabBarKey }) => {
|
|
22
25
|
const { t } = useTranslation('common');
|
|
23
26
|
const { styles } = useStyles();
|
|
24
27
|
const router = useRouter();
|
|
@@ -30,7 +33,6 @@ export default memo<{ className?: string }>(({ className }) => {
|
|
|
30
33
|
),
|
|
31
34
|
key: SidebarTabKey.Chat,
|
|
32
35
|
onClick: () => {
|
|
33
|
-
setTab(SidebarTabKey.Chat);
|
|
34
36
|
router.push('/chat');
|
|
35
37
|
},
|
|
36
38
|
title: t('tab.chat'),
|
|
@@ -39,7 +41,6 @@ export default memo<{ className?: string }>(({ className }) => {
|
|
|
39
41
|
icon: (active) => <Icon className={active ? styles.active : undefined} icon={Bot} />,
|
|
40
42
|
key: SidebarTabKey.Market,
|
|
41
43
|
onClick: () => {
|
|
42
|
-
setTab(SidebarTabKey.Market);
|
|
43
44
|
router.push('/market');
|
|
44
45
|
},
|
|
45
46
|
title: t('tab.market'),
|
|
@@ -48,7 +49,6 @@ export default memo<{ className?: string }>(({ className }) => {
|
|
|
48
49
|
icon: (active) => <Icon className={active ? styles.active : undefined} icon={User} />,
|
|
49
50
|
key: SidebarTabKey.Setting,
|
|
50
51
|
onClick: () => {
|
|
51
|
-
setTab(SidebarTabKey.Setting);
|
|
52
52
|
router.push('/settings');
|
|
53
53
|
},
|
|
54
54
|
title: t('tab.setting'),
|
|
@@ -56,12 +56,5 @@ export default memo<{ className?: string }>(({ className }) => {
|
|
|
56
56
|
],
|
|
57
57
|
[t],
|
|
58
58
|
);
|
|
59
|
-
return
|
|
60
|
-
<MobileTabBar
|
|
61
|
-
activeKey={tab}
|
|
62
|
-
className={className}
|
|
63
|
-
items={items}
|
|
64
|
-
onChange={(key) => setTab(key as any)}
|
|
65
|
-
/>
|
|
66
|
-
);
|
|
59
|
+
return <MobileTabBar activeKey={tabBarKey} className={className} items={items} />;
|
|
67
60
|
});
|
|
@@ -23,11 +23,10 @@ import { GlobalStore, useGlobalStore } from '@/store/global';
|
|
|
23
23
|
import { SidebarTabKey } from '@/store/global/initialState';
|
|
24
24
|
|
|
25
25
|
export interface BottomActionProps {
|
|
26
|
-
|
|
27
|
-
tab: GlobalStore['sidebarKey'];
|
|
26
|
+
tab?: GlobalStore['sidebarKey'];
|
|
28
27
|
}
|
|
29
28
|
|
|
30
|
-
const BottomActions = memo<BottomActionProps>(({ tab
|
|
29
|
+
const BottomActions = memo<BottomActionProps>(({ tab }) => {
|
|
31
30
|
const router = useRouter();
|
|
32
31
|
const { t } = useTranslation('common');
|
|
33
32
|
|
|
@@ -113,10 +112,6 @@ const BottomActions = memo<BottomActionProps>(({ tab, setTab }) => {
|
|
|
113
112
|
</Flexbox>
|
|
114
113
|
),
|
|
115
114
|
onClick: () => {
|
|
116
|
-
setTab(SidebarTabKey.Setting);
|
|
117
|
-
useGlobalStore.setState({
|
|
118
|
-
sidebarKey: SidebarTabKey.Setting,
|
|
119
|
-
});
|
|
120
115
|
router.push('/settings/common');
|
|
121
116
|
},
|
|
122
117
|
},
|
|
@@ -9,11 +9,10 @@ import { SidebarTabKey } from '@/store/global/initialState';
|
|
|
9
9
|
import { useSessionStore } from '@/store/session';
|
|
10
10
|
|
|
11
11
|
export interface TopActionProps {
|
|
12
|
-
|
|
13
|
-
tab: GlobalStore['sidebarKey'];
|
|
12
|
+
tab?: GlobalStore['sidebarKey'];
|
|
14
13
|
}
|
|
15
14
|
|
|
16
|
-
const TopActions = memo<TopActionProps>(({ tab
|
|
15
|
+
const TopActions = memo<TopActionProps>(({ tab }) => {
|
|
17
16
|
const { t } = useTranslation('common');
|
|
18
17
|
const switchBackToChat = useGlobalStore((s) => s.switchBackToChat);
|
|
19
18
|
|
|
@@ -24,7 +23,6 @@ const TopActions = memo<TopActionProps>(({ tab, setTab }) => {
|
|
|
24
23
|
onClick={(e) => {
|
|
25
24
|
e.preventDefault();
|
|
26
25
|
switchBackToChat(useSessionStore.getState().activeId);
|
|
27
|
-
setTab(SidebarTabKey.Chat);
|
|
28
26
|
}}
|
|
29
27
|
>
|
|
30
28
|
<ActionIcon
|
|
@@ -39,9 +37,6 @@ const TopActions = memo<TopActionProps>(({ tab, setTab }) => {
|
|
|
39
37
|
<ActionIcon
|
|
40
38
|
active={tab === SidebarTabKey.Market}
|
|
41
39
|
icon={Compass}
|
|
42
|
-
onClick={() => {
|
|
43
|
-
setTab(SidebarTabKey.Market);
|
|
44
|
-
}}
|
|
45
40
|
placement={'right'}
|
|
46
41
|
size="large"
|
|
47
42
|
title={t('tab.market')}
|
|
@@ -2,20 +2,22 @@ import { SideNav } from '@lobehub/ui';
|
|
|
2
2
|
import { memo } from 'react';
|
|
3
3
|
|
|
4
4
|
import AvatarWithUpload from '@/features/AvatarWithUpload';
|
|
5
|
-
import {
|
|
5
|
+
import { SidebarTabKey } from '@/store/global/initialState';
|
|
6
6
|
|
|
7
7
|
import BottomActions from './BottomActions';
|
|
8
8
|
import TopActions from './TopActions';
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
interface Props {
|
|
11
|
+
sidebarKey?: SidebarTabKey;
|
|
12
|
+
}
|
|
12
13
|
|
|
14
|
+
export default memo<Props>(({ sidebarKey }) => {
|
|
13
15
|
return (
|
|
14
16
|
<SideNav
|
|
15
17
|
avatar={<AvatarWithUpload id={'avatar'} />}
|
|
16
|
-
bottomActions={<BottomActions
|
|
18
|
+
bottomActions={<BottomActions tab={sidebarKey} />}
|
|
17
19
|
style={{ height: '100%' }}
|
|
18
|
-
topActions={<TopActions
|
|
20
|
+
topActions={<TopActions tab={sidebarKey} />}
|
|
19
21
|
/>
|
|
20
22
|
);
|
|
21
23
|
});
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import { useTheme } from 'antd-style';
|
|
2
|
-
import {
|
|
2
|
+
import { ReactNode, memo } from 'react';
|
|
3
3
|
import { Flexbox } from 'react-layout-kit';
|
|
4
4
|
|
|
5
5
|
import SideBar from '@/features/SideBar';
|
|
6
6
|
import { useIsPWA } from '@/hooks/useIsPWA';
|
|
7
|
+
import { SidebarTabKey } from '@/store/global/initialState';
|
|
7
8
|
|
|
8
|
-
|
|
9
|
+
interface AppLayoutDesktopProps {
|
|
10
|
+
children: ReactNode;
|
|
11
|
+
sidebarKey?: SidebarTabKey;
|
|
12
|
+
}
|
|
13
|
+
const AppLayoutDesktop = memo<AppLayoutDesktopProps>(({ children, sidebarKey }) => {
|
|
9
14
|
const isPWA = useIsPWA();
|
|
10
15
|
const theme = useTheme();
|
|
11
16
|
|
|
@@ -16,7 +21,7 @@ const AppLayoutDesktop = memo<PropsWithChildren>(({ children }) => {
|
|
|
16
21
|
style={isPWA ? { borderTop: `1px solid ${theme.colorBorder}` } : {}}
|
|
17
22
|
width={'100%'}
|
|
18
23
|
>
|
|
19
|
-
<SideBar />
|
|
24
|
+
<SideBar sidebarKey={sidebarKey} />
|
|
20
25
|
{children}
|
|
21
26
|
</Flexbox>
|
|
22
27
|
);
|
|
@@ -5,6 +5,7 @@ import { CSSProperties, PropsWithChildren, ReactNode, memo } from 'react';
|
|
|
5
5
|
import { Flexbox } from 'react-layout-kit';
|
|
6
6
|
|
|
7
7
|
import SafeSpacing from '@/components/SafeSpacing';
|
|
8
|
+
import { SidebarTabKey } from '@/store/global/slices/common/initialState';
|
|
8
9
|
|
|
9
10
|
const MobileTabBar = dynamic(() => import('@/features/MobileTabBar'));
|
|
10
11
|
|
|
@@ -39,11 +40,12 @@ interface AppMobileLayoutProps extends PropsWithChildren {
|
|
|
39
40
|
navBar?: ReactNode;
|
|
40
41
|
showTabBar?: boolean;
|
|
41
42
|
style?: CSSProperties;
|
|
43
|
+
tabBarKey?: SidebarTabKey;
|
|
42
44
|
title?: MobileNavBarTitleProps;
|
|
43
45
|
}
|
|
44
46
|
|
|
45
47
|
const AppLayoutMobile = memo<AppMobileLayoutProps>(
|
|
46
|
-
({ children, showTabBar, navBar, style, className }) => {
|
|
48
|
+
({ children, showTabBar, tabBarKey, navBar, style, className }) => {
|
|
47
49
|
const { styles, cx } = useStyles();
|
|
48
50
|
|
|
49
51
|
return (
|
|
@@ -58,7 +60,7 @@ const AppLayoutMobile = memo<AppMobileLayoutProps>(
|
|
|
58
60
|
{showTabBar && (
|
|
59
61
|
<>
|
|
60
62
|
<SafeSpacing mobile position={'bottom'} />
|
|
61
|
-
<MobileTabBar className={styles.mobileTabBar} />
|
|
63
|
+
<MobileTabBar className={styles.mobileTabBar} tabBarKey={tabBarKey} />
|
|
62
64
|
</>
|
|
63
65
|
)}
|
|
64
66
|
</Flexbox>
|
|
@@ -38,16 +38,4 @@ describe('createCommonSlice', () => {
|
|
|
38
38
|
expect(router.push).toHaveBeenCalledWith('/chat?session=session-id');
|
|
39
39
|
});
|
|
40
40
|
});
|
|
41
|
-
|
|
42
|
-
describe('switchSideBar', () => {
|
|
43
|
-
it('should switch sidebar', () => {
|
|
44
|
-
const { result } = renderHook(() => useGlobalStore());
|
|
45
|
-
|
|
46
|
-
act(() => {
|
|
47
|
-
result.current.switchSideBar(SidebarTabKey.Market);
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
expect(result.current.sidebarKey).toEqual(SidebarTabKey.Market);
|
|
51
|
-
});
|
|
52
|
-
});
|
|
53
41
|
});
|
|
@@ -13,8 +13,6 @@ import type { GlobalServerConfig, GlobalSettings } from '@/types/settings';
|
|
|
13
13
|
import { merge } from '@/utils/merge';
|
|
14
14
|
import { setNamespace } from '@/utils/storeDebug';
|
|
15
15
|
|
|
16
|
-
import type { SidebarTabKey } from './initialState';
|
|
17
|
-
|
|
18
16
|
const n = setNamespace('common');
|
|
19
17
|
|
|
20
18
|
/**
|
|
@@ -23,7 +21,6 @@ const n = setNamespace('common');
|
|
|
23
21
|
export interface CommonAction {
|
|
24
22
|
refreshUserConfig: () => Promise<void>;
|
|
25
23
|
switchBackToChat: (sessionId?: string) => void;
|
|
26
|
-
switchSideBar: (key: SidebarTabKey) => void;
|
|
27
24
|
updateAvatar: (avatar: string) => Promise<void>;
|
|
28
25
|
useCheckLatestVersion: () => SWRResponse<string>;
|
|
29
26
|
useFetchServerConfig: () => SWRResponse;
|
|
@@ -44,9 +41,7 @@ export const createCommonSlice: StateCreator<
|
|
|
44
41
|
switchBackToChat: (sessionId) => {
|
|
45
42
|
get().router?.push(SESSION_CHAT_URL(sessionId || INBOX_SESSION_ID, get().isMobile));
|
|
46
43
|
},
|
|
47
|
-
|
|
48
|
-
set({ sidebarKey: key }, false, n('switchSideBar', key));
|
|
49
|
-
},
|
|
44
|
+
|
|
50
45
|
updateAvatar: async (avatar) => {
|
|
51
46
|
await userService.updateAvatar(avatar);
|
|
52
47
|
await get().refreshUserConfig();
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { SidebarTabKey } from '../initialState';
|
|
2
|
-
import { useGlobalStore } from '../store';
|
|
3
|
-
import { useOnFinishHydrationGlobal } from './useOnFinishHydrationGlobal';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* 切换侧边栏选项
|
|
7
|
-
* @desc 只会在应用初始化时(且水合后)执行一次
|
|
8
|
-
*/
|
|
9
|
-
export const useSwitchSideBarOnInit = (key: SidebarTabKey) => {
|
|
10
|
-
useOnFinishHydrationGlobal(() => {
|
|
11
|
-
useGlobalStore.getState().switchSideBar(key);
|
|
12
|
-
});
|
|
13
|
-
};
|