mirror of
https://github.com/langgenius/dify.git
synced 2026-04-29 04:26:30 +08:00
form inputs in email sender
This commit is contained in:
parent
463ea14d44
commit
ed16265eee
@ -57,6 +57,7 @@ const singleRunFormParamsHooks: Record<BlockEnum, any> = {
|
|||||||
[BlockEnum.IterationStart]: undefined,
|
[BlockEnum.IterationStart]: undefined,
|
||||||
[BlockEnum.LoopStart]: undefined,
|
[BlockEnum.LoopStart]: undefined,
|
||||||
[BlockEnum.LoopEnd]: undefined,
|
[BlockEnum.LoopEnd]: undefined,
|
||||||
|
[BlockEnum.HumanInput]: undefined,
|
||||||
}
|
}
|
||||||
|
|
||||||
const useSingleRunFormParamsHooks = (nodeType: BlockEnum) => {
|
const useSingleRunFormParamsHooks = (nodeType: BlockEnum) => {
|
||||||
@ -89,6 +90,7 @@ const getDataForCheckMoreHooks: Record<BlockEnum, any> = {
|
|||||||
[BlockEnum.Assigner]: undefined,
|
[BlockEnum.Assigner]: undefined,
|
||||||
[BlockEnum.LoopStart]: undefined,
|
[BlockEnum.LoopStart]: undefined,
|
||||||
[BlockEnum.LoopEnd]: undefined,
|
[BlockEnum.LoopEnd]: undefined,
|
||||||
|
[BlockEnum.HumanInput]: undefined,
|
||||||
}
|
}
|
||||||
|
|
||||||
const useGetDataForCheckMoreHooks = <T>(nodeType: BlockEnum) => {
|
const useGetDataForCheckMoreHooks = <T>(nodeType: BlockEnum) => {
|
||||||
@ -197,7 +199,6 @@ const useLastRun = <T>({
|
|||||||
setTabType(TabType.lastRun)
|
setTabType(TabType.lastRun)
|
||||||
|
|
||||||
setInitShowLastRunTab(false)
|
setInitShowLastRunTab(false)
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [initShowLastRunTab])
|
}, [initShowLastRunTab])
|
||||||
const invalidLastRun = useInvalidLastRun(appId!, id)
|
const invalidLastRun = useInvalidLastRun(appId!, id)
|
||||||
|
|
||||||
|
|||||||
@ -29,6 +29,7 @@ import ParameterExtractorDefault from '@/app/components/workflow/nodes/parameter
|
|||||||
import IterationDefault from '@/app/components/workflow/nodes/iteration/default'
|
import IterationDefault from '@/app/components/workflow/nodes/iteration/default'
|
||||||
import DocumentExtractorDefault from '@/app/components/workflow/nodes/document-extractor/default'
|
import DocumentExtractorDefault from '@/app/components/workflow/nodes/document-extractor/default'
|
||||||
import LoopDefault from '@/app/components/workflow/nodes/loop/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 { ssePost } from '@/service/base'
|
||||||
import { noop } from 'lodash-es'
|
import { noop } from 'lodash-es'
|
||||||
import { getInputVars as doGetInputVars } from '@/app/components/base/prompt-editor/constants'
|
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: checkIterationValid } = IterationDefault
|
||||||
const { checkValid: checkDocumentExtractorValid } = DocumentExtractorDefault
|
const { checkValid: checkDocumentExtractorValid } = DocumentExtractorDefault
|
||||||
const { checkValid: checkLoopValid } = LoopDefault
|
const { checkValid: checkLoopValid } = LoopDefault
|
||||||
|
const { checkValid: checkHumanInputValid } = HumanInputDefault
|
||||||
import {
|
import {
|
||||||
useStoreApi,
|
useStoreApi,
|
||||||
} from 'reactflow'
|
} from 'reactflow'
|
||||||
@ -68,6 +70,7 @@ const checkValidFns: Record<BlockEnum, Function> = {
|
|||||||
[BlockEnum.Iteration]: checkIterationValid,
|
[BlockEnum.Iteration]: checkIterationValid,
|
||||||
[BlockEnum.DocExtractor]: checkDocumentExtractorValid,
|
[BlockEnum.DocExtractor]: checkDocumentExtractorValid,
|
||||||
[BlockEnum.Loop]: checkLoopValid,
|
[BlockEnum.Loop]: checkLoopValid,
|
||||||
|
[BlockEnum.HumanInput]: checkHumanInputValid,
|
||||||
} as any
|
} as any
|
||||||
|
|
||||||
export type Params<T> = {
|
export type Params<T> = {
|
||||||
@ -251,7 +254,6 @@ const useOneStepRun = <T>({
|
|||||||
const { isValid } = checkValidWrap()
|
const { isValid } = checkValidWrap()
|
||||||
setCanShowSingleRun(isValid)
|
setCanShowSingleRun(isValid)
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [data._isSingleRun])
|
}, [data._isSingleRun])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { memo, useCallback, useState } from 'react'
|
import { memo, useCallback, useMemo, useState } from 'react'
|
||||||
import useSWR from 'swr'
|
import useSWR from 'swr'
|
||||||
import { Trans, useTranslation } from 'react-i18next'
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
import { useAppContext } from '@/context/app-context'
|
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 Button from '@/app/components/base/button'
|
||||||
import Divider from '@/app/components/base/divider'
|
import Divider from '@/app/components/base/divider'
|
||||||
import EmailInput from './recipient/email-input'
|
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 { EmailConfig } from '../../types'
|
||||||
import type {
|
import type {
|
||||||
Node,
|
Node,
|
||||||
NodeOutPutVar,
|
NodeOutPutVar,
|
||||||
} from '@/app/components/workflow/types'
|
} from '@/app/components/workflow/types'
|
||||||
|
import { InputVarType, VarType } from '@/app/components/workflow/types'
|
||||||
import { fetchMembers } from '@/service/common'
|
import { fetchMembers } from '@/service/common'
|
||||||
import { noop } from 'lodash-es'
|
import { noop, unionBy } from 'lodash-es'
|
||||||
import cn from '@/utils/classnames'
|
import cn from '@/utils/classnames'
|
||||||
|
|
||||||
const i18nPrefix = 'workflow.nodes.humanInput'
|
const i18nPrefix = 'workflow.nodes.humanInput'
|
||||||
@ -27,6 +36,32 @@ type EmailConfigureModalProps = {
|
|||||||
availableNodes?: Node[]
|
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 = ({
|
const EmailSenderModal = ({
|
||||||
isShow,
|
isShow,
|
||||||
onClose,
|
onClose,
|
||||||
@ -52,9 +87,54 @@ const EmailSenderModal = ({
|
|||||||
)
|
)
|
||||||
const accounts = members?.accounts || []
|
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 [collapsed, setCollapsed] = useState(true)
|
||||||
const [done, setDone] = useState(false)
|
const [done, setDone] = useState(false)
|
||||||
|
|
||||||
|
const handleValueChange = (variable: string, v: string) => {
|
||||||
|
setInputs({
|
||||||
|
...inputs,
|
||||||
|
[variable]: v,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const handleConfirm = useCallback(() => {
|
const handleConfirm = useCallback(() => {
|
||||||
// TODO send api
|
// TODO send api
|
||||||
setDone(true)
|
setDone(true)
|
||||||
@ -193,22 +273,36 @@ const EmailSenderModal = ({
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{/* vars */}
|
{/* vars */}
|
||||||
<div className='px-6'>
|
<>
|
||||||
<Divider className='!mb-2 !mt-4 !h-px !w-12 bg-divider-regular'/>
|
<div className='px-6'>
|
||||||
</div>
|
<Divider className='!mb-2 !mt-4 !h-px !w-12 bg-divider-regular'/>
|
||||||
<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>
|
<div className='px-6 py-2'>
|
||||||
{!collapsed && (
|
<div className='group flex h-6 cursor-pointer items-center' onClick={() => setCollapsed(!collapsed)}>
|
||||||
<div className='mt-3'>
|
<div className='system-sm-semibold-uppercase mr-1 text-text-secondary'>{t(`${i18nPrefix}.deliveryMethod.emailSender.vars`)}</div>
|
||||||
{/* form TODO */}
|
<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>
|
||||||
</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'>
|
<div className='flex flex-row-reverse gap-2 p-6 pt-5'>
|
||||||
<Button
|
<Button
|
||||||
variant='primary'
|
variant='primary'
|
||||||
|
|||||||
@ -39,6 +39,7 @@ export const canRunBySingle = (nodeType: BlockEnum, isChildNode: boolean) => {
|
|||||||
|| nodeType === BlockEnum.IfElse
|
|| nodeType === BlockEnum.IfElse
|
||||||
|| nodeType === BlockEnum.VariableAggregator
|
|| nodeType === BlockEnum.VariableAggregator
|
||||||
|| nodeType === BlockEnum.Assigner
|
|| nodeType === BlockEnum.Assigner
|
||||||
|
|| nodeType === BlockEnum.HumanInput
|
||||||
}
|
}
|
||||||
|
|
||||||
type ConnectedSourceOrTargetNodesChange = {
|
type ConnectedSourceOrTargetNodesChange = {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user