mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-24 15:45:35 +08:00
Added available tools to block configs, added ability to run workflow
This commit is contained in:
+191
-2
@@ -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}
|
||||
|
||||
@@ -21,6 +21,9 @@ export const AgentBlock: BlockConfig = {
|
||||
}
|
||||
}
|
||||
},
|
||||
tools: {
|
||||
access: ['model']
|
||||
},
|
||||
subBlocks: [
|
||||
{
|
||||
id: 'systemPrompt',
|
||||
|
||||
@@ -12,6 +12,9 @@ export const ApiBlock: BlockConfig = {
|
||||
},
|
||||
workflow: {
|
||||
outputType: 'json',
|
||||
tools: {
|
||||
access: ['http']
|
||||
},
|
||||
subBlocks: [
|
||||
{
|
||||
id: 'url',
|
||||
|
||||
@@ -12,6 +12,9 @@ export const FunctionBlock: BlockConfig = {
|
||||
},
|
||||
workflow: {
|
||||
outputType: 'json',
|
||||
tools: {
|
||||
access: ['function']
|
||||
},
|
||||
subBlocks: [
|
||||
{
|
||||
id: 'code',
|
||||
|
||||
@@ -44,5 +44,9 @@ export interface BlockConfig {
|
||||
workflow: {
|
||||
outputType: OutputTypeConfig
|
||||
subBlocks: SubBlockConfig[]
|
||||
tools?: {
|
||||
access: string[]
|
||||
config?: Record<string, any>
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user