feat: upload file in FileManager #345 (#529)

### What problem does this PR solve?

feat: upload file in FileManager #345 

### Type of change

- [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
balibabu 2024-04-25 08:46:18 +08:00 committed by GitHub
parent b06d6395bb
commit 51e7697df7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 412 additions and 50 deletions

View File

@ -1,4 +1,8 @@
import { IFileListRequestBody } from '@/interfaces/request/file-manager';
import {
IConnectRequestBody,
IFileListRequestBody,
} from '@/interfaces/request/file-manager';
import { UploadFile } from 'antd';
import { useCallback } from 'react';
import { useDispatch, useSelector } from 'umi';
@ -94,3 +98,47 @@ export const useSelectParentFolderList = () => {
);
return parentFolderList.toReversed();
};
export const useUploadFile = () => {
const dispatch = useDispatch();
const uploadFile = useCallback(
(file: UploadFile, parentId: string, path: string) => {
try {
return dispatch<any>({
type: 'fileManager/uploadFile',
payload: {
file,
parentId,
path,
},
});
} catch (errorInfo) {
console.log('Failed:', errorInfo);
}
},
[dispatch],
);
return uploadFile;
};
export const useConnectToKnowledge = () => {
const dispatch = useDispatch();
const uploadFile = useCallback(
(payload: IConnectRequestBody) => {
try {
return dispatch<any>({
type: 'fileManager/connectFileToKnowledge',
payload,
});
} catch (errorInfo) {
console.log('Failed:', errorInfo);
}
},
[dispatch],
);
return uploadFile;
};

View File

@ -3,7 +3,7 @@ export interface IFile {
create_time: number;
created_by: string;
id: string;
kb_ids: string[];
kbs_info: { kb_id: string; kb_name: string }[];
location: string;
name: string;
parent_id: string;

View File

@ -3,3 +3,12 @@ import { IPaginationRequestBody } from './base';
export interface IFileListRequestBody extends IPaginationRequestBody {
parent_id?: string; // folder id
}
interface BaseRequestBody {
parentId: string;
}
export interface IConnectRequestBody extends BaseRequestBody {
fileIds: string[];
kbIds: string[];
}

View File

@ -382,7 +382,7 @@ export default {
passwordDescription:
'Please enter your current password to change your password.',
model: 'Model Providers',
modelDescription: 'Manage your account settings and preferences here.',
modelDescription: 'Set the model parameter and API Key here.',
team: 'Team',
logout: 'Log out',
username: 'Username',

View File

@ -352,7 +352,7 @@ export default {
password: '密碼',
passwordDescription: '請輸入您當前的密碼以更改您的密碼。',
model: '模型提供商',
modelDescription: '在此管理您的帳戶設置和首選項。',
modelDescription: '在此設置模型參數和 API Key。',
team: '團隊',
logout: '登出',
username: '使用者名稱',

View File

@ -369,7 +369,7 @@ export default {
password: '密码',
passwordDescription: '请输入您当前的密码以更改您的密码。',
model: '模型提供商',
modelDescription: '在此管理您的帐户设置和首选项。',
modelDescription: '在此设置模型参数和 API Key。',
team: '团队',
logout: '登出',
username: '用户名',

View File

@ -17,9 +17,15 @@ interface IProps {
record: IFile;
setCurrentRecord: (record: any) => void;
showRenameModal: (record: IFile) => void;
showConnectToKnowledgeModal: (ids: string[]) => void;
}
const ActionCell = ({ record, setCurrentRecord, showRenameModal }: IProps) => {
const ActionCell = ({
record,
setCurrentRecord,
showRenameModal,
showConnectToKnowledgeModal,
}: IProps) => {
const documentId = record.id;
const beingUsed = false;
const { t } = useTranslate('knowledgeDetails');
@ -41,9 +47,17 @@ const ActionCell = ({ record, setCurrentRecord, showRenameModal }: IProps) => {
showRenameModal(record);
};
const onShowConnectToKnowledgeModal = () => {
showConnectToKnowledgeModal([documentId]);
};
return (
<Space size={0}>
<Button type="text" className={styles.iconButton}>
<Button
type="text"
className={styles.iconButton}
onClick={onShowConnectToKnowledgeModal}
>
<ToolOutlined size={20} />
</Button>

View File

@ -0,0 +1,58 @@
import { useFetchKnowledgeList } from '@/hooks/knowledgeHook';
import { IModalProps } from '@/interfaces/common';
import { Form, Modal, Select, SelectProps } from 'antd';
const ConnectToKnowledgeModal = ({
visible,
hideModal,
onOk,
}: IModalProps<string[]>) => {
const [form] = Form.useForm();
const { list } = useFetchKnowledgeList();
const options: SelectProps['options'] = list?.map((item) => ({
label: item.name,
value: item.id,
}));
const handleOk = async () => {
const values = await form.getFieldsValue();
const knowledgeIds = values.knowledgeIds ?? [];
if (knowledgeIds.length > 0) {
return onOk?.(knowledgeIds);
}
};
return (
<Modal
title="Add to Knowledge Base"
open={visible}
onOk={handleOk}
onCancel={hideModal}
>
<Form form={form}>
<Form.Item
name="knowledgeIds"
noStyle
rules={[
{
required: true,
message: 'Please select your favourite colors!',
type: 'array',
},
]}
>
<Select
mode="multiple"
allowClear
style={{ width: '100%' }}
placeholder="Please select"
options={options}
/>
</Form.Item>
</Form>
</Modal>
);
};
export default ConnectToKnowledgeModal;

View File

@ -32,6 +32,7 @@ import styles from './index.less';
interface IProps {
selectedRowKeys: string[];
showFolderCreateModal: () => void;
showFileUploadModal: () => void;
}
const itemRender: BreadcrumbProps['itemRender'] = (
@ -48,7 +49,11 @@ const itemRender: BreadcrumbProps['itemRender'] = (
);
};
const FileToolbar = ({ selectedRowKeys, showFolderCreateModal }: IProps) => {
const FileToolbar = ({
selectedRowKeys,
showFolderCreateModal,
showFileUploadModal,
}: IProps) => {
const { t } = useTranslate('knowledgeDetails');
const { fetchDocumentList } = useFetchDocumentListOnMount();
const { setPagination, searchString } = useGetPagination(fetchDocumentList);
@ -59,6 +64,7 @@ const FileToolbar = ({ selectedRowKeys, showFolderCreateModal }: IProps) => {
return [
{
key: '1',
onClick: showFileUploadModal,
label: (
<div>
<Button type="link">
@ -85,7 +91,7 @@ const FileToolbar = ({ selectedRowKeys, showFolderCreateModal }: IProps) => {
// disabled: true,
},
];
}, [t, showFolderCreateModal]);
}, [t, showFolderCreateModal, showFileUploadModal]);
const { handleRemoveFile } = useHandleDeleteFile(selectedRowKeys);

View File

@ -1,61 +1,124 @@
import { IModalProps } from '@/interfaces/common';
import { InboxOutlined } from '@ant-design/icons';
import { Modal, Segmented, Upload, UploadProps, message } from 'antd';
import { useState } from 'react';
import {
Flex,
Modal,
Segmented,
Tabs,
TabsProps,
Upload,
UploadFile,
UploadProps,
} from 'antd';
import { Dispatch, SetStateAction, useState } from 'react';
import { useHandleUploadFile } from '../hooks';
const { Dragger } = Upload;
const FileUploadModal = () => {
const [isModalOpen, setIsModalOpen] = useState(false);
const FileUpload = ({
directory,
fileList,
setFileList,
}: {
directory: boolean;
fileList: UploadFile[];
setFileList: Dispatch<SetStateAction<UploadFile[]>>;
}) => {
const props: UploadProps = {
name: 'file',
multiple: true,
action: 'https://660d2bd96ddfa2943b33731c.mockapi.io/api/upload',
onChange(info) {
const { status } = info.file;
if (status !== 'uploading') {
console.log(info.file, info.fileList);
}
if (status === 'done') {
message.success(`${info.file.name} file uploaded successfully.`);
} else if (status === 'error') {
message.error(`${info.file.name} file upload failed.`);
}
onRemove: (file) => {
const index = fileList.indexOf(file);
const newFileList = fileList.slice();
newFileList.splice(index, 1);
setFileList(newFileList);
},
onDrop(e) {
console.log('Dropped files', e.dataTransfer.files);
beforeUpload: (file) => {
setFileList((pre) => {
return [...pre, file];
});
return false;
},
directory,
fileList,
};
const handleOk = () => {
setIsModalOpen(false);
return (
<Dragger {...props}>
<p className="ant-upload-drag-icon">
<InboxOutlined />
</p>
<p className="ant-upload-text">
Click or drag file to this area to upload
</p>
<p className="ant-upload-hint">
Support for a single or bulk upload. Strictly prohibited from uploading
company data or other banned files.
</p>
</Dragger>
);
};
const FileUploadModal = ({ visible, hideModal }: IModalProps<any>) => {
const [value, setValue] = useState<string | number>('local');
const { onFileUploadOk, fileUploadLoading } = useHandleUploadFile();
const [fileList, setFileList] = useState<UploadFile[]>([]);
const [directoryFileList, setDirectoryFileList] = useState<UploadFile[]>([]);
const onOk = () => {
onFileUploadOk([...fileList, ...directoryFileList]);
};
const handleCancel = () => {
setIsModalOpen(false);
};
const items: TabsProps['items'] = [
{
key: '1',
label: 'File',
children: (
<FileUpload
directory={false}
fileList={fileList}
setFileList={setFileList}
></FileUpload>
),
},
{
key: '2',
label: 'Directory',
children: (
<FileUpload
directory
fileList={directoryFileList}
setFileList={setDirectoryFileList}
></FileUpload>
),
},
];
return (
<>
<Modal
title="File upload"
open={isModalOpen}
onOk={handleOk}
onCancel={handleCancel}
open={visible}
onOk={onOk}
onCancel={hideModal}
confirmLoading={fileUploadLoading}
>
<Segmented options={['Local uploads', 'S3 uploads']} block />
<Dragger {...props}>
<p className="ant-upload-drag-icon">
<InboxOutlined />
</p>
<p className="ant-upload-text">
Click or drag file to this area to upload
</p>
<p className="ant-upload-hint">
Support for a single or bulk upload. Strictly prohibited from
uploading company data or other banned files.
</p>
</Dragger>
<Flex gap={'large'} vertical>
<Segmented
options={[
{ label: 'Local uploads', value: 'local' },
{ label: 'S3 uploads', value: 's3' },
]}
block
value={value}
onChange={setValue}
/>
{value === 'local' ? (
<Tabs defaultActiveKey="1" items={items} />
) : (
'coming soon'
)}
</Flex>
</Modal>
</>
);

View File

@ -4,6 +4,7 @@ import {
useTranslate,
} from '@/hooks/commonHooks';
import {
useConnectToKnowledge,
useCreateFolder,
useFetchFileList,
useFetchParentFolderList,
@ -11,11 +12,14 @@ import {
useRenameFile,
useSelectFileList,
useSelectParentFolderList,
useUploadFile,
} from '@/hooks/fileManagerHooks';
import { useOneNamespaceEffectsLoading } from '@/hooks/storeHooks';
import { Pagination } from '@/interfaces/common';
import { IFile } from '@/interfaces/database/file-manager';
import { getFilePathByWebkitRelativePath } from '@/utils/fileUtil';
import { PaginationProps } from 'antd';
import { UploadFile } from 'antd/lib';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useDispatch, useNavigate, useSearchParams, useSelector } from 'umi';
@ -250,3 +254,87 @@ export const useHandleDeleteFile = (fileIds: string[]) => {
export const useSelectFileListLoading = () => {
return useOneNamespaceEffectsLoading('fileManager', ['listFile']);
};
export const useHandleUploadFile = () => {
const {
visible: fileUploadVisible,
hideModal: hideFileUploadModal,
showModal: showFileUploadModal,
} = useSetModalState();
const uploadFile = useUploadFile();
const id = useGetFolderId();
const onFileUploadOk = useCallback(
async (fileList: UploadFile[]) => {
console.info('fileList', fileList);
if (fileList.length > 0) {
const ret = await uploadFile(
fileList[0],
id,
getFilePathByWebkitRelativePath(fileList[0] as any),
);
if (ret === 0) {
hideFileUploadModal();
}
}
},
[uploadFile, hideFileUploadModal, id],
);
const loading = useOneNamespaceEffectsLoading('fileManager', ['uploadFile']);
return {
fileUploadLoading: loading,
onFileUploadOk,
fileUploadVisible,
hideFileUploadModal,
showFileUploadModal,
};
};
export const useHandleConnectToKnowledge = () => {
const {
visible: connectToKnowledgeVisible,
hideModal: hideConnectToKnowledgeModal,
showModal: showConnectToKnowledgeModal,
} = useSetModalState();
const connectToKnowledge = useConnectToKnowledge();
const id = useGetFolderId();
const [fileIds, setFileIds] = useState<string[]>([]);
const onConnectToKnowledgeOk = useCallback(
async (knowledgeIds: string[]) => {
const ret = await connectToKnowledge({
parentId: id,
fileIds,
kbIds: knowledgeIds,
});
if (ret === 0) {
hideConnectToKnowledgeModal();
}
},
[connectToKnowledge, hideConnectToKnowledgeModal, id, fileIds],
);
const loading = useOneNamespaceEffectsLoading('fileManager', [
'connectFileToKnowledge',
]);
const handleShowConnectToKnowledgeModal = useCallback(
(ids: string[]) => {
setFileIds(ids);
showConnectToKnowledgeModal();
},
[showConnectToKnowledgeModal],
);
return {
connectToKnowledgeLoading: loading,
onConnectToKnowledgeOk,
connectToKnowledgeVisible,
hideConnectToKnowledgeModal,
showConnectToKnowledgeModal: handleShowConnectToKnowledgeModal,
};
};

View File

@ -7,13 +7,17 @@ import ActionCell from './action-cell';
import FileToolbar from './file-toolbar';
import {
useGetRowSelection,
useHandleConnectToKnowledge,
useHandleCreateFolder,
useHandleUploadFile,
useNavigateToOtherFolder,
useRenameCurrentFile,
useSelectFileListLoading,
} from './hooks';
import RenameModal from '@/components/rename-modal';
import ConnectToKnowledgeModal from './connect-to-knowledge-modal';
import FileUploadModal from './file-upload-modal';
import FolderCreateModal from './folder-create-modal';
import styles from './index.less';
@ -37,6 +41,14 @@ const FileManager = () => {
folderCreateLoading,
onFolderCreateOk,
} = useHandleCreateFolder();
const { fileUploadVisible, hideFileUploadModal, showFileUploadModal } =
useHandleUploadFile();
const {
connectToKnowledgeVisible,
hideConnectToKnowledgeModal,
showConnectToKnowledgeModal,
onConnectToKnowledgeOk,
} = useHandleConnectToKnowledge();
const columns: ColumnsType<IFile> = [
{
@ -64,6 +76,17 @@ const FileManager = () => {
return formatDate(text);
},
},
{
title: 'kbs_info',
dataIndex: 'kbs_info',
key: 'kbs_info',
render(value) {
console.info(value);
return Array.isArray(value)
? value?.map((x) => x.kb_name).join(',')
: '';
},
},
{
title: 'Location',
dataIndex: 'location',
@ -80,6 +103,7 @@ const FileManager = () => {
console.info(record);
}}
showRenameModal={showFileRenameModal}
showConnectToKnowledgeModal={showConnectToKnowledgeModal}
></ActionCell>
),
},
@ -90,6 +114,7 @@ const FileManager = () => {
<FileToolbar
selectedRowKeys={rowSelection.selectedRowKeys as string[]}
showFolderCreateModal={showFolderCreateModal}
showFileUploadModal={showFileUploadModal}
></FileToolbar>
<Table
dataSource={fileList}
@ -111,6 +136,15 @@ const FileManager = () => {
hideModal={hideFolderCreateModal}
onOk={onFolderCreateOk}
></FolderCreateModal>
<FileUploadModal
visible={fileUploadVisible}
hideModal={hideFileUploadModal}
></FileUploadModal>
<ConnectToKnowledgeModal
visible={connectToKnowledgeVisible}
hideModal={hideConnectToKnowledgeModal}
onOk={onConnectToKnowledgeOk}
></ConnectToKnowledgeModal>
</section>
);
};

View File

@ -56,6 +56,20 @@ const model: DvaModel<FileManagerModelState> = {
}
return data.retcode;
},
*uploadFile({ payload = {} }, { call, put }) {
const formData = new FormData();
formData.append('parent_id', payload.parentId);
formData.append('file', payload.file);
formData.append('path', payload.path);
const { data } = yield call(fileManagerService.uploadFile, formData);
if (data.retcode === 0) {
yield put({
type: 'listFile',
payload: { parentId: payload.parentId },
});
}
return data.retcode;
},
*createFolder({ payload = {} }, { call, put }) {
const { data } = yield call(fileManagerService.createFolder, payload);
if (data.retcode === 0) {
@ -79,6 +93,19 @@ const model: DvaModel<FileManagerModelState> = {
}
return data.retcode;
},
*connectFileToKnowledge({ payload = {} }, { call, put }) {
const { data } = yield call(
fileManagerService.connectFileToKnowledge,
omit(payload, 'parentId'),
);
if (data.retcode === 0) {
yield put({
type: 'listFile',
payload: { parentId: payload.parentId },
});
}
return data.retcode;
},
},
};
export default model;

View File

@ -228,7 +228,7 @@ const UserSettingModel = () => {
<section className={styles.modelWrapper}>
<SettingTitle
title={t('model')}
description={t('profileDescription')}
description={t('modelDescription')}
showRightButton
clickButton={showSystemSettingModal}
></SettingTitle>

View File

@ -9,6 +9,7 @@ const {
renameFile,
getAllParentFolder,
createFolder,
connectFileToKnowledge,
} = api;
const methods = {
@ -36,6 +37,10 @@ const methods = {
url: createFolder,
method: 'post',
},
connectFileToKnowledge: {
url: connectFileToKnowledge,
method: 'post',
},
} as const;
const fileManagerService = registerServer<keyof typeof methods>(

View File

@ -74,4 +74,5 @@ export default {
renameFile: `${api_host}/file/rename`,
getAllParentFolder: `${api_host}/file/all_parent_folder`,
createFolder: `${api_host}/file/create`,
connectFileToKnowledge: `${api_host}/file2document/convert`,
};

View File

@ -85,3 +85,12 @@ export const downloadFile = ({
downloadElement.click();
document.body.removeChild(downloadElement);
};
export const getFilePathByWebkitRelativePath = (file: File) => {
const path = file.webkitRelativePath;
return path;
// if (path !== '') {
// return path.slice(0, path.lastIndexOf('/'));
// }
// return path;
};