5 Commits
Author SHA1 Message Date
Tung Nguyen (Blockchain) 50abaace75 Merge pull request #58 from sipherxyz/develop
Release v1.0.6
2024-11-04 21:05:04 +07:00
Tung Nguyen d0cf2abdaf Bump version 1.0.6 2024-11-04 21:01:54 +07:00
Tung Nguyen 36ed264742 add new LLM models 2024-11-04 21:01:02 +07:00
Tung Nguyen fb8bc917ba add TextSwitchCase node 2024-11-04 21:01:02 +07:00
Tung Nguyen (Blockchain) 8d538c9678 Merge pull request #56 from sipherxyz/develop
Fix invalid path aux.py in windows
2024-10-31 20:34:42 +07:00
7 changed files with 318 additions and 76 deletions
+16 -1
View File
@@ -67,6 +67,21 @@ Randomizes the order of lines in a multiline string.
![text random multiline](https://github.com/user-attachments/assets/86f811e3-579e-4ccc-81a3-e216cd851d3c)
#### 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.
![text switch case](https://github.com/user-attachments/assets/4c5450a8-6a3a-4d3c-8c2a-c6e3a33cb95f)
### Inpainting Nodes
#### PrepareImageAndMaskForInpaint
@@ -199,4 +214,4 @@ Handles completion requests to LLMs.
- `config`: Model configuration
- `seed`: Random seed
![Screenshot 2024-10-30 at 11 20 12](https://github.com/user-attachments/assets/45b8d4fd-57cd-4bd9-8274-d3e6ac4ef938)
![Screenshot 2024-10-30 at 11 20 12](https://github.com/user-attachments/assets/45b8d4fd-57cd-4bd9-8274-d3e6ac4ef938)
+12 -2
View File
@@ -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",
+88 -1
View File
@@ -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
View File
@@ -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"]
+53
View File
@@ -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
View File
@@ -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
View File
@@ -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;
}
});
});
}