feat(data-process): 优化上传切分流程与PDF高亮预览
This commit is contained in:
@@ -9,6 +9,8 @@ const props = defineProps<{
|
||||
externalSource: ExternalDataSource
|
||||
externalPulling: boolean
|
||||
externalConnected: boolean
|
||||
previewBuilding: boolean
|
||||
sourceUploading: boolean
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
@@ -31,14 +33,23 @@ const AUTH_MODES = [
|
||||
|
||||
const FILE_PAGE_SIZE = 10
|
||||
const currentFilePage = ref(1)
|
||||
type FileStage = 'queued' | 'uploading' | 'waiting' | 'processing' | 'success' | 'upload-failed' | 'preview-failed'
|
||||
|
||||
const FILE_STAGE_META: Record<FileStage, { label: string; icon: string }> = {
|
||||
queued: { label: '等待上传', icon: 'fa-clock-o' },
|
||||
uploading: { label: '正在上传', icon: 'fa-cloud-upload' },
|
||||
waiting: { label: '等待切分', icon: 'fa-clock-o' },
|
||||
processing: { label: '正在切分', icon: 'fa-spinner fa-spin' },
|
||||
success: { label: '切分完成', icon: 'fa-check-circle' },
|
||||
'upload-failed': { label: '上传失败', icon: 'fa-exclamation-circle' },
|
||||
'preview-failed': { label: '切分失败', icon: 'fa-exclamation-circle' },
|
||||
}
|
||||
|
||||
const isExternal = computed(() => props.processType === 'external')
|
||||
|
||||
// 后端首版严格支持这些可验证的文本格式;不要把无法解析的二进制文档
|
||||
// 静默替换成示例正文。
|
||||
const uploadAccept = computed(() => props.processType === 'unstructured'
|
||||
? '.txt,.md,.json,.jsonl'
|
||||
: '.json,.jsonl,.csv,.txt,.md')
|
||||
? '.txt,.md,.markdown,.pdf,.docx,.pptx,.json,.jsonl,.ndjson'
|
||||
: '.json,.jsonl,.ndjson,.csv,.tsv,.xlsx')
|
||||
|
||||
const pagedUploadedFiles = computed(() => {
|
||||
const start = (currentFilePage.value - 1) * FILE_PAGE_SIZE
|
||||
@@ -67,6 +78,48 @@ function formatSize(size: number) {
|
||||
if (size >= 1024 * 1024) return `${(size / 1024 / 1024).toFixed(1)} MB`
|
||||
return `${(size / 1024).toFixed(1)} KB`
|
||||
}
|
||||
|
||||
function getFileStage(file: UploadedDataFile): FileStage {
|
||||
if (file.status === 'queued' || file.status === 'uploading') return file.status
|
||||
if (file.status === 'failed') return 'upload-failed'
|
||||
if (file.previewStatus === 'processing' || file.previewStatus === 'success') return file.previewStatus
|
||||
if (file.previewStatus === 'failed') return 'preview-failed'
|
||||
return 'waiting'
|
||||
}
|
||||
|
||||
function getFileProgress(file: UploadedDataFile) {
|
||||
const stage = getFileStage(file)
|
||||
const progress = stage === 'queued' || stage === 'uploading' || stage === 'upload-failed'
|
||||
? file.uploadProgress
|
||||
: stage === 'waiting' ? 100 : file.previewProgress ?? 0
|
||||
return Math.min(100, Math.max(0, progress))
|
||||
}
|
||||
|
||||
function isFileProcessing(file: UploadedDataFile) {
|
||||
return getFileStage(file) === 'processing'
|
||||
}
|
||||
|
||||
function getFileBarPercentage(file: UploadedDataFile) {
|
||||
// Element Plus 的不定进度动画需要非零宽度;这里不作为完成百分比展示。
|
||||
return isFileProcessing(file) ? 100 : getFileProgress(file)
|
||||
}
|
||||
|
||||
function getFileProgressStatus(file: UploadedDataFile): 'success' | 'exception' | undefined {
|
||||
const stage = getFileStage(file)
|
||||
if (stage === 'waiting' || stage === 'success') return 'success'
|
||||
if (stage === 'upload-failed' || stage === 'preview-failed') return 'exception'
|
||||
return undefined
|
||||
}
|
||||
|
||||
function getFileProgressText(file: UploadedDataFile) {
|
||||
if (isFileProcessing(file)) return '处理中'
|
||||
if (getFileStage(file) === 'waiting') return '已上传'
|
||||
return `${getFileProgress(file)}%`
|
||||
}
|
||||
|
||||
function getFileError(file: UploadedDataFile) {
|
||||
return file.uploadError || file.previewError
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -167,7 +220,7 @@ function formatSize(size: number) {
|
||||
<div class="external-actions">
|
||||
<el-button
|
||||
:loading="externalPulling && !externalConnected"
|
||||
:disabled="externalPulling"
|
||||
:disabled="externalPulling || previewBuilding"
|
||||
plain
|
||||
@click="emit('test-connection')"
|
||||
>
|
||||
@@ -176,7 +229,7 @@ function formatSize(size: number) {
|
||||
<el-button
|
||||
type="primary"
|
||||
:loading="externalPulling"
|
||||
:disabled="externalPulling"
|
||||
:disabled="externalPulling || previewBuilding"
|
||||
@click="emit('pull-data')"
|
||||
>
|
||||
拉取数据
|
||||
@@ -200,10 +253,34 @@ function formatSize(size: number) {
|
||||
<template v-if="file.count"> · {{ file.count.toLocaleString() }} 条</template>
|
||||
</span>
|
||||
</div>
|
||||
<span class="file-status"><i class="fa fa-check-circle" aria-hidden="true" /> 拉取成功</span>
|
||||
<div
|
||||
class="file-preview-progress"
|
||||
:class="`is-${getFileStage(file)}`"
|
||||
:title="getFileError(file)"
|
||||
>
|
||||
<div class="file-status" role="status" aria-live="polite">
|
||||
<span>
|
||||
<i class="fa" :class="FILE_STAGE_META[getFileStage(file)].icon" aria-hidden="true" />
|
||||
{{ FILE_STAGE_META[getFileStage(file)].label }}
|
||||
</span>
|
||||
<span class="file-progress-value">{{ getFileProgressText(file) }}</span>
|
||||
</div>
|
||||
<el-progress
|
||||
:percentage="getFileBarPercentage(file)"
|
||||
:indeterminate="isFileProcessing(file)"
|
||||
:duration="1.5"
|
||||
:stroke-width="5"
|
||||
:show-text="false"
|
||||
:status="getFileProgressStatus(file)"
|
||||
:aria-valuenow="isFileProcessing(file) ? undefined : getFileProgress(file)"
|
||||
:aria-valuetext="isFileProcessing(file) ? '正在切分,进度未知' : getFileProgressText(file)"
|
||||
/>
|
||||
<small v-if="getFileError(file)" class="file-preview-error">{{ getFileError(file) }}</small>
|
||||
</div>
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
:disabled="previewBuilding || file.status === 'uploading'"
|
||||
:aria-label="`删除数据集 ${file.name}`"
|
||||
@click="emit('remove-file', file.uid)"
|
||||
>
|
||||
@@ -233,7 +310,13 @@ function formatSize(size: number) {
|
||||
<h3 id="source-upload-title">源数据上传</h3>
|
||||
<p>上传后可在下一步检查内容和切分效果,支持同时添加多个文件</p>
|
||||
</div>
|
||||
<el-button v-if="uploadedFiles.length === 0" link type="primary" @click="emit('use-sample')">
|
||||
<el-button
|
||||
v-if="uploadedFiles.length === 0"
|
||||
link
|
||||
type="primary"
|
||||
:disabled="previewBuilding"
|
||||
@click="emit('use-sample')"
|
||||
>
|
||||
使用示例数据
|
||||
</el-button>
|
||||
</div>
|
||||
@@ -243,6 +326,7 @@ function formatSize(size: number) {
|
||||
drag
|
||||
multiple
|
||||
:accept="uploadAccept"
|
||||
:disabled="previewBuilding"
|
||||
:auto-upload="false"
|
||||
:show-file-list="false"
|
||||
:on-change="(file: UploadFile) => emit('file-change', file)"
|
||||
@@ -253,25 +337,29 @@ function formatSize(size: number) {
|
||||
<template #tip>
|
||||
<div class="el-upload__tip">
|
||||
{{ processType === 'unstructured'
|
||||
? '支持 TXT、Markdown、PDF、Word、JSON、JSONL,单文件不超过 200MB'
|
||||
: '支持 JSON、JSONL、CSV、Excel,单文件不超过 200MB' }}
|
||||
? '支持 TXT、MD、MARKDOWN、PDF、DOCX、PPTX、JSON、JSONL、NDJSON;旧版 DOC/PPT 请先转换,单文件不超过 200MB'
|
||||
: '支持 JSON、JSONL、NDJSON、CSV、TSV、XLSX;旧版 XLS 请先转换,单文件不超过 200MB' }}
|
||||
</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
|
||||
<section v-else class="uploaded-file-list" aria-label="已上传文件列表">
|
||||
<div class="uploaded-file-list-header">
|
||||
<span>已添加 {{ uploadedFiles.length }} 个文件</span>
|
||||
<span>
|
||||
已选择 {{ uploadedFiles.length }} 个文件
|
||||
<small v-if="sourceUploading" class="upload-queue-status"> · 正在逐个上传</small>
|
||||
</span>
|
||||
<div class="continue-upload">
|
||||
<el-upload
|
||||
multiple
|
||||
:accept="uploadAccept"
|
||||
:disabled="previewBuilding"
|
||||
:auto-upload="false"
|
||||
:show-file-list="false"
|
||||
:on-change="(file: UploadFile) => emit('file-change', file)"
|
||||
aria-label="继续添加源数据文件"
|
||||
>
|
||||
<el-button size="small" type="primary">继续上传</el-button>
|
||||
<el-button size="small" type="primary" :disabled="previewBuilding">继续上传</el-button>
|
||||
</el-upload>
|
||||
</div>
|
||||
</div>
|
||||
@@ -285,10 +373,34 @@ function formatSize(size: number) {
|
||||
<template v-if="file.count"> · {{ file.count.toLocaleString() }} 条</template>
|
||||
</span>
|
||||
</div>
|
||||
<span class="file-status"><i class="fa fa-check-circle" aria-hidden="true" /> 校验通过</span>
|
||||
<div
|
||||
class="file-preview-progress"
|
||||
:class="`is-${getFileStage(file)}`"
|
||||
:title="getFileError(file)"
|
||||
>
|
||||
<div class="file-status" role="status" aria-live="polite">
|
||||
<span>
|
||||
<i class="fa" :class="FILE_STAGE_META[getFileStage(file)].icon" aria-hidden="true" />
|
||||
{{ FILE_STAGE_META[getFileStage(file)].label }}
|
||||
</span>
|
||||
<span class="file-progress-value">{{ getFileProgressText(file) }}</span>
|
||||
</div>
|
||||
<el-progress
|
||||
:percentage="getFileBarPercentage(file)"
|
||||
:indeterminate="isFileProcessing(file)"
|
||||
:duration="1.5"
|
||||
:stroke-width="5"
|
||||
:show-text="false"
|
||||
:status="getFileProgressStatus(file)"
|
||||
:aria-valuenow="isFileProcessing(file) ? undefined : getFileProgress(file)"
|
||||
:aria-valuetext="isFileProcessing(file) ? '正在切分,进度未知' : getFileProgressText(file)"
|
||||
/>
|
||||
<small v-if="getFileError(file)" class="file-preview-error">{{ getFileError(file) }}</small>
|
||||
</div>
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
:disabled="previewBuilding || file.status === 'uploading'"
|
||||
:aria-label="`删除文件 ${file.name}`"
|
||||
@click="emit('remove-file', file.uid)"
|
||||
>
|
||||
@@ -441,6 +553,11 @@ function formatSize(size: number) {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.upload-queue-status {
|
||||
color: #5b50f2;
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
.uploaded-file-pagination {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
@@ -495,9 +612,59 @@ function formatSize(size: number) {
|
||||
}
|
||||
}
|
||||
|
||||
.file-preview-progress {
|
||||
display: flex;
|
||||
flex: 0 1 220px;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
min-width: 150px;
|
||||
|
||||
&.is-queued .file-status,
|
||||
&.is-waiting .file-status {
|
||||
color: #8a93a3;
|
||||
}
|
||||
|
||||
&.is-uploading .file-status,
|
||||
&.is-processing .file-status {
|
||||
color: #5b50f2;
|
||||
}
|
||||
|
||||
&.is-success .file-status {
|
||||
color: #2ca66a;
|
||||
}
|
||||
|
||||
&.is-upload-failed .file-status,
|
||||
&.is-preview-failed .file-status,
|
||||
.file-preview-error {
|
||||
color: #d94b4b;
|
||||
}
|
||||
}
|
||||
|
||||
.file-status {
|
||||
color: #2ca66a;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
font-size: 12px;
|
||||
|
||||
> span:first-child {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.file-progress-value {
|
||||
flex: 0 0 auto;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.file-preview-error {
|
||||
overflow: hidden;
|
||||
font-size: 11px;
|
||||
line-height: 1.4;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.uploaded-file :deep(.el-button) {
|
||||
@@ -514,8 +681,12 @@ function formatSize(size: number) {
|
||||
padding: 8px 10px;
|
||||
}
|
||||
|
||||
.file-status {
|
||||
.file-preview-progress {
|
||||
flex: 0 1 auto;
|
||||
min-width: 130px;
|
||||
}
|
||||
|
||||
.file-status {
|
||||
line-height: 1.4;
|
||||
white-space: normal;
|
||||
}
|
||||
@@ -545,7 +716,8 @@ function formatSize(size: number) {
|
||||
min-width: calc(100% - 40px);
|
||||
}
|
||||
|
||||
.file-status {
|
||||
.file-preview-progress {
|
||||
flex: 1 0 calc(100% - 38px);
|
||||
margin-left: 38px;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user