Compare commits
14
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5c9dd80370 | ||
|
|
727f5f2e48 | ||
|
|
1c238f7697 | ||
|
|
119d7cce15 | ||
|
|
4afc8f6083 | ||
|
|
a9ec3af066 | ||
|
|
9edae81fee | ||
|
|
b941b12f12 | ||
|
|
3069de188a | ||
|
|
a968f08abd | ||
|
|
4153d3e5ff | ||
|
|
9d9c1a6c84 | ||
|
|
16ef10a4d9 | ||
|
|
b3766e440a |
+2
-1
@@ -2,4 +2,5 @@ __pycache__/
|
||||
https/
|
||||
nodes/config.json
|
||||
workflow/my_workflow.json
|
||||
workflow/my_workflow_app.json
|
||||
workflow/my_workflow_app.json
|
||||
app/*
|
||||
@@ -1,21 +1,27 @@
|
||||
##
|
||||
v0.6.0 🚀🚗🚚🏃 Workflow-to-APP
|
||||
v0.7.0 🚀🚗🚚🏃 Workflow-to-APP
|
||||
- 支持多个web app 切换
|
||||
- 新增AppInfo节点,可以通过简单的配置,把workflow转变为一个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](./app/text-to-image_1_Wed%20Dec%2027%202023.json)
|
||||
- [image-to-image](./app/image-to-image_1_Wed%20Dec%2027%202023.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、CheckpointLoaderSimple、LoraLoader
|
||||
> 暂时支持6种节点作为界面上的输入节点:Load Image、CLIPTextEncode、TextInput_、FloatSlider、IntNumber、CheckpointLoaderSimple、LoraLoader
|
||||
|
||||
> 输出节点:PreviewImage 、SaveImage、ShowTextForGPT
|
||||
> 输出节点:PreviewImage 、SaveImage、ShowTextForGPT、VHS_VideoCombine
|
||||
|
||||
|
||||
### 3D
|
||||
|
||||
+98
-19
@@ -4,7 +4,7 @@ import subprocess
|
||||
import importlib.util
|
||||
import sys,json
|
||||
import urllib
|
||||
|
||||
import hashlib
|
||||
import datetime
|
||||
|
||||
|
||||
@@ -79,6 +79,13 @@ 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
|
||||
# 生成自签名证书
|
||||
@@ -162,17 +169,75 @@ def get_workflows():
|
||||
workflows=read_workflow_json_files(workflow_path)
|
||||
return workflows
|
||||
|
||||
def get_my_workflow_for_app():
|
||||
# print("#####path::", current_path)
|
||||
workflow_path=os.path.join(current_path, "workflow/my_workflow_app.json")
|
||||
print('workflow_path: ',workflow_path)
|
||||
json_data={}
|
||||
try:
|
||||
with open(workflow_path) as json_file:
|
||||
json_data = json.load(json_file)
|
||||
except:
|
||||
print('-')
|
||||
return json_data
|
||||
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")
|
||||
@@ -180,11 +245,22 @@ def save_workflow_json(data):
|
||||
json.dump(data, file)
|
||||
return workflow_path
|
||||
|
||||
def save_workflow_for_app(data):
|
||||
workflow_path=os.path.join(current_path, "workflow/my_workflow_app.json")
|
||||
with open(workflow_path, 'w') as file:
|
||||
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 workflow_path
|
||||
return filename
|
||||
|
||||
def get_nodes_map():
|
||||
# print("#####path::", current_path)
|
||||
@@ -295,14 +371,17 @@ async def mixlab_workflow_hander(request):
|
||||
'file_path':file_path
|
||||
}
|
||||
elif data['task']=='save_app':
|
||||
file_path=save_workflow_for_app(data['data'])
|
||||
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(),
|
||||
'data':get_my_workflow_for_app(filename),
|
||||
'status':'success',
|
||||
}
|
||||
elif data['task']=='list':
|
||||
@@ -359,7 +438,7 @@ PromptServer.add_routes=new_add_routes
|
||||
|
||||
# 导入节点
|
||||
from .nodes.PromptNode import RandomPrompt
|
||||
from .nodes.ImageNode import NoiseImage,TransparentImage,LoadImagesFromPath,LoadImagesFromURL,ResizeImage,TextImage,SvgImage,Image3D,EmptyLayer,ShowLayer,NewLayer,MergeLayers,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
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 101 KiB |
+5
-5
@@ -75,11 +75,11 @@ class ChatGPTNode:
|
||||
"required": {
|
||||
"api_key":("KEY", {"default": "", "multiline": True}),
|
||||
"api_url":("URL", {"default": "", "multiline": True}),
|
||||
"prompt": ("STRING", {"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
|
||||
"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"}),
|
||||
@@ -167,7 +167,7 @@ class ShowTextForGPT:
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"text": ("STRING", {"forceInput": True}),
|
||||
"text": ("STRING", {"forceInput": True,"dynamicPrompts": False}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,8 +189,8 @@ class CharacterInText:
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"text": ("STRING", {"multiline": True}),
|
||||
"character": ("STRING", {"multiline": True}),
|
||||
"text": ("STRING", {"multiline": True,"dynamicPrompts": False}),
|
||||
"character": ("STRING", {"multiline": True,"dynamicPrompts": False}),
|
||||
"start_index": ("INT", {
|
||||
"default": 1,
|
||||
"min": 0, #Minimum value
|
||||
|
||||
+7
-6
@@ -369,14 +369,14 @@ class AppInfo:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"name": ("STRING",{"multiline": False,"default": "Mixlab-App"}),
|
||||
"name": ("STRING",{"multiline": False,"default": "Mixlab-App","dynamicPrompts": False}),
|
||||
"image": ("IMAGE",),
|
||||
"input_ids":("STRING",{"multiline": True,"default": "\n".join(["1","2","3"])}),
|
||||
"output_ids":("STRING",{"multiline": True,"default": "\n".join(["5","9"])}),
|
||||
"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": ""}),
|
||||
"description":("STRING",{"multiline": True,"default": "","dynamicPrompts": False}),
|
||||
"version":("INT", {
|
||||
"default": 1,
|
||||
"min": 1,
|
||||
@@ -384,6 +384,7 @@ class AppInfo:
|
||||
"step": 1,
|
||||
"display": "number"
|
||||
}),
|
||||
"share_prefix":("STRING",{"multiline": False,"default": "","dynamicPrompts": False}),
|
||||
}
|
||||
|
||||
}
|
||||
@@ -398,13 +399,13 @@ class AppInfo:
|
||||
INPUT_IS_LIST = False
|
||||
OUTPUT_IS_LIST = (False,)
|
||||
|
||||
def run(self,name,image,input_ids,output_ids,description,version):
|
||||
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]}, "result": (image,)}
|
||||
return {"ui": {"json": [name,im,input_ids,output_ids,description,version,share_prefix]}, "result": (image,)}
|
||||
|
||||
|
||||
|
||||
|
||||
+442
-74
@@ -13,7 +13,77 @@
|
||||
margin-left: 5%;
|
||||
}
|
||||
|
||||
.apps {
|
||||
margin: 0 32px;
|
||||
background: whitesmoke;
|
||||
color: black;
|
||||
padding: 12px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.apps .content {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
|
||||
.apps .card {
|
||||
width: 200px;
|
||||
margin: 12px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
background: #f8f8f8;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.apps .selected {
|
||||
box-shadow: 0px 0px 10px 10px #fbe9f0
|
||||
}
|
||||
|
||||
.apps .card:hover {
|
||||
box-shadow: 0px 0px 10px 10px #e9fbfa
|
||||
}
|
||||
|
||||
.apps .card h5 {
|
||||
font-size: 14px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.apps .card p {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.apps .card img {
|
||||
width: auto;
|
||||
height: 100%;
|
||||
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
.apps .card .item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.apps .card .icon {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
overflow: hidden;
|
||||
background: #e3e3e3;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
|
||||
.apps .card .version {
|
||||
font-size: 12px;
|
||||
|
||||
}
|
||||
|
||||
.status {
|
||||
background: black;
|
||||
@@ -42,17 +112,24 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 400px;
|
||||
background: #eee;
|
||||
/* background: #eee; */
|
||||
margin: 24px;
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
/* justify-content: center; */
|
||||
}
|
||||
|
||||
.panel .header {
|
||||
margin-bottom: 8px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
}
|
||||
|
||||
.panel h1 {
|
||||
padding: 0 12px;
|
||||
margin-top: 12px;
|
||||
margin-bottom: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.panel img,
|
||||
@@ -68,13 +145,18 @@
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.output {
|
||||
width: 90%;
|
||||
margin: 12px;
|
||||
}
|
||||
|
||||
.output_card {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
box-shadow: 0px 0px 8px 3px #e6e7e7;
|
||||
|
||||
box-shadow: 0px 0px 20px 7px #e6e7e7;
|
||||
margin-top: 24px;
|
||||
display: flex;
|
||||
|
||||
flex-wrap: wrap;
|
||||
/* justify-content: center;
|
||||
align-items: center; */
|
||||
|
||||
@@ -84,6 +166,8 @@
|
||||
video {
|
||||
max-width: 400px;
|
||||
max-height: 600px;
|
||||
margin: 8px;
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
.card {
|
||||
@@ -125,7 +209,7 @@
|
||||
border: 3px solid;
|
||||
}
|
||||
|
||||
.run_btn:hover {
|
||||
button:hover {
|
||||
border-color: yellow;
|
||||
color: yellow;
|
||||
}
|
||||
@@ -138,28 +222,28 @@
|
||||
.upload_btn {
|
||||
width: 188px;
|
||||
cursor: pointer;
|
||||
height: 188px;
|
||||
background: black;
|
||||
/* height: 188px; */
|
||||
/* background: black; */
|
||||
color: white;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
margin-left: calc(50% - 94px);
|
||||
margin-top: calc(30vh - 94px);
|
||||
color: black;
|
||||
}
|
||||
|
||||
.upload_btn:hover {
|
||||
/* .upload_btn:hover {
|
||||
outline: 4px solid yellow;
|
||||
color: yellow;
|
||||
}
|
||||
} */
|
||||
|
||||
.show_text {
|
||||
font-size: 14px;
|
||||
/* display: inline-block; */
|
||||
/* margin: 13px; */
|
||||
margin: 8px;
|
||||
padding: 32px;
|
||||
min-width: 200px;
|
||||
background: #242424;
|
||||
color: white;
|
||||
}
|
||||
@@ -170,15 +254,39 @@
|
||||
font-size: 12px;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
label::after {
|
||||
content: attr(data-content);
|
||||
/* Set the initial content using the data-content attribute */
|
||||
/* position: absolute; */
|
||||
/* top: 100%;
|
||||
left: 0; */
|
||||
margin-left: 4px;
|
||||
font-size: 12px;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
select,
|
||||
button,
|
||||
input {
|
||||
height: 32px;
|
||||
cursor: pointer;
|
||||
background: #000000bf;
|
||||
color: white;
|
||||
outline: none;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
}
|
||||
</style>
|
||||
<!-- <script src="../../../scripts/api.js" type="module"></script> -->
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div style="margin: 24px;
|
||||
background: #eee;
|
||||
padding: 24px;
|
||||
color: #4a4a4a;">Explore your creative potential with <a class="link"
|
||||
<div style="margin: 0 24px;
|
||||
margin-bottom: 24px;
|
||||
padding: 8px;
|
||||
color: #4a4a4a;
|
||||
border-bottom: 1px dashed #595959;">Explore your creative potential with <a class="link"
|
||||
href="https://github.com/shadowcz007/comfyui-mixlab-nodes" target="_blank">mixlab-nodes</a> / 尽情发挥你的创意
|
||||
<br>
|
||||
<a class="link" href="https://www.mixcomfy.com" target="_blank">ComfyUI中文爱好者社区推荐</a>
|
||||
@@ -258,46 +366,104 @@
|
||||
});
|
||||
}
|
||||
|
||||
async function get_my_app() {
|
||||
|
||||
function success(isSuccess, btn, text) {
|
||||
isSuccess ? btn.innerText = 'success' : text;
|
||||
setTimeout(() => {
|
||||
btn.innerText = text;
|
||||
}, 5000)
|
||||
}
|
||||
|
||||
async function get_my_app(filename = null) {
|
||||
|
||||
let url = get_url()
|
||||
|
||||
const res = await fetch(`${url}/mixlab/workflow`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
task: 'my_app'
|
||||
task: 'my_app',
|
||||
filename
|
||||
})
|
||||
})
|
||||
let result = await res.json();
|
||||
let data = [];
|
||||
try {
|
||||
for (const res of result.data) {
|
||||
let { output, app } = res.data;
|
||||
if (app.filename) data.push({
|
||||
...app,
|
||||
data: output,
|
||||
date: res.date
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
let { output, app } = result.data
|
||||
|
||||
return {
|
||||
...app,
|
||||
data: output
|
||||
}
|
||||
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
|
||||
function createOutputs(outputData) {
|
||||
// Array.from( window._appData.output,n=>n.id)
|
||||
const container = document.createElement("div");
|
||||
container.className = 'output_card'
|
||||
const container = document.createElement('div');
|
||||
container.className = "output";
|
||||
|
||||
const action = document.createElement('div');
|
||||
container.appendChild(action)
|
||||
|
||||
const copyHTML = document.createElement('button');
|
||||
copyHTML.innerText = 'copy as html'
|
||||
action.appendChild(copyHTML)
|
||||
|
||||
const copyImage = document.createElement('button');
|
||||
copyImage.innerText = 'copy image'
|
||||
action.appendChild(copyImage)
|
||||
copyImage.style.marginLeft = '18px';
|
||||
|
||||
const copyText = document.createElement('button');
|
||||
copyText.innerText = 'copy text for share'
|
||||
action.appendChild(copyText)
|
||||
copyText.style.marginLeft = '18px';
|
||||
|
||||
const output_card = document.createElement("div");
|
||||
output_card.className = 'output_card'
|
||||
container.appendChild(output_card)
|
||||
|
||||
|
||||
|
||||
copyText.addEventListener('click', e => {
|
||||
e.preventDefault();
|
||||
copyTextToClipboard((window._appData.share_prefix || '') + " " + output_card.outerHTML, (r) => success(r, copyText, 'copy text for share'))
|
||||
|
||||
})
|
||||
|
||||
copyImage.addEventListener('click', e => {
|
||||
e.preventDefault();
|
||||
// copyHtmlWithImagesToClipboard(output_card.outerHTML)
|
||||
copyImagesToClipboard(output_card.outerHTML, (r) => success(r, copyImage, 'copy image'))
|
||||
// copyTextToClipboard()
|
||||
})
|
||||
copyHTML.addEventListener('click', e => {
|
||||
e.preventDefault();
|
||||
copyHtmlWithImagesToClipboard((window._appData.share_prefix || '') + " " + output_card.outerHTML, (r) => success(r, copyHTML, 'copy as html'))
|
||||
// copyImagesToClipboard(output_card.outerHTML)
|
||||
})
|
||||
|
||||
for (const node of outputData) {
|
||||
console.log('output', node)
|
||||
// console.log('output', node)
|
||||
if (node.class_type == "ShowTextForGPT") {
|
||||
let div = document.createElement('div');
|
||||
div.className = "show_text"
|
||||
div.id = `output_${node.id}`;
|
||||
div.innerText = node.inputs.text[0]
|
||||
container.appendChild(div);
|
||||
div.innerText = Array.isArray(node.inputs.text) ? node.inputs.text[0] : node.inputs.text
|
||||
output_card.appendChild(div);
|
||||
};
|
||||
if (["SaveImage", "PreviewImage"].includes(node.class_type)) {
|
||||
let img = new Image();
|
||||
img.id = `output_${node.id}`;
|
||||
img.src = base64Df;
|
||||
container.appendChild(img);
|
||||
output_card.appendChild(img);
|
||||
}
|
||||
|
||||
// video ,gif
|
||||
@@ -313,7 +479,7 @@
|
||||
|
||||
v.appendChild(video);
|
||||
v.appendChild(img);
|
||||
container.appendChild(v);
|
||||
output_card.appendChild(v);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -362,6 +528,134 @@
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function copyHtmlWithImagesToClipboard(data, cb) {
|
||||
// 创建一个临时div元素
|
||||
const tempDiv = document.createElement('div');
|
||||
|
||||
// 将HTML字符串赋值给div的innerHTML属性
|
||||
tempDiv.innerHTML = data;
|
||||
|
||||
// 获取div中的所有图像元素
|
||||
const images = tempDiv.getElementsByTagName('img');
|
||||
|
||||
// 遍历图像元素,并将图像数据转换为Base64编码
|
||||
for (let i = 0; i < images.length; i++) {
|
||||
const image = images[i];
|
||||
const canvas = document.createElement('canvas');
|
||||
const context = canvas.getContext('2d');
|
||||
|
||||
// 设置canvas尺寸与图像尺寸相同
|
||||
canvas.width = image.width;
|
||||
canvas.height = image.height;
|
||||
|
||||
// 在canvas上绘制图像
|
||||
context.drawImage(image, 0, 0);
|
||||
|
||||
// 将canvas转换为Base64编码
|
||||
const imageData = canvas.toDataURL();
|
||||
|
||||
// 将Base64编码替换图像元素的src属性
|
||||
image.src = imageData;
|
||||
}
|
||||
|
||||
|
||||
let richText = tempDiv.innerHTML;
|
||||
|
||||
// 创建一个新的Blob对象,并将富文本字符串作为数据传递进去
|
||||
const blob = new Blob([richText], { type: 'text/html' });
|
||||
|
||||
// 创建一个ClipboardItem对象,并将Blob对象添加到其中
|
||||
const clipboardItem = new ClipboardItem({ 'text/html': blob });
|
||||
|
||||
// 使用Clipboard API将内容复制到剪贴板
|
||||
navigator.clipboard.write([clipboardItem])
|
||||
.then(() => {
|
||||
console.log('富文本已成功复制到剪贴板');
|
||||
tempDiv.remove()
|
||||
if (cb) cb(true)
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('复制到剪贴板失败:', error);
|
||||
tempDiv.remove()
|
||||
if (cb) cb(false)
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
// const htmlWithImages = "<p>这是要复制的HTML内容</p><img src='data:image/png;base64,iVBORw0KG...'>"
|
||||
// copyHtmlWithImagesToClipboard(htmlWithImages);
|
||||
|
||||
function copyImagesToClipboard(html, cb) {
|
||||
const tempDiv = document.createElement('div');
|
||||
tempDiv.innerHTML = html;
|
||||
const images = tempDiv.querySelectorAll('img');
|
||||
const promises = Array.from(images).map((image) => {
|
||||
return new Promise((resolve) => {
|
||||
const img = new Image();
|
||||
img.src = image.src;
|
||||
img.onload = () => {
|
||||
const canvas = document.createElement('canvas');
|
||||
const context = canvas.getContext('2d');
|
||||
canvas.width = img.width;
|
||||
canvas.height = img.height;
|
||||
context.drawImage(img, 0, 0);
|
||||
canvas.toBlob((blob) => {
|
||||
const clipboardItem = new ClipboardItem({ 'image/png': blob });
|
||||
navigator.clipboard.write([clipboardItem])
|
||||
.then(() => {
|
||||
resolve();
|
||||
tempDiv.remove()
|
||||
if (cb) cb(true)
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(error);
|
||||
tempDiv.remove()
|
||||
if (cb) cb(false)
|
||||
});
|
||||
});
|
||||
};
|
||||
});
|
||||
});
|
||||
Promise.all([...promises])
|
||||
.then(() => {
|
||||
console.log('所有图片已成功复制到剪贴板');
|
||||
if (cb) cb(true)
|
||||
tempDiv.remove()
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('复制到剪贴板失败:', error);
|
||||
if (cb) cb(false)
|
||||
tempDiv.remove()
|
||||
});
|
||||
}
|
||||
|
||||
function copyTextToClipboard(html, cb) {
|
||||
const tempDiv = document.createElement('div');
|
||||
tempDiv.innerHTML = html;
|
||||
|
||||
const text = tempDiv.innerText;
|
||||
const textData = new ClipboardItem({ 'text/plain': new Blob([text], { type: 'text/plain' }) });
|
||||
|
||||
navigator.clipboard.write([textData])
|
||||
.then(() => {
|
||||
console.log('所有文本已成功复制到剪贴板', text);
|
||||
if (cb) cb(true)
|
||||
tempDiv.remove()
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('复制到剪贴板失败:', error);
|
||||
if (cb) cb(false)
|
||||
tempDiv.remove()
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// const htmlString = "<p>这是要复制的HTML内容</p><img src='url'><img src='url'>";
|
||||
// copyImagesToClipboard(htmlString);
|
||||
|
||||
|
||||
|
||||
function createInputs(inputData) {
|
||||
// Assuming you have an HTML element with the id "container" to hold the UI
|
||||
const container = document.createElement("div");
|
||||
@@ -393,6 +687,7 @@
|
||||
// Create a label for the upload control
|
||||
const nameLabel = document.createElement("label");
|
||||
nameLabel.textContent = data.title || "LoadImage: ";
|
||||
nameLabel.style.marginBottom = '12px'
|
||||
uploadContainer.appendChild(nameLabel);
|
||||
|
||||
let actionDiv = document.createElement('div');
|
||||
@@ -408,9 +703,7 @@
|
||||
actionDiv.appendChild(uploadImageInputHide);
|
||||
|
||||
const btnFromClipboard = document.createElement("button");
|
||||
btnFromClipboard.style = `width: 156px;
|
||||
height: 24px;
|
||||
margin-left: 18px;`
|
||||
btnFromClipboard.style = `width: 156px; margin-left: 18px;`
|
||||
btnFromClipboard.innerText = 'paste from clipboard'
|
||||
actionDiv.appendChild(btnFromClipboard);
|
||||
|
||||
@@ -469,9 +762,14 @@
|
||||
|
||||
if (['FloatSlider', 'IntNumber'].includes(data.class_type)) {
|
||||
// 滑块输入
|
||||
let silde = createFloatSlide(data.title, data.inputs.number, (v) => {
|
||||
window._appData.data[data.id].inputs.number = v;
|
||||
})
|
||||
let silde = createNumSlide(data.title,
|
||||
data.inputs.number,
|
||||
(v) => {
|
||||
window._appData.data[data.id].inputs.number = v;
|
||||
},
|
||||
0,
|
||||
data.class_type === 'IntNumber' ? 6000 : 1,
|
||||
data.class_type === 'IntNumber' ? 'int' : 'float')
|
||||
container.appendChild(silde);
|
||||
}
|
||||
|
||||
@@ -537,19 +835,20 @@
|
||||
return container
|
||||
}
|
||||
|
||||
function createFloatSlide(labelText, value = 0, callback, minValue = 0, maxValue = 1) {
|
||||
function createNumSlide(labelText, value = 0, callback, minValue = 0, maxValue = 1, type = 'float') {
|
||||
|
||||
// 创建滑块输入元素
|
||||
var slider = document.createElement("input");
|
||||
slider.type = "range";
|
||||
slider.min = minValue;
|
||||
slider.max = maxValue;
|
||||
slider.step = 0.01
|
||||
slider.step = type == 'float' ? 0.01 : 1
|
||||
slider.value = value;
|
||||
|
||||
// 创建标签元素
|
||||
var label = document.createElement("label");
|
||||
label.innerHTML = labelText;
|
||||
label.setAttribute('data-content', value);
|
||||
|
||||
// 创建容器元素,并将滑块输入和标签添加到容器中
|
||||
var container = document.createElement("div");
|
||||
@@ -558,9 +857,11 @@
|
||||
container.className = 'card'
|
||||
|
||||
// 添加change事件监听器
|
||||
slider.addEventListener("change", function (event) {
|
||||
slider.addEventListener("input", function (event) {
|
||||
var value = event.target.value;
|
||||
value = type == 'float' ? value : Math.round(value)
|
||||
console.log("滑块输入的值为:" + value);
|
||||
label.setAttribute('data-content', value);
|
||||
// 在这里可以执行其他操作,根据需要进行相应的处理
|
||||
callback && callback(value)
|
||||
});
|
||||
@@ -599,21 +900,21 @@
|
||||
return [div, selectElement];
|
||||
}
|
||||
|
||||
function getTypeFromUrl(url) {
|
||||
function getFilenameFromUrl(url) {
|
||||
const queryString = url.split('?')[1];
|
||||
if (!queryString) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const params = new URLSearchParams(queryString);
|
||||
const type = params.get('type');
|
||||
const filename = decodeURIComponent(params.get('filename'));
|
||||
|
||||
return type;
|
||||
return filename;
|
||||
}
|
||||
|
||||
|
||||
|
||||
function createUI(inputData, outputData) {
|
||||
function createUI(inputData, outputData, share = true) {
|
||||
|
||||
let mainDiv = document.createElement('div');
|
||||
let leftDiv = document.createElement('div');
|
||||
@@ -631,8 +932,26 @@
|
||||
// top: 12px;flex:0.6`
|
||||
|
||||
// 创建标题
|
||||
let titleDiv = document.createElement('div');
|
||||
titleDiv.className = 'header'
|
||||
|
||||
var title = document.createElement('h1');
|
||||
title.textContent = 'My Application';
|
||||
titleDiv.appendChild(title);
|
||||
|
||||
if (share) {
|
||||
const shareBtn = document.createElement('button');
|
||||
shareBtn.innerText = 'copy url';
|
||||
shareBtn.addEventListener('click', e => {
|
||||
e.preventDefault();
|
||||
let url = `${get_url()}/mixlab/app?filename=${encodeURIComponent(window._appData.filename)}`;
|
||||
copyTextToClipboard(url, success(e, shareBtn, 'copy url'));
|
||||
})
|
||||
|
||||
titleDiv.appendChild(shareBtn);
|
||||
}
|
||||
|
||||
|
||||
|
||||
let iconDes = document.createElement('div');
|
||||
// 创建应用图标
|
||||
@@ -663,7 +982,7 @@
|
||||
submitButton.className = 'run_btn'
|
||||
|
||||
// 将所有UI元素添加到页面中
|
||||
leftDiv.appendChild(title);
|
||||
leftDiv.appendChild(titleDiv);
|
||||
leftDiv.appendChild(iconDes);
|
||||
// leftDiv.appendChild(des);
|
||||
leftDiv.appendChild(status);
|
||||
@@ -750,16 +1069,16 @@
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function createUploadJson() {
|
||||
function createUploadJson(detail) {
|
||||
|
||||
// 创建一个div元素
|
||||
var div = document.createElement('div');
|
||||
div.className = 'upload_btn'
|
||||
div.textContent = '点击上传JSON文件';
|
||||
div.className = 'upload_btn card'
|
||||
div.textContent = '上传并运行你的JSON文件';
|
||||
div.addEventListener('click', function () {
|
||||
document.getElementById('jsonFileInput').click();
|
||||
});
|
||||
@@ -775,10 +1094,11 @@
|
||||
reader.onload = function (e) {
|
||||
var contents = e.target.result;
|
||||
var jsonData = JSON.parse(contents);
|
||||
setTimeout(() => {
|
||||
div.remove();
|
||||
input.remove();
|
||||
}, 500);
|
||||
|
||||
|
||||
Array.from(detail.querySelectorAll('.card'), c => c.classList.remove('selected'));
|
||||
div.className = 'upload_btn card selected'
|
||||
|
||||
|
||||
let { output, app } = jsonData;
|
||||
|
||||
@@ -786,8 +1106,8 @@
|
||||
...app,
|
||||
data: output
|
||||
};
|
||||
|
||||
createApp(window._appData);
|
||||
if (document.body.querySelector('.app')) document.body.querySelector('.app').remove()
|
||||
createApp(window._appData, false);
|
||||
|
||||
// res(jsonData);
|
||||
};
|
||||
@@ -795,16 +1115,16 @@
|
||||
});
|
||||
|
||||
// 将div和input元素添加到body中
|
||||
document.body.appendChild(div);
|
||||
// document.body.appendChild(div);
|
||||
document.body.appendChild(input);
|
||||
|
||||
|
||||
return div
|
||||
}
|
||||
|
||||
|
||||
async function createApp(appData) {
|
||||
async function createApp(appData, share = true) {
|
||||
console.log(appData)
|
||||
// 使用示例:
|
||||
var ui = createUI(appData.input, appData.output);
|
||||
var ui = createUI(appData.input, appData.output, share);
|
||||
|
||||
// 更新标题
|
||||
ui.title.update(appData.name || 'Mixlab APP');
|
||||
@@ -859,8 +1179,8 @@
|
||||
// if (!images) return;
|
||||
const src = `${get_url()}/view?filename=${encodeURIComponent(images[0].filename)}&type=${images[0].type}&subfolder=${encodeURIComponent(images[0].subfolder)}&t=${+new Date()}`;
|
||||
show(src, detail.node, 'image');
|
||||
} else if (text && text[0]) {
|
||||
ui.output.update("text", text[0], detail.node)
|
||||
} else if (text) {
|
||||
ui.output.update("text", Array.isArray(text) ? text[0] : text, detail.node)
|
||||
} else if (gifs && gifs[0]) {
|
||||
// if (!images) return;
|
||||
const src = `${get_url()}/view?filename=${encodeURIComponent(gifs[0].filename)}&type=${gifs[0].type}&subfolder=${encodeURIComponent(gifs[0].subfolder)
|
||||
@@ -886,24 +1206,72 @@
|
||||
|
||||
api.api_base = ""
|
||||
api.init();
|
||||
|
||||
}
|
||||
|
||||
// 创建app的选择菜单
|
||||
function createAppList(apps = []) {
|
||||
let details = document.createElement('details');
|
||||
details.className = 'apps';
|
||||
|
||||
details.innerHTML = `<summary>ComfyUI APP Store / ${apps.length}</summary>
|
||||
<div class="content"> </div>`
|
||||
|
||||
let div = details.querySelector('div');
|
||||
|
||||
for (let index = 0; index < apps.length; index++) {
|
||||
const app = apps[index];
|
||||
let d = document.createDocumentFragment();
|
||||
let dd = document.createElement('div');
|
||||
d.appendChild(dd);
|
||||
dd.className = 'card' + (index == 0 ? ' selected' : '')
|
||||
dd.innerHTML = `
|
||||
<div class="item icon">
|
||||
<img src="${app.icon || base64Df}"/>
|
||||
</div>
|
||||
<div class="item" style="margin-left: 24px;">
|
||||
<div>
|
||||
<h5>${app.name}</h5>
|
||||
<p>${app.description}</p>
|
||||
</div>
|
||||
<div >
|
||||
<p class="version">version: ${app.version}</p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
|
||||
div.appendChild(d);
|
||||
dd.addEventListener('click', async e => {
|
||||
e.preventDefault();
|
||||
Array.from(div.querySelectorAll('.card'), c => c.classList.remove('selected'));
|
||||
dd.className = 'card selected'
|
||||
// console.log(app.filename)
|
||||
|
||||
window._appData = (await get_my_app(app.filename))[0];
|
||||
if (document.body.querySelector('.app')) document.body.querySelector('.app').remove()
|
||||
createApp(window._appData);
|
||||
|
||||
})
|
||||
// console.log(div)
|
||||
};
|
||||
|
||||
let uploadApp = createUploadJson(details);
|
||||
div.appendChild(uploadApp);
|
||||
|
||||
document.body.appendChild(details);
|
||||
}
|
||||
|
||||
async function init_app() {
|
||||
|
||||
let appData = {}
|
||||
const filename = getFilenameFromUrl(location.href);
|
||||
window._apps = await get_my_app(filename);
|
||||
|
||||
const type = getTypeFromUrl(location.href);
|
||||
if (type === 'new') {
|
||||
createUploadJson();
|
||||
} else {
|
||||
appData = await get_my_app();
|
||||
// console.log(appData)
|
||||
window._appData = appData;
|
||||
window._appData = window._apps[0];
|
||||
|
||||
createApp(appData);
|
||||
}
|
||||
createAppList(window._apps);
|
||||
|
||||
createApp(window._appData);
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -81,8 +81,9 @@ function extractInputAndOutputData (jsonData, inputIds = [], outputIds = []) {
|
||||
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
|
||||
} else if (node.type === 'LoraLoader') {
|
||||
options = node.widgets.filter(w => w.name === 'lora_name')[0]
|
||||
.options.values
|
||||
}
|
||||
} catch (error) {}
|
||||
|
||||
@@ -119,7 +120,8 @@ async function save_app (json) {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
data: json,
|
||||
task: 'save_app'
|
||||
task: 'save_app',
|
||||
filename: json.app.filename
|
||||
})
|
||||
})
|
||||
return await res.json()
|
||||
@@ -144,6 +146,7 @@ function downloadJsonFile (jsonData, fileName = 'mix_app.json') {
|
||||
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)
|
||||
@@ -170,7 +173,9 @@ async function save (json, download = false) {
|
||||
description,
|
||||
version,
|
||||
input,
|
||||
output
|
||||
output,
|
||||
share_prefix,
|
||||
filename: `${name}_${version}_${new Date().toDateString()}.json`
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -180,14 +185,15 @@ async function save (json, download = false) {
|
||||
// let http_workflow = app.graph.serialize()
|
||||
|
||||
if (download) {
|
||||
await downloadJsonFile(
|
||||
data,
|
||||
`${data.app.name}_${data.app.version}_${new Date().toDateString()}.json`
|
||||
)
|
||||
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?type=new`
|
||||
`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?type=new`)
|
||||
if (open)
|
||||
window.open(
|
||||
`${getUrl()}/mixlab/app?filename=${encodeURIComponent(data.app.filename)}`
|
||||
)
|
||||
} else {
|
||||
await save_app(data)
|
||||
|
||||
@@ -208,7 +214,7 @@ app.registerExtension({
|
||||
const orig_nodeCreated = nodeType.prototype.onNodeCreated
|
||||
nodeType.prototype.onNodeCreated = function () {
|
||||
orig_nodeCreated?.apply(this, arguments)
|
||||
// console.log(this)
|
||||
console.log('#orig_nodeCreated', this)
|
||||
const widget = {
|
||||
type: 'div',
|
||||
name: 'AppInfoRun',
|
||||
@@ -218,7 +224,7 @@ app.registerExtension({
|
||||
get_position_style(
|
||||
ctx,
|
||||
widget_width,
|
||||
node.widgets[4].last_y + 24,
|
||||
node.size[1] - widget_height,
|
||||
node.size[1]
|
||||
)
|
||||
)
|
||||
@@ -236,7 +242,7 @@ app.registerExtension({
|
||||
widget.div = $el('div', {})
|
||||
|
||||
const btn = document.createElement('button')
|
||||
btn.innerText = 'Save For App'
|
||||
btn.innerText = 'Save & Open'
|
||||
btn.style = style
|
||||
|
||||
btn.addEventListener('click', () => {
|
||||
@@ -246,6 +252,7 @@ app.registerExtension({
|
||||
} else {
|
||||
alert('Please run the workflow before saving')
|
||||
// app.queuePrompt(0, 1)
|
||||
this.widgets.filter(w => w.name === 'version')[0].value += 1
|
||||
}
|
||||
})
|
||||
|
||||
@@ -261,6 +268,7 @@ app.registerExtension({
|
||||
} else {
|
||||
alert('Please run the workflow before saving')
|
||||
// app.queuePrompt(0, 1)
|
||||
this.widgets.filter(w => w.name === 'version')[0].value += 1
|
||||
}
|
||||
})
|
||||
|
||||
@@ -282,7 +290,7 @@ app.registerExtension({
|
||||
const onExecuted = nodeType.prototype.onExecuted
|
||||
nodeType.prototype.onExecuted = async function (message) {
|
||||
onExecuted?.apply(this, arguments)
|
||||
// console.log(this.widgets)
|
||||
console.log(message.json)
|
||||
|
||||
window._mixlab_app_json = message.json
|
||||
try {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { app } from '../../../scripts/app.js'
|
||||
const repoOwner = 'shadowcz007' // 替换为仓库的所有者
|
||||
const repoName = 'comfyui-mixlab-nodes' // 替换为仓库的名称
|
||||
|
||||
const version = 'v0.6.0'
|
||||
const version = 'v0.7.0'
|
||||
|
||||
fetch(`https://api.github.com/repos/${repoOwner}/${repoName}/releases/latest`)
|
||||
.then(response => response.json())
|
||||
|
||||
@@ -4,7 +4,6 @@ import { ComfyWidgets } from '../../../scripts/widgets.js'
|
||||
import { $el } from '../../../scripts/ui.js'
|
||||
import { closeIcon } from './svg_icons.js'
|
||||
|
||||
|
||||
import {
|
||||
GroupNodeConfig,
|
||||
GroupNodeHandler
|
||||
@@ -668,23 +667,10 @@ app.registerExtension({
|
||||
...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)
|
||||
|
||||
@@ -728,7 +714,7 @@ app.registerExtension({
|
||||
let textB = document.createElement('p')
|
||||
btn.appendChild(textB)
|
||||
btn.appendChild(btnB)
|
||||
textB.style.fontSize='12px';
|
||||
textB.style.fontSize = '12px'
|
||||
textB.innerText = `Locate and navigate nodes ♾️Mixlab`
|
||||
|
||||
btnB.style = `float: right; border: none; color: var(--input-text);
|
||||
@@ -804,11 +790,12 @@ app.registerExtension({
|
||||
for (let nodeId in nodes) {
|
||||
let n = nodes[nodeId].class_type
|
||||
if (nodesMap[n]) {
|
||||
const { url, title } = 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', () => {
|
||||
d.addEventListener('click', () => {
|
||||
const node = app.graph.getNodeById(nodeId)
|
||||
if (!node) return
|
||||
app.canvas.centerOnNode(node)
|
||||
@@ -824,10 +811,10 @@ app.registerExtension({
|
||||
})
|
||||
|
||||
d.innerHTML = `
|
||||
<span>${'#' + nodeId} ${n}</span>
|
||||
<span>${'#' + nodeId} ${title}</span>
|
||||
<a href="${url}" target="_blank" style="text-decoration: none;">🔗</a>
|
||||
`
|
||||
d.title = title
|
||||
d.title = n
|
||||
|
||||
nodesDiv.appendChild(d)
|
||||
}
|
||||
|
||||
+592
-684
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,717 @@
|
||||
{
|
||||
"last_node_id": 25,
|
||||
"last_link_id": 32,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 5,
|
||||
"type": "CLIPTextEncode",
|
||||
"pos": [
|
||||
1029,
|
||||
-2149
|
||||
],
|
||||
"size": {
|
||||
"0": 425.27801513671875,
|
||||
"1": 180.6060791015625
|
||||
},
|
||||
"flags": {},
|
||||
"order": 7,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "clip",
|
||||
"type": "CLIP",
|
||||
"link": 6
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "CONDITIONING",
|
||||
"type": "CONDITIONING",
|
||||
"links": [
|
||||
3
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"title": "负向prompt",
|
||||
"properties": {
|
||||
"Node name for S&R": "CLIPTextEncode"
|
||||
},
|
||||
"widgets_values": [
|
||||
"text, watermark"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"type": "VAEDecode",
|
||||
"pos": [
|
||||
1867,
|
||||
-2378
|
||||
],
|
||||
"size": {
|
||||
"0": 210,
|
||||
"1": 46
|
||||
},
|
||||
"flags": {
|
||||
"collapsed": false
|
||||
},
|
||||
"order": 10,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "samples",
|
||||
"type": "LATENT",
|
||||
"link": 7
|
||||
},
|
||||
{
|
||||
"name": "vae",
|
||||
"type": "VAE",
|
||||
"link": 8
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
9
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "VAEDecode"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 15,
|
||||
"type": "EnhanceImage",
|
||||
"pos": [
|
||||
2591,
|
||||
-2531
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 58
|
||||
},
|
||||
"flags": {},
|
||||
"order": 12,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"link": 25
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
18
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "EnhanceImage"
|
||||
},
|
||||
"widgets_values": [
|
||||
1.1
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 21,
|
||||
"type": "EmptyLatentImage",
|
||||
"pos": [
|
||||
998,
|
||||
-2674
|
||||
],
|
||||
"size": [
|
||||
315,
|
||||
106
|
||||
],
|
||||
"flags": {},
|
||||
"order": 8,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "width",
|
||||
"type": "INT",
|
||||
"link": 30,
|
||||
"widget": {
|
||||
"name": "width"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "height",
|
||||
"type": "INT",
|
||||
"link": 32,
|
||||
"widget": {
|
||||
"name": "height"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "LATENT",
|
||||
"type": "LATENT",
|
||||
"links": [
|
||||
26
|
||||
],
|
||||
"shape": 3
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "EmptyLatentImage"
|
||||
},
|
||||
"widgets_values": [
|
||||
512,
|
||||
512,
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 24,
|
||||
"type": "LimitNumber",
|
||||
"pos": [
|
||||
665,
|
||||
-2958
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 82
|
||||
},
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "number",
|
||||
"type": "*",
|
||||
"link": 29
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "number",
|
||||
"type": "*",
|
||||
"links": [
|
||||
30
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "LimitNumber"
|
||||
},
|
||||
"widgets_values": [
|
||||
512,
|
||||
4089
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 25,
|
||||
"type": "LimitNumber",
|
||||
"pos": [
|
||||
648,
|
||||
-2659
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 82
|
||||
},
|
||||
"flags": {},
|
||||
"order": 5,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "number",
|
||||
"type": "*",
|
||||
"link": 31
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "number",
|
||||
"type": "*",
|
||||
"links": [
|
||||
32
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "LimitNumber"
|
||||
},
|
||||
"widgets_values": [
|
||||
512,
|
||||
4089
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 22,
|
||||
"type": "IntNumber",
|
||||
"pos": [
|
||||
302,
|
||||
-2758
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 58
|
||||
},
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "INT",
|
||||
"type": "INT",
|
||||
"links": [
|
||||
29
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"title": "Width",
|
||||
"properties": {
|
||||
"Node name for S&R": "IntNumber"
|
||||
},
|
||||
"widgets_values": [
|
||||
512
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 23,
|
||||
"type": "IntNumber",
|
||||
"pos": [
|
||||
299,
|
||||
-2601
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 58
|
||||
},
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "INT",
|
||||
"type": "INT",
|
||||
"links": [
|
||||
31
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"title": "Height",
|
||||
"properties": {
|
||||
"Node name for S&R": "IntNumber"
|
||||
},
|
||||
"widgets_values": [
|
||||
512
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"type": "CheckpointLoaderSimple",
|
||||
"pos": [
|
||||
567,
|
||||
-2422
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 98
|
||||
},
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "MODEL",
|
||||
"type": "MODEL",
|
||||
"links": [
|
||||
1
|
||||
],
|
||||
"slot_index": 0
|
||||
},
|
||||
{
|
||||
"name": "CLIP",
|
||||
"type": "CLIP",
|
||||
"links": [
|
||||
5,
|
||||
6
|
||||
],
|
||||
"slot_index": 1
|
||||
},
|
||||
{
|
||||
"name": "VAE",
|
||||
"type": "VAE",
|
||||
"links": [
|
||||
8
|
||||
],
|
||||
"slot_index": 2
|
||||
}
|
||||
],
|
||||
"title": "Model",
|
||||
"properties": {
|
||||
"Node name for S&R": "CheckpointLoaderSimple"
|
||||
},
|
||||
"widgets_values": [
|
||||
"deliberate_v2.safetensors"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"type": "KSampler",
|
||||
"pos": [
|
||||
1509,
|
||||
-2394
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 262
|
||||
},
|
||||
"flags": {},
|
||||
"order": 9,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "model",
|
||||
"type": "MODEL",
|
||||
"link": 1
|
||||
},
|
||||
{
|
||||
"name": "positive",
|
||||
"type": "CONDITIONING",
|
||||
"link": 2
|
||||
},
|
||||
{
|
||||
"name": "negative",
|
||||
"type": "CONDITIONING",
|
||||
"link": 3
|
||||
},
|
||||
{
|
||||
"name": "latent_image",
|
||||
"type": "LATENT",
|
||||
"link": 26,
|
||||
"slot_index": 3
|
||||
},
|
||||
{
|
||||
"name": "denoise",
|
||||
"type": "FLOAT",
|
||||
"link": 16,
|
||||
"widget": {
|
||||
"name": "denoise"
|
||||
},
|
||||
"slot_index": 4
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "LATENT",
|
||||
"type": "LATENT",
|
||||
"links": [
|
||||
7
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "KSampler"
|
||||
},
|
||||
"widgets_values": [
|
||||
730250870715434,
|
||||
"randomize",
|
||||
15,
|
||||
6.9,
|
||||
"euler",
|
||||
"karras",
|
||||
0.59
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 14,
|
||||
"type": "FloatSlider",
|
||||
"pos": [
|
||||
1007,
|
||||
-2819
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 58
|
||||
},
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "FLOAT",
|
||||
"type": "FLOAT",
|
||||
"links": [
|
||||
16
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"title": "denoise",
|
||||
"properties": {
|
||||
"Node name for S&R": "FloatSlider"
|
||||
},
|
||||
"widgets_values": [
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 16,
|
||||
"type": "PreviewImage",
|
||||
"pos": [
|
||||
2949,
|
||||
-2615
|
||||
],
|
||||
"size": {
|
||||
"0": 210,
|
||||
"1": 246
|
||||
},
|
||||
"flags": {},
|
||||
"order": 13,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 18
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "PreviewImage"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"type": "CLIPTextEncode",
|
||||
"pos": [
|
||||
1022,
|
||||
-2375
|
||||
],
|
||||
"size": {
|
||||
"0": 422.84503173828125,
|
||||
"1": 164.31304931640625
|
||||
},
|
||||
"flags": {},
|
||||
"order": 6,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "clip",
|
||||
"type": "CLIP",
|
||||
"link": 5
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "CONDITIONING",
|
||||
"type": "CONDITIONING",
|
||||
"links": [
|
||||
2
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"title": "prompt",
|
||||
"properties": {
|
||||
"Node name for S&R": "CLIPTextEncode"
|
||||
},
|
||||
"widgets_values": [
|
||||
"superman,fat cat"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"type": "AppInfo",
|
||||
"pos": [
|
||||
2134,
|
||||
-2528
|
||||
],
|
||||
"size": {
|
||||
"0": 408.4201965332031,
|
||||
"1": 406.9195861816406
|
||||
},
|
||||
"flags": {},
|
||||
"order": 11,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"link": 9
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
25
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "AppInfo"
|
||||
},
|
||||
"widgets_values": [
|
||||
"Text-to-Image",
|
||||
"4\n22\n23\n2\n\n\n",
|
||||
"16",
|
||||
"演示基本的文生图流程",
|
||||
1,
|
||||
"#comfyui-mixlab-nodes# ",
|
||||
null
|
||||
]
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
1,
|
||||
2,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
"MODEL"
|
||||
],
|
||||
[
|
||||
2,
|
||||
4,
|
||||
0,
|
||||
1,
|
||||
1,
|
||||
"CONDITIONING"
|
||||
],
|
||||
[
|
||||
3,
|
||||
5,
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
"CONDITIONING"
|
||||
],
|
||||
[
|
||||
5,
|
||||
2,
|
||||
1,
|
||||
4,
|
||||
0,
|
||||
"CLIP"
|
||||
],
|
||||
[
|
||||
6,
|
||||
2,
|
||||
1,
|
||||
5,
|
||||
0,
|
||||
"CLIP"
|
||||
],
|
||||
[
|
||||
7,
|
||||
1,
|
||||
0,
|
||||
6,
|
||||
0,
|
||||
"LATENT"
|
||||
],
|
||||
[
|
||||
8,
|
||||
2,
|
||||
2,
|
||||
6,
|
||||
1,
|
||||
"VAE"
|
||||
],
|
||||
[
|
||||
9,
|
||||
6,
|
||||
0,
|
||||
7,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
16,
|
||||
14,
|
||||
0,
|
||||
1,
|
||||
4,
|
||||
"FLOAT"
|
||||
],
|
||||
[
|
||||
18,
|
||||
15,
|
||||
0,
|
||||
16,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
25,
|
||||
7,
|
||||
0,
|
||||
15,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
26,
|
||||
21,
|
||||
0,
|
||||
1,
|
||||
3,
|
||||
"LATENT"
|
||||
],
|
||||
[
|
||||
29,
|
||||
22,
|
||||
0,
|
||||
24,
|
||||
0,
|
||||
"*"
|
||||
],
|
||||
[
|
||||
30,
|
||||
24,
|
||||
0,
|
||||
21,
|
||||
0,
|
||||
"INT"
|
||||
],
|
||||
[
|
||||
31,
|
||||
23,
|
||||
0,
|
||||
25,
|
||||
0,
|
||||
"*"
|
||||
],
|
||||
[
|
||||
32,
|
||||
25,
|
||||
0,
|
||||
21,
|
||||
1,
|
||||
"INT"
|
||||
]
|
||||
],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {},
|
||||
"version": 0.4
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 2.6 MiB After Width: | Height: | Size: 2.5 MiB |
Reference in New Issue
Block a user