1
+ import * as StableStudio from "@stability/stablestudio-plugin" ;
1
2
import { useLocation } from "react-router-dom" ;
2
3
import { create } from "zustand" ;
4
+ import { Generation } from "~/Generation" ;
3
5
4
- export type Comfy = {
6
+ export type ComfyApp = {
5
7
setup : ( ) => void ;
6
8
registerNodes : ( ) => void ;
7
9
loadGraphData : ( graph : Graph ) => void ;
@@ -13,6 +15,21 @@ export type Comfy = {
13
15
refreshComboInNodes : ( ) => Promise < void > ;
14
16
queuePrompt : ( number : number , batchCount : number ) => Promise < void > ;
15
17
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
+ } [ ] ;
16
33
} ;
17
34
18
35
export type Graph = {
@@ -91,11 +108,11 @@ type State = {
91
108
} ;
92
109
93
110
export namespace Comfy {
94
- export const get = ( ) : Comfy | null =>
111
+ export const get = ( ) : ComfyApp | null =>
95
112
( (
96
113
( 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 ;
99
116
100
117
export const use = create < State > ( ( set ) => ( {
101
118
output : [ ] ,
@@ -115,4 +132,152 @@ export namespace Comfy {
115
132
unlisteners : [ ] ,
116
133
setUnlisteners : ( unlisteners ) => set ( { unlisteners } ) ,
117
134
} ) ) ;
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
+ } ) ;
118
283
}
0 commit comments