refactor(web): rag-pipeline evaluation

This commit is contained in:
JzoNg
2026-04-10 17:31:10 +08:00
parent d1ca468c1e
commit 8b6b3cddea
3 changed files with 151 additions and 101 deletions

View File

@ -1,26 +1,17 @@
'use client'
import type { EvaluationResourceProps } from '../../types'
import { useEffect, useMemo } from 'react'
import { useEffect } from 'react'
import { useTranslation } from 'react-i18next'
import Button from '@/app/components/base/button'
import { useDocLink } from '@/context/i18n'
import { useAvailableEvaluationMetrics } from '@/service/use-evaluation'
import { getEvaluationMockConfig } from '../../mock'
import { isEvaluationRunnable, useEvaluationResource, useEvaluationStore } from '../../store'
import { useEvaluationStore } from '../../store'
import HistoryTab from '../batch-test-panel/history-tab'
import UploadRunPopover from '../batch-test-panel/input-fields/upload-run-popover'
import { useInputFieldsActions } from '../batch-test-panel/input-fields/use-input-fields-actions'
import JudgeModelSelector from '../judge-model-selector'
import PipelineMetricItem from '../pipeline/pipeline-metric-item'
import PipelineBatchActions from '../pipeline/pipeline-batch-actions'
import PipelineMetricsSection from '../pipeline/pipeline-metrics-section'
import PipelineResultsPanel from '../pipeline/pipeline-results-panel'
import SectionHeader, { InlineSectionHeader } from '../section-header'
const PIPELINE_INPUT_FIELDS = [
{ name: 'query', type: 'string' },
{ name: 'Expect Results', type: 'string' },
]
const PipelineEvaluation = ({
resourceType,
resourceId,
@ -29,49 +20,11 @@ const PipelineEvaluation = ({
const { t: tCommon } = useTranslation('common')
const docLink = useDocLink()
const ensureResource = useEvaluationStore(state => state.ensureResource)
const addBuiltinMetric = useEvaluationStore(state => state.addBuiltinMetric)
const removeMetric = useEvaluationStore(state => state.removeMetric)
const updateMetricThreshold = useEvaluationStore(state => state.updateMetricThreshold)
const { data: availableMetricsData } = useAvailableEvaluationMetrics()
const resource = useEvaluationResource(resourceType, resourceId)
const config = getEvaluationMockConfig(resourceType)
const builtinMetricMap = useMemo(() => new Map(
resource.metrics
.filter(metric => metric.kind === 'builtin')
.map(metric => [metric.optionId, metric]),
), [resource.metrics])
const availableMetricIds = useMemo(() => new Set(availableMetricsData?.metrics ?? []), [availableMetricsData?.metrics])
const availableBuiltinMetrics = useMemo(() => {
return config.builtinMetrics.filter(metric =>
availableMetricIds.has(metric.id) || builtinMetricMap.has(metric.id),
)
}, [availableMetricIds, builtinMetricMap, config.builtinMetrics])
const isConfigReady = !!resource.judgeModelId && builtinMetricMap.size > 0
const isRunnable = isEvaluationRunnable(resource)
const actions = useInputFieldsActions({
resourceType,
resourceId,
inputFields: PIPELINE_INPUT_FIELDS,
isInputFieldsLoading: false,
isPanelReady: isConfigReady,
isRunnable,
templateFileName: config.templateFileName,
})
useEffect(() => {
ensureResource(resourceType, resourceId)
}, [ensureResource, resourceId, resourceType])
const handleToggleMetric = (metricId: string) => {
const selectedMetric = builtinMetricMap.get(metricId)
if (selectedMetric) {
removeMetric(resourceType, resourceId, selectedMetric.id)
return
}
addBuiltinMetric(resourceType, resourceId, metricId)
}
return (
<div className="flex h-full min-h-0 flex-col bg-background-default xl:flex-row">
<div className="flex min-h-0 flex-col border-b border-divider-subtle bg-background-default xl:w-[450px] xl:shrink-0 xl:border-r xl:border-b-0">
@ -108,57 +61,15 @@ const PipelineEvaluation = ({
</div>
</section>
<section>
<InlineSectionHeader title={t('metrics.title')} tooltip={t('metrics.description')} />
<div className="mt-1 space-y-0.5">
{availableBuiltinMetrics.map((metric) => {
const selectedMetric = builtinMetricMap.get(metric.id)
<PipelineMetricsSection
resourceType={resourceType}
resourceId={resourceId}
/>
return (
<PipelineMetricItem
key={metric.id}
metric={metric}
selected={!!selectedMetric}
threshold={selectedMetric?.threshold}
disabledCondition
onToggle={() => handleToggleMetric(metric.id)}
onThresholdChange={value => updateMetricThreshold(resourceType, resourceId, selectedMetric?.id ?? '', value)}
/>
)
})}
</div>
</section>
<div className="flex gap-2 pt-2">
<Button
className="flex-1 justify-center"
variant="secondary"
disabled={!actions.canDownloadTemplate}
onClick={actions.handleDownloadTemplate}
>
<span aria-hidden="true" className="mr-1 i-ri-file-excel-2-line h-4 w-4" />
{t('batch.downloadTemplate')}
</Button>
<div className="flex-1">
<UploadRunPopover
open={actions.isUploadPopoverOpen}
onOpenChange={actions.setIsUploadPopoverOpen}
triggerDisabled={actions.uploadButtonDisabled}
triggerLabel={t('pipeline.uploadAndRun')}
inputFields={PIPELINE_INPUT_FIELDS}
currentFileName={actions.currentFileName}
currentFileExtension={actions.currentFileExtension}
currentFileSize={actions.currentFileSize}
isFileUploading={actions.isFileUploading}
isRunDisabled={actions.isRunDisabled}
isRunning={actions.isRunning}
onUploadFile={actions.handleUploadFile}
onClearUploadedFile={actions.handleClearUploadedFile}
onDownloadTemplate={actions.handleDownloadTemplate}
onRun={actions.handleRun}
/>
</div>
</div>
<PipelineBatchActions
resourceType={resourceType}
resourceId={resourceId}
/>
</div>
</div>

View File

@ -0,0 +1,70 @@
'use client'
import type { EvaluationResourceProps } from '../../types'
import type { InputField } from '../batch-test-panel/input-fields/input-fields-utils'
import { useTranslation } from 'react-i18next'
import Button from '@/app/components/base/button'
import { getEvaluationMockConfig } from '../../mock'
import { isEvaluationRunnable, useEvaluationResource } from '../../store'
import UploadRunPopover from '../batch-test-panel/input-fields/upload-run-popover'
import { useInputFieldsActions } from '../batch-test-panel/input-fields/use-input-fields-actions'
const PIPELINE_INPUT_FIELDS: InputField[] = [
{ name: 'query', type: 'string' },
{ name: 'Expect Results', type: 'string' },
]
const PipelineBatchActions = ({
resourceType,
resourceId,
}: EvaluationResourceProps) => {
const { t } = useTranslation('evaluation')
const resource = useEvaluationResource(resourceType, resourceId)
const config = getEvaluationMockConfig(resourceType)
const isConfigReady = !!resource.judgeModelId && resource.metrics.some(metric => metric.kind === 'builtin')
const isRunnable = isEvaluationRunnable(resource)
const actions = useInputFieldsActions({
resourceType,
resourceId,
inputFields: PIPELINE_INPUT_FIELDS,
isInputFieldsLoading: false,
isPanelReady: isConfigReady,
isRunnable,
templateFileName: config.templateFileName,
})
return (
<div className="flex gap-2 pt-2">
<Button
className="flex-1 justify-center"
variant="secondary"
disabled={!actions.canDownloadTemplate}
onClick={actions.handleDownloadTemplate}
>
<span aria-hidden="true" className="mr-1 i-ri-file-excel-2-line h-4 w-4" />
{t('batch.downloadTemplate')}
</Button>
<div className="flex-1">
<UploadRunPopover
open={actions.isUploadPopoverOpen}
onOpenChange={actions.setIsUploadPopoverOpen}
triggerDisabled={actions.uploadButtonDisabled}
triggerLabel={t('pipeline.uploadAndRun')}
inputFields={PIPELINE_INPUT_FIELDS}
currentFileName={actions.currentFileName}
currentFileExtension={actions.currentFileExtension}
currentFileSize={actions.currentFileSize}
isFileUploading={actions.isFileUploading}
isRunDisabled={actions.isRunDisabled}
isRunning={actions.isRunning}
onUploadFile={actions.handleUploadFile}
onClearUploadedFile={actions.handleClearUploadedFile}
onDownloadTemplate={actions.handleDownloadTemplate}
onRun={actions.handleRun}
/>
</div>
</div>
)
}
export default PipelineBatchActions

View File

@ -0,0 +1,69 @@
'use client'
import type { EvaluationResourceProps } from '../../types'
import { useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { useAvailableEvaluationMetrics } from '@/service/use-evaluation'
import { getEvaluationMockConfig } from '../../mock'
import { useEvaluationResource, useEvaluationStore } from '../../store'
import { InlineSectionHeader } from '../section-header'
import PipelineMetricItem from './pipeline-metric-item'
const PipelineMetricsSection = ({
resourceType,
resourceId,
}: EvaluationResourceProps) => {
const { t } = useTranslation('evaluation')
const addBuiltinMetric = useEvaluationStore(state => state.addBuiltinMetric)
const removeMetric = useEvaluationStore(state => state.removeMetric)
const updateMetricThreshold = useEvaluationStore(state => state.updateMetricThreshold)
const { data: availableMetricsData } = useAvailableEvaluationMetrics()
const resource = useEvaluationResource(resourceType, resourceId)
const config = getEvaluationMockConfig(resourceType)
const builtinMetricMap = useMemo(() => new Map(
resource.metrics
.filter(metric => metric.kind === 'builtin')
.map(metric => [metric.optionId, metric]),
), [resource.metrics])
const availableMetricIds = useMemo(() => new Set(availableMetricsData?.metrics ?? []), [availableMetricsData?.metrics])
const availableBuiltinMetrics = useMemo(() => {
return config.builtinMetrics.filter(metric =>
availableMetricIds.has(metric.id) || builtinMetricMap.has(metric.id),
)
}, [availableMetricIds, builtinMetricMap, config.builtinMetrics])
const handleToggleMetric = (metricId: string) => {
const selectedMetric = builtinMetricMap.get(metricId)
if (selectedMetric) {
removeMetric(resourceType, resourceId, selectedMetric.id)
return
}
addBuiltinMetric(resourceType, resourceId, metricId)
}
return (
<section>
<InlineSectionHeader title={t('metrics.title')} tooltip={t('metrics.description')} />
<div className="mt-1 space-y-0.5">
{availableBuiltinMetrics.map((metric) => {
const selectedMetric = builtinMetricMap.get(metric.id)
return (
<PipelineMetricItem
key={metric.id}
metric={metric}
selected={!!selectedMetric}
threshold={selectedMetric?.threshold}
disabledCondition
onToggle={() => handleToggleMetric(metric.id)}
onThresholdChange={value => updateMetricThreshold(resourceType, resourceId, selectedMetric?.id ?? '', value)}
/>
)
})}
</div>
</section>
)
}
export default PipelineMetricsSection