form inputs in email sender

This commit is contained in:
JzoNg 2025-09-04 15:42:17 +08:00
parent 463ea14d44
commit ed16265eee
4 changed files with 116 additions and 18 deletions

View File

@ -57,6 +57,7 @@ const singleRunFormParamsHooks: Record<BlockEnum, any> = {
[BlockEnum.IterationStart]: undefined,
[BlockEnum.LoopStart]: undefined,
[BlockEnum.LoopEnd]: undefined,
[BlockEnum.HumanInput]: undefined,
}
const useSingleRunFormParamsHooks = (nodeType: BlockEnum) => {
@ -89,6 +90,7 @@ const getDataForCheckMoreHooks: Record<BlockEnum, any> = {
[BlockEnum.Assigner]: undefined,
[BlockEnum.LoopStart]: undefined,
[BlockEnum.LoopEnd]: undefined,
[BlockEnum.HumanInput]: undefined,
}
const useGetDataForCheckMoreHooks = <T>(nodeType: BlockEnum) => {
@ -197,7 +199,6 @@ const useLastRun = <T>({
setTabType(TabType.lastRun)
setInitShowLastRunTab(false)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [initShowLastRunTab])
const invalidLastRun = useInvalidLastRun(appId!, id)

View File

@ -29,6 +29,7 @@ import ParameterExtractorDefault from '@/app/components/workflow/nodes/parameter
import IterationDefault from '@/app/components/workflow/nodes/iteration/default'
import DocumentExtractorDefault from '@/app/components/workflow/nodes/document-extractor/default'
import LoopDefault from '@/app/components/workflow/nodes/loop/default'
import HumanInputDefault from '@/app/components/workflow/nodes/human-input/default'
import { ssePost } from '@/service/base'
import { noop } from 'lodash-es'
import { getInputVars as doGetInputVars } from '@/app/components/base/prompt-editor/constants'
@ -47,6 +48,7 @@ const { checkValid: checkParameterExtractorValid } = ParameterExtractorDefault
const { checkValid: checkIterationValid } = IterationDefault
const { checkValid: checkDocumentExtractorValid } = DocumentExtractorDefault
const { checkValid: checkLoopValid } = LoopDefault
const { checkValid: checkHumanInputValid } = HumanInputDefault
import {
useStoreApi,
} from 'reactflow'
@ -68,6 +70,7 @@ const checkValidFns: Record<BlockEnum, Function> = {
[BlockEnum.Iteration]: checkIterationValid,
[BlockEnum.DocExtractor]: checkDocumentExtractorValid,
[BlockEnum.Loop]: checkLoopValid,
[BlockEnum.HumanInput]: checkHumanInputValid,
} as any
export type Params<T> = {
@ -251,7 +254,6 @@ const useOneStepRun = <T>({
const { isValid } = checkValidWrap()
setCanShowSingleRun(isValid)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [data._isSingleRun])
useEffect(() => {

View File

@ -1,4 +1,4 @@
import { memo, useCallback, useState } from 'react'
import { memo, useCallback, useMemo, useState } from 'react'
import useSWR from 'swr'
import { Trans, useTranslation } from 'react-i18next'
import { useAppContext } from '@/context/app-context'
@ -7,13 +7,22 @@ import Modal from '@/app/components/base/modal'
import Button from '@/app/components/base/button'
import Divider from '@/app/components/base/divider'
import EmailInput from './recipient/email-input'
import FormItem from '@/app/components/workflow/nodes/_base/components/before-run-form/form-item'
import { getInputVars as doGetInputVars } from '@/app/components/base/prompt-editor/constants'
import {
getNodeInfoById,
isConversationVar,
isENV,
isSystemVar,
} from '@/app/components/workflow/nodes/_base/components/variable/utils'
import type { EmailConfig } from '../../types'
import type {
Node,
NodeOutPutVar,
} from '@/app/components/workflow/types'
import { InputVarType, VarType } from '@/app/components/workflow/types'
import { fetchMembers } from '@/service/common'
import { noop } from 'lodash-es'
import { noop, unionBy } from 'lodash-es'
import cn from '@/utils/classnames'
const i18nPrefix = 'workflow.nodes.humanInput'
@ -27,6 +36,32 @@ type EmailConfigureModalProps = {
availableNodes?: Node[]
}
const isOutput = (valueSelector: string[]) => {
return valueSelector[0] === '$output'
}
const getOriginVar = (valueSelector: string[], list: NodeOutPutVar[]) => {
const targetVar = list.find(item => item.nodeId === valueSelector[0])
if (!targetVar)
return undefined
let curr: any = targetVar.vars
for (let i = 1; i < valueSelector.length; i++) {
const key = valueSelector[i]
const isLast = i === valueSelector.length - 1
if (Array.isArray(curr))
curr = curr.find((v: any) => v.variable.replace('conversation.', '') === key)
if (isLast)
return curr
else if (curr?.type === VarType.object || curr?.type === VarType.file)
curr = curr.children
}
return undefined
}
const EmailSenderModal = ({
isShow,
onClose,
@ -52,9 +87,54 @@ const EmailSenderModal = ({
)
const accounts = members?.accounts || []
const generatedInputs = useMemo(() => {
const valueSelectors = doGetInputVars(config?.body || '')
const variables = unionBy(valueSelectors, item => item.join('.')).map((item) => {
const varInfo = getNodeInfoById(availableNodes, item[0])?.data
return {
label: {
nodeType: varInfo?.type,
nodeName: varInfo?.title || availableNodes[0]?.data.title, // default start node title
variable: isSystemVar(item) ? item.join('.') : item[item.length - 1],
isChatVar: isConversationVar(item),
},
variable: `#${item.join('.')}#`,
value_selector: item,
}
})
const varInputs = variables.filter(item => !isENV(item.value_selector) && !isOutput(item.value_selector)).map((item) => {
const originalVar = getOriginVar(item.value_selector, nodesOutputVars)
if (!originalVar) {
return {
label: item.label || item.variable,
variable: item.variable,
type: InputVarType.textInput,
required: false,
value_selector: item.value_selector,
}
}
return {
label: item.label || item.variable,
variable: item.variable,
type: originalVar.type === VarType.number ? InputVarType.number : InputVarType.textInput,
required: false,
}
})
return varInputs
}, [availableNodes, config?.body, nodesOutputVars])
const [inputs, setInputs] = useState<Record<string, any>>({})
const [collapsed, setCollapsed] = useState(true)
const [done, setDone] = useState(false)
const handleValueChange = (variable: string, v: string) => {
setInputs({
...inputs,
[variable]: v,
})
}
const handleConfirm = useCallback(() => {
// TODO send api
setDone(true)
@ -193,22 +273,36 @@ const EmailSenderModal = ({
</>
)}
{/* vars */}
<div className='px-6'>
<Divider className='!mb-2 !mt-4 !h-px !w-12 bg-divider-regular'/>
</div>
<div className='px-6 py-2'>
<div className='group flex h-6 cursor-pointer items-center' onClick={() => setCollapsed(!collapsed)}>
<div className='system-sm-semibold-uppercase mr-1 text-text-secondary'>{t(`${i18nPrefix}.deliveryMethod.emailSender.vars`)}</div>
<div className='system-xs-regular text-text-tertiary'>{t(`${i18nPrefix}.deliveryMethod.emailSender.optional`)}</div>
<RiArrowRightSFill className={cn('h-4 w-4 text-text-quaternary group-hover:text-text-primary', !collapsed && 'rotate-90')} />
<>
<div className='px-6'>
<Divider className='!mb-2 !mt-4 !h-px !w-12 bg-divider-regular'/>
</div>
<div className='system-xs-regular text-text-tertiary'>{t(`${i18nPrefix}.deliveryMethod.emailSender.varsTip`)}</div>
{!collapsed && (
<div className='mt-3'>
{/* form TODO */}
<div className='px-6 py-2'>
<div className='group flex h-6 cursor-pointer items-center' onClick={() => setCollapsed(!collapsed)}>
<div className='system-sm-semibold-uppercase mr-1 text-text-secondary'>{t(`${i18nPrefix}.deliveryMethod.emailSender.vars`)}</div>
<div className='system-xs-regular text-text-tertiary'>{t(`${i18nPrefix}.deliveryMethod.emailSender.optional`)}</div>
<RiArrowRightSFill className={cn('h-4 w-4 text-text-quaternary group-hover:text-text-primary', !collapsed && 'rotate-90')} />
</div>
)}
</div>
<div className='system-xs-regular text-text-tertiary'>{t(`${i18nPrefix}.deliveryMethod.emailSender.varsTip`)}</div>
{!collapsed && (
<div className='mt-3 space-y-4'>
{generatedInputs.map((variable, index) => (
<div
key={variable.variable}
className='mb-4 last-of-type:mb-0'
>
<FormItem
autoFocus={index === 0}
payload={variable}
value={inputs[variable.variable]}
onChange={v => handleValueChange(variable.variable, v)}
/>
</div>
))}
</div>
)}
</div>
</>
<div className='flex flex-row-reverse gap-2 p-6 pt-5'>
<Button
variant='primary'

View File

@ -39,6 +39,7 @@ export const canRunBySingle = (nodeType: BlockEnum, isChildNode: boolean) => {
|| nodeType === BlockEnum.IfElse
|| nodeType === BlockEnum.VariableAggregator
|| nodeType === BlockEnum.Assigner
|| nodeType === BlockEnum.HumanInput
}
type ConnectedSourceOrTargetNodesChange = {