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>
82 lines
4.0 KiB
Python
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 |