import {
ArrowLeftOutlined,
CheckCircleOutlined,
DeleteOutlined,
ExclamationCircleOutlined,
FileTextOutlined,
InboxOutlined,
LoadingOutlined,
QuestionCircleOutlined,
} from '@ant-design/icons';
import type { UploadFile } from 'antd';
import {
Button,
Card,
Checkbox,
Divider,
Empty,
Input,
InputNumber,
Progress,
Select,
Spin,
Tooltip,
Upload,
message,
} from 'antd';
import { useEffect, useState } from 'react';
import type { Segment } from '~/api/dify-dataset/type';
import { useDocumentUpload } from '~/hooks/dify-dataset-manager/document-upload';
import type { DocumentUploadProps, UploadedDocument } from '~/types/dify-dataset-manager/document-upload';
import { SUPPORTED_ACCEPT, SUPPORTED_FILE_EXTENSIONS, SUPPORTED_FORMATS } from '~/types/dify-dataset-manager/document-upload';
const { Dragger } = Upload;
const MAX_FILE_SIZE_MB = 15;
/**
* 文档上传组件
* 支持多文件上传,两步流程:选择文件 → 上传并配置分段
*/
export default function DocumentUpload({
datasetId,
onClose,
onSuccess,
}: DocumentUploadProps) {
const {
// 状态
step,
fileList,
uploadedDocuments,
currentSettings,
previewLoading,
// 方法
handleFileChange,
handleRemoveFile,
handleNextStep,
handleDocumentChange,
handleReprocess,
handlePrevStep,
handleGoToDocuments,
updateCurrentSettings,
// 计算属性方法
getCurrentDocument,
getCurrentProgress,
getStatusText,
isCurrentDocProcessing,
getCompletionStats,
} = useDocumentUpload(datasetId, onClose, onSuccess);
const selectedFiles = fileList.filter((f: UploadFile) => f.originFileObj).map((f: UploadFile) => f.originFileObj as File);
// 平滑进度条逻辑
const [displayPercent, setDisplayPercent] = useState(0);
const targetPercent = getCurrentProgress();
useEffect(() => {
if (targetPercent > displayPercent) {
// 如果目标进度大于当前显示进度,启动动画
const diff = targetPercent - displayPercent;
// 动态步长:差距越大跑得越快,但最小步长为1
const step = Math.max(1, Math.ceil(diff / 10));
const timer = requestAnimationFrame(() => {
setDisplayPercent(prev => Math.min(targetPercent, prev + step));
});
return () => cancelAnimationFrame(timer);
} else if (targetPercent < displayPercent && targetPercent === 0) {
// 如果目标重置为0(例如重新开始),立即重置
setDisplayPercent(0);
}
}, [targetPercent, displayPercent]);
/**
* 渲染步骤指示器(两步流程)
*/
const renderSteps = () => (
1 ? 'completed' : ''}`}>
1
选择数据源
1 ? 'completed' : ''}`}>
2
文本分段与清洗
);
/**
* 渲染第一步:选择文件(支持多文件)
*/
const renderStep1 = () => (
上传文本文件
文档需上传至知识智能理解法治知识库,广东烟草智能理解将按照于知识库,你可以在聊后指数文档所据案中检索它
{
const lowerName = file.name.toLowerCase();
const isSupported = SUPPORTED_FILE_EXTENSIONS.some((ext) => lowerName.endsWith(ext));
if (!isSupported) {
message.error(`仅支持上传 ${SUPPORTED_FORMATS} 格式的文件`);
return Upload.LIST_IGNORE;
}
const isWithinLimit = file.size / 1024 / 1024 <= MAX_FILE_SIZE_MB;
if (!isWithinLimit) {
message.error(`单个文件大小不能超过 ${MAX_FILE_SIZE_MB}MB`);
return Upload.LIST_IGNORE;
}
return false;
}}
multiple={true}
accept={SUPPORTED_ACCEPT}
showUploadList={false}
>
拖拽文件或至此,或者 选择文件
已支持 {SUPPORTED_FORMATS},每个文件不超过 15MB。支持批量上传多个文件。
{/* 已选文件列表 */}
{selectedFiles.length > 0 && (
嵌入已就绪 ({selectedFiles.length} 个文件)
{fileList.map((file: UploadFile) => (
{file.name}
{file.originFileObj
? `${file.originFileObj.type?.split('/')[1]?.toUpperCase() || 'FILE'},${(file.originFileObj.size / 1024 / 1024).toFixed(2)}MB`
: ''}
}
onClick={() => handleRemoveFile(file)}
className="remove-file-btn"
/>
))}
)}
);
/**
* 渲染第二步:分段配置与预览
* 左侧始终显示配置面板,右侧预览框内显示进度或分段内容
*/
const renderStep2 = () => {
const currentDoc = getCurrentDocument();
const isProcessing = isCurrentDocProcessing();
const stats = getCompletionStats();
return (
{/* 分段配置与预览 */}
{/* 左侧设置区域 */}
分段设置
{/* 分段标识符 */}
updateCurrentSettings('separator', e.target.value)}
placeholder="\n\n"
className="setting-input"
disabled={isProcessing}
/>
{/* 分段最大长度 */}
updateCurrentSettings('maxTokens', value || 1024)}
min={100}
max={4000}
className="setting-input-number"
disabled={isProcessing}
/>
characters
{/* 分段重叠长度 */}
updateCurrentSettings('chunkOverlap', value || 50)}
min={0}
max={500}
className="setting-input-number"
disabled={isProcessing}
/>
characters
{/* 文本预处理规则 */}
文本预处理规则
updateCurrentSettings('removeExtraSpaces', e.target.checked)}
disabled={isProcessing}
>
替换掉连续的空格、换行符和制表符
updateCurrentSettings('removeUrlsEmails', e.target.checked)}
disabled={isProcessing}
>
删除所有 URL 和电子邮件地址
{/* 索引方式 */}
索引方式
!isProcessing && updateCurrentSettings('indexingTechnique', 'high_quality')}
>
高质量
推荐
!isProcessing && updateCurrentSettings('indexingTechnique', 'economy')}
>
经济
{/* 操作按钮 */}
{/* 右侧预览区域 */}
预览
{uploadedDocuments.length > 0 && (
<>
}
className="preview-card"
>
{/* 处理进度(在预览框内显示) */}
{isProcessing ? (
{currentDoc?.file.name}
{getStatusText()}
) : currentDoc?.stage === 'error' ? (
{currentDoc.error || '处理失败'}
) : previewLoading ? (
) : (currentDoc?.segments?.length ?? 0) === 0 ? (
) : (
{currentDoc?.segments.map((segment: Segment, index: number) => (
#{index + 1}
{segment.word_count} 字符
{segment.content}
))}
)}
{/* 完成状态底部操作 */}
{stats.completed > 0 && (
{stats.completed}/{stats.total} 个文档处理完成
)}
);
};
return (
{/* 页面头部 */}
}
onClick={onClose}
className="back-btn"
>
知识库
{renderSteps()}
{/* 内容区域 */}
{step === 1 && renderStep1()}
{step === 2 && renderStep2()}
);
}