@lobehub/chat 0.152.6 → 0.152.8

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 (206) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/docs/self-hosting/advanced/settings-url-share.mdx +100 -0
  3. package/docs/self-hosting/advanced/settings-url-share.zh-CN.mdx +100 -0
  4. package/locales/ar/common.json +1 -0
  5. package/locales/ar/setting.json +4 -1
  6. package/locales/bg-BG/common.json +1 -0
  7. package/locales/bg-BG/setting.json +4 -1
  8. package/locales/de-DE/common.json +1 -0
  9. package/locales/de-DE/setting.json +4 -1
  10. package/locales/en-US/common.json +1 -0
  11. package/locales/en-US/setting.json +4 -1
  12. package/locales/es-ES/common.json +1 -0
  13. package/locales/es-ES/setting.json +4 -1
  14. package/locales/fr-FR/common.json +1 -0
  15. package/locales/fr-FR/setting.json +4 -1
  16. package/locales/it-IT/common.json +1 -0
  17. package/locales/it-IT/setting.json +4 -1
  18. package/locales/ja-JP/common.json +1 -0
  19. package/locales/ja-JP/setting.json +4 -1
  20. package/locales/ko-KR/common.json +1 -0
  21. package/locales/ko-KR/setting.json +4 -1
  22. package/locales/nl-NL/common.json +1 -0
  23. package/locales/nl-NL/setting.json +4 -1
  24. package/locales/pl-PL/common.json +1 -0
  25. package/locales/pl-PL/setting.json +4 -1
  26. package/locales/pt-BR/common.json +1 -0
  27. package/locales/pt-BR/setting.json +4 -1
  28. package/locales/ru-RU/common.json +1 -0
  29. package/locales/ru-RU/setting.json +4 -1
  30. package/locales/tr-TR/common.json +1 -0
  31. package/locales/tr-TR/setting.json +4 -1
  32. package/locales/vi-VN/common.json +1 -0
  33. package/locales/vi-VN/setting.json +4 -1
  34. package/locales/zh-CN/common.json +1 -0
  35. package/locales/zh-CN/setting.json +4 -1
  36. package/locales/zh-TW/common.json +1 -0
  37. package/locales/zh-TW/setting.json +4 -1
  38. package/package.json +1 -1
  39. package/src/app/(main)/(mobile)/me/features/AvatarBanner.tsx +52 -0
  40. package/src/app/(main)/(mobile)/me/features/Cate.tsx +35 -0
  41. package/src/app/(main)/(mobile)/me/features/ExtraCate.tsx +26 -0
  42. package/src/app/(main)/(mobile)/me/features/useExtraCate.tsx +62 -0
  43. package/src/app/(main)/(mobile)/me/layout.tsx +11 -0
  44. package/src/app/(main)/(mobile)/me/loading.tsx +17 -0
  45. package/src/app/(main)/(mobile)/me/page.tsx +31 -0
  46. package/src/app/(main)/@nav/_layout/Desktop/index.tsx +1 -1
  47. package/src/app/(main)/@nav/_layout/Mobile.tsx +3 -3
  48. package/src/app/(main)/chat/(mobile)/features/SessionHeader.tsx +3 -3
  49. package/src/app/(main)/chat/_layout/Desktop/index.tsx +6 -8
  50. package/src/app/(main)/chat/_layout/Mobile/index.tsx +5 -3
  51. package/src/app/(main)/chat/_layout/type.ts +5 -0
  52. package/src/app/(main)/chat/features/SettingButton.tsx +3 -4
  53. package/src/app/(main)/chat/features/ShareButton/ShareModal.tsx +2 -2
  54. package/src/app/(main)/chat/layout.ts +5 -2
  55. package/src/app/(main)/chat/settings/{(desktop) → _layout/Desktop}/Header.tsx +2 -0
  56. package/src/app/(main)/chat/settings/_layout/Desktop/index.tsx +28 -0
  57. package/src/app/(main)/chat/settings/{(mobile) → _layout/Mobile}/Header.tsx +3 -2
  58. package/src/app/(main)/chat/settings/_layout/Mobile/index.tsx +15 -0
  59. package/src/app/(main)/chat/settings/error.tsx +5 -0
  60. package/src/app/(main)/chat/settings/features/HeaderContent.tsx +15 -9
  61. package/src/app/(main)/chat/settings/features/SubmitAgentButton/SubmitAgentModal.tsx +2 -0
  62. package/src/app/(main)/chat/settings/features/SubmitAgentButton/index.tsx +16 -8
  63. package/src/app/(main)/chat/settings/layout.tsx +9 -2
  64. package/src/app/(main)/chat/settings/loading.tsx +3 -0
  65. package/src/app/(main)/chat/settings/not-found.tsx +3 -0
  66. package/src/app/(main)/chat/settings/page.tsx +2 -9
  67. package/src/app/(main)/market/@detail/default.tsx +1 -10
  68. package/src/app/(main)/market/{@detail/_layout/Desktop.tsx → _layout/Desktop/DetailSidebar.tsx} +2 -2
  69. package/src/app/(main)/market/_layout/Desktop/index.tsx +2 -1
  70. package/src/app/(main)/market/{@detail/_layout/Mobile.tsx → _layout/Mobile/DetailModal.tsx} +2 -2
  71. package/src/app/(main)/market/_layout/Mobile/index.tsx +3 -1
  72. package/src/app/(main)/settings/@category/default.tsx +16 -0
  73. package/src/app/(main)/settings/@category/features/CategoryContent.tsx +33 -0
  74. package/src/app/(main)/settings/@category/features/UpgradeAlert.tsx +38 -0
  75. package/src/app/(main)/settings/_layout/Desktop/Header.tsx +78 -23
  76. package/src/app/(main)/settings/_layout/Desktop/SideBar.tsx +39 -27
  77. package/src/app/(main)/settings/_layout/Desktop/index.tsx +41 -17
  78. package/src/app/(main)/settings/_layout/Mobile/{SubSettingHeader.tsx → Header.tsx} +3 -1
  79. package/src/app/(main)/settings/_layout/Mobile/index.tsx +7 -18
  80. package/src/app/(main)/settings/_layout/type.ts +6 -0
  81. package/src/app/(main)/settings/about/features/AboutList.tsx +134 -0
  82. package/src/app/(main)/settings/about/features/Analytics.tsx +42 -0
  83. package/src/app/(main)/settings/about/index.tsx +46 -0
  84. package/src/app/(main)/settings/about/page.tsx +13 -33
  85. package/src/app/(main)/settings/agent/{Agent.tsx → index.tsx} +8 -4
  86. package/src/app/(main)/settings/agent/page.tsx +8 -16
  87. package/src/app/(main)/settings/common/{Common.tsx → features/Common.tsx} +6 -4
  88. package/src/app/(main)/settings/common/{Theme.tsx → features/Theme/index.tsx} +8 -6
  89. package/src/app/(main)/settings/common/index.tsx +11 -16
  90. package/src/app/(main)/settings/common/page.tsx +8 -7
  91. package/src/app/(main)/settings/error.tsx +5 -0
  92. package/src/app/(main)/settings/features/Footer.tsx +2 -0
  93. package/src/app/(main)/settings/features/UpgradeAlert.tsx +21 -13
  94. package/src/app/(main)/settings/hooks/useCategory.tsx +54 -0
  95. package/src/app/(main)/settings/hooks/useSyncSettings.ts +2 -2
  96. package/src/app/(main)/settings/layout.ts +4 -1
  97. package/src/app/(main)/settings/llm/Anthropic/index.tsx +4 -8
  98. package/src/app/(main)/settings/llm/Azure/index.tsx +3 -1
  99. package/src/app/(main)/settings/llm/Bedrock/index.tsx +3 -1
  100. package/src/app/(main)/settings/llm/Google/index.tsx +4 -2
  101. package/src/app/(main)/settings/llm/Groq/index.tsx +3 -1
  102. package/src/app/(main)/settings/llm/Minimax/index.tsx +3 -9
  103. package/src/app/(main)/settings/llm/Mistral/index.tsx +3 -9
  104. package/src/app/(main)/settings/llm/Moonshot/index.tsx +3 -1
  105. package/src/app/(main)/settings/llm/Ollama/index.tsx +3 -1
  106. package/src/app/(main)/settings/llm/OpenAI/index.tsx +2 -0
  107. package/src/app/(main)/settings/llm/OpenRouter/index.tsx +3 -9
  108. package/src/app/(main)/settings/llm/Perplexity/index.tsx +3 -9
  109. package/src/app/(main)/settings/llm/TogetherAI/index.tsx +3 -9
  110. package/src/app/(main)/settings/llm/ZeroOne/index.tsx +3 -9
  111. package/src/app/(main)/settings/llm/Zhipu/index.tsx +3 -10
  112. package/src/app/(main)/settings/llm/components/Checker.tsx +2 -0
  113. package/src/app/(main)/settings/llm/components/Footer.tsx +26 -0
  114. package/src/app/(main)/settings/llm/components/ProviderConfig/index.tsx +29 -3
  115. package/src/app/(main)/settings/llm/index.tsx +11 -23
  116. package/src/app/(main)/settings/llm/page.tsx +15 -0
  117. package/src/app/(main)/settings/loading.tsx +9 -0
  118. package/src/app/(main)/settings/not-found.tsx +3 -0
  119. package/src/app/(main)/settings/page.tsx +2 -14
  120. package/src/app/(main)/settings/sync/{DeviceInfo → features/DeviceInfo}/Card.tsx +6 -5
  121. package/src/app/(main)/settings/sync/features/DeviceInfo/DeviceName.tsx +63 -0
  122. package/src/app/(main)/settings/sync/{components → features/DeviceInfo}/SystemIcon.tsx +6 -14
  123. package/src/app/(main)/settings/sync/{DeviceInfo → features/DeviceInfo}/index.tsx +22 -36
  124. package/src/app/(main)/settings/sync/{WebRTC → features/WebRTC}/ChannelNameInput.tsx +3 -3
  125. package/src/app/(main)/settings/sync/{WebRTC → features/WebRTC}/index.tsx +10 -9
  126. package/src/app/(main)/settings/sync/index.tsx +17 -0
  127. package/src/app/(main)/settings/sync/page.tsx +11 -15
  128. package/src/app/(main)/settings/tts/features/OpenAI.tsx +54 -0
  129. package/src/app/(main)/settings/tts/{TTS/index.tsx → features/STT.tsx} +11 -27
  130. package/src/app/(main)/settings/tts/index.tsx +15 -0
  131. package/src/app/(main)/settings/tts/page.tsx +8 -16
  132. package/src/app/layout.tsx +6 -2
  133. package/src/components/BrandWatermark/index.tsx +39 -0
  134. package/src/components/Cell/Divider.tsx +19 -0
  135. package/src/components/Cell/index.tsx +38 -0
  136. package/src/components/Menu/index.tsx +97 -0
  137. package/src/components/SkeletonLoading/index.tsx +21 -0
  138. package/src/const/url.ts +2 -0
  139. package/src/features/AgentSetting/AgentChat/index.tsx +135 -0
  140. package/src/features/AgentSetting/AgentMeta/index.tsx +4 -3
  141. package/src/features/AgentSetting/AgentModal/index.tsx +95 -0
  142. package/src/features/AgentSetting/AgentPlugin/index.tsx +65 -66
  143. package/src/features/AgentSetting/AgentPrompt/index.tsx +101 -47
  144. package/src/features/AgentSetting/AgentTTS/index.tsx +4 -0
  145. package/src/features/AgentSetting/StoreUpdater.tsx +2 -0
  146. package/src/features/AgentSetting/index.tsx +6 -6
  147. package/src/features/AgentSetting/store/index.ts +2 -0
  148. package/src/features/AvatarWithUpload/index.tsx +4 -2
  149. package/src/hooks/useQuery.test.ts +20 -0
  150. package/src/hooks/useQuery.ts +7 -0
  151. package/src/hooks/useQueryRoute.test.ts +86 -0
  152. package/src/hooks/useQueryRoute.ts +46 -0
  153. package/src/layout/GlobalProvider/index.tsx +7 -1
  154. package/src/locales/default/common.ts +1 -0
  155. package/src/locales/default/setting.ts +3 -0
  156. package/src/services/chat.ts +2 -2
  157. package/src/store/chat/slices/message/selectors.ts +2 -2
  158. package/src/store/global/initialState.ts +1 -0
  159. package/src/store/serverConfig/Provider.tsx +3 -2
  160. package/src/store/serverConfig/selectors.ts +1 -0
  161. package/src/store/serverConfig/store.ts +1 -0
  162. package/src/store/user/initialState.ts +5 -3
  163. package/src/store/user/selectors.ts +1 -1
  164. package/src/store/user/slices/auth/action.test.ts +118 -0
  165. package/src/store/user/slices/auth/action.ts +81 -0
  166. package/src/store/user/slices/auth/initialState.ts +20 -0
  167. package/src/store/user/slices/auth/selectors.ts +6 -0
  168. package/src/store/user/slices/common/action.test.ts +1 -224
  169. package/src/store/user/slices/common/action.ts +3 -112
  170. package/src/store/user/slices/settings/initialState.ts +0 -2
  171. package/src/store/user/slices/sync/action.test.ts +150 -0
  172. package/src/store/user/slices/sync/action.ts +94 -0
  173. package/src/store/user/slices/{common → sync}/initialState.ts +2 -7
  174. package/src/store/user/store.ts +11 -2
  175. package/src/app/(main)/chat/settings/(desktop)/index.tsx +0 -23
  176. package/src/app/(main)/chat/settings/(mobile)/index.tsx +0 -16
  177. package/src/app/(main)/settings/(desktop)/index.tsx +0 -23
  178. package/src/app/(main)/settings/(mobile)/features/AvatarBanner.tsx +0 -68
  179. package/src/app/(main)/settings/(mobile)/features/ExtraList.tsx +0 -65
  180. package/src/app/(main)/settings/(mobile)/index.tsx +0 -53
  181. package/src/app/(main)/settings/about/AboutList.tsx +0 -53
  182. package/src/app/(main)/settings/about/Analytics.tsx +0 -40
  183. package/src/app/(main)/settings/about/style.ts +0 -22
  184. package/src/app/(main)/settings/agent/loading.tsx +0 -3
  185. package/src/app/(main)/settings/common/loading.tsx +0 -3
  186. package/src/app/(main)/settings/features/SettingList/index.tsx +0 -47
  187. package/src/app/(main)/settings/llm/layout.tsx +0 -11
  188. package/src/app/(main)/settings/llm/loading.tsx +0 -3
  189. package/src/app/(main)/settings/sync/DeviceInfo/DeviceName.tsx +0 -66
  190. package/src/app/(main)/settings/sync/PageTitle.tsx +0 -11
  191. package/src/app/(main)/settings/sync/layout.tsx +0 -12
  192. package/src/app/(main)/settings/sync/loading.tsx +0 -3
  193. package/src/app/(main)/settings/tts/loading.tsx +0 -3
  194. package/src/features/AgentSetting/AgentConfig/index.tsx +0 -202
  195. package/src/features/AgentSetting/AgentConfig/useSyncConfig.ts +0 -23
  196. package/src/store/user/slices/common/selectors.ts +0 -6
  197. /package/src/app/(main)/settings/{features/SettingList → about/features}/Item.tsx +0 -0
  198. /package/src/app/(main)/settings/{features → common/features/Theme}/ThemeSwatches/ThemeSwatchesNeutral.tsx +0 -0
  199. /package/src/app/(main)/settings/{features → common/features/Theme}/ThemeSwatches/ThemeSwatchesPrimary.tsx +0 -0
  200. /package/src/app/(main)/settings/{features → common/features/Theme}/ThemeSwatches/index.ts +0 -0
  201. /package/src/app/(main)/settings/sync/{Alert.tsx → features/Alert.tsx} +0 -0
  202. /package/src/app/(main)/settings/sync/{components → features/WebRTC}/SyncSwitch/index.css +0 -0
  203. /package/src/app/(main)/settings/sync/{components → features/WebRTC}/SyncSwitch/index.tsx +0 -0
  204. /package/src/app/(main)/settings/sync/{util.ts → features/WebRTC/generateRandomRoomName.ts} +0 -0
  205. /package/src/app/(main)/settings/tts/{TTS/options.ts → features/const.ts} +0 -0
  206. /package/src/features/AgentSetting/{AgentConfig → AgentModal}/ModelSelect.tsx +0 -0
@@ -0,0 +1,95 @@
1
+ 'use client';
2
+
3
+ import { Form, ItemGroup, SliderWithInput } from '@lobehub/ui';
4
+ import { Switch } from 'antd';
5
+ import { debounce } from 'lodash-es';
6
+ import { memo } from 'react';
7
+ import { useTranslation } from 'react-i18next';
8
+
9
+ import { useSyncSettings } from '@/app/(main)/settings/hooks/useSyncSettings';
10
+ import { FORM_STYLE } from '@/const/layoutTokens';
11
+
12
+ import { useStore } from '../store';
13
+ import ModelSelect from './ModelSelect';
14
+
15
+ const AgentModal = memo(() => {
16
+ const { t } = useTranslation('setting');
17
+ const [form] = Form.useForm();
18
+
19
+ const [enableMaxTokens, updateConfig] = useStore((s) => [
20
+ s.config.enableMaxTokens,
21
+ s.setAgentConfig,
22
+ ]);
23
+
24
+ useSyncSettings(form);
25
+
26
+ const model: ItemGroup = {
27
+ children: [
28
+ {
29
+ children: <ModelSelect />,
30
+ desc: t('settingModel.model.desc'),
31
+ label: t('settingModel.model.title'),
32
+ name: 'model',
33
+ tag: 'model',
34
+ },
35
+ {
36
+ children: <SliderWithInput max={1} min={0} step={0.1} />,
37
+ desc: t('settingModel.temperature.desc'),
38
+ label: t('settingModel.temperature.title'),
39
+ name: ['params', 'temperature'],
40
+ tag: 'temperature',
41
+ },
42
+ {
43
+ children: <SliderWithInput max={1} min={0} step={0.1} />,
44
+ desc: t('settingModel.topP.desc'),
45
+ label: t('settingModel.topP.title'),
46
+ name: ['params', 'top_p'],
47
+ tag: 'top_p',
48
+ },
49
+ {
50
+ children: <SliderWithInput max={2} min={-2} step={0.1} />,
51
+ desc: t('settingModel.presencePenalty.desc'),
52
+ label: t('settingModel.presencePenalty.title'),
53
+ name: ['params', 'presence_penalty'],
54
+ tag: 'presence_penalty',
55
+ },
56
+ {
57
+ children: <SliderWithInput max={2} min={-2} step={0.1} />,
58
+ desc: t('settingModel.frequencyPenalty.desc'),
59
+ label: t('settingModel.frequencyPenalty.title'),
60
+ name: ['params', 'frequency_penalty'],
61
+ tag: 'frequency_penalty',
62
+ },
63
+ {
64
+ children: <Switch />,
65
+ label: t('settingModel.enableMaxTokens.title'),
66
+ minWidth: undefined,
67
+ name: 'enableMaxTokens',
68
+ valuePropName: 'checked',
69
+ },
70
+ {
71
+ children: <SliderWithInput max={32_000} min={0} step={100} />,
72
+ desc: t('settingModel.maxTokens.desc'),
73
+ divider: false,
74
+ hidden: !enableMaxTokens,
75
+ label: t('settingModel.maxTokens.title'),
76
+ name: ['params', 'max_tokens'],
77
+ tag: 'max_tokens',
78
+ },
79
+ ],
80
+ title: t('settingModel.title'),
81
+ };
82
+
83
+ return (
84
+ <Form
85
+ form={form}
86
+ items={[model]}
87
+ itemsType={'group'}
88
+ onValuesChange={debounce(updateConfig, 100)}
89
+ variant={'pure'}
90
+ {...FORM_STYLE}
91
+ />
92
+ );
93
+ });
94
+
95
+ export default AgentModal;
@@ -1,7 +1,9 @@
1
- import { Avatar, Form, Icon, Tooltip } from '@lobehub/ui';
1
+ 'use client';
2
+
3
+ import { Avatar, Form, Icon, ItemGroup, Tooltip } from '@lobehub/ui';
2
4
  import { Button, Empty, Space, Switch, Tag, Typography } from 'antd';
3
5
  import isEqual from 'fast-deep-equal';
4
- import { LucideToyBrick, LucideTrash2, Store } from 'lucide-react';
6
+ import { LucideTrash2, Store } from 'lucide-react';
5
7
  import { memo, useState } from 'react';
6
8
  import { Trans, useTranslation } from 'react-i18next';
7
9
  import { Center, Flexbox } from 'react-layout-kit';
@@ -90,73 +92,70 @@ const AgentPlugin = memo(() => {
90
92
  const hasDeprecated = deprecatedList.length > 0;
91
93
 
92
94
  const loadingSkeleton = LoadingList();
95
+
96
+ const extra = (
97
+ <Space.Compact style={{ width: 'auto' }}>
98
+ <AddPluginButton />
99
+ {hasDeprecated ? (
100
+ <Tooltip title={t('plugin.clearDeprecated')}>
101
+ <Button
102
+ icon={<Icon icon={LucideTrash2} />}
103
+ onClick={(e) => {
104
+ e.stopPropagation();
105
+ for (const i of deprecatedList) {
106
+ toggleAgentPlugin(i.tag as string);
107
+ }
108
+ }}
109
+ size={'small'}
110
+ />
111
+ </Tooltip>
112
+ ) : null}
113
+ <Tooltip title={t('plugin.store')}>
114
+ <Button
115
+ icon={<Icon icon={Store} />}
116
+ onClick={(e) => {
117
+ e.stopPropagation();
118
+ setShowStore(true);
119
+ }}
120
+ size={'small'}
121
+ />
122
+ </Tooltip>
123
+ </Space.Compact>
124
+ );
125
+
126
+ const empty = (
127
+ <Center padding={40}>
128
+ <Empty
129
+ description={
130
+ <Trans i18nKey={'plugin.empty'} ns={'setting'}>
131
+ 暂无安装插件,
132
+ <Typography.Link
133
+ href={'/'}
134
+ onClick={(e) => {
135
+ e.preventDefault();
136
+ setShowStore(true);
137
+ }}
138
+ >
139
+ 前往插件市场
140
+ </Typography.Link>
141
+ 安装
142
+ </Trans>
143
+ }
144
+ image={Empty.PRESENTED_IMAGE_SIMPLE}
145
+ />
146
+ </Center>
147
+ );
148
+
149
+ const plugin: ItemGroup = {
150
+ children: isLoading ? loadingSkeleton : isEmpty ? empty : [...deprecatedList, ...list],
151
+ extra,
152
+ title: t('settingPlugin.title'),
153
+ };
154
+
93
155
  return (
94
156
  <>
95
157
  <PluginStore open={showStore} setOpen={setShowStore} />
96
- <Form
97
- items={[
98
- {
99
- children: isLoading ? (
100
- loadingSkeleton
101
- ) : isEmpty ? (
102
- <Center padding={40}>
103
- <Empty
104
- description={
105
- <Trans i18nKey={'plugin.empty'} ns={'setting'}>
106
- 暂无安装插件,
107
- <Typography.Link
108
- href={'/'}
109
- onClick={(e) => {
110
- e.preventDefault();
111
- setShowStore(true);
112
- }}
113
- >
114
- 前往插件市场
115
- </Typography.Link>
116
- 安装
117
- </Trans>
118
- }
119
- image={Empty.PRESENTED_IMAGE_SIMPLE}
120
- />
121
- </Center>
122
- ) : (
123
- [...deprecatedList, ...list]
124
- ),
125
- extra: (
126
- <Space.Compact style={{ width: 'auto' }}>
127
- <AddPluginButton />
128
- {hasDeprecated ? (
129
- <Tooltip title={t('plugin.clearDeprecated')}>
130
- <Button
131
- icon={<Icon icon={LucideTrash2} />}
132
- onClick={(e) => {
133
- e.stopPropagation();
134
- for (const i of deprecatedList) {
135
- toggleAgentPlugin(i.tag as string);
136
- }
137
- }}
138
- size={'small'}
139
- />
140
- </Tooltip>
141
- ) : null}
142
- <Tooltip title={t('plugin.store')}>
143
- <Button
144
- icon={<Icon icon={Store} />}
145
- onClick={(e) => {
146
- e.stopPropagation();
147
- setShowStore(true);
148
- }}
149
- size={'small'}
150
- />
151
- </Tooltip>
152
- </Space.Compact>
153
- ),
154
- icon: LucideToyBrick,
155
- title: t('settingPlugin.title'),
156
- },
157
- ]}
158
- {...FORM_STYLE}
159
- />
158
+ <Form items={[plugin]} itemsType={'group'} variant={'pure'} {...FORM_STYLE} />
160
159
  </>
161
160
  );
162
161
  });
@@ -1,7 +1,8 @@
1
- import { EditableMessage, FormGroup } from '@lobehub/ui';
1
+ 'use client';
2
+
3
+ import { EditableMessage, Form } from '@lobehub/ui';
2
4
  import { Button } from 'antd';
3
5
  import { createStyles } from 'antd-style';
4
- import { Bot } from 'lucide-react';
5
6
  import { memo, useState } from 'react';
6
7
  import { useTranslation } from 'react-i18next';
7
8
  import { Flexbox } from 'react-layout-kit';
@@ -9,65 +10,118 @@ import { Flexbox } from 'react-layout-kit';
9
10
  import { FORM_STYLE } from '@/const/layoutTokens';
10
11
 
11
12
  import { useStore } from '../store';
12
- import TokenTag from './TokenTag';
13
13
 
14
- export const useStyles = createStyles(({ css, token }) => ({
14
+ export const useStyles = createStyles(({ css, token, responsive }) => ({
15
+ container: css`
16
+ position: relative;
17
+ width: 100%;
18
+ background: ${token.colorFillSecondary};
19
+ border-radius: ${token.borderRadiusLG}px;
20
+ `,
21
+ content: css`
22
+ z-index: 2;
23
+ padding: 8px;
24
+ background: ${token.colorBgContainer};
25
+ border-radius: ${token.borderRadiusLG - 1}px;
26
+ `,
15
27
  markdown: css`
16
28
  border: unset;
17
29
  `,
18
- textarea: css`
19
- background: ${token.colorFillTertiary};
30
+ wrapper: css`
31
+ width: 100%;
32
+ ${responsive.mobile} {
33
+ padding-block: 8px;
34
+ padding-inline: 4px;
35
+ }
20
36
  `,
21
37
  }));
22
38
 
23
- const AgentPrompt = memo(() => {
39
+ const AgentPrompt = memo<{ modal?: boolean }>(({ modal }) => {
24
40
  const { t } = useTranslation('setting');
25
41
  const { styles } = useStyles();
26
42
  const [editing, setEditing] = useState(false);
27
43
  const [systemRole, updateConfig] = useStore((s) => [s.config.systemRole, s.setAgentConfig]);
28
44
 
29
- return (
30
- <FormGroup
31
- extra={<TokenTag />}
32
- icon={Bot}
33
- style={FORM_STYLE.style}
34
- title={t('settingAgent.prompt.title')}
45
+ const content = (
46
+ <EditableMessage
47
+ classNames={{
48
+ markdown: styles.markdown,
49
+ }}
50
+ editButtonSize={'small'}
51
+ editing={editing}
52
+ height={'auto'}
53
+ inputType={'pure'}
54
+ onChange={(e) => {
55
+ updateConfig({ systemRole: e });
56
+ }}
57
+ onEditingChange={setEditing}
58
+ placeholder={t('settingAgent.prompt.placeholder')}
59
+ showEditWhenEmpty
60
+ text={{
61
+ cancel: t('cancel', { ns: 'common' }),
62
+ confirm: t('ok', { ns: 'common' }),
63
+ }}
64
+ value={systemRole}
65
+ />
66
+ );
67
+
68
+ const editButton = !editing && !!systemRole && (
69
+ <Button
70
+ onClick={(e) => {
71
+ e.stopPropagation();
72
+ setEditing(true);
73
+ }}
74
+ size={'small'}
75
+ type={'primary'}
35
76
  >
36
- <Flexbox gap={16} paddingBlock={16}>
37
- <EditableMessage
38
- classNames={styles}
39
- editButtonSize={'small'}
40
- editing={editing}
41
- height={'auto'}
42
- inputType={'ghost'}
43
- onChange={(e) => {
44
- updateConfig({ systemRole: e });
45
- }}
46
- onEditingChange={setEditing}
47
- placeholder={t('settingAgent.prompt.placeholder')}
48
- showEditWhenEmpty
49
- text={{
50
- cancel: t('cancel', { ns: 'common' }),
51
- confirm: t('ok', { ns: 'common' }),
52
- }}
53
- value={systemRole}
54
- />
55
- {!editing && !!systemRole && (
56
- <Flexbox direction={'horizontal-reverse'}>
57
- <Button
58
- onClick={(e) => {
59
- e.stopPropagation();
60
- setEditing(true);
61
- }}
62
- size={'small'}
63
- type={'primary'}
64
- >
65
- {t('edit', { ns: 'common' })}
66
- </Button>
67
- </Flexbox>
68
- )}
77
+ {t('edit', { ns: 'common' })}
78
+ </Button>
79
+ );
80
+
81
+ if (modal)
82
+ return (
83
+ <Form
84
+ items={[
85
+ {
86
+ children: (
87
+ <>
88
+ <div style={{ height: 24 }} />
89
+ {content}
90
+ </>
91
+ ),
92
+ extra: editButton,
93
+ title: t('settingAgent.prompt.title'),
94
+ },
95
+ ]}
96
+ itemsType={'group'}
97
+ variant={'pure'}
98
+ {...FORM_STYLE}
99
+ />
100
+ );
101
+
102
+ return (
103
+ <div className={styles.wrapper}>
104
+ <Flexbox className={styles.container} padding={4}>
105
+ <Flexbox horizontal justify={'space-between'} paddingBlock={8} paddingInline={12}>
106
+ <h1 style={{ fontSize: 18, fontWeight: 'bold', marginBottom: 4 }}>
107
+ {t('settingAgent.prompt.title')}
108
+ </h1>
109
+ {editButton}
110
+ </Flexbox>
111
+ <Flexbox
112
+ align={'center'}
113
+ className={styles.content}
114
+ flex={1}
115
+ gap={16}
116
+ horizontal
117
+ justify={'space-between'}
118
+ padding={12}
119
+ wrap={'wrap'}
120
+ >
121
+ {content}
122
+ </Flexbox>
69
123
  </Flexbox>
70
- </FormGroup>
124
+ </div>
71
125
  );
72
126
  });
73
127
 
@@ -1,3 +1,5 @@
1
+ 'use client';
2
+
1
3
  import { VoiceList } from '@lobehub/tts';
2
4
  import { Form, ItemGroup } from '@lobehub/ui';
3
5
  import { Form as AFrom, Select, Switch } from 'antd';
@@ -107,7 +109,9 @@ const AgentTTS = memo(() => {
107
109
  },
108
110
  }}
109
111
  items={[tts]}
112
+ itemsType={'group'}
110
113
  onValuesChange={debounce(updateConfig, 100)}
114
+ variant={'pure'}
111
115
  {...FORM_STYLE}
112
116
  />
113
117
  );
@@ -1,3 +1,5 @@
1
+ 'use client';
2
+
1
3
  import { memo } from 'react';
2
4
  import { createStoreUpdater } from 'zustand-utils';
3
5
 
@@ -1,7 +1,6 @@
1
- import { memo } from 'react';
2
-
3
- import AgentConfig from './AgentConfig';
1
+ import AgentChat from './AgentChat';
4
2
  import AgentMeta from './AgentMeta';
3
+ import AgentModal from './AgentModal';
5
4
  import AgentPlugin from './AgentPlugin';
6
5
  import AgentPrompt from './AgentPrompt';
7
6
  import AgentTTS from './AgentTTS';
@@ -10,17 +9,18 @@ import { Provider, createStore } from './store';
10
9
 
11
10
  type AgentSettingsProps = StoreUpdaterProps;
12
11
 
13
- const AgentSettings = memo<AgentSettingsProps>((props) => {
12
+ const AgentSettings = (props: AgentSettingsProps) => {
14
13
  return (
15
14
  <Provider createStore={createStore}>
16
15
  <StoreUpdater {...props} />
17
16
  <AgentPrompt />
18
17
  <AgentMeta />
19
- <AgentConfig />
18
+ <AgentChat />
19
+ <AgentModal />
20
20
  <AgentTTS />
21
21
  <AgentPlugin />
22
22
  </Provider>
23
23
  );
24
- });
24
+ };
25
25
 
26
26
  export default AgentSettings;
@@ -1,3 +1,5 @@
1
+ 'use client';
2
+
1
3
  import { createContext } from 'zustand-utils';
2
4
  import { subscribeWithSelector } from 'zustand/middleware';
3
5
  import { shallow } from 'zustand/shallow';
@@ -1,3 +1,5 @@
1
+ 'use client';
2
+
1
3
  import { Upload } from 'antd';
2
4
  import { createStyles } from 'antd-style';
3
5
  import NextImage from 'next/image';
@@ -5,7 +7,7 @@ import { CSSProperties, memo, useCallback } from 'react';
5
7
 
6
8
  import { DEFAULT_USER_AVATAR_URL } from '@/const/meta';
7
9
  import { useUserStore } from '@/store/user';
8
- import { commonSelectors } from '@/store/user/selectors';
10
+ import { userProfileSelectors } from '@/store/user/selectors';
9
11
  import { imageToBase64 } from '@/utils/imageToBase64';
10
12
  import { createUploadImageHandler } from '@/utils/uploadFIle';
11
13
 
@@ -39,7 +41,7 @@ const AvatarWithUpload = memo<AvatarWithUploadProps>(
39
41
  ({ size = 40, compressSize = 256, style, id }) => {
40
42
  const { styles } = useStyle();
41
43
  const [avatar, updateAvatar] = useUserStore((s) => [
42
- commonSelectors.userAvatar(s),
44
+ userProfileSelectors.userAvatar(s),
43
45
  s.updateAvatar,
44
46
  ]);
45
47
 
@@ -0,0 +1,20 @@
1
+ import { renderHook } from '@testing-library/react';
2
+ import { useSearchParams } from 'next/navigation';
3
+ import { describe, expect, it, vi } from 'vitest';
4
+
5
+ import { useQuery } from './useQuery';
6
+
7
+ // Mocks
8
+ vi.mock('next/navigation', () => ({
9
+ useSearchParams: vi.fn(() => 'baz=qux&foo=bar'),
10
+ }));
11
+
12
+ describe('useQuery', () => {
13
+ it('should parse query', () => {
14
+ const { result } = renderHook(() => useQuery());
15
+ expect(result.current).toEqual({
16
+ baz: 'qux',
17
+ foo: 'bar',
18
+ });
19
+ });
20
+ });
@@ -0,0 +1,7 @@
1
+ import { useSearchParams } from 'next/navigation';
2
+ import qs from 'query-string';
3
+
4
+ export const useQuery = () => {
5
+ const rawQuery = useSearchParams();
6
+ return qs.parse(rawQuery.toString());
7
+ };
@@ -0,0 +1,86 @@
1
+ import { renderHook } from '@testing-library/react';
2
+ import { describe, expect, it, vi } from 'vitest';
3
+
4
+ import { useQueryRoute } from './useQueryRoute';
5
+
6
+ // Mocks
7
+ vi.mock('next/navigation', () => ({
8
+ useRouter: vi.fn(() => ({
9
+ push: vi.fn((href) => href),
10
+ replace: vi.fn((href) => href),
11
+ })),
12
+ }));
13
+ vi.mock('@/hooks/useQuery', () => ({
14
+ useQuery: vi.fn(() => ({ foo: 'bar' })),
15
+ }));
16
+ vi.mock('@/utils/env', () => ({
17
+ isOnServerSide: false,
18
+ }));
19
+
20
+ describe('useQueryRoute', () => {
21
+ it('should generate correct href without hash and replace', () => {
22
+ const { result } = renderHook(() =>
23
+ useQueryRoute().push('/example', {
24
+ query: { baz: 'qux' },
25
+ }),
26
+ );
27
+
28
+ expect(result.current).toBe('/example?baz=qux&foo=bar');
29
+ });
30
+
31
+ it('should replace entire query string when replace is true', () => {
32
+ const { result } = renderHook(() =>
33
+ useQueryRoute().push('/example', {
34
+ replace: true,
35
+ query: { baz: 'qux' },
36
+ }),
37
+ );
38
+
39
+ expect(result.current).toBe('/example?baz=qux');
40
+ });
41
+
42
+ it('should append hash to the URL', () => {
43
+ const { result } = renderHook(() =>
44
+ useQueryRoute().push('/example', {
45
+ replace: true,
46
+ query: { foo: 'bar' },
47
+ hash: 'section1',
48
+ }),
49
+ );
50
+
51
+ expect(result.current).toBe('/example?foo=bar#section1');
52
+ });
53
+
54
+ it('should handle scenarios when on server side', () => {
55
+ const { result } = renderHook(() =>
56
+ useQueryRoute().push('/example', {
57
+ query: { foo: 'bar' },
58
+ hash: 'section1',
59
+ }),
60
+ );
61
+
62
+ expect(result.current).toBe('/example?foo=bar#section1');
63
+ });
64
+
65
+ it('should handle cases with empty query and hash', () => {
66
+ const { result } = renderHook(() =>
67
+ useQueryRoute().replace('/example', {
68
+ replace: true,
69
+ query: {},
70
+ hash: '',
71
+ }),
72
+ );
73
+
74
+ expect(result.current).toBe('/example');
75
+ });
76
+
77
+ it('should handle cases without hash when on server side', () => {
78
+ const { result } = renderHook(() =>
79
+ useQueryRoute().replace('/example', {
80
+ query: { foo: 'bar' },
81
+ }),
82
+ );
83
+
84
+ expect(result.current).toBe('/example?foo=bar');
85
+ });
86
+ });
@@ -0,0 +1,46 @@
1
+ import { useRouter } from 'next/navigation';
2
+ import qs, { type ParsedQuery } from 'query-string';
3
+ import { useMemo } from 'react';
4
+
5
+ import { useQuery } from '@/hooks/useQuery';
6
+ import { isOnServerSide } from '@/utils/env';
7
+
8
+ interface QueryRouteOptions {
9
+ hash?: string;
10
+ query?: ParsedQuery;
11
+ replace?: boolean;
12
+ replaceHash?: boolean;
13
+ withHash?: boolean;
14
+ }
15
+
16
+ interface GenHrefOptions extends QueryRouteOptions {
17
+ prevQuery?: ParsedQuery;
18
+ url: string;
19
+ }
20
+
21
+ const genHref = ({ hash, replace, url, prevQuery = {}, query = {} }: GenHrefOptions): string => {
22
+ let href = qs.stringifyUrl({ query: replace ? query : { ...prevQuery, ...query }, url });
23
+
24
+ if (!isOnServerSide && hash) {
25
+ href = [href, hash || location?.hash?.slice(1)].filter(Boolean).join('#');
26
+ }
27
+
28
+ return href;
29
+ };
30
+
31
+ export const useQueryRoute = () => {
32
+ const router = useRouter();
33
+ const prevQuery = useQuery();
34
+
35
+ return useMemo(
36
+ () => ({
37
+ push: (url: string, options: QueryRouteOptions = {}) => {
38
+ return router.push(genHref({ prevQuery, url, ...options }));
39
+ },
40
+ replace: (url: string, options: QueryRouteOptions = {}) => {
41
+ return router.replace(genHref({ prevQuery, url, ...options }));
42
+ },
43
+ }),
44
+ [prevQuery],
45
+ );
46
+ };