Compare commits

...

7 Commits

Author SHA1 Message Date
egutierrez ec0a5e53ac docs(comfyui): remata drift de conteo en comfyui-skill (11→17) y gamedev-2d (36→47)
- gamedev-2d.md: el header decía '31 builders + 5 de apoyo' (=36); inventario real = 47
  funciones (36 builders: 31 de generación + 5 de transformación; 11 de apoyo: post-proceso,
  puente a Godot, style presets, pipelines one-shot).
- comfyui-skill.md: añade bloque de tamaño del grupo (17 funciones tag comfyui-skill); la
  página no tenía conteo interno (el 11 obsoleto vivía solo en INDEX.md).
- INDEX.md: gamedev-2d 36→47 y comfyui-skill 11→17, con descripciones actualizadas.

Cierra el drift residual señalado en el report 0210.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 07:33:08 +02:00
egutierrez 604d3d4feb docs(comfyui): higiene de capability pages — drift 29→126 + styles + build_flux + parallax
- comfyui.md: bloque de tamaño real del grupo (126 funciones tag comfyui: 63 puras,
  50 impuras, 13 pipelines) con punteros a los sub-grupos (comfyui-skill, comfyui-styles,
  comfyui-judge, gamedev-2d). Corrige la firma corta de build_flux (variant/steps=None/
  weight_dtype='default' + camino custom-advanced) que arrastraba drift del report 0205.
  Añade sección Styles con las 5 funciones del sub-grupo.
- comfyui-styles.md (NUEVA): página madre del sub-grupo de estilo (catálogo WAS +
  style presets gamedev), tabla de las 5 funciones, ejemplos canónicos alineados con
  los retornos reales y fronteras.
- comfyui-overview.md: añade audio (05b) y styles (04b) al mapa cross-grupo y a la tabla
  resumen; referencia las nuevas páginas madre comfyui-styles y gamedev-2d.
- INDEX.md: comfyui 29→126 con descripción actualizada; nueva fila comfyui-styles.
- comfyui_build_parallax_background_workflow.md: añade sección ## Ejemplo lanzable
  (el indexer extrae example del cuerpo, no del frontmatter) — cobertura del grupo
  pasa a 126/126 con ejemplo.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 07:27:32 +02:00
egutierrez 643ebfb849 fix(comfyui): comfyui_interrupt_queue firma sin keyword-only para que fn run la despache 2026-06-28 04:55:39 +02:00
egutierrez 537516e32e merge(comfyui): comfyui_interrupt_queue — control de cola (interrupt + clear_pending) 2026-06-28 04:54:46 +02:00
egutierrez ca07b25297 feat(comfyui): comfyui_interrupt_queue v1.1.0 — clear_pending + cleared/queue_remaining + tests
Alinea la funcion al contrato de control de cola (punto 3 del roadmap ComfyUI):
- firma keyword-only: clear_pending (vacia pendientes con POST /queue {clear:true}) + timeout
- output {ok, interrupted, cleared, queue_remaining, error}; GET /queue al final
- no lanza en fallo de red: degrada a {ok:False, error}
- test con mock HTTP local (golden + clear + cola vacia + error path), 4/4 verde
- .md autosuficiente con gotchas + capability growth log

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 04:54:14 +02:00
egutierrez fbbff7d5e7 chore: auto-commit (1 archivos)
- logs/ardour_mcp_server.log

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-28 04:48:32 +02:00
egutierrez bdd841d9af merge(comfyui): higiene — 5 funciones de la sesión en capability page + tests list_templates/extract_template 2026-06-28 04:47:48 +02:00
11 changed files with 487 additions and 50 deletions
+4 -3
View File
@@ -14,7 +14,7 @@ Indice de grupos de capacidades del registry. Cada grupo agrupa >=3 funciones qu
| Grupo | N | Que cubre |
|---|---|---|
| [gamedev-2d](gamedev-2d.md) | 36 | Assets 2D para Godot via ComfyUI: 31 builders de workflow (pixelart/seamless/iso/sprite/topdown/card/enemy/prop/structure/foliage/trap/projectile/decal/particle/rune/weather/badge/skill-tree/dialogue/icon/portrait/VFX...) + 5 de apoyo: post-proceso (pixelize, luma->alpha) + puente de assets a Godot 4 (.import + reimport headless). Tag canonico `gamedev-2d` (antes `gamedev`, ya unificado) |
| [gamedev-2d](gamedev-2d.md) | 47 | Assets 2D para Godot via ComfyUI: 36 builders de workflow (31 de generación desde texto: pixelart/seamless/iso/sprite/topdown/card/enemy/prop/structure/foliage/trap/projectile/decal/particle/rune/weather/badge/skill-tree/dialogue/icon/portrait/VFX... + 5 de transformación desde imagen: asset_variant/sprite_from_sketch/inpaint_asset/outpaint_asset/directional_sprite) + 11 de apoyo: post-proceso (pixelize, luma->alpha, flatten_alpha), puente de assets a Godot 4 (.import + reimport headless), style presets (get/apply_gamedev_style_preset) y pipelines one-shot (asset_pack/character_set/styled_asset). Tag canonico `gamedev-2d` (antes `gamedev`, ya unificado) |
| [gamedev-engine](gamedev-engine.md) | 8 | Runtime de juego C++ multiplataforma (PC + WebAssembly): SDL3 + sokol_gfx + miniaudio. Game loop fixed-timestep, camara 2D, input unificado (teclado/gamepad/touch), sprite batch, setup de render/audio y build a wasm. Grupo hermano de `gamedev-2d` (este ejecuta el juego, aquel genera los assets) |
| [registry](registry.md) | 17 | Auditoria y monitorizacion del propio registry: copied-code, uses-functions, unused, proposals, telemetria |
| [systemd](systemd.md) | 14 | Generar, instalar, restart y status de unit files systemd via SSH (deploys a VPS) |
@@ -72,8 +72,9 @@ Indice de grupos de capacidades del registry. Cada grupo agrupa >=3 funciones qu
| [seo](seo.md) | 3 | SEO orientado a datos sobre Google Search Console: autenticar con service account (`gsc_auth`), extraer Search Analytics paginado (`pull_gsc_search_analytics`) y el pipeline de ingesta a DuckDB + espejo Postgres para Metabase (`ingest_gsc_search_analytics`). Cadena de ingesta del proyecto `seo_analytics`; alimenta dashboards de striking distance, CTR opportunities y content decay |
| [local-hub](local-hub.md) | 4 | Exponer los procesos locales como subdominios `*.localhost` (via Caddy, sin DNS) y reunirlos en una pantalla principal Glance con estado en vivo, refrescada a diario por dag_engine. Descubre servicios (manifiesto + registry), renderiza Caddyfile + config Glance (puras), y el pipeline `refresh_local_hub` regenera+recarga. Fuente de verdad: `apps/local_hub/local_services.yaml` |
| [comfyui-judge](comfyui-judge.md) | 4 | Panel multi-juez de calidad de imagen: estético LAION-V2 (`comfyui_score_aesthetic`, 0-10) + fidelidad CLIP prompt↔imagen (`comfyui_score_clip_alignment`, 0-1) + crítica LLM-vision (`comfyui_critique_image_llm`, good/bad). Agregados por voto mayoría en `comfyui_judge_image`. Gate objetivo para tests/DoD y el bucle de mejora de skills ComfyUI; degrada con gracia si un juez cae. Jueces estético/fidelidad por subproceso al venv ComfyUI (torch+open_clip), crítico via claude-direct |
| [comfyui](comfyui.md) | 29 | Controlar ComfyUI (Stable Diffusion por grafos) de dos formas: por API HTTP (build_txt2img_workflow puro → submit → wait → object_info; download_model con validación Civitai/HF) y por la UI web vía CDP sobre la pestaña abierta (load_workflow_ui, set_node_widget_ui para tunear prompt/steps/seed en vivo, queue_prompt_ui = botón Queue Prompt, export_workflow_ui, refresh_nodes_ui). El API format es el puente entre ambos caminos. Las funciones de UI componen `cdp_eval`. Incluye imagen→3D nativo (Hunyuan3D-2, tag `img-to-3d`): build_image_to_3d_workflow + fetch_output_mesh + install_3d_model + pipeline image_to_3d_oneshot |
| [comfyui-skill](comfyui-skill.md) | 11 | Tratar una configuración de generación ComfyUI como una skill: receta versionada en disco (checkpoint + LoRAs + params + scaffold de prompt + post-proceso) que se compila a un workflow cambiando solo el subject. Save/load/list de recetas, bucle de mejora genera→juzga→bump con gate objetivo (el score del juez decide qué se promueve), export de la skill a grafo cargable en el navegador, y cosecha de Civitai (extract_recipe_from_png + harvest oneshot) que destila el workflow embebido de una imagen pública en una skill candidata |
| [comfyui](comfyui.md) | 126 | Controlar ComfyUI (Stable Diffusion por grafos) de dos formas: por API HTTP (build_txt2img_workflow puro → submit → wait → object_info; download_model con validación Civitai/HF) y por la UI web vía CDP sobre la pestaña abierta (load_workflow_ui, set_node_widget_ui para tunear prompt/steps/seed en vivo, queue_prompt_ui = botón Queue Prompt, export_workflow_ui, refresh_nodes_ui). El API format es el puente entre ambos caminos. Las funciones de UI componen `cdp_eval`. Cubre txt2img/img2img/inpaint/controlnet/sdxl-refiner/flux, upscale + hires-fix + facedetailer, vídeo (LTX/Wan/SVD), audio (ACE-Step), imagen→3D nativo (Hunyuan3D-2) + post-proceso de malla, templates oficiales, civitai harvest y control de cola. N = funciones con tag `comfyui` (incluye los sub-grupos `comfyui-skill`/`comfyui-styles` y 45 de `gamedev-2d`); las páginas madre de cada sub-grupo desglosan su parte |
| [comfyui-skill](comfyui-skill.md) | 17 | Tratar una configuración de generación ComfyUI como una skill: receta versionada en disco (checkpoint + LoRAs + params + scaffold de prompt + post-proceso) que se compila a un workflow cambiando solo el subject. Save/load/list de recetas, bucle de mejora genera→juzga→bump con gate objetivo (el score del juez decide qué se promueve), export de la skill a grafo cargable en el navegador, y cosecha de Civitai (extract_recipe_from_png + harvest oneshot) que destila el workflow embebido de una imagen pública en una skill candidata |
| [comfyui-styles](comfyui-styles.md) | 5 | Capa de estilo reutilizable sobre los builders ComfyUI. Catálogo WAS (tag `comfyui-styles`): `curated_styles_catalog` (~190 estilos), `generate_styles_llm` (genera estilos por LLM via ask_llm), `append_styles` (merge+dedup+backup sobre el styles.json del selector WAS). Style presets gamedev (tag `gamedev-2d`): `get_gamedev_style_preset` (gameboy/ghibli/pixel-art-retro como datos puros) + `apply_style_preset` (preset+subject → kwargs de un builder gamedev-2d). El estilo se trata como dato curado, no como prompt repetido |
| [comfyui-overview](comfyui-overview.md) | — | Mapa cross-grupo de las capacidades de generación ComfyUI (txt2img, img2img/inpaint, controlnet, skills/multiestilo-LoRA, video, upscale/detail, 3D, juez, operación): cada capacidad → builders/pipelines del registry + grafos UI + skills que la cubren. Índice de entrada al stack ComfyUI; las firmas y gotchas viven en `comfyui.md`/`comfyui-skill.md`/`comfyui-judge.md`. Catálogo navegable de los grafos en disco (subcarpetas por capacidad) en `~/ComfyUI/CAPABILITIES.md` |
## Como anadir grupo
+14
View File
@@ -8,7 +8,9 @@ Las tres páginas madre detalladas siguen siendo la fuente de verdad por grupo:
- [comfyui.md](comfyui.md) — grupo `comfyui`: builders de workflow, ejecución HTTP, UI vía CDP, I/O.
- [comfyui-skill.md](comfyui-skill.md) — grupo `comfyui-skill`: recetas de estilo versionadas.
- [comfyui-styles.md](comfyui-styles.md) — grupo `comfyui-styles`: presets + catálogo de estilo (selector WAS).
- [comfyui-judge.md](comfyui-judge.md) — grupo `comfyui-judge`: panel multi-juez de calidad.
- [gamedev-2d.md](gamedev-2d.md) — grupo `gamedev-2d`: 47 builders de assets 2D para Godot (45 también `comfyui`).
El catálogo navegable con los grafos concretos en disco (subcarpetas por capacidad, cómo cargar
cada uno) vive **fuera del repo**, junto a la instalación: `~/ComfyUI/CAPABILITIES.md`. Este doc es
@@ -25,7 +27,9 @@ Filtros MCP: `mcp__registry__fn_search query="" tag="comfyui"` (y `tag="comfyui-
| 02 | **img2img / inpaint** | imagen → imagen, regenerar zona enmascarada | `build_img2img`, `build_inpaint` | ✅ | — |
| 03 | **controlnet** | generación guiada por mapa (depth/pose/canny) | `build_controlnet` | ✅ | — |
| 04 | **skills (multiestilo/LoRA)** | recetas de estilo reproducibles con `{subject}` | `build_skill_workflow`, `inject_lora`, `generate_with_skill_oneshot`, `harvest_civitai_skill_oneshot` | ✅ ×2 | ✅ ×2 |
| 04b | **styles (presets/catálogo)** | estilo reutilizable: catálogo WAS + presets gamedev | `curated_styles_catalog`, `generate_styles_llm`, `append_styles`, `get_gamedev_style_preset`, `apply_style_preset` | — | — |
| 05 | **video** | imagen/texto → vídeo (SVD, LTX, Wan) | `build_img2vid`, `build_video` | ✅ | — |
| 05b | **audio** | texto → música/SFX/voz (ACE-Step) | `build_audio_workflow`, `fetch_output_audio` | — | — |
| 06 | **upscale / detail** | ampliar y recuperar detalle (ESRGAN, hires-fix, FaceDetailer) | `build_upscale`, `build_hires_fix`, `inject_hires_fix`, `build_facedetailer` | — | — |
| 07 | **3D** | imagen/texto → malla 3D (Hunyuan3D) + limpieza | `build_image_to_3d`, `build_textured_3d_multiview`, `image_to_3d_oneshot`, `text_to_3d_oneshot`, `mesh_cleanup_oneshot` | — | — |
| 08 | **juez / calidad** | puntuar lo generado (gate de DoD y bucle de mejora) | `judge_image`, `score_aesthetic`, `score_clip_alignment`, `critique_image_llm` | — | — |
@@ -67,6 +71,16 @@ sus IDs reales cuando se ejecute `fn index`.
- `comfyui_extract_recipe_from_png_py_ml` — destila un PNG de Civitai en receta candidata.
- CRUD + telemetría: `comfyui_list_skills_py_ml`, `comfyui_load_skill_py_ml`, `comfyui_save_skill_py_ml`, `comfyui_update_skill_score_py_ml`, `comfyui_bump_skill_version_py_ml`.
### 04b · styles (presets / catálogo)
Página madre: [comfyui-styles.md](comfyui-styles.md). Estilo reutilizable como dato, no como prompt repetido.
- `comfyui_curated_styles_catalog_py_ml` (pura) — catálogo curado (~190 estilos) para el selector WAS.
- `comfyui_generate_styles_llm_py_ml` (impura) — genera N estilos de una categoría vía `ask_llm`.
- `comfyui_append_styles_py_ml` (impura) — fusiona estilos sobre el `styles.json` WAS (merge+dedup+backup).
- `comfyui_get_gamedev_style_preset_py_ml` (pura) — receta de *style preset* gamedev (gameboy/ghibli/pixel-art-retro).
- `comfyui_apply_style_preset_py_ml` (pura) — traduce un preset + subject a los kwargs de un builder gamedev-2d.
### 05 · video
- `comfyui_build_img2vid_workflow_py_ml` (pura) — SVD: condicionamiento por CLIP_VISION (sin prompt de texto).
+7
View File
@@ -12,6 +12,13 @@ submit/wait). Una skill no es un workflow: es la *receta* que compila a uno.
Filtro MCP: `mcp__registry__fn_search query="" tag="comfyui-skill"`.
> **Tamaño del grupo (al 28/06/2026):** 17 funciones con tag `comfyui-skill` — CRUD de recetas
> (save/load/list), compilación a workflow (`build_skill_workflow`), inyectores encadenables
> (`inject_hires_fix`/`inject_multi_lora`, `build_ipadapter_workflow`), bucle de mejora
> genera→juzga→bump (`generate_with_skill_oneshot` + `update_skill_score` + `bump_skill_version`),
> export a grafo (`export_skill_template`), mixer de capacidades (`compose_capabilities` +
> `generate_mixed_oneshot`) y cosecha de Civitai (`extract_recipe_from_png` + `harvest_civitai_skill_oneshot`).
## Qué es una skill
Una receta vive en `~/ComfyUI/skills_library/<slug>/` y la manipulan las funciones de este grupo:
+101
View File
@@ -0,0 +1,101 @@
# ComfyUI Styles — presets y catálogo de estilo
Tag: `comfyui-styles` (+ `gamedev-2d` para los dos presets gamedev). Sub-grupo de
[`comfyui`](comfyui.md) que añade una **capa de estilo reutilizable** sobre los builders de
workflow: en vez de repetir a mano los mismos modificadores de cámara/iluminación/render en cada
prompt, el estilo se trata como un dato curado y reusable.
Dos vertientes complementarias:
- **Catálogo WAS** (`comfyui-styles`): ~190 estilos curados en el formato exacto del selector WAS de
ComfyUI (*Prompt Styles Selector* / *Prompt Multiple Styles Selector*), generación de estilos
nuevos por LLM, y fusión segura sobre el `styles.json` del usuario.
- **Style presets gamedev** (`gamedev-2d`): recetas que empaquetan como datos puros el *look* de un
juego entero (prefijo/sufijo de prompt, checkpoint, LoRA, negative, tamaño, post-proceso) y se
traducen a los kwargs que consume un builder de sujeto del grupo [`gamedev-2d`](gamedev-2d.md).
Filtro MCP: `mcp__registry__fn_search query="" tag="comfyui-styles"` (catálogo WAS) y
`mcp__registry__fn_search query="style preset" tag="gamedev-2d"` (presets gamedev).
## Funciones del grupo
### Catálogo WAS — dominio `ml` (tag `comfyui-styles`)
| ID | Firma corta | Qué hace |
|---|---|---|
| [comfyui_curated_styles_catalog_py_ml](../../python/functions/ml/comfyui_curated_styles_catalog.md) | `curated_styles_catalog(category=None) -> dict` | Catálogo curado (~190 estilos) en el formato exacto `{nombre: {prompt, negative_prompt}}` que consume el selector WAS. Cada `prompt` son modificadores de estilo potentes (cámara, lente, iluminación, render engine, medio artístico, paleta, mood), no descripciones de escena. Filtra por `category`. **Pura**. |
| [comfyui_generate_styles_llm_py_ml](../../python/functions/ml/comfyui_generate_styles_llm.md) | `generate_styles_llm(category, n=8, prefix='', avoid=None, model='claude-haiku-4-5-20251001') -> dict` | Genera N estilos de una categoría temática usando `ask_llm` (grupo claude-direct, API directa, arranque 0), en el mismo formato `{nombre: {prompt, negative_prompt}}`. `avoid` evita duplicar nombres ya existentes. **Impura** (LLM). |
| [comfyui_append_styles_py_ml](../../python/functions/ml/comfyui_append_styles.md) | `append_styles(new_styles, styles_path=DEFAULT_STYLES_PATH, overwrite=False, backup=True, dry_run=False) -> dict` | Fusiona (merge + dedup por nombre) un dict de estilos sobre el `styles.json` del selector WAS de forma SEGURA y NO destructiva: preserva todos los existentes (ganan salvo `overwrite=True`), hace backup con timestamp antes de escribir. `dry_run=True` previsualiza sin tocar disco. **Impura** (I/O disco). |
### Style presets gamedev — dominio `ml` (tag `gamedev-2d`)
| ID | Firma corta | Qué hace |
|---|---|---|
| [comfyui_get_gamedev_style_preset_py_ml](../../python/functions/ml/comfyui_get_gamedev_style_preset.md) | `get_gamedev_style_preset(name=None) -> dict` | Devuelve la receta de un *style preset* gamedev curado (`gameboy`, `ghibli`, `pixel-art-retro`) o el catálogo de nombres si `name=None`. Un preset empaqueta como DATOS puros el look de un juego entero: `subject_prefix`/`suffix`, `style`, `negative`, checkpoint recomendado, LoRA + strength, `size`, `transparent`, post-proceso. **Pura**. |
| [comfyui_apply_style_preset_py_ml](../../python/functions/ml/comfyui_apply_style_preset.md) | `apply_style_preset(preset, subject, *, style=None, negative=None) -> dict` | Traduce un *style preset* gamedev (de `get_gamedev_style_preset`) + un `subject` del usuario a lo que necesita un builder de sujeto del grupo gamedev-2d: el subject combinado con el prefijo/sufijo del estilo y los kwargs comunes (`style`, `checkpoint`, `lora`, `lora_strength`, `negative`, resolución) listos para `**spread`. `style`/`negative` permiten override puntual. **Pura**. |
## Ejemplo canónico — generar un estilo, fusionarlo y aplicarlo
Dos flujos típicos: (1) ampliar el catálogo del selector WAS, y (2) usar un preset gamedev para
generar un asset con look consistente.
### A) Ampliar el catálogo WAS con estilos nuevos por LLM
```python
import sys, os
sys.path.insert(0, os.path.join("python", "functions"))
from ml.comfyui_generate_styles_llm import comfyui_generate_styles_llm
from ml.comfyui_append_styles import comfyui_append_styles
# 1. Pedir 6 estilos de una categoría. Devuelve el dict {nombre: {prompt, negative_prompt}}
# directo (best-effort: {} si el LLM falla).
nuevos = comfyui_generate_styles_llm("film noir cinematic", n=6, prefix="noir-")
# 2. Previsualizar la fusión (no escribe), luego aplicar con backup.
if nuevos:
print(comfyui_append_styles(nuevos, dry_run=True)["total_after"]) # nº tras fusionar, sin tocar disco
res = comfyui_append_styles(nuevos) # backup + merge + dedup + escritura
print(res["total_before"], "->", res["total_after"], "añadidos:", len(res["added"]))
```
### B) Aplicar un style preset gamedev a un sujeto
```python
import sys, os
sys.path.insert(0, os.path.join("python", "functions"))
from ml.comfyui_get_gamedev_style_preset import comfyui_get_gamedev_style_preset
from ml.comfyui_apply_style_preset import comfyui_apply_style_preset
from ml.comfyui_build_enemy_creature_workflow import comfyui_build_enemy_creature_workflow
preset = comfyui_get_gamedev_style_preset("gameboy") # receta pura del look Game Boy
ap = comfyui_apply_style_preset(preset, "a wizard casting a spell")
# ap = {subject, builder_kwargs, size, transparent, post, ...} listo para un builder gamedev-2d:
wf = comfyui_build_enemy_creature_workflow(
ap["subject"], size=ap["size"], transparent=ap["transparent"], **ap["builder_kwargs"]
)
```
El catálogo curado completo se consulta sin red (devuelve el dict plano directo):
```python
from ml.comfyui_curated_styles_catalog import comfyui_curated_styles_catalog
print(comfyui_curated_styles_catalog("__categories__")) # {'categories': {...}, 'total': 190}
todos = comfyui_curated_styles_catalog() # dict {nombre: {prompt, negative_prompt}}
print(len(todos), list(todos)[:5])
```
## Fronteras
- **No genera imágenes**: este sub-grupo produce y gestiona DATOS de estilo (dicts de prompt /
negative, presets). Generar el asset es trabajo de los builders del grupo [`comfyui`](comfyui.md)
y [`gamedev-2d`](gamedev-2d.md), o de los pipelines oneshot (p.ej.
`comfyui_generate_styled_asset_oneshot_py_pipelines`, que compone un preset + un builder + submit).
- **El catálogo WAS asume el custom node WAS instalado**: `append_styles` escribe sobre el
`styles.json` que lee el selector WAS en la UI. Sin ese node, el catálogo sigue siendo usable como
dict de modificadores, pero el selector no aparecerá en el grafo.
- **Los dos presets gamedev (`get`/`apply`) llevan tag `gamedev-2d`**, no `comfyui-styles`: son la
vía de estilo para los builders de assets de juego, no para el selector WAS genérico. Se listan
aquí por afinidad de capacidad (estilo reutilizable).
- **Formato exacto**: el dict de estilos es `{nombre: {prompt, negative_prompt}}`. Los prompts son
modificadores (cámara/lente/luz/render/medio/paleta/mood), no descripciones de escena — la escena
la pone el `subject` del usuario.
+28 -1
View File
@@ -13,6 +13,17 @@ Tag: `comfyui`. Grupo de funciones para controlar [ComfyUI](https://github.com/c
Filtro MCP: `mcp__registry__fn_search query="" tag="comfyui"`.
> **Tamaño del grupo (al 28/06/2026):** 126 funciones con tag `comfyui` (63 puras, 50 impuras,
> 13 pipelines). El grupo se reparte en sub-grupos con página madre propia:
> [`comfyui-skill`](comfyui-skill.md) (recetas de estilo versionadas),
> [`comfyui-styles`](comfyui-styles.md) (presets + catálogo de estilo para el selector WAS),
> [`comfyui-judge`](comfyui-judge.md) (panel de calidad) y
> [`gamedev-2d`](gamedev-2d.md) (assets 2D para Godot: 47 funciones, 45 de ellas también `comfyui`).
> Esta página documenta el **núcleo** (lifecycle del server, API HTTP, builders, I/O de workflows,
> imagen→3D, UI por CDP, audio, templates); los builders específicos de gamedev-2d viven en su
> propia página. El mapa cross-grupo de capacidades está en
> [comfyui-overview.md](comfyui-overview.md).
## Dos caminos, mismo motor
```
@@ -44,7 +55,7 @@ El **API format** (dict de nodos numerados que produce `build_txt2img_workflow`
| ID | Firma corta | Qué hace |
|---|---|---|
| [comfyui_build_txt2img_workflow_py_ml](../../python/functions/ml/comfyui_build_txt2img_workflow.md) | `build_txt2img_workflow(ckpt_name, positive, negative='', *, steps, cfg, width, height, seed, ...) -> dict` | Construye el dict del workflow txt2img básico (Checkpoint → CLIPTextEncode×2 + EmptyLatent → KSampler → VAEDecode → SaveImage) en API format. **Pura**. |
| [comfyui_build_flux_workflow_py_ml](../../python/functions/ml/comfyui_build_flux_workflow.md) | `build_flux_workflow(prompt, *, unet='flux1-schnell-fp8-e4m3fn.safetensors', clip_l, t5xxl, vae='ae.safetensors', width=1024, height=1024, steps=4, guidance=3.5, seed, weight_dtype='fp8_e4m3fn', ...) -> dict` | Builder txt2img para **Flux** (schnell/dev): UNETLoader + DualCLIPLoader (clip_l + t5xxl, type flux) + VAELoader → CLIPTextEncode → FluxGuidance + EmptySD3LatentImage → KSampler (cfg fijo 1.0) → VAEDecode → SaveImage. La guía va por FluxGuidance, no por el cfg. fp8 + ~4 pasos para 8 GB. **Pura**. |
| [comfyui_build_flux_workflow_py_ml](../../python/functions/ml/comfyui_build_flux_workflow.md) | `build_flux_workflow(prompt, *, variant='schnell', width=1024, height=1024, steps=None, guidance=3.5, seed=0, unet_name=None, clip_l_name='clip_l.safetensors', t5xxl_name='t5xxl_fp8_e4m3fn_scaled.safetensors', vae_name='ae.safetensors', weight_dtype='default', sampler_name='euler', scheduler='simple', ...) -> dict` | Builder txt2img para **Flux** (`variant='schnell'` o `'dev'`): UNETLoader + DualCLIPLoader (clip_l + t5xxl, type flux) + VAELoader → CLIPTextEncode → FluxGuidance + EmptySD3LatentImage → camino custom-advanced (RandomNoise + KSamplerSelect + BasicScheduler → BasicGuider → SamplerCustomAdvanced) → VAEDecode → SaveImage. La guía va por FluxGuidance, no por el cfg. `steps=None` autoselecciona por variante (~4 schnell); `unet_name=None` deduce el checkpoint de la variante; `weight_dtype='default'`. **Pura**. |
| [comfyui_object_info_py_ml](../../python/functions/ml/comfyui_object_info.md) | `object_info(server='127.0.0.1:8188', node_class=None, timeout) -> dict` | Catálogo de nodos del server: inputs, tipos y enums (lista de checkpoints/samplers visibles). Para validar antes de enviar. Impura. |
| [comfyui_submit_workflow_py_ml](../../python/functions/ml/comfyui_submit_workflow.md) | `submit_workflow(workflow, server, client_id, timeout) -> dict` | Encola un workflow API format vía POST /prompt; devuelve `prompt_id` + posición en cola. HTTP 400 propaga la validación por nodo. Impura. |
| [comfyui_wait_result_py_ml](../../python/functions/ml/comfyui_wait_result.md) | `wait_result(prompt_id, server, timeout, poll_interval) -> dict` | Sondea GET /history/{prompt_id} hasta que termina; devuelve los outputs (PNGs con filename/subfolder/type). Impura. |
@@ -207,6 +218,22 @@ un error accionable, sin lanzar.
| [comfyui_list_templates_py_ml](../../python/functions/ml/comfyui_list_templates.md) | `list_templates(comfyui_python=None, bundle=None, name_filter=None, with_nodes=True, workflows_only=True, limit=0) -> dict` | Lista los templates oficiales con su grafo: nombre, bundle/categoría, path en disco, `n_nodes` y `node_types` (class_types reales, aplanando subgrafos y descartando UUID de instancia). Filtra por bundle/nombre; excluye entradas no-workflow por defecto. Impura (lee disco vía el intérprete de ComfyUI). |
| [comfyui_extract_template_py_ml](../../python/functions/ml/comfyui_extract_template.md) | `extract_template(name, comfyui_python=None, to_api=False, server='127.0.0.1:8188') -> dict` | Extrae el grafo completo (formato UI) + `class_types` de un template por su `template_id`. `to_api=True` lo convierte a API format vía `comfyui_import_workflow_json` (requiere servidor ComfyUI vivo). Nombre inexistente → `ok=False` con sugerencias cercanas, sin traceback. Impura. |
### Estilos — presets y catálogo (sub-grupo `comfyui-styles`)
Capa de **estilo reutilizable** sobre los builders: un catálogo curado de ~190 modificadores de
estilo para el selector WAS (Prompt Styles Selector), generación de estilos por LLM, y *style
presets* gamedev (gameboy, ghibli, pixel-art-retro) que empaquetan como datos puros el look de un
juego entero (prefijo/sufijo de prompt, checkpoint, LoRA, negative, tamaño). Página madre dedicada:
[comfyui-styles.md](comfyui-styles.md). Las 5 funciones:
| ID | Firma corta | Qué hace |
|---|---|---|
| [comfyui_curated_styles_catalog_py_ml](../../python/functions/ml/comfyui_curated_styles_catalog.md) | `curated_styles_catalog(category=None) -> dict` | Catálogo curado (~190 estilos) en formato `{nombre: {prompt, negative_prompt}}` para el selector WAS. Cada prompt son modificadores potentes (cámara, lente, iluminación, render, medio, paleta). **Pura**. |
| [comfyui_generate_styles_llm_py_ml](../../python/functions/ml/comfyui_generate_styles_llm.md) | `generate_styles_llm(category, n=8, prefix='', avoid=None, model='claude-haiku-4-5-...') -> dict` | Genera N estilos nuevos de una categoría temática vía `ask_llm` (grupo claude-direct), en el mismo formato del selector WAS. **Impura**. |
| [comfyui_append_styles_py_ml](../../python/functions/ml/comfyui_append_styles.md) | `append_styles(new_styles, styles_path=..., overwrite=False, backup=True, dry_run=False) -> dict` | Fusiona (merge + dedup) estilos nuevos sobre el `styles.json` del selector WAS de forma NO destructiva: preserva los existentes (salvo `overwrite`), backup con timestamp. **Impura**. |
| [comfyui_get_gamedev_style_preset_py_ml](../../python/functions/ml/comfyui_get_gamedev_style_preset.md) | `get_gamedev_style_preset(name=None) -> dict` | Devuelve la receta de un *style preset* gamedev curado (gameboy, ghibli, pixel-art-retro) o el catálogo de nombres si `name=None`. Empaqueta el look como datos puros. **Pura**. |
| [comfyui_apply_style_preset_py_ml](../../python/functions/ml/comfyui_apply_style_preset.md) | `apply_style_preset(preset, subject, *, style=None, negative=None) -> dict` | Traduce un *style preset* gamedev + un subject del usuario a los kwargs que consume un builder de sujeto del grupo gamedev-2d (subject combinado + `**kwargs` listos para spread). **Pura**. |
## Ejemplo canónico end-to-end (build → load → tune → queue → resultado)
Combina API + UI: construyes el workflow por API, lo cargas en la UI del usuario, ajustas el
+3 -2
View File
@@ -11,8 +11,9 @@ Cluster de funciones para producir y mover assets 2D de juego entre **ComfyUI**
3. **Puente de assets** (CPU): coloca el resultado en un proyecto Godot
con sus import settings.
Tag único del grupo: `gamedev-2d` (los 31 builders de workflow + las 5 funciones de
apoyo de post-proceso y puente). El tag plano `gamedev` quedó deprecado y unificado a
Tag único del grupo: `gamedev-2d` **47 funciones**: 36 builders de workflow (31 de
generación desde texto + 5 de transformación desde una imagen de entrada) + 11 de apoyo
(post-proceso, puente a Godot, style presets y pipelines one-shot). El tag plano `gamedev` quedó deprecado y unificado a
`gamedev-2d`. El **runtime de juego C++** (el motor que ejecuta el juego: game loop,
cámara, input, render por lotes, audio) vive en el grupo hermano `gamedev-engine`.
Filtro: `mcp__registry__fn_search query="" tag="gamedev-2d"`.
File diff suppressed because one or more lines are too long
@@ -78,6 +78,21 @@ CheckpointLoaderSimple -> ... -> KSampler -> VAEDecode --IMAGE--+-> SaveImage (f
`-> DepthAnythingV2Preprocessor -> SaveImage (depth)
```
## Ejemplo
```python
import sys, os
sys.path.insert(0, os.path.join("python", "functions"))
from ml.comfyui_build_parallax_background_workflow import comfyui_build_parallax_background_workflow
# Fondo apaisado + su mapa de profundidad, 4 bandas de parallax (función pura, sin red).
wf = comfyui_build_parallax_background_workflow("forest at dusk, fantasy", layers=4, seed=7)
# El dict API format trae DOS SaveImage: el fondo y el depth map. Encólalo con submit_workflow.
saves = [n for n in wf.values() if n.get("class_type") == "SaveImage"]
print(len(saves), "SaveImage (fondo + depth)") # 2
```
## Cuando usarla
Cuando necesites el fondo de un nivel 2D con scroll parallax y quieras las capas
+43 -22
View File
@@ -3,10 +3,10 @@ name: comfyui_interrupt_queue
kind: function
lang: py
domain: ml
version: "1.0.0"
version: "1.1.0"
purity: impure
signature: "def comfyui_interrupt_queue(server: str = \"127.0.0.1:8188\") -> dict"
description: "Corta la generacion en curso de ComfyUI (POST /interrupt) y devuelve el estado de la cola (GET /queue). Devuelve {ok, interrupted, queue_running, queue_pending, error}. NO lanza excepcion en fallo de red: degrada a {ok: False, error}. /interrupt corta solo el prompt en ejecucion, no vacia los pendientes. Impura: HTTP POST + GET, solo stdlib (urllib, json)."
signature: "def comfyui_interrupt_queue(*, clear_pending: bool = False, server: str = \"127.0.0.1:8188\", timeout: float = 10.0) -> dict"
description: "Corta la generacion en curso de ComfyUI (POST /interrupt) y, si clear_pending=True, vacia ademas la cola de pendientes (POST /queue {\"clear\":true}). Consulta GET /queue al final para reportar queue_remaining. Devuelve {ok, interrupted, cleared, queue_remaining, error}. NO lanza excepcion en fallo de red: degrada a {ok: False, error}. /interrupt corta solo el prompt en ejecucion, no vacia los pendientes salvo clear_pending. Impura: HTTP POST + GET, solo stdlib (urllib, json)."
tags: [comfyui, ml, queue, interrupt, control, http]
uses_functions: []
uses_types: []
@@ -15,12 +15,16 @@ returns_optional: false
error_type: "error_go_core"
imports: []
params:
- name: clear_pending
desc: "keyword-only. Si True, ademas de cortar el prompt en ejecucion vacia la cola de pendientes con POST /queue {\"clear\":true}. Default False."
- name: server
desc: "host:port del servidor ComfyUI sin esquema (default '127.0.0.1:8188')."
output: "dict con ok (bool, True si interrupt + lectura de cola OK), interrupted (bool, True si POST /interrupt respondio), queue_running (int, prompts ejecutandose), queue_pending (int, prompts encolados), error (str, vacio si todo OK)."
tested: false
tests: []
test_file_path: ""
desc: "keyword-only. host:port del servidor ComfyUI sin esquema (default '127.0.0.1:8188')."
- name: timeout
desc: "keyword-only. Timeout de cada peticion HTTP en segundos (default 10.0)."
output: "dict con ok (bool, True si interrupt + clear (si se pidio) + lectura de cola OK), interrupted (bool, True si POST /interrupt respondio), cleared (bool, True si clear_pending y POST /queue {clear:true} respondio; False si no se pidio o fallo), queue_remaining (int, queue_running + queue_pending tras la operacion), error (str, vacio si todo OK)."
tested: true
tests: ["test_interrumpe_sin_vaciar", "test_clear_pending_vacia_cola", "test_clear_pending_cola_vacia_no_rompe", "test_servidor_caido_no_lanza"]
test_file_path: "python/functions/ml/tests/test_comfyui_interrupt_queue.py"
file_path: "python/functions/ml/comfyui_interrupt_queue.py"
---
@@ -31,30 +35,47 @@ import sys, os
sys.path.insert(0, os.path.join(os.environ["HOME"], "fn_registry", "python", "functions"))
from ml.comfyui_interrupt_queue import comfyui_interrupt_queue
# Solo cortar el prompt en ejecucion (los pendientes siguen):
res = comfyui_interrupt_queue()
# {'ok': True, 'interrupted': True, 'queue_running': 0, 'queue_pending': 0, 'error': ''}
if res["ok"] and res["interrupted"]:
print(f"cortado; pendientes en cola: {res['queue_pending']}")
# {'ok': True, 'interrupted': True, 'cleared': False, 'queue_remaining': 3, 'error': ''}
# Cortar el actual Y vaciar los pendientes de golpe:
res = comfyui_interrupt_queue(clear_pending=True)
# {'ok': True, 'interrupted': True, 'cleared': True, 'queue_remaining': 0, 'error': ''}
if res["ok"]:
print(f"cortado; quedan {res['queue_remaining']} en cola")
```
O lanzable directo con: `./fn run comfyui_interrupt_queue`.
O lanzable directo: `./fn run comfyui_interrupt_queue` · `./fn run comfyui_interrupt_queue --clear`.
## Cuando usarla
Para abortar una generacion que se esta tomando demasiado, que tira de mas VRAM de
la prevista, o tras encolar por error un workflow pesado. Tambien para inspeccionar
de un vistazo cuanto queda en cola (`queue_running` / `queue_pending`) sin parsear
el JSON de /queue a mano. Es el freno de mano del round-trip build -> submit -> wait.
la prevista, o tras encolar por error un workflow pesado. Con `clear_pending=True`
es el freno de mano completo: corta el actual y borra todo lo encolado en una sola
llamada (sin tener que encadenar `comfyui_queue_manage("clear")` despues). Tras la
operacion `queue_remaining` dice de un vistazo cuanto queda en cola.
## Gotchas
- `/interrupt` corta SOLO el prompt en ejecucion; los pendientes (`queue_pending`)
siguen y el siguiente arranca de inmediato. Para vaciar la cola entera hay que
llamar `POST /queue` con `{"clear": true}` (no lo hace esta funcion — solo corta
+ lee).
- `/interrupt` corta SOLO el prompt en ejecucion; sin `clear_pending` los pendientes
(`queue_pending`) siguen y el siguiente arranca de inmediato. Pasa
`clear_pending=True` para vaciar tambien la cola (POST /queue {"clear": true}).
- No es idempotente en el sentido de "sin efecto": si hay algo ejecutandose, lo
mata. Si la cola esta vacia, el interrupt es inocuo (interrupted=True igual).
mata. Si la cola esta vacia, tanto el interrupt como el clear son inocuos
(`interrupted=True`/`cleared=True` igual, `queue_remaining=0`).
- `queue_remaining` se lee al FINAL (GET /queue tras interrupt+clear): es
`queue_running + queue_pending`. Justo tras un interrupt sin clear puede ser >0
porque el siguiente pendiente ya arranco.
- En fallo de red NO lanza: devuelve `ok=False` con el mensaje en `error`. Comprueba
`ok` antes de fiarte de los conteos.
`ok` antes de fiarte de `queue_remaining`.
- Tras el interrupt conviene liberar VRAM con `POST /free` si vas a encolar otro
trabajo pesado (esta funcion no lo hace).
trabajo pesado (esta funcion no lo hace; ver el round-trip build -> submit -> wait).
- Para operaciones de cola mas finas (borrar UN prompt por id, contar el historial)
usa `comfyui_queue_manage`; esta funcion se centra en el interrupt + clear masivo.
## Capability growth log
- v1.1.0 (2026-06-28) — anade flag `clear_pending` (vacia la cola en la misma
llamada) + param `timeout`; el output pasa a {ok, interrupted, cleared,
queue_remaining, error} y se anaden tests (mock HTTP local).
+57 -22
View File
@@ -1,38 +1,52 @@
"""Interrumpe la generacion en curso de ComfyUI y devuelve el estado de la cola.
"""Interrumpe la generacion en curso de ComfyUI y, opcionalmente, vacia la cola.
Funcion impura: hace red (HTTP POST /interrupt + GET /queue). Solo stdlib.
Funcion impura: hace red (HTTP POST /interrupt, POST /queue, GET /queue). Solo
stdlib (urllib, json).
POST /interrupt corta el prompt que ComfyUI esta ejecutando ahora mismo (no vacia
la cola: los prompts pendientes siguen). GET /queue devuelve queue_running (lo que
se ejecuta) y queue_pending (lo encolado). Esta funcion combina ambos en un dict
honesto que NO lanza excepcion en fallo de red: devuelve {ok: False, error}.
POST /interrupt corta el prompt que ComfyUI esta ejecutando ahora mismo: NO vacia
los pendientes, solo aborta el actual y el siguiente arranca de inmediato. Para
vaciar de golpe los pendientes hay que ademas hacer POST /queue con {"clear": true}
(lo que activa el flag clear_pending). GET /queue se consulta al final para reportar
cuantos trabajos quedan en cola tras la operacion (queue_remaining).
NO lanza excepcion en fallo de red: devuelve un dict de estado {ok: False, error}.
"""
import json
import urllib.error
import urllib.request
def comfyui_interrupt_queue(server: str = "127.0.0.1:8188") -> dict:
"""Interrumpe la generacion en curso y devuelve el estado de la cola.
def comfyui_interrupt_queue(
clear_pending: bool = False,
server: str = "127.0.0.1:8188",
timeout: float = 10.0,
) -> dict:
"""Corta la generacion en curso de ComfyUI y devuelve el estado de la cola.
Args:
clear_pending: si True, ademas de cortar el prompt en ejecucion vacia la
cola de pendientes con POST /queue {"clear": true}. keyword-only.
server: host:port del servidor ComfyUI sin esquema (default
"127.0.0.1:8188").
"127.0.0.1:8188"). keyword-only.
timeout: timeout de cada peticion HTTP en segundos (default 10.0).
keyword-only.
Returns:
dict con:
- ok (bool): True si tanto el interrupt como la lectura de la cola
tuvieron exito.
- ok (bool): True si el interrupt, la lectura de la cola y (si se pidio)
el clear tuvieron exito.
- interrupted (bool): True si el POST /interrupt respondio sin error.
- queue_running (int): numero de prompts ejecutandose ahora mismo.
- queue_pending (int): numero de prompts encolados pendientes.
- cleared (bool): True si clear_pending era True y el POST /queue
{"clear": true} respondio sin error; False si no se pidio o fallo.
- queue_remaining (int): trabajos que quedan en cola tras la operacion
(queue_running + queue_pending segun GET /queue al final).
- error (str): mensaje de error si algo fallo; cadena vacia si todo OK.
"""
out = {
"ok": False,
"interrupted": False,
"queue_running": 0,
"queue_pending": 0,
"cleared": False,
"queue_remaining": 0,
"error": "",
}
base = f"http://{server}"
@@ -40,19 +54,37 @@ def comfyui_interrupt_queue(server: str = "127.0.0.1:8188") -> dict:
# 1. POST /interrupt (cuerpo vacio): corta el prompt en ejecucion.
try:
req = urllib.request.Request(f"{base}/interrupt", data=b"", method="POST")
with urllib.request.urlopen(req, timeout=10.0):
with urllib.request.urlopen(req, timeout=timeout):
out["interrupted"] = True
except urllib.error.URLError as exc:
reason = getattr(exc, "reason", exc)
out["error"] = f"interrupt fallo: no se pudo conectar a {base}/interrupt: {reason}"
return out
# 2. GET /queue: estado actual de la cola tras el interrupt.
# 2. Opcional: POST /queue {"clear": true} para vaciar los pendientes.
if clear_pending:
try:
payload = json.dumps({"clear": True}).encode()
req = urllib.request.Request(
f"{base}/queue",
data=payload,
method="POST",
headers={"Content-Type": "application/json"},
)
with urllib.request.urlopen(req, timeout=timeout):
out["cleared"] = True
except urllib.error.URLError as exc:
reason = getattr(exc, "reason", exc)
out["error"] = f"clear fallo: no se pudo conectar a {base}/queue: {reason}"
return out
# 3. GET /queue: cuantos trabajos quedan en cola tras la operacion.
try:
with urllib.request.urlopen(f"{base}/queue", timeout=10.0) as resp:
with urllib.request.urlopen(f"{base}/queue", timeout=timeout) as resp:
data = json.loads(resp.read())
out["queue_running"] = len(data.get("queue_running", []))
out["queue_pending"] = len(data.get("queue_pending", []))
running = len(data.get("queue_running", []))
pending = len(data.get("queue_pending", []))
out["queue_remaining"] = running + pending
out["ok"] = True
except urllib.error.URLError as exc:
reason = getattr(exc, "reason", exc)
@@ -63,9 +95,12 @@ def comfyui_interrupt_queue(server: str = "127.0.0.1:8188") -> dict:
if __name__ == "__main__":
res = comfyui_interrupt_queue()
import sys
clear = "--clear" in sys.argv[1:]
res = comfyui_interrupt_queue(clear_pending=clear)
print(
f"ok={res['ok']} interrupted={res['interrupted']} "
f"running={res['queue_running']} pending={res['queue_pending']} "
f"cleared={res['cleared']} queue_remaining={res['queue_remaining']} "
f"error={res['error']!r}"
)
@@ -0,0 +1,149 @@
"""Tests de comfyui_interrupt_queue contra un servidor ComfyUI simulado.
La funcion es pura I/O (HTTP), asi que levantamos un http.server local que imita
los endpoints relevantes de ComfyUI (/interrupt, /queue) y verificamos:
- Golden: interrupt sin clear corta el actual pero NO vacia los pendientes.
- Edge: clear_pending=True vacia la cola (queue_remaining=0).
- Edge: clear_pending=True con la cola ya vacia no rompe.
- Error: si el servidor no responde, devuelve {ok:False, error} sin lanzar.
"""
import http.server
import json
import os
import socket
import sys
import threading
sys.path.insert(0, os.path.dirname(__file__))
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", ".."))
from ml.comfyui_interrupt_queue import comfyui_interrupt_queue
class _FakeComfyHandler(http.server.BaseHTTPRequestHandler):
"""Imita ComfyUI: estado de cola mutable compartido via la clase del server."""
def log_message(self, *args): # silenciar el log del servidor en los tests
pass
def _send_json(self, obj, code=200):
body = json.dumps(obj).encode()
self.send_response(code)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
def do_POST(self):
st = self.server.state
if self.path == "/interrupt":
st["running"] = [] # interrupt corta el prompt en ejecucion
self._send_json({})
return
if self.path == "/queue":
length = int(self.headers.get("Content-Length", 0))
raw = self.rfile.read(length) if length else b"{}"
body = json.loads(raw or b"{}")
if body.get("clear"):
st["pending"] = [] # clear vacia los pendientes
elif "delete" in body:
st["pending"] = [
p for p in st["pending"] if p not in body["delete"]
]
self._send_json({})
return
self._send_json({"error": "not found"}, code=404)
def do_GET(self):
st = self.server.state
if self.path == "/queue":
self._send_json(
{
"queue_running": st["running"],
"queue_pending": st["pending"],
}
)
return
self._send_json({"error": "not found"}, code=404)
def _start_fake_server(running, pending):
"""Levanta el servidor fake en un puerto efimero. Devuelve (server, addr, thread)."""
server = http.server.HTTPServer(("127.0.0.1", 0), _FakeComfyHandler)
server.state = {"running": list(running), "pending": list(pending)}
thread = threading.Thread(target=server.serve_forever, daemon=True)
thread.start()
host, port = server.server_address
return server, f"{host}:{port}", thread
def _free_port():
"""Reserva y libera un puerto para garantizar que NADA escucha ahi (error path)."""
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(("127.0.0.1", 0))
port = s.getsockname()[1]
s.close()
return port
def test_interrumpe_sin_vaciar():
# Golden: 1 ejecutandose + 2 pendientes; interrupt corta el actual, pendientes siguen.
server, addr, _ = _start_fake_server(running=["r1"], pending=["p1", "p2"])
try:
res = comfyui_interrupt_queue(server=addr)
finally:
server.shutdown()
assert res["ok"] is True
assert res["interrupted"] is True
assert res["cleared"] is False
# running cortado (0) + 2 pendientes que siguen = 2 restantes.
assert res["queue_remaining"] == 2
assert res["error"] == ""
def test_clear_pending_vacia_cola():
# Edge: clear_pending vacia los pendientes -> queue_remaining 0.
server, addr, _ = _start_fake_server(running=["r1"], pending=["p1", "p2", "p3"])
try:
res = comfyui_interrupt_queue(clear_pending=True, server=addr)
finally:
server.shutdown()
assert res["ok"] is True
assert res["interrupted"] is True
assert res["cleared"] is True
assert res["queue_remaining"] == 0
assert res["error"] == ""
def test_clear_pending_cola_vacia_no_rompe():
# Edge: clear_pending con la cola ya vacia es inocuo, no rompe.
server, addr, _ = _start_fake_server(running=[], pending=[])
try:
res = comfyui_interrupt_queue(clear_pending=True, server=addr)
finally:
server.shutdown()
assert res["ok"] is True
assert res["interrupted"] is True
assert res["cleared"] is True
assert res["queue_remaining"] == 0
assert res["error"] == ""
def test_servidor_caido_no_lanza():
# Error: nada escucha en el puerto -> {ok:False, error} sin excepcion cruda.
dead = f"127.0.0.1:{_free_port()}"
res = comfyui_interrupt_queue(server=dead, timeout=1.0)
assert res["ok"] is False
assert res["interrupted"] is False
assert res["error"] != ""
assert "interrupt fallo" in res["error"]
if __name__ == "__main__":
test_interrumpe_sin_vaciar()
test_clear_pending_vacia_cola()
test_clear_pending_cola_vacia_no_rompe()
test_servidor_caido_no_lanza()
print("OK: 4 tests passed")