@lobehub/chat 0.162.8 → 0.162.10

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,56 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ### [Version 0.162.10](https://github.com/lobehub/lobe-chat/compare/v0.162.9...v0.162.10)
6
+
7
+ <sup>Released on **2024-05-29**</sup>
8
+
9
+ #### ♻ Code Refactoring
10
+
11
+ - **misc**: Refactor the config import for server import.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### Code refactoring
19
+
20
+ - **misc**: Refactor the config import for server import, closes [#2718](https://github.com/lobehub/lobe-chat/issues/2718) ([d4ee64b](https://github.com/lobehub/lobe-chat/commit/d4ee64b))
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
+
30
+ ### [Version 0.162.9](https://github.com/lobehub/lobe-chat/compare/v0.162.8...v0.162.9)
31
+
32
+ <sup>Released on **2024-05-29**</sup>
33
+
34
+ #### ♻ Code Refactoring
35
+
36
+ - **misc**: Refactor the settings to add optimistic updating.
37
+
38
+ <br/>
39
+
40
+ <details>
41
+ <summary><kbd>Improvements and Fixes</kbd></summary>
42
+
43
+ #### Code refactoring
44
+
45
+ - **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))
46
+
47
+ </details>
48
+
49
+ <div align="right">
50
+
51
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
52
+
53
+ </div>
54
+
5
55
  ### [Version 0.162.8](https://github.com/lobehub/lobe-chat/compare/v0.162.7...v0.162.8)
6
56
 
7
57
  <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.10",
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
  ),
@@ -34,7 +34,7 @@ export const DB_MessageSchema = z.object({
34
34
 
35
35
  fromModel: z.string().optional(),
36
36
  fromProvider: z.string().optional(),
37
- translate: TranslateSchema.optional().or(z.literal(false)),
37
+ translate: TranslateSchema.optional().or(z.literal(false)).or(z.null()),
38
38
  tts: z.any().optional(),
39
39
 
40
40
  traceId: z.string().optional(),
@@ -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]);
@@ -4,14 +4,15 @@ import { Icon } from '@lobehub/ui';
4
4
  import { Button, Result, Table, Upload } from 'antd';
5
5
  import { createStyles } from 'antd-style';
6
6
  import { CheckCircle, ImportIcon } from 'lucide-react';
7
- import React, { ReactNode, memo, useState } from 'react';
7
+ import React, { ReactNode, memo, useMemo, useState } from 'react';
8
8
  import { useTranslation } from 'react-i18next';
9
9
  import { Center, Flexbox } from 'react-layout-kit';
10
10
 
11
11
  import DataStyleModal from '@/components/DataStyleModal';
12
- import { useImportConfig } from '@/hooks/useImportConfig';
13
12
  import { ImportResult, ImportResults } from '@/services/config';
14
13
 
14
+ import { useImportConfig } from './useImportConfig';
15
+
15
16
  const useStyles = createStyles(({ css, token }) => {
16
17
  const size = 28;
17
18
 
@@ -145,12 +146,29 @@ interface DataImporterProps {
145
146
  }
146
147
  const DataImporter = memo<DataImporterProps>(({ children, onFinishImport }) => {
147
148
  const { t } = useTranslation('common');
148
- const { importConfig } = useImportConfig();
149
+ const importConfig = useImportConfig();
149
150
  const [duration, setDuration] = useState(0);
150
151
  const [importState, setImportState] = useState(ImportState.Start);
151
152
  const [importData, setImportData] = useState<ImportResults | undefined>();
152
153
  const { styles } = useStyles();
153
154
 
155
+ const dataSource = useMemo(() => {
156
+ if (!importData) return;
157
+
158
+ const { type, ...res } = importData;
159
+
160
+ if (type === 'settings') return;
161
+
162
+ return Object.entries(res)
163
+ .filter(([, v]) => !!v)
164
+ .map(([item, value]: [string, ImportResult]) => ({
165
+ added: value.added,
166
+ error: value.errors,
167
+ skips: value.skips,
168
+ title: t(`importModal.result.${item as keyof ImportResults}`),
169
+ }));
170
+ }, [importData]);
171
+
154
172
  return (
155
173
  <>
156
174
  <DataStyleModal
@@ -187,7 +205,7 @@ const DataImporter = memo<DataImporterProps>(({ children, onFinishImport }) => {
187
205
  style={{ paddingBlock: 24 }}
188
206
  subTitle={
189
207
  // if there is no importData, means it's only import the settings
190
- !importData ? (
208
+ !dataSource ? (
191
209
  t('importModal.finish.onlySettings')
192
210
  ) : (
193
211
  <Flexbox gap={16} width={400}>
@@ -200,14 +218,7 @@ const DataImporter = memo<DataImporterProps>(({ children, onFinishImport }) => {
200
218
  { dataIndex: 'skips', title: t('importModal.result.skips') },
201
219
  { dataIndex: 'error', title: t('importModal.result.errors') },
202
220
  ]}
203
- dataSource={Object.entries(importData).map(
204
- ([item, value]: [string, ImportResult]) => ({
205
- added: value.added,
206
- error: value.errors,
207
- skips: value.skips,
208
- title: t(`importModal.result.${item as keyof ImportResults}`),
209
- }),
210
- )}
221
+ dataSource={dataSource}
211
222
  pagination={false}
212
223
  size={'small'}
213
224
  />
@@ -0,0 +1,27 @@
1
+ import { useCallback } from 'react';
2
+
3
+ import { ImportResults, configService } from '@/services/config';
4
+ import { useChatStore } from '@/store/chat';
5
+ import { useSessionStore } from '@/store/session';
6
+ import { importConfigFile } from '@/utils/config';
7
+
8
+ export const useImportConfig = () => {
9
+ const refreshSessions = useSessionStore((s) => s.refreshSessions);
10
+ const [refreshMessages, refreshTopics] = useChatStore((s) => [s.refreshMessages, s.refreshTopic]);
11
+
12
+ return useCallback(
13
+ async (file: File) =>
14
+ new Promise<ImportResults | undefined>((resolve) => {
15
+ importConfigFile(file, async (config) => {
16
+ const data = await configService.importConfigState(config);
17
+
18
+ await refreshSessions();
19
+ await refreshMessages();
20
+ await refreshTopics();
21
+
22
+ resolve(data);
23
+ });
24
+ }),
25
+ [],
26
+ );
27
+ };
@@ -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
  };
@@ -5,7 +5,6 @@ import { memo, useEffect } from 'react';
5
5
  import { createStoreUpdater } from 'zustand-utils';
6
6
 
7
7
  import { LOBE_URL_IMPORT_NAME } from '@/const/url';
8
- import { useImportConfig } from '@/hooks/useImportConfig';
9
8
  import { useIsMobile } from '@/hooks/useIsMobile';
10
9
  import { useEnabledDataSync } from '@/hooks/useSyncData';
11
10
  import { useAgentStore } from '@/store/agent';
@@ -17,9 +16,10 @@ import { authSelectors } from '@/store/user/selectors';
17
16
  const StoreInitialization = memo(() => {
18
17
  const router = useRouter();
19
18
 
20
- const [useInitUserState, isLogin] = useUserStore((s) => [
21
- s.useInitUserState,
19
+ const [isLogin, useInitUserState, importUrlShareSettings] = useUserStore((s) => [
22
20
  authSelectors.isLogin(s),
21
+ s.useInitUserState,
22
+ s.importUrlShareSettings,
23
23
  ]);
24
24
 
25
25
  const { serverConfig } = useServerConfigStore();
@@ -52,10 +52,9 @@ const StoreInitialization = memo(() => {
52
52
  useStoreUpdater('router', router);
53
53
 
54
54
  // Import settings from the url
55
- const { importSettings } = useImportConfig();
56
55
  const searchParam = useSearchParams().get(LOBE_URL_IMPORT_NAME);
57
56
  useEffect(() => {
58
- importSettings(searchParam);
57
+ importUrlShareSettings(searchParam);
59
58
  }, [searchParam]);
60
59
 
61
60
  // useEffect(() => {
@@ -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
  };
@@ -1,3 +1,4 @@
1
+ import { importService } from '@/services/import';
1
2
  import { messageService } from '@/services/message';
2
3
  import { sessionService } from '@/services/session';
3
4
  import { topicService } from '@/services/topic';
@@ -6,10 +7,6 @@ import { sessionSelectors } from '@/store/session/selectors';
6
7
  import { useUserStore } from '@/store/user';
7
8
  import { settingsSelectors } from '@/store/user/selectors';
8
9
  import { ConfigFile } from '@/types/exportConfig';
9
- import { ChatMessage } from '@/types/message';
10
- import { LobeSessions, SessionGroupItem } from '@/types/session';
11
- import { ChatTopic } from '@/types/topic';
12
- import { UserSettings } from '@/types/user/settings';
13
10
  import { createConfigFile, exportConfigFile } from '@/utils/config';
14
11
 
15
12
  export interface ImportResult {
@@ -20,71 +17,30 @@ export interface ImportResult {
20
17
  export interface ImportResults {
21
18
  messages?: ImportResult;
22
19
  sessionGroups?: ImportResult;
23
- sessions: ImportResult;
20
+ sessions?: ImportResult;
24
21
  topics?: ImportResult;
22
+ type?: string;
25
23
  }
26
24
 
27
25
  class ConfigService {
28
- /**
29
- * import sessions from files
30
- * @param sessions
31
- */
32
- importSessions = async (sessions: LobeSessions) => {
33
- return await sessionService.batchCreateSessions(sessions);
34
- };
35
- importMessages = async (messages: ChatMessage[]) => {
36
- return messageService.batchCreateMessages(messages);
37
- };
38
- importSettings = async (settings: UserSettings) => {
39
- useUserStore.getState().importAppSettings(settings);
40
- };
41
- importTopics = async (topics: ChatTopic[]) => {
42
- return topicService.batchCreateTopics(topics);
43
- };
44
- importSessionGroups = async (sessionGroups: SessionGroupItem[]) => {
45
- return sessionService.batchCreateSessionGroups(sessionGroups || []);
46
- };
47
-
48
- importConfigState = async (config: ConfigFile): Promise<ImportResults | undefined> => {
49
- switch (config.exportType) {
50
- case 'settings': {
51
- await this.importSettings(config.state.settings);
52
-
53
- break;
54
- }
55
-
56
- case 'agents': {
57
- const sessionGroups = await this.importSessionGroups(config.state.sessionGroups);
58
-
59
- const data = await this.importSessions(config.state.sessions);
60
- return {
61
- sessionGroups: this.mapImportResult(sessionGroups),
62
- sessions: this.mapImportResult(data),
63
- };
64
- }
65
-
66
- case 'all': {
67
- await this.importSettings(config.state.settings);
68
- }
69
- // all and sessions have the same data process, so we can fall through
70
-
71
- // eslint-disable-next-line no-fallthrough
72
- case 'sessions': {
73
- const sessionGroups = await this.importSessionGroups(config.state.sessionGroups);
74
- const sessions = await this.importSessions(config.state.sessions);
75
- const topics = await this.importTopics(config.state.topics);
76
- const messages = await this.importMessages(config.state.messages);
77
-
78
- return {
79
- messages: this.mapImportResult(messages),
80
- sessionGroups: this.mapImportResult(sessionGroups),
81
- sessions: this.mapImportResult(sessions),
82
- topics: this.mapImportResult(topics),
83
- };
84
- }
26
+ importConfigState = async (config: ConfigFile): Promise<ImportResults> => {
27
+ if (config.exportType === 'settings') {
28
+ await importService.importSettings(config.state.settings);
29
+ return { type: 'settings' };
85
30
  }
31
+
32
+ const data = await importService.importData({
33
+ messages: (config.state as any).messages || [],
34
+ sessionGroups: (config.state as any).sessionGroups || [],
35
+ sessions: (config.state as any).sessions || [],
36
+ topics: (config.state as any).topics || [],
37
+ });
38
+
39
+ return { ...data, type: config.exportType };
86
40
  };
87
41
 
42
+ // TODO: Seperate export feature into a new service like importService
43
+
88
44
  /**
89
45
  * export all agents
90
46
  */
@@ -190,18 +146,6 @@ class ConfigService {
190
146
 
191
147
  private getAgent = (id: string) =>
192
148
  sessionSelectors.getSessionById(id)(useSessionStore.getState());
193
-
194
- private mapImportResult = (input: {
195
- added: number;
196
- errors?: Error[];
197
- skips: string[];
198
- }): ImportResult => {
199
- return {
200
- added: input.added,
201
- errors: input.errors?.length || 0,
202
- skips: input.skips.length,
203
- };
204
- };
205
149
  }
206
150
 
207
151
  export const configService = new ConfigService();
@@ -0,0 +1,62 @@
1
+ import { MessageModel } from '@/database/client/models/message';
2
+ import { SessionModel } from '@/database/client/models/session';
3
+ import { SessionGroupModel } from '@/database/client/models/sessionGroup';
4
+ import { TopicModel } from '@/database/client/models/topic';
5
+ import { ImportResult, ImportResults } from '@/services/config';
6
+ import { useUserStore } from '@/store/user';
7
+ import { ConfigStateSessions } from '@/types/exportConfig';
8
+ import { UserSettings } from '@/types/user/settings';
9
+
10
+ export class ClientService {
11
+ importSettings = async (settings: UserSettings) => {
12
+ await useUserStore.getState().importAppSettings(settings);
13
+ };
14
+
15
+ importData = async (config: ConfigStateSessions): Promise<ImportResults> => {
16
+ const { messages, sessionGroups, sessions, topics } = config;
17
+
18
+ let messageResult: ImportResult | undefined;
19
+ let sessionResult: ImportResult | undefined;
20
+ let sessionGroupResult: ImportResult | undefined;
21
+ let topicResult: ImportResult | undefined;
22
+
23
+ if (messages.length > 0) {
24
+ const res = await MessageModel.batchCreate(messages);
25
+ messageResult = this.mapImportResult(res);
26
+ }
27
+
28
+ if (sessionGroups.length > 0) {
29
+ const res = await SessionGroupModel.batchCreate(sessionGroups);
30
+ sessionGroupResult = this.mapImportResult(res);
31
+ }
32
+
33
+ if (topics.length > 0) {
34
+ const res = await TopicModel.batchCreate(topics as any);
35
+ topicResult = this.mapImportResult(res);
36
+ }
37
+
38
+ if (sessions.length > 0) {
39
+ const data = await SessionModel.batchCreate(sessions);
40
+ sessionResult = this.mapImportResult(data);
41
+ }
42
+
43
+ return {
44
+ messages: messageResult,
45
+ sessionGroups: sessionGroupResult,
46
+ sessions: sessionResult,
47
+ topics: topicResult,
48
+ };
49
+ };
50
+
51
+ private mapImportResult = (input: {
52
+ added: number;
53
+ errors?: Error[];
54
+ skips: string[];
55
+ }): ImportResult => {
56
+ return {
57
+ added: input.added,
58
+ errors: input.errors?.length || 0,
59
+ skips: input.skips.length,
60
+ };
61
+ };
62
+ }
@@ -0,0 +1,3 @@
1
+ import { ClientService } from './client';
2
+
3
+ export const importService = new ClientService();
@@ -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
  });
@@ -3,6 +3,7 @@ import isEqual from 'fast-deep-equal';
3
3
  import { DeepPartial } from 'utility-types';
4
4
  import type { StateCreator } from 'zustand/vanilla';
5
5
 
6
+ import { shareService } from '@/services/share';
6
7
  import { userService } from '@/services/user';
7
8
  import type { UserStore } from '@/store/user';
8
9
  import { LocaleMode } from '@/types/locale';
@@ -14,6 +15,8 @@ import { merge } from '@/utils/merge';
14
15
 
15
16
  export interface UserSettingsAction {
16
17
  importAppSettings: (settings: UserSettings) => Promise<void>;
18
+ importUrlShareSettings: (settingsParams: string | null) => Promise<void>;
19
+ internal_createSignal: () => AbortController;
17
20
  resetSettings: () => Promise<void>;
18
21
  setSettings: (settings: DeepPartial<UserSettings>) => Promise<void>;
19
22
  switchLocale: (locale: LocaleMode) => Promise<void>;
@@ -21,6 +24,7 @@ export interface UserSettingsAction {
21
24
  updateDefaultAgent: (agent: DeepPartial<LobeAgentSettings>) => Promise<void>;
22
25
  updateGeneralConfig: (settings: Partial<UserGeneralConfig>) => Promise<void>;
23
26
  updateKeyVaults: (settings: Partial<UserKeyVaults>) => Promise<void>;
27
+
24
28
  updateSystemAgent: (key: string, value: { model: string; provider: string }) => Promise<void>;
25
29
  }
26
30
 
@@ -35,6 +39,33 @@ export const createSettingsSlice: StateCreator<
35
39
 
36
40
  await setSettings(importAppSettings);
37
41
  },
42
+
43
+ /**
44
+ * Import settings from a string in json format
45
+ */
46
+ importUrlShareSettings: async (settingsParams: string | null) => {
47
+ if (settingsParams) {
48
+ const importSettings = shareService.decodeShareSettings(settingsParams);
49
+ if (importSettings?.message || !importSettings?.data) {
50
+ // handle some error
51
+ return;
52
+ }
53
+
54
+ await get().setSettings(importSettings.data);
55
+ }
56
+ },
57
+
58
+ internal_createSignal: () => {
59
+ const abortController = get().updateSettingsSignal;
60
+ if (abortController && !abortController.signal.aborted) abortController.abort('canceled');
61
+
62
+ const newSignal = new AbortController();
63
+
64
+ set({ updateSettingsSignal: newSignal }, false, 'signalForUpdateSettings');
65
+
66
+ return newSignal;
67
+ },
68
+
38
69
  resetSettings: async () => {
39
70
  await userService.resetUserSettings();
40
71
  await get().refreshUserState();
@@ -47,8 +78,10 @@ export const createSettingsSlice: StateCreator<
47
78
  if (isEqual(prevSetting, nextSettings)) return;
48
79
 
49
80
  const diffs = difference(nextSettings, defaultSettings);
81
+ set({ settings: diffs }, false, 'optimistic_updateSettings');
50
82
 
51
- await userService.updateUserSettings(diffs);
83
+ const abortController = get().internal_createSignal();
84
+ await userService.updateUserSettings(diffs, abortController.signal);
52
85
  await get().refreshUserState();
53
86
  },
54
87
  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,
@@ -38,7 +38,7 @@ export interface ChatMessage extends BaseDataModel {
38
38
  fromModel?: string;
39
39
  fromProvider?: string;
40
40
  // 翻译
41
- translate?: ChatTranslate | false;
41
+ translate?: ChatTranslate | false | null;
42
42
  // TTS
43
43
  tts?: ChatTTS;
44
44
  } & Record<string, any>;
@@ -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;
@@ -1,45 +0,0 @@
1
- import { useMemo } from 'react';
2
-
3
- import { ImportResults, configService } from '@/services/config';
4
- import { shareService } from '@/services/share';
5
- import { useChatStore } from '@/store/chat';
6
- import { useSessionStore } from '@/store/session';
7
- import { useUserStore } from '@/store/user';
8
- import { importConfigFile } from '@/utils/config';
9
-
10
- export const useImportConfig = () => {
11
- const refreshSessions = useSessionStore((s) => s.refreshSessions);
12
- const [refreshMessages, refreshTopics] = useChatStore((s) => [s.refreshMessages, s.refreshTopic]);
13
- const [setSettings] = useUserStore((s) => [s.setSettings]);
14
-
15
- const importConfig = async (file: File) =>
16
- new Promise<ImportResults | undefined>((resolve) => {
17
- importConfigFile(file, async (config) => {
18
- const data = await configService.importConfigState(config);
19
-
20
- await refreshSessions();
21
- await refreshMessages();
22
- await refreshTopics();
23
-
24
- resolve(data);
25
- });
26
- });
27
-
28
- /**
29
- * Import settings from a string in json format
30
- * @param settingsParams
31
- * @returns
32
- */
33
- const importSettings = (settingsParams: string | null) => {
34
- if (settingsParams) {
35
- const importSettings = shareService.decodeShareSettings(settingsParams);
36
- if (importSettings?.message || !importSettings?.data) {
37
- // handle some error
38
- return;
39
- }
40
- setSettings(importSettings.data);
41
- }
42
- };
43
-
44
- return useMemo(() => ({ importConfig, importSettings }), []);
45
- };