import React, { useState, useCallback } from "react"; import { Play, CheckCircle, RotateCcw, Clock, Repeat, Split, Layers, ChevronRight, Loader2, } from "lucide-react"; import { Button } from "~/components/ui/button"; import { cn } from "~/lib/utils"; import { Badge } from "~/components/ui/badge"; export interface ActionData { id: string; name: string; description: string | null; type: string; parameters: Record; order: number; pluginId: string | null; } interface WizardActionItemProps { action: ActionData; index: number; isActive: boolean; isCompleted: boolean; onExecute: (actionId: string, parameters?: Record) => void; onExecuteRobot: ( pluginName: string, actionId: string, parameters: Record, options?: { autoAdvance?: boolean } ) => Promise; onSkip: ( pluginName: string, actionId: string, parameters: Record, options?: { autoAdvance?: boolean } ) => Promise; onCompleted: () => void; readOnly?: boolean; isExecuting?: boolean; depth?: number; isRobotConnected?: boolean; } export function WizardActionItem({ action, index, isActive, isCompleted, onExecute, onExecuteRobot, onSkip, onCompleted, readOnly, isExecuting, depth = 0, isRobotConnected = false, }: WizardActionItemProps): React.JSX.Element { // Local state for container children completion const [completedChildren, setCompletedChildren] = useState>(new Set()); // Local state for loop iterations const [currentIteration, setCurrentIteration] = useState(1); // Local state to track execution of this specific item const [isRunningLocal, setIsRunningLocal] = useState(false); // Local state for wait countdown const [countdown, setCountdown] = useState(null); const isContainer = action.type === "hristudio-core.sequence" || action.type === "hristudio-core.parallel" || action.type === "hristudio-core.loop" || action.type === "sequence" || action.type === "parallel" || action.type === "loop"; // Branch support const isBranch = action.type === "hristudio-core.branch" || action.type === "branch"; const isWait = action.type === "hristudio-core.wait" || action.type === "wait"; // Helper to get children const children = (action.parameters.children as ActionData[]) || []; const iterations = (action.parameters.iterations as number) || 1; // Recursive helper to check for robot actions const hasRobotActions = useCallback((item: ActionData): boolean => { if (item.type === "robot_action" || !!item.pluginId) return true; if (item.parameters?.children && Array.isArray(item.parameters.children)) { return (item.parameters.children as ActionData[]).some(hasRobotActions); } return false; }, []); const containsRobotActions = hasRobotActions(action); // Countdown effect React.useEffect(() => { let interval: NodeJS.Timeout; if (isRunningLocal && countdown !== null && countdown > 0) { interval = setInterval(() => { setCountdown((prev) => (prev !== null && prev > 0 ? prev - 1 : 0)); }, 1000); } return () => clearInterval(interval); }, [isRunningLocal, countdown]); // Derived state for disabled button const isButtonDisabled = isExecuting || isRunningLocal || (!isWait && !isRobotConnected && (action.type === 'robot_action' || !!action.pluginId || (isContainer && containsRobotActions))); // Handler for child completion const handleChildCompleted = useCallback((childIndex: number) => { setCompletedChildren(prev => { const next = new Set(prev); next.add(childIndex); return next; }); }, []); // Handler for next loop iteration const handleNextIteration = useCallback(() => { if (currentIteration < iterations) { setCompletedChildren(new Set()); setCurrentIteration(prev => prev + 1); } else { // Loop finished - allow manual completion of the loop action } }, [currentIteration, iterations]); // Check if current iteration is complete (all children done) const isIterationComplete = children.length > 0 && children.every((_, idx) => completedChildren.has(idx)); const isLoopComplete = isIterationComplete && currentIteration >= iterations; return (
0 && "ml-4 mt-2 border-l pl-4 border-l-border/30" )} > {/* Visual Connection Line for Root items is handled by parent list, but for nested items we handle it via border-l above */}
{/* Header Row */}
{/* Icon based on type */} {isContainer && action.type.includes("loop") && } {isContainer && action.type.includes("parallel") && } {isBranch && } {isWait && }
{action.name}
{/* Completion Badge */} {isCompleted && }
{action.description && (
{action.description}
)} {/* Details for Control Flow */} {isWait && (
Wait {String(action.parameters.duration || 1)}s
)} {action.type.includes("loop") && (
{String(action.parameters.iterations || 1)} Iterations
)} {((!!isContainer && children.length > 0) ? (
{/* Loop Iteration Status & Controls */} {action.type.includes("loop") && (
Iteration {currentIteration} of {iterations} {isIterationComplete && currentIteration < iterations && ( All actions complete. Ready for next iteration. )} {isLoopComplete && ( Loop complete! )}
{isLoopComplete ? ( ) : ( isIterationComplete && currentIteration < iterations && !readOnly && (
) )}
)}
{action.type.includes("loop") ? "Loop Body" : "Actions"}
{children.map((child, idx) => ( handleChildCompleted(idx)} readOnly={readOnly || isCompleted || completedChildren.has(idx) || (action.type.includes("parallel") && true)} isExecuting={isExecuting} depth={depth + 1} isRobotConnected={isRobotConnected} /> ))}
) : null) as any} {/* Active Action Controls */} {isActive && !readOnly && (
{/* Parallel Container Controls */} {isContainer && action.type.includes("parallel") ? ( <> ) : ( /* Standard Single Action Controls */ (action.pluginId && !["hristudio-woz"].includes(action.pluginId!) && (action.pluginId !== "hristudio-core" || isWait)) ? ( <> ) : ( // Manual/Wizard Actions (Leaf nodes) !isContainer && action.type !== "wizard_wait_for_response" && ( ) ) )}
)} {/* Branching / Choice UI */} {isActive && (action.type === "wizard_wait_for_response" || isBranch) && action.parameters?.options && Array.isArray(action.parameters.options) && (
{(action.parameters.options as any[]).map((opt, optIdx) => { const label = typeof opt === "string" ? opt : opt.label; const value = typeof opt === "string" ? opt : opt.value; const nextStepId = typeof opt === "object" ? opt.nextStepId : undefined; return ( ); })}
)} {/* Retry for failed/completed robot actions */} {isCompleted && action.pluginId && !isContainer && (
)}
); }