feat: Add default value support for all workflow start node variable types (#24129)
Co-authored-by: crazywoola <100913391+crazywoola@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -21,6 +21,10 @@ import Checkbox from '@/app/components/base/checkbox'
|
|||||||
import { DEFAULT_FILE_UPLOAD_SETTING } from '@/app/components/workflow/constants'
|
import { DEFAULT_FILE_UPLOAD_SETTING } from '@/app/components/workflow/constants'
|
||||||
import { DEFAULT_VALUE_MAX_LEN } from '@/config'
|
import { DEFAULT_VALUE_MAX_LEN } from '@/config'
|
||||||
import { SimpleSelect } from '@/app/components/base/select'
|
import { SimpleSelect } from '@/app/components/base/select'
|
||||||
|
import Textarea from '@/app/components/base/textarea'
|
||||||
|
import { FileUploaderInAttachmentWrapper } from '@/app/components/base/file-uploader'
|
||||||
|
import { TransferMethod } from '@/types/app'
|
||||||
|
import type { FileEntity } from '@/app/components/base/file-uploader/types'
|
||||||
|
|
||||||
const TEXT_MAX_LENGTH = 256
|
const TEXT_MAX_LENGTH = 256
|
||||||
|
|
||||||
@@ -82,6 +86,8 @@ const ConfigModal: FC<IConfigModalProps> = ({
|
|||||||
return () => {
|
return () => {
|
||||||
const newPayload = produce(tempPayload, (draft) => {
|
const newPayload = produce(tempPayload, (draft) => {
|
||||||
draft.type = type
|
draft.type = type
|
||||||
|
// Clear default value when switching types
|
||||||
|
draft.default = undefined
|
||||||
if ([InputVarType.singleFile, InputVarType.multiFiles].includes(type)) {
|
if ([InputVarType.singleFile, InputVarType.multiFiles].includes(type)) {
|
||||||
(Object.keys(DEFAULT_FILE_UPLOAD_SETTING)).forEach((key) => {
|
(Object.keys(DEFAULT_FILE_UPLOAD_SETTING)).forEach((key) => {
|
||||||
if (key !== 'max_length')
|
if (key !== 'max_length')
|
||||||
@@ -234,6 +240,41 @@ const ConfigModal: FC<IConfigModalProps> = ({
|
|||||||
</Field>
|
</Field>
|
||||||
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Default value for text input */}
|
||||||
|
{type === InputVarType.textInput && (
|
||||||
|
<Field title={t('appDebug.variableConfig.defaultValue')}>
|
||||||
|
<Input
|
||||||
|
value={tempPayload.default || ''}
|
||||||
|
onChange={e => handlePayloadChange('default')(e.target.value || undefined)}
|
||||||
|
placeholder={t('appDebug.variableConfig.inputPlaceholder')!}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Default value for paragraph */}
|
||||||
|
{type === InputVarType.paragraph && (
|
||||||
|
<Field title={t('appDebug.variableConfig.defaultValue')}>
|
||||||
|
<Textarea
|
||||||
|
value={tempPayload.default || ''}
|
||||||
|
onChange={e => handlePayloadChange('default')(e.target.value || undefined)}
|
||||||
|
placeholder={t('appDebug.variableConfig.inputPlaceholder')!}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Default value for number input */}
|
||||||
|
{type === InputVarType.number && (
|
||||||
|
<Field title={t('appDebug.variableConfig.defaultValue')}>
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
value={tempPayload.default || ''}
|
||||||
|
onChange={e => handlePayloadChange('default')(e.target.value || undefined)}
|
||||||
|
placeholder={t('appDebug.variableConfig.inputPlaceholder')!}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
)}
|
||||||
|
|
||||||
{type === InputVarType.select && (
|
{type === InputVarType.select && (
|
||||||
<>
|
<>
|
||||||
<Field title={t('appDebug.variableConfig.options')}>
|
<Field title={t('appDebug.variableConfig.options')}>
|
||||||
@@ -263,11 +304,30 @@ const ConfigModal: FC<IConfigModalProps> = ({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{[InputVarType.singleFile, InputVarType.multiFiles].includes(type) && (
|
{[InputVarType.singleFile, InputVarType.multiFiles].includes(type) && (
|
||||||
<FileUploadSetting
|
<>
|
||||||
payload={tempPayload as UploadFileSetting}
|
<FileUploadSetting
|
||||||
onChange={(p: UploadFileSetting) => setTempPayload(p as InputVar)}
|
payload={tempPayload as UploadFileSetting}
|
||||||
isMultiple={type === InputVarType.multiFiles}
|
onChange={(p: UploadFileSetting) => setTempPayload(p as InputVar)}
|
||||||
/>
|
isMultiple={type === InputVarType.multiFiles}
|
||||||
|
/>
|
||||||
|
<Field title={t('appDebug.variableConfig.defaultValue')}>
|
||||||
|
<FileUploaderInAttachmentWrapper
|
||||||
|
value={(type === InputVarType.singleFile ? (tempPayload.default ? [tempPayload.default] : []) : (tempPayload.default || [])) as unknown as FileEntity[]}
|
||||||
|
onChange={(files) => {
|
||||||
|
if (type === InputVarType.singleFile)
|
||||||
|
handlePayloadChange('default')(files?.[0] || undefined)
|
||||||
|
else
|
||||||
|
handlePayloadChange('default')(files || undefined)
|
||||||
|
}}
|
||||||
|
fileConfig={{
|
||||||
|
allowed_file_types: tempPayload.allowed_file_types || [SupportUploadFileTypes.document],
|
||||||
|
allowed_file_extensions: tempPayload.allowed_file_extensions || [],
|
||||||
|
allowed_file_upload_methods: tempPayload.allowed_file_upload_methods || [TransferMethod.remote_url],
|
||||||
|
number_limits: type === InputVarType.singleFile ? 1 : tempPayload.max_length || 5,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className='!mt-5 flex h-6 items-center space-x-2'>
|
<div className='!mt-5 flex h-6 items-center space-x-2'>
|
||||||
|
@@ -210,7 +210,7 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
...item.paragraph,
|
...item.paragraph,
|
||||||
default: value || item.default,
|
default: value || item.default || item.paragraph.default,
|
||||||
type: 'paragraph',
|
type: 'paragraph',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -218,7 +218,7 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => {
|
|||||||
const convertedNumber = Number(initInputs[item.number.variable]) ?? undefined
|
const convertedNumber = Number(initInputs[item.number.variable]) ?? undefined
|
||||||
return {
|
return {
|
||||||
...item.number,
|
...item.number,
|
||||||
default: convertedNumber || item.default,
|
default: convertedNumber || item.default || item.number.default,
|
||||||
type: 'number',
|
type: 'number',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -251,7 +251,7 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
...item['text-input'],
|
...item['text-input'],
|
||||||
default: value || item.default,
|
default: value || item.default || item['text-input'].default,
|
||||||
type: 'text-input',
|
type: 'text-input',
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@@ -183,7 +183,7 @@ export const useEmbeddedChatbot = () => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
...item.paragraph,
|
...item.paragraph,
|
||||||
default: value || item.default,
|
default: value || item.default || item.paragraph.default,
|
||||||
type: 'paragraph',
|
type: 'paragraph',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -191,7 +191,7 @@ export const useEmbeddedChatbot = () => {
|
|||||||
const convertedNumber = Number(initInputs[item.number.variable]) ?? undefined
|
const convertedNumber = Number(initInputs[item.number.variable]) ?? undefined
|
||||||
return {
|
return {
|
||||||
...item.number,
|
...item.number,
|
||||||
default: convertedNumber || item.default,
|
default: convertedNumber || item.default || item.number.default,
|
||||||
type: 'number',
|
type: 'number',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -224,7 +224,7 @@ export const useEmbeddedChatbot = () => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
...item['text-input'],
|
...item['text-input'],
|
||||||
default: value || item.default,
|
default: value || item.default || item['text-input'].default,
|
||||||
type: 'text-input',
|
type: 'text-input',
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import type { ChangeEvent, FC, FormEvent } from 'react'
|
import type { ChangeEvent, FC, FormEvent } from 'react'
|
||||||
import { useEffect } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import React, { useCallback } from 'react'
|
import React, { useCallback } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import {
|
import {
|
||||||
@@ -41,6 +41,7 @@ const RunOnce: FC<IRunOnceProps> = ({
|
|||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const media = useBreakpoints()
|
const media = useBreakpoints()
|
||||||
const isPC = media === MediaType.pc
|
const isPC = media === MediaType.pc
|
||||||
|
const [isInitialized, setIsInitialized] = useState(false)
|
||||||
|
|
||||||
const onClear = () => {
|
const onClear = () => {
|
||||||
const newInputs: Record<string, any> = {}
|
const newInputs: Record<string, any> = {}
|
||||||
@@ -64,16 +65,24 @@ const RunOnce: FC<IRunOnceProps> = ({
|
|||||||
}, [onInputsChange, inputsRef])
|
}, [onInputsChange, inputsRef])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (isInitialized) return
|
||||||
const newInputs: Record<string, any> = {}
|
const newInputs: Record<string, any> = {}
|
||||||
promptConfig.prompt_variables.forEach((item) => {
|
promptConfig.prompt_variables.forEach((item) => {
|
||||||
if (item.type === 'select')
|
if (item.type === 'select')
|
||||||
newInputs[item.key] = item.default
|
newInputs[item.key] = item.default
|
||||||
else if (item.type === 'string' || item.type === 'paragraph')
|
else if (item.type === 'string' || item.type === 'paragraph')
|
||||||
newInputs[item.key] = ''
|
newInputs[item.key] = item.default || ''
|
||||||
|
else if (item.type === 'number')
|
||||||
|
newInputs[item.key] = item.default
|
||||||
|
else if (item.type === 'file')
|
||||||
|
newInputs[item.key] = item.default
|
||||||
|
else if (item.type === 'file-list')
|
||||||
|
newInputs[item.key] = item.default || []
|
||||||
else
|
else
|
||||||
newInputs[item.key] = undefined
|
newInputs[item.key] = undefined
|
||||||
})
|
})
|
||||||
onInputsChange(newInputs)
|
onInputsChange(newInputs)
|
||||||
|
setIsInitialized(true)
|
||||||
}, [promptConfig.prompt_variables, onInputsChange])
|
}, [promptConfig.prompt_variables, onInputsChange])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -81,7 +90,7 @@ const RunOnce: FC<IRunOnceProps> = ({
|
|||||||
<section>
|
<section>
|
||||||
{/* input form */}
|
{/* input form */}
|
||||||
<form onSubmit={onSubmit}>
|
<form onSubmit={onSubmit}>
|
||||||
{(inputs === null || inputs === undefined || Object.keys(inputs).length === 0) ? null
|
{(inputs === null || inputs === undefined || Object.keys(inputs).length === 0) || !isInitialized ? null
|
||||||
: promptConfig.prompt_variables.map(item => (
|
: promptConfig.prompt_variables.map(item => (
|
||||||
<div className='mt-4 w-full' key={item.key}>
|
<div className='mt-4 w-full' key={item.key}>
|
||||||
<label className='system-md-semibold flex h-6 items-center text-text-secondary'>{item.name}</label>
|
<label className='system-md-semibold flex h-6 items-center text-text-secondary'>{item.name}</label>
|
||||||
@@ -122,6 +131,7 @@ const RunOnce: FC<IRunOnceProps> = ({
|
|||||||
)}
|
)}
|
||||||
{item.type === 'file' && (
|
{item.type === 'file' && (
|
||||||
<FileUploaderInAttachmentWrapper
|
<FileUploaderInAttachmentWrapper
|
||||||
|
value={inputs[item.key] ? [inputs[item.key]] : []}
|
||||||
onChange={(files) => { handleInputsChange({ ...inputsRef.current, [item.key]: getProcessedFiles(files)[0] }) }}
|
onChange={(files) => { handleInputsChange({ ...inputsRef.current, [item.key]: getProcessedFiles(files)[0] }) }}
|
||||||
fileConfig={{
|
fileConfig={{
|
||||||
...item.config,
|
...item.config,
|
||||||
@@ -131,6 +141,7 @@ const RunOnce: FC<IRunOnceProps> = ({
|
|||||||
)}
|
)}
|
||||||
{item.type === 'file-list' && (
|
{item.type === 'file-list' && (
|
||||||
<FileUploaderInAttachmentWrapper
|
<FileUploaderInAttachmentWrapper
|
||||||
|
value={inputs[item.key]}
|
||||||
onChange={(files) => { handleInputsChange({ ...inputsRef.current, [item.key]: getProcessedFiles(files) }) }}
|
onChange={(files) => { handleInputsChange({ ...inputsRef.current, [item.key]: getProcessedFiles(files) }) }}
|
||||||
fileConfig={{
|
fileConfig={{
|
||||||
...item.config,
|
...item.config,
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
memo,
|
memo,
|
||||||
useCallback,
|
useCallback,
|
||||||
useEffect,
|
|
||||||
useMemo,
|
useMemo,
|
||||||
} from 'react'
|
} from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
@@ -33,7 +32,7 @@ type Props = {
|
|||||||
const InputsPanel = ({ onRun }: Props) => {
|
const InputsPanel = ({ onRun }: Props) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const workflowStore = useWorkflowStore()
|
const workflowStore = useWorkflowStore()
|
||||||
const { inputs, setInputs } = useStore(s => ({
|
const { inputs } = useStore(s => ({
|
||||||
inputs: s.inputs,
|
inputs: s.inputs,
|
||||||
setInputs: s.setInputs,
|
setInputs: s.setInputs,
|
||||||
}))
|
}))
|
||||||
@@ -48,23 +47,13 @@ const InputsPanel = ({ onRun }: Props) => {
|
|||||||
const startVariables = startNode?.data.variables
|
const startVariables = startNode?.data.variables
|
||||||
const { checkInputsForm } = useCheckInputsForms()
|
const { checkInputsForm } = useCheckInputsForms()
|
||||||
|
|
||||||
const initialInputs = useMemo(() => {
|
const initialInputs = { ...inputs }
|
||||||
const initInputs: Record<string, any> = {}
|
if (startVariables) {
|
||||||
if (startVariables) {
|
startVariables.forEach((variable) => {
|
||||||
startVariables.forEach((variable) => {
|
if (variable.default)
|
||||||
if (variable.default)
|
initialInputs[variable.variable] = variable.default
|
||||||
initInputs[variable.variable] = variable.default
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return initInputs
|
|
||||||
}, [startVariables])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setInputs({
|
|
||||||
...initialInputs,
|
|
||||||
...inputs,
|
|
||||||
})
|
})
|
||||||
}, [initialInputs])
|
}
|
||||||
|
|
||||||
const variables = useMemo(() => {
|
const variables = useMemo(() => {
|
||||||
const data = startVariables || []
|
const data = startVariables || []
|
||||||
@@ -102,11 +91,11 @@ const InputsPanel = ({ onRun }: Props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const doRun = useCallback(() => {
|
const doRun = useCallback(() => {
|
||||||
if (!checkInputsForm(inputs, variables as any))
|
if (!checkInputsForm(initialInputs, variables as any))
|
||||||
return
|
return
|
||||||
onRun()
|
onRun()
|
||||||
handleRun({ inputs: getProcessedInputs(inputs, variables as any), files })
|
handleRun({ inputs: getProcessedInputs(initialInputs, variables as any), files })
|
||||||
}, [files, handleRun, inputs, onRun, variables, checkInputsForm])
|
}, [files, handleRun, initialInputs, onRun, variables, checkInputsForm])
|
||||||
|
|
||||||
const canRun = useMemo(() => {
|
const canRun = useMemo(() => {
|
||||||
if (files?.some(item => (item.transfer_method as any) === TransferMethod.local_file && !item.upload_file_id))
|
if (files?.some(item => (item.transfer_method as any) === TransferMethod.local_file && !item.upload_file_id))
|
||||||
@@ -128,7 +117,7 @@ const InputsPanel = ({ onRun }: Props) => {
|
|||||||
autoFocus={index === 0}
|
autoFocus={index === 0}
|
||||||
className='!block'
|
className='!block'
|
||||||
payload={variable}
|
payload={variable}
|
||||||
value={inputs[variable.variable]}
|
value={initialInputs[variable.variable]}
|
||||||
onChange={v => handleValueChange(variable.variable, v)}
|
onChange={v => handleValueChange(variable.variable, v)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -41,6 +41,7 @@ export const userInputsFormToPromptVariables = (useInputs: UserInputFormItem[] |
|
|||||||
options: [],
|
options: [],
|
||||||
is_context_var,
|
is_context_var,
|
||||||
hide: content.hide,
|
hide: content.hide,
|
||||||
|
default: content.default,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
else if (type === 'number') {
|
else if (type === 'number') {
|
||||||
@@ -51,6 +52,7 @@ export const userInputsFormToPromptVariables = (useInputs: UserInputFormItem[] |
|
|||||||
type,
|
type,
|
||||||
options: [],
|
options: [],
|
||||||
hide: content.hide,
|
hide: content.hide,
|
||||||
|
default: content.default,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
else if (type === 'select') {
|
else if (type === 'select') {
|
||||||
@@ -78,6 +80,7 @@ export const userInputsFormToPromptVariables = (useInputs: UserInputFormItem[] |
|
|||||||
number_limits: 1,
|
number_limits: 1,
|
||||||
},
|
},
|
||||||
hide: content.hide,
|
hide: content.hide,
|
||||||
|
default: content.default,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
else if (type === 'file-list') {
|
else if (type === 'file-list') {
|
||||||
@@ -93,6 +96,7 @@ export const userInputsFormToPromptVariables = (useInputs: UserInputFormItem[] |
|
|||||||
number_limits: content.max_length,
|
number_limits: content.max_length,
|
||||||
},
|
},
|
||||||
hide: content.hide,
|
hide: content.hide,
|
||||||
|
default: content.default,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
Reference in New Issue
Block a user