mirror of
https://github.com/simstudioai/sim.git
synced 2026-01-10 23:48:09 -05:00
Implemented state with logger
This commit is contained in:
@@ -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 (
|
||||
<div className="w-full h-[calc(100vh-56px)]">
|
||||
<style>{keyframeStyles}</style>
|
||||
|
||||
11
stores/workflow/state-logger.ts
Normal file
11
stores/workflow/state-logger.ts
Normal file
@@ -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,
|
||||
})
|
||||
})
|
||||
}
|
||||
46
stores/workflow/types.ts
Normal file
46
stores/workflow/types.ts
Normal file
@@ -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<string, BlockInput>
|
||||
outputType: OutputType
|
||||
}
|
||||
|
||||
export interface BlockInput {
|
||||
id: string
|
||||
type: SubBlockType
|
||||
value: string | number | string[][] | null
|
||||
}
|
||||
|
||||
export interface WorkflowState {
|
||||
blocks: Record<string, BlockState>
|
||||
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
|
||||
117
stores/workflow/workflow-store.ts
Normal file
117
stores/workflow/workflow-store.ts
Normal file
@@ -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<WorkflowStore>()(
|
||||
devtools(
|
||||
(set, get) => ({
|
||||
...initialState,
|
||||
|
||||
addBlock: (id: string, type: BlockType, name: string, position: Position) => {
|
||||
const blockConfig = getBlock(type)
|
||||
if (!blockConfig) return
|
||||
|
||||
const inputs: Record<string, any> = {}
|
||||
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' }
|
||||
)
|
||||
)
|
||||
Reference in New Issue
Block a user