drawerIndex.tsx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385
  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 [ sListFlag, setSListFlag ] = React.useState<boolean>();
  65. const [ cUpdateFlag, setCUpdateFlag ] = React.useState<boolean>();
  66. const [ detailFlag, setDetailFlag ] = React.useState<boolean>();
  67. const [ deleteFlag, setDeleteFlag ] = React.useState<boolean>();
  68. const [ createFlag, setCreateFlag ] = React.useState<boolean>();
  69. const [userInfoAll, setUserInfoAll] = React.useState<any>({});
  70. const props : UploadProps = {
  71. name: 'files',
  72. multiple: true,
  73. action: '/api/deepseek/api/uploadDocument/' + params.knowledgeId,
  74. headers: getHeaders(),
  75. beforeUpload( file, fileList ) {
  76. setUploadLoading( true );
  77. // const allowedExtensions = ['md', 'txt', 'pdf', 'jpg', 'png', 'jpeg', 'docx', 'xlsx', 'pptx', 'eml', 'csv', 'tar', 'gz', 'bz2', 'zip', 'rar', 'jar'];
  78. const allowedExtensions = [ 'txt', 'pdf', 'jpg', 'png', 'jpeg', 'doc', 'docx', 'ppt', 'pptx' ];
  79. // 检查文件类型
  80. for ( const file of fileList ) {
  81. const fileExt = file.name.split( '.' ).pop()?.toLowerCase();
  82. if ( !fileExt || !allowedExtensions.includes( fileExt ) ) {
  83. message.error( `不支持 ${ fileExt } 格式的文件上传` );
  84. setUploadLoading( false );
  85. return Upload.LIST_IGNORE;
  86. }
  87. }
  88. // 检查文件大小
  89. let totalSize = 0;
  90. for ( const file of fileList ) {
  91. const fileExt = file.name.split( '.' ).pop()?.toLowerCase();
  92. const fileSizeMB = file.size / 1024 / 1024;
  93. if ( fileSizeMB > 30 ) {
  94. message.error( '单个文件不能大于30M' );
  95. setUploadLoading( false );
  96. return Upload.LIST_IGNORE;
  97. }
  98. if ( [ 'jpg', 'png', 'jpeg' ].includes( fileExt! ) && fileSizeMB > 5 ) {
  99. message.error( '单张图片不能大于5M' );
  100. setUploadLoading( false );
  101. return Upload.LIST_IGNORE;
  102. }
  103. totalSize += fileSizeMB;
  104. }
  105. if ( totalSize > 125 ) {
  106. message.error( '文件总大小超过125M' );
  107. setUploadLoading( false );
  108. return Upload.LIST_IGNORE;
  109. }
  110. },
  111. onChange( info ) {
  112. const { status } = info.file;
  113. if ( status !== 'uploading' ) {
  114. console.log( status, 'status--uploading' );
  115. }
  116. if ( status === 'done' ) {
  117. console.log( status, 'status--done' );
  118. console.info( info.file.response, 'info.file.response.data' );
  119. if ( info.file.response.code === 200 && info.file.response.data === 1 ) {
  120. message.success( `${ info.file.name } file uploaded successfully.` );
  121. init( params.knowledgeId );
  122. }
  123. setUploadLoading( false );
  124. } else if ( status === 'error' ) {
  125. console.log( status, 'status--error' );
  126. // 检查是否是504超时错误
  127. const response = info.file.response;
  128. const error = info.file.error;
  129. // 检查HTML格式的504错误或其他504错误格式
  130. const responseStr = String(response || '');
  131. const errorStr = String(error || '');
  132. if (responseStr.includes('504') ||
  133. responseStr.includes('Gateway Time-out') ||
  134. responseStr.includes('<h1>504 Gateway Time-out</h1>') ||
  135. errorStr.includes('timeout') ||
  136. errorStr.includes('504')) {
  137. message.error( '上传文件超时,请修改文件后再上传' );
  138. } else {
  139. message.error( `${ info.file.name } 文件上传失败` );
  140. }
  141. setUploadLoading( false );
  142. }
  143. },
  144. onDrop( e ) {
  145. console.log( 'Dropped files', e.dataTransfer.files );
  146. },
  147. };
  148. const handleUpload = async () => {
  149. if ( fileList.length === 0 ) return;
  150. setUploading( true );
  151. const formData = new FormData();
  152. // 添加所有文件
  153. fileList.forEach( file => {
  154. if ( file.originFileObj ) {
  155. formData.append( 'files', file.originFileObj );
  156. }
  157. } );
  158. try {
  159. const res = await axios.post( '/api/deepseek/api/uploadDocument/' + params.knowledgeId, formData, {
  160. headers: { 'Content-Type': 'multipart/form-data' }
  161. } );
  162. message.success( `${ fileList.length }个文件上传成功` );
  163. setFileList( [] );
  164. } catch ( err: any ) {
  165. // 检查是否是504超时错误
  166. if (err?.response?.status === 504 || err?.code === 'ECONNABORTED' || String(err).includes('timeout')) {
  167. message.error( '上传文件超时,请修改文件后再上传' );
  168. } else {
  169. message.error( '上传失败' );
  170. }
  171. } finally {
  172. setUploading( false );
  173. }
  174. };
  175. React.useEffect( () => {
  176. init( params.knowledgeId );
  177. const cList = LocalStorage.getStatusFlag( 'deepseek:slice:list' );
  178. setSListFlag( cList );
  179. const cDetail = LocalStorage.getStatusFlag( 'deepseek:config:update' );
  180. setCUpdateFlag( cDetail );
  181. const detail = LocalStorage.getStatusFlag( 'deepseek:document:detail' );
  182. setDetailFlag( detail );
  183. const deleteF = LocalStorage.getStatusFlag( 'deepseek:document:delete' );
  184. setDeleteFlag( deleteF );
  185. const createF = LocalStorage.getStatusFlag( 'deepseek:document:create' );
  186. setCreateFlag( createF );
  187. setUserInfoAll(LocalStorage.getUserInfo());
  188. return () => reset();
  189. }, [params.knowledgeId] );
  190. const columns : TableColumnsType<Record> = [
  191. {
  192. title: '序号',
  193. dataIndex: 'index',
  194. width: 80,
  195. render: ( text, record, index ) => {
  196. return index + 1;
  197. }
  198. },
  199. {
  200. title: '文件名',
  201. dataIndex: 'name',
  202. width: 300,
  203. sorter: (a, b) => a.name.localeCompare(b.name),
  204. render: ( text, record ) => {
  205. return (
  206. `${ text }`
  207. )
  208. }
  209. },
  210. {
  211. title: '文件大小',
  212. dataIndex: 'length',
  213. width: 100,
  214. render: ( text ) => {
  215. if ( text ) {
  216. const size = ( text / 1024 / 1024 ).toFixed( 2 );
  217. return `${ size } M`;
  218. } else {
  219. return '--'
  220. }
  221. }
  222. },
  223. {
  224. title: '字符数量',
  225. dataIndex: 'wordNum',
  226. width: 100,
  227. render: ( text ) => {
  228. if ( text ) {
  229. return `${ text }`;
  230. } else {
  231. return '--'
  232. }
  233. }
  234. },
  235. {
  236. title: '分段',
  237. dataIndex: 'sliceTotal',
  238. width: 100,
  239. render: ( text ) => {
  240. if ( text ) {
  241. return `${ text }`;
  242. } else {
  243. return '--';
  244. }
  245. }
  246. },
  247. {
  248. title: '上传时间',
  249. dataIndex: 'createTime',
  250. width: 180,
  251. render: ( text ) => {
  252. if ( text ) {
  253. return dayjs( text ).format( 'YYYY-MM-DD HH:mm:ss' );
  254. } else {
  255. return '--';
  256. }
  257. }
  258. },
  259. {
  260. title: '更新时间',
  261. dataIndex: 'updateTime',
  262. width: 180,
  263. render: ( text ) => {
  264. if ( text ) {
  265. return dayjs( text ).format( 'YYYY-MM-DD HH:mm:ss' );
  266. } else {
  267. return '--';
  268. }
  269. }
  270. },
  271. {
  272. title: '操作',
  273. dataIndex: 'operation',
  274. width: 80,
  275. fixed: 'right',
  276. render: ( text, record:any ) => {
  277. return (
  278. <>
  279. {
  280. <a
  281. style={ { marginRight: 16 } }
  282. onClick={ () => {
  283. // window.location= record.url
  284. window.open(`${record.url}`, '_blank');
  285. // 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');
  286. } }
  287. >
  288. 查看
  289. </a>
  290. }
  291. </>
  292. )
  293. }
  294. }
  295. ];
  296. const paginationConfig : TablePaginationConfig = {
  297. // 显示数据总量
  298. showTotal: ( total : number ) => {
  299. return `共 ${ total } 条`;
  300. },
  301. // 展示分页条数切换
  302. showSizeChanger: true,
  303. // 指定每页显示条数
  304. pageSizeOptions: [ '10', '20', '50', '100' ],
  305. // 快速跳转至某页
  306. showQuickJumper: true,
  307. current: page.page,
  308. pageSize: page.size,
  309. total: page.total,
  310. onChange: async ( page, pageSize ) => {
  311. await onChangePagination( page, pageSize );
  312. },
  313. };
  314. return (
  315. <div className='knowledgeLibInfo'>
  316. <Spin spinning={ uploadLoading || listLoading }>
  317. {
  318. <>
  319. <div className='knowledgeLibInfo-table'>
  320. <Table
  321. scroll={ { x: 'max-content' } }
  322. rowKey={ ( record ) => record.documentId }
  323. loading={ listLoading }
  324. columns={ columns }
  325. dataSource={ list }
  326. pagination={ paginationConfig }
  327. />
  328. </div>
  329. {
  330. infoModalOpen &&
  331. <InfoModal
  332. id={ infoModalId }
  333. open={ infoModalOpen }
  334. onClickConfirm={ infoModalOnClickConfirm }
  335. onClickCancel={ infoModalOnClickCancel }
  336. />
  337. }
  338. {
  339. infoModalSettingOpen &&
  340. <InfoModalSetting
  341. id={ infoModalSettingId }
  342. open={ infoModalSettingOpen }
  343. onClickConfirm={ infoModalSettingOnClickConfirm }
  344. onClickCancel={ infoModalSettingOnClickCancel }
  345. />
  346. }
  347. </>
  348. }
  349. </Spin>
  350. </div>
  351. );
  352. };
  353. export default observer( KnowledgeLibInfo );