feat(data_process): 问答对数据评测体系与质量分雷达图
- 三层评测:规则层沿用原五维规则分,语义层用本地 BGE 向量算问答/来源 相关性,评审层复用生成模型按 rubric 打分(忠实度/正确性/清晰度等, 区分 standard/reasoning/dpo 输出类型),任一层失败自动降级 - 组合分 = 规则 35% + 语义 20% + 评审 45%,缺层自动重归一 - 新增 results/evaluate-batch 批量评测接口,镜像批量重生成的并发、 乐观锁与部分成功语义;生成阶段不再展示质量分 - 详情页与结果编辑页新增"数据评测"按钮和批量进度;质量分列悬停弹出 雷达图浮窗(评审 5 维 + 语义 2 维、三层分项、评审理由) - 手动编辑/恢复后重算规则与语义层并丢弃过期评审分,雷达图不再展示 失效数据
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import type { BulkResultRegenerationState, PreviewItem, ResultItem } from './types'
|
||||
import QualityRadarPopover from './QualityRadarPopover.vue'
|
||||
import type { BulkResultRegenerationState, PreviewItem, ResultEvaluationState, ResultItem } from './types'
|
||||
import type { DataProcessOutputType } from '@/types/dataProcess'
|
||||
|
||||
const props = defineProps<{
|
||||
@@ -9,6 +10,7 @@ const props = defineProps<{
|
||||
selectedId: string | null
|
||||
regeneratingResultId: string | null
|
||||
bulkRegeneration: BulkResultRegenerationState
|
||||
evaluation: ResultEvaluationState
|
||||
outputType: DataProcessOutputType
|
||||
}>()
|
||||
|
||||
@@ -17,6 +19,7 @@ const emit = defineEmits<{
|
||||
'update:field': [id: string, field: 'instruction' | 'input' | 'output' | 'chosen' | 'rejected', value: string]
|
||||
'regenerate:item': [id: string]
|
||||
'regenerate:all': []
|
||||
'evaluate:all': []
|
||||
}>()
|
||||
|
||||
const search = ref('')
|
||||
@@ -30,6 +33,15 @@ const bulkRegenerationActive = computed(() => props.bulkRegeneration.status ===
|
||||
const bulkRegenerationVisible = computed(() => (
|
||||
props.bulkRegeneration.status !== 'idle' && props.bulkRegeneration.total > 0
|
||||
))
|
||||
const evaluationActive = computed(() => props.evaluation.status === 'running')
|
||||
const evaluationVisible = computed(() => (
|
||||
props.evaluation.status !== 'idle' && props.evaluation.total > 0
|
||||
))
|
||||
const evaluationPercentage = computed(() => {
|
||||
if (!props.evaluation.total) return 0
|
||||
return Math.round((props.evaluation.completed / props.evaluation.total) * 100)
|
||||
})
|
||||
const evaluatedCount = computed(() => props.items.filter((item) => item.qualityDetails?.evaluated).length)
|
||||
const bulkRegenerationPercentage = computed(() => (
|
||||
props.bulkRegeneration.total > 0
|
||||
? Math.round((props.bulkRegeneration.completed / props.bulkRegeneration.total) * 100)
|
||||
@@ -98,21 +110,47 @@ function selectRelative(offset: number) {
|
||||
<template>
|
||||
<section class="result-step">
|
||||
<div class="result-workspace">
|
||||
<aside class="result-list-pane" :class="{ 'has-bulk-progress': bulkRegenerationVisible }">
|
||||
<aside class="result-list-pane" :class="{ 'has-bulk-progress': bulkRegenerationVisible || evaluationVisible }">
|
||||
<div class="pane-header result-list-header">
|
||||
<div class="result-list-title"><strong>生成结果</strong><span>共 {{ items.length }} 条</span></div>
|
||||
<el-button
|
||||
v-if="invalidCount > 0"
|
||||
size="small"
|
||||
plain
|
||||
type="primary"
|
||||
:loading="bulkRegenerationActive"
|
||||
:disabled="Boolean(regeneratingResultId) || bulkRegenerationActive"
|
||||
@click="emit('regenerate:all')"
|
||||
>
|
||||
<i v-if="!bulkRegenerationActive" class="fa fa-refresh" style="margin-right: 4px;" />
|
||||
{{ bulkRegenerationActive ? '重新生成中' : `全部重新生成(${invalidCount})` }}
|
||||
</el-button>
|
||||
<div class="result-list-title">
|
||||
<strong>生成结果</strong><span>共 {{ items.length }} 条<template v-if="evaluatedCount"> · 已评测 {{ evaluatedCount }}</template></span>
|
||||
</div>
|
||||
<div class="result-list-actions">
|
||||
<el-button
|
||||
size="small"
|
||||
plain
|
||||
:loading="evaluationActive"
|
||||
:disabled="!items.length || bulkRegenerationActive || Boolean(regeneratingResultId)"
|
||||
@click="emit('evaluate:all')"
|
||||
>
|
||||
<i v-if="!evaluationActive" class="fa fa-check-square-o" style="margin-right: 4px;" />
|
||||
数据评测
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="invalidCount > 0"
|
||||
size="small"
|
||||
plain
|
||||
type="primary"
|
||||
:loading="bulkRegenerationActive"
|
||||
:disabled="Boolean(regeneratingResultId) || bulkRegenerationActive || evaluationActive"
|
||||
@click="emit('regenerate:all')"
|
||||
>
|
||||
<i v-if="!bulkRegenerationActive" class="fa fa-refresh" style="margin-right: 4px;" />
|
||||
{{ bulkRegenerationActive ? '重新生成中' : `全部重新生成(${invalidCount})` }}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="evaluationVisible" class="bulk-regeneration-progress">
|
||||
<div>
|
||||
<span>数据评测 {{ evaluation.completed }} / {{ evaluation.total }}</span>
|
||||
<span>成功 {{ evaluation.succeeded }} · 失败 {{ evaluation.failed }}</span>
|
||||
</div>
|
||||
<el-progress
|
||||
:percentage="evaluationPercentage"
|
||||
:show-text="false"
|
||||
:stroke-width="5"
|
||||
:color="evaluation.failed > 0 ? '#d97706' : '#5b50f2'"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="bulkRegenerationVisible" class="bulk-regeneration-progress">
|
||||
<div>
|
||||
@@ -146,6 +184,23 @@ function selectRelative(offset: number) {
|
||||
<strong>{{ item.instruction || '未填写指令' }}</strong>
|
||||
<small>{{ outputType === 'dpo' ? (item.chosen || '未填写 Chosen') : (item.output || '未填写输出') }}</small>
|
||||
</span>
|
||||
<el-popover
|
||||
v-if="item.qualityScore != null && item.qualityDetails"
|
||||
placement="right"
|
||||
:width="296"
|
||||
trigger="hover"
|
||||
:show-after="150"
|
||||
popper-class="quality-radar-popper"
|
||||
>
|
||||
<template #reference>
|
||||
<span
|
||||
class="result-score"
|
||||
:class="item.qualityScore >= 80 ? 'is-success' : item.qualityScore >= 60 ? 'is-warning' : 'is-danger'"
|
||||
@click.stop
|
||||
>{{ item.qualityScore.toFixed(0) }}</span>
|
||||
</template>
|
||||
<QualityRadarPopover :quality="item.qualityDetails" :score="item.qualityScore" />
|
||||
</el-popover>
|
||||
<i v-if="itemRegenerating(item.id)" class="css-spinner" />
|
||||
<i
|
||||
v-else
|
||||
@@ -303,6 +358,42 @@ function selectRelative(offset: number) {
|
||||
}
|
||||
}
|
||||
|
||||
.result-list-actions {
|
||||
display: flex;
|
||||
flex: none;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.result-score {
|
||||
flex: none;
|
||||
min-width: 34px;
|
||||
padding: 2px 8px;
|
||||
border-radius: 10px;
|
||||
color: #475467;
|
||||
background: #f2f4f7;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
font-variant-numeric: tabular-nums;
|
||||
text-align: center;
|
||||
cursor: default;
|
||||
|
||||
&.is-success {
|
||||
color: #067647;
|
||||
background: #e6f4ee;
|
||||
}
|
||||
|
||||
&.is-warning {
|
||||
color: #b54708;
|
||||
background: #fef0c7;
|
||||
}
|
||||
|
||||
&.is-danger {
|
||||
color: #b42318;
|
||||
background: #fee4e2;
|
||||
}
|
||||
}
|
||||
|
||||
.pane-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
Reference in New Issue
Block a user