3.2 KiB
FreezeFrame – Copilot Instructions
What This Is
FreezeFrame is a ComfyUI custom node extension that adds pausable sampler nodes. It wraps ComfyUI's standard KSampler API, injecting a per-step callback that polls a shared PAUSE_STATE dict. A frontend JS extension adds PAUSE/RESUME buttons to each node in the LiteGraph canvas UI.
Architecture
| File | Role |
|---|---|
__init__.py |
Package entry point: registers the aiohttp API route (POST /comfy/pause_signal), exports NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS, WEB_DIRECTORY |
nodes.py |
Four node classes (PSampler, PSamplerAdvanced, PSamplerCustom, PSamplerCustomAdvanced) |
shared.py |
Global PAUSE_STATE dict + state_lock (threading.Lock) shared between the async API handler and the synchronous sampler callback |
js/pausing_bridge.js |
ComfyUI frontend extension — injects PAUSE/RESUME widgets into each node via app.registerExtension |
Data flow: Button click → api.fetchApi("/comfy/pause_signal") → aiohttp route in __init__.py → writes PAUSE_STATE["command"] under state_lock → intercept_callback in nodes.py polls the state every 0.1 s and busy-waits while "PAUSE".
ComfyUI Node Conventions
Every node class must define:
INPUT_TYPES(s)– classmethod returning a dict with"required"(and optionally"optional") keysRETURN_TYPES– tuple of type strings (e.g.("LATENT",))FUNCTION– string naming the method ComfyUI will callCATEGORY– display category in the node menu (all nodes here use"ComfyPause")- The execution method named by
FUNCTION
NODE_CLASS_MAPPINGS and NODE_DISPLAY_NAME_MAPPINGS in __init__.py must be kept in sync with any new or renamed node classes. WEB_DIRECTORY = "./js" tells ComfyUI to serve files from the js/ folder.
Pause Mechanism Pattern
All four nodes use the same intercept_callback pattern — wrap the preview callback then poll PAUSE_STATE:
def intercept_callback(step, x0, x, total_steps):
if preview_callback: preview_callback(step, x0, x, total_steps)
if PAUSE_STATE.get("command") == "PAUSE":
while PAUSE_STATE.get("command") == "PAUSE":
time.sleep(0.1)
Reading PAUSE_STATE without the lock is intentional — CPython's GIL makes dict reads safe; the lock is only needed when writing.
Frontend (LiteGraph) Conventions
- LiteGraph renders on a
<canvas>— CSS margins don't apply. Use the ghost widget trick to create vertical spacing:const w = this.addWidget("label", "", ""); w.computeSize = () => [0, height]; - Buttons are added via
this.addWidget("button", label, null, callback). - Use
api.fetchApi(path, options)(from../../scripts/api.js) for backend calls, notfetch. - Extend
nodeType.prototype.onNodeCreated(not override) — always call the original first.
Linting
ruff check . # lint
ruff check . --fix # auto-fix
Ruff is the only configured linter (.ruff_cache is present; no pyproject.toml ruff config section, so defaults apply).
Installation / Runtime
This package is not pip-installed. It runs by being placed inside ComfyUI/custom_nodes/. There is no standalone test suite — testing requires a running ComfyUI instance.