Treat prompts and source media as user-owned inputs, keep MediaHub credentials/config/execution in one reusable middle block, and reserve outputs for Preview/Save nodes. Constraint: Samples must show users exactly which block to copy into an existing workflow. Rejected: Classifying API Key and execution nodes as inputs or outputs | exposes internal mechanics and weakens sample readability. Confidence: high Scope-risk: narrow Directive: Keep sample responsibilities separated and all connections flowing left to right. Tested: 124 pytest tests; JavaScript syntax; Python compilation; JSON validation; live ComfyUI accepted all eight prompt structures. Not-tested: Manual visual import of all eight revised examples.
79 lines
2.0 KiB
Python
79 lines
2.0 KiB
Python
#!/usr/bin/env python3
|
|
"""Apply the canonical three-region layout to user-facing workflows."""
|
|
|
|
import json
|
|
from pathlib import Path
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
|
|
INPUT_X = 80
|
|
CONFIG_X = 620
|
|
EXECUTION_X = 1120
|
|
RESOLVE_X = 1500
|
|
OUTPUT_X = 1900
|
|
|
|
NODE_LAYOUT = {
|
|
"PrimitiveStringMultiline": (INPUT_X, 140, 420, 170),
|
|
"LoadImage": (INPUT_X, 390, 420, 320),
|
|
"MediaHubAPIKey": (CONFIG_X, 100, 340, 72),
|
|
"MediaHubGenerateImage": (EXECUTION_X, 260, 360, 130),
|
|
"MediaHubSubmitVideo": (EXECUTION_X, 300, 320, 130),
|
|
"MediaHubResolveVideo": (RESOLVE_X, 300, 320, 140),
|
|
"PreviewImage": (OUTPUT_X, 220, 460, 380),
|
|
"SaveVideo": (OUTPUT_X, 300, 420, 220),
|
|
}
|
|
|
|
GROUPS = [
|
|
{
|
|
"id": 1,
|
|
"title": "Inputs / 输入",
|
|
"bounding": [40, 40, 500, 780],
|
|
"color": "#3f789e",
|
|
"font_size": 24,
|
|
"flags": {},
|
|
},
|
|
{
|
|
"id": 2,
|
|
"title": "Provider Config / 配置",
|
|
"bounding": [580, 40, 1260, 780],
|
|
"color": "#9e7b3f",
|
|
"font_size": 24,
|
|
"flags": {},
|
|
},
|
|
{
|
|
"id": 3,
|
|
"title": "Outputs / 输出",
|
|
"bounding": [1860, 40, 540, 780],
|
|
"color": "#4b8a55",
|
|
"font_size": 24,
|
|
"flags": {},
|
|
},
|
|
]
|
|
|
|
|
|
def layout_workflow(path: Path) -> None:
|
|
workflow = json.loads(path.read_text())
|
|
for node in workflow["nodes"]:
|
|
node_type = node["type"]
|
|
if node_type.endswith("Config"):
|
|
node["pos"] = [CONFIG_X, 240]
|
|
node["size"] = [420, max(node["size"][1], 300)]
|
|
continue
|
|
if node_type in NODE_LAYOUT:
|
|
x, y, width, height = NODE_LAYOUT[node_type]
|
|
node["pos"] = [x, y]
|
|
node["size"] = [width, height]
|
|
workflow["groups"] = GROUPS
|
|
path.write_text(json.dumps(workflow, ensure_ascii=False, indent=2) + "\n")
|
|
|
|
|
|
def main() -> int:
|
|
for path in sorted((ROOT / "examples").glob("*_preview.json")):
|
|
layout_workflow(path)
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|