+160
@@ -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/
|
||||
+3
-3
@@ -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"
|
||||
|
||||
Binary file not shown.
Binary file not shown.
+6
-77
@@ -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
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
from server import PromptServer
|
||||
from aiohttp import web
|
||||
import time
|
||||
|
||||
class HarroServer:
|
||||
|
||||
@classmethod
|
||||
def functionThatNeedsABetterName():
|
||||
### do stuff
|
||||
print("HELP")
|
||||
+195
-24
@@ -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;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user