mirror of
https://github.com/simstudioai/sim.git
synced 2026-01-10 15:38:00 -05:00
fix(registry-state): fixed bug on add workflow / improvement(ux): added autoconnect
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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() {
|
||||
<Switch id="debug-mode" />
|
||||
</div>
|
||||
<div className="flex items-center justify-between py-1">
|
||||
<Label htmlFor="auto-save" className="font-medium">
|
||||
Auto-save workflows
|
||||
<Label htmlFor="auto-connect" className="font-medium">
|
||||
Auto-connect on drop
|
||||
</Label>
|
||||
<Switch id="auto-save" />
|
||||
<Switch
|
||||
id="auto-connect"
|
||||
checked={isAutoConnectEnabled}
|
||||
onCheckedChange={toggleAutoConnect}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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(),
|
||||
|
||||
29
stores/settings/general/store.ts
Normal file
29
stores/settings/general/store.ts
Normal file
@@ -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<GeneralStore>()(
|
||||
devtools(
|
||||
persist(
|
||||
(set) => ({
|
||||
isAutoConnectEnabled: true,
|
||||
|
||||
toggleAutoConnect: () =>
|
||||
set((state) => ({ isAutoConnectEnabled: !state.isAutoConnectEnabled })),
|
||||
}),
|
||||
{
|
||||
name: 'general-settings',
|
||||
}
|
||||
),
|
||||
{ name: 'general-store' }
|
||||
)
|
||||
)
|
||||
@@ -84,101 +84,103 @@ export const useWorkflowRegistry = create<WorkflowRegistry>()(
|
||||
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<WorkflowRegistry>()(
|
||||
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<WorkflowRegistry>()(
|
||||
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) => {
|
||||
|
||||
Reference in New Issue
Block a user