Skip to content

Commit e7089b8

Browse files
committed
generation requests return images
1 parent 4d435a4 commit e7089b8

File tree

9 files changed

+298
-214
lines changed

9 files changed

+298
-214
lines changed

.github/workflows/comfy_windows.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ on:
66
branches:
77
- tauri
88

9+
concurrency:
10+
group: comfyui_windows
11+
912
jobs:
1013
repackage_comfyui:
1114
permissions:

packages/stablestudio-ui/src-tauri/src/main.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
33

44
use std::collections::HashMap;
5-
use std::fmt::format;
65
use std::fs::File;
76
use std::sync::OnceLock;
87
use tauri::api::process::CommandEvent;

packages/stablestudio-ui/src-tauri/src/server.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,11 @@ impl Builder {
106106
"`ws${window.location.protocol === \"https:\" ? \"s\" : \"\"}://${location.host}/ws${existingSession}`",
107107
"`ws://localhost:5000/ws${existingSession}`"
108108
);
109+
110+
// add some stuff to app.js
111+
if path_name.ends_with("app.js") && !file_contents.ends_with("app.api = api;") {
112+
file_contents = file_contents + "\napp.api = api;";
113+
}
109114

110115
let response = HttpResponse::from_data(file_contents.as_bytes()).with_status_code(200).with_header(
111116
Header::from_bytes("Content-Type", mimetype.unwrap().as_str()).unwrap(),

packages/stablestudio-ui/src/App/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@ export namespace App {
222222

223223
setRunning(true);
224224
setIsSetup(SetupState.ComfyRunning);
225+
Comfy.registerListeners();
225226
}, [isSetup, print, setRunning, setUnlisteners]);
226227

227228
useEffect(() => {

packages/stablestudio-ui/src/Comfy/index.tsx

Lines changed: 169 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
import * as StableStudio from "@stability/stablestudio-plugin";
12
import { useLocation } from "react-router-dom";
23
import { create } from "zustand";
4+
import { Generation } from "~/Generation";
35

4-
export type Comfy = {
6+
export type ComfyApp = {
57
setup: () => void;
68
registerNodes: () => void;
79
loadGraphData: (graph: Graph) => void;
@@ -13,6 +15,21 @@ export type Comfy = {
1315
refreshComboInNodes: () => Promise<void>;
1416
queuePrompt: (number: number, batchCount: number) => Promise<void>;
1517
clean: () => void;
18+
api: ComfyAPI;
19+
};
20+
21+
export type ComfyAPI = {
22+
addEventListener: (event: string, callback: (detail: any) => void) => void;
23+
};
24+
25+
export type Comfy = { app: ComfyApp; api: ComfyAPI };
26+
27+
export type ComfyOutput = {
28+
images: {
29+
filename: string;
30+
subfolder: string;
31+
type: string;
32+
}[];
1633
};
1734

1835
export type Graph = {
@@ -91,11 +108,11 @@ type State = {
91108
};
92109

93110
export namespace Comfy {
94-
export const get = (): Comfy | null =>
111+
export const get = (): ComfyApp | null =>
95112
((
96113
(document.getElementById("comfyui-window") as HTMLIFrameElement)
97-
?.contentWindow as Window & { app: Comfy }
98-
)?.app as Comfy) ?? null;
114+
?.contentWindow as Window & { app: ComfyApp }
115+
)?.app as ComfyApp) ?? null;
99116

100117
export const use = create<State>((set) => ({
101118
output: [],
@@ -115,4 +132,152 @@ export namespace Comfy {
115132
unlisteners: [],
116133
setUnlisteners: (unlisteners) => set({ unlisteners }),
117134
}));
135+
136+
export const registerListeners = async () => {
137+
let api = get()?.api;
138+
139+
while (!api) {
140+
await new Promise((resolve) => setTimeout(resolve, 1000));
141+
api = get()?.api;
142+
}
143+
144+
api.addEventListener("executed", async ({ detail }) => {
145+
const { output, prompt_id } = detail;
146+
147+
console.log("executed_in_comfy_domain", detail);
148+
149+
const newInputs: Record<ID, Generation.Image.Input> = {};
150+
const responses: Generation.Images = [];
151+
152+
const input = Generation.Image.Input.get(prompt_id);
153+
154+
const images = await Promise.all(
155+
(output as ComfyOutput).images.map(async (image) => {
156+
console.log("image", image);
157+
const resp = await fetch(
158+
`http://localhost:3000/view?filename=${image.filename}&subfolder=${
159+
image.subfolder || ""
160+
}&type=${image.type}`,
161+
{
162+
cache: "no-cache",
163+
}
164+
);
165+
166+
const blob = await resp.blob();
167+
const url = URL.createObjectURL(blob);
168+
console.log("url", url);
169+
170+
const output = Generation.Image.Output.get(prompt_id);
171+
172+
return {
173+
id: ID.create(),
174+
blob,
175+
inputID: output?.inputID ?? "",
176+
createdAt: new Date(),
177+
};
178+
})
179+
);
180+
181+
for (const image of images) {
182+
const inputID = ID.create();
183+
const newInput = {
184+
...Generation.Image.Input.initial(inputID),
185+
...input,
186+
seed: 0,
187+
id: inputID,
188+
};
189+
190+
const cropped = await cropImage(image, newInput);
191+
if (!cropped) continue;
192+
193+
responses.push(cropped);
194+
newInputs[inputID] = newInput;
195+
}
196+
197+
Generation.Image.Inputs.set({
198+
...Generation.Image.Inputs.get(),
199+
...newInputs,
200+
});
201+
responses.forEach(Generation.Image.add);
202+
Generation.Image.Output.received(prompt_id, responses);
203+
});
204+
205+
api.addEventListener("execution_start", ({ detail }) => {
206+
const { prompt_id } = detail;
207+
208+
console.log("execution_start", detail);
209+
210+
if (prompt_id) {
211+
let input = Generation.Image.Input.get(prompt_id);
212+
if (!input) {
213+
input = Generation.Image.Input.initial(prompt_id);
214+
Generation.Image.Inputs.set((inputs) => ({
215+
...inputs,
216+
[prompt_id]: input,
217+
}));
218+
}
219+
const output = Generation.Image.Output.requested(
220+
prompt_id,
221+
{},
222+
prompt_id
223+
);
224+
Generation.Image.Output.set(output);
225+
}
226+
});
227+
228+
api.addEventListener("execution_error", ({ detail }) => {
229+
console.log("execution_error", detail);
230+
Generation.Image.Output.clear(detail.prompt_id);
231+
});
232+
233+
console.log("registered ComfyUI listeners");
234+
};
235+
}
236+
237+
function cropImage(
238+
image: StableStudio.StableDiffusionImage,
239+
input: Generation.Image.Input
240+
) {
241+
return new Promise<Generation.Image | void>((resolve) => {
242+
const id = image.id;
243+
const blob = image.blob;
244+
if (!blob || !id) return resolve();
245+
246+
// crop image to box size
247+
const croppedCanvas = document.createElement("canvas");
248+
croppedCanvas.width = input.width;
249+
croppedCanvas.height = input.height;
250+
251+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
252+
const croppedCtx = croppedCanvas.getContext("2d")!;
253+
254+
const img = new window.Image();
255+
img.src = URL.createObjectURL(blob);
256+
img.onload = () => {
257+
croppedCtx.drawImage(
258+
img,
259+
0,
260+
0,
261+
input.width,
262+
input.height,
263+
0,
264+
0,
265+
input.width,
266+
input.height
267+
);
268+
269+
croppedCanvas.toBlob((blob) => {
270+
if (blob) {
271+
const objectURL = URL.createObjectURL(blob);
272+
resolve({
273+
id,
274+
inputID: input.id,
275+
created: new Date(),
276+
src: objectURL,
277+
finishReason: 0,
278+
});
279+
}
280+
});
281+
};
282+
});
118283
}

0 commit comments

Comments
 (0)