Old project/workflow names and browser settings migrate on load; /funpack redirects to /fun. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016acRss69eH1235eRdFTZHY
155 lines
6.1 KiB
Python
155 lines
6.1 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("FUNUI_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["FUNUI_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 funui_routes # noqa: E402
|
|
|
|
P = config.UI_PREFIX
|
|
|
|
|
|
def _register_nodes() -> str:
|
|
"""Put ComfyUI's node registry, and FunUI's own nodes, where the routes
|
|
can see them.
|
|
|
|
Without this the pipeline is entirely "there is no node called X installed"
|
|
and nothing that edits a node has anything to edit -- so the one thing the
|
|
dev server exists for, looking at the app, cannot reach the half of the app
|
|
that matters. ComfyUI itself does both of these at startup; this is the same
|
|
two steps without the rest of ComfyUI.
|
|
|
|
This also loads comfy_extras (MiniMaxH3SigmaShift, CreateVideo, ...) and,
|
|
if present, the KJNodes pack (ImageTransformKJ) -- without them a preset
|
|
that names one of those nodes looks broken here specifically, not because
|
|
the preset is wrong, but because this server stopped one step short of
|
|
what `pytest`'s own `registered` fixture already does for the real thing.
|
|
"""
|
|
import asyncio
|
|
|
|
try:
|
|
import nodes as comfy_nodes
|
|
except Exception as exc: # noqa: BLE001
|
|
return f" WITHOUT ComfyUI's nodes ({exc}): the pipeline will look empty"
|
|
|
|
extras_note = ""
|
|
try:
|
|
asyncio.run(comfy_nodes.init_extra_nodes(init_custom_nodes=False))
|
|
except Exception as exc: # noqa: BLE001
|
|
extras_note = f"; comfy_extras WITHOUT loading fully ({exc})"
|
|
|
|
kjnodes_note = ""
|
|
kjnodes = Path(COMFYUI) / "custom_nodes" / "ComfyUI-KJNodes" if COMFYUI else None
|
|
if kjnodes and kjnodes.is_dir():
|
|
try:
|
|
asyncio.run(comfy_nodes.load_custom_node(str(kjnodes), module_parent="custom_nodes"))
|
|
except Exception as exc: # noqa: BLE001
|
|
kjnodes_note = f"; KJNodes WITHOUT loading ({exc})"
|
|
else:
|
|
kjnodes_note = "; KJNodes not installed, skipped"
|
|
|
|
from core import nodes as funui_nodes
|
|
ours = funui_nodes.install_into(comfy_nodes.NODE_CLASS_MAPPINGS,
|
|
comfy_nodes.NODE_DISPLAY_NAME_MAPPINGS)
|
|
return (f" {len(comfy_nodes.NODE_CLASS_MAPPINGS)} nodes registered "
|
|
f"({len(ours)} of them FunUI's){extras_note}{kjnodes_note}")
|
|
|
|
|
|
def build_app() -> web.Application:
|
|
# Matches ComfyUI's own --max-upload-size default (100MB, server.py) --
|
|
# aiohttp's bare default is 1MB, which made every real-sized upload here
|
|
# fail in a way production never would, on a ceiling nothing declared.
|
|
app = web.Application(client_max_size=100 * 1024 * 1024)
|
|
routes = web.RouteTableDef()
|
|
funui_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"FunUI v5 dev server: http://127.0.0.1:{port}{P}/")
|
|
print(_register_nodes())
|
|
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)
|