"use client" import { useEffect, useRef, useState, useTransition } from "react" // import AutoSizer from "react-virtualized-auto-sizer" import { RenderedScene } from "@/types" import { getRender, newRender } from "@/app/engine/render" import { useStore } from "@/app/store" import { cn } from "@/lib/utils" import { getInitialRenderedScene } from "@/lib/getInitialRenderedScene" import { Progress } from "@/app/interface/progress" import { see } from "@/app/engine/caption" import { writeIntoBubble } from "@/lib/writeIntoBubble" // import { Bubble } from "./bubble" export function Panel({ panel, className = "", width = 1, height = 1, }: { panel: number className?: string width?: number height?: number }) { const font = useStore(state => state.font) const preset = useStore(state => state.preset) const setGeneratingImages = useStore(state => state.setGeneratingImages) const panels = useStore(state => state.panels) const prompt = panels[panel] || "" const zoomLevel = useStore(state => state.zoomLevel) // const setCaption = useStore(state => state.setCaption) // const captions = useStore(state => state.captions) // const caption = captions[panel] || "" const [_isPending, startTransition] = useTransition() const [rendered, setRendered] = useState(getInitialRenderedScene()) const renderedRef = useRef() const timeoutRef = useRef(null) const delay = 3000 + (1000 * panel) // since this run in its own loop, we need to use references everywhere // but perhaps this could be refactored useEffect(() => { // console.log("Panel prompt: "+ prompt) if (!prompt?.length) { return } // important: update the status, and clear the scene setGeneratingImages(panel, true) setRendered(getInitialRenderedScene()) setTimeout(() => { startTransition(async () => { console.log(`Loading panel ${panel}..`) let newRendered = await newRender({ prompt, width, height }) try { newRendered = await newRender({ prompt, width, height }) } catch (err) { console.log("Failed to load the panel! Don't worry, we are retrying..") newRendered = await newRender({ prompt, width, height }) } if (newRendered) { // console.log("newRendered:", newRendered) setRendered(renderedRef.current = newRendered) // but we are still loading! } else { setRendered(renderedRef.current = { renderId: "", status: "error", assetUrl: "", alt: "", maskUrl: "", error: "failed to fetch the data", segments: [] }) setGeneratingImages(panel, false) return } }) }, 2000 * panel) }, [prompt, width, height]) const checkStatus = () => { startTransition(async () => { clearTimeout(timeoutRef.current) if (!renderedRef.current?.renderId || renderedRef.current?.status !== "pending") { timeoutRef.current = setTimeout(checkStatus, delay) return } try { setGeneratingImages(panel, true) // console.log(`Checking job status API for job ${renderedRef.current?.renderId}`) const newRendered = await getRender(renderedRef.current.renderId) // console.log("got a response!", newRendered) if (JSON.stringify(renderedRef.current) !== JSON.stringify(newRendered)) { console.log("updated panel:", newRendered) setRendered(renderedRef.current = newRendered) setGeneratingImages(panel, true) } // console.log("status:", newRendered.status) if (newRendered.status === "pending") { // console.log("job not finished") timeoutRef.current = setTimeout(checkStatus, delay) } else if (newRendered.status === "error") { console.log(`panel got an error :/ "${newRendered.error}", but let's try to recover..`) try { const newAttempt = await newRender({ prompt, width, height }) setRendered(renderedRef.current = newAttempt) } catch (err) { console.error("yeah sorry, something is wrong.. aborting") setGeneratingImages(panel, false) } } else { console.log("panel finished!") setGeneratingImages(panel, false) } } catch (err) { console.error(err) timeoutRef.current = setTimeout(checkStatus, delay) } }) } useEffect(() => { // console.log("starting timeout") clearTimeout(timeoutRef.current) // normally it should reply in < 1sec, but we could also use an interval timeoutRef.current = setTimeout(checkStatus, delay) return () => { clearTimeout(timeoutRef.current) } }, [prompt, width, height]) /* doing the captionning from the browser is expensive a simpler solution is to caption directly during SDXL generation useEffect(() => { if (!rendered.assetUrl) { return } // the asset url can evolve with time (link to a better resolution image) // however it would be costly to ask for the caption, the low resolution is enough for the semantic resolution // so we just do nothing if we already have the caption if (caption) { return } startTransition(async () => { try { const newCaption = await see({ prompt: "please caption the following image", imageBase64: rendered.assetUrl }) if (newCaption) { setCaption(newCaption) } } catch (err) { console.error(`failed to generate the caption:`, err) } }) }, [rendered.assetUrl, caption]) */ const frameClassName = cn( `w-full h-full`, `border-stone-800`, `transition-all duration-200 ease-in-out`, zoomLevel > 140 ? `border-[2px] md:border-[4px] rounded-sm md:rounded-md` : zoomLevel > 120 ? `border-[1.5px] md:border-[3px] rounded-xs md:rounded-sm` : zoomLevel > 90 ? `border-[1px] md:border-[2px] rounded-xs md:rounded-sm` : zoomLevel > 40 ? `border-[0.5px] md:border-[1px] rounded-none md:rounded-xs` : `border-transparent md:border-[0.5px] rounded-none md:rounded-none`, `shadow-sm`, `overflow-hidden`, `print:border-[1.5px] print:shadow-none`, ) const [newMask, setNewMask] = useState("") useEffect(() => { const transformMask = async () => { if (rendered.maskUrl) { const imgSrc = await writeIntoBubble( rendered.maskUrl, "LOREM IPSUM! Dolor sit amet.." ) setNewMask(imgSrc) } } transformMask() }, [rendered.maskUrl]) if (prompt && !rendered.assetUrl) { return (
) } return (
{rendered.assetUrl && {rendered.alt}} {/* Hello, world! */}
) }