Files
Gokay Aydogan eac683ddcc feat: v2.3.0 — durable job inbox, provenance, in-canvas cost UI
- Durable Job Inbox: every async submit is journaled to the shared
  sqlite store and survives ComfyUI restarts; the Fal Job Inbox node
  lists pending/collected jobs and outputs the newest pending
  request_id/endpoint for one-wire recovery via Result by Request ID.
- Provenance: Fal Save Media from URL writes a <file>.fal.json sidecar
  and embeds a fal_provenance PNG text chunk (preserving existing text
  chunks and ICC profiles), returning the request_id as a second output;
  new Fal Provenance from File node reads saved files back into
  endpoint_id + request_id for free re-materialization.
- First web extension (web/ + /fal_api server routes): per-node cost
  pills rendered above every priced fal node (live estimates on
  free-typed endpoint fields), a fal sidebar tab with session spend,
  account balance and a job list with copy/cancel, and in-canvas
  endpoint search with price labels. All features degrade silently on
  older frontends; routes are exception-guarded and cached.

Review fixes: PNG provenance embed preserves source text chunks and ICC
profile; jobs route limit clamped.

15 new tests (91 total).
2026-07-02 15:17:25 +03:00

57 lines
1.5 KiB
Python

import importlib
import importlib.util
node_list = [
"image_node",
"video_node",
"llm_node",
"vlm_node",
"trainer_node",
"upscaler_node",
"platform_node",
"billing_node",
"inbox_node",
]
NODE_CLASS_MAPPINGS = {}
NODE_DISPLAY_NAME_MAPPINGS = {}
for module_name in node_list:
imported_module = importlib.import_module(f".nodes.{module_name}", __name__)
NODE_CLASS_MAPPINGS = {**NODE_CLASS_MAPPINGS, **imported_module.NODE_CLASS_MAPPINGS}
NODE_DISPLAY_NAME_MAPPINGS = {
**NODE_DISPLAY_NAME_MAPPINGS,
**imported_module.NODE_DISPLAY_NAME_MAPPINGS,
}
try:
from .nodes.dynamic import get_dynamic_mappings
dyn_classes, dyn_display = get_dynamic_mappings()
# static nodes win on any key collision
for k, v in dyn_classes.items():
NODE_CLASS_MAPPINGS.setdefault(k, v)
for k, v in dyn_display.items():
NODE_DISPLAY_NAME_MAPPINGS.setdefault(k, v)
except Exception as _dynamic_error: # never break static nodes
import logging
logging.getLogger(__name__).error(
"Failed to load dynamic fal nodes: %s", _dynamic_error
)
WEB_DIRECTORY = "./web"
try:
from .nodes import server_routes as _server_routes # noqa: F401 registers /fal_api routes
except Exception as _routes_error: # never break node loading over HTTP extras
import logging
logging.getLogger(__name__).warning(
"fal API server routes not registered: %s", _routes_error
)
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS", "WEB_DIRECTORY"]