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
171dd5c7
Commit
171dd5c7
authored
Feb 23, 2024
by
StyleZhang
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
node-handle
parent
b5ed4af2
Changes
8
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
170 additions
and
42 deletions
+170
-42
page.tsx
web/app/(commonLayout)/workflow/page.tsx
+15
-6
index.tsx
web/app/components/workflow/block-selector/index.tsx
+2
-2
hooks.ts
web/app/components/workflow/hooks.ts
+16
-0
next-step.tsx
.../components/workflow/nodes/_base/components/next-step.tsx
+3
-2
node-handle.tsx
...omponents/workflow/nodes/_base/components/node-handle.tsx
+95
-0
node.tsx
web/app/components/workflow/nodes/_base/node.tsx
+0
-5
node.tsx
web/app/components/workflow/nodes/if-else/node.tsx
+23
-3
index.tsx
web/app/components/workflow/nodes/index.tsx
+16
-24
No files found.
web/app/(commonLayout)/workflow/page.tsx
View file @
171dd5c7
...
...
@@ -29,6 +29,12 @@ const initialNodes = [
position
:
{
x
:
738
,
y
:
330
},
data
:
{
type
:
'llm'
},
},
{
id
:
'5'
,
type
:
'custom'
,
position
:
{
x
:
1100
,
y
:
130
},
data
:
{
type
:
'llm'
},
},
]
const
initialEdges
=
[
...
...
@@ -36,20 +42,23 @@ const initialEdges = [
id
:
'0'
,
type
:
'custom'
,
source
:
'1'
,
sourceHandle
:
'source'
,
target
:
'2'
,
},
{
id
:
'1'
,
type
:
'custom'
,
source
:
'2'
,
sourceHandle
:
'condition1'
,
target
:
'3'
,
},
{
id
:
'2'
,
type
:
'custom'
,
source
:
'2'
,
target
:
'4'
,
},
// {
// id: '2',
// type: 'custom',
// source: '2',
// sourceHandle: 'condition2',
// target: '4',
// },
]
const
Page
:
FC
=
()
=>
{
...
...
web/app/components/workflow/block-selector/index.tsx
View file @
171dd5c7
...
...
@@ -29,7 +29,7 @@ type NodeSelectorProps = {
placement
?:
Placement
offset
?:
OffsetOptions
triggerStyle
?:
React
.
CSSProperties
triggerClassName
?:
string
triggerClassName
?:
(
open
:
boolean
)
=>
string
popupClassName
?:
string
asChild
?:
boolean
}
...
...
@@ -68,7 +68,7 @@ const NodeSelector: FC<NodeSelectorProps> = ({
className=
{
`
flex items-center justify-center
w-4 h-4 rounded-full bg-primary-600 cursor-pointer z-10 group-hover:flex
${triggerClassName}
${triggerClassName
?.(open)
}
`
}
style=
{
triggerStyle
}
>
...
...
web/app/components/workflow/hooks.ts
View file @
171dd5c7
...
...
@@ -17,9 +17,17 @@ export const useWorkflow = () => {
const
handleEnterNode
=
useCallback
<
NodeMouseHandler
>
((
_
,
node
)
=>
{
const
{
getNodes
,
setNodes
,
edges
,
setEdges
,
}
=
store
.
getState
()
const
newNodes
=
produce
(
getNodes
(),
(
draft
)
=>
{
const
currentNode
=
draft
.
find
(
n
=>
n
.
id
===
node
.
id
)
if
(
currentNode
)
currentNode
.
data
=
{
...
currentNode
.
data
,
hovering
:
true
}
})
setNodes
(
newNodes
)
const
newEdges
=
produce
(
edges
,
(
draft
)
=>
{
const
connectedEdges
=
getConnectedEdges
([
node
],
edges
)
...
...
@@ -33,9 +41,17 @@ export const useWorkflow = () => {
},
[
store
])
const
handleLeaveNode
=
useCallback
<
NodeMouseHandler
>
((
_
,
node
)
=>
{
const
{
getNodes
,
setNodes
,
edges
,
setEdges
,
}
=
store
.
getState
()
const
newNodes
=
produce
(
getNodes
(),
(
draft
)
=>
{
const
currentNode
=
draft
.
find
(
n
=>
n
.
id
===
node
.
id
)
if
(
currentNode
)
currentNode
.
data
=
{
...
currentNode
.
data
,
hovering
:
false
}
})
setNodes
(
newNodes
)
const
newEdges
=
produce
(
edges
,
(
draft
)
=>
{
const
connectedEdges
=
getConnectedEdges
([
node
],
edges
)
...
...
web/app/components/workflow/nodes/_base/components/next-step.tsx
View file @
171dd5c7
...
...
@@ -17,6 +17,7 @@ const NextStep = () => {
const
store
=
useStoreApi
()
const
selectedNode
=
useStore
(
state
=>
state
.
selectedNode
)
const
outgoers
:
Node
[]
=
getOutgoers
(
selectedNode
as
Node
,
store
.
getState
().
getNodes
(),
store
.
getState
().
edges
)
const
svgHeight
=
outgoers
.
length
>
1
?
(
outgoers
.
length
+
1
)
*
36
+
12
*
outgoers
.
length
:
36
const
renderAddNextNodeTrigger
=
useCallback
((
open
:
boolean
)
=>
{
return
(
...
...
@@ -53,9 +54,9 @@ const NextStep = () => {
<
div
className=
'shrink-0 relative flex items-center justify-center w-9 h-9 bg-white rounded-lg border-[0.5px] border-gray-200 shadow-xs'
>
<
BlockIcon
type=
{
selectedNode
!
.
data
.
type
}
/>
</
div
>
<
svg
className=
'shrink-0 w-6'
>
<
svg
className=
'shrink-0 w-6'
style=
{
{
height
:
svgHeight
}
}
>
{
(
!
outgoers
.
length
||
outgoers
.
length
===
1
)
&&
(
outgoers
.
length
<
2
&&
(
<
g
>
<
path
d=
'M0,18 L24,18'
...
...
web/app/components/workflow/nodes/_base/components/node-handle.tsx
0 → 100644
View file @
171dd5c7
import
type
{
NodeProps
}
from
'reactflow'
import
{
Handle
,
Position
,
getConnectedEdges
,
getIncomers
,
useStoreApi
,
}
from
'reactflow'
import
{
BlockEnum
}
from
'../../../types'
import
type
{
Node
}
from
'../../../types'
import
BlockSelector
from
'../../../block-selector'
export
const
NodeTargetHandle
=
({
id
,
data
,
}:
NodeProps
)
=>
{
const
store
=
useStoreApi
()
const
incomers
=
getIncomers
({
id
}
as
Node
,
store
.
getState
().
getNodes
(),
store
.
getState
().
edges
)
return
(
<>
<
Handle
type=
'target'
position=
{
Position
.
Left
}
className=
{
`
!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 || !incomers.length) && 'opacity-0'}
`
}
isConnectable=
{
data
.
type
!==
BlockEnum
.
Start
}
/>
{
incomers
.
length
===
0
&&
data
.
type
!==
BlockEnum
.
Start
&&
(
<
BlockSelector
onSelect=
{
()
=>
{}
}
asChild
placement=
'left'
triggerClassName=
{
open
=>
`
hidden absolute -left-2 top-4
${data.hovering && '!flex'}
${open && '!flex'}
`
}
/>
)
}
</>
)
}
type
NodeSourceHandleProps
=
{
handleId
?:
string
handleClassName
?:
string
nodeSelectorClassName
?:
string
}
&
Pick
<
NodeProps
,
'id'
|
'data'
>
export
const
NodeSourceHandle
=
({
id
,
data
,
handleId
,
handleClassName
,
nodeSelectorClassName
,
}:
NodeSourceHandleProps
)
=>
{
const
store
=
useStoreApi
()
const
connectedEdges
=
getConnectedEdges
([{
id
}
as
Node
],
store
.
getState
().
edges
)
const
connected
=
connectedEdges
.
find
(
edge
=>
edge
.
sourceHandle
===
handleId
)
return
(
<>
<
Handle
id=
{
handleId
}
type=
'source'
position=
{
Position
.
Right
}
className=
{
`
!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
${!connected && 'opacity-0'}
${handleClassName}
`
}
/>
{
!
connected
&&
(
<
BlockSelector
onSelect=
{
()
=>
{}
}
asChild
triggerClassName=
{
open
=>
`
hidden
${nodeSelectorClassName}
${data.hovering && '!flex'}
${open && '!flex'}
`
}
/>
)
}
</>
)
}
web/app/components/workflow/nodes/_base/node.tsx
View file @
171dd5c7
...
...
@@ -9,7 +9,6 @@ import {
import
type
{
NodeProps
}
from
'reactflow'
import
BlockIcon
from
'../../block-icon'
import
{
useWorkflow
}
from
'../../hooks'
import
BlockSelector
from
'../../block-selector'
import
NodeControl
from
'./components/node-control'
type
BaseNodeProps
=
{
...
...
@@ -48,10 +47,6 @@ 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
>
<
BlockSelector
onSelect=
{
()
=>
{}
}
asChild
/>
</
div
>
)
}
...
...
web/app/components/workflow/nodes/if-else/node.tsx
View file @
171dd5c7
import
type
{
FC
}
from
'react'
import
{
memo
}
from
'react'
import
{
useTranslation
}
from
'react-i18next'
import
type
{
NodeProps
}
from
'reactflow'
import
{
NodeSourceHandle
}
from
'../_base/components/node-handle'
import
{
mockData
}
from
'./mock'
import
{
ComparisonOperator
}
from
'./types'
import
{
isEmptyRelatedOperator
}
from
'./utils'
...
...
@@ -12,12 +14,21 @@ const notTranslateKey = [
ComparisonOperator
.
lessThan
,
ComparisonOperator
.
lessThanOrEqual
,
]
const
Node
:
FC
=
(
)
=>
{
const
Node
=
(
props
:
Pick
<
NodeProps
,
'id'
|
'data'
>
)
=>
{
const
{
t
}
=
useTranslation
()
const
{
conditions
,
logical_operator
}
=
mockData
return
(
<
div
className=
'px-3'
>
<
div
className=
'flex items-center h-6 relative px-1'
>
<
div
className=
'w-full text-right text-gray-700 text-xs font-semibold'
>
IF
</
div
>
<
NodeSourceHandle
{
...
props
}
handleId=
'condition1'
handleClassName=
'!top-1 !-right-3'
nodeSelectorClassName=
'absolute top-1 -right-5'
/>
</
div
>
<
div
className=
'mb-0.5 leading-4 text-[10px] font-medium text-gray-500 uppercase'
>
{
t
(
`${i18nPrefix}.conditions`
)
}
</
div
>
<
div
className=
'space-y-0.5'
>
{
conditions
.
map
((
condition
,
i
)
=>
(
...
...
@@ -34,8 +45,17 @@ const Node: FC = () => {
</
div
>
))
}
</
div
>
<
div
className=
'flex items-center h-6 relative px-1'
>
<
div
className=
'w-full text-right text-gray-700 text-xs font-semibold'
>
ELSE
</
div
>
<
NodeSourceHandle
{
...
props
}
handleId=
'condition2'
handleClassName=
'!top-1 !-right-3'
nodeSelectorClassName=
'absolute top-1 -right-5'
/>
</
div
>
</
div
>
)
}
export
default
Node
export
default
memo
(
Node
)
web/app/components/workflow/nodes/index.tsx
View file @
171dd5c7
import
{
memo
}
from
'react'
import
type
{
NodeProps
}
from
'reactflow'
import
{
Handle
,
Position
,
}
from
'reactflow'
import
type
{
SelectedNode
}
from
'../types'
import
{
BlockEnum
}
from
'../types'
import
{
BlockEnum
,
type
SelectedNode
}
from
'../types'
import
{
NodeComponentMap
,
PanelComponentMap
,
}
from
'./constants'
import
BaseNode
from
'./_base/node'
import
BasePanel
from
'./_base/panel'
import
{
NodeSourceHandle
,
NodeTargetHandle
,
}
from
'./_base/components/node-handle'
const
CustomNode
=
memo
((
props
:
NodeProps
)
=>
{
const
nodeData
=
props
.
data
...
...
@@ -19,27 +18,20 @@ const CustomNode = memo((props: NodeProps) => {
return
(
<>
<
Handle
type=
'target'
position=
{
Position
.
Left
}
className=
{
`
!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
${nodeData.type === BlockEnum.Start && 'opacity-0'}
`
}
isConnectable=
{
nodeData
.
type
!==
BlockEnum
.
Start
}
/>
<
NodeTargetHandle
{
...
props
}
/>
<
BaseNode
{
...
props
}
>
<
NodeComponent
/>
</
BaseNode
>
<
Handle
type=
'source'
position=
{
Position
.
Right
}
className=
{
`
!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
`
}
/>
{
nodeData
.
type
!==
BlockEnum
.
IfElse
&&
(
<
NodeSourceHandle
{
...
props
}
handleClassName=
'!top-[17px] !right-0'
nodeSelectorClassName=
'absolute -right-2 top-4'
handleId=
'source'
/>
)
}
</>
)
})
...
...
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