diff --git a/app/w/[id]/workflow.tsx b/app/w/[id]/workflow.tsx
index a80ca6e6e6..72cc1d48da 100644
--- a/app/w/[id]/workflow.tsx
+++ b/app/w/[id]/workflow.tsx
@@ -236,33 +236,6 @@ function WorkflowCanvas() {
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
@@ -339,27 +312,20 @@ function WorkflowCanvas() {
setIsExecuting(true)
setExecutionResult(null)
- // 1. Get initial node
- const { block: initialBlock, config: initialBlockConfig } =
- getInitialNode()
-
- // 2. Serialize the workflow
+ // 1. Serialize the workflow
const serializer = new Serializer()
const serializedWorkflow = serializer.serializeWorkflow(
- Object.values(blocks).map(serializeBlock),
+ blocks,
edges
)
- // 3. Create executor and run workflow
+ // 2. 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
+ window.location.pathname.split('/').pop() || 'workflow'
)
- // 4. Handle result
+ // 3. Handle result
setExecutionResult(result)
if (result.success) {
@@ -403,6 +369,13 @@ function WorkflowCanvas() {
+ {/* */}
mockError || 'Mock error'
});
+jest.mock('@/tools', () => ({
+ tools: {}
+}));
+
describe('Executor', () => {
beforeEach(() => {
// Reset tools mock
@@ -82,7 +86,7 @@ describe('Executor', () => {
);
const executor = new Executor(workflow);
- const result = await executor.execute('workflow-1', { input: 'test' });
+ const result = await executor.execute('workflow-1');
expect(result.success).toBe(true);
expect(result.data).toEqual({ result: 'test processed' });
@@ -125,7 +129,7 @@ describe('Executor', () => {
};
const executor = new Executor(workflow);
- const result = await executor.execute('workflow-1', {});
+ const result = await executor.execute('workflow-1');
expect(result.success).toBe(false);
expect(result.error).toContain('Missing required parameter');
@@ -166,7 +170,7 @@ describe('Executor', () => {
);
const executor = new Executor(workflow);
- const result = await executor.execute('workflow-1', { input: 'test' });
+ const result = await executor.execute('workflow-1');
expect(result.success).toBe(false);
expect(result.error).toContain('API Error');
@@ -200,7 +204,7 @@ describe('Executor', () => {
};
const executor = new Executor(workflow);
- const result = await executor.execute('workflow-1', { input: 42 });
+ const result = await executor.execute('workflow-1');
expect(result.success).toBe(false);
expect(result.error).toContain('Invalid type for input');
@@ -240,7 +244,7 @@ describe('Executor', () => {
);
const executor = new Executor(workflow);
- const result = await executor.execute('workflow-1', { input: 'test' });
+ const result = await executor.execute('workflow-1');
expect(result.success).toBe(false);
expect(result.error).toContain('Tool output missing required field');
@@ -248,40 +252,40 @@ describe('Executor', () => {
});
describe('Complex Workflows', () => {
- it('should execute a workflow with multiple connected blocks', async () => {
- const processorTool = createMockTool(
- 'processor',
- 'Processor Tool',
- { processed: 'TEST' }
+ it('should execute blocks in correct order and pass data between them', async () => {
+ const mockTool1 = createMockTool(
+ 'tool-1',
+ 'Tool 1',
+ { output: 'test data' }
);
- const formatterTool = createMockTool(
- 'formatter',
- 'Formatter Tool',
- { result: '' }
+ const mockTool2 = createMockTool(
+ 'tool-2',
+ 'Tool 2',
+ { result: 'processed data' }
);
- (tools as any)['processor'] = processorTool;
- (tools as any)['formatter'] = formatterTool;
+ (tools as any)['tool-1'] = mockTool1;
+ (tools as any)['tool-2'] = mockTool2;
const workflow: SerializedWorkflow = {
version: '1.0',
blocks: [
{
- id: 'process',
+ id: 'block-1',
position: { x: 0, y: 0 },
config: {
- tool: 'processor',
- params: { input: 'test' },
+ tool: 'tool-1',
+ params: { input: 'initial' },
interface: {
- inputs: { input: 'string' },
- outputs: { processed: 'string' }
+ inputs: {},
+ outputs: { output: 'string' }
}
}
},
{
- id: 'format',
- position: { x: 100, y: 0 },
+ id: 'block-2',
+ position: { x: 200, y: 0 },
config: {
- tool: 'formatter',
+ tool: 'tool-2',
params: {},
interface: {
inputs: { input: 'string' },
@@ -290,31 +294,89 @@ describe('Executor', () => {
}
}
],
- connections: [{
- source: 'process',
- target: 'format',
- sourceHandle: 'processed',
- targetHandle: 'input'
- }]
+ connections: [
+ {
+ source: 'block-1',
+ target: 'block-2',
+ sourceHandle: 'output',
+ targetHandle: 'input'
+ }
+ ]
};
// Mock fetch for both tools
global.fetch = jest.fn()
- .mockImplementationOnce(() => Promise.resolve({
- ok: true,
- json: () => Promise.resolve({ processed: 'TEST' })
- }))
- .mockImplementationOnce(() => Promise.resolve({
- ok: true,
- json: () => Promise.resolve({ result: '' })
- }));
+ .mockImplementationOnce(() =>
+ Promise.resolve({
+ ok: true,
+ json: () => Promise.resolve({ output: 'test data' })
+ })
+ )
+ .mockImplementationOnce(() =>
+ Promise.resolve({
+ ok: true,
+ json: () => Promise.resolve({ result: 'processed data' })
+ })
+ );
const executor = new Executor(workflow);
- const result = await executor.execute('workflow-1', { input: 'test' });
+ const result = await executor.execute('workflow-1');
expect(result.success).toBe(true);
- expect(result.data).toEqual({ result: '' });
+ expect(result.data).toEqual({ result: 'processed data' });
expect(global.fetch).toHaveBeenCalledTimes(2);
});
+
+ it('should handle cycles in workflow', async () => {
+ const workflow: SerializedWorkflow = {
+ version: '1.0',
+ blocks: [
+ {
+ id: 'block-1',
+ position: { x: 0, y: 0 },
+ config: {
+ tool: 'test-tool',
+ params: {},
+ interface: {
+ inputs: {},
+ outputs: {}
+ }
+ }
+ },
+ {
+ id: 'block-2',
+ position: { x: 200, y: 0 },
+ config: {
+ tool: 'test-tool',
+ params: {},
+ interface: {
+ inputs: {},
+ outputs: {}
+ }
+ }
+ }
+ ],
+ connections: [
+ {
+ source: 'block-1',
+ target: 'block-2',
+ sourceHandle: 'output',
+ targetHandle: 'input'
+ },
+ {
+ source: 'block-2',
+ target: 'block-1',
+ sourceHandle: 'output',
+ targetHandle: 'input'
+ }
+ ]
+ };
+
+ const executor = new Executor(workflow);
+ const result = await executor.execute('workflow-1');
+
+ expect(result.success).toBe(false);
+ expect(result.error).toContain('Workflow contains cycles');
+ });
});
});
diff --git a/executor/index.ts b/executor/index.ts
index 97b695a982..3aa428f306 100644
--- a/executor/index.ts
+++ b/executor/index.ts
@@ -1,6 +1,7 @@
-import { SerializedWorkflow, SerializedBlock, BlockConfig } from '@/serializer/types';
+import { SerializedWorkflow, SerializedBlock } from '@/serializer/types';
import { ExecutionContext, ExecutionResult, Tool } from './types';
import { tools } from '@/tools';
+import { BlockState } from '@/stores/workflow/types';
export class Executor {
private workflow: SerializedWorkflow;
@@ -14,7 +15,7 @@ export class Executor {
inputs: Record,
context: ExecutionContext
): Promise> {
- const config = block.config as BlockConfig;
+ const config = block.config;
const toolId = config.tool;
if (!toolId) {
@@ -113,15 +114,17 @@ export class Executor {
return typeof value === 'number';
case 'boolean':
return typeof value === 'boolean';
- case 'object':
- return typeof value === 'object' && value !== null;
- case 'array':
- return Array.isArray(value);
- case 'function':
- return typeof value === 'function';
+ case 'json':
+ try {
+ if (typeof value === 'string') {
+ JSON.parse(value);
+ }
+ return true;
+ } catch {
+ return false;
+ }
default:
- // For complex types like 'Record', 'string[]', etc.
- // We just do basic object/array validation
+ // For complex types, we just do basic object/array validation
return true;
}
}
@@ -187,20 +190,22 @@ export class Executor {
}
});
- // If this is a start block, pass through workflow inputs
- if (Object.keys(inputs).length === 0 && context.input) {
- return context.input;
+ // If this is a start block with no inputs, use the block's params
+ if (Object.keys(inputs).length === 0) {
+ const targetBlock = this.workflow.blocks.find(b => b.id === block.id);
+ if (targetBlock) {
+ return targetBlock.config.params;
+ }
}
return inputs;
}
- async execute(workflowId: string, input: Record): Promise {
+ async execute(workflowId: string): Promise {
const startTime = new Date();
const context: ExecutionContext = {
workflowId,
blockStates: new Map(),
- input,
metadata: {
startTime: startTime.toISOString()
}
diff --git a/executor/types.ts b/executor/types.ts
index e835ab80f8..34c23b5b0b 100644
--- a/executor/types.ts
+++ b/executor/types.ts
@@ -28,7 +28,7 @@ export interface ToolRegistry {
export interface ExecutionContext {
workflowId: string;
blockStates: Map;
- input: Record;
+ input?: Record;
metadata?: Record;
}
diff --git a/serializer/__tests__/serializer.test.ts b/serializer/__tests__/serializer.test.ts
index 6af98ebfa9..a90a1e4b25 100644
--- a/serializer/__tests__/serializer.test.ts
+++ b/serializer/__tests__/serializer.test.ts
@@ -1,6 +1,47 @@
-import { Node, Edge } from 'reactflow';
+import { Edge } from 'reactflow';
import { Serializer } from '../index';
import { SerializedWorkflow } from '../types';
+import { BlockState } from '@/stores/workflow/types';
+import { OutputType } from '@/blocks/types';
+
+jest.mock('@/blocks', () => ({
+ getBlock: (type: string) => {
+ if (type === 'http') {
+ return {
+ type,
+ workflow: {
+ tools: {
+ access: ['http.request'],
+ config: {
+ tool: () => 'http.request'
+ }
+ },
+ inputs: {
+ url: 'string',
+ method: 'string'
+ },
+ outputType: 'json'
+ }
+ };
+ }
+ // Default agent block config
+ return {
+ type,
+ workflow: {
+ tools: {
+ access: ['openai.chat'],
+ config: {
+ tool: () => 'openai.chat'
+ }
+ },
+ inputs: {
+ prompt: 'string'
+ },
+ outputType: 'string'
+ }
+ };
+ }
+}));
describe('Serializer', () => {
let serializer: Serializer;
@@ -11,61 +52,51 @@ describe('Serializer', () => {
describe('serializeWorkflow', () => {
it('should serialize a workflow with agent and http blocks', () => {
- const blocks: Node[] = [
- {
+ const blocks: Record = {
+ 'agent-1': {
id: 'agent-1',
- type: 'custom',
+ type: 'agent',
+ name: 'GPT-4o Agent',
position: { x: 100, y: 100 },
- data: {
- tool: 'openai.chat',
- params: {
- model: 'gpt-4o',
- systemPrompt: 'You are helpful',
- temperature: 0.7
+ subBlocks: {
+ 'model': {
+ id: 'model',
+ type: 'dropdown',
+ value: 'gpt-4o'
},
- interface: {
- inputs: {
- prompt: 'string'
- },
- outputs: {
- response: 'string',
- tokens: 'number'
- }
+ 'systemPrompt': {
+ id: 'systemPrompt',
+ type: 'long-input',
+ value: 'You are helpful'
},
- title: 'GPT-4o Agent',
- description: 'Language model block',
- category: 'AI',
- icon: 'brain',
- color: '#7F2FFF'
- }
+ 'temperature': {
+ id: 'temperature',
+ type: 'slider',
+ value: 0.7
+ }
+ },
+ outputType: 'string'
},
- {
+ 'http-1': {
id: 'http-1',
- type: 'custom',
+ type: 'http',
+ name: 'API Call',
position: { x: 400, y: 100 },
- data: {
- tool: 'http.request',
- params: {
- url: 'https://api.example.com',
- method: 'GET'
+ subBlocks: {
+ 'url': {
+ id: 'url',
+ type: 'short-input',
+ value: 'https://api.example.com'
},
- interface: {
- inputs: {
- body: 'object'
- },
- outputs: {
- data: 'object',
- status: 'number'
- }
- },
- title: 'API Call',
- description: 'HTTP request block',
- category: 'Web',
- icon: 'globe',
- color: '#00FF00'
- }
+ 'method': {
+ id: 'method',
+ type: 'dropdown',
+ value: 'GET'
+ }
+ },
+ outputType: 'json'
}
- ];
+ };
const connections: Edge[] = [
{
@@ -93,13 +124,6 @@ describe('Serializer', () => {
systemPrompt: 'You are helpful',
temperature: 0.7
});
- expect(agentBlock?.metadata).toEqual({
- title: 'GPT-4o Agent',
- description: 'Language model block',
- category: 'AI',
- icon: 'brain',
- color: '#7F2FFF'
- });
// Test http block serialization
const httpBlock = serialized.blocks.find(b => b.id === 'http-1');
@@ -112,21 +136,22 @@ describe('Serializer', () => {
});
it('should handle blocks with minimal required configuration', () => {
- const blocks: Node[] = [{
- id: 'minimal-1',
- type: 'custom',
- position: { x: 0, y: 0 },
- data: {
- tool: 'openai.chat',
- params: {
- model: 'gpt-4o'
+ const blocks: Record = {
+ 'minimal-1': {
+ id: 'minimal-1',
+ type: 'agent',
+ name: 'Minimal Agent',
+ position: { x: 0, y: 0 },
+ subBlocks: {
+ 'model': {
+ id: 'model',
+ type: 'dropdown',
+ value: 'gpt-4o'
+ }
},
- interface: {
- inputs: {},
- outputs: {}
- }
+ outputType: 'string'
}
- }];
+ };
const serialized = serializer.serializeWorkflow(blocks, []);
const block = serialized.blocks[0];
@@ -134,71 +159,68 @@ describe('Serializer', () => {
expect(block.id).toBe('minimal-1');
expect(block.config.tool).toBe('openai.chat');
expect(block.config.params).toEqual({ model: 'gpt-4o' });
- expect(block.metadata).toBeUndefined();
});
it('should handle complex workflow with multiple interconnected blocks', () => {
- const blocks: Node[] = [
- {
+ const blocks: Record = {
+ 'input-1': {
id: 'input-1',
- type: 'custom',
+ type: 'http',
+ name: 'Data Input',
position: { x: 100, y: 100 },
- data: {
- tool: 'http.request',
- params: {
- url: 'https://api.data.com',
- method: 'GET'
+ subBlocks: {
+ 'url': {
+ id: 'url',
+ type: 'short-input',
+ value: 'https://api.data.com'
},
- interface: {
- inputs: {},
- outputs: {
- data: 'object'
- }
+ 'method': {
+ id: 'method',
+ type: 'dropdown',
+ value: 'GET'
}
- }
+ },
+ outputType: 'json'
},
- {
+ 'process-1': {
id: 'process-1',
- type: 'custom',
+ type: 'agent',
+ name: 'Data Processor',
position: { x: 300, y: 100 },
- data: {
- tool: 'openai.chat',
- params: {
- model: 'gpt-4o',
- systemPrompt: 'Process this data'
+ subBlocks: {
+ 'model': {
+ id: 'model',
+ type: 'dropdown',
+ value: 'gpt-4o'
},
- interface: {
- inputs: {
- data: 'object',
- config: 'object'
- },
- outputs: {
- result: 'string'
- }
+ 'systemPrompt': {
+ id: 'systemPrompt',
+ type: 'long-input',
+ value: 'Process this data'
}
- }
+ },
+ outputType: 'string'
},
- {
+ 'output-1': {
id: 'output-1',
- type: 'custom',
+ type: 'http',
+ name: 'Data Output',
position: { x: 500, y: 100 },
- data: {
- tool: 'http.request',
- params: {
- url: 'https://api.output.com',
- method: 'POST'
+ subBlocks: {
+ 'url': {
+ id: 'url',
+ type: 'short-input',
+ value: 'https://api.output.com'
},
- interface: {
- inputs: {
- body: 'string'
- },
- outputs: {
- status: 'number'
- }
+ 'method': {
+ id: 'method',
+ type: 'dropdown',
+ value: 'POST'
}
- }
+ },
+ outputType: 'json'
}
- ];
+ };
const connections: Edge[] = [
{
@@ -230,57 +252,56 @@ describe('Serializer', () => {
expect(conn1.target).toBe('process-1');
expect(conn2.source).toBe('process-1');
expect(conn2.target).toBe('output-1');
-
- // Verify interface matching
- const process = serialized.blocks.find(b => b.id === 'process-1');
- expect(process?.config.interface.inputs).toHaveProperty('data');
- expect(process?.config.interface.outputs).toHaveProperty('result');
});
it('should preserve tool-specific parameters', () => {
- const blocks: Node[] = [{
- id: 'agent-1',
- type: 'custom',
- position: { x: 0, y: 0 },
- data: {
- tool: 'openai.chat',
- params: {
- model: 'gpt-4o',
- temperature: 0.7,
- maxTokens: 1000,
- topP: 0.9,
- frequencyPenalty: 0.1,
- presencePenalty: 0.1
+ const blocks: Record = {
+ 'agent-1': {
+ id: 'agent-1',
+ type: 'agent',
+ name: 'Advanced Agent',
+ position: { x: 0, y: 0 },
+ subBlocks: {
+ 'model': {
+ id: 'model',
+ type: 'dropdown',
+ value: 'gpt-4o'
+ },
+ 'temperature': {
+ id: 'temperature',
+ type: 'slider',
+ value: 0.7
+ },
+ 'maxTokens': {
+ id: 'maxTokens',
+ type: 'slider',
+ value: 1000
+ }
},
- interface: {
- inputs: { prompt: 'string' },
- outputs: { response: 'string' }
- }
+ outputType: 'string'
}
- }];
+ };
const serialized = serializer.serializeWorkflow(blocks, []);
const block = serialized.blocks[0];
+ expect(block.config.tool).toBe('openai.chat');
expect(block.config.params).toEqual({
model: 'gpt-4o',
temperature: 0.7,
- maxTokens: 1000,
- topP: 0.9,
- frequencyPenalty: 0.1,
- presencePenalty: 0.1
+ maxTokens: 1000
});
});
});
describe('deserializeWorkflow', () => {
- it('should deserialize a workflow back to ReactFlow format', () => {
+ it('should deserialize a workflow back to blocks and connections', () => {
const workflow: SerializedWorkflow = {
version: '1.0',
blocks: [
{
id: 'agent-1',
- position: { x: 100, y: 100 },
+ position: { x: 0, y: 0 },
config: {
tool: 'openai.chat',
params: {
@@ -289,27 +310,21 @@ describe('Serializer', () => {
},
interface: {
inputs: { prompt: 'string' },
- outputs: { response: 'string' }
+ outputs: { output: 'string' }
}
- },
- metadata: {
- title: 'GPT-4o Agent',
- category: 'AI'
}
}
],
connections: []
};
- const { blocks, connections } = serializer.deserializeWorkflow(workflow);
+ const { blocks } = serializer.deserializeWorkflow(workflow);
+ const block = blocks['agent-1'];
- expect(blocks).toHaveLength(1);
- const block = blocks[0];
- expect(block.id).toBe('agent-1');
- expect(block.type).toBe('custom');
- expect(block.data.tool).toBe('openai.chat');
- expect(block.data.params.model).toBe('gpt-4o');
- expect(block.data.title).toBe('GPT-4o Agent');
+ expect(block.type).toBe('openai.chat');
+ expect(block.subBlocks.model.value).toBe('gpt-4o');
+ expect(block.subBlocks.systemPrompt.value).toBe('You are helpful');
+ expect(block.outputType).toBe('string');
});
});
});
\ No newline at end of file
diff --git a/serializer/index.ts b/serializer/index.ts
index 37cdb82f9e..3b512c3eec 100644
--- a/serializer/index.ts
+++ b/serializer/index.ts
@@ -1,11 +1,14 @@
-import { Node, Edge } from "reactflow";
-import { SerializedBlock, SerializedConnection, SerializedWorkflow } from "./types";
+import { BlockState, SubBlockState } from '@/stores/workflow/types';
+import { Edge } from 'reactflow';
+import { SerializedBlock, SerializedConnection, SerializedWorkflow } from './types';
+import { getBlock } from '@/blocks';
+import { OutputType, SubBlockType } from '@/blocks/types';
export class Serializer {
- serializeWorkflow(blocks: Node[], connections: Edge[]): SerializedWorkflow {
+ serializeWorkflow(blocks: Record, connections: Edge[]): SerializedWorkflow {
return {
version: '1.0',
- blocks: blocks.map(block => this.serializeBlock(block)),
+ blocks: Object.values(blocks).map(block => this.serializeBlock(block)),
connections: connections.map(conn => ({
source: conn.source,
target: conn.target,
@@ -15,45 +18,60 @@ export class Serializer {
};
}
- private serializeBlock(block: Node): SerializedBlock {
- const { data } = block;
- const serialized: SerializedBlock = {
+ private serializeBlock(block: BlockState): SerializedBlock {
+ const blockConfig = getBlock(block.type);
+ if (!blockConfig) {
+ throw new Error(`Block configuration not found for type: ${block.type}`);
+ }
+
+ // Get the tool ID from the block's configuration
+ const tools = blockConfig.workflow.tools;
+ if (!tools?.access || tools.access.length === 0) {
+ throw new Error(`No tools specified for block type: ${block.type}`);
+ }
+
+ // Get all values from subBlocks
+ const params: Record = {};
+ Object.entries(block.subBlocks || {}).forEach(([id, subBlock]) => {
+ if (subBlock?.value !== undefined) {
+ params[id] = subBlock.value;
+ }
+ });
+
+ // Get the tool ID from the block's configuration
+ const toolId = tools.config?.tool?.(params) || params.tool || tools.access[0];
+ if (!toolId || !tools.access.includes(toolId)) {
+ throw new Error(`Invalid or unauthorized tool: ${toolId}`);
+ }
+
+ return {
id: block.id,
- position: {
- x: block.position.x,
- y: block.position.y
- },
+ position: block.position,
config: {
- tool: data.tool,
- params: data.params || {},
+ tool: toolId,
+ params: params,
interface: {
- inputs: data.interface?.inputs || {},
- outputs: data.interface?.outputs || {}
+ inputs: blockConfig.workflow.inputs || {},
+ outputs: {
+ output: block.outputType
+ }
}
}
};
-
- const metadata = {
- title: data.title,
- description: data.description,
- category: data.category,
- icon: data.icon,
- color: data.color
- };
-
- if (Object.values(metadata).some(value => value !== undefined)) {
- serialized.metadata = metadata;
- }
-
- return serialized;
}
deserializeWorkflow(serialized: SerializedWorkflow): {
- blocks: Node[];
+ blocks: Record;
connections: Edge[];
} {
+ const blocks: Record = {};
+ serialized.blocks.forEach(block => {
+ const deserialized = this.deserializeBlock(block);
+ blocks[deserialized.id] = deserialized;
+ });
+
return {
- blocks: serialized.blocks.map(block => this.deserializeBlock(block)),
+ blocks,
connections: serialized.connections.map(conn => ({
id: `${conn.source}-${conn.target}`,
source: conn.source,
@@ -64,17 +82,31 @@ export class Serializer {
};
}
- private deserializeBlock(serialized: SerializedBlock): Node {
+ private deserializeBlock(serialized: SerializedBlock): BlockState {
return {
id: serialized.id,
- type: 'custom',
+ type: serialized.config.tool,
+ name: `${serialized.config.tool} Block`,
position: serialized.position,
- data: {
- tool: serialized.config.tool,
- params: serialized.config.params,
- interface: serialized.config.interface,
- ...(serialized.metadata || {})
- }
+ subBlocks: Object.entries(serialized.config.params).reduce((acc, [key, value]) => {
+ acc[key] = {
+ id: key,
+ type: this.inferSubBlockType(value),
+ value: value
+ };
+ return acc;
+ }, {} as Record),
+ outputType: serialized.config.interface.outputs.output as OutputType
};
}
+
+ private inferSubBlockType(value: any): SubBlockType {
+ if (Array.isArray(value) && Array.isArray(value[0])) {
+ return 'table';
+ }
+ if (typeof value === 'string' && value.length > 100) {
+ return 'long-input';
+ }
+ return 'short-input';
+ }
}
\ No newline at end of file