mirror of
https://github.com/langgenius/dify.git
synced 2026-05-09 21:28:25 +08:00
Fix human input typing regressions
This commit is contained in:
parent
85d05f5113
commit
c2fd595a82
@ -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<string, string> = {}
|
||||
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])
|
||||
|
||||
@ -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,
|
||||
]}
|
||||
|
||||
@ -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' },
|
||||
|
||||
@ -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<string, string> = {}) => {
|
||||
const initialInputs: Record<string, string | undefined> = {}
|
||||
export const initializeInputs = (formInputs: FormInputItem[], defaultValues: Record<string, HumanInputResolvedValue> = {}) => {
|
||||
const initialInputs: Record<string, string> = {}
|
||||
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
|
||||
}
|
||||
|
||||
@ -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>): FormInputItem => ({
|
||||
const createParagraphFormInput = (overrides?: Partial<ParagraphFormInput>): 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,
|
||||
|
||||
@ -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 (
|
||||
<div>
|
||||
<button
|
||||
@ -63,7 +65,7 @@ const createHookReturn = (): [RefObject<HTMLDivElement | null>, boolean] => {
|
||||
return [{ current: null }, false]
|
||||
}
|
||||
|
||||
const createInput = (overrides?: Partial<FormInputItem>): FormInputItem => ({
|
||||
const createInput = (overrides?: Partial<ParagraphFormInput>): ParagraphFormInput => ({
|
||||
type: InputVarType.paragraph,
|
||||
output_variable_name: 'user_name',
|
||||
default: {
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
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, VarType } from '@/app/components/workflow/types'
|
||||
import { InputVarType, SupportUploadFileTypes, VarType } from '@/app/components/workflow/types'
|
||||
import { TransferMethod } from '@/types/app'
|
||||
import InputField from '../input-field'
|
||||
|
||||
type VarReferencePickerProps = {
|
||||
@ -55,16 +56,16 @@ vi.mock('@/app/components/workflow/nodes/_base/components/file-upload-setting',
|
||||
__esModule: true,
|
||||
default: ({ onChange }: { onChange: (payload: {
|
||||
allowed_file_extensions: string[]
|
||||
allowed_file_types: string[]
|
||||
allowed_file_upload_methods: string[]
|
||||
allowed_file_types: SupportUploadFileTypes[]
|
||||
allowed_file_upload_methods: TransferMethod[]
|
||||
max_length?: number
|
||||
}) => void }) => (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onChange({
|
||||
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_length: 4,
|
||||
})}
|
||||
>
|
||||
@ -73,7 +74,7 @@ vi.mock('@/app/components/workflow/nodes/_base/components/file-upload-setting',
|
||||
),
|
||||
}))
|
||||
|
||||
const createPayload = (overrides?: Partial<FormInputItem>): FormInputItem => ({
|
||||
const createPayload = (overrides?: Partial<ParagraphFormInput>): ParagraphFormInput => ({
|
||||
type: InputVarType.paragraph,
|
||||
output_variable_name: 'valid_name',
|
||||
default: {
|
||||
|
||||
@ -21,6 +21,8 @@ import Modal from '../../../modal'
|
||||
import InputField from './input-field'
|
||||
import VariableBlock from './variable-block'
|
||||
|
||||
const i18nPrefix = 'nodes.humanInput.insertInputField'
|
||||
|
||||
type HITLInputComponentUIProps = {
|
||||
nodeId: string
|
||||
varName: string
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import type { Item as TypeSelectItem } from '@/app/components/app/configuration/config-var/config-modal/type-select'
|
||||
import type { FormInputItem, FormInputItemDefault, ParagraphFormInput } from '@/app/components/workflow/nodes/human-input/types'
|
||||
import type { ValueSelector } from '@/app/components/workflow/types'
|
||||
import type { UploadFileSetting, ValueSelector } from '@/app/components/workflow/types'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { produce } from 'immer'
|
||||
@ -151,36 +151,27 @@ const InputField: React.FC<InputFieldProps> = ({
|
||||
}
|
||||
})
|
||||
}, [])
|
||||
const handleFilePayloadChange = useCallback((payload: {
|
||||
allowed_file_extensions: string[]
|
||||
allowed_file_types: string[]
|
||||
allowed_file_upload_methods: string[]
|
||||
}) => {
|
||||
const handleFilePayloadChange = useCallback((payload: UploadFileSetting) => {
|
||||
setTempPayload((prev) => {
|
||||
if (!isFileFormInput(prev))
|
||||
return prev
|
||||
|
||||
return {
|
||||
...prev,
|
||||
allowed_file_extensions: payload.allowed_file_extensions,
|
||||
allowed_file_extensions: payload.allowed_file_extensions || [],
|
||||
allowed_file_types: payload.allowed_file_types,
|
||||
allowed_file_upload_methods: payload.allowed_file_upload_methods,
|
||||
}
|
||||
})
|
||||
}, [])
|
||||
const handleFileListPayloadChange = useCallback((payload: {
|
||||
allowed_file_extensions: string[]
|
||||
allowed_file_types: string[]
|
||||
allowed_file_upload_methods: string[]
|
||||
max_length?: number
|
||||
}) => {
|
||||
const handleFileListPayloadChange = useCallback((payload: UploadFileSetting) => {
|
||||
setTempPayload((prev) => {
|
||||
if (!isFileListFormInput(prev))
|
||||
return prev
|
||||
|
||||
return {
|
||||
...prev,
|
||||
allowed_file_extensions: payload.allowed_file_extensions,
|
||||
allowed_file_extensions: payload.allowed_file_extensions || [],
|
||||
allowed_file_types: payload.allowed_file_types,
|
||||
allowed_file_upload_methods: payload.allowed_file_upload_methods,
|
||||
max_upload_count: payload.max_length,
|
||||
@ -296,7 +287,10 @@ const InputField: React.FC<InputFieldProps> = ({
|
||||
{isFileFormInput(tempPayload) && (
|
||||
<div className="mt-4">
|
||||
<FileUploadSetting
|
||||
payload={tempPayload}
|
||||
payload={{
|
||||
...tempPayload,
|
||||
max_length: 1,
|
||||
}}
|
||||
isMultiple={false}
|
||||
onChange={handleFilePayloadChange}
|
||||
/>
|
||||
@ -307,7 +301,7 @@ const InputField: React.FC<InputFieldProps> = ({
|
||||
<FileUploadSetting
|
||||
payload={{
|
||||
...tempPayload,
|
||||
max_length: tempPayload.max_upload_count,
|
||||
max_length: tempPayload.max_upload_count || 5,
|
||||
}}
|
||||
isMultiple
|
||||
onChange={handleFileListPayloadChange}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import { InputVarType } from '@/app/components/workflow/types'
|
||||
import { Note, rehypeNotes, rehypeVariable, Variable } from '../variable-in-markdown'
|
||||
|
||||
describe('variable-in-markdown', () => {
|
||||
@ -87,7 +88,7 @@ describe('variable-in-markdown', () => {
|
||||
const { rerender } = render(
|
||||
<Note
|
||||
input={{
|
||||
type: 'paragraph',
|
||||
type: InputVarType.paragraph,
|
||||
output_variable_name: 'approval',
|
||||
default: {
|
||||
type: 'variable',
|
||||
@ -104,7 +105,7 @@ describe('variable-in-markdown', () => {
|
||||
rerender(
|
||||
<Note
|
||||
input={{
|
||||
type: 'paragraph',
|
||||
type: InputVarType.paragraph,
|
||||
output_variable_name: 'approval',
|
||||
default: {
|
||||
type: 'constant',
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { FormInputItem, HumanInputNodeType } from '../../types'
|
||||
import type { HumanInputNodeType, ParagraphFormInput } from '../../types'
|
||||
import { act, renderHook } from '@testing-library/react'
|
||||
import { BlockEnum, InputVarType } from '@/app/components/workflow/types'
|
||||
import useFormContent from '../use-form-content'
|
||||
@ -15,7 +15,7 @@ vi.mock('@/app/components/workflow/nodes/_base/hooks/use-node-crud', () => ({
|
||||
default: (...args: unknown[]) => mockUseNodeCrud(...args),
|
||||
}))
|
||||
|
||||
const createFormInput = (overrides: Partial<FormInputItem> = {}): FormInputItem => ({
|
||||
const createFormInput = (overrides: Partial<ParagraphFormInput> = {}): ParagraphFormInput => ({
|
||||
type: InputVarType.paragraph,
|
||||
output_variable_name: 'old_name',
|
||||
default: {
|
||||
|
||||
@ -3,7 +3,11 @@ import type {
|
||||
UploadFileSetting,
|
||||
ValueSelector,
|
||||
} from '@/app/components/workflow/types'
|
||||
import { InputVarType } from '@/app/components/workflow/types'
|
||||
import {
|
||||
InputVarType,
|
||||
SupportUploadFileTypes,
|
||||
} from '@/app/components/workflow/types'
|
||||
import { TransferMethod } from '@/types/app'
|
||||
|
||||
export type HumanInputNodeType = CommonNodeType & {
|
||||
delivery_methods: DeliveryMethod[]
|
||||
@ -175,8 +179,8 @@ export const createDefaultFileFormInput = (
|
||||
type: InputVarType.singleFile,
|
||||
output_variable_name,
|
||||
allowed_file_extensions: [],
|
||||
allowed_file_types: ['image'],
|
||||
allowed_file_upload_methods: ['local_file', 'remote_url'],
|
||||
allowed_file_types: [SupportUploadFileTypes.image],
|
||||
allowed_file_upload_methods: [TransferMethod.local_file, TransferMethod.remote_url],
|
||||
})
|
||||
|
||||
export const createDefaultFileListFormInput = (
|
||||
@ -185,8 +189,8 @@ export const createDefaultFileListFormInput = (
|
||||
type: InputVarType.multiFiles,
|
||||
output_variable_name,
|
||||
allowed_file_extensions: [],
|
||||
allowed_file_types: ['image'],
|
||||
allowed_file_upload_methods: ['local_file', 'remote_url'],
|
||||
allowed_file_types: [SupportUploadFileTypes.image],
|
||||
allowed_file_upload_methods: [TransferMethod.local_file, TransferMethod.remote_url],
|
||||
max_upload_count: 5,
|
||||
})
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user