fix:wildcard not match space #131
This commit is contained in:
+17
-14
@@ -271,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
|
||||
|
||||
@@ -334,4 +335,6 @@ Disclaimer: Opened source was not easy. I have a lot of respect for the contribu
|
||||
|
||||
[ComfyUI_InstantID](https://github.com/cubiq/ComfyUI_InstantID) - Face migration
|
||||
|
||||
[ComfyUI-Custom-Scripts](https://github.com/pythongosssss/ComfyUI-Custom-Scripts) - pyssss🐍
|
||||
[ComfyUI-Custom-Scripts](https://github.com/pythongosssss/ComfyUI-Custom-Scripts) - pyssss🐍
|
||||
|
||||
[cg-image-picker](https://github.com/chrisgoringe/cg-image-picker) - Image Preview Chooser
|
||||
|
||||
@@ -281,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 |
|
||||
|
||||
## 示例
|
||||
|
||||
@@ -352,3 +353,5 @@
|
||||
[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) - 图片选择器
|
||||
|
||||
@@ -19,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):
|
||||
|
||||
@@ -2618,6 +2618,7 @@ class instantID:
|
||||
del pipe
|
||||
|
||||
return (new_pipe, model, positive, negative)
|
||||
|
||||
class instantIDApply(instantID):
|
||||
|
||||
def __init__(self):
|
||||
|
||||
+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"
|
||||
}
|
||||
@@ -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({})
|
||||
+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:
|
||||
|
||||
@@ -25,8 +25,11 @@ const zhCN = {
|
||||
"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": "已停用",
|
||||
|
||||
@@ -70,3 +70,44 @@ export function formatTime(time, format) {
|
||||
};
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
+14
-1
@@ -1,6 +1,6 @@
|
||||
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";
|
||||
|
||||
@@ -270,6 +270,19 @@ 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;
|
||||
};
|
||||
},
|
||||
|
||||
@@ -4,36 +4,11 @@ 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') {
|
||||
@@ -81,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'))
|
||||
@@ -272,13 +247,9 @@ 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)
|
||||
|
||||
@@ -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