Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1daa1a4603 | ||
|
|
062773d929 | ||
|
|
57decadaef | ||
|
|
ec804ab7c9 | ||
|
|
3ee7533098 | ||
|
|
0d383ccc1f | ||
|
|
e2f2257c34 | ||
|
|
d62b9fc4c6 | ||
|
|
be7ad0c7fb | ||
|
|
156864cc8b | ||
|
|
7240e496cc | ||
|
|
8acdf4018d | ||
|
|
1ea7c3e203 | ||
|
|
e54aeb6125 | ||
|
|
d59f51fbcf | ||
|
|
a667eb6982 | ||
|
|
8d72732247 | ||
|
|
f0f3b30a62 | ||
|
|
d99fe24542 | ||
|
|
ea4c7381bd | ||
|
|
e900d20641 | ||
|
|
8a46647d8c | ||
|
|
968178bf57 | ||
|
|
406a255db0 | ||
|
|
574557810e | ||
|
|
998a02c3a4 | ||
|
|
c6f964c921 | ||
|
|
efb0e147c5 | ||
|
|
9cf7356f98 | ||
|
|
af05c43174 | ||
|
|
380c68ff2b | ||
|
|
068b00b99f | ||
|
|
cd6a42ab64 | ||
|
|
f115abec92 | ||
|
|
f0e23cf878 | ||
|
|
a94f11d809 | ||
|
|
38972bea5f | ||
|
|
a761ff552a | ||
|
|
dbeb84ea9a | ||
|
|
0a4938f39a | ||
|
|
b2182c716d | ||
|
|
8253be73f6 | ||
|
|
20318e296e | ||
|
|
cea1b69286 | ||
|
|
ab8aa69389 | ||
|
|
681491f1d0 | ||
|
|
c2fb815074 | ||
|
|
fffa14dc44 | ||
|
|
df37166d42 | ||
|
|
25fa3a8f6a | ||
|
|
b11507c5e6 | ||
|
|
f06d02489f | ||
|
|
c203af2f71 | ||
|
|
c715155a70 | ||
|
|
8163133294 | ||
|
|
765be5dab4 | ||
|
|
7c1523389d | ||
|
|
7a2b1ba166 | ||
|
|
45b4dcfcd0 | ||
|
|
3b2e535566 | ||
|
|
db556d13a3 | ||
|
|
a987063c68 | ||
|
|
4ce30ef899 | ||
|
|
d988282d98 | ||
|
|
695fdf7ceb | ||
|
|
0befe164cc | ||
|
|
d506c68a80 | ||
|
|
c59c429b75 | ||
|
|
c726b6e4a2 | ||
|
|
96075ad4e1 | ||
|
|
7a8dc07a8a | ||
|
|
1fdac0bc09 | ||
|
|
804b942a36 | ||
|
|
6335d4378b | ||
|
|
cfc2189616 | ||
|
|
e06e032701 | ||
|
|
c9499c2c79 | ||
|
|
2bf43541bb | ||
|
|
0386c7266d | ||
|
|
7aa6ed9a5d | ||
|
|
b71325afa5 | ||
|
|
2cc29bdf77 | ||
|
|
b5d602abc4 | ||
|
|
33c45637ac | ||
|
|
662d4478d0 | ||
|
|
31d3809572 | ||
|
|
024ff4a309 | ||
|
|
9ae8d30b6b | ||
|
|
44349c10b0 | ||
|
|
506520a3c4 | ||
|
|
50f7020977 | ||
|
|
02a27a03cc | ||
|
|
4ad6bacf7b | ||
|
|
26ecc0fa44 | ||
|
|
2619befca6 | ||
|
|
ea24f52b13 | ||
|
|
4abfc47346 | ||
|
|
3b95010d06 | ||
|
|
b3c1b96088 | ||
|
|
a9f1326873 | ||
|
|
75a696fb64 | ||
|
|
24aaacba6d | ||
|
|
e3cd7d5f91 | ||
|
|
6359c3f70f |
@@ -1,38 +1,44 @@
|
||||
##
|
||||
v0.7.0 🚀🚗🚚🏃 Workflow-to-APP
|
||||
- 支持多个web app 切换
|
||||
> 适配了最新版comfyui的py3.11 ,torch 2.1.2+cu121
|
||||
|
||||
## 🚀🚗🚚🏃 Workflow-to-APP
|
||||
- 新增AppInfo节点,可以通过简单的配置,把workflow转变为一个Web APP。
|
||||
- 支持多个web app 切换
|
||||
- 发布为app的workflow,可以在右键里再次编辑了
|
||||
- web app可以设置分类,在comfyui右键菜单可以编辑更新web app
|
||||
|
||||
|
||||
- Support multiple web app switching.
|
||||
- Add the AppInfo node, which allows you to transform the workflow into a web app by simple configuration.
|
||||
- The workflow, which is now released as an app, can also be edited again by right-clicking.
|
||||
- The web app can be configured with categories, and the web app can be edited and updated in the right-click menu of ComfyUI.
|
||||
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
Example:
|
||||
- workflow
|
||||

|
||||
[text-to-image](./workflow/Text-to-Image-app.json)
|
||||
|
||||
APP-JSON:
|
||||
- [text-to-image](./example/text-to-image_1_Wed%20Dec%2027%202023.json)
|
||||
- [image-to-image](./example/image-to-image_1_Wed%20Dec%2027%202023.json)
|
||||
- [text-to-image](./example/Text-to-Image_3.json)
|
||||
- [image-to-image](./example/Image-to-Image_2.json)
|
||||
- text-to-text
|
||||
|
||||
> 暂时支持6种节点作为界面上的输入节点:Load Image、CLIPTextEncode、TextInput_、FloatSlider、IntNumber、CheckpointLoaderSimple、LoraLoader
|
||||
> 暂时支持8种节点作为界面上的输入节点:Load Image、CLIPTextEncode、PromptSlide、TextInput_、Color、FloatSlider、IntNumber、CheckpointLoaderSimple、LoraLoader
|
||||
|
||||
> 输出节点:PreviewImage 、SaveImage、ShowTextForGPT、VHS_VideoCombine
|
||||
|
||||
|
||||
### 3D
|
||||

|
||||
[workflow](./workflow/3D-workflow.json)
|
||||
> seed统一输入控件,支持:SamplerCustom、KSampler
|
||||
|
||||
|
||||
### 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! 💻🌐
|
||||
## 🏃🚗🚚🚀 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! 💻🌐
|
||||
|
||||
>
|
||||

|
||||
|
||||
https://github.com/shadowcz007/comfyui-mixlab-nodes/assets/12645064/e7e77f90-e43e-410a-ab3a-1952b7b4e7da
|
||||
@@ -43,6 +49,7 @@ https://github.com/shadowcz007/comfyui-mixlab-nodes/assets/12645064/e7e77f90-e43
|
||||
|
||||
!! Please use the address with HTTPS (https://127.0.0.1).
|
||||
|
||||
|
||||
### SpeechRecognition & SpeechSynthesis
|
||||

|
||||
|
||||
@@ -51,11 +58,41 @@ https://github.com/shadowcz007/comfyui-mixlab-nodes/assets/12645064/e7e77f90-e43
|
||||
### GPT
|
||||
> Support for calling multiple GPTs.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 . Azure OpenAI:https://xxxx.openai.azure.com
|
||||
|
||||
|
||||

|
||||
|
||||
[workflow-5](./workflow/5-gpt-workflow.json)
|
||||
|
||||
|
||||
## Prompt
|
||||
> PromptSlide
|
||||

|
||||
|
||||

|
||||
|
||||
> randomPrompt
|
||||
|
||||

|
||||
|
||||
> ClipInterrogator
|
||||
|
||||
[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.
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
|
||||
### 3D
|
||||

|
||||
[workflow](./workflow/3D-workflow.json)
|
||||
|
||||
|
||||
### 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.
|
||||
|
||||
@@ -67,13 +104,6 @@ https://github.com/shadowcz007/comfyui-mixlab-nodes/assets/12645064/e7e77f90-e43
|
||||
> Conveniently load images from a fixed address on the internet to ensure that default images in the workflow can be executed.
|
||||
|
||||
|
||||
### 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.
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
## Utils
|
||||
> The Color node provides a color picker for easy color selection, the Font node offers built-in font selection for use with TextImage to generate text images, and the DynamicDelayByText node allows delayed execution based on the length of the input text.
|
||||
|
||||
@@ -81,6 +111,7 @@ https://github.com/shadowcz007/comfyui-mixlab-nodes/assets/12645064/e7e77f90-e43
|
||||
|
||||
- [Added DynamicDelayByText, enabling delayed execution based on input text length.](./workflow/audio-chatgpt-workflow.json)
|
||||
|
||||
|
||||
## Other Nodes
|
||||
|
||||

|
||||
@@ -88,9 +119,7 @@ https://github.com/shadowcz007/comfyui-mixlab-nodes/assets/12645064/e7e77f90-e43
|
||||
|
||||
[workflow-1](./workflow/1-workflow.json)
|
||||
|
||||
> randomPrompt
|
||||
|
||||

|
||||
|
||||
> TransparentImage
|
||||
|
||||
@@ -113,6 +142,10 @@ Add edges to an image.
|
||||

|
||||
|
||||
|
||||
> LaMaInpainting
|
||||
|
||||
from [simple-lama-inpainting](https://github.com/enesmsahin/simple-lama-inpainting)
|
||||
|
||||
|
||||
### Improvement
|
||||
|
||||
@@ -126,13 +159,23 @@ An improvement has been made to directly redirect to GitHub to search for missin
|
||||

|
||||
|
||||
|
||||
### Update
|
||||
v0.8.0 🚀🚗🚚🏃 LaMaInpainting
|
||||
- 新增 LaMaInpainting
|
||||
- 优化color节点的输出
|
||||
- 修复高清显示屏上定位节点不准的情况
|
||||
|
||||
- Add LaMaInpainting
|
||||
- Optimize the output of the color node
|
||||
- Fix the issue of inaccurate positioning node on high-definition display screens
|
||||
|
||||
|
||||
### Models
|
||||
[Download CLIPSeg](https://huggingface.co/CIDAS/clipseg-rd64-refined/tree/main), move to : model/clipseg
|
||||
|
||||
<!-- ### Workflow
|
||||
[Workflow](./workflow.md) -->
|
||||
[Download CLIPSeg](https://huggingface.co/CIDAS/clipseg-rd64-refined/tree/main), move to : models/clipseg
|
||||
|
||||
[Download lama](https://github.com/enesmsahin/simple-lama-inpainting/releases/download/v0.1.0/big-lama.pt), move to : models/lama
|
||||
|
||||
[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
|
||||
|
||||
@@ -167,7 +210,6 @@ pip3 install -r requirements.txt
|
||||
访问 [www.mixcomfy.com](https://www.mixcomfy.com),获得更多内测功能,关注微信公众号:Mixlab无界社区
|
||||
|
||||
|
||||
|
||||
#### Thanks:
|
||||
[ComfyUI-CLIPSeg](https://github.com/biegert/ComfyUI-CLIPSeg/tree/main)
|
||||
|
||||
|
||||
+92
-17
@@ -133,8 +133,38 @@ def create_for_https():
|
||||
return (crt,key)
|
||||
|
||||
|
||||
|
||||
# workflow 目录下的所有json
|
||||
def read_workflow_json_files_all(folder_path):
|
||||
print('#read_workflow_json_files_all',folder_path)
|
||||
json_files = []
|
||||
for root, dirs, files in os.walk(folder_path):
|
||||
for file in files:
|
||||
if file.endswith('.json'):
|
||||
json_files.append(os.path.join(root, file))
|
||||
|
||||
data = []
|
||||
for file_path in json_files:
|
||||
try:
|
||||
with open(file_path) as json_file:
|
||||
json_data = json.load(json_file)
|
||||
creation_time = datetime.datetime.fromtimestamp(os.path.getctime(file_path))
|
||||
numeric_timestamp = creation_time.timestamp()
|
||||
file_info = {
|
||||
'filename': os.path.basename(file_path),
|
||||
'category': os.path.dirname(file_path),
|
||||
'data': json_data,
|
||||
'date': numeric_timestamp
|
||||
}
|
||||
data.append(file_info)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
sorted_data = sorted(data, key=lambda x: x['date'], reverse=True)
|
||||
return sorted_data
|
||||
|
||||
# workflow
|
||||
def read_workflow_json_files(folder_path):
|
||||
def read_workflow_json_files(folder_path ):
|
||||
json_files = []
|
||||
for filename in os.listdir(folder_path):
|
||||
if filename.endswith('.json'):
|
||||
@@ -169,29 +199,46 @@ def get_workflows():
|
||||
workflows=read_workflow_json_files(workflow_path)
|
||||
return workflows
|
||||
|
||||
def get_my_workflow_for_app(filename="my_workflow_app.json"):
|
||||
def get_my_workflow_for_app(filename="my_workflow_app.json",category="",is_all=False):
|
||||
app_path=os.path.join(current_path, "app")
|
||||
if not os.path.exists(app_path):
|
||||
os.mkdir(app_path)
|
||||
|
||||
category_path=os.path.join(app_path,category)
|
||||
if not os.path.exists(category_path):
|
||||
os.mkdir(category_path)
|
||||
|
||||
apps=[]
|
||||
if filename==None:
|
||||
data=read_workflow_json_files(app_path)
|
||||
|
||||
#TODO 支持目录内遍历
|
||||
if is_all:
|
||||
data=read_workflow_json_files_all(category_path)
|
||||
else:
|
||||
data=read_workflow_json_files(category_path)
|
||||
|
||||
i=0
|
||||
for item in data:
|
||||
# print(item)
|
||||
try:
|
||||
x=item["data"]
|
||||
if i==0:
|
||||
apps.append({
|
||||
"filename":item["filename"],
|
||||
# "category":item['category'],
|
||||
"data":x,
|
||||
"date":item["date"]
|
||||
"date":item["date"],
|
||||
})
|
||||
else:
|
||||
category=''
|
||||
if 'category' in x['app']:
|
||||
category=x['app']['category']
|
||||
apps.append({
|
||||
"filename":item["filename"],
|
||||
"category":category,
|
||||
"data":{
|
||||
"app":{
|
||||
"category":category,
|
||||
"description":x['app']['description'],
|
||||
"filename":(x['app']['filename'] if 'filename' in x['app'] else "") ,
|
||||
"icon":(x['app']['icon'] if 'icon' in x['app'] else None),
|
||||
@@ -205,7 +252,7 @@ def get_my_workflow_for_app(filename="my_workflow_app.json"):
|
||||
except Exception as e:
|
||||
print("发生异常:", str(e))
|
||||
else:
|
||||
app_workflow_path=os.path.join(app_path, filename)
|
||||
app_workflow_path=os.path.join(category_path, filename)
|
||||
# print('app_workflow_path: ',app_workflow_path)
|
||||
try:
|
||||
with open(app_workflow_path) as json_file:
|
||||
@@ -216,17 +263,22 @@ def get_my_workflow_for_app(filename="my_workflow_app.json"):
|
||||
except Exception as e:
|
||||
print("发生异常:", str(e))
|
||||
|
||||
if len(apps)==1:
|
||||
data=read_workflow_json_files(app_path)
|
||||
if len(apps)==1 and category!='' and category!=None:
|
||||
data=read_workflow_json_files(category_path)
|
||||
|
||||
for item in data:
|
||||
x=item["data"]
|
||||
print(apps[0]['filename'] ,item["filename"])
|
||||
# print(apps[0]['filename'] ,item["filename"])
|
||||
if apps[0]['filename']!=item["filename"]:
|
||||
category=''
|
||||
if 'category' in x['app']:
|
||||
category=x['app']['category']
|
||||
apps.append({
|
||||
"filename":item["filename"],
|
||||
# "category":category,
|
||||
"data":{
|
||||
"app":{
|
||||
"category":category,
|
||||
"description":x['app']['description'],
|
||||
"filename":(x['app']['filename'] if 'filename' in x['app'] else "") ,
|
||||
"icon":(x['app']['icon'] if 'icon' in x['app'] else None),
|
||||
@@ -245,11 +297,16 @@ def save_workflow_json(data):
|
||||
json.dump(data, file)
|
||||
return workflow_path
|
||||
|
||||
def save_workflow_for_app(data,filename="my_workflow_app.json"):
|
||||
def save_workflow_for_app(data,filename="my_workflow_app.json",category=""):
|
||||
app_path=os.path.join(current_path, "app")
|
||||
if not os.path.exists(app_path):
|
||||
os.mkdir(app_path)
|
||||
app_workflow_path=os.path.join(app_path, filename)
|
||||
|
||||
category_path=os.path.join(app_path,category)
|
||||
if not os.path.exists(category_path):
|
||||
os.mkdir(category_path)
|
||||
|
||||
app_workflow_path=os.path.join(category_path, filename)
|
||||
|
||||
try:
|
||||
output_str = json.dumps(data['output'])
|
||||
@@ -371,17 +428,26 @@ async def mixlab_workflow_hander(request):
|
||||
'file_path':file_path
|
||||
}
|
||||
elif data['task']=='save_app':
|
||||
file_path=save_workflow_for_app(data['data'],data['filename'])
|
||||
category=""
|
||||
if "category" in data:
|
||||
category=data['category']
|
||||
file_path=save_workflow_for_app(data['data'],data['filename'],category)
|
||||
result={
|
||||
'status':'success',
|
||||
'file_path':file_path
|
||||
}
|
||||
elif data['task']=='my_app':
|
||||
filename=None
|
||||
category=""
|
||||
admin=False
|
||||
if 'filename' in data:
|
||||
filename=data['filename']
|
||||
if 'category' in data:
|
||||
category=data['category']
|
||||
if 'admin' in data:
|
||||
admin=data['admin']
|
||||
result={
|
||||
'data':get_my_workflow_for_app(filename),
|
||||
'data':get_my_workflow_for_app(filename,category,admin),
|
||||
'status':'success',
|
||||
}
|
||||
elif data['task']=='list':
|
||||
@@ -437,22 +503,28 @@ PromptServer.add_routes=new_add_routes
|
||||
|
||||
|
||||
# 导入节点
|
||||
from .nodes.PromptNode import RandomPrompt
|
||||
from .nodes.ImageNode import NoiseImage,TransparentImage,LoadImagesFromPath,LoadImagesFromURL,ResizeImage,TextImage,SvgImage,Image3D,ShowLayer,NewLayer,MergeLayers,AreaToMask,SmoothMask,FeatheredMask,SplitLongMask,ImageCropByAlpha,EnhanceImage,FaceToMask
|
||||
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
|
||||
from .nodes.Clipseg import CLIPSeg,CombineMasks
|
||||
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
|
||||
|
||||
# 要导出的所有节点及其名称的字典
|
||||
# 注意:名称应全局唯一
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"AppInfo":AppInfo,
|
||||
"RandomPrompt":RandomPrompt,
|
||||
"PromptSlide":PromptSlide,
|
||||
"PromptSimplification":PromptSimplification,
|
||||
"PromptImage":PromptImage,
|
||||
"ClipInterrogator":ClipInterrogator,
|
||||
"NoiseImage":NoiseImage,
|
||||
"GradientImage":GradientImage,
|
||||
"TransparentImage":TransparentImage,
|
||||
"ResizeImageMixlab":ResizeImage,
|
||||
"LoadImagesFromPath":LoadImagesFromPath,
|
||||
@@ -492,6 +564,7 @@ NODE_CLASS_MAPPINGS = {
|
||||
"GetImageSize_":GetImageSize_,
|
||||
"SwitchByIndex":SwitchByIndex,
|
||||
"LimitNumber":LimitNumber,
|
||||
"LaMaInpainting":LaMaInpainting
|
||||
# "GamePal":GamePal
|
||||
}
|
||||
|
||||
@@ -511,7 +584,9 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"SpeechSynthesis":"SpeechSynthesis ♾️Mixlab",
|
||||
"SpeechRecognition":"SpeechRecognition ♾️Mixlab",
|
||||
"3DImage":"3DImage ♾️Mixlab",
|
||||
"DynamicDelayProcessor":"DynamicDelayByText ♾️Mixlab"
|
||||
"DynamicDelayProcessor":"DynamicDelayByText ♾️Mixlab",
|
||||
"LaMaInpainting":"LaMaInpainting ♾️Mixlab",
|
||||
"PromptSlide":"PromptSlide ♾️Mixlab"
|
||||
|
||||
# "GamePal":"GamePal ♾️Mixlab"
|
||||
}
|
||||
@@ -520,5 +595,5 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
WEB_DIRECTORY = "./web"
|
||||
|
||||
print('--------------')
|
||||
print('\033[91mMixlab Nodes: \033[93mLoaded\033[0m')
|
||||
print('\033[91m ### Mixlab Nodes: \033[93mLoaded\033[0m')
|
||||
print('--------------')
|
||||
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 477 KiB |
@@ -0,0 +1,10 @@
|
||||
Chibi Anime Style
|
||||
Gakuen Anime Style
|
||||
Gekiga Anime Style
|
||||
Jidaimono Anime Style
|
||||
Kawaii Anime Style
|
||||
Mecha Anime Style
|
||||
Realistic Anime Style
|
||||
Semi-Realistic Anime Style
|
||||
Shoji Anime Style
|
||||
Kemonomimi Anime Style
|
||||
+2052
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,23 @@
|
||||
GoPro
|
||||
Drone
|
||||
polaroid
|
||||
black and white film
|
||||
Kodachrome
|
||||
shot on 8mm
|
||||
shot on 16mm
|
||||
shot on 35mm
|
||||
Microscopic
|
||||
Fisheye Lens
|
||||
Wide Angle
|
||||
Ultra-Wide Angle
|
||||
Panorama
|
||||
Short Exposure
|
||||
Long Exposure
|
||||
Double Exposure
|
||||
f2.8
|
||||
Depth of Field
|
||||
Soft Focus
|
||||
Deep Focus
|
||||
Shallow Focus
|
||||
Vanishing Point
|
||||
Vantage Point
|
||||
@@ -4774,6 +4774,7 @@
|
||||
"Color",
|
||||
"CombineMasks_",
|
||||
"EnhanceImage",
|
||||
"GradientImage",
|
||||
"FaceToMask",
|
||||
"FeatheredMask",
|
||||
"FloatingVideo",
|
||||
@@ -4784,6 +4785,9 @@
|
||||
"MergeLayers",
|
||||
"NewLayer",
|
||||
"RandomPrompt",
|
||||
"PromptSlide",
|
||||
"PromptSimplification",
|
||||
"ClipInterrogator",
|
||||
"ScreenShare",
|
||||
"ShowLayer",
|
||||
"ShowTextForGPT",
|
||||
@@ -4793,12 +4797,14 @@
|
||||
"SplitLongMask",
|
||||
"SvgImage",
|
||||
"TextImage",
|
||||
"ResizeImageMixlab",
|
||||
"TransparentImage",
|
||||
"VAEDecodeConsistencyDecoder",
|
||||
"VAELoaderConsistencyDecoder",
|
||||
"TextToNumber",
|
||||
"TextInput_",
|
||||
"DynamicDelayProcessor"
|
||||
"DynamicDelayProcessor",
|
||||
"LaMaInpainting"
|
||||
],
|
||||
{
|
||||
"title_aux": "comfyui-mixlab-nodes"
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
Mood Lighting
|
||||
Moody Lighting
|
||||
Studio Lighting
|
||||
Cove Lighting
|
||||
Soft Lighting
|
||||
Hard Lighting
|
||||
Volumetric Lighting
|
||||
Low-Key Lighting
|
||||
High-Key Lighting
|
||||
Epic Light
|
||||
Rembrandt Lighting
|
||||
Contre-Jour
|
||||
Veiling Flare
|
||||
Crepuscular Rays
|
||||
Rays of Shimmering Light
|
||||
Godrays
|
||||
@@ -0,0 +1,132 @@
|
||||
Aaron Siskind
|
||||
Alessio Albi
|
||||
Alfred Eisenstaedt
|
||||
Alfred Stieglitz
|
||||
Alyssa Monks
|
||||
André Kertész
|
||||
Andreas Gursky
|
||||
Andrew Wyeth
|
||||
Anne Geddes
|
||||
Annie Leibovitz
|
||||
Ansel Adams
|
||||
Arnold Newman
|
||||
August Sander
|
||||
Balthus
|
||||
Berenice Abbott
|
||||
Bill Brandt
|
||||
Bill Henson
|
||||
Brassaï (Gyula Halász)
|
||||
Brooke Shaden
|
||||
Bruce Davidson
|
||||
Bruce Weber
|
||||
Bunny Yeager
|
||||
Carleton Watkins
|
||||
Carrie Mae Weems
|
||||
Chuck Close
|
||||
Cindy Sherman
|
||||
Clarence H. White
|
||||
Claude Cahun
|
||||
Danny Lyon
|
||||
David LaChapelle
|
||||
Dawoud Bey
|
||||
Diane Arbus
|
||||
Don McCullin
|
||||
Dora Maar
|
||||
Dorothea Lange
|
||||
Duane Michals
|
||||
Eadweard Muybridge
|
||||
Edward Burtynsky
|
||||
Edward Curtis
|
||||
Edward Ruscha
|
||||
Edward Steichen
|
||||
Edward Weston
|
||||
Elliott Erwitt
|
||||
Ernst Haas
|
||||
Eugene Atget
|
||||
Fan Ho
|
||||
Francesca Woodman
|
||||
Frans Lanting
|
||||
Garry Winogrand
|
||||
Georges Melies
|
||||
Gerda Taro
|
||||
Gertrude Käsebier
|
||||
Gordon Parks
|
||||
Graciela Iturbide
|
||||
Gregory Crewdson
|
||||
Harold Edgerton
|
||||
Helen Levitt
|
||||
Helmut Newton
|
||||
Hendrik Kerstens
|
||||
Henri Cartier-Bresson
|
||||
Hugh Kretschmer
|
||||
Irving Penn
|
||||
Jacques Henri Lartigue
|
||||
James Nachtwey
|
||||
James Van Der Zee
|
||||
Jay Maisel
|
||||
Jerry Uelsmann
|
||||
Joel Peter Witkin
|
||||
Joel Sartore
|
||||
John Frederick William Herschel
|
||||
Josef Sudek
|
||||
Julia Margaret Cameron
|
||||
Karl Blossfeldt
|
||||
Larry Burrows
|
||||
László Moholy-Nagy (photography)
|
||||
Lee Jeffries
|
||||
Lewis Hine
|
||||
Lorna Simpson
|
||||
Lynsey Addario
|
||||
Margaret Bourke-White
|
||||
Mario Testino
|
||||
Martin Parr
|
||||
Martin Schoeller
|
||||
Mary Ellen Mark
|
||||
Mathew B. Brady
|
||||
Méret Oppenheim
|
||||
Meryl McMaster
|
||||
Mick Rock
|
||||
Miles Aldridge
|
||||
Minor Martin White
|
||||
Nan Goldin
|
||||
Nathan Wirth
|
||||
Olive Cotton
|
||||
Olivier Rousteing
|
||||
Patrick Demarchelier
|
||||
Paul Nicklen
|
||||
Paul Outerbridge
|
||||
Paul Strand
|
||||
Pete Souza
|
||||
Peter Dombrovskis
|
||||
Peter Henry Emerson
|
||||
Peter Lik
|
||||
Peter Lindbergh
|
||||
Philip-Lorca diCorcia
|
||||
Philippe Halsman
|
||||
Ralph Gibson
|
||||
Richard Avedon
|
||||
Robert Adams
|
||||
Robert Bechtle
|
||||
Robert Capa
|
||||
Robert Frank
|
||||
Robert Mapplethorpe
|
||||
Roger Fenton
|
||||
Ruth Bernhard
|
||||
Sally Mann
|
||||
Sebastião Salgado
|
||||
Shirin Neshat
|
||||
Stefan Gesell
|
||||
Steven Meisel
|
||||
Susan Meiselas
|
||||
Vivian Maier
|
||||
Vivian Maier
|
||||
Viviane Sassen
|
||||
Walker Evans
|
||||
Wes Anderson
|
||||
William Eggleston
|
||||
William Eugene Smith
|
||||
William Henry Fox Talbot
|
||||
Yinka Shonibare
|
||||
Yousuf Karsh
|
||||
Man Ray
|
||||
Robert Mapplethorpe
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
Vintage
|
||||
Grain
|
||||
Sepia
|
||||
High Key
|
||||
Low Key
|
||||
High Dynamic Range
|
||||
Cross Process
|
||||
Radial Blur
|
||||
Infrared
|
||||
Lomo
|
||||
Photocopy
|
||||
Pencil Sketch
|
||||
Pop Art
|
||||
Orton
|
||||
Mosaic
|
||||
Selective Black and White
|
||||
Torn Paper
|
||||
Tilt-Shift
|
||||
Double Exposure
|
||||
Polaroid
|
||||
Liquid Ink
|
||||
Color Splash
|
||||
Sketch
|
||||
Water Drops
|
||||
Polarizer
|
||||
Chinese Painting
|
||||
Water Droplets
|
||||
Polarization
|
||||
Color Inversion
|
||||
Fish-eye
|
||||
Soft Focus
|
||||
Solarization
|
||||
Posterize
|
||||
Comic Book
|
||||
Duotone
|
||||
Gradient Map
|
||||
Edge Detection
|
||||
Oil Painting
|
||||
Reflection
|
||||
Mirror
|
||||
ASCII Art
|
||||
Glitch
|
||||
Time-Lapse
|
||||
Day to Night
|
||||
Surreal
|
||||
Black and White
|
||||
Sepia Tone
|
||||
Vintage Film
|
||||
Grainy Texture
|
||||
High Key Lighting
|
||||
Low Key Lighting
|
||||
Cross Processed Film
|
||||
Infrared Photography
|
||||
Photocopy
|
||||
Pencil Drawing
|
||||
Pop Art Filter
|
||||
Mosaic Filter
|
||||
Selective Desaturation
|
||||
Torn Paper
|
||||
Tilt-Shift Photography
|
||||
Double Exposure
|
||||
Polaroid Style Frame
|
||||
Water Drops Texture
|
||||
Polarizer
|
||||
Chinese Painting
|
||||
Water Droplets Texture
|
||||
Polarization
|
||||
Color Inversion
|
||||
Fish-eye Lens
|
||||
Soft Focus
|
||||
Solarize Filter
|
||||
Edge Detection
|
||||
Oil Painting
|
||||
Reflection
|
||||
Mirror Image
|
||||
Time-Lapse Photography
|
||||
Day to Night Transition
|
||||
Surreal Art Style
|
||||
Abstract Expressionism
|
||||
Acrylic Painting
|
||||
Anime
|
||||
Art Deco
|
||||
Biomorphic Abstraction
|
||||
Black and White Photograph
|
||||
Cartoon
|
||||
Charcoal Sketch
|
||||
Chibi Anime
|
||||
Chinese Painting
|
||||
Classicist Painting
|
||||
Collage
|
||||
Concept Art
|
||||
Cyberpunk
|
||||
Dada Art
|
||||
Digital Art
|
||||
Fantasy Art
|
||||
Fashion Art
|
||||
Fashion Sketch
|
||||
Fish-Eye lens Photograph
|
||||
Goth Art
|
||||
Graffiti
|
||||
Harlem Renaissance
|
||||
High Key Photograph
|
||||
Hyperrealist Pencil Sketch
|
||||
Impressionist Painting
|
||||
Josei Anime
|
||||
Long Exposure Photograph
|
||||
Low Key Photograph
|
||||
Macro Photograph
|
||||
Manga
|
||||
Metal Sculpture
|
||||
Mid Century Modern Illustration
|
||||
Mixed Media
|
||||
Modern Art
|
||||
Moe Anime
|
||||
Nihonga
|
||||
Origami
|
||||
Paper Mache
|
||||
Pen and Ink
|
||||
Pencil Sketch
|
||||
Photograph
|
||||
Photorealism
|
||||
Pinup Art
|
||||
Romanticist Painting
|
||||
Sci-Fi Art
|
||||
Semi Realistic Fantasy Art
|
||||
Semi Realistic Cyberpunk Art
|
||||
Shallow Depth of Field Photograph
|
||||
Steam Punk Art
|
||||
Stone Sculpture
|
||||
Superhero Comic
|
||||
Surrealist Art
|
||||
Tempura Painting
|
||||
Underground Comic
|
||||
Watercolor Painting
|
||||
Zulu Urban Art
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2
-1
@@ -180,8 +180,9 @@ class ShowTextForGPT:
|
||||
CATEGORY = "♾️Mixlab/GPT"
|
||||
|
||||
def run(self, text):
|
||||
# print(session_history)
|
||||
# print(text)
|
||||
return {"ui": {"text": text}, "result": (text,)}
|
||||
|
||||
|
||||
|
||||
class CharacterInText:
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
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_fn(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",)
|
||||
RETURN_NAMES = ("prompt",)
|
||||
|
||||
FUNCTION = "run"
|
||||
|
||||
CATEGORY = "♾️Mixlab/prompt"
|
||||
OUTPUT_NODE = True
|
||||
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_fn(ci,im)
|
||||
analysis_result.append( 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
|
||||
|
||||
# analysis_result=[]
|
||||
|
||||
return {"ui":{"prompt": prompt_result,"analysis":analysis_result},"result": (prompt_result,)}
|
||||
+3
-3
@@ -31,11 +31,11 @@ logger = logging.getLogger('CLIPSeg nodes')
|
||||
clipseg_model_dir = os.path.join(folder_paths.models_dir, "clipseg")
|
||||
|
||||
if not os.path.exists(clipseg_model_dir):
|
||||
print(f"## clipseg model not found: {clipseg_model_dir},pls download from https://huggingface.co/CIDAS/clipseg-rd64-refined/tree/main")
|
||||
clipseg_model_dir='CIDAS/clipseg-rd64-refined'
|
||||
|
||||
"""Helper methods for CLIPSeg nodes"""
|
||||
|
||||
|
||||
# Tensor to PIL
|
||||
def tensor2pil(image):
|
||||
return Image.fromarray(np.clip(255. * image.cpu().numpy().squeeze(), 0, 255).astype(np.uint8))
|
||||
@@ -101,7 +101,7 @@ class CLIPSeg:
|
||||
return {"required":
|
||||
{
|
||||
"image": ("IMAGE",),
|
||||
"text": ("STRING", {"multiline": False}),
|
||||
"text": ("STRING", {"multiline": False,"dynamicPrompts": False}),
|
||||
|
||||
},
|
||||
"optional":
|
||||
@@ -246,7 +246,7 @@ class CombineMasks:
|
||||
|
||||
# Resize heatmap and binary mask to match the original image dimensions
|
||||
dimensions = (image_np.shape[1], image_np.shape[0])
|
||||
print('heatmap',heatmap)
|
||||
# print('heatmap',heatmap)
|
||||
if dimensions is None or dimensions[0] == 0 or dimensions[1] == 0:
|
||||
raise ValueError("Invalid dimensions")
|
||||
|
||||
|
||||
+258
-38
@@ -1,6 +1,7 @@
|
||||
import numpy as np
|
||||
import requests
|
||||
import torch
|
||||
# from PIL import Image, ImageDraw
|
||||
from PIL import Image, ImageOps,ImageFilter,ImageEnhance,ImageDraw,ImageSequence, ImageFont
|
||||
from PIL.PngImagePlugin import PngInfo
|
||||
import base64,os,random
|
||||
@@ -155,6 +156,54 @@ def get_not_transparent_area(image):
|
||||
return (x, y, w, h)
|
||||
|
||||
|
||||
|
||||
|
||||
def generate_gradient_image(width, height, start_color_hex, end_color_hex):
|
||||
image = Image.new('RGBA', (width, height))
|
||||
draw = ImageDraw.Draw(image)
|
||||
|
||||
if len(start_color_hex) == 7:
|
||||
start_color_hex += "FF"
|
||||
if len(end_color_hex) == 7:
|
||||
end_color_hex += "FF"
|
||||
|
||||
start_color_hex = start_color_hex.lstrip("#")
|
||||
end_color_hex = end_color_hex.lstrip("#")
|
||||
|
||||
# 将十六进制颜色代码转换为RGBA元组,包括透明度
|
||||
start_color = tuple(int(start_color_hex[i:i+2], 16) for i in (0, 2, 4, 6))
|
||||
end_color = tuple(int(end_color_hex[i:i+2], 16) for i in (0, 2, 4, 6))
|
||||
|
||||
for y in range(height):
|
||||
# 计算当前行的颜色
|
||||
r = int(start_color[0] + (end_color[0] - start_color[0]) * y / height)
|
||||
g = int(start_color[1] + (end_color[1] - start_color[1]) * y / height)
|
||||
b = int(start_color[2] + (end_color[2] - start_color[2]) * y / height)
|
||||
a = int(start_color[3] + (end_color[3] - start_color[3]) * y / height)
|
||||
|
||||
# 绘制当前行的渐变色
|
||||
draw.line((0, y, width, y), fill=(r, g, b, a))
|
||||
|
||||
# Create a mask from the image's alpha channel
|
||||
mask = image.split()[-1]
|
||||
|
||||
# Convert the mask to a black and white image
|
||||
mask = mask.convert('L')
|
||||
|
||||
image=image.convert('RGB')
|
||||
|
||||
return (image, mask)
|
||||
|
||||
# 示例用法
|
||||
# width = 500
|
||||
# height = 200
|
||||
# start_color_hex = 'FF0000FF' # 红色,完全不透明
|
||||
# end_color_hex = '0000FFFF' # 蓝色,完全不透明
|
||||
|
||||
# gradient_image = generate_gradient_image(width, height, start_color_hex, end_color_hex)
|
||||
# gradient_image.save('gradient_image.png')
|
||||
|
||||
|
||||
# 读取不了分层
|
||||
def load_psd(image):
|
||||
layers=[]
|
||||
@@ -254,10 +303,59 @@ def get_images_filepath(f,white_bg=False):
|
||||
|
||||
return images
|
||||
|
||||
|
||||
|
||||
def get_average_color_image(image):
|
||||
# 打开图片
|
||||
# image = Image.open(image_path)
|
||||
|
||||
# 将图片转换为RGB模式
|
||||
image = image.convert("RGB")
|
||||
|
||||
# 获取图片的像素值
|
||||
pixel_data = image.load()
|
||||
|
||||
# 初始化颜色总和和像素数量
|
||||
total_red = 0
|
||||
total_green = 0
|
||||
total_blue = 0
|
||||
pixel_count = 0
|
||||
|
||||
# 遍历图片的每个像素
|
||||
for i in range(image.width):
|
||||
for j in range(image.height):
|
||||
# 获取像素的RGB值
|
||||
r, g, b = pixel_data[i, j]
|
||||
|
||||
# 累加颜色值
|
||||
total_red += r
|
||||
total_green += g
|
||||
total_blue += b
|
||||
|
||||
# 像素数量加1
|
||||
pixel_count += 1
|
||||
|
||||
# 计算平均颜色值
|
||||
average_red = int(total_red // pixel_count)
|
||||
average_green = int(total_green // pixel_count)
|
||||
average_blue = int(total_blue // pixel_count)
|
||||
|
||||
# 返回平均颜色值
|
||||
|
||||
im = Image.new("RGB", (image.width, image.height), (average_red, average_green, average_blue))
|
||||
|
||||
return im
|
||||
|
||||
|
||||
|
||||
# 创建噪声图像
|
||||
def create_noisy_image(width, height, mode="RGB", noise_level=128):
|
||||
def create_noisy_image(width, height, mode="RGB", noise_level=128, background_color="#FFFFFF"):
|
||||
|
||||
background_rgb = tuple(int(background_color[i:i+2], 16) for i in (1, 3, 5))
|
||||
image = Image.new(mode, (width, height), background_rgb)
|
||||
|
||||
# 创建空白图像
|
||||
image = Image.new(mode, (width, height))
|
||||
# image = Image.new(mode, (width, height))
|
||||
|
||||
# 遍历每个像素,并随机设置像素值
|
||||
pixels = image.load()
|
||||
@@ -418,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)
|
||||
@@ -506,6 +620,7 @@ def generate_text_image(text, font_path, font_size, text_color, vertical=True, s
|
||||
y = 0
|
||||
for line in lines:
|
||||
for char in line:
|
||||
#print('char',char)
|
||||
char_coordinates.append((x, y))
|
||||
x += font_size + spacing
|
||||
y += font_size + spacing
|
||||
@@ -514,10 +629,10 @@ def generate_text_image(text, font_path, font_size, text_color, vertical=True, s
|
||||
# 3. Calculate image width and height
|
||||
if layout == "vertical":
|
||||
width = (len(lines) * (font_size + spacing)) - spacing
|
||||
height = (len(max(lines, key=len)) * (font_size + spacing)) + spacing
|
||||
height = ((len(max(lines, key=len))+1) * (font_size + spacing)) + spacing
|
||||
else:
|
||||
width = (len(max(lines, key=len)) * (font_size + spacing)) - spacing
|
||||
height = (len(lines) * (font_size + spacing)) + spacing
|
||||
height = ((len(lines)-1) * (font_size + spacing)) + font_size
|
||||
|
||||
# 4. Draw each character on the image
|
||||
image = Image.new('RGBA', (width, height), (255, 255, 255,0))
|
||||
@@ -813,7 +928,10 @@ class TransparentImage:
|
||||
# result 里输出给下个节点的数据
|
||||
# print('TransparentImage',len(images_rgb))
|
||||
return {"ui":{"images": ui_images,"image_paths":image_paths},"result": (image_paths,images_rgb,images_rgba)}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class EnhanceImage:
|
||||
@classmethod
|
||||
@@ -835,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,)
|
||||
|
||||
|
||||
|
||||
@@ -869,7 +994,7 @@ class LoadImagesFromPath:
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"file_path": ("STRING",{"multiline": False,"default": ""}),
|
||||
"file_path": ("STRING",{"multiline": False,"default": "","dynamicPrompts": False}),
|
||||
},
|
||||
"optional":{
|
||||
"white_bg": (["disable","enable"],),
|
||||
@@ -990,8 +1115,8 @@ class TextImage:
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
|
||||
"text": ("STRING",{"multiline": True,"default": "龍馬精神迎新歲"}),
|
||||
"font_path": ("STRING",{"multiline": False,"default": FONT_PATH}),
|
||||
"text": ("STRING",{"multiline": True,"default": "龍馬精神迎新歲","dynamicPrompts": False}),
|
||||
"font_path": ("STRING",{"multiline": False,"default": FONT_PATH,"dynamicPrompts": False}),
|
||||
"font_size": ("INT",{
|
||||
"default":100,
|
||||
"min": 100, #Minimum value
|
||||
@@ -1001,12 +1126,12 @@ class TextImage:
|
||||
}),
|
||||
"spacing": ("INT",{
|
||||
"default":12,
|
||||
"min": 1, #Minimum value
|
||||
"min": -200, #Minimum value
|
||||
"max": 200, #Maximum value
|
||||
"step": 1, #Slider's step
|
||||
"display": "number" # Cosmetic only: display as "number" or "slider"
|
||||
}),
|
||||
"text_color":("STRING",{"multiline": False,"default": "#000000"}),
|
||||
"text_color":("STRING",{"multiline": False,"default": "#000000","dynamicPrompts": False}),
|
||||
"vertical":("BOOLEAN", {"default": True},),
|
||||
},
|
||||
}
|
||||
@@ -1547,6 +1672,80 @@ class MergeLayers:
|
||||
|
||||
|
||||
|
||||
class GradientImage:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"width": ("INT",{
|
||||
"default": 512,
|
||||
"min": 1, # 最小值
|
||||
"max": 8192, # 最大值
|
||||
"step": 1, # 间隔
|
||||
"display": "number" # 控件类型: 输入框 number、滑块 slider
|
||||
}),
|
||||
"height": ("INT",{
|
||||
"default": 512,
|
||||
"min": 1,
|
||||
"max": 8192,
|
||||
"step": 1,
|
||||
"display": "number"
|
||||
}),
|
||||
"start_color_hex": ("STRING",{"multiline": False,"default": "#FFFFFF","dynamicPrompts": False}),
|
||||
"end_color_hex": ("STRING",{"multiline": False,"default": "#000000","dynamicPrompts": False}),
|
||||
},
|
||||
}
|
||||
|
||||
# 输出的数据类型
|
||||
RETURN_TYPES = ("IMAGE","MASK",)
|
||||
|
||||
# 运行时方法名称
|
||||
FUNCTION = "run"
|
||||
|
||||
# 右键菜单目录
|
||||
CATEGORY = "♾️Mixlab/image"
|
||||
|
||||
# 输入是否为列表
|
||||
INPUT_IS_LIST = False
|
||||
|
||||
# 输出是否为列表
|
||||
OUTPUT_IS_LIST = (False,False,)
|
||||
|
||||
def run(self,width,height,start_color_hex, end_color_hex):
|
||||
|
||||
im,mask=generate_gradient_image(width, height, start_color_hex, end_color_hex)
|
||||
|
||||
#获取临时目录:temp
|
||||
output_dir = folder_paths.get_temp_directory()
|
||||
|
||||
(
|
||||
full_output_folder,
|
||||
filename,
|
||||
counter,
|
||||
subfolder,
|
||||
_,
|
||||
) = folder_paths.get_save_image_path('tmp_', output_dir)
|
||||
|
||||
image_file = f"{filename}_{counter:05}.png"
|
||||
|
||||
image_path=os.path.join(full_output_folder, image_file)
|
||||
# 保存图片
|
||||
im.save(image_path,compress_level=6)
|
||||
|
||||
# 把PIL数据类型转为tensor
|
||||
im=pil2tensor(im)
|
||||
|
||||
mask=pil2tensor(mask)
|
||||
|
||||
# 定义ui字段,数据将回传到web前端的 nodeType.prototype.onExecuted
|
||||
# result是节点的输出
|
||||
return {"ui":{"images": [{
|
||||
"filename": image_file,
|
||||
"subfolder": subfolder,
|
||||
"type":"temp"
|
||||
}]},"result": (im,mask,)}
|
||||
|
||||
|
||||
|
||||
class NoiseImage:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
@@ -1572,7 +1771,7 @@ class NoiseImage:
|
||||
"step": 1,
|
||||
"display": "slider"
|
||||
}),
|
||||
|
||||
"color_hex": ("STRING",{"multiline": False,"default": "#FFFFFF","dynamicPrompts": False}),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1591,9 +1790,9 @@ class NoiseImage:
|
||||
# 输出是否为列表
|
||||
OUTPUT_IS_LIST = (False,)
|
||||
|
||||
def run(self,width,height,noise_level):
|
||||
def run(self,width,height,noise_level,color_hex):
|
||||
# 创建噪声图像
|
||||
im=create_noisy_image(width,height,"RGB",noise_level)
|
||||
im=create_noisy_image(width,height,"RGB",noise_level,color_hex)
|
||||
|
||||
#获取临时目录:temp
|
||||
output_dir = folder_paths.get_temp_directory()
|
||||
@@ -1642,38 +1841,59 @@ 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}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_TYPES = ("IMAGE","IMAGE")
|
||||
RETURN_NAMES = ("image","average_image",)
|
||||
|
||||
FUNCTION = "run"
|
||||
|
||||
CATEGORY = "♾️Mixlab/image"
|
||||
|
||||
INPUT_IS_LIST = True
|
||||
OUTPUT_IS_LIST = (False,)
|
||||
OUTPUT_IS_LIST = (True,True,)
|
||||
|
||||
def run(self,width,height,scale_option,image=None):
|
||||
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=[]
|
||||
|
||||
if image==None:
|
||||
im=create_noisy_image(w,h,"RGB")
|
||||
else:
|
||||
im=image[0]
|
||||
im=tensor2pil(im)
|
||||
im=resize_image(im,scale_option,w,h)
|
||||
im=im.convert('RGB')
|
||||
a_im=get_average_color_image(im)
|
||||
|
||||
im=pil2tensor(im)
|
||||
imgs.append(im)
|
||||
|
||||
im=pil2tensor(im)
|
||||
a_im=pil2tensor(a_im)
|
||||
average_images.append(a_im)
|
||||
else:
|
||||
for im in image:
|
||||
im=tensor2pil(im)
|
||||
im=resize_image(im,scale_option,w,h,fill_color)
|
||||
im=im.convert('RGB')
|
||||
|
||||
a_im=get_average_color_image(im)
|
||||
|
||||
im=pil2tensor(im)
|
||||
imgs.append(im)
|
||||
|
||||
a_im=pil2tensor(a_im)
|
||||
average_images.append(a_im)
|
||||
|
||||
return (im,)
|
||||
return (imgs,average_images,)
|
||||
@@ -0,0 +1,84 @@
|
||||
import os
|
||||
import folder_paths
|
||||
from simple_lama_inpainting import SimpleLama
|
||||
from PIL import Image
|
||||
|
||||
import numpy as np
|
||||
|
||||
import torch
|
||||
|
||||
|
||||
|
||||
llma_model_path=os.path.join(folder_paths.models_dir, "lama/big-lama.pt")
|
||||
if not os.path.exists(llma_model_path):
|
||||
os.environ['LAMA_MODEL']=''
|
||||
print(f"## lama torchscript model not found: {llma_model_path},pls download from https://github.com/enesmsahin/simple-lama-inpainting/releases/download/v0.1.0/big-lama.pt")
|
||||
else:
|
||||
os.environ['LAMA_MODEL'] = llma_model_path
|
||||
|
||||
# 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)
|
||||
|
||||
|
||||
# simple_lama = SimpleLama()
|
||||
|
||||
# img_path = "image.png"
|
||||
# mask_path = "mask.png"
|
||||
|
||||
# image = Image.open(img_path)
|
||||
# mask = Image.open(mask_path).convert('L')
|
||||
|
||||
# result = simple_lama(image, mask)
|
||||
# result.save("inpainted.png")
|
||||
|
||||
|
||||
class LaMaInpainting:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"image": ("IMAGE",),
|
||||
"mask": ("MASK",),
|
||||
},
|
||||
|
||||
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
|
||||
FUNCTION = "run"
|
||||
|
||||
CATEGORY = "♾️Mixlab/image"
|
||||
|
||||
INPUT_IS_LIST = True
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
global simple_lama
|
||||
simple_lama = None
|
||||
def run(self,image,mask):
|
||||
global simple_lama
|
||||
|
||||
result=[]
|
||||
if simple_lama==None:
|
||||
simple_lama = SimpleLama()
|
||||
else:
|
||||
simple_lama.model.to("cuda" if torch.cuda.is_available() else "cpu")
|
||||
|
||||
for i in range(len(image)):
|
||||
im=image[i]
|
||||
ma=mask[i]
|
||||
im=tensor2pil(im)
|
||||
ma=tensor2pil(ma)
|
||||
ma =ma.convert('L')
|
||||
|
||||
res = simple_lama(im, ma)
|
||||
res=pil2tensor(res)
|
||||
result.append(res)
|
||||
# result.save("inpainted.png")
|
||||
if simple_lama.device=='cuda':
|
||||
simple_lama.model.to('cpu')
|
||||
|
||||
return (result,)
|
||||
+278
-56
@@ -1,15 +1,16 @@
|
||||
import random
|
||||
import comfy.utils
|
||||
import json
|
||||
import os
|
||||
import numpy as np
|
||||
from urllib import request, parse
|
||||
|
||||
|
||||
|
||||
def queue_prompt(prompt_workflow):
|
||||
p = {"prompt": prompt_workflow}
|
||||
data = json.dumps(p).encode('utf-8')
|
||||
req = request.Request("http://127.0.0.1:8188/prompt", data=data)
|
||||
request.urlopen(req)
|
||||
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')
|
||||
# req = request.Request("http://127.0.0.1:8188/prompt", data=data)
|
||||
# request.urlopen(req)
|
||||
|
||||
|
||||
default_prompt1='''Swing
|
||||
@@ -45,6 +46,225 @@ 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:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
|
||||
"prompt_keyword": ("STRING",
|
||||
{
|
||||
"multiline": False,
|
||||
"default": '',
|
||||
"dynamicPrompts": False
|
||||
}),
|
||||
|
||||
"weight":("FLOAT", {"default": 1, "min": -3,"max": 3,
|
||||
"step": 0.01,
|
||||
"display": "slider"}),
|
||||
|
||||
# "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 = ("prompt",)
|
||||
|
||||
FUNCTION = "run"
|
||||
|
||||
CATEGORY = "♾️Mixlab/prompt"
|
||||
|
||||
INPUT_IS_LIST = False
|
||||
OUTPUT_IS_LIST = (False,)
|
||||
OUTPUT_NODE = False
|
||||
|
||||
# 运行的函数
|
||||
def run(self,prompt_keyword,weight):
|
||||
# if weight < min_value:
|
||||
# weight= min_value
|
||||
# elif weight > max_value:
|
||||
# weight= max_value
|
||||
p=addWeight(prompt_keyword,weight)
|
||||
return (p,)
|
||||
|
||||
|
||||
|
||||
|
||||
class RandomPrompt:
|
||||
|
||||
'''
|
||||
@@ -87,7 +307,7 @@ class RandomPrompt:
|
||||
|
||||
# 运行的函数
|
||||
def run(self,max_count,mutable_prompt,immutable_prompt,random_sample):
|
||||
print('#运行的函数',mutable_prompt,immutable_prompt,max_count,random_sample)
|
||||
# print('#运行的函数',mutable_prompt,immutable_prompt,max_count,random_sample)
|
||||
|
||||
# Split the text into an array of words
|
||||
words1 = mutable_prompt.split("\n")
|
||||
@@ -106,6 +326,8 @@ class RandomPrompt:
|
||||
w1=w1.strip()
|
||||
for w2 in words2:
|
||||
w2=w2.strip()
|
||||
if '``' not in w2:
|
||||
w2=w2+',``'
|
||||
if w1!='' and w2!='':
|
||||
prompts.append(w2.replace('``', w1))
|
||||
pbar.update(1)
|
||||
@@ -126,62 +348,62 @@ class RandomPrompt:
|
||||
|
||||
|
||||
|
||||
class RunWorkflow:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"workflow": ("STRING", {
|
||||
"multiline": False,
|
||||
"default": ''
|
||||
}),
|
||||
"prompt": ("STRING", {
|
||||
"multiline": False,
|
||||
"default": ''
|
||||
}),
|
||||
"image": ("IMAGE",),
|
||||
"input_node": ("STRING", {
|
||||
"multiline": False,
|
||||
"default": ''
|
||||
}),
|
||||
"output_node": ("STRING", {
|
||||
"multiline": False,
|
||||
"default": ''
|
||||
}),
|
||||
},
|
||||
# class RunWorkflow:
|
||||
# @classmethod
|
||||
# def INPUT_TYPES(s):
|
||||
# return {
|
||||
# "required": {
|
||||
# "workflow": ("STRING", {
|
||||
# "multiline": False,
|
||||
# "default": ''
|
||||
# }),
|
||||
# "prompt": ("STRING", {
|
||||
# "multiline": False,
|
||||
# "default": ''
|
||||
# }),
|
||||
# "image": ("IMAGE",),
|
||||
# "input_node": ("STRING", {
|
||||
# "multiline": False,
|
||||
# "default": ''
|
||||
# }),
|
||||
# "output_node": ("STRING", {
|
||||
# "multiline": False,
|
||||
# "default": ''
|
||||
# }),
|
||||
# },
|
||||
|
||||
}
|
||||
# }
|
||||
|
||||
|
||||
|
||||
RETURN_TYPES = ("IMAGE","STRING",)
|
||||
# RETURN_TYPES = ("IMAGE","STRING",)
|
||||
|
||||
FUNCTION = "run"
|
||||
# FUNCTION = "run"
|
||||
|
||||
CATEGORY = "♾️Mixlab/workflow"
|
||||
# CATEGORY = "♾️Mixlab/workflow"
|
||||
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
OUTPUT_NODE = True
|
||||
# OUTPUT_IS_LIST = (True,)
|
||||
# OUTPUT_NODE = True
|
||||
|
||||
|
||||
# 运行的函数
|
||||
def run(self,workflow,prompt,image,input_node,output_node):
|
||||
print('#运行的函数',prompt,image,input_node,output_node)
|
||||
workflow=json.loads(workflow)
|
||||
input_node=input_node.split(".")
|
||||
workflow[input_node[0]][input_node[1]][input_node[2]]=prompt
|
||||
# # 运行的函数
|
||||
# def run(self,workflow,prompt,image,input_node,output_node):
|
||||
# print('#运行的函数',prompt,image,input_node,output_node)
|
||||
# workflow=json.loads(workflow)
|
||||
# input_node=input_node.split(".")
|
||||
# workflow[input_node[0]][input_node[1]][input_node[2]]=prompt
|
||||
|
||||
workflow_new={}
|
||||
# 遍历,seed设为随机
|
||||
for key, value in workflow.items():
|
||||
if 'inputs' in value:
|
||||
if 'seed' in value['inputs']:
|
||||
value['inputs']['seed']= random.randint(1, 18446744073709551614)
|
||||
workflow_new[key]=value
|
||||
# workflow_new={}
|
||||
# # 遍历,seed设为随机
|
||||
# for key, value in workflow.items():
|
||||
# if 'inputs' in value:
|
||||
# if 'seed' in value['inputs']:
|
||||
# value['inputs']['seed']= random.randint(1, 18446744073709551614)
|
||||
# workflow_new[key]=value
|
||||
|
||||
queue_prompt(workflow_new)
|
||||
print('#运行的函数',workflow_new[input_node[0]])
|
||||
# queue_prompt(workflow_new)
|
||||
# print('#运行的函数',workflow_new[input_node[0]])
|
||||
|
||||
# return (new_prompt)
|
||||
return {"ui":{"images": []},"result": ([image],['text'],)}
|
||||
# # return (new_prompt)
|
||||
# return {"ui":{"images": []},"result": ([image],['text'],)}
|
||||
|
||||
|
||||
+107
-21
@@ -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,
|
||||
@@ -79,6 +79,16 @@ font_files = get_font_files(r_directory)
|
||||
# print(font_files)
|
||||
|
||||
|
||||
def flatten_list(nested_list):
|
||||
flat_list = []
|
||||
for item in nested_list:
|
||||
if isinstance(item, list):
|
||||
flat_list.extend(flatten_list(item))
|
||||
else:
|
||||
flat_list.append(item)
|
||||
return flat_list
|
||||
|
||||
|
||||
class ColorInput:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
@@ -88,18 +98,23 @@ class ColorInput:
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
# RETURN_NAMES = ("WIDTH","HEIGHT","X","Y",)
|
||||
RETURN_TYPES = ("STRING","INT","INT","INT","FLOAT",)
|
||||
RETURN_NAMES = ("hex","r","g","b","a",)
|
||||
|
||||
FUNCTION = "run"
|
||||
|
||||
CATEGORY = "♾️Mixlab/utils"
|
||||
|
||||
INPUT_IS_LIST = False
|
||||
OUTPUT_IS_LIST = (False,False,)
|
||||
OUTPUT_IS_LIST = (False,False,False,False,False,)
|
||||
|
||||
def run(self,color):
|
||||
return (color,)
|
||||
h=color['hex']
|
||||
r=color['r']
|
||||
g=color['g']
|
||||
b=color['b']
|
||||
a=color['a']
|
||||
return (h,r,g,b,a,)
|
||||
|
||||
|
||||
|
||||
@@ -120,10 +135,10 @@ class FontInput:
|
||||
CATEGORY = "♾️Mixlab/utils"
|
||||
|
||||
INPUT_IS_LIST = False
|
||||
OUTPUT_IS_LIST = (False,False,)
|
||||
OUTPUT_IS_LIST = (False,)
|
||||
|
||||
def run(self,font):
|
||||
|
||||
|
||||
return (font_files[font],)
|
||||
|
||||
class TextToNumber:
|
||||
@@ -173,10 +188,31 @@ 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"
|
||||
}),
|
||||
"min_value":("FLOAT", {
|
||||
"default": 0,
|
||||
"min": -0xffffffffffffffff,
|
||||
"max": 0xffffffffffffffff,
|
||||
"step": 0.001,
|
||||
"display": "number"
|
||||
}),
|
||||
"max_value":("FLOAT", {
|
||||
"default": 1,
|
||||
"min": -0xffffffffffffffff,
|
||||
"max": 0xffffffffffffffff,
|
||||
"step": 0.001,
|
||||
"display": "number"
|
||||
}),
|
||||
"step":("FLOAT", {
|
||||
"default": 0.001,
|
||||
"min": -0xffffffffffffffff,
|
||||
"max": 0xffffffffffffffff,
|
||||
"step": 0.001,
|
||||
"display": "number"
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -189,8 +225,11 @@ class FloatSlider:
|
||||
INPUT_IS_LIST = False
|
||||
OUTPUT_IS_LIST = (False,)
|
||||
|
||||
def run(self,number):
|
||||
|
||||
def run(self,number,min_value,max_value,step):
|
||||
if number < min_value:
|
||||
number= min_value
|
||||
elif number > max_value:
|
||||
number= max_value
|
||||
return (number,)
|
||||
|
||||
|
||||
@@ -202,9 +241,30 @@ class IntNumber:
|
||||
"default": 0,
|
||||
"min": -1, #Minimum value
|
||||
"max": 0xffffffffffffffff,
|
||||
"step": 1, #Slider's step
|
||||
"display": "number" # Cosmetic only: display as "number" or "slider"
|
||||
"step": 1,
|
||||
"display": "number"
|
||||
}),
|
||||
"min_value":("INT", {
|
||||
"default": 0,
|
||||
"min": -0xffffffffffffffff,
|
||||
"max": 0xffffffffffffffff,
|
||||
"step": 1,
|
||||
"display": "number"
|
||||
}),
|
||||
"max_value":("INT", {
|
||||
"default": 1,
|
||||
"min": -0xffffffffffffffff,
|
||||
"max": 0xffffffffffffffff,
|
||||
"step": 1,
|
||||
"display": "number"
|
||||
}),
|
||||
"step":("INT", {
|
||||
"default": 1,
|
||||
"min": -0xffffffffffffffff,
|
||||
"max": 0xffffffffffffffff,
|
||||
"step":1,
|
||||
"display": "number"
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -217,8 +277,11 @@ class IntNumber:
|
||||
INPUT_IS_LIST = False
|
||||
OUTPUT_IS_LIST = (False,)
|
||||
|
||||
def run(self,number):
|
||||
|
||||
def run(self,number,min_value,max_value,step):
|
||||
if number < min_value:
|
||||
number= min_value
|
||||
elif number > max_value:
|
||||
number= max_value
|
||||
return (number,)
|
||||
|
||||
class MultiplicationNode:
|
||||
@@ -385,6 +448,8 @@ class AppInfo:
|
||||
"display": "number"
|
||||
}),
|
||||
"share_prefix":("STRING",{"multiline": False,"default": "","dynamicPrompts": False}),
|
||||
"link":("STRING",{"multiline": False,"default": "https://","dynamicPrompts": False}),
|
||||
"category":("STRING",{"multiline": False,"default": "","dynamicPrompts": False}),
|
||||
}
|
||||
|
||||
}
|
||||
@@ -396,16 +461,29 @@ class AppInfo:
|
||||
|
||||
CATEGORY = "♾️Mixlab"
|
||||
|
||||
INPUT_IS_LIST = False
|
||||
OUTPUT_IS_LIST = (False,)
|
||||
OUTPUT_NODE = True
|
||||
INPUT_IS_LIST = True
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
|
||||
def run(self,name,image,input_ids,output_ids,description,version,share_prefix):
|
||||
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])
|
||||
|
||||
return {"ui": {"json": [name,im,input_ids,output_ids,description,version,share_prefix]}, "result": (image,)}
|
||||
return {"ui": {"json": [name,im,input_ids,output_ids,description,version,share_prefix,link,category]}, "result": (image,)}
|
||||
|
||||
|
||||
|
||||
@@ -446,6 +524,7 @@ class SwitchByIndex:
|
||||
"step": 1,
|
||||
"display": "number"
|
||||
}),
|
||||
"flat": (['off',"on"],),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -459,13 +538,20 @@ class SwitchByIndex:
|
||||
INPUT_IS_LIST = True
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
|
||||
def run(self, A,B,index):
|
||||
def run(self, A,B,index,flat):
|
||||
|
||||
flat=flat[0]
|
||||
|
||||
C=[]
|
||||
index=index[0]
|
||||
for a in A:
|
||||
C.append(a)
|
||||
for b in B:
|
||||
C.append(b)
|
||||
|
||||
if flat=='on':
|
||||
C=flatten_list(C)
|
||||
|
||||
if index>-1:
|
||||
try:
|
||||
C=[C[index]]
|
||||
|
||||
+3
-1
@@ -3,4 +3,6 @@ pyOpenSSL
|
||||
watchdog
|
||||
opencv-python-headless
|
||||
matplotlib
|
||||
openai
|
||||
openai
|
||||
simple-lama-inpainting
|
||||
clip-interrogator==0.6.0
|
||||
+843
-115
File diff suppressed because it is too large
Load Diff
@@ -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) {
|
||||
|
||||
@@ -70,11 +70,13 @@ function extractInputAndOutputData (jsonData, inputIds = [], outputIds = []) {
|
||||
const data = jsonData
|
||||
const input = []
|
||||
const output = []
|
||||
const seed = {}
|
||||
|
||||
for (const id in data) {
|
||||
if (data.hasOwnProperty(id)) {
|
||||
let node = app.graph.getNodeById(id)
|
||||
if (inputIds.includes(id)) {
|
||||
let node = app.graph.getNodeById(id)
|
||||
// let node = app.graph.getNodeById(id)
|
||||
let options = []
|
||||
// 模型
|
||||
try {
|
||||
@@ -87,6 +89,29 @@ function extractInputAndOutputData (jsonData, inputIds = [], outputIds = []) {
|
||||
}
|
||||
} catch (error) {}
|
||||
|
||||
if (node.type == 'IntNumber' || node.type == 'FloatSlider') {
|
||||
// min max step
|
||||
let [v, min, max, step] = Array.from(node.widgets, w => w.value)
|
||||
options = { min, max, step }
|
||||
// node.widgets.filter(w => w.type === 'number')[0].options
|
||||
}
|
||||
|
||||
if (node.type == 'PromptSlide') {
|
||||
// min max step
|
||||
options = node.widgets.filter(w => w.type === 'slider')[0].options
|
||||
// 备选的keywords清单
|
||||
let ks = getLocalData(`_mixlab_PromptSlide`)
|
||||
let keywords = ks[id]
|
||||
// console.log('keywords',keywords)
|
||||
if (keywords && keywords[0]) {
|
||||
options.keywords = keywords
|
||||
}
|
||||
}
|
||||
|
||||
if(node.type=='Color'){
|
||||
|
||||
}
|
||||
|
||||
input[inputIds.indexOf(id)] = {
|
||||
...data[id],
|
||||
title: node.title,
|
||||
@@ -96,14 +121,23 @@ function extractInputAndOutputData (jsonData, inputIds = [], outputIds = []) {
|
||||
// input.push()
|
||||
}
|
||||
if (outputIds.includes(id)) {
|
||||
let node = app.graph.getNodeById(id)
|
||||
// let node = app.graph.getNodeById(id)
|
||||
// output.push()
|
||||
output[outputIds.indexOf(id)] = { ...data[id], title: node.title, id }
|
||||
}
|
||||
|
||||
if (node.type === 'KSampler'||node.type=='SamplerCustom') {
|
||||
// seed 的类型收集
|
||||
try {
|
||||
seed[id] = node.widgets.filter(
|
||||
w => (w.name === 'seed'||w.name=='noise_seed')
|
||||
)[0].linkedWidgets[0].value
|
||||
} catch (error) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { input, output }
|
||||
return { input, output, seed }
|
||||
}
|
||||
|
||||
function getUrl () {
|
||||
@@ -113,6 +147,16 @@ function getUrl () {
|
||||
return url
|
||||
}
|
||||
|
||||
const getLocalData = key => {
|
||||
let data = {}
|
||||
try {
|
||||
data = JSON.parse(localStorage.getItem(key)) || {}
|
||||
} catch (error) {
|
||||
return {}
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
async function save_app (json) {
|
||||
let url = getUrl()
|
||||
|
||||
@@ -121,7 +165,8 @@ async function save_app (json) {
|
||||
body: JSON.stringify({
|
||||
data: json,
|
||||
task: 'save_app',
|
||||
filename: json.app.filename
|
||||
filename: json.app.filename,
|
||||
category: json.app.category
|
||||
})
|
||||
})
|
||||
return await res.json()
|
||||
@@ -147,6 +192,8 @@ async function save (json, download = false) {
|
||||
const name = json[0],
|
||||
version = json[5],
|
||||
share_prefix = json[6], //用于分享的功能扩展
|
||||
link = json[7], //用于创建界面上的跳转链接
|
||||
category = json[8] || '', //用于分类
|
||||
description = json[4],
|
||||
inputIds = json[2].split('\n').filter(f => f),
|
||||
outputIds = json[3].split('\n').filter(f => f)
|
||||
@@ -162,7 +209,7 @@ async function save (json, download = false) {
|
||||
try {
|
||||
let data = await app.graphToPrompt()
|
||||
|
||||
const { input, output } = extractInputAndOutputData(
|
||||
const { input, output, seed } = extractInputAndOutputData(
|
||||
data.output,
|
||||
inputIds,
|
||||
outputIds
|
||||
@@ -174,8 +221,11 @@ async function save (json, download = false) {
|
||||
version,
|
||||
input,
|
||||
output,
|
||||
seed, //控制是fixed 还是random
|
||||
share_prefix,
|
||||
filename: `${name}_${version}_${new Date().toDateString()}.json`
|
||||
link,
|
||||
category,
|
||||
filename: `${name}_${version}.json`
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -183,27 +233,23 @@ async function save (json, download = false) {
|
||||
} catch (error) {}
|
||||
// console.log(data.app)
|
||||
// let http_workflow = app.graph.serialize()
|
||||
|
||||
await save_app(data)
|
||||
if (download) {
|
||||
await save_app(data)
|
||||
await downloadJsonFile(data, data.app.filename)
|
||||
let open = window.confirm(
|
||||
`You can now access the standalone application on a new page!\n${getUrl()}/mixlab/app?filename=${encodeURIComponent(data.app.filename)}`
|
||||
)
|
||||
if (open)
|
||||
window.open(
|
||||
`${getUrl()}/mixlab/app?filename=${encodeURIComponent(data.app.filename)}`
|
||||
)
|
||||
} else {
|
||||
await save_app(data)
|
||||
|
||||
let open = window.confirm(
|
||||
`You can now access the standalone application on a new page!\n${getUrl()}/mixlab/app`
|
||||
)
|
||||
if (open) window.open(`${getUrl()}/mixlab/app`)
|
||||
await downloadJsonFile(data, data.app.filename)
|
||||
}
|
||||
let open = window.confirm(
|
||||
`You can now access the standalone application on a new page!\n${getUrl()}/mixlab/app?filename=${encodeURIComponent(
|
||||
data.app.filename
|
||||
)}&category=${encodeURIComponent(data.app.category)}`
|
||||
)
|
||||
if (open)
|
||||
window.open(
|
||||
`${getUrl()}/mixlab/app?filename=${encodeURIComponent(
|
||||
data.app.filename
|
||||
)}&category=${encodeURIComponent(data.app.category)}`
|
||||
)
|
||||
} catch (error) {
|
||||
console.log('###SpeechRecognition', error)
|
||||
console.log('###error', error)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -288,10 +334,9 @@ app.registerExtension({
|
||||
}
|
||||
|
||||
const onExecuted = nodeType.prototype.onExecuted
|
||||
nodeType.prototype.onExecuted = async function (message) {
|
||||
nodeType.prototype.onExecuted = function (message) {
|
||||
onExecuted?.apply(this, arguments)
|
||||
console.log(message.json)
|
||||
|
||||
window._mixlab_app_json = message.json
|
||||
try {
|
||||
const div = this.widgets.filter(w => w.div)[0].div
|
||||
|
||||
@@ -3,7 +3,7 @@ import { app } from '../../../scripts/app.js'
|
||||
const repoOwner = 'shadowcz007' // 替换为仓库的所有者
|
||||
const repoName = 'comfyui-mixlab-nodes' // 替换为仓库的名称
|
||||
|
||||
const version = 'v0.7.0'
|
||||
const version = 'v0.11.0'
|
||||
|
||||
fetch(`https://api.github.com/repos/${repoOwner}/${repoName}/releases/latest`)
|
||||
.then(response => response.json())
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
import { app } from '../../../scripts/app.js'
|
||||
// import { api } from '../../../scripts/api.js'
|
||||
import { ComfyWidgets } from '../../../scripts/widgets.js'
|
||||
import { $el } from '../../../scripts/ui.js'
|
||||
|
||||
app.registerExtension({
|
||||
name: 'Mixlab.prompt.ClipInterrogator',
|
||||
async beforeRegisterNodeDef (nodeType, nodeData, app) {
|
||||
if (nodeData.name === 'ClipInterrogator') {
|
||||
function populate (prompts, items) {
|
||||
if (this.widgets) {
|
||||
for (let i = 0; i < this.widgets.length; i++) {
|
||||
if (this.widgets[i].type !== 'combo') this.widgets[i].onRemove?.()
|
||||
}
|
||||
this.widgets.length = 2
|
||||
}
|
||||
|
||||
const w = ComfyWidgets['STRING'](
|
||||
this,
|
||||
'text',
|
||||
['STRING', { multiline: true }],
|
||||
app
|
||||
).widget
|
||||
w.inputEl.readOnly = true
|
||||
w.inputEl.style.opacity = 0.6
|
||||
|
||||
w.value = prompts.join('\n\n')
|
||||
|
||||
const w2 = ComfyWidgets['STRING'](
|
||||
this,
|
||||
'text',
|
||||
['STRING', { multiline: true }],
|
||||
app
|
||||
).widget
|
||||
w2.inputEl.readOnly = true
|
||||
w2.inputEl.style.opacity = 0.6
|
||||
|
||||
w2.value = JSON.stringify(items, null, 2)
|
||||
|
||||
console.log('ClipInterrogator',w,w2)
|
||||
requestAnimationFrame(() => {
|
||||
const sz = this.computeSize()
|
||||
if (sz[0] < this.size[0]) {
|
||||
sz[0] = this.size[0]
|
||||
}
|
||||
if (sz[1] < this.size[1]) {
|
||||
sz[1] = this.size[1]
|
||||
}
|
||||
this.onResize?.(sz)
|
||||
app.graph.setDirtyCanvas(true, false)
|
||||
})
|
||||
}
|
||||
|
||||
// When the node is executed we will be sent the input text, display this in the widget
|
||||
const onExecuted = nodeType.prototype.onExecuted
|
||||
nodeType.prototype.onExecuted = function (message) {
|
||||
onExecuted?.apply(this, arguments)
|
||||
console.log('##', message)
|
||||
populate.call(this, message.prompt, message.analysis)
|
||||
}
|
||||
|
||||
this.serialize_widgets = true //需要保存参数
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -256,6 +256,7 @@ app.registerExtension({
|
||||
const onExecuted = nodeType.prototype.onExecuted;
|
||||
nodeType.prototype.onExecuted = function (message) {
|
||||
onExecuted?.apply(this, arguments);
|
||||
console.log('##',message.text)
|
||||
populate.call(this, message.text);
|
||||
};
|
||||
|
||||
|
||||
@@ -1800,15 +1800,19 @@ const updateUI = node => {
|
||||
pw.inputEl.title = `Total of ${prompts.length} prompts`
|
||||
} else {
|
||||
// 动态添加
|
||||
console.log('ComfyWidgets',ComfyWidgets.STRING(
|
||||
node,
|
||||
'prompts',
|
||||
['STRING', { multiline: true }]
|
||||
))
|
||||
// console.log('ComfyWidgets',ComfyWidgets.STRING(
|
||||
// node,
|
||||
// 'prompts',
|
||||
// ['STRING', { multiline: true }]
|
||||
// ))
|
||||
|
||||
// ComfyWidgets.STRING(this, "", ["", {default:this.properties.text, multiline: true}], app)
|
||||
|
||||
const w = ComfyWidgets.STRING(
|
||||
node,
|
||||
'prompts',
|
||||
['STRING', { multiline: true }]
|
||||
['STRING', { multiline: true }],
|
||||
app
|
||||
).widget
|
||||
w.inputEl.readOnly = true
|
||||
w.inputEl.style.opacity = 0.6
|
||||
@@ -2089,13 +2093,13 @@ const node = {
|
||||
name: 'RandomPrompt',
|
||||
async init (app) {
|
||||
// Any initial setup to run as soon as the page loads
|
||||
console.log('[logging]', 'extension init')
|
||||
// console.log('[logging]', 'extension init')
|
||||
|
||||
if (window.location.href.match('/?')) {
|
||||
const { workflow } = getURLParameters(window.location.href)
|
||||
if (workflow)
|
||||
get_my_workflow().then(data => {
|
||||
console.log('#get_my_workflow', data)
|
||||
// console.log('#get_my_workflow', data)
|
||||
let my_workflow = data.filter(
|
||||
d => d.filename == 'my_workflow.json'
|
||||
)[0]
|
||||
@@ -2131,10 +2135,15 @@ const node = {
|
||||
// }
|
||||
},
|
||||
loadedGraphNode (node, app) {
|
||||
// Fires for each node when loading/dragging/etc a workflow json or png
|
||||
// If you break something in the backend and want to patch workflows in the frontend
|
||||
// This is the place to do this
|
||||
// console.log("[logging]", "loaded graph node: ", exportGraph(node.graph));
|
||||
if (node.type === 'RandomPrompt') {
|
||||
try {
|
||||
let max_count = node.widgets.filter(w => w.name === "max_count")[0];
|
||||
max_count.value= node.widgets_values[0]
|
||||
// console.log('RandomPrompt',max_count,node.widgets_values[0])
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
},
|
||||
async nodeCreated (node) {
|
||||
if (node.type === 'RandomPrompt') {
|
||||
@@ -2227,7 +2236,7 @@ const node = {
|
||||
const r = onExecuted?.apply?.(this, arguments)
|
||||
|
||||
let prompts = message.prompts
|
||||
console.log('executed', message)
|
||||
// console.log('executed', message)
|
||||
// console.log('#RandomPrompt', this.widgets)
|
||||
const pw = this.widgets.filter(w => w.name === 'prompts')[0]
|
||||
|
||||
@@ -2238,7 +2247,7 @@ const node = {
|
||||
} else {
|
||||
// 动态添加
|
||||
const w = ComfyWidgets.STRING(
|
||||
node,
|
||||
this,
|
||||
'prompts',
|
||||
['STRING', { multiline: true }],
|
||||
app
|
||||
|
||||
@@ -0,0 +1,366 @@
|
||||
import { app } from '../../../scripts/app.js'
|
||||
import { api } from '../../../scripts/api.js'
|
||||
import { ComfyWidgets } from '../../../scripts/widgets.js'
|
||||
import { $el } from '../../../scripts/ui.js'
|
||||
|
||||
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 - 24}px`,
|
||||
// height: `${node_height * 0.3 - MARGIN * 2}px`,
|
||||
// background: '#EEEEEE',
|
||||
paddingLeft: '12px',
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
// alignItems: 'center',
|
||||
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 = {}
|
||||
try {
|
||||
data = JSON.parse(localStorage.getItem(key)) || {}
|
||||
} catch (error) {
|
||||
return {}
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
const setLocalDataOfWin = (key, value) => {
|
||||
localStorage.setItem(key, JSON.stringify(value))
|
||||
// window[key] = value
|
||||
}
|
||||
|
||||
const createSelect = (select, opts, targetWidget) => {
|
||||
select.style.display = 'block'
|
||||
let html = ''
|
||||
let isMatch = false
|
||||
for (const opt of opts) {
|
||||
html += `<option value='${opt}' ${
|
||||
targetWidget.value === opt ? 'selected' : ''
|
||||
}>${opt}</option>`
|
||||
if (targetWidget.value === opt) isMatch = true
|
||||
}
|
||||
select.innerHTML = html
|
||||
if (!isMatch) targetWidget.value = opts[0]
|
||||
// 添加change事件监听器
|
||||
select.addEventListener('change', function () {
|
||||
// 获取选中的选项的值
|
||||
var selectedOption = select.options[select.selectedIndex].value
|
||||
targetWidget.value = selectedOption
|
||||
// console.log(widget,selectedOption)
|
||||
})
|
||||
}
|
||||
|
||||
app.registerExtension({
|
||||
name: 'Mixlab.prompt.PromptSlide',
|
||||
async beforeRegisterNodeDef (nodeType, nodeData, app) {
|
||||
if (nodeType.comfyClass == 'PromptSlide') {
|
||||
const orig_nodeCreated = nodeType.prototype.onNodeCreated
|
||||
nodeType.prototype.onNodeCreated = async function () {
|
||||
orig_nodeCreated?.apply(this, arguments)
|
||||
|
||||
const prompt_keyword = this.widgets.filter(
|
||||
w => w.name == 'prompt_keyword'
|
||||
)[0]
|
||||
// console.log('PromptSlide nodeData', prompt_keyword)
|
||||
|
||||
const widget = {
|
||||
type: 'div',
|
||||
name: 'upload',
|
||||
draw (ctx, node, widget_width, y, widget_height) {
|
||||
Object.assign(
|
||||
this.div.style,
|
||||
get_position_style(ctx, widget_width, y, node.size[1])
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
widget.div = $el('div', {})
|
||||
|
||||
const btn = document.createElement('button')
|
||||
btn.innerText = 'Upload Keywords'
|
||||
|
||||
btn.style = `cursor: pointer;
|
||||
font-weight: 300;
|
||||
margin: 2px;
|
||||
color: var(--descrip-text);
|
||||
background-color: var(--comfy-input-bg);
|
||||
border-radius: 8px;
|
||||
border-color: var(--border-color);
|
||||
border-style: solid; height: 30px;min-width: 122px;
|
||||
`
|
||||
|
||||
const select = document.createElement('select')
|
||||
select.style = `display:none;cursor: pointer;
|
||||
font-weight: 300;
|
||||
margin: 2px;
|
||||
color: var(--descrip-text);
|
||||
background-color: var(--comfy-input-bg);
|
||||
border-radius: 8px;
|
||||
border-color: var(--border-color);
|
||||
border-style: solid; height: 30px;min-width: 100px;
|
||||
`
|
||||
widget.select = select
|
||||
|
||||
// const btn=document.createElement('button');
|
||||
// btn.innerText='Upload'
|
||||
btn.addEventListener('click', () => {
|
||||
let inp = document.createElement('input')
|
||||
inp.type = 'file'
|
||||
inp.accept = '.txt'
|
||||
inp.click()
|
||||
inp.addEventListener('change', event => {
|
||||
// 获取选择的文件
|
||||
const file = event.target.files[0]
|
||||
this.title = file.name.split('.')[0]
|
||||
|
||||
// console.log(file.name.split('.')[0])
|
||||
// 创建文件读取器
|
||||
const reader = new FileReader()
|
||||
|
||||
// 定义读取完成事件的回调函数
|
||||
reader.onload = event => {
|
||||
// 读取完成后的文本内容
|
||||
const fileContent = event.target.result.split('\n')
|
||||
const keywords = Array.from(fileContent, f => f.trim()).filter(
|
||||
f => f
|
||||
)
|
||||
// 打印文件内容
|
||||
// console.log(keywords)
|
||||
|
||||
// widget.value = keywords
|
||||
let ks = getLocalData(`_mixlab_PromptSlide`)
|
||||
ks[this.id] = keywords
|
||||
setLocalDataOfWin(`_mixlab_PromptSlide`, ks)
|
||||
|
||||
createSelect(select, keywords, prompt_keyword)
|
||||
|
||||
inp.remove()
|
||||
}
|
||||
|
||||
// 以文本方式读取文件
|
||||
reader.readAsText(file)
|
||||
})
|
||||
})
|
||||
|
||||
widget.div.appendChild(btn)
|
||||
widget.div.appendChild(select)
|
||||
document.body.appendChild(widget.div)
|
||||
this.addCustomWidget(widget)
|
||||
|
||||
const onRemoved = this.onRemoved
|
||||
this.onRemoved = () => {
|
||||
widget.div.remove()
|
||||
return onRemoved?.()
|
||||
}
|
||||
|
||||
if (this.onResize) {
|
||||
this.onResize(this.size)
|
||||
}
|
||||
|
||||
this.serialize_widgets = true //需要保存参数
|
||||
}
|
||||
}
|
||||
},
|
||||
async loadedGraphNode (node, app) {
|
||||
if (node.type === 'PromptSlide') {
|
||||
try {
|
||||
let prompt = node.widgets.filter(w => w.name === 'prompt_keyword')[0]
|
||||
let ks = getLocalData(`_mixlab_PromptSlide`)
|
||||
|
||||
let keywords = ks[node.id]
|
||||
// console.log('keywords',keywords)
|
||||
let widget = node.widgets.filter(w => w.select)[0]
|
||||
if (keywords && keywords[0]) {
|
||||
// let widget = node.widgets.filter(w => w.select)[0]
|
||||
// console.log('select',widget,widget.value)
|
||||
widget.select.style.display = 'block'
|
||||
createSelect(widget.select, keywords, prompt)
|
||||
}
|
||||
} catch (error) {}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
})
|
||||
+373
-160
@@ -48,6 +48,40 @@ async function get_nodes_map () {
|
||||
return await res.json()
|
||||
}
|
||||
|
||||
function get_url () {
|
||||
let api_host = `${window.location.hostname}:${window.location.port}`
|
||||
let api_base = ''
|
||||
let url = `${window.location.protocol}//${api_host}${api_base}`
|
||||
return url
|
||||
}
|
||||
|
||||
async function get_my_app (filename = null, category = '') {
|
||||
let url = get_url()
|
||||
const res = await fetch(`${url}/mixlab/workflow`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
task: 'my_app',
|
||||
filename,
|
||||
category,
|
||||
admin: true
|
||||
})
|
||||
})
|
||||
let result = await res.json()
|
||||
let data = []
|
||||
try {
|
||||
for (const res of result.data) {
|
||||
let { app, workflow } = res.data
|
||||
if (app.filename)
|
||||
data.push({
|
||||
...app,
|
||||
data: workflow,
|
||||
date: res.date
|
||||
})
|
||||
}
|
||||
} catch (error) {}
|
||||
return data
|
||||
}
|
||||
|
||||
function loadCSS (url) {
|
||||
var link = document.createElement('link')
|
||||
link.rel = 'stylesheet'
|
||||
@@ -667,178 +701,357 @@ app.registerExtension({
|
||||
...options
|
||||
] // and return the options
|
||||
}
|
||||
LGraphCanvas.prototype.centerOnNode = function (node) {
|
||||
// console.log(node)
|
||||
var dpr = window.devicePixelRatio || 1 // 获取设备像素比
|
||||
this.ds.offset[0] =
|
||||
-node.pos[0] -
|
||||
node.size[0] * 0.5 +
|
||||
(this.canvas.width * 0.5) / (this.ds.scale * dpr) // 考虑设备像素比
|
||||
this.ds.offset[1] =
|
||||
-node.pos[1] -
|
||||
node.size[1] * 0.5 +
|
||||
(this.canvas.height * 0.5) / (this.ds.scale * dpr) // 考虑设备像素比
|
||||
this.setDirty(true, true)
|
||||
}
|
||||
},
|
||||
async setup () {
|
||||
// Add canvas menu options
|
||||
const orig = LGraphCanvas.prototype.getCanvasMenuOptions
|
||||
LGraphCanvas.prototype.getCanvasMenuOptions = function () {
|
||||
const options = orig.apply(this, arguments)
|
||||
setup () {
|
||||
setTimeout(async () => {
|
||||
// Add canvas menu options
|
||||
const orig = LGraphCanvas.prototype.getCanvasMenuOptions
|
||||
|
||||
options.push(null, {
|
||||
content: `Nodes Map ♾️Mixlab`,
|
||||
disabled: false, // or a function determining whether to disable
|
||||
callback: async () => {
|
||||
nodesMap =
|
||||
nodesMap && Object.keys(nodesMap).length > 0
|
||||
? nodesMap
|
||||
: await getCustomnodeMappings('url')
|
||||
const apps = await get_my_app()
|
||||
|
||||
const nodesDiv = document.createDocumentFragment()
|
||||
const nodes = (await app.graphToPrompt()).output
|
||||
let apps_map = { '0': [] }
|
||||
|
||||
// console.log('[Mixlab]', 'loaded graph node: ', app)
|
||||
let div =
|
||||
document.querySelector('#mixlab_find_the_node') ||
|
||||
document.createElement('div')
|
||||
div.id = 'mixlab_find_the_node'
|
||||
div.style = `
|
||||
flex-direction: column;
|
||||
align-items: end;
|
||||
display:flex;position: absolute;
|
||||
top: 50px; left: 50px; width: 200px;
|
||||
color: var(--descrip-text);
|
||||
background-color: var(--comfy-menu-bg);
|
||||
padding: 10px;
|
||||
border: 1px solid black;z-index: 999999999;padding-top: 0;`
|
||||
for (const app of apps) {
|
||||
if (app.category) {
|
||||
if (!apps_map[app.category]) apps_map[app.category] = []
|
||||
apps_map[app.category].push(app)
|
||||
} else {
|
||||
apps_map['0'].push(app)
|
||||
}
|
||||
}
|
||||
|
||||
div.innerHTML = ''
|
||||
|
||||
let btn = document.createElement('div')
|
||||
btn.style = `display: flex;
|
||||
width: calc(100% - 24px);
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0 12px;
|
||||
height: 44px;`
|
||||
let btnB = document.createElement('button')
|
||||
let textB = document.createElement('p')
|
||||
btn.appendChild(textB)
|
||||
btn.appendChild(btnB)
|
||||
textB.style.fontSize = '12px'
|
||||
textB.innerText = `Locate and navigate nodes ♾️Mixlab`
|
||||
|
||||
btnB.style = `float: right; border: none; color: var(--input-text);
|
||||
background-color: var(--comfy-input-bg); border-color: var(--border-color);cursor: pointer;`
|
||||
btnB.addEventListener('click', () => {
|
||||
div.style.display = 'none'
|
||||
})
|
||||
btnB.innerText = 'X'
|
||||
|
||||
// 悬浮框拖动事件
|
||||
div.addEventListener('mousedown', function (e) {
|
||||
var startX = e.clientX
|
||||
var startY = e.clientY
|
||||
var offsetX = div.offsetLeft
|
||||
var offsetY = div.offsetTop
|
||||
|
||||
function moveBox (e) {
|
||||
var newX = e.clientX
|
||||
var newY = e.clientY
|
||||
var deltaX = newX - startX
|
||||
var deltaY = newY - startY
|
||||
div.style.left = offsetX + deltaX + 'px'
|
||||
div.style.top = offsetY + deltaY + 'px'
|
||||
}
|
||||
|
||||
function stopMoving () {
|
||||
document.removeEventListener('mousemove', moveBox)
|
||||
document.removeEventListener('mouseup', stopMoving)
|
||||
}
|
||||
|
||||
document.addEventListener('mousemove', moveBox)
|
||||
document.addEventListener('mouseup', stopMoving)
|
||||
})
|
||||
|
||||
div.appendChild(btn)
|
||||
|
||||
const updateNodes = (ns, nd) => {
|
||||
for (let nodeId in ns) {
|
||||
let n = ns[nodeId].class_type
|
||||
if (nodesMap[n]) {
|
||||
const { url, title } = nodesMap[n]
|
||||
let d = document.createElement('button')
|
||||
d.style = `text-align: left;margin:6px;color: var(--input-text);
|
||||
background-color: var(--comfy-input-bg); border-color: var(--border-color);cursor: pointer;`
|
||||
d.addEventListener('click', () => {
|
||||
const node = app.graph.getNodeById(nodeId)
|
||||
if (!node) return
|
||||
app.canvas.centerOnNode(node)
|
||||
app.canvas.setZoom(1)
|
||||
})
|
||||
d.addEventListener('mouseover', async () => {
|
||||
// console.log('mouseover')
|
||||
let n = (await app.graphToPrompt()).output
|
||||
if (!deepEqual(n, ns)) {
|
||||
nd.innerHTML = ''
|
||||
updateNodes(n, nd)
|
||||
}
|
||||
})
|
||||
|
||||
d.innerHTML = `
|
||||
<span>${'#' + nodeId} ${n}</span>
|
||||
<a href="${url}" target="_blank" style="text-decoration: none;">🔗</a>
|
||||
`
|
||||
d.title = title
|
||||
|
||||
nd.appendChild(d)
|
||||
let apps_opts = []
|
||||
for (const category in apps_map) {
|
||||
console.log('category',typeof(category))
|
||||
if (category === '0') {
|
||||
apps_opts.push(
|
||||
...Array.from(apps_map[category], a => {
|
||||
return {
|
||||
content: a.name,
|
||||
has_submenu: false,
|
||||
callback: async () => {
|
||||
try {
|
||||
let item = (await get_my_app(a.filename))[0]
|
||||
if (item) {
|
||||
// console.log(item.data)
|
||||
app.loadGraphData(item.data)
|
||||
setTimeout(() => {
|
||||
const node = app.graph._nodes_in_order[0]
|
||||
if (!node) return
|
||||
app.canvas.centerOnNode(node)
|
||||
app.canvas.setZoom(0.5)
|
||||
}, 1000)
|
||||
}
|
||||
} catch (error) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let nodesDivv = document.createElement('div')
|
||||
|
||||
for (let nodeId in nodes) {
|
||||
let n = nodes[nodeId].class_type
|
||||
if (nodesMap[n]) {
|
||||
const { url, title: _title } = nodesMap[n]
|
||||
let title = app.graph.getNodeById(nodeId).title || _title
|
||||
let d = document.createElement('button')
|
||||
d.style = `text-align: left;margin:6px;color: var(--input-text);
|
||||
background-color: var(--comfy-input-bg); border-color: var(--border-color);cursor: pointer;`
|
||||
d.addEventListener('click', () => {
|
||||
const node = app.graph.getNodeById(nodeId)
|
||||
if (!node) return
|
||||
app.canvas.centerOnNode(node)
|
||||
app.canvas.setZoom(1)
|
||||
})
|
||||
d.addEventListener('mouseover', async () => {
|
||||
console.log('mouseover')
|
||||
let n = (await app.graphToPrompt()).output
|
||||
if (!deepEqual(n, nodes)) {
|
||||
nodesDivv.innerHTML = ''
|
||||
updateNodes(n, nodesDivv)
|
||||
})
|
||||
)
|
||||
} else {
|
||||
// 二级
|
||||
apps_opts.push({
|
||||
content: '🚀 '+category,
|
||||
has_submenu: true,
|
||||
disabled: false,
|
||||
submenu: {
|
||||
options: Array.from(apps_map[category], a => {
|
||||
return {
|
||||
content: a.name,
|
||||
callback: async () => {
|
||||
try {
|
||||
let item = (await get_my_app(a.filename, a.category))[0]
|
||||
if (item) {
|
||||
// console.log(item.data)
|
||||
app.loadGraphData(item.data)
|
||||
setTimeout(() => {
|
||||
const node = app.graph._nodes_in_order[0]
|
||||
if (!node) return
|
||||
app.canvas.centerOnNode(node)
|
||||
app.canvas.setZoom(0.5)
|
||||
}, 1000)
|
||||
}
|
||||
} catch (error) {}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
d.innerHTML = `
|
||||
<span>${'#' + nodeId} ${title}</span>
|
||||
<a href="${url}" target="_blank" style="text-decoration: none;">🔗</a>
|
||||
`
|
||||
d.title = n
|
||||
// console.log('apps',apps_map, apps_opts,apps)
|
||||
LGraphCanvas.prototype.getCanvasMenuOptions = function () {
|
||||
const options = orig.apply(this, arguments)
|
||||
|
||||
nodesDiv.appendChild(d)
|
||||
options.push(
|
||||
null,
|
||||
{
|
||||
content: `Nodes Map ♾️Mixlab`,
|
||||
disabled: false,
|
||||
callback: async () => {
|
||||
nodesMap =
|
||||
nodesMap && Object.keys(nodesMap).length > 0
|
||||
? nodesMap
|
||||
: await getCustomnodeMappings('url')
|
||||
|
||||
const nodesDiv = document.createDocumentFragment()
|
||||
const nodes = (await app.graphToPrompt()).output
|
||||
|
||||
// console.log('[Mixlab]', 'loaded graph node: ', app)
|
||||
let div =
|
||||
document.querySelector('#mixlab_find_the_node') ||
|
||||
document.createElement('div')
|
||||
div.id = 'mixlab_find_the_node'
|
||||
div.style = `
|
||||
flex-direction: column;
|
||||
align-items: end;
|
||||
display:flex;position: absolute;
|
||||
top: 50px; left: 50px; width: 200px;
|
||||
color: var(--descrip-text);
|
||||
background-color: var(--comfy-menu-bg);
|
||||
padding: 10px;
|
||||
border: 1px solid black;z-index: 999999999;padding-top: 0;`
|
||||
|
||||
div.innerHTML = ''
|
||||
|
||||
let btn = document.createElement('div')
|
||||
btn.style = `display: flex;
|
||||
width: calc(100% - 24px);
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0 12px;
|
||||
height: 44px;`
|
||||
let btnB = document.createElement('button')
|
||||
let textB = document.createElement('p')
|
||||
btn.appendChild(textB)
|
||||
btn.appendChild(btnB)
|
||||
textB.style.fontSize = '12px'
|
||||
textB.innerText = `Locate and navigate nodes ♾️Mixlab`
|
||||
|
||||
btnB.style = `float: right; border: none; color: var(--input-text);
|
||||
background-color: var(--comfy-input-bg); border-color: var(--border-color);cursor: pointer;`
|
||||
btnB.addEventListener('click', () => {
|
||||
div.style.display = 'none'
|
||||
})
|
||||
btnB.innerText = 'X'
|
||||
|
||||
// 悬浮框拖动事件
|
||||
div.addEventListener('mousedown', function (e) {
|
||||
var startX = e.clientX
|
||||
var startY = e.clientY
|
||||
var offsetX = div.offsetLeft
|
||||
var offsetY = div.offsetTop
|
||||
|
||||
function moveBox (e) {
|
||||
var newX = e.clientX
|
||||
var newY = e.clientY
|
||||
var deltaX = newX - startX
|
||||
var deltaY = newY - startY
|
||||
div.style.left = offsetX + deltaX + 'px'
|
||||
div.style.top = offsetY + deltaY + 'px'
|
||||
}
|
||||
|
||||
function stopMoving () {
|
||||
document.removeEventListener('mousemove', moveBox)
|
||||
document.removeEventListener('mouseup', stopMoving)
|
||||
}
|
||||
|
||||
document.addEventListener('mousemove', moveBox)
|
||||
document.addEventListener('mouseup', stopMoving)
|
||||
})
|
||||
|
||||
div.appendChild(btn)
|
||||
|
||||
const updateNodes = (ns, nd) => {
|
||||
let appInfoNodes = {}
|
||||
try {
|
||||
let appInfo = app.graph._nodes.filter(
|
||||
n => n.type === 'AppInfo'
|
||||
)[0]
|
||||
if (appInfo) {
|
||||
appInfoNodes[appInfo.id] = 2
|
||||
for (const id of appInfo.widgets[1].value.split('\n')) {
|
||||
if (id && id.trim() && parseInt(id)) {
|
||||
appInfoNodes[id] = 0
|
||||
}
|
||||
}
|
||||
for (const id of app.graph._nodes
|
||||
.filter(n => n.type === 'AppInfo')[0]
|
||||
.widgets[2].value.split('\n')) {
|
||||
if (id && id.trim() && parseInt(id)) {
|
||||
appInfoNodes[id] = 1
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
|
||||
for (let nodeId in ns) {
|
||||
let n = ns[nodeId].title || ns[nodeId].class_type
|
||||
if (nodesMap[n]) {
|
||||
const { url, title } = nodesMap[n]
|
||||
let d = document.createElement('button')
|
||||
d.style = `text-align: left;
|
||||
margin:6px;
|
||||
color: var(--input-text);
|
||||
background-color: var(--comfy-input-bg);
|
||||
border-color: ${
|
||||
appInfoNodes[nodeId] >= 0
|
||||
? appInfoNodes[nodeId] === 1
|
||||
? 'blue'
|
||||
: 'red'
|
||||
: 'var(--border-color)'
|
||||
};
|
||||
cursor: pointer;`
|
||||
|
||||
if (appInfoNodes[nodeId] === 2) {
|
||||
// appinfo
|
||||
d.style.backgroundColor = '#326328'
|
||||
d.style.color = '#ffffff'
|
||||
d.style.borderColor = 'transparent'
|
||||
}
|
||||
|
||||
d.addEventListener('click', () => {
|
||||
// console.log('node')
|
||||
const node = app.graph.getNodeById(nodeId)
|
||||
|
||||
if (!node) return
|
||||
app.canvas.centerOnNode(node)
|
||||
app.canvas.setZoom(1)
|
||||
})
|
||||
d.addEventListener('mouseover', async () => {
|
||||
// console.log('mouseover')
|
||||
let n = (await app.graphToPrompt()).output
|
||||
if (!deepEqual(n, ns)) {
|
||||
nd.innerHTML = ''
|
||||
updateNodes(n, nd)
|
||||
}
|
||||
})
|
||||
|
||||
d.innerHTML = `
|
||||
<span>${'#' + nodeId} ${n}</span>
|
||||
<a href="${url}" target="_blank" style="text-decoration: none;">🔗</a>
|
||||
`
|
||||
d.title = title
|
||||
|
||||
nd.appendChild(d)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let nodesDivv = document.createElement('div')
|
||||
let appInfoNodes = {}
|
||||
try {
|
||||
let appInfo = app.graph._nodes.filter(
|
||||
n => n.type === 'AppInfo'
|
||||
)[0]
|
||||
if (appInfo) {
|
||||
appInfoNodes[appInfo.id] = 2
|
||||
for (const id of appInfo.widgets[1].value.split('\n')) {
|
||||
if (id && id.trim() && parseInt(id)) {
|
||||
appInfoNodes[id] = 0
|
||||
}
|
||||
}
|
||||
for (const id of app.graph._nodes
|
||||
.filter(n => n.type === 'AppInfo')[0]
|
||||
.widgets[2].value.split('\n')) {
|
||||
if (id && id.trim() && parseInt(id)) {
|
||||
appInfoNodes[id] = 1
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
|
||||
for (let nodeId in nodes) {
|
||||
let n = nodes[nodeId].class_type
|
||||
if (nodesMap[n]) {
|
||||
const { url, title: _title } = nodesMap[n]
|
||||
let title = app.graph.getNodeById(nodeId).title || _title
|
||||
let d = document.createElement('button')
|
||||
d.style = `text-align: left;
|
||||
margin:6px;
|
||||
color: var(--input-text);
|
||||
background-color: var(--comfy-input-bg);
|
||||
border-color: ${
|
||||
appInfoNodes[nodeId] >= 0
|
||||
? appInfoNodes[nodeId] === 1
|
||||
? 'blue'
|
||||
: 'red'
|
||||
: 'var(--border-color)'
|
||||
};
|
||||
cursor: pointer;`
|
||||
|
||||
if (appInfoNodes[nodeId] === 2) {
|
||||
// appinfo
|
||||
d.style.backgroundColor = '#326328'
|
||||
d.style.color = '#ffffff'
|
||||
d.style.borderColor = 'transparent'
|
||||
}
|
||||
|
||||
d.addEventListener('click', () => {
|
||||
console.log('click')
|
||||
const node = app.graph.getNodeById(nodeId)
|
||||
if (!node) return
|
||||
app.canvas.centerOnNode(node)
|
||||
app.canvas.setZoom(1)
|
||||
})
|
||||
d.addEventListener('mouseover', async () => {
|
||||
// console.log('mouseover')
|
||||
let n = (await app.graphToPrompt()).output
|
||||
if (!deepEqual(n, nodes)) {
|
||||
nodesDivv.innerHTML = ''
|
||||
updateNodes(n, nodesDivv)
|
||||
}
|
||||
})
|
||||
|
||||
d.innerHTML = `
|
||||
<span>${'#' + nodeId} ${title}</span>
|
||||
<a href="${url}" target="_blank" style="text-decoration: none;">🔗</a>
|
||||
`
|
||||
d.title = n
|
||||
|
||||
nodesDiv.appendChild(d)
|
||||
}
|
||||
}
|
||||
|
||||
nodesDivv.appendChild(nodesDiv)
|
||||
nodesDivv.style = `overflow: scroll;
|
||||
height: 70vh;width: 100%;`
|
||||
|
||||
div.appendChild(nodesDivv)
|
||||
|
||||
if (!document.querySelector('#mixlab_find_the_node'))
|
||||
document.body.appendChild(div)
|
||||
}
|
||||
},
|
||||
{
|
||||
content: 'Workflow App ♾️Mixlab',
|
||||
has_submenu: true,
|
||||
disabled: false,
|
||||
submenu: {
|
||||
options:apps_opts
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
nodesDivv.appendChild(nodesDiv)
|
||||
nodesDivv.style = `overflow: scroll;
|
||||
height: 70vh;width: 100%;`
|
||||
|
||||
div.appendChild(nodesDivv)
|
||||
|
||||
if (!document.querySelector('#mixlab_find_the_node'))
|
||||
document.body.appendChild(div)
|
||||
}
|
||||
})
|
||||
|
||||
// options.push({
|
||||
// content: `Save For App ♾️Mixlab`,
|
||||
// disabled: false, // or a function determining whether to disable
|
||||
// callback: async () => {
|
||||
|
||||
// }
|
||||
// })
|
||||
return options
|
||||
}
|
||||
return options
|
||||
}
|
||||
}, 1000)
|
||||
}
|
||||
})
|
||||
|
||||
+141
-44
@@ -2,7 +2,7 @@ import { app } from '../../../scripts/app.js'
|
||||
import { api } from '../../../scripts/api.js'
|
||||
import { ComfyWidgets } from '../../../scripts/widgets.js'
|
||||
import { $el } from '../../../scripts/ui.js'
|
||||
import { addValueControlWidget } from "../../../scripts/widgets.js";
|
||||
import { addValueControlWidget } from '../../../scripts/widgets.js'
|
||||
|
||||
const getLocalData = key => {
|
||||
let data = {}
|
||||
@@ -45,8 +45,47 @@ function get_position_style (ctx, widget_width, y, node_height) {
|
||||
}
|
||||
}
|
||||
|
||||
function hexToRGBA (hexColor) {
|
||||
var hex = hexColor.replace('#', '')
|
||||
var r = parseInt(hex.substring(0, 2), 16)
|
||||
var g = parseInt(hex.substring(2, 4), 16)
|
||||
var b = parseInt(hex.substring(4, 6), 16)
|
||||
|
||||
// 获取透明度的十六进制值
|
||||
var alphaHex = hex.substring(6)
|
||||
|
||||
// 将透明度的十六进制值转换为十进制值
|
||||
var alpha = parseInt(alphaHex, 16) / 255
|
||||
|
||||
return [r, g, b, alpha]
|
||||
}
|
||||
|
||||
app.registerExtension({
|
||||
name: 'Mixlab.utils.Color',
|
||||
init () {
|
||||
$el('link', {
|
||||
rel: 'stylesheet',
|
||||
href: '/extensions/comfyui-mixlab-nodes/lib/classic.min.css',
|
||||
parent: document.head
|
||||
})
|
||||
|
||||
$el('style', {
|
||||
textContent: `
|
||||
.pickr{
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.pickr .pcr-button {
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
outline: 1px solid white;
|
||||
}
|
||||
|
||||
`,
|
||||
parent: document.body
|
||||
})
|
||||
},
|
||||
async getCustomWidgets (app) {
|
||||
return {
|
||||
TCOLOR (node, inputName, inputData, app) {
|
||||
@@ -60,8 +99,17 @@ app.registerExtension({
|
||||
return [128, 32] // a method to compute the current size of the widget
|
||||
},
|
||||
async serializeValue (nodeId, widgetIndex) {
|
||||
let data = getLocalData('_mixlab_utils_color')
|
||||
return data[node.id] || '#000000'
|
||||
// let data = getLocalData('_mixlab_utils_color')
|
||||
// let hex = data[node.id] || '#000000'
|
||||
let hex = widget.value || '#000000'
|
||||
let [r, g, b, a] = hexToRGBA(hex)
|
||||
return {
|
||||
hex,
|
||||
r,
|
||||
g,
|
||||
b,
|
||||
a
|
||||
}
|
||||
}
|
||||
}
|
||||
// widget.something = something; // maybe adds stuff to it
|
||||
@@ -77,7 +125,7 @@ app.registerExtension({
|
||||
nodeType.prototype.onNodeCreated = function () {
|
||||
orig_nodeCreated?.apply(this, arguments)
|
||||
|
||||
console.log('Color nodeData', this.widgets)
|
||||
// console.log('Color nodeData', this.widgets)
|
||||
|
||||
const widget = {
|
||||
type: 'div',
|
||||
@@ -87,6 +135,7 @@ app.registerExtension({
|
||||
this.div.style,
|
||||
get_position_style(ctx, widget_width, 44, node.size[1])
|
||||
)
|
||||
// console.log('draw',y,node.widgets[0].last_y)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,35 +143,9 @@ app.registerExtension({
|
||||
|
||||
document.body.appendChild(widget.div)
|
||||
|
||||
const inputDiv = (key, placeholder, value) => {
|
||||
const inputDiv = () => {
|
||||
let div = document.createElement('div')
|
||||
const ip = document.createElement('input')
|
||||
ip.type = 'color'
|
||||
ip.className = `${'comfy-multiline-input'} ${placeholder}`
|
||||
div.style = `display: flex;
|
||||
align-items: center;
|
||||
margin: 6px 8px;
|
||||
margin-top: 0;`
|
||||
ip.placeholder = placeholder
|
||||
ip.value = value
|
||||
|
||||
ip.style = `outline: none;
|
||||
border: none;
|
||||
padding: 4px;
|
||||
width: 100%;cursor: pointer;
|
||||
height: 32px;`
|
||||
const label = document.createElement('label')
|
||||
label.style = 'font-size: 10px;min-width:32px'
|
||||
label.innerText = placeholder
|
||||
div.appendChild(label)
|
||||
div.appendChild(ip)
|
||||
|
||||
ip.addEventListener('change', () => {
|
||||
let data = getLocalData(key)
|
||||
data[this.id] = ip.value.trim()
|
||||
localStorage.setItem(key, JSON.stringify(data))
|
||||
// console.log(this.id, ip.value.trim())
|
||||
})
|
||||
div.id = `color_picker_${this.id}`
|
||||
return div
|
||||
}
|
||||
|
||||
@@ -132,10 +155,85 @@ app.registerExtension({
|
||||
|
||||
this.addCustomWidget(widget)
|
||||
|
||||
const pickr = Pickr.create({
|
||||
el: `#${inputColor.id}`,
|
||||
theme: 'classic', // or 'monolith', or 'nano'
|
||||
// closeOnScroll: true,
|
||||
default:'#000000',
|
||||
swatches: [
|
||||
'rgba(244, 67, 54, 1)',
|
||||
'rgba(233, 30, 99, 0.95)',
|
||||
'rgba(156, 39, 176, 0.9)',
|
||||
'rgba(103, 58, 183, 0.85)',
|
||||
'rgba(63, 81, 181, 0.8)',
|
||||
'rgba(33, 150, 243, 0.75)',
|
||||
'rgba(3, 169, 244, 0.7)',
|
||||
'rgba(0, 188, 212, 0.7)',
|
||||
'rgba(0, 150, 136, 0.75)',
|
||||
'rgba(76, 175, 80, 0.8)',
|
||||
'rgba(139, 195, 74, 0.85)',
|
||||
'rgba(205, 220, 57, 0.9)',
|
||||
'rgba(255, 235, 59, 0.95)',
|
||||
'rgba(255, 193, 7, 1)'
|
||||
],
|
||||
|
||||
components: {
|
||||
// Main components
|
||||
preview: true,
|
||||
opacity: true,
|
||||
hue: true,
|
||||
// Input / output Options
|
||||
interaction: {
|
||||
hex: true,
|
||||
rgba: true,
|
||||
hsla: true,
|
||||
hsva: true,
|
||||
cmyk: true,
|
||||
input: true,
|
||||
// clear: true,
|
||||
save: true,
|
||||
cancel: true
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
pickr
|
||||
.on('save', (color, instance) => {
|
||||
// console.log('Event: "save"', color.toHEXA().toString())
|
||||
// let data = getLocalData('_mixlab_utils_color')
|
||||
// data[this.id] = color.toHEXA().toString()
|
||||
// localStorage.setItem('_mixlab_utils_color', JSON.stringify(data))
|
||||
|
||||
try {
|
||||
let tc = this.widgets.filter(w => w.type == 'TCOLOR')[0]
|
||||
tc.value = color.toHEXA().toString()
|
||||
} catch (error) {}
|
||||
})
|
||||
.on('cancel', instance => {
|
||||
pickr && pickr.hide()
|
||||
})
|
||||
this.pickr = pickr
|
||||
const handleMouseWheel = () => {
|
||||
try {
|
||||
this.pickr && this.pickr.hide()
|
||||
} catch (error) {}
|
||||
}
|
||||
|
||||
document.addEventListener('wheel', handleMouseWheel)
|
||||
|
||||
const onRemoved = this.onRemoved
|
||||
this.onRemoved = () => {
|
||||
inputColor.remove()
|
||||
widget.div.remove()
|
||||
|
||||
try {
|
||||
this.pickr.destroyAndRemove()
|
||||
this.pickr = null
|
||||
document.removeEventListener('wheel', handleMouseWheel)
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
|
||||
return onRemoved?.()
|
||||
}
|
||||
|
||||
@@ -148,13 +246,11 @@ app.registerExtension({
|
||||
// You can modify widgets/add handlers/etc here
|
||||
|
||||
if (node.type === 'Color') {
|
||||
let widget = node.widgets.filter(w => w.div)[0]
|
||||
try {
|
||||
let TCOLOR = node.widgets.filter(w => w.type == 'TCOLOR')[0]
|
||||
|
||||
let data = getLocalData('_mixlab_utils_color')
|
||||
|
||||
let id = node.id
|
||||
|
||||
widget.div.querySelector('.Color').value = data[id] || '#000000'
|
||||
setTimeout(() => node.pickr.setColor(TCOLOR.value || '#000000'), 1000)
|
||||
} catch (error) {}
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -164,16 +260,17 @@ app.registerExtension({
|
||||
|
||||
async beforeRegisterNodeDef (nodeType, nodeData, app) {
|
||||
if (nodeType.comfyClass == 'TextToNumber') {
|
||||
|
||||
const onExecuted = nodeType.prototype.onExecuted
|
||||
nodeType.prototype.onExecuted = function (message) {
|
||||
onExecuted?.apply(this, arguments)
|
||||
const random_number=this.widgets.filter(w=>w.name==='random_number')[0]
|
||||
if(random_number.value==='enable'){
|
||||
const n=this.widgets.filter(w=>w.name==='number')[0]
|
||||
n.value=message.num[0]
|
||||
const random_number = this.widgets.filter(
|
||||
w => w.name === 'random_number'
|
||||
)[0]
|
||||
if (random_number.value === 'enable') {
|
||||
const n = this.widgets.filter(w => w.name === 'number')[0]
|
||||
n.value = message.num[0]
|
||||
}
|
||||
|
||||
|
||||
console.log('TextToNumber', random_number.value)
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+2
File diff suppressed because one or more lines are too long
+5
File diff suppressed because one or more lines are too long
Vendored
+5
File diff suppressed because one or more lines are too long
Vendored
+1
@@ -0,0 +1 @@
|
||||
/*! PhotoSwipe main CSS by Dmytro Semenov | photoswipe.com */.pswp{--pswp-bg:#000;--pswp-placeholder-bg:#222;--pswp-root-z-index:100000;--pswp-preloader-color:rgba(79, 79, 79, 0.4);--pswp-preloader-color-secondary:rgba(255, 255, 255, 0.9);--pswp-icon-color:#fff;--pswp-icon-color-secondary:#4f4f4f;--pswp-icon-stroke-color:#4f4f4f;--pswp-icon-stroke-width:2px;--pswp-error-text-color:var(--pswp-icon-color)}.pswp{position:fixed;top:0;left:0;width:100%;height:100%;z-index:var(--pswp-root-z-index);display:none;touch-action:none;outline:0;opacity:.003;contain:layout style size;-webkit-tap-highlight-color:transparent}.pswp:focus{outline:0}.pswp *{box-sizing:border-box}.pswp img{max-width:none}.pswp--open{display:block}.pswp,.pswp__bg{transform:translateZ(0);will-change:opacity}.pswp__bg{opacity:.005;background:var(--pswp-bg)}.pswp,.pswp__scroll-wrap{overflow:hidden}.pswp__bg,.pswp__container,.pswp__content,.pswp__img,.pswp__item,.pswp__scroll-wrap,.pswp__zoom-wrap{position:absolute;top:0;left:0;width:100%;height:100%}.pswp__img,.pswp__zoom-wrap{width:auto;height:auto}.pswp--click-to-zoom.pswp--zoom-allowed .pswp__img{cursor:-webkit-zoom-in;cursor:-moz-zoom-in;cursor:zoom-in}.pswp--click-to-zoom.pswp--zoomed-in .pswp__img{cursor:move;cursor:-webkit-grab;cursor:-moz-grab;cursor:grab}.pswp--click-to-zoom.pswp--zoomed-in .pswp__img:active{cursor:-webkit-grabbing;cursor:-moz-grabbing;cursor:grabbing}.pswp--no-mouse-drag.pswp--zoomed-in .pswp__img,.pswp--no-mouse-drag.pswp--zoomed-in .pswp__img:active,.pswp__img{cursor:-webkit-zoom-out;cursor:-moz-zoom-out;cursor:zoom-out}.pswp__button,.pswp__container,.pswp__counter,.pswp__img{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.pswp__item{z-index:1;overflow:hidden}.pswp__hidden{display:none!important}.pswp__content{pointer-events:none}.pswp__content>*{pointer-events:auto}.pswp__error-msg-container{display:grid}.pswp__error-msg{margin:auto;font-size:1em;line-height:1;color:var(--pswp-error-text-color)}.pswp .pswp__hide-on-close{opacity:.005;will-change:opacity;transition:opacity var(--pswp-transition-duration) cubic-bezier(.4,0,.22,1);z-index:10;pointer-events:none}.pswp--ui-visible .pswp__hide-on-close{opacity:1;pointer-events:auto}.pswp__button{position:relative;display:block;width:50px;height:60px;padding:0;margin:0;overflow:hidden;cursor:pointer;background:0 0;border:0;box-shadow:none;opacity:.85;-webkit-appearance:none;-webkit-touch-callout:none}.pswp__button:active,.pswp__button:focus,.pswp__button:hover{transition:none;padding:0;background:0 0;border:0;box-shadow:none;opacity:1}.pswp__button:disabled{opacity:.3;cursor:auto}.pswp__icn{fill:var(--pswp-icon-color);color:var(--pswp-icon-color-secondary)}.pswp__icn{position:absolute;top:14px;left:9px;width:32px;height:32px;overflow:hidden;pointer-events:none}.pswp__icn-shadow{stroke:var(--pswp-icon-stroke-color);stroke-width:var(--pswp-icon-stroke-width);fill:none}.pswp__icn:focus{outline:0}.pswp__img--with-bg,div.pswp__img--placeholder{background:var(--pswp-placeholder-bg)}.pswp__top-bar{position:absolute;left:0;top:0;width:100%;height:60px;display:flex;flex-direction:row;justify-content:flex-end;z-index:10;pointer-events:none!important}.pswp__top-bar>*{pointer-events:auto;will-change:opacity}.pswp__button--close{margin-right:6px}.pswp__button--arrow{position:absolute;top:0;width:75px;height:100px;top:50%;margin-top:-50px}.pswp__button--arrow:disabled{display:none;cursor:default}.pswp__button--arrow .pswp__icn{top:50%;margin-top:-30px;width:60px;height:60px;background:0 0;border-radius:0}.pswp--one-slide .pswp__button--arrow{display:none}.pswp--touch .pswp__button--arrow{visibility:hidden}.pswp--has_mouse .pswp__button--arrow{visibility:visible}.pswp__button--arrow--prev{right:auto;left:0}.pswp__button--arrow--next{right:0}.pswp__button--arrow--next .pswp__icn{left:auto;right:14px;transform:scale(-1,1)}.pswp__button--zoom{display:none}.pswp--zoom-allowed .pswp__button--zoom{display:block}.pswp--zoomed-in .pswp__zoom-icn-bar-v{display:none}.pswp__preloader{position:relative;overflow:hidden;width:50px;height:60px;margin-right:auto}.pswp__preloader .pswp__icn{opacity:0;transition:opacity .2s linear;animation:pswp-clockwise .6s linear infinite}.pswp__preloader--active .pswp__icn{opacity:.85}@keyframes pswp-clockwise{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}.pswp__counter{height:30px;margin-top:15px;margin-inline-start:20px;font-size:14px;line-height:30px;color:var(--pswp-icon-color);text-shadow:1px 1px 3px var(--pswp-icon-color-secondary);opacity:.85}.pswp--one-slide .pswp__counter{display:none}
|
||||
Vendored
+3
File diff suppressed because one or more lines are too long
+2423
-705
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,723 @@
|
||||
{
|
||||
"last_node_id": 22,
|
||||
"last_link_id": 23,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 9,
|
||||
"type": "CLIPTextEncode",
|
||||
"pos": [
|
||||
2070,
|
||||
830
|
||||
],
|
||||
"size": {
|
||||
"0": 425.27801513671875,
|
||||
"1": 180.6060791015625
|
||||
},
|
||||
"flags": {},
|
||||
"order": 5,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "clip",
|
||||
"type": "CLIP",
|
||||
"link": 7
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "CONDITIONING",
|
||||
"type": "CONDITIONING",
|
||||
"links": [
|
||||
4
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "CLIPTextEncode"
|
||||
},
|
||||
"widgets_values": [
|
||||
"text, watermark"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"type": "EmptyLatentImage",
|
||||
"pos": [
|
||||
2070,
|
||||
1060
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 106
|
||||
},
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "LATENT",
|
||||
"type": "LATENT",
|
||||
"links": [
|
||||
5
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "EmptyLatentImage"
|
||||
},
|
||||
"widgets_values": [
|
||||
512,
|
||||
512,
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"type": "CheckpointLoaderSimple",
|
||||
"pos": [
|
||||
1640,
|
||||
920
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 98
|
||||
},
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "MODEL",
|
||||
"type": "MODEL",
|
||||
"links": [
|
||||
2
|
||||
],
|
||||
"slot_index": 0
|
||||
},
|
||||
{
|
||||
"name": "CLIP",
|
||||
"type": "CLIP",
|
||||
"links": [
|
||||
6,
|
||||
7
|
||||
],
|
||||
"slot_index": 1
|
||||
},
|
||||
{
|
||||
"name": "VAE",
|
||||
"type": "VAE",
|
||||
"links": [
|
||||
9
|
||||
],
|
||||
"slot_index": 2
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "CheckpointLoaderSimple"
|
||||
},
|
||||
"widgets_values": [
|
||||
"deliberate_v2.safetensors"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"type": "CLIPTextEncode",
|
||||
"pos": [
|
||||
2080,
|
||||
630
|
||||
],
|
||||
"size": {
|
||||
"0": 422.84503173828125,
|
||||
"1": 164.31304931640625
|
||||
},
|
||||
"flags": {},
|
||||
"order": 8,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "clip",
|
||||
"type": "CLIP",
|
||||
"link": 6
|
||||
},
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"link": 16,
|
||||
"widget": {
|
||||
"name": "text"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "CONDITIONING",
|
||||
"type": "CONDITIONING",
|
||||
"links": [
|
||||
3
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "CLIPTextEncode"
|
||||
},
|
||||
"widgets_values": [
|
||||
"beautiful scenery nature glass bottle landscape, , purple galaxy bottle,"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"type": "VAEDecode",
|
||||
"pos": [
|
||||
2870,
|
||||
630
|
||||
],
|
||||
"size": {
|
||||
"0": 210,
|
||||
"1": 46
|
||||
},
|
||||
"flags": {
|
||||
"collapsed": false
|
||||
},
|
||||
"order": 11,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "samples",
|
||||
"type": "LATENT",
|
||||
"link": 8
|
||||
},
|
||||
{
|
||||
"name": "vae",
|
||||
"type": "VAE",
|
||||
"link": 9
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
12
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "VAEDecode"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 16,
|
||||
"type": "ShowTextForGPT",
|
||||
"pos": [
|
||||
3288,
|
||||
-323
|
||||
],
|
||||
"size": {
|
||||
"0": 449.1168212890625,
|
||||
"1": 76
|
||||
},
|
||||
"flags": {},
|
||||
"order": 9,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"link": 20,
|
||||
"widget": {
|
||||
"name": "text"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "STRING",
|
||||
"type": "STRING",
|
||||
"links": null,
|
||||
"shape": 6
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "ShowTextForGPT"
|
||||
},
|
||||
"widgets_values": [
|
||||
"a girl face,super,(Pop Art:1.26),(Black and White:1.26)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"type": "PreviewImage",
|
||||
"pos": [
|
||||
3319,
|
||||
-161
|
||||
],
|
||||
"size": {
|
||||
"0": 435.8727111816406,
|
||||
"1": 511.8609619140625
|
||||
},
|
||||
"flags": {},
|
||||
"order": 13,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 13
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "PreviewImage"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"type": "KSampler",
|
||||
"pos": [
|
||||
2520,
|
||||
630
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 262
|
||||
},
|
||||
"flags": {},
|
||||
"order": 10,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "model",
|
||||
"type": "MODEL",
|
||||
"link": 2
|
||||
},
|
||||
{
|
||||
"name": "positive",
|
||||
"type": "CONDITIONING",
|
||||
"link": 3
|
||||
},
|
||||
{
|
||||
"name": "negative",
|
||||
"type": "CONDITIONING",
|
||||
"link": 4
|
||||
},
|
||||
{
|
||||
"name": "latent_image",
|
||||
"type": "LATENT",
|
||||
"link": 5
|
||||
},
|
||||
{
|
||||
"name": "seed",
|
||||
"type": "INT",
|
||||
"link": 21,
|
||||
"widget": {
|
||||
"name": "seed"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "LATENT",
|
||||
"type": "LATENT",
|
||||
"links": [
|
||||
8
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "KSampler"
|
||||
},
|
||||
"widgets_values": [
|
||||
1063141893699118,
|
||||
"fixed",
|
||||
20,
|
||||
8,
|
||||
"euler",
|
||||
"normal",
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 18,
|
||||
"type": "IntNumber",
|
||||
"pos": [
|
||||
2852,
|
||||
275
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 58
|
||||
},
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "INT",
|
||||
"type": "INT",
|
||||
"links": [
|
||||
21
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"title": "Seed",
|
||||
"properties": {
|
||||
"Node name for S&R": "IntNumber"
|
||||
},
|
||||
"widgets_values": [
|
||||
-1
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 22,
|
||||
"type": "PromptSlide",
|
||||
"pos": [
|
||||
2364,
|
||||
-294
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 82
|
||||
},
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "prompt",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
23
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "PromptSlide"
|
||||
},
|
||||
"widgets_values": [
|
||||
"Pop Art",
|
||||
1.26
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 12,
|
||||
"type": "AppInfo",
|
||||
"pos": [
|
||||
2924,
|
||||
846
|
||||
],
|
||||
"size": {
|
||||
"0": 400,
|
||||
"1": 272
|
||||
},
|
||||
"flags": {},
|
||||
"order": 12,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"link": 12
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
13
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "AppInfo"
|
||||
},
|
||||
"widgets_values": [
|
||||
"Prompt-weight-test",
|
||||
"21\n22\n18",
|
||||
"11\n16",
|
||||
"",
|
||||
1,
|
||||
"",
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 21,
|
||||
"type": "PromptSlide",
|
||||
"pos": [
|
||||
2360,
|
||||
-544
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 82
|
||||
},
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "prompt",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
22
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "PromptSlide"
|
||||
},
|
||||
"widgets_values": [
|
||||
"Black and White",
|
||||
1.26
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 13,
|
||||
"type": "RandomPrompt",
|
||||
"pos": [
|
||||
2840,
|
||||
-330
|
||||
],
|
||||
"size": {
|
||||
"0": 400,
|
||||
"1": 224
|
||||
},
|
||||
"flags": {},
|
||||
"order": 6,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "mutable_prompt",
|
||||
"type": "STRING",
|
||||
"link": 22,
|
||||
"widget": {
|
||||
"name": "mutable_prompt"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "immutable_prompt",
|
||||
"type": "STRING",
|
||||
"link": 23,
|
||||
"widget": {
|
||||
"name": "immutable_prompt"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "STRING",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
19
|
||||
],
|
||||
"shape": 6,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "RandomPrompt"
|
||||
},
|
||||
"widgets_values": [
|
||||
1,
|
||||
"",
|
||||
" ``",
|
||||
"disable",
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 15,
|
||||
"type": "RandomPrompt",
|
||||
"pos": [
|
||||
2840,
|
||||
-30
|
||||
],
|
||||
"size": {
|
||||
"0": 400,
|
||||
"1": 224
|
||||
},
|
||||
"flags": {},
|
||||
"order": 7,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "mutable_prompt",
|
||||
"type": "STRING",
|
||||
"link": 19,
|
||||
"widget": {
|
||||
"name": "mutable_prompt"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "STRING",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
16,
|
||||
20
|
||||
],
|
||||
"shape": 6,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "RandomPrompt"
|
||||
},
|
||||
"widgets_values": [
|
||||
1,
|
||||
"",
|
||||
" a girl face,super,``",
|
||||
"disable",
|
||||
""
|
||||
]
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
2,
|
||||
6,
|
||||
0,
|
||||
5,
|
||||
0,
|
||||
"MODEL"
|
||||
],
|
||||
[
|
||||
3,
|
||||
8,
|
||||
0,
|
||||
5,
|
||||
1,
|
||||
"CONDITIONING"
|
||||
],
|
||||
[
|
||||
4,
|
||||
9,
|
||||
0,
|
||||
5,
|
||||
2,
|
||||
"CONDITIONING"
|
||||
],
|
||||
[
|
||||
5,
|
||||
7,
|
||||
0,
|
||||
5,
|
||||
3,
|
||||
"LATENT"
|
||||
],
|
||||
[
|
||||
6,
|
||||
6,
|
||||
1,
|
||||
8,
|
||||
0,
|
||||
"CLIP"
|
||||
],
|
||||
[
|
||||
7,
|
||||
6,
|
||||
1,
|
||||
9,
|
||||
0,
|
||||
"CLIP"
|
||||
],
|
||||
[
|
||||
8,
|
||||
5,
|
||||
0,
|
||||
10,
|
||||
0,
|
||||
"LATENT"
|
||||
],
|
||||
[
|
||||
9,
|
||||
6,
|
||||
2,
|
||||
10,
|
||||
1,
|
||||
"VAE"
|
||||
],
|
||||
[
|
||||
12,
|
||||
10,
|
||||
0,
|
||||
12,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
13,
|
||||
12,
|
||||
0,
|
||||
11,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
16,
|
||||
15,
|
||||
0,
|
||||
8,
|
||||
1,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
19,
|
||||
13,
|
||||
0,
|
||||
15,
|
||||
0,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
20,
|
||||
15,
|
||||
0,
|
||||
16,
|
||||
0,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
21,
|
||||
18,
|
||||
0,
|
||||
5,
|
||||
4,
|
||||
"INT"
|
||||
],
|
||||
[
|
||||
22,
|
||||
21,
|
||||
0,
|
||||
13,
|
||||
0,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
23,
|
||||
22,
|
||||
0,
|
||||
13,
|
||||
1,
|
||||
"STRING"
|
||||
]
|
||||
],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {},
|
||||
"version": 0.4
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 2.5 MiB After Width: | Height: | Size: 2.5 MiB |
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 1.2 MiB |
Reference in New Issue
Block a user