diff --git a/app/w/[id]/components/workflow-block/components/sub-block/sub-block.tsx b/app/w/[id]/components/workflow-block/components/sub-block/sub-block.tsx index eea19c7aff..fb003ba9de 100644 --- a/app/w/[id]/components/workflow-block/components/sub-block/sub-block.tsx +++ b/app/w/[id]/components/workflow-block/components/sub-block/sub-block.tsx @@ -91,7 +91,7 @@ export function SubBlock({ blockId, config, isConnecting }: SubBlockProps) { ) case 'tool-input': @@ -101,7 +101,7 @@ export function SubBlock({ blockId, config, isConnecting }: SubBlockProps) { diff --git a/app/w/[id]/components/workflow-block/workflow-block.tsx b/app/w/[id]/components/workflow-block/workflow-block.tsx index 8bdb85d8d5..2566a11647 100644 --- a/app/w/[id]/components/workflow-block/workflow-block.tsx +++ b/app/w/[id]/components/workflow-block/workflow-block.tsx @@ -5,9 +5,10 @@ import { Handle, Position } from 'reactflow' import { cn } from '@/lib/utils' import { ActionBar } from './components/action-bar/action-bar' import { ConnectionBlocks } from './components/connection-blocks/connection-blocks' -import { useState } from 'react' +import { useState, useEffect, useRef } from 'react' import { useWorkflowStore } from '@/stores/workflow/store' import { Badge } from '@/components/ui/badge' +import { useUpdateNodeInternals } from 'reactflow' interface WorkflowBlockProps { id: string @@ -18,6 +19,11 @@ interface WorkflowBlockProps { selected?: boolean } +interface SubBlockPosition { + id: string + top: number +} + export function WorkflowBlock({ id, type, @@ -37,6 +43,60 @@ export function WorkflowBlock({ const [isEditing, setIsEditing] = useState(false) const [editedName, setEditedName] = useState('') const updateBlockName = useWorkflowStore((state) => state.updateBlockName) + const blockRef = useRef(null) + const [subBlockPositions, setSubBlockPositions] = useState< + SubBlockPosition[] + >([]) + const updateNodeInternals = useUpdateNodeInternals() + + // Calculate subblock positions when the component mounts or updates + useEffect(() => { + const calculatePositions = () => { + if (!blockRef.current) return + + const positions: SubBlockPosition[] = [] + const blockRect = blockRef.current.getBoundingClientRect() + + workflow.subBlocks + .filter((block) => block.outputHandle) + .forEach((block) => { + const subBlockElement = blockRef.current?.querySelector( + `[data-subblock-id="${block.id}"]` + ) + if (subBlockElement) { + const subBlockRect = subBlockElement.getBoundingClientRect() + positions.push({ + id: block.id, + // Move handle 25px up from the bottom + top: subBlockRect.bottom - blockRect.top - 25, + }) + } + }) + + setSubBlockPositions(positions) + updateNodeInternals(id) + } + + // Calculate initial positions + calculatePositions() + + // Use ResizeObserver to detect size changes and recalculate + const resizeObserver = new ResizeObserver(() => { + calculatePositions() + }) + + if (blockRef.current) { + resizeObserver.observe(blockRef.current) + } + + // Recalculate on window resize + window.addEventListener('resize', calculatePositions) + + return () => { + resizeObserver.disconnect() + window.removeEventListener('resize', calculatePositions) + } + }, [workflow.subBlocks, id, updateNodeInternals]) function groupSubBlocks(subBlocks: SubBlockConfig[]) { const rows: SubBlockConfig[][] = [] @@ -87,6 +147,7 @@ export function WorkflowBlock({ return ( - + {/* Main output handle */} + {subBlockPositions.length === 0 && ( + + )} + + {/* Subblock output handles */} + {subBlockPositions.map((position) => ( + + ))} ) } diff --git a/blocks/blocks/condition.ts b/blocks/blocks/condition.ts new file mode 100644 index 0000000000..1a9bccf29a --- /dev/null +++ b/blocks/blocks/condition.ts @@ -0,0 +1,46 @@ +import { ConditionalIcon } from '@/components/icons' +import { BlockConfig } from '../types' +import { CodeExecutionOutput } from '@/tools/function/execute' + +export const ConditionBlock: BlockConfig = { + type: 'condition', + toolbar: { + title: 'Condition', + description: 'Add a condition', + bgColor: '#FF972F', + icon: ConditionalIcon, + category: 'blocks', + }, + tools: { + access: ['function_execute'] + }, + workflow: { + inputs: { + code: { type: 'string', required: true } + }, + outputs: { + response: { + type: { + result: 'any', + stdout: 'string' + } + } + }, + subBlocks: [ + { + id: 'if', + title: 'if', + type: 'code', + layout: 'full', + outputHandle: true + }, + { + id: 'elseIf', + title: 'else if', + type: 'code', + layout: 'full', + outputHandle: true + } + ], + }, +} \ No newline at end of file diff --git a/blocks/blocks/function.ts b/blocks/blocks/function.ts index 608e0761bf..ba9f4d6783 100644 --- a/blocks/blocks/function.ts +++ b/blocks/blocks/function.ts @@ -7,7 +7,7 @@ export const FunctionBlock: BlockConfig = { toolbar: { title: 'Function', description: 'Add custom logic', - bgColor: '#FF8D2F', + bgColor: '#FF402F', icon: CodeIcon, category: 'blocks', }, @@ -29,10 +29,8 @@ export const FunctionBlock: BlockConfig = { subBlocks: [ { id: 'code', - title: 'Code', type: 'code', layout: 'full', - placeholder: 'Enter your code here...' } ], }, diff --git a/blocks/index.ts b/blocks/index.ts index e94076303b..808d9bc338 100644 --- a/blocks/index.ts +++ b/blocks/index.ts @@ -10,6 +10,7 @@ import { JinaBlock } from './blocks/jina' import { TranslateBlock } from './blocks/translate' import { SlackMessageBlock } from './blocks/slack' import { GitHubBlock } from './blocks/github' +import { ConditionBlock } from './blocks/condition' // Export blocks for ease of use export { @@ -21,7 +22,8 @@ export { JinaBlock, TranslateBlock, SlackMessageBlock, - GitHubBlock + GitHubBlock, + ConditionBlock } // Registry of all block configurations @@ -34,7 +36,8 @@ const blocks: Record = { jina_reader: JinaBlock, translate: TranslateBlock, slack_message: SlackMessageBlock, - github_repo_info: GitHubBlock + github_repo_info: GitHubBlock, + condition: ConditionBlock } // Build a reverse mapping of tools to block types diff --git a/blocks/types.ts b/blocks/types.ts index 5f6c755f89..4c94585573 100644 --- a/blocks/types.ts +++ b/blocks/types.ts @@ -38,7 +38,7 @@ export interface ParamConfig { export interface SubBlockConfig { id: string - title: string + title?: string type: SubBlockType layout?: SubBlockLayout options?: string[] | { label: string; id: string }[] @@ -48,6 +48,7 @@ export interface SubBlockConfig { placeholder?: string password?: boolean connectionDroppable?: boolean + outputHandle?: boolean hidden?: boolean value?: (params: Record) => string }