@lobehub/chat 0.122.9 → 0.123.1

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 (231) hide show
  1. package/.env.example +22 -2
  2. package/.eslintrc.js +1 -0
  3. package/CHANGELOG.md +59 -0
  4. package/Dockerfile +7 -1
  5. package/README.md +12 -10
  6. package/README.zh-CN.md +12 -10
  7. package/docs/Deployment/Docker-Deployment.md +1 -1
  8. package/docs/Deployment/Docker-Deployment.zh-CN.md +1 -1
  9. package/docs/Deployment/Environment-Variable.md +65 -23
  10. package/docs/Deployment/Environment-Variable.zh-CN.md +64 -22
  11. package/locales/ar/chat.json +12 -1
  12. package/locales/ar/common.json +16 -0
  13. package/locales/ar/error.json +35 -6
  14. package/locales/ar/setting.json +53 -6
  15. package/locales/de-DE/chat.json +12 -1
  16. package/locales/de-DE/common.json +16 -0
  17. package/locales/de-DE/error.json +35 -6
  18. package/locales/de-DE/setting.json +53 -6
  19. package/locales/en-US/chat.json +13 -2
  20. package/locales/en-US/common.json +16 -0
  21. package/locales/en-US/error.json +35 -6
  22. package/locales/en-US/setting.json +53 -6
  23. package/locales/es-ES/chat.json +12 -1
  24. package/locales/es-ES/common.json +16 -0
  25. package/locales/es-ES/error.json +35 -6
  26. package/locales/es-ES/setting.json +53 -6
  27. package/locales/fr-FR/chat.json +13 -2
  28. package/locales/fr-FR/common.json +16 -0
  29. package/locales/fr-FR/error.json +35 -6
  30. package/locales/fr-FR/setting.json +53 -6
  31. package/locales/it-IT/chat.json +12 -1
  32. package/locales/it-IT/common.json +16 -0
  33. package/locales/it-IT/error.json +35 -6
  34. package/locales/it-IT/setting.json +53 -6
  35. package/locales/ja-JP/chat.json +12 -1
  36. package/locales/ja-JP/common.json +16 -0
  37. package/locales/ja-JP/error.json +35 -6
  38. package/locales/ja-JP/setting.json +53 -6
  39. package/locales/ko-KR/chat.json +12 -1
  40. package/locales/ko-KR/common.json +16 -0
  41. package/locales/ko-KR/error.json +35 -6
  42. package/locales/ko-KR/setting.json +53 -6
  43. package/locales/nl-NL/chat.json +12 -1
  44. package/locales/nl-NL/common.json +16 -0
  45. package/locales/nl-NL/error.json +35 -6
  46. package/locales/nl-NL/setting.json +53 -6
  47. package/locales/pl-PL/chat.json +12 -1
  48. package/locales/pl-PL/common.json +16 -0
  49. package/locales/pl-PL/error.json +35 -6
  50. package/locales/pl-PL/setting.json +53 -6
  51. package/locales/pt-BR/chat.json +12 -1
  52. package/locales/pt-BR/common.json +16 -0
  53. package/locales/pt-BR/error.json +35 -6
  54. package/locales/pt-BR/setting.json +53 -6
  55. package/locales/ru-RU/chat.json +12 -1
  56. package/locales/ru-RU/common.json +16 -0
  57. package/locales/ru-RU/error.json +35 -6
  58. package/locales/ru-RU/setting.json +53 -6
  59. package/locales/tr-TR/chat.json +12 -1
  60. package/locales/tr-TR/common.json +16 -0
  61. package/locales/tr-TR/error.json +35 -6
  62. package/locales/tr-TR/setting.json +53 -6
  63. package/locales/vi-VN/chat.json +12 -1
  64. package/locales/vi-VN/common.json +16 -0
  65. package/locales/vi-VN/error.json +35 -6
  66. package/locales/vi-VN/setting.json +53 -6
  67. package/locales/zh-CN/chat.json +12 -1
  68. package/locales/zh-CN/common.json +16 -0
  69. package/locales/zh-CN/error.json +34 -5
  70. package/locales/zh-CN/setting.json +59 -12
  71. package/locales/zh-TW/chat.json +12 -1
  72. package/locales/zh-TW/common.json +16 -0
  73. package/locales/zh-TW/error.json +35 -6
  74. package/locales/zh-TW/setting.json +53 -6
  75. package/next.config.mjs +8 -2
  76. package/package.json +8 -2
  77. package/src/app/api/chat/[provider]/agentRuntime.ts +129 -0
  78. package/src/app/api/chat/[provider]/route.ts +64 -0
  79. package/src/app/api/chat/auth.ts +42 -0
  80. package/src/app/api/chat/google/route.ts +86 -0
  81. package/src/app/api/config/route.ts +6 -1
  82. package/src/app/api/config.ts +3 -3
  83. package/src/app/api/errorResponse.test.ts +8 -8
  84. package/src/app/api/errorResponse.ts +43 -6
  85. package/src/app/api/openai/createBizOpenAI/createAzureOpenai.ts +1 -1
  86. package/src/app/api/openai/createBizOpenAI/createOpenai.ts +1 -1
  87. package/src/app/api/openai/createBizOpenAI/index.ts +2 -2
  88. package/src/app/api/plugin/gateway/route.ts +4 -1
  89. package/src/app/chat/(desktop)/features/ChatHeader.tsx +6 -4
  90. package/src/app/chat/(desktop)/features/ChatInput/Footer/index.tsx +13 -3
  91. package/src/app/chat/(desktop)/features/ChatInput/Header/index.tsx +16 -24
  92. package/src/app/chat/(desktop)/features/SideBar/index.tsx +1 -0
  93. package/src/app/chat/features/ChatHeader/ShareButton/Preview.tsx +3 -3
  94. package/src/app/chat/features/SessionListContent/List/Item/index.tsx +2 -3
  95. package/src/app/settings/llm/Azure/index.tsx +145 -0
  96. package/src/app/settings/llm/Bedrock/index.tsx +109 -0
  97. package/src/app/settings/llm/{LLM/Checker.tsx → Checker.tsx} +11 -14
  98. package/src/app/settings/llm/Google/index.tsx +74 -0
  99. package/src/app/settings/llm/{LLM → OpenAI}/index.tsx +26 -44
  100. package/src/app/settings/llm/Zhipu/index.tsx +78 -0
  101. package/src/app/settings/llm/const.ts +13 -0
  102. package/src/app/settings/llm/page.tsx +42 -2
  103. package/src/app/settings/llm/useSyncSettings.ts +23 -0
  104. package/src/chains/__tests__/langDetect.test.ts +2 -2
  105. package/src/chains/__tests__/pickEmoji.test.ts +2 -2
  106. package/src/chains/langDetect.ts +2 -2
  107. package/src/chains/pickEmoji.ts +2 -2
  108. package/src/chains/summaryAgentName.ts +2 -2
  109. package/src/chains/summaryDescription.ts +2 -2
  110. package/src/chains/summaryTags.ts +2 -2
  111. package/src/chains/summaryTitle.ts +2 -2
  112. package/src/chains/translate.ts +2 -2
  113. package/src/components/ModelIcon/index.tsx +37 -0
  114. package/src/components/ModelProviderIcon/index.tsx +44 -0
  115. package/src/components/ModelSelect/index.tsx +133 -0
  116. package/src/components/ModelTag/ModelIcon.tsx +35 -0
  117. package/src/components/ModelTag/index.tsx +13 -0
  118. package/src/config/modelProviders/bedrock.ts +43 -0
  119. package/src/config/modelProviders/google.ts +20 -0
  120. package/src/config/modelProviders/index.ts +18 -0
  121. package/src/config/modelProviders/openai.ts +110 -0
  122. package/src/config/modelProviders/zhipu.ts +34 -0
  123. package/src/config/{server.ts → server/app.ts} +1 -24
  124. package/src/config/server/index.ts +13 -0
  125. package/src/config/server/provider.ts +78 -0
  126. package/src/const/auth.ts +30 -0
  127. package/src/const/fetch.ts +3 -0
  128. package/src/const/settings.ts +24 -4
  129. package/src/database/core/migrations/migrateSettingsToUser/index.test.ts +2 -1
  130. package/src/database/core/migrations/migrateSettingsToUser/index.ts +1 -18
  131. package/src/database/core/migrations/migrateSettingsToUser/type.ts +100 -0
  132. package/src/database/models/message.ts +3 -1
  133. package/src/database/schemas/message.ts +1 -0
  134. package/src/database/schemas/session.ts +1 -0
  135. package/src/features/AgentSetting/AgentConfig/ModelSelect.tsx +71 -0
  136. package/src/features/AgentSetting/AgentConfig/index.tsx +171 -161
  137. package/src/features/AgentSetting/AgentConfig/useSyncConfig.ts +23 -0
  138. package/src/features/AgentSetting/AgentPrompt/TokenTag.tsx +39 -0
  139. package/src/features/AgentSetting/AgentPrompt/index.tsx +4 -26
  140. package/src/features/AgentSetting/store/index.ts +10 -2
  141. package/src/features/ChatInput/ActionBar/FileUpload.tsx +15 -5
  142. package/src/features/ChatInput/ActionBar/ModelSwitch.tsx +56 -14
  143. package/src/features/ChatInput/ActionBar/Token/TokenTag.tsx +42 -7
  144. package/src/features/ChatInput/ActionBar/Token/index.tsx +5 -2
  145. package/src/features/ChatInput/ActionBar/Tools/index.tsx +11 -1
  146. package/src/features/ChatInput/useChatInput.ts +5 -1
  147. package/src/features/Conversation/Error/APIKeyForm/Bedrock.tsx +80 -0
  148. package/src/features/Conversation/Error/APIKeyForm/Google.tsx +61 -0
  149. package/src/features/Conversation/Error/APIKeyForm/OpenAI.tsx +63 -0
  150. package/src/features/Conversation/Error/APIKeyForm/Zhipu.tsx +62 -0
  151. package/src/features/Conversation/Error/APIKeyForm/index.tsx +72 -0
  152. package/src/features/Conversation/Error/AccessCodeForm.tsx +63 -0
  153. package/src/features/Conversation/Error/ErrorJsonViewer.tsx +26 -0
  154. package/src/features/Conversation/Error/InvalidAPIKey.tsx +16 -0
  155. package/src/features/Conversation/Error/InvalidAccessCode.tsx +50 -0
  156. package/src/features/Conversation/Error/OpenAiBizError.tsx +9 -17
  157. package/src/features/Conversation/Error/{Plugin/PluginSettings.tsx → PluginSettings.tsx} +8 -3
  158. package/src/features/Conversation/Error/index.tsx +75 -70
  159. package/src/features/Conversation/Error/style.tsx +9 -3
  160. package/src/features/Conversation/Extras/Assistant.tsx +2 -3
  161. package/src/features/Conversation/Plugins/Inspector/index.tsx +8 -2
  162. package/src/features/Conversation/components/ChatItem/index.tsx +9 -24
  163. package/src/features/Conversation/types/index.tsx +2 -5
  164. package/src/hooks/_header.ts +35 -0
  165. package/src/libs/agent-runtime/BaseAI.ts +9 -0
  166. package/src/libs/agent-runtime/azureOpenai/index.ts +72 -0
  167. package/src/libs/agent-runtime/bedrock/index.ts +134 -0
  168. package/src/libs/agent-runtime/error.ts +26 -0
  169. package/src/libs/agent-runtime/google/index.ts +160 -0
  170. package/src/libs/agent-runtime/index.ts +9 -0
  171. package/src/libs/agent-runtime/openai/index.test.ts +176 -0
  172. package/src/libs/agent-runtime/openai/index.ts +107 -0
  173. package/src/libs/agent-runtime/types/chat.ts +119 -0
  174. package/src/libs/agent-runtime/types/index.ts +2 -0
  175. package/src/libs/agent-runtime/types/type.ts +34 -0
  176. package/src/libs/agent-runtime/utils/createError.ts +10 -0
  177. package/src/libs/agent-runtime/utils/debugStream.ts +18 -0
  178. package/src/libs/agent-runtime/utils/env.ts +1 -0
  179. package/src/libs/agent-runtime/utils/handleOpenAIError.ts +39 -0
  180. package/src/libs/agent-runtime/utils/uriParser.ts +16 -0
  181. package/src/libs/agent-runtime/zhipu/authToken.ts +22 -0
  182. package/src/libs/agent-runtime/zhipu/index.ts +125 -0
  183. package/src/locales/default/chat.ts +12 -2
  184. package/src/locales/default/common.ts +16 -0
  185. package/src/locales/default/error.ts +42 -5
  186. package/src/locales/default/setting.ts +61 -12
  187. package/src/services/__tests__/chat.test.ts +8 -2
  188. package/src/services/__tests__/message.test.ts +1 -1
  189. package/src/services/_auth.ts +106 -0
  190. package/src/services/_header.ts +1 -0
  191. package/src/services/_url.ts +0 -2
  192. package/src/services/chat.ts +34 -14
  193. package/src/store/chat/slices/message/action.ts +9 -6
  194. package/src/store/global/slices/common/action.ts +8 -2
  195. package/src/store/global/slices/settings/action.test.ts +2 -2
  196. package/src/store/global/slices/settings/action.ts +11 -4
  197. package/src/store/global/slices/settings/selectors/__snapshots__/modelProvider.test.ts.snap +24 -92
  198. package/src/store/global/slices/settings/selectors/__snapshots__/selectors.test.ts.snap +2 -0
  199. package/src/store/global/slices/settings/selectors/modelProvider.test.ts +127 -50
  200. package/src/store/global/slices/settings/selectors/modelProvider.ts +145 -43
  201. package/src/store/session/slices/agent/selectors.test.ts +0 -48
  202. package/src/store/session/slices/agent/selectors.ts +7 -12
  203. package/src/types/agent/index.ts +6 -2
  204. package/src/types/fetch.ts +4 -2
  205. package/src/types/files.ts +3 -3
  206. package/src/types/llm.ts +34 -0
  207. package/src/types/message/index.ts +7 -3
  208. package/src/types/openai/chat.ts +6 -11
  209. package/src/types/settings/index.ts +1 -0
  210. package/src/types/settings/modelProvider.ts +37 -1
  211. package/src/utils/__snapshots__/parseModels.test.ts.snap +63 -0
  212. package/src/utils/parseModels.test.ts +24 -0
  213. package/src/utils/parseModels.ts +37 -0
  214. package/src/app/api/openai/chat/createChatCompletion.test.ts +0 -166
  215. package/src/app/api/openai/chat/createChatCompletion.ts +0 -76
  216. package/src/app/api/openai/chat/route.ts +0 -19
  217. package/src/app/api/openai/models/route.ts +0 -17
  218. package/src/app/settings/llm/LLM/ModelList.tsx +0 -51
  219. package/src/app/settings/llm/LLM/getModelList.ts +0 -9
  220. package/src/app/settings/llm/index.tsx +0 -36
  221. package/src/const/llm.ts +0 -32
  222. package/src/features/Conversation/Error/ApiKeyForm.tsx +0 -87
  223. package/src/features/Conversation/Error/ErrorActionContainer.tsx +0 -23
  224. package/src/features/Conversation/Error/InvalidAccess.tsx +0 -90
  225. package/src/features/Conversation/Error/OpenAPIKey.tsx +0 -13
  226. package/src/features/Conversation/Error/Plugin/PluginError.tsx +0 -30
  227. package/src/services/modelList.ts +0 -15
  228. /package/src/app/chat/(desktop)/features/ChatInput/{Header → Footer}/DragUpload.tsx +0 -0
  229. /package/src/{app/api/openai/chat → libs/agent-runtime/utils}/desensitizeUrl.test.ts +0 -0
  230. /package/src/{app/api/openai/chat → libs/agent-runtime/utils}/desensitizeUrl.ts +0 -0
  231. /package/src/types/{translate.ts → message/translate.ts} +0 -0
@@ -2,24 +2,27 @@ import { PluginRequestPayload, createHeadersWithPluginSettings } from '@lobehub/
2
2
  import { produce } from 'immer';
3
3
  import { merge } from 'lodash-es';
4
4
 
5
- import { isVisionModel } from '@/const/llm';
6
5
  import { DEFAULT_AGENT_CONFIG } from '@/const/settings';
6
+ import { ModelProvider } from '@/libs/agent-runtime';
7
7
  import { filesSelectors, useFileStore } from '@/store/file';
8
+ import { useGlobalStore } from '@/store/global';
9
+ import { modelProviderSelectors } from '@/store/global/selectors';
8
10
  import { useToolStore } from '@/store/tool';
9
11
  import { pluginSelectors, toolSelectors } from '@/store/tool/selectors';
10
12
  import { ChatMessage } from '@/types/message';
11
- import type { OpenAIChatMessage, OpenAIChatStreamPayload } from '@/types/openai/chat';
13
+ import type { ChatStreamPayload, OpenAIChatMessage } from '@/types/openai/chat';
12
14
  import { UserMessageContentPart } from '@/types/openai/chat';
13
15
  import { fetchAIFactory, getMessageError } from '@/utils/fetch';
14
16
 
17
+ import { createHeaderWithAuth } from './_auth';
15
18
  import { createHeaderWithOpenAI } from './_header';
16
- import { OPENAI_URLS, PLUGINS_URLS } from './_url';
19
+ import { PLUGINS_URLS } from './_url';
17
20
 
18
21
  interface FetchOptions {
19
22
  signal?: AbortSignal | undefined;
20
23
  }
21
24
 
22
- interface GetChatCompletionPayload extends Partial<Omit<OpenAIChatStreamPayload, 'messages'>> {
25
+ interface GetChatCompletionPayload extends Partial<Omit<ChatStreamPayload, 'messages'>> {
23
26
  messages: ChatMessage[];
24
27
  }
25
28
 
@@ -48,29 +51,39 @@ class ChatService {
48
51
 
49
52
  const filterTools = toolSelectors.enabledSchema(enabledPlugins)(useToolStore.getState());
50
53
 
54
+ // check this model can use function call
55
+ const canUseFC = modelProviderSelectors.modelEnabledFunctionCall(payload.model)(
56
+ useGlobalStore.getState(),
57
+ );
51
58
  // the rule that model can use tools:
52
59
  // 1. tools is not empty
53
- // 2. model is not in vision white list, because vision model can't use tools
54
- // TODO: we need to find some method to let vision model use tools
55
- const shouldUseTools = filterTools.length > 0 && !isVisionModel(payload.model);
60
+ // 2. model can use function call
61
+ const shouldUseTools = filterTools.length > 0 && canUseFC;
62
+
56
63
  const tools = shouldUseTools ? filterTools : undefined;
57
64
 
58
65
  return this.getChatCompletion({ ...params, messages: oaiMessages, tools }, options);
59
66
  };
60
67
 
61
- getChatCompletion = (params: Partial<OpenAIChatStreamPayload>, options?: FetchOptions) => {
68
+ getChatCompletion = async (params: Partial<ChatStreamPayload>, options?: FetchOptions) => {
69
+ const { provider = ModelProvider.OpenAI, ...res } = params;
62
70
  const payload = merge(
63
71
  {
64
72
  model: DEFAULT_AGENT_CONFIG.model,
65
73
  stream: true,
66
74
  ...DEFAULT_AGENT_CONFIG.params,
67
75
  },
68
- params,
76
+ res,
69
77
  );
70
78
 
71
- return fetch(OPENAI_URLS.chat, {
79
+ const headers = await createHeaderWithAuth({
80
+ headers: { 'Content-Type': 'application/json' },
81
+ provider,
82
+ });
83
+
84
+ return fetch(`/api/chat/${provider}`, {
72
85
  body: JSON.stringify(payload),
73
- headers: createHeaderWithOpenAI({ 'Content-Type': 'application/json' }),
86
+ headers,
74
87
  method: 'POST',
75
88
  signal: options?.signal,
76
89
  });
@@ -112,7 +125,7 @@ class ChatService {
112
125
  model,
113
126
  }: {
114
127
  messages: ChatMessage[];
115
- model?: string;
128
+ model: string;
116
129
  tools?: string[];
117
130
  }): OpenAIChatMessage[] => {
118
131
  // handle content type for vision model
@@ -125,7 +138,11 @@ class ChatService {
125
138
 
126
139
  if (imageList.length === 0) return m.content;
127
140
 
128
- if (!isVisionModel(model)) {
141
+ const canUploadFile = modelProviderSelectors.modelEnabledUpload(model)(
142
+ useGlobalStore.getState(),
143
+ );
144
+
145
+ if (!canUploadFile) {
129
146
  return m.content;
130
147
  }
131
148
 
@@ -156,11 +173,14 @@ class ChatService {
156
173
 
157
174
  return produce(postMessages, (draft) => {
158
175
  if (!tools || tools.length === 0) return;
176
+ const hasFC = modelProviderSelectors.modelEnabledFunctionCall(model)(
177
+ useGlobalStore.getState(),
178
+ );
179
+ if (!hasFC) return;
159
180
 
160
181
  const systemMessage = draft.find((i) => i.role === 'system');
161
182
 
162
183
  const toolsSystemRoles = toolSelectors.enabledSystemRoles(tools)(useToolStore.getState());
163
-
164
184
  if (!toolsSystemRoles) return;
165
185
 
166
186
  if (systemMessage) {
@@ -5,7 +5,6 @@ import { template } from 'lodash-es';
5
5
  import useSWR, { SWRResponse, mutate } from 'swr';
6
6
  import { StateCreator } from 'zustand/vanilla';
7
7
 
8
- import { GPT4_VISION_MODEL_DEFAULT_MAX_TOKENS } from '@/const/llm';
9
8
  import { LOADING_FLAT, isFunctionMessageAtStart, testFunctionMessageAtEnd } from '@/const/message';
10
9
  import { CreateMessageParams } from '@/database/models/message';
11
10
  import { chatService } from '@/services/chat';
@@ -255,13 +254,14 @@ export const chatMessage: StateCreator<
255
254
  coreProcessMessage: async (messages, userMessageId) => {
256
255
  const { fetchAIChatMessage, triggerFunctionCall, refreshMessages, activeTopicId } = get();
257
256
 
258
- const { model } = getAgentConfig();
257
+ const { model, provider } = getAgentConfig();
259
258
 
260
259
  // 1. Add an empty message to place the AI response
261
260
  const assistantMessage: CreateMessageParams = {
262
261
  role: 'assistant',
263
262
  content: LOADING_FLAT,
264
263
  fromModel: model,
264
+ fromProvider: provider,
265
265
 
266
266
  parentId: userMessageId,
267
267
  sessionId: get().activeId,
@@ -288,13 +288,14 @@ export const chatMessage: StateCreator<
288
288
  const functionMessage: CreateMessageParams = {
289
289
  role: 'function',
290
290
  content: functionCallContent,
291
- extra: {
292
- fromModel: model,
293
- },
291
+ fromModel: model,
292
+ fromProvider: provider,
293
+
294
294
  parentId: userMessageId,
295
295
  sessionId: get().activeId,
296
296
  topicId: activeTopicId,
297
297
  };
298
+
298
299
  functionId = await messageService.create(functionMessage);
299
300
  }
300
301
 
@@ -368,7 +369,8 @@ export const chatMessage: StateCreator<
368
369
  if (config.model === 'gpt-4-vision-preview') {
369
370
  /* eslint-disable unicorn/no-lonely-if */
370
371
  if (!config.params.max_tokens)
371
- config.params.max_tokens = GPT4_VISION_MODEL_DEFAULT_MAX_TOKENS;
372
+ // refs: https://github.com/lobehub/lobe-chat/issues/837
373
+ config.params.max_tokens = 2048;
372
374
  }
373
375
 
374
376
  const fetcher = () =>
@@ -376,6 +378,7 @@ export const chatMessage: StateCreator<
376
378
  {
377
379
  messages: preprocessMsgs,
378
380
  model: config.model,
381
+ provider: config.provider,
379
382
  ...config.params,
380
383
  plugins: config.plugins,
381
384
  },
@@ -1,5 +1,6 @@
1
1
  import { gt } from 'semver';
2
2
  import useSWR, { SWRResponse, mutate } from 'swr';
3
+ import { DeepPartial } from 'utility-types';
3
4
  import type { StateCreator } from 'zustand/vanilla';
4
5
 
5
6
  import { INBOX_SESSION_ID } from '@/const/session';
@@ -8,7 +9,7 @@ import { CURRENT_VERSION } from '@/const/version';
8
9
  import { globalService } from '@/services/global';
9
10
  import { UserConfig, userService } from '@/services/user';
10
11
  import type { GlobalStore } from '@/store/global';
11
- import type { GlobalServerConfig } from '@/types/settings';
12
+ import type { GlobalServerConfig, GlobalSettings } from '@/types/settings';
12
13
  import { merge } from '@/utils/merge';
13
14
  import { setNamespace } from '@/utils/storeDebug';
14
15
 
@@ -63,7 +64,12 @@ export const createCommonSlice: StateCreator<
63
64
  useSWR<GlobalServerConfig>('fetchGlobalConfig', globalService.getGlobalConfig, {
64
65
  onSuccess: (data) => {
65
66
  if (data) {
66
- const defaultSettings = merge(get().defaultSettings, { defaultAgent: data.defaultAgent });
67
+ const serverSettings: DeepPartial<GlobalSettings> = {
68
+ defaultAgent: data.defaultAgent,
69
+ languageModel: data.languageModel,
70
+ };
71
+
72
+ const defaultSettings = merge(get().defaultSettings, serverSettings);
67
73
  set({ defaultSettings, serverConfig: data }, false, n('initGlobalConfig'));
68
74
  }
69
75
  },
@@ -67,14 +67,14 @@ describe('SettingsAction', () => {
67
67
  });
68
68
  });
69
69
 
70
- describe('setOpenAIConfig', () => {
70
+ describe('setModelProviderConfig', () => {
71
71
  it('should set OpenAI configuration', async () => {
72
72
  const { result } = renderHook(() => useGlobalStore());
73
73
  const openAIConfig: Partial<OpenAIConfig> = { OPENAI_API_KEY: 'test-key' };
74
74
 
75
75
  // Perform the action
76
76
  await act(async () => {
77
- await result.current.setOpenAIConfig(openAIConfig);
77
+ await result.current.setModelProviderConfig('openAI', openAIConfig);
78
78
  });
79
79
 
80
80
  // Assert that updateUserSettings was called with the correct OpenAI configuration
@@ -7,7 +7,7 @@ import { userService } from '@/services/user';
7
7
  import type { GlobalStore } from '@/store/global';
8
8
  import { SettingsTabs } from '@/store/global/initialState';
9
9
  import { LobeAgentSettings } from '@/types/session';
10
- import type { GlobalSettings, OpenAIConfig } from '@/types/settings';
10
+ import { GlobalLLMConfig, GlobalLLMProviderKey, GlobalSettings } from '@/types/settings';
11
11
  import { difference } from '@/utils/difference';
12
12
  import { merge } from '@/utils/merge';
13
13
 
@@ -17,10 +17,14 @@ import { merge } from '@/utils/merge';
17
17
  export interface SettingsAction {
18
18
  importAppSettings: (settings: GlobalSettings) => Promise<void>;
19
19
  resetSettings: () => Promise<void>;
20
- setOpenAIConfig: (config: Partial<OpenAIConfig>) => Promise<void>;
20
+ setModelProviderConfig: <T extends GlobalLLMProviderKey>(
21
+ provider: T,
22
+ config: Partial<GlobalLLMConfig[T]>,
23
+ ) => Promise<void>;
21
24
  setSettings: (settings: DeepPartial<GlobalSettings>) => Promise<void>;
22
25
  switchSettingTabs: (tab: SettingsTabs) => void;
23
26
  switchThemeMode: (themeMode: ThemeMode) => Promise<void>;
27
+ toggleProviderEnabled: (provider: GlobalLLMProviderKey, enabled: boolean) => Promise<void>;
24
28
  updateDefaultAgent: (agent: DeepPartial<LobeAgentSettings>) => Promise<void>;
25
29
  }
26
30
 
@@ -41,8 +45,8 @@ export const createSettingsSlice: StateCreator<
41
45
  await userService.resetUserSettings();
42
46
  await get().refreshUserConfig();
43
47
  },
44
- setOpenAIConfig: async (config) => {
45
- await get().setSettings({ languageModel: { openAI: config } });
48
+ setModelProviderConfig: async (provider, config) => {
49
+ await get().setSettings({ languageModel: { [provider]: config } });
46
50
  },
47
51
  setSettings: async (settings) => {
48
52
  const { settings: prevSetting, defaultSettings } = get();
@@ -63,6 +67,9 @@ export const createSettingsSlice: StateCreator<
63
67
  switchThemeMode: async (themeMode) => {
64
68
  await get().setSettings({ themeMode });
65
69
  },
70
+ toggleProviderEnabled: async (provider, enabled) => {
71
+ await get().setSettings({ languageModel: { [provider]: { enabled } } });
72
+ },
66
73
  updateDefaultAgent: async (defaultAgent) => {
67
74
  await get().setSettings({ defaultAgent });
68
75
  },
@@ -3,98 +3,30 @@
3
3
  exports[`modelProviderSelectors > CUSTOM_MODELS > custom deletion, addition, and renaming of models 1`] = `
4
4
  [
5
5
  {
6
- "displayName": "llama",
7
- "name": "llama",
8
- },
9
- {
10
- "displayName": "claude-2",
11
- "name": "claude-2",
12
- },
13
- {
14
- "displayName": "gpt-4-32k",
15
- "name": "gpt-4-0125-preview",
16
- },
17
- ]
18
- `;
19
-
20
- exports[`modelProviderSelectors > CUSTOM_MODELS > duplicate naming model 1`] = `
21
- [
22
- {
23
- "displayName": "gpt-3.5-turbo",
24
- "name": "gpt-3.5-turbo",
25
- },
26
- {
27
- "displayName": "gpt-3.5-turbo-1106",
28
- "name": "gpt-3.5-turbo-1106",
29
- },
30
- {
31
- "displayName": "gpt-3.5-turbo-16k",
32
- "name": "gpt-3.5-turbo-16k",
33
- },
34
- {
35
- "displayName": "gpt-4",
36
- "name": "gpt-4",
37
- },
38
- {
39
- "displayName": "gpt-4-32k",
40
- "name": "gpt-4-32k",
41
- },
42
- {
43
- "displayName": "gpt-4-vision-preview",
44
- "name": "gpt-4-vision-preview",
45
- },
46
- {
47
- "displayName": "gpt-4-32k",
48
- "name": "gpt-4-0125-preview",
49
- },
50
- ]
51
- `;
52
-
53
- exports[`modelProviderSelectors > CUSTOM_MODELS > only add the model 1`] = `
54
- [
55
- {
56
- "displayName": "gpt-3.5-turbo",
57
- "name": "gpt-3.5-turbo",
58
- },
59
- {
60
- "displayName": "gpt-3.5-turbo-1106",
61
- "name": "gpt-3.5-turbo-1106",
62
- },
63
- {
64
- "displayName": "gpt-3.5-turbo-16k",
65
- "name": "gpt-3.5-turbo-16k",
66
- },
67
- {
68
- "displayName": "gpt-4",
69
- "name": "gpt-4",
70
- },
71
- {
72
- "displayName": "gpt-4-32k",
73
- "name": "gpt-4-32k",
74
- },
75
- {
76
- "displayName": "gpt-4-0125-preview",
77
- "name": "gpt-4-0125-preview",
78
- },
79
- {
80
- "displayName": "gpt-4-vision-preview",
81
- "name": "gpt-4-vision-preview",
82
- },
83
- {
84
- "displayName": "model1",
85
- "name": "model1",
86
- },
87
- {
88
- "displayName": "model2",
89
- "name": "model2",
90
- },
91
- {
92
- "displayName": "model3",
93
- "name": "model3",
94
- },
95
- {
96
- "displayName": "model4",
97
- "name": "model4",
6
+ "chatModels": [
7
+ {
8
+ "displayName": "llama",
9
+ "functionCall": true,
10
+ "id": "llama",
11
+ "isCustom": true,
12
+ "vision": true,
13
+ },
14
+ {
15
+ "displayName": "claude-2",
16
+ "functionCall": true,
17
+ "id": "claude-2",
18
+ "isCustom": true,
19
+ "vision": true,
20
+ },
21
+ {
22
+ "displayName": "gpt-4-32k",
23
+ "functionCall": true,
24
+ "id": "gpt-4-0125-preview",
25
+ "tokens": 128000,
26
+ },
27
+ ],
28
+ "enabled": true,
29
+ "id": "openai",
98
30
  },
99
31
  ]
100
32
  `;
@@ -81,6 +81,7 @@ exports[`settingsSelectors > defaultAgent > should merge DEFAULT_AGENT and s.set
81
81
  "top_p": 1,
82
82
  },
83
83
  "plugins": [],
84
+ "provider": "openai",
84
85
  "systemRole": "user",
85
86
  "tts": {
86
87
  "showAllLocaleVoice": false,
@@ -112,6 +113,7 @@ exports[`settingsSelectors > defaultAgentConfig > should merge DEFAULT_AGENT_CON
112
113
  "top_p": 1,
113
114
  },
114
115
  "plugins": [],
116
+ "provider": "openai",
115
117
  "systemRole": "user",
116
118
  "tts": {
117
119
  "showAllLocaleVoice": false,
@@ -1,28 +1,30 @@
1
- import { GlobalStore } from '../../../store';
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { DEFAULT_SETTINGS } from '@/const/settings';
4
+ import { agentSelectors } from '@/store/session/slices/agent';
5
+ import { merge } from '@/utils/merge';
6
+
7
+ import { GlobalStore, useGlobalStore } from '../../../store';
8
+ import { initialSettingsState } from '../initialState';
2
9
  import { modelProviderSelectors } from './modelProvider';
3
10
 
4
11
  describe('modelProviderSelectors', () => {
5
12
  describe('CUSTOM_MODELS', () => {
6
13
  it('custom deletion, addition, and renaming of models', () => {
7
- const s = {
14
+ const s = merge(initialSettingsState, {
8
15
  serverConfig: {
9
16
  customModelName:
10
17
  '-all,+llama,+claude-2,-gpt-3.5-turbo,gpt-4-0125-preview=gpt-4-turbo,gpt-4-0125-preview=gpt-4-32k',
11
18
  },
12
- settings: {
13
- languageModel: {
14
- openAI: {},
15
- },
16
- },
17
- } as unknown as GlobalStore;
19
+ }) as unknown as GlobalStore;
18
20
 
19
- const result = modelProviderSelectors.modelList(s);
21
+ const result = modelProviderSelectors.modelSelectList(s).filter((r) => r.enabled);
20
22
 
21
23
  expect(result).toMatchSnapshot();
22
24
  });
23
25
 
24
26
  it('duplicate naming model', () => {
25
- const s = {
27
+ const s = merge(initialSettingsState, {
26
28
  serverConfig: {},
27
29
  settings: {
28
30
  languageModel: {
@@ -31,57 +33,48 @@ describe('modelProviderSelectors', () => {
31
33
  },
32
34
  },
33
35
  },
34
- } as unknown as GlobalStore;
36
+ }) as unknown as GlobalStore;
35
37
 
36
- const result = modelProviderSelectors.modelList(s);
38
+ const result = modelProviderSelectors.modelSelectList(s).filter((r) => r.enabled);
37
39
 
38
- expect(result).toMatchSnapshot();
40
+ expect(result[0].chatModels.find((s) => s.id === 'gpt-4-0125-preview')?.displayName).toEqual(
41
+ 'gpt-4-32k',
42
+ );
39
43
  });
40
44
 
41
45
  it('should delete model', () => {
42
- const s = {
43
- serverConfig: {
44
- customModelName: '-gpt-4',
45
- },
46
+ const s = merge(initialSettingsState, {
47
+ serverConfig: { customModelName: '-gpt-4' },
48
+ }) as unknown as GlobalStore;
49
+
50
+ const result = modelProviderSelectors.modelSelectList(s).filter((r) => r.enabled);
51
+
52
+ expect(result.find((r) => r.id === 'gpt-4')).toBeUndefined();
53
+ });
54
+
55
+ it('show the hidden model', () => {
56
+ const s = merge(initialSettingsState, {
57
+ serverConfig: {},
46
58
  settings: {
47
59
  languageModel: {
48
- openAI: {},
60
+ openAI: {
61
+ customModelName: '+gpt-4-1106-preview',
62
+ },
49
63
  },
50
64
  },
51
- } as unknown as GlobalStore;
65
+ }) as unknown as GlobalStore;
52
66
 
53
- const result = modelProviderSelectors.modelList(s);
67
+ const result = modelProviderSelectors.modelSelectList(s).filter((r) => r.enabled);
54
68
 
55
- expect(result).toEqual([
56
- {
57
- displayName: 'gpt-3.5-turbo',
58
- name: 'gpt-3.5-turbo',
59
- },
60
- {
61
- displayName: 'gpt-3.5-turbo-1106',
62
- name: 'gpt-3.5-turbo-1106',
63
- },
64
- {
65
- displayName: 'gpt-3.5-turbo-16k',
66
- name: 'gpt-3.5-turbo-16k',
67
- },
68
- {
69
- displayName: 'gpt-4-32k',
70
- name: 'gpt-4-32k',
71
- },
72
- {
73
- displayName: 'gpt-4-0125-preview',
74
- name: 'gpt-4-0125-preview',
75
- },
76
- {
77
- displayName: 'gpt-4-vision-preview',
78
- name: 'gpt-4-vision-preview',
79
- },
80
- ]);
69
+ expect(result[0].chatModels.find((o) => o.id === 'gpt-4-1106-preview')).toEqual({
70
+ functionCall: true,
71
+ id: 'gpt-4-1106-preview',
72
+ tokens: 128000,
73
+ });
81
74
  });
82
75
 
83
76
  it('only add the model', () => {
84
- const s = {
77
+ const s = merge(initialSettingsState, {
85
78
  serverConfig: {},
86
79
  settings: {
87
80
  languageModel: {
@@ -90,11 +83,95 @@ describe('modelProviderSelectors', () => {
90
83
  },
91
84
  },
92
85
  },
93
- } as unknown as GlobalStore;
86
+ }) as unknown as GlobalStore;
94
87
 
95
- const result = modelProviderSelectors.modelList(s);
88
+ const result = modelProviderSelectors.modelSelectList(s).filter((r) => r.enabled);
96
89
 
97
- expect(result).toMatchSnapshot();
90
+ expect(result[0].chatModels).toContainEqual({
91
+ displayName: 'model1',
92
+ functionCall: true,
93
+ id: 'model1',
94
+ isCustom: true,
95
+ vision: true,
96
+ });
97
+ expect(result[0].chatModels).toContainEqual({
98
+ displayName: 'model2',
99
+ functionCall: true,
100
+ id: 'model2',
101
+ isCustom: true,
102
+ vision: true,
103
+ });
104
+ expect(result[0].chatModels).toContainEqual({
105
+ displayName: 'model3',
106
+ functionCall: true,
107
+ id: 'model3',
108
+ isCustom: true,
109
+ vision: true,
110
+ });
111
+ expect(result[0].chatModels).toContainEqual({
112
+ displayName: 'model4',
113
+ functionCall: true,
114
+ id: 'model4',
115
+ isCustom: true,
116
+ vision: true,
117
+ });
118
+ });
119
+ });
120
+
121
+ describe('modelEnabledVision', () => {
122
+ it('should return true if the model has vision ability', () => {
123
+ const hasAbility = modelProviderSelectors.modelEnabledVision('gpt-4-vision-preview')(
124
+ useGlobalStore.getState(),
125
+ );
126
+ expect(hasAbility).toBeTruthy();
127
+ });
128
+
129
+ it('should return false if the model does not have vision ability', () => {
130
+ const hasAbility = modelProviderSelectors.modelEnabledVision('some-other-model')(
131
+ useGlobalStore.getState(),
132
+ );
133
+
134
+ expect(hasAbility).toBeFalsy();
135
+ });
136
+
137
+ it('should return false if the model include vision in id', () => {
138
+ const hasAbility = modelProviderSelectors.modelEnabledVision('some-other-model-vision')(
139
+ useGlobalStore.getState(),
140
+ );
141
+
142
+ expect(hasAbility).toBeTruthy();
143
+ });
144
+ });
145
+
146
+ describe('modelEnabledFiles', () => {
147
+ it('should return false if the model does not have file ability', () => {
148
+ const enabledFiles = modelProviderSelectors.modelEnabledFiles('gpt-4-vision-preview')(
149
+ useGlobalStore.getState(),
150
+ );
151
+ expect(enabledFiles).toBeFalsy();
152
+ });
153
+
154
+ it('should return true if the model has file ability', () => {
155
+ const enabledFiles = modelProviderSelectors.modelEnabledFiles('gpt-4-all')(
156
+ useGlobalStore.getState(),
157
+ );
158
+ expect(enabledFiles).toBeTruthy();
159
+ });
160
+ });
161
+
162
+ describe('modelHasMaxToken', () => {
163
+ it('should return true if the model is in the list of models that show tokens', () => {
164
+ const show = modelProviderSelectors.modelHasMaxToken('gpt-3.5-turbo')(
165
+ useGlobalStore.getState(),
166
+ );
167
+ expect(show).toBeTruthy();
168
+ });
169
+
170
+ it('should return false if the model is not in the list of models that show tokens', () => {
171
+ const show = modelProviderSelectors.modelHasMaxToken('some-other-model')(
172
+ useGlobalStore.getState(),
173
+ );
174
+ expect(show).toBe(false);
98
175
  });
99
176
  });
100
177
  });