When trying to duplicate a node in a workflow while a different node is selected, the selected node is duplicated unexpectedly #5167

Closed
opened 2026-02-21 18:09:43 -05:00 by yindo · 1 comment
Owner

Originally created by @edo1z on GitHub (Aug 22, 2024).

Self Checks

  • This is only for bug report, if you would like to ask a question, please head to Discussions.
  • I have searched for existing issues search for existing issues, including closed ones.
  • I confirm that I am using English to submit this report (我已阅读并同意 Language Policy).
  • [FOR CHINESE USERS] 请务必使用英文提交 Issue,否则会被关闭。谢谢!:)
  • Please do not modify this template :) and fill in all the required fields.

Dify version

0.7.1

Cloud or Self Hosted

Self Hosted (Docker)

Steps to reproduce

  1. Select a node different from the one you want to duplicate.
  2. Hover over the node you want to duplicate and click "Duplicate" from the menu that appears in the upper right corner.

88ec36fc1986b5d3e9a01e0ce68bdb57

This issue may be a duplicate of #6792.

✔️ Expected Behavior

  • The node you want to duplicate should be duplicated.

Actual Behavior

The selected node, which is different from the one you intended to duplicate, is duplicated instead.

Originally created by @edo1z on GitHub (Aug 22, 2024). ### Self Checks - [X] This is only for bug report, if you would like to ask a question, please head to [Discussions](https://github.com/langgenius/dify/discussions/categories/general). - [X] I have searched for existing issues [search for existing issues](https://github.com/langgenius/dify/issues), including closed ones. - [X] I confirm that I am using English to submit this report (我已阅读并同意 [Language Policy](https://github.com/langgenius/dify/issues/1542)). - [X] [FOR CHINESE USERS] 请务必使用英文提交 Issue,否则会被关闭。谢谢!:) - [X] Please do not modify this template :) and fill in all the required fields. ### Dify version 0.7.1 ### Cloud or Self Hosted Self Hosted (Docker) ### Steps to reproduce 1. Select a node different from the one you want to duplicate. 2. Hover over the node you want to duplicate and click "Duplicate" from the menu that appears in the upper right corner. ![88ec36fc1986b5d3e9a01e0ce68bdb57](https://github.com/user-attachments/assets/c93f7e7f-b4ee-45c2-9bd8-803463434764) This issue may be a duplicate of #6792. ### ✔️ Expected Behavior - The node you want to duplicate should be duplicated. ### ❌ Actual Behavior The selected node, which is different from the one you intended to duplicate, is duplicated instead.
yindo added the 🐞 bug label 2026-02-21 18:09:43 -05:00
yindo closed this issue 2026-02-21 18:09:43 -05:00
Author
Owner

@dosubot[bot] commented on GitHub (Aug 22, 2024):

Hi @edo1z! I'm here to help you with any bugs, questions, or contributions you have for the Dify repository. Let's get to the bottom of this issue together!

The issue you're experiencing occurs due to the logic in the handleNodesCopy function. This function sets the clipboard elements to the selected node if it is not a start node. When the handleNodesPaste function is called, it pastes the node(s) from the clipboard, which includes the selected node.

Here is the relevant code snippet:

const handleNodesCopy = useCallback(() => {
  if (getNodesReadOnly())
    return

  const { setClipboardElements } = workflowStore.getState()

  const {
    getNodes,
  } = store.getState()

  const nodes = getNodes()
  const bundledNodes = nodes.filter(node => node.data._isBundled && node.data.type !== BlockEnum.Start && !node.data.isInIteration)

  if (bundledNodes.length) {
    setClipboardElements(bundledNodes)
    return
  }

  const selectedNode = nodes.find(node => node.data.selected && node.data.type !== BlockEnum.Start)

  if (selectedNode)
    setClipboardElements([selectedNode])
}, [getNodesReadOnly, store, workflowStore])

The handleNodesPaste function then pastes the node(s) from the clipboard:

const handleNodesPaste = useCallback(() => {
  if (getNodesReadOnly())
    return

  const {
    clipboardElements,
    mousePosition,
  } = workflowStore.getState()

  const {
    getNodes,
    setNodes,
  } = store.getState()

  const nodesToPaste: Node[] = []
  const nodes = getNodes()

  if (clipboardElements.length) {
    const { x, y } = getTopLeftNodePosition(clipboardElements)
    const { screenToFlowPosition } = reactflow
    const currentPosition = screenToFlowPosition({ x: mousePosition.pageX, y: mousePosition.pageY })
    const offsetX = currentPosition.x - x
    const offsetY = currentPosition.y - y
    clipboardElements.forEach((nodeToPaste, index) => {
      const nodeType = nodeToPaste.data.type
      const newNode = generateNewNode({
        type: nodeToPaste.type,
        data: {
          ...NODES_INITIAL_DATA[nodeType],
          ...nodeToPaste.data,
          selected: false,
          _isBundled: false,
          _connectedSourceHandleIds: [],
          _connectedTargetHandleIds: [],
          title: genNewNodeTitleFromOld(nodeToPaste.data.title),
        },
        position: {
          x: nodeToPaste.position.x + offsetX,
          y: nodeToPaste.position.y + offsetY,
        },
        extent: nodeToPaste.extent,
        zIndex: nodeToPaste.zIndex,
      })
      newNode.id = newNode.id + index

      // If only the iteration start node is copied, remove the isIterationStart flag
      // This new node is movable and can be placed anywhere
      if (clipboardElements.length === 1 && newNode.data.isIterationStart)
        newNode.data.isIterationStart = false

      let newChildren: Node[] = []
      if (nodeToPaste.data.type === BlockEnum.Iteration) {
        newNode.data._children = [];
        (newNode.data as IterationNodeType).start_node_id = ''

        newChildren = handleNodeIterationChildrenCopy(nodeToPaste.id, newNode.id)

        newChildren.forEach((child) => {
          newNode.data._children?.push(child.id)
          if (child.data.isIterationStart)
            (newNode.data as IterationNodeType).start_node_id = child.id
        })
      }

      nodesToPaste.push(newNode)

      if (newChildren.length)
        nodesToPaste.push(...newChildren)
    })

    setNodes([...nodes, ...nodesToPaste])
    saveStateToHistory(WorkflowHistoryEvent.NodePaste)
    handleSyncWorkflowDraft()
  }
}, [getNodesReadOnly, workflowStore, store, reactflow, saveStateToHistory, handleSyncWorkflowDraft, handleNodeIterationChildrenCopy])

This logic ensures that the selected node is copied to the clipboard and then pasted, leading to the duplication of the selected node instead of the intended node [1][2].

To continue talking to Dosu, mention @dosu.

@dosubot[bot] commented on GitHub (Aug 22, 2024): <!-- Greeting --> Hi @edo1z! I'm here to help you with any bugs, questions, or contributions you have for the Dify repository. Let's get to the bottom of this issue together! <!-- Answer --> The issue you're experiencing occurs due to the logic in the `handleNodesCopy` function. This function sets the clipboard elements to the selected node if it is not a start node. When the `handleNodesPaste` function is called, it pastes the node(s) from the clipboard, which includes the selected node. Here is the relevant code snippet: ```ts const handleNodesCopy = useCallback(() => { if (getNodesReadOnly()) return const { setClipboardElements } = workflowStore.getState() const { getNodes, } = store.getState() const nodes = getNodes() const bundledNodes = nodes.filter(node => node.data._isBundled && node.data.type !== BlockEnum.Start && !node.data.isInIteration) if (bundledNodes.length) { setClipboardElements(bundledNodes) return } const selectedNode = nodes.find(node => node.data.selected && node.data.type !== BlockEnum.Start) if (selectedNode) setClipboardElements([selectedNode]) }, [getNodesReadOnly, store, workflowStore]) ``` The `handleNodesPaste` function then pastes the node(s) from the clipboard: ```ts const handleNodesPaste = useCallback(() => { if (getNodesReadOnly()) return const { clipboardElements, mousePosition, } = workflowStore.getState() const { getNodes, setNodes, } = store.getState() const nodesToPaste: Node[] = [] const nodes = getNodes() if (clipboardElements.length) { const { x, y } = getTopLeftNodePosition(clipboardElements) const { screenToFlowPosition } = reactflow const currentPosition = screenToFlowPosition({ x: mousePosition.pageX, y: mousePosition.pageY }) const offsetX = currentPosition.x - x const offsetY = currentPosition.y - y clipboardElements.forEach((nodeToPaste, index) => { const nodeType = nodeToPaste.data.type const newNode = generateNewNode({ type: nodeToPaste.type, data: { ...NODES_INITIAL_DATA[nodeType], ...nodeToPaste.data, selected: false, _isBundled: false, _connectedSourceHandleIds: [], _connectedTargetHandleIds: [], title: genNewNodeTitleFromOld(nodeToPaste.data.title), }, position: { x: nodeToPaste.position.x + offsetX, y: nodeToPaste.position.y + offsetY, }, extent: nodeToPaste.extent, zIndex: nodeToPaste.zIndex, }) newNode.id = newNode.id + index // If only the iteration start node is copied, remove the isIterationStart flag // This new node is movable and can be placed anywhere if (clipboardElements.length === 1 && newNode.data.isIterationStart) newNode.data.isIterationStart = false let newChildren: Node[] = [] if (nodeToPaste.data.type === BlockEnum.Iteration) { newNode.data._children = []; (newNode.data as IterationNodeType).start_node_id = '' newChildren = handleNodeIterationChildrenCopy(nodeToPaste.id, newNode.id) newChildren.forEach((child) => { newNode.data._children?.push(child.id) if (child.data.isIterationStart) (newNode.data as IterationNodeType).start_node_id = child.id }) } nodesToPaste.push(newNode) if (newChildren.length) nodesToPaste.push(...newChildren) }) setNodes([...nodes, ...nodesToPaste]) saveStateToHistory(WorkflowHistoryEvent.NodePaste) handleSyncWorkflowDraft() } }, [getNodesReadOnly, workflowStore, store, reactflow, saveStateToHistory, handleSyncWorkflowDraft, handleNodeIterationChildrenCopy]) ``` This logic ensures that the selected node is copied to the clipboard and then pasted, leading to the duplication of the selected node instead of the intended node <sup>[[1]](https://github.com/langgenius/dify/blob/main/web/app/components/workflow/hooks/use-nodes-interactions.ts)</sup><sup>[[2]](https://github.com/langgenius/dify/blob/main/web/app/components/workflow/nodes/iteration/use-interactions.ts)</sup>. <!-- Dosu Comment Footer --> *To continue talking to [Dosu](https://dosu.dev), mention @dosu.*
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: langgenius/dify#5167