@lobehub/chat 0.161.21 → 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 (111) hide show
  1. package/CHANGELOG.md +58 -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 +5 -5
  10. package/src/app/(main)/settings/llm/Ollama/Checker.tsx +1 -1
  11. package/src/app/(main)/settings/llm/components/Checker.tsx +10 -6
  12. package/src/app/(main)/settings/llm/components/ProviderConfig/index.tsx +6 -5
  13. package/src/app/(main)/settings/llm/components/ProviderModelList/CustomModelOption.tsx +1 -1
  14. package/src/app/(main)/settings/llm/components/ProviderModelList/ModelFetcher.tsx +1 -1
  15. package/src/app/(main)/settings/llm/components/ProviderModelList/Option.tsx +1 -1
  16. package/src/app/(main)/settings/llm/components/ProviderModelList/index.tsx +1 -1
  17. package/src/app/(main)/settings/llm/const.ts +2 -1
  18. package/src/const/settings/agent.ts +2 -2
  19. package/src/const/settings/common.ts +2 -2
  20. package/src/const/settings/index.ts +4 -3
  21. package/src/const/settings/llm.ts +2 -21
  22. package/src/const/settings/sync.ts +2 -2
  23. package/src/const/settings/systemAgent.ts +2 -2
  24. package/src/const/settings/tts.ts +2 -2
  25. package/src/database/client/core/db.ts +19 -0
  26. package/src/database/client/core/migrations/migrateSettingsToUser/type.ts +1 -1
  27. package/src/database/client/core/schemas.ts +3 -2
  28. package/src/database/client/models/__tests__/user.test.ts +5 -5
  29. package/src/database/client/models/user.ts +2 -3
  30. package/src/database/client/schemas/user.ts +10 -4
  31. package/src/features/AgentSetting/AgentMeta/index.tsx +3 -3
  32. package/src/features/AgentSetting/AgentTTS/index.tsx +2 -2
  33. package/src/features/ChatInput/STT/browser.tsx +2 -2
  34. package/src/features/ChatInput/STT/openai.tsx +2 -2
  35. package/src/features/Conversation/Error/APIKeyForm/Bedrock.tsx +5 -5
  36. package/src/features/Conversation/Error/APIKeyForm/ProviderApiKeyForm.tsx +5 -5
  37. package/src/features/Conversation/Error/APIKeyForm/index.tsx +1 -1
  38. package/src/features/Conversation/Error/AccessCodeForm.tsx +4 -4
  39. package/src/features/Conversation/Extras/TTS/index.tsx +2 -2
  40. package/src/features/Conversation/Plugins/Render/MarkdownType/index.tsx +2 -2
  41. package/src/features/Conversation/components/BubblesLoading.tsx +49 -41
  42. package/src/features/Conversation/components/ChatItem/index.tsx +2 -2
  43. package/src/features/PluginDevModal/LocalForm.tsx +2 -2
  44. package/src/features/PluginStore/index.tsx +6 -2
  45. package/src/features/User/UserPanel/LangButton.tsx +2 -2
  46. package/src/features/User/UserPanel/ThemeButton.tsx +2 -2
  47. package/src/hooks/_header.ts +6 -3
  48. package/src/hooks/useTTS.ts +2 -2
  49. package/src/layout/GlobalProvider/AppTheme.tsx +4 -4
  50. package/src/libs/agent-runtime/types/type.ts +3 -2
  51. package/src/migrations/FromV6ToV7/fixtures/output-v7-from-v1.json +203 -0
  52. package/src/migrations/FromV6ToV7/fixtures/provider-input-v6.json +103 -0
  53. package/src/migrations/FromV6ToV7/fixtures/provider-output-v7.json +118 -0
  54. package/src/migrations/FromV6ToV7/index.ts +101 -0
  55. package/src/migrations/FromV6ToV7/migrations.test.ts +64 -0
  56. package/src/migrations/FromV6ToV7/types/v6.ts +61 -0
  57. package/src/migrations/FromV6ToV7/types/v7.ts +71 -0
  58. package/src/migrations/index.ts +9 -3
  59. package/src/services/__tests__/chat.test.ts +19 -19
  60. package/src/services/__tests__/share.test.ts +4 -4
  61. package/src/services/_auth.test.ts +10 -5
  62. package/src/services/_auth.ts +11 -7
  63. package/src/services/_header.ts +6 -3
  64. package/src/services/config.ts +2 -2
  65. package/src/services/ollama.ts +3 -3
  66. package/src/services/share.ts +3 -3
  67. package/src/services/user/client.test.ts +3 -3
  68. package/src/services/user/client.ts +3 -3
  69. package/src/services/user/type.ts +2 -2
  70. package/src/store/agent/store.ts +4 -9
  71. package/src/store/chat/store.ts +4 -8
  72. package/src/store/file/store.ts +3 -9
  73. package/src/store/global/store.ts +5 -8
  74. package/src/store/market/store.ts +5 -8
  75. package/src/store/middleware/createDevtools.ts +23 -0
  76. package/src/store/serverConfig/store.ts +5 -11
  77. package/src/store/session/store.ts +3 -1
  78. package/src/store/tool/store.ts +4 -9
  79. package/src/store/user/helpers.ts +3 -2
  80. package/src/store/user/selectors.ts +10 -2
  81. package/src/store/user/slices/common/action.test.ts +3 -3
  82. package/src/store/user/slices/common/action.ts +4 -4
  83. package/src/store/user/slices/modelList/action.test.ts +2 -2
  84. package/src/store/user/slices/modelList/action.ts +16 -4
  85. package/src/store/user/slices/modelList/selectors/index.ts +1 -0
  86. package/src/store/user/slices/modelList/selectors/keyVaults.ts +25 -0
  87. package/src/store/user/slices/modelList/selectors/modelConfig.test.ts +1 -1
  88. package/src/store/user/slices/modelList/selectors/modelConfig.ts +1 -6
  89. package/src/store/user/slices/modelList/selectors/modelProvider.ts +1 -1
  90. package/src/store/user/slices/settings/action.test.ts +16 -14
  91. package/src/store/user/slices/settings/action.ts +14 -8
  92. package/src/store/user/slices/settings/initialState.ts +3 -3
  93. package/src/store/user/slices/settings/selectors/general.test.ts +45 -0
  94. package/src/store/user/slices/settings/selectors/general.ts +40 -0
  95. package/src/store/user/slices/settings/selectors/index.ts +1 -0
  96. package/src/store/user/slices/settings/selectors/settings.test.ts +0 -39
  97. package/src/store/user/slices/settings/selectors/settings.ts +11 -34
  98. package/src/store/user/store.ts +5 -8
  99. package/src/types/exportConfig.ts +2 -2
  100. package/src/types/serverConfig.ts +2 -2
  101. package/src/types/user/index.ts +2 -2
  102. package/src/types/{settings/base.ts → user/settings/general.ts} +4 -1
  103. package/src/types/user/settings/index.ts +32 -0
  104. package/src/types/user/settings/keyVaults.ts +36 -0
  105. package/src/types/user/settings/modelProvider.ts +34 -0
  106. package/src/types/{settings → user/settings}/sync.ts +1 -1
  107. package/src/types/{settings → user/settings}/systemAgent.ts +1 -1
  108. package/src/types/user/settings/tool.ts +5 -0
  109. package/src/types/{settings → user/settings}/tts.ts +1 -1
  110. package/src/types/settings/index.ts +0 -33
  111. package/src/types/settings/modelProvider.ts +0 -62
@@ -1,11 +1,11 @@
1
- import { PersistOptions, devtools, persist } from 'zustand/middleware';
1
+ import { PersistOptions, persist } from 'zustand/middleware';
2
2
  import { shallow } from 'zustand/shallow';
3
3
  import { createWithEqualityFn } from 'zustand/traditional';
4
4
  import type { StateCreator } from 'zustand/vanilla';
5
5
 
6
6
  import { createHyperStorage } from '@/store/middleware/createHyperStorage';
7
- import { isDev } from '@/utils/env';
8
7
 
8
+ import { createDevtools } from '../middleware/createDevtools';
9
9
  import { type StoreAction, createMarketAction } from './action';
10
10
  import { type StoreState, initialState } from './initialState';
11
11
 
@@ -40,12 +40,9 @@ const createStore: StateCreator<Store, [['zustand/devtools', never]]> = (...para
40
40
  ...createMarketAction(...parameters),
41
41
  });
42
42
 
43
+ const devtools = createDevtools('market');
44
+
43
45
  export const useMarketStore = createWithEqualityFn<Store>()(
44
- persist(
45
- devtools(createStore, {
46
- name: LOBE_AGENT_MARKET + (isDev ? '_DEV' : ''),
47
- }),
48
- persistOptions,
49
- ),
46
+ persist(devtools(createStore), persistOptions),
50
47
  shallow,
51
48
  );
@@ -0,0 +1,23 @@
1
+ import { optionalDevtools } from 'zustand-utils';
2
+ import { devtools as _devtools } from 'zustand/middleware';
3
+
4
+ import { isDev } from '@/utils/env';
5
+
6
+ export const createDevtools =
7
+ (name: string): typeof _devtools =>
8
+ (initializer) => {
9
+ let showDevtools = false;
10
+
11
+ // check url to show devtools
12
+ if (typeof window !== 'undefined') {
13
+ const url = new URL(window.location.href);
14
+ const debug = url.searchParams.get('debug');
15
+ if (debug?.includes(name)) {
16
+ showDevtools = true;
17
+ }
18
+ }
19
+
20
+ return optionalDevtools(showDevtools)(initializer, {
21
+ name: `LobeChat_${name}` + (isDev ? '_DEV' : ''),
22
+ });
23
+ };
@@ -1,13 +1,12 @@
1
1
  import { StoreApi } from 'zustand';
2
2
  import { createContext } from 'zustand-utils';
3
- import { devtools } from 'zustand/middleware';
4
3
  import { shallow } from 'zustand/shallow';
5
4
  import { createWithEqualityFn } from 'zustand/traditional';
6
5
  import { StateCreator } from 'zustand/vanilla';
7
6
 
8
7
  import { DEFAULT_FEATURE_FLAGS, IFeatureFlags } from '@/config/featureFlags';
8
+ import { createDevtools } from '@/store/middleware/createDevtools';
9
9
  import { GlobalServerConfig } from '@/types/serverConfig';
10
- import { isDev } from '@/utils/env';
11
10
  import { merge } from '@/utils/merge';
12
11
  import { StoreApiWithSelector } from '@/utils/zustand';
13
12
 
@@ -42,21 +41,16 @@ declare global {
42
41
  }
43
42
  }
44
43
 
44
+ const devtools = createDevtools('serverConfig');
45
+
45
46
  export const initServerConfigStore = (initState: Partial<ServerConfigStore>) =>
46
- createWithEqualityFn<ServerConfigStore>()(
47
- devtools(createStore(initState || {}), {
48
- name: 'LobeChat_ServerConfig' + (isDev ? '_DEV' : ''),
49
- }),
50
- shallow,
51
- );
47
+ createWithEqualityFn<ServerConfigStore>()(devtools(createStore(initState || {})), shallow);
52
48
 
53
49
  export const createServerConfigStore = (initState?: Partial<ServerConfigStore>) => {
54
50
  // make sure there is only one store
55
51
  if (!store) {
56
52
  store = createWithEqualityFn<ServerConfigStore>()(
57
- devtools(createStore(initState || {}), {
58
- name: 'LobeChat_ServerConfig' + (isDev ? '_DEV' : ''),
59
- }),
53
+ devtools(createStore(initState || {})),
60
54
  shallow,
61
55
  );
62
56
 
@@ -1,10 +1,11 @@
1
- import { devtools, subscribeWithSelector } from 'zustand/middleware';
1
+ import { subscribeWithSelector } from 'zustand/middleware';
2
2
  import { shallow } from 'zustand/shallow';
3
3
  import { createWithEqualityFn } from 'zustand/traditional';
4
4
  import { StateCreator } from 'zustand/vanilla';
5
5
 
6
6
  import { isDev } from '@/utils/env';
7
7
 
8
+ import { createDevtools } from '../middleware/createDevtools';
8
9
  import { SessionStoreState, initialState } from './initialState';
9
10
  import { SessionAction, createSessionSlice } from './slices/session/action';
10
11
  import { SessionGroupAction, createSessionGroupSlice } from './slices/sessionGroup/action';
@@ -20,6 +21,7 @@ const createStore: StateCreator<SessionStore, [['zustand/devtools', never]]> = (
20
21
  });
21
22
 
22
23
  // =============== implement useStore ============ //
24
+ const devtools = createDevtools('session');
23
25
 
24
26
  export const useSessionStore = createWithEqualityFn<SessionStore>()(
25
27
  subscribeWithSelector(
@@ -1,10 +1,8 @@
1
- import { devtools } from 'zustand/middleware';
2
1
  import { shallow } from 'zustand/shallow';
3
2
  import { createWithEqualityFn } from 'zustand/traditional';
4
3
  import { StateCreator } from 'zustand/vanilla';
5
4
 
6
- import { isDev } from '@/utils/env';
7
-
5
+ import { createDevtools } from '../middleware/createDevtools';
8
6
  import { ToolStoreState, initialState } from './initialState';
9
7
  import { BuiltinToolAction, createBuiltinToolSlice } from './slices/builtin';
10
8
  import { CustomPluginAction, createCustomPluginSlice } from './slices/customPlugin';
@@ -29,9 +27,6 @@ const createStore: StateCreator<ToolStore, [['zustand/devtools', never]]> = (...
29
27
 
30
28
  // =============== 实装 useStore ============ //
31
29
 
32
- export const useToolStore = createWithEqualityFn<ToolStore>()(
33
- devtools(createStore, {
34
- name: 'LobeChat_Tool' + (isDev ? '_DEV' : ''),
35
- }),
36
- shallow,
37
- );
30
+ const devtools = createDevtools('tools');
31
+
32
+ export const useToolStore = createWithEqualityFn<ToolStore>()(devtools(createStore), shallow);
@@ -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
 
@@ -7,14 +7,14 @@ import { userService } from '@/services/user';
7
7
  import { ClientService } from '@/services/user/client';
8
8
  import type { UserStore } from '@/store/user';
9
9
  import type { GlobalServerConfig } from '@/types/serverConfig';
10
- import type { GlobalSettings } from '@/types/settings';
11
10
  import { UserInitializationState } from '@/types/user';
11
+ import type { UserSettings } from '@/types/user/settings';
12
12
  import { switchLang } from '@/utils/client/switchLang';
13
13
  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
 
@@ -78,7 +78,7 @@ export const createCommonSlice: StateCreator<
78
78
 
79
79
  if (data) {
80
80
  // merge settings
81
- const serverSettings: DeepPartial<GlobalSettings> = {
81
+ const serverSettings: DeepPartial<UserSettings> = {
82
82
  defaultAgent: serverConfig.defaultAgent,
83
83
  languageModel: serverConfig.languageModel,
84
84
  };
@@ -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 { GeneralModelProviderConfig } from '@/types/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<GeneralModelProviderConfig> = { 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 { GlobalLLMConfig, GlobalLLMProviderKey } from '@/types/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';
@@ -28,9 +32,8 @@ export interface ModelListAction {
28
32
  removeEnabledModels: (provider: GlobalLLMProviderKey, model: string) => Promise<void>;
29
33
  setModelProviderConfig: <T extends GlobalLLMProviderKey>(
30
34
  provider: T,
31
- config: Partial<GlobalLLMConfig[T]>,
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',
@@ -1,4 +1,4 @@
1
- import { GlobalLLMProviderKey } from '@/types/settings';
1
+ import { GlobalLLMProviderKey } from '@/types/user/settings';
2
2
 
3
3
  import { UserStore } from '../../../store';
4
4
  import { currentLLMSettings, getProviderConfigById } from '../../settings/selectors/settings';
@@ -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
  };
@@ -3,7 +3,7 @@ import { uniqBy } from 'lodash-es';
3
3
  import { filterEnabledModels } from '@/config/modelProviders';
4
4
  import { ChatModelCard, ModelProviderCard } from '@/types/llm';
5
5
  import { ServerModelProviderConfig } from '@/types/serverConfig';
6
- import { GlobalLLMProviderKey } from '@/types/settings';
6
+ import { GlobalLLMProviderKey } from '@/types/user/settings';
7
7
 
8
8
  import { UserStore } from '../../../store';
9
9
  import { currentSettings, getProviderConfigById } from '../../settings/selectors/settings';
@@ -7,7 +7,10 @@ import { DEFAULT_AGENT, DEFAULT_SETTINGS } from '@/const/settings';
7
7
  import { userService } from '@/services/user';
8
8
  import { useUserStore } from '@/store/user';
9
9
  import { LobeAgentSettings } from '@/types/session';
10
- import { GlobalSettings } from '@/types/settings';
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: GlobalSettings = {
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<GlobalSettings> = { 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
 
@@ -116,7 +118,7 @@ describe('SettingsAction', () => {
116
118
  describe('setTranslationSystemAgent', () => {
117
119
  it('should set partial settings', async () => {
118
120
  const { result } = renderHook(() => useUserStore());
119
- const systemAgentSettings: Partial<GlobalSettings> = {
121
+ const systemAgentSettings: Partial<UserSettings> = {
120
122
  systemAgent: {
121
123
  translation: {
122
124
  model: 'testmodel',
@@ -7,19 +7,21 @@ 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 { GlobalSettings } from '@/types/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';
14
14
 
15
15
  export interface UserSettingsAction {
16
- importAppSettings: (settings: GlobalSettings) => Promise<void>;
16
+ importAppSettings: (settings: UserSettings) => Promise<void>;
17
17
  resetSettings: () => Promise<void>;
18
- setSettings: (settings: DeepPartial<GlobalSettings>) => Promise<void>;
18
+ setSettings: (settings: DeepPartial<UserSettings>) => Promise<void>;
19
19
  setTranslationSystemAgent: (provider: string, model: string) => Promise<void>;
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
  });
@@ -1,11 +1,11 @@
1
1
  import { DeepPartial } from 'utility-types';
2
2
 
3
3
  import { DEFAULT_SETTINGS } from '@/const/settings';
4
- import { GlobalSettings } from '@/types/settings';
4
+ import { UserSettings } from '@/types/user/settings';
5
5
 
6
6
  export interface UserSettingsState {
7
- defaultSettings: GlobalSettings;
8
- settings: DeepPartial<GlobalSettings>;
7
+ defaultSettings: UserSettings;
8
+ settings: DeepPartial<UserSettings>;
9
9
  }
10
10
 
11
11
  export const initialSettingsState: UserSettingsState = {
@@ -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';