File size: 5,315 Bytes
921d377 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 | /**
* InteractiveHost — wizard / editor shell for one interactive project.
*
* Mirrors CreatorStudioHost's two-mode contract:
* - `mode="wizard"` when creating a new project
* - `mode="editor"` when opening an existing project id
*
* The host owns the transition: when the wizard finishes it hands
* the new experience id to `onCreated`, which App.tsx uses to
* swap the host from wizard mode to editor mode without tearing
* down the parent tab.
*
* Both sub-views share the same page chrome — dark background,
* back button top-left, toast provider mounted once at the root so
* every panel can surface success/error toasts uniformly.
*/
import React, { useState } from "react";
import { ArrowLeft } from "lucide-react";
import { InteractiveEditor } from "./InteractiveEditor";
import { InteractiveWizard } from "./interactive/Wizard";
import { WizardAuto, type AutoInteractionSelection } from "./interactive/WizardAuto";
import { WizardAutoPreview } from "./interactive/WizardAutoPreview";
import type { PlanAutoResult } from "./interactive/types";
import { SecondaryButton, ToastProvider } from "./interactive/ui";
export interface InteractiveHostProps {
backendUrl: string;
apiKey?: string;
/** When set, host renders the editor for this experience id. */
projectId?: string | null;
/** Called when the user leaves the host (back to landing grid). */
onExit: () => void;
/** Called when the wizard creates a new project. */
onCreated: (experienceId: string) => void;
/**
* Optional: called when the user clicks "Play" inside the editor.
* App.tsx hands this up to the same ``setInteractivePlayId`` path
* the landing grid's Play CTA uses, so authoring + viewing share
* one code path.
*/
onPlay?: (experienceId: string) => void;
}
/**
* Wizard stages the host walks through when no project is open.
*
* auto One-box WizardAuto (default). AI drafts the project
* from a single sentence.
* preview Editable AI draft. User tweaks fields and creates.
* advanced Classic 5-step InteractiveWizard (power-user escape
* hatch; all original behaviour preserved).
*/
type WizardStage = "auto" | "preview" | "advanced";
export function InteractiveHost({
backendUrl, apiKey, projectId, onExit, onCreated, onPlay,
}: InteractiveHostProps) {
const [stage, setStage] = useState<WizardStage>("auto");
const [planResult, setPlanResult] = useState<PlanAutoResult | null>(null);
const [originalIdea, setOriginalIdea] = useState<string>("");
const [interaction, setInteraction] = useState<AutoInteractionSelection>({
interaction_type: "standard_project",
});
// Enterprise shell: full viewport height, three stacked rows.
// [0] TopBar — fixed 'Cancel / Back to projects' button.
// [1] Body — wizard or editor, filling the remaining space.
// Each mode manages its own internal scroll.
// Nothing scrolls at the page level; layout is owned by the
// sub-views so Back / Next never drift offscreen.
return (
<ToastProvider>
<div className="flex flex-col h-screen bg-[#0f0f0f] text-[#f1f1f1]">
<TopBar onExit={onExit} label={projectId ? "Back to projects" : "Cancel"} />
<div className="flex-1 min-h-0 flex flex-col">
{projectId ? (
<InteractiveEditor
backendUrl={backendUrl}
apiKey={apiKey}
projectId={projectId}
onPlay={onPlay ? () => onPlay(projectId) : undefined}
/>
) : stage === "advanced" ? (
// Classic 5-step wizard stays intact for power users.
// Nothing inside it was touched by the AUTO-* batches.
<InteractiveWizard
backendUrl={backendUrl}
apiKey={apiKey}
onCreated={onCreated}
onCancel={onExit}
/>
) : stage === "preview" && planResult ? (
<WizardAutoPreview
backendUrl={backendUrl}
apiKey={apiKey}
initial={planResult}
originalIdea={originalIdea}
interaction={interaction}
onCreated={onCreated}
onStartOver={() => {
setPlanResult(null);
setStage("auto");
}}
/>
) : (
<WizardAuto
backendUrl={backendUrl}
apiKey={apiKey}
onPlanned={(result, idea, selection) => {
setPlanResult(result);
setOriginalIdea(idea);
setInteraction(selection);
setStage("preview");
}}
onSwitchToAdvanced={() => setStage("advanced")}
onCancel={onExit}
/>
)}
</div>
</div>
</ToastProvider>
);
}
function TopBar({ onExit, label }: { onExit: () => void; label: string }) {
return (
<div className="shrink-0 bg-[#0f0f0f] border-b border-[#3f3f3f]">
<div className="max-w-6xl mx-auto px-6 py-3">
<SecondaryButton
onClick={onExit}
size="sm"
icon={<ArrowLeft className="w-3.5 h-3.5" aria-hidden />}
aria-label={label}
>
{label}
</SecondaryButton>
</div>
</div>
);
}
|