Compare commits
3 Commits
62a1d03eac
...
250e060271
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
250e060271 | ||
|
|
7b36bc774e | ||
|
|
4e5c43fad5 |
@@ -421,7 +421,7 @@ async def dashboard_stats() -> dict[str, Any]:
|
||||
("数据类型转换", "/data-convert", "数据类型转换"),
|
||||
]
|
||||
service_status = []
|
||||
for svc_type, path, _label in service_checks:
|
||||
for svc_type, _path, _label in service_checks:
|
||||
try:
|
||||
svc_count = 0
|
||||
if svc_type == "模型训练":
|
||||
|
||||
@@ -104,6 +104,68 @@ def count_dataset_records(content: str) -> int:
|
||||
return len([line for line in text.splitlines() if line.strip()])
|
||||
|
||||
|
||||
_EVAL_METHOD_LABELS = {
|
||||
"standard": "标准匹配",
|
||||
"metric_standard": "综合评测",
|
||||
"semantic": "语义相似度",
|
||||
"sentiment": "情感分析",
|
||||
"accuracy": "准确性评估",
|
||||
"safety": "安全性评估",
|
||||
"relevance": "相关性评估",
|
||||
"fluency": "流畅性评估",
|
||||
"factuality": "事实性评估",
|
||||
"custom": "自定义评估",
|
||||
}
|
||||
|
||||
|
||||
def _eval_method_label(value: Any) -> str:
|
||||
if isinstance(value, list):
|
||||
return "、".join(_eval_method_label(item) for item in value if item)
|
||||
text = str(value or "").strip()
|
||||
return _EVAL_METHOD_LABELS.get(text, text)
|
||||
|
||||
|
||||
def _basic_metric_labels(config: dict[str, Any] | None) -> list[str]:
|
||||
cfg = config or {}
|
||||
labels: list[str] = []
|
||||
bleu = cfg.get("bleu") or {}
|
||||
if bleu.get("enabled"):
|
||||
labels.append(f"BLEU-{int(bleu.get('ngram') or 4)}")
|
||||
rouge = cfg.get("rouge") or {}
|
||||
if rouge.get("enabled"):
|
||||
methods = rouge.get("methods") or []
|
||||
method_labels = {
|
||||
"rouge1": "ROUGE-1",
|
||||
"rouge2": "ROUGE-2",
|
||||
"rougeL": "ROUGE-L",
|
||||
"rouge_1": "ROUGE-1",
|
||||
"rouge_2": "ROUGE-2",
|
||||
"rouge_l": "ROUGE-L",
|
||||
}
|
||||
labels.extend(method_labels.get(str(item), str(item)) for item in methods)
|
||||
cosine = cfg.get("cosine") or {}
|
||||
if cosine.get("enabled"):
|
||||
labels.append("Cosine")
|
||||
return labels
|
||||
|
||||
|
||||
def _build_eval_metric_label(payload: dict[str, Any], dimension: dict[str, Any] | None = None) -> str:
|
||||
parts: list[str] = []
|
||||
dim = dimension or {}
|
||||
dim_type = str(dim.get("type") or payload.get("dimension_type") or "").strip()
|
||||
method_label = _eval_method_label(dim.get("eval_method") or payload.get("eval_method"))
|
||||
if dim_type in {"classification", "metric"} and method_label:
|
||||
parts.append(f"LLM:{method_label}")
|
||||
elif dim_type == "text_similarity" and method_label:
|
||||
parts.append(method_label)
|
||||
|
||||
parts.extend(_basic_metric_labels(payload.get("basic_metrics") or {}))
|
||||
if not parts:
|
||||
metric = str(payload.get("metric") or payload.get("eval_type") or "").strip()
|
||||
return "自定义评测" if metric == "custom" else (metric or "-")
|
||||
return " + ".join(parts)
|
||||
|
||||
|
||||
def version_number(value: Any, default: int = 0) -> int:
|
||||
try:
|
||||
number = int(value)
|
||||
@@ -339,6 +401,7 @@ class PlatformStore:
|
||||
self.ensure_seed_data()
|
||||
# Track which compute nodes have an active inference model loaded
|
||||
self._inference_nodes: set[str] = set()
|
||||
self._last_runtime_refresh = 0.0
|
||||
|
||||
# ── inference node tracking ────────────────────────────────────
|
||||
|
||||
@@ -463,6 +526,10 @@ class PlatformStore:
|
||||
def refresh_runtime_state(self) -> None:
|
||||
if get_settings().compute_mode != "simulator":
|
||||
return
|
||||
now_ts = time.monotonic()
|
||||
if now_ts - self._last_runtime_refresh < 2:
|
||||
return
|
||||
self._last_runtime_refresh = now_ts
|
||||
|
||||
with self.connect() as conn:
|
||||
rows = conn.execute(
|
||||
@@ -2146,19 +2213,54 @@ class PlatformStore:
|
||||
def _json_payload_row(self, row: PgRow) -> dict[str, Any]:
|
||||
payload = json_loads(row["payload"], {})
|
||||
payload.update({"id": row["id"], "status": row.get("status"), "create_time": row["create_time"]})
|
||||
if not payload.get("metric_label"):
|
||||
payload["metric_label"] = _build_eval_metric_label(payload)
|
||||
if not payload.get("metric") or payload.get("metric") == "custom":
|
||||
payload["metric"] = payload["metric_label"]
|
||||
return payload
|
||||
|
||||
def _enrich_eval_payload(self, conn: PgConnection, payload: dict[str, Any]) -> dict[str, Any]:
|
||||
data = dict(payload)
|
||||
model_id = str(data.get("model_id") or "")
|
||||
if model_id and not data.get("model_name"):
|
||||
model = conn.execute("SELECT name FROM models WHERE id=?", (model_id,)).fetchone()
|
||||
if not model:
|
||||
model = conn.execute("SELECT name FROM trained_models WHERE id=? OR name=?", (model_id, model_id)).fetchone()
|
||||
if model:
|
||||
data["model_name"] = model["name"]
|
||||
|
||||
dataset_id = str(data.get("dataset_id") or "")
|
||||
if dataset_id and not data.get("dataset"):
|
||||
dataset = conn.execute("SELECT name FROM datasets WHERE id=?", (dataset_id,)).fetchone()
|
||||
if dataset:
|
||||
data["dataset"] = dataset["name"]
|
||||
|
||||
dimension = None
|
||||
dimension_id = str(data.get("dimension_id") or "")
|
||||
if dimension_id:
|
||||
dimension_row = conn.execute("SELECT payload FROM eval_dimensions WHERE id=?", (dimension_id,)).fetchone()
|
||||
if dimension_row:
|
||||
dimension = json_loads(dimension_row["payload"], {})
|
||||
data.setdefault("dimension_type", dimension.get("type"))
|
||||
data.setdefault("eval_method", dimension.get("eval_method"))
|
||||
data.setdefault("evaluator_model", dimension.get("eval_model"))
|
||||
|
||||
data["metric_label"] = _build_eval_metric_label(data, dimension)
|
||||
if not data.get("metric") or data.get("metric") in {"custom", "自定义评测"}:
|
||||
data["metric"] = data["metric_label"]
|
||||
return data
|
||||
|
||||
def eval_tasks(self) -> list[dict[str, Any]]:
|
||||
with self.connect() as conn:
|
||||
rows = conn.execute("SELECT * FROM eval_tasks ORDER BY create_time DESC").fetchall()
|
||||
return [self._json_payload_row(row) for row in rows]
|
||||
return [self._enrich_eval_payload(conn, self._json_payload_row(row)) for row in rows]
|
||||
|
||||
def eval_task(self, task_id: str) -> dict[str, Any]:
|
||||
with self.connect() as conn:
|
||||
row = conn.execute("SELECT * FROM eval_tasks WHERE id=?", (task_id,)).fetchone()
|
||||
if not row:
|
||||
raise KeyError(task_id)
|
||||
payload = self._json_payload_row(row)
|
||||
payload = self._enrich_eval_payload(conn, self._json_payload_row(row))
|
||||
payload.setdefault("sample_count", 0)
|
||||
payload.setdefault("completed_count", 0)
|
||||
payload.setdefault("passed_count", 0)
|
||||
@@ -2184,12 +2286,29 @@ class PlatformStore:
|
||||
"metric": payload.get("metric") or "custom",
|
||||
}
|
||||
with self.connect() as conn:
|
||||
model = conn.execute("SELECT name FROM models WHERE id=?", (str(payload.get("model_id")),)).fetchone()
|
||||
model_id = str(payload.get("model_id") or "")
|
||||
model = conn.execute("SELECT name FROM models WHERE id=?", (model_id,)).fetchone()
|
||||
trained_model = conn.execute("SELECT name FROM trained_models WHERE id=? OR name=?", (model_id, model_id)).fetchone()
|
||||
dataset = conn.execute("SELECT name FROM datasets WHERE id=?", (str(payload.get("dataset_id")),)).fetchone()
|
||||
dimension = None
|
||||
dimension_id = str(payload.get("dimension_id") or "")
|
||||
if dimension_id:
|
||||
dimension_row = conn.execute("SELECT payload FROM eval_dimensions WHERE id=?", (dimension_id,)).fetchone()
|
||||
if dimension_row:
|
||||
dimension = json_loads(dimension_row["payload"], {})
|
||||
if model:
|
||||
data.setdefault("model_name", model["name"])
|
||||
elif trained_model:
|
||||
data.setdefault("model_name", trained_model["name"])
|
||||
if dataset:
|
||||
data.setdefault("dataset", dataset["name"])
|
||||
if dimension:
|
||||
data.setdefault("dimension_type", dimension.get("type"))
|
||||
data.setdefault("eval_method", dimension.get("eval_method"))
|
||||
data.setdefault("evaluator_model", dimension.get("eval_model"))
|
||||
data["metric_label"] = _build_eval_metric_label(data, dimension)
|
||||
if data.get("metric") == "custom":
|
||||
data["metric"] = data["metric_label"]
|
||||
conn.execute(
|
||||
"INSERT INTO eval_tasks (id, name, payload, status, create_time) VALUES (?, ?, ?, ?, ?)",
|
||||
(task_id, name, json_dumps(data), status, now),
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
import { post } from '../request'
|
||||
|
||||
/** 记录用户访问某个业务模块(用于看板用户操作分布统计) */
|
||||
export const recordModuleVisit = (module: string, detail?: string) =>
|
||||
post('/system/audit/visit', { action: module, detail: detail || '' })
|
||||
@@ -8,6 +8,8 @@ import type {
|
||||
UpdateUserAccessPayload,
|
||||
} from '@/types'
|
||||
|
||||
export type { SystemUser } from '@/types'
|
||||
|
||||
/** 系统信息(CPU/内存/磁盘/GPU/网络/系统) */
|
||||
export const getSystemInfo = () => get<SystemInfo>('/system-info')
|
||||
|
||||
|
||||
@@ -1,37 +1,6 @@
|
||||
import axios, { type AxiosInstance, type AxiosRequestConfig } from 'axios'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
/**
|
||||
* 用户操作分布:哪些模块路径算"业务操作"(用于看板统计)
|
||||
* 请求命中这些路径时,会自动调用 record_visit 记录一次(同一模块 60 秒内去重)
|
||||
*/
|
||||
const VISIT_TRACKED_PREFIXES: Array<[string, string]> = [
|
||||
['/fine-tune', 'fine-tune'],
|
||||
['/model-eval', 'model-eval'],
|
||||
['/model-inference', 'model-inference'],
|
||||
['/data-process', 'data-process'],
|
||||
['/data-convert', 'data-convert'],
|
||||
['/model-manage', 'model-manage'],
|
||||
['/dataset-manage', 'dataset'],
|
||||
]
|
||||
|
||||
function trackVisit(url: string | undefined) {
|
||||
if (!url) return
|
||||
for (const [prefix, module] of VISIT_TRACKED_PREFIXES) {
|
||||
if (url.includes(prefix)) {
|
||||
const key = `visit:${module}`
|
||||
const last = Number(sessionStorage.getItem(key) || 0)
|
||||
if (Date.now() - last < 60000) return // 60 秒内去重
|
||||
sessionStorage.setItem(key, String(Date.now()))
|
||||
// fire-and-forget 调用后端记录接口
|
||||
import('./modules/audit-visit').then(({ recordModuleVisit }) => {
|
||||
recordModuleVisit(module, url).catch(() => { /* ignore */ })
|
||||
}).catch(() => { /* ignore */ })
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 后端统一响应格式
|
||||
* code === 0 表示成功,data 为业务数据
|
||||
@@ -91,8 +60,6 @@ service.interceptors.response.use(
|
||||
return response
|
||||
}
|
||||
if (res.code === 0) {
|
||||
// 记录业务模块访问(用于看板用户操作分布统计)
|
||||
trackVisit(response.config.url)
|
||||
return res.data
|
||||
}
|
||||
// 业务错误
|
||||
|
||||
@@ -245,7 +245,9 @@ export interface EvalTask {
|
||||
model_name?: string
|
||||
model_id?: number | string
|
||||
dataset?: string
|
||||
dataset_id?: number | string
|
||||
metric?: string
|
||||
metric_label?: string
|
||||
score?: number
|
||||
status?: string
|
||||
create_time?: string
|
||||
|
||||
@@ -66,8 +66,12 @@ const operationDistribution = ref<{ name: string; value: number }[]>([])
|
||||
const serviceIcon: Record<string, string> = {
|
||||
'模型推理': 'fa-cube',
|
||||
'模型微调': 'fa-sliders',
|
||||
'模型训练': 'fa-sliders',
|
||||
'模型评测': 'fa-bar-chart',
|
||||
'模型管理': 'fa-cubes',
|
||||
'数据集管理': 'fa-file-text',
|
||||
'数据处理': 'fa-filter',
|
||||
'数据类型转换': 'fa-exchange',
|
||||
}
|
||||
const roleLabel: Record<string, string> = {
|
||||
admin: '超级管理员',
|
||||
@@ -115,7 +119,7 @@ const chartOption = computed<EChartsOption>(() => ({
|
||||
borderWidth: 0,
|
||||
padding: [10, 12],
|
||||
textStyle: { color: '#ffffff', fontSize: 12 },
|
||||
valueFormatter: (value) => `${value}`,
|
||||
valueFormatter: (value) => String(value ?? ''),
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
@@ -252,7 +256,7 @@ const loginDurationChartOption = computed<EChartsOption>(() => {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: { type: 'shadow' },
|
||||
valueFormatter: (value: number) => `${value} 小时`,
|
||||
valueFormatter: (value: unknown) => String(Number(Array.isArray(value) ? value[0] : value) || 0) + ' 小时',
|
||||
},
|
||||
xAxis: {
|
||||
type: 'value',
|
||||
@@ -738,10 +742,11 @@ function viewTask(task: DashboardTask) {
|
||||
|
||||
.service-table {
|
||||
display: grid;
|
||||
grid-template-rows: 36px repeat(4, minmax(48px, 1fr));
|
||||
grid-auto-rows: minmax(44px, auto);
|
||||
flex: 1 1 auto;
|
||||
margin-top: 12px;
|
||||
min-height: 0;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.service-row {
|
||||
@@ -935,7 +940,7 @@ function viewTask(task: DashboardTask) {
|
||||
}
|
||||
|
||||
.service-table {
|
||||
grid-template-rows: 32px repeat(4, minmax(40px, 1fr));
|
||||
grid-auto-rows: minmax(38px, auto);
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -99,23 +99,23 @@ onMounted(() => {
|
||||
<el-button type="primary" :icon="Plus" @click="showCreate = true">新建项目</el-button>
|
||||
</template>
|
||||
<template #columns>
|
||||
<el-table-column prop="name" label="项目名" min-width="140" />
|
||||
<el-table-column prop="code" label="编码ID" min-width="100" />
|
||||
<el-table-column prop="name" label="项目名称" min-width="140" />
|
||||
<el-table-column prop="code" label="编码 ID" min-width="100" />
|
||||
<el-table-column prop="status" label="状态" min-width="100" />
|
||||
<el-table-column prop="description" label="描述" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column prop="create_time" label="创建时间" min-width="180" />
|
||||
</template>
|
||||
<template #actions="{ row }">
|
||||
<el-button link type="primary" @click="openDetail(row.id)">详情</el-button>
|
||||
<el-button link type="danger" @click="handleDelete(row)">删除</el-button>
|
||||
<el-button link type="danger" @click="handleDelete(asProject(row))">删除</el-button>
|
||||
</template>
|
||||
</DataTablePage>
|
||||
<el-dialog v-model="showCreate" title="新建项目" width="520px">
|
||||
<el-form label-width="90px">
|
||||
<el-form-item label="名称" required>
|
||||
<el-input v-model="form.name" placeholder="项目名" />
|
||||
<el-input v-model="form.name" placeholder="项目名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="编码ID" required>
|
||||
<el-form-item label="编码 ID" required>
|
||||
<el-select v-model="form.tenant_id" style="width: 100%" placeholder="选择租户编码">
|
||||
<el-option v-for="t in tenantCodeOptions" :key="t.value" :label="t.label" :value="t.tenantId" />
|
||||
</el-select>
|
||||
|
||||
@@ -30,7 +30,7 @@ function formatQuota(quota: Record<string, unknown> | undefined | null) {
|
||||
if (q.gpu > 0) parts.push(`GPU ${q.gpu}`)
|
||||
if (q.storage > 0) parts.push(`存储 ${q.storage}GB`)
|
||||
if (q.maxProjects > 0) parts.push(`项目 ${q.maxProjects}`)
|
||||
return parts.length ? parts.join(' | ') : '—'
|
||||
return parts.length ? parts.join(' | ') : '-'
|
||||
}
|
||||
|
||||
async function load() {
|
||||
@@ -52,7 +52,7 @@ function asTenant(row: unknown): Tenant {
|
||||
|
||||
function quotaText(row: unknown): string {
|
||||
const quota = asTenant(row).quota || {}
|
||||
return Object.keys(quota).length ? JSON.stringify(quota) : '—'
|
||||
return Object.keys(quota).length ? JSON.stringify(quota) : '-'
|
||||
}
|
||||
|
||||
async function submitCreate() {
|
||||
@@ -118,13 +118,13 @@ onMounted(load)
|
||||
</template>
|
||||
<template #columns>
|
||||
<el-table-column prop="name" label="租户名称" min-width="140" />
|
||||
<el-table-column prop="code" label="用户ID" min-width="100" />
|
||||
<el-table-column prop="code" label="租户 ID" min-width="100" />
|
||||
<el-table-column prop="status" label="状态" min-width="100" />
|
||||
<el-table-column prop="create_time" label="创建时间" min-width="180" />
|
||||
</template>
|
||||
<template #actions="{ row }">
|
||||
<el-button link type="primary" @click="openDetail(row.id)">详情</el-button>
|
||||
<el-button link type="danger" @click="handleDelete(row)">删除</el-button>
|
||||
<el-button link type="danger" @click="handleDelete(asTenant(row))">删除</el-button>
|
||||
</template>
|
||||
</DataTablePage>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user