Skip to content

Commit

Permalink
♻️ refactor: refactor the config import for server import (lobehub#2718)
Browse files Browse the repository at this point in the history
  • Loading branch information
arvinxx authored May 29, 2024
1 parent 5eb43e8 commit d4ee64b
Show file tree
Hide file tree
Showing 10 changed files with 156 additions and 139 deletions.
2 changes: 1 addition & 1 deletion src/database/client/schemas/message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export const DB_MessageSchema = z.object({

fromModel: z.string().optional(),
fromProvider: z.string().optional(),
translate: TranslateSchema.optional().or(z.literal(false)),
translate: TranslateSchema.optional().or(z.literal(false)).or(z.null()),
tts: z.any().optional(),

traceId: z.string().optional(),
Expand Down
35 changes: 23 additions & 12 deletions src/features/DataImporter/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@ import { Icon } from '@lobehub/ui';
import { Button, Result, Table, Upload } from 'antd';
import { createStyles } from 'antd-style';
import { CheckCircle, ImportIcon } from 'lucide-react';
import React, { ReactNode, memo, useState } from 'react';
import React, { ReactNode, memo, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Center, Flexbox } from 'react-layout-kit';

import DataStyleModal from '@/components/DataStyleModal';
import { useImportConfig } from '@/hooks/useImportConfig';
import { ImportResult, ImportResults } from '@/services/config';

import { useImportConfig } from './useImportConfig';

const useStyles = createStyles(({ css, token }) => {
const size = 28;

Expand Down Expand Up @@ -145,12 +146,29 @@ interface DataImporterProps {
}
const DataImporter = memo<DataImporterProps>(({ children, onFinishImport }) => {
const { t } = useTranslation('common');
const { importConfig } = useImportConfig();
const importConfig = useImportConfig();
const [duration, setDuration] = useState(0);
const [importState, setImportState] = useState(ImportState.Start);
const [importData, setImportData] = useState<ImportResults | undefined>();
const { styles } = useStyles();

const dataSource = useMemo(() => {
if (!importData) return;

const { type, ...res } = importData;

if (type === 'settings') return;

return Object.entries(res)
.filter(([, v]) => !!v)
.map(([item, value]: [string, ImportResult]) => ({
added: value.added,
error: value.errors,
skips: value.skips,
title: t(`importModal.result.${item as keyof ImportResults}`),
}));
}, [importData]);

return (
<>
<DataStyleModal
Expand Down Expand Up @@ -187,7 +205,7 @@ const DataImporter = memo<DataImporterProps>(({ children, onFinishImport }) => {
style={{ paddingBlock: 24 }}
subTitle={
// if there is no importData, means it's only import the settings
!importData ? (
!dataSource ? (
t('importModal.finish.onlySettings')
) : (
<Flexbox gap={16} width={400}>
Expand All @@ -200,14 +218,7 @@ const DataImporter = memo<DataImporterProps>(({ children, onFinishImport }) => {
{ dataIndex: 'skips', title: t('importModal.result.skips') },
{ dataIndex: 'error', title: t('importModal.result.errors') },
]}
dataSource={Object.entries(importData).map(
([item, value]: [string, ImportResult]) => ({
added: value.added,
error: value.errors,
skips: value.skips,
title: t(`importModal.result.${item as keyof ImportResults}`),
}),
)}
dataSource={dataSource}
pagination={false}
size={'small'}
/>
Expand Down
27 changes: 27 additions & 0 deletions src/features/DataImporter/useImportConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { useCallback } from 'react';

import { ImportResults, configService } from '@/services/config';
import { useChatStore } from '@/store/chat';
import { useSessionStore } from '@/store/session';
import { importConfigFile } from '@/utils/config';

export const useImportConfig = () => {
const refreshSessions = useSessionStore((s) => s.refreshSessions);
const [refreshMessages, refreshTopics] = useChatStore((s) => [s.refreshMessages, s.refreshTopic]);

return useCallback(
async (file: File) =>
new Promise<ImportResults | undefined>((resolve) => {
importConfigFile(file, async (config) => {
const data = await configService.importConfigState(config);

await refreshSessions();
await refreshMessages();
await refreshTopics();

resolve(data);
});
}),
[],
);
};
45 changes: 0 additions & 45 deletions src/hooks/useImportConfig.ts

This file was deleted.

9 changes: 4 additions & 5 deletions src/layout/GlobalProvider/StoreInitialization.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { memo, useEffect } from 'react';
import { createStoreUpdater } from 'zustand-utils';

import { LOBE_URL_IMPORT_NAME } from '@/const/url';
import { useImportConfig } from '@/hooks/useImportConfig';
import { useIsMobile } from '@/hooks/useIsMobile';
import { useEnabledDataSync } from '@/hooks/useSyncData';
import { useAgentStore } from '@/store/agent';
Expand All @@ -17,9 +16,10 @@ import { authSelectors } from '@/store/user/selectors';
const StoreInitialization = memo(() => {
const router = useRouter();

const [useInitUserState, isLogin] = useUserStore((s) => [
s.useInitUserState,
const [isLogin, useInitUserState, importUrlShareSettings] = useUserStore((s) => [
authSelectors.isLogin(s),
s.useInitUserState,
s.importUrlShareSettings,
]);

const { serverConfig } = useServerConfigStore();
Expand Down Expand Up @@ -52,10 +52,9 @@ const StoreInitialization = memo(() => {
useStoreUpdater('router', router);

// Import settings from the url
const { importSettings } = useImportConfig();
const searchParam = useSearchParams().get(LOBE_URL_IMPORT_NAME);
useEffect(() => {
importSettings(searchParam);
importUrlShareSettings(searchParam);
}, [searchParam]);

// useEffect(() => {
Expand Down
92 changes: 18 additions & 74 deletions src/services/config.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { importService } from '@/services/import';
import { messageService } from '@/services/message';
import { sessionService } from '@/services/session';
import { topicService } from '@/services/topic';
Expand All @@ -6,10 +7,6 @@ import { sessionSelectors } from '@/store/session/selectors';
import { useUserStore } from '@/store/user';
import { settingsSelectors } from '@/store/user/selectors';
import { ConfigFile } from '@/types/exportConfig';
import { ChatMessage } from '@/types/message';
import { LobeSessions, SessionGroupItem } from '@/types/session';
import { ChatTopic } from '@/types/topic';
import { UserSettings } from '@/types/user/settings';
import { createConfigFile, exportConfigFile } from '@/utils/config';

export interface ImportResult {
Expand All @@ -20,71 +17,30 @@ export interface ImportResult {
export interface ImportResults {
messages?: ImportResult;
sessionGroups?: ImportResult;
sessions: ImportResult;
sessions?: ImportResult;
topics?: ImportResult;
type?: string;
}

class ConfigService {
/**
* import sessions from files
* @param sessions
*/
importSessions = async (sessions: LobeSessions) => {
return await sessionService.batchCreateSessions(sessions);
};
importMessages = async (messages: ChatMessage[]) => {
return messageService.batchCreateMessages(messages);
};
importSettings = async (settings: UserSettings) => {
useUserStore.getState().importAppSettings(settings);
};
importTopics = async (topics: ChatTopic[]) => {
return topicService.batchCreateTopics(topics);
};
importSessionGroups = async (sessionGroups: SessionGroupItem[]) => {
return sessionService.batchCreateSessionGroups(sessionGroups || []);
};

importConfigState = async (config: ConfigFile): Promise<ImportResults | undefined> => {
switch (config.exportType) {
case 'settings': {
await this.importSettings(config.state.settings);

break;
}

case 'agents': {
const sessionGroups = await this.importSessionGroups(config.state.sessionGroups);

const data = await this.importSessions(config.state.sessions);
return {
sessionGroups: this.mapImportResult(sessionGroups),
sessions: this.mapImportResult(data),
};
}

case 'all': {
await this.importSettings(config.state.settings);
}
// all and sessions have the same data process, so we can fall through

// eslint-disable-next-line no-fallthrough
case 'sessions': {
const sessionGroups = await this.importSessionGroups(config.state.sessionGroups);
const sessions = await this.importSessions(config.state.sessions);
const topics = await this.importTopics(config.state.topics);
const messages = await this.importMessages(config.state.messages);

return {
messages: this.mapImportResult(messages),
sessionGroups: this.mapImportResult(sessionGroups),
sessions: this.mapImportResult(sessions),
topics: this.mapImportResult(topics),
};
}
importConfigState = async (config: ConfigFile): Promise<ImportResults> => {
if (config.exportType === 'settings') {
await importService.importSettings(config.state.settings);
return { type: 'settings' };
}

const data = await importService.importData({
messages: (config.state as any).messages || [],
sessionGroups: (config.state as any).sessionGroups || [],
sessions: (config.state as any).sessions || [],
topics: (config.state as any).topics || [],
});

return { ...data, type: config.exportType };
};

// TODO: Seperate export feature into a new service like importService

/**
* export all agents
*/
Expand Down Expand Up @@ -190,18 +146,6 @@ class ConfigService {

private getAgent = (id: string) =>
sessionSelectors.getSessionById(id)(useSessionStore.getState());

private mapImportResult = (input: {
added: number;
errors?: Error[];
skips: string[];
}): ImportResult => {
return {
added: input.added,
errors: input.errors?.length || 0,
skips: input.skips.length,
};
};
}

export const configService = new ConfigService();
62 changes: 62 additions & 0 deletions src/services/import/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { MessageModel } from '@/database/client/models/message';
import { SessionModel } from '@/database/client/models/session';
import { SessionGroupModel } from '@/database/client/models/sessionGroup';
import { TopicModel } from '@/database/client/models/topic';
import { ImportResult, ImportResults } from '@/services/config';
import { useUserStore } from '@/store/user';
import { ConfigStateSessions } from '@/types/exportConfig';
import { UserSettings } from '@/types/user/settings';

export class ClientService {
importSettings = async (settings: UserSettings) => {
await useUserStore.getState().importAppSettings(settings);
};

importData = async (config: ConfigStateSessions): Promise<ImportResults> => {
const { messages, sessionGroups, sessions, topics } = config;

let messageResult: ImportResult | undefined;
let sessionResult: ImportResult | undefined;
let sessionGroupResult: ImportResult | undefined;
let topicResult: ImportResult | undefined;

if (messages.length > 0) {
const res = await MessageModel.batchCreate(messages);
messageResult = this.mapImportResult(res);
}

if (sessionGroups.length > 0) {
const res = await SessionGroupModel.batchCreate(sessionGroups);
sessionGroupResult = this.mapImportResult(res);
}

if (topics.length > 0) {
const res = await TopicModel.batchCreate(topics as any);
topicResult = this.mapImportResult(res);
}

if (sessions.length > 0) {
const data = await SessionModel.batchCreate(sessions);
sessionResult = this.mapImportResult(data);
}

return {
messages: messageResult,
sessionGroups: sessionGroupResult,
sessions: sessionResult,
topics: topicResult,
};
};

private mapImportResult = (input: {
added: number;
errors?: Error[];
skips: string[];
}): ImportResult => {
return {
added: input.added,
errors: input.errors?.length || 0,
skips: input.skips.length,
};
};
}
3 changes: 3 additions & 0 deletions src/services/import/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { ClientService } from './client';

export const importService = new ClientService();
Loading

0 comments on commit d4ee64b

Please sign in to comment.