@lobehub/chat 0.28.0 → 0.29.0

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,32 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ## [Version 0.29.0](https://github.com/lobehub/lobe-chat/compare/v0.28.0...v0.29.0)
6
+
7
+ <sup>Released on **2023-07-30**</sup>
8
+
9
+ #### ✨ Features
10
+
11
+ - **misc**: 实现单个会话和角色的导出功能, 实现清空所有会话消息.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### What's improved
19
+
20
+ - **misc**: 实现单个会话和角色的导出功能 ([d15a481](https://github.com/lobehub/lobe-chat/commit/d15a481))
21
+ - **misc**: 实现清空所有会话消息 ([64c5125](https://github.com/lobehub/lobe-chat/commit/64c5125))
22
+
23
+ </details>
24
+
25
+ <div align="right">
26
+
27
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
28
+
29
+ </div>
30
+
5
31
  ## [Version 0.28.0](https://github.com/lobehub/lobe-chat/compare/v0.27.4...v0.28.0)
6
32
 
7
33
  <sup>Released on **2023-07-30**</sup>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "0.28.0",
3
+ "version": "0.29.0",
4
4
  "description": "Lobe Chat is an open-source chatbot client using LangChain, Typescript and Next.js",
5
5
  "keywords": [
6
6
  "chatbot",
@@ -22,8 +22,8 @@ const BottomAction = memo<{ children: ReactNode }>(({ children }) => {
22
22
  const { t } = useTranslation('common');
23
23
 
24
24
  const { exportSessions, exportSettings, exportAll, exportAgents } = useExportConfig();
25
-
26
25
  const { importConfig } = useImportConfig();
26
+
27
27
  const items: MenuProps['items'] = useMemo(
28
28
  () => [
29
29
  {
@@ -0,0 +1,62 @@
1
+ import { sessionSelectors, useSessionStore } from '@/store/session';
2
+ import { settingsSelectors, useSettings } from '@/store/settings';
3
+ import { createConfigFile, exportConfigFile } from '@/utils/config';
4
+
5
+ const getAgents = () => sessionSelectors.exportAgents(useSessionStore.getState());
6
+ const getAgent = (id: string) => sessionSelectors.getExportAgent(id)(useSessionStore.getState());
7
+
8
+ const getSessions = () => sessionSelectors.exportSessions(useSessionStore.getState());
9
+ const getSession = (id: string) => sessionSelectors.getSessionById(id)(useSessionStore.getState());
10
+
11
+ const getSettings = () => settingsSelectors.exportSettings(useSettings.getState());
12
+
13
+ export const exportAgents = () => {
14
+ const sessions = getAgents();
15
+
16
+ const config = createConfigFile('agents', { sessions });
17
+
18
+ exportConfigFile(config, 'agents');
19
+ };
20
+
21
+ export const exportSingleAgent = (id: string) => {
22
+ const agent = getAgent(id);
23
+ if (!agent) return;
24
+
25
+ const config = createConfigFile('agents', { sessions: { [id]: agent } });
26
+
27
+ exportConfigFile(config, agent.meta?.title || 'agent');
28
+ };
29
+
30
+ export const exportSessions = () => {
31
+ const sessions = getSessions();
32
+ const config = createConfigFile('sessions', { sessions });
33
+
34
+ exportConfigFile(config, 'sessions');
35
+ };
36
+
37
+ export const exportSingleSession = (id: string) => {
38
+ const session = getSession(id);
39
+ if (!session) return;
40
+ const sessions = { [id]: session };
41
+
42
+ const config = createConfigFile('sessions', { sessions });
43
+
44
+ exportConfigFile(config, `${session.meta?.title}-session`);
45
+ };
46
+
47
+ export const exportSettings = () => {
48
+ const settings = getSettings();
49
+
50
+ const config = createConfigFile('settings', { settings });
51
+
52
+ exportConfigFile(config, 'settings');
53
+ };
54
+
55
+ export const exportAll = () => {
56
+ const sessions = getSessions();
57
+ const settings = getSettings();
58
+
59
+ const config = createConfigFile('all', { sessions, settings });
60
+
61
+ exportConfigFile(config, 'config');
62
+ };
@@ -1,69 +1,6 @@
1
- import { transform } from 'lodash-es';
2
1
  import { useMemo } from 'react';
3
- import { shallow } from 'zustand/shallow';
4
2
 
5
- import { Migration } from '@/migrations';
6
- import { useSessionStore } from '@/store/session';
7
- import { useSettings } from '@/store/settings';
8
- import {
9
- ConfigFileAgents,
10
- ConfigFileAll,
11
- ConfigFileSessions,
12
- ConfigFileSettings,
13
- } from '@/types/exportConfig';
14
- import { exportConfigFile } from '@/utils/config';
3
+ import { exportAgents, exportAll, exportSessions, exportSettings } from '@/helpers/export';
15
4
 
16
- export const useExportConfig = () => {
17
- const [sessions] = useSessionStore((s) => [s.sessions], shallow);
18
-
19
- const [settings] = useSettings((s) => [s.settings, s.importSettings], shallow);
20
-
21
- const exportAgents = () => {
22
- const config: ConfigFileAgents = {
23
- exportType: 'agents',
24
- state: {
25
- sessions: transform(sessions, (result, value, key) => {
26
- result[key] = { ...value, chats: {}, topics: {} };
27
- }),
28
- },
29
- version: Migration.targetVersion,
30
- };
31
-
32
- exportConfigFile(config, 'agents');
33
- };
34
-
35
- const exportSessions = () => {
36
- const config: ConfigFileSessions = {
37
- exportType: 'sessions',
38
- state: { sessions },
39
- version: Migration.targetVersion,
40
- };
41
-
42
- exportConfigFile(config, 'sessions');
43
- };
44
-
45
- const exportSettings = () => {
46
- const config: ConfigFileSettings = {
47
- exportType: 'settings',
48
- state: { settings },
49
- version: Migration.targetVersion,
50
- };
51
-
52
- exportConfigFile(config, 'settings');
53
- };
54
-
55
- const exportAll = () => {
56
- // 将 入参转换为 配置文件格式
57
- const config: ConfigFileAll = {
58
- exportType: 'all',
59
- state: { sessions, settings },
60
- version: Migration.targetVersion,
61
- };
62
- exportConfigFile(config, 'config');
63
- };
64
-
65
- return useMemo(
66
- () => ({ exportAgents, exportAll, exportSessions, exportSettings }),
67
- [sessions, settings],
68
- );
69
- };
5
+ export const useExportConfig = () =>
6
+ useMemo(() => ({ exportAgents, exportAll, exportSessions, exportSettings }), []);
@@ -2,9 +2,10 @@ export default {
2
2
  danger: {
3
3
  clear: {
4
4
  action: '立即清除',
5
- confirm: '确认清除所有聊天、设置数据?',
6
- desc: '清除所有聊天、设置数据',
7
- title: '清除所有数据',
5
+ confirm: '确认清除所有聊天数据?',
6
+ desc: '清除所有会话数据',
7
+ success: '已清除所有会话消息',
8
+ title: '清除所有会话消息',
8
9
  },
9
10
  reset: {
10
11
  action: '立即重置',
@@ -1,4 +1,4 @@
1
- import { ConfigState } from '@/types/exportConfig';
1
+ import { ConfigStateAll } from '@/types/exportConfig';
2
2
  import { VersionController } from '@/utils/VersionController';
3
3
 
4
4
  // 当前最新的版本号
@@ -7,7 +7,7 @@ export const CURRENT_CONFIG_VERSION = 1;
7
7
  // 历史记录版本升级模块
8
8
  export const ConfigMigrations = [];
9
9
 
10
- export const Migration = new VersionController<ConfigState>(
10
+ export const Migration = new VersionController<ConfigStateAll>(
11
11
  ConfigMigrations,
12
12
  CURRENT_CONFIG_VERSION,
13
13
  );
@@ -1,12 +1,13 @@
1
1
  import { ActionIcon, Avatar, Icon, List } from '@lobehub/ui';
2
2
  import { useHover } from 'ahooks';
3
- import { Dropdown, type MenuProps, Popconfirm, Tag } from 'antd';
3
+ import { App, Dropdown, type MenuProps, Tag } from 'antd';
4
4
  import { FolderOutput, MoreVertical, Pin, PinOff, Trash } from 'lucide-react';
5
- import { FC, memo, useMemo, useRef, useState } from 'react';
5
+ import { FC, memo, useMemo, useRef } from 'react';
6
6
  import { useTranslation } from 'react-i18next';
7
7
  import { Flexbox } from 'react-layout-kit';
8
8
  import { shallow } from 'zustand/shallow';
9
9
 
10
+ import { exportSingleAgent, exportSingleSession } from '@/helpers/export';
10
11
  import { agentSelectors, chatSelectors, sessionSelectors, useSessionStore } from '@/store/session';
11
12
  import { useSettings } from '@/store/settings';
12
13
 
@@ -24,10 +25,10 @@ interface SessionItemProps {
24
25
  const SessionItem: FC<SessionItemProps> = memo(({ id, active = true, loading, pin }) => {
25
26
  const ref = useRef(null);
26
27
  const isHovering = useHover(ref);
27
- const [popOpen, setPopOpen] = useState(false);
28
- const [dropdownOpen, setDropdownOpen] = useState(false);
28
+
29
29
  const { t } = useTranslation('common');
30
- const isHighlight = isHovering || dropdownOpen;
30
+ const isHighlight = isHovering;
31
+
31
32
  const { styles, theme, cx } = useStyles(isHighlight);
32
33
  const [defaultModel] = useSettings((s) => [s.settings.model], shallow);
33
34
 
@@ -58,23 +59,31 @@ const SessionItem: FC<SessionItemProps> = memo(({ id, active = true, loading, pi
58
59
  ];
59
60
  }, shallow);
60
61
 
61
- // TODO: 动作绑定
62
+ const { modal } = App.useApp();
62
63
  const items: MenuProps['items'] = useMemo(
63
64
  () => [
64
65
  {
65
66
  icon: <Icon icon={pin ? PinOff : Pin} />,
66
67
  key: 'pin',
67
68
  label: t(pin ? 'pinOff' : 'pin'),
69
+ // TODO: 动作绑定
70
+ onClick: () => {},
68
71
  },
69
72
  {
70
73
  children: [
71
74
  {
72
75
  key: 'agent',
73
76
  label: <div>{t('exportType.agent')}</div>,
77
+ onClick: () => {
78
+ exportSingleAgent(id);
79
+ },
74
80
  },
75
81
  {
76
82
  key: 'agentWithMessage',
77
83
  label: <div>{t('exportType.agentWithMessage')}</div>,
84
+ onClick: () => {
85
+ exportSingleSession(id);
86
+ },
78
87
  },
79
88
  ],
80
89
  icon: <Icon icon={FolderOutput} />,
@@ -82,10 +91,24 @@ const SessionItem: FC<SessionItemProps> = memo(({ id, active = true, loading, pi
82
91
  label: t('export'),
83
92
  },
84
93
  {
94
+ danger: true,
85
95
  icon: <Icon icon={Trash} />,
86
96
  key: 'delete',
87
97
  label: t('delete'),
88
- onClick: () => setPopOpen(true),
98
+ onClick: ({ domEvent }) => {
99
+ domEvent.stopPropagation();
100
+
101
+ modal.confirm({
102
+ centered: true,
103
+ okButtonProps: { danger: true },
104
+ okText: t('ok'),
105
+ onOk: () => {
106
+ removeSession(id);
107
+ },
108
+ rootClassName: styles.modalRoot,
109
+ title: t('confirmRemoveSessionItemAlert'),
110
+ });
111
+ },
89
112
  },
90
113
  ],
91
114
  [id],
@@ -123,15 +146,6 @@ const SessionItem: FC<SessionItemProps> = memo(({ id, active = true, loading, pi
123
146
  {model}
124
147
  </Tag>
125
148
  )}
126
- {/*{showChatLength && (*/}
127
- {/* <Tag*/}
128
- {/* bordered={false}*/}
129
- {/* style={{ color: theme.colorTextSecondary, display: 'flex', gap: 4 }}*/}
130
- {/* >*/}
131
- {/* <Icon icon={LucideMessageCircle} />*/}
132
- {/* {chatLength}*/}
133
- {/* </Tag>*/}
134
- {/*)}*/}
135
149
  </Flexbox>
136
150
  )}
137
151
  </Flexbox>
@@ -140,42 +154,20 @@ const SessionItem: FC<SessionItemProps> = memo(({ id, active = true, loading, pi
140
154
  style={{ color: theme.colorText }}
141
155
  title={title}
142
156
  />
143
- <Popconfirm
144
- arrow={false}
145
- cancelText={t('cancel')}
146
- okButtonProps={{ danger: true }}
147
- okText={t('ok')}
148
- onCancel={() => setPopOpen(false)}
149
- onConfirm={(e) => {
150
- e?.stopPropagation();
151
- removeSession(id);
152
- setPopOpen(false);
153
- }}
154
- open={popOpen}
155
- overlayStyle={{ width: 280 }}
156
- title={t('confirmRemoveSessionItemAlert')}
157
- >
158
- <Dropdown
159
- arrow={false}
160
- menu={{ items }}
161
- onOpenChange={setDropdownOpen}
162
- open={dropdownOpen}
163
- trigger={['click']}
164
- >
165
- <ActionIcon
166
- className="session-remove"
167
- icon={MoreVertical}
168
- onClick={(e) => {
169
- e.preventDefault();
170
- e.stopPropagation();
171
- }}
172
- size={{
173
- blockSize: 28,
174
- fontSize: 16,
175
- }}
176
- />
177
- </Dropdown>
178
- </Popconfirm>
157
+ <Dropdown arrow={false} menu={{ items }} trigger={['click']}>
158
+ <ActionIcon
159
+ className="session-remove"
160
+ icon={MoreVertical}
161
+ onClick={(e) => {
162
+ e.preventDefault();
163
+ e.stopPropagation();
164
+ }}
165
+ size={{
166
+ blockSize: 28,
167
+ fontSize: 16,
168
+ }}
169
+ />
170
+ </Dropdown>
179
171
  </Flexbox>
180
172
  </div>
181
173
  );
@@ -52,6 +52,9 @@ export const useStyles = createStyles(({ css, token }, isHighlight: boolean) =>
52
52
  hover: css`
53
53
  background-color: ${token.colorFillSecondary};
54
54
  `,
55
+ modalRoot: css`
56
+ z-index: 2000;
57
+ `,
55
58
  pin: css`
56
59
  background-color: ${token.colorFillTertiary};
57
60
  `,
@@ -6,19 +6,32 @@ import { memo, useMemo } from 'react';
6
6
  import { useTranslation } from 'react-i18next';
7
7
 
8
8
  import HeaderTitle from '@/components/HeaderTitle';
9
+ import { exportSingleAgent, exportSingleSession } from '@/helpers/export';
10
+ import { useSessionStore } from '@/store/session';
9
11
 
10
12
  const Header = memo(() => {
11
13
  const { t } = useTranslation('setting');
14
+ const id = useSessionStore((s) => s.activeId);
12
15
 
13
- const items: MenuProps['items'] = useMemo(
16
+ const items = useMemo<MenuProps['items']>(
14
17
  () => [
15
18
  {
16
19
  key: 'agent',
17
20
  label: <div>{t('exportType.agent', { ns: 'common' })}</div>,
21
+ onClick: () => {
22
+ if (!id) return;
23
+
24
+ exportSingleAgent(id);
25
+ },
18
26
  },
19
27
  {
20
28
  key: 'agentWithMessage',
21
29
  label: <div>{t('exportType.agentWithMessage', { ns: 'common' })}</div>,
30
+ onClick: () => {
31
+ if (!id) return;
32
+
33
+ exportSingleSession(id);
34
+ },
22
35
  },
23
36
  ],
24
37
  [],
@@ -1,5 +1,5 @@
1
1
  import { Form, type ItemGroup, ThemeSwitch } from '@lobehub/ui';
2
- import { Form as AntForm, Button, Input, Popconfirm, Select, Switch } from 'antd';
2
+ import { Form as AntForm, App, Button, Input, Select, Switch } from 'antd';
3
3
  import isEqual from 'fast-deep-equal';
4
4
  import { debounce } from 'lodash-es';
5
5
  import { AppWindow, BrainCog, MessagesSquare, Palette, Webhook } from 'lucide-react';
@@ -11,6 +11,7 @@ import { shallow } from 'zustand/shallow';
11
11
  import { FORM_STYLE } from '@/const/layoutTokens';
12
12
  import AvatarWithUpload from '@/features/AvatarWithUpload';
13
13
  import { options } from '@/locales/options';
14
+ import { useSessionStore } from '@/store/session';
14
15
  import { settingsSelectors, useSettings } from '@/store/settings';
15
16
  import { DEFAULT_SETTINGS } from '@/store/settings/initialState';
16
17
  import { LanguageModel } from '@/types/llm';
@@ -27,6 +28,7 @@ type SettingItemGroup = ItemGroup & {
27
28
  const SettingForm = memo(() => {
28
29
  const { t } = useTranslation('setting');
29
30
  const [form] = AntForm.useForm();
31
+ const clearSessions = useSessionStore((s) => s.clearSessions);
30
32
  const settings = useSettings(settingsSelectors.currentSettings, isEqual);
31
33
  const { setThemeMode, setSettings, resetSettings } = useSettings(
32
34
  (s) => ({
@@ -37,14 +39,35 @@ const SettingForm = memo(() => {
37
39
  shallow,
38
40
  );
39
41
 
42
+ const { message, modal } = App.useApp();
40
43
  const handleReset = useCallback(() => {
41
- resetSettings();
42
- form.setFieldsValue(DEFAULT_SETTINGS);
44
+ modal.confirm({
45
+ cancelText: t('cancel', { ns: 'common' }),
46
+ centered: true,
47
+ okButtonProps: { danger: true },
48
+ okText: t('ok', { ns: 'common' }),
49
+ onOk: () => {
50
+ resetSettings();
51
+ form.setFieldsValue(DEFAULT_SETTINGS);
52
+ },
53
+ title: t('danger.reset.confirm'),
54
+ });
43
55
  }, []);
44
56
 
45
57
  const handleClear = useCallback(() => {
46
- handleReset();
47
- // TODO: 删除聊天记录
58
+ modal.confirm({
59
+ cancelText: t('cancel', { ns: 'common' }),
60
+ centered: true,
61
+ okButtonProps: {
62
+ danger: true,
63
+ },
64
+ okText: t('ok', { ns: 'common' }),
65
+ onOk: () => {
66
+ clearSessions();
67
+ message.success(t('danger.clear.success'));
68
+ },
69
+ title: t('danger.clear.confirm'),
70
+ });
48
71
  }, []);
49
72
 
50
73
  const theme: SettingItemGroup = useMemo(
@@ -240,18 +263,9 @@ const SettingForm = memo(() => {
240
263
  },
241
264
  {
242
265
  children: (
243
- <Popconfirm
244
- arrow={false}
245
- cancelText={t('cancel', { ns: 'common' })}
246
- okButtonProps={{ danger: true }}
247
- okText={t('ok', { ns: 'common' })}
248
- onConfirm={handleReset}
249
- title={t('danger.reset.confirm')}
250
- >
251
- <Button danger type="primary">
252
- {t('danger.reset.action')}
253
- </Button>
254
- </Popconfirm>
266
+ <Button danger onClick={handleReset} type="primary">
267
+ {t('danger.reset.action')}
268
+ </Button>
255
269
  ),
256
270
  desc: t('danger.reset.title'),
257
271
  label: t('danger.reset.desc'),
@@ -259,18 +273,9 @@ const SettingForm = memo(() => {
259
273
  },
260
274
  {
261
275
  children: (
262
- <Popconfirm
263
- arrow={false}
264
- cancelText={t('cancel', { ns: 'common' })}
265
- okButtonProps={{ danger: true }}
266
- okText={t('ok', { ns: 'common' })}
267
- onConfirm={handleClear}
268
- title={t('danger.clear.confirm')}
269
- >
270
- <Button danger type="primary">
271
- {t('danger.clear.action')}
272
- </Button>
273
- </Popconfirm>
276
+ <Button danger onClick={handleClear} type="primary">
277
+ {t('danger.clear.action')}
278
+ </Button>
274
279
  ),
275
280
  desc: t('danger.clear.title'),
276
281
  label: t('danger.clear.desc'),
@@ -11,18 +11,20 @@ import { SessionDispatch, sessionsReducer } from './reducers/session';
11
11
 
12
12
  export interface SessionAction {
13
13
  activeSession: (sessionId: string) => void;
14
+ clearSessions: () => void;
15
+
14
16
  /**
15
17
  * @title 添加会话
16
18
  * @param session - 会话信息
17
19
  * @returns void
18
20
  */
19
21
  createSession: () => Promise<string>;
20
-
21
22
  /**
22
23
  * 变更session
23
24
  * @param payload - 聊天记录
24
25
  */
25
26
  dispatchSession: (payload: SessionDispatch) => void;
27
+
26
28
  /**
27
29
  * 导入会话
28
30
  * @param sessions
@@ -34,14 +36,12 @@ export interface SessionAction {
34
36
  * @returns 压缩后的消息
35
37
  */
36
38
  // genShareUrl: () => string;
37
-
38
39
  /**
39
40
  * @title 删除会话
40
41
  * @param index - 会话索引
41
42
  * @returns void
42
43
  */
43
44
  removeSession: (sessionId: string) => void;
44
-
45
45
  /**
46
46
  * @title 切换会话
47
47
  * @param sessionId - 会话索引
@@ -60,6 +60,10 @@ export const createSessionSlice: StateCreator<
60
60
  set({ activeId: sessionId });
61
61
  },
62
62
 
63
+ clearSessions: () => {
64
+ set({ sessions: {} });
65
+ },
66
+
63
67
  createSession: async () => {
64
68
  const { dispatchSession, switchSession } = get();
65
69
 
@@ -100,7 +104,6 @@ export const createSessionSlice: StateCreator<
100
104
  }),
101
105
  });
102
106
  },
103
-
104
107
  removeSession: (sessionId) => {
105
108
  get().dispatchSession({ id: sessionId, type: 'removeSession' });
106
109
 
@@ -108,6 +111,7 @@ export const createSessionSlice: StateCreator<
108
111
  Router.push('/');
109
112
  }
110
113
  },
114
+
111
115
  switchSession: async (sessionId) => {
112
116
  if (get().activeId === sessionId) return;
113
117
 
@@ -118,7 +122,6 @@ export const createSessionSlice: StateCreator<
118
122
  // 新会话
119
123
  await Router.push(`/chat/${sessionId}`);
120
124
  },
121
-
122
125
  // genShareUrl: () => {
123
126
  // const session = sessionSelectors.currentSession(get());
124
127
  // if (!session) return '';
@@ -0,0 +1,23 @@
1
+ import { transform } from 'lodash-es';
2
+
3
+ import { SessionStore } from '@/store/session';
4
+ import { LobeAgentSession, LobeSessions } from '@/types/session';
5
+
6
+ import { getSessionById } from './list';
7
+
8
+ export const exportSessions = (s: SessionStore) => s.sessions;
9
+
10
+ // 排除 chats
11
+ export const exportAgents = (s: SessionStore) => {
12
+ return transform(s.sessions, (result: LobeSessions, value, key) => {
13
+ // 移除 chats 和 topics
14
+ result[key] = { ...value, chats: {}, topics: {} } as LobeAgentSession;
15
+ });
16
+ };
17
+ // 排除 chats
18
+ export const getExportAgent =
19
+ (id: string) =>
20
+ (s: SessionStore): LobeAgentSession => {
21
+ const session = getSessionById(id)(s);
22
+ return { ...session, chats: {}, topics: {} };
23
+ };
@@ -1,14 +1,18 @@
1
+ import { exportAgents, exportSessions, getExportAgent } from './export';
1
2
  import {
3
+ currentSession,
2
4
  currentSessionSafe,
3
- currentSessionSel,
4
5
  getSessionById,
5
6
  getSessionMetaById,
6
7
  sessionList,
7
8
  } from './list';
8
9
 
9
10
  export const sessionSelectors = {
10
- currentSession: currentSessionSel,
11
+ currentSession,
11
12
  currentSessionSafe,
13
+ exportAgents,
14
+ exportSessions,
15
+ getExportAgent,
12
16
  getSessionById,
13
17
  getSessionMetaById,
14
18
  sessionList,
@@ -5,13 +5,14 @@ import { filterWithKeywords } from '@/utils/filter';
5
5
 
6
6
  import { initLobeSession } from '../initialState';
7
7
 
8
- export const currentSessionSel = (s: SessionStore): LobeAgentSession | undefined => {
8
+ export const currentSession = (s: SessionStore): LobeAgentSession | undefined => {
9
9
  if (!s.activeId) return;
10
10
 
11
11
  return s.sessions[s.activeId];
12
12
  };
13
+
13
14
  export const currentSessionSafe = (s: SessionStore): LobeAgentSession => {
14
- return currentSessionSel(s) || initLobeSession;
15
+ return currentSession(s) || initLobeSession;
15
16
  };
16
17
 
17
18
  export const sessionList = (s: SessionStore) => {
@@ -49,7 +49,11 @@ export const createSettings: StateCreator<
49
49
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
50
50
  const { OPENAI_API_KEY: _, password: __, ...settings } = importSettings;
51
51
 
52
- setSettings(settings);
52
+ setSettings({
53
+ ...settings,
54
+ // 如果用户存在用户头像,那么不做导入
55
+ avatar: get().settings.avatar ?? settings.avatar,
56
+ });
53
57
  },
54
58
  resetSettings: () => {
55
59
  set({ settings: DEFAULT_SETTINGS });
@@ -1,6 +1,7 @@
1
1
  import { defaults } from 'lodash-es';
2
2
 
3
3
  import { DEFAULT_SETTINGS } from '@/store/settings/initialState';
4
+ import { GlobalSettings } from '@/types/settings';
4
5
 
5
6
  import { SettingsStore } from './store';
6
7
 
@@ -11,7 +12,16 @@ const selecThemeMode = (s: SettingsStore) => ({
11
12
  themeMode: s.settings.themeMode,
12
13
  });
13
14
 
15
+ export const exportSettings = (s: SettingsStore) => {
16
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
17
+ const { OPENAI_API_KEY: _, password: __, ...settings } = s.settings;
18
+
19
+ return settings as GlobalSettings;
20
+ };
21
+
14
22
  export const settingsSelectors = {
15
23
  currentSettings,
24
+ exportSettings,
25
+
16
26
  selecThemeMode,
17
27
  };
@@ -1,41 +1,61 @@
1
1
  import { LobeSessions } from '@/types/session';
2
2
  import { GlobalSettings } from '@/types/settings';
3
3
 
4
- export interface ConfigState {
4
+ // 存在4种导出方式
5
+ export type ExportType = 'agents' | 'sessions' | 'settings' | 'all';
6
+
7
+ // 4种方式对应的 state
8
+ export interface ConfigStateAll {
5
9
  sessions: LobeSessions;
6
10
  settings: GlobalSettings;
7
11
  }
8
-
9
- export interface SettingsConfigState {
12
+ export interface ConfigStateSettings {
10
13
  settings: GlobalSettings;
11
14
  }
12
- export interface SessionsConfigState {
15
+ export interface ConfigStateSessions {
13
16
  sessions: LobeSessions;
14
17
  }
15
18
 
16
- export type ExportType = 'agents' | 'sessions' | 'settings' | 'all';
17
-
18
- export type ConfigFile = ConfigFileSettings | ConfigFileSessions | ConfigFileAll | ConfigFileAgents;
19
-
19
+ // 4种方式对应的 file
20
20
  export interface ConfigFileSettings {
21
21
  exportType: 'settings';
22
- state: SettingsConfigState;
22
+ state: ConfigStateSettings;
23
23
  version: number;
24
24
  }
25
25
  export interface ConfigFileSessions {
26
26
  exportType: 'sessions';
27
- state: SessionsConfigState;
27
+ state: ConfigStateSessions;
28
28
  version: number;
29
29
  }
30
-
31
30
  export interface ConfigFileAgents {
32
31
  exportType: 'agents';
33
- state: SessionsConfigState;
32
+ state: ConfigStateSessions;
34
33
  version: number;
35
34
  }
36
-
37
35
  export interface ConfigFileAll {
38
36
  exportType: 'all';
39
- state: ConfigState;
37
+ state: ConfigStateAll;
40
38
  version: number;
41
39
  }
40
+
41
+ export type ConfigFile = ConfigFileSettings | ConfigFileSessions | ConfigFileAll | ConfigFileAgents;
42
+
43
+ // 用于 map 收集类型的 map
44
+ export interface ConfigModelMap {
45
+ agents: {
46
+ file: ConfigFileAgents;
47
+ state: ConfigStateSessions;
48
+ };
49
+ all: {
50
+ file: ConfigFileAll;
51
+ state: ConfigStateAll;
52
+ };
53
+ sessions: {
54
+ file: ConfigFileSessions;
55
+ state: ConfigStateSessions;
56
+ };
57
+ settings: {
58
+ file: ConfigFileSettings;
59
+ state: ConfigStateSettings;
60
+ };
61
+ }
@@ -1,7 +1,15 @@
1
1
  import { notification } from 'antd';
2
2
 
3
3
  import { CURRENT_CONFIG_VERSION, Migration } from '@/migrations';
4
- import { ConfigFile } from '@/types/exportConfig';
4
+ import {
5
+ ConfigFile,
6
+ ConfigFileAgents,
7
+ ConfigFileAll,
8
+ ConfigFileSessions,
9
+ ConfigFileSettings,
10
+ ConfigModelMap,
11
+ ExportType,
12
+ } from '@/types/exportConfig';
5
13
 
6
14
  export const exportConfigFile = (config: object, fileName?: string) => {
7
15
  const file = `LobeChat-${fileName || '-config'}-v${CURRENT_CONFIG_VERSION}.json`;
@@ -48,3 +56,48 @@ export const importConfigFile = (info: any, onConfigImport: (config: ConfigFile)
48
56
  //@ts-ignore file 类型不明确
49
57
  reader.readAsText(info.file.originFileObj, 'utf8');
50
58
  };
59
+
60
+ type CreateConfigFileState<T extends ExportType> = ConfigModelMap[T]['state'];
61
+
62
+ type CreateConfigFile<T extends ExportType> = ConfigModelMap[T]['file'];
63
+
64
+ export const createConfigFile = <T extends ExportType>(
65
+ type: T,
66
+ state: CreateConfigFileState<T>,
67
+ ): CreateConfigFile<T> => {
68
+ switch (type) {
69
+ case 'agents': {
70
+ return {
71
+ exportType: 'agents',
72
+ state,
73
+ version: Migration.targetVersion,
74
+ } as ConfigFileAgents;
75
+ }
76
+
77
+ case 'sessions': {
78
+ return {
79
+ exportType: 'sessions',
80
+ state,
81
+ version: Migration.targetVersion,
82
+ } as ConfigFileSessions;
83
+ }
84
+
85
+ case 'settings': {
86
+ return {
87
+ exportType: 'settings',
88
+ state,
89
+ version: Migration.targetVersion,
90
+ } as ConfigFileSettings;
91
+ }
92
+
93
+ case 'all': {
94
+ return {
95
+ exportType: 'all',
96
+ state,
97
+ version: Migration.targetVersion,
98
+ } as ConfigFileAll;
99
+ }
100
+ }
101
+
102
+ throw new Error('缺少正确的导出类型,请检查实现...');
103
+ };