Compare commits
22
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1daa1a4603 | ||
|
|
062773d929 | ||
|
|
57decadaef | ||
|
|
ec804ab7c9 | ||
|
|
3ee7533098 | ||
|
|
0d383ccc1f | ||
|
|
e2f2257c34 | ||
|
|
d62b9fc4c6 | ||
|
|
be7ad0c7fb | ||
|
|
156864cc8b | ||
|
|
7240e496cc | ||
|
|
8acdf4018d | ||
|
|
1ea7c3e203 | ||
|
|
e54aeb6125 | ||
|
|
d59f51fbcf | ||
|
|
a667eb6982 | ||
|
|
8d72732247 | ||
|
|
f0f3b30a62 | ||
|
|
d99fe24542 | ||
|
|
ea4c7381bd | ||
|
|
e900d20641 | ||
|
|
968178bf57 |
@@ -33,6 +33,8 @@ APP-JSON:
|
||||
|
||||
> 输出节点:PreviewImage 、SaveImage、ShowTextForGPT、VHS_VideoCombine
|
||||
|
||||
> seed统一输入控件,支持:SamplerCustom、KSampler
|
||||
|
||||
|
||||
## 🏃🚗🚚🚀 Real-time Design
|
||||
> ScreenShareNode & FloatingVideoNode. Now comfyui supports capturing screen pixel streams from any software and can be used for LCM-Lora integration. Let's get started with implementation and design! 💻🌐
|
||||
@@ -75,6 +77,9 @@ https://github.com/shadowcz007/comfyui-mixlab-nodes/assets/12645064/e7e77f90-e43
|
||||
|
||||
[add clip-interrogator](https://github.com/pharmapsychotic/clip-interrogator)
|
||||
|
||||
> PromptImage & PromptSimplification,Assist in simplifying prompt words, comparing images and prompt word nodes.
|
||||
|
||||
|
||||
### Layers
|
||||
> A new layer class node has been added, allowing you to separate the image into layers. After merging the images, you can input the controlnet for further processing.
|
||||
|
||||
|
||||
+3
-1
@@ -503,7 +503,7 @@ PromptServer.add_routes=new_add_routes
|
||||
|
||||
|
||||
# 导入节点
|
||||
from .nodes.PromptNode import RandomPrompt,PromptSlide
|
||||
from .nodes.PromptNode import RandomPrompt,PromptSlide,PromptSimplification,PromptImage
|
||||
from .nodes.ImageNode import NoiseImage,TransparentImage,GradientImage,LoadImagesFromPath,LoadImagesFromURL,ResizeImage,TextImage,SvgImage,Image3D,ShowLayer,NewLayer,MergeLayers,AreaToMask,SmoothMask,FeatheredMask,SplitLongMask,ImageCropByAlpha,EnhanceImage,FaceToMask
|
||||
from .nodes.Vae import VAELoader,VAEDecode
|
||||
from .nodes.ScreenShareNode import ScreenShareNode,FloatingVideo
|
||||
@@ -520,6 +520,8 @@ NODE_CLASS_MAPPINGS = {
|
||||
"AppInfo":AppInfo,
|
||||
"RandomPrompt":RandomPrompt,
|
||||
"PromptSlide":PromptSlide,
|
||||
"PromptSimplification":PromptSimplification,
|
||||
"PromptImage":PromptImage,
|
||||
"ClipInterrogator":ClipInterrogator,
|
||||
"NoiseImage":NoiseImage,
|
||||
"GradientImage":GradientImage,
|
||||
|
||||
Binary file not shown.
@@ -4786,6 +4786,7 @@
|
||||
"NewLayer",
|
||||
"RandomPrompt",
|
||||
"PromptSlide",
|
||||
"PromptSimplification",
|
||||
"ClipInterrogator",
|
||||
"ScreenShare",
|
||||
"ShowLayer",
|
||||
|
||||
+2
-1
@@ -180,8 +180,9 @@ class ShowTextForGPT:
|
||||
CATEGORY = "♾️Mixlab/GPT"
|
||||
|
||||
def run(self, text):
|
||||
# print(session_history)
|
||||
# print(text)
|
||||
return {"ui": {"text": text}, "result": (text,)}
|
||||
|
||||
|
||||
|
||||
class CharacterInText:
|
||||
|
||||
@@ -42,7 +42,7 @@ def pil2tensor(image):
|
||||
return torch.from_numpy(np.array(image).astype(np.float32) / 255.0).unsqueeze(0)
|
||||
|
||||
|
||||
def image_analysis(ci,image):
|
||||
def image_analysis_fn(ci,image):
|
||||
image = image.convert('RGB')
|
||||
image_features = ci.image_to_features(image)
|
||||
|
||||
@@ -88,13 +88,13 @@ class ClipInterrogator:
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING","STRING",)
|
||||
RETURN_NAMES = ("prompt","analysis",)
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("prompt",)
|
||||
|
||||
FUNCTION = "run"
|
||||
|
||||
CATEGORY = "♾️Mixlab/prompt"
|
||||
|
||||
OUTPUT_NODE = True
|
||||
INPUT_IS_LIST = True
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
global ci
|
||||
@@ -137,8 +137,8 @@ class ClipInterrogator:
|
||||
im=im.convert('RGB')
|
||||
|
||||
if analysis=='on':
|
||||
analysis_res=image_analysis(ci,im)
|
||||
analysis_result.append(json.dumps(analysis_res))
|
||||
analysis_res=image_analysis_fn(ci,im)
|
||||
analysis_result.append( analysis_res )
|
||||
pbar.update(1)
|
||||
|
||||
prompt=image_to_prompt(ci,im,prompt_mode)
|
||||
@@ -155,4 +155,6 @@ class ClipInterrogator:
|
||||
ci.caption_model = ci.caption_model.to('cpu')
|
||||
ci.caption_offloaded = True
|
||||
|
||||
return {"ui":{"prompt": prompt_result,"analysis":analysis_result},"result": (prompt_result,analysis_result,)}
|
||||
# analysis_result=[]
|
||||
|
||||
return {"ui":{"prompt": prompt_result,"analysis":analysis_result},"result": (prompt_result,)}
|
||||
+157
-3
@@ -1,9 +1,11 @@
|
||||
import random
|
||||
import comfy.utils
|
||||
import json
|
||||
import os
|
||||
import numpy as np
|
||||
from urllib import request, parse
|
||||
|
||||
|
||||
import folder_paths
|
||||
from PIL import Image, ImageOps,ImageFilter,ImageEnhance,ImageDraw,ImageSequence, ImageFont
|
||||
from PIL.PngImagePlugin import PngInfo
|
||||
# def queue_prompt(prompt_workflow):
|
||||
# p = {"prompt": prompt_workflow}
|
||||
# data = json.dumps(p).encode('utf-8')
|
||||
@@ -45,12 +47,164 @@ default_prompt1='''Swing
|
||||
default_prompt1="\n".join([p.strip() for p in default_prompt1.split('\n') if p.strip()!=''])
|
||||
|
||||
|
||||
def tensor2pil(image):
|
||||
return Image.fromarray(np.clip(255. * image.cpu().numpy().squeeze(), 0, 255).astype(np.uint8))
|
||||
|
||||
def addWeight(text, weight=1):
|
||||
if weight == 1:
|
||||
return text
|
||||
else:
|
||||
return f"({text}:{round(weight,2)})"
|
||||
|
||||
def prompt_delete_words(sentence, new_words_length):
|
||||
# 使用逗号分割句子,并去除空格
|
||||
words = [word.strip() for word in sentence.split(",")]
|
||||
|
||||
# 计算需要删除的单词数量
|
||||
num_to_delete = len(words) - new_words_length
|
||||
|
||||
words_to=[w for w in words]
|
||||
|
||||
# 逐个删除单词并存储在新列表中
|
||||
new_words = []
|
||||
for i in range(len(words)):
|
||||
if num_to_delete > 0:
|
||||
num_to_delete -= 1
|
||||
else:
|
||||
words_to.pop()
|
||||
if len(words_to)>0:
|
||||
new_words.append(", ".join(words_to))
|
||||
|
||||
return new_words
|
||||
|
||||
# # 测试方法
|
||||
# sentence = "a computer, a glass tablet with a keyboard on a dark background, 3d illustration, reflection, cgi 8k, clear glass, archaic, cut-away, white outline"
|
||||
# new_words_length = 5
|
||||
# result = prompt_delete_words(sentence, new_words_length)
|
||||
# print(result)
|
||||
|
||||
class PromptImage:
|
||||
def __init__(self):
|
||||
self.temp_dir = folder_paths.get_temp_directory()
|
||||
self.type = "temp"
|
||||
self.prefix_append = "PromptImage"
|
||||
self.compress_level = 4
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"prompts": ("STRING",
|
||||
{
|
||||
"multiline": True,
|
||||
"default": '',
|
||||
"dynamicPrompts": False
|
||||
}),
|
||||
|
||||
"images": ("IMAGE",{"default": None}),
|
||||
"save_to_image": (["enable", "disable"],),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ()
|
||||
|
||||
OUTPUT_NODE = True
|
||||
|
||||
INPUT_IS_LIST = True
|
||||
|
||||
FUNCTION = "run"
|
||||
|
||||
CATEGORY = "♾️Mixlab/prompt"
|
||||
|
||||
# 运行的函数
|
||||
def run(self,prompts,images,save_to_image):
|
||||
filename_prefix="mixlab_"
|
||||
filename_prefix += self.prefix_append
|
||||
full_output_folder, filename, counter, subfolder, filename_prefix = folder_paths.get_save_image_path(
|
||||
filename_prefix, self.temp_dir, images[0].shape[1], images[0].shape[0])
|
||||
results = list()
|
||||
|
||||
save_to_image=save_to_image[0]=='enable'
|
||||
|
||||
for index in range(len(images)):
|
||||
image=images[index]
|
||||
img=tensor2pil(image)
|
||||
|
||||
metadata = None
|
||||
if save_to_image:
|
||||
metadata = PngInfo()
|
||||
prompt_text=prompts[index]
|
||||
if prompt_text is not None:
|
||||
metadata.add_text("prompt_text", prompt_text)
|
||||
|
||||
file = f"{filename}_{index}_{counter:05}_.png"
|
||||
img.save(os.path.join(full_output_folder, file), pnginfo=metadata, compress_level=self.compress_level)
|
||||
results.append({
|
||||
"filename": file,
|
||||
"subfolder": subfolder,
|
||||
"type": self.type
|
||||
})
|
||||
counter += 1
|
||||
|
||||
return { "ui": { "_images": results,"prompts":prompts } }
|
||||
|
||||
|
||||
|
||||
|
||||
class PromptSimplification:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"prompt": ("STRING",
|
||||
{
|
||||
"multiline": True,
|
||||
"default": '',
|
||||
"dynamicPrompts": False
|
||||
}),
|
||||
|
||||
"length":("INT", {"default": 5, "min": 1,"max":100, "step": 1, "display": "number"}),
|
||||
|
||||
# "min_value":("FLOAT", {
|
||||
# "default": -2,
|
||||
# "min": -10,
|
||||
# "max": 0xffffffffffffffff,
|
||||
# "step": 0.01,
|
||||
# "display": "number"
|
||||
# }),
|
||||
# "max_value":("FLOAT", {
|
||||
# "default": 2,
|
||||
# "min": -10,
|
||||
# "max": 0xffffffffffffffff,
|
||||
# "step": 0.01,
|
||||
# "display": "number"
|
||||
# }),
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("prompts",)
|
||||
|
||||
FUNCTION = "run"
|
||||
|
||||
CATEGORY = "♾️Mixlab/prompt"
|
||||
|
||||
INPUT_IS_LIST = True
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
OUTPUT_NODE = True
|
||||
|
||||
# 运行的函数
|
||||
def run(self,prompt,length):
|
||||
length=length[0]
|
||||
result=[]
|
||||
for p in prompt:
|
||||
nps=prompt_delete_words(p,length)
|
||||
for n in nps:
|
||||
result.append(n)
|
||||
|
||||
return {"ui": {"prompts": result}, "result": (result,)}
|
||||
|
||||
|
||||
|
||||
class PromptSlide:
|
||||
|
||||
+3
-2
@@ -188,7 +188,7 @@ class FloatSlider:
|
||||
"number":("FLOAT", {
|
||||
"default": 0,
|
||||
"min": 0, #Minimum value
|
||||
"max": 1, #Maximum value
|
||||
"max": 0xffffffffffffffff, #Maximum value
|
||||
"step": 0.001, #Slider's step
|
||||
"display": "slider" # Cosmetic only: display as "number" or "slider"
|
||||
}),
|
||||
@@ -262,7 +262,7 @@ class IntNumber:
|
||||
"default": 1,
|
||||
"min": -0xffffffffffffffff,
|
||||
"max": 0xffffffffffffffff,
|
||||
"step":1,
|
||||
"step":1,
|
||||
"display": "number"
|
||||
}),
|
||||
},
|
||||
@@ -461,6 +461,7 @@ class AppInfo:
|
||||
|
||||
CATEGORY = "♾️Mixlab"
|
||||
|
||||
OUTPUT_NODE = True
|
||||
INPUT_IS_LIST = True
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
|
||||
|
||||
+140
-21
@@ -323,6 +323,15 @@
|
||||
height: 56px !important;
|
||||
outline: 1px solid white;
|
||||
}
|
||||
|
||||
|
||||
/* 给prompt image 节点使用 */
|
||||
.prompt_image {
|
||||
color: #2f2f2f;
|
||||
padding: 0 10px;
|
||||
font-size: 12px;
|
||||
width: 200px;
|
||||
}
|
||||
</style>
|
||||
<!-- <script src="../../../scripts/api.js" type="module"></script> -->
|
||||
<link href="/extensions/comfyui-mixlab-nodes/lib/photoswipe.min.css" rel="stylesheet">
|
||||
@@ -443,20 +452,27 @@
|
||||
|
||||
function randomSeed(seed, data) {
|
||||
for (const id in data) {
|
||||
|
||||
if (data[id].inputs.seed != undefined
|
||||
&& !Array.isArray(data[id].inputs.seed) //如果是数组,则由其他节点控制
|
||||
&& ['increment', 'decrement', 'randomize'].includes(seed[id])) {
|
||||
data[id].inputs.seed = Math.round(Math.random() * 1849378600828930)
|
||||
// console.log('new Seed', data[id])
|
||||
}
|
||||
if (data[id].inputs.noise_seed != undefined
|
||||
&& !Array.isArray(data[id].inputs.noise_seed) //如果是数组,则由其他节点控制
|
||||
&& ['increment', 'decrement', 'randomize'].includes(seed[id])) {
|
||||
data[id].inputs.noise_seed = Math.round(Math.random() * 1849378600828930)
|
||||
|
||||
}
|
||||
console.log('new Seed', data[id])
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
function updateSeed(id, val) {
|
||||
console.log(val)
|
||||
if (!Array.isArray(window._appData.data[id].inputs.seed)) window._appData.data[id].inputs.seed = Math.round(val);
|
||||
// console.log(val)
|
||||
if (window._appData.data[id].inputs.seed && !Array.isArray(window._appData.data[id].inputs.seed)) window._appData.data[id].inputs.seed = Math.round(val);
|
||||
if (window._appData.data[id].inputs.noise_seed && !Array.isArray(window._appData.data[id].inputs.noise_seed)) window._appData.data[id].inputs.noise_seed = Math.round(val);
|
||||
}
|
||||
|
||||
|
||||
@@ -596,7 +612,16 @@
|
||||
div.innerText = Array.isArray(node.inputs.text) ? node.inputs.text[0] : node.inputs.text
|
||||
output_card.appendChild(div);
|
||||
};
|
||||
if (["SaveImage", "PreviewImage"].includes(node.class_type)) {
|
||||
|
||||
if (node.class_type == "ClipInterrogator") {
|
||||
let div = document.createElement('div');
|
||||
div.className = "show_text";
|
||||
div.id = `output_${node.id}`;
|
||||
div.innerText = '#ClipInterrogator: …… '
|
||||
output_card.appendChild(div);
|
||||
};
|
||||
|
||||
if (["SaveImage", "PreviewImage", "PromptImage"].includes(node.class_type)) {
|
||||
|
||||
let a = document.createElement('a');
|
||||
a.id = `output_${node.id}`
|
||||
@@ -929,7 +954,7 @@
|
||||
|
||||
const label = data.title;
|
||||
|
||||
if (!options.keywords.includes(data.inputs.prompt_keyword)) {
|
||||
if (options.keywords && !options.keywords?.includes(data.inputs.prompt_keyword)) {
|
||||
options.keywords = [data.inputs.prompt_keyword, ...options.keywords]
|
||||
};
|
||||
|
||||
@@ -959,16 +984,20 @@
|
||||
let silde = createNumSlide(data.title,
|
||||
data.inputs.number,
|
||||
(v) => {
|
||||
// console.log(data.id,window._appData.data[data.id])
|
||||
window._appData.data[data.id].inputs.number = v;
|
||||
},
|
||||
options.min,
|
||||
options.max,
|
||||
data.class_type === 'IntNumber' ? 'int' : 'float')
|
||||
data.class_type === 'IntNumber' ? 'int' : 'float',
|
||||
null,
|
||||
data.id
|
||||
)
|
||||
container.appendChild(silde);
|
||||
}
|
||||
|
||||
// Check if the class_type is "CLIPTextEncode"
|
||||
if (["TextInput_", "CLIPTextEncode"].includes(data.class_type)) {
|
||||
if (["TextInput_", "CLIPTextEncode", "PromptSimplification"].includes(data.class_type)) {
|
||||
// Create a container for the upload control
|
||||
const uploadContainer = document.createElement("div");
|
||||
uploadContainer.className = 'card';
|
||||
@@ -981,13 +1010,27 @@
|
||||
// Create an input field for the image name
|
||||
const textInput = document.createElement("textarea");
|
||||
// textInput.className=;
|
||||
textInput.value = data.inputs.text;
|
||||
if (data.class_type == "PromptSimplification") {
|
||||
textInput.value = data.inputs.prompt;
|
||||
} else {
|
||||
textInput.value = data.inputs.text;
|
||||
}
|
||||
|
||||
// uploadImageInput.type = "text";
|
||||
let json = localStorage.getItem(`t_${data.id}`)
|
||||
try {
|
||||
// 缓存
|
||||
const { value, height } = JSON.parse(json);
|
||||
textInput.value = value;
|
||||
textInput.style.height = height;
|
||||
|
||||
if (data.class_type == "PromptSimplification") {
|
||||
window._appData.data[data.id].inputs.prompt = textInput.value;
|
||||
} else {
|
||||
window._appData.data[data.id].inputs.text = textInput.value;
|
||||
}
|
||||
|
||||
|
||||
} catch (error) {
|
||||
|
||||
}
|
||||
@@ -1003,8 +1046,12 @@
|
||||
textInput.addEventListener('input', (event) => {
|
||||
// console.log(textInput.value)
|
||||
autoResize(textInput);
|
||||
window._appData.data[data.id].inputs.text = textInput.value;
|
||||
|
||||
if (data.class_type == "PromptSimplification") {
|
||||
window._appData.data[data.id].inputs.prompt = textInput.value;
|
||||
} else {
|
||||
window._appData.data[data.id].inputs.text = textInput.value;
|
||||
}
|
||||
localStorage.setItem(`t_${data.id}`, JSON.stringify({
|
||||
value: textInput.value,
|
||||
height: textInput.style.height
|
||||
@@ -1019,6 +1066,22 @@
|
||||
if (["CheckpointLoaderSimple", "LoraLoader"].includes(data.class_type)) {
|
||||
let value = data.inputs.ckpt_name || data.inputs.lora_name;
|
||||
|
||||
try {
|
||||
let v = localStorage.getItem(`_model_${data.id}_${data.class_type}`)
|
||||
if (v) {
|
||||
value = v;
|
||||
if (data.class_type === 'CheckpointLoaderSimple') {
|
||||
window._appData.data[data.id].inputs.ckpt_name = value;
|
||||
}
|
||||
if (data.class_type === 'LoraLoader') {
|
||||
window._appData.data[data.id].inputs.lora_name = value;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
let [div, selectDom] = createSelectWithOptions(data.title, Array.from(data.options, o => {
|
||||
return {
|
||||
value: o,
|
||||
@@ -1035,6 +1098,8 @@
|
||||
if (data.class_type === 'LoraLoader') {
|
||||
window._appData.data[data.id].inputs.lora_name = selectDom.value;
|
||||
}
|
||||
|
||||
localStorage.setItem(`_model_${data.id}_${data.class_type}`, selectDom.value)
|
||||
})
|
||||
|
||||
container.appendChild(div);
|
||||
@@ -1162,8 +1227,11 @@
|
||||
|
||||
value = 'float' ? parseFloat(value.toFixed(3)) : parseInt(value)
|
||||
try {
|
||||
let i=parseFloat(localStorage.getItem(`_slider_${targetId}`))
|
||||
if(!!i) value = i
|
||||
let i = parseFloat(localStorage.getItem(`_slider_${targetId}`))
|
||||
if (!!i) {
|
||||
value = i;
|
||||
callback && callback(value)
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
}
|
||||
@@ -1192,6 +1260,8 @@
|
||||
|
||||
let defaultValue = (targetId ? localStorage.getItem(`_slide_${targetId}`) : '') || keywords[0];
|
||||
|
||||
window._appData.data[targetId].inputs.prompt_keyword = defaultValue;
|
||||
|
||||
let selectTag = createSelect(Array.from(keywords, (k, i) => {
|
||||
return {
|
||||
value: k,
|
||||
@@ -1515,7 +1585,7 @@
|
||||
|
||||
}
|
||||
|
||||
if (val && type == "images" && output.querySelector(`#output_${id} img`)) {
|
||||
if (val && (type == "images" || type == 'images_prompts') && output.querySelector(`#output_${id} img`)) {
|
||||
let imgDiv = output.querySelector(`#output_${id}`)
|
||||
imgDiv.style.display = 'none';
|
||||
|
||||
@@ -1523,7 +1593,16 @@
|
||||
// Array.from(imgDiv.parentElement.querySelectorAll('.output_images'), im => im.remove());
|
||||
|
||||
for (const v of val) {
|
||||
let im = await createImage(v);
|
||||
|
||||
let url = v, prompt = ''
|
||||
|
||||
if (type == 'images_prompts') {
|
||||
// 是个数组,多了对应的prompt
|
||||
url = v[0];
|
||||
prompt = v[1];
|
||||
}
|
||||
|
||||
let im = await createImage(url);
|
||||
|
||||
// 构建新的
|
||||
let a = document.createElement('a');
|
||||
@@ -1531,12 +1610,22 @@
|
||||
a.setAttribute('data-pswp-width', im.naturalWidth);
|
||||
a.setAttribute('data-pswp-height', im.naturalHeight);
|
||||
a.setAttribute('target', "_blank");
|
||||
a.setAttribute('href', v);
|
||||
a.setAttribute('href', url);
|
||||
|
||||
|
||||
let img = new Image();
|
||||
// img;
|
||||
img.src = v;
|
||||
a.appendChild(img)
|
||||
img.src = url;
|
||||
a.appendChild(img);
|
||||
|
||||
if (prompt) {
|
||||
a.style.textDecoration = 'none';
|
||||
let p = document.createElement('p')
|
||||
p.className = 'prompt_image'
|
||||
p.innerText = prompt;
|
||||
a.appendChild(p)
|
||||
}
|
||||
|
||||
// imgDiv.parentElement.appendChild(a);
|
||||
imgDiv.parentElement.insertBefore(a, imgDiv.parentElement.firstChild);
|
||||
}
|
||||
@@ -1695,19 +1784,25 @@
|
||||
};
|
||||
|
||||
api.addEventListener("status", ({ detail }) => {
|
||||
console.log("status", detail);
|
||||
console.log("status", detail, detail.exec_info?.queue_remaining);
|
||||
try {
|
||||
ui.status.update(`queue#${detail.exec_info.queue_remaining}`);
|
||||
ui.status.update(`queue#${detail.exec_info?.queue_remaining}`);
|
||||
if (detail.exec_info?.queue_remaining === 0) {
|
||||
// 运行按钮重设
|
||||
ui.submitButton.reset()
|
||||
console.log('运行按钮重设')
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
console.log(error)
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
api.addEventListener("progress", ({ detail }) => {
|
||||
console.log("progress", detail);
|
||||
const class_type = window._appData.data[detail?.node]?.class_type || ''
|
||||
try {
|
||||
ui.status.update(`${detail.value}/${detail.max}`);
|
||||
ui.status.update(`${parseFloat(100 * detail.value / detail.max).toFixed(1)}% ${class_type}`);
|
||||
ui.submitButton.running()
|
||||
} catch (error) {
|
||||
|
||||
@@ -1721,6 +1816,12 @@
|
||||
const text = detail?.output?.text;
|
||||
const gifs = detail?.output?.gifs;
|
||||
|
||||
const prompt = detail?.output?.prompt;
|
||||
const analysis = detail?.output?.analysis;
|
||||
|
||||
const _images = detail?.output?._images;
|
||||
const prompts = detail?.output?.prompts;
|
||||
|
||||
if (images) {
|
||||
// if (!images) return;
|
||||
|
||||
@@ -1730,17 +1831,30 @@
|
||||
return `${url}/view?filename=${encodeURIComponent(img.filename)}&type=${img.type}&subfolder=${encodeURIComponent(img.subfolder)}&t=${+new Date()}`;
|
||||
}), detail.node, 'images');
|
||||
|
||||
} else if (_images && prompts) {
|
||||
let url = get_url();
|
||||
|
||||
show(Array.from(_images, (img, i) => {
|
||||
return [`${url}/view?filename=${encodeURIComponent(img.filename)
|
||||
}&type=${img.type}&subfolder=${encodeURIComponent(img.subfolder)
|
||||
}&t=${+new Date()}`, prompts[i]];
|
||||
}), detail.node, 'images_prompts');
|
||||
|
||||
} else if (text) {
|
||||
ui.output.update("text", Array.isArray(text) ? text[0] : text, detail.node)
|
||||
ui.output.update("text", Array.isArray(text) ? text.join('\n\n') : text, detail.node)
|
||||
} else if (gifs && gifs[0]) {
|
||||
// if (!images) return;
|
||||
const src = `${get_url()}/view?filename=${encodeURIComponent(gifs[0].filename)}&type=${gifs[0].type}&subfolder=${encodeURIComponent(gifs[0].subfolder)
|
||||
}&&format=${gifs[0].format}&t=${+new Date()}`;
|
||||
|
||||
show(src, detail.node, gifs[0].format.match('video') ? 'video' : 'image');
|
||||
} else if (prompt && analysis) {
|
||||
// #ClipInterrogator: ……
|
||||
ui.output.update("text", `${prompt.join('\n\n')}\n${JSON.stringify(analysis, null, 2)}`, detail.node)
|
||||
}
|
||||
|
||||
|
||||
|
||||
try {
|
||||
ui.status.update(`executed_#${window._appData.data[detail.node]?.class_type}`);
|
||||
ui.submitButton.reset()
|
||||
@@ -1768,6 +1882,11 @@
|
||||
// console.log("b_preview", detail)
|
||||
// show(URL.createObjectURL(detail));
|
||||
// });
|
||||
api.addEventListener("execution_error", ({ detail }) => {
|
||||
|
||||
console.log("execution_error", detail)
|
||||
// show(URL.createObjectURL(detail));
|
||||
});
|
||||
|
||||
|
||||
api.addEventListener('execution_start', async ({ detail }) => {
|
||||
|
||||
@@ -126,11 +126,11 @@ function extractInputAndOutputData (jsonData, inputIds = [], outputIds = []) {
|
||||
output[outputIds.indexOf(id)] = { ...data[id], title: node.title, id }
|
||||
}
|
||||
|
||||
if (node.type === 'KSampler') {
|
||||
if (node.type === 'KSampler'||node.type=='SamplerCustom') {
|
||||
// seed 的类型收集
|
||||
try {
|
||||
seed[id] = node.widgets.filter(
|
||||
w => w.name === 'seed'
|
||||
w => (w.name === 'seed'||w.name=='noise_seed')
|
||||
)[0].linkedWidgets[0].value
|
||||
} catch (error) {}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { app } from '../../../scripts/app.js'
|
||||
const repoOwner = 'shadowcz007' // 替换为仓库的所有者
|
||||
const repoName = 'comfyui-mixlab-nodes' // 替换为仓库的名称
|
||||
|
||||
const version = 'v0.10.0'
|
||||
const version = 'v0.11.0'
|
||||
|
||||
fetch(`https://api.github.com/repos/${repoOwner}/${repoName}/releases/latest`)
|
||||
.then(response => response.json())
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
import { app } from '../../../scripts/app.js'
|
||||
// import { api } from '../../../scripts/api.js'
|
||||
import { ComfyWidgets } from '../../../scripts/widgets.js'
|
||||
import { $el } from '../../../scripts/ui.js'
|
||||
|
||||
app.registerExtension({
|
||||
name: 'Mixlab.prompt.ClipInterrogator',
|
||||
async beforeRegisterNodeDef (nodeType, nodeData, app) {
|
||||
if (nodeData.name === 'ClipInterrogator') {
|
||||
function populate (prompts, items) {
|
||||
if (this.widgets) {
|
||||
for (let i = 0; i < this.widgets.length; i++) {
|
||||
if (this.widgets[i].type !== 'combo') this.widgets[i].onRemove?.()
|
||||
}
|
||||
this.widgets.length = 2
|
||||
}
|
||||
|
||||
const w = ComfyWidgets['STRING'](
|
||||
this,
|
||||
'text',
|
||||
['STRING', { multiline: true }],
|
||||
app
|
||||
).widget
|
||||
w.inputEl.readOnly = true
|
||||
w.inputEl.style.opacity = 0.6
|
||||
|
||||
w.value = prompts.join('\n\n')
|
||||
|
||||
const w2 = ComfyWidgets['STRING'](
|
||||
this,
|
||||
'text',
|
||||
['STRING', { multiline: true }],
|
||||
app
|
||||
).widget
|
||||
w2.inputEl.readOnly = true
|
||||
w2.inputEl.style.opacity = 0.6
|
||||
|
||||
w2.value = JSON.stringify(items, null, 2)
|
||||
|
||||
console.log('ClipInterrogator',w,w2)
|
||||
requestAnimationFrame(() => {
|
||||
const sz = this.computeSize()
|
||||
if (sz[0] < this.size[0]) {
|
||||
sz[0] = this.size[0]
|
||||
}
|
||||
if (sz[1] < this.size[1]) {
|
||||
sz[1] = this.size[1]
|
||||
}
|
||||
this.onResize?.(sz)
|
||||
app.graph.setDirtyCanvas(true, false)
|
||||
})
|
||||
}
|
||||
|
||||
// When the node is executed we will be sent the input text, display this in the widget
|
||||
const onExecuted = nodeType.prototype.onExecuted
|
||||
nodeType.prototype.onExecuted = function (message) {
|
||||
onExecuted?.apply(this, arguments)
|
||||
console.log('##', message)
|
||||
populate.call(this, message.prompt, message.analysis)
|
||||
}
|
||||
|
||||
this.serialize_widgets = true //需要保存参数
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -256,6 +256,7 @@ app.registerExtension({
|
||||
const onExecuted = nodeType.prototype.onExecuted;
|
||||
nodeType.prototype.onExecuted = function (message) {
|
||||
onExecuted?.apply(this, arguments);
|
||||
console.log('##',message.text)
|
||||
populate.call(this, message.text);
|
||||
};
|
||||
|
||||
|
||||
@@ -35,6 +35,24 @@ function get_position_style (ctx, widget_width, y, node_height) {
|
||||
justifyContent: 'space-between'
|
||||
}
|
||||
}
|
||||
function createImage (url) {
|
||||
let im = new Image()
|
||||
return new Promise((res, rej) => {
|
||||
im.onload = () => res(im)
|
||||
im.src = url
|
||||
})
|
||||
}
|
||||
|
||||
async function fetchImage (url) {
|
||||
try {
|
||||
const response = await fetch(url)
|
||||
const blob = await response.blob()
|
||||
|
||||
return blob
|
||||
} catch (error) {
|
||||
console.error('出现错误:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const getLocalData = key => {
|
||||
let data = {}
|
||||
@@ -132,8 +150,8 @@ app.registerExtension({
|
||||
inp.click()
|
||||
inp.addEventListener('change', event => {
|
||||
// 获取选择的文件
|
||||
const file = event.target.files[0];
|
||||
this.title=file.name.split('.')[0];
|
||||
const file = event.target.files[0]
|
||||
this.title = file.name.split('.')[0]
|
||||
|
||||
// console.log(file.name.split('.')[0])
|
||||
// 创建文件读取器
|
||||
@@ -202,3 +220,147 @@ app.registerExtension({
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
app.registerExtension({
|
||||
name: 'Mixlab.prompt.PromptImage',
|
||||
_createResult: async (node, widget, message) => {
|
||||
widget.div.innerHTML = ``
|
||||
|
||||
const width = node.size[0] * 0.5 - 12
|
||||
|
||||
let height_add = 0
|
||||
|
||||
for (let index = 0; index < message._images.length; index++) {
|
||||
const img = message._images[index]
|
||||
let url = api.apiURL(
|
||||
`/view?filename=${encodeURIComponent(img.filename)}&type=${
|
||||
img.type
|
||||
}&subfolder=${
|
||||
img.subfolder
|
||||
}${app.getPreviewFormatParam()}${app.getRandParam()}`
|
||||
)
|
||||
|
||||
let image = await createImage(url)
|
||||
|
||||
// 创建card
|
||||
let div = document.createElement('div')
|
||||
div.className = 'card'
|
||||
div.draggable = true
|
||||
|
||||
div.ondragend = async event => {
|
||||
console.log('拖动停止')
|
||||
let url = div.querySelector('img').src
|
||||
|
||||
let blob = await fetchImage(url)
|
||||
|
||||
let imageNode = null
|
||||
// No image node selected: add a new one
|
||||
if (!imageNode) {
|
||||
const newNode = LiteGraph.createNode('LoadImage')
|
||||
newNode.pos = [...app.canvas.graph_mouse]
|
||||
imageNode = app.graph.add(newNode)
|
||||
app.graph.change()
|
||||
}
|
||||
|
||||
|
||||
// const blob = item.getAsFile();
|
||||
imageNode.pasteFile(blob)
|
||||
}
|
||||
|
||||
div.setAttribute('data-scale', image.naturalHeight / image.naturalWidth)
|
||||
|
||||
let h = (image.naturalHeight * width) / image.naturalWidth
|
||||
if (index % 2 === 0) height_add += h
|
||||
div.style = `width: ${width}px;height:${h}px;position: relative;margin: 4px;`
|
||||
div.innerHTML = `<img src="${url}" style='width: 100%'/>
|
||||
<p style="position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
background:#444444c2;
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
padding: 5px;
|
||||
text-align: left;">${message.prompts[index]}</p>`
|
||||
widget.div.appendChild(div)
|
||||
}
|
||||
|
||||
node.size[1] = 98 + height_add
|
||||
},
|
||||
async beforeRegisterNodeDef (nodeType, nodeData, app) {
|
||||
if (nodeType.comfyClass == 'PromptImage') {
|
||||
const orig_nodeCreated = nodeType.prototype.onNodeCreated
|
||||
nodeType.prototype.onNodeCreated = function () {
|
||||
orig_nodeCreated?.apply(this, arguments)
|
||||
console.log('#orig_nodeCreated', this)
|
||||
const widget = {
|
||||
type: 'div',
|
||||
name: 'result',
|
||||
draw (ctx, node, widget_width, y, widget_height) {
|
||||
Object.assign(this.div.style, {
|
||||
...get_position_style(ctx, widget_width, y, node.size[1]),
|
||||
flexWrap: 'wrap',
|
||||
justifyContent: 'space-between',
|
||||
// outline: '1px solid red',
|
||||
paddingLeft: '0px',
|
||||
width: widget_width + 'px'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
widget.div = $el('div', {})
|
||||
|
||||
document.body.appendChild(widget.div)
|
||||
|
||||
this.addCustomWidget(widget)
|
||||
|
||||
const onRemoved = this.onRemoved
|
||||
this.onRemoved = () => {
|
||||
widget.div.remove()
|
||||
return onRemoved?.()
|
||||
}
|
||||
|
||||
const onResize = this.onResize
|
||||
this.onResize = function () {
|
||||
// 缩放发生
|
||||
// console.log('##缩放发生', this.size)
|
||||
let w = this.size[0] * 0.5 - 12
|
||||
Array.from(widget.div.querySelectorAll('.card'), card => {
|
||||
card.style.width = `${w}px`
|
||||
card.style.height = `${
|
||||
w * parseFloat(card.getAttribute('data-scale'))
|
||||
}px`
|
||||
})
|
||||
return onResize?.apply(this, arguments)
|
||||
}
|
||||
|
||||
// this.serialize_widgets = true //需要保存参数
|
||||
}
|
||||
|
||||
const onExecuted = nodeType.prototype.onExecuted
|
||||
nodeType.prototype.onExecuted = async function (message) {
|
||||
onExecuted?.apply(this, arguments)
|
||||
console.log('PromptImage', message.prompts, message._images)
|
||||
// window._mixlab_app_json = message.json
|
||||
try {
|
||||
let widget = this.widgets.filter(w => w.name === 'result')[0]
|
||||
widget.value = message
|
||||
|
||||
this._createResult(this, widget, message)
|
||||
} catch (error) {}
|
||||
}
|
||||
|
||||
this.serialize_widgets = true //需要保存参数
|
||||
}
|
||||
},
|
||||
async loadedGraphNode (node, app) {
|
||||
if (node.type === 'PromptImage') {
|
||||
// await sleep(0)
|
||||
let widget = node.widgets.filter(w => w.name === 'result')[0]
|
||||
console.log('widget.value', widget.value)
|
||||
let cards = widget.div.querySelectorAll('.card')
|
||||
if (cards.length == 0) node.size = [280, 120]
|
||||
|
||||
this._createResult(node, widget, widget.value)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user