mirror of
https://github.com/simstudioai/sim.git
synced 2026-01-08 22:48:14 -05:00
Added dynamic handles; added conditional block; changed function block
This commit is contained in:
@@ -91,7 +91,7 @@ export function SubBlock({ blockId, config, isConnecting }: SubBlockProps) {
|
||||
<Switch
|
||||
blockId={blockId}
|
||||
subBlockId={config.id}
|
||||
title={config.title}
|
||||
title={config.title ?? ''}
|
||||
/>
|
||||
)
|
||||
case 'tool-input':
|
||||
@@ -101,7 +101,7 @@ export function SubBlock({ blockId, config, isConnecting }: SubBlockProps) {
|
||||
<CheckboxList
|
||||
blockId={blockId}
|
||||
subBlockId={config.id}
|
||||
title={config.title}
|
||||
title={config.title ?? ''}
|
||||
options={config.options as { label: string; id: string }[]}
|
||||
layout={config.layout}
|
||||
/>
|
||||
|
||||
@@ -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<HTMLDivElement>(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 (
|
||||
<Card
|
||||
ref={blockRef}
|
||||
className={cn(
|
||||
'w-[320px] shadow-md select-none group relative cursor-default',
|
||||
!isEnabled && 'shadow-sm'
|
||||
@@ -155,6 +216,7 @@ export function WorkflowBlock({
|
||||
className={`space-y-1 ${
|
||||
subBlock.layout === 'half' ? 'flex-1' : 'w-full'
|
||||
}`}
|
||||
data-subblock-id={subBlock.id}
|
||||
>
|
||||
<SubBlock
|
||||
blockId={id}
|
||||
@@ -167,18 +229,40 @@ export function WorkflowBlock({
|
||||
))}
|
||||
</div>
|
||||
|
||||
<Handle
|
||||
type="source"
|
||||
position={horizontalHandles ? Position.Right : Position.Bottom}
|
||||
className={cn(
|
||||
'!w-3.5 !h-3.5',
|
||||
'!bg-white !rounded-full !border !border-gray-200',
|
||||
'!opacity-0 group-hover:!opacity-100',
|
||||
'!transition-opacity !duration-200 !cursor-crosshair',
|
||||
'hover:!border-blue-500',
|
||||
horizontalHandles ? '!right-[-7px]' : '!bottom-[-7px]'
|
||||
)}
|
||||
/>
|
||||
{/* Main output handle */}
|
||||
{subBlockPositions.length === 0 && (
|
||||
<Handle
|
||||
type="source"
|
||||
position={horizontalHandles ? Position.Right : Position.Bottom}
|
||||
className={cn(
|
||||
'!w-3.5 !h-3.5',
|
||||
'!bg-white !rounded-full !border !border-gray-200',
|
||||
'!opacity-0 group-hover:!opacity-100',
|
||||
'!transition-opacity !duration-200 !cursor-crosshair',
|
||||
'hover:!border-blue-500',
|
||||
horizontalHandles ? '!right-[-7px]' : '!bottom-[-7px]'
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Subblock output handles */}
|
||||
{subBlockPositions.map((position) => (
|
||||
<Handle
|
||||
key={position.id}
|
||||
type="source"
|
||||
position={Position.Right}
|
||||
id={`output-${position.id}`}
|
||||
style={{ top: position.top }}
|
||||
className={cn(
|
||||
'!w-3.5 !h-3.5',
|
||||
'!bg-white !rounded-full !border !border-gray-200',
|
||||
'!opacity-0 group-hover:!opacity-100',
|
||||
'!transition-opacity !duration-200 !cursor-crosshair',
|
||||
'hover:!border-blue-500',
|
||||
'!right-[-7px]'
|
||||
)}
|
||||
/>
|
||||
))}
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
46
blocks/blocks/condition.ts
Normal file
46
blocks/blocks/condition.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { ConditionalIcon } from '@/components/icons'
|
||||
import { BlockConfig } from '../types'
|
||||
import { CodeExecutionOutput } from '@/tools/function/execute'
|
||||
|
||||
export const ConditionBlock: BlockConfig<CodeExecutionOutput> = {
|
||||
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
|
||||
}
|
||||
],
|
||||
},
|
||||
}
|
||||
@@ -7,7 +7,7 @@ export const FunctionBlock: BlockConfig<CodeExecutionOutput> = {
|
||||
toolbar: {
|
||||
title: 'Function',
|
||||
description: 'Add custom logic',
|
||||
bgColor: '#FF8D2F',
|
||||
bgColor: '#FF402F',
|
||||
icon: CodeIcon,
|
||||
category: 'blocks',
|
||||
},
|
||||
@@ -29,10 +29,8 @@ export const FunctionBlock: BlockConfig<CodeExecutionOutput> = {
|
||||
subBlocks: [
|
||||
{
|
||||
id: 'code',
|
||||
title: 'Code',
|
||||
type: 'code',
|
||||
layout: 'full',
|
||||
placeholder: 'Enter your code here...'
|
||||
}
|
||||
],
|
||||
},
|
||||
|
||||
@@ -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<string, BlockConfig> = {
|
||||
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
|
||||
|
||||
@@ -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, any>) => string
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user