mirror of
https://github.com/simstudioai/sim.git
synced 2026-01-24 14:27:56 -05:00
Compare commits
5 Commits
fix/nested
...
fix/ghost-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c56df9cfb0 | ||
|
|
89f4c71acc | ||
|
|
8d751359c3 | ||
|
|
4cda2b4eba | ||
|
|
bd2838a88c |
@@ -81,6 +81,7 @@ import { useWorkflowDiffStore } from '@/stores/workflow-diff/store'
|
|||||||
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
|
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
|
||||||
import { getUniqueBlockName, prepareBlockState } from '@/stores/workflows/utils'
|
import { getUniqueBlockName, prepareBlockState } from '@/stores/workflows/utils'
|
||||||
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
|
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
|
||||||
|
import type { BlockState } from '@/stores/workflows/workflow/types'
|
||||||
|
|
||||||
/** Lazy-loaded components for non-critical UI that can load after initial render */
|
/** Lazy-loaded components for non-critical UI that can load after initial render */
|
||||||
const LazyChat = lazy(() =>
|
const LazyChat = lazy(() =>
|
||||||
@@ -535,8 +536,7 @@ const WorkflowContent = React.memo(() => {
|
|||||||
return edgesToFilter.filter((edge) => {
|
return edgesToFilter.filter((edge) => {
|
||||||
const sourceBlock = blocks[edge.source]
|
const sourceBlock = blocks[edge.source]
|
||||||
const targetBlock = blocks[edge.target]
|
const targetBlock = blocks[edge.target]
|
||||||
if (!sourceBlock || !targetBlock) return false
|
return Boolean(sourceBlock && targetBlock)
|
||||||
return !isAnnotationOnlyBlock(sourceBlock.type) && !isAnnotationOnlyBlock(targetBlock.type)
|
|
||||||
})
|
})
|
||||||
}, [edges, isShowingDiff, isDiffReady, diffAnalysis, blocks])
|
}, [edges, isShowingDiff, isDiffReady, diffAnalysis, blocks])
|
||||||
|
|
||||||
@@ -1097,6 +1097,13 @@ const WorkflowContent = React.memo(() => {
|
|||||||
[collaborativeBatchRemoveEdges]
|
[collaborativeBatchRemoveEdges]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const isAutoConnectSourceCandidate = useCallback((block: BlockState): boolean => {
|
||||||
|
if (!block.enabled) return false
|
||||||
|
if (block.type === 'response') return false
|
||||||
|
if (isAnnotationOnlyBlock(block.type)) return false
|
||||||
|
return true
|
||||||
|
}, [])
|
||||||
|
|
||||||
/** Finds the closest block to a position for auto-connect. */
|
/** Finds the closest block to a position for auto-connect. */
|
||||||
const findClosestOutput = useCallback(
|
const findClosestOutput = useCallback(
|
||||||
(newNodePosition: { x: number; y: number }): BlockData | null => {
|
(newNodePosition: { x: number; y: number }): BlockData | null => {
|
||||||
@@ -1109,8 +1116,7 @@ const WorkflowContent = React.memo(() => {
|
|||||||
position: { x: number; y: number }
|
position: { x: number; y: number }
|
||||||
distanceSquared: number
|
distanceSquared: number
|
||||||
} | null>((acc, [id, block]) => {
|
} | null>((acc, [id, block]) => {
|
||||||
if (!block.enabled) return acc
|
if (!isAutoConnectSourceCandidate(block)) return acc
|
||||||
if (block.type === 'response') return acc
|
|
||||||
const node = nodeIndex.get(id)
|
const node = nodeIndex.get(id)
|
||||||
if (!node) return acc
|
if (!node) return acc
|
||||||
|
|
||||||
@@ -1140,7 +1146,7 @@ const WorkflowContent = React.memo(() => {
|
|||||||
position: closest.position,
|
position: closest.position,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[blocks, getNodes, getNodeAnchorPosition, isPointInLoopNode]
|
[blocks, getNodes, getNodeAnchorPosition, isPointInLoopNode, isAutoConnectSourceCandidate]
|
||||||
)
|
)
|
||||||
|
|
||||||
/** Determines the appropriate source handle based on block type. */
|
/** Determines the appropriate source handle based on block type. */
|
||||||
@@ -1208,7 +1214,8 @@ const WorkflowContent = React.memo(() => {
|
|||||||
position: { x: number; y: number }
|
position: { x: number; y: number }
|
||||||
distanceSquared: number
|
distanceSquared: number
|
||||||
} | null>((acc, block) => {
|
} | null>((acc, block) => {
|
||||||
if (block.type === 'response') return acc
|
const blockState = blocks[block.id]
|
||||||
|
if (!blockState || !isAutoConnectSourceCandidate(blockState)) return acc
|
||||||
const distanceSquared =
|
const distanceSquared =
|
||||||
(block.position.x - targetPosition.x) ** 2 + (block.position.y - targetPosition.y) ** 2
|
(block.position.x - targetPosition.x) ** 2 + (block.position.y - targetPosition.y) ** 2
|
||||||
if (!acc || distanceSquared < acc.distanceSquared) {
|
if (!acc || distanceSquared < acc.distanceSquared) {
|
||||||
@@ -1225,7 +1232,7 @@ const WorkflowContent = React.memo(() => {
|
|||||||
}
|
}
|
||||||
: undefined
|
: undefined
|
||||||
},
|
},
|
||||||
[]
|
[blocks, isAutoConnectSourceCandidate]
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1241,8 +1248,6 @@ const WorkflowContent = React.memo(() => {
|
|||||||
position: { x: number; y: number },
|
position: { x: number; y: number },
|
||||||
targetBlockId: string,
|
targetBlockId: string,
|
||||||
options: {
|
options: {
|
||||||
blockType: string
|
|
||||||
enableTriggerMode?: boolean
|
|
||||||
targetParentId?: string | null
|
targetParentId?: string | null
|
||||||
existingChildBlocks?: { id: string; type: string; position: { x: number; y: number } }[]
|
existingChildBlocks?: { id: string; type: string; position: { x: number; y: number } }[]
|
||||||
containerId?: string
|
containerId?: string
|
||||||
@@ -1250,17 +1255,6 @@ const WorkflowContent = React.memo(() => {
|
|||||||
): Edge | undefined => {
|
): Edge | undefined => {
|
||||||
if (!autoConnectRef.current) return undefined
|
if (!autoConnectRef.current) return undefined
|
||||||
|
|
||||||
// Don't auto-connect starter or annotation-only blocks
|
|
||||||
if (options.blockType === 'starter' || isAnnotationOnlyBlock(options.blockType)) {
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if target is a trigger block
|
|
||||||
const targetBlockConfig = getBlock(options.blockType)
|
|
||||||
const isTargetTrigger =
|
|
||||||
options.enableTriggerMode || targetBlockConfig?.category === 'triggers'
|
|
||||||
if (isTargetTrigger) return undefined
|
|
||||||
|
|
||||||
// Case 1: Adding block inside a container with existing children
|
// Case 1: Adding block inside a container with existing children
|
||||||
if (options.existingChildBlocks && options.existingChildBlocks.length > 0) {
|
if (options.existingChildBlocks && options.existingChildBlocks.length > 0) {
|
||||||
const closestBlock = findClosestBlockInSet(options.existingChildBlocks, position)
|
const closestBlock = findClosestBlockInSet(options.existingChildBlocks, position)
|
||||||
@@ -1368,7 +1362,6 @@ const WorkflowContent = React.memo(() => {
|
|||||||
const name = getUniqueBlockName(baseName, blocks)
|
const name = getUniqueBlockName(baseName, blocks)
|
||||||
|
|
||||||
const autoConnectEdge = tryCreateAutoConnectEdge(position, id, {
|
const autoConnectEdge = tryCreateAutoConnectEdge(position, id, {
|
||||||
blockType: data.type,
|
|
||||||
targetParentId: null,
|
targetParentId: null,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -1439,8 +1432,6 @@ const WorkflowContent = React.memo(() => {
|
|||||||
.map((b) => ({ id: b.id, type: b.type, position: b.position }))
|
.map((b) => ({ id: b.id, type: b.type, position: b.position }))
|
||||||
|
|
||||||
const autoConnectEdge = tryCreateAutoConnectEdge(relativePosition, id, {
|
const autoConnectEdge = tryCreateAutoConnectEdge(relativePosition, id, {
|
||||||
blockType: data.type,
|
|
||||||
enableTriggerMode: data.enableTriggerMode,
|
|
||||||
targetParentId: containerInfo.loopId,
|
targetParentId: containerInfo.loopId,
|
||||||
existingChildBlocks,
|
existingChildBlocks,
|
||||||
containerId: containerInfo.loopId,
|
containerId: containerInfo.loopId,
|
||||||
@@ -1469,8 +1460,6 @@ const WorkflowContent = React.memo(() => {
|
|||||||
if (checkTriggerConstraints(data.type)) return
|
if (checkTriggerConstraints(data.type)) return
|
||||||
|
|
||||||
const autoConnectEdge = tryCreateAutoConnectEdge(position, id, {
|
const autoConnectEdge = tryCreateAutoConnectEdge(position, id, {
|
||||||
blockType: data.type,
|
|
||||||
enableTriggerMode: data.enableTriggerMode,
|
|
||||||
targetParentId: null,
|
targetParentId: null,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -1526,7 +1515,6 @@ const WorkflowContent = React.memo(() => {
|
|||||||
const name = getUniqueBlockName(baseName, blocks)
|
const name = getUniqueBlockName(baseName, blocks)
|
||||||
|
|
||||||
const autoConnectEdge = tryCreateAutoConnectEdge(basePosition, id, {
|
const autoConnectEdge = tryCreateAutoConnectEdge(basePosition, id, {
|
||||||
blockType: type,
|
|
||||||
targetParentId: null,
|
targetParentId: null,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -1562,8 +1550,6 @@ const WorkflowContent = React.memo(() => {
|
|||||||
const name = getUniqueBlockName(baseName, blocks)
|
const name = getUniqueBlockName(baseName, blocks)
|
||||||
|
|
||||||
const autoConnectEdge = tryCreateAutoConnectEdge(basePosition, id, {
|
const autoConnectEdge = tryCreateAutoConnectEdge(basePosition, id, {
|
||||||
blockType: type,
|
|
||||||
enableTriggerMode,
|
|
||||||
targetParentId: null,
|
targetParentId: null,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -2364,24 +2350,6 @@ const WorkflowContent = React.memo(() => {
|
|||||||
|
|
||||||
if (!sourceNode || !targetNode) return
|
if (!sourceNode || !targetNode) return
|
||||||
|
|
||||||
// Prevent connections to/from annotation-only blocks (non-executable)
|
|
||||||
if (
|
|
||||||
isAnnotationOnlyBlock(sourceNode.data?.type) ||
|
|
||||||
isAnnotationOnlyBlock(targetNode.data?.type)
|
|
||||||
) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prevent incoming connections to trigger blocks (webhook, schedule, etc.)
|
|
||||||
if (targetNode.data?.config?.category === 'triggers') {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prevent incoming connections to starter blocks (still keep separate for backward compatibility)
|
|
||||||
if (targetNode.data?.type === 'starter') {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get parent information (handle container start node case)
|
// Get parent information (handle container start node case)
|
||||||
const sourceParentId =
|
const sourceParentId =
|
||||||
blocks[sourceNode.id]?.data?.parentId ||
|
blocks[sourceNode.id]?.data?.parentId ||
|
||||||
@@ -2787,7 +2755,6 @@ const WorkflowContent = React.memo(() => {
|
|||||||
.map((b) => ({ id: b.id, type: b.type, position: b.position }))
|
.map((b) => ({ id: b.id, type: b.type, position: b.position }))
|
||||||
|
|
||||||
const autoConnectEdge = tryCreateAutoConnectEdge(relativePositionBefore, node.id, {
|
const autoConnectEdge = tryCreateAutoConnectEdge(relativePositionBefore, node.id, {
|
||||||
blockType: node.data?.type || '',
|
|
||||||
targetParentId: potentialParentId,
|
targetParentId: potentialParentId,
|
||||||
existingChildBlocks,
|
existingChildBlocks,
|
||||||
containerId: potentialParentId,
|
containerId: potentialParentId,
|
||||||
|
|||||||
@@ -352,7 +352,7 @@ export function SocketProvider({ children, user }: SocketProviderProps) {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
useWorkflowStore.setState({
|
useWorkflowStore.getState().replaceWorkflowState({
|
||||||
blocks: workflowState.blocks || {},
|
blocks: workflowState.blocks || {},
|
||||||
edges: workflowState.edges || [],
|
edges: workflowState.edges || [],
|
||||||
loops: workflowState.loops || {},
|
loops: workflowState.loops || {},
|
||||||
|
|||||||
@@ -4,9 +4,10 @@ import { create } from 'zustand'
|
|||||||
import { devtools } from 'zustand/middleware'
|
import { devtools } from 'zustand/middleware'
|
||||||
import { DEFAULT_DUPLICATE_OFFSET } from '@/lib/workflows/autolayout/constants'
|
import { DEFAULT_DUPLICATE_OFFSET } from '@/lib/workflows/autolayout/constants'
|
||||||
import { getBlockOutputs } from '@/lib/workflows/blocks/block-outputs'
|
import { getBlockOutputs } from '@/lib/workflows/blocks/block-outputs'
|
||||||
|
import { TriggerUtils } from '@/lib/workflows/triggers/triggers'
|
||||||
import { getBlock } from '@/blocks'
|
import { getBlock } from '@/blocks'
|
||||||
import type { SubBlockConfig } from '@/blocks/types'
|
import type { SubBlockConfig } from '@/blocks/types'
|
||||||
import { normalizeName, RESERVED_BLOCK_NAMES } from '@/executor/constants'
|
import { isAnnotationOnlyBlock, normalizeName, RESERVED_BLOCK_NAMES } from '@/executor/constants'
|
||||||
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
|
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
|
||||||
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
|
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
|
||||||
import { filterNewEdges, getUniqueBlockName, mergeSubblockState } from '@/stores/workflows/utils'
|
import { filterNewEdges, getUniqueBlockName, mergeSubblockState } from '@/stores/workflows/utils'
|
||||||
@@ -90,6 +91,26 @@ function resolveInitialSubblockValue(config: SubBlockConfig): unknown {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isValidEdge(
|
||||||
|
edge: Edge,
|
||||||
|
blocks: Record<string, { type: string; triggerMode?: boolean }>
|
||||||
|
): boolean {
|
||||||
|
const sourceBlock = blocks[edge.source]
|
||||||
|
const targetBlock = blocks[edge.target]
|
||||||
|
if (!sourceBlock || !targetBlock) return false
|
||||||
|
if (isAnnotationOnlyBlock(sourceBlock.type)) return false
|
||||||
|
if (isAnnotationOnlyBlock(targetBlock.type)) return false
|
||||||
|
if (TriggerUtils.isTriggerBlock(targetBlock)) return false
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterValidEdges(
|
||||||
|
edges: Edge[],
|
||||||
|
blocks: Record<string, { type: string; triggerMode?: boolean }>
|
||||||
|
): Edge[] {
|
||||||
|
return edges.filter((edge) => isValidEdge(edge, blocks))
|
||||||
|
}
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
blocks: {},
|
blocks: {},
|
||||||
edges: [],
|
edges: [],
|
||||||
@@ -360,8 +381,9 @@ export const useWorkflowStore = create<WorkflowStore>()(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (edges && edges.length > 0) {
|
if (edges && edges.length > 0) {
|
||||||
|
const validEdges = filterValidEdges(edges, newBlocks)
|
||||||
const existingEdgeIds = new Set(currentEdges.map((e) => e.id))
|
const existingEdgeIds = new Set(currentEdges.map((e) => e.id))
|
||||||
for (const edge of edges) {
|
for (const edge of validEdges) {
|
||||||
if (!existingEdgeIds.has(edge.id)) {
|
if (!existingEdgeIds.has(edge.id)) {
|
||||||
newEdges.push({
|
newEdges.push({
|
||||||
id: edge.id || crypto.randomUUID(),
|
id: edge.id || crypto.randomUUID(),
|
||||||
@@ -495,8 +517,11 @@ export const useWorkflowStore = create<WorkflowStore>()(
|
|||||||
},
|
},
|
||||||
|
|
||||||
batchAddEdges: (edges: Edge[]) => {
|
batchAddEdges: (edges: Edge[]) => {
|
||||||
|
const blocks = get().blocks
|
||||||
const currentEdges = get().edges
|
const currentEdges = get().edges
|
||||||
const filtered = filterNewEdges(edges, currentEdges)
|
|
||||||
|
const validEdges = filterValidEdges(edges, blocks)
|
||||||
|
const filtered = filterNewEdges(validEdges, currentEdges)
|
||||||
const newEdges = [...currentEdges]
|
const newEdges = [...currentEdges]
|
||||||
|
|
||||||
for (const edge of filtered) {
|
for (const edge of filtered) {
|
||||||
@@ -512,7 +537,6 @@ export const useWorkflowStore = create<WorkflowStore>()(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const blocks = get().blocks
|
|
||||||
set({
|
set({
|
||||||
blocks: { ...blocks },
|
blocks: { ...blocks },
|
||||||
edges: newEdges,
|
edges: newEdges,
|
||||||
@@ -572,7 +596,7 @@ export const useWorkflowStore = create<WorkflowStore>()(
|
|||||||
) => {
|
) => {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
const nextBlocks = workflowState.blocks || {}
|
const nextBlocks = workflowState.blocks || {}
|
||||||
const nextEdges = workflowState.edges || []
|
const nextEdges = filterValidEdges(workflowState.edges || [], nextBlocks)
|
||||||
const nextLoops =
|
const nextLoops =
|
||||||
Object.keys(workflowState.loops || {}).length > 0
|
Object.keys(workflowState.loops || {}).length > 0
|
||||||
? workflowState.loops
|
? workflowState.loops
|
||||||
@@ -1083,7 +1107,7 @@ export const useWorkflowStore = create<WorkflowStore>()(
|
|||||||
|
|
||||||
const newState = {
|
const newState = {
|
||||||
blocks: deployedState.blocks,
|
blocks: deployedState.blocks,
|
||||||
edges: deployedState.edges,
|
edges: filterValidEdges(deployedState.edges ?? [], deployedState.blocks),
|
||||||
loops: deployedState.loops || {},
|
loops: deployedState.loops || {},
|
||||||
parallels: deployedState.parallels || {},
|
parallels: deployedState.parallels || {},
|
||||||
needsRedeployment: false,
|
needsRedeployment: false,
|
||||||
|
|||||||
Reference in New Issue
Block a user