mirror of
https://github.com/simstudioai/sim.git
synced 2026-01-09 15:07:55 -05:00
Finished if block completely
This commit is contained in:
@@ -0,0 +1,455 @@
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { ChevronDown, ChevronUp, Plus, Trash } from 'lucide-react'
|
||||
import { highlight, languages } from 'prismjs'
|
||||
import 'prismjs/components/prism-javascript'
|
||||
import 'prismjs/themes/prism.css'
|
||||
import Editor from 'react-simple-code-editor'
|
||||
import { Handle, Position, useUpdateNodeInternals } from 'reactflow'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { EnvVarDropdown, checkEnvVarTrigger } from '@/components/ui/env-var-dropdown'
|
||||
import { TagDropdown, checkTagTrigger } from '@/components/ui/tag-dropdown'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { useSubBlockValue } from '../hooks/use-sub-block-value'
|
||||
|
||||
interface ConditionalBlock {
|
||||
id: string
|
||||
title: string
|
||||
value: string
|
||||
}
|
||||
|
||||
interface ConditionInputProps {
|
||||
blockId: string
|
||||
subBlockId: string
|
||||
isConnecting: boolean
|
||||
}
|
||||
|
||||
export function ConditionInput({ blockId, subBlockId, isConnecting }: ConditionInputProps) {
|
||||
const [storeValue, setStoreValue] = useSubBlockValue(blockId, subBlockId)
|
||||
const [lineCount, setLineCount] = useState(1)
|
||||
const [showTags, setShowTags] = useState(false)
|
||||
const [showEnvVars, setShowEnvVars] = useState(false)
|
||||
const [searchTerm, setSearchTerm] = useState('')
|
||||
const [cursorPosition, setCursorPosition] = useState(0)
|
||||
const [activeSourceBlockId, setActiveSourceBlockId] = useState<string | null>(null)
|
||||
const editorRef = useRef<HTMLDivElement>(null)
|
||||
const [visualLineHeights, setVisualLineHeights] = useState<{ [key: string]: number[] }>({})
|
||||
const updateNodeInternals = useUpdateNodeInternals()
|
||||
|
||||
// Initialize conditional blocks with empty values
|
||||
const [conditionalBlocks, setConditionalBlocks] = useState<ConditionalBlock[]>([
|
||||
{ id: crypto.randomUUID(), title: 'if', value: '' },
|
||||
])
|
||||
|
||||
// Sync store value with conditional blocks on initial load
|
||||
useEffect(() => {
|
||||
if (storeValue !== null) {
|
||||
try {
|
||||
const parsedValue = JSON.parse(storeValue.toString())
|
||||
if (Array.isArray(parsedValue)) {
|
||||
setConditionalBlocks(parsedValue)
|
||||
}
|
||||
} catch {
|
||||
// If the store value isn't valid JSON, initialize with default block
|
||||
setConditionalBlocks([{ id: crypto.randomUUID(), title: 'if', value: '' }])
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
// Update store whenever conditional blocks change
|
||||
useEffect(() => {
|
||||
setStoreValue(JSON.stringify(conditionalBlocks))
|
||||
updateNodeInternals(`${blockId}-${subBlockId}`)
|
||||
}, [conditionalBlocks, blockId, subBlockId])
|
||||
|
||||
// Update block value
|
||||
const updateBlockValue = (blockId: string, newValue: string) => {
|
||||
setConditionalBlocks((blocks) =>
|
||||
blocks.map((block) => (block.id === blockId ? { ...block, value: newValue } : block))
|
||||
)
|
||||
}
|
||||
|
||||
// Update the line counting logic to be block-specific
|
||||
useEffect(() => {
|
||||
if (!editorRef.current) return
|
||||
|
||||
const calculateVisualLines = () => {
|
||||
const preElement = editorRef.current?.querySelector('pre')
|
||||
if (!preElement) return
|
||||
|
||||
const newVisualLineHeights: { [key: string]: number[] } = {}
|
||||
|
||||
conditionalBlocks.forEach((block) => {
|
||||
const lines = block.value.split('\n')
|
||||
const blockVisualHeights: number[] = []
|
||||
|
||||
// Create a hidden container with the same width as the editor
|
||||
const container = document.createElement('div')
|
||||
container.style.cssText = `
|
||||
position: absolute;
|
||||
visibility: hidden;
|
||||
width: ${preElement.clientWidth}px;
|
||||
font-family: ${window.getComputedStyle(preElement).fontFamily};
|
||||
font-size: ${window.getComputedStyle(preElement).fontSize};
|
||||
padding: 12px;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
`
|
||||
document.body.appendChild(container)
|
||||
|
||||
// Process each line
|
||||
lines.forEach((line) => {
|
||||
const lineDiv = document.createElement('div')
|
||||
|
||||
if (line.includes('<') && line.includes('>')) {
|
||||
const parts = line.split(/(<[^>]+>)/g)
|
||||
parts.forEach((part) => {
|
||||
const span = document.createElement('span')
|
||||
span.textContent = part
|
||||
if (part.startsWith('<') && part.endsWith('>')) {
|
||||
span.style.color = 'rgb(153, 0, 85)'
|
||||
}
|
||||
lineDiv.appendChild(span)
|
||||
})
|
||||
} else {
|
||||
lineDiv.textContent = line || ' '
|
||||
}
|
||||
|
||||
container.appendChild(lineDiv)
|
||||
|
||||
const actualHeight = lineDiv.getBoundingClientRect().height
|
||||
const lineUnits = Math.ceil(actualHeight / 21)
|
||||
blockVisualHeights.push(lineUnits)
|
||||
|
||||
container.removeChild(lineDiv)
|
||||
})
|
||||
|
||||
document.body.removeChild(container)
|
||||
newVisualLineHeights[block.id] = blockVisualHeights
|
||||
})
|
||||
|
||||
setVisualLineHeights(newVisualLineHeights)
|
||||
}
|
||||
|
||||
calculateVisualLines()
|
||||
|
||||
const resizeObserver = new ResizeObserver(calculateVisualLines)
|
||||
resizeObserver.observe(editorRef.current)
|
||||
|
||||
return () => resizeObserver.disconnect()
|
||||
}, [conditionalBlocks])
|
||||
|
||||
// Modify the line numbers rendering to be block-specific
|
||||
const renderLineNumbers = (blockId: string) => {
|
||||
const numbers: JSX.Element[] = []
|
||||
let lineNumber = 1
|
||||
const blockHeights = visualLineHeights[blockId] || []
|
||||
|
||||
blockHeights.forEach((height) => {
|
||||
for (let i = 0; i < height; i++) {
|
||||
numbers.push(
|
||||
<div
|
||||
key={`${lineNumber}-${i}`}
|
||||
className={cn('text-xs text-muted-foreground leading-[21px]', i > 0 && 'invisible')}
|
||||
>
|
||||
{lineNumber}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
lineNumber++
|
||||
})
|
||||
|
||||
return numbers
|
||||
}
|
||||
|
||||
// Handle drops from connection blocks
|
||||
const handleDrop = (e: React.DragEvent) => {
|
||||
e.preventDefault()
|
||||
try {
|
||||
const data = JSON.parse(e.dataTransfer.getData('application/json'))
|
||||
if (data.type !== 'connectionBlock') return
|
||||
|
||||
// Get current cursor position from the textarea
|
||||
const textarea = editorRef.current?.querySelector('textarea')
|
||||
const dropPosition =
|
||||
textarea?.selectionStart ?? conditionalBlocks.map((block) => block.value).join('\n').length
|
||||
|
||||
// Insert '<' at drop position to trigger the dropdown
|
||||
const newValue =
|
||||
conditionalBlocks
|
||||
.map((block) => block.value)
|
||||
.join('\n')
|
||||
.slice(0, dropPosition) +
|
||||
'<' +
|
||||
conditionalBlocks
|
||||
.map((block) => block.value)
|
||||
.join('\n')
|
||||
.slice(dropPosition)
|
||||
|
||||
updateBlockValue(data.connectionData?.sourceBlockId || '', newValue)
|
||||
setCursorPosition(dropPosition + 1)
|
||||
setShowTags(true)
|
||||
|
||||
if (data.connectionData?.sourceBlockId) {
|
||||
setActiveSourceBlockId(data.connectionData.sourceBlockId)
|
||||
}
|
||||
|
||||
// Set cursor position after state updates
|
||||
setTimeout(() => {
|
||||
if (textarea) {
|
||||
textarea.selectionStart = dropPosition + 1
|
||||
textarea.selectionEnd = dropPosition + 1
|
||||
textarea.focus()
|
||||
}
|
||||
}, 0)
|
||||
} catch (error) {
|
||||
console.error('Failed to parse drop data:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// Handle tag selection
|
||||
const handleTagSelect = (newValue: string) => {
|
||||
updateBlockValue(activeSourceBlockId || '', newValue)
|
||||
setShowTags(false)
|
||||
setActiveSourceBlockId(null)
|
||||
}
|
||||
|
||||
// Handle environment variable selection
|
||||
const handleEnvVarSelect = (newValue: string) => {
|
||||
updateBlockValue(activeSourceBlockId || '', newValue)
|
||||
setShowEnvVars(false)
|
||||
}
|
||||
|
||||
// Update block titles based on position
|
||||
const updateBlockTitles = (blocks: ConditionalBlock[]): ConditionalBlock[] => {
|
||||
return blocks.map((block, index) => ({
|
||||
...block,
|
||||
title: index === 0 ? 'if' : index === blocks.length - 1 ? 'else' : 'else if',
|
||||
}))
|
||||
}
|
||||
|
||||
// Update these functions to use updateBlockTitles
|
||||
const addBlock = (afterId: string) => {
|
||||
const blockIndex = conditionalBlocks.findIndex((block) => block.id === afterId)
|
||||
const newBlock = { id: crypto.randomUUID(), title: '', value: '' }
|
||||
|
||||
const newBlocks = [...conditionalBlocks]
|
||||
newBlocks.splice(blockIndex + 1, 0, newBlock)
|
||||
setConditionalBlocks(updateBlockTitles(newBlocks))
|
||||
}
|
||||
|
||||
const removeBlock = (id: string) => {
|
||||
if (conditionalBlocks.length === 1) return
|
||||
setConditionalBlocks((blocks) => updateBlockTitles(blocks.filter((block) => block.id !== id)))
|
||||
}
|
||||
|
||||
const moveBlock = (id: string, direction: 'up' | 'down') => {
|
||||
const blockIndex = conditionalBlocks.findIndex((block) => block.id === id)
|
||||
if (
|
||||
(direction === 'up' && blockIndex === 0) ||
|
||||
(direction === 'down' && blockIndex === conditionalBlocks.length - 1)
|
||||
)
|
||||
return
|
||||
|
||||
const newBlocks = [...conditionalBlocks]
|
||||
const targetIndex = direction === 'up' ? blockIndex - 1 : blockIndex + 1
|
||||
;[newBlocks[blockIndex], newBlocks[targetIndex]] = [
|
||||
newBlocks[targetIndex],
|
||||
newBlocks[blockIndex],
|
||||
]
|
||||
setConditionalBlocks(updateBlockTitles(newBlocks))
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{conditionalBlocks.map((block, index) => (
|
||||
<div
|
||||
key={block.id}
|
||||
className="overflow-visible rounded-lg border bg-background group relative"
|
||||
>
|
||||
<div className="flex h-10 items-center justify-between border-b bg-card px-3">
|
||||
<span className="text-sm font-medium">{block.title}</span>
|
||||
<Handle
|
||||
type="source"
|
||||
position={Position.Right}
|
||||
id={`condition-${block.id}`}
|
||||
className={cn(
|
||||
'!w-3.5 !h-3.5',
|
||||
'!bg-white !rounded-full !border !border-gray-200',
|
||||
'group-hover:!border-blue-500',
|
||||
'!transition-border !duration-150 !cursor-crosshair',
|
||||
'!absolute !z-50',
|
||||
'!right-[-25px]'
|
||||
)}
|
||||
data-nodeid={`${blockId}-${subBlockId}`}
|
||||
data-handleid={`condition-${block.id}`}
|
||||
style={{
|
||||
top: '50%',
|
||||
transform: 'translateY(-50%)',
|
||||
}}
|
||||
isConnectableStart={true}
|
||||
isConnectableEnd={false}
|
||||
/>
|
||||
<div className="flex items-center gap-1">
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => addBlock(block.id)}
|
||||
className="h-8 w-8"
|
||||
>
|
||||
<Plus className="h-4 w-4" />
|
||||
<span className="sr-only">Add Block</span>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Add Block</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<div className="flex items-center">
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => moveBlock(block.id, 'up')}
|
||||
disabled={index === 0}
|
||||
className="h-8 w-8"
|
||||
>
|
||||
<ChevronUp className="h-4 w-4" />
|
||||
<span className="sr-only">Move Up</span>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Move Up</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => moveBlock(block.id, 'down')}
|
||||
disabled={index === conditionalBlocks.length - 1}
|
||||
className="h-8 w-8"
|
||||
>
|
||||
<ChevronDown className="h-4 w-4" />
|
||||
<span className="sr-only">Move Down</span>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Move Down</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => removeBlock(block.id)}
|
||||
disabled={conditionalBlocks.length === 1}
|
||||
className="h-8 w-8 text-destructive hover:text-destructive"
|
||||
>
|
||||
<Trash className="h-4 w-4" />
|
||||
<span className="sr-only">Delete Block</span>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Delete Condition</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
'relative min-h-[100px] rounded-md bg-background font-mono text-sm',
|
||||
isConnecting && 'ring-2 ring-blue-500 ring-offset-2'
|
||||
)}
|
||||
onDragOver={(e) => e.preventDefault()}
|
||||
onDrop={handleDrop}
|
||||
>
|
||||
{/* Line numbers */}
|
||||
<div
|
||||
className="absolute left-0 top-0 bottom-0 w-[30px] bg-muted/30 flex flex-col items-end pr-3 pt-3 select-none"
|
||||
aria-hidden="true"
|
||||
>
|
||||
{renderLineNumbers(block.id)}
|
||||
</div>
|
||||
|
||||
<div className="pl-[30px] pt-0 mt-0 relative" ref={editorRef}>
|
||||
{block.value.length === 0 && (
|
||||
<div className="absolute left-[42px] top-[12px] text-muted-foreground/50 select-none pointer-events-none">
|
||||
{'<response> === true'}
|
||||
</div>
|
||||
)}
|
||||
<Editor
|
||||
value={block.value}
|
||||
onValueChange={(newCode) => {
|
||||
updateBlockValue(block.id, newCode)
|
||||
|
||||
// Check for tag trigger and environment variable trigger
|
||||
const textarea = editorRef.current?.querySelector('textarea')
|
||||
if (textarea) {
|
||||
const pos = textarea.selectionStart
|
||||
setCursorPosition(pos)
|
||||
|
||||
const tagTrigger = checkTagTrigger(newCode, pos)
|
||||
setShowTags(tagTrigger.show)
|
||||
if (!tagTrigger.show) {
|
||||
setActiveSourceBlockId(null)
|
||||
}
|
||||
|
||||
const envVarTrigger = checkEnvVarTrigger(newCode, pos)
|
||||
setShowEnvVars(envVarTrigger.show)
|
||||
setSearchTerm(envVarTrigger.show ? envVarTrigger.searchTerm : '')
|
||||
}
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Escape') {
|
||||
setShowTags(false)
|
||||
setShowEnvVars(false)
|
||||
}
|
||||
}}
|
||||
highlight={(code) => highlight(code, languages.javascript, 'javascript')}
|
||||
padding={12}
|
||||
style={{
|
||||
fontFamily: 'inherit',
|
||||
minHeight: '46px',
|
||||
lineHeight: '21px',
|
||||
}}
|
||||
className="focus:outline-none"
|
||||
textareaClassName="focus:outline-none focus:ring-0 bg-transparent"
|
||||
/>
|
||||
|
||||
{showEnvVars && (
|
||||
<EnvVarDropdown
|
||||
visible={showEnvVars}
|
||||
onSelect={handleEnvVarSelect}
|
||||
searchTerm={searchTerm}
|
||||
inputValue={block.value}
|
||||
cursorPosition={cursorPosition}
|
||||
onClose={() => {
|
||||
setShowEnvVars(false)
|
||||
setSearchTerm('')
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{showTags && (
|
||||
<TagDropdown
|
||||
visible={showTags}
|
||||
onSelect={handleTagSelect}
|
||||
blockId={blockId}
|
||||
activeSourceBlockId={activeSourceBlockId}
|
||||
inputValue={block.value}
|
||||
cursorPosition={cursorPosition}
|
||||
onClose={() => {
|
||||
setShowTags(false)
|
||||
setActiveSourceBlockId(null)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import { Label } from '@/components/ui/label'
|
||||
import { SubBlockConfig } from '../../../../../../../blocks/types'
|
||||
import { CheckboxList } from './components/checkbox-list'
|
||||
import { Code } from './components/code'
|
||||
import { ConditionInput } from './components/condition-input'
|
||||
import { Dropdown } from './components/dropdown'
|
||||
import { LongInput } from './components/long-input'
|
||||
import { ShortInput } from './components/short-input'
|
||||
@@ -89,6 +90,10 @@ export function SubBlock({ blockId, config, isConnecting }: SubBlockProps) {
|
||||
layout={config.layout}
|
||||
/>
|
||||
)
|
||||
case 'condition-input':
|
||||
return (
|
||||
<ConditionInput blockId={blockId} subBlockId={config.id} isConnecting={isConnecting} />
|
||||
)
|
||||
default:
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -26,7 +26,6 @@ interface SubBlockPosition {
|
||||
|
||||
export function WorkflowBlock({ id, type, config, name, selected }: WorkflowBlockProps) {
|
||||
const { toolbar, workflow } = config
|
||||
// Dragging connection state
|
||||
const [isConnecting, setIsConnecting] = useState(false)
|
||||
const isEnabled = useWorkflowStore((state) => state.blocks[id]?.enabled ?? true)
|
||||
const horizontalHandles = useWorkflowStore(
|
||||
@@ -36,62 +35,12 @@ export function WorkflowBlock({ id, type, config, name, selected }: WorkflowBloc
|
||||
const [editedName, setEditedName] = useState('')
|
||||
const updateBlockName = useWorkflowStore((state) => state.updateBlockName)
|
||||
const blockRef = useRef<HTMLDivElement>(null)
|
||||
const [subBlockPositions, setSubBlockPositions] = useState<SubBlockPosition[]>([])
|
||||
const updateNodeInternals = useUpdateNodeInternals()
|
||||
|
||||
// Add a small delay to ensure DOM is ready
|
||||
// Add effect to update node internals when handles change
|
||||
useEffect(() => {
|
||||
const calculatePositions = () => {
|
||||
if (!blockRef.current) return
|
||||
|
||||
// Add setTimeout to ensure styles are applied
|
||||
setTimeout(() => {
|
||||
const positions: SubBlockPosition[] = []
|
||||
const blockRect = blockRef.current?.getBoundingClientRect()
|
||||
|
||||
if (!blockRect) return
|
||||
|
||||
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,
|
||||
top: subBlockRect.bottom - blockRect.top - 25,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
setSubBlockPositions(positions)
|
||||
updateNodeInternals(id)
|
||||
}, 0)
|
||||
}
|
||||
|
||||
// Calculate initial positions with a slight delay
|
||||
const initialTimer = setTimeout(calculatePositions, 50)
|
||||
|
||||
// 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 () => {
|
||||
clearTimeout(initialTimer)
|
||||
resizeObserver.disconnect()
|
||||
window.removeEventListener('resize', calculatePositions)
|
||||
}
|
||||
}, [workflow.subBlocks, id, updateNodeInternals])
|
||||
updateNodeInternals(id)
|
||||
}, [id, horizontalHandles])
|
||||
|
||||
function groupSubBlocks(subBlocks: SubBlockConfig[]) {
|
||||
// Filter out hidden subblocks
|
||||
@@ -157,6 +106,7 @@ export function WorkflowBlock({ id, type, config, name, selected }: WorkflowBloc
|
||||
<Handle
|
||||
type="target"
|
||||
position={horizontalHandles ? Position.Left : Position.Top}
|
||||
id="target"
|
||||
className={cn(
|
||||
'!w-3.5 !h-3.5',
|
||||
'!bg-white !rounded-full !border !border-gray-200',
|
||||
@@ -164,6 +114,10 @@ export function WorkflowBlock({ id, type, config, name, selected }: WorkflowBloc
|
||||
'!transition-border !duration-150 !cursor-crosshair',
|
||||
horizontalHandles ? '!left-[-7px]' : '!top-[-7px]'
|
||||
)}
|
||||
data-nodeid={id}
|
||||
data-handleid="target"
|
||||
isConnectableStart={false}
|
||||
isConnectableEnd={true}
|
||||
/>
|
||||
|
||||
<div className="flex items-center justify-between p-3 border-b workflow-drag-handle cursor-grab [&:active]:cursor-grabbing">
|
||||
@@ -208,7 +162,6 @@ export function WorkflowBlock({ id, type, config, name, selected }: WorkflowBloc
|
||||
<div
|
||||
key={`${id}-${rowIndex}-${blockIndex}`}
|
||||
className={`space-y-1 ${subBlock.layout === 'half' ? 'flex-1' : 'w-full'}`}
|
||||
data-subblock-id={subBlock.id}
|
||||
>
|
||||
<SubBlock blockId={id} config={subBlock} isConnecting={isConnecting} />
|
||||
</div>
|
||||
@@ -217,11 +170,12 @@ export function WorkflowBlock({ id, type, config, name, selected }: WorkflowBloc
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Main output handle */}
|
||||
{subBlockPositions.length === 0 && (
|
||||
{/* Main output handle - only render if not a condition block */}
|
||||
{type !== 'condition' && (
|
||||
<Handle
|
||||
type="source"
|
||||
position={horizontalHandles ? Position.Right : Position.Bottom}
|
||||
id="source"
|
||||
className={cn(
|
||||
'!w-3.5 !h-3.5',
|
||||
'!bg-white !rounded-full !border !border-gray-200',
|
||||
@@ -229,26 +183,12 @@ export function WorkflowBlock({ id, type, config, name, selected }: WorkflowBloc
|
||||
'!transition-border !duration-150 !cursor-crosshair',
|
||||
horizontalHandles ? '!right-[-7px]' : '!bottom-[-7px]'
|
||||
)}
|
||||
data-nodeid={id}
|
||||
data-handleid="source"
|
||||
isConnectableStart={true}
|
||||
isConnectableEnd={false}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Subblock output handles */}
|
||||
{subBlockPositions.map((position) => (
|
||||
<Handle
|
||||
key={`${id}-${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',
|
||||
'!transition-border !duration-150 !cursor-crosshair',
|
||||
'group-hover:!border-blue-500',
|
||||
'!right-[-7px]'
|
||||
)}
|
||||
/>
|
||||
))}
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -28,9 +28,8 @@ export const ConditionBlock: BlockConfig<CodeExecutionOutput> = {
|
||||
},
|
||||
subBlocks: [
|
||||
{
|
||||
id: 'if',
|
||||
title: 'if',
|
||||
type: 'code',
|
||||
id: 'conditions',
|
||||
type: 'condition-input',
|
||||
layout: 'full',
|
||||
},
|
||||
],
|
||||
|
||||
@@ -19,7 +19,7 @@ export type SubBlockType =
|
||||
| 'switch'
|
||||
| 'tool-input'
|
||||
| 'checkbox-list'
|
||||
|
||||
| 'condition-input'
|
||||
export type SubBlockLayout = 'full' | 'half'
|
||||
|
||||
// Tool output type utilities
|
||||
@@ -75,7 +75,6 @@ export interface SubBlockConfig {
|
||||
placeholder?: string
|
||||
password?: boolean
|
||||
connectionDroppable?: boolean
|
||||
outputHandle?: boolean
|
||||
hidden?: boolean
|
||||
value?: (params: Record<string, any>) => string
|
||||
minimizable?: boolean
|
||||
|
||||
Reference in New Issue
Block a user