mirror of
http://112.124.100.131/huang.ze/ebiz-dify-ai.git
synced 2025-12-09 19:06:51 +08:00
feat: Iteration node support parallel mode (#9493)
This commit is contained in:
@@ -12,15 +12,15 @@ import Tooltip from '@/app/components/base/tooltip'
|
||||
type Props = {
|
||||
className?: string
|
||||
title: JSX.Element | string | DefaultTFuncReturn
|
||||
tooltip?: React.ReactNode
|
||||
isSubTitle?: boolean
|
||||
tooltip?: string
|
||||
supportFold?: boolean
|
||||
children?: JSX.Element | string | null
|
||||
operations?: JSX.Element
|
||||
inline?: boolean
|
||||
}
|
||||
|
||||
const Filed: FC<Props> = ({
|
||||
const Field: FC<Props> = ({
|
||||
className,
|
||||
title,
|
||||
isSubTitle,
|
||||
@@ -60,4 +60,4 @@ const Filed: FC<Props> = ({
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default React.memo(Filed)
|
||||
export default React.memo(Field)
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
useToolIcon,
|
||||
} from '../../hooks'
|
||||
import { useNodeIterationInteractions } from '../iteration/use-interactions'
|
||||
import type { IterationNodeType } from '../iteration/types'
|
||||
import {
|
||||
NodeSourceHandle,
|
||||
NodeTargetHandle,
|
||||
@@ -34,6 +35,7 @@ import NodeControl from './components/node-control'
|
||||
import AddVariablePopupWithPosition from './components/add-variable-popup-with-position'
|
||||
import cn from '@/utils/classnames'
|
||||
import BlockIcon from '@/app/components/workflow/block-icon'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
|
||||
type BaseNodeProps = {
|
||||
children: ReactElement
|
||||
@@ -166,9 +168,27 @@ const BaseNode: FC<BaseNodeProps> = ({
|
||||
/>
|
||||
<div
|
||||
title={data.title}
|
||||
className='grow mr-1 system-sm-semibold-uppercase text-text-primary truncate'
|
||||
className='grow mr-1 system-sm-semibold-uppercase text-text-primary truncate flex items-center'
|
||||
>
|
||||
{data.title}
|
||||
<div>
|
||||
{data.title}
|
||||
</div>
|
||||
{
|
||||
data.type === BlockEnum.Iteration && (data as IterationNodeType).is_parallel && (
|
||||
<Tooltip popupContent={
|
||||
<div className='w-[180px]'>
|
||||
<div className='font-extrabold'>
|
||||
{t('workflow.nodes.iteration.parallelModeEnableTitle')}
|
||||
</div>
|
||||
{t('workflow.nodes.iteration.parallelModeEnableDesc')}
|
||||
</div>}
|
||||
>
|
||||
<div className='flex justify-center items-center px-[5px] py-[3px] ml-1 border-[1px] border-text-warning rounded-[5px] text-text-warning system-2xs-medium-uppercase '>
|
||||
{t('workflow.nodes.iteration.parallelModeUpper')}
|
||||
</div>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
{
|
||||
data._iterationLength && data._iterationIndex && data._runningStatus === NodeRunningStatus.Running && (
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { BlockEnum } from '../../types'
|
||||
import { BlockEnum, ErrorHandleMode } from '../../types'
|
||||
import type { NodeDefault } from '../../types'
|
||||
import type { IterationNodeType } from './types'
|
||||
import { ALL_CHAT_AVAILABLE_BLOCKS, ALL_COMPLETION_AVAILABLE_BLOCKS } from '@/app/components/workflow/constants'
|
||||
import {
|
||||
ALL_CHAT_AVAILABLE_BLOCKS,
|
||||
ALL_COMPLETION_AVAILABLE_BLOCKS,
|
||||
} from '@/app/components/workflow/constants'
|
||||
const i18nPrefix = 'workflow'
|
||||
|
||||
const nodeDefault: NodeDefault<IterationNodeType> = {
|
||||
@@ -10,25 +13,45 @@ const nodeDefault: NodeDefault<IterationNodeType> = {
|
||||
iterator_selector: [],
|
||||
output_selector: [],
|
||||
_children: [],
|
||||
_isShowTips: false,
|
||||
is_parallel: false,
|
||||
parallel_nums: 10,
|
||||
error_handle_mode: ErrorHandleMode.Terminated,
|
||||
},
|
||||
getAvailablePrevNodes(isChatMode: boolean) {
|
||||
const nodes = isChatMode
|
||||
? ALL_CHAT_AVAILABLE_BLOCKS
|
||||
: ALL_COMPLETION_AVAILABLE_BLOCKS.filter(type => type !== BlockEnum.End)
|
||||
: ALL_COMPLETION_AVAILABLE_BLOCKS.filter(
|
||||
type => type !== BlockEnum.End,
|
||||
)
|
||||
return nodes
|
||||
},
|
||||
getAvailableNextNodes(isChatMode: boolean) {
|
||||
const nodes = isChatMode ? ALL_CHAT_AVAILABLE_BLOCKS : ALL_COMPLETION_AVAILABLE_BLOCKS
|
||||
const nodes = isChatMode
|
||||
? ALL_CHAT_AVAILABLE_BLOCKS
|
||||
: ALL_COMPLETION_AVAILABLE_BLOCKS
|
||||
return nodes
|
||||
},
|
||||
checkValid(payload: IterationNodeType, t: any) {
|
||||
let errorMessages = ''
|
||||
|
||||
if (!errorMessages && (!payload.iterator_selector || payload.iterator_selector.length === 0))
|
||||
errorMessages = t(`${i18nPrefix}.errorMsg.fieldRequired`, { field: t(`${i18nPrefix}.nodes.iteration.input`) })
|
||||
if (
|
||||
!errorMessages
|
||||
&& (!payload.iterator_selector || payload.iterator_selector.length === 0)
|
||||
) {
|
||||
errorMessages = t(`${i18nPrefix}.errorMsg.fieldRequired`, {
|
||||
field: t(`${i18nPrefix}.nodes.iteration.input`),
|
||||
})
|
||||
}
|
||||
|
||||
if (!errorMessages && (!payload.output_selector || payload.output_selector.length === 0))
|
||||
errorMessages = t(`${i18nPrefix}.errorMsg.fieldRequired`, { field: t(`${i18nPrefix}.nodes.iteration.output`) })
|
||||
if (
|
||||
!errorMessages
|
||||
&& (!payload.output_selector || payload.output_selector.length === 0)
|
||||
) {
|
||||
errorMessages = t(`${i18nPrefix}.errorMsg.fieldRequired`, {
|
||||
field: t(`${i18nPrefix}.nodes.iteration.output`),
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
isValid: !errorMessages,
|
||||
|
||||
@@ -8,12 +8,16 @@ import {
|
||||
useNodesInitialized,
|
||||
useViewport,
|
||||
} from 'reactflow'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { IterationStartNodeDumb } from '../iteration-start'
|
||||
import { useNodeIterationInteractions } from './use-interactions'
|
||||
import type { IterationNodeType } from './types'
|
||||
import AddBlock from './add-block'
|
||||
import cn from '@/utils/classnames'
|
||||
import type { NodeProps } from '@/app/components/workflow/types'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
|
||||
const i18nPrefix = 'workflow.nodes.iteration'
|
||||
|
||||
const Node: FC<NodeProps<IterationNodeType>> = ({
|
||||
id,
|
||||
@@ -22,11 +26,20 @@ const Node: FC<NodeProps<IterationNodeType>> = ({
|
||||
const { zoom } = useViewport()
|
||||
const nodesInitialized = useNodesInitialized()
|
||||
const { handleNodeIterationRerender } = useNodeIterationInteractions()
|
||||
const { t } = useTranslation()
|
||||
|
||||
useEffect(() => {
|
||||
if (nodesInitialized)
|
||||
handleNodeIterationRerender(id)
|
||||
}, [nodesInitialized, id, handleNodeIterationRerender])
|
||||
if (data.is_parallel && data._isShowTips) {
|
||||
Toast.notify({
|
||||
type: 'warning',
|
||||
message: t(`${i18nPrefix}.answerNodeWarningDesc`),
|
||||
duration: 5000,
|
||||
})
|
||||
data._isShowTips = false
|
||||
}
|
||||
}, [nodesInitialized, id, handleNodeIterationRerender, data, t])
|
||||
|
||||
return (
|
||||
<div className={cn(
|
||||
|
||||
@@ -8,11 +8,17 @@ import VarReferencePicker from '../_base/components/variable/var-reference-picke
|
||||
import Split from '../_base/components/split'
|
||||
import ResultPanel from '../../run/result-panel'
|
||||
import IterationResultPanel from '../../run/iteration-result-panel'
|
||||
import { MAX_ITERATION_PARALLEL_NUM, MIN_ITERATION_PARALLEL_NUM } from '../../constants'
|
||||
import type { IterationNodeType } from './types'
|
||||
import useConfig from './use-config'
|
||||
import { InputVarType, type NodePanelProps } from '@/app/components/workflow/types'
|
||||
import { ErrorHandleMode, InputVarType, type NodePanelProps } from '@/app/components/workflow/types'
|
||||
import Field from '@/app/components/workflow/nodes/_base/components/field'
|
||||
import BeforeRunForm from '@/app/components/workflow/nodes/_base/components/before-run-form'
|
||||
import Switch from '@/app/components/base/switch'
|
||||
import Select from '@/app/components/base/select'
|
||||
import Slider from '@/app/components/base/slider'
|
||||
import Input from '@/app/components/base/input'
|
||||
import Divider from '@/app/components/base/divider'
|
||||
|
||||
const i18nPrefix = 'workflow.nodes.iteration'
|
||||
|
||||
@@ -21,7 +27,20 @@ const Panel: FC<NodePanelProps<IterationNodeType>> = ({
|
||||
data,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const responseMethod = [
|
||||
{
|
||||
value: ErrorHandleMode.Terminated,
|
||||
name: t(`${i18nPrefix}.ErrorMethod.operationTerminated`),
|
||||
},
|
||||
{
|
||||
value: ErrorHandleMode.ContinueOnError,
|
||||
name: t(`${i18nPrefix}.ErrorMethod.continueOnError`),
|
||||
},
|
||||
{
|
||||
value: ErrorHandleMode.RemoveAbnormalOutput,
|
||||
name: t(`${i18nPrefix}.ErrorMethod.removeAbnormalOutput`),
|
||||
},
|
||||
]
|
||||
const {
|
||||
readOnly,
|
||||
inputs,
|
||||
@@ -47,6 +66,9 @@ const Panel: FC<NodePanelProps<IterationNodeType>> = ({
|
||||
setIterator,
|
||||
iteratorInputKey,
|
||||
iterationRunResult,
|
||||
changeParallel,
|
||||
changeErrorResponseMode,
|
||||
changeParallelNums,
|
||||
} = useConfig(id, data)
|
||||
|
||||
return (
|
||||
@@ -87,6 +109,39 @@ const Panel: FC<NodePanelProps<IterationNodeType>> = ({
|
||||
/>
|
||||
</Field>
|
||||
</div>
|
||||
<div className='px-4 pb-2'>
|
||||
<Field title={t(`${i18nPrefix}.parallelMode`)} tooltip={<div className='w-[230px]'>{t(`${i18nPrefix}.parallelPanelDesc`)}</div>} inline>
|
||||
<Switch defaultValue={inputs.is_parallel} onChange={changeParallel} />
|
||||
</Field>
|
||||
</div>
|
||||
{
|
||||
inputs.is_parallel && (<div className='px-4 pb-2'>
|
||||
<Field title={t(`${i18nPrefix}.MaxParallelismTitle`)} isSubTitle tooltip={<div className='w-[230px]'>{t(`${i18nPrefix}.MaxParallelismDesc`)}</div>}>
|
||||
<div className='flex row'>
|
||||
<Input type='number' wrapperClassName='w-18 mr-4 ' max={MAX_ITERATION_PARALLEL_NUM} min={MIN_ITERATION_PARALLEL_NUM} value={inputs.parallel_nums} onChange={(e) => { changeParallelNums(Number(e.target.value)) }} />
|
||||
<Slider
|
||||
value={inputs.parallel_nums}
|
||||
onChange={changeParallelNums}
|
||||
max={MAX_ITERATION_PARALLEL_NUM}
|
||||
min={MIN_ITERATION_PARALLEL_NUM}
|
||||
className=' flex-shrink-0 flex-1 mt-4'
|
||||
/>
|
||||
</div>
|
||||
|
||||
</Field>
|
||||
</div>)
|
||||
}
|
||||
<div className='px-4 py-2'>
|
||||
<Divider className='h-[1px]'/>
|
||||
</div>
|
||||
|
||||
<div className='px-4 py-2'>
|
||||
<Field title={t(`${i18nPrefix}.errorResponseMethod`)} >
|
||||
<Select items={responseMethod} defaultValue={inputs.error_handle_mode} onSelect={changeErrorResponseMode} allowSearch={false}>
|
||||
</Select>
|
||||
</Field>
|
||||
</div>
|
||||
|
||||
{isShowSingleRun && (
|
||||
<BeforeRunForm
|
||||
nodeName={inputs.title}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type {
|
||||
BlockEnum,
|
||||
CommonNodeType,
|
||||
ErrorHandleMode,
|
||||
ValueSelector,
|
||||
VarType,
|
||||
} from '@/app/components/workflow/types'
|
||||
@@ -12,4 +13,8 @@ export type IterationNodeType = CommonNodeType & {
|
||||
iterator_selector: ValueSelector
|
||||
output_selector: ValueSelector
|
||||
output_type: VarType // output type.
|
||||
is_parallel: boolean // open the parallel mode or not
|
||||
parallel_nums: number // the numbers of parallel
|
||||
error_handle_mode: ErrorHandleMode // how to handle error in the iteration
|
||||
_isShowTips: boolean // when answer node in parallel mode iteration show tips
|
||||
}
|
||||
|
||||
@@ -8,12 +8,13 @@ import {
|
||||
useWorkflow,
|
||||
} from '../../hooks'
|
||||
import { VarType } from '../../types'
|
||||
import type { ValueSelector, Var } from '../../types'
|
||||
import type { ErrorHandleMode, ValueSelector, Var } from '../../types'
|
||||
import useNodeCrud from '../_base/hooks/use-node-crud'
|
||||
import { getNodeInfoById, getNodeUsedVarPassToServerKey, getNodeUsedVars, isSystemVar, toNodeOutputVars } from '../_base/components/variable/utils'
|
||||
import useOneStepRun from '../_base/hooks/use-one-step-run'
|
||||
import type { IterationNodeType } from './types'
|
||||
import type { VarType as VarKindType } from '@/app/components/workflow/nodes/tool/types'
|
||||
import type { Item } from '@/app/components/base/select'
|
||||
|
||||
const DELIMITER = '@@@@@'
|
||||
const useConfig = (id: string, payload: IterationNodeType) => {
|
||||
@@ -184,6 +185,25 @@ const useConfig = (id: string, payload: IterationNodeType) => {
|
||||
})
|
||||
}, [iteratorInputKey, runInputData, setRunInputData])
|
||||
|
||||
const changeParallel = useCallback((value: boolean) => {
|
||||
const newInputs = produce(inputs, (draft) => {
|
||||
draft.is_parallel = value
|
||||
})
|
||||
setInputs(newInputs)
|
||||
}, [inputs, setInputs])
|
||||
|
||||
const changeErrorResponseMode = useCallback((item: Item) => {
|
||||
const newInputs = produce(inputs, (draft) => {
|
||||
draft.error_handle_mode = item.value as ErrorHandleMode
|
||||
})
|
||||
setInputs(newInputs)
|
||||
}, [inputs, setInputs])
|
||||
const changeParallelNums = useCallback((num: number) => {
|
||||
const newInputs = produce(inputs, (draft) => {
|
||||
draft.parallel_nums = num
|
||||
})
|
||||
setInputs(newInputs)
|
||||
}, [inputs, setInputs])
|
||||
return {
|
||||
readOnly,
|
||||
inputs,
|
||||
@@ -210,6 +230,9 @@ const useConfig = (id: string, payload: IterationNodeType) => {
|
||||
setIterator,
|
||||
iteratorInputKey,
|
||||
iterationRunResult,
|
||||
changeParallel,
|
||||
changeErrorResponseMode,
|
||||
changeParallelNums,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user