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:
@@ -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))
|
||||
Reference in New Issue
Block a user