From 8973b3f7dd7377be3d0817fba2ba1b10fe95449e Mon Sep 17 00:00:00 2001 From: 0velia <130620218+0velia@users.noreply.github.com> Date: Mon, 13 Jul 2026 01:21:13 +0700 Subject: [PATCH] Add files via upload --- generator/app.py | 171 ++++++++++++++++++++++++++++++++++----- generator/generator.html | 2 +- 2 files changed, 151 insertions(+), 22 deletions(-) diff --git a/generator/app.py b/generator/app.py index 872843d..ffa5a46 100644 --- a/generator/app.py +++ b/generator/app.py @@ -1,30 +1,159 @@ import os import sys +import json import webview -def get_resource_path(relative_path): - """ Get the absolute path to the resource, working correctly both - during local development and after compilation inside PyInstaller """ - try: - # PyInstaller creates a temporary folder and stores the path in _MEIPASS - base_path = sys._MEIPASS - except Exception: - base_path = os.path.dirname(os.path.realpath(__file__)) - return os.path.join(base_path, relative_path) +class Api: + def __init__(self): + # Resolve project directories correctly relative to the execution root + self.base_dir = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) + self.lists_dir = os.path.join(self.base_dir, "lists") + + if not os.path.exists(self.lists_dir): + os.makedirs(self.lists_dir) -if __name__ == "__main__": - # Locate the embedded HTML generator file - html_path = get_resource_path("generator.html") + def save_list_asset(self, filename, content): + """Catches text asset streams from the UI and commits them to the local filesystem.""" + try: + # Ensure safe filename structure + safe_name = os.path.basename(filename) + if not safe_name.endswith('.txt'): + safe_name += '.txt' + + target_path = os.path.join(self.lists_dir, safe_name) + with open(target_path, "w", encoding="utf-8") as f: + f.write(content) + + print(f"[Backend] Synced list asset: {safe_name}") + return {"status": "success", "message": f"Saved {safe_name}"} + except Exception as e: + print(f"[Backend Error] Failed to save asset: {str(e)}") + return {"status": "error", "message": str(e)} + + def build_node(self, slots_json): + """Generates a highly robust, dynamic custom node pipeline __init__.py inside the workspace.""" + try: + slots = json.loads(slots_json) + print(f"[Backend] Compiling custom node architecture for {len(slots)} configuration slots.") + + # Formulate code block template to deploy into __init__.py + code_template = f'''import os +import glob + +class DynamicDropdownNode: + @classmethod + def INPUT_TYPES(cls): + base_dir = os.path.dirname(os.path.realpath(__file__)) + lists_dir = os.path.join(base_dir, "lists") + + inputs = {{ + "required": {{}}, + "optional": {{ + "input_string": ("STRING", {{"forceInput": True}}), + "clip": ("CLIP",), + }} + }} + + # Statically declared base order sequence built via Desktop Companion App + ui_order = {[slot['name'].replace(" ", "_").lower() for slot in slots]} + + # Pre-seed slots from directory + if os.path.exists(lists_dir): + # Dynamic lookup for lists assets + for key in ui_order: + file_path = os.path.join(lists_dir, f"{{key}}.txt") + if os.path.exists(file_path): + with open(file_path, "r", encoding="utf-8") as f: + options = [line.strip() for line in f.readlines() if line.strip()] + inputs["required"][key] = (options if options else ["None"],) + + # Grab any floating text assets dropped into the folder later + all_txt_files = glob.glob(os.path.join(lists_dir, "*.txt")) + for file_path in all_txt_files: + file_name = os.path.basename(file_path).replace(".txt", "") + if file_name not in inputs["required"]: + with open(file_path, "r", encoding="utf-8") as f: + options = [line.strip() for line in f.readlines() if line.strip()] + inputs["required"][file_name] = (options if options else ["None"],) + + # Fallback layout validation layer + if not inputs["required"]: + inputs["required"]["placeholder"] = (["(Add your .txt files inside lists/)"],) + + return inputs + + RETURN_TYPES = ("STRING", "CONDITIONING") + RETURN_NAMES = ("PROMPT_STRING", "CONDITIONING") + FUNCTION = "execute" + CATEGORY = "🎭 Dynamic Dropdowns" + + def execute(self, clip=None, input_string="", **kwargs): + selections = [] + + # Bundle values matching the keyword arguments mapping order sequence + for key, val in kwargs.items(): + clean_val = str(val).strip() + if clean_val and clean_val != "None" and not clean_val.startswith("("): + selections.append(clean_val) + + if input_string and input_string.strip(): + selections.insert(0, input_string.strip()) + + final_string = ", ".join(selections) + # Deep scrub structural syntax blocks + final_string = final_string.replace('"', '').replace('\\\\', '').replace(', ,', ',') + + conditioning = [] + if clip is not None: + tokens = clip.tokenize(final_string) + cond, pooled = clip.encode_from_tokens(tokens, return_pooled=True) + conditioning = [[cond, {{"pooled_output": pooled}}]] + + return (final_string, conditioning) + +NODE_CLASS_MAPPINGS = {{ + "DynamicDropdownNode": DynamicDropdownNode +}} + +NODE_DISPLAY_NAME_MAPPINGS = {{ + "DynamicDropdownNode": "🎭 Dynamic Dropdowns" +}} +''' + # Deploy output direct to root configuration layout context path + target_init_path = os.path.join(self.base_dir, "__init__.py") + with open(target_init_path, "w", encoding="utf-8") as f: + f.write(code_template) + + print(f"[Backend] Custom node compilation committed to target path: {target_init_path}") + return {"status": "success", "message": "Node built successfully!"} + except Exception as e: + print(f"[Backend Error] Node building sequence failure: {str(e)}") + return {"status": "error", "message": str(e)} + +def load_ui_interface(): + # Resolve absolute resource paths to point directly to interface documents + current_dir = os.path.dirname(os.path.realpath(__file__)) + html_ui_path = os.path.join(current_dir, "generator.html") - # Create the standalone window environment + if not os.path.exists(html_ui_path): + print(f"[Fatal Launch Failure] Missing interface component template file mapping asset: {html_ui_path}") + sys.exit(1) + + api = Api() + + # Initialize high-performance framework window with hardware layer bindings window = webview.create_window( - title="ComfyUI Dynamic Dropdowns Node Generator", - url=html_path, - width=900, - height=850, - resizable=True + title="🎭 ComfyUI Dynamic Dropdowns - Node Builder Assistant", + url=html_ui_path, + js_api=api, + width=460, + height=720, + resizable=True, + min_size=(380, 640), + background_color='#09090f' ) - # Force the local OS renderer (WebView2 Edge Chromium on Windows) - # so the interface looks identical and functions smoothly offline on all PCs - webview.start(gui="cef" if os.name != "nt" else "mshtml" if not html_path else None) + webview.start(debug=False) + +if __name__ == "__main__": + load_ui_interface() \ No newline at end of file diff --git a/generator/generator.html b/generator/generator.html index 935eac7..bd4ce53 100644 --- a/generator/generator.html +++ b/generator/generator.html @@ -302,4 +302,4 @@ root.render(); - + \ No newline at end of file