@lobehub/chat 0.162.9 → 0.162.11

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.11](https://github.com/lobehub/lobe-chat/compare/v0.162.10...v0.162.11)
6
+
7
+ <sup>Released on **2024-05-29**</sup>
8
+
9
+ #### 🐛 Bug Fixes
10
+
11
+ - **misc**: Fix import config.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### What's fixed
19
+
20
+ - **misc**: Fix import config, closes [#2720](https://github.com/lobehub/lobe-chat/issues/2720) ([a5ddd9a](https://github.com/lobehub/lobe-chat/commit/a5ddd9a))
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.10](https://github.com/lobehub/lobe-chat/compare/v0.162.9...v0.162.10)
31
+
32
+ <sup>Released on **2024-05-29**</sup>
33
+
34
+ #### ♻ Code Refactoring
35
+
36
+ - **misc**: Refactor the config import for server import.
37
+
38
+ <br/>
39
+
40
+ <details>
41
+ <summary><kbd>Improvements and Fixes</kbd></summary>
42
+
43
+ #### Code refactoring
44
+
45
+ - **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))
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.9](https://github.com/lobehub/lobe-chat/compare/v0.162.8...v0.162.9)
6
56
 
7
57
  <sup>Released on **2024-05-29**</sup>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "0.162.9",
3
+ "version": "0.162.11",
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",
@@ -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(),
@@ -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
+ };
@@ -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,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,34 @@ 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
- };
26
+ importConfigState = async (config: ConfigFile): Promise<ImportResults> => {
27
+ if (config.exportType === 'settings') {
28
+ await importService.importSettings(config.state.settings);
29
+ return { type: 'settings' };
30
+ }
47
31
 
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
- }
32
+ if (config.exportType === 'all') {
33
+ await importService.importSettings(config.state.settings);
85
34
  }
35
+
36
+ const data = await importService.importData({
37
+ messages: (config.state as any).messages || [],
38
+ sessionGroups: (config.state as any).sessionGroups || [],
39
+ sessions: (config.state as any).sessions || [],
40
+ topics: (config.state as any).topics || [],
41
+ });
42
+
43
+ return { ...data, type: config.exportType };
86
44
  };
87
45
 
46
+ // TODO: Seperate export feature into a new service like importService
47
+
88
48
  /**
89
49
  * export all agents
90
50
  */
@@ -190,18 +150,6 @@ class ConfigService {
190
150
 
191
151
  private getAgent = (id: string) =>
192
152
  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
153
  }
206
154
 
207
155
  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();
@@ -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,8 +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>;
17
19
  internal_createSignal: () => AbortController;
18
-
19
20
  resetSettings: () => Promise<void>;
20
21
  setSettings: (settings: DeepPartial<UserSettings>) => Promise<void>;
21
22
  switchLocale: (locale: LocaleMode) => Promise<void>;
@@ -39,6 +40,21 @@ export const createSettingsSlice: StateCreator<
39
40
  await setSettings(importAppSettings);
40
41
  },
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
+
42
58
  internal_createSignal: () => {
43
59
  const abortController = get().updateSettingsSignal;
44
60
  if (abortController && !abortController.signal.aborted) abortController.abort('canceled');
@@ -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>;
@@ -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
- };