mirror of
http://112.124.100.131/huang.ze/ebiz-dify-ai.git
synced 2025-12-10 19:36:53 +08:00
feat: workflow new nodes (#4683)
Co-authored-by: Joel <iamjoel007@gmail.com> Co-authored-by: Patryk Garstecki <patryk20120@yahoo.pl> Co-authored-by: Sebastian.W <thiner@gmail.com> Co-authored-by: 呆萌闷油瓶 <253605712@qq.com> Co-authored-by: takatost <takatost@users.noreply.github.com> Co-authored-by: rechardwang <wh_goodjob@163.com> Co-authored-by: Nite Knite <nkCoding@gmail.com> Co-authored-by: Chenhe Gu <guchenhe@gmail.com> Co-authored-by: Joshua <138381132+joshua20231026@users.noreply.github.com> Co-authored-by: Weaxs <459312872@qq.com> Co-authored-by: Ikko Eltociear Ashimine <eltociear@gmail.com> Co-authored-by: leejoo0 <81673835+leejoo0@users.noreply.github.com> Co-authored-by: JzoNg <jzongcode@gmail.com> Co-authored-by: sino <sino2322@gmail.com> Co-authored-by: Vikey Chen <vikeytk@gmail.com> Co-authored-by: wanghl <Wang-HL@users.noreply.github.com> Co-authored-by: Haolin Wang-汪皓临 <haolin.wang@atlaslovestravel.com> Co-authored-by: Zixuan Cheng <61724187+Theysua@users.noreply.github.com> Co-authored-by: crazywoola <100913391+crazywoola@users.noreply.github.com> Co-authored-by: Bowen Liang <bowenliang@apache.org> Co-authored-by: Bowen Liang <liangbowen@gf.com.cn> Co-authored-by: fanghongtai <42790567+fanghongtai@users.noreply.github.com> Co-authored-by: wxfanghongtai <wxfanghongtai@gf.com.cn> Co-authored-by: Matri <qjp@bithuman.io> Co-authored-by: Benjamin <benjaminx@gmail.com>
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
import {
|
||||
memo,
|
||||
useCallback,
|
||||
} from 'react'
|
||||
import cn from 'classnames'
|
||||
import { useVariableAssigner } from '../../hooks'
|
||||
import type { VariableAssignerNodeType } from '../../types'
|
||||
import {
|
||||
PortalToFollowElem,
|
||||
PortalToFollowElemContent,
|
||||
PortalToFollowElemTrigger,
|
||||
} from '@/app/components/base/portal-to-follow-elem'
|
||||
import { Plus02 } from '@/app/components/base/icons/src/vender/line/general'
|
||||
import AddVariablePopup from '@/app/components/workflow/nodes/_base/components/add-variable-popup'
|
||||
import type {
|
||||
NodeOutPutVar,
|
||||
ValueSelector,
|
||||
Var,
|
||||
} from '@/app/components/workflow/types'
|
||||
|
||||
export type AddVariableProps = {
|
||||
open: boolean
|
||||
onOpenChange: (open: boolean) => void
|
||||
variableAssignerNodeId: string
|
||||
variableAssignerNodeData: VariableAssignerNodeType
|
||||
availableVars: NodeOutPutVar[]
|
||||
handleId?: string
|
||||
}
|
||||
const AddVariable = ({
|
||||
open,
|
||||
onOpenChange,
|
||||
availableVars,
|
||||
variableAssignerNodeId,
|
||||
variableAssignerNodeData,
|
||||
handleId,
|
||||
}: AddVariableProps) => {
|
||||
const { handleAssignVariableValueChange } = useVariableAssigner()
|
||||
|
||||
const handleSelectVariable = useCallback((v: ValueSelector, varDetail: Var) => {
|
||||
handleAssignVariableValueChange(
|
||||
variableAssignerNodeId,
|
||||
v,
|
||||
varDetail,
|
||||
handleId,
|
||||
)
|
||||
onOpenChange(false)
|
||||
}, [handleAssignVariableValueChange, variableAssignerNodeId, handleId, onOpenChange])
|
||||
|
||||
return (
|
||||
<div className={cn(
|
||||
'hidden group-hover:flex absolute top-0 left-0 z-10 pointer-events-none',
|
||||
open && '!flex',
|
||||
variableAssignerNodeData.selected && '!flex',
|
||||
)}>
|
||||
<PortalToFollowElem
|
||||
placement={'left-start'}
|
||||
offset={{
|
||||
mainAxis: 4,
|
||||
crossAxis: -60,
|
||||
}}
|
||||
open={open}
|
||||
onOpenChange={onOpenChange}
|
||||
>
|
||||
<PortalToFollowElemTrigger
|
||||
onClick={() => onOpenChange(!open)}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
'flex items-center justify-center',
|
||||
'w-4 h-4 rounded-full bg-primary-600 cursor-pointer z-10',
|
||||
)}
|
||||
>
|
||||
<Plus02 className='w-2.5 h-2.5 text-white' />
|
||||
</div>
|
||||
</PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemContent className='z-[1000]'>
|
||||
<AddVariablePopup
|
||||
onSelect={handleSelectVariable}
|
||||
availableVars={availableVars}
|
||||
/>
|
||||
</PortalToFollowElemContent>
|
||||
</PortalToFollowElem>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(AddVariable)
|
||||
@@ -0,0 +1,107 @@
|
||||
import {
|
||||
memo,
|
||||
useMemo,
|
||||
} from 'react'
|
||||
import cn from 'classnames'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useNodes } from 'reactflow'
|
||||
import { useStore } from '../../../store'
|
||||
import { BlockEnum } from '../../../types'
|
||||
import type {
|
||||
Node,
|
||||
ValueSelector,
|
||||
VarType,
|
||||
} from '../../../types'
|
||||
import type { VariableAssignerNodeType } from '../types'
|
||||
import {
|
||||
useGetAvailableVars,
|
||||
useVariableAssigner,
|
||||
} from '../hooks'
|
||||
import { filterVar } from '../utils'
|
||||
import NodeHandle from './node-handle'
|
||||
import NodeVariableItem from './node-variable-item'
|
||||
import { isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils'
|
||||
|
||||
const i18nPrefix = 'workflow.nodes.variableAssigner'
|
||||
type GroupItem = {
|
||||
groupEnabled: boolean
|
||||
targetHandleId: string
|
||||
title: string
|
||||
type: string
|
||||
variables: ValueSelector[]
|
||||
variableAssignerNodeId: string
|
||||
variableAssignerNodeData: VariableAssignerNodeType
|
||||
}
|
||||
type NodeGroupItemProps = {
|
||||
item: GroupItem
|
||||
}
|
||||
const NodeGroupItem = ({
|
||||
item,
|
||||
}: NodeGroupItemProps) => {
|
||||
const { t } = useTranslation()
|
||||
const enteringNodePayload = useStore(s => s.enteringNodePayload)
|
||||
const hoveringAssignVariableGroupId = useStore(s => s.hoveringAssignVariableGroupId)
|
||||
const nodes: Node[] = useNodes()
|
||||
const {
|
||||
handleGroupItemMouseEnter,
|
||||
handleGroupItemMouseLeave,
|
||||
} = useVariableAssigner()
|
||||
const getAvailableVars = useGetAvailableVars()
|
||||
const outputType = useMemo(() => {
|
||||
if (item.targetHandleId === 'target')
|
||||
return item.variableAssignerNodeData.output_type
|
||||
|
||||
const group = item.variableAssignerNodeData.advanced_settings?.groups.find(group => group.groupId === item.targetHandleId)
|
||||
return group?.output_type || ''
|
||||
}, [item.variableAssignerNodeData, item.targetHandleId])
|
||||
const availableVars = getAvailableVars(item.variableAssignerNodeId, item.targetHandleId, filterVar(outputType as VarType))
|
||||
const showSelectionBorder = enteringNodePayload?.nodeId === item.variableAssignerNodeId && item.groupEnabled && hoveringAssignVariableGroupId === item.targetHandleId
|
||||
const connected = item.variableAssignerNodeData._connectedTargetHandleIds?.includes(item.targetHandleId)
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'relative pt-1 px-1.5 pb-1.5 rounded-lg border border-transparent',
|
||||
showSelectionBorder && '!border-primary-600',
|
||||
)}
|
||||
onMouseEnter={() => handleGroupItemMouseEnter(item.targetHandleId)}
|
||||
onMouseLeave={handleGroupItemMouseLeave}
|
||||
>
|
||||
<div className='flex items-center justify-between h-4 text-[10px] font-medium text-gray-500'>
|
||||
<NodeHandle
|
||||
connected={connected}
|
||||
variableAssignerNodeId={item.variableAssignerNodeId}
|
||||
variableAssignerNodeData={item.variableAssignerNodeData}
|
||||
handleId={item.targetHandleId}
|
||||
availableVars={availableVars}
|
||||
/>
|
||||
<span className='grow uppercase truncate' title={item.title}>{item.title}</span>
|
||||
<span className='shrink-0 ml-2'>{item.type}</span>
|
||||
</div>
|
||||
{
|
||||
!item.variables.length && (
|
||||
<div className='relative flex items-center px-1 h-[22px] justify-between bg-gray-100 rounded-md space-x-1 text-[10px] font-normal text-gray-400 uppercase'>
|
||||
{t(`${i18nPrefix}.varNotSet`)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
!!item.variables.length && item.variables.map((variable = [], index) => {
|
||||
const isSystem = isSystemVar(variable)
|
||||
const node = isSystem ? nodes.find(node => node.data.type === BlockEnum.Start) : nodes.find(node => node.id === variable[0])
|
||||
const varName = isSystem ? `sys.${variable[variable.length - 1]}` : variable.slice(1).join('.')
|
||||
|
||||
return (
|
||||
<NodeVariableItem
|
||||
key={index}
|
||||
node={node as Node}
|
||||
varName={varName}
|
||||
/>
|
||||
)
|
||||
})
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(NodeGroupItem)
|
||||
@@ -0,0 +1,69 @@
|
||||
import type { MouseEvent } from 'react'
|
||||
import {
|
||||
memo,
|
||||
useCallback,
|
||||
useState,
|
||||
} from 'react'
|
||||
import cn from 'classnames'
|
||||
import {
|
||||
Handle,
|
||||
Position,
|
||||
} from 'reactflow'
|
||||
import type { VariableAssignerNodeType } from '../types'
|
||||
import AddVariable from './add-variable'
|
||||
import type { NodeOutPutVar } from '@/app/components/workflow/types'
|
||||
import { useStore } from '@/app/components/workflow/store'
|
||||
|
||||
type NodeHandleProps = {
|
||||
handleId?: string
|
||||
connected?: boolean
|
||||
variableAssignerNodeId: string
|
||||
availableVars: NodeOutPutVar[]
|
||||
variableAssignerNodeData: VariableAssignerNodeType
|
||||
}
|
||||
const NodeHandle = ({
|
||||
connected,
|
||||
variableAssignerNodeId,
|
||||
handleId = 'target',
|
||||
availableVars,
|
||||
variableAssignerNodeData,
|
||||
}: NodeHandleProps) => {
|
||||
const [open, setOpen] = useState(false)
|
||||
const connectingNodePayload = useStore(s => s.connectingNodePayload)
|
||||
const isUnConnectable = connectingNodePayload?.handleType === 'source'
|
||||
|
||||
const handleOpenChange = useCallback((v: boolean) => {
|
||||
setOpen(v)
|
||||
}, [])
|
||||
|
||||
const handleHandleClick = useCallback((e: MouseEvent) => {
|
||||
e.stopPropagation()
|
||||
setOpen(v => !v)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<Handle
|
||||
id={handleId}
|
||||
type='target'
|
||||
onClick={handleHandleClick}
|
||||
position={Position.Left}
|
||||
isConnectable={!isUnConnectable}
|
||||
className={cn(
|
||||
'!-left-[13px] !top-1 !w-4 !h-4 !bg-transparent !rounded-none !outline-none !border-none z-[1] !transform-none',
|
||||
'after:absolute after:w-0.5 after:h-2 after:left-[5px] after:top-1 after:bg-primary-500 pointer-events-none',
|
||||
!connected && 'after:opacity-0',
|
||||
)}
|
||||
>
|
||||
<AddVariable
|
||||
open={open}
|
||||
onOpenChange={handleOpenChange}
|
||||
variableAssignerNodeId={variableAssignerNodeId}
|
||||
variableAssignerNodeData={variableAssignerNodeData}
|
||||
handleId={handleId}
|
||||
availableVars={availableVars}
|
||||
/>
|
||||
</Handle>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(NodeHandle)
|
||||
@@ -0,0 +1,36 @@
|
||||
import { memo } from 'react'
|
||||
import { VarBlockIcon } from '@/app/components/workflow/block-icon'
|
||||
import { Line3 } from '@/app/components/base/icons/src/public/common'
|
||||
import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development'
|
||||
import type { Node } from '@/app/components/workflow/types'
|
||||
import { BlockEnum } from '@/app/components/workflow/types'
|
||||
|
||||
type NodeVariableItemProps = {
|
||||
node: Node
|
||||
varName: string
|
||||
}
|
||||
const NodeVariableItem = ({
|
||||
node,
|
||||
varName,
|
||||
}: NodeVariableItemProps) => {
|
||||
return (
|
||||
<div className='relative flex items-center mt-0.5 h-6 bg-gray-100 rounded-md px-1 text-xs font-normal text-gray-700' >
|
||||
<div className='flex items-center'>
|
||||
<div className='p-[1px]'>
|
||||
<VarBlockIcon
|
||||
className='!text-gray-900'
|
||||
type={node?.data.type || BlockEnum.Start}
|
||||
/>
|
||||
</div>
|
||||
<div className='max-w-[85px] truncate mx-0.5 text-xs font-medium text-gray-700' title={node?.data.title}>{node?.data.title}</div>
|
||||
<Line3 className='mr-0.5'></Line3>
|
||||
</div>
|
||||
<div className='flex items-center text-primary-600'>
|
||||
<Variable02 className='w-3.5 h-3.5' />
|
||||
<div className='max-w-[75px] truncate ml-0.5 text-xs font-medium' title={varName}>{varName}</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(NodeVariableItem)
|
||||
@@ -0,0 +1,176 @@
|
||||
'use client'
|
||||
import React, { useCallback } from 'react'
|
||||
import type { ChangeEvent, FC } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import produce from 'immer'
|
||||
import { useBoolean } from 'ahooks'
|
||||
import type { VarGroupItem as VarGroupItemType } from '../types'
|
||||
import VarReferencePicker from '../../_base/components/variable/var-reference-picker'
|
||||
import VarList from '../components/var-list'
|
||||
import Field from '@/app/components/workflow/nodes/_base/components/field'
|
||||
import { VarType } from '@/app/components/workflow/types'
|
||||
import type { NodeOutPutVar, ValueSelector, Var } from '@/app/components/workflow/types'
|
||||
import { VarType as VarKindType } from '@/app/components/workflow/nodes/tool/types'
|
||||
import { Trash03 } from '@/app/components/base/icons/src/vender/line/general'
|
||||
import { Folder } from '@/app/components/base/icons/src/vender/line/files'
|
||||
import { checkKeys } from '@/utils/var'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
|
||||
const i18nPrefix = 'workflow.nodes.variableAssigner'
|
||||
|
||||
type Payload = VarGroupItemType & {
|
||||
group_name?: string
|
||||
}
|
||||
|
||||
type Props = {
|
||||
readOnly: boolean
|
||||
nodeId: string
|
||||
payload: Payload
|
||||
onChange: (newPayload: Payload) => void
|
||||
groupEnabled: boolean
|
||||
onGroupNameChange?: (value: string) => void
|
||||
canRemove?: boolean
|
||||
onRemove?: () => void
|
||||
availableVars: NodeOutPutVar[]
|
||||
}
|
||||
|
||||
const VarGroupItem: FC<Props> = ({
|
||||
readOnly,
|
||||
nodeId,
|
||||
payload,
|
||||
onChange,
|
||||
groupEnabled,
|
||||
onGroupNameChange,
|
||||
canRemove,
|
||||
onRemove,
|
||||
availableVars,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const handleAddVariable = useCallback((value: ValueSelector | string, _varKindType: VarKindType, varInfo?: Var) => {
|
||||
const chosenVariables = payload.variables
|
||||
if (chosenVariables.some(item => item.join('.') === (value as ValueSelector).join('.')))
|
||||
return
|
||||
|
||||
const newPayload = produce(payload, (draft: Payload) => {
|
||||
draft.variables.push(value as ValueSelector)
|
||||
if (varInfo && varInfo.type !== VarType.any)
|
||||
draft.output_type = varInfo.type
|
||||
})
|
||||
onChange(newPayload)
|
||||
}, [onChange, payload])
|
||||
|
||||
const handleListChange = useCallback((newList: ValueSelector[], changedItem?: ValueSelector) => {
|
||||
if (changedItem) {
|
||||
const chosenVariables = payload.variables
|
||||
if (chosenVariables.some(item => item.join('.') === (changedItem as ValueSelector).join('.')))
|
||||
return
|
||||
}
|
||||
|
||||
const newPayload = produce(payload, (draft) => {
|
||||
draft.variables = newList
|
||||
if (newList.length === 0)
|
||||
draft.output_type = VarType.any
|
||||
})
|
||||
onChange(newPayload)
|
||||
}, [onChange, payload])
|
||||
|
||||
const filterVar = useCallback((varPayload: Var) => {
|
||||
if (payload.output_type === VarType.any)
|
||||
return true
|
||||
return varPayload.type === payload.output_type
|
||||
}, [payload.output_type])
|
||||
|
||||
const [isEditGroupName, {
|
||||
setTrue: setEditGroupName,
|
||||
setFalse: setNotEditGroupName,
|
||||
}] = useBoolean(false)
|
||||
|
||||
const handleGroupNameChange = useCallback((e: ChangeEvent<any>) => {
|
||||
const value = e.target.value
|
||||
const { isValid, errorKey, errorMessageKey } = checkKeys([value], false)
|
||||
if (!isValid) {
|
||||
Toast.notify({
|
||||
type: 'error',
|
||||
message: t(`appDebug.varKeyError.${errorMessageKey}`, { key: errorKey }),
|
||||
})
|
||||
return
|
||||
}
|
||||
onGroupNameChange?.(value)
|
||||
}, [onGroupNameChange, t])
|
||||
|
||||
return (
|
||||
<Field
|
||||
className='group'
|
||||
title={groupEnabled
|
||||
? <div className='flex items-center'>
|
||||
<div className='flex items-center !normal-case'>
|
||||
<Folder className='mr-0.5 w-3.5 h-3.5' />
|
||||
{(!isEditGroupName)
|
||||
? (
|
||||
<div className='flex items-center h-6 px-1 rounded-lg cursor-text hover:bg-gray-100' onClick={setEditGroupName}>
|
||||
{payload.group_name}
|
||||
</div>
|
||||
)
|
||||
: (
|
||||
<input
|
||||
type='text'
|
||||
className='h-6 px-1 rounded-lg bg-white border border-gray-300 focus:outline-none'
|
||||
// style={{
|
||||
// width: `${((payload.group_name?.length || 0) + 1) / 2}em`,
|
||||
// }}
|
||||
size={payload.group_name?.length} // to fit the input width
|
||||
autoFocus
|
||||
value={payload.group_name}
|
||||
onChange={handleGroupNameChange}
|
||||
onBlur={setNotEditGroupName}
|
||||
maxLength={30}
|
||||
/>)}
|
||||
|
||||
</div>
|
||||
{canRemove && (
|
||||
<div
|
||||
className='group-hover:block hidden ml-0.5 p-1 rounded-md text-gray-500 cursor-pointer hover:bg-[#FEE4E2] hover:text-[#D92D20]'
|
||||
onClick={onRemove}
|
||||
>
|
||||
<Trash03
|
||||
className='w-4 h-4'
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
: t(`${i18nPrefix}.title`)!}
|
||||
operations={
|
||||
<div className='flex items-center h-6 space-x-2'>
|
||||
{payload.variables.length > 0 && (
|
||||
<div className='flex items-center h-[18px] px-1 border border-black/8 rounded-[5px] text-xs font-medium text-gray-500 capitalize'>{payload.output_type}</div>
|
||||
)}
|
||||
{
|
||||
!readOnly
|
||||
? <VarReferencePicker
|
||||
isAddBtnTrigger
|
||||
readonly={false}
|
||||
nodeId={nodeId}
|
||||
isShowNodeName
|
||||
value={[]}
|
||||
onChange={handleAddVariable}
|
||||
defaultVarKindType={VarKindType.variable}
|
||||
filterVar={filterVar}
|
||||
availableVars={availableVars}
|
||||
/>
|
||||
: undefined
|
||||
}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<VarList
|
||||
readonly={readOnly}
|
||||
nodeId={nodeId}
|
||||
list={payload.variables}
|
||||
onChange={handleListChange}
|
||||
filterVar={filterVar}
|
||||
/>
|
||||
</Field>
|
||||
)
|
||||
}
|
||||
export default React.memo(VarGroupItem)
|
||||
@@ -4,6 +4,7 @@ import { useTranslation } from 'react-i18next'
|
||||
import React, { useCallback } from 'react'
|
||||
import produce from 'immer'
|
||||
import RemoveButton from '../../../_base/components/remove-button'
|
||||
import ListNoDataPlaceholder from '../../../_base/components/list-no-data-placeholder'
|
||||
import VarReferencePicker from '@/app/components/workflow/nodes/_base/components/variable/var-reference-picker'
|
||||
import type { ValueSelector, Var } from '@/app/components/workflow/types'
|
||||
import { VarType as VarKindType } from '@/app/components/workflow/nodes/tool/types'
|
||||
@@ -12,9 +13,8 @@ type Props = {
|
||||
readonly: boolean
|
||||
nodeId: string
|
||||
list: ValueSelector[]
|
||||
onChange: (list: ValueSelector[]) => void
|
||||
onChange: (list: ValueSelector[], value?: ValueSelector) => void
|
||||
onOpen?: (index: number) => void
|
||||
onlyLeafNodeVar?: boolean
|
||||
filterVar?: (payload: Var, valueSelector: ValueSelector) => boolean
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@ const VarList: FC<Props> = ({
|
||||
list,
|
||||
onChange,
|
||||
onOpen = () => { },
|
||||
onlyLeafNodeVar,
|
||||
filterVar,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
@@ -33,7 +32,7 @@ const VarList: FC<Props> = ({
|
||||
const newList = produce(list, (draft) => {
|
||||
draft[index] = value as ValueSelector
|
||||
})
|
||||
onChange(newList)
|
||||
onChange(newList, value as ValueSelector)
|
||||
}
|
||||
}, [list, onChange])
|
||||
|
||||
@@ -52,9 +51,9 @@ const VarList: FC<Props> = ({
|
||||
|
||||
if (list.length === 0) {
|
||||
return (
|
||||
<div className='flex rounded-md bg-gray-50 items-center h-[42px] justify-center leading-[18px] text-xs font-normal text-gray-500'>
|
||||
<ListNoDataPlaceholder>
|
||||
{t('workflow.nodes.variableAssigner.noVarTip')}
|
||||
</div>
|
||||
</ListNoDataPlaceholder>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -70,7 +69,6 @@ const VarList: FC<Props> = ({
|
||||
value={item}
|
||||
onChange={handleVarReferenceChange(index)}
|
||||
onOpen={handleOpen(index)}
|
||||
onlyLeafNodeVar={onlyLeafNodeVar}
|
||||
filterVar={filterVar}
|
||||
defaultVarKindType={VarKindType.variable}
|
||||
/>
|
||||
|
||||
@@ -2,7 +2,6 @@ import { useCallback } from 'react'
|
||||
import produce from 'immer'
|
||||
import type { VariableAssignerNodeType } from '../../types'
|
||||
import type { ValueSelector } from '@/app/components/workflow/types'
|
||||
import { useEdgesInteractions } from '@/app/components/workflow/hooks'
|
||||
|
||||
type Params = {
|
||||
id: string
|
||||
@@ -10,18 +9,15 @@ type Params = {
|
||||
setInputs: (newInputs: VariableAssignerNodeType) => void
|
||||
}
|
||||
function useVarList({
|
||||
id,
|
||||
inputs,
|
||||
setInputs,
|
||||
}: Params) {
|
||||
const { handleVariableAssignerEdgesChange } = useEdgesInteractions()
|
||||
const handleVarListChange = useCallback((newList: ValueSelector[]) => {
|
||||
const newInputs = produce(inputs, (draft) => {
|
||||
draft.variables = newList
|
||||
})
|
||||
setInputs(newInputs)
|
||||
handleVariableAssignerEdgesChange(id, newList)
|
||||
}, [inputs, setInputs, id, handleVariableAssignerEdgesChange])
|
||||
}, [inputs, setInputs])
|
||||
|
||||
const handleAddVariable = useCallback(() => {
|
||||
const newInputs = produce(inputs, (draft) => {
|
||||
|
||||
@@ -7,14 +7,14 @@ const i18nPrefix = 'workflow'
|
||||
|
||||
const nodeDefault: NodeDefault<VariableAssignerNodeType> = {
|
||||
defaultValue: {
|
||||
output_type: VarType.string,
|
||||
output_type: VarType.any,
|
||||
variables: [],
|
||||
},
|
||||
getAvailablePrevNodes(isChatMode: boolean) {
|
||||
const nodes = isChatMode
|
||||
? ALL_CHAT_AVAILABLE_BLOCKS
|
||||
: ALL_COMPLETION_AVAILABLE_BLOCKS.filter(type => type !== BlockEnum.End)
|
||||
return nodes.filter(type => type !== BlockEnum.IfElse && type !== BlockEnum.QuestionClassifier)
|
||||
return nodes
|
||||
},
|
||||
getAvailableNextNodes(isChatMode: boolean) {
|
||||
const nodes = isChatMode ? ALL_CHAT_AVAILABLE_BLOCKS : ALL_COMPLETION_AVAILABLE_BLOCKS
|
||||
|
||||
243
web/app/components/workflow/nodes/variable-assigner/hooks.ts
Normal file
243
web/app/components/workflow/nodes/variable-assigner/hooks.ts
Normal file
@@ -0,0 +1,243 @@
|
||||
import { useCallback } from 'react'
|
||||
import {
|
||||
useEdges,
|
||||
useNodes,
|
||||
useStoreApi,
|
||||
} from 'reactflow'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { uniqBy } from 'lodash-es'
|
||||
import produce from 'immer'
|
||||
import {
|
||||
useIsChatMode,
|
||||
useNodeDataUpdate,
|
||||
useWorkflow,
|
||||
} from '../../hooks'
|
||||
import { getNodesConnectedSourceOrTargetHandleIdsMap } from '../../utils'
|
||||
import type {
|
||||
Edge,
|
||||
Node,
|
||||
ValueSelector,
|
||||
Var,
|
||||
} from '../../types'
|
||||
import { useWorkflowStore } from '../../store'
|
||||
import type {
|
||||
VarGroupItem,
|
||||
VariableAssignerNodeType,
|
||||
} from './types'
|
||||
import { toNodeAvailableVars } from '@/app/components/workflow/nodes/_base/components/variable/utils'
|
||||
|
||||
export const useVariableAssigner = () => {
|
||||
const store = useStoreApi()
|
||||
const workflowStore = useWorkflowStore()
|
||||
const { handleNodeDataUpdate } = useNodeDataUpdate()
|
||||
|
||||
const handleAssignVariableValueChange = useCallback((nodeId: string, value: ValueSelector, varDetail: Var, groupId?: string) => {
|
||||
const { getNodes } = store.getState()
|
||||
const nodes = getNodes()
|
||||
const node: Node<VariableAssignerNodeType> = nodes.find(node => node.id === nodeId)!
|
||||
|
||||
let payload
|
||||
if (groupId && groupId !== 'target') {
|
||||
payload = {
|
||||
advanced_settings: {
|
||||
...node.data.advanced_settings,
|
||||
groups: node.data.advanced_settings?.groups.map((group: VarGroupItem & { groupId: string }) => {
|
||||
if (group.groupId === groupId && !group.variables.some(item => item.join('.') === (value as ValueSelector).join('.'))) {
|
||||
return {
|
||||
...group,
|
||||
variables: [...group.variables, value],
|
||||
output_type: varDetail.type,
|
||||
}
|
||||
}
|
||||
return group
|
||||
}),
|
||||
},
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (node.data.variables.some(item => item.join('.') === (value as ValueSelector).join('.')))
|
||||
return
|
||||
payload = {
|
||||
variables: [...node.data.variables, value],
|
||||
output_type: varDetail.type,
|
||||
}
|
||||
}
|
||||
handleNodeDataUpdate({
|
||||
id: nodeId,
|
||||
data: payload,
|
||||
})
|
||||
}, [store, handleNodeDataUpdate])
|
||||
|
||||
const handleAddVariableInAddVariablePopupWithPosition = useCallback((
|
||||
nodeId: string,
|
||||
variableAssignerNodeId: string,
|
||||
variableAssignerNodeHandleId: string,
|
||||
value: ValueSelector,
|
||||
varDetail: Var,
|
||||
) => {
|
||||
const {
|
||||
getNodes,
|
||||
setNodes,
|
||||
} = store.getState()
|
||||
const {
|
||||
setShowAssignVariablePopup,
|
||||
} = workflowStore.getState()
|
||||
|
||||
const newNodes = produce(getNodes(), (draft) => {
|
||||
draft.forEach((node) => {
|
||||
if (node.id === nodeId || node.id === variableAssignerNodeId) {
|
||||
node.data = {
|
||||
...node.data,
|
||||
_showAddVariablePopup: false,
|
||||
_holdAddVariablePopup: false,
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
setNodes(newNodes)
|
||||
setShowAssignVariablePopup(undefined)
|
||||
handleAssignVariableValueChange(variableAssignerNodeId, value, varDetail, variableAssignerNodeHandleId)
|
||||
}, [store, workflowStore, handleAssignVariableValueChange])
|
||||
|
||||
const handleRemoveEdges = useCallback((nodeId: string, enabled: boolean) => {
|
||||
const {
|
||||
getNodes,
|
||||
setNodes,
|
||||
edges,
|
||||
setEdges,
|
||||
} = store.getState()
|
||||
const nodes = getNodes()
|
||||
const needDeleteEdges = edges.filter(edge => edge.target === nodeId)
|
||||
|
||||
if (!needDeleteEdges.length)
|
||||
return
|
||||
|
||||
const currentNode = nodes.find(node => node.id === nodeId)!
|
||||
const groups = currentNode.data.advanced_settings?.groups || []
|
||||
|
||||
let shouldKeepEdges: Edge[] = []
|
||||
|
||||
if (enabled) {
|
||||
shouldKeepEdges = edges.filter((edge) => {
|
||||
return edge.target === nodeId && edge.targetHandle === 'target'
|
||||
}).map((edge) => {
|
||||
return {
|
||||
...edge,
|
||||
targetHandle: groups[0].groupId,
|
||||
}
|
||||
})
|
||||
}
|
||||
else {
|
||||
shouldKeepEdges = edges.filter((edge) => {
|
||||
return edge.target === nodeId && edge.targetHandle === groups[0].groupId
|
||||
}).map((edge) => {
|
||||
return {
|
||||
...edge,
|
||||
targetHandle: 'target',
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const nodesConnectedSourceOrTargetHandleIdsMap = getNodesConnectedSourceOrTargetHandleIdsMap(
|
||||
[
|
||||
...needDeleteEdges.map((needDeleteEdge) => {
|
||||
return {
|
||||
type: 'remove',
|
||||
edge: needDeleteEdge,
|
||||
}
|
||||
}),
|
||||
...shouldKeepEdges.map((shouldKeepEdge) => {
|
||||
return {
|
||||
type: 'add',
|
||||
edge: shouldKeepEdge,
|
||||
}
|
||||
}),
|
||||
],
|
||||
nodes,
|
||||
)
|
||||
|
||||
const newNodes = produce(nodes, (draft) => {
|
||||
draft.forEach((node) => {
|
||||
if (nodesConnectedSourceOrTargetHandleIdsMap[node.id]) {
|
||||
node.data = {
|
||||
...node.data,
|
||||
...nodesConnectedSourceOrTargetHandleIdsMap[node.id],
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
setNodes(newNodes)
|
||||
const newEdges = produce(edges, (draft) => {
|
||||
draft = draft.filter(edge => edge.target !== nodeId)
|
||||
draft.push(...shouldKeepEdges)
|
||||
return draft
|
||||
})
|
||||
setEdges(newEdges)
|
||||
}, [store])
|
||||
|
||||
const handleGroupItemMouseEnter = useCallback((groupId: string) => {
|
||||
const {
|
||||
setHoveringAssignVariableGroupId,
|
||||
} = workflowStore.getState()
|
||||
|
||||
setHoveringAssignVariableGroupId(groupId)
|
||||
}, [workflowStore])
|
||||
|
||||
const handleGroupItemMouseLeave = useCallback(() => {
|
||||
const {
|
||||
connectingNodePayload,
|
||||
setHoveringAssignVariableGroupId,
|
||||
} = workflowStore.getState()
|
||||
|
||||
if (connectingNodePayload)
|
||||
setHoveringAssignVariableGroupId(undefined)
|
||||
}, [workflowStore])
|
||||
|
||||
return {
|
||||
handleAddVariableInAddVariablePopupWithPosition,
|
||||
handleRemoveEdges,
|
||||
handleGroupItemMouseEnter,
|
||||
handleGroupItemMouseLeave,
|
||||
handleAssignVariableValueChange,
|
||||
}
|
||||
}
|
||||
|
||||
export const useGetAvailableVars = () => {
|
||||
const { t } = useTranslation()
|
||||
const nodes: Node[] = useNodes()
|
||||
const edges: Edge[] = useEdges()
|
||||
const { getBeforeNodesInSameBranch } = useWorkflow()
|
||||
const isChatMode = useIsChatMode()
|
||||
const getAvailableVars = useCallback((nodeId: string, handleId: string, filterVar: (v: Var) => boolean) => {
|
||||
const availableNodes: Node[] = []
|
||||
const currentNode = nodes.find(node => node.id === nodeId)!
|
||||
|
||||
if (!currentNode)
|
||||
return []
|
||||
const parentNode = nodes.find(node => node.id === currentNode.parentId)
|
||||
const connectedEdges = edges.filter(edge => edge.target === nodeId && edge.targetHandle === handleId)
|
||||
|
||||
if (parentNode && !connectedEdges.length) {
|
||||
const beforeNodes = getBeforeNodesInSameBranch(parentNode.id)
|
||||
availableNodes.push(...beforeNodes)
|
||||
}
|
||||
else {
|
||||
connectedEdges.forEach((connectedEdge) => {
|
||||
const beforeNodes = getBeforeNodesInSameBranch(connectedEdge.source)
|
||||
const connectedNode = nodes.find(node => node.id === connectedEdge.source)!
|
||||
|
||||
availableNodes.push(connectedNode, ...beforeNodes)
|
||||
})
|
||||
}
|
||||
|
||||
return toNodeAvailableVars({
|
||||
parentNode,
|
||||
t,
|
||||
beforeNodes: uniqBy(availableNodes, 'id').filter(node => node.id !== nodeId),
|
||||
isChatMode,
|
||||
filterVar,
|
||||
})
|
||||
}, [nodes, edges, t, isChatMode, getBeforeNodesInSameBranch])
|
||||
|
||||
return getAvailableVars
|
||||
}
|
||||
@@ -1,91 +1,61 @@
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import {
|
||||
memo,
|
||||
useMemo,
|
||||
useRef,
|
||||
} from 'react'
|
||||
import type { NodeProps } from 'reactflow'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { NodeTargetHandle } from '../_base/components/node-handle'
|
||||
import { BlockEnum } from '../../types'
|
||||
import NodeGroupItem from './components/node-group-item'
|
||||
import type { VariableAssignerNodeType } from './types'
|
||||
import { VarBlockIcon } from '@/app/components/workflow/block-icon'
|
||||
import { Line3 } from '@/app/components/base/icons/src/public/common'
|
||||
import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development'
|
||||
import {
|
||||
useWorkflow,
|
||||
} from '@/app/components/workflow/hooks'
|
||||
|
||||
const i18nPrefix = 'workflow.nodes.variableAssigner'
|
||||
|
||||
const Node: FC<NodeProps<VariableAssignerNodeType>> = (props) => {
|
||||
const { t } = useTranslation()
|
||||
const ref = useRef<HTMLDivElement>(null)
|
||||
const { id, data } = props
|
||||
const { variables: originVariables, output_type } = data
|
||||
const { getTreeLeafNodes } = useWorkflow()
|
||||
const { advanced_settings } = data
|
||||
|
||||
const availableNodes = getTreeLeafNodes(id)
|
||||
const variables = originVariables.filter(item => item.length > 0)
|
||||
const groups = useMemo(() => {
|
||||
if (!advanced_settings?.group_enabled) {
|
||||
return [{
|
||||
groupEnabled: false,
|
||||
targetHandleId: 'target',
|
||||
title: t(`${i18nPrefix}.title`),
|
||||
type: data.output_type,
|
||||
variables: data.variables,
|
||||
variableAssignerNodeId: id,
|
||||
variableAssignerNodeData: data,
|
||||
}]
|
||||
}
|
||||
return advanced_settings.groups.map((group) => {
|
||||
return {
|
||||
groupEnabled: true,
|
||||
targetHandleId: group.groupId,
|
||||
title: group.group_name,
|
||||
type: group.output_type,
|
||||
variables: group.variables,
|
||||
variableAssignerNodeId: id,
|
||||
variableAssignerNodeData: data,
|
||||
}
|
||||
})
|
||||
}, [t, advanced_settings, data, id])
|
||||
|
||||
return (
|
||||
<div className='mb-1 px-3 py-1'>
|
||||
<div className='mb-0.5 leading-4 text-xs font-medium text-gray-500 uppercase'>{t(`${i18nPrefix}.title`)}</div>
|
||||
<div className='relative mb-1 px-1' ref={ref}>
|
||||
{
|
||||
variables.length === 0 && (
|
||||
<div className='relative flex items-center h-6 justify-between bg-gray-100 rounded-md px-1 space-x-1 text-xs font-normal text-gray-400 uppercase'>
|
||||
{t(`${i18nPrefix}.varNotSet`)}
|
||||
<NodeTargetHandle
|
||||
{...props}
|
||||
handleId='varNotSet'
|
||||
handleClassName='!top-1/2 !-translate-y-1/2 !-left-[21px]'
|
||||
groups.map((item) => {
|
||||
return (
|
||||
<NodeGroupItem
|
||||
key={item.title}
|
||||
item={item}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{variables.length > 0 && (
|
||||
<>
|
||||
<div className='space-y-0.5'>
|
||||
{variables.map((item, index) => {
|
||||
const node = availableNodes.find(node => node.id === item[0])
|
||||
const varName = item[item.length - 1]
|
||||
|
||||
return (
|
||||
<div key={index} className='relative flex items-center h-6 bg-gray-100 rounded-md px-1 text-xs font-normal text-gray-700' >
|
||||
<NodeTargetHandle
|
||||
{...props}
|
||||
handleId={item[0]}
|
||||
handleClassName='!top-1/2 !-translate-y-1/2 !-left-[21px]'
|
||||
/>
|
||||
<div className='flex items-center'>
|
||||
<div className='p-[1px]'>
|
||||
<VarBlockIcon
|
||||
className='!text-gray-900'
|
||||
type={(node?.data.type as BlockEnum) || BlockEnum.Start}
|
||||
/>
|
||||
</div>
|
||||
<div className='max-w-[85px] truncate mx-0.5 text-xs font-medium text-gray-700' title={node?.data.title}>{node?.data.title}</div>
|
||||
<Line3 className='mr-0.5'></Line3>
|
||||
</div>
|
||||
<div className='flex items-center text-primary-600'>
|
||||
<Variable02 className='w-3.5 h-3.5' />
|
||||
<div className='max-w-[75px] truncate ml-0.5 text-xs font-medium' title={varName}>{varName}</div>
|
||||
</div>
|
||||
{/* <div className='ml-0.5 text-xs font-normal text-gray-500'>{output_type}</div> */}
|
||||
</div>
|
||||
)
|
||||
},
|
||||
|
||||
)}
|
||||
</div>
|
||||
<div className='mt-2 flex items-center h-6 justify-between bg-gray-100 rounded-md px-1 space-x-1 text-xs font-normal text-gray-700'>
|
||||
<div className='text-xs font-medium text-gray-500 uppercase'>
|
||||
{t(`${i18nPrefix}.outputType`)}
|
||||
</div>
|
||||
<div className='text-xs font-normal text-gray-700'>
|
||||
{t(`${i18nPrefix}.type.${output_type}`)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
)
|
||||
})
|
||||
}
|
||||
</div >
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(Node)
|
||||
export default memo(Node)
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
// import cn from 'classnames'
|
||||
// import Field from '../_base/components/field'
|
||||
import RemoveEffectVarConfirm from '../_base/components/remove-effect-var-confirm'
|
||||
import useConfig from './use-config'
|
||||
import VarList from './components/var-list'
|
||||
import type { VariableAssignerNodeType } from './types'
|
||||
import Field from '@/app/components/workflow/nodes/_base/components/field'
|
||||
import Selector from '@/app/components/workflow/nodes/_base/components/selector'
|
||||
import AddButton from '@/app/components/base/button/add-button'
|
||||
import { ChevronDown } from '@/app/components/base/icons/src/vender/line/arrows'
|
||||
import type { NodePanelProps } from '@/app/components/workflow/types'
|
||||
import { VarType } from '@/app/components/workflow/types'
|
||||
import VarGroupItem from './components/var-group-item'
|
||||
import { type NodePanelProps } from '@/app/components/workflow/types'
|
||||
import Split from '@/app/components/workflow/nodes/_base/components/split'
|
||||
import OutputVars, { VarItem } from '@/app/components/workflow/nodes/_base/components/output-vars'
|
||||
// import OutputVars, { VarItem } from '@/app/components/workflow/nodes/_base/components/output-vars'
|
||||
// import Switch from '@/app/components/base/switch'
|
||||
import AddButton from '@/app/components/workflow/nodes/_base/components/add-button'
|
||||
|
||||
const i18nPrefix = 'workflow.nodes.variableAssigner'
|
||||
const Panel: FC<NodePanelProps<VariableAssignerNodeType>> = ({
|
||||
@@ -23,70 +23,105 @@ const Panel: FC<NodePanelProps<VariableAssignerNodeType>> = ({
|
||||
const {
|
||||
readOnly,
|
||||
inputs,
|
||||
handleOutputTypeChange,
|
||||
handleVarListChange,
|
||||
handleAddVariable,
|
||||
handleOnVarOpen,
|
||||
handleListOrTypeChange,
|
||||
isEnableGroup,
|
||||
// handleGroupEnabledChange,
|
||||
handleAddGroup,
|
||||
handleListOrTypeChangeInGroup,
|
||||
handleGroupRemoved,
|
||||
handleVarGroupNameChange,
|
||||
isShowRemoveVarConfirm,
|
||||
hideRemoveVarConfirm,
|
||||
onRemoveVarConfirm,
|
||||
getAvailableVars,
|
||||
filterVar,
|
||||
} = useConfig(id, data)
|
||||
|
||||
const typeOptions = [
|
||||
{ label: t(`${i18nPrefix}.type.string`), value: VarType.string },
|
||||
{ label: t(`${i18nPrefix}.type.number`), value: VarType.number },
|
||||
{ label: t(`${i18nPrefix}.type.object`), value: VarType.object },
|
||||
{ label: t(`${i18nPrefix}.type.array`), value: VarType.array },
|
||||
]
|
||||
|
||||
return (
|
||||
<div className='mt-2'>
|
||||
<div className='px-4 pb-4 space-y-4'>
|
||||
<Field
|
||||
title={t(`${i18nPrefix}.outputVarType`)}
|
||||
>
|
||||
<Selector
|
||||
readonly={readOnly}
|
||||
value={inputs.output_type}
|
||||
options={typeOptions}
|
||||
onChange={handleOutputTypeChange}
|
||||
trigger={
|
||||
<div className='flex items-center h-8 justify-between px-2.5 rounded-lg bg-gray-100 capitalize'>
|
||||
<div className='text-[13px] font-normal text-gray-900'>{inputs.output_type}</div>
|
||||
{!readOnly && <ChevronDown className='w-3.5 h-3.5 text-gray-700' />}
|
||||
</div>
|
||||
}
|
||||
popupClassName='!top-[36px] !w-[387px]'
|
||||
showChecked
|
||||
/>
|
||||
</Field>
|
||||
<Field
|
||||
title={t(`${i18nPrefix}.title`)}
|
||||
operations={
|
||||
!readOnly ? <AddButton onClick={handleAddVariable} /> : undefined
|
||||
}
|
||||
>
|
||||
<VarList
|
||||
readonly={readOnly}
|
||||
nodeId={id}
|
||||
list={inputs.variables}
|
||||
onChange={handleVarListChange}
|
||||
onOpen={handleOnVarOpen}
|
||||
onlyLeafNodeVar
|
||||
filterVar={filterVar}
|
||||
/>
|
||||
</Field>
|
||||
</div>
|
||||
<Split />
|
||||
<div className='px-4 pt-4 pb-2'>
|
||||
<OutputVars>
|
||||
<>
|
||||
<VarItem
|
||||
name='output'
|
||||
type={inputs.output_type}
|
||||
description={t(`${i18nPrefix}.outputVars.output`)}
|
||||
{!isEnableGroup
|
||||
? (
|
||||
<VarGroupItem
|
||||
readOnly={readOnly}
|
||||
nodeId={id}
|
||||
payload={{
|
||||
output_type: inputs.output_type,
|
||||
variables: inputs.variables,
|
||||
}}
|
||||
onChange={handleListOrTypeChange}
|
||||
groupEnabled={false}
|
||||
availableVars={getAvailableVars(id, 'target', filterVar(inputs.output_type))}
|
||||
/>
|
||||
</>
|
||||
</OutputVars>
|
||||
)
|
||||
: (<div>
|
||||
<div className='space-y-2'>
|
||||
{inputs.advanced_settings?.groups.map((item, index) => (
|
||||
<div key={item.groupId}>
|
||||
<VarGroupItem
|
||||
readOnly={readOnly}
|
||||
nodeId={id}
|
||||
payload={item}
|
||||
onChange={handleListOrTypeChangeInGroup(item.groupId)}
|
||||
groupEnabled
|
||||
canRemove={!readOnly && inputs.advanced_settings?.groups.length > 1}
|
||||
onRemove={handleGroupRemoved(item.groupId)}
|
||||
onGroupNameChange={handleVarGroupNameChange(item.groupId)}
|
||||
availableVars={getAvailableVars(id, item.groupId, filterVar(item.output_type))}
|
||||
/>
|
||||
{index !== inputs.advanced_settings?.groups.length - 1 && <Split className='my-4' />}
|
||||
</div>
|
||||
|
||||
))}
|
||||
</div>
|
||||
<AddButton
|
||||
className='mt-2'
|
||||
text={t(`${i18nPrefix}.addGroup`)}
|
||||
onClick={handleAddGroup}
|
||||
/>
|
||||
</div>)}
|
||||
</div>
|
||||
{/* <Split /> */}
|
||||
{/* <div className={cn('px-4 pt-4', isEnableGroup ? 'pb-4' : 'pb-2')}>
|
||||
<Field
|
||||
title={t(`${i18nPrefix}.aggregationGroup`)}
|
||||
tooltip={t(`${i18nPrefix}.aggregationGroupTip`)!}
|
||||
operations={
|
||||
<Switch
|
||||
defaultValue={isEnableGroup}
|
||||
onChange={handleGroupEnabledChange}
|
||||
size='md'
|
||||
disabled={readOnly}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</div> */}
|
||||
{/* {isEnableGroup && (
|
||||
<>
|
||||
<Split />
|
||||
<div className='px-4 pt-4 pb-2'>
|
||||
<OutputVars>
|
||||
<>
|
||||
{inputs.advanced_settings?.groups.map((item, index) => (
|
||||
<VarItem
|
||||
key={index}
|
||||
name={`${item.group_name}.output`}
|
||||
type={item.output_type}
|
||||
description={t(`${i18nPrefix}.outputVars.varDescribe`, {
|
||||
groupName: item.group_name,
|
||||
})}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
</OutputVars>
|
||||
</div>
|
||||
</>
|
||||
)} */}
|
||||
<RemoveEffectVarConfirm
|
||||
isShow={isShowRemoveVarConfirm}
|
||||
onCancel={hideRemoveVarConfirm}
|
||||
onConfirm={onRemoveVarConfirm}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
import type { CommonNodeType, ValueSelector, VarType } from '@/app/components/workflow/types'
|
||||
|
||||
export type VariableAssignerNodeType = CommonNodeType & {
|
||||
export type VarGroupItem = {
|
||||
output_type: VarType
|
||||
variables: ValueSelector[]
|
||||
}
|
||||
export type VariableAssignerNodeType = CommonNodeType & VarGroupItem & {
|
||||
advanced_settings: {
|
||||
group_enabled: boolean
|
||||
groups: ({
|
||||
group_name: string
|
||||
groupId: string
|
||||
} & VarGroupItem)[]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { useCallback, useState } from 'react'
|
||||
import produce from 'immer'
|
||||
import useVarList from './components/var-list/use-var-list'
|
||||
import type { VariableAssignerNodeType } from './types'
|
||||
import { useBoolean } from 'ahooks'
|
||||
import { v4 as uuid4 } from 'uuid'
|
||||
import type { ValueSelector, Var } from '../../types'
|
||||
import { VarType } from '../../types'
|
||||
import type { VarGroupItem, VariableAssignerNodeType } from './types'
|
||||
import { useGetAvailableVars, useVariableAssigner } from './hooks'
|
||||
import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud'
|
||||
import type { ValueSelector, Var } from '@/app/components/workflow/types'
|
||||
import { BlockEnum, VarType } from '@/app/components/workflow/types'
|
||||
|
||||
import {
|
||||
useNodesReadOnly,
|
||||
useWorkflow,
|
||||
@@ -12,68 +15,176 @@ import {
|
||||
|
||||
const useConfig = (id: string, payload: VariableAssignerNodeType) => {
|
||||
const { nodesReadOnly: readOnly } = useNodesReadOnly()
|
||||
const { inputs, setInputs } = useNodeCrud<VariableAssignerNodeType>(id, payload)
|
||||
const { getBeforeNodeById } = useWorkflow()
|
||||
const beforeNodes = getBeforeNodeById(id)
|
||||
const { handleOutVarRenameChange, isVarUsedInNodes, removeUsedVarInNodes } = useWorkflow()
|
||||
|
||||
useEffect(() => {
|
||||
if (beforeNodes.length !== 1 || inputs.variables.length > 0)
|
||||
return
|
||||
const beforeNode = beforeNodes[0]
|
||||
if (beforeNode.data.type === BlockEnum.KnowledgeRetrieval) {
|
||||
const newInputs = produce(inputs, (draft: VariableAssignerNodeType) => {
|
||||
draft.output_type = VarType.array
|
||||
draft.variables[0] = [beforeNode.id, 'result']
|
||||
const { inputs, setInputs } = useNodeCrud<VariableAssignerNodeType>(id, payload)
|
||||
const isEnableGroup = !!inputs.advanced_settings?.group_enabled
|
||||
const { handleRemoveEdges } = useVariableAssigner()
|
||||
|
||||
// Not Enable Group
|
||||
const handleListOrTypeChange = useCallback((payload: VarGroupItem) => {
|
||||
setInputs({
|
||||
...inputs,
|
||||
...payload,
|
||||
})
|
||||
}, [inputs, setInputs])
|
||||
|
||||
const handleListOrTypeChangeInGroup = useCallback((groupId: string) => {
|
||||
return (payload: VarGroupItem) => {
|
||||
const index = inputs.advanced_settings.groups.findIndex(item => item.groupId === groupId)
|
||||
const newInputs = produce(inputs, (draft) => {
|
||||
draft.advanced_settings.groups[index] = {
|
||||
...draft.advanced_settings.groups[index],
|
||||
...payload,
|
||||
}
|
||||
})
|
||||
setInputs(newInputs)
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [beforeNodes, inputs.variables])
|
||||
}, [inputs, setInputs])
|
||||
|
||||
const handleOutputTypeChange = useCallback((outputType: string) => {
|
||||
const newInputs = produce(inputs, (draft: VariableAssignerNodeType) => {
|
||||
draft.output_type = outputType as VarType
|
||||
const getAvailableVars = useGetAvailableVars()
|
||||
const filterVar = (varType: VarType) => {
|
||||
return (v: Var) => {
|
||||
if (varType === VarType.any)
|
||||
return true
|
||||
if (v.type === VarType.any)
|
||||
return true
|
||||
return v.type === varType
|
||||
}
|
||||
}
|
||||
|
||||
const [isShowRemoveVarConfirm, {
|
||||
setTrue: showRemoveVarConfirm,
|
||||
setFalse: hideRemoveVarConfirm,
|
||||
}] = useBoolean(false)
|
||||
|
||||
const [removedVars, setRemovedVars] = useState<ValueSelector[]>([])
|
||||
const [removeType, setRemoveType] = useState<'group' | 'enableChanged'>('group')
|
||||
const [removedGroupIndex, setRemovedGroupIndex] = useState<number>(-1)
|
||||
const handleGroupRemoved = useCallback((groupId: string) => {
|
||||
return () => {
|
||||
const index = inputs.advanced_settings.groups.findIndex(item => item.groupId === groupId)
|
||||
if (isVarUsedInNodes([id, inputs.advanced_settings.groups[index].group_name, 'output'])) {
|
||||
showRemoveVarConfirm()
|
||||
setRemovedVars([[id, inputs.advanced_settings.groups[index].group_name, 'output']])
|
||||
setRemoveType('group')
|
||||
setRemovedGroupIndex(index)
|
||||
return
|
||||
}
|
||||
const newInputs = produce(inputs, (draft) => {
|
||||
draft.advanced_settings.groups.splice(index, 1)
|
||||
})
|
||||
setInputs(newInputs)
|
||||
}
|
||||
}, [id, inputs, isVarUsedInNodes, setInputs, showRemoveVarConfirm])
|
||||
|
||||
const handleGroupEnabledChange = useCallback((enabled: boolean) => {
|
||||
const newInputs = produce(inputs, (draft) => {
|
||||
if (!draft.advanced_settings)
|
||||
draft.advanced_settings = { group_enabled: false, groups: [] }
|
||||
if (enabled) {
|
||||
if (draft.advanced_settings.groups.length === 0) {
|
||||
const DEFAULT_GROUP_NAME = 'Group1'
|
||||
draft.advanced_settings.groups = [{
|
||||
output_type: draft.output_type,
|
||||
variables: draft.variables,
|
||||
group_name: DEFAULT_GROUP_NAME,
|
||||
groupId: uuid4(),
|
||||
}]
|
||||
|
||||
handleOutVarRenameChange(id, [id, 'output'], [id, DEFAULT_GROUP_NAME, 'output'])
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (draft.advanced_settings.groups.length > 0) {
|
||||
if (draft.advanced_settings.groups.length > 1) {
|
||||
const useVars = draft.advanced_settings.groups.filter((item, index) => index > 0 && isVarUsedInNodes([id, item.group_name, 'output']))
|
||||
if (useVars.length > 0) {
|
||||
showRemoveVarConfirm()
|
||||
setRemovedVars(useVars.map(item => [id, item.group_name, 'output']))
|
||||
setRemoveType('enableChanged')
|
||||
return
|
||||
}
|
||||
}
|
||||
draft.output_type = draft.advanced_settings.groups[0].output_type
|
||||
draft.variables = draft.advanced_settings.groups[0].variables
|
||||
handleOutVarRenameChange(id, [id, draft.advanced_settings.groups[0].group_name, 'output'], [id, 'output'])
|
||||
}
|
||||
}
|
||||
draft.advanced_settings.group_enabled = enabled
|
||||
})
|
||||
setInputs(newInputs)
|
||||
handleRemoveEdges(id, enabled)
|
||||
}, [handleOutVarRenameChange, id, inputs, isVarUsedInNodes, setInputs, showRemoveVarConfirm, handleRemoveEdges])
|
||||
|
||||
const handleAddGroup = useCallback(() => {
|
||||
let maxInGroupName = 1
|
||||
inputs.advanced_settings.groups.forEach((item) => {
|
||||
const match = item.group_name.match(/(\d+)$/)
|
||||
if (match) {
|
||||
const num = parseInt(match[1], 10)
|
||||
if (num > maxInGroupName)
|
||||
maxInGroupName = num
|
||||
}
|
||||
})
|
||||
const newInputs = produce(inputs, (draft) => {
|
||||
draft.advanced_settings.groups.push({
|
||||
output_type: VarType.any,
|
||||
variables: [],
|
||||
group_name: `Group${maxInGroupName + 1}`,
|
||||
groupId: uuid4(),
|
||||
})
|
||||
})
|
||||
setInputs(newInputs)
|
||||
}, [inputs, setInputs])
|
||||
|
||||
const { handleVarListChange, handleAddVariable } = useVarList({
|
||||
id,
|
||||
inputs,
|
||||
setInputs,
|
||||
})
|
||||
const handleVarGroupNameChange = useCallback((groupId: string) => {
|
||||
return (name: string) => {
|
||||
const index = inputs.advanced_settings.groups.findIndex(item => item.groupId === groupId)
|
||||
const newInputs = produce(inputs, (draft) => {
|
||||
draft.advanced_settings.groups[index].group_name = name
|
||||
})
|
||||
handleOutVarRenameChange(id, [id, inputs.advanced_settings.groups[index].group_name, 'output'], [id, name, 'output'])
|
||||
setInputs(newInputs)
|
||||
}
|
||||
}, [handleOutVarRenameChange, id, inputs, setInputs])
|
||||
|
||||
const { variables } = inputs
|
||||
const [currVarIndex, setCurrVarIndex] = useState(-1)
|
||||
const currVar = variables[currVarIndex]
|
||||
const handleOnVarOpen = useCallback((index: number) => {
|
||||
setCurrVarIndex(index)
|
||||
}, [])
|
||||
const filterVar = useCallback((varPayload: Var, valueSelector: ValueSelector) => {
|
||||
const type = varPayload.type
|
||||
if ((inputs.output_type !== VarType.array && type !== inputs.output_type) || (
|
||||
inputs.output_type === VarType.array && ![VarType.array, VarType.arrayString, VarType.arrayNumber, VarType.arrayObject].includes(type)
|
||||
))
|
||||
return false
|
||||
const onRemoveVarConfirm = useCallback(() => {
|
||||
removedVars.forEach((v) => {
|
||||
removeUsedVarInNodes(v)
|
||||
})
|
||||
hideRemoveVarConfirm()
|
||||
if (removeType === 'group') {
|
||||
const newInputs = produce(inputs, (draft) => {
|
||||
draft.advanced_settings.groups.splice(removedGroupIndex, 1)
|
||||
})
|
||||
setInputs(newInputs)
|
||||
}
|
||||
else {
|
||||
// removeType === 'enableChanged' to enabled
|
||||
const newInputs = produce(inputs, (draft) => {
|
||||
draft.advanced_settings.group_enabled = false
|
||||
draft.output_type = draft.advanced_settings.groups[0].output_type
|
||||
draft.variables = draft.advanced_settings.groups[0].variables
|
||||
})
|
||||
setInputs(newInputs)
|
||||
}
|
||||
}, [removedVars, hideRemoveVarConfirm, removeType, removeUsedVarInNodes, inputs, setInputs, removedGroupIndex])
|
||||
|
||||
// can not choose the same node
|
||||
if (!currVar)
|
||||
return true
|
||||
|
||||
const selectNodeId = valueSelector[0]
|
||||
|
||||
if (selectNodeId !== currVar[0] && variables.find(v => v[0] === selectNodeId))
|
||||
return false
|
||||
|
||||
return true
|
||||
}, [currVar, inputs.output_type, variables])
|
||||
return {
|
||||
readOnly,
|
||||
inputs,
|
||||
handleOutputTypeChange,
|
||||
handleVarListChange,
|
||||
handleAddVariable,
|
||||
handleOnVarOpen,
|
||||
handleListOrTypeChange,
|
||||
isEnableGroup,
|
||||
handleGroupEnabledChange,
|
||||
handleAddGroup,
|
||||
handleListOrTypeChangeInGroup,
|
||||
handleGroupRemoved,
|
||||
handleVarGroupNameChange,
|
||||
isShowRemoveVarConfirm,
|
||||
hideRemoveVarConfirm,
|
||||
onRemoveVarConfirm,
|
||||
getAvailableVars,
|
||||
filterVar,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
import type { VariableAssignerNodeType } from './types'
|
||||
import type { Var } from '../../types'
|
||||
import { VarType } from '../../types'
|
||||
|
||||
export const checkNodeValid = (payload: VariableAssignerNodeType) => {
|
||||
export const checkNodeValid = () => {
|
||||
return true
|
||||
}
|
||||
|
||||
export const filterVar = (varType: VarType) => {
|
||||
return (v: Var) => {
|
||||
if (varType === VarType.any)
|
||||
return true
|
||||
if (v.type === VarType.any)
|
||||
return true
|
||||
return v.type === varType
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user