diff --git a/apps/sim/hooks/use-collaborative-workflow.ts b/apps/sim/hooks/use-collaborative-workflow.ts index c2fa032d86..bdfbbae43b 100644 --- a/apps/sim/hooks/use-collaborative-workflow.ts +++ b/apps/sim/hooks/use-collaborative-workflow.ts @@ -22,7 +22,7 @@ import { useUndoRedoStore } from '@/stores/undo-redo' import { useWorkflowDiffStore } from '@/stores/workflow-diff/store' import { useWorkflowRegistry } from '@/stores/workflows/registry/store' import { useSubBlockStore } from '@/stores/workflows/subblock/store' -import { mergeSubblockState, normalizeName } from '@/stores/workflows/utils' +import { filterNewEdges, mergeSubblockState, normalizeName } from '@/stores/workflows/utils' import { useWorkflowStore } from '@/stores/workflows/workflow/store' import type { BlockState, Loop, Parallel, Position } from '@/stores/workflows/workflow/types' @@ -242,7 +242,10 @@ export function useCollaborativeWorkflow() { case EDGES_OPERATIONS.BATCH_ADD_EDGES: { const { edges } = payload if (Array.isArray(edges) && edges.length > 0) { - workflowStore.batchAddEdges(edges) + const newEdges = filterNewEdges(edges, workflowStore.edges) + if (newEdges.length > 0) { + workflowStore.batchAddEdges(newEdges) + } } break } @@ -976,6 +979,9 @@ export function useCollaborativeWorkflow() { if (edges.length === 0) return false + const newEdges = filterNewEdges(edges, workflowStore.edges) + if (newEdges.length === 0) return false + const operationId = crypto.randomUUID() addToQueue({ @@ -983,16 +989,16 @@ export function useCollaborativeWorkflow() { operation: { operation: EDGES_OPERATIONS.BATCH_ADD_EDGES, target: OPERATION_TARGETS.EDGES, - payload: { edges }, + payload: { edges: newEdges }, }, workflowId: activeWorkflowId || '', userId: session?.user?.id || 'unknown', }) - workflowStore.batchAddEdges(edges) + workflowStore.batchAddEdges(newEdges) if (!options?.skipUndoRedo) { - edges.forEach((edge) => undoRedo.recordAddEdge(edge.id)) + newEdges.forEach((edge) => undoRedo.recordAddEdge(edge.id)) } return true diff --git a/apps/sim/stores/workflows/utils.ts b/apps/sim/stores/workflows/utils.ts index 2caadeea1a..c0b3f3a9a4 100644 --- a/apps/sim/stores/workflows/utils.ts +++ b/apps/sim/stores/workflows/utils.ts @@ -1,5 +1,19 @@ import type { Edge } from 'reactflow' import { v4 as uuidv4 } from 'uuid' + +export function filterNewEdges(edgesToAdd: Edge[], currentEdges: Edge[]): Edge[] { + return edgesToAdd.filter((edge) => { + if (edge.source === edge.target) return false + return !currentEdges.some( + (e) => + e.source === edge.source && + e.sourceHandle === edge.sourceHandle && + e.target === edge.target && + e.targetHandle === edge.targetHandle + ) + }) +} + import { getBlockOutputs } from '@/lib/workflows/blocks/block-outputs' import { getBlock } from '@/blocks' import { normalizeName } from '@/executor/constants' diff --git a/apps/sim/stores/workflows/workflow/store.test.ts b/apps/sim/stores/workflows/workflow/store.test.ts index f1fef5bef7..1ed122c238 100644 --- a/apps/sim/stores/workflows/workflow/store.test.ts +++ b/apps/sim/stores/workflows/workflow/store.test.ts @@ -297,7 +297,7 @@ describe('workflow store', () => { expectEdgeConnects(edges, 'block-1', 'block-2') }) - it('should not add duplicate edges', () => { + it('should not add duplicate connections', () => { const { addBlock, batchAddEdges } = useWorkflowStore.getState() addBlock('block-1', 'starter', 'Start', { x: 0, y: 0 }) @@ -309,17 +309,6 @@ describe('workflow store', () => { const state = useWorkflowStore.getState() expectEdgeCount(state, 1) }) - - it('should prevent self-referencing edges', () => { - const { addBlock, batchAddEdges } = useWorkflowStore.getState() - - addBlock('block-1', 'function', 'Self', { x: 0, y: 0 }) - - batchAddEdges([{ id: 'e1', source: 'block-1', target: 'block-1' }]) - - const state = useWorkflowStore.getState() - expectEdgeCount(state, 0) - }) }) describe('batchRemoveEdges', () => { diff --git a/apps/sim/stores/workflows/workflow/store.ts b/apps/sim/stores/workflows/workflow/store.ts index 9f46b0de6f..789e83695e 100644 --- a/apps/sim/stores/workflows/workflow/store.ts +++ b/apps/sim/stores/workflows/workflow/store.ts @@ -9,7 +9,12 @@ import { getBlock } from '@/blocks' import type { SubBlockConfig } from '@/blocks/types' import { useWorkflowRegistry } from '@/stores/workflows/registry/store' import { useSubBlockStore } from '@/stores/workflows/subblock/store' -import { getUniqueBlockName, mergeSubblockState, normalizeName } from '@/stores/workflows/utils' +import { + filterNewEdges, + getUniqueBlockName, + mergeSubblockState, + normalizeName, +} from '@/stores/workflows/utils' import type { Position, SubBlockState, @@ -496,29 +501,11 @@ export const useWorkflowStore = create()( batchAddEdges: (edges: Edge[]) => { const currentEdges = get().edges + const filtered = filterNewEdges(edges, currentEdges) const newEdges = [...currentEdges] - const existingEdgeIds = new Set(currentEdges.map((e) => e.id)) - - for (const edge of edges) { - // Skip if edge ID already exists - if (existingEdgeIds.has(edge.id)) continue - - // Skip self-referencing edges - if (edge.source === edge.target) continue - - // Skip if identical connection already exists (same ports) - const connectionExists = newEdges.some( - (e) => - e.source === edge.source && - e.sourceHandle === edge.sourceHandle && - e.target === edge.target && - e.targetHandle === edge.targetHandle - ) - if (connectionExists) continue - // Skip if would create a cycle + for (const edge of filtered) { if (wouldCreateCycle([...newEdges], edge.source, edge.target)) continue - newEdges.push({ id: edge.id || crypto.randomUUID(), source: edge.source, @@ -528,7 +515,6 @@ export const useWorkflowStore = create()( type: edge.type || 'default', data: edge.data || {}, }) - existingEdgeIds.add(edge.id) } const blocks = get().blocks