balibabu
feat: add batch operations for document list (#302)
e52051a
raw
history blame
8.56 kB
import { ReactComponent as SelectFilesEndIcon } from '@/assets/svg/select-files-end.svg';
import { ReactComponent as SelectFilesStartIcon } from '@/assets/svg/select-files-start.svg';
import ChunkMethodModal from '@/components/chunk-method-modal';
import { KnowledgeRouteKey } from '@/constants/knowledge';
import {
useRunDocument,
useSelectDocumentList,
useUploadDocument,
} from '@/hooks/documentHooks';
import {
useDeleteDocumentById,
useFetchKnowledgeDetail,
useKnowledgeBaseId,
} from '@/hooks/knowledgeHook';
import {
useChangeDocumentParser,
useSetSelectedRecord,
} from '@/hooks/logicHooks';
import { useFetchTenantInfo } from '@/hooks/userSettingHook';
import { IKnowledgeFile } from '@/interfaces/database/knowledge';
import { getExtension, isFileUploadDone } from '@/utils/documentUtils';
import {
ArrowLeftOutlined,
CloudUploadOutlined,
DeleteOutlined,
EditOutlined,
FileDoneOutlined,
} from '@ant-design/icons';
import {
Button,
Card,
Flex,
Progress,
Space,
Upload,
UploadFile,
UploadProps,
} from 'antd';
import classNames from 'classnames';
import { ReactElement, useCallback, useMemo, useRef, useState } from 'react';
import { Link, useNavigate } from 'umi';
import { useTranslate } from '@/hooks/commonHooks';
import styles from './index.less';
const { Dragger } = Upload;
type UploadRequestOption = Parameters<
NonNullable<UploadProps['customRequest']>
>[0];
const UploaderItem = ({
file,
isUpload,
remove,
handleEdit,
}: {
isUpload: boolean;
originNode: ReactElement;
file: UploadFile;
fileList: object[];
showModal: () => void;
remove: (id: string) => void;
setRecord: (record: IKnowledgeFile) => void;
handleEdit: (id: string) => void;
}) => {
const { removeDocument } = useDeleteDocumentById();
const documentId = file?.response?.id;
const handleRemove = async () => {
if (file.status === 'error') {
remove(documentId);
} else {
const ret: any = await removeDocument(documentId);
if (ret === 0) {
remove(documentId);
}
}
};
const handleEditClick = () => {
if (file.status === 'done') {
handleEdit(documentId);
}
};
return (
<Card className={styles.uploaderItem}>
<Flex justify="space-between">
<FileDoneOutlined className={styles.fileIcon} />
<section className={styles.uploaderItemTextWrapper}>
<div>
<b>{file.name}</b>
</div>
<span>{file.size}</span>
</section>
{isUpload ? (
<DeleteOutlined
className={styles.deleteIcon}
onClick={handleRemove}
/>
) : (
<EditOutlined onClick={handleEditClick} />
)}
</Flex>
<Flex>
<Progress
showInfo={false}
percent={100}
className={styles.uploaderItemProgress}
strokeColor="
rgba(127, 86, 217, 1)
"
/>
<span>100%</span>
</Flex>
</Card>
);
};
const KnowledgeUploadFile = () => {
const knowledgeBaseId = useKnowledgeBaseId();
const [isUpload, setIsUpload] = useState(true);
const [uploadedFileIds, setUploadedFileIds] = useState<string[]>([]);
const fileListRef = useRef<UploadFile[]>([]);
const navigate = useNavigate();
const { currentRecord, setRecord } = useSetSelectedRecord();
const {
changeParserLoading,
onChangeParserOk,
changeParserVisible,
hideChangeParserModal,
showChangeParserModal,
} = useChangeDocumentParser(currentRecord.id);
const documentList = useSelectDocumentList();
const runDocumentByIds = useRunDocument();
const uploadDocument = useUploadDocument();
const { t } = useTranslate('knowledgeDetails');
const enabled = useMemo(() => {
if (isUpload) {
return (
uploadedFileIds.length > 0 &&
fileListRef.current.filter((x) => isFileUploadDone(x)).length ===
uploadedFileIds.length
);
}
return true;
}, [uploadedFileIds, isUpload]);
const createRequest: (props: UploadRequestOption) => void = async function ({
file,
onSuccess,
onError,
// onProgress,
}) {
const data = await uploadDocument(file as UploadFile);
if (data?.retcode === 0) {
setUploadedFileIds((pre) => {
return pre.concat(data.data.id);
});
if (onSuccess) {
onSuccess(data.data);
}
} else {
if (onError) {
onError(data?.data);
}
}
};
const removeIdFromUploadedIds = useCallback((id: string) => {
setUploadedFileIds((pre) => {
return pre.filter((x) => x !== id);
});
}, []);
const handleItemEdit = useCallback(
(id: string) => {
const document = documentList.find((x) => x.id === id);
if (document) {
setRecord(document);
}
showChangeParserModal();
},
[documentList, showChangeParserModal, setRecord],
);
const props: UploadProps = {
name: 'file',
multiple: true,
itemRender(originNode, file, fileList, actions) {
fileListRef.current = fileList;
const remove = (id: string) => {
if (isFileUploadDone(file)) {
removeIdFromUploadedIds(id);
}
actions.remove();
};
return (
<UploaderItem
isUpload={isUpload}
file={file}
fileList={fileList}
originNode={originNode}
remove={remove}
showModal={showChangeParserModal}
setRecord={setRecord}
handleEdit={handleItemEdit}
></UploaderItem>
);
},
customRequest: createRequest,
onDrop(e) {
console.log('Dropped files', e.dataTransfer.files);
},
};
const runSelectedDocument = () => {
const ids = fileListRef.current.map((x) => x.response.id);
runDocumentByIds({ doc_ids: ids, run: 1 });
};
const handleNextClick = () => {
if (!isUpload) {
runSelectedDocument();
navigate(`/knowledge/${KnowledgeRouteKey.Dataset}?id=${knowledgeBaseId}`);
} else {
setIsUpload(false);
}
};
useFetchTenantInfo();
useFetchKnowledgeDetail();
return (
<>
<div className={styles.uploadWrapper}>
<section>
<Space className={styles.backToList}>
<ArrowLeftOutlined />
<Link to={`/knowledge/dataset?id=${knowledgeBaseId}`}>
Back to select files
</Link>
</Space>
<div className={styles.progressWrapper}>
<Flex align="center" justify="center">
<SelectFilesStartIcon></SelectFilesStartIcon>
<Progress
percent={100}
showInfo={false}
className={styles.progress}
strokeColor="
rgba(127, 86, 217, 1)
"
/>
<SelectFilesEndIcon></SelectFilesEndIcon>
</Flex>
<Flex justify="space-around">
<p className={styles.selectFilesText}>
<b>{t('selectFiles')}</b>
</p>
<p className={styles.changeSpecificCategoryText}>
<b>{t('changeSpecificCategory')}</b>
</p>
</Flex>
</div>
</section>
<section className={styles.uploadContent}>
<Dragger
{...props}
className={classNames(styles.uploader, {
[styles.hiddenUploader]: !isUpload,
})}
>
<Button className={styles.uploaderButton}>
<CloudUploadOutlined className={styles.uploaderIcon} />
</Button>
<p className="ant-upload-text">{t('uploadTitle')}</p>
<p className="ant-upload-hint">{t('uploadDescription')}</p>
</Dragger>
</section>
<section className={styles.footer}>
<Button
type="primary"
className={styles.nextButton}
onClick={handleNextClick}
disabled={!enabled}
size="large"
>
{t('next', { keyPrefix: 'common' })}
</Button>
</section>
</div>
<ChunkMethodModal
documentId={currentRecord.id}
parserId={currentRecord.parser_id}
parserConfig={currentRecord.parser_config}
documentExtension={getExtension(currentRecord.name)}
onOk={onChangeParserOk}
visible={changeParserVisible}
hideModal={hideChangeParserModal}
loading={changeParserLoading}
/>
</>
);
};
export default KnowledgeUploadFile;