diff --git a/blocks/blocks/agent.ts b/blocks/blocks/agent.ts index 6c747ce8a6..ef6cc42ed3 100644 --- a/blocks/blocks/agent.ts +++ b/blocks/blocks/agent.ts @@ -19,6 +19,26 @@ export const AgentBlock: BlockConfig = { icon: AgentIcon, category: 'basic', }, + tools: { + access: ['openai.chat', 'anthropic.chat', 'google.chat', 'xai.chat'], + config: { + tool: (params: Record) => { + const model = params.model || 'gpt-4o'; + + if (!model) { + throw new Error('No model selected'); + } + + const tool = MODEL_TOOLS[model as keyof typeof MODEL_TOOLS]; + + if (!tool) { + throw new Error(`Invalid model selected: ${model}`); + } + + return tool; + } + } + }, workflow: { outputType: { default: 'string', @@ -30,26 +50,6 @@ export const AgentBlock: BlockConfig = { } } }, - tools: { - access: ['openai.chat', 'anthropic.chat', 'google.chat', 'xai.chat'], - config: { - tool: (params: Record) => { - const model = params.model || 'gpt-4o'; - - if (!model) { - throw new Error('No model selected'); - } - - const tool = MODEL_TOOLS[model as keyof typeof MODEL_TOOLS]; - - if (!tool) { - throw new Error(`Invalid model selected: ${model}`); - } - - return tool; - } - } - }, inputs: { systemPrompt: 'string' }, diff --git a/blocks/blocks/api.ts b/blocks/blocks/api.ts index 0c95b27eca..cd780746a2 100644 --- a/blocks/blocks/api.ts +++ b/blocks/blocks/api.ts @@ -10,11 +10,11 @@ export const ApiBlock: BlockConfig = { icon: ApiIcon, category: 'basic', }, + tools: { + access: ['http.request'] + }, workflow: { outputType: 'json', - tools: { - access: ['http.request'] - }, inputs: { url: 'string', method: 'string', diff --git a/blocks/blocks/function.ts b/blocks/blocks/function.ts index b648d13e27..2a628ca070 100644 --- a/blocks/blocks/function.ts +++ b/blocks/blocks/function.ts @@ -10,11 +10,11 @@ export const FunctionBlock: BlockConfig = { icon: CodeIcon, category: 'advanced', }, + tools: { + access: ['function'] + }, workflow: { outputType: 'json', - tools: { - access: ['function'] - }, inputs: { code: 'string' }, diff --git a/blocks/index.ts b/blocks/index.ts index b26c51b2f8..d84cf7fcf6 100644 --- a/blocks/index.ts +++ b/blocks/index.ts @@ -8,22 +8,33 @@ import { FunctionBlock } from './blocks/function' // Export blocks for ease of use export { AgentBlock, ApiBlock, FunctionBlock } -// Combined blocks registry -export const BLOCKS: BlockConfig[] = [ - AgentBlock, - ApiBlock, - FunctionBlock, -] +// Registry of all block configurations +const blocks: Record = { + agent: AgentBlock, + api: ApiBlock, + function: FunctionBlock +} + +// Build a reverse mapping of tools to block types +const toolToBlockType = Object.entries(blocks).reduce((acc, [blockType, config]) => { + config.tools.access.forEach(toolId => { + acc[toolId] = blockType + }) + return acc +}, {} as Record) // Helper functions export const getBlock = (type: string): BlockConfig | undefined => - BLOCKS.find(block => block.type === type) + blocks[type] + +export const getBlockTypeForTool = (toolId: string): string | undefined => + toolToBlockType[toolId] export const getBlocksByCategory = (category: 'basic' | 'advanced'): BlockConfig[] => - BLOCKS.filter(block => block.toolbar.category === category) + Object.values(blocks).filter(block => block.toolbar.category === category) export const getAllBlockTypes = (): string[] => - BLOCKS.map(block => block.type) + Object.keys(blocks) export const isValidBlockType = (type: string): type is string => - BLOCKS.some(block => block.type === type) \ No newline at end of file + type in blocks \ No newline at end of file diff --git a/blocks/types.ts b/blocks/types.ts index 7a66880722..89a6595339 100644 --- a/blocks/types.ts +++ b/blocks/types.ts @@ -42,15 +42,15 @@ export interface BlockConfig { icon: BlockIcon category: BlockCategory } + tools: { + access: string[] + config?: { + tool: (params: Record) => string + } + } workflow: { outputType: OutputTypeConfig subBlocks: SubBlockConfig[] - tools: { - access: string[] - config?: { - tool: (params: Record) => string - } - } inputs?: Record } } \ No newline at end of file diff --git a/jest.config.js b/jest.config.js index ac732aec6a..870d85818a 100644 --- a/jest.config.js +++ b/jest.config.js @@ -3,12 +3,14 @@ module.exports = { preset: 'ts-jest', testEnvironment: 'node', moduleNameMapper: { - '^@/(.*)$': '/$1' + '^@/(.*)$': '/$1', + // Mock CSS imports + '\\.(css|less|scss|sass)$': 'identity-obj-proxy', }, testMatch: ['**/__tests__/**/*.test.ts'], transform: { - '^.+\\.tsx?$': ['ts-jest', { - tsconfig: 'tsconfig.json' - }] + '^.+\\.(ts|tsx)$': ['ts-jest', { + tsconfig: 'tsconfig.json', + }], } }; \ No newline at end of file diff --git a/serializer/__tests__/serializer.test.ts b/serializer/__tests__/serializer.test.ts index a90a1e4b25..72ef39242d 100644 --- a/serializer/__tests__/serializer.test.ts +++ b/serializer/__tests__/serializer.test.ts @@ -4,42 +4,71 @@ import { SerializedWorkflow } from '../types'; import { BlockState } from '@/stores/workflow/types'; import { OutputType } from '@/blocks/types'; +// Mock icons +jest.mock('@/components/icons', () => ({ + AgentIcon: () => 'AgentIcon', + ApiIcon: () => 'ApiIcon', + CodeIcon: () => 'CodeIcon', +})); + +// Mock blocks jest.mock('@/blocks', () => ({ getBlock: (type: string) => { - if (type === 'http') { + if (type === 'api') { return { type, + toolbar: { + title: 'API', + description: 'Use any API', + bgColor: '#2F55FF', + icon: () => null, + category: 'basic', + }, + tools: { + access: ['http.request'] + }, workflow: { - tools: { - access: ['http.request'], - config: { - tool: () => 'http.request' - } - }, + outputType: 'json', inputs: { url: 'string', method: 'string' }, - outputType: 'json' + subBlocks: [] } }; } // Default agent block config return { type, + toolbar: { + title: 'Agent', + description: 'Use any LLM', + bgColor: '#7F2FFF', + icon: () => null, + category: 'basic', + }, + tools: { + access: ['openai.chat'], + config: { + tool: () => 'openai.chat' + } + }, workflow: { - tools: { - access: ['openai.chat'], - config: { - tool: () => 'openai.chat' - } - }, + outputType: 'string', inputs: { prompt: 'string' }, - outputType: 'string' + subBlocks: [] } }; + }, + getBlockTypeForTool: (toolId: string) => { + const toolToType: Record = { + 'openai.chat': 'agent', + 'http.request': 'api', + 'function': 'function' + }; + return toolToType[toolId]; } })); @@ -79,7 +108,7 @@ describe('Serializer', () => { }, 'http-1': { id: 'http-1', - type: 'http', + type: 'api', name: 'API Call', position: { x: 400, y: 100 }, subBlocks: { @@ -165,7 +194,7 @@ describe('Serializer', () => { const blocks: Record = { 'input-1': { id: 'input-1', - type: 'http', + type: 'api', name: 'Data Input', position: { x: 100, y: 100 }, subBlocks: { @@ -203,7 +232,7 @@ describe('Serializer', () => { }, 'output-1': { id: 'output-1', - type: 'http', + type: 'api', name: 'Data Output', position: { x: 500, y: 100 }, subBlocks: { @@ -321,7 +350,7 @@ describe('Serializer', () => { const { blocks } = serializer.deserializeWorkflow(workflow); const block = blocks['agent-1']; - expect(block.type).toBe('openai.chat'); + expect(block.type).toBe('agent'); expect(block.subBlocks.model.value).toBe('gpt-4o'); expect(block.subBlocks.systemPrompt.value).toBe('You are helpful'); expect(block.outputType).toBe('string'); diff --git a/serializer/index.ts b/serializer/index.ts index 3b512c3eec..d5792f3ffd 100644 --- a/serializer/index.ts +++ b/serializer/index.ts @@ -1,7 +1,7 @@ import { BlockState, SubBlockState } from '@/stores/workflow/types'; import { Edge } from 'reactflow'; import { SerializedBlock, SerializedConnection, SerializedWorkflow } from './types'; -import { getBlock } from '@/blocks'; +import { getBlock, getBlockTypeForTool } from '@/blocks'; import { OutputType, SubBlockType } from '@/blocks/types'; export class Serializer { @@ -25,7 +25,7 @@ export class Serializer { } // Get the tool ID from the block's configuration - const tools = blockConfig.workflow.tools; + const tools = blockConfig.tools; if (!tools?.access || tools.access.length === 0) { throw new Error(`No tools specified for block type: ${block.type}`); } @@ -83,15 +83,28 @@ export class Serializer { } private deserializeBlock(serialized: SerializedBlock): BlockState { + const toolId = serialized.config.tool; + const blockType = getBlockTypeForTool(toolId); + + if (!blockType) { + throw new Error(`Could not determine block type for tool: ${toolId}`); + } + + const blockConfig = getBlock(blockType); + if (!blockConfig) { + throw new Error(`Block configuration not found for type: ${blockType}`); + } + return { id: serialized.id, - type: serialized.config.tool, - name: `${serialized.config.tool} Block`, + type: blockType, + name: `${blockType} Block`, position: serialized.position, subBlocks: Object.entries(serialized.config.params).reduce((acc, [key, value]) => { + const subBlock = blockConfig.workflow.subBlocks?.find(sb => sb.id === key); acc[key] = { id: key, - type: this.inferSubBlockType(value), + type: subBlock?.type || 'short-input', value: value }; return acc; @@ -99,14 +112,4 @@ export class Serializer { 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