drawerIndex.tsx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379
  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. if (response && (String(response).includes('504') || String(response).includes('Gateway Time-out'))) {
  130. message.error( '上传文件超时,请修改文件后再上传' );
  131. } else if (error && (String(error).includes('timeout') || String(error).includes('504'))) {
  132. message.error( '上传文件超时,请修改文件后再上传' );
  133. } else {
  134. message.error( `${ info.file.name } 文件上传失败` );
  135. }
  136. setUploadLoading( false );
  137. }
  138. },
  139. onDrop( e ) {
  140. console.log( 'Dropped files', e.dataTransfer.files );
  141. },
  142. };
  143. const handleUpload = async () => {
  144. if ( fileList.length === 0 ) return;
  145. setUploading( true );
  146. const formData = new FormData();
  147. // 添加所有文件
  148. fileList.forEach( file => {
  149. if ( file.originFileObj ) {
  150. formData.append( 'files', file.originFileObj );
  151. }
  152. } );
  153. try {
  154. const res = await axios.post( '/api/deepseek/api/uploadDocument/' + params.knowledgeId, formData, {
  155. headers: { 'Content-Type': 'multipart/form-data' }
  156. } );
  157. message.success( `${ fileList.length }个文件上传成功` );
  158. setFileList( [] );
  159. } catch ( err: any ) {
  160. // 检查是否是504超时错误
  161. if (err?.response?.status === 504 || err?.code === 'ECONNABORTED' || String(err).includes('timeout')) {
  162. message.error( '上传文件超时,请修改文件后再上传' );
  163. } else {
  164. message.error( '上传失败' );
  165. }
  166. } finally {
  167. setUploading( false );
  168. }
  169. };
  170. React.useEffect( () => {
  171. init( params.knowledgeId );
  172. const cList = LocalStorage.getStatusFlag( 'deepseek:slice:list' );
  173. setSListFlag( cList );
  174. const cDetail = LocalStorage.getStatusFlag( 'deepseek:config:update' );
  175. setCUpdateFlag( cDetail );
  176. const detail = LocalStorage.getStatusFlag( 'deepseek:document:detail' );
  177. setDetailFlag( detail );
  178. const deleteF = LocalStorage.getStatusFlag( 'deepseek:document:delete' );
  179. setDeleteFlag( deleteF );
  180. const createF = LocalStorage.getStatusFlag( 'deepseek:document:create' );
  181. setCreateFlag( createF );
  182. setUserInfoAll(LocalStorage.getUserInfo());
  183. return () => reset();
  184. }, [params.knowledgeId] );
  185. const columns : TableColumnsType<Record> = [
  186. {
  187. title: '序号',
  188. dataIndex: 'index',
  189. width: 80,
  190. render: ( text, record, index ) => {
  191. return index + 1;
  192. }
  193. },
  194. {
  195. title: '文件名',
  196. dataIndex: 'name',
  197. width: 300,
  198. sorter: (a, b) => a.name.localeCompare(b.name),
  199. render: ( text, record ) => {
  200. return (
  201. `${ text }`
  202. )
  203. }
  204. },
  205. {
  206. title: '文件大小',
  207. dataIndex: 'length',
  208. width: 100,
  209. render: ( text ) => {
  210. if ( text ) {
  211. const size = ( text / 1024 / 1024 ).toFixed( 2 );
  212. return `${ size } M`;
  213. } else {
  214. return '--'
  215. }
  216. }
  217. },
  218. {
  219. title: '字符数量',
  220. dataIndex: 'wordNum',
  221. width: 100,
  222. render: ( text ) => {
  223. if ( text ) {
  224. return `${ text }`;
  225. } else {
  226. return '--'
  227. }
  228. }
  229. },
  230. {
  231. title: '分段',
  232. dataIndex: 'sliceTotal',
  233. width: 100,
  234. render: ( text ) => {
  235. if ( text ) {
  236. return `${ text }`;
  237. } else {
  238. return '--';
  239. }
  240. }
  241. },
  242. {
  243. title: '上传时间',
  244. dataIndex: 'createTime',
  245. width: 180,
  246. render: ( text ) => {
  247. if ( text ) {
  248. return dayjs( text ).format( 'YYYY-MM-DD HH:mm:ss' );
  249. } else {
  250. return '--';
  251. }
  252. }
  253. },
  254. {
  255. title: '更新时间',
  256. dataIndex: 'updateTime',
  257. width: 180,
  258. render: ( text ) => {
  259. if ( text ) {
  260. return dayjs( text ).format( 'YYYY-MM-DD HH:mm:ss' );
  261. } else {
  262. return '--';
  263. }
  264. }
  265. },
  266. {
  267. title: '操作',
  268. dataIndex: 'operation',
  269. width: 80,
  270. fixed: 'right',
  271. render: ( text, record:any ) => {
  272. return (
  273. <>
  274. {
  275. <a
  276. style={ { marginRight: 16 } }
  277. onClick={ () => {
  278. // window.location= record.url
  279. window.open(`${record.url}`, '_blank');
  280. // 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');
  281. } }
  282. >
  283. 查看
  284. </a>
  285. }
  286. </>
  287. )
  288. }
  289. }
  290. ];
  291. const paginationConfig : TablePaginationConfig = {
  292. // 显示数据总量
  293. showTotal: ( total : number ) => {
  294. return `共 ${ total } 条`;
  295. },
  296. // 展示分页条数切换
  297. showSizeChanger: true,
  298. // 指定每页显示条数
  299. pageSizeOptions: [ '10', '20', '50', '100' ],
  300. // 快速跳转至某页
  301. showQuickJumper: true,
  302. current: page.page,
  303. pageSize: page.size,
  304. total: page.total,
  305. onChange: async ( page, pageSize ) => {
  306. await onChangePagination( page, pageSize );
  307. },
  308. };
  309. return (
  310. <div className='knowledgeLibInfo'>
  311. <Spin spinning={ uploadLoading || listLoading }>
  312. {
  313. <>
  314. <div className='knowledgeLibInfo-table'>
  315. <Table
  316. scroll={ { x: 'max-content' } }
  317. rowKey={ ( record ) => record.documentId }
  318. loading={ listLoading }
  319. columns={ columns }
  320. dataSource={ list }
  321. pagination={ paginationConfig }
  322. />
  323. </div>
  324. {
  325. infoModalOpen &&
  326. <InfoModal
  327. id={ infoModalId }
  328. open={ infoModalOpen }
  329. onClickConfirm={ infoModalOnClickConfirm }
  330. onClickCancel={ infoModalOnClickCancel }
  331. />
  332. }
  333. {
  334. infoModalSettingOpen &&
  335. <InfoModalSetting
  336. id={ infoModalSettingId }
  337. open={ infoModalSettingOpen }
  338. onClickConfirm={ infoModalSettingOnClickConfirm }
  339. onClickCancel={ infoModalSettingOnClickCancel }
  340. />
  341. }
  342. </>
  343. }
  344. </Spin>
  345. </div>
  346. );
  347. };
  348. export default observer( KnowledgeLibInfo );