Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions apps/sim/app/api/form/[identifier]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { preprocessExecution } from '@/lib/execution/preprocessing'
import { LoggingSession } from '@/lib/logs/execution/logging-session'
import { normalizeInputFormatValue } from '@/lib/workflows/input-format'
import { createStreamingResponse } from '@/lib/workflows/streaming/streaming'
import { isValidStartBlockType } from '@/lib/workflows/triggers/start-block-types'
import { isInputDefinitionTrigger } from '@/lib/workflows/triggers/input-definition-triggers'
import { setFormAuthCookie, validateFormAuth } from '@/app/api/form/utils'
import { createErrorResponse, createSuccessResponse } from '@/app/api/workflows/utils'

Expand All @@ -36,7 +36,7 @@ async function getWorkflowInputSchema(workflowId: string): Promise<any[]> {
.from(workflowBlocks)
.where(eq(workflowBlocks.workflowId, workflowId))

const startBlock = blocks.find((block) => isValidStartBlockType(block.type))
const startBlock = blocks.find((block) => isInputDefinitionTrigger(block.type))

if (!startBlock) {
return []
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { memo, useCallback } from 'react'
import { ArrowLeftRight, ArrowUpDown, Circle, CircleOff, LogOut } from 'lucide-react'
import { Button, Copy, Tooltip, Trash2 } from '@/components/emcn'
import { cn } from '@/lib/core/utils/cn'
import { isValidStartBlockType } from '@/lib/workflows/triggers/start-block-types'
import { isInputDefinitionTrigger } from '@/lib/workflows/triggers/input-definition-triggers'
import { useUserPermissionsContext } from '@/app/workspace/[workspaceId]/providers/workspace-permissions-provider'
import { useCollaborativeWorkflow } from '@/hooks/use-collaborative-workflow'
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
Expand Down Expand Up @@ -90,7 +90,7 @@ export const ActionBar = memo(

const userPermissions = useUserPermissionsContext()

const isStartBlock = isValidStartBlockType(blockType)
const isStartBlock = isInputDefinitionTrigger(blockType)
const isResponseBlock = blockType === 'response'
const isNoteBlock = blockType === 'note'
const isSubflowBlock = blockType === 'loop' || blockType === 'parallel'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
PopoverDivider,
PopoverItem,
} from '@/components/emcn'
import { isValidStartBlockType } from '@/lib/workflows/triggers/start-block-types'
import { TriggerUtils } from '@/lib/workflows/triggers/triggers'

/**
* Block information for context menu actions
Expand Down Expand Up @@ -74,12 +74,16 @@ export function BlockMenu({
const allEnabled = selectedBlocks.every((b) => b.enabled)
const allDisabled = selectedBlocks.every((b) => !b.enabled)

const hasStarterBlock = selectedBlocks.some((b) => isValidStartBlockType(b.type))
const hasSingletonBlock = selectedBlocks.some(
(b) =>
TriggerUtils.requiresSingleInstance(b.type) || TriggerUtils.isSingleInstanceBlockType(b.type)
)
const hasTriggerBlock = selectedBlocks.some((b) => TriggerUtils.isTriggerBlock(b))
const allNoteBlocks = selectedBlocks.every((b) => b.type === 'note')
const isSubflow =
isSingleBlock && (selectedBlocks[0]?.type === 'loop' || selectedBlocks[0]?.type === 'parallel')

const canRemoveFromSubflow = showRemoveFromSubflow && !hasStarterBlock
const canRemoveFromSubflow = showRemoveFromSubflow && !hasTriggerBlock

const getToggleEnabledLabel = () => {
if (allEnabled) return 'Disable'
Expand Down Expand Up @@ -127,7 +131,7 @@ export function BlockMenu({
<span>Paste</span>
<span className='ml-auto opacity-70 group-hover:opacity-100'>⌘V</span>
</PopoverItem>
{!hasStarterBlock && (
{!hasSingletonBlock && (
<PopoverItem
disabled={disableEdit}
onClick={() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { Skeleton } from '@/components/ui'
import { isDev } from '@/lib/core/config/feature-flags'
import { cn } from '@/lib/core/utils/cn'
import { getBaseUrl, getEmailDomain } from '@/lib/core/utils/urls'
import { isValidStartBlockType } from '@/lib/workflows/triggers/start-block-types'
import { isInputDefinitionTrigger } from '@/lib/workflows/triggers/input-definition-triggers'
import {
type FieldConfig,
useCreateForm,
Expand Down Expand Up @@ -147,7 +147,7 @@ export function FormDeploy({

useEffect(() => {
const blocks = Object.values(useWorkflowStore.getState().blocks)
const startBlock = blocks.find((b) => isValidStartBlockType(b.type))
const startBlock = blocks.find((b) => isInputDefinitionTrigger(b.type))

if (startBlock) {
const inputFormat = useSubBlockStore.getState().getValue(startBlock.id, 'inputFormat')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
Textarea,
} from '@/components/emcn'
import { normalizeInputFormatValue } from '@/lib/workflows/input-format'
import { isValidStartBlockType } from '@/lib/workflows/triggers/start-block-types'
import { isInputDefinitionTrigger } from '@/lib/workflows/triggers/input-definition-triggers'
import type { InputFormatField } from '@/lib/workflows/types'
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
Expand Down Expand Up @@ -52,7 +52,7 @@ export function ApiInfoModal({ open, onOpenChange, workflowId }: ApiInfoModalPro
for (const [blockId, block] of Object.entries(blocks)) {
if (!block || typeof block !== 'object') continue
const blockType = (block as { type?: string }).type
if (blockType && isValidStartBlockType(blockType)) {
if (blockType && isInputDefinitionTrigger(blockType)) {
return blockId
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
import { Skeleton } from '@/components/ui'
import { generateToolInputSchema, sanitizeToolName } from '@/lib/mcp/workflow-tool-schema'
import { normalizeInputFormatValue } from '@/lib/workflows/input-format'
import { isValidStartBlockType } from '@/lib/workflows/triggers/start-block-types'
import { isInputDefinitionTrigger } from '@/lib/workflows/triggers/input-definition-triggers'
import type { InputFormatField } from '@/lib/workflows/types'
import {
useAddWorkflowMcpTool,
Expand Down Expand Up @@ -107,7 +107,7 @@ export function McpDeploy({
for (const [blockId, block] of Object.entries(blocks)) {
if (!block || typeof block !== 'object') continue
const blockType = (block as { type?: string }).type
if (blockType && isValidStartBlockType(blockType)) {
if (blockType && isInputDefinitionTrigger(blockType)) {
return blockId
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ 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 { isValidStartBlockType } from '@/lib/workflows/triggers/start-block-types'
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'
Expand All @@ -27,7 +27,7 @@ export function useAccessibleReferencePrefixes(blockId?: string | null): Set<str
const accessibleIds = new Set<string>(ancestorIds)
accessibleIds.add(blockId)

const starterBlock = Object.values(blocks).find((block) => isValidStartBlockType(block.type))
const starterBlock = Object.values(blocks).find((block) => isInputDefinitionTrigger(block.type))
if (starterBlock && ancestorIds.includes(starterBlock.id)) {
accessibleIds.add(starterBlock.id)
}
Expand Down
28 changes: 20 additions & 8 deletions apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,21 @@ function mapEdgesByNode(edges: Edge[], nodeIds: Set<string>): Map<string, Edge[]
return result
}

/**
* Syncs the panel editor with the current selection state.
* Shows block details when exactly one block is selected, clears otherwise.
*/
function syncPanelWithSelection(selectedIds: string[]) {
const { currentBlockId, clearCurrentBlock, setCurrentBlockId } = usePanelEditorStore.getState()
if (selectedIds.length === 1 && selectedIds[0] !== currentBlockId) {
setCurrentBlockId(selectedIds[0])
} else if (selectedIds.length === 0 && currentBlockId) {
clearCurrentBlock()
} else if (selectedIds.length > 1 && currentBlockId) {
clearCurrentBlock()
}
}

/** Custom node types for ReactFlow. */
const nodeTypes: NodeTypes = {
workflowBlock: WorkflowBlock,
Expand Down Expand Up @@ -2075,7 +2090,10 @@ const WorkflowContent = React.memo(() => {
...node,
selected: pendingSet.has(node.id),
}))
setDisplayNodes(resolveParentChildSelectionConflicts(withSelection, blocks))
const resolved = resolveParentChildSelectionConflicts(withSelection, blocks)
setDisplayNodes(resolved)
const selectedIds = resolved.filter((node) => node.selected).map((node) => node.id)
syncPanelWithSelection(selectedIds)
return
}

Expand Down Expand Up @@ -2175,13 +2193,7 @@ const WorkflowContent = React.memo(() => {
})
const selectedIds = selectedIdsRef.current as string[] | null
if (selectedIds !== null) {
const { currentBlockId, clearCurrentBlock, setCurrentBlockId } =
usePanelEditorStore.getState()
if (selectedIds.length === 1 && selectedIds[0] !== currentBlockId) {
setCurrentBlockId(selectedIds[0])
} else if (selectedIds.length === 0 && currentBlockId) {
clearCurrentBlock()
}
syncPanelWithSelection(selectedIds)
}
},
[blocks]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
type GetBlockUpstreamReferencesResultType,
} from '@/lib/copilot/tools/shared/schemas'
import { BlockPathCalculator } from '@/lib/workflows/blocks/block-path-calculator'
import { isValidStartBlockType } from '@/lib/workflows/triggers/start-block-types'
import { isInputDefinitionTrigger } from '@/lib/workflows/triggers/input-definition-triggers'
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
import type { Loop, Parallel } from '@/stores/workflows/workflow/types'
Expand Down Expand Up @@ -141,7 +141,7 @@ export class GetBlockUpstreamReferencesClientTool extends BaseClientTool {
const accessibleIds = new Set<string>(ancestorIds)
accessibleIds.add(blockId)

const starterBlock = Object.values(blocks).find((b) => isValidStartBlockType(b.type))
const starterBlock = Object.values(blocks).find((b) => isInputDefinitionTrigger(b.type))
if (starterBlock && ancestorIds.includes(starterBlock.id)) {
accessibleIds.add(starterBlock.id)
}
Expand Down
4 changes: 2 additions & 2 deletions apps/sim/lib/mcp/workflow-tool-schema.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { z } from 'zod'
import { normalizeInputFormatValue } from '@/lib/workflows/input-format'
import { isValidStartBlockType } from '@/lib/workflows/triggers/start-block-types'
import { isInputDefinitionTrigger } from '@/lib/workflows/triggers/input-definition-triggers'
import type { InputFormatField } from '@/lib/workflows/types'
import type { McpToolSchema } from './types'

Expand Down Expand Up @@ -217,7 +217,7 @@ export function extractInputFormatFromBlocks(
const blockObj = block as Record<string, unknown>
const blockType = blockObj.type as string

if (isValidStartBlockType(blockType)) {
if (isInputDefinitionTrigger(blockType)) {
// Try to get inputFormat from subBlocks.inputFormat.value
const subBlocks = blockObj.subBlocks as Record<string, { value?: unknown }> | undefined
const subBlockValue = subBlocks?.inputFormat?.value
Expand Down
4 changes: 2 additions & 2 deletions apps/sim/lib/workflows/input-format.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { isValidStartBlockType } from '@/lib/workflows/triggers/start-block-types'
import { isInputDefinitionTrigger } from '@/lib/workflows/triggers/input-definition-triggers'
import type { InputFormatField } from '@/lib/workflows/types'

/**
Expand All @@ -25,7 +25,7 @@ export function extractInputFieldsFromBlocks(
// Find trigger block
const triggerEntry = Object.entries(blocks).find(([, block]) => {
const b = block as Record<string, unknown>
return typeof b.type === 'string' && isValidStartBlockType(b.type)
return typeof b.type === 'string' && isInputDefinitionTrigger(b.type)
})

if (!triggerEntry) return []
Expand Down
28 changes: 28 additions & 0 deletions apps/sim/lib/workflows/triggers/input-definition-triggers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/**
* Trigger types that define workflow input parameters (inputFormat).
* These are triggers where users can configure input schema for the workflow.
*
* This module is kept lightweight with no dependencies to avoid circular imports.
*
* Note: External triggers like webhook/schedule are NOT included here because
* they receive input from external event payloads, not user-defined inputFormat.
*/
export const INPUT_DEFINITION_TRIGGER_TYPES = [
'starter',
'start',
'start_trigger',
'api_trigger',
'input_trigger',
] as const

export type InputDefinitionTriggerType = (typeof INPUT_DEFINITION_TRIGGER_TYPES)[number]

/**
* Check if a block type is a trigger that defines workflow input parameters.
* Used to find blocks that have inputFormat subblock for workflow input schema.
*/
export function isInputDefinitionTrigger(
blockType: string
): blockType is InputDefinitionTriggerType {
return INPUT_DEFINITION_TRIGGER_TYPES.includes(blockType as InputDefinitionTriggerType)
}
21 changes: 0 additions & 21 deletions apps/sim/lib/workflows/triggers/start-block-types.ts

This file was deleted.

4 changes: 2 additions & 2 deletions apps/sim/lib/workflows/triggers/trigger-utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createLogger } from '@sim/logger'
import { isValidStartBlockType } from '@/lib/workflows/triggers/start-block-types'
import { isInputDefinitionTrigger } from '@/lib/workflows/triggers/input-definition-triggers'
import {
type StartBlockCandidate,
StartBlockPath,
Expand All @@ -22,7 +22,7 @@ export function hasValidStartBlockInState(state: WorkflowState | null | undefine

const startBlock = Object.values(state.blocks).find((block: BlockState) => {
const blockType = block?.type
return isValidStartBlockType(blockType)
return isInputDefinitionTrigger(blockType)
})

return !!startBlock
Expand Down