8 Commits
4 changed files with 178 additions and 8 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 371 KiB

+3
View File
@@ -44,3 +44,6 @@ for x in NODE_MAP:
x['class'].CATEGORY = 'StableWildcards'
NODE_CLASS_MAPPINGS[x['name']] = x['class']
NODE_DISPLAY_NAME_MAPPINGS[x['name']] = x['disp']
# Location of web extensions
WEB_DIRECTORY = './js'
+117
View File
@@ -0,0 +1,117 @@
/**
# The MIT License (MIT)
#
# Copyright © 2023 Michael Wolfe (DigitalIO)
#
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
# documentation files (the “Software”), to deal in the Software without restriction, including without limitation the
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
# and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of
# the Software.
#
# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
# BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#*/
import { app } from "../../scripts/app.js";
import { api } from "../../scripts/api.js";
/**
Call the server to process the prompt and set it as the textarea title
*/
async function updateTitle(node, e) {
const promptValue = node.widgets[0].element.value;
const seedValue = node.widgets[1].value;
// Get the resulting string from the server
const response = await api.fetchApi(`/stable-wildcards/process`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({prompt: promptValue, seed: seedValue})
});
const data = await response.json();
// Update the title
node.widgets[0].element.title = data['prompt'];
}
/**
Change the title for the text area to include the loaded result
*/
function loadStableWildcardNode(node, app) {
let wildcardData = app.graph.extra['stable-wildcards'];
// Fallback to use the server on changes
if (!wildcardData || !wildcardData[node.id]) {
updateTitle(node, null);
return;
}
// Change the title
node.widgets[0].element.title = wildcardData[node.id];
}
/**
Listen for prompt changes and update title
*/
function stableWildcardNodeCreated(node, app) {
let lastCall;
// Use the change event to update the title for the prompt (textarea)
node.widgets[0].element.addEventListener(
'change',
updateTitle.bind(null, node),
false
);
// The seed widget does not offer a change callback.
// Instead proxy the widget and hook the set function
node.widgets[1] = new Proxy(node.widgets[1], {
set(target, p, newValue, receiver) {
// We are now responsible for all updates
target[p] = newValue;
// If the property updated was the value (thats the seed we care about)
if (p === 'value') {
// Using the UI slider to change the seed can call this function
// hundreds of times per second. We don't want to send a server request for
// every update, so only update once the value has stoped changing
if (lastCall) {
clearTimeout(lastCall);
}
lastCall = setTimeout(updateTitle.bind(null, node), 250);
}
// Return true for no issues
return true;
}
});
}
// Create the Stable Wildcard extension
const StableWildcardsExtension = {
name: 'StableWildcards',
loadedGraphNode(node, app) {
if (node.type !== "Stable Wildcards") {
return;
}
loadStableWildcardNode(node, app);
},
nodeCreated(node, app) {
if (node.comfyClass !== "Stable Wildcards") {
return;
}
stableWildcardNodeCreated(node, app);
}
}
// Register app
app.registerExtension(StableWildcardsExtension);
+58 -8
View File
@@ -18,6 +18,15 @@
import sys
from random import Random
import re
import server
from aiohttp import web
@server.PromptServer.instance.routes.post("/stable-wildcards/process")
async def process_stable_wildcards(req):
json_data = await req.json()
prompt = StableWildcard.process_wildcards(json_data['prompt'], json_data['seed'])
return web.json_response({"prompt": prompt}, content_type='application/json')
class StableWildcard:
@@ -43,44 +52,85 @@ class StableWildcard:
return {
"required": {
# Without dynamicPrompts set to false ComfyUI will randomize our prompt before we get to it!
"prompt": ('STRING', {'default': '', 'multiline': True, 'dynamicPrompts': False}),
"seed" : ('INT', {'default': 0, 'min': 0, 'max': sys.maxsize}),
"prompt" : ('STRING', {'default': '', 'multiline': True, 'dynamicPrompts': False}),
"seed" : ('INT', {'default': 0, 'min': 0, 'max': sys.maxsize}),
# Currently unused, but if a breaking change must be introduced
# it can be used to provide backwards compatibility.
"version": ([1], {'default': 1})
},
"hidden" : {
"id" : "UNIQUE_ID",
"png_info": "EXTRA_PNGINFO",
},
"optional": {}
}
def execute(self, prompt, seed, **kwargs):
@staticmethod
def process_wildcards(prompt, seed):
"""
Process wildcards using a seed to produce stable output.
To achieve stable results a new random object is created
using the given seed.
"""
# Setup RNG
rng = Random(int(seed))
# Search & replace matches
match = self.WILDCARD_PATTERN.search(prompt)
match = StableWildcard.WILDCARD_PATTERN.search(prompt)
while match:
# Get the options - Remove the {}, split by | character
# Because the search pattern requires at least one character,
# ops is guaranteed to have at least one option
ops = match.group()[1:-1].split('|')
# Pick a random option.
pick = ops[rng.randint(0, len(ops)-1)]
pick = ops[rng.randint(0, len(ops) - 1)]
# Replace the match and update the string
# Limit one match incase there are repeated wildcards
prompt = prompt.replace(match.group(), pick, 1)
# Search for more wildcards
match = self.WILDCARD_PATTERN.search(prompt)
match = StableWildcard.WILDCARD_PATTERN.search(prompt)
return prompt
def execute(self, prompt, seed, **kwargs):
"""
Process the wildcards for execution
"""
# Process the wildcards
prompt = self.process_wildcards(prompt, seed)
# Output result console
print('\033[96m Stable Wildcard: ({}) "{}"\033[0m'.format(seed, prompt))
# Save output into metadata if everything exists
if 'id' in kwargs and 'png_info' in kwargs:
id = kwargs['id']
png_info = kwargs['png_info']
# Check for workflow and extra
if 'workflow' in png_info:
workflow = png_info['workflow']
# Create extra if missing
if 'extra' not in workflow:
workflow['extra'] = {}
# Create a namespace if necessary
if 'stable-wildcards' not in workflow['extra']:
workflow['extra']['stable-wildcards'] = {}
# Save the result in metadata by id
workflow['extra']['stable-wildcards'][id] = prompt
else:
print('\033[96m Stable Wildcard: Workflow missing, output not saved in metadata\033[0m')
else:
print('\033[96m Stable Wildcard: Hidden input missing, output not saved in metadata\033[0m')
return prompt,