dify
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
import {
|
||||
memo,
|
||||
useCallback,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { useVariableAssigner } from '../../hooks'
|
||||
import type { VariableAssignerNodeType } from '../../types'
|
||||
import cn from '@/utils/classnames'
|
||||
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 = {
|
||||
variableAssignerNodeId: string
|
||||
variableAssignerNodeData: VariableAssignerNodeType
|
||||
availableVars: NodeOutPutVar[]
|
||||
handleId?: string
|
||||
}
|
||||
const AddVariable = ({
|
||||
availableVars,
|
||||
variableAssignerNodeId,
|
||||
variableAssignerNodeData,
|
||||
handleId,
|
||||
}: AddVariableProps) => {
|
||||
const [open, setOpen] = useState(false)
|
||||
const { handleAssignVariableValueChange } = useVariableAssigner()
|
||||
|
||||
const handleSelectVariable = useCallback((v: ValueSelector, varDetail: Var) => {
|
||||
handleAssignVariableValueChange(
|
||||
variableAssignerNodeId,
|
||||
v,
|
||||
varDetail,
|
||||
handleId,
|
||||
)
|
||||
setOpen(false)
|
||||
}, [handleAssignVariableValueChange, variableAssignerNodeId, handleId, setOpen])
|
||||
|
||||
return (
|
||||
<div className={cn(
|
||||
open && '!flex',
|
||||
variableAssignerNodeData.selected && '!flex',
|
||||
)}>
|
||||
<PortalToFollowElem
|
||||
placement={'right'}
|
||||
offset={4}
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
>
|
||||
<PortalToFollowElemTrigger
|
||||
onClick={() => setOpen(!open)}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
'group/addvariable flex items-center justify-center',
|
||||
'h-4 w-4 cursor-pointer',
|
||||
'hover:rounded-full hover:bg-primary-600',
|
||||
open && '!rounded-full !bg-primary-600',
|
||||
)}
|
||||
>
|
||||
<Plus02
|
||||
className={cn(
|
||||
'h-2.5 w-2.5 text-text-tertiary',
|
||||
'group-hover/addvariable:text-text-primary',
|
||||
open && '!text-text-primary',
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemContent className='z-[1000]'>
|
||||
<AddVariablePopup
|
||||
onSelect={handleSelectVariable}
|
||||
availableVars={availableVars}
|
||||
/>
|
||||
</PortalToFollowElemContent>
|
||||
</PortalToFollowElem>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(AddVariable)
|
||||
@@ -0,0 +1,155 @@
|
||||
import {
|
||||
memo,
|
||||
useMemo,
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import useNodes from '@/app/components/workflow/store/workflow/use-nodes'
|
||||
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 AddVariable from './add-variable'
|
||||
import { isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils'
|
||||
import cn from '@/utils/classnames'
|
||||
import { isExceptionVariable } from '@/app/components/workflow/utils'
|
||||
import {
|
||||
VariableLabelInNode,
|
||||
} from '@/app/components/workflow/nodes/_base/components/variable/variable-label'
|
||||
|
||||
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 groupEnabled = item.groupEnabled
|
||||
const outputType = useMemo(() => {
|
||||
if (!groupEnabled)
|
||||
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, groupEnabled])
|
||||
const availableVars = getAvailableVars(item.variableAssignerNodeId, item.targetHandleId, filterVar(outputType as VarType), true)
|
||||
const showSelectionBorder = useMemo(() => {
|
||||
if (groupEnabled && enteringNodePayload?.nodeId === item.variableAssignerNodeId) {
|
||||
if (hoveringAssignVariableGroupId)
|
||||
return hoveringAssignVariableGroupId !== item.targetHandleId
|
||||
else
|
||||
return enteringNodePayload?.nodeData.advanced_settings?.groups[0].groupId !== item.targetHandleId
|
||||
}
|
||||
|
||||
return false
|
||||
}, [enteringNodePayload, groupEnabled, hoveringAssignVariableGroupId, item.targetHandleId, item.variableAssignerNodeId])
|
||||
const showSelectedBorder = useMemo(() => {
|
||||
if (groupEnabled && enteringNodePayload?.nodeId === item.variableAssignerNodeId) {
|
||||
if (hoveringAssignVariableGroupId)
|
||||
return hoveringAssignVariableGroupId === item.targetHandleId
|
||||
else
|
||||
return enteringNodePayload?.nodeData.advanced_settings?.groups[0].groupId === item.targetHandleId
|
||||
}
|
||||
|
||||
return false
|
||||
}, [enteringNodePayload, groupEnabled, hoveringAssignVariableGroupId, item.targetHandleId, item.variableAssignerNodeId])
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'relative rounded-lg border-[1.5px] border-transparent px-1.5 pb-1.5 pt-1',
|
||||
showSelectionBorder && '!border-dashed !border-divider-subtle bg-state-base-hover',
|
||||
showSelectedBorder && '!border-text-accent !bg-util-colors-blue-blue-50',
|
||||
)}
|
||||
onMouseEnter={() => groupEnabled && handleGroupItemMouseEnter(item.targetHandleId)}
|
||||
onMouseLeave={handleGroupItemMouseLeave}
|
||||
>
|
||||
<div className='flex h-4 items-center justify-between text-[10px] font-medium text-text-tertiary'>
|
||||
<span
|
||||
className={cn(
|
||||
'grow truncate uppercase',
|
||||
showSelectedBorder && 'text-text-accent',
|
||||
)}
|
||||
title={item.title}
|
||||
>
|
||||
{item.title}
|
||||
</span>
|
||||
<div className='flex items-center'>
|
||||
<span className='ml-2 shrink-0'>{item.type}</span>
|
||||
<div className='ml-2 mr-1 h-2.5 w-[1px] bg-divider-regular'></div>
|
||||
<AddVariable
|
||||
availableVars={availableVars}
|
||||
variableAssignerNodeId={item.variableAssignerNodeId}
|
||||
variableAssignerNodeData={item.variableAssignerNodeData}
|
||||
handleId={item.targetHandleId}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{
|
||||
!item.variables.length && (
|
||||
<div
|
||||
className={cn(
|
||||
'relative flex h-[22px] items-center justify-between space-x-1 rounded-md bg-workflow-block-parma-bg px-1 text-[10px] font-normal uppercase text-text-tertiary',
|
||||
(showSelectedBorder || showSelectionBorder) && '!bg-black/[0.02]',
|
||||
)}
|
||||
>
|
||||
{t(`${i18nPrefix}.varNotSet`)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
!!item.variables.length && (
|
||||
<div className='space-y-0.5'>
|
||||
{
|
||||
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('.')
|
||||
const isException = isExceptionVariable(varName, node?.data.type)
|
||||
|
||||
return (
|
||||
<VariableLabelInNode
|
||||
key={index}
|
||||
variables={variable}
|
||||
nodeType={node?.data.type}
|
||||
nodeTitle={node?.data.title}
|
||||
isExceptionVariable={isException}
|
||||
/>
|
||||
)
|
||||
})
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(NodeGroupItem)
|
||||
@@ -0,0 +1,127 @@
|
||||
import {
|
||||
memo,
|
||||
useMemo,
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import cn from '@/utils/classnames'
|
||||
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 { BubbleX, Env } from '@/app/components/base/icons/src/vender/line/others'
|
||||
import Badge from '@/app/components/base/badge'
|
||||
import type { Node, ValueSelector } from '@/app/components/workflow/types'
|
||||
import { isConversationVar, isENV, isRagVariableVar, isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils'
|
||||
import { InputField } from '@/app/components/base/icons/src/vender/pipeline'
|
||||
|
||||
type NodeVariableItemProps = {
|
||||
node: Node
|
||||
variable: ValueSelector
|
||||
writeMode?: string
|
||||
showBorder?: boolean
|
||||
className?: string
|
||||
isException?: boolean
|
||||
}
|
||||
|
||||
const i18nPrefix = 'workflow.nodes.assigner'
|
||||
|
||||
const NodeVariableItem = ({
|
||||
node,
|
||||
variable,
|
||||
writeMode,
|
||||
showBorder,
|
||||
className,
|
||||
isException,
|
||||
}: NodeVariableItemProps) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const isSystem = isSystemVar(variable)
|
||||
const isEnv = isENV(variable)
|
||||
const isChatVar = isConversationVar(variable)
|
||||
const isRagVar = isRagVariableVar(variable)
|
||||
const varName = useMemo(() => {
|
||||
if(isSystem)
|
||||
return `sys.${variable[variable.length - 1]}`
|
||||
if(isRagVar)
|
||||
return variable[variable.length - 1]
|
||||
return variable.slice(1).join('.')
|
||||
}, [isRagVar, isSystem, variable])
|
||||
|
||||
const VariableIcon = useMemo(() => {
|
||||
if (isEnv) {
|
||||
return (
|
||||
<Env className='h-3.5 w-3.5 shrink-0 text-util-colors-violet-violet-600' />
|
||||
)
|
||||
}
|
||||
|
||||
if (isChatVar) {
|
||||
return (
|
||||
<BubbleX className='h-3.5 w-3.5 shrink-0 text-util-colors-teal-teal-700' />
|
||||
)
|
||||
}
|
||||
|
||||
if(isRagVar) {
|
||||
return (
|
||||
<InputField className='h-3.5 w-3.5 shrink-0 text-text-accent' />
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Variable02
|
||||
className={cn(
|
||||
'h-3.5 w-3.5 shrink-0 text-text-accent',
|
||||
isException && 'text-text-warning',
|
||||
)}
|
||||
/>
|
||||
)
|
||||
}, [isEnv, isChatVar, isRagVar, isException])
|
||||
|
||||
const VariableName = useMemo(() => {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'system-xs-medium ml-0.5 shrink truncate text-text-accent',
|
||||
isEnv && 'text-text-primary',
|
||||
isException && 'text-text-warning',
|
||||
isChatVar && 'text-util-colors-teal-teal-700',
|
||||
)}
|
||||
title={varName}
|
||||
>
|
||||
{varName}
|
||||
</div>
|
||||
)
|
||||
}, [isEnv, isChatVar, varName, isException])
|
||||
return (
|
||||
<div className={cn(
|
||||
'relative flex items-center gap-1 self-stretch rounded-md bg-workflow-block-parma-bg p-[3px] pl-[5px]',
|
||||
showBorder && '!bg-state-base-hover',
|
||||
className,
|
||||
)}>
|
||||
<div className='flex w-0 grow items-center'>
|
||||
{
|
||||
node && (
|
||||
<>
|
||||
<div className='shrink-0 p-[1px]'>
|
||||
<VarBlockIcon
|
||||
className='!text-text-primary'
|
||||
type={node.data.type}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className='mx-0.5 shrink-[1000] truncate text-xs font-medium text-text-secondary'
|
||||
title={node?.data.title}
|
||||
>
|
||||
{node?.data.title}
|
||||
</div>
|
||||
<Line3 className='mr-0.5 shrink-0'></Line3>
|
||||
</>
|
||||
)
|
||||
}
|
||||
{VariableIcon}
|
||||
{VariableName}
|
||||
</div>
|
||||
{writeMode && <Badge className='shrink-0' text={t(`${i18nPrefix}.operations.${writeMode}`)} />}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(NodeVariableItem)
|
||||
@@ -0,0 +1,179 @@
|
||||
'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 {
|
||||
RiDeleteBinLine,
|
||||
} from '@remixicon/react'
|
||||
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 { Folder } from '@/app/components/base/icons/src/vender/line/files'
|
||||
import { checkKeys, replaceSpaceWithUnderscoreInVarNameInput } 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>) => {
|
||||
replaceSpaceWithUnderscoreInVarNameInput(e.target)
|
||||
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 h-3.5 w-3.5' />
|
||||
{(!isEditGroupName)
|
||||
? (
|
||||
<div className='system-sm-semibold flex h-6 cursor-text items-center rounded-lg px-1 text-text-secondary hover:bg-gray-100' onClick={setEditGroupName}>
|
||||
{payload.group_name}
|
||||
</div>
|
||||
)
|
||||
: (
|
||||
<input
|
||||
type='text'
|
||||
className='h-6 rounded-lg border border-gray-300 bg-white px-1 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='ml-0.5 hidden cursor-pointer rounded-md p-1 text-text-tertiary hover:bg-state-destructive-hover hover:text-text-destructive group-hover:block'
|
||||
onClick={onRemove}
|
||||
>
|
||||
<RiDeleteBinLine
|
||||
className='h-4 w-4'
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
: t(`${i18nPrefix}.title`)!}
|
||||
operations={
|
||||
<div className='flex h-6 items-center space-x-2'>
|
||||
{payload.variables.length > 0 && (
|
||||
<div className='system-2xs-medium-uppercase flex h-[18px] items-center rounded-[5px] border border-divider-deep px-1 text-text-tertiary'>{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)
|
||||
@@ -0,0 +1,86 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
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'
|
||||
import { noop } from 'lodash-es'
|
||||
|
||||
type Props = {
|
||||
readonly: boolean
|
||||
nodeId: string
|
||||
list: ValueSelector[]
|
||||
onChange: (list: ValueSelector[], value?: ValueSelector) => void
|
||||
onOpen?: (index: number) => void
|
||||
filterVar?: (payload: Var, valueSelector: ValueSelector) => boolean
|
||||
}
|
||||
|
||||
const VarList: FC<Props> = ({
|
||||
readonly,
|
||||
nodeId,
|
||||
list,
|
||||
onChange,
|
||||
onOpen = noop,
|
||||
filterVar,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const handleVarReferenceChange = useCallback((index: number) => {
|
||||
return (value: ValueSelector | string) => {
|
||||
const newList = produce(list, (draft) => {
|
||||
draft[index] = value as ValueSelector
|
||||
})
|
||||
onChange(newList, value as ValueSelector)
|
||||
}
|
||||
}, [list, onChange])
|
||||
|
||||
const handleVarRemove = useCallback((index: number) => {
|
||||
return () => {
|
||||
const newList = produce(list, (draft) => {
|
||||
draft.splice(index, 1)
|
||||
})
|
||||
onChange(newList)
|
||||
}
|
||||
}, [list, onChange])
|
||||
|
||||
const handleOpen = useCallback((index: number) => {
|
||||
return () => onOpen(index)
|
||||
}, [onOpen])
|
||||
|
||||
if (list.length === 0) {
|
||||
return (
|
||||
<ListNoDataPlaceholder>
|
||||
{t('workflow.nodes.variableAssigner.noVarTip')}
|
||||
</ListNoDataPlaceholder>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='space-y-2'>
|
||||
{list.map((item, index) => (
|
||||
<div className='flex items-center space-x-1' key={index}>
|
||||
<VarReferencePicker
|
||||
readonly={readonly}
|
||||
nodeId={nodeId}
|
||||
isShowNodeName
|
||||
className='grow'
|
||||
value={item}
|
||||
onChange={handleVarReferenceChange(index)}
|
||||
onOpen={handleOpen(index)}
|
||||
filterVar={filterVar}
|
||||
defaultVarKindType={VarKindType.variable}
|
||||
/>
|
||||
{!readonly && (
|
||||
<RemoveButton
|
||||
onClick={handleVarRemove(index)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default React.memo(VarList)
|
||||
@@ -0,0 +1,34 @@
|
||||
import { useCallback } from 'react'
|
||||
import { produce } from 'immer'
|
||||
import type { VariableAssignerNodeType } from '../../types'
|
||||
import type { ValueSelector } from '@/app/components/workflow/types'
|
||||
|
||||
type Params = {
|
||||
id: string
|
||||
inputs: VariableAssignerNodeType
|
||||
setInputs: (newInputs: VariableAssignerNodeType) => void
|
||||
}
|
||||
function useVarList({
|
||||
inputs,
|
||||
setInputs,
|
||||
}: Params) {
|
||||
const handleVarListChange = useCallback((newList: ValueSelector[]) => {
|
||||
const newInputs = produce(inputs, (draft) => {
|
||||
draft.variables = newList
|
||||
})
|
||||
setInputs(newInputs)
|
||||
}, [inputs, setInputs])
|
||||
|
||||
const handleAddVariable = useCallback(() => {
|
||||
const newInputs = produce(inputs, (draft) => {
|
||||
draft.variables.push([])
|
||||
})
|
||||
setInputs(newInputs)
|
||||
}, [inputs, setInputs])
|
||||
return {
|
||||
handleVarListChange,
|
||||
handleAddVariable,
|
||||
}
|
||||
}
|
||||
|
||||
export default useVarList
|
||||
@@ -0,0 +1,56 @@
|
||||
import { type NodeDefault, VarType } from '../../types'
|
||||
import type { VariableAssignerNodeType } from './types'
|
||||
import { genNodeMetaData } from '@/app/components/workflow/utils'
|
||||
import { BlockEnum } from '@/app/components/workflow/types'
|
||||
import { BlockClassificationEnum } from '@/app/components/workflow/block-selector/types'
|
||||
|
||||
const i18nPrefix = 'workflow'
|
||||
|
||||
const metaData = genNodeMetaData({
|
||||
classification: BlockClassificationEnum.Transform,
|
||||
sort: 3,
|
||||
type: BlockEnum.VariableAggregator,
|
||||
})
|
||||
const nodeDefault: NodeDefault<VariableAssignerNodeType> = {
|
||||
metaData,
|
||||
defaultValue: {
|
||||
output_type: VarType.any,
|
||||
variables: [],
|
||||
},
|
||||
checkValid(payload: VariableAssignerNodeType, t: any) {
|
||||
let errorMessages = ''
|
||||
const { variables, advanced_settings } = payload
|
||||
const { group_enabled = false, groups = [] } = advanced_settings || {}
|
||||
// enable group
|
||||
const validateVariables = (variables: any[], field: string) => {
|
||||
variables.forEach((variable) => {
|
||||
if (!variable || variable.length === 0)
|
||||
errorMessages = t(`${i18nPrefix}.errorMsg.fieldRequired`, { field: t(field) })
|
||||
})
|
||||
}
|
||||
|
||||
if (group_enabled) {
|
||||
if (!groups || groups.length === 0) {
|
||||
errorMessages = t(`${i18nPrefix}.errorMsg.fieldRequired`, { field: t(`${i18nPrefix}.nodes.variableAssigner.title`) })
|
||||
}
|
||||
else if (!errorMessages) {
|
||||
groups.forEach((group) => {
|
||||
validateVariables(group.variables || [], `${i18nPrefix}.errorMsg.fields.variableValue`)
|
||||
})
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!variables || variables.length === 0)
|
||||
errorMessages = t(`${i18nPrefix}.errorMsg.fieldRequired`, { field: t(`${i18nPrefix}.nodes.variableAssigner.title`) })
|
||||
else if (!errorMessages)
|
||||
validateVariables(variables, `${i18nPrefix}.errorMsg.fields.variableValue`)
|
||||
}
|
||||
|
||||
return {
|
||||
isValid: !errorMessages,
|
||||
errorMessage: errorMessages,
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
export default nodeDefault
|
||||
@@ -0,0 +1,165 @@
|
||||
import { useCallback } from 'react'
|
||||
import {
|
||||
useStoreApi,
|
||||
} from 'reactflow'
|
||||
import useNodes from '@/app/components/workflow/store/workflow/use-nodes'
|
||||
|
||||
import { uniqBy } from 'lodash-es'
|
||||
import { produce } from 'immer'
|
||||
import {
|
||||
useIsChatMode,
|
||||
useNodeDataUpdate,
|
||||
useWorkflow,
|
||||
useWorkflowVariables,
|
||||
} from '../../hooks'
|
||||
import type {
|
||||
Node,
|
||||
ValueSelector,
|
||||
Var,
|
||||
} from '../../types'
|
||||
import { useWorkflowStore } from '../../store'
|
||||
import type {
|
||||
VarGroupItem,
|
||||
VariableAssignerNodeType,
|
||||
} from './types'
|
||||
|
||||
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 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,
|
||||
handleGroupItemMouseEnter,
|
||||
handleGroupItemMouseLeave,
|
||||
handleAssignVariableValueChange,
|
||||
}
|
||||
}
|
||||
|
||||
export const useGetAvailableVars = () => {
|
||||
const nodes: Node[] = useNodes()
|
||||
const { getBeforeNodesInSameBranchIncludeParent } = useWorkflow()
|
||||
const { getNodeAvailableVars } = useWorkflowVariables()
|
||||
const isChatMode = useIsChatMode()
|
||||
const getAvailableVars = useCallback((nodeId: string, handleId: string, filterVar: (v: Var) => boolean, hideEnv = false) => {
|
||||
const availableNodes: Node[] = []
|
||||
const currentNode = nodes.find(node => node.id === nodeId)!
|
||||
|
||||
if (!currentNode)
|
||||
return []
|
||||
const beforeNodes = getBeforeNodesInSameBranchIncludeParent(nodeId)
|
||||
availableNodes.push(...beforeNodes)
|
||||
const parentNode = nodes.find(node => node.id === currentNode.parentId)
|
||||
|
||||
if (hideEnv) {
|
||||
return getNodeAvailableVars({
|
||||
parentNode,
|
||||
beforeNodes: uniqBy(availableNodes, 'id').filter(node => node.id !== nodeId),
|
||||
isChatMode,
|
||||
hideEnv,
|
||||
hideChatVar: false,
|
||||
filterVar,
|
||||
})
|
||||
.map(node => ({
|
||||
...node,
|
||||
vars: node.isStartNode ? node.vars.filter(v => !v.variable.startsWith('sys.')) : node.vars,
|
||||
}))
|
||||
.filter(item => item.vars.length > 0)
|
||||
}
|
||||
|
||||
return getNodeAvailableVars({
|
||||
parentNode,
|
||||
beforeNodes: uniqBy(availableNodes, 'id').filter(node => node.id !== nodeId),
|
||||
isChatMode,
|
||||
filterVar,
|
||||
})
|
||||
}, [nodes, getBeforeNodesInSameBranchIncludeParent, getNodeAvailableVars, isChatMode])
|
||||
|
||||
return getAvailableVars
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import type { FC } from 'react'
|
||||
import {
|
||||
memo,
|
||||
useMemo,
|
||||
useRef,
|
||||
} from 'react'
|
||||
import type { NodeProps } from 'reactflow'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import NodeGroupItem from './components/node-group-item'
|
||||
import type { VariableAssignerNodeType } from './types'
|
||||
|
||||
const i18nPrefix = 'workflow.nodes.variableAssigner'
|
||||
|
||||
const Node: FC<NodeProps<VariableAssignerNodeType>> = (props) => {
|
||||
const { t } = useTranslation()
|
||||
const ref = useRef<HTMLDivElement>(null)
|
||||
const { id, data } = props
|
||||
const { advanced_settings } = data
|
||||
|
||||
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='relative mb-1 space-y-0.5 px-1' ref={ref}>
|
||||
{
|
||||
groups.map((item) => {
|
||||
return (
|
||||
<NodeGroupItem
|
||||
key={item.title}
|
||||
item={item}
|
||||
/>
|
||||
)
|
||||
})
|
||||
}
|
||||
</div >
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(Node)
|
||||
@@ -0,0 +1,127 @@
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Field from '../_base/components/field'
|
||||
import RemoveEffectVarConfirm from '../_base/components/remove-effect-var-confirm'
|
||||
import useConfig from './use-config'
|
||||
import type { VariableAssignerNodeType } from './types'
|
||||
import VarGroupItem from './components/var-group-item'
|
||||
import cn from '@/utils/classnames'
|
||||
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 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>> = ({
|
||||
id,
|
||||
data,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const {
|
||||
readOnly,
|
||||
inputs,
|
||||
handleListOrTypeChange,
|
||||
isEnableGroup,
|
||||
handleGroupEnabledChange,
|
||||
handleAddGroup,
|
||||
handleListOrTypeChangeInGroup,
|
||||
handleGroupRemoved,
|
||||
handleVarGroupNameChange,
|
||||
isShowRemoveVarConfirm,
|
||||
hideRemoveVarConfirm,
|
||||
onRemoveVarConfirm,
|
||||
getAvailableVars,
|
||||
filterVar,
|
||||
} = useConfig(id, data)
|
||||
|
||||
return (
|
||||
<div className='mt-2'>
|
||||
<div className='space-y-4 px-4 pb-4'>
|
||||
{!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), true)}
|
||||
/>
|
||||
)
|
||||
: (<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), true)}
|
||||
/>
|
||||
{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 />
|
||||
<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>
|
||||
</>
|
||||
)}
|
||||
<RemoveEffectVarConfirm
|
||||
isShow={isShowRemoveVarConfirm}
|
||||
onCancel={hideRemoveVarConfirm}
|
||||
onConfirm={onRemoveVarConfirm}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(Panel)
|
||||
@@ -0,0 +1,15 @@
|
||||
import type { CommonNodeType, ValueSelector, VarType } from '@/app/components/workflow/types'
|
||||
|
||||
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)[]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
import { useCallback, useRef, useState } from 'react'
|
||||
import { produce } from 'immer'
|
||||
import { useBoolean, useDebounceFn } 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 } from './hooks'
|
||||
import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud'
|
||||
|
||||
import {
|
||||
useNodesReadOnly,
|
||||
useWorkflow,
|
||||
} from '@/app/components/workflow/hooks'
|
||||
import useInspectVarsCrud from '../../hooks/use-inspect-vars-crud'
|
||||
|
||||
const useConfig = (id: string, payload: VariableAssignerNodeType) => {
|
||||
const {
|
||||
deleteNodeInspectorVars,
|
||||
renameInspectVarName,
|
||||
} = useInspectVarsCrud()
|
||||
const { nodesReadOnly: readOnly } = useNodesReadOnly()
|
||||
const { handleOutVarRenameChange, isVarUsedInNodes, removeUsedVarInNodes } = useWorkflow()
|
||||
|
||||
const { inputs, setInputs } = useNodeCrud<VariableAssignerNodeType>(id, payload)
|
||||
const isEnableGroup = !!inputs.advanced_settings?.group_enabled
|
||||
|
||||
// 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)
|
||||
}
|
||||
}, [inputs, setInputs])
|
||||
|
||||
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)
|
||||
deleteNodeInspectorVars(id)
|
||||
}, [deleteNodeInspectorVars, handleOutVarRenameChange, id, inputs, isVarUsedInNodes, setInputs, showRemoveVarConfirm])
|
||||
|
||||
const handleAddGroup = useCallback(() => {
|
||||
let maxInGroupName = 1
|
||||
inputs.advanced_settings.groups.forEach((item) => {
|
||||
const match = /(\d+)$/.exec(item.group_name)
|
||||
if (match) {
|
||||
const num = Number.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)
|
||||
deleteNodeInspectorVars(id)
|
||||
}, [deleteNodeInspectorVars, id, inputs, setInputs])
|
||||
|
||||
// record the first old name value
|
||||
const oldNameRecord = useRef<Record<string, string>>({})
|
||||
|
||||
const {
|
||||
run: renameInspectNameWithDebounce,
|
||||
} = useDebounceFn(
|
||||
(id: string, newName: string) => {
|
||||
const oldName = oldNameRecord.current[id]
|
||||
renameInspectVarName(id, oldName, newName)
|
||||
delete oldNameRecord.current[id]
|
||||
},
|
||||
{ wait: 500 },
|
||||
)
|
||||
|
||||
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)
|
||||
if(!(id in oldNameRecord.current))
|
||||
oldNameRecord.current[id] = inputs.advanced_settings.groups[index].group_name
|
||||
renameInspectNameWithDebounce(id, name)
|
||||
}
|
||||
}, [handleOutVarRenameChange, id, inputs, renameInspectNameWithDebounce, setInputs])
|
||||
|
||||
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])
|
||||
|
||||
return {
|
||||
readOnly,
|
||||
inputs,
|
||||
handleListOrTypeChange,
|
||||
isEnableGroup,
|
||||
handleGroupEnabledChange,
|
||||
handleAddGroup,
|
||||
handleListOrTypeChangeInGroup,
|
||||
handleGroupRemoved,
|
||||
handleVarGroupNameChange,
|
||||
isShowRemoveVarConfirm,
|
||||
hideRemoveVarConfirm,
|
||||
onRemoveVarConfirm,
|
||||
getAvailableVars,
|
||||
filterVar,
|
||||
}
|
||||
}
|
||||
|
||||
export default useConfig
|
||||
@@ -0,0 +1,92 @@
|
||||
import type { RefObject } from 'react'
|
||||
import type { InputVar, ValueSelector, Variable } from '@/app/components/workflow/types'
|
||||
import { useCallback } from 'react'
|
||||
import type { VariableAssignerNodeType } from './types'
|
||||
|
||||
type Params = {
|
||||
id: string,
|
||||
payload: VariableAssignerNodeType,
|
||||
runInputData: Record<string, any>
|
||||
runInputDataRef: RefObject<Record<string, any>>
|
||||
getInputVars: (textList: string[]) => InputVar[]
|
||||
setRunInputData: (data: Record<string, any>) => void
|
||||
toVarInputs: (variables: Variable[]) => InputVar[]
|
||||
varSelectorsToVarInputs: (variables: ValueSelector[]) => InputVar[]
|
||||
}
|
||||
const useSingleRunFormParams = ({
|
||||
payload,
|
||||
runInputData,
|
||||
setRunInputData,
|
||||
varSelectorsToVarInputs,
|
||||
}: Params) => {
|
||||
const setInputVarValues = useCallback((newPayload: Record<string, any>) => {
|
||||
setRunInputData(newPayload)
|
||||
}, [setRunInputData])
|
||||
const inputVarValues = (() => {
|
||||
const vars: Record<string, any> = {}
|
||||
Object.keys(runInputData)
|
||||
.forEach((key) => {
|
||||
vars[key] = runInputData[key]
|
||||
})
|
||||
return vars
|
||||
})()
|
||||
|
||||
const forms = (() => {
|
||||
const allInputs: ValueSelector[] = []
|
||||
const isGroupEnabled = !!payload.advanced_settings?.group_enabled
|
||||
if (!isGroupEnabled && payload.variables && payload.variables.length) {
|
||||
payload.variables.forEach((varSelector) => {
|
||||
allInputs.push(varSelector)
|
||||
})
|
||||
}
|
||||
if (isGroupEnabled && payload.advanced_settings && payload.advanced_settings.groups && payload.advanced_settings.groups.length) {
|
||||
payload.advanced_settings.groups.forEach((group) => {
|
||||
group.variables?.forEach((varSelector) => {
|
||||
allInputs.push(varSelector)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const varInputs = varSelectorsToVarInputs(allInputs)
|
||||
// remove duplicate inputs
|
||||
const existVarsKey: Record<string, boolean> = {}
|
||||
const uniqueVarInputs: InputVar[] = []
|
||||
varInputs.forEach((input) => {
|
||||
if(!input)
|
||||
return
|
||||
if (!existVarsKey[input.variable]) {
|
||||
existVarsKey[input.variable] = true
|
||||
uniqueVarInputs.push({
|
||||
...input,
|
||||
required: false, // just one of the inputs is required
|
||||
})
|
||||
}
|
||||
})
|
||||
return [
|
||||
{
|
||||
inputs: uniqueVarInputs,
|
||||
values: inputVarValues,
|
||||
onChange: setInputVarValues,
|
||||
},
|
||||
]
|
||||
})()
|
||||
|
||||
const getDependentVars = () => {
|
||||
if(payload.advanced_settings?.group_enabled) {
|
||||
const vars: ValueSelector[][] = []
|
||||
payload.advanced_settings.groups.forEach((group) => {
|
||||
if(group.variables)
|
||||
vars.push([...group.variables])
|
||||
})
|
||||
return vars
|
||||
}
|
||||
return [payload.variables]
|
||||
}
|
||||
|
||||
return {
|
||||
forms,
|
||||
getDependentVars,
|
||||
}
|
||||
}
|
||||
|
||||
export default useSingleRunFormParams
|
||||
@@ -0,0 +1,16 @@
|
||||
import type { Var } from '../../types'
|
||||
import { VarType } from '../../types'
|
||||
|
||||
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