mirror of
https://git.mirrors.martin98.com/https://github.com/langgenius/dify.git
synced 2025-08-12 19:39:02 +08:00
fix: workflow restore (#3711)
This commit is contained in:
parent
96160837d2
commit
83caffe000
@ -402,3 +402,5 @@ export const TOOL_OUTPUT_STRUCT: Var[] = [
|
|||||||
type: VarType.arrayFile,
|
type: VarType.arrayFile,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
export const WORKFLOW_DATA_UPDATE = 'WORKFLOW_DATA_UPDATE'
|
||||||
|
@ -73,6 +73,7 @@ const Header: FC = () => {
|
|||||||
|
|
||||||
const handleRestore = useCallback(() => {
|
const handleRestore = useCallback(() => {
|
||||||
workflowStore.setState({ isRestoring: false })
|
workflowStore.setState({ isRestoring: false })
|
||||||
|
workflowStore.setState({ backupDraft: undefined })
|
||||||
handleSyncWorkflowDraft(true)
|
handleSyncWorkflowDraft(true)
|
||||||
}, [handleSyncWorkflowDraft, workflowStore])
|
}, [handleSyncWorkflowDraft, workflowStore])
|
||||||
|
|
||||||
|
@ -19,6 +19,7 @@ import type {
|
|||||||
Viewport,
|
Viewport,
|
||||||
} from 'reactflow'
|
} from 'reactflow'
|
||||||
import {
|
import {
|
||||||
|
changeNodesAndEdgesId,
|
||||||
getLayoutByDagre,
|
getLayoutByDagre,
|
||||||
initialEdges,
|
initialEdges,
|
||||||
initialNodes,
|
initialNodes,
|
||||||
@ -39,6 +40,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
AUTO_LAYOUT_OFFSET,
|
AUTO_LAYOUT_OFFSET,
|
||||||
SUPPORT_OUTPUT_VARS_NODE,
|
SUPPORT_OUTPUT_VARS_NODE,
|
||||||
|
WORKFLOW_DATA_UPDATE,
|
||||||
} from '../constants'
|
} from '../constants'
|
||||||
import { findUsedVarNodes, getNodeOutputVars, updateNodeVars } from '../nodes/_base/components/variable/utils'
|
import { findUsedVarNodes, getNodeOutputVars, updateNodeVars } from '../nodes/_base/components/variable/utils'
|
||||||
import { useNodesExtraData } from './use-nodes-data'
|
import { useNodesExtraData } from './use-nodes-data'
|
||||||
@ -56,6 +58,8 @@ import {
|
|||||||
fetchAllCustomTools,
|
fetchAllCustomTools,
|
||||||
} from '@/service/tools'
|
} from '@/service/tools'
|
||||||
import I18n from '@/context/i18n'
|
import I18n from '@/context/i18n'
|
||||||
|
import { useEventEmitterContextContext } from '@/context/event-emitter'
|
||||||
|
|
||||||
export const useIsChatMode = () => {
|
export const useIsChatMode = () => {
|
||||||
const appDetail = useAppStore(s => s.appDetail)
|
const appDetail = useAppStore(s => s.appDetail)
|
||||||
|
|
||||||
@ -69,6 +73,7 @@ export const useWorkflow = () => {
|
|||||||
const workflowStore = useWorkflowStore()
|
const workflowStore = useWorkflowStore()
|
||||||
const nodesExtraData = useNodesExtraData()
|
const nodesExtraData = useNodesExtraData()
|
||||||
const { handleSyncWorkflowDraft } = useNodesSyncDraft()
|
const { handleSyncWorkflowDraft } = useNodesSyncDraft()
|
||||||
|
const { eventEmitter } = useEventEmitterContextContext()
|
||||||
|
|
||||||
const handleLayout = useCallback(async () => {
|
const handleLayout = useCallback(async () => {
|
||||||
workflowStore.setState({ nodeAnimation: true })
|
workflowStore.setState({ nodeAnimation: true })
|
||||||
@ -314,15 +319,21 @@ export const useWorkflow = () => {
|
|||||||
}, [locale])
|
}, [locale])
|
||||||
|
|
||||||
const renderTreeFromRecord = useCallback((nodes: Node[], edges: Edge[], viewport?: Viewport) => {
|
const renderTreeFromRecord = useCallback((nodes: Node[], edges: Edge[], viewport?: Viewport) => {
|
||||||
const { setNodes } = store.getState()
|
const { setViewport } = reactflow
|
||||||
const { setViewport, setEdges } = reactflow
|
|
||||||
|
|
||||||
setNodes(initialNodes(nodes, edges))
|
const [newNodes, newEdges] = changeNodesAndEdgesId(nodes, edges)
|
||||||
setEdges(initialEdges(edges, nodes))
|
|
||||||
|
eventEmitter?.emit({
|
||||||
|
type: WORKFLOW_DATA_UPDATE,
|
||||||
|
payload: {
|
||||||
|
nodes: initialNodes(newNodes, newEdges),
|
||||||
|
edges: initialEdges(newEdges, newNodes),
|
||||||
|
},
|
||||||
|
} as any)
|
||||||
|
|
||||||
if (viewport)
|
if (viewport)
|
||||||
setViewport(viewport)
|
setViewport(viewport)
|
||||||
}, [store, reactflow])
|
}, [reactflow, eventEmitter])
|
||||||
|
|
||||||
const getNode = useCallback((nodeId?: string) => {
|
const getNode = useCallback((nodeId?: string) => {
|
||||||
const { getNodes } = store.getState()
|
const { getNodes } = store.getState()
|
||||||
|
@ -14,6 +14,8 @@ import {
|
|||||||
import ReactFlow, {
|
import ReactFlow, {
|
||||||
Background,
|
Background,
|
||||||
ReactFlowProvider,
|
ReactFlowProvider,
|
||||||
|
useEdgesState,
|
||||||
|
useNodesState,
|
||||||
useOnViewportChange,
|
useOnViewportChange,
|
||||||
} from 'reactflow'
|
} from 'reactflow'
|
||||||
import type { Viewport } from 'reactflow'
|
import type { Viewport } from 'reactflow'
|
||||||
@ -46,9 +48,11 @@ import {
|
|||||||
initialEdges,
|
initialEdges,
|
||||||
initialNodes,
|
initialNodes,
|
||||||
} from './utils'
|
} from './utils'
|
||||||
|
import { WORKFLOW_DATA_UPDATE } from './constants'
|
||||||
import Loading from '@/app/components/base/loading'
|
import Loading from '@/app/components/base/loading'
|
||||||
import { FeaturesProvider } from '@/app/components/base/features'
|
import { FeaturesProvider } from '@/app/components/base/features'
|
||||||
import type { Features as FeaturesData } from '@/app/components/base/features/types'
|
import type { Features as FeaturesData } from '@/app/components/base/features/types'
|
||||||
|
import { useEventEmitterContextContext } from '@/context/event-emitter'
|
||||||
|
|
||||||
const nodeTypes = {
|
const nodeTypes = {
|
||||||
custom: CustomNode,
|
custom: CustomNode,
|
||||||
@ -63,10 +67,12 @@ type WorkflowProps = {
|
|||||||
viewport?: Viewport
|
viewport?: Viewport
|
||||||
}
|
}
|
||||||
const Workflow: FC<WorkflowProps> = memo(({
|
const Workflow: FC<WorkflowProps> = memo(({
|
||||||
nodes,
|
nodes: originalNodes,
|
||||||
edges,
|
edges: originalEdges,
|
||||||
viewport,
|
viewport,
|
||||||
}) => {
|
}) => {
|
||||||
|
const [nodes, setNodes] = useNodesState(originalNodes)
|
||||||
|
const [edges, setEdges] = useEdgesState(originalEdges)
|
||||||
const showFeaturesPanel = useStore(state => state.showFeaturesPanel)
|
const showFeaturesPanel = useStore(state => state.showFeaturesPanel)
|
||||||
const nodeAnimation = useStore(s => s.nodeAnimation)
|
const nodeAnimation = useStore(s => s.nodeAnimation)
|
||||||
const {
|
const {
|
||||||
@ -76,6 +82,15 @@ const Workflow: FC<WorkflowProps> = memo(({
|
|||||||
const { workflowReadOnly } = useWorkflowReadOnly()
|
const { workflowReadOnly } = useWorkflowReadOnly()
|
||||||
const { nodesReadOnly } = useNodesReadOnly()
|
const { nodesReadOnly } = useNodesReadOnly()
|
||||||
|
|
||||||
|
const { eventEmitter } = useEventEmitterContextContext()
|
||||||
|
|
||||||
|
eventEmitter?.useSubscription((v: any) => {
|
||||||
|
if (v.type === WORKFLOW_DATA_UPDATE) {
|
||||||
|
setNodes(v.payload.nodes)
|
||||||
|
setEdges(v.payload.edges)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setAutoFreeze(false)
|
setAutoFreeze(false)
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@ import type { ToolDefaultValue } from '../../../block-selector/types'
|
|||||||
import {
|
import {
|
||||||
useNodesExtraData,
|
useNodesExtraData,
|
||||||
useNodesInteractions,
|
useNodesInteractions,
|
||||||
|
useNodesReadOnly,
|
||||||
} from '../../../hooks'
|
} from '../../../hooks'
|
||||||
import { useStore } from '../../../store'
|
import { useStore } from '../../../store'
|
||||||
|
|
||||||
@ -35,6 +36,7 @@ export const NodeTargetHandle = memo(({
|
|||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(false)
|
||||||
const { handleNodeAdd } = useNodesInteractions()
|
const { handleNodeAdd } = useNodesInteractions()
|
||||||
const nodesExtraData = useNodesExtraData()
|
const nodesExtraData = useNodesExtraData()
|
||||||
|
const { getNodesReadOnly } = useNodesReadOnly()
|
||||||
const connected = data._connectedTargetHandleIds?.includes(handleId)
|
const connected = data._connectedTargetHandleIds?.includes(handleId)
|
||||||
const availablePrevNodes = nodesExtraData[data.type].availablePrevNodes
|
const availablePrevNodes = nodesExtraData[data.type].availablePrevNodes
|
||||||
const isConnectable = !!availablePrevNodes.length
|
const isConnectable = !!availablePrevNodes.length
|
||||||
@ -78,7 +80,7 @@ export const NodeTargetHandle = memo(({
|
|||||||
onClick={handleHandleClick}
|
onClick={handleHandleClick}
|
||||||
>
|
>
|
||||||
{
|
{
|
||||||
!connected && isConnectable && !data._isInvalidConnection && (
|
!connected && isConnectable && !data._isInvalidConnection && !getNodesReadOnly() && (
|
||||||
<BlockSelector
|
<BlockSelector
|
||||||
open={open}
|
open={open}
|
||||||
onOpenChange={handleOpenChange}
|
onOpenChange={handleOpenChange}
|
||||||
@ -113,6 +115,7 @@ export const NodeSourceHandle = memo(({
|
|||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(false)
|
||||||
const { handleNodeAdd } = useNodesInteractions()
|
const { handleNodeAdd } = useNodesInteractions()
|
||||||
const nodesExtraData = useNodesExtraData()
|
const nodesExtraData = useNodesExtraData()
|
||||||
|
const { getNodesReadOnly } = useNodesReadOnly()
|
||||||
const availableNextNodes = nodesExtraData[data.type].availableNextNodes
|
const availableNextNodes = nodesExtraData[data.type].availableNextNodes
|
||||||
const isConnectable = !!availableNextNodes.length
|
const isConnectable = !!availableNextNodes.length
|
||||||
const connected = data._connectedSourceHandleIds?.includes(handleId)
|
const connected = data._connectedSourceHandleIds?.includes(handleId)
|
||||||
@ -159,7 +162,7 @@ export const NodeSourceHandle = memo(({
|
|||||||
onClick={handleHandleClick}
|
onClick={handleHandleClick}
|
||||||
>
|
>
|
||||||
{
|
{
|
||||||
!connected && isConnectable && !data._isInvalidConnection && (
|
!connected && isConnectable && !data._isInvalidConnection && !getNodesReadOnly() && (
|
||||||
<BlockSelector
|
<BlockSelector
|
||||||
open={open}
|
open={open}
|
||||||
onOpenChange={handleOpenChange}
|
onOpenChange={handleOpenChange}
|
||||||
|
@ -58,7 +58,7 @@ const BaseNode: FC<BaseNodeProps> = ({
|
|||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
{
|
{
|
||||||
data.type !== BlockEnum.VariableAssigner && !data._runningStatus && !nodesReadOnly && (
|
data.type !== BlockEnum.VariableAssigner && !data._runningStatus && (
|
||||||
<NodeTargetHandle
|
<NodeTargetHandle
|
||||||
id={id}
|
id={id}
|
||||||
data={data}
|
data={data}
|
||||||
@ -68,7 +68,7 @@ const BaseNode: FC<BaseNodeProps> = ({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
data.type !== BlockEnum.IfElse && data.type !== BlockEnum.QuestionClassifier && !data._runningStatus && !nodesReadOnly && (
|
data.type !== BlockEnum.IfElse && data.type !== BlockEnum.QuestionClassifier && !data._runningStatus && (
|
||||||
<NodeSourceHandle
|
<NodeSourceHandle
|
||||||
id={id}
|
id={id}
|
||||||
data={data}
|
data={data}
|
||||||
|
@ -4,6 +4,7 @@ import {
|
|||||||
getOutgoers,
|
getOutgoers,
|
||||||
} from 'reactflow'
|
} from 'reactflow'
|
||||||
import dagre from 'dagre'
|
import dagre from 'dagre'
|
||||||
|
import { v4 as uuid4 } from 'uuid'
|
||||||
import {
|
import {
|
||||||
cloneDeep,
|
cloneDeep,
|
||||||
uniqBy,
|
uniqBy,
|
||||||
@ -331,3 +332,28 @@ export const getToolCheckParams = (
|
|||||||
language,
|
language,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const changeNodesAndEdgesId = (nodes: Node[], edges: Edge[]) => {
|
||||||
|
const idMap = nodes.reduce((acc, node) => {
|
||||||
|
acc[node.id] = uuid4()
|
||||||
|
|
||||||
|
return acc
|
||||||
|
}, {} as Record<string, string>)
|
||||||
|
|
||||||
|
const newNodes = nodes.map((node) => {
|
||||||
|
return {
|
||||||
|
...node,
|
||||||
|
id: idMap[node.id],
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const newEdges = edges.map((edge) => {
|
||||||
|
return {
|
||||||
|
...edge,
|
||||||
|
source: idMap[edge.source],
|
||||||
|
target: idMap[edge.target],
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return [newNodes, newEdges] as [Node[], Edge[]]
|
||||||
|
}
|
||||||
|
@ -83,6 +83,7 @@
|
|||||||
"sortablejs": "^1.15.0",
|
"sortablejs": "^1.15.0",
|
||||||
"swr": "^2.1.0",
|
"swr": "^2.1.0",
|
||||||
"use-context-selector": "^1.4.1",
|
"use-context-selector": "^1.4.1",
|
||||||
|
"uuid": "^9.0.1",
|
||||||
"zustand": "^4.5.1"
|
"zustand": "^4.5.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@ -104,6 +105,7 @@
|
|||||||
"@types/react-window-infinite-loader": "^1.0.6",
|
"@types/react-window-infinite-loader": "^1.0.6",
|
||||||
"@types/recordrtc": "^5.6.11",
|
"@types/recordrtc": "^5.6.11",
|
||||||
"@types/sortablejs": "^1.15.1",
|
"@types/sortablejs": "^1.15.1",
|
||||||
|
"@types/uuid": "^9.0.8",
|
||||||
"autoprefixer": "^10.4.14",
|
"autoprefixer": "^10.4.14",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"eslint": "^8.36.0",
|
"eslint": "^8.36.0",
|
||||||
|
890
web/yarn.lock
890
web/yarn.lock
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user