import { useState, useEffect } from "react"; import { useApp } from "../state/store"; import { wizardApi, type BatchOperation } from "../lib/wizardApi"; import { Branch, Check, Close, ChevronRight } from "../components/icons"; type WizardStep = "Intake" | "Analyze" | "Generate" | "Validate" | "Draft saved" | "Publish"; const STEPS: WizardStep[] = ["Intake", "Analyze", "Generate", "Validate", "Draft saved", "Publish"]; interface DraftState { step: WizardStep; flowKey?: string; name: string; description: string; nodes: any[]; edges: any[]; fields: { name: string; type: string }[]; rules: string[]; } export default function Wizard() { const mode = useApp((s) => s.mode); const pushToast = useApp((s) => s.pushToast); const refreshLive = useApp((s) => s.refreshLive); const actor = useApp((s) => s.actor); const setScene = useApp((s) => s.setScene); const canPublish = mode === "live" && !!actor?.user_id; const [draft, setDraft] = useState(() => { try { const stored = localStorage.getItem("fm.mc.wizard.draft"); if (stored) return JSON.parse(stored); } catch {} return { step: "Intake", name: "", description: "", nodes: [], edges: [], fields: [], rules: [] }; }); const [working, setWorking] = useState(false); const [publishedKey, setPublishedKey] = useState(null); useEffect(() => { localStorage.setItem("fm.mc.wizard.draft", JSON.stringify(draft)); }, [draft]); const updateDraft = (patch: Partial) => { setDraft(d => ({ ...d, ...patch })); }; const handleIntakeSubmit = async () => { if (!draft.description) return; setWorking(true); try { const slug = `process_${Date.now()}`; const res = await wizardApi.createDraft({ name: slug, display_name: draft.name || "Untitled Process", description: draft.description, source_context: "EA2_DRAFT_PROCESS:process_creation", config: { wizard: { marker: "EA2_DRAFT_PROCESS", maxDepth: 5, maxNodes: 40, chat: [], uploads: [], panelState: {}, debug: [] } } }); // Auto-generate basic 5-step template const templateNodes = [ { _key: "n1", kind: "step", display_name: "Capture request details", dispatch_kind: "human" }, { _key: "n2", kind: "step", display_name: "Validate requirements", dispatch_kind: "agent", agent_capability: "evaluate_business_rule" }, { _key: "n3", kind: "step", display_name: "Prepare work package", dispatch_kind: "agent", agent_capability: "process_design_assistant" }, { _key: "n4", kind: "step", display_name: "Review and approve", dispatch_kind: "human" }, { _key: "n5", kind: "step", display_name: "Close out process", dispatch_kind: "human" } ]; updateDraft({ flowKey: res._key, step: "Analyze", nodes: templateNodes }); pushToast("ok", "Draft created in EA2"); } catch (err: any) { pushToast("err", `Failed to create draft: ${err.message}`); } finally { setWorking(false); } }; const handleStructureSave = async () => { if (!draft.flowKey || !actor) return; setWorking(true); try { const stepCreateOps: BatchOperation[] = draft.nodes.map(n => wizardApi.ops.createStep({ display_name: n.display_name, dispatch_kind: n.dispatch_kind, agent_capability: n.agent_capability, }) ); const viewCreateOps: BatchOperation[] = draft.nodes.map(n => wizardApi.ops.createView({ display_name: n.display_name }) ); const versionCreateOp: BatchOperation = wizardApi.ops.createVersion(draft.name || "Process"); const allCreateOps = [...stepCreateOps, ...viewCreateOps, versionCreateOp]; const createRes = await wizardApi.applyBatch(draft.flowKey, allCreateOps, actor); const createdOps: any[] = createRes?.result?.ops || []; const stepKeys = createdOps.slice(0, draft.nodes.length).map(o => o.key); const viewKeys = createdOps.slice(draft.nodes.length, draft.nodes.length * 2).map(o => o.key); const versionKey = createdOps[createdOps.length - 1]?.key; const nodesWithKeys = draft.nodes.map((n, i) => ({ ...n, _key: stepKeys[i] || n._key, viewKey: viewKeys[i] })); const wireOps: BatchOperation[] = [ ...nodesWithKeys.map((n, _i) => wizardApi.ops.childEdge(draft.flowKey!, n._key)), ...nodesWithKeys.map(n => wizardApi.ops.dispatchEdge(n._key)), ...nodesWithKeys.map(n => wizardApi.ops.presentationEdge(n._key, n.viewKey)), ]; if (versionKey) wireOps.push(wizardApi.ops.governsEdge(versionKey, draft.flowKey!)); if (wireOps.length) await wizardApi.applyBatch(draft.flowKey, wireOps, actor); updateDraft({ step: "Generate", nodes: nodesWithKeys }); pushToast("ok", `Saved ${stepKeys.length} steps with views and version to EA2`); } catch (err: any) { pushToast("err", `Save failed: ${err.message}`); } finally { setWorking(false); } }; const handleDataSave = async () => { if (!draft.flowKey || !actor) return; setWorking(true); try { await wizardApi.updateDraftConfig(draft.flowKey, { config: { wizard: { panelState: { fields: draft.fields } } } }); if (draft.fields.length > 0 && draft.nodes.length > 0) { const replaceOps: BatchOperation[] = draft.nodes.map(n => wizardApi.ops.createView({ display_name: n.display_name, fields: draft.fields }) ); const res = await wizardApi.applyBatch(draft.flowKey, replaceOps, actor); const newViewKeys = (res?.result?.ops || []).map((o: any) => o.key); const wireOps: BatchOperation[] = draft.nodes.map((n, i) => wizardApi.ops.presentationEdge(n._key, newViewKeys[i]) ); if (wireOps.length) await wizardApi.applyBatch(draft.flowKey, wireOps, actor); updateDraft({ nodes: draft.nodes.map((n, i) => ({ ...n, viewKey: newViewKeys[i] })) }); } updateDraft({ step: "Validate" }); pushToast("ok", `Saved ${draft.fields.length} fields onto every step's form`); } catch (err: any) { pushToast("err", `Save failed: ${err.message}`); } finally { setWorking(false); } }; const handleRulesSave = async () => { if (!draft.flowKey || !actor) return; setWorking(true); try { await wizardApi.updateDraftConfig(draft.flowKey, { config: { wizard: { panelState: { rules: draft.rules } } } }); updateDraft({ step: "Draft saved" }); pushToast("ok", "Rules saved"); } catch (err: any) { pushToast("err", `Save failed: ${err.message}`); } finally { setWorking(false); } }; const handlePublish = async () => { if (!draft.flowKey || !actor) return; setWorking(true); try { const ops: BatchOperation[] = [wizardApi.ops.publishFlow(draft.flowKey)]; await wizardApi.applyBatch(draft.flowKey, ops, actor); updateDraft({ step: "Publish" }); setPublishedKey(draft.flowKey); pushToast("ok", "Process published successfully!"); refreshLive(); } catch (err: any) { pushToast("err", `Publish failed: ${err.message}`); } finally { setWorking(false); } }; const handleStartInstance = async () => { if(!publishedKey) return; setWorking(true); try { await wizardApi.startInstance(publishedKey); pushToast("ok", "New instance started!"); setScene("mission"); } catch(err:any) { pushToast("err", `Failed to start: ${err.message}`); } finally { setWorking(false); } } const renderStepNav = () => (
{STEPS.map((step, i) => { const isActive = step === draft.step; const isPast = STEPS.indexOf(step) < STEPS.indexOf(draft.step); return (
{i + 1} {step} {i < STEPS.length - 1 && }
); })}
); return (
Process Creation Wizard

Build a new process

{renderStepNav()}
{draft.step === "Intake" && (

What process do you want to build?

updateDraft({ name: e.target.value })} placeholder="e.g. Laptop Procurement" />