Co-authored-by: crazywoola <100913391+crazywoola@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>tags/1.8.0
| 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 | ||||
| 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') | ||||
| </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')}> | ||||
| )} | )} | ||||
| {[InputVarType.singleFile, InputVarType.multiFiles].includes(type) && ( | {[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'> | <div className='!mt-5 flex h-6 items-center space-x-2'> |
| return { | return { | ||||
| ...item.paragraph, | ...item.paragraph, | ||||
| default: value || item.default, | |||||
| default: value || item.default || item.paragraph.default, | |||||
| type: 'paragraph', | type: 'paragraph', | ||||
| } | } | ||||
| } | } | ||||
| 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', | ||||
| } | } | ||||
| } | } | ||||
| 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', | ||||
| } | } | ||||
| }) | }) |
| return { | return { | ||||
| ...item.paragraph, | ...item.paragraph, | ||||
| default: value || item.default, | |||||
| default: value || item.default || item.paragraph.default, | |||||
| type: 'paragraph', | type: 'paragraph', | ||||
| } | } | ||||
| } | } | ||||
| 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', | ||||
| } | } | ||||
| } | } | ||||
| 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', | ||||
| } | } | ||||
| }) | }) |
| 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 { | ||||
| 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> = {} | ||||
| }, [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 ( | ||||
| <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> | ||||
| )} | )} | ||||
| {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, | ||||
| )} | )} | ||||
| {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, |
| import { | import { | ||||
| memo, | memo, | ||||
| useCallback, | useCallback, | ||||
| useEffect, | |||||
| useMemo, | useMemo, | ||||
| } from 'react' | } from 'react' | ||||
| import { useTranslation } from 'react-i18next' | import { useTranslation } from 'react-i18next' | ||||
| 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, | ||||
| })) | })) | ||||
| const startVariables = startNode?.data.variables | const startVariables = startNode?.data.variables | ||||
| const { checkInputsForm } = useCheckInputsForms() | 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 variables = useMemo(() => { | ||||
| const data = startVariables || [] | const data = startVariables || [] | ||||
| } | } | ||||
| 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 }) | |||||
| }, [files, handleRun, inputs, onRun, variables, checkInputsForm]) | |||||
| handleRun({ inputs: getProcessedInputs(initialInputs, variables as any), files }) | |||||
| }, [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)) | ||||
| 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> |
| 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') { | ||||
| type, | type, | ||||
| options: [], | options: [], | ||||
| hide: content.hide, | hide: content.hide, | ||||
| default: content.default, | |||||
| }) | }) | ||||
| } | } | ||||
| else if (type === 'select') { | else if (type === 'select') { | ||||
| 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') { | ||||
| number_limits: content.max_length, | number_limits: content.max_length, | ||||
| }, | }, | ||||
| hide: content.hide, | hide: content.hide, | ||||
| default: content.default, | |||||
| }) | }) | ||||
| } | } | ||||
| else { | else { |