Compare commits
21
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8a46647d8c | ||
|
|
406a255db0 | ||
|
|
574557810e | ||
|
|
998a02c3a4 | ||
|
|
c6f964c921 | ||
|
|
efb0e147c5 | ||
|
|
9cf7356f98 | ||
|
|
af05c43174 | ||
|
|
380c68ff2b | ||
|
|
068b00b99f | ||
|
|
cd6a42ab64 | ||
|
|
f115abec92 | ||
|
|
f0e23cf878 | ||
|
|
a94f11d809 | ||
|
|
38972bea5f | ||
|
|
a761ff552a | ||
|
|
dbeb84ea9a | ||
|
|
0a4938f39a | ||
|
|
b2182c716d | ||
|
|
8253be73f6 | ||
|
|
20318e296e |
@@ -71,6 +71,9 @@ https://github.com/shadowcz007/comfyui-mixlab-nodes/assets/12645064/e7e77f90-e43
|
||||
|
||||

|
||||
|
||||
> ClipInterrogator
|
||||
|
||||
[add clip-interrogator](https://github.com/pharmapsychotic/clip-interrogator)
|
||||
|
||||
### 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.
|
||||
@@ -167,9 +170,7 @@ v0.8.0 🚀🚗🚚🏃 LaMaInpainting
|
||||
|
||||
[Download lama](https://github.com/enesmsahin/simple-lama-inpainting/releases/download/v0.1.0/big-lama.pt), move to : models/lama
|
||||
|
||||
<!-- ### Workflow
|
||||
[Workflow](./workflow.md) -->
|
||||
|
||||
[Download Salesforce\blip-image-captioning-base](https://huggingface.co/Salesforce/blip-image-captioning-base), move to : models/clip_interrogator/Salesforce/blip-image-captioning-base
|
||||
|
||||
## Installation
|
||||
|
||||
|
||||
@@ -512,6 +512,7 @@ from .nodes.ChatGPT import ChatGPTNode,ShowTextForGPT,CharacterInText
|
||||
from .nodes.Audio import GamePal,SpeechRecognition,SpeechSynthesis
|
||||
from .nodes.Utils import AppInfo,IntNumber,FloatSlider,TextInput,ColorInput,FontInput,TextToNumber,DynamicDelayProcessor,LimitNumber,SwitchByIndex,GetImageSize_,MultiplicationNode
|
||||
from .nodes.Lama import LaMaInpainting
|
||||
from .nodes.ClipInterrogator import ClipInterrogator
|
||||
|
||||
# 要导出的所有节点及其名称的字典
|
||||
# 注意:名称应全局唯一
|
||||
@@ -519,6 +520,7 @@ NODE_CLASS_MAPPINGS = {
|
||||
"AppInfo":AppInfo,
|
||||
"RandomPrompt":RandomPrompt,
|
||||
"PromptSlide":PromptSlide,
|
||||
"ClipInterrogator":ClipInterrogator,
|
||||
"NoiseImage":NoiseImage,
|
||||
"GradientImage":GradientImage,
|
||||
"TransparentImage":TransparentImage,
|
||||
|
||||
@@ -4786,6 +4786,7 @@
|
||||
"NewLayer",
|
||||
"RandomPrompt",
|
||||
"PromptSlide",
|
||||
"ClipInterrogator",
|
||||
"ScreenShare",
|
||||
"ShowLayer",
|
||||
"ShowTextForGPT",
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,158 @@
|
||||
import os
|
||||
import folder_paths
|
||||
|
||||
from PIL import Image
|
||||
import comfy.utils
|
||||
import numpy as np
|
||||
import json
|
||||
import torch
|
||||
|
||||
from transformers import AutoProcessor, BlipForConditionalGeneration
|
||||
|
||||
from clip_interrogator import Config, Interrogator
|
||||
|
||||
def load_caption_model(model_path,config,t='blip-base'):
|
||||
dtype=torch.float16 if config.device == 'cuda' else torch.float32
|
||||
caption_model = BlipForConditionalGeneration.from_pretrained(model_path, torch_dtype=dtype)
|
||||
|
||||
caption_processor = AutoProcessor.from_pretrained(model_path)
|
||||
|
||||
caption_model.eval()
|
||||
if not config.caption_offload:
|
||||
caption_model = caption_model.to(config.device)
|
||||
|
||||
return (caption_model,caption_processor)
|
||||
|
||||
|
||||
|
||||
caption_model_path=os.path.join(folder_paths.models_dir, "clip_interrogator/Salesforce/blip-image-captioning-base")
|
||||
if not os.path.exists(caption_model_path):
|
||||
print(f"## clip_interrogator_model not found: {caption_model_path}, pls download from https://huggingface.co/Salesforce/blip-image-captioning-base")
|
||||
|
||||
cache_path=os.path.join(folder_paths.models_dir, "clip_interrogator")
|
||||
|
||||
|
||||
|
||||
# Tensor to PIL
|
||||
def tensor2pil(image):
|
||||
return Image.fromarray(np.clip(255. * image.cpu().numpy().squeeze(), 0, 255).astype(np.uint8))
|
||||
|
||||
# Convert PIL to Tensor
|
||||
def pil2tensor(image):
|
||||
return torch.from_numpy(np.array(image).astype(np.float32) / 255.0).unsqueeze(0)
|
||||
|
||||
|
||||
def image_analysis(ci,image):
|
||||
image = image.convert('RGB')
|
||||
image_features = ci.image_to_features(image)
|
||||
|
||||
top_mediums = ci.mediums.rank(image_features, 5)
|
||||
top_artists = ci.artists.rank(image_features, 5)
|
||||
top_movements = ci.movements.rank(image_features, 5)
|
||||
top_trendings = ci.trendings.rank(image_features, 5)
|
||||
top_flavors = ci.flavors.rank(image_features, 5)
|
||||
|
||||
medium_ranks = {medium: sim for medium, sim in zip(top_mediums, ci.similarities(image_features, top_mediums))}
|
||||
artist_ranks = {artist: sim for artist, sim in zip(top_artists, ci.similarities(image_features, top_artists))}
|
||||
movement_ranks = {movement: sim for movement, sim in zip(top_movements, ci.similarities(image_features, top_movements))}
|
||||
trending_ranks = {trending: sim for trending, sim in zip(top_trendings, ci.similarities(image_features, top_trendings))}
|
||||
flavor_ranks = {flavor: sim for flavor, sim in zip(top_flavors, ci.similarities(image_features, top_flavors))}
|
||||
|
||||
return medium_ranks, artist_ranks, movement_ranks, trending_ranks, flavor_ranks
|
||||
|
||||
def image_to_prompt(ci,image, mode):
|
||||
ci.config.chunk_size = 2048 if ci.config.clip_model_name == "ViT-L-14/openai" else 1024
|
||||
ci.config.flavor_intermediate_count = 2048 if ci.config.clip_model_name == "ViT-L-14/openai" else 1024
|
||||
image = image.convert('RGB')
|
||||
if mode == 'best':
|
||||
return ci.interrogate(image)
|
||||
elif mode == 'classic':
|
||||
return ci.interrogate_classic(image)
|
||||
elif mode == 'fast':
|
||||
return ci.interrogate_fast(image)
|
||||
elif mode == 'negative':
|
||||
return ci.interrogate_negative(image)
|
||||
|
||||
# image = Image.open(image_path).convert('RGB')
|
||||
# ci = Interrogator(Config(clip_model_name="ViT-L-14/openai"))
|
||||
# print(ci.interrogate(image))
|
||||
|
||||
|
||||
class ClipInterrogator:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"image": ("IMAGE",),
|
||||
"prompt_mode": (['fast','classic','best','negative'],),
|
||||
"image_analysis": (["off","on"],),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING","STRING",)
|
||||
RETURN_NAMES = ("prompt","analysis",)
|
||||
|
||||
FUNCTION = "run"
|
||||
|
||||
CATEGORY = "♾️Mixlab/prompt"
|
||||
|
||||
INPUT_IS_LIST = True
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
global ci
|
||||
ci = None
|
||||
def run(self,image,prompt_mode,image_analysis):
|
||||
global ci
|
||||
|
||||
prompt_mode=prompt_mode[0]
|
||||
analysis=image_analysis[0]
|
||||
|
||||
prompt_result=[]
|
||||
analysis_result=[]
|
||||
|
||||
# 进度条
|
||||
pbar = comfy.utils.ProgressBar(len(image)*(2 if analysis=='on' else 1))
|
||||
|
||||
if ci==None:
|
||||
config=Config(
|
||||
clip_model_name="ViT-L-14/openai",
|
||||
device="cuda" if torch.cuda.is_available() else "cpu",
|
||||
download_cache=True,
|
||||
clip_model_path=cache_path,
|
||||
cache_path=cache_path
|
||||
)
|
||||
config.apply_low_vram_defaults()
|
||||
|
||||
caption_model,caption_processor=load_caption_model(caption_model_path,config)
|
||||
|
||||
config.caption_model= caption_model
|
||||
config.caption_processor= caption_processor
|
||||
|
||||
ci = Interrogator(config)
|
||||
# else:
|
||||
# simple_lama.model.to("cuda" if torch.cuda.is_available() else "cpu")
|
||||
|
||||
for i in range(len(image)):
|
||||
im=image[i]
|
||||
|
||||
im=tensor2pil(im)
|
||||
im=im.convert('RGB')
|
||||
|
||||
if analysis=='on':
|
||||
analysis_res=image_analysis(ci,im)
|
||||
analysis_result.append(json.dumps(analysis_res))
|
||||
pbar.update(1)
|
||||
|
||||
prompt=image_to_prompt(ci,im,prompt_mode)
|
||||
pbar.update(1)
|
||||
prompt_result.append(prompt)
|
||||
|
||||
|
||||
# result.save("inpainted.png")
|
||||
if ci.config.clip_offload and not ci.clip_offloaded:
|
||||
ci.clip_model = ci.clip_model.to('cpu')
|
||||
ci.clip_offloaded = True
|
||||
|
||||
if ci.config.caption_offload and not ci.caption_offloaded:
|
||||
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,)}
|
||||
+42
-17
@@ -516,26 +516,42 @@ def merge_images(bg_image, layer_image, mask, x, y, width, height, scale_option)
|
||||
return bg_image
|
||||
|
||||
|
||||
def resize_image(layer_image,scale_option,width,height):
|
||||
|
||||
def resize_image(layer_image, scale_option, width, height,color="white"):
|
||||
layer_image = layer_image.convert("RGB")
|
||||
original_width, original_height = layer_image.size
|
||||
|
||||
if scale_option == "height":
|
||||
# 按照高度比例缩放
|
||||
original_width, original_height = layer_image.size
|
||||
# Scale image based on height
|
||||
scale = height / original_height
|
||||
new_width = int(original_width * scale)
|
||||
layer_image = layer_image.resize((new_width, height))
|
||||
|
||||
elif scale_option == "width":
|
||||
# 按照宽度比例缩放
|
||||
original_width, original_height = layer_image.size
|
||||
# Scale image based on width
|
||||
scale = width / original_width
|
||||
new_height = int(original_height * scale)
|
||||
layer_image = layer_image.resize((width, new_height))
|
||||
|
||||
elif scale_option == "overall":
|
||||
# 整体缩放
|
||||
# Scale image overall
|
||||
layer_image = layer_image.resize((width, height))
|
||||
|
||||
elif scale_option == "center":
|
||||
# Scale image to minimum of width and height, center it, and fill extra area with black
|
||||
scale = min(width / original_width, height / original_height)
|
||||
new_width = int(original_width * scale)
|
||||
new_height = int(original_height * scale)
|
||||
resized_image = Image.new("RGB", (width, height), color=color)
|
||||
resized_image.paste(layer_image.resize((new_width, new_height)), ((width - new_width) // 2, (height - new_height) // 2))
|
||||
resized_image=resized_image.convert("RGB")
|
||||
return resized_image
|
||||
|
||||
return layer_image
|
||||
|
||||
|
||||
|
||||
|
||||
# def generate_text_image(text_list, font_path, font_size, text_color, vertical=True, spacing=0):
|
||||
# # Load Chinese font
|
||||
# font = ImageFont.truetype(font_path, font_size)
|
||||
@@ -937,20 +953,27 @@ class EnhanceImage:
|
||||
|
||||
CATEGORY = "♾️Mixlab/image"
|
||||
|
||||
INPUT_IS_LIST = False
|
||||
INPUT_IS_LIST = True
|
||||
|
||||
OUTPUT_IS_LIST = (False,)
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
|
||||
# 运行的函数
|
||||
def run(self,image,contrast):
|
||||
# print('EnhanceImage',image.shape)
|
||||
image=tensor2pil(image)
|
||||
|
||||
image=enhance_depth_map(image,contrast)
|
||||
# print('EnhanceImage',len(image),image[0].shape)
|
||||
contrast=contrast[0]
|
||||
res=[]
|
||||
for ims in image:
|
||||
for im in ims:
|
||||
|
||||
image=pil2tensor(image)
|
||||
image=tensor2pil(im)
|
||||
|
||||
image=enhance_depth_map(image,contrast)
|
||||
|
||||
image=pil2tensor(image)
|
||||
|
||||
res.append(image)
|
||||
|
||||
return (image,)
|
||||
return (res,)
|
||||
|
||||
|
||||
|
||||
@@ -1818,13 +1841,14 @@ class ResizeImage:
|
||||
"step": 1, #Slider's step
|
||||
"display": "number" # Cosmetic only: display as "number" or "slider"
|
||||
}),
|
||||
"scale_option": (["width","height",'overall'],),
|
||||
"scale_option": (["width","height",'overall','center'],),
|
||||
|
||||
},
|
||||
|
||||
"optional":{
|
||||
"image": ("IMAGE",),
|
||||
"average_color": (["on",'off'],),
|
||||
"fill_color":("STRING",{"multiline": False,"default": "#FFFFFF","dynamicPrompts": False}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1838,12 +1862,13 @@ class ResizeImage:
|
||||
INPUT_IS_LIST = True
|
||||
OUTPUT_IS_LIST = (True,True,)
|
||||
|
||||
def run(self,width,height,scale_option,image=None,average_color=['on']):
|
||||
def run(self,width,height,scale_option,image=None,average_color=['on'],fill_color=["#FFFFFF"]):
|
||||
|
||||
w=width[0]
|
||||
h=height[0]
|
||||
scale_option=scale_option[0]
|
||||
average_color=average_color[0]
|
||||
fill_color=fill_color[0]
|
||||
|
||||
imgs=[]
|
||||
average_images=[]
|
||||
@@ -1860,7 +1885,7 @@ class ResizeImage:
|
||||
else:
|
||||
for im in image:
|
||||
im=tensor2pil(im)
|
||||
im=resize_image(im,scale_option,w,h)
|
||||
im=resize_image(im,scale_option,w,h,fill_color)
|
||||
im=im.convert('RGB')
|
||||
|
||||
a_im=get_average_color_image(im)
|
||||
|
||||
+17
-5
@@ -34,13 +34,13 @@ def create_temp_file(image):
|
||||
) = folder_paths.get_save_image_path('tmp', output_dir)
|
||||
|
||||
|
||||
image=tensor2pil(image)
|
||||
im=tensor2pil(image)
|
||||
|
||||
image_file = f"{filename}_{counter:05}.png"
|
||||
|
||||
image_path=os.path.join(full_output_folder, image_file)
|
||||
|
||||
image.save(image_path,compress_level=4)
|
||||
im.save(image_path,compress_level=4)
|
||||
|
||||
return [{
|
||||
"filename": image_file,
|
||||
@@ -461,12 +461,24 @@ class AppInfo:
|
||||
|
||||
CATEGORY = "♾️Mixlab"
|
||||
|
||||
INPUT_IS_LIST = False
|
||||
OUTPUT_IS_LIST = (False,)
|
||||
INPUT_IS_LIST = True
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
|
||||
def run(self,name,image,input_ids,output_ids,description,version,share_prefix,link,category):
|
||||
name=name[0]
|
||||
im=image[0][0]
|
||||
# image [img,] img[batch,w,h,a] 列表里面是batch,
|
||||
|
||||
im=create_temp_file(image)
|
||||
input_ids=input_ids[0]
|
||||
output_ids=output_ids[0]
|
||||
description=description[0]
|
||||
version=version[0]
|
||||
share_prefix=share_prefix[0]
|
||||
link=link[0]
|
||||
category=category[0]
|
||||
|
||||
#TODO batch 的方式需要处理
|
||||
im=create_temp_file(im)
|
||||
|
||||
# id=get_json_hash([name,im,input_ids,output_ids,description,version])
|
||||
|
||||
|
||||
+2
-1
@@ -4,4 +4,5 @@ watchdog
|
||||
opencv-python-headless
|
||||
matplotlib
|
||||
openai
|
||||
simple-lama-inpainting
|
||||
simple-lama-inpainting
|
||||
clip-interrogator==0.6.0
|
||||
+182
-28
@@ -276,7 +276,7 @@
|
||||
|
||||
.show_text {
|
||||
font-size: 14px;
|
||||
/* display: inline-block; */
|
||||
user-select: text;
|
||||
margin: 8px;
|
||||
padding: 32px;
|
||||
min-width: 200px;
|
||||
@@ -302,7 +302,7 @@
|
||||
color: #555;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
select,
|
||||
button,
|
||||
@@ -399,6 +399,47 @@
|
||||
|
||||
}
|
||||
|
||||
async function getQueue(clientId) {
|
||||
try {
|
||||
|
||||
const res = await fetch(`${get_url()}/queue`);
|
||||
const data = await res.json();
|
||||
return {
|
||||
// Running action uses a different endpoint for cancelling
|
||||
Running: Array.from(data.queue_running, prompt => {
|
||||
if (prompt[3].client_id === clientId) {
|
||||
let prompt_id = prompt[1];
|
||||
return {
|
||||
prompt_id,
|
||||
remove: () => interrupt(),
|
||||
}
|
||||
}
|
||||
}),
|
||||
Pending: data.queue_pending.map((prompt) => ({ prompt })),
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return { Running: [], Pending: [] };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function interrupt() {
|
||||
try {
|
||||
await fetch(`${get_url()}/interrupt`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: undefined
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
function randomSeed(seed, data) {
|
||||
for (const id in data) {
|
||||
@@ -939,9 +980,20 @@
|
||||
|
||||
// Create an input field for the image name
|
||||
const textInput = document.createElement("textarea");
|
||||
// uploadImageInput.type = "text";
|
||||
// textInput.className=;
|
||||
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;
|
||||
} catch (error) {
|
||||
|
||||
}
|
||||
|
||||
uploadContainer.appendChild(textInput);
|
||||
// autoResize(textInput);
|
||||
|
||||
function autoResize(textarea) {
|
||||
textarea.style.height = 'auto';
|
||||
@@ -952,6 +1004,11 @@
|
||||
// console.log(textInput.value)
|
||||
autoResize(textInput);
|
||||
window._appData.data[data.id].inputs.text = textInput.value;
|
||||
|
||||
localStorage.setItem(`t_${data.id}`, JSON.stringify({
|
||||
value: textInput.value,
|
||||
height: textInput.style.height
|
||||
}));
|
||||
})
|
||||
|
||||
// Append the upload container to the main container
|
||||
@@ -1104,6 +1161,12 @@
|
||||
function createNumSlide(labelText, value = 0, callback, minValue = 0, maxValue = 1, type = 'float', keywords = null, targetId = null) {
|
||||
|
||||
value = 'float' ? parseFloat(value.toFixed(3)) : parseInt(value)
|
||||
try {
|
||||
let i=parseFloat(localStorage.getItem(`_slider_${targetId}`))
|
||||
if(!!i) value = i
|
||||
} catch (error) {
|
||||
|
||||
}
|
||||
|
||||
// 输入div
|
||||
let inputDiv = document.createElement('div');
|
||||
@@ -1126,13 +1189,17 @@
|
||||
if (keywords && keywords[0]) {
|
||||
// label.innerHTML = ``
|
||||
// 有备选的关键词
|
||||
let selectTag = createSelect(Array.from(keywords, (k,i) => {
|
||||
|
||||
let defaultValue = (targetId ? localStorage.getItem(`_slide_${targetId}`) : '') || keywords[0];
|
||||
|
||||
let selectTag = createSelect(Array.from(keywords, (k, i) => {
|
||||
return {
|
||||
value: k,
|
||||
text: k,
|
||||
selected:i==0
|
||||
selected: i == 0
|
||||
}
|
||||
}), keywords[0]);
|
||||
}), defaultValue);
|
||||
|
||||
selectTag.style = `background: none;
|
||||
color: black; max-width: 300px;
|
||||
border-bottom: 1px solid #acacac;
|
||||
@@ -1141,8 +1208,8 @@
|
||||
selectTag.addEventListener('change', e => {
|
||||
e.preventDefault();
|
||||
window._appData.data[targetId].inputs.prompt_keyword = selectTag.value;
|
||||
// label.querySelector('.label').innerText = selectTag.value
|
||||
// console.log(window._appData.data[targetId].inputs.prompt_keyword,selectTag.value)
|
||||
|
||||
targetId ? localStorage.setItem(`_slide_${targetId}`, selectTag.value) : ''
|
||||
})
|
||||
label.appendChild(selectTag);
|
||||
}
|
||||
@@ -1177,6 +1244,8 @@
|
||||
label.setAttribute('data-content', value);
|
||||
// 在这里可以执行其他操作,根据需要进行相应的处理
|
||||
callback && callback(value)
|
||||
|
||||
localStorage.setItem(`_slider_${targetId}`, value)
|
||||
});
|
||||
|
||||
// 返回容器元素
|
||||
@@ -1199,7 +1268,7 @@
|
||||
|
||||
// 设置默认值
|
||||
selectElement.value = defaultValue;
|
||||
console.log(defaultValue,options)
|
||||
// console.log(defaultValue, options)
|
||||
return selectElement
|
||||
}
|
||||
|
||||
@@ -1433,7 +1502,6 @@
|
||||
update: async function (type = "image", val, id) {
|
||||
console.log(val, id)
|
||||
if (val && type == "image" && output.querySelector(`#output_${id} img`)) {
|
||||
// if (output.querySelector(`#output_${id}`)) {
|
||||
|
||||
let im = await createImage(val)
|
||||
|
||||
@@ -1445,10 +1513,33 @@
|
||||
a.setAttribute('target', "_blank");
|
||||
a.setAttribute('href', val);
|
||||
|
||||
// }
|
||||
// else {
|
||||
// output.querySelector(`#output_${id}`).src = val;
|
||||
// }
|
||||
}
|
||||
|
||||
if (val && type == "images" && output.querySelector(`#output_${id} img`)) {
|
||||
let imgDiv = output.querySelector(`#output_${id}`)
|
||||
imgDiv.style.display = 'none';
|
||||
|
||||
// 清空
|
||||
// Array.from(imgDiv.parentElement.querySelectorAll('.output_images'), im => im.remove());
|
||||
|
||||
for (const v of val) {
|
||||
let im = await createImage(v);
|
||||
|
||||
// 构建新的
|
||||
let a = document.createElement('a');
|
||||
a.className = `${imgDiv.id} output_images`
|
||||
a.setAttribute('data-pswp-width', im.naturalWidth);
|
||||
a.setAttribute('data-pswp-height', im.naturalHeight);
|
||||
a.setAttribute('target', "_blank");
|
||||
a.setAttribute('href', v);
|
||||
|
||||
let img = new Image();
|
||||
// img;
|
||||
img.src = v;
|
||||
a.appendChild(img)
|
||||
// imgDiv.parentElement.appendChild(a);
|
||||
imgDiv.parentElement.insertBefore(a, imgDiv.parentElement.firstChild);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1478,19 +1569,42 @@
|
||||
},
|
||||
submitButton: {
|
||||
element: submitButton,
|
||||
update: function (callback) {
|
||||
update: function (runFn, cancelFn) {
|
||||
submitButton.addEventListener('dblclick', (e) => {
|
||||
e.preventDefault()
|
||||
submitButton.classList.remove('disabled');
|
||||
});
|
||||
submitButton.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (submitButton.classList.contains('data-click')) {
|
||||
return
|
||||
} else {
|
||||
submitButton.classList.add('data-click')
|
||||
setTimeout(() => submitButton.classList.remove('data-click'), 500)
|
||||
}
|
||||
|
||||
if (!submitButton.classList.contains('disabled')) {
|
||||
callback && callback();
|
||||
runFn && runFn();
|
||||
submitButton.classList.add('disabled');
|
||||
setTimeout(() => submitButton.classList.remove('disabled'), 500)
|
||||
submitButton.innerText = 'Cancel'
|
||||
} else {
|
||||
// 如果能取消
|
||||
let canCancel = cancelFn && cancelFn();
|
||||
if (canCancel) submitButton.classList.remove('disabled');
|
||||
if (canCancel) submitButton.innerText = 'Create';
|
||||
}
|
||||
|
||||
});
|
||||
},
|
||||
running: () => {
|
||||
submitButton.innerText = 'Cancel';
|
||||
if (!submitButton.classList.contains('disabled')) submitButton.classList.add('disabled');
|
||||
},
|
||||
reset: () => {
|
||||
submitButton.innerText = 'Create';
|
||||
submitButton.classList.remove('disabled');
|
||||
submitButton.classList.remove('data-click');
|
||||
}
|
||||
},
|
||||
};
|
||||
@@ -1522,7 +1636,6 @@
|
||||
Array.from(detail.querySelectorAll('.card'), c => c.classList.remove('selected'));
|
||||
div.className = 'upload_btn card selected'
|
||||
|
||||
|
||||
let { output, app } = jsonData;
|
||||
|
||||
window._appData = {
|
||||
@@ -1561,10 +1674,19 @@
|
||||
ui.status.update(appData ? 'READY' : '-');
|
||||
|
||||
// 添加提交按钮点击事件
|
||||
ui.submitButton.update(function () {
|
||||
// 在提交按钮点击时执行的逻辑
|
||||
queuePrompt(window._appData.data, window._appData.seed, api.clientId)
|
||||
});
|
||||
ui.submitButton.update(
|
||||
() => {
|
||||
// 在提交按钮点击时执行的逻辑
|
||||
queuePrompt(window._appData.data, window._appData.seed, api.clientId);
|
||||
}, () => {
|
||||
// 取消
|
||||
if (api.runningCancel) {
|
||||
api.runningCancel();
|
||||
api.runningCancel = null;
|
||||
return true
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
const show = (src, id, type = "image") => {
|
||||
@@ -1586,12 +1708,13 @@
|
||||
console.log("progress", detail);
|
||||
try {
|
||||
ui.status.update(`${detail.value}/${detail.max}`);
|
||||
ui.submitButton.running()
|
||||
} catch (error) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
api.addEventListener("executed", ({ detail }) => {
|
||||
api.addEventListener("executed", async ({ detail }) => {
|
||||
console.log("executed", detail)
|
||||
// if (!enabled) return;
|
||||
const images = detail?.output?.images;
|
||||
@@ -1599,9 +1722,14 @@
|
||||
const gifs = detail?.output?.gifs;
|
||||
|
||||
if (images) {
|
||||
// if (!images) return;
|
||||
const src = `${get_url()}/view?filename=${encodeURIComponent(images[0].filename)}&type=${images[0].type}&subfolder=${encodeURIComponent(images[0].subfolder)}&t=${+new Date()}`;
|
||||
show(src, detail.node, 'image');
|
||||
// if (!images) return;
|
||||
|
||||
let url = get_url();
|
||||
|
||||
show(Array.from(images, img => {
|
||||
return `${url}/view?filename=${encodeURIComponent(img.filename)}&type=${img.type}&subfolder=${encodeURIComponent(img.subfolder)}&t=${+new Date()}`;
|
||||
}), detail.node, 'images');
|
||||
|
||||
} else if (text) {
|
||||
ui.output.update("text", Array.isArray(text) ? text[0] : text, detail.node)
|
||||
} else if (gifs && gifs[0]) {
|
||||
@@ -1614,11 +1742,25 @@
|
||||
|
||||
|
||||
try {
|
||||
ui.status.update(`executed_#${detail.node}`);
|
||||
ui.status.update(`executed_#${window._appData.data[detail.node]?.class_type}`);
|
||||
ui.submitButton.reset()
|
||||
} catch (error) {
|
||||
|
||||
}
|
||||
|
||||
// console.log(Running, Pending);
|
||||
try {
|
||||
const { Running, Pending } = await getQueue(api.clientId);
|
||||
if (Running && Running[0]) {
|
||||
api.runningCancel = Running[0].remove;
|
||||
ui.submitButton.running()
|
||||
} else {
|
||||
api.runningCancel = null;
|
||||
}
|
||||
} catch (error) {
|
||||
api.runningCancel = null;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// api.addEventListener("b_preview", ({ detail }) => {
|
||||
@@ -1628,13 +1770,25 @@
|
||||
// });
|
||||
|
||||
|
||||
api.addEventListener('execution_start', ({ detail }) => {
|
||||
api.addEventListener('execution_start', async ({ detail }) => {
|
||||
console.log("execution_start", detail)
|
||||
try {
|
||||
ui.status.update(`execution_start`);
|
||||
ui.submitButton.running()
|
||||
} catch (error) {
|
||||
|
||||
}
|
||||
|
||||
try {
|
||||
const { Running, Pending } = await getQueue(api.clientId);
|
||||
if (Running && Running[0]) {
|
||||
api.runningCancel = Running[0].remove;
|
||||
} else {
|
||||
api.runningCancel = null;
|
||||
}
|
||||
} catch (error) {
|
||||
api.runningCancel = null;
|
||||
}
|
||||
})
|
||||
|
||||
api.api_base = ""
|
||||
|
||||
@@ -196,6 +196,9 @@ app.registerExtension({
|
||||
}
|
||||
if (bg) {
|
||||
data.bg_image = await parseImage(bg)
|
||||
if (!data.bg_image.match('data:image/')) {
|
||||
delete data.bg_image
|
||||
}
|
||||
}
|
||||
|
||||
if (material) {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { app } from '../../../scripts/app.js'
|
||||
const repoOwner = 'shadowcz007' // 替换为仓库的所有者
|
||||
const repoName = 'comfyui-mixlab-nodes' // 替换为仓库的名称
|
||||
|
||||
const version = 'v0.9.1'
|
||||
const version = 'v0.10.0'
|
||||
|
||||
fetch(`https://api.github.com/repos/${repoOwner}/${repoName}/releases/latest`)
|
||||
.then(response => response.json())
|
||||
|
||||
@@ -132,8 +132,10 @@ app.registerExtension({
|
||||
inp.click()
|
||||
inp.addEventListener('change', event => {
|
||||
// 获取选择的文件
|
||||
const file = event.target.files[0]
|
||||
const file = event.target.files[0];
|
||||
this.title=file.name.split('.')[0];
|
||||
|
||||
// console.log(file.name.split('.')[0])
|
||||
// 创建文件读取器
|
||||
const reader = new FileReader()
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user