mirror of
https://github.com/langgenius/dify.git
synced 2026-05-08 11:47:35 +08:00
feat(web): run history of batch test
This commit is contained in:
parent
a7ef8f9c12
commit
03325e9750
@ -1,47 +1,163 @@
|
||||
import type { BatchTestRecord } from '../../types'
|
||||
import type { EvaluationResourceProps } from '../../types'
|
||||
import type { EvaluationLogFile } from '@/types/evaluation'
|
||||
import { keepPreviousData, useMutation, useQuery } from '@tanstack/react-query'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Badge from '@/app/components/base/badge'
|
||||
import Pagination from '@/app/components/base/pagination'
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/app/components/base/ui/dropdown-menu'
|
||||
import { consoleClient, consoleQuery } from '@/service/client'
|
||||
import { downloadUrl } from '@/utils/download'
|
||||
|
||||
type HistoryTabProps = {
|
||||
batchRecords: BatchTestRecord[]
|
||||
const PAGE_SIZE = 16
|
||||
const LOADING_ROW_IDS = ['1', '2', '3', '4', '5', '6']
|
||||
|
||||
const formatCreatedAt = (createdAt: string) => {
|
||||
if (!createdAt)
|
||||
return '-'
|
||||
|
||||
return createdAt.includes('T') ? createdAt.slice(0, 10) : createdAt
|
||||
}
|
||||
|
||||
const HistoryTab = ({ batchRecords }: HistoryTabProps) => {
|
||||
const HistoryTab = ({
|
||||
resourceType,
|
||||
resourceId,
|
||||
}: EvaluationResourceProps) => {
|
||||
const { t } = useTranslation('evaluation')
|
||||
const statusLabels = {
|
||||
running: t('batch.status.running'),
|
||||
success: t('batch.status.success'),
|
||||
failed: t('batch.status.failed'),
|
||||
}
|
||||
const [page, setPage] = useState(0)
|
||||
const logsQuery = useQuery({
|
||||
...consoleQuery.evaluation.logs.queryOptions({
|
||||
input: {
|
||||
params: {
|
||||
targetType: resourceType,
|
||||
targetId: resourceId,
|
||||
},
|
||||
query: {
|
||||
page: page + 1,
|
||||
page_size: PAGE_SIZE,
|
||||
},
|
||||
},
|
||||
refetchOnWindowFocus: false,
|
||||
}),
|
||||
placeholderData: keepPreviousData,
|
||||
})
|
||||
const fileDownloadMutation = useMutation({
|
||||
mutationFn: async (file: EvaluationLogFile) => {
|
||||
const fileInfo = await consoleClient.evaluation.file({
|
||||
params: {
|
||||
targetType: resourceType,
|
||||
targetId: resourceId,
|
||||
fileId: file.id,
|
||||
},
|
||||
})
|
||||
|
||||
downloadUrl({ url: fileInfo.download_url, fileName: file.name })
|
||||
},
|
||||
})
|
||||
const records = logsQuery.data?.data ?? []
|
||||
const total = logsQuery.data?.total ?? 0
|
||||
const isInitialLoading = logsQuery.isLoading && !logsQuery.data
|
||||
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
{batchRecords.length === 0 && (
|
||||
<div className="rounded-2xl border border-dashed border-divider-subtle px-4 py-10 text-center system-sm-regular text-text-tertiary">
|
||||
{t('batch.emptyHistory')}
|
||||
</div>
|
||||
)}
|
||||
{batchRecords.map(record => (
|
||||
<div key={record.id} className="rounded-2xl border border-divider-subtle bg-background-default-subtle p-4">
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div>
|
||||
<div className="system-sm-semibold text-text-primary">{record.summary}</div>
|
||||
<div className="mt-1 system-xs-regular text-text-tertiary">{record.fileName}</div>
|
||||
</div>
|
||||
<Badge className={record.status === 'failed' ? 'badge-warning' : record.status === 'success' ? 'badge-accent' : ''}>
|
||||
{record.status === 'running'
|
||||
? (
|
||||
<span className="flex items-center gap-1">
|
||||
<span aria-hidden="true" className="i-ri-loader-4-line h-3 w-3 animate-spin" />
|
||||
{statusLabels.running}
|
||||
</span>
|
||||
)
|
||||
: statusLabels[record.status]}
|
||||
</Badge>
|
||||
<div className="flex min-h-full flex-col">
|
||||
<div className="min-h-0 flex-1 overflow-hidden">
|
||||
<table className="w-full table-fixed border-collapse overflow-hidden rounded-md">
|
||||
<colgroup>
|
||||
<col className="w-[120px]" />
|
||||
<col className="w-[95px]" />
|
||||
<col className="w-[80px]" />
|
||||
<col className="w-[67px]" />
|
||||
<col className="w-[40px]" />
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr className="border-b border-divider-regular">
|
||||
<th className="h-7 px-3 text-left system-xs-medium-uppercase text-text-tertiary">
|
||||
<span className="inline-flex items-center gap-0.5">
|
||||
{t('history.columns.time')}
|
||||
<span aria-hidden="true" className="i-ri-arrow-down-line h-3.5 w-3.5" />
|
||||
</span>
|
||||
</th>
|
||||
<th className="h-7 px-3 text-left system-xs-medium-uppercase text-text-tertiary">{t('history.columns.creator')}</th>
|
||||
<th className="h-7 px-3 text-left system-xs-medium-uppercase text-text-tertiary">{t('history.columns.version')}</th>
|
||||
<th className="h-7 px-3 text-left system-xs-medium-uppercase text-text-tertiary">{t('history.columns.status')}</th>
|
||||
<th className="h-7 text-center text-text-tertiary">
|
||||
<span aria-hidden="true" className="i-ri-download-2-line inline-block h-3.5 w-3.5" />
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{isInitialLoading && LOADING_ROW_IDS.map(rowId => (
|
||||
<tr key={rowId} className="border-b border-divider-subtle">
|
||||
<td colSpan={5} className="h-10 px-3">
|
||||
<div className="h-4 animate-pulse rounded bg-background-section" />
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
{!isInitialLoading && records.map(record => (
|
||||
<tr key={`${record.created_at}-${record.test_file.id}`} className="border-b border-divider-subtle">
|
||||
<td className="h-10 truncate px-3 system-sm-regular text-text-secondary">{formatCreatedAt(record.created_at)}</td>
|
||||
<td className="h-10 truncate px-3 system-sm-regular text-text-secondary">{record.created_by}</td>
|
||||
<td className="h-10 truncate px-3 system-sm-regular text-text-secondary">{record.version || '-'}</td>
|
||||
<td className="h-10 px-3 text-center">
|
||||
{record.result_file
|
||||
? <span aria-label={t('history.status.completed')} className="i-ri-checkbox-circle-fill inline-block h-4 w-4 text-util-colors-green-green-600" />
|
||||
: <span aria-label={t('history.status.running')} className="i-ri-loader-4-line inline-block h-4 w-4 animate-spin text-text-accent" />}
|
||||
</td>
|
||||
<td className="h-10 text-center">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger
|
||||
render={(
|
||||
<button
|
||||
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"
|
||||
/>
|
||||
)}
|
||||
>
|
||||
<span aria-hidden="true" className="i-ri-more-2-fill h-4 w-4" />
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent popupClassName="w-[180px] rounded-lg border-[0.5px] border-components-panel-border py-1 shadow-lg">
|
||||
<DropdownMenuItem
|
||||
className="gap-2"
|
||||
onClick={() => fileDownloadMutation.mutate(record.test_file)}
|
||||
>
|
||||
<span aria-hidden="true" className="i-ri-file-download-line h-4 w-4" />
|
||||
{t('history.actions.downloadTestFile')}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
className="gap-2"
|
||||
disabled={!record.result_file}
|
||||
onClick={() => record.result_file && fileDownloadMutation.mutate(record.result_file)}
|
||||
>
|
||||
<span aria-hidden="true" className="i-ri-download-2-line h-4 w-4" />
|
||||
{t('history.actions.downloadResultFile')}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
{!isInitialLoading && records.length === 0 && (
|
||||
<div className="rounded-2xl border border-dashed border-divider-subtle px-4 py-10 text-center system-sm-regular text-text-tertiary">
|
||||
{t('history.empty')}
|
||||
</div>
|
||||
<div className="mt-3 system-xs-regular text-text-tertiary">{record.startedAt}</div>
|
||||
</div>
|
||||
))}
|
||||
)}
|
||||
</div>
|
||||
{total > PAGE_SIZE && (
|
||||
<Pagination
|
||||
className="px-0 py-3"
|
||||
current={page}
|
||||
limit={PAGE_SIZE}
|
||||
total={total}
|
||||
onChange={setPage}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -76,7 +76,7 @@ const BatchTestPanel = ({
|
||||
onUploadFileNameChange={uploadedFileName => setUploadedFileName(resourceType, resourceId, uploadedFileName)}
|
||||
/>
|
||||
)}
|
||||
{resource.activeBatchTab === 'history' && <HistoryTab batchRecords={resource.batchRecords} />}
|
||||
{resource.activeBatchTab === 'history' && <HistoryTab resourceType={resourceType} resourceId={resourceId} />}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -47,6 +47,9 @@
|
||||
"conditions.valueTypes.number": "Number",
|
||||
"conditions.valueTypes.string": "String",
|
||||
"description": "Configure automated testing to grade your application's performance.",
|
||||
"history.actions.downloadResultFile": "Download result",
|
||||
"history.actions.downloadTestFile": "Download test file",
|
||||
"history.actions.open": "Open history actions",
|
||||
"history.columns.creator": "Creator",
|
||||
"history.columns.status": "Status",
|
||||
"history.columns.time": "Time",
|
||||
@ -55,6 +58,8 @@
|
||||
"history.empty": "No test history yet",
|
||||
"history.latestVersion": "Latest",
|
||||
"history.searchPlaceholder": "Search",
|
||||
"history.status.completed": "Completed",
|
||||
"history.status.running": "Running",
|
||||
"history.title": "Test History",
|
||||
"judgeModel.description": "Choose the model used to score your evaluation results.",
|
||||
"judgeModel.title": "Judge Model",
|
||||
|
||||
@ -47,6 +47,20 @@
|
||||
"conditions.valueTypes.number": "数值",
|
||||
"conditions.valueTypes.string": "文本",
|
||||
"description": "配置自动化测试,对应用表现进行评分。",
|
||||
"history.actions.downloadResultFile": "下载结果文件",
|
||||
"history.actions.downloadTestFile": "下载测试文件",
|
||||
"history.actions.open": "打开历史记录操作",
|
||||
"history.columns.creator": "创建人",
|
||||
"history.columns.status": "状态",
|
||||
"history.columns.time": "时间",
|
||||
"history.columns.version": "版本",
|
||||
"history.creatorYou": "你",
|
||||
"history.empty": "还没有测试历史",
|
||||
"history.latestVersion": "最新",
|
||||
"history.searchPlaceholder": "搜索",
|
||||
"history.status.completed": "已完成",
|
||||
"history.status.running": "运行中",
|
||||
"history.title": "测试历史",
|
||||
"judgeModel.description": "选择用于打分和判定评测结果的模型。",
|
||||
"judgeModel.title": "判定模型",
|
||||
"metrics.add": "添加指标",
|
||||
|
||||
@ -77,6 +77,19 @@ export type EvaluationRun = {
|
||||
created_at: number
|
||||
}
|
||||
|
||||
export type EvaluationLogFile = {
|
||||
id: string
|
||||
name: string
|
||||
}
|
||||
|
||||
export type EvaluationLog = {
|
||||
created_at: string
|
||||
created_by: string
|
||||
test_file: EvaluationLogFile
|
||||
result_file: EvaluationLogFile | null
|
||||
version: string
|
||||
}
|
||||
|
||||
export type EvaluationRunMetric = {
|
||||
name?: string
|
||||
value?: unknown
|
||||
@ -97,7 +110,7 @@ export type EvaluationRunItem = {
|
||||
}
|
||||
|
||||
export type EvaluationLogsResponse = {
|
||||
data: EvaluationRun[]
|
||||
data: EvaluationLog[]
|
||||
total: number
|
||||
page: number
|
||||
page_size: number
|
||||
|
||||
Loading…
Reference in New Issue
Block a user