"use client" import { useEffect, useRef, useState, useTransition } from "react" import { RxReload } from "react-icons/rx" 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" export function Panel({ panel, className = "", width = 1, height = 1, }: { panel: number className?: string width?: number height?: number }) { const panelId = `${panel}` const [mouseOver, setMouseOver] = useState(false) const ref = useRef(null) 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 captions = useStore(state => state.captions) const caption = captions[panel] || "" const zoomLevel = useStore(state => state.zoomLevel) const showCaptions = useStore(state => state.showCaptions) const addToUpscaleQueue = useStore(state => state.addToUpscaleQueue) const [_isPending, startTransition] = useTransition() const renderedScenes = useStore(state => state.renderedScenes) const setRendered = useStore(state => state.setRendered) const rendered = renderedScenes[panel] || getInitialRenderedScene() // keep a ref in sync const renderedRef = useRef() const renderedKey = JSON.stringify(rendered) useEffect(() => { renderedRef.current = rendered }, [renderedKey]) const timeoutRef = useRef(null) const enableRateLimiter = `${process.env.NEXT_PUBLIC_ENABLE_RATE_LIMITER}` === "true" const delay = enableRateLimiter ? (3000 + (1000 * panel)) : 1000 const startImageGeneration = ({ prompt, width, height }: { prompt: string, width: number, height: number}) => { // console.log("Panel prompt: "+ prompt) if (!prompt?.length) { return } // important: update the status, and clear the scene setGeneratingImages(panelId, true) // just to empty it setRendered(panelId, getInitialRenderedScene()) setTimeout(() => { startTransition(async () => { // console.log(`Loading panel ${panel}..`) let newRendered: RenderedScene try { newRendered = await newRender({ prompt, width, height }) } catch (err) { // "Failed to load the panel! Don't worry, we are retrying..") newRendered = await newRender({ prompt, width, height }) } if (newRendered) { // console.log("newRendered:", newRendered) setRendered(panelId, newRendered) if (newRendered.status === "completed") { setGeneratingImages(panelId, false) addToUpscaleQueue(panelId, newRendered) } // but we are still loading! } else { setRendered(panelId, { renderId: "", status: "pending", assetUrl: "", alt: "", maskUrl: "", error: "", segments: [] }) setGeneratingImages(panelId, false) return } }) }, enableRateLimiter ? 2000 * panel : 0) } // since this run in its own loop, we need to use references everywhere // but perhaps this could be refactored useEffect(() => { startImageGeneration({ prompt, width, height }) }, [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(panelId, 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(panelId, renderedRef.current = newRendered) setGeneratingImages(panelId, 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" || (newRendered.status === "completed" && !newRendered.assetUrl?.length)) { // console.log(`panel got an error and/or an empty asset url :/ "${newRendered.error}", but let's try to recover..`) try { const newAttempt = await newRender({ prompt, width, height }) setRendered(panelId, newAttempt) } catch (err) { console.error("yeah sorry, something is wrong.. aborting", err) setGeneratingImages(panelId, false) } } else { console.log("panel finished!") setGeneratingImages(panelId, false) addToUpscaleQueue(panelId, newRendered) } } 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( //`flex`, `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 handleReload = () => { console.log(`Asked to reload panel ${panelId}`) startImageGeneration({ prompt, width, height }) } if (prompt && !rendered.assetUrl) { return (
) } return (
setMouseOver(true)} onMouseLeave={() => setMouseOver(false)} >
140 ? `border-b-[2px] md:border-b-[4px]` : zoomLevel > 120 ? `border-b-[1.5px] md:border-b-[3px]` : zoomLevel > 90 ? `border-b-[1px] md:border-b-[2px]` : zoomLevel > 40 ? `border-b-[0.5px] md:border-b-[1px]` : `border-transparent md:border-b-[0.5px]`, `print:border-b-[1.5px]`, `truncate`, zoomLevel > 200 ? `p-4 md:p-8` : zoomLevel > 180 ? `p-[14px] md:p-8` : zoomLevel > 160 ? `p-[12px] md:p-[28px]` : zoomLevel > 140 ? `p-[10px] md:p-[26px]` : zoomLevel > 120 ? `p-2 md:p-6` : zoomLevel > 100 ? `p-1.5 md:p-[20px]` : zoomLevel > 90 ? `p-1.5 md:p-4` : zoomLevel > 40 ? `p-1 md:p-2` : `p-0.5 md:p-2`, zoomLevel > 220 ? `text-xl md:text-4xl` : zoomLevel > 200 ? `text-lg md:text-3xl` : zoomLevel > 180 ? `text-md md:text-2xl` : zoomLevel > 140 ? `text-2xs md:text-2xl` : zoomLevel > 120 ? `text-3xs md:text-xl` : zoomLevel > 100 ? `text-4xs md:text-lg` : zoomLevel > 90 ? `text-5xs md:text-sm` : zoomLevel > 40 ? `md:text-xs` : `md:text-2xs`, showCaptions ? ( zoomLevel > 90 ? `block` : `hidden md:block` ) : `hidden`, )} >{caption || ""}
{rendered.assetUrl && {rendered.alt}} { // there is an issue, this env check doesn't work.. // process.env.NEXT_PUBLIC_CAN_REDRAW === "true" ?
Redraw
//: null }
) }