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:
17hz
2025-08-22 18:44:48 +08:00
committed by GitHub
parent 51cc2bf429
commit ffe1685b54
6 changed files with 100 additions and 36 deletions

View File

@@ -21,6 +21,10 @@ import Checkbox from '@/app/components/base/checkbox'
import { DEFAULT_FILE_UPLOAD_SETTING } from '@/app/components/workflow/constants'
import { DEFAULT_VALUE_MAX_LEN } from '@/config'
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
@@ -82,6 +86,8 @@ const ConfigModal: FC<IConfigModalProps> = ({
return () => {
const newPayload = produce(tempPayload, (draft) => {
draft.type = type
// Clear default value when switching types
draft.default = undefined
if ([InputVarType.singleFile, InputVarType.multiFiles].includes(type)) {
(Object.keys(DEFAULT_FILE_UPLOAD_SETTING)).forEach((key) => {
if (key !== 'max_length')
@@ -234,6 +240,41 @@ const ConfigModal: FC<IConfigModalProps> = ({
</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 && (
<>
<Field title={t('appDebug.variableConfig.options')}>
@@ -263,11 +304,30 @@ const ConfigModal: FC<IConfigModalProps> = ({
)}
{[InputVarType.singleFile, InputVarType.multiFiles].includes(type) && (
<FileUploadSetting
payload={tempPayload as UploadFileSetting}
onChange={(p: UploadFileSetting) => setTempPayload(p as InputVar)}
isMultiple={type === InputVarType.multiFiles}
/>
<>
<FileUploadSetting
payload={tempPayload as UploadFileSetting}
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'>

View File

@@ -210,7 +210,7 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => {
return {
...item.paragraph,
default: value || item.default,
default: value || item.default || item.paragraph.default,
type: 'paragraph',
}
}
@@ -218,7 +218,7 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => {
const convertedNumber = Number(initInputs[item.number.variable]) ?? undefined
return {
...item.number,
default: convertedNumber || item.default,
default: convertedNumber || item.default || item.number.default,
type: 'number',
}
}
@@ -251,7 +251,7 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => {
return {
...item['text-input'],
default: value || item.default,
default: value || item.default || item['text-input'].default,
type: 'text-input',
}
})

View File

@@ -183,7 +183,7 @@ export const useEmbeddedChatbot = () => {
return {
...item.paragraph,
default: value || item.default,
default: value || item.default || item.paragraph.default,
type: 'paragraph',
}
}
@@ -191,7 +191,7 @@ export const useEmbeddedChatbot = () => {
const convertedNumber = Number(initInputs[item.number.variable]) ?? undefined
return {
...item.number,
default: convertedNumber || item.default,
default: convertedNumber || item.default || item.number.default,
type: 'number',
}
}
@@ -224,7 +224,7 @@ export const useEmbeddedChatbot = () => {
return {
...item['text-input'],
default: value || item.default,
default: value || item.default || item['text-input'].default,
type: 'text-input',
}
})

View File

@@ -1,5 +1,5 @@
import type { ChangeEvent, FC, FormEvent } from 'react'
import { useEffect } from 'react'
import { useEffect, useState } from 'react'
import React, { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import {
@@ -41,6 +41,7 @@ const RunOnce: FC<IRunOnceProps> = ({
const { t } = useTranslation()
const media = useBreakpoints()
const isPC = media === MediaType.pc
const [isInitialized, setIsInitialized] = useState(false)
const onClear = () => {
const newInputs: Record<string, any> = {}
@@ -64,16 +65,24 @@ const RunOnce: FC<IRunOnceProps> = ({
}, [onInputsChange, inputsRef])
useEffect(() => {
if (isInitialized) return
const newInputs: Record<string, any> = {}
promptConfig.prompt_variables.forEach((item) => {
if (item.type === 'select')
newInputs[item.key] = item.default
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
newInputs[item.key] = undefined
})
onInputsChange(newInputs)
setIsInitialized(true)
}, [promptConfig.prompt_variables, onInputsChange])
return (
@@ -81,7 +90,7 @@ const RunOnce: FC<IRunOnceProps> = ({
<section>
{/* input form */}
<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 => (
<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>
@@ -122,6 +131,7 @@ const RunOnce: FC<IRunOnceProps> = ({
)}
{item.type === 'file' && (
<FileUploaderInAttachmentWrapper
value={inputs[item.key] ? [inputs[item.key]] : []}
onChange={(files) => { handleInputsChange({ ...inputsRef.current, [item.key]: getProcessedFiles(files)[0] }) }}
fileConfig={{
...item.config,
@@ -131,6 +141,7 @@ const RunOnce: FC<IRunOnceProps> = ({
)}
{item.type === 'file-list' && (
<FileUploaderInAttachmentWrapper
value={inputs[item.key]}
onChange={(files) => { handleInputsChange({ ...inputsRef.current, [item.key]: getProcessedFiles(files) }) }}
fileConfig={{
...item.config,

View File

@@ -1,7 +1,6 @@
import {
memo,
useCallback,
useEffect,
useMemo,
} from 'react'
import { useTranslation } from 'react-i18next'
@@ -33,7 +32,7 @@ type Props = {
const InputsPanel = ({ onRun }: Props) => {
const { t } = useTranslation()
const workflowStore = useWorkflowStore()
const { inputs, setInputs } = useStore(s => ({
const { inputs } = useStore(s => ({
inputs: s.inputs,
setInputs: s.setInputs,
}))
@@ -48,23 +47,13 @@ const InputsPanel = ({ onRun }: Props) => {
const startVariables = startNode?.data.variables
const { checkInputsForm } = useCheckInputsForms()
const initialInputs = useMemo(() => {
const initInputs: Record<string, any> = {}
if (startVariables) {
startVariables.forEach((variable) => {
if (variable.default)
initInputs[variable.variable] = variable.default
})
}
return initInputs
}, [startVariables])
useEffect(() => {
setInputs({
...initialInputs,
...inputs,
const initialInputs = { ...inputs }
if (startVariables) {
startVariables.forEach((variable) => {
if (variable.default)
initialInputs[variable.variable] = variable.default
})
}, [initialInputs])
}
const variables = useMemo(() => {
const data = startVariables || []
@@ -102,11 +91,11 @@ const InputsPanel = ({ onRun }: Props) => {
}
const doRun = useCallback(() => {
if (!checkInputsForm(inputs, variables as any))
if (!checkInputsForm(initialInputs, variables as any))
return
onRun()
handleRun({ inputs: getProcessedInputs(inputs, variables as any), files })
}, [files, handleRun, inputs, onRun, variables, checkInputsForm])
handleRun({ inputs: getProcessedInputs(initialInputs, variables as any), files })
}, [files, handleRun, initialInputs, onRun, variables, checkInputsForm])
const canRun = useMemo(() => {
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}
className='!block'
payload={variable}
value={inputs[variable.variable]}
value={initialInputs[variable.variable]}
onChange={v => handleValueChange(variable.variable, v)}
/>
</div>

View File

@@ -41,6 +41,7 @@ export const userInputsFormToPromptVariables = (useInputs: UserInputFormItem[] |
options: [],
is_context_var,
hide: content.hide,
default: content.default,
})
}
else if (type === 'number') {
@@ -51,6 +52,7 @@ export const userInputsFormToPromptVariables = (useInputs: UserInputFormItem[] |
type,
options: [],
hide: content.hide,
default: content.default,
})
}
else if (type === 'select') {
@@ -78,6 +80,7 @@ export const userInputsFormToPromptVariables = (useInputs: UserInputFormItem[] |
number_limits: 1,
},
hide: content.hide,
default: content.default,
})
}
else if (type === 'file-list') {
@@ -93,6 +96,7 @@ export const userInputsFormToPromptVariables = (useInputs: UserInputFormItem[] |
number_limits: content.max_length,
},
hide: content.hide,
default: content.default,
})
}
else {