Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
50abaace75 | ||
|
|
d0cf2abdaf | ||
|
|
36ed264742 | ||
|
|
fb8bc917ba | ||
|
|
8d538c9678 |
@@ -67,6 +67,21 @@ Randomizes the order of lines in a multiline string.
|
||||
|
||||

|
||||
|
||||
#### TextSwitchCase
|
||||
|
||||
Switch between multiple cases based on a condition.
|
||||
|
||||
**Inputs:**
|
||||
|
||||
- `switch_cases`: Switch cases, separated by new lines
|
||||
- `condition`: Condition to switch on
|
||||
- `default_value`: Default value when no condition matches
|
||||
- `delimiter`: Delimiter between case and value, default is `:`
|
||||
|
||||
The `switch_cases` format is `case<delimiter>value`, where `case` is the condition to match and `value` is the value to return when the condition matches. You can have new lines in the value to return multiple lines.
|
||||
|
||||

|
||||
|
||||
### Inpainting Nodes
|
||||
|
||||
#### PrepareImageAndMaskForInpaint
|
||||
@@ -199,4 +214,4 @@ Handles completion requests to LLMs.
|
||||
- `config`: Model configuration
|
||||
- `seed`: Random seed
|
||||
|
||||

|
||||

|
||||
|
||||
+12
-2
@@ -11,6 +11,8 @@ from ..utils import ensure_package, tensor2pil, pil2base64
|
||||
gpt_models = [
|
||||
"gpt-3.5-turbo",
|
||||
"gpt-3.5-turbo-16k",
|
||||
"gpt-4o",
|
||||
"gpt-4o-mini",
|
||||
"gpt-4-turbo",
|
||||
"gpt-4-vision-preview",
|
||||
"gpt-4-turbo-preview",
|
||||
@@ -20,9 +22,16 @@ gpt_models = [
|
||||
"gpt-4",
|
||||
]
|
||||
|
||||
gpt_vision_models = ["gpt-4-turbo", "gpt-4-turbo-preview", "gpt-4-vision-preview"]
|
||||
gpt_vision_models = ["gpt-4o", "gpt-4o-mini", "gpt-4-turbo", "gpt-4-turbo-preview", "gpt-4-vision-preview"]
|
||||
|
||||
claude3_models = ["claude-3-opus-20240229", "claude-3-sonnet-20240229", "claude-3-haiku-20240307"]
|
||||
claude3_models = [
|
||||
"claude-3-5-sonnet-latest",
|
||||
"claude-3-5-sonnet-20241022",
|
||||
"claude-3-opus-latest",
|
||||
"claude-3-opus-20240229",
|
||||
"claude-3-sonnet-20240229",
|
||||
"claude-3-haiku-20240307",
|
||||
]
|
||||
claude2_models = ["claude-2.1"]
|
||||
|
||||
aws_regions = [
|
||||
@@ -40,6 +49,7 @@ aws_regions = [
|
||||
bedrock_anthropic_versions = ["bedrock-2023-05-31"]
|
||||
|
||||
bedrock_claude3_models = [
|
||||
"anthropic.claude-3-5-sonnet-20241022-v2:0",
|
||||
"anthropic.claude-3-haiku-20240307-v1:0",
|
||||
"anthropic.claude-3-sonnet-20240229-v1:0",
|
||||
"anthropic.claude-3-opus-20240229-v1:0",
|
||||
|
||||
@@ -20,6 +20,42 @@ from .utils import pil2tensor, tensor2pil, ensure_package, get_dict_attribute
|
||||
MAX_RESOLUTION = 8192
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
any_type = AnyType("*")
|
||||
|
||||
|
||||
def prepare_image_for_preview(image: Image.Image, output_dir: str, prefix=None):
|
||||
if prefix is None:
|
||||
prefix = "preview_" + "".join(random.choice("abcdefghijklmnopqrstupvxyz") for x in range(5))
|
||||
@@ -242,7 +278,7 @@ class UtilLoadImageAsMaskFromUrl(UtilLoadImageFromUrl):
|
||||
mask = np.array(mask, dtype=np.float32) / 255.0
|
||||
mask = torch.from_numpy(mask)
|
||||
if channel == "alpha":
|
||||
mask = 1. - mask
|
||||
mask = 1.0 - mask
|
||||
else:
|
||||
mask = torch.zeros((64, 64), dtype=torch.float32, device="cpu")
|
||||
|
||||
@@ -531,6 +567,55 @@ class UtilBooleanPrimitive:
|
||||
return (value, str(value))
|
||||
|
||||
|
||||
class UtilTextSwitchCase:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"switch_cases": (
|
||||
"STRING",
|
||||
{
|
||||
"default": "",
|
||||
"multiline": True,
|
||||
"dynamicPrompts": False,
|
||||
"placeholder": "case_1:output_1\ncase_2:output_2\nthat span multiple lines\ncase_3:output_3",
|
||||
},
|
||||
),
|
||||
"condition": ("STRING", {"default": ""}),
|
||||
"default_value": ("STRING", {"default": ""}),
|
||||
"delimiter": ("STRING", {"default": ":"}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
CATEGORY = "Art Venture/Utils"
|
||||
FUNCTION = "text_switch_case"
|
||||
|
||||
def text_switch_case(self, switch_cases: str, condition: str, default_value: str, delimiter: str = ":"):
|
||||
# Split into cases first
|
||||
cases = switch_cases.split("\n")
|
||||
current_case = None
|
||||
current_output = []
|
||||
|
||||
for line in cases:
|
||||
if delimiter in line:
|
||||
# Process previous case if exists
|
||||
if current_case is not None and condition == current_case:
|
||||
return ("\n".join(current_output),)
|
||||
|
||||
# Start new case
|
||||
current_case, output = line.split(delimiter, 1)
|
||||
current_output = [output]
|
||||
elif current_case is not None:
|
||||
current_output.append(line)
|
||||
|
||||
# Check last case
|
||||
if current_case is not None and condition == current_case:
|
||||
return ("\n".join(current_output),)
|
||||
|
||||
return (default_value,)
|
||||
|
||||
|
||||
class UtilImageMuxer:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
@@ -1194,6 +1279,7 @@ NODE_CLASS_MAPPINGS = {
|
||||
"NumberScaler": UtilNumberScaler,
|
||||
"MergeModels": UtilModelMerge,
|
||||
"TextRandomMultiline": UtilTextRandomMultiline,
|
||||
"TextSwitchCase": UtilTextSwitchCase,
|
||||
}
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"LoadImageFromUrl": "Load Image From URL",
|
||||
@@ -1229,4 +1315,5 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"NumberScaler": "Number Scaler",
|
||||
"MergeModels": "Merge Models",
|
||||
"TextRandomMultiline": "Text Random Multiline",
|
||||
"TextSwitchCase": "Text Switch Case",
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
[project]
|
||||
name = "comfyui-art-venture"
|
||||
description = "A comprehensive set of custom nodes for ComfyUI, focusing on utilities for image processing, JSON manipulation, model operations and working with object via URLs"
|
||||
version = "1.0.5"
|
||||
version = "1.0.6"
|
||||
license = "LICENSE"
|
||||
dependencies = ["timm==0.6.13", "transformers", "fairscale", "pycocoevalcap", "opencv-python", "qrcode[pil]", "pytorch_lightning", "kornia", "pydantic", "segment_anything", "boto3>=1.34.101"]
|
||||
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
import { app } from '../../../scripts/app.js';
|
||||
import { ComfyWidgets } from '../../../scripts/widgets.js';
|
||||
|
||||
import {
|
||||
addKVState,
|
||||
chainCallback,
|
||||
hideWidgetForGood,
|
||||
addWidgetChangeCallback,
|
||||
} from './utils.js';
|
||||
|
||||
function addTextSwitchCaseWidget(nodeType) {
|
||||
chainCallback(nodeType.prototype, 'onNodeCreated', function () {
|
||||
const dataWidget = this.widgets.find((w) => w.name === 'switch_cases');
|
||||
const delimiterWidget = this.widgets.find((w) => w.name === 'delimiter');
|
||||
this.widgets = this.widgets.filter((w) => w.name !== 'condition');
|
||||
|
||||
let conditionCombo = null;
|
||||
|
||||
const updateConditionCombo = () => {
|
||||
if (!delimiterWidget.value) return;
|
||||
|
||||
const cases = (dataWidget.value ?? '')
|
||||
.split('\n')
|
||||
.filter((line) => line.includes(delimiterWidget.value))
|
||||
.map((line) => line.split(delimiterWidget.value)[0]);
|
||||
|
||||
if (!conditionCombo) {
|
||||
conditionCombo = ComfyWidgets['COMBO'](this, 'condition', [
|
||||
['__default__', ...(cases ?? [])],
|
||||
]).widget;
|
||||
} else {
|
||||
conditionCombo.options.values = ['__default__', ...cases];
|
||||
}
|
||||
};
|
||||
|
||||
updateConditionCombo();
|
||||
dataWidget.inputEl.addEventListener('input', updateConditionCombo);
|
||||
addWidgetChangeCallback(delimiterWidget, updateConditionCombo);
|
||||
});
|
||||
}
|
||||
|
||||
app.registerExtension({
|
||||
name: 'ArtVenture.TextSwitchCase',
|
||||
async beforeRegisterNodeDef(nodeType, nodeData) {
|
||||
if (!nodeData) return;
|
||||
if (nodeData.name !== 'TextSwitchCase') {
|
||||
return;
|
||||
}
|
||||
|
||||
addKVState(nodeType);
|
||||
addTextSwitchCaseWidget(nodeType);
|
||||
},
|
||||
});
|
||||
+2
-71
@@ -3,6 +3,8 @@ import { api } from '../../../scripts/api.js';
|
||||
import { $el } from '../../../scripts/ui.js';
|
||||
import { createImageHost } from '../../../scripts/ui/imagePreview.js';
|
||||
|
||||
import { chainCallback, addKVState } from './utils.js';
|
||||
|
||||
const style = `
|
||||
.comfy-img-preview video {
|
||||
object-fit: contain;
|
||||
@@ -15,24 +17,6 @@ const URL_REGEX = /^((blob:)?https?:\/\/|\/view\?|\/api\/view\?|data:image\/)/
|
||||
|
||||
const supportedNodes = ['LoadImageFromUrl', 'LoadImageAsMaskFromUrl', 'LoadVideoFromUrl'];
|
||||
|
||||
function chainCallback(object, property, callback) {
|
||||
if (object == undefined) {
|
||||
//This should not happen.
|
||||
console.error('Tried to add callback to non-existant object');
|
||||
return;
|
||||
}
|
||||
if (property in object) {
|
||||
const callback_orig = object[property];
|
||||
object[property] = function () {
|
||||
const r = callback_orig.apply(this, arguments);
|
||||
callback.apply(this, arguments);
|
||||
return r;
|
||||
};
|
||||
} else {
|
||||
object[property] = callback;
|
||||
}
|
||||
}
|
||||
|
||||
function injectHidden(widget) {
|
||||
widget.computeSize = (target_width) => {
|
||||
if (widget.hidden) {
|
||||
@@ -54,59 +38,6 @@ function injectHidden(widget) {
|
||||
});
|
||||
}
|
||||
|
||||
function addKVState(nodeType) {
|
||||
chainCallback(nodeType.prototype, 'onNodeCreated', function () {
|
||||
chainCallback(this, 'onConfigure', function (info) {
|
||||
if (!this.widgets) {
|
||||
//Node has no widgets, there is nothing to restore
|
||||
return;
|
||||
}
|
||||
if (typeof info.widgets_values != 'object') {
|
||||
//widgets_values is in some unknown inactionable format
|
||||
return;
|
||||
}
|
||||
let widgetDict = info.widgets_values;
|
||||
if (widgetDict.length == undefined) {
|
||||
for (let w of this.widgets) {
|
||||
if (w.name in widgetDict) {
|
||||
w.value = widgetDict[w.name];
|
||||
} else {
|
||||
//attempt to restore default value
|
||||
let inputs = LiteGraph.getNodeType(this.type).nodeData.input;
|
||||
let initialValue = null;
|
||||
if (inputs?.required?.hasOwnProperty(w.name)) {
|
||||
if (inputs.required[w.name][1]?.hasOwnProperty('default')) {
|
||||
initialValue = inputs.required[w.name][1].default;
|
||||
} else if (inputs.required[w.name][0].length) {
|
||||
initialValue = inputs.required[w.name][0][0];
|
||||
}
|
||||
} else if (inputs?.optional?.hasOwnProperty(w.name)) {
|
||||
if (inputs.optional[w.name][1]?.hasOwnProperty('default')) {
|
||||
initialValue = inputs.optional[w.name][1].default;
|
||||
} else if (inputs.optional[w.name][0].length) {
|
||||
initialValue = inputs.optional[w.name][0][0];
|
||||
}
|
||||
}
|
||||
if (initialValue) {
|
||||
w.value = initialValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
chainCallback(this, 'onSerialize', function (info) {
|
||||
info.widgets_values = {};
|
||||
if (!this.widgets) {
|
||||
//object has no widgets, there is nothing to store
|
||||
return;
|
||||
}
|
||||
for (let w of this.widgets) {
|
||||
info.widgets_values[w.name] = w.value;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function migrateWidget(nodeType, oldWidgetName, newWidgetName) {
|
||||
chainCallback(nodeType.prototype, 'onNodeCreated', function () {
|
||||
if (!this.widgets) return;
|
||||
|
||||
+146
@@ -0,0 +1,146 @@
|
||||
export const CONVERTED_TYPE = "converted-widget";
|
||||
|
||||
export function hideWidgetForGood(node, widget, suffix = "") {
|
||||
widget.origType = widget.type;
|
||||
widget.origComputeSize = widget.computeSize;
|
||||
widget.computeSize = () => [0, -4]; // -4 is due to the gap litegraph adds between widgets automatically
|
||||
widget.type = CONVERTED_TYPE + suffix;
|
||||
|
||||
// Hide any linked widgets, e.g. seed+seedControl
|
||||
if (widget.linkedWidgets) {
|
||||
for (const w of widget.linkedWidgets) {
|
||||
hideWidgetForGood(node, w, ":" + widget.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const doesInputWithNameExist = (node, name) => {
|
||||
return node.inputs ? node.inputs.some((input) => input.name === name) : false;
|
||||
};
|
||||
|
||||
const HIDDEN_TAG = "tschide";
|
||||
const origProps = {};
|
||||
|
||||
// Toggle Widget + change size
|
||||
export function toggleWidget(node, widget, show = false, suffix = "", updateSize = true) {
|
||||
if (!widget || doesInputWithNameExist(node, widget.name)) return;
|
||||
|
||||
// Store the original properties of the widget if not already stored
|
||||
if (!origProps[widget.name]) {
|
||||
origProps[widget.name] = {
|
||||
origType: widget.type,
|
||||
origComputeSize: widget.computeSize,
|
||||
};
|
||||
}
|
||||
|
||||
const origSize = node.size;
|
||||
|
||||
// Set the widget type and computeSize based on the show flag
|
||||
widget.type = show ? origProps[widget.name].origType : HIDDEN_TAG + suffix;
|
||||
widget.computeSize = show
|
||||
? origProps[widget.name].origComputeSize
|
||||
: () => [0, -4];
|
||||
|
||||
// Recursively handle linked widgets if they exist
|
||||
widget.linkedWidgets?.forEach((w) =>
|
||||
toggleWidget(node, w, ":" + widget.name, show)
|
||||
);
|
||||
|
||||
// Calculate the new height for the node based on its computeSize method
|
||||
if (updateSize) {
|
||||
const newHeight = node.computeSize()[1];
|
||||
node.setSize([node.size[0], newHeight]);
|
||||
}
|
||||
}
|
||||
|
||||
export function addWidgetChangeCallback(widget, callback) {
|
||||
let widgetValue = widget.value;
|
||||
let originalDescriptor = Object.getOwnPropertyDescriptor(widget, "value");
|
||||
Object.defineProperty(widget, "value", {
|
||||
get() {
|
||||
return originalDescriptor && originalDescriptor.get
|
||||
? originalDescriptor.get.call(widget)
|
||||
: widgetValue;
|
||||
},
|
||||
set(newVal) {
|
||||
if (originalDescriptor && originalDescriptor.set) {
|
||||
originalDescriptor.set.call(widget, newVal);
|
||||
} else {
|
||||
widgetValue = newVal;
|
||||
}
|
||||
|
||||
callback(newVal);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function chainCallback(object, property, callback) {
|
||||
if (object == undefined) {
|
||||
//This should not happen.
|
||||
console.error("Tried to add callback to non-existant object");
|
||||
return;
|
||||
}
|
||||
if (property in object) {
|
||||
const callback_orig = object[property];
|
||||
object[property] = function () {
|
||||
const r = callback_orig.apply(this, arguments);
|
||||
callback.apply(this, arguments);
|
||||
return r;
|
||||
};
|
||||
} else {
|
||||
object[property] = callback;
|
||||
}
|
||||
}
|
||||
|
||||
export function addKVState(nodeType) {
|
||||
chainCallback(nodeType.prototype, 'onNodeCreated', function () {
|
||||
chainCallback(this, 'onConfigure', function (info) {
|
||||
if (!this.widgets) {
|
||||
//Node has no widgets, there is nothing to restore
|
||||
return;
|
||||
}
|
||||
if (typeof info.widgets_values != 'object') {
|
||||
//widgets_values is in some unknown inactionable format
|
||||
return;
|
||||
}
|
||||
let widgetDict = info.widgets_values;
|
||||
if (widgetDict.length == undefined) {
|
||||
for (let w of this.widgets) {
|
||||
if (w.name in widgetDict) {
|
||||
w.value = widgetDict[w.name];
|
||||
} else {
|
||||
//attempt to restore default value
|
||||
let inputs = LiteGraph.getNodeType(this.type).nodeData.input;
|
||||
let initialValue = null;
|
||||
if (inputs?.required?.hasOwnProperty(w.name)) {
|
||||
if (inputs.required[w.name][1]?.hasOwnProperty('default')) {
|
||||
initialValue = inputs.required[w.name][1].default;
|
||||
} else if (inputs.required[w.name][0].length) {
|
||||
initialValue = inputs.required[w.name][0][0];
|
||||
}
|
||||
} else if (inputs?.optional?.hasOwnProperty(w.name)) {
|
||||
if (inputs.optional[w.name][1]?.hasOwnProperty('default')) {
|
||||
initialValue = inputs.optional[w.name][1].default;
|
||||
} else if (inputs.optional[w.name][0].length) {
|
||||
initialValue = inputs.optional[w.name][0][0];
|
||||
}
|
||||
}
|
||||
if (initialValue) {
|
||||
w.value = initialValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
chainCallback(this, 'onSerialize', function (info) {
|
||||
info.widgets_values = {};
|
||||
if (!this.widgets) {
|
||||
//object has no widgets, there is nothing to store
|
||||
return;
|
||||
}
|
||||
for (let w of this.widgets) {
|
||||
info.widgets_values[w.name] = w.value;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user