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