chore: auto-commit (61 archivos)

- docs/capabilities/INDEX.md
- docs/capabilities/comfyui.md
- python/functions/browser/comfyui_export_workflow_ui.md
- python/functions/browser/comfyui_export_workflow_ui.py
- python/functions/browser/comfyui_load_workflow_ui.md
- python/functions/browser/comfyui_load_workflow_ui.py
- python/functions/browser/comfyui_queue_prompt_ui.md
- python/functions/browser/comfyui_queue_prompt_ui.py
- python/functions/browser/comfyui_refresh_nodes_ui.md
- python/functions/browser/comfyui_refresh_nodes_ui.py
- ...

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-24 00:30:30 +02:00
parent 495f545ec1
commit f12272d002
72 changed files with 6049 additions and 0 deletions
@@ -0,0 +1,66 @@
---
name: comfyui_export_workflow_ui
kind: function
lang: py
domain: browser
version: "1.0.0"
purity: impure
signature: "def comfyui_export_workflow_ui(*, port: int = 9222, server_url_substr: str = '8188', api_format: bool = True, save_path: str | None = None, timeout_s: float = 15.0) -> dict"
description: "Exporta el workflow actual del grafo de ComfyUI desde la UI via CDP. Con api_format=True devuelve el API format ((await app.graphToPrompt()).output, listo para POST /prompt); con False el UI graph serializado (app.graph.serialize(), recargable en la UI). Opcionalmente escribe el JSON a disco. Compone cdp_eval. Impura: red (CDP) + escritura opcional."
tags: [comfyui, browser, cdp, ml, image-generation, stable-diffusion, ui-automation]
uses_functions: ["cdp_eval_py_browser"]
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports: ["json", "os"]
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'."
- name: api_format
desc: "True devuelve el API format (POST /prompt); False el UI graph serializado (recargable con la UI). Default True."
- name: save_path
desc: "Si se pasa, ruta donde escribir el JSON (se expande ~ y se crean los padres). None no escribe a disco."
- name: timeout_s
desc: "Timeout de la conexion CDP en segundos. Default 15.0."
output: "dict {ok: bool, workflow: dict, saved_to: str|None, error: str}. workflow es el API format o el UI graph segun api_format; saved_to es la ruta escrita o None."
tested: false
tests: []
test_file_path: ""
file_path: "python/functions/browser/comfyui_export_workflow_ui.py"
---
## Ejemplo
```python
import sys, os
sys.path.insert(0, os.path.join("python", "functions"))
from browser.comfyui_export_workflow_ui import comfyui_export_workflow_ui
# Captura el API format del grafo actual y guardalo a disco.
out = comfyui_export_workflow_ui(api_format=True, save_path="/tmp/wf_actual.json")
print(out["ok"], len(out["workflow"]), "nodos ->", out["saved_to"])
# El API format devuelto es re-enviable por API:
from ml.comfyui_submit_workflow import comfyui_submit_workflow
resp = comfyui_submit_workflow(out["workflow"])
```
## Cuando usarla
Para capturar lo que el usuario tiene montado en la UI y (a) re-enviarlo por API
con `comfyui_submit_workflow`, (b) persistirlo como plantilla, o (c) verificar
que un cambio hecho con `comfyui_set_node_widget_ui` quedo reflejado en el grafo.
Es el reverso de `comfyui_load_workflow_ui`.
## Gotchas
- `api_format=True` da el formato de POST /prompt (sin links visuales ni
posiciones); `api_format=False` da el grafo de UI (con todo lo necesario para
`app.loadGraphData`). Elige segun si vas a re-enviar por API o a recargar en UI.
- `graphToPrompt()` es asincrono: se espera la Promise (`await_promise=True`). Si
la pestana no tiene `window.app`, devuelve `ok=False` con error claro.
- El export refleja el estado EN VIVO del grafo, incluidos los cambios de
`comfyui_set_node_widget_ui` aplicados antes.
@@ -0,0 +1,96 @@
"""Exporta el workflow actual del grafo de ComfyUI desde la UI via CDP.
Con api_format=True devuelve el API format (el dict que acepta POST /prompt,
extraido de `(await app.graphToPrompt()).output`); con False devuelve el UI graph
serializado (`app.graph.serialize()`, con links y posiciones para volver a
cargar en la UI). Opcionalmente escribe el JSON a disco. Compone cdp_eval.
Funcion impura: hace red (CDP WebSocket) y, si save_path, escribe en disco.
"""
import json
import os
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_export_workflow_ui(
*,
port: int = 9222,
server_url_substr: str = "8188",
api_format: bool = True,
save_path: str | None = None,
timeout_s: float = 15.0,
) -> dict:
"""Exporta el workflow actual del grafo de la UI 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.
api_format: True devuelve el API format (POST /prompt); False devuelve el
UI graph serializado (recargable con la UI). Default True.
save_path: si se pasa, ruta donde escribir el JSON exportado. Se expande
~ y se crean los directorios padre. None no escribe a disco.
timeout_s: timeout de la conexion CDP en segundos.
Returns:
dict {ok: bool, workflow: dict, saved_to: str|None, error: str}.
"""
if api_format:
expr = (
"(async function(){"
" if(!window.app || typeof app.graphToPrompt!=='function'){"
" return {error:'window.app.graphToPrompt no disponible en la pestana'};"
" }"
" try{ var p = await app.graphToPrompt(); return {workflow: p.output, error:''}; }"
" catch(e){ return {error:String(e)}; }"
"})()"
)
await_p = True
else:
expr = (
"(function(){"
" if(!window.app || !app.graph || typeof app.graph.serialize!=='function'){"
" return {error:'window.app.graph.serialize no disponible en la pestana'};"
" }"
" try{ return {workflow: app.graph.serialize(), error:''}; }"
" catch(e){ return {error:String(e)}; }"
"})()"
)
await_p = False
r = cdp_eval(
expr,
port=port,
target_url_substr=server_url_substr,
await_promise=await_p,
timeout_s=timeout_s,
)
if not r["ok"]:
return {"ok": False, "workflow": {}, "saved_to": None, "error": r["error"]}
val = r["value"] or {}
if val.get("error"):
return {"ok": False, "workflow": {}, "saved_to": None, "error": val["error"]}
workflow = val.get("workflow") or {}
saved_to = None
if save_path:
path = os.path.expanduser(save_path)
parent = os.path.dirname(path)
if parent:
os.makedirs(parent, exist_ok=True)
with open(path, "w", encoding="utf-8") as fh:
json.dump(workflow, fh, ensure_ascii=False, indent=2)
saved_to = path
return {"ok": True, "workflow": workflow, "saved_to": saved_to, "error": ""}
if __name__ == "__main__":
out = comfyui_export_workflow_ui(api_format=True)
print(json.dumps(
{"ok": out["ok"], "nodes": len(out["workflow"]), "error": out["error"]},
ensure_ascii=False, indent=2,
))
@@ -0,0 +1,68 @@
---
name: comfyui_load_workflow_ui
kind: function
lang: py
domain: browser
version: "1.0.0"
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"
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."
tags: [comfyui, browser, cdp, ml, image-generation, stable-diffusion, ui-automation]
uses_functions: ["cdp_eval_py_browser"]
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports: ["json"]
params:
- name: workflow
desc: "dict en API format (claves = node_ids, valores con class_type + inputs); tipicamente el resultado de comfyui_build_txt2img_workflow."
- 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: filename
desc: "Nombre que ComfyUI asocia al workflow cargado. Default 'workflow.json'."
- name: timeout_s
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."
tested: false
tests: []
test_file_path: ""
file_path: "python/functions/browser/comfyui_load_workflow_ui.py"
---
## Ejemplo
```python
import sys, os
sys.path.insert(0, os.path.join("python", "functions"))
from ml.comfyui_build_txt2img_workflow import comfyui_build_txt2img_workflow
from browser.comfyui_load_workflow_ui import comfyui_load_workflow_ui
wf = comfyui_build_txt2img_workflow(
ckpt_name="dreamshaper_8.safetensors",
positive="a red apple on a wooden table, sharp focus",
)
# Requiere la UI de ComfyUI abierta en el Chrome con CDP en el puerto 9222.
print(comfyui_load_workflow_ui(wf)) # -> {'ok': True, 'loaded': True, 'error': ''}
```
## Cuando usarla
Cuando tienes un workflow en API format (lo construyo con
`comfyui_build_txt2img_workflow` o lo exporto de otro lado) y quieres verlo y
editarlo en la UI del navegador del usuario antes de encolarlo. Es el puente
"API format -> grafo visual": cargas, luego ajustas widgets con
`comfyui_set_node_widget_ui` y encolas con `comfyui_queue_prompt_ui`.
## Gotchas
- Requiere que la pestana de ComfyUI ya este 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`.
- `app.loadApiJson` REEMPLAZA el grafo actual de la UI por el del workflow; pierde
los cambios no exportados. Exporta antes con `comfyui_export_workflow_ui` si los
necesitas.
- 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")`.
@@ -0,0 +1,81 @@
"""Carga un workflow ComfyUI (API format) en la UI del navegador via CDP.
Inyecta `app.loadApiJson(<workflow>, filename)` en la pestana de ComfyUI ya
abierta en el navegador diario, reconstruyendo el grafo visual a partir del API
format (el mismo dict que produce comfyui_build_txt2img_workflow). Compone la
primitiva de transport cdp_eval; no abre ventana nueva ni reinventa CDP.
Funcion impura: hace red (CDP WebSocket) y muta el grafo de la UI.
"""
import json
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_load_workflow_ui(
workflow: dict,
*,
port: int = 9222,
server_url_substr: str = "8188",
filename: str = "workflow.json",
timeout_s: float = 20.0,
) -> dict:
"""Carga un workflow API format en el grafo de la UI de ComfyUI.
Args:
workflow: dict en API format (claves = node_ids, valores con class_type +
inputs). Tipicamente el resultado de comfyui_build_txt2img_workflow.
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 todas las
abiertas.
filename: nombre que ComfyUI asocia al workflow cargado.
timeout_s: timeout de la conexion CDP en segundos.
Returns:
dict {ok: bool, loaded: bool, error: str}. ok/loaded True si
app.loadApiJson termino sin excepcion en la pagina.
"""
expr = (
"(async function(){"
" if(!window.app || typeof app.loadApiJson!=='function'){"
" return {loaded:false, error:'window.app.loadApiJson no disponible en la pestana'};"
" }"
" try{"
f" await app.loadApiJson({json.dumps(workflow)}, {json.dumps(filename)});"
" return {loaded:true, error:'', nodes: app.graph? app.graph._nodes.length : -1};"
" }catch(e){ return {loaded:false, error:String(e)}; }"
"})()"
)
r = cdp_eval(
expr,
port=port,
target_url_substr=server_url_substr,
await_promise=True,
timeout_s=timeout_s,
)
if not r["ok"]:
return {"ok": False, "loaded": False, "error": r["error"]}
val = r["value"] or {}
return {
"ok": bool(val.get("loaded")),
"loaded": bool(val.get("loaded")),
"error": val.get("error", ""),
}
if __name__ == "__main__":
import os
import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
from ml.comfyui_build_txt2img_workflow import comfyui_build_txt2img_workflow
wf = comfyui_build_txt2img_workflow(
ckpt_name="v1-5-pruned-emaonly-fp16.safetensors",
positive="a red apple on a wooden table, sharp focus",
)
print(json.dumps(comfyui_load_workflow_ui(wf), ensure_ascii=False, indent=2))
@@ -0,0 +1,62 @@
---
name: comfyui_queue_prompt_ui
kind: function
lang: py
domain: browser
version: "1.0.0"
purity: impure
signature: "def comfyui_queue_prompt_ui(*, port: int = 9222, server_url_substr: str = '8188', timeout_s: float = 20.0) -> dict"
description: "Encola el grafo actual de ComfyUI desde la UI (equivale a pulsar 'Queue Prompt'): llama app.queuePrompt(0) en la pestana, que serializa el grafo al API format y hace POST /prompt al server. Compone cdp_eval. Impura: red (CDP) + dispara trabajo de GPU."
tags: [comfyui, browser, cdp, ml, image-generation, stable-diffusion, ui-automation]
uses_functions: ["cdp_eval_py_browser"]
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports: ["json"]
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'."
- name: timeout_s
desc: "Timeout de la conexion CDP en segundos. Default 20.0."
output: "dict {ok: bool, queued: bool, error: str}. queued True si app.queuePrompt resolvio sin excepcion."
tested: false
tests: []
test_file_path: ""
file_path: "python/functions/browser/comfyui_queue_prompt_ui.py"
---
## Ejemplo
```python
import sys, os
sys.path.insert(0, os.path.join("python", "functions"))
from browser.comfyui_queue_prompt_ui import comfyui_queue_prompt_ui
from ml.comfyui_wait_result import comfyui_wait_result
print(comfyui_queue_prompt_ui()) # -> {'ok': True, 'queued': True, 'error': ''}
# El PNG aparece en ~/ComfyUI/output/. Para esperar el resultado por API se usa
# el prompt_id; si solo encolas desde la UI, sondea la carpeta output/ o usa el
# historial (GET /history) para localizar el archivo nuevo.
```
## Cuando usarla
Como ultimo paso del flujo por UI: tras cargar (`comfyui_load_workflow_ui`) y
ajustar widgets (`comfyui_set_node_widget_ui`), dispara la generacion sin que el
usuario pulse el boton. Reproduce exactamente "Queue Prompt" del frontend.
## Gotchas
- Tiene efecto secundario real: arranca trabajo de GPU en el server. No es
idempotente — cada llamada encola un prompt nuevo.
- `app.queuePrompt(0)` encola el grafo TAL CUAL esta en la UI en ese momento, no
un workflow que le pases. Para encolar uno concreto, cargalo antes con
`comfyui_load_workflow_ui`.
- No devuelve el `prompt_id` (la UI lo gestiona internamente). Para correlar el
resultado por API mejor usa `comfyui_submit_workflow` (devuelve prompt_id) +
`comfyui_wait_result`; esta funcion es para el caso "como si pulsara el boton".
- Si el grafo tiene errores de validacion, ComfyUI los muestra en la UI y la
Promise puede rechazar: aqui se refleja como `ok=False` con el error.
@@ -0,0 +1,61 @@
"""Encola el grafo actual de ComfyUI desde la UI (equivale a pulsar "Queue Prompt").
Llama `app.queuePrompt(0)` en la pestana de ComfyUI abierta en el navegador, que
serializa el grafo visual al API format y hace POST /prompt al server. Compone
cdp_eval.
Funcion impura: hace red (CDP WebSocket) y dispara trabajo de GPU en el server.
"""
import json
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_queue_prompt_ui(
*,
port: int = 9222,
server_url_substr: str = "8188",
timeout_s: float = 20.0,
) -> dict:
"""Encola el grafo actual de la UI 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.
timeout_s: timeout de la conexion CDP en segundos.
Returns:
dict {ok: bool, queued: bool, error: str}. queued True si
app.queuePrompt resolvio sin excepcion.
"""
expr = (
"(async function(){"
" if(!window.app || typeof app.queuePrompt!=='function'){"
" return {queued:false, error:'window.app.queuePrompt no disponible en la pestana'};"
" }"
" try{ await app.queuePrompt(0); return {queued:true, error:''}; }"
" catch(e){ return {queued:false, error:String(e)}; }"
"})()"
)
r = cdp_eval(
expr,
port=port,
target_url_substr=server_url_substr,
await_promise=True,
timeout_s=timeout_s,
)
if not r["ok"]:
return {"ok": False, "queued": False, "error": r["error"]}
val = r["value"] or {}
return {
"ok": bool(val.get("queued")),
"queued": bool(val.get("queued")),
"error": val.get("error", ""),
}
if __name__ == "__main__":
print(json.dumps(comfyui_queue_prompt_ui(), ensure_ascii=False, indent=2))
@@ -0,0 +1,60 @@
---
name: comfyui_refresh_nodes_ui
kind: function
lang: py
domain: browser
version: "1.0.0"
purity: impure
signature: "def comfyui_refresh_nodes_ui(*, port: int = 9222, server_url_substr: str = '8188', timeout_s: float = 15.0) -> dict"
description: "Refresca los combos del grafo de ComfyUI desde la UI via CDP: llama app.refreshComboInNodes(), que vuelve a pedir GET /object_info y actualiza los combos de todos los nodos (checkpoints, loras, vae, samplers) sin recargar la pagina. Util tras descargar modelos nuevos. Compone cdp_eval. Impura: red (CDP) + refresca estado de la UI."
tags: [comfyui, browser, cdp, ml, image-generation, stable-diffusion, ui-automation]
uses_functions: ["cdp_eval_py_browser"]
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports: ["json"]
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'."
- name: timeout_s
desc: "Timeout de la conexion CDP en segundos. Default 15.0."
output: "dict {ok: bool, refreshed: bool, error: str}. refreshed True si app.refreshComboInNodes resolvio sin excepcion."
tested: false
tests: []
test_file_path: ""
file_path: "python/functions/browser/comfyui_refresh_nodes_ui.py"
---
## Ejemplo
```python
import sys, os
sys.path.insert(0, os.path.join("python", "functions"))
from ml.comfyui_download_model import comfyui_download_model
from browser.comfyui_refresh_nodes_ui import comfyui_refresh_nodes_ui
# Tras bajar un checkpoint nuevo, refresca los combos para que aparezca en los
# CheckpointLoaderSimple sin recargar la pagina.
comfyui_download_model("https://.../nuevo.safetensors", "checkpoints")
print(comfyui_refresh_nodes_ui()) # -> {'ok': True, 'refreshed': True, 'error': ''}
```
## Cuando usarla
Justo despues de añadir modelos a `~/ComfyUI/models/` (con
`comfyui_download_model` o a mano) para que los nodos de la UI vean los archivos
nuevos en sus combos sin un F5 que perderia el grafo no guardado.
## Gotchas
- Solo refresca combos (listas que vienen de /object_info): checkpoints, loras,
vae, samplers, schedulers. NO recarga el grafo ni cambia los valores ya
seleccionados.
- Si el server no ve aun el archivo nuevo (lo copiaste a la carpeta equivocada o
ComfyUI no reescanea), el combo seguira sin mostrarlo aunque `refreshed=True`:
el refresh fue exitoso pero el catalogo del server no lo incluye.
- Requiere la pestana de ComfyUI abierta en el Chrome con CDP; sin target,
`ok=False`.
@@ -0,0 +1,63 @@
"""Refresca los combos del grafo de ComfyUI desde la UI via CDP.
Llama `app.refreshComboInNodes()`, que vuelve a pedir GET /object_info al server
y actualiza los combos de todos los nodos (lista de checkpoints, loras, vaes,
samplers) sin recargar la pagina. Util tras descargar modelos nuevos con
comfyui_download_model para que aparezcan en los CheckpointLoaderSimple sin un
F5. Compone cdp_eval.
Funcion impura: hace red (CDP WebSocket) y refresca estado de la UI.
"""
import json
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_refresh_nodes_ui(
*,
port: int = 9222,
server_url_substr: str = "8188",
timeout_s: float = 15.0,
) -> dict:
"""Refresca los combos (checkpoints/loras/vae) de los nodos del grafo.
Args:
port: puerto de remote debugging del Chrome diario. Default 9222.
server_url_substr: substring de la URL de la pestana de ComfyUI.
timeout_s: timeout de la conexion CDP en segundos.
Returns:
dict {ok: bool, refreshed: bool, error: str}. refreshed True si
app.refreshComboInNodes resolvio sin excepcion.
"""
expr = (
"(async function(){"
" if(!window.app || typeof app.refreshComboInNodes!=='function'){"
" return {refreshed:false, error:'window.app.refreshComboInNodes no disponible en la pestana'};"
" }"
" try{ await app.refreshComboInNodes(); return {refreshed:true, error:''}; }"
" catch(e){ return {refreshed:false, error:String(e)}; }"
"})()"
)
r = cdp_eval(
expr,
port=port,
target_url_substr=server_url_substr,
await_promise=True,
timeout_s=timeout_s,
)
if not r["ok"]:
return {"ok": False, "refreshed": False, "error": r["error"]}
val = r["value"] or {}
return {
"ok": bool(val.get("refreshed")),
"refreshed": bool(val.get("refreshed")),
"error": val.get("error", ""),
}
if __name__ == "__main__":
print(json.dumps(comfyui_refresh_nodes_ui(), ensure_ascii=False, indent=2))
@@ -0,0 +1,72 @@
---
name: comfyui_set_node_widget_ui
kind: function
lang: py
domain: browser
version: "1.0.0"
purity: impure
signature: "def comfyui_set_node_widget_ui(node: str, widget_name: str, value, *, match: str = 'type', port: int = 9222, server_url_substr: str = '8188', timeout_s: float = 15.0) -> dict"
description: "Edita en vivo el valor de un widget de un nodo del grafo de ComfyUI via CDP. Localiza el nodo en app.graph._nodes por type (comfyClass), id o title; asigna widget.value, invoca widget.callback si existe y marca el canvas dirty. Cubre widgets numericos (steps/cfg/seed) y de texto (CLIPTextEncode.text). Compone cdp_eval. Impura: red (CDP) + muta el grafo."
tags: [comfyui, browser, cdp, ml, image-generation, stable-diffusion, ui-automation]
uses_functions: ["cdp_eval_py_browser"]
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports: ["json"]
params:
- name: node
desc: "Identificador del nodo a localizar, interpretado segun `match`."
- name: widget_name
desc: "Nombre del widget a editar (ej. 'text', 'steps', 'seed', 'cfg', 'sampler_name')."
- name: value
desc: "Nuevo valor (str, int, float o bool). Se serializa a JSON para inyectarlo."
- name: match
desc: "Criterio de busqueda: 'type' (por comfyClass/type, ej. 'CLIPTextEncode'/'KSampler'), 'id' (por n.id) o 'title' (por titulo visible). Default 'type'."
- 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'."
- name: timeout_s
desc: "Timeout de la conexion CDP en segundos. Default 15.0."
output: "dict {ok, matched_nodes (int), set (bool), old_value, new_value, error}. Con match='type' y varios matches, actua sobre el primero y reporta cuantos coincidieron."
tested: false
tests: []
test_file_path: ""
file_path: "python/functions/browser/comfyui_set_node_widget_ui.py"
---
## Ejemplo
```python
import sys, os
sys.path.insert(0, os.path.join("python", "functions"))
from browser.comfyui_set_node_widget_ui import comfyui_set_node_widget_ui
# Cambiar el prompt positivo (widget de texto del CLIPTextEncode) ...
print(comfyui_set_node_widget_ui(
"CLIPTextEncode", "text", "a blue ceramic mug, studio light", match="type"))
# ... y los pasos del sampler (widget numerico).
print(comfyui_set_node_widget_ui("KSampler", "steps", 25, match="type"))
# -> {'ok': True, 'matched_nodes': 2, 'set': True, 'old_value': 20, 'new_value': 25, 'error': ''}
```
## Cuando usarla
Para ajustar parametros de un workflow ya cargado en la UI sin reconstruirlo:
cambiar el prompt, los steps, la seed, el cfg o el sampler en vivo antes de
encolar con `comfyui_queue_prompt_ui`. Es el paso de "tuning" entre
`comfyui_load_workflow_ui` y la cola.
## Gotchas
- Con `match="type"` y un workflow txt2img hay DOS `CLIPTextEncode` (positivo y
negativo): `matched_nodes=2` y solo se edita el primero (el positivo en el grafo
por defecto). Para apuntar al negativo usa `match="id"` o `match="title"`.
- Nodo o widget inexistente NO lanza: devuelve `ok=False`, `set=False` y un
`error` claro ("sin nodo que matchee ..." / "el nodo no tiene widget ...").
- `widget.callback` se invoca con el nuevo valor para propagar el cambio (combos,
derivados); si el callback de un widget concreto espera mas argumentos, el fallo
se traga (try/catch) y el `value` ya queda asignado igualmente.
- El cambio vive en el grafo de la UI; para persistirlo a un archivo exportalo con
`comfyui_export_workflow_ui` o encolalo.
@@ -0,0 +1,103 @@
"""Edita en vivo el valor de un widget de un nodo del grafo de ComfyUI via CDP.
Localiza un nodo en `app.graph._nodes` por su tipo (comfyClass), su id o su
titulo, y asigna el valor del widget cuyo `name` coincide. Cubre tanto widgets
numericos (steps, cfg, seed del KSampler) como de texto (el `text` de un
CLIPTextEncode). Tras asignar `widget.value` invoca `widget.callback` si existe
para propagar el cambio y marca el canvas dirty. Compone cdp_eval.
Funcion impura: hace red (CDP WebSocket) y muta el grafo de la UI.
"""
import json
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_set_node_widget_ui(
node: str,
widget_name: str,
value,
*,
match: str = "type",
port: int = 9222,
server_url_substr: str = "8188",
timeout_s: float = 15.0,
) -> dict:
"""Asigna el valor de un widget de un nodo del grafo en vivo.
Args:
node: identificador del nodo a localizar, interpretado segun `match`.
widget_name: nombre del widget a editar (ej. "text", "steps", "seed",
"cfg", "sampler_name").
value: nuevo valor (str, int, float o bool). Se serializa a JSON.
match: criterio de busqueda del nodo. "type" (por comfyClass/type, ej.
"CLIPTextEncode" o "KSampler"), "id" (por n.id) o "title" (por el
titulo visible del nodo). Default "type".
port: puerto de remote debugging del Chrome diario. Default 9222.
server_url_substr: substring de la URL de la pestana de ComfyUI.
timeout_s: timeout de la conexion CDP en segundos.
Returns:
dict {ok, matched_nodes (int), set (bool), old_value, new_value, error}.
Si `match="type"` produce varios nodos, actua sobre el primero y reporta
cuantos coincidieron en matched_nodes.
"""
expr = (
"(function(){"
" if(!window.app || !app.graph) return {matched_nodes:0, set:false, error:'window.app.graph no disponible'};"
" var nodes = app.graph._nodes || [];"
f" var key = {json.dumps(match)};"
f" var target = {json.dumps(node)};"
f" var wname = {json.dumps(widget_name)};"
f" var nval = {json.dumps(value)};"
" var matches = nodes.filter(function(n){"
" if(key==='id') return String(n.id)===String(target);"
" if(key==='title') return n.title===target;"
" return (n.comfyClass||n.type)===target;"
" });"
" if(matches.length===0) return {matched_nodes:0, set:false, error:'sin nodo que matchee '+key+'='+target};"
" var n = matches[0];"
" var w = (n.widgets||[]).find(function(x){return x.name===wname;});"
" if(!w) return {matched_nodes:matches.length, set:false, error:'el nodo no tiene widget \"'+wname+'\"'};"
" var old = w.value;"
" w.value = nval;"
" if(typeof w.callback==='function'){ try{ w.callback(nval); }catch(e){} }"
" if(typeof app.graph.setDirtyCanvas==='function') app.graph.setDirtyCanvas(true,true);"
" return {matched_nodes:matches.length, set:true, old_value:old, new_value:w.value, error:''};"
"})()"
)
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,
"matched_nodes": 0,
"set": False,
"old_value": None,
"new_value": None,
"error": r["error"],
}
val = r["value"] or {}
return {
"ok": bool(val.get("set")),
"matched_nodes": val.get("matched_nodes", 0),
"set": bool(val.get("set")),
"old_value": val.get("old_value"),
"new_value": val.get("new_value"),
"error": val.get("error", ""),
}
if __name__ == "__main__":
out = comfyui_set_node_widget_ui(
"KSampler", "steps", 25, match="type"
)
print(json.dumps(out, ensure_ascii=False, indent=2))