mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-24 15:45:35 +08:00
fix(tag-dropdown): exclude downstream blocks in loops and parallel siblings (#3312)
* fix(tag-dropdown): exclude downstream blocks in loops and parallel siblings from reference picker * chore(serializer): remove unused computeAccessibleBlockIds method * chore(block-path-calculator): remove unused calculateAccessibleBlocksForWorkflow method * chore(tag-dropdown): remove no-op loop node filter * fix(tag-dropdown): remove parallel container from accessible references in parallel branches * chore(tag-dropdown): remove no-op starter block filter * fix(tag-dropdown): restore parallel container in accessible references for blocks inside parallel * fix(copilot): exclude downstream loop nodes and parallel siblings from accessible references
This commit is contained in:
+4
-20
@@ -2,7 +2,6 @@ import { useMemo } from 'react'
|
||||
import { useShallow } from 'zustand/react/shallow'
|
||||
import { BlockPathCalculator } from '@/lib/workflows/blocks/block-path-calculator'
|
||||
import { SYSTEM_REFERENCE_PREFIXES } from '@/lib/workflows/sanitization/references'
|
||||
import { isInputDefinitionTrigger } from '@/lib/workflows/triggers/input-definition-triggers'
|
||||
import { normalizeName } from '@/executor/constants'
|
||||
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
|
||||
import type { Loop, Parallel } from '@/stores/workflows/workflow/types'
|
||||
@@ -27,27 +26,12 @@ export function useAccessibleReferencePrefixes(blockId?: string | null): Set<str
|
||||
const accessibleIds = new Set<string>(ancestorIds)
|
||||
accessibleIds.add(blockId)
|
||||
|
||||
const starterBlock = Object.values(blocks).find((block) => isInputDefinitionTrigger(block.type))
|
||||
if (starterBlock && ancestorIds.includes(starterBlock.id)) {
|
||||
accessibleIds.add(starterBlock.id)
|
||||
}
|
||||
|
||||
const loopValues = Object.values(loops as Record<string, Loop>)
|
||||
loopValues.forEach((loop) => {
|
||||
if (!loop?.nodes) return
|
||||
if (loop.nodes.includes(blockId)) {
|
||||
accessibleIds.add(loop.id) // Add the loop block itself
|
||||
loop.nodes.forEach((nodeId) => accessibleIds.add(nodeId))
|
||||
}
|
||||
Object.values(loops as Record<string, Loop>).forEach((loop) => {
|
||||
if (loop?.nodes?.includes(blockId)) accessibleIds.add(loop.id)
|
||||
})
|
||||
|
||||
const parallelValues = Object.values(parallels as Record<string, Parallel>)
|
||||
parallelValues.forEach((parallel) => {
|
||||
if (!parallel?.nodes) return
|
||||
if (parallel.nodes.includes(blockId)) {
|
||||
accessibleIds.add(parallel.id) // Add the parallel block itself
|
||||
parallel.nodes.forEach((nodeId) => accessibleIds.add(nodeId))
|
||||
}
|
||||
Object.values(parallels as Record<string, Parallel>).forEach((parallel) => {
|
||||
if (parallel?.nodes?.includes(blockId)) accessibleIds.add(parallel.id)
|
||||
})
|
||||
|
||||
const prefixes = new Set<string>()
|
||||
|
||||
@@ -14,7 +14,6 @@ import {
|
||||
loadDeployedWorkflowState,
|
||||
loadWorkflowFromNormalizedTables,
|
||||
} from '@/lib/workflows/persistence/utils'
|
||||
import { isInputDefinitionTrigger } from '@/lib/workflows/triggers/input-definition-triggers'
|
||||
import { hasTriggerCapability } from '@/lib/workflows/triggers/trigger-utils'
|
||||
import { getBlock } from '@/blocks/registry'
|
||||
import { normalizeName } from '@/executor/constants'
|
||||
@@ -457,20 +456,9 @@ export async function executeGetBlockUpstreamReferences(
|
||||
const accessibleIds = new Set<string>(ancestorIds)
|
||||
accessibleIds.add(blockId)
|
||||
|
||||
const starterBlock = Object.values(blocks).find((b) => isInputDefinitionTrigger(b.type))
|
||||
if (starterBlock && ancestorIds.includes(starterBlock.id)) {
|
||||
accessibleIds.add(starterBlock.id)
|
||||
}
|
||||
containingLoopIds.forEach((loopId) => accessibleIds.add(loopId))
|
||||
|
||||
containingLoopIds.forEach((loopId) => {
|
||||
accessibleIds.add(loopId)
|
||||
loops[loopId]?.nodes?.forEach((nodeId: string) => accessibleIds.add(nodeId))
|
||||
})
|
||||
|
||||
containingParallelIds.forEach((parallelId) => {
|
||||
accessibleIds.add(parallelId)
|
||||
parallels[parallelId]?.nodes?.forEach((nodeId: string) => accessibleIds.add(nodeId))
|
||||
})
|
||||
containingParallelIds.forEach((parallelId) => accessibleIds.add(parallelId))
|
||||
|
||||
const accessibleBlocks: AccessibleBlockEntry[] = []
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import type { SerializedWorkflow } from '@/serializer/types'
|
||||
|
||||
/**
|
||||
* Shared utility for calculating block paths and accessible connections.
|
||||
* Used by both frontend (useBlockConnections) and backend (InputResolver) to ensure consistency.
|
||||
@@ -65,36 +63,4 @@ export class BlockPathCalculator {
|
||||
|
||||
return Array.from(pathNodes)
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates accessible blocks for all blocks in a workflow.
|
||||
* This ensures consistent block reference resolution across frontend and backend.
|
||||
*
|
||||
* @param workflow - The serialized workflow
|
||||
* @returns Map of block ID to Set of accessible block IDs
|
||||
*/
|
||||
static calculateAccessibleBlocksForWorkflow(
|
||||
workflow: SerializedWorkflow
|
||||
): Map<string, Set<string>> {
|
||||
const accessibleMap = new Map<string, Set<string>>()
|
||||
|
||||
for (const block of workflow.blocks) {
|
||||
const accessibleBlocks = new Set<string>()
|
||||
|
||||
// Find all blocks along paths leading to this block
|
||||
const pathNodes = BlockPathCalculator.findAllPathNodes(workflow.connections, block.id)
|
||||
pathNodes.forEach((nodeId) => accessibleBlocks.add(nodeId))
|
||||
|
||||
// Only add starter block if it's actually upstream (already in pathNodes)
|
||||
// Don't add it just because it exists on the canvas
|
||||
const starterBlock = workflow.blocks.find((b) => b.metadata?.id === 'starter')
|
||||
if (starterBlock && starterBlock.id !== block.id && pathNodes.includes(starterBlock.id)) {
|
||||
accessibleBlocks.add(starterBlock.id)
|
||||
}
|
||||
|
||||
accessibleMap.set(block.id, accessibleBlocks)
|
||||
}
|
||||
|
||||
return accessibleMap
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { Edge } from 'reactflow'
|
||||
import { BlockPathCalculator } from '@/lib/workflows/blocks/block-path-calculator'
|
||||
import type { CanonicalModeOverrides } from '@/lib/workflows/subblocks/visibility'
|
||||
import {
|
||||
buildCanonicalIndex,
|
||||
@@ -153,13 +152,6 @@ export class Serializer {
|
||||
const safeLoops = Object.keys(canonicalLoops).length > 0 ? canonicalLoops : loops || {}
|
||||
const safeParallels =
|
||||
Object.keys(canonicalParallels).length > 0 ? canonicalParallels : parallels || {}
|
||||
const accessibleBlocksMap = this.computeAccessibleBlockIds(
|
||||
blocks,
|
||||
edges,
|
||||
safeLoops,
|
||||
safeParallels
|
||||
)
|
||||
|
||||
if (validateRequired) {
|
||||
this.validateSubflowsBeforeExecution(blocks, safeLoops, safeParallels)
|
||||
}
|
||||
@@ -170,7 +162,6 @@ export class Serializer {
|
||||
this.serializeBlock(block, {
|
||||
validateRequired,
|
||||
allBlocks: blocks,
|
||||
accessibleBlocksMap,
|
||||
})
|
||||
),
|
||||
connections: edges.map((edge) => ({
|
||||
@@ -201,7 +192,6 @@ export class Serializer {
|
||||
options: {
|
||||
validateRequired: boolean
|
||||
allBlocks: Record<string, BlockState>
|
||||
accessibleBlocksMap: Map<string, Set<string>>
|
||||
}
|
||||
): SerializedBlock {
|
||||
// Special handling for subflow blocks (loops, parallels, etc.)
|
||||
@@ -540,46 +530,6 @@ export class Serializer {
|
||||
}
|
||||
}
|
||||
|
||||
private computeAccessibleBlockIds(
|
||||
blocks: Record<string, BlockState>,
|
||||
edges: Edge[],
|
||||
loops: Record<string, Loop>,
|
||||
parallels: Record<string, Parallel>
|
||||
): Map<string, Set<string>> {
|
||||
const accessibleMap = new Map<string, Set<string>>()
|
||||
const simplifiedEdges = edges.map((edge) => ({ source: edge.source, target: edge.target }))
|
||||
|
||||
const starterBlock = Object.values(blocks).find((block) => block.type === 'starter')
|
||||
|
||||
Object.keys(blocks).forEach((blockId) => {
|
||||
const ancestorIds = BlockPathCalculator.findAllPathNodes(simplifiedEdges, blockId)
|
||||
const accessibleIds = new Set<string>(ancestorIds)
|
||||
accessibleIds.add(blockId)
|
||||
|
||||
if (starterBlock && ancestorIds.includes(starterBlock.id)) {
|
||||
accessibleIds.add(starterBlock.id)
|
||||
}
|
||||
|
||||
Object.values(loops).forEach((loop) => {
|
||||
if (!loop?.nodes) return
|
||||
if (loop.nodes.includes(blockId)) {
|
||||
loop.nodes.forEach((nodeId) => accessibleIds.add(nodeId))
|
||||
}
|
||||
})
|
||||
|
||||
Object.values(parallels).forEach((parallel) => {
|
||||
if (!parallel?.nodes) return
|
||||
if (parallel.nodes.includes(blockId)) {
|
||||
parallel.nodes.forEach((nodeId) => accessibleIds.add(nodeId))
|
||||
}
|
||||
})
|
||||
|
||||
accessibleMap.set(blockId, accessibleIds)
|
||||
})
|
||||
|
||||
return accessibleMap
|
||||
}
|
||||
|
||||
private selectToolId(blockConfig: any, params: Record<string, any>): string {
|
||||
try {
|
||||
return blockConfig.tools.config?.tool
|
||||
|
||||
Reference in New Issue
Block a user