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
13a54c3f
Commit
13a54c3f
authored
Feb 20, 2024
by
StyleZhang
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
block-selector edit
parent
d58a1b13
Changes
8
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
249 additions
and
157 deletions
+249
-157
context.tsx
web/app/components/workflow/block-selector/context.tsx
+123
-0
index.tsx
web/app/components/workflow/block-selector/index.tsx
+17
-74
tabs.tsx
web/app/components/workflow/block-selector/tabs.tsx
+9
-10
types.ts
web/app/components/workflow/block-selector/types.ts
+3
-0
index.tsx
web/app/components/workflow/index.tsx
+4
-1
next-step.tsx
.../components/workflow/nodes/_base/components/next-step.tsx
+54
-46
node.tsx
web/app/components/workflow/nodes/_base/node.tsx
+29
-23
panel.tsx
web/app/components/workflow/nodes/_base/panel.tsx
+10
-3
No files found.
web/app/components/workflow/block-selector/context.tsx
0 → 100644
View file @
13a54c3f
'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
className
?:
string
callback
?:
OnSelect
}
export
type
BlockSelectorContextValue
=
{
from
:
string
open
:
boolean
setOpen
:
(
open
:
boolean
)
=>
void
referenceRef
:
any
handleToggle
:
(
v
:
UpdateParams
)
=>
void
}
export
const
BlockSelectorContext
=
createContext
<
BlockSelectorContextValue
>
({
from
:
''
,
open
:
false
,
setOpen
:
()
=>
{},
referenceRef
:
null
,
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
,
placement
,
offset
,
className
,
callback
,
}:
UpdateParams
)
=>
{
setFrom
(
from
||
'node'
)
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
,
}
}
>
{
children
}
{
open
&&
(
<
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 @
13a54c3f
import
type
{
FC
,
ReactElement
}
from
'react'
import
{
memo
,
useState
,
}
from
'react'
import
type
{
OffsetOptions
,
Placement
,
}
from
'@floating-ui/react'
import
{
FloatingPortal
,
flip
,
offset
,
shift
,
useClick
,
useDismiss
,
useFloating
,
useInteractions
,
}
from
'@floating-ui/react'
import
type
{
FC
}
from
'react'
import
{
memo
}
from
'react'
import
Tabs
from
'./tabs'
import
type
{
OnSelect
}
from
'./types'
import
{
SearchLg
}
from
'@/app/components/base/icons/src/vender/line/general'
type
NodeSelectorProps
=
{
placement
?:
Placement
offset
?:
OffsetOptions
onSelect
:
OnSelect
className
?:
string
children
:
(
props
:
any
)
=>
ReactElement
}
const
NodeSelector
:
FC
<
NodeSelectorProps
>
=
({
placement
=
'top'
,
offset
:
offsetValue
=
0
,
onSelect
,
className
,
children
,
})
=>
{
const
[
open
,
setOpen
]
=
useState
(
false
)
const
{
refs
,
floatingStyles
,
context
}
=
useFloating
({
placement
,
strategy
:
'fixed'
,
open
,
onOpenChange
:
setOpen
,
middleware
:
[
flip
(),
shift
(),
offset
(
offsetValue
),
],
})
const
click
=
useClick
(
context
)
const
dismiss
=
useDismiss
(
context
,
{
bubbles
:
false
,
})
const
{
getReferenceProps
,
getFloatingProps
}
=
useInteractions
([
click
,
dismiss
,
])
return
(
<>
{
children
({
...
getReferenceProps
(),
ref
:
refs
.
setReference
,
open
})
}
{
open
&&
(
<
FloatingPortal
>
<
div
ref=
{
refs
.
setFloating
}
style=
{
floatingStyles
}
{
...
getFloatingProps
()}
className=
'z-[1000]'
>
<
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'
/>
</
div
>
</
div
>
<
Tabs
/>
</
div
>
</
div
>
</
FloatingPortal
>
)
}
</>
<
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'
/>
</
div
>
</
div
>
<
Tabs
onSelect=
{
onSelect
}
/>
</
div
>
)
}
...
...
web/app/components/workflow/block-selector/tabs.tsx
View file @
13a54c3f
import
type
{
FC
}
from
'react'
import
{
memo
,
useState
,
}
from
'react'
import
{
useNodeId
}
from
'reactflow'
import
BlockIcon
from
'../block-icon'
import
{
useWorkflowContext
}
from
'../context
'
import
type
{
OnSelect
}
from
'./types
'
import
{
BLOCK_CLASSIFICATIONS
,
BLOCK_GROUP_BY_CLASSIFICATION
,
TABS
,
}
from
'./constants'
const
Tabs
=
()
=>
{
const
{
nodes
,
handleAddNextNode
,
}
=
useWorkflowContext
()
export
type
TabsProps
=
{
onSelect
:
OnSelect
}
const
Tabs
:
FC
<
TabsProps
>
=
({
onSelect
,
})
=>
{
const
[
activeTab
,
setActiveTab
]
=
useState
(
TABS
[
0
].
key
)
const
nodeId
=
useNodeId
()
const
currentNode
=
nodes
.
find
(
node
=>
node
.
id
===
nodeId
)
return
(
<
div
>
...
...
@@ -59,7 +58,7 @@ const Tabs = () => {
className=
'flex items-center px-3 h-8 rounded-lg hover:bg-gray-50 cursor-pointer'
onClick=
{
(
e
)
=>
{
e
.
stopPropagation
()
handleAddNextNode
(
currentNode
!
,
block
.
type
)
onSelect
(
block
.
type
)
}
}
>
<
BlockIcon
...
...
web/app/components/workflow/block-selector/types.ts
0 → 100644
View file @
13a54c3f
import
type
{
BlockEnum
}
from
'../types'
export
type
OnSelect
=
(
type
:
BlockEnum
)
=>
void
web/app/components/workflow/index.tsx
View file @
13a54c3f
...
...
@@ -21,6 +21,7 @@ import AppInfoPanel from './app-info-panel'
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
,
...
...
@@ -93,7 +94,9 @@ const WorkflowWrap: FC<WorkflowWrapProps> = ({
handleAddNextNode
,
handleUpdateNodeData
,
}
}
>
<
Workflow
/>
<
BlockSelectorContextProvider
>
<
Workflow
/>
</
BlockSelectorContextProvider
>
</
WorkflowContext
.
Provider
>
)
}
...
...
web/app/components/workflow/nodes/_base/components/next-step.tsx
View file @
13a54c3f
...
...
@@ -6,9 +6,12 @@ import {
}
from
'react'
import
{
getOutgoers
}
from
'reactflow'
import
BlockIcon
from
'../../../block-icon'
import
type
{
Node
}
from
'../../../types'
import
type
{
BlockEnum
,
Node
,
}
from
'../../../types'
import
{
useWorkflowContext
}
from
'../../../context'
import
BlockSelector
from
'../../../block-selector
'
import
{
useBlockSelectorContext
}
from
'../../../block-selector/context
'
import
{
Plus
}
from
'@/app/components/base/icons/src/vender/line/general'
import
Button
from
'@/app/components/base/button'
...
...
@@ -18,49 +21,23 @@ 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
renderBlockSelectorChildren
=
useCallback
(({
open
,
ref
,
...
restProps
}:
any
)
=>
{
return
(
<
div
{
...
restProps
}
ref=
{
ref
}
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
renderBlockSelectorButtonChildren
=
useCallback
(({
open
,
ref
,
...
restProps
}:
any
)
=>
{
return
(
<
div
{
...
restProps
}
ref=
{
ref
}
>
<
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
>
)
},
[])
const
handleSelectBlock
=
useCallback
((
type
:
BlockEnum
)
=>
{
handleAddNextNode
(
selectedNode
,
type
)
},
[
selectedNode
,
handleAddNextNode
])
return
(
<
div
className=
'flex py-1'
>
...
...
@@ -71,9 +48,26 @@ const NextStep: FC<NextStepProps> = ({
<
div
className=
'grow'
>
{
!
outgoers
.
length
&&
(
<
BlockSelector
className=
'!w-[328px]'
>
{
renderBlockSelectorChildren
}
</
BlockSelector
>
<
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
>
)
}
{
...
...
@@ -87,12 +81,26 @@ const NextStep: FC<NextStepProps> = ({
className=
'shrink-0 mr-1.5'
/>
<
div
className=
'grow'
>
{
outgoer
.
data
.
name
}
</
div
>
<
BlockSelector
placement=
'top-end'
offset=
{
6
}
<
div
ref=
{
from
===
'panel'
?
referenceRef
:
null
}
onClick=
{
()
=>
{
handleToggle
({
from
:
'panel'
,
className
:
'w-[328px]'
,
placement
:
'top-end'
,
offset
:
6
,
})
}
}
>
{
renderBlockSelectorButtonChildren
}
</
BlockSelector
>
<
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
>
</
div
>
))
}
...
...
web/app/components/workflow/nodes/_base/node.tsx
View file @
13a54c3f
...
...
@@ -11,7 +11,8 @@ import {
import
type
{
NodeProps
}
from
'reactflow'
import
{
getOutgoers
}
from
'reactflow'
import
{
useWorkflowContext
}
from
'../../context'
import
BlockSelector
from
'../../block-selector'
import
type
{
BlockEnum
}
from
'../../types'
import
{
useBlockSelectorContext
}
from
'../../block-selector/context'
import
NodeControl
from
'../../node-control'
import
BlockIcon
from
'../../block-icon'
import
{
Plus02
}
from
'@/app/components/base/icons/src/vender/line/general'
...
...
@@ -30,30 +31,23 @@ const BaseNode: FC<BaseNodeProps> = ({
edges
,
selectedNodeId
,
handleSelectedNodeIdChange
,
handleAddNextNode
,
}
=
useWorkflowContext
()
const
{
from
,
open
,
referenceRef
,
handleToggle
,
}
=
useBlockSelectorContext
()
const
currentNode
=
useMemo
(()
=>
{
return
nodes
.
find
(
node
=>
node
.
id
===
nodeId
)
},
[
nodeId
,
nodes
])
const
outgoers
=
useMemo
(()
=>
{
return
getOutgoers
(
currentNode
!
,
nodes
,
edges
)
},
[
currentNode
,
nodes
,
edges
])
const
renderBlockSelectorChildren
=
useCallback
(({
open
,
ref
,
...
restProps
}:
any
)
=>
{
return
(
<
div
onClick=
{
e
=>
e
.
stopPropagation
()
}
>
<
div
{
...
restProps
}
ref=
{
ref
}
className=
{
`
hidden absolute -bottom-2 left-1/2 -translate-x-1/2 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
>
</
div
>
)
},
[])
const
handleSelectBlock
=
useCallback
((
type
:
BlockEnum
)
=>
{
handleAddNextNode
(
currentNode
!
,
type
)
},
[
currentNode
,
handleAddNextNode
])
return
(
<
div
...
...
@@ -81,12 +75,24 @@ const BaseNode: FC<BaseNodeProps> = ({
</
div
>
{
!
outgoers
.
length
&&
(
<
BlockSelector
placement=
'right'
offset=
{
6
}
<
div
ref=
{
from
===
'node'
?
referenceRef
:
null
}
onClick=
{
(
e
)
=>
{
e
.
stopPropagation
()
handleToggle
({
placement
:
'right'
,
offset
:
6
,
callback
:
handleSelectBlock
,
})
}
}
className=
{
`
hidden absolute -bottom-2 left-1/2 -translate-x-1/2 items-center justify-center
w-4 h-4 rounded-full bg-primary-600 cursor-pointer z-10 group-hover:flex
${open && from === 'node' && '!flex'}
`
}
>
{
renderBlockSelectorChildren
}
</
BlockSelector
>
<
Plus02
className=
'w-2.5 h-2.5 text-white'
/>
</
div
>
)
}
</
div
>
...
...
web/app/components/workflow/nodes/_base/panel.tsx
View file @
13a54c3f
...
...
@@ -10,7 +10,10 @@ import type { NodeProps } from 'reactflow'
import
{
useWorkflowContext
}
from
'../../context'
import
BlockIcon
from
'../../block-icon'
import
NextStep
from
'./components/next-step'
import
{
XClose
}
from
'@/app/components/base/icons/src/vender/line/general'
import
{
DotsHorizontal
,
XClose
,
}
from
'@/app/components/base/icons/src/vender/line/general'
import
{
GitBranch01
}
from
'@/app/components/base/icons/src/vender/line/development'
type
BasePanelProps
=
{
...
...
@@ -37,12 +40,16 @@ const BasePanel: FC<BasePanelProps> = ({
size=
'md'
/>
<
div
className=
'grow py-1 text-base text-gray-900 font-semibold '
>
{
data
.
title
}
</
div
>
<
div
className=
'shrink-0 flex items-center'
>
<
div
className=
'shrink-0 flex items-center text-gray-500'
>
<
div
className=
'flex items-center justify-center w-6 h-6 cursor-pointer'
>
<
DotsHorizontal
className=
'w-4 h-4'
/>
</
div
>
<
div
className=
'mx-3 w-[1px] h-3.5 bg-gray-200'
/>
<
div
className=
'flex items-center justify-center w-6 h-6 cursor-pointer'
onClick=
{
()
=>
handleSelectedNodeIdChange
(
''
)
}
>
<
XClose
className=
'w-4 h-4
text-gray-500
'
/>
<
XClose
className=
'w-4 h-4'
/>
</
div
>
</
div
>
</
div
>
...
...
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