This commit is contained in:
nosiu
2025-03-02 22:59:19 +01:00
parent dfe2410d3e
commit c0876c9bae
9 changed files with 4659 additions and 0 deletions
+20
View File
@@ -0,0 +1,20 @@
name: Publish to Comfy registry
on:
workflow_dispatch:
push:
branches:
- main
paths:
- "pyproject.toml"
jobs:
publish-node:
name: Publish Custom Node to registry
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v4
- name: Publish Custom Node
uses: Comfy-Org/publish-node-action@main
with:
personal_access_token: ${{ secrets.REGISTRY_ACCESS_TOKEN }}
+160
View File
@@ -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/
+65
View File
@@ -0,0 +1,65 @@
# ComfyUI Text Randomizer 1.0.0
<sub>[About](#comfyui-text-randomizer-100) | [Custom nodes](#custom-nodes) | [Workflows](#workflows) | [Tips](#tips)</sub>
A simple text randomizer for ComfyUI that can generate random and surprising results.
[[ PUT IMAGE HERE]]
## Custom nodes
<sub>[About](#comfyui-text-randomizer-100) | [Custom nodes](#custom-nodes) | [Workflows](#workflows) | [Tips](#tips)</sub>
- ### Text randomizer
The Text Randomizer node randomizes text placed between brackets. Options should be separated by `|`, and any text outside the brackets remains unchanged.
**Example:**
Input text: `[man|girl], sitting on a [bench|grass]`.
Possible outcomes:
- man, sitting on a bench
- girl, sitting on a bench
- man, sitting on a grass
- girl, sitting on a grass
**Note:** You can nest brackets to create more varied outcomes.
- ### Text randomizer with check
Similar to `Text Randomizer`, but it displays a warning if brackets or parentheses are mismatched. This check happens in real time, so you don’t need to run the workflow to see the errors.
**Note:** The check works separately for brackets and parentheses. For example, input like `[ ( ] )` will pass the check.
- ### Get one text or another at random
Returns one of two text options at random.
- ### Concatenate text
Joins two pieces of text, adding `, ` between them.
- ### Show text
Displays the final text you’ve generated. This is useful for debugging your prompts and checking what is fed into the TextEncoder.
## Workflows
<sub>[About](#comfyui-text-randomizer-100) | [Custom nodes](#custom-nodes) | [Workflows](#workflows) | [Tips](#tips)</sub>
You can find example workflows in the `/workflows` folder.
### workflow.json
Demonstrates how to achieve similar text outputs in different ways, either by grouping separate topics and concatenating texts or putting everything into one node.
[[ PUT IMAGE HERE]]
### workflow2.json
Shows how to randomize two text nodes and return text from either one node or another.
[[ PUT IMAGE HERE]]
## Tips
<sub>[About](#comfyui-text-randomizer-100) | [Custom nodes](#custom-nodes) | [Workflows](#workflows) | [Tips](#tips)</sub>
- You can use `Text Randomizer with Check` as a simple tool to check brackets without connecting it to anything. Just paste your text inside to see if your brackets or parentheses match.
- By adding `|` at the end of the expression, you can create an empty option. For example (spaces added for clarity): `[ apple | banana | grapefruit | orange | ]` gives an equal chance to select "apple," "banana," "grapefruit," "orange," or nothing. \
\
Other examples:
- [apple|] gives a 50% chance of getting "apple" and 50% chance of getting nothing.
- [apple||] gives a 33.3% chance of getting "apple" and a 66.6% chance of getting nothing.
+4
View File
@@ -0,0 +1,4 @@
from .nodes import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS
WEB_DIRECTORY = './ui/'
__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS', 'WEB_DIRECTORY']
+125
View File
@@ -0,0 +1,125 @@
import re
import random
CATEGORY_NAME = "randomizer"
class RandomizeText:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"text": ("STRING", {"multiline": True}),
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
}
}
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("text",)
FUNCTION = "randomize"
CATEGORY = CATEGORY_NAME
def randomize(self, text, seed, **kwargs):
random.seed(seed)
while '[' in text:
text = re.sub(r'\[([^\[\]]+)\]', lambda x: random.choice(x.group(1).split('|')), text)
return (text,)
# ===========================================================================
class RandomizeTextWithCheck(RandomizeText):
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"text": ("STRING", {"multiline": True}),
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
},
"optional": {
"info_text": ("STRING", {"multiline": True}),
}
}
# ===========================================================================
class ConcatText:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"text1": ("STRING", {"multiline": True, "forceInput": True}),
"text2": ("STRING", {"multiline": True, "forceInput": True}),
}
}
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("text",)
FUNCTION = "concat"
CATEGORY = CATEGORY_NAME
def concat(self, text1, text2):
return (text1 + ", " + text2,)
# ===========================================================================
class RandomTextChoice:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"text1": ("STRING", {"multiline": True, "forceInput": True}),
"text2": ("STRING", {"multiline": True, "forceInput": True}),
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
}
}
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("text",)
FUNCTION = "choice"
CATEGORY = CATEGORY_NAME
def choice(self, text1, text2, seed):
random.seed(seed)
text = random.choice([text1, text2])
return (text,)
# ===========================================================================
class ShowText:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"text": ("STRING", {"forceInput": True, "dynamicPrompts": True}),
},
"optional": {
"preview": ("STRING", {"multiline": True }),
}
}
RETURN_TYPES = ()
FUNCTION = "showText"
OUTPUT_NODE = True
CATEGORY = CATEGORY_NAME
def showText(self, text, **kwargs):
return {"ui": {"text": text}}
NODE_CLASS_MAPPINGS = {
"RandomizeText": RandomizeText,
"RandomizeTextWithCheck": RandomizeTextWithCheck,
"RandomTextChoice": RandomTextChoice,
"ConcatText": ConcatText,
"ShowText": ShowText,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"RandomizeText": "Text randomizer",
"RandomizeTextWithCheck": "Text randomizer with check",
"RandomTextChoice": "Get one text or another at random",
"ConcatText": "Concatenate text",
"ShowText": "Show text",
}
+14
View File
@@ -0,0 +1,14 @@
[project]
name = "comfyui-text-randomizer"
description = "A simple text randomizer for ComfyUI that can generate random and surprising results"
version = 1.0.0"
license = { file = "LICENSE" }
dependencies = []
[project.urls]
Repository = "https://github.com/nosiu/comfyui-text-randomizer"
[tool.comfy]
PublisherId = "nosiu"
DisplayName = "comfyui-text-randomizer"
Icon = ""
+152
View File
@@ -0,0 +1,152 @@
import { app } from "../../scripts/app.js";
function checkBrackets(value, domWidget) {
const input = value
const resultMessage = domWidget.element
const brackets = []
const parentheses = []
const unmatchedClosingBrackets = []
const unmatchedClosingParentheses = []
for (let i = 0; i < input.length; i++) {
const char = input[i]
if (char === '[') {
brackets.push(i)
}
if (char === ']') {
if (brackets.length === 0) {
unmatchedClosingBrackets.push(i)
} else {
brackets.pop()
}
}
if (char === '(') {
parentheses.push(i)
}
if (char === ')') {
if (parentheses.length === 0) {
unmatchedClosingParentheses.push(i)
} else {
parentheses.pop()
}
}
}
let message = ""
let hasErrors = false
if (brackets.length > 0) {
message += `Unmatched opening bracket at: ${brackets.map(p => p + 1).join(', ')}. `
hasErrors = true
}
if (unmatchedClosingBrackets.length > 0) {
message += `Unmatched closing bracket at: ${unmatchedClosingBrackets.map(p => p + 1).join(', ')}. `
hasErrors = true
}
if (parentheses.length > 0) {
message += `Unmatched opening parenthesis at: ${parentheses.map(p => p + 1).join(', ')}. `
hasErrors = true
}
if (unmatchedClosingParentheses.length > 0) {
message += `Unmatched closing parenthesis at: ${unmatchedClosingParentheses.map(p => p + 1).join(', ')}. `
hasErrors = true
}
if (!hasErrors) {
message = "All brackets and parentheses are properly matched!"
resultMessage.style.color = "green"
} else {
resultMessage.style.color = "red"
}
resultMessage.value = message
domWidget.value = message
}
app.registerExtension({
name: "ComfyUI.text-randomizer-01",
async beforeRegisterNodeDef(nodeType) {
// ===================================
// SHOW TEXT
// ===================================
if (nodeType?.comfyClass == "ShowText") {
const onExecuted = nodeType.prototype.onExecuted
nodeType.prototype.onExecuted = function (message) {
const r = onExecuted?.apply(this, arguments)
const text = message.text.join("")
if (text) {
const previewWidget = this.widgets.find(w => w.name === "preview")
previewWidget.value = text
}
return r
}
const onNodeCreated = nodeType.prototype.onNodeCreated
nodeType.prototype.onNodeCreated = function () {
const r = onNodeCreated ? onNodeCreated.apply(this, arguments) : undefined
const previewWidget = this.widgets.find(w => w.name === "preview")
if (previewWidget) {
previewWidget.element.readOnly = true
}
return r
}
const onConnectionsChange = nodeType.prototype.onConnectionsChange;
nodeType.prototype.onConnectionsChange = function (side, slot, connect, link_info, output) {
const r = onConnectionsChange?.apply(this, arguments);
if (output.name === "text") {
const previewWidget = this.widgets.find(w => w.name === "preview")
if (previewWidget) {
previewWidget.value = ""
}
}
return r
}
}
// ===================================
// RANDOMIZE TEXT
// ===================================
if (nodeType?.comfyClass == "RandomizeTextWithCheck") {
const onNodeCreated = nodeType.prototype.onNodeCreated;
nodeType.prototype.onNodeCreated = function () {
const r = onNodeCreated ? onNodeCreated.apply(this, arguments) : undefined
const textwWidget = this.widgets.find(w => w.name === "text")
const infoTextWidget = this.widgets.find(w => w.name === "info_text")
infoTextWidget.element.readOnly = true
infoTextWidget.value = ""
if (textwWidget) {
const callback = textwWidget.element.onkeyup
textwWidget.element.onkeyup = (e) => {
checkBrackets(textwWidget.value, infoTextWidget)
if (callback) callback(e)
}
}
return r
}
const onGraphConfigured = nodeType.prototype.onGraphConfigured;
nodeType.prototype.onGraphConfigured = function () {
const r = onGraphConfigured?.apply(this, arguments)
const textwWidget = this.widgets.find(w => w.name === "text")
if (textwWidget) {
checkBrackets(textwWidget.value, this.widgets.find(w => w.name === "info_text"))
}
return r
}
}
}
})
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff