jbilcke-hf's picture
jbilcke-hf HF staff
emergency fix
f3c09ed
raw
history blame
10.7 kB
"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<HTMLImageElement>(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<RenderedScene>()
const renderedKey = JSON.stringify(rendered)
useEffect(() => { renderedRef.current = rendered }, [renderedKey])
const timeoutRef = useRef<any>(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 (
<div className={cn(
frameClassName,
`flex flex-col items-center justify-center`,
className,
)}>
<Progress isLoading />
</div>
)
}
return (
<div className={cn(
frameClassName,
{ "grayscale": preset.color === "grayscale" },
className
)}
onMouseEnter={() => setMouseOver(true)}
onMouseLeave={() => setMouseOver(false)}
>
<div className={cn(
`bg-stone-50`,
`border-stone-800`,
`transition-all duration-200 ease-in-out`,
zoomLevel > 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 || ""}
</div>
{rendered.assetUrl &&
<img
ref={ref}
src={rendered.assetUrl}
width={width}
height={height}
alt={rendered.alt}
className={cn(
`comic-panel w-full h-full object-cover max-w-max`,
// showCaptions ? `-mt-11` : ''
)}
/>}
{
// there is an issue, this env check doesn't work..
// process.env.NEXT_PUBLIC_CAN_REDRAW === "true" ?
<div
className={cn(`relative -mt-14 ml-4`,)}>
<div className="flex flex-row">
<div
onClick={rendered.status === "completed" ? handleReload : undefined}
className={cn(
`bg-stone-100 rounded-lg`,
`flex flex-row space-x-2 items-center`,
`py-2 px-3 cursor-pointer`,
`transition-all duration-200 ease-in-out`,
rendered.status === "completed" ? "opacity-95" : "opacity-50",
mouseOver && rendered.assetUrl ? `scale-95 hover:scale-100 hover:opacity-100`: `scale-0`
)}>
<RxReload
className="w-5 h-5"
/>
<span className="text-base">Redraw</span>
</div>
</div>
</div>
//: null
}
</div>
)
}