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
a17c0e5b
Commit
a17c0e5b
authored
Feb 06, 2024
by
StyleZhang
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
init
parent
20d5fdea
Changes
11
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
273 additions
and
64 deletions
+273
-64
context.tsx
web/app/components/workflow/context.tsx
+20
-0
custom-edge.tsx
web/app/components/workflow/custom-edge.tsx
+26
-4
hooks.ts
web/app/components/workflow/hooks.ts
+10
-3
index.tsx
web/app/components/workflow/index.tsx
+99
-36
node.tsx
web/app/components/workflow/nodes/_base/node.tsx
+11
-1
panel.tsx
web/app/components/workflow/nodes/_base/panel.tsx
+54
-9
constants.ts
web/app/components/workflow/nodes/constants.ts
+11
-0
index.tsx
web/app/components/workflow/nodes/index.tsx
+26
-7
node.tsx
web/app/components/workflow/nodes/start/node.tsx
+3
-2
panel.tsx
web/app/components/workflow/nodes/start/panel.tsx
+4
-2
types.ts
web/app/components/workflow/types.ts
+9
-0
No files found.
web/app/components/workflow/context.tsx
View file @
a17c0e5b
'use client'
import
{
createContext
,
useContext
}
from
'use-context-selector'
import
type
{
Edge
}
from
'reactflow'
import
type
{
Node
}
from
'./types'
export
type
WorkflowContextValue
=
{
nodes
:
Node
[]
edges
:
Edge
[]
selectedNodeId
?:
string
handleSelectedNodeIdChange
:
(
nodeId
:
string
)
=>
void
selectedNode
?:
Node
}
export
const
WorkflowContext
=
createContext
<
WorkflowContextValue
>
({
nodes
:
[],
edges
:
[],
handleSelectedNodeIdChange
:
()
=>
{},
})
export
const
useWorkflowContext
=
()
=>
useContext
(
WorkflowContext
)
web/app/components/workflow/custom-edge.tsx
View file @
a17c0e5b
import
{
memo
}
from
'react'
import
type
{
EdgeProps
}
from
'reactflow'
import
type
{
EdgeProps
}
from
'reactflow'
import
{
import
{
BaseEdge
,
BaseEdge
,
getBezierPath
,
EdgeLabelRenderer
,
getSmoothStepPath
,
}
from
'reactflow'
}
from
'reactflow'
const
CustomEdge
=
({
const
CustomEdge
=
({
...
@@ -11,19 +13,39 @@ const CustomEdge = ({
...
@@ -11,19 +13,39 @@ const CustomEdge = ({
targetX
,
targetX
,
targetY
,
targetY
,
}:
EdgeProps
)
=>
{
}:
EdgeProps
)
=>
{
const
[
edgePath
]
=
getBezierPath
({
const
[
edgePath
,
labelX
,
labelY
,
]
=
getSmoothStepPath
({
sourceX
,
sourceX
,
sourceY
,
sourceY
,
targetX
,
targetX
,
targetY
,
targetY
,
borderRadius
:
30
,
})
})
console
.
log
(
'edgePath'
,
edgePath
)
return
(
return
(
<>
<>
<
BaseEdge
id=
{
id
}
path=
{
edgePath
}
/>
<
BaseEdge
id=
{
id
}
path=
{
edgePath
}
/>
<
EdgeLabelRenderer
>
<
div
className=
{
`
flex items-center px-2 h-6 bg-white rounded-lg shadow-xs
text-[10px] font-semibold text-gray-700
nodrag nopan
`
}
style=
{
{
position
:
'absolute'
,
transform
:
`translate(-50%, -50%) translate(${labelX}px, ${labelY}px)`
,
pointerEvents
:
'all'
,
}
}
>
Topic 2
</
div
>
</
EdgeLabelRenderer
>
</>
</>
)
)
}
}
export
default
CustomEdge
export
default
memo
(
CustomEdge
)
web/app/components/workflow/hooks.ts
View file @
a17c0e5b
import
{
import
{
useCallback
,
useCallback
,
useMemo
,
useState
,
useState
,
}
from
'react'
}
from
'react'
import
type
{
Node
}
from
'./types'
export
const
useWorkflow
=
()
=>
{
export
const
useWorkflow
=
(
nodes
:
Node
[]
)
=>
{
const
[
selectedNodeId
,
setSelectedNodeId
]
=
useState
(
''
)
const
[
selectedNodeId
,
setSelectedNodeId
]
=
useState
(
''
)
const
handleSelectedNodeId
=
useCallback
((
nodeId
:
string
)
=>
setSelectedNodeId
(
nodeId
),
[])
const
handleSelectedNodeIdChange
=
useCallback
((
nodeId
:
string
)
=>
setSelectedNodeId
(
nodeId
),
[])
const
selectedNode
=
useMemo
(()
=>
{
return
nodes
.
find
(
node
=>
node
.
id
===
selectedNodeId
)
},
[
nodes
,
selectedNodeId
])
return
{
return
{
selectedNodeId
,
selectedNodeId
,
handleSelectedNodeId
,
selectedNode
,
handleSelectedNodeIdChange
,
}
}
}
}
web/app/components/workflow/index.tsx
View file @
a17c0e5b
import
type
{
FC
}
from
'react'
import
ReactFlow
,
{
import
ReactFlow
,
{
Background
,
Background
,
ReactFlowProvider
,
useEdgesState
,
useNodesState
,
}
from
'reactflow'
}
from
'reactflow'
import
'reactflow/dist/style.css'
import
'reactflow/dist/style.css'
import
{
WorkflowContext
,
useWorkflowContext
,
}
from
'./context'
import
{
useWorkflow
}
from
'./hooks'
import
Header
from
'./header'
import
Header
from
'./header'
import
CustomNode
from
'./nodes'
import
CustomNode
,
{
Panel
,
}
from
'./nodes'
import
CustomEdge
from
'./custom-edge'
import
CustomEdge
from
'./custom-edge'
const
nodeTypes
=
{
const
nodeTypes
=
{
...
@@ -13,47 +24,69 @@ const edgeTypes = {
...
@@ -13,47 +24,69 @@ const edgeTypes = {
custom
:
CustomEdge
,
custom
:
CustomEdge
,
}
}
const
initialNodes
=
[
{
id
:
'1'
,
type
:
'custom'
,
position
:
{
x
:
330
,
y
:
30
},
data
:
{
type
:
'start'
},
},
{
id
:
'2'
,
type
:
'custom'
,
position
:
{
x
:
330
,
y
:
212
},
data
:
{
type
:
'start'
},
},
{
id
:
'3'
,
type
:
'custom'
,
position
:
{
x
:
150
,
y
:
394
},
data
:
{
type
:
'start'
},
},
{
id
:
'4'
,
type
:
'custom'
,
position
:
{
x
:
510
,
y
:
394
},
data
:
{
type
:
'start'
},
},
]
const
initialEdges
=
[
{
id
:
'1'
,
source
:
'1'
,
target
:
'2'
,
type
:
'custom'
,
},
{
id
:
'2'
,
source
:
'2'
,
target
:
'3'
,
type
:
'custom'
,
},
{
id
:
'3'
,
source
:
'2'
,
target
:
'4'
,
type
:
'custom'
,
},
]
const
Workflow
=
()
=>
{
const
Workflow
=
()
=>
{
const
{
nodes
,
edges
,
}
=
useWorkflowContext
()
return
(
return
(
<
div
className=
'relative w-full h-full'
>
<
div
className=
'relative w-full h-full'
>
<
Header
/>
<
Header
/>
<
Panel
/>
<
ReactFlow
<
ReactFlow
nodeTypes=
{
nodeTypes
}
nodeTypes=
{
nodeTypes
}
edgeTypes=
{
edgeTypes
}
edgeTypes=
{
edgeTypes
}
nodes=
{
[
nodes=
{
nodes
}
{
edges=
{
edges
}
id
:
'start'
,
type
:
'custom'
,
position
:
{
x
:
330
,
y
:
30
},
data
:
{
list
:
[]
},
},
{
id
:
'1'
,
type
:
'custom'
,
position
:
{
x
:
400
,
y
:
250
},
data
:
{
list
:
[]
},
},
{
id
:
'2'
,
type
:
'custom'
,
position
:
{
x
:
100
,
y
:
250
},
data
:
{
list
:
[]
},
},
]
}
edges=
{
[
{
id
:
'e1-2'
,
source
:
'start'
,
target
:
'1'
,
type
:
'custom'
,
},
{
id
:
'e1-3'
,
source
:
'start'
,
target
:
'2'
,
type
:
'custom'
,
},
]
}
>
>
<
Background
<
Background
gap=
{
[
14
,
14
]
}
gap=
{
[
14
,
14
]
}
...
@@ -64,4 +97,34 @@ const Workflow = () => {
...
@@ -64,4 +97,34 @@ const Workflow = () => {
)
)
}
}
export
default
Workflow
const
WorkflowWrap
:
FC
=
()
=>
{
const
[
nodes
]
=
useNodesState
(
initialNodes
)
const
[
edges
]
=
useEdgesState
(
initialEdges
)
const
{
selectedNodeId
,
handleSelectedNodeIdChange
,
selectedNode
,
}
=
useWorkflow
(
nodes
)
return
(
<
WorkflowContext
.
Provider
value=
{
{
selectedNodeId
,
handleSelectedNodeIdChange
,
selectedNode
,
nodes
,
edges
,
}
}
>
<
Workflow
/>
</
WorkflowContext
.
Provider
>
)
}
const
WorkflowWrapWithReactFlowProvider
=
()
=>
{
return
(
<
ReactFlowProvider
>
<
WorkflowWrap
/>
</
ReactFlowProvider
>
)
}
export
default
WorkflowWrapWithReactFlowProvider
web/app/components/workflow/nodes/_base/node.tsx
View file @
a17c0e5b
...
@@ -3,6 +3,8 @@ import type {
...
@@ -3,6 +3,8 @@ import type {
ReactNode
,
ReactNode
,
}
from
'react'
}
from
'react'
import
{
memo
}
from
'react'
import
{
memo
}
from
'react'
import
{
useNodeId
}
from
'reactflow'
import
{
useWorkflowContext
}
from
'../../context'
import
{
Plus
}
from
'@/app/components/base/icons/src/vender/line/general'
import
{
Plus
}
from
'@/app/components/base/icons/src/vender/line/general'
type
BaseNodeProps
=
{
type
BaseNodeProps
=
{
...
@@ -12,12 +14,20 @@ type BaseNodeProps = {
...
@@ -12,12 +14,20 @@ type BaseNodeProps = {
const
BaseNode
:
FC
<
BaseNodeProps
>
=
({
const
BaseNode
:
FC
<
BaseNodeProps
>
=
({
children
,
children
,
})
=>
{
})
=>
{
const
nodeId
=
useNodeId
()
const
{
selectedNodeId
,
handleSelectedNodeIdChange
,
}
=
useWorkflowContext
()
return
(
return
(
<
div
<
div
className=
{
`
className=
{
`
group relative pb-2 w-[296px] bg-[#fcfdff]
border border-white
rounded-2xl shadow-xs
group relative pb-2 w-[296px] bg-[#fcfdff] rounded-2xl shadow-xs
hover:shadow-lg
hover:shadow-lg
${selectedNodeId === nodeId ? 'border-[2px] border-primary-600' : 'border border-white'}
`
}
`
}
onClick=
{
()
=>
handleSelectedNodeIdChange
(
nodeId
||
''
)
}
>
>
<
div
className=
'flex items-center px-3 pt-3 pb-2'
>
<
div
className=
'flex items-center px-3 pt-3 pb-2'
>
<
div
className=
'mr-2'
></
div
>
<
div
className=
'mr-2'
></
div
>
...
...
web/app/components/workflow/nodes/_base/panel.tsx
View file @
a17c0e5b
...
@@ -2,6 +2,14 @@ import type {
...
@@ -2,6 +2,14 @@ import type {
FC
,
FC
,
ReactNode
,
ReactNode
,
}
from
'react'
}
from
'react'
import
{
useState
}
from
'react'
import
{
useWorkflowContext
}
from
'../../context'
import
{
XClose
}
from
'@/app/components/base/icons/src/vender/line/general'
enum
TabEnum
{
Inputs
=
'inputs'
,
Outputs
=
'outputs'
,
}
type
BasePanelProps
=
{
type
BasePanelProps
=
{
defaultElement
?:
ReactNode
defaultElement
?:
ReactNode
...
@@ -14,26 +22,63 @@ const BasePanel: FC<BasePanelProps> = ({
...
@@ -14,26 +22,63 @@ const BasePanel: FC<BasePanelProps> = ({
inputsElement
,
inputsElement
,
ouputsElement
,
ouputsElement
,
})
=>
{
})
=>
{
const
initialActiveTab
=
inputsElement
?
TabEnum
.
Inputs
:
ouputsElement
?
TabEnum
.
Outputs
:
''
const
[
activeTab
,
setActiveTab
]
=
useState
(
initialActiveTab
)
const
{
handleSelectedNodeIdChange
}
=
useWorkflowContext
()
return
(
return
(
<
div
className=
'absolute top-2 right-2 bottom-2 w-[420px]
shadow-lg border-[0.5px] border-gray-200 rounded-2xl
'
>
<
div
className=
'absolute top-2 right-2 bottom-2 w-[420px]
bg-white shadow-lg border-[0.5px] border-gray-200 rounded-2xl z-20
'
>
<
div
className=
'flex items-center px-4 pt-3'
>
<
div
className=
'flex items-center px-4 pt-3'
>
<
div
className=
'mr-2 w-6 h-6'
></
div
>
<
div
className=
'shrink-0 mr-2 w-6 h-6'
></
div
>
<
div
className=
'py-1 text-base text-gray-900 font-semibold '
>
LLM
</
div
>
<
div
className=
'grow py-1 text-base text-gray-900 font-semibold '
>
LLM
</
div
>
<
div
className=
'shrink-0 flex items-center'
>
<
div
className=
'w-6 h-6 cursor-pointer'
onClick=
{
()
=>
handleSelectedNodeIdChange
(
''
)
}
>
<
XClose
className=
'w-4 h-4 text-gray-500'
/>
</
div
>
</
div
>
</
div
>
</
div
>
<
div
className=
'p-2'
>
<
div
className=
'p-2'
>
<
div
className=
'py-[5px] pl-1.5 pr-2 text-xs text-gray-400'
>
<
div
className=
'py-[5px] pl-1.5 pr-2 text-xs text-gray-400'
>
Add description...
Add description...
</
div
>
</
div
>
</
div
>
</
div
>
<
div
className=
'flex items-center px-4 h-[42px]'
>
{
inputs
(
inputsElement
||
ouputsElement
)
&&
(
</
div
>
<
div
className=
'flex items-center px-4 h-[42px]'
>
{
inputsElement
&&
(
<
div
className=
'cursor-pointer'
onClick=
{
()
=>
setActiveTab
(
TabEnum
.
Inputs
)
}
>
inputs
</
div
>
)
}
{
ouputsElement
&&
(
<
div
className=
'ml-4 cursor-pointer'
onClick=
{
()
=>
setActiveTab
(
TabEnum
.
Outputs
)
}
>
outpus
</
div
>
)
}
</
div
>
)
}
<
div
className=
'py-2 border-t-[0.5px] border-b-[0.5px] border-black/5'
>
<
div
className=
'py-2 border-t-[0.5px] border-b-[0.5px] border-black/5'
>
{
defaultElement
}
{
defaultElement
}
{
inputsElement
}
{
activeTab
===
TabEnum
.
Inputs
&&
inputsElement
}
{
ouputsElement
}
{
activeTab
===
TabEnum
.
Outputs
&&
ouputsElement
}
</
div
>
<
div
className=
'p-4'
>
next step
</
div
>
</
div
>
<
div
className=
'p-4'
></
div
>
</
div
>
</
div
>
)
)
}
}
...
...
web/app/components/workflow/nodes/constants.ts
0 → 100644
View file @
a17c0e5b
import
type
{
ComponentType
}
from
'react'
import
StartNode
from
'./start/node'
import
StartPanel
from
'./start/panel'
export
const
NodeMap
:
Record
<
string
,
ComponentType
>
=
{
start
:
StartNode
,
}
export
const
PanelMap
:
Record
<
string
,
ComponentType
>
=
{
start
:
StartPanel
,
}
web/app/components/workflow/nodes/index.tsx
View file @
a17c0e5b
import
{
memo
}
from
'react'
import
{
import
{
Handle
,
Handle
,
Position
,
Position
,
useNodeId
,
}
from
'reactflow'
}
from
'reactflow'
import
StartNode
from
'./start/node
'
import
{
useWorkflowContext
}
from
'../context
'
import
{
const
NodeMap
=
{
NodeMap
,
'start-node'
:
StartNode
,
PanelMap
,
}
}
from
'./constants'
const
CustomNode
=
()
=>
{
const
CustomNode
=
()
=>
{
const
nodeId
=
useNodeId
()
const
{
nodes
}
=
useWorkflowContext
()
const
currentNode
=
nodes
.
find
(
node
=>
node
.
id
===
nodeId
)
const
NodeComponent
=
NodeMap
[
currentNode
!
.
data
.
type
as
string
]
return
(
return
(
<>
<>
<
Handle
<
Handle
...
@@ -16,7 +23,7 @@ const CustomNode = () => {
...
@@ -16,7 +23,7 @@ const CustomNode = () => {
position=
{
Position
.
Top
}
position=
{
Position
.
Top
}
className=
'!-top-0.5 !w-2 !h-0.5 !bg-primary-500 !rounded-none !border-none !min-h-[2px]'
className=
'!-top-0.5 !w-2 !h-0.5 !bg-primary-500 !rounded-none !border-none !min-h-[2px]'
/>
/>
<
StartNode
/>
<
NodeComponent
/>
<
Handle
<
Handle
type=
'source'
type=
'source'
position=
{
Position
.
Bottom
}
position=
{
Position
.
Bottom
}
...
@@ -26,4 +33,16 @@ const CustomNode = () => {
...
@@ -26,4 +33,16 @@ const CustomNode = () => {
)
)
}
}
export
default
CustomNode
export
const
Panel
=
()
=>
{
const
{
selectedNode
}
=
useWorkflowContext
()
const
PanelComponent
=
PanelMap
[
selectedNode
?.
data
.
type
||
''
]
if
(
!
PanelComponent
)
return
null
return
(
<
PanelComponent
/>
)
}
export
default
memo
(
CustomNode
)
web/app/components/workflow/nodes/start/node.tsx
View file @
a17c0e5b
import
type
{
FC
}
from
'react'
import
BaseNode
from
'../_base/node'
import
BaseNode
from
'../_base/node'
const
Node
=
()
=>
{
const
Node
:
FC
=
()
=>
{
return
(
return
(
<
BaseNode
>
<
BaseNode
>
start node
<
div
>
start node
</
div
>
</
BaseNode
>
</
BaseNode
>
)
)
}
}
...
...
web/app/components/workflow/nodes/start/panel.tsx
View file @
a17c0e5b
import
type
{
FC
}
from
'react'
import
BasePanel
from
'../_base/panel'
import
BasePanel
from
'../_base/panel'
const
Panel
=
()
=>
{
const
Panel
:
FC
=
()
=>
{
return
(
return
(
<
BasePanel
<
BasePanel
inputsElement=
{
<
div
>
start panel
</
div
>
}
inputsElement=
{
<
div
>
start panel inputs
</
div
>
}
ouputsElement=
{
<
div
>
start panel outputs
</
div
>
}
/>
/>
)
)
}
}
...
...
web/app/components/workflow/types.ts
View file @
a17c0e5b
import
type
{
Node
as
ReactFlowNode
}
from
'reactflow'
export
type
NodeData
=
{
type
:
string
name
?:
string
icon
?:
any
description
?:
string
}
export
type
Node
=
ReactFlowNode
<
NodeData
>
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