mirror of
https://github.com/langgenius/dify.git
synced 2026-05-06 18:27:19 +08:00
feat(web): input fields display
This commit is contained in:
parent
12c3b2e0cd
commit
acef9630d5
@ -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<string, unknown>) => {
|
||||
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<StartNodeType> | 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<HTMLInputElement>(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 = ({
|
||||
<div className="system-md-semibold text-text-primary">{t('batch.requirementsTitle')}</div>
|
||||
<div className="mt-1 system-xs-regular text-text-tertiary">{t('batch.requirementsDescription')}</div>
|
||||
<div className="mt-3 rounded-xl bg-background-section p-3">
|
||||
{displayedRequirementFields.map(field => (
|
||||
<div key={field.id} className="flex items-center py-1">
|
||||
{isInputFieldsLoading && (
|
||||
<div className="px-1 py-0.5 system-xs-regular text-text-tertiary">
|
||||
{t('batch.loadingInputFields')}
|
||||
</div>
|
||||
)}
|
||||
{!isInputFieldsLoading && inputFields.length === 0 && (
|
||||
<div className="px-1 py-0.5 system-xs-regular text-text-tertiary">
|
||||
{t('batch.noInputFields')}
|
||||
</div>
|
||||
)}
|
||||
{!isInputFieldsLoading && inputFields.map(field => (
|
||||
<div key={field.name} className="flex items-center py-1">
|
||||
<div className="rounded px-1 py-0.5 system-xs-medium text-text-tertiary">
|
||||
{field.label}
|
||||
{field.name}
|
||||
</div>
|
||||
<div className="text-[10px] leading-3 text-text-quaternary">
|
||||
{field.type}
|
||||
@ -141,7 +203,7 @@ const InputFieldsTab = ({
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
<Button variant="secondary" className="w-full justify-center" disabled={!isPanelReady} onClick={handleDownloadTemplate}>
|
||||
<Button variant="secondary" className="w-full justify-center" disabled={!canDownloadTemplate} onClick={handleDownloadTemplate}>
|
||||
<span aria-hidden="true" className="mr-1 i-ri-download-line h-4 w-4" />
|
||||
{t('batch.downloadTemplate')}
|
||||
</Button>
|
||||
|
||||
@ -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.",
|
||||
|
||||
@ -3,6 +3,8 @@
|
||||
"batch.downloadTemplate": "下载 Excel 模板",
|
||||
"batch.emptyHistory": "还没有测试历史。",
|
||||
"batch.fileRequired": "请先上传评估数据集文件,再运行测试。",
|
||||
"batch.loadingInputFields": "正在加载输入字段...",
|
||||
"batch.noInputFields": "未找到已发布 Start 节点的输入字段。",
|
||||
"batch.noticeDescription": "配置尚未完成。请先在左侧选择判定模型和指标,以生成批量测试模板。",
|
||||
"batch.noticeTitle": "快速开始",
|
||||
"batch.requirementsDescription": "运行此批量测试所需的输入变量。请确保上传的数据集包含这些字段。",
|
||||
|
||||
Loading…
Reference in New Issue
Block a user