Compare commits

...
34 Commits
Author SHA1 Message Date
shadowcz007 686ebcfd8b v0.17.1 2024-03-09 13:57:29 +08:00
shadowcz007 51aaba39cf Merge branch 'main' of https://github.com/shadowcz007/comfyui-mixlab-nodes 2024-03-09 00:16:10 +08:00
shadowcz007 d7c6632499 Update index.html 2024-03-09 00:16:07 +08:00
shadow 1b0ea06876 Merge pull request #188 from cd0304/main
add chatglm4 model
2024-03-09 00:07:52 +08:00
cd0304 8ebe88629b Update ChatGPT.py 2024-03-07 23:22:22 +08:00
cd0304 e226992703 Update requirements.txt 2024-03-07 23:20:39 +08:00
shadowcz007 11a8394d69 Update index.html 2024-03-05 00:04:27 +08:00
shadowcz007 9f54a1b91a Update index.html 2024-03-04 20:47:17 +08:00
shadow 35d11061e9 Merge pull request #185 from shadowcz007/0.18-story
update  web/index.html
2024-03-04 20:32:13 +08:00
shadowcz007 65d8b490ca ing 2024-03-04 20:31:45 +08:00
shadowcz007 0fef12c3b1 Update index.html 2024-03-03 11:58:47 +08:00
shadowcz007 8516bff224 Update index.html 2024-03-03 11:11:39 +08:00
shadowcz007 1bcc501352 Update index.html 2024-03-02 23:55:46 +08:00
shadowcz007 f492b17fbe Update index.html 2024-03-02 23:16:08 +08:00
shadowcz007 48ae90f80e 增加说明 2024-02-28 20:07:31 +08:00
shadowcz007 9321ccbc48 test 2024-02-27 21:53:10 +08:00
shadowcz007 402cd01e1a Update README.md 2024-02-27 15:31:43 +08:00
shadowcz007 7b2d0e29c6 Update PromptNode.py 2024-02-27 15:18:58 +08:00
shadowcz007 52d38c401a Update PromptNode.py 2024-02-27 14:51:52 +08:00
shadowcz007 a34dd61076 fixbug 2024-02-24 14:54:40 +08:00
shadowcz007 a53a3e772a Update README.md 2024-02-24 10:40:11 +08:00
shadowcz007 8f24c294a7 update 2024-02-23 18:47:47 +08:00
shadowcz007 acc3f76654 ing 2024-02-18 20:45:58 +08:00
shadowcz007 8c977fb442 ing 2024-02-18 17:33:11 +08:00
shadowcz007 aa20a2de67 fixbug 2024-02-13 15:55:50 +08:00
shadowcz007 5fcb154d89 Update ui_mixlab.js 2024-02-13 15:48:22 +08:00
shadowcz007 0980129f4e Update ui_mixlab.js 2024-02-13 15:45:56 +08:00
shadowcz007 1258746886 v0.17.0
- app模式支持VHS_LoadVideo节点作为输入
- 动态提示,鼠标悬浮可显示结果
2024-02-13 15:18:48 +08:00
shadowcz007 ed128b0ad6 app 支持VHS_LoadVideo 节点作为输入 2024-02-13 15:09:34 +08:00
shadowcz007 f0db08acd6 add TESTNODE_TOKEN
显示text-to-token的过程,方便对prompt进行精修
2024-02-12 21:32:37 +08:00
shadowcz007 7c655e3080 Update ui_mixlab.js 2024-02-12 19:40:09 +08:00
shadowcz007 1b9871c3df Update ui_mixlab.js 2024-02-12 19:20:34 +08:00
shadowcz007 7568aaf243 Update ui_mixlab.js 2024-02-12 17:42:11 +08:00
shadowcz007 a76be8450d mouseover show dynamic_prompt's result 2024-02-12 17:11:03 +08:00
15 changed files with 637 additions and 112 deletions
+19 -3
View File
@@ -1,6 +1,6 @@
> 适配了最新版comfyui的py3.11 ,torch 2.1.2+cu121
> [discord](https://discord.gg/cXs9vZSqeK)
> [Mixlab nodes discord](https://discord.gg/cXs9vZSqeK)
####
[comfyui-ultralytics-yolo](https://github.com/shadowcz007/comfyui-ultralytics-yolo)
@@ -39,12 +39,19 @@ APP-JSON:
- [image-to-image](./example/Image-to-Image_2.json)
- text-to-text
> 暂时支持8种节点作为界面上的输入节点:Load Image、CLIPTextEncode、PromptSlide、TextInput_、Color、FloatSlider、IntNumber、CheckpointLoaderSimple、LoraLoader
> 暂时支持 9 种节点作为界面上的输入节点:Load Image、VHS_LoadVideo、CLIPTextEncode、PromptSlide、TextInput_、Color、FloatSlider、IntNumber、CheckpointLoaderSimple、LoraLoader
> 输出节点:PreviewImage 、SaveImage、ShowTextForGPT、VHS_VideoCombine、PromptImage
> seed统一输入控件,支持:SamplerCustom、KSampler
> 配套[ps插件](https://github.com/shadowcz007/comfyui-ps-plugin)
> 如果遇到上传图片不成功,请检查下:局域网或者是云服务,请使用https,端口8189这个服务( 感谢 @Damien 反馈问题)
> If you encounter difficulties in uploading images, please check the following: for local network or cloud services, please use HTTPS and the service on port 8189. (Thanks to @Damien for reporting the issue.)
## 🏃🚗🚚🚀 Real-time Design
> ScreenShareNode & FloatingVideoNode. Now comfyui supports capturing screen pixel streams from any software and can be used for LCM-Lora integration. Let's get started with implementation and design! 💻🌐
@@ -66,7 +73,7 @@ https://github.com/shadowcz007/comfyui-mixlab-nodes/assets/12645064/e7e77f90-e43
[Voice + Real-time Face Swap Workflow](./workflow/语音+实时换脸workflow.json)
### GPT
> Support for calling multiple GPTs.ChatGPT、ChatGLM3 , Some code provided by rui. If you are using OpenAI's service, fill in https://api.openai.com/v1 . If you are using a local LLM service, fill in http://127.0.0.1:xxxx/v1 . Azure OpenAI:https://xxxx.openai.azure.com
> Support for calling multiple GPTs.ChatGPT、ChatGLM3 、ChatGLM4 , Some code provided by rui. If you are using OpenAI's service, fill in https://api.openai.com/v1 . If you are using a local LLM service, fill in http://127.0.0.1:xxxx/v1 . Azure OpenAI:https://xxxx.openai.azure.com
![gpt-workflow.svg](./assets/gpt-workflow.svg)
@@ -161,6 +168,7 @@ from [simple-lama-inpainting](https://github.com/enesmsahin/simple-lama-inpainti
"briarmbg","u2net","u2netp","u2net_human_seg","u2net_cloth_seg","silueta","isnet-general-use","isnet-anime"
*** briarmbg *** model was developed by BRlA Al and can be used as an open-source model for non-commercial purposes
### Improvement
@@ -224,6 +232,14 @@ pip3 install -r requirements.txt
访问 [www.mixcomfy.com](https://www.mixcomfy.com),获得更多内测功能,关注微信公众号:Mixlab无界社区
####
File / LoadImagesFromPath SaveImageToLocal LoadImagesFromURL
#### discussions:
[discussions](https://github.com/shadowcz007/comfyui-mixlab-nodes/discussions)
+5 -2
View File
@@ -535,9 +535,9 @@ from .nodes.ImageNode import SaveImageToLocal,SplitImage,GridOutput,GetImageSize
# from .nodes.Vae import VAELoader,VAEDecode
from .nodes.ScreenShareNode import ScreenShareNode,FloatingVideo
from .nodes.ChatGPT import ChatGPTNode,ShowTextForGPT,CharacterInText
from .nodes.ChatGPT import ChatGPTNode,ShowTextForGPT,CharacterInText,TextSplitByDelimiter
from .nodes.Audio import GamePal,SpeechRecognition,SpeechSynthesis
from .nodes.Utils import CreateLoraNames,CreateSampler_names,CreateCkptNames,CreateSeedNode,TESTNODE_,AppInfo,IntNumber,FloatSlider,TextInput,ColorInput,FontInput,TextToNumber,DynamicDelayProcessor,LimitNumber,SwitchByIndex,MultiplicationNode
from .nodes.Utils import CreateLoraNames,CreateSampler_names,CreateCkptNames,CreateSeedNode,TESTNODE_,TESTNODE_TOKEN,AppInfo,IntNumber,FloatSlider,TextInput,ColorInput,FontInput,TextToNumber,DynamicDelayProcessor,LimitNumber,SwitchByIndex,MultiplicationNode
from .nodes.Mask import OutlineMask,FeatheredMask
@@ -546,7 +546,9 @@ from .nodes.Mask import OutlineMask,FeatheredMask
NODE_CLASS_MAPPINGS = {
"AppInfo":AppInfo,
"TESTNODE_":TESTNODE_,
"TESTNODE_TOKEN":TESTNODE_TOKEN,
"RandomPrompt":RandomPrompt,
# "LoraPrompt":LoraPrompt,
"EmbeddingPrompt":EmbeddingPrompt,
"PromptSlide":PromptSlide,
"PromptSimplification":PromptSimplification,
@@ -583,6 +585,7 @@ NODE_CLASS_MAPPINGS = {
"ChatGPTOpenAI":ChatGPTNode,
"ShowTextForGPT":ShowTextForGPT,
"CharacterInText":CharacterInText,
"TextSplitByDelimiter":TextSplitByDelimiter,
"SpeechRecognition":SpeechRecognition,
"SpeechSynthesis":SpeechSynthesis,
"Color":ColorInput,
+1
View File
@@ -0,0 +1 @@
{}
+63 -4
View File
@@ -4,7 +4,7 @@ import urllib.error
import re,json,os,string,random
import folder_paths
import hashlib
from zhipuai import ZhipuAI
def get_unique_hash(string):
hash_object = hashlib.sha1(string.encode())
unique_hash = hash_object.hexdigest()
@@ -46,6 +46,11 @@ def openai_client(key,url):
base_url=url
)
return client
def ZhipuAI_client(key):
client = ZhipuAI(
api_key=key, # 填写您的 APIKey
)
return client
@@ -100,7 +105,7 @@ class ChatGPTNode:
"default": "You are ChatGPT, a large language model trained by OpenAI. Answer as concisely as possible.",
"multiline": True,"dynamicPrompts": False
}),
"model": (["gpt-3.5-turbo","gpt-35-turbo","gpt-3.5-turbo-16k", "gpt-3.5-turbo-16k-0613", "gpt-4-0613","gpt-4-1106-preview"],
"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","glm-4"],
{"default": "gpt-3.5-turbo"}),
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff, "step": 1}),
"context_size":("INT", {"default": 1, "min": 0, "max":30, "step": 1}),
@@ -143,8 +148,13 @@ class ChatGPTNode:
if is_azure_url(api_url):
client=azure_client(api_key,api_url)
else:
client=openai_client(api_key,api_url)
print('openai url')
# 根据用户选择的模型,设置相应的接口和模型名称
if model == "glm-4" :
client = ZhipuAI_client(api_key) # 使用 Zhipuai 的接口
print('using Zhipuai interface')
else :
client = openai_client(api_key,api_url) # 使用 ChatGPT 的接口
print('using ChatGPT interface')
# 把用户的提示添加到会话历史中
# 调用API时传递整个会话历史
@@ -291,3 +301,52 @@ class CharacterInText:
return (b+start_index,)
class TextSplitByDelimiter:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"text": ("STRING", {"multiline": True,"dynamicPrompts": False}),
"delimiter":(["newline","comma"],),
"start_index": ("INT", {
"default": 0,
"min": 0, #Minimum value
"max": 1000, #Maximum value
"step": 1, #Slider's step
"display": "number" # Cosmetic only: display as "number" or "slider"
}),
"skip_every": ("INT", {
"default": 0,
"min": 0, #Minimum value
"max": 10, #Maximum value
"step": 1, #Slider's step
"display": "number" # Cosmetic only: display as "number" or "slider"
}),
"max_count": ("INT", {
"default": 10,
"min": 1, #Minimum value
"max": 1000, #Maximum value
"step": 1, #Slider's step
"display": "number" # Cosmetic only: display as "number" or "slider"
}),
}
}
INPUT_IS_LIST = False
RETURN_TYPES = ("STRING",)
FUNCTION = "run"
# OUTPUT_NODE = True
OUTPUT_IS_LIST = (True,)
CATEGORY = "♾️Mixlab/GPT"
def run(self, text,delimiter,start_index,skip_every,max_count):
arr=[]
if delimiter=='newline':
arr = [line for line in text.split('\n') if line.strip()]
elif delimiter=='comma':
arr = [line for line in text.split(',') if line.strip()]
arr= arr[start_index:start_index + max_count * (skip_every+1):(skip_every+1)]
return (arr,)
+1 -1
View File
@@ -2408,7 +2408,7 @@ class SaveImageToLocal:
full_output_folder, filename, counter, subfolder, filename_prefix = folder_paths.get_save_image_path(filename_prefix, self.output_dir, images[0].shape[1], images[0].shape[0])
if not os.path.exists(file_path) and not extension:
if not os.path.exists(file_path):
# 使用os.makedirs函数创建新目录
os.makedirs(file_path)
print("目录已创建")
+130 -1
View File
@@ -6,6 +6,12 @@ from urllib import request, parse
import folder_paths
from PIL import Image, ImageOps,ImageFilter,ImageEnhance,ImageDraw,ImageSequence, ImageFont
from PIL.PngImagePlugin import PngInfo
import hashlib
import requests
import json
# def queue_prompt(prompt_workflow):
# p = {"prompt": prompt_workflow}
# data = json.dumps(p).encode('utf-8')
@@ -28,6 +34,50 @@ def join_with_(text_list,delimiter):
return joined_text
def load_json(file_path):
try:
with open(file_path, 'r') as json_file:
data = json.load(json_file)
return data
except FileNotFoundError:
print(f"File not found: {file_path}")
return None
except json.JSONDecodeError:
print(f"Error decoding JSON in file: {file_path}")
return None
def save_json(data_dict, file_path):
try:
with open(file_path, 'w') as json_file:
json.dump(data_dict, json_file, indent=4)
print(f"Data saved to {file_path}")
except Exception as e:
print(f"Error saving JSON to file: {e}")
# pysss的lora加载器
# def get_model_version_info(hash_value):
# # http://127.0.0.1:1082
# proxies = {'http': 'http://127.0.0.1:1082', 'https': 'https://127.0.0.1:1082'}
# api_url = f"https://civitai.com/api/v1/model-versions/by-hash/{hash_value}"
# print(api_url)
# response = requests.get(api_url,proxies=proxies, verify=False)
# if response.status_code == 200:
# return response.json()
# else:
# return None
# def calculate_sha256(file_path):
# sha256_hash = hashlib.sha256()
# with open(file_path, "rb") as f:
# for chunk in iter(lambda: f.read(4096), b""):
# sha256_hash.update(chunk)
# return sha256_hash.hexdigest()
class AnyType(str):
"""A special class that is always equal in not equal comparisons. Credit to pythongosssss"""
@@ -385,7 +435,82 @@ class RandomPrompt:
return {"ui": {"prompts": prompts}, "result": (prompts,)}
# class LoraPrompt:
# @classmethod
# def INPUT_TYPES(s):
# return {
# "required": {
# "lora_name":(sorted(folder_paths.get_filename_list("loras"), key=str.lower),),
# "weight": ("FLOAT", {"default": 1, "min": -2, "max": 2,"step":0.01 ,"display": "slider"}),
# "force_update": ("BOOLEAN", {"default": False}),
# },
# }
# RETURN_TYPES = ("STRING","STRING",any_type)
# RETURN_NAMES = ("lora_name","prompt","tags",)
# FUNCTION = "run"
# CATEGORY = "♾️Mixlab/Prompt"
# OUTPUT_IS_LIST = (False,False,True,)
# # OUTPUT_NODE = True
# # 运行的函数
# def run(self,lora_name,weight,force_update=False):
# # print('##LoraPrompt',__file__)
# # 从本地数据库读取
# json_tags_path = os.path.join(os.path.dirname(os.path.dirname(__file__)),r'data/loras_tags.json')
# if not os.path.exists(json_tags_path):
# save_json({},json_tags_path)
# lora_tags = load_json(json_tags_path)
# output_tags = lora_tags.get(lora_name, None) if lora_tags is not None else None
# if output_tags is not None:
# output_tags = ",".join(output_tags)
# print("trainedWords:",output_tags)
# else:
# output_tags = ""
# lora_path = folder_paths.get_full_path("loras", lora_name)
# if output_tags == "" or force_update:
# print("calculating lora hash")
# LORAsha256 = calculate_sha256(lora_path)
# print("requesting infos")
# model_info = get_model_version_info(LORAsha256)
# if model_info is not None:
# if "trainedWords" in model_info:
# print("tags found!")
# if lora_tags is None:
# lora_tags = {}
# lora_tags[lora_name] = model_info["trainedWords"]
# save_json(lora_tags,json_tags_path)
# output_tags = ",".join(model_info["trainedWords"])
# print("trainedWords:",output_tags)
# else:
# print("No informations found.")
# if lora_tags is None:
# lora_tags = {}
# lora_tags[lora_name] = []
# save_json(lora_tags,json_tags_path)
# weight = round(weight, 3)
# prompt=[]
# for p in output_tags.split(','):
# if weight!=1:
# prompt.append('('+p+':'+str(weight)+')')
# else:
# prompt.append(p)
# prompt=",".join(prompt)
# return (lora_name,prompt,output_tags.split(','),)
@@ -426,7 +551,7 @@ class JoinWithDelimiter:
def INPUT_TYPES(s):
return {"required": {
"text_list": (any_type,),
"delimiter":(["newline","comma"],),
"delimiter":(["newline","comma","backslash","space"],),
},
}
@@ -445,6 +570,10 @@ class JoinWithDelimiter:
delimiter='\n'
elif delimiter=='comma':
delimiter=','
elif delimiter=='backslash':
delimiter='\\'
elif delimiter=='space':
delimiter=' '
t=''
if isinstance(text_list, list):
t=join_with_(text_list,delimiter)
+65 -15
View File
@@ -6,7 +6,7 @@ import numpy as np
import folder_paths
import matplotlib.font_manager as fm
import torch
import importlib.util
def recursive_search(directory, excluded_dir_names=None):
@@ -347,7 +347,7 @@ class MultiplicationNode:
return {"required": {
"numberA":(any_type,),
"multiply_by":("FLOAT", {
"default": 0,
"default": 1,
"min": -2, #Minimum value
"max": 0xffffffffffffffff,
"step": 0.01, #Slider's step
@@ -381,7 +381,7 @@ class TextInput:
@classmethod
def INPUT_TYPES(s):
return {"required": {
"text": ("STRING",{"multiline": True,"default": ""}),
"text": ("STRING",{"multiline": True,"default": ""})
},
}
@@ -560,9 +560,11 @@ class SwitchByIndex:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"A":(any_type,),
"B":(any_type,),
"optional":{
"A":(any_type,),
"B":(any_type,),
},
"required": {
"index":("INT", {
"default": -1,
"min": -1,
@@ -574,17 +576,17 @@ class SwitchByIndex:
}
}
RETURN_TYPES = (any_type,)
RETURN_NAMES = ("C",)
RETURN_TYPES = (any_type,"INT",)
RETURN_NAMES = ("C","count",)
FUNCTION = "run"
CATEGORY = "♾️Mixlab/Utils"
INPUT_IS_LIST = True
OUTPUT_IS_LIST = (True,)
OUTPUT_IS_LIST = (True,False,)
def run(self, A,B,index,flat):
def run(self, A=[],B=[],index=-1,flat='on'):
flat=flat[0]
@@ -604,8 +606,8 @@ class SwitchByIndex:
C=[C[index]]
except Exception as e:
C=[]
return (C,)
return (C,len(C),)
@@ -695,7 +697,9 @@ class ListStatistics:
class TESTNODE_:
@classmethod
def INPUT_TYPES(s):
return {"required": { "ANY":(any_type,), },
return {"required": {
"ANY":(any_type,),
},
}
RETURN_TYPES = (any_type,)
@@ -709,16 +713,62 @@ class TESTNODE_:
OUTPUT_IS_LIST = (True,)
def run(self,ANY):
print(ANY)
# print(ANY)
# data=ANY
list_stats = ListStatistics()
# 调用count_types方法进行统计
result = list_stats.count_types(ANY)
# 假设我们有一个模块文件名为 my_module.py,它位于 'importables' 目录下
module_path = os.path.join(os.path.dirname(__file__),'test.py')
# 使用 spec_from_file_location 获取模块的元数据(名称、定义等)
spec = importlib.util.spec_from_file_location('test', module_path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
functions = getattr(module, 'run') # 获取函数
functions(ANY)
return {"ui": {"data": result,"type":[str(type(ANY[0]))]}, "result": (ANY,)}
class TESTNODE_TOKEN:
@classmethod
def INPUT_TYPES(s):
return {"required": {
"text":("STRING", {"forceInput": True,}),
"clip": ("CLIP", )
},
}
RETURN_TYPES = ("STRING",)
FUNCTION = "run"
CATEGORY = "♾️Mixlab/__TEST"
OUTPUT_NODE = True
INPUT_IS_LIST = False
OUTPUT_IS_LIST = (False,)
def run(self,text,clip=None):
# print(text)
tokens = clip.tokenize(text)
tokens=[v for v in tokens.values()][0][0]
tokens=json.dumps(tokens)
return (tokens,)
class CreateSeedNode:
def __init__(self):
+8
View File
@@ -0,0 +1,8 @@
import folder_paths
# 外挂一个文件,用来编写新的节点
def run(v):
output_dir = folder_paths.get_temp_directory()
print('1323',v,output_dir)
+2 -1
View File
@@ -6,4 +6,5 @@ matplotlib
openai
simple-lama-inpainting
clip-interrogator==0.6.0
transformers>=4.36.0
transformers>=4.36.0
zhipuai
+178 -31
View File
@@ -11,6 +11,12 @@
padding: 0;
}
.header {
display: flex;
align-items: center;
justify-content: space-around;
}
.app {
display: flex;
width: 90%;
@@ -142,6 +148,10 @@
word-wrap: break-word;
}
.description img {
min-height: unset !important;
}
.panel {
display: flex;
flex-direction: column;
@@ -172,6 +182,7 @@
width: fit-content;
max-width: 100%;
margin-left: 12px;
min-height: 200px;
}
.input_card {
@@ -378,9 +389,7 @@
<body>
<div id="editor_container"></div>
<div style="display: flex;
align-items: center;
justify-content: space-around;">
<div class="header">
<div style="margin: 0 24px;
margin-bottom: 24px;
padding: 8px;
@@ -435,8 +444,9 @@
body
})
// console.log(resp)
let data = await resp.json()
// console.log(data)
let { name, subfolder } = data
let src = `${url}/view?filename=${encodeURIComponent(
name
@@ -888,6 +898,49 @@
}
function generateRainbowVideo() {
// 创建一个canvas元素
const canvas = document.createElement('canvas');
canvas.width = 640; // 设置canvas宽度
canvas.height = 480; // 设置canvas高度
const context = canvas.getContext('2d');
// 绘制第一帧彩虹
context.fillStyle = 'red';
context.fillRect(0, 0, canvas.width / 2, canvas.height);
context.fillStyle = 'orange';
context.fillRect(canvas.width / 2, 0, canvas.width / 2, canvas.height);
// 绘制第二帧彩虹
context.fillStyle = 'yellow';
context.fillRect(0, 0, canvas.width / 2, canvas.height);
context.fillStyle = 'green';
context.fillRect(canvas.width / 2, 0, canvas.width / 2, canvas.height);
const stream = canvas.captureStream();
return new Promise((res, rej) => {
// 导出视频
const mediaRecorder = new MediaRecorder(stream);
const chunks = [];
mediaRecorder.ondataavailable = function (event) {
chunks.push(event.data);
};
mediaRecorder.onstop = function () {
const blob = new Blob(chunks, { type: 'video/mp4' });
const url = URL.createObjectURL(blob);
res(url)
};
mediaRecorder.start();
setTimeout(function () {
mediaRecorder.stop();
}, 1000); // 设置录制时长,这里设置为1秒
})
}
async function calculateImageHash(blob) {
const buffer = await blob.arrayBuffer();
const hashBuffer = await crypto.subtle.digest('SHA-256', buffer);
@@ -1081,20 +1134,25 @@
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") {
console.log(data)
// 图片 or 视频输入
if (data.class_type === "LoadImage" || data.class_type === "VHS_LoadVideo") {
let isVideoUpload = data.class_type === "VHS_LoadVideo";
// 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.textContent = data.title || (isVideoUpload ? "LoadVideo: " : "LoadImage: ");
nameLabel.style.marginBottom = '12px'
uploadContainer.appendChild(nameLabel);
let actionDiv = document.createElement('div');
actionDiv.style = `padding: 0 8px;`
// Create an input field for the image name
const uploadImageInput = document.createElement("button");
@@ -1109,25 +1167,55 @@
const btnFromClipboard = document.createElement("button");
btnFromClipboard.style = `width: 156px; margin-left: 18px;`
btnFromClipboard.innerText = 'paste from clipboard'
actionDiv.appendChild(btnFromClipboard);
if (!isVideoUpload) actionDiv.appendChild(btnFromClipboard);
const btnForImageEdit = document.createElement("button");
btnForImageEdit.style = ` width: 32px; background: none;margin-left: 18px;`
btnForImageEdit.innerHTML = '<?xml version="1.0" ?><svg version="1.1" style="width: 24px;" viewBox="0 0 50 50" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g id="Layer_1_1_"><path d="M18.293,31.707h6.414l24-24l-6.414-6.414l-24,24V31.707z M45.879,7.707l-3.586,3.586l-3.586-3.586l3.586-3.586 L45.879,7.707z M20.293,26.121l17-17l3.586,3.586l-17,17h-3.586V26.121z"/><polygon points="43.293,19.707 41.293,19.707 41.293,46.707 3.293,46.707 3.293,8.707 31.293,8.707 31.293,6.707 1.293,6.707 1.293,48.707 43.293,48.707 "/></g></svg>'
actionDiv.appendChild(btnForImageEdit);
if (!isVideoUpload) actionDiv.appendChild(btnForImageEdit);
uploadContainer.appendChild(actionDiv)
// Create an image element to display the uploaded image
const imageElement = document.createElement("img");
imageElement.src = base64Df
let imageElement = document.createElement("img");
if (isVideoUpload) {
// 视频
imageElement = document.createElement('video');
imageElement.setAttribute('controls', true)
let [subfolder, name] = data.inputs.video.split('/');
// console.log(subfolder,name)
if (!name) {
subfolder = "";
name = data.inputs.video;
}
let url = `${get_url()}/view?filename=${encodeURIComponent(name)}&type=input&subfolder=${subfolder}&rand=${Math.random()}`
imageElement.src = url;
// imageElement.innerHTML=`<img src="${base64Df}"/>`
} else {
// 图片
let [subfolder, name] = data.inputs.image.split('/');
if (!name) {
subfolder = "";
name = data.inputs.image;
}
// imageElement.src = base64Df
let url = `${get_url()}/view?filename=${encodeURIComponent(name)}&type=input&subfolder=${subfolder}&rand=${Math.random()}`
imageElement.src = url;
imageElement.setAttribute('onerror',`this.src='${base64Df}'`)
}
imageElement.style.maxWidth = '200px';
btnFromClipboard.addEventListener('click', (event) => handleClipboardImage(imageElement, data));
btnForImageEdit.addEventListener('click', e => editImage(imageElement, data))
if (!isVideoUpload) btnFromClipboard.addEventListener('click', (event) => handleClipboardImage(imageElement, data));
if (!isVideoUpload) btnForImageEdit.addEventListener('click', e => editImage(imageElement, data))
uploadImageInput.addEventListener('click', (event) => {
@@ -1145,15 +1233,24 @@
reader.onloadend = async function () {
// 获取读取的文件内容,即 Blob 对象
const fileBlob = new Blob([reader.result], { type: file.type });
// console.log( file.type.split('/')[1])
let hashId = await calculateImageHash(fileBlob)
if (hashId == window._appData.data[data.id].hashId) return
let { url, name } = await uploadImage(fileBlob)
let { url, name } = await uploadImage(fileBlob, '.' + file.type.split('/')[1])
if (isVideoUpload) {
imageElement.srcObject = null;
}
// 在这里可以对 Blob 对象进行进一步处理
imageElement.src = url;
window._appData.data[data.id].inputs.image = name;
if (isVideoUpload) {
window._appData.data[data.id].inputs.video = name;
} else {
window._appData.data[data.id].inputs.image = name;
}
window._appData.data[data.id].hashId = hashId;
console.log("上传的文件:", url, data.id, name);
@@ -1172,6 +1269,7 @@
container.appendChild(uploadContainer);
}
// 滑块输入
if (["PromptSlide"].includes(data.class_type)) {
// 滑块输入
let options = data.options || {
@@ -1201,6 +1299,7 @@
}
// 数字输入支持
if (['FloatSlider', 'IntNumber'].includes(data.class_type)) {
// console.log('data.options',data.options)
// 滑块输入
@@ -1223,8 +1322,8 @@
container.appendChild(silde);
}
// Check if the class_type is "CLIPTextEncode"
if (["TextInput_", "CLIPTextEncode", "PromptSimplification"].includes(data.class_type)) {
// 文本输入支持
if (["TextInput_", "CLIPTextEncode", "PromptSimplification","ChinesePrompt_Mix"].includes(data.class_type)) {
// Create a container for the upload control
const uploadContainer = document.createElement("div");
uploadContainer.className = 'card';
@@ -1289,7 +1388,7 @@
container.appendChild(uploadContainer);
}
// lora的输入支持
if (["CheckpointLoaderSimple", "LoraLoader"].includes(data.class_type)) {
let value = data.inputs.ckpt_name || data.inputs.lora_name;
@@ -1333,6 +1432,7 @@
container.appendChild(div);
}
// 色彩选择器
if (["Color"].includes(data.class_type)) {
let value = data.inputs.color.hex || '#000000';
let d = document.createElement('div');
@@ -2015,9 +2115,10 @@
};
api.addEventListener("status", ({ detail }) => {
console.log("status", detail, detail.exec_info?.queue_remaining);
console.log("status", detail, detail?.exec_info?.queue_remaining);
try {
ui.status.update(`queue#${detail.exec_info?.queue_remaining}`);
window.parent.postMessage({ cmd: 'status', data: `queue#${detail.exec_info?.queue_remaining}` }, '*');
if (detail.exec_info?.queue_remaining === 0) {
// 运行按钮重设
ui.submitButton.reset()
@@ -2025,6 +2126,7 @@
}
} catch (error) {
console.log(error)
window.parent.postMessage({ cmd: 'status' }, '*');
}
});
@@ -2124,6 +2226,7 @@
api.addEventListener("execution_error", ({ detail }) => {
console.log("execution_error", detail)
window.parent.postMessage({ cmd: 'status', data: `execution_error:${JSON.stringify(detail)}` }, '*');
// show(URL.createObjectURL(detail));
});
@@ -2153,7 +2256,6 @@
api.init();
// 外挂的UI
createAllColorInput();
@@ -2204,7 +2306,7 @@
}
// 创建app的选择菜单
function createAppList(apps = []) {
function createAppList(apps = [], innerApp = false) {
let details = document.createElement('details');
details.className = 'apps';
@@ -2250,27 +2352,72 @@
// console.log(div)
};
let uploadApp = createUploadJson(details);
div.appendChild(uploadApp);
if (!innerApp) {
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);
const innerApp = checkIsInnerApp();
window._appData = window._apps[0];
if (!innerApp) {
const { category, filename } = getFilenameAndCategoryFromUrl(location.href);
window._apps = await get_my_app(category, filename);
createAppList(window._apps);
window._appData = window._apps[0];
createApp(window._appData);
createAppList(window._apps);
createApp(window._appData);
}
};
init_app();
// 支持内嵌app
function checkIsInnerApp() {
const url = new URL(window.location.href);
const params = new URLSearchParams(url.search);
const innerApp = params.get("innerApp");
// console.log(window.location.href, innerApp == 1, document.body);
if (innerApp == 1) {
document.body.querySelector('.header').style.display = 'none';
window.parent.postMessage({ innerApp, cmd: 'init' }, '*');
// 在iframe中监听来自父窗口的消息
window.addEventListener("message", function (event) {
console.log("Received message from parent:", event.data);
const { init, url } = event.data;
window._hostUrl = url;
window._apps = init;
window._appData = window._apps[0];
if (window._appData) {
createAppList(window._apps, innerApp);
createApp(window._appData);
} else {
// todo welcome页面
document.body.innerHTML = `<h3 style="padding: 99px;">Welcome to Mixlab Nodes App!</h3>`
}
});
}
return innerApp == 1
}
</script>
+1 -1
View File
@@ -277,7 +277,7 @@ async function save (json, download = false, showInfo = true) {
function getInputsAndOutputs () {
const inputs =
`LoadImage CLIPTextEncode PromptSlide TextInput_ Color FloatSlider IntNumber CheckpointLoaderSimple LoraLoader`.split(
`LoadImage VHS_LoadVideo CLIPTextEncode PromptSlide TextInput_ Color FloatSlider IntNumber CheckpointLoaderSimple LoraLoader`.split(
' '
),
outputs = `PreviewImage SaveImage ShowTextForGPT VHS_VideoCombine`.split(
+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.16.0'
const version = 'v0.17.1'
fetch(`https://api.github.com/repos/${repoOwner}/${repoName}/releases/latest`)
.then(response => response.json())
+2 -3
View File
@@ -408,7 +408,7 @@ const _createResult = async (node, widget, message) => {
const width = node.size[0] * 0.5 - 12
let height_add = 0
for (let index = 0; index < message._images.length; index++) {
const imgs = message._images[index]
@@ -559,8 +559,7 @@ app.registerExtension({
let cards = widget.div.querySelectorAll('.card')
if (cards.length == 0) node.size = [280, 120]
_createResult(node, widget, widget.value)
if(widget.value) _createResult(node, widget, widget.value)
}
}
})
+151 -40
View File
@@ -9,33 +9,31 @@ import {
import { smart_init, addSmartMenu } from './smart_connect.js'
let isScriptLoaded = {};
let isScriptLoaded = {}
function loadExternalScript(url) {
function loadExternalScript (url) {
return new Promise((resolve, reject) => {
if (isScriptLoaded[url]) {
resolve();
return;
resolve()
return
}
const script = document.createElement('script');
script.src = url;
const script = document.createElement('script')
script.src = url
script.onload = () => {
isScriptLoaded[url]= true;
resolve();
};
script.onerror = reject;
document.head.appendChild(script);
});
isScriptLoaded[url] = true
resolve()
}
script.onerror = reject
document.head.appendChild(script)
})
}
//
function createChart(chartDom,nodes){
var myChart = echarts.init(chartDom);
var option;
function createChart (chartDom, nodes) {
var myChart = echarts.init(chartDom)
var option
console.log(nodes)
option = {
@@ -46,24 +44,25 @@ function createChart(chartDom,nodes){
{
name: 'nodeA',
value: 10,
children: Array.from(nodes,n=>{
children: Array.from(nodes, n => {
return {
name:n.type,
value:n.count
name: n.type,
value: n.count
}
})
},
}
]
}
]
};
option && myChart.setOption(option);
}
option && myChart.setOption(option)
}
async function createNodesCharts () {
await loadExternalScript('/extensions/comfyui-mixlab-nodes/lib/echarts.min.js')
await loadExternalScript(
'/extensions/comfyui-mixlab-nodes/lib/echarts.min.js'
)
const templates = await loadTemplate()
var nodes = {}
Array.from(templates, t => {
@@ -74,7 +73,6 @@ async function createNodesCharts () {
}
})
nodes = Object.values(nodes).sort((a, b) => b.count - a.count)
const menu = document.querySelector('.comfy-menu')
const separator = document.createElement('div')
@@ -86,11 +84,9 @@ async function createNodesCharts () {
menu.append(separator)
const appsButton = document.createElement('button')
appsButton.textContent = 'Nodes';
appsButton.textContent = 'Nodes'
appsButton.onclick = () => {
appsButton.onclick = () => {
let div = document.querySelector('#mixlab_apps')
if (!div) {
div = document.createElement('div')
@@ -150,10 +146,9 @@ async function createNodesCharts () {
div.appendChild(btn)
let chartDom = document.createElement('div')
chartDom.style=`height:80vh;width:450px`
chartDom.className='chart'
chartDom.style = `height:80vh;width:450px`
chartDom.className = 'chart'
div.appendChild(chartDom)
}
if (div.style.display == 'flex') {
div.style.display = 'none'
@@ -173,14 +168,11 @@ async function createNodesCharts () {
background-color: var(--comfy-menu-bg);
padding: 10px;
border: 1px solid black;z-index: 999999999;padding-top: 0;`
};
createChart(div.querySelector('.chart'),nodes)
}
createChart(div.querySelector('.chart'), nodes)
}
menu.append(appsButton)
}
function copyNodeValues (src, dest) {
@@ -366,7 +358,14 @@ injectCSS(`::-webkit-scrollbar {
animation-name: loading_mixlab;
animation-duration: 2s;
animation-iteration-count: infinite;
}`)
}
.dynamic_prompt{
border-left: 2px solid var(--input-text);
}
`)
async function getCustomnodeMappings (mode = 'url') {
// mode = "local";
@@ -824,6 +823,73 @@ const loadTemplate = async () => {
return templates ?? []
}
function drawBadge (node, orig, restArgs) {
let ctx = restArgs[0]
const r = orig?.apply?.(node, restArgs)
if (
!node.flags.collapsed &&
node.constructor.title_mode != LiteGraph.NO_TITLE
) {
let text = `#${node.id} `
let nick = node.getNickname()
if (nick) {
if (nick == 'ComfyUI') {
nick = '🦊'
}
if (nick.length > 25) {
text += nick.substring(0, 23) + '..'
} else {
text += nick
}
}
if (text != '') {
let fgColor = 'white'
let bgColor = '#0F1F0F'
let visible = true
ctx.save()
ctx.font = '12px sans-serif'
const sz = ctx.measureText(text)
ctx.fillStyle = bgColor
ctx.beginPath()
ctx.roundRect(
node.size[0] - sz.width - 12,
-LiteGraph.NODE_TITLE_HEIGHT - 20,
sz.width + 12,
20,
5
)
ctx.fill()
ctx.fillStyle = fgColor
ctx.fillText(
text,
node.size[0] - sz.width - 6,
-LiteGraph.NODE_TITLE_HEIGHT - 6
)
ctx.restore()
if (node.has_errors) {
ctx.save()
ctx.font = 'bold 14px sans-serif'
const sz2 = ctx.measureText(node.type)
ctx.fillStyle = 'white'
ctx.fillText(
node.type,
node.size[0] / 2 - sz2.width / 2,
node.size[1] / 2
)
ctx.restore()
}
}
}
return r
}
app.registerExtension({
name: 'Comfy.Mixlab.ui',
init () {
@@ -1397,6 +1463,51 @@ app.registerExtension({
// createNodesCharts()
},
nodeCreated (node) {
if (node.widgets) {
// Locate dynamic prompt text widgets
// Include any widgets with dynamicPrompts set to true, and customtext
for (let index = 0; index < node.widgets.length; index++) {
const widget = node.widgets[index]
if (
(widget.type === 'customtext' && widget.dynamicPrompts !== false) ||
widget.dynamicPrompts
) {
widget.element.classList.add('dynamic_prompt')
widget.element.addEventListener('mouseover', e => {
// console.log(node.widgets_values[index])
if (node.widgets_values&&node.widgets_values[index])
widget.element.setAttribute('title', node.widgets_values[index])
})
}
}
}
fetch('manager/badge_mode').then(r => {
if (r.status === 404) {
// 右上角的badge是否已经绘制
if (!node.badge_enabled) {
if (!node.getNickname) {
node.getNickname = function () {
if (node.nickname) {
return node.nickname
}
return
// return getNickname(node, node.comfyClass.trim())
}
}
const orig = node.__proto__.onDrawForeground
node.onDrawForeground = function (ctx) {
drawBadge(node, orig, arguments)
}
node.badge_enabled = true
}
}
})
},
async loadedGraphNode (node, app) {
// console.log(
// '#ui init',
+10 -9
View File
@@ -1,6 +1,6 @@
{
"last_node_id": 23,
"last_link_id": 25,
"last_node_id": 24,
"last_link_id": 26,
"nodes": [
{
"id": 9,
@@ -203,6 +203,7 @@
"Node name for S&R": "ShowTextForGPT"
},
"widgets_values": [
"a girl face,super,(Pop Art:1.26),(Black and White:1.26)",
"a girl face,super,(Pop Art:1.26),(Black and White:1.26)"
]
},
@@ -537,7 +538,7 @@
"type": "IMAGE",
"links": [
24,
25
26
],
"slot_index": 0
}
@@ -547,7 +548,7 @@
}
},
{
"id": 23,
"id": 24,
"type": "AppInfo",
"pos": [
3363.0014990624995,
@@ -562,9 +563,9 @@
"mode": 0,
"inputs": [
{
"name": "LOGO",
"name": "IMAGE",
"type": "IMAGE",
"link": 25
"link": 26
}
],
"properties": {
@@ -580,7 +581,7 @@
"https://",
"",
"enable",
1
2
]
}
],
@@ -706,10 +707,10 @@
"IMAGE"
],
[
25,
26,
10,
0,
23,
24,
0,
"IMAGE"
]