Added available tools to block configs, added ability to run workflow

This commit is contained in:
Waleed Latif
2025-01-16 23:42:13 -08:00
parent 87b8c60684
commit e6965cfc9d
5 changed files with 204 additions and 2 deletions

View File

@@ -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<any>(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<string, any> = {};
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 (
<div className="w-full h-[calc(100vh-56px)]">
<div className="relative w-full h-[calc(100vh-56px)]">
<style>{keyframeStyles}</style>
{/* Run Button */}
<div className="absolute top-4 right-4 z-10">
<button
onClick={handleRunWorkflow}
disabled={isExecuting || Object.keys(blocks).length === 0}
className={`px-4 py-2 rounded-md text-white ${
isExecuting
? 'bg-gray-400 cursor-not-allowed'
: 'bg-blue-500 hover:bg-blue-600'
}`}
>
{isExecuting ? 'Running...' : 'Run Workflow'}
</button>
</div>
{/* Execution Result */}
{executionResult && (
<div className={`absolute top-16 right-4 z-10 p-4 rounded-md ${
executionResult.success ? 'bg-green-100' : 'bg-red-100'
}`}>
{executionResult.success ? (
<div>
<h3 className="font-bold text-green-800">Success</h3>
<pre className="mt-2 text-sm">
{JSON.stringify(executionResult.data, null, 2)}
</pre>
</div>
) : (
<div>
<h3 className="font-bold text-red-800">Error</h3>
<p className="mt-2 text-sm text-red-600">{executionResult.error}</p>
</div>
)}
</div>
)}
<ReactFlow
nodes={nodes}
edges={edges}

View File

@@ -21,6 +21,9 @@ export const AgentBlock: BlockConfig = {
}
}
},
tools: {
access: ['model']
},
subBlocks: [
{
id: 'systemPrompt',

View File

@@ -12,6 +12,9 @@ export const ApiBlock: BlockConfig = {
},
workflow: {
outputType: 'json',
tools: {
access: ['http']
},
subBlocks: [
{
id: 'url',

View File

@@ -12,6 +12,9 @@ export const FunctionBlock: BlockConfig = {
},
workflow: {
outputType: 'json',
tools: {
access: ['function']
},
subBlocks: [
{
id: 'code',

View File

@@ -44,5 +44,9 @@ export interface BlockConfig {
workflow: {
outputType: OutputTypeConfig
subBlocks: SubBlockConfig[]
tools?: {
access: string[]
config?: Record<string, any>
}
}
}