fix(data-process): 补齐原文参照与详情统计
This commit is contained in:
@@ -42,6 +42,7 @@ const savingResult = ref(false)
|
||||
const restoringResultId = ref<string | number | null>(null)
|
||||
const publishDialogVisible = ref(false)
|
||||
const publishing = ref(false)
|
||||
const configExpanded = ref(false)
|
||||
let resultFilterTimer: ReturnType<typeof setTimeout> | null = null
|
||||
|
||||
const editForm = reactive({ instruction: '', input: '', output: '' })
|
||||
@@ -90,8 +91,24 @@ const chunkMethodLabelMap: Record<string, string> = {
|
||||
custom: '自定义分隔符',
|
||||
}
|
||||
|
||||
function numeric(value: number | undefined) {
|
||||
return Number.isFinite(value) ? Number(value) : 0
|
||||
const preprocessOptionLabelMap: Record<string, string> = {
|
||||
clean_invalid: '清理无效数据',
|
||||
detect_structure: '识别表格结构',
|
||||
deduplicate: '重复数据去重',
|
||||
normalize_format: '数据格式标准化',
|
||||
filter_anomaly: '异常数据过滤',
|
||||
desensitize: '敏感信息脱敏',
|
||||
clean_invalid_content: '清理无效内容',
|
||||
detect_document_structure: '识别文档结构',
|
||||
merge_short_content: '合并过短内容',
|
||||
filter_low_quality: '过滤低质量内容',
|
||||
deduplicate_content: '重复内容去重',
|
||||
preserve_context: '保留上下文',
|
||||
}
|
||||
|
||||
function numeric(value: unknown) {
|
||||
const parsed = typeof value === 'number' ? value : Number(value)
|
||||
return Number.isFinite(parsed) ? parsed : 0
|
||||
}
|
||||
|
||||
function formatDateTime(value?: string | null) {
|
||||
@@ -101,13 +118,31 @@ function formatDateTime(value?: string | null) {
|
||||
}
|
||||
|
||||
const retentionRate = computed(() => {
|
||||
const inputCount = numeric(detail.value?.input_count)
|
||||
return inputCount
|
||||
? Number(((numeric(detail.value?.output_count) / inputCount) * 100).toFixed(1))
|
||||
const outputCount = numeric(detail.value?.output_count)
|
||||
const denominator = outputCount + numeric(detail.value?.filtered_count)
|
||||
return denominator
|
||||
? Number(((outputCount / denominator) * 100).toFixed(1))
|
||||
: 0
|
||||
})
|
||||
|
||||
const progressPercentage = computed(() => Math.min(100, Math.max(0, numeric(detail.value?.progress))))
|
||||
const retentionDenominator = computed(() => (
|
||||
numeric(detail.value?.output_count) + numeric(detail.value?.filtered_count)
|
||||
))
|
||||
const retentionPercentage = computed(() => Math.min(100, Math.max(0, retentionRate.value)))
|
||||
const progressPercentage = computed(() => (
|
||||
detail.value?.status === 'completed'
|
||||
? 100
|
||||
: Math.min(100, Math.max(0, numeric(detail.value?.progress)))
|
||||
))
|
||||
const isUnstructured = computed(() => detail.value?.process_type === 'unstructured')
|
||||
const sourceFileCount = computed(() => (
|
||||
numeric(detail.value?.source_file_count) || detail.value?.source_files?.length || 0
|
||||
))
|
||||
const previewCount = computed(() => numeric(detail.value?.preview_count))
|
||||
const inputMetricLabel = computed(() => isUnstructured.value ? '输入切片' : '输入记录')
|
||||
const inputMetricCount = computed(() => (
|
||||
isUnstructured.value ? previewCount.value : numeric(detail.value?.input_count)
|
||||
))
|
||||
const sourceDatasetName = computed(() => (
|
||||
detail.value?.source_dataset_name
|
||||
|| detail.value?.source_dataset
|
||||
@@ -125,22 +160,44 @@ const completeTime = computed(() => detail.value?.complete_time || detail.value?
|
||||
|
||||
const durationText = computed(() => {
|
||||
if (detail.value?.duration) return detail.value.duration
|
||||
const seconds = detail.value?.duration_seconds
|
||||
if (!Number.isFinite(seconds)) return detail.value?.status === 'running' ? '处理中' : '-'
|
||||
const safeSeconds = Math.max(0, Math.round(Number(seconds)))
|
||||
const providedDuration = detail.value?.duration_seconds
|
||||
let seconds = providedDuration == null ? null : numeric(providedDuration)
|
||||
if (seconds == null && startTime.value) {
|
||||
const started = new Date(startTime.value).getTime()
|
||||
const finished = completeTime.value
|
||||
? new Date(completeTime.value).getTime()
|
||||
: detail.value?.status === 'running' ? Date.now() : Number.NaN
|
||||
if (Number.isFinite(started) && Number.isFinite(finished) && finished >= started) {
|
||||
seconds = (finished - started) / 1000
|
||||
}
|
||||
}
|
||||
if (seconds == null) return detail.value?.status === 'running' ? '处理中' : '-'
|
||||
const safeSeconds = Math.max(0, Math.round(seconds))
|
||||
const hours = Math.floor(safeSeconds / 3600)
|
||||
const minutes = Math.floor(safeSeconds / 60)
|
||||
const restSeconds = safeSeconds % 60
|
||||
if (hours) return `${hours} 小时 ${minutes % 60} 分 ${restSeconds} 秒`
|
||||
return minutes ? `${minutes} 分 ${restSeconds} 秒` : `${restSeconds} 秒`
|
||||
})
|
||||
|
||||
const configRows = computed(() => Object.entries(detail.value?.config || {})
|
||||
.filter(([key]) => !/(?:password|secret|token|api_key)/i.test(key))
|
||||
.filter(([key]) => (
|
||||
key !== 'generation_model_snapshot'
|
||||
&& !/(?:password|secret|token|api_key)/i.test(key)
|
||||
))
|
||||
.map(([key, value]) => ({
|
||||
label: configLabelMap[key] || key.split('_').join(' '),
|
||||
value: formatConfigValue(key, value),
|
||||
})))
|
||||
|
||||
function formatConfigValue(key: string, value: unknown) {
|
||||
if (key === 'generation_model_id') {
|
||||
const snapshot = detail.value?.config?.generation_model_snapshot
|
||||
if (snapshot && typeof snapshot === 'object' && !Array.isArray(snapshot)) {
|
||||
const model = snapshot as Record<string, unknown>
|
||||
return String(model.name || model.display_name || model.model_name || value || '-')
|
||||
}
|
||||
}
|
||||
if (key === 'chunk_method' && typeof value === 'string') {
|
||||
return chunkMethodLabelMap[value] || value
|
||||
}
|
||||
@@ -148,7 +205,14 @@ function formatConfigValue(key: string, value: unknown) {
|
||||
const split = value as Partial<DataProcessDatasetSplit>
|
||||
return `训练集 ${split.train ?? 0}% / 验证集 ${split.validation ?? 0}% / 测试集 ${split.test ?? 0}%`
|
||||
}
|
||||
if (Array.isArray(value)) return value.length ? value.join('、') : '-'
|
||||
if (Array.isArray(value)) {
|
||||
if (key === 'preprocess_options') {
|
||||
return value.length
|
||||
? value.map((item) => preprocessOptionLabelMap[String(item)] || String(item)).join('、')
|
||||
: '-'
|
||||
}
|
||||
return value.length ? value.join('、') : '-'
|
||||
}
|
||||
if (typeof value === 'boolean') return value ? '是' : '否'
|
||||
if (value && typeof value === 'object') return JSON.stringify(value)
|
||||
return value == null || value === '' ? '-' : String(value)
|
||||
@@ -436,19 +500,19 @@ onBeforeUnmount(() => {
|
||||
<small>{{ completeTime ? `完成于 ${formatDateTime(completeTime)}` : `当前进度 ${progressPercentage}%` }}</small>
|
||||
</div>
|
||||
<div class="metric-card">
|
||||
<span>输入数据</span>
|
||||
<strong>{{ numeric(detail.input_count).toLocaleString() }}</strong>
|
||||
<small>来源:{{ sourceDatasetName }}</small>
|
||||
<span>{{ inputMetricLabel }}</span>
|
||||
<strong>{{ inputMetricCount.toLocaleString() }}</strong>
|
||||
<small>{{ sourceFileCount ? `源文件 ${sourceFileCount} 个:` : '来源:' }}{{ sourceDatasetName }}</small>
|
||||
</div>
|
||||
<div class="metric-card">
|
||||
<span>输出结果</span>
|
||||
<strong>{{ numeric(detail.output_count).toLocaleString() }}</strong>
|
||||
<small>{{ outputDatasetName || '尚未生成输出数据集' }}</small>
|
||||
<small>{{ outputDatasetName || '尚未发布为数据集' }}</small>
|
||||
</div>
|
||||
<div class="metric-card is-primary">
|
||||
<span>数据保留率</span>
|
||||
<strong>{{ numeric(detail.input_count) ? `${retentionRate}%` : '-' }}</strong>
|
||||
<el-progress :percentage="progressPercentage" :show-text="false" :stroke-width="5" />
|
||||
<span>结果保留率</span>
|
||||
<strong>{{ retentionDenominator ? `${retentionRate}%` : '-' }}</strong>
|
||||
<el-progress :percentage="retentionPercentage" :show-text="false" :stroke-width="5" />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -477,7 +541,7 @@ onBeforeUnmount(() => {
|
||||
link
|
||||
@click="router.push(`/dataset/${outputDatasetId}/preview`)"
|
||||
>{{ outputDatasetName }} <i class="fa fa-external-link" /></el-button>
|
||||
<span v-else>{{ outputDatasetName || '尚未生成' }}</span>
|
||||
<span v-else>{{ outputDatasetName || '尚未发布为数据集' }}</span>
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
@@ -488,7 +552,8 @@ onBeforeUnmount(() => {
|
||||
<div><h2 id="statistics-title">处理统计</h2><p>查看数据清洗、过滤和输出情况</p></div>
|
||||
</div>
|
||||
<div class="statistics-grid">
|
||||
<div><span>原始数据</span><strong>{{ numeric(detail.input_count).toLocaleString() }}</strong></div>
|
||||
<div><span>源文件</span><strong>{{ sourceFileCount.toLocaleString() }}</strong></div>
|
||||
<div><span>{{ inputMetricLabel }}</span><strong>{{ inputMetricCount.toLocaleString() }}</strong></div>
|
||||
<div><span>成功输出</span><strong>{{ numeric(detail.output_count).toLocaleString() }}</strong></div>
|
||||
<div><span>过滤数据</span><strong>{{ numeric(detail.filtered_count).toLocaleString() }}</strong></div>
|
||||
<div><span>重复数据</span><strong>{{ numeric(detail.duplicate_count).toLocaleString() }}</strong></div>
|
||||
@@ -499,13 +564,29 @@ onBeforeUnmount(() => {
|
||||
</div>
|
||||
|
||||
<section class="detail-section config-section" aria-labelledby="config-title">
|
||||
<div class="section-heading">
|
||||
<div class="section-heading config-heading">
|
||||
<div><h2 id="config-title">处理配置</h2><p>任务执行时使用的规则与参数</p></div>
|
||||
<div class="config-actions">
|
||||
<span>共 {{ configRows.length }} 项</span>
|
||||
<el-button
|
||||
text
|
||||
:aria-expanded="configExpanded"
|
||||
aria-controls="config-content"
|
||||
@click="configExpanded = !configExpanded"
|
||||
>
|
||||
{{ configExpanded ? '收起' : '展开' }}
|
||||
<i class="fa" :class="configExpanded ? 'fa-chevron-up' : 'fa-chevron-down'" />
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<dl v-if="configRows.length" class="config-grid">
|
||||
<div v-for="item in configRows" :key="item.label"><dt>{{ item.label }}</dt><dd>{{ item.value }}</dd></div>
|
||||
</dl>
|
||||
<div v-else class="compact-empty">暂无处理配置</div>
|
||||
<el-collapse-transition>
|
||||
<div v-show="configExpanded" id="config-content" class="config-content">
|
||||
<dl v-if="configRows.length" class="config-grid">
|
||||
<div v-for="item in configRows" :key="item.label"><dt>{{ item.label }}</dt><dd>{{ item.value }}</dd></div>
|
||||
</dl>
|
||||
<div v-else class="compact-empty">暂无处理配置</div>
|
||||
</div>
|
||||
</el-collapse-transition>
|
||||
</section>
|
||||
|
||||
<section class="detail-section result-section" aria-labelledby="result-title" v-loading="resultLoading">
|
||||
@@ -698,6 +779,22 @@ onBeforeUnmount(() => {
|
||||
p { margin: 4px 0 0; color: #94a3b8; font-size: 12px; }
|
||||
}
|
||||
|
||||
.config-heading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.config-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
> span { color: #94a3b8; font-size: 12px; }
|
||||
:deep(.el-button) { gap: 6px; }
|
||||
}
|
||||
|
||||
.info-list { margin: 0; padding: 4px 18px 12px; }
|
||||
.info-list > div {
|
||||
min-height: 44px; border-bottom: 1px solid #f1f5f9; display: grid; grid-template-columns: 100px minmax(0, 1fr); align-items: center;
|
||||
@@ -718,7 +815,7 @@ onBeforeUnmount(() => {
|
||||
.config-grid { margin: 0; padding: 8px 18px 16px; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: 40px; }
|
||||
.config-grid > div { min-height: 48px; border-bottom: 1px solid #f1f5f9; display: flex; align-items: center; justify-content: space-between; gap: 20px; }
|
||||
.config-grid dt { color: #64748b; font-size: 12px; }
|
||||
.config-grid dd { margin: 0; color: #334155; font-size: 13px; text-align: right; }
|
||||
.config-grid dd { max-width: 68%; margin: 0; color: #334155; font-size: 13px; overflow-wrap: anywhere; text-align: right; }
|
||||
|
||||
.result-toolbar { display: flex; align-items: center; justify-content: space-between; gap: 20px; border-bottom: 1px solid #eef0f3; }
|
||||
.result-toolbar .section-heading { border-bottom: 0; }
|
||||
|
||||
Reference in New Issue
Block a user