@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.
- package/CHANGELOG.md +33 -0
- package/package.json +1 -1
- package/src/app/(main)/chat/settings/features/SubmitAgentButton/SubmitAgentModal.tsx +3 -3
- package/src/app/(main)/settings/common/features/Common.tsx +1 -1
- package/src/app/(main)/settings/common/features/Theme/ThemeSwatches/ThemeSwatchesNeutral.tsx +5 -5
- package/src/app/(main)/settings/common/features/Theme/ThemeSwatches/ThemeSwatchesPrimary.tsx +5 -5
- package/src/app/(main)/settings/common/features/Theme/index.tsx +5 -4
- package/src/app/(main)/settings/llm/Azure/index.tsx +4 -4
- package/src/app/(main)/settings/llm/Bedrock/index.tsx +4 -4
- package/src/app/(main)/settings/llm/Ollama/Checker.tsx +1 -1
- package/src/app/(main)/settings/llm/components/ProviderConfig/index.tsx +5 -4
- package/src/app/(main)/settings/llm/const.ts +2 -1
- package/src/const/settings/common.ts +2 -2
- package/src/const/settings/index.ts +2 -1
- package/src/const/settings/llm.ts +0 -19
- package/src/database/client/core/db.ts +19 -0
- package/src/database/client/core/schemas.ts +3 -2
- package/src/database/client/models/__tests__/user.test.ts +5 -5
- package/src/database/client/models/user.ts +2 -3
- package/src/database/client/schemas/user.ts +10 -4
- package/src/features/AgentSetting/AgentMeta/index.tsx +3 -3
- package/src/features/AgentSetting/AgentTTS/index.tsx +2 -2
- package/src/features/ChatInput/STT/browser.tsx +2 -2
- package/src/features/ChatInput/STT/openai.tsx +2 -2
- package/src/features/Conversation/Error/APIKeyForm/Bedrock.tsx +5 -5
- package/src/features/Conversation/Error/APIKeyForm/ProviderApiKeyForm.tsx +4 -4
- package/src/features/Conversation/Error/AccessCodeForm.tsx +4 -4
- package/src/features/Conversation/Extras/TTS/index.tsx +2 -2
- package/src/features/Conversation/Plugins/Render/MarkdownType/index.tsx +2 -2
- package/src/features/Conversation/components/ChatItem/index.tsx +2 -2
- package/src/features/PluginDevModal/LocalForm.tsx +2 -2
- package/src/features/PluginStore/index.tsx +6 -2
- package/src/features/User/UserPanel/LangButton.tsx +2 -2
- package/src/features/User/UserPanel/ThemeButton.tsx +2 -2
- package/src/hooks/_header.ts +6 -3
- package/src/hooks/useTTS.ts +2 -2
- package/src/layout/GlobalProvider/AppTheme.tsx +4 -4
- package/src/libs/agent-runtime/types/type.ts +3 -2
- package/src/migrations/FromV6ToV7/fixtures/output-v7-from-v1.json +203 -0
- package/src/migrations/FromV6ToV7/fixtures/provider-input-v6.json +103 -0
- package/src/migrations/FromV6ToV7/fixtures/provider-output-v7.json +118 -0
- package/src/migrations/FromV6ToV7/index.ts +101 -0
- package/src/migrations/FromV6ToV7/migrations.test.ts +64 -0
- package/src/migrations/FromV6ToV7/types/v6.ts +61 -0
- package/src/migrations/FromV6ToV7/types/v7.ts +71 -0
- package/src/migrations/index.ts +9 -3
- package/src/services/__tests__/chat.test.ts +19 -19
- package/src/services/__tests__/share.test.ts +2 -2
- package/src/services/_auth.test.ts +10 -5
- package/src/services/_auth.ts +11 -7
- package/src/services/_header.ts +6 -3
- package/src/services/ollama.ts +3 -3
- package/src/services/user/client.test.ts +1 -1
- package/src/store/user/helpers.ts +3 -2
- package/src/store/user/selectors.ts +10 -2
- package/src/store/user/slices/common/action.test.ts +3 -3
- package/src/store/user/slices/common/action.ts +2 -2
- package/src/store/user/slices/modelList/action.test.ts +2 -2
- package/src/store/user/slices/modelList/action.ts +15 -3
- package/src/store/user/slices/modelList/selectors/index.ts +1 -0
- package/src/store/user/slices/modelList/selectors/keyVaults.ts +25 -0
- package/src/store/user/slices/modelList/selectors/modelConfig.test.ts +1 -1
- package/src/store/user/slices/modelList/selectors/modelConfig.ts +0 -5
- package/src/store/user/slices/settings/action.test.ts +14 -12
- package/src/store/user/slices/settings/action.ts +12 -6
- package/src/store/user/slices/settings/selectors/general.test.ts +45 -0
- package/src/store/user/slices/settings/selectors/general.ts +40 -0
- package/src/store/user/slices/settings/selectors/index.ts +1 -0
- package/src/store/user/slices/settings/selectors/settings.test.ts +0 -39
- package/src/store/user/slices/settings/selectors/settings.ts +6 -32
- package/src/types/user/settings/general.ts +1 -1
- package/src/types/user/settings/index.ts +7 -29
- package/src/types/user/settings/keyVaults.ts +36 -0
- package/src/types/user/settings/modelProvider.ts +4 -40
package/src/services/_auth.ts
CHANGED
|
@@ -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 {
|
|
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 } =
|
|
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 =
|
|
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 =
|
|
34
|
+
const config = keyVaultsConfigSelectors.ollamaConfig(useUserStore.getState());
|
|
33
35
|
|
|
34
|
-
return { endpoint: config?.
|
|
36
|
+
return { endpoint: config?.baseURL };
|
|
35
37
|
}
|
|
36
38
|
|
|
37
39
|
default: {
|
|
38
|
-
const config =
|
|
40
|
+
const config = keyVaultsConfigSelectors.getVaultByProvider(provider as GlobalLLMProviderKey)(
|
|
41
|
+
useUserStore.getState(),
|
|
42
|
+
);
|
|
39
43
|
|
|
40
|
-
return { apiKey: config?.apiKey, endpoint: config?.
|
|
44
|
+
return { apiKey: config?.apiKey, endpoint: config?.baseURL };
|
|
41
45
|
}
|
|
42
46
|
}
|
|
43
47
|
};
|
package/src/services/_header.ts
CHANGED
|
@@ -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 {
|
|
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 =
|
|
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.
|
|
21
|
+
[OPENAI_END_POINT]: openAIConfig.baseURL || '',
|
|
19
22
|
};
|
|
20
23
|
};
|
package/src/services/ollama.ts
CHANGED
|
@@ -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 {
|
|
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 =
|
|
28
|
+
const config = keyVaultsConfigSelectors.ollamaConfig(useUserStore.getState());
|
|
29
29
|
|
|
30
|
-
return config.
|
|
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 {
|
|
1
|
+
import { userGeneralSettingsSelectors } from './slices/settings/selectors';
|
|
2
2
|
import { useUserStore } from './store';
|
|
3
3
|
|
|
4
|
-
const getCurrentLanguage = () =>
|
|
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 {
|
|
2
|
+
export {
|
|
3
|
+
keyVaultsConfigSelectors,
|
|
4
|
+
modelConfigSelectors,
|
|
5
|
+
modelProviderSelectors,
|
|
6
|
+
} from './slices/modelList/selectors';
|
|
3
7
|
export { preferenceSelectors } from './slices/preference/selectors';
|
|
4
|
-
export {
|
|
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 {
|
|
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
|
|
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 {
|
|
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<
|
|
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 {
|
|
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],
|
|
@@ -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
|
+
};
|
|
@@ -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
|
-
|
|
26
|
-
|
|
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({
|
|
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:
|
|
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({
|
|
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(
|
|
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().
|
|
65
|
+
await get().updateGeneralConfig({ language: locale });
|
|
66
66
|
|
|
67
67
|
switchLang(locale);
|
|
68
68
|
},
|
|
69
69
|
switchThemeMode: async (themeMode) => {
|
|
70
|
-
await get().
|
|
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
|
+
};
|
|
@@ -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
|
|
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
|
-
|
|
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,
|