17 Commits
Author SHA1 Message Date
Michael Wolfe 41b72c45a8 Version bump 2025-09-15 16:35:31 -04:00
ttrebuchon 0fdd9276ac TRIAGE: Comment out proxy code in StableWildcard.js
(Minimal triage to allow the nodepack to be usable again, proxy behavior still needs to be fixed for long term behavior)
2025-09-15 16:33:43 -04:00
snomiao ab02676118 chore(publish): update workflow for node publishing with permissions and conditional execution 2025-03-17 13:53:33 -04:00
Michael Wolfe 3c2412952f Update pyproject.toml
Version bump
2024-08-12 14:57:47 -04:00
snomiao 7c831e17dd chore(licence-update): Update PyProject Toml - License 2024-08-12 12:23:50 -04:00
DigitalIO cbf44f350c version bump 2024-05-24 17:13:06 -04:00
haohaocreates cfa473e1dc chore(publish): Add Github Action for Publishing to Comfy Registry 2024-05-24 16:29:38 -04:00
Michael Wolfe f9f6a245b6 Update pyproject.toml 2024-05-24 16:22:39 -04:00
haohaocreates 018d925e09 chore(pyproject): Add pyproject.toml for Custom Node Registry 2024-05-24 16:21:50 -04:00
DigitalIO f61e9a45c1 Changeing the seed now updates the prompt textbox title 2023-12-18 18:39:49 -05:00
DigitalIO a100eb0915 Regenerate the missing data, duh 2023-12-17 23:14:17 -05:00
DigitalIO e4c0950809 Title will now stay updated with the current prompt 2023-12-17 22:46:47 -05:00
DigitalIO 6c2c986281 Refactored the class 2023-12-17 22:46:19 -05:00
DigitalIO 164a04df78 Previous output is not loaded as a title for the text area. Hovering over will display previous result 2023-12-17 20:28:15 -05:00
DigitalIO 78f8932662 Added additional checks before setting metadata 2023-12-17 13:52:40 -05:00
DigitalIO 2e7e7ad666 Results are now saved into the image metadata 2023-12-15 15:32:22 -05:00
DigitalIO d58ce0e632 Example Workflow 2023-10-13 14:56:03 -04:00
6 changed files with 222 additions and 8 deletions
+25
View File
@@ -0,0 +1,25 @@
name: Publish to Comfy registry
on:
workflow_dispatch:
push:
branches:
- main
paths:
- "pyproject.toml"
permissions:
issues: write
jobs:
publish-node:
name: Publish Custom Node to registry
runs-on: ubuntu-latest
if: ${{ github.repository_owner == 'DigitalIO' }}
steps:
- name: Check out code
uses: actions/checkout@v4
- name: Publish Custom Node
uses: Comfy-Org/publish-node-action@v1
with:
## Add your own personal access token to your Github Repository secrets and reference it here.
personal_access_token: ${{ secrets.REGISTRY_ACCESS_TOKEN }}
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'
+122
View File
@@ -0,0 +1,122 @@
/**
# 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
);
// TRIAGE: The below code was commented out due to breaking fundamental behavior
// in ComfyUI in later versions.
// Fixing/finding an alternative to the proxy will be necessary in order to
// re-enable updating the node title.
// 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);
+14
View File
@@ -0,0 +1,14 @@
[project]
name = "comfyui-stable-wildcards"
description = "Wildcard implementation that can be reproduced with workflows."
version = "1.1.3"
license = { text = "MIT License" }
[project.urls]
Repository = "https://github.com/DigitalIO/ComfyUI-stable-wildcards"
# Used by Comfy Registry https://comfyregistry.org
[tool.comfy]
PublisherId = "digitalio"
DisplayName = "ComfyUI-stable-wildcards"
Icon = ""
+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,