Refactor input field form components and schema

This commit is contained in:
twwu 2025-04-27 15:29:11 +08:00
parent 8367ae85de
commit fd8ee9f53e
21 changed files with 933 additions and 774 deletions

View File

@ -12,14 +12,14 @@ import Option from './option'
type InputTypeSelectFieldProps = {
label: string
labeOptions?: Omit<LabelProps, 'htmlFor' | 'label'>
labelOptions?: Omit<LabelProps, 'htmlFor' | 'label'>
supportFile: boolean
className?: string
} & Omit<CustomSelectProps<FileTypeSelectOption>, 'options' | 'value' | 'onChange' | 'CustomTrigger' | 'CustomOption'>
const InputTypeSelectField = ({
label,
labeOptions,
labelOptions,
supportFile,
className,
...customSelectProps
@ -39,7 +39,7 @@ const InputTypeSelectField = ({
<Label
htmlFor={field.name}
label={label}
{...(labeOptions ?? {})}
{...(labelOptions ?? {})}
/>
<CustomSelect<FileTypeSelectOption>
value={field.state.value}

View File

@ -13,12 +13,8 @@ const BaseField = <T,>({
config,
}: BaseFieldProps<T>) => withForm({
defaultValues: initialData,
props: {
config,
},
render: function Render({
form,
config,
}) {
const { type, label, placeholder, variable, tooltip, showConditions, max, min, options, required, showOptional } = config

View File

@ -48,7 +48,7 @@ const BaseForm = <T,>({
initialData,
config,
})
return <FieldComponent key={index} form={baseForm} config={config} />
return <FieldComponent key={index} form={baseForm} />
})}
</div>
<baseForm.AppForm>

View File

@ -20,7 +20,7 @@ export type NumberConfiguration = {
}
export type SelectConfiguration = {
options?: Option[] // Options for select field
options: Option[] // Options for select field
}
export type BaseConfiguration<T> = {
@ -33,7 +33,7 @@ export type BaseConfiguration<T> = {
showConditions: ShowCondition<T>[] // Show this field only when all conditions are met
type: BaseFieldType
tooltip?: string // Tooltip for this field
} & NumberConfiguration & SelectConfiguration
} & NumberConfiguration & Partial<SelectConfiguration>
export type BaseFormProps<T> = {
initialData?: T

View File

@ -0,0 +1,221 @@
import React, { useMemo } from 'react'
import { type InputFieldConfiguration, InputFieldType } from './types'
import { withForm } from '../..'
import { useStore } from '@tanstack/react-form'
type InputFieldProps<T> = {
initialData?: T
config: InputFieldConfiguration<T>
}
const InputField = <T,>({
initialData,
config,
}: InputFieldProps<T>) => withForm({
defaultValues: initialData,
render: function Render({
form,
}) {
const {
type,
label,
placeholder,
variable,
tooltip,
showConditions,
max,
min,
required,
showOptional,
supportFile,
description,
options,
listeners,
} = config
const fieldValues = useStore(form.store, state => state.values)
const isAllConditionsMet = useMemo(() => {
if (!showConditions.length) return true
return showConditions.every((condition) => {
const { variable, value } = condition
const fieldValue = fieldValues[variable as keyof typeof fieldValues]
return fieldValue === value
})
}, [fieldValues, showConditions])
if (!isAllConditionsMet)
return <></>
if (type === InputFieldType.textInput) {
return (
<form.AppField
name={variable}
children={field => (
<field.TextField
label={label}
labelOptions={{
tooltip,
isRequired: required,
showOptional,
}}
placeholder={placeholder}
/>
)}
/>
)
}
if (type === InputFieldType.numberInput) {
return (
<form.AppField
name={variable}
children={field => (
<field.NumberInputField
label={label}
labelOptions={{
tooltip,
isRequired: required,
showOptional,
}}
placeholder={placeholder}
max={max}
min={min}
/>
)}
/>
)
}
if (type === InputFieldType.numberSlider) {
return (
<form.AppField
name={variable}
children={field => (
<field.NumberSliderField
label={label}
labelOptions={{
tooltip,
isRequired: required,
showOptional,
}}
description={description}
max={max}
min={min}
/>
)}
/>
)
}
if (type === InputFieldType.checkbox) {
return (
<form.AppField
name={variable}
children={field => (
<field.CheckboxField
label={label}
/>
)}
/>
)
}
if (type === InputFieldType.select) {
return (
<form.AppField
name={variable}
children={field => (
<field.SelectField
label={label}
labelOptions={{
tooltip,
isRequired: required,
showOptional,
}}
options={options!}
/>
)}
/>
)
}
if (type === InputFieldType.inputTypeSelect) {
return (
<form.AppField
name={variable}
listeners={listeners}
children={field => (
<field.InputTypeSelectField
label={label}
labelOptions={{
tooltip,
isRequired: required,
showOptional,
}}
supportFile={!!supportFile}
/>
)}
/>
)
}
if (type === InputFieldType.uploadMethod) {
return (
<form.AppField
name={variable}
children={field => (
<field.UploadMethodField
label={label}
labelOptions={{
tooltip,
isRequired: required,
showOptional,
}}
/>
)}
/>
)
}
if (type === InputFieldType.fileTypes) {
return (
<form.AppField
name={variable}
children={field => (
<field.FileTypesField
label={label}
labelOptions={{
tooltip,
isRequired: required,
showOptional,
}}
/>
)}
/>
)
}
if (type === InputFieldType.options) {
return (
<form.AppField
name={variable}
children={field => (
<field.OptionsField
label={label}
labelOptions={{
tooltip,
isRequired: required,
showOptional,
}}
/>
)}
/>
)
}
return <></>
},
})
export default InputField

View File

@ -1,103 +0,0 @@
import { useTranslation } from 'react-i18next'
import { InputType } from '../types'
import { InputVarType } from '@/app/components/workflow/types'
import { useMemo } from 'react'
import {
RiAlignLeft,
RiCheckboxLine,
RiFileCopy2Line,
RiFileTextLine,
RiHashtag,
RiListCheck3,
RiTextSnippet,
} from '@remixicon/react'
const i18nFileTypeMap: Record<string, string> = {
'file': 'single-file',
'file-list': 'multi-files',
}
const INPUT_TYPE_ICON = {
[InputVarType.textInput]: RiTextSnippet,
[InputVarType.paragraph]: RiAlignLeft,
[InputVarType.number]: RiHashtag,
[InputVarType.select]: RiListCheck3,
[InputVarType.checkbox]: RiCheckboxLine,
[InputVarType.singleFile]: RiFileTextLine,
[InputVarType.multiFiles]: RiFileCopy2Line,
}
const DATA_TYPE = {
[InputVarType.textInput]: 'string',
[InputVarType.paragraph]: 'string',
[InputVarType.number]: 'number',
[InputVarType.select]: 'string',
[InputVarType.checkbox]: 'boolean',
[InputVarType.singleFile]: 'file',
[InputVarType.multiFiles]: 'array[file]',
}
export const useInputTypeOptions = (supportFile: boolean) => {
const { t } = useTranslation()
const options = supportFile ? InputType.options : InputType.exclude(['file', 'file-list']).options
return options.map((value) => {
return {
value,
label: t(`appDebug.variableConfig.${i18nFileTypeMap[value] || value}`),
Icon: INPUT_TYPE_ICON[value],
type: DATA_TYPE[value],
}
})
}
export const useHiddenFieldNames = (type: InputVarType) => {
const { t } = useTranslation()
const hiddenFieldNames = useMemo(() => {
let fieldNames = []
switch (type) {
case InputVarType.textInput:
case InputVarType.paragraph:
fieldNames = [
t('appDebug.variableConfig.defaultValue'),
t('appDebug.variableConfig.placeholder'),
t('appDebug.variableConfig.tooltips'),
]
break
case InputVarType.number:
fieldNames = [
t('appDebug.variableConfig.defaultValue'),
t('appDebug.variableConfig.unit'),
t('appDebug.variableConfig.placeholder'),
t('appDebug.variableConfig.tooltips'),
]
break
case InputVarType.select:
fieldNames = [
t('appDebug.variableConfig.defaultValue'),
t('appDebug.variableConfig.tooltips'),
]
break
case InputVarType.singleFile:
fieldNames = [
t('appDebug.variableConfig.uploadMethod'),
t('appDebug.variableConfig.tooltips'),
]
break
case InputVarType.multiFiles:
fieldNames = [
t('appDebug.variableConfig.uploadMethod'),
t('appDebug.variableConfig.maxNumberOfUploads'),
t('appDebug.variableConfig.tooltips'),
]
break
default:
fieldNames = [
t('appDebug.variableConfig.tooltips'),
]
}
return fieldNames.map(name => name.toLowerCase()).join(', ')
}, [type, t])
return hiddenFieldNames
}

View File

@ -1,83 +0,0 @@
import { useTranslation } from 'react-i18next'
import { withForm } from '../../..'
import { type InputVar, SupportUploadFileTypes } from '@/app/components/workflow/types'
import { getNewVarInWorkflow } from '@/utils/var'
import { useField } from '@tanstack/react-form'
import Label from '../../../components/label'
import FileTypeItem from '@/app/components/workflow/nodes/_base/components/file-type-item'
import { useCallback, useMemo } from 'react'
type FileTypesFieldsProps = {
initialData?: InputVar
}
const UseFileTypesFields = ({
initialData,
}: FileTypesFieldsProps) => {
const FileTypesFields = useMemo(() => {
return withForm({
defaultValues: initialData || getNewVarInWorkflow(''),
render: function Render({
form,
}) {
const { t } = useTranslation()
const allowFileTypesField = useField({ form, name: 'allowed_file_types' })
const allowFileExtensionsField = useField({ form, name: 'allowed_file_extensions' })
const { value: allowed_file_types = [] } = allowFileTypesField.state
const { value: allowed_file_extensions = [] } = allowFileExtensionsField.state
const handleSupportFileTypeChange = useCallback((type: SupportUploadFileTypes) => {
let newAllowFileTypes = [...allowed_file_types]
if (type === SupportUploadFileTypes.custom) {
if (!newAllowFileTypes.includes(SupportUploadFileTypes.custom))
newAllowFileTypes = [SupportUploadFileTypes.custom]
else
newAllowFileTypes = newAllowFileTypes.filter(v => v !== type)
}
else {
newAllowFileTypes = newAllowFileTypes.filter(v => v !== SupportUploadFileTypes.custom)
if (newAllowFileTypes.includes(type))
newAllowFileTypes = newAllowFileTypes.filter(v => v !== type)
else
newAllowFileTypes.push(type)
}
allowFileTypesField.handleChange(newAllowFileTypes)
}, [allowFileTypesField, allowed_file_types])
const handleCustomFileTypesChange = useCallback((customFileTypes: string[]) => {
allowFileExtensionsField.handleChange(customFileTypes)
}, [allowFileExtensionsField])
return (
<div className='flex flex-col gap-y-0.5'>
<Label
htmlFor='allowed_file_types'
label={t('appDebug.variableConfig.file.supportFileTypes')}
/>
{
[SupportUploadFileTypes.document, SupportUploadFileTypes.image, SupportUploadFileTypes.audio, SupportUploadFileTypes.video].map((type: SupportUploadFileTypes) => (
<FileTypeItem
key={type}
type={type as SupportUploadFileTypes.image | SupportUploadFileTypes.document | SupportUploadFileTypes.audio | SupportUploadFileTypes.video}
selected={allowed_file_types.includes(type)}
onToggle={handleSupportFileTypeChange}
/>
))
}
<FileTypeItem
type={SupportUploadFileTypes.custom}
selected={allowed_file_types.includes(SupportUploadFileTypes.custom)}
onToggle={handleSupportFileTypeChange}
customFileTypes={allowed_file_extensions}
onCustomFileTypesChange={handleCustomFileTypesChange}
/>
</div>
)
},
})
}, [initialData])
return FileTypesFields
}
export default UseFileTypesFields

View File

@ -1,75 +0,0 @@
import { useTranslation } from 'react-i18next'
import { withForm } from '../../..'
import type { InputVar } from '@/app/components/workflow/types'
import { getNewVarInWorkflow } from '@/utils/var'
import { useField } from '@tanstack/react-form'
import Label from '../../../components/label'
import { useCallback, useMemo } from 'react'
import { useFileSizeLimit } from '@/app/components/base/file-uploader/hooks'
import { formatFileSize } from '@/utils/format'
import InputNumberWithSlider from '@/app/components/workflow/nodes/_base/components/input-number-with-slider'
import { useFileUploadConfig } from '@/service/use-common'
type MaxNumberOfUploadsFieldProps = {
initialData?: InputVar
}
const UseMaxNumberOfUploadsField = ({
initialData,
}: MaxNumberOfUploadsFieldProps) => {
const MaxNumberOfUploadsField = useMemo(() => {
return withForm({
defaultValues: initialData || getNewVarInWorkflow(''),
render: function Render({
form,
}) {
const { t } = useTranslation()
const maxNumberOfUploadsField = useField({ form, name: 'max_length' })
const { value: max_length = 0 } = maxNumberOfUploadsField.state
const { data: fileUploadConfigResponse } = useFileUploadConfig()
const {
imgSizeLimit,
docSizeLimit,
audioSizeLimit,
videoSizeLimit,
maxFileUploadLimit,
} = useFileSizeLimit(fileUploadConfigResponse)
const handleMaxUploadNumLimitChange = useCallback((value: number) => {
maxNumberOfUploadsField.handleChange(value)
}, [maxNumberOfUploadsField])
return (
<div className='flex flex-col gap-y-0.5'>
<Label
htmlFor='allowed_file_types'
label={t('appDebug.variableConfig.maxNumberOfUploads')}
/>
<div>
<div className='body-xs-regular mb-1.5 text-text-tertiary'>
{t('appDebug.variableConfig.maxNumberTip', {
imgLimit: formatFileSize(imgSizeLimit),
docLimit: formatFileSize(docSizeLimit),
audioLimit: formatFileSize(audioSizeLimit),
videoLimit: formatFileSize(videoSizeLimit),
})}
</div>
<InputNumberWithSlider
value={max_length}
min={1}
max={maxFileUploadLimit}
onChange={handleMaxUploadNumLimitChange}
/>
</div>
</div>
)
},
})
}, [initialData])
return MaxNumberOfUploadsField
}
export default UseMaxNumberOfUploadsField

View File

@ -1,64 +0,0 @@
import { useTranslation } from 'react-i18next'
import { withForm } from '../../..'
import type { InputVar } from '@/app/components/workflow/types'
import { getNewVarInWorkflow } from '@/utils/var'
import { useField } from '@tanstack/react-form'
import Label from '../../../components/label'
import { useCallback, useMemo } from 'react'
import OptionCard from '@/app/components/workflow/nodes/_base/components/option-card'
import { TransferMethod } from '@/types/app'
type UploadMethodFieldProps = {
initialData?: InputVar
}
const UseUploadMethodField = ({
initialData,
}: UploadMethodFieldProps) => {
const UploadMethodField = useMemo(() => {
return withForm({
defaultValues: initialData || getNewVarInWorkflow(''),
render: function Render({
form,
}) {
const { t } = useTranslation()
const allowFileUploadMethodField = useField({ form, name: 'allowed_file_upload_methods' })
const { value: allowed_file_upload_methods = [] } = allowFileUploadMethodField.state
const handleUploadMethodChange = useCallback((method: TransferMethod) => {
allowFileUploadMethodField.handleChange(method === TransferMethod.all ? [TransferMethod.local_file, TransferMethod.remote_url] : [method])
}, [allowFileUploadMethodField])
return (
<div className='flex flex-col gap-y-0.5'>
<Label
htmlFor='allowed_file_types'
label={t('appDebug.variableConfig.uploadFileTypes')}
/>
<div className='grid grid-cols-3 gap-2'>
<OptionCard
title={t('appDebug.variableConfig.localUpload')}
selected={allowed_file_upload_methods.length === 1 && allowed_file_upload_methods.includes(TransferMethod.local_file)}
onSelect={handleUploadMethodChange.bind(null, TransferMethod.local_file)}
/>
<OptionCard
title="URL"
selected={allowed_file_upload_methods.length === 1 && allowed_file_upload_methods.includes(TransferMethod.remote_url)}
onSelect={handleUploadMethodChange.bind(null, TransferMethod.remote_url)}
/>
<OptionCard
title={t('appDebug.variableConfig.both')}
selected={allowed_file_upload_methods.includes(TransferMethod.local_file) && allowed_file_upload_methods.includes(TransferMethod.remote_url)}
onSelect={handleUploadMethodChange.bind(null, TransferMethod.all)}
/>
</div>
</div>
)
},
})
}, [initialData])
return UploadMethodField
}
export default UseUploadMethodField

View File

@ -1,328 +0,0 @@
import { useTranslation } from 'react-i18next'
import { useAppForm } from '../..'
import { type FileTypeSelectOption, type InputFieldFormProps, TEXT_MAX_LENGTH, createInputFieldSchema } from './types'
import { getNewVarInWorkflow } from '@/utils/var'
import { useHiddenFieldNames, useInputTypeOptions } from './hooks'
import Divider from '../../../divider'
import { useCallback, useMemo, useState } from 'react'
import { useStore } from '@tanstack/react-form'
import { ChangeType, InputVarType } from '@/app/components/workflow/types'
import ShowAllSettings from './show-all-settings'
import Button from '../../../button'
import UseFileTypesFields from './hooks/use-file-types-fields'
import UseUploadMethodField from './hooks/use-upload-method-field'
import UseMaxNumberOfUploadsField from './hooks/use-max-number-of-uploads-filed'
import { DEFAULT_FILE_UPLOAD_SETTING } from '@/app/components/workflow/constants'
import { DEFAULT_VALUE_MAX_LEN } from '@/config'
import { RiArrowDownSLine } from '@remixicon/react'
import cn from '@/utils/classnames'
import Badge from '../../../badge'
import Toast from '../../../toast'
const InputFieldForm = ({
initialData,
supportFile = false,
onCancel,
onSubmit,
}: InputFieldFormProps) => {
const { t } = useTranslation()
const form = useAppForm({
defaultValues: initialData || getNewVarInWorkflow(''),
validators: {
onSubmit: ({ value }) => {
const { type } = value
const schema = createInputFieldSchema(type, t)
const result = schema.safeParse(value)
if (!result.success) {
const issues = result.error.issues
const firstIssue = issues[0].message
Toast.notify({
type: 'error',
message: firstIssue,
})
return firstIssue
}
return undefined
},
},
onSubmit: ({ value }) => {
const moreInfo = value.variable === initialData?.variable
? undefined
: {
type: ChangeType.changeVarName,
payload: { beforeKey: initialData?.variable || '', afterKey: value.variable },
}
onSubmit(value, moreInfo)
},
})
const [showAllSettings, setShowAllSettings] = useState(false)
const type = useStore(form.store, state => state.values.type)
const options = useStore(form.store, state => state.values.options)
const hiddenFieldNames = useHiddenFieldNames(type)
const inputTypes = useInputTypeOptions(supportFile)
const FileTypesFields = UseFileTypesFields({ initialData })
const UploadMethodField = UseUploadMethodField({ initialData })
const MaxNumberOfUploads = UseMaxNumberOfUploadsField({ initialData })
const isTextInput = [InputVarType.textInput, InputVarType.paragraph].includes(type)
const isNumberInput = type === InputVarType.number
const isSelectInput = type === InputVarType.select
const isSingleFile = type === InputVarType.singleFile
const isMultipleFile = type === InputVarType.multiFiles
const defaultSelectOptions = useMemo(() => {
if (isSelectInput && options) {
const defaultOptions = [
{
value: '',
label: t('appDebug.variableConfig.noDefaultSelected'),
},
]
const otherOptions = options.map((option: string) => ({
value: option,
label: option,
}))
return [...defaultOptions, ...otherOptions]
}
return []
}, [isSelectInput, options, t])
const handleTypeChange = useCallback((type: string) => {
if ([InputVarType.singleFile, InputVarType.multiFiles].includes(type as InputVarType)) {
(Object.keys(DEFAULT_FILE_UPLOAD_SETTING)).forEach((key) => {
if (key !== 'max_length')
form.setFieldValue(key as keyof typeof form.options.defaultValues, (DEFAULT_FILE_UPLOAD_SETTING as any)[key])
})
if (type === InputVarType.multiFiles)
form.setFieldValue('max_length', DEFAULT_FILE_UPLOAD_SETTING.max_length)
}
if (type === InputVarType.paragraph)
form.setFieldValue('max_length', DEFAULT_VALUE_MAX_LEN)
}, [form])
const handleShowAllSettings = useCallback(() => {
setShowAllSettings(true)
}, [])
return (
<form
className='w-full'
onSubmit={(e) => {
e.preventDefault()
e.stopPropagation()
form.handleSubmit()
}}
>
<div className='flex flex-col gap-4 px-4 py-2'>
<form.AppField
name='type'
children={field => (
<field.CustomSelectField<FileTypeSelectOption>
label={t('appDebug.variableConfig.fieldType')}
options={inputTypes}
onChange={handleTypeChange}
triggerProps={{
className: 'gap-x-0.5',
}}
popupProps={{
className: 'w-[368px]',
wrapperClassName: 'z-40',
itemClassName: 'gap-x-1',
}}
CustomTrigger={(option, open) => {
return (
<>
{option ? (
<>
<option.Icon className='h-4 w-4 shrink-0 text-text-tertiary' />
<span className='grow p-1'>{option.label}</span>
<div className='pr-0.5'>
<Badge text={option.type} uppercase={false} />
</div>
</>
) : (
<span className='grow p-1'>{t('common.placeholder.select')}</span>
)}
<RiArrowDownSLine
className={cn(
'h-4 w-4 shrink-0 text-text-quaternary group-hover:text-text-secondary',
open && 'text-text-secondary',
)}
/>
</>
)
}}
CustomOption={(option) => {
return (
<>
<option.Icon className='h-4 w-4 shrink-0 text-text-tertiary' />
<span className='grow px-1'>{option.label}</span>
<Badge text={option.type} uppercase={false} />
</>
)
}}
/>
)}
/>
<form.AppField
name='variable'
children={field => (
<field.TextField
label={t('appDebug.variableConfig.varName')}
placeholder={t('appDebug.variableConfig.inputPlaceholder')!}
/>
)}
/>
<form.AppField
name='label'
children={field => (
<field.TextField
label={t('appDebug.variableConfig.labelName')}
placeholder={t('appDebug.variableConfig.inputPlaceholder')!}
/>
)}
/>
{isTextInput && (
<form.AppField
name='max_length'
children={field => (
<field.NumberInputField
label={t('appDebug.variableConfig.maxLength')}
max={TEXT_MAX_LENGTH}
min={1}
/>
)}
/>
)}
{isSelectInput && (
<form.AppField
name='options'
listeners={{
onChange: () => {
form.setFieldValue('default', '')
},
}}
children={field => (
<field.OptionsField
label={t('appDebug.variableConfig.options')}
/>
)}
/>
)}
{(isSingleFile || isMultipleFile) && (
<FileTypesFields form={form} />
)}
<form.AppField
name='required'
children={field => (
<field.CheckboxField
label={t('appDebug.variableConfig.required')}
/>
)}
/>
<Divider type='horizontal' />
{!showAllSettings && (
<ShowAllSettings
handleShowAllSettings={handleShowAllSettings}
description={hiddenFieldNames}
/>
)}
{showAllSettings && (
<>
{isTextInput && (
<form.AppField
name='default'
children={field => (
<field.TextField
label={t('appDebug.variableConfig.defaultValue')}
placeholder={t('appDebug.variableConfig.defaultValuePlaceholder')!}
showOptional
/>
)}
/>
)}
{isNumberInput && (
<form.AppField
name='default'
children={field => (
<field.NumberInputField
label={t('appDebug.variableConfig.defaultValue')}
placeholder={t('appDebug.variableConfig.defaultValuePlaceholder')!}
showOptional
/>
)}
/>
)}
{isSelectInput && (
<form.AppField
name='default'
children={field => (
<field.SelectField
label={t('appDebug.variableConfig.startSelectedOption')}
options={defaultSelectOptions}
showOptional
/>
)}
/>
)}
{(isTextInput || isNumberInput) && (
<form.AppField
name='placeholder'
children={field => (
<field.TextField
label={t('appDebug.variableConfig.placeholder')}
placeholder={t('appDebug.variableConfig.placeholderPlaceholder')!}
showOptional
/>
)}
/>
)}
{isNumberInput && (
<form.AppField
name='unit'
children={field => (
<field.TextField
label={t('appDebug.variableConfig.unit')}
placeholder={t('appDebug.variableConfig.unitPlaceholder')!}
showOptional
/>
)}
/>
)}
{(isSingleFile || isMultipleFile) && (
<UploadMethodField form={form} />
)}
{isMultipleFile && (
<MaxNumberOfUploads form={form} />
)}
<form.AppField
name='hint'
children={(field) => {
return (
<field.TextField
label={t('appDebug.variableConfig.tooltips')}
placeholder={t('appDebug.variableConfig.tooltipsPlaceholder')!}
showOptional
/>
)
}
}
/>
</>
)}
</div>
<div className='flex items-center justify-end gap-x-2 p-4 pt-2'>
<Button variant='secondary' onClick={onCancel}>
{t('common.operation.cancel')}
</Button>
<form.AppForm>
<form.Actions />
</form.AppForm>
</div>
</form>
)
}
export default InputFieldForm

View File

@ -1,113 +1,39 @@
import { DEFAULT_FILE_UPLOAD_SETTING } from '@/app/components/workflow/constants'
import type { MoreInfo } from '@/app/components/workflow/types'
import { type InputVar, InputVarType } from '@/app/components/workflow/types'
import { MAX_VAR_KEY_LENGTH } from '@/config'
import type { RemixiconComponentType } from '@remixicon/react'
import type { TFunction } from 'i18next'
import { z } from 'zod'
import type { DeepKeys, FieldListeners } from '@tanstack/react-form'
import type { NumberConfiguration, SelectConfiguration, ShowCondition } from '../base/types'
export const TEXT_MAX_LENGTH = 256
export const InputType = z.enum([
'text-input',
'paragraph',
'number',
'select',
'checkbox',
'file',
'file-list',
])
const TransferMethod = z.enum([
'all',
'local_file',
'remote_url',
])
const SupportedFileTypes = z.enum([
'image',
'document',
'video',
'audio',
'custom',
])
export const createInputFieldSchema = (type: InputVarType, t: TFunction) => {
const commonSchema = z.object({
type: InputType,
variable: z.string({
invalid_type_error: t('appDebug.varKeyError.notValid', { key: t('appDebug.variableConfig.varName') }),
}).nonempty({
message: t('appDebug.varKeyError.canNoBeEmpty', { key: t('appDebug.variableConfig.varName') }),
}).max(MAX_VAR_KEY_LENGTH, {
message: t('appDebug.varKeyError.tooLong', { key: t('appDebug.variableConfig.varName') }),
}).regex(/^(?!\d)\w+/, {
message: t('appDebug.varKeyError.notStartWithNumber', { key: t('appDebug.variableConfig.varName') }),
}),
label: z.string().nonempty({
message: t('appDebug.variableConfig.errorMsg.labelNameRequired'),
}),
required: z.boolean(),
hint: z.string().optional(),
})
if (type === InputVarType.textInput || type === InputVarType.paragraph) {
return z.object({
max_length: z.number().min(1).max(TEXT_MAX_LENGTH),
default: z.string().optional(),
}).merge(commonSchema).passthrough()
}
if (type === InputVarType.number) {
return z.object({
default: z.number().optional(),
unit: z.string().optional(),
placeholder: z.string().optional(),
}).merge(commonSchema).passthrough()
}
if (type === InputVarType.select) {
return z.object({
options: z.array(z.string()).nonempty({
message: t('appDebug.variableConfig.errorMsg.atLeastOneOption'),
}).refine(
arr => new Set(arr).size === arr.length,
{
message: t('appDebug.variableConfig.errorMsg.optionRepeat'),
},
),
default: z.string().optional(),
}).merge(commonSchema).passthrough()
}
if (type === InputVarType.singleFile) {
return z.object({
allowed_file_types: z.array(SupportedFileTypes),
allowed_file_extensions: z.string().optional(),
allowed_file_upload_methods: z.array(TransferMethod),
}).merge(commonSchema).passthrough()
}
if (type === InputVarType.multiFiles) {
return z.object({
allowed_file_types: z.array(SupportedFileTypes),
allowed_file_extensions: z.array(z.string()).optional(),
allowed_file_upload_methods: z.array(TransferMethod),
max_length: z.number().min(1).max(DEFAULT_FILE_UPLOAD_SETTING.max_length),
}).merge(commonSchema).passthrough()
}
return commonSchema.passthrough()
export enum InputFieldType {
textInput = 'textInput',
numberInput = 'numberInput',
numberSlider = 'numberSlider',
checkbox = 'checkbox',
options = 'options',
select = 'select',
inputTypeSelect = 'inputTypeSelect',
uploadMethod = 'uploadMethod',
fileTypes = 'fileTypes',
}
export type InputFieldFormProps = {
initialData?: InputVar
supportFile?: boolean
onCancel: () => void
onSubmit: (value: InputVar, moreInfo?: MoreInfo) => void
export type InputTypeSelectConfiguration = {
supportFile: boolean
}
export type TextFieldsProps = {
initialData?: InputVar
export type NumberSliderConfiguration = {
description: string
max?: number
min?: number
}
export type FileTypeSelectOption = {
value: string
export type InputFieldConfiguration<T> = {
label: string
Icon: RemixiconComponentType
type: string
}
variable: DeepKeys<T> // Variable name
maxLength?: number // Max length for text input
placeholder?: string
required: boolean
showOptional?: boolean // show optional label
showConditions: ShowCondition<T>[] // Show this field only when all conditions are met
type: InputFieldType
tooltip?: string // Tooltip for this field
listeners?: FieldListeners<T, DeepKeys<T>> // Listener for this field
} & NumberConfiguration & Partial<InputTypeSelectConfiguration>
& Partial<NumberSliderConfiguration>
& Partial<SelectConfiguration>

View File

@ -0,0 +1,65 @@
import type { ZodSchema, ZodString } from 'zod'
import { z } from 'zod'
import { type InputFieldConfiguration, InputFieldType } from './types'
export const generateZodSchema = <T>(fields: InputFieldConfiguration<T>[]) => {
const shape: Record<string, ZodSchema> = {}
fields.forEach((field) => {
let zodType
switch (field.type) {
case InputFieldType.textInput:
zodType = z.string()
break
case InputFieldType.numberInput:
zodType = z.number()
break
case InputFieldType.numberSlider:
zodType = z.number()
break
case InputFieldType.checkbox:
zodType = z.boolean()
break
case InputFieldType.fileTypes:
zodType = z.array(z.string())
break
case InputFieldType.inputTypeSelect:
zodType = z.string()
break
case InputFieldType.uploadMethod:
zodType = z.array(z.string())
break
default:
zodType = z.any()
break
}
if (field.required) {
if ([InputFieldType.textInput].includes(field.type))
zodType = (zodType as ZodString).nonempty(`${field.label} is required`)
}
else {
zodType = zodType.optional()
}
if (field.maxLength) {
if ([InputFieldType.textInput].includes(field.type))
zodType = (zodType as ZodString).max(field.maxLength, `${field.label} exceeds max length of ${field.maxLength}`)
}
if (field.min) {
if ([InputFieldType.numberInput].includes(field.type))
zodType = (zodType as ZodString).min(field.min, `${field.label} must be at least ${field.min}`)
}
if (field.max) {
if ([InputFieldType.numberInput].includes(field.type))
zodType = (zodType as ZodString).max(field.max, `${field.label} exceeds max value of ${field.max}`)
}
shape[field.variable] = zodType
})
return z.object(shape)
}

View File

@ -11,8 +11,6 @@ import FileTypesField from './components/field/file-types'
import UploadMethodField from './components/field/upload-method'
import NumberSliderField from './components/field/number-slider'
export type FormType = ReturnType<typeof useFormContext>
export const { fieldContext, useFieldContext, formContext, useFormContext }
= createFormHookContexts()
@ -35,3 +33,5 @@ export const { useAppForm, withForm } = createFormHook({
fieldContext,
formContext,
})
export type FormType = ReturnType<typeof useFormContext>

View File

@ -0,0 +1,303 @@
import { useTranslation } from 'react-i18next'
import { InputVarType } from '@/app/components/workflow/types'
import { useCallback, useMemo } from 'react'
import type { InputFieldConfiguration } from '@/app/components/base/form/form-scenarios/input-field/types'
import { InputFieldType } from '@/app/components/base/form/form-scenarios/input-field/types'
import type { DeepKeys } from '@tanstack/react-form'
import { useFileUploadConfig } from '@/service/use-common'
import { useFileSizeLimit } from '@/app/components/base/file-uploader/hooks'
import { formatFileSize } from '@/utils/format'
import { DEFAULT_FILE_UPLOAD_SETTING } from '@/app/components/workflow/constants'
import { DEFAULT_VALUE_MAX_LEN } from '@/config'
import type { FormData } from './types'
import { TEXT_MAX_LENGTH } from './schema'
export const useHiddenFieldNames = (type: InputVarType) => {
const { t } = useTranslation()
const hiddenFieldNames = useMemo(() => {
let fieldNames = []
switch (type) {
case InputVarType.textInput:
case InputVarType.paragraph:
fieldNames = [
t('appDebug.variableConfig.defaultValue'),
t('appDebug.variableConfig.placeholder'),
t('appDebug.variableConfig.tooltips'),
]
break
case InputVarType.number:
fieldNames = [
t('appDebug.variableConfig.defaultValue'),
t('appDebug.variableConfig.unit'),
t('appDebug.variableConfig.placeholder'),
t('appDebug.variableConfig.tooltips'),
]
break
case InputVarType.select:
fieldNames = [
t('appDebug.variableConfig.defaultValue'),
t('appDebug.variableConfig.tooltips'),
]
break
case InputVarType.singleFile:
fieldNames = [
t('appDebug.variableConfig.uploadMethod'),
t('appDebug.variableConfig.tooltips'),
]
break
case InputVarType.multiFiles:
fieldNames = [
t('appDebug.variableConfig.uploadMethod'),
t('appDebug.variableConfig.maxNumberOfUploads'),
t('appDebug.variableConfig.tooltips'),
]
break
default:
fieldNames = [
t('appDebug.variableConfig.tooltips'),
]
}
return fieldNames.map(name => name.toLowerCase()).join(', ')
}, [type, t])
return hiddenFieldNames
}
export const useConfigurations = (props: {
type: string,
options: string[] | undefined,
setFieldValue: (fieldName: DeepKeys<FormData>, value: any) => void,
supportFile: boolean
}) => {
const { t } = useTranslation()
const { type, options, setFieldValue, supportFile } = props
const { data: fileUploadConfigResponse } = useFileUploadConfig()
const {
imgSizeLimit,
docSizeLimit,
audioSizeLimit,
videoSizeLimit,
} = useFileSizeLimit(fileUploadConfigResponse)
const isSelectInput = type === InputVarType.select
const defaultSelectOptions = useMemo(() => {
if (isSelectInput && options) {
const defaultOptions = [
{
value: '',
label: t('appDebug.variableConfig.noDefaultSelected'),
},
]
const otherOptions = options.map((option: string) => ({
value: option,
label: option,
}))
return [...defaultOptions, ...otherOptions]
}
return []
}, [isSelectInput, options, t])
const handleTypeChange = useCallback((type: string) => {
if ([InputVarType.singleFile, InputVarType.multiFiles].includes(type as InputVarType)) {
setFieldValue('allowedFileUploadMethods', DEFAULT_FILE_UPLOAD_SETTING.allowed_file_upload_methods)
setFieldValue('allowedTypesAndExtensions', {
allowedFileTypes: DEFAULT_FILE_UPLOAD_SETTING.allowed_file_types,
allowedFileExtensions: DEFAULT_FILE_UPLOAD_SETTING.allowed_file_extensions,
})
if (type === InputVarType.multiFiles)
setFieldValue('maxLength', DEFAULT_FILE_UPLOAD_SETTING.max_length)
}
if (type === InputVarType.paragraph)
setFieldValue('maxLength', DEFAULT_VALUE_MAX_LEN)
}, [setFieldValue])
const initialConfigurations = useMemo((): InputFieldConfiguration<FormData>[] => {
return [{
type: InputFieldType.inputTypeSelect,
label: t('appDebug.variableConfig.fieldType'),
variable: 'type',
required: true,
showConditions: [],
listeners: {
onChange: ({ value }) => handleTypeChange(value as string),
},
supportFile,
}, {
type: InputFieldType.textInput,
label: t('appDebug.variableConfig.varName'),
variable: 'variable',
placeholder: t('appDebug.variableConfig.inputPlaceholder'),
required: true,
showConditions: [],
}, {
type: InputFieldType.textInput,
label: t('appDebug.variableConfig.labelName'),
variable: 'label',
placeholder: t('appDebug.variableConfig.inputPlaceholder'),
required: true,
showConditions: [],
}, {
type: InputFieldType.numberInput,
label: t('appDebug.variableConfig.maxLength'),
variable: 'maxLength',
placeholder: t('appDebug.variableConfig.inputPlaceholder'),
required: true,
showConditions: [{
variable: 'type',
value: 'text-input',
}],
min: 1,
max: TEXT_MAX_LENGTH,
}, {
type: InputFieldType.options,
label: t('appDebug.variableConfig.options'),
variable: 'options',
required: true,
showConditions: [{
variable: 'type',
value: 'select',
}],
}, {
type: InputFieldType.fileTypes,
label: t('appDebug.variableConfig.file.supportFileTypes'),
variable: 'allowedTypesAndExtensions',
required: true,
showConditions: [{
variable: 'type',
value: 'file',
}],
}, {
type: InputFieldType.fileTypes,
label: t('appDebug.variableConfig.file.supportFileTypes'),
variable: 'allowedTypesAndExtensions',
required: true,
showConditions: [{
variable: 'type',
value: 'file-list',
}],
}, {
type: InputFieldType.checkbox,
label: t('appDebug.variableConfig.required'),
variable: 'required',
required: true,
showConditions: [],
}]
}, [handleTypeChange, supportFile, t])
const hiddenConfigurations = useMemo((): InputFieldConfiguration<FormData>[] => {
return [{
type: InputFieldType.textInput,
label: t('appDebug.variableConfig.defaultValue'),
variable: 'default',
placeholder: t('appDebug.variableConfig.defaultValuePlaceholder'),
required: false,
showConditions: [{
variable: 'type',
value: 'text-input',
}],
showOptional: true,
}, {
type: InputFieldType.textInput,
label: t('appDebug.variableConfig.defaultValue'),
variable: 'default',
placeholder: t('appDebug.variableConfig.defaultValuePlaceholder'),
required: false,
showConditions: [{
variable: 'type',
value: 'number',
}],
showOptional: true,
}, {
type: InputFieldType.select,
label: t('appDebug.variableConfig.startSelectedOption'),
variable: 'default',
required: false,
showConditions: [{
variable: 'type',
value: 'select',
}],
showOptional: true,
options: defaultSelectOptions,
}, {
type: InputFieldType.textInput,
label: t('appDebug.variableConfig.placeholder'),
variable: 'placeholder',
placeholder: t('appDebug.variableConfig.placeholderPlaceholder'),
required: false,
showConditions: [{
variable: 'type',
value: 'text-input',
}],
showOptional: true,
}, {
type: InputFieldType.textInput,
label: t('appDebug.variableConfig.unit'),
variable: 'unit',
placeholder: t('appDebug.variableConfig.unitPlaceholder'),
required: false,
showConditions: [{
variable: 'type',
value: 'number',
}],
showOptional: true,
}, {
type: InputFieldType.textInput,
label: t('appDebug.variableConfig.placeholder'),
variable: 'placeholder',
placeholder: t('appDebug.variableConfig.placeholderPlaceholder'),
required: false,
showConditions: [{
variable: 'type',
value: 'number',
}],
showOptional: true,
}, {
type: InputFieldType.uploadMethod,
label: t('appDebug.variableConfig.uploadFileTypes'),
variable: 'allowedFileUploadMethods',
required: false,
showConditions: [{
variable: 'type',
value: 'file',
}],
}, {
type: InputFieldType.uploadMethod,
label: t('appDebug.variableConfig.uploadFileTypes'),
variable: 'allowedFileUploadMethods',
required: false,
showConditions: [{
variable: 'type',
value: 'file-list',
}],
}, {
type: InputFieldType.numberSlider,
label: t('appDebug.variableConfig.maxNumberOfUploads'),
variable: 'maxLength',
required: false,
showConditions: [{
variable: 'type',
value: 'file-list',
}],
description: t('appDebug.variableConfig.maxNumberTip', {
imgLimit: formatFileSize(imgSizeLimit),
docLimit: formatFileSize(docSizeLimit),
audioLimit: formatFileSize(audioSizeLimit),
videoLimit: formatFileSize(videoSizeLimit),
}),
}, {
type: InputFieldType.textInput,
label: t('appDebug.variableConfig.tooltips'),
variable: 'hint',
required: false,
showConditions: [],
showOptional: true,
}]
}, [defaultSelectOptions, imgSizeLimit, docSizeLimit, audioSizeLimit, videoSizeLimit, t])
return {
initialConfigurations,
hiddenConfigurations,
}
}

View File

@ -0,0 +1,129 @@
import { useTranslation } from 'react-i18next'
import { useCallback, useState } from 'react'
import type { DeepKeys } from '@tanstack/react-form'
import { useStore } from '@tanstack/react-form'
import { ChangeType } from '@/app/components/workflow/types'
import { useFileUploadConfig } from '@/service/use-common'
import type { FormData, InputFieldFormProps } from './types'
import { useAppForm } from '@/app/components/base/form'
import { createInputFieldSchema } from './schema'
import Toast from '@/app/components/base/toast'
import { useFileSizeLimit } from '@/app/components/base/file-uploader/hooks'
import { useConfigurations, useHiddenFieldNames } from './hooks'
import Divider from '@/app/components/base/divider'
import ShowAllSettings from './show-all-settings'
import Button from '@/app/components/base/button'
import InputField from '@/app/components/base/form/form-scenarios/input-field/field'
const InputFieldForm = ({
initialData,
supportFile = false,
onCancel,
onSubmit,
}: InputFieldFormProps) => {
const { t } = useTranslation()
const { data: fileUploadConfigResponse } = useFileUploadConfig()
const {
maxFileUploadLimit,
} = useFileSizeLimit(fileUploadConfigResponse)
const inputFieldForm = useAppForm({
defaultValues: initialData,
validators: {
onSubmit: ({ value }) => {
const { type } = value
const schema = createInputFieldSchema(type, t, { maxFileUploadLimit })
const result = schema.safeParse(value)
if (!result.success) {
const issues = result.error.issues
const firstIssue = issues[0].message
Toast.notify({
type: 'error',
message: firstIssue,
})
return firstIssue
}
return undefined
},
},
onSubmit: ({ value }) => {
const moreInfo = value.variable === initialData?.variable
? undefined
: {
type: ChangeType.changeVarName,
payload: { beforeKey: initialData?.variable || '', afterKey: value.variable },
}
onSubmit(value, moreInfo)
},
})
const [showAllSettings, setShowAllSettings] = useState(false)
const type = useStore(inputFieldForm.store, state => state.values.type)
const options = useStore(inputFieldForm.store, state => state.values.options)
const setFieldValue = useCallback((fieldName: DeepKeys<FormData>, value: any) => {
inputFieldForm.setFieldValue(fieldName, value)
}, [inputFieldForm])
const hiddenFieldNames = useHiddenFieldNames(type)
const { initialConfigurations, hiddenConfigurations } = useConfigurations({
type,
options,
setFieldValue,
supportFile,
})
const handleShowAllSettings = useCallback(() => {
setShowAllSettings(true)
}, [])
return (
<form
className='w-full'
onSubmit={(e) => {
e.preventDefault()
e.stopPropagation()
inputFieldForm.handleSubmit()
}}
>
<div className='flex flex-col gap-4 px-4 py-2'>
{initialConfigurations.map((config, index) => {
const FieldComponent = InputField<FormData>({
initialData,
config,
})
return <FieldComponent key={`${config.type}-${index}`} form={inputFieldForm} />
})}
<Divider type='horizontal' />
{!showAllSettings && (
<ShowAllSettings
handleShowAllSettings={handleShowAllSettings}
description={hiddenFieldNames}
/>
)}
{showAllSettings && (
<>
{hiddenConfigurations.map((config, index) => {
const FieldComponent = InputField<FormData>({
initialData,
config,
})
return <FieldComponent key={`hidden-${config.type}-${index}`} form={inputFieldForm} />
})}
</>
)}
</div>
<div className='flex items-center justify-end gap-x-2 p-4 pt-2'>
<Button variant='secondary' onClick={onCancel}>
{t('common.operation.cancel')}
</Button>
<inputFieldForm.AppForm>
<inputFieldForm.Actions />
</inputFieldForm.AppForm>
</div>
</form>
)
}
export default InputFieldForm

View File

@ -0,0 +1,98 @@
import { InputVarType } from '@/app/components/workflow/types'
import { MAX_VAR_KEY_LENGTH } from '@/config'
import type { TFunction } from 'i18next'
import { z } from 'zod'
import type { SchemaOptions } from './types'
export const TEXT_MAX_LENGTH = 256
export const InputType = z.enum([
'text-input',
'paragraph',
'number',
'select',
'checkbox',
'file',
'file-list',
])
const TransferMethod = z.enum([
'all',
'local_file',
'remote_url',
])
const SupportedFileTypes = z.enum([
'image',
'document',
'video',
'audio',
'custom',
])
export const createInputFieldSchema = (type: InputVarType, t: TFunction, options: SchemaOptions) => {
const { maxFileUploadLimit } = options
const commonSchema = z.object({
type: InputType,
variable: z.string({
invalid_type_error: t('appDebug.varKeyError.notValid', { key: t('appDebug.variableConfig.varName') }),
}).nonempty({
message: t('appDebug.varKeyError.canNoBeEmpty', { key: t('appDebug.variableConfig.varName') }),
}).max(MAX_VAR_KEY_LENGTH, {
message: t('appDebug.varKeyError.tooLong', { key: t('appDebug.variableConfig.varName') }),
}).regex(/^(?!\d)\w+/, {
message: t('appDebug.varKeyError.notStartWithNumber', { key: t('appDebug.variableConfig.varName') }),
}),
label: z.string().nonempty({
message: t('appDebug.variableConfig.errorMsg.labelNameRequired'),
}),
required: z.boolean(),
hint: z.string().optional(),
})
if (type === InputVarType.textInput || type === InputVarType.paragraph) {
return z.object({
maxLength: z.number().min(1).max(TEXT_MAX_LENGTH),
default: z.string().optional(),
}).merge(commonSchema).passthrough()
}
if (type === InputVarType.number) {
return z.object({
default: z.number().optional(),
unit: z.string().optional(),
placeholder: z.string().optional(),
}).merge(commonSchema).passthrough()
}
if (type === InputVarType.select) {
return z.object({
options: z.array(z.string()).nonempty({
message: t('appDebug.variableConfig.errorMsg.atLeastOneOption'),
}).refine(
arr => new Set(arr).size === arr.length,
{
message: t('appDebug.variableConfig.errorMsg.optionRepeat'),
},
),
default: z.string().optional(),
}).merge(commonSchema).passthrough()
}
if (type === InputVarType.singleFile) {
return z.object({
allowedFileTypes: z.array(SupportedFileTypes),
allowedTypesAndExtensions: z.object({
allowedFileExtensions: z.string().optional(),
allowedFileUploadMethods: z.array(TransferMethod),
}),
}).merge(commonSchema).passthrough()
}
if (type === InputVarType.multiFiles) {
return z.object({
allowedFileTypes: z.array(SupportedFileTypes),
allowedTypesAndExtensions: z.object({
allowedFileExtensions: z.string().optional(),
allowedFileUploadMethods: z.array(TransferMethod),
}),
maxLength: z.number().min(1).max(maxFileUploadLimit),
}).merge(commonSchema).passthrough()
}
return commonSchema.passthrough()
}

View File

@ -0,0 +1,31 @@
import type { InputVarType, MoreInfo, SupportUploadFileTypes } from '@/app/components/workflow/types'
import type { TransferMethod } from '@/types/app'
export type FormData = {
type: InputVarType
label: string
variable: string
maxLength?: number
default?: string | number
required: boolean
hint?: string
options?: string[]
placeholder?: string
unit?: string
allowedFileUploadMethods?: TransferMethod[]
allowedTypesAndExtensions: {
allowedFileTypes?: SupportUploadFileTypes[]
allowedFileExtensions?: string[]
}
}
export type InputFieldFormProps = {
initialData: FormData
supportFile?: boolean
onCancel: () => void
onSubmit: (value: FormData, moreInfo?: MoreInfo) => void
}
export type SchemaOptions = {
maxFileUploadLimit: number
}

View File

@ -1,7 +1,8 @@
import InputFieldForm from '@/app/components/base/form/form-scenarios/input-field'
import { RiCloseLine } from '@remixicon/react'
import DialogWrapper from './dialog-wrapper'
import DialogWrapper from '../dialog-wrapper'
import type { InputVar } from '@/app/components/workflow/types'
import InputFieldForm from './form'
import { convertToInputFieldFormData } from './utils'
type InputFieldEditorProps = {
show: boolean
@ -14,6 +15,8 @@ const InputFieldEditor = ({
onClose,
initialData,
}: InputFieldEditorProps) => {
const formData = convertToInputFieldFormData(initialData)
return (
<DialogWrapper
show={show}
@ -33,7 +36,7 @@ const InputFieldEditor = ({
<RiCloseLine className='size-4 text-text-tertiary' />
</button>
<InputFieldForm
initialData={initialData}
initialData={formData}
supportFile
onCancel={onClose}
onSubmit={(value) => {

View File

@ -0,0 +1,39 @@
import type { InputVar } from '@/app/components/workflow/types'
import type { FormData } from './form/types'
import { getNewVarInWorkflow } from '@/utils/var'
export const convertToInputFieldFormData = (data?: InputVar): FormData => {
const {
type,
label,
variable,
max_length,
'default': defaultValue,
required,
hint,
options,
placeholder,
unit,
allowed_file_upload_methods,
allowed_file_types,
allowed_file_extensions,
} = data || getNewVarInWorkflow('')
return {
type,
label: label as string,
variable,
maxLength: max_length,
default: defaultValue,
required,
hint,
options,
placeholder,
unit,
allowedFileUploadMethods: allowed_file_upload_methods,
allowedTypesAndExtensions: {
allowedFileTypes: allowed_file_types,
allowedFileExtensions: allowed_file_extensions,
},
}
}

View File

@ -5,6 +5,7 @@ import {
PRE_PROMPT_PLACEHOLDER_TEXT,
QUERY_PLACEHOLDER_TEXT,
} from '@/app/components/base/prompt-editor/constants'
import type { InputVar } from '@/app/components/workflow/types'
import { InputVarType } from '@/app/components/workflow/types'
const otherAllowedRegex = /^\w+$/
@ -27,7 +28,7 @@ export const getNewVar = (key: string, type: string) => {
}
}
export const getNewVarInWorkflow = (key: string, type = InputVarType.textInput) => {
export const getNewVarInWorkflow = (key: string, type = InputVarType.textInput): InputVar => {
const { max_length, ...rest } = VAR_ITEM_TEMPLATE_IN_WORKFLOW
if (type !== InputVarType.textInput) {
return {