diff --git a/app/w/[id]/workflow.tsx b/app/w/[id]/workflow.tsx index 330eca844b..4642fe74bd 100644 --- a/app/w/[id]/workflow.tsx +++ b/app/w/[id]/workflow.tsx @@ -1,6 +1,6 @@ 'use client' -import { useCallback, useEffect } from 'react' +import { useCallback, useEffect, useState } from 'react' import ReactFlow, { Background, NodeProps, @@ -23,6 +23,9 @@ import { WorkflowBlock } from '../components/workflow-block/workflow-block' import { BlockConfig } from '../../../blocks/types/block' import { useWorkflowStore } from '@/stores/workflow/workflow-store' import { initializeStateLogger } from '@/stores/workflow/state-logger' +import { Serializer } from '@/serializer' +import { Executor } from '@/executor' +import { BlockState, SubBlockState } from '@/stores/workflow/types' /** * Represents the data structure for a workflow node @@ -99,6 +102,8 @@ function WorkflowCanvas() { removeEdge, setSelectedBlock, } = useWorkflowStore() + const [isExecuting, setIsExecuting] = useState(false) + const [executionResult, setExecutionResult] = useState(null) // Convert blocks to ReactFlow nodes const nodes = Object.values(blocks).map((block) => ({ @@ -226,9 +231,193 @@ function WorkflowCanvas() { } ` + useEffect(() => { + initializeStateLogger() + }, []) + + /** + * Gets the initial node in the workflow by finding the node with no incoming edges + * @returns {Object} Object containing the initial block and its configuration + * @throws {Error} If no initial block is found or block configuration is invalid + */ + const getInitialNode = () => { + const initialBlockId = Object.values(blocks).find(block => + !edges.some(edge => edge.target === block.id) + )?.id; + + if (!initialBlockId) { + throw new Error('Could not determine the initial block in the workflow'); + } + + const blockConfig = getBlock(blocks[initialBlockId].type); + if (!blockConfig) { + throw new Error(`Block configuration not found for type: ${blocks[initialBlockId].type}`); + } + + return { + block: blocks[initialBlockId], + config: blockConfig + }; + }; + + /** + * Determines the initial input parameters based on the block type + * @param {BlockState} block - The block to get initial input for + * @param {BlockConfig} blockConfig - The block's configuration + * @returns {Object} The initial input parameters for the block + */ + const getInitialInput = (block: BlockState, blockConfig: BlockConfig) => { + if (block.type === 'agent') { + return { + model: block.subBlocks?.['model']?.value || 'gpt-4o', + systemPrompt: block.subBlocks?.['systemPrompt']?.value, + temperature: block.subBlocks?.['temperature']?.value, + apiKey: block.subBlocks?.['apiKey']?.value, + prompt: block.subBlocks?.['systemPrompt']?.value + }; + } else if (block.type === 'api') { + return { + url: block.subBlocks?.['url']?.value, + method: block.subBlocks?.['method']?.value, + headers: block.subBlocks?.['headers']?.value, + body: block.subBlocks?.['body']?.value + }; + } + return {}; + }; + + /** + * Serializes a block into the format expected by the executor + * @param {BlockState} block - The block to serialize + * @returns {Object} The serialized block with its configuration and parameters + * @throws {Error} If block configuration or tools are not properly defined + */ + const serializeBlock = (block: BlockState) => { + const blockConfig = getBlock(block.type); + if (!blockConfig) { + throw new Error(`Block configuration not found for type: ${block.type}`); + } + + const tools = blockConfig.workflow.tools; + if (!tools || !tools.access || tools.access.length === 0) { + throw new Error(`No tools specified for block type: ${block.type}`); + } + + // Get the values from subBlocks + const params: Record = {}; + Object.entries(block.subBlocks || {}).forEach(([id, subBlock]) => { + if (subBlock) { + params[id] = subBlock.value; + } + }); + + return { + id: block.id, + type: 'custom', + position: block.position, + data: { + tool: tools.access[0], + params, + interface: { + inputs: block.type === 'agent' ? { prompt: 'string' } : {}, + outputs: { + [block.type === 'agent' ? 'response' : 'output']: + typeof blockConfig.workflow.outputType === 'string' + ? blockConfig.workflow.outputType + : blockConfig.workflow.outputType.default + } + } + }, + }; + }; + + /** + * Handles the execution of the workflow + * Serializes the workflow, executes it, and handles the results + */ + const handleRunWorkflow = async () => { + try { + setIsExecuting(true) + setExecutionResult(null) + + // 1. Get initial node + const { block: initialBlock, config: initialBlockConfig } = getInitialNode(); + + // 2. Serialize the workflow + const serializer = new Serializer() + const serializedWorkflow = serializer.serializeWorkflow( + Object.values(blocks).map(serializeBlock), + edges + ); + + // 3. Create executor and run workflow + const executor = new Executor(serializedWorkflow) + const initialInput = getInitialInput(initialBlock, initialBlockConfig); + + const result = await executor.execute( + window.location.pathname.split('/').pop() || 'workflow', + initialInput + ) + + // 4. Handle result + setExecutionResult(result) + + if (result.success) { + console.log('Workflow executed successfully:', result.data) + } else { + console.error('Workflow execution failed:', result.error) + } + } catch (error) { + console.error('Error executing workflow:', error) + setExecutionResult({ + success: false, + error: error instanceof Error ? error.message : 'Unknown error occurred' + }) + } finally { + setIsExecuting(false) + } + }; + return ( -
+
+ + {/* Run Button */} +
+ +
+ + {/* Execution Result */} + {executionResult && ( +
+ {executionResult.success ? ( +
+

Success

+
+                {JSON.stringify(executionResult.data, null, 2)}
+              
+
+ ) : ( +
+

Error

+

{executionResult.error}

+
+ )} +
+ )} + + } } } \ No newline at end of file