@lobehub/chat 0.27.4 → 0.28.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,41 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ## [Version 0.28.0](https://github.com/lobehub/lobe-chat/compare/v0.27.4...v0.28.0)
6
+
7
+ <sup>Released on **2023-07-30**</sup>
8
+
9
+ #### ♻ Code Refactoring
10
+
11
+ - **misc**: 重构 settings 相关类型.
12
+
13
+ #### ✨ Features
14
+
15
+ - **misc**: 优化 SideBar 实现,激活态指示更加明确, 实现 session 导入功能, 实现配置导出功能.
16
+
17
+ <br/>
18
+
19
+ <details>
20
+ <summary><kbd>Improvements and Fixes</kbd></summary>
21
+
22
+ #### Code refactoring
23
+
24
+ - **misc**: 重构 settings 相关类型 ([6b7c0a0](https://github.com/lobehub/lobe-chat/commit/6b7c0a0))
25
+
26
+ #### What's improved
27
+
28
+ - **misc**: 优化 SideBar 实现,激活态指示更加明确 ([8a467df](https://github.com/lobehub/lobe-chat/commit/8a467df))
29
+ - **misc**: 实现 session 导入功能 ([5650167](https://github.com/lobehub/lobe-chat/commit/5650167))
30
+ - **misc**: 实现配置导出功能 ([c1f73fe](https://github.com/lobehub/lobe-chat/commit/c1f73fe))
31
+
32
+ </details>
33
+
34
+ <div align="right">
35
+
36
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
37
+
38
+ </div>
39
+
5
40
  ### [Version 0.27.4](https://github.com/lobehub/lobe-chat/compare/v0.27.3...v0.27.4)
6
41
 
7
42
  <sup>Released on **2023-07-29**</sup>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "0.27.4",
3
+ "version": "0.28.0",
4
4
  "description": "Lobe Chat is an open-source chatbot client using LangChain, Typescript and Next.js",
5
5
  "keywords": [
6
6
  "chatbot",
@@ -0,0 +1,112 @@
1
+ import { ActionIcon, Icon } from '@lobehub/ui';
2
+ import { Dropdown, MenuProps, Upload } from 'antd';
3
+ import {
4
+ Feather,
5
+ FileClock,
6
+ FolderInput,
7
+ FolderOutput,
8
+ Github,
9
+ Heart,
10
+ Settings,
11
+ } from 'lucide-react';
12
+ import Router from 'next/router';
13
+ import { ReactNode, memo, useMemo } from 'react';
14
+ import { useTranslation } from 'react-i18next';
15
+
16
+ import { useExportConfig } from '@/hooks/useExportConfig';
17
+ import { useImportConfig } from '@/hooks/useImportConfig';
18
+
19
+ import pkg from '../../../package.json';
20
+
21
+ const BottomAction = memo<{ children: ReactNode }>(({ children }) => {
22
+ const { t } = useTranslation('common');
23
+
24
+ const { exportSessions, exportSettings, exportAll, exportAgents } = useExportConfig();
25
+
26
+ const { importConfig } = useImportConfig();
27
+ const items: MenuProps['items'] = useMemo(
28
+ () => [
29
+ {
30
+ icon: <Icon icon={FolderInput} />,
31
+ key: 'import',
32
+ label: (
33
+ <Upload maxCount={1} onChange={importConfig} showUploadList={false}>
34
+ {t('import')}
35
+ </Upload>
36
+ ),
37
+ },
38
+ {
39
+ children: [
40
+ {
41
+ key: 'allAgent',
42
+ label: <div>{t('exportType.allAgent')}</div>,
43
+ onClick: exportAgents,
44
+ },
45
+ {
46
+ key: 'allAgentWithMessage',
47
+ label: <div>{t('exportType.allAgentWithMessage')}</div>,
48
+ onClick: exportSessions,
49
+ },
50
+ {
51
+ key: 'globalSetting',
52
+ label: <div>{t('exportType.globalSetting')}</div>,
53
+ onClick: exportSettings,
54
+ },
55
+ {
56
+ type: 'divider',
57
+ },
58
+ {
59
+ key: 'all',
60
+ label: <div>{t('exportType.all')}</div>,
61
+ onClick: exportAll,
62
+ },
63
+ ],
64
+ icon: <Icon icon={FolderOutput} />,
65
+ key: 'export',
66
+ label: t('export'),
67
+ },
68
+ {
69
+ type: 'divider',
70
+ },
71
+ {
72
+ icon: <Icon icon={Feather} />,
73
+ key: 'feedback',
74
+ label: t('feedback'),
75
+ onClick: () => window.open(pkg.bugs.url, '__blank'),
76
+ },
77
+ {
78
+ icon: <Icon icon={FileClock} />,
79
+ key: 'changelog',
80
+ label: t('changelog'),
81
+ onClick: () => window.open(`${pkg.homepage}/blob/master/CHANGELOG.md`, '__blank'),
82
+ },
83
+ {
84
+ icon: <Icon icon={Heart} />,
85
+ key: 'about',
86
+ label: t('about'),
87
+ onClick: () => window.open(pkg.homepage, '__blank'),
88
+ },
89
+ {
90
+ type: 'divider',
91
+ },
92
+ {
93
+ icon: <Icon icon={Settings} />,
94
+ key: 'setting',
95
+ label: t('setting'),
96
+ onClick: () => Router.push('/setting'),
97
+ },
98
+ ],
99
+ [],
100
+ );
101
+
102
+ return (
103
+ <>
104
+ <ActionIcon icon={Github} onClick={() => window.open(pkg.homepage, '__blank')} />
105
+ <Dropdown arrow={false} menu={{ items }} trigger={['click']}>
106
+ {children}
107
+ </Dropdown>
108
+ </>
109
+ );
110
+ });
111
+
112
+ export default BottomAction;
@@ -1,106 +1,24 @@
1
- import { ActionIcon, Icon, SideNav } from '@lobehub/ui';
2
- import { Dropdown, type MenuProps } from 'antd';
3
- import {
4
- Feather,
5
- FileClock,
6
- FolderInput,
7
- FolderOutput,
8
- Github,
9
- Heart,
10
- MessageSquare,
11
- Settings,
12
- Settings2,
13
- Sticker,
14
- } from 'lucide-react';
1
+ import { ActionIcon, SideNav } from '@lobehub/ui';
2
+ import { MessageSquare, Settings2, Sticker } from 'lucide-react';
15
3
  import Router from 'next/router';
16
- import { memo, useMemo } from 'react';
17
- import { useTranslation } from 'react-i18next';
4
+ import { memo } from 'react';
18
5
  import { shallow } from 'zustand/shallow';
19
6
 
20
- import pkg from '@/../package.json';
21
7
  import AvatarWithUpload from '@/features/AvatarWithUpload';
22
8
  import { useSettings } from '@/store/settings';
23
9
 
10
+ import BottomAction from './BottomAction';
11
+
24
12
  export default memo(() => {
25
13
  const [tab, setTab] = useSettings((s) => [s.sidebarKey, s.switchSideBar], shallow);
26
- const { t } = useTranslation('common');
27
- const items: MenuProps['items'] = useMemo(
28
- () => [
29
- {
30
- icon: <Icon icon={FolderInput} />,
31
- key: 'import',
32
- label: <div>{t('import')}</div>,
33
- },
34
- {
35
- children: [
36
- {
37
- key: 'allAgent',
38
- label: <div>{t('exportType.allAgent')}</div>,
39
- },
40
- {
41
- key: 'allAgentWithMessage',
42
- label: <div>{t('exportType.allAgentWithMessage')}</div>,
43
- },
44
- {
45
- key: 'globalSetting',
46
- label: <div>{t('exportType.globalSetting')}</div>,
47
- },
48
- {
49
- type: 'divider',
50
- },
51
- {
52
- key: 'all',
53
- label: <div>{t('exportType.all')}</div>,
54
- },
55
- ],
56
- icon: <Icon icon={FolderOutput} />,
57
- key: 'export',
58
- label: t('export'),
59
- },
60
- {
61
- type: 'divider',
62
- },
63
- {
64
- icon: <Icon icon={Feather} />,
65
- key: 'feedback',
66
- label: t('feedback'),
67
- onClick: () => window.open(pkg.bugs.url, '__blank'),
68
- },
69
- {
70
- icon: <Icon icon={FileClock} />,
71
- key: 'changelog',
72
- label: t('changelog'),
73
- onClick: () => window.open(`${pkg.homepage}/blob/master/CHANGELOG.md`, '__blank'),
74
- },
75
- {
76
- icon: <Icon icon={Heart} />,
77
- key: 'about',
78
- label: t('about'),
79
- onClick: () => window.open(pkg.homepage, '__blank'),
80
- },
81
- {
82
- type: 'divider',
83
- },
84
- {
85
- icon: <Icon icon={Settings} />,
86
- key: 'setting',
87
- label: t('setting'),
88
- onClick: () => Router.push('/setting'),
89
- },
90
- ],
91
- [],
92
- );
93
14
 
94
15
  return (
95
16
  <SideNav
96
17
  avatar={<AvatarWithUpload />}
97
18
  bottomActions={
98
- <>
99
- <ActionIcon icon={Github} onClick={() => window.open(pkg.homepage, '__blank')} />
100
- <Dropdown arrow={false} menu={{ items }} trigger={['click']}>
101
- <ActionIcon icon={Settings2} />
102
- </Dropdown>
103
- </>
19
+ <BottomAction>
20
+ <ActionIcon active={tab === 'setting'} icon={Settings2} />
21
+ </BottomAction>
104
22
  }
105
23
  style={{ height: '100vh' }}
106
24
  topActions={
@@ -108,7 +26,12 @@ export default memo(() => {
108
26
  <ActionIcon
109
27
  active={tab === 'chat'}
110
28
  icon={MessageSquare}
111
- onClick={() => setTab('chat')}
29
+ onClick={() => {
30
+ // 如果已经在 chat 路径下了,那么就不用再跳转了
31
+ if (Router.asPath.startsWith('/chat')) return;
32
+
33
+ Router.push('/');
34
+ }}
112
35
  size="large"
113
36
  />
114
37
  <ActionIcon
@@ -0,0 +1,69 @@
1
+ import { transform } from 'lodash-es';
2
+ import { useMemo } from 'react';
3
+ import { shallow } from 'zustand/shallow';
4
+
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';
15
+
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
+ };
@@ -0,0 +1,36 @@
1
+ import { useMemo } from 'react';
2
+
3
+ import { useSessionStore } from '@/store/session';
4
+ import { useSettings } from '@/store/settings';
5
+ import { importConfigFile } from '@/utils/config';
6
+
7
+ export const useImportConfig = () => {
8
+ const importSessions = useSessionStore((s) => s.importSessions);
9
+ const importSettings = useSettings((s) => s.importSettings);
10
+
11
+ const importConfig = (info: any) => {
12
+ importConfigFile(info, (config) => {
13
+ switch (config.exportType) {
14
+ case 'settings': {
15
+ importSettings(config.state.settings);
16
+ break;
17
+ }
18
+
19
+ case 'sessions':
20
+ case 'agents': {
21
+ importSessions(config.state.sessions);
22
+ break;
23
+ }
24
+
25
+ case 'all': {
26
+ importSessions(config.state.sessions);
27
+ importSettings(config.state.settings);
28
+
29
+ break;
30
+ }
31
+ }
32
+ });
33
+ };
34
+
35
+ return useMemo(() => ({ importConfig }), []);
36
+ };
@@ -13,8 +13,8 @@ import AvatarWithUpload from '@/features/AvatarWithUpload';
13
13
  import { options } from '@/locales/options';
14
14
  import { settingsSelectors, useSettings } from '@/store/settings';
15
15
  import { DEFAULT_SETTINGS } from '@/store/settings/initialState';
16
- import { ConfigKeys } from '@/types/exportConfig';
17
16
  import { LanguageModel } from '@/types/llm';
17
+ import { ConfigKeys } from '@/types/settings';
18
18
 
19
19
  import { ThemeSwatchesNeutral, ThemeSwatchesPrimary } from './ThemeSwatches';
20
20
 
@@ -5,6 +5,7 @@ import { Flexbox } from 'react-layout-kit';
5
5
 
6
6
  import SideBar from '@/features/SideBar';
7
7
  import { createI18nNext } from '@/locales/create';
8
+ import { useSettings } from '@/store/settings';
8
9
  import { genSiteHeadTitle } from '@/utils/genSiteHeadTitle';
9
10
 
10
11
  const initI18n = createI18nNext('setting');
@@ -17,6 +18,13 @@ const SettingLayout = memo<{ children: ReactNode }>(({ children }) => {
17
18
  initI18n.finally();
18
19
  }, []);
19
20
 
21
+ useEffect(() => {
22
+ const hasRehydrated = useSettings.persist.hasHydrated();
23
+ if (hasRehydrated) {
24
+ useSettings.setState({ sidebarKey: 'setting' });
25
+ }
26
+ }, []);
27
+
20
28
  return (
21
29
  <>
22
30
  <Head>
@@ -1,9 +1,10 @@
1
+ import { produce } from 'immer';
1
2
  import { merge } from 'lodash-es';
2
3
  import Router from 'next/router';
3
4
  import { StateCreator } from 'zustand/vanilla';
4
5
 
5
6
  import { SessionStore, initLobeSession } from '@/store/session';
6
- import { LobeAgentSession } from '@/types/session';
7
+ import { LobeAgentSession, LobeSessions } from '@/types/session';
7
8
  import { uuid } from '@/utils/uuid';
8
9
 
9
10
  import { SessionDispatch, sessionsReducer } from './reducers/session';
@@ -18,10 +19,22 @@ export interface SessionAction {
18
19
  createSession: () => Promise<string>;
19
20
 
20
21
  /**
21
- * 分发聊天记录
22
+ * 变更session
22
23
  * @param payload - 聊天记录
23
24
  */
24
25
  dispatchSession: (payload: SessionDispatch) => void;
26
+ /**
27
+ * 导入会话
28
+ * @param sessions
29
+ */
30
+ importSessions: (sessions: LobeSessions) => void;
31
+
32
+ /**
33
+ * 生成压缩后的消息
34
+ * @returns 压缩后的消息
35
+ */
36
+ // genShareUrl: () => string;
37
+
25
38
  /**
26
39
  * @title 删除会话
27
40
  * @param index - 会话索引
@@ -35,12 +48,6 @@ export interface SessionAction {
35
48
  * @returns void
36
49
  */
37
50
  switchSession: (sessionId?: string | 'new') => Promise<void>;
38
-
39
- /**
40
- * 生成压缩后的消息
41
- * @returns 压缩后的消息
42
- */
43
- // genShareUrl: () => string;
44
51
  }
45
52
 
46
53
  export const createSessionSlice: StateCreator<
@@ -80,6 +87,20 @@ export const createSessionSlice: StateCreator<
80
87
  });
81
88
  },
82
89
 
90
+ importSessions: (importSessions) => {
91
+ const { sessions } = get();
92
+ set({
93
+ sessions: produce(sessions, (draft) => {
94
+ for (const [id, session] of Object.entries(importSessions)) {
95
+ // 如果已经存在,则跳过
96
+ if (draft[id]) continue;
97
+
98
+ draft[id] = session;
99
+ }
100
+ }),
101
+ });
102
+ },
103
+
83
104
  removeSession: (sessionId) => {
84
105
  get().dispatchSession({ id: sessionId, type: 'removeSession' });
85
106
 
@@ -87,7 +108,6 @@ export const createSessionSlice: StateCreator<
87
108
  Router.push('/');
88
109
  }
89
110
  },
90
-
91
111
  switchSession: async (sessionId) => {
92
112
  if (get().activeId === sessionId) return;
93
113
 
@@ -1,16 +1,17 @@
1
1
  import { ThemeMode } from 'antd-style';
2
2
  import type { StateCreator } from 'zustand/vanilla';
3
3
 
4
- import type { ConfigSettings } from '@/types/exportConfig';
4
+ import type { GlobalSettings } from '@/types/settings';
5
5
 
6
6
  import type { SidebarTabKey } from './initialState';
7
- import { DEFAULT_SETTINGS, GlobalSettingsState } from './initialState';
7
+ import { DEFAULT_SETTINGS, SettingsState } from './initialState';
8
8
  import type { SettingsStore } from './store';
9
9
 
10
10
  /**
11
11
  * 设置操作
12
12
  */
13
13
  export interface SettingsAction {
14
+ importSettings: (settings: GlobalSettings) => void;
14
15
  /**
15
16
  * 重置设置
16
17
  */
@@ -19,12 +20,12 @@ export interface SettingsAction {
19
20
  * 设置全局设置
20
21
  * @param settings - 全局设置
21
22
  */
22
- setGlobalSettings: (settings: GlobalSettingsState) => void;
23
+ setGlobalSettings: (settings: SettingsState) => void;
23
24
  /**
24
25
  * 设置部分配置设置
25
26
  * @param settings - 部分配置设置
26
27
  */
27
- setSettings: (settings: Partial<ConfigSettings>) => void;
28
+ setSettings: (settings: Partial<GlobalSettings>) => void;
28
29
  /**
29
30
  * 设置主题模式
30
31
  * @param themeMode - 主题模式
@@ -43,6 +44,13 @@ export const createSettings: StateCreator<
43
44
  [],
44
45
  SettingsAction
45
46
  > = (set, get) => ({
47
+ importSettings: (importSettings) => {
48
+ const { setSettings } = get();
49
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
50
+ const { OPENAI_API_KEY: _, password: __, ...settings } = importSettings;
51
+
52
+ setSettings(settings);
53
+ },
46
54
  resetSettings: () => {
47
55
  set({ settings: DEFAULT_SETTINGS });
48
56
  },
@@ -1,9 +1,9 @@
1
- import type { ConfigSettings } from '@/types/exportConfig';
2
1
  import { LanguageModel } from '@/types/llm';
2
+ import type { GlobalSettings } from '@/types/settings';
3
3
 
4
- export type SidebarTabKey = 'chat' | 'market';
4
+ export type SidebarTabKey = 'chat' | 'market' | 'setting';
5
5
 
6
- export const DEFAULT_SETTINGS: ConfigSettings = {
6
+ export const DEFAULT_SETTINGS: GlobalSettings = {
7
7
  OPENAI_API_KEY: '',
8
8
  avatar: '',
9
9
  compressThreshold: 24,
@@ -26,15 +26,15 @@ export const DEFAULT_SETTINGS: ConfigSettings = {
26
26
  topP: 1,
27
27
  };
28
28
 
29
- export interface GlobalSettingsState {
29
+ export interface SettingsState {
30
30
  inputHeight: number;
31
31
  sessionExpandable?: boolean;
32
32
  sessionsWidth: number;
33
- settings: ConfigSettings;
33
+ settings: GlobalSettings;
34
34
  sidebarKey: SidebarTabKey;
35
35
  }
36
36
 
37
- export const initialState: GlobalSettingsState = {
37
+ export const initialState: SettingsState = {
38
38
  inputHeight: 200,
39
39
  sessionExpandable: true,
40
40
  sessionsWidth: 320,
@@ -1,9 +1,9 @@
1
1
  import { StateCreator } from 'zustand/vanilla';
2
2
 
3
3
  import { type SettingsAction, createSettings } from './action';
4
- import { type GlobalSettingsState, initialState } from './initialState';
4
+ import { type SettingsState, initialState } from './initialState';
5
5
 
6
- export type SettingsStore = SettingsAction & GlobalSettingsState;
6
+ export type SettingsStore = SettingsAction & SettingsState;
7
7
 
8
8
  export const createStore: StateCreator<SettingsStore, [['zustand/devtools', never]]> = (
9
9
  ...parameters
@@ -1,46 +1,41 @@
1
- import type { NeutralColors, PrimaryColors } from '@lobehub/ui';
2
- import { ThemeMode } from 'antd-style';
1
+ import { LobeSessions } from '@/types/session';
2
+ import { GlobalSettings } from '@/types/settings';
3
3
 
4
- import { LanguageModel } from '@/types/llm';
5
-
6
- import { Locales } from './locale';
4
+ export interface ConfigState {
5
+ sessions: LobeSessions;
6
+ settings: GlobalSettings;
7
+ }
7
8
 
8
- /**
9
- * 配置设置
10
- */
11
- export interface ConfigSettings {
12
- OPENAI_API_KEY: string;
13
- avatar: string;
14
- compressThreshold: number;
15
- enableCompressThreshold: boolean;
16
- enableHistoryCount: boolean;
17
- enableMaxTokens: boolean;
18
- endpoint: string;
19
- fontSize: number;
20
- frequencyPenalty: number;
21
- historyCount: number;
22
- language: Locales;
23
- maxTokens: number;
24
- model: LanguageModel;
25
- neutralColor: NeutralColors | '';
26
- password: string;
27
- presencePenalty: number;
28
- primaryColor: PrimaryColors | '';
29
- temperature: number;
30
- themeMode: ThemeMode;
31
- topP: number;
9
+ export interface SettingsConfigState {
10
+ settings: GlobalSettings;
11
+ }
12
+ export interface SessionsConfigState {
13
+ sessions: LobeSessions;
32
14
  }
33
15
 
34
- export type ConfigKeys = keyof ConfigSettings;
16
+ export type ExportType = 'agents' | 'sessions' | 'settings' | 'all';
35
17
 
36
- export interface ConfigState {
37
- settings: ConfigSettings;
18
+ export type ConfigFile = ConfigFileSettings | ConfigFileSessions | ConfigFileAll | ConfigFileAgents;
19
+
20
+ export interface ConfigFileSettings {
21
+ exportType: 'settings';
22
+ state: SettingsConfigState;
23
+ version: number;
24
+ }
25
+ export interface ConfigFileSessions {
26
+ exportType: 'sessions';
27
+ state: SessionsConfigState;
28
+ version: number;
29
+ }
30
+
31
+ export interface ConfigFileAgents {
32
+ exportType: 'agents';
33
+ state: SessionsConfigState;
34
+ version: number;
38
35
  }
39
36
 
40
- export interface ConfigFile {
37
+ export interface ConfigFileAll {
38
+ exportType: 'all';
41
39
  state: ConfigState;
42
- /**
43
- * 配置文件的版本号
44
- */
45
40
  version: number;
46
41
  }
@@ -0,0 +1,32 @@
1
+ import { NeutralColors, PrimaryColors } from '@lobehub/ui';
2
+ import { ThemeMode } from 'antd-style';
3
+
4
+ import { Locales } from '@/locales/resources';
5
+ import { LanguageModel } from '@/types/llm';
6
+
7
+ /**
8
+ * 配置设置
9
+ */
10
+ export interface GlobalSettings {
11
+ OPENAI_API_KEY: string;
12
+ avatar: string;
13
+ compressThreshold: number;
14
+ enableCompressThreshold: boolean;
15
+ enableHistoryCount: boolean;
16
+ enableMaxTokens: boolean;
17
+ endpoint: string;
18
+ fontSize: number;
19
+ frequencyPenalty: number;
20
+ historyCount: number;
21
+ language: Locales;
22
+ maxTokens: number;
23
+ model: LanguageModel;
24
+ neutralColor: NeutralColors | '';
25
+ password: string;
26
+ presencePenalty: number;
27
+ primaryColor: PrimaryColors | '';
28
+ temperature: number;
29
+ themeMode: ThemeMode;
30
+ topP: number;
31
+ }
32
+ export type ConfigKeys = keyof GlobalSettings;
@@ -0,0 +1,50 @@
1
+ import { notification } from 'antd';
2
+
3
+ import { CURRENT_CONFIG_VERSION, Migration } from '@/migrations';
4
+ import { ConfigFile } from '@/types/exportConfig';
5
+
6
+ export const exportConfigFile = (config: object, fileName?: string) => {
7
+ const file = `LobeChat-${fileName || '-config'}-v${CURRENT_CONFIG_VERSION}.json`;
8
+
9
+ // 创建一个 Blob 对象
10
+ const blob = new Blob([JSON.stringify(config)], { type: 'application/json' });
11
+
12
+ // 创建一个 URL 对象,用于下载
13
+ const url = URL.createObjectURL(blob);
14
+
15
+ // 创建一个 <a> 元素,设置下载链接和文件名
16
+ const a = document.createElement('a');
17
+ a.href = url;
18
+ a.download = file;
19
+
20
+ // 触发 <a> 元素的点击事件,开始下载
21
+ document.body.append(a);
22
+ a.click();
23
+
24
+ // 下载完成后,清除 URL 对象
25
+ URL.revokeObjectURL(url);
26
+ a.remove();
27
+ };
28
+
29
+ export const importConfigFile = (info: any, onConfigImport: (config: ConfigFile) => void) => {
30
+ const reader = new FileReader();
31
+ //读取完文件之后的回调函数
32
+ reader.onloadend = function (evt) {
33
+ const fileString = evt.target?.result;
34
+ const fileJson = fileString as string;
35
+
36
+ try {
37
+ const config = JSON.parse(fileJson);
38
+ const { state } = Migration.migrate(config);
39
+
40
+ onConfigImport({ ...config, state });
41
+ } catch (error) {
42
+ notification.error({
43
+ description: `出错原因: ${(error as Error).message}`,
44
+ message: '导入失败',
45
+ });
46
+ }
47
+ };
48
+ //@ts-ignore file 类型不明确
49
+ reader.readAsText(info.file.originFileObj, 'utf8');
50
+ };