mirror of
https://github.com/simstudioai/sim.git
synced 2026-04-06 03:00:16 -04:00
v0.2.3: fix (#592)
* fix(variable resolution): use variable references to not have escaping issues (#587) * fix(variable-resolution): don't inject stringified json, use var refs * fix lint * remove unused var --------- Co-authored-by: Vikhyath Mondreti <vikhyathmondreti@vikhyaths-air.lan> * fix(subblock updates): special selectors persistence (#591) * fix(knowledge-base-selector): should trigger sockets event for persistence * fix subblock value updates for non useSubblockValue components * fix lint --------- Co-authored-by: Vikhyath Mondreti <vikhyathmondreti@Vikhyaths-MacBook-Air.local> * fix(race-cond): for auto-connect rare race condition between adding edge + block (#582) * auto connect race condition * fix lint * Update apps/sim/hooks/use-collaborative-workflow.ts Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> * Update apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> * fix lint --------- Co-authored-by: Vikhyath Mondreti <vikhyathmondreti@Vikhyaths-Air.attlocal.net> Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> Co-authored-by: Vikhyath Mondreti <vikhyathmondreti@Vikhyaths-MacBook-Air.local> --------- Co-authored-by: Vikhyath Mondreti <vikhyathmondreti@vikhyaths-air.lan> Co-authored-by: Vikhyath Mondreti <vikhyathmondreti@Vikhyaths-MacBook-Air.local> Co-authored-by: Vikhyath Mondreti <vikhyathmondreti@Vikhyaths-Air.attlocal.net> Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
b0c1547198
commit
f4e627a9f7
@@ -58,14 +58,12 @@ function resolveCodeVariables(
|
||||
|
||||
// Find the block ID by looking for a block name that normalizes to this value
|
||||
let blockId = null
|
||||
let matchedBlockName = null
|
||||
|
||||
for (const [blockName, id] of Object.entries(blockNameMapping)) {
|
||||
// Apply the same normalization logic as the UI: remove spaces and lowercase
|
||||
const normalizedName = blockName.replace(/\s+/g, '').toLowerCase()
|
||||
if (normalizedName === normalizedBlockName) {
|
||||
blockId = id
|
||||
matchedBlockName = blockName
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -151,9 +149,6 @@ export async function POST(req: NextRequest) {
|
||||
})
|
||||
|
||||
// Resolve variables in the code with workflow environment variables
|
||||
logger.info(`[${requestId}] Original code:`, code.substring(0, 200))
|
||||
logger.info(`[${requestId}] Execution params keys:`, Object.keys(executionParams))
|
||||
|
||||
const { resolvedCode, contextVariables } = resolveCodeVariables(
|
||||
code,
|
||||
executionParams,
|
||||
@@ -162,9 +157,6 @@ export async function POST(req: NextRequest) {
|
||||
blockNameMapping
|
||||
)
|
||||
|
||||
logger.info(`[${requestId}] Resolved code:`, resolvedCode.substring(0, 200))
|
||||
logger.info(`[${requestId}] Context variables keys:`, Object.keys(contextVariables))
|
||||
|
||||
const executionMethod = 'vm' // Default execution method
|
||||
|
||||
// // Try to use Freestyle if the API key is available
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
} from '@/components/ui/command'
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
|
||||
import type { SubBlockConfig } from '@/blocks/types'
|
||||
import { useCollaborativeWorkflow } from '@/hooks/use-collaborative-workflow'
|
||||
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
|
||||
|
||||
interface DocumentData {
|
||||
@@ -50,7 +51,8 @@ export function DocumentSelector({
|
||||
isPreview = false,
|
||||
previewValue,
|
||||
}: DocumentSelectorProps) {
|
||||
const { getValue, setValue } = useSubBlockStore()
|
||||
const { getValue } = useSubBlockStore()
|
||||
const { collaborativeSetSubblockValue } = useCollaborativeWorkflow()
|
||||
|
||||
const [documents, setDocuments] = useState<DocumentData[]>([])
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
@@ -117,7 +119,7 @@ export function DocumentSelector({
|
||||
if (selectedId && !fetchedDocuments.some((doc: DocumentData) => doc.id === selectedId)) {
|
||||
setSelectedId('')
|
||||
if (!isPreview) {
|
||||
setValue(blockId, subBlock.id, '')
|
||||
collaborativeSetSubblockValue(blockId, subBlock.id, '')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,7 +133,7 @@ export function DocumentSelector({
|
||||
setSelectedId(singleDoc.id)
|
||||
setSelectedDocument(singleDoc)
|
||||
if (!isPreview) {
|
||||
setValue(blockId, subBlock.id, singleDoc.id)
|
||||
collaborativeSetSubblockValue(blockId, subBlock.id, singleDoc.id)
|
||||
}
|
||||
onDocumentSelect?.(singleDoc.id)
|
||||
}
|
||||
@@ -141,7 +143,15 @@ export function DocumentSelector({
|
||||
setError((err as Error).message)
|
||||
setDocuments([])
|
||||
}
|
||||
}, [knowledgeBaseId, selectedId, setValue, blockId, subBlock.id, isPreview, onDocumentSelect])
|
||||
}, [
|
||||
knowledgeBaseId,
|
||||
selectedId,
|
||||
collaborativeSetSubblockValue,
|
||||
blockId,
|
||||
subBlock.id,
|
||||
isPreview,
|
||||
onDocumentSelect,
|
||||
])
|
||||
|
||||
// Handle dropdown open/close - fetch documents when opening
|
||||
const handleOpenChange = (isOpen: boolean) => {
|
||||
@@ -163,7 +173,7 @@ export function DocumentSelector({
|
||||
setSelectedId(document.id)
|
||||
|
||||
if (!isPreview) {
|
||||
setValue(blockId, subBlock.id, document.id)
|
||||
collaborativeSetSubblockValue(blockId, subBlock.id, document.id)
|
||||
}
|
||||
|
||||
onDocumentSelect?.(document.id)
|
||||
@@ -193,10 +203,10 @@ export function DocumentSelector({
|
||||
setInitialFetchDone(false)
|
||||
setError(null)
|
||||
if (!isPreview) {
|
||||
setValue(blockId, subBlock.id, '')
|
||||
collaborativeSetSubblockValue(blockId, subBlock.id, '')
|
||||
}
|
||||
}
|
||||
}, [knowledgeBaseId, blockId, subBlock.id, setValue, isPreview])
|
||||
}, [knowledgeBaseId, blockId, subBlock.id, collaborativeSetSubblockValue, isPreview])
|
||||
|
||||
// Fetch documents when knowledge base is available and we haven't fetched yet
|
||||
useEffect(() => {
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useEffect, useState } from 'react'
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import { env } from '@/lib/env'
|
||||
import type { SubBlockConfig } from '@/blocks/types'
|
||||
import { useCollaborativeWorkflow } from '@/hooks/use-collaborative-workflow'
|
||||
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
|
||||
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
|
||||
import type { ConfluenceFileInfo } from './components/confluence-file-selector'
|
||||
@@ -36,7 +37,8 @@ export function FileSelectorInput({
|
||||
isPreview = false,
|
||||
previewValue,
|
||||
}: FileSelectorInputProps) {
|
||||
const { getValue, setValue } = useSubBlockStore()
|
||||
const { getValue } = useSubBlockStore()
|
||||
const { collaborativeSetSubblockValue } = useCollaborativeWorkflow()
|
||||
const { activeWorkflowId } = useWorkflowRegistry()
|
||||
const [selectedFileId, setSelectedFileId] = useState<string>('')
|
||||
const [_fileInfo, setFileInfo] = useState<FileInfo | ConfluenceFileInfo | null>(null)
|
||||
@@ -115,19 +117,19 @@ export function FileSelectorInput({
|
||||
const handleFileChange = (fileId: string, info?: any) => {
|
||||
setSelectedFileId(fileId)
|
||||
setFileInfo(info || null)
|
||||
setValue(blockId, subBlock.id, fileId)
|
||||
collaborativeSetSubblockValue(blockId, subBlock.id, fileId)
|
||||
}
|
||||
|
||||
// Handle issue selection
|
||||
const handleIssueChange = (issueKey: string, info?: JiraIssueInfo) => {
|
||||
setSelectedIssueId(issueKey)
|
||||
setIssueInfo(info || null)
|
||||
setValue(blockId, subBlock.id, issueKey)
|
||||
collaborativeSetSubblockValue(blockId, subBlock.id, issueKey)
|
||||
|
||||
// Clear the fields when a new issue is selected
|
||||
if (isJira) {
|
||||
setValue(blockId, 'summary', '')
|
||||
setValue(blockId, 'description', '')
|
||||
collaborativeSetSubblockValue(blockId, 'summary', '')
|
||||
collaborativeSetSubblockValue(blockId, 'description', '')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,14 +137,14 @@ export function FileSelectorInput({
|
||||
const handleChannelChange = (channelId: string, info?: DiscordChannelInfo) => {
|
||||
setSelectedChannelId(channelId)
|
||||
setChannelInfo(info || null)
|
||||
setValue(blockId, subBlock.id, channelId)
|
||||
collaborativeSetSubblockValue(blockId, subBlock.id, channelId)
|
||||
}
|
||||
|
||||
// Handle calendar selection
|
||||
const handleCalendarChange = (calendarId: string, info?: GoogleCalendarInfo) => {
|
||||
setSelectedCalendarId(calendarId)
|
||||
setCalendarInfo(info || null)
|
||||
setValue(blockId, subBlock.id, calendarId)
|
||||
collaborativeSetSubblockValue(blockId, subBlock.id, calendarId)
|
||||
}
|
||||
|
||||
// For Google Drive
|
||||
@@ -337,7 +339,7 @@ export function FileSelectorInput({
|
||||
onChange={(value, info) => {
|
||||
setSelectedMessageId(value)
|
||||
setMessageInfo(info || null)
|
||||
setValue(blockId, subBlock.id, value)
|
||||
collaborativeSetSubblockValue(blockId, subBlock.id, value)
|
||||
}}
|
||||
provider='microsoft-teams'
|
||||
requiredScopes={subBlock.requiredScopes || []}
|
||||
|
||||
@@ -4,9 +4,9 @@ import { useRef, useState } from 'react'
|
||||
import { X } from 'lucide-react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Progress } from '@/components/ui/progress'
|
||||
import { useCollaborativeWorkflow } from '@/hooks/use-collaborative-workflow'
|
||||
import { useNotificationStore } from '@/stores/notifications/store'
|
||||
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
|
||||
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
|
||||
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
|
||||
import { useSubBlockValue } from '../hooks/use-sub-block-value'
|
||||
|
||||
@@ -58,6 +58,7 @@ export function FileUpload({
|
||||
// Stores
|
||||
const { addNotification } = useNotificationStore()
|
||||
const { activeWorkflowId } = useWorkflowRegistry()
|
||||
const { collaborativeSetSubblockValue } = useCollaborativeWorkflow()
|
||||
|
||||
// Use preview value when in preview mode, otherwise use store value
|
||||
const value = isPreview ? previewValue : storeValue
|
||||
@@ -298,15 +299,15 @@ export function FileUpload({
|
||||
|
||||
setStoreValue(newFiles)
|
||||
|
||||
// Make sure to update the subblock store value for the workflow execution
|
||||
useSubBlockStore.getState().setValue(blockId, subBlockId, newFiles)
|
||||
// Use collaborative update for persistence
|
||||
collaborativeSetSubblockValue(blockId, subBlockId, newFiles)
|
||||
useWorkflowStore.getState().triggerUpdate()
|
||||
} else {
|
||||
// For single file: Replace with last uploaded file
|
||||
setStoreValue(uploadedFiles[0] || null)
|
||||
|
||||
// Make sure to update the subblock store value for the workflow execution
|
||||
useSubBlockStore.getState().setValue(blockId, subBlockId, uploadedFiles[0] || null)
|
||||
// Use collaborative update for persistence
|
||||
collaborativeSetSubblockValue(blockId, subBlockId, uploadedFiles[0] || null)
|
||||
useWorkflowStore.getState().triggerUpdate()
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -363,16 +364,18 @@ export function FileUpload({
|
||||
const updatedFiles = filesArray.filter((f) => f.path !== file.path)
|
||||
setStoreValue(updatedFiles.length > 0 ? updatedFiles : null)
|
||||
|
||||
// Make sure to update the subblock store value for the workflow execution
|
||||
useSubBlockStore
|
||||
.getState()
|
||||
.setValue(blockId, subBlockId, updatedFiles.length > 0 ? updatedFiles : null)
|
||||
// Use collaborative update for persistence
|
||||
collaborativeSetSubblockValue(
|
||||
blockId,
|
||||
subBlockId,
|
||||
updatedFiles.length > 0 ? updatedFiles : null
|
||||
)
|
||||
} else {
|
||||
// For single file: Clear the value
|
||||
setStoreValue(null)
|
||||
|
||||
// Make sure to update the subblock store
|
||||
useSubBlockStore.getState().setValue(blockId, subBlockId, null)
|
||||
// Use collaborative update for persistence
|
||||
collaborativeSetSubblockValue(blockId, subBlockId, null)
|
||||
}
|
||||
|
||||
useWorkflowStore.getState().triggerUpdate()
|
||||
@@ -413,7 +416,7 @@ export function FileUpload({
|
||||
|
||||
// Clear input state immediately for better UX
|
||||
setStoreValue(null)
|
||||
useSubBlockStore.getState().setValue(blockId, subBlockId, null)
|
||||
collaborativeSetSubblockValue(blockId, subBlockId, null)
|
||||
useWorkflowStore.getState().triggerUpdate()
|
||||
|
||||
if (fileInputRef.current) {
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
} from '@/components/ui/command'
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
|
||||
import type { SubBlockConfig } from '@/blocks/types'
|
||||
import { useCollaborativeWorkflow } from '@/hooks/use-collaborative-workflow'
|
||||
import { type KnowledgeBaseData, useKnowledgeStore } from '@/stores/knowledge/store'
|
||||
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
|
||||
|
||||
@@ -36,7 +37,8 @@ export function KnowledgeBaseSelector({
|
||||
}: KnowledgeBaseSelectorProps) {
|
||||
const { getKnowledgeBasesList, knowledgeBasesList, loadingKnowledgeBasesList } =
|
||||
useKnowledgeStore()
|
||||
const { getValue, setValue } = useSubBlockStore()
|
||||
const { getValue } = useSubBlockStore()
|
||||
const { collaborativeSetSubblockValue } = useCollaborativeWorkflow()
|
||||
|
||||
const [knowledgeBases, setKnowledgeBases] = useState<KnowledgeBaseData[]>([])
|
||||
const [loading, setLoading] = useState(false)
|
||||
@@ -90,7 +92,8 @@ export function KnowledgeBaseSelector({
|
||||
setSelectedKnowledgeBases([knowledgeBase])
|
||||
|
||||
if (!isPreview) {
|
||||
setValue(blockId, subBlock.id, knowledgeBase.id)
|
||||
// Use collaborative update for both local store and persistence
|
||||
collaborativeSetSubblockValue(blockId, subBlock.id, knowledgeBase.id)
|
||||
}
|
||||
|
||||
onKnowledgeBaseSelect?.(knowledgeBase.id)
|
||||
@@ -117,7 +120,8 @@ export function KnowledgeBaseSelector({
|
||||
if (!isPreview) {
|
||||
const selectedIds = newSelected.map((kb) => kb.id)
|
||||
const valueToStore = selectedIds.length === 1 ? selectedIds[0] : selectedIds.join(',')
|
||||
setValue(blockId, subBlock.id, valueToStore)
|
||||
// Use collaborative update for both local store and persistence
|
||||
collaborativeSetSubblockValue(blockId, subBlock.id, valueToStore)
|
||||
}
|
||||
|
||||
onKnowledgeBaseSelect?.(newSelected.map((kb) => kb.id))
|
||||
@@ -133,7 +137,8 @@ export function KnowledgeBaseSelector({
|
||||
if (!isPreview) {
|
||||
const selectedIds = newSelected.map((kb) => kb.id)
|
||||
const valueToStore = selectedIds.length === 1 ? selectedIds[0] : selectedIds.join(',')
|
||||
setValue(blockId, subBlock.id, valueToStore)
|
||||
// Use collaborative update for both local store and persistence
|
||||
collaborativeSetSubblockValue(blockId, subBlock.id, valueToStore)
|
||||
}
|
||||
|
||||
onKnowledgeBaseSelect?.(newSelected.map((kb) => kb.id))
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import type { SubBlockConfig } from '@/blocks/types'
|
||||
import { useCollaborativeWorkflow } from '@/hooks/use-collaborative-workflow'
|
||||
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
|
||||
import { type DiscordServerInfo, DiscordServerSelector } from './components/discord-server-selector'
|
||||
import { type JiraProjectInfo, JiraProjectSelector } from './components/jira-project-selector'
|
||||
@@ -26,7 +27,8 @@ export function ProjectSelectorInput({
|
||||
isPreview = false,
|
||||
previewValue,
|
||||
}: ProjectSelectorInputProps) {
|
||||
const { getValue, setValue } = useSubBlockStore()
|
||||
const { getValue } = useSubBlockStore()
|
||||
const { collaborativeSetSubblockValue } = useCollaborativeWorkflow()
|
||||
const [selectedProjectId, setSelectedProjectId] = useState<string>('')
|
||||
const [_projectInfo, setProjectInfo] = useState<JiraProjectInfo | DiscordServerInfo | null>(null)
|
||||
|
||||
@@ -58,21 +60,21 @@ export function ProjectSelectorInput({
|
||||
) => {
|
||||
setSelectedProjectId(projectId)
|
||||
setProjectInfo(info || null)
|
||||
setValue(blockId, subBlock.id, projectId)
|
||||
collaborativeSetSubblockValue(blockId, subBlock.id, projectId)
|
||||
|
||||
// Clear the issue-related fields when a new project is selected
|
||||
if (provider === 'jira') {
|
||||
setValue(blockId, 'summary', '')
|
||||
setValue(blockId, 'description', '')
|
||||
setValue(blockId, 'issueKey', '')
|
||||
collaborativeSetSubblockValue(blockId, 'summary', '')
|
||||
collaborativeSetSubblockValue(blockId, 'description', '')
|
||||
collaborativeSetSubblockValue(blockId, 'issueKey', '')
|
||||
} else if (provider === 'discord') {
|
||||
setValue(blockId, 'channelId', '')
|
||||
collaborativeSetSubblockValue(blockId, 'channelId', '')
|
||||
} else if (provider === 'linear') {
|
||||
if (subBlock.id === 'teamId') {
|
||||
setValue(blockId, 'teamId', projectId)
|
||||
setValue(blockId, 'projectId', '')
|
||||
collaborativeSetSubblockValue(blockId, 'teamId', projectId)
|
||||
collaborativeSetSubblockValue(blockId, 'projectId', '')
|
||||
} else if (subBlock.id === 'projectId') {
|
||||
setValue(blockId, 'projectId', projectId)
|
||||
collaborativeSetSubblockValue(blockId, 'projectId', projectId)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -419,6 +419,7 @@ const WorkflowContent = React.memo(() => {
|
||||
}
|
||||
|
||||
const { type } = event.detail
|
||||
console.log('🛠️ Adding block from toolbar:', type)
|
||||
|
||||
if (!type) return
|
||||
if (type === 'connectionBlock') return
|
||||
@@ -439,32 +440,42 @@ const WorkflowContent = React.memo(() => {
|
||||
y: window.innerHeight / 2,
|
||||
})
|
||||
|
||||
// Add the container node directly to canvas with default dimensions
|
||||
addBlock(id, type, name, centerPosition, {
|
||||
width: 500,
|
||||
height: 300,
|
||||
type: type === 'loop' ? 'loopNode' : 'parallelNode',
|
||||
})
|
||||
|
||||
// Auto-connect logic for container nodes
|
||||
const isAutoConnectEnabled = useGeneralStore.getState().isAutoConnectEnabled
|
||||
let autoConnectEdge
|
||||
if (isAutoConnectEnabled) {
|
||||
const closestBlock = findClosestOutput(centerPosition)
|
||||
if (closestBlock) {
|
||||
// Get appropriate source handle
|
||||
const sourceHandle = determineSourceHandle(closestBlock)
|
||||
|
||||
addEdge({
|
||||
autoConnectEdge = {
|
||||
id: crypto.randomUUID(),
|
||||
source: closestBlock.id,
|
||||
target: id,
|
||||
sourceHandle,
|
||||
targetHandle: 'target',
|
||||
type: 'workflowEdge',
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add the container node directly to canvas with default dimensions and auto-connect edge
|
||||
addBlock(
|
||||
id,
|
||||
type,
|
||||
name,
|
||||
centerPosition,
|
||||
{
|
||||
width: 500,
|
||||
height: 300,
|
||||
type: type === 'loop' ? 'loopNode' : 'parallelNode',
|
||||
},
|
||||
undefined,
|
||||
undefined,
|
||||
autoConnectEdge
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -486,27 +497,30 @@ const WorkflowContent = React.memo(() => {
|
||||
Object.values(blocks).filter((b) => b.type === type).length + 1
|
||||
}`
|
||||
|
||||
// Add the block to the workflow
|
||||
addBlock(id, type, name, centerPosition)
|
||||
|
||||
// Auto-connect logic
|
||||
const isAutoConnectEnabled = useGeneralStore.getState().isAutoConnectEnabled
|
||||
let autoConnectEdge
|
||||
if (isAutoConnectEnabled && type !== 'starter') {
|
||||
const closestBlock = findClosestOutput(centerPosition)
|
||||
console.log('🎯 Closest block found:', closestBlock)
|
||||
if (closestBlock) {
|
||||
// Get appropriate source handle
|
||||
const sourceHandle = determineSourceHandle(closestBlock)
|
||||
|
||||
addEdge({
|
||||
autoConnectEdge = {
|
||||
id: crypto.randomUUID(),
|
||||
source: closestBlock.id,
|
||||
target: id,
|
||||
sourceHandle,
|
||||
targetHandle: 'target',
|
||||
type: 'workflowEdge',
|
||||
})
|
||||
}
|
||||
console.log('✅ Auto-connect edge created:', autoConnectEdge)
|
||||
}
|
||||
}
|
||||
|
||||
// Add the block to the workflow with auto-connect edge
|
||||
addBlock(id, type, name, centerPosition, undefined, undefined, undefined, autoConnectEdge)
|
||||
}
|
||||
|
||||
window.addEventListener('add-block-from-toolbar', handleAddBlockFromToolbar as EventListener)
|
||||
@@ -583,30 +597,40 @@ const WorkflowContent = React.memo(() => {
|
||||
// Resize the parent container to fit the new child container
|
||||
resizeLoopNodesWrapper()
|
||||
} else {
|
||||
// Add the container node directly to canvas with default dimensions
|
||||
addBlock(id, data.type, name, position, {
|
||||
width: 500,
|
||||
height: 300,
|
||||
type: data.type === 'loop' ? 'loopNode' : 'parallelNode',
|
||||
})
|
||||
|
||||
// Auto-connect the container to the closest node on the canvas
|
||||
const isAutoConnectEnabled = useGeneralStore.getState().isAutoConnectEnabled
|
||||
let autoConnectEdge
|
||||
if (isAutoConnectEnabled) {
|
||||
const closestBlock = findClosestOutput(position)
|
||||
if (closestBlock) {
|
||||
const sourceHandle = determineSourceHandle(closestBlock)
|
||||
|
||||
addEdge({
|
||||
autoConnectEdge = {
|
||||
id: crypto.randomUUID(),
|
||||
source: closestBlock.id,
|
||||
target: id,
|
||||
sourceHandle,
|
||||
targetHandle: 'target',
|
||||
type: 'workflowEdge',
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add the container node directly to canvas with default dimensions and auto-connect edge
|
||||
addBlock(
|
||||
id,
|
||||
data.type,
|
||||
name,
|
||||
position,
|
||||
{
|
||||
width: 500,
|
||||
height: 300,
|
||||
type: data.type === 'loop' ? 'loopNode' : 'parallelNode',
|
||||
},
|
||||
undefined,
|
||||
undefined,
|
||||
autoConnectEdge
|
||||
)
|
||||
}
|
||||
|
||||
return
|
||||
@@ -706,26 +730,27 @@ const WorkflowContent = React.memo(() => {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Regular canvas drop
|
||||
addBlock(id, data.type, name, position)
|
||||
|
||||
// Regular auto-connect logic
|
||||
const isAutoConnectEnabled = useGeneralStore.getState().isAutoConnectEnabled
|
||||
let autoConnectEdge
|
||||
if (isAutoConnectEnabled && data.type !== 'starter') {
|
||||
const closestBlock = findClosestOutput(position)
|
||||
if (closestBlock) {
|
||||
const sourceHandle = determineSourceHandle(closestBlock)
|
||||
|
||||
addEdge({
|
||||
autoConnectEdge = {
|
||||
id: crypto.randomUUID(),
|
||||
source: closestBlock.id,
|
||||
target: id,
|
||||
sourceHandle,
|
||||
targetHandle: 'target',
|
||||
type: 'workflowEdge',
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Regular canvas drop with auto-connect edge
|
||||
addBlock(id, data.type, name, position, undefined, undefined, undefined, autoConnectEdge)
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error('Error dropping block:', { err })
|
||||
|
||||
@@ -91,6 +91,10 @@ export function useCollaborativeWorkflow() {
|
||||
payload.parentId,
|
||||
payload.extent
|
||||
)
|
||||
// Handle auto-connect edge if present
|
||||
if (payload.autoConnectEdge) {
|
||||
workflowStore.addEdge(payload.autoConnectEdge)
|
||||
}
|
||||
break
|
||||
case 'update-position': {
|
||||
// Apply position update only if it's newer than the last applied timestamp
|
||||
@@ -164,6 +168,10 @@ export function useCollaborativeWorkflow() {
|
||||
payload.parentId,
|
||||
payload.extent
|
||||
)
|
||||
// Handle auto-connect edge if present
|
||||
if (payload.autoConnectEdge) {
|
||||
workflowStore.addEdge(payload.autoConnectEdge)
|
||||
}
|
||||
break
|
||||
}
|
||||
} else if (target === 'edge') {
|
||||
@@ -284,7 +292,8 @@ export function useCollaborativeWorkflow() {
|
||||
position: Position,
|
||||
data?: Record<string, any>,
|
||||
parentId?: string,
|
||||
extent?: 'parent'
|
||||
extent?: 'parent',
|
||||
autoConnectEdge?: Edge
|
||||
) => {
|
||||
// Create complete block data upfront using the same logic as the store
|
||||
const blockConfig = getBlock(type)
|
||||
@@ -306,10 +315,14 @@ export function useCollaborativeWorkflow() {
|
||||
height: 0,
|
||||
parentId,
|
||||
extent,
|
||||
autoConnectEdge, // Include edge data for atomic operation
|
||||
}
|
||||
|
||||
// Apply locally first
|
||||
workflowStore.addBlock(id, type, name, position, data, parentId, extent)
|
||||
if (autoConnectEdge) {
|
||||
workflowStore.addEdge(autoConnectEdge)
|
||||
}
|
||||
|
||||
// Then broadcast to other clients with complete block data
|
||||
if (!isApplyingRemoteChange.current) {
|
||||
@@ -354,10 +367,14 @@ export function useCollaborativeWorkflow() {
|
||||
height: 0, // Default height, will be set by the UI
|
||||
parentId,
|
||||
extent,
|
||||
autoConnectEdge, // Include edge data for atomic operation
|
||||
}
|
||||
|
||||
// Apply locally first
|
||||
workflowStore.addBlock(id, type, name, position, data, parentId, extent)
|
||||
if (autoConnectEdge) {
|
||||
workflowStore.addEdge(autoConnectEdge)
|
||||
}
|
||||
|
||||
// Then broadcast to other clients with complete block data
|
||||
if (!isApplyingRemoteChange.current) {
|
||||
|
||||
@@ -29,6 +29,34 @@ const db = socketDb
|
||||
// Constants
|
||||
const DEFAULT_LOOP_ITERATIONS = 5
|
||||
|
||||
/**
|
||||
* Shared function to handle auto-connect edge insertion
|
||||
* @param tx - Database transaction
|
||||
* @param workflowId - The workflow ID
|
||||
* @param autoConnectEdge - The auto-connect edge data
|
||||
* @param logger - Logger instance
|
||||
*/
|
||||
async function insertAutoConnectEdge(
|
||||
tx: any,
|
||||
workflowId: string,
|
||||
autoConnectEdge: any,
|
||||
logger: any
|
||||
) {
|
||||
if (!autoConnectEdge) return
|
||||
|
||||
await tx.insert(workflowEdges).values({
|
||||
id: autoConnectEdge.id,
|
||||
workflowId,
|
||||
sourceBlockId: autoConnectEdge.source,
|
||||
targetBlockId: autoConnectEdge.target,
|
||||
sourceHandle: autoConnectEdge.sourceHandle || null,
|
||||
targetHandle: autoConnectEdge.targetHandle || null,
|
||||
})
|
||||
logger.debug(
|
||||
`Added auto-connect edge ${autoConnectEdge.id}: ${autoConnectEdge.source} -> ${autoConnectEdge.target}`
|
||||
)
|
||||
}
|
||||
|
||||
// Enum for subflow types
|
||||
enum SubflowType {
|
||||
LOOP = 'loop',
|
||||
@@ -246,6 +274,9 @@ async function handleBlockOperationTx(
|
||||
}
|
||||
|
||||
await tx.insert(workflowBlocks).values(insertData)
|
||||
|
||||
// Handle auto-connect edge if present
|
||||
await insertAutoConnectEdge(tx, workflowId, payload.autoConnectEdge, logger)
|
||||
} catch (insertError) {
|
||||
logger.error(`[SERVER] ❌ Failed to insert block ${payload.id}:`, insertError)
|
||||
throw insertError
|
||||
@@ -592,6 +623,9 @@ async function handleBlockOperationTx(
|
||||
}
|
||||
|
||||
await tx.insert(workflowBlocks).values(insertData)
|
||||
|
||||
// Handle auto-connect edge if present
|
||||
await insertAutoConnectEdge(tx, workflowId, payload.autoConnectEdge, logger)
|
||||
} catch (insertError) {
|
||||
logger.error(`[SERVER] ❌ Failed to insert duplicated block ${payload.id}:`, insertError)
|
||||
throw insertError
|
||||
|
||||
@@ -279,6 +279,32 @@ describe('Socket Server Index Integration', () => {
|
||||
expect(() => WorkflowOperationSchema.parse(validOperation)).not.toThrow()
|
||||
})
|
||||
|
||||
it.concurrent('should validate block operations with autoConnectEdge', async () => {
|
||||
const { WorkflowOperationSchema } = await import('./validation/schemas')
|
||||
|
||||
const validOperationWithAutoEdge = {
|
||||
operation: 'add',
|
||||
target: 'block',
|
||||
payload: {
|
||||
id: 'test-block',
|
||||
type: 'action',
|
||||
name: 'Test Block',
|
||||
position: { x: 100, y: 200 },
|
||||
autoConnectEdge: {
|
||||
id: 'auto-edge-123',
|
||||
source: 'source-block',
|
||||
target: 'test-block',
|
||||
sourceHandle: 'output',
|
||||
targetHandle: 'target',
|
||||
type: 'workflowEdge',
|
||||
},
|
||||
},
|
||||
timestamp: Date.now(),
|
||||
}
|
||||
|
||||
expect(() => WorkflowOperationSchema.parse(validOperationWithAutoEdge)).not.toThrow()
|
||||
})
|
||||
|
||||
it.concurrent('should validate edge operations', async () => {
|
||||
const { WorkflowOperationSchema } = await import('./validation/schemas')
|
||||
|
||||
|
||||
@@ -5,6 +5,16 @@ const PositionSchema = z.object({
|
||||
y: z.number(),
|
||||
})
|
||||
|
||||
// Schema for auto-connect edge data
|
||||
const AutoConnectEdgeSchema = z.object({
|
||||
id: z.string(),
|
||||
source: z.string(),
|
||||
target: z.string(),
|
||||
sourceHandle: z.string().nullable().optional(),
|
||||
targetHandle: z.string().nullable().optional(),
|
||||
type: z.string().optional(),
|
||||
})
|
||||
|
||||
export const BlockOperationSchema = z.object({
|
||||
operation: z.enum([
|
||||
'add',
|
||||
@@ -35,6 +45,7 @@ export const BlockOperationSchema = z.object({
|
||||
isWide: z.boolean().optional(),
|
||||
advancedMode: z.boolean().optional(),
|
||||
height: z.number().optional(),
|
||||
autoConnectEdge: AutoConnectEdgeSchema.optional(), // Add support for auto-connect edges
|
||||
}),
|
||||
timestamp: z.number(),
|
||||
})
|
||||
@@ -69,4 +80,4 @@ export const WorkflowOperationSchema = z.union([
|
||||
SubflowOperationSchema,
|
||||
])
|
||||
|
||||
export { PositionSchema }
|
||||
export { PositionSchema, AutoConnectEdgeSchema }
|
||||
|
||||
Reference in New Issue
Block a user