import { useCallback, useEffect, useState } from "react"; import { Workflow, Plus, Square, RefreshCw, ChevronDown, ChevronRight, Loader2, AlertTriangle, Info, } from "lucide-react"; import { cn } from "@/lib/utils"; import { useFlows, type FlowSummary } from "@/hooks/use-flows"; import { useSocket } from "@/providers/socket-provider"; import { useNotification } from "@/providers/notification-provider"; import { Dialog } from "@/components/ui/dialog"; import { Badge } from "@/components/ui/badge"; // --------------------------------------------------------------------------- // Start flow dialog // --------------------------------------------------------------------------- function StartFlowDialog({ open, onClose, onStart, }: { open: boolean; onClose: () => void; onStart: ( id: string, blueprint: string, description: string, params: Record, ) => Promise; }) { const socket = useSocket(); const [blueprints, setBlueprints] = useState([]); const [loadingBlueprints, setLoadingBlueprints] = useState(false); const [id, setId] = useState(""); const [blueprint, setBlueprint] = useState(""); const [description, setDescription] = useState(""); const [paramsJson, setParamsJson] = useState("{}"); const [submitting, setSubmitting] = useState(false); const [paramsError, setParamsError] = useState(null); const [submitted, setSubmitted] = useState(false); const [blueprintDef, setBlueprintDef] = useState | null>(null); const [loadingDef, setLoadingDef] = useState(false); const [defExpanded, setDefExpanded] = useState(false); // Fetch blueprints when dialog opens useEffect(() => { if (!open) return; setLoadingBlueprints(true); socket .flows() .getFlowBlueprints() .then((names) => { const list = names ?? []; setBlueprints(list); if (list.length > 0 && !blueprint) { setBlueprint(list[0]!); } }) .catch(() => setBlueprints([])) .finally(() => setLoadingBlueprints(false)); // eslint-disable-next-line react-hooks/exhaustive-deps }, [open, socket]); // Fetch blueprint definition when selection changes useEffect(() => { if (!blueprint) { setBlueprintDef(null); return; } let cancelled = false; setLoadingDef(true); setBlueprintDef(null); socket .flows() .getFlowBlueprint(blueprint) .then((def) => { if (cancelled) return; setBlueprintDef(def); // Pre-populate parameters with defaults from the definition const paramsDef = def?.parameters ?? def?.params ?? def?.["parameters"] ?? def?.["params"]; if (paramsDef && typeof paramsDef === "object") { const defaults: Record = {}; const params = paramsDef as Record; for (const [key, val] of Object.entries(params)) { if (val && typeof val === "object" && "default" in (val as Record)) { defaults[key] = (val as Record).default; } } if (Object.keys(defaults).length > 0) { setParamsJson(JSON.stringify(defaults, null, 2)); } } }) .catch(() => { if (!cancelled) setBlueprintDef(null); }) .finally(() => { if (!cancelled) setLoadingDef(false); }); return () => { cancelled = true; }; }, [blueprint, socket]); const reset = () => { setId(""); setBlueprint(""); setDescription(""); setParamsJson("{}"); setParamsError(null); setSubmitting(false); setSubmitted(false); setBlueprintDef(null); setLoadingDef(false); setDefExpanded(false); }; const handleSubmit = async () => { setSubmitted(true); if (!isValid) return; let params: Record = {}; try { params = JSON.parse(paramsJson); setParamsError(null); } catch { setParamsError("Invalid JSON"); return; } setSubmitting(true); try { await onStart(id, blueprint, description, params); reset(); onClose(); } catch { setSubmitting(false); } }; const isValid = id.trim().length > 0 && blueprint.length > 0 && description.trim().length > 0; return ( { if (!submitting) { reset(); onClose(); } }} title="Start Flow" footer={ <> } > {/* Flow ID */}
setId(e.target.value)} placeholder="my-flow-id" className="w-full rounded-lg border border-border bg-surface-100 px-3 py-2 text-sm text-fg placeholder:text-fg-subtle focus:border-brand-500 focus:outline-none focus:ring-1 focus:ring-brand-500" /> {submitted && !id.trim() && (

Flow ID is required

)}
{/* Blueprint name */}
{loadingBlueprints ? (
Loading blueprints...
) : ( )} {submitted && !blueprint && (

Blueprint is required

)} {/* Blueprint details info section */} {loadingDef && (
Loading blueprint details...
)} {blueprintDef && !loadingDef && (
Blueprint Details
{/* Description from definition */} {!!(blueprintDef.description || blueprintDef.desc) && (

{String(blueprintDef.description ?? blueprintDef.desc)}

)} {/* Parameters schema */} {(() => { const paramsDef = blueprintDef.parameters ?? blueprintDef.params ?? blueprintDef["parameters"] ?? blueprintDef["params"]; if (!paramsDef || typeof paramsDef !== "object") return null; const entries = Object.entries(paramsDef as Record); if (entries.length === 0) return null; return (

Parameters

{entries.map(([name, schema]) => { const s = schema as Record | null; const type = s?.type ? String(s.type) : undefined; const defaultVal = s && "default" in s ? s.default : undefined; const desc = s?.description ? String(s.description) : undefined; return (
{name} {type && ( {type} )} {defaultVal !== undefined && ( default: {JSON.stringify(defaultVal)} )} {desc && - {desc}}
); })}
); })()} {/* Raw JSON toggle */} {defExpanded && (
                {JSON.stringify(blueprintDef, null, 2)}
              
)}
)}
{/* Description */}
setDescription(e.target.value)} placeholder="Human-readable description" className="w-full rounded-lg border border-border bg-surface-100 px-3 py-2 text-sm text-fg placeholder:text-fg-subtle focus:border-brand-500 focus:outline-none focus:ring-1 focus:ring-brand-500" /> {submitted && !description.trim() && (

Description is required

)}
{/* Parameters (JSON) */}