mirror of
https://github.com/simstudioai/sim.git
synced 2026-01-11 16:08:04 -05:00
Compare commits
4 Commits
fix/resize
...
improvemen
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1814aef84f | ||
|
|
c07dbfa06a | ||
|
|
a2930446bc | ||
|
|
40fc9ca504 |
@@ -87,8 +87,8 @@ export const ActionBar = memo(
|
||||
|
||||
const userPermissions = useUserPermissionsContext()
|
||||
|
||||
// Check for start_trigger (unified start block) - prevent duplication but allow deletion
|
||||
const isStartBlock = blockType === 'starter' || blockType === 'start_trigger'
|
||||
const isResponseBlock = blockType === 'response'
|
||||
const isNoteBlock = blockType === 'note'
|
||||
|
||||
/**
|
||||
@@ -140,7 +140,7 @@ export const ActionBar = memo(
|
||||
</Tooltip.Root>
|
||||
)}
|
||||
|
||||
{!isStartBlock && (
|
||||
{!isStartBlock && !isResponseBlock && (
|
||||
<Tooltip.Root>
|
||||
<Tooltip.Trigger asChild>
|
||||
<Button
|
||||
|
||||
@@ -23,7 +23,8 @@ interface TriggerValidationResult {
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that pasting/duplicating trigger blocks won't violate constraints.
|
||||
* Validates that pasting/duplicating blocks won't violate constraints.
|
||||
* Checks both trigger constraints and single-instance block constraints.
|
||||
* Returns validation result with error message if invalid.
|
||||
*/
|
||||
export function validateTriggerPaste(
|
||||
@@ -43,6 +44,12 @@ export function validateTriggerPaste(
|
||||
return { isValid: false, message }
|
||||
}
|
||||
}
|
||||
|
||||
const singleInstanceIssue = TriggerUtils.getSingleInstanceBlockIssue(existingBlocks, block.type)
|
||||
if (singleInstanceIssue) {
|
||||
const message = `A workflow can only have one ${singleInstanceIssue.blockName} block. ${action === 'paste' ? 'Please remove the existing one before pasting.' : 'Cannot duplicate.'}`
|
||||
return { isValid: false, message }
|
||||
}
|
||||
}
|
||||
return { isValid: true }
|
||||
}
|
||||
|
||||
@@ -1129,17 +1129,18 @@ const WorkflowContent = React.memo(() => {
|
||||
)
|
||||
|
||||
/**
|
||||
* Checks if adding a trigger block would violate constraints and shows notification if so.
|
||||
* Checks if adding a block would violate constraints (triggers or single-instance blocks)
|
||||
* and shows notification if so.
|
||||
* @returns true if validation failed (caller should return early), false if ok to proceed
|
||||
*/
|
||||
const checkTriggerConstraints = useCallback(
|
||||
(blockType: string): boolean => {
|
||||
const issue = TriggerUtils.getTriggerAdditionIssue(blocks, blockType)
|
||||
if (issue) {
|
||||
const triggerIssue = TriggerUtils.getTriggerAdditionIssue(blocks, blockType)
|
||||
if (triggerIssue) {
|
||||
const message =
|
||||
issue.issue === 'legacy'
|
||||
triggerIssue.issue === 'legacy'
|
||||
? 'Cannot add new trigger blocks when a legacy Start block exists. Available in newer workflows.'
|
||||
: `A workflow can only have one ${issue.triggerName} trigger block. Please remove the existing one before adding a new one.`
|
||||
: `A workflow can only have one ${triggerIssue.triggerName} trigger block. Please remove the existing one before adding a new one.`
|
||||
addNotification({
|
||||
level: 'error',
|
||||
message,
|
||||
@@ -1147,6 +1148,17 @@ const WorkflowContent = React.memo(() => {
|
||||
})
|
||||
return true
|
||||
}
|
||||
|
||||
const singleInstanceIssue = TriggerUtils.getSingleInstanceBlockIssue(blocks, blockType)
|
||||
if (singleInstanceIssue) {
|
||||
addNotification({
|
||||
level: 'error',
|
||||
message: `A workflow can only have one ${singleInstanceIssue.blockName} block. Please remove the existing one before adding a new one.`,
|
||||
workflowId: activeWorkflowId || undefined,
|
||||
})
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
},
|
||||
[blocks, addNotification, activeWorkflowId]
|
||||
|
||||
@@ -17,6 +17,7 @@ export const ResponseBlock: BlockConfig<ResponseBlockOutput> = {
|
||||
category: 'blocks',
|
||||
bgColor: '#2F55FF',
|
||||
icon: ResponseIcon,
|
||||
singleInstance: true,
|
||||
subBlocks: [
|
||||
{
|
||||
id: 'dataMode',
|
||||
|
||||
@@ -320,6 +320,7 @@ export interface BlockConfig<T extends ToolResponse = ToolResponse> {
|
||||
subBlocks: SubBlockConfig[]
|
||||
triggerAllowed?: boolean
|
||||
authMode?: AuthMode
|
||||
singleInstance?: boolean
|
||||
tools: {
|
||||
access: string[]
|
||||
config?: {
|
||||
|
||||
@@ -11,6 +11,7 @@ import { extractAndPersistCustomTools } from '@/lib/workflows/persistence/custom
|
||||
import { loadWorkflowFromNormalizedTables } from '@/lib/workflows/persistence/utils'
|
||||
import { isValidKey } from '@/lib/workflows/sanitization/key-validation'
|
||||
import { validateWorkflowState } from '@/lib/workflows/sanitization/validation'
|
||||
import { TriggerUtils } from '@/lib/workflows/triggers/triggers'
|
||||
import { getAllBlocks, getBlock } from '@/blocks/registry'
|
||||
import type { SubBlockConfig } from '@/blocks/types'
|
||||
import { EDGE, normalizeName } from '@/executor/constants'
|
||||
@@ -62,6 +63,8 @@ type SkippedItemType =
|
||||
| 'invalid_subflow_parent'
|
||||
| 'nested_subflow_not_allowed'
|
||||
| 'duplicate_block_name'
|
||||
| 'duplicate_trigger'
|
||||
| 'duplicate_single_instance_block'
|
||||
|
||||
/**
|
||||
* Represents an item that was skipped during operation application
|
||||
@@ -1775,6 +1778,34 @@ function applyOperationsToWorkflowState(
|
||||
break
|
||||
}
|
||||
|
||||
const triggerIssue = TriggerUtils.getTriggerAdditionIssue(modifiedState.blocks, params.type)
|
||||
if (triggerIssue) {
|
||||
logSkippedItem(skippedItems, {
|
||||
type: 'duplicate_trigger',
|
||||
operationType: 'add',
|
||||
blockId: block_id,
|
||||
reason: `Cannot add ${triggerIssue.triggerName} - a workflow can only have one`,
|
||||
details: { requestedType: params.type, issue: triggerIssue.issue },
|
||||
})
|
||||
break
|
||||
}
|
||||
|
||||
// Check single-instance block constraints (e.g., Response block)
|
||||
const singleInstanceIssue = TriggerUtils.getSingleInstanceBlockIssue(
|
||||
modifiedState.blocks,
|
||||
params.type
|
||||
)
|
||||
if (singleInstanceIssue) {
|
||||
logSkippedItem(skippedItems, {
|
||||
type: 'duplicate_single_instance_block',
|
||||
operationType: 'add',
|
||||
blockId: block_id,
|
||||
reason: `Cannot add ${singleInstanceIssue.blockName} - a workflow can only have one`,
|
||||
details: { requestedType: params.type },
|
||||
})
|
||||
break
|
||||
}
|
||||
|
||||
// Create new block with proper structure
|
||||
const newBlock = createBlockFromParams(
|
||||
block_id,
|
||||
|
||||
@@ -592,4 +592,34 @@ export class TriggerUtils {
|
||||
const parentWithType = parent as T & { type?: string }
|
||||
return parentWithType.type === 'loop' || parentWithType.type === 'parallel'
|
||||
}
|
||||
|
||||
static isSingleInstanceBlockType(blockType: string): boolean {
|
||||
const blockConfig = getBlock(blockType)
|
||||
return blockConfig?.singleInstance === true
|
||||
}
|
||||
|
||||
static wouldViolateSingleInstanceBlock<T extends { type: string }>(
|
||||
blocks: T[] | Record<string, T>,
|
||||
blockType: string
|
||||
): boolean {
|
||||
if (!TriggerUtils.isSingleInstanceBlockType(blockType)) {
|
||||
return false
|
||||
}
|
||||
|
||||
const blockArray = Array.isArray(blocks) ? blocks : Object.values(blocks)
|
||||
return blockArray.some((block) => block.type === blockType)
|
||||
}
|
||||
|
||||
static getSingleInstanceBlockIssue<T extends { type: string }>(
|
||||
blocks: T[] | Record<string, T>,
|
||||
blockType: string
|
||||
): { issue: 'duplicate'; blockName: string } | null {
|
||||
if (!TriggerUtils.wouldViolateSingleInstanceBlock(blocks, blockType)) {
|
||||
return null
|
||||
}
|
||||
|
||||
const blockConfig = getBlock(blockType)
|
||||
const blockName = blockConfig?.name || blockType
|
||||
return { issue: 'duplicate', blockName }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,6 +41,10 @@ export function getUniqueBlockName(baseName: string, existingBlocks: Record<stri
|
||||
return 'Start'
|
||||
}
|
||||
|
||||
if (normalizedBaseName === 'response') {
|
||||
return 'Response'
|
||||
}
|
||||
|
||||
const baseNameMatch = baseName.match(/^(.*?)(\s+\d+)?$/)
|
||||
const namePrefix = baseNameMatch ? baseNameMatch[1].trim() : baseName
|
||||
|
||||
|
||||
Reference in New Issue
Block a user