doc-upload-form.tsx 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413
  1. import { addDocument, addYuque, apiInterceptors, uploadDocument } from '@/client/api';
  2. import { StepChangeParams } from '@/types/knowledge';
  3. import { InboxOutlined, MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
  4. import { Button, Form, Input, Spin, Typography, Upload, message } from 'antd';
  5. import { RcFile, UploadChangeParam } from 'antd/es/upload';
  6. import { default as classNames, default as cls } from 'classnames';
  7. import { useState } from 'react';
  8. import { useTranslation } from 'react-i18next';
  9. type FileParams = {
  10. file: RcFile;
  11. fileList: FileList;
  12. };
  13. type IProps = {
  14. className: string;
  15. handleStepChange: (params: StepChangeParams) => void;
  16. spaceName: string;
  17. docType: string;
  18. };
  19. type FieldType = {
  20. docName: string;
  21. textSource: string;
  22. originFileObj: FileParams;
  23. text: string;
  24. webPageUrl: string;
  25. questions: Record<string, any>[];
  26. doc_token?: string;
  27. };
  28. const { Dragger } = Upload;
  29. const { TextArea } = Input;
  30. export default function DocUploadForm(props: IProps) {
  31. const { className, handleStepChange, spaceName, docType } = props;
  32. const { t } = useTranslation();
  33. const [form] = Form.useForm();
  34. const [spinning, setSpinning] = useState<boolean>(false);
  35. const [files, setFiles] = useState<any>([]);
  36. const upload = async (data: FieldType) => {
  37. const { docName, textSource, text, webPageUrl, doc_token, questions = [] } = data;
  38. let docId: any;
  39. setSpinning(true);
  40. switch (docType) {
  41. case 'URL':
  42. [, docId] = await apiInterceptors(
  43. addDocument(spaceName as string, {
  44. doc_name: docName,
  45. content: webPageUrl,
  46. doc_type: 'URL',
  47. questions: questions?.map(item => item.question),
  48. }),
  49. );
  50. break;
  51. case 'TEXT':
  52. [, docId] = await apiInterceptors(
  53. addDocument(spaceName as string, {
  54. doc_name: docName,
  55. source: textSource,
  56. content: text,
  57. doc_type: 'TEXT',
  58. questions: questions.map(item => item.question),
  59. }),
  60. );
  61. break;
  62. case 'YUQUEURL':
  63. [, docId] = await apiInterceptors(
  64. addYuque({
  65. doc_name: docName,
  66. space_name: spaceName,
  67. content: webPageUrl,
  68. doc_type: 'YUQUEURL',
  69. doc_token: doc_token || '',
  70. questions: questions?.map(item => item.question),
  71. }),
  72. );
  73. break;
  74. }
  75. setSpinning(false);
  76. if (docType === 'DOCUMENT' && files.length < 1) {
  77. return message.error('Upload failed, please re-upload.');
  78. } else if (docType !== 'DOCUMENT' && !docId) {
  79. return message.error('Upload failed, please re-upload.');
  80. }
  81. handleStepChange({
  82. label: 'forward',
  83. files:
  84. docType === 'DOCUMENT'
  85. ? files
  86. : [
  87. {
  88. name: docName,
  89. doc_id: docId || -1,
  90. },
  91. ],
  92. });
  93. };
  94. const handleFileChange = ({ fileList }: UploadChangeParam) => {
  95. if (fileList.length === 0) {
  96. form.setFieldValue('originFileObj', null);
  97. }
  98. };
  99. const renderText = () => {
  100. return (
  101. <>
  102. <Form.Item<FieldType>
  103. label={`${t('Name')}:`}
  104. name='docName'
  105. rules={[{ required: true, message: t('Please_input_the_name') }]}
  106. >
  107. <Input className='mb-5 h-12' placeholder={t('Please_input_the_name')} />
  108. </Form.Item>
  109. <Form.Item<FieldType>
  110. label={`${t('Text_Source')}:`}
  111. name='textSource'
  112. rules={[{ required: true, message: t('Please_input_the_text_source') }]}
  113. >
  114. <Input className='mb-5 h-12' placeholder={t('Please_input_the_text_source')} />
  115. </Form.Item>
  116. <Form.Item<FieldType>
  117. label={`${t('Text')}:`}
  118. name='text'
  119. rules={[{ required: true, message: t('Please_input_the_description') }]}
  120. >
  121. <TextArea rows={4} />
  122. </Form.Item>
  123. <Form.Item<FieldType> label={`${t('Correlation_problem')}:`}>
  124. <Form.List name='questions'>
  125. {(fields, { add, remove }) => (
  126. <>
  127. {fields.map(({ key, name }) => (
  128. <div key={key} className={cls('flex flex-1 items-center gap-8 mb-6')}>
  129. <Form.Item label='' name={[name, 'question']} className='grow'>
  130. <Input placeholder={t('input_question')} />
  131. </Form.Item>
  132. <Form.Item>
  133. <MinusCircleOutlined
  134. onClick={() => {
  135. remove(name);
  136. }}
  137. />
  138. </Form.Item>
  139. </div>
  140. ))}
  141. <Form.Item>
  142. <Button
  143. type='dashed'
  144. onClick={() => {
  145. add();
  146. }}
  147. block
  148. icon={<PlusOutlined />}
  149. >
  150. {t('Add_problem')}
  151. </Button>
  152. </Form.Item>
  153. </>
  154. )}
  155. </Form.List>
  156. </Form.Item>
  157. </>
  158. );
  159. };
  160. const renderWebPage = () => {
  161. return (
  162. <>
  163. <Form.Item<FieldType>
  164. label={`${t('Name')}:`}
  165. name='docName'
  166. rules={[{ required: true, message: t('Please_input_the_name') }]}
  167. >
  168. <Input className='mb-5 h-12' placeholder={t('Please_input_the_name')} />
  169. </Form.Item>
  170. <Form.Item<FieldType>
  171. label={`${t('Web_Page_URL')}:`}
  172. name='webPageUrl'
  173. rules={[{ required: true, message: t('Please_input_the_Web_Page_URL') }]}
  174. >
  175. <Input className='mb-5 h-12' placeholder={t('Please_input_the_Web_Page_URL')} />
  176. </Form.Item>
  177. <Form.Item<FieldType> label={`${t('Correlation_problem')}:`}>
  178. <Form.List name='questions'>
  179. {(fields, { add, remove }) => (
  180. <>
  181. {fields.map(({ key, name }) => (
  182. <div key={key} className={cls('flex flex-1 items-center gap-8 mb-6')}>
  183. <Form.Item label='' name={[name, 'question']} className='grow'>
  184. <Input placeholder={t('input_question')} />
  185. </Form.Item>
  186. <Form.Item>
  187. <MinusCircleOutlined
  188. onClick={() => {
  189. remove(name);
  190. }}
  191. />
  192. </Form.Item>
  193. </div>
  194. ))}
  195. <Form.Item>
  196. <Button
  197. type='dashed'
  198. onClick={() => {
  199. add();
  200. }}
  201. block
  202. icon={<PlusOutlined />}
  203. >
  204. {t('Add_problem')}
  205. </Button>
  206. </Form.Item>
  207. </>
  208. )}
  209. </Form.List>
  210. </Form.Item>
  211. </>
  212. );
  213. };
  214. const renderYuquePage = () => {
  215. return (
  216. <>
  217. <Form.Item<FieldType>
  218. label={`${t('Name')}:`}
  219. name='docName'
  220. rules={[{ required: true, message: t('Please_input_the_name') }]}
  221. >
  222. <Input className='mb-5 h-12' placeholder={t('Please_input_the_name')} />
  223. </Form.Item>
  224. <Form.Item<FieldType>
  225. label={t('document_url')}
  226. name='webPageUrl'
  227. rules={[{ required: true, message: t('input_document_url') }]}
  228. >
  229. <Input className='mb-5 h-12' placeholder={t('input_document_url')} />
  230. </Form.Item>
  231. <Form.Item<FieldType>
  232. label={t('document_token')}
  233. name='doc_token'
  234. tooltip={
  235. <>
  236. {t('Get_token')}
  237. <Typography.Link href='https://yuque.antfin-inc.com/lark/openapi/dh8zp4' target='_blank'>
  238. {t('Reference_link')}
  239. </Typography.Link>
  240. </>
  241. }
  242. >
  243. <Input className='mb-5 h-12' placeholder={t('input_document_token')} />
  244. </Form.Item>
  245. <Form.Item<FieldType> label={`${t('Correlation_problem')}:`}>
  246. <Form.List name='questions'>
  247. {(fields, { add, remove }) => (
  248. <>
  249. {fields.map(({ key, name }) => (
  250. <div key={key} className={cls('flex flex-1 items-center gap-8 mb-6')}>
  251. <Form.Item label='' name={[name, 'question']} className='grow'>
  252. <Input placeholder={t('input_question')} />
  253. </Form.Item>
  254. <Form.Item>
  255. <MinusCircleOutlined
  256. onClick={() => {
  257. remove(name);
  258. }}
  259. />
  260. </Form.Item>
  261. </div>
  262. ))}
  263. <Form.Item>
  264. <Button
  265. type='dashed'
  266. onClick={() => {
  267. add();
  268. }}
  269. block
  270. icon={<PlusOutlined />}
  271. >
  272. {t('Add_problem')}
  273. </Button>
  274. </Form.Item>
  275. </>
  276. )}
  277. </Form.List>
  278. </Form.Item>
  279. </>
  280. );
  281. };
  282. const uploadFile = async (options: any) => {
  283. const { onSuccess, onError, file } = options;
  284. const formData = new FormData();
  285. const filename = file?.name;
  286. formData.append('doc_name', filename);
  287. formData.append('doc_file', file);
  288. formData.append('doc_type', 'DOCUMENT');
  289. const [, docId] = await apiInterceptors(uploadDocument(spaceName, formData));
  290. if (Number.isInteger(docId)) {
  291. onSuccess && onSuccess(docId || 0);
  292. setFiles((files: any) => {
  293. files.push({
  294. name: filename,
  295. doc_id: docId || -1,
  296. });
  297. return files;
  298. });
  299. } else {
  300. onError && onError({ name: '', message: '' });
  301. }
  302. };
  303. const renderDocument = () => {
  304. return (
  305. <>
  306. <Form.Item<FieldType> name='originFileObj' rules={[{ required: true, message: t('Please_select_file') }]}>
  307. <Dragger
  308. multiple
  309. onChange={handleFileChange}
  310. maxCount={100}
  311. accept='.pdf,.ppt,.pptx,.xls,.xlsx,.doc,.docx,.txt,.md,.zip,.csv'
  312. customRequest={uploadFile}
  313. >
  314. <p className='ant-upload-drag-icon'>
  315. <InboxOutlined />
  316. </p>
  317. <p style={{ color: 'rgb(22, 108, 255)', fontSize: '20px' }}>{t('Select_or_Drop_file')}</p>
  318. <p className='ant-upload-hint' style={{ color: 'rgb(22, 108, 255)' }}>
  319. PDF, PowerPoint, Excel, Word, Text, Markdown, Zip1, Csv
  320. </p>
  321. </Dragger>
  322. </Form.Item>
  323. <Form.Item<FieldType> label='关联问题:'>
  324. <Form.List name='questions'>
  325. {(fields, { add, remove }) => (
  326. <>
  327. {fields.map(({ key, name }) => (
  328. <div key={key} className={cls('flex flex-1 items-center gap-8 mb-6')}>
  329. <Form.Item label='' name={[name, 'question']} className='grow'>
  330. <Input placeholder='请输入问题' />
  331. </Form.Item>
  332. <Form.Item>
  333. <MinusCircleOutlined
  334. onClick={() => {
  335. remove(name);
  336. }}
  337. />
  338. </Form.Item>
  339. </div>
  340. ))}
  341. <Form.Item>
  342. <Button
  343. type='dashed'
  344. onClick={() => {
  345. add();
  346. }}
  347. block
  348. icon={<PlusOutlined />}
  349. >
  350. {t('Add_problem')}
  351. </Button>
  352. </Form.Item>
  353. </>
  354. )}
  355. </Form.List>
  356. </Form.Item>
  357. </>
  358. );
  359. };
  360. const renderFormContainer = () => {
  361. switch (docType) {
  362. case 'URL':
  363. return renderWebPage();
  364. case 'DOCUMENT':
  365. return renderDocument();
  366. case 'YUQUEURL':
  367. return renderYuquePage();
  368. default:
  369. return renderText();
  370. }
  371. };
  372. return (
  373. <Spin spinning={spinning}>
  374. <Form
  375. form={form}
  376. size='large'
  377. className={classNames('mt-4', className)}
  378. layout='vertical'
  379. name='basic'
  380. initialValues={{ remember: true }}
  381. autoComplete='off'
  382. onFinish={upload}
  383. >
  384. {renderFormContainer()}
  385. <Form.Item>
  386. <Button
  387. onClick={() => {
  388. handleStepChange({ label: 'back' });
  389. }}
  390. className='mr-4'
  391. >{`${t('Back')}`}</Button>
  392. <Button type='primary' loading={spinning} htmlType='submit'>
  393. {t('Next')}
  394. </Button>
  395. </Form.Item>
  396. </Form>
  397. </Spin>
  398. );
  399. }