mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-08 20:42:30 +08:00
### What problem does this PR solve? Feat: Import & export the agents. #3851 ### Type of change - [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
@ -1,8 +1,16 @@
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from '@/components/ui/tooltip';
|
||||
import { useSetModalState } from '@/hooks/common-hooks';
|
||||
import { FolderInput, FolderOutput } from 'lucide-react';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import ReactFlow, {
|
||||
Background,
|
||||
ConnectionMode,
|
||||
ControlButton,
|
||||
Controls,
|
||||
NodeMouseHandler,
|
||||
} from 'reactflow';
|
||||
@ -13,12 +21,14 @@ import FormDrawer from '../flow-drawer';
|
||||
import {
|
||||
useGetBeginNodeDataQuery,
|
||||
useHandleDrop,
|
||||
useHandleExportOrImportJsonFile,
|
||||
useSelectCanvasData,
|
||||
useShowFormDrawer,
|
||||
useValidateConnection,
|
||||
useWatchNodeFormDataChange,
|
||||
} from '../hooks';
|
||||
import { BeginQuery } from '../interface';
|
||||
import JsonUploadModal from '../json-upload-modal';
|
||||
import RunDrawer from '../run-drawer';
|
||||
import { ButtonEdge } from './edge';
|
||||
import styles from './index.less';
|
||||
@ -115,6 +125,14 @@ function FlowCanvas({ drawerVisible, hideDrawer }: IProps) {
|
||||
|
||||
const getBeginNodeDataQuery = useGetBeginNodeDataQuery();
|
||||
|
||||
const {
|
||||
handleExportJson,
|
||||
handleImportJson,
|
||||
fileUploadVisible,
|
||||
onFileUploadOk,
|
||||
hideFileUploadModal,
|
||||
} = useHandleExportOrImportJsonFile();
|
||||
|
||||
useEffect(() => {
|
||||
if (drawerVisible) {
|
||||
const query: BeginQuery[] = getBeginNodeDataQuery();
|
||||
@ -192,7 +210,28 @@ function FlowCanvas({ drawerVisible, hideDrawer }: IProps) {
|
||||
deleteKeyCode={['Delete', 'Backspace']}
|
||||
>
|
||||
<Background />
|
||||
<Controls />
|
||||
<Controls>
|
||||
<ControlButton onClick={handleImportJson}>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<FolderInput />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Import</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</ControlButton>
|
||||
<ControlButton onClick={handleExportJson}>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<FolderOutput />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Export</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</ControlButton>
|
||||
</Controls>
|
||||
</ReactFlow>
|
||||
{formDrawerVisible && (
|
||||
<FormDrawer
|
||||
@ -214,6 +253,13 @@ function FlowCanvas({ drawerVisible, hideDrawer }: IProps) {
|
||||
showModal={showChatModal}
|
||||
></RunDrawer>
|
||||
)}
|
||||
{fileUploadVisible && (
|
||||
<JsonUploadModal
|
||||
onOk={onFileUploadOk}
|
||||
visible={fileUploadVisible}
|
||||
hideModal={hideFileUploadModal}
|
||||
></JsonUploadModal>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -12,14 +12,16 @@ import React, {
|
||||
import { Connection, Edge, Node, Position, ReactFlowInstance } from 'reactflow';
|
||||
// import { shallow } from 'zustand/shallow';
|
||||
import { variableEnabledFieldMap } from '@/constants/chat';
|
||||
import { FileMimeType } from '@/constants/common';
|
||||
import {
|
||||
ModelVariableType,
|
||||
settledModelVariableMap,
|
||||
} from '@/constants/knowledge';
|
||||
import { useFetchModelId } from '@/hooks/logic-hooks';
|
||||
import { Variable } from '@/interfaces/database/chat';
|
||||
import { downloadJsonFile } from '@/utils/file-util';
|
||||
import { useDebounceEffect } from 'ahooks';
|
||||
import { FormInstance, message } from 'antd';
|
||||
import { FormInstance, UploadFile, message } from 'antd';
|
||||
import { DefaultOptionType } from 'antd/es/select';
|
||||
import dayjs from 'dayjs';
|
||||
import { humanId } from 'human-id';
|
||||
@ -261,30 +263,45 @@ export const useShowFormDrawer = () => {
|
||||
};
|
||||
};
|
||||
|
||||
export const useSaveGraph = () => {
|
||||
export const useBuildDslData = () => {
|
||||
const { data } = useFetchFlow();
|
||||
const { setFlow, loading } = useSetFlow();
|
||||
const { id } = useParams();
|
||||
const { nodes, edges } = useGraphStore((state) => state);
|
||||
useEffect(() => {}, [nodes]);
|
||||
const saveGraph = useCallback(
|
||||
async (currentNodes?: Node[]) => {
|
||||
|
||||
const buildDslData = useCallback(
|
||||
(currentNodes?: Node[]) => {
|
||||
const dslComponents = buildDslComponentsByGraph(
|
||||
currentNodes ?? nodes,
|
||||
edges,
|
||||
data.dsl.components,
|
||||
);
|
||||
|
||||
return {
|
||||
...data.dsl,
|
||||
graph: { nodes: currentNodes ?? nodes, edges },
|
||||
components: dslComponents,
|
||||
};
|
||||
},
|
||||
[data.dsl, edges, nodes],
|
||||
);
|
||||
|
||||
return { buildDslData };
|
||||
};
|
||||
|
||||
export const useSaveGraph = () => {
|
||||
const { data } = useFetchFlow();
|
||||
const { setFlow, loading } = useSetFlow();
|
||||
const { id } = useParams();
|
||||
const { buildDslData } = useBuildDslData();
|
||||
|
||||
const saveGraph = useCallback(
|
||||
async (currentNodes?: Node[]) => {
|
||||
return setFlow({
|
||||
id,
|
||||
title: data.title,
|
||||
dsl: {
|
||||
...data.dsl,
|
||||
graph: { nodes: currentNodes ?? nodes, edges },
|
||||
components: dslComponents,
|
||||
},
|
||||
dsl: buildDslData(currentNodes),
|
||||
});
|
||||
},
|
||||
[nodes, edges, setFlow, id, data],
|
||||
[setFlow, id, data.title, buildDslData],
|
||||
);
|
||||
|
||||
return { saveGraph, loading };
|
||||
@ -774,3 +791,54 @@ export const useWatchAgentChange = (chatDrawerVisible: boolean) => {
|
||||
|
||||
return time;
|
||||
};
|
||||
|
||||
export const useHandleExportOrImportJsonFile = () => {
|
||||
const { buildDslData } = useBuildDslData();
|
||||
const {
|
||||
visible: fileUploadVisible,
|
||||
hideModal: hideFileUploadModal,
|
||||
showModal: showFileUploadModal,
|
||||
} = useSetModalState();
|
||||
const setGraphInfo = useSetGraphInfo();
|
||||
const { data } = useFetchFlow();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const onFileUploadOk = useCallback(
|
||||
async (fileList: UploadFile[]) => {
|
||||
if (fileList.length > 0) {
|
||||
const file: File = fileList[0] as unknown as File;
|
||||
if (file.type !== FileMimeType.Json) {
|
||||
message.error(t('flow.jsonUploadTypeErrorMessage'));
|
||||
return;
|
||||
}
|
||||
|
||||
const graphStr = await file.text();
|
||||
const errorMessage = t('flow.jsonUploadContentErrorMessage');
|
||||
try {
|
||||
const graph = JSON.parse(graphStr);
|
||||
if (graphStr && !isEmpty(graph) && Array.isArray(graph?.nodes)) {
|
||||
setGraphInfo(graph ?? ({} as IGraph));
|
||||
hideFileUploadModal();
|
||||
} else {
|
||||
message.error(errorMessage);
|
||||
}
|
||||
} catch (error) {
|
||||
message.error(errorMessage);
|
||||
}
|
||||
}
|
||||
},
|
||||
[hideFileUploadModal, setGraphInfo, t],
|
||||
);
|
||||
|
||||
const handleExportJson = useCallback(() => {
|
||||
downloadJsonFile(buildDslData().graph, `${data.title}.json`);
|
||||
}, [buildDslData, data.title]);
|
||||
|
||||
return {
|
||||
fileUploadVisible,
|
||||
handleExportJson,
|
||||
handleImportJson: showFileUploadModal,
|
||||
hideFileUploadModal,
|
||||
onFileUploadOk,
|
||||
};
|
||||
};
|
||||
|
||||
13
web/src/pages/flow/json-upload-modal/index.less
Normal file
13
web/src/pages/flow/json-upload-modal/index.less
Normal file
@ -0,0 +1,13 @@
|
||||
.uploader {
|
||||
:global {
|
||||
.ant-upload-list {
|
||||
max-height: 40vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.uploadLimit {
|
||||
color: red;
|
||||
font-size: 12px;
|
||||
}
|
||||
97
web/src/pages/flow/json-upload-modal/index.tsx
Normal file
97
web/src/pages/flow/json-upload-modal/index.tsx
Normal file
@ -0,0 +1,97 @@
|
||||
import { useTranslate } from '@/hooks/common-hooks';
|
||||
import { IModalProps } from '@/interfaces/common';
|
||||
import { InboxOutlined } from '@ant-design/icons';
|
||||
import { Modal, Upload, UploadFile, UploadProps } from 'antd';
|
||||
import { Dispatch, SetStateAction, useState } from 'react';
|
||||
|
||||
import { FileMimeType } from '@/constants/common';
|
||||
|
||||
import styles from './index.less';
|
||||
|
||||
const { Dragger } = Upload;
|
||||
|
||||
const FileUpload = ({
|
||||
directory,
|
||||
fileList,
|
||||
setFileList,
|
||||
}: {
|
||||
directory: boolean;
|
||||
fileList: UploadFile[];
|
||||
setFileList: Dispatch<SetStateAction<UploadFile[]>>;
|
||||
}) => {
|
||||
const { t } = useTranslate('fileManager');
|
||||
const props: UploadProps = {
|
||||
multiple: false,
|
||||
accept: FileMimeType.Json,
|
||||
onRemove: (file) => {
|
||||
const index = fileList.indexOf(file);
|
||||
const newFileList = fileList.slice();
|
||||
newFileList.splice(index, 1);
|
||||
setFileList(newFileList);
|
||||
},
|
||||
beforeUpload: (file) => {
|
||||
setFileList(() => {
|
||||
return [file];
|
||||
});
|
||||
|
||||
return false;
|
||||
},
|
||||
directory,
|
||||
fileList,
|
||||
};
|
||||
|
||||
return (
|
||||
<Dragger {...props} className={styles.uploader}>
|
||||
<p className="ant-upload-drag-icon">
|
||||
<InboxOutlined />
|
||||
</p>
|
||||
<p className="ant-upload-text">{t('uploadTitle')}</p>
|
||||
<p className="ant-upload-hint">{t('uploadDescription')}</p>
|
||||
{false && <p className={styles.uploadLimit}>{t('uploadLimit')}</p>}
|
||||
</Dragger>
|
||||
);
|
||||
};
|
||||
|
||||
const JsonUploadModal = ({
|
||||
visible,
|
||||
hideModal,
|
||||
loading,
|
||||
onOk: onFileUploadOk,
|
||||
}: IModalProps<UploadFile[]>) => {
|
||||
const { t } = useTranslate('fileManager');
|
||||
const [fileList, setFileList] = useState<UploadFile[]>([]);
|
||||
const [directoryFileList, setDirectoryFileList] = useState<UploadFile[]>([]);
|
||||
|
||||
const clearFileList = () => {
|
||||
setFileList([]);
|
||||
setDirectoryFileList([]);
|
||||
};
|
||||
|
||||
const onOk = async () => {
|
||||
const ret = await onFileUploadOk?.([...fileList, ...directoryFileList]);
|
||||
return ret;
|
||||
};
|
||||
|
||||
const afterClose = () => {
|
||||
clearFileList();
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={t('uploadFile')}
|
||||
open={visible}
|
||||
onOk={onOk}
|
||||
onCancel={hideModal}
|
||||
confirmLoading={loading}
|
||||
afterClose={afterClose}
|
||||
>
|
||||
<FileUpload
|
||||
directory={false}
|
||||
fileList={fileList}
|
||||
setFileList={setFileList}
|
||||
></FileUpload>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default JsonUploadModal;
|
||||
Reference in New Issue
Block a user