This commit is contained in:
Chris
2025-11-27 11:09:21 +11:00
parent 6099d9fc10
commit dc60707eba
5 changed files with 95 additions and 93 deletions
+8 -7
View File
@@ -9,7 +9,7 @@ HIDDEN = {
"prompt": "PROMPT",
"extra_pnginfo": "EXTRA_PNGINFO",
"uid":"UNIQUE_ID",
"node_identifier": "NID",
}
class ImageFilter(PreviewImage):
@@ -38,6 +38,7 @@ class ImageFilter(PreviewImage):
"pick_list_start" : ("INT", {"default":0, "tooltip":"The number used in pick_list for the first image"}),
"pick_list" : ("STRING", {"default":"", "tooltip":"If a comma separated list of integers is provided, the images with these indices will be selected automatically."}),
"video_frames" : ("INT", {"default":1, "min":1, "tooltip": "treat each block of n images as a video"}),
"graph_id": ("STRING", {"default":""}),
},
"hidden": HIDDEN,
}
@@ -46,7 +47,7 @@ class ImageFilter(PreviewImage):
def IS_CHANGED(cls, pick_list, **kwargs):
return pick_list or float("NaN")
def func(self, images, timeout, ontimeout, uid, node_identifier, tip="", extra1="", extra2="", extra3="", latents=None, masks=None, pick_list_start:int=0, pick_list:str="", video_frames:int=1, **kwargs):
def func(self, images, timeout, ontimeout, uid, graph_id, tip="", extra1="", extra2="", extra3="", latents=None, masks=None, pick_list_start:int=0, pick_list:str="", video_frames:int=1, **kwargs):
e1, e2, e3 = extra1, extra2, extra3
B = images.shape[0]
@@ -63,7 +64,7 @@ class ImageFilter(PreviewImage):
urls:list[str] = self.save_images(images=images, **kwargs)['ui']['images']
payload = {"uid": uid, "urls":urls, "allsame":all_the_same, "extras":[extra1, extra2, extra3], "tip":tip, "video_frames":video_frames}
response:Response = send_and_wait(payload, timeout, uid, node_identifier)
response:Response = send_and_wait(payload, timeout, uid, graph_id)
if isinstance(response, TimeoutResponse):
if ontimeout=='send none': images_to_return = []
@@ -118,13 +119,13 @@ class TextImageFilterWithExtras(PreviewImage):
def IS_CHANGED(cls, **kwargs):
return float("NaN")
def func(self, image, text, timeout, uid, node_identifier, extra1="", extra2="", extra3="", mask=None, tip="", textareaheight=None, **kwargs):
def func(self, image, text, timeout, uid, graph_id, extra1="", extra2="", extra3="", mask=None, tip="", textareaheight=None, **kwargs):
urls:list[str] = self.save_images(images=image, **kwargs)['ui']['images']
payload = {"uid": uid, "urls":urls, "text":text, "extras":[extra1, extra2, extra3], "tip":tip}
if textareaheight is not None: payload['textareaheight'] = textareaheight
if mask is not None: payload['mask_urls'] = self.save_images(images=mask_to_image(mask), **kwargs)['ui']['images']
response = send_and_wait(payload, timeout, uid, node_identifier)
response = send_and_wait(payload, timeout, uid, graph_id)
if isinstance(response, TimeoutResponse):
return (image, text, extra1, extra2, extra3)
@@ -165,7 +166,7 @@ class MaskImageFilter(PreviewImage, LoadImage):
@classmethod
def VALIDATE_INPUTS(cls, *args, **kwargs): return True
def func(self, image, timeout, uid, if_no_mask, node_identifier, mask=None, extra1="", extra2="", extra3="", tip="", **kwargs):
def func(self, image, timeout, uid, if_no_mask, graph_id, mask=None, extra1="", extra2="", extra3="", tip="", **kwargs):
if mask is not None and mask.shape[:3] == image.shape[:3] and not torch.all(mask==0):
saveable = torch.cat((image, mask.unsqueeze(-1)), dim=-1)
else:
@@ -173,7 +174,7 @@ class MaskImageFilter(PreviewImage, LoadImage):
urls:list[dict[str,str]] = self.save_images(images=saveable, **kwargs)['ui']['images']
payload = {"uid": uid, "urls":urls, "maskedit":True, "extras":[extra1, extra2, extra3], "tip":tip}
response = send_and_wait(payload, timeout, uid, node_identifier)
response = send_and_wait(payload, timeout, uid, graph_id)
if (response.masked_image):
try:
+13 -13
View File
@@ -27,11 +27,11 @@ class RequestResponse(Response): pass
class MessageState:
_latest:'Optional[MessageState]' = None
unique_expected = None
graph_id_expected = None
def __init__(self, data:dict|str={}):
data_dict:dict = data if isinstance(data,dict) else json.loads(data)
self.unique:str = data_dict.pop('unique', None)
self.graph_id:str = data_dict.pop('graph_id', None)
self.special:Optional[str] = data_dict.pop('special',None)
self.response:Response = Response(**data_dict)
@@ -51,9 +51,9 @@ class MessageState:
def request_state(cls): return MessageState(data={'special':REQUEST_RESHOW})
@classmethod
def start_waiting(cls, unique):
def start_waiting(cls, graph_id):
cls._latest = cls.waiting_state()
cls.unique_expected = unique
cls.graph_id_expected = graph_id
@classmethod
def get_response(cls) -> Response:
@@ -85,7 +85,7 @@ async def cg_image_filter_message(request):
response = post.get("response")
message = MessageState(response)
if str(MessageState.unique_expected)==str(message.unique):
if str(MessageState.graph_id_expected)==str(message.graph_id):
if (MessageState.waiting()):
MessageState.set_latest(message)
else:
@@ -95,26 +95,26 @@ async def cg_image_filter_message(request):
return web.json_response({})
def wait_for_response(secs, uid, unique) -> Response:
MessageState.start_waiting(unique)
def wait_for_response(secs, uid, graph_id) -> Response:
MessageState.start_waiting(graph_id)
try:
end_time = time.monotonic() + secs
while(time.monotonic() < end_time and MessageState.waiting()):
throw_exception_if_processing_interrupted()
PromptServer.instance.send_sync("cg-image-filter-images", {"tick": int(end_time - time.monotonic()), "uid": uid, "unique":unique})
PromptServer.instance.send_sync("cg-image-filter-images", {"tick": int(end_time - time.monotonic()), "uid": uid, "graph_id":graph_id})
time.sleep(0.5)
if MessageState.waiting():
PromptServer.instance.send_sync("cg-image-filter-images", {"timeout": True, "uid": uid, "unique":unique})
PromptServer.instance.send_sync("cg-image-filter-images", {"timeout": True, "uid": uid, "graph_id":graph_id})
return MessageState.get_response()
finally: MessageState.stop_waiting()
def send_and_wait(payload, timeout, uid, unique) -> Response:
payload['uid'] = uid
payload['unique'] = unique
def send_and_wait(payload, timeout, uid, graph_id) -> Response:
payload['uid'] = uid
payload['graph_id'] = graph_id
while True:
PromptServer.instance.send_sync("cg-image-filter-images", payload)
r = wait_for_response(timeout, uid, unique)
r = wait_for_response(timeout, uid, graph_id)
if isinstance(r,CancelledResponse): raise InterruptProcessingException()
if (not isinstance(r, RequestResponse)): return r
+24 -22
View File
@@ -3,8 +3,8 @@ import { api } from "../../scripts/api.js";
import { create } from "./utils.js";
import { popup } from "./popup.js";
import { ComfyWidgets } from "../../scripts/widgets.js";
import { unique_to_tab } from "./weak_map.js";
import { graph_id_to_tab } from "./weak_map.js";
import { Log } from "./log.js";
const FILTER_TYPES = ["Image Filter","Text Image Filter","Text Image Filter with Extras","Mask Image Filter"]
@@ -107,32 +107,34 @@ app.registerExtension({
if (FILTER_TYPES.includes(nodeType.comfyClass )) {
const onNodeCreated = nodeType.prototype.onNodeCreated;
nodeType.prototype.onNodeCreated = function () {
this._ni_widget = this.widgets.find((n)=>n.name=='node_identifier')
if (!(this._ni_widget)) {
this._ni_widget = ComfyWidgets["STRING"](this, "node_identifier", ["STRING", { "default":"" }], app).widget
}
this._ni_widget.hidden = true
this._ni_widget.computeSize = () => [0,0]
set_graph_id_widget(this)
return onNodeCreated ? onNodeCreated.apply(this, arguments) : undefined;
}
}
},
afterConfigureGraph() {
setTimeout( ()=> {
const checked = document.getElementsByClassName('p-togglebutton-checked')[0]
app.graph.nodes.forEach( (node)=> {
const unique_id_widget = node.widgets?.find((n)=>n.name=='node_identifier')
if (unique_id_widget) {
unique_id_widget.value = `${node.graph.id}:${node.id}`
unique_to_tab.set(unique_id_widget.value, checked )
}
})
}, 1000)
setTimeout( ()=> { app.graph.nodes.forEach( set_graph_id_widget ) }, 1000 )
link_to_tab(3)
}
})
function set_graph_id_widget(node) {
const graph_id_widget = node.widgets?.find((n)=>n.name=='graph_id')
if (graph_id_widget) {
graph_id_widget.hidden = true
graph_id_widget.value = `${app.graph.id}` // app.graph.id is unique per tab, regardless of subgraph
graph_id_widget.computeSize = () => [0,0]
}
}
})
function link_to_tab(tries) {
const tab = document.getElementsByClassName('p-togglebutton-checked')[0]
if (tab && app.graph.id) {
graph_id_to_tab.set(app.graph.id, tab )
} else if (tries>0) {
setTimeout( ()=> { link_to_tab(tries-1) }, 500 )
} else {
Log.log(`cg-image-filter: could not link graph to tab`)
}
}
+49 -50
View File
@@ -5,7 +5,7 @@ import { mask_editor_listen_for_cancel, mask_editor_showing, hide_mask_editor, p
import { Log } from "./log.js";
import { create } from "./utils.js";
import { FloatingWindow } from "./floating_window.js";
import { unique_to_tab } from "./weak_map.js";
import { graph_id_to_tab } from "./weak_map.js";
//const EXTENSION_NODES = ["Image Filter", "Text Image Filter", "Mask Image Filter", "Text Image Filter with Extras",]
const POPUP_NODES = ["Image Filter", "Text Image Filter", "Text Image Filter with Extras",]
@@ -169,7 +169,7 @@ class Popup extends HTMLElement {
special (int)
masked_image (string)
*extras (list of strings)
*unique (string)
*graph_id (string)
(*) are added
*/
if (Date.now()-this.last_response_sent < 1000) {
@@ -177,13 +177,7 @@ class Popup extends HTMLElement {
return
}
const unique = this.node?._ni_widget?.value
if (!unique) {
if (this.node) Log.error(`Node ${this.node.id} has no _ni_widget when trying to send ${msg}`)
else Log.error(`No node when trying to send ${msg}`)
return
}
msg.unique = `${unique}`
msg.graph_id = `${app.graph.id}`
if (!msg.special) {
if (this.n_extras>0) {
@@ -230,13 +224,13 @@ class Popup extends HTMLElement {
handle_message(message) {
Log.message_in(message)
Log.log( this._handle_message(message, false) )
this._handle_message(message, false)
this.render()
}
handle_deferred_message(e) {
Log.message_in(this.saved_message, "(deferred)")
Log.log( this._handle_message(this.saved_message, true) )
this._handle_message(this.saved_message, true)
this.render()
}
@@ -267,24 +261,31 @@ class Popup extends HTMLElement {
return node
}
_flash_tab(graph_id) {
const tab = graph_id_to_tab.get(graph_id)
if (tab) {
const element = tab.firstElementChild?.firstElementChild?.firstElementChild || tab
if (this.tab_orginal_background===undefined) this.tab_orginal_background = element.style.backgroundColor
element.style.backgroundColor = '#ffff0040'
setTimeout( ()=>{ element.style.backgroundColor = this.tab_orginal_background }, 100 )
}
}
_handle_message(message, using_saved) {
const detail = message.detail
const uid = detail.uid
const the_node = this.find_node(uid)
const graph_id = message.detail.graph_id
if (graph_id != app.graph.id) {
this._flash_tab(message.detail.graph_id)
Log.detail(`Message for different tab`)
return
}
if (!the_node) Log.log(`No node found with uid ${uid}. Maybe it's been removed. Continuing with caution`)
if (this.node!=the_node) this.on_new_node(the_node)
const tab = unique_to_tab.get(message.detail.unique)
if (!this.node || this.node._ni_widget?.value != message.detail.unique) {
if (tab) {
if (!tab.innerHTML.endsWith('!&nbsp;')) tab.innerHTML += '!&nbsp;'
}
return console.log(`Message unique id wasn't mine`)
} else {
if (tab) {
if (tab.innerHTML.endsWith('!&nbsp;')) tab.innerHTML = tab.innerHTML.substring(0, tab.innerHTML.length-7)
}
}
if (detail.tick) {
this.counter_text.innerText = `${detail.tick}s`
@@ -294,12 +295,12 @@ class Popup extends HTMLElement {
if (detail.timeout) {
this.close()
return `Timeout`
return Log.log(`Image Filter Timeout`)
}
if (this.handling_message) return `Ignoring message because we're already handling a message`
if (this.handling_message) return Log.detail(`Ignoring message because we're already handling a message`)
this.set_title(this.node.title ?? "Image Filter")
this.set_title(this.node?.title ?? "Image Filter")
this.allsame = detail.allsame || false
if (detail.tip) this.tip_row.innerHTML = detail.tip.replace(/(?:\r\n|\r|\n)/g, '<br/>')
else this.tip_row.innerHTML = ""
@@ -326,8 +327,6 @@ class Popup extends HTMLElement {
} finally { this.handling_message = false }
}
window_not_showing(uid) {
const node = this.find_node(uid)
return (
@@ -346,18 +345,21 @@ class Popup extends HTMLElement {
}
handle_maskedit(detail) {
this.state = State.MASK
//this.node = this.find_node(detail.uid)
this.node.imgs = []
detail.urls.forEach((url, i)=>{
this.node.imgs.push( new Image() );
this.node.imgs[i].src = api.apiURL( `/view?filename=${encodeURIComponent(url.filename)}&type=${url.type}&subfolder=${url.subfolder}`)
})
ComfyApp.copyToClipspace(this.node)
ComfyApp.clipspace_return_node = this.node
ComfyApp.open_maskeditor()
this.seen_editor = false
if (!this.node) {
Log.log(`No node to handle maskedit - maybe it's been removed`)
this.seen_editor = true
} else {
this.state = State.MASK
this.node.imgs = []
detail.urls.forEach((url, i)=>{
this.node.imgs.push( new Image() );
this.node.imgs[i].src = api.apiURL( `/view?filename=${encodeURIComponent(url.filename)}&type=${url.type}&subfolder=${url.subfolder}`)
})
ComfyApp.copyToClipspace(this.node)
ComfyApp.clipspace_return_node = this.node
ComfyApp.open_maskeditor()
this.seen_editor = false
}
setTimeout(this.wait_while_mask_editing.bind(this), 200)
}
@@ -401,31 +403,27 @@ class Popup extends HTMLElement {
}
this.n_images = (this.video_frames<=1) ? detail.urls.length : Math.ceil(detail.urls.length / this.video_frames)
this.laidOut = -1
this.picked = new Set()
if (this.n_images==1) this.picked.add('0')
this.grid.innerHTML = ''
this.overlaygrid.innerHTML = ''
var latestImage = null
var latestImage
detail.urls.forEach((url, i)=>{
console.log(url)
Log.log(url)
if (i%this.video_frames == 0) {
const thisImage = create('img', null, this.grid, {src:get_full_url(url)})
latestImage = thisImage
latestImage = create('img', null, this.grid, {src:get_full_url(url)})
latestImage.onload = this.layout.bind(this)
latestImage.image_index = i/this.video_frames
latestImage.addEventListener('mouseover', (e)=>this.on_mouse_enter(thisImage))
latestImage.addEventListener('mouseout', (e)=>this.on_mouse_out(thisImage))
latestImage.addEventListener('mouseover', (e)=>this.on_mouse_enter(latestImage))
latestImage.addEventListener('mouseout', (e)=>this.on_mouse_out(latestImage))
latestImage.frames = [get_full_url(url),]
} else {
latestImage.frames.push(get_full_url(url))
}
if (detail.mask_urls) { create('img', null, this.overlaygrid, {src:get_full_url(detail.mask_urls[i])})}
if (detail.mask_urls && this.video_frames==1) { create('img', null, this.overlaygrid, {src:get_full_url(detail.mask_urls[i])}) }
})
this.layout()
@@ -435,6 +433,7 @@ class Popup extends HTMLElement {
setTimeout(this.advance_videos.bind(this), 1000)
}
/* cooldown to prevent us catching a click that was intended for an element we are now covering */
this.in_cooldown = true
setTimeout(()=>{this.in_cooldown = false}, 500)
+1 -1
View File
@@ -13,4 +13,4 @@ class WeakMap {
}
}
export const unique_to_tab = new WeakMap()
export const graph_id_to_tab = new WeakMap()