'use client'; import React, { useState, useCallback, useMemo } from 'react'; import { Plus, Trash2, AlertTriangle, ChevronsUpDown, Check, MoreHorizontal } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Label } from '@/components/ui/label'; import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from '@/components/ui/command'; import { cn } from '@/lib/utils'; import { Input } from '@/components/ui/input'; export interface ConditionalStep { id: string; name: string; description: string; type: 'instruction' | 'condition' | 'parallel' | 'sequence'; config: Record; conditions?: { type: 'if' | 'else' | 'elseif'; expression?: string; }; children?: ConditionalStep[]; order: number; enabled?: boolean; hasIssues?: boolean; } interface ConditionalWorkflowBuilderProps { steps: ConditionalStep[]; onStepsChange: (steps: ConditionalStep[]) => void; agentTools?: { agentpress_tools: Array<{ name: string; description: string; icon?: string; enabled: boolean }>; mcp_tools: Array<{ name: string; description: string; icon?: string; server?: string }>; }; isLoadingTools?: boolean; } const normalizeToolName = (toolName: string, toolType: 'agentpress' | 'mcp') => { if (toolType === 'agentpress') { const agentPressMapping: Record = { 'sb_shell_tool': 'Shell Tool', 'sb_files_tool': 'Files Tool', 'sb_browser_tool': 'Browser Tool', 'sb_deploy_tool': 'Deploy Tool', 'sb_expose_tool': 'Expose Tool', 'web_search_tool': 'Web Search', 'sb_vision_tool': 'Vision Tool', 'data_providers_tool': 'Data Providers', }; return agentPressMapping[toolName] || toolName; } else { return toolName .split('_') .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()) .join(' '); } }; export function ConditionalWorkflowBuilder({ steps, onStepsChange, agentTools, isLoadingTools }: ConditionalWorkflowBuilderProps) { const [toolSearchOpen, setToolSearchOpen] = useState<{[key: string]: boolean}>({}); const [activeConditionTab, setActiveConditionTab] = useState<{[key: string]: string}>({}); steps.forEach((step, index) => { console.log(`Step ${index}:`, { name: step.name, type: step.type, hasChildren: !!step.children, childrenCount: step.children?.length || 0, children: step.children?.map(child => ({ name: child.name, type: child.type })) }); }); const generateId = () => Math.random().toString(36).substr(2, 9); const addStep = useCallback((parentId?: string, afterStepId?: string) => { const newStep: ConditionalStep = { id: generateId(), name: 'Step', description: '', type: 'instruction', config: {}, order: 0, enabled: true, }; const updateSteps = (items: ConditionalStep[]): ConditionalStep[] => { if (!parentId) { if (afterStepId) { const index = items.findIndex(s => s.id === afterStepId); return [...items.slice(0, index + 1), newStep, ...items.slice(index + 1)]; } return [...items, newStep]; } return items.map(step => { if (step.id === parentId) { return { ...step, children: [...(step.children || []), newStep] }; } if (step.children) { return { ...step, children: updateSteps(step.children) }; } return step; }); }; onStepsChange(updateSteps(steps)); }, [steps, onStepsChange]); const addCondition = useCallback((afterStepId: string) => { const ifStep: ConditionalStep = { id: generateId(), name: 'If', description: '', type: 'condition', config: {}, conditions: { type: 'if', expression: '' }, children: [], order: 0, enabled: true, hasIssues: true }; const updateSteps = (items: ConditionalStep[]): ConditionalStep[] => { const index = items.findIndex(s => s.id === afterStepId); if (index !== -1) { return [ ...items.slice(0, index + 1), ifStep, ...items.slice(index + 1) ]; } return items.map(step => { if (step.children) { return { ...step, children: updateSteps(step.children) }; } return step; }); }; onStepsChange(updateSteps(steps)); }, [steps, onStepsChange]); const addElseCondition = useCallback((siblingId: string) => { const elseIfStep: ConditionalStep = { id: generateId(), name: 'Else If', description: '', type: 'condition', config: {}, conditions: { type: 'elseif', expression: '' }, children: [], order: 0, enabled: true, hasIssues: true }; const updateSteps = (items: ConditionalStep[]): ConditionalStep[] => { const index = items.findIndex(s => s.id === siblingId); if (index !== -1) { return [ ...items.slice(0, index + 1), elseIfStep, ...items.slice(index + 1) ]; } return items.map(step => { if (step.children) { return { ...step, children: updateSteps(step.children) }; } return step; }); }; onStepsChange(updateSteps(steps)); }, [steps, onStepsChange]); const addFinalElse = useCallback((siblingId: string) => { const elseStep: ConditionalStep = { id: generateId(), name: 'Else', description: '', type: 'condition', config: {}, conditions: { type: 'else' }, children: [], order: 0, enabled: true, hasIssues: false }; const updateSteps = (items: ConditionalStep[]): ConditionalStep[] => { const index = items.findIndex(s => s.id === siblingId); if (index !== -1) { return [ ...items.slice(0, index + 1), elseStep, ...items.slice(index + 1) ]; } return items.map(step => { if (step.children) { return { ...step, children: updateSteps(step.children) }; } return step; }); }; onStepsChange(updateSteps(steps)); }, [steps, onStepsChange]); const updateStep = useCallback((stepId: string, updates: Partial) => { const updateSteps = (items: ConditionalStep[]): ConditionalStep[] => { return items.map(step => { if (step.id === stepId) { const updatedStep = { ...step, ...updates }; if (updatedStep.type === 'instruction' && updatedStep.name && updatedStep.name !== 'New Step') { updatedStep.hasIssues = false; } else if (updatedStep.type === 'condition' && (updatedStep.conditions?.type === 'if' || updatedStep.conditions?.type === 'elseif') && updatedStep.conditions?.expression) { updatedStep.hasIssues = false; } else if (updatedStep.type === 'condition' && updatedStep.conditions?.type === 'else') { updatedStep.hasIssues = false; } return updatedStep; } if (step.children) { return { ...step, children: updateSteps(step.children) }; } return step; }); }; onStepsChange(updateSteps(steps)); }, [steps, onStepsChange]); const removeStep = useCallback((stepId: string) => { const removeFromSteps = (items: ConditionalStep[]): ConditionalStep[] => { return items .filter(step => step.id !== stepId) .map(step => { if (step.children) { return { ...step, children: removeFromSteps(step.children) }; } return step; }); }; onStepsChange(removeFromSteps(steps)); }, [steps, onStepsChange]); const getStepNumber = useCallback((stepId: string, items: ConditionalStep[] = steps, counter = { value: 0 }): number => { for (const step of items) { counter.value++; if (step.id === stepId) { return counter.value; } if (step.children && step.children.length > 0) { const found = getStepNumber(stepId, step.children, counter); if (found > 0) return found; } } return 0; }, [steps]); const getConditionLetter = (index: number) => { return String.fromCharCode(65 + index); }; const renderConditionTabs = (conditionSteps: ConditionalStep[], groupKey: string) => { const activeTabId = activeConditionTab[groupKey] || conditionSteps[0]?.id; const activeStep = conditionSteps.find(s => s.id === activeTabId) || conditionSteps[0]; const hasElse = conditionSteps.some(step => step.conditions?.type === 'else'); const handleKeyDown = (e: React.KeyboardEvent, step: ConditionalStep) => { if (e.key === 'Backspace' || e.key === 'Delete') { e.preventDefault(); if (conditionSteps.length > 1 && !(conditionSteps.length === 1 && step.conditions?.type === 'if')) { removeStep(step.id); const remainingConditions = conditionSteps.filter(s => s.id !== step.id); if (remainingConditions.length > 0) { setActiveConditionTab(prev => ({ ...prev, [groupKey]: remainingConditions[0].id })); } } } }; return (
{conditionSteps.map((step, index) => { const letter = getConditionLetter(index); const isActive = step.id === activeTabId; const conditionType = step.conditions?.type === 'if' ? 'If' : step.conditions?.type === 'elseif' ? 'Else If' : step.conditions?.type === 'else' ? 'Else' : 'If'; return ( ); })} {!hasElse && ( )} {!hasElse && ( )}
{activeStep && (
{(activeStep.conditions?.type === 'if' || activeStep.conditions?.type === 'elseif') ? (
updateStep(activeStep.id, { conditions: { ...activeStep.conditions, expression: e.target.value } })} placeholder="e.g., user asks about pricing" className="w-full bg-transparent text-sm px-3 py-2 rounded-md" />
) : (
Otherwise (fallback condition)
)}
{activeStep.children && activeStep.children.length > 0 && ( <> {activeStep.children.map((child, index) => renderStep(child, index + 1, true, activeStep.id))} )}
)}
); }; const renderStep = (step: ConditionalStep, stepNumber: number, isNested: boolean = false, parentId?: string) => { const isCondition = step.type === 'condition'; const isSequence = step.type === 'sequence'; if (isCondition) { return null; } return (
{step.hasIssues && ( )}
{stepNumber}
{isSequence ? (
{step.description}
) : ( updateStep(step.id, { name: e.target.value })} placeholder="Step name" className="w-full bg-transparent border-0 outline-none text-base font-medium placeholder:text-muted-foreground" /> )}
{!isSequence && step.description !== undefined && ( updateStep(step.id, { description: e.target.value })} placeholder="Add a description" className="-mt-2 w-full bg-transparent border-0 outline-none text-sm text-muted-foreground placeholder:text-muted-foreground mb-3" /> )} {!isSequence && ( setToolSearchOpen(prev => ({ ...prev, [step.id]: open }))} > No tools found. {isLoadingTools ? ( Loading tools... ) : agentTools ? ( <> {agentTools.agentpress_tools.filter(tool => tool.enabled).length > 0 && ( {agentTools.agentpress_tools.filter(tool => tool.enabled).map((tool) => ( { updateStep(step.id, { config: { ...step.config, tool_name: tool.name } }); setToolSearchOpen(prev => ({ ...prev, [step.id]: false })); }} >
{tool.icon || '🔧'} {normalizeToolName(tool.name, 'agentpress')}
))}
)} {agentTools.mcp_tools.length > 0 && ( {agentTools.mcp_tools.map((tool) => ( { updateStep(step.id, { config: { ...step.config, tool_name: tool.server ? `${tool.server}:${tool.name}` : tool.name } }); setToolSearchOpen(prev => ({ ...prev, [step.id]: false })); }} >
{tool.icon || '🔧'} {normalizeToolName(tool.name, 'mcp')}
))}
)} ) : ( Failed to load tools )}
)} {step.children && step.children.length > 0 && (
{step.children.map((child, index) => renderStep(child, index + 1, true, step.id))}
)}
); }; const renderSteps = () => { const result: React.ReactNode[] = []; let stepCounter = 0; let i = 0; while (i < steps.length) { const step = steps[i]; if (step.type === 'condition') { const conditionGroup: ConditionalStep[] = []; while (i < steps.length && steps[i].type === 'condition') { conditionGroup.push(steps[i]); i++; } stepCounter++; result.push(
{stepCounter}
Add rule
{renderConditionTabs(conditionGroup, conditionGroup[0].id)}
); } else { stepCounter++; result.push(renderStep(step, stepCounter, false)); i++; } } return result; }; return (
{steps.length === 0 ? (

Start building your workflow

Add steps and conditions to create a smart workflow that adapts to different scenarios.

) : (
{renderSteps()}
)}
); }