diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6769e21 --- /dev/null +++ b/.gitignore @@ -0,0 +1,160 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ \ No newline at end of file diff --git a/__init__.py b/__init__.py index 947db62..2227a2d 100644 --- a/__init__.py +++ b/__init__.py @@ -7,14 +7,14 @@ from .harronode import Harronode +from .harroserver import HarroServer NODE_CLASS_MAPPINGS = { - "Harronode": Harronode - + "Harronode": Harronode, } NODE_DISPLAY_NAME_MAPPINGS = { - "Harronode": "Harronode - Prompt Builder" + "Harronode": "Harronode - Prompt Builder", } WEB_DIRECTORY = "js" diff --git a/__pycache__/__init__.cpython-310.pyc b/__pycache__/__init__.cpython-310.pyc index 0f29b3e..9866af5 100644 Binary files a/__pycache__/__init__.cpython-310.pyc and b/__pycache__/__init__.cpython-310.pyc differ diff --git a/__pycache__/harronode.cpython-310.pyc b/__pycache__/harronode.cpython-310.pyc index dba54b3..9a2c518 100644 Binary files a/__pycache__/harronode.cpython-310.pyc and b/__pycache__/harronode.cpython-310.pyc differ diff --git a/harronode.py b/harronode.py index 59ca277..72d5236 100644 --- a/harronode.py +++ b/harronode.py @@ -1,6 +1,6 @@ import json from pathlib import Path -import torch +from server import PromptServer import server class Harronode: @@ -15,10 +15,12 @@ class Harronode: accents = data.get("accents", []) content = data.get("content", []) + # setup inputs for the node inputs = { "required": { "prompt": ("STRING", {"default": "", "multiline": True}), "text_on_image": ("STRING", {"default": "", "multiline": True}), + "mode": ("BOOLEAN", {"default": True, "label_on": "Editable", "label_off": "Fixed"}), "text_weight": ("FLOAT", {"default": 1.0, "min":0.5, "max":1.5, "step": 0.01}), "color_count": ("INT", {"default": 1, "min": 0, "max": 3, "step": 1}), "style_count": ("INT", {"default": 1, "min": 1, "max": 2, "step": 1}), @@ -42,82 +44,9 @@ class Harronode: return inputs RETURN_TYPES = ("STRING",) - FUNCTION = "fuck_me_up_fam" + FUNCTION = "doTheThing" CATEGORY = "Harronode/PromptBuilder" - def fuck_me_up_fam(self, *args, **kwargs): + def doTheThing(self, *args, **kwargs): output = kwargs['prompt'] - return(output,) - - -def onprompt_populate_prompt(json_data): - try: - prompt = json_data['prompt'] - - updated_widget_values = {} - for k, v in prompt.items(): - if 'class_type' in v and (v['class_type'] == 'Harronode'): - inputs = v['inputs'] - if isinstance(inputs['prompt'], str): - inputs['prompt'] = prompt_builder.build_prompt(inputs) - - server.PromptServer.instance.send_sync("impact-node-feedback", {"node_id": k, "widget_name": "prompt", "type": "STRING", "value": inputs['prompt']}) - updated_widget_values[k] = inputs['prompt'] - except Exception as e: - print(f"[WARN] ComfyUI-Harronode: Error on prompt\n{e}") - -def harroonprompt(json_data): - try: - onprompt_populate_prompt(json_data) - except Exception as e: - print(f"[WARN] ComfyUI-Harronode: Error on prompt - autopopulation of prompt will not work.\n{e}") - - return json_data - -class prompt_builder: - def build_prompt(inputs): - text = "" - print(inputs) - text = text + "('" - text = text + inputs["text_on_image"] - text_weight = inputs["text_weight"] - text = text + f"':{text_weight:.2f}) text logo, " - color_count = inputs["color_count"] - if color_count > 0: - color1 = inputs["color_1"] - color2 = inputs["color_2"] - color3 = inputs["color_3"] - for i in range(1, color_count + 1): - variable_name = f"color{i}" - color_value = locals()[variable_name] - text = text + color_value + ", " - style_count = inputs["style_count"] - if style_count > 0: - style1 = inputs["style_1"] - style2 = inputs["style_2"] - for i in range(1, style_count + 1): - variable_name = f"style{i}" - style_value = locals()[variable_name] - text = text + style_value + ", " - accent_count = inputs["accent_count"] - if accent_count > 0: - accent1 = inputs["accent_1"] - accent2 = inputs["accent_2"] - accent3 = inputs["accent_3"] - for i in range(1, accent_count + 1): - variable_name = f"accent{i}" - accent_value = locals()[variable_name] - text = text + accent_value + ", " - content_count = inputs["content_count"] - if content_count > 0: - content1 = inputs["content_1"] - content2 = inputs["content_2"] - content3 = inputs["content_3"] - for i in range(1, content_count + 1): - variable_name = f"content{i}" - content_value = locals()[variable_name] - text = text + content_value + ", " - - return text - -server.PromptServer.instance.add_on_prompt_handler(harroonprompt) + return(output,) # comma outputs a tuple with a single record instead of a bunch of records all containing the prompt diff --git a/harroserver.py b/harroserver.py new file mode 100644 index 0000000..04943d4 --- /dev/null +++ b/harroserver.py @@ -0,0 +1,10 @@ +from server import PromptServer +from aiohttp import web +import time + +class HarroServer: + + @classmethod + def functionThatNeedsABetterName(): + ### do stuff + print("HELP") \ No newline at end of file diff --git a/js/harronode.js b/js/harronode.js index 045eac2..4c15b52 100644 --- a/js/harronode.js +++ b/js/harronode.js @@ -1,17 +1,89 @@ import { app } from "../../scripts/app.js"; +import { api } from "../../scripts/api.js"; +import "../../scripts/widgets.js"; +import { ComfyWidgets } from "../../scripts/widgets.js"; let origProps = {}; let initialized = false; +let previousPrompt = ""; +let tokenDict = { + "text": { + "location": 0, + "currentValue":"" + }, + "color_1":{ + "location": 0, + "currentValue":"" + }, + "color_2":{ + "location": 0, + "currentValue":"" + }, + "color_3":{ + "location": 0, + "currentValue":"" + }, + "style_1":{ + "location": 0, + "currentValue":"" + }, + "style_2":{ + "location": 0, + "currentValue":"" + }, + "accent_1":{ + "location": 0, + "currentValue":"" + }, + "accent_2":{ + "location": 0, + "currentValue":"" + }, + "accent_3":{ + "location": 0, + "currentValue":"" + }, + "content_1":{ + "location": 0, + "currentValue":"" + }, + "content_2":{ + "location": 0, + "currentValue":"" + }, + "content_3":{ + "location": 0, + "currentValue":"" + }, + "tokens": [] +} const harroWidgetHandlers = { "Harronode": { 'color_count': handleColorCount, 'style_count': handleStyleCount, 'accent_count': handleAccentCount, - 'content_count': handleContentCount + 'content_count': handleContentCount, + 'color_1': handlePromptChange, + 'color_2': handlePromptChange, + 'color_3': handlePromptChange, + 'style_1': handlePromptChange, + 'style_2': handlePromptChange, + 'accent_1': handlePromptChange, + 'accent_2': handlePromptChange, + 'accent_3': handlePromptChange, + 'content_1': handlePromptChange, + 'content_2': handlePromptChange, + 'content_3': handlePromptChange, + 'text_weight': handlePromptChange, }, }; +// ------------------ HANDLERS +function handlePromptChange(node, widget) { + rebuildPrompt(node); +} + function handleColorCount(node, widget) { updateColorWidgets(node, widget.value); } @@ -28,6 +100,8 @@ function handleContentCount(node, widget) { updateContentWidgets(node, widget.value); } +//----------------------END HANDLERS + //-------function credit to efficiency nodes for comfyUI---------// const findWidgetByName = (node, name) => { @@ -40,7 +114,8 @@ const doesInputWithNameExist = (node, name) => { const HIDDEN_TAG = "harrohide"; -function toggleWidget(node, widget, show = false, suffix = "") { +// function to hide a widget from view +function harrToggleWidget(node, widget, show = false, suffix = "") { if (!widget || doesInputWithNameExist(node, widget.name)) return; // Store the original properties of the widget if not already stored @@ -55,7 +130,7 @@ function toggleWidget(node, widget, show = false, suffix = "") { widget.computeSize = show ? origProps[widget.name].origComputeSize : () => [0, -4]; // Recursively handle linked widgets if they exist - widget.linkedWidgets?.forEach(w => toggleWidget(node, w, ":" + widget.name, show)); + widget.linkedWidgets?.forEach(w => harrToggleWidget(node, w, ":" + widget.name, show)); // Calculate the new height for the node based on its computeSize method const newHeight = node.computeSize()[1]; @@ -63,19 +138,21 @@ function toggleWidget(node, widget, show = false, suffix = "") { } //--------------------------END CREDIT---------------------------// +// -- BASE CODE NEEDED BY PROMPT BUILDER function updateColorWidgets(node, color_count){ for (let i = 1; i <= 3; i++) { //find widget const color_picker = findWidgetByName(node, `color_${i}`) if(i <= color_count){ //set visible and required - toggleWidget(node, color_picker, true) + harrToggleWidget(node, color_picker, true) } else { //hide and set not required - toggleWidget(node, color_picker, false) + harrToggleWidget(node, color_picker, false) } } + rebuildPrompt(node); } function updateStyleWidgets(node, color_count){ @@ -84,13 +161,14 @@ function updateStyleWidgets(node, color_count){ const style_picker = findWidgetByName(node, `style_${i}`) if(i <= color_count){ //set visible and required - toggleWidget(node, style_picker, true) + harrToggleWidget(node, style_picker, true) } else { //hide and set not required - toggleWidget(node, style_picker, false) + harrToggleWidget(node, style_picker, false) } } + rebuildPrompt(node); } function updateAccentWidgets(node, color_count){ for (let i = 1; i <= 3; i++) { @@ -98,13 +176,14 @@ function updateAccentWidgets(node, color_count){ const accent_picker = findWidgetByName(node, `accent_${i}`) if(i <= color_count){ //set visible and required - toggleWidget(node, accent_picker, true) + harrToggleWidget(node, accent_picker, true) } else { //hide and set not required - toggleWidget(node, accent_picker, false) + harrToggleWidget(node, accent_picker, false) } } + rebuildPrompt(node); } function updateContentWidgets(node, color_count){ for (let i = 1; i <= 3; i++) { @@ -112,15 +191,67 @@ function updateContentWidgets(node, color_count){ const content_picker = findWidgetByName(node, `content_${i}`) if(i <= color_count){ //set visible and required - toggleWidget(node, content_picker, true) + harrToggleWidget(node, content_picker, true) } else { //hide and set not required - toggleWidget(node, content_picker, false) + harrToggleWidget(node, content_picker, false) } } + rebuildPrompt(node); } +function buildPrompt(node, widgets = null, event = null){ + var prompt = ""; + var _widgets; + if(widgets != null) + { + _widgets = widgets; + + } + //get widget values from node + else { + if(node.comfyClass == "Harronode"){ + _widgets = node.widgets; + } + } + prompt += '("' + findWidgetByName(node, "text_on_image").value+ '":' + parseFloat(findWidgetByName(node, "text_weight").value).toFixed(2) + ") text logo, "; + //foreach color + var colors = findWidgetByName(node, "color_count").value; + for(var i = 1; i <= colors; i++){ + var color = findWidgetByName(node, `color_${i}`).value; + prompt += color + ", "; + } + //foreach style + var styles = findWidgetByName(node, "style_count").value; + for(var i = 1; i <= styles; i++){ + var style = findWidgetByName(node, `style_${i}`).value; + prompt += style + ", "; + } + //foreach accent + var accents = findWidgetByName(node, "accent_count").value; + for(var i = 1; i <= accents; i++){ + var accent = findWidgetByName(node, `accent_${i}`).value; + prompt += accent + ", "; + } + //foreach content + var contents = findWidgetByName(node, "content_count").value; + for(var i = 1; i <= contents; i++){ + var content = findWidgetByName(node, `content_${i}`).value; + prompt += content + ", "; + } + return prompt; +} + +function rebuildPrompt(node){ + // build prompt + var promptOutput = ""; + promptOutput = buildPrompt(node); + node.widgets[0].inputEl.value = promptOutput; +} + +// -- END BASE CODE NEEDED BY PROMPT BUILDER + function startupLogic (node, widget) //change name { // Retrieve the handler for the current node title and widget name @@ -128,21 +259,38 @@ function startupLogic (node, widget) //change name if (handler) { handler(node, widget); } - prompt = findWidgetByName(node, 'prompt'); - } app.registerExtension({ name: "harronode.harronode", - nodeCreated(node) { + setup() + { + function harronode_node_feedback(event){ + const node = app.graph._nodes_by_id[event.detail.node_id]; + let promptOutput = ""; + let editedPrompt = node.widgets[0].inputEl.value; + if (node) { + if(node.comfyClass == "Harronode"){ + let widgets = event.detail.value; + console.log(widgets); + rebuildPrompt(node); + } + } + else { + console.log(`Harronode Prompt Builder - failed to find ${event.detail.node_id}`); + } + } + + api.addEventListener("harronode-node-feedback", harronode_node_feedback); + }, + async nodeCreated(node) + { for (const w of node.widgets || []) { let widgetValue = w.value; // Store the original descriptor if it exists let originalDescriptor = Object.getOwnPropertyDescriptor(w, 'value'); - startupLogic(node, w); - Object.defineProperty(w, 'value', { get() { // If there's an original getter, use it. Otherwise, return widgetValue. @@ -163,14 +311,37 @@ app.registerExtension({ startupLogic(node, w); } }); + if(node.comfyClass == "Harronode"){ + const editablePrompt = node.widgets.find((w) => w.name == 'prompt'); + const mode_widget = node.widgets.find((w) => w.name == 'mode'); + Object.defineProperty(mode_widget, "value", { + set: (value) => { + node._mode_value = value == true || value == "Editable"; + editablePrompt.inputEl.disabled = value == false || value == "Editable"; + }, + get: () => { + if(node._mode_value != undefined) + return node._mode_value; + else + return true; + } + }); + } + } + //do this if we're messing with the prompt builder + if(node.comfyClass == "Harronode"){ + node.widgets[0].inputEl.placeholder = "Generated Prompt - Changing a parameter may overwrite what is in this box."; + node.widgets[1].inputEl.placeholder = "Input Text to Display on Image"; + //janky af but I don't care at this point + if(!initialized) { + console.log(node.widgets); + const thebutton = node.addWidget("button", "CLICK to Rebuild Prompt (Erases edits)", "", () => { + rebuildPrompt(node); + }); + initialized = true; + console.log(node.widgets); + } } setTimeout(() => { initialized = true; }, 500); - - if(node.comfyClass == "Harronode"){ - node.widgets[0].inputEl.placeholder = "Populated Prompt (Will be generated automatically)"; - node.widgets[1].inputEl.placeholder = "Input Text to Display on Image"; - const prompt = node.widgets.find((w) => w.name == 'prompt'); - prompt.inputEl.disabled = 1 == 1; - } } -}); +}); \ No newline at end of file