@lobehub/chat 0.1.6

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 (149) hide show
  1. package/.changelogrc.js +1 -0
  2. package/.commitlintrc.js +1 -0
  3. package/.editorconfig +16 -0
  4. package/.eslintignore +32 -0
  5. package/.eslintrc.js +6 -0
  6. package/.github/ISSUE_TEMPLATE/1_bug_report.yml +45 -0
  7. package/.github/ISSUE_TEMPLATE/2_feature_request.yml +21 -0
  8. package/.github/ISSUE_TEMPLATE/3_question.yml +15 -0
  9. package/.github/ISSUE_TEMPLATE/4_other.md +7 -0
  10. package/.github/PULL_REQUEST_TEMPLATE.md +17 -0
  11. package/.github/dependabot.yml +17 -0
  12. package/.github/workflows/auto-merge.yml +32 -0
  13. package/.github/workflows/contributor-help.yml +29 -0
  14. package/.github/workflows/issue-check-inactive.yml +22 -0
  15. package/.github/workflows/issue-close-require.yml +46 -0
  16. package/.github/workflows/issue-remove-inactive.yml +25 -0
  17. package/.github/workflows/release.yml +34 -0
  18. package/.github/workflows/test.yml +30 -0
  19. package/.gitpod.yml +3 -0
  20. package/.husky/commit-msg +4 -0
  21. package/.husky/pre-commit +5 -0
  22. package/.i18nrc.js +13 -0
  23. package/.prettierignore +63 -0
  24. package/.prettierrc.js +1 -0
  25. package/.releaserc.js +1 -0
  26. package/.remarkrc.js +1 -0
  27. package/.stylelintrc.js +8 -0
  28. package/CHANGELOG.md +20 -0
  29. package/README.md +147 -0
  30. package/locales/en_US/common.json +41 -0
  31. package/locales/en_US/setting.json +117 -0
  32. package/locales/zh_CN/common.json +41 -0
  33. package/locales/zh_CN/setting.json +117 -0
  34. package/next.config.mjs +32 -0
  35. package/package.json +140 -0
  36. package/public/next.svg +1 -0
  37. package/public/vercel.svg +1 -0
  38. package/scripts/genDefaultLocale.ts +18 -0
  39. package/scripts/genResources.ts +51 -0
  40. package/scripts/toc.ts +7 -0
  41. package/src/const/fetch.ts +1 -0
  42. package/src/const/modelTokens.ts +8 -0
  43. package/src/features/AvatarWithUpload/index.tsx +27 -0
  44. package/src/features/FolderPanel/index.tsx +55 -0
  45. package/src/features/SideBar/index.tsx +35 -0
  46. package/src/helpers/prompt.test.ts +36 -0
  47. package/src/helpers/prompt.ts +36 -0
  48. package/src/helpers/url.ts +17 -0
  49. package/src/layout/index.tsx +57 -0
  50. package/src/layout/style.ts +18 -0
  51. package/src/locales/create.ts +49 -0
  52. package/src/locales/default/common.ts +41 -0
  53. package/src/locales/default/setting.ts +117 -0
  54. package/src/locales/index.ts +5 -0
  55. package/src/locales/options.ts +19 -0
  56. package/src/locales/resources/en_US.ts +9 -0
  57. package/src/locales/resources/index.ts +13 -0
  58. package/src/locales/resources/zh_CN.ts +9 -0
  59. package/src/migrations/FromV0ToV1.ts +12 -0
  60. package/src/migrations/index.ts +13 -0
  61. package/src/pages/_app.page.tsx +13 -0
  62. package/src/pages/_document.page.tsx +70 -0
  63. package/src/pages/api/LangChainStream.ts +95 -0
  64. package/src/pages/api/chain.api.ts +17 -0
  65. package/src/pages/api/openai.api.ts +31 -0
  66. package/src/pages/chat/SessionList/Header.tsx +56 -0
  67. package/src/pages/chat/SessionList/List/SessionItem.tsx +90 -0
  68. package/src/pages/chat/SessionList/List/index.tsx +31 -0
  69. package/src/pages/chat/SessionList/List/style.ts +77 -0
  70. package/src/pages/chat/SessionList/index.tsx +18 -0
  71. package/src/pages/chat/[id]/Config/ConfigCell.tsx +68 -0
  72. package/src/pages/chat/[id]/Config/ReadMode.tsx +63 -0
  73. package/src/pages/chat/[id]/Config/index.tsx +79 -0
  74. package/src/pages/chat/[id]/Conversation/ChatList.tsx +36 -0
  75. package/src/pages/chat/[id]/Conversation/Input.tsx +61 -0
  76. package/src/pages/chat/[id]/Conversation/index.tsx +32 -0
  77. package/src/pages/chat/[id]/Header.tsx +86 -0
  78. package/src/pages/chat/[id]/edit/AgentConfig.tsx +95 -0
  79. package/src/pages/chat/[id]/edit/AgentMeta.tsx +117 -0
  80. package/src/pages/chat/[id]/edit/FormItem.tsx +26 -0
  81. package/src/pages/chat/[id]/edit/Prompt.tsx +68 -0
  82. package/src/pages/chat/[id]/edit/index.page.tsx +62 -0
  83. package/src/pages/chat/[id]/edit/style.ts +42 -0
  84. package/src/pages/chat/[id]/index.page.tsx +40 -0
  85. package/src/pages/chat/index.page.tsx +1 -0
  86. package/src/pages/chat/layout.tsx +51 -0
  87. package/src/pages/index.page.tsx +1 -0
  88. package/src/pages/setting/Header.tsx +27 -0
  89. package/src/pages/setting/SettingForm.tsx +299 -0
  90. package/src/pages/setting/SliderWithInput/index.tsx +40 -0
  91. package/src/pages/setting/ThemeSwatches/ThemeSwatchesNeutral.tsx +33 -0
  92. package/src/pages/setting/ThemeSwatches/ThemeSwatchesPrimary.tsx +33 -0
  93. package/src/pages/setting/ThemeSwatches/index.ts +2 -0
  94. package/src/pages/setting/index.page.tsx +41 -0
  95. package/src/prompts/agent.ts +65 -0
  96. package/src/services/chatModel.ts +34 -0
  97. package/src/services/langChain.ts +18 -0
  98. package/src/services/url.ts +8 -0
  99. package/src/store/middleware/createHashStorage.ts +49 -0
  100. package/src/store/session/index.ts +33 -0
  101. package/src/store/session/initialState.ts +11 -0
  102. package/src/store/session/selectors.ts +3 -0
  103. package/src/store/session/slices/agentConfig/action.ts +226 -0
  104. package/src/store/session/slices/agentConfig/index.ts +3 -0
  105. package/src/store/session/slices/agentConfig/initialState.ts +34 -0
  106. package/src/store/session/slices/agentConfig/selectors.ts +54 -0
  107. package/src/store/session/slices/chat/action.ts +210 -0
  108. package/src/store/session/slices/chat/index.ts +3 -0
  109. package/src/store/session/slices/chat/initialState.ts +12 -0
  110. package/src/store/session/slices/chat/messageReducer.test.ts +70 -0
  111. package/src/store/session/slices/chat/messageReducer.ts +84 -0
  112. package/src/store/session/slices/chat/selectors.ts +83 -0
  113. package/src/store/session/slices/session/action.ts +118 -0
  114. package/src/store/session/slices/session/index.ts +3 -0
  115. package/src/store/session/slices/session/initialState.ts +31 -0
  116. package/src/store/session/slices/session/reducers/session.test.ts +456 -0
  117. package/src/store/session/slices/session/reducers/session.ts +113 -0
  118. package/src/store/session/slices/session/selectors/chat.ts +4 -0
  119. package/src/store/session/slices/session/selectors/index.ts +20 -0
  120. package/src/store/session/slices/session/selectors/list.ts +65 -0
  121. package/src/store/session/store.ts +17 -0
  122. package/src/store/settings/action.ts +40 -0
  123. package/src/store/settings/index.ts +23 -0
  124. package/src/store/settings/initialState.ts +41 -0
  125. package/src/store/settings/selectors.ts +17 -0
  126. package/src/store/settings/store.ts +13 -0
  127. package/src/styles/antdOverride.ts +33 -0
  128. package/src/styles/global.ts +23 -0
  129. package/src/styles/index.ts +6 -0
  130. package/src/types/chatMessage.ts +46 -0
  131. package/src/types/exportConfig.ts +44 -0
  132. package/src/types/global.d.ts +14 -0
  133. package/src/types/i18next.d.ts +8 -0
  134. package/src/types/langchain.ts +34 -0
  135. package/src/types/llm.ts +49 -0
  136. package/src/types/locale.ts +1 -0
  137. package/src/types/meta.ts +26 -0
  138. package/src/types/openai.ts +62 -0
  139. package/src/types/session.ts +59 -0
  140. package/src/utils/VersionController.test.ts +90 -0
  141. package/src/utils/VersionController.ts +64 -0
  142. package/src/utils/compass.ts +94 -0
  143. package/src/utils/fetch.ts +132 -0
  144. package/src/utils/filter.test.ts +120 -0
  145. package/src/utils/filter.ts +29 -0
  146. package/src/utils/uploadFIle.ts +8 -0
  147. package/src/utils/uuid.ts +9 -0
  148. package/tsconfig.json +31 -0
  149. package/vitest.config.ts +11 -0
@@ -0,0 +1,42 @@
1
+ import { createStyles } from 'antd-style';
2
+
3
+ export const useStyles = createStyles(({ css, token }) => ({
4
+ collapseHeader: css`
5
+ .ant-collapse-header {
6
+ align-items: center !important;
7
+ }
8
+ `,
9
+ footer: css`
10
+ position: sticky;
11
+ bottom: 0;
12
+ border-top: 1px solid ${token.colorBorder};
13
+ `,
14
+ form: css`
15
+ overflow-y: auto;
16
+ `,
17
+ header: css`
18
+ background: ${token.colorBgContainer};
19
+ border-bottom: 1px solid ${token.colorSplit};
20
+ `,
21
+ profile: css`
22
+ font-size: 20px;
23
+ font-weight: bold;
24
+ color: ${token.colorText};
25
+ `,
26
+ prompt: css`
27
+ padding: 12px;
28
+ background: ${token.colorFillTertiary};
29
+ `,
30
+ title: css`
31
+ font-size: 16px;
32
+ font-weight: 500;
33
+
34
+ .ant-collapse-header {
35
+ padding: 0 !important;
36
+ }
37
+
38
+ .ant-collapse-content-box {
39
+ padding: 0 !important;
40
+ }
41
+ `,
42
+ }));
@@ -0,0 +1,40 @@
1
+ import isEqual from 'fast-deep-equal';
2
+ import Head from 'next/head';
3
+ import { memo } from 'react';
4
+ import { Flexbox } from 'react-layout-kit';
5
+
6
+ import { sessionSelectors, useSessionStore } from '@/store/session';
7
+
8
+ import Layout from '../layout';
9
+ import Config from './Config';
10
+ import Conversation from './Conversation';
11
+ import Header from './Header';
12
+
13
+ const Chat = memo(() => {
14
+ const [title] = useSessionStore((s) => {
15
+ const context = sessionSelectors.currentSession(s);
16
+ return [context?.meta.title];
17
+ }, isEqual);
18
+
19
+ const pageTitle = title ? `${title} - LobeChat` : 'LobeChat';
20
+
21
+ return (
22
+ <Layout>
23
+ <Head>
24
+ <title>{pageTitle}</title>
25
+ </Head>
26
+
27
+ <Flexbox flex={1}>
28
+ <Header />
29
+ <Flexbox
30
+ id={'lobe-conversion-container'}
31
+ style={{ height: 'calc(100vh - 64px)', position: 'relative' }}
32
+ >
33
+ <Conversation />
34
+ <Config />
35
+ </Flexbox>
36
+ </Flexbox>
37
+ </Layout>
38
+ );
39
+ });
40
+ export default Chat;
@@ -0,0 +1 @@
1
+ export { default } from './[id]/index.page';
@@ -0,0 +1,51 @@
1
+ import { useRouter } from 'next/router';
2
+ import { PropsWithChildren, memo, useEffect } from 'react';
3
+ import { Flexbox } from 'react-layout-kit';
4
+ import { shallow } from 'zustand/shallow';
5
+
6
+ import SideBar from '@/features/SideBar';
7
+ import { createI18nNext } from '@/locales/create';
8
+ import { useSessionStore } from '@/store/session';
9
+ import { useSettings } from '@/store/settings';
10
+
11
+ import { Sessions } from './SessionList';
12
+
13
+ const initI18n = createI18nNext();
14
+
15
+ const ChatLayout = memo<PropsWithChildren>(({ children }) => {
16
+ useEffect(() => {
17
+ initI18n.finally();
18
+ }, []);
19
+
20
+ const [activeSession] = useSessionStore((s) => {
21
+ return [s.activeSession];
22
+ }, shallow);
23
+
24
+ const router = useRouter();
25
+ const { id } = router.query;
26
+
27
+ useEffect(() => {
28
+ const hasRehydrated = useSessionStore.persist.hasHydrated();
29
+ // 只有当水合完毕后,才能正常去激活会话
30
+ if (typeof id === 'string' && hasRehydrated) {
31
+ activeSession(id);
32
+ }
33
+ }, [id]);
34
+
35
+ useEffect(() => {
36
+ const hasRehydrated = useSettings.persist.hasHydrated();
37
+ if (hasRehydrated) {
38
+ useSettings.setState({ sidebarKey: 'chat' });
39
+ }
40
+ }, []);
41
+
42
+ return (
43
+ <Flexbox horizontal width={'100%'}>
44
+ <SideBar />
45
+ <Sessions />
46
+ {children}
47
+ </Flexbox>
48
+ );
49
+ });
50
+
51
+ export default ChatLayout;
@@ -0,0 +1 @@
1
+ export { default } from './chat/index.page';
@@ -0,0 +1,27 @@
1
+ import { ChatHeader } from '@lobehub/ui';
2
+ import { createStyles } from 'antd-style';
3
+ import { useTranslation } from 'react-i18next';
4
+ import Router from 'next/router';
5
+ import { memo } from 'react';
6
+
7
+ const useStyles = createStyles(({ css, token }) => ({
8
+ title: css`
9
+ font-size: 16px;
10
+ font-weight: bold;
11
+ color: ${token.colorText};
12
+ `,
13
+ }));
14
+ const Header = memo(() => {
15
+ const { t } = useTranslation('setting');
16
+
17
+ const { styles } = useStyles();
18
+ return (
19
+ <ChatHeader
20
+ left={<div className={styles.title}>{t('header')}</div>}
21
+ onBackClick={() => Router.back()}
22
+ showBackButton
23
+ />
24
+ );
25
+ });
26
+
27
+ export default Header;
@@ -0,0 +1,299 @@
1
+ import { Form, type ItemGroup, ThemeSwitch } from '@lobehub/ui';
2
+ import { Form as AntForm, Button, Input, Popconfirm, Select, Switch } from 'antd';
3
+ import isEqual from 'fast-deep-equal';
4
+ import { debounce } from 'lodash-es';
5
+ import { AppWindow, Bot, MessagesSquare, Palette, Webhook } from 'lucide-react';
6
+ import { memo, useCallback, useMemo } from 'react';
7
+ import { useTranslation } from 'react-i18next';
8
+ import { shallow } from 'zustand/shallow';
9
+
10
+ import AvatarWithUpload from '@/features/AvatarWithUpload';
11
+ import { options } from '@/locales/options';
12
+ import SliderWithInput from '@/pages/setting/SliderWithInput';
13
+ import { settingsSelectors, useSettings } from '@/store/settings';
14
+ import { DEFAULT_SETTINGS } from '@/store/settings/initialState';
15
+ import { ConfigKeys } from '@/types/exportConfig';
16
+ import { LanguageModel } from '@/types/llm';
17
+
18
+ import { ThemeSwatchesNeutral, ThemeSwatchesPrimary } from './ThemeSwatches';
19
+
20
+ type SettingItemGroup = ItemGroup & {
21
+ children: {
22
+ name?: ConfigKeys;
23
+ }[];
24
+ };
25
+
26
+ const SettingForm = memo(() => {
27
+ const { t } = useTranslation('setting');
28
+ const [form] = AntForm.useForm();
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
+ );
38
+
39
+ const handleReset = useCallback(() => {
40
+ resetSettings();
41
+ form.setFieldsValue(DEFAULT_SETTINGS);
42
+ }, []);
43
+
44
+ const handleClear = useCallback(() => {
45
+ handleReset();
46
+ // TODO: 删除聊天记录
47
+ }, []);
48
+
49
+ const theme: SettingItemGroup = useMemo(
50
+ () => ({
51
+ children: [
52
+ {
53
+ children: <AvatarWithUpload />,
54
+ label: t('settingTheme.avatar.title'),
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,
94
+ },
95
+ ],
96
+ icon: Palette,
97
+ title: t('settingTheme.title'),
98
+ }),
99
+ [settings],
100
+ );
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
+
287
+ return (
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
+ />
296
+ );
297
+ });
298
+
299
+ export default SettingForm;
@@ -0,0 +1,40 @@
1
+ import { InputNumber, Slider } from 'antd';
2
+ import { SliderSingleProps } from 'antd/es/slider';
3
+ import { isNull } from 'lodash-es';
4
+ import { memo, useCallback } from 'react';
5
+ import { Flexbox } from 'react-layout-kit';
6
+
7
+ const SliderWithInput = memo<SliderSingleProps>(
8
+ ({ step, value, onChange, max, min, defaultValue, ...props }) => {
9
+ const handleOnchange = useCallback((value: number | null) => {
10
+ if (Number.isNaN(value) || isNull(value)) return;
11
+ onChange?.(value);
12
+ }, []);
13
+
14
+ return (
15
+ <Flexbox direction={'horizontal'} gap={8}>
16
+ <Slider
17
+ defaultValue={defaultValue}
18
+ max={max}
19
+ min={min}
20
+ onChange={handleOnchange}
21
+ step={step}
22
+ style={{ flex: 1 }}
23
+ value={typeof value === 'number' ? value : 0}
24
+ {...props}
25
+ />
26
+ <InputNumber
27
+ defaultValue={defaultValue}
28
+ max={max}
29
+ min={min}
30
+ onChange={handleOnchange}
31
+ step={Number(step)}
32
+ style={{ flex: 1, maxWidth: 64 }}
33
+ value={typeof value === 'number' ? value : 0}
34
+ />
35
+ </Flexbox>
36
+ );
37
+ },
38
+ );
39
+
40
+ 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;
@@ -0,0 +1,2 @@
1
+ export { default as ThemeSwatchesNeutral } from './ThemeSwatchesNeutral';
2
+ export { default as ThemeSwatchesPrimary } from './ThemeSwatchesPrimary';
@@ -0,0 +1,41 @@
1
+ import Head from 'next/head';
2
+ import { memo, useEffect } from 'react';
3
+ import { useTranslation } from 'react-i18next';
4
+ import { Flexbox } from 'react-layout-kit';
5
+
6
+ import SideBar from '@/features/SideBar';
7
+ import { createI18nNext } from '@/locales/create';
8
+ import { Sessions } from '@/pages/chat/SessionList';
9
+
10
+ import Header from './Header';
11
+ import SettingForm from './SettingForm';
12
+
13
+ const initI18n = createI18nNext('setting');
14
+
15
+ const SettingLayout = memo(() => {
16
+ const { t } = useTranslation('setting');
17
+ const pageTitle = `${t('header')} - LobeChat`;
18
+
19
+ useEffect(() => {
20
+ initI18n.finally();
21
+ }, []);
22
+ return (
23
+ <>
24
+ <Head>
25
+ <title>{pageTitle}</title>
26
+ </Head>
27
+ <Flexbox height={'100vh'} horizontal width={'100%'}>
28
+ <SideBar />
29
+ <Sessions />
30
+ <Flexbox flex={1}>
31
+ <Header />
32
+ <Flexbox align={'center'} flex={1} padding={24} style={{ overflow: 'auto' }}>
33
+ <SettingForm />
34
+ </Flexbox>
35
+ </Flexbox>
36
+ </Flexbox>
37
+ </>
38
+ );
39
+ });
40
+
41
+ export default SettingLayout;
@@ -0,0 +1,65 @@
1
+ import { OpenAIStreamPayload } from '@/types/openai';
2
+
3
+ // 自动起名
4
+ export const promptSummaryAgentName = (content: string): Partial<OpenAIStreamPayload> => ({
5
+ messages: [
6
+ {
7
+ content: `你是一名擅长起名的起名大师,你需要将用户的描述总结为 20 个字以内的角色,格式要求如下:
8
+ 输入: {文本作为JSON引用字符串}
9
+ 输出: {角色名}
10
+ `,
11
+ role: 'system',
12
+ },
13
+ {
14
+ content: `输入: {你是一名专业的前端开发者,擅长结合 vitest 和\`testing-library/react\` 书写单元测试。接下来用户会输入一串 ts 代码,你需要给出完善的单元测试。\n你需要注意,单元测试代码中,不应该使用 jest 。如果需要使用 \`jest.fn\`,请使用 \`vi.fn\` 替换}`,
15
+ role: 'user',
16
+ },
17
+ { content: '前端 vitest 测试专家', role: 'assistant' },
18
+ {
19
+ content: `输入: {你是一名前端专家,请将下面的代码转成 ts,不要修改实现。如果原本 js 中没有定义的全局变量,需要补充 declare 的类型声明。}`,
20
+ role: 'user',
21
+ },
22
+ { content: 'js 转 ts 专家', role: 'assistant' },
23
+ {
24
+ content: `输入:{你是一名擅长比喻和隐喻的UX Writter。用户会输入文案,你需要给出3个优化后的结果,使用 markdown格式的文本。下面是一个例子:
25
+ 输入:页面加载中
26
+ 输出:页面似乎在思考,一会儿才能准备好}`,
27
+ role: 'user',
28
+ },
29
+ { content: '文案比喻优化专家', role: 'assistant' },
30
+ { content: `输入: {${content}}`, role: 'user' },
31
+ ],
32
+ });
33
+
34
+ // 自动挑选 emoji 和背景色
35
+ export const promptPickEmoji = (content: string): Partial<OpenAIStreamPayload> => ({
36
+ messages: [
37
+ {
38
+ content: `你是一名非常懂设计与时尚的设计师,你需要从用户的描述中匹配一个合适的 emoji。
39
+ 输入:你是一名精通体验设计的设计系统设计师,设计系统存在诸多类别的 token,比如品牌色、成功色等,你需要为各个类别的 token 提供说明文案。
40
+ 输出: 💅
41
+
42
+ 输入:用户会输入一串 ts 代码,为了确保所有功能和分支的 100% 的覆盖率,你需要给出需要考虑哪些数据场景。
43
+ 输出: 🧪
44
+ `,
45
+ role: 'system',
46
+ },
47
+ {
48
+ content: `输入:${content}`,
49
+ role: 'user',
50
+ },
51
+ ],
52
+ });
53
+
54
+ export const promptSummaryDescription = (content: string): Partial<OpenAIStreamPayload> => ({
55
+ messages: [
56
+ {
57
+ content: '你是一名擅长会话的助理,你需要将用户的提示词做一个3句话以内的总结。',
58
+ role: 'system',
59
+ },
60
+ {
61
+ content: `输入:${content}`,
62
+ role: 'user',
63
+ },
64
+ ],
65
+ });