From fdc761ebfa8239e26a42d0508c4f06430549fab3 Mon Sep 17 00:00:00 2001 From: yolain Date: Tue, 9 Apr 2024 23:48:41 +0800 Subject: [PATCH] add:easy ipadapterStyleComposition --- README.en.md | 3 +- README.md | 3 +- py/easyNodes.py | 153 ++++++++++++++++++++++-------- py/libs/cache.py | 84 +++++++++++++++- py/libs/utils.py | 7 ++ py/logic.py | 53 +++++++++-- web/js/common/i18n.js | 1 + web/js/easy/easyDynamicWidgets.js | 32 +++++++ web/js/easy/easyExtraMenu.js | 3 +- web/js/easy/easySuggestion.js | 2 +- 10 files changed, 285 insertions(+), 56 deletions(-) diff --git a/README.en.md b/README.en.md index 0b34e38..6465552 100644 --- a/README.en.md +++ b/README.en.md @@ -31,8 +31,9 @@ ## Changelog -**v1.1.4** +**v1.1.4 2024/4/10)** +- Added `easy ipadapterStyleComposition` - Added the right-click menu to view checkpoints and lora information in all Loaders - Fixed `easy preSamplingNoiseIn`、`easy latentNoisy`、`east Unsampler` compatible with ComfyUI Revision>=2098 [0542088e] or later diff --git a/README.md b/README.md index 572777b..41a4a91 100644 --- a/README.md +++ b/README.md @@ -35,8 +35,9 @@ ## 更新日志 -**v1.1.4 (2024/4/7)** +**v1.1.4 2024/4/10)** +- 增加 `easy ipadapterStyleComposition` - 增加 在Loaders上右键菜单可查看 checkpoints、lora 信息 - 修复 `easy preSamplingNoiseIn`、`easy latentNoisy`、`east Unsampler` 以兼容ComfyUI Revision>=2098 [0542088e] 以上版本 diff --git a/py/easyNodes.py b/py/easyNodes.py index 6b6709a..3e359cd 100644 --- a/py/easyNodes.py +++ b/py/easyNodes.py @@ -22,13 +22,13 @@ from .wildcards import process_with_loras, get_wildcard_list, process from .adv_encode import advanced_encode from .layer_diffuse.func import LayerDiffuse, LayerMethod -from .libs.utils import find_wildcards_seed, is_linked_styles_selector, easySave, get_local_filepath, add_folder_path_and_extensions +from .libs.utils import find_wildcards_seed, is_linked_styles_selector, easySave, get_local_filepath, add_folder_path_and_extensions, AlwaysEqualProxy from .libs.loader import easyLoader from .libs.sampler import easySampler from .libs.xyplot import easyXYPlot from .libs.controlnet import easyControlnet from .libs.conditioning import prompt_to_cond, set_cond -from .libs.cache import cache, update_cache +from .libs import cache as backend_cache from .libs.easing import EasingBase sampler = easySampler() @@ -280,20 +280,23 @@ class promptLine: @classmethod def INPUT_TYPES(s): - return {"required": {"prompt": ("STRING", {"multiline": True, "default": "text"}), - "start_index": ("INT", {"default": 0, "min": 0, "max": 9999}), - "max_rows": ("INT", {"default": 1000, "min": 1, "max": 9999}), - } - } + return {"required": { + "prompt": ("STRING", {"multiline": True, "default": "text"}), + "start_index": ("INT", {"default": 0, "min": 0, "max": 9999}), + "max_rows": ("INT", {"default": 1000, "min": 1, "max": 9999}), + }, + "hidden":{ + "workflow_prompt": "PROMPT", "my_unique_id": "UNIQUE_ID" + } + } - RETURN_TYPES = ("STRING",) - RETURN_NAMES = ("STRING",) - OUTPUT_IS_LIST = (True,) + RETURN_TYPES = ("STRING", AlwaysEqualProxy('*')) + RETURN_NAMES = ("STRING", "COMBO") + OUTPUT_IS_LIST = (True, True) FUNCTION = "generate_strings" CATEGORY = "EasyUse/Prompt" - def generate_strings(self, prompt, start_index, max_rows): - + def generate_strings(self, prompt, start_index, max_rows, workflow_prompt=None, my_unique_id=None): lines = prompt.split('\n') start_index = max(0, min(start_index, len(lines) - 1)) @@ -302,7 +305,8 @@ class promptLine: rows = lines[start_index:end_index] - return (rows,) + + return (rows, rows) class promptConcat: @classmethod @@ -2041,8 +2045,7 @@ class ipadapter: 'FACEID PLUS V2', 'FACEID PORTRAIT (style transfer)' ] - self.weight_types = ["linear", "ease in", "ease out", 'ease in-out', 'reverse in-out', 'weak input', 'weak output', - 'weak middle', 'strong middle', 'style transfer (SDXL)'] + self.weight_types = ["linear", "ease in", "ease out", 'ease in-out', 'reverse in-out', 'weak input', 'weak output', 'weak middle', 'strong middle', 'style transfer', 'composition'] self.presets = self.normal_presets + self.faceid_presets @@ -2061,8 +2064,8 @@ class ipadapter: clipvision_name = clipvision_files[0] if len(clipvision_files)>0 else None clipvision_file = folder_paths.get_full_path("clip_vision", clipvision_name) if clipvision_name else None - if clipvision_name is not None: - log_node_info(node_name, f"Using {clipvision_name}") + # if clipvision_name is not None: + # log_node_info(node_name, f"Using {clipvision_name}") return clipvision_file, clipvision_name @@ -2145,8 +2148,8 @@ class ipadapter: ipadapter_files = [e for e in ipadapter_list if re.search(pattern, e, re.IGNORECASE)] ipadapter_name = ipadapter_files[0] if len(ipadapter_files)>0 else None ipadapter_file = folder_paths.get_full_path("ipadapter", ipadapter_name) if ipadapter_name else None - if ipadapter_name is not None: - log_node_info(node_name, f"Using {ipadapter_name}") + # if ipadapter_name is not None: + # log_node_info(node_name, f"Using {ipadapter_name}") return ipadapter_file, ipadapter_name, is_insightface, lora_pattern @@ -2196,12 +2199,14 @@ class ipadapter: raise Exception("ClipVision model not found.") if clipvision_file == pipeline['clipvision']['file']: clip_vision = pipeline['clipvision']['model'] - elif cache_mode in ["all", "clip_vision only"] and clipvision_name in cache: - log_node_info("easy ipadapterApply", f"Using ClipModel {clipvision_name} Cached") - clip_vision = cache[clipvision_name][1] + elif cache_mode in ["all", "clip_vision only"] and clipvision_name in backend_cache.cache: + log_node_info("easy ipadapterApply", f"Using ClipVisonModel {clipvision_name} Cached") + _, clip_vision = backend_cache.cache[clipvision_name][1] else: clip_vision = load_clip_vision(clipvision_file) - update_cache(clipvision_name, (False, clip_vision)) + log_node_info("easy ipadapterApply", f"Using ClipVisonModel {clipvision_name}") + if cache_mode in ["all", "clip_vision only"]: + backend_cache.update_cache(clipvision_name, 'clip_vision', (False, clip_vision)) pipeline['clipvision']['file'] = clipvision_file pipeline['clipvision']['model'] = clip_vision @@ -2210,9 +2215,21 @@ class ipadapter: ipadapter_file, ipadapter_name, is_insightface, lora_pattern = self.get_ipadapter_file(preset, is_sdxl, node_name) model_type = 'sdxl' if is_sdxl else 'sd15' if ipadapter_file is None: - ipadapter_file = get_local_filepath(IPADAPTER_MODELS[preset][model_type]["model_url"], IPADAPTER_DIR) - ipadapter = self.ipadapter_model_loader(ipadapter_file) - pipeline['ipadapter']['file'] = ipadapter_file + model_url = IPADAPTER_MODELS[preset][model_type]["model_url"] + ipadapter_file = get_local_filepath(model_url, IPADAPTER_DIR) + ipadapter_name = os.path.basename(model_url) + if ipadapter_file == pipeline['ipadapter']['file']: + ipadapter = pipeline['ipadapter']['model'] + elif cache_mode in ["all", "ipadapter only"] and ipadapter_name in backend_cache.cache: + log_node_info("easy ipadapterApply", f"Using IpAdapterModel {ipadapter_name} Cached") + _, ipadapter = backend_cache.cache[ipadapter_name][1] + else: + ipadapter = self.ipadapter_model_loader(ipadapter_file) + pipeline['ipadapter']['file'] = ipadapter_file + log_node_info("easy ipadapterApply", f"Using IpAdapterModel {ipadapter_name}") + if cache_mode in ["all", "ipadapter only"]: + backend_cache.update_cache(ipadapter_name, 'ipadapter', (False, ipadapter)) + pipeline['ipadapter']['model'] = ipadapter # 3. Load the lora model if needed @@ -2225,12 +2242,13 @@ class ipadapter: icache_key = 'insightface-' + provider if provider == pipeline['insightface']['provider']: insightface = pipeline['insightface']['model'] - elif icache_key in cache: + elif cache_mode in ["all", "insightface only"] and icache_key in backend_cache.cache: log_node_info("easy ipadapterApply", f"Using InsightFaceModel {icache_key} Cached") - insightface = cache[icache_key][1] + _, insightface = backend_cache.cache[icache_key][1] else: insightface = insightface_loader(provider) - update_cache(icache_key, (False, insightface)) + if cache_mode in ["all", "insightface only"]: + backend_cache.update_cache(icache_key, 'insightface',(False, insightface)) pipeline['insightface']['provider'] = provider pipeline['insightface']['model'] = insightface @@ -2255,7 +2273,7 @@ class ipadapterApply(ipadapter): "weight_faceidv2": ("FLOAT", { "default": 1.0, "min": -1, "max": 5.0, "step": 0.05 }), "start_at": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}), "end_at": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001}), - "cache_mode": (["insightface only", "clip_vision only", "all", "none"], {"default": "insightface only"},), + "cache_mode": (["insightface only", "clip_vision only", "ipadapter only", "all", "none"], {"default": "insightface only"},), "use_tiled": ("BOOLEAN", {"default": False},), }, @@ -2316,7 +2334,7 @@ class ipadapterApplyAdvanced(ipadapter): "start_at": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}), "end_at": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001}), "embeds_scaling": (['V only', 'K+V', 'K+V w/ C penalty', 'K+mean(V) w/ C penalty'],), - "cache_mode": (["insightface only", "clip_vision only", "all", "none"], {"default": "insightface only"},), + "cache_mode": (["insightface only", "clip_vision only","ipadapter only", "all", "none"], {"default": "insightface only"},), "use_tiled": ("BOOLEAN", {"default": False},), "use_batch": ("BOOLEAN", {"default": False},), "sharpening": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.05}), @@ -2335,9 +2353,10 @@ class ipadapterApplyAdvanced(ipadapter): CATEGORY = "EasyUse/Adapter" FUNCTION = "apply" - def apply(self, model, image, preset, lora_strength, provider, weight, weight_faceidv2, weight_type, combine_embeds, start_at, end_at, embeds_scaling, cache_mode, use_tiled, use_batch, sharpening, image_negative=None, clip_vision=None, attn_mask=None, optional_ipadapter=None): + def apply(self, model, image, preset, lora_strength, provider, weight, weight_faceidv2, weight_type, combine_embeds, start_at, end_at, embeds_scaling, cache_mode, use_tiled, use_batch, sharpening, weight_style=1.0, weight_composition=1.0, image_style=None, image_composition=None, expand_style=False, image_negative=None, clip_vision=None, attn_mask=None, optional_ipadapter=None): tiles, masks = image, [None] model, ipadapter = self.load_model(model, preset, lora_strength, provider, clip_vision=clip_vision, optional_ipadapter=optional_ipadapter, cache_mode=cache_mode) + if use_tiled: if use_batch: if "IPAdapterTiledBatch" not in ALL_NODE_CLASS_MAPPINGS: @@ -2347,7 +2366,7 @@ class ipadapterApplyAdvanced(ipadapter): if "IPAdapterTiled" not in ALL_NODE_CLASS_MAPPINGS: self.error() cls = ALL_NODE_CLASS_MAPPINGS["IPAdapterTiled"] - model, tiles, masks = cls().apply_tiled(model, ipadapter, image, weight, weight_type, start_at, end_at, sharpening=sharpening, combine_embeds=combine_embeds, image_negative=image_negative, attn_mask=attn_mask, clip_vision=clip_vision, embeds_scaling=embeds_scaling) + model, tiles, masks = cls().apply_tiled(model, ipadapter, weight, weight_type, start_at, end_at, sharpening=sharpening, combine_embeds=combine_embeds, image_negative=image_negative, attn_mask=attn_mask, clip_vision=clip_vision, embeds_scaling=embeds_scaling) else: if use_batch: if "IPAdapterBatch" not in ALL_NODE_CLASS_MAPPINGS: @@ -2357,9 +2376,61 @@ class ipadapterApplyAdvanced(ipadapter): if "IPAdapterAdvanced" not in ALL_NODE_CLASS_MAPPINGS: self.error() cls = ALL_NODE_CLASS_MAPPINGS["IPAdapterAdvanced"] - model, = cls().apply_ipadapter(model, ipadapter, weight=weight, weight_type=weight_type, start_at=start_at, end_at=end_at, combine_embeds=combine_embeds, weight_faceidv2=weight_faceidv2, image=image, image_negative=image_negative, clip_vision=clip_vision, attn_mask=attn_mask, insightface=None, embeds_scaling=embeds_scaling) + model, = cls().apply_ipadapter(model, ipadapter, weight=weight, weight_type=weight_type, start_at=start_at, end_at=end_at, combine_embeds=combine_embeds, weight_faceidv2=weight_faceidv2, image=image, image_negative=image_negative, weight_style=1.0, weight_composition=1.0, image_style=image_style, image_composition=image_composition, expand_style=expand_style, clip_vision=clip_vision, attn_mask=attn_mask, insightface=None, embeds_scaling=embeds_scaling) return (model, tiles, masks, ipadapter) + +class ipadapterStyleComposition(ipadapter): + def __init__(self): + super().__init__() + pass + + @classmethod + def INPUT_TYPES(cls): + ipa_cls = cls() + normal_presets = ipa_cls.normal_presets + weight_types = ipa_cls.weight_types + return { + "required": { + "model": ("MODEL",), + "image_style": ("IMAGE",), + "preset": (normal_presets,), + "weight_style": ("FLOAT", {"default": 1.0, "min": -1, "max": 5, "step": 0.05}), + "weight_composition": ("FLOAT", {"default": 1.0, "min": -1, "max": 5, "step": 0.05}), + "expand_style": ("BOOLEAN", {"default": False}), + "combine_embeds": (["concat", "add", "subtract", "average", "norm average"], {"default": "average"}), + "start_at": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}), + "end_at": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001}), + "embeds_scaling": (['V only', 'K+V', 'K+V w/ C penalty', 'K+mean(V) w/ C penalty'],), + "cache_mode": (["insightface only", "clip_vision only", "ipadapter only", "all", "none"], + {"default": "insightface only"},), + }, + "optional": { + "image_composition": ("IMAGE",), + "image_negative": ("IMAGE",), + "attn_mask": ("MASK",), + "clip_vision": ("CLIP_VISION",), + "optional_ipadapter": ("IPADAPTER",), + } + } + + CATEGORY = "EasyUse/Adapter" + + RETURN_TYPES = ("MODEL", "IPADAPTER",) + RETURN_NAMES = ("model", "ipadapter",) + CATEGORY = "EasyUse/Adapter" + FUNCTION = "apply" + + def apply(self, model, preset, weight_style, weight_composition, expand_style, combine_embeds, start_at, end_at, embeds_scaling, cache_mode, image_style=None , image_composition=None, image_negative=None, clip_vision=None, attn_mask=None, optional_ipadapter=None): + model, ipadapter = self.load_model(model, preset, 0, 'CPU', clip_vision=None, optional_ipadapter=optional_ipadapter, cache_mode=cache_mode) + + if "IPAdapterAdvanced" not in ALL_NODE_CLASS_MAPPINGS: + self.error() + cls = ALL_NODE_CLASS_MAPPINGS["IPAdapterAdvanced"] + + model, = cls().apply_ipadapter(model, ipadapter, start_at=start_at, end_at=end_at, weight_style=weight_style, weight_composition=weight_composition, weight_type='linear', combine_embeds=combine_embeds, weight_faceidv2=weight_composition, image_style=image_style, image_composition=image_composition, image_negative=image_negative, expand_style=expand_style, clip_vision=clip_vision, attn_mask=attn_mask, insightface=None, embeds_scaling=embeds_scaling) + return (model, ipadapter) + class ipadapterApplyEncoder(ipadapter): def __init__(self): super().__init__() @@ -2501,23 +2572,23 @@ class instantID: model = pipe['model'] # Load InstantID cache_key = 'instantID' - if cache_key in cache: + if cache_key in backend_cache.cache: log_node_info("easy instantIDApply","Using InstantIDModel Cached") - instantid_model = cache[cache_key][1] + _, instantid_model = backend_cache.cache[cache_key][1] if "InstantIDModelLoader" in ALL_NODE_CLASS_MAPPINGS: load_instant_cls = ALL_NODE_CLASS_MAPPINGS["InstantIDModelLoader"] instantid_model, = load_instant_cls().load_model(instantid_file) - update_cache(cache_key, (False, instantid_model)) + backend_cache.update_cache(cache_key, 'instantid', (False, instantid_model)) else: self.error() icache_key = 'insightface-' + insightface - if icache_key in cache: + if icache_key in backend_cache.cache: log_node_info("easy instantIDApply", f"Using InsightFaceModel {insightface} Cached") - insightface_model = cache[icache_key][1] + _, insightface_model = backend_cache.cache[icache_key][1] elif "InstantIDFaceAnalysis" in ALL_NODE_CLASS_MAPPINGS: load_insightface_cls = ALL_NODE_CLASS_MAPPINGS["InstantIDFaceAnalysis"] insightface_model, = load_insightface_cls().load_insight_face(insightface) - update_cache(icache_key, (False, insightface_model)) + backend_cache.update_cache(icache_key, 'insightface', (False, insightface_model)) else: self.error() @@ -6417,6 +6488,7 @@ NODE_CLASS_MAPPINGS = { "easy ipadapterApplyADV": ipadapterApplyAdvanced, "easy ipadapterApplyEncoder": ipadapterApplyEncoder, "easy ipadapterApplyEmbeds": ipadapterApplyEmbeds, + "easy ipadapterStyleComposition": ipadapterStyleComposition, "easy instantIDApply": instantIDApply, "easy instantIDApplyADV": instantIDApplyAdvanced, # Inpaint 内补 @@ -6511,6 +6583,7 @@ NODE_DISPLAY_NAME_MAPPINGS = { # Adapter 适配器 "easy ipadapterApply": "Easy Apply IPAdapter", "easy ipadapterApplyADV": "Easy Apply IPAdapter (Advanced)", + "easy ipadapterStyleComposition": "Easy Apply IPAdapter (StyleComposition)", "easy ipadapterApplyEncoder": "Easy Apply IPAdapter (Encoder)", "easy ipadapterApplyEmbeds": "Easy Apply IPAdapter (Embeds)", "easy instantIDApply": "Easy Apply InstantID", diff --git a/py/libs/cache.py b/py/libs/cache.py index 51b99e4..271ec3e 100644 --- a/py/libs/cache.py +++ b/py/libs/cache.py @@ -1,12 +1,86 @@ -cache = {} +import itertools +from typing import Optional + +class TaggedCache: + def __init__(self, tag_settings: Optional[dict]=None): + self._tag_settings = tag_settings or {} # tag cache size + self._data = {} + + def __getitem__(self, key): + for tag_data in self._data.values(): + if key in tag_data: + return tag_data[key] + raise KeyError(f'Key `{key}` does not exist') + + def __setitem__(self, key, value: tuple): + # value: (tag: str, (islist: bool, data: *)) + + # if key already exists, pop old value + for tag_data in self._data.values(): + if key in tag_data: + tag_data.pop(key, None) + break + + tag = value[0] + if tag not in self._data: + + try: + from cachetools import LRUCache + + default_size = 20 + if 'ckpt' in tag: + default_size = 5 + elif tag in ['latent', 'image']: + default_size = 100 + + self._data[tag] = LRUCache(maxsize=self._tag_settings.get(tag, default_size)) + + except (ImportError, ModuleNotFoundError): + # TODO: implement a simple lru dict + self._data[tag] = {} + self._data[tag][key] = value + + def __delitem__(self, key): + for tag_data in self._data.values(): + if key in tag_data: + del tag_data[key] + return + raise KeyError(f'Key `{key}` does not exist') + + def __contains__(self, key): + return any(key in tag_data for tag_data in self._data.values()) + + def items(self): + yield from itertools.chain(*map(lambda x :x.items(), self._data.values())) + + def get(self, key, default=None): + """D.get(k[,d]) -> D[k] if k in D, else d. d defaults to None.""" + for tag_data in self._data.values(): + if key in tag_data: + return tag_data[key] + return default + + def clear(self): + # clear all cache + self._data = {} + +cache_settings = {} +cache = TaggedCache(cache_settings) cache_count = {} - -def update_cache(k, v): - cache[k] = v +def update_cache(k, tag, v): + cache[k] = (tag, v) cnt = cache_count.get(k) if cnt is None: cnt = 0 cache_count[k] = cnt else: - cache_count[k] += 1 \ No newline at end of file + cache_count[k] += 1 +def remove_cache(key): + global cache + if key == '*': + cache = TaggedCache(cache_settings) + elif key in cache: + del cache[key] + else: + print(f"invalid {key}") \ No newline at end of file diff --git a/py/libs/utils.py b/py/libs/utils.py index 6e8cd3f..112ed02 100644 --- a/py/libs/utils.py +++ b/py/libs/utils.py @@ -1,3 +1,10 @@ +class AlwaysEqualProxy(str): + def __eq__(self, _): + return True + + def __ne__(self, _): + return False + comfy_ui_revision = None def get_comfyui_revision(): try: diff --git a/py/logic.py b/py/logic.py index 0812878..c626c07 100644 --- a/py/logic.py +++ b/py/logic.py @@ -1,6 +1,7 @@ from typing import Iterator, List, Tuple, Dict, Any, Union, Optional from _decimal import Context, getcontext from decimal import Decimal +from .libs.utils import AlwaysEqualProxy import torch import numpy as np import json @@ -287,12 +288,6 @@ COMPARE_FUNCTIONS = { "a <= b": lambda a, b: a <= b, "a >= b": lambda a, b: a >= b, } -class AlwaysEqualProxy(str): - def __eq__(self, _): - return True - - def __ne__(self, _): - return False # 比较 class Compare: @@ -517,6 +512,46 @@ class cleanGPUUsed: return () +from .libs.cache import remove_cache +class cleanCacheKey: + @classmethod + def INPUT_TYPES(s): + return {"required": { + "anything": (AlwaysEqualProxy("*"), {}), + "cache_key": ("STRING", {"default": "*"}), + }, "optional": {}, + "hidden": {"unique_id": "UNIQUE_ID", "extra_pnginfo": "EXTRA_PNGINFO",} + } + + RETURN_TYPES = () + RETURN_NAMES = () + OUTPUT_NODE = True + FUNCTION = "empty_cache" + CATEGORY = "EasyUse/Logic" + + def empty_cache(self, anything, cache_name, unique_id=None, extra_pnginfo=None): + remove_cache(cache_name) + return () + +class cleanCacheAll: + @classmethod + def INPUT_TYPES(s): + return {"required": { + "anything": (AlwaysEqualProxy("*"), {}), + }, "optional": {}, + "hidden": {"unique_id": "UNIQUE_ID", "extra_pnginfo": "EXTRA_PNGINFO",} + } + + RETURN_TYPES = () + RETURN_NAMES = () + OUTPUT_NODE = True + FUNCTION = "empty_cache" + CATEGORY = "EasyUse/Logic" + + def empty_cache(self, anything, unique_id=None, extra_pnginfo=None): + remove_cache('*') + return () + NODE_CLASS_MAPPINGS = { "easy string": String, "easy int": Int, @@ -532,7 +567,9 @@ NODE_CLASS_MAPPINGS = { "easy convertAnything": ConvertAnything, "easy showAnything": showAnything, "easy showTensorShape": showTensorShape, - "easy cleanGpuUsed": cleanGPUUsed + "easy cleanCacheKey": cleanCacheKey, + "easy cleanCacheAll": cleanCacheAll, + "easy cleanGpuUsed": cleanGPUUsed, } NODE_DISPLAY_NAME_MAPPINGS = { "easy string": "String", @@ -549,5 +586,7 @@ NODE_DISPLAY_NAME_MAPPINGS = { "easy convertAnything": "Convert Any", "easy showAnything": "Show Any", "easy showTensorShape": "Show Tensor Shape", + "easy cleanCacheKey": "Clean Cache Key", + "easy cleanCacheAll": "Clean Cache All", "easy cleanGpuUsed": "Clean GPU Used" } \ No newline at end of file diff --git a/web/js/common/i18n.js b/web/js/common/i18n.js index b1defc6..004828d 100644 --- a/web/js/common/i18n.js +++ b/web/js/common/i18n.js @@ -24,6 +24,7 @@ const zhCN = { "Saving Preview...": "正在保存预览图...", "Saving Succeed":"保存成功", "Saving Failed":"保存失败", + "No COMBO link": "沒有找到COMBO连接", // GroupMap "Groups Map (EasyUse)": "管理组 (EasyUse)", "Always": "启用中", diff --git a/web/js/easy/easyDynamicWidgets.js b/web/js/easy/easyDynamicWidgets.js index 5a77412..93305c0 100644 --- a/web/js/easy/easyDynamicWidgets.js +++ b/web/js/easy/easyDynamicWidgets.js @@ -1,6 +1,8 @@ import { app } from "/scripts/app.js"; import { api } from "/scripts/api.js"; import { ComfyWidgets } from "/scripts/widgets.js"; +import { toast} from "../common/toast.js"; +import { $t } from '../common/i18n.js'; let origProps = {}; @@ -1028,6 +1030,36 @@ app.registerExtension({ } } + + if (nodeData.name == 'easy promptLine') { + const onAdded = nodeType.prototype.onAdded; + nodeType.prototype.onAdded = async function () { + onAdded ? onAdded.apply(this, []) : undefined; + let prompt_widget = this.widgets.find(w => w.name == "prompt") + const button = this.addWidget("button", "get values from COMBO link", '', () => { + const output_link = this.outputs[1]?.links?.length>0 ? this.outputs[1]['links'][0] : null + const all_nodes = app.graph._nodes + const node = all_nodes.find(cate=> cate.inputs?.find(input=> input.link == output_link)) + if(!output_link || !node){ + toast.error($t('No COMBO link'), 3000) + return + } + else{ + const input = node.inputs.find(input=> input.link == output_link) + const widget_name = input.widget.name + const widgets = node.widgets + const widget = widgets.find(cate=> cate.name == widget_name) + let values = widget?.options.values || null + if(values){ + values = values.join('\n') + prompt_widget.value = values + } + } + }, { + serialize: false + }) + } + } } }); diff --git a/web/js/easy/easyExtraMenu.js b/web/js/easy/easyExtraMenu.js index 3d1f2ad..2a4f2a3 100644 --- a/web/js/easy/easyExtraMenu.js +++ b/web/js/easy/easyExtraMenu.js @@ -6,7 +6,7 @@ const loaders = ['easy fullLoader', 'easy a1111Loader', 'easy comfyLoader'] const preSampling = ['easy preSampling', 'easy preSamplingAdvanced', 'easy preSamplingDynamicCFG', 'easy preSamplingNoiseIn', 'easy preSamplingLayerDiffusion', 'easy fullkSampler'] const kSampler = ['easy kSampler', 'easy kSamplerTiled', 'easy kSamplerInpainting', 'easy kSamplerDownscaleUnet', 'easy kSamplerLayerDiffusion'] const controlnet = ['easy controlnetLoader', 'easy controlnetLoaderADV', 'easy instantIDApply', 'easy instantIDApplyADV'] -const ipadapter = ['easy ipadapterApply', 'easy ipadapterApplyADV'] +const ipadapter = ['easy ipadapterApply', 'easy ipadapterApplyADV', 'easy ipadapterStyleComposition'] const positive_prompt = ['easy positive', 'easy wildcards'] const widgetMapping = { "positive_prompt":{ @@ -90,6 +90,7 @@ const inputMapping = { "ipadapter":{ "model":"model", "image":"image", + "image_style": "image", "attn_mask":"attn_mask", "optional_ipadapter":"optional_ipadapter" } diff --git a/web/js/easy/easySuggestion.js b/web/js/easy/easySuggestion.js index a158e2b..0672c26 100644 --- a/web/js/easy/easySuggestion.js +++ b/web/js/easy/easySuggestion.js @@ -8,7 +8,7 @@ const preSamplingNodes = ["easy preSampling", "easy preSamplingAdvanced", "easy const kSampler = ["easy kSampler", "easy kSamplerTiled","easy kSamplerInpainting", "easy kSamplerDownscaleUnet", "easy kSamplerSDTurbo"] const controlNetNodes = ["easy controlnetLoader", "easy controlnetLoaderADV"] const instantIDNodes = ["easy instantIDApply", "easy instantIDApplyADV"] -const ipadapterNodes = ["easy ipadapterApply", "easy ipadapterApplyADV"] +const ipadapterNodes = ["easy ipadapterApply", "easy ipadapterApplyADV" , "easy ipadapterStyleComposition"] const pipeNodes = ['easy pipeIn','easy pipeOut', 'easy pipeEdit'] const xyNodes = ['easy XYPlot', 'easy XYPlotAdvanced'] const extraNodes = ['easy setNode']