Skip to content

Commit 40fc9ca

Browse files
committed
improvement(response): only allow singleton
1 parent 283a521 commit 40fc9ca

File tree

6 files changed

+62
-6
lines changed

6 files changed

+62
-6
lines changed

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/utils/workflow-canvas-helpers.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ interface TriggerValidationResult {
2323
}
2424

2525
/**
26-
* Validates that pasting/duplicating trigger blocks won't violate constraints.
26+
* Validates that pasting/duplicating blocks won't violate constraints.
27+
* Checks both trigger constraints and single-instance block constraints.
2728
* Returns validation result with error message if invalid.
2829
*/
2930
export function validateTriggerPaste(
@@ -43,6 +44,13 @@ export function validateTriggerPaste(
4344
return { isValid: false, message }
4445
}
4546
}
47+
48+
const singleInstanceIssue = TriggerUtils.getSingleInstanceBlockIssue(existingBlocks, block.type)
49+
if (singleInstanceIssue) {
50+
const actionText = action === 'paste' ? 'paste' : 'duplicate'
51+
const message = `A workflow can only have one ${singleInstanceIssue.blockName} block. ${action === 'paste' ? 'Please remove the existing one before pasting.' : `Cannot ${actionText}.`}`
52+
return { isValid: false, message }
53+
}
4654
}
4755
return { isValid: true }
4856
}

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1129,24 +1129,36 @@ const WorkflowContent = React.memo(() => {
11291129
)
11301130

11311131
/**
1132-
* Checks if adding a trigger block would violate constraints and shows notification if so.
1132+
* Checks if adding a block would violate constraints (triggers or single-instance blocks)
1133+
* and shows notification if so.
11331134
* @returns true if validation failed (caller should return early), false if ok to proceed
11341135
*/
11351136
const checkTriggerConstraints = useCallback(
11361137
(blockType: string): boolean => {
1137-
const issue = TriggerUtils.getTriggerAdditionIssue(blocks, blockType)
1138-
if (issue) {
1138+
const triggerIssue = TriggerUtils.getTriggerAdditionIssue(blocks, blockType)
1139+
if (triggerIssue) {
11391140
const message =
1140-
issue.issue === 'legacy'
1141+
triggerIssue.issue === 'legacy'
11411142
? 'Cannot add new trigger blocks when a legacy Start block exists. Available in newer workflows.'
1142-
: `A workflow can only have one ${issue.triggerName} trigger block. Please remove the existing one before adding a new one.`
1143+
: `A workflow can only have one ${triggerIssue.triggerName} trigger block. Please remove the existing one before adding a new one.`
11431144
addNotification({
11441145
level: 'error',
11451146
message,
11461147
workflowId: activeWorkflowId || undefined,
11471148
})
11481149
return true
11491150
}
1151+
1152+
const singleInstanceIssue = TriggerUtils.getSingleInstanceBlockIssue(blocks, blockType)
1153+
if (singleInstanceIssue) {
1154+
addNotification({
1155+
level: 'error',
1156+
message: `A workflow can only have one ${singleInstanceIssue.blockName} block. Please remove the existing one before adding a new one.`,
1157+
workflowId: activeWorkflowId || undefined,
1158+
})
1159+
return true
1160+
}
1161+
11501162
return false
11511163
},
11521164
[blocks, addNotification, activeWorkflowId]

apps/sim/blocks/blocks/response.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export const ResponseBlock: BlockConfig<ResponseBlockOutput> = {
1717
category: 'blocks',
1818
bgColor: '#2F55FF',
1919
icon: ResponseIcon,
20+
singleInstance: true,
2021
subBlocks: [
2122
{
2223
id: 'dataMode',

apps/sim/blocks/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,7 @@ export interface BlockConfig<T extends ToolResponse = ToolResponse> {
320320
subBlocks: SubBlockConfig[]
321321
triggerAllowed?: boolean
322322
authMode?: AuthMode
323+
singleInstance?: boolean
323324
tools: {
324325
access: string[]
325326
config?: {

apps/sim/lib/workflows/triggers/triggers.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -592,4 +592,34 @@ export class TriggerUtils {
592592
const parentWithType = parent as T & { type?: string }
593593
return parentWithType.type === 'loop' || parentWithType.type === 'parallel'
594594
}
595+
596+
static isSingleInstanceBlockType(blockType: string): boolean {
597+
const blockConfig = getBlock(blockType)
598+
return blockConfig?.singleInstance === true
599+
}
600+
601+
static wouldViolateSingleInstanceBlock<T extends { type: string }>(
602+
blocks: T[] | Record<string, T>,
603+
blockType: string
604+
): boolean {
605+
if (!TriggerUtils.isSingleInstanceBlockType(blockType)) {
606+
return false
607+
}
608+
609+
const blockArray = Array.isArray(blocks) ? blocks : Object.values(blocks)
610+
return blockArray.some((block) => block.type === blockType)
611+
}
612+
613+
static getSingleInstanceBlockIssue<T extends { type: string }>(
614+
blocks: T[] | Record<string, T>,
615+
blockType: string
616+
): { issue: 'duplicate'; blockName: string } | null {
617+
if (!TriggerUtils.wouldViolateSingleInstanceBlock(blocks, blockType)) {
618+
return null
619+
}
620+
621+
const blockConfig = getBlock(blockType)
622+
const blockName = blockConfig?.name || blockType
623+
return { issue: 'duplicate', blockName }
624+
}
595625
}

apps/sim/stores/workflows/utils.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ export function getUniqueBlockName(baseName: string, existingBlocks: Record<stri
4141
return 'Start'
4242
}
4343

44+
if (normalizedBaseName === 'response') {
45+
return 'Response'
46+
}
47+
4448
const baseNameMatch = baseName.match(/^(.*?)(\s+\d+)?$/)
4549
const namePrefix = baseNameMatch ? baseNameMatch[1].trim() : baseName
4650

0 commit comments

Comments
 (0)