feat: 评测任务元数据增强与前端展示优化
后端: - 新增评测指标标签生成(_build_eval_metric_label/_basic_metric_labels) - eval 任务 payload 富化(_enrich_eval_payload): 解析模型名/数据集名/维度配置, 生成 metric_label - 任务创建时解析 trained_models/模型名, metric 回退为 metric_label - 运行时状态刷新增加 2s 节流, 避免频繁触发 前端: - EvalTask 类型增加 dataset_id/metric_label - 评测列表与详情展示 metric_label 指标标签, 模型名/指标超长 tooltip 省略 - 创建页改用 getComputeGpus 过滤空闲 GPU, 移除 getSystemInfo 依赖
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
<script setup lang="ts">
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref, watch } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ElMessage } from 'element-plus'
|
||||
@@ -10,8 +10,7 @@ import StartEvalStep from './create/StartEvalStep.vue'
|
||||
import { createDimension, startEval } from '@/api/modules/eval'
|
||||
import { getTrainedModels, getModelList } from '@/api/modules/model'
|
||||
import { getDatasetList } from '@/api/modules/dataset'
|
||||
import { getSystemInfo } from '@/api/modules/system'
|
||||
import { getComputeNodes, type ComputeNode } from '@/api/modules/compute'
|
||||
import { getComputeGpus } from '@/api/modules/compute'
|
||||
import type { DatasetItem, Dimension, GpuInfo, ModelItem, TrainedModel } from '@/types'
|
||||
|
||||
type StepExposed = { validate: () => Promise<boolean> }
|
||||
@@ -83,9 +82,8 @@ async function loadData() {
|
||||
const results = await Promise.allSettled([
|
||||
getTrainedModels(),
|
||||
getDatasetList(),
|
||||
getSystemInfo(),
|
||||
getModelList(),
|
||||
getComputeNodes(),
|
||||
getComputeGpus(),
|
||||
])
|
||||
|
||||
if (results[0].status === 'fulfilled') trainedModels.value = results[0].value?.models || []
|
||||
@@ -93,18 +91,12 @@ async function loadData() {
|
||||
evalDatasets.value = (results[1].value || []).filter((dataset) => dataset.type === 'eval')
|
||||
}
|
||||
if (results[2].status === 'fulfilled') {
|
||||
const allGpus: GpuInfo[] = results[2].value?.gpu || []
|
||||
const nodes: ComputeNode[] = (results[4].status === 'fulfilled' ? results[4].value : []) || []
|
||||
const onlineIds = new Set(nodes.filter((n) => n.enabled && n.scheduler_status === 'online').map((n) => n.id))
|
||||
// Only show idle GPUs from online compute nodes
|
||||
gpus.value = allGpus.filter(
|
||||
(g) => g.status === 'idle' && (!g.node_id || onlineIds.has(g.node_id)),
|
||||
evalModels.value = (results[2].value || []).filter(
|
||||
(model) => model.purpose === 'evaluation' || (model.model_source === 'api' && !!model.api_url),
|
||||
)
|
||||
}
|
||||
if (results[3].status === 'fulfilled') {
|
||||
evalModels.value = (results[3].value || []).filter(
|
||||
(model) => model.purpose === 'evaluation' || (model.model_source === 'api' && !!model.api_url),
|
||||
)
|
||||
gpus.value = ((results[3].value || []) as unknown as GpuInfo[]).filter((g) => g.status === 'idle')
|
||||
}
|
||||
|
||||
const failedCount = results.filter((result) => result.status === 'rejected').length
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<script setup lang="ts">
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import PageCard from '@/components/PageCard.vue'
|
||||
@@ -50,6 +50,8 @@ const passRate = computed(() => {
|
||||
})
|
||||
|
||||
const overallScore = computed(() => formatScore(detail.value?.overall_score, detail.value?.overall_score_max))
|
||||
const displayModelName = computed(() => detail.value?.model_name || String(detail.value?.model_id || '-'))
|
||||
const displayMetric = computed(() => detail.value?.metric_label || detail.value?.metric || '-')
|
||||
|
||||
function formatDateTime(value?: string) {
|
||||
if (!value) return '-'
|
||||
@@ -121,9 +123,9 @@ onUnmounted(stopPolling)
|
||||
</div>
|
||||
<dl class="task-meta">
|
||||
<div><dt>任务 ID</dt><dd>{{ detail?.id || taskId }}</dd></div>
|
||||
<div><dt>评测模型</dt><dd>{{ detail?.model_name || '-' }}</dd></div>
|
||||
<div><dt>评测模型</dt><dd>{{ displayModelName }}</dd></div>
|
||||
<div><dt>测试集</dt><dd>{{ detail?.dataset || '-' }}</dd></div>
|
||||
<div><dt>评测指标</dt><dd>{{ detail?.metric || '-' }}</dd></div>
|
||||
<div><dt>评测指标</dt><dd>{{ displayMetric }}</dd></div>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
@@ -141,7 +143,7 @@ onUnmounted(stopPolling)
|
||||
<div class="overview-item score-hero">
|
||||
<span>综合得分</span>
|
||||
<strong>{{ overallScore }}</strong>
|
||||
<small>大模型综合评分</small>
|
||||
<small>模型综合评分</small>
|
||||
</div>
|
||||
<div class="overview-item">
|
||||
<span>样本通过率</span>
|
||||
@@ -164,7 +166,7 @@ onUnmounted(stopPolling)
|
||||
<div class="review-copy">
|
||||
<div class="section-heading">
|
||||
<div>
|
||||
<h2 id="overall-review-title">大模型综合评价</h2>
|
||||
<h2 id="overall-review-title">综合评价</h2>
|
||||
<p>基于全部已评测样本生成的总体结论</p>
|
||||
</div>
|
||||
<el-tag v-if="detail.evaluator_model" type="primary" size="small">
|
||||
@@ -172,7 +174,7 @@ onUnmounted(stopPolling)
|
||||
</el-tag>
|
||||
</div>
|
||||
<p class="review-text">
|
||||
{{ detail.overall_evaluation || (detail.status === 'running' ? '评测仍在进行,综合评价将在样本评分完成后生成。' : '暂无综合评价。') }}
|
||||
{{ detail.overall_evaluation || (detail.status === 'running' ? '评测正在进行,综合评价将在样本完成后生成。' : '暂无综合评价。') }}
|
||||
</p>
|
||||
|
||||
<div class="suggestion-block">
|
||||
@@ -190,8 +192,8 @@ onUnmounted(stopPolling)
|
||||
<section v-if="detail.dimension_summary?.length" class="dimension-summary" aria-labelledby="dimension-title">
|
||||
<div class="section-heading compact-heading">
|
||||
<div>
|
||||
<h2 id="dimension-title">维度表现</h2>
|
||||
<p>查看各评测维度的得分与样本通过率</p>
|
||||
<h2 id="dimension-title">指标表现</h2>
|
||||
<p>查看各评测指标的得分与通过率</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dimension-grid">
|
||||
@@ -212,22 +214,10 @@ onUnmounted(stopPolling)
|
||||
<p>共 {{ filteredSamples.length }} 条结果,展开行可查看评分依据与子维度分数</p>
|
||||
</div>
|
||||
<div class="sample-filters" aria-label="样本筛选">
|
||||
<el-input
|
||||
v-model="keyword"
|
||||
clearable
|
||||
placeholder="搜索问题、回答或评价"
|
||||
aria-label="搜索样本"
|
||||
@input="resetPage"
|
||||
>
|
||||
<el-input v-model="keyword" clearable placeholder="搜索问题、回答或评价" aria-label="搜索样本" @input="resetPage">
|
||||
<template #prefix><i class="fa fa-search" aria-hidden="true" /></template>
|
||||
</el-input>
|
||||
<el-select
|
||||
v-model="judgementFilter"
|
||||
clearable
|
||||
placeholder="全部判定"
|
||||
aria-label="按判定筛选"
|
||||
@change="resetPage"
|
||||
>
|
||||
<el-select v-model="judgementFilter" clearable placeholder="全部判定" aria-label="按判定筛选" @change="resetPage">
|
||||
<el-option label="正确" value="正确" />
|
||||
<el-option label="部分正确" value="部分正确" />
|
||||
<el-option label="错误" value="错误" />
|
||||
@@ -235,18 +225,12 @@ onUnmounted(stopPolling)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-table
|
||||
v-if="filteredSamples.length"
|
||||
class="sample-results-table"
|
||||
:data="paginatedSamples"
|
||||
row-key="id"
|
||||
table-layout="fixed"
|
||||
>
|
||||
<el-table v-if="filteredSamples.length" class="sample-results-table" :data="paginatedSamples" row-key="id" table-layout="fixed">
|
||||
<el-table-column type="expand" width="48">
|
||||
<template #default="{ row }">
|
||||
<div class="sample-detail-grid">
|
||||
<div class="evaluation-reason">
|
||||
<span>大模型评分依据</span>
|
||||
<span>评分依据</span>
|
||||
<p>{{ row.evaluation_reason || '暂无评分依据。' }}</p>
|
||||
</div>
|
||||
<div v-if="row.error_type" class="error-type">
|
||||
@@ -275,9 +259,7 @@ onUnmounted(stopPolling)
|
||||
<template #default="{ row }"><p class="cell-copy">{{ row.model_output || '等待生成' }}</p></template>
|
||||
</el-table-column>
|
||||
<el-table-column label="得分" width="90" align="center">
|
||||
<template #default="{ row }">
|
||||
<span class="sample-score">{{ formatScore(row.score, row.max_score) }}</span>
|
||||
</template>
|
||||
<template #default="{ row }"><span class="sample-score">{{ formatScore(row.score, row.max_score) }}</span></template>
|
||||
</el-table-column>
|
||||
<el-table-column label="判定" width="96" align="center">
|
||||
<template #default="{ row }">
|
||||
@@ -294,21 +276,11 @@ onUnmounted(stopPolling)
|
||||
<p>{{ detail.status === 'running' ? '任务正在运行,结果生成后会显示在这里。' : '请调整筛选条件或稍后重试。' }}</p>
|
||||
</div>
|
||||
|
||||
<el-pagination
|
||||
v-if="filteredSamples.length > pageSize"
|
||||
v-model:current-page="currentPage"
|
||||
v-model:page-size="pageSize"
|
||||
background
|
||||
layout="total, sizes, prev, pager, next"
|
||||
:page-sizes="[10, 20, 50]"
|
||||
:total="filteredSamples.length"
|
||||
aria-label="样本结果分页"
|
||||
/>
|
||||
<el-pagination v-if="filteredSamples.length > pageSize" v-model:current-page="currentPage" v-model:page-size="pageSize" background layout="total, sizes, prev, pager, next" :page-sizes="[10, 20, 50]" :total="filteredSamples.length" aria-label="样本结果分页" />
|
||||
</section>
|
||||
</template>
|
||||
</PageCard>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.eval-detail-page {
|
||||
min-width: 0;
|
||||
@@ -767,3 +739,6 @@ onUnmounted(stopPolling)
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<script setup lang="ts">
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
@@ -57,6 +57,14 @@ function handleViewDetail(row: any) {
|
||||
router.push({ name: 'model-eval-detail', params: { id: row.id } })
|
||||
}
|
||||
|
||||
function displayModelName(row: Partial<EvalTask>) {
|
||||
return row.model_name || String(row.model_id || '-')
|
||||
}
|
||||
|
||||
function displayMetric(row: Partial<EvalTask>) {
|
||||
return row.metric_label || row.metric || '-'
|
||||
}
|
||||
|
||||
const { start: startPolling, stop: stopPolling } = usePolling(
|
||||
async () => {
|
||||
await loadEvalList({ silent: true })
|
||||
@@ -102,9 +110,21 @@ onUnmounted(() => {
|
||||
</template>
|
||||
<template #columns>
|
||||
<el-table-column label="任务名称" prop="eval_task_name" align="center" />
|
||||
<el-table-column label="评测模型" prop="model_name" align="center" />
|
||||
<el-table-column label="评测模型" align="center" min-width="160">
|
||||
<template #default="{ row }">
|
||||
<el-tooltip :content="displayModelName(row)" placement="top" :disabled="displayModelName(row).length < 18">
|
||||
<span class="cell-ellipsis">{{ displayModelName(row) }}</span>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="数据集" prop="dataset" align="center" />
|
||||
<el-table-column label="指标" prop="metric" align="center" />
|
||||
<el-table-column label="指标" align="center" min-width="220">
|
||||
<template #default="{ row }">
|
||||
<el-tooltip :content="displayMetric(row)" placement="top" :disabled="displayMetric(row).length < 24">
|
||||
<span class="cell-ellipsis">{{ displayMetric(row) }}</span>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="评分" prop="score" width="100" align="center" />
|
||||
<el-table-column label="状态" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
@@ -159,7 +179,7 @@ onUnmounted(() => {
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
/* 胶囊切换栏样式 */
|
||||
/* 胶囊切换栏 */
|
||||
.capsule-tabs {
|
||||
display: flex;
|
||||
background: #f1f5f9;
|
||||
@@ -193,4 +213,13 @@ onUnmounted(() => {
|
||||
}
|
||||
}
|
||||
|
||||
.cell-ellipsis {
|
||||
display: inline-block;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
vertical-align: middle;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user