drawerIndex.tsx 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406
  1. import * as React from 'react';
  2. import { generatePath, useParams, useLocation } from 'react-router-dom';
  3. import { observer } from 'mobx-react';
  4. import config, { getHeaders } from '@/apis/config';
  5. import {
  6. Button,
  7. Table,
  8. TableColumnsType,
  9. Modal,
  10. TablePaginationConfig,
  11. Upload,
  12. UploadProps,
  13. message,
  14. Spin
  15. } from 'antd';
  16. import { EditOutlined, DeleteOutlined, InboxOutlined, PlusOutlined, ArrowLeftOutlined } from '@ant-design/icons';
  17. import InfoModal from './components/InfoModal';
  18. import InfoModalSetting from './components/InfoModalSetting';
  19. import router from '@/router';
  20. import { Record } from './types';
  21. import dayjs from 'dayjs';
  22. import axios from 'axios';
  23. import LocalStorage from '@/LocalStorage';
  24. import store from './store';
  25. import './style.less';
  26. const { Dragger } = Upload;
  27. interface Props{
  28. drawerItem: any;
  29. }
  30. const KnowledgeLibInfo : React.FC<Props> = ({drawerItem}:Props) => {
  31. const params = {
  32. knowledgeId: drawerItem.value,
  33. ...drawerItem,
  34. }
  35. const {
  36. state,
  37. init,
  38. onClickModify,
  39. onClickDelete,
  40. onChangePagination,
  41. onClickDocumentDetail,
  42. infoModalOnClickConfirm,
  43. infoModalOnClickCancel,
  44. infoModalSettingOnClickConfirm,
  45. infoModalSettingOnClickCancel,
  46. onClickSettings,
  47. reset
  48. } = store;
  49. const {
  50. knowledge_id,
  51. listLoading,
  52. page,
  53. list,
  54. infoModalOpen,
  55. infoModalId,
  56. infoModalSettingOpen,
  57. infoModalSettingId,
  58. knowledgeDetail,
  59. } = state;
  60. const location = useLocation();
  61. const [ uploadLoading, setUploadLoading ] = React.useState( false );
  62. const [ fileList, setFileList ] = React.useState<any[]>( [] );
  63. const [ uploading, setUploading ] = React.useState( false );
  64. const uploadMessageRef = React.useRef<(() => void) | null>( null );
  65. const [ sListFlag, setSListFlag ] = React.useState<boolean>();
  66. const [ cUpdateFlag, setCUpdateFlag ] = React.useState<boolean>();
  67. const [ detailFlag, setDetailFlag ] = React.useState<boolean>();
  68. const [ deleteFlag, setDeleteFlag ] = React.useState<boolean>();
  69. const [ createFlag, setCreateFlag ] = React.useState<boolean>();
  70. const [userInfoAll, setUserInfoAll] = React.useState<any>({});
  71. const props : UploadProps = {
  72. name: 'files',
  73. multiple: true,
  74. action: '/api/deepseek/api/uploadDocument/' + params.knowledgeId,
  75. headers: getHeaders(),
  76. beforeUpload( file, fileList ) {
  77. setUploadLoading( true );
  78. // const allowedExtensions = ['md', 'txt', 'pdf', 'jpg', 'png', 'jpeg', 'docx', 'xlsx', 'pptx', 'eml', 'csv', 'tar', 'gz', 'bz2', 'zip', 'rar', 'jar'];
  79. const allowedExtensions = [ 'txt', 'pdf', 'jpg', 'png', 'jpeg', 'doc', 'docx', 'ppt', 'pptx' ];
  80. // 检查文件类型
  81. for ( const file of fileList ) {
  82. const fileExt = file.name.split( '.' ).pop()?.toLowerCase();
  83. if ( !fileExt || !allowedExtensions.includes( fileExt ) ) {
  84. message.error( `不支持 ${ fileExt } 格式的文件上传` );
  85. setUploadLoading( false );
  86. return Upload.LIST_IGNORE;
  87. }
  88. }
  89. // 检查文件大小
  90. let totalSize = 0;
  91. for ( const file of fileList ) {
  92. const fileExt = file.name.split( '.' ).pop()?.toLowerCase();
  93. const fileSizeMB = file.size / 1024 / 1024;
  94. if ( fileSizeMB > 30 ) {
  95. message.error( '单个文件不能大于30M' );
  96. setUploadLoading( false );
  97. return Upload.LIST_IGNORE;
  98. }
  99. if ( [ 'jpg', 'png', 'jpeg' ].includes( fileExt! ) && fileSizeMB > 5 ) {
  100. message.error( '单张图片不能大于5M' );
  101. setUploadLoading( false );
  102. return Upload.LIST_IGNORE;
  103. }
  104. totalSize += fileSizeMB;
  105. }
  106. if ( totalSize > 125 ) {
  107. message.error( '文件总大小超过125M' );
  108. setUploadLoading( false );
  109. return Upload.LIST_IGNORE;
  110. }
  111. },
  112. onChange( info ) {
  113. const { status, name } = info.file;
  114. if ( status === 'uploading' ) {
  115. // 文件开始上传,显示持续提示
  116. if ( !uploadMessageRef.current ) {
  117. const closeMessage = message.loading(
  118. `文件 "${name}" 上传中...`,
  119. 0 // 0表示不自动关闭
  120. );
  121. uploadMessageRef.current = closeMessage;
  122. }
  123. setUploadLoading( true );
  124. } else if ( status === 'done' ) {
  125. console.log( status, 'status--done' );
  126. console.info( info.file.response, 'info.file.response.data' );
  127. // 关闭上传提示
  128. if ( uploadMessageRef.current ) {
  129. uploadMessageRef.current();
  130. uploadMessageRef.current = null;
  131. }
  132. if ( info.file.response.code === 200 && info.file.response.data === 1 ) {
  133. message.success( `${ info.file.name } 文件上传成功.` );
  134. init( params.knowledgeId );
  135. }
  136. setUploadLoading( false );
  137. } else if ( status === 'error' ) {
  138. console.log( status, 'status--error' );
  139. // 关闭上传提示
  140. if ( uploadMessageRef.current ) {
  141. uploadMessageRef.current();
  142. uploadMessageRef.current = null;
  143. }
  144. // 检查是否是504超时错误
  145. const response = info.file.response;
  146. const error = info.file.error;
  147. // 检查HTML格式的504错误或其他504错误格式
  148. const responseStr = String(response || '');
  149. const errorStr = String(error || '');
  150. if (responseStr.includes('504') ||
  151. responseStr.includes('Gateway Time-out') ||
  152. responseStr.includes('<h1>504 Gateway Time-out</h1>') ||
  153. errorStr.includes('timeout') ||
  154. errorStr.includes('504')) {
  155. // 替换文件对象中的错误信息,避免显示HTML内容
  156. info.file.response = '上传文件超时,请修改文件后再上传';
  157. info.file.error = '上传文件超时,请修改文件后再上传';
  158. message.error( '上传文件超时,请修改文件后再上传' );
  159. } else {
  160. message.error( `${ info.file.name } 文件上传失败` );
  161. }
  162. setUploadLoading( false );
  163. }
  164. },
  165. onDrop( e ) {
  166. console.log( 'Dropped files', e.dataTransfer.files );
  167. },
  168. };
  169. const handleUpload = async () => {
  170. if ( fileList.length === 0 ) return;
  171. setUploading( true );
  172. const formData = new FormData();
  173. // 添加所有文件
  174. fileList.forEach( file => {
  175. if ( file.originFileObj ) {
  176. formData.append( 'files', file.originFileObj );
  177. }
  178. } );
  179. try {
  180. const res = await axios.post( '/api/deepseek/api/uploadDocument/' + params.knowledgeId, formData, {
  181. headers: { 'Content-Type': 'multipart/form-data' }
  182. } );
  183. message.success( `${ fileList.length }个文件上传成功` );
  184. setFileList( [] );
  185. } catch ( err: any ) {
  186. // 检查是否是504超时错误
  187. if (err?.response?.status === 504 || err?.code === 'ECONNABORTED' || String(err).includes('timeout')) {
  188. message.error( '上传文件超时,请修改文件后再上传' );
  189. } else {
  190. message.error( '上传失败' );
  191. }
  192. } finally {
  193. setUploading( false );
  194. }
  195. };
  196. React.useEffect( () => {
  197. init( params.knowledgeId );
  198. const cList = LocalStorage.getStatusFlag( 'deepseek:slice:list' );
  199. setSListFlag( cList );
  200. const cDetail = LocalStorage.getStatusFlag( 'deepseek:config:update' );
  201. setCUpdateFlag( cDetail );
  202. const detail = LocalStorage.getStatusFlag( 'deepseek:document:detail' );
  203. setDetailFlag( detail );
  204. const deleteF = LocalStorage.getStatusFlag( 'deepseek:document:delete' );
  205. setDeleteFlag( deleteF );
  206. const createF = LocalStorage.getStatusFlag( 'deepseek:document:create' );
  207. setCreateFlag( createF );
  208. setUserInfoAll(LocalStorage.getUserInfo());
  209. return () => reset();
  210. }, [params.knowledgeId] );
  211. const columns : TableColumnsType<Record> = [
  212. {
  213. title: '序号',
  214. dataIndex: 'index',
  215. width: 80,
  216. render: ( text, record, index ) => {
  217. return index + 1;
  218. }
  219. },
  220. {
  221. title: '文件名',
  222. dataIndex: 'name',
  223. width: 300,
  224. sorter: (a, b) => a.name.localeCompare(b.name),
  225. render: ( text, record ) => {
  226. return (
  227. `${ text }`
  228. )
  229. }
  230. },
  231. {
  232. title: '文件大小',
  233. dataIndex: 'length',
  234. width: 100,
  235. render: ( text ) => {
  236. if ( text ) {
  237. const size = ( text / 1024 / 1024 ).toFixed( 2 );
  238. return `${ size } M`;
  239. } else {
  240. return '--'
  241. }
  242. }
  243. },
  244. {
  245. title: '字符数量',
  246. dataIndex: 'wordNum',
  247. width: 100,
  248. render: ( text ) => {
  249. if ( text ) {
  250. return `${ text }`;
  251. } else {
  252. return '--'
  253. }
  254. }
  255. },
  256. {
  257. title: '分段',
  258. dataIndex: 'sliceTotal',
  259. width: 100,
  260. render: ( text ) => {
  261. if ( text ) {
  262. return `${ text }`;
  263. } else {
  264. return '--';
  265. }
  266. }
  267. },
  268. {
  269. title: '上传时间',
  270. dataIndex: 'createTime',
  271. width: 180,
  272. render: ( text ) => {
  273. if ( text ) {
  274. return dayjs( text ).format( 'YYYY-MM-DD HH:mm:ss' );
  275. } else {
  276. return '--';
  277. }
  278. }
  279. },
  280. {
  281. title: '更新时间',
  282. dataIndex: 'updateTime',
  283. width: 180,
  284. render: ( text ) => {
  285. if ( text ) {
  286. return dayjs( text ).format( 'YYYY-MM-DD HH:mm:ss' );
  287. } else {
  288. return '--';
  289. }
  290. }
  291. },
  292. {
  293. title: '操作',
  294. dataIndex: 'operation',
  295. width: 80,
  296. fixed: 'right',
  297. render: ( text, record:any ) => {
  298. return (
  299. <>
  300. {
  301. <a
  302. style={ { marginRight: 16 } }
  303. onClick={ () => {
  304. // window.location= record.url
  305. window.open(`${record.url}`, '_blank');
  306. // window.open(`https://10.1.28.14:9000/deepseek-doc/%E6%95%B0%E5%AD%97%E9%A1%B9%E7%9B%AE%E7%AE%A1%E7%90%86%E5%B9%B3%E5%8F%B0%E7%94%A8%E6%88%B7%E6%93%8D%E4%BD%9C%E6%89%8B%E5%86%8C_%E9%83%A8%E5%88%863.pdf`, '_blank');
  307. } }
  308. >
  309. 查看
  310. </a>
  311. }
  312. </>
  313. )
  314. }
  315. }
  316. ];
  317. const paginationConfig : TablePaginationConfig = {
  318. // 显示数据总量
  319. showTotal: ( total : number ) => {
  320. return `共 ${ total } 条`;
  321. },
  322. // 展示分页条数切换
  323. showSizeChanger: true,
  324. // 指定每页显示条数
  325. pageSizeOptions: [ '10', '20', '50', '100' ],
  326. // 快速跳转至某页
  327. showQuickJumper: true,
  328. current: page.page,
  329. pageSize: page.size,
  330. total: page.total,
  331. onChange: async ( page, pageSize ) => {
  332. await onChangePagination( page, pageSize );
  333. },
  334. };
  335. return (
  336. <div className='knowledgeLibInfo'>
  337. <Spin spinning={ uploadLoading || listLoading }>
  338. {
  339. <>
  340. <div className='knowledgeLibInfo-table'>
  341. <Table
  342. scroll={ { x: 'max-content' } }
  343. rowKey={ ( record ) => record.documentId }
  344. loading={ listLoading }
  345. columns={ columns }
  346. dataSource={ list }
  347. pagination={ paginationConfig }
  348. />
  349. </div>
  350. {
  351. infoModalOpen &&
  352. <InfoModal
  353. id={ infoModalId }
  354. open={ infoModalOpen }
  355. onClickConfirm={ infoModalOnClickConfirm }
  356. onClickCancel={ infoModalOnClickCancel }
  357. />
  358. }
  359. {
  360. infoModalSettingOpen &&
  361. <InfoModalSetting
  362. id={ infoModalSettingId }
  363. open={ infoModalSettingOpen }
  364. onClickConfirm={ infoModalSettingOnClickConfirm }
  365. onClickCancel={ infoModalSettingOnClickCancel }
  366. />
  367. }
  368. </>
  369. }
  370. </Spin>
  371. </div>
  372. );
  373. };
  374. export default observer( KnowledgeLibInfo );