Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
686ebcfd8b | ||
|
|
51aaba39cf | ||
|
|
d7c6632499 | ||
|
|
1b0ea06876 | ||
|
|
8ebe88629b | ||
|
|
e226992703 | ||
|
|
11a8394d69 | ||
|
|
9f54a1b91a | ||
|
|
35d11061e9 | ||
|
|
65d8b490ca | ||
|
|
0fef12c3b1 | ||
|
|
8516bff224 | ||
|
|
1bcc501352 | ||
|
|
f492b17fbe | ||
|
|
48ae90f80e | ||
|
|
9321ccbc48 | ||
|
|
402cd01e1a | ||
|
|
7b2d0e29c6 | ||
|
|
52d38c401a | ||
|
|
a34dd61076 | ||
|
|
a53a3e772a | ||
|
|
8f24c294a7 | ||
|
|
acc3f76654 | ||
|
|
8c977fb442 | ||
|
|
aa20a2de67 | ||
|
|
5fcb154d89 | ||
|
|
0980129f4e | ||
|
|
1258746886 | ||
|
|
ed128b0ad6 | ||
|
|
f0db08acd6 | ||
|
|
7c655e3080 | ||
|
|
1b9871c3df | ||
|
|
7568aaf243 | ||
|
|
a76be8450d |
@@ -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
|
||||
|
||||

|
||||
|
||||
@@ -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
@@ -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,
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{}
|
||||
+63
-4
@@ -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
@@ -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
@@ -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
@@ -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):
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import folder_paths
|
||||
|
||||
# 外挂一个文件,用来编写新的节点
|
||||
def run(v):
|
||||
|
||||
output_dir = folder_paths.get_temp_directory()
|
||||
|
||||
print('1323',v,output_dir)
|
||||
+2
-1
@@ -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
@@ -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>
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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
@@ -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',
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user