@@ -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 in putCount = numeric ( detail . value ? . in put_count )
return inputCount
? Number ( ( ( numeric ( detail . value ? . output _count ) / inputCount ) * 100 ) . toFixed ( 1 ) )
const out putCount = numeric ( detail . value ? . out put_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 safeS econds = Math . max ( 0 , Math . round ( Number ( seconds ) ) )
const providedDuration = detail . value ? . duration _seconds
let seconds = providedDuration == null ? null : numeric ( providedDuration )
if ( s econds == 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 _c ount) .toLocaleString ( ) } } < / strong >
< small > 来源 : { { sourceDatasetName } } < / small >
< span > { { inputMetricLabel } } < / span >
< strong > { { inputMetricC ount. 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 = "progress Percentage" :show-text = "false" :stroke-width = "5" / >
< span > 结果 保留率< / span >
< strong > { { retentionDenominator ? ` ${ retentionRate } % ` : '-' } } < / strong >
< el-progress :percentage = "retention Percentage" :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 _c ount) .toLocaleString ( ) } } < / strong > < / div >
< div > < span > 源文件 < / span > < strong > { { sourceFileC ount. 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 : 4 px 0 0 ; color : # 94 a3b8 ; font - size : 12 px ; }
}
. config - heading {
display : flex ;
align - items : center ;
justify - content : space - between ;
gap : 16 px ;
}
. config - actions {
display : flex ;
align - items : center ;
gap : 8 px ;
> span { color : # 94 a3b8 ; font - size : 12 px ; }
: deep ( . el - button ) { gap : 6 px ; }
}
. info - list { margin : 0 ; padding : 4 px 18 px 12 px ; }
. info - list > div {
min - height : 44 px ; border - bottom : 1 px solid # f1f5f9 ; display : grid ; grid - template - columns : 100 px minmax ( 0 , 1 fr ) ; align - items : center ;
@@ -718,7 +815,7 @@ onBeforeUnmount(() => {
. config - grid { margin : 0 ; padding : 8 px 18 px 16 px ; display : grid ; grid - template - columns : repeat ( 2 , minmax ( 0 , 1 fr ) ) ; column - gap : 40 px ; }
. config - grid > div { min - height : 48 px ; border - bottom : 1 px solid # f1f5f9 ; display : flex ; align - items : center ; justify - content : space - between ; gap : 20 px ; }
. config - grid dt { color : # 64748 b ; font - size : 12 px ; }
. config - grid dd { margin : 0 ; color : # 334155 ; font - size : 13 px ; text - align : right ; }
. config - grid dd { max - width : 68 % ; margin: 0 ; color : # 334155 ; font - size : 13 px ; overflow - wrap : anywhere ; text - align : right ; }
. result - toolbar { display : flex ; align - items : center ; justify - content : space - between ; gap : 20 px ; border - bottom : 1 px solid # eef0f3 ; }
. result - toolbar . section - heading { border - bottom : 0 ; }