This commit is contained in:
shadowcz007
2023-12-27 16:29:00 +08:00
parent 4d309435c8
commit 2d336afb85
14 changed files with 347 additions and 70 deletions
+13 -15
View File
@@ -1,20 +1,16 @@
##
v0.5.0 🚀🚗🚚🏃‍
- Added video composition support to the MergeLayers.
- Enhanced visual selection support for the NewLayer node.
- Introduced the NoiseImage node and ResizeImage node.
- Improved compatibility for TextImage with line breaks.
- Optimized the 3DImage node to export textures for modification.
- [Added DynamicDelayByText, enabling delayed execution based on input text length.](./workflow/audio-chatgpt-workflow.json)
v0.6.0 🚀🚗🚚🏃‍ Workflow-to-APP
- 新增AppInfo节点,可以通过简单的配置,把workflow转变为一个Web APP。
- Add the AppInfo node, which allows you to transform the workflow into a web app by simple configuration.
![](./assets/appinfo-readme.png)
Example:
- workflow
![APP info](./workflow/appinfo-workflow.svg)
- 为MergeLayers添加了视频合成功能。
- NewLayer节点增加了视觉选择支持。
- 添加了NoiseImage节点和ResizeImage节点。
- 支持带有换行的文本图像。
- 对3D节点进行了优化,支持导出纹理以进行修改。
- [添加了DynamicDelayByText功能,可以根据输入文本的长度进行延迟执行。](./workflow/audio-chatgpt-workflow.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)
### 3D
![](./assets/3dimage.png)
@@ -66,7 +62,9 @@ https://github.com/shadowcz007/comfyui-mixlab-nodes/assets/12645064/e7e77f90-e43
## Utils
> The Color node provides a color picker for easy color selection, the Font node offers built-in font selection for use with TextImage to generate text images, and the DynamicDelayByText node allows delayed execution based on the length of the input text.
- [添加了DynamicDelayByText功能,可以根据输入文本的长度进行延迟执行。](./workflow/audio-chatgpt-workflow.json)
- [Added DynamicDelayByText, enabling delayed execution based on input text length.](./workflow/audio-chatgpt-workflow.json)
## Other Nodes
@@ -104,7 +102,7 @@ Add edges to an image.
### Improvement
- Add "help" option to the context menu for each node.
- Add "find the node" option to the global context menu.
- Add "Nodes Map" option to the global context menu.
An improvement has been made to directly redirect to GitHub to search for missing nodes when loading the graph.
+2 -2
View File
@@ -365,14 +365,13 @@ from .nodes.ScreenShareNode import ScreenShareNode,FloatingVideo
from .nodes.Clipseg import CLIPSeg,CombineMasks
from .nodes.ChatGPT import ChatGPTNode,ShowTextForGPT,CharacterInText
from .nodes.Audio import GamePal,SpeechRecognition,SpeechSynthesis
from .nodes.Utils import AppInfo,ColorInput,FontInput,TextToNumber,DynamicDelayProcessor
from .nodes.Utils import AppInfo,FloatSlider,ColorInput,FontInput,TextToNumber,DynamicDelayProcessor
# 要导出的所有节点及其名称的字典
# 注意:名称应全局唯一
NODE_CLASS_MAPPINGS = {
"AppInfo":AppInfo,
"RandomPrompt":RandomPrompt,
"NoiseImage":NoiseImage,
"TransparentImage":TransparentImage,
@@ -403,6 +402,7 @@ NODE_CLASS_MAPPINGS = {
"SpeechRecognition":SpeechRecognition,
"SpeechSynthesis":SpeechSynthesis,
"Color":ColorInput,
"FloatSlider":FloatSlider,
"Font":FontInput,
"TextToNumber":TextToNumber,
"DynamicDelayProcessor":DynamicDelayProcessor
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.

After

Width:  |  Height:  |  Size: 240 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 254 KiB

+7 -5
View File
@@ -4762,15 +4762,15 @@
"https://github.com/shadowcz007/comfyui-mixlab-nodes": [
[
"3DImage",
"AppInfo",
"ResizeImage",
"NoiseImage",
"AreaToMask",
"CLIPSeg",
"CLIPSeg_",
"CharacterInText",
"ChatGPTOpenAI",
"Color",
"CombineMasks_",
"CombineSegMasks",
"EmptyLayer",
"EnhanceImage",
"FaceToMask",
"FeatheredMask",
@@ -4792,10 +4792,12 @@
"TextImage",
"TransparentImage",
"VAEDecodeConsistencyDecoder",
"VAELoaderConsistencyDecoder"
"VAELoaderConsistencyDecoder",
"TextToNumber",
"DynamicDelayProcessor"
],
{
"title_aux": "comfyui-mixlab-nodes [WIP]"
"title_aux": "comfyui-mixlab-nodes"
}
],
"https://github.com/shiimizu/ComfyUI_smZNodes": [
+44 -1
View File
@@ -6,6 +6,18 @@ import numpy as np
import folder_paths
import matplotlib.font_manager as fm
# import json
# import hashlib
# def get_json_hash(json_content):
# json_string = json.dumps(json_content, sort_keys=True)
# hash_object = hashlib.sha256(json_string.encode())
# hash_value = hash_object.hexdigest()
# return hash_value
def tensor2pil(image):
return Image.fromarray(np.clip(255. * image.cpu().numpy().squeeze(), 0, 255).astype(np.uint8))
@@ -152,6 +164,35 @@ class TextToNumber:
result= random.randint(1, 10000000000)
return {"ui": {"text": [text],"num":[result]}, "result": (result,)}
class FloatSlider:
@classmethod
def INPUT_TYPES(s):
return {"required": {
"number":("FLOAT", {
"default": 0,
"min": 0, #Minimum value
"max": 1, #Maximum value
"step": 0.01, #Slider's step
"display": "slider" # Cosmetic only: display as "number" or "slider"
}),
},
}
RETURN_TYPES = ("FLOAT",)
FUNCTION = "run"
CATEGORY = "♾️Mixlab/utils"
INPUT_IS_LIST = False
OUTPUT_IS_LIST = (False,)
def run(self,number):
return (number,)
# 接收一个值,然后根据字符串或数值长度计算延迟时间,用户可以自定义延迟"字/s",延迟之后将转化
import comfy.samplers
@@ -274,7 +315,7 @@ class AppInfo:
FUNCTION = "run"
CATEGORY = "♾️Mixlab/utils"
CATEGORY = "♾️Mixlab"
INPUT_IS_LIST = False
OUTPUT_IS_LIST = (False,)
@@ -283,6 +324,8 @@ class AppInfo:
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,)}
+180 -15
View File
@@ -8,6 +8,9 @@
<style>
.app {
display: flex;
width: 90%;
min-width: 400px;
margin-left: 5%;
}
@@ -36,6 +39,8 @@
background: #eee;
margin: 24px;
flex: 1;
align-items: center;
justify-content: center;
}
.panel h1 {
@@ -51,8 +56,12 @@
.input_card {
display: flex;
flex-direction: column;
}
.output_card {
box-shadow: 0px 0px 8px 3px #e6e7e7;
}
.card {
background-color: #eee;
@@ -66,7 +75,7 @@
width: 100%;
height: 200px;
/* min-width: 300px; */
margin: 12px;
margin-top: 12px;
}
.card img {
@@ -86,11 +95,37 @@
cursor: pointer;
border: 3px solid;
}
.run_btn:hover{
.run_btn:hover {
border-color: yellow;
color: yellow;
}
.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;
margin-left: calc(50% - 94px);
margin-top: calc(30vh - 94px);
}
.upload_btn:hover {
outline: 4px solid yellow;
color: yellow;
}
.link {
text-decoration: none;
color: gray;
@@ -107,13 +142,13 @@
padding: 24px;
color: #4a4a4a;">Explore your creative potential with <a class="link"
href="https://github.com/shadowcz007/comfyui-mixlab-nodes" target="_blank">mixlab-nodes</a> / 尽情发挥你的创意
<br>
<br>
<a class="link" href="https://www.mixcomfy.com" target="_blank">ComfyUI中文爱好者社区推荐</a>
</div>
<div></div>
<script type="module">
import { api } from "../../../scripts/api.js";
console.log('api', api)
// console.log('api', api)
const base64Df =
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAAAXNSR0IArs4c6QAAALZJREFUKFOFkLERwjAQBPdbgBkInECGaMLUQDsE0AkRVRAYWqAByxldPPOWHwnw4OBGye1p50UDSoA+W2ABLPN7i+C5dyC6R/uiAUXRQCs0bXoNIu4QPQzAxDKxHoALOrZcqtiyR/T6CXw7+3IGHhkYcy6BOR2izwT8LptG8rbMiCRAUb+CQ6WzQVb0SNOi5Z2/nX35DRyb/ENazhpWKoGwrpD6nICp5c2qogc4of+c7QcrhgF4Aa/aoAFHiL+RAAAAAElFTkSuQmCC'
@@ -256,12 +291,13 @@
// Create an input field for the image name
const nameInput = document.createElement("input");
nameInput.type = "file";
nameInput.style=`width: 88px;`
nameInput.style = `width: 88px;`
uploadContainer.appendChild(nameInput);
// Create an image element to display the uploaded image
const imageElement = document.createElement("img");
imageElement.src = base64Df
imageElement.style.maxWidth='200px'
nameInput.addEventListener('change', (event) => {
@@ -296,6 +332,14 @@
container.appendChild(uploadContainer);
}
if (data.class_type === 'FloatSlider') {
// 滑块输入
let silde = createFloatSlide(data.title, data.inputs.number, (v) => {
window._appData.data[data.id].inputs.number = v;
})
container.appendChild(silde);
}
// Check if the class_type is "CLIPTextEncode"
if (data.class_type === "CLIPTextEncode") {
// Create a container for the upload control
@@ -322,12 +366,59 @@
// Append the upload container to the main container
container.appendChild(uploadContainer);
}
});
return container
}
function createFloatSlide(labelText, value = 0, callback, minValue = 0, maxValue = 1) {
// 创建滑块输入元素
var slider = document.createElement("input");
slider.type = "range";
slider.min = minValue;
slider.max = maxValue;
slider.step=0.01
slider.value=value;
// 创建标签元素
var label = document.createElement("label");
label.innerHTML = labelText;
// 创建容器元素,并将滑块输入和标签添加到容器中
var container = document.createElement("div");
container.appendChild(label);
container.appendChild(slider);
container.className='card'
// 添加change事件监听器
slider.addEventListener("change", function (event) {
var value = event.target.value;
console.log("滑块输入的值为:" + value);
// 在这里可以执行其他操作,根据需要进行相应的处理
callback && callback(value)
});
// 返回容器元素
return container;
}
function getTypeFromUrl(url) {
const queryString = url.split('?')[1];
if (!queryString) {
return null;
}
const params = new URLSearchParams(queryString);
const type = params.get('type');
return type;
}
@@ -339,9 +430,13 @@
mainDiv.className = 'app'
leftDiv.className = 'panel'
leftDiv.style.alignItems = 'flex-start';
rightDiv.className = 'panel'
leftDiv.style.flex=0.4
rightDiv.style.flex=0.6
leftDiv.style.flex = 0.4
rightDiv.style.flex = 0.6
// rightDiv.style=`position: fixed;
// right: 0;
// top: 12px;flex:0.6`
// 创建标题
var title = document.createElement('h1');
@@ -350,7 +445,7 @@
let iconDes = document.createElement('div');
// 创建应用图标
var icon = document.createElement('img');
icon.style.width = '98px';
icon.style.width = '48px';
// icon.style.height = '98px';
icon.src = base64Df;
@@ -426,27 +521,79 @@
element: output,
update: function (url, id) {
console.log(url, id)
if(output.querySelector(`#output_${id}`)) output.querySelector(`#output_${id}`).src = url
if (output.querySelector(`#output_${id}`)) output.querySelector(`#output_${id}`).src = url
}
},
submitButton: {
element: submitButton,
update: function (callback) {
submitButton.addEventListener('click', 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() {
// 创建一个div元素
var div = document.createElement('div');
div.className = 'upload_btn'
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);
setTimeout(() => {
div.remove();
input.remove();
}, 500);
let { output, app } = jsonData;
window._appData = {
...app,
data: output
};
createApp(window._appData);
// res(jsonData);
};
reader.readAsText(file);
});
// 将div和input元素添加到body中
document.body.appendChild(div);
document.body.appendChild(input);
}
async function init_app() {
const appData = await get_my_app();
// console.log(appData)
window._appData = appData
async function createApp(appData) {
// 使用示例:
var ui = createUI(appData.input, appData.output);
@@ -485,7 +632,6 @@
api.addEventListener("progress", ({ detail }) => {
console.log("progress", detail);
try {
ui.status.update(`${detail.value}/${detail.max}`);
} catch (error) {
@@ -519,6 +665,25 @@
api.api_base = ""
api.init();
}
async function init_app() {
let appData = {}
const type = getTypeFromUrl(location.href);
if (type === 'new') {
createUploadJson();
} else {
appData = await get_my_app();
// console.log(appData)
window._appData = appData;
createApp(appData);
}
};
+93 -27
View File
@@ -30,7 +30,7 @@ function get_position_style (ctx, widget_width, y, node_height) {
display: 'flex',
flexDirection: 'row',
// alignItems: 'center',
justifyContent: 'space-around'
justifyContent: 'flex-start'
}
}
@@ -75,11 +75,13 @@ function extractInputAndOutputData (jsonData, inputIds = [], outputIds = []) {
if (data.hasOwnProperty(id)) {
if (inputIds.includes(id)) {
let node = app.graph.getNodeById(id)
input.push({ ...data[id], title: node.title,id })
input[inputIds.indexOf(id)] = { ...data[id], title: node.title, id }
// input.push()
}
if (outputIds.includes(id)) {
let node = app.graph.getNodeById(id)
output.push({ ...data[id], title: node.title,id })
// output.push()
output[outputIds.indexOf(id)] = { ...data[id], title: node.title, id }
}
}
}
@@ -87,10 +89,15 @@ function extractInputAndOutputData (jsonData, inputIds = [], outputIds = []) {
return { input, output }
}
async function save_app (json) {
function getUrl () {
let api_host = `${window.location.hostname}:${window.location.port}`
let api_base = ''
let url = `${window.location.protocol}//${api_host}${api_base}`
return url
}
async function save_app (json) {
let url = getUrl()
const res = await fetch(`${url}/mixlab/workflow`, {
method: 'POST',
@@ -102,12 +109,28 @@ async function save_app (json) {
return await res.json()
}
async function save (json) {
function downloadJsonFile (jsonData, fileName = 'mix_app.json') {
const dataString = JSON.stringify(jsonData)
const blob = new Blob([dataString], { type: 'application/json' })
const url = URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = fileName
link.click()
// 释放URL对象
setTimeout(() => {
URL.revokeObjectURL(url)
}, 0)
}
async function save (json, download = false) {
const name = json[0],
version = json[5],
description = json[4],
inputIds = json[2].split('\n'),
outputIds = json[3].split('\n')
inputIds = json[2].split('\n').filter(f => f),
outputIds = json[3].split('\n').filter(f => f)
const iconData = json[1][0]
let { filename, subfolder, type } = iconData
@@ -137,10 +160,26 @@ async function save (json) {
try {
data.app.icon = await drawImageToCanvas(iconUrl)
} catch (error) {}
console.log(data.app)
// console.log(data.app)
// let http_workflow = app.graph.serialize()
await save_app(data)
window.alert('You can now access the standalone application on a new page!')
if (download) {
await downloadJsonFile(
data,
`${data.app.name}_${data.app.version}_${new Date().toDateString()}.json`
)
let open = window.confirm(
`You can now access the standalone application on a new page!\n${getUrl()}/mixlab/app?type=new`
)
if (open) window.open(`${getUrl()}/mixlab/app?type=new`)
} else {
await save_app(data)
let open = window.confirm(
`You can now access the standalone application on a new page!\n${getUrl()}/mixlab/app`
)
if (open) window.open(`${getUrl()}/mixlab/app`)
}
} catch (error) {
console.log('###SpeechRecognition', error)
}
@@ -153,13 +192,13 @@ app.registerExtension({
const orig_nodeCreated = nodeType.prototype.onNodeCreated
nodeType.prototype.onNodeCreated = function () {
orig_nodeCreated?.apply(this, arguments)
console.log(this)
// console.log(this)
const widget = {
type: 'div',
name: 'AppInfoRun',
draw (ctx, node, widget_width, y, widget_height) {
Object.assign(
this.button.style,
this.div.style,
get_position_style(
ctx,
widget_width,
@@ -170,17 +209,21 @@ app.registerExtension({
}
}
widget.button = $el('button', {})
widget.button.innerText = 'Save For App ♾️Mixlab'
widget.button.style = `
flex-direction: row;
background-color: var(--comfy-input-bg);
border-radius: 8px;
border-color: var(--border-color);
border-style: solid;
color: var(--descrip-text);
`
widget.button.addEventListener('click', () => {
const style = `
flex-direction: row;
background-color: var(--comfy-input-bg);
border-radius: 8px;
border-color: var(--border-color);
border-style: solid;
color: var(--descrip-text);`
widget.div = $el('div', {})
const btn = document.createElement('button')
btn.innerText = 'Save For App'
btn.style = style
btn.addEventListener('click', () => {
// console.log('hahhah')
if (window._mixlab_app_json) {
save(window._mixlab_app_json)
@@ -190,13 +233,30 @@ app.registerExtension({
}
})
document.body.appendChild(widget.button)
const download = document.createElement('button')
download.innerText = 'Download For App'
download.style = style
download.style.marginLeft = '12px'
download.addEventListener('click', () => {
// console.log('hahhah')
if (window._mixlab_app_json) {
save(window._mixlab_app_json, true)
} else {
alert('Please run the workflow before saving')
// app.queuePrompt(0, 1)
}
})
document.body.appendChild(widget.div)
widget.div.appendChild(btn)
widget.div.appendChild(download)
this.addCustomWidget(widget)
const onRemoved = this.onRemoved
this.onRemoved = () => {
widget.button.remove()
widget.div.remove()
return onRemoved?.()
}
@@ -207,9 +267,15 @@ app.registerExtension({
nodeType.prototype.onExecuted = async function (message) {
onExecuted?.apply(this, arguments)
// console.log(this.widgets)
const button = this.widgets.filter(w => w.button)[0].button
window._mixlab_app_json = message.json
button.style.outline = '1px solid red'
try {
const div = this.widgets.filter(w => w.div)[0].div
Array.from(
div.querySelectorAll('button'),
b => (b.style.background = 'yellow')
)
} catch (error) {}
}
}
}
+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.5.2'
const version = 'v0.6.0'
fetch(`https://api.github.com/repos/${repoOwner}/${repoName}/releases/latest`)
.then(response => response.json())
+4 -3
View File
@@ -675,7 +675,7 @@ app.registerExtension({
const options = orig.apply(this, arguments)
options.push(null, {
content: `Find ♾️Mixlab`,
content: `Nodes Map ♾️Mixlab`,
disabled: false, // or a function determining whether to disable
callback: async () => {
nodesMap =
@@ -709,12 +709,13 @@ app.registerExtension({
justify-content: space-between;
align-items: center;
padding: 0 12px;
height: 32px;`
height: 44px;`
let btnB = document.createElement('button')
let textB = document.createElement('p')
btn.appendChild(textB)
btn.appendChild(btnB)
textB.innerText = `Find The Node`
textB.style.fontSize='12px';
textB.innerText = `Locate and navigate nodes ♾️Mixlab`
btnB.style = `float: right; border: none; color: var(--input-text);
background-color: var(--comfy-input-bg); border-color: var(--border-color);cursor: pointer;`
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 2.6 MiB

-1
View File
@@ -1 +0,0 @@
~