@lobehub/chat 0.161.22 → 0.161.23

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 (74) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/package.json +1 -1
  3. package/src/app/(main)/chat/settings/features/SubmitAgentButton/SubmitAgentModal.tsx +3 -3
  4. package/src/app/(main)/settings/common/features/Common.tsx +1 -1
  5. package/src/app/(main)/settings/common/features/Theme/ThemeSwatches/ThemeSwatchesNeutral.tsx +5 -5
  6. package/src/app/(main)/settings/common/features/Theme/ThemeSwatches/ThemeSwatchesPrimary.tsx +5 -5
  7. package/src/app/(main)/settings/common/features/Theme/index.tsx +5 -4
  8. package/src/app/(main)/settings/llm/Azure/index.tsx +4 -4
  9. package/src/app/(main)/settings/llm/Bedrock/index.tsx +4 -4
  10. package/src/app/(main)/settings/llm/Ollama/Checker.tsx +1 -1
  11. package/src/app/(main)/settings/llm/components/ProviderConfig/index.tsx +5 -4
  12. package/src/app/(main)/settings/llm/const.ts +2 -1
  13. package/src/const/settings/common.ts +2 -2
  14. package/src/const/settings/index.ts +2 -1
  15. package/src/const/settings/llm.ts +0 -19
  16. package/src/database/client/core/db.ts +19 -0
  17. package/src/database/client/core/schemas.ts +3 -2
  18. package/src/database/client/models/__tests__/user.test.ts +5 -5
  19. package/src/database/client/models/user.ts +2 -3
  20. package/src/database/client/schemas/user.ts +10 -4
  21. package/src/features/AgentSetting/AgentMeta/index.tsx +3 -3
  22. package/src/features/AgentSetting/AgentTTS/index.tsx +2 -2
  23. package/src/features/ChatInput/STT/browser.tsx +2 -2
  24. package/src/features/ChatInput/STT/openai.tsx +2 -2
  25. package/src/features/Conversation/Error/APIKeyForm/Bedrock.tsx +5 -5
  26. package/src/features/Conversation/Error/APIKeyForm/ProviderApiKeyForm.tsx +4 -4
  27. package/src/features/Conversation/Error/AccessCodeForm.tsx +4 -4
  28. package/src/features/Conversation/Extras/TTS/index.tsx +2 -2
  29. package/src/features/Conversation/Plugins/Render/MarkdownType/index.tsx +2 -2
  30. package/src/features/Conversation/components/ChatItem/index.tsx +2 -2
  31. package/src/features/PluginDevModal/LocalForm.tsx +2 -2
  32. package/src/features/PluginStore/index.tsx +6 -2
  33. package/src/features/User/UserPanel/LangButton.tsx +2 -2
  34. package/src/features/User/UserPanel/ThemeButton.tsx +2 -2
  35. package/src/hooks/_header.ts +6 -3
  36. package/src/hooks/useTTS.ts +2 -2
  37. package/src/layout/GlobalProvider/AppTheme.tsx +4 -4
  38. package/src/libs/agent-runtime/types/type.ts +3 -2
  39. package/src/migrations/FromV6ToV7/fixtures/output-v7-from-v1.json +203 -0
  40. package/src/migrations/FromV6ToV7/fixtures/provider-input-v6.json +103 -0
  41. package/src/migrations/FromV6ToV7/fixtures/provider-output-v7.json +118 -0
  42. package/src/migrations/FromV6ToV7/index.ts +101 -0
  43. package/src/migrations/FromV6ToV7/migrations.test.ts +64 -0
  44. package/src/migrations/FromV6ToV7/types/v6.ts +61 -0
  45. package/src/migrations/FromV6ToV7/types/v7.ts +71 -0
  46. package/src/migrations/index.ts +9 -3
  47. package/src/services/__tests__/chat.test.ts +19 -19
  48. package/src/services/__tests__/share.test.ts +2 -2
  49. package/src/services/_auth.test.ts +10 -5
  50. package/src/services/_auth.ts +11 -7
  51. package/src/services/_header.ts +6 -3
  52. package/src/services/ollama.ts +3 -3
  53. package/src/services/user/client.test.ts +1 -1
  54. package/src/store/user/helpers.ts +3 -2
  55. package/src/store/user/selectors.ts +10 -2
  56. package/src/store/user/slices/common/action.test.ts +3 -3
  57. package/src/store/user/slices/common/action.ts +2 -2
  58. package/src/store/user/slices/modelList/action.test.ts +2 -2
  59. package/src/store/user/slices/modelList/action.ts +15 -3
  60. package/src/store/user/slices/modelList/selectors/index.ts +1 -0
  61. package/src/store/user/slices/modelList/selectors/keyVaults.ts +25 -0
  62. package/src/store/user/slices/modelList/selectors/modelConfig.test.ts +1 -1
  63. package/src/store/user/slices/modelList/selectors/modelConfig.ts +0 -5
  64. package/src/store/user/slices/settings/action.test.ts +14 -12
  65. package/src/store/user/slices/settings/action.ts +12 -6
  66. package/src/store/user/slices/settings/selectors/general.test.ts +45 -0
  67. package/src/store/user/slices/settings/selectors/general.ts +40 -0
  68. package/src/store/user/slices/settings/selectors/index.ts +1 -0
  69. package/src/store/user/slices/settings/selectors/settings.test.ts +0 -39
  70. package/src/store/user/slices/settings/selectors/settings.ts +6 -32
  71. package/src/types/user/settings/general.ts +1 -1
  72. package/src/types/user/settings/index.ts +7 -29
  73. package/src/types/user/settings/keyVaults.ts +36 -0
  74. package/src/types/user/settings/modelProvider.ts +4 -40
@@ -1,15 +1,17 @@
1
1
  import { JWTPayload, LOBE_CHAT_AUTH_HEADER } from '@/const/auth';
2
2
  import { ModelProvider } from '@/libs/agent-runtime';
3
3
  import { useUserStore } from '@/store/user';
4
- import { modelConfigSelectors, settingsSelectors } from '@/store/user/selectors';
4
+ import { keyVaultsConfigSelectors, settingsSelectors } from '@/store/user/selectors';
5
+ import { GlobalLLMProviderKey } from '@/types/user/settings';
5
6
  import { createJWT } from '@/utils/jwt';
6
7
 
7
8
  export const getProviderAuthPayload = (provider: string) => {
8
9
  switch (provider) {
9
10
  case ModelProvider.Bedrock: {
10
- const { accessKeyId, region, secretAccessKey } = modelConfigSelectors.bedrockConfig(
11
+ const { accessKeyId, region, secretAccessKey } = keyVaultsConfigSelectors.bedrockConfig(
11
12
  useUserStore.getState(),
12
13
  );
14
+
13
15
  const awsSecretAccessKey = secretAccessKey;
14
16
  const awsAccessKeyId = accessKeyId;
15
17
 
@@ -19,7 +21,7 @@ export const getProviderAuthPayload = (provider: string) => {
19
21
  }
20
22
 
21
23
  case ModelProvider.Azure: {
22
- const azure = modelConfigSelectors.azureConfig(useUserStore.getState());
24
+ const azure = keyVaultsConfigSelectors.azureConfig(useUserStore.getState());
23
25
 
24
26
  return {
25
27
  apiKey: azure.apiKey,
@@ -29,15 +31,17 @@ export const getProviderAuthPayload = (provider: string) => {
29
31
  }
30
32
 
31
33
  case ModelProvider.Ollama: {
32
- const config = modelConfigSelectors.ollamaConfig(useUserStore.getState());
34
+ const config = keyVaultsConfigSelectors.ollamaConfig(useUserStore.getState());
33
35
 
34
- return { endpoint: config?.endpoint };
36
+ return { endpoint: config?.baseURL };
35
37
  }
36
38
 
37
39
  default: {
38
- const config = settingsSelectors.providerConfig(provider)(useUserStore.getState());
40
+ const config = keyVaultsConfigSelectors.getVaultByProvider(provider as GlobalLLMProviderKey)(
41
+ useUserStore.getState(),
42
+ );
39
43
 
40
- return { apiKey: config?.apiKey, endpoint: config?.endpoint };
44
+ return { apiKey: config?.apiKey, endpoint: config?.baseURL };
41
45
  }
42
46
  }
43
47
  };
@@ -1,6 +1,9 @@
1
1
  import { LOBE_CHAT_ACCESS_CODE, OPENAI_API_KEY_HEADER_KEY, OPENAI_END_POINT } from '@/const/fetch';
2
2
  import { useUserStore } from '@/store/user';
3
- import { modelConfigSelectors, settingsSelectors } from '@/store/user/selectors';
3
+ import {
4
+ keyVaultsConfigSelectors,
5
+ settingsSelectors,
6
+ } from '@/store/user/selectors';
4
7
 
5
8
  /**
6
9
  * TODO: Need to be removed after tts refactor
@@ -8,13 +11,13 @@ import { modelConfigSelectors, settingsSelectors } from '@/store/user/selectors'
8
11
  */
9
12
  // eslint-disable-next-line no-undef
10
13
  export const createHeaderWithOpenAI = (header?: HeadersInit): HeadersInit => {
11
- const openAIConfig = modelConfigSelectors.openAIConfig(useUserStore.getState());
14
+ const openAIConfig = keyVaultsConfigSelectors.openAIConfig(useUserStore.getState());
12
15
 
13
16
  // eslint-disable-next-line no-undef
14
17
  return {
15
18
  ...header,
16
19
  [LOBE_CHAT_ACCESS_CODE]: settingsSelectors.password(useUserStore.getState()),
17
20
  [OPENAI_API_KEY_HEADER_KEY]: openAIConfig.apiKey || '',
18
- [OPENAI_END_POINT]: openAIConfig.endpoint || '',
21
+ [OPENAI_END_POINT]: openAIConfig.baseURL || '',
19
22
  };
20
23
  };
@@ -3,7 +3,7 @@ import { ListResponse, Ollama as OllamaBrowser, ProgressResponse } from 'ollama/
3
3
  import { createErrorResponse } from '@/app/api/errorResponse';
4
4
  import { ModelProvider } from '@/libs/agent-runtime';
5
5
  import { useUserStore } from '@/store/user';
6
- import { modelConfigSelectors } from '@/store/user/selectors';
6
+ import { keyVaultsConfigSelectors } from '@/store/user/selectors';
7
7
  import { ChatErrorType } from '@/types/fetch';
8
8
  import { getMessageError } from '@/utils/fetch';
9
9
 
@@ -25,9 +25,9 @@ export class OllamaService {
25
25
  }
26
26
 
27
27
  getHost = (): string => {
28
- const config = modelConfigSelectors.ollamaConfig(useUserStore.getState());
28
+ const config = keyVaultsConfigSelectors.ollamaConfig(useUserStore.getState());
29
29
 
30
- return config.endpoint || DEFAULT_BASE_URL;
30
+ return config.baseURL || DEFAULT_BASE_URL;
31
31
  };
32
32
 
33
33
  getOllamaClient = () => {
@@ -58,7 +58,7 @@ describe('ClientService', () => {
58
58
  });
59
59
 
60
60
  it('should update user settings correctly', async () => {
61
- const settingsPatch: DeepPartial<UserSettings> = { themeMode: 'dark' };
61
+ const settingsPatch: DeepPartial<UserSettings> = { general: { themeMode: 'dark' } };
62
62
  (UserModel.updateSettings as Mock).mockResolvedValue(undefined);
63
63
 
64
64
  await clientService.updateUserSettings(settingsPatch);
@@ -1,7 +1,8 @@
1
- import { settingsSelectors } from './slices/settings/selectors';
1
+ import { userGeneralSettingsSelectors } from './slices/settings/selectors';
2
2
  import { useUserStore } from './store';
3
3
 
4
- const getCurrentLanguage = () => settingsSelectors.currentLanguage(useUserStore.getState());
4
+ const getCurrentLanguage = () =>
5
+ userGeneralSettingsSelectors.currentLanguage(useUserStore.getState());
5
6
 
6
7
  export const globalHelpers = {
7
8
  getCurrentLanguage,
@@ -1,5 +1,13 @@
1
1
  export { authSelectors, userProfileSelectors } from './slices/auth/selectors';
2
- export { modelConfigSelectors, modelProviderSelectors } from './slices/modelList/selectors';
2
+ export {
3
+ keyVaultsConfigSelectors,
4
+ modelConfigSelectors,
5
+ modelProviderSelectors,
6
+ } from './slices/modelList/selectors';
3
7
  export { preferenceSelectors } from './slices/preference/selectors';
4
- export { settingsSelectors, systemAgentSelectors } from './slices/settings/selectors';
8
+ export {
9
+ settingsSelectors,
10
+ systemAgentSelectors,
11
+ userGeneralSettingsSelectors,
12
+ } from './slices/settings/selectors';
5
13
  export { syncSettingsSelectors } from './slices/sync/selectors';
@@ -84,7 +84,7 @@ describe('createCommonSlice', () => {
84
84
  telemetry: true,
85
85
  },
86
86
  settings: {
87
- language: 'en-US',
87
+ general: { language: 'en-US' },
88
88
  },
89
89
  };
90
90
 
@@ -166,7 +166,7 @@ describe('createCommonSlice', () => {
166
166
  isOnboard: true,
167
167
  preference: savedPreference,
168
168
  settings: {
169
- language: 'en-US',
169
+ general: { language: 'en-US' },
170
170
  },
171
171
  };
172
172
  vi.spyOn(userService, 'getUserState').mockResolvedValueOnce(mockUserState);
@@ -216,7 +216,7 @@ describe('createCommonSlice', () => {
216
216
  isOnboard: true,
217
217
  preference: {} as any,
218
218
  settings: {
219
- language: 'en-US',
219
+ general: { language: 'en-US' },
220
220
  },
221
221
  };
222
222
 
@@ -14,7 +14,7 @@ import { merge } from '@/utils/merge';
14
14
  import { setNamespace } from '@/utils/storeDebug';
15
15
 
16
16
  import { preferenceSelectors } from '../preference/selectors';
17
- import { settingsSelectors } from '../settings/selectors';
17
+ import { userGeneralSettingsSelectors } from '../settings/selectors';
18
18
 
19
19
  const n = setNamespace('common');
20
20
 
@@ -115,7 +115,7 @@ export const createCommonSlice: StateCreator<
115
115
  get().refreshDefaultModelProviderList({ trigger: 'fetchUserState' });
116
116
 
117
117
  // auto switch language
118
- const { language } = settingsSelectors.currentSettings(get());
118
+ const language = userGeneralSettingsSelectors.config(get()).language;
119
119
  if (language === 'auto') {
120
120
  switchLang('auto');
121
121
  }
@@ -4,7 +4,7 @@ import { describe, expect, it, vi } from 'vitest';
4
4
  import { modelsService } from '@/services/models';
5
5
  import { userService } from '@/services/user';
6
6
  import { useUserStore } from '@/store/user';
7
- import { OpenAICompatibleProviderConfig } from '@/types/user/settings';
7
+ import { ProviderConfig } from '@/types/user/settings';
8
8
 
9
9
  import { settingsSelectors } from '../settings/selectors';
10
10
  import { CustomModelCardDispatch } from './reducers/customModelCard';
@@ -24,7 +24,7 @@ describe('LLMSettingsSliceAction', () => {
24
24
  describe('setModelProviderConfig', () => {
25
25
  it('should set OpenAI configuration', async () => {
26
26
  const { result } = renderHook(() => useUserStore());
27
- const openAIConfig: Partial<OpenAICompatibleProviderConfig> = { apiKey: 'test-key' };
27
+ const openAIConfig: Partial<ProviderConfig> = { fetchOnClient: true };
28
28
 
29
29
  // Perform the action
30
30
  await act(async () => {
@@ -6,7 +6,11 @@ import { DEFAULT_MODEL_PROVIDER_LIST } from '@/config/modelProviders';
6
6
  import { ModelProvider } from '@/libs/agent-runtime';
7
7
  import { UserStore } from '@/store/user';
8
8
  import { ChatModelCard } from '@/types/llm';
9
- import { UserModelProviderConfig, GlobalLLMProviderKey } from '@/types/user/settings';
9
+ import {
10
+ GlobalLLMProviderKey,
11
+ UserKeyVaults,
12
+ UserModelProviderConfig,
13
+ } from '@/types/user/settings';
10
14
 
11
15
  import { settingsSelectors } from '../settings/selectors';
12
16
  import { CustomModelCardDispatch, customModelCardsReducer } from './reducers/customModelCard';
@@ -30,7 +34,6 @@ export interface ModelListAction {
30
34
  provider: T,
31
35
  config: Partial<UserModelProviderConfig[T]>,
32
36
  ) => Promise<void>;
33
-
34
37
  toggleEditingCustomModelCard: (params?: { id: string; provider: GlobalLLMProviderKey }) => void;
35
38
 
36
39
  toggleProviderEnabled: (provider: GlobalLLMProviderKey, enabled: boolean) => Promise<void>;
@@ -41,6 +44,11 @@ export interface ModelListAction {
41
44
  options: { label?: string; value?: string }[],
42
45
  ) => Promise<void>;
43
46
 
47
+ updateKeyVaultConfig: <T extends GlobalLLMProviderKey>(
48
+ provider: T,
49
+ config: Partial<UserKeyVaults[T]>,
50
+ ) => Promise<void>;
51
+
44
52
  useFetchProviderModelList: (
45
53
  provider: GlobalLLMProviderKey,
46
54
  enabledAutoFetch: boolean,
@@ -137,10 +145,10 @@ export const createModelListSlice: StateCreator<
137
145
  toggleEditingCustomModelCard: (params) => {
138
146
  set({ editingCustomCardModel: params }, false, 'toggleEditingCustomModelCard');
139
147
  },
148
+
140
149
  toggleProviderEnabled: async (provider, enabled) => {
141
150
  await get().setSettings({ languageModel: { [provider]: { enabled } } });
142
151
  },
143
-
144
152
  updateEnabledModels: async (provider, value, options) => {
145
153
  const { dispatchCustomModelCards, setModelProviderConfig } = get();
146
154
  const enabledModels = modelProviderSelectors.getEnableModelsById(provider)(get());
@@ -171,6 +179,10 @@ export const createModelListSlice: StateCreator<
171
179
  await setModelProviderConfig(provider, { enabledModels: value.filter(Boolean) });
172
180
  },
173
181
 
182
+ updateKeyVaultConfig: async (provider, config) => {
183
+ await get().setSettings({ keyVaults: { [provider]: config } });
184
+ },
185
+
174
186
  useFetchProviderModelList: (provider, enabledAutoFetch) =>
175
187
  useSWR<ChatModelCard[] | undefined>(
176
188
  [provider, enabledAutoFetch],
@@ -1,2 +1,3 @@
1
+ export { keyVaultsConfigSelectors } from './keyVaults';
1
2
  export { modelConfigSelectors } from './modelConfig';
2
3
  export { modelProviderSelectors } from './modelProvider';
@@ -0,0 +1,25 @@
1
+ import { UserStore } from '@/store/user';
2
+ import { GlobalLLMProviderKey, OpenAICompatibleKeyVault } from '@/types/user/settings';
3
+
4
+ import { currentSettings } from '../../settings/selectors/settings';
5
+
6
+ export const keyVaultsSettings = (s: UserStore) => currentSettings(s).keyVaults;
7
+
8
+ const openAIConfig = (s: UserStore) => keyVaultsSettings(s).openai || {};
9
+ const bedrockConfig = (s: UserStore) => keyVaultsSettings(s).bedrock || {};
10
+ const ollamaConfig = (s: UserStore) => keyVaultsSettings(s).ollama || {};
11
+ const azureConfig = (s: UserStore) => keyVaultsSettings(s).azure || {};
12
+ const getVaultByProvider = (provider: GlobalLLMProviderKey) => (s: UserStore) =>
13
+ (keyVaultsSettings(s)[provider] || {}) as OpenAICompatibleKeyVault;
14
+
15
+ const isProviderEndpointNotEmpty = (provider: string) => (s: UserStore) =>
16
+ !!getVaultByProvider(provider as GlobalLLMProviderKey)(s)?.baseURL;
17
+
18
+ export const keyVaultsConfigSelectors = {
19
+ azureConfig,
20
+ bedrockConfig,
21
+ getVaultByProvider,
22
+ isProviderEndpointNotEmpty,
23
+ ollamaConfig,
24
+ openAIConfig,
25
+ };
@@ -38,7 +38,7 @@ describe('modelConfigSelectors', () => {
38
38
  it('client fetch should disabled on default', () => {
39
39
  const s = merge(initialSettingsState, {
40
40
  settings: {
41
- languageModel: {
41
+ keyVaults: {
42
42
  azure: {
43
43
  endpoint: 'endpoint',
44
44
  apiKey: 'apikey',
@@ -6,9 +6,6 @@ import { currentLLMSettings, getProviderConfigById } from '../../settings/select
6
6
  const isProviderEnabled = (provider: GlobalLLMProviderKey) => (s: UserStore) =>
7
7
  getProviderConfigById(provider)(s)?.enabled || false;
8
8
 
9
- const isProviderEndpointNotEmpty = (provider: GlobalLLMProviderKey | string) => (s: UserStore) =>
10
- !!getProviderConfigById(provider)(s)?.endpoint;
11
-
12
9
  const isProviderFetchOnClient = (provider: GlobalLLMProviderKey | string) => (s: UserStore) => {
13
10
  const config = getProviderConfigById(provider)(s);
14
11
  if (typeof config?.fetchOnClient !== 'undefined') return config?.fetchOnClient;
@@ -56,10 +53,8 @@ export const modelConfigSelectors = {
56
53
  isAutoFetchModelsEnabled,
57
54
  isAzureEnabled,
58
55
  isProviderEnabled,
59
- isProviderEndpointNotEmpty,
60
56
  isProviderFetchOnClient,
61
57
 
62
58
  ollamaConfig,
63
-
64
59
  openAIConfig,
65
60
  };
@@ -8,6 +8,9 @@ import { userService } from '@/services/user';
8
8
  import { useUserStore } from '@/store/user';
9
9
  import { LobeAgentSettings } from '@/types/session';
10
10
  import { UserSettings } from '@/types/user/settings';
11
+ import { merge } from '@/utils/merge';
12
+
13
+ vi.mock('zustand/traditional');
11
14
 
12
15
  // Mock userService
13
16
  vi.mock('@/services/user', () => ({
@@ -21,10 +24,9 @@ describe('SettingsAction', () => {
21
24
  describe('importAppSettings', () => {
22
25
  it('should import app settings', async () => {
23
26
  const { result } = renderHook(() => useUserStore());
24
- const newSettings: UserSettings = {
25
- ...DEFAULT_SETTINGS,
26
- themeMode: 'dark',
27
- };
27
+ const newSettings: UserSettings = merge(DEFAULT_SETTINGS, {
28
+ general: { themeMode: 'dark' },
29
+ });
28
30
 
29
31
  // Mock the internal setSettings function call
30
32
  const setSettingsSpy = vi.spyOn(result.current, 'setSettings');
@@ -35,14 +37,12 @@ describe('SettingsAction', () => {
35
37
  });
36
38
 
37
39
  // Assert that setSettings was called with the correct settings
38
- expect(setSettingsSpy).toHaveBeenCalledWith({
39
- ...DEFAULT_SETTINGS,
40
- password: undefined,
41
- themeMode: 'dark',
42
- });
40
+ expect(setSettingsSpy).toHaveBeenCalledWith(newSettings);
43
41
 
44
42
  // Assert that the state has been updated
45
- expect(userService.updateUserSettings).toHaveBeenCalledWith({ themeMode: 'dark' });
43
+ expect(userService.updateUserSettings).toHaveBeenCalledWith({
44
+ general: { themeMode: 'dark' },
45
+ });
46
46
 
47
47
  // Restore the spy
48
48
  setSettingsSpy.mockRestore();
@@ -69,7 +69,7 @@ describe('SettingsAction', () => {
69
69
  describe('setSettings', () => {
70
70
  it('should set partial settings', async () => {
71
71
  const { result } = renderHook(() => useUserStore());
72
- const partialSettings: Partial<UserSettings> = { themeMode: 'dark' };
72
+ const partialSettings: DeepPartial<UserSettings> = { general: { themeMode: 'dark' } };
73
73
 
74
74
  // Perform the action
75
75
  await act(async () => {
@@ -92,7 +92,9 @@ describe('SettingsAction', () => {
92
92
  });
93
93
 
94
94
  // Assert that updateUserSettings was called with the correct theme mode
95
- expect(userService.updateUserSettings).toHaveBeenCalledWith({ themeMode });
95
+ expect(userService.updateUserSettings).toHaveBeenCalledWith({
96
+ general: { themeMode },
97
+ });
96
98
  });
97
99
  });
98
100
 
@@ -7,7 +7,7 @@ import { userService } from '@/services/user';
7
7
  import type { UserStore } from '@/store/user';
8
8
  import { LocaleMode } from '@/types/locale';
9
9
  import { LobeAgentSettings } from '@/types/session';
10
- import { UserSettings } from '@/types/user/settings';
10
+ import { UserGeneralConfig, UserKeyVaults, UserSettings } from '@/types/user/settings';
11
11
  import { switchLang } from '@/utils/client/switchLang';
12
12
  import { difference } from '@/utils/difference';
13
13
  import { merge } from '@/utils/merge';
@@ -20,6 +20,8 @@ export interface UserSettingsAction {
20
20
  switchLocale: (locale: LocaleMode) => Promise<void>;
21
21
  switchThemeMode: (themeMode: ThemeMode) => Promise<void>;
22
22
  updateDefaultAgent: (agent: DeepPartial<LobeAgentSettings>) => Promise<void>;
23
+ updateGeneralConfig: (settings: Partial<UserGeneralConfig>) => Promise<void>;
24
+ updateKeyVaults: (settings: Partial<UserKeyVaults>) => Promise<void>;
23
25
  }
24
26
 
25
27
  export const createSettingsSlice: StateCreator<
@@ -30,10 +32,8 @@ export const createSettingsSlice: StateCreator<
30
32
  > = (set, get) => ({
31
33
  importAppSettings: async (importAppSettings) => {
32
34
  const { setSettings } = get();
33
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
34
- const { password: _, ...settings } = importAppSettings;
35
35
 
36
- await setSettings(settings);
36
+ await setSettings(importAppSettings);
37
37
  },
38
38
  resetSettings: async () => {
39
39
  await userService.resetUserSettings();
@@ -62,14 +62,20 @@ export const createSettingsSlice: StateCreator<
62
62
  });
63
63
  },
64
64
  switchLocale: async (locale) => {
65
- await get().setSettings({ language: locale });
65
+ await get().updateGeneralConfig({ language: locale });
66
66
 
67
67
  switchLang(locale);
68
68
  },
69
69
  switchThemeMode: async (themeMode) => {
70
- await get().setSettings({ themeMode });
70
+ await get().updateGeneralConfig({ themeMode });
71
71
  },
72
72
  updateDefaultAgent: async (defaultAgent) => {
73
73
  await get().setSettings({ defaultAgent });
74
74
  },
75
+ updateGeneralConfig: async (general) => {
76
+ await get().setSettings({ general });
77
+ },
78
+ updateKeyVaults: async (keyVaults) => {
79
+ await get().setSettings({ keyVaults });
80
+ },
75
81
  });
@@ -0,0 +1,45 @@
1
+ import { UserStore } from '@/store/user';
2
+ import { UserState, initialState } from '@/store/user/initialState';
3
+ import { merge } from '@/utils/merge';
4
+
5
+ import { userGeneralSettingsSelectors } from './general';
6
+
7
+ describe('settingsSelectors', () => {
8
+ describe('currentLanguage', () => {
9
+ it('should return the correct language setting', () => {
10
+ const s: UserState = merge(initialState, {
11
+ settings: {
12
+ general: { language: 'fr' },
13
+ },
14
+ });
15
+
16
+ const result = userGeneralSettingsSelectors.currentLanguage(s as UserStore);
17
+
18
+ expect(result).toBe('fr');
19
+ });
20
+ });
21
+
22
+ describe('currentThemeMode', () => {
23
+ it('should return the correct theme', () => {
24
+ const s: UserState = merge(initialState, {
25
+ settings: {
26
+ general: { themeMode: 'light' },
27
+ },
28
+ });
29
+
30
+ const result = userGeneralSettingsSelectors.currentThemeMode(s as UserStore);
31
+
32
+ expect(result).toBe('light');
33
+ });
34
+ it('should return the auto if not set the themeMode', () => {
35
+ const s: UserState = merge(initialState, {
36
+ settings: {
37
+ general: { themeMode: undefined },
38
+ },
39
+ });
40
+ const result = userGeneralSettingsSelectors.currentThemeMode(s as UserStore);
41
+
42
+ expect(result).toBe('auto');
43
+ });
44
+ });
45
+ });
@@ -0,0 +1,40 @@
1
+ import { DEFAULT_LANG } from '@/const/locale';
2
+ import { Locales } from '@/locales/resources';
3
+ import { isOnServerSide } from '@/utils/env';
4
+
5
+ import { UserStore } from '../../../store';
6
+ import { currentSettings } from './settings';
7
+
8
+ const generalConfig = (s: UserStore) => currentSettings(s).general || {};
9
+
10
+ const currentLanguage = (s: UserStore) => {
11
+ const locale = generalConfig(s).language;
12
+
13
+ if (locale === 'auto') {
14
+ if (isOnServerSide) return DEFAULT_LANG;
15
+
16
+ return navigator.language as Locales;
17
+ }
18
+
19
+ return locale;
20
+ };
21
+
22
+ const currentThemeMode = (s: UserStore) => {
23
+ const themeMode = generalConfig(s).themeMode;
24
+ return themeMode || 'auto';
25
+ };
26
+
27
+ const neutralColor = (s: UserStore) => generalConfig(s).neutralColor;
28
+ const primaryColor = (s: UserStore) => generalConfig(s).primaryColor;
29
+ const fontSize = (s: UserStore) => generalConfig(s).fontSize;
30
+ const language = (s: UserStore) => generalConfig(s).language;
31
+
32
+ export const userGeneralSettingsSelectors = {
33
+ config: generalConfig,
34
+ currentLanguage,
35
+ currentThemeMode,
36
+ fontSize,
37
+ language,
38
+ neutralColor,
39
+ primaryColor,
40
+ };
@@ -1,2 +1,3 @@
1
+ export { userGeneralSettingsSelectors } from './general';
1
2
  export { settingsSelectors } from './settings';
2
3
  export { systemAgentSelectors } from './systemAgent';
@@ -117,45 +117,6 @@ describe('settingsSelectors', () => {
117
117
  });
118
118
  });
119
119
 
120
- describe('currentLanguage', () => {
121
- it('should return the correct language setting', () => {
122
- const s = {
123
- settings: {
124
- language: 'fr',
125
- },
126
- } as unknown as UserStore;
127
-
128
- const result = settingsSelectors.currentLanguage(s);
129
-
130
- expect(result).toBe('fr');
131
- });
132
- });
133
-
134
- describe('currentThemeMode', () => {
135
- it('should return the correct theme', () => {
136
- const s = {
137
- settings: {
138
- themeMode: 'light',
139
- },
140
- } as unknown as UserStore;
141
-
142
- const result = settingsSelectors.currentThemeMode(s);
143
-
144
- expect(result).toBe('light');
145
- });
146
- it('should return the auto if not set the themeMode', () => {
147
- const s = {
148
- settings: {
149
- themeMode: undefined,
150
- },
151
- } as unknown as UserStore;
152
-
153
- const result = settingsSelectors.currentThemeMode(s);
154
-
155
- expect(result).toBe('auto');
156
- });
157
- });
158
-
159
120
  describe('dalleConfig', () => {
160
121
  it('should return the dalle configuration', () => {
161
122
  const s = {
@@ -1,4 +1,3 @@
1
- import { DEFAULT_LANG } from '@/const/locale';
2
1
  import { DEFAULT_AGENT_META } from '@/const/meta';
3
2
  import {
4
3
  DEFAULT_AGENT,
@@ -6,13 +5,11 @@ import {
6
5
  DEFAULT_SYSTEM_AGENT_CONFIG,
7
6
  DEFAULT_TTS_CONFIG,
8
7
  } from '@/const/settings';
9
- import { Locales } from '@/locales/resources';
10
8
  import {
11
- OpenAICompatibleProviderConfig,
12
9
  GlobalLLMProviderKey,
10
+ ProviderConfig,
13
11
  UserSettings,
14
12
  } from '@/types/user/settings';
15
- import { isOnServerSide } from '@/utils/env';
16
13
  import { merge } from '@/utils/merge';
17
14
 
18
15
  import { UserStore } from '../../../store';
@@ -22,9 +19,11 @@ export const currentSettings = (s: UserStore): UserSettings => merge(s.defaultSe
22
19
  export const currentLLMSettings = (s: UserStore) => currentSettings(s).languageModel;
23
20
 
24
21
  export const getProviderConfigById = (provider: string) => (s: UserStore) =>
25
- currentLLMSettings(s)[provider as GlobalLLMProviderKey] as OpenAICompatibleProviderConfig | undefined;
22
+ currentLLMSettings(s)[provider as GlobalLLMProviderKey] as
23
+ | ProviderConfig
24
+ | undefined;
26
25
 
27
- const password = (s: UserStore) => currentSettings(s).password;
26
+ const password = (s: UserStore) => currentSettings(s).keyVaults.password || '';
28
27
 
29
28
  const currentTTS = (s: UserStore) => merge(DEFAULT_TTS_CONFIG, currentSettings(s).tts);
30
29
 
@@ -33,30 +32,7 @@ const defaultAgentConfig = (s: UserStore) => merge(DEFAULT_AGENT_CONFIG, default
33
32
 
34
33
  const defaultAgentMeta = (s: UserStore) => merge(DEFAULT_AGENT_META, defaultAgent(s).meta);
35
34
 
36
- // TODO: Maybe we can also export settings difference
37
- const exportSettings = (s: UserStore) => {
38
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
39
- const { password: _, ...settings } = currentSettings(s);
40
-
41
- return settings as UserSettings;
42
- };
43
-
44
- const currentLanguage = (s: UserStore) => {
45
- const locale = currentSettings(s).language;
46
-
47
- if (locale === 'auto') {
48
- if (isOnServerSide) return DEFAULT_LANG;
49
-
50
- return navigator.language as Locales;
51
- }
52
-
53
- return locale;
54
- };
55
-
56
- export const currentThemeMode = (s: UserStore) => {
57
- const themeMode = currentSettings(s).themeMode;
58
- return themeMode || 'auto';
59
- };
35
+ const exportSettings = currentSettings;
60
36
 
61
37
  const dalleConfig = (s: UserStore) => currentSettings(s).tool?.dalle || {};
62
38
  const isDalleAutoGenerating = (s: UserStore) => currentSettings(s).tool?.dalle?.autoGenerate;
@@ -65,11 +41,9 @@ const currentSystemAgent = (s: UserStore) =>
65
41
  merge(DEFAULT_SYSTEM_AGENT_CONFIG, currentSettings(s).systemAgent);
66
42
 
67
43
  export const settingsSelectors = {
68
- currentLanguage,
69
44
  currentSettings,
70
45
  currentSystemAgent,
71
46
  currentTTS,
72
- currentThemeMode,
73
47
  dalleConfig,
74
48
  defaultAgent,
75
49
  defaultAgentConfig,
@@ -3,7 +3,7 @@ import type { ThemeMode } from 'antd-style';
3
3
 
4
4
  import { LocaleMode } from '@/types/locale';
5
5
 
6
- export interface UserGeneralSettings {
6
+ export interface UserGeneralConfig {
7
7
  fontSize: number;
8
8
  language: LocaleMode;
9
9
  neutralColor?: NeutralColors;