ComfyUI's own queue and its own socket, not a second one alongside them. A
FunPack queue would be a second thing tracking what is running, and the one the
user believes is whichever spoke last -- so this speaks to /prompt, /interrupt
and /ws as any ComfyUI client does and holds only a view of the server's state.
The graph is asked for, never assembled here, for the same reason.
The client id is remembered: ComfyUI addresses progress to the client that
queued the run, so a reload with a fresh id hears nothing about the generation
already in flight -- the run continues, the GPU time is spent, and the app says
Ready.
Faults found on the way, each in code that already had passing tests:
* `[hidden]` was inert across the whole kit. The UA rule is element-level, so
`.cx-btn { display: inline-flex }` beats it -- Cancel was marked hidden, sat
there, and nothing reported anything. jsdom cannot see it: the attribute was
always correct. One reset rule, and a browser test over four element kinds.
* A panel passed no height to its body and a split pane passed none to its
panes, so the viewer asked for 100% of `auto` and collapsed to a single line
inside a panel that was the right size around it.
* subscribe() delivered the first state outside the guard every later delivery
gets, so one bad listener took down whoever was wiring the UI up -- at
startup, before anything existed to report it.
* A whole tests directory was never run: the test script globbed
composer/tests only. `node --test` discovers them itself now, so there is no
list to forget.
* `empty` was not in the content rule's key list, so three elements were never
asked to prove their empty-state text comes back as text rather than markup.
The dev server now mounts the REAL routes -- register() already took a route
table -- instead of reimplementing them over http.server. It answered POST
/api/pipeline with 501, so the edit-and-queue path could not be looked at, which
is the one thing the dev server exists for.
105 lines
3.8 KiB
Python
105 lines
3.8 KiB
Python
"""Dev server: serves the app exactly as ComfyUI will, without ComfyUI.
|
|
|
|
It mounts the REAL routes -- `core.routes.register` takes a route table rather
|
|
than reaching for ComfyUI's server -- so what the browser gets here is what
|
|
ComfyUI would give it, refusals and all. The previous version reimplemented the
|
|
handlers over http.server, which meant looking at the app exercised a copy: it
|
|
answered GET /api/pipeline with a payload assembled here, and answered POST with
|
|
501, so the whole edit-and-queue path could not be seen at all.
|
|
|
|
python tools/devserver.py [port]
|
|
|
|
There is no queue behind it. Generating reaches /prompt, which ComfyUI serves
|
|
and this does not, and the app says so -- which is the honest answer and worth
|
|
seeing, since that is exactly what a person hitting Generate with ComfyUI down
|
|
would get.
|
|
"""
|
|
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
|
|
|
|
|
def _add_comfyui() -> str:
|
|
"""Put ComfyUI on the path, because most modules import it.
|
|
|
|
Without this the dev server loads only the modules that need nothing from
|
|
ComfyUI -- one out of nine -- and the app looks almost empty while reporting
|
|
nothing wrong. Looking at the app is supposed to be how the app gets tested,
|
|
so the preview has to see what ComfyUI would see.
|
|
"""
|
|
import os
|
|
candidates = [os.environ.get("COMFYUI_ROOT"),
|
|
str(Path.home() / "Documents" / "ComfyUI")]
|
|
for candidate in candidates:
|
|
if candidate and (Path(candidate) / "comfy" / "supported_models.py").is_file():
|
|
sys.path.append(candidate)
|
|
return candidate
|
|
return ""
|
|
|
|
|
|
def _reexec_with_comfyui_python(comfyui: str) -> None:
|
|
"""Restart under ComfyUI's own interpreter when this one cannot import torch.
|
|
|
|
Finding ComfyUI's SOURCE is not enough: the modules import torch, and the
|
|
interpreter that launches this may be a bare system python. Getting that
|
|
wrong is not a crash, it is nine modules quietly absent and an app that
|
|
looks almost empty -- so the server corrects it rather than serving a
|
|
misleading page.
|
|
"""
|
|
import os
|
|
if os.environ.get("FUNPACK_DEVSERVER_REEXEC"):
|
|
return # already tried; do not loop
|
|
try:
|
|
import torch # noqa: F401
|
|
return
|
|
except ImportError:
|
|
pass
|
|
|
|
candidate = Path(comfyui) / "venv" / "bin" / "python"
|
|
if not candidate.is_file():
|
|
print(f" WARNING: {sys.executable} cannot import torch and there is no "
|
|
f"venv at {candidate}. Modules needing it will be absent.")
|
|
return
|
|
|
|
print(f" re-running under {candidate} (this one has no torch)")
|
|
os.environ["FUNPACK_DEVSERVER_REEXEC"] = "1"
|
|
os.execv(str(candidate), [str(candidate), *sys.argv])
|
|
|
|
|
|
COMFYUI = _add_comfyui()
|
|
if COMFYUI:
|
|
_reexec_with_comfyui_python(COMFYUI)
|
|
|
|
from aiohttp import web # noqa: E402
|
|
from core import config, routes as funpack_routes # noqa: E402
|
|
|
|
P = config.UI_PREFIX
|
|
|
|
|
|
def build_app() -> web.Application:
|
|
app = web.Application()
|
|
routes = web.RouteTableDef()
|
|
funpack_routes.register(routes)
|
|
|
|
@routes.get("/")
|
|
async def _root(_req):
|
|
raise web.HTTPFound(P + "/")
|
|
|
|
app.add_routes(routes)
|
|
return app
|
|
|
|
|
|
if __name__ == "__main__":
|
|
port = int(sys.argv[1]) if len(sys.argv) > 1 else 8188
|
|
print(f"FunPack v5 dev server: http://127.0.0.1:{port}{P}/")
|
|
if COMFYUI:
|
|
print(f" with ComfyUI from {COMFYUI}")
|
|
else:
|
|
# Said out loud rather than left to be discovered as a half-empty page.
|
|
print(" WITHOUT ComfyUI: every module that imports it will be absent. "
|
|
"Set COMFYUI_ROOT to fix.")
|
|
print(" no queue behind it: Generate will report that /prompt is not here")
|
|
web.run_app(build_app(), host="127.0.0.1", port=port, print=None)
|