Files
2026-07-13 01:21:13 +07:00

159 lines
6.4 KiB
Python

import os
import sys
import json
import webview
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)
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")
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 Builder Assistant",
url=html_ui_path,
js_api=api,
width=460,
height=720,
resizable=True,
min_size=(380, 640),
background_color='#09090f'
)
webview.start(debug=False)
if __name__ == "__main__":
load_ui_interface()