refactor: streamline input variable handling and enhance type definitions in human input components

This commit is contained in:
twwu 2026-01-21 15:44:14 +08:00
parent 528e3400da
commit 645793c48c
11 changed files with 44 additions and 29 deletions

View File

@ -1,5 +1,4 @@
import type { ValueSelector } from '../../workflow/types'
import { uniqBy } from 'es-toolkit/compat'
import { SupportUploadFileTypes } from '../../workflow/types'
export const CONTEXT_PLACEHOLDER_TEXT = '{{#context#}}'
@ -58,8 +57,7 @@ export const getInputVars = (text: string): ValueSelector[] => {
return valueSelector
})
const uniqueInputVars = uniqBy(inputVars, item => item.join('.'))
return uniqueInputVars
return inputVars
}
return []
}

View File

@ -4,6 +4,7 @@ import type { Props as FormProps } from './form'
import type { Emoji } from '@/app/components/tools/types'
import type { SpecialResultPanelProps } from '@/app/components/workflow/run/special-result-panel'
import type { NodeRunningStatus } from '@/app/components/workflow/types'
import type { HumanInputFormData } from '@/types/workflow'
import * as React from 'react'
import { useEffect, useRef } from 'react'
import { useTranslation } from 'react-i18next'
@ -35,7 +36,7 @@ export type BeforeRunFormProps = {
showGeneratedForm?: boolean
handleShowGeneratedForm?: (data: Record<string, any>) => void
handleHideGeneratedForm?: () => void
formData?: any
formData?: HumanInputFormData
handleSubmitHumanInputForm?: (data: any) => Promise<void>
handleAfterHumanInputStepRun?: () => void
} & Partial<SpecialResultPanelProps>

View File

@ -324,7 +324,7 @@ const BasePanel: FC<BasePanelProps> = ({
const currentDataSource = useMemo(() => {
if (data.type === BlockEnum.DataSource && data.provider_type !== DataSourceClassification.localFile)
return dataSourceList?.find(item => item.plugin_id === data.plugin_id)
}, [dataSourceList, data.provider_id, data.type, data.provider_type])
}, [data.type, data.provider_type, data.plugin_id, dataSourceList])
const handleAuthorizationItemClick = useCallback((credential_id: string) => {
handleNodeDataUpdateWithSyncDraft({

View File

@ -1,4 +1,4 @@
import type { DeliveryMethod, DeliveryMethodType } from '../../types'
import type { DeliveryMethod, DeliveryMethodType, FormInputItem } from '../../types'
import type {
Node,
NodeOutPutVar,
@ -19,6 +19,7 @@ type Props = {
nodesOutputVars?: NodeOutPutVar[]
availableNodes?: Node[]
formContent?: string
formInputs?: FormInputItem[]
onChange: (value: DeliveryMethod[]) => void
readonly?: boolean
}
@ -29,6 +30,7 @@ const DeliveryMethodForm: React.FC<Props> = ({
nodesOutputVars,
availableNodes,
formContent,
formInputs,
onChange,
readonly,
}) => {
@ -95,6 +97,7 @@ const DeliveryMethodForm: React.FC<Props> = ({
nodesOutputVars={nodesOutputVars}
availableNodes={availableNodes}
formContent={formContent}
formInputs={formInputs}
readonly={readonly}
/>
))}

View File

@ -1,5 +1,5 @@
import type { FC } from 'react'
import type { DeliveryMethod, EmailConfig } from '../../types'
import type { DeliveryMethod, EmailConfig, FormInputItem } from '../../types'
import type {
Node,
NodeOutPutVar,
@ -33,6 +33,7 @@ type DeliveryMethodItemProps = {
nodesOutputVars?: NodeOutPutVar[]
availableNodes?: Node[]
formContent?: string
formInputs?: FormInputItem[]
onChange: (method: DeliveryMethod) => void
onDelete: (type: DeliveryMethodType) => void
readonly?: boolean
@ -44,6 +45,7 @@ const DeliveryMethodItem: FC<DeliveryMethodItemProps> = ({
nodesOutputVars,
availableNodes,
formContent,
formInputs,
onChange,
onDelete,
readonly,
@ -187,6 +189,7 @@ const DeliveryMethodItem: FC<DeliveryMethodItemProps> = ({
isShow={showTestEmailModal}
config={method.config as EmailConfig}
formContent={formContent}
formInputs={formInputs}
nodesOutputVars={nodesOutputVars}
availableNodes={availableNodes}
onClose={() => setShowTestEmailModal(false)}

View File

@ -1,7 +1,8 @@
import type { EmailConfig } from '../../types'
import type { EmailConfig, FormInputItem } from '../../types'
import type {
Node,
NodeOutPutVar,
ValueSelector,
} from '@/app/components/workflow/types'
import { RiArrowRightSFill, RiCloseLine } from '@remixicon/react'
import { noop, unionBy } from 'es-toolkit/compat'
@ -36,6 +37,7 @@ type EmailConfigureModalProps = {
onClose: () => void
config?: EmailConfig
formContent?: string
formInputs?: FormInputItem[]
nodesOutputVars?: NodeOutPutVar[]
availableNodes?: Node[]
}
@ -69,6 +71,7 @@ const EmailSenderModal = ({
onClose,
config,
formContent,
formInputs,
nodesOutputVars = [],
availableNodes = [],
}: EmailConfigureModalProps) => {
@ -86,8 +89,14 @@ const EmailSenderModal = ({
const accounts = members?.accounts || []
const generatedInputs = useMemo(() => {
const placeholderValueSelectors = (formInputs || []).reduce((acc, input) => {
if (input.placeholder.type === 'variable') {
acc.push(input.placeholder.selector)
}
return acc
}, [] as ValueSelector[])
const valueSelectors = doGetInputVars((formContent || '') + (config?.body || ''))
const variables = unionBy(valueSelectors, item => item.join('.')).map((item) => {
const variables = unionBy([...valueSelectors, ...placeholderValueSelectors], item => item.join('.')).map((item) => {
const varInfo = getNodeInfoById(availableNodes, item[0])?.data
return {
@ -120,7 +129,7 @@ const EmailSenderModal = ({
}
})
return varInputs
}, [availableNodes, config?.body, formContent, nodesOutputVars])
}, [availableNodes, config?.body, formContent, formInputs, nodesOutputVars])
const [inputs, setInputs] = useState<Record<string, unknown>>({})
const [collapsed, setCollapsed] = useState(true)

View File

@ -1,9 +1,9 @@
'use client'
import type { FormInputItem, UserAction } from '@/app/components/workflow/nodes/human-input/types'
import type { HumanInputFormData } from '@/types/workflow'
import { RiArrowLeftLine } from '@remixicon/react'
import * as React from 'react'
import { useState } from 'react'
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'
@ -11,11 +11,7 @@ import { getButtonStyle, initializeInputs, splitByOutputVar } from '@/app/compon
type Props = {
nodeName: string
data: {
form_content: string
inputs: FormInputItem[]
actions: UserAction[]
}
data: HumanInputFormData
showBackButton?: boolean
handleBack?: () => void
onSubmit?: (data: any) => Promise<void>
@ -67,6 +63,7 @@ const FormContent = ({
formInputFields={data.inputs}
inputs={inputs}
onInputChange={handleInputsChange}
resolvedPlaceholderValues={data.resolved_placeholder_values}
/>
))}
<div className="flex flex-wrap gap-1 py-1">

View File

@ -1,6 +1,7 @@
import type { HumanInputNodeType } from '../types'
import type { Props as FormProps } from '@/app/components/workflow/nodes/_base/components/before-run-form/form'
import type { InputVar } from '@/app/components/workflow/types'
import type { HumanInputFormData } from '@/types/workflow'
import { useCallback, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useStore as useAppStore } from '@/app/components/app/store'
@ -28,13 +29,19 @@ const useSingleRunFormParams = ({
const { t } = useTranslation()
const { inputs } = useNodeCrud<HumanInputNodeType>(id, payload)
const [showGeneratedForm, setShowGeneratedForm] = useState(false)
const [formData, setFormData] = useState<any>(null)
const [formData, setFormData] = useState<HumanInputFormData | null>(null)
const [requiredInputs, setRequiredInputs] = useState<Record<string, any>>()
const generatedInputs = useMemo(() => {
const placeholderInputs = inputs.inputs.reduce((acc, input) => {
if (input.placeholder.type === 'variable') {
acc.push(...getInputVars([`{{#${input.placeholder.selector.join('.')}#}}`]))
}
return acc
}, [] as InputVar[])
if (!inputs.form_content)
return []
return getInputVars([inputs.form_content]).filter(item => !isOutput(item.value_selector || []))
}, [getInputVars, inputs.form_content])
return placeholderInputs
return [...placeholderInputs, ...getInputVars([inputs.form_content]).filter(item => !isOutput(item.value_selector || []))]
}, [getInputVars, inputs.form_content, inputs.inputs])
const forms = useMemo(() => {
const forms: FormProps[] = [{

View File

@ -89,6 +89,7 @@ const Panel: FC<NodePanelProps<HumanInputNodeType>> = ({
nodeId={id}
value={inputs.delivery_methods || []}
formContent={inputs.form_content}
formInputs={inputs.inputs}
nodesOutputVars={availableVars}
availableNodes={availableNodesWithParent}
onChange={handleDeliveryMethodChange}

View File

@ -3057,7 +3057,7 @@
},
"app/components/workflow/nodes/_base/components/before-run-form/index.tsx": {
"ts/no-explicit-any": {
"count": 12
"count": 11
}
},
"app/components/workflow/nodes/_base/components/editor/code-editor/editor-support-vars.tsx": {
@ -3440,7 +3440,7 @@
},
"app/components/workflow/nodes/human-input/hooks/use-single-run-form-params.ts": {
"ts/no-explicit-any": {
"count": 7
"count": 6
}
},
"app/components/workflow/nodes/if-else/components/condition-list/condition-input.tsx": {

View File

@ -1,10 +1,10 @@
import type { FormInputItem, UserAction } from '@/app/components/workflow/nodes/human-input/types'
import type { BlockEnum } from '@/app/components/workflow/types'
import type { CommonResponse } from '@/models/common'
import type { FlowType } from '@/types/common'
import type {
ConversationVariableResponse,
FetchWorkflowDraftResponse,
HumanInputFormData,
NodesDefaultConfigsResponse,
VarInInspect,
} from '@/types/workflow'
@ -111,11 +111,7 @@ export const fetchHumanInputNodeStepRunForm = (
inputs: Record<string, any>
},
) => {
return post<{
form_content: string
inputs: FormInputItem[]
user_actions: UserAction[]
}>(`${url}/preview`, { body: data })
return post<HumanInputFormData>(`${url}/preview`, { body: data })
}
export const submitHumanInputNodeStepRunForm = (