Merge pull request #95 from shadowcz007/v0.7-apps

V0.7 apps
This commit is contained in:
shadow
2023-12-30 20:53:19 +08:00
committed by GitHub
14 changed files with 1897 additions and 809 deletions
+2 -1
View File
@@ -2,4 +2,5 @@ __pycache__/
https/
nodes/config.json
workflow/my_workflow.json
workflow/my_workflow_app.json
workflow/my_workflow_app.json
app/*
+10 -4
View File
@@ -1,19 +1,25 @@
##
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.
![](./assets/0-m-app.png)
![](./assets/appinfo-readme.png)
Example:
- workflow
![APP info](./workflow/appinfo-workflow.svg)
[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、VHS_VideoCombine
+98 -19
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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);
};
+22 -14
View File
@@ -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 {
+1 -1
View File
@@ -3,7 +3,7 @@ import { app } from '../../../scripts/app.js'
const repoOwner = 'shadowcz007' // 替换为仓库的所有者
const repoName = 'comfyui-mixlab-nodes' // 替换为仓库的名称
const version = 'v0.6.0'
const version = 'v0.7.0'
fetch(`https://api.github.com/repos/${repoOwner}/${repoName}/releases/latest`)
.then(response => response.json())
+592 -684
View File
File diff suppressed because it is too large Load Diff
+717
View File
@@ -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