Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ecc972f7b4 | ||
|
|
c079652878 | ||
|
|
b333bf05c6 | ||
|
|
f52a8fd40b | ||
|
|
f320647d78 | ||
|
|
631acfe44a | ||
|
|
7269f02f8a | ||
|
|
fdc761ebfa | ||
|
|
689d988130 | ||
|
|
e29fd5ed24 | ||
|
|
35f19b75fa | ||
|
|
96125a65a0 | ||
|
|
70ca7cc35f | ||
|
|
5858fb0606 | ||
|
|
149fab2105 |
+44
-19
@@ -31,7 +31,24 @@
|
||||
|
||||
## Changelog
|
||||
|
||||
**v1.1.2 (2024/3/25)**
|
||||
**v1.1.4 (2024/4/10)**
|
||||
|
||||
- Added `easy preSamplingCustom` - Custom-PreSampling, can be supported cosXL-edit
|
||||
- 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
|
||||
|
||||
|
||||
**v1.1.3 (2024/4/4)**
|
||||
|
||||
- `easy ipadapterApply` Added **COMPOSITION** preset
|
||||
- Supported [ResAdapter](https://huggingface.co/jiaxiangc/res-adapter) when load ResAdapter lora
|
||||
- Added `easy promptLine`
|
||||
- Added `easy promptReplace`
|
||||
- Added `easy promptConcat`
|
||||
- `easy wildcards` Added **multiline_mode**
|
||||
|
||||
**v1.1.2 (39c5ccf)**
|
||||
|
||||
- Optimized some of the recommended nodes for slots related to EasyUse
|
||||
- Added **Enable ContextMenu Auto Nest Subdirectories** The setting item is enabled by default, and it can be classified into subdirectories, checkpoints and loras previews
|
||||
@@ -43,12 +60,9 @@
|
||||
- Added `easy ipadapterApplyEmbeds`
|
||||
- Added `easy preMaskDetailerFix`
|
||||
- Fixed `easy stylesSelector` is change the prompt when not select the style
|
||||
|
||||
(4c25580)
|
||||
|
||||
- `easy kSamplerInpainting` add *additional* widget,you can choose 'Differential Diffusion' or 'Only InpaintModelConditioning'
|
||||
- Fixed `easy pipeEdit` error when add lora to prompt
|
||||
- Fixed layerDiffuse xyplot bug
|
||||
- `easy kSamplerInpainting` add *additional* widget,you can choose 'Differential Diffusion' or 'Only InpaintModelConditioning'
|
||||
|
||||
**v1.1.1 (2024/3/16)**
|
||||
|
||||
@@ -257,20 +271,21 @@
|
||||
|
||||
Disclaimer: Opened source was not easy. I have a lot of respect for the contributions of these original authors. I just did some integration and optimization.
|
||||
|
||||
| Nodes Name(Search Name) | Related libraries | Library-related node |
|
||||
|:---------------------------|:----------------------------------------------------------------------------|:-------------------------|
|
||||
| easy setNode | [ComfyUI-extensions](https://github.com/diffus3/ComfyUI-extensions) | diffus3.SetNode |
|
||||
| easy getNode | [ComfyUI-extensions](https://github.com/diffus3/ComfyUI-extensions) | diffus3.GetNode |
|
||||
| easy bookmark | [rgthree-comfy](https://github.com/rgthree/rgthree-comfy) | Bookmark 🔖 |
|
||||
| easy portraitMarker | [comfyui-portrait-master](https://github.com/florestefano1975/comfyui-portrait-master) | Portrait Master |
|
||||
| easy LLLiteLoader | [ControlNet-LLLite-ComfyUI](https://github.com/kohya-ss/ControlNet-LLLite-ComfyUI) | LLLiteLoader |
|
||||
| easy globalSeed | [ComfyUI-Inspire-Pack](https://github.com/ltdrdata/ComfyUI-Inspire-Pack) | Global Seed (Inspire) |
|
||||
| easy preSamplingDynamicCFG | [sd-dynamic-thresholding](https://github.com/mcmonkeyprojects/sd-dynamic-thresholding) | DynamicThresholdingFull |
|
||||
| dynamicThresholdingFull | [sd-dynamic-thresholding](https://github.com/mcmonkeyprojects/sd-dynamic-thresholding) | DynamicThresholdingFull |
|
||||
| easy imageInsetCrop | [rgthree-comfy](https://github.com/rgthree/rgthree-comfy) | ImageInsetCrop |
|
||||
| easy poseEditor | [ComfyUI_Custom_Nodes_AlekPet](https://github.com/AlekPet/ComfyUI_Custom_Nodes_AlekPet) | poseNode |
|
||||
| Nodes Name(Search Name) | Related libraries | Library-related node |
|
||||
|:-------------------------------|:----------------------------------------------------------------------------|:-------------------------|
|
||||
| easy setNode | [ComfyUI-extensions](https://github.com/diffus3/ComfyUI-extensions) | diffus3.SetNode |
|
||||
| easy getNode | [ComfyUI-extensions](https://github.com/diffus3/ComfyUI-extensions) | diffus3.GetNode |
|
||||
| easy bookmark | [rgthree-comfy](https://github.com/rgthree/rgthree-comfy) | Bookmark 🔖 |
|
||||
| easy portraitMarker | [comfyui-portrait-master](https://github.com/florestefano1975/comfyui-portrait-master) | Portrait Master |
|
||||
| easy LLLiteLoader | [ControlNet-LLLite-ComfyUI](https://github.com/kohya-ss/ControlNet-LLLite-ComfyUI) | LLLiteLoader |
|
||||
| easy globalSeed | [ComfyUI-Inspire-Pack](https://github.com/ltdrdata/ComfyUI-Inspire-Pack) | Global Seed (Inspire) |
|
||||
| easy preSamplingDynamicCFG | [sd-dynamic-thresholding](https://github.com/mcmonkeyprojects/sd-dynamic-thresholding) | DynamicThresholdingFull |
|
||||
| dynamicThresholdingFull | [sd-dynamic-thresholding](https://github.com/mcmonkeyprojects/sd-dynamic-thresholding) | DynamicThresholdingFull |
|
||||
| easy imageInsetCrop | [rgthree-comfy](https://github.com/rgthree/rgthree-comfy) | ImageInsetCrop |
|
||||
| easy poseEditor | [ComfyUI_Custom_Nodes_AlekPet](https://github.com/AlekPet/ComfyUI_Custom_Nodes_AlekPet) | poseNode |
|
||||
| easy preSamplingLayerDiffusion | [ComfyUI-layerdiffusion](https://github.com/huchenlei/ComfyUI-layerdiffusion) | LayeredDiffusionApply... |
|
||||
| easy dynamiCrafterLoader | [ComfyUI-layerdiffusion](https://github.com/ExponentialML/ComfyUI_Native_DynamiCrafter) | Apply Dynamicrafter |
|
||||
| easy dynamiCrafterLoader | [ComfyUI-layerdiffusion](https://github.com/ExponentialML/ComfyUI_Native_DynamiCrafter) | Apply Dynamicrafter |
|
||||
| easy imageChooser | [cg-image-picker](https://github.com/chrisgoringe/cg-image-picker) | Preview Chooser |
|
||||
|
||||
## Workflow Examples
|
||||
|
||||
@@ -312,4 +327,14 @@ Disclaimer: Opened source was not easy. I have a lot of respect for the contribu
|
||||
|
||||
[ComfyUI-Impact-Pack](https://github.com/ltdrdata/ComfyUI-Impact-Pack) - General modpack 1
|
||||
|
||||
[ComfyUI-Inspire-Pack](https://github.com/ltdrdata/ComfyUI-Inspire-Pack) - General Modpack 2
|
||||
[ComfyUI-Inspire-Pack](https://github.com/ltdrdata/ComfyUI-Inspire-Pack) - General Modpack 2
|
||||
|
||||
[ComfyUI-ResAdapter](https://github.com/jiaxiangc/ComfyUI-ResAdapter) - Make model generation independent of training resolution
|
||||
|
||||
[ComfyUI_IPAdapter_plus](https://github.com/cubiq/ComfyUI_IPAdapter_plus) - Style migration
|
||||
|
||||
[ComfyUI_InstantID](https://github.com/cubiq/ComfyUI_InstantID) - Face migration
|
||||
|
||||
[ComfyUI-Custom-Scripts](https://github.com/pythongosssss/ComfyUI-Custom-Scripts) - pyssss🐍
|
||||
|
||||
[cg-image-picker](https://github.com/chrisgoringe/cg-image-picker) - Image Preview Chooser
|
||||
|
||||
@@ -35,7 +35,25 @@
|
||||
|
||||
## 更新日志
|
||||
|
||||
**v1.1.2 (2024/3/25)**
|
||||
**v1.1.4 (2024/4/13)**
|
||||
|
||||
- 增加 `easy preSamplingCustom` - 自定义预采样,可支持cosXL-edit
|
||||
- 增加 `easy ipadapterStyleComposition`
|
||||
- 增加 在Loaders上右键菜单可查看 checkpoints、lora 信息
|
||||
- 修复 `easy preSamplingNoiseIn`、`easy latentNoisy`、`east Unsampler` 以兼容ComfyUI Revision>=2098 [0542088e] 以上版本
|
||||
- 修复 FooocusInpaint修改ModelPatcher计算权重引发的问题,理应在生成model后重置ModelPatcher为默认值
|
||||
|
||||
**v1.1.3 (2024/4/4)**
|
||||
|
||||
- `easy ipadapterApply` 增加 **COMPOSITION** 预置项
|
||||
- 增加 对[ResAdapter](https://huggingface.co/jiaxiangc/res-adapter) lora模型 的加载支持
|
||||
- 增加 `easy promptLine`
|
||||
- 增加 `easy promptReplace`
|
||||
- 增加 `easy promptConcat`
|
||||
- `easy wildcards` 增加 **multiline_mode**属性
|
||||
- 增加 当节点需要下载模型时,若huggingface连接超时,会切换至镜像地址下载模型
|
||||
|
||||
**v1.1.2 (39c5ccf)**
|
||||
|
||||
- 改写 EasyUse 相关节点的部分插槽推荐节点
|
||||
- 增加 **启用上下文菜单自动嵌套子目录** 设置项,默认为启用状态,可分类子目录及checkpoints、loras预览图
|
||||
@@ -46,11 +64,8 @@
|
||||
- 增加 `easy ipadapterApplyEncoder`
|
||||
- 增加 `easy ipadapterApplyEmbeds`
|
||||
- 增加 `easy preMaskDetailerFix`
|
||||
- `easy kSamplerInpainting` 增加 **additional** 属性,可设置成 Differential Diffusion 或 Only InpaintModelConditioning
|
||||
- 修复 `easy stylesSelector` 当未选择样式时,原有提示词发生了变化
|
||||
|
||||
(4c25580)
|
||||
|
||||
- `easy kSamplerInpainting` 增加 *additional* 属性,可设置成 Differential Diffusion 或 Only InpaintModelConditioning
|
||||
- 修复 `easy pipeEdit` 提示词输入lora时报错
|
||||
- 修复 layerDiffuse xyplot相关bug
|
||||
|
||||
@@ -266,21 +281,22 @@
|
||||
|
||||
声明: 非常尊重这些原作者们的付出,开源不易,我仅仅只是做了一些整合与优化。
|
||||
|
||||
| 节点名 (搜索名) | 相关的库 | 库相关的节点 |
|
||||
|:-------------------------------|:----------------------------------------------------------------------------|:-----------------------|
|
||||
| easy setNode | [ComfyUI-extensions](https://github.com/diffus3/ComfyUI-extensions) | diffus3.SetNode |
|
||||
| easy getNode | [ComfyUI-extensions](https://github.com/diffus3/ComfyUI-extensions) | diffus3.GetNode |
|
||||
| easy bookmark | [rgthree-comfy](https://github.com/rgthree/rgthree-comfy) | Bookmark 🔖 |
|
||||
| easy portraitMarker | [comfyui-portrait-master](https://github.com/florestefano1975/comfyui-portrait-master) | Portrait Master |
|
||||
| easy LLLiteLoader | [ControlNet-LLLite-ComfyUI](https://github.com/kohya-ss/ControlNet-LLLite-ComfyUI) | LLLiteLoader |
|
||||
| easy globalSeed | [ComfyUI-Inspire-Pack](https://github.com/ltdrdata/ComfyUI-Inspire-Pack) | Global Seed (Inspire) |
|
||||
| 节点名 (搜索名) | 相关的库 | 库相关的节点 |
|
||||
|:-------------------------------|:----------------------------------------------------------------------------|:------------------------|
|
||||
| easy setNode | [ComfyUI-extensions](https://github.com/diffus3/ComfyUI-extensions) | diffus3.SetNode |
|
||||
| easy getNode | [ComfyUI-extensions](https://github.com/diffus3/ComfyUI-extensions) | diffus3.GetNode |
|
||||
| easy bookmark | [rgthree-comfy](https://github.com/rgthree/rgthree-comfy) | Bookmark 🔖 |
|
||||
| easy portraitMarker | [comfyui-portrait-master](https://github.com/florestefano1975/comfyui-portrait-master) | Portrait Master |
|
||||
| easy LLLiteLoader | [ControlNet-LLLite-ComfyUI](https://github.com/kohya-ss/ControlNet-LLLite-ComfyUI) | LLLiteLoader |
|
||||
| easy globalSeed | [ComfyUI-Inspire-Pack](https://github.com/ltdrdata/ComfyUI-Inspire-Pack) | Global Seed (Inspire) |
|
||||
| easy preSamplingDynamicCFG | [sd-dynamic-thresholding](https://github.com/mcmonkeyprojects/sd-dynamic-thresholding) | DynamicThresholdingFull |
|
||||
| dynamicThresholdingFull | [sd-dynamic-thresholding](https://github.com/mcmonkeyprojects/sd-dynamic-thresholding) | DynamicThresholdingFull |
|
||||
| easy imageInsetCrop | [rgthree-comfy](https://github.com/rgthree/rgthree-comfy) | ImageInsetCrop |
|
||||
| easy poseEditor | [ComfyUI_Custom_Nodes_AlekPet](https://github.com/AlekPet/ComfyUI_Custom_Nodes_AlekPet) | poseNode |
|
||||
| easy if | [ComfyUI-Logic](https://github.com/theUpsider/ComfyUI-Logic) | IfExecute |
|
||||
| easy preSamplingLayerDiffusion | [ComfyUI-layerdiffusion](https://github.com/huchenlei/ComfyUI-layerdiffusion) | LayeredDiffusionApply等 |
|
||||
| easy dynamiCrafterLoader | [ComfyUI-layerdiffusion](https://github.com/ExponentialML/ComfyUI_Native_DynamiCrafter) | Apply Dynamicrafter |
|
||||
| easy imageInsetCrop | [rgthree-comfy](https://github.com/rgthree/rgthree-comfy) | ImageInsetCrop |
|
||||
| easy poseEditor | [ComfyUI_Custom_Nodes_AlekPet](https://github.com/AlekPet/ComfyUI_Custom_Nodes_AlekPet) | poseNode |
|
||||
| easy if | [ComfyUI-Logic](https://github.com/theUpsider/ComfyUI-Logic) | IfExecute |
|
||||
| easy preSamplingLayerDiffusion | [ComfyUI-layerdiffusion](https://github.com/huchenlei/ComfyUI-layerdiffusion) | LayeredDiffusionApply等 |
|
||||
| easy dynamiCrafterLoader | [ComfyUI-layerdiffusion](https://github.com/ExponentialML/ComfyUI_Native_DynamiCrafter) | Apply Dynamicrafter |
|
||||
| easy imageChooser | [cg-image-picker](https://github.com/chrisgoringe/cg-image-picker) | Preview Chooser |
|
||||
|
||||
## 示例
|
||||
|
||||
@@ -329,3 +345,13 @@
|
||||
[ComfyUI-Inspire-Pack](https://github.com/ltdrdata/ComfyUI-Inspire-Pack) - 常规整合包2
|
||||
|
||||
[ComfyUI-Logic](https://github.com/theUpsider/ComfyUI-Logic) - ComfyUI逻辑运算
|
||||
|
||||
[ComfyUI-ResAdapter](https://github.com/jiaxiangc/ComfyUI-ResAdapter) - 让模型生成不受训练分辨率限制
|
||||
|
||||
[ComfyUI_IPAdapter_plus](https://github.com/cubiq/ComfyUI_IPAdapter_plus) - 风格迁移
|
||||
|
||||
[ComfyUI_InstantID](https://github.com/cubiq/ComfyUI_InstantID) - 人脸迁移
|
||||
|
||||
[ComfyUI-Custom-Scripts](https://github.com/pythongosssss/ComfyUI-Custom-Scripts) - pyssss 小蛇🐍脚本
|
||||
|
||||
[cg-image-picker](https://github.com/chrisgoringe/cg-image-picker) - 图片选择器
|
||||
|
||||
+1
-1
@@ -85,4 +85,4 @@ WEB_DIRECTORY = "./web"
|
||||
__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS', "WEB_DIRECTORY"]
|
||||
|
||||
|
||||
print('\033[34mComfy-Easy-Use (v1.1.2): \033[92mLoaded\033[0m')
|
||||
print('\033[34mComfy-Easy-Use (v1.1.4): \033[92mLoaded\033[0m')
|
||||
+4
-3
@@ -308,9 +308,10 @@ def advanced_encode(clip, text, token_normalization, weight_interpretation, w_ma
|
||||
|
||||
embeddings_final, pooled = prepareXL(embs_l, embs_g, pooled, clip_balance)
|
||||
|
||||
cond = [[embeddings_final,
|
||||
{"pooled_output": pooled, "width": width, "height": height, "crop_w": crop_w,
|
||||
"crop_h": crop_h, "target_width": target_width, "target_height": target_height}]]
|
||||
cond = [[embeddings_final, {"pooled_output": pooled}]]
|
||||
# cond = [[embeddings_final,
|
||||
# {"pooled_output": pooled, "width": width, "height": height, "crop_w": crop_w,
|
||||
# "crop_h": crop_h, "target_width": target_width, "target_height": target_height}]]
|
||||
else:
|
||||
embeddings_final, pooled = advanced_encode_from_tokens(tokenized['l'],
|
||||
token_normalization,
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import re
|
||||
import os
|
||||
import torch
|
||||
import hashlib
|
||||
import sys
|
||||
import json
|
||||
import shutil
|
||||
import folder_paths
|
||||
from folder_paths import get_directory_by_type
|
||||
from server import PromptServer
|
||||
from .config import RESOURCES_DIR, FOOOCUS_STYLES_DIR, FOOOCUS_STYLES_SAMPLES
|
||||
from .easyNodes import easyCache
|
||||
from .logic import ConvertAnything
|
||||
from .libs.model import easyModelManager
|
||||
from .libs.utils import getMetadata
|
||||
|
||||
try:
|
||||
import aiohttp
|
||||
@@ -18,6 +19,15 @@ except ImportError:
|
||||
print("pip install aiohttp")
|
||||
sys.exit()
|
||||
|
||||
@PromptServer.instance.routes.get("/easyuse/reboot")
|
||||
def reboot(self):
|
||||
try:
|
||||
sys.stdout.close_log()
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
return os.execv(sys.executable, [sys.executable] + sys.argv)
|
||||
|
||||
# parse csv
|
||||
@PromptServer.instance.routes.post("/easyuse/upload/csv")
|
||||
async def parse_csv(request):
|
||||
@@ -119,6 +129,8 @@ async def getModelsThumbnail(request):
|
||||
loras = folder_paths.get_filename_list("loras_thumb")
|
||||
checkpoints_full = []
|
||||
loras_full = []
|
||||
if len(checkpoints) + len(loras) >= 300:
|
||||
return web.Response(status=400)
|
||||
for index, i in enumerate(checkpoints):
|
||||
full_path = folder_paths.get_full_path('checkpoints_thumb', str(i))
|
||||
if full_path:
|
||||
@@ -129,5 +141,123 @@ async def getModelsThumbnail(request):
|
||||
loras_full.append(full_path)
|
||||
return web.json_response(checkpoints_full + loras_full)
|
||||
|
||||
@PromptServer.instance.routes.post("/easyuse/metadata/notes/{name}")
|
||||
async def save_notes(request):
|
||||
name = request.match_info["name"]
|
||||
pos = name.index("/")
|
||||
type = name[0:pos]
|
||||
name = name[pos+1:]
|
||||
|
||||
file_path = None
|
||||
if type == "embeddings" or type == "loras":
|
||||
name = name.lower()
|
||||
files = folder_paths.get_filename_list(type)
|
||||
for f in files:
|
||||
lower_f = f.lower()
|
||||
if lower_f == name:
|
||||
file_path = folder_paths.get_full_path(type, f)
|
||||
else:
|
||||
n = os.path.splitext(f)[0].lower()
|
||||
if n == name:
|
||||
file_path = folder_paths.get_full_path(type, f)
|
||||
|
||||
if file_path is not None:
|
||||
break
|
||||
else:
|
||||
file_path = folder_paths.get_full_path(
|
||||
type, name)
|
||||
if not file_path:
|
||||
return web.Response(status=404)
|
||||
|
||||
file_no_ext = os.path.splitext(file_path)[0]
|
||||
info_file = file_no_ext + ".txt"
|
||||
with open(info_file, "w") as f:
|
||||
f.write(await request.text())
|
||||
|
||||
return web.Response(status=200)
|
||||
|
||||
@PromptServer.instance.routes.get("/easyuse/metadata/{name}")
|
||||
async def load_metadata(request):
|
||||
name = request.match_info["name"]
|
||||
pos = name.index("/")
|
||||
type = name[0:pos]
|
||||
name = name[pos+1:]
|
||||
|
||||
file_path = None
|
||||
if type == "embeddings":
|
||||
name = name.lower()
|
||||
files = folder_paths.get_filename_list(type)
|
||||
for f in files:
|
||||
lower_f = f.lower()
|
||||
if lower_f == name:
|
||||
file_path = folder_paths.get_full_path(type, f)
|
||||
else:
|
||||
n = os.path.splitext(f)[0].lower()
|
||||
if n == name:
|
||||
file_path = folder_paths.get_full_path(type, f)
|
||||
|
||||
if file_path is not None:
|
||||
break
|
||||
else:
|
||||
file_path = folder_paths.get_full_path(type, name)
|
||||
if not file_path:
|
||||
return web.Response(status=404)
|
||||
|
||||
try:
|
||||
header = getMetadata(file_path)
|
||||
header_json = json.loads(header)
|
||||
meta = header_json["__metadata__"] if "__metadata__" in header_json else None
|
||||
except:
|
||||
meta = None
|
||||
|
||||
if meta is None:
|
||||
meta = {}
|
||||
|
||||
file_no_ext = os.path.splitext(file_path)[0]
|
||||
|
||||
info_file = file_no_ext + ".txt"
|
||||
if os.path.isfile(info_file):
|
||||
with open(info_file, "r") as f:
|
||||
meta["easyuse.notes"] = f.read()
|
||||
|
||||
hash_file = file_no_ext + ".sha256"
|
||||
if os.path.isfile(hash_file):
|
||||
with open(hash_file, "rt") as f:
|
||||
meta["easyuse.sha256"] = f.read()
|
||||
else:
|
||||
with open(file_path, "rb") as f:
|
||||
meta["easyuse.sha256"] = hashlib.sha256(f.read()).hexdigest()
|
||||
with open(hash_file, "wt") as f:
|
||||
f.write(meta["easyuse.sha256"])
|
||||
|
||||
return web.json_response(meta)
|
||||
|
||||
@PromptServer.instance.routes.post("/easyuse/save/{name}")
|
||||
async def save_preview(request):
|
||||
name = request.match_info["name"]
|
||||
pos = name.index("/")
|
||||
type = name[0:pos]
|
||||
name = name[pos+1:]
|
||||
|
||||
body = await request.json()
|
||||
|
||||
dir = get_directory_by_type(body.get("type", "output"))
|
||||
subfolder = body.get("subfolder", "")
|
||||
full_output_folder = os.path.join(dir, os.path.normpath(subfolder))
|
||||
|
||||
if os.path.commonpath((dir, os.path.abspath(full_output_folder))) != dir:
|
||||
return web.Response(status=400)
|
||||
|
||||
filepath = os.path.join(full_output_folder, body.get("filename", ""))
|
||||
image_path = folder_paths.get_full_path(type, name)
|
||||
image_path = os.path.splitext(
|
||||
image_path)[0] + os.path.splitext(filepath)[1]
|
||||
|
||||
shutil.copyfile(filepath, image_path)
|
||||
|
||||
return web.json_response({
|
||||
"image": type + "/" + os.path.basename(image_path)
|
||||
})
|
||||
|
||||
NODE_CLASS_MAPPINGS = {}
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {}
|
||||
@@ -231,6 +231,14 @@ IPADAPTER_MODELS = {
|
||||
"sdxl": {
|
||||
"model_url": "https://huggingface.co/h94/IP-Adapter-FaceID/resolve/main/ip-adapter-faceid-portrait_sdxl.bin",
|
||||
}
|
||||
},
|
||||
"COMPOSITION": {
|
||||
"sd15": {
|
||||
"model_url": "https://huggingface.co/ostris/ip-composition-adapter/resolve/main/ip_plus_composition_sd15.safetensors"
|
||||
},
|
||||
"sdxl": {
|
||||
"model_url": "https://huggingface.co/ostris/ip-composition-adapter/resolve/main/ip_plus_composition_sdxl.safetensors"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+451
-59
@@ -1,7 +1,11 @@
|
||||
import sys, os, re, json, time, math
|
||||
import sys, os, re, json, time, math, copy
|
||||
import torch
|
||||
import folder_paths
|
||||
import comfy.utils, comfy.samplers, comfy.controlnet, comfy.model_base, comfy.model_management
|
||||
import comfy.utils, comfy.sample, comfy.samplers, comfy.controlnet, comfy.model_base, comfy.model_management
|
||||
try:
|
||||
import comfy.sampler_helpers
|
||||
except:
|
||||
pass
|
||||
from comfy.sd import CLIP, VAE
|
||||
from comfy.model_patcher import ModelPatcher
|
||||
from comfy_extras.chainner_models import model_loading
|
||||
@@ -18,17 +22,18 @@ 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()
|
||||
easyCache = easyLoader()
|
||||
default_calculate_weight = copy.copy(ModelPatcher.calculate_weight)
|
||||
|
||||
image_suffixs = set([".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp", ".tiff", ".svg", ".ico", ".apng", ".tif", ".hdr", ".exr"])
|
||||
|
||||
@@ -88,12 +93,14 @@ class wildcardsPrompt:
|
||||
"Select to add LoRA": (["Select the LoRA to add to the text"] + folder_paths.get_filename_list("loras"),),
|
||||
"Select to add Wildcard": (["Select the Wildcard to add to the text"] + wildcard_list,),
|
||||
"seed": ("INT", {"default": 0, "min": 0, "max": MAX_SEED_NUM}),
|
||||
"multiline_mode": ("BOOLEAN", {"default": False}),
|
||||
},
|
||||
"hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID"},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING", "STRING")
|
||||
RETURN_NAMES = ("text", "populated_text")
|
||||
OUTPUT_IS_LIST = (True, True)
|
||||
OUTPUT_NODE = True
|
||||
FUNCTION = "main"
|
||||
|
||||
@@ -109,7 +116,14 @@ class wildcardsPrompt:
|
||||
easyCache.update_loaded_objects(prompt)
|
||||
|
||||
text = kwargs['text']
|
||||
populated_text = process(text, seed)
|
||||
if "multiline_mode" in kwargs and kwargs["multiline_mode"]:
|
||||
populated_text = []
|
||||
text = text.split("\n")
|
||||
for t in text:
|
||||
populated_text.append(process(t, seed))
|
||||
else:
|
||||
populated_text = [process(text, seed)]
|
||||
text = [text]
|
||||
return {"ui": {"value": [seed]}, "result": (text, populated_text)}
|
||||
|
||||
# 负面提示词
|
||||
@@ -239,8 +253,9 @@ class promptList:
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("LIST",)
|
||||
RETURN_NAMES = ("prompt_list",)
|
||||
RETURN_TYPES = ("LIST", "STRING")
|
||||
RETURN_NAMES = ("prompt_list", "prompt_strings")
|
||||
OUTPUT_IS_LIST = (False, True)
|
||||
FUNCTION = "run"
|
||||
CATEGORY = "EasyUse/Prompt"
|
||||
|
||||
@@ -259,9 +274,94 @@ class promptList:
|
||||
if isinstance(v, str) and v != '':
|
||||
prompts.append(v)
|
||||
|
||||
return (prompts,)
|
||||
return (prompts, prompts)
|
||||
|
||||
# 肖像大师
|
||||
#promptLine
|
||||
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}),
|
||||
},
|
||||
"hidden":{
|
||||
"workflow_prompt": "PROMPT", "my_unique_id": "UNIQUE_ID"
|
||||
}
|
||||
}
|
||||
|
||||
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, workflow_prompt=None, my_unique_id=None):
|
||||
lines = prompt.split('\n')
|
||||
|
||||
start_index = max(0, min(start_index, len(lines) - 1))
|
||||
|
||||
end_index = min(start_index + max_rows, len(lines))
|
||||
|
||||
rows = lines[start_index:end_index]
|
||||
|
||||
|
||||
return (rows, rows)
|
||||
|
||||
class promptConcat:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {"required": {
|
||||
},
|
||||
"optional": {
|
||||
"prompt1": ("STRING", {"multiline": False, "default": "", "forceInput": True}),
|
||||
"prompt2": ("STRING", {"multiline": False, "default": "", "forceInput": True}),
|
||||
"separator": ("STRING", {"multiline": False, "default": ""}),
|
||||
},
|
||||
}
|
||||
RETURN_TYPES = ("STRING", )
|
||||
RETURN_NAMES = ("prompt", )
|
||||
FUNCTION = "concat_text"
|
||||
CATEGORY = "EasyUse/Prompt"
|
||||
|
||||
def concat_text(self, prompt1="", prompt2="", separator=""):
|
||||
|
||||
return (prompt1 + separator + prompt2,)
|
||||
|
||||
class promptReplace:
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"prompt": ("STRING", {"multiline": True, "default": "", "forceInput": True}),
|
||||
},
|
||||
"optional": {
|
||||
"find1": ("STRING", {"multiline": False, "default": ""}),
|
||||
"replace1": ("STRING", {"multiline": False, "default": ""}),
|
||||
"find2": ("STRING", {"multiline": False, "default": ""}),
|
||||
"replace2": ("STRING", {"multiline": False, "default": ""}),
|
||||
"find3": ("STRING", {"multiline": False, "default": ""}),
|
||||
"replace3": ("STRING", {"multiline": False, "default": ""}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("prompt",)
|
||||
FUNCTION = "replace_text"
|
||||
CATEGORY = "EasyUse/Prompt"
|
||||
|
||||
def replace_text(self, text, find1="", replace1="", find2="", replace2="", find3="", replace3=""):
|
||||
|
||||
text = text.replace(find1, replace1)
|
||||
text = text.replace(find2, replace2)
|
||||
text = text.replace(find3, replace3)
|
||||
|
||||
return (text,)
|
||||
|
||||
|
||||
# 肖像大师
|
||||
# Created by AI Wiz Art (Stefano Flore)
|
||||
# Version: 2.2
|
||||
# https://stefanoflore.it
|
||||
@@ -503,10 +603,9 @@ class latentNoisy:
|
||||
device = comfy.model_management.get_torch_device()
|
||||
end_at_step = min(steps, end_at_step)
|
||||
start_at_step = min(start_at_step, end_at_step)
|
||||
real_model = None
|
||||
comfy.model_management.load_model_gpu(model)
|
||||
real_model = model.model
|
||||
sampler = comfy.samplers.KSampler(real_model, steps=steps, device=device, sampler=sampler_name,
|
||||
model_patcher = comfy.model_patcher.ModelPatcher(model.model, load_device=device, offload_device=comfy.model_management.unet_offload_device())
|
||||
sampler = comfy.samplers.KSampler(model_patcher, steps=steps, device=device, sampler=sampler_name,
|
||||
scheduler=scheduler, denoise=1.0, model_options=model.model_options)
|
||||
sigmas = sampler.sigmas
|
||||
sigma = sigmas[start_at_step] - sigmas[end_at_step]
|
||||
@@ -1938,7 +2037,8 @@ class ipadapter:
|
||||
'VIT-G (medium strength)',
|
||||
'PLUS (high strength)',
|
||||
'PLUS FACE (portraits)',
|
||||
'FULL FACE - SD1.5 only (portraits stronger)'
|
||||
'FULL FACE - SD1.5 only (portraits stronger)',
|
||||
'COMPOSITION'
|
||||
]
|
||||
self.faceid_presets = [
|
||||
'FACEID',
|
||||
@@ -1946,8 +2046,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
|
||||
|
||||
|
||||
@@ -1966,8 +2065,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
|
||||
|
||||
@@ -2008,6 +2107,11 @@ class ipadapter:
|
||||
if is_sdxl:
|
||||
raise Exception("full face model is not supported for SDXL")
|
||||
pattern = 'full.face.sd15\.(safetensors|bin)$'
|
||||
elif preset.startswith("composition"):
|
||||
if is_sdxl:
|
||||
pattern = 'plus.composition.sdxl\.(safetensors|bin)$'
|
||||
else:
|
||||
pattern = 'plus.composition.sd15\.(safetensors|bin)$'
|
||||
elif preset.startswith("faceid portrait"):
|
||||
if is_sdxl:
|
||||
pattern = 'portrait.sdxl\.(safetensors|bin)$'
|
||||
@@ -2045,8 +2149,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
|
||||
|
||||
@@ -2096,12 +2200,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
|
||||
|
||||
@@ -2110,9 +2216,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
|
||||
@@ -2125,12 +2243,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
|
||||
|
||||
@@ -2155,7 +2274,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},),
|
||||
},
|
||||
|
||||
@@ -2183,12 +2302,12 @@ class ipadapterApply(ipadapter):
|
||||
if "IPAdapterAdvanced" not in ALL_NODE_CLASS_MAPPINGS:
|
||||
self.error()
|
||||
cls = ALL_NODE_CLASS_MAPPINGS["IPAdapterAdvanced"]
|
||||
model, = cls().apply_ipadapter(model, ipadapter, image, weight, "linear", start_at, end_at, combine_embeds="concat", weight_faceidv2=weight_faceidv2, image_negative=None, clip_vision=None, attn_mask=attn_mask, insightface=None, embeds_scaling='V only')
|
||||
model, = cls().apply_ipadapter(model, ipadapter, start_at=start_at, end_at=end_at, weight=weight, weight_type="linear", combine_embeds="concat", weight_faceidv2=weight_faceidv2, image=image, image_negative=None, clip_vision=None, attn_mask=attn_mask, insightface=None, embeds_scaling='V only')
|
||||
else:
|
||||
if "IPAdapter" not in ALL_NODE_CLASS_MAPPINGS:
|
||||
self.error()
|
||||
cls = ALL_NODE_CLASS_MAPPINGS["IPAdapter"]
|
||||
model, = cls().apply_ipadapter(model, ipadapter, image, weight, start_at, end_at, attn_mask)
|
||||
model, = cls().apply_ipadapter(model, ipadapter, image, weight, start_at, end_at, weight_type='standard',attn_mask=attn_mask)
|
||||
|
||||
return (model, tiles, masks, ipadapter)
|
||||
|
||||
@@ -2216,7 +2335,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}),
|
||||
@@ -2235,9 +2354,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:
|
||||
@@ -2247,7 +2367,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, image=image, weight=weight, weight_type=weight_type, start_at=start_at, end_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:
|
||||
@@ -2257,9 +2377,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, image, weight, weight_type, start_at, end_at, combine_embeds=combine_embeds, weight_faceidv2=weight_faceidv2, 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__()
|
||||
@@ -2401,23 +2573,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()
|
||||
|
||||
@@ -2446,6 +2618,7 @@ class instantID:
|
||||
del pipe
|
||||
|
||||
return (new_pipe, model, positive, negative)
|
||||
|
||||
class instantIDApply(instantID):
|
||||
|
||||
def __init__(self):
|
||||
@@ -2806,8 +2979,8 @@ class samplerSettingsNoiseIn:
|
||||
|
||||
device = comfy.model_management.get_torch_device()
|
||||
comfy.model_management.load_model_gpu(model)
|
||||
real_model = model.model
|
||||
sampler = comfy.samplers.KSampler(real_model, steps=steps, device=device, sampler=sampler_name,
|
||||
model_patcher = comfy.model_patcher.ModelPatcher(model.model, load_device=device, offload_device=comfy.model_management.unet_offload_device())
|
||||
sampler = comfy.samplers.KSampler(model_patcher, steps=steps, device=device, sampler=sampler_name,
|
||||
scheduler=scheduler, denoise=1.0, model_options=model.model_options)
|
||||
sigmas = sampler.sigmas
|
||||
sigma = sigmas[start_at_step] - sigmas[end_at_step]
|
||||
@@ -2849,6 +3022,209 @@ class samplerSettingsNoiseIn:
|
||||
|
||||
return (new_pipe,)
|
||||
|
||||
# 预采样设置(自定义)
|
||||
from comfy_extras.nodes_custom_sampler import BasicGuider, DualCFGGuider, CFGGuider, KSamplerSelect, DisableNoise, RandomNoise, BasicScheduler, KarrasScheduler, ExponentialScheduler, PolyexponentialScheduler, SDTurboScheduler, VPScheduler
|
||||
from tqdm import trange
|
||||
class samplerCustomSettings:
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {"required":
|
||||
{"pipe": ("PIPE_LINE",),
|
||||
"guider": (['CFG','DualCFG','IP2P+DualCFG','Basic'],{"default":"Basic"}),
|
||||
"cfg": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0}),
|
||||
"cfg_negative": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0}),
|
||||
"sampler_name": (comfy.samplers.KSampler.SAMPLERS + ['inversed_euler'],),
|
||||
"scheduler": (comfy.samplers.KSampler.SCHEDULERS + ['karrasADV','exponentialADV','polyExponential','sdturbo','vp'],),
|
||||
"steps": ("INT", {"default": 20, "min": 1, "max": 10000}),
|
||||
"sigma_max": ("FLOAT", {"default": 14.614642, "min": 0.0, "max": 1000.0, "step": 0.01, "round": False}),
|
||||
"sigma_min": ("FLOAT", {"default": 0.0291675, "min": 0.0, "max": 1000.0, "step": 0.01, "round": False}),
|
||||
"rho": ("FLOAT", {"default": 7.0, "min": 0.0, "max": 100.0, "step": 0.01, "round": False}),
|
||||
"beta_d": ("FLOAT", {"default": 19.9, "min": 0.0, "max": 1000.0, "step": 0.01, "round": False}),
|
||||
"beta_min": ("FLOAT", {"default": 0.1, "min": 0.0, "max": 1000.0, "step": 0.01, "round": False}),
|
||||
"eps_s": ("FLOAT", {"default": 0.001, "min": 0.0, "max": 1.0, "step": 0.0001, "round": False}),
|
||||
"denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}),
|
||||
"add_noise": (["enable", "disable"], {"default": "enable"}),
|
||||
"seed": ("INT", {"default": 0, "min": 0, "max": MAX_SEED_NUM}),
|
||||
},
|
||||
"optional": {
|
||||
"image_to_latent": ("IMAGE",),
|
||||
"latent": ("LATENT",),
|
||||
"optional_sampler":("SAMPLER",),
|
||||
"optional_sigmas":("SIGMAS",),
|
||||
},
|
||||
"hidden":
|
||||
{"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID"},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("PIPE_LINE", )
|
||||
RETURN_NAMES = ("pipe",)
|
||||
OUTPUT_NODE = True
|
||||
|
||||
FUNCTION = "settings"
|
||||
CATEGORY = "EasyUse/PreSampling"
|
||||
|
||||
def ip2p(self, positive, negative, vae=None, pixels=None, latent=None):
|
||||
if latent is not None:
|
||||
concat_latent = latent
|
||||
else:
|
||||
x = (pixels.shape[1] // 8) * 8
|
||||
y = (pixels.shape[2] // 8) * 8
|
||||
|
||||
if pixels.shape[1] != x or pixels.shape[2] != y:
|
||||
x_offset = (pixels.shape[1] % 8) // 2
|
||||
y_offset = (pixels.shape[2] % 8) // 2
|
||||
pixels = pixels[:, x_offset:x + x_offset, y_offset:y + y_offset, :]
|
||||
|
||||
concat_latent = vae.encode(pixels)
|
||||
|
||||
out_latent = {}
|
||||
out_latent["samples"] = torch.zeros_like(concat_latent)
|
||||
|
||||
out = []
|
||||
for conditioning in [positive, negative]:
|
||||
c = []
|
||||
for t in conditioning:
|
||||
d = t[1].copy()
|
||||
d["concat_latent_image"] = concat_latent
|
||||
n = [t[0], d]
|
||||
c.append(n)
|
||||
out.append(c)
|
||||
return (out[0], out[1], out_latent)
|
||||
|
||||
def get_inversed_euler_sampler(self):
|
||||
@torch.no_grad()
|
||||
def sample_inversed_euler(model, x, sigmas, extra_args=None, callback=None, disable=None, s_churn=0., s_tmin=0.,
|
||||
s_tmax=float('inf'), s_noise=1.):
|
||||
"""Implements Algorithm 2 (Euler steps) from Karras et al. (2022)."""
|
||||
extra_args = {} if extra_args is None else extra_args
|
||||
s_in = x.new_ones([x.shape[0]])
|
||||
for i in trange(1, len(sigmas), disable=disable):
|
||||
sigma_in = sigmas[i - 1]
|
||||
|
||||
if i == 1:
|
||||
sigma_t = sigmas[i]
|
||||
else:
|
||||
sigma_t = sigma_in
|
||||
|
||||
denoised = model(x, sigma_t * s_in, **extra_args)
|
||||
|
||||
if i == 1:
|
||||
d = (x - denoised) / (2 * sigmas[i])
|
||||
else:
|
||||
d = (x - denoised) / sigmas[i - 1]
|
||||
|
||||
dt = sigmas[i] - sigmas[i - 1]
|
||||
x = x + d * dt
|
||||
if callback is not None:
|
||||
callback(
|
||||
{'x': x, 'i': i, 'sigma': sigmas[i], 'sigma_hat': sigmas[i], 'denoised': denoised})
|
||||
return x / sigmas[-1]
|
||||
|
||||
ksampler = comfy.samplers.KSAMPLER(sample_inversed_euler)
|
||||
return (ksampler,)
|
||||
|
||||
def settings(self, pipe, guider, cfg, cfg_negative, sampler_name, scheduler, steps, sigma_max, sigma_min, rho, beta_d, beta_min, eps_s, denoise, add_noise, seed, image_to_latent=None, latent=None, optional_sampler=None, optional_sigmas=None, prompt=None, extra_pnginfo=None, my_unique_id=None):
|
||||
# 图生图转换
|
||||
vae = pipe["vae"]
|
||||
model = pipe["model"]
|
||||
positive = pipe['positive']
|
||||
negative = pipe['negative']
|
||||
batch_size = pipe["loader_settings"]["batch_size"] if "batch_size" in pipe["loader_settings"] else 1
|
||||
_guider, sigmas = None, None
|
||||
if image_to_latent is not None:
|
||||
if guider == "IP2P+DualCFG":
|
||||
positive, negative, latent = self.ip2p(pipe['positive'], pipe['negative'], vae, image_to_latent)
|
||||
samples = latent
|
||||
else:
|
||||
samples = {"samples": vae.encode(image_to_latent[:, :, :, :3])}
|
||||
samples = RepeatLatentBatch().repeat(samples, batch_size)[0]
|
||||
images = image_to_latent
|
||||
elif latent is not None:
|
||||
if guider == "IP2P+DualCFG":
|
||||
positive, negative, latent = self.ip2p(pipe['positive'], pipe['negative'], latent=latent)
|
||||
samples = latent
|
||||
else:
|
||||
samples = latent
|
||||
images = pipe["images"]
|
||||
else:
|
||||
samples = pipe["samples"]
|
||||
images = pipe["images"]
|
||||
|
||||
# guider
|
||||
if guider == 'CFG':
|
||||
_guider, = CFGGuider().get_guider(model, positive, negative, cfg)
|
||||
elif guider in ['DualCFG', 'IP2P+DualCFG']:
|
||||
_guider, = DualCFGGuider().get_guider(model, positive, negative, pipe['negative'], cfg, cfg_negative)
|
||||
else:
|
||||
_guider, = BasicGuider().get_guider(model, positive)
|
||||
|
||||
# sampler
|
||||
if optional_sampler:
|
||||
sampler = optional_sampler
|
||||
else:
|
||||
if sampler_name == 'inversed_euler':
|
||||
sampler, = self.get_inversed_euler_sampler()
|
||||
else:
|
||||
sampler, = KSamplerSelect().get_sampler(sampler_name)
|
||||
|
||||
# sigmas
|
||||
if optional_sigmas:
|
||||
sigmas = optional_sigmas
|
||||
else:
|
||||
if scheduler == 'vp':
|
||||
sigmas, = VPScheduler().get_sigmas(steps, beta_d, beta_min, eps_s)
|
||||
elif scheduler == 'karrasADV':
|
||||
sigmas, = KarrasScheduler().get_sigmas(steps, sigma_max, sigma_min, rho)
|
||||
elif scheduler == 'exponentialADV':
|
||||
sigmas, = ExponentialScheduler().get_sigmas(steps, sigma_max, sigma_min)
|
||||
elif scheduler == 'polyExponential':
|
||||
sigmas, = PolyexponentialScheduler().get_sigmas(steps, sigma_max, sigma_min, rho)
|
||||
elif scheduler == 'sdturbo':
|
||||
sigmas, = SDTurboScheduler().get_sigmas(model, steps, denoise)
|
||||
else:
|
||||
sigmas, = BasicScheduler().get_sigmas(model, scheduler, steps, denoise)
|
||||
|
||||
# noise
|
||||
if add_noise == 'disabled':
|
||||
noise, = DisableNoise().get_noise()
|
||||
else:
|
||||
noise, = RandomNoise().get_noise(seed)
|
||||
|
||||
new_pipe = {
|
||||
"model": pipe['model'],
|
||||
"positive": pipe['positive'],
|
||||
"negative": pipe['negative'],
|
||||
"vae": pipe['vae'],
|
||||
"clip": pipe['clip'],
|
||||
|
||||
"samples": samples,
|
||||
"images": images,
|
||||
"seed": seed,
|
||||
|
||||
"loader_settings": {
|
||||
**pipe["loader_settings"],
|
||||
"steps": steps,
|
||||
"cfg": cfg,
|
||||
"sampler_name": sampler_name,
|
||||
"scheduler": scheduler,
|
||||
"denoise": denoise,
|
||||
"custom": {
|
||||
"noise": noise,
|
||||
"guider": _guider,
|
||||
"sampler": sampler,
|
||||
"sigmas": sigmas,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
del pipe
|
||||
|
||||
return {"ui": {"value": [seed]}, "result": (new_pipe,)}
|
||||
|
||||
# 预采样设置(SDTurbo)
|
||||
from .gradual_latent_hires_fix import sample_dpmpp_2s_ancestral, sample_dpmpp_2m_sde, sample_lcm, sample_euler_ancestral
|
||||
class sdTurboSettings:
|
||||
@@ -3386,6 +3762,7 @@ class samplerFull(LayerDiffuse):
|
||||
"vae": ("VAE",),
|
||||
"clip": ("CLIP",),
|
||||
"xyPlot": ("XYPLOT",),
|
||||
"image": ("IMAGE",),
|
||||
},
|
||||
"hidden":
|
||||
{"tile_size": "INT", "prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID",
|
||||
@@ -3413,6 +3790,8 @@ class samplerFull(LayerDiffuse):
|
||||
|
||||
samp_seed = seed if seed is not None else pipe['seed']
|
||||
|
||||
samp_custom = pipe["loader_settings"]["custom"] if "custom" in pipe["loader_settings"] else None
|
||||
|
||||
steps = steps if steps is not None else pipe['loader_settings']['steps']
|
||||
start_step = pipe['loader_settings']['start_step'] if 'start_step' in pipe['loader_settings'] else 0
|
||||
last_step = pipe['loader_settings']['last_step'] if 'last_step' in pipe['loader_settings'] else 10000
|
||||
@@ -3465,7 +3844,7 @@ class samplerFull(LayerDiffuse):
|
||||
samp_negative,
|
||||
steps, start_step, last_step, cfg, sampler_name, scheduler, denoise,
|
||||
image_output, link_id, save_prefix, tile_size, prompt, extra_pnginfo, my_unique_id,
|
||||
preview_latent, force_full_denoise=force_full_denoise, disable_noise=disable_noise):
|
||||
preview_latent, force_full_denoise=force_full_denoise, disable_noise=disable_noise, samp_custom=None):
|
||||
|
||||
# LayerDiffusion
|
||||
if "layer_diffusion_method" in pipe['loader_settings']:
|
||||
@@ -3492,7 +3871,7 @@ class samplerFull(LayerDiffuse):
|
||||
# 推理初始时间
|
||||
start_time = int(time.time() * 1000)
|
||||
# 开始推理
|
||||
samp_samples = sampler.common_ksampler(samp_model, samp_seed, steps, cfg, sampler_name, scheduler, samp_positive, samp_negative, samples, denoise=denoise, preview_latent=preview_latent, start_step=start_step, last_step=last_step, force_full_denoise=force_full_denoise, disable_noise=disable_noise)
|
||||
samp_samples = sampler.common_ksampler(samp_model, samp_seed, steps, cfg, sampler_name, scheduler, samp_positive, samp_negative, samples, denoise=denoise, preview_latent=preview_latent, start_step=start_step, last_step=last_step, force_full_denoise=force_full_denoise, disable_noise=disable_noise, custom=samp_custom)
|
||||
# 推理结束时间
|
||||
end_time = int(time.time() * 1000)
|
||||
latent = samp_samples["samples"]
|
||||
@@ -3547,12 +3926,13 @@ class samplerFull(LayerDiffuse):
|
||||
if image_output in ("Sender", "Sender/Save"):
|
||||
PromptServer.instance.send_sync("img-send", {"link_id": link_id, "images": results})
|
||||
|
||||
ModelPatcher.calculate_weight = default_calculate_weight
|
||||
return {"ui": {"images": results},
|
||||
"result": sampler.get_output(new_pipe,)}
|
||||
|
||||
def process_xyPlot(pipe, samp_model, samp_clip, samp_samples, samp_vae, samp_seed, samp_positive, samp_negative,
|
||||
steps, cfg, sampler_name, scheduler, denoise,
|
||||
image_output, link_id, save_prefix, tile_size, prompt, extra_pnginfo, my_unique_id, preview_latent, xyPlot, force_full_denoise, disable_noise):
|
||||
image_output, link_id, save_prefix, tile_size, prompt, extra_pnginfo, my_unique_id, preview_latent, xyPlot, force_full_denoise, disable_noise, samp_custom):
|
||||
|
||||
sampleXYplot = easyXYPlot(xyPlot, save_prefix, image_output, prompt, extra_pnginfo, my_unique_id, sampler, easyCache)
|
||||
|
||||
@@ -3560,7 +3940,7 @@ class samplerFull(LayerDiffuse):
|
||||
return process_sample_state(pipe, samp_model, samp_clip, samp_samples, samp_vae, samp_seed, samp_positive,
|
||||
samp_negative, steps, 0, 10000, cfg,
|
||||
sampler_name, scheduler, denoise, image_output, link_id, save_prefix, tile_size, prompt,
|
||||
extra_pnginfo, my_unique_id, preview_latent)
|
||||
extra_pnginfo, my_unique_id, preview_latent, samp_custom=samp_custom)
|
||||
|
||||
# Downscale Model Unet
|
||||
if samp_model is not None:
|
||||
@@ -3663,6 +4043,7 @@ class samplerFull(LayerDiffuse):
|
||||
if image_output in ("Hide", "Hide/Save"):
|
||||
return sampler.get_output(new_pipe)
|
||||
|
||||
ModelPatcher.calculate_weight = default_calculate_weight
|
||||
return {"ui": {"images": results}, "result": (sampler.get_output(new_pipe))}
|
||||
|
||||
preview_latent = True
|
||||
@@ -3675,9 +4056,9 @@ class samplerFull(LayerDiffuse):
|
||||
else:
|
||||
xyPlot = pipe["loader_settings"]["xyplot"] if "xyplot" in pipe["loader_settings"] else xyPlot
|
||||
if xyPlot is not None:
|
||||
return process_xyPlot(pipe, samp_model, samp_clip, samp_samples, samp_vae, samp_seed, samp_positive, samp_negative, steps, cfg, sampler_name, scheduler, denoise, image_output, link_id, save_prefix, tile_size, prompt, extra_pnginfo, my_unique_id, preview_latent, xyPlot, force_full_denoise, disable_noise)
|
||||
return process_xyPlot(pipe, samp_model, samp_clip, samp_samples, samp_vae, samp_seed, samp_positive, samp_negative, steps, cfg, sampler_name, scheduler, denoise, image_output, link_id, save_prefix, tile_size, prompt, extra_pnginfo, my_unique_id, preview_latent, xyPlot, force_full_denoise, disable_noise, samp_custom)
|
||||
else:
|
||||
return process_sample_state(pipe, samp_model, samp_clip, samp_samples, samp_vae, samp_seed, samp_positive, samp_negative, steps, start_step, last_step, cfg, sampler_name, scheduler, denoise, image_output, link_id, save_prefix, tile_size, prompt, extra_pnginfo, my_unique_id, preview_latent, force_full_denoise, disable_noise)
|
||||
return process_sample_state(pipe, samp_model, samp_clip, samp_samples, samp_vae, samp_seed, samp_positive, samp_negative, steps, start_step, last_step, cfg, sampler_name, scheduler, denoise, image_output, link_id, save_prefix, tile_size, prompt, extra_pnginfo, my_unique_id, preview_latent, force_full_denoise, disable_noise, samp_custom)
|
||||
|
||||
# 简易采样器
|
||||
class samplerSimple:
|
||||
@@ -3916,13 +4297,14 @@ class samplerSimpleInpainting:
|
||||
raise Exception("Differential Diffusion not found,please update comfyui")
|
||||
|
||||
# when patch was linked
|
||||
fooocus_model = None
|
||||
if patch is not None:
|
||||
worker = InpaintWorker(node_name="easy kSamplerInpainting")
|
||||
fooocus_model, = worker.patch(model, latent, patch)
|
||||
|
||||
new_pipe = {
|
||||
**pipe,
|
||||
"model": fooocus_model if fooocus_model is not None else model,
|
||||
"model": fooocus_model if fooocus_model else model,
|
||||
"positive": positive,
|
||||
"negative": negative,
|
||||
"vae": vae,
|
||||
@@ -3934,7 +4316,7 @@ class samplerSimpleInpainting:
|
||||
del pipe
|
||||
|
||||
return samplerFull().run(new_pipe, None, None,None,None,None, image_output, link_id, save_prefix,
|
||||
None, model, None, None, None, None, None, None,
|
||||
None, None, None, None, None, None, None, None,
|
||||
tile_size, prompt, extra_pnginfo, my_unique_id, force_full_denoise, disable_noise)
|
||||
|
||||
# SDTurbo采样器
|
||||
@@ -4310,23 +4692,23 @@ class unsampler:
|
||||
if "noise_mask" in latent:
|
||||
noise_mask = comfy.sample.prepare_mask(latent["noise_mask"], noise.shape, device)
|
||||
|
||||
real_model = None
|
||||
real_model = model.model
|
||||
|
||||
noise = noise.to(device)
|
||||
latent_image = latent_image.to(device)
|
||||
|
||||
positive = comfy.sample.convert_cond(positive)
|
||||
negative = comfy.sample.convert_cond(negative)
|
||||
_positive = comfy.sampler_helpers.convert_cond(positive)
|
||||
_negative = comfy.sampler_helpers.convert_cond(negative)
|
||||
models, inference_memory = comfy.sampler_helpers.get_additional_models({"positive": _positive, "negative": _negative}, model.model_dtype())
|
||||
|
||||
models, inference_memory = comfy.sample.get_additional_models(positive, negative, model.model_dtype())
|
||||
|
||||
comfy.model_management.load_models_gpu([model] + models, model.memory_required(noise.shape) + inference_memory)
|
||||
|
||||
sampler = comfy.samplers.KSampler(real_model, steps=steps, device=device, sampler=sampler_name,
|
||||
model_patcher = comfy.model_patcher.ModelPatcher(model.model, load_device=device, offload_device=comfy.model_management.unet_offload_device())
|
||||
|
||||
sampler = comfy.samplers.KSampler(model_patcher, steps=steps, device=device, sampler=sampler_name,
|
||||
scheduler=scheduler, denoise=1.0, model_options=model.model_options)
|
||||
|
||||
sigmas = sigmas = sampler.sigmas.flip(0) + 0.0001
|
||||
sigmas = sampler.sigmas.flip(0) + 0.0001
|
||||
|
||||
pbar = comfy.utils.ProgressBar(steps)
|
||||
|
||||
@@ -6293,6 +6675,9 @@ NODE_CLASS_MAPPINGS = {
|
||||
"easy negative": negativePrompt,
|
||||
"easy wildcards": wildcardsPrompt,
|
||||
"easy promptList": promptList,
|
||||
"easy promptLine": promptLine,
|
||||
"easy promptConcat": promptConcat,
|
||||
"easy promptReplace": promptReplace,
|
||||
"easy stylesSelector": stylesPromptSelector,
|
||||
"easy portraitMaster": portraitMaster,
|
||||
# loaders 加载器
|
||||
@@ -6313,6 +6698,7 @@ NODE_CLASS_MAPPINGS = {
|
||||
"easy ipadapterApplyADV": ipadapterApplyAdvanced,
|
||||
"easy ipadapterApplyEncoder": ipadapterApplyEncoder,
|
||||
"easy ipadapterApplyEmbeds": ipadapterApplyEmbeds,
|
||||
"easy ipadapterStyleComposition": ipadapterStyleComposition,
|
||||
"easy instantIDApply": instantIDApply,
|
||||
"easy instantIDApplyADV": instantIDApplyAdvanced,
|
||||
# Inpaint 内补
|
||||
@@ -6324,6 +6710,7 @@ NODE_CLASS_MAPPINGS = {
|
||||
"easy preSampling": samplerSettings,
|
||||
"easy preSamplingAdvanced": samplerSettingsAdvanced,
|
||||
"easy preSamplingNoiseIn": samplerSettingsNoiseIn,
|
||||
"easy preSamplingCustom": samplerCustomSettings,
|
||||
"easy preSamplingSdTurbo": sdTurboSettings,
|
||||
"easy preSamplingDynamicCFG": dynamicCFGSettings,
|
||||
"easy preSamplingCascade": cascadeSettings,
|
||||
@@ -6386,6 +6773,9 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"easy negative": "Negative",
|
||||
"easy wildcards": "Wildcards",
|
||||
"easy promptList": "PromptList",
|
||||
"easy promptLine": "PromptLine",
|
||||
"easy promptConcat": "PromptConcat",
|
||||
"easy promptReplace": "PromptReplace",
|
||||
"easy stylesSelector": "Styles Selector",
|
||||
"easy portraitMaster": "Portrait Master",
|
||||
# loaders 加载器
|
||||
@@ -6404,6 +6794,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",
|
||||
@@ -6417,6 +6808,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"easy preSampling": "PreSampling",
|
||||
"easy preSamplingAdvanced": "PreSampling (Advanced)",
|
||||
"easy preSamplingNoiseIn": "PreSampling (NoiseIn)",
|
||||
"easy preSamplingCustom": "PreSampling (Custom)",
|
||||
"easy preSamplingSdTurbo": "PreSampling (SDTurbo)",
|
||||
"easy preSamplingDynamicCFG": "PreSampling (DynamicCFG)",
|
||||
"easy preSamplingCascade": "PreSampling (Cascade)",
|
||||
|
||||
+75
-1
@@ -4,9 +4,13 @@ import hashlib
|
||||
import folder_paths
|
||||
import torch
|
||||
import numpy as np
|
||||
import comfy.model_management
|
||||
from server import PromptServer
|
||||
from nodes import MAX_RESOLUTION
|
||||
from .log import log_node_info
|
||||
from .libs.image import pil2tensor, tensor2pil, ResizeMode, get_new_bounds
|
||||
from .libs.chooser import ChooserMessage, ChooserCancelled
|
||||
from nodes import PreviewImage
|
||||
|
||||
# 图像裁切
|
||||
class imageInsetCrop:
|
||||
@@ -582,7 +586,75 @@ class imageRemBg:
|
||||
else:
|
||||
return (None, None)
|
||||
|
||||
# 姿势编辑器
|
||||
# 图像选择器
|
||||
class imageChooser(PreviewImage):
|
||||
@classmethod
|
||||
def INPUT_TYPES(self):
|
||||
return {
|
||||
"required":{
|
||||
|
||||
},
|
||||
"optional": {
|
||||
"images": ("IMAGE",),
|
||||
},
|
||||
"hidden": {"prompt": "PROMPT", "my_unique_id": "UNIQUE_ID"},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_NAMES = ("image",)
|
||||
FUNCTION = "chooser"
|
||||
OUTPUT_NODE = True
|
||||
INPUT_IS_LIST = True
|
||||
CATEGORY = "EasyUse/Image"
|
||||
|
||||
last_ic = {}
|
||||
@classmethod
|
||||
def IS_CHANGED(cls, my_unique_id, **kwargs):
|
||||
return cls.last_ic[my_unique_id[0]]
|
||||
|
||||
def tensor_bundle(self, tensor_in: torch.Tensor, picks):
|
||||
if tensor_in is not None and len(picks):
|
||||
batch = tensor_in.shape[0]
|
||||
return torch.cat(tuple([tensor_in[(x) % batch].unsqueeze_(0) for x in picks])).reshape(
|
||||
[-1] + list(tensor_in.shape[1:]))
|
||||
else:
|
||||
return None
|
||||
|
||||
def chooser(self, prompt=None, my_unique_id=None, **kwargs):
|
||||
|
||||
id = my_unique_id[0]
|
||||
if id not in ChooserMessage.stash:
|
||||
ChooserMessage.stash[id] = {}
|
||||
my_stash = ChooserMessage.stash[id]
|
||||
|
||||
# enable stashing. If images is None, we are operating in read-from-stash mode
|
||||
if 'images' in kwargs:
|
||||
my_stash['images'] = kwargs['images']
|
||||
else:
|
||||
kwargs['images'] = my_stash.get('images', None)
|
||||
|
||||
if (kwargs['images'] is None):
|
||||
return (None, None, None, "")
|
||||
|
||||
images_in = torch.cat(kwargs.pop('images'))
|
||||
self.batch = images_in.shape[0]
|
||||
for x in kwargs: kwargs[x] = kwargs[x][0]
|
||||
result = self.save_images(images=images_in, prompt=prompt)
|
||||
|
||||
images = result['ui']['images']
|
||||
PromptServer.instance.send_sync("easyuse-image-choose", {"id": id, "urls": images})
|
||||
|
||||
# wait for selection
|
||||
try:
|
||||
selections = ChooserMessage.waitForMessage(id, asList=True)
|
||||
choosen = [x for x in selections if x >= 0] if len(selections)>1 else [0]
|
||||
except ChooserCancelled:
|
||||
raise comfy.model_management.InterruptProcessingException()
|
||||
|
||||
return {"ui": {"images": images},
|
||||
"result": (self.tensor_bundle(images_in, choosen),)}
|
||||
|
||||
# 姿势编辑器
|
||||
class poseEditor:
|
||||
@classmethod
|
||||
def INPUT_TYPES(self):
|
||||
@@ -638,6 +710,7 @@ NODE_CLASS_MAPPINGS = {
|
||||
"easy imageSplitList": imageSplitList,
|
||||
"easy imageSave": imageSaveSimple,
|
||||
"easy imageRemBg": imageRemBg,
|
||||
"easy imageChooser": imageChooser,
|
||||
"easy joinImageBatch": JoinImageBatch,
|
||||
"easy poseEditor": poseEditor
|
||||
}
|
||||
@@ -657,6 +730,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"easy imageSplitList": "imageSplitList",
|
||||
"easy imageSave": "SaveImage (Simple)",
|
||||
"easy imageRemBg": "Image Remove Bg",
|
||||
"easy imageChooser": "Image Chooser",
|
||||
"easy joinImageBatch": "JoinImageBatch",
|
||||
"easy poseEditor": "PoseEditor"
|
||||
}
|
||||
+79
-5
@@ -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
|
||||
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}")
|
||||
@@ -0,0 +1,52 @@
|
||||
from server import PromptServer
|
||||
from aiohttp import web
|
||||
import time
|
||||
|
||||
class ChooserCancelled(Exception):
|
||||
pass
|
||||
|
||||
class ChooserMessage:
|
||||
stash = {}
|
||||
messages = {}
|
||||
cancelled = False
|
||||
|
||||
@classmethod
|
||||
def addMessage(cls, id, message):
|
||||
if message == '__cancel__':
|
||||
cls.messages = {}
|
||||
cls.cancelled = True
|
||||
elif message == '__start__':
|
||||
cls.messages = {}
|
||||
cls.stash = {}
|
||||
cls.cancelled = False
|
||||
else:
|
||||
cls.messages[str(id)] = message
|
||||
|
||||
@classmethod
|
||||
def waitForMessage(cls, id, period=0.1, asList=False):
|
||||
sid = str(id)
|
||||
while not (sid in cls.messages) and not ("-1" in cls.messages):
|
||||
if cls.cancelled:
|
||||
cls.cancelled = False
|
||||
raise ChooserCancelled()
|
||||
time.sleep(period)
|
||||
if cls.cancelled:
|
||||
cls.cancelled = False
|
||||
raise ChooserCancelled()
|
||||
message = cls.messages.pop(str(id), None) or cls.messages.pop("-1")
|
||||
try:
|
||||
if asList:
|
||||
return [int(x.strip()) for x in message.split(",")]
|
||||
else:
|
||||
return int(message.strip())
|
||||
except ValueError:
|
||||
print(
|
||||
f"ERROR IN IMAGE_CHOOSER - failed to parse '${message}' as ${'comma separated list of ints' if asList else 'int'}")
|
||||
return [1] if asList else 1
|
||||
|
||||
|
||||
@PromptServer.instance.routes.post('/easyuse/image_chooser_message')
|
||||
async def make_image_selection(request):
|
||||
post = await request.post()
|
||||
ChooserMessage.addMessage(post.get("id"), post.get("message"))
|
||||
return web.json_response({})
|
||||
@@ -276,6 +276,32 @@ class easyLoader:
|
||||
lbw_a, lbw_b, "", lbw)
|
||||
else:
|
||||
_lora = comfy.utils.load_torch_file(lora_path, safe_load=True)
|
||||
keys = _lora.keys()
|
||||
if "down_blocks.0.resnets.0.norm1.bias" in keys:
|
||||
print('Using LORA for Resadapter')
|
||||
key_map = {}
|
||||
key_map = comfy.lora.model_lora_keys_unet(model.model, key_map)
|
||||
mapping_norm = {}
|
||||
|
||||
for key in keys:
|
||||
if ".weight" in key:
|
||||
key_name_in_ori_sd = key_map[key.replace(".weight", "")]
|
||||
mapping_norm[key_name_in_ori_sd] = _lora[key]
|
||||
elif ".bias" in key:
|
||||
key_name_in_ori_sd = key_map[key.replace(".bias", "")]
|
||||
mapping_norm[key_name_in_ori_sd.replace(".weight", ".bias")] = _lora[
|
||||
key
|
||||
]
|
||||
else:
|
||||
print("===>Unexpected key", key)
|
||||
mapping_norm[key] = _lora[key]
|
||||
|
||||
for k in mapping_norm.keys():
|
||||
if k not in model.model.state_dict():
|
||||
print("===>Missing key:", k)
|
||||
model.model.load_state_dict(mapping_norm, strict=False)
|
||||
return (model, clip)
|
||||
|
||||
model, clip = comfy.sd.load_lora_for_models(model, clip, _lora, model_strength, clip_strength)
|
||||
|
||||
self.add_to_cache("lora", unique_id, (model, clip))
|
||||
|
||||
+24
-13
@@ -49,16 +49,10 @@ class easySampler:
|
||||
|
||||
def common_ksampler(self, model, seed, steps, cfg, sampler_name, scheduler, positive, negative, latent, denoise=1.0,
|
||||
disable_noise=False, start_step=None, last_step=None, force_full_denoise=False,
|
||||
preview_latent=True, disable_pbar=False):
|
||||
preview_latent=True, disable_pbar=False, custom=None):
|
||||
device = comfy.model_management.get_torch_device()
|
||||
latent_image = latent["samples"]
|
||||
|
||||
if disable_noise:
|
||||
noise = torch.zeros(latent_image.size(), dtype=latent_image.dtype, layout=latent_image.layout, device="cpu")
|
||||
else:
|
||||
batch_inds = latent["batch_index"] if "batch_index" in latent else None
|
||||
noise = comfy.sample.prepare_noise(latent_image, seed, batch_inds)
|
||||
|
||||
noise_mask = None
|
||||
if "noise_mask" in latent:
|
||||
noise_mask = latent["noise_mask"]
|
||||
@@ -80,12 +74,29 @@ class easySampler:
|
||||
preview_bytes = previewer.decode_latent_to_preview_image(preview_format, x0)
|
||||
pbar.update_absolute(step + 1, total_steps, preview_bytes)
|
||||
|
||||
samples = comfy.sample.sample(model, noise, steps, cfg, sampler_name, scheduler, positive, negative,
|
||||
latent_image,
|
||||
denoise=denoise, disable_noise=disable_noise, start_step=start_step,
|
||||
last_step=last_step,
|
||||
force_full_denoise=force_full_denoise, noise_mask=noise_mask, callback=callback,
|
||||
disable_pbar=disable_pbar, seed=seed)
|
||||
if custom is not None:
|
||||
guider = custom['guider'] if 'guider' in custom else None
|
||||
sampler = custom['sampler'] if 'sampler' in custom else None
|
||||
sigmas = custom['sigmas'] if 'sigmas' in custom else None
|
||||
noise = custom['noise'] if 'noise' in custom else None
|
||||
samples = guider.sample(noise.generate_noise(latent), latent_image, sampler, sigmas,
|
||||
denoise_mask=noise_mask, callback=callback, disable_pbar=disable_pbar,
|
||||
seed=noise.seed)
|
||||
samples = samples.to(comfy.model_management.intermediate_device())
|
||||
else:
|
||||
if disable_noise:
|
||||
noise = torch.zeros(latent_image.size(), dtype=latent_image.dtype, layout=latent_image.layout,
|
||||
device="cpu")
|
||||
else:
|
||||
batch_inds = latent["batch_index"] if "batch_index" in latent else None
|
||||
noise = comfy.sample.prepare_noise(latent_image, seed, batch_inds)
|
||||
|
||||
samples = comfy.sample.sample(model, noise, steps, cfg, sampler_name, scheduler, positive, negative,
|
||||
latent_image,
|
||||
denoise=denoise, disable_noise=disable_noise, start_step=start_step,
|
||||
last_step=last_step,
|
||||
force_full_denoise=force_full_denoise, noise_mask=noise_mask, callback=callback,
|
||||
disable_pbar=disable_pbar, seed=seed)
|
||||
|
||||
out = latent.copy()
|
||||
out["samples"] = samples
|
||||
|
||||
+43
-3
@@ -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:
|
||||
@@ -121,11 +128,14 @@ def is_linked_styles_selector(prompt, my_unique_id, prompt_type='positive'):
|
||||
else:
|
||||
return False
|
||||
|
||||
use_mirror = False
|
||||
def get_local_filepath(url, dirname, local_file_name=None):
|
||||
"""Get local file path when is already downloaded or download it"""
|
||||
import os
|
||||
from server import PromptServer
|
||||
from urllib.parse import urlparse
|
||||
from torch.hub import download_url_to_file
|
||||
global use_mirror
|
||||
if not os.path.exists(dirname):
|
||||
os.makedirs(dirname)
|
||||
if not local_file_name:
|
||||
@@ -133,8 +143,23 @@ def get_local_filepath(url, dirname, local_file_name=None):
|
||||
local_file_name = os.path.basename(parsed_url.path)
|
||||
destination = os.path.join(dirname, local_file_name)
|
||||
if not os.path.exists(destination):
|
||||
print(f'downloading {url} to {destination}')
|
||||
download_url_to_file(url, destination)
|
||||
try:
|
||||
if use_mirror:
|
||||
url = url.replace('huggingface.co', 'hf-mirror.com')
|
||||
print(f'downloading {url} to {destination}')
|
||||
PromptServer.instance.send_sync("easyuse-toast", {'content': f'Downloading model to {destination}, please wait...', 'duration': 10000})
|
||||
download_url_to_file(url, destination)
|
||||
except Exception as e:
|
||||
use_mirror = True
|
||||
url = url.replace('huggingface.co', 'hf-mirror.com')
|
||||
print(f'无法从huggingface下载,正在尝试从 {url} 下载...')
|
||||
PromptServer.instance.send_sync("easyuse-toast", {'content': f'无法连接huggingface,正在尝试从 {url} 下载...', 'duration': 10000})
|
||||
try:
|
||||
download_url_to_file(url, destination)
|
||||
except Exception as err:
|
||||
PromptServer.instance.send_sync("easyuse-toast",
|
||||
{'content': f'无法从 {url} 下载模型'}, type='error')
|
||||
raise Exception(f'无法从 {url} 下载,错误信息:{str(err.args[0])}')
|
||||
return destination
|
||||
|
||||
def to_lora_patch_dict(state_dict: dict) -> dict:
|
||||
@@ -167,4 +192,19 @@ def easySave(images, filename_prefix, output_type, prompt=None, extra_pnginfo=No
|
||||
return results['ui']['images']
|
||||
else:
|
||||
results = SaveImage().save_images(images, filename_prefix, prompt, extra_pnginfo)
|
||||
return results['ui']['images']
|
||||
return results['ui']['images']
|
||||
|
||||
def getMetadata(filepath):
|
||||
with open(filepath, "rb") as file:
|
||||
# https://github.com/huggingface/safetensors#format
|
||||
# 8 bytes: N, an unsigned little-endian 64-bit integer, containing the size of the header
|
||||
header_size = int.from_bytes(file.read(8), "little", signed=False)
|
||||
|
||||
if header_size <= 0:
|
||||
raise BufferError("Invalid header size")
|
||||
|
||||
header = file.read(header_size)
|
||||
if header_size <= 0:
|
||||
raise BufferError("Invalid header")
|
||||
|
||||
return header
|
||||
+46
-7
@@ -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 clearCacheKey:
|
||||
@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 clearCacheAll:
|
||||
@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 clearCacheKey": clearCacheKey,
|
||||
"easy clearCacheAll": clearCacheAll,
|
||||
"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 clearCacheKey": "Clear Cache Key",
|
||||
"easy clearCacheAll": "Clear Cache All",
|
||||
"easy cleanGpuUsed": "Clean GPU Used"
|
||||
}
|
||||
+1
-2
@@ -156,9 +156,8 @@ def process(text, seed=None):
|
||||
|
||||
def replace_wildcard(string):
|
||||
global easy_wildcard_dict
|
||||
pattern = r"__([\w.\-+/*\\]+)__"
|
||||
pattern = r"__([\w\s.\-+/*\\]+?)__"
|
||||
matches = re.findall(pattern, string)
|
||||
|
||||
replacements_found = False
|
||||
|
||||
for match in matches:
|
||||
|
||||
+3
-1
@@ -2,4 +2,6 @@
|
||||
@import "dropdown.css";
|
||||
@import "selector.css";
|
||||
@import "groupmap.css";
|
||||
@import "contextmenu.css";
|
||||
@import "contextmenu.css";
|
||||
@import "modelinfo.css";
|
||||
@import "toast.css";
|
||||
@@ -0,0 +1,265 @@
|
||||
.easyuse-model-info {
|
||||
color: white;
|
||||
max-width: 90vw;
|
||||
font-family: var(--font-family);
|
||||
}
|
||||
.easyuse-model-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
.easyuse-model-header{
|
||||
margin:0 0 15px 0;
|
||||
}
|
||||
.easyuse-model-header-remark{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top:5px;
|
||||
}
|
||||
.easyuse-model-info h2 {
|
||||
text-align: left;
|
||||
margin:0;
|
||||
}
|
||||
.easyuse-model-info h5 {
|
||||
text-align: left;
|
||||
margin:0 15px 0 0px;
|
||||
font-weight: 400;
|
||||
color:var(--descrip-text);
|
||||
}
|
||||
.easyuse-model-info p {
|
||||
margin: 5px 0;
|
||||
}
|
||||
.easyuse-model-info a {
|
||||
color: var(--theme-color-light);
|
||||
}
|
||||
.easyuse-model-info a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.easyuse-model-tags-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
list-style: none;
|
||||
gap: 10px;
|
||||
max-height: 200px;
|
||||
overflow: auto;
|
||||
margin: 10px 0;
|
||||
padding: 0;
|
||||
}
|
||||
.easyuse-model-tag {
|
||||
background-color: var(--comfy-input-bg);
|
||||
border: 2px solid var(--border-color);
|
||||
color: var(--input-text);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
border-radius: 5px;
|
||||
padding: 2px 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.easyuse-model-tag--selected span::before {
|
||||
content: "✅";
|
||||
position: absolute;
|
||||
background-color: var(--theme-color-light);
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
text-align: center;
|
||||
}
|
||||
.easyuse-model-tag:hover {
|
||||
border: 2px solid var(--theme-color-light);
|
||||
}
|
||||
.easyuse-model-tag p {
|
||||
margin: 0;
|
||||
}
|
||||
.easyuse-model-tag span {
|
||||
text-align: center;
|
||||
border-radius: 5px;
|
||||
background-color: var(--theme-color-light);
|
||||
padding: 2px;
|
||||
position: relative;
|
||||
min-width: 20px;
|
||||
overflow: hidden;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.easyuse-model-metadata .comfy-modal-content {
|
||||
max-width: 100%;
|
||||
}
|
||||
.easyuse-model-metadata label {
|
||||
margin-right: 1ch;
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.easyuse-model-metadata span {
|
||||
color: var(--theme-color-light);
|
||||
}
|
||||
|
||||
.easyuse-preview {
|
||||
max-width:660px;
|
||||
margin-right: 15px;
|
||||
position: relative;
|
||||
}
|
||||
.easyuse-preview-group{
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
border-radius:.5rem;
|
||||
width: 660px;
|
||||
}
|
||||
.easyuse-preview-list{
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
width: 100%;
|
||||
transition: all .5s ease-in-out;
|
||||
}
|
||||
.easyuse-preview-list.no-transition{
|
||||
transition: none;
|
||||
}
|
||||
.easyuse-preview-slide{
|
||||
display: flex;
|
||||
flex-basis: calc(50% - 5px);
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
position: relative;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding-right:5px;
|
||||
padding-left:0;
|
||||
}
|
||||
.easyuse-preview-slide:nth-child(even){
|
||||
padding-left:5px;
|
||||
padding-right:0;
|
||||
}
|
||||
.easyuse-preview-slide-content{
|
||||
position: relative;
|
||||
min-height:150px;
|
||||
width: 100%;
|
||||
}
|
||||
.easyuse-preview-slide-content .save{
|
||||
position: absolute;
|
||||
right: 6px;
|
||||
z-index: 12;
|
||||
bottom: 6px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 26px;
|
||||
padding: 0 9px;
|
||||
color: var(--input-text);
|
||||
font-size: 12px;
|
||||
line-height: 26px;
|
||||
background: rgba(0, 0, 0, .5);
|
||||
border-radius: 13px;
|
||||
cursor: pointer;
|
||||
min-width:80px;
|
||||
text-align: center;
|
||||
}
|
||||
.easyuse-preview-slide-content .save:hover{
|
||||
filter: brightness(120%);
|
||||
will-change: auto;
|
||||
}
|
||||
|
||||
.easyuse-preview-slide-content img {
|
||||
border-radius: 14px;
|
||||
object-position: center center;
|
||||
max-width: 100%;
|
||||
max-height:700px;
|
||||
border-style: none;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.easyuse-preview button {
|
||||
position: absolute;
|
||||
z-index:10;
|
||||
top: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width:30px;
|
||||
height:30px;
|
||||
border-radius:15px;
|
||||
border:1px solid rgba(66, 63, 78, .15);
|
||||
background-color: rgba(66, 63, 78, .5);
|
||||
color:hsla(0, 0%, 100%, .8);
|
||||
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke;
|
||||
transition-timing-function: cubic-bezier(.4,0,.2,1);
|
||||
transition-duration: .15s;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
.easyuse-preview button.left{
|
||||
left:10px;
|
||||
}
|
||||
.easyuse-preview button.right{
|
||||
right:10px;
|
||||
}
|
||||
|
||||
.easyuse-model-detail{
|
||||
margin-top: 16px;
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
width:300px;
|
||||
}
|
||||
.easyuse-model-detail-head{
|
||||
height: 40px;
|
||||
padding: 0 10px;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
line-height: 40px;
|
||||
}
|
||||
.easyuse-model-detail-body{
|
||||
box-sizing: border-box;
|
||||
font-size: 12px;
|
||||
}
|
||||
.easyuse-model-detail-item{
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
border-top: 1px solid var(--border-color);
|
||||
}
|
||||
.easyuse-model-detail-item-label{
|
||||
flex-shrink: 0;
|
||||
width: 88px;
|
||||
padding-top: 5px;
|
||||
padding-bottom: 5px;
|
||||
padding-left: 10px;
|
||||
border-right: 1px solid var(--border-color);
|
||||
color: var(--input-text);
|
||||
font-weight: 400;
|
||||
}
|
||||
.easyuse-model-detail-item-value{
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
padding: 5px 10px 5px 10px;
|
||||
color: var(--input-text);
|
||||
}
|
||||
.easyuse-model-detail-textarea{
|
||||
border-top:1px solid var(--border-color);
|
||||
padding:10px;
|
||||
height:100px;
|
||||
overflow-y: auto;
|
||||
font-size: 12px;
|
||||
}
|
||||
.easyuse-model-detail-textarea textarea{
|
||||
width:100%;
|
||||
height:100%;
|
||||
border:0;
|
||||
background-color:transparent;
|
||||
color: var(--input-text);
|
||||
}
|
||||
.easyuse-model-detail-textarea textarea::placeholder{
|
||||
color:var(--descrip-text);
|
||||
}
|
||||
.easyuse-model-detail-textarea.empty{
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: var(--descrip-text);
|
||||
}
|
||||
|
||||
.easyuse-model-notes {
|
||||
background-color: rgba(0, 0, 0, 0.25);
|
||||
padding: 5px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
.easyuse-model-notes:empty {
|
||||
display: none;
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
:root {
|
||||
--theme-color:#3f3eed;
|
||||
--theme-color-light:#006691;
|
||||
--success-color: #52c41a;
|
||||
--error-color: #ff4d4f;
|
||||
--warning-color: #faad14;
|
||||
--font-family: Inter, -apple-system, BlinkMacSystemFont, Helvetica Neue, sans-serif;
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
.easyuse-toast-container{
|
||||
position: fixed;
|
||||
z-index: 99999;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: start;
|
||||
padding:10px 0;
|
||||
}
|
||||
.easyuse-toast-container > div {
|
||||
position: relative;
|
||||
height: fit-content;
|
||||
padding: 4px;
|
||||
margin-top: -100px; /* re-set by JS */
|
||||
opacity: 0;
|
||||
transition: all 0.33s ease-in-out;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.easyuse-toast-container > div:last-child {
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.easyuse-toast-container > div:not(.-show) {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.easyuse-toast-container > div.-show {
|
||||
opacity: 1;
|
||||
margin-top: 0px !important;
|
||||
}
|
||||
|
||||
.easyuse-toast-container > div.-show {
|
||||
opacity: 1;
|
||||
transform: translateY(0%);
|
||||
}
|
||||
|
||||
.easyuse-toast-container > div > div {
|
||||
position: relative;
|
||||
background: var(--comfy-menu-bg);
|
||||
color: var(--input-text);
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: fit-content;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.88);
|
||||
padding: 9px 12px;
|
||||
border-radius: 8px;
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: 14px;
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
.easyuse-toast-container > div > div > span {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.easyuse-toast-container > div > div > span svg {
|
||||
width: 16px;
|
||||
height: auto;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.easyuse-toast-container > div > div > span svg[data-icon=info-circle]{
|
||||
fill: var(--theme-color-light);
|
||||
}
|
||||
.easyuse-toast-container > div > div > span svg[data-icon=check-circle]{
|
||||
fill: var(--success-color);
|
||||
}
|
||||
.easyuse-toast-container > div > div > span svg[data-icon=close-circle]{
|
||||
fill: var(--error-color);
|
||||
}
|
||||
.easyuse-toast-container > div > div > span svg[data-icon=exclamation-circle]{
|
||||
fill: var(--warning-color);
|
||||
}
|
||||
/*rotate animation*/
|
||||
@keyframes rotate {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
.easyuse-toast-container > div > div > span svg[data-icon=loading]{
|
||||
fill: var(--theme-color);
|
||||
animation: rotate 1s linear infinite;
|
||||
}
|
||||
|
||||
.easyuse-toast-container a {
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
color: var(--theme-color-light);
|
||||
margin-left: 4px;
|
||||
display: inline-block;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.easyuse-toast-container a:hover {
|
||||
color: var(--theme-color-light);
|
||||
text-decoration: none;
|
||||
}
|
||||
+30
-2
@@ -2,8 +2,34 @@ import {getLocale} from './utils.js'
|
||||
const locale = getLocale()
|
||||
|
||||
const zhCN = {
|
||||
// ExtraMenu
|
||||
"💎 View Checkpoint Info...": "💎 查看 Checkpoint 信息...",
|
||||
"💎 View Lora Info...": "💎 查看 Lora 信息...",
|
||||
"🔃 Reload Node": "🔃 刷新节点",
|
||||
// ModelInfo
|
||||
"Updated At:": "最近更新:",
|
||||
"Created At:": "首次发布:",
|
||||
"✏️ Edit": "✏️ 编辑",
|
||||
"💾 Save": "💾 保存",
|
||||
"No notes": "当前还没有备注内容",
|
||||
"Saving Notes...": "正在保存备注...",
|
||||
"Type your notes here":"在这里输入备注内容",
|
||||
"Notes": "备注",
|
||||
"Type": "类型",
|
||||
"Trained Words": "训练词",
|
||||
"BaseModel": "基础算法",
|
||||
"Details": "详情",
|
||||
"Download": "下载量",
|
||||
"Source": "来源",
|
||||
"Saving Preview...": "正在保存预览图...",
|
||||
"Saving Succeed":"保存成功",
|
||||
"Saving Failed":"保存失败",
|
||||
"No COMBO link": "沒有找到COMBO连接",
|
||||
"Reboot ComfyUI":"重启ComfyUI",
|
||||
"Are you sure you'd like to reboot the server?": "是否要重启ComfyUI?",
|
||||
// GroupMap
|
||||
"Groups Map (EasyUse)": "管理组 (EasyUse)",
|
||||
"Reboot ComfyUI (EasyUse)": "重启服务 (EasyUse)",
|
||||
"Always": "启用中",
|
||||
"Bypass": "已忽略",
|
||||
"Never": "已停用",
|
||||
@@ -12,9 +38,11 @@ const zhCN = {
|
||||
// Quick
|
||||
"Enable ALT+1~9 to paste nodes from nodes template (ComfyUI-Easy-Use)": "启用ALT1~9从节点模板粘贴到工作流 (ComfyUI-Easy-Use)",
|
||||
"Enable process bar in queue button (ComfyUI-Easy-Use)": "启用提示词队列进度显示条 (ComfyUI-Easy-Use)",
|
||||
"Enable ContextMenu Auto Nest Subdirectories (ComfyUI-Easy-Use)": "启用上下文菜单自动嵌套子目录 (ComfyUI-Easy-Use)"
|
||||
"Enable ContextMenu Auto Nest Subdirectories (ComfyUI-Easy-Use)": "启用上下文菜单自动嵌套子目录 (ComfyUI-Easy-Use)",
|
||||
"Too many thumbnails, have closed the display": "模型缩略图太多啦,为您关闭了显示"
|
||||
}
|
||||
|
||||
export const $t = (key) => {
|
||||
return locale === 'zh-CN' ? zhCN[key] : key
|
||||
const cn = zhCN[key]
|
||||
return locale === 'zh-CN' && cn ? cn : key
|
||||
}
|
||||
@@ -0,0 +1,683 @@
|
||||
import { $el, ComfyDialog } from "/scripts/ui.js";
|
||||
import { api } from "/scripts/api.js";
|
||||
import {formatTime} from './utils.js';
|
||||
import {$t} from "./i18n.js";
|
||||
import {toast} from "./toast.js";
|
||||
|
||||
class MetadataDialog extends ComfyDialog {
|
||||
constructor() {
|
||||
super();
|
||||
this.element.classList.add("easyuse-model-metadata");
|
||||
}
|
||||
show(metadata) {
|
||||
super.show(
|
||||
$el(
|
||||
"div",
|
||||
Object.keys(metadata).map((k) =>
|
||||
$el("div", [$el("label", { textContent: k }), $el("span", { textContent: metadata[k] })])
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class ModelInfoDialog extends ComfyDialog {
|
||||
constructor(name) {
|
||||
super();
|
||||
this.name = name;
|
||||
this.element.classList.add("easyuse-model-info");
|
||||
}
|
||||
|
||||
get customNotes() {
|
||||
return this.metadata["easyuse.notes"];
|
||||
}
|
||||
|
||||
set customNotes(v) {
|
||||
this.metadata["easyuse.notes"] = v;
|
||||
}
|
||||
|
||||
get hash() {
|
||||
return this.metadata["easyuse.sha256"];
|
||||
}
|
||||
|
||||
async show(type, value) {
|
||||
this.type = type;
|
||||
|
||||
const req = api.fetchApi("/easyuse/metadata/" + encodeURIComponent(`${type}/${value}`));
|
||||
this.info = $el("div", { style: { flex: "auto" } });
|
||||
// this.img = $el("img", { style: { display: "none" } });
|
||||
this.imgCurrent = 0
|
||||
this.imgList = $el("div.easyuse-preview-list",{
|
||||
style: { display: "none" }
|
||||
})
|
||||
this.imgWrapper = $el("div.easyuse-preview", [
|
||||
$el("div.easyuse-preview-group",[
|
||||
this.imgList
|
||||
]),
|
||||
]);
|
||||
this.main = $el("main", { style: { display: "flex" } }, [this.imgWrapper, this.info]);
|
||||
this.content = $el("div.easyuse-model-content", [
|
||||
$el("div.easyuse-model-header",[$el("h2", { textContent: this.name })])
|
||||
, this.main]);
|
||||
|
||||
const loading = $el("div", { textContent: "ℹ️ Loading...", parent: this.content });
|
||||
|
||||
super.show(this.content);
|
||||
|
||||
this.metadata = await (await req).json();
|
||||
this.viewMetadata.style.cursor = this.viewMetadata.style.opacity = "";
|
||||
this.viewMetadata.removeAttribute("disabled");
|
||||
|
||||
loading.remove();
|
||||
this.addInfo();
|
||||
}
|
||||
|
||||
createButtons() {
|
||||
const btns = super.createButtons();
|
||||
this.viewMetadata = $el("button", {
|
||||
type: "button",
|
||||
textContent: "View raw metadata",
|
||||
disabled: "disabled",
|
||||
style: {
|
||||
opacity: 0.5,
|
||||
cursor: "not-allowed",
|
||||
},
|
||||
onclick: (e) => {
|
||||
if (this.metadata) {
|
||||
new MetadataDialog().show(this.metadata);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
btns.unshift(this.viewMetadata);
|
||||
return btns;
|
||||
}
|
||||
|
||||
parseNote() {
|
||||
if (!this.customNotes) return [];
|
||||
|
||||
let notes = [];
|
||||
// Extract links from notes
|
||||
const r = new RegExp("(\\bhttps?:\\/\\/[^\\s]+)", "g");
|
||||
let end = 0;
|
||||
let m;
|
||||
do {
|
||||
m = r.exec(this.customNotes);
|
||||
let pos;
|
||||
let fin = 0;
|
||||
if (m) {
|
||||
pos = m.index;
|
||||
fin = m.index + m[0].length;
|
||||
} else {
|
||||
pos = this.customNotes.length;
|
||||
}
|
||||
|
||||
let pre = this.customNotes.substring(end, pos);
|
||||
if (pre) {
|
||||
pre = pre.replaceAll("\n", "<br>");
|
||||
notes.push(
|
||||
$el("span", {
|
||||
innerHTML: pre,
|
||||
})
|
||||
);
|
||||
}
|
||||
if (m) {
|
||||
notes.push(
|
||||
$el("a", {
|
||||
href: m[0],
|
||||
textContent: m[0],
|
||||
target: "_blank",
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
end = fin;
|
||||
} while (m);
|
||||
return notes;
|
||||
}
|
||||
|
||||
addInfoEntry(name, value) {
|
||||
return $el(
|
||||
"p",
|
||||
{
|
||||
parent: this.info,
|
||||
},
|
||||
[
|
||||
typeof name === "string" ? $el("label", { textContent: name + ": " }) : name,
|
||||
typeof value === "string" ? $el("span", { textContent: value }) : value,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
async getCivitaiDetails() {
|
||||
const req = await fetch("https://civitai.com/api/v1/model-versions/by-hash/" + this.hash);
|
||||
if (req.status === 200) {
|
||||
return await req.json();
|
||||
} else if (req.status === 404) {
|
||||
throw new Error("Model not found");
|
||||
} else {
|
||||
throw new Error(`Error loading info (${req.status}) ${req.statusText}`);
|
||||
}
|
||||
}
|
||||
|
||||
addCivitaiInfo() {
|
||||
const promise = this.getCivitaiDetails();
|
||||
const content = $el("span", { textContent: "ℹ️ Loading..." });
|
||||
|
||||
this.addInfoEntry(
|
||||
$el("label", [
|
||||
$el("img", {
|
||||
style: {
|
||||
width: "18px",
|
||||
position: "relative",
|
||||
top: "3px",
|
||||
margin: "0 5px 0 0",
|
||||
},
|
||||
src: "https://civitai.com/favicon.ico",
|
||||
}),
|
||||
$el("span", { textContent: "Civitai: " }),
|
||||
]),
|
||||
content
|
||||
);
|
||||
|
||||
return promise
|
||||
.then((info) => {
|
||||
this.imgWrapper.style.display = 'block'
|
||||
// 变更标题信息
|
||||
let header = this.element.querySelector('.easyuse-model-header')
|
||||
if(header){
|
||||
header.replaceChildren(
|
||||
$el("h2", { textContent: this.name }),
|
||||
$el("div.easyuse-model-header-remark",[
|
||||
$el("h5", { textContent: $t("Updated At:") + formatTime(new Date(info.updatedAt),'yyyy/MM/dd')}),
|
||||
$el("h5", { textContent: $t("Created At:") + formatTime(new Date(info.updatedAt),'yyyy/MM/dd')}),
|
||||
])
|
||||
)
|
||||
}
|
||||
// 替换内容
|
||||
let textarea = null
|
||||
let notes = this.parseNote.call(this)
|
||||
let editText = $t("✏️ Edit")
|
||||
console.log(notes)
|
||||
let textarea_div = $el("div.easyuse-model-detail-textarea",[
|
||||
$el("p",notes?.length>0 ? notes : {textContent:$t('No notes')}),
|
||||
])
|
||||
if(!notes || notes.length == 0) textarea_div.classList.add('empty')
|
||||
else textarea_div.classList.remove('empty')
|
||||
this.info.replaceChildren(
|
||||
$el("div.easyuse-model-detail",[
|
||||
$el("div.easyuse-model-detail-head.flex-b",[
|
||||
$el('span',$t("Notes")),
|
||||
$el("a", {
|
||||
textContent: editText,
|
||||
href: "#",
|
||||
style: {
|
||||
fontSize: "12px",
|
||||
float: "right",
|
||||
color: "var(--warning-color)",
|
||||
textDecoration: "none",
|
||||
},
|
||||
onclick: async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (textarea) {
|
||||
if(textarea.value != this.customNotes){
|
||||
toast.showLoading($t('Saving Notes...'))
|
||||
this.customNotes = textarea.value;
|
||||
const resp = await api.fetchApi(
|
||||
"/easyuse/metadata/notes/" + encodeURIComponent(`${this.type}/${this.name}`),
|
||||
{
|
||||
method: "POST",
|
||||
body: this.customNotes,
|
||||
}
|
||||
);
|
||||
toast.hideLoading()
|
||||
if (resp.status !== 200) {
|
||||
toast.error($t('Saving Failed'))
|
||||
console.error(resp);
|
||||
alert(`Error saving notes (${resp.status}) ${resp.statusText}`);
|
||||
return;
|
||||
}
|
||||
toast.success($t('Saving Succeed'))
|
||||
notes = this.parseNote.call(this)
|
||||
console.log(notes)
|
||||
textarea_div.replaceChildren($el("p",notes?.length>0 ? notes : {textContent:$t('No notes')}));
|
||||
if(textarea.value) textarea_div.classList.remove('empty')
|
||||
else textarea_div.classList.add('empty')
|
||||
}else {
|
||||
textarea_div.replaceChildren($el("p",{textContent:$t('No notes')}));
|
||||
textarea_div.classList.add('empty')
|
||||
}
|
||||
e.target.textContent = editText;
|
||||
textarea.remove();
|
||||
textarea = null;
|
||||
|
||||
} else {
|
||||
e.target.textContent = "💾 Save";
|
||||
textarea = $el("textarea", {
|
||||
placeholder: $t("Type your notes here"),
|
||||
style: {
|
||||
width: "100%",
|
||||
minWidth: "200px",
|
||||
minHeight: "50px",
|
||||
height:"100px"
|
||||
},
|
||||
textContent: this.customNotes,
|
||||
});
|
||||
textarea_div.replaceChildren(textarea);
|
||||
textarea.focus()
|
||||
}
|
||||
}
|
||||
})
|
||||
]),
|
||||
textarea_div
|
||||
]),
|
||||
$el("div.easyuse-model-detail",[
|
||||
$el("div.easyuse-model-detail-head",{textContent:$t("Details")}),
|
||||
$el("div.easyuse-model-detail-body",[
|
||||
$el("div.easyuse-model-detail-item",[
|
||||
$el("div.easyuse-model-detail-item-label",{textContent:$t("Type")}),
|
||||
$el("div.easyuse-model-detail-item-value",{textContent:info.model.type}),
|
||||
]),
|
||||
$el("div.easyuse-model-detail-item",[
|
||||
$el("div.easyuse-model-detail-item-label",{textContent:$t("BaseModel")}),
|
||||
$el("div.easyuse-model-detail-item-value",{textContent:info.baseModel}),
|
||||
]),
|
||||
$el("div.easyuse-model-detail-item",[
|
||||
$el("div.easyuse-model-detail-item-label",{textContent:$t("Download")}),
|
||||
$el("div.easyuse-model-detail-item-value",{textContent:info.stats?.downloadCount || 0}),
|
||||
]),
|
||||
$el("div.easyuse-model-detail-item",[
|
||||
$el("div.easyuse-model-detail-item-label",{textContent:$t("Trained Words")}),
|
||||
$el("div.easyuse-model-detail-item-value",{textContent:info?.trainedWords.join(',') || '-'}),
|
||||
]),
|
||||
$el("div.easyuse-model-detail-item",[
|
||||
$el("div.easyuse-model-detail-item-label",{textContent:$t("Source")}),
|
||||
$el("div.easyuse-model-detail-item-value",[
|
||||
$el("label", [
|
||||
$el("img", {
|
||||
style: {
|
||||
width: "14px",
|
||||
position: "relative",
|
||||
top: "3px",
|
||||
margin: "0 5px 0 0",
|
||||
},
|
||||
src: "https://civitai.com/favicon.ico",
|
||||
}),
|
||||
$el("a", {
|
||||
href: "https://civitai.com/models/" + info.modelId,
|
||||
textContent: "View " + info.model.name,
|
||||
target: "_blank",
|
||||
})
|
||||
])
|
||||
]),
|
||||
])
|
||||
]),
|
||||
])
|
||||
);
|
||||
|
||||
if (info.images?.length) {
|
||||
this.imgCurrent = 0
|
||||
this.isSaving = false
|
||||
info.images.map(cate=>
|
||||
cate.url &&
|
||||
this.imgList.appendChild(
|
||||
$el('div.easyuse-preview-slide',[
|
||||
$el('div.easyuse-preview-slide-content',[
|
||||
$el('img',{src:(cate.url)}),
|
||||
$el("div.save", {
|
||||
textContent: "Save as preview",
|
||||
onclick: async () => {
|
||||
if(this.isSaving) return
|
||||
this.isSaving = true
|
||||
toast.showLoading($t('Saving Preview...'))
|
||||
// Convert the preview to a blob
|
||||
const blob = await (await fetch(cate.url)).blob();
|
||||
|
||||
// Store it in temp
|
||||
const name = "temp_preview." + new URL(cate.url).pathname.split(".")[1];
|
||||
const body = new FormData();
|
||||
body.append("image", new File([blob], name));
|
||||
body.append("overwrite", "true");
|
||||
body.append("type", "temp");
|
||||
|
||||
const resp = await api.fetchApi("/upload/image", {
|
||||
method: "POST",
|
||||
body,
|
||||
});
|
||||
|
||||
if (resp.status !== 200) {
|
||||
this.isSaving = false
|
||||
toast.error($t('Saving Failed'))
|
||||
toast.hideLoading()
|
||||
console.error(resp);
|
||||
alert(`Error saving preview (${req.status}) ${req.statusText}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Use as preview
|
||||
await api.fetchApi("/easyuse/save/" + encodeURIComponent(`${this.type}/${this.name}`), {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
filename: name,
|
||||
type: "temp",
|
||||
}),
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
},
|
||||
}).then(_=>{
|
||||
toast.success($t('Saving Succeed'))
|
||||
toast.hideLoading()
|
||||
});
|
||||
this.isSaving = false
|
||||
app.refreshComboInNodes();
|
||||
},
|
||||
})
|
||||
])
|
||||
])
|
||||
)
|
||||
)
|
||||
let _this = this
|
||||
this.imgDistance = (-660 * this.imgCurrent).toString()
|
||||
this.imgList.style.display = ''
|
||||
this.imgList.style.transform = 'translate3d(' + this.imgDistance +'px, 0px, 0px)'
|
||||
this.slides = this.imgList.querySelectorAll('.easyuse-preview-slide')
|
||||
// 添加按钮
|
||||
this.slideLeftButton = $el("button.left",{
|
||||
parent: this.imgWrapper,
|
||||
style:{
|
||||
display:info.images.length <= 2 ? 'none' : 'block'
|
||||
},
|
||||
innerHTML:`<svg viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg" width="16" height="16" style="transform: rotate(90deg);"><path d="M3.13523 6.15803C3.3241 5.95657 3.64052 5.94637 3.84197 6.13523L7.5 9.56464L11.158 6.13523C11.3595 5.94637 11.6759 5.95657 11.8648 6.15803C12.0536 6.35949 12.0434 6.67591 11.842 6.86477L7.84197 10.6148C7.64964 10.7951 7.35036 10.7951 7.15803 10.6148L3.15803 6.86477C2.95657 6.67591 2.94637 6.35949 3.13523 6.15803Z" fill="currentColor" fill-rule="evenodd" clip-rule="evenodd"></path></svg>`,
|
||||
onclick: ()=>{
|
||||
if(info.images.length <= 2) return
|
||||
_this.imgList.classList.remove("no-transition")
|
||||
if(_this.imgCurrent == 0){
|
||||
_this.imgCurrent = (info.images.length/2)-1
|
||||
this.slides[this.slides.length-1].style.transform = 'translate3d(' + (-660 * (this.imgCurrent+1)).toString()+'px, 0px, 0px)'
|
||||
this.slides[this.slides.length-2].style.transform = 'translate3d(' + (-660 * (this.imgCurrent+1)).toString()+'px, 0px, 0px)'
|
||||
_this.imgList.style.transform = 'translate3d(660px, 0px, 0px)'
|
||||
setTimeout(_=>{
|
||||
this.slides[this.slides.length-1].style.transform = 'translate3d(0px, 0px, 0px)'
|
||||
this.slides[this.slides.length-2].style.transform = 'translate3d(0px, 0px, 0px)'
|
||||
_this.imgDistance = (-660 * this.imgCurrent).toString()
|
||||
_this.imgList.style.transform = 'translate3d(' + _this.imgDistance +'px, 0px, 0px)'
|
||||
_this.imgList.classList.add("no-transition")
|
||||
},500)
|
||||
}
|
||||
else {
|
||||
_this.imgCurrent = _this.imgCurrent-1
|
||||
_this.imgDistance = (-660 * this.imgCurrent).toString()
|
||||
_this.imgList.style.transform = 'translate3d(' + _this.imgDistance +'px, 0px, 0px)'
|
||||
}
|
||||
}
|
||||
})
|
||||
this.slideRightButton = $el("button.right",{
|
||||
parent: this.imgWrapper,
|
||||
style:{
|
||||
display:info.images.length <= 2 ? 'none' : 'block'
|
||||
},
|
||||
innerHTML:`<svg viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg" width="16" height="16" style="transform: rotate(-90deg);"><path d="M3.13523 6.15803C3.3241 5.95657 3.64052 5.94637 3.84197 6.13523L7.5 9.56464L11.158 6.13523C11.3595 5.94637 11.6759 5.95657 11.8648 6.15803C12.0536 6.35949 12.0434 6.67591 11.842 6.86477L7.84197 10.6148C7.64964 10.7951 7.35036 10.7951 7.15803 10.6148L3.15803 6.86477C2.95657 6.67591 2.94637 6.35949 3.13523 6.15803Z" fill="currentColor" fill-rule="evenodd" clip-rule="evenodd"></path></svg>`,
|
||||
onclick: ()=>{
|
||||
if(info.images.length <= 2) return
|
||||
_this.imgList.classList.remove("no-transition")
|
||||
|
||||
if( _this.imgCurrent >= (info.images.length/2)-1){
|
||||
_this.imgCurrent = 0
|
||||
const max = info.images.length/2
|
||||
this.slides[0].style.transform = 'translate3d(' + (660 * max).toString()+'px, 0px, 0px)'
|
||||
this.slides[1].style.transform = 'translate3d(' + (660 * max).toString()+'px, 0px, 0px)'
|
||||
_this.imgList.style.transform = 'translate3d(' + (-660 * max).toString()+'px, 0px, 0px)'
|
||||
setTimeout(_=>{
|
||||
this.slides[0].style.transform = 'translate3d(0px, 0px, 0px)'
|
||||
this.slides[1].style.transform = 'translate3d(0px, 0px, 0px)'
|
||||
_this.imgDistance = (-660 * this.imgCurrent).toString()
|
||||
_this.imgList.style.transform = 'translate3d(' + _this.imgDistance +'px, 0px, 0px)'
|
||||
_this.imgList.classList.add("no-transition")
|
||||
},500)
|
||||
}
|
||||
else {
|
||||
_this.imgCurrent = _this.imgCurrent+1
|
||||
_this.imgDistance = (-660 * this.imgCurrent).toString()
|
||||
_this.imgList.style.transform = 'translate3d(' + _this.imgDistance +'px, 0px, 0px)'
|
||||
}
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
if(info.description){
|
||||
$el("div", {
|
||||
parent: this.content,
|
||||
innerHTML: info.description,
|
||||
style: {
|
||||
marginTop: "10px",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return info;
|
||||
})
|
||||
.catch((err) => {
|
||||
this.imgWrapper.style.display = 'none'
|
||||
content.textContent = "⚠️ " + err.message;
|
||||
})
|
||||
.finally(_=>{
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class CheckpointInfoDialog extends ModelInfoDialog {
|
||||
async addInfo() {
|
||||
// super.addInfo();
|
||||
await this.addCivitaiInfo();
|
||||
}
|
||||
}
|
||||
|
||||
const MAX_TAGS = 500
|
||||
export class LoraInfoDialog extends ModelInfoDialog {
|
||||
getTagFrequency() {
|
||||
if (!this.metadata.ss_tag_frequency) return [];
|
||||
|
||||
const datasets = JSON.parse(this.metadata.ss_tag_frequency);
|
||||
const tags = {};
|
||||
for (const setName in datasets) {
|
||||
const set = datasets[setName];
|
||||
for (const t in set) {
|
||||
if (t in tags) {
|
||||
tags[t] += set[t];
|
||||
} else {
|
||||
tags[t] = set[t];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Object.entries(tags).sort((a, b) => b[1] - a[1]);
|
||||
}
|
||||
|
||||
getResolutions() {
|
||||
let res = [];
|
||||
if (this.metadata.ss_bucket_info) {
|
||||
const parsed = JSON.parse(this.metadata.ss_bucket_info);
|
||||
if (parsed?.buckets) {
|
||||
for (const { resolution, count } of Object.values(parsed.buckets)) {
|
||||
res.push([count, `${resolution.join("x")} * ${count}`]);
|
||||
}
|
||||
}
|
||||
}
|
||||
res = res.sort((a, b) => b[0] - a[0]).map((a) => a[1]);
|
||||
let r = this.metadata.ss_resolution;
|
||||
if (r) {
|
||||
const s = r.split(",");
|
||||
const w = s[0].replace("(", "");
|
||||
const h = s[1].replace(")", "");
|
||||
res.push(`${w.trim()}x${h.trim()} (Base res)`);
|
||||
} else if ((r = this.metadata["modelspec.resolution"])) {
|
||||
res.push(r + " (Base res");
|
||||
}
|
||||
if (!res.length) {
|
||||
res.push("⚠️ Unknown");
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
getTagList(tags) {
|
||||
return tags.map((t) =>
|
||||
$el(
|
||||
"li.easyuse-model-tag",
|
||||
{
|
||||
dataset: {
|
||||
tag: t[0],
|
||||
},
|
||||
$: (el) => {
|
||||
el.onclick = () => {
|
||||
el.classList.toggle("easyuse-model-tag--selected");
|
||||
};
|
||||
},
|
||||
},
|
||||
[
|
||||
$el("p", {
|
||||
textContent: t[0],
|
||||
}),
|
||||
$el("span", {
|
||||
textContent: t[1],
|
||||
}),
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
addTags() {
|
||||
let tags = this.getTagFrequency();
|
||||
let hasMore;
|
||||
if (tags?.length) {
|
||||
const c = tags.length;
|
||||
let list;
|
||||
if (c > MAX_TAGS) {
|
||||
tags = tags.slice(0, MAX_TAGS);
|
||||
hasMore = $el("p", [
|
||||
$el("span", { textContent: `⚠️ Only showing first ${MAX_TAGS} tags ` }),
|
||||
$el("a", {
|
||||
href: "#",
|
||||
textContent: `Show all ${c}`,
|
||||
onclick: () => {
|
||||
list.replaceChildren(...this.getTagList(this.getTagFrequency()));
|
||||
hasMore.remove();
|
||||
},
|
||||
}),
|
||||
]);
|
||||
}
|
||||
list = $el("ol.easyuse-model-tags-list", this.getTagList(tags));
|
||||
this.tags = $el("div", [list]);
|
||||
} else {
|
||||
this.tags = $el("p", { textContent: "⚠️ No tag frequency metadata found" });
|
||||
}
|
||||
|
||||
this.content.append(this.tags);
|
||||
|
||||
if (hasMore) {
|
||||
this.content.append(hasMore);
|
||||
}
|
||||
}
|
||||
|
||||
async addInfo() {
|
||||
// this.addInfoEntry("Name", this.metadata.ss_output_name || "⚠️ Unknown");
|
||||
// this.addInfoEntry("Base Model", this.metadata.ss_sd_model_name || "⚠️ Unknown");
|
||||
// this.addInfoEntry("Clip Skip", this.metadata.ss_clip_skip || "⚠️ Unknown");
|
||||
//
|
||||
// this.addInfoEntry(
|
||||
// "Resolution",
|
||||
// $el(
|
||||
// "select",
|
||||
// this.getResolutions().map((r) => $el("option", { textContent: r }))
|
||||
// )
|
||||
// );
|
||||
|
||||
// super.addInfo();
|
||||
const p = this.addCivitaiInfo();
|
||||
this.addTags();
|
||||
|
||||
const info = await p;
|
||||
if (info) {
|
||||
// $el(
|
||||
// "p",
|
||||
// {
|
||||
// parent: this.content,
|
||||
// textContent: "Trained Words: ",
|
||||
// },
|
||||
// [
|
||||
// $el("pre", {
|
||||
// textContent: info.trainedWords.join(", "),
|
||||
// style: {
|
||||
// whiteSpace: "pre-wrap",
|
||||
// margin: "10px 0",
|
||||
// background: "#222",
|
||||
// padding: "5px",
|
||||
// borderRadius: "5px",
|
||||
// maxHeight: "250px",
|
||||
// overflow: "auto",
|
||||
// },
|
||||
// }),
|
||||
// ]
|
||||
// );
|
||||
$el("div", {
|
||||
parent: this.content,
|
||||
innerHTML: info.description,
|
||||
style: {
|
||||
maxHeight: "250px",
|
||||
overflow: "auto",
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
createButtons() {
|
||||
const btns = super.createButtons();
|
||||
|
||||
function copyTags(e, tags) {
|
||||
const textarea = $el("textarea", {
|
||||
parent: document.body,
|
||||
style: {
|
||||
position: "fixed",
|
||||
},
|
||||
textContent: tags.map((el) => el.dataset.tag).join(", "),
|
||||
});
|
||||
textarea.select();
|
||||
try {
|
||||
document.execCommand("copy");
|
||||
if (!e.target.dataset.text) {
|
||||
e.target.dataset.text = e.target.textContent;
|
||||
}
|
||||
e.target.textContent = "Copied " + tags.length + " tags";
|
||||
setTimeout(() => {
|
||||
e.target.textContent = e.target.dataset.text;
|
||||
}, 1000);
|
||||
} catch (ex) {
|
||||
prompt("Copy to clipboard: Ctrl+C, Enter", text);
|
||||
} finally {
|
||||
document.body.removeChild(textarea);
|
||||
}
|
||||
}
|
||||
|
||||
btns.unshift(
|
||||
$el("button", {
|
||||
type: "button",
|
||||
textContent: "Copy Selected",
|
||||
onclick: (e) => {
|
||||
copyTags(e, [...this.tags.querySelectorAll(".easyuse-model-tag--selected")]);
|
||||
},
|
||||
}),
|
||||
$el("button", {
|
||||
type: "button",
|
||||
textContent: "Copy All",
|
||||
onclick: (e) => {
|
||||
copyTags(e, [...this.tags.querySelectorAll(".easyuse-model-tag")]);
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
return btns;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
import {sleep} from "./utils.js";
|
||||
|
||||
class Toast{
|
||||
|
||||
constructor() {
|
||||
this.info_icon = `<svg focusable="false" data-icon="info-circle" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm32 664c0 4.4-3.6 8-8 8h-48c-4.4 0-8-3.6-8-8V456c0-4.4 3.6-8 8-8h48c4.4 0 8 3.6 8 8v272zm-32-344a48.01 48.01 0 010-96 48.01 48.01 0 010 96z"></path></svg>`
|
||||
this.success_icon = `<svg focusable="false" data-icon="check-circle" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm193.5 301.7l-210.6 292a31.8 31.8 0 01-51.7 0L318.5 484.9c-3.8-5.3 0-12.7 6.5-12.7h46.9c10.2 0 19.9 4.9 25.9 13.3l71.2 98.8 157.2-218c6-8.3 15.6-13.3 25.9-13.3H699c6.5 0 10.3 7.4 6.5 12.7z"></path></svg>`
|
||||
this.error_icon = `<svg focusable="false" data-icon="close-circle" width="1em" height="1em" fill="currentColor" aria-hidden="true" fill-rule="evenodd" viewBox="64 64 896 896"><path d="M512 64c247.4 0 448 200.6 448 448S759.4 960 512 960 64 759.4 64 512 264.6 64 512 64zm127.98 274.82h-.04l-.08.06L512 466.75 384.14 338.88c-.04-.05-.06-.06-.08-.06a.12.12 0 00-.07 0c-.03 0-.05.01-.09.05l-45.02 45.02a.2.2 0 00-.05.09.12.12 0 000 .07v.02a.27.27 0 00.06.06L466.75 512 338.88 639.86c-.05.04-.06.06-.06.08a.12.12 0 000 .07c0 .03.01.05.05.09l45.02 45.02a.2.2 0 00.09.05.12.12 0 00.07 0c.02 0 .04-.01.08-.05L512 557.25l127.86 127.87c.04.04.06.05.08.05a.12.12 0 00.07 0c.03 0 .05-.01.09-.05l45.02-45.02a.2.2 0 00.05-.09.12.12 0 000-.07v-.02a.27.27 0 00-.05-.06L557.25 512l127.87-127.86c.04-.04.05-.06.05-.08a.12.12 0 000-.07c0-.03-.01-.05-.05-.09l-45.02-45.02a.2.2 0 00-.09-.05.12.12 0 00-.07 0z"></path></svg>`
|
||||
this.warn_icon = `<svg focusable="false" data-icon="exclamation-circle" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm-32 232c0-4.4 3.6-8 8-8h48c4.4 0 8 3.6 8 8v272c0 4.4-3.6 8-8 8h-48c-4.4 0-8-3.6-8-8V296zm32 440a48.01 48.01 0 010-96 48.01 48.01 0 010 96z"></path></svg>`
|
||||
this.loading_icon = `<svg focusable="false" data-icon="loading" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="0 0 1024 1024"><path d="M988 548c-19.9 0-36-16.1-36-36 0-59.4-11.6-117-34.6-171.3a440.45 440.45 0 00-94.3-139.9 437.71 437.71 0 00-139.9-94.3C629 83.6 571.4 72 512 72c-19.9 0-36-16.1-36-36s16.1-36 36-36c69.1 0 136.2 13.5 199.3 40.3C772.3 66 827 103 874 150c47 47 83.9 101.8 109.7 162.7 26.7 63.1 40.2 130.2 40.2 199.3.1 19.9-16 36-35.9 36z"></path></svg>`
|
||||
}
|
||||
|
||||
async showToast(data){
|
||||
let container = document.querySelector(".easyuse-toast-container");
|
||||
if (!container) {
|
||||
container = document.createElement("div");
|
||||
container.classList.add("easyuse-toast-container");
|
||||
document.body.appendChild(container);
|
||||
}
|
||||
await this.hideToast(data.id);
|
||||
const toastContainer = document.createElement("div");
|
||||
const content = document.createElement("span");
|
||||
content.innerHTML = data.content;
|
||||
toastContainer.appendChild(content);
|
||||
for (let a = 0; a < (data.actions || []).length; a++) {
|
||||
const action = data.actions[a];
|
||||
if (a > 0) {
|
||||
const sep = document.createElement("span");
|
||||
sep.innerHTML = " | ";
|
||||
toastContainer.appendChild(sep);
|
||||
}
|
||||
const actionEl = document.createElement("a");
|
||||
actionEl.innerText = action.label;
|
||||
if (action.href) {
|
||||
actionEl.target = "_blank";
|
||||
actionEl.href = action.href;
|
||||
}
|
||||
if (action.callback) {
|
||||
actionEl.onclick = (e) => {
|
||||
return action.callback(e);
|
||||
};
|
||||
}
|
||||
toastContainer.appendChild(actionEl);
|
||||
}
|
||||
const animContainer = document.createElement("div");
|
||||
animContainer.setAttribute("toast-id", data.id);
|
||||
animContainer.appendChild(toastContainer);
|
||||
container.appendChild(animContainer);
|
||||
await sleep(64);
|
||||
animContainer.style.marginTop = `-${animContainer.offsetHeight}px`;
|
||||
await sleep(64);
|
||||
animContainer.classList.add("-show");
|
||||
if (data.duration) {
|
||||
await sleep(data.duration);
|
||||
this.hideToast(data.id);
|
||||
}
|
||||
}
|
||||
async hideToast(id) {
|
||||
const msg = document.querySelector(`.easyuse-toast-container > [toast-id="${id}"]`);
|
||||
if (msg === null || msg === void 0 ? void 0 : msg.classList.contains("-show")) {
|
||||
msg.classList.remove("-show");
|
||||
await sleep(750);
|
||||
}
|
||||
msg && msg.remove();
|
||||
}
|
||||
async clearAllMessages() {
|
||||
let container = document.querySelector(".easyuse-toast-container");
|
||||
container && (container.innerHTML = "");
|
||||
}
|
||||
|
||||
async info(content, duration = 3000, actions = []) {
|
||||
this.showToast({
|
||||
id: `toast-info`,
|
||||
content: `${this.info_icon} ${content}`,
|
||||
duration,
|
||||
actions
|
||||
});
|
||||
}
|
||||
async success(content, duration = 3000, actions = []) {
|
||||
this.showToast({
|
||||
id: `toast-success`,
|
||||
content: `${this.success_icon} ${content}`,
|
||||
duration,
|
||||
actions
|
||||
});
|
||||
}
|
||||
async error(content, duration = 3000, actions = []) {
|
||||
this.showToast({
|
||||
id: `toast-error`,
|
||||
content: `${this.error_icon} ${content}`,
|
||||
duration,
|
||||
actions
|
||||
});
|
||||
}
|
||||
async warn(content, duration = 3000, actions = []) {
|
||||
this.showToast({
|
||||
id: `toast-warn`,
|
||||
content: `${this.warn_icon} ${content}`,
|
||||
duration,
|
||||
actions
|
||||
});
|
||||
}
|
||||
async showLoading(content, duration = 0, actions = []) {
|
||||
this.showToast({
|
||||
id: `toast-loading`,
|
||||
content: `${this.loading_icon} ${content}`,
|
||||
duration,
|
||||
actions
|
||||
});
|
||||
}
|
||||
|
||||
async hideLoading() {
|
||||
this.hideToast("toast-loading");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export const toast = new Toast();
|
||||
+66
-1
@@ -1,3 +1,10 @@
|
||||
export function sleep(ms = 100, value) {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve(value);
|
||||
}, ms);
|
||||
});
|
||||
}
|
||||
export function addPreconnect(href, crossorigin=false){
|
||||
const preconnect = document.createElement("link");
|
||||
preconnect.rel = 'preconnect'
|
||||
@@ -5,7 +12,6 @@ export function addPreconnect(href, crossorigin=false){
|
||||
if(crossorigin) preconnect.crossorigin = ''
|
||||
document.head.appendChild(preconnect);
|
||||
}
|
||||
|
||||
export function addCss(href, base=true) {
|
||||
const link = document.createElement("link");
|
||||
link.rel = "stylesheet";
|
||||
@@ -45,4 +51,63 @@ export function spliceExtension(fileName){
|
||||
}
|
||||
export function getExtension(fileName){
|
||||
return fileName.substring(fileName.lastIndexOf('.') + 1)
|
||||
}
|
||||
|
||||
export function formatTime(time, format) {
|
||||
time = typeof (time) === "number" ? time : (time instanceof Date ? time.getTime() : parseInt(time));
|
||||
if (isNaN(time)) return null;
|
||||
if (typeof (format) !== 'string' || !format) format = 'yyyy-MM-dd hh:mm:ss';
|
||||
let _time = new Date(time);
|
||||
time = _time.toString().split(/[\s\:]/g).slice(0, -2);
|
||||
time[1] = ['01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12'][_time.getMonth()];
|
||||
let _mapping = {
|
||||
MM: 1,
|
||||
dd: 2,
|
||||
yyyy: 3,
|
||||
hh: 4,
|
||||
mm: 5,
|
||||
ss: 6
|
||||
};
|
||||
return format.replace(/([Mmdhs]|y{2})\1/g, (key) => time[_mapping[key]]);
|
||||
}
|
||||
|
||||
|
||||
let origProps = {};
|
||||
export const findWidgetByName = (node, name) => node.widgets.find((w) => w.name === name);
|
||||
|
||||
export const doesInputWithNameExist = (node, name) => node.inputs ? node.inputs.some((input) => input.name === name) : false;
|
||||
|
||||
export function updateNodeHeight(node) {node.setSize([node.size[0], node.computeSize()[1]]);}
|
||||
|
||||
export function toggleWidget(node, widget, show = false, suffix = "") {
|
||||
if (!widget || doesInputWithNameExist(node, widget.name)) return;
|
||||
if (!origProps[widget.name]) {
|
||||
origProps[widget.name] = { origType: widget.type, origComputeSize: widget.computeSize };
|
||||
}
|
||||
const origSize = node.size;
|
||||
|
||||
widget.type = show ? origProps[widget.name].origType : "easyHidden" + suffix;
|
||||
widget.computeSize = show ? origProps[widget.name].origComputeSize : () => [0, -4];
|
||||
|
||||
widget.linkedWidgets?.forEach(w => toggleWidget(node, w, ":" + widget.name, show));
|
||||
|
||||
const height = show ? Math.max(node.computeSize()[1], origSize[1]) : node.size[1];
|
||||
node.setSize([node.size[0], height]);
|
||||
}
|
||||
|
||||
export function isLocalNetwork(ip) {
|
||||
const localNetworkRanges = [
|
||||
'192.168.',
|
||||
'10.',
|
||||
'127.',
|
||||
/^172\.((1[6-9]|2[0-9]|3[0-1])\.)/
|
||||
];
|
||||
|
||||
return localNetworkRanges.some(range => {
|
||||
if (typeof range === 'string') {
|
||||
return ip.startsWith(range);
|
||||
} else {
|
||||
return range.test(ip);
|
||||
}
|
||||
});
|
||||
}
|
||||
+33
-2
@@ -1,9 +1,26 @@
|
||||
import { api } from "/scripts/api.js";
|
||||
import { app } from "/scripts/app.js";
|
||||
import {deepEqual,addCss} from "../common/utils.js";
|
||||
import {deepEqual, addCss, isLocalNetwork} from "../common/utils.js";
|
||||
import {$t} from '../common/i18n.js';
|
||||
import {toast} from "../common/toast.js";
|
||||
|
||||
addCss('css/index.css')
|
||||
|
||||
api.addEventListener("easyuse-toast",event=>{
|
||||
const content = event.detail.content
|
||||
const type = event.detail.type
|
||||
const duration = event.detail.duration
|
||||
if(!type){
|
||||
toast.info(content, duration)
|
||||
}
|
||||
else{
|
||||
toast.showToast({
|
||||
id: `toast-${type}`,
|
||||
content: `${toast[type+"_icon"]} ${content}`,
|
||||
duration: duration || 3000,
|
||||
})
|
||||
}
|
||||
})
|
||||
app.registerExtension({
|
||||
name: "comfy.easyUse",
|
||||
init() {
|
||||
@@ -18,6 +35,7 @@ app.registerExtension({
|
||||
emptyImg.src = "data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs=";
|
||||
|
||||
options.push(null,
|
||||
// Groups Map
|
||||
{
|
||||
content: '📜 '+ $t('Groups Map (EasyUse)'),
|
||||
callback: async() => {
|
||||
@@ -250,8 +268,21 @@ app.registerExtension({
|
||||
}
|
||||
|
||||
}
|
||||
},
|
||||
}
|
||||
);
|
||||
// Only show the reboot option if the server is running on a local network 仅在本地或局域网环境可重启服务
|
||||
if(isLocalNetwork(window.location.host)){
|
||||
options.push(null,{
|
||||
content: '🔴 '+ $t('Reboot ComfyUI (EasyUse)'),
|
||||
callback: _ =>{
|
||||
if (confirm($t("Are you sure you'd like to reboot the server?"))){
|
||||
try {
|
||||
api.fetchApi("/easyuse/reboot");
|
||||
} catch (exception) {}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
return options;
|
||||
};
|
||||
},
|
||||
|
||||
@@ -3,9 +3,10 @@ import {api} from "/scripts/api.js";
|
||||
import {$el} from "/scripts/ui.js";
|
||||
import {$t} from "../common/i18n.js";
|
||||
import {getExtension, spliceExtension} from '../common/utils.js'
|
||||
import {toast} from "../common/toast.js";
|
||||
|
||||
const setting_id = "Comfy.EasyUse.MenuNestSub"
|
||||
let enableMenuNestSub = true
|
||||
let enableMenuNestSub = false
|
||||
let thumbnails = []
|
||||
|
||||
export function addMenuNestSubSetting(app) {
|
||||
@@ -20,7 +21,7 @@ export function addMenuNestSubSetting(app) {
|
||||
});
|
||||
}
|
||||
|
||||
const getEnableMenuNestSub = _ => app.ui.settings.getSettingValue(setting_id, true)
|
||||
const getEnableMenuNestSub = _ => app.ui.settings.getSettingValue(setting_id, enableMenuNestSub)
|
||||
|
||||
const Loaders = ['easy fullLoader','easy a1111Loader','easy comfyLoader']
|
||||
app.registerExtension({
|
||||
@@ -33,6 +34,9 @@ app.registerExtension({
|
||||
let data = await imgRes.json();
|
||||
thumbnails = data
|
||||
}
|
||||
else if(getEnableMenuNestSub()){
|
||||
toast.error($t("Too many thumbnails, have closed the display"))
|
||||
}
|
||||
const existingContextMenu = LiteGraph.ContextMenu;
|
||||
LiteGraph.ContextMenu = function(values,options){
|
||||
const threshold = 15;
|
||||
|
||||
@@ -1,37 +1,14 @@
|
||||
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 = {};
|
||||
import { findWidgetByName, toggleWidget, updateNodeHeight} from "../common/utils.js";
|
||||
|
||||
const seedNodes = ["easy seed", "easy latentNoisy", "easy wildcards", "easy preSampling", "easy preSamplingAdvanced", "easy preSamplingNoiseIn", "easy preSamplingSdTurbo", "easy preSamplingCascade", "easy preSamplingDynamicCFG", "easy preSamplingLayerDiffusion", "easy fullkSampler", "easy fullCascadeKSampler"]
|
||||
const loaderNodes = ["easy fullLoader", "easy a1111Loader", "easy comfyLoader"]
|
||||
const findWidgetByName = (node, name) => node.widgets.find((w) => w.name === name);
|
||||
|
||||
const doesInputWithNameExist = (node, name) => node.inputs ? node.inputs.some((input) => input.name === name) : false;
|
||||
|
||||
function updateNodeHeight(node) {node.setSize([node.size[0], node.computeSize()[1]]);}
|
||||
|
||||
function toggleWidget(node, widget, show = false, suffix = "") {
|
||||
if (!widget || doesInputWithNameExist(node, widget.name)) return;
|
||||
if (!origProps[widget.name]) {
|
||||
origProps[widget.name] = { origType: widget.type, origComputeSize: widget.computeSize };
|
||||
}
|
||||
const origSize = node.size;
|
||||
|
||||
widget.type = show ? origProps[widget.name].origType : "easyHidden" + suffix;
|
||||
widget.computeSize = show ? origProps[widget.name].origComputeSize : () => [0, -4];
|
||||
|
||||
widget.linkedWidgets?.forEach(w => toggleWidget(node, w, ":" + widget.name, show));
|
||||
|
||||
const height = show ? Math.max(node.computeSize()[1], origSize[1]) : node.size[1];
|
||||
node.setSize([node.size[0], height]);
|
||||
}
|
||||
|
||||
function toggleInput(node, name, show = false) {
|
||||
if(!show){
|
||||
}
|
||||
}
|
||||
|
||||
function widgetLogic(node, widget) {
|
||||
if (widget.name === 'lora_name') {
|
||||
@@ -79,7 +56,7 @@ function widgetLogic(node, widget) {
|
||||
}else {
|
||||
toggleWidget(node, findWidgetByName(node, 'link_id'))
|
||||
}
|
||||
if (widget.value === 'Hide' || widget.value === 'Preview' || widget.value === 'Sender') {
|
||||
if (widget.value === 'Hide' || widget.value === 'Preview' || widget.value == 'PreviewChooser' || widget.value === 'Sender') {
|
||||
toggleWidget(node, findWidgetByName(node, 'save_prefix'))
|
||||
toggleWidget(node, findWidgetByName(node, 'output_path'))
|
||||
toggleWidget(node, findWidgetByName(node, 'embed_workflow'))
|
||||
@@ -270,17 +247,66 @@ function widgetLogic(node, widget) {
|
||||
if (widget.name === 'num_embeds') {
|
||||
let number_to_show = widget.value + 1
|
||||
for (let i = 0; i < number_to_show; i++) {
|
||||
toggleInput(node, 'image'+i, true)
|
||||
toggleInput(node, 'mask'+i, true)
|
||||
toggleWidget(node, findWidgetByName(node, 'weight'+i), true)
|
||||
}
|
||||
for (let i = number_to_show; i < 6; i++) {
|
||||
toggleInput(node, 'image'+i)
|
||||
toggleInput(node, 'mask'+i)
|
||||
toggleWidget(node, findWidgetByName(node, 'weight'+i))
|
||||
}
|
||||
updateNodeHeight(node)
|
||||
}
|
||||
|
||||
if (widget.name === 'guider'){
|
||||
switch (widget.value){
|
||||
case 'Basic':
|
||||
toggleWidget(node, findWidgetByName(node, 'cfg'))
|
||||
toggleWidget(node, findWidgetByName(node, 'cfg_negative'))
|
||||
break
|
||||
case 'CFG':
|
||||
toggleWidget(node, findWidgetByName(node, 'cfg'),true)
|
||||
toggleWidget(node, findWidgetByName(node, 'cfg_negative'))
|
||||
break
|
||||
case 'IP2P+DualCFG':
|
||||
case 'DualCFG':
|
||||
toggleWidget(node, findWidgetByName(node, 'cfg'),true)
|
||||
toggleWidget(node, findWidgetByName(node, 'cfg_negative'), true)
|
||||
break
|
||||
|
||||
}
|
||||
updateNodeHeight(node)
|
||||
}
|
||||
|
||||
if (widget.name === 'scheduler'){
|
||||
if (['karrasADV','exponentialADV','polyExponential'].includes(widget.value)){
|
||||
toggleWidget(node, findWidgetByName(node, 'sigma_max'), true)
|
||||
toggleWidget(node, findWidgetByName(node, 'sigma_min'), true)
|
||||
toggleWidget(node, findWidgetByName(node, 'denoise'))
|
||||
toggleWidget(node, findWidgetByName(node, 'beta_d'))
|
||||
toggleWidget(node, findWidgetByName(node, 'beta_min'))
|
||||
toggleWidget(node, findWidgetByName(node, 'eps_s'))
|
||||
if(widget.value != 'exponentialADV'){
|
||||
toggleWidget(node, findWidgetByName(node, 'rho'), true)
|
||||
}else{
|
||||
toggleWidget(node, findWidgetByName(node, 'rho'))
|
||||
}
|
||||
}else if(widget.value == 'vp'){
|
||||
toggleWidget(node, findWidgetByName(node, 'sigma_max'))
|
||||
toggleWidget(node, findWidgetByName(node, 'sigma_min'))
|
||||
toggleWidget(node, findWidgetByName(node, 'denoise'))
|
||||
toggleWidget(node, findWidgetByName(node, 'rho'))
|
||||
toggleWidget(node, findWidgetByName(node, 'beta_d'),true)
|
||||
toggleWidget(node, findWidgetByName(node, 'beta_min'),true)
|
||||
toggleWidget(node, findWidgetByName(node, 'eps_s'),true)
|
||||
}else{
|
||||
toggleWidget(node, findWidgetByName(node, 'denoise'),true)
|
||||
toggleWidget(node, findWidgetByName(node, 'sigma_max'))
|
||||
toggleWidget(node, findWidgetByName(node, 'sigma_min'))
|
||||
toggleWidget(node, findWidgetByName(node, 'beta_d'))
|
||||
toggleWidget(node, findWidgetByName(node, 'beta_min'))
|
||||
toggleWidget(node, findWidgetByName(node, 'eps_s'))
|
||||
toggleWidget(node, findWidgetByName(node, 'rho'))
|
||||
}
|
||||
updateNodeHeight(node)
|
||||
}
|
||||
}
|
||||
|
||||
function widgetLogic2(node, widget) {
|
||||
@@ -535,6 +561,7 @@ app.registerExtension({
|
||||
case "easy latentNoisy":
|
||||
case "easy preSamplingAdvanced":
|
||||
case "easy preSamplingNoiseIn":
|
||||
case "easy preSamplingCustom":
|
||||
case "easy preSamplingSdTurbo":
|
||||
case "easy preSamplingCascade":
|
||||
case "easy preSamplingLayerDiffusion":
|
||||
@@ -1028,6 +1055,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
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1040,7 +1097,7 @@ const getSetWidgets = ['rescale_after_model', 'rescale',
|
||||
'num_loras', 'mode', 'toggle', 'resolution', 'target_parameter',
|
||||
'input_count', 'replace_count', 'downscale_mode', 'range_mode','text_combine_mode', 'input_mode',
|
||||
'lora_count','ckpt_count', 'conditioning_mode', 'preset', 'use_tiled', 'use_batch', 'num_embeds',
|
||||
"easing_mode"
|
||||
"easing_mode", "guider", "scheduler"
|
||||
]
|
||||
|
||||
function getSetters(node) {
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import {app} from "/scripts/app.js";
|
||||
import {$t} from '../common/i18n.js'
|
||||
import {CheckpointInfoDialog, LoraInfoDialog} from "../common/model.js";
|
||||
|
||||
const loaders = ['easy fullLoader', 'easy a1111Loader', 'easy comfyLoader']
|
||||
const preSampling = ['easy preSampling', 'easy preSamplingAdvanced', 'easy preSamplingDynamicCFG', 'easy preSamplingNoiseIn', 'easy preSamplingLayerDiffusion', 'easy fullkSampler']
|
||||
const preSampling = ['easy preSampling', 'easy preSamplingAdvanced', 'easy preSamplingDynamicCFG', 'easy preSamplingNoiseIn', 'easy preSamplingCustom', '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":{
|
||||
@@ -88,6 +90,7 @@ const inputMapping = {
|
||||
"ipadapter":{
|
||||
"model":"model",
|
||||
"image":"image",
|
||||
"image_style": "image",
|
||||
"attn_mask":"attn_mask",
|
||||
"optional_ipadapter":"optional_ipadapter"
|
||||
}
|
||||
@@ -264,6 +267,25 @@ const addMenu = (content, type, nodes_include, nodeType, has_submenu=true) => {
|
||||
has_submenu: has_submenu,
|
||||
callback: (value, options, e, menu, node) => showSwapMenu(value, options, e, menu, node, type, nodes_include)
|
||||
})
|
||||
if(type == 'loaders'){
|
||||
options.unshift({
|
||||
content: $t("💎 View Lora Info..."),
|
||||
callback: (value, options, e, menu, node) => {
|
||||
const widget = node.widgets.find(cate=> cate.name == 'lora_name')
|
||||
let name = widget.value;
|
||||
if (!name || name == 'None') return
|
||||
new LoraInfoDialog(name).show('loras', name);
|
||||
}
|
||||
})
|
||||
options.unshift({
|
||||
content: $t("💎 View Checkpoint Info..."),
|
||||
callback: (value, options, e, menu, node) => {
|
||||
let name = node.widgets[0].value;
|
||||
if (!name || name == 'None') return
|
||||
new CheckpointInfoDialog(name).show('checkpoints', name);
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
const showSwapMenu = (value, options, e, menu, node, type, nodes_include) => {
|
||||
@@ -482,7 +504,7 @@ app.registerExtension({
|
||||
// 刷新节点
|
||||
addMenuHandler(nodeType, function (_, options) {
|
||||
options.unshift({
|
||||
content: "🔃 Reload Node",
|
||||
content: $t("🔃 Reload Node"),
|
||||
callback: (value, options, e, menu, node) => {
|
||||
let graphcanvas = LGraphCanvas.active_canvas;
|
||||
if (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1) {
|
||||
|
||||
@@ -743,6 +743,9 @@ const NODE_COLORS = {
|
||||
"easy positive":"green",
|
||||
"easy negative":"red",
|
||||
"easy promptList":"cyan",
|
||||
"easy promptLine":"cyan",
|
||||
"easy promptConcat":"cyan",
|
||||
"easy promptReplace":"cyan",
|
||||
"easy XYInputs: Seeds++ Batch": customXYLink,
|
||||
"easy XYInputs: ModelMergeBlocks": customXYLink,
|
||||
}
|
||||
|
||||
@@ -2,13 +2,13 @@ import {app} from "/scripts/app.js";
|
||||
import {api} from "/scripts/api.js";
|
||||
import {$el} from "/scripts/ui.js";
|
||||
|
||||
const propmts = ["easy wildcards", "easy positive", "easy negative", "easy stylesSelector"]
|
||||
const propmts = ["easy wildcards", "easy positive", "easy negative", "easy stylesSelector", "easy promptConcat", "easy promptReplace"]
|
||||
const loaders = ["easy a1111Loader", "easy comfyLoader", "easy fullLoader", "easy svdLoader", "easy cascadeLoader", "easy sv3dLoader"]
|
||||
const preSamplingNodes = ["easy preSampling", "easy preSamplingAdvanced", "easy preSamplingNoiseIn", "easy preSamplingDynamicCFG","easy preSamplingSdTurbo", "easy preSamplingLayerDiffusion"]
|
||||
const preSamplingNodes = ["easy preSampling", "easy preSamplingAdvanced", "easy preSamplingNoiseIn", "preSamplingCustom", "easy preSamplingDynamicCFG","easy preSamplingSdTurbo", "easy preSamplingLayerDiffusion"]
|
||||
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']
|
||||
@@ -20,6 +20,36 @@ const suggestions = {
|
||||
"INT": [...["Reroute"],...preSamplingNodes,...['easy fullkSampler']]
|
||||
}
|
||||
},
|
||||
"easy positive":{
|
||||
"from":{
|
||||
"STRING": [...["Reroute"],...propmts]
|
||||
}
|
||||
},
|
||||
"easy negative":{
|
||||
"from":{
|
||||
"STRING": [...["Reroute"],...propmts]
|
||||
}
|
||||
},
|
||||
"easy wildcards":{
|
||||
"from":{
|
||||
"STRING": [...["Reroute","easy showAnything"],...propmts,]
|
||||
}
|
||||
},
|
||||
"easy stylesSelector":{
|
||||
"from":{
|
||||
"STRING": [...["Reroute","easy showAnything"],...propmts,]
|
||||
}
|
||||
},
|
||||
"easy promptConcat":{
|
||||
"from":{
|
||||
"STRING": [...["Reroute","easy showAnything"],...propmts,]
|
||||
}
|
||||
},
|
||||
"easy promptReplace":{
|
||||
"from":{
|
||||
"STRING": [...["Reroute","easy showAnything"],...propmts,]
|
||||
}
|
||||
},
|
||||
// sd相关
|
||||
"easy fullLoader": {
|
||||
"from":{
|
||||
@@ -89,6 +119,11 @@ const suggestions = {
|
||||
"from": {
|
||||
"PIPE_LINE": [...["Reroute"], ...kSampler, ...pipeNodes, ...controlNetNodes, ...xyNodes, ...extraNodes]
|
||||
}
|
||||
},
|
||||
"easy preSamplingCustom": {
|
||||
"from": {
|
||||
"PIPE_LINE": [...["Reroute"], ...kSampler, ...pipeNodes, ...controlNetNodes, ...xyNodes, ...extraNodes]
|
||||
}
|
||||
},
|
||||
"easy preSamplingLayerDiffusion": {
|
||||
"from": {
|
||||
@@ -127,12 +162,33 @@ const suggestions = {
|
||||
"from": {
|
||||
"PIPE_LINE": [...["Reroute"], ...preSamplingNodes, ...controlNetNodes, ...instantIDNodes, ...pipeNodes, ...extraNodes],
|
||||
"MODEL": modelNormalNodes
|
||||
},
|
||||
"to":{
|
||||
"COMBO": [...["Reroute", "easy promptLine"]]
|
||||
}
|
||||
},
|
||||
"easy instantIDApplyADV":{
|
||||
"from": {
|
||||
"PIPE_LINE": [...["Reroute"], ...preSamplingNodes, ...controlNetNodes, ...instantIDNodes, ...pipeNodes, ...extraNodes],
|
||||
"MODEL": modelNormalNodes
|
||||
},
|
||||
"to":{
|
||||
"COMBO": [...["Reroute", "easy promptLine"]]
|
||||
}
|
||||
},
|
||||
"easy ipadapterApply":{
|
||||
"to":{
|
||||
"COMBO": [...["Reroute", "easy promptLine"]]
|
||||
}
|
||||
},
|
||||
"easy ipadapterApplyADV":{
|
||||
"to":{
|
||||
"COMBO": [...["Reroute", "easy promptLine"]]
|
||||
}
|
||||
},
|
||||
"easy ipadapterStyleComposition":{
|
||||
"to":{
|
||||
"COMBO": [...["Reroute", "easy promptLine"]]
|
||||
}
|
||||
},
|
||||
// fix
|
||||
|
||||
+1
-1
@@ -201,7 +201,7 @@ app.registerExtension({
|
||||
},
|
||||
{
|
||||
values: () => {
|
||||
const setterNodes = graph._nodes.filter((otherNode) => otherNode.type == 'easy setNode');
|
||||
const setterNodes = node.graph._nodes.filter((otherNode) => otherNode.type == 'easy setNode');
|
||||
return setterNodes.map((otherNode) => otherNode.widgets[0].value).sort();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
import { app } from "/scripts/app.js";
|
||||
import { api } from "/scripts/api.js";
|
||||
|
||||
import { restart_from_here } from "./prompt.js";
|
||||
import { FlowState } from "./state.js";
|
||||
import { send_cancel, send_message, send_onstart, skip_next_restart_message } from "./messaging.js";
|
||||
import { display_preview_images, additionalDrawBackground, click_is_in_image } from "./preview.js";
|
||||
import {toggleWidget} from "../common/utils.js";
|
||||
|
||||
function progressButtonPressed() {
|
||||
const node = app.graph._nodes_by_id[this.node_id];
|
||||
if (node) {
|
||||
if (FlowState.paused()) {
|
||||
send_message(node.id, [...node.selected, -1, ...node.anti_selected]);
|
||||
}
|
||||
if (FlowState.idle()) {
|
||||
skip_next_restart_message();
|
||||
restart_from_here(node.id).then(() => { send_message(node.id, [...node.selected, -1, ...node.anti_selected]); });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function cancelButtonPressed() { if (FlowState.running()) { send_cancel(); } }
|
||||
|
||||
app.registerExtension({
|
||||
name:'comfy.easyuse.imageChooser',
|
||||
init() {
|
||||
window.addEventListener("beforeunload", send_cancel, true);
|
||||
},
|
||||
setup(app) {
|
||||
function easyuseImageChooser(event) {
|
||||
display_preview_images(event);
|
||||
}
|
||||
api.addEventListener("easyuse-image-choose", easyuseImageChooser);
|
||||
|
||||
/*
|
||||
If a run is interrupted, send a cancel message (unless we're doing the cancelling, to avoid infinite loop)
|
||||
*/
|
||||
const original_api_interrupt = api.interrupt;
|
||||
api.interrupt = function () {
|
||||
if (FlowState.paused() && !FlowState.cancelling) send_cancel();
|
||||
original_api_interrupt.apply(this, arguments);
|
||||
}
|
||||
|
||||
/*
|
||||
At the start of execution
|
||||
*/
|
||||
function on_execution_start() {
|
||||
if (send_onstart()) {
|
||||
app.graph._nodes.forEach((node)=> {
|
||||
if (node.selected || node.anti_selected) {
|
||||
node.selected.clear();
|
||||
node.anti_selected.clear();
|
||||
node.update();
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
api.addEventListener("execution_start", on_execution_start);
|
||||
},
|
||||
|
||||
async nodeCreated(node, app) {
|
||||
if(node.comfyClass == 'easy imageChooser'){
|
||||
node.send_button_widget = node.addWidget("button", "", "", progressButtonPressed, {serialize: false});
|
||||
node.cancel_button_widget = node.addWidget("button", "", "", cancelButtonPressed, {serialize: false});
|
||||
/* Capture clicks */
|
||||
const org_onMouseDown = node.onMouseDown;
|
||||
|
||||
/* A property defining the top of the image when there is just one */
|
||||
if(node?.imageIndex === undefined){
|
||||
Object.defineProperty(node, 'imageIndex', {
|
||||
get : function() { return null; },
|
||||
set: function (v) {node.overIndex= v},
|
||||
})
|
||||
}
|
||||
if(node?.imagey === undefined){
|
||||
Object.defineProperty(node, 'imagey', {
|
||||
get : function() { return null; },
|
||||
set: function (v) {return node.widgets[node.widgets.length-1].last_y+LiteGraph.NODE_WIDGET_HEIGHT;},
|
||||
})
|
||||
}
|
||||
|
||||
node.onMouseDown = function( e, pos, canvas ) {
|
||||
if (e.isPrimary) {
|
||||
const i = click_is_in_image(node, pos);
|
||||
if (i>=0) { this.imageClicked(i); }
|
||||
}
|
||||
return (org_onMouseDown && org_onMouseDown.apply(this, arguments));
|
||||
}
|
||||
|
||||
}
|
||||
},
|
||||
|
||||
beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||
if(nodeData?.name == 'easy imageChooser'){
|
||||
|
||||
const onDrawBackground = nodeType.prototype.onDrawBackground;
|
||||
nodeType.prototype.onDrawBackground = function(ctx) {
|
||||
onDrawBackground.apply(this, arguments);
|
||||
additionalDrawBackground(this, ctx);
|
||||
}
|
||||
|
||||
nodeType.prototype.imageClicked = function (imageIndex) {
|
||||
if (this.selected.has(imageIndex)) this.selected.delete(imageIndex);
|
||||
else this.selected.add(imageIndex);
|
||||
this.update();
|
||||
}
|
||||
|
||||
const update = nodeType.prototype.update;
|
||||
nodeType.prototype.update = function() {
|
||||
if (update) update.apply(this,arguments);
|
||||
if (this.send_button_widget) {
|
||||
this.send_button_widget.node_id = this.id;
|
||||
const selection = ( this.selected ? this.selected.size : 0 ) + ( this.anti_selected ? this.anti_selected.size : 0 )
|
||||
const maxlength = this.imgs.length;
|
||||
if (FlowState.paused_here(this.id) && selection>0) {
|
||||
this.send_button_widget.name = (selection>1) ? "Progress selected (" + selection + '/' + maxlength +")" : "Progress selected image";
|
||||
} else if (FlowState.idle() && selection>0) {
|
||||
this.send_button_widget.name = (selection>1) ? "Progress selected (" + selection + '/' + maxlength +")" : "Progress selected image as restart";
|
||||
}
|
||||
else {
|
||||
this.send_button_widget.name = "";
|
||||
}
|
||||
}
|
||||
if (this.cancel_button_widget) {
|
||||
const isRunning = FlowState.running()
|
||||
this.cancel_button_widget.name = isRunning ? "Cancel current run" : "";
|
||||
}
|
||||
this.setDirtyCanvas(true,true);
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,34 @@
|
||||
import { api } from "/scripts/api.js";
|
||||
import { FlowState } from "./state.js";
|
||||
|
||||
function send_message_from_pausing_node(message) {
|
||||
const id = app.runningNodeId;
|
||||
send_message(id, message);
|
||||
}
|
||||
|
||||
function send_message(id, message) {
|
||||
const body = new FormData();
|
||||
body.append('message',message);
|
||||
body.append('id', id);
|
||||
api.fetchApi("/easyuse/image_chooser_message", { method: "POST", body, });
|
||||
}
|
||||
|
||||
function send_cancel() {
|
||||
send_message(-1,'__cancel__');
|
||||
//FlowState.cancelling = true;
|
||||
//api.interrupt();
|
||||
//FlowState.cancelling = false;
|
||||
}
|
||||
|
||||
var skip_next = 0;
|
||||
function skip_next_restart_message() { skip_next += 1; }
|
||||
function send_onstart() {
|
||||
if (skip_next>0) {
|
||||
skip_next -= 1;
|
||||
return false;
|
||||
}
|
||||
send_message(-1,'__start__');
|
||||
return true;
|
||||
}
|
||||
|
||||
export { send_message_from_pausing_node, send_cancel, send_message, send_onstart, skip_next_restart_message }
|
||||
@@ -0,0 +1,86 @@
|
||||
import { app } from "/scripts/app.js";
|
||||
|
||||
function display_preview_images(event) {
|
||||
const node = app.graph._nodes_by_id[event.detail.id];
|
||||
if (node) {
|
||||
node.selected = new Set();
|
||||
node.anti_selected = new Set();
|
||||
showImages(node, event.detail.urls);
|
||||
} else {
|
||||
console.log(`Image Chooser Preview - failed to find ${event.detail.id}`)
|
||||
}
|
||||
}
|
||||
|
||||
function showImages(node, urls) {
|
||||
node.imgs = [];
|
||||
urls.forEach((u)=> {
|
||||
const img = new Image();
|
||||
node.imgs.push(img);
|
||||
img.onload = () => { app.graph.setDirtyCanvas(true); };
|
||||
img.src = `/view?filename=${encodeURIComponent(u.filename)}&type=temp&subfolder=${app.getPreviewFormatParam()}`
|
||||
})
|
||||
node.setSizeForImage?.();
|
||||
}
|
||||
|
||||
function drawRect(node, s, ctx) {
|
||||
const padding = 1;
|
||||
var rect;
|
||||
if (node.imageRects) {
|
||||
rect = node.imageRects[s];
|
||||
} else {
|
||||
const y = node.imagey;
|
||||
rect = [padding,y+padding,node.size[0]-2*padding,node.size[1]-y-2*padding];
|
||||
}
|
||||
ctx.strokeRect(rect[0]+padding, rect[1]+padding, rect[2]-padding*2, rect[3]-padding*2);
|
||||
}
|
||||
|
||||
function additionalDrawBackground(node, ctx) {
|
||||
if (!node.imgs) return;
|
||||
if (node.imageRects) {
|
||||
for (let i = 0; i < node.imgs.length; i++) {
|
||||
// delete underlying image
|
||||
ctx.fillStyle = "#000";
|
||||
ctx.fillRect(...node.imageRects[i])
|
||||
// draw the new one
|
||||
const img = node.imgs[i];
|
||||
const cellWidth = node.imageRects[i][2];
|
||||
const cellHeight = node.imageRects[i][3];
|
||||
|
||||
let wratio = cellWidth/img.width;
|
||||
let hratio = cellHeight/img.height;
|
||||
var ratio = Math.min(wratio, hratio);
|
||||
|
||||
let imgHeight = ratio * img.height;
|
||||
let imgWidth = ratio * img.width;
|
||||
|
||||
const imgX = node.imageRects[i][0] + (cellWidth - imgWidth)/2;
|
||||
const imgY = node.imageRects[i][1] + (cellHeight - imgHeight)/2;
|
||||
const cell_padding = 2;
|
||||
ctx.drawImage(img, imgX+cell_padding, imgY+cell_padding, imgWidth-cell_padding*2, imgHeight-cell_padding*2);
|
||||
|
||||
}
|
||||
}
|
||||
ctx.lineWidth = 2;
|
||||
ctx.strokeStyle = "green";
|
||||
node?.selected?.forEach((s) => { drawRect(node,s, ctx) })
|
||||
ctx.strokeStyle = "#F88";
|
||||
node?.anti_selected?.forEach((s) => { drawRect(node,s, ctx) })
|
||||
}
|
||||
|
||||
function click_is_in_image(node, pos) {
|
||||
if (node.imgs?.length>1) {
|
||||
for (var i = 0; i<node.imageRects.length; i++) {
|
||||
const dx = pos[0] - node.imageRects[i][0];
|
||||
const dy = pos[1] - node.imageRects[i][1];
|
||||
if ( dx > 0 && dx < node.imageRects[i][2] &&
|
||||
dy > 0 && dy < node.imageRects[i][3] ) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
} else if (node.imgs?.length==1) {
|
||||
if (pos[1]>node.imagey) return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
export { display_preview_images, additionalDrawBackground, click_is_in_image }
|
||||
@@ -0,0 +1,114 @@
|
||||
import { app } from "/scripts/app.js";
|
||||
|
||||
function links_with(p, node_id, down, up) {
|
||||
const links_with = [];
|
||||
p.workflow.links.forEach((l) => {
|
||||
if (down && l[1]===node_id && !links_with.includes(l[3])) links_with.push(l[3])
|
||||
if (up && l[3]===node_id && !links_with.includes(l[1])) links_with.push(l[1])
|
||||
});
|
||||
return links_with;
|
||||
}
|
||||
|
||||
function _all_v_nodes(p, here_id) {
|
||||
/*
|
||||
Make a list of all downstream nodes.
|
||||
*/
|
||||
const downstream = [];
|
||||
const to_process = [here_id]
|
||||
while(to_process.length>0) {
|
||||
const id = to_process.pop();
|
||||
downstream.push(id);
|
||||
to_process.push(
|
||||
...links_with(p,id,true,false).filter((nid)=>{
|
||||
return !(downstream.includes(nid) || to_process.includes(nid))
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
/*
|
||||
Now all upstream nodes from any of the downstream nodes (except us).
|
||||
Put us on the result list so we don't flow up through us
|
||||
*/
|
||||
to_process.push(...downstream.filter((n)=>{ return n!=here_id}));
|
||||
const back_upstream = [here_id];
|
||||
while(to_process.length>0) {
|
||||
const id = to_process.pop();
|
||||
back_upstream.push(id);
|
||||
to_process.push(
|
||||
...links_with(p,id,false,true).filter((nid)=>{
|
||||
return !(back_upstream.includes(nid) || to_process.includes(nid))
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
const keep = [];
|
||||
keep.push(...downstream);
|
||||
keep.push(...back_upstream.filter((n)=>{return !keep.includes(n)}));
|
||||
|
||||
console.log(`Nodes to keep: ${keep}`);
|
||||
return keep;
|
||||
}
|
||||
|
||||
async function all_v_nodes(here_id) {
|
||||
const p = structuredClone(await app.graphToPrompt());
|
||||
const all_nodes = [];
|
||||
p.workflow.nodes.forEach((node)=>{all_nodes.push(node.id)})
|
||||
p.workflow.links = p.workflow.links.filter((l)=>{ return (all_nodes.includes(l[1]) && all_nodes.includes(l[3]))} )
|
||||
return _all_v_nodes(p,here_id);
|
||||
}
|
||||
|
||||
async function restart_from_here(here_id, go_down_to_chooser=false) {
|
||||
const p = structuredClone(await app.graphToPrompt());
|
||||
/*
|
||||
Make a list of all nodes, and filter out links that are no longer valid
|
||||
*/
|
||||
const all_nodes = [];
|
||||
p.workflow.nodes.forEach((node)=>{all_nodes.push(node.id)})
|
||||
p.workflow.links = p.workflow.links.filter((l)=>{ return (all_nodes.includes(l[1]) && all_nodes.includes(l[3]))} )
|
||||
|
||||
/* Move downstream to a chooser */
|
||||
if (go_down_to_chooser) {
|
||||
while (!app.graph._nodes_by_id[here_id].isChooser) {
|
||||
here_id = links_with(p, here_id, true, false)[0];
|
||||
}
|
||||
}
|
||||
|
||||
const keep = _all_v_nodes(p, here_id);
|
||||
|
||||
/*
|
||||
Filter p.workflow.nodes and p.workflow.links
|
||||
*/
|
||||
p.workflow.nodes = p.workflow.nodes.filter((node) => {
|
||||
if (node.id===here_id) node.inputs.forEach((i)=>{i.link=null}) // remove our upstream links
|
||||
return (keep.includes(node.id)) // only keep keepers
|
||||
})
|
||||
p.workflow.links = p.workflow.links.filter((l) => {return (keep.includes(l[1]) && keep.includes(l[3]))})
|
||||
|
||||
/*
|
||||
Filter the p.output object to only include nodes we're keeping
|
||||
*/
|
||||
const new_output = {}
|
||||
for (const [key, value] of Object.entries(p.output)) {
|
||||
if (keep.includes(parseInt(key))) new_output[key] = value;
|
||||
}
|
||||
/*
|
||||
Filter the p.output entry for the start node to remove any list (ie link) inputs
|
||||
*/
|
||||
const new_inputs = {};
|
||||
for (const [key, value] of Object.entries(new_output[here_id.toString()].inputs)) {
|
||||
if (!Array.isArray(value)) new_inputs[key] = value;
|
||||
}
|
||||
new_output[here_id.toString()].inputs = new_inputs;
|
||||
|
||||
p.output = new_output;
|
||||
|
||||
// temporarily hijack graph_to_prompt with a version that restores the old one but returns this prompt
|
||||
const gtp_was = app.graphToPrompt;
|
||||
app.graphToPrompt = () => {
|
||||
app.graphToPrompt = gtp_was;
|
||||
return p;
|
||||
}
|
||||
app.queuePrompt(0);
|
||||
}
|
||||
|
||||
export { restart_from_here, all_v_nodes }
|
||||
@@ -0,0 +1,26 @@
|
||||
import { app } from "/scripts/app.js";
|
||||
|
||||
export class FlowState {
|
||||
constructor(){}
|
||||
static idle() {
|
||||
return (!app.runningNodeId);
|
||||
}
|
||||
static paused() {
|
||||
return true;
|
||||
}
|
||||
static paused_here(node_id) {
|
||||
return (FlowState.paused() && FlowState.here(node_id))
|
||||
}
|
||||
static running() {
|
||||
return (!FlowState.idle());
|
||||
}
|
||||
static here(node_id) {
|
||||
return (app.runningNodeId==node_id);
|
||||
}
|
||||
static state() {
|
||||
if (FlowState.paused()) return "Paused";
|
||||
if (FlowState.running()) return "Running";
|
||||
return "Idle";
|
||||
}
|
||||
static cancelling = false;
|
||||
}
|
||||
Reference in New Issue
Block a user