diff --git a/web/app/components/evaluation/components/batch-test-panel/input-fields-tab.tsx b/web/app/components/evaluation/components/batch-test-panel/input-fields-tab.tsx index 1d0d4e210c..f70de8c5b2 100644 --- a/web/app/components/evaluation/components/batch-test-panel/input-fields-tab.tsx +++ b/web/app/components/evaluation/components/batch-test-panel/input-fields-tab.tsx @@ -1,12 +1,18 @@ import type { ChangeEvent } from 'react' import type { EvaluationResourceProps } from '../../types' +import type { StartNodeType } from '@/app/components/workflow/nodes/start/types' +import type { InputVar, Node } from '@/app/components/workflow/types' import { useMutation } from '@tanstack/react-query' -import { useRef } from 'react' +import { useMemo, useRef } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import { toast } from '@/app/components/base/ui/toast' +import { inputVarTypeToVarType } from '@/app/components/workflow/nodes/_base/components/variable/utils' +import { BlockEnum, InputVarType } from '@/app/components/workflow/types' import { upload } from '@/service/base' import { useStartEvaluationRunMutation } from '@/service/use-evaluation' +import { useSnippetPublishedWorkflow } from '@/service/use-snippet-workflows' +import { useAppWorkflow } from '@/service/use-workflow' import { getEvaluationMockConfig } from '../../mock' import { useEvaluationResource, useEvaluationStore } from '../../store' import { buildEvaluationRunRequest } from '../../store-utils' @@ -16,6 +22,37 @@ type InputFieldsTabProps = EvaluationResourceProps & { isRunnable: boolean } +type InputField = { + name: string + type: string +} + +const getGraphNodes = (graph?: Record) => { + return Array.isArray(graph?.nodes) ? graph.nodes as Node[] : [] +} + +const getStartNodeInputFields = (nodes?: Node[]): InputField[] => { + const startNode = nodes?.find(node => node.data.type === BlockEnum.Start) as Node | undefined + const variables = startNode?.data.variables + + if (!Array.isArray(variables)) + return [] + + return variables + .filter((variable): variable is InputVar => typeof variable.variable === 'string' && !!variable.variable) + .map(variable => ({ + name: variable.variable, + type: inputVarTypeToVarType(variable.type ?? InputVarType.textInput), + })) +} + +const escapeCsvCell = (value: string) => { + if (!/[",\n\r]/.test(value)) + return value + + return `"${value.replace(/"/g, '""')}"` +} + const InputFieldsTab = ({ resourceType, resourceId, @@ -24,10 +61,17 @@ const InputFieldsTab = ({ }: InputFieldsTabProps) => { const { t } = useTranslation('evaluation') const config = getEvaluationMockConfig(resourceType) - const requirementFields = config.fieldOptions - .filter(field => field.id.includes('.input.') || field.group.toLowerCase().includes('input')) - .slice(0, 4) - const displayedRequirementFields = requirementFields.length > 0 ? requirementFields : config.fieldOptions.slice(0, 4) + const { data: currentAppWorkflow, isLoading: isAppWorkflowLoading } = useAppWorkflow(resourceType === 'apps' ? resourceId : '') + const { data: currentSnippetWorkflow, isLoading: isSnippetWorkflowLoading } = useSnippetPublishedWorkflow(resourceType === 'snippets' ? resourceId : '') + const inputFields = useMemo(() => { + if (resourceType === 'apps') + return getStartNodeInputFields(currentAppWorkflow?.graph.nodes) + + if (resourceType === 'snippets') + return getStartNodeInputFields(getGraphNodes(currentSnippetWorkflow?.graph)) + + return [] + }, [currentAppWorkflow?.graph.nodes, currentSnippetWorkflow?.graph, resourceType]) const resource = useEvaluationResource(resourceType, resourceId) const uploadedFileId = resource.uploadedFileId const uploadedFileName = resource.uploadedFileName @@ -59,10 +103,18 @@ const InputFieldsTab = ({ const fileInputRef = useRef(null) const isFileUploading = uploadMutation.isPending const isRunning = startRunMutation.isPending + const isInputFieldsLoading = (resourceType === 'apps' && isAppWorkflowLoading) + || (resourceType === 'snippets' && isSnippetWorkflowLoading) + const canDownloadTemplate = isPanelReady && !isInputFieldsLoading && inputFields.length > 0 const isRunDisabled = !isRunnable || !uploadedFileId || isFileUploading || isRunning const handleDownloadTemplate = () => { - const content = ['case_id,input,expected', '1,Example input,Example output'].join('\n') + if (!inputFields.length) { + toast.warning(t('batch.noInputFields')) + return + } + + const content = `${inputFields.map(field => escapeCsvCell(field.name)).join(',')}\n` const link = document.createElement('a') link.href = `data:text/csv;charset=utf-8,${encodeURIComponent(content)}` link.download = config.templateFileName @@ -128,10 +180,20 @@ const InputFieldsTab = ({
{t('batch.requirementsTitle')}
{t('batch.requirementsDescription')}
- {displayedRequirementFields.map(field => ( -
+ {isInputFieldsLoading && ( +
+ {t('batch.loadingInputFields')} +
+ )} + {!isInputFieldsLoading && inputFields.length === 0 && ( +
+ {t('batch.noInputFields')} +
+ )} + {!isInputFieldsLoading && inputFields.map(field => ( +
- {field.label} + {field.name}
{field.type} @@ -141,7 +203,7 @@ const InputFieldsTab = ({
- diff --git a/web/i18n/en-US/evaluation.json b/web/i18n/en-US/evaluation.json index ec1af7f7f1..57033d17e4 100644 --- a/web/i18n/en-US/evaluation.json +++ b/web/i18n/en-US/evaluation.json @@ -3,6 +3,8 @@ "batch.downloadTemplate": "Download Excel Template", "batch.emptyHistory": "No test history yet.", "batch.fileRequired": "Upload an evaluation dataset file before running the test.", + "batch.loadingInputFields": "Loading input fields...", + "batch.noInputFields": "No published start node input fields found.", "batch.noticeDescription": "Configuration incomplete. Select the Judge Model and Metrics on the left to generate your batch test template.", "batch.noticeTitle": "Quick start", "batch.requirementsDescription": "The input variables required to run this batch test. Ensure your uploaded dataset matches these fields.", diff --git a/web/i18n/zh-Hans/evaluation.json b/web/i18n/zh-Hans/evaluation.json index f3157f0dec..2d54c30385 100644 --- a/web/i18n/zh-Hans/evaluation.json +++ b/web/i18n/zh-Hans/evaluation.json @@ -3,6 +3,8 @@ "batch.downloadTemplate": "下载 Excel 模板", "batch.emptyHistory": "还没有测试历史。", "batch.fileRequired": "请先上传评估数据集文件,再运行测试。", + "batch.loadingInputFields": "正在加载输入字段...", + "batch.noInputFields": "未找到已发布 Start 节点的输入字段。", "batch.noticeDescription": "配置尚未完成。请先在左侧选择判定模型和指标,以生成批量测试模板。", "batch.noticeTitle": "快速开始", "batch.requirementsDescription": "运行此批量测试所需的输入变量。请确保上传的数据集包含这些字段。",