From eb02561906c2c673f054975e7b199aa8b9bfb2cd Mon Sep 17 00:00:00 2001
From: space-nuko <24979496+space-nuko@users.noreply.github.com>
Date: Mon, 5 Jun 2023 15:45:08 -0500
Subject: [PATCH 1/3] Basic preview support
(as of latest PR commit)
---
src/lib/api.ts | 97 ++++++++++++-------
src/lib/components/ComfyApp.ts | 4 +
.../components/ComfyBoxWorkflowsView.svelte | 3 +
src/lib/nodes/widgets/ComfyGalleryNode.ts | 6 +-
src/lib/stores/layoutStates.ts | 8 ++
src/lib/stores/queueState.ts | 37 +++++++
src/lib/stores/uiState.ts | 2 +
src/lib/widgets/GalleryWidget.svelte | 77 ++++++++++++++-
8 files changed, 198 insertions(+), 36 deletions(-)
diff --git a/src/lib/api.ts b/src/lib/api.ts
index 492aa2b..ec54872 100644
--- a/src/lib/api.ts
+++ b/src/lib/api.ts
@@ -101,6 +101,7 @@ export type ComfyUIPromptExtraData = {
}
type ComfyAPIEvents = {
+ // JSON
status: (status: ComfyAPIStatusResponse | null, error?: Error | null) => void,
progress: (progress: Progress) => void,
reconnecting: () => void,
@@ -111,6 +112,9 @@ type ComfyAPIEvents = {
execution_cached: (promptID: PromptID, nodes: ComfyNodeID[]) => void,
execution_interrupted: (error: ComfyInterruptedError) => void,
execution_error: (error: ComfyExecutionError) => void,
+
+ // Binary
+ b_preview: (imageBlob: Blob) => void
}
export default class ComfyAPI {
@@ -126,7 +130,7 @@ export default class ComfyAPI {
}
/**
- * Poll status for colab and other things that don't support websockets.
+ * Poll status for colab and other things that don't support websockets.
*/
private pollQueue() {
setInterval(async () => {
@@ -176,6 +180,7 @@ export default class ComfyAPI {
this.socket = new WebSocket(
`ws${window.location.protocol === "https:" ? "s" : ""}://${hostname}:${port}/ws${existingSession}`
);
+ this.socket.binaryType = "arraybuffer";
this.socket.addEventListener("open", () => {
opened = true;
@@ -204,38 +209,64 @@ export default class ComfyAPI {
this.socket.addEventListener("message", (event) => {
try {
- const msg = JSON.parse(event.data);
- switch (msg.type) {
- case "status":
- if (msg.data.sid) {
- this.clientId = msg.data.sid;
- sessionStorage["Comfy.SessionId"] = this.clientId;
- }
- this.eventBus.emit("status", { execInfo: { queueRemaining: msg.data.status.exec_info.queue_remaining } });
- break;
- case "progress":
- this.eventBus.emit("progress", msg.data as Progress);
- break;
- case "executing":
- this.eventBus.emit("executing", msg.data.prompt_id, msg.data.node);
- break;
- case "executed":
- this.eventBus.emit("executed", msg.data.prompt_id, msg.data.node, msg.data.output);
- break;
- case "execution_start":
- this.eventBus.emit("execution_start", msg.data.prompt_id);
- break;
- case "execution_cached":
- this.eventBus.emit("execution_cached", msg.data.prompt_id, msg.data.nodes);
- break;
- case "execution_interrupted":
- this.eventBus.emit("execution_interrupted", msg.data);
- break;
- case "execution_error":
- this.eventBus.emit("execution_error", msg.data);
- break;
- default:
- console.warn("Unhandled message:", event.data);
+ if (event.data instanceof ArrayBuffer) {
+ const view = new DataView(event.data);
+ const eventType = view.getUint32(0);
+ const buffer = event.data.slice(4);
+ switch (eventType) {
+ case 1:
+ const view2 = new DataView(event.data);
+ const imageType = view2.getUint32(0)
+ let imageMime: string
+ switch (imageType) {
+ case 1:
+ default:
+ imageMime = "image/jpeg";
+ break;
+ case 2:
+ imageMime = "image/png"
+ }
+ const imageBlob = new Blob([buffer.slice(4)], { type: imageMime });
+ this.eventBus.emit("b_preview", imageBlob);
+ break;
+ default:
+ throw new Error(`Unknown binary websocket message of type ${eventType}`);
+ }
+ }
+ else {
+ const msg = JSON.parse(event.data);
+ switch (msg.type) {
+ case "status":
+ if (msg.data.sid) {
+ this.clientId = msg.data.sid;
+ sessionStorage["Comfy.SessionId"] = this.clientId;
+ }
+ this.eventBus.emit("status", { execInfo: { queueRemaining: msg.data.status.exec_info.queue_remaining } });
+ break;
+ case "progress":
+ this.eventBus.emit("progress", msg.data as Progress);
+ break;
+ case "executing":
+ this.eventBus.emit("executing", msg.data.prompt_id, msg.data.node);
+ break;
+ case "executed":
+ this.eventBus.emit("executed", msg.data.prompt_id, msg.data.node, msg.data.output);
+ break;
+ case "execution_start":
+ this.eventBus.emit("execution_start", msg.data.prompt_id);
+ break;
+ case "execution_cached":
+ this.eventBus.emit("execution_cached", msg.data.prompt_id, msg.data.nodes);
+ break;
+ case "execution_interrupted":
+ this.eventBus.emit("execution_interrupted", msg.data);
+ break;
+ case "execution_error":
+ this.eventBus.emit("execution_error", msg.data);
+ break;
+ default:
+ console.warn("Unhandled message:", event.data);
+ }
}
} catch (error) {
console.error("Error handling message", event.data, error);
diff --git a/src/lib/components/ComfyApp.ts b/src/lib/components/ComfyApp.ts
index 97abc08..0f0369c 100644
--- a/src/lib/components/ComfyApp.ts
+++ b/src/lib/components/ComfyApp.ts
@@ -651,6 +651,10 @@ export default class ComfyApp {
}
});
+ this.api.addEventListener("b_preview", (imageBlob: Blob) => {
+ queueState.previewUpdated(imageBlob);
+ });
+
const config = get(configState);
if (config.pollSystemStatsInterval > 0) {
diff --git a/src/lib/components/ComfyBoxWorkflowsView.svelte b/src/lib/components/ComfyBoxWorkflowsView.svelte
index 4fd5430..722a1dd 100644
--- a/src/lib/components/ComfyBoxWorkflowsView.svelte
+++ b/src/lib/components/ComfyBoxWorkflowsView.svelte
@@ -386,6 +386,9 @@
showLightbox(entry.images, i, e)}
- src={image}
+ src={imageURL}
+ loading="lazy"
alt="thumbnail" />
{/each}
diff --git a/src/lib/components/PromptDisplay.svelte b/src/lib/components/PromptDisplay.svelte
index b96e2c7..ee9de16 100644
--- a/src/lib/components/PromptDisplay.svelte
+++ b/src/lib/components/PromptDisplay.svelte
@@ -8,7 +8,7 @@
import Gallery from "$lib/components/gradio/gallery/Gallery.svelte";
import { ImageViewer } from "$lib/ImageViewer";
import type { Styles } from "@gradio/utils";
- import { comfyFileToComfyBoxMetadata, comfyURLToComfyFile, countNewLines } from "$lib/utils";
+ import { comfyFileToComfyBoxMetadata, comfyURLToComfyFile, countNewLines, type ComfyImageLocation, convertComfyOutputToComfyURL } from "$lib/utils";
import ReceiveOutputTargets from "./modal/ReceiveOutputTargets.svelte";
import workflowState, { type ComfyBoxWorkflow, type WorkflowReceiveOutputTargets } from "$lib/stores/workflowState";
import type { ComfyReceiveOutputNode } from "$lib/nodes/actions";
@@ -17,7 +17,7 @@
const splitLength = 50;
export let prompt: SerializedPromptInputsAll;
- export let images: string[] = []; // list of image URLs to ComfyUI's /view? endpoint
+ export let images: ComfyImageLocation[] = [];
export let isMobile: boolean = false;
export let expandAll: boolean = false;
export let closeModal: () => void;
@@ -36,10 +36,7 @@
let litegraphType = "(none)"
$: if (images.length > 0) {
- // since the image links come from gradio, have to parse the URL for the
- // ComfyImageLocation params
- comfyBoxImages = images.map(comfyURLToComfyFile)
- .map(comfyFileToComfyBoxMetadata);
+ comfyBoxImages = images.map(comfyFileToComfyBoxMetadata);
}
else {
comfyBoxImages = []
@@ -199,7 +196,7 @@