Files
JasonHoku-ComfyUI-Ultimate-…/html_generator.py
T
Jason HokuandClaude Fable 5.1 e37e8e861c feat: Dashboard Upscale phase 2 (inline editing, cancel) + embed a ComfyUI nodes workflow in every image (Roadmap 93, 71)
Item 71: comfy_nodes_workflow.py is an exact Python port of the Revise view's "copy as nodes" graph
builder (pinned to a golden file generated from the JS, now in resources/logic_comfynodes.js) plus
lossless PNG tEXt / WebP EXIF writers in the format ComfyUI's frontend reads on drop. The Builder's new
Run Setting "Embed ComfyUI Nodes Workflow" (session_settings.pack_nodes_metadata) stamps every saved
image at every save site (generation, remote VAE, inline/deferred upscales, dashboard upscales, worker
images via a master pass after collection); export_favorites falls back to the Python builder.

Item 93: the upscale modal gets step/pipeline reordering, inline validation (mirrored server-side,
400 on bad configs), a preset bar (Update / Save as / Revert / Delete, read-modify-write), "Run once
(unsaved)", an output summary, separate Cancel and Close, a running lock and reattach after closing.
Cancel now lands within one sampler step through a per-job unet function wrapper on a cloned model
(never the process-wide interrupt flag), keeps completed outputs, and sends the final progress event.
Also fixed: the dashboard received the session's OLDEST items as "new" after an upscale (slice at the
wrong end), the poll targeted the wrong button, jobs were never pruned, two jobs could rewrite one
session's manifest (409 guard).

Known quirk recorded, not changed: a final pipeline step with several ratio/denoise values saves only
the last combination; the summary and a warning now say so.

Tests: 101 python, 59 node, 9 Playwright.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-04 19:01:49 -08:00

82 lines
4.0 KiB
Python

import os
import json
def get_html_template(title, manifest_data, node_id, standalone=False, inline_manifest=False):
"""Build the dashboard page.
By default the page is a SHELL: no item data is inlined (an empty `fullManifest` flagged
`__uscg_lazy__`), and the SPA fetches `/config_tester/manifest?session=<title>` itself. That
keeps the HTML (and ComfyUI's `executed` websocket message / history entry, which carries the
whole string) ~500 KB instead of growing with the session. `manifest_data` is only used when
`inline_manifest=True` (old behaviour; offline export / tests).
"""
# 1. Normalize Data
if not inline_manifest:
manifest_data = {"items": [], "meta": {"model": "", "positive": "", "negative": ""},
"session_name": str(title), "__uscg_lazy__": True}
elif isinstance(manifest_data, list):
manifest_data = {"items": manifest_data, "meta": {"model": "", "positive": "", "negative": ""}}
# Indent JSON for readability
json_str = json.dumps(manifest_data, indent=2)
# The manifest is inlined inside a <script> element: a note/prompt containing "</script>" would
# end it early (page break / script injection). "<\/" is an equivalent JSON escape, so
# JSON.parse (web/dashboard.js's incremental refresh) still reads the original text.
json_str = json_str.replace("</", "<\\/")
# 2. Resolve File Paths
current_dir = os.path.dirname(__file__)
resource_dir = os.path.join(current_dir, "resources")
# Define JS Load Order (Dependencies first!)
js_files = [
"logic_state.js", # Global variables
"logic_utils.js", # API & Helper functions
"logic_ratings.js", # Pairwise review: pure logic (normalisers, Bradley-Terry, pairing, scoreboard)
"logic_wfparams.js", # Workflow Sweep: wf_params helpers (pure; must load before logic_ui/logic_pipeline)
"logic_dragdrop.js", # Revise-modal image drag/send helpers (issue #22; pure, node-testable)
"logic_comfynodes.js", # buildComfyNodesWorkflow (pure; node-testable, parity-pinned to comfy_nodes_workflow.py)
"logic_ui.js", # DOM Creation, Modals, JSON Bars
"logic_upscale_edit.js", # Upscale modal: pure helpers (step defaults, validation, moveItem, preset shape; node-testable)
"logic_upscale_modal.js", # Upscale Modal (preset management, async upscale)
"logic_virtual.js", # The Virtual Scroll Engine & Viewport
"logic_pipeline.js", # Sorting & Filtering Logic
"logic_compare.js", # Pairwise review: compare overlay, votes, scoreboard modal
"logic_events.js", # Event Listeners (Message, Keyboard)
"logic_init.js", # Initialization
"logic_standalone.js" # Standalone-page mode (own websocket, run API); must stay LAST
]
try:
# Load HTML
with open(os.path.join(resource_dir, "template.html"), "r", encoding="utf-8") as f:
html_template = f.read()
# Load CSS
with open(os.path.join(resource_dir, "report.css"), "r", encoding="utf-8") as f:
css_content = f.read()
# Load and Concatenate JS
js_content = ""
for js_file in js_files:
file_path = os.path.join(resource_dir, js_file)
with open(file_path, "r", encoding="utf-8") as f:
js_content += f"\n/* --- {js_file} --- */\n"
js_content += f.read() + "\n"
except FileNotFoundError as e:
return f"<h1>Error: Resource files not found.</h1><p>{e}</p>"
# 3. Inject Data
wrapped_json = f"/*__JSON_START__*/\nlet fullManifest = {json_str};\n/*__JSON_END__*/"
# 4. Final Replacements
final_html = html_template \
.replace("__TITLE__", str(title)) \
.replace("__NODE_ID__", str(node_id)) \
.replace("__STANDALONE_FLAG__", "true" if standalone else "false") \
.replace("let fullManifest = __JSON_DATA__;", wrapped_json) \
.replace("__CSS_CONTENT__", css_content) \
.replace("__JS_CONTENT__", js_content)
return final_html