Compare commits

...
Author SHA1 Message Date
shadow 1daa1a4603 Merge pull request #111 from shadowcz007/v.11.0-PromptImage-node-图片和prompt匹配
V0.11.0 PromptImage & PromptSimplification
2024-01-06 00:17:26 +08:00
shadowcz007 062773d929 v0.11.0
> PromptImage & PromptSimplification,Assist in simplifying prompt words, comparing images and prompt word nodes.
2024-01-06 00:16:33 +08:00
shadowcz007 57decadaef Update index.html 2024-01-06 00:14:04 +08:00
shadowcz007 ec804ab7c9 优化 2024-01-05 23:09:08 +08:00
shadowcz007 3ee7533098 Update prompt_mixlab.js 2024-01-05 16:40:30 +08:00
shadowcz007 0d383ccc1f add PromptImage 2024-01-05 15:27:13 +08:00
shadowcz007 e2f2257c34 Update index.html 2024-01-05 12:22:23 +08:00
shadowcz007 d62b9fc4c6 ClipInterrogator可以作为输出 2024-01-05 11:52:04 +08:00
shadowcz007 be7ad0c7fb Update index.html 2024-01-04 23:35:56 +08:00
shadowcz007 156864cc8b PromptSimplification 2024-01-04 23:33:24 +08:00
shadowcz007 7240e496cc Update PromptNode.py 2024-01-04 23:02:48 +08:00
shadowcz007 8acdf4018d test PromptSimplification 2024-01-04 20:32:43 +08:00
shadowcz007 1ea7c3e203 修复floatSlide最大值问题 2024-01-04 19:43:33 +08:00
shadowcz007 e54aeb6125 Update index.html 2024-01-04 18:35:54 +08:00
shadowcz007 d59f51fbcf Update README.md 2024-01-04 18:20:52 +08:00
shadowcz007 a667eb6982 修复seed 为fixed 的运行按钮bug & 支持sd-xl 的SamplerCustom 2024-01-04 18:20:00 +08:00
shadowcz007 8d72732247 Update index.html 2024-01-04 17:19:46 +08:00
shadowcz007 f0f3b30a62 Update index.html 2024-01-04 17:03:36 +08:00
shadowcz007 d99fe24542 fixbug 2024-01-04 16:05:27 +08:00
shadowcz007 ea4c7381bd Update index.html 2024-01-04 14:07:38 +08:00
shadow e900d20641 Merge pull request #107 from shadowcz007/v0.10-add-clip-interrogator
Update index.html
2024-01-04 14:02:42 +08:00
shadow 968178bf57 Merge pull request #106 from shadowcz007/v0.10-add-clip-interrogator
V0.10 add clip interrogator
2024-01-04 13:37:31 +08:00
14 changed files with 553 additions and 40 deletions
+5
View File
@@ -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
View File
@@ -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.
+1
View File
@@ -4786,6 +4786,7 @@
"NewLayer",
"RandomPrompt",
"PromptSlide",
"PromptSimplification",
"ClipInterrogator",
"ScreenShare",
"ShowLayer",
+2 -1
View File
@@ -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:
+9 -7
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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 }) => {
+2 -2
View File
@@ -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) {}
}
+1 -1
View File
@@ -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())
+65
View File
@@ -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 //需要保存参数
}
}
})
+1
View File
@@ -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);
};
+164 -2
View File
@@ -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)
}
}
})