Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
D
dify
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
ai-tech
dify
Commits
ea76f462
Commit
ea76f462
authored
Feb 22, 2024
by
StyleZhang
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
block-selector
parent
0759b29c
Changes
10
Hide whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
123 additions
and
404 deletions
+123
-404
context.tsx
web/app/components/workflow/block-selector/context.tsx
+0
-137
index.tsx
web/app/components/workflow/block-selector/index.tsx
+67
-17
tabs.tsx
web/app/components/workflow/block-selector/tabs.tsx
+2
-2
types.ts
web/app/components/workflow/block-selector/types.ts
+0
-3
index.tsx
web/app/components/workflow/index.tsx
+1
-4
hooks.ts
...ponents/workflow/nodes/_base/components/add-node/hooks.ts
+0
-40
index.tsx
...onents/workflow/nodes/_base/components/add-node/index.tsx
+0
-116
next-step.tsx
.../components/workflow/nodes/_base/components/next-step.tsx
+47
-53
node.tsx
web/app/components/workflow/nodes/_base/node.tsx
+4
-30
index.tsx
web/app/components/workflow/nodes/index.tsx
+2
-2
No files found.
web/app/components/workflow/block-selector/context.tsx
deleted
100644 → 0
View file @
0759b29c
'use client'
import
{
useCallback
,
useRef
,
useState
}
from
'react'
import
{
createContext
,
useContext
}
from
'use-context-selector'
import
type
{
OffsetOptions
,
Placement
,
}
from
'@floating-ui/react'
import
{
FloatingPortal
,
flip
,
offset
,
shift
,
useDismiss
,
useFloating
,
useInteractions
,
}
from
'@floating-ui/react'
import
type
{
OnSelect
}
from
'./types'
import
BlockSelector
from
'./index'
type
UpdateParams
=
{
from
?:
string
placement
?:
Placement
offset
?:
OffsetOptions
open
?:
boolean
className
?:
string
callback
?:
OnSelect
}
export
type
BlockSelectorContextValue
=
{
from
:
string
open
:
boolean
setOpen
:
(
open
:
boolean
)
=>
void
referenceRef
:
any
floatingRef
:
any
floatingStyles
:
React
.
CSSProperties
getFloatingProps
:
any
handleToggle
:
(
v
:
UpdateParams
)
=>
void
}
export
const
BlockSelectorContext
=
createContext
<
BlockSelectorContextValue
>
({
from
:
''
,
open
:
false
,
setOpen
:
()
=>
{},
referenceRef
:
null
,
floatingRef
:
null
,
floatingStyles
:
{},
getFloatingProps
:
()
=>
{},
handleToggle
:
()
=>
{},
})
export
const
useBlockSelectorContext
=
()
=>
useContext
(
BlockSelectorContext
)
type
BlockSelectorContextProviderProps
=
{
children
:
React
.
ReactNode
}
export
const
BlockSelectorContextProvider
=
({
children
,
}:
BlockSelectorContextProviderProps
)
=>
{
const
[
from
,
setFrom
]
=
useState
(
'node'
)
const
[
open
,
setOpen
]
=
useState
(
false
)
const
[
placement
,
setPlacement
]
=
useState
<
Placement
>
(
'top'
)
const
[
offsetValue
,
setOffsetValue
]
=
useState
<
OffsetOptions
>
(
0
)
const
[
className
,
setClassName
]
=
useState
<
string
>
(
''
)
const
callbackRef
=
useRef
<
OnSelect
|
undefined
>
(
undefined
)
const
{
refs
,
floatingStyles
,
context
}
=
useFloating
({
placement
,
strategy
:
'fixed'
,
open
,
onOpenChange
:
setOpen
,
middleware
:
[
flip
(),
shift
(),
offset
(
offsetValue
),
],
})
const
dismiss
=
useDismiss
(
context
)
const
{
getFloatingProps
}
=
useInteractions
([
dismiss
,
])
const
handleToggle
=
useCallback
(({
from
,
open
,
placement
,
offset
,
className
,
callback
,
}:
UpdateParams
)
=>
{
setFrom
(
from
||
'node'
)
if
(
open
!==
undefined
)
setOpen
(
open
)
else
setOpen
(
v
=>
!
v
)
setPlacement
(
placement
||
'top'
)
setOffsetValue
(
offset
||
0
)
setClassName
(
className
||
''
)
callbackRef
.
current
=
callback
},
[])
const
handleSelect
=
useCallback
<
OnSelect
>
((
type
)
=>
{
if
(
callbackRef
.
current
)
callbackRef
.
current
(
type
)
setOpen
(
v
=>
!
v
)
},
[])
return
(
<
BlockSelectorContext
.
Provider
value=
{
{
from
,
open
,
setOpen
,
handleToggle
,
referenceRef
:
refs
.
setReference
,
floatingRef
:
refs
.
setFloating
,
floatingStyles
,
getFloatingProps
,
}
}
>
{
children
}
{
open
&&
(
from
===
'node'
||
from
===
'panel'
)
&&
(
<
FloatingPortal
>
<
div
ref=
{
refs
.
setFloating
}
style=
{
floatingStyles
}
{
...
getFloatingProps
()}
className=
'z-[1000]'
>
<
BlockSelector
className=
{
className
}
onSelect=
{
handleSelect
}
/>
</
div
>
</
FloatingPortal
>
)
}
</
BlockSelectorContext
.
Provider
>
)
}
web/app/components/workflow/block-selector/index.tsx
View file @
ea76f462
import
type
{
FC
}
from
'react'
import
{
memo
}
from
'react'
import
{
memo
,
useState
,
}
from
'react'
import
type
{
OffsetOptions
,
Placement
,
}
from
'@floating-ui/react'
import
type
{
BlockEnum
}
from
'../types'
import
Tabs
from
'./tabs'
import
type
{
OnSelect
}
from
'./types'
import
{
SearchLg
}
from
'@/app/components/base/icons/src/vender/line/general'
import
{
PortalToFollowElem
,
PortalToFollowElemContent
,
PortalToFollowElemTrigger
,
}
from
'@/app/components/base/portal-to-follow-elem'
import
{
Plus02
,
SearchLg
,
}
from
'@/app/components/base/icons/src/vender/line/general'
type
NodeSelectorProps
=
{
onSelect
:
OnSelect
className
?:
string
onSelect
:
(
type
:
BlockEnum
)
=>
void
trigger
?:
(
open
:
boolean
)
=>
React
.
ReactNode
placement
?:
Placement
offset
?:
OffsetOptions
popupClassName
?:
string
asChild
?:
boolean
}
const
NodeSelector
:
FC
<
NodeSelectorProps
>
=
({
onSelect
,
className
,
trigger
,
placement
=
'right'
,
offset
=
6
,
popupClassName
,
asChild
,
})
=>
{
const
[
open
,
setOpen
]
=
useState
(
false
)
return
(
<
div
className=
{
`w-[256px] rounded-lg border-[0.5px] border-gray-200 bg-white shadow-lg ${className}`
}
>
<
div
className=
'px-2 pt-2'
>
<
div
className=
'flex items-center px-2 rounded-lg bg-gray-100'
>
<
SearchLg
className=
'shrink-0 ml-[1px] mr-[5px] w-3.5 h-3.5 text-gray-400'
/>
<
input
className=
'grow px-0.5 py-[7px] text-[13px] bg-transparent appearance-none outline-none'
placeholder=
'Search block'
/>
<
PortalToFollowElem
placement=
{
placement
}
offset=
{
offset
}
open=
{
open
}
onOpenChange=
{
setOpen
}
>
<
PortalToFollowElemTrigger
asChild=
{
asChild
}
onClick=
{
()
=>
setOpen
(
v
=>
!
v
)
}
>
{
trigger
?
trigger
(
open
)
:
(
<
div
className=
{
`
hidden absolute -right-2 top-4 items-center justify-center
w-4 h-4 rounded-full bg-primary-600 cursor-pointer z-10 group-hover:flex
${open && '!flex'}
`
}
>
<
Plus02
className=
'w-2.5 h-2.5 text-white'
/>
</
div
>
)
}
</
PortalToFollowElemTrigger
>
<
PortalToFollowElemContent
className=
'z-[1000]'
>
<
div
className=
{
`w-[256px] rounded-lg border-[0.5px] border-gray-200 bg-white shadow-lg ${popupClassName}`
}
>
<
div
className=
'px-2 pt-2'
>
<
div
className=
'flex items-center px-2 rounded-lg bg-gray-100'
>
<
SearchLg
className=
'shrink-0 ml-[1px] mr-[5px] w-3.5 h-3.5 text-gray-400'
/>
<
input
className=
'grow px-0.5 py-[7px] text-[13px] bg-transparent appearance-none outline-none'
placeholder=
'Search block'
/>
</
div
>
</
div
>
<
Tabs
onSelect=
{
onSelect
}
/>
</
div
>
</
div
>
<
Tabs
onSelect=
{
onSelect
}
/>
</
div
>
</
PortalToFollowElemContent
>
</
PortalToFollowElem
>
)
}
...
...
web/app/components/workflow/block-selector/tabs.tsx
View file @
ea76f462
...
...
@@ -4,7 +4,7 @@ import {
useState
,
}
from
'react'
import
BlockIcon
from
'../block-icon'
import
type
{
OnSelect
}
from
'
./types'
import
type
{
BlockEnum
}
from
'.
./types'
import
{
BLOCK_CLASSIFICATIONS
,
BLOCK_GROUP_BY_CLASSIFICATION
,
...
...
@@ -12,7 +12,7 @@ import {
}
from
'./constants'
export
type
TabsProps
=
{
onSelect
:
OnSelect
onSelect
:
(
type
:
BlockEnum
)
=>
void
}
const
Tabs
:
FC
<
TabsProps
>
=
({
onSelect
,
...
...
web/app/components/workflow/block-selector/types.ts
deleted
100644 → 0
View file @
0759b29c
import
type
{
BlockEnum
}
from
'../types'
export
type
OnSelect
=
(
type
:
BlockEnum
)
=>
void
web/app/components/workflow/index.tsx
View file @
ea76f462
...
...
@@ -23,7 +23,6 @@ import DebugAndPreview from './debug-and-preview'
import
ZoomInOut
from
'./zoom-in-out'
import
CustomEdge
from
'./custom-edge'
import
type
{
Node
}
from
'./types'
import
{
BlockSelectorContextProvider
}
from
'./block-selector/context'
const
nodeTypes
=
{
custom
:
CustomNode
,
...
...
@@ -97,9 +96,7 @@ const WorkflowWrap: FC<WorkflowWrapProps> = ({
handleAddNextNode
,
handleUpdateNodeData
,
}
}
>
<
BlockSelectorContextProvider
>
<
Workflow
/>
</
BlockSelectorContextProvider
>
<
Workflow
/>
</
WorkflowContext
.
Provider
>
)
}
...
...
web/app/components/workflow/nodes/_base/components/add-node/hooks.ts
deleted
100644 → 0
View file @
0759b29c
import
{
useState
}
from
'react'
import
{
flip
,
offset
,
shift
,
useDismiss
,
useFloating
,
useInteractions
,
}
from
'@floating-ui/react'
export
const
useAddBranch
=
()
=>
{
const
[
isOpen
,
setIsOpen
]
=
useState
(
false
)
const
[
dismissEnable
,
setDismissEnable
]
=
useState
(
true
)
const
{
refs
,
floatingStyles
,
context
}
=
useFloating
({
placement
:
'bottom'
,
strategy
:
'fixed'
,
open
:
isOpen
,
onOpenChange
:
setIsOpen
,
middleware
:
[
flip
(),
shift
(),
offset
(
4
),
],
})
const
dismiss
=
useDismiss
(
context
,
{
enabled
:
dismissEnable
,
})
const
{
getFloatingProps
}
=
useInteractions
([
dismiss
,
])
return
{
refs
,
floatingStyles
,
getFloatingProps
,
isOpen
,
setIsOpen
,
setDismissEnable
,
}
}
web/app/components/workflow/nodes/_base/components/add-node/index.tsx
deleted
100644 → 0
View file @
0759b29c
import
type
{
FC
,
MouseEvent
}
from
'react'
import
{
memo
,
useMemo
,
}
from
'react'
import
{
FloatingPortal
}
from
'@floating-ui/react'
import
{
useBlockSelectorContext
}
from
'../../../../block-selector/context'
import
type
{
BlockEnum
,
Node
,
}
from
'../../../../types'
import
{
useAddBranch
}
from
'./hooks'
import
{
Plus02
}
from
'@/app/components/base/icons/src/vender/line/general'
type
AddNodeProps
=
{
outgoers
:
Node
[]
onAddNextNode
:
(
type
:
BlockEnum
)
=>
void
branches
?:
{
id
:
string
;
name
:
string
}[]
}
const
AddNode
:
FC
<
AddNodeProps
>
=
({
onAddNextNode
,
branches
,
})
=>
{
const
{
refs
,
isOpen
,
setIsOpen
,
setDismissEnable
,
floatingStyles
,
getFloatingProps
,
}
=
useAddBranch
()
const
{
from
,
open
,
referenceRef
,
handleToggle
,
}
=
useBlockSelectorContext
()
const
hasBranches
=
branches
&&
!!
branches
.
length
const
handleAdd
=
(
e
:
MouseEvent
<
HTMLDivElement
>
)
=>
{
e
.
stopPropagation
()
if
(
hasBranches
)
return
setIsOpen
(
v
=>
!
v
)
handleToggle
({
placement
:
'right'
,
offset
:
6
,
callback
:
onAddNextNode
,
})
}
const
buttonRef
=
useMemo
(()
=>
{
if
(
hasBranches
)
return
refs
.
setReference
if
(
from
===
'node'
)
return
referenceRef
return
null
},
[
from
,
hasBranches
,
referenceRef
,
refs
.
setReference
])
const
buttonShouldShow
=
useMemo
(()
=>
{
if
(
hasBranches
&&
isOpen
)
return
true
return
open
&&
from
===
'node'
},
[
from
,
hasBranches
,
isOpen
,
open
])
return
(
<>
<
div
ref=
{
buttonRef
}
onClick=
{
handleAdd
}
className=
{
`
hidden absolute -right-2 top-4 items-center justify-center
w-4 h-4 rounded-full bg-primary-600 cursor-pointer z-10 group-hover:flex
${buttonShouldShow && '!flex'}
`
}
>
<
Plus02
className=
'w-2.5 h-2.5 text-white'
/>
</
div
>
{
isOpen
&&
hasBranches
&&
(
<
FloatingPortal
>
<
div
ref=
{
refs
.
setFloating
}
style=
{
floatingStyles
}
{
...
getFloatingProps
()}
className=
'p-1 w-[108px] rounded-lg border-[0.5px] border-gray-200 bg-white shadow-lg'
>
{
branches
.
map
(
branch
=>
(
<
div
key=
{
branch
.
id
}
className=
'flex items-center px-3 pr-2 h-[30px] text-[13px] font-medium text-gray-700 cursor-pointer rounded-lg hover:bg-gray-50'
onClick=
{
()
=>
{
setDismissEnable
(
false
)
handleToggle
({
open
:
true
,
placement
:
'right'
,
offset
:
6
,
callback
:
onAddNextNode
,
})
}
}
>
{
branch
.
name
}
</
div
>
))
}
</
div
>
</
FloatingPortal
>
)
}
</>
)
}
export
default
memo
(
AddNode
)
web/app/components/workflow/nodes/_base/components/next-step.tsx
View file @
ea76f462
...
...
@@ -9,7 +9,7 @@ import BlockIcon from '../../../block-icon'
import
type
{
Node
}
from
'../../../types'
import
{
BlockEnum
}
from
'../../../types'
import
{
useWorkflowContext
}
from
'../../../context'
import
{
useBlockSelectorContext
}
from
'../../../block-selector/context
'
import
BlockSelector
from
'../../../block-selector
'
import
{
Plus
}
from
'@/app/components/base/icons/src/vender/line/general'
import
Button
from
'@/app/components/base/button'
...
...
@@ -19,23 +19,43 @@ type NextStepProps = {
const
NextStep
:
FC
<
NextStepProps
>
=
({
selectedNode
,
})
=>
{
const
{
from
,
open
,
referenceRef
,
handleToggle
,
}
=
useBlockSelectorContext
()
const
{
nodes
,
edges
,
handleAddNextNode
,
}
=
useWorkflowContext
()
const
outgoers
=
useMemo
(()
=>
{
return
getOutgoers
(
selectedNode
,
nodes
,
edges
)
},
[
selectedNode
,
nodes
,
edges
])
const
handleSelectBlock
=
useCallback
((
type
:
BlockEnum
)
=>
{
handleAddNextNode
(
selectedNode
,
type
)
},
[
selectedNode
,
handleAddNextNode
])
const
renderAddNextNodeTrigger
=
useCallback
((
open
:
boolean
)
=>
{
return
(
<
div
className=
{
`
flex items-center px-2 w-[328px] h-9 rounded-lg border border-dashed border-gray-200 bg-gray-50
hover:bg-gray-100 text-xs text-gray-500 cursor-pointer
${open && '!bg-gray-100'}
`
}
>
<
div
className=
'flex items-center justify-center mr-1.5 w-5 h-5 rounded-[5px] bg-gray-200'
>
<
Plus
className=
'w-3 h-3'
/>
</
div
>
SELECT NEXT BLOCK
</
div
>
)
},
[])
const
renderChangeCurrentNodeTrigger
=
useCallback
((
open
:
boolean
)
=>
{
return
(
<
Button
className=
{
`
hidden group-hover:flex px-2 py-0 h-6 bg-white text-xs text-gray-700 font-medium rounded-md
${open && '!bg-gray-100 !flex'}
`
}
>
Change
</
Button
>
)
},
[])
return
(
<
div
className=
'flex py-1'
>
...
...
@@ -55,54 +75,28 @@ const NextStep: FC<NextStepProps> = ({
className=
'shrink-0 mr-1.5'
/>
<
div
className=
'grow'
>
{
outgoer
.
data
.
name
}
</
div
>
<
div
ref=
{
from
===
'panel'
?
referenceRef
:
null
}
onClick=
{
()
=>
{
handleToggle
({
from
:
'panel'
,
className
:
'w-[328px]'
,
placement
:
'top-end'
,
offset
:
{
mainAxis
:
6
,
crossAxis
:
8
,
},
})
<
BlockSelector
onSelect=
{
()
=>
{}
}
placement=
'top-end'
offset=
{
{
mainAxis
:
6
,
crossAxis
:
8
,
}
}
>
<
Button
className=
{
`
hidden group-hover:flex px-2 py-0 h-6 bg-white text-xs text-gray-700 font-medium rounded-md
${open && '!bg-gray-100 !flex'}
`
}
>
Change
</
Button
>
</
div
>
trigger=
{
renderChangeCurrentNodeTrigger
}
popupClassName=
'!w-[328px]'
/>
</
div
>
))
}
{
(
!
outgoers
.
length
||
selectedNode
.
data
.
type
===
BlockEnum
.
IfElse
)
&&
(
<
div
onClick=
{
()
=>
{
handleToggle
({
from
:
'panel'
,
className
:
'w-[328px]'
,
callback
:
handleSelectBlock
,
})
}
}
ref=
{
from
===
'panel'
?
referenceRef
:
null
}
className=
{
`
flex items-center px-2 w-[328px] h-9 rounded-lg border border-dashed border-gray-200 bg-gray-50
hover:bg-gray-100 text-xs text-gray-500 cursor-pointer
${open && from === 'panel' && '!bg-gray-100'}
`
}
>
<
div
className=
'flex items-center justify-center mr-1.5 w-5 h-5 rounded-[5px] bg-gray-200'
>
<
Plus
className=
'w-3 h-3'
/>
</
div
>
SELECT NEXT BLOCK
</
div
>
<
BlockSelector
onSelect=
{
()
=>
{}
}
placement=
'top'
offset=
{
0
}
trigger=
{
renderAddNextNodeTrigger
}
popupClassName=
'!w-[328px]'
/>
)
}
</
div
>
...
...
web/app/components/workflow/nodes/_base/node.tsx
View file @
ea76f462
...
...
@@ -5,16 +5,13 @@ import type {
import
{
cloneElement
,
memo
,
useCallback
,
useMemo
,
}
from
'react'
import
type
{
NodeProps
}
from
'reactflow'
import
{
getOutgoers
}
from
'reactflow'
import
{
useWorkflowContext
}
from
'../../context'
import
{
BlockEnum
}
from
'../../types'
import
NodeControl
from
'../../node-control'
import
BlockIcon
from
'../../block-icon'
import
AddNode
from
'./components/add-node/index
'
import
BlockSelector
from
'../../block-selector
'
type
BaseNodeProps
=
{
children
:
ReactElement
...
...
@@ -27,34 +24,12 @@ const BaseNode: FC<BaseNodeProps> = ({
})
=>
{
const
{
nodes
,
edges
,
selectedNodeId
,
handleSelectedNodeIdChange
,
handleAddNextNode
,
}
=
useWorkflowContext
()
const
currentNode
=
useMemo
(()
=>
{
return
nodes
.
find
(
node
=>
node
.
id
===
nodeId
)
},
[
nodeId
,
nodes
])
const
outgoers
=
useMemo
(()
=>
{
return
getOutgoers
(
currentNode
!
,
nodes
,
edges
)
},
[
currentNode
,
nodes
,
edges
])
const
handleSelectBlock
=
useCallback
((
type
:
BlockEnum
)
=>
{
handleAddNextNode
(
currentNode
!
,
type
)
},
[
currentNode
,
handleAddNextNode
])
const
branches
=
useMemo
(()
=>
{
if
(
data
.
type
===
BlockEnum
.
IfElse
)
{
return
[
{
id
:
'1'
,
name
:
'Is True'
,
},
{
id
:
'2'
,
name
:
'Is False'
,
},
]
}
},
[
data
])
return
(
<
div
...
...
@@ -80,10 +55,9 @@ const BaseNode: FC<BaseNodeProps> = ({
<
div
className=
'px-3 pt-1 pb-1 text-xs text-gray-500'
>
Define the initial parameters for launching a workflow
</
div
>
<
AddNode
outgoers=
{
outgoers
}
branches=
{
branches
}
onAddNextNode=
{
handleSelectBlock
}
<
BlockSelector
onSelect=
{
()
=>
{}
}
asChild
/>
</
div
>
)
...
...
web/app/components/workflow/nodes/index.tsx
View file @
ea76f462
...
...
@@ -25,7 +25,7 @@ const CustomNode = ({
type=
'target'
position=
{
Position
.
Left
}
className=
{
`
!top-
4
!left-0 !w-4 !h-4 !bg-transparent !rounded-none !outline-none !border-none !translate-y-0 z-[1]
!top-
[17px]
!left-0 !w-4 !h-4 !bg-transparent !rounded-none !outline-none !border-none !translate-y-0 z-[1]
after:absolute after:w-0.5 after:h-2 after:-left-0.5 after:top-1 after:bg-primary-500
${data.type === BlockEnum.Start && 'opacity-0'}
`
}
...
...
@@ -41,7 +41,7 @@ const CustomNode = ({
type=
'source'
position=
{
Position
.
Right
}
className=
{
`
!top-
4 !right-0 !w-4 !h-4 !bg-transparent !rounded-none !outline-none !border-none !translate-y-0
!top-
[17px] !right-0 !w-4 !h-4 !bg-transparent !rounded-none !outline-none !border-none !translate-y-0 z-[1]
after:absolute after:w-0.5 after:h-2 after:-right-0.5 after:top-1 after:bg-primary-500
`
}
/>
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment