import * as React from 'react'; import { generatePath, useParams, useLocation } from 'react-router-dom'; import { observer } from 'mobx-react'; import config, { getHeaders } from '@/apis/config'; import { Button, Table, TableColumnsType, Modal, TablePaginationConfig, Upload, UploadProps, message, Spin, Row, Col, Card } from 'antd'; import { EditOutlined, DeleteOutlined, InboxOutlined, PlusOutlined, ArrowLeftOutlined, CloseOutlined, BulbOutlined } from '@ant-design/icons'; import InfoModal from './components/InfoModal'; import InfoModalSetting from './components/InfoModalSetting'; import router from '@/router'; import { Record } from './types'; import dayjs from 'dayjs'; import axios from 'axios'; import LocalStorage from '@/LocalStorage'; import store from './store'; import './style.less'; const { Dragger } = Upload; const KnowledgeLibInfo : React.FC = () => { const { state, init, onClickModify, onClickDelete, onChangePagination, onClickDocumentDetail, infoModalOnClickConfirm, infoModalOnClickCancel, infoModalSettingOnClickConfirm, infoModalSettingOnClickCancel, onClickSettings, reset } = store; const { knowledge_id, listLoading, page, list, infoModalOpen, infoModalId, infoModalSettingOpen, infoModalSettingId, knowledgeDetail, } = state; const location = useLocation(); const [ uploadLoading, setUploadLoading ] = React.useState( false ); const params = useParams(); const [ fileList, setFileList ] = React.useState( [] ); const [ uploading, setUploading ] = React.useState( false ); const uploadMessageRef = React.useRef<(() => void) | null>( null ); const [ sListFlag, setSListFlag ] = React.useState(); const [ cUpdateFlag, setCUpdateFlag ] = React.useState(); const [ detailFlag, setDetailFlag ] = React.useState(); const [ deleteFlag, setDeleteFlag ] = React.useState(); const [ createFlag, setCreateFlag ] = React.useState(); const [userInfoAll, setUserInfoAll] = React.useState({}); // 新手引导(知识库文档)整体可见性(持久化到 localStorage) const [showDocGuide, setShowDocGuide] = React.useState(() => localStorage.getItem('knowledgeDocGuideHidden') !== 'true'); const hideDocGuide = () => { localStorage.setItem('knowledgeDocGuideHidden', 'true'); setShowDocGuide(false); }; const props : UploadProps = { name: 'files', multiple: true, action: '/api/deepseek/api/uploadDocument/' + params.knowledgeId, headers: getHeaders(), beforeUpload( file, fileList ) { setUploadLoading( true ); // const allowedExtensions = ['md', 'txt', 'pdf', 'jpg', 'png', 'jpeg', 'docx', 'xlsx', 'pptx', 'eml', 'csv', 'tar', 'gz', 'bz2', 'zip', 'rar', 'jar']; const allowedExtensions = [ 'txt', 'pdf', 'jpg', 'png', 'jpeg', 'doc', 'docx', 'ppt', 'pptx' ]; // 检查文件类型 for ( const file of fileList ) { const fileExt = file.name.split( '.' ).pop()?.toLowerCase(); if ( !fileExt || !allowedExtensions.includes( fileExt ) ) { message.error( `不支持 ${ fileExt } 格式的文件上传` ); setUploadLoading( false ); return Upload.LIST_IGNORE; } } // 检查文件大小 let totalSize = 0; for ( const file of fileList ) { const fileExt = file.name.split( '.' ).pop()?.toLowerCase(); const fileSizeMB = file.size / 1024 / 1024; if ( fileSizeMB > 30 ) { message.error( '单个文件不能大于30M' ); setUploadLoading( false ); return Upload.LIST_IGNORE; } if ( [ 'jpg', 'png', 'jpeg' ].includes( fileExt! ) && fileSizeMB > 5 ) { message.error( '单张图片不能大于5M' ); setUploadLoading( false ); return Upload.LIST_IGNORE; } totalSize += fileSizeMB; } if ( totalSize > 125 ) { message.error( '文件总大小超过125M' ); setUploadLoading( false ); return Upload.LIST_IGNORE; } }, onChange( info ) { const { status, name } = info.file; if ( status === 'uploading' ) { // 文件开始上传,显示持续提示 if ( !uploadMessageRef.current ) { const closeMessage = message.loading( `为了确保文件 "${name}" 的解析质量,系统需要一些时间进行准备(通常为2-20分钟)。辛苦您耐心等待,您可以继续使用其他功能,只要不关闭当前页面即可。`, 0 // 0表示不自动关闭 ); uploadMessageRef.current = closeMessage; } setUploadLoading( true ); } else if ( status === 'done' ) { console.log( status, 'status--done' ); console.info( info.file.response, 'info.file.response.data' ); // 关闭上传提示 if ( uploadMessageRef.current ) { uploadMessageRef.current(); uploadMessageRef.current = null; } if ( info.file.response.code === 200 && info.file.response.data === 1 ) { message.success( `${ info.file.name } file uploaded successfully.` ); init( params.knowledgeId ); } setUploadLoading( false ); } else if ( status === 'error' ) { console.log( status, 'status--error' ); // 关闭上传提示 if ( uploadMessageRef.current ) { uploadMessageRef.current(); uploadMessageRef.current = null; } // 检查是否是504超时错误 const response = info.file.response; const error = info.file.error; // 检查HTML格式的504错误或其他504错误格式 const responseStr = String(response || ''); const errorStr = String(error || ''); if (responseStr.includes('504') || responseStr.includes('Gateway Time-out') || responseStr.includes('

504 Gateway Time-out

') || errorStr.includes('timeout') || errorStr.includes('504')) { info.file.response = '上传文件超时,请修改文件后再上传'; info.file.error = '上传文件超时,请修改文件后再上传'; message.error( '上传文件超时,请修改文件后再上传' ); } else { message.error( `${ info.file.name } 文件上传失败` ); } setUploadLoading( false ); } }, onDrop( e ) { console.log( 'Dropped files', e.dataTransfer.files ); }, }; const handleUpload = async () => { if ( fileList.length === 0 ) return; setUploading( true ); const formData = new FormData(); // 添加所有文件 fileList.forEach( file => { if ( file.originFileObj ) { formData.append( 'files', file.originFileObj ); } } ); try { const res = await axios.post( '/api/deepseek/api/uploadDocument/' + params.knowledgeId, formData, { headers: { 'Content-Type': 'multipart/form-data' } } ); message.success( `${ fileList.length }个文件上传成功` ); setFileList( [] ); } catch ( err: any ) { // 检查是否是504超时错误 if (err?.response?.status === 504 || err?.code === 'ECONNABORTED' || String(err).includes('timeout')) { message.error( '上传文件超时,请修改文件后再上传' ); } else { message.error( '上传失败' ); } } finally { setUploading( false ); } }; React.useEffect( () => { init( params.knowledgeId ); const cList = LocalStorage.getStatusFlag( 'deepseek:slice:list' ); setSListFlag( cList ); const cDetail = LocalStorage.getStatusFlag( 'deepseek:config:update' ); setCUpdateFlag( cDetail ); const detail = LocalStorage.getStatusFlag( 'deepseek:document:detail' ); setDetailFlag( detail ); const deleteF = LocalStorage.getStatusFlag( 'deepseek:document:delete' ); setDeleteFlag( deleteF ); const createF = LocalStorage.getStatusFlag( 'deepseek:document:create' ); setCreateFlag( createF ); setUserInfoAll(LocalStorage.getUserInfo()); return () => reset(); }, [] ); const columns : TableColumnsType = [ { title: '序号', dataIndex: 'index', width: 80, render: ( text, record, index ) => { return index + 1; } }, { title: '文件名', dataIndex: 'name', width: 300, sorter: (a, b) => a.name.localeCompare(b.name), render: ( text, record ) => { return ( `${ text }` ) } }, { title: '文件大小', dataIndex: 'length', width: 100, render: ( text ) => { if ( text ) { const size = ( text / 1024 / 1024 ).toFixed( 2 ); return `${ size } M`; } else { return '--' } } }, { title: '字符数量', dataIndex: 'wordNum', width: 100, render: ( text ) => { if ( text ) { return `${ text }`; } else { return '--' } } }, { title: '分段', dataIndex: 'sliceTotal', width: 100, render: ( text ) => { if ( text ) { return `${ text }`; } else { return '--'; } } }, { title: '上传时间', dataIndex: 'createTime', width: 180, render: ( text ) => { if ( text ) { return dayjs( text ).format( 'YYYY-MM-DD HH:mm:ss' ); } else { return '--'; } } }, { title: '更新时间', dataIndex: 'updateTime', width: 180, render: ( text ) => { if ( text ) { return dayjs( text ).format( 'YYYY-MM-DD HH:mm:ss' ); } else { return '--'; } } }, { title: '操作', dataIndex: 'operation', width: 180, fixed: 'right', render: ( text, record:any ) => { return ( <> { (createFlag||userInfoAll.id===record.createBy) && { const path = generatePath( '/deepseek/knowledgeLib/:knowledgeId/:createBy/slice/:documentId/:embeddingId', { knowledgeId: params.knowledgeId as string, createBy: params.createBy as string, documentId: record.documentId, embeddingId: state.knowledgeDetail.embeddingId, } ); router.navigate( { pathname: path },{state:{ createBy:record.createBy}} ); } } > 切片 } { (cUpdateFlag||userInfoAll.id===record.createBy) && { onClickSettings( record.documentId ); } } > 配置 } { (cUpdateFlag||userInfoAll.id===record.createBy) && { onClickModify( record.documentId ); } }> } { (deleteFlag||userInfoAll.id===record.createBy) && { Modal.confirm( { title: '删除', content: `确定删除知识文件:${ record.name }吗?`, okType: 'danger', onOk: async () => { const userInfo = LocalStorage.getUserInfo(); await onClickDelete( record.documentId ); } } ); } } > } ) } } ]; const paginationConfig : TablePaginationConfig = { // 显示数据总量 showTotal: ( total : number ) => { return `共 ${ total } 条`; }, // 展示分页条数切换 showSizeChanger: true, // 指定每页显示条数 pageSizeOptions: [ '10', '20', '50', '100' ], // 快速跳转至某页 showQuickJumper: true, current: page.page, pageSize: page.size, total: page.total, onChange: async ( page, pageSize ) => { await onChangePagination( page, pageSize ); }, }; return (
{showDocGuide && (
提示:如何上传并管理知识库文档?
上传知识文档} >
支持多种格式(Word、PDF、图片等),单个/总大小有上限,上传后自动处理(约5-10分钟,如遇到超时,可等待几分钟后刷新页面,将自动成功)。
{/*
支持pdf,doc,docx文件格式上传,建议单个文件不要超过10M,页数控制在100页以内。尽量单文件上传。
*/}
step 1
查看切片与索引} >
自动解析成功后可查看切片数量与索引状态,必要时可重新生成或调整配置。
step 2
在应用中生效} >
在RAG应用中切片将会被模型检索并召回,形成大模型对话的依据语料。
step 3
)} { page.total === 0 &&
{ ((createFlag&&userInfoAll.id==='1')||userInfoAll.id===params?.createBy) &&

点击上传,或拖放文件到此处

{/* 支持文件格式md,txt,pdf,jpg,png,jpeg,docx,xlsx, pptx,eml,csv,单个文档 ≤ 30M,单张图片 ≤ 2.5M,文件总 大小不得超过125M. */} 支持pdf,doc,docx,jpg,png,jpeg文件格式上传,建议单个文件不要超过10M,页数控制在100页以内。尽量单文件上传。

}
} { page.total > 0 && <>
{ ((createFlag&&userInfoAll.id==='1')||userInfoAll.id===params?.createBy) && }
record.documentId } loading={ listLoading } columns={ columns } dataSource={ list } pagination={ paginationConfig } /> { infoModalOpen && } { infoModalSettingOpen && } } ); }; export default observer( KnowledgeLibInfo );