/** * Step 4 — Review summary + planner preview. * * Renders a tidy summary of every prior step and asks the planner * to "preview" the resolved Intent so authors can sanity-check * topic / objective / scheme choices before they hit Create. * * The preview call is a non-mutating GET-equivalent (POST /plan * has no DB side-effects) so it's safe to re-run on retry. */ import React, { useEffect, useMemo, useState } from "react"; import { Loader2 } from "lucide-react"; import type { InteractiveApi } from "../api"; import { InteractiveApiError } from "../types"; import type { PlanIntent } from "../types"; import type { WizardForm } from "../wizardState"; import { toPlanPayload } from "../wizardState"; export interface Step4Props { form: WizardForm; api: InteractiveApi; } export function Step4Review({ form, api }: Step4Props) { const [preview, setPreview] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const payload = useMemo(() => toPlanPayload(form), [form]); useEffect(() => { if (form.interaction_type === "persona_live_play") { setLoading(false); setError(null); setPreview({ prompt: form.prompt, mode: form.experience_mode, objective: "", topic: "", branch_count: 0, depth: 0, scenes_per_branch: 0, success_metric: "", seed_intents: ["tease", "dance", "outfit_change"], scheme: "affinity_tier", audience: { role: form.audience_role, level: form.audience_level, language: form.audience_language, locale_hint: form.audience_locale_hint, interests: [], }, raw_hints: {}, }); return; } const ctrl = new AbortController(); let cancelled = false; setLoading(true); setError(null); api.plan(payload) .then((intent) => { if (!cancelled) setPreview(intent); }) .catch((err: Error) => { if (cancelled || err.name === "AbortError") return; const apiErr = err as InteractiveApiError; setError(apiErr.message || "Couldn't preview plan."); }) .finally(() => { if (!cancelled) setLoading(false); }); return () => { cancelled = true; ctrl.abort(); }; }, [api, payload]); return (
); } function SummaryCard({ form }: { form: WizardForm }) { const personaLive = form.interaction_type === "persona_live_play"; return (
{!personaLive && } {!personaLive && } {!personaLive && }
); } function PreviewCard({ loading, error, intent, personaLive, }: { loading: boolean; error: string | null; intent: PlanIntent | null; personaLive?: boolean; }) { const persona = !!personaLive; return (
{persona ? "Live session preview" : "Planner preview"}
{loading && (
{persona ? "Preparing persona progression preview…" : "Asking the planner to resolve your prompt…"}
)} {error && (
{error}
)} {!loading && !error && intent && (
{!persona && } {!persona && } {!persona && } {!persona && } {!persona && } {!persona && }
)}
); } function Row({ label, value, multiline }: { label: string; value: string; multiline?: boolean }) { return (
{label}
{value || (empty)}
); } function DLRow({ term, value }: { term: string; value: string }) { return (
{term}
{value}
); }