feat: 新增ThinkRemover_UTK节点,支持分离<think>内容和剩余内容,版本号更新至1.1.7
This commit is contained in:
+28
-3
@@ -8,13 +8,17 @@ A comprehensive toolkit for ComfyUI that provides various utility nodes for imag
|
||||
:license: MIT, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
__version__ = "1.1.6"
|
||||
__version__ = "1.1.7"
|
||||
__author__ = "CyberDickLang"
|
||||
__email__ = "286878701@qq.com"
|
||||
__url__ = "https://github.com/whmc76"
|
||||
|
||||
# 更新日志
|
||||
CHANGELOG = {
|
||||
"1.1.7": [
|
||||
"新增 ThinkRemover_UTK 节点:",
|
||||
"- 支持分离文本中的<think>内容和剩余内容,便于上下文处理和提示词优化"
|
||||
],
|
||||
"1.1.6": [
|
||||
"新增 TextboxNode_UTK 节点(文本框节点):",
|
||||
"- 基于 ComfyUI-LayerStyle 项目集成",
|
||||
@@ -195,6 +199,8 @@ try:
|
||||
|
||||
# 文本框节点
|
||||
from .nodes.tools.textbox_node import NODE_CLASS_MAPPINGS as TEXTBOX_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS as TEXTBOX_DISPLAY_MAPPINGS
|
||||
from .nodes.tools.text_concatenate_node import NODE_CLASS_MAPPINGS as TEXT_CONCATENATE_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS as TEXT_CONCATENATE_DISPLAY_MAPPINGS
|
||||
from .nodes.tools.math_expression_node import NODE_CLASS_MAPPINGS as MATH_EXPRESSION_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS as MATH_EXPRESSION_DISPLAY_MAPPINGS
|
||||
|
||||
except ImportError as e:
|
||||
print(f"导入错误: {e}")
|
||||
@@ -215,6 +221,10 @@ except ImportError as e:
|
||||
IMITATION_HUE_DISPLAY_MAPPINGS = {}
|
||||
TEXTBOX_MAPPINGS = {}
|
||||
TEXTBOX_DISPLAY_MAPPINGS = {}
|
||||
TEXT_CONCATENATE_MAPPINGS = {}
|
||||
TEXT_CONCATENATE_DISPLAY_MAPPINGS = {}
|
||||
MATH_EXPRESSION_MAPPINGS = {}
|
||||
MATH_EXPRESSION_DISPLAY_MAPPINGS = {}
|
||||
|
||||
# 尝试导入其他可能有依赖的节点
|
||||
try:
|
||||
@@ -307,6 +317,14 @@ except ImportError:
|
||||
RESTORE_CROP_MAPPINGS = {}
|
||||
RESTORE_CROP_DISPLAY = {}
|
||||
|
||||
try:
|
||||
from .nodes.tools.think_remover_node import NODE_CLASS_MAPPINGS as THINK_REMOVER_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS as THINK_REMOVER_DISPLAY_MAPPINGS
|
||||
except ImportError as e:
|
||||
print(f"导入错误: {e}")
|
||||
# ... 其它 except ...
|
||||
THINK_REMOVER_MAPPINGS = {}
|
||||
THINK_REMOVER_DISPLAY_MAPPINGS = {}
|
||||
|
||||
# 合并所有节点映射
|
||||
NODE_CLASS_MAPPINGS = {}
|
||||
NODE_CLASS_MAPPINGS.update(EMPTY_UNIT_MAPPINGS)
|
||||
@@ -332,6 +350,9 @@ NODE_CLASS_MAPPINGS.update(MASK_OPERATIONS_MAPPINGS)
|
||||
NODE_CLASS_MAPPINGS.update(LOAD_AUDIO_MAPPINGS)
|
||||
NODE_CLASS_MAPPINGS.update(AUDIO_CROP_MAPPINGS)
|
||||
NODE_CLASS_MAPPINGS.update(TEXTBOX_MAPPINGS)
|
||||
NODE_CLASS_MAPPINGS.update(TEXT_CONCATENATE_MAPPINGS)
|
||||
NODE_CLASS_MAPPINGS.update(MATH_EXPRESSION_MAPPINGS)
|
||||
NODE_CLASS_MAPPINGS.update(THINK_REMOVER_MAPPINGS)
|
||||
|
||||
# 合并显示名称映射
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {}
|
||||
@@ -358,6 +379,9 @@ NODE_DISPLAY_NAME_MAPPINGS.update(LOAD_AUDIO_DISPLAY_MAPPINGS)
|
||||
NODE_DISPLAY_NAME_MAPPINGS.update(AUDIO_CROP_DISPLAY_MAPPINGS)
|
||||
NODE_DISPLAY_NAME_MAPPINGS.update(MASK_OPERATIONS_DISPLAY_MAPPINGS)
|
||||
NODE_DISPLAY_NAME_MAPPINGS.update(TEXTBOX_DISPLAY_MAPPINGS)
|
||||
NODE_DISPLAY_NAME_MAPPINGS.update(TEXT_CONCATENATE_DISPLAY_MAPPINGS)
|
||||
NODE_DISPLAY_NAME_MAPPINGS.update(MATH_EXPRESSION_DISPLAY_MAPPINGS)
|
||||
NODE_DISPLAY_NAME_MAPPINGS.update(THINK_REMOVER_DISPLAY_MAPPINGS)
|
||||
|
||||
NODE_CATEGORIES = {
|
||||
"UniversalToolkit": [
|
||||
@@ -385,9 +409,10 @@ NODE_CATEGORIES = {
|
||||
"CropByMask_UTK",
|
||||
"RestoreCropBox_UTK",
|
||||
"Show_UTK",
|
||||
"ShowFloat_UTK",
|
||||
"PreviewMask_UTK",
|
||||
"TextboxNode_UTK",
|
||||
"TextConcatenate_UTK",
|
||||
"MathExpression_UTK",
|
||||
"ThinkRemover_UTK",
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,245 @@
|
||||
import ast
|
||||
import math
|
||||
import random
|
||||
import operator as op
|
||||
|
||||
# Hack: string type that is always equal in not equal comparisons
|
||||
class AnyType(str):
|
||||
def __ne__(self, __value: object) -> bool:
|
||||
return False
|
||||
|
||||
any = AnyType("*")
|
||||
|
||||
operators = {
|
||||
ast.Add: op.add,
|
||||
ast.Sub: op.sub,
|
||||
ast.Mult: op.mul,
|
||||
ast.Div: op.truediv,
|
||||
ast.FloorDiv: op.floordiv,
|
||||
ast.Pow: op.pow,
|
||||
ast.BitXor: op.xor,
|
||||
ast.USub: op.neg,
|
||||
ast.Mod: op.mod,
|
||||
ast.BitAnd: op.and_,
|
||||
ast.BitOr: op.or_,
|
||||
ast.Invert: op.invert,
|
||||
ast.And: lambda a, b: 1 if a and b else 0,
|
||||
ast.Or: lambda a, b: 1 if a or b else 0,
|
||||
ast.Not: lambda a: 0 if a else 1,
|
||||
ast.RShift: op.rshift,
|
||||
ast.LShift: op.lshift
|
||||
}
|
||||
|
||||
functions = {
|
||||
"round": {
|
||||
"args": (1, 2),
|
||||
"call": lambda a, b = None: round(a, b),
|
||||
"hint": "number, dp? = 0"
|
||||
},
|
||||
"ceil": {
|
||||
"args": (1, 1),
|
||||
"call": lambda a: math.ceil(a),
|
||||
"hint": "number"
|
||||
},
|
||||
"floor": {
|
||||
"args": (1, 1),
|
||||
"call": lambda a: math.floor(a),
|
||||
"hint": "number"
|
||||
},
|
||||
"min": {
|
||||
"args": (2, None),
|
||||
"call": lambda *args: min(*args),
|
||||
"hint": "...numbers"
|
||||
},
|
||||
"max": {
|
||||
"args": (2, None),
|
||||
"call": lambda *args: max(*args),
|
||||
"hint": "...numbers"
|
||||
},
|
||||
"randomint": {
|
||||
"args": (2, 2),
|
||||
"call": lambda a, b: random.randint(a, b),
|
||||
"hint": "min, max"
|
||||
},
|
||||
"randomchoice": {
|
||||
"args": (2, None),
|
||||
"call": lambda *args: random.choice(args),
|
||||
"hint": "...numbers"
|
||||
},
|
||||
"sqrt": {
|
||||
"args": (1, 1),
|
||||
"call": lambda a: math.sqrt(a),
|
||||
"hint": "number"
|
||||
},
|
||||
"int": {
|
||||
"args": (1, 1),
|
||||
"call": lambda a = None: int(a),
|
||||
"hint": "number"
|
||||
},
|
||||
"iif": {
|
||||
"args": (3, 3),
|
||||
"call": lambda a, b, c = None: b if a else c,
|
||||
"hint": "value, truepart, falsepart"
|
||||
},
|
||||
}
|
||||
|
||||
autocompleteWords = list({
|
||||
"text": x,
|
||||
"value": f"{x}()",
|
||||
"showValue": False,
|
||||
"hint": f"{functions[x]['hint']}",
|
||||
"caretOffset": -1
|
||||
} for x in functions.keys())
|
||||
|
||||
class MathExpression_UTK:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"expression": ("STRING", {"multiline": True, "dynamicPrompts": False, "pysssss.autocomplete": {
|
||||
"words": autocompleteWords,
|
||||
"separator": ""
|
||||
}}),
|
||||
},
|
||||
"optional": {
|
||||
"a": (any, ),
|
||||
"b": (any,),
|
||||
"c": (any, ),
|
||||
},
|
||||
"hidden": {"extra_pnginfo": "EXTRA_PNGINFO",
|
||||
"prompt": "PROMPT"},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("INT", "FLOAT", )
|
||||
FUNCTION = "evaluate"
|
||||
CATEGORY = "UniversalToolkit/Tools"
|
||||
OUTPUT_NODE = True
|
||||
|
||||
@classmethod
|
||||
def IS_CHANGED(s, expression, **kwargs):
|
||||
if "random" in expression:
|
||||
return float("nan")
|
||||
return expression
|
||||
|
||||
def get_widget_value(self, extra_pnginfo, prompt, node_name, widget_name):
|
||||
workflow = extra_pnginfo["workflow"] if "workflow" in extra_pnginfo else { "nodes": [] }
|
||||
node_id = None
|
||||
for node in workflow["nodes"]:
|
||||
name = node["type"]
|
||||
if "properties" in node:
|
||||
if "Node name for S&R" in node["properties"]:
|
||||
name = node["properties"]["Node name for S&R"]
|
||||
if name == node_name:
|
||||
node_id = node["id"]
|
||||
break
|
||||
if "title" in node:
|
||||
name = node["title"]
|
||||
if name == node_name:
|
||||
node_id = node["id"]
|
||||
break
|
||||
if node_id is not None:
|
||||
values = prompt[str(node_id)]
|
||||
if "inputs" in values:
|
||||
if widget_name in values["inputs"]:
|
||||
value = values["inputs"][widget_name]
|
||||
if isinstance(value, list):
|
||||
raise ValueError("Converted widgets are not supported via named reference, use the inputs instead.")
|
||||
return value
|
||||
raise NameError(f"Widget not found: {node_name}.{widget_name}")
|
||||
raise NameError(f"Node not found: {node_name}.{widget_name}")
|
||||
|
||||
def get_size(self, target, property):
|
||||
if isinstance(target, dict) and "samples" in target:
|
||||
# Latent
|
||||
if property == "width":
|
||||
return target["samples"].shape[3] * 8
|
||||
return target["samples"].shape[2] * 8
|
||||
else:
|
||||
# Image
|
||||
if property == "width":
|
||||
return target.shape[2]
|
||||
return target.shape[1]
|
||||
|
||||
def evaluate(self, expression, prompt, extra_pnginfo={}, a=None, b=None, c=None):
|
||||
expression = expression.replace('\n', ' ').replace('\r', '')
|
||||
node = ast.parse(expression, mode='eval').body
|
||||
|
||||
lookup = {"a": a, "b": b, "c": c}
|
||||
|
||||
def eval_op(node, l, r):
|
||||
l = eval_expr(l)
|
||||
r = eval_expr(r)
|
||||
l = l if isinstance(l, int) else float(l)
|
||||
r = r if isinstance(r, int) else float(r)
|
||||
return operators[type(node.op)](l, r)
|
||||
|
||||
def eval_expr(node):
|
||||
if isinstance(node, ast.Constant) or isinstance(node, ast.Num):
|
||||
return node.n
|
||||
elif isinstance(node, ast.BinOp):
|
||||
return eval_op(node, node.left, node.right)
|
||||
elif isinstance(node, ast.BoolOp):
|
||||
return eval_op(node, node.values[0], node.values[1])
|
||||
elif isinstance(node, ast.UnaryOp):
|
||||
return operators[type(node.op)](eval_expr(node.operand))
|
||||
elif isinstance(node, ast.Attribute):
|
||||
if node.value.id in lookup:
|
||||
if node.attr == "width" or node.attr == "height":
|
||||
return self.get_size(lookup[node.value.id], node.attr)
|
||||
|
||||
return self.get_widget_value(extra_pnginfo, prompt, node.value.id, node.attr)
|
||||
elif isinstance(node, ast.Name):
|
||||
if node.id in lookup:
|
||||
val = lookup[node.id]
|
||||
if isinstance(val, (int, float, complex)):
|
||||
return val
|
||||
else:
|
||||
raise TypeError(
|
||||
f"Compex types (LATENT/IMAGE) need to reference their width/height, e.g. {node.id}.width")
|
||||
raise NameError(f"Name not found: {node.id}")
|
||||
elif isinstance(node, ast.Call):
|
||||
if node.func.id in functions:
|
||||
fn = functions[node.func.id]
|
||||
l = len(node.args)
|
||||
if l < fn["args"][0] or (fn["args"][1] is not None and l > fn["args"][1]):
|
||||
if fn["args"][1] is None:
|
||||
toErr = " or more"
|
||||
else:
|
||||
toErr = f" to {fn['args'][1]}"
|
||||
raise SyntaxError(
|
||||
f"Invalid function call: {node.func.id} requires {fn['args'][0]}{toErr} arguments")
|
||||
args = []
|
||||
for arg in node.args:
|
||||
args.append(eval_expr(arg))
|
||||
return fn["call"](*args)
|
||||
raise NameError(f"Invalid function call: {node.func.id}")
|
||||
elif isinstance(node, ast.Compare):
|
||||
l = eval_expr(node.left)
|
||||
r = eval_expr(node.comparators[0])
|
||||
if isinstance(node.ops[0], ast.Eq):
|
||||
return 1 if l == r else 0
|
||||
if isinstance(node.ops[0], ast.NotEq):
|
||||
return 1 if l != r else 0
|
||||
if isinstance(node.ops[0], ast.Gt):
|
||||
return 1 if l > r else 0
|
||||
if isinstance(node.ops[0], ast.GtE):
|
||||
return 1 if l >= r else 0
|
||||
if isinstance(node.ops[0], ast.Lt):
|
||||
return 1 if l < r else 0
|
||||
if isinstance(node.ops[0], ast.LtE):
|
||||
return 1 if l <= r else 0
|
||||
raise NotImplementedError(
|
||||
"Operator " + node.ops[0].__class__.__name__ + " not supported.")
|
||||
else:
|
||||
raise TypeError(node)
|
||||
|
||||
r = eval_expr(node)
|
||||
return {"ui": {"value": [r]}, "result": (int(r), float(r),)}
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"MathExpression_UTK": MathExpression_UTK,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"MathExpression_UTK": "Math Expression (UTK)",
|
||||
}
|
||||
@@ -38,91 +38,11 @@ class Show_UTK:
|
||||
outs[6] = input # LATENT
|
||||
return tuple(outs)
|
||||
|
||||
class ShowInt_UTK:
|
||||
CATEGORY = "UniversalToolkit/Tools"
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {"required": {"int_val": ("INT",)}}
|
||||
RETURN_TYPES = ("INT",)
|
||||
RETURN_NAMES = ("int_val",)
|
||||
FUNCTION = "show"
|
||||
IS_PREVIEW = True
|
||||
def show(self, int_val=None):
|
||||
if int_val is None:
|
||||
int_val = 0
|
||||
return (int_val,)
|
||||
|
||||
class ShowFloat_UTK:
|
||||
CATEGORY = "UniversalToolkit/Tools"
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {"required": {"float_val": ("FLOAT",)}}
|
||||
RETURN_TYPES = ("FLOAT",)
|
||||
RETURN_NAMES = ("float_val",)
|
||||
FUNCTION = "show"
|
||||
IS_PREVIEW = True
|
||||
def show(self, float_val=None):
|
||||
if float_val is None:
|
||||
float_val = 0.0
|
||||
return (float_val,)
|
||||
|
||||
class ShowList_UTK:
|
||||
CATEGORY = "UniversalToolkit/Tools"
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {"required": {"list_val": ("LIST",)}}
|
||||
RETURN_TYPES = ("LIST",)
|
||||
RETURN_NAMES = ("list_val",)
|
||||
FUNCTION = "show"
|
||||
IS_PREVIEW = True
|
||||
def show(self, list_val=None):
|
||||
if list_val is None:
|
||||
list_val = []
|
||||
return (list_val,)
|
||||
|
||||
class ShowText_UTK:
|
||||
CATEGORY = "UniversalToolkit/Tools"
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {"required": {"text": ("STRING",)}}
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("text",)
|
||||
FUNCTION = "show"
|
||||
IS_PREVIEW = True
|
||||
def show(self, text=None):
|
||||
if text is None:
|
||||
text = ""
|
||||
return (text,)
|
||||
|
||||
class PreviewMask_UTK:
|
||||
CATEGORY = "UniversalToolkit/Tools"
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {"required": {"mask": ("MASK",)}}
|
||||
RETURN_TYPES = ("MASK",)
|
||||
RETURN_NAMES = ("mask",)
|
||||
FUNCTION = "show"
|
||||
IS_PREVIEW = True
|
||||
def show(self, mask=None):
|
||||
if mask is None:
|
||||
mask = torch.zeros([1, 1, 64, 64], dtype=torch.float32)
|
||||
return (mask,)
|
||||
|
||||
# Node mappings
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"Show_UTK": Show_UTK,
|
||||
"ShowInt_UTK": ShowInt_UTK,
|
||||
"ShowFloat_UTK": ShowFloat_UTK,
|
||||
"ShowList_UTK": ShowList_UTK,
|
||||
"ShowText_UTK": ShowText_UTK,
|
||||
"PreviewMask_UTK": PreviewMask_UTK,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"Show_UTK": "Show (UTK)",
|
||||
"ShowInt_UTK": "Show Int (UTK)",
|
||||
"ShowFloat_UTK": "Show Float (UTK)",
|
||||
"ShowList_UTK": "Show List (UTK)",
|
||||
"ShowText_UTK": "Show Text (UTK)",
|
||||
"PreviewMask_UTK": "Preview Mask (UTK)",
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
"""
|
||||
Text Concatenate Node (UTK)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
拼接多个字符串,可自定义分隔符和空白处理。
|
||||
|
||||
:copyright: (c) 2024 by May
|
||||
:license: MIT, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
class TextConcatenate_UTK:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"delimiter": ("STRING", {"default": ", "}),
|
||||
"clean_whitespace": (["true", "false"], {}),
|
||||
},
|
||||
"optional": {
|
||||
"text_a": ("STRING", {"forceInput": True}),
|
||||
"text_b": ("STRING", {"forceInput": True}),
|
||||
"text_c": ("STRING", {"forceInput": True}),
|
||||
"text_d": ("STRING", {"forceInput": True}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
FUNCTION = "text_concatenate"
|
||||
CATEGORY = "UniversalToolkit/Text"
|
||||
DESCRIPTION = "拼接多个字符串,可自定义分隔符和空白处理。"
|
||||
|
||||
def text_concatenate(self, delimiter, clean_whitespace, **kwargs):
|
||||
text_inputs = []
|
||||
# 处理特殊分隔符\n
|
||||
if delimiter in ("\n", "\\n"):
|
||||
delimiter = "\n"
|
||||
# 按输入名排序拼接
|
||||
for k in sorted(kwargs.keys()):
|
||||
v = kwargs[k]
|
||||
if isinstance(v, str):
|
||||
if clean_whitespace == "true":
|
||||
v = v.strip()
|
||||
if v != "":
|
||||
text_inputs.append(v)
|
||||
merged_text = delimiter.join(text_inputs)
|
||||
return (merged_text,)
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"TextConcatenate_UTK": TextConcatenate_UTK,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"TextConcatenate_UTK": "Text Concatenate (UTK)",
|
||||
}
|
||||
@@ -20,7 +20,7 @@ class TextBoxNode_UTK:
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("text",)
|
||||
FUNCTION = "textbox"
|
||||
CATEGORY = "UniversalToolkit/Prompt"
|
||||
CATEGORY = "UniversalToolkit/Text"
|
||||
DESCRIPTION = "提供一个多行文本输入框,作为工作流中的文本参数节点。"
|
||||
|
||||
def textbox(self, text):
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import torch
|
||||
|
||||
class ThinkRemover_UTK:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"text": ("STRING",),
|
||||
},
|
||||
"optional": {}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING", "STRING")
|
||||
RETURN_NAMES = ("cleared_content", "think_content")
|
||||
FUNCTION = "think_remover"
|
||||
CATEGORY = "UniversalToolkit/Tools"
|
||||
DESCRIPTION = "分离文本中的<think>内容和剩余内容。"
|
||||
|
||||
def think_remover(self, text):
|
||||
cleared_content = text
|
||||
think_content = text
|
||||
think_tag = '</think>'
|
||||
# 检查是否包含'</think>'
|
||||
if think_tag in text.lower():
|
||||
end_index = text.lower().index(think_tag) + len(think_tag)
|
||||
think_content = text[:end_index].strip()
|
||||
cleared_content = text[end_index:].strip()
|
||||
return (cleared_content, think_content)
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"ThinkRemover_UTK": ThinkRemover_UTK,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"ThinkRemover_UTK": "Think Remover (UTK)",
|
||||
}
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
[project]
|
||||
name = "universaltoolkit"
|
||||
description = "A comprehensive toolkit based on ComfyUI, providing image, mask, audio, and tools nodes, fully modular and v3 compatible."
|
||||
version = "1.1.6"
|
||||
version = "1.1.7"
|
||||
license = {file = "LICENSE"}
|
||||
dependencies = ["torch", "numpy", "Pillow", "opencv-python", "scipy", "tqdm"]
|
||||
requires-python = ">=3.8"
|
||||
|
||||
Reference in New Issue
Block a user