import { useState, useCallback, useEffect } from "react"; import { Plus, Trash2, ChevronUp, ChevronDown, Pencil, GripVertical } from "lucide-react"; import type { AutomationStep, AutomationStepType } from "@fusion/core"; import { StepTypeBadge } from "./StepTypeBadge"; interface ScheduleStepsEditorProps { steps: AutomationStep[]; onChange: (steps: AutomationStep[]) => void; /** Called when editing state changes. Useful for parent form validation. */ onEditingChange?: (isEditing: boolean) => void; } function generateStepId(): string { // crypto.randomUUID() may be unavailable in non-secure contexts (HTTP), // older browsers, or some test environments. Fall back to a // cryptographically-acceptable alternative when needed. if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") { return crypto.randomUUID(); } // Deterministic fallback: timestamp + random hex return `step-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`; } function createEmptyStep(type: AutomationStepType): AutomationStep { return { id: generateStepId(), type, name: type === "command" ? "New Command Step" : "New AI Prompt Step", command: type === "command" ? "" : undefined, prompt: type === "ai-prompt" ? "" : undefined, continueOnFailure: false, }; } interface StepEditorProps { step: AutomationStep; onSave: (step: AutomationStep) => void; onCancel: () => void; } function StepEditor({ step, onSave, onCancel }: StepEditorProps) { const [name, setName] = useState(step.name); const [type, setType] = useState(step.type); const [command, setCommand] = useState(step.command ?? ""); const [prompt, setPrompt] = useState(step.prompt ?? ""); const [modelProvider, setModelProvider] = useState(step.modelProvider ?? ""); const [modelId, setModelId] = useState(step.modelId ?? ""); const [timeoutMs, setTimeoutMs] = useState(step.timeoutMs); const [continueOnFailure, setContinueOnFailure] = useState(step.continueOnFailure ?? false); const [errors, setErrors] = useState>({}); const validate = useCallback((): boolean => { const e: Record = {}; if (!name.trim()) e.name = "Step name is required"; if (type === "command" && !command.trim()) e.command = "Command is required"; if (type === "ai-prompt" && !prompt.trim()) e.prompt = "Prompt is required"; if (timeoutMs !== undefined && timeoutMs < 1000) { e.timeoutMs = "Timeout must be at least 1 second (1000ms)"; } if ((modelProvider && !modelId) || (!modelProvider && modelId)) { e.model = "Both model provider and model ID must be set, or both empty"; } setErrors(e); return Object.keys(e).length === 0; }, [name, type, command, prompt, timeoutMs, modelProvider, modelId]); const handleSave = useCallback(() => { if (!validate()) return; onSave({ ...step, name: name.trim(), type, command: type === "command" ? command.trim() : undefined, prompt: type === "ai-prompt" ? prompt.trim() : undefined, modelProvider: type === "ai-prompt" && modelProvider ? modelProvider.trim() : undefined, modelId: type === "ai-prompt" && modelId ? modelId.trim() : undefined, timeoutMs: timeoutMs || undefined, continueOnFailure, }); }, [validate, onSave, step, name, type, command, prompt, modelProvider, modelId, timeoutMs, continueOnFailure]); return (
setName(e.target.value)} aria-invalid={!!errors.name} /> {errors.name && {errors.name}}
{type === "command" && (