fix(browser): limpiar previews/outputs residuales al cargar workflow ComfyUI
app.loadApiJson (lo que usa comfyui_load_workflow_ui) reconstruye el grafo pero no llama a app.clean(), por lo que no resetea el store app.nodeOutputs ni los previews de los nodos. Cuando un workflow nuevo reusa un node_id existente en el store, el preview cacheado del workflow anterior se re-pinta sobre el nodo nuevo (visto: imagen 3D pegada bajo un CheckpointLoaderSimple/SaveGLB). - Nueva funcion comfyui_clear_node_outputs_ui: limpieza no destructiva del store app.nodeOutputs + node.imgs/images, sin tocar la topologia del grafo. - comfyui_load_workflow_ui v1.1.0: anade clear_outputs=True (default) que invoca la limpieza antes de loadApiJson, replicando la garantia de loadGraphData. Reproducido y verificado en la UI real (CDP 9222) con evidencia antes/despues. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,67 @@
|
|||||||
|
---
|
||||||
|
name: comfyui_clear_node_outputs_ui
|
||||||
|
kind: function
|
||||||
|
lang: py
|
||||||
|
domain: browser
|
||||||
|
version: "1.0.0"
|
||||||
|
purity: impure
|
||||||
|
signature: "def comfyui_clear_node_outputs_ui(*, port: int = 9222, server_url_substr: str = '8188', timeout_s: float = 15.0) -> dict"
|
||||||
|
description: "Limpia outputs/previews residuales de TODOS los nodos del grafo de ComfyUI en la UI via CDP: vacia app.nodeOutputs (store de previews keyed by node_id) y borra imgs/images de cada nodo vivo, sin tocar la topologia del grafo (no borra nodos ni links). Arregla el bug de imagenes pegadas a nodos que no corresponden tras cargar un workflow nuevo con app.loadApiJson. Compone cdp_eval. Impura: red (CDP) + muta la UI."
|
||||||
|
tags: [comfyui, browser, cdp, ml, ui-automation, image-generation]
|
||||||
|
uses_functions: ["cdp_eval_py_browser"]
|
||||||
|
uses_types: []
|
||||||
|
returns: []
|
||||||
|
returns_optional: false
|
||||||
|
error_type: "error_go_core"
|
||||||
|
imports: []
|
||||||
|
params:
|
||||||
|
- name: port
|
||||||
|
desc: "Puerto de remote debugging del Chrome diario. Default 9222."
|
||||||
|
- name: server_url_substr
|
||||||
|
desc: "Substring de la URL de la pestana de ComfyUI (default '8188', el puerto del server). Identifica la pestana entre las abiertas."
|
||||||
|
- name: timeout_s
|
||||||
|
desc: "Timeout de la conexion CDP en segundos. Default 15.0."
|
||||||
|
output: "dict {ok, cleared, error, store_cleared, nodes_touched, nodes}. ok/cleared True si la limpieza termino sin excepcion. store_cleared = entradas borradas de app.nodeOutputs; nodes_touched = nodos a los que se les quito un preview; nodes = total de nodos del grafo."
|
||||||
|
tested: false
|
||||||
|
tests: []
|
||||||
|
test_file_path: ""
|
||||||
|
file_path: "python/functions/browser/comfyui_clear_node_outputs_ui.py"
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ejemplo
|
||||||
|
|
||||||
|
```python
|
||||||
|
import sys, os
|
||||||
|
sys.path.insert(0, os.path.join("python", "functions"))
|
||||||
|
from browser.comfyui_clear_node_outputs_ui import comfyui_clear_node_outputs_ui
|
||||||
|
|
||||||
|
# Requiere la UI de ComfyUI abierta en el Chrome con CDP en el puerto 9222.
|
||||||
|
print(comfyui_clear_node_outputs_ui())
|
||||||
|
# -> {'ok': True, 'cleared': True, 'error': '', 'store_cleared': 6, 'nodes_touched': 2, 'nodes': 12}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Cuando usarla
|
||||||
|
|
||||||
|
Cuando ves previews/outputs de imagenes pegados a nodos que no los produjeron
|
||||||
|
(una imagen bajo un `CheckpointLoaderSimple`, un `SaveGLB`, etc.) tras haber
|
||||||
|
cargado varios workflows seguidos en la misma pestana. Es la limpieza no
|
||||||
|
destructiva: borra los previews residuales del grafo actual SIN recargarlo ni
|
||||||
|
perder la topologia. `comfyui_load_workflow_ui(..., clear_outputs=True)` la
|
||||||
|
invoca automaticamente antes de cargar, asi que normalmente no hace falta
|
||||||
|
llamarla a mano; usala solo para limpiar un grafo ya cargado sin recargarlo.
|
||||||
|
|
||||||
|
## Gotchas
|
||||||
|
|
||||||
|
- Requiere la pestana de ComfyUI abierta en un Chrome con
|
||||||
|
`--remote-debugging-port=9222`. Si no hay target que matchee
|
||||||
|
`server_url_substr`, `cdp_eval` devuelve error y aqui `ok=False`.
|
||||||
|
- Borra TODOS los previews del grafo, incluidos los legitimos de la ultima
|
||||||
|
ejecucion. Si quieres conservar un preview concreto, no la llames; el residuo
|
||||||
|
cross-workflow se evita de raiz cargando con
|
||||||
|
`comfyui_load_workflow_ui(..., clear_outputs=True)`.
|
||||||
|
- No es `app.clean()`: a proposito NO hace `rootGraph.clear()`, por eso es
|
||||||
|
segura sobre el grafo vivo del usuario (no borra nodos ni conexiones).
|
||||||
|
- El store que vacia es `app.nodeOutputs`; el nombre interno puede variar entre
|
||||||
|
versiones de ComfyUI. Si una version renombra el store, el borrado del store
|
||||||
|
no aplica pero el barrido de `node.imgs`/`node.images` sigue limpiando los
|
||||||
|
previews visibles.
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
"""Limpia los outputs/previews residuales de los nodos de ComfyUI en la UI via CDP.
|
||||||
|
|
||||||
|
ComfyUI cachea los outputs de cada ejecucion en `app.nodeOutputs`, un store
|
||||||
|
indexado por node_id. La ruta de carga `app.loadApiJson` (la que usa
|
||||||
|
comfyui_load_workflow_ui) reconstruye el grafo pero NO resetea ese store ni los
|
||||||
|
previews de los nodos. Cuando un workflow nuevo reusa un node_id que ya existia
|
||||||
|
en el store, el preview cacheado del workflow anterior se vuelve a pintar sobre
|
||||||
|
el nodo nuevo, que muchas veces es de otro tipo (ej. una imagen pegada bajo un
|
||||||
|
`CheckpointLoaderSimple` o un `SaveGLB`).
|
||||||
|
|
||||||
|
Esta funcion vacia `app.nodeOutputs` y borra `imgs`/`images` de todos los nodos
|
||||||
|
vivos del grafo, sin tocar la topologia del grafo (no borra nodos ni links), y
|
||||||
|
marca el canvas dirty para repintar. Es la version no destructiva de
|
||||||
|
`app.clean()` (que ademas haria `rootGraph.clear()`).
|
||||||
|
|
||||||
|
Funcion impura: hace red (CDP WebSocket) y muta el estado de la UI.
|
||||||
|
"""
|
||||||
|
|
||||||
|
try: # ejecucion directa del archivo / fn run (browser/ en sys.path[0])
|
||||||
|
from cdp_eval import cdp_eval
|
||||||
|
except ImportError: # importado como paquete (sys.path = python/functions)
|
||||||
|
from browser.cdp_eval import cdp_eval
|
||||||
|
|
||||||
|
|
||||||
|
def comfyui_clear_node_outputs_ui(
|
||||||
|
*,
|
||||||
|
port: int = 9222,
|
||||||
|
server_url_substr: str = "8188",
|
||||||
|
timeout_s: float = 15.0,
|
||||||
|
) -> dict:
|
||||||
|
"""Limpia previews/outputs residuales de todos los nodos del grafo de ComfyUI.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
port: puerto de remote debugging del Chrome diario. Default 9222.
|
||||||
|
server_url_substr: substring de la URL de la pestana de ComfyUI (default
|
||||||
|
"8188", el puerto del server). Identifica la pestana entre las
|
||||||
|
abiertas.
|
||||||
|
timeout_s: timeout de la conexion CDP en segundos.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict {ok: bool, cleared: bool, error: str, store_cleared: int,
|
||||||
|
nodes_touched: int, nodes: int}. ok/cleared True si la limpieza termino
|
||||||
|
sin excepcion en la pagina. `store_cleared` es el numero de entradas
|
||||||
|
eliminadas de `app.nodeOutputs`; `nodes_touched` los nodos a los que se
|
||||||
|
les quito un preview; `nodes` el total de nodos del grafo.
|
||||||
|
"""
|
||||||
|
expr = (
|
||||||
|
"(function(){"
|
||||||
|
" if(!window.app){ return {ok:false, cleared:false, error:'window.app no disponible en la pestana'}; }"
|
||||||
|
" try{"
|
||||||
|
" var store=0;"
|
||||||
|
" if(app.nodeOutputs){ for(var k in app.nodeOutputs){ if(Object.prototype.hasOwnProperty.call(app.nodeOutputs,k)){ delete app.nodeOutputs[k]; store++; } } }"
|
||||||
|
" var nodes=(app.graph && app.graph._nodes)? app.graph._nodes : [];"
|
||||||
|
" var touched=0;"
|
||||||
|
" for(var i=0;i<nodes.length;i++){"
|
||||||
|
" var nd=nodes[i];"
|
||||||
|
" if(nd.imgs!==undefined || nd.images!==undefined){ touched++; }"
|
||||||
|
" nd.imgs=undefined;"
|
||||||
|
" nd.images=undefined;"
|
||||||
|
" nd.imageIndex=null;"
|
||||||
|
" nd.overIndex=null;"
|
||||||
|
" if('animatedImages' in nd){ nd.animatedImages=undefined; }"
|
||||||
|
" }"
|
||||||
|
" if(app.graph && app.graph.setDirtyCanvas){ app.graph.setDirtyCanvas(true,true); }"
|
||||||
|
" return {ok:true, cleared:true, error:'', store_cleared:store, nodes_touched:touched, nodes:nodes.length};"
|
||||||
|
" }catch(e){ return {ok:false, cleared:false, error:String(e)}; }"
|
||||||
|
"})()"
|
||||||
|
)
|
||||||
|
r = cdp_eval(
|
||||||
|
expr,
|
||||||
|
port=port,
|
||||||
|
target_url_substr=server_url_substr,
|
||||||
|
await_promise=False,
|
||||||
|
timeout_s=timeout_s,
|
||||||
|
)
|
||||||
|
if not r["ok"]:
|
||||||
|
return {
|
||||||
|
"ok": False,
|
||||||
|
"cleared": False,
|
||||||
|
"error": r["error"],
|
||||||
|
"store_cleared": 0,
|
||||||
|
"nodes_touched": 0,
|
||||||
|
"nodes": 0,
|
||||||
|
}
|
||||||
|
val = r["value"] or {}
|
||||||
|
return {
|
||||||
|
"ok": bool(val.get("cleared")),
|
||||||
|
"cleared": bool(val.get("cleared")),
|
||||||
|
"error": val.get("error", ""),
|
||||||
|
"store_cleared": int(val.get("store_cleared", 0)),
|
||||||
|
"nodes_touched": int(val.get("nodes_touched", 0)),
|
||||||
|
"nodes": int(val.get("nodes", 0)),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import json
|
||||||
|
|
||||||
|
print(
|
||||||
|
json.dumps(
|
||||||
|
comfyui_clear_node_outputs_ui(),
|
||||||
|
ensure_ascii=False,
|
||||||
|
indent=2,
|
||||||
|
)
|
||||||
|
)
|
||||||
@@ -3,12 +3,12 @@ name: comfyui_load_workflow_ui
|
|||||||
kind: function
|
kind: function
|
||||||
lang: py
|
lang: py
|
||||||
domain: browser
|
domain: browser
|
||||||
version: "1.0.0"
|
version: "1.1.0"
|
||||||
purity: impure
|
purity: impure
|
||||||
signature: "def comfyui_load_workflow_ui(workflow: dict, *, port: int = 9222, server_url_substr: str = '8188', filename: str = 'workflow.json', timeout_s: float = 20.0) -> dict"
|
signature: "def comfyui_load_workflow_ui(workflow: dict, *, port: int = 9222, server_url_substr: str = '8188', filename: str = 'workflow.json', clear_outputs: bool = True, timeout_s: float = 20.0) -> dict"
|
||||||
description: "Carga un workflow ComfyUI (API format) en la UI del navegador via CDP: inyecta app.loadApiJson(<workflow>, filename) en la pestana de ComfyUI abierta y reconstruye el grafo visual. Compone cdp_eval (transport CDP). Impura: red (CDP WebSocket) + muta el grafo de la UI."
|
description: "Carga un workflow ComfyUI (API format) en la UI del navegador via CDP: inyecta app.loadApiJson(<workflow>, filename) en la pestana de ComfyUI abierta y reconstruye el grafo visual. Por defecto (clear_outputs=True) limpia antes los previews/outputs residuales para que un preview cacheado del workflow anterior no se pegue a un nodo nuevo que reusa el mismo node_id. Compone cdp_eval + comfyui_clear_node_outputs_ui. Impura: red (CDP WebSocket) + muta el grafo de la UI."
|
||||||
tags: [comfyui, browser, cdp, ml, image-generation, stable-diffusion, ui-automation]
|
tags: [comfyui, browser, cdp, ml, image-generation, stable-diffusion, ui-automation]
|
||||||
uses_functions: ["cdp_eval_py_browser"]
|
uses_functions: ["cdp_eval_py_browser", "comfyui_clear_node_outputs_ui_py_browser"]
|
||||||
uses_types: []
|
uses_types: []
|
||||||
returns: []
|
returns: []
|
||||||
returns_optional: false
|
returns_optional: false
|
||||||
@@ -23,6 +23,8 @@ params:
|
|||||||
desc: "Substring de la URL de la pestana de ComfyUI (default '8188', el puerto del server). Identifica la pestana entre las abiertas."
|
desc: "Substring de la URL de la pestana de ComfyUI (default '8188', el puerto del server). Identifica la pestana entre las abiertas."
|
||||||
- name: filename
|
- name: filename
|
||||||
desc: "Nombre que ComfyUI asocia al workflow cargado. Default 'workflow.json'."
|
desc: "Nombre que ComfyUI asocia al workflow cargado. Default 'workflow.json'."
|
||||||
|
- name: clear_outputs
|
||||||
|
desc: "Si True (default) limpia previews/outputs residuales (app.nodeOutputs + node.imgs) antes de cargar, evitando que un preview cacheado de un workflow anterior se pegue a un nodo nuevo que reusa el mismo node_id. False conserva los previews previos a proposito."
|
||||||
- name: timeout_s
|
- name: timeout_s
|
||||||
desc: "Timeout de la conexion CDP en segundos. Default 20.0."
|
desc: "Timeout de la conexion CDP en segundos. Default 20.0."
|
||||||
output: "dict {ok: bool, loaded: bool, error: str}. ok/loaded True si app.loadApiJson termino sin excepcion en la pagina."
|
output: "dict {ok: bool, loaded: bool, error: str}. ok/loaded True si app.loadApiJson termino sin excepcion en la pagina."
|
||||||
@@ -66,3 +68,17 @@ editarlo en la UI del navegador del usuario antes de encolarlo. Es el puente
|
|||||||
necesitas.
|
necesitas.
|
||||||
- Espera la Promise de carga (`await_promise=True`). El conteo de nodos cargados
|
- Espera la Promise de carga (`await_promise=True`). El conteo de nodos cargados
|
||||||
se puede verificar con `cdp_eval("app.graph._nodes.length", target_url_substr="8188")`.
|
se puede verificar con `cdp_eval("app.graph._nodes.length", target_url_substr="8188")`.
|
||||||
|
- `app.loadApiJson` (a diferencia de la ruta del menu `app.loadGraphData`) NO
|
||||||
|
llama a `app.clean()`, asi que NO resetea el store `app.nodeOutputs` ni los
|
||||||
|
previews de los nodos. Sin `clear_outputs=True`, un preview cacheado de un
|
||||||
|
workflow anterior se re-pinta sobre el nodo nuevo que reuse el mismo node_id
|
||||||
|
(visto: imagen 3D pegada bajo un `CheckpointLoaderSimple`/`SaveGLB`). El
|
||||||
|
default `clear_outputs=True` lo evita delegando en
|
||||||
|
`comfyui_clear_node_outputs_ui`.
|
||||||
|
|
||||||
|
## Capability growth log
|
||||||
|
|
||||||
|
- v1.1.0 (2026-06-24) — anade `clear_outputs=True` (default): limpia los
|
||||||
|
previews/outputs residuales (`app.nodeOutputs` + `node.imgs`) antes de cargar,
|
||||||
|
delegando en `comfyui_clear_node_outputs_ui`. Fija el bug de imagenes
|
||||||
|
residuales pegadas a nodos que reusan node_id entre workflows.
|
||||||
|
|||||||
@@ -11,8 +11,10 @@ import json
|
|||||||
|
|
||||||
try: # ejecucion directa del archivo / fn run (browser/ en sys.path[0])
|
try: # ejecucion directa del archivo / fn run (browser/ en sys.path[0])
|
||||||
from cdp_eval import cdp_eval
|
from cdp_eval import cdp_eval
|
||||||
|
from comfyui_clear_node_outputs_ui import comfyui_clear_node_outputs_ui
|
||||||
except ImportError: # importado como paquete (sys.path = python/functions)
|
except ImportError: # importado como paquete (sys.path = python/functions)
|
||||||
from browser.cdp_eval import cdp_eval
|
from browser.cdp_eval import cdp_eval
|
||||||
|
from browser.comfyui_clear_node_outputs_ui import comfyui_clear_node_outputs_ui
|
||||||
|
|
||||||
|
|
||||||
def comfyui_load_workflow_ui(
|
def comfyui_load_workflow_ui(
|
||||||
@@ -21,6 +23,7 @@ def comfyui_load_workflow_ui(
|
|||||||
port: int = 9222,
|
port: int = 9222,
|
||||||
server_url_substr: str = "8188",
|
server_url_substr: str = "8188",
|
||||||
filename: str = "workflow.json",
|
filename: str = "workflow.json",
|
||||||
|
clear_outputs: bool = True,
|
||||||
timeout_s: float = 20.0,
|
timeout_s: float = 20.0,
|
||||||
) -> dict:
|
) -> dict:
|
||||||
"""Carga un workflow API format en el grafo de la UI de ComfyUI.
|
"""Carga un workflow API format en el grafo de la UI de ComfyUI.
|
||||||
@@ -33,12 +36,24 @@ def comfyui_load_workflow_ui(
|
|||||||
"8188", el puerto del server). Identifica la pestana entre todas las
|
"8188", el puerto del server). Identifica la pestana entre todas las
|
||||||
abiertas.
|
abiertas.
|
||||||
filename: nombre que ComfyUI asocia al workflow cargado.
|
filename: nombre que ComfyUI asocia al workflow cargado.
|
||||||
|
clear_outputs: si True (default), limpia los previews/outputs residuales
|
||||||
|
(app.nodeOutputs + node.imgs) ANTES de cargar, replicando lo que hace
|
||||||
|
la ruta del menu (app.clean()). Evita que un preview cacheado de un
|
||||||
|
workflow anterior se pegue a un nodo nuevo que reusa el mismo node_id
|
||||||
|
(bug de imagenes residuales). Ponlo en False solo si quieres conservar
|
||||||
|
a proposito los previews del grafo previo.
|
||||||
timeout_s: timeout de la conexion CDP en segundos.
|
timeout_s: timeout de la conexion CDP en segundos.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
dict {ok: bool, loaded: bool, error: str}. ok/loaded True si
|
dict {ok: bool, loaded: bool, error: str}. ok/loaded True si
|
||||||
app.loadApiJson termino sin excepcion en la pagina.
|
app.loadApiJson termino sin excepcion en la pagina.
|
||||||
"""
|
"""
|
||||||
|
if clear_outputs:
|
||||||
|
comfyui_clear_node_outputs_ui(
|
||||||
|
port=port,
|
||||||
|
server_url_substr=server_url_substr,
|
||||||
|
timeout_s=timeout_s,
|
||||||
|
)
|
||||||
expr = (
|
expr = (
|
||||||
"(async function(){"
|
"(async function(){"
|
||||||
" if(!window.app || typeof app.loadApiJson!=='function'){"
|
" if(!window.app || typeof app.loadApiJson!=='function'){"
|
||||||
|
|||||||
Reference in New Issue
Block a user