Add files via upload
This commit is contained in:
+150
-21
@@ -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()
|
||||
@@ -302,4 +302,4 @@
|
||||
root.render(<App />);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
Reference in New Issue
Block a user