mirror of
https://github.com/lobehub/lobehub.git
synced 2026-03-27 13:29:15 +07:00
✨ feat: 实现单个会话和角色的导出功能
This commit is contained in:
62
src/helpers/export.ts
Normal file
62
src/helpers/export.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { sessionSelectors, useSessionStore } from '@/store/session';
|
||||
import { settingsSelectors, useSettings } from '@/store/settings';
|
||||
import { createConfigFile, exportConfigFile } from '@/utils/config';
|
||||
|
||||
const getAgents = () => sessionSelectors.exportAgents(useSessionStore.getState());
|
||||
const getAgent = (id: string) => sessionSelectors.getExportAgent(id)(useSessionStore.getState());
|
||||
|
||||
const getSessions = () => sessionSelectors.exportSessions(useSessionStore.getState());
|
||||
const getSession = (id: string) => sessionSelectors.getSessionById(id)(useSessionStore.getState());
|
||||
|
||||
const getSettings = () => settingsSelectors.exportSettings(useSettings.getState());
|
||||
|
||||
export const exportAgents = () => {
|
||||
const sessions = getAgents();
|
||||
|
||||
const config = createConfigFile('agents', { sessions });
|
||||
|
||||
exportConfigFile(config, 'agents');
|
||||
};
|
||||
|
||||
export const exportSingleAgent = (id: string) => {
|
||||
const agent = getAgent(id);
|
||||
if (!agent) return;
|
||||
|
||||
const config = createConfigFile('agents', { sessions: { [id]: agent } });
|
||||
|
||||
exportConfigFile(config, agent.meta?.title || 'agent');
|
||||
};
|
||||
|
||||
export const exportSessions = () => {
|
||||
const sessions = getSessions();
|
||||
const config = createConfigFile('sessions', { sessions });
|
||||
|
||||
exportConfigFile(config, 'sessions');
|
||||
};
|
||||
|
||||
export const exportSingleSession = (id: string) => {
|
||||
const session = getSession(id);
|
||||
if (!session) return;
|
||||
const sessions = { [id]: session };
|
||||
|
||||
const config = createConfigFile('sessions', { sessions });
|
||||
|
||||
exportConfigFile(config, `${session.meta?.title}-session`);
|
||||
};
|
||||
|
||||
export const exportSettings = () => {
|
||||
const settings = getSettings();
|
||||
|
||||
const config = createConfigFile('settings', { settings });
|
||||
|
||||
exportConfigFile(config, 'settings');
|
||||
};
|
||||
|
||||
export const exportAll = () => {
|
||||
const sessions = getSessions();
|
||||
const settings = getSettings();
|
||||
|
||||
const config = createConfigFile('all', { sessions, settings });
|
||||
|
||||
exportConfigFile(config, 'config');
|
||||
};
|
||||
@@ -1,69 +1,6 @@
|
||||
import { transform } from 'lodash-es';
|
||||
import { useMemo } from 'react';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
|
||||
import { Migration } from '@/migrations';
|
||||
import { useSessionStore } from '@/store/session';
|
||||
import { useSettings } from '@/store/settings';
|
||||
import {
|
||||
ConfigFileAgents,
|
||||
ConfigFileAll,
|
||||
ConfigFileSessions,
|
||||
ConfigFileSettings,
|
||||
} from '@/types/exportConfig';
|
||||
import { exportConfigFile } from '@/utils/config';
|
||||
import { exportAgents, exportAll, exportSessions, exportSettings } from '@/helpers/export';
|
||||
|
||||
export const useExportConfig = () => {
|
||||
const [sessions] = useSessionStore((s) => [s.sessions], shallow);
|
||||
|
||||
const [settings] = useSettings((s) => [s.settings, s.importSettings], shallow);
|
||||
|
||||
const exportAgents = () => {
|
||||
const config: ConfigFileAgents = {
|
||||
exportType: 'agents',
|
||||
state: {
|
||||
sessions: transform(sessions, (result, value, key) => {
|
||||
result[key] = { ...value, chats: {}, topics: {} };
|
||||
}),
|
||||
},
|
||||
version: Migration.targetVersion,
|
||||
};
|
||||
|
||||
exportConfigFile(config, 'agents');
|
||||
};
|
||||
|
||||
const exportSessions = () => {
|
||||
const config: ConfigFileSessions = {
|
||||
exportType: 'sessions',
|
||||
state: { sessions },
|
||||
version: Migration.targetVersion,
|
||||
};
|
||||
|
||||
exportConfigFile(config, 'sessions');
|
||||
};
|
||||
|
||||
const exportSettings = () => {
|
||||
const config: ConfigFileSettings = {
|
||||
exportType: 'settings',
|
||||
state: { settings },
|
||||
version: Migration.targetVersion,
|
||||
};
|
||||
|
||||
exportConfigFile(config, 'settings');
|
||||
};
|
||||
|
||||
const exportAll = () => {
|
||||
// 将 入参转换为 配置文件格式
|
||||
const config: ConfigFileAll = {
|
||||
exportType: 'all',
|
||||
state: { sessions, settings },
|
||||
version: Migration.targetVersion,
|
||||
};
|
||||
exportConfigFile(config, 'config');
|
||||
};
|
||||
|
||||
return useMemo(
|
||||
() => ({ exportAgents, exportAll, exportSessions, exportSettings }),
|
||||
[sessions, settings],
|
||||
);
|
||||
};
|
||||
export const useExportConfig = () =>
|
||||
useMemo(() => ({ exportAgents, exportAll, exportSessions, exportSettings }), []);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ConfigState } from '@/types/exportConfig';
|
||||
import { ConfigStateAll } from '@/types/exportConfig';
|
||||
import { VersionController } from '@/utils/VersionController';
|
||||
|
||||
// 当前最新的版本号
|
||||
@@ -7,7 +7,7 @@ export const CURRENT_CONFIG_VERSION = 1;
|
||||
// 历史记录版本升级模块
|
||||
export const ConfigMigrations = [];
|
||||
|
||||
export const Migration = new VersionController<ConfigState>(
|
||||
export const Migration = new VersionController<ConfigStateAll>(
|
||||
ConfigMigrations,
|
||||
CURRENT_CONFIG_VERSION,
|
||||
);
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { ActionIcon, Avatar, Icon, List } from '@lobehub/ui';
|
||||
import { useHover } from 'ahooks';
|
||||
import { Dropdown, type MenuProps, Popconfirm, Tag } from 'antd';
|
||||
import { App, Dropdown, type MenuProps, Tag } from 'antd';
|
||||
import { FolderOutput, MoreVertical, Pin, PinOff, Trash } from 'lucide-react';
|
||||
import { FC, memo, useMemo, useRef, useState } from 'react';
|
||||
import { FC, memo, useMemo, useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Flexbox } from 'react-layout-kit';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
|
||||
import { exportSingleAgent, exportSingleSession } from '@/helpers/export';
|
||||
import { agentSelectors, chatSelectors, sessionSelectors, useSessionStore } from '@/store/session';
|
||||
import { useSettings } from '@/store/settings';
|
||||
|
||||
@@ -24,10 +25,10 @@ interface SessionItemProps {
|
||||
const SessionItem: FC<SessionItemProps> = memo(({ id, active = true, loading, pin }) => {
|
||||
const ref = useRef(null);
|
||||
const isHovering = useHover(ref);
|
||||
const [popOpen, setPopOpen] = useState(false);
|
||||
const [dropdownOpen, setDropdownOpen] = useState(false);
|
||||
|
||||
const { t } = useTranslation('common');
|
||||
const isHighlight = isHovering || dropdownOpen;
|
||||
const isHighlight = isHovering;
|
||||
|
||||
const { styles, theme, cx } = useStyles(isHighlight);
|
||||
const [defaultModel] = useSettings((s) => [s.settings.model], shallow);
|
||||
|
||||
@@ -58,23 +59,31 @@ const SessionItem: FC<SessionItemProps> = memo(({ id, active = true, loading, pi
|
||||
];
|
||||
}, shallow);
|
||||
|
||||
// TODO: 动作绑定
|
||||
const { modal } = App.useApp();
|
||||
const items: MenuProps['items'] = useMemo(
|
||||
() => [
|
||||
{
|
||||
icon: <Icon icon={pin ? PinOff : Pin} />,
|
||||
key: 'pin',
|
||||
label: t(pin ? 'pinOff' : 'pin'),
|
||||
// TODO: 动作绑定
|
||||
onClick: () => {},
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
key: 'agent',
|
||||
label: <div>{t('exportType.agent')}</div>,
|
||||
onClick: () => {
|
||||
exportSingleAgent(id);
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'agentWithMessage',
|
||||
label: <div>{t('exportType.agentWithMessage')}</div>,
|
||||
onClick: () => {
|
||||
exportSingleSession(id);
|
||||
},
|
||||
},
|
||||
],
|
||||
icon: <Icon icon={FolderOutput} />,
|
||||
@@ -82,10 +91,24 @@ const SessionItem: FC<SessionItemProps> = memo(({ id, active = true, loading, pi
|
||||
label: t('export'),
|
||||
},
|
||||
{
|
||||
danger: true,
|
||||
icon: <Icon icon={Trash} />,
|
||||
key: 'delete',
|
||||
label: t('delete'),
|
||||
onClick: () => setPopOpen(true),
|
||||
onClick: ({ domEvent }) => {
|
||||
domEvent.stopPropagation();
|
||||
|
||||
modal.confirm({
|
||||
centered: true,
|
||||
okButtonProps: { danger: true },
|
||||
okText: t('ok'),
|
||||
onOk: () => {
|
||||
removeSession(id);
|
||||
},
|
||||
rootClassName: styles.modalRoot,
|
||||
title: t('confirmRemoveSessionItemAlert'),
|
||||
});
|
||||
},
|
||||
},
|
||||
],
|
||||
[id],
|
||||
@@ -123,15 +146,6 @@ const SessionItem: FC<SessionItemProps> = memo(({ id, active = true, loading, pi
|
||||
{model}
|
||||
</Tag>
|
||||
)}
|
||||
{/*{showChatLength && (*/}
|
||||
{/* <Tag*/}
|
||||
{/* bordered={false}*/}
|
||||
{/* style={{ color: theme.colorTextSecondary, display: 'flex', gap: 4 }}*/}
|
||||
{/* >*/}
|
||||
{/* <Icon icon={LucideMessageCircle} />*/}
|
||||
{/* {chatLength}*/}
|
||||
{/* </Tag>*/}
|
||||
{/*)}*/}
|
||||
</Flexbox>
|
||||
)}
|
||||
</Flexbox>
|
||||
@@ -140,42 +154,20 @@ const SessionItem: FC<SessionItemProps> = memo(({ id, active = true, loading, pi
|
||||
style={{ color: theme.colorText }}
|
||||
title={title}
|
||||
/>
|
||||
<Popconfirm
|
||||
arrow={false}
|
||||
cancelText={t('cancel')}
|
||||
okButtonProps={{ danger: true }}
|
||||
okText={t('ok')}
|
||||
onCancel={() => setPopOpen(false)}
|
||||
onConfirm={(e) => {
|
||||
e?.stopPropagation();
|
||||
removeSession(id);
|
||||
setPopOpen(false);
|
||||
}}
|
||||
open={popOpen}
|
||||
overlayStyle={{ width: 280 }}
|
||||
title={t('confirmRemoveSessionItemAlert')}
|
||||
>
|
||||
<Dropdown
|
||||
arrow={false}
|
||||
menu={{ items }}
|
||||
onOpenChange={setDropdownOpen}
|
||||
open={dropdownOpen}
|
||||
trigger={['click']}
|
||||
>
|
||||
<ActionIcon
|
||||
className="session-remove"
|
||||
icon={MoreVertical}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}}
|
||||
size={{
|
||||
blockSize: 28,
|
||||
fontSize: 16,
|
||||
}}
|
||||
/>
|
||||
</Dropdown>
|
||||
</Popconfirm>
|
||||
<Dropdown arrow={false} menu={{ items }} trigger={['click']}>
|
||||
<ActionIcon
|
||||
className="session-remove"
|
||||
icon={MoreVertical}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}}
|
||||
size={{
|
||||
blockSize: 28,
|
||||
fontSize: 16,
|
||||
}}
|
||||
/>
|
||||
</Dropdown>
|
||||
</Flexbox>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -52,6 +52,9 @@ export const useStyles = createStyles(({ css, token }, isHighlight: boolean) =>
|
||||
hover: css`
|
||||
background-color: ${token.colorFillSecondary};
|
||||
`,
|
||||
modalRoot: css`
|
||||
z-index: 2000;
|
||||
`,
|
||||
pin: css`
|
||||
background-color: ${token.colorFillTertiary};
|
||||
`,
|
||||
|
||||
@@ -6,19 +6,32 @@ import { memo, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import HeaderTitle from '@/components/HeaderTitle';
|
||||
import { exportSingleAgent, exportSingleSession } from '@/helpers/export';
|
||||
import { useSessionStore } from '@/store/session';
|
||||
|
||||
const Header = memo(() => {
|
||||
const { t } = useTranslation('setting');
|
||||
const id = useSessionStore((s) => s.activeId);
|
||||
|
||||
const items: MenuProps['items'] = useMemo(
|
||||
const items = useMemo<MenuProps['items']>(
|
||||
() => [
|
||||
{
|
||||
key: 'agent',
|
||||
label: <div>{t('exportType.agent', { ns: 'common' })}</div>,
|
||||
onClick: () => {
|
||||
if (!id) return;
|
||||
|
||||
exportSingleAgent(id);
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'agentWithMessage',
|
||||
label: <div>{t('exportType.agentWithMessage', { ns: 'common' })}</div>,
|
||||
onClick: () => {
|
||||
if (!id) return;
|
||||
|
||||
exportSingleSession(id);
|
||||
},
|
||||
},
|
||||
],
|
||||
[],
|
||||
|
||||
23
src/store/session/slices/session/selectors/export.ts
Normal file
23
src/store/session/slices/session/selectors/export.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { transform } from 'lodash-es';
|
||||
|
||||
import { SessionStore } from '@/store/session';
|
||||
import { LobeAgentSession, LobeSessions } from '@/types/session';
|
||||
|
||||
import { getSessionById } from './list';
|
||||
|
||||
export const exportSessions = (s: SessionStore) => s.sessions;
|
||||
|
||||
// 排除 chats
|
||||
export const exportAgents = (s: SessionStore) => {
|
||||
return transform(s.sessions, (result: LobeSessions, value, key) => {
|
||||
// 移除 chats 和 topics
|
||||
result[key] = { ...value, chats: {}, topics: {} } as LobeAgentSession;
|
||||
});
|
||||
};
|
||||
// 排除 chats
|
||||
export const getExportAgent =
|
||||
(id: string) =>
|
||||
(s: SessionStore): LobeAgentSession => {
|
||||
const session = getSessionById(id)(s);
|
||||
return { ...session, chats: {}, topics: {} };
|
||||
};
|
||||
@@ -1,14 +1,18 @@
|
||||
import { exportAgents, exportSessions, getExportAgent } from './export';
|
||||
import {
|
||||
currentSession,
|
||||
currentSessionSafe,
|
||||
currentSessionSel,
|
||||
getSessionById,
|
||||
getSessionMetaById,
|
||||
sessionList,
|
||||
} from './list';
|
||||
|
||||
export const sessionSelectors = {
|
||||
currentSession: currentSessionSel,
|
||||
currentSession,
|
||||
currentSessionSafe,
|
||||
exportAgents,
|
||||
exportSessions,
|
||||
getExportAgent,
|
||||
getSessionById,
|
||||
getSessionMetaById,
|
||||
sessionList,
|
||||
|
||||
@@ -5,13 +5,14 @@ import { filterWithKeywords } from '@/utils/filter';
|
||||
|
||||
import { initLobeSession } from '../initialState';
|
||||
|
||||
export const currentSessionSel = (s: SessionStore): LobeAgentSession | undefined => {
|
||||
export const currentSession = (s: SessionStore): LobeAgentSession | undefined => {
|
||||
if (!s.activeId) return;
|
||||
|
||||
return s.sessions[s.activeId];
|
||||
};
|
||||
|
||||
export const currentSessionSafe = (s: SessionStore): LobeAgentSession => {
|
||||
return currentSessionSel(s) || initLobeSession;
|
||||
return currentSession(s) || initLobeSession;
|
||||
};
|
||||
|
||||
export const sessionList = (s: SessionStore) => {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { defaults } from 'lodash-es';
|
||||
|
||||
import { DEFAULT_SETTINGS } from '@/store/settings/initialState';
|
||||
import { GlobalSettings } from '@/types/settings';
|
||||
|
||||
import { SettingsStore } from './store';
|
||||
|
||||
@@ -11,7 +12,16 @@ const selecThemeMode = (s: SettingsStore) => ({
|
||||
themeMode: s.settings.themeMode,
|
||||
});
|
||||
|
||||
export const exportSettings = (s: SettingsStore) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { OPENAI_API_KEY: _, password: __, ...settings } = s.settings;
|
||||
|
||||
return settings as GlobalSettings;
|
||||
};
|
||||
|
||||
export const settingsSelectors = {
|
||||
currentSettings,
|
||||
exportSettings,
|
||||
|
||||
selecThemeMode,
|
||||
};
|
||||
|
||||
@@ -1,41 +1,61 @@
|
||||
import { LobeSessions } from '@/types/session';
|
||||
import { GlobalSettings } from '@/types/settings';
|
||||
|
||||
export interface ConfigState {
|
||||
sessions: LobeSessions;
|
||||
settings: GlobalSettings;
|
||||
}
|
||||
|
||||
export interface SettingsConfigState {
|
||||
settings: GlobalSettings;
|
||||
}
|
||||
export interface SessionsConfigState {
|
||||
sessions: LobeSessions;
|
||||
}
|
||||
|
||||
// 存在4种导出方式
|
||||
export type ExportType = 'agents' | 'sessions' | 'settings' | 'all';
|
||||
|
||||
export type ConfigFile = ConfigFileSettings | ConfigFileSessions | ConfigFileAll | ConfigFileAgents;
|
||||
// 4种方式对应的 state
|
||||
export interface ConfigStateAll {
|
||||
sessions: LobeSessions;
|
||||
settings: GlobalSettings;
|
||||
}
|
||||
export interface ConfigStateSettings {
|
||||
settings: GlobalSettings;
|
||||
}
|
||||
export interface ConfigStateSessions {
|
||||
sessions: LobeSessions;
|
||||
}
|
||||
|
||||
// 4种方式对应的 file
|
||||
export interface ConfigFileSettings {
|
||||
exportType: 'settings';
|
||||
state: SettingsConfigState;
|
||||
state: ConfigStateSettings;
|
||||
version: number;
|
||||
}
|
||||
export interface ConfigFileSessions {
|
||||
exportType: 'sessions';
|
||||
state: SessionsConfigState;
|
||||
state: ConfigStateSessions;
|
||||
version: number;
|
||||
}
|
||||
|
||||
export interface ConfigFileAgents {
|
||||
exportType: 'agents';
|
||||
state: SessionsConfigState;
|
||||
state: ConfigStateSessions;
|
||||
version: number;
|
||||
}
|
||||
export interface ConfigFileAll {
|
||||
exportType: 'all';
|
||||
state: ConfigStateAll;
|
||||
version: number;
|
||||
}
|
||||
|
||||
export interface ConfigFileAll {
|
||||
exportType: 'all';
|
||||
state: ConfigState;
|
||||
version: number;
|
||||
export type ConfigFile = ConfigFileSettings | ConfigFileSessions | ConfigFileAll | ConfigFileAgents;
|
||||
|
||||
// 用于 map 收集类型的 map
|
||||
export interface ConfigModelMap {
|
||||
agents: {
|
||||
file: ConfigFileAgents;
|
||||
state: ConfigStateSessions;
|
||||
};
|
||||
all: {
|
||||
file: ConfigFileAll;
|
||||
state: ConfigStateAll;
|
||||
};
|
||||
sessions: {
|
||||
file: ConfigFileSessions;
|
||||
state: ConfigStateSessions;
|
||||
};
|
||||
settings: {
|
||||
file: ConfigFileSettings;
|
||||
state: ConfigStateSettings;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
import { notification } from 'antd';
|
||||
|
||||
import { CURRENT_CONFIG_VERSION, Migration } from '@/migrations';
|
||||
import { ConfigFile } from '@/types/exportConfig';
|
||||
import {
|
||||
ConfigFile,
|
||||
ConfigFileAgents,
|
||||
ConfigFileAll,
|
||||
ConfigFileSessions,
|
||||
ConfigFileSettings,
|
||||
ConfigModelMap,
|
||||
ExportType,
|
||||
} from '@/types/exportConfig';
|
||||
|
||||
export const exportConfigFile = (config: object, fileName?: string) => {
|
||||
const file = `LobeChat-${fileName || '-config'}-v${CURRENT_CONFIG_VERSION}.json`;
|
||||
@@ -48,3 +56,48 @@ export const importConfigFile = (info: any, onConfigImport: (config: ConfigFile)
|
||||
//@ts-ignore file 类型不明确
|
||||
reader.readAsText(info.file.originFileObj, 'utf8');
|
||||
};
|
||||
|
||||
type CreateConfigFileState<T extends ExportType> = ConfigModelMap[T]['state'];
|
||||
|
||||
type CreateConfigFile<T extends ExportType> = ConfigModelMap[T]['file'];
|
||||
|
||||
export const createConfigFile = <T extends ExportType>(
|
||||
type: T,
|
||||
state: CreateConfigFileState<T>,
|
||||
): CreateConfigFile<T> => {
|
||||
switch (type) {
|
||||
case 'agents': {
|
||||
return {
|
||||
exportType: 'agents',
|
||||
state,
|
||||
version: Migration.targetVersion,
|
||||
} as ConfigFileAgents;
|
||||
}
|
||||
|
||||
case 'sessions': {
|
||||
return {
|
||||
exportType: 'sessions',
|
||||
state,
|
||||
version: Migration.targetVersion,
|
||||
} as ConfigFileSessions;
|
||||
}
|
||||
|
||||
case 'settings': {
|
||||
return {
|
||||
exportType: 'settings',
|
||||
state,
|
||||
version: Migration.targetVersion,
|
||||
} as ConfigFileSettings;
|
||||
}
|
||||
|
||||
case 'all': {
|
||||
return {
|
||||
exportType: 'all',
|
||||
state,
|
||||
version: Migration.targetVersion,
|
||||
} as ConfigFileAll;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('缺少正确的导出类型,请检查实现...');
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user