segmentation.tsx 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. import { apiInterceptors, getChunkStrategies, getDocumentList, syncBatchDocument } from '@/client/api';
  2. import { DoneIcon, FileError, PendingIcon, SyncIcon } from '@/components/icons';
  3. import { File, IChunkStrategyResponse, ISyncBatchParameter, StepChangeParams } from '@/types/knowledge';
  4. import Icon from '@ant-design/icons';
  5. import { Alert, Button, Collapse, Form, Spin, message } from 'antd';
  6. import { useEffect, useState } from 'react';
  7. import { useTranslation } from 'react-i18next';
  8. import StrategyForm from './strategy-form';
  9. type IProps = {
  10. spaceName: string;
  11. docType: string;
  12. handleStepChange: (params: StepChangeParams) => void;
  13. uploadFiles: Array<File>;
  14. };
  15. type FieldType = {
  16. fileStrategies: Array<ISyncBatchParameter>;
  17. };
  18. let intervalId: string | number | NodeJS.Timeout | undefined;
  19. export default function Segmentation(props: IProps) {
  20. const { spaceName, docType, uploadFiles, handleStepChange } = props;
  21. const { t } = useTranslation();
  22. const [form] = Form.useForm();
  23. const [files, setFiles] = useState(uploadFiles);
  24. const [loading, setLoading] = useState<boolean>();
  25. const [strategies, setStrategies] = useState<Array<IChunkStrategyResponse>>([]);
  26. const [syncStatus, setSyncStatus] = useState<string>('');
  27. async function getStrategies() {
  28. setLoading(true);
  29. const [, allStrategies] = await apiInterceptors(getChunkStrategies());
  30. setLoading(false);
  31. setStrategies((allStrategies || [])?.filter(i => i.type.indexOf(docType) > -1));
  32. }
  33. useEffect(() => {
  34. getStrategies();
  35. return () => {
  36. intervalId && clearInterval(intervalId);
  37. };
  38. // eslint-disable-next-line react-hooks/exhaustive-deps
  39. }, []);
  40. const handleFinish = async (data: FieldType) => {
  41. if (checkParameter(data)) {
  42. setLoading(true);
  43. const [, result] = await apiInterceptors(syncBatchDocument(spaceName, data.fileStrategies));
  44. setLoading(false);
  45. if (result?.tasks && result?.tasks?.length > 0) {
  46. message.success(`Segemation task start successfully. task id: ${result?.tasks.join(',')}`);
  47. setSyncStatus('RUNNING');
  48. const docIds = data.fileStrategies.map(i => i.doc_id);
  49. intervalId = setInterval(async () => {
  50. const status = await updateSyncStatus(docIds);
  51. if (status === 'FINISHED') {
  52. clearInterval(intervalId);
  53. setSyncStatus('FINISHED');
  54. message.success('Congratulation, All files sync successfully.');
  55. handleStepChange({
  56. label: 'finish',
  57. });
  58. } else if (status === 'FAILED') {
  59. clearInterval(intervalId);
  60. handleStepChange({
  61. label: 'finish',
  62. });
  63. }
  64. }, 3000);
  65. }
  66. }
  67. };
  68. function checkParameter(data: FieldType) {
  69. let checked = true;
  70. if (syncStatus === 'RUNNING') {
  71. checked = false;
  72. message.warning('The task is still running, do not submit it again.');
  73. }
  74. const { fileStrategies } = data;
  75. fileStrategies.map(item => {
  76. const name = item?.chunk_parameters?.chunk_strategy || 'Automatic';
  77. if (!name) {
  78. message.error(`Please select chunk strategy for ${item.name}.`);
  79. checked = false;
  80. }
  81. const strategy = strategies.filter(item => item.strategy === name)[0];
  82. const newParam: any = {
  83. chunk_strategy: item?.chunk_parameters?.chunk_strategy || 'Automatic',
  84. };
  85. if (strategy && strategy.parameters) {
  86. // remove unused parameter, otherwise api will failed.
  87. strategy.parameters.forEach(param => {
  88. const paramName = param.param_name;
  89. newParam[paramName] = (item?.chunk_parameters as any)[paramName];
  90. });
  91. }
  92. item.chunk_parameters = newParam;
  93. });
  94. return checked;
  95. }
  96. async function updateSyncStatus(docIds: Array<number>) {
  97. const [, docs] = await apiInterceptors(
  98. getDocumentList(spaceName as any, {
  99. doc_ids: docIds,
  100. }),
  101. );
  102. if (docs?.data && docs?.data.length > 0) {
  103. const copy = [...files!];
  104. // set file status one by one
  105. docs?.data.map(doc => {
  106. const file = copy?.filter(file => file.doc_id === doc.id)?.[0];
  107. if (file) {
  108. file.status = doc.status;
  109. }
  110. });
  111. setFiles(copy);
  112. // all doc sync finished
  113. if (docs?.data.every(item => item.status === 'FINISHED' || item.status === 'FAILED')) {
  114. return 'FINISHED';
  115. }
  116. }
  117. }
  118. function renderStrategy() {
  119. if (!strategies || !strategies.length) {
  120. return <Alert message={`Cannot find one strategy for ${docType} type knowledge.`} type='warning' />;
  121. }
  122. return (
  123. <Form.List name='fileStrategies'>
  124. {fields => {
  125. switch (docType) {
  126. case 'TEXT':
  127. case 'URL':
  128. case 'YUQUEURL':
  129. return fields?.map(field => (
  130. <StrategyForm
  131. key={field.key}
  132. strategies={strategies}
  133. docType={docType}
  134. fileName={files![field.name].name}
  135. field={field}
  136. />
  137. ));
  138. case 'DOCUMENT':
  139. return (
  140. <Collapse defaultActiveKey={0} size={files.length > 5 ? 'small' : 'middle'}>
  141. {fields?.map(field => (
  142. // field [{name: 0, key: 0, isListField: true, fieldKey: 0}, {name: 1, key: 1, isListField: true, fieldKey: 1}]
  143. <Collapse.Panel
  144. header={`${field.name + 1}. ${files![field.name].name}`}
  145. key={field.key}
  146. extra={renderSyncStatus(field.name)}
  147. >
  148. <StrategyForm
  149. strategies={strategies}
  150. docType={docType}
  151. fileName={files![field.name].name}
  152. field={field}
  153. />
  154. </Collapse.Panel>
  155. ))}
  156. </Collapse>
  157. );
  158. }
  159. }}
  160. </Form.List>
  161. );
  162. }
  163. function renderSyncStatus(index: number) {
  164. const status = files![index].status;
  165. switch (status) {
  166. case 'FINISHED':
  167. return <Icon component={DoneIcon} />;
  168. case 'RUNNING':
  169. return <Icon className='animate-spin animate-infinite' component={SyncIcon} />;
  170. case 'FAILED':
  171. return <Icon component={FileError} />;
  172. default:
  173. return <Icon component={PendingIcon} />;
  174. }
  175. }
  176. return (
  177. <Spin spinning={loading}>
  178. <Form
  179. labelCol={{ span: 6 }}
  180. wrapperCol={{ span: 18 }}
  181. labelAlign='right'
  182. form={form}
  183. size='large'
  184. className='mt-4'
  185. layout='horizontal'
  186. name='basic'
  187. autoComplete='off'
  188. initialValues={{
  189. fileStrategies: files,
  190. }}
  191. onFinish={handleFinish}
  192. >
  193. {renderStrategy()}
  194. <Form.Item className='mt-4'>
  195. <Button
  196. onClick={() => {
  197. handleStepChange({ label: 'back' });
  198. }}
  199. className='mr-4'
  200. >{`${t('Back')}`}</Button>
  201. <Button type='primary' htmlType='submit' loading={loading || syncStatus === 'RUNNING'}>
  202. {t('Process')}
  203. </Button>
  204. </Form.Item>
  205. </Form>
  206. </Spin>
  207. );
  208. }