Compare commits

...
17 Commits
Author SHA1 Message Date
shadowcz007 f847dcccf4 v0.2.5.2 2023-12-05 17:22:51 +08:00
shadowcz007 ff3f8f52d0 update 2023-12-05 17:22:28 +08:00
shadowcz007 d7d46682fc 优化GPT 2023-12-05 13:47:08 +08:00
shadowcz007 dfe720f3ec v0.2.5.1 2023-12-05 00:22:43 +08:00
shadowcz007 2c68662c22 文件名冲突引起的插件不生效 2023-12-05 00:21:43 +08:00
shadowcz007 bc1b998ba5 Update gpt.js 2023-12-04 23:56:44 +08:00
shadowcz007 9d5ccc3389 v0.2.5 2023-12-04 20:15:10 +08:00
shadowcz007 a811f884cc update 2023-12-04 20:07:29 +08:00
shadowcz007 8b86c379d1 v0.2.5
新增GPT节点
2023-12-04 20:01:51 +08:00
shadowcz007 8c0321b1cf Update ui.js 2023-12-02 20:03:38 +08:00
shadowcz007 62ab2c3514 readme 2023-12-02 17:22:24 +08:00
shadowcz007 ed61ca761a v0.2.4
Clicking on the floating window image can copy it to the clipboard.
2023-12-02 11:55:15 +08:00
shadowcz007 95ca17d816 点击悬浮窗图片可以拷贝到剪切板 2023-12-02 11:54:30 +08:00
shadowcz007 db6c721a8f 单击图片可复制到剪切板 2023-12-02 11:35:27 +08:00
shadow b5c68751aa Merge pull request #17 from shadowcz007/v0.3-psd读取分层
V0.3 psd读取分层
2023-12-02 00:42:46 +08:00
shadowcz007 7780bfd671 v0.2.3 2023-12-02 00:42:21 +08:00
shadow a56970693a Merge pull request #15 from shadowcz007/main
1
2023-12-01 23:37:02 +08:00
24 changed files with 1626 additions and 969 deletions
+26 -5
View File
@@ -14,6 +14,24 @@ https://github.com/shadowcz007/comfyui-mixlab-nodes/assets/12645064/e7e77f90-e43
!! Please use the address with HTTPS (https://127.0.0.1).
### LoadImagesFromLocal
> Monitor changes to images in a local folder, and trigger real-time execution of workflows, supporting common image formats, especially PSD format, in conjunction with Photoshop. Q: Translate into English
![watch](./assets/4-loadfromlocal-watcher-workflow.svg)
[workflow-4](./workflow/4-loadfromlocal-watcher-workflow.json)
### GPT
> ChatGPT、ChatGLM3 , Some code provided by rui. If you are using OpenAI's service, fill in https://api.openai.com/v1 . If you are using a local LLM service, fill in http://127.0.0.1:xxxx/v1
![gpt-workflow.svg](./assets/gpt-workflow.svg)
[workflow-5](./workflow/5-gpt-workflow.json)
## Installation
manually install, simply clone the repo into the custom_nodes directory with this command:
@@ -47,7 +65,7 @@ pip3 install -r requirements.txt
## Nodes
![main](./assets/all.png)
![main](./assets/all-workflow.svg)
![main2](./assets/detect-face-all.png)
[workflow-1](./workflow/1-workflow.json)
@@ -62,11 +80,7 @@ pip3 install -r requirements.txt
> LoadImagesFromLocal
![watch](./assets/load-watch.png)
[workflow-4](./workflow/4-loadfromlocal-watcher-workflow.json)
> Consistency Decoder
@@ -84,6 +98,13 @@ Add edges to an image.
![FeatheredMask](./assets/FlVou_Y6kaGWYoEj1Tn0aTd4AjMI.jpg)
### Improvement
An improvement has been made to directly redirect to GitHub to search for missing nodes when loading the graph.
![node-not-found](./assets/node-not-found.png)
### Models
[Download CLIPSeg](https://huggingface.co/CIDAS/clipseg-rd64-refined/tree/main), move to : model/clipseg
+9 -3
View File
@@ -260,6 +260,7 @@ from .nodes.ImageNode import TransparentImage,LoadImagesFromPath,AreaToMask,Smoo
from .nodes.Vae import VAELoader,VAEDecode
from .nodes.ScreenShareNode import ScreenShareNode,FloatingVideo
from .nodes.Clipseg import CLIPSeg,CombineMasks
from .nodes.ChatGPT import ChatGPTNode,ShowTextForGPT,CharacterInText
# 要导出的所有节点及其名称的字典
# 注意:名称应全局唯一
@@ -279,17 +280,22 @@ NODE_CLASS_MAPPINGS = {
"ScreenShare":ScreenShareNode,
"FloatingVideo":FloatingVideo,
"CLIPSeg_":CLIPSeg,
"CombineMasks_":CombineMasks
"CombineMasks_":CombineMasks,
"ChatGPT":ChatGPTNode,
"ShowTextForGPT":ShowTextForGPT,
"CharacterInText":CharacterInText
}
# 一个包含节点友好/可读的标题的字典
NODE_DISPLAY_NAME_MAPPINGS = {
"RandomPrompt": "Random Prompt #Example Node",
"RandomPrompt": "Random Prompt #Mixlab",
"SplitLongMask":"Splitting a long image into sections",
"VAELoaderConsistencyDecoder":"Consistency Decoder Loader",
"VAEDecodeConsistencyDecoder":"Consistency Decoder Decode",
"ScreenShare":"ScreenShare #Mixlab",
"FloatingVideo":"FloatingVideo #Mixlab"
"FloatingVideo":"FloatingVideo #Mixlab",
"ChatGPT":"ChatGPT #Mixlab",
"ShowTextForGPT":"ShowTextForGPT #Mixlab"
}
# web ui的节点功能
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 522 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 3.3 MiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 257 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

+200
View File
@@ -0,0 +1,200 @@
import openai
import time
import urllib.error
import re,json
# 判断是否是azure服务
def is_azure_url(url):
pattern = r'.*\.azure\.com$'
if re.match(pattern, url):
return True
else:
return False
def azure_client(key,url):
client = openai.AzureOpenAI(
api_key=key,
# https://learn.microsoft.com/en-us/azure/ai-services/openai/reference#rest-api-versioning
api_version="2023-07-01-preview",
# https://learn.microsoft.com/en-us/azure/cognitive-services/openai/how-to/create-resource?pivots=web-portal#create-a-resource
azure_endpoint=url
)
return client
def openai_client(key,url):
client = openai.OpenAI(
api_key=key,
base_url=url
)
return client
def chat(client, model_name,messages ):
try_count = 0
while True:
try_count += 1
try:
response = client.chat.completions.create(
model=model_name,
messages=messages
)
break
except openai.AuthenticationError as ex:
raise ex
except (urllib.error.HTTPError, openai.OpenAIError) as ex:
if try_count >= 3:
raise ex
time.sleep(5)
continue
finish_reason = response.choices[0].finish_reason
if finish_reason != "stop":
raise RuntimeError("API finished with unexpected reason: " + finish_reason)
content=""
try:
content=response.choices[0].message.content
except:
content=response.choices[0].delta['content']
return content
class ChatGPTNode:
def __init__(self):
# self.__client = OpenAI()
self.session_history = [] # 用于存储会话历史的列表
# self.seed=0
self.system_content="You are ChatGPT, a large language model trained by OpenAI. Answer as concisely as possible."
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"api_key":("KEY", {"default": "", "multiline": True}),
"api_url":("URL", {"default": "", "multiline": True}),
"prompt": ("STRING", {"default": "", "multiline": True}),
"system_content": ("STRING",
{
"default": "You are ChatGPT, a large language model trained by OpenAI. Answer as concisely as possible.",
"multiline": True
}),
"model": (["gpt-3.5-turbo", "gpt-3.5-turbo-16k-0613", "gpt-4-0613","gpt-4-1106-preview"],
{"default": "gpt-3.5-turbo"}),
"seed": ("INT", {"default": 0, "min": 0, "max": 10000, "step": 1}),
"context_size":("INT", {"default": 1, "min": 0, "max":30, "step": 1}),
},
}
RETURN_TYPES = ("STRING","STRING","STRING",)
RETURN_NAMES = ("text","messages","session_history",)
FUNCTION = "generate_contextual_text"
CATEGORY = "♾️Mixlab/GPT"
INPUT_IS_LIST = False
OUTPUT_IS_LIST = (False,False,False,)
def generate_contextual_text(self,
api_key,
api_url,
prompt,
system_content,
model,
seed,context_size):
# print(api_key!='',api_url,prompt,system_content,model,seed)
# 可以选择保留会话历史以维持上下文记忆
# 或者在此处清除会话历史 self.session_history.clear()
# if seed!=self.seed:
# self.seed=seed
# self.session_history=[]
# 把系统信息和初始信息添加到会话历史中
if system_content:
self.system_content=system_content
# self.session_history=[]
# self.session_history.append({"role": "system", "content": system_content})
#
if is_azure_url(api_url):
client=azure_client(api_key,api_url)
else:
client=openai_client(api_key,api_url)
print('openai url')
# 把用户的提示添加到会话历史中
# 调用API时传递整个会话历史
def crop_list_tail(lst, size):
if size >= len(lst):
return lst
elif size==0:
return []
else:
return lst[-size:]
session_history=crop_list_tail(self.session_history,context_size)
messages=[{"role": "system", "content": self.system_content}]+session_history+[{"role": "user", "content": prompt}]
response_content = chat(client,model,messages)
self.session_history=self.session_history+[{"role": "user", "content": prompt}]+[{'role':'assistant',"content":response_content}]
return (response_content,json.dumps(messages, indent=4),json.dumps(self.session_history, indent=4),)
class ShowTextForGPT:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"text": ("STRING", {"forceInput": True}),
}
}
INPUT_IS_LIST = True
RETURN_TYPES = ("STRING",)
FUNCTION = "run"
OUTPUT_NODE = True
OUTPUT_IS_LIST = (True,)
CATEGORY = "♾️Mixlab/GPT"
def run(self, text):
# print(session_history)
return {"ui": {"text": text}, "result": (text,)}
class CharacterInText:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"text": ("STRING", {"multiline": True}),
"character": ("STRING", {"multiline": True}),
"start_index": ("INT", {
"default": 1,
"min": 0, #Minimum value
"max": 1024, #Maximum value
"step": 1, #Slider's step
"display": "number" # Cosmetic only: display as "number" or "slider"
}),
}
}
INPUT_IS_LIST = False
RETURN_TYPES = ("INT",)
FUNCTION = "run"
# OUTPUT_NODE = True
OUTPUT_IS_LIST = (False,)
CATEGORY = "♾️Mixlab/GPT"
def run(self, text,character,start_index):
# print(text,character,start_index)
b=1 if character in text else 0
return (b+start_index,)
+2 -2
View File
@@ -99,7 +99,7 @@ class CLIPSeg:
}
}
CATEGORY = "Mixlab/mask"
CATEGORY = "♾️Mixlab/mask"
RETURN_TYPES = ("MASK", "IMAGE", "IMAGE",)
RETURN_NAMES = ("Mask","Heatmap Mask", "BW Mask")
@@ -204,7 +204,7 @@ class CombineMasks:
},
}
CATEGORY = "Mixlab/mask"
CATEGORY = "♾️Mixlab/mask"
RETURN_TYPES = ("MASK", "IMAGE", "IMAGE",)
RETURN_NAMES = ("Combined Mask","Heatmap Mask", "BW Mask")
+71 -43
View File
@@ -1,6 +1,6 @@
import numpy as np
import torch
from PIL import Image, ImageOps,ImageFilter,ImageEnhance,ImageDraw
from PIL import Image, ImageOps,ImageFilter,ImageEnhance,ImageDraw,ImageSequence
from PIL.PngImagePlugin import PngInfo
import base64,os
from io import BytesIO
@@ -153,25 +153,48 @@ def get_not_transparent_area(image):
return (x, y, w, h)
# 读取不了分层
def load_psd(image):
layers=[]
print('load_psd',image.format)
if image.format=='PSD':
layers = [frame.copy() for frame in ImageSequence.Iterator(image)]
print('#PSD',len(layers))
else:
image = ImageOps.exif_transpose(image) #校对方向
layers.append(image)
return layers
def load_image(fp,white_bg=False):
i = Image.open(fp)
i = ImageOps.exif_transpose(i)
image = i.convert("RGB")
image = np.array(image).astype(np.float32) / 255.0
image = torch.from_numpy(image)[None,]
if 'A' in i.getbands():
mask = np.array(i.getchannel('A')).astype(np.float32) / 255.0
mask = 1. - torch.from_numpy(mask)
if white_bg==True:
nw = mask.unsqueeze(0).unsqueeze(-1).repeat(1, 1, 1, 3)
# 将mask的黑色部分对image进行白色处理
image[nw == 1] = 1.0
else:
mask = torch.zeros((64,64), dtype=torch.float32, device="cpu")
return (image,mask)
im = Image.open(fp)
# ims=load_psd(im)
im = ImageOps.exif_transpose(im) #校对方向
ims=[im]
images=[]
for i in ims:
image = i.convert("RGB")
image = np.array(image).astype(np.float32) / 255.0
image = torch.from_numpy(image)[None,]
if 'A' in i.getbands():
mask = np.array(i.getchannel('A')).astype(np.float32) / 255.0
mask = 1. - torch.from_numpy(mask)
if white_bg==True:
nw = mask.unsqueeze(0).unsqueeze(-1).repeat(1, 1, 1, 3)
# 将mask的黑色部分对image进行白色处理
image[nw == 1] = 1.0
else:
mask = torch.zeros((64,64), dtype=torch.float32, device="cpu")
images.append({
"image":image,
"mask":mask
})
return images
# 获取图片s
@@ -183,23 +206,27 @@ def get_images_filepath(f,white_bg=False):
for file in files:
file_path = os.path.join(root, file)
try:
(im,mask)=load_image(file_path,white_bg)
images.append({
"image":im,
"mask":mask,
"file_path":file_path
})
imgs=load_image(file_path,white_bg)
for img in imgs:
images.append({
"image":img['image'],
"mask":img['mask'],
"file_path":file_path,
"psd":len(imgs)>1
})
except:
print('非图片',file_path)
elif os.path.isfile(f):
try:
(im,mask)=load_image(f,white_bg)
images.append({
"image":im,
"mask":mask,
"file_path":f
})
imgs=load_image(f,white_bg)
for img in imgs:
images.append({
"image":img['image'],
"mask":img['mask'],
"file_path":file_path,
"psd":len(imgs)>1
})
except:
print('非图片',f)
else:
@@ -314,7 +341,7 @@ class SmoothMask:
FUNCTION = "run"
CATEGORY = "Mixlab/mask"
CATEGORY = "♾️Mixlab/mask"
INPUT_IS_LIST = False
@@ -362,7 +389,7 @@ class FeatheredMask:
FUNCTION = "run"
CATEGORY = "Mixlab/mask"
CATEGORY = "♾️Mixlab/mask"
OUTPUT_IS_LIST = (False,)
@@ -429,7 +456,7 @@ class SplitLongMask:
FUNCTION = "run"
CATEGORY = "Mixlab/mask"
CATEGORY = "♾️Mixlab/mask"
OUTPUT_IS_LIST = (True,)
@@ -472,7 +499,7 @@ class TransparentImage:
FUNCTION = "run"
CATEGORY = "Mixlab/image"
CATEGORY = "♾️Mixlab/image"
# INPUT_IS_LIST = True, 一个batch传进来
OUTPUT_IS_LIST = (True,True,True,)
@@ -541,7 +568,7 @@ class EnhanceImage:
FUNCTION = "run"
CATEGORY = "Mixlab/image"
CATEGORY = "♾️Mixlab/image"
INPUT_IS_LIST = False
@@ -600,7 +627,7 @@ class LoadImagesFromPath:
FUNCTION = "run"
CATEGORY = "Mixlab/image"
CATEGORY = "♾️Mixlab/image"
# INPUT_IS_LIST = True
OUTPUT_IS_LIST = (True,True,False,)
@@ -613,14 +640,15 @@ class LoadImagesFromPath:
global watcher_folder
# print('###监听:',watcher_folder,watcher,file_path,result)
if watcher=='enable':
if watcher_folder==None:
watcher_folder = FolderWatcher(file_path)
if watcher_folder==None:
watcher_folder = FolderWatcher(file_path)
watcher_folder.set_folder_path(file_path)
if watcher=='enable':
# 在这里可以进行其他操作,监听会在后台持续
watcher_folder.set_folder_path(file_path)
watcher_folder.start()
else:
if watcher_folder!=None:
watcher_folder.stop()
@@ -660,7 +688,7 @@ class ImageCropByAlpha:
FUNCTION = "run"
CATEGORY = "Mixlab/image"
CATEGORY = "♾️Mixlab/image"
INPUT_IS_LIST = False
OUTPUT_IS_LIST = (False,)
@@ -691,7 +719,7 @@ class AreaToMask:
FUNCTION = "run"
CATEGORY = "Mixlab/mask"
CATEGORY = "♾️Mixlab/mask"
INPUT_IS_LIST = False
OUTPUT_IS_LIST = (False,)
@@ -726,7 +754,7 @@ class FaceToMask:
FUNCTION = "run"
CATEGORY = "Mixlab/mask"
CATEGORY = "♾️Mixlab/mask"
INPUT_IS_LIST = False
OUTPUT_IS_LIST = (False,)
+2 -2
View File
@@ -79,7 +79,7 @@ class RandomPrompt:
FUNCTION = "run"
CATEGORY = "Mixlab/prompt"
CATEGORY = "♾️Mixlab/prompt"
OUTPUT_IS_LIST = (True,)
OUTPUT_NODE = True
@@ -158,7 +158,7 @@ class RunWorkflow:
FUNCTION = "run"
CATEGORY = "Mixlab/workflow"
CATEGORY = "♾️Mixlab/workflow"
OUTPUT_IS_LIST = (True,)
OUTPUT_NODE = True
+37 -4
View File
@@ -24,9 +24,42 @@ def base64_save(base64_data):
return (image,mask)
# # 把白色部分处理成黑色
# def convert_to_bw(image):
# # 读取图片
# # image = Image.open(image_path)
# # 获取图片的宽度和高度
# width, height = image.size
# # 遍历图片的每个像素点
# for x in range(width):
# for y in range(height):
# # 获取当前像素点的RGB值
# r, g, b = image.getpixel((x, y))
# # 判断当前像素点是否为白色
# if r == 255 and g == 255 and b == 255:
# # 将白色部分处理成黑色
# image.putpixel((x, y), (0, 0, 0))
# else:
# # 将非白色部分处理成白色
# image.putpixel((x, y), (255, 255, 255))
# # 转换为黑白图
# mask = image.convert("L")
# # # 保存处理后的图片
# # image.save("black_white_image.jpg")
# # print("图片处理完成!")
# return mask
def load_image(i,white_bg=False):
# i = Image.open(fp)
image = i.convert("RGB")
image = np.array(image).astype(np.float32) / 255.0
image = torch.from_numpy(image)[None,]
if 'A' in i.getbands():
@@ -52,11 +85,11 @@ class ScreenShareNode:
# "seed": ("INT", {"default": 1, "min": 0, "max": 0xffffffffffffffff}),
} }
RETURN_TYPES = ('IMAGE','MASK','STRING')
RETURN_TYPES = ('IMAGE','STRING')
FUNCTION = "run"
CATEGORY = "Mixlab/image"
CATEGORY = "♾️Mixlab/image"
# INPUT_IS_LIST = True
OUTPUT_IS_LIST = (False,False,False)
@@ -65,7 +98,7 @@ class ScreenShareNode:
def run(self,image_base64,prompt):
im,mask=base64_save(image_base64)
# print('##########prompt',prompt)
return (im,mask,prompt)
return (im,prompt)
class FloatingVideo:
@@ -81,7 +114,7 @@ class FloatingVideo:
OUTPUT_NODE = True
FUNCTION = "run"
CATEGORY = "Mixlab/image"
CATEGORY = "♾️Mixlab/image"
# INPUT_IS_LIST = True
# OUTPUT_IS_LIST = (False,False,)
+2 -2
View File
@@ -145,7 +145,7 @@ class VAELoader:
RETURN_TYPES = ("VAE",)
FUNCTION = "load_vae"
CATEGORY = "Mixlab/ConsistencyDecoder"
CATEGORY = "♾️Mixlab/ConsistencyDecoder"
#TODO: scale factor?
def load_vae(self, vae_name):
@@ -165,7 +165,7 @@ class VAEDecode:
RETURN_TYPES = ("IMAGE",)
FUNCTION = "decode"
CATEGORY = "Mixlab/ConsistencyDecoder"
CATEGORY = "♾️Mixlab/ConsistencyDecoder"
def decode(self, vae, samples):
image = vae.decode(samples["samples"].to("cuda:0"))
+7 -6
View File
@@ -48,7 +48,7 @@ class FolderWatcher:
config['folder_path']=folder_path
save_to_json(config_json,config)
# self.observer = Observer()
self.observer = None
self.event_handler = self._create_event_handler()
self.status = "Not started"
self.event_type='-'
@@ -97,11 +97,12 @@ class FolderWatcher:
print('Listening')
def stop(self):
self.observer.stop()
self.observer.join()
self.observer=None
self.status = "Stopped"
self.event_type='-'
if self.observer!=None:
self.observer.stop()
self.observer.join()
self.observer=None
self.status = "Stopped"
self.event_type='-'
print('Stopped')
+2 -1
View File
@@ -2,4 +2,5 @@ numpy
pyOpenSSL
watchdog
opencv-python-headless
matplotlib
matplotlib
openai
@@ -3,21 +3,20 @@ import { app } from '../../../scripts/app.js'
const repoOwner = 'shadowcz007' // 替换为仓库的所有者
const repoName = 'comfyui-mixlab-nodes' // 替换为仓库的名称
const version='v0.2.2'
const version = 'v0.2.5.2'
fetch(`https://api.github.com/repos/${repoOwner}/${repoName}/releases/latest`)
.then(response => response.json())
.then(data => {
const latestVersion = data.tag_name
console.log('Latest release version:', latestVersion )
if(latestVersion!=version){
console.log('Latest release version:', latestVersion)
// if (latestVersion === localStorage.getItem('_mixlab_nodes_vesion')) return
if (latestVersion != version) {
// localStorage.setItem('_mixlab_nodes_vesion', latestVersion)
app.ui.dialog.show(`<h4 style="font-size: 18px;">${repoName} <br>
Latest release version: ${latestVersion}</h4>
<p>Please proceed to the official repository to download the latest version.</p>
<a style=" color: #2196F3;
<a style="color: #2196F3;
font-size: 18px;
font-weight: 800;
letter-spacing: 2px;
@@ -25,12 +24,12 @@ fetch(`https://api.github.com/repos/${repoOwner}/${repoName}/releases/latest`)
href="https://github.com/shadowcz007/comfyui-mixlab-nodes/releases/">https://github.com/shadowcz007/comfyui-mixlab-nodes/releases</a>
`)
// window.alert(
// `Please proceed to the official repository to download the latest version.https://github.com/shadowcz007/comfyui-mixlab-nodes/releases/`
// )
// window.open(
// 'https://github.com/shadowcz007/comfyui-mixlab-nodes/releases/'
// )
// window.alert(
// `Please proceed to the official repository to download the latest version.https://github.com/shadowcz007/comfyui-mixlab-nodes/releases/`
// )
// window.open(
// 'https://github.com/shadowcz007/comfyui-mixlab-nodes/releases/'
// )
}
})
.catch(error => {
+240
View File
@@ -0,0 +1,240 @@
import { app } from '../../../scripts/app.js'
// import { api } from '../../../scripts/api.js'
import { ComfyWidgets } from '../../../scripts/widgets.js'
import { $el } from '../../../scripts/ui.js'
async function getConfig () {
let api_host = `${window.location.hostname}:${window.location.port}`
let api_base = ''
let url = `${window.location.protocol}//${api_host}${api_base}`
const res = await fetch(`${url}/mixlab`, {
method: 'POST'
})
return await res.json()
}
function get_position_style (ctx, widget_width, y, node_height) {
const MARGIN = 4 // the margin around the html element
/* Create a transform that deals with all the scrolling and zooming */
const elRect = ctx.canvas.getBoundingClientRect()
const transform = new DOMMatrix()
.scaleSelf(
elRect.width / ctx.canvas.width,
elRect.height / ctx.canvas.height
)
.multiplySelf(ctx.getTransform())
.translateSelf(MARGIN, MARGIN + y)
return {
transformOrigin: '0 0',
transform: transform,
left: `0`,
top: `0`,
cursor: 'pointer',
position: 'absolute',
maxWidth: `${widget_width - MARGIN * 2}px`,
// maxHeight: `${node_height - MARGIN * 2}px`, // we're assuming we have the whole height of the node
width: `${widget_width - MARGIN * 2}px`,
// height: `${node_height * 0.3 - MARGIN * 2}px`,
// background: '#EEEEEE',
display: 'flex',
flexDirection: 'column',
// alignItems: 'center',
justifyContent: 'space-around'
}
}
app.registerExtension({
name: 'Mixlab.GPT.ChatGPT',
async getCustomWidgets (app) {
return {
KEY (node, inputName, inputData, app) {
// console.log('node', inputName, inputData[0])
const widget = {
type: inputData[0], // the type, CHEESE
name: inputName, // the name, slice
size: [128, 24], // a default size
draw (ctx, node, width, y) {},
computeSize (...args) {
return [128, 24] // a method to compute the current size of the widget
},
async serializeValue (nodeId, widgetIndex) {
//localStorage.getItem('_mixlab_api_key') || ''
return 'by Mixlab'
}
}
// widget.something = something; // maybe adds stuff to it
node.addCustomWidget(widget) // adds it to the node
return widget // and returns it.
},
URL (node, inputName, inputData, app) {
// console.log('node', inputName, inputData[0])
const widget = {
type: inputData[0], // the type, CHEESE
name: inputName, // the name, slice
size: [128, 24], // a default size
draw (ctx, node, width, y) {
// a method to draw the widget (ctx is a CanvasRenderingContext2D)
},
computeSize (...args) {
return [128, 24] // a method to compute the current size of the widget
},
async serializeValue (nodeId, widgetIndex) {
return localStorage.getItem('_mixlab_api_url') || 'https://api.openai.com/v1'
}
}
// widget.something = something; // maybe adds stuff to it
node.addCustomWidget(widget) // adds it to the node
return widget // and returns it.
}
}
},
async beforeRegisterNodeDef (nodeType, nodeData, app) {
// console.log(nodeType.comfyClass)
if (nodeType.comfyClass == 'ChatGPT') {
const orig_nodeCreated = nodeType.prototype.onNodeCreated
nodeType.prototype.onNodeCreated = function () {
orig_nodeCreated?.apply(this, arguments)
console.log('ChatGPT widtget', this.widgets)
const api_key = this.widgets.filter(w => w.name == 'api_key')[0]
const api_url = this.widgets.filter(w => w.name == 'api_url')[0]
// console.log('api_key', api_key, api_url)
const widget = {
type: 'div',
name: 'chatgpt div',
draw (ctx, node, widget_width, y, widget_height) {
Object.assign(
this.div.style,
get_position_style(ctx, widget_width, api_key.y, node.size[1])
)
}
}
widget.div = $el('div', {})
document.body.appendChild(widget.div)
const inputKey = document.createElement('input'),
inputUrl = document.createElement('input')
inputKey.type = 'text'
inputUrl.type = 'text'
inputKey.placeholder = 'Key'
inputUrl.placeholder = 'URL'
inputKey.style = `margin:4px 48px;`
inputUrl.style = `margin:4px 48px`
inputUrl.value = localStorage.getItem('_mixlab_api_url') || 'https://api.openai.com/v1'
inputKey.value = localStorage.getItem('_mixlab_api_key') || 'by Mixlab'
widget.div.appendChild(inputKey)
widget.div.appendChild(inputUrl)
inputKey.addEventListener('change', () => {
api_key.serializeValue = () => inputKey.value || 'by Mixlab'
localStorage.setItem('_mixlab_api_key', inputKey.value)
})
inputUrl.addEventListener('change', () => {
api_url.serializeValue = () => inputUrl.value || 'https://api.openai.com/v1'
localStorage.setItem('_mixlab_api_url', inputUrl.value)
})
/*
Add the widget, make sure we clean up nicely, and we do not want to be serialized!
*/
this.addCustomWidget(widget)
const onRemoved = this.onRemoved
this.onRemoved = () => {
inputUrl.remove()
inputKey.remove()
widget.div.remove()
return onRemoved?.()
}
this.serialize_widgets = false
}
}
}
})
app.registerExtension({
name: 'Mixlab.GPT.ShowTextForGPT',
async beforeRegisterNodeDef (nodeType, nodeData, app) {
if (nodeData.name === 'ShowTextForGPT') {
function populate (text) {
if (this.widgets) {
// const pos = this.widgets.findIndex((w) => w.name === "text");
// if (pos !== -1) {
// for (let i = pos; i < this.widgets.length; i++) {
// this.widgets[i].onRemove?.();
// }
// this.widgets.length = pos;
// }
for (let i = 0; i < this.widgets.length; i++) {
this.widgets[i].onRemove?.()
}
this.widgets.length = 0
}
console.log('ShowTextForGPT', this.widgets, text)
for (const list of text) {
const w = ComfyWidgets['STRING'](
this,
'text',
['STRING', { multiline: true }],
app
).widget
w.inputEl.readOnly = true
w.inputEl.style.opacity = 0.6
let res = list
try {
res = JSON.stringify(JSON.parse(list), null, 2)
} catch (error) {
// console.log(list)
}
w.value = res
}
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)
populate.call(this, message.text)
}
const onConfigure = nodeType.prototype.onConfigure
nodeType.prototype.onConfigure = function () {
onConfigure?.apply(this, arguments)
if (this.widgets_values?.length) {
populate.call(this, this.widgets_values)
}
}
}
}
})
@@ -29,6 +29,36 @@ async function interrupt () {
})
}
async function clipboardWriteImage (win, url) {
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
// console.log(url)
const img = await createImage(url)
// console.log(img)
canvas.width = img.naturalWidth
canvas.height = img.naturalHeight
ctx.clearRect(0, 0, canvas.width, canvas.height)
ctx.drawImage(img, 0, 0)
// 将canvas转为blob
canvas.toBlob(async blob => {
const data = [
new ClipboardItem({
[blob.type]: blob
})
]
win.navigator.clipboard
.write(data)
.then(() => {
console.log('Image copied to clipboard')
})
.catch(error => {
console.error('Failed to copy image to clipboard:', error)
})
})
}
async function uploadFile (file) {
try {
const body = new FormData()
@@ -801,6 +831,7 @@ app.registerExtension({
widget.preview = $el('video', {
controls: true,
draggable: true,
style: {
width: '100%'
},
@@ -808,6 +839,28 @@ app.registerExtension({
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAAAXNSR0IArs4c6QAAALZJREFUKFOFkLERwjAQBPdbgBkInECGaMLUQDsE0AkRVRAYWqAByxldPPOWHwnw4OBGye1p50UDSoA+W2ABLPN7i+C5dyC6R/uiAUXRQCs0bXoNIu4QPQzAxDKxHoALOrZcqtiyR/T6CXw7+3IGHhkYcy6BOR2izwT8LptG8rbMiCRAUb+CQ6WzQVb0SNOi5Z2/nX35DRyb/ENazhpWKoGwrpD6nICp5c2qogc4of+c7QcrhgF4Aa/aoAFHiL+RAAAAAElFTkSuQmCC'
})
// })
// const dropTarget = document.getElementById('your-drop-target-id')
// dropTarget.addEventListener('dragover', event => {
// event.preventDefault()
// })
// dropTarget.addEventListener('drop', event => {
// event.preventDefault()
// const imageUrl = event.dataTransfer.getData('text/plain')
// navigator.clipboard
// .writeText(imageUrl)
// .then(() => {
// console.log('Image URL copied to clipboard')
// })
// .catch(error => {
// console.error('Failed to copy image URL to clipboard:', error)
// })
// })
widget.canvas = $el('canvas', {
style: {
display: 'none'
@@ -818,7 +871,6 @@ app.registerExtension({
innerText: 'PictureInPicture',
style: {
display: 'pictureInPictureEnabled' in document ? 'block' : 'none',
cursor: 'pointer',
padding: '8px 0',
fontWeight: '300',
@@ -831,6 +883,28 @@ app.registerExtension({
widget.card.appendChild(widget.preview)
widget.card.appendChild(widget.canvas)
widget.preview.addEventListener('click', event => {
const imageUrl = window._mixlab_screen_result || ''
// console.log(imageUrl)
try {
if (imageUrl) clipboardWriteImage(pipWindow, imageUrl)
} catch (error) {
console.log(error)
if (imageUrl) clipboardWriteImage(window, imageUrl)
}
// pipWindow.navigator.permissions
// .query({ name: 'clipboard-write' })
// .then(result => {
// if (result.state === 'granted' || result.state === 'prompt') {
// // 执行复制操作
// } else {
// console.error('Clipboard write permission denied')
// }
// })
})
// widget.card.appendChild(widget.PictureInPicture)
widget.PictureInPicture.addEventListener('click', async () => {
@@ -874,7 +948,17 @@ app.registerExtension({
width: calc(100% - 24px);
margin: 12px;`
// console.log(pipWindow.document)
let inputDiv = document.createElement('div')
// inputDiv.style = ``
let infoDiv = document.createElement('div')
infoDiv.style = ` width: 100%;
height: 16px;
color: white;
margin-bottom: 4px;
font-size: 12px;
text-shadow: 1px 1px gray;`
infoDiv.id = 'info'
// Move the player to the Picture-in-Picture window.
let input = document.createElement('textarea')
input.style = `
@@ -928,8 +1012,14 @@ app.registerExtension({
} else {
input.style.display = 'none'
}
try {
pipWindow.document.querySelector('#info').innerText = ''
} catch (error) {
console.log(error)
}
})
// TODO 需要判断是否有screenshare节点,没有的话,不需要添加
let pauseBtn = document.createElement('butotn')
pauseBtn.innerText = '⏸'
pauseBtn.style = `cursor: pointer;height: 24px;margin:4px;
@@ -950,6 +1040,13 @@ app.registerExtension({
if (w) {
w.liveBtn.innerText = 'Live Run'
}
try {
pipWindow.document.querySelector('#info').innerText =
'Stop Live'
} catch (error) {
console.log(error)
}
} else {
pauseBtn.innerText = '⏸'
let node = this.graph._nodes.filter(
@@ -961,6 +1058,12 @@ app.registerExtension({
window._mixlab_stopLive = await startLive(w.liveBtn)
console.log('window._mixlab_stopLive', window._mixlab_stopLive)
}
try {
pipWindow.document.querySelector('#info').innerText = 'Live'
} catch (error) {
console.log(error)
}
}
})
@@ -972,19 +1075,69 @@ app.registerExtension({
window._mixlab_screen_prompt =
window._mixlab_screen_prompt_input || window._mixlab_screen_prompt
document.querySelector('#queue-button').click()
try {
pipWindow.document.querySelector('#info').innerText =
'Update Prompt'
} catch (error) {
console.log(error)
}
})
widget.preview.addEventListener('click', event => {
const imageUrl = window._mixlab_screen_result || ''
// console.log(imageUrl)
try {
if (imageUrl) clipboardWriteImage(pipWindow, imageUrl)
} catch (error) {
console.log(error)
if (imageUrl) clipboardWriteImage(window, imageUrl)
}
try {
pipWindow.document.querySelector('#info').innerText =
'Image copied to clipboard'
setTimeout(
() =>
(pipWindow.document.querySelector('#info').innerText = ''),
8000
)
} catch (error) {
console.log(error)
}
// pipWindow.navigator.permissions
// .query({ name: 'clipboard-write' })
// .then(result => {
// if (result.state === 'granted' || result.state === 'prompt') {
// // 执行复制操作
// } else {
// console.error('Clipboard write permission denied')
// }
// })
})
pipWindow.document.body.append(widget.preview)
pipWindow.document.body.append(div)
// console.log(pipWindow)
div.appendChild(btnDiv)
btnDiv.appendChild(btn)
btnDiv.appendChild(pauseBtn)
btnDiv.appendChild(promptFinishBtn)
div.appendChild(input)
// 输入框
div.appendChild(inputDiv)
inputDiv.appendChild(infoDiv)
inputDiv.appendChild(input)
input.addEventListener('input', () => {
window._mixlab_screen_prompt_input = input.value
try {
pipWindow.document.querySelector('#info').innerText = ''
} catch (error) {
console.log(error)
}
})
input.addEventListener('keydown', handleKeyDown)
@@ -1000,6 +1153,13 @@ app.registerExtension({
window._mixlab_screen_prompt_input ||
window._mixlab_screen_prompt
document.querySelector('#queue-button').click()
try {
pipWindow.document.querySelector('#info').innerText =
'Update Prompt'
} catch (error) {
console.log(error)
}
}
}
}
@@ -1035,7 +1195,11 @@ app.registerExtension({
const videoTrack = stream.getVideoTracks()[0]
video.preview.srcObject = new MediaStream([videoTrack])
video.preview.play()
try {
video.preview.play()
} catch (error) {
console.log(error)
}
// 检查浏览器是否支持画中画模式
if ('pictureInPictureEnabled' in document) {
@@ -1068,7 +1232,7 @@ app.registerExtension({
const context = canvas.getContext('2d')
if (message?.images_) {
const base64 = message.images_[0]
window._mixlab_screen_result = `data:image/png;base64,${message.images_[0]}`
const image = new Image()
image.onload = function () {
canvas.width = image.width
@@ -1076,7 +1240,7 @@ app.registerExtension({
context.drawImage(image, 0, 0)
}
// console.log(`data:image/jpeg;base64,${base64}`)
image.src = `data:image/jpeg;base64,${base64}`
image.src = window._mixlab_screen_result
}
const onRemoved = this.onRemoved
this.onRemoved = () => {
@@ -1457,7 +1621,7 @@ const node = {
)[0]
if (my_workflow?.data) {
// app.loadGraphData(my_workflow.data)
localStorage.setItem('workflow',JSON.stringify(my_workflow.data));
localStorage.setItem('workflow', JSON.stringify(my_workflow.data))
}
})
}
-39
View File
@@ -1,39 +0,0 @@
import { app } from '../../../scripts/app.js'
const missingNodeGithub = missingNodeTypes => {
return Array.from(
new Set(missingNodeTypes)
,n=>{
const url = `https://github.com/search?q=${n}&type=code`;
return `<li style="color: white;
background: black;
padding: 8px;
font-size: 12px;">${n}<a href="${url}" target="_blank"> 🔗</a></li>`;
})
}
app.showMissingNodesError = function (missingNodeTypes, hasAddedNodes = true) {
// console.log('###MIXLAB', missingNodeTypes, hasAddedNodes)
this.ui.dialog.show(
`When loading the graph, the following node types were not found: <ul>${ missingNodeGithub(missingNodeTypes)
.join('')}</ul>${
hasAddedNodes
? 'Nodes that have failed to load will show as red on the graph.'
: ''
}`
)
this.logging.addEntry('Comfy.App', 'warn', {
MissingNodes: missingNodeTypes
})
}
// app.ui.dialog.show = function (html) {
// console.log('###MIXLAB', html)
// if (typeof html === 'string') {
// this.textElement.innerHTML = html
// } else {
// this.textElement.replaceChildren(html)
// }
// this.element.style.display = 'flex'
// }
+88
View File
@@ -0,0 +1,88 @@
import { app } from '../../../scripts/app.js'
async function getCustomnodeMappings (mode = 'url') {
// mode = "local";
let api_host = `${window.location.hostname}:${window.location.port}`
let api_base = ''
let url = `${window.location.protocol}//${api_host}${api_base}`
const response = await fetch(`${url}/customnode/getmappings?mode=${mode}`)
const data = await response.json()
let nodes = {}
try {
for (let url in data) {
let n = data[url]
for (let node of n[0]) {
nodes[node] = { url, title: n[1].title_aux }
}
}
} catch (error) {}
return nodes
}
const missingNodeGithub = (missingNodeTypes, nodesMap) => {
let ts = {}
Array.from(new Set(missingNodeTypes), n => {
if (nodesMap[n]) {
let title = nodesMap[n].title
if (!ts[title]) {
ts[title] = {
title,
nodes: {},
url: nodesMap[n].url
}
}
ts[title].nodes[n] = 1
} else {
ts[n] = {
title: n,
nodes: {},
url: `https://github.com/search?q=${n}&type=code`
}
ts[n].nodes[n] = 1
}
})
return Array.from(Object.values(ts), n => {
const url = n.url
return `<li style="color: white;
background: black;
padding: 8px;
font-size: 12px;">${n.title}<a href="${url}" target="_blank"> 🔗</a></li>`
})
}
app.showMissingNodesError = async function (
missingNodeTypes,
hasAddedNodes = true
) {
const nodesMap = await getCustomnodeMappings()
console.log('#nodesMap', nodesMap)
// console.log('###MIXLAB', missingNodeTypes, hasAddedNodes)
this.ui.dialog.show(
`When loading the graph, the following node types were not found: <ul>${missingNodeGithub(
missingNodeTypes,
nodesMap
).join('')}</ul>${
hasAddedNodes
? 'Nodes that have failed to load will show as red on the graph.'
: ''
}`
)
this.logging.addEntry('Comfy.App', 'warn', {
MissingNodes: missingNodeTypes
})
}
// app.ui.dialog.show = function (html) {
// console.log('###MIXLAB', html)
// if (typeof html === 'string') {
// this.textElement.innerHTML = html
// } else {
// this.textElement.replaceChildren(html)
// }
// this.element.style.display = 'flex'
// }
@@ -14,7 +14,9 @@ async function getConfig () {
return await res.json()
}
if(!window._mixlab_screen_prompt) window._mixlab_screen_prompt="beautiful scenery nature glass bottle landscape,under water"
if (!window._mixlab_screen_prompt)
window._mixlab_screen_prompt =
'beautiful scenery nature glass bottle landscape,under water'
function get_position_style (ctx, widget_width, y, node_height) {
const MARGIN = 4 // the margin around the html element
@@ -59,7 +61,28 @@ app.registerExtension({
name: inputName, // the name, slice
size: [128, 24], // a default size
draw (ctx, node, width, y) {
// a method to draw the widget (ctx is a CanvasRenderingContext2D)
// // 绘制文件图标的函数
// function drawFileIcon () {
// // 清空画布
// // ctx.clearRect(0, 0, canvas.width, canvas.height)
// // 绘制文件外框
// ctx.fillStyle = '#000'
// ctx.fillRect(5, 5, 40, 40)
// // 绘制文件夹图标
// ctx.fillStyle = '#f00'
// ctx.fillRect(10, 15, 30, 20)
// // 绘制监听符号
// ctx.beginPath()
// ctx.arc(30, 35, 5, 0, 2 * Math.PI)
// ctx.fillStyle = '#00f'
// ctx.fill()
// }
// // 调用绘制函数
// drawFileIcon()
},
computeSize (...args) {
return [128, 24] // a method to compute the current size of the widget
@@ -101,11 +124,7 @@ app.registerExtension({
nodeType.prototype.onNodeCreated = function () {
orig_nodeCreated?.apply(this, arguments)
// console.log(
// 'watch widtget',
// this.widgets.filter(w => w.name == 'watcher')[0]
// )
console.log('watch widtget', this.widgets)
const watcher = this.widgets.filter(w => w.name == 'watcher')[0]
@@ -133,7 +152,6 @@ app.registerExtension({
}
}
// 上次路径填充
getConfig().then(json => {
let w = this.widgets.filter(w => w.name == 'file_path')[0]
@@ -142,7 +160,6 @@ app.registerExtension({
}
// console.log(json.event_type)
window._mixlab_file_path_watcher = json.event_type
})
/*
-719
View File
@@ -1,719 +0,0 @@
{
"last_node_id": 28,
"last_link_id": 30,
"nodes": [
{
"id": 7,
"type": "CLIPTextEncode",
"pos": [
108,
316
],
"size": {
"0": 425.27801513671875,
"1": 180.6060791015625
},
"flags": {},
"order": 8,
"mode": 0,
"inputs": [
{
"name": "clip",
"type": "CLIP",
"link": 16
}
],
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [
6
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "CLIPTextEncode"
},
"widgets_values": [
"text, watermark"
]
},
{
"id": 18,
"type": "ControlNetApply",
"pos": [
485,
792
],
"size": {
"0": 317.4000244140625,
"1": 98
},
"flags": {},
"order": 10,
"mode": 0,
"inputs": [
{
"name": "conditioning",
"type": "CONDITIONING",
"link": 22,
"slot_index": 0
},
{
"name": "control_net",
"type": "CONTROL_NET",
"link": 18,
"slot_index": 1
},
{
"name": "image",
"type": "IMAGE",
"link": 26
}
],
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [
21
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "ControlNetApply"
},
"widgets_values": [
1
]
},
{
"id": 6,
"type": "CLIPTextEncode",
"pos": [
117,
95
],
"size": {
"0": 422.84503173828125,
"1": 164.31304931640625
},
"flags": {},
"order": 7,
"mode": 0,
"inputs": [
{
"name": "clip",
"type": "CLIP",
"link": 15
}
],
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [
22
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "CLIPTextEncode"
},
"widgets_values": [
"a future city,buiding,future,magic,under water"
]
},
{
"id": 15,
"type": "LoraLoader",
"pos": [
116,
-113
],
"size": {
"0": 315,
"1": 126
},
"flags": {},
"order": 3,
"mode": 0,
"inputs": [
{
"name": "model",
"type": "MODEL",
"link": 13
},
{
"name": "clip",
"type": "CLIP",
"link": 14
}
],
"outputs": [
{
"name": "MODEL",
"type": "MODEL",
"links": [
12,
23
],
"shape": 3,
"slot_index": 0
},
{
"name": "CLIP",
"type": "CLIP",
"links": [
15,
16
],
"shape": 3,
"slot_index": 1
}
],
"properties": {
"Node name for S&R": "LoraLoader"
},
"widgets_values": [
"lcm-lora-sdv1-5.safetensors",
1,
1
]
},
{
"id": 4,
"type": "CheckpointLoaderSimple",
"pos": [
-380,
185
],
"size": {
"0": 315,
"1": 98
},
"flags": {},
"order": 0,
"mode": 0,
"outputs": [
{
"name": "MODEL",
"type": "MODEL",
"links": [
13
],
"slot_index": 0
},
{
"name": "CLIP",
"type": "CLIP",
"links": [
14
],
"slot_index": 1
},
{
"name": "VAE",
"type": "VAE",
"links": [
8
],
"slot_index": 2
}
],
"properties": {
"Node name for S&R": "CheckpointLoaderSimple"
},
"widgets_values": [
"deliberate_v2.safetensors"
]
},
{
"id": 19,
"type": "DiffControlNetLoader",
"pos": [
40,
792
],
"size": {
"0": 367.8165283203125,
"1": 58.083831787109375
},
"flags": {},
"order": 6,
"mode": 0,
"inputs": [
{
"name": "model",
"type": "MODEL",
"link": 23,
"slot_index": 0
}
],
"outputs": [
{
"name": "CONTROL_NET",
"type": "CONTROL_NET",
"links": [
18
],
"shape": 3
}
],
"properties": {
"Node name for S&R": "DiffControlNetLoader"
},
"widgets_values": [
"control_v11f1p_sd15_depth.pth"
]
},
{
"id": 5,
"type": "EmptyLatentImage",
"pos": [
65,
573
],
"size": {
"0": 315,
"1": 106
},
"flags": {},
"order": 1,
"mode": 0,
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": [
2
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "EmptyLatentImage"
},
"widgets_values": [
512,
512,
1
]
},
{
"id": 25,
"type": "LeReS-DepthMapPreprocessor",
"pos": [
47,
904
],
"size": {
"0": 369.6000061035156,
"1": 130
},
"flags": {},
"order": 4,
"mode": 0,
"inputs": [
{
"name": "image",
"type": "IMAGE",
"link": 29
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
26,
27
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "LeReS-DepthMapPreprocessor"
},
"widgets_values": [
0.1,
0,
"disable",
512
]
},
{
"id": 3,
"type": "KSampler",
"pos": [
1100,
-97
],
"size": {
"0": 315,
"1": 262
},
"flags": {},
"order": 11,
"mode": 0,
"inputs": [
{
"name": "model",
"type": "MODEL",
"link": 12,
"slot_index": 0
},
{
"name": "positive",
"type": "CONDITIONING",
"link": 21
},
{
"name": "negative",
"type": "CONDITIONING",
"link": 6
},
{
"name": "latent_image",
"type": "LATENT",
"link": 2
}
],
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": [
7
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "KSampler"
},
"widgets_values": [
170633013599955,
"fixed",
4,
1.6,
"lcm",
"simple",
1
]
},
{
"id": 8,
"type": "VAEDecode",
"pos": [
1100,
-241
],
"size": {
"0": 210,
"1": 46
},
"flags": {},
"order": 12,
"mode": 0,
"inputs": [
{
"name": "samples",
"type": "LATENT",
"link": 7
},
{
"name": "vae",
"type": "VAE",
"link": 8
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
28
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "VAEDecode"
}
},
{
"id": 26,
"type": "FloatingVideo",
"pos": [
1689,
-484
],
"size": [
315,
106
],
"flags": {},
"order": 13,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 28
}
],
"properties": {
"Node name for S&R": "FloatingVideo"
}
},
{
"id": 27,
"type": "ScreenShare",
"pos": [
-387,
454
],
"size": {
"0": 315,
"1": 154
},
"flags": {},
"order": 2,
"mode": 0,
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
29,
30
],
"shape": 3,
"slot_index": 0
},
{
"name": "MASK",
"type": "MASK",
"links": null,
"shape": 3
},
{
"name": "STRING",
"type": "STRING",
"links": null,
"shape": 3
}
],
"properties": {
"Node name for S&R": "ScreenShare"
}
},
{
"id": 28,
"type": "PreviewImage",
"pos": [
-393,
975
],
"size": [
210,
246
],
"flags": {},
"order": 5,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 30
}
],
"properties": {
"Node name for S&R": "PreviewImage"
}
},
{
"id": 20,
"type": "PreviewImage",
"pos": [
492,
955
],
"size": {
"0": 526.9624633789062,
"1": 367.3833923339844
},
"flags": {},
"order": 9,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 27
}
],
"properties": {
"Node name for S&R": "PreviewImage"
}
}
],
"links": [
[
2,
5,
0,
3,
3,
"LATENT"
],
[
6,
7,
0,
3,
2,
"CONDITIONING"
],
[
7,
3,
0,
8,
0,
"LATENT"
],
[
8,
4,
2,
8,
1,
"VAE"
],
[
12,
15,
0,
3,
0,
"MODEL"
],
[
13,
4,
0,
15,
0,
"MODEL"
],
[
14,
4,
1,
15,
1,
"CLIP"
],
[
15,
15,
1,
6,
0,
"CLIP"
],
[
16,
15,
1,
7,
0,
"CLIP"
],
[
18,
19,
0,
18,
1,
"CONTROL_NET"
],
[
21,
18,
0,
3,
1,
"CONDITIONING"
],
[
22,
6,
0,
18,
0,
"CONDITIONING"
],
[
23,
15,
0,
19,
0,
"MODEL"
],
[
26,
25,
0,
18,
2,
"IMAGE"
],
[
27,
25,
0,
20,
0,
"IMAGE"
],
[
28,
8,
0,
26,
0,
"IMAGE"
],
[
29,
27,
0,
25,
0,
"IMAGE"
],
[
30,
27,
0,
28,
0,
"IMAGE"
]
],
"groups": [],
"config": {},
"extra": {},
"version": 0.4
}
+108 -114
View File
@@ -1,6 +1,6 @@
{
"last_node_id": 21,
"last_link_id": 45,
"last_node_id": 22,
"last_link_id": 48,
"nodes": [
{
"id": 7,
@@ -14,7 +14,7 @@
"1": 200
},
"flags": {},
"order": 7,
"order": 4,
"mode": 0,
"inputs": [
{
@@ -89,12 +89,12 @@
504,
33
],
"size": [
400,
200
],
"size": {
"0": 400,
"1": 200
},
"flags": {},
"order": 6,
"order": 8,
"mode": 0,
"inputs": [
{
@@ -105,7 +105,7 @@
{
"name": "text",
"type": "STRING",
"link": 43,
"link": 48,
"widget": {
"name": "text"
}
@@ -295,7 +295,7 @@
"Node name for S&R": "KSampler"
},
"widgets_values": [
613900833686415,
1115769645491668,
"randomize",
4,
1.6,
@@ -311,94 +311,24 @@
338,
786
],
"size": [
210,
246
],
"size": {
"0": 210,
"1": 246
},
"flags": {},
"order": 4,
"order": 6,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 37
"link": 46
}
],
"properties": {
"Node name for S&R": "PreviewImage"
}
},
{
"id": 19,
"type": "ScreenShare",
"pos": [
-55,
512
],
"size": {
"0": 325.3117370605469,
"1": 459.7692565917969
},
"flags": {},
"order": 2,
"mode": 0,
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
37,
42
],
"shape": 3,
"slot_index": 0
},
{
"name": "MASK",
"type": "MASK",
"links": null,
"shape": 3
},
{
"name": "STRING",
"type": "STRING",
"links": [
43
],
"shape": 3,
"slot_index": 2
}
],
"properties": {
"Node name for S&R": "ScreenShare"
}
},
{
"id": 20,
"type": "FloatingVideo",
"pos": [
2041,
277
],
"size": [
315,
106
],
"flags": {},
"order": 12,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 41
}
],
"properties": {
"Node name for S&R": "FloatingVideo"
}
},
{
"id": 6,
"type": "LoraLoader",
@@ -411,7 +341,7 @@
"1": 126
},
"flags": {},
"order": 8,
"order": 5,
"mode": 0,
"inputs": [
{
@@ -463,7 +393,7 @@
"1": 98
},
"flags": {},
"order": 3,
"order": 2,
"mode": 0,
"outputs": [
{
@@ -515,13 +445,13 @@
"1": 58
},
"flags": {},
"order": 5,
"order": 7,
"mode": 0,
"inputs": [
{
"name": "image",
"type": "IMAGE",
"link": 42
"link": 47
}
],
"outputs": [
@@ -541,6 +471,70 @@
"widgets_values": [
512
]
},
{
"id": 22,
"type": "ScreenShare",
"pos": [
-63,
483
],
"size": {
"0": 315,
"1": 98
},
"flags": {},
"order": 3,
"mode": 0,
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
46,
47
],
"shape": 3,
"slot_index": 0
},
{
"name": "STRING",
"type": "STRING",
"links": [
48
],
"shape": 3,
"slot_index": 1
}
],
"properties": {
"Node name for S&R": "ScreenShare"
}
},
{
"id": 20,
"type": "FloatingVideo",
"pos": [
1928,
295
],
"size": [
315,
58
],
"flags": {},
"order": 12,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 41
}
],
"properties": {
"Node name for S&R": "FloatingVideo"
}
}
],
"links": [
@@ -744,14 +738,6 @@
0,
"LATENT"
],
[
37,
19,
0,
2,
0,
"IMAGE"
],
[
38,
16,
@@ -784,22 +770,6 @@
0,
"IMAGE"
],
[
42,
19,
0,
18,
0,
"IMAGE"
],
[
43,
19,
2,
8,
1,
"STRING"
],
[
44,
6,
@@ -815,6 +785,30 @@
6,
1,
"CLIP"
],
[
46,
22,
0,
2,
0,
"IMAGE"
],
[
47,
22,
0,
18,
0,
"IMAGE"
],
[
48,
22,
1,
8,
1,
"STRING"
]
],
"groups": [],
+620
View File
@@ -0,0 +1,620 @@
{
"last_node_id": 46,
"last_link_id": 42,
"nodes": [
{
"id": 27,
"type": "CLIPTextEncode",
"pos": [
1961.178268896482,
527.6060791015625
],
"size": {
"0": 422.84503173828125,
"1": 164.31304931640625
},
"flags": {},
"order": 5,
"mode": 0,
"inputs": [
{
"name": "clip",
"type": "CLIP",
"link": 24
},
{
"name": "text",
"type": "STRING",
"link": 42,
"widget": {
"name": "text"
}
}
],
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [
21
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "CLIPTextEncode"
},
"widgets_values": [
"beautiful scenery nature glass bottle landscape, , purple galaxy bottle,"
]
},
{
"id": 28,
"type": "CLIPTextEncode",
"pos": [
1958.7452854980445,
266
],
"size": {
"0": 425.27801513671875,
"1": 180.6060791015625
},
"flags": {},
"order": 3,
"mode": 0,
"inputs": [
{
"name": "clip",
"type": "CLIP",
"link": 25
}
],
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [
22
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "CLIPTextEncode"
},
"widgets_values": [
"text, watermark"
]
},
{
"id": 24,
"type": "KSampler",
"pos": [
2434.0233006347635,
80
],
"size": {
"0": 315,
"1": 262
},
"flags": {},
"order": 8,
"mode": 0,
"inputs": [
{
"name": "model",
"type": "MODEL",
"link": 20
},
{
"name": "positive",
"type": "CONDITIONING",
"link": 21
},
{
"name": "negative",
"type": "CONDITIONING",
"link": 22
},
{
"name": "latent_image",
"type": "LATENT",
"link": 23
}
],
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": [
26
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "KSampler"
},
"widgets_values": [
261818020972971,
"randomize",
15,
8,
"euler",
"karras",
1
]
},
{
"id": 29,
"type": "VAEDecode",
"pos": [
2799.0233006347635,
80
],
"size": {
"0": 210,
"1": 46
},
"flags": {
"collapsed": false
},
"order": 9,
"mode": 0,
"inputs": [
{
"name": "samples",
"type": "LATENT",
"link": 26
},
{
"name": "vae",
"type": "VAE",
"link": 27
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
31
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "VAEDecode"
}
},
{
"id": 26,
"type": "EmptyLatentImage",
"pos": [
2069.0233006347635,
80
],
"size": {
"0": 315,
"1": 106
},
"flags": {},
"order": 0,
"mode": 0,
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": [
23
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "EmptyLatentImage"
},
"widgets_values": [
512,
512,
1
]
},
{
"id": 25,
"type": "CheckpointLoaderSimple",
"pos": [
1593.7452854980445,
80
],
"size": {
"0": 315,
"1": 98
},
"flags": {},
"order": 1,
"mode": 0,
"outputs": [
{
"name": "MODEL",
"type": "MODEL",
"links": [
20
],
"slot_index": 0
},
{
"name": "CLIP",
"type": "CLIP",
"links": [
24,
25
],
"slot_index": 1
},
{
"name": "VAE",
"type": "VAE",
"links": [
27
],
"slot_index": 2
}
],
"properties": {
"Node name for S&R": "CheckpointLoaderSimple"
},
"widgets_values": [
"deliberate_v2.safetensors"
]
},
{
"id": 31,
"type": "PreviewImage",
"pos": [
2439,
408
],
"size": {
"0": 563.4671630859375,
"1": 720.8866577148438
},
"flags": {},
"order": 10,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 31
}
],
"properties": {
"Node name for S&R": "PreviewImage"
}
},
{
"id": 44,
"type": "ShowTextForGPT",
"pos": [
1171,
425
],
"size": [
432.46002197265625,
264.40771484375
],
"flags": {},
"order": 6,
"mode": 0,
"inputs": [
{
"name": "text",
"type": "STRING",
"link": 40,
"widget": {
"name": "text"
}
}
],
"outputs": [
{
"name": "STRING",
"type": "STRING",
"links": null,
"shape": 6
}
],
"properties": {
"Node name for S&R": "ShowTextForGPT"
},
"widgets_values": [
"[\n {\n \"role\": \"system\",\n \"content\": \"你现在充当stable diffsion 提示词专家,根据我输入的场景或需求描述,生成提示词,由stable diffsion根据你的提示词生成画面。stable diffusion是文本到图像的扩散模型。你的任务是在各种情况下产生适当的提示,引导人工智能创造出所需的图像。\\n\\n一个优秀的提示词需要遵循下面的规范:\\n\\n“““\\n\\n简洁明了:避免过于复杂或模糊的描述,以免造成模型的混乱或错误。\\n\\n具体细致:提供足够的细节,以便模型能够准确地捕捉想要生成的图片的特征。\\n\\n逻辑连贯:避免出现矛盾或不合理的描述,以免影响模型的理解和生成。\\n\\n创意独特:有创意,展示自己的想象力和个性,以便模型能够生成有趣和新颖的图片。\\n\\n我建议你可以参考以下几个步骤思考:\\n\\n• 确定主题:首先,你需要确定想要生成图片的主题,例如风景、动物、人物等。\\n\\n• 确定风格:其次,根据主题要求,确定合适的风格(如极简、现代)或根据主题(人物、风景等)调整提示词的语言风格、词汇和描述角度。\\n\\n• 选择关键词:再次,你需要选择一些能够描述主题的关键词,常用的关键词类别包括:主体、媒介、风格、画家、网站、分辨率、额外细节、色调和光影。 可以使用名人的名字作为关键词,来控制人物形象,因为他们在训练集中出现频次较大而训练充分。\\n\\n• 组合句子:然后,你需要将关键词组合成一个或多个简单句子,提示词的句式通常如:【图片的风格】,【内容主题 】,【 细节描述】,【 绘画风格或者艺术家风格】, 用逗号或分号隔开,例如a blue sky with white clouds, a green field with yellow flowers。\\n\\n• 调整细节:最后,根据告诉你的内容主题,尽可能细致刻画画面,譬如,要画“大海”,你需要给出类似这样的提示:梦幻的大海,白沙滩岸边铺满了粉色的玫瑰花,月光轻柔的人洒在海面上,绿色发光的海浪。对于细节描述,我们可以拆分【形容词】+【视角】+【时间】+【颜色】+【其他】,形容词可以是梦幻,神秘,浪漫或者写实 ……视角可以是:超广角,俯视和仰视 ……,时间:秋天,清晨,黄昏,夜晚 ……,颜色可以是 红黄绿蓝橙紫……,其他可以包含图片的尺寸,4k,8k ,HD,光效,高细节等。\\n\\n• 如果想让生成的图片更加的艺术化、风格化,可以考虑在 提示词中添加绘画风格和艺术家。艺术绘画风格可以是一些美术风格:梵高风格,油画,水彩,古风,CG感,动漫,少女,赛博朋克,卡通画,中国画,黄昏等等,艺术家风格包含:现实主义,印象派,野兽派,新艺术,表现主义,立体主义,未来主义等等\\n\\n• 以简洁的英语输出。输出完整提示词后,把它翻译成中文,然后保留英语版本备用。\\n\\n“““\\n\\n为了让你更好的理解提示词,我收集了一些表现效果较好的提示词案例,你可以对照上面的规则学习吸收:\\n\\n“““\\n\\nMaximalist chaotic buenos aires, birds eye view, illustrated by hergé, style of tin tin comics, pen and ink\\n\\nHyper -realistic girl in blue coat, left in the woods, weird, otherworldly, real, vintage photograph, film set, 85mm lens, f/2.8 aperture\\n\\ninstagram photo of a Japanese and Scandinavian design style living room with lots of golden light, hyperrealistic surrealism, award winning masterpiece with incredible details\\n\\nPortrait of an owl, steampunk, indigo blue, colorful, illustration, highly detailed, simple, smooth, and clean vector, no jagged lines, vector art, smooth, made all with grey colored gears inspired by future technology\\n\\n“““\\n\\n请通过回复 “确认 “来确认你是否理解了这项任务。然后我将向你发送第一条命令。\"\n },\n {\n \"role\": \"user\",\n \"content\": \"证件照,女生,请认真回答噢,很重要这个问题\"\n }\n]"
]
},
{
"id": 43,
"type": "ShowTextForGPT",
"pos": [
1166,
740
],
"size": [
424.00797187258513,
430.6391176555039
],
"flags": {},
"order": 7,
"mode": 0,
"inputs": [
{
"name": "text",
"type": "STRING",
"link": 39,
"widget": {
"name": "text"
}
}
],
"outputs": [
{
"name": "STRING",
"type": "STRING",
"links": null,
"shape": 6
}
],
"properties": {
"Node name for S&R": "ShowTextForGPT"
},
"widgets_values": [
"[\n {\n \"role\": \"user\",\n \"content\": \"\"\n },\n {\n \"role\": \"assistant\",\n \"content\": \"ChatGPT is an artificial intelligence chatbot program launched by OpenAI in November 2022. The program is based on a large language model GPT-3.5, trained with Instruction Tuning and Reinforcement Learning with Human Feedback (RLHF).\"\n },\n {\n \"role\": \"user\",\n \"content\": \"\"\n },\n {\n \"role\": \"assistant\",\n \"content\": \"GPT-3.5 is a language model developed by OpenAI, based on the Transformer architecture. It is designed to perform a wide range of natural language processing tasks, including language translation, text summarization, and text generation.\"\n },\n {\n \"role\": \"user\",\n \"content\": \"\"\n },\n {\n \"role\": \"assistant\",\n \"content\": \"Is there anything specific you would like to know about GPT-3.5? I am here to help.\"\n },\n {\n \"role\": \"user\",\n \"content\": \"今天天气\"\n },\n {\n \"role\": \"assistant\",\n \"content\": \"I'm sorry, I am a text-based AI model and do not have the capability to access the current weather. However, I can tell you that it is likely to be a beautiful day, with the sun shining and a gentle breeze in the air. Enjoy your day!\"\n },\n {\n \"role\": \"user\",\n \"content\": \"今天天气不错噢,小明……\"\n },\n {\n \"role\": \"assistant\",\n \"content\": \"谢谢您的夸奖,不过我是一个人工智能模型,没有能力感知天气,只是通过语言理解回答问题。如果您需要查询当地的天气情况,建议您查询当地的天气预报。\"\n },\n {\n \"role\": \"user\",\n \"content\": \"今天天气不错噢,小明……\"\n },\n {\n \"role\": \"assistant\",\n \"content\": \"今天天气不错噢,小明……(这句话是重复的,之前已经回答过您的问题了。如果您需要查询当地的天气情况,建议您查询当地的天气预报。\"\n },\n {\n \"role\": \"user\",\n \"content\": \"证件照,女生\"\n },\n {\n \"role\": \"assistant\",\n \"content\": \"“女生分别为: Cut out of a colorful plaid skirt and white shirt, faced front, with a friendly smile, blonde-haired girl, age 22, blue eyes, naturalistic, clear, bright, young,以及人像,摄于户外,背景为美丽的自然风光,使用的相机是佳能5D Mark IV,镜头焦距为50mm,拍摄模式为肖像模式,使用的后期处理软件为Adobe Lightroom和Photoshop。\"\n },\n {\n \"role\": \"user\",\n \"content\": \"证件照,女生\"\n },\n {\n \"role\": \"assistant\",\n \"content\": \"• 拍摄证件照,要求提供女生的照片,背景可以是一片白色的天空,或者一片纯色的草地,不能有杂色。\\n• 视角应该从 above,即从上方俯视,让女生显得稳定自信。\\n• 时间应该选择在阳光明媚的早晨,这样可以让女生看起来更加青春活力。\\n• 颜色可以选择蓝色,这种颜色可以让人感到清新自然。\\n• 照片应该有高清清晰的效果,不能有模糊模糊的现象。\\n• 照片的尺寸可以选择2K或者4K,这样可以让照片更加清晰。\\n• 光效可以选择自然光,这样可以让照片看起来更加自然。\\n• 照片的风格可以选择日系或者韩系,这样可以让照片看起来更加时尚。\\n• 照片的背景可以选择浅色系,这样可以让照片看起来更加清新自然。\\n• 照片中不能有重大的人物或者动物,否则需要事先沟通好。\\n• 照片中不能有太强烈的光线或者阴影,需要调整好光线和阴影的平衡,使照片更加均匀。\\n• 照片中不能有太多的文字或者文字的内容,需要将文字或者文字的内容进行处理。\\n• 在拍摄证件照之前,需要让女生进行一些准备,比如保持好心态,调整好光线,保持好角度等等。\\n• 拍摄完证件照之后,需要将照片进行一些处理,比如进行美颜,磨皮,瘦脸等等。\"\n },\n {\n \"role\": \"user\",\n \"content\": \"证件照,女生,请认真回答噢,很重要这个问题\"\n },\n {\n \"role\": \"assistant\",\n \"content\": \"确认\"\n }\n]"
]
},
{
"id": 45,
"type": "ShowTextForGPT",
"pos": [
1092,
262
],
"size": [
635.8358236838778,
101.46092513017766
],
"flags": {},
"order": 4,
"mode": 0,
"inputs": [
{
"name": "text",
"type": "STRING",
"link": 41,
"widget": {
"name": "text"
}
}
],
"outputs": [
{
"name": "STRING",
"type": "STRING",
"links": null,
"shape": 6
}
],
"properties": {
"Node name for S&R": "ShowTextForGPT"
},
"widgets_values": [
"确认"
]
},
{
"id": 42,
"type": "ChatGPT",
"pos": [
611,
285
],
"size": [
417.3319772767625,
531.1873476643206
],
"flags": {},
"order": 2,
"mode": 0,
"outputs": [
{
"name": "text",
"type": "STRING",
"links": [
41,
42
],
"shape": 3,
"slot_index": 0
},
{
"name": "messages",
"type": "STRING",
"links": [
40
],
"shape": 3,
"slot_index": 1
},
{
"name": "session_history",
"type": "STRING",
"links": [
39
],
"shape": 3,
"slot_index": 2
}
],
"properties": {
"Node name for S&R": "ChatGPT"
}
}
],
"links": [
[
2,
4,
0,
5,
0,
"STRING"
],
[
3,
6,
0,
4,
0,
"STRING"
],
[
4,
6,
1,
7,
0,
"JSON"
],
[
8,
10,
0,
11,
0,
"STRING"
],
[
12,
10,
1,
14,
0,
"STRING"
],
[
13,
10,
1,
16,
0,
"JSON"
],
[
20,
25,
0,
24,
0,
"MODEL"
],
[
21,
27,
0,
24,
1,
"CONDITIONING"
],
[
22,
28,
0,
24,
2,
"CONDITIONING"
],
[
23,
26,
0,
24,
3,
"LATENT"
],
[
24,
25,
1,
27,
0,
"CLIP"
],
[
25,
25,
1,
28,
0,
"CLIP"
],
[
26,
24,
0,
29,
0,
"LATENT"
],
[
27,
25,
2,
29,
1,
"VAE"
],
[
31,
29,
0,
31,
0,
"IMAGE"
],
[
39,
42,
2,
43,
0,
"STRING"
],
[
40,
42,
1,
44,
0,
"STRING"
],
[
41,
42,
0,
45,
0,
"STRING"
],
[
42,
42,
0,
27,
1,
"STRING"
]
],
"groups": [],
"config": {},
"extra": {},
"version": 0.4
}