mirror of https://github.com/langgenius/dify.git
chore
This commit is contained in:
parent
f09f91e25a
commit
94cda3e837
|
|
@ -34,16 +34,19 @@ const initialNodes = [
|
|||
const initialEdges = [
|
||||
{
|
||||
id: '0',
|
||||
type: 'custom',
|
||||
source: '1',
|
||||
target: '2',
|
||||
},
|
||||
{
|
||||
id: '1',
|
||||
type: 'custom',
|
||||
source: '2',
|
||||
target: '3',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
type: 'custom',
|
||||
source: '2',
|
||||
target: '4',
|
||||
},
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ type NodeSelectorProps = {
|
|||
trigger?: (open: boolean) => React.ReactNode
|
||||
placement?: Placement
|
||||
offset?: OffsetOptions
|
||||
triggerStyle?: React.CSSProperties
|
||||
popupClassName?: string
|
||||
asChild?: boolean
|
||||
}
|
||||
|
|
@ -32,6 +33,7 @@ const NodeSelector: FC<NodeSelectorProps> = ({
|
|||
trigger,
|
||||
placement = 'right',
|
||||
offset = 6,
|
||||
triggerStyle,
|
||||
popupClassName,
|
||||
asChild,
|
||||
}) => {
|
||||
|
|
@ -50,10 +52,10 @@ const NodeSelector: FC<NodeSelectorProps> = ({
|
|||
: (
|
||||
<div
|
||||
className={`
|
||||
hidden absolute -right-2 top-4 items-center justify-center
|
||||
flex items-center justify-center
|
||||
w-4 h-4 rounded-full bg-primary-600 cursor-pointer z-10 group-hover:flex
|
||||
${open && '!flex'}
|
||||
`}
|
||||
style={triggerStyle}
|
||||
>
|
||||
<Plus02 className='w-2.5 h-2.5 text-white' />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,7 @@
|
|||
'use client'
|
||||
|
||||
import { createContext, useContext } from 'use-context-selector'
|
||||
import type {
|
||||
Edge,
|
||||
ReactFlowInstance,
|
||||
} from 'reactflow'
|
||||
import type { Edge } from 'reactflow'
|
||||
import type {
|
||||
BlockEnum,
|
||||
Node,
|
||||
|
|
@ -12,11 +9,8 @@ import type {
|
|||
|
||||
export type WorkflowContextValue = {
|
||||
mode: string
|
||||
reactFlow: ReactFlowInstance
|
||||
nodes: Node[]
|
||||
edges: Edge[]
|
||||
selectedNodeId?: string
|
||||
handleSelectedNodeIdChange: (nodeId: string) => void
|
||||
selectedNode?: Node
|
||||
handleAddNextNode: (prevNode: Node, nextNodeType: BlockEnum) => void
|
||||
handleUpdateNodeData: (nodeId: string, data: Node['data']) => void
|
||||
|
|
@ -24,10 +18,8 @@ export type WorkflowContextValue = {
|
|||
|
||||
export const WorkflowContext = createContext<WorkflowContextValue>({
|
||||
mode: 'workflow',
|
||||
reactFlow: null as any,
|
||||
nodes: [],
|
||||
edges: [],
|
||||
handleSelectedNodeIdChange: () => {},
|
||||
handleAddNextNode: () => {},
|
||||
handleUpdateNodeData: () => {},
|
||||
})
|
||||
|
|
|
|||
|
|
@ -3,8 +3,11 @@ import type { EdgeProps } from 'reactflow'
|
|||
import {
|
||||
BaseEdge,
|
||||
EdgeLabelRenderer,
|
||||
getSmoothStepPath,
|
||||
Position,
|
||||
getSimpleBezierPath,
|
||||
} from 'reactflow'
|
||||
import BlockSelector from './block-selector'
|
||||
import { useStore } from './store'
|
||||
|
||||
const CustomEdge = ({
|
||||
id,
|
||||
|
|
@ -12,38 +15,50 @@ const CustomEdge = ({
|
|||
sourceY,
|
||||
targetX,
|
||||
targetY,
|
||||
selected,
|
||||
}: EdgeProps) => {
|
||||
const hoveringEdgeId = useStore(state => state.hoveringEdgeId)
|
||||
const [
|
||||
edgePath,
|
||||
labelX,
|
||||
labelY,
|
||||
] = getSmoothStepPath({
|
||||
] = getSimpleBezierPath({
|
||||
sourceX,
|
||||
sourceY,
|
||||
sourcePosition: Position.Right,
|
||||
targetX,
|
||||
targetY,
|
||||
borderRadius: 30,
|
||||
offset: -20,
|
||||
targetPosition: Position.Left,
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
<BaseEdge id={id} path={edgePath} style={{ strokeWidth: 5 }} />
|
||||
<BaseEdge
|
||||
id={id}
|
||||
path={edgePath}
|
||||
style={{
|
||||
stroke: selected ? '#2970FF' : '#D0D5DD',
|
||||
strokeWidth: 2,
|
||||
}}
|
||||
/>
|
||||
<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>
|
||||
{
|
||||
hoveringEdgeId === id && (
|
||||
<div
|
||||
className='nopan nodrag'
|
||||
style={{
|
||||
position: 'absolute',
|
||||
transform: `translate(-50%, -50%) translate(${labelX}px, ${labelY}px)`,
|
||||
pointerEvents: 'all',
|
||||
}}
|
||||
>
|
||||
<BlockSelector
|
||||
asChild
|
||||
onSelect={() => {}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</EdgeLabelRenderer>
|
||||
</>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,13 +2,11 @@ import type {
|
|||
Dispatch,
|
||||
SetStateAction,
|
||||
} from 'react'
|
||||
import {
|
||||
useCallback,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { useCallback } from 'react'
|
||||
import produce from 'immer'
|
||||
import type { Edge } from 'reactflow'
|
||||
import type {
|
||||
Edge,
|
||||
} from 'reactflow'
|
||||
import type {
|
||||
BlockEnum,
|
||||
Node,
|
||||
|
|
@ -20,16 +18,7 @@ export const useWorkflow = (
|
|||
edges: Edge[],
|
||||
setNodes: Dispatch<SetStateAction<Node[]>>,
|
||||
setEdges: Dispatch<SetStateAction<Edge[]>>,
|
||||
initialSelectedNodeId?: string,
|
||||
) => {
|
||||
const [selectedNodeId, setSelectedNodeId] = useState(initialSelectedNodeId)
|
||||
|
||||
const handleSelectedNodeIdChange = useCallback((nodeId: string) => setSelectedNodeId(nodeId), [])
|
||||
|
||||
const selectedNode = useMemo(() => {
|
||||
return nodes.find(node => node.id === selectedNodeId)
|
||||
}, [nodes, selectedNodeId])
|
||||
|
||||
const handleAddNextNode = useCallback((prevNode: Node, nextNodeType: BlockEnum) => {
|
||||
const nextNode = {
|
||||
id: `node-${Date.now()}`,
|
||||
|
|
@ -68,9 +57,6 @@ export const useWorkflow = (
|
|||
}, [setNodes])
|
||||
|
||||
return {
|
||||
selectedNodeId,
|
||||
selectedNode,
|
||||
handleSelectedNodeIdChange,
|
||||
handleAddNextNode,
|
||||
handleUpdateNodeData,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import type { FC } from 'react'
|
||||
import { memo } from 'react'
|
||||
import type { Edge } from 'reactflow'
|
||||
import ReactFlow, {
|
||||
Background,
|
||||
ReactFlowProvider,
|
||||
useEdgesState,
|
||||
useNodesState,
|
||||
useReactFlow,
|
||||
} from 'reactflow'
|
||||
import 'reactflow/dist/style.css'
|
||||
import './style.css'
|
||||
|
|
@ -20,6 +20,7 @@ import ZoomInOut from './zoom-in-out'
|
|||
import CustomEdge from './custom-edge'
|
||||
import Panel from './panel'
|
||||
import type { Node } from './types'
|
||||
import { useStore } from './store'
|
||||
|
||||
const nodeTypes = {
|
||||
custom: CustomNode,
|
||||
|
|
@ -28,11 +29,13 @@ const edgeTypes = {
|
|||
custom: CustomEdge,
|
||||
}
|
||||
|
||||
const Workflow = () => {
|
||||
const Workflow = memo(() => {
|
||||
const {
|
||||
nodes,
|
||||
edges,
|
||||
} = useWorkflowContext()
|
||||
const handleEnterEdge = useStore(state => state.handleEnterEdge)
|
||||
const handleLeaveEdge = useStore(state => state.handleLeaveEdge)
|
||||
|
||||
return (
|
||||
<div className='relative w-full h-full'>
|
||||
|
|
@ -44,6 +47,8 @@ const Workflow = () => {
|
|||
edgeTypes={edgeTypes}
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
onEdgeMouseEnter={handleEnterEdge}
|
||||
onEdgeMouseLeave={handleLeaveEdge}
|
||||
>
|
||||
<Background
|
||||
gap={[14, 14]}
|
||||
|
|
@ -52,24 +57,23 @@ const Workflow = () => {
|
|||
</ReactFlow>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})
|
||||
Workflow.displayName = 'Workflow'
|
||||
|
||||
type WorkflowWrapProps = {
|
||||
selectedNodeId?: string
|
||||
nodes: Node[]
|
||||
edges: Edge[]
|
||||
}
|
||||
const WorkflowWrap: FC<WorkflowWrapProps> = ({
|
||||
const WorkflowWrap: FC<WorkflowWrapProps> = memo(({
|
||||
nodes: initialNodes,
|
||||
edges: initialEdges,
|
||||
selectedNodeId: initialSelectedNodeId,
|
||||
}) => {
|
||||
const reactFlow = useReactFlow()
|
||||
const [nodes, setNodes] = useNodesState(initialNodes)
|
||||
const [edges, setEdges] = useEdgesState(initialEdges)
|
||||
|
||||
const {
|
||||
selectedNodeId,
|
||||
handleSelectedNodeIdChange,
|
||||
selectedNode,
|
||||
handleAddNextNode,
|
||||
handleUpdateNodeData,
|
||||
} = useWorkflow(
|
||||
|
|
@ -77,16 +81,11 @@ const WorkflowWrap: FC<WorkflowWrapProps> = ({
|
|||
edges,
|
||||
setNodes,
|
||||
setEdges,
|
||||
initialSelectedNodeId,
|
||||
)
|
||||
|
||||
return (
|
||||
<WorkflowContext.Provider value={{
|
||||
mode: 'workflow',
|
||||
reactFlow,
|
||||
selectedNodeId,
|
||||
handleSelectedNodeIdChange,
|
||||
selectedNode,
|
||||
nodes,
|
||||
edges,
|
||||
handleAddNextNode,
|
||||
|
|
@ -95,7 +94,9 @@ const WorkflowWrap: FC<WorkflowWrapProps> = ({
|
|||
<Workflow />
|
||||
</WorkflowContext.Provider>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
WorkflowWrap.displayName = 'WorkflowWrap'
|
||||
|
||||
const WorkflowWrapWithReactFlowProvider: FC<WorkflowWrapProps> = ({
|
||||
selectedNodeId,
|
||||
|
|
@ -114,4 +115,4 @@ const WorkflowWrapWithReactFlowProvider: FC<WorkflowWrapProps> = ({
|
|||
)
|
||||
}
|
||||
|
||||
export default WorkflowWrapWithReactFlowProvider
|
||||
export default memo(WorkflowWrapWithReactFlowProvider)
|
||||
|
|
|
|||
|
|
@ -8,7 +8,9 @@ import {
|
|||
useMemo,
|
||||
} from 'react'
|
||||
import type { NodeProps } from 'reactflow'
|
||||
import { useWorkflowContext } from '../../context'
|
||||
import { useNodes } from 'reactflow'
|
||||
import { useStore } from '../../store'
|
||||
import type { NodeData } from '../../types'
|
||||
import BlockIcon from '../../block-icon'
|
||||
import BlockSelector from '../../block-selector'
|
||||
import NodeControl from './components/node-control'
|
||||
|
|
@ -22,11 +24,9 @@ const BaseNode: FC<BaseNodeProps> = ({
|
|||
data,
|
||||
children,
|
||||
}) => {
|
||||
const {
|
||||
nodes,
|
||||
selectedNodeId,
|
||||
handleSelectedNodeIdChange,
|
||||
} = useWorkflowContext()
|
||||
const nodes = useNodes<NodeData>()
|
||||
const selectedNodeId = useStore(state => state.selectedNodeId)
|
||||
const handleSelectedNodeId = useStore(state => state.handleSelectedNodeId)
|
||||
const currentNode = useMemo(() => {
|
||||
return nodes.find(node => node.id === nodeId)
|
||||
}, [nodeId, nodes])
|
||||
|
|
@ -38,7 +38,7 @@ const BaseNode: FC<BaseNodeProps> = ({
|
|||
hover:shadow-lg
|
||||
${selectedNodeId === nodeId ? 'border-[2px] border-primary-600' : 'border border-white'}
|
||||
`}
|
||||
onClick={() => handleSelectedNodeIdChange(nodeId || '')}
|
||||
onClick={() => handleSelectedNodeId(nodeId || '')}
|
||||
>
|
||||
<NodeControl />
|
||||
<div className='flex items-center px-3 pt-3 pb-2'>
|
||||
|
|
|
|||
|
|
@ -5,9 +5,11 @@ import type {
|
|||
import {
|
||||
cloneElement,
|
||||
memo,
|
||||
useMemo,
|
||||
} from 'react'
|
||||
import type { NodeProps } from 'reactflow'
|
||||
import { useWorkflowContext } from '../../context'
|
||||
import { useStore } from '../../store'
|
||||
import BlockIcon from '../../block-icon'
|
||||
import NextStep from './components/next-step'
|
||||
import {
|
||||
|
|
@ -26,9 +28,13 @@ const BasePanel: FC<BasePanelProps> = ({
|
|||
children,
|
||||
}) => {
|
||||
const {
|
||||
handleSelectedNodeIdChange,
|
||||
selectedNode,
|
||||
nodes,
|
||||
} = useWorkflowContext()
|
||||
const selectedNodeId = useStore(state => state.selectedNodeId)
|
||||
const handleSelectedNodeId = useStore(state => state.handleSelectedNodeId)
|
||||
const selectedNode = useMemo(() => {
|
||||
return nodes.find(node => node.id === selectedNodeId)
|
||||
}, [nodes, selectedNodeId])
|
||||
|
||||
return (
|
||||
<div className='mr-2 w-[420px] h-full bg-white shadow-lg border-[0.5px] border-gray-200 rounded-2xl z-10 overflow-y-auto'>
|
||||
|
|
@ -47,7 +53,7 @@ const BasePanel: FC<BasePanelProps> = ({
|
|||
<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('')}
|
||||
onClick={() => handleSelectedNodeId('')}
|
||||
>
|
||||
<XClose className='w-4 h-4' />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -3,16 +3,22 @@ import {
|
|||
memo,
|
||||
useMemo,
|
||||
} from 'react'
|
||||
import { useNodes } from 'reactflow'
|
||||
import { useWorkflowContext } from '../context'
|
||||
import { Panel as NodePanel } from '../nodes'
|
||||
import { useStore } from '../store'
|
||||
import WorkflowInfo from './workflow-info'
|
||||
import DebugAndPreview from './debug-and-preview'
|
||||
|
||||
const Panel: FC = () => {
|
||||
const {
|
||||
mode,
|
||||
selectedNode,
|
||||
} = useWorkflowContext()
|
||||
const nodes = useNodes()
|
||||
const selectedNodeId = useStore(state => state.selectedNodeId)
|
||||
const selectedNode = useMemo(() => {
|
||||
return nodes.find(node => node.id === selectedNodeId)
|
||||
}, [nodes, selectedNodeId])
|
||||
const {
|
||||
showWorkflowInfoPanel,
|
||||
showNodePanel,
|
||||
|
|
@ -29,7 +35,7 @@ const Panel: FC = () => {
|
|||
<div className='absolute top-14 right-0 bottom-2 flex'>
|
||||
{
|
||||
showNodePanel && (
|
||||
<NodePanel node={selectedNode!} />
|
||||
<NodePanel node={selectedNode as any} />
|
||||
)
|
||||
}
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
import { create } from 'zustand'
|
||||
import type { EdgeMouseHandler } from 'reactflow'
|
||||
|
||||
type State = {
|
||||
selectedNodeId: string
|
||||
hoveringEdgeId: string
|
||||
}
|
||||
|
||||
type Action = {
|
||||
handleSelectedNodeId: (selectedNodeId: State['selectedNodeId']) => void
|
||||
handleEnterEdge: EdgeMouseHandler
|
||||
handleLeaveEdge: EdgeMouseHandler
|
||||
}
|
||||
|
||||
export const useStore = create<State & Action>(set => ({
|
||||
selectedNodeId: '',
|
||||
handleSelectedNodeId: selectedNodeId => set(() => ({ selectedNodeId })),
|
||||
hoveringEdgeId: '',
|
||||
handleEnterEdge: (_, edge) => set(() => ({ hoveringEdgeId: edge.id })),
|
||||
handleLeaveEdge: () => set(() => ({ hoveringEdgeId: '' })),
|
||||
}))
|
||||
|
|
@ -4,7 +4,7 @@ import {
|
|||
memo,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { useWorkflowContext } from './context'
|
||||
import { useReactFlow } from 'reactflow'
|
||||
import {
|
||||
PortalToFollowElem,
|
||||
PortalToFollowElemContent,
|
||||
|
|
@ -43,7 +43,7 @@ const ZOOM_IN_OUT_OPTIONS = [
|
|||
]
|
||||
|
||||
const ZoomInOut: FC = () => {
|
||||
const { reactFlow } = useWorkflowContext()
|
||||
const reactFlow = useReactFlow()
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
const handleZoom = (type: string) => {
|
||||
|
|
|
|||
|
|
@ -79,7 +79,8 @@
|
|||
"sharp": "^0.33.2",
|
||||
"sortablejs": "^1.15.0",
|
||||
"swr": "^2.1.0",
|
||||
"use-context-selector": "^1.4.1"
|
||||
"use-context-selector": "^1.4.1",
|
||||
"zustand": "^4.5.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@antfu/eslint-config": "^0.36.0",
|
||||
|
|
|
|||
828
web/yarn.lock
828
web/yarn.lock
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue