BennyKok's picture
chore: disable animation for now
1342931
"use client"
import { getWebsocketUrl } from '@/server/generate'
import { useCallback, useEffect, useRef, useState } from 'react'
import useSWR from 'swr'
import useWebSocket, { ReadyState } from 'react-use-websocket';
import { useDebounce } from "use-debounce";
import { Input } from './ui/input';
import { Badge } from './ui/badge';
import { Skeleton } from './ui/skeleton';
import { cn } from '@/lib/utils';
export function WebsocketDemo() {
const { data } = useSWR("ws", getWebsocketUrl, {
revalidateOnFocus: false,
})
const [ws, setWs] = useState<WebSocket>()
const [status, setStatus] = useState("not-connected")
const [prompt, setPrompt] = useState('A anime cat');
const [debouncedPrompt] = useDebounce(prompt, 200);
const [currentLog, setCurrentLog] = useState<string>();
const [reconnectCounter, setReconnectCounter] = useState(0)
const canvasRef = useRef<HTMLCanvasElement>(null); // Reference to the canvas element
const sendInput = useCallback(() => {
if (status == "reconnecting" || status == "connecting")
return
if (ws?.readyState == ws?.CLOSED) {
setStatus('reconnecting')
setReconnectCounter(x => x + 1)
return
}
if (status != "ready")
return
ws?.send(JSON.stringify(
{
"event": "input",
"inputs": {
"input_text": debouncedPrompt
}
}
))
}, [ws, debouncedPrompt, status])
const preStatus = useRef(status)
useEffect(() => {
if (preStatus.current != status && status == "ready")
sendInput();
preStatus.current = status
}, [status, sendInput])
useEffect(() => {
sendInput();
}, [debouncedPrompt])
const connectWS = useCallback((data: NonNullable<Awaited<ReturnType<typeof getWebsocketUrl>>>) => {
setStatus("connecting");
const websocket = new WebSocket(data.ws_connection_url);
websocket.binaryType = "arraybuffer";
websocket.onopen = () => {
setStatus("connected");
};
websocket.onmessage = (event) => {
if (typeof event.data === "string") {
const message = JSON.parse(event.data);
if (message?.event == "status" && message?.data?.sid) {
setStatus("ready");
}
if (message?.event) {
if (message?.event == "executing" && message?.data?.node == null)
setCurrentLog("done")
else if (message?.event == "live_status")
setCurrentLog(`running - ${message.data?.current_node} ${(message.data.progress * 100).toFixed(2)}%`)
else if (message?.event == "elapsed_time")
setCurrentLog(`elapsed time: ${Math.ceil(message.data?.elapsed_time * 100) / 100}s`)
}
console.log("Received message:", message);
}
if (event.data instanceof ArrayBuffer) {
console.log("Received binary message:");
drawImage(event.data);
}
};
websocket.onclose = () => setStatus("closed");
websocket.onerror = () => setStatus("error");
setWs(websocket);
return () => {
websocket.close();
};
}, [data])
const drawImage = useCallback((arrayBuffer: ArrayBuffer) => {
const view = new DataView(arrayBuffer);
const eventType = view.getUint32(0);
const buffer = arrayBuffer.slice(4);
switch (eventType) {
case 1:
const view2 = new DataView(arrayBuffer);
const imageType = view2.getUint32(0)
let imageMime
switch (imageType) {
case 1:
default:
imageMime = "image/jpeg";
break;
case 2:
imageMime = "image/png"
break;
case 3:
imageMime = "image/webp"
}
const blob = new Blob([buffer.slice(4)], { type: imageMime });
const fileSize = blob.size;
console.log(`Received image size: ${(fileSize / 1024).toFixed(2)} KB`);
// const blob = new Blob([arrayBuffer], { type: 'image/png' }); // Assuming the image is a JPEG
const url = URL.createObjectURL(blob);
const canvas = canvasRef.current;
const ctx = canvas?.getContext('2d');
if (ctx) {
console.log("drawing");
const img = new Image();
img.onload = () => {
if (canvas) {
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
}
URL.revokeObjectURL(url); // Clean up
};
img.src = url;
}
// this.dispatchEvent(new CustomEvent("b_preview", { detail: imageBlob }));
break;
default:
throw new Error(`Unknown binary websocket message of type ${eventType}`);
}
}, []);
useEffect(() => {
if (!data) {
setStatus("not-connected");
return;
}
return connectWS(data)
}, [connectWS, reconnectCounter])
const pending = (status == "not-connected" || status == "connecting" || status == "reconnecting" || currentLog?.startsWith("running") || (!currentLog && status == "connected"))
return (
<div className='flex md:flex-col gap-2 px-2 flex-col-reverse'>
<div className='flex gap-2'>
<Badge variant={'outline'} className='w-fit'>Status: {status}</Badge>
{(currentLog || status == "connected" || status == "ready") && <Badge variant={'outline'} className='w-fit'>
{currentLog}
{status == "connected" && !currentLog && "stating comfy ui"}
{status == "ready" && !currentLog && " running"}
</Badge>}
</div>
<div className='relative w-full'>
<canvas ref={canvasRef} className='rounded-lg ring-1 ring-black/10 w-full aspect-square' width={1024} height={1024}></canvas>
{/* {
<><Skeleton className={
cn("absolute top-0 left-0 w-full h-full aspect-square opacity-20 transition-opacity", pending ? "visible" : "invisible opacity-0")
} /></>
} */}
</div>
<Input
type="text"
value={prompt}
onChange={(e) => setPrompt(e.target.value)}
/>
</div>
)
}