init: first available

This commit is contained in:
uihp
2025-02-07 02:25:44 +08:00
commit f323a35e3e
11 changed files with 248 additions and 0 deletions
+2
View File
@@ -0,0 +1,2 @@
# Auto detect text files and perform LF normalization
* text=auto
+2
View File
@@ -0,0 +1,2 @@
__pycache__/
*.pyc
+1
View File
@@ -0,0 +1 @@
# ComfyUI StringChain
+15
View File
@@ -0,0 +1,15 @@
class StringChain:
@classmethod
def INPUT_TYPES(cls) -> dict:
return {
'optional': { 'previous': ('STRING', {'forceInput': True, 'default': '', 'multiline': True}) },
'required': {
'separator': ('STRING', {'default': ', '}),
'string': ('STRING', {'default': '', 'multiline': True}),
}
}
RETURN_TYPES = ('STRING',)
FUNCTION = 'combine'
CATEGORY = 'utils'
def combine(self, string: str, separator: str, previous: str = '') -> tuple[str]:
return (f'{previous}{separator}{string}',) if previous and string else (previous or string,)
+19
View File
@@ -0,0 +1,19 @@
class StringConcat:
@classmethod
def INPUT_TYPES(cls) -> dict:
return {
'optional': {
'previous': ('STRING', {'forceInput': True, 'default': '', 'multiline': True}),
'string': ('STRING', {'default': '', 'multiline': True})
},
'required': {
'separator': ('STRING', {'default': ', '}),
'reverse': ('BOOLEAN', {'default': False})
}
}
RETURN_TYPES = ('STRING',)
FUNCTION = 'concat'
CATEGORY = 'utils'
def concat(self, separator: str, reverse: bool, previous: str = '', string: str = '') -> tuple[str]:
if reverse: previous, string = string, previous
return (f'{previous}{separator}{string}',) if previous and string else (previous or string,)
+27
View File
@@ -0,0 +1,27 @@
from .common import FlexibleOptionalInputType, AnyType
optional = FlexibleOptionalInputType(AnyType('STRING'))
optional.update({
'previous': ('STRING', {'forceInput': True, 'default': '', 'multiline': True})
})
class StringToggle:
@classmethod
def INPUT_TYPES(cls) -> dict:
return {
'required': {
'separator': ('STRING', {'default': ', '}),
'reverse': ('BOOLEAN', {'default': False})
},
'optional': optional
}
RETURN_TYPES = ('STRING',)
FUNCTION = 'join'
CATEGORY = 'utils'
def join(self, separator: str, reverse: bool, previous: str = '', **kwargs) -> tuple[str]:
arguments = {}
for (argtype, unique), value in [(k.split('_'),v) for k,v in kwargs.items() if '_' in k]:
arguments.setdefault(argtype, {})[unique] = value
strings = ([previous] if previous else []) + [text for unique, text in arguments['text'].items() if arguments['append'][unique]]
if reverse: strings = strings[::-1]
return (separator.join(strings),)
+14
View File
@@ -0,0 +1,14 @@
from .StringChain import StringChain
from .StringConcat import StringConcat
from .StringToggle import StringToggle
WEB_DIRECTORY = "./js"
NODE_CLASS_MAPPINGS = {
'String Chain': StringChain,
'String Concat': StringConcat,
'String Toggle': StringToggle,
'String Toggle (Multiline)': StringToggle
}
__all__ = ['NODE_CLASS_MAPPINGS', 'WEB_DIRECTORY']
+31
View File
@@ -0,0 +1,31 @@
''' Credit to rgthree (https://github.com/rgthree/rgthree-comfy) below '''
class AnyType(str):
"""A special class that is always equal in not equal comparisons. Credit to pythongosssss"""
def __ne__(self, __value: object) -> bool:
return False
class FlexibleOptionalInputType(dict):
"""A special class to make flexible nodes that pass data to our python handlers.
Enables both flexible/dynamic input types (like for Any Switch) or a dynamic number of inputs
(like for Any Switch, Context Switch, Context Merge, Power Lora Loader, etc).
Note, for ComfyUI, all that's needed is the `__contains__` override below, which tells ComfyUI
that our node will handle the input, regardless of what it is.
However, with https://github.com/comfyanonymous/ComfyUI/pull/2666 a large change would occur
requiring more details on the input itself. There, we need to return a list/tuple where the first
item is the type. This can be a real type, or use the AnyType for additional flexibility.
This should be forwards compatible unless more changes occur in the PR.
"""
def __init__(self, type):
self.type = type
def __getitem__(self, key):
return (self.type, )
def __contains__(self, key):
return True
+46
View File
@@ -0,0 +1,46 @@
import { app } from "../../scripts/app.js"
import { DividerWidget } from "./common.js"
import { OVERRIDDEN_SERVER_NODES } from './common.js'
const upgradeNodeType = (nodeType) => class extends nodeType {
constructor() {
super(...arguments)
this.units = []
this.nameUniqueCounter = 0
this.addWidget('button', 'Add', null, () => this.addTextUnit())
this.addWidget('button', 'Remove', null, () => this.removeTextUnit(this.units.pop()?.id))
}
configure(info) {
super.configure(info)
const { widgets_values } = info
const units_values = widgets_values.slice(3)
units_values.filter((each, index) => {
if (typeof each !== 'string') return
this.addTextUnit(each, units_values[index-1] ?? true)
})
}
addTextUnit(text='', append=true) {
const id = Symbol()
const unit = { id, enabled: true, text }
const [width,] = this.size
Object.assign(this.addCustomWidget(new DividerWidget({ marginTop: 2, marginBottom: 2, thickness: 2 })), { id })
Object.assign(this.addWidget('toggle', `append_${this.nameUniqueCounter}`, append, () => unit.enabled = !unit.enabled), { id })
Object.assign(this.addWidget('text', `text_${this.nameUniqueCounter++}`, text), { id })
this.units.push(unit)
this.setSize([width, this.computeSize()[1]])
return id
}
removeTextUnit(id) {
this.units = this.units.filter(each => each.id !== id)
this.widgets = this.widgets.filter(each => each.id !== id)
this.setSize(this.size)
}
}
app.registerExtension({
name: "string-master.StringToggle",
async beforeRegisterNodeDef(nodeType, nodeData) {
if (nodeData.name === "String Toggle")
OVERRIDDEN_SERVER_NODES.set(nodeType, upgradeNodeType(nodeType))
}
})
+51
View File
@@ -0,0 +1,51 @@
import { app } from "../../scripts/app.js"
import { ComfyWidgets } from "../../scripts/widgets.js"
import { DividerWidget } from "./common.js"
import { OVERRIDDEN_SERVER_NODES } from './common.js'
const upgradeNodeType = (nodeType) => class extends nodeType {
constructor() {
super(...arguments)
this.units = []
this.nameUniqueCounter = 0
this.addWidget('button', 'Add', null, () => this.addTextUnit())
this.addWidget('button', 'Remove', null, () => this.removeTextUnit(this.units.pop()?.id))
}
configure(info) {
super.configure(info)
const { widgets_values } = info
const units_values = widgets_values.slice(3)
units_values.filter((each, index) => {
if (typeof each !== 'string') return
this.addTextUnit(each, units_values[index-1] ?? true)
})
}
addTextUnit(text='', append=true) {
const id = Symbol()
const unit = { id, enabled: true, text }
const [width, height] = this.size
Object.assign(this.addCustomWidget(new DividerWidget({ marginTop: 2, marginBottom: 2, thickness: 2 })), { id })
Object.assign(this.addWidget('toggle', `append_${this.nameUniqueCounter}`, append, () => unit.enabled = !unit.enabled), { id })
Object.assign(ComfyWidgets.STRING(this, `text_${this.nameUniqueCounter++}`, [ 'STRING', { default: unit.text, multiline: true } ], app).widget, { id, isDOMWidget: true })
this.units.push(unit)
this.setSize([width, height])
return id
}
removeTextUnit(id) {
this.units = this.units.filter(each => each.id !== id)
this.widgets = this.widgets.filter(each => {
if (each.id === id) {
if (each.isDOMWidget) each.element.remove()
} else return true
})
this.setSize(this.size)
}
}
app.registerExtension({
name: "string-master.StringToggleMultiline",
async beforeRegisterNodeDef(nodeType, nodeData) {
if (nodeData.name === "String Toggle (Multiline)")
OVERRIDDEN_SERVER_NODES.set(nodeType, upgradeNodeType(nodeType))
}
})
+40
View File
@@ -0,0 +1,40 @@
export const OVERRIDDEN_SERVER_NODES = new Map()
const registerNodeType = LiteGraph.registerNodeType
LiteGraph.registerNodeType = async (nodeId, baseClass) => registerNodeType.call(LiteGraph, nodeId, OVERRIDDEN_SERVER_NODES.get(baseClass) || baseClass)
export class DividerWidget { // Credit to rgthree
options = { serialize: false }
value = null
name = "divider"
widgetOptions = {
marginTop: 7,
marginBottom: 7,
marginLeft: 15,
marginRight: 15,
color: LiteGraph.WIDGET_OUTLINE_COLOR,
thickness: 1
}
constructor(widgetOptions) {
Object.assign(this.widgetOptions, widgetOptions || {})
}
draw(ctx, node, width, posY, h) {
if (this.widgetOptions.thickness) {
ctx.strokeStyle = this.widgetOptions.color
const x = this.widgetOptions.marginLeft
const y = posY + this.widgetOptions.marginTop
const w = width - this.widgetOptions.marginLeft - this.widgetOptions.marginRight
ctx.stroke(new Path2D(`M ${x} ${y} h ${w}`))
}
}
computeSize(width) {
return [
width,
this.widgetOptions.marginTop + this.widgetOptions.marginBottom + this.widgetOptions.thickness
]
}
}