diff --git a/app/w/[id]/workflow.tsx b/app/w/[id]/workflow.tsx
index a62ee1b54a..224a2534e8 100644
--- a/app/w/[id]/workflow.tsx
+++ b/app/w/[id]/workflow.tsx
@@ -7,12 +7,14 @@ import ReactFlow, {
ConnectionLineType,
EdgeTypes,
NodeTypes,
+ Position,
ReactFlowProvider,
useOnViewportChange,
useReactFlow,
} from 'reactflow'
import 'reactflow/dist/style.css'
import { useNotificationStore } from '@/stores/notifications/store'
+import { useGeneralStore } from '@/stores/settings/general/store'
import { initializeStateLogger } from '@/stores/workflow/logger'
import { useWorkflowRegistry } from '@/stores/workflow/registry/store'
import { useWorkflowStore } from '@/stores/workflow/store'
@@ -207,6 +209,26 @@ function WorkflowContent() {
)
// Handle drops
+ const findClosestOutput = useCallback(
+ (newNodePosition: { x: number; y: number }) => {
+ const existingBlocks = Object.entries(blocks)
+ .filter(([_, block]) => block.enabled && block.type !== 'condition')
+ .map(([id, block]) => ({
+ id,
+ position: block.position,
+ distance: Math.sqrt(
+ Math.pow(block.position.x - newNodePosition.x, 2) +
+ Math.pow(block.position.y - newNodePosition.y, 2)
+ ),
+ }))
+ .sort((a, b) => a.distance - b.distance)
+
+ return existingBlocks[0]?.id
+ },
+ [blocks]
+ )
+
+ // Update the onDrop handler
const onDrop = useCallback(
(event: React.DragEvent) => {
event.preventDefault()
@@ -233,11 +255,27 @@ function WorkflowContent() {
}`
addBlock(id, data.type, name, position)
+
+ // Auto-connect logic
+ const isAutoConnectEnabled = useGeneralStore.getState().isAutoConnectEnabled
+ if (isAutoConnectEnabled && data.type !== 'starter') {
+ const closestBlockId = findClosestOutput(position)
+ if (closestBlockId) {
+ addEdge({
+ id: crypto.randomUUID(),
+ source: closestBlockId,
+ target: id,
+ sourceHandle: 'source',
+ targetHandle: 'target',
+ type: 'custom',
+ })
+ }
+ }
} catch (err) {
console.error('Error dropping block:', err)
}
},
- [project, blocks, addBlock]
+ [project, blocks, addBlock, addEdge, findClosestOutput]
)
// Node selection
diff --git a/app/w/components/sidebar/components/settings-modal/components/environment/environment.tsx b/app/w/components/sidebar/components/settings-modal/components/environment/environment.tsx
index b8930324c0..31ab6fe5e7 100644
--- a/app/w/components/sidebar/components/settings-modal/components/environment/environment.tsx
+++ b/app/w/components/sidebar/components/settings-modal/components/environment/environment.tsx
@@ -14,8 +14,8 @@ import {
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
-import { useEnvironmentStore } from '@/stores/environment/store'
-import { EnvironmentVariable as StoreEnvironmentVariable } from '@/stores/environment/types'
+import { useEnvironmentStore } from '@/stores/settings/environment/store'
+import { EnvironmentVariable as StoreEnvironmentVariable } from '@/stores/settings/environment/types'
// Extend the store type with our UI-specific fields
interface UIEnvironmentVariable extends StoreEnvironmentVariable {
diff --git a/app/w/components/sidebar/components/settings-modal/components/general/general.tsx b/app/w/components/sidebar/components/settings-modal/components/general/general.tsx
index f6d782674a..31203e563f 100644
--- a/app/w/components/sidebar/components/settings-modal/components/general/general.tsx
+++ b/app/w/components/sidebar/components/settings-modal/components/general/general.tsx
@@ -13,10 +13,13 @@ import {
import { Button } from '@/components/ui/button'
import { Label } from '@/components/ui/label'
import { Switch } from '@/components/ui/switch'
+import { useGeneralStore } from '@/stores/settings/general/store'
import { resetAllStores } from '@/stores'
export function General() {
const router = useRouter()
+ const isAutoConnectEnabled = useGeneralStore((state) => state.isAutoConnectEnabled)
+ const toggleAutoConnect = useGeneralStore((state) => state.toggleAutoConnect)
const handleResetData = () => {
resetAllStores()
@@ -35,10 +38,14 @@ export function General() {
-
diff --git a/app/w/hooks/use-workflow-execution.ts b/app/w/hooks/use-workflow-execution.ts
index 685d0b5649..526dbc918f 100644
--- a/app/w/hooks/use-workflow-execution.ts
+++ b/app/w/hooks/use-workflow-execution.ts
@@ -1,7 +1,7 @@
import { useCallback, useState } from 'react'
import { useConsoleStore } from '@/stores/console/store'
-import { useEnvironmentStore } from '@/stores/environment/store'
import { useNotificationStore } from '@/stores/notifications/store'
+import { useEnvironmentStore } from '@/stores/settings/environment/store'
import { useWorkflowRegistry } from '@/stores/workflow/registry/store'
import { useWorkflowStore } from '@/stores/workflow/store'
import { Executor } from '@/executor'
diff --git a/components/ui/env-var-dropdown.tsx b/components/ui/env-var-dropdown.tsx
index 5caa265d04..a250cc68e5 100644
--- a/components/ui/env-var-dropdown.tsx
+++ b/components/ui/env-var-dropdown.tsx
@@ -1,6 +1,6 @@
import React, { useEffect, useState } from 'react'
import { cn } from '@/lib/utils'
-import { useEnvironmentStore } from '@/stores/environment/store'
+import { useEnvironmentStore } from '@/stores/settings/environment/store'
interface EnvVarDropdownProps {
visible: boolean
diff --git a/stores/chat/store.ts b/stores/chat/store.ts
index 24c02b3505..1ed09dfedd 100644
--- a/stores/chat/store.ts
+++ b/stores/chat/store.ts
@@ -1,6 +1,6 @@
import { create } from 'zustand'
import { devtools } from 'zustand/middleware'
-import { useEnvironmentStore } from '../environment/store'
+import { useEnvironmentStore } from '../settings/environment/store'
import { useWorkflowStore } from '../workflow/store'
import { ChatMessage, ChatStore } from './types'
import { calculateBlockPosition, getNextBlockNumber } from './utils'
diff --git a/stores/index.ts b/stores/index.ts
index 7633e093a1..9aacab6c85 100644
--- a/stores/index.ts
+++ b/stores/index.ts
@@ -1,14 +1,13 @@
import { useChatStore } from './chat/store'
import { useConsoleStore } from './console/store'
-import { useEnvironmentStore } from './environment/store'
import { useExecutionStore } from './execution/store'
import { useNotificationStore } from './notifications/store'
+import { useEnvironmentStore } from './settings/environment/store'
+import { useGeneralStore } from './settings/general/store'
import { useWorkflowRegistry } from './workflow/registry/store'
import { useWorkflowStore } from './workflow/store'
-/**
- * Reset all application stores to their initial state
- */
+// Reset all application stores to their initial state
export const resetAllStores = () => {
// Selectively clear localStorage items
if (typeof window !== 'undefined') {
@@ -30,12 +29,11 @@ export const resetAllStores = () => {
useEnvironmentStore.setState({ variables: {} })
useExecutionStore.getState().reset()
useConsoleStore.setState({ entries: [], isOpen: false })
+ useGeneralStore.setState({ isAutoConnectEnabled: true })
useChatStore.setState({ messages: [], isProcessing: false, error: null })
}
-/**
- * Log the current state of all stores
- */
+// Log the current state of all stores
export const logAllStores = () => {
const state = {
workflow: useWorkflowStore.getState(),
diff --git a/stores/environment/store.ts b/stores/settings/environment/store.ts
similarity index 100%
rename from stores/environment/store.ts
rename to stores/settings/environment/store.ts
diff --git a/stores/environment/types.ts b/stores/settings/environment/types.ts
similarity index 100%
rename from stores/environment/types.ts
rename to stores/settings/environment/types.ts
diff --git a/stores/settings/general/store.ts b/stores/settings/general/store.ts
new file mode 100644
index 0000000000..31b7e3e7d4
--- /dev/null
+++ b/stores/settings/general/store.ts
@@ -0,0 +1,29 @@
+import { create } from 'zustand'
+import { devtools, persist } from 'zustand/middleware'
+
+interface General {
+ isAutoConnectEnabled: boolean
+}
+
+interface GeneralActions {
+ toggleAutoConnect: () => void
+}
+
+type GeneralStore = General & GeneralActions
+
+export const useGeneralStore = create()(
+ devtools(
+ persist(
+ (set) => ({
+ isAutoConnectEnabled: true,
+
+ toggleAutoConnect: () =>
+ set((state) => ({ isAutoConnectEnabled: !state.isAutoConnectEnabled })),
+ }),
+ {
+ name: 'general-settings',
+ }
+ ),
+ { name: 'general-store' }
+ )
+)
diff --git a/stores/workflow/registry/store.ts b/stores/workflow/registry/store.ts
index 1f543c7774..521ba84f69 100644
--- a/stores/workflow/registry/store.ts
+++ b/stores/workflow/registry/store.ts
@@ -84,101 +84,103 @@ export const useWorkflowRegistry = create()(
error: null,
}))
- // Add starter block to new workflow
+ // Create starter block for new workflow
const starterId = crypto.randomUUID()
- useWorkflowStore.setState({
- blocks: {
- [starterId]: {
- id: starterId,
- type: 'starter',
- name: 'Starter',
- position: { x: 100, y: 100 },
- subBlocks: {
- startWorkflow: {
- id: 'startWorkflow',
- type: 'dropdown',
- value: 'manual',
- },
- webhookPath: {
- id: 'webhookPath',
- type: 'short-input',
- value: '',
- },
- webhookSecret: {
- id: 'webhookSecret',
- type: 'short-input',
- value: '',
- },
- scheduleType: {
- id: 'scheduleType',
- type: 'dropdown',
- value: 'daily',
- },
- minutesInterval: {
- id: 'minutesInterval',
- type: 'short-input',
- value: '',
- },
- minutesStartingAt: {
- id: 'minutesStartingAt',
- type: 'short-input',
- value: '',
- },
- hourlyMinute: {
- id: 'hourlyMinute',
- type: 'short-input',
- value: '',
- },
- dailyTime: {
- id: 'dailyTime',
- type: 'short-input',
- value: '',
- },
- weeklyDay: {
- id: 'weeklyDay',
- type: 'dropdown',
- value: 'MON',
- },
- weeklyDayTime: {
- id: 'weeklyDayTime',
- type: 'short-input',
- value: '',
- },
- monthlyDay: {
- id: 'monthlyDay',
- type: 'short-input',
- value: '',
- },
- monthlyTime: {
- id: 'monthlyTime',
- type: 'short-input',
- value: '',
- },
- cronExpression: {
- id: 'cronExpression',
- type: 'short-input',
- value: '',
- },
- timezone: {
- id: 'timezone',
- type: 'dropdown',
- value: 'UTC',
- },
- },
- outputs: {
- response: {
- type: {
- result: 'any',
- stdout: 'string',
- executionTime: 'number',
- },
- },
- },
- enabled: true,
- horizontalHandles: true,
- isWide: false,
- height: 0,
+ const starterBlock = {
+ id: starterId,
+ type: 'starter' as const,
+ name: 'Start',
+ position: { x: 100, y: 100 },
+ subBlocks: {
+ startWorkflow: {
+ id: 'startWorkflow',
+ type: 'dropdown' as const,
+ value: 'manual',
},
+ webhookPath: {
+ id: 'webhookPath',
+ type: 'short-input' as const,
+ value: '',
+ },
+ webhookSecret: {
+ id: 'webhookSecret',
+ type: 'short-input' as const,
+ value: '',
+ },
+ scheduleType: {
+ id: 'scheduleType',
+ type: 'dropdown' as const,
+ value: 'daily',
+ },
+ minutesInterval: {
+ id: 'minutesInterval',
+ type: 'short-input' as const,
+ value: '',
+ },
+ minutesStartingAt: {
+ id: 'minutesStartingAt',
+ type: 'short-input' as const,
+ value: '',
+ },
+ hourlyMinute: {
+ id: 'hourlyMinute',
+ type: 'short-input' as const,
+ value: '',
+ },
+ dailyTime: {
+ id: 'dailyTime',
+ type: 'short-input' as const,
+ value: '',
+ },
+ weeklyDay: {
+ id: 'weeklyDay',
+ type: 'dropdown' as const,
+ value: 'MON',
+ },
+ weeklyDayTime: {
+ id: 'weeklyDayTime',
+ type: 'short-input' as const,
+ value: '',
+ },
+ monthlyDay: {
+ id: 'monthlyDay',
+ type: 'short-input' as const,
+ value: '',
+ },
+ monthlyTime: {
+ id: 'monthlyTime',
+ type: 'short-input' as const,
+ value: '',
+ },
+ cronExpression: {
+ id: 'cronExpression',
+ type: 'short-input' as const,
+ value: '',
+ },
+ timezone: {
+ id: 'timezone',
+ type: 'dropdown' as const,
+ value: 'UTC',
+ },
+ },
+ outputs: {
+ response: {
+ type: {
+ result: 'any',
+ stdout: 'string',
+ executionTime: 'number',
+ },
+ },
+ },
+ enabled: true,
+ horizontalHandles: true,
+ isWide: false,
+ height: 0,
+ }
+
+ const initialState = {
+ blocks: {
+ [starterId]: starterBlock,
},
edges: [],
loops: {},
@@ -187,97 +189,7 @@ export const useWorkflowRegistry = create()(
present: {
state: {
blocks: {
- [starterId]: {
- id: starterId,
- type: 'starter',
- name: 'Starter',
- position: { x: 100, y: 100 },
- subBlocks: {
- startWorkflow: {
- id: 'startWorkflow',
- type: 'dropdown',
- value: 'manual',
- },
- webhookPath: {
- id: 'webhookPath',
- type: 'short-input',
- value: '',
- },
- webhookSecret: {
- id: 'webhookSecret',
- type: 'short-input',
- value: '',
- },
- scheduleType: {
- id: 'scheduleType',
- type: 'dropdown',
- value: 'daily',
- },
- minutesInterval: {
- id: 'minutesInterval',
- type: 'short-input',
- value: '',
- },
- minutesStartingAt: {
- id: 'minutesStartingAt',
- type: 'short-input',
- value: '',
- },
- hourlyMinute: {
- id: 'hourlyMinute',
- type: 'short-input',
- value: '',
- },
- dailyTime: {
- id: 'dailyTime',
- type: 'short-input',
- value: '',
- },
- weeklyDay: {
- id: 'weeklyDay',
- type: 'dropdown',
- value: 'MON',
- },
- weeklyDayTime: {
- id: 'weeklyDayTime',
- type: 'short-input',
- value: '',
- },
- monthlyDay: {
- id: 'monthlyDay',
- type: 'short-input',
- value: '',
- },
- monthlyTime: {
- id: 'monthlyTime',
- type: 'short-input',
- value: '',
- },
- cronExpression: {
- id: 'cronExpression',
- type: 'short-input',
- value: '',
- },
- timezone: {
- id: 'timezone',
- type: 'dropdown',
- value: 'UTC',
- },
- },
- outputs: {
- response: {
- type: {
- result: 'any',
- stdout: 'string',
- executionTime: 'number',
- },
- },
- },
- enabled: true,
- horizontalHandles: true,
- isWide: false,
- height: 0,
- },
+ [starterId]: starterBlock,
},
edges: [],
loops: {},
@@ -288,14 +200,20 @@ export const useWorkflowRegistry = create()(
future: [],
},
lastSaved: Date.now(),
- })
+ }
// Save workflow list to localStorage
const workflows = get().workflows
localStorage.setItem('workflow-registry', JSON.stringify(workflows))
// Save initial workflow state to localStorage
- localStorage.setItem(`workflow-${metadata.id}`, JSON.stringify(useWorkflowStore.getState()))
+ localStorage.setItem(`workflow-${metadata.id}`, JSON.stringify(initialState))
+
+ // If this is the first workflow, set it as active and update workflow store
+ if (Object.keys(workflows).length === 1) {
+ set({ activeWorkflowId: metadata.id })
+ useWorkflowStore.setState(initialState)
+ }
},
removeWorkflow: (id: string) => {