mirror of
http://112.124.100.131/huang.ze/ebiz-dify-ai.git
synced 2025-12-16 06:16: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,123 @@
|
||||
import {
|
||||
memo,
|
||||
useCallback,
|
||||
useMemo,
|
||||
useRef,
|
||||
} from 'react'
|
||||
import { useClickAway } from 'ahooks'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useStore } from '../../../store'
|
||||
import {
|
||||
useIsChatMode,
|
||||
useNodeDataUpdate,
|
||||
useWorkflow,
|
||||
} from '../../../hooks'
|
||||
import type {
|
||||
ValueSelector,
|
||||
Var,
|
||||
VarType,
|
||||
} from '../../../types'
|
||||
import { useVariableAssigner } from '../../variable-assigner/hooks'
|
||||
import { filterVar } from '../../variable-assigner/utils'
|
||||
import AddVariablePopup from './add-variable-popup'
|
||||
import { toNodeAvailableVars } from '@/app/components/workflow/nodes/_base/components/variable/utils'
|
||||
|
||||
type AddVariablePopupWithPositionProps = {
|
||||
nodeId: string
|
||||
nodeData: any
|
||||
}
|
||||
const AddVariablePopupWithPosition = ({
|
||||
nodeId,
|
||||
nodeData,
|
||||
}: AddVariablePopupWithPositionProps) => {
|
||||
const { t } = useTranslation()
|
||||
const ref = useRef(null)
|
||||
const showAssignVariablePopup = useStore(s => s.showAssignVariablePopup)
|
||||
const setShowAssignVariablePopup = useStore(s => s.setShowAssignVariablePopup)
|
||||
const { handleNodeDataUpdate } = useNodeDataUpdate()
|
||||
const { handleAddVariableInAddVariablePopupWithPosition } = useVariableAssigner()
|
||||
const isChatMode = useIsChatMode()
|
||||
const { getBeforeNodesInSameBranch } = useWorkflow()
|
||||
|
||||
const outputType = useMemo(() => {
|
||||
if (!showAssignVariablePopup)
|
||||
return ''
|
||||
|
||||
if (showAssignVariablePopup.variableAssignerNodeHandleId === 'target')
|
||||
return showAssignVariablePopup.variableAssignerNodeData.output_type
|
||||
|
||||
const group = showAssignVariablePopup.variableAssignerNodeData.advanced_settings?.groups.find(group => group.groupId === showAssignVariablePopup.variableAssignerNodeHandleId)
|
||||
return group?.output_type || ''
|
||||
}, [showAssignVariablePopup])
|
||||
const availableVars = useMemo(() => {
|
||||
if (!showAssignVariablePopup)
|
||||
return []
|
||||
|
||||
return toNodeAvailableVars({
|
||||
parentNode: showAssignVariablePopup.parentNode,
|
||||
t,
|
||||
beforeNodes: [
|
||||
...getBeforeNodesInSameBranch(showAssignVariablePopup.nodeId),
|
||||
{
|
||||
id: showAssignVariablePopup.nodeId,
|
||||
data: showAssignVariablePopup.nodeData,
|
||||
} as any,
|
||||
],
|
||||
isChatMode,
|
||||
filterVar: filterVar(outputType as VarType),
|
||||
})
|
||||
}, [getBeforeNodesInSameBranch, isChatMode, showAssignVariablePopup, t, outputType])
|
||||
|
||||
useClickAway(() => {
|
||||
if (nodeData._holdAddVariablePopup) {
|
||||
handleNodeDataUpdate({
|
||||
id: nodeId,
|
||||
data: {
|
||||
_holdAddVariablePopup: false,
|
||||
},
|
||||
})
|
||||
}
|
||||
else {
|
||||
handleNodeDataUpdate({
|
||||
id: nodeId,
|
||||
data: {
|
||||
_showAddVariablePopup: false,
|
||||
},
|
||||
})
|
||||
setShowAssignVariablePopup(undefined)
|
||||
}
|
||||
}, ref)
|
||||
|
||||
const handleAddVariable = useCallback((value: ValueSelector, varDetail: Var) => {
|
||||
if (showAssignVariablePopup) {
|
||||
handleAddVariableInAddVariablePopupWithPosition(
|
||||
showAssignVariablePopup.nodeId,
|
||||
showAssignVariablePopup.variableAssignerNodeId,
|
||||
showAssignVariablePopup.variableAssignerNodeHandleId,
|
||||
value,
|
||||
varDetail,
|
||||
)
|
||||
}
|
||||
}, [showAssignVariablePopup, handleAddVariableInAddVariablePopupWithPosition])
|
||||
|
||||
if (!showAssignVariablePopup)
|
||||
return null
|
||||
|
||||
return (
|
||||
<div
|
||||
className='absolute z-10'
|
||||
style={{
|
||||
left: showAssignVariablePopup.x,
|
||||
top: showAssignVariablePopup.y,
|
||||
}}
|
||||
ref={ref}
|
||||
>
|
||||
<AddVariablePopup
|
||||
availableVars={availableVars}
|
||||
onSelect={handleAddVariable}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(AddVariablePopupWithPosition)
|
||||
@@ -0,0 +1,36 @@
|
||||
import { memo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import VarReferenceVars from '@/app/components/workflow/nodes/_base/components/variable/var-reference-vars'
|
||||
import type {
|
||||
NodeOutPutVar,
|
||||
ValueSelector,
|
||||
Var,
|
||||
} from '@/app/components/workflow/types'
|
||||
|
||||
export type AddVariablePopupProps = {
|
||||
availableVars: NodeOutPutVar[]
|
||||
onSelect: (value: ValueSelector, item: Var) => void
|
||||
}
|
||||
export const AddVariablePopup = ({
|
||||
availableVars,
|
||||
onSelect,
|
||||
}: AddVariablePopupProps) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div className='w-[240px] bg-white border-[0.5px] border-gray-200 rounded-lg shadow-lg'>
|
||||
<div className='flex items-center px-4 h-[34px] text-[13px] font-semibold text-gray-700 border-b-[0.5px] border-b-gray-200'>
|
||||
{t('workflow.nodes.variableAssigner.setAssignVariable')}
|
||||
</div>
|
||||
<div className='p-1'>
|
||||
<VarReferenceVars
|
||||
hideSearch
|
||||
vars={availableVars}
|
||||
onChange={onSelect}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(AddVariablePopup)
|
||||
@@ -7,6 +7,7 @@ import type { InputVar } from '../../../../types'
|
||||
import { BlockEnum, InputVarType } from '../../../../types'
|
||||
import CodeEditor from '../editor/code-editor'
|
||||
import { CodeLanguage } from '../../../code/types'
|
||||
import TextEditor from '../editor/text-editor'
|
||||
import Select from '@/app/components/base/select'
|
||||
import TextGenerationImageUploader from '@/app/components/base/image-uploader/text-generation-image-uploader'
|
||||
import { Resolution } from '@/types/app'
|
||||
@@ -34,7 +35,7 @@ const FormItem: FC<Props> = ({
|
||||
const { t } = useTranslation()
|
||||
const { type } = payload
|
||||
const fileSettings = useFeatures(s => s.features.file)
|
||||
const handleContextItemChange = useCallback((index: number) => {
|
||||
const handleArrayItemChange = useCallback((index: number) => {
|
||||
return (newValue: any) => {
|
||||
const newValues = produce(value, (draft: any) => {
|
||||
draft[index] = newValue
|
||||
@@ -43,7 +44,7 @@ const FormItem: FC<Props> = ({
|
||||
}
|
||||
}, [value, onChange])
|
||||
|
||||
const handleContextItemRemove = useCallback((index: number) => {
|
||||
const handleArrayItemRemove = useCallback((index: number) => {
|
||||
return () => {
|
||||
const newValues = produce(value, (draft: any) => {
|
||||
draft.splice(index, 1)
|
||||
@@ -77,9 +78,13 @@ const FormItem: FC<Props> = ({
|
||||
}
|
||||
return ''
|
||||
})()
|
||||
|
||||
const isArrayLikeType = [InputVarType.contexts, InputVarType.iterator].includes(type)
|
||||
const isContext = type === InputVarType.contexts
|
||||
const isIterator = type === InputVarType.iterator
|
||||
return (
|
||||
<div className={`${className}`}>
|
||||
{type !== InputVarType.contexts && <div className='h-8 leading-8 text-[13px] font-medium text-gray-700 truncate'>{typeof payload.label === 'object' ? nodeKey : payload.label}</div>}
|
||||
{!isArrayLikeType && <div className='h-8 leading-8 text-[13px] font-medium text-gray-700 truncate'>{typeof payload.label === 'object' ? nodeKey : payload.label}</div>}
|
||||
<div className='grow'>
|
||||
{
|
||||
type === InputVarType.textInput && (
|
||||
@@ -160,7 +165,7 @@ const FormItem: FC<Props> = ({
|
||||
}
|
||||
|
||||
{
|
||||
type === InputVarType.contexts && (
|
||||
isContext && (
|
||||
<div className='space-y-2'>
|
||||
{(value || []).map((item: any, index: number) => (
|
||||
<CodeEditor
|
||||
@@ -170,13 +175,37 @@ const FormItem: FC<Props> = ({
|
||||
headerRight={
|
||||
(value as any).length > 1
|
||||
? (<Trash03
|
||||
onClick={handleContextItemRemove(index)}
|
||||
onClick={handleArrayItemRemove(index)}
|
||||
className='mr-1 w-3.5 h-3.5 text-gray-500 cursor-pointer'
|
||||
/>)
|
||||
: undefined
|
||||
}
|
||||
language={CodeLanguage.json}
|
||||
onChange={handleContextItemChange(index)}
|
||||
onChange={handleArrayItemChange(index)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
isIterator && (
|
||||
<div className='space-y-2'>
|
||||
{(value || []).map((item: any, index: number) => (
|
||||
<TextEditor
|
||||
key={index}
|
||||
isInNode
|
||||
value={item}
|
||||
title={<span>{t('appDebug.variableConig.content')} {index + 1} </span>}
|
||||
onChange={handleArrayItemChange(index)}
|
||||
headerRight={
|
||||
(value as any).length > 1
|
||||
? (<Trash03
|
||||
onClick={handleArrayItemRemove(index)}
|
||||
className='mr-1 w-3.5 h-3.5 text-gray-500 cursor-pointer'
|
||||
/>)
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -32,20 +32,22 @@ const Form: FC<Props> = ({
|
||||
onChange(newValues)
|
||||
}
|
||||
}, [values, onChange])
|
||||
|
||||
const isArrayLikeType = [InputVarType.contexts, InputVarType.iterator].includes(inputs[0]?.type)
|
||||
const isContext = inputs[0]?.type === InputVarType.contexts
|
||||
const handleAddContext = useCallback(() => {
|
||||
const newValues = produce(values, (draft: any) => {
|
||||
const key = inputs[0].variable
|
||||
draft[key].push(RETRIEVAL_OUTPUT_STRUCT)
|
||||
draft[key].push(isContext ? RETRIEVAL_OUTPUT_STRUCT : '')
|
||||
})
|
||||
onChange(newValues)
|
||||
}, [values, onChange, inputs])
|
||||
}, [values, onChange, inputs, isContext])
|
||||
|
||||
return (
|
||||
<div className={cn(className, 'space-y-2')}>
|
||||
{label && (
|
||||
<div className='mb-1 flex items-center justify-between'>
|
||||
<div className='flex items-center h-6 text-xs font-medium text-gray-500 uppercase'>{label}</div>
|
||||
{inputs[0]?.type === InputVarType.contexts && (
|
||||
{isArrayLikeType && (
|
||||
<AddButton onClick={handleAddContext} />
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -46,6 +46,9 @@ const Base: FC<Props> = ({
|
||||
const handleCopy = useCallback(() => {
|
||||
copy(value)
|
||||
setIsCopied(true)
|
||||
setTimeout(() => {
|
||||
setIsCopied(false)
|
||||
}, 2000)
|
||||
}, [value])
|
||||
|
||||
return (
|
||||
|
||||
@@ -3,11 +3,14 @@ import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import cn from 'classnames'
|
||||
import { useBoolean } from 'ahooks'
|
||||
import type { DefaultTFuncReturn } from 'i18next'
|
||||
import { HelpCircle } from '@/app/components/base/icons/src/vender/line/general'
|
||||
import TooltipPlus from '@/app/components/base/tooltip-plus'
|
||||
import { ChevronRight } from '@/app/components/base/icons/src/vender/line/arrows'
|
||||
|
||||
type Props = {
|
||||
title: string
|
||||
className?: string
|
||||
title: JSX.Element | string | DefaultTFuncReturn
|
||||
tooltip?: string
|
||||
supportFold?: boolean
|
||||
children?: JSX.Element | string | null
|
||||
@@ -16,6 +19,7 @@ type Props = {
|
||||
}
|
||||
|
||||
const Filed: FC<Props> = ({
|
||||
className,
|
||||
title,
|
||||
tooltip,
|
||||
children,
|
||||
@@ -27,7 +31,7 @@ const Filed: FC<Props> = ({
|
||||
toggle: toggleFold,
|
||||
}] = useBoolean(true)
|
||||
return (
|
||||
<div className={cn(inline && 'flex justify-between items-center', supportFold && 'cursor-pointer')}>
|
||||
<div className={cn(className, inline && 'flex justify-between items-center', supportFold && 'cursor-pointer')}>
|
||||
<div
|
||||
onClick={() => supportFold && toggleFold()}
|
||||
className='flex justify-between items-center'>
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
|
||||
type Props = {
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
const ListNoDataPlaceholder: FC<Props> = ({
|
||||
children,
|
||||
}) => {
|
||||
return (
|
||||
<div className='flex rounded-md bg-gray-50 items-center min-h-[42px] justify-center leading-[18px] text-xs font-normal text-gray-500'>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default React.memo(ListNoDataPlaceholder)
|
||||
@@ -11,7 +11,7 @@ import type {
|
||||
import BlockIcon from '@/app/components/workflow/block-icon'
|
||||
import BlockSelector from '@/app/components/workflow/block-selector'
|
||||
import {
|
||||
useNodesExtraData,
|
||||
useAvailableBlocks,
|
||||
useNodesInteractions,
|
||||
useNodesReadOnly,
|
||||
useToolIcon,
|
||||
@@ -33,10 +33,12 @@ const Item = ({
|
||||
const { t } = useTranslation()
|
||||
const { handleNodeChange } = useNodesInteractions()
|
||||
const { nodesReadOnly } = useNodesReadOnly()
|
||||
const nodesExtraData = useNodesExtraData()
|
||||
const toolIcon = useToolIcon(data)
|
||||
const availablePrevNodes = nodesExtraData[data.type].availablePrevNodes
|
||||
const availableNextNodes = nodesExtraData[data.type].availableNextNodes
|
||||
const {
|
||||
availablePrevBlocks,
|
||||
availableNextBlocks,
|
||||
} = useAvailableBlocks(data.type, data.isInIteration)
|
||||
|
||||
const handleSelect = useCallback<OnSelectBlock>((type, toolDefaultValue) => {
|
||||
handleNodeChange(nodeId, type, sourceHandle, toolDefaultValue)
|
||||
}, [nodeId, sourceHandle, handleNodeChange])
|
||||
@@ -84,7 +86,7 @@ const Item = ({
|
||||
}}
|
||||
trigger={renderTrigger}
|
||||
popupClassName='!w-[328px]'
|
||||
availableBlocksTypes={intersection(availablePrevNodes, availableNextNodes).filter(item => item !== data.type)}
|
||||
availableBlocksTypes={intersection(availablePrevBlocks, availableNextBlocks).filter(item => item !== data.type)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ import type { Node } from '../../../types'
|
||||
import BlockSelector from '../../../block-selector'
|
||||
import type { ToolDefaultValue } from '../../../block-selector/types'
|
||||
import {
|
||||
useNodesExtraData,
|
||||
useAvailableBlocks,
|
||||
useNodesInteractions,
|
||||
useNodesReadOnly,
|
||||
} from '../../../hooks'
|
||||
@@ -35,11 +35,12 @@ export const NodeTargetHandle = memo(({
|
||||
}: NodeHandleProps) => {
|
||||
const [open, setOpen] = useState(false)
|
||||
const { handleNodeAdd } = useNodesInteractions()
|
||||
const nodesExtraData = useNodesExtraData()
|
||||
const { getNodesReadOnly } = useNodesReadOnly()
|
||||
const connected = data._connectedTargetHandleIds?.includes(handleId)
|
||||
const availablePrevNodes = nodesExtraData[data.type].availablePrevNodes
|
||||
const isConnectable = !!availablePrevNodes.length
|
||||
const { availablePrevBlocks } = useAvailableBlocks(data.type, data.isInIteration)
|
||||
const isConnectable = !!availablePrevBlocks.length && (
|
||||
!data.isIterationStart
|
||||
)
|
||||
|
||||
const handleOpenChange = useCallback((v: boolean) => {
|
||||
setOpen(v)
|
||||
@@ -80,7 +81,7 @@ export const NodeTargetHandle = memo(({
|
||||
onClick={handleHandleClick}
|
||||
>
|
||||
{
|
||||
!connected && isConnectable && !data._isInvalidConnection && !getNodesReadOnly() && (
|
||||
!connected && isConnectable && !getNodesReadOnly() && (
|
||||
<BlockSelector
|
||||
open={open}
|
||||
onOpenChange={handleOpenChange}
|
||||
@@ -94,7 +95,7 @@ export const NodeTargetHandle = memo(({
|
||||
${data.selected && '!flex'}
|
||||
${open && '!flex'}
|
||||
`}
|
||||
availableBlocksTypes={availablePrevNodes}
|
||||
availableBlocksTypes={availablePrevBlocks}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -112,12 +113,14 @@ export const NodeSourceHandle = memo(({
|
||||
nodeSelectorClassName,
|
||||
}: NodeHandleProps) => {
|
||||
const notInitialWorkflow = useStore(s => s.notInitialWorkflow)
|
||||
const connectingNodePayload = useStore(s => s.connectingNodePayload)
|
||||
const [open, setOpen] = useState(false)
|
||||
const { handleNodeAdd } = useNodesInteractions()
|
||||
const nodesExtraData = useNodesExtraData()
|
||||
const { getNodesReadOnly } = useNodesReadOnly()
|
||||
const availableNextNodes = nodesExtraData[data.type].availableNextNodes
|
||||
const isConnectable = !!availableNextNodes.length
|
||||
const { availableNextBlocks } = useAvailableBlocks(data.type, data.isInIteration)
|
||||
const isUnConnectable = !availableNextBlocks.length || ((connectingNodePayload?.nodeType === BlockEnum.VariableAssigner || connectingNodePayload?.nodeType === BlockEnum.VariableAggregator) && connectingNodePayload?.handleType === 'target')
|
||||
const isConnectable = !isUnConnectable
|
||||
|
||||
const connected = data._connectedSourceHandleIds?.includes(handleId)
|
||||
const handleOpenChange = useCallback((v: boolean) => {
|
||||
setOpen(v)
|
||||
@@ -162,7 +165,7 @@ export const NodeSourceHandle = memo(({
|
||||
onClick={handleHandleClick}
|
||||
>
|
||||
{
|
||||
!connected && isConnectable && !data._isInvalidConnection && !getNodesReadOnly() && (
|
||||
!connected && isConnectable && !getNodesReadOnly() && (
|
||||
<BlockSelector
|
||||
open={open}
|
||||
onOpenChange={handleOpenChange}
|
||||
@@ -175,7 +178,7 @@ export const NodeSourceHandle = memo(({
|
||||
${data.selected && '!flex'}
|
||||
${open && '!flex'}
|
||||
`}
|
||||
availableBlocksTypes={availableNextNodes}
|
||||
availableBlocksTypes={availableNextBlocks}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import {
|
||||
memo,
|
||||
useCallback,
|
||||
} from 'react'
|
||||
import cn from 'classnames'
|
||||
import type { OnResize } from 'reactflow'
|
||||
import { NodeResizeControl } from 'reactflow'
|
||||
import { useNodesInteractions } from '../../../hooks'
|
||||
import type { CommonNodeType } from '../../../types'
|
||||
|
||||
const Icon = () => {
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="none">
|
||||
<path d="M5.19009 11.8398C8.26416 10.6196 10.7144 8.16562 11.9297 5.08904" stroke="black" strokeOpacity="0.16" strokeWidth="2" strokeLinecap="round"/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
type NodeResizerProps = {
|
||||
nodeId: string
|
||||
nodeData: CommonNodeType
|
||||
}
|
||||
const NodeResizer = ({
|
||||
nodeId,
|
||||
nodeData,
|
||||
}: NodeResizerProps) => {
|
||||
const { handleNodeResize } = useNodesInteractions()
|
||||
|
||||
const handleResize = useCallback<OnResize>((_, params) => {
|
||||
handleNodeResize(nodeId, params)
|
||||
}, [nodeId, handleNodeResize])
|
||||
|
||||
return (
|
||||
<div className={cn(
|
||||
'hidden group-hover:block',
|
||||
nodeData.selected && '!block',
|
||||
)}>
|
||||
<NodeResizeControl
|
||||
position='bottom-right'
|
||||
className='!border-none !bg-transparent'
|
||||
onResize={handleResize}
|
||||
minWidth={272}
|
||||
minHeight={176}
|
||||
>
|
||||
<div className='absolute bottom-[1px] right-[1px]'><Icon /></div>
|
||||
</NodeResizeControl>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(NodeResizer)
|
||||
@@ -7,38 +7,39 @@ import { useTranslation } from 'react-i18next'
|
||||
import { intersection } from 'lodash-es'
|
||||
import BlockSelector from '@/app/components/workflow/block-selector'
|
||||
import {
|
||||
useNodesExtraData,
|
||||
useAvailableBlocks,
|
||||
useNodesInteractions,
|
||||
} from '@/app/components/workflow/hooks'
|
||||
import type {
|
||||
BlockEnum,
|
||||
Node,
|
||||
OnSelectBlock,
|
||||
} from '@/app/components/workflow/types'
|
||||
|
||||
type ChangeBlockProps = {
|
||||
nodeId: string
|
||||
nodeType: BlockEnum
|
||||
nodeData: Node['data']
|
||||
sourceHandle: string
|
||||
}
|
||||
const ChangeBlock = ({
|
||||
nodeId,
|
||||
nodeType,
|
||||
nodeData,
|
||||
sourceHandle,
|
||||
}: ChangeBlockProps) => {
|
||||
const { t } = useTranslation()
|
||||
const { handleNodeChange } = useNodesInteractions()
|
||||
const nodesExtraData = useNodesExtraData()
|
||||
const availablePrevNodes = nodesExtraData[nodeType].availablePrevNodes
|
||||
const availableNextNodes = nodesExtraData[nodeType].availableNextNodes
|
||||
const {
|
||||
availablePrevBlocks,
|
||||
availableNextBlocks,
|
||||
} = useAvailableBlocks(nodeData.type, nodeData.isInIteration)
|
||||
|
||||
const availableNodes = useMemo(() => {
|
||||
if (availableNextNodes.length && availableNextNodes.length)
|
||||
return intersection(availablePrevNodes, availableNextNodes)
|
||||
else if (availablePrevNodes.length)
|
||||
return availablePrevNodes
|
||||
if (availablePrevBlocks.length && availableNextBlocks.length)
|
||||
return intersection(availablePrevBlocks, availableNextBlocks)
|
||||
else if (availablePrevBlocks.length)
|
||||
return availablePrevBlocks
|
||||
else
|
||||
return availableNextNodes
|
||||
}, [availablePrevNodes, availableNextNodes])
|
||||
return availableNextBlocks
|
||||
}, [availablePrevBlocks, availableNextBlocks])
|
||||
|
||||
const handleSelect = useCallback<OnSelectBlock>((type, toolDefaultValue) => {
|
||||
handleNodeChange(nodeId, type, sourceHandle, toolDefaultValue)
|
||||
|
||||
@@ -20,6 +20,7 @@ import ShortcutsName from '@/app/components/workflow/shortcuts-name'
|
||||
import type { Node } from '@/app/components/workflow/types'
|
||||
import { BlockEnum } from '@/app/components/workflow/types'
|
||||
import { useGetLanguage } from '@/context/i18n'
|
||||
import { CollectionType } from '@/app/components/tools/types'
|
||||
|
||||
type PanelOperatorPopupProps = {
|
||||
id: string
|
||||
@@ -46,28 +47,35 @@ const PanelOperatorPopup = ({
|
||||
const nodesExtraData = useNodesExtraData()
|
||||
const buildInTools = useStore(s => s.buildInTools)
|
||||
const customTools = useStore(s => s.customTools)
|
||||
const workflowTools = useStore(s => s.workflowTools)
|
||||
const edge = edges.find(edge => edge.target === id)
|
||||
const author = useMemo(() => {
|
||||
if (data.type !== BlockEnum.Tool)
|
||||
return nodesExtraData[data.type].author
|
||||
|
||||
if (data.provider_type === 'builtin')
|
||||
if (data.provider_type === CollectionType.builtIn)
|
||||
return buildInTools.find(toolWithProvider => toolWithProvider.id === data.provider_id)?.author
|
||||
|
||||
if (data.provider_type === CollectionType.workflow)
|
||||
return workflowTools.find(toolWithProvider => toolWithProvider.id === data.provider_id)?.author
|
||||
|
||||
return customTools.find(toolWithProvider => toolWithProvider.id === data.provider_id)?.author
|
||||
}, [data, nodesExtraData, buildInTools, customTools])
|
||||
}, [data, nodesExtraData, buildInTools, customTools, workflowTools])
|
||||
|
||||
const about = useMemo(() => {
|
||||
if (data.type !== BlockEnum.Tool)
|
||||
return nodesExtraData[data.type].about
|
||||
|
||||
if (data.provider_type === 'builtin')
|
||||
if (data.provider_type === CollectionType.builtIn)
|
||||
return buildInTools.find(toolWithProvider => toolWithProvider.id === data.provider_id)?.description[language]
|
||||
|
||||
return customTools.find(toolWithProvider => toolWithProvider.id === data.provider_id)?.description[language]
|
||||
}, [data, nodesExtraData, language, buildInTools, customTools])
|
||||
if (data.provider_type === CollectionType.workflow)
|
||||
return workflowTools.find(toolWithProvider => toolWithProvider.id === data.provider_id)?.description[language]
|
||||
|
||||
const showChangeBlock = data.type !== BlockEnum.Start && !nodesReadOnly
|
||||
return customTools.find(toolWithProvider => toolWithProvider.id === data.provider_id)?.description[language]
|
||||
}, [data, nodesExtraData, language, buildInTools, customTools, workflowTools])
|
||||
|
||||
const showChangeBlock = data.type !== BlockEnum.Start && !nodesReadOnly && data.type !== BlockEnum.Iteration
|
||||
|
||||
return (
|
||||
<div className='w-[240px] border-[0.5px] border-gray-200 rounded-lg shadow-xl bg-white'>
|
||||
@@ -97,7 +105,7 @@ const PanelOperatorPopup = ({
|
||||
showChangeBlock && (
|
||||
<ChangeBlock
|
||||
nodeId={id}
|
||||
nodeType={data.type}
|
||||
nodeData={data}
|
||||
sourceHandle={edge?.sourceHandle || 'source'}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -18,8 +18,8 @@ const ReadonlyInputWithSelectVar: FC<Props> = ({
|
||||
nodeId,
|
||||
value,
|
||||
}) => {
|
||||
const { getBeforeNodesInSameBranch } = useWorkflow()
|
||||
const availableNodes = getBeforeNodesInSameBranch(nodeId)
|
||||
const { getBeforeNodesInSameBranchIncludeParent } = useWorkflow()
|
||||
const availableNodes = getBeforeNodesInSameBranchIncludeParent(nodeId)
|
||||
const startNode = availableNodes.find((node: any) => {
|
||||
return node.data.type === BlockEnum.Start
|
||||
})
|
||||
|
||||
@@ -11,6 +11,8 @@ import type { QuestionClassifierNodeType } from '../../../question-classifier/ty
|
||||
import type { HttpNodeType } from '../../../http/types'
|
||||
import { VarType as ToolVarType } from '../../../tool/types'
|
||||
import type { ToolNodeType } from '../../../tool/types'
|
||||
import type { ParameterExtractorNodeType } from '../../../parameter-extractor/types'
|
||||
import type { IterationNodeType } from '../../../iteration/types'
|
||||
import { BlockEnum, InputVarType, VarType } from '@/app/components/workflow/types'
|
||||
import type { StartNodeType } from '@/app/components/workflow/nodes/start/types'
|
||||
import type { Node, NodeOutPutVar, ValueSelector, Var } from '@/app/components/workflow/types'
|
||||
@@ -19,6 +21,7 @@ import {
|
||||
HTTP_REQUEST_OUTPUT_STRUCT,
|
||||
KNOWLEDGE_RETRIEVAL_OUTPUT_STRUCT,
|
||||
LLM_OUTPUT_STRUCT,
|
||||
PARAMETER_EXTRACTOR_COMMON_STRUCT,
|
||||
QUESTION_CLASSIFIER_OUTPUT_STRUCT,
|
||||
SUPPORT_OUTPUT_VARS_NODE,
|
||||
TEMPLATE_TRANSFORM_OUTPUT_STRUCT,
|
||||
@@ -27,6 +30,10 @@ import {
|
||||
import type { PromptItem } from '@/models/debug'
|
||||
import { VAR_REGEX } from '@/config'
|
||||
|
||||
export const isSystemVar = (valueSelector: ValueSelector) => {
|
||||
return valueSelector[0] === 'sys' || valueSelector[1] === 'sys'
|
||||
}
|
||||
|
||||
const inputVarTypeToVarType = (type: InputVarType): VarType => {
|
||||
if (type === InputVarType.number)
|
||||
return VarType.number
|
||||
@@ -54,6 +61,7 @@ const findExceptVarInObject = (obj: any, filterVar: (payload: Var, selector: Val
|
||||
|
||||
const formatItem = (item: any, isChatMode: boolean, filterVar: (payload: Var, selector: ValueSelector) => boolean): NodeOutPutVar => {
|
||||
const { id, data } = item
|
||||
|
||||
const res: NodeOutPutVar = {
|
||||
nodeId: id,
|
||||
title: data.title,
|
||||
@@ -136,13 +144,58 @@ const formatItem = (item: any, isChatMode: boolean, filterVar: (payload: Var, se
|
||||
case BlockEnum.VariableAssigner: {
|
||||
const {
|
||||
output_type,
|
||||
advanced_settings,
|
||||
} = data as VariableAssignerNodeType
|
||||
res.vars = [
|
||||
{
|
||||
variable: 'output',
|
||||
type: output_type,
|
||||
},
|
||||
]
|
||||
const isGroup = !!advanced_settings?.group_enabled
|
||||
if (!isGroup) {
|
||||
res.vars = [
|
||||
{
|
||||
variable: 'output',
|
||||
type: output_type,
|
||||
},
|
||||
]
|
||||
}
|
||||
else {
|
||||
res.vars = advanced_settings?.groups.map((group) => {
|
||||
return {
|
||||
variable: group.group_name,
|
||||
type: VarType.object,
|
||||
children: [{
|
||||
variable: 'output',
|
||||
type: group.output_type,
|
||||
}],
|
||||
}
|
||||
})
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case BlockEnum.VariableAggregator: {
|
||||
const {
|
||||
output_type,
|
||||
advanced_settings,
|
||||
} = data as VariableAssignerNodeType
|
||||
const isGroup = !!advanced_settings?.group_enabled
|
||||
if (!isGroup) {
|
||||
res.vars = [
|
||||
{
|
||||
variable: 'output',
|
||||
type: output_type,
|
||||
},
|
||||
]
|
||||
}
|
||||
else {
|
||||
res.vars = advanced_settings?.groups.map((group) => {
|
||||
return {
|
||||
variable: group.group_name,
|
||||
type: VarType.object,
|
||||
children: [{
|
||||
variable: 'output',
|
||||
type: group.output_type,
|
||||
}],
|
||||
}
|
||||
})
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
@@ -150,6 +203,28 @@ const formatItem = (item: any, isChatMode: boolean, filterVar: (payload: Var, se
|
||||
res.vars = TOOL_OUTPUT_STRUCT
|
||||
break
|
||||
}
|
||||
|
||||
case BlockEnum.ParameterExtractor: {
|
||||
res.vars = [
|
||||
...PARAMETER_EXTRACTOR_COMMON_STRUCT,
|
||||
...((data as ParameterExtractorNodeType).parameters || []).map((p) => {
|
||||
return {
|
||||
variable: p.name,
|
||||
type: p.type as unknown as VarType,
|
||||
}
|
||||
})]
|
||||
break
|
||||
}
|
||||
|
||||
case BlockEnum.Iteration: {
|
||||
res.vars = [
|
||||
{
|
||||
variable: 'output',
|
||||
type: (data as IterationNodeType).output_type || VarType.arrayString,
|
||||
},
|
||||
]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
const selector = [id]
|
||||
@@ -183,37 +258,113 @@ export const toNodeOutputVars = (nodes: any[], isChatMode: boolean, filterVar =
|
||||
return res
|
||||
}
|
||||
|
||||
export const isSystemVar = (valueSelector: ValueSelector) => {
|
||||
return valueSelector[0] === 'sys' || valueSelector[1] === 'sys'
|
||||
const getIterationItemType = ({
|
||||
valueSelector,
|
||||
beforeNodesOutputVars,
|
||||
}: {
|
||||
valueSelector: ValueSelector
|
||||
beforeNodesOutputVars: NodeOutPutVar[]
|
||||
}): VarType => {
|
||||
const outputVarNodeId = valueSelector[0]
|
||||
const targetVar = beforeNodesOutputVars.find(v => v.nodeId === outputVarNodeId)
|
||||
if (!targetVar)
|
||||
return VarType.string
|
||||
|
||||
let arrayType: VarType = VarType.string
|
||||
|
||||
const isSystem = isSystemVar(valueSelector)
|
||||
let curr: any = targetVar.vars
|
||||
|
||||
if (isSystem)
|
||||
return curr.find((v: any) => v.variable === (valueSelector).join('.'))?.type;
|
||||
|
||||
(valueSelector).slice(1).forEach((key, i) => {
|
||||
const isLast = i === valueSelector.length - 2
|
||||
curr = curr?.find((v: any) => v.variable === key)
|
||||
if (isLast) {
|
||||
arrayType = curr?.type
|
||||
}
|
||||
else {
|
||||
if (curr?.type === VarType.object)
|
||||
curr = curr.children
|
||||
}
|
||||
})
|
||||
switch (arrayType as VarType) {
|
||||
case VarType.arrayString:
|
||||
return VarType.string
|
||||
case VarType.arrayNumber:
|
||||
return VarType.number
|
||||
case VarType.arrayObject:
|
||||
return VarType.object
|
||||
case VarType.array:
|
||||
return VarType.any
|
||||
case VarType.arrayFile:
|
||||
return VarType.object
|
||||
default:
|
||||
return VarType.string
|
||||
}
|
||||
}
|
||||
|
||||
export const getNodeInfoById = (nodes: any, id: string) => {
|
||||
if (!isArray(nodes))
|
||||
return
|
||||
return nodes.find((node: any) => node.id === id)
|
||||
}
|
||||
export const getVarType = ({
|
||||
parentNode,
|
||||
valueSelector,
|
||||
isIterationItem,
|
||||
availableNodes,
|
||||
isChatMode,
|
||||
isConstant,
|
||||
}:
|
||||
{
|
||||
valueSelector: ValueSelector
|
||||
parentNode?: Node | null
|
||||
isIterationItem?: boolean
|
||||
availableNodes: any[]
|
||||
isChatMode: boolean
|
||||
isConstant?: boolean
|
||||
}): VarType => {
|
||||
if (isConstant)
|
||||
return VarType.string
|
||||
|
||||
export const getVarType = (value: ValueSelector, availableNodes: any[], isChatMode: boolean): VarType | undefined => {
|
||||
const isSystem = isSystemVar(value)
|
||||
const beforeNodesOutputVars = toNodeOutputVars(availableNodes, isChatMode)
|
||||
|
||||
const isIterationInnerVar = parentNode?.data.type === BlockEnum.Iteration
|
||||
if (isIterationItem) {
|
||||
return getIterationItemType({
|
||||
valueSelector,
|
||||
beforeNodesOutputVars,
|
||||
})
|
||||
}
|
||||
if (isIterationInnerVar) {
|
||||
if (valueSelector[1] === 'item') {
|
||||
const itemType = getIterationItemType({
|
||||
valueSelector: (parentNode?.data as any).iterator_selector || [],
|
||||
beforeNodesOutputVars,
|
||||
})
|
||||
return itemType
|
||||
}
|
||||
if (valueSelector[1] === 'index')
|
||||
return VarType.number
|
||||
|
||||
return VarType.string
|
||||
}
|
||||
const isSystem = isSystemVar(valueSelector)
|
||||
const startNode = availableNodes.find((node: any) => {
|
||||
return node.data.type === BlockEnum.Start
|
||||
})
|
||||
const allOutputVars = toNodeOutputVars(availableNodes, isChatMode)
|
||||
|
||||
const targetVarNodeId = isSystem ? startNode?.id : value[0]
|
||||
const targetVar = allOutputVars.find(v => v.nodeId === targetVarNodeId)
|
||||
const targetVarNodeId = isSystem ? startNode?.id : valueSelector[0]
|
||||
const targetVar = beforeNodesOutputVars.find(v => v.nodeId === targetVarNodeId)
|
||||
|
||||
if (!targetVar)
|
||||
return undefined
|
||||
return VarType.string
|
||||
|
||||
let type: VarType = VarType.string
|
||||
let curr: any = targetVar.vars
|
||||
if (isSystem) {
|
||||
return curr.find((v: any) => v.variable === (value as ValueSelector).join('.'))?.type
|
||||
return curr.find((v: any) => v.variable === (valueSelector as ValueSelector).join('.'))?.type
|
||||
}
|
||||
else {
|
||||
(value as ValueSelector).slice(1).forEach((key, i) => {
|
||||
const isLast = i === value.length - 2
|
||||
(valueSelector as ValueSelector).slice(1).forEach((key, i) => {
|
||||
const isLast = i === valueSelector.length - 2
|
||||
curr = curr.find((v: any) => v.variable === key)
|
||||
if (isLast) {
|
||||
type = curr?.type
|
||||
@@ -227,6 +378,57 @@ export const getVarType = (value: ValueSelector, availableNodes: any[], isChatMo
|
||||
}
|
||||
}
|
||||
|
||||
// node output vars + parent inner vars(if in iteration or other wrap node)
|
||||
export const toNodeAvailableVars = ({
|
||||
parentNode,
|
||||
t,
|
||||
beforeNodes,
|
||||
isChatMode,
|
||||
filterVar,
|
||||
}: {
|
||||
parentNode?: Node | null
|
||||
t?: any
|
||||
// to get those nodes output vars
|
||||
beforeNodes: Node[]
|
||||
isChatMode: boolean
|
||||
filterVar: (payload: Var, selector: ValueSelector) => boolean
|
||||
}): NodeOutPutVar[] => {
|
||||
const beforeNodesOutputVars = toNodeOutputVars(beforeNodes, isChatMode, filterVar)
|
||||
const isInIteration = parentNode?.data.type === BlockEnum.Iteration
|
||||
if (isInIteration) {
|
||||
const iterationNode: any = parentNode
|
||||
const itemType = getVarType({
|
||||
parentNode: iterationNode,
|
||||
isIterationItem: true,
|
||||
valueSelector: iterationNode?.data.iterator_selector || [],
|
||||
availableNodes: beforeNodes,
|
||||
isChatMode,
|
||||
})
|
||||
const iterationVar = {
|
||||
nodeId: iterationNode?.id,
|
||||
title: t('workflow.nodes.iteration.currentIteration'),
|
||||
vars: [
|
||||
{
|
||||
variable: 'item',
|
||||
type: itemType,
|
||||
},
|
||||
{
|
||||
variable: 'index',
|
||||
type: VarType.number,
|
||||
},
|
||||
],
|
||||
}
|
||||
beforeNodesOutputVars.unshift(iterationVar)
|
||||
}
|
||||
return beforeNodesOutputVars
|
||||
}
|
||||
|
||||
export const getNodeInfoById = (nodes: any, id: string) => {
|
||||
if (!isArray(nodes))
|
||||
return
|
||||
return nodes.find((node: any) => node.id === id)
|
||||
}
|
||||
|
||||
const matchNotSystemVars = (prompts: string[]) => {
|
||||
if (!prompts)
|
||||
return []
|
||||
@@ -326,11 +528,98 @@ export const getNodeUsedVars = (node: Node): ValueSelector[] => {
|
||||
|
||||
case BlockEnum.VariableAssigner: {
|
||||
res = (data as VariableAssignerNodeType)?.variables
|
||||
break
|
||||
}
|
||||
|
||||
case BlockEnum.VariableAggregator: {
|
||||
res = (data as VariableAssignerNodeType)?.variables
|
||||
break
|
||||
}
|
||||
|
||||
case BlockEnum.ParameterExtractor: {
|
||||
const payload = (data as ParameterExtractorNodeType)
|
||||
res = [payload.query]
|
||||
const varInInstructions = matchNotSystemVars([payload.instruction || ''])
|
||||
res.push(...varInInstructions)
|
||||
break
|
||||
}
|
||||
|
||||
case BlockEnum.Iteration: {
|
||||
res = [(data as IterationNodeType).iterator_selector]
|
||||
break
|
||||
}
|
||||
}
|
||||
return res || []
|
||||
}
|
||||
|
||||
// used can be used in iteration node
|
||||
export const getNodeUsedVarPassToServerKey = (node: Node, valueSelector: ValueSelector): string | string[] => {
|
||||
const { data } = node
|
||||
const { type } = data
|
||||
let res: string | string[] = ''
|
||||
switch (type) {
|
||||
case BlockEnum.LLM: {
|
||||
const payload = (data as LLMNodeType)
|
||||
res = [`#${valueSelector.join('.')}#`]
|
||||
if (payload.context?.variable_selector.join('.') === valueSelector.join('.'))
|
||||
res.push('#context#')
|
||||
|
||||
break
|
||||
}
|
||||
case BlockEnum.KnowledgeRetrieval: {
|
||||
res = 'query'
|
||||
break
|
||||
}
|
||||
case BlockEnum.IfElse: {
|
||||
const targetVar = (data as IfElseNodeType).conditions?.find(c => c.variable_selector.join('.') === valueSelector.join('.'))
|
||||
if (targetVar)
|
||||
res = `#${valueSelector.join('.')}#`
|
||||
break
|
||||
}
|
||||
case BlockEnum.Code: {
|
||||
const targetVar = (data as CodeNodeType).variables?.find(v => v.value_selector.join('.') === valueSelector.join('.'))
|
||||
if (targetVar)
|
||||
res = targetVar.variable
|
||||
break
|
||||
}
|
||||
case BlockEnum.TemplateTransform: {
|
||||
const targetVar = (data as TemplateTransformNodeType).variables?.find(v => v.value_selector.join('.') === valueSelector.join('.'))
|
||||
if (targetVar)
|
||||
res = targetVar.variable
|
||||
break
|
||||
}
|
||||
case BlockEnum.QuestionClassifier: {
|
||||
res = 'query'
|
||||
break
|
||||
}
|
||||
case BlockEnum.HttpRequest: {
|
||||
res = `#${valueSelector.join('.')}#`
|
||||
break
|
||||
}
|
||||
|
||||
case BlockEnum.Tool: {
|
||||
res = `#${valueSelector.join('.')}#`
|
||||
break
|
||||
}
|
||||
|
||||
case BlockEnum.VariableAssigner: {
|
||||
res = `#${valueSelector.join('.')}#`
|
||||
break
|
||||
}
|
||||
|
||||
case BlockEnum.VariableAggregator: {
|
||||
res = `#${valueSelector.join('.')}#`
|
||||
break
|
||||
}
|
||||
|
||||
case BlockEnum.ParameterExtractor: {
|
||||
res = 'query'
|
||||
break
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
export const findUsedVarNodes = (varSelector: ValueSelector, availableNodes: Node[]): Node[] => {
|
||||
const res: Node[] = []
|
||||
availableNodes.forEach((node) => {
|
||||
@@ -345,6 +634,7 @@ export const updateNodeVars = (oldNode: Node, oldVarSelector: ValueSelector, new
|
||||
const newNode = produce(oldNode, (draft: any) => {
|
||||
const { data } = draft
|
||||
const { type } = data
|
||||
|
||||
switch (type) {
|
||||
case BlockEnum.End: {
|
||||
const payload = data as EndNodeType
|
||||
@@ -480,6 +770,31 @@ export const updateNodeVars = (oldNode: Node, oldVarSelector: ValueSelector, new
|
||||
}
|
||||
break
|
||||
}
|
||||
case BlockEnum.VariableAggregator: {
|
||||
const payload = data as VariableAssignerNodeType
|
||||
if (payload.variables) {
|
||||
payload.variables = payload.variables.map((v) => {
|
||||
if (v.join('.') === oldVarSelector.join('.'))
|
||||
v = newVarSelector
|
||||
return v
|
||||
})
|
||||
}
|
||||
break
|
||||
}
|
||||
case BlockEnum.ParameterExtractor: {
|
||||
const payload = data as ParameterExtractorNodeType
|
||||
if (payload.query.join('.') === oldVarSelector.join('.'))
|
||||
payload.query = newVarSelector
|
||||
payload.instruction = replaceOldVarInText(payload.instruction, oldVarSelector, newVarSelector)
|
||||
break
|
||||
}
|
||||
case BlockEnum.Iteration: {
|
||||
const payload = data as IterationNodeType
|
||||
if (payload.iterator_selector.join('.') === oldVarSelector.join('.'))
|
||||
payload.iterator_selector = newVarSelector
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
return newNode
|
||||
@@ -567,10 +882,33 @@ export const getNodeOutputVars = (node: Node, isChatMode: boolean): ValueSelecto
|
||||
break
|
||||
}
|
||||
|
||||
case BlockEnum.VariableAggregator: {
|
||||
res.push([id, 'output'])
|
||||
break
|
||||
}
|
||||
|
||||
case BlockEnum.Tool: {
|
||||
varsToValueSelectorList(TOOL_OUTPUT_STRUCT, [id], res)
|
||||
break
|
||||
}
|
||||
|
||||
case BlockEnum.ParameterExtractor: {
|
||||
const {
|
||||
parameters,
|
||||
} = data as ParameterExtractorNodeType
|
||||
if (parameters?.length > 0) {
|
||||
parameters.forEach((p) => {
|
||||
res.push([id, p.name])
|
||||
})
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
case BlockEnum.Iteration: {
|
||||
res.push([id, 'output'])
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return res
|
||||
|
||||
@@ -4,10 +4,11 @@ import React, { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import cn from 'classnames'
|
||||
import produce from 'immer'
|
||||
import { useStoreApi } from 'reactflow'
|
||||
import VarReferencePopup from './var-reference-popup'
|
||||
import { getNodeInfoById, isSystemVar, toNodeOutputVars } from './utils'
|
||||
import type { ValueSelector, Var } from '@/app/components/workflow/types'
|
||||
import { BlockEnum, VarType } from '@/app/components/workflow/types'
|
||||
import { getNodeInfoById, getVarType, isSystemVar, toNodeAvailableVars } from './utils'
|
||||
import type { Node, NodeOutPutVar, ValueSelector, Var } from '@/app/components/workflow/types'
|
||||
import { BlockEnum } from '@/app/components/workflow/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'
|
||||
@@ -24,7 +25,7 @@ import { VarType as VarKindType } from '@/app/components/workflow/nodes/tool/typ
|
||||
import TypeSelector from '@/app/components/workflow/nodes/_base/components/selector'
|
||||
import { ChevronDown } from '@/app/components/base/icons/src/vender/line/arrows'
|
||||
import { XClose } from '@/app/components/base/icons/src/vender/line/general'
|
||||
|
||||
import AddButton from '@/app/components/base/button/add-button'
|
||||
const TRIGGER_DEFAULT_WIDTH = 227
|
||||
|
||||
type Props = {
|
||||
@@ -33,12 +34,15 @@ type Props = {
|
||||
isShowNodeName: boolean
|
||||
readonly: boolean
|
||||
value: ValueSelector | string
|
||||
onChange: (value: ValueSelector | string, varKindType: VarKindType) => void
|
||||
onChange: (value: ValueSelector | string, varKindType: VarKindType, varInfo?: Var) => void
|
||||
onOpen?: () => void
|
||||
isSupportConstantValue?: boolean
|
||||
defaultVarKindType?: VarKindType
|
||||
onlyLeafNodeVar?: boolean
|
||||
filterVar?: (payload: Var, valueSelector: ValueSelector) => boolean
|
||||
availableNodes?: Node[]
|
||||
availableVars?: NodeOutPutVar[]
|
||||
isAddBtnTrigger?: boolean
|
||||
}
|
||||
|
||||
const VarReferencePicker: FC<Props> = ({
|
||||
@@ -53,8 +57,27 @@ const VarReferencePicker: FC<Props> = ({
|
||||
defaultVarKindType = VarKindType.constant,
|
||||
onlyLeafNodeVar,
|
||||
filterVar = () => true,
|
||||
availableNodes: passedInAvailableNodes,
|
||||
availableVars,
|
||||
isAddBtnTrigger,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const store = useStoreApi()
|
||||
const {
|
||||
getNodes,
|
||||
} = store.getState()
|
||||
const isChatMode = useIsChatMode()
|
||||
|
||||
const { getTreeLeafNodes, getBeforeNodesInSameBranch } = useWorkflow()
|
||||
const availableNodes = passedInAvailableNodes || (onlyLeafNodeVar ? getTreeLeafNodes(nodeId) : getBeforeNodesInSameBranch(nodeId))
|
||||
const startNode = availableNodes.find((node: any) => {
|
||||
return node.data.type === BlockEnum.Start
|
||||
})
|
||||
|
||||
const node = getNodes().find(n => n.id === nodeId)
|
||||
const isInIteration = !!node?.data.isInIteration
|
||||
const iterationNode = isInIteration ? getNodes().find(n => n.id === node.parentId) : null
|
||||
|
||||
const triggerRef = useRef<HTMLDivElement>(null)
|
||||
const [triggerWidth, setTriggerWidth] = useState(TRIGGER_DEFAULT_WIDTH)
|
||||
useEffect(() => {
|
||||
@@ -63,63 +86,60 @@ const VarReferencePicker: FC<Props> = ({
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [triggerRef.current])
|
||||
|
||||
const isChatMode = useIsChatMode()
|
||||
const [varKindType, setVarKindType] = useState<VarKindType>(defaultVarKindType)
|
||||
const isConstant = isSupportConstantValue && varKindType === VarKindType.constant
|
||||
const { getTreeLeafNodes, getBeforeNodesInSameBranch } = useWorkflow()
|
||||
const availableNodes = onlyLeafNodeVar ? getTreeLeafNodes(nodeId) : getBeforeNodesInSameBranch(nodeId)
|
||||
const allOutputVars = toNodeOutputVars(availableNodes, isChatMode)
|
||||
const outputVars = toNodeOutputVars(availableNodes, isChatMode, filterVar)
|
||||
|
||||
const outputVars = (() => {
|
||||
if (availableVars)
|
||||
return availableVars
|
||||
|
||||
const vars = toNodeAvailableVars({
|
||||
parentNode: iterationNode,
|
||||
t,
|
||||
beforeNodes: availableNodes,
|
||||
isChatMode,
|
||||
filterVar,
|
||||
})
|
||||
|
||||
return vars
|
||||
})()
|
||||
const [open, setOpen] = useState(false)
|
||||
useEffect(() => {
|
||||
onOpen()
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [open])
|
||||
const hasValue = !isConstant && value.length > 0
|
||||
const startNode = availableNodes.find((node: any) => {
|
||||
return node.data.type === BlockEnum.Start
|
||||
})
|
||||
|
||||
const isIterationVar = (() => {
|
||||
if (!isInIteration)
|
||||
return false
|
||||
if (value[0] === node?.parentId && ['item', 'index'].includes(value[1]))
|
||||
return true
|
||||
return false
|
||||
})()
|
||||
|
||||
const outputVarNodeId = hasValue ? value[0] : ''
|
||||
const outputVarNode = (() => {
|
||||
if (!hasValue || isConstant)
|
||||
return null
|
||||
|
||||
if (isIterationVar)
|
||||
return iterationNode?.data
|
||||
|
||||
if (isSystemVar(value as ValueSelector))
|
||||
return startNode?.data
|
||||
|
||||
return getNodeInfoById(availableNodes, outputVarNodeId)?.data
|
||||
})()
|
||||
const varName = hasValue ? `${isSystemVar(value as ValueSelector) ? 'sys.' : ''}${value[value.length - 1]}` : ''
|
||||
|
||||
const getVarType = () => {
|
||||
if (isConstant)
|
||||
return 'undefined'
|
||||
const isSystem = isSystemVar(value as ValueSelector)
|
||||
const targetVarNodeId = isSystem ? startNode?.id : outputVarNodeId
|
||||
const targetVar = allOutputVars.find(v => v.nodeId === targetVarNodeId)
|
||||
|
||||
if (!targetVar)
|
||||
return 'undefined'
|
||||
|
||||
let type: VarType = VarType.string
|
||||
let curr: any = targetVar.vars
|
||||
if (isSystem) {
|
||||
return curr.find((v: any) => v.variable === (value as ValueSelector).join('.'))?.type
|
||||
const varName = (() => {
|
||||
if (hasValue) {
|
||||
const isSystem = isSystemVar(value as ValueSelector)
|
||||
const varName = value.length >= 3 ? (value as ValueSelector).slice(-2).join('.') : value[value.length - 1]
|
||||
return `${isSystem ? 'sys.' : ''}${varName}`
|
||||
}
|
||||
else {
|
||||
(value as ValueSelector).slice(1).forEach((key, i) => {
|
||||
const isLast = i === value.length - 2
|
||||
curr = curr.find((v: any) => v.variable === key)
|
||||
if (isLast) {
|
||||
type = curr?.type
|
||||
}
|
||||
else {
|
||||
if (curr.type === VarType.object)
|
||||
curr = curr.children
|
||||
}
|
||||
})
|
||||
return type
|
||||
}
|
||||
}
|
||||
return ''
|
||||
})()
|
||||
|
||||
const varKindTypes = [
|
||||
{
|
||||
@@ -150,7 +170,7 @@ const VarReferencePicker: FC<Props> = ({
|
||||
}
|
||||
}, [controlFocus])
|
||||
|
||||
const handleVarReferenceChange = useCallback((value: ValueSelector) => {
|
||||
const handleVarReferenceChange = useCallback((value: ValueSelector, varInfo: Var) => {
|
||||
// sys var not passed to backend
|
||||
const newValue = produce(value, (draft) => {
|
||||
if (draft[1] && draft[1].startsWith('sys')) {
|
||||
@@ -161,7 +181,7 @@ const VarReferencePicker: FC<Props> = ({
|
||||
})
|
||||
}
|
||||
})
|
||||
onChange(newValue, varKindType)
|
||||
onChange(newValue, varKindType, varInfo)
|
||||
setOpen(false)
|
||||
}, [onChange, varKindType])
|
||||
|
||||
@@ -176,7 +196,14 @@ const VarReferencePicker: FC<Props> = ({
|
||||
onChange([], varKindType)
|
||||
}, [onChange, varKindType])
|
||||
|
||||
const type = getVarType()
|
||||
const type = getVarType({
|
||||
parentNode: iterationNode,
|
||||
valueSelector: value as ValueSelector,
|
||||
availableNodes,
|
||||
isChatMode,
|
||||
isConstant: !!isConstant,
|
||||
})
|
||||
|
||||
// 8(left/right-padding) + 14(icon) + 4 + 14 + 2 = 42 + 17 buff
|
||||
const availableWidth = triggerWidth - 56
|
||||
const [maxNodeNameWidth, maxVarNameWidth, maxTypeWidth] = (() => {
|
||||
@@ -193,86 +220,92 @@ const VarReferencePicker: FC<Props> = ({
|
||||
<PortalToFollowElem
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
placement='bottom-start'
|
||||
placement={isAddBtnTrigger ? 'bottom-end' : 'bottom-start'}
|
||||
>
|
||||
<PortalToFollowElemTrigger onClick={() => {
|
||||
if (readonly)
|
||||
return
|
||||
!isConstant ? setOpen(!open) : setControlFocus(Date.now())
|
||||
}} className='!flex'>
|
||||
<div ref={triggerRef} className={cn((open || isFocus) ? 'border-gray-300' : 'border-gray-100', 'relative group/wrap flex items-center w-full h-8 p-1 rounded-lg bg-gray-100 border')}>
|
||||
{isSupportConstantValue
|
||||
? <div onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
setOpen(false)
|
||||
setControlFocus(Date.now())
|
||||
}} className='mr-1 flex items-center space-x-1'>
|
||||
<TypeSelector
|
||||
noLeft
|
||||
triggerClassName='!text-xs'
|
||||
readonly={readonly}
|
||||
DropDownIcon={ChevronDown}
|
||||
value={varKindType}
|
||||
options={varKindTypes}
|
||||
onChange={handleVarKindTypeChange}
|
||||
/>
|
||||
<div className='h-4 w-px bg-black/5'></div>
|
||||
{isAddBtnTrigger
|
||||
? (
|
||||
<div>
|
||||
<AddButton onClick={() => { }}></AddButton>
|
||||
</div>
|
||||
: (!hasValue && <div className='ml-1.5 mr-1'>
|
||||
<Variable02 className='w-3.5 h-3.5 text-gray-400' />
|
||||
</div>)}
|
||||
{isConstant
|
||||
? (
|
||||
<input
|
||||
type='text'
|
||||
className='w-full h-8 leading-8 pl-0.5 bg-transparent text-[13px] font-normal text-gray-900 placeholder:text-gray-400 focus:outline-none overflow-hidden'
|
||||
value={isConstant ? value : ''}
|
||||
onChange={handleStaticChange}
|
||||
onFocus={() => setIsFocus(true)}
|
||||
onBlur={() => setIsFocus(false)}
|
||||
readOnly={readonly}
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<div className={cn('inline-flex h-full items-center px-1.5 rounded-[5px]', hasValue && 'bg-white')}>
|
||||
{hasValue
|
||||
? (
|
||||
<>
|
||||
{isShowNodeName && (
|
||||
<div className='flex items-center'>
|
||||
<div className='p-[1px]'>
|
||||
<VarBlockIcon
|
||||
className='!text-gray-900'
|
||||
type={outputVarNode?.type || BlockEnum.Start}
|
||||
/>
|
||||
</div>
|
||||
<div className='mx-0.5 text-xs font-medium text-gray-700 truncate' title={outputVarNode?.title} style={{
|
||||
maxWidth: maxNodeNameWidth,
|
||||
}}>{outputVarNode?.title}</div>
|
||||
<Line3 className='mr-0.5'></Line3>
|
||||
</div>
|
||||
)}
|
||||
<div className='flex items-center text-primary-600'>
|
||||
{!hasValue && <Variable02 className='w-3.5 h-3.5' />}
|
||||
<div className='ml-0.5 text-xs font-medium truncate' title={varName} style={{
|
||||
maxWidth: maxVarNameWidth,
|
||||
}}>{varName}</div>
|
||||
</div>
|
||||
<div className='ml-0.5 text-xs font-normal text-gray-500 capitalize truncate' title={type} style={{
|
||||
maxWidth: maxTypeWidth,
|
||||
}}>{type}</div>
|
||||
</>
|
||||
)
|
||||
: <div className='text-[13px] font-normal text-gray-400'>{t('workflow.common.setVarValuePlaceholder')}</div>}
|
||||
)
|
||||
: (<div ref={triggerRef} className={cn((open || isFocus) ? 'border-gray-300' : 'border-gray-100', 'relative group/wrap flex items-center w-full h-8 p-1 rounded-lg bg-gray-100 border')}>
|
||||
{isSupportConstantValue
|
||||
? <div onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
setOpen(false)
|
||||
setControlFocus(Date.now())
|
||||
}} className='mr-1 flex items-center space-x-1'>
|
||||
<TypeSelector
|
||||
noLeft
|
||||
triggerClassName='!text-xs'
|
||||
readonly={readonly}
|
||||
DropDownIcon={ChevronDown}
|
||||
value={varKindType}
|
||||
options={varKindTypes}
|
||||
onChange={handleVarKindTypeChange}
|
||||
/>
|
||||
<div className='h-4 w-px bg-black/5'></div>
|
||||
</div>
|
||||
)}
|
||||
{(hasValue && !readonly) && (<div
|
||||
className='invisible group-hover/wrap:visible absolute h-5 right-1 top-[50%] translate-y-[-50%] group p-1 rounded-md hover:bg-black/5 cursor-pointer'
|
||||
onClick={handleClearVar}
|
||||
>
|
||||
<XClose className='w-3.5 h-3.5 text-gray-500 group-hover:text-gray-800' />
|
||||
: (!hasValue && <div className='ml-1.5 mr-1'>
|
||||
<Variable02 className='w-3.5 h-3.5 text-gray-400' />
|
||||
</div>)}
|
||||
{isConstant
|
||||
? (
|
||||
<input
|
||||
type='text'
|
||||
className='w-full h-8 leading-8 pl-0.5 bg-transparent text-[13px] font-normal text-gray-900 placeholder:text-gray-400 focus:outline-none overflow-hidden'
|
||||
value={isConstant ? value : ''}
|
||||
onChange={handleStaticChange}
|
||||
onFocus={() => setIsFocus(true)}
|
||||
onBlur={() => setIsFocus(false)}
|
||||
readOnly={readonly}
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<div className={cn('inline-flex h-full items-center px-1.5 rounded-[5px]', hasValue && 'bg-white')}>
|
||||
{hasValue
|
||||
? (
|
||||
<>
|
||||
{isShowNodeName && (
|
||||
<div className='flex items-center'>
|
||||
<div className='p-[1px]'>
|
||||
<VarBlockIcon
|
||||
className='!text-gray-900'
|
||||
type={outputVarNode?.type || BlockEnum.Start}
|
||||
/>
|
||||
</div>
|
||||
<div className='mx-0.5 text-xs font-medium text-gray-700 truncate' title={outputVarNode?.title} style={{
|
||||
maxWidth: maxNodeNameWidth,
|
||||
}}>{outputVarNode?.title}</div>
|
||||
<Line3 className='mr-0.5'></Line3>
|
||||
</div>
|
||||
)}
|
||||
<div className='flex items-center text-primary-600'>
|
||||
{!hasValue && <Variable02 className='w-3.5 h-3.5' />}
|
||||
<div className='ml-0.5 text-xs font-medium truncate' title={varName} style={{
|
||||
maxWidth: maxVarNameWidth,
|
||||
}}>{varName}</div>
|
||||
</div>
|
||||
<div className='ml-0.5 text-xs font-normal text-gray-500 capitalize truncate' title={type} style={{
|
||||
maxWidth: maxTypeWidth,
|
||||
}}>{type}</div>
|
||||
</>
|
||||
)
|
||||
: <div className='text-[13px] font-normal text-gray-400'>{t('workflow.common.setVarValuePlaceholder')}</div>}
|
||||
</div>
|
||||
)}
|
||||
{(hasValue && !readonly) && (<div
|
||||
className='invisible group-hover/wrap:visible absolute h-5 right-1 top-[50%] translate-y-[-50%] group p-1 rounded-md hover:bg-black/5 cursor-pointer'
|
||||
onClick={handleClearVar}
|
||||
>
|
||||
<XClose className='w-3.5 h-3.5 text-gray-500 group-hover:text-gray-800' />
|
||||
</div>)}
|
||||
</div>)}
|
||||
</div>
|
||||
</PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemContent style={{
|
||||
zIndex: 100,
|
||||
@@ -281,7 +314,7 @@ const VarReferencePicker: FC<Props> = ({
|
||||
<VarReferencePopup
|
||||
vars={outputVars}
|
||||
onChange={handleVarReferenceChange}
|
||||
itemWidth={triggerWidth}
|
||||
itemWidth={isAddBtnTrigger ? 260 : triggerWidth}
|
||||
/>
|
||||
)}
|
||||
</PortalToFollowElemContent>
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import VarReferenceVars from './var-reference-vars'
|
||||
import { type NodeOutPutVar, type ValueSelector } from '@/app/components/workflow/types'
|
||||
import type { NodeOutPutVar, ValueSelector, Var } from '@/app/components/workflow/types'
|
||||
|
||||
type Props = {
|
||||
vars: NodeOutPutVar[]
|
||||
onChange: (value: ValueSelector) => void
|
||||
onChange: (value: ValueSelector, varDetail: Var) => void
|
||||
itemWidth?: number
|
||||
}
|
||||
const VarReferencePopup: FC<Props> = ({
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import useNodeInfo from './use-node-info'
|
||||
import {
|
||||
useIsChatMode,
|
||||
useWorkflow,
|
||||
} from '@/app/components/workflow/hooks'
|
||||
import { toNodeOutputVars } from '@/app/components/workflow/nodes/_base/components/variable/utils'
|
||||
import { toNodeAvailableVars } from '@/app/components/workflow/nodes/_base/components/variable/utils'
|
||||
import type { ValueSelector, Var } from '@/app/components/workflow/types'
|
||||
|
||||
type Params = {
|
||||
onlyLeafNodeVar?: boolean
|
||||
filterVar: (payload: Var, selector: ValueSelector) => boolean
|
||||
}
|
||||
|
||||
const useAvailableVarList = (nodeId: string, {
|
||||
onlyLeafNodeVar,
|
||||
filterVar,
|
||||
@@ -16,14 +18,29 @@ const useAvailableVarList = (nodeId: string, {
|
||||
onlyLeafNodeVar: false,
|
||||
filterVar: () => true,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const { getTreeLeafNodes, getBeforeNodesInSameBranch } = useWorkflow()
|
||||
const isChatMode = useIsChatMode()
|
||||
|
||||
const availableNodes = onlyLeafNodeVar ? getTreeLeafNodes(nodeId) : getBeforeNodesInSameBranch(nodeId)
|
||||
const availableVars = toNodeOutputVars(availableNodes, isChatMode, filterVar)
|
||||
|
||||
const {
|
||||
parentNode: iterationNode,
|
||||
} = useNodeInfo(nodeId)
|
||||
|
||||
const availableVars = toNodeAvailableVars({
|
||||
parentNode: iterationNode,
|
||||
t,
|
||||
beforeNodes: availableNodes,
|
||||
isChatMode,
|
||||
filterVar,
|
||||
})
|
||||
|
||||
return {
|
||||
availableVars,
|
||||
availableNodes,
|
||||
availableNodesWithParent: iterationNode ? [...availableNodes, iterationNode] : availableNodes,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import { useStoreApi } from 'reactflow'
|
||||
|
||||
const useNodeInfo = (nodeId: string) => {
|
||||
const store = useStoreApi()
|
||||
const {
|
||||
getNodes,
|
||||
} = store.getState()
|
||||
const allNodes = getNodes()
|
||||
const node = allNodes.find(n => n.id === nodeId)
|
||||
const isInIteration = !!node?.data.isInIteration
|
||||
const parentNodeId = node?.parentId
|
||||
const parentNode = allNodes.find(n => n.id === parentNodeId)
|
||||
return {
|
||||
node,
|
||||
isInIteration,
|
||||
parentNode,
|
||||
}
|
||||
}
|
||||
|
||||
export default useNodeInfo
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { unionBy } from 'lodash-es'
|
||||
import produce from 'immer'
|
||||
import {
|
||||
useIsChatMode,
|
||||
useNodeDataUpdate,
|
||||
@@ -11,7 +12,7 @@ import { getNodeInfoById, isSystemVar, toNodeOutputVars } from '@/app/components
|
||||
import type { CommonNodeType, InputVar, ValueSelector, Var, Variable } from '@/app/components/workflow/types'
|
||||
import { BlockEnum, InputVarType, NodeRunningStatus, VarType } from '@/app/components/workflow/types'
|
||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||
import { singleNodeRun } from '@/service/workflow'
|
||||
import { getIterationSingleNodeRunUrl, singleNodeRun } from '@/service/workflow'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import LLMDefault from '@/app/components/workflow/nodes/llm/default'
|
||||
import KnowledgeRetrievalDefault from '@/app/components/workflow/nodes/knowledge-retrieval/default'
|
||||
@@ -22,7 +23,12 @@ import QuestionClassifyDefault from '@/app/components/workflow/nodes/question-cl
|
||||
import HTTPDefault from '@/app/components/workflow/nodes/http/default'
|
||||
import ToolDefault from '@/app/components/workflow/nodes/tool/default'
|
||||
import VariableAssigner from '@/app/components/workflow/nodes/variable-assigner/default'
|
||||
import ParameterExtractorDefault from '@/app/components/workflow/nodes/parameter-extractor/default'
|
||||
import IterationDefault from '@/app/components/workflow/nodes/iteration/default'
|
||||
import { ssePost } from '@/service/base'
|
||||
|
||||
import { getInputVars as doGetInputVars } from '@/app/components/base/prompt-editor/constants'
|
||||
import type { NodeTracing } from '@/types/workflow'
|
||||
const { checkValid: checkLLMValid } = LLMDefault
|
||||
const { checkValid: checkKnowledgeRetrievalValid } = KnowledgeRetrievalDefault
|
||||
const { checkValid: checkIfElseValid } = IfElseDefault
|
||||
@@ -32,6 +38,8 @@ const { checkValid: checkQuestionClassifyValid } = QuestionClassifyDefault
|
||||
const { checkValid: checkHttpValid } = HTTPDefault
|
||||
const { checkValid: checkToolValid } = ToolDefault
|
||||
const { checkValid: checkVariableAssignerValid } = VariableAssigner
|
||||
const { checkValid: checkParameterExtractorValid } = ParameterExtractorDefault
|
||||
const { checkValid: checkIterationValid } = IterationDefault
|
||||
|
||||
const checkValidFns: Record<BlockEnum, Function> = {
|
||||
[BlockEnum.LLM]: checkLLMValid,
|
||||
@@ -43,6 +51,9 @@ const checkValidFns: Record<BlockEnum, Function> = {
|
||||
[BlockEnum.HttpRequest]: checkHttpValid,
|
||||
[BlockEnum.Tool]: checkToolValid,
|
||||
[BlockEnum.VariableAssigner]: checkVariableAssignerValid,
|
||||
[BlockEnum.VariableAggregator]: checkVariableAssignerValid,
|
||||
[BlockEnum.ParameterExtractor]: checkParameterExtractorValid,
|
||||
[BlockEnum.Iteration]: checkIterationValid,
|
||||
} as any
|
||||
|
||||
type Params<T> = {
|
||||
@@ -50,6 +61,7 @@ type Params<T> = {
|
||||
data: CommonNodeType<T>
|
||||
defaultRunInputData: Record<string, any>
|
||||
moreDataForCheckValid?: any
|
||||
iteratorInputKey?: string
|
||||
}
|
||||
|
||||
const varTypeToInputVarType = (type: VarType, {
|
||||
@@ -78,13 +90,16 @@ const useOneStepRun = <T>({
|
||||
data,
|
||||
defaultRunInputData,
|
||||
moreDataForCheckValid,
|
||||
iteratorInputKey,
|
||||
}: Params<T>) => {
|
||||
const { t } = useTranslation()
|
||||
const { getBeforeNodesInSameBranch } = useWorkflow() as any
|
||||
const { getBeforeNodesInSameBranch, getBeforeNodesInSameBranchIncludeParent } = useWorkflow() as any
|
||||
const isChatMode = useIsChatMode()
|
||||
const isIteration = data.type === BlockEnum.Iteration
|
||||
|
||||
const availableNodes = getBeforeNodesInSameBranch(id)
|
||||
const allOutputVars = toNodeOutputVars(getBeforeNodesInSameBranch(id), isChatMode)
|
||||
const availableNodesIncludeParent = getBeforeNodesInSameBranchIncludeParent(id)
|
||||
const allOutputVars = toNodeOutputVars(availableNodes, isChatMode)
|
||||
const getVar = (valueSelector: ValueSelector): Var | undefined => {
|
||||
let res: Var | undefined
|
||||
const isSystem = valueSelector[0] === 'sys'
|
||||
@@ -95,14 +110,17 @@ const useOneStepRun = <T>({
|
||||
return targetVar.vars.find(item => item.variable.split('.')[1] === valueSelector[1])
|
||||
|
||||
let curr: any = targetVar.vars
|
||||
if (!curr)
|
||||
return
|
||||
|
||||
valueSelector.slice(1).forEach((key, i) => {
|
||||
const isLast = i === valueSelector.length - 2
|
||||
curr = curr.find((v: any) => v.variable === key)
|
||||
curr = curr?.find((v: any) => v.variable === key)
|
||||
if (isLast) {
|
||||
res = curr
|
||||
}
|
||||
else {
|
||||
if (curr.type === VarType.object)
|
||||
if (curr?.type === VarType.object)
|
||||
curr = curr.children
|
||||
}
|
||||
})
|
||||
@@ -113,11 +131,14 @@ const useOneStepRun = <T>({
|
||||
const checkValid = checkValidFns[data.type]
|
||||
const appId = useAppStore.getState().appDetail?.id
|
||||
const [runInputData, setRunInputData] = useState<Record<string, any>>(defaultRunInputData || {})
|
||||
const iterationTimes = iteratorInputKey ? runInputData[iteratorInputKey].length : 0
|
||||
const [runResult, setRunResult] = useState<any>(null)
|
||||
|
||||
const { handleNodeDataUpdate }: { handleNodeDataUpdate: (data: any) => void } = useNodeDataUpdate()
|
||||
const [canShowSingleRun, setCanShowSingleRun] = useState(false)
|
||||
const isShowSingleRun = data._isSingleRun && canShowSingleRun
|
||||
const [iterationRunResult, setIterationRunResult] = useState<NodeTracing[][]>([])
|
||||
|
||||
useEffect(() => {
|
||||
if (!checkValid) {
|
||||
setCanShowSingleRun(true)
|
||||
@@ -152,6 +173,15 @@ const useOneStepRun = <T>({
|
||||
},
|
||||
})
|
||||
}
|
||||
const showSingleRun = () => {
|
||||
handleNodeDataUpdate({
|
||||
id,
|
||||
data: {
|
||||
...data,
|
||||
_isSingleRun: true,
|
||||
},
|
||||
})
|
||||
}
|
||||
const runningStatus = data._singleRunningStatus || NodeRunningStatus.NotStart
|
||||
const isCompleted = runningStatus === NodeRunningStatus.Succeeded || runningStatus === NodeRunningStatus.Failed
|
||||
|
||||
@@ -165,34 +195,117 @@ const useOneStepRun = <T>({
|
||||
})
|
||||
let res: any
|
||||
try {
|
||||
res = await singleNodeRun(appId!, id, { inputs: submitData }) as any
|
||||
if (!isIteration) {
|
||||
res = await singleNodeRun(appId!, id, { inputs: submitData }) as any
|
||||
}
|
||||
else {
|
||||
setIterationRunResult([])
|
||||
let _iterationResult: NodeTracing[][] = []
|
||||
let _runResult: any = null
|
||||
ssePost(
|
||||
getIterationSingleNodeRunUrl(isChatMode, appId!, id),
|
||||
{ body: { inputs: submitData } },
|
||||
{
|
||||
onWorkflowStarted: () => {
|
||||
},
|
||||
onWorkflowFinished: (params) => {
|
||||
handleNodeDataUpdate({
|
||||
id,
|
||||
data: {
|
||||
...data,
|
||||
_singleRunningStatus: NodeRunningStatus.Succeeded,
|
||||
},
|
||||
})
|
||||
const { data: iterationData } = params
|
||||
_runResult.created_by = iterationData.created_by.name
|
||||
setRunResult(_runResult)
|
||||
},
|
||||
onIterationNext: () => {
|
||||
// iteration next trigger time is triggered one more time than iterationTimes
|
||||
if (_iterationResult.length >= iterationTimes!)
|
||||
return
|
||||
|
||||
const newIterationRunResult = produce(_iterationResult, (draft) => {
|
||||
draft.push([])
|
||||
})
|
||||
_iterationResult = newIterationRunResult
|
||||
setIterationRunResult(newIterationRunResult)
|
||||
},
|
||||
onIterationFinish: (params) => {
|
||||
_runResult = params.data
|
||||
setRunResult(_runResult)
|
||||
},
|
||||
onNodeStarted: (params) => {
|
||||
const newIterationRunResult = produce(_iterationResult, (draft) => {
|
||||
draft[draft.length - 1].push({
|
||||
...params.data,
|
||||
status: NodeRunningStatus.Running,
|
||||
} as NodeTracing)
|
||||
})
|
||||
_iterationResult = newIterationRunResult
|
||||
setIterationRunResult(newIterationRunResult)
|
||||
},
|
||||
onNodeFinished: (params) => {
|
||||
const iterationRunResult = _iterationResult
|
||||
|
||||
const { data } = params
|
||||
const currentIndex = iterationRunResult[iterationRunResult.length - 1].findIndex(trace => trace.node_id === data.node_id)
|
||||
const newIterationRunResult = produce(iterationRunResult, (draft) => {
|
||||
if (currentIndex > -1) {
|
||||
draft[draft.length - 1][currentIndex] = {
|
||||
...data,
|
||||
status: NodeRunningStatus.Succeeded,
|
||||
} as NodeTracing
|
||||
}
|
||||
})
|
||||
_iterationResult = newIterationRunResult
|
||||
setIterationRunResult(newIterationRunResult)
|
||||
},
|
||||
onError: () => {
|
||||
handleNodeDataUpdate({
|
||||
id,
|
||||
data: {
|
||||
...data,
|
||||
_singleRunningStatus: NodeRunningStatus.Failed,
|
||||
},
|
||||
})
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
if (res.error)
|
||||
throw new Error(res.error)
|
||||
}
|
||||
catch (e: any) {
|
||||
if (!isIteration) {
|
||||
handleNodeDataUpdate({
|
||||
id,
|
||||
data: {
|
||||
...data,
|
||||
_singleRunningStatus: NodeRunningStatus.Failed,
|
||||
},
|
||||
})
|
||||
return false
|
||||
}
|
||||
}
|
||||
finally {
|
||||
if (!isIteration) {
|
||||
setRunResult({
|
||||
...res,
|
||||
total_tokens: res.execution_metadata?.total_tokens || 0,
|
||||
created_by: res.created_by_account?.name || '',
|
||||
})
|
||||
}
|
||||
}
|
||||
if (!isIteration) {
|
||||
handleNodeDataUpdate({
|
||||
id,
|
||||
data: {
|
||||
...data,
|
||||
_singleRunningStatus: NodeRunningStatus.Failed,
|
||||
_singleRunningStatus: NodeRunningStatus.Succeeded,
|
||||
},
|
||||
})
|
||||
return false
|
||||
}
|
||||
finally {
|
||||
setRunResult({
|
||||
...res,
|
||||
total_tokens: res.execution_metadata?.total_tokens || 0,
|
||||
created_by: res.created_by_account?.name || '',
|
||||
})
|
||||
}
|
||||
handleNodeDataUpdate({
|
||||
id,
|
||||
data: {
|
||||
...data,
|
||||
_singleRunningStatus: NodeRunningStatus.Succeeded,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const handleStop = () => {
|
||||
@@ -241,12 +354,12 @@ const useOneStepRun = <T>({
|
||||
})
|
||||
|
||||
const variables = unionBy(valueSelectors, item => item.join('.')).map((item) => {
|
||||
const varInfo = getNodeInfoById(availableNodes, item[0])?.data
|
||||
const varInfo = getNodeInfoById(availableNodesIncludeParent, item[0])?.data
|
||||
|
||||
return {
|
||||
label: {
|
||||
nodeType: varInfo?.type,
|
||||
nodeName: varInfo?.title || availableNodes[0]?.data.title, // default start node title
|
||||
nodeName: varInfo?.title || availableNodesIncludeParent[0]?.data.title, // default start node title
|
||||
variable: isSystemVar(item) ? item.join('.') : item[item.length - 1],
|
||||
},
|
||||
variable: `#${item.join('.')}#`,
|
||||
@@ -261,6 +374,7 @@ const useOneStepRun = <T>({
|
||||
return {
|
||||
isShowSingleRun,
|
||||
hideSingleRun,
|
||||
showSingleRun,
|
||||
toVarInputs,
|
||||
getInputVars,
|
||||
runningStatus,
|
||||
@@ -270,6 +384,7 @@ const useOneStepRun = <T>({
|
||||
runInputData,
|
||||
setRunInputData,
|
||||
runResult,
|
||||
iterationRunResult,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,9 +5,11 @@ import type {
|
||||
import {
|
||||
cloneElement,
|
||||
memo,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
} from 'react'
|
||||
import cn from 'classnames'
|
||||
import type { NodeProps } from '../../types'
|
||||
import {
|
||||
BlockEnum,
|
||||
@@ -17,11 +19,14 @@ import {
|
||||
useNodesReadOnly,
|
||||
useToolIcon,
|
||||
} from '../../hooks'
|
||||
import { useNodeIterationInteractions } from '../iteration/use-interactions'
|
||||
import {
|
||||
NodeSourceHandle,
|
||||
NodeTargetHandle,
|
||||
} from './components/node-handle'
|
||||
import NodeResizer from './components/node-resizer'
|
||||
import NodeControl from './components/node-control'
|
||||
import AddVariablePopupWithPosition from './components/add-variable-popup-with-position'
|
||||
import BlockIcon from '@/app/components/workflow/block-icon'
|
||||
import {
|
||||
CheckCircle,
|
||||
@@ -40,9 +45,24 @@ const BaseNode: FC<BaseNodeProps> = ({
|
||||
}) => {
|
||||
const nodeRef = useRef<HTMLDivElement>(null)
|
||||
const { nodesReadOnly } = useNodesReadOnly()
|
||||
const { handleNodeIterationChildSizeChange } = useNodeIterationInteractions()
|
||||
const toolIcon = useToolIcon(data)
|
||||
|
||||
const showSelectedBorder = data.selected || data._isBundled
|
||||
useEffect(() => {
|
||||
if (nodeRef.current && data.selected && data.isInIteration) {
|
||||
const resizeObserver = new ResizeObserver(() => {
|
||||
handleNodeIterationChildSizeChange(id)
|
||||
})
|
||||
|
||||
resizeObserver.observe(nodeRef.current)
|
||||
|
||||
return () => {
|
||||
resizeObserver.disconnect()
|
||||
}
|
||||
}
|
||||
}, [data.isInIteration, data.selected, id, handleNodeIterationChildSizeChange])
|
||||
|
||||
const showSelectedBorder = data.selected || data._isBundled || data._isEntering
|
||||
const {
|
||||
showRunningBorder,
|
||||
showSuccessBorder,
|
||||
@@ -57,26 +77,47 @@ const BaseNode: FC<BaseNodeProps> = ({
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`
|
||||
flex border-[2px] rounded-2xl
|
||||
${(showSelectedBorder && !data._isInvalidConnection) ? 'border-primary-600' : 'border-transparent'}
|
||||
`}
|
||||
className={cn(
|
||||
'flex border-[2px] rounded-2xl',
|
||||
showSelectedBorder ? 'border-primary-600' : 'border-transparent',
|
||||
)}
|
||||
ref={nodeRef}
|
||||
style={{
|
||||
width: data.type === BlockEnum.Iteration ? data.width : 'auto',
|
||||
height: data.type === BlockEnum.Iteration ? data.height : 'auto',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className={`
|
||||
group relative pb-1 w-[240px] bg-[#fcfdff] shadow-xs
|
||||
border border-transparent rounded-[15px]
|
||||
${!data._runningStatus && 'hover:shadow-lg'}
|
||||
${showRunningBorder && '!border-primary-500'}
|
||||
${showSuccessBorder && '!border-[#12B76A]'}
|
||||
${showFailedBorder && '!border-[#F04438]'}
|
||||
${data._isInvalidConnection && '!border-[#F04438]'}
|
||||
${data._isBundled && '!shadow-lg'}
|
||||
`}
|
||||
className={cn(
|
||||
'group relative pb-1 shadow-xs',
|
||||
'border border-transparent rounded-[15px]',
|
||||
data.type !== BlockEnum.Iteration && 'w-[240px] bg-[#fcfdff]',
|
||||
data.type === BlockEnum.Iteration && 'flex flex-col w-full h-full bg-[#fcfdff]/80',
|
||||
!data._runningStatus && 'hover:shadow-lg',
|
||||
showRunningBorder && '!border-primary-500',
|
||||
showSuccessBorder && '!border-[#12B76A]',
|
||||
showFailedBorder && '!border-[#F04438]',
|
||||
data._isBundled && '!shadow-lg',
|
||||
)}
|
||||
>
|
||||
{
|
||||
data.type !== BlockEnum.VariableAssigner && !data._isCandidate && (
|
||||
data._showAddVariablePopup && (
|
||||
<AddVariablePopupWithPosition
|
||||
nodeId={id}
|
||||
nodeData={data}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
data.type === BlockEnum.Iteration && (
|
||||
<NodeResizer
|
||||
nodeId={id}
|
||||
nodeData={data}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
data.type !== BlockEnum.VariableAssigner && data.type !== BlockEnum.VariableAggregator && !data._isCandidate && (
|
||||
<NodeTargetHandle
|
||||
id={id}
|
||||
data={data}
|
||||
@@ -103,7 +144,10 @@ const BaseNode: FC<BaseNodeProps> = ({
|
||||
/>
|
||||
)
|
||||
}
|
||||
<div className='flex items-center px-3 pt-3 pb-2'>
|
||||
<div className={cn(
|
||||
'flex items-center px-3 pt-3 pb-2 rounded-t-2xl',
|
||||
data.type === BlockEnum.Iteration && 'bg-[rgba(250,252,255,0.9)]',
|
||||
)}>
|
||||
<BlockIcon
|
||||
className='shrink-0 mr-2'
|
||||
type={data.type}
|
||||
@@ -116,6 +160,13 @@ const BaseNode: FC<BaseNodeProps> = ({
|
||||
>
|
||||
{data.title}
|
||||
</div>
|
||||
{
|
||||
data._iterationLength && data._iterationIndex && data._runningStatus === NodeRunningStatus.Running && (
|
||||
<div className='mr-1.5 text-xs font-medium text-primary-600'>
|
||||
{data._iterationIndex}/{data._iterationLength}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
(data._runningStatus === NodeRunningStatus.Running || data._singleRunningStatus === NodeRunningStatus.Running) && (
|
||||
<Loading02 className='w-3.5 h-3.5 text-primary-600 animate-spin' />
|
||||
@@ -132,9 +183,20 @@ const BaseNode: FC<BaseNodeProps> = ({
|
||||
)
|
||||
}
|
||||
</div>
|
||||
{cloneElement(children, { id, data })}
|
||||
{
|
||||
data.desc && (
|
||||
data.type !== BlockEnum.Iteration && (
|
||||
cloneElement(children, { id, data })
|
||||
)
|
||||
}
|
||||
{
|
||||
data.type === BlockEnum.Iteration && (
|
||||
<div className='grow pl-1 pr-1 pb-1'>
|
||||
{cloneElement(children, { id, data })}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
data.desc && data.type !== BlockEnum.Iteration && (
|
||||
<div className='px-3 pt-1 pb-2 text-xs leading-[18px] text-gray-500 whitespace-pre-line break-words'>
|
||||
{data.desc}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user