Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ea24f52b13 | ||
|
|
4abfc47346 | ||
|
|
3b95010d06 | ||
|
|
b3c1b96088 | ||
|
|
a9f1326873 | ||
|
|
75a696fb64 | ||
|
|
24aaacba6d | ||
|
|
e3cd7d5f91 | ||
|
|
2e228e8db5 | ||
|
|
5c9dd80370 | ||
|
|
727f5f2e48 | ||
|
|
1c238f7697 | ||
|
|
119d7cce15 | ||
|
|
4afc8f6083 | ||
|
|
a9ec3af066 | ||
|
|
9edae81fee | ||
|
|
b941b12f12 | ||
|
|
3069de188a | ||
|
|
a968f08abd | ||
|
|
4153d3e5ff | ||
|
|
9d9c1a6c84 | ||
|
|
16ef10a4d9 | ||
|
|
b3766e440a | ||
|
|
6359c3f70f | ||
|
|
efe73fb965 | ||
|
|
c45a962fcc | ||
|
|
f98a03e2e9 | ||
|
|
5b6257814d | ||
|
|
69a445d4ed | ||
|
|
e82c786b8a | ||
|
|
eec2225c89 | ||
|
|
f7355e0b71 | ||
|
|
6c6a99cfe4 | ||
|
|
b4634e2e0d | ||
|
|
3f4cba0612 | ||
|
|
38db99cc75 | ||
|
|
4d5906394b | ||
|
|
2fc212b156 | ||
|
|
53fbb5b027 | ||
|
|
4f24721450 | ||
|
|
83043727b5 | ||
|
|
2d336afb85 | ||
|
|
4d309435c8 | ||
|
|
099ce9cdfd | ||
|
|
8914e60cb8 | ||
|
|
dbd30a40e9 | ||
|
|
c9a598fd59 | ||
|
|
e331e588cf | ||
|
|
2011557771 | ||
|
|
f0ba45d14e | ||
|
|
8352a521b7 | ||
|
|
aa3d4d79f8 | ||
|
|
4f650d760c | ||
|
|
ea4b792627 | ||
|
|
883605239a | ||
|
|
5b8cab920c | ||
|
|
8d3d327335 | ||
|
|
32574050c4 | ||
|
|
8d45a90d9b | ||
|
|
f66862a422 | ||
|
|
6a56be3a9b | ||
|
|
ebf6395de2 | ||
|
|
5df9fbf50d | ||
|
|
ff961155c9 | ||
|
|
14838d06a8 | ||
|
|
99def24dd8 | ||
|
|
f5b210d142 | ||
|
|
a137a23b48 | ||
|
|
6bbf06d9e9 | ||
|
|
0b614b40cf | ||
|
|
27673561bd | ||
|
|
6e2070410d | ||
|
|
7ccf21f74f | ||
|
|
3b2710f285 | ||
|
|
c4b277235b | ||
|
|
a55318add1 | ||
|
|
b57123a4fe | ||
|
|
04dcc00670 | ||
|
|
746a02b49f | ||
|
|
bdbe3db2a9 | ||
|
|
27ae99ad86 | ||
|
|
38add89547 | ||
|
|
a9612fbb2f | ||
|
|
429cc29b5b | ||
|
|
8eca94e405 | ||
|
|
ad71daafb6 | ||
|
|
c936d83688 | ||
|
|
c6684d680f | ||
|
|
fe358b0e13 | ||
|
|
897f259a2a | ||
|
|
f3302c1b3a | ||
|
|
573feeaaab | ||
|
|
019c98ecc1 | ||
|
|
ad6a51a4b5 | ||
|
|
f94278776e | ||
|
|
315885cb0b | ||
|
|
7e605f8228 | ||
|
|
aed70435f9 | ||
|
|
1fb1728ede | ||
|
|
497c4fe5a3 | ||
|
|
09ad7764b8 | ||
|
|
bc3d24fddf | ||
|
|
2d634d628a | ||
|
|
790c22d919 | ||
|
|
c275a56806 | ||
|
|
82c3c7addd | ||
|
|
b464d85c04 | ||
|
|
078618b4cf | ||
|
|
561805a417 | ||
|
|
7bb4324365 | ||
|
|
0608653d35 | ||
|
|
fa3472cdc5 | ||
|
|
7d553b6fcf | ||
|
|
677627630e | ||
|
|
9f23172b22 | ||
|
|
14ceaa472d | ||
|
|
1188b9d3bc | ||
|
|
424c9a9423 | ||
|
|
8f60c81ef3 | ||
|
|
dd648d1ae5 | ||
|
|
02e839e272 | ||
|
|
ec8c56707b | ||
|
|
1e8d317ee8 | ||
|
|
e81df111a7 | ||
|
|
90e55ffe14 | ||
|
|
4e73b1d3fc | ||
|
|
21dca1e34f | ||
|
|
c2292850bb | ||
|
|
04791c92e2 | ||
|
|
d9462b6d8a | ||
|
|
be9b83559e | ||
|
|
958889afee | ||
|
|
dce677035f | ||
|
|
d98a8855ad | ||
|
|
7cd0587a64 | ||
|
|
bb3967f11e | ||
|
|
36ee203bd7 | ||
|
|
914919ba75 | ||
|
|
a4514e8565 | ||
|
|
116e983c50 | ||
|
|
fb667b6c42 | ||
|
|
b0a090cf14 | ||
|
|
110b470d33 | ||
|
|
c517c4d015 | ||
|
|
35ed4f9101 | ||
|
|
18c723c2e3 | ||
|
|
631223602c | ||
|
|
a6cc907de2 | ||
|
|
f847dcccf4 | ||
|
|
ff3f8f52d0 | ||
|
|
d7d46682fc | ||
|
|
dfe720f3ec | ||
|
|
2c68662c22 | ||
|
|
bc1b998ba5 | ||
|
|
9d5ccc3389 | ||
|
|
a811f884cc | ||
|
|
8b86c379d1 | ||
|
|
8c0321b1cf | ||
|
|
62ab2c3514 | ||
|
|
ed61ca761a | ||
|
|
95ca17d816 | ||
|
|
db6c721a8f | ||
|
|
b5c68751aa | ||
|
|
7780bfd671 | ||
|
|
a56970693a | ||
|
|
c8b24fe84b | ||
|
|
798aabf333 | ||
|
|
d38a7aa558 | ||
|
|
0bdf1e47a6 | ||
|
|
a44016e57c | ||
|
|
35a9351e53 | ||
|
|
c1b2112bb8 | ||
|
|
42a3dd261f | ||
|
|
e14487fab1 | ||
|
|
c16df1a473 |
@@ -1,3 +1,6 @@
|
||||
__pycache__/
|
||||
https/
|
||||
nodes/config.json
|
||||
nodes/config.json
|
||||
workflow/my_workflow.json
|
||||
workflow/my_workflow_app.json
|
||||
app/*
|
||||
@@ -1,12 +1,35 @@
|
||||
##
|
||||
In progress.
|
||||
!!
|
||||
|
||||
### Workflow-to-APP 🚀🚗🚚🏃
|
||||
- 新增AppInfo节点,可以通过简单的配置,把workflow转变为一个Web APP。
|
||||
- 支持多个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.
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
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-text
|
||||
|
||||
> 暂时支持6种节点作为界面上的输入节点:Load Image、CLIPTextEncode、TextInput_、FloatSlider、IntNumber、CheckpointLoaderSimple、LoraLoader
|
||||
|
||||
> 输出节点:PreviewImage 、SaveImage、ShowTextForGPT、VHS_VideoCombine
|
||||
|
||||
|
||||
### 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! 💻🌐
|
||||
|
||||

|
||||
|
||||
|
||||
### 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
|
||||
|
||||
|
||||
@@ -15,40 +38,53 @@ https://github.com/shadowcz007/comfyui-mixlab-nodes/assets/12645064/e7e77f90-e43
|
||||
|
||||
!! Please use the address with HTTPS (https://127.0.0.1).
|
||||
|
||||
## Installation
|
||||
|
||||
manually install, simply clone the repo into the custom_nodes directory with this command:
|
||||
### SpeechRecognition & SpeechSynthesis
|
||||

|
||||
|
||||
```
|
||||
cd ComfyUI/custom_nodes
|
||||
[Voice + Real-time Face Swap Workflow](./workflow/语音+实时换脸workflow.json)
|
||||
|
||||
git clone https://github.com/shadowcz007/comfyui-mixlab-nodes.git
|
||||
### 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
|
||||
|
||||
```
|
||||

|
||||
|
||||
Install the requirements:
|
||||
[workflow-5](./workflow/5-gpt-workflow.json)
|
||||
|
||||
run directly:
|
||||
```
|
||||
cd ComfyUI_Mixlab
|
||||
install.bat
|
||||
```
|
||||
|
||||
or install the requirements using:
|
||||
```
|
||||
../../../python_embeded/python.exe -s -m pip install -r requirements.txt
|
||||
```
|
||||
|
||||
If you are using a venv, make sure you have it activated before installation and use:
|
||||
```
|
||||
pip3 install -r requirements.txt
|
||||
```
|
||||
### 3D
|
||||

|
||||
[workflow](./workflow/3D-workflow.json)
|
||||
|
||||
|
||||
### 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.
|
||||
|
||||
## Nodes
|
||||

|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
### 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.
|
||||
|
||||

|
||||
|
||||
[workflow-4](./workflow/4-loadfromlocal-watcher-workflow.json)
|
||||
|
||||
### LoadImagesFromURL
|
||||
> Conveniently load images from a fixed address on the internet to ensure that default images in the workflow can be executed.
|
||||
|
||||
|
||||
## 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.
|
||||
|
||||
- [添加了DynamicDelayByText功能,可以根据输入文本的长度进行延迟执行。](./workflow/audio-chatgpt-workflow.json)
|
||||
|
||||
- [Added DynamicDelayByText, enabling delayed execution based on input text length.](./workflow/audio-chatgpt-workflow.json)
|
||||
|
||||
## Other Nodes
|
||||
|
||||

|
||||

|
||||
|
||||
[workflow-1](./workflow/1-workflow.json)
|
||||
@@ -62,13 +98,6 @@ pip3 install -r requirements.txt
|
||||

|
||||
|
||||
|
||||
|
||||
>LoadImagesFromLocal
|
||||
|
||||

|
||||
|
||||
[workflow-4](./workflow/4-loadfromlocal-watcher-workflow.json)
|
||||
|
||||
> Consistency Decoder
|
||||
|
||||
[openai Consistency Decoder]( https://github.com/openai/consistencydecoder)
|
||||
@@ -85,16 +114,103 @@ Add edges to an image.
|
||||

|
||||
|
||||
|
||||
> LaMaInpainting
|
||||
|
||||
from [simple-lama-inpainting](https://github.com/enesmsahin/simple-lama-inpainting)
|
||||
|
||||
|
||||
### Improvement
|
||||
|
||||
- Add "help" option to the context menu for each node.
|
||||
- Add "Nodes Map" option to the global context menu.
|
||||
|
||||
An improvement has been made to directly redirect to GitHub to search for missing nodes when loading the graph.
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
|
||||
### 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
|
||||
[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
|
||||
|
||||
<!-- ### Workflow
|
||||
[Workflow](./workflow.md) -->
|
||||
|
||||
|
||||
## Installation
|
||||
|
||||
manually install, simply clone the repo into the custom_nodes directory with this command:
|
||||
|
||||
```
|
||||
cd ComfyUI/custom_nodes
|
||||
|
||||
git clone https://github.com/shadowcz007/comfyui-mixlab-nodes.git
|
||||
|
||||
```
|
||||
|
||||
Install the requirements:
|
||||
|
||||
run directly:
|
||||
```
|
||||
cd ComfyUI/custom_nodes/comfyui-mixlab-nodes
|
||||
install.bat
|
||||
```
|
||||
|
||||
or install the requirements using:
|
||||
```
|
||||
../../../python_embeded/python.exe -s -m pip install -r requirements.txt
|
||||
```
|
||||
|
||||
If you are using a venv, make sure you have it activated before installation and use:
|
||||
```
|
||||
pip3 install -r requirements.txt
|
||||
```
|
||||
|
||||
#### Chinese community
|
||||
访问 [www.mixcomfy.com](https://www.mixcomfy.com),获得更多内测功能,关注微信公众号:Mixlab无界社区
|
||||
|
||||
|
||||
#### Thanks:
|
||||
[ComfyUI-CLIPSeg](https://github.com/biegert/ComfyUI-CLIPSeg/tree/main)
|
||||
|
||||
#### discussions:
|
||||
[discussions](https://github.com/shadowcz007/comfyui-mixlab-nodes/discussions)
|
||||
|
||||
### TODO:
|
||||
- 音频播放节点:带可视化、支持多音轨、可配置音轨音量
|
||||
- vector https://github.com/GeorgLegato/stable-diffusion-webui-vectorstudio
|
||||
|
||||
|
||||
<picture>
|
||||
<source
|
||||
media="(prefers-color-scheme: dark)"
|
||||
srcset="
|
||||
https://api.star-history.com/svg?repos=shadowcz007/comfyui-mixlab-nodes&type=Date&theme=dark
|
||||
"
|
||||
/>
|
||||
<source
|
||||
media="(prefers-color-scheme: light)"
|
||||
srcset="
|
||||
https://api.star-history.com/svg?repos=shadowcz007/comfyui-mixlab-nodes&type=Date
|
||||
"
|
||||
/>
|
||||
<img
|
||||
alt="Star History Chart"
|
||||
src="https://api.star-history.com/svg?repos=shadowcz007/comfyui-mixlab-nodes&type=Date"
|
||||
/>
|
||||
</picture>
|
||||
|
||||
|
||||
@@ -4,6 +4,9 @@ import subprocess
|
||||
import importlib.util
|
||||
import sys,json
|
||||
import urllib
|
||||
import hashlib
|
||||
import datetime
|
||||
|
||||
|
||||
python = sys.executable
|
||||
|
||||
@@ -61,9 +64,28 @@ except ImportError:
|
||||
sys.exit()
|
||||
|
||||
|
||||
|
||||
def install_openai():
|
||||
# Helper function to install the OpenAI module if not already installed
|
||||
try:
|
||||
importlib.import_module('openai')
|
||||
except ImportError:
|
||||
import pip
|
||||
pip.main(['install', 'openai'])
|
||||
|
||||
install_openai()
|
||||
|
||||
|
||||
current_path = os.path.abspath(os.path.dirname(__file__))
|
||||
|
||||
|
||||
|
||||
def calculate_md5(string):
|
||||
encoded_string = string.encode()
|
||||
md5_hash = hashlib.md5(encoded_string).hexdigest()
|
||||
return md5_hash
|
||||
|
||||
|
||||
def create_key(key_p,crt_p):
|
||||
import OpenSSL
|
||||
# 生成自签名证书
|
||||
@@ -95,25 +117,188 @@ def create_key(key_p,crt_p):
|
||||
return
|
||||
|
||||
|
||||
|
||||
def create_for_https():
|
||||
# print("#####path::", current_path)
|
||||
|
||||
https_key_path=os.path.join(current_path, "https")
|
||||
crt=os.path.join(https_key_path, "certificate.crt")
|
||||
key=os.path.join(https_key_path, "private.key")
|
||||
# print("##https_key_path", crt,key)
|
||||
print('\033[91mhttps_key: ', crt,key)
|
||||
if not os.path.exists(https_key_path):
|
||||
# 使用mkdir()方法创建新目录
|
||||
os.mkdir(https_key_path)
|
||||
if not os.path.exists(crt):
|
||||
create_key(key,crt)
|
||||
|
||||
print('https_key OK: ', crt,key)
|
||||
return (crt,key)
|
||||
|
||||
|
||||
# workflow
|
||||
def read_workflow_json_files(folder_path):
|
||||
json_files = []
|
||||
for filename in os.listdir(folder_path):
|
||||
if filename.endswith('.json'):
|
||||
json_files.append(filename)
|
||||
|
||||
data = []
|
||||
for file in json_files:
|
||||
file_path = os.path.join(folder_path, file)
|
||||
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': file,
|
||||
'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
|
||||
|
||||
def get_workflows():
|
||||
# print("#####path::", current_path)
|
||||
workflow_path=os.path.join(current_path, "workflow")
|
||||
print('workflow_path: ',workflow_path)
|
||||
if not os.path.exists(workflow_path):
|
||||
# 使用mkdir()方法创建新目录
|
||||
os.mkdir(workflow_path)
|
||||
workflows=read_workflow_json_files(workflow_path)
|
||||
return workflows
|
||||
|
||||
def get_my_workflow_for_app(filename="my_workflow_app.json"):
|
||||
app_path=os.path.join(current_path, "app")
|
||||
if not os.path.exists(app_path):
|
||||
os.mkdir(app_path)
|
||||
|
||||
apps=[]
|
||||
if filename==None:
|
||||
data=read_workflow_json_files(app_path)
|
||||
i=0
|
||||
for item in data:
|
||||
try:
|
||||
x=item["data"]
|
||||
if i==0:
|
||||
apps.append({
|
||||
"filename":item["filename"],
|
||||
"data":x,
|
||||
"date":item["date"]
|
||||
})
|
||||
else:
|
||||
apps.append({
|
||||
"filename":item["filename"],
|
||||
"data":{
|
||||
"app":{
|
||||
"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),
|
||||
"name":x['app']['name'],
|
||||
"version":x['app']['version'],
|
||||
}
|
||||
},
|
||||
"date":item["date"]
|
||||
})
|
||||
i+=1
|
||||
except Exception as e:
|
||||
print("发生异常:", str(e))
|
||||
else:
|
||||
app_workflow_path=os.path.join(app_path, filename)
|
||||
# print('app_workflow_path: ',app_workflow_path)
|
||||
try:
|
||||
with open(app_workflow_path) as json_file:
|
||||
apps = [{
|
||||
'filename':filename,
|
||||
'data':json.load(json_file)
|
||||
}]
|
||||
except Exception as e:
|
||||
print("发生异常:", str(e))
|
||||
|
||||
if len(apps)==1:
|
||||
data=read_workflow_json_files(app_path)
|
||||
|
||||
for item in data:
|
||||
x=item["data"]
|
||||
print(apps[0]['filename'] ,item["filename"])
|
||||
if apps[0]['filename']!=item["filename"]:
|
||||
apps.append({
|
||||
"filename":item["filename"],
|
||||
"data":{
|
||||
"app":{
|
||||
"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),
|
||||
"name":x['app']['name'],
|
||||
"version":x['app']['version'],
|
||||
}
|
||||
},
|
||||
"date":item["date"]
|
||||
})
|
||||
|
||||
return apps
|
||||
|
||||
def save_workflow_json(data):
|
||||
workflow_path=os.path.join(current_path, "workflow/my_workflow.json")
|
||||
with open(workflow_path, 'w') as file:
|
||||
json.dump(data, file)
|
||||
return workflow_path
|
||||
|
||||
def save_workflow_for_app(data,filename="my_workflow_app.json"):
|
||||
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)
|
||||
|
||||
try:
|
||||
output_str = json.dumps(data['output'])
|
||||
data['app']['id']=calculate_md5(output_str)
|
||||
# id=data['app']['id']
|
||||
except Exception as e:
|
||||
print("发生异常:", str(e))
|
||||
|
||||
with open(app_workflow_path, 'w') as file:
|
||||
json.dump(data, file)
|
||||
return filename
|
||||
|
||||
def get_nodes_map():
|
||||
# print("#####path::", current_path)
|
||||
data_path=os.path.join(current_path, "data")
|
||||
print('data_path: ',data_path)
|
||||
# if not os.path.exists(data_path):
|
||||
# # 使用mkdir()方法创建新目录
|
||||
# os.mkdir(data_path)
|
||||
json_data={}
|
||||
nodes_map=os.path.join(current_path, "data/extension-node-map.json")
|
||||
if os.path.exists(nodes_map):
|
||||
with open(nodes_map) as json_file:
|
||||
json_data = json.load(json_file)
|
||||
|
||||
return json_data
|
||||
|
||||
|
||||
# 保存原始的 get 方法
|
||||
_original_request = aiohttp.ClientSession._request
|
||||
|
||||
# 定义新的 get 方法
|
||||
async def new_request(self, method, url, *args, **kwargs):
|
||||
# 检查环境变量以确定是否使用代理
|
||||
proxy = os.environ.get('HTTP_PROXY') or os.environ.get('HTTPS_PROXY') or os.environ.get('http_proxy') or os.environ.get('https_proxy')
|
||||
# print('Proxy Config:',proxy)
|
||||
if proxy and 'proxy' not in kwargs:
|
||||
kwargs['proxy'] = proxy
|
||||
print('Use Proxy:',proxy)
|
||||
# 调用原始的 _request 方法
|
||||
return await _original_request(self, method, url, *args, **kwargs)
|
||||
|
||||
# 应用 Monkey Patch
|
||||
aiohttp.ClientSession._request = new_request
|
||||
|
||||
# https
|
||||
async def new_start(self, address, port, verbose=True, call_on_start=None):
|
||||
|
||||
|
||||
runner = web.AppRunner(self.app, access_log=None)
|
||||
await runner.setup()
|
||||
site = web.TCPSite(runner, address, port)
|
||||
@@ -152,13 +337,78 @@ routes = web.RouteTableDef()
|
||||
async def mixlab_hander(request):
|
||||
config=os.path.join(current_path, "nodes/config.json")
|
||||
data={}
|
||||
# print(config)
|
||||
if os.path.exists(config):
|
||||
with open(config, 'r') as f:
|
||||
data = json.load(f)
|
||||
# print(data)
|
||||
try:
|
||||
if os.path.exists(config):
|
||||
with open(config, 'r') as f:
|
||||
data = json.load(f)
|
||||
# print(data)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
return web.json_response(data)
|
||||
|
||||
|
||||
@routes.get('/mixlab/app')
|
||||
async def mixlab_app_handler(request):
|
||||
html_file = os.path.join(current_path, "web/index.html")
|
||||
if os.path.exists(html_file):
|
||||
with open(html_file, 'r', encoding='utf-8', errors='ignore') as f:
|
||||
html_data = f.read()
|
||||
return web.Response(text=html_data, content_type='text/html')
|
||||
else:
|
||||
return web.Response(text="HTML file not found", status=404)
|
||||
|
||||
|
||||
@routes.post('/mixlab/workflow')
|
||||
async def mixlab_workflow_hander(request):
|
||||
data = await request.json()
|
||||
result={}
|
||||
try:
|
||||
if 'task' in data:
|
||||
if data['task']=='save':
|
||||
file_path=save_workflow_json(data['data'])
|
||||
result={
|
||||
'status':'success',
|
||||
'file_path':file_path
|
||||
}
|
||||
elif data['task']=='save_app':
|
||||
file_path=save_workflow_for_app(data['data'],data['filename'])
|
||||
result={
|
||||
'status':'success',
|
||||
'file_path':file_path
|
||||
}
|
||||
elif data['task']=='my_app':
|
||||
filename=None
|
||||
if 'filename' in data:
|
||||
filename=data['filename']
|
||||
result={
|
||||
'data':get_my_workflow_for_app(filename),
|
||||
'status':'success',
|
||||
}
|
||||
elif data['task']=='list':
|
||||
result={
|
||||
'data':get_workflows(),
|
||||
'status':'success',
|
||||
}
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
return web.json_response(result)
|
||||
|
||||
@routes.post('/mixlab/nodes_map')
|
||||
async def nodes_map_hander(request):
|
||||
data = await request.json()
|
||||
result={}
|
||||
try:
|
||||
result={
|
||||
'data':get_nodes_map(),
|
||||
'status':'success',
|
||||
}
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
return web.json_response(result)
|
||||
|
||||
# 把插件自定义的路由添加到comfyui server里
|
||||
def new_add_routes(self):
|
||||
import nodes
|
||||
self.app.add_routes(routes)
|
||||
@@ -188,18 +438,32 @@ PromptServer.add_routes=new_add_routes
|
||||
|
||||
# 导入节点
|
||||
from .nodes.PromptNode import RandomPrompt
|
||||
from .nodes.ImageNode import TransparentImage,LoadImagesFromPath,AreaToMask,SmoothMask,FeatheredMask,SplitLongMask,ImageCropByAlpha,EnhanceImage,FaceToMask
|
||||
from .nodes.ImageNode import NoiseImage,TransparentImage,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
|
||||
|
||||
# 要导出的所有节点及其名称的字典
|
||||
# 注意:名称应全局唯一
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"AppInfo":AppInfo,
|
||||
"RandomPrompt":RandomPrompt,
|
||||
"NoiseImage":NoiseImage,
|
||||
"TransparentImage":TransparentImage,
|
||||
"ResizeImageMixlab":ResizeImage,
|
||||
"LoadImagesFromPath":LoadImagesFromPath,
|
||||
"LoadImagesFromURL":LoadImagesFromURL,
|
||||
"TextImage":TextImage,
|
||||
"EnhanceImage":EnhanceImage,
|
||||
"SvgImage":SvgImage,
|
||||
"3DImage":Image3D,
|
||||
"ShowLayer":ShowLayer,
|
||||
"NewLayer":NewLayer,
|
||||
"MergeLayers":MergeLayers,
|
||||
"SplitLongMask":SplitLongMask,
|
||||
"FeatheredMask":FeatheredMask,
|
||||
"SmoothMask":SmoothMask,
|
||||
@@ -210,23 +474,53 @@ NODE_CLASS_MAPPINGS = {
|
||||
"VAEDecodeConsistencyDecoder":VAEDecode,
|
||||
"ScreenShare":ScreenShareNode,
|
||||
"FloatingVideo":FloatingVideo,
|
||||
"CLIPSeg":CLIPSeg,
|
||||
"CombineMasks":CombineMasks
|
||||
"CLIPSeg_":CLIPSeg,
|
||||
"CombineMasks_":CombineMasks,
|
||||
"ChatGPTOpenAI":ChatGPTNode,
|
||||
"ShowTextForGPT":ShowTextForGPT,
|
||||
"CharacterInText":CharacterInText,
|
||||
"SpeechRecognition":SpeechRecognition,
|
||||
"SpeechSynthesis":SpeechSynthesis,
|
||||
"Color":ColorInput,
|
||||
"FloatSlider":FloatSlider,
|
||||
"IntNumber":IntNumber,
|
||||
"TextInput_":TextInput,
|
||||
"Font":FontInput,
|
||||
"TextToNumber":TextToNumber,
|
||||
"DynamicDelayProcessor":DynamicDelayProcessor,
|
||||
"MultiplicationNode":MultiplicationNode,
|
||||
"GetImageSize_":GetImageSize_,
|
||||
"SwitchByIndex":SwitchByIndex,
|
||||
"LimitNumber":LimitNumber,
|
||||
"LaMaInpainting":LaMaInpainting
|
||||
# "GamePal":GamePal
|
||||
}
|
||||
|
||||
# 一个包含节点友好/可读的标题的字典
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"RandomPrompt": "Random Prompt #Example Node",
|
||||
"AppInfo":"AppInfo ♾️Mixlab",
|
||||
"ResizeImageMixlab":"ResizeImage ♾️Mixlab",
|
||||
"RandomPrompt": "Random Prompt ♾️Mixlab",
|
||||
"SplitLongMask":"Splitting a long image into sections",
|
||||
"VAELoaderConsistencyDecoder":"Consistency Decoder Loader",
|
||||
"VAEDecodeConsistencyDecoder":"Consistency Decoder Decode",
|
||||
"ScreenShare":"ScreenShare #Mixlab",
|
||||
"FloatingVideo":"FloatingVideo #Mixlab"
|
||||
"ScreenShare":"ScreenShare ♾️Mixlab",
|
||||
"FloatingVideo":"FloatingVideo ♾️Mixlab",
|
||||
"ChatGPTOpenAI":"ChatGPT ♾️Mixlab",
|
||||
"ShowTextForGPT":"ShowTextForGPT ♾️Mixlab",
|
||||
"MergeLayers":"MergeLayers ♾️Mixlab",
|
||||
"SpeechSynthesis":"SpeechSynthesis ♾️Mixlab",
|
||||
"SpeechRecognition":"SpeechRecognition ♾️Mixlab",
|
||||
"3DImage":"3DImage ♾️Mixlab",
|
||||
"DynamicDelayProcessor":"DynamicDelayByText ♾️Mixlab",
|
||||
"LaMaInpainting":"LaMaInpainting ♾️Mixlab"
|
||||
|
||||
# "GamePal":"GamePal ♾️Mixlab"
|
||||
}
|
||||
|
||||
# web ui的节点功能
|
||||
WEB_DIRECTORY = "./web"
|
||||
|
||||
print('--------------')
|
||||
print('\033[91mMixlab Nodes: \033[93mLoaded\033[0m')
|
||||
print('\033[91m ### Mixlab Nodes: \033[93mLoaded\033[0m')
|
||||
print('--------------')
|
||||
|
After Width: | Height: | Size: 101 KiB |
|
After Width: | Height: | Size: 450 KiB |
|
After Width: | Height: | Size: 522 KiB |
|
After Width: | Height: | Size: 3.3 MiB |
|
Before Width: | Height: | Size: 257 KiB |
|
After Width: | Height: | Size: 240 KiB |
|
After Width: | Height: | Size: 254 KiB |
|
After Width: | Height: | Size: 73 KiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 255 KiB |
|
After Width: | Height: | Size: 7.1 MiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 51 KiB |
|
After Width: | Height: | Size: 9.9 MiB |
@@ -0,0 +1,100 @@
|
||||
|
||||
|
||||
|
||||
|
||||
class SpeechRecognition:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"upload":("AUDIOINPUTMIX",), },
|
||||
"optional":{
|
||||
"start_by":("INT", {
|
||||
"default": 0,
|
||||
"min": 0, #Minimum value
|
||||
"max": 2048, #Maximum value
|
||||
"step": 1, #Slider's step
|
||||
"display": "number" # Cosmetic only: display as "number" or "slider"
|
||||
}),
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("prompt",)
|
||||
|
||||
FUNCTION = "run"
|
||||
|
||||
CATEGORY = "♾️Mixlab/audio"
|
||||
|
||||
INPUT_IS_LIST = False
|
||||
OUTPUT_IS_LIST = (False,)
|
||||
|
||||
def run(self,upload,start_by):
|
||||
return {"ui": {"start_by": [start_by]}, "result": (upload,)}
|
||||
|
||||
|
||||
class SpeechSynthesis:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"text": ("STRING", {"forceInput": True}),
|
||||
}
|
||||
}
|
||||
|
||||
INPUT_IS_LIST = True
|
||||
RETURN_TYPES = ("STRING",)
|
||||
FUNCTION = "run"
|
||||
OUTPUT_NODE = True
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
|
||||
CATEGORY = "♾️Mixlab/audio"
|
||||
|
||||
def run(self, text):
|
||||
# print(session_history)
|
||||
return {"ui": {"text": text}, "result": (text,)}
|
||||
|
||||
|
||||
#
|
||||
class GamePal:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"input_text": ("STRING",{"multiline": True,"default": ""}),
|
||||
},
|
||||
"optional": {
|
||||
|
||||
"input_num": ("INT",{
|
||||
"default":100,
|
||||
"min": -1, #Minimum value
|
||||
"max": 0xffffffffffffffff, #Maximum value
|
||||
"step": 1, #Slider's step
|
||||
"display": "slider" # Cosmetic only: display as "number" or "slider"
|
||||
}),
|
||||
"python_code": ("STRING",{"multiline": True,"default": "result= 1 if 'Mixlab' in input_text else 0"}),
|
||||
}
|
||||
}
|
||||
|
||||
INPUT_IS_LIST = False
|
||||
RETURN_TYPES = ("INT",)
|
||||
FUNCTION = "run"
|
||||
OUTPUT_NODE = True
|
||||
OUTPUT_IS_LIST = (False,)
|
||||
|
||||
CATEGORY = "♾️Mixlab/audio"
|
||||
|
||||
def run(self, input_text,input_num,python_code):
|
||||
exec(python_code)
|
||||
res=None
|
||||
try:
|
||||
# 可能会引发异常的代码
|
||||
res=result
|
||||
except:
|
||||
# 处理异常的代码
|
||||
print('')
|
||||
|
||||
print(res)
|
||||
|
||||
# print(session_history)
|
||||
return {"ui": {"text": [input_text],"num":[input_num]}, "result": (res,)}
|
||||
@@ -0,0 +1,217 @@
|
||||
import openai
|
||||
import time
|
||||
import urllib.error
|
||||
import re,json
|
||||
|
||||
# 判断是否是azure服务
|
||||
def is_azure_url(url):
|
||||
pattern = r'.*\.azure\.com$'
|
||||
if re.match(pattern, url):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def azure_client(key,url):
|
||||
client = openai.AzureOpenAI(
|
||||
api_key=key,
|
||||
# https://learn.microsoft.com/en-us/azure/ai-services/openai/reference#rest-api-versioning
|
||||
api_version="2023-07-01-preview",
|
||||
# https://learn.microsoft.com/en-us/azure/cognitive-services/openai/how-to/create-resource?pivots=web-portal#create-a-resource
|
||||
azure_endpoint=url
|
||||
)
|
||||
return client
|
||||
|
||||
def openai_client(key,url):
|
||||
client = openai.OpenAI(
|
||||
api_key=key,
|
||||
base_url=url
|
||||
)
|
||||
return client
|
||||
|
||||
|
||||
|
||||
def chat(client, model_name,messages ):
|
||||
|
||||
try_count = 0
|
||||
while True:
|
||||
try_count += 1
|
||||
try:
|
||||
response = client.chat.completions.create(
|
||||
model=model_name,
|
||||
messages=messages
|
||||
)
|
||||
break
|
||||
except openai.AuthenticationError as ex:
|
||||
raise ex
|
||||
except (urllib.error.HTTPError, openai.OpenAIError) as ex:
|
||||
if try_count >= 3:
|
||||
raise ex
|
||||
time.sleep(3)
|
||||
continue
|
||||
|
||||
finish_reason = response.choices[0].finish_reason
|
||||
if finish_reason != "stop":
|
||||
raise RuntimeError("API finished with unexpected reason: " + finish_reason)
|
||||
|
||||
content=""
|
||||
try:
|
||||
content=response.choices[0].message.content
|
||||
except:
|
||||
content=response.choices[0].delta['content']
|
||||
|
||||
return content
|
||||
|
||||
|
||||
class ChatGPTNode:
|
||||
def __init__(self):
|
||||
# self.__client = OpenAI()
|
||||
self.session_history = [] # 用于存储会话历史的列表
|
||||
# self.seed=0
|
||||
self.system_content="You are ChatGPT, a large language model trained by OpenAI. Answer as concisely as possible."
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"api_key":("KEY", {"default": "", "multiline": True}),
|
||||
"api_url":("URL", {"default": "", "multiline": True}),
|
||||
"prompt": ("STRING", {"multiline": True,"dynamicPrompts": False}),
|
||||
"system_content": ("STRING",
|
||||
{
|
||||
"default": "You are ChatGPT, a large language model trained by OpenAI. Answer as concisely as possible.",
|
||||
"multiline": True,"dynamicPrompts": False
|
||||
}),
|
||||
"model": (["gpt-3.5-turbo","gpt-35-turbo","gpt-3.5-turbo-16k", "gpt-3.5-turbo-16k-0613", "gpt-4-0613","gpt-4-1106-preview"],
|
||||
{"default": "gpt-3.5-turbo"}),
|
||||
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff, "step": 1}),
|
||||
"context_size":("INT", {"default": 1, "min": 0, "max":30, "step": 1}),
|
||||
},
|
||||
"hidden": {
|
||||
"unique_id": "UNIQUE_ID",
|
||||
"extra_pnginfo": "EXTRA_PNGINFO",
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING","STRING","STRING",)
|
||||
RETURN_NAMES = ("text","messages","session_history",)
|
||||
FUNCTION = "generate_contextual_text"
|
||||
CATEGORY = "♾️Mixlab/GPT"
|
||||
INPUT_IS_LIST = False
|
||||
OUTPUT_IS_LIST = (False,False,False,)
|
||||
|
||||
|
||||
def generate_contextual_text(self,
|
||||
api_key,
|
||||
api_url,
|
||||
prompt,
|
||||
system_content,
|
||||
model,
|
||||
seed,context_size,unique_id = None, extra_pnginfo=None):
|
||||
# print(api_key!='',api_url,prompt,system_content,model,seed)
|
||||
# 可以选择保留会话历史以维持上下文记忆
|
||||
# 或者在此处清除会话历史 self.session_history.clear()
|
||||
# if seed!=self.seed:
|
||||
# self.seed=seed
|
||||
# self.session_history=[]
|
||||
|
||||
# 把系统信息和初始信息添加到会话历史中
|
||||
if system_content:
|
||||
self.system_content=system_content
|
||||
# self.session_history=[]
|
||||
# self.session_history.append({"role": "system", "content": system_content})
|
||||
|
||||
#
|
||||
if is_azure_url(api_url):
|
||||
client=azure_client(api_key,api_url)
|
||||
else:
|
||||
client=openai_client(api_key,api_url)
|
||||
print('openai url')
|
||||
|
||||
# 把用户的提示添加到会话历史中
|
||||
# 调用API时传递整个会话历史
|
||||
|
||||
def crop_list_tail(lst, size):
|
||||
if size >= len(lst):
|
||||
return lst
|
||||
elif size==0:
|
||||
return []
|
||||
else:
|
||||
return lst[-size:]
|
||||
|
||||
session_history=crop_list_tail(self.session_history,context_size)
|
||||
|
||||
messages=[{"role": "system", "content": self.system_content}]+session_history+[{"role": "user", "content": prompt}]
|
||||
response_content = chat(client,model,messages)
|
||||
|
||||
self.session_history=self.session_history+[{"role": "user", "content": prompt}]+[{'role':'assistant',"content":response_content}]
|
||||
|
||||
|
||||
# if unique_id and extra_pnginfo and "workflow" in extra_pnginfo[0]:
|
||||
# workflow = extra_pnginfo[0]["workflow"]
|
||||
# node = next((x for x in workflow["nodes"] if str(x["id"]) == unique_id[0]), None)
|
||||
# if node:
|
||||
# node["widgets_values"] = ["",
|
||||
# api_url,
|
||||
# prompt,
|
||||
# system_content,
|
||||
# model,
|
||||
# seed,
|
||||
# context_size]
|
||||
|
||||
return (response_content,json.dumps(messages, indent=4),json.dumps(self.session_history, indent=4),)
|
||||
|
||||
|
||||
|
||||
class ShowTextForGPT:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"text": ("STRING", {"forceInput": True,"dynamicPrompts": False}),
|
||||
}
|
||||
}
|
||||
|
||||
INPUT_IS_LIST = True
|
||||
RETURN_TYPES = ("STRING",)
|
||||
FUNCTION = "run"
|
||||
OUTPUT_NODE = True
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
|
||||
CATEGORY = "♾️Mixlab/GPT"
|
||||
|
||||
def run(self, text):
|
||||
# print(session_history)
|
||||
return {"ui": {"text": text}, "result": (text,)}
|
||||
|
||||
|
||||
class CharacterInText:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"text": ("STRING", {"multiline": True,"dynamicPrompts": False}),
|
||||
"character": ("STRING", {"multiline": True,"dynamicPrompts": False}),
|
||||
"start_index": ("INT", {
|
||||
"default": 1,
|
||||
"min": 0, #Minimum value
|
||||
"max": 1024, #Maximum value
|
||||
"step": 1, #Slider's step
|
||||
"display": "number" # Cosmetic only: display as "number" or "slider"
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
INPUT_IS_LIST = False
|
||||
RETURN_TYPES = ("INT",)
|
||||
FUNCTION = "run"
|
||||
# OUTPUT_NODE = True
|
||||
OUTPUT_IS_LIST = (False,)
|
||||
|
||||
CATEGORY = "♾️Mixlab/GPT"
|
||||
|
||||
def run(self, text,character,start_index):
|
||||
# print(text,character,start_index)
|
||||
b=1 if character in text else 0
|
||||
|
||||
return (b+start_index,)
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
#### Thanks:
|
||||
# [ComfyUI-CLIPSeg](https://github.com/biegert/ComfyUI-CLIPSeg/tree/main)
|
||||
|
||||
from transformers import CLIPSegProcessor, CLIPSegForImageSegmentation
|
||||
|
||||
from PIL import Image
|
||||
@@ -32,6 +35,16 @@ if not os.path.exists(clipseg_model_dir):
|
||||
|
||||
"""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))
|
||||
|
||||
# Convert PIL to Tensor
|
||||
def pil2tensor(image):
|
||||
return torch.from_numpy(np.array(image).astype(np.float32) / 255.0).unsqueeze(0)
|
||||
|
||||
|
||||
def tensor_to_numpy(tensor: torch.Tensor) -> np.ndarray:
|
||||
"""Convert a tensor to a numpy array and scale its values to 0-255."""
|
||||
array = tensor.numpy().squeeze()
|
||||
@@ -88,7 +101,7 @@ class CLIPSeg:
|
||||
return {"required":
|
||||
{
|
||||
"image": ("IMAGE",),
|
||||
"text": ("STRING", {"multiline": False}),
|
||||
"text": ("STRING", {"multiline": False,"dynamicPrompts": False}),
|
||||
|
||||
},
|
||||
"optional":
|
||||
@@ -99,12 +112,12 @@ class CLIPSeg:
|
||||
}
|
||||
}
|
||||
|
||||
CATEGORY = "Mixlab/mask"
|
||||
CATEGORY = "♾️Mixlab/mask"
|
||||
RETURN_TYPES = ("MASK", "IMAGE", "IMAGE",)
|
||||
RETURN_NAMES = ("Mask","Heatmap Mask", "BW Mask")
|
||||
|
||||
# INPUT_IS_LIST = True
|
||||
# OUTPUT_IS_LIST = (True,)
|
||||
OUTPUT_IS_LIST = (False,False,False,)
|
||||
|
||||
FUNCTION = "segment_image"
|
||||
def segment_image(self, image: torch.Tensor, text: str, blur: float, threshold: float, dilation_factor: int) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
|
||||
@@ -177,12 +190,13 @@ class CLIPSeg:
|
||||
binary_mask_image = Image.fromarray(binary_mask_resized[..., 0])
|
||||
|
||||
# convert PIL image to numpy array
|
||||
tensor_bw = binary_mask_image.convert("RGB")
|
||||
tensor_bw = np.array(tensor_bw).astype(np.float32) / 255.0
|
||||
tensor_bw = torch.from_numpy(tensor_bw)[None,]
|
||||
tensor_bw = tensor_bw.squeeze(0)[..., 0]
|
||||
tensor_bw = binary_mask_image.convert("L")
|
||||
tensor_bw=pil2tensor(tensor_bw)
|
||||
# tensor_bw = np.array(tensor_bw).astype(np.float32) / 255.0
|
||||
# tensor_bw = torch.from_numpy(tensor_bw)[None,]
|
||||
# tensor_bw = tensor_bw.squeeze(0)[..., 0]
|
||||
|
||||
return tensor_bw, image_out_heatmap, image_out_binary
|
||||
return (tensor_bw, image_out_heatmap, image_out_binary,)
|
||||
|
||||
#OUTPUT_NODE = False
|
||||
|
||||
@@ -204,7 +218,7 @@ class CombineMasks:
|
||||
},
|
||||
}
|
||||
|
||||
CATEGORY = "Mixlab/mask"
|
||||
CATEGORY = "♾️Mixlab/mask"
|
||||
RETURN_TYPES = ("MASK", "IMAGE", "IMAGE",)
|
||||
RETURN_NAMES = ("Combined Mask","Heatmap Mask", "BW Mask")
|
||||
|
||||
@@ -232,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")
|
||||
|
||||
@@ -252,7 +266,7 @@ class CombineMasks:
|
||||
|
||||
# A dictionary that contains all nodes you want to export with their names
|
||||
# NOTE: names should be globally unique
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"CLIPSeg": CLIPSeg,
|
||||
"CombineSegMasks": CombineMasks,
|
||||
}
|
||||
# NODE_CLASS_MAPPINGS = {
|
||||
# "CLIPSeg": CLIPSeg,
|
||||
# "CombineSegMasks": CombineMasks,
|
||||
# }
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
import os
|
||||
import folder_paths
|
||||
from simple_lama_inpainting import SimpleLama
|
||||
from PIL import Image
|
||||
|
||||
import numpy as np
|
||||
|
||||
import torch
|
||||
|
||||
|
||||
os.environ['LAMA_MODEL'] = os.path.join(folder_paths.models_dir, "lama/big-lama.pt")
|
||||
if os.environ.get("LAMA_MODEL"):
|
||||
model_path=os.environ.get("LAMA_MODEL")
|
||||
if not os.path.exists(model_path):
|
||||
os.environ['LAMA_MODEL']=''
|
||||
raise FileNotFoundError(
|
||||
f"lama torchscript model not found: {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,)
|
||||
@@ -79,7 +79,7 @@ class RandomPrompt:
|
||||
|
||||
FUNCTION = "run"
|
||||
|
||||
CATEGORY = "Mixlab/prompt"
|
||||
CATEGORY = "♾️Mixlab/prompt"
|
||||
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
OUTPUT_NODE = True
|
||||
@@ -158,7 +158,7 @@ class RunWorkflow:
|
||||
|
||||
FUNCTION = "run"
|
||||
|
||||
CATEGORY = "Mixlab/workflow"
|
||||
CATEGORY = "♾️Mixlab/workflow"
|
||||
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
OUTPUT_NODE = True
|
||||
|
||||
@@ -24,9 +24,42 @@ def base64_save(base64_data):
|
||||
return (image,mask)
|
||||
|
||||
|
||||
# # 把白色部分处理成黑色
|
||||
# def convert_to_bw(image):
|
||||
# # 读取图片
|
||||
# # image = Image.open(image_path)
|
||||
|
||||
# # 获取图片的宽度和高度
|
||||
# width, height = image.size
|
||||
|
||||
# # 遍历图片的每个像素点
|
||||
# for x in range(width):
|
||||
# for y in range(height):
|
||||
# # 获取当前像素点的RGB值
|
||||
# r, g, b = image.getpixel((x, y))
|
||||
|
||||
# # 判断当前像素点是否为白色
|
||||
# if r == 255 and g == 255 and b == 255:
|
||||
# # 将白色部分处理成黑色
|
||||
# image.putpixel((x, y), (0, 0, 0))
|
||||
# else:
|
||||
# # 将非白色部分处理成白色
|
||||
# image.putpixel((x, y), (255, 255, 255))
|
||||
|
||||
# # 转换为黑白图
|
||||
# mask = image.convert("L")
|
||||
|
||||
# # # 保存处理后的图片
|
||||
# # image.save("black_white_image.jpg")
|
||||
|
||||
# # print("图片处理完成!")
|
||||
# return mask
|
||||
|
||||
|
||||
def load_image(i,white_bg=False):
|
||||
# i = Image.open(fp)
|
||||
image = i.convert("RGB")
|
||||
|
||||
image = np.array(image).astype(np.float32) / 255.0
|
||||
image = torch.from_numpy(image)[None,]
|
||||
if 'A' in i.getbands():
|
||||
@@ -46,27 +79,31 @@ class ScreenShareNode:
|
||||
def INPUT_TYPES(s):
|
||||
return { "required":{
|
||||
"image_base64": ("CHEESE",),
|
||||
"refresh_rate": ("INT", {"default": 500, "min": 0,"step": 50, "max": 0xffffffffffffffff}),
|
||||
},
|
||||
"optional":{
|
||||
"prompt": ("PROMPT",),
|
||||
"slide": ("SLIDE",),
|
||||
"seed": ("SEED",),
|
||||
|
||||
# "seed": ("INT", {"default": 1, "min": 0, "max": 0xffffffffffffffff}),
|
||||
} }
|
||||
|
||||
RETURN_TYPES = ('IMAGE','MASK','STRING')
|
||||
|
||||
RETURN_TYPES = ('IMAGE','STRING','FLOAT',"INT")
|
||||
RETURN_NAMES = ("IMAGE","PROMPT","FLOAT","INT")
|
||||
FUNCTION = "run"
|
||||
|
||||
CATEGORY = "Mixlab/image"
|
||||
CATEGORY = "♾️Mixlab/image"
|
||||
|
||||
# INPUT_IS_LIST = True
|
||||
OUTPUT_IS_LIST = (False,False,False)
|
||||
OUTPUT_IS_LIST = (False,False,False,False)
|
||||
|
||||
# 运行的函数
|
||||
def run(self,image_base64,prompt):
|
||||
def run(self,image_base64,refresh_rate ,prompt,slide,seed):
|
||||
im,mask=base64_save(image_base64)
|
||||
# print('##########prompt',prompt)
|
||||
return (im,mask,prompt)
|
||||
|
||||
return {"ui":{"refresh_rate": [refresh_rate]},"result": (im,prompt,slide,seed,)}
|
||||
|
||||
|
||||
class FloatingVideo:
|
||||
@classmethod
|
||||
@@ -81,7 +118,7 @@ class FloatingVideo:
|
||||
OUTPUT_NODE = True
|
||||
FUNCTION = "run"
|
||||
|
||||
CATEGORY = "Mixlab/image"
|
||||
CATEGORY = "♾️Mixlab/image"
|
||||
|
||||
# INPUT_IS_LIST = True
|
||||
# OUTPUT_IS_LIST = (False,False,)
|
||||
@@ -104,3 +141,15 @@ class FloatingVideo:
|
||||
|
||||
return { "ui": { "images_": results } }
|
||||
|
||||
|
||||
|
||||
# class SildeNode:
|
||||
# CATEGORY = "quicknodes"
|
||||
# @classmethod
|
||||
# def INPUT_TYPES(s):
|
||||
# return { "required":{} }
|
||||
# RETURN_TYPES = ()
|
||||
# RETURN_NAMES = ()
|
||||
# FUNCTION = "func"
|
||||
# def func(self):
|
||||
# return ()
|
||||
@@ -0,0 +1,532 @@
|
||||
import os
|
||||
import re,random
|
||||
from PIL import Image
|
||||
import numpy as np
|
||||
# FONT_PATH= os.path.abspath(os.path.join(os.path.dirname(__file__),'../assets/王汉宗颜楷体繁.ttf'))
|
||||
import folder_paths
|
||||
import matplotlib.font_manager as fm
|
||||
|
||||
# import json
|
||||
# import hashlib
|
||||
|
||||
|
||||
# def get_json_hash(json_content):
|
||||
# json_string = json.dumps(json_content, sort_keys=True)
|
||||
# hash_object = hashlib.sha256(json_string.encode())
|
||||
# hash_value = hash_object.hexdigest()
|
||||
# return hash_value
|
||||
|
||||
|
||||
|
||||
def tensor2pil(image):
|
||||
return Image.fromarray(np.clip(255. * image.cpu().numpy().squeeze(), 0, 255).astype(np.uint8))
|
||||
|
||||
|
||||
def create_temp_file(image):
|
||||
output_dir = folder_paths.get_temp_directory()
|
||||
|
||||
(
|
||||
full_output_folder,
|
||||
filename,
|
||||
counter,
|
||||
subfolder,
|
||||
_,
|
||||
) = folder_paths.get_save_image_path('tmp', output_dir)
|
||||
|
||||
|
||||
image=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)
|
||||
|
||||
return [{
|
||||
"filename": image_file,
|
||||
"subfolder": subfolder,
|
||||
"type": "temp"
|
||||
}]
|
||||
|
||||
def get_font_files(directory):
|
||||
font_files = {}
|
||||
|
||||
# 从指定目录加载字体
|
||||
for file in os.listdir(directory):
|
||||
if file.endswith('.ttf') or file.endswith('.otf'):
|
||||
font_name = os.path.splitext(file)[0]
|
||||
font_path = os.path.join(directory, file)
|
||||
font_files[font_name] = os.path.abspath(font_path)
|
||||
|
||||
# 尝试获取系统字体
|
||||
try:
|
||||
font_paths = fm.findSystemFonts()
|
||||
for path in font_paths:
|
||||
try:
|
||||
font_prop = fm.FontProperties(fname=path)
|
||||
font_name = font_prop.get_name()
|
||||
font_files[font_name] = path
|
||||
except Exception as e:
|
||||
print(f"Error processing font {path}: {e}")
|
||||
except Exception as e:
|
||||
print(f"Error finding system fonts: {e}")
|
||||
|
||||
return font_files
|
||||
|
||||
r_directory = os.path.join(os.path.dirname(__file__), '../assets/')
|
||||
|
||||
font_files = get_font_files(r_directory)
|
||||
# print(font_files)
|
||||
|
||||
|
||||
class ColorInput:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
|
||||
"color":("TCOLOR",),
|
||||
},
|
||||
}
|
||||
|
||||
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,False,False,False,)
|
||||
|
||||
def run(self,color):
|
||||
h=color['hex']
|
||||
r=color['r']
|
||||
g=color['g']
|
||||
b=color['b']
|
||||
a=color['a']
|
||||
return (h,r,g,b,a,)
|
||||
|
||||
|
||||
|
||||
class FontInput:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
|
||||
"font": (list(font_files.keys()),),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
# RETURN_NAMES = ("WIDTH","HEIGHT","X","Y",)
|
||||
|
||||
FUNCTION = "run"
|
||||
|
||||
CATEGORY = "♾️Mixlab/utils"
|
||||
|
||||
INPUT_IS_LIST = False
|
||||
OUTPUT_IS_LIST = (False,False,)
|
||||
|
||||
def run(self,font):
|
||||
|
||||
return (font_files[font],)
|
||||
|
||||
class TextToNumber:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"text": ("STRING",{"multiline": False,"default": "1"}),
|
||||
"random_number": (["enable", "disable"],),
|
||||
"number":("INT", {
|
||||
"default": 0,
|
||||
"min": 0, #Minimum value
|
||||
"max": 10000000000, #Maximum value
|
||||
"step": 1, #Slider's step
|
||||
"display": "number" # Cosmetic only: display as "number" or "slider"
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("INT",)
|
||||
# RETURN_NAMES = ("WIDTH","HEIGHT","X","Y",)
|
||||
|
||||
FUNCTION = "run"
|
||||
|
||||
CATEGORY = "♾️Mixlab/utils"
|
||||
|
||||
INPUT_IS_LIST = False
|
||||
OUTPUT_IS_LIST = (False,)
|
||||
|
||||
def run(self,text,random_number,number):
|
||||
|
||||
numbers = re.findall(r'\d+', text)
|
||||
result=0
|
||||
for n in numbers:
|
||||
result = int(n)
|
||||
# print(result)
|
||||
|
||||
if random_number=='enable' and result>0:
|
||||
result= random.randint(1, 10000000000)
|
||||
return {"ui": {"text": [text],"num":[result]}, "result": (result,)}
|
||||
|
||||
|
||||
|
||||
class FloatSlider:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"number":("FLOAT", {
|
||||
"default": 0,
|
||||
"min": 0, #Minimum value
|
||||
"max": 1, #Maximum value
|
||||
"step": 0.001, #Slider's step
|
||||
"display": "slider" # Cosmetic only: display as "number" or "slider"
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("FLOAT",)
|
||||
|
||||
FUNCTION = "run"
|
||||
|
||||
CATEGORY = "♾️Mixlab/utils"
|
||||
|
||||
INPUT_IS_LIST = False
|
||||
OUTPUT_IS_LIST = (False,)
|
||||
|
||||
def run(self,number):
|
||||
|
||||
return (number,)
|
||||
|
||||
|
||||
class IntNumber:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"number":("INT", {
|
||||
"default": 0,
|
||||
"min": -1, #Minimum value
|
||||
"max": 0xffffffffffffffff,
|
||||
"step": 1, #Slider's step
|
||||
"display": "number" # Cosmetic only: display as "number" or "slider"
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("INT",)
|
||||
|
||||
FUNCTION = "run"
|
||||
|
||||
CATEGORY = "♾️Mixlab/utils"
|
||||
|
||||
INPUT_IS_LIST = False
|
||||
OUTPUT_IS_LIST = (False,)
|
||||
|
||||
def run(self,number):
|
||||
|
||||
return (number,)
|
||||
|
||||
class MultiplicationNode:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"numberA":(any_type,),
|
||||
"numberB":("FLOAT", {
|
||||
"default": 0,
|
||||
"min": -1, #Minimum value
|
||||
"max": 0xffffffffffffffff,
|
||||
"step": 0.1, #Slider's step
|
||||
"display": "number" # Cosmetic only: display as "number" or "slider"
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("FLOAT","INT",)
|
||||
|
||||
FUNCTION = "run"
|
||||
|
||||
CATEGORY = "♾️Mixlab/utils"
|
||||
|
||||
INPUT_IS_LIST = False
|
||||
OUTPUT_IS_LIST = (False,False,)
|
||||
|
||||
def run(self,numberA,numberB):
|
||||
b=int(numberA*numberB)
|
||||
a=float(numberA*numberB)
|
||||
return (a,b,)
|
||||
|
||||
class TextInput:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"text": ("STRING",{"multiline": True,"default": ""}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
|
||||
FUNCTION = "run"
|
||||
|
||||
CATEGORY = "♾️Mixlab/utils"
|
||||
|
||||
INPUT_IS_LIST = False
|
||||
OUTPUT_IS_LIST = (False,)
|
||||
|
||||
def run(self,text):
|
||||
|
||||
return (text,)
|
||||
|
||||
# 接收一个值,然后根据字符串或数值长度计算延迟时间,用户可以自定义延迟"字/s",延迟之后将转化
|
||||
|
||||
import comfy.samplers
|
||||
import folder_paths
|
||||
|
||||
# import time
|
||||
|
||||
|
||||
class AnyType(str):
|
||||
"""A special class that is always equal in not equal comparisons. Credit to pythongosssss"""
|
||||
|
||||
def __ne__(self, __value: object) -> bool:
|
||||
return False
|
||||
|
||||
any_type = AnyType("*")
|
||||
import time
|
||||
|
||||
class DynamicDelayProcessor:
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
# print("print INPUT_TYPES",cls)
|
||||
return {
|
||||
"required":{
|
||||
"delay_seconds":("INT",{
|
||||
"default":1,
|
||||
"min": 0,
|
||||
"max": 1000000,
|
||||
}),
|
||||
},
|
||||
"optional":{
|
||||
"any_input":(any_type,),
|
||||
"delay_by_text":("STRING",{"multiline":True,}),
|
||||
"words_per_seconds":("FLOAT",{ "default":1.50,"min": 0.0,"max": 1000.00,"display":"Chars per second?"}),
|
||||
"replace_output": (["disable","enable"],),
|
||||
"replace_value":("INT",{ "default":-1,"min": 0,"max": 1000000,"display":"Replacement value"})
|
||||
}
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def calculate_words_length(cls,text):
|
||||
chinese_char_pattern = re.compile(r'[\u4e00-\u9fff]')
|
||||
english_word_pattern = re.compile(r'\b[a-zA-Z]+\b')
|
||||
number_pattern = re.compile(r'\b[0-9]+\b')
|
||||
|
||||
words_length = 0
|
||||
for segment in text.split():
|
||||
if chinese_char_pattern.search(segment):
|
||||
# 中文字符,每个字符计为 1
|
||||
words_length += len(segment)
|
||||
elif number_pattern.match(segment):
|
||||
# 数字,每个字符计为 1
|
||||
words_length += len(segment)
|
||||
elif english_word_pattern.match(segment):
|
||||
# 英文单词,整个单词计为 1
|
||||
words_length += 1
|
||||
|
||||
return words_length
|
||||
|
||||
|
||||
|
||||
FUNCTION = "run"
|
||||
RETURN_TYPES = (any_type,)
|
||||
RETURN_NAMES = ('output',)
|
||||
|
||||
CATEGORY = "♾️Mixlab/utils"
|
||||
def run(self,any_input,delay_seconds,delay_by_text,words_per_seconds,replace_output,replace_value):
|
||||
# print(f"Delay text:",delay_by_text )
|
||||
# 获取开始时间戳
|
||||
start_time = time.time()
|
||||
|
||||
# 计算延迟时间
|
||||
delay_time = delay_seconds
|
||||
if delay_by_text and isinstance(delay_by_text, str) and words_per_seconds > 0:
|
||||
words_length = self.calculate_words_length(delay_by_text)
|
||||
print(f"Delay text: {delay_by_text}, Length: {words_length}")
|
||||
delay_time += words_length / words_per_seconds
|
||||
|
||||
# 延迟执行
|
||||
print(f"延迟执行: {delay_time}")
|
||||
time.sleep(delay_time)
|
||||
|
||||
# 获取结束时间戳并计算间隔
|
||||
end_time = time.time()
|
||||
elapsed_time = end_time - start_time
|
||||
print(f"实际延迟时间: {elapsed_time} 秒")
|
||||
|
||||
# 根据 replace_output 决定输出值
|
||||
return (max(0, replace_value),) if replace_output == "enable" else (any_input,)
|
||||
|
||||
|
||||
|
||||
|
||||
# app 配置节点
|
||||
class AppInfo:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"name": ("STRING",{"multiline": False,"default": "Mixlab-App","dynamicPrompts": False}),
|
||||
"image": ("IMAGE",),
|
||||
"input_ids":("STRING",{"multiline": True,"default": "\n".join(["1","2","3"]),"dynamicPrompts": False}),
|
||||
"output_ids":("STRING",{"multiline": True,"default": "\n".join(["5","9"]),"dynamicPrompts": False}),
|
||||
},
|
||||
|
||||
"optional":{
|
||||
"description":("STRING",{"multiline": True,"default": "","dynamicPrompts": False}),
|
||||
"version":("INT", {
|
||||
"default": 1,
|
||||
"min": 1,
|
||||
"max": 10000,
|
||||
"step": 1,
|
||||
"display": "number"
|
||||
}),
|
||||
"share_prefix":("STRING",{"multiline": False,"default": "","dynamicPrompts": False}),
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_NAMES = ("IMAGE",)
|
||||
|
||||
FUNCTION = "run"
|
||||
|
||||
CATEGORY = "♾️Mixlab"
|
||||
|
||||
INPUT_IS_LIST = False
|
||||
OUTPUT_IS_LIST = (False,)
|
||||
|
||||
def run(self,name,image,input_ids,output_ids,description,version,share_prefix):
|
||||
|
||||
im=create_temp_file(image)
|
||||
|
||||
# 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,)}
|
||||
|
||||
|
||||
|
||||
|
||||
class GetImageSize_:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"image": ("IMAGE",),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("INT", "INT")
|
||||
RETURN_NAMES = ("width", "height")
|
||||
|
||||
FUNCTION = "get_size"
|
||||
|
||||
CATEGORY = "♾️Mixlab/utils"
|
||||
|
||||
def get_size(self, image):
|
||||
_, height, width, _ = image.shape
|
||||
return (width, height)
|
||||
|
||||
|
||||
|
||||
class SwitchByIndex:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"A":(any_type,),
|
||||
"B":(any_type,),
|
||||
"index":("INT", {
|
||||
"default": -1,
|
||||
"min": -1,
|
||||
"max": 1000,
|
||||
"step": 1,
|
||||
"display": "number"
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (any_type,)
|
||||
RETURN_NAMES = ("C",)
|
||||
|
||||
FUNCTION = "run"
|
||||
|
||||
CATEGORY = "♾️Mixlab/utils"
|
||||
|
||||
INPUT_IS_LIST = True
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
|
||||
def run(self, A,B,index):
|
||||
C=[]
|
||||
index=index[0]
|
||||
for a in A:
|
||||
C.append(a)
|
||||
for b in B:
|
||||
C.append(b)
|
||||
if index>-1:
|
||||
try:
|
||||
C=[C[index]]
|
||||
except Exception as e:
|
||||
C=[]
|
||||
return (C,)
|
||||
|
||||
|
||||
|
||||
class LimitNumber:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"number":(any_type,),
|
||||
"min_value":("INT", {
|
||||
"default": 0,
|
||||
"min": 0,
|
||||
"max": 0xffffffffffffffff,
|
||||
"step": 1,
|
||||
"display": "number"
|
||||
}),
|
||||
"max_value":("INT", {
|
||||
"default": 1,
|
||||
"min": 1,
|
||||
"max": 0xffffffffffffffff,
|
||||
"step": 1,
|
||||
"display": "number"
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (any_type,)
|
||||
RETURN_NAMES = ("number",)
|
||||
|
||||
FUNCTION = "run"
|
||||
|
||||
CATEGORY = "♾️Mixlab/utils"
|
||||
|
||||
INPUT_IS_LIST = False
|
||||
OUTPUT_IS_LIST = (False,)
|
||||
|
||||
def run(self, number, min_value, max_value):
|
||||
nn=number
|
||||
|
||||
if isinstance(number, int):
|
||||
min_value=int(min_value)
|
||||
max_value=int(max_value)
|
||||
if isinstance(number, float):
|
||||
min_value=float(min_value)
|
||||
max_value=float(max_value)
|
||||
|
||||
if number < min_value:
|
||||
nn= min_value
|
||||
elif number > max_value:
|
||||
nn= max_value
|
||||
|
||||
return (nn,)
|
||||
|
||||
@@ -145,7 +145,7 @@ class VAELoader:
|
||||
RETURN_TYPES = ("VAE",)
|
||||
FUNCTION = "load_vae"
|
||||
|
||||
CATEGORY = "Mixlab/ConsistencyDecoder"
|
||||
CATEGORY = "♾️Mixlab/_test"
|
||||
|
||||
#TODO: scale factor?
|
||||
def load_vae(self, vae_name):
|
||||
@@ -165,7 +165,7 @@ class VAEDecode:
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
FUNCTION = "decode"
|
||||
|
||||
CATEGORY = "Mixlab/ConsistencyDecoder"
|
||||
CATEGORY = "♾️Mixlab/_test"
|
||||
|
||||
def decode(self, vae, samples):
|
||||
image = vae.decode(samples["samples"].to("cuda:0"))
|
||||
|
||||
@@ -8,25 +8,34 @@ import os
|
||||
# print('Watcher:',current_directory)
|
||||
|
||||
def save_to_json(file_path, data):
|
||||
with open(file_path, 'w') as f:
|
||||
json.dump(data, f)
|
||||
try:
|
||||
with open(file_path, 'w') as f:
|
||||
json.dump(data, f)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
def read_from_json(file_path):
|
||||
with open(file_path, 'r') as f:
|
||||
data = json.load(f)
|
||||
data={}
|
||||
try:
|
||||
with open(file_path, 'r') as f:
|
||||
data = json.load(f)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
return data
|
||||
|
||||
|
||||
# read_from_json()
|
||||
current_path = os.path.abspath(os.path.dirname(__file__))
|
||||
config_json=os.path.join(current_path,'config.json')
|
||||
print('Watcher:',config_json)
|
||||
# print('Watcher:',config_json)
|
||||
|
||||
def read_config():
|
||||
config={}
|
||||
if os.path.exists(config_json):
|
||||
# print('exists')
|
||||
config=read_from_json(config_json)
|
||||
try:
|
||||
if os.path.exists(config_json):
|
||||
config=read_from_json(config_json)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
return config
|
||||
|
||||
|
||||
@@ -39,10 +48,10 @@ class FolderWatcher:
|
||||
config['folder_path']=folder_path
|
||||
save_to_json(config_json,config)
|
||||
|
||||
# self.observer = Observer()
|
||||
self.observer = None
|
||||
self.event_handler = self._create_event_handler()
|
||||
self.status = "Not started"
|
||||
self.event_type=''
|
||||
self.event_type='-'
|
||||
|
||||
def _create_event_handler(self):
|
||||
|
||||
@@ -77,22 +86,23 @@ class FolderWatcher:
|
||||
config=read_config()
|
||||
config['folder_path']=new_folder_path
|
||||
save_to_json(config_json,config)
|
||||
self.event_type=''
|
||||
self.event_type='-'
|
||||
|
||||
def start(self):
|
||||
self.observer = Observer()
|
||||
self.observer.schedule(self.event_handler, self.folder_path, recursive=True)
|
||||
self.observer.start()
|
||||
self.status = "Listening"
|
||||
self.event_type=''
|
||||
self.event_type='-'
|
||||
print('Listening')
|
||||
|
||||
def stop(self):
|
||||
self.observer.stop()
|
||||
self.observer.join()
|
||||
self.observer=None
|
||||
self.status = "Stopped"
|
||||
self.event_type=''
|
||||
if self.observer!=None:
|
||||
self.observer.stop()
|
||||
self.observer.join()
|
||||
self.observer=None
|
||||
self.status = "Stopped"
|
||||
self.event_type='-'
|
||||
print('Stopped')
|
||||
|
||||
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
numpy
|
||||
pyOpenSSL
|
||||
watchdog
|
||||
watchdog
|
||||
opencv-python-headless
|
||||
matplotlib
|
||||
openai
|
||||
simple-lama-inpainting
|
||||
@@ -0,0 +1,627 @@
|
||||
import { app } from '../../../scripts/app.js'
|
||||
import { api } from '../../../scripts/api.js'
|
||||
import { $el } from '../../../scripts/ui.js'
|
||||
|
||||
const getLocalData = key => {
|
||||
let data = {}
|
||||
try {
|
||||
data = JSON.parse(localStorage.getItem(key)) || {}
|
||||
} catch (error) {
|
||||
return {}
|
||||
}
|
||||
return data
|
||||
}
|
||||
function getContentTypeFromBase64 (base64Data) {
|
||||
const regex = /^data:(.+);base64,/
|
||||
const matches = base64Data.match(regex)
|
||||
if (matches && matches.length >= 2) {
|
||||
return matches[1]
|
||||
}
|
||||
return null
|
||||
}
|
||||
function base64ToBlobFromURL (base64URL, contentType) {
|
||||
return fetch(base64URL).then(response => response.blob())
|
||||
}
|
||||
const setLocalDataOfWin = (key, value) => {
|
||||
localStorage.setItem(key, JSON.stringify(value))
|
||||
// window[key] = value
|
||||
}
|
||||
async function uploadImage (blob, fileType = '.svg', filename) {
|
||||
// const blob = await (await fetch(src)).blob();
|
||||
const body = new FormData()
|
||||
body.append(
|
||||
'image',
|
||||
new File([blob], (filename || new Date().getTime()) + fileType)
|
||||
)
|
||||
|
||||
const resp = await api.fetchApi('/upload/image', {
|
||||
method: 'POST',
|
||||
body
|
||||
})
|
||||
|
||||
// console.log(resp)
|
||||
let data = await resp.json()
|
||||
let { name, subfolder } = data
|
||||
let src = api.apiURL(
|
||||
`/view?filename=${encodeURIComponent(
|
||||
name
|
||||
)}&type=input&subfolder=${subfolder}${app.getPreviewFormatParam()}${app.getRandParam()}`
|
||||
)
|
||||
|
||||
return src
|
||||
}
|
||||
|
||||
function createImage (url) {
|
||||
let im = new Image()
|
||||
return new Promise((res, rej) => {
|
||||
im.onload = () => res(im)
|
||||
im.src = url
|
||||
})
|
||||
}
|
||||
|
||||
const parseImage = url => {
|
||||
return new Promise((res, rej) => {
|
||||
fetch(url)
|
||||
.then(response => response.blob())
|
||||
.then(blob => {
|
||||
const reader = new FileReader()
|
||||
reader.onloadend = () => {
|
||||
const base64data = reader.result
|
||||
res(base64data)
|
||||
// 在这里可以将base64数据用于进一步处理或显示图片
|
||||
}
|
||||
reader.readAsDataURL(blob)
|
||||
})
|
||||
.catch(error => {
|
||||
console.log('发生错误:', error)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function get_position_style (ctx, widget_width, y, node_height) {
|
||||
const MARGIN = 4 // the margin around the html element
|
||||
|
||||
/* Create a transform that deals with all the scrolling and zooming */
|
||||
const elRect = ctx.canvas.getBoundingClientRect()
|
||||
const transform = new DOMMatrix()
|
||||
.scaleSelf(
|
||||
elRect.width / ctx.canvas.width,
|
||||
elRect.height / ctx.canvas.height
|
||||
)
|
||||
.multiplySelf(ctx.getTransform())
|
||||
.translateSelf(MARGIN, MARGIN + y)
|
||||
|
||||
return {
|
||||
transformOrigin: '0 0',
|
||||
transform: transform,
|
||||
left: `0`,
|
||||
top: `0`,
|
||||
cursor: 'pointer',
|
||||
position: 'absolute',
|
||||
maxWidth: `${widget_width - MARGIN * 2}px`,
|
||||
// maxHeight: `${node_height - MARGIN * 2}px`, // we're assuming we have the whole height of the node
|
||||
width: `${widget_width - MARGIN * 2}px`,
|
||||
// height: `${node_height * 0.3 - MARGIN * 2}px`,
|
||||
// background: '#EEEEEE',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
// alignItems: 'center',
|
||||
justifyContent: 'space-around'
|
||||
}
|
||||
}
|
||||
|
||||
async function extractMaterial (
|
||||
modelViewerVariants,
|
||||
selectMaterial,
|
||||
material_img
|
||||
) {
|
||||
// 材质
|
||||
const materialsNames = []
|
||||
for (
|
||||
let index = 0;
|
||||
index < modelViewerVariants.model.materials.length;
|
||||
index++
|
||||
) {
|
||||
let m = modelViewerVariants.model.materials[index]
|
||||
let thumbUrl
|
||||
try {
|
||||
thumbUrl =
|
||||
await m.pbrMetallicRoughness.baseColorTexture.texture.source.createThumbnail(
|
||||
1024,
|
||||
1024
|
||||
)
|
||||
} catch (error) {}
|
||||
if (thumbUrl)
|
||||
materialsNames.push({
|
||||
value: m.name,
|
||||
text: `#${index} ${m.name}`,
|
||||
index,
|
||||
thumbUrl
|
||||
})
|
||||
}
|
||||
|
||||
selectMaterial.innerHTML = ''
|
||||
material_img.innerHTML = ''
|
||||
|
||||
for (let index = 0; index < materialsNames.length; index++) {
|
||||
const name = materialsNames[index]
|
||||
const option = document.createElement('option')
|
||||
option.value = name.thumbUrl
|
||||
option.textContent = name.text
|
||||
option.setAttribute('data-index', index)
|
||||
selectMaterial.appendChild(option)
|
||||
let img = new Image()
|
||||
img.src = name.thumbUrl
|
||||
// img.setAttribute('data-index',name.index)
|
||||
img.style.width = '40px'
|
||||
material_img.appendChild(img)
|
||||
if (index == 0) {
|
||||
material_img.setAttribute('src', name.thumbUrl)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function changeMaterial (
|
||||
modelViewerVariants,
|
||||
targetMaterial,
|
||||
newImageUrl
|
||||
) {
|
||||
const targetTexture = await modelViewerVariants.createTexture(newImageUrl)
|
||||
// 用图片创建纹理
|
||||
targetMaterial.pbrMetallicRoughness.baseColorTexture.setTexture(targetTexture)
|
||||
}
|
||||
|
||||
app.registerExtension({
|
||||
name: 'Mixlab.3D.3DImage',
|
||||
async getCustomWidgets (app) {
|
||||
return {
|
||||
THREED (node, inputName, inputData, app) {
|
||||
// console.log('##node', node, inputName, inputData)
|
||||
const widget = {
|
||||
type: inputData[0], // the type, CHEESE
|
||||
name: inputName, // the name, slice
|
||||
size: [128, 88], // a default size
|
||||
draw (ctx, node, width, y) {},
|
||||
computeSize (...args) {
|
||||
return [128, 88] // a method to compute the current size of the widget
|
||||
},
|
||||
async serializeValue (nodeId, widgetIndex) {
|
||||
let d = getLocalData('_mixlab_3d_image')
|
||||
// console.log('serializeValue', node)
|
||||
if (d && d[node.id]) {
|
||||
let { url, bg, material } = d[node.id]
|
||||
let data = {}
|
||||
if (url) {
|
||||
data.image = await parseImage(url)
|
||||
}
|
||||
if (bg) {
|
||||
data.bg_image = await parseImage(bg)
|
||||
}
|
||||
|
||||
if (material) {
|
||||
data.material = await parseImage(material)
|
||||
}
|
||||
|
||||
return JSON.parse(JSON.stringify(data))
|
||||
} else {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
}
|
||||
node.addCustomWidget(widget)
|
||||
return widget
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
async beforeRegisterNodeDef (nodeType, nodeData, app) {
|
||||
if (nodeType.comfyClass == '3DImage') {
|
||||
const orig_nodeCreated = nodeType.prototype.onNodeCreated
|
||||
nodeType.prototype.onNodeCreated = async function () {
|
||||
orig_nodeCreated?.apply(this, arguments)
|
||||
|
||||
const uploadWidget = this.widgets.filter(w => w.name == 'upload')[0]
|
||||
|
||||
const widget = {
|
||||
type: 'div',
|
||||
name: 'upload-preview',
|
||||
draw (ctx, node, widget_width, y, widget_height) {
|
||||
Object.assign(
|
||||
this.div.style,
|
||||
get_position_style(ctx, widget_width, 88, node.size[1])
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
widget.div = $el('div', {})
|
||||
widget.div.style.width = `120px`
|
||||
|
||||
document.body.appendChild(widget.div)
|
||||
|
||||
const inputDiv = (key, placeholder, preview) => {
|
||||
let div = document.createElement('div')
|
||||
const ip = document.createElement('input')
|
||||
ip.type = 'file'
|
||||
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: 60%;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)
|
||||
|
||||
let that = this,
|
||||
filename = new Date().getTime()
|
||||
|
||||
ip.addEventListener('change', async event => {
|
||||
const file = event.target.files[0]
|
||||
const reader = new FileReader()
|
||||
filename = new Date().getTime()
|
||||
// 读取文件内容
|
||||
reader.onload = async e => {
|
||||
const fileURL = URL.createObjectURL(file)
|
||||
// console.log('文件URL: ', fileURL)
|
||||
let html = `<model-viewer src="${fileURL}"
|
||||
min-field-of-view="0deg" max-field-of-view="180deg"
|
||||
shadow-intensity="1"
|
||||
camera-controls
|
||||
touch-action="pan-y">
|
||||
|
||||
<div class="controls">
|
||||
<div>Variant: <select class="variant"></select></div>
|
||||
<div>Material: <select class="material"></select></div>
|
||||
<div>Material: <div class="material_img"> </div></div>
|
||||
<div><button class="bg">BG</button></div>
|
||||
<div><button class="export">Export GLB</button></div>
|
||||
|
||||
</div></model-viewer>`
|
||||
|
||||
preview.innerHTML = html
|
||||
if (that.size[1] < 400) {
|
||||
that.setSize([that.size[0], that.size[1] + 300])
|
||||
app.canvas.draw(true, true)
|
||||
}
|
||||
|
||||
const modelViewerVariants = preview.querySelector('model-viewer')
|
||||
const select = preview.querySelector('.variant')
|
||||
const selectMaterial = preview.querySelector('.material')
|
||||
const material_img = preview.querySelector('.material_img')
|
||||
const bg = preview.querySelector('.bg')
|
||||
const exportGLB = preview.querySelector('.export')
|
||||
|
||||
if (modelViewerVariants) {
|
||||
modelViewerVariants.style.width = `${that.size[0] - 24}px`
|
||||
modelViewerVariants.style.height = `${that.size[1] - 48}px`
|
||||
}
|
||||
|
||||
modelViewerVariants.addEventListener('load', async () => {
|
||||
const names = modelViewerVariants.availableVariants
|
||||
|
||||
// 变量
|
||||
for (const name of names) {
|
||||
const option = document.createElement('option')
|
||||
option.value = name
|
||||
option.textContent = name
|
||||
select.appendChild(option)
|
||||
}
|
||||
// Adds a default option.
|
||||
if (names.length === 0) {
|
||||
const option = document.createElement('option')
|
||||
option.value = 'default'
|
||||
option.textContent = 'Default'
|
||||
select.appendChild(option)
|
||||
}
|
||||
|
||||
// 材质
|
||||
extractMaterial(
|
||||
modelViewerVariants,
|
||||
selectMaterial,
|
||||
material_img
|
||||
)
|
||||
})
|
||||
|
||||
let timer = null
|
||||
const delay = 500 // 延迟时间,单位为毫秒
|
||||
|
||||
async function checkCameraChange () {
|
||||
let dd = getLocalData(key)
|
||||
let base64Data = modelViewerVariants.toDataURL()
|
||||
|
||||
const contentType = getContentTypeFromBase64(base64Data)
|
||||
|
||||
const blob = await base64ToBlobFromURL(base64Data, contentType)
|
||||
|
||||
// const fileBlob = new Blob([e.target.result], { type: file.type });
|
||||
let url = await uploadImage(blob, '.png')
|
||||
// console.log(url)
|
||||
|
||||
let bg_blob = await base64ToBlobFromURL(
|
||||
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mN88uXrPQAFwwK/6xJ6CQAAAABJRU5ErkJggg=='
|
||||
)
|
||||
let url_bg = await uploadImage(bg_blob, '.png')
|
||||
// console.log('url_bg',url_bg)
|
||||
|
||||
if (!dd[that.id]) {
|
||||
dd[that.id] = { url, bg: url_bg }
|
||||
} else {
|
||||
dd[that.id] = { ...dd[that.id], url }
|
||||
}
|
||||
|
||||
// 材质贴图
|
||||
let thumbUrl = material_img.getAttribute('src')
|
||||
if (thumbUrl) {
|
||||
let tb = await base64ToBlobFromURL(thumbUrl)
|
||||
let tUrl = await uploadImage(tb, '.png')
|
||||
// console.log('材质贴图', tUrl, thumbUrl)
|
||||
dd[that.id].material = tUrl
|
||||
}
|
||||
|
||||
setLocalDataOfWin(key, dd)
|
||||
}
|
||||
|
||||
function startTimer () {
|
||||
if (timer) clearTimeout(timer)
|
||||
timer = setTimeout(checkCameraChange, delay)
|
||||
}
|
||||
|
||||
modelViewerVariants.addEventListener('camera-change', startTimer)
|
||||
|
||||
select.addEventListener('input', async event => {
|
||||
modelViewerVariants.variantName =
|
||||
event.target.value === 'default' ? null : event.target.value
|
||||
// 材质
|
||||
await extractMaterial(
|
||||
modelViewerVariants,
|
||||
selectMaterial,
|
||||
material_img
|
||||
)
|
||||
checkCameraChange()
|
||||
})
|
||||
|
||||
selectMaterial.addEventListener('input', event => {
|
||||
// console.log(selectMaterial.value)
|
||||
material_img.setAttribute('src', selectMaterial.value)
|
||||
|
||||
if (selectMaterial.getAttribute('data-new-material')) {
|
||||
let index =
|
||||
~~selectMaterial.selectedOptions[0].getAttribute(
|
||||
'data-index'
|
||||
)
|
||||
changeMaterial(
|
||||
modelViewerVariants,
|
||||
modelViewerVariants.model.materials[index],
|
||||
selectMaterial.getAttribute('data-new-material')
|
||||
)
|
||||
}
|
||||
|
||||
checkCameraChange()
|
||||
})
|
||||
|
||||
bg.addEventListener('click', () => {
|
||||
// 创建一个input元素
|
||||
var input = document.createElement('input')
|
||||
input.type = 'file'
|
||||
|
||||
// 监听input的change事件
|
||||
input.addEventListener('change', function () {
|
||||
// 获取上传的文件
|
||||
var file = input.files[0]
|
||||
|
||||
// 创建一个FileReader对象来读取文件
|
||||
var reader = new FileReader()
|
||||
|
||||
// 监听FileReader的load事件
|
||||
reader.addEventListener('load', async () => {
|
||||
let base64 = reader.result
|
||||
// 将读取的文件内容设置为div的背景
|
||||
preview.style.backgroundImage = 'url(' + base64 + ')'
|
||||
|
||||
const contentType = getContentTypeFromBase64(base64)
|
||||
|
||||
const blob = await base64ToBlobFromURL(base64, contentType)
|
||||
|
||||
// const fileBlob = new Blob([e.target.result], { type: file.type });
|
||||
let bg_url = await uploadImage(blob, '.png')
|
||||
let bg_img = await createImage(base64)
|
||||
|
||||
let dd = getLocalData(key)
|
||||
// console.log(dd[that.id],bg_url)
|
||||
if (!dd[that.id]) dd[that.id] = { url: '', bg: bg_url }
|
||||
dd[that.id] = {
|
||||
...dd[that.id],
|
||||
bg: bg_url,
|
||||
bg_w: bg_img.naturalWidth,
|
||||
bg_h: bg_img.naturalHeight
|
||||
}
|
||||
|
||||
setLocalDataOfWin(key, dd)
|
||||
|
||||
// 更新尺寸
|
||||
let w = that.size[0] - 24,
|
||||
h = (w * bg_img.naturalHeight) / bg_img.naturalWidth
|
||||
|
||||
if (modelViewerVariants) {
|
||||
modelViewerVariants.style.width = `${w}px`
|
||||
modelViewerVariants.style.height = `${h}px`
|
||||
}
|
||||
preview.style.width = `${w}px`
|
||||
})
|
||||
|
||||
// 读取文件
|
||||
reader.readAsDataURL(file)
|
||||
})
|
||||
|
||||
// 触发input的点击事件
|
||||
input.click()
|
||||
})
|
||||
|
||||
exportGLB.addEventListener('click', async () => {
|
||||
const glTF = await modelViewerVariants.exportScene()
|
||||
const file = new File([glTF], 'export.glb')
|
||||
const link = document.createElement('a')
|
||||
link.download = file.name
|
||||
link.href = URL.createObjectURL(file)
|
||||
link.click()
|
||||
})
|
||||
|
||||
uploadWidget.value = await uploadWidget.serializeValue()
|
||||
|
||||
// 更新尺寸
|
||||
let dd = getLocalData(key)
|
||||
// console.log(dd[that.id],bg_url)
|
||||
if (dd[that.id]) {
|
||||
const { bg_w, bg_h } = dd[that.id]
|
||||
if (bg_h && bg_w) {
|
||||
let w = that.size[0] - 24,
|
||||
h = (w * bg_h) / bg_w
|
||||
|
||||
if (modelViewerVariants) {
|
||||
modelViewerVariants.style.width = `${w}px`
|
||||
modelViewerVariants.style.height = `${h}px`
|
||||
}
|
||||
preview.style.width = `${w}px`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 以文本形式读取文件
|
||||
reader.readAsDataURL(file)
|
||||
})
|
||||
return div
|
||||
}
|
||||
|
||||
let preview = document.createElement('div')
|
||||
preview.className = 'preview'
|
||||
preview.style = `margin-top: 12px;display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;background-repeat: no-repeat;background-size: contain;`
|
||||
|
||||
let upload = inputDiv('_mixlab_3d_image', '3D Model', preview)
|
||||
|
||||
widget.div.appendChild(upload)
|
||||
widget.div.appendChild(preview)
|
||||
this.addCustomWidget(widget)
|
||||
|
||||
const onResize = this.onResize
|
||||
let that = this
|
||||
this.onResize = function () {
|
||||
let modelViewerVariants = preview.querySelector('model-viewer')
|
||||
|
||||
// 更新尺寸
|
||||
let dd = getLocalData('_mixlab_3d_image')
|
||||
// console.log(dd[that.id],bg_url)
|
||||
if (dd[that.id]) {
|
||||
const { bg_w, bg_h } = dd[that.id]
|
||||
if (bg_h && bg_w) {
|
||||
let w = that.size[0] - 24,
|
||||
h = (w * bg_h) / bg_w
|
||||
|
||||
if (modelViewerVariants) {
|
||||
modelViewerVariants.style.width = `${w}px`
|
||||
modelViewerVariants.style.height = `${h}px`
|
||||
}
|
||||
preview.style.width = `${w}px`
|
||||
}
|
||||
}
|
||||
|
||||
return onResize?.apply(this, arguments)
|
||||
}
|
||||
|
||||
const onRemoved = this.onRemoved
|
||||
this.onRemoved = () => {
|
||||
upload.remove()
|
||||
preview.remove()
|
||||
widget.div.remove()
|
||||
return onRemoved?.()
|
||||
}
|
||||
|
||||
if (this.onResize) {
|
||||
this.onResize(this.size)
|
||||
}
|
||||
// this.isVirtualNode = true
|
||||
this.serialize_widgets = false //需要保存参数
|
||||
}
|
||||
|
||||
const onExecuted = nodeType.prototype.onExecuted
|
||||
nodeType.prototype.onExecuted = function (message) {
|
||||
const r = onExecuted?.apply?.(this, arguments)
|
||||
|
||||
let div = this.widgets.filter(d => d.div)[0]?.div
|
||||
console.log('Test', this.widgets)
|
||||
|
||||
let material = message.material[0]
|
||||
if (material) {
|
||||
const { filename, subfolder, type } = material
|
||||
let src = api.apiURL(
|
||||
`/view?filename=${encodeURIComponent(
|
||||
filename
|
||||
)}&type=${type}&subfolder=${subfolder}${app.getPreviewFormatParam()}${app.getRandParam()}`
|
||||
)
|
||||
|
||||
const modelViewerVariants = div.querySelector('model-viewer')
|
||||
|
||||
const selectMaterial = div.querySelector('.material')
|
||||
|
||||
let index =
|
||||
~~selectMaterial.selectedOptions[0].getAttribute('data-index')
|
||||
|
||||
selectMaterial.setAttribute('data-new-material', src)
|
||||
|
||||
changeMaterial(
|
||||
modelViewerVariants,
|
||||
modelViewerVariants.model.materials[index],
|
||||
src
|
||||
)
|
||||
}
|
||||
|
||||
this.onResize?.(this.size)
|
||||
|
||||
return r
|
||||
}
|
||||
}
|
||||
},
|
||||
async loadedGraphNode (node, app) {
|
||||
// Fires every time a node is constructed
|
||||
// You can modify widgets/add handlers/etc here
|
||||
const sleep = (t = 1000) => {
|
||||
return new Promise((res, rej) => {
|
||||
setTimeout(() => res(1), t)
|
||||
})
|
||||
}
|
||||
if (node.type === '3DImage') {
|
||||
// await sleep(0)
|
||||
let widget = node.widgets.filter(w => w.name === 'upload-preview')[0]
|
||||
|
||||
let dd = getLocalData('_mixlab_3d_image')
|
||||
|
||||
let id = node.id
|
||||
// console.log('3dImage load', node.widgets[0], node.widgets)
|
||||
if (!dd[id]) return
|
||||
|
||||
let { url, bg } = dd[id]
|
||||
if (!url) return
|
||||
// let base64 = await parseImage(url)
|
||||
|
||||
let pre = widget.div.querySelector('.preview')
|
||||
pre.style.width = `${node.size[0]}px`
|
||||
pre.innerHTML = `
|
||||
${url ? `<img src="${url}" style="width:100%"/>` : ''}
|
||||
`
|
||||
pre.style.backgroundImage = 'url(' + bg + ')'
|
||||
|
||||
const uploadWidget = node.widgets.filter(w => w.name == 'upload')[0]
|
||||
uploadWidget.value = await uploadWidget.serializeValue()
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,306 @@
|
||||
import { app } from '../../../scripts/app.js'
|
||||
import { $el } from '../../../scripts/ui.js'
|
||||
import { api } from '../../../scripts/api.js'
|
||||
|
||||
function get_position_style (ctx, widget_width, y, node_height) {
|
||||
const MARGIN = 12 // the margin around the html element
|
||||
|
||||
/* Create a transform that deals with all the scrolling and zooming */
|
||||
const elRect = ctx.canvas.getBoundingClientRect()
|
||||
const transform = new DOMMatrix()
|
||||
.scaleSelf(
|
||||
elRect.width / ctx.canvas.width,
|
||||
elRect.height / ctx.canvas.height
|
||||
)
|
||||
.multiplySelf(ctx.getTransform())
|
||||
.translateSelf(MARGIN, MARGIN + y)
|
||||
|
||||
return {
|
||||
transformOrigin: '0 0',
|
||||
transform: transform,
|
||||
left: `0`,
|
||||
top: `0`,
|
||||
cursor: 'pointer',
|
||||
position: 'absolute',
|
||||
maxWidth: `${widget_width - MARGIN * 2}px`,
|
||||
// maxHeight: `${node_height - MARGIN * 2}px`, // we're assuming we have the whole height of the node
|
||||
width: `${widget_width - MARGIN * 2}px`,
|
||||
// height: `${node_height * 0.3 - MARGIN * 2}px`,
|
||||
// background: '#EEEEEE',
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
// alignItems: 'center',
|
||||
justifyContent: 'flex-start'
|
||||
}
|
||||
}
|
||||
|
||||
async function drawImageToCanvas (imageUrl) {
|
||||
var canvas = document.createElement('canvas')
|
||||
var ctx = canvas.getContext('2d')
|
||||
var img = new Image()
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
img.onload = function () {
|
||||
var scaleFactor = 320 / img.width
|
||||
var canvasWidth = img.width * scaleFactor
|
||||
var canvasHeight = img.height * scaleFactor
|
||||
|
||||
canvas.width = canvasWidth
|
||||
canvas.height = canvasHeight
|
||||
|
||||
ctx.drawImage(img, 0, 0, canvasWidth, canvasHeight)
|
||||
|
||||
resolve()
|
||||
}
|
||||
|
||||
img.onerror = function () {
|
||||
reject(new Error('Failed to load image'))
|
||||
}
|
||||
|
||||
img.src = imageUrl
|
||||
})
|
||||
|
||||
var base64 = canvas.toDataURL('image/jpeg')
|
||||
// console.log(base64); // 输出Base64数据
|
||||
return base64
|
||||
// 可以在这里执行其他操作,比如将Base64数据保存到服务器或显示在页面上
|
||||
}
|
||||
|
||||
function extractInputAndOutputData (jsonData, inputIds = [], outputIds = []) {
|
||||
const data = jsonData
|
||||
const input = []
|
||||
const output = []
|
||||
|
||||
for (const id in data) {
|
||||
if (data.hasOwnProperty(id)) {
|
||||
if (inputIds.includes(id)) {
|
||||
let node = app.graph.getNodeById(id)
|
||||
let options = []
|
||||
// 模型
|
||||
try {
|
||||
if (node.type === 'CheckpointLoaderSimple') {
|
||||
options = node.widgets.filter(w => w.name === 'ckpt_name')[0]
|
||||
.options.values
|
||||
} else if (node.type === 'LoraLoader') {
|
||||
options = node.widgets.filter(w => w.name === 'lora_name')[0]
|
||||
.options.values
|
||||
}
|
||||
} catch (error) {}
|
||||
|
||||
input[inputIds.indexOf(id)] = {
|
||||
...data[id],
|
||||
title: node.title,
|
||||
id,
|
||||
options
|
||||
}
|
||||
// input.push()
|
||||
}
|
||||
if (outputIds.includes(id)) {
|
||||
let node = app.graph.getNodeById(id)
|
||||
// output.push()
|
||||
output[outputIds.indexOf(id)] = { ...data[id], title: node.title, id }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { input, output }
|
||||
}
|
||||
|
||||
function getUrl () {
|
||||
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 save_app (json) {
|
||||
let url = getUrl()
|
||||
|
||||
const res = await fetch(`${url}/mixlab/workflow`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
data: json,
|
||||
task: 'save_app',
|
||||
filename: json.app.filename
|
||||
})
|
||||
})
|
||||
return await res.json()
|
||||
}
|
||||
|
||||
function downloadJsonFile (jsonData, fileName = 'mix_app.json') {
|
||||
const dataString = JSON.stringify(jsonData)
|
||||
const blob = new Blob([dataString], { type: 'application/json' })
|
||||
const url = URL.createObjectURL(blob)
|
||||
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
link.download = fileName
|
||||
link.click()
|
||||
|
||||
// 释放URL对象
|
||||
setTimeout(() => {
|
||||
URL.revokeObjectURL(url)
|
||||
}, 0)
|
||||
}
|
||||
|
||||
async function save (json, download = false) {
|
||||
const name = json[0],
|
||||
version = json[5],
|
||||
share_prefix = json[6], //用于分享的功能扩展
|
||||
description = json[4],
|
||||
inputIds = json[2].split('\n').filter(f => f),
|
||||
outputIds = json[3].split('\n').filter(f => f)
|
||||
|
||||
const iconData = json[1][0]
|
||||
let { filename, subfolder, type } = iconData
|
||||
let iconUrl = api.apiURL(
|
||||
`/view?filename=${encodeURIComponent(
|
||||
filename
|
||||
)}&type=${type}&subfolder=${subfolder}${app.getPreviewFormatParam()}${app.getRandParam()}`
|
||||
)
|
||||
|
||||
try {
|
||||
let data = await app.graphToPrompt()
|
||||
|
||||
const { input, output } = extractInputAndOutputData(
|
||||
data.output,
|
||||
inputIds,
|
||||
outputIds
|
||||
)
|
||||
|
||||
data.app = {
|
||||
name,
|
||||
description,
|
||||
version,
|
||||
input,
|
||||
output,
|
||||
share_prefix,
|
||||
filename: `${name}_${version}_${new Date().toDateString()}.json`
|
||||
}
|
||||
|
||||
try {
|
||||
data.app.icon = await drawImageToCanvas(iconUrl)
|
||||
} catch (error) {}
|
||||
// console.log(data.app)
|
||||
// let http_workflow = app.graph.serialize()
|
||||
|
||||
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`)
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('###SpeechRecognition', error)
|
||||
}
|
||||
}
|
||||
|
||||
app.registerExtension({
|
||||
name: 'Mixlab.utils.AppInfo',
|
||||
async beforeRegisterNodeDef (nodeType, nodeData, app) {
|
||||
if (nodeType.comfyClass == 'AppInfo') {
|
||||
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: 'AppInfoRun',
|
||||
draw (ctx, node, widget_width, y, widget_height) {
|
||||
Object.assign(
|
||||
this.div.style,
|
||||
get_position_style(
|
||||
ctx,
|
||||
widget_width,
|
||||
node.size[1] - widget_height,
|
||||
node.size[1]
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const style = `
|
||||
flex-direction: row;
|
||||
background-color: var(--comfy-input-bg);
|
||||
border-radius: 8px;
|
||||
border-color: var(--border-color);
|
||||
border-style: solid;
|
||||
color: var(--descrip-text);`
|
||||
|
||||
widget.div = $el('div', {})
|
||||
|
||||
const btn = document.createElement('button')
|
||||
btn.innerText = 'Save & Open'
|
||||
btn.style = style
|
||||
|
||||
btn.addEventListener('click', () => {
|
||||
// console.log('hahhah')
|
||||
if (window._mixlab_app_json) {
|
||||
save(window._mixlab_app_json)
|
||||
} else {
|
||||
alert('Please run the workflow before saving')
|
||||
// app.queuePrompt(0, 1)
|
||||
this.widgets.filter(w => w.name === 'version')[0].value += 1
|
||||
}
|
||||
})
|
||||
|
||||
const download = document.createElement('button')
|
||||
download.innerText = 'Download For App'
|
||||
download.style = style
|
||||
download.style.marginLeft = '12px'
|
||||
|
||||
download.addEventListener('click', () => {
|
||||
// console.log('hahhah')
|
||||
if (window._mixlab_app_json) {
|
||||
save(window._mixlab_app_json, true)
|
||||
} else {
|
||||
alert('Please run the workflow before saving')
|
||||
// app.queuePrompt(0, 1)
|
||||
this.widgets.filter(w => w.name === 'version')[0].value += 1
|
||||
}
|
||||
})
|
||||
|
||||
document.body.appendChild(widget.div)
|
||||
widget.div.appendChild(btn)
|
||||
widget.div.appendChild(download)
|
||||
|
||||
this.addCustomWidget(widget)
|
||||
|
||||
const onRemoved = this.onRemoved
|
||||
this.onRemoved = () => {
|
||||
widget.div.remove()
|
||||
return onRemoved?.()
|
||||
}
|
||||
|
||||
this.serialize_widgets = true //需要保存参数
|
||||
}
|
||||
|
||||
const onExecuted = nodeType.prototype.onExecuted
|
||||
nodeType.prototype.onExecuted = async 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
|
||||
Array.from(
|
||||
div.querySelectorAll('button'),
|
||||
b => (b.style.background = 'yellow')
|
||||
)
|
||||
} catch (error) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,398 @@
|
||||
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}px`,
|
||||
// height: `${node_height * 0.3 - MARGIN * 2}px`,
|
||||
// background: '#EEEEEE',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
// alignItems: 'center',
|
||||
justifyContent: 'space-around'
|
||||
}
|
||||
}
|
||||
|
||||
const getLocalData = key => {
|
||||
let data = {}
|
||||
try {
|
||||
data = JSON.parse(localStorage.getItem(key)) || {}
|
||||
} catch (error) {
|
||||
return {}
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
function speakText (text) {
|
||||
const speechMsg = new SpeechSynthesisUtterance()
|
||||
speechMsg.text = text
|
||||
|
||||
// 语音合成结束时触发的事件
|
||||
speechMsg.onend = function (event) {
|
||||
console.log('语音播放结束')
|
||||
window._mixlab_speech_synthesis_onend = true
|
||||
}
|
||||
|
||||
// 语音合成错误时触发的事件
|
||||
speechMsg.onerror = function (event) {
|
||||
console.error('语音播放错误:', event.error)
|
||||
}
|
||||
|
||||
// 使用浏览器默认语音合成器进行语音播放
|
||||
speechSynthesis.speak(speechMsg)
|
||||
}
|
||||
|
||||
// 调用方法,将文字转换为语音播放
|
||||
// speakText('Hello, how are you?');
|
||||
// #MixCopilot
|
||||
|
||||
const start = (element, id, startBtn, node) => {
|
||||
startBtn.className = 'loading_mixlab'
|
||||
|
||||
window.recognition = new webkitSpeechRecognition()
|
||||
|
||||
window.recognition.continuous = true
|
||||
window.recognition.interimResults = true
|
||||
window.recognition.lang = navigator.language
|
||||
|
||||
let timeoutId, intervalId
|
||||
|
||||
window.recognition.onstart = () => {
|
||||
console.log('开始语音输入', window._mixlab_speech_synthesis_onend)
|
||||
window._mixlab_speech_synthesis_onend = false
|
||||
}
|
||||
|
||||
window.recognition.onresult = function (event) {
|
||||
const result = event.results[event.results.length - 1][0].transcript
|
||||
console.log('识别结果:', result)
|
||||
element.value = result
|
||||
|
||||
let data = getLocalData('_mixlab_speech_recognition')
|
||||
data[id] = result.trim()
|
||||
localStorage.setItem('_mixlab_speech_recognition', JSON.stringify(data))
|
||||
|
||||
if (timeoutId) clearTimeout(timeoutId)
|
||||
|
||||
if (!window.recognition) return
|
||||
|
||||
timeoutId = setTimeout(function () {
|
||||
console.log('结果传递::', result)
|
||||
|
||||
// 把数据发送到chatgpt的输入prompt里
|
||||
try {
|
||||
const sendToId = node.widgets.filter(
|
||||
w => w.name === 'Send to ChatGPT #'
|
||||
)[0].value
|
||||
app.graph
|
||||
.getNodeById(sendToId)
|
||||
.widgets.filter(w => w.name === 'prompt')[0].value = result
|
||||
} catch (error) {}
|
||||
|
||||
setTimeout(() => app.queuePrompt(0, 1), 100)
|
||||
window.recognition?.stop()
|
||||
window.recognition = null
|
||||
startBtn.className = ''
|
||||
startBtn.innerText = 'START'
|
||||
|
||||
timeoutId = null
|
||||
|
||||
intervalId = setInterval(() => {
|
||||
if (
|
||||
app.ui.lastQueueSize === 0 &&
|
||||
!window.recognition &&
|
||||
window._mixlab_speech_synthesis_onend
|
||||
) {
|
||||
start(element, id, startBtn, node)
|
||||
startBtn.innerText = 'STOP'
|
||||
if (intervalId) {
|
||||
clearInterval(intervalId)
|
||||
}
|
||||
}
|
||||
}, 2200)
|
||||
}, 2000)
|
||||
}
|
||||
|
||||
window.recognition.onend = function () {
|
||||
console.log('语音输入结束')
|
||||
}
|
||||
|
||||
window.recognition.onspeechend = function () {
|
||||
console.log('onspeechend')
|
||||
}
|
||||
|
||||
window.recognition.onerror = function (event) {
|
||||
console.log('Error occurred in recognition: ' + event.error)
|
||||
}
|
||||
|
||||
window.recognition.start()
|
||||
}
|
||||
|
||||
app.registerExtension({
|
||||
name: 'Mixlab.audio.SpeechRecognition',
|
||||
async getCustomWidgets (app) {
|
||||
return {
|
||||
AUDIOINPUTMIX (node, inputName, inputData, app) {
|
||||
// console.log('##node', node)
|
||||
const widget = {
|
||||
type: inputData[0], // the type, CHEESE
|
||||
name: inputName, // the name, slice
|
||||
size: [128, 32], // a default size
|
||||
draw (ctx, node, width, y) {},
|
||||
computeSize (...args) {
|
||||
return [128, 32] // a method to compute the current size of the widget
|
||||
},
|
||||
async serializeValue (nodeId, widgetIndex) {
|
||||
let data = getLocalData('_mixlab_speech_recognition')
|
||||
return data[node.id] || 'Hello Mixlab'
|
||||
}
|
||||
}
|
||||
// widget.something = something; // maybe adds stuff to it
|
||||
node.addCustomWidget(widget) // adds it to the node
|
||||
return widget // and returns it.
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
async beforeRegisterNodeDef (nodeType, nodeData, app) {
|
||||
if (nodeType.comfyClass == 'SpeechRecognition') {
|
||||
const orig_nodeCreated = nodeType.prototype.onNodeCreated
|
||||
nodeType.prototype.onNodeCreated = function () {
|
||||
orig_nodeCreated?.apply(this, arguments)
|
||||
|
||||
const sendTo = ComfyWidgets.INT(
|
||||
this,
|
||||
'Send to ChatGPT #',
|
||||
['INT', { default: 0 }],
|
||||
app
|
||||
)
|
||||
// console.log('sendTo',sendTo)
|
||||
|
||||
const widget = {
|
||||
type: 'div',
|
||||
name: 'chatgptdiv',
|
||||
draw (ctx, node, widget_width, y, widget_height) {
|
||||
Object.assign(
|
||||
this.div.style,
|
||||
get_position_style(ctx, widget_width, 78, node.size[1])
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
widget.div = $el('div', {})
|
||||
|
||||
document.body.appendChild(widget.div)
|
||||
|
||||
const inputDiv = (key, placeholder) => {
|
||||
let div = document.createElement('div')
|
||||
const startBtn = document.createElement('button')
|
||||
|
||||
const textArea = document.createElement('textarea')
|
||||
textArea.placeholder = 'speak text'
|
||||
// sendTo.type='range';
|
||||
// sendTo.min=0;
|
||||
// sendTo.max=2000;
|
||||
// sendTo.step=1;
|
||||
// sendTo.className='comfy-multiline-input'
|
||||
|
||||
textArea.className = `${'comfy-multiline-input'} ${placeholder}`
|
||||
|
||||
textArea.style = `margin-top: 14px;
|
||||
height: 44px;`
|
||||
|
||||
div.style = `flex-direction: column;
|
||||
display: flex;
|
||||
margin: 0px 8px 6px;`
|
||||
|
||||
startBtn.style = `
|
||||
margin-top:48px;
|
||||
background-color: var(--comfy-input-bg);
|
||||
border-radius: 8px;
|
||||
border-color: var(--border-color);
|
||||
border-style: solid;
|
||||
color: var(--descrip-text);
|
||||
`
|
||||
|
||||
startBtn.innerText = 'START'
|
||||
|
||||
div.appendChild(startBtn)
|
||||
// div.appendChild(sendTo);
|
||||
div.appendChild(textArea)
|
||||
|
||||
startBtn.addEventListener('click', () => {
|
||||
if (window.recognition) {
|
||||
window.recognition.stop()
|
||||
window.recognition = null
|
||||
startBtn.innerText = 'START'
|
||||
startBtn.className = ''
|
||||
} else {
|
||||
start(textArea, this.id, startBtn, this)
|
||||
startBtn.innerText = 'STOP'
|
||||
}
|
||||
})
|
||||
|
||||
// sendTo.addEventListener('change',()=>{
|
||||
// console.log(sendTo.value)
|
||||
// })
|
||||
|
||||
return div
|
||||
}
|
||||
|
||||
let inputAudio = inputDiv('_mixlab_speech_recognition', 'audio')
|
||||
widget.div.appendChild(inputAudio)
|
||||
|
||||
this.addCustomWidget(widget)
|
||||
|
||||
const onRemoved = this.onRemoved
|
||||
this.onRemoved = () => {
|
||||
inputAudio.remove()
|
||||
widget.div.remove()
|
||||
return onRemoved?.()
|
||||
}
|
||||
|
||||
this.serialize_widgets = true //需要保存参数
|
||||
}
|
||||
|
||||
// const onGraphConfigured=nodeType.prototype.onGraphConfigured;
|
||||
// nodeType.prototype.onGraphConfigured = function (message) {
|
||||
// onGraphConfigured?.apply(this, arguments)
|
||||
// console.log('###SpeechRecognition onGraphConfigured',this,message)
|
||||
// }
|
||||
|
||||
const onExecuted = nodeType.prototype.onExecuted
|
||||
nodeType.prototype.onExecuted = function (message) {
|
||||
onExecuted?.apply(this, arguments)
|
||||
// console.log('this.widgets', this.widgets)
|
||||
|
||||
try {
|
||||
// 是否根据start by 开启
|
||||
let open = message.start_by[0] > 0
|
||||
if (open) {
|
||||
const div = this.widgets.filter(w => w.name == 'chatgptdiv')[0].div
|
||||
const startBtn = div.querySelector('button')
|
||||
let textArea = div.querySelector('textarea')
|
||||
if (open && !window.recognition) {
|
||||
start(textArea, this.id, startBtn, this)
|
||||
startBtn.innerText = 'STOP'
|
||||
} else if (!open && window.recognition) {
|
||||
window.recognition.stop()
|
||||
window.recognition = null
|
||||
startBtn.innerText = 'START'
|
||||
startBtn.className = ''
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('###SpeechRecognition', error)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
async loadedGraphNode (node, app) {
|
||||
if (node.type === 'SpeechRecognition') {
|
||||
let data = getLocalData('_mixlab_speech_recognition')
|
||||
// console.log('_mixlab_speech_recognition', node )
|
||||
let div = node.widgets.filter(f => f.type === 'div')[0]
|
||||
if (div && data[node.id]) {
|
||||
div.div.querySelector('textarea').value = data[node.id]
|
||||
}
|
||||
|
||||
try {
|
||||
let open = node.widgets_values[1] > 0
|
||||
if (open) {
|
||||
const div = node.widgets.filter(w => w.name == 'chatgptdiv')[0].div
|
||||
const startBtn = div.querySelector('button')
|
||||
let textArea = div.querySelector('textarea')
|
||||
if (open && !window.recognition) {
|
||||
start(textArea, node.id, startBtn, node)
|
||||
startBtn.innerText = 'STOP'
|
||||
} else if (!open && window.recognition) {
|
||||
window.recognition.stop()
|
||||
window.recognition = null
|
||||
startBtn.innerText = 'START'
|
||||
startBtn.className = ''
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('###SpeechRecognition', error)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
app.registerExtension({
|
||||
name: 'Mixlab.audio.SpeechSynthesis',
|
||||
async beforeRegisterNodeDef (nodeType, nodeData, app) {
|
||||
if (nodeData.name === 'SpeechSynthesis') {
|
||||
function populate (text) {
|
||||
// console.log('SpeechSynthesis',this.widgets)
|
||||
|
||||
if (this.widgets) {
|
||||
const pos = this.widgets.findIndex(w => w.name === 'text')
|
||||
if (pos !== -1) {
|
||||
for (let i = pos; i < this.widgets.length; i++) {
|
||||
this.widgets[i].onRemove?.()
|
||||
}
|
||||
this.widgets.length = pos
|
||||
}
|
||||
}
|
||||
|
||||
for (let list of text) {
|
||||
const w = ComfyWidgets['STRING'](
|
||||
this,
|
||||
'text',
|
||||
['STRING', { multiline: true }],
|
||||
app
|
||||
).widget
|
||||
w.inputEl.readOnly = true
|
||||
w.inputEl.style.opacity = 0.6
|
||||
w.value = list
|
||||
}
|
||||
|
||||
speakText(text.join('\n'))
|
||||
|
||||
// console.log('ShowTextForGPT',this.widgets.length)
|
||||
requestAnimationFrame(() => {
|
||||
const sz = this.computeSize()
|
||||
if (sz[0] < this.size[0]) {
|
||||
sz[0] = this.size[0]
|
||||
}
|
||||
if (sz[1] < this.size[1]) {
|
||||
sz[1] = this.size[1]
|
||||
}
|
||||
this.onResize?.(sz)
|
||||
app.graph.setDirtyCanvas(true, false)
|
||||
})
|
||||
}
|
||||
|
||||
// When the node is executed we will be sent the input text, display this in the widget
|
||||
const onExecuted = nodeType.prototype.onExecuted
|
||||
nodeType.prototype.onExecuted = function (message) {
|
||||
onExecuted?.apply(this, arguments)
|
||||
populate.call(this, message.text)
|
||||
}
|
||||
|
||||
this.serialize_widgets = true //需要保存参数
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -1,23 +0,0 @@
|
||||
const repoOwner = 'shadowcz007' // 替换为仓库的所有者
|
||||
const repoName = 'comfyui-mixlab-nodes' // 替换为仓库的名称
|
||||
|
||||
const version='v0.1'
|
||||
|
||||
fetch(`https://api.github.com/repos/${repoOwner}/${repoName}/releases/latest`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
const latestVersion = data.tag_name
|
||||
console.log('Latest release version:', latestVersion)
|
||||
if(latestVersion!=version){
|
||||
window.alert(
|
||||
`Please proceed to the official repository to download the latest version.https://github.com/shadowcz007/comfyui-mixlab-nodes/releases/`
|
||||
)
|
||||
window.open(
|
||||
'https://github.com/shadowcz007/comfyui-mixlab-nodes/releases/'
|
||||
)
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error fetching release information:', error)
|
||||
})
|
||||
// #MixCopilot
|
||||
@@ -0,0 +1,42 @@
|
||||
import { app } from '../../../scripts/app.js'
|
||||
|
||||
const repoOwner = 'shadowcz007' // 替换为仓库的所有者
|
||||
const repoName = 'comfyui-mixlab-nodes' // 替换为仓库的名称
|
||||
|
||||
const version = 'v0.8.0'
|
||||
|
||||
fetch(`https://api.github.com/repos/${repoOwner}/${repoName}/releases/latest`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
const latestVersion = data.tag_name
|
||||
console.log('Latest release version:', latestVersion)
|
||||
if (
|
||||
latestVersion &&
|
||||
latestVersion === localStorage.getItem('_mixlab_nodes_vesion')
|
||||
)
|
||||
return
|
||||
if (latestVersion && latestVersion != version) {
|
||||
localStorage.setItem('_mixlab_nodes_vesion', latestVersion)
|
||||
app.ui.dialog.show(`<h4 style="font-size: 18px;">${repoName} <br>
|
||||
Latest release version: ${latestVersion}</h4>
|
||||
<p>Please proceed to the official repository to download the latest version.</p>
|
||||
<a style="color: #2196F3;
|
||||
font-size: 18px;
|
||||
font-weight: 800;
|
||||
letter-spacing: 2px;
|
||||
}"
|
||||
href="https://github.com/shadowcz007/comfyui-mixlab-nodes/releases/">https://github.com/shadowcz007/comfyui-mixlab-nodes/releases</a>
|
||||
`)
|
||||
|
||||
// window.alert(
|
||||
// `Please proceed to the official repository to download the latest version.https://github.com/shadowcz007/comfyui-mixlab-nodes/releases/`
|
||||
// )
|
||||
// window.open(
|
||||
// 'https://github.com/shadowcz007/comfyui-mixlab-nodes/releases/'
|
||||
// )
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error fetching release information:', error)
|
||||
})
|
||||
// #MixCopilot
|
||||
@@ -0,0 +1,277 @@
|
||||
import { app } from '../../../scripts/app.js'
|
||||
// import { api } from '../../../scripts/api.js'
|
||||
import { ComfyWidgets } from '../../../scripts/widgets.js'
|
||||
import { $el } from '../../../scripts/ui.js'
|
||||
|
||||
async function getConfig () {
|
||||
let api_host = `${window.location.hostname}:${window.location.port}`
|
||||
let api_base = ''
|
||||
let url = `${window.location.protocol}//${api_host}${api_base}`
|
||||
|
||||
const res = await fetch(`${url}/mixlab`, {
|
||||
method: 'POST'
|
||||
})
|
||||
return await res.json()
|
||||
}
|
||||
|
||||
function get_position_style (ctx, widget_width, y, node_height) {
|
||||
const MARGIN = 4 // the margin around the html element
|
||||
|
||||
/* Create a transform that deals with all the scrolling and zooming */
|
||||
const elRect = ctx.canvas.getBoundingClientRect()
|
||||
const transform = new DOMMatrix()
|
||||
.scaleSelf(
|
||||
elRect.width / ctx.canvas.width,
|
||||
elRect.height / ctx.canvas.height
|
||||
)
|
||||
.multiplySelf(ctx.getTransform())
|
||||
.translateSelf(MARGIN, MARGIN + y)
|
||||
|
||||
return {
|
||||
transformOrigin: '0 0',
|
||||
transform: transform,
|
||||
left: `0`,
|
||||
top: `0`,
|
||||
cursor: 'pointer',
|
||||
position: 'absolute',
|
||||
maxWidth: `${widget_width - MARGIN * 2}px`,
|
||||
// maxHeight: `${node_height - MARGIN * 2}px`, // we're assuming we have the whole height of the node
|
||||
width: `${widget_width - MARGIN * 2}px`,
|
||||
// height: `${node_height * 0.3 - MARGIN * 2}px`,
|
||||
// background: '#EEEEEE',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
// alignItems: 'center',
|
||||
justifyContent: 'space-around'
|
||||
}
|
||||
}
|
||||
|
||||
const getLocalData = key => {
|
||||
let data = {}
|
||||
try {
|
||||
data = JSON.parse(localStorage.getItem(key)) || {}
|
||||
} catch (error) {
|
||||
return {}
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
app.registerExtension({
|
||||
name: 'Mixlab.GPT.ChatGPTOpenAI',
|
||||
async getCustomWidgets (app) {
|
||||
return {
|
||||
KEY (node, inputName, inputData, app) {
|
||||
console.log('##inputData', inputData)
|
||||
const widget = {
|
||||
type: inputData[0], // the type, CHEESE
|
||||
name: inputName, // the name, slice
|
||||
size: [128, 32], // a default size
|
||||
draw (ctx, node, width, y) {},
|
||||
computeSize (...args) {
|
||||
return [128,32] // a method to compute the current size of the widget
|
||||
},
|
||||
async serializeValue (nodeId, widgetIndex) {
|
||||
let data = getLocalData('_mixlab_api_key')
|
||||
return data[node.id] || 'by Mixlab'
|
||||
}
|
||||
}
|
||||
// widget.something = something; // maybe adds stuff to it
|
||||
node.addCustomWidget(widget) // adds it to the node
|
||||
return widget // and returns it.
|
||||
},
|
||||
URL (node, inputName, inputData, app) {
|
||||
// console.log('node', inputName, inputData[0])
|
||||
const widget = {
|
||||
type: inputData[0], // the type, CHEESE
|
||||
name: inputName, // the name, slice
|
||||
size: [128, 32], // a default size
|
||||
draw (ctx, node, width, y) {
|
||||
// a method to draw the widget (ctx is a CanvasRenderingContext2D)
|
||||
},
|
||||
computeSize (...args) {
|
||||
return [128, 32] // a method to compute the current size of the widget
|
||||
},
|
||||
async serializeValue (nodeId, widgetIndex) {
|
||||
let data = getLocalData('_mixlab_api_url')
|
||||
return data[node.id] || 'https://api.openai.com/v1'
|
||||
}
|
||||
}
|
||||
// widget.something = something; // maybe adds stuff to it
|
||||
node.addCustomWidget(widget) // adds it to the node
|
||||
return widget // and returns it.
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
async beforeRegisterNodeDef (nodeType, nodeData, app) {
|
||||
if (nodeType.comfyClass == 'ChatGPTOpenAI') {
|
||||
const orig_nodeCreated = nodeType.prototype.onNodeCreated
|
||||
nodeType.prototype.onNodeCreated = function () {
|
||||
orig_nodeCreated?.apply(this, arguments)
|
||||
|
||||
const api_key = this.widgets.filter(w => w.name == 'api_key')[0]
|
||||
const api_url = this.widgets.filter(w => w.name == 'api_url')[0]
|
||||
|
||||
console.log('ChatGPTOpenAI nodeData', this.widgets)
|
||||
|
||||
const widget = {
|
||||
type: 'div',
|
||||
name: 'chatgptdiv',
|
||||
draw (ctx, node, widget_width, y, widget_height) {
|
||||
Object.assign(
|
||||
this.div.style,
|
||||
get_position_style(ctx, widget_width, api_key.y, node.size[1])
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
widget.div = $el('div', {})
|
||||
|
||||
document.body.appendChild(widget.div)
|
||||
|
||||
const inputDiv = (key, placeholder) => {
|
||||
let div = document.createElement('div')
|
||||
const ip = document.createElement('input')
|
||||
ip.type = placeholder === 'Key' ? 'password' : 'text'
|
||||
ip.className = `${'comfy-multiline-input'} ${placeholder}`
|
||||
div.style = `display: flex;
|
||||
align-items: center;
|
||||
margin: 6px 8px;
|
||||
margin-top: 0;`
|
||||
ip.placeholder = placeholder
|
||||
ip.value = placeholder
|
||||
|
||||
ip.style = `margin-left: 24px;
|
||||
outline: none;
|
||||
border: none;
|
||||
padding: 4px;width: 100%;`
|
||||
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, key)
|
||||
})
|
||||
return div
|
||||
}
|
||||
|
||||
let inputKey = inputDiv('_mixlab_api_key', 'Key')
|
||||
let inputUrl = inputDiv('_mixlab_api_url', 'URL')
|
||||
|
||||
widget.div.appendChild(inputKey)
|
||||
widget.div.appendChild(inputUrl)
|
||||
|
||||
this.addCustomWidget(widget)
|
||||
|
||||
const onRemoved = this.onRemoved
|
||||
this.onRemoved = () => {
|
||||
inputUrl.remove()
|
||||
inputKey.remove()
|
||||
widget.div.remove()
|
||||
return onRemoved?.()
|
||||
}
|
||||
|
||||
this.serialize_widgets = true //需要保存参数
|
||||
}
|
||||
}
|
||||
},
|
||||
async loadedGraphNode (node, app) {
|
||||
// Fires every time a node is constructed
|
||||
// You can modify widgets/add handlers/etc here
|
||||
|
||||
if (node.type === 'ChatGPTOpenAI') {
|
||||
let widget = node.widgets.filter(w => w.div)[0]
|
||||
|
||||
let apiKey = getLocalData('_mixlab_api_key'),
|
||||
url = getLocalData('_mixlab_api_url')
|
||||
|
||||
let id = node.id
|
||||
|
||||
// console.log('ChatGPTOpenAI serialize_widgets', this)
|
||||
|
||||
widget.div.querySelector('.Key').value = apiKey[id] || 'by Mixlab'
|
||||
widget.div.querySelector('.URL').value =
|
||||
url[id] || 'https://api.openai.com/v1'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
app.registerExtension({
|
||||
name: 'Mixlab.GPT.ShowTextForGPT',
|
||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||
if (nodeData.name === "ShowTextForGPT") {
|
||||
function populate(text) {
|
||||
if (this.widgets) {
|
||||
|
||||
const pos = this.widgets.findIndex((w) => w.name === "text");
|
||||
if (pos !== -1) {
|
||||
for (let i = pos; i < this.widgets.length; i++) {
|
||||
this.widgets[i].onRemove?.();
|
||||
}
|
||||
this.widgets.length = pos;
|
||||
}
|
||||
}
|
||||
// console.log('ShowTextForGPT',text)
|
||||
for (let list of text) {
|
||||
const w = ComfyWidgets["STRING"](this, "text", ["STRING", { multiline: true }], app).widget;
|
||||
w.inputEl.readOnly = true;
|
||||
w.inputEl.style.opacity = 0.6;
|
||||
|
||||
try {
|
||||
let data=JSON.parse(list);
|
||||
data=Array.from(data,d=>{
|
||||
return {
|
||||
...d,
|
||||
content:decodeURIComponent(d.content)
|
||||
}
|
||||
})
|
||||
list=JSON.stringify(data,null,2)
|
||||
} catch (error) {
|
||||
// console.log(error)
|
||||
}
|
||||
|
||||
w.value =list;
|
||||
|
||||
}
|
||||
// console.log('ShowTextForGPT',this.widgets.length)
|
||||
requestAnimationFrame(() => {
|
||||
const sz = this.computeSize();
|
||||
if (sz[0] < this.size[0]) {
|
||||
sz[0] = this.size[0];
|
||||
}
|
||||
if (sz[1] < this.size[1]) {
|
||||
sz[1] = this.size[1];
|
||||
}
|
||||
this.onResize?.(sz);
|
||||
app.graph.setDirtyCanvas(true, false);
|
||||
});
|
||||
}
|
||||
|
||||
// When the node is executed we will be sent the input text, display this in the widget
|
||||
const onExecuted = nodeType.prototype.onExecuted;
|
||||
nodeType.prototype.onExecuted = function (message) {
|
||||
onExecuted?.apply(this, arguments);
|
||||
populate.call(this, message.text);
|
||||
};
|
||||
|
||||
const onConfigure = nodeType.prototype.onConfigure;
|
||||
nodeType.prototype.onConfigure = function () {
|
||||
onConfigure?.apply(this, arguments);
|
||||
if (this.widgets_values?.length) {
|
||||
|
||||
populate.call(this, this.widgets_values);
|
||||
}
|
||||
};
|
||||
|
||||
this.serialize_widgets = true //需要保存参数
|
||||
|
||||
}
|
||||
|
||||
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,444 @@
|
||||
import { app } from '../../../scripts/app.js'
|
||||
import { api } from '../../../scripts/api.js'
|
||||
import { ComfyWidgets } from '../../../scripts/widgets.js'
|
||||
import { $el } from '../../../scripts/ui.js'
|
||||
|
||||
async function uploadImage (blob, fileType = '.svg', filename) {
|
||||
// const blob = await (await fetch(src)).blob();
|
||||
const body = new FormData()
|
||||
body.append(
|
||||
'image',
|
||||
new File([blob], (filename || new Date().getTime()) + fileType)
|
||||
)
|
||||
|
||||
const resp = await api.fetchApi('/upload/image', {
|
||||
method: 'POST',
|
||||
body
|
||||
})
|
||||
|
||||
// console.log(resp)
|
||||
let data = await resp.json()
|
||||
let { name, subfolder } = data
|
||||
let src = api.apiURL(
|
||||
`/view?filename=${encodeURIComponent(
|
||||
name
|
||||
)}&type=input&subfolder=${subfolder}${app.getPreviewFormatParam()}${app.getRandParam()}`
|
||||
)
|
||||
|
||||
return src
|
||||
}
|
||||
|
||||
function base64ToBlobFromURL (base64URL, contentType) {
|
||||
return fetch(base64URL).then(response => response.blob())
|
||||
}
|
||||
|
||||
function getContentTypeFromBase64 (base64Data) {
|
||||
const regex = /^data:(.+);base64,/
|
||||
const matches = base64Data.match(regex)
|
||||
if (matches && matches.length >= 2) {
|
||||
return matches[1]
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
// 示例用法
|
||||
// const base64Data = 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAAAAAAAD/...'; // 替换为实际的base64图片数据
|
||||
// const contentType = getContentTypeFromBase64(base64Data);
|
||||
// console.log(contentType);
|
||||
|
||||
// // 示例用法
|
||||
// const base64Data = '...'; // 替换为实际的base64图片数据
|
||||
// const contentType = 'image/jpeg'; // 替换为实际的图片类型
|
||||
|
||||
// const blob = base64ToBlob(base64Data, contentType);
|
||||
// console.log(blob);
|
||||
|
||||
function get_position_style (ctx, widget_width, y, node_height) {
|
||||
const MARGIN = 4 // the margin around the html element
|
||||
|
||||
/* Create a transform that deals with all the scrolling and zooming */
|
||||
const elRect = ctx.canvas.getBoundingClientRect()
|
||||
const transform = new DOMMatrix()
|
||||
.scaleSelf(
|
||||
elRect.width / ctx.canvas.width,
|
||||
elRect.height / ctx.canvas.height
|
||||
)
|
||||
.multiplySelf(ctx.getTransform())
|
||||
.translateSelf(MARGIN, MARGIN + y)
|
||||
|
||||
return {
|
||||
transformOrigin: '0 0',
|
||||
transform: transform,
|
||||
left: `0`,
|
||||
top: `0`,
|
||||
cursor: 'pointer',
|
||||
position: 'absolute',
|
||||
maxWidth: `${widget_width - MARGIN * 2}px`,
|
||||
// maxHeight: `${node_height - MARGIN * 2}px`, // we're assuming we have the whole height of the node
|
||||
width: `${widget_width - MARGIN * 2}px`,
|
||||
// height: `${node_height * 0.3 - MARGIN * 2}px`,
|
||||
// background: '#EEEEEE',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
// alignItems: 'center',
|
||||
justifyContent: 'space-around'
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
function createImage (url) {
|
||||
let im = new Image()
|
||||
return new Promise((res, rej) => {
|
||||
im.onload = () => res(im)
|
||||
im.src = url
|
||||
})
|
||||
}
|
||||
|
||||
const parseImage = url => {
|
||||
return new Promise((res, rej) => {
|
||||
fetch(url)
|
||||
.then(response => response.blob())
|
||||
.then(blob => {
|
||||
const reader = new FileReader()
|
||||
reader.onloadend = () => {
|
||||
const base64data = reader.result
|
||||
res(base64data)
|
||||
// 在这里可以将base64数据用于进一步处理或显示图片
|
||||
}
|
||||
reader.readAsDataURL(blob)
|
||||
})
|
||||
.catch(error => {
|
||||
console.log('发生错误:', error)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const parseSvg = async svgContent => {
|
||||
let scale = 2
|
||||
// 创建一个临时的DOM元素来解析SVG
|
||||
const tempContainer = document.createElement('div')
|
||||
tempContainer.innerHTML = svgContent
|
||||
|
||||
// 提取SVG元素
|
||||
const svgElement = tempContainer.querySelector('svg')
|
||||
if (!svgElement) return
|
||||
// 获取SVG中 rect元素
|
||||
var rectElements = svgElement?.querySelectorAll('rect') || []
|
||||
// console.log(rectElements,svgElement)
|
||||
// 定义一个数组来存储处理后的数据
|
||||
var data = []
|
||||
|
||||
Array.from(rectElements, (rectElement, i) => {
|
||||
// 获取rect元素的属性值
|
||||
var x = ~~(rectElement.getAttribute('x') || 0)
|
||||
var y = ~~(rectElement.getAttribute('y') || 0)
|
||||
var width = ~~rectElement.getAttribute('width')
|
||||
var height = ~~rectElement.getAttribute('height')
|
||||
// console.log('rectElements',rectElement,x,y,width,height)
|
||||
if (x != undefined && y != undefined && width && height) {
|
||||
// 创建一个新的canvas元素
|
||||
var canvas = document.createElement('canvas')
|
||||
canvas.width = width
|
||||
canvas.height = height
|
||||
var context = canvas.getContext('2d')
|
||||
|
||||
// 填充颜色到canvas
|
||||
var fill = rectElement.getAttribute('fill')
|
||||
context.fillStyle = fill
|
||||
context.fillRect(0, 0, width, height)
|
||||
|
||||
// 将canvas转换为base64格式
|
||||
var base64 = canvas.toDataURL()
|
||||
|
||||
// 将数据转化为指定的JSON格式
|
||||
|
||||
var rectData = {
|
||||
x: parseInt(x),
|
||||
y: parseInt(y),
|
||||
width: parseInt(width),
|
||||
height: parseInt(height),
|
||||
z_index: i + 1,
|
||||
scale_option: 'width',
|
||||
image: base64,
|
||||
mask: base64,
|
||||
type: 'base64',
|
||||
_t: 'rect'
|
||||
}
|
||||
|
||||
// 将处理后的数据添加到数组中
|
||||
data.push(rectData)
|
||||
}
|
||||
})
|
||||
|
||||
var svgWidth = svgElement.getAttribute('width')
|
||||
var svgHeight = svgElement.getAttribute('height')
|
||||
|
||||
if (!(svgWidth && svgHeight)) {
|
||||
// viewBox
|
||||
let viewBox = svgElement.viewBox.baseVal
|
||||
|
||||
svgWidth = viewBox.width
|
||||
svgHeight = viewBox.height
|
||||
} else {
|
||||
try {
|
||||
svgWidth = ~~svgWidth.replace('px', '')
|
||||
svgHeight = ~~svgHeight.replace('px', '')
|
||||
} catch (error) {}
|
||||
}
|
||||
|
||||
// 创建一个新的canvas元素
|
||||
var canvas = document.createElement('canvas')
|
||||
canvas.width = svgWidth
|
||||
canvas.height = svgHeight
|
||||
var context = canvas.getContext('2d')
|
||||
// 绘制SVG到canvas
|
||||
var svgString = new XMLSerializer().serializeToString(svgElement)
|
||||
var DOMURL = window.URL || window.webkitURL || window
|
||||
|
||||
var svgBlob = new Blob([svgString], { type: 'image/svg+xml;charset=utf-8' })
|
||||
var url = DOMURL.createObjectURL(svgBlob)
|
||||
|
||||
let img = await createImage(url)
|
||||
context.drawImage(img, 0, 0)
|
||||
|
||||
let base64 = canvas.toDataURL()
|
||||
|
||||
var rectData = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: parseInt(svgWidth),
|
||||
height: parseInt(svgHeight),
|
||||
z_index: 0,
|
||||
scale_option: 'width',
|
||||
image: base64,
|
||||
mask: base64,
|
||||
type: 'base64',
|
||||
_t: 'canvas'
|
||||
}
|
||||
data.push(rectData)
|
||||
|
||||
// 打印处理后的数据
|
||||
console.log('layers', { data, image: base64, svgElement })
|
||||
return { data, image: base64, svgElement }
|
||||
}
|
||||
|
||||
function exportModelViewerImage (
|
||||
modelViewer,
|
||||
width,
|
||||
height,
|
||||
format = 'image/png',
|
||||
quality = 1.0
|
||||
) {
|
||||
const canvas = document.createElement('canvas')
|
||||
canvas.width = width
|
||||
canvas.height = height
|
||||
const context = canvas.getContext('2d')
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
context.drawImage(modelViewer, 0, 0, width, height)
|
||||
|
||||
resolve(canvas.toDataURL(format, quality))
|
||||
})
|
||||
}
|
||||
|
||||
app.registerExtension({
|
||||
name: 'Mixlab.image.SvgImage',
|
||||
async getCustomWidgets (app) {
|
||||
return {
|
||||
SVG (node, inputName, inputData, app) {
|
||||
// console.log('##node', node, inputName, inputData)
|
||||
const widget = {
|
||||
type: inputData[0], // the type, CHEESE
|
||||
name: inputName, // the name, slice
|
||||
size: [128, 88], // a default size
|
||||
draw (ctx, node, width, y) {},
|
||||
computeSize (...args) {
|
||||
return [128, 88] // a method to compute the current size of the widget
|
||||
},
|
||||
async serializeValue (nodeId, widgetIndex) {
|
||||
let d = getLocalData('_mixlab_svg_image')
|
||||
// console.log('serializeValue',d)
|
||||
if (d) {
|
||||
let url = d[node.id]
|
||||
let dt = await fetch(url)
|
||||
let svgStr = await dt.text()
|
||||
const { data, image } = (await parseSvg(svgStr)) || {}
|
||||
// console.log(data, image)
|
||||
return JSON.parse(JSON.stringify({ data, image }))
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// console.log('##node',node.serialize)
|
||||
// widget.something = something; // maybe adds stuff to it
|
||||
node.addCustomWidget(widget) // adds it to the node
|
||||
|
||||
return widget // and returns it.
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
async beforeRegisterNodeDef (nodeType, nodeData, app) {
|
||||
if (nodeType.comfyClass == 'SvgImage') {
|
||||
const orig_nodeCreated = nodeType.prototype.onNodeCreated
|
||||
nodeType.prototype.onNodeCreated = async function () {
|
||||
orig_nodeCreated?.apply(this, arguments)
|
||||
|
||||
const uploadWidget = this.widgets.filter(w => w.name == 'upload')[0]
|
||||
// console.log('SvgImage nodeData',await uploadWidget.serializeValue())
|
||||
|
||||
const widget = {
|
||||
type: 'div',
|
||||
name: 'upload-preview',
|
||||
draw (ctx, node, widget_width, y, widget_height) {
|
||||
Object.assign(
|
||||
this.div.style,
|
||||
get_position_style(ctx, widget_width, 44, node.size[1])
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
widget.div = $el('div', {})
|
||||
|
||||
document.body.appendChild(widget.div)
|
||||
|
||||
const inputDiv = (key, placeholder, svgContainer) => {
|
||||
let div = document.createElement('div')
|
||||
const ip = document.createElement('input')
|
||||
ip.type = 'file'
|
||||
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: 60%;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)
|
||||
|
||||
let that = this
|
||||
|
||||
ip.addEventListener('change', event => {
|
||||
const file = event.target.files[0]
|
||||
const reader = new FileReader()
|
||||
|
||||
// 读取文件内容
|
||||
reader.onload = async e => {
|
||||
const svgContent = e.target.result
|
||||
|
||||
var blob = new Blob([svgContent], { type: 'image/svg+xml' })
|
||||
let url = await uploadImage(blob)
|
||||
// console.log(url)
|
||||
const { svgElement, data, image } = await parseSvg(svgContent)
|
||||
// 将提取的SVG元素显示在页面上
|
||||
let dd = getLocalData(key)
|
||||
dd[that.id] = url
|
||||
setLocalDataOfWin(key, dd)
|
||||
// console.log(this.id, ip.value.trim())
|
||||
|
||||
svgElement.style = `width: 90%;padding: 5%;height: auto;`
|
||||
// 将提取的SVG元素显示在页面上
|
||||
|
||||
svgContainer.innerHTML = ''
|
||||
svgContainer.appendChild(svgElement)
|
||||
let h = ~~getComputedStyle(svgElement).height.replace('px', '')
|
||||
if (that.size && that.size[1] < h) {
|
||||
that.setSize([that.size[0], that.size[1] + h])
|
||||
app.canvas.draw(true, true)
|
||||
}
|
||||
// console.log(that.size,~~getComputedStyle(svgElement).height.replace('px',''))
|
||||
|
||||
uploadWidget.value = await uploadWidget.serializeValue()
|
||||
}
|
||||
|
||||
// 以文本形式读取文件
|
||||
reader.readAsText(file)
|
||||
})
|
||||
return div
|
||||
}
|
||||
|
||||
let svg = document.createElement('div')
|
||||
svg.className = 'preview'
|
||||
svg.style = `background:#eee;margin-top: 12px;`
|
||||
|
||||
let upload = inputDiv('_mixlab_svg_image', 'Svg', svg)
|
||||
|
||||
widget.div.appendChild(upload)
|
||||
widget.div.appendChild(svg)
|
||||
this.addCustomWidget(widget)
|
||||
|
||||
const onRemoved = this.onRemoved
|
||||
this.onRemoved = () => {
|
||||
upload.remove()
|
||||
svg.remove()
|
||||
widget.div.remove()
|
||||
return onRemoved?.()
|
||||
}
|
||||
|
||||
if (this.onResize) {
|
||||
this.onResize(this.size)
|
||||
}
|
||||
|
||||
this.serialize_widgets = true //需要保存参数
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
},
|
||||
async loadedGraphNode (node, app) {
|
||||
// Fires every time a node is constructed
|
||||
// You can modify widgets/add handlers/etc here
|
||||
const sleep = (t = 1000) => {
|
||||
return new Promise((res, rej) => {
|
||||
setTimeout(() => res(1), t)
|
||||
})
|
||||
}
|
||||
if (node.type === 'SvgImage') {
|
||||
// await sleep(0)
|
||||
let widget = node.widgets.filter(w => w.name === 'upload-preview')[0]
|
||||
|
||||
let dd = getLocalData('_mixlab_svg_image')
|
||||
|
||||
let id = node.id
|
||||
console.log('SvgImage load', node.widgets[0], node.widgets)
|
||||
if (!dd[id]) return
|
||||
let dt = await fetch(dd[id])
|
||||
let svgStr = await dt.text()
|
||||
|
||||
const { svgElement, data, image } = await parseSvg(svgStr)
|
||||
svgElement.style = `width: 90%;padding: 5%;height:auto`
|
||||
// 将提取的SVG元素显示在页面上
|
||||
|
||||
widget.div.querySelector('.preview').innerHTML = ''
|
||||
widget.div.querySelector('.preview').appendChild(svgElement)
|
||||
|
||||
const uploadWidget = node.widgets.filter(w => w.name == 'upload')[0]
|
||||
uploadWidget.value = await uploadWidget.serializeValue()
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,611 @@
|
||||
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}px`,
|
||||
// height: `${node_height * 0.3 - MARGIN * 2}px`,
|
||||
// background: '#EEEEEE',
|
||||
display: 'flex',
|
||||
// flexDirection: 'column',
|
||||
// alignItems: 'center',
|
||||
justifyContent: 'space-around'
|
||||
}
|
||||
}
|
||||
|
||||
const getLocalData = key => {
|
||||
let data = {}
|
||||
try {
|
||||
data = JSON.parse(localStorage.getItem(key)) || {}
|
||||
} catch (error) {
|
||||
return {}
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
function createImage (url) {
|
||||
let im = new Image()
|
||||
return new Promise((res, rej) => {
|
||||
im.onload = () => res(im)
|
||||
im.src = url
|
||||
})
|
||||
}
|
||||
|
||||
const parseSvg = async svgContent => {
|
||||
// 创建一个临时的DOM元素来解析SVG
|
||||
const tempContainer = document.createElement('div')
|
||||
tempContainer.innerHTML = svgContent
|
||||
|
||||
// 提取SVG元素
|
||||
const svgElement = tempContainer.querySelector('svg')
|
||||
if (!svgElement) return
|
||||
// 获取SVG中 rect元素
|
||||
var rectElements = svgElement?.querySelectorAll('rect') || []
|
||||
// console.log(rectElements,svgElement)
|
||||
// 定义一个数组来存储处理后的数据
|
||||
var data = []
|
||||
|
||||
Array.from(rectElements, (rectElement, i) => {
|
||||
// 获取rect元素的属性值
|
||||
var x = ~~(rectElement.getAttribute('x') || 0)
|
||||
var y = ~~(rectElement.getAttribute('y') || 0)
|
||||
var width = ~~rectElement.getAttribute('width')
|
||||
var height = ~~rectElement.getAttribute('height')
|
||||
// console.log('rectElements',rectElement,x,y,width,height)
|
||||
if (x != undefined && y != undefined && width && height) {
|
||||
// 创建一个新的canvas元素
|
||||
var canvas = document.createElement('canvas')
|
||||
canvas.width = width
|
||||
canvas.height = height
|
||||
var context = canvas.getContext('2d')
|
||||
|
||||
// 填充颜色到canvas
|
||||
var fill = rectElement.getAttribute('fill')
|
||||
context.fillStyle = fill
|
||||
context.fillRect(0, 0, width, height)
|
||||
|
||||
// 将canvas转换为base64格式
|
||||
var base64 = canvas.toDataURL()
|
||||
|
||||
// 将数据转化为指定的JSON格式
|
||||
|
||||
var rectData = {
|
||||
x: parseInt(x),
|
||||
y: parseInt(y),
|
||||
width: parseInt(width),
|
||||
height: parseInt(height),
|
||||
z_index: i + 1,
|
||||
scale_option: 'width',
|
||||
image: base64,
|
||||
mask: base64,
|
||||
type: 'base64',
|
||||
_t: 'rect'
|
||||
}
|
||||
|
||||
// 将处理后的数据添加到数组中
|
||||
data.push(rectData)
|
||||
}
|
||||
})
|
||||
|
||||
var svgWidth = svgElement.getAttribute('width')
|
||||
var svgHeight = svgElement.getAttribute('height')
|
||||
|
||||
if (!(svgWidth && svgHeight)) {
|
||||
// viewBox
|
||||
let viewBox = svgElement.viewBox.baseVal
|
||||
|
||||
svgWidth = viewBox.width
|
||||
svgHeight = viewBox.height
|
||||
}
|
||||
|
||||
// 创建一个新的canvas元素
|
||||
var canvas = document.createElement('canvas')
|
||||
canvas.width = svgWidth
|
||||
canvas.height = svgHeight
|
||||
var context = canvas.getContext('2d')
|
||||
// 绘制SVG到canvas
|
||||
var svgString = new XMLSerializer().serializeToString(svgElement)
|
||||
var DOMURL = window.URL || window.webkitURL || window
|
||||
|
||||
var svgBlob = new Blob([svgString], { type: 'image/svg+xml;charset=utf-8' })
|
||||
var url = DOMURL.createObjectURL(svgBlob)
|
||||
|
||||
let img = await createImage(url)
|
||||
context.drawImage(img, 0, 0)
|
||||
|
||||
let base64 = canvas.toDataURL()
|
||||
|
||||
var rectData = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: parseInt(svgWidth),
|
||||
height: parseInt(svgHeight),
|
||||
z_index: 0,
|
||||
scale_option: 'width',
|
||||
image: base64,
|
||||
mask: base64,
|
||||
type: 'base64',
|
||||
_t: 'canvas'
|
||||
}
|
||||
data.push(rectData)
|
||||
|
||||
// 打印处理后的数据
|
||||
console.log('layers', { data, image: base64, svgElement })
|
||||
return { data, image: base64, svgElement }
|
||||
}
|
||||
|
||||
async function setArea (cw, ch, topBase64, base64, data, fn) {
|
||||
let displayHeight = Math.round(window.screen.availHeight * 0.8)
|
||||
let div = document.createElement('div')
|
||||
div.innerHTML = `
|
||||
<div id='ml_overlay' style='position: absolute;top:0;background: #251f1fc4;
|
||||
height: 100vh;
|
||||
z-index:999999;
|
||||
width: 100%;'>
|
||||
<img id='ml_video' style='position: absolute;
|
||||
height: ${displayHeight}px;user-select: none;
|
||||
-webkit-user-drag: none;
|
||||
outline: 2px solid #eaeaea;
|
||||
box-shadow: 8px 9px 17px #575757;' />
|
||||
<div id='ml_selection' style='position: absolute;
|
||||
border: 2px dashed red;
|
||||
pointer-events: none;
|
||||
background-image: url("${topBase64}");
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
'></div>
|
||||
<div class="mx_close"> X </div>
|
||||
</div>`
|
||||
// document.body.querySelector('#ml_overlay')
|
||||
document.body.appendChild(div)
|
||||
|
||||
// let canvas = document.createElement('canvas')
|
||||
// canvas.width = cw
|
||||
// canvas.height = ch
|
||||
|
||||
let img = div.querySelector('#ml_video')
|
||||
// let overlay = div.querySelector('#ml_overlay')
|
||||
let selection = div.querySelector('#ml_selection')
|
||||
let close = div.querySelector('.mx_close')
|
||||
let startX, startY, endX, endY
|
||||
let start = false
|
||||
let setDone = false
|
||||
// Set video source
|
||||
img.src = base64
|
||||
// canvas.toDataURL();
|
||||
close.style = `cursor: pointer;
|
||||
position: fixed;
|
||||
left: 12px;
|
||||
top: 12px;
|
||||
z-index: 99999999;
|
||||
background: black;
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
text-align: center;
|
||||
line-height: 44px;`
|
||||
|
||||
// init area
|
||||
// const data = getSetAreaData()
|
||||
let x = 0,
|
||||
y = 0,
|
||||
width = (cw * displayHeight) / ch,
|
||||
height = displayHeight
|
||||
|
||||
let imgWidth = cw
|
||||
let imgHeight = ch
|
||||
|
||||
if (data && data.width > 0 && data.height > 0) {
|
||||
// 相同尺寸窗口,恢复选区
|
||||
x = (width * data.x) / imgWidth
|
||||
y = (height * data.y) / imgHeight
|
||||
width = (width * data.width) / imgWidth
|
||||
height = (height * data.height) / imgHeight
|
||||
}
|
||||
|
||||
selection.style.left = x + 'px'
|
||||
selection.style.top = y + 'px'
|
||||
selection.style.width = width + 'px'
|
||||
selection.style.height = height + 'px'
|
||||
|
||||
// Add mouse events
|
||||
img.addEventListener('mousedown', startSelection)
|
||||
img.addEventListener('mousemove', updateSelection)
|
||||
img.addEventListener('mouseup', endSelection)
|
||||
|
||||
const removeDiv = () => {
|
||||
div.remove()
|
||||
close.removeEventListener('click', removeDiv)
|
||||
img.removeEventListener('mousedown', startSelection)
|
||||
img.removeEventListener('mousemove', updateSelection)
|
||||
img.removeEventListener('mouseup', endSelection)
|
||||
img.removeEventListener('mousedown', setDoneCheck)
|
||||
}
|
||||
close.addEventListener('click', removeDiv)
|
||||
|
||||
const setDoneCheck = event => {
|
||||
console.log(setDone)
|
||||
if (setDone) {
|
||||
img.addEventListener('mousedown', startSelection)
|
||||
img.addEventListener('mousemove', updateSelection)
|
||||
img.addEventListener('mouseup', endSelection)
|
||||
setDone = false
|
||||
start = false
|
||||
startX = event.clientX
|
||||
startY = event.clientY
|
||||
}
|
||||
}
|
||||
img.addEventListener('mousedown', setDoneCheck)
|
||||
|
||||
function remove () {
|
||||
img.removeEventListener('mousedown', startSelection)
|
||||
img.removeEventListener('mousemove', updateSelection)
|
||||
img.removeEventListener('mouseup', endSelection)
|
||||
setDone = true
|
||||
// div.remove()
|
||||
}
|
||||
|
||||
function startSelection (event) {
|
||||
if (start == false) {
|
||||
startX = event.clientX
|
||||
startY = event.clientY
|
||||
updateSelection(event)
|
||||
start = true
|
||||
} else {
|
||||
}
|
||||
}
|
||||
|
||||
function updateSelection (event) {
|
||||
endX = event.clientX
|
||||
endY = event.clientY
|
||||
|
||||
// Calculate width, height, and coordinates
|
||||
let width = Math.abs(endX - startX)
|
||||
let height = Math.abs(endY - startY)
|
||||
let left = Math.min(startX, endX)
|
||||
let top = Math.min(startY, endY)
|
||||
|
||||
// Set selection style
|
||||
selection.style.left = left + 'px'
|
||||
selection.style.top = top + 'px'
|
||||
selection.style.width = width + 'px'
|
||||
selection.style.height = height + 'px'
|
||||
}
|
||||
|
||||
function endSelection (event) {
|
||||
endX = event.clientX
|
||||
endY = event.clientY
|
||||
|
||||
// 获取img元素的真实宽度和高度
|
||||
let imgWidth = img.naturalWidth
|
||||
let imgHeight = img.naturalHeight
|
||||
|
||||
// 换算起始坐标
|
||||
let realStartX = (startX / img.offsetWidth) * imgWidth
|
||||
let realStartY = (startY / img.offsetHeight) * imgHeight
|
||||
|
||||
// 换算起始坐标
|
||||
let realEndX = (endX / img.offsetWidth) * imgWidth
|
||||
let realEndY = (endY / img.offsetHeight) * imgHeight
|
||||
|
||||
startX = realStartX
|
||||
startY = realStartY
|
||||
endX = realEndX
|
||||
endY = realEndY
|
||||
// Calculate width, height, and coordinates
|
||||
let width = Math.round(Math.abs(endX - startX))
|
||||
let height = Math.round(Math.abs(endY - startY))
|
||||
let left = Math.round(Math.min(startX, endX))
|
||||
let top = Math.round(Math.min(startY, endY))
|
||||
|
||||
if (width <= 0 && height <= 0) return remove()
|
||||
|
||||
if (fn) fn(left, top, width, height)
|
||||
|
||||
remove()
|
||||
}
|
||||
}
|
||||
|
||||
app.registerExtension({
|
||||
name: 'Mixlab.layer.ShowLayer',
|
||||
async getCustomWidgets (app) {
|
||||
return {
|
||||
EDIT (node, inputName, inputData, app) {
|
||||
// console.log('EditLayer##node', node,inputName, inputData)
|
||||
const widget = {
|
||||
type: inputData[0], // the type, CHEESE
|
||||
name: inputName, // the name, slice
|
||||
size: [128, 44], // a default size
|
||||
draw (ctx, node, widget_width, y, widget_height) {
|
||||
// console.log('EditLayer', this)
|
||||
if (this.input)
|
||||
Object.assign(
|
||||
this.input.style,
|
||||
get_position_style(ctx, widget_width, 32, node.size[1])
|
||||
)
|
||||
},
|
||||
computeSize (...args) {
|
||||
return [128, 44] // a method to compute the current size of the widget
|
||||
},
|
||||
async serializeValue (nodeId, widgetIndex) {
|
||||
let d = getLocalData('_mixlab_edit_layer')
|
||||
// console.log('EditLayer',d[node.id])
|
||||
return d[node.id]
|
||||
}
|
||||
}
|
||||
// widget.something = something; // maybe adds stuff to it
|
||||
node.addCustomWidget(widget) // adds it to the node
|
||||
return widget // and returns it.
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
async beforeRegisterNodeDef (nodeType, nodeData, app) {
|
||||
if (nodeType.comfyClass == 'ShowLayer') {
|
||||
const orig_nodeCreated = nodeType.prototype.onNodeCreated
|
||||
nodeType.prototype.onNodeCreated = async function () {
|
||||
orig_nodeCreated?.apply(this, arguments)
|
||||
|
||||
const findNode = nodeId => {
|
||||
let node = app.graph._nodes_by_id[nodeId]
|
||||
if (node?.type == 'Reroute') {
|
||||
let linkId = node.inputs.filter(i => i.type == '*')[0].link
|
||||
nodeId = app.graph.links.filter(link => link.id == linkId)[0]
|
||||
?.origin_id
|
||||
return findNode(nodeId)
|
||||
} else {
|
||||
return nodeId
|
||||
}
|
||||
}
|
||||
|
||||
// 获取layers数据
|
||||
const getLayers = async () => {
|
||||
console.log(
|
||||
'getLayers1',
|
||||
this.inputs.filter(ip => ip.name === 'layers')
|
||||
)
|
||||
let linkId = this.inputs.filter(ip => ip.name === 'layers')[0].link
|
||||
let nodeId = app.graph.links?.filter(link => link.id == linkId)[0]
|
||||
?.origin_id
|
||||
|
||||
if (nodeId) {
|
||||
nodeId = findNode(nodeId)
|
||||
}
|
||||
|
||||
// let node = app.graph._nodes_by_id[nodeId]
|
||||
// if (node?.type == 'Reroute') {
|
||||
// linkId = node.inputs[0].link
|
||||
// nodeId = app.graph.links.filter(link => link.id == linkId)[0]
|
||||
// ?.origin_id
|
||||
// }
|
||||
|
||||
let d = getLocalData('_mixlab_svg_image')
|
||||
console.log('test', d[nodeId])
|
||||
|
||||
if (d[nodeId]) {
|
||||
let url = d[nodeId]
|
||||
let dt = await fetch(url)
|
||||
|
||||
let svgStr = await dt.text()
|
||||
|
||||
const { data } = (await parseSvg(svgStr)) || {}
|
||||
console.log('fetch', data)
|
||||
return data
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
// 修改layers数据
|
||||
const setLayer = async (editIndex, layers = null) => {
|
||||
// let editIndex = 0
|
||||
let lys = layers || (await getLayers())
|
||||
let layer = lys[editIndex]
|
||||
// console.log(layer)
|
||||
|
||||
const updateValue = name => {
|
||||
const x = this.widgets.filter(w => w.name == name)[0]
|
||||
x.value = layer[name]
|
||||
}
|
||||
if (layer) {
|
||||
Array.from(['x', 'y', 'width', 'height', 'z_index'], n =>
|
||||
updateValue(n)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
let that = this
|
||||
const save_edit_layer_index = i => {
|
||||
let data = getLocalData('_mixlab_edit_layer')
|
||||
data[that.id] = i
|
||||
localStorage.setItem('_mixlab_edit_layer', JSON.stringify(data))
|
||||
}
|
||||
|
||||
await setLayer(0)
|
||||
save_edit_layer_index(0)
|
||||
|
||||
const edit = this.widgets.filter(w => w.name == 'edit')[0]
|
||||
|
||||
edit.input = $el('div', {})
|
||||
edit.input.style = `
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
align-items: center;
|
||||
margin-top: 0;`
|
||||
|
||||
const ip = $el('input', {})
|
||||
ip.className = 'comfy-multiline-input'
|
||||
ip.type = 'number'
|
||||
ip.min = 0
|
||||
ip.step = 1
|
||||
ip.max = Math.max(0, (await getLayers()).length - 1)
|
||||
// ip.className = `${'comfy-multiline-input'} `
|
||||
|
||||
ip.value = 0
|
||||
|
||||
ip.style = `
|
||||
background-color: var(--comfy-input-bg);
|
||||
color: var(--input-text);
|
||||
outline: none;
|
||||
border: none;
|
||||
padding: 4px;
|
||||
width: 60%;
|
||||
cursor: pointer;
|
||||
height: 24px;`
|
||||
const label = document.createElement('label')
|
||||
label.style = 'font-size: 10px;min-width:32px'
|
||||
label.innerText = 'Layer Index'
|
||||
edit.input.appendChild(label)
|
||||
edit.input.appendChild(ip)
|
||||
|
||||
document.body.appendChild(edit.input)
|
||||
|
||||
ip.addEventListener('click', async event => {
|
||||
console.log(await getLayers())
|
||||
ip.max = Math.max(0, (await getLayers()).length - 1)
|
||||
})
|
||||
|
||||
ip.addEventListener('change', async event => {
|
||||
let index = ~~ip.value
|
||||
let lys = await getLayers()
|
||||
await setLayer(index, lys)
|
||||
app.graph.setDirtyCanvas(true, true)
|
||||
save_edit_layer_index(index)
|
||||
})
|
||||
|
||||
// console.log('EditLayer nodeData', edit)
|
||||
|
||||
const onRemoved = this.onRemoved
|
||||
this.onRemoved = () => {
|
||||
edit.input.remove()
|
||||
return onRemoved?.()
|
||||
}
|
||||
|
||||
if (this.onResize) {
|
||||
this.onResize(this.size)
|
||||
}
|
||||
|
||||
this.serialize_widgets = false //需要保存参数
|
||||
}
|
||||
}
|
||||
},
|
||||
async loadedGraphNode (node, app) {
|
||||
// Fires every time a node is constructed
|
||||
// You can modify widgets/add handlers/etc here
|
||||
if (node.type === 'SvgImage') {
|
||||
let widget = node.widgets.filter(w => w.div)[0]
|
||||
let data = getLocalData('_mixlab_svg_image')
|
||||
let id = node.id
|
||||
|
||||
// widget.div.querySelector('.Svg').value = data[id] || '#000000'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
app.registerExtension({
|
||||
name: 'Mixlab.layer.NewLayer',
|
||||
async beforeRegisterNodeDef (nodeType, nodeData, app) {
|
||||
if (nodeData.name === 'NewLayer') {
|
||||
const orig_nodeCreated = nodeType.prototype.onNodeCreated
|
||||
nodeType.prototype.onNodeCreated = async function () {
|
||||
orig_nodeCreated?.apply(this, arguments)
|
||||
|
||||
let b = this.widgets.filter(w => w.type === 'button')[0]
|
||||
// const [w, h, base64] = canvas
|
||||
|
||||
if (!b) {
|
||||
const updateValue = (x1, y1, w1, h1) => {
|
||||
if (this.widgets) {
|
||||
for (const widget of this.widgets) {
|
||||
if (widget.name === 'x') {
|
||||
widget.value = x1
|
||||
}
|
||||
if (widget.name === 'y') {
|
||||
widget.value = y1
|
||||
}
|
||||
if (widget.name === 'width') {
|
||||
widget.value = w1
|
||||
}
|
||||
if (widget.name === 'height') {
|
||||
widget.value = h1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.addWidget('button', 'Set Area', '', () => {
|
||||
let data = {}
|
||||
for (const widget of this.widgets) {
|
||||
if (widget.name === 'x') {
|
||||
data.x = widget.value
|
||||
}
|
||||
if (widget.name === 'y') {
|
||||
data.y = widget.value
|
||||
}
|
||||
if (widget.name === 'width') {
|
||||
data.width = widget.value
|
||||
}
|
||||
if (widget.name === 'height') {
|
||||
data.height = widget.value
|
||||
}
|
||||
}
|
||||
try {
|
||||
console.log('this.inputs', this.inputs)
|
||||
let topLinkId = this.inputs[0].link
|
||||
let topNodeId = app.graph.links[topLinkId].origin_id
|
||||
let topIm = app.graph.getNodeById(topNodeId).imgs[0]
|
||||
|
||||
let linkId = this.inputs[3].link
|
||||
let nodeId = app.graph.links[linkId].origin_id
|
||||
// console.log(linkId,this.inputs)
|
||||
let im = app.graph.getNodeById(nodeId).imgs[0]
|
||||
// let src = im.src
|
||||
setArea(
|
||||
im.naturalWidth,
|
||||
im.naturalHeight,
|
||||
topIm.src,
|
||||
im.src,
|
||||
data,
|
||||
updateValue
|
||||
)
|
||||
} catch (error) {}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const onRemoved = this.onRemoved
|
||||
this.onRemoved = () => {
|
||||
// let b = this.widgets.filter(w => w.type === 'button')[0];
|
||||
|
||||
return onRemoved?.()
|
||||
}
|
||||
|
||||
if (this.onResize) {
|
||||
this.onResize(this.size)
|
||||
}
|
||||
|
||||
this.serialize_widgets = true //需要保存参数
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1 @@
|
||||
export const closeIcon = '<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="m256-200-56-56 224-224-224-224 56-56 224 224 224-224 56 56-224 224 224 224-56 56-224-224-224 224Z"/></svg>'
|
||||
@@ -0,0 +1,858 @@
|
||||
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 { closeIcon } from './svg_icons.js'
|
||||
|
||||
|
||||
import {
|
||||
GroupNodeConfig,
|
||||
GroupNodeHandler
|
||||
} from '../../../extensions/core/groupNode.js'
|
||||
|
||||
function deepEqual (obj1, obj2) {
|
||||
if (typeof obj1 !== typeof obj2) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (typeof obj1 !== 'object' || obj1 === null || obj2 === null) {
|
||||
return obj1 === obj2
|
||||
}
|
||||
|
||||
const keys1 = Object.keys(obj1)
|
||||
const keys2 = Object.keys(obj2)
|
||||
|
||||
if (keys1.length !== keys2.length) {
|
||||
return false
|
||||
}
|
||||
|
||||
for (let key of keys1) {
|
||||
if (!deepEqual(obj1[key], obj2[key])) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
async function get_nodes_map () {
|
||||
let api_host = `${window.location.hostname}:${window.location.port}`
|
||||
let api_base = ''
|
||||
let url = `${window.location.protocol}//${api_host}${api_base}`
|
||||
|
||||
const res = await fetch(`${url}/mixlab/nodes_map`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
data: 'json'
|
||||
})
|
||||
})
|
||||
return await res.json()
|
||||
}
|
||||
|
||||
function loadCSS (url) {
|
||||
var link = document.createElement('link')
|
||||
link.rel = 'stylesheet'
|
||||
link.type = 'text/css'
|
||||
link.href = url
|
||||
document.getElementsByTagName('head')[0].appendChild(link)
|
||||
}
|
||||
|
||||
var cssURL =
|
||||
'https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.5.0/github-markdown-light.min.css'
|
||||
loadCSS(cssURL)
|
||||
|
||||
function injectCSS (css) {
|
||||
// 检查页面中是否已经存在具有相同内容的style标签
|
||||
const existingStyle = document.querySelector('style')
|
||||
if (existingStyle && existingStyle.textContent === css) {
|
||||
return // 如果已经存在相同的样式,则不进行注入
|
||||
}
|
||||
|
||||
// 创建一个新的style标签,并将CSS内容注入其中
|
||||
const style = document.createElement('style')
|
||||
style.textContent = css
|
||||
|
||||
// 将style标签插入到页面的head元素中
|
||||
const head = document.querySelector('head')
|
||||
head.appendChild(style)
|
||||
}
|
||||
|
||||
injectCSS(`::-webkit-scrollbar {
|
||||
width: 2px;
|
||||
}
|
||||
|
||||
@keyframes loading_mixlab {
|
||||
0% {
|
||||
background-color: green;
|
||||
}
|
||||
|
||||
50% {
|
||||
background-color: lightgreen;
|
||||
}
|
||||
|
||||
100% {
|
||||
background-color: green;
|
||||
}
|
||||
}
|
||||
|
||||
.loading_mixlab {
|
||||
background-color: green;
|
||||
animation-name: loading_mixlab;
|
||||
animation-duration: 2s;
|
||||
animation-iteration-count: infinite;
|
||||
}`)
|
||||
|
||||
async function getCustomnodeMappings (mode = 'url') {
|
||||
// mode = "local";
|
||||
let api_host = `${window.location.hostname}:${window.location.port}`
|
||||
let api_base = ''
|
||||
let url = `${window.location.protocol}//${api_host}${api_base}`
|
||||
|
||||
let nodes = {}
|
||||
|
||||
const data = (await get_nodes_map()).data
|
||||
|
||||
for (let url in data) {
|
||||
let n = data[url]
|
||||
for (let node of n[0]) {
|
||||
// if(node=='CLIPSeg')console.log('#CLIPSeg',n)
|
||||
nodes[node] = { url, title: n[1].title_aux }
|
||||
}
|
||||
}
|
||||
|
||||
// try {
|
||||
// const response = await fetch(`${url}/customnode/getmappings?mode=${mode}`)
|
||||
// const data = await response.json()
|
||||
// for (let url in data) {
|
||||
// let n = data[url]
|
||||
// for (let node of n[0]) {
|
||||
// // if(node=='CLIPSeg')console.log('#CLIPSeg',n)
|
||||
// nodes[node] = { url, title: n[1].title_aux }
|
||||
// }
|
||||
// }
|
||||
// } catch (error) {
|
||||
// const data = (await get_nodes_map()).data
|
||||
|
||||
// for (let url in data) {
|
||||
// let n = data[url]
|
||||
// for (let node of n[0]) {
|
||||
// // if(node=='CLIPSeg')console.log('#CLIPSeg',n)
|
||||
// nodes[node] = { url, title: n[1].title_aux }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
return nodes
|
||||
}
|
||||
|
||||
const missingNodeGithub = (missingNodeTypes, nodesMap) => {
|
||||
let ts = {}
|
||||
|
||||
Array.from(new Set(missingNodeTypes), n => {
|
||||
if (nodesMap[n]) {
|
||||
let title = nodesMap[n].title
|
||||
if (!ts[title]) {
|
||||
ts[title] = {
|
||||
title,
|
||||
nodes: {},
|
||||
url: nodesMap[n].url
|
||||
}
|
||||
}
|
||||
ts[title].nodes[n] = 1
|
||||
} else {
|
||||
ts[n] = {
|
||||
title: n,
|
||||
nodes: {},
|
||||
url: `https://github.com/search?q=${n}&type=code`
|
||||
}
|
||||
ts[n].nodes[n] = 1
|
||||
}
|
||||
})
|
||||
|
||||
return Array.from(Object.values(ts), n => {
|
||||
const url = n.url
|
||||
return `<li style="color: white;
|
||||
background: black;
|
||||
padding: 8px;
|
||||
font-size: 12px;">${n.title}<a href="${url}" target="_blank"> 🔗</a></li>`
|
||||
})
|
||||
}
|
||||
|
||||
let nodesMap
|
||||
|
||||
function get_position_style (ctx, widget_width, y, node_height) {
|
||||
const MARGIN = 4 // the margin around the html element
|
||||
|
||||
/* Create a transform that deals with all the scrolling and zooming */
|
||||
const elRect = ctx.canvas.getBoundingClientRect()
|
||||
const transform = new DOMMatrix()
|
||||
.scaleSelf(
|
||||
elRect.width / ctx.canvas.width,
|
||||
elRect.height / ctx.canvas.height
|
||||
)
|
||||
.multiplySelf(ctx.getTransform())
|
||||
.translateSelf(MARGIN, MARGIN + y)
|
||||
|
||||
return {
|
||||
transformOrigin: '0 0',
|
||||
transform: transform,
|
||||
left: `0`,
|
||||
top: `0`,
|
||||
cursor: 'pointer',
|
||||
position: 'absolute',
|
||||
maxWidth: `${widget_width - MARGIN * 2}px`,
|
||||
// maxHeight: `${node_height - MARGIN * 2}px`, // we're assuming we have the whole height of the node
|
||||
width: `${widget_width - MARGIN * 2}px`,
|
||||
// height: `${node_height * 0.3 - MARGIN * 2}px`,
|
||||
// background: '#EEEEEE',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
// alignItems: 'center',
|
||||
justifyContent: 'space-around'
|
||||
}
|
||||
}
|
||||
|
||||
app.showMissingNodesError = async function (
|
||||
missingNodeTypes,
|
||||
hasAddedNodes = true
|
||||
) {
|
||||
nodesMap =
|
||||
nodesMap && Object.keys(nodesMap).length > 0
|
||||
? nodesMap
|
||||
: await getCustomnodeMappings('url')
|
||||
|
||||
// console.log('#nodesMap', nodesMap)
|
||||
// console.log('###MIXLAB', missingNodeTypes, hasAddedNodes)
|
||||
this.ui.dialog.show(
|
||||
`When loading the graph, the following node types were not found: <ul>${missingNodeGithub(
|
||||
missingNodeTypes,
|
||||
nodesMap
|
||||
).join('')}</ul>${
|
||||
hasAddedNodes
|
||||
? 'Nodes that have failed to load will show as red on the graph.'
|
||||
: ''
|
||||
}`
|
||||
)
|
||||
this.logging.addEntry('Comfy.App', 'warn', {
|
||||
MissingNodes: missingNodeTypes
|
||||
})
|
||||
}
|
||||
|
||||
// app.registerExtension({
|
||||
// name: 'Comfy.MDNote',
|
||||
// registerCustomNodes () {
|
||||
// class NoteNode {
|
||||
// // color = LGraphCanvas.node_colors.yellow.color
|
||||
// // bgcolor = LGraphCanvas.node_colors.yellow.bgcolor
|
||||
// // groupcolor = LGraphCanvas.node_colors.yellow.groupcolor
|
||||
// constructor () {
|
||||
// if (!this.properties) {
|
||||
// this.properties = {}
|
||||
// this.properties.text = ''
|
||||
// }
|
||||
// console.log('NoteNode1', this)
|
||||
|
||||
// const widget = {
|
||||
// type: 'div',
|
||||
// name: 'input_color',
|
||||
// draw (ctx, node, widget_width, y, widget_height) {
|
||||
// Object.assign(
|
||||
// this.div.style,
|
||||
// get_position_style(
|
||||
// ctx,
|
||||
// widget_width,
|
||||
// 44,
|
||||
// node.size[1]
|
||||
// )
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
|
||||
// widget.div = $el('div', {});
|
||||
// widget.div.innerText='1111'
|
||||
|
||||
// document.body.appendChild(widget.div)
|
||||
|
||||
// this.addCustomWidget(widget)
|
||||
|
||||
// this.serialize_widgets = true
|
||||
// this.isVirtualNode = true
|
||||
// }
|
||||
// }
|
||||
|
||||
// // Load default visibility
|
||||
|
||||
// LiteGraph.registerNodeType(
|
||||
// 'MDNote',
|
||||
// Object.assign(NoteNode, {
|
||||
// title_mode: LiteGraph.NORMAL_TITLE,
|
||||
// title: 'MDNote',
|
||||
// collapsable: true
|
||||
// })
|
||||
// )
|
||||
|
||||
// NoteNode.category = '♾️Mixlab/utils'
|
||||
// },
|
||||
|
||||
// })
|
||||
|
||||
async function fetchReadmeContent (url) {
|
||||
try {
|
||||
// var repo = 'owner/repo'; // 仓库的拥有者和名称
|
||||
var match = url.match(/github.com\/([^/]+\/[^/]+)/)
|
||||
var repo = match[1]
|
||||
var url = `https://api.github.com/repos/${repo}/readme`
|
||||
var response = await fetch(url)
|
||||
var data = await response.json()
|
||||
var readmeUrl = data.download_url
|
||||
|
||||
var readmeResponse = await fetch(readmeUrl)
|
||||
var content = await readmeResponse.text()
|
||||
// console.log(content) // 在控制台输出readme.md文件的内容
|
||||
|
||||
return content
|
||||
} catch (error) {
|
||||
console.log('获取readme.md文件信息失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
function createModal (url, markdown, title) {
|
||||
// Create modal element
|
||||
var div =
|
||||
document.querySelector('#mix-modal') || document.createElement('div')
|
||||
div.id = 'mix-modal'
|
||||
div.innerHTML = ''
|
||||
div.style.cssText = `width: 100%;
|
||||
z-index: 9990;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
color: var(--descrip-text);
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
`
|
||||
|
||||
var modal = document.createElement('div')
|
||||
|
||||
div.appendChild(modal)
|
||||
modal.classList.add('modal-body')
|
||||
// Set modal styles
|
||||
modal.style.cssText = `
|
||||
background: white;
|
||||
height: 80vh;
|
||||
position: fixed;
|
||||
overflow:hidden;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 9999;
|
||||
border-radius: 4px;
|
||||
box-shadow: 4px 4px 14px rgba(255,255,255,0.5);
|
||||
|
||||
`
|
||||
// Create modal content area
|
||||
var modalContent = document.createElement('div')
|
||||
modalContent.classList.add('modal-content')
|
||||
// Create modal header
|
||||
const headerElement = document.createElement('div')
|
||||
headerElement.classList.add('modal-header')
|
||||
headerElement.style.cssText = `
|
||||
display: flex;
|
||||
padding: 20px 24px 8px 24px;
|
||||
justify-content: space-between;
|
||||
`
|
||||
|
||||
const headTitleElement = document.createElement('a')
|
||||
headTitleElement.classList.add('header-title')
|
||||
headTitleElement.style.cssText = `
|
||||
color: var(--descrip-text);
|
||||
font-size: 18px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
`
|
||||
headTitleElement.onmouseenter = function () {
|
||||
headTitleElement.style.color = 'var(--comfy-menu-bg)'
|
||||
}
|
||||
headTitleElement.onmouseleave = function () {
|
||||
headTitleElement.style.color = 'var(--descrip-text)'
|
||||
}
|
||||
headTitleElement.textContent = title || ''
|
||||
headTitleElement.href = url
|
||||
headTitleElement.target = '_blank'
|
||||
const linkIcon = document.createElement('small')
|
||||
linkIcon.textContent = '🔗'
|
||||
headTitleElement.appendChild(linkIcon)
|
||||
headerElement.appendChild(headTitleElement)
|
||||
|
||||
// Create close button
|
||||
const closeButton = document.createElement('span')
|
||||
closeButton.classList.add('close')
|
||||
closeButton.innerHTML = closeIcon
|
||||
// Set close button styles
|
||||
closeButton.style.cssText = `
|
||||
padding: 4px;
|
||||
cursor: pointer;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
user-select: none;
|
||||
fill: var(--descrip-text);
|
||||
`
|
||||
closeButton.onmouseenter = function () {
|
||||
closeButton.style.fill = 'var(--comfy-menu-bg)'
|
||||
}
|
||||
closeButton.onmouseleave = function () {
|
||||
closeButton.style.fill = 'var(--descrip-text)'
|
||||
}
|
||||
|
||||
headerElement.appendChild(closeButton)
|
||||
|
||||
// Click event to close the modal
|
||||
function closeMixModal () {
|
||||
div.style.display = 'none'
|
||||
window.removeEventListener('keydown', MixModalEscKeyEvent)
|
||||
}
|
||||
closeButton.onclick = function () {
|
||||
closeMixModal()
|
||||
}
|
||||
|
||||
// Set modal content area styles
|
||||
modalContent.style.cssText = `
|
||||
position: relative;
|
||||
padding: 0px;
|
||||
overflow: hidden scroll;;
|
||||
height: 100%;
|
||||
min-width:300px
|
||||
`
|
||||
|
||||
// Append close button to modal content area
|
||||
modal.appendChild(headerElement)
|
||||
|
||||
// Create element for displaying Markdown content
|
||||
var markdownContent = document.createElement('div')
|
||||
markdownContent.classList.add('markdown-content', 'markdown-body')
|
||||
markdownContent.style.cssText = `max-width: 50vw;padding: 0px 24px 100px 24px;`
|
||||
|
||||
showdown.setFlavor('github')
|
||||
var converter = new showdown.Converter()
|
||||
|
||||
var html = converter.makeHtml(markdown)
|
||||
|
||||
// Hide images in the markdown when they fail to load
|
||||
var regex = /<img[^>]+src="?([^"\s]+)"?[^>]*>/g
|
||||
html = html.replace(regex, function (match, src) {
|
||||
return `<img src="${src}" onerror="this.style.display='none'">`
|
||||
})
|
||||
|
||||
// Open links in a new tab or window
|
||||
html = html.replace(/<a/g, '<a target="_blank"')
|
||||
|
||||
// Fix href attribute to absolute path
|
||||
html = html.replace(
|
||||
/<a([^>]+href=["'])(?!https?:\/\/)([^"'>]+)/g,
|
||||
function (match, prefix, path) {
|
||||
var absolutePath = url + '/' + path
|
||||
return '<a' + prefix + absolutePath
|
||||
}
|
||||
)
|
||||
|
||||
markdownContent.innerHTML = html
|
||||
|
||||
// Append Markdown content element to modal content area
|
||||
modalContent.appendChild(markdownContent)
|
||||
|
||||
// Append modal content area to modal element
|
||||
modal.appendChild(modalContent)
|
||||
|
||||
const footerElement = document.createElement('div')
|
||||
footerElement.style.cssText = `
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
text-align: right;
|
||||
padding:10px;
|
||||
font-size:12px
|
||||
`
|
||||
|
||||
const footerText = document.createElement('a')
|
||||
footerText.href = 'https://github.com/shadowcz007/comfyui-mixlab-nodes'
|
||||
footerText.innerText = 'Support by Mixlab'
|
||||
footerText.style.cssText = `color:inherit`
|
||||
footerText.target = '_blank'
|
||||
footerText.onmouseenter = function () {
|
||||
footerText.style.color = 'var(--input-text)'
|
||||
}
|
||||
footerText.onmouseleave = function () {
|
||||
footerText.style.color = 'inherit'
|
||||
}
|
||||
|
||||
footerText.onclick = function (e) {
|
||||
e.stopPropagation()
|
||||
}
|
||||
footerElement.appendChild(footerText)
|
||||
|
||||
div.appendChild(footerElement)
|
||||
|
||||
// Append modal element to the page
|
||||
if (!document.querySelector('#mix-modal')) {
|
||||
document.body.appendChild(div)
|
||||
}
|
||||
function MixModalEscKeyEvent (event) {
|
||||
if (event.key == 'Escape') {
|
||||
closeMixModal()
|
||||
}
|
||||
}
|
||||
window.removeEventListener('keydown', MixModalEscKeyEvent)
|
||||
window.addEventListener('keydown', MixModalEscKeyEvent)
|
||||
|
||||
const bgElement = document.createElement('div')
|
||||
bgElement.classList.add('mix-modal-bg')
|
||||
bgElement.style.cssText = `
|
||||
width:100%;
|
||||
height:100%;
|
||||
background-color: rgba(0,0,0,0.8);
|
||||
`
|
||||
bgElement.onclick = function () {
|
||||
closeMixModal()
|
||||
}
|
||||
|
||||
div.appendChild(bgElement)
|
||||
}
|
||||
|
||||
app.registerExtension({
|
||||
name: 'Comfy.Mixlab.ui',
|
||||
init () {
|
||||
LGraphCanvas.prototype.helpAboutNode = async function (node) {
|
||||
nodesMap =
|
||||
nodesMap && Object.keys(nodesMap).length > 0
|
||||
? nodesMap
|
||||
: await getCustomnodeMappings('url')
|
||||
|
||||
console.log('node & node map', node, nodesMap, nodesMap[node.type])
|
||||
let repo = nodesMap[node.type]
|
||||
if (repo) {
|
||||
let markdown = await fetchReadmeContent(repo.url)
|
||||
createModal(repo.url, markdown, repo.title)
|
||||
}
|
||||
}
|
||||
|
||||
const getNodeMenuOptions = LGraphCanvas.prototype.getNodeMenuOptions // store the existing method
|
||||
LGraphCanvas.prototype.getNodeMenuOptions = function (node) {
|
||||
// replace it
|
||||
const options = getNodeMenuOptions.apply(this, arguments) // start by calling the stored one
|
||||
node.setDirtyCanvas(true, true) // force a redraw of (foreground, background)
|
||||
|
||||
return [
|
||||
{
|
||||
content: 'Help ♾️Mixlab', // with a name
|
||||
callback: () => {
|
||||
LGraphCanvas.prototype.helpAboutNode(node)
|
||||
} // and the callback
|
||||
},
|
||||
null,
|
||||
...options
|
||||
] // and return the options
|
||||
}
|
||||
|
||||
const getGroupMenuOptions = LGraphCanvas.prototype.getGroupMenuOptions // store the existing method
|
||||
LGraphCanvas.prototype.getGroupMenuOptions = function (node) {
|
||||
// replace it
|
||||
const options = getGroupMenuOptions.apply(this, arguments) // start by calling the stored one
|
||||
node.setDirtyCanvas(true, true) // force a redraw of (foreground, background)
|
||||
|
||||
// templete
|
||||
const key = 'Comfy.NodeTemplates'
|
||||
let templates = localStorage.getItem(key)
|
||||
if (templates) {
|
||||
templates = JSON.parse(templates)
|
||||
} else {
|
||||
templates = []
|
||||
}
|
||||
const store = () => localStorage.setItem(key, JSON.stringify(templates))
|
||||
|
||||
return [
|
||||
{
|
||||
content: 'Clone Group ♾️Mixlab', // with a name
|
||||
callback: async (value, opts, e, menu, group) => {
|
||||
const clipboardAction = async cb => {
|
||||
// We use the clipboard functions but dont want to overwrite the current user clipboard
|
||||
// Restore it after we've run our callback
|
||||
const old = localStorage.getItem('litegrapheditor_clipboard')
|
||||
await cb()
|
||||
localStorage.setItem('litegrapheditor_clipboard', old)
|
||||
}
|
||||
|
||||
clipboardAction(async () => {
|
||||
let name = group.title
|
||||
let nodes = group._nodes
|
||||
|
||||
app.canvas.copyToClipboard(nodes)
|
||||
let data = localStorage.getItem('litegrapheditor_clipboard')
|
||||
data = JSON.parse(data)
|
||||
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
const node = app.graph.getNodeById(nodes[i].id)
|
||||
const nodeData = node.serialize()
|
||||
|
||||
let groupData = GroupNodeHandler.getGroupData(node)
|
||||
if (groupData) {
|
||||
groupData = groupData.nodeData
|
||||
if (!data.groupNodes) {
|
||||
data.groupNodes = {}
|
||||
}
|
||||
data.groupNodes[nodeData.name] = groupData
|
||||
data.nodes[i].type = nodeData.name
|
||||
}
|
||||
}
|
||||
|
||||
await GroupNodeConfig.registerFromWorkflow(data.groupNodes, {})
|
||||
localStorage.setItem(
|
||||
'litegrapheditor_clipboard',
|
||||
JSON.stringify(data)
|
||||
)
|
||||
app.canvas.pasteFromClipboard()
|
||||
})
|
||||
} // and the callback
|
||||
},
|
||||
{
|
||||
content: 'Save Group as Template ♾️Mixlab', // with a name
|
||||
callback: async (value, opts, e, menu, group) => {
|
||||
// console.log(options)
|
||||
|
||||
const clipboardAction = async cb => {
|
||||
// We use the clipboard functions but dont want to overwrite the current user clipboard
|
||||
// Restore it after we've run our callback
|
||||
const old = localStorage.getItem('litegrapheditor_clipboard')
|
||||
await cb()
|
||||
localStorage.setItem('litegrapheditor_clipboard', old)
|
||||
}
|
||||
|
||||
clipboardAction(() => {
|
||||
let name = group.title + ' ♾️Mixlab'
|
||||
let nodes = group._nodes
|
||||
|
||||
app.canvas.copyToClipboard(nodes)
|
||||
let data = localStorage.getItem('litegrapheditor_clipboard')
|
||||
data = JSON.parse(data)
|
||||
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
const node = app.graph.getNodeById(nodes[i].id)
|
||||
const nodeData = node.serialize()
|
||||
|
||||
let groupData = GroupNodeHandler.getGroupData(node)
|
||||
if (groupData) {
|
||||
groupData = groupData.nodeData
|
||||
if (!data.groupNodes) {
|
||||
data.groupNodes = {}
|
||||
}
|
||||
data.groupNodes[nodeData.name] = groupData
|
||||
data.nodes[i].type = nodeData.name
|
||||
}
|
||||
}
|
||||
|
||||
templates.push({
|
||||
name,
|
||||
data: JSON.stringify(data)
|
||||
})
|
||||
store()
|
||||
})
|
||||
} // and the callback
|
||||
},
|
||||
null,
|
||||
...options
|
||||
] // and return the options
|
||||
}
|
||||
LGraphCanvas.prototype.centerOnNode = function(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)
|
||||
|
||||
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 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) => {
|
||||
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 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)
|
||||
}
|
||||
})
|
||||
|
||||
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)
|
||||
}
|
||||
})
|
||||
|
||||
// options.push({
|
||||
// content: `Save For App ♾️Mixlab`,
|
||||
// disabled: false, // or a function determining whether to disable
|
||||
// callback: async () => {
|
||||
|
||||
// }
|
||||
// })
|
||||
return options
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,205 @@
|
||||
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'
|
||||
|
||||
const getLocalData = key => {
|
||||
let data = {}
|
||||
try {
|
||||
data = JSON.parse(localStorage.getItem(key)) || {}
|
||||
} catch (error) {
|
||||
return {}
|
||||
}
|
||||
return data
|
||||
}
|
||||
function get_position_style (ctx, widget_width, y, node_height) {
|
||||
const MARGIN = 4 // the margin around the html element
|
||||
|
||||
/* Create a transform that deals with all the scrolling and zooming */
|
||||
const elRect = ctx.canvas.getBoundingClientRect()
|
||||
const transform = new DOMMatrix()
|
||||
.scaleSelf(
|
||||
elRect.width / ctx.canvas.width,
|
||||
elRect.height / ctx.canvas.height
|
||||
)
|
||||
.multiplySelf(ctx.getTransform())
|
||||
.translateSelf(MARGIN, MARGIN + y)
|
||||
|
||||
return {
|
||||
transformOrigin: '0 0',
|
||||
transform: transform,
|
||||
left: `0`,
|
||||
top: `0`,
|
||||
cursor: 'pointer',
|
||||
position: 'absolute',
|
||||
maxWidth: `${widget_width - MARGIN * 2}px`,
|
||||
// maxHeight: `${node_height - MARGIN * 2}px`, // we're assuming we have the whole height of the node
|
||||
width: `${widget_width - MARGIN * 2}px`,
|
||||
// height: `${node_height * 0.3 - MARGIN * 2}px`,
|
||||
// background: '#EEEEEE',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
// alignItems: 'center',
|
||||
justifyContent: 'space-around'
|
||||
}
|
||||
}
|
||||
|
||||
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',
|
||||
async getCustomWidgets (app) {
|
||||
return {
|
||||
TCOLOR (node, inputName, inputData, app) {
|
||||
// console.log('##node', node)
|
||||
const widget = {
|
||||
type: inputData[0], // the type, CHEESE
|
||||
name: inputName, // the name, slice
|
||||
size: [128, 32], // a default size
|
||||
draw (ctx, node, width, y) {},
|
||||
computeSize (...args) {
|
||||
return [128, 32] // a method to compute the current size of the widget
|
||||
},
|
||||
async serializeValue (nodeId, widgetIndex) {
|
||||
let data = getLocalData('_mixlab_utils_color');
|
||||
let hex=data[node.id] || '#000000'
|
||||
let [r,g,b,a]=hexToRGBA(hex)
|
||||
return {
|
||||
hex,
|
||||
r,
|
||||
g,
|
||||
b,
|
||||
a
|
||||
}
|
||||
}
|
||||
}
|
||||
// widget.something = something; // maybe adds stuff to it
|
||||
node.addCustomWidget(widget) // adds it to the node
|
||||
return widget // and returns it.
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
async beforeRegisterNodeDef (nodeType, nodeData, app) {
|
||||
if (nodeType.comfyClass == 'Color') {
|
||||
const orig_nodeCreated = nodeType.prototype.onNodeCreated
|
||||
nodeType.prototype.onNodeCreated = function () {
|
||||
orig_nodeCreated?.apply(this, arguments)
|
||||
|
||||
console.log('Color nodeData', this.widgets)
|
||||
|
||||
const widget = {
|
||||
type: 'div',
|
||||
name: 'input_color',
|
||||
draw (ctx, node, widget_width, y, widget_height) {
|
||||
Object.assign(
|
||||
this.div.style,
|
||||
get_position_style(ctx, widget_width, 44, node.size[1])
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
widget.div = $el('div', {})
|
||||
|
||||
document.body.appendChild(widget.div)
|
||||
|
||||
const inputDiv = (key, placeholder, value) => {
|
||||
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: 70%;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())
|
||||
})
|
||||
return div
|
||||
}
|
||||
|
||||
let inputColor = inputDiv('_mixlab_utils_color', 'Color', '#000000')
|
||||
|
||||
widget.div.appendChild(inputColor)
|
||||
|
||||
this.addCustomWidget(widget)
|
||||
|
||||
const onRemoved = this.onRemoved
|
||||
this.onRemoved = () => {
|
||||
inputColor.remove()
|
||||
widget.div.remove()
|
||||
return onRemoved?.()
|
||||
}
|
||||
|
||||
this.serialize_widgets = true //需要保存参数
|
||||
}
|
||||
}
|
||||
},
|
||||
async loadedGraphNode (node, app) {
|
||||
// Fires every time a node is constructed
|
||||
// You can modify widgets/add handlers/etc here
|
||||
|
||||
if (node.type === 'Color') {
|
||||
let widget = node.widgets.filter(w => w.div)[0]
|
||||
|
||||
let data = getLocalData('_mixlab_utils_color')
|
||||
|
||||
let id = node.id
|
||||
|
||||
widget.div.querySelector('.Color').value = data[id] || '#000000'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
app.registerExtension({
|
||||
name: 'Mixlab.utils.TextToNumber',
|
||||
|
||||
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]
|
||||
}
|
||||
|
||||
console.log('TextToNumber', random_number.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -14,6 +14,10 @@ async function getConfig () {
|
||||
return await res.json()
|
||||
}
|
||||
|
||||
if (!window._mixlab_screen_prompt)
|
||||
window._mixlab_screen_prompt =
|
||||
'beautiful scenery nature glass bottle landscape,under water'
|
||||
|
||||
function get_position_style (ctx, widget_width, y, node_height) {
|
||||
const MARGIN = 4 // the margin around the html element
|
||||
|
||||
@@ -57,7 +61,28 @@ app.registerExtension({
|
||||
name: inputName, // the name, slice
|
||||
size: [128, 24], // a default size
|
||||
draw (ctx, node, width, y) {
|
||||
// a method to draw the widget (ctx is a CanvasRenderingContext2D)
|
||||
// // 绘制文件图标的函数
|
||||
// function drawFileIcon () {
|
||||
// // 清空画布
|
||||
// // ctx.clearRect(0, 0, canvas.width, canvas.height)
|
||||
|
||||
// // 绘制文件外框
|
||||
// ctx.fillStyle = '#000'
|
||||
// ctx.fillRect(5, 5, 40, 40)
|
||||
|
||||
// // 绘制文件夹图标
|
||||
// ctx.fillStyle = '#f00'
|
||||
// ctx.fillRect(10, 15, 30, 20)
|
||||
|
||||
// // 绘制监听符号
|
||||
// ctx.beginPath()
|
||||
// ctx.arc(30, 35, 5, 0, 2 * Math.PI)
|
||||
// ctx.fillStyle = '#00f'
|
||||
// ctx.fill()
|
||||
// }
|
||||
|
||||
// // 调用绘制函数
|
||||
// drawFileIcon()
|
||||
},
|
||||
computeSize (...args) {
|
||||
return [128, 24] // a method to compute the current size of the widget
|
||||
@@ -69,6 +94,26 @@ app.registerExtension({
|
||||
// widget.something = something; // maybe adds stuff to it
|
||||
node.addCustomWidget(widget) // adds it to the node
|
||||
return widget // and returns it.
|
||||
},
|
||||
PROMPT (node, inputName, inputData, app) {
|
||||
// console.log('node', inputName, inputData[0])
|
||||
const widget = {
|
||||
type: inputData[0], // the type, CHEESE
|
||||
name: inputName, // the name, slice
|
||||
size: [128, 24], // a default size
|
||||
draw (ctx, node, width, y) {
|
||||
// a method to draw the widget (ctx is a CanvasRenderingContext2D)
|
||||
},
|
||||
computeSize (...args) {
|
||||
return [128, 24] // a method to compute the current size of the widget
|
||||
},
|
||||
async serializeValue (nodeId, widgetIndex) {
|
||||
return window._mixlab_screen_prompt || ''
|
||||
}
|
||||
}
|
||||
// widget.something = something; // maybe adds stuff to it
|
||||
node.addCustomWidget(widget) // adds it to the node
|
||||
return widget // and returns it.
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -79,11 +124,7 @@ app.registerExtension({
|
||||
nodeType.prototype.onNodeCreated = function () {
|
||||
orig_nodeCreated?.apply(this, arguments)
|
||||
|
||||
|
||||
// console.log(
|
||||
// 'watch widtget',
|
||||
// this.widgets.filter(w => w.name == 'watcher')[0]
|
||||
// )
|
||||
console.log('watch widtget', this.widgets)
|
||||
|
||||
const watcher = this.widgets.filter(w => w.name == 'watcher')[0]
|
||||
|
||||
@@ -111,7 +152,6 @@ app.registerExtension({
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 上次路径填充
|
||||
getConfig().then(json => {
|
||||
let w = this.widgets.filter(w => w.name == 'file_path')[0]
|
||||
@@ -120,7 +160,6 @@ app.registerExtension({
|
||||
}
|
||||
// console.log(json.event_type)
|
||||
window._mixlab_file_path_watcher = json.event_type
|
||||
|
||||
})
|
||||
|
||||
/*
|
||||
@@ -130,7 +169,7 @@ app.registerExtension({
|
||||
this.onRemoved = function () {
|
||||
// widget.card.remove()
|
||||
}
|
||||
this.serialize_widgets = false
|
||||
this.serialize_widgets = true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,24 @@
|
||||
::-webkit-scrollbar {
|
||||
width: 2px;
|
||||
}
|
||||
width: 2px;
|
||||
}
|
||||
|
||||
@keyframes loading_mixlab {
|
||||
0% {
|
||||
background-color: green;
|
||||
}
|
||||
|
||||
50% {
|
||||
background-color: lightgreen;
|
||||
}
|
||||
|
||||
100% {
|
||||
background-color: green;
|
||||
}
|
||||
}
|
||||
|
||||
.loading_mixlab {
|
||||
background-color: green;
|
||||
animation-name: loading_mixlab;
|
||||
animation-duration: 2s;
|
||||
animation-iteration-count: infinite;
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"last_node_id": 21,
|
||||
"last_link_id": 45,
|
||||
"last_node_id": 23,
|
||||
"last_link_id": 51,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 7,
|
||||
@@ -14,7 +14,7 @@
|
||||
"1": 200
|
||||
},
|
||||
"flags": {},
|
||||
"order": 7,
|
||||
"order": 4,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
@@ -89,12 +89,12 @@
|
||||
504,
|
||||
33
|
||||
],
|
||||
"size": [
|
||||
400,
|
||||
200
|
||||
],
|
||||
"size": {
|
||||
"0": 400,
|
||||
"1": 200
|
||||
},
|
||||
"flags": {},
|
||||
"order": 6,
|
||||
"order": 8,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
@@ -105,7 +105,7 @@
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"link": 43,
|
||||
"link": 51,
|
||||
"widget": {
|
||||
"name": "text"
|
||||
}
|
||||
@@ -295,7 +295,7 @@
|
||||
"Node name for S&R": "KSampler"
|
||||
},
|
||||
"widgets_values": [
|
||||
613900833686415,
|
||||
482859286431021,
|
||||
"randomize",
|
||||
4,
|
||||
1.6,
|
||||
@@ -311,94 +311,24 @@
|
||||
338,
|
||||
786
|
||||
],
|
||||
"size": [
|
||||
210,
|
||||
246
|
||||
],
|
||||
"size": {
|
||||
"0": 210,
|
||||
"1": 246
|
||||
},
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
"order": 7,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 37
|
||||
"link": 50
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "PreviewImage"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 19,
|
||||
"type": "ScreenShare",
|
||||
"pos": [
|
||||
-55,
|
||||
512
|
||||
],
|
||||
"size": {
|
||||
"0": 325.3117370605469,
|
||||
"1": 459.7692565917969
|
||||
},
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
37,
|
||||
42
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
},
|
||||
{
|
||||
"name": "MASK",
|
||||
"type": "MASK",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
},
|
||||
{
|
||||
"name": "STRING",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
43
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 2
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "ScreenShare"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 20,
|
||||
"type": "FloatingVideo",
|
||||
"pos": [
|
||||
2041,
|
||||
277
|
||||
],
|
||||
"size": [
|
||||
315,
|
||||
106
|
||||
],
|
||||
"flags": {},
|
||||
"order": 12,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 41
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "FloatingVideo"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"type": "LoraLoader",
|
||||
@@ -411,7 +341,7 @@
|
||||
"1": 126
|
||||
},
|
||||
"flags": {},
|
||||
"order": 8,
|
||||
"order": 5,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
@@ -463,7 +393,7 @@
|
||||
"1": 98
|
||||
},
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
@@ -515,13 +445,13 @@
|
||||
"1": 58
|
||||
},
|
||||
"flags": {},
|
||||
"order": 5,
|
||||
"order": 6,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"link": 42
|
||||
"link": 49
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
@@ -541,6 +471,93 @@
|
||||
"widgets_values": [
|
||||
512
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 20,
|
||||
"type": "FloatingVideo",
|
||||
"pos": [
|
||||
1928,
|
||||
295
|
||||
],
|
||||
"size": [
|
||||
315,
|
||||
58
|
||||
],
|
||||
"flags": {},
|
||||
"order": 12,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 41
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "FloatingVideo"
|
||||
},
|
||||
"widgets_values": [
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 23,
|
||||
"type": "ScreenShare",
|
||||
"pos": [
|
||||
-65,
|
||||
446
|
||||
],
|
||||
"size": [
|
||||
312.78457519531213,
|
||||
606.2132135620109
|
||||
],
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
49,
|
||||
50
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
},
|
||||
{
|
||||
"name": "PROMPT",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
51
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 1
|
||||
},
|
||||
{
|
||||
"name": "FLOAT",
|
||||
"type": "FLOAT",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
},
|
||||
{
|
||||
"name": "INT",
|
||||
"type": "INT",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "ScreenShare"
|
||||
},
|
||||
"widgets_values": [
|
||||
null,
|
||||
2018,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
]
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
@@ -744,14 +761,6 @@
|
||||
0,
|
||||
"LATENT"
|
||||
],
|
||||
[
|
||||
37,
|
||||
19,
|
||||
0,
|
||||
2,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
38,
|
||||
16,
|
||||
@@ -784,22 +793,6 @@
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
42,
|
||||
19,
|
||||
0,
|
||||
18,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
43,
|
||||
19,
|
||||
2,
|
||||
8,
|
||||
1,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
44,
|
||||
6,
|
||||
@@ -815,6 +808,30 @@
|
||||
6,
|
||||
1,
|
||||
"CLIP"
|
||||
],
|
||||
[
|
||||
49,
|
||||
23,
|
||||
0,
|
||||
18,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
50,
|
||||
23,
|
||||
0,
|
||||
2,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
51,
|
||||
23,
|
||||
1,
|
||||
8,
|
||||
1,
|
||||
"STRING"
|
||||
]
|
||||
],
|
||||
"groups": [],
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"last_node_id": 23,
|
||||
"last_link_id": 47,
|
||||
"last_link_id": 48,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 11,
|
||||
@@ -94,7 +94,7 @@
|
||||
"Node name for S&R": "KSampler"
|
||||
},
|
||||
"widgets_values": [
|
||||
882790958612696,
|
||||
1088992701378297,
|
||||
"randomize",
|
||||
4,
|
||||
1.6,
|
||||
@@ -110,10 +110,10 @@
|
||||
-470,
|
||||
840
|
||||
],
|
||||
"size": [
|
||||
430.924072265625,
|
||||
253.48239135742188
|
||||
],
|
||||
"size": {
|
||||
"0": 430.924072265625,
|
||||
"1": 253.48239135742188
|
||||
},
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
@@ -212,7 +212,7 @@
|
||||
"1": 58
|
||||
},
|
||||
"flags": {},
|
||||
"order": 9,
|
||||
"order": 8,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
@@ -271,45 +271,6 @@
|
||||
"control_scribble-fp16.safetensors"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"type": "CLIPTextEncode",
|
||||
"pos": [
|
||||
678,
|
||||
-77
|
||||
],
|
||||
"size": {
|
||||
"0": 400,
|
||||
"1": 200
|
||||
},
|
||||
"flags": {},
|
||||
"order": 5,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "clip",
|
||||
"type": "CLIP",
|
||||
"link": 32
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "CONDITIONING",
|
||||
"type": "CONDITIONING",
|
||||
"links": [
|
||||
39
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "CLIPTextEncode"
|
||||
},
|
||||
"widgets_values": [
|
||||
"superman,"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"type": "CLIPTextEncode",
|
||||
@@ -322,7 +283,7 @@
|
||||
"1": 200
|
||||
},
|
||||
"flags": {},
|
||||
"order": 6,
|
||||
"order": 5,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
@@ -361,7 +322,7 @@
|
||||
"1": 126
|
||||
},
|
||||
"flags": {},
|
||||
"order": 7,
|
||||
"order": 6,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
@@ -465,7 +426,7 @@
|
||||
"1": 246
|
||||
},
|
||||
"flags": {},
|
||||
"order": 8,
|
||||
"order": 7,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
@@ -512,6 +473,53 @@
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"type": "CLIPTextEncode",
|
||||
"pos": [
|
||||
678,
|
||||
-77
|
||||
],
|
||||
"size": [
|
||||
400,
|
||||
200
|
||||
],
|
||||
"flags": {},
|
||||
"order": 9,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "clip",
|
||||
"type": "CLIP",
|
||||
"link": 32
|
||||
},
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"link": 48,
|
||||
"widget": {
|
||||
"name": "text"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "CONDITIONING",
|
||||
"type": "CONDITIONING",
|
||||
"links": [
|
||||
39
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "CLIPTextEncode"
|
||||
},
|
||||
"widgets_values": [
|
||||
"superman,"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 22,
|
||||
"type": "LoadImagesFromPath",
|
||||
@@ -521,7 +529,7 @@
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 202
|
||||
"1": 238
|
||||
},
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
@@ -542,6 +550,15 @@
|
||||
"type": "MASK",
|
||||
"links": null,
|
||||
"shape": 6
|
||||
},
|
||||
{
|
||||
"name": "STRING",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
48
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 2
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
@@ -813,6 +830,14 @@
|
||||
18,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
48,
|
||||
22,
|
||||
2,
|
||||
8,
|
||||
1,
|
||||
"STRING"
|
||||
]
|
||||
],
|
||||
"groups": [
|
||||
|
||||
@@ -0,0 +1,631 @@
|
||||
{
|
||||
"last_node_id": 47,
|
||||
"last_link_id": 46,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 27,
|
||||
"type": "CLIPTextEncode",
|
||||
"pos": [
|
||||
1961.178268896482,
|
||||
527.6060791015625
|
||||
],
|
||||
"size": {
|
||||
"0": 422.84503173828125,
|
||||
"1": 164.31304931640625
|
||||
},
|
||||
"flags": {},
|
||||
"order": 5,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "clip",
|
||||
"type": "CLIP",
|
||||
"link": 24
|
||||
},
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"link": 46,
|
||||
"widget": {
|
||||
"name": "text"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "CONDITIONING",
|
||||
"type": "CONDITIONING",
|
||||
"links": [
|
||||
21
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "CLIPTextEncode"
|
||||
},
|
||||
"widgets_values": [
|
||||
"beautiful scenery nature glass bottle landscape, , purple galaxy bottle,"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 28,
|
||||
"type": "CLIPTextEncode",
|
||||
"pos": [
|
||||
1958.7452854980445,
|
||||
266
|
||||
],
|
||||
"size": {
|
||||
"0": 425.27801513671875,
|
||||
"1": 180.6060791015625
|
||||
},
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "clip",
|
||||
"type": "CLIP",
|
||||
"link": 25
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "CONDITIONING",
|
||||
"type": "CONDITIONING",
|
||||
"links": [
|
||||
22
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "CLIPTextEncode"
|
||||
},
|
||||
"widgets_values": [
|
||||
"text, watermark"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 24,
|
||||
"type": "KSampler",
|
||||
"pos": [
|
||||
2434.0233006347635,
|
||||
80
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 262
|
||||
},
|
||||
"flags": {},
|
||||
"order": 8,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "model",
|
||||
"type": "MODEL",
|
||||
"link": 20
|
||||
},
|
||||
{
|
||||
"name": "positive",
|
||||
"type": "CONDITIONING",
|
||||
"link": 21
|
||||
},
|
||||
{
|
||||
"name": "negative",
|
||||
"type": "CONDITIONING",
|
||||
"link": 22
|
||||
},
|
||||
{
|
||||
"name": "latent_image",
|
||||
"type": "LATENT",
|
||||
"link": 23
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "LATENT",
|
||||
"type": "LATENT",
|
||||
"links": [
|
||||
26
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "KSampler"
|
||||
},
|
||||
"widgets_values": [
|
||||
971428736321335,
|
||||
"randomize",
|
||||
15,
|
||||
8,
|
||||
"euler",
|
||||
"karras",
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 29,
|
||||
"type": "VAEDecode",
|
||||
"pos": [
|
||||
2799.0233006347635,
|
||||
80
|
||||
],
|
||||
"size": {
|
||||
"0": 210,
|
||||
"1": 46
|
||||
},
|
||||
"flags": {
|
||||
"collapsed": false
|
||||
},
|
||||
"order": 9,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "samples",
|
||||
"type": "LATENT",
|
||||
"link": 26
|
||||
},
|
||||
{
|
||||
"name": "vae",
|
||||
"type": "VAE",
|
||||
"link": 27
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
31
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "VAEDecode"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 26,
|
||||
"type": "EmptyLatentImage",
|
||||
"pos": [
|
||||
2069.0233006347635,
|
||||
80
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 106
|
||||
},
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "LATENT",
|
||||
"type": "LATENT",
|
||||
"links": [
|
||||
23
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "EmptyLatentImage"
|
||||
},
|
||||
"widgets_values": [
|
||||
512,
|
||||
512,
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 25,
|
||||
"type": "CheckpointLoaderSimple",
|
||||
"pos": [
|
||||
1593.7452854980445,
|
||||
80
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 98
|
||||
},
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "MODEL",
|
||||
"type": "MODEL",
|
||||
"links": [
|
||||
20
|
||||
],
|
||||
"slot_index": 0
|
||||
},
|
||||
{
|
||||
"name": "CLIP",
|
||||
"type": "CLIP",
|
||||
"links": [
|
||||
24,
|
||||
25
|
||||
],
|
||||
"slot_index": 1
|
||||
},
|
||||
{
|
||||
"name": "VAE",
|
||||
"type": "VAE",
|
||||
"links": [
|
||||
27
|
||||
],
|
||||
"slot_index": 2
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "CheckpointLoaderSimple"
|
||||
},
|
||||
"widgets_values": [
|
||||
"deliberate_v2.safetensors"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 31,
|
||||
"type": "PreviewImage",
|
||||
"pos": [
|
||||
2439,
|
||||
408
|
||||
],
|
||||
"size": {
|
||||
"0": 563.4671630859375,
|
||||
"1": 720.8866577148438
|
||||
},
|
||||
"flags": {},
|
||||
"order": 10,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 31
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "PreviewImage"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 44,
|
||||
"type": "ShowTextForGPT",
|
||||
"pos": [
|
||||
1171,
|
||||
425
|
||||
],
|
||||
"size": {
|
||||
"0": 432.46002197265625,
|
||||
"1": 264.40771484375
|
||||
},
|
||||
"flags": {},
|
||||
"order": 6,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"link": 44,
|
||||
"widget": {
|
||||
"name": "text"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "STRING",
|
||||
"type": "STRING",
|
||||
"links": null,
|
||||
"shape": 6
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "ShowTextForGPT"
|
||||
},
|
||||
"widgets_values": [
|
||||
"[\n {\n \"role\": \"system\",\n \"content\": \"You are ChatGPT, a large language model trained by OpenAI. Answer as concisely as possible.\"\n },\n {\n \"role\": \"user\",\n \"content\": \"\"\n }\n]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 43,
|
||||
"type": "ShowTextForGPT",
|
||||
"pos": [
|
||||
1166,
|
||||
740
|
||||
],
|
||||
"size": {
|
||||
"0": 424.0079650878906,
|
||||
"1": 430.6391296386719
|
||||
},
|
||||
"flags": {},
|
||||
"order": 7,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"link": 45,
|
||||
"widget": {
|
||||
"name": "text"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "STRING",
|
||||
"type": "STRING",
|
||||
"links": null,
|
||||
"shape": 6
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "ShowTextForGPT"
|
||||
},
|
||||
"widgets_values": [
|
||||
"[\n {\n \"role\": \"user\",\n \"content\": \"\"\n },\n {\n \"role\": \"assistant\",\n \"content\": \"I'm sorry, I'm ChatGLM3-6B, not ChatGPT. I am a language model jointly trained by Tsinghua University KEG Lab and Zhipu AI Company.\"\n }\n]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 45,
|
||||
"type": "ShowTextForGPT",
|
||||
"pos": [
|
||||
1092,
|
||||
262
|
||||
],
|
||||
"size": {
|
||||
"0": 635.8358154296875,
|
||||
"1": 101.46092224121094
|
||||
},
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"link": 43,
|
||||
"widget": {
|
||||
"name": "text"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "STRING",
|
||||
"type": "STRING",
|
||||
"links": null,
|
||||
"shape": 6
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "ShowTextForGPT"
|
||||
},
|
||||
"widgets_values": [
|
||||
"I'm sorry, I'm ChatGLM3-6B, not ChatGPT. I am a language model jointly trained by Tsinghua University KEG Lab and Zhipu AI Company."
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 47,
|
||||
"type": "ChatGPTOpenAI",
|
||||
"pos": [
|
||||
585,
|
||||
306
|
||||
],
|
||||
"size": {
|
||||
"0": 400,
|
||||
"1": 342
|
||||
},
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
43,
|
||||
46
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
},
|
||||
{
|
||||
"name": "messages",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
44
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 1
|
||||
},
|
||||
{
|
||||
"name": "session_history",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
45
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 2
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "ChatGPTOpenAI"
|
||||
},
|
||||
"widgets_values": [
|
||||
null,
|
||||
null,
|
||||
"",
|
||||
"You are ChatGPT, a large language model trained by OpenAI. Answer as concisely as possible.",
|
||||
"gpt-3.5-turbo-16k",
|
||||
2220,
|
||||
"randomize",
|
||||
1,
|
||||
null
|
||||
]
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
2,
|
||||
4,
|
||||
0,
|
||||
5,
|
||||
0,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
3,
|
||||
6,
|
||||
0,
|
||||
4,
|
||||
0,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
4,
|
||||
6,
|
||||
1,
|
||||
7,
|
||||
0,
|
||||
"JSON"
|
||||
],
|
||||
[
|
||||
8,
|
||||
10,
|
||||
0,
|
||||
11,
|
||||
0,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
12,
|
||||
10,
|
||||
1,
|
||||
14,
|
||||
0,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
13,
|
||||
10,
|
||||
1,
|
||||
16,
|
||||
0,
|
||||
"JSON"
|
||||
],
|
||||
[
|
||||
20,
|
||||
25,
|
||||
0,
|
||||
24,
|
||||
0,
|
||||
"MODEL"
|
||||
],
|
||||
[
|
||||
21,
|
||||
27,
|
||||
0,
|
||||
24,
|
||||
1,
|
||||
"CONDITIONING"
|
||||
],
|
||||
[
|
||||
22,
|
||||
28,
|
||||
0,
|
||||
24,
|
||||
2,
|
||||
"CONDITIONING"
|
||||
],
|
||||
[
|
||||
23,
|
||||
26,
|
||||
0,
|
||||
24,
|
||||
3,
|
||||
"LATENT"
|
||||
],
|
||||
[
|
||||
24,
|
||||
25,
|
||||
1,
|
||||
27,
|
||||
0,
|
||||
"CLIP"
|
||||
],
|
||||
[
|
||||
25,
|
||||
25,
|
||||
1,
|
||||
28,
|
||||
0,
|
||||
"CLIP"
|
||||
],
|
||||
[
|
||||
26,
|
||||
24,
|
||||
0,
|
||||
29,
|
||||
0,
|
||||
"LATENT"
|
||||
],
|
||||
[
|
||||
27,
|
||||
25,
|
||||
2,
|
||||
29,
|
||||
1,
|
||||
"VAE"
|
||||
],
|
||||
[
|
||||
31,
|
||||
29,
|
||||
0,
|
||||
31,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
43,
|
||||
47,
|
||||
0,
|
||||
45,
|
||||
0,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
44,
|
||||
47,
|
||||
1,
|
||||
44,
|
||||
0,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
45,
|
||||
47,
|
||||
2,
|
||||
43,
|
||||
0,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
46,
|
||||
47,
|
||||
0,
|
||||
27,
|
||||
1,
|
||||
"STRING"
|
||||
]
|
||||
],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {},
|
||||
"version": 0.4
|
||||
}
|
||||
@@ -0,0 +1,251 @@
|
||||
{
|
||||
"last_node_id": 5,
|
||||
"last_link_id": 4,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 4,
|
||||
"type": "ChatGPTOpenAI",
|
||||
"pos": [
|
||||
-512,
|
||||
-236
|
||||
],
|
||||
"size": {
|
||||
"0": 400,
|
||||
"1": 342
|
||||
},
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
2
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
},
|
||||
{
|
||||
"name": "messages",
|
||||
"type": "STRING",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
},
|
||||
{
|
||||
"name": "session_history",
|
||||
"type": "STRING",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "ChatGPTOpenAI"
|
||||
},
|
||||
"widgets_values": [
|
||||
null,
|
||||
null,
|
||||
"描述一个科幻的场景",
|
||||
"You are ChatGPT, a large language model trained by OpenAI. Answer as concisely as possible.",
|
||||
"gpt-35-turbo",
|
||||
6933,
|
||||
"randomize",
|
||||
1,
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"type": "ChatGPTOpenAI",
|
||||
"pos": [
|
||||
-36,
|
||||
-234
|
||||
],
|
||||
"size": {
|
||||
"0": 400,
|
||||
"1": 342
|
||||
},
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "prompt",
|
||||
"type": "STRING",
|
||||
"link": 2,
|
||||
"widget": {
|
||||
"name": "prompt"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
3
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
},
|
||||
{
|
||||
"name": "messages",
|
||||
"type": "STRING",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
},
|
||||
{
|
||||
"name": "session_history",
|
||||
"type": "STRING",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "ChatGPTOpenAI"
|
||||
},
|
||||
"widgets_values": [
|
||||
null,
|
||||
null,
|
||||
"",
|
||||
"增加丰富的细节和光影,摄影技巧,镜头语言,材质肌理",
|
||||
"gpt-3.5-turbo",
|
||||
2836,
|
||||
"randomize",
|
||||
1,
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"type": "ChatGPTOpenAI",
|
||||
"pos": [
|
||||
-30,
|
||||
183
|
||||
],
|
||||
"size": {
|
||||
"0": 400,
|
||||
"1": 342
|
||||
},
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "prompt",
|
||||
"type": "STRING",
|
||||
"link": 3,
|
||||
"widget": {
|
||||
"name": "prompt"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
4
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
},
|
||||
{
|
||||
"name": "messages",
|
||||
"type": "STRING",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
},
|
||||
{
|
||||
"name": "session_history",
|
||||
"type": "STRING",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "ChatGPTOpenAI"
|
||||
},
|
||||
"widgets_values": [
|
||||
null,
|
||||
null,
|
||||
"",
|
||||
"翻译成英文,并按照格式输出: 画面、主题、细节、灯光、氛围、艺术家、其他",
|
||||
"gpt-3.5-turbo",
|
||||
1085,
|
||||
"randomize",
|
||||
1,
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"type": "ShowTextForGPT",
|
||||
"pos": [
|
||||
447,
|
||||
-229
|
||||
],
|
||||
"size": [
|
||||
503.79851499517997,
|
||||
356.0560985581077
|
||||
],
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"link": 4,
|
||||
"widget": {
|
||||
"name": "text"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "STRING",
|
||||
"type": "STRING",
|
||||
"links": null,
|
||||
"shape": 6
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "ShowTextForGPT"
|
||||
},
|
||||
"widgets_values": [
|
||||
"Picture: The picture is composed of carefully chosen elements, capturing the subject matter in a visually striking way.\n\nTheme: The theme of the photograph could vary, from capturing nature's beauty to showcasing urban landscapes, human emotions, or abstract concepts.\n\nDetails: The photograph captures intricate details, bringing attention to the subject's textures, colors, shapes, and patterns.\n\nLighting: The photographer manipulates lighting, using techniques like natural light, dramatic shadows, or artificial lighting to enhance the mood and atmosphere of the photograph.\n\nAmbiance: The photograph evokes a specific ambiance or mood, whether it's serene, mysterious, joyful, melancholic, or any other emotional response.\n\nArtist: The photographer skillfully crafts the image, demonstrating their artistic vision, technical skills, and creative expression through the composition, framing, and post-processing choices.\n\nOthers: Apart from the elements mentioned above, the photograph may also incorporate other creative techniques like long exposure, multiple exposures, color grading, or unconventional perspectives to create a unique and captivating image.\n\nIn the future, as technology and imagination continue to advance, photography will likely continue to evolve and innovate, offering even more realistic and awe-inspiring visual experiences for humans."
|
||||
]
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
2,
|
||||
4,
|
||||
0,
|
||||
2,
|
||||
0,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
3,
|
||||
2,
|
||||
0,
|
||||
5,
|
||||
0,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
4,
|
||||
5,
|
||||
0,
|
||||
3,
|
||||
0,
|
||||
"STRING"
|
||||
]
|
||||
],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {},
|
||||
"version": 0.4
|
||||
}
|
||||
@@ -0,0 +1,731 @@
|
||||
{
|
||||
"last_node_id": 24,
|
||||
"last_link_id": 59,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 7,
|
||||
"type": "CLIPTextEncode",
|
||||
"pos": [
|
||||
515,
|
||||
130
|
||||
],
|
||||
"size": {
|
||||
"0": 400,
|
||||
"1": 200
|
||||
},
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "clip",
|
||||
"type": "CLIP",
|
||||
"link": 33
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "CONDITIONING",
|
||||
"type": "CONDITIONING",
|
||||
"links": [
|
||||
29
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "CLIPTextEncode"
|
||||
},
|
||||
"widgets_values": [
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"type": "CLIPTextEncode",
|
||||
"pos": [
|
||||
515,
|
||||
1268
|
||||
],
|
||||
"size": {
|
||||
"0": 400,
|
||||
"1": 200
|
||||
},
|
||||
"flags": {},
|
||||
"order": 6,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "clip",
|
||||
"type": "CLIP",
|
||||
"link": 32
|
||||
},
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"link": 59,
|
||||
"widget": {
|
||||
"name": "text"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "CONDITIONING",
|
||||
"type": "CONDITIONING",
|
||||
"links": [
|
||||
51
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "CLIPTextEncode"
|
||||
},
|
||||
"widgets_values": [
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"type": "LoraLoader",
|
||||
"pos": [
|
||||
515,
|
||||
460
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 126
|
||||
},
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "model",
|
||||
"type": "MODEL",
|
||||
"link": 31
|
||||
},
|
||||
{
|
||||
"name": "clip",
|
||||
"type": "CLIP",
|
||||
"link": 45
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "MODEL",
|
||||
"type": "MODEL",
|
||||
"links": [
|
||||
44
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
},
|
||||
{
|
||||
"name": "CLIP",
|
||||
"type": "CLIP",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "LoraLoader"
|
||||
},
|
||||
"widgets_values": [
|
||||
"lcm-lora-sdv1-5.safetensors",
|
||||
1,
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"type": "KSampler",
|
||||
"pos": [
|
||||
1015,
|
||||
130
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 262
|
||||
},
|
||||
"flags": {},
|
||||
"order": 7,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "model",
|
||||
"type": "MODEL",
|
||||
"link": 44
|
||||
},
|
||||
{
|
||||
"name": "positive",
|
||||
"type": "CONDITIONING",
|
||||
"link": 51
|
||||
},
|
||||
{
|
||||
"name": "negative",
|
||||
"type": "CONDITIONING",
|
||||
"link": 29
|
||||
},
|
||||
{
|
||||
"name": "latent_image",
|
||||
"type": "LATENT",
|
||||
"link": 54
|
||||
},
|
||||
{
|
||||
"name": "denoise",
|
||||
"type": "FLOAT",
|
||||
"link": 56,
|
||||
"widget": {
|
||||
"name": "denoise"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "LATENT",
|
||||
"type": "LATENT",
|
||||
"links": [
|
||||
35
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "KSampler"
|
||||
},
|
||||
"widgets_values": [
|
||||
758428944049342,
|
||||
"fixed",
|
||||
4,
|
||||
1.6,
|
||||
"lcm",
|
||||
"karras",
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"type": "VAEDecode",
|
||||
"pos": [
|
||||
1430,
|
||||
130
|
||||
],
|
||||
"size": {
|
||||
"0": 210,
|
||||
"1": 46
|
||||
},
|
||||
"flags": {},
|
||||
"order": 8,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "samples",
|
||||
"type": "LATENT",
|
||||
"link": 35
|
||||
},
|
||||
{
|
||||
"name": "vae",
|
||||
"type": "VAE",
|
||||
"link": 34
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
41
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "VAEDecode"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 20,
|
||||
"type": "FloatingVideo",
|
||||
"pos": [
|
||||
1740,
|
||||
130
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 58
|
||||
},
|
||||
"flags": {},
|
||||
"order": 9,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 41
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "FloatingVideo"
|
||||
},
|
||||
"widgets_values": [
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"type": "PreviewImage",
|
||||
"pos": [
|
||||
515,
|
||||
716
|
||||
],
|
||||
"size": {
|
||||
"0": 210,
|
||||
"1": 246
|
||||
},
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 55
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "PreviewImage"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 23,
|
||||
"type": "VAEEncode",
|
||||
"pos": [
|
||||
515,
|
||||
1092
|
||||
],
|
||||
"size": {
|
||||
"0": 210,
|
||||
"1": 46
|
||||
},
|
||||
"flags": {},
|
||||
"order": 5,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "pixels",
|
||||
"type": "IMAGE",
|
||||
"link": 57
|
||||
},
|
||||
{
|
||||
"name": "vae",
|
||||
"type": "VAE",
|
||||
"link": 58
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "LATENT",
|
||||
"type": "LATENT",
|
||||
"links": [
|
||||
54
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "VAEEncode"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"type": "CheckpointLoaderSimple",
|
||||
"pos": [
|
||||
100,
|
||||
130
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 98
|
||||
},
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "MODEL",
|
||||
"type": "MODEL",
|
||||
"links": [
|
||||
31
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
},
|
||||
{
|
||||
"name": "CLIP",
|
||||
"type": "CLIP",
|
||||
"links": [
|
||||
32,
|
||||
33,
|
||||
45
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 1
|
||||
},
|
||||
{
|
||||
"name": "VAE",
|
||||
"type": "VAE",
|
||||
"links": [
|
||||
34,
|
||||
58
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 2
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "CheckpointLoaderSimple"
|
||||
},
|
||||
"widgets_values": [
|
||||
"deliberate_v2.safetensors"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 24,
|
||||
"type": "ScreenShare",
|
||||
"pos": [
|
||||
100,
|
||||
358
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 170
|
||||
},
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
55,
|
||||
57
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
},
|
||||
{
|
||||
"name": "PROMPT",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
59
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 1
|
||||
},
|
||||
{
|
||||
"name": "FLOAT",
|
||||
"type": "FLOAT",
|
||||
"links": [
|
||||
56
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 2
|
||||
},
|
||||
{
|
||||
"name": "INT",
|
||||
"type": "INT",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "ScreenShare"
|
||||
},
|
||||
"widgets_values": [
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
]
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
2,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
3,
|
||||
6,
|
||||
0,
|
||||
5,
|
||||
0,
|
||||
"MODEL"
|
||||
],
|
||||
[
|
||||
5,
|
||||
8,
|
||||
0,
|
||||
5,
|
||||
2,
|
||||
"CONDITIONING"
|
||||
],
|
||||
[
|
||||
6,
|
||||
9,
|
||||
0,
|
||||
5,
|
||||
3,
|
||||
"LATENT"
|
||||
],
|
||||
[
|
||||
7,
|
||||
10,
|
||||
0,
|
||||
6,
|
||||
0,
|
||||
"MODEL"
|
||||
],
|
||||
[
|
||||
8,
|
||||
10,
|
||||
1,
|
||||
6,
|
||||
1,
|
||||
"CLIP"
|
||||
],
|
||||
[
|
||||
9,
|
||||
5,
|
||||
0,
|
||||
11,
|
||||
0,
|
||||
"LATENT"
|
||||
],
|
||||
[
|
||||
10,
|
||||
10,
|
||||
2,
|
||||
11,
|
||||
1,
|
||||
"VAE"
|
||||
],
|
||||
[
|
||||
11,
|
||||
6,
|
||||
1,
|
||||
7,
|
||||
0,
|
||||
"CLIP"
|
||||
],
|
||||
[
|
||||
12,
|
||||
6,
|
||||
1,
|
||||
8,
|
||||
0,
|
||||
"CLIP"
|
||||
],
|
||||
[
|
||||
14,
|
||||
11,
|
||||
0,
|
||||
3,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
17,
|
||||
1,
|
||||
2,
|
||||
7,
|
||||
1,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
18,
|
||||
7,
|
||||
0,
|
||||
15,
|
||||
0,
|
||||
"CONDITIONING"
|
||||
],
|
||||
[
|
||||
19,
|
||||
15,
|
||||
0,
|
||||
5,
|
||||
1,
|
||||
"CONDITIONING"
|
||||
],
|
||||
[
|
||||
20,
|
||||
16,
|
||||
0,
|
||||
15,
|
||||
1,
|
||||
"CONTROL_NET"
|
||||
],
|
||||
[
|
||||
24,
|
||||
1,
|
||||
0,
|
||||
18,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
25,
|
||||
18,
|
||||
0,
|
||||
15,
|
||||
2,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
29,
|
||||
7,
|
||||
0,
|
||||
5,
|
||||
2,
|
||||
"CONDITIONING"
|
||||
],
|
||||
[
|
||||
31,
|
||||
10,
|
||||
0,
|
||||
6,
|
||||
0,
|
||||
"MODEL"
|
||||
],
|
||||
[
|
||||
32,
|
||||
10,
|
||||
1,
|
||||
8,
|
||||
0,
|
||||
"CLIP"
|
||||
],
|
||||
[
|
||||
33,
|
||||
10,
|
||||
1,
|
||||
7,
|
||||
0,
|
||||
"CLIP"
|
||||
],
|
||||
[
|
||||
34,
|
||||
10,
|
||||
2,
|
||||
11,
|
||||
1,
|
||||
"VAE"
|
||||
],
|
||||
[
|
||||
35,
|
||||
5,
|
||||
0,
|
||||
11,
|
||||
0,
|
||||
"LATENT"
|
||||
],
|
||||
[
|
||||
41,
|
||||
11,
|
||||
0,
|
||||
20,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
44,
|
||||
6,
|
||||
0,
|
||||
5,
|
||||
0,
|
||||
"MODEL"
|
||||
],
|
||||
[
|
||||
45,
|
||||
10,
|
||||
1,
|
||||
6,
|
||||
1,
|
||||
"CLIP"
|
||||
],
|
||||
[
|
||||
51,
|
||||
8,
|
||||
0,
|
||||
5,
|
||||
1,
|
||||
"CONDITIONING"
|
||||
],
|
||||
[
|
||||
54,
|
||||
23,
|
||||
0,
|
||||
5,
|
||||
3,
|
||||
"LATENT"
|
||||
],
|
||||
[
|
||||
55,
|
||||
24,
|
||||
0,
|
||||
2,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
56,
|
||||
24,
|
||||
2,
|
||||
5,
|
||||
4,
|
||||
"FLOAT"
|
||||
],
|
||||
[
|
||||
57,
|
||||
24,
|
||||
0,
|
||||
23,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
58,
|
||||
10,
|
||||
2,
|
||||
23,
|
||||
1,
|
||||
"VAE"
|
||||
],
|
||||
[
|
||||
59,
|
||||
24,
|
||||
1,
|
||||
8,
|
||||
1,
|
||||
"STRING"
|
||||
]
|
||||
],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {},
|
||||
"version": 0.4
|
||||
}
|
||||
|
After Width: | Height: | Size: 2.5 MiB |
@@ -0,0 +1,314 @@
|
||||
{
|
||||
"last_node_id": 10,
|
||||
"last_link_id": 16,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 9,
|
||||
"type": "SpeechSynthesis",
|
||||
"pos": [
|
||||
40,
|
||||
356
|
||||
],
|
||||
"size": {
|
||||
"0": 352.8227844238281,
|
||||
"1": 95.3553237915039
|
||||
},
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"link": 12,
|
||||
"widget": {
|
||||
"name": "text"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "STRING",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
13,
|
||||
16
|
||||
],
|
||||
"shape": 6,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "SpeechSynthesis"
|
||||
},
|
||||
"widgets_values": [
|
||||
"作为一个人工智能助手,我没有性别,也无法进行社交互动。我的主要任务是为用户提供帮助和信息。请问有什么问题我可以为您解答吗?"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"type": "SpeechRecognition",
|
||||
"pos": [
|
||||
537,
|
||||
321
|
||||
],
|
||||
"size": {
|
||||
"0": 228.5580291748047,
|
||||
"1": 239.85951232910156
|
||||
},
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "start_by",
|
||||
"type": "INT",
|
||||
"link": 14,
|
||||
"widget": {
|
||||
"name": "start_by"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "prompt",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
15
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "SpeechRecognition"
|
||||
},
|
||||
"widgets_values": [
|
||||
null,
|
||||
1,
|
||||
4,
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"type": "ChatGPTOpenAI",
|
||||
"pos": [
|
||||
-403,
|
||||
14
|
||||
],
|
||||
"size": {
|
||||
"0": 400,
|
||||
"1": 358
|
||||
},
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
12
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
},
|
||||
{
|
||||
"name": "messages",
|
||||
"type": "STRING",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
},
|
||||
{
|
||||
"name": "session_history",
|
||||
"type": "STRING",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "ChatGPTOpenAI"
|
||||
},
|
||||
"widgets_values": [
|
||||
null,
|
||||
null,
|
||||
"跟你一样泡妞",
|
||||
"You are ChatGPT, a large language model trained by OpenAI. Answer as concisely as possible.",
|
||||
"gpt-3.5-turbo",
|
||||
354,
|
||||
"randomize",
|
||||
1,
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"type": "ShowTextForGPT",
|
||||
"pos": [
|
||||
867,
|
||||
349
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 76.00000762939453
|
||||
},
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"link": 15,
|
||||
"widget": {
|
||||
"name": "text"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "STRING",
|
||||
"type": "STRING",
|
||||
"links": null,
|
||||
"shape": 6
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "ShowTextForGPT"
|
||||
},
|
||||
"widgets_values": [
|
||||
"纽斯"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"type": "DynamicDelayProcessor",
|
||||
"pos": [
|
||||
496,
|
||||
4
|
||||
],
|
||||
"size": {
|
||||
"0": 400,
|
||||
"1": 200
|
||||
},
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "any_input",
|
||||
"type": "*",
|
||||
"link": 13
|
||||
},
|
||||
{
|
||||
"name": "delay_by_text",
|
||||
"type": "STRING",
|
||||
"link": 16,
|
||||
"widget": {
|
||||
"name": "delay_by_text"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "output",
|
||||
"type": "*",
|
||||
"links": [
|
||||
14
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "DynamicDelayProcessor"
|
||||
},
|
||||
"widgets_values": [
|
||||
1,
|
||||
"",
|
||||
2.5,
|
||||
"enable",
|
||||
1
|
||||
]
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
4,
|
||||
2,
|
||||
0,
|
||||
5,
|
||||
0,
|
||||
"*"
|
||||
],
|
||||
[
|
||||
7,
|
||||
4,
|
||||
0,
|
||||
6,
|
||||
0,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
8,
|
||||
6,
|
||||
0,
|
||||
5,
|
||||
1,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
9,
|
||||
5,
|
||||
0,
|
||||
7,
|
||||
0,
|
||||
"INT"
|
||||
],
|
||||
[
|
||||
12,
|
||||
4,
|
||||
0,
|
||||
9,
|
||||
0,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
13,
|
||||
9,
|
||||
0,
|
||||
5,
|
||||
0,
|
||||
"*"
|
||||
],
|
||||
[
|
||||
14,
|
||||
5,
|
||||
0,
|
||||
8,
|
||||
0,
|
||||
"INT"
|
||||
],
|
||||
[
|
||||
15,
|
||||
8,
|
||||
0,
|
||||
10,
|
||||
0,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
16,
|
||||
9,
|
||||
0,
|
||||
5,
|
||||
1,
|
||||
"STRING"
|
||||
]
|
||||
],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {},
|
||||
"version": 0.4
|
||||
}
|
||||
@@ -0,0 +1,828 @@
|
||||
{
|
||||
"last_node_id": 23,
|
||||
"last_link_id": 0,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 7,
|
||||
"type": "ResizeImageMixlab",
|
||||
"pos": [
|
||||
1096.229866976564,
|
||||
731.8207909156248
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 106
|
||||
},
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "ResizeImageMixlab"
|
||||
},
|
||||
"widgets_values": [
|
||||
512,
|
||||
512,
|
||||
"width"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"type": "LoadImagesFromPath",
|
||||
"pos": [
|
||||
1101.229866976564,
|
||||
900.8207909156248
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 238
|
||||
},
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": null,
|
||||
"shape": 6
|
||||
},
|
||||
{
|
||||
"name": "MASK",
|
||||
"type": "MASK",
|
||||
"links": null,
|
||||
"shape": 6
|
||||
},
|
||||
{
|
||||
"name": "STRING",
|
||||
"type": "STRING",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "LoadImagesFromPath"
|
||||
},
|
||||
"widgets_values": [
|
||||
"C:\\Users\\38957\\Documents\\GitHub\\extract-anything\\outputs",
|
||||
"disable",
|
||||
"enable",
|
||||
0,
|
||||
"disable",
|
||||
null,
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"type": "ShowTextForGPT",
|
||||
"pos": [
|
||||
1786,
|
||||
252
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 58
|
||||
},
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"link": null,
|
||||
"widget": {
|
||||
"name": "text"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "STRING",
|
||||
"type": "STRING",
|
||||
"links": null,
|
||||
"shape": 6
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "ShowTextForGPT"
|
||||
},
|
||||
"widgets_values": [
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"type": "TextImage",
|
||||
"pos": [
|
||||
1448.4608623625004,
|
||||
735.0514200906249
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 198
|
||||
},
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
},
|
||||
{
|
||||
"name": "MASK",
|
||||
"type": "MASK",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "TextImage"
|
||||
},
|
||||
"widgets_values": [
|
||||
"龍馬精神迎新歲",
|
||||
"C:\\Users\\38957\\Documents\\ai-lab\\ComfyUI_windows_portable\\ComfyUI\\custom_nodes\\comfyui-mixlab-nodes\\assets\\王汉宗颜楷体繁.ttf",
|
||||
100,
|
||||
12,
|
||||
"#000000",
|
||||
true
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"type": "ChatGPTOpenAI",
|
||||
"pos": [
|
||||
1086,
|
||||
235
|
||||
],
|
||||
"size": {
|
||||
"0": 400,
|
||||
"1": 358
|
||||
},
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
},
|
||||
{
|
||||
"name": "messages",
|
||||
"type": "STRING",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
},
|
||||
{
|
||||
"name": "session_history",
|
||||
"type": "STRING",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "ChatGPTOpenAI"
|
||||
},
|
||||
"widgets_values": [
|
||||
null,
|
||||
null,
|
||||
"",
|
||||
"You are ChatGPT, a large language model trained by OpenAI. Answer as concisely as possible.",
|
||||
"gpt-3.5-turbo",
|
||||
0,
|
||||
"randomize",
|
||||
1,
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 16,
|
||||
"type": "SpeechRecognition",
|
||||
"pos": [
|
||||
2892.502226325001,
|
||||
724.176680771093
|
||||
],
|
||||
"size": {
|
||||
"0": 322.593505859375,
|
||||
"1": 135.22850036621094
|
||||
},
|
||||
"flags": {},
|
||||
"order": 5,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "prompt",
|
||||
"type": "STRING",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "SpeechRecognition"
|
||||
},
|
||||
"widgets_values": [
|
||||
null,
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 17,
|
||||
"type": "SpeechSynthesis",
|
||||
"pos": [
|
||||
2901.502226325001,
|
||||
924.1766807710948
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 58
|
||||
},
|
||||
"flags": {},
|
||||
"order": 6,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"link": null,
|
||||
"widget": {
|
||||
"name": "text"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "STRING",
|
||||
"type": "STRING",
|
||||
"links": null,
|
||||
"shape": 6
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "SpeechSynthesis"
|
||||
},
|
||||
"widgets_values": [
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 18,
|
||||
"type": "ShowLayer",
|
||||
"pos": [
|
||||
2157.913220668749,
|
||||
232.7887044208986
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 226
|
||||
},
|
||||
"flags": {},
|
||||
"order": 7,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "layers",
|
||||
"type": "LAYER",
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "ShowLayer"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 19,
|
||||
"type": "NewLayer",
|
||||
"pos": [
|
||||
2494.913220668749,
|
||||
232.7887044208986
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 218
|
||||
},
|
||||
"flags": {},
|
||||
"order": 8,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"name": "mask",
|
||||
"type": "MASK",
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"name": "layers",
|
||||
"type": "LAYER",
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "layers",
|
||||
"type": "LAYER",
|
||||
"links": null,
|
||||
"shape": 6
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "NewLayer"
|
||||
},
|
||||
"widgets_values": [
|
||||
0,
|
||||
0,
|
||||
512,
|
||||
512,
|
||||
0,
|
||||
"width"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 20,
|
||||
"type": "MergeLayers",
|
||||
"pos": [
|
||||
2163.913220668749,
|
||||
515.7887044208991
|
||||
],
|
||||
"size": {
|
||||
"0": 315.47509765625,
|
||||
"1": 69.53152465820312
|
||||
},
|
||||
"flags": {},
|
||||
"order": 9,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "layers",
|
||||
"type": "LAYER",
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "MergeLayers"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"type": "CharacterInText",
|
||||
"pos": [
|
||||
1778,
|
||||
386
|
||||
],
|
||||
"size": {
|
||||
"0": 319.45068359375,
|
||||
"1": 194.45606994628906
|
||||
},
|
||||
"flags": {},
|
||||
"order": 10,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "INT",
|
||||
"type": "INT",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "CharacterInText"
|
||||
},
|
||||
"widgets_values": [
|
||||
"",
|
||||
"",
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 21,
|
||||
"type": "Note",
|
||||
"pos": [
|
||||
2507.333181606249,
|
||||
517.1286262958992
|
||||
],
|
||||
"size": {
|
||||
"0": 210,
|
||||
"1": 58
|
||||
},
|
||||
"flags": {},
|
||||
"order": 11,
|
||||
"mode": 0,
|
||||
"properties": {
|
||||
"text": ""
|
||||
},
|
||||
"widgets_values": [
|
||||
"TODO:可视化操作"
|
||||
],
|
||||
"color": "#323",
|
||||
"bgcolor": "#535"
|
||||
},
|
||||
{
|
||||
"id": 22,
|
||||
"type": "Note",
|
||||
"pos": [
|
||||
1533,
|
||||
347
|
||||
],
|
||||
"size": {
|
||||
"0": 210,
|
||||
"1": 58
|
||||
},
|
||||
"flags": {},
|
||||
"order": 12,
|
||||
"mode": 0,
|
||||
"properties": {
|
||||
"text": ""
|
||||
},
|
||||
"widgets_values": [
|
||||
"funtion call"
|
||||
],
|
||||
"color": "#323",
|
||||
"bgcolor": "#535"
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"type": "3DImage",
|
||||
"pos": [
|
||||
1794.6918193937495,
|
||||
724.2820719460937
|
||||
],
|
||||
"size": {
|
||||
"0": 309.2974548339844,
|
||||
"1": 433.12554931640625
|
||||
},
|
||||
"flags": {},
|
||||
"order": 13,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "material",
|
||||
"type": "IMAGE",
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
},
|
||||
{
|
||||
"name": "MASK",
|
||||
"type": "MASK",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
},
|
||||
{
|
||||
"name": "BG_IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
},
|
||||
{
|
||||
"name": "MATERIAL",
|
||||
"type": "IMAGE",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "3DImage"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 13,
|
||||
"type": "ScreenShare",
|
||||
"pos": [
|
||||
2138.691819393749,
|
||||
728.2820719460937
|
||||
],
|
||||
"size": {
|
||||
"0": 322.9458923339844,
|
||||
"1": 594.0604858398438
|
||||
},
|
||||
"flags": {},
|
||||
"order": 14,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
},
|
||||
{
|
||||
"name": "PROMPT",
|
||||
"type": "STRING",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
},
|
||||
{
|
||||
"name": "FLOAT",
|
||||
"type": "FLOAT",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
},
|
||||
{
|
||||
"name": "INT",
|
||||
"type": "INT",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "ScreenShare"
|
||||
},
|
||||
"widgets_values": [
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 14,
|
||||
"type": "FloatingVideo",
|
||||
"pos": [
|
||||
2486.691819393749,
|
||||
726.2820719460937
|
||||
],
|
||||
"size": {
|
||||
"0": 302.3232421875,
|
||||
"1": 303.61279296875
|
||||
},
|
||||
"flags": {},
|
||||
"order": 15,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "FloatingVideo"
|
||||
},
|
||||
"widgets_values": [
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"type": "TransparentImage",
|
||||
"pos": [
|
||||
2496.691819393749,
|
||||
1157.2820719460938
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 146
|
||||
},
|
||||
"flags": {},
|
||||
"order": 16,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"name": "masks",
|
||||
"type": "MASK",
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "file_path",
|
||||
"type": "STRING",
|
||||
"links": null,
|
||||
"shape": 6
|
||||
},
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": null,
|
||||
"shape": 6
|
||||
},
|
||||
{
|
||||
"name": "RGBA",
|
||||
"type": "RGBA",
|
||||
"links": null,
|
||||
"shape": 6
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "TransparentImage"
|
||||
},
|
||||
"widgets_values": [
|
||||
"yes",
|
||||
"yes",
|
||||
"Mixlab_save"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 12,
|
||||
"type": "ImageCropByAlpha",
|
||||
"pos": [
|
||||
1806.6918193937495,
|
||||
1240.2820719460938
|
||||
],
|
||||
"size": {
|
||||
"0": 289.27215576171875,
|
||||
"1": 46
|
||||
},
|
||||
"flags": {},
|
||||
"order": 17,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"name": "RGBA",
|
||||
"type": "RGBA",
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "ImageCropByAlpha"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"type": "EnhanceImage",
|
||||
"pos": [
|
||||
1098.6918193937504,
|
||||
1217.2820719460938
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 58
|
||||
},
|
||||
"flags": {},
|
||||
"order": 18,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "EnhanceImage"
|
||||
},
|
||||
"widgets_values": [
|
||||
0.5
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"type": "SvgImage",
|
||||
"pos": [
|
||||
1454.6918193937504,
|
||||
1074.2820719460938
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 170
|
||||
},
|
||||
"flags": {},
|
||||
"order": 19,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
},
|
||||
{
|
||||
"name": "layers",
|
||||
"type": "LAYER",
|
||||
"links": null,
|
||||
"shape": 6
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "SvgImage"
|
||||
},
|
||||
"widgets_values": [
|
||||
null,
|
||||
null
|
||||
]
|
||||
}
|
||||
],
|
||||
"links": [],
|
||||
"groups": [
|
||||
{
|
||||
"title": "GPT",
|
||||
"bounding": [
|
||||
1083,
|
||||
157,
|
||||
1039,
|
||||
450
|
||||
],
|
||||
"color": "#b06634",
|
||||
"font_size": 24,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"title": "Image",
|
||||
"bounding": [
|
||||
1086,
|
||||
650,
|
||||
1735,
|
||||
682
|
||||
],
|
||||
"color": "#A88",
|
||||
"font_size": 24,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"title": "Audio",
|
||||
"bounding": [
|
||||
2882,
|
||||
650,
|
||||
344,
|
||||
342
|
||||
],
|
||||
"color": "#8A8",
|
||||
"font_size": 24,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"title": "Layer",
|
||||
"bounding": [
|
||||
2148,
|
||||
158,
|
||||
672,
|
||||
437
|
||||
],
|
||||
"color": "#a1309b",
|
||||
"font_size": 24,
|
||||
"locked": false
|
||||
}
|
||||
],
|
||||
"config": {},
|
||||
"extra": {},
|
||||
"version": 0.4
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||

|
||||

|
||||
|
||||
@@ -0,0 +1,862 @@
|
||||
{
|
||||
"last_node_id": 28,
|
||||
"last_link_id": 64,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 7,
|
||||
"type": "CLIPTextEncode",
|
||||
"pos": [
|
||||
515,
|
||||
130
|
||||
],
|
||||
"size": {
|
||||
"0": 400,
|
||||
"1": 200
|
||||
},
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "clip",
|
||||
"type": "CLIP",
|
||||
"link": 33
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "CONDITIONING",
|
||||
"type": "CONDITIONING",
|
||||
"links": [
|
||||
29
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "CLIPTextEncode"
|
||||
},
|
||||
"widgets_values": [
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"type": "LoraLoader",
|
||||
"pos": [
|
||||
515,
|
||||
460
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 126
|
||||
},
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "model",
|
||||
"type": "MODEL",
|
||||
"link": 31
|
||||
},
|
||||
{
|
||||
"name": "clip",
|
||||
"type": "CLIP",
|
||||
"link": 45
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "MODEL",
|
||||
"type": "MODEL",
|
||||
"links": [
|
||||
44
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
},
|
||||
{
|
||||
"name": "CLIP",
|
||||
"type": "CLIP",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "LoraLoader"
|
||||
},
|
||||
"widgets_values": [
|
||||
"lcm-lora-sdv1-5.safetensors",
|
||||
1,
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"type": "VAEDecode",
|
||||
"pos": [
|
||||
1430,
|
||||
130
|
||||
],
|
||||
"size": {
|
||||
"0": 210,
|
||||
"1": 46
|
||||
},
|
||||
"flags": {},
|
||||
"order": 11,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "samples",
|
||||
"type": "LATENT",
|
||||
"link": 35
|
||||
},
|
||||
{
|
||||
"name": "vae",
|
||||
"type": "VAE",
|
||||
"link": 34
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
41
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "VAEDecode"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 23,
|
||||
"type": "VAEEncode",
|
||||
"pos": [
|
||||
515,
|
||||
1092
|
||||
],
|
||||
"size": {
|
||||
"0": 210,
|
||||
"1": 46
|
||||
},
|
||||
"flags": {},
|
||||
"order": 6,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "pixels",
|
||||
"type": "IMAGE",
|
||||
"link": 57
|
||||
},
|
||||
{
|
||||
"name": "vae",
|
||||
"type": "VAE",
|
||||
"link": 58
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "LATENT",
|
||||
"type": "LATENT",
|
||||
"links": [
|
||||
54
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "VAEEncode"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"type": "CheckpointLoaderSimple",
|
||||
"pos": [
|
||||
100,
|
||||
130
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 98
|
||||
},
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "MODEL",
|
||||
"type": "MODEL",
|
||||
"links": [
|
||||
31
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
},
|
||||
{
|
||||
"name": "CLIP",
|
||||
"type": "CLIP",
|
||||
"links": [
|
||||
32,
|
||||
33,
|
||||
45
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 1
|
||||
},
|
||||
{
|
||||
"name": "VAE",
|
||||
"type": "VAE",
|
||||
"links": [
|
||||
34,
|
||||
58
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 2
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "CheckpointLoaderSimple"
|
||||
},
|
||||
"widgets_values": [
|
||||
"deliberate_v2.safetensors"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"type": "CLIPTextEncode",
|
||||
"pos": [
|
||||
515,
|
||||
1268
|
||||
],
|
||||
"size": {
|
||||
"0": 400,
|
||||
"1": 200
|
||||
},
|
||||
"flags": {},
|
||||
"order": 8,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "clip",
|
||||
"type": "CLIP",
|
||||
"link": 32
|
||||
},
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"link": 62,
|
||||
"widget": {
|
||||
"name": "text"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "CONDITIONING",
|
||||
"type": "CONDITIONING",
|
||||
"links": [
|
||||
51
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "CLIPTextEncode"
|
||||
},
|
||||
"widgets_values": [
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 20,
|
||||
"type": "FloatingVideo",
|
||||
"pos": [
|
||||
1274,
|
||||
536
|
||||
],
|
||||
"size": [
|
||||
315,
|
||||
58
|
||||
],
|
||||
"flags": {},
|
||||
"order": 12,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 41
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "FloatingVideo"
|
||||
},
|
||||
"widgets_values": [
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"type": "KSampler",
|
||||
"pos": [
|
||||
1004,
|
||||
130
|
||||
],
|
||||
"size": [
|
||||
315,
|
||||
262
|
||||
],
|
||||
"flags": {},
|
||||
"order": 10,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "model",
|
||||
"type": "MODEL",
|
||||
"link": 44
|
||||
},
|
||||
{
|
||||
"name": "positive",
|
||||
"type": "CONDITIONING",
|
||||
"link": 51
|
||||
},
|
||||
{
|
||||
"name": "negative",
|
||||
"type": "CONDITIONING",
|
||||
"link": 29
|
||||
},
|
||||
{
|
||||
"name": "latent_image",
|
||||
"type": "LATENT",
|
||||
"link": 54
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "LATENT",
|
||||
"type": "LATENT",
|
||||
"links": [
|
||||
35
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "KSampler"
|
||||
},
|
||||
"widgets_values": [
|
||||
758428944049342,
|
||||
"fixed",
|
||||
4,
|
||||
1.6,
|
||||
"lcm",
|
||||
"karras",
|
||||
0.61
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 24,
|
||||
"type": "ScreenShare",
|
||||
"pos": [
|
||||
62,
|
||||
543
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 194
|
||||
},
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
55,
|
||||
57
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
},
|
||||
{
|
||||
"name": "PROMPT",
|
||||
"type": "STRING",
|
||||
"links": [],
|
||||
"shape": 3,
|
||||
"slot_index": 1
|
||||
},
|
||||
{
|
||||
"name": "FLOAT",
|
||||
"type": "FLOAT",
|
||||
"links": [],
|
||||
"shape": 3,
|
||||
"slot_index": 2
|
||||
},
|
||||
{
|
||||
"name": "INT",
|
||||
"type": "INT",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "ScreenShare"
|
||||
},
|
||||
"widgets_values": [
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"type": "PreviewImage",
|
||||
"pos": [
|
||||
-416,
|
||||
516
|
||||
],
|
||||
"size": {
|
||||
"0": 210,
|
||||
"1": 246
|
||||
},
|
||||
"flags": {},
|
||||
"order": 5,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 55
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "PreviewImage"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 26,
|
||||
"type": "ChatGPTOpenAI",
|
||||
"pos": [
|
||||
5,
|
||||
1325
|
||||
],
|
||||
"size": [
|
||||
400,
|
||||
357.99993896484375
|
||||
],
|
||||
"flags": {},
|
||||
"order": 7,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "prompt",
|
||||
"type": "STRING",
|
||||
"link": 61,
|
||||
"widget": {
|
||||
"name": "prompt"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
62,
|
||||
63
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
},
|
||||
{
|
||||
"name": "messages",
|
||||
"type": "STRING",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
},
|
||||
{
|
||||
"name": "session_history",
|
||||
"type": "STRING",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "ChatGPTOpenAI"
|
||||
},
|
||||
"widgets_values": [
|
||||
null,
|
||||
null,
|
||||
"",
|
||||
"你需要根据用户的输入,转化为英文单词的短句子组合,输出英文",
|
||||
"gpt-35-turbo",
|
||||
1249,
|
||||
"randomize",
|
||||
1,
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 28,
|
||||
"type": "SpeechSynthesis",
|
||||
"pos": [
|
||||
542,
|
||||
1570
|
||||
],
|
||||
"size": [
|
||||
315,
|
||||
76
|
||||
],
|
||||
"flags": {},
|
||||
"order": 9,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"link": 63,
|
||||
"widget": {
|
||||
"name": "text"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "STRING",
|
||||
"type": "STRING",
|
||||
"links": null,
|
||||
"shape": 6
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "SpeechSynthesis"
|
||||
},
|
||||
"widgets_values": [
|
||||
"A very handsome elderly gentleman."
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 25,
|
||||
"type": "SpeechRecognition",
|
||||
"pos": [
|
||||
-5,
|
||||
1101
|
||||
],
|
||||
"size": [
|
||||
392.5606180664065,
|
||||
166.64923671875022
|
||||
],
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "prompt",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
61
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "SpeechRecognition"
|
||||
},
|
||||
"widgets_values": [
|
||||
null,
|
||||
null
|
||||
]
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
2,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
3,
|
||||
6,
|
||||
0,
|
||||
5,
|
||||
0,
|
||||
"MODEL"
|
||||
],
|
||||
[
|
||||
5,
|
||||
8,
|
||||
0,
|
||||
5,
|
||||
2,
|
||||
"CONDITIONING"
|
||||
],
|
||||
[
|
||||
6,
|
||||
9,
|
||||
0,
|
||||
5,
|
||||
3,
|
||||
"LATENT"
|
||||
],
|
||||
[
|
||||
7,
|
||||
10,
|
||||
0,
|
||||
6,
|
||||
0,
|
||||
"MODEL"
|
||||
],
|
||||
[
|
||||
8,
|
||||
10,
|
||||
1,
|
||||
6,
|
||||
1,
|
||||
"CLIP"
|
||||
],
|
||||
[
|
||||
9,
|
||||
5,
|
||||
0,
|
||||
11,
|
||||
0,
|
||||
"LATENT"
|
||||
],
|
||||
[
|
||||
10,
|
||||
10,
|
||||
2,
|
||||
11,
|
||||
1,
|
||||
"VAE"
|
||||
],
|
||||
[
|
||||
11,
|
||||
6,
|
||||
1,
|
||||
7,
|
||||
0,
|
||||
"CLIP"
|
||||
],
|
||||
[
|
||||
12,
|
||||
6,
|
||||
1,
|
||||
8,
|
||||
0,
|
||||
"CLIP"
|
||||
],
|
||||
[
|
||||
14,
|
||||
11,
|
||||
0,
|
||||
3,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
17,
|
||||
1,
|
||||
2,
|
||||
7,
|
||||
1,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
18,
|
||||
7,
|
||||
0,
|
||||
15,
|
||||
0,
|
||||
"CONDITIONING"
|
||||
],
|
||||
[
|
||||
19,
|
||||
15,
|
||||
0,
|
||||
5,
|
||||
1,
|
||||
"CONDITIONING"
|
||||
],
|
||||
[
|
||||
20,
|
||||
16,
|
||||
0,
|
||||
15,
|
||||
1,
|
||||
"CONTROL_NET"
|
||||
],
|
||||
[
|
||||
24,
|
||||
1,
|
||||
0,
|
||||
18,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
25,
|
||||
18,
|
||||
0,
|
||||
15,
|
||||
2,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
29,
|
||||
7,
|
||||
0,
|
||||
5,
|
||||
2,
|
||||
"CONDITIONING"
|
||||
],
|
||||
[
|
||||
31,
|
||||
10,
|
||||
0,
|
||||
6,
|
||||
0,
|
||||
"MODEL"
|
||||
],
|
||||
[
|
||||
32,
|
||||
10,
|
||||
1,
|
||||
8,
|
||||
0,
|
||||
"CLIP"
|
||||
],
|
||||
[
|
||||
33,
|
||||
10,
|
||||
1,
|
||||
7,
|
||||
0,
|
||||
"CLIP"
|
||||
],
|
||||
[
|
||||
34,
|
||||
10,
|
||||
2,
|
||||
11,
|
||||
1,
|
||||
"VAE"
|
||||
],
|
||||
[
|
||||
35,
|
||||
5,
|
||||
0,
|
||||
11,
|
||||
0,
|
||||
"LATENT"
|
||||
],
|
||||
[
|
||||
41,
|
||||
11,
|
||||
0,
|
||||
20,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
44,
|
||||
6,
|
||||
0,
|
||||
5,
|
||||
0,
|
||||
"MODEL"
|
||||
],
|
||||
[
|
||||
45,
|
||||
10,
|
||||
1,
|
||||
6,
|
||||
1,
|
||||
"CLIP"
|
||||
],
|
||||
[
|
||||
51,
|
||||
8,
|
||||
0,
|
||||
5,
|
||||
1,
|
||||
"CONDITIONING"
|
||||
],
|
||||
[
|
||||
54,
|
||||
23,
|
||||
0,
|
||||
5,
|
||||
3,
|
||||
"LATENT"
|
||||
],
|
||||
[
|
||||
55,
|
||||
24,
|
||||
0,
|
||||
2,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
57,
|
||||
24,
|
||||
0,
|
||||
23,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
58,
|
||||
10,
|
||||
2,
|
||||
23,
|
||||
1,
|
||||
"VAE"
|
||||
],
|
||||
[
|
||||
61,
|
||||
25,
|
||||
0,
|
||||
26,
|
||||
0,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
62,
|
||||
26,
|
||||
0,
|
||||
8,
|
||||
1,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
63,
|
||||
26,
|
||||
0,
|
||||
28,
|
||||
0,
|
||||
"STRING"
|
||||
]
|
||||
],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {},
|
||||
"version": 0.4
|
||||
}
|
||||