init
This commit is contained in:
@@ -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
@@ -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/
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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']
|
||||
@@ -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",
|
||||
}
|
||||
@@ -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
@@ -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
Reference in New Issue
Block a user