您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

index.tsx 7.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. import { ReactComponent as SelectFilesEndIcon } from '@/assets/svg/select-files-end.svg';
  2. import { ReactComponent as SelectFilesStartIcon } from '@/assets/svg/select-files-start.svg';
  3. import {
  4. useDeleteDocumentById,
  5. useFetchKnowledgeDetail,
  6. useGetDocumentDefaultParser,
  7. useKnowledgeBaseId,
  8. } from '@/hooks/knowledgeHook';
  9. import {
  10. useFetchTenantInfo,
  11. useSelectParserList,
  12. } from '@/hooks/userSettingHook';
  13. import uploadService from '@/services/uploadService';
  14. import {
  15. ArrowLeftOutlined,
  16. DeleteOutlined,
  17. EditOutlined,
  18. FileDoneOutlined,
  19. InboxOutlined,
  20. } from '@ant-design/icons';
  21. import {
  22. Button,
  23. Card,
  24. Flex,
  25. Popover,
  26. Progress,
  27. Radio,
  28. RadioChangeEvent,
  29. Space,
  30. Upload,
  31. UploadFile,
  32. UploadProps,
  33. } from 'antd';
  34. import classNames from 'classnames';
  35. import {
  36. ReactElement,
  37. useCallback,
  38. useEffect,
  39. useMemo,
  40. useRef,
  41. useState,
  42. } from 'react';
  43. import { Link, useDispatch, useNavigate } from 'umi';
  44. import { KnowledgeRouteKey } from '@/constants/knowledge';
  45. import { isFileUploadDone } from '@/utils/documentUtils';
  46. import styles from './index.less';
  47. const { Dragger } = Upload;
  48. type UploadRequestOption = Parameters<
  49. NonNullable<UploadProps['customRequest']>
  50. >[0];
  51. const UploaderItem = ({
  52. file,
  53. isUpload,
  54. remove,
  55. }: {
  56. isUpload: boolean;
  57. originNode: ReactElement;
  58. file: UploadFile;
  59. fileList: object[];
  60. remove: (id: string) => void;
  61. }) => {
  62. const { parserConfig, defaultParserId } = useGetDocumentDefaultParser();
  63. const { removeDocument } = useDeleteDocumentById();
  64. const [value, setValue] = useState(defaultParserId);
  65. const dispatch = useDispatch();
  66. const documentId = file?.response?.id;
  67. const parserList = useSelectParserList();
  68. const saveParser = (parserId: string) => {
  69. dispatch({
  70. type: 'kFModel/document_change_parser',
  71. payload: {
  72. parser_id: parserId,
  73. doc_id: documentId,
  74. parser_config: parserConfig,
  75. },
  76. });
  77. };
  78. const onChange = (e: RadioChangeEvent) => {
  79. const val = e.target.value;
  80. setValue(val);
  81. saveParser(val);
  82. };
  83. const content = (
  84. <Radio.Group onChange={onChange} value={value}>
  85. <Space direction="vertical">
  86. {parserList.map(
  87. (
  88. x, // value is lowercase, key is uppercase
  89. ) => (
  90. <Radio value={x.value} key={x.value}>
  91. {x.label}
  92. </Radio>
  93. ),
  94. )}
  95. </Space>
  96. </Radio.Group>
  97. );
  98. const handleRemove = async () => {
  99. if (file.status === 'error') {
  100. remove(documentId);
  101. } else {
  102. const ret: any = await removeDocument(documentId);
  103. if (ret === 0) {
  104. remove(documentId);
  105. }
  106. }
  107. };
  108. useEffect(() => {
  109. setValue(defaultParserId);
  110. }, [defaultParserId]);
  111. return (
  112. <Card className={styles.uploaderItem}>
  113. <Flex justify="space-between">
  114. <FileDoneOutlined className={styles.fileIcon} />
  115. <section className={styles.uploaderItemTextWrapper}>
  116. <div>
  117. <b>{file.name}</b>
  118. </div>
  119. <span>{file.size}</span>
  120. </section>
  121. {isUpload ? (
  122. <DeleteOutlined
  123. className={styles.deleteIcon}
  124. onClick={handleRemove}
  125. />
  126. ) : (
  127. <Popover content={content} placement="bottom">
  128. <EditOutlined />
  129. </Popover>
  130. )}
  131. </Flex>
  132. <Flex>
  133. <Progress
  134. showInfo={false}
  135. percent={100}
  136. className={styles.uploaderItemProgress}
  137. strokeColor="
  138. rgba(127, 86, 217, 1)
  139. "
  140. />
  141. <span>100%</span>
  142. </Flex>
  143. </Card>
  144. );
  145. };
  146. const KnowledgeUploadFile = () => {
  147. const knowledgeBaseId = useKnowledgeBaseId();
  148. const [isUpload, setIsUpload] = useState(true);
  149. const dispatch = useDispatch();
  150. const [uploadedFileIds, setUploadedFileIds] = useState<string[]>([]);
  151. const fileListRef = useRef<UploadFile[]>([]);
  152. const navigate = useNavigate();
  153. const enabled = useMemo(() => {
  154. if (isUpload) {
  155. return (
  156. uploadedFileIds.length > 0 &&
  157. fileListRef.current.filter((x) => isFileUploadDone(x)).length ===
  158. uploadedFileIds.length
  159. );
  160. }
  161. return true;
  162. }, [uploadedFileIds, isUpload]);
  163. const createRequest: (props: UploadRequestOption) => void = async function ({
  164. file,
  165. onSuccess,
  166. onError,
  167. // onProgress,
  168. }) {
  169. const ret = await uploadService.uploadFile(file, knowledgeBaseId);
  170. const data = ret?.data;
  171. if (data?.retcode === 0) {
  172. setUploadedFileIds((pre) => {
  173. return pre.concat(data.data.id);
  174. });
  175. if (onSuccess) {
  176. onSuccess(data.data);
  177. }
  178. } else {
  179. if (onError) {
  180. onError(data?.data);
  181. }
  182. }
  183. };
  184. const removeIdFromUploadedIds = useCallback((id: string) => {
  185. setUploadedFileIds((pre) => {
  186. return pre.filter((x) => x !== id);
  187. });
  188. }, []);
  189. const props: UploadProps = {
  190. name: 'file',
  191. multiple: true,
  192. itemRender(originNode, file, fileList, actions) {
  193. fileListRef.current = fileList;
  194. const remove = (id: string) => {
  195. if (isFileUploadDone(file)) {
  196. removeIdFromUploadedIds(id);
  197. }
  198. actions.remove();
  199. };
  200. return (
  201. <UploaderItem
  202. isUpload={isUpload}
  203. file={file}
  204. fileList={fileList}
  205. originNode={originNode}
  206. remove={remove}
  207. ></UploaderItem>
  208. );
  209. },
  210. customRequest: createRequest,
  211. onDrop(e) {
  212. console.log('Dropped files', e.dataTransfer.files);
  213. },
  214. };
  215. const runSelectedDocument = () => {
  216. const ids = fileListRef.current.map((x) => x.response.id);
  217. dispatch({
  218. type: 'kFModel/document_run',
  219. payload: { doc_ids: ids, run: 1 },
  220. });
  221. };
  222. const handleNextClick = () => {
  223. if (!isUpload) {
  224. runSelectedDocument();
  225. navigate(`/knowledge/${KnowledgeRouteKey.Dataset}?id=${knowledgeBaseId}`);
  226. } else {
  227. setIsUpload(false);
  228. }
  229. };
  230. useFetchTenantInfo();
  231. useFetchKnowledgeDetail();
  232. return (
  233. <div className={styles.uploadWrapper}>
  234. <section>
  235. <Space className={styles.backToList}>
  236. <ArrowLeftOutlined />
  237. <Link to={`/knowledge/dataset?id=${knowledgeBaseId}`}>
  238. Back to select files
  239. </Link>
  240. </Space>
  241. <div className={styles.progressWrapper}>
  242. <Flex align="center" justify="center">
  243. <SelectFilesStartIcon></SelectFilesStartIcon>
  244. <Progress
  245. percent={100}
  246. showInfo={false}
  247. className={styles.progress}
  248. strokeColor="
  249. rgba(127, 86, 217, 1)
  250. "
  251. />
  252. <SelectFilesEndIcon></SelectFilesEndIcon>
  253. </Flex>
  254. <Flex justify="space-around">
  255. <p className={styles.selectFilesText}>
  256. <b>Select files</b>
  257. </p>
  258. <p className={styles.changeSpecificCategoryText}>
  259. <b>Change specific category</b>
  260. </p>
  261. </Flex>
  262. </div>
  263. </section>
  264. <section className={styles.uploadContent}>
  265. <Dragger
  266. {...props}
  267. className={classNames(styles.uploader, {
  268. [styles.hiddenUploader]: !isUpload,
  269. })}
  270. >
  271. <p className="ant-upload-drag-icon">
  272. <InboxOutlined />
  273. </p>
  274. <p className="ant-upload-text">
  275. Click or drag file to this area to upload
  276. </p>
  277. <p className="ant-upload-hint">
  278. Support for a single or bulk upload. Strictly prohibited from
  279. uploading company data or other banned files.
  280. </p>
  281. </Dragger>
  282. </section>
  283. <section className={styles.footer}>
  284. <Button
  285. type="primary"
  286. // className={styles.nextButton}
  287. onClick={handleNextClick}
  288. disabled={!enabled}
  289. >
  290. Next
  291. </Button>
  292. </section>
  293. </div>
  294. );
  295. };
  296. export default KnowledgeUploadFile;