From 79fc352a5a54d09b7057a9d9b9886cd8fd0a4fe8 Mon Sep 17 00:00:00 2001 From: JzoNg Date: Fri, 10 Apr 2026 17:48:28 +0800 Subject: [PATCH] feat(web): evaluation run detail --- .../batch-test-panel/history-tab.tsx | 49 ++++- .../input-fields/use-input-fields-actions.ts | 4 +- .../components/layout/pipeline-evaluation.tsx | 5 +- .../pipeline/pipeline-results-panel.tsx | 126 +++++++++++- .../pipeline/pipeline-results-table.tsx | 118 ++++++++++++ .../pipeline/pipeline-results-utils.ts | 179 ++++++++++++++++++ web/app/components/evaluation/store-utils.ts | 1 + web/app/components/evaluation/store.ts | 10 + web/app/components/evaluation/types.ts | 1 + web/i18n/en-US/evaluation.json | 12 ++ web/i18n/zh-Hans/evaluation.json | 23 +++ web/types/evaluation.ts | 3 + 12 files changed, 518 insertions(+), 13 deletions(-) create mode 100644 web/app/components/evaluation/components/pipeline/pipeline-results-table.tsx create mode 100644 web/app/components/evaluation/components/pipeline/pipeline-results-utils.ts diff --git a/web/app/components/evaluation/components/batch-test-panel/history-tab.tsx b/web/app/components/evaluation/components/batch-test-panel/history-tab.tsx index 7080e175c4..d8735065e2 100644 --- a/web/app/components/evaluation/components/batch-test-panel/history-tab.tsx +++ b/web/app/components/evaluation/components/batch-test-panel/history-tab.tsx @@ -1,7 +1,7 @@ import type { EvaluationResourceProps } from '../../types' -import type { EvaluationLogFile } from '@/types/evaluation' +import type { EvaluationLog, EvaluationLogFile } from '@/types/evaluation' import { keepPreviousData, useMutation, useQuery } from '@tanstack/react-query' -import { useState } from 'react' +import { useEffect, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import Pagination from '@/app/components/base/pagination' import { @@ -11,7 +11,9 @@ import { DropdownMenuTrigger, } from '@/app/components/base/ui/dropdown-menu' import { consoleClient, consoleQuery } from '@/service/client' +import { cn } from '@/utils/classnames' import { downloadUrl } from '@/utils/download' +import { useEvaluationResource, useEvaluationStore } from '../../store' const PAGE_SIZE = 16 const LOADING_ROW_IDS = ['1', '2', '3', '4', '5', '6'] @@ -23,12 +25,18 @@ const formatCreatedAt = (createdAt: string) => { return createdAt.includes('T') ? createdAt.slice(0, 10) : createdAt } +const getLogRunId = (record: EvaluationLog) => { + return record.run_id ?? record.evaluation_run_id ?? record.id ?? null +} + const HistoryTab = ({ resourceType, resourceId, }: EvaluationResourceProps) => { const { t } = useTranslation('evaluation') const [page, setPage] = useState(0) + const resource = useEvaluationResource(resourceType, resourceId) + const setSelectedRunId = useEvaluationStore(state => state.setSelectedRunId) const logsQuery = useQuery({ ...consoleQuery.evaluation.logs.queryOptions({ input: { @@ -58,10 +66,19 @@ const HistoryTab = ({ downloadUrl({ url: fileInfo.download_url, fileName: file.name }) }, }) - const records = logsQuery.data?.data ?? [] + const records = useMemo(() => logsQuery.data?.data ?? [], [logsQuery.data?.data]) const total = logsQuery.data?.total ?? 0 const isInitialLoading = logsQuery.isLoading && !logsQuery.data + useEffect(() => { + if (resource.selectedRunId) + return + + const firstRunId = records.map(getLogRunId).find((runId): runId is string => !!runId) + if (firstRunId) + setSelectedRunId(resourceType, resourceId, firstRunId) + }, [records, resource.selectedRunId, resourceId, resourceType, setSelectedRunId]) + return (
@@ -98,7 +115,19 @@ const HistoryTab = ({ ))} {!isInitialLoading && records.map(record => ( - + { + const runId = getLogRunId(record) + if (runId) + setSelectedRunId(resourceType, resourceId, runId) + }} + > {formatCreatedAt(record.created_at)} {record.created_by} {record.version || '-'} @@ -115,6 +144,7 @@ const HistoryTab = ({ type="button" aria-label={t('history.actions.open')} className="inline-flex h-8 w-8 items-center justify-center rounded-md text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary" + onClick={event => event.stopPropagation()} /> )} > @@ -123,7 +153,10 @@ const HistoryTab = ({ fileDownloadMutation.mutate(record.test_file)} + onClick={(event) => { + event.stopPropagation() + fileDownloadMutation.mutate(record.test_file) + }} >
- +
) diff --git a/web/app/components/evaluation/components/pipeline/pipeline-results-panel.tsx b/web/app/components/evaluation/components/pipeline/pipeline-results-panel.tsx index 3e7286adbf..c66f3ecc2f 100644 --- a/web/app/components/evaluation/components/pipeline/pipeline-results-panel.tsx +++ b/web/app/components/evaluation/components/pipeline/pipeline-results-panel.tsx @@ -1,16 +1,132 @@ 'use client' +import type { EvaluationResourceProps } from '../../types' +import { skipToken, useMutation, useQuery } from '@tanstack/react-query' import { useTranslation } from 'react-i18next' +import { consoleClient, consoleQuery } from '@/service/client' +import { downloadUrl } from '@/utils/download' +import { useEvaluationResource } from '../../store' +import { decodeModelSelection } from '../../utils' +import PipelineResultsTable from './pipeline-results-table' +import { getMetricColumns, getRunDate } from './pipeline-results-utils' -const PipelineResultsPanel = () => { +const PAGE_SIZE = 100 + +const PipelineResultsPanel = ({ + resourceType, + resourceId, +}: EvaluationResourceProps) => { const { t } = useTranslation('evaluation') + const resource = useEvaluationResource(resourceType, resourceId) + const selectedModel = decodeModelSelection(resource.judgeModelId) + const selectedRunId = resource.selectedRunId + const runDetailQuery = useQuery(consoleQuery.evaluation.runDetail.queryOptions({ + input: selectedRunId + ? { + params: { + targetType: resourceType, + targetId: resourceId, + runId: selectedRunId, + }, + query: { + page: 1, + page_size: PAGE_SIZE, + }, + } + : skipToken, + refetchOnWindowFocus: false, + })) + const resultFileDownloadMutation = useMutation({ + mutationFn: async (fileId: string) => { + const fileInfo = await consoleClient.evaluation.file({ + params: { + targetType: resourceType, + targetId: resourceId, + fileId, + }, + }) + + downloadUrl({ url: fileInfo.download_url, fileName: fileInfo.name }) + }, + }) + const runDetail = runDetailQuery.data + const items = runDetail?.items.data ?? [] + const metricColumns = getMetricColumns(resource, items) + const thresholdColumns = metricColumns.filter(column => column.threshold !== undefined) + const isEmpty = !selectedRunId || (!runDetailQuery.isLoading && items.length === 0) + + if (isEmpty) { + return ( +
+
+
+
+ ) + } return ( -
-
-