@lobehub/chat 1.1.0 → 1.3.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/CHANGELOG.md +50 -0
- package/locales/en_US/setting.json +78 -58
- package/locales/zh_CN/setting.json +36 -16
- package/package.json +1 -1
- package/src/features/AvatarWithUpload/index.tsx +27 -0
- package/src/features/FolderPanel/index.tsx +3 -3
- package/src/{pages/Sidebar.tsx → features/SideBar/index.tsx} +4 -5
- package/src/layout/index.tsx +18 -3
- package/src/locales/create.ts +13 -12
- package/src/locales/default/setting.ts +36 -16
- package/src/locales/options.ts +19 -0
- package/src/pages/chat/layout.tsx +2 -2
- package/src/pages/setting/SettingForm.tsx +266 -9
- package/src/pages/setting/SliderWithInput/index.tsx +34 -0
- package/src/pages/setting/ThemeSwatches/ThemeSwatchesNeutral.tsx +33 -0
- package/src/pages/setting/ThemeSwatches/ThemeSwatchesPrimary.tsx +33 -0
- package/src/pages/setting/ThemeSwatches/index.ts +2 -0
- package/src/pages/setting/index.page.tsx +4 -4
- package/src/store/settings/action.ts +16 -7
- package/src/store/settings/initialState.ts +21 -5
- package/src/store/settings/selectors.ts +9 -1
- package/src/store/settings/store.ts +2 -2
- package/src/styles/antdOverride.ts +15 -11
- package/src/types/exportConfig.ts +25 -4
- package/src/types/i18next.d.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,56 @@
|
|
|
2
2
|
|
|
3
3
|
# Changelog
|
|
4
4
|
|
|
5
|
+
## [Version 1.3.0](https://github.com/lobehub/lobe-chat/compare/v1.2.0...v1.3.0)
|
|
6
|
+
|
|
7
|
+
<sup>Released on **2023-07-18**</sup>
|
|
8
|
+
|
|
9
|
+
#### ✨ Features
|
|
10
|
+
|
|
11
|
+
- **misc**: Implement settings and configuration functionality.
|
|
12
|
+
|
|
13
|
+
<br/>
|
|
14
|
+
|
|
15
|
+
<details>
|
|
16
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
|
17
|
+
|
|
18
|
+
#### What's improved
|
|
19
|
+
|
|
20
|
+
- **misc**: Implement settings and configuration functionality ([496c2d9](https://github.com/lobehub/lobe-chat/commit/496c2d9))
|
|
21
|
+
|
|
22
|
+
</details>
|
|
23
|
+
|
|
24
|
+
<div align="right">
|
|
25
|
+
|
|
26
|
+
[](#readme-top)
|
|
27
|
+
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
## [Version 1.2.0](https://github.com/lobehub/lobe-chat/compare/v1.1.0...v1.2.0)
|
|
31
|
+
|
|
32
|
+
<sup>Released on **2023-07-18**</sup>
|
|
33
|
+
|
|
34
|
+
#### ✨ Features
|
|
35
|
+
|
|
36
|
+
- **misc**: Add new components, modify import statements, and update CSS styles.
|
|
37
|
+
|
|
38
|
+
<br/>
|
|
39
|
+
|
|
40
|
+
<details>
|
|
41
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
|
42
|
+
|
|
43
|
+
#### What's improved
|
|
44
|
+
|
|
45
|
+
- **misc**: Add new components, modify import statements, and update CSS styles ([9b261db](https://github.com/lobehub/lobe-chat/commit/9b261db))
|
|
46
|
+
|
|
47
|
+
</details>
|
|
48
|
+
|
|
49
|
+
<div align="right">
|
|
50
|
+
|
|
51
|
+
[](#readme-top)
|
|
52
|
+
|
|
53
|
+
</div>
|
|
54
|
+
|
|
5
55
|
## [Version 1.1.0](https://github.com/lobehub/lobe-chat/compare/v1.0.0...v1.1.0)
|
|
6
56
|
|
|
7
57
|
<sup>Released on **2023-07-18**</sup>
|
|
@@ -1,97 +1,117 @@
|
|
|
1
1
|
{
|
|
2
2
|
"danger": {
|
|
3
|
-
"reset": {
|
|
4
|
-
"title": "Reset All Settings",
|
|
5
|
-
"desc": "Reset all settings to default values",
|
|
6
|
-
"action": "Reset Now",
|
|
7
|
-
"confirm": "Confirm reset all settings?",
|
|
8
|
-
"currentVersion": "Current Version"
|
|
9
|
-
},
|
|
10
3
|
"clear": {
|
|
11
|
-
"title": "Clear All Data",
|
|
12
|
-
"desc": "Clear all chat and settings data",
|
|
13
4
|
"action": "Clear Now",
|
|
14
|
-
"confirm": "
|
|
5
|
+
"confirm": "Are you sure you want to clear all chat and settings data?",
|
|
6
|
+
"desc": "Clear all chat and settings data",
|
|
7
|
+
"title": "Clear All Data"
|
|
8
|
+
},
|
|
9
|
+
"reset": {
|
|
10
|
+
"action": "Reset Now",
|
|
11
|
+
"confirm": "Are you sure you want to reset all settings?",
|
|
12
|
+
"currentVersion": "Current Version",
|
|
13
|
+
"desc": "Reset all settings to default values",
|
|
14
|
+
"title": "Reset All Settings"
|
|
15
15
|
}
|
|
16
16
|
},
|
|
17
17
|
"header": "Settings",
|
|
18
18
|
"settingChat": {
|
|
19
|
-
"title": "Chat Settings",
|
|
20
|
-
"inputTemplate": {
|
|
21
|
-
"title": "User Input Template",
|
|
22
|
-
"desc": "The latest user message will be filled into this template"
|
|
23
|
-
},
|
|
24
19
|
"compressThreshold": {
|
|
25
|
-
"
|
|
26
|
-
"
|
|
20
|
+
"desc": "When the uncompressed chat history exceeds this value, it will be compressed",
|
|
21
|
+
"title": "Chat History Compression Threshold"
|
|
22
|
+
},
|
|
23
|
+
"enableCompressThreshold": {
|
|
24
|
+
"title": "Enable Chat History Compression Threshold"
|
|
25
|
+
},
|
|
26
|
+
"enableHistoryCount": {
|
|
27
|
+
"title": "Enable History Message Count Limit"
|
|
27
28
|
},
|
|
28
29
|
"historyCount": {
|
|
29
|
-
"
|
|
30
|
-
"
|
|
30
|
+
"desc": "Number of history messages to include in each request",
|
|
31
|
+
"title": "History Message Count"
|
|
31
32
|
},
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"
|
|
33
|
+
"inputTemplate": {
|
|
34
|
+
"desc": "The latest user message will be filled into this template",
|
|
35
|
+
"title": "User Input Preprocessing"
|
|
35
36
|
},
|
|
36
|
-
"
|
|
37
|
-
"title": "Send Key"
|
|
38
|
-
}
|
|
37
|
+
"title": "Chat Settings"
|
|
39
38
|
},
|
|
40
39
|
"settingModel": {
|
|
41
|
-
"
|
|
40
|
+
"enableMaxTokens": {
|
|
41
|
+
"title": "Enable Single Reply Limit"
|
|
42
|
+
},
|
|
43
|
+
"frequencyPenalty": {
|
|
44
|
+
"desc": "The higher the value, the more likely it is to reduce repeated words",
|
|
45
|
+
"title": "Frequency Penalty"
|
|
46
|
+
},
|
|
47
|
+
"maxTokens": {
|
|
48
|
+
"desc": "Maximum number of tokens used in a single interaction",
|
|
49
|
+
"title": "Single Reply Limit"
|
|
50
|
+
},
|
|
42
51
|
"model": {
|
|
52
|
+
"desc": "ChatGPT Model",
|
|
43
53
|
"title": "Model"
|
|
44
54
|
},
|
|
55
|
+
"presencePenalty": {
|
|
56
|
+
"desc": "The higher the value, the more likely it is to expand to new topics",
|
|
57
|
+
"title": "Topic Freshness"
|
|
58
|
+
},
|
|
45
59
|
"temperature": {
|
|
46
|
-
"
|
|
47
|
-
"
|
|
60
|
+
"desc": "The higher the value, the more random the reply",
|
|
61
|
+
"title": "Randomness"
|
|
48
62
|
},
|
|
63
|
+
"title": "Model Settings",
|
|
49
64
|
"topP": {
|
|
50
|
-
"
|
|
51
|
-
"
|
|
52
|
-
},
|
|
53
|
-
"presencePenalty": {
|
|
54
|
-
"title": "Topic Freshness (presence_penalty)",
|
|
55
|
-
"desc": "The higher the value, the more likely it is to expand to new topics"
|
|
56
|
-
},
|
|
57
|
-
"frequencyPenalty": {
|
|
58
|
-
"title": "Frequency Penalty (frequency_penalty)",
|
|
59
|
-
"desc": "The higher the value, the more likely it is to reduce repeated words"
|
|
65
|
+
"desc": "Similar to randomness, but do not change together with randomness",
|
|
66
|
+
"title": "Top-p Sampling"
|
|
60
67
|
}
|
|
61
68
|
},
|
|
62
69
|
"settingOpenAI": {
|
|
70
|
+
"endpoint": {
|
|
71
|
+
"desc": "Must include http(s)://, in addition to the default address",
|
|
72
|
+
"placeholder": "https://api.openai.com/v1",
|
|
73
|
+
"title": "API Proxy Endpoint"
|
|
74
|
+
},
|
|
63
75
|
"title": "OpenAI Settings",
|
|
64
76
|
"token": {
|
|
65
|
-
"
|
|
66
|
-
"
|
|
67
|
-
"
|
|
68
|
-
},
|
|
69
|
-
"endpoint": {
|
|
70
|
-
"title": "API Endpoint",
|
|
71
|
-
"desc": "In addition to the default address, it must include http(s)://"
|
|
77
|
+
"desc": "Use your own OpenAI Key",
|
|
78
|
+
"placeholder": "OpenAI API Key",
|
|
79
|
+
"title": "API Key"
|
|
72
80
|
}
|
|
73
81
|
},
|
|
74
82
|
"settingSystem": {
|
|
75
|
-
"title": "System Settings",
|
|
76
83
|
"accessCode": {
|
|
77
|
-
"
|
|
78
|
-
"
|
|
79
|
-
"
|
|
80
|
-
}
|
|
84
|
+
"desc": "Encryption access is enabled by the administrator",
|
|
85
|
+
"placeholder": "Please enter the access password",
|
|
86
|
+
"title": "Access Password"
|
|
87
|
+
},
|
|
88
|
+
"title": "System Settings"
|
|
81
89
|
},
|
|
82
90
|
"settingTheme": {
|
|
83
|
-
"title": "Theme Settings",
|
|
84
91
|
"avatar": {
|
|
85
|
-
"title": "Avatar"
|
|
86
|
-
"desc": "Supports URL / Base64 / Emoji"
|
|
92
|
+
"title": "Avatar"
|
|
87
93
|
},
|
|
88
94
|
"fontSize": {
|
|
89
|
-
"
|
|
90
|
-
"
|
|
95
|
+
"desc": "Font size of chat content",
|
|
96
|
+
"title": "Font Size"
|
|
91
97
|
},
|
|
92
98
|
"lang": {
|
|
93
|
-
"
|
|
94
|
-
|
|
95
|
-
|
|
99
|
+
"title": "Language Settings"
|
|
100
|
+
},
|
|
101
|
+
"neutralColor": {
|
|
102
|
+
"desc": "Custom grayscale for different color biases",
|
|
103
|
+
"title": "Neutral Color"
|
|
104
|
+
},
|
|
105
|
+
"primaryColor": {
|
|
106
|
+
"desc": "Custom theme color",
|
|
107
|
+
"title": "Theme Color"
|
|
108
|
+
},
|
|
109
|
+
"themeMode": {
|
|
110
|
+
"auto": "Auto",
|
|
111
|
+
"dark": "Dark",
|
|
112
|
+
"light": "Light",
|
|
113
|
+
"title": "Theme"
|
|
114
|
+
},
|
|
115
|
+
"title": "Theme Settings"
|
|
96
116
|
}
|
|
97
117
|
}
|
|
@@ -20,6 +20,12 @@
|
|
|
20
20
|
"desc": "当未压缩的历史消息超过该值时,将进行压缩",
|
|
21
21
|
"title": "历史消息长度压缩阈值"
|
|
22
22
|
},
|
|
23
|
+
"enableCompressThreshold": {
|
|
24
|
+
"title": "是否开启历史消息长度压缩阈值"
|
|
25
|
+
},
|
|
26
|
+
"enableHistoryCount": {
|
|
27
|
+
"title": "是否开启携带的历史消息数限制"
|
|
28
|
+
},
|
|
23
29
|
"historyCount": {
|
|
24
30
|
"desc": "每次请求携带的历史消息数",
|
|
25
31
|
"title": "附带历史消息数"
|
|
@@ -28,45 +34,47 @@
|
|
|
28
34
|
"desc": "用户最新的一条消息会填充到此模板",
|
|
29
35
|
"title": "用户输入预处理"
|
|
30
36
|
},
|
|
31
|
-
"maxTokens": {
|
|
32
|
-
"desc": "单次交互所用的最大 Token 数",
|
|
33
|
-
"title": "单次回复限制 (max_tokens)"
|
|
34
|
-
},
|
|
35
|
-
"sendKey": {
|
|
36
|
-
"title": "发送键"
|
|
37
|
-
},
|
|
38
37
|
"title": "聊天设置"
|
|
39
38
|
},
|
|
40
39
|
"settingModel": {
|
|
40
|
+
"enableMaxTokens": {
|
|
41
|
+
"title": "开启单次回复限制"
|
|
42
|
+
},
|
|
41
43
|
"frequencyPenalty": {
|
|
42
44
|
"desc": "值越大,越有可能降低重复字词",
|
|
43
|
-
"title": "频率惩罚度
|
|
45
|
+
"title": "频率惩罚度"
|
|
46
|
+
},
|
|
47
|
+
"maxTokens": {
|
|
48
|
+
"desc": "单次交互所用的最大 Token 数",
|
|
49
|
+
"title": "单次回复限制"
|
|
44
50
|
},
|
|
45
51
|
"model": {
|
|
52
|
+
"desc": "ChatGPT 模型",
|
|
46
53
|
"title": "模型"
|
|
47
54
|
},
|
|
48
55
|
"presencePenalty": {
|
|
49
56
|
"desc": "值越大,越有可能扩展到新话题",
|
|
50
|
-
"title": "话题新鲜度
|
|
57
|
+
"title": "话题新鲜度"
|
|
51
58
|
},
|
|
52
59
|
"temperature": {
|
|
53
60
|
"desc": "值越大,回复越随机",
|
|
54
|
-
"title": "随机性
|
|
61
|
+
"title": "随机性"
|
|
55
62
|
},
|
|
56
63
|
"title": "模型设置",
|
|
57
64
|
"topP": {
|
|
58
65
|
"desc": "与随机性类似,但不要和随机性一起更改",
|
|
59
|
-
"title": "核采样
|
|
66
|
+
"title": "核采样"
|
|
60
67
|
}
|
|
61
68
|
},
|
|
62
69
|
"settingOpenAI": {
|
|
63
70
|
"endpoint": {
|
|
64
71
|
"desc": "除默认地址外,必须包含 http(s)://",
|
|
65
|
-
"
|
|
72
|
+
"placeholder": "https://api.openai.com/v1",
|
|
73
|
+
"title": "接口代理地址"
|
|
66
74
|
},
|
|
67
75
|
"title": "OpenAI 设置",
|
|
68
76
|
"token": {
|
|
69
|
-
"desc": "使用自己的 Key
|
|
77
|
+
"desc": "使用自己的 OpenAI Key",
|
|
70
78
|
"placeholder": "OpenAI API Key",
|
|
71
79
|
"title": "API Key"
|
|
72
80
|
}
|
|
@@ -81,7 +89,6 @@
|
|
|
81
89
|
},
|
|
82
90
|
"settingTheme": {
|
|
83
91
|
"avatar": {
|
|
84
|
-
"desc": "支持 URL / Base64 / Emoji 表情符号",
|
|
85
92
|
"title": "头像"
|
|
86
93
|
},
|
|
87
94
|
"fontSize": {
|
|
@@ -89,8 +96,21 @@
|
|
|
89
96
|
"title": "字体大小"
|
|
90
97
|
},
|
|
91
98
|
"lang": {
|
|
92
|
-
"
|
|
93
|
-
|
|
99
|
+
"title": "语言设置"
|
|
100
|
+
},
|
|
101
|
+
"neutralColor": {
|
|
102
|
+
"desc": "不同色彩倾向的灰阶自定义",
|
|
103
|
+
"title": "中性色"
|
|
104
|
+
},
|
|
105
|
+
"primaryColor": {
|
|
106
|
+
"desc": "自定义主题色",
|
|
107
|
+
"title": "主题色"
|
|
108
|
+
},
|
|
109
|
+
"themeMode": {
|
|
110
|
+
"auto": "自动",
|
|
111
|
+
"dark": "深色",
|
|
112
|
+
"light": "浅色",
|
|
113
|
+
"title": "主题"
|
|
94
114
|
},
|
|
95
115
|
"title": "主题设置"
|
|
96
116
|
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Avatar, Logo } from '@lobehub/ui';
|
|
2
|
+
import { Upload } from 'antd';
|
|
3
|
+
import { memo } from 'react';
|
|
4
|
+
import { shallow } from 'zustand/shallow';
|
|
5
|
+
|
|
6
|
+
import { useSettings } from '@/store/settings';
|
|
7
|
+
import { createUploadImageHandler } from '@/utils/uploadFIle';
|
|
8
|
+
|
|
9
|
+
interface AvatarWithUploadProps {
|
|
10
|
+
size?: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export default memo<AvatarWithUploadProps>(({ size = 40 }) => {
|
|
14
|
+
const [avatar, setSettings] = useSettings((st) => [st.settings.avatar, st.setSettings], shallow);
|
|
15
|
+
|
|
16
|
+
const handleUploadAvatar = createUploadImageHandler((avatar) => {
|
|
17
|
+
setSettings({ avatar });
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<div style={{ maxHeight: size, maxWidth: size }}>
|
|
22
|
+
<Upload beforeUpload={handleUploadAvatar} itemRender={() => void 0} maxCount={1}>
|
|
23
|
+
{avatar ? <Avatar avatar={avatar} size={size} /> : <Logo size={size} />}
|
|
24
|
+
</Upload>
|
|
25
|
+
</div>
|
|
26
|
+
);
|
|
27
|
+
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { DraggablePanel } from '@lobehub/ui';
|
|
2
2
|
import { createStyles } from 'antd-style';
|
|
3
3
|
import isEqual from 'fast-deep-equal';
|
|
4
|
-
import { PropsWithChildren, useState } from 'react';
|
|
4
|
+
import { PropsWithChildren, memo, useState } from 'react';
|
|
5
5
|
import { shallow } from 'zustand/shallow';
|
|
6
6
|
|
|
7
7
|
import { useSettings } from '@/store/settings';
|
|
@@ -14,7 +14,7 @@ export const useStyles = createStyles(({ css, token }) => ({
|
|
|
14
14
|
`,
|
|
15
15
|
}));
|
|
16
16
|
|
|
17
|
-
export default ({ children }
|
|
17
|
+
export default memo<PropsWithChildren>(({ children }) => {
|
|
18
18
|
const { styles } = useStyles();
|
|
19
19
|
const [sessionsWidth, sessionExpandable] = useSettings(
|
|
20
20
|
(s) => [s.sessionsWidth, s.sessionExpandable],
|
|
@@ -52,4 +52,4 @@ export default ({ children }: PropsWithChildren) => {
|
|
|
52
52
|
{children}
|
|
53
53
|
</DraggablePanel>
|
|
54
54
|
);
|
|
55
|
-
};
|
|
55
|
+
});
|
|
@@ -1,16 +1,17 @@
|
|
|
1
|
-
import { ActionIcon,
|
|
1
|
+
import { ActionIcon, SideNav } from '@lobehub/ui';
|
|
2
2
|
import { MessageSquare, Settings2, Sticker } from 'lucide-react';
|
|
3
3
|
import Router from 'next/router';
|
|
4
4
|
import { memo } from 'react';
|
|
5
5
|
import { shallow } from 'zustand/shallow';
|
|
6
6
|
|
|
7
|
+
import AvatarWithUpload from '@/features/AvatarWithUpload';
|
|
7
8
|
import { useSettings } from '@/store/settings';
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
export default memo(() => {
|
|
10
11
|
const [tab, setTab] = useSettings((s) => [s.sidebarKey, s.switchSideBar], shallow);
|
|
11
12
|
return (
|
|
12
13
|
<SideNav
|
|
13
|
-
avatar={<
|
|
14
|
+
avatar={<AvatarWithUpload />}
|
|
14
15
|
bottomActions={<ActionIcon icon={Settings2} onClick={() => Router.push('/setting')} />}
|
|
15
16
|
style={{ height: '100vh' }}
|
|
16
17
|
topActions={
|
|
@@ -32,5 +33,3 @@ const Sidebar = memo(() => {
|
|
|
32
33
|
/>
|
|
33
34
|
);
|
|
34
35
|
});
|
|
35
|
-
|
|
36
|
-
export default Sidebar;
|
package/src/layout/index.tsx
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import { ThemeProvider } from '@lobehub/ui';
|
|
1
|
+
import { ThemeProvider, lobeCustomTheme } from '@lobehub/ui';
|
|
2
2
|
import { App, ConfigProvider } from 'antd';
|
|
3
|
+
import { useThemeMode } from 'antd-style';
|
|
3
4
|
import 'antd/dist/reset.css';
|
|
4
5
|
import Zh_CN from 'antd/locale/zh_CN';
|
|
5
|
-
import { PropsWithChildren, useEffect } from 'react';
|
|
6
|
+
import { PropsWithChildren, useCallback, useEffect } from 'react';
|
|
7
|
+
import { shallow } from 'zustand/shallow';
|
|
6
8
|
|
|
7
9
|
import { useSessionStore } from '@/store/session';
|
|
8
10
|
import { useSettings } from '@/store/settings';
|
|
@@ -27,14 +29,27 @@ const Layout = ({ children }: PropsWithChildren) => {
|
|
|
27
29
|
};
|
|
28
30
|
|
|
29
31
|
export default ({ children }: PropsWithChildren) => {
|
|
32
|
+
const themeMode = useSettings((s) => s.settings.themeMode, shallow);
|
|
33
|
+
const { primaryColor, neutralColor } = useSettings(
|
|
34
|
+
(s) => ({ neutralColor: s.settings.neutralColor, primaryColor: s.settings.primaryColor }),
|
|
35
|
+
shallow,
|
|
36
|
+
);
|
|
37
|
+
const { browserPrefers } = useThemeMode();
|
|
38
|
+
const isDarkMode = themeMode === 'auto' ? browserPrefers === 'dark' : themeMode === 'dark';
|
|
39
|
+
|
|
30
40
|
useEffect(() => {
|
|
31
41
|
// refs: https://github.com/pmndrs/zustand/blob/main/docs/integrations/persisting-store-data.md#hashydrated
|
|
32
42
|
useSessionStore.persist.rehydrate();
|
|
33
43
|
useSettings.persist.rehydrate();
|
|
34
44
|
}, []);
|
|
35
45
|
|
|
46
|
+
const genCustomToken: any = useCallback(
|
|
47
|
+
() => lobeCustomTheme({ isDarkMode, neutralColor, primaryColor }),
|
|
48
|
+
[primaryColor, neutralColor, isDarkMode],
|
|
49
|
+
);
|
|
50
|
+
|
|
36
51
|
return (
|
|
37
|
-
<ThemeProvider themeMode={
|
|
52
|
+
<ThemeProvider customToken={genCustomToken || {}} themeMode={themeMode}>
|
|
38
53
|
<GlobalStyle />
|
|
39
54
|
<Layout>{children}</Layout>
|
|
40
55
|
</ThemeProvider>
|
package/src/locales/create.ts
CHANGED
|
@@ -3,20 +3,20 @@ import LanguageDetector from 'i18next-browser-languagedetector';
|
|
|
3
3
|
import { isArray } from 'lodash-es';
|
|
4
4
|
import { initReactI18next } from 'react-i18next';
|
|
5
5
|
|
|
6
|
-
import type { Namespaces
|
|
6
|
+
import type { Namespaces } from '@/types/locale';
|
|
7
7
|
|
|
8
8
|
import resources from './resources';
|
|
9
9
|
|
|
10
|
-
const getRes = (res: Resources, namespace: Namespaces[]) => {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
};
|
|
10
|
+
// const getRes = (res: Resources, namespace: Namespaces[]) => {
|
|
11
|
+
// const newRes: any = {};
|
|
12
|
+
// for (const [locale, value] of Object.entries(res)) {
|
|
13
|
+
// newRes[locale] = {};
|
|
14
|
+
// for (const ns of namespace) {
|
|
15
|
+
// newRes[locale][ns] = value[ns];
|
|
16
|
+
// }
|
|
17
|
+
// }
|
|
18
|
+
// return newRes;
|
|
19
|
+
// };
|
|
20
20
|
|
|
21
21
|
export const createI18nNext = (namespace?: Namespaces[] | Namespaces) => {
|
|
22
22
|
const ns: Namespaces[] = namespace
|
|
@@ -42,7 +42,8 @@ export const createI18nNext = (namespace?: Namespaces[] | Namespaces) => {
|
|
|
42
42
|
escapeValue: false, // not needed for react as it escapes by default
|
|
43
43
|
},
|
|
44
44
|
ns,
|
|
45
|
-
resources: getRes(resources, ns),
|
|
45
|
+
// resources: getRes(resources, ns),
|
|
46
|
+
resources,
|
|
46
47
|
})
|
|
47
48
|
);
|
|
48
49
|
};
|
|
@@ -20,6 +20,12 @@ export default {
|
|
|
20
20
|
desc: '当未压缩的历史消息超过该值时,将进行压缩',
|
|
21
21
|
title: '历史消息长度压缩阈值',
|
|
22
22
|
},
|
|
23
|
+
enableCompressThreshold: {
|
|
24
|
+
title: '是否开启历史消息长度压缩阈值',
|
|
25
|
+
},
|
|
26
|
+
enableHistoryCount: {
|
|
27
|
+
title: '是否开启携带的历史消息数限制',
|
|
28
|
+
},
|
|
23
29
|
historyCount: {
|
|
24
30
|
desc: '每次请求携带的历史消息数',
|
|
25
31
|
title: '附带历史消息数',
|
|
@@ -28,45 +34,47 @@ export default {
|
|
|
28
34
|
desc: '用户最新的一条消息会填充到此模板',
|
|
29
35
|
title: '用户输入预处理',
|
|
30
36
|
},
|
|
31
|
-
maxTokens: {
|
|
32
|
-
desc: '单次交互所用的最大 Token 数',
|
|
33
|
-
title: '单次回复限制 (max_tokens)',
|
|
34
|
-
},
|
|
35
|
-
sendKey: {
|
|
36
|
-
title: '发送键',
|
|
37
|
-
},
|
|
38
37
|
title: '聊天设置',
|
|
39
38
|
},
|
|
40
39
|
settingModel: {
|
|
40
|
+
enableMaxTokens: {
|
|
41
|
+
title: '开启单次回复限制',
|
|
42
|
+
},
|
|
41
43
|
frequencyPenalty: {
|
|
42
44
|
desc: '值越大,越有可能降低重复字词',
|
|
43
|
-
title: '频率惩罚度
|
|
45
|
+
title: '频率惩罚度',
|
|
46
|
+
},
|
|
47
|
+
maxTokens: {
|
|
48
|
+
desc: '单次交互所用的最大 Token 数',
|
|
49
|
+
title: '单次回复限制',
|
|
44
50
|
},
|
|
45
51
|
model: {
|
|
52
|
+
desc: 'ChatGPT 模型',
|
|
46
53
|
title: '模型',
|
|
47
54
|
},
|
|
48
55
|
presencePenalty: {
|
|
49
56
|
desc: '值越大,越有可能扩展到新话题',
|
|
50
|
-
title: '话题新鲜度
|
|
57
|
+
title: '话题新鲜度',
|
|
51
58
|
},
|
|
52
59
|
temperature: {
|
|
53
60
|
desc: '值越大,回复越随机',
|
|
54
|
-
title: '随机性
|
|
61
|
+
title: '随机性',
|
|
55
62
|
},
|
|
56
63
|
title: '模型设置',
|
|
57
64
|
topP: {
|
|
58
65
|
desc: '与随机性类似,但不要和随机性一起更改',
|
|
59
|
-
title: '核采样
|
|
66
|
+
title: '核采样',
|
|
60
67
|
},
|
|
61
68
|
},
|
|
62
69
|
settingOpenAI: {
|
|
63
70
|
endpoint: {
|
|
64
71
|
desc: '除默认地址外,必须包含 http(s)://',
|
|
65
|
-
|
|
72
|
+
placeholder: 'https://api.openai.com/v1',
|
|
73
|
+
title: '接口代理地址',
|
|
66
74
|
},
|
|
67
75
|
title: 'OpenAI 设置',
|
|
68
76
|
token: {
|
|
69
|
-
desc: '使用自己的 Key
|
|
77
|
+
desc: '使用自己的 OpenAI Key',
|
|
70
78
|
placeholder: 'OpenAI API Key',
|
|
71
79
|
title: 'API Key',
|
|
72
80
|
},
|
|
@@ -81,7 +89,6 @@ export default {
|
|
|
81
89
|
},
|
|
82
90
|
settingTheme: {
|
|
83
91
|
avatar: {
|
|
84
|
-
desc: '支持 URL / Base64 / Emoji 表情符号',
|
|
85
92
|
title: '头像',
|
|
86
93
|
},
|
|
87
94
|
fontSize: {
|
|
@@ -89,8 +96,21 @@ export default {
|
|
|
89
96
|
title: '字体大小',
|
|
90
97
|
},
|
|
91
98
|
lang: {
|
|
92
|
-
|
|
93
|
-
|
|
99
|
+
title: '语言设置',
|
|
100
|
+
},
|
|
101
|
+
neutralColor: {
|
|
102
|
+
desc: '不同色彩倾向的灰阶自定义',
|
|
103
|
+
title: '中性色',
|
|
104
|
+
},
|
|
105
|
+
primaryColor: {
|
|
106
|
+
desc: '自定义主题色',
|
|
107
|
+
title: '主题色',
|
|
108
|
+
},
|
|
109
|
+
themeMode: {
|
|
110
|
+
auto: '自动',
|
|
111
|
+
dark: '深色',
|
|
112
|
+
light: '浅色',
|
|
113
|
+
title: '主题',
|
|
94
114
|
},
|
|
95
115
|
title: '主题设置',
|
|
96
116
|
},
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { SelectProps } from 'antd';
|
|
2
|
+
|
|
3
|
+
import type { Locales } from '@/types/locale';
|
|
4
|
+
|
|
5
|
+
type LocaleOptions = SelectProps['options'] &
|
|
6
|
+
{
|
|
7
|
+
value: Locales;
|
|
8
|
+
}[];
|
|
9
|
+
|
|
10
|
+
export const options: LocaleOptions = [
|
|
11
|
+
{
|
|
12
|
+
label: '简体中文',
|
|
13
|
+
value: 'zh-CN',
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
label: 'English',
|
|
17
|
+
value: 'en-US',
|
|
18
|
+
},
|
|
19
|
+
] as LocaleOptions;
|
|
@@ -3,11 +3,11 @@ import { PropsWithChildren, memo, useEffect } from 'react';
|
|
|
3
3
|
import { Flexbox } from 'react-layout-kit';
|
|
4
4
|
import { shallow } from 'zustand/shallow';
|
|
5
5
|
|
|
6
|
+
import SideBar from '@/features/SideBar';
|
|
6
7
|
import { createI18nNext } from '@/locales/create';
|
|
7
8
|
import { useSessionStore } from '@/store/session';
|
|
8
9
|
import { useSettings } from '@/store/settings';
|
|
9
10
|
|
|
10
|
-
import Sidebar from '../Sidebar';
|
|
11
11
|
import { Sessions } from './SessionList';
|
|
12
12
|
|
|
13
13
|
const initI18n = createI18nNext();
|
|
@@ -41,7 +41,7 @@ const ChatLayout = memo<PropsWithChildren>(({ children }) => {
|
|
|
41
41
|
|
|
42
42
|
return (
|
|
43
43
|
<Flexbox horizontal width={'100%'}>
|
|
44
|
-
<
|
|
44
|
+
<SideBar />
|
|
45
45
|
<Sessions />
|
|
46
46
|
{children}
|
|
47
47
|
</Flexbox>
|
|
@@ -1,31 +1,96 @@
|
|
|
1
|
-
import { Form,
|
|
1
|
+
import { Form, type ItemGroup, ThemeSwitch } from '@lobehub/ui';
|
|
2
|
+
import { Form as AntForm, Button, Input, Popconfirm, Select, Switch } from 'antd';
|
|
2
3
|
import isEqual from 'fast-deep-equal';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
4
|
+
import { debounce } from 'lodash-es';
|
|
5
|
+
import { AppWindow, Bot, MessagesSquare, Palette, Webhook } from 'lucide-react';
|
|
6
|
+
import { memo, useCallback, useMemo } from 'react';
|
|
5
7
|
import { useTranslation } from 'react-i18next';
|
|
8
|
+
import { shallow } from 'zustand/shallow';
|
|
6
9
|
|
|
10
|
+
import AvatarWithUpload from '@/features/AvatarWithUpload';
|
|
11
|
+
import { options } from '@/locales/options';
|
|
12
|
+
import SliderWithInput from '@/pages/setting/SliderWithInput';
|
|
7
13
|
import { settingsSelectors, useSettings } from '@/store/settings';
|
|
14
|
+
import { DEFAULT_SETTINGS } from '@/store/settings/initialState';
|
|
8
15
|
import { ConfigKeys } from '@/types/exportConfig';
|
|
16
|
+
import { LanguageModel } from '@/types/llm';
|
|
17
|
+
|
|
18
|
+
import { ThemeSwatchesNeutral, ThemeSwatchesPrimary } from './ThemeSwatches';
|
|
9
19
|
|
|
10
20
|
type SettingItemGroup = ItemGroup & {
|
|
11
21
|
children: {
|
|
12
|
-
name
|
|
22
|
+
name?: ConfigKeys;
|
|
13
23
|
}[];
|
|
14
24
|
};
|
|
15
25
|
|
|
16
26
|
const SettingForm = memo(() => {
|
|
27
|
+
const { t } = useTranslation('setting');
|
|
28
|
+
const [form] = AntForm.useForm();
|
|
17
29
|
const settings = useSettings(settingsSelectors.currentSettings, isEqual);
|
|
30
|
+
const { setThemeMode, setSettings, resetSettings } = useSettings(
|
|
31
|
+
(s) => ({
|
|
32
|
+
resetSettings: s.resetSettings,
|
|
33
|
+
setSettings: s.setSettings,
|
|
34
|
+
setThemeMode: s.setThemeMode,
|
|
35
|
+
}),
|
|
36
|
+
shallow,
|
|
37
|
+
);
|
|
18
38
|
|
|
19
|
-
const
|
|
39
|
+
const handleReset = useCallback(() => {
|
|
40
|
+
resetSettings();
|
|
41
|
+
form.setFieldsValue(DEFAULT_SETTINGS);
|
|
42
|
+
}, []);
|
|
43
|
+
|
|
44
|
+
const handleClear = useCallback(() => {
|
|
45
|
+
handleReset();
|
|
46
|
+
// TODO: 删除聊天记录
|
|
47
|
+
}, []);
|
|
20
48
|
|
|
21
49
|
const theme: SettingItemGroup = useMemo(
|
|
22
50
|
() => ({
|
|
23
51
|
children: [
|
|
24
52
|
{
|
|
25
|
-
children: <
|
|
26
|
-
desc: t('settingTheme.avatar.desc'),
|
|
53
|
+
children: <AvatarWithUpload />,
|
|
27
54
|
label: t('settingTheme.avatar.title'),
|
|
28
|
-
|
|
55
|
+
minWidth: undefined,
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
children: (
|
|
59
|
+
<ThemeSwitch
|
|
60
|
+
labels={{
|
|
61
|
+
auto: t('settingTheme.themeMode.auto'),
|
|
62
|
+
dark: t('settingTheme.themeMode.dark'),
|
|
63
|
+
light: t('settingTheme.themeMode.light'),
|
|
64
|
+
}}
|
|
65
|
+
onThemeSwitch={setThemeMode}
|
|
66
|
+
themeMode={settings.themeMode}
|
|
67
|
+
type={'select'}
|
|
68
|
+
/>
|
|
69
|
+
),
|
|
70
|
+
label: t('settingTheme.themeMode.title'),
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
children: <Select options={options} />,
|
|
74
|
+
label: t('settingTheme.lang.title'),
|
|
75
|
+
name: 'language',
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
children: <SliderWithInput max={18} min={12} />,
|
|
79
|
+
desc: t('settingTheme.fontSize.desc'),
|
|
80
|
+
label: t('settingTheme.fontSize.title'),
|
|
81
|
+
name: 'fontSize',
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
children: <ThemeSwatchesPrimary />,
|
|
85
|
+
desc: t('settingTheme.primaryColor.desc'),
|
|
86
|
+
label: t('settingTheme.primaryColor.title'),
|
|
87
|
+
minWidth: undefined,
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
children: <ThemeSwatchesNeutral />,
|
|
91
|
+
desc: t('settingTheme.neutralColor.desc'),
|
|
92
|
+
label: t('settingTheme.neutralColor.title'),
|
|
93
|
+
minWidth: undefined,
|
|
29
94
|
},
|
|
30
95
|
],
|
|
31
96
|
icon: Palette,
|
|
@@ -34,8 +99,200 @@ const SettingForm = memo(() => {
|
|
|
34
99
|
[settings],
|
|
35
100
|
);
|
|
36
101
|
|
|
102
|
+
const OpenAI: SettingItemGroup = useMemo(
|
|
103
|
+
() => ({
|
|
104
|
+
children: [
|
|
105
|
+
{
|
|
106
|
+
children: <Input.Password placeholder={t('settingOpenAI.token.placeholder')} />,
|
|
107
|
+
desc: t('settingOpenAI.token.desc'),
|
|
108
|
+
label: t('settingOpenAI.token.title'),
|
|
109
|
+
name: 'token',
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
children: <Input placeholder={t('settingOpenAI.endpoint.placeholder')} />,
|
|
113
|
+
desc: t('settingOpenAI.endpoint.desc'),
|
|
114
|
+
|
|
115
|
+
label: t('settingOpenAI.endpoint.title'),
|
|
116
|
+
|
|
117
|
+
name: 'endpoint',
|
|
118
|
+
},
|
|
119
|
+
],
|
|
120
|
+
icon: Webhook,
|
|
121
|
+
title: t('settingOpenAI.title'),
|
|
122
|
+
}),
|
|
123
|
+
[settings],
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
const Chat: SettingItemGroup = useMemo(
|
|
127
|
+
() => ({
|
|
128
|
+
children: [
|
|
129
|
+
{
|
|
130
|
+
children: <Switch />,
|
|
131
|
+
label: t('settingChat.enableHistoryCount.title'),
|
|
132
|
+
minWidth: undefined,
|
|
133
|
+
name: 'enableHistoryCount',
|
|
134
|
+
valuePropName: 'checked',
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
children: <SliderWithInput max={32} min={0} />,
|
|
138
|
+
desc: t('settingChat.historyCount.desc'),
|
|
139
|
+
hidden: !settings.enableHistoryCount,
|
|
140
|
+
label: t('settingChat.historyCount.title'),
|
|
141
|
+
name: 'historyCount',
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
children: <Switch />,
|
|
145
|
+
label: t('settingChat.enableCompressThreshold.title'),
|
|
146
|
+
minWidth: undefined,
|
|
147
|
+
name: 'enableCompressThreshold',
|
|
148
|
+
valuePropName: 'checked',
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
children: <SliderWithInput max={32} min={0} />,
|
|
152
|
+
desc: t('settingChat.compressThreshold.desc'),
|
|
153
|
+
hidden: !settings.enableCompressThreshold,
|
|
154
|
+
label: t('settingChat.compressThreshold.title'),
|
|
155
|
+
name: 'compressThreshold',
|
|
156
|
+
},
|
|
157
|
+
],
|
|
158
|
+
icon: MessagesSquare,
|
|
159
|
+
title: t('settingChat.title'),
|
|
160
|
+
}),
|
|
161
|
+
[settings],
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
const Model: SettingItemGroup = useMemo(
|
|
165
|
+
() => ({
|
|
166
|
+
children: [
|
|
167
|
+
{
|
|
168
|
+
children: (
|
|
169
|
+
<Select
|
|
170
|
+
options={Object.values(LanguageModel).map((value) => ({
|
|
171
|
+
label: value,
|
|
172
|
+
value,
|
|
173
|
+
}))}
|
|
174
|
+
/>
|
|
175
|
+
),
|
|
176
|
+
desc: t('settingModel.model.desc'),
|
|
177
|
+
label: t('settingModel.model.title'),
|
|
178
|
+
name: 'model',
|
|
179
|
+
tag: 'model',
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
children: <SliderWithInput max={1} min={0} step={0.1} />,
|
|
183
|
+
desc: t('settingModel.temperature.desc'),
|
|
184
|
+
label: t('settingModel.temperature.title'),
|
|
185
|
+
name: 'temperature',
|
|
186
|
+
tag: 'temperature',
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
children: <SliderWithInput max={1} min={0} step={0.1} />,
|
|
190
|
+
desc: t('settingModel.topP.desc'),
|
|
191
|
+
label: t('settingModel.topP.title'),
|
|
192
|
+
name: 'topP',
|
|
193
|
+
tag: 'top_p',
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
children: <SliderWithInput max={2} min={-2} step={0.1} />,
|
|
197
|
+
desc: t('settingModel.presencePenalty.desc'),
|
|
198
|
+
label: t('settingModel.presencePenalty.title'),
|
|
199
|
+
name: 'presencePenalty',
|
|
200
|
+
tag: 'presence_penalty',
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
children: <SliderWithInput max={2} min={-2} step={0.1} />,
|
|
204
|
+
desc: t('settingModel.frequencyPenalty.desc'),
|
|
205
|
+
label: t('settingModel.frequencyPenalty.title'),
|
|
206
|
+
name: 'frequencyPenalty',
|
|
207
|
+
tag: 'frequency_penalty',
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
children: <Switch />,
|
|
211
|
+
label: t('settingModel.enableMaxTokens.title'),
|
|
212
|
+
minWidth: undefined,
|
|
213
|
+
name: 'enableMaxTokens',
|
|
214
|
+
valuePropName: 'checked',
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
children: <SliderWithInput max={32_000} min={0} step={100} />,
|
|
218
|
+
desc: t('settingModel.maxTokens.desc'),
|
|
219
|
+
hidden: !settings.enableMaxTokens,
|
|
220
|
+
label: t('settingModel.maxTokens.title'),
|
|
221
|
+
name: 'maxTokens',
|
|
222
|
+
tag: 'max_tokens',
|
|
223
|
+
},
|
|
224
|
+
],
|
|
225
|
+
icon: Bot,
|
|
226
|
+
title: t('settingModel.title'),
|
|
227
|
+
}),
|
|
228
|
+
[settings],
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
const System: SettingItemGroup = useMemo(
|
|
232
|
+
() => ({
|
|
233
|
+
children: [
|
|
234
|
+
{
|
|
235
|
+
children: <Input.Password placeholder={t('settingSystem.accessCode.placeholder')} />,
|
|
236
|
+
desc: t('settingSystem.accessCode.desc'),
|
|
237
|
+
label: t('settingSystem.accessCode.title'),
|
|
238
|
+
name: 'accessCode',
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
children: (
|
|
242
|
+
<Popconfirm
|
|
243
|
+
arrow={false}
|
|
244
|
+
cancelText={t('cancel', { ns: 'common' })}
|
|
245
|
+
okButtonProps={{ danger: true }}
|
|
246
|
+
okText={t('ok', { ns: 'common' })}
|
|
247
|
+
onConfirm={handleReset}
|
|
248
|
+
title={t('danger.reset.confirm')}
|
|
249
|
+
>
|
|
250
|
+
<Button danger type="primary">
|
|
251
|
+
{t('danger.reset.action')}
|
|
252
|
+
</Button>
|
|
253
|
+
</Popconfirm>
|
|
254
|
+
),
|
|
255
|
+
desc: t('danger.reset.title'),
|
|
256
|
+
label: t('danger.reset.desc'),
|
|
257
|
+
minWidth: undefined,
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
children: (
|
|
261
|
+
<Popconfirm
|
|
262
|
+
arrow={false}
|
|
263
|
+
cancelText={t('cancel', { ns: 'common' })}
|
|
264
|
+
okButtonProps={{ danger: true }}
|
|
265
|
+
okText={t('ok', { ns: 'common' })}
|
|
266
|
+
onConfirm={handleClear}
|
|
267
|
+
title={t('danger.clear.confirm')}
|
|
268
|
+
>
|
|
269
|
+
<Button danger type="primary">
|
|
270
|
+
{t('danger.clear.action')}
|
|
271
|
+
</Button>
|
|
272
|
+
</Popconfirm>
|
|
273
|
+
),
|
|
274
|
+
desc: t('danger.clear.title'),
|
|
275
|
+
label: t('danger.clear.desc'),
|
|
276
|
+
minWidth: undefined,
|
|
277
|
+
},
|
|
278
|
+
],
|
|
279
|
+
icon: AppWindow,
|
|
280
|
+
title: t('settingSystem.title'),
|
|
281
|
+
}),
|
|
282
|
+
[settings],
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
const items = useMemo(() => [theme, OpenAI, Chat, Model, System], [settings]);
|
|
286
|
+
|
|
37
287
|
return (
|
|
38
|
-
<Form
|
|
288
|
+
<Form
|
|
289
|
+
form={form}
|
|
290
|
+
initialValues={settings}
|
|
291
|
+
itemMinWidth="max(30%,240px)"
|
|
292
|
+
items={items}
|
|
293
|
+
onValuesChange={debounce(setSettings, 100)}
|
|
294
|
+
style={{ maxWidth: 1024, width: '100%' }}
|
|
295
|
+
/>
|
|
39
296
|
);
|
|
40
297
|
});
|
|
41
298
|
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { InputNumber, Slider } from 'antd';
|
|
2
|
+
import { SliderSingleProps } from 'antd/es/slider';
|
|
3
|
+
import { memo } from 'react';
|
|
4
|
+
import { Flexbox } from 'react-layout-kit';
|
|
5
|
+
|
|
6
|
+
const SliderWithInput = memo<SliderSingleProps>(
|
|
7
|
+
({ step, value, onChange, max, min, defaultValue, ...props }) => {
|
|
8
|
+
return (
|
|
9
|
+
<Flexbox direction={'horizontal'} gap={8}>
|
|
10
|
+
<Slider
|
|
11
|
+
defaultValue={defaultValue}
|
|
12
|
+
max={max}
|
|
13
|
+
min={min}
|
|
14
|
+
onChange={onChange}
|
|
15
|
+
step={step}
|
|
16
|
+
style={{ flex: 1 }}
|
|
17
|
+
value={value}
|
|
18
|
+
{...props}
|
|
19
|
+
/>
|
|
20
|
+
<InputNumber
|
|
21
|
+
defaultValue={defaultValue}
|
|
22
|
+
max={max}
|
|
23
|
+
min={min}
|
|
24
|
+
onChange={onChange as any}
|
|
25
|
+
step={Number(step)}
|
|
26
|
+
style={{ flex: 1, maxWidth: 64 }}
|
|
27
|
+
value={value}
|
|
28
|
+
/>
|
|
29
|
+
</Flexbox>
|
|
30
|
+
);
|
|
31
|
+
},
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
export default SliderWithInput;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import {
|
|
2
|
+
NeutralColors,
|
|
3
|
+
Swatches,
|
|
4
|
+
findCustomThemeName,
|
|
5
|
+
neutralColors,
|
|
6
|
+
neutralColorsSwatches,
|
|
7
|
+
} from '@lobehub/ui';
|
|
8
|
+
import { memo } from 'react';
|
|
9
|
+
import { shallow } from 'zustand/shallow';
|
|
10
|
+
|
|
11
|
+
import { useSettings } from '@/store/settings';
|
|
12
|
+
|
|
13
|
+
const ThemeSwatchesNeutral = memo(() => {
|
|
14
|
+
const { neutralColor, setSettings } = useSettings(
|
|
15
|
+
(s) => ({ neutralColor: s.settings.neutralColor, setSettings: s.setSettings }),
|
|
16
|
+
shallow,
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
const handleSelect = (v: any) => {
|
|
20
|
+
const name = findCustomThemeName('neutral', v) as NeutralColors;
|
|
21
|
+
setSettings({ neutralColor: name || '' });
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<Swatches
|
|
26
|
+
activeColor={neutralColor ? neutralColors[neutralColor] : undefined}
|
|
27
|
+
colors={neutralColorsSwatches}
|
|
28
|
+
onSelect={handleSelect}
|
|
29
|
+
/>
|
|
30
|
+
);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
export default ThemeSwatchesNeutral;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import {
|
|
2
|
+
PrimaryColors,
|
|
3
|
+
Swatches,
|
|
4
|
+
findCustomThemeName,
|
|
5
|
+
primaryColors,
|
|
6
|
+
primaryColorsSwatches,
|
|
7
|
+
} from '@lobehub/ui';
|
|
8
|
+
import { memo } from 'react';
|
|
9
|
+
import { shallow } from 'zustand/shallow';
|
|
10
|
+
|
|
11
|
+
import { useSettings } from '@/store/settings';
|
|
12
|
+
|
|
13
|
+
const ThemeSwatchesPrimary = memo(() => {
|
|
14
|
+
const { primaryColor, setSettings } = useSettings(
|
|
15
|
+
(s) => ({ primaryColor: s.settings.primaryColor, setSettings: s.setSettings }),
|
|
16
|
+
shallow,
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
const handleSelect = (v: any) => {
|
|
20
|
+
const name = findCustomThemeName('primary', v) as PrimaryColors;
|
|
21
|
+
setSettings({ primaryColor: name || '' });
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<Swatches
|
|
26
|
+
activeColor={primaryColor ? primaryColors[primaryColor] : undefined}
|
|
27
|
+
colors={primaryColorsSwatches}
|
|
28
|
+
onSelect={handleSelect}
|
|
29
|
+
/>
|
|
30
|
+
);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
export default ThemeSwatchesPrimary;
|
|
@@ -3,10 +3,10 @@ import { memo, useEffect } from 'react';
|
|
|
3
3
|
import { useTranslation } from 'react-i18next';
|
|
4
4
|
import { Flexbox } from 'react-layout-kit';
|
|
5
5
|
|
|
6
|
+
import SideBar from '@/features/SideBar';
|
|
6
7
|
import { createI18nNext } from '@/locales/create';
|
|
7
8
|
import { Sessions } from '@/pages/chat/SessionList';
|
|
8
9
|
|
|
9
|
-
import Sidebar from '../Sidebar';
|
|
10
10
|
import Header from './Header';
|
|
11
11
|
import SettingForm from './SettingForm';
|
|
12
12
|
|
|
@@ -24,12 +24,12 @@ const SettingLayout = memo(() => {
|
|
|
24
24
|
<Head>
|
|
25
25
|
<title>{pageTitle}</title>
|
|
26
26
|
</Head>
|
|
27
|
-
<Flexbox horizontal width={'100%'}>
|
|
28
|
-
<
|
|
27
|
+
<Flexbox height={'100vh'} horizontal width={'100%'}>
|
|
28
|
+
<SideBar />
|
|
29
29
|
<Sessions />
|
|
30
30
|
<Flexbox flex={1}>
|
|
31
31
|
<Header />
|
|
32
|
-
<Flexbox align={'center'} padding={24}>
|
|
32
|
+
<Flexbox align={'center'} flex={1} padding={24} style={{ overflow: 'auto' }}>
|
|
33
33
|
<SettingForm />
|
|
34
34
|
</Flexbox>
|
|
35
35
|
</Flexbox>
|
|
@@ -1,15 +1,17 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ThemeMode } from 'antd-style';
|
|
2
2
|
import type { StateCreator } from 'zustand/vanilla';
|
|
3
3
|
|
|
4
4
|
import type { ConfigSettings } from '@/types/exportConfig';
|
|
5
5
|
|
|
6
6
|
import type { SidebarTabKey } from './initialState';
|
|
7
|
-
import {
|
|
7
|
+
import { DEFAULT_SETTINGS, GlobalSettingsState } from './initialState';
|
|
8
8
|
import type { SettingsStore } from './store';
|
|
9
9
|
|
|
10
10
|
export interface SettingsAction {
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
resetSettings: () => void;
|
|
12
|
+
setGlobalSettings: (settings: GlobalSettingsState) => void;
|
|
13
|
+
setSettings: (settings: Partial<ConfigSettings>) => void;
|
|
14
|
+
setThemeMode: (themeMode: ThemeMode) => void;
|
|
13
15
|
switchSideBar: (key: SidebarTabKey) => void;
|
|
14
16
|
}
|
|
15
17
|
|
|
@@ -19,11 +21,18 @@ export const createSettings: StateCreator<
|
|
|
19
21
|
[],
|
|
20
22
|
SettingsAction
|
|
21
23
|
> = (set, get) => ({
|
|
22
|
-
|
|
24
|
+
resetSettings: () => {
|
|
25
|
+
set({ settings: DEFAULT_SETTINGS });
|
|
26
|
+
},
|
|
27
|
+
setGlobalSettings: (settings) => {
|
|
23
28
|
set({ ...settings });
|
|
24
29
|
},
|
|
25
|
-
|
|
26
|
-
|
|
30
|
+
setSettings: (settings) => {
|
|
31
|
+
const oldSetting = get().settings;
|
|
32
|
+
set({ settings: { ...oldSetting, ...settings } });
|
|
33
|
+
},
|
|
34
|
+
setThemeMode: (themeMode) => {
|
|
35
|
+
get().setSettings({ themeMode });
|
|
27
36
|
},
|
|
28
37
|
switchSideBar: (key) => {
|
|
29
38
|
set({ sidebarKey: key });
|
|
@@ -1,22 +1,38 @@
|
|
|
1
|
-
import type { ThemeMode } from 'antd-style';
|
|
2
|
-
|
|
3
1
|
import type { ConfigSettings } from '@/types/exportConfig';
|
|
4
2
|
|
|
5
3
|
export type SidebarTabKey = 'chat' | 'market';
|
|
6
4
|
export const DEFAULT_SETTINGS: ConfigSettings = {
|
|
5
|
+
accessCode: '',
|
|
7
6
|
avatar: '',
|
|
7
|
+
compressThreshold: 24,
|
|
8
|
+
enableCompressThreshold: true,
|
|
9
|
+
enableHistoryCount: true,
|
|
10
|
+
enableMaxTokens: true,
|
|
11
|
+
endpoint: '',
|
|
12
|
+
fontSize: 14,
|
|
13
|
+
frequencyPenalty: 0,
|
|
14
|
+
historyCount: 24,
|
|
15
|
+
language: 'zh-CN',
|
|
16
|
+
maxTokens: 2000,
|
|
17
|
+
model: 'gpt-3.5-turbo',
|
|
18
|
+
neutralColor: '',
|
|
19
|
+
presencePenalty: 0,
|
|
20
|
+
primaryColor: '',
|
|
21
|
+
temperature: 0.5,
|
|
22
|
+
themeMode: 'auto',
|
|
23
|
+
token: '',
|
|
24
|
+
topP: 1,
|
|
8
25
|
};
|
|
9
26
|
|
|
10
|
-
export interface
|
|
27
|
+
export interface GlobalSettingsState {
|
|
11
28
|
inputHeight: number;
|
|
12
29
|
sessionExpandable?: boolean;
|
|
13
30
|
sessionsWidth: number;
|
|
14
31
|
settings: ConfigSettings;
|
|
15
32
|
sidebarKey: SidebarTabKey;
|
|
16
|
-
themeMode?: ThemeMode;
|
|
17
33
|
}
|
|
18
34
|
|
|
19
|
-
export const initialState:
|
|
35
|
+
export const initialState: GlobalSettingsState = {
|
|
20
36
|
inputHeight: 200,
|
|
21
37
|
sessionExpandable: true,
|
|
22
38
|
sessionsWidth: 320,
|
|
@@ -1,9 +1,17 @@
|
|
|
1
|
+
import { defaults } from 'lodash-es';
|
|
2
|
+
|
|
1
3
|
import { DEFAULT_SETTINGS } from '@/store/settings/initialState';
|
|
2
4
|
|
|
3
5
|
import { SettingsStore } from './store';
|
|
4
6
|
|
|
5
|
-
const currentSettings = (s: SettingsStore) => s.settings
|
|
7
|
+
const currentSettings = (s: SettingsStore) => defaults(s.settings, DEFAULT_SETTINGS);
|
|
8
|
+
|
|
9
|
+
const selecThemeMode = (s: SettingsStore) => ({
|
|
10
|
+
setThemeMode: s.setThemeMode,
|
|
11
|
+
themeMode: s.settings.themeMode,
|
|
12
|
+
});
|
|
6
13
|
|
|
7
14
|
export const settingsSelectors = {
|
|
8
15
|
currentSettings,
|
|
16
|
+
selecThemeMode,
|
|
9
17
|
};
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { StateCreator } from 'zustand/vanilla';
|
|
2
2
|
|
|
3
3
|
import { type SettingsAction, createSettings } from './action';
|
|
4
|
-
import { type
|
|
4
|
+
import { type GlobalSettingsState, initialState } from './initialState';
|
|
5
5
|
|
|
6
|
-
export type SettingsStore = SettingsAction &
|
|
6
|
+
export type SettingsStore = SettingsAction & GlobalSettingsState;
|
|
7
7
|
|
|
8
8
|
export const createStore: StateCreator<SettingsStore, [['zustand/devtools', never]]> = (
|
|
9
9
|
...parameters
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Theme, css } from 'antd-style';
|
|
2
|
-
import {
|
|
2
|
+
import { readableColor } from 'polished';
|
|
3
3
|
|
|
4
4
|
export default (token: Theme) => css`
|
|
5
5
|
.ant-btn {
|
|
@@ -10,20 +10,24 @@ export default (token: Theme) => css`
|
|
|
10
10
|
z-index: 1100;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
.ant-
|
|
13
|
+
.ant-slider-track,
|
|
14
|
+
.ant-tabs-ink-bar,
|
|
15
|
+
.ant-switch-checked {
|
|
16
|
+
background: ${token.colorPrimary} !important;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.ant-btn-primary:not(.ant-btn-dangerous) {
|
|
20
|
+
color: ${readableColor(token.colorPrimary)};
|
|
14
21
|
background: ${token.colorPrimary};
|
|
15
|
-
box-shadow: 0 6px 16px 0 ${rgba(token.colorPrimary, 0.1)},
|
|
16
|
-
0 3px 6px -4px ${rgba(token.colorPrimary, 0.2)},
|
|
17
|
-
0 9px 28px 8px ${rgba(token.colorPrimary, 0.1)};
|
|
18
22
|
|
|
19
|
-
|
|
20
|
-
color: ${token.
|
|
23
|
+
&:hover {
|
|
24
|
+
color: ${readableColor(token.colorPrimary)} !important;
|
|
25
|
+
background: ${token.colorPrimaryHover} !important;
|
|
21
26
|
}
|
|
22
27
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
color: ${token.colorTextLightSolid};
|
|
28
|
+
&:active {
|
|
29
|
+
color: ${readableColor(token.colorPrimaryActive)} !important;
|
|
30
|
+
background: ${token.colorPrimaryActive} !important;
|
|
27
31
|
}
|
|
28
32
|
}
|
|
29
33
|
`;
|
|
@@ -1,11 +1,32 @@
|
|
|
1
|
+
import type { NeutralColors, PrimaryColors } from '@lobehub/ui';
|
|
2
|
+
import { ThemeMode } from 'antd-style';
|
|
3
|
+
|
|
4
|
+
import { Locales } from './locale';
|
|
5
|
+
|
|
1
6
|
/**
|
|
2
7
|
* 配置设置
|
|
3
8
|
*/
|
|
4
9
|
export interface ConfigSettings {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
10
|
+
accessCode: string;
|
|
11
|
+
avatar: string;
|
|
12
|
+
compressThreshold: number;
|
|
13
|
+
enableCompressThreshold: boolean;
|
|
14
|
+
enableHistoryCount: boolean;
|
|
15
|
+
enableMaxTokens: boolean;
|
|
16
|
+
endpoint: string;
|
|
17
|
+
fontSize: number;
|
|
18
|
+
frequencyPenalty: number;
|
|
19
|
+
historyCount: number;
|
|
20
|
+
language: Locales;
|
|
21
|
+
maxTokens: number;
|
|
22
|
+
model: string;
|
|
23
|
+
neutralColor: NeutralColors | '';
|
|
24
|
+
presencePenalty: number;
|
|
25
|
+
primaryColor: PrimaryColors | '';
|
|
26
|
+
temperature: number;
|
|
27
|
+
themeMode: ThemeMode;
|
|
28
|
+
token: string;
|
|
29
|
+
topP: number;
|
|
9
30
|
}
|
|
10
31
|
|
|
11
32
|
export type ConfigKeys = keyof ConfigSettings;
|