From c2fd595a82bfe5fba72527a93398f2ab727a3f42 Mon Sep 17 00:00:00 2001 From: JzoNg Date: Wed, 22 Apr 2026 08:01:54 +0800 Subject: [PATCH] Fix human input typing regressions --- .../(humanInputLayout)/form/[token]/form.tsx | 10 ++++++- .../__tests__/content-item.spec.tsx | 6 ++--- .../__tests__/human-input-form.spec.tsx | 10 ++++--- .../chat/answer/human-input-content/utils.ts | 18 ++++++++----- .../__tests__/component-ui.spec.tsx | 25 ++++++++++-------- .../__tests__/component.spec.tsx | 24 +++++++++-------- .../__tests__/input-field.spec.tsx | 15 ++++++----- .../plugins/hitl-input-block/component-ui.tsx | 2 ++ .../plugins/hitl-input-block/input-field.tsx | 26 +++++++------------ .../__tests__/variable-in-markdown.spec.tsx | 5 ++-- .../hooks/__tests__/use-form-content.spec.ts | 4 +-- .../workflow/nodes/human-input/types.ts | 14 ++++++---- 12 files changed, 91 insertions(+), 68 deletions(-) diff --git a/web/app/(humanInputLayout)/form/[token]/form.tsx b/web/app/(humanInputLayout)/form/[token]/form.tsx index 76dcd24293..d5d177616f 100644 --- a/web/app/(humanInputLayout)/form/[token]/form.tsx +++ b/web/app/(humanInputLayout)/form/[token]/form.tsx @@ -20,6 +20,7 @@ import ExpirationTime from '@/app/components/base/chat/chat/answer/human-input-c import { getButtonStyle } from '@/app/components/base/chat/chat/answer/human-input-content/utils' import Loading from '@/app/components/base/loading' import DifyLogo from '@/app/components/base/logo/dify-logo' +import { isParagraphFormInput } from '@/app/components/workflow/nodes/human-input/types' import useDocumentTitle from '@/hooks/use-document-title' import { useParams } from '@/next/navigation' import { useGetHumanInputForm, useSubmitHumanInputForm } from '@/service/use-share' @@ -67,7 +68,14 @@ const FormContent = () => { return const initialInputs: Record = {} formData.inputs.forEach((item) => { - initialInputs[item.output_variable_name] = item.default.type === 'variable' ? formData.resolved_default_values[item.output_variable_name] || '' : item.default.value + if (isParagraphFormInput(item)) { + initialInputs[item.output_variable_name] = item.default.type === 'variable' + ? formData.resolved_default_values[item.output_variable_name] || '' + : item.default.value + return + } + + initialInputs[item.output_variable_name] = '' }) setInputs(initialInputs) }, [formData?.inputs, formData?.resolved_default_values]) diff --git a/web/app/components/base/chat/chat/answer/human-input-content/__tests__/content-item.spec.tsx b/web/app/components/base/chat/chat/answer/human-input-content/__tests__/content-item.spec.tsx index b1a6ec51ae..c313a422c2 100644 --- a/web/app/components/base/chat/chat/answer/human-input-content/__tests__/content-item.spec.tsx +++ b/web/app/components/base/chat/chat/answer/human-input-content/__tests__/content-item.spec.tsx @@ -91,12 +91,12 @@ describe('ContentItem', () => { content="{{#$output.user_bio#}}" formInputFields={[ { - type: 'text-input', + type: 'select', output_variable_name: 'user_bio', - default: { + option_source: { type: 'constant', - value: '', selector: [], + value: [], }, } as FormInputItem, ]} diff --git a/web/app/components/base/chat/chat/answer/human-input-content/__tests__/human-input-form.spec.tsx b/web/app/components/base/chat/chat/answer/human-input-content/__tests__/human-input-form.spec.tsx index 4b3f7b2445..d49b71d086 100644 --- a/web/app/components/base/chat/chat/answer/human-input-content/__tests__/human-input-form.spec.tsx +++ b/web/app/components/base/chat/chat/answer/human-input-content/__tests__/human-input-form.spec.tsx @@ -114,14 +114,16 @@ describe('HumanInputForm', () => { ...mockFormData, inputs: [ { - type: 'text-input', + type: 'select', output_variable_name: 'field2', - default: { type: 'variable', value: '', selector: [] }, + option_source: { type: 'variable', value: [], selector: [] }, } as FormInputItem, { - type: 'number', + type: 'file', output_variable_name: 'field3', - default: { type: 'constant', value: '0', selector: [] }, + allowed_file_extensions: [], + allowed_file_types: [], + allowed_file_upload_methods: [], } as FormInputItem, ], resolved_default_values: { field2: 'default value' }, diff --git a/web/app/components/base/chat/chat/answer/human-input-content/utils.ts b/web/app/components/base/chat/chat/answer/human-input-content/utils.ts index bf36c34f46..46bd6dc670 100644 --- a/web/app/components/base/chat/chat/answer/human-input-content/utils.ts +++ b/web/app/components/base/chat/chat/answer/human-input-content/utils.ts @@ -1,5 +1,6 @@ import type { FormInputItem } from '@/app/components/workflow/nodes/human-input/types' import type { Locale } from '@/i18n-config' +import type { HumanInputResolvedValue } from '@/types/workflow' import dayjs from 'dayjs' import isSameOrAfter from 'dayjs/plugin/isSameOrAfter' import relativeTime from 'dayjs/plugin/relativeTime' @@ -34,13 +35,18 @@ export const splitByOutputVar = (content: string): string[] => { return parts.filter(part => part.length > 0) } -export const initializeInputs = (formInputs: FormInputItem[], defaultValues: Record = {}) => { - const initialInputs: Record = {} +export const initializeInputs = (formInputs: FormInputItem[], defaultValues: Record = {}) => { + const initialInputs: Record = {} formInputs.forEach((item) => { - if (isParagraphFormInput(item)) - initialInputs[item.output_variable_name] = item.default.type === 'variable' ? defaultValues[item.output_variable_name] || '' : item.default.value - else - initialInputs[item.output_variable_name] = undefined + if (isParagraphFormInput(item)) { + const resolvedValue = defaultValues[item.output_variable_name] + initialInputs[item.output_variable_name] = item.default.type === 'variable' && typeof resolvedValue === 'string' + ? resolvedValue + : item.default.value + return + } + + initialInputs[item.output_variable_name] = '' }) return initialInputs } diff --git a/web/app/components/base/prompt-editor/plugins/hitl-input-block/__tests__/component-ui.spec.tsx b/web/app/components/base/prompt-editor/plugins/hitl-input-block/__tests__/component-ui.spec.tsx index 9da2dcfd18..065f94a27f 100644 --- a/web/app/components/base/prompt-editor/plugins/hitl-input-block/__tests__/component-ui.spec.tsx +++ b/web/app/components/base/prompt-editor/plugins/hitl-input-block/__tests__/component-ui.spec.tsx @@ -1,15 +1,16 @@ import type { ComponentProps } from 'react' import type { WorkflowNodesMap } from '../../workflow-variable-block/node' -import type { FormInputItem } from '@/app/components/workflow/nodes/human-input/types' +import type { FormInputItem, ParagraphFormInput } from '@/app/components/workflow/nodes/human-input/types' import type { ValueSelector } from '@/app/components/workflow/types' import { LexicalComposer } from '@lexical/react/LexicalComposer' import { cleanup, fireEvent, render } from '@testing-library/react' -import { BlockEnum, InputVarType } from '@/app/components/workflow/types' +import { BlockEnum, InputVarType, SupportUploadFileTypes } from '@/app/components/workflow/types' +import { TransferMethod } from '@/types/app' import HITLInputComponentUI from '../component-ui' import { HITLInputNode } from '../node' -const createFormInput = (overrides?: Partial): FormInputItem => ({ +const createParagraphFormInput = (overrides?: Partial): ParagraphFormInput => ({ type: InputVarType.paragraph, output_variable_name: 'customer_name', default: { @@ -93,7 +94,7 @@ describe('HITLInputComponentUI', () => { const selector = ['node-2', 'answer'] as ValueSelector const { getByText } = renderComponent({ - formInput: createFormInput({ + formInput: createParagraphFormInput({ default: { type: 'variable', selector, @@ -114,14 +115,15 @@ describe('HITLInputComponentUI', () => { it('should render select option summary for constant options', () => { const { getByText } = renderComponent({ - formInput: createFormInput({ + formInput: { type: InputVarType.select, + output_variable_name: 'customer_name', option_source: { type: 'constant', selector: [], value: ['alpha', 'beta'], }, - }), + } satisfies FormInputItem, }) expect(getByText('alpha, beta')).toBeInTheDocument() @@ -129,13 +131,14 @@ describe('HITLInputComponentUI', () => { it('should render file-list summary with max uploads', () => { const { getByText } = renderComponent({ - formInput: createFormInput({ + formInput: { type: InputVarType.multiFiles, + output_variable_name: 'customer_name', allowed_file_extensions: ['.pdf'], - allowed_file_types: ['document'], - allowed_file_upload_methods: ['local_file'], + allowed_file_types: [SupportUploadFileTypes.document], + allowed_file_upload_methods: [TransferMethod.local_file], max_upload_count: 4, - }), + } satisfies FormInputItem, }) expect(getByText(/document/)).toBeInTheDocument() @@ -240,7 +243,7 @@ describe('HITLInputComponentUI', () => { it('should render variable selector when workflowNodesMap fallback is used', () => { const { getByText } = renderComponent({ workflowNodesMap: undefined as unknown as WorkflowNodesMap, - formInput: createFormInput({ + formInput: createParagraphFormInput({ default: { type: 'variable', selector: ['node-2', 'answer'] as ValueSelector, diff --git a/web/app/components/base/prompt-editor/plugins/hitl-input-block/__tests__/component.spec.tsx b/web/app/components/base/prompt-editor/plugins/hitl-input-block/__tests__/component.spec.tsx index ee82595d1c..d17495dfde 100644 --- a/web/app/components/base/prompt-editor/plugins/hitl-input-block/__tests__/component.spec.tsx +++ b/web/app/components/base/prompt-editor/plugins/hitl-input-block/__tests__/component.spec.tsx @@ -1,5 +1,5 @@ import type { RefObject } from 'react' -import type { FormInputItem } from '@/app/components/workflow/nodes/human-input/types' +import type { FormInputItem, ParagraphFormInput } from '@/app/components/workflow/nodes/human-input/types' import { render, screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { InputVarType } from '@/app/components/workflow/types' @@ -15,15 +15,17 @@ vi.mock('../../../hooks', () => ({ vi.mock('../component-ui', () => ({ default: ({ formInput, onChange }: { formInput?: FormInputItem, onChange: (payload: FormInputItem) => void }) => { - const basePayload: FormInputItem = formInput ?? { - type: InputVarType.paragraph, - output_variable_name: 'user_name', - default: { - type: 'constant', - selector: [], - value: 'hello', - }, - } + const basePayload: ParagraphFormInput = (formInput && formInput.type === InputVarType.paragraph + ? formInput + : { + type: InputVarType.paragraph, + output_variable_name: 'user_name', + default: { + type: 'constant', + selector: [], + value: 'hello', + }, + }) satisfies ParagraphFormInput return (