Skip to content

Commit

Permalink
Merge pull request #58 from sipherxyz/develop
Browse files Browse the repository at this point in the history
Release v1.0.6
  • Loading branch information
tungnguyensipher authored Nov 4, 2024
2 parents 8d538c9 + d0cf2ab commit 50abaac
Show file tree
Hide file tree
Showing 7 changed files with 318 additions and 76 deletions.
17 changes: 16 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
14 changes: 12 additions & 2 deletions modules/llm/chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
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",
Expand All @@ -20,9 +22,16 @@
"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 = [
Expand All @@ -40,6 +49,7 @@
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",
Expand Down
89 changes: 88 additions & 1 deletion modules/utility_nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,42 @@
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))
Expand Down Expand Up @@ -242,7 +278,7 @@ def load_image(self, image: str, channel: str, output_mode=False, url=""):
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")

Expand Down Expand Up @@ -531,6 +567,55 @@ def boolean_primitive(self, value: bool, reverse: bool):
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):
Expand Down Expand Up @@ -1194,6 +1279,7 @@ def random_multiline(self, text: str, amount=1, seed=0):
"NumberScaler": UtilNumberScaler,
"MergeModels": UtilModelMerge,
"TextRandomMultiline": UtilTextRandomMultiline,
"TextSwitchCase": UtilTextSwitchCase,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"LoadImageFromUrl": "Load Image From URL",
Expand Down Expand Up @@ -1229,4 +1315,5 @@ def random_multiline(self, text: str, amount=1, seed=0):
"NumberScaler": "Number Scaler",
"MergeModels": "Merge Models",
"TextRandomMultiline": "Text Random Multiline",
"TextSwitchCase": "Text Switch Case",
}
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -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"]

Expand Down
53 changes: 53 additions & 0 deletions web/text-switch-case.js
Original file line number Diff line number Diff line change
@@ -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);
},
});
73 changes: 2 additions & 71 deletions web/upload.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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) {
Expand All @@ -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;
Expand Down
Loading

0 comments on commit 50abaac

Please sign in to comment.