@lobehub/chat 0.162.8 → 0.162.9

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 CHANGED
@@ -2,6 +2,31 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ### [Version 0.162.9](https://github.com/lobehub/lobe-chat/compare/v0.162.8...v0.162.9)
6
+
7
+ <sup>Released on **2024-05-29**</sup>
8
+
9
+ #### ♻ Code Refactoring
10
+
11
+ - **misc**: Refactor the settings to add optimistic updating.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### Code refactoring
19
+
20
+ - **misc**: Refactor the settings to add optimistic updating, closes [#2709](https://github.com/lobehub/lobe-chat/issues/2709) ([fade53e](https://github.com/lobehub/lobe-chat/commit/fade53e))
21
+
22
+ </details>
23
+
24
+ <div align="right">
25
+
26
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
27
+
28
+ </div>
29
+
5
30
  ### [Version 0.162.8](https://github.com/lobehub/lobe-chat/compare/v0.162.7...v0.162.8)
6
31
 
7
32
  <sup>Released on **2024-05-28**</sup>
package/README.md CHANGED
@@ -262,14 +262,14 @@ Our marketplace is not just a showcase platform but also a collaborative space.
262
262
 
263
263
  <!-- AGENT LIST -->
264
264
 
265
- | Recent Submits | Description |
266
- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
267
- | [Dart/Flutter Dev](https://chat-preview.lobehub.com/market?agent=dart-flutter)<br/><sup>By **[rezmeplxrf](https://github.com/rezmeplxrf)** on **2024-05-28**</sup> | Dart/Flutter Expert. Never nest more than 3 levels deep. Use riverpod, flutter_riverpod, riverpod_hook, flutter_hook for state management.<br/>`dart` `flutter` `development` `state-management` `riverpod` |
268
- | [C# .NET Technology Expert](https://chat-preview.lobehub.com/market?agent=dotnet-expert)<br/><sup>By **[johnnyqian](https://github.com/johnnyqian)** on **2024-05-28**</sup> | C# .NET Technology Expert<br/>`net` `developer` `net-core` `azure` `c` `microsoft` `sql-server` `entity-framework` `ef` `ef-core` |
269
- | [Daily Assistant](https://chat-preview.lobehub.com/market?agent=junior-helper)<br/><sup>By **[Qinks6](https://github.com/Qinks6)** on **2024-05-28**</sup> | A cute little helper that can search and draw<br/>`assistant` `search` `drawing` `information-retrieval` `user-interaction` |
270
- | [Node.js Optimizer](https://chat-preview.lobehub.com/market?agent=node-js-devoloper)<br/><sup>By **[chrisuhg](https://github.com/chrisuhg)** on **2024-05-28**</sup> | Specializes in Node.js code review, performance optimization, asynchronous programming, error handling, code refactoring, dependency management, security enhancement, test coverage, and documentation writing.<br/>`node-js` `code-optimization` `performance-optimization` `asynchronous-programming` `error-handling` |
271
-
272
- > 📊 Total agents: [<kbd>**279**</kbd> ](https://github.com/lobehub/lobe-chat-agents)
265
+ | Recent Submits | Description |
266
+ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
267
+ | [Dart/Flutter Dev](https://chat-preview.lobehub.com/market?agent=dart-flutter)<br/><sup>By **[rezmeplxrf](https://github.com/rezmeplxrf)** on **2024-05-28**</sup> | Dart/Flutter Expert. Never nest more than 3 levels deep. Use riverpod, flutter_riverpod, riverpod_hook, flutter_hook for state management.<br/>`dart` `flutter` `development` `state-management` `riverpod` |
268
+ | [C# .NET Technology Expert](https://chat-preview.lobehub.com/market?agent=dotnet-expert)<br/><sup>By **[johnnyqian](https://github.com/johnnyqian)** on **2024-05-28**</sup> | C# .NET Technology Expert<br/>`net` `developer` `net-core` `azure` `c` `microsoft` `sql-server` `entity-framework` `ef` `ef-core` |
269
+ | [Christian Missionary](https://chat-preview.lobehub.com/market?agent=jesus-missionary)<br/><sup>By **[epochaudio](https://github.com/epochaudio)** on **2024-05-28**</sup> | As a missionary of Jesus, I will enlighten your understanding and practical application of God's word based on the teachings of the Bible. Whether in times of confusion or seeking spiritual growth, I am here to serve you by this source of wisdom.<br/>`bible-teaching` `christian-mission` `theology-preaching` |
270
+ | [Daily Assistant](https://chat-preview.lobehub.com/market?agent=junior-helper)<br/><sup>By **[Qinks6](https://github.com/Qinks6)** on **2024-05-28**</sup> | A cute little helper that can search and draw<br/>`assistant` `search` `drawing` `information-retrieval` `user-interaction` |
271
+
272
+ > 📊 Total agents: [<kbd>**280**</kbd> ](https://github.com/lobehub/lobe-chat-agents)
273
273
 
274
274
  <!-- AGENT LIST -->
275
275
 
package/README.zh-CN.md CHANGED
@@ -250,14 +250,14 @@ LobeChat 的插件生态系统是其核心功能的重要扩展,它极大地
250
250
 
251
251
  <!-- AGENT LIST -->
252
252
 
253
- | 最近新增 | 助手说明 |
254
- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
255
- | [Dart/Flutter Dev](https://chat-preview.lobehub.com/market?agent=dart-flutter)<br/><sup>By **[rezmeplxrf](https://github.com/rezmeplxrf)** on **2024-05-28**</sup> | Dart/Flutter 전문가. 3단계 이상 중첩하지 않음. 상태 관리에 riverpod, flutter_riverpod, riverpod_hook, flutter_hook 사용.<br/>`dart` `flutter` `개발` `상태-관리` `riverpod` |
256
- | [C# .NET 技术专家](https://chat-preview.lobehub.com/market?agent=dotnet-expert)<br/><sup>By **[johnnyqian](https://github.com/johnnyqian)** on **2024-05-28**</sup> | C# .NET 技术专家<br/>`net` `developer` `net-core` `azure` `c` `microsoft` `sql-server` `entity-framework` `ef` `ef-core` |
257
- | [日常小助手](https://chat-preview.lobehub.com/market?agent=junior-helper)<br/><sup>By **[Qinks6](https://github.com/Qinks6)** on **2024-05-28**</sup> | 一个能搜索、能画图的小可爱<br/>`助手` `搜索` `绘图` `信息查询` `用户交互` |
258
- | [Node.js 优化师](https://chat-preview.lobehub.com/market?agent=node-js-devoloper)<br/><sup>By **[chrisuhg](https://github.com/chrisuhg)** on **2024-05-28**</sup> | 擅长 Node.js 代码审查、性能优化、异步编程、错误处理、代码重构、依赖管理、安全增强、测试覆盖率和文档编写。<br/>`node-js` `代码优化` `性能优化` `异步编程` `错误处理` |
259
-
260
- > 📊 Total agents: [<kbd>**279**</kbd> ](https://github.com/lobehub/lobe-chat-agents)
253
+ | 最近新增 | 助手说明 |
254
+ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
255
+ | [Dart/Flutter Dev](https://chat-preview.lobehub.com/market?agent=dart-flutter)<br/><sup>By **[rezmeplxrf](https://github.com/rezmeplxrf)** on **2024-05-28**</sup> | Dart/Flutter 전문가. 3단계 이상 중첩하지 않음. 상태 관리에 riverpod, flutter_riverpod, riverpod_hook, flutter_hook 사용.<br/>`dart` `flutter` `개발` `상태-관리` `riverpod` |
256
+ | [C# .NET 技术专家](https://chat-preview.lobehub.com/market?agent=dotnet-expert)<br/><sup>By **[johnnyqian](https://github.com/johnnyqian)** on **2024-05-28**</sup> | C# .NET 技术专家<br/>`net` `developer` `net-core` `azure` `c` `microsoft` `sql-server` `entity-framework` `ef` `ef-core` |
257
+ | [基督传教士](https://chat-preview.lobehub.com/market?agent=jesus-missionary)<br/><sup>By **[epochaudio](https://github.com/epochaudio)** on **2024-05-28**</sup> | 作为一名耶稣传教士,我将依据圣经教导以启迪你对神的话语的理解和实际运用。无论是在困惑还是寻求灵性成长的过程中,我都在这智慧的源泉旁为你服务<br/>`圣经教学` `基督传教` `神学布道` |
258
+ | [日常小助手](https://chat-preview.lobehub.com/market?agent=junior-helper)<br/><sup>By **[Qinks6](https://github.com/Qinks6)** on **2024-05-28**</sup> | 一个能搜索、能画图的小可爱<br/>`助手` `搜索` `绘图` `信息查询` `用户交互` |
259
+
260
+ > 📊 Total agents: [<kbd>**280**</kbd> ](https://github.com/lobehub/lobe-chat-agents)
261
261
 
262
262
  <!-- AGENT LIST -->
263
263
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "0.162.8",
3
+ "version": "0.162.9",
4
4
  "description": "Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
5
5
  "keywords": [
6
6
  "framework",
@@ -41,7 +41,6 @@ const Theme = memo(() => {
41
41
  {
42
42
  children: (
43
43
  <SelectWithImg
44
- defaultValue={themeMode}
45
44
  height={60}
46
45
  onChange={setThemeMode}
47
46
  options={[
@@ -65,6 +64,7 @@ const Theme = memo(() => {
65
64
  },
66
65
  ]}
67
66
  unoptimized={false}
67
+ value={themeMode}
68
68
  width={100}
69
69
  />
70
70
  ),
@@ -5,7 +5,7 @@ import { Flexbox } from 'react-layout-kit';
5
5
 
6
6
  import { useChatStore } from '@/store/chat';
7
7
  import { useUserStore } from '@/store/user';
8
- import { settingsSelectors } from '@/store/user/selectors';
8
+ import { keyVaultsConfigSelectors } from '@/store/user/selectors';
9
9
 
10
10
  import { FormAction } from './style';
11
11
 
@@ -16,7 +16,7 @@ interface AccessCodeFormProps {
16
16
  const AccessCodeForm = memo<AccessCodeFormProps>(({ id }) => {
17
17
  const { t } = useTranslation('error');
18
18
  const [password, updateKeyVaults] = useUserStore((s) => [
19
- settingsSelectors.password(s),
19
+ keyVaultsConfigSelectors.password(s),
20
20
  s.updateKeyVaults,
21
21
  ]);
22
22
  const [resend, deleteMessage] = useChatStore((s) => [s.regenerateMessage, s.deleteMessage]);
@@ -1,11 +1,11 @@
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 {
4
- keyVaultsConfigSelectors,
5
- settingsSelectors,
6
- } from '@/store/user/selectors';
3
+ import { keyVaultsConfigSelectors } from '@/store/user/selectors';
7
4
 
8
- // TODO: Need to be removed after tts refactor
5
+ /**
6
+ * TODO: Need to be removed after tts refactor
7
+ * @deprecated
8
+ */
9
9
  // eslint-disable-next-line no-undef
10
10
  export const createHeaderWithOpenAI = (header?: HeadersInit): HeadersInit => {
11
11
  const openai = keyVaultsConfigSelectors.openAIConfig(useUserStore.getState());
@@ -16,7 +16,7 @@ export const createHeaderWithOpenAI = (header?: HeadersInit): HeadersInit => {
16
16
  // eslint-disable-next-line no-undef
17
17
  return {
18
18
  ...header,
19
- [LOBE_CHAT_ACCESS_CODE]: settingsSelectors.password(useUserStore.getState()),
19
+ [LOBE_CHAT_ACCESS_CODE]: keyVaultsConfigSelectors.password(useUserStore.getState()),
20
20
  [OPENAI_API_KEY_HEADER_KEY]: apiKey,
21
21
  [OPENAI_END_POINT]: endpoint,
22
22
  };
@@ -1,11 +1,7 @@
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 {
5
- keyVaultsConfigSelectors,
6
- settingsSelectors,
7
- userProfileSelectors,
8
- } from '@/store/user/selectors';
4
+ import { keyVaultsConfigSelectors, userProfileSelectors } from '@/store/user/selectors';
9
5
  import { GlobalLLMProviderKey } from '@/types/user/settings';
10
6
  import { createJWT } from '@/utils/jwt';
11
7
 
@@ -51,7 +47,7 @@ export const getProviderAuthPayload = (provider: string) => {
51
47
  };
52
48
 
53
49
  const createAuthTokenWithPayload = async (payload = {}) => {
54
- const accessCode = settingsSelectors.password(useUserStore.getState());
50
+ const accessCode = keyVaultsConfigSelectors.password(useUserStore.getState());
55
51
  const userId = userProfileSelectors.userId(useUserStore.getState());
56
52
 
57
53
  return await createJWT<JWTPayload>({ accessCode, userId, ...payload });
@@ -1,9 +1,6 @@
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 {
4
- keyVaultsConfigSelectors,
5
- settingsSelectors,
6
- } from '@/store/user/selectors';
3
+ import { keyVaultsConfigSelectors } from '@/store/user/selectors';
7
4
 
8
5
  /**
9
6
  * TODO: Need to be removed after tts refactor
@@ -16,7 +13,7 @@ export const createHeaderWithOpenAI = (header?: HeadersInit): HeadersInit => {
16
13
  // eslint-disable-next-line no-undef
17
14
  return {
18
15
  ...header,
19
- [LOBE_CHAT_ACCESS_CODE]: settingsSelectors.password(useUserStore.getState()),
16
+ [LOBE_CHAT_ACCESS_CODE]: keyVaultsConfigSelectors.password(useUserStore.getState()),
20
17
  [OPENAI_API_KEY_HEADER_KEY]: openAIConfig.apiKey || '',
21
18
  [OPENAI_END_POINT]: openAIConfig.baseURL || '',
22
19
  };
@@ -33,7 +33,8 @@ export class ClientService implements IUserService {
33
33
  };
34
34
  }
35
35
 
36
- updateUserSettings = async (patch: DeepPartial<UserSettings>) => {
36
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
37
+ updateUserSettings = async (patch: DeepPartial<UserSettings>, _?: any) => {
37
38
  return UserModel.updateSettings(patch);
38
39
  };
39
40
 
@@ -53,7 +53,7 @@ export const createPluginSlice: StateCreator<
53
53
  const previousSettings = pluginSelectors.getPluginSettingsById(id)(get());
54
54
  const nextSettings = merge(previousSettings, settings);
55
55
 
56
- set({ updatePluginSettingsSignal: newSignal });
56
+ set({ updatePluginSettingsSignal: newSignal }, false, 'create new Signal');
57
57
  await pluginService.updatePluginSettings(id, nextSettings, newSignal.signal);
58
58
 
59
59
  await get().refreshPlugins();
@@ -32,11 +32,10 @@ describe('LLMSettingsSliceAction', () => {
32
32
  });
33
33
 
34
34
  // Assert that updateUserSettings was called with the correct OpenAI configuration
35
- expect(userService.updateUserSettings).toHaveBeenCalledWith({
36
- languageModel: {
37
- openai: openAIConfig,
38
- },
39
- });
35
+ expect(userService.updateUserSettings).toHaveBeenCalledWith(
36
+ { languageModel: { openai: openAIConfig } },
37
+ expect.any(AbortSignal),
38
+ );
40
39
  });
41
40
  });
42
41
 
@@ -212,11 +211,10 @@ describe('LLMSettingsSliceAction', () => {
212
211
  await result.current.removeEnabledModels('azure', model);
213
212
  });
214
213
 
215
- expect(spyOn).toHaveBeenCalledWith({
216
- languageModel: {
217
- azure: { enabledModels: ['gpt-4'] },
218
- },
219
- });
214
+ expect(spyOn).toHaveBeenCalledWith(
215
+ { languageModel: { azure: { enabledModels: ['gpt-4'] } } },
216
+ expect.any(AbortSignal),
217
+ );
220
218
  });
221
219
  });
222
220
 
@@ -250,11 +248,10 @@ describe('LLMSettingsSliceAction', () => {
250
248
  await result.current.toggleProviderEnabled('minimax', true);
251
249
  });
252
250
 
253
- expect(userService.updateUserSettings).toHaveBeenCalledWith({
254
- languageModel: {
255
- minimax: { enabled: true },
256
- },
257
- });
251
+ expect(userService.updateUserSettings).toHaveBeenCalledWith(
252
+ { languageModel: { minimax: { enabled: true } } },
253
+ expect.any(AbortSignal),
254
+ );
258
255
  });
259
256
 
260
257
  it('should disable the provider', async () => {
@@ -265,11 +262,10 @@ describe('LLMSettingsSliceAction', () => {
265
262
  await result.current.toggleProviderEnabled(provider, false);
266
263
  });
267
264
 
268
- expect(userService.updateUserSettings).toHaveBeenCalledWith({
269
- languageModel: {
270
- openai: { enabled: false },
271
- },
272
- });
265
+ expect(userService.updateUserSettings).toHaveBeenCalledWith(
266
+ { languageModel: { openai: { enabled: false } } },
267
+ expect.any(AbortSignal),
268
+ );
273
269
  });
274
270
  });
275
271
 
@@ -285,15 +281,18 @@ describe('LLMSettingsSliceAction', () => {
285
281
  await result.current.updateEnabledModels(provider, modelKeys, options);
286
282
  });
287
283
 
288
- expect(userService.updateUserSettings).toHaveBeenCalledWith({
289
- languageModel: {
290
- openai: {
291
- customModelCards: [{ id: 'custom-model' }],
292
- // TODO:目标单测中需要包含下面这一行
293
- // enabledModels: ['gpt-3.5-turbo', 'custom-model'],
284
+ expect(userService.updateUserSettings).toHaveBeenCalledWith(
285
+ {
286
+ languageModel: {
287
+ openai: {
288
+ customModelCards: [{ id: 'custom-model' }],
289
+ // TODO:目标单测中需要包含下面这一行
290
+ // enabledModels: ['gpt-3.5-turbo', 'custom-model'],
291
+ },
294
292
  },
295
293
  },
296
- });
294
+ expect.any(AbortSignal),
295
+ );
297
296
  });
298
297
 
299
298
  it('should not add removed model to customModelCards', async () => {
@@ -316,11 +315,12 @@ describe('LLMSettingsSliceAction', () => {
316
315
  await result.current.updateEnabledModels(provider, modelKeys, options);
317
316
  });
318
317
 
319
- expect(userService.updateUserSettings).toHaveBeenCalledWith({
320
- languageModel: {
321
- openai: { enabledModels: ['gpt-3.5-turbo'] },
318
+ expect(userService.updateUserSettings).toHaveBeenCalledWith(
319
+ {
320
+ languageModel: { openai: { enabledModels: ['gpt-3.5-turbo'] } },
322
321
  },
323
- });
322
+ expect.any(AbortSignal),
323
+ );
324
324
  });
325
325
  });
326
326
 
@@ -1,9 +1,14 @@
1
1
  import { UserStore } from '@/store/user';
2
- import { GlobalLLMProviderKey, OpenAICompatibleKeyVault } from '@/types/user/settings';
2
+ import {
3
+ GlobalLLMProviderKey,
4
+ OpenAICompatibleKeyVault,
5
+ UserKeyVaults,
6
+ } from '@/types/user/settings';
3
7
 
4
8
  import { currentSettings } from '../../settings/selectors/settings';
5
9
 
6
- export const keyVaultsSettings = (s: UserStore) => currentSettings(s).keyVaults;
10
+ export const keyVaultsSettings = (s: UserStore): UserKeyVaults =>
11
+ currentSettings(s).keyVaults || {};
7
12
 
8
13
  const openAIConfig = (s: UserStore) => keyVaultsSettings(s).openai || {};
9
14
  const bedrockConfig = (s: UserStore) => keyVaultsSettings(s).bedrock || {};
@@ -15,6 +20,8 @@ const getVaultByProvider = (provider: GlobalLLMProviderKey) => (s: UserStore) =>
15
20
  const isProviderEndpointNotEmpty = (provider: string) => (s: UserStore) =>
16
21
  !!getVaultByProvider(provider as GlobalLLMProviderKey)(s)?.baseURL;
17
22
 
23
+ const password = (s: UserStore) => keyVaultsSettings(s).password || '';
24
+
18
25
  export const keyVaultsConfigSelectors = {
19
26
  azureConfig,
20
27
  bedrockConfig,
@@ -22,4 +29,5 @@ export const keyVaultsConfigSelectors = {
22
29
  isProviderEndpointNotEmpty,
23
30
  ollamaConfig,
24
31
  openAIConfig,
32
+ password,
25
33
  };
@@ -40,9 +40,10 @@ describe('SettingsAction', () => {
40
40
  expect(setSettingsSpy).toHaveBeenCalledWith(newSettings);
41
41
 
42
42
  // Assert that the state has been updated
43
- expect(userService.updateUserSettings).toHaveBeenCalledWith({
44
- general: { themeMode: 'dark' },
45
- });
43
+ expect(userService.updateUserSettings).toHaveBeenCalledWith(
44
+ { general: { themeMode: 'dark' } },
45
+ expect.any(AbortSignal),
46
+ );
46
47
 
47
48
  // Restore the spy
48
49
  setSettingsSpy.mockRestore();
@@ -77,7 +78,10 @@ describe('SettingsAction', () => {
77
78
  });
78
79
 
79
80
  // Assert that updateUserSettings was called with the correct settings
80
- expect(userService.updateUserSettings).toHaveBeenCalledWith(partialSettings);
81
+ expect(userService.updateUserSettings).toHaveBeenCalledWith(
82
+ partialSettings,
83
+ expect.any(AbortSignal),
84
+ );
81
85
  });
82
86
  });
83
87
 
@@ -92,9 +96,10 @@ describe('SettingsAction', () => {
92
96
  });
93
97
 
94
98
  // Assert that updateUserSettings was called with the correct theme mode
95
- expect(userService.updateUserSettings).toHaveBeenCalledWith({
96
- general: { themeMode },
97
- });
99
+ expect(userService.updateUserSettings).toHaveBeenCalledWith(
100
+ { general: { themeMode } },
101
+ expect.any(AbortSignal),
102
+ );
98
103
  });
99
104
  });
100
105
 
@@ -111,7 +116,10 @@ describe('SettingsAction', () => {
111
116
  });
112
117
 
113
118
  // Assert that updateUserSettings was called with the merged agent settings
114
- expect(userService.updateUserSettings).toHaveBeenCalledWith({ defaultAgent: updatedAgent });
119
+ expect(userService.updateUserSettings).toHaveBeenCalledWith(
120
+ { defaultAgent: updatedAgent },
121
+ expect.any(AbortSignal),
122
+ );
115
123
  });
116
124
  });
117
125
 
@@ -136,7 +144,10 @@ describe('SettingsAction', () => {
136
144
  });
137
145
 
138
146
  // Assert that updateUserSettings was called with the correct settings
139
- expect(userService.updateUserSettings).toHaveBeenCalledWith(systemAgentSettings);
147
+ expect(userService.updateUserSettings).toHaveBeenCalledWith(
148
+ systemAgentSettings,
149
+ expect.any(AbortSignal),
150
+ );
140
151
  });
141
152
  });
142
153
  });
@@ -14,6 +14,8 @@ import { merge } from '@/utils/merge';
14
14
 
15
15
  export interface UserSettingsAction {
16
16
  importAppSettings: (settings: UserSettings) => Promise<void>;
17
+ internal_createSignal: () => AbortController;
18
+
17
19
  resetSettings: () => Promise<void>;
18
20
  setSettings: (settings: DeepPartial<UserSettings>) => Promise<void>;
19
21
  switchLocale: (locale: LocaleMode) => Promise<void>;
@@ -21,6 +23,7 @@ export interface UserSettingsAction {
21
23
  updateDefaultAgent: (agent: DeepPartial<LobeAgentSettings>) => Promise<void>;
22
24
  updateGeneralConfig: (settings: Partial<UserGeneralConfig>) => Promise<void>;
23
25
  updateKeyVaults: (settings: Partial<UserKeyVaults>) => Promise<void>;
26
+
24
27
  updateSystemAgent: (key: string, value: { model: string; provider: string }) => Promise<void>;
25
28
  }
26
29
 
@@ -35,6 +38,18 @@ export const createSettingsSlice: StateCreator<
35
38
 
36
39
  await setSettings(importAppSettings);
37
40
  },
41
+
42
+ internal_createSignal: () => {
43
+ const abortController = get().updateSettingsSignal;
44
+ if (abortController && !abortController.signal.aborted) abortController.abort('canceled');
45
+
46
+ const newSignal = new AbortController();
47
+
48
+ set({ updateSettingsSignal: newSignal }, false, 'signalForUpdateSettings');
49
+
50
+ return newSignal;
51
+ },
52
+
38
53
  resetSettings: async () => {
39
54
  await userService.resetUserSettings();
40
55
  await get().refreshUserState();
@@ -47,8 +62,10 @@ export const createSettingsSlice: StateCreator<
47
62
  if (isEqual(prevSetting, nextSettings)) return;
48
63
 
49
64
  const diffs = difference(nextSettings, defaultSettings);
65
+ set({ settings: diffs }, false, 'optimistic_updateSettings');
50
66
 
51
- await userService.updateUserSettings(diffs);
67
+ const abortController = get().internal_createSignal();
68
+ await userService.updateUserSettings(diffs, abortController.signal);
52
69
  await get().refreshUserState();
53
70
  },
54
71
  switchLocale: async (locale) => {
@@ -6,6 +6,7 @@ import { UserSettings } from '@/types/user/settings';
6
6
  export interface UserSettingsState {
7
7
  defaultSettings: UserSettings;
8
8
  settings: DeepPartial<UserSettings>;
9
+ updateSettingsSignal?: AbortController;
9
10
  }
10
11
 
11
12
  export const initialSettingsState: UserSettingsState = {
@@ -8,6 +8,7 @@ import {
8
8
  import {
9
9
  GlobalLLMProviderKey,
10
10
  ProviderConfig,
11
+ UserModelProviderConfig,
11
12
  UserSettings,
12
13
  } from '@/types/user/settings';
13
14
  import { merge } from '@/utils/merge';
@@ -16,14 +17,11 @@ import { UserStore } from '../../../store';
16
17
 
17
18
  export const currentSettings = (s: UserStore): UserSettings => merge(s.defaultSettings, s.settings);
18
19
 
19
- export const currentLLMSettings = (s: UserStore) => currentSettings(s).languageModel;
20
+ export const currentLLMSettings = (s: UserStore): UserModelProviderConfig =>
21
+ currentSettings(s).languageModel || {};
20
22
 
21
23
  export const getProviderConfigById = (provider: string) => (s: UserStore) =>
22
- currentLLMSettings(s)[provider as GlobalLLMProviderKey] as
23
- | ProviderConfig
24
- | undefined;
25
-
26
- const password = (s: UserStore) => currentSettings(s).keyVaults.password || '';
24
+ currentLLMSettings(s)[provider as GlobalLLMProviderKey] as ProviderConfig | undefined;
27
25
 
28
26
  const currentTTS = (s: UserStore) => merge(DEFAULT_TTS_CONFIG, currentSettings(s).tts);
29
27
 
@@ -50,6 +48,5 @@ export const settingsSelectors = {
50
48
  defaultAgentMeta,
51
49
  exportSettings,
52
50
  isDalleAutoGenerating,
53
- password,
54
51
  providerConfig: getProviderConfigById,
55
52
  };
@@ -1,10 +1,16 @@
1
+ import { DEFAULT_SYNC_CONFIG } from '@/const/settings/sync';
2
+ import { UserSyncSettings } from '@/types/user/settings';
3
+
1
4
  import { UserStore } from '../../store';
2
5
  import { currentSettings } from '../settings/selectors/settings';
3
6
 
4
- const webrtcConfig = (s: UserStore) => currentSettings(s).sync.webrtc;
7
+ const syncConfig = (s: UserStore): UserSyncSettings =>
8
+ currentSettings(s).sync || DEFAULT_SYNC_CONFIG;
9
+
10
+ const webrtcConfig = (s: UserStore) => syncConfig(s).webrtc;
5
11
  const webrtcChannelName = (s: UserStore) => webrtcConfig(s).channelName;
6
12
  const enableWebRTC = (s: UserStore) => webrtcConfig(s).enabled;
7
- const deviceName = (s: UserStore) => currentSettings(s).sync.deviceName;
13
+ const deviceName = (s: UserStore) => syncConfig(s).deviceName;
8
14
 
9
15
  export const syncSettingsSelectors = {
10
16
  deviceName,
@@ -25,7 +25,7 @@ export interface UserSettings {
25
25
  general: UserGeneralConfig;
26
26
  keyVaults: UserKeyVaults;
27
27
  languageModel: UserModelProviderConfig;
28
- sync: UserSyncSettings;
28
+ sync?: UserSyncSettings;
29
29
  systemAgent: UserSystemAgentConfig;
30
30
  tool: UserToolConfig;
31
31
  tts: UserTTSConfig;