132 lines
4.5 KiB
TypeScript
132 lines
4.5 KiB
TypeScript
"use client";
|
|
import { useEffect, useRef, useState } from "react";
|
|
import Link from "next/link";
|
|
|
|
type Phase = "idle" | "submitting" | "generating" | "review" | "ready" | "failed";
|
|
|
|
export default function CreateForm({ level: initialLevel, enabled }: { level: string; enabled: boolean }) {
|
|
const [level, setLevel] = useState(initialLevel);
|
|
const [subject, setSubject] = useState("");
|
|
const [phase, setPhase] = useState<Phase>("idle");
|
|
const [msg, setMsg] = useState("");
|
|
const [slug, setSlug] = useState("");
|
|
const poll = useRef<ReturnType<typeof setInterval> | undefined>(undefined);
|
|
|
|
useEffect(() => () => clearInterval(poll.current), []);
|
|
|
|
async function start(e: React.FormEvent) {
|
|
e.preventDefault();
|
|
setMsg("");
|
|
setSlug("");
|
|
setPhase("submitting");
|
|
try {
|
|
const res = await fetch("/api/create", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ level, subject }),
|
|
});
|
|
const data = await res.json();
|
|
if (!res.ok) {
|
|
setPhase("failed");
|
|
setMsg(data.error || "Something went wrong.");
|
|
return;
|
|
}
|
|
if (data.status === "needs_review") {
|
|
setPhase("review");
|
|
return;
|
|
}
|
|
setSlug(data.slug);
|
|
setPhase("generating");
|
|
watch(data.id, data.slug);
|
|
} catch {
|
|
setPhase("failed");
|
|
setMsg("Couldn't reach the server.");
|
|
}
|
|
}
|
|
|
|
function watch(id: number, baseSlug: string) {
|
|
clearInterval(poll.current);
|
|
poll.current = setInterval(async () => {
|
|
try {
|
|
const r = await fetch(`/api/create/${id}`);
|
|
const d = await r.json();
|
|
if (d.status === "ready") {
|
|
clearInterval(poll.current);
|
|
setSlug(baseSlug);
|
|
setPhase("ready");
|
|
} else if (d.status === "failed" || d.status === "blocked") {
|
|
clearInterval(poll.current);
|
|
setPhase("failed");
|
|
setMsg(d.error || "The drawing couldn't be made. Try a different subject.");
|
|
}
|
|
} catch {
|
|
/* keep polling */
|
|
}
|
|
}, 2500);
|
|
}
|
|
|
|
if (!enabled) {
|
|
return (
|
|
<div className="card" style={{ marginTop: 16 }}>
|
|
<p className="muted" style={{ margin: 0 }}>
|
|
The Create feature isn't turned on for this DrawIt instance yet. Ask your administrator to configure a
|
|
drawing server (ComfyUI).
|
|
</p>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const busy = phase === "submitting" || phase === "generating";
|
|
|
|
return (
|
|
<div style={{ marginTop: 16, maxWidth: 560 }}>
|
|
<form onSubmit={start} className="card" style={{ display: "grid", gap: 12 }}>
|
|
<label className="field">
|
|
<span style={{ fontWeight: 700 }}>What do you want to draw?</span>
|
|
<input
|
|
value={subject}
|
|
onChange={(e) => setSubject(e.target.value)}
|
|
placeholder="e.g. Zebra"
|
|
maxLength={30}
|
|
disabled={busy}
|
|
required
|
|
/>
|
|
</label>
|
|
<label className="field">
|
|
<span style={{ fontWeight: 700 }}>Level</span>
|
|
<select value={level} onChange={(e) => setLevel(e.target.value)} disabled={busy}>
|
|
<option value="early-beginner">Early Beginner</option>
|
|
<option value="beginner">Beginner</option>
|
|
</select>
|
|
</label>
|
|
<button className="btn big" type="submit" disabled={busy || subject.trim().length < 2}>
|
|
{busy ? "Drawing…" : "✨ Make my lesson"}
|
|
</button>
|
|
</form>
|
|
|
|
{phase === "generating" && (
|
|
<div className="card" style={{ marginTop: 12, textAlign: "center" }}>
|
|
<p style={{ margin: 0 }}>🎨 Drawing your {subject || "picture"}… this can take a little while.</p>
|
|
<p className="muted" style={{ marginTop: 6, fontSize: "0.9rem" }}>You can stay on this page.</p>
|
|
</div>
|
|
)}
|
|
{phase === "review" && (
|
|
<div className="card" style={{ marginTop: 12 }}>
|
|
<p style={{ margin: 0 }}>📨 Thanks! "{subject}" was sent to a grown-up to approve. Check back soon.</p>
|
|
</div>
|
|
)}
|
|
{phase === "ready" && (
|
|
<div className="card" style={{ marginTop: 12, textAlign: "center" }}>
|
|
<p style={{ marginTop: 0 }}>🎉 Your lesson is ready!</p>
|
|
<Link className="btn big" href={`/learn/${level}/${slug}-trace`}>Start drawing</Link>
|
|
</div>
|
|
)}
|
|
{phase === "failed" && (
|
|
<div className="notice" style={{ marginTop: 12 }}>
|
|
<p style={{ margin: 0 }}>{msg || "Something went wrong. Try a different subject."}</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|