From 471d14f882427086af2931a5a98309e64dd14f2c Mon Sep 17 00:00:00 2001 From: JzoNg Date: Fri, 9 Jan 2026 18:31:36 +0800 Subject: [PATCH 1/4] step run of human input --- .../components/before-run-form/index.tsx | 27 +++++-- .../_base/components/workflow-panel/index.tsx | 1 + .../workflow-panel/last-run/use-last-run.ts | 9 +-- .../components/single-run-form.tsx | 61 +++++--------- .../hooks/use-single-run-form-params.ts | 80 ++++++++----------- web/service/workflow.ts | 24 ++++++ 6 files changed, 99 insertions(+), 103 deletions(-) diff --git a/web/app/components/workflow/nodes/_base/components/before-run-form/index.tsx b/web/app/components/workflow/nodes/_base/components/before-run-form/index.tsx index ad88e97d68..98e8b36c90 100644 --- a/web/app/components/workflow/nodes/_base/components/before-run-form/index.tsx +++ b/web/app/components/workflow/nodes/_base/components/before-run-form/index.tsx @@ -32,10 +32,12 @@ export type BeforeRunFormProps = { showSpecialResultPanel?: boolean existVarValuesInForms: Record[] filteredExistVarForms: FormProps[] - generatedFormContentData?: Record showGeneratedForm?: boolean handleShowGeneratedForm?: (data: Record) => void handleHideGeneratedForm?: () => void + formData?: any + handleSubmitHumanInputForm?: (data: any) => Promise + handleAfterHumanInputStepRun?: () => void } & Partial function formatValue(value: string | any, type: InputVarType) { @@ -73,14 +75,17 @@ const BeforeRunForm: FC = ({ forms, filteredExistVarForms, existVarValuesInForms, - generatedFormContentData, showGeneratedForm = false, handleShowGeneratedForm, handleHideGeneratedForm, + formData, + handleSubmitHumanInputForm, + handleAfterHumanInputStepRun, }) => { const { t } = useTranslation() const isHumanInput = nodeType === BlockEnum.HumanInput + const showBackButton = filteredExistVarForms.length > 0 const isFileLoaded = (() => { if (!forms || forms.length === 0) @@ -154,6 +159,11 @@ const BeforeRunForm: FC = ({ onRun(submitData) } + const handleHumanInputFormSubmit = async (data: any) => { + await handleSubmitHumanInputForm?.(data) + handleAfterHumanInputStepRun?.() + } + const hasRun = useRef(false) useEffect(() => { // React 18 run twice in dev mode @@ -162,7 +172,9 @@ const BeforeRunForm: FC = ({ hasRun.current = true if (filteredExistVarForms.length === 0 && !isHumanInput) onRun({}) - }, [filteredExistVarForms, onRun]) + if (filteredExistVarForms.length === 0 && isHumanInput) + handleShowGeneratedForm?.({}) + }, [filteredExistVarForms, handleShowGeneratedForm, isHumanInput, onRun]) if (filteredExistVarForms.length === 0 && !isHumanInput) return null @@ -187,14 +199,13 @@ const BeforeRunForm: FC = ({ ))} )} - {showGeneratedForm && generatedFormContentData && ( + {showGeneratedForm && formData && ( )} {!showGeneratedForm && ( diff --git a/web/app/components/workflow/nodes/_base/components/workflow-panel/index.tsx b/web/app/components/workflow/nodes/_base/components/workflow-panel/index.tsx index c834f29ab3..8278579d58 100644 --- a/web/app/components/workflow/nodes/_base/components/workflow-panel/index.tsx +++ b/web/app/components/workflow/nodes/_base/components/workflow-panel/index.tsx @@ -445,6 +445,7 @@ const BasePanel: FC = ({ {...passedLogParams} existVarValuesInForms={getExistVarValuesInForms(singleRunParams?.forms as any)} filteredExistVarForms={getFilteredExistVarForms(singleRunParams?.forms as any)} + handleAfterHumanInputStepRun={handleAfterCustomSingleRun} /> )} diff --git a/web/app/components/workflow/nodes/_base/components/workflow-panel/last-run/use-last-run.ts b/web/app/components/workflow/nodes/_base/components/workflow-panel/last-run/use-last-run.ts index 853296efd5..dcbf392a8f 100644 --- a/web/app/components/workflow/nodes/_base/components/workflow-panel/last-run/use-last-run.ts +++ b/web/app/components/workflow/nodes/_base/components/workflow-panel/last-run/use-last-run.ts @@ -133,6 +133,7 @@ const useLastRun = ({ const isLoopNode = blockType === BlockEnum.Loop const isAggregatorNode = blockType === BlockEnum.VariableAggregator const isCustomRunNode = isSupportCustomRunForm(blockType) + const isHumanInputNode = blockType === BlockEnum.HumanInput const { handleSyncWorkflowDraft } = useNodesSyncDraft() const { getData: getDataForCheckMore, @@ -342,17 +343,11 @@ const useLastRun = ({ return if (blockType === BlockEnum.TriggerWebhook || blockType === BlockEnum.TriggerPlugin || blockType === BlockEnum.TriggerSchedule) setShowVariableInspectPanel(true) - if (isCustomRunNode) { + if (isCustomRunNode || isHumanInputNode) { showSingleRun() return } const vars = singleRunParams?.getDependentVars?.() - // TODO human input - if (singleRunParams?.generatedFormContentData) { - singleRunParams?.handleShowGeneratedForm() - showSingleRun() - return - } // no need to input params if (isAggregatorNode ? checkAggregatorVarsSet(vars) : isAllVarsHasValue(vars)) { callRunApi({}, async () => { diff --git a/web/app/components/workflow/nodes/human-input/components/single-run-form.tsx b/web/app/components/workflow/nodes/human-input/components/single-run-form.tsx index d11a6b6c18..f295455603 100644 --- a/web/app/components/workflow/nodes/human-input/components/single-run-form.tsx +++ b/web/app/components/workflow/nodes/human-input/components/single-run-form.tsx @@ -7,58 +7,32 @@ import { useState } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' import ContentItem from '@/app/components/base/chat/chat/answer/human-input-content/content-item' -import { UserActionButtonType } from '@/app/components/workflow/nodes/human-input/types' +import { getButtonStyle, initializeInputs, splitByOutputVar } from '@/app/components/base/chat/chat/answer/human-input-content/utils' type Props = { nodeName: string - formContent: string - inputFields: FormInputItem[] - userActions: UserAction[] + data: { + form_content: string + inputs: FormInputItem[] + actions: UserAction[] + } showBackButton?: boolean handleBack?: () => void + onSubmit?: (data: any) => Promise } const FormContent = ({ nodeName, - formContent, - inputFields, - userActions, + data, showBackButton, handleBack, + onSubmit, }: Props) => { const { t } = useTranslation() - - const splitByOutputVar = (content: string): string[] => { - const outputVarRegex = /(\{\{#\$output\.[^#]+#\}\})/g - const parts = content.split(outputVarRegex) - return parts.filter(part => part.length > 0) - } - - const initializeInputs = (formInputs: FormInputItem[]) => { - const initialInputs: Record = {} - formInputs.forEach((item) => { - if (item.type === 'text-input' || item.type === 'paragraph') - initialInputs[item.output_variable_name] = '' - else - initialInputs[item.output_variable_name] = undefined - }) - return initialInputs - } - - const contentList = splitByOutputVar(formContent) - const defaultInputValues = initializeInputs(inputFields) - const [inputs, setInputs] = useState(defaultInputValues) - - const getButtonStyle = (style: UserActionButtonType) => { - if (style === UserActionButtonType.Primary) - return 'primary' - if (style === UserActionButtonType.Default) - return 'secondary' - if (style === UserActionButtonType.Accent) - return 'secondary-accent' - if (style === UserActionButtonType.Ghost) - return 'ghost' - } + const defaultInputs = initializeInputs(data.inputs) + const contentList = splitByOutputVar(data.form_content) + const [inputs, setInputs] = useState(defaultInputs) + const [isSubmitting, setIsSubmitting] = useState(false) // use immer const handleInputsChange = (name: string, value: any) => { @@ -69,7 +43,9 @@ const FormContent = ({ } const submit = async (actionID: string) => { - // TODO + setIsSubmitting(true) + await onSubmit?.({ inputs, action: actionID }) + setIsSubmitting(false) } return ( @@ -89,15 +65,16 @@ const FormContent = ({ ))}
- {userActions.map((action: any) => ( + {data.actions.map((action: any) => (