diff --git a/app/w/[id]/workflow.tsx b/app/w/[id]/workflow.tsx
index 4c2c17465e..108bab8122 100644
--- a/app/w/[id]/workflow.tsx
+++ b/app/w/[id]/workflow.tsx
@@ -1,6 +1,6 @@
'use client'
-import { useCallback } from 'react'
+import { useCallback, useEffect } from 'react'
import ReactFlow, {
Background,
NodeProps,
@@ -22,6 +22,8 @@ import { getBlock } from '../../../blocks/configs'
import { WorkflowBlock } from '../components/workflow-block/workflow-block'
import { BlockConfig } from '../../../blocks/types/block'
import { BlockType } from '../../../blocks/types/block'
+import { useWorkflowStore } from '@/stores/workflow/workflow-store'
+import { initializeStateLogger } from '@/stores/workflow/state-logger'
/**
* Represents the data structure for a workflow node
@@ -84,35 +86,69 @@ const edgeTypes: EdgeTypes = { custom: CustomEdge }
* Main canvas component for the workflow editor
*/
function WorkflowCanvas() {
- // Flow state management
- const [nodes, setNodes, onNodesChange] = useNodesState([])
- const [edges, setEdges, onEdgesChange] = useEdgesState([])
+ // Replace useNodesState and useEdgesState with our store
+ const { blocks, edges, addBlock, updateBlockPosition, addEdge, removeEdge } =
+ useWorkflowStore()
+
+ // Convert blocks to ReactFlow nodes
+ const nodes = Object.values(blocks).map((block) => ({
+ id: block.id,
+ type: 'workflowBlock',
+ position: block.position,
+ data: {
+ type: block.type,
+ config: getBlock(block.type),
+ name: block.name,
+ },
+ }))
+
const { project } = useReactFlow()
- /**
- * Handles new edge connections between nodes
- */
- const onConnect = useCallback(
- (connection: Connection) => setEdges((eds) => addEdge(connection, eds)),
- [setEdges]
+ const onNodesChange = useCallback(
+ (changes: any) => {
+ changes.forEach((change: any) => {
+ if (change.type === 'position' && change.position) {
+ updateBlockPosition(change.id, change.position)
+ }
+ })
+ },
+ [updateBlockPosition]
)
- /**
- * Handles dropping new blocks onto the canvas
- */
+ const onEdgesChange = useCallback(
+ (changes: any) => {
+ changes.forEach((change: any) => {
+ if (change.type === 'remove') {
+ removeEdge(change.id)
+ }
+ })
+ },
+ [removeEdge]
+ )
+
+ const onConnect = useCallback(
+ (connection: any) => {
+ addEdge({
+ ...connection,
+ id: crypto.randomUUID(),
+ type: 'custom',
+ })
+ },
+ [addEdge]
+ )
+
+ // Update onDrop to use our store
const onDrop = useCallback(
(event: React.DragEvent) => {
event.preventDefault()
try {
- // Calculate drop position
const reactFlowBounds = event.currentTarget.getBoundingClientRect()
const position = project({
x: event.clientX - reactFlowBounds.left,
y: event.clientY - reactFlowBounds.top,
})
- // Get block configuration
const { type } = JSON.parse(
event.dataTransfer.getData('application/json')
)
@@ -123,26 +159,17 @@ function WorkflowCanvas() {
return
}
- // Create new node
- const newNode = {
- id: crypto.randomUUID(),
- type: 'workflowBlock',
- position,
- data: {
- type,
- config: blockConfig,
- name: `${blockConfig.toolbar.title} ${
- nodes.filter((n) => n.data.type === type).length + 1
- }`,
- },
- }
+ const id = crypto.randomUUID()
+ const name = `${blockConfig.toolbar.title} ${
+ Object.values(blocks).filter((b) => b.type === type).length + 1
+ }`
- setNodes((nds) => [...nds, newNode])
+ addBlock(id, type, name, position)
} catch (err) {
console.error('Error dropping block:', err)
}
},
- [project, nodes, setNodes]
+ [project, blocks, addBlock]
)
// Keyframe animation styles
@@ -153,6 +180,10 @@ function WorkflowCanvas() {
}
`
+ useEffect(() => {
+ initializeStateLogger()
+ }, [])
+
return (
diff --git a/stores/workflow/state-logger.ts b/stores/workflow/state-logger.ts
new file mode 100644
index 0000000000..7652c36759
--- /dev/null
+++ b/stores/workflow/state-logger.ts
@@ -0,0 +1,11 @@
+import { useWorkflowStore } from './workflow-store'
+
+export function initializeStateLogger() {
+ useWorkflowStore.subscribe((state) => {
+ console.log('Workflow State Updated:', {
+ blocks: state.blocks,
+ edges: state.edges,
+ selectedBlockId: state.selectedBlockId,
+ })
+ })
+}
\ No newline at end of file
diff --git a/stores/workflow/types.ts b/stores/workflow/types.ts
new file mode 100644
index 0000000000..df49afd4c1
--- /dev/null
+++ b/stores/workflow/types.ts
@@ -0,0 +1,46 @@
+import { Node, Edge } from 'reactflow'
+import { BlockType, OutputType, SubBlockType } from '@/blocks/types/block'
+
+export interface Position {
+ x: number
+ y: number
+}
+
+export interface BlockState {
+ id: string
+ type: BlockType
+ name: string
+ position: Position
+ inputs: Record
+ outputType: OutputType
+}
+
+export interface BlockInput {
+ id: string
+ type: SubBlockType
+ value: string | number | string[][] | null
+}
+
+export interface WorkflowState {
+ blocks: Record
+ edges: Edge[]
+ selectedBlockId: string | null
+}
+
+export interface WorkflowActions {
+ addBlock: (
+ id: string,
+ type: BlockType,
+ name: string,
+ position: Position
+ ) => void
+ updateBlockPosition: (id: string, position: Position) => void
+ updateBlockInput: (blockId: string, inputId: string, value: any) => void
+ removeBlock: (id: string) => void
+ addEdge: (edge: Edge) => void
+ removeEdge: (edgeId: string) => void
+ setSelectedBlock: (id: string | null) => void
+ clear: () => void
+}
+
+export type WorkflowStore = WorkflowState & WorkflowActions
\ No newline at end of file
diff --git a/stores/workflow/workflow-store.ts b/stores/workflow/workflow-store.ts
new file mode 100644
index 0000000000..947bbc6a2f
--- /dev/null
+++ b/stores/workflow/workflow-store.ts
@@ -0,0 +1,117 @@
+import { create } from 'zustand'
+import { devtools } from 'zustand/middleware'
+import { Edge } from 'reactflow'
+import { BlockType } from '@/blocks/types/block'
+import { Position } from '@/stores/workflow/types'
+import { WorkflowStore } from './types'
+import { getBlock } from '@/blocks/configs'
+
+const initialState = {
+ blocks: {},
+ edges: [],
+ selectedBlockId: null,
+}
+
+export const useWorkflowStore = create()(
+ devtools(
+ (set, get) => ({
+ ...initialState,
+
+ addBlock: (id: string, type: BlockType, name: string, position: Position) => {
+ const blockConfig = getBlock(type)
+ if (!blockConfig) return
+
+ const inputs: Record = {}
+ blockConfig.workflow.subBlocks.forEach((subBlock) => {
+ inputs[subBlock.id || crypto.randomUUID()] = {
+ id: subBlock.id || crypto.randomUUID(),
+ type: subBlock.type,
+ value: null,
+ }
+ })
+
+ set((state) => ({
+ blocks: {
+ ...state.blocks,
+ [id]: {
+ id,
+ type,
+ name,
+ position,
+ inputs,
+ outputType:
+ typeof blockConfig.workflow.outputType === 'string'
+ ? blockConfig.workflow.outputType
+ : blockConfig.workflow.outputType.default,
+ },
+ },
+ }))
+ },
+
+ updateBlockPosition: (id: string, position: Position) => {
+ set((state) => ({
+ blocks: {
+ ...state.blocks,
+ [id]: {
+ ...state.blocks[id],
+ position,
+ },
+ },
+ }))
+ },
+
+ updateBlockInput: (blockId: string, inputId: string, value: any) => {
+ set((state) => ({
+ blocks: {
+ ...state.blocks,
+ [blockId]: {
+ ...state.blocks[blockId],
+ inputs: {
+ ...state.blocks[blockId].inputs,
+ [inputId]: {
+ ...state.blocks[blockId].inputs[inputId],
+ value,
+ },
+ },
+ },
+ },
+ }))
+ },
+
+ removeBlock: (id: string) => {
+ set((state) => {
+ const { [id]: _, ...remainingBlocks } = state.blocks
+ const remainingEdges = state.edges.filter(
+ (edge) => edge.source !== id && edge.target !== id
+ )
+ return {
+ blocks: remainingBlocks,
+ edges: remainingEdges,
+ selectedBlockId: state.selectedBlockId === id ? null : state.selectedBlockId,
+ }
+ })
+ },
+
+ addEdge: (edge: Edge) => {
+ set((state) => ({
+ edges: [...state.edges, edge],
+ }))
+ },
+
+ removeEdge: (edgeId: string) => {
+ set((state) => ({
+ edges: state.edges.filter((edge) => edge.id !== edgeId),
+ }))
+ },
+
+ setSelectedBlock: (id: string | null) => {
+ set({ selectedBlockId: id })
+ },
+
+ clear: () => {
+ set(initialState)
+ },
+ }),
+ { name: 'workflow-store' }
+ )
+)
\ No newline at end of file