"use client" import { useEffect, useRef, useState, useTransition } from "react" import { useSpring, animated } from "@react-spring/web" import { usePathname, useRouter, useSearchParams } from "next/navigation" import { cn } from "@/lib/utils" import { headingFont } from "@/app/interface/fonts" import { useCharacterLimit } from "@/lib/useCharacterLimit" import { generateAnimation } from "@/app/server/actions/animation" import { getLatestPosts, getPost, postToCommunity } from "@/app/server/actions/community" import { useCountdown } from "@/lib/useCountdown" import { Countdown } from "../countdown" import { getSDXLModels } from "@/app/server/actions/models" import { Post, SDXLModel } from "@/types" import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip" import { TooltipProvider } from "@radix-ui/react-tooltip" export function Generate() { const router = useRouter() const pathname = usePathname() const searchParams = useSearchParams() const [_isPending, startTransition] = useTransition() const scrollRef = useRef(null) const [isLocked, setLocked] = useState(false) const [promptDraft, setPromptDraft] = useState("") const [assetUrl, setAssetUrl] = useState("") const [isOverSubmitButton, setOverSubmitButton] = useState(false) const [models, setModels] = useState([]) const [selectedModel, setSelectedModel] = useState() const [runs, setRuns] = useState(0) const runsRef = useRef(0) const [showModels, setShowModels] = useState(true) // useEffect(() => { runsRef.current = runs }, [runs]) const [communityRoll, setCommunityRoll] = useState([]) console.log("runs:", runs) const { progressPercent, remainingTimeInSec } = useCountdown({ isActive: isLocked, timerId: runs, // everytime we change this, the timer will reset durationInSec: 50, // it usually takes 40 seconds, but there might be lag onEnd: () => {} }) const { shouldWarn, colorClass, nbCharsUsed, nbCharsLimits } = useCharacterLimit({ value: promptDraft, nbCharsLimits: 70, warnBelow: 10, }) const submitButtonBouncer = useSpring({ transform: isOverSubmitButton ? 'scale(1.05)' : 'scale(1.0)', boxShadow: isOverSubmitButton ? `0px 5px 15px 0px rgba(0, 0, 0, 0.05)` : `0px 0px 0px 0px rgba(0, 0, 0, 0.05)`, loop: true, config: { tension: 300, friction: 10, }, }) const handleSubmit = () => { console.log("handleSubmit:", { isLocked, promptDraft }) if (isLocked) { return } if (!promptDraft) { return } setShowModels(false) setRuns(runs + 1) setLocked(true) scrollRef.current?.scroll({ top: 0, behavior: 'smooth' }) startTransition(async () => { const huggingFaceLora = selectedModel ? selectedModel.repo.trim() : "KappaNeuro/studio-ghibli-style" const triggerWord = selectedModel ? selectedModel.trigger_word : "Studio Ghibli Style" // now you got a read/write object const current = new URLSearchParams(Array.from(searchParams.entries())) current.set("prompt", promptDraft) current.set("model", huggingFaceLora) const search = current.toString() router.push(`${pathname}${search ? `?${search}` : ""}`) try { console.log("starting transition, calling generateAnimation") const newAssetUrl = await generateAnimation({ positivePrompt: promptDraft, negativePrompt: "", huggingFaceLora, triggerWord, size: "608x416", // "1024x512", // "512x512" // "320x768" nbFrames: 8, // if duration is 1000ms then it means 8 FPS duration: 1000, // in ms steps: 25, }) setAssetUrl(newAssetUrl) try { const post = await postToCommunity({ prompt: promptDraft, model: huggingFaceLora, assetUrl: newAssetUrl, }) console.log("successfully submitted to the community!", post) // now you got a read/write object const current = new URLSearchParams(Array.from(searchParams.entries())) current.set("postId", post.postId.trim()) current.set("prompt", post.prompt.trim()) current.set("model", post.model.trim()) const search = current.toString() router.push(`${pathname}${search ? `?${search}` : ""}`) } catch (err) { console.error(`not a blocker, but we failed to post to the community (reason: ${err})`) } } catch (err) { console.error(err) } finally { setLocked(false) } }) } useEffect(() => { startTransition(async () => { const models = await getSDXLModels() setModels(models) const defaultModel = models.find(model => model.title.toLowerCase().includes("ghibli")) || models[0] if (defaultModel) { setSelectedModel(defaultModel) } // now we load URL params const current = new URLSearchParams(Array.from(searchParams.entries())) // URL query params const existingPostId = current.get("postId") || "" const existingPrompt = current.get("prompt")?.trim() || "" const existingModelName = current.get("model")?.toLowerCase().trim() || "" // if and only if we don't have a post id, then we look at the other query params if (existingPrompt) { setPromptDraft(existingPrompt) } if (existingModelName) { let existingModel = models.find(model => model.title.toLowerCase().trim().includes(existingModelName)) if (existingModel) { setSelectedModel(existingModel) } } // if we have a post id, then we use that to override all the previous values if (existingPostId) { try { const post = await getPost(existingPostId) if (post.assetUrl) { setAssetUrl(post.assetUrl) } if (post.prompt) { setPromptDraft(post.prompt) } if (post.model) { const existingModel = models.find(model => model.title.toLowerCase().trim().includes(post.model.toLowerCase().trim())) if (existingModel) { setSelectedModel(existingModel) } } } catch (err) { console.error(`failed to load the community post (${err})`) } } }) }, []) useEffect(() => { startTransition(async () => { const posts = await getLatestPosts({ maxNbPosts: 16 }) if (posts?.length) { setCommunityRoll(posts) } }) }, []) const handleSelectCommunityPost = (post: Post) => { if (isLocked) { return } scrollRef.current?.scroll({ top: 0, behavior: 'smooth' }) // now you got a read/write object const current = new URLSearchParams(Array.from(searchParams.entries())) current.set("postId", post.postId.trim()) current.set("prompt", post.prompt.trim()) current.set("model", post.model.trim()) const search = current.toString() router.push(`${pathname}${search ? `?${search}` : ""}`) if (post.assetUrl) { setAssetUrl(post.assetUrl) } if (post.prompt) { setPromptDraft(post.prompt) } if (post.model) { const existingModel = models.find(model => model.title.toLowerCase().trim().includes(post.model.toLowerCase().trim())) if (existingModel) { setSelectedModel(existingModel) } } } return (
{isLocked ? : null}
{assetUrl ?
{assetUrl.startsWith("data:video/mp4") ?
: null}
setPromptDraft(e.target.value)} onKeyDown={({ key }) => { if (key === 'Enter') { if (!isLocked) { handleSubmit() } } }} disabled={isLocked} />
{nbCharsUsed} / {nbCharsLimits}
setOverSubmitButton(true)} onMouseLeave={() => setOverSubmitButton(false)} className={cn( `px-6 py-3`, `rounded-full`, `transition-all duration-300 ease-in-out`, isLocked ? `bg-orange-500/20 border-orange-800/10` : `bg-sky-500/80 hover:bg-sky-400/100 border-sky-800/20`, `text-center`, `w-full`, `text-2xl text-sky-50`, `border`, headingFont.className, // `transition-all duration-300`, // `hover:animate-bounce` )} disabled={isLocked} onClick={handleSubmit} > {isLocked ? `Loading..` : "Generate"}

{models.length ? "Pick a style:" : "Loading styles.."}

{models.map(model =>
{ if (!isLocked) { setSelectedModel(model) } }}>
{!isLocked &&

{model.title}

}
)}

{communityRoll.length ? "Community creations:" : "Loading community roll.."}

{communityRoll.map(post =>
{ handleSelectCommunityPost(post) }}>
{!isLocked &&

{post.prompt}

}
)}
) }