@lobehub/chat 0.122.8 → 0.123.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.
Files changed (227) hide show
  1. package/.env.example +22 -2
  2. package/.eslintrc.js +1 -0
  3. package/CHANGELOG.md +50 -0
  4. package/README.md +11 -9
  5. package/README.zh-CN.md +11 -9
  6. package/docs/Deployment/Environment-Variable.md +65 -23
  7. package/docs/Deployment/Environment-Variable.zh-CN.md +64 -22
  8. package/locales/ar/chat.json +12 -1
  9. package/locales/ar/common.json +16 -0
  10. package/locales/ar/error.json +35 -6
  11. package/locales/ar/setting.json +53 -6
  12. package/locales/de-DE/chat.json +12 -1
  13. package/locales/de-DE/common.json +16 -0
  14. package/locales/de-DE/error.json +35 -6
  15. package/locales/de-DE/setting.json +53 -6
  16. package/locales/en-US/chat.json +13 -2
  17. package/locales/en-US/common.json +16 -0
  18. package/locales/en-US/error.json +35 -6
  19. package/locales/en-US/setting.json +53 -6
  20. package/locales/es-ES/chat.json +12 -1
  21. package/locales/es-ES/common.json +16 -0
  22. package/locales/es-ES/error.json +35 -6
  23. package/locales/es-ES/setting.json +53 -6
  24. package/locales/fr-FR/chat.json +13 -2
  25. package/locales/fr-FR/common.json +16 -0
  26. package/locales/fr-FR/error.json +35 -6
  27. package/locales/fr-FR/setting.json +53 -6
  28. package/locales/it-IT/chat.json +12 -1
  29. package/locales/it-IT/common.json +16 -0
  30. package/locales/it-IT/error.json +35 -6
  31. package/locales/it-IT/setting.json +53 -6
  32. package/locales/ja-JP/chat.json +12 -1
  33. package/locales/ja-JP/common.json +16 -0
  34. package/locales/ja-JP/error.json +35 -6
  35. package/locales/ja-JP/setting.json +53 -6
  36. package/locales/ko-KR/chat.json +12 -1
  37. package/locales/ko-KR/common.json +16 -0
  38. package/locales/ko-KR/error.json +35 -6
  39. package/locales/ko-KR/setting.json +53 -6
  40. package/locales/nl-NL/chat.json +12 -1
  41. package/locales/nl-NL/common.json +16 -0
  42. package/locales/nl-NL/error.json +35 -6
  43. package/locales/nl-NL/setting.json +53 -6
  44. package/locales/pl-PL/chat.json +12 -1
  45. package/locales/pl-PL/common.json +16 -0
  46. package/locales/pl-PL/error.json +35 -6
  47. package/locales/pl-PL/setting.json +53 -6
  48. package/locales/pt-BR/chat.json +12 -1
  49. package/locales/pt-BR/common.json +16 -0
  50. package/locales/pt-BR/error.json +35 -6
  51. package/locales/pt-BR/setting.json +53 -6
  52. package/locales/ru-RU/chat.json +12 -1
  53. package/locales/ru-RU/common.json +16 -0
  54. package/locales/ru-RU/error.json +35 -6
  55. package/locales/ru-RU/setting.json +53 -6
  56. package/locales/tr-TR/chat.json +12 -1
  57. package/locales/tr-TR/common.json +16 -0
  58. package/locales/tr-TR/error.json +35 -6
  59. package/locales/tr-TR/setting.json +53 -6
  60. package/locales/vi-VN/chat.json +12 -1
  61. package/locales/vi-VN/common.json +16 -0
  62. package/locales/vi-VN/error.json +35 -6
  63. package/locales/vi-VN/setting.json +53 -6
  64. package/locales/zh-CN/chat.json +12 -1
  65. package/locales/zh-CN/common.json +16 -0
  66. package/locales/zh-CN/error.json +34 -5
  67. package/locales/zh-CN/setting.json +59 -12
  68. package/locales/zh-TW/chat.json +12 -1
  69. package/locales/zh-TW/common.json +16 -0
  70. package/locales/zh-TW/error.json +35 -6
  71. package/locales/zh-TW/setting.json +53 -6
  72. package/next.config.mjs +8 -2
  73. package/package.json +8 -1
  74. package/src/app/api/chat/[provider]/agentRuntime.ts +129 -0
  75. package/src/app/api/chat/[provider]/route.ts +64 -0
  76. package/src/app/api/chat/auth.ts +42 -0
  77. package/src/app/api/chat/google/route.ts +86 -0
  78. package/src/app/api/config/route.ts +6 -1
  79. package/src/app/api/config.ts +3 -3
  80. package/src/app/api/errorResponse.test.ts +8 -8
  81. package/src/app/api/errorResponse.ts +43 -6
  82. package/src/app/api/openai/createBizOpenAI/createAzureOpenai.ts +1 -1
  83. package/src/app/api/openai/createBizOpenAI/createOpenai.ts +1 -1
  84. package/src/app/api/openai/createBizOpenAI/index.ts +2 -2
  85. package/src/app/chat/(desktop)/features/ChatHeader.tsx +6 -4
  86. package/src/app/chat/(desktop)/features/ChatInput/Footer/index.tsx +13 -3
  87. package/src/app/chat/(desktop)/features/ChatInput/Header/index.tsx +16 -24
  88. package/src/app/chat/(desktop)/features/SideBar/index.tsx +1 -0
  89. package/src/app/chat/features/ChatHeader/ShareButton/Preview.tsx +3 -3
  90. package/src/app/chat/features/SessionListContent/List/Item/index.tsx +2 -3
  91. package/src/app/settings/llm/Azure/index.tsx +145 -0
  92. package/src/app/settings/llm/Bedrock/index.tsx +109 -0
  93. package/src/app/settings/llm/{LLM/Checker.tsx → Checker.tsx} +20 -22
  94. package/src/app/settings/llm/Google/index.tsx +74 -0
  95. package/src/app/settings/llm/{LLM → OpenAI}/index.tsx +27 -45
  96. package/src/app/settings/llm/Zhipu/index.tsx +78 -0
  97. package/src/app/settings/llm/const.ts +13 -0
  98. package/src/app/settings/llm/page.tsx +42 -2
  99. package/src/app/settings/llm/useSyncSettings.ts +23 -0
  100. package/src/chains/__tests__/langDetect.test.ts +2 -2
  101. package/src/chains/__tests__/pickEmoji.test.ts +2 -2
  102. package/src/chains/langDetect.ts +2 -2
  103. package/src/chains/pickEmoji.ts +2 -2
  104. package/src/chains/summaryAgentName.ts +2 -2
  105. package/src/chains/summaryDescription.ts +2 -2
  106. package/src/chains/summaryTags.ts +2 -2
  107. package/src/chains/summaryTitle.ts +2 -2
  108. package/src/chains/translate.ts +2 -2
  109. package/src/components/ModelIcon/index.tsx +37 -0
  110. package/src/components/ModelProviderIcon/index.tsx +44 -0
  111. package/src/components/ModelSelect/index.tsx +133 -0
  112. package/src/components/ModelTag/ModelIcon.tsx +35 -0
  113. package/src/components/ModelTag/index.tsx +13 -0
  114. package/src/config/modelProviders/bedrock.ts +43 -0
  115. package/src/config/modelProviders/google.ts +20 -0
  116. package/src/config/modelProviders/index.ts +18 -0
  117. package/src/config/modelProviders/openai.ts +102 -0
  118. package/src/config/modelProviders/zhipu.ts +34 -0
  119. package/src/config/{server.ts → server/app.ts} +1 -24
  120. package/src/config/server/index.ts +13 -0
  121. package/src/config/server/provider.ts +78 -0
  122. package/src/const/auth.ts +30 -0
  123. package/src/const/fetch.ts +3 -0
  124. package/src/const/settings.ts +24 -4
  125. package/src/database/core/migrations/migrateSettingsToUser/index.test.ts +2 -1
  126. package/src/database/core/migrations/migrateSettingsToUser/index.ts +1 -18
  127. package/src/database/core/migrations/migrateSettingsToUser/type.ts +100 -0
  128. package/src/database/models/message.ts +3 -1
  129. package/src/database/schemas/message.ts +1 -0
  130. package/src/database/schemas/session.ts +1 -0
  131. package/src/features/AgentSetting/AgentConfig/ModelSelect.tsx +71 -0
  132. package/src/features/AgentSetting/AgentConfig/index.tsx +171 -161
  133. package/src/features/AgentSetting/AgentConfig/useSyncConfig.ts +23 -0
  134. package/src/features/AgentSetting/AgentPrompt/TokenTag.tsx +39 -0
  135. package/src/features/AgentSetting/AgentPrompt/index.tsx +4 -26
  136. package/src/features/AgentSetting/store/index.ts +10 -2
  137. package/src/features/ChatInput/ActionBar/FileUpload.tsx +11 -4
  138. package/src/features/ChatInput/ActionBar/ModelSwitch.tsx +56 -14
  139. package/src/features/ChatInput/ActionBar/Token/TokenTag.tsx +42 -7
  140. package/src/features/ChatInput/ActionBar/Token/index.tsx +5 -2
  141. package/src/features/ChatInput/ActionBar/Tools/index.tsx +11 -1
  142. package/src/features/ChatInput/useChatInput.ts +5 -1
  143. package/src/features/Conversation/Error/APIKeyForm/Bedrock.tsx +80 -0
  144. package/src/features/Conversation/Error/APIKeyForm/Google.tsx +61 -0
  145. package/src/features/Conversation/Error/APIKeyForm/OpenAI.tsx +63 -0
  146. package/src/features/Conversation/Error/APIKeyForm/Zhipu.tsx +62 -0
  147. package/src/features/Conversation/Error/APIKeyForm/index.tsx +72 -0
  148. package/src/features/Conversation/Error/AccessCodeForm.tsx +63 -0
  149. package/src/features/Conversation/Error/ErrorJsonViewer.tsx +26 -0
  150. package/src/features/Conversation/Error/InvalidAPIKey.tsx +16 -0
  151. package/src/features/Conversation/Error/InvalidAccessCode.tsx +50 -0
  152. package/src/features/Conversation/Error/OpenAiBizError.tsx +9 -17
  153. package/src/features/Conversation/Error/{Plugin/PluginSettings.tsx → PluginSettings.tsx} +8 -3
  154. package/src/features/Conversation/Error/index.tsx +75 -70
  155. package/src/features/Conversation/Error/style.tsx +9 -3
  156. package/src/features/Conversation/Extras/Assistant.tsx +2 -3
  157. package/src/features/Conversation/Plugins/Inspector/index.tsx +8 -2
  158. package/src/features/Conversation/components/ChatItem/index.tsx +9 -24
  159. package/src/features/Conversation/types/index.tsx +2 -5
  160. package/src/hooks/_header.ts +35 -0
  161. package/src/libs/agent-runtime/BaseAI.ts +9 -0
  162. package/src/libs/agent-runtime/azureOpenai/index.ts +72 -0
  163. package/src/libs/agent-runtime/bedrock/index.ts +134 -0
  164. package/src/libs/agent-runtime/error.ts +26 -0
  165. package/src/libs/agent-runtime/google/index.ts +160 -0
  166. package/src/libs/agent-runtime/index.ts +9 -0
  167. package/src/libs/agent-runtime/openai/index.test.ts +176 -0
  168. package/src/libs/agent-runtime/openai/index.ts +107 -0
  169. package/src/libs/agent-runtime/types/chat.ts +119 -0
  170. package/src/libs/agent-runtime/types/index.ts +2 -0
  171. package/src/libs/agent-runtime/types/type.ts +34 -0
  172. package/src/libs/agent-runtime/utils/createError.ts +10 -0
  173. package/src/libs/agent-runtime/utils/debugStream.ts +18 -0
  174. package/src/libs/agent-runtime/utils/env.ts +1 -0
  175. package/src/libs/agent-runtime/utils/handleOpenAIError.ts +39 -0
  176. package/src/libs/agent-runtime/utils/uriParser.ts +16 -0
  177. package/src/libs/agent-runtime/zhipu/authToken.ts +22 -0
  178. package/src/libs/agent-runtime/zhipu/index.ts +125 -0
  179. package/src/locales/default/chat.ts +12 -2
  180. package/src/locales/default/common.ts +16 -0
  181. package/src/locales/default/error.ts +42 -5
  182. package/src/locales/default/setting.ts +61 -12
  183. package/src/services/__tests__/chat.test.ts +8 -2
  184. package/src/services/__tests__/message.test.ts +1 -1
  185. package/src/services/_auth.ts +106 -0
  186. package/src/services/_header.ts +1 -0
  187. package/src/services/_url.ts +0 -2
  188. package/src/services/chat.ts +32 -14
  189. package/src/store/chat/slices/message/action.ts +9 -6
  190. package/src/store/global/slices/common/action.ts +8 -2
  191. package/src/store/global/slices/settings/action.test.ts +2 -2
  192. package/src/store/global/slices/settings/action.ts +11 -4
  193. package/src/store/global/slices/settings/selectors/__snapshots__/modelProvider.test.ts.snap +242 -78
  194. package/src/store/global/slices/settings/selectors/__snapshots__/selectors.test.ts.snap +2 -0
  195. package/src/store/global/slices/settings/selectors/modelProvider.test.ts +171 -47
  196. package/src/store/global/slices/settings/selectors/modelProvider.ts +136 -43
  197. package/src/store/session/slices/agent/selectors.test.ts +0 -48
  198. package/src/store/session/slices/agent/selectors.ts +7 -12
  199. package/src/types/agent/index.ts +6 -2
  200. package/src/types/fetch.ts +4 -2
  201. package/src/types/files.ts +3 -3
  202. package/src/types/llm.ts +27 -0
  203. package/src/types/message/index.ts +7 -3
  204. package/src/types/openai/chat.ts +6 -11
  205. package/src/types/settings/index.ts +1 -0
  206. package/src/types/settings/modelProvider.ts +37 -1
  207. package/src/utils/__snapshots__/parseModels.test.ts.snap +63 -0
  208. package/src/utils/parseModels.test.ts +24 -0
  209. package/src/utils/parseModels.ts +37 -0
  210. package/src/app/api/openai/chat/createChatCompletion.test.ts +0 -166
  211. package/src/app/api/openai/chat/createChatCompletion.ts +0 -76
  212. package/src/app/api/openai/chat/route.ts +0 -19
  213. package/src/app/api/openai/models/route.ts +0 -17
  214. package/src/app/settings/llm/LLM/ModelList.tsx +0 -51
  215. package/src/app/settings/llm/LLM/getModelList.ts +0 -9
  216. package/src/app/settings/llm/index.tsx +0 -36
  217. package/src/const/llm.ts +0 -32
  218. package/src/features/Conversation/Error/ApiKeyForm.tsx +0 -87
  219. package/src/features/Conversation/Error/ErrorActionContainer.tsx +0 -23
  220. package/src/features/Conversation/Error/InvalidAccess.tsx +0 -90
  221. package/src/features/Conversation/Error/OpenAPIKey.tsx +0 -13
  222. package/src/features/Conversation/Error/Plugin/PluginError.tsx +0 -30
  223. package/src/services/modelList.ts +0 -15
  224. /package/src/app/chat/(desktop)/features/ChatInput/{Header → Footer}/DragUpload.tsx +0 -0
  225. /package/src/{app/api/openai/chat → libs/agent-runtime/utils}/desensitizeUrl.test.ts +0 -0
  226. /package/src/{app/api/openai/chat → libs/agent-runtime/utils}/desensitizeUrl.ts +0 -0
  227. /package/src/types/{translate.ts → message/translate.ts} +0 -0
@@ -1,186 +1,196 @@
1
1
  import { Form, ItemGroup, SelectWithImg, SliderWithInput } from '@lobehub/ui';
2
- import { Form as AFrom, Input, Select, Switch } from 'antd';
2
+ import { Form as AFrom, Input, Switch } from 'antd';
3
3
  import { useThemeMode } from 'antd-style';
4
- import isEqual from 'fast-deep-equal';
5
4
  import { debounce } from 'lodash-es';
6
5
  import { BrainCog, LayoutList, MessageSquare, MessagesSquare } from 'lucide-react';
7
- import { memo, useEffect } from 'react';
6
+ import { memo, useMemo } from 'react';
8
7
  import { useTranslation } from 'react-i18next';
9
8
 
10
9
  import { FORM_STYLE } from '@/const/layoutTokens';
11
- import { useGlobalStore } from '@/store/global';
12
- import { modelProviderSelectors } from '@/store/global/selectors';
13
10
 
14
11
  import { useStore } from '../store';
12
+ import ModelSelect from './ModelSelect';
13
+ import { useSyncConfig } from './useSyncConfig';
15
14
 
16
15
  const AgentConfig = memo(() => {
17
16
  const { t } = useTranslation('setting');
18
- const updateConfig = useStore((s) => s.setAgentConfig);
19
17
  const [form] = AFrom.useForm();
20
18
  const { isDarkMode } = useThemeMode();
21
19
 
22
- const config = useStore((s) => s.config, isEqual);
20
+ const [
21
+ displayMode,
22
+ enableAutoCreateTopic,
23
+ enableHistoryCount,
24
+ enableMaxTokens,
25
+ enableCompressThreshold,
26
+ updateConfig,
27
+ ] = useStore((s) => [
28
+ s.config.displayMode,
29
+ s.config.enableAutoCreateTopic,
30
+ s.config.enableHistoryCount,
31
+ s.config.enableCompressThreshold,
32
+ s.config.enableMaxTokens,
33
+ s.setAgentConfig,
34
+ ]);
23
35
 
24
- const modelList = useGlobalStore(modelProviderSelectors.modelList);
36
+ useSyncConfig(form);
25
37
 
26
- useEffect(() => {
27
- form.setFieldsValue(config);
28
- }, [config]);
38
+ const formItems = useMemo(() => {
39
+ const chat: ItemGroup = {
40
+ children: [
41
+ {
42
+ children: (
43
+ <SelectWithImg
44
+ height={86}
45
+ onChange={(mode) => updateConfig({ displayMode: mode })}
46
+ options={[
47
+ {
48
+ icon: MessagesSquare,
49
+ img: `/images/chatmode_chat_${isDarkMode ? 'dark' : 'light'}.webp`,
50
+ label: t('settingChat.chatStyleType.type.chat'),
51
+ value: 'chat',
52
+ },
53
+ {
54
+ icon: LayoutList,
55
+ img: `/images/chatmode_docs_${isDarkMode ? 'dark' : 'light'}.webp`,
56
+ label: t('settingChat.chatStyleType.type.docs'),
57
+ value: 'docs',
58
+ },
59
+ ]}
60
+ value={displayMode}
61
+ width={144}
62
+ />
63
+ ),
64
+ label: t('settingChat.chatStyleType.title'),
65
+ minWidth: undefined,
66
+ },
67
+ {
68
+ children: <Input.TextArea placeholder={t('settingChat.inputTemplate.placeholder')} />,
69
+ desc: t('settingChat.inputTemplate.desc'),
70
+ label: t('settingChat.inputTemplate.title'),
71
+ name: 'inputTemplate',
72
+ },
73
+ {
74
+ children: <Switch />,
75
+ desc: t('settingChat.enableAutoCreateTopic.desc'),
76
+ label: t('settingChat.enableAutoCreateTopic.title'),
77
+ minWidth: undefined,
78
+ name: 'enableAutoCreateTopic',
79
+ valuePropName: 'checked',
80
+ },
81
+ {
82
+ children: <SliderWithInput max={8} min={0} />,
83
+ desc: t('settingChat.autoCreateTopicThreshold.desc'),
84
+ divider: false,
85
+ hidden: !enableAutoCreateTopic,
86
+ label: t('settingChat.autoCreateTopicThreshold.title'),
87
+ name: 'autoCreateTopicThreshold',
88
+ },
89
+ {
90
+ children: <Switch />,
91
+ label: t('settingChat.enableHistoryCount.title'),
92
+ minWidth: undefined,
93
+ name: 'enableHistoryCount',
94
+ valuePropName: 'checked',
95
+ },
96
+ {
97
+ children: <SliderWithInput max={32} min={1} />,
98
+ desc: t('settingChat.historyCount.desc'),
99
+ divider: false,
100
+ hidden: !enableHistoryCount,
101
+ label: t('settingChat.historyCount.title'),
102
+ name: 'historyCount',
103
+ },
104
+ {
105
+ children: <Switch />,
106
+ label: t('settingChat.enableCompressThreshold.title'),
107
+ minWidth: undefined,
108
+ name: 'enableCompressThreshold',
109
+ valuePropName: 'checked',
110
+ },
111
+ {
112
+ children: <SliderWithInput max={32} min={0} />,
113
+ desc: t('settingChat.compressThreshold.desc'),
114
+ divider: false,
115
+ hidden: !enableCompressThreshold,
116
+ label: t('settingChat.compressThreshold.title'),
117
+ name: 'compressThreshold',
118
+ },
119
+ ],
120
+ icon: MessageSquare,
121
+ title: t('settingChat.title'),
122
+ };
29
123
 
30
- const chat: ItemGroup = {
31
- children: [
32
- {
33
- children: (
34
- <SelectWithImg
35
- height={86}
36
- onChange={(mode) => updateConfig({ displayMode: mode })}
37
- options={[
38
- {
39
- icon: MessagesSquare,
40
- img: `/images/chatmode_chat_${isDarkMode ? 'dark' : 'light'}.webp`,
41
- label: t('settingChat.chatStyleType.type.chat'),
42
- value: 'chat',
43
- },
44
- {
45
- icon: LayoutList,
46
- img: `/images/chatmode_docs_${isDarkMode ? 'dark' : 'light'}.webp`,
47
- label: t('settingChat.chatStyleType.type.docs'),
48
- value: 'docs',
49
- },
50
- ]}
51
- value={config.displayMode}
52
- width={144}
53
- />
54
- ),
55
- label: t('settingChat.chatStyleType.title'),
56
- minWidth: undefined,
57
- },
58
- {
59
- children: <Input.TextArea placeholder={t('settingChat.inputTemplate.placeholder')} />,
60
- desc: t('settingChat.inputTemplate.desc'),
61
- label: t('settingChat.inputTemplate.title'),
62
- name: 'inputTemplate',
63
- },
64
- {
65
- children: <Switch />,
66
- desc: t('settingChat.enableAutoCreateTopic.desc'),
67
- label: t('settingChat.enableAutoCreateTopic.title'),
68
- minWidth: undefined,
69
- name: 'enableAutoCreateTopic',
70
- valuePropName: 'checked',
71
- },
72
- {
73
- children: <SliderWithInput max={8} min={0} />,
74
- desc: t('settingChat.autoCreateTopicThreshold.desc'),
75
- divider: false,
76
- hidden: !config.enableAutoCreateTopic,
77
- label: t('settingChat.autoCreateTopicThreshold.title'),
78
- name: 'autoCreateTopicThreshold',
79
- },
80
- {
81
- children: <Switch />,
82
- label: t('settingChat.enableHistoryCount.title'),
83
- minWidth: undefined,
84
- name: 'enableHistoryCount',
85
- valuePropName: 'checked',
86
- },
87
- {
88
- children: <SliderWithInput max={32} min={1} />,
89
- desc: t('settingChat.historyCount.desc'),
90
- divider: false,
91
- hidden: !config.enableHistoryCount,
92
- label: t('settingChat.historyCount.title'),
93
- name: 'historyCount',
94
- },
95
- {
96
- children: <Switch />,
97
- label: t('settingChat.enableCompressThreshold.title'),
98
- minWidth: undefined,
99
- name: 'enableCompressThreshold',
100
- valuePropName: 'checked',
101
- },
102
- {
103
- children: <SliderWithInput max={32} min={0} />,
104
- desc: t('settingChat.compressThreshold.desc'),
105
- divider: false,
106
- hidden: !config.enableCompressThreshold,
107
- label: t('settingChat.compressThreshold.title'),
108
- name: 'compressThreshold',
109
- },
110
- ],
111
- icon: MessageSquare,
112
- title: t('settingChat.title'),
113
- };
114
-
115
- const model: ItemGroup = {
116
- children: [
117
- {
118
- children: (
119
- <Select
120
- options={modelList.map(({ name, displayName }) => ({
121
- label: displayName,
122
- value: name,
123
- }))}
124
- />
125
- ),
126
- desc: t('settingModel.model.desc'),
127
- label: t('settingModel.model.title'),
128
- name: 'model',
129
- tag: 'model',
130
- },
131
- {
132
- children: <SliderWithInput max={1} min={0} step={0.1} />,
133
- desc: t('settingModel.temperature.desc'),
134
- label: t('settingModel.temperature.title'),
135
- name: ['params', 'temperature'],
136
- tag: 'temperature',
137
- },
138
- {
139
- children: <SliderWithInput max={1} min={0} step={0.1} />,
140
- desc: t('settingModel.topP.desc'),
141
- label: t('settingModel.topP.title'),
142
- name: ['params', 'top_p'],
143
- tag: 'top_p',
144
- },
145
- {
146
- children: <SliderWithInput max={2} min={-2} step={0.1} />,
147
- desc: t('settingModel.presencePenalty.desc'),
148
- label: t('settingModel.presencePenalty.title'),
149
- name: ['params', 'presence_penalty'],
150
- tag: 'presence_penalty',
151
- },
152
- {
153
- children: <SliderWithInput max={2} min={-2} step={0.1} />,
154
- desc: t('settingModel.frequencyPenalty.desc'),
155
- label: t('settingModel.frequencyPenalty.title'),
156
- name: ['params', 'frequency_penalty'],
157
- tag: 'frequency_penalty',
158
- },
159
- {
160
- children: <Switch />,
161
- label: t('settingModel.enableMaxTokens.title'),
162
- minWidth: undefined,
163
- name: 'enableMaxTokens',
164
- valuePropName: 'checked',
165
- },
166
- {
167
- children: <SliderWithInput max={32_000} min={0} step={100} />,
168
- desc: t('settingModel.maxTokens.desc'),
169
- divider: false,
170
- hidden: !config?.enableMaxTokens,
171
- label: t('settingModel.maxTokens.title'),
172
- name: ['params', 'max_tokens'],
173
- tag: 'max_tokens',
174
- },
175
- ],
176
- icon: BrainCog,
177
- title: t('settingModel.title'),
178
- };
124
+ const model: ItemGroup = {
125
+ children: [
126
+ {
127
+ children: <ModelSelect />,
128
+ desc: t('settingModel.model.desc'),
129
+ label: t('settingModel.model.title'),
130
+ name: 'model',
131
+ tag: 'model',
132
+ },
133
+ {
134
+ children: <SliderWithInput max={1} min={0} step={0.1} />,
135
+ desc: t('settingModel.temperature.desc'),
136
+ label: t('settingModel.temperature.title'),
137
+ name: ['params', 'temperature'],
138
+ tag: 'temperature',
139
+ },
140
+ {
141
+ children: <SliderWithInput max={1} min={0} step={0.1} />,
142
+ desc: t('settingModel.topP.desc'),
143
+ label: t('settingModel.topP.title'),
144
+ name: ['params', 'top_p'],
145
+ tag: 'top_p',
146
+ },
147
+ {
148
+ children: <SliderWithInput max={2} min={-2} step={0.1} />,
149
+ desc: t('settingModel.presencePenalty.desc'),
150
+ label: t('settingModel.presencePenalty.title'),
151
+ name: ['params', 'presence_penalty'],
152
+ tag: 'presence_penalty',
153
+ },
154
+ {
155
+ children: <SliderWithInput max={2} min={-2} step={0.1} />,
156
+ desc: t('settingModel.frequencyPenalty.desc'),
157
+ label: t('settingModel.frequencyPenalty.title'),
158
+ name: ['params', 'frequency_penalty'],
159
+ tag: 'frequency_penalty',
160
+ },
161
+ {
162
+ children: <Switch />,
163
+ label: t('settingModel.enableMaxTokens.title'),
164
+ minWidth: undefined,
165
+ name: 'enableMaxTokens',
166
+ valuePropName: 'checked',
167
+ },
168
+ {
169
+ children: <SliderWithInput max={32_000} min={0} step={100} />,
170
+ desc: t('settingModel.maxTokens.desc'),
171
+ divider: false,
172
+ hidden: !enableMaxTokens,
173
+ label: t('settingModel.maxTokens.title'),
174
+ name: ['params', 'max_tokens'],
175
+ tag: 'max_tokens',
176
+ },
177
+ ],
178
+ icon: BrainCog,
179
+ title: t('settingModel.title'),
180
+ };
181
+ return [chat, model];
182
+ }, [
183
+ displayMode,
184
+ enableAutoCreateTopic,
185
+ enableHistoryCount,
186
+ enableMaxTokens,
187
+ enableCompressThreshold,
188
+ ]);
179
189
 
180
190
  return (
181
191
  <Form
182
192
  form={form}
183
- items={[chat, model]}
193
+ items={formItems}
184
194
  onValuesChange={debounce(updateConfig, 100)}
185
195
  {...FORM_STYLE}
186
196
  />
@@ -0,0 +1,23 @@
1
+ import { FormInstance } from 'antd/es/form/hooks/useForm';
2
+ import { useEffect } from 'react';
3
+
4
+ import { useStoreApi } from '../store';
5
+
6
+ export const useSyncConfig = (form: FormInstance) => {
7
+ const storeApi = useStoreApi();
8
+
9
+ useEffect(() => {
10
+ // set the first time
11
+ form.setFieldsValue(storeApi.getState().config);
12
+
13
+ // sync with later updated settings
14
+ const unsubscribe = storeApi.subscribe(
15
+ (s) => s.config,
16
+ (config) => form.setFieldsValue(config),
17
+ );
18
+
19
+ return () => {
20
+ unsubscribe();
21
+ };
22
+ }, []);
23
+ };
@@ -0,0 +1,39 @@
1
+ import { TokenTag } from '@lobehub/ui';
2
+ import { memo } from 'react';
3
+ import { useTranslation } from 'react-i18next';
4
+ import { Flexbox } from 'react-layout-kit';
5
+
6
+ import { useTokenCount } from '@/hooks/useTokenCount';
7
+ import { useGlobalStore } from '@/store/global';
8
+ import { modelProviderSelectors } from '@/store/global/selectors';
9
+
10
+ import { useStore } from '../store';
11
+
12
+ const Tokens = memo(() => {
13
+ const { t } = useTranslation('chat');
14
+ const [systemRole, model] = useStore((s) => [s.config.systemRole, s.config.model]);
15
+ const systemTokenCount = useTokenCount(systemRole);
16
+
17
+ const showTag = useGlobalStore(modelProviderSelectors.modelHasMaxToken(model));
18
+ const modelMaxTokens = useGlobalStore(modelProviderSelectors.modelMaxToken(model));
19
+
20
+ return (
21
+ <Flexbox align={'center'} gap={8} horizontal>
22
+ {showTag && (
23
+ <TokenTag
24
+ displayMode={'used'}
25
+ maxValue={modelMaxTokens}
26
+ shape={'square'}
27
+ text={{
28
+ overload: t('tokenTag.overload'),
29
+ remained: t('tokenTag.remained'),
30
+ used: t('tokenTag.used'),
31
+ }}
32
+ value={systemTokenCount}
33
+ />
34
+ )}
35
+ </Flexbox>
36
+ );
37
+ });
38
+
39
+ export default Tokens;
@@ -1,4 +1,4 @@
1
- import { EditableMessage, FormGroup, TokenTag } from '@lobehub/ui';
1
+ import { EditableMessage, FormGroup } from '@lobehub/ui';
2
2
  import { Button } from 'antd';
3
3
  import { createStyles } from 'antd-style';
4
4
  import { Bot } from 'lucide-react';
@@ -7,11 +7,9 @@ import { useTranslation } from 'react-i18next';
7
7
  import { Flexbox } from 'react-layout-kit';
8
8
 
9
9
  import { FORM_STYLE } from '@/const/layoutTokens';
10
- import { ModelTokens } from '@/const/llm';
11
- import { useTokenCount } from '@/hooks/useTokenCount';
12
- import { LanguageModel } from '@/types/llm';
13
10
 
14
11
  import { useStore } from '../store';
12
+ import TokenTag from './TokenTag';
15
13
 
16
14
  export const useStyles = createStyles(({ css, token }) => ({
17
15
  markdown: css`
@@ -26,31 +24,11 @@ const AgentPrompt = memo(() => {
26
24
  const { t } = useTranslation('setting');
27
25
  const { styles } = useStyles();
28
26
  const [editing, setEditing] = useState(false);
29
- const updateConfig = useStore((s) => s.setAgentConfig);
30
- const [systemRole, model] = useStore((s) => [s.config.systemRole, s.config.model]);
31
- const systemTokenCount = useTokenCount(systemRole);
32
-
33
- const showTag = model in ModelTokens;
27
+ const [systemRole, updateConfig] = useStore((s) => [s.config.systemRole, s.setAgentConfig]);
34
28
 
35
29
  return (
36
30
  <FormGroup
37
- extra={
38
- <Flexbox align={'center'} gap={8} horizontal>
39
- {showTag && (
40
- <TokenTag
41
- displayMode={'used'}
42
- maxValue={ModelTokens[model as LanguageModel]}
43
- shape={'square'}
44
- text={{
45
- overload: t('tokenTag.overload', { ns: 'chat' }),
46
- remained: t('tokenTag.remained', { ns: 'chat' }),
47
- used: t('tokenTag.used', { ns: 'chat' }),
48
- }}
49
- value={systemTokenCount}
50
- />
51
- )}
52
- </Flexbox>
53
- }
31
+ extra={<TokenTag />}
54
32
  icon={Bot}
55
33
  style={FORM_STYLE.style}
56
34
  title={t('settingAgent.prompt.title')}
@@ -1,5 +1,6 @@
1
1
  import { StoreApi } from 'zustand';
2
2
  import { createContext } from 'zustand-utils';
3
+ import { subscribeWithSelector } from 'zustand/middleware';
3
4
  import { shallow } from 'zustand/shallow';
4
5
  import { createWithEqualityFn } from 'zustand/traditional';
5
6
 
@@ -7,6 +8,13 @@ import { Store, store } from './action';
7
8
 
8
9
  export type { State } from './initialState';
9
10
 
10
- export const createStore = () => createWithEqualityFn(store, shallow);
11
+ export const createStore = () => createWithEqualityFn(subscribeWithSelector(store), shallow);
11
12
 
12
- export const { useStore, useStoreApi, Provider } = createContext<StoreApi<Store>>();
13
+ interface StoreApiWithSelector extends Omit<StoreApi<Store>, 'subscribe'> {
14
+ subscribe: <T extends keyof Store>(
15
+ selector: (state: Store, prevState: Store) => void,
16
+ listener?: (state: Store[T]) => void,
17
+ ) => () => void;
18
+ }
19
+
20
+ export const { useStore, useStoreApi, Provider } = createContext<StoreApiWithSelector>();
@@ -7,6 +7,8 @@ import { useTranslation } from 'react-i18next';
7
7
  import { Center } from 'react-layout-kit';
8
8
 
9
9
  import { useFileStore } from '@/store/file';
10
+ import { useGlobalStore } from '@/store/global';
11
+ import { modelProviderSelectors } from '@/store/global/selectors';
10
12
  import { useSessionStore } from '@/store/session';
11
13
  import { agentSelectors } from '@/store/session/selectors';
12
14
 
@@ -16,9 +18,8 @@ const FileUpload = memo(() => {
16
18
  const theme = useTheme();
17
19
  const upload = useFileStore((s) => s.uploadFile);
18
20
 
19
- const canUpload = useSessionStore(agentSelectors.modelHasVisionAbility);
20
-
21
- if (!canUpload) return null;
21
+ const model = useSessionStore(agentSelectors.currentAgentModel);
22
+ const canUpload = useGlobalStore(modelProviderSelectors.modelEnabledVision(model));
22
23
 
23
24
  return (
24
25
  <Upload
@@ -31,6 +32,7 @@ const FileUpload = memo(() => {
31
32
  setLoading(false);
32
33
  return false;
33
34
  }}
35
+ disabled={!canUpload}
34
36
  multiple={true}
35
37
  showUploadList={false}
36
38
  >
@@ -44,7 +46,12 @@ const FileUpload = memo(() => {
44
46
  ></Icon>
45
47
  </Center>
46
48
  ) : (
47
- <ActionIcon icon={LucideImage} placement={'bottom'} title={t('upload.actionTooltip')} />
49
+ <ActionIcon
50
+ disable={!canUpload}
51
+ icon={LucideImage}
52
+ placement={'bottom'}
53
+ title={t(canUpload ? 'upload.actionTooltip' : 'upload.disabled')}
54
+ />
48
55
  )}
49
56
  </Upload>
50
57
  );
@@ -1,41 +1,83 @@
1
1
  import { ActionIcon } from '@lobehub/ui';
2
2
  import { Dropdown } from 'antd';
3
+ import { createStyles } from 'antd-style';
3
4
  import isEqual from 'fast-deep-equal';
4
5
  import { BrainCog } from 'lucide-react';
5
- import { memo } from 'react';
6
+ import { memo, useMemo } from 'react';
6
7
  import { useTranslation } from 'react-i18next';
7
8
 
9
+ import { ModelItemRender, ProviderItemRender } from '@/components/ModelSelect';
8
10
  import { useGlobalStore } from '@/store/global';
9
11
  import { modelProviderSelectors } from '@/store/global/selectors';
10
12
  import { useSessionStore } from '@/store/session';
11
13
  import { agentSelectors } from '@/store/session/selectors';
12
- import { LanguageModel } from '@/types/llm';
14
+ import { ModelProviderCard } from '@/types/llm';
15
+
16
+ const useStyles = createStyles(({ css, prefixCls }) => ({
17
+ menu: css`
18
+ .${prefixCls}-dropdown-menu-item {
19
+ display: flex;
20
+ gap: 8px;
21
+ }
22
+ .${prefixCls}-dropdown-menu {
23
+ &-item-group-title {
24
+ padding-inline: 8px;
25
+ }
26
+
27
+ &-item-group-list {
28
+ margin: 0 !important;
29
+ }
30
+ }
31
+ `,
32
+ }));
13
33
 
14
34
  const ModelSwitch = memo(() => {
15
- const { t } = useTranslation('setting');
35
+ const { t } = useTranslation('chat');
36
+ const { styles } = useStyles();
37
+ const model = useSessionStore(agentSelectors.currentAgentModel);
38
+ const updateAgentConfig = useSessionStore((s) => s.updateAgentConfig);
16
39
 
17
- const [model, updateAgentConfig] = useSessionStore((s) => {
18
- return [agentSelectors.currentAgentModel(s), s.updateAgentConfig];
19
- });
40
+ const select = useGlobalStore(modelProviderSelectors.modelSelectList, isEqual);
41
+ const enabledList = select.filter((s) => s.enabled);
20
42
 
21
- const modelList = useGlobalStore(modelProviderSelectors.modelList, isEqual);
43
+ const items = useMemo(() => {
44
+ const getModelItems = (provider: ModelProviderCard) =>
45
+ provider.chatModels
46
+ .filter((c) => !c.hidden)
47
+ .map((model) => ({
48
+ key: model.id,
49
+ label: <ModelItemRender {...model} />,
50
+ onClick: () => {
51
+ updateAgentConfig({ model: model.id, provider: provider.id });
52
+ },
53
+ }));
22
54
 
55
+ if (enabledList.length === 1) {
56
+ const provider = enabledList[0];
57
+ return getModelItems(provider);
58
+ }
59
+
60
+ return enabledList.map((provider) => ({
61
+ children: getModelItems(provider),
62
+ key: provider.id,
63
+ label: <ProviderItemRender provider={provider.id} />,
64
+ type: 'group',
65
+ }));
66
+ }, [enabledList]);
23
67
  return (
24
68
  <Dropdown
25
69
  menu={{
26
70
  activeKey: model,
27
- items: modelList.map(({ name, displayName }) => ({ key: name, label: displayName })),
28
- onClick: (e) => {
29
- updateAgentConfig({ model: e.key as LanguageModel });
30
- },
71
+ className: styles.menu,
72
+ items,
31
73
  style: {
32
- maxHeight: 400,
33
- overflow: 'scroll',
74
+ maxHeight: 500,
75
+ overflowY: 'scroll',
34
76
  },
35
77
  }}
36
78
  trigger={['click']}
37
79
  >
38
- <ActionIcon icon={BrainCog} placement={'bottom'} title={t('settingModel.model.title')} />
80
+ <ActionIcon icon={BrainCog} placement={'bottom'} title={t('ModelSwitch.title')} />
39
81
  </Dropdown>
40
82
  );
41
83
  });