1737 lines
63 KiB
HTML
1737 lines
63 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Mixlab APP</title>
|
|
<style>
|
|
body {
|
|
margin: 0;
|
|
padding: 0;
|
|
}
|
|
|
|
.app {
|
|
display: flex;
|
|
width: 90%;
|
|
min-width: 400px;
|
|
margin-left: 5%;
|
|
user-select: none;
|
|
}
|
|
|
|
.apps {
|
|
margin: 0 32px;
|
|
background: whitesmoke;
|
|
color: black;
|
|
padding: 12px;
|
|
cursor: pointer;
|
|
margin: 8px 44px;
|
|
}
|
|
|
|
.apps .content {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
|
|
.apps .card {
|
|
width: 200px;
|
|
margin: 12px;
|
|
display: flex;
|
|
flex-direction: row;
|
|
background: #ffffff;
|
|
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;
|
|
max-height: 35px;
|
|
overflow: hidden;
|
|
width: 98px;
|
|
}
|
|
|
|
.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: 48px;
|
|
height: 48px;
|
|
overflow: hidden;
|
|
background: #e3e3e3;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
}
|
|
|
|
|
|
.apps .card .version {
|
|
font-size: 12px;
|
|
|
|
}
|
|
|
|
.card .toggle {
|
|
margin-left: 12px;
|
|
background: none;
|
|
color: black;
|
|
}
|
|
|
|
.card .toggle:hover {
|
|
color: #7f7f7f !important;
|
|
}
|
|
|
|
em .toggle:hover {
|
|
color: #7f7f7f !important;
|
|
}
|
|
|
|
.status_seed {
|
|
display: flex;
|
|
flex-direction: column;
|
|
margin: 12px;
|
|
}
|
|
|
|
.seeds {
|
|
font-size: 12px;
|
|
margin-top: 4px;
|
|
}
|
|
|
|
.status {
|
|
background: black;
|
|
color: white;
|
|
display: flex;
|
|
width: fit-content;
|
|
padding: 4px;
|
|
font-size: 12px;
|
|
|
|
}
|
|
|
|
.description {
|
|
display: flex;
|
|
margin: 12px;
|
|
background: white;
|
|
padding: 8px;
|
|
width: 80%;
|
|
}
|
|
|
|
.description p {
|
|
max-width: 200px;
|
|
word-wrap: break-word;
|
|
}
|
|
|
|
.panel {
|
|
display: flex;
|
|
flex-direction: column;
|
|
min-width: 400px;
|
|
/* 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: 0;
|
|
}
|
|
|
|
.panel img,
|
|
video {
|
|
height: fit-content;
|
|
width: fit-content;
|
|
max-width: 100%;
|
|
margin-left: 12px;
|
|
}
|
|
|
|
.input_card {
|
|
display: flex;
|
|
flex-direction: column;
|
|
width: 100%;
|
|
}
|
|
|
|
.output {
|
|
width: 90%;
|
|
margin: 12px;
|
|
}
|
|
|
|
.output_card {
|
|
height: 100%;
|
|
width: 100%;
|
|
|
|
margin-top: 24px;
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
/* justify-content: center;
|
|
align-items: center; */
|
|
|
|
}
|
|
|
|
.output_card img,
|
|
video {
|
|
max-width: 200px;
|
|
max-height: 600px;
|
|
margin: 8px;
|
|
min-width: 200px;
|
|
box-shadow: 0px 0px 20px 7px #e6e7e7;
|
|
}
|
|
|
|
.card {
|
|
background-color: #eee;
|
|
display: flex;
|
|
flex-direction: column;
|
|
padding: 8px;
|
|
font-size: 12px;
|
|
}
|
|
|
|
.card textarea {
|
|
width: 100%;
|
|
/* height: 200px; */
|
|
/* min-width: 300px; */
|
|
margin-top: 12px;
|
|
resize: vertical;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.card img {
|
|
width: 100%;
|
|
margin-top: 12px;
|
|
}
|
|
|
|
.card .select {
|
|
margin-top: 12px;
|
|
}
|
|
|
|
.run_btn {
|
|
background: black;
|
|
color: white;
|
|
width: 88px;
|
|
height: 88px;
|
|
position: fixed;
|
|
bottom: 72px;
|
|
left: calc(50% - 44px);
|
|
border-radius: 100%;
|
|
cursor: pointer;
|
|
border: 3px solid;
|
|
}
|
|
|
|
button:hover {
|
|
border-color: yellow !important;
|
|
color: yellow !important;
|
|
}
|
|
|
|
.disabled {
|
|
background-color: #eee !important;
|
|
color: #4a4a4a !important;
|
|
}
|
|
|
|
.upload_btn {
|
|
width: 188px;
|
|
cursor: pointer;
|
|
/* height: 188px; */
|
|
/* background: black; */
|
|
color: white;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
text-align: center;
|
|
font-size: 14px;
|
|
color: black;
|
|
}
|
|
|
|
/* .upload_btn:hover {
|
|
outline: 4px solid yellow;
|
|
color: yellow;
|
|
} */
|
|
|
|
.show_text {
|
|
font-size: 14px;
|
|
/* display: inline-block; */
|
|
margin: 8px;
|
|
padding: 32px;
|
|
min-width: 200px;
|
|
background: #242424;
|
|
color: white;
|
|
}
|
|
|
|
.link {
|
|
text-decoration: none;
|
|
color: gray;
|
|
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;
|
|
margin-top: 8px;
|
|
}
|
|
|
|
|
|
.pickr .pcr-button {
|
|
width: 56px !important;
|
|
height: 56px !important;
|
|
outline: 1px solid white;
|
|
}
|
|
</style>
|
|
<!-- <script src="../../../scripts/api.js" type="module"></script> -->
|
|
<link href="/extensions/comfyui-mixlab-nodes/lib/photoswipe.min.css" rel="stylesheet">
|
|
<link href="/extensions/comfyui-mixlab-nodes/lib/classic.min.css" rel="stylesheet">
|
|
<script src="/extensions/comfyui-mixlab-nodes/lib/pickr.min.js"></script>
|
|
</head>
|
|
|
|
<body>
|
|
<div style="display: flex;
|
|
align-items: center;
|
|
justify-content: space-around;">
|
|
<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>
|
|
</div>
|
|
|
|
<a target="_blank" href="https://github.com/shadowcz007/comfyui-mixlab-nodes" style="text-decoration: none;
|
|
color: black;font-size:12px">
|
|
<svg height="32" aria-hidden="true" viewBox="0 0 16 16" version="1.1" width="32" data-view-component="true"
|
|
class="octicon octicon-mark-github v-align-middle color-fg-default">
|
|
<path
|
|
d="M8 0c4.42 0 8 3.58 8 8a8.013 8.013 0 0 1-5.45 7.59c-.4.08-.55-.17-.55-.38 0-.27.01-1.13.01-2.2 0-.75-.25-1.23-.54-1.48 1.78-.2 3.65-.88 3.65-3.95 0-.88-.31-1.59-.82-2.15.08-.2.36-1.02-.08-2.12 0 0-.67-.22-2.2.82-.64-.18-1.32-.27-2-.27-.68 0-1.36.09-2 .27-1.53-1.03-2.2-.82-2.2-.82-.44 1.1-.16 1.92-.08 2.12-.51.56-.82 1.28-.82 2.15 0 3.06 1.86 3.75 3.64 3.95-.23.2-.44.55-.51 1.07-.46.21-1.61.55-2.33-.66-.15-.24-.6-.83-1.23-.82-.67.01-.27.38.01.53.34.19.73.9.82 1.13.16.45.68 1.31 2.69.94 0 .67.01 1.3.01 1.49 0 .21-.15.45-.55.38A7.995 7.995 0 0 1 0 8c0-4.42 3.58-8 8-8Z">
|
|
</path>
|
|
</svg> Code
|
|
|
|
</a>
|
|
|
|
</div>
|
|
<!-- <script type="module" src="https://cdn.bootcdn.net/ajax/libs/photoswipe/5.4.0/photoswipe-lightbox.esm.min.js"></script> -->
|
|
<script type="module">
|
|
import PhotoSwipeLightbox from '/extensions/comfyui-mixlab-nodes/lib/photoswipe-lightbox.esm.min.js'
|
|
// console.log(Lightbox)
|
|
|
|
import { api } from "../../../scripts/api.js";
|
|
// console.log('api', api)
|
|
const base64Df =
|
|
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAAAXNSR0IArs4c6QAAALZJREFUKFOFkLERwjAQBPdbgBkInECGaMLUQDsE0AkRVRAYWqAByxldPPOWHwnw4OBGye1p50UDSoA+W2ABLPN7i+C5dyC6R/uiAUXRQCs0bXoNIu4QPQzAxDKxHoALOrZcqtiyR/T6CXw7+3IGHhkYcy6BOR2izwT8LptG8rbMiCRAUb+CQ6WzQVb0SNOi5Z2/nX35DRyb/ENazhpWKoGwrpD6nICp5c2qogc4of+c7QcrhgF4Aa/aoAFHiL+RAAAAAElFTkSuQmCC'
|
|
|
|
|
|
function get_url() {
|
|
let api_host = `${window.location.hostname}:${window.location.port}`
|
|
let api_base = ''
|
|
let url = `${window.location.protocol}//${api_host}${api_base}`
|
|
return url
|
|
}
|
|
|
|
async function uploadImage(blob, fileType = '.png', filename) {
|
|
const body = new FormData()
|
|
body.append(
|
|
'image',
|
|
new File([blob], (filename || new Date().getTime()) + fileType)
|
|
)
|
|
|
|
const url = get_url()
|
|
|
|
const resp = await fetch(`${url}/upload/image`, {
|
|
method: 'POST',
|
|
body
|
|
})
|
|
|
|
// console.log(resp)
|
|
let data = await resp.json()
|
|
let { name, subfolder } = data
|
|
let src = `${url}/view?filename=${encodeURIComponent(
|
|
name
|
|
)}&type=input&subfolder=${subfolder}&rand=${Math.random()}`
|
|
|
|
return { url: src, name }
|
|
|
|
}
|
|
|
|
|
|
function randomSeed(seed, data) {
|
|
for (const id in data) {
|
|
|
|
if (data[id].inputs.seed != undefined
|
|
&& !Array.isArray(data[id].inputs.seed) //如果是数组,则由其他节点控制
|
|
&& ['increment', 'decrement', 'randomize'].includes(seed[id])) {
|
|
data[id].inputs.seed = Math.round(Math.random() * 1849378600828930)
|
|
// console.log('new Seed', data[id])
|
|
}
|
|
}
|
|
return data
|
|
}
|
|
|
|
function updateSeed(id, val) {
|
|
console.log(val)
|
|
if (!Array.isArray(window._appData.data[id].inputs.seed)) window._appData.data[id].inputs.seed = Math.round(val);
|
|
}
|
|
|
|
|
|
function queuePrompt(promptWorkflow, seed, client_id) {
|
|
// appinfo升级后 兼容,补丁
|
|
for (const id in promptWorkflow) {
|
|
if (promptWorkflow[id].class_type == 'AppInfo') {
|
|
promptWorkflow[id].inputs.category = promptWorkflow[id].inputs.category || ""
|
|
}
|
|
}
|
|
// 随机seed
|
|
promptWorkflow = randomSeed(seed, promptWorkflow);
|
|
|
|
let url = get_url()
|
|
const data = JSON.stringify({ prompt: promptWorkflow, client_id });
|
|
fetch(`${url}/prompt`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: data,
|
|
})
|
|
.then(response => {
|
|
// Handle response here
|
|
console.log(response)
|
|
})
|
|
.catch(error => {
|
|
// Handle error here
|
|
});
|
|
}
|
|
|
|
|
|
function success(isSuccess, btn, text) {
|
|
isSuccess ? btn.innerText = 'success' : text;
|
|
setTimeout(() => {
|
|
btn.innerText = text;
|
|
}, 5000)
|
|
}
|
|
|
|
async function get_my_app(category = "", filename = null) {
|
|
let url = get_url()
|
|
const res = await fetch(`${url}/mixlab/workflow`, {
|
|
method: 'POST',
|
|
body: JSON.stringify({
|
|
task: 'my_app',
|
|
filename,
|
|
category
|
|
})
|
|
})
|
|
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) {
|
|
}
|
|
return data
|
|
}
|
|
|
|
|
|
function createOutputs(outputData, link) {
|
|
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';
|
|
|
|
let isURL = false;
|
|
try {
|
|
new URL(link)
|
|
isURL = true;
|
|
} catch (error) {
|
|
isURL = false;
|
|
}
|
|
// new URL(link)
|
|
if (isURL) {
|
|
const linkBtn = document.createElement('button');
|
|
// linkBtn.href = link;
|
|
linkBtn.innerText = 'go to'
|
|
action.appendChild(linkBtn)
|
|
linkBtn.style.marginLeft = '18px';
|
|
linkBtn.addEventListener('click', e => {
|
|
e.preventDefault();
|
|
window.open(link);
|
|
})
|
|
}
|
|
|
|
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)
|
|
if (node.class_type == "ShowTextForGPT") {
|
|
let div = document.createElement('div');
|
|
div.className = "show_text"
|
|
div.id = `output_${node.id}`;
|
|
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 a = document.createElement('a');
|
|
a.id = `output_${node.id}`
|
|
a.setAttribute('data-pswp-width', "200");
|
|
a.setAttribute('data-pswp-height', "200");
|
|
a.setAttribute('target', "_blank");
|
|
a.setAttribute('href', base64Df);
|
|
|
|
let img = new Image();
|
|
// img;
|
|
img.src = base64Df;
|
|
a.appendChild(img)
|
|
output_card.appendChild(a);
|
|
}
|
|
|
|
// video ,gif
|
|
if (["VHS_VideoCombine"].includes(node.class_type)) {
|
|
|
|
let a = document.createElement('a');
|
|
a.id = `output_${node.id}`
|
|
a.setAttribute('data-pswp-width', "200");
|
|
a.setAttribute('data-pswp-height', "200");
|
|
a.setAttribute('target', "_blank");
|
|
a.setAttribute('href', base64Df);
|
|
|
|
// TODO 支持视频 https://photoswipe.com/custom-content/
|
|
|
|
// let v = document.createElement('div');
|
|
let video = document.createElement('video'), img = new Image();
|
|
video.style.display = 'none'
|
|
video.controls = 'true'
|
|
video.autoplay = 'true'
|
|
video.loop = 'true'
|
|
// v.id = `output_${node.id}`;
|
|
|
|
img.src = base64Df;
|
|
|
|
a.appendChild(video);
|
|
a.appendChild(img);
|
|
output_card.appendChild(a);
|
|
}
|
|
|
|
}
|
|
return container
|
|
}
|
|
|
|
|
|
async function calculateImageHash(blob) {
|
|
const buffer = await blob.arrayBuffer();
|
|
const hashBuffer = await crypto.subtle.digest('SHA-256', buffer);
|
|
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
const hashHex = hashArray.map(byte => byte.toString(16).padStart(2, '0')).join('');
|
|
return hashHex;
|
|
}
|
|
|
|
async function handleClipboardImage(imageElement, data) {
|
|
try {
|
|
const clipboardItems = await navigator.clipboard.read();
|
|
for (const clipboardItem of clipboardItems) {
|
|
for (const type of clipboardItem.types) {
|
|
|
|
if (type.startsWith('image/')) {
|
|
const fileBlob = await clipboardItem.getType(type);
|
|
// // 获取读取的文件内容,即 Blob 对象
|
|
let hashId = await calculateImageHash(fileBlob)
|
|
|
|
if (hashId == window._appData.data[data.id].hashId) return
|
|
|
|
let { url, name } = await uploadImage(fileBlob);
|
|
// 在这里可以对 Blob 对象进行进一步处理
|
|
imageElement.src = url;
|
|
window._appData.data[data.id].inputs.image = name;
|
|
window._appData.data[data.id].hashId = hashId;
|
|
|
|
console.log("上传的文件:", url, data.id, name);
|
|
|
|
// const img = document.createElement('img');
|
|
// img.src = URL.createObjectURL(blob);
|
|
// document.body.appendChild(img);
|
|
// console.log( URL.createObjectURL(blob));
|
|
}
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error('无法读取剪贴板中的图片:', error);
|
|
}
|
|
}
|
|
|
|
|
|
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");
|
|
container.className = 'input_card'
|
|
|
|
// const inputData = [
|
|
// {
|
|
// inputs: {
|
|
// image: "1703554480406.png",
|
|
// upload: "image"
|
|
// },
|
|
// class_type: "LoadImage"
|
|
// },
|
|
// {
|
|
// inputs: {
|
|
// image: "6b7f3c570ee13ef22aad3d26dcc7414.png",
|
|
// upload: "image"
|
|
// },
|
|
// class_type: "LoadImage"
|
|
// }
|
|
// ];
|
|
inputData = inputData.filter(inp => inp);
|
|
// console.log('inputData',inputData)
|
|
inputData.forEach(data => {
|
|
// console.log(data)
|
|
// Check if the class_type is "LoadImage"
|
|
if (data.class_type === "LoadImage") {
|
|
// Create a container for the upload control
|
|
const uploadContainer = document.createElement("div");
|
|
uploadContainer.className = 'card';
|
|
|
|
// 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');
|
|
|
|
// Create an input field for the image name
|
|
const uploadImageInput = document.createElement("button");
|
|
uploadImageInput.style = `width: 88px;`;
|
|
uploadImageInput.innerText = 'upload'
|
|
const uploadImageInputHide = document.createElement('input');
|
|
uploadImageInputHide.type = "file";
|
|
uploadImageInputHide.style.display = "none"
|
|
actionDiv.appendChild(uploadImageInput);
|
|
actionDiv.appendChild(uploadImageInputHide);
|
|
|
|
const btnFromClipboard = document.createElement("button");
|
|
btnFromClipboard.style = `width: 156px; margin-left: 18px;`
|
|
btnFromClipboard.innerText = 'paste from clipboard'
|
|
actionDiv.appendChild(btnFromClipboard);
|
|
|
|
uploadContainer.appendChild(actionDiv)
|
|
|
|
// Create an image element to display the uploaded image
|
|
const imageElement = document.createElement("img");
|
|
imageElement.src = base64Df
|
|
imageElement.style.maxWidth = '200px';
|
|
|
|
|
|
btnFromClipboard.addEventListener('click', (event) => handleClipboardImage(imageElement, data));
|
|
|
|
|
|
uploadImageInput.addEventListener('click', (event) => {
|
|
uploadImageInputHide.click()
|
|
})
|
|
uploadImageInputHide.addEventListener('change', (event) => {
|
|
|
|
// 获取用户选择的文件
|
|
const file = event.target.files[0];
|
|
|
|
// 创建一个 FileReader 对象
|
|
const reader = new FileReader();
|
|
|
|
// 读取文件并在读取完成后执行回调函数
|
|
reader.onloadend = async function () {
|
|
// 获取读取的文件内容,即 Blob 对象
|
|
const fileBlob = new Blob([reader.result], { type: file.type });
|
|
|
|
let hashId = await calculateImageHash(fileBlob)
|
|
|
|
if (hashId == window._appData.data[data.id].hashId) return
|
|
|
|
let { url, name } = await uploadImage(fileBlob)
|
|
// 在这里可以对 Blob 对象进行进一步处理
|
|
imageElement.src = url;
|
|
window._appData.data[data.id].inputs.image = name;
|
|
window._appData.data[data.id].hashId = hashId;
|
|
|
|
console.log("上传的文件:", url, data.id, name);
|
|
};
|
|
|
|
// 开始读取文件
|
|
reader.readAsArrayBuffer(file);
|
|
|
|
|
|
})
|
|
|
|
// imageElement.src = `${get_url()}/view?filename=${encodeURIComponent(data.inputs.image)}&type=${type}&subfolder=${subfolder}`;
|
|
uploadContainer.appendChild(imageElement);
|
|
|
|
// Append the upload container to the main container
|
|
container.appendChild(uploadContainer);
|
|
}
|
|
|
|
if (["PromptSlide"].includes(data.class_type)) {
|
|
// 滑块输入
|
|
let options = data.options || {
|
|
min: -3,
|
|
max: 3,
|
|
};
|
|
|
|
const label = data.title;
|
|
|
|
if (!options.keywords.includes(data.inputs.prompt_keyword)) {
|
|
options.keywords = [data.inputs.prompt_keyword, ...options.keywords]
|
|
};
|
|
|
|
// data.title == 'PromptSlide ♾️Mixlab' ? data.inputs.prompt_keyword : data.title
|
|
let silde = createNumSlide(label,
|
|
data.inputs.weight,
|
|
(v) => {
|
|
window._appData.data[data.id].inputs.weight = parseFloat(v);
|
|
},
|
|
options.min,
|
|
options.max,
|
|
'float',
|
|
options.keywords,
|
|
data.id
|
|
)
|
|
container.appendChild(silde);
|
|
}
|
|
|
|
|
|
if (['FloatSlider', 'IntNumber'].includes(data.class_type)) {
|
|
// console.log('data.options',data.options)
|
|
// 滑块输入
|
|
let options = data.options || {
|
|
min: 0,
|
|
max: data.class_type === 'IntNumber' ? 6000 : 1,
|
|
};
|
|
let silde = createNumSlide(data.title,
|
|
data.inputs.number,
|
|
(v) => {
|
|
window._appData.data[data.id].inputs.number = v;
|
|
},
|
|
options.min,
|
|
options.max,
|
|
data.class_type === 'IntNumber' ? 'int' : 'float')
|
|
container.appendChild(silde);
|
|
}
|
|
|
|
// Check if the class_type is "CLIPTextEncode"
|
|
if (["TextInput_", "CLIPTextEncode"].includes(data.class_type)) {
|
|
// Create a container for the upload control
|
|
const uploadContainer = document.createElement("div");
|
|
uploadContainer.className = 'card';
|
|
|
|
// Create a label for the upload control
|
|
const nameLabel = document.createElement("label");
|
|
nameLabel.textContent = data.title || "CLIPTextEncode: ";
|
|
uploadContainer.appendChild(nameLabel);
|
|
|
|
// Create an input field for the image name
|
|
const textInput = document.createElement("textarea");
|
|
// uploadImageInput.type = "text";
|
|
textInput.value = data.inputs.text;
|
|
uploadContainer.appendChild(textInput);
|
|
|
|
function autoResize(textarea) {
|
|
textarea.style.height = 'auto';
|
|
textarea.style.height = textarea.scrollHeight + 'px';
|
|
}
|
|
|
|
textInput.addEventListener('input', (event) => {
|
|
// console.log(textInput.value)
|
|
autoResize(textInput);
|
|
window._appData.data[data.id].inputs.text = textInput.value;
|
|
})
|
|
|
|
// Append the upload container to the main container
|
|
container.appendChild(uploadContainer);
|
|
}
|
|
|
|
|
|
if (["CheckpointLoaderSimple", "LoraLoader"].includes(data.class_type)) {
|
|
let value = data.inputs.ckpt_name || data.inputs.lora_name;
|
|
|
|
let [div, selectDom] = createSelectWithOptions(data.title, Array.from(data.options, o => {
|
|
return {
|
|
value: o,
|
|
text: o
|
|
}
|
|
}), value);
|
|
|
|
selectDom.addEventListener('change', e => {
|
|
e.preventDefault();
|
|
// console.log(selectDom.value)
|
|
if (data.class_type === 'CheckpointLoaderSimple') {
|
|
window._appData.data[data.id].inputs.ckpt_name = selectDom.value;
|
|
}
|
|
if (data.class_type === 'LoraLoader') {
|
|
window._appData.data[data.id].inputs.lora_name = selectDom.value;
|
|
}
|
|
})
|
|
|
|
container.appendChild(div);
|
|
}
|
|
|
|
if (["Color"].includes(data.class_type)) {
|
|
let value = data.inputs.color.hex || '#000000';
|
|
let d = document.createElement('div');
|
|
d.className = 'card';
|
|
|
|
let label = document.createElement('label');
|
|
label.innerText = data.title;
|
|
|
|
let color = document.createElement('div');
|
|
color.id = `color_input_${data.id}`;
|
|
color.className = 'color_input';
|
|
color.setAttribute('data-color', value);
|
|
color.setAttribute('data-id', data.id);
|
|
|
|
d.appendChild(label);
|
|
d.appendChild(color);
|
|
|
|
container.appendChild(d);
|
|
}
|
|
|
|
|
|
});
|
|
return container
|
|
}
|
|
|
|
function createColorInput(elId, value, nodeId) {
|
|
const pickr = Pickr.create({
|
|
el: `#${elId}`,
|
|
theme: 'classic',
|
|
default: value,
|
|
swatches: [
|
|
'rgba(244, 67, 54, 1)',
|
|
'rgba(233, 30, 99, 0.95)',
|
|
'rgba(156, 39, 176, 0.9)',
|
|
'rgba(103, 58, 183, 0.85)',
|
|
'rgba(63, 81, 181, 0.8)',
|
|
'rgba(33, 150, 243, 0.75)',
|
|
'rgba(3, 169, 244, 0.7)',
|
|
'rgba(0, 188, 212, 0.7)',
|
|
'rgba(0, 150, 136, 0.75)',
|
|
'rgba(76, 175, 80, 0.8)',
|
|
'rgba(139, 195, 74, 0.85)',
|
|
'rgba(205, 220, 57, 0.9)',
|
|
'rgba(255, 235, 59, 0.95)',
|
|
'rgba(255, 193, 7, 1)'
|
|
],
|
|
components: {
|
|
// Main components
|
|
preview: true,
|
|
opacity: true,
|
|
hue: true,
|
|
// Input / output Options
|
|
interaction: {
|
|
hex: true,
|
|
rgba: true,
|
|
hsla: true,
|
|
hsva: true,
|
|
cmyk: true,
|
|
input: true,
|
|
// clear: true,
|
|
save: true,
|
|
cancel: true
|
|
}
|
|
}
|
|
});
|
|
|
|
pickr
|
|
.on('save', (color, instance) => {
|
|
try {
|
|
// console.log(color)
|
|
// window._appData.data[data.id].inputs.color.hex = color.toHEXA().toString();
|
|
let [r, g, b, a] = color.toRGBA();
|
|
window._appData.data[nodeId].inputs.color = {
|
|
...window._appData.data[nodeId].inputs.color,
|
|
r, g, b, a,
|
|
hex: color.toHEXA().toString()
|
|
}
|
|
} catch (error) { }
|
|
})
|
|
.on('cancel', instance => {
|
|
pickr && pickr.hide()
|
|
})
|
|
}
|
|
|
|
function createAllColorInput() {
|
|
for (const cInp of document.querySelectorAll('.color_input')) {
|
|
createColorInput(cInp.id, cInp.getAttribute('data-color'), cInp.getAttribute('data-id'));
|
|
}
|
|
}
|
|
|
|
function createToggleBtn() {
|
|
var toggleButton = document.createElement("button");
|
|
toggleButton.innerHTML = `<svg style="width:24px;height: 24px;vertical-align: middle;fill: currentColor;overflow: hidden;" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3972"><path d="M514 114.3c-219.9 0-398.9 178.9-398.9 398.8 0.1 220 179 398.9 398.9 398.9 219.9 0 398.8-178.9 398.8-398.8S733.9 114.3 514 114.3z m218.3 489v1.7c0 0.5-0.1 1-0.1 1.6 0 0.3 0 0.6-0.1 0.9 0 0.5-0.1 1-0.2 1.5 0 0.3-0.1 0.7-0.1 1-0.1 0.4-0.1 0.8-0.2 1.2-0.1 0.4-0.2 0.9-0.2 1.3-0.1 0.3-0.1 0.6-0.2 0.8-0.1 0.6-0.3 1.2-0.4 1.8 0 0.1-0.1 0.2-0.1 0.3-2.2 8.5-6.6 16.6-13.3 23.3L600.7 755.4c-20 20-52.7 20-72.6 0-20-20-20-52.7 0-72.6l28.9-28.9H347c-28.3 0-51.4-23.1-51.4-51.4 0-28.3 23.1-51.4 51.4-51.4h334c13.2 0 26.4 5 36.4 15s15 23.2 15 36.4c0 0.3-0.1 0.6-0.1 0.8z m0.1-179.5c0 28.3-23.1 51.4-51.4 51.4H347c-13.2 0-26.4-5-36.4-15s-15-23.2-15-36.4v-0.8-1.6c0-0.5 0.1-1.1 0.1-1.6 0-0.3 0-0.6 0.1-0.9 0-0.5 0.1-1 0.2-1.5 0-0.3 0.1-0.7 0.1-1 0.1-0.4 0.1-0.8 0.2-1.2 0.1-0.4 0.2-0.9 0.2-1.3 0.1-0.3 0.1-0.6 0.2-0.8 0.1-0.6 0.3-1.2 0.4-1.8 0-0.1 0.1-0.2 0.1-0.3 2.2-8.5 6.6-16.6 13.3-23.3l116.6-116.6c20-20 52.7-20 72.6 0 20 20 20 52.7 0 72.6L471 372.5h210c28.2 0 51.4 23.1 51.4 51.3z" p-id="3973"></path></svg>`;
|
|
toggleButton.className = 'toggle'
|
|
return toggleButton
|
|
}
|
|
|
|
function createCheckbox(defaultValue, labelText) {
|
|
let div = document.createElement('div');
|
|
// Create a checkbox element
|
|
var checkbox = document.createElement('input');
|
|
checkbox.type = 'checkbox';
|
|
|
|
// Set the default value
|
|
checkbox.checked = defaultValue;
|
|
|
|
// Create a label element
|
|
var label = document.createElement('label');
|
|
label.innerHTML = labelText;
|
|
|
|
// Append the checkbox and label to the body or any other container element
|
|
div.appendChild(checkbox);
|
|
div.appendChild(label);
|
|
|
|
return [div, checkbox]
|
|
|
|
}
|
|
|
|
function createNumSlide(labelText, value = 0, callback, minValue = 0, maxValue = 1, type = 'float', keywords = null, targetId = null) {
|
|
|
|
value = 'float' ? parseFloat(value.toFixed(3)) : parseInt(value)
|
|
|
|
// 输入div
|
|
let inputDiv = document.createElement('div');
|
|
inputDiv.style.display = 'flex'
|
|
|
|
// 创建滑块输入元素
|
|
var slider = document.createElement("input");
|
|
slider.type = "range";
|
|
slider.min = minValue;
|
|
slider.max = maxValue;
|
|
slider.step = type == 'float' ? 0.01 : 1
|
|
slider.value = value;
|
|
slider.style.width = '150px'
|
|
|
|
// 创建标签元素
|
|
var label = document.createElement("label");
|
|
label.innerHTML = labelText;
|
|
label.setAttribute('data-content', value);
|
|
|
|
if (keywords && keywords[0]) {
|
|
// label.innerHTML = ``
|
|
// 有备选的关键词
|
|
let selectTag = createSelect(Array.from(keywords, (k,i) => {
|
|
return {
|
|
value: k,
|
|
text: k,
|
|
selected:i==0
|
|
}
|
|
}), keywords[0]);
|
|
selectTag.style = `background: none;
|
|
color: black; max-width: 300px;
|
|
border-bottom: 1px solid #acacac;
|
|
border-radius: 0;`
|
|
// selectTag.setAttribute('data-content',labelText);
|
|
selectTag.addEventListener('change', e => {
|
|
e.preventDefault();
|
|
window._appData.data[targetId].inputs.prompt_keyword = selectTag.value;
|
|
// label.querySelector('.label').innerText = selectTag.value
|
|
// console.log(window._appData.data[targetId].inputs.prompt_keyword,selectTag.value)
|
|
})
|
|
label.appendChild(selectTag);
|
|
}
|
|
|
|
// 创建容器元素,并将滑块输入和标签添加到容器中
|
|
var container = document.createElement("div");
|
|
container.appendChild(label);
|
|
|
|
container.className = 'card';
|
|
|
|
|
|
// 创建切换按钮元素
|
|
var toggleButton = createToggleBtn()
|
|
|
|
toggleButton.addEventListener("click", function () {
|
|
if (slider.type === 'range') {
|
|
slider.type = 'number'
|
|
} else {
|
|
slider.type = 'range'
|
|
}
|
|
});
|
|
|
|
inputDiv.appendChild(slider);
|
|
inputDiv.appendChild(toggleButton);
|
|
container.appendChild(inputDiv)
|
|
|
|
// 添加change事件监听器
|
|
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)
|
|
});
|
|
|
|
// 返回容器元素
|
|
return container;
|
|
|
|
}
|
|
|
|
function createSelect(options, defaultValue) {
|
|
var selectElement = document.createElement("select");
|
|
selectElement.className = "select"
|
|
|
|
// 循环遍历选项数组
|
|
for (var i = 0; i < options.length; i++) {
|
|
var option = document.createElement("option");
|
|
option.value = options[i].value;
|
|
option.text = options[i].text;
|
|
selectElement.appendChild(option);
|
|
// if(options[i].selected)
|
|
}
|
|
|
|
// 设置默认值
|
|
selectElement.value = defaultValue;
|
|
console.log(defaultValue,options)
|
|
return selectElement
|
|
}
|
|
|
|
function createSelectWithOptions(title, options, defaultValue) {
|
|
|
|
const div = document.createElement("div");
|
|
div.className = 'card';
|
|
|
|
// Create a label for the upload control
|
|
const nameLabel = document.createElement("label");
|
|
nameLabel.textContent = title;
|
|
div.appendChild(nameLabel);
|
|
|
|
var selectElement = createSelect(options, defaultValue);
|
|
|
|
div.appendChild(selectElement)
|
|
|
|
return [div, selectElement];
|
|
}
|
|
|
|
function getFilenameAndCategoryFromUrl(url) {
|
|
const queryString = url.split('?')[1];
|
|
if (!queryString) {
|
|
return {};
|
|
}
|
|
|
|
const params = new URLSearchParams(queryString);
|
|
|
|
const filename = params.get('filename') ? decodeURIComponent(params.get('filename')) : null;
|
|
const category = params.get('category') ? decodeURIComponent(params.get('category') || '') : '';
|
|
|
|
return { category, filename };
|
|
}
|
|
|
|
function createImage(url) {
|
|
let im = new Image()
|
|
return new Promise((res, rej) => {
|
|
im.onload = () => res(im)
|
|
im.src = url
|
|
})
|
|
}
|
|
|
|
function createUI(data, share = true) {
|
|
// appData.input, appData.output, appData.seed, share, appData.link
|
|
const { input: inputData, output: outputData, data: workflow, seed, link } = data;
|
|
|
|
let mainDiv = document.createElement('div');
|
|
let leftDetails = document.createElement('details');
|
|
leftDetails.setAttribute('open', 'true')
|
|
leftDetails.innerHTML = `<summary>INPUT</summary>
|
|
<div class="content"></div>`
|
|
|
|
let leftDiv = leftDetails.querySelector('.content');
|
|
let rightDiv = document.createElement('div');
|
|
|
|
mainDiv.className = 'app'
|
|
leftDiv.className = 'panel'
|
|
leftDiv.style.alignItems = 'flex-start';
|
|
rightDiv.className = 'panel'
|
|
leftDiv.style.flex = 0.4
|
|
rightDiv.style.flex = 1;
|
|
// rightDiv.style.height='70vh'
|
|
// rightDiv.style=`position: fixed;
|
|
// right: 0;
|
|
// 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)}&category=${encodeURIComponent(window._appData.category || '')}`;
|
|
copyTextToClipboard(url, success(e, shareBtn, 'copy url'));
|
|
})
|
|
|
|
titleDiv.appendChild(shareBtn);
|
|
}
|
|
|
|
|
|
|
|
let iconDes = document.createElement('div');
|
|
// 创建应用图标
|
|
var icon = document.createElement('img');
|
|
icon.style.width = '48px';
|
|
// icon.style.height = '98px';
|
|
icon.src = base64Df;
|
|
|
|
var des = document.createElement('p');
|
|
des.style = `margin-left: 12px; font-size: 14px;`
|
|
iconDes.appendChild(icon)
|
|
iconDes.appendChild(des);
|
|
iconDes.className = 'description'
|
|
|
|
// 创建状态标签
|
|
let statusDiv = document.createElement('div');
|
|
statusDiv.className = 'status_seed'
|
|
var status = document.createElement('div');
|
|
status.textContent = 'Status';
|
|
status.className = 'status';
|
|
|
|
// seed 汇总
|
|
var seeds = document.createElement('details');
|
|
// seeds.textContent = 'Status';
|
|
seeds.className = 'seeds';
|
|
|
|
try {
|
|
if (Object.keys(seed).length > 0) {
|
|
seeds.innerHTML = `<summary>SEED</summary>
|
|
<div class="content"> </div>`;
|
|
const content = seeds.querySelector('.content')
|
|
for (const id in seed) {
|
|
const s = seed[id];
|
|
if (!Array.isArray(workflow[id].inputs.seed)) {
|
|
|
|
let seedInput = document.createElement('div');
|
|
content.appendChild(seedInput)
|
|
seedInput.style = `outline: 1px dashed gray;margin-bottom: 12px;margin-top: 12px;`
|
|
|
|
let em = document.createElement('em');
|
|
let emText = document.createElement('span');
|
|
emText.innerText = `#${id} ${s.toUpperCase()}`;
|
|
em.appendChild(emText);
|
|
|
|
seedInput.appendChild(em)
|
|
|
|
if (s === 'fixed') {
|
|
// console.log('###fiex',1)
|
|
let inSeed = createNumSlide(``, 0, (newSeed) => updateSeed(id, newSeed), 0, 1849378600828930, 'int');
|
|
inSeed.style = `padding: 8px;background: none`
|
|
|
|
let toggleRandomize = createToggleBtn();
|
|
toggleRandomize.style = `background:none;color:black`;
|
|
|
|
toggleRandomize.addEventListener("click", function () {
|
|
if (data.seed[id] === 'randomize') {
|
|
data.seed[id] = 'fixed';
|
|
inSeed.style.display = 'block'
|
|
} else {
|
|
data.seed[id] = 'randomize';
|
|
inSeed.style.display = 'none'
|
|
}
|
|
emText.innerText = `#${id} ${data.seed[id].toUpperCase()}`;
|
|
});
|
|
|
|
seedInput.appendChild(inSeed);
|
|
em.appendChild(toggleRandomize);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
}
|
|
}
|
|
|
|
} catch (error) {
|
|
console.log(error)
|
|
}
|
|
|
|
|
|
statusDiv.appendChild(status);
|
|
statusDiv.appendChild(seeds);
|
|
|
|
// 创建输入框
|
|
var input1 = createInputs(inputData)
|
|
|
|
var output = createOutputs(outputData, link)
|
|
|
|
// 创建提交按钮
|
|
var submitButton = document.createElement('button');
|
|
submitButton.textContent = 'Create';
|
|
submitButton.className = 'run_btn'
|
|
|
|
// 将所有UI元素添加到页面中
|
|
leftDiv.appendChild(titleDiv);
|
|
leftDiv.appendChild(iconDes);
|
|
// leftDiv.appendChild(des);
|
|
leftDiv.appendChild(statusDiv);
|
|
leftDiv.appendChild(input1);
|
|
mainDiv.appendChild(submitButton);
|
|
|
|
rightDiv.appendChild(output);
|
|
|
|
mainDiv.appendChild(leftDetails);
|
|
mainDiv.appendChild(rightDiv);
|
|
|
|
document.body.appendChild(mainDiv)
|
|
|
|
// 返回每个UI元素的引用和对应的更新方法
|
|
return {
|
|
title: {
|
|
element: title,
|
|
update: function (newTitle) {
|
|
title.textContent = newTitle;
|
|
}
|
|
},
|
|
icon: {
|
|
element: icon,
|
|
update: function (newIconPath) {
|
|
icon.src = newIconPath;
|
|
}
|
|
},
|
|
des: {
|
|
element: des,
|
|
update: function (text) {
|
|
des.textContent = text;
|
|
}
|
|
},
|
|
status: {
|
|
element: status,
|
|
update: function (newStatus) {
|
|
status.textContent = newStatus;
|
|
}
|
|
},
|
|
input1: {
|
|
element: input1,
|
|
update: function () {
|
|
// 可以在这里添加上传图片的逻辑
|
|
}
|
|
},
|
|
output: {
|
|
element: output,
|
|
update: async function (type = "image", val, id) {
|
|
console.log(val, id)
|
|
if (val && type == "image" && output.querySelector(`#output_${id} img`)) {
|
|
// if (output.querySelector(`#output_${id}`)) {
|
|
|
|
let im = await createImage(val)
|
|
|
|
output.querySelector(`#output_${id} img`).src = val;
|
|
|
|
let a = output.querySelector(`#output_${id}`);
|
|
a.setAttribute('data-pswp-width', im.naturalWidth);
|
|
a.setAttribute('data-pswp-height', im.naturalHeight);
|
|
a.setAttribute('target', "_blank");
|
|
a.setAttribute('href', val);
|
|
|
|
// }
|
|
// else {
|
|
// output.querySelector(`#output_${id}`).src = val;
|
|
// }
|
|
|
|
}
|
|
|
|
if (val && type == "video" && output.querySelector(`#output_${id} video`)) {
|
|
|
|
let video = output.querySelector(`#output_${id} video`);
|
|
let img = output.querySelector(`#output_${id} img`);
|
|
img.style.display = 'none';
|
|
video.style.display = 'block';
|
|
|
|
video.onloadeddata = function () {
|
|
|
|
let a = output.querySelector(`#output_${id}`);
|
|
a.setAttribute('data-pswp-width', video.videoWidth);
|
|
a.setAttribute('data-pswp-height', video.videoHeight);
|
|
a.setAttribute('target', "_blank");
|
|
a.setAttribute('href', val);
|
|
};
|
|
|
|
video.src = val;
|
|
|
|
}
|
|
|
|
if (val && type == "text" && output.querySelector(`#output_${id}`)) output.querySelector(`#output_${id}`).innerText = val;
|
|
|
|
}
|
|
},
|
|
submitButton: {
|
|
element: submitButton,
|
|
update: function (callback) {
|
|
submitButton.addEventListener('dblclick', (e) => {
|
|
submitButton.classList.remove('disabled');
|
|
});
|
|
submitButton.addEventListener('click', (e) => {
|
|
|
|
if (!submitButton.classList.contains('disabled')) {
|
|
callback && callback();
|
|
submitButton.classList.add('disabled');
|
|
setTimeout(() => submitButton.classList.remove('disabled'), 500)
|
|
}
|
|
|
|
});
|
|
}
|
|
},
|
|
};
|
|
}
|
|
|
|
function createUploadJson(detail) {
|
|
|
|
// 创建一个div元素
|
|
var div = document.createElement('div');
|
|
div.className = 'upload_btn card'
|
|
div.textContent = '上传并运行你的JSON文件';
|
|
div.addEventListener('click', function () {
|
|
document.getElementById('jsonFileInput').click();
|
|
});
|
|
|
|
// 创建一个input元素
|
|
var input = document.createElement('input');
|
|
input.type = 'file';
|
|
input.id = 'jsonFileInput';
|
|
input.style.display = 'none';
|
|
input.addEventListener('change', function (event) {
|
|
var file = event.target.files[0];
|
|
var reader = new FileReader();
|
|
reader.onload = function (e) {
|
|
var contents = e.target.result;
|
|
var jsonData = JSON.parse(contents);
|
|
|
|
|
|
Array.from(detail.querySelectorAll('.card'), c => c.classList.remove('selected'));
|
|
div.className = 'upload_btn card selected'
|
|
|
|
|
|
let { output, app } = jsonData;
|
|
|
|
window._appData = {
|
|
...app,
|
|
data: output
|
|
};
|
|
if (document.body.querySelector('.app')) document.body.querySelector('.app').remove()
|
|
createApp(window._appData, false);
|
|
|
|
// res(jsonData);
|
|
};
|
|
reader.readAsText(file);
|
|
});
|
|
|
|
// 将div和input元素添加到body中
|
|
// document.body.appendChild(div);
|
|
document.body.appendChild(input);
|
|
return div
|
|
}
|
|
|
|
|
|
async function createApp(appData, share = true) {
|
|
// console.log(appData)
|
|
// 使用示例:
|
|
var ui = createUI(appData, share);
|
|
|
|
// 更新标题
|
|
ui.title.update(appData.name || 'Mixlab APP');
|
|
|
|
// 更新应用图标
|
|
ui.icon.update(appData.icon || base64Df);
|
|
|
|
ui.des.update(appData.description || '-');
|
|
|
|
// 更新状态标签
|
|
ui.status.update(appData ? 'READY' : '-');
|
|
|
|
// 添加提交按钮点击事件
|
|
ui.submitButton.update(function () {
|
|
// 在提交按钮点击时执行的逻辑
|
|
queuePrompt(window._appData.data, window._appData.seed, api.clientId)
|
|
});
|
|
|
|
|
|
const show = (src, id, type = "image") => {
|
|
// console.log(src)
|
|
ui.output.update(type, src, id)
|
|
};
|
|
|
|
api.addEventListener("status", ({ detail }) => {
|
|
console.log("status", detail);
|
|
try {
|
|
ui.status.update(`queue#${detail.exec_info.queue_remaining}`);
|
|
} catch (error) {
|
|
|
|
}
|
|
|
|
});
|
|
|
|
api.addEventListener("progress", ({ detail }) => {
|
|
console.log("progress", detail);
|
|
try {
|
|
ui.status.update(`${detail.value}/${detail.max}`);
|
|
} catch (error) {
|
|
|
|
}
|
|
});
|
|
|
|
api.addEventListener("executed", ({ detail }) => {
|
|
console.log("executed", detail)
|
|
// if (!enabled) return;
|
|
const images = detail?.output?.images;
|
|
const text = detail?.output?.text;
|
|
const gifs = detail?.output?.gifs;
|
|
|
|
if (images) {
|
|
// 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) {
|
|
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)
|
|
}&&format=${gifs[0].format}&t=${+new Date()}`;
|
|
|
|
show(src, detail.node, gifs[0].format.match('video') ? 'video' : 'image');
|
|
}
|
|
|
|
|
|
try {
|
|
ui.status.update(`executed_#${detail.node}`);
|
|
} catch (error) {
|
|
|
|
}
|
|
|
|
});
|
|
|
|
// api.addEventListener("b_preview", ({ detail }) => {
|
|
// // if (!enabled) return;
|
|
// console.log("b_preview", detail)
|
|
// show(URL.createObjectURL(detail));
|
|
// });
|
|
|
|
|
|
api.addEventListener('execution_start', ({ detail }) => {
|
|
console.log("execution_start", detail)
|
|
try {
|
|
ui.status.update(`execution_start`);
|
|
} catch (error) {
|
|
|
|
}
|
|
})
|
|
|
|
api.api_base = ""
|
|
api.init();
|
|
|
|
// 外挂的UI
|
|
|
|
createAllColorInput();
|
|
|
|
|
|
const lightbox = new PhotoSwipeLightbox({
|
|
gallery: '.output_card',
|
|
children: 'a',
|
|
pswpModule: () => import('/extensions/comfyui-mixlab-nodes/lib/photoswipe.esm.min.js')
|
|
});
|
|
lightbox.init();
|
|
// window._lightbox = lightbox
|
|
|
|
// lightbox.addFilter('preventPointerEvent', (preventPointerEvent, originalEvent, pointerType) => {
|
|
// // return true to preventDefault pointermove/pointerdown events
|
|
// // (also applies to touchmove/mousemove)
|
|
// return true;
|
|
// });
|
|
|
|
}
|
|
|
|
// 创建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.category, 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() {
|
|
|
|
const { category, filename } = getFilenameAndCategoryFromUrl(location.href);
|
|
window._apps = await get_my_app(category, filename);
|
|
|
|
window._appData = window._apps[0];
|
|
|
|
createAppList(window._apps);
|
|
|
|
createApp(window._appData);
|
|
|
|
|
|
};
|
|
|
|
init_app()
|
|
</script>
|
|
|
|
|
|
</body>
|
|
|
|
</html> |