Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9589f28ef7 | ||
|
|
35492c5671 | ||
|
|
db1e695bf3 | ||
|
|
ecc4aec43b | ||
|
|
fc063c2205 | ||
|
|
4d60ce138a | ||
|
|
2afd24f6e4 | ||
|
|
437acd023a | ||
|
|
b00523ae14 | ||
|
|
4405a74993 | ||
|
|
cb16090868 | ||
|
|
396e510dce | ||
|
|
3b9790b969 | ||
|
|
a35d07a7ac | ||
|
|
6d004c61fc | ||
|
|
ffdd06da1b | ||
|
|
f03f34cacb | ||
|
|
0c86ea849e | ||
|
|
0efa4c38c0 | ||
|
|
6092ab7793 | ||
|
|
929def87eb | ||
|
|
be074ccff7 | ||
|
|
3445199393 |
@@ -6,13 +6,14 @@
|
||||
|
||||
##### `最新`:
|
||||
|
||||
- 增加 SiliconflowLLM,可以使用由Siliconflow提供的免费LLM
|
||||
|
||||
- 增加 Edit Mask,方便在生成的时候手动绘制 mask [workflow](./workflow/edit-mask-workflow.json)
|
||||
|
||||
<!-- - ChatGPT 节点支持 Local LLM(llama.cpp),Phi3、llama3 都可以直接一个节点运行了。模型下载后,放置到 `models/llamafile/` -->
|
||||
|
||||
- ChatGPT 节点支持 Local LLM(llama.cpp),Phi3、llama3 都可以直接一个节点运行了。模型下载后,放置到 `models/llamafile/`
|
||||
|
||||
- 右键菜单支持 text-to-text,方便对 prompt 词补全
|
||||
|
||||
<!-- - 右键菜单支持 text-to-text,方便对 prompt 词补全 -->
|
||||
<!--
|
||||
强烈推荐:
|
||||
[Phi-3-mini-4k-instruct-function-calling-GGUF](https://huggingface.co/nold/Phi-3-mini-4k-instruct-function-calling-GGUF)
|
||||
|
||||
@@ -21,7 +22,7 @@
|
||||
- 右键菜单支持 image-to-text,使用多模态模型,多模态使用 [llava-phi-3-mini-gguf](https://huggingface.co/xtuner/llava-phi-3-mini-gguf/tree/main),注意需要把llava-phi-3-mini-mmproj-f16.gguf也下载
|
||||
|
||||

|
||||

|
||||
 -->
|
||||
|
||||
|
||||
#### `相关插件推荐`
|
||||
|
||||
+170
-26
@@ -3,12 +3,14 @@ import os
|
||||
import subprocess
|
||||
import importlib.util
|
||||
import sys,json
|
||||
import urllib
|
||||
import execution
|
||||
import uuid
|
||||
import hashlib
|
||||
import datetime
|
||||
import folder_paths
|
||||
import logging
|
||||
import base64,io,re
|
||||
import random
|
||||
from PIL import Image
|
||||
from comfy.cli_args import args
|
||||
python = sys.executable
|
||||
@@ -171,8 +173,7 @@ def create_for_https():
|
||||
os.mkdir(https_key_path)
|
||||
if not os.path.exists(crt):
|
||||
create_key(key,crt)
|
||||
|
||||
print('https_key OK: ', crt,key)
|
||||
# print('https_key OK: ', crt,key)
|
||||
return (crt,key)
|
||||
|
||||
|
||||
@@ -309,9 +310,10 @@ def get_my_workflow_for_app(filename="my_workflow_app.json",category="",is_all=F
|
||||
print('app_workflow_path: ',app_workflow_path)
|
||||
try:
|
||||
with open(app_workflow_path) as json_file:
|
||||
json_data=json.load(json_file)
|
||||
apps = [{
|
||||
'filename':filename,
|
||||
'data':json.load(json_file)
|
||||
'data':json_data
|
||||
}]
|
||||
except Exception as e:
|
||||
print("发生异常:", str(e))
|
||||
@@ -521,9 +523,17 @@ async def new_start(self, address, port, verbose=True, call_on_start=None):
|
||||
logging.info("\n")
|
||||
logging.info("\n\nStarting server")
|
||||
|
||||
import socket
|
||||
|
||||
hostname = socket.gethostname()
|
||||
ip_address = socket.gethostbyname(hostname)
|
||||
|
||||
# print(f"本机的IP地址是: {ip_address}")
|
||||
|
||||
|
||||
# print("\033[93mStarting server\n")
|
||||
logging.info("\033[93mTo see the GUI go to: http://{}:{}".format(address, http_port))
|
||||
logging.info("\033[93mTo see the GUI go to: https://{}:{}\033[0m".format(address, https_port))
|
||||
logging.info("\033[93mTo see the GUI go to: http://{}:{} or http://{}:{}".format(ip_address, http_port,address,http_port))
|
||||
logging.info("\033[93mTo see the GUI go to: https://{}:{} or https://{}:{}\033[0m".format(ip_address, https_port,address,https_port))
|
||||
|
||||
# print("\033[93mTo see the GUI go to: http://{}:{}".format(address, http_port))
|
||||
# print("\033[93mTo see the GUI go to: https://{}:{}\033[0m".format(address, https_port))
|
||||
@@ -608,13 +618,34 @@ async def mixlab_workflow_hander(request):
|
||||
category=data['category']
|
||||
if 'admin' in data:
|
||||
admin=data['admin']
|
||||
|
||||
ds=get_my_workflow_for_app(filename,category,admin)
|
||||
data=[]
|
||||
for json_data in ds:
|
||||
# 不传给前端
|
||||
if 'output' in json_data['data']:
|
||||
del json_data['data']['output']
|
||||
if 'workflow' in json_data['data']:
|
||||
del json_data['data']['workflow']
|
||||
data.append(json_data)
|
||||
|
||||
result={
|
||||
'data':get_my_workflow_for_app(filename,category,admin),
|
||||
'data':data,
|
||||
'status':'success',
|
||||
}
|
||||
elif data['task']=='list':
|
||||
ds=get_workflows()
|
||||
data=[]
|
||||
for json_data in ds:
|
||||
# 不传给前端
|
||||
if 'output' in json_data['data']:
|
||||
del json_data['data']['output']
|
||||
if 'workflow' in json_data['data']:
|
||||
del json_data['data']['workflow']
|
||||
data.append(json_data)
|
||||
|
||||
result={
|
||||
'data':get_workflows(),
|
||||
'data':data,
|
||||
'status':'success',
|
||||
}
|
||||
except Exception as e:
|
||||
@@ -699,23 +730,134 @@ async def rembg_hander(request):
|
||||
|
||||
return web.json_response(result)
|
||||
|
||||
|
||||
@routes.post("/mixlab/prompt_result")
|
||||
async def post_prompt_result(request):
|
||||
data = await request.json()
|
||||
res=None
|
||||
# print(data)
|
||||
try:
|
||||
action=data['action']
|
||||
if action=='save':
|
||||
result=data['data']
|
||||
res=save_prompt_result(result['prompt_id'],result)
|
||||
elif action=='all':
|
||||
res=get_prompt_result()
|
||||
except Exception as e:
|
||||
print('/mixlab/prompt_result',False,e)
|
||||
# 保存运行结果?暂时去掉
|
||||
# @routes.post("/mixlab/prompt_result")
|
||||
# async def post_prompt_result(request):
|
||||
# data = await request.json()
|
||||
# res=None
|
||||
# # print(data)
|
||||
# try:
|
||||
# action=data['action']
|
||||
# if action=='save':
|
||||
# result=data['data']
|
||||
# res=save_prompt_result(result['prompt_id'],result)
|
||||
# elif action=='all':
|
||||
# res=get_prompt_result()
|
||||
# except Exception as e:
|
||||
# print('/mixlab/prompt_result',False,e)
|
||||
|
||||
return web.json_response({"result":res})
|
||||
# return web.json_response({"result":res})
|
||||
|
||||
# 种子设置
|
||||
def random_seed(seed, data):
|
||||
max_seed = 4294967295
|
||||
|
||||
for id, value in data.items():
|
||||
if 'seed' in value['inputs'] and not isinstance(value['inputs']['seed'], list) and seed[id] in ['increment', 'decrement', 'randomize']:
|
||||
value['inputs']['seed'] = round(random.random() * max_seed)
|
||||
|
||||
if 'noise_seed' in value['inputs'] and not isinstance(value['inputs']['noise_seed'], list) and seed[id] in ['increment', 'decrement', 'randomize']:
|
||||
value['inputs']['noise_seed'] = round(random.random() * max_seed)
|
||||
|
||||
if value.get('class_type') == "Seed_" and seed[id] in ['increment', 'decrement', 'randomize']:
|
||||
value['inputs']['seed'] = round(random.random() * max_seed)
|
||||
|
||||
print('new Seed', value)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
# 运行工作流,代替官方的prompt接口
|
||||
@routes.post("/mixlab/prompt")
|
||||
async def mixlab_post_prompt(request):
|
||||
p_intance=PromptServer.instance
|
||||
logging.info("got prompt")
|
||||
resp_code = 200
|
||||
out_string = ""
|
||||
json_data = await request.json()
|
||||
# json_data = p_intance.trigger_on_prompt(json_data)
|
||||
# filename,category, client_id ,input
|
||||
# workflow 的 filename,category
|
||||
|
||||
# 输入的参数
|
||||
input_data=json_data['input'] if "input" in json_data else []
|
||||
# 种子
|
||||
seed=json_data['seed'] if "seed" in json_data else {}
|
||||
|
||||
apps=get_my_workflow_for_app(json_data['filename'],json_data['category'],False)
|
||||
|
||||
prompt=json_data['prompt'] if 'prompt' in json_data else None
|
||||
|
||||
if len(apps)==1:
|
||||
# 取到prompt
|
||||
prompt=apps[0]['data']['output']
|
||||
# 更新input_data到prompt里
|
||||
'''
|
||||
{
|
||||
"inputs": {
|
||||
"number": 512,
|
||||
"min_value": 512,
|
||||
"max_value": 2048,
|
||||
"step": 1
|
||||
},
|
||||
"class_type": "IntNumber",
|
||||
"id": "22"
|
||||
},
|
||||
'''
|
||||
|
||||
for inp in input_data:
|
||||
id=inp['id']
|
||||
if prompt[id]['class_type']==inp['class_type']:
|
||||
prompt[id]['inputs'].update(inp['inputs'])
|
||||
|
||||
|
||||
if prompt==None:
|
||||
return web.json_response({"error": "no prompt", "node_errors": []}, status=400)
|
||||
else:
|
||||
# 种子更新
|
||||
'''
|
||||
"seed": {
|
||||
"45": "randomize",
|
||||
"46": "randomize"
|
||||
}
|
||||
'''
|
||||
json_data["prompt"]=random_seed(seed,prompt)
|
||||
|
||||
# print("#json_data",prompt)
|
||||
# 需要把apps处理成 prompt
|
||||
# 注意seed的处理
|
||||
|
||||
if "number" in json_data:
|
||||
number = float(json_data['number'])
|
||||
else:
|
||||
number = p_intance.number
|
||||
if "front" in json_data:
|
||||
if json_data['front']:
|
||||
number = -number
|
||||
|
||||
p_intance.number += 1
|
||||
|
||||
if "prompt" in json_data:
|
||||
prompt = json_data["prompt"]
|
||||
valid = execution.validate_prompt(prompt)
|
||||
extra_data = {}
|
||||
if "extra_data" in json_data:
|
||||
extra_data = json_data["extra_data"]
|
||||
|
||||
if "client_id" in json_data:
|
||||
extra_data["client_id"] = json_data["client_id"]
|
||||
if valid[0]:
|
||||
prompt_id = str(uuid.uuid4())
|
||||
outputs_to_execute = valid[2]
|
||||
p_intance.prompt_queue.put((number, prompt_id, prompt, extra_data, outputs_to_execute))
|
||||
response = {"prompt_id": prompt_id, "number": number, "node_errors": valid[3]}
|
||||
return web.json_response(response)
|
||||
else:
|
||||
logging.warning("invalid prompt: {}".format(valid[1]))
|
||||
return web.json_response({"error": valid[1], "node_errors": valid[3]}, status=400)
|
||||
else:
|
||||
return web.json_response({"error": "no prompt", "node_errors": []}, status=400)
|
||||
|
||||
|
||||
|
||||
async def start_local_llm(data):
|
||||
@@ -991,7 +1133,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"IncrementingListNode_":"Create Incrementing Number List ♾️Mixlab",
|
||||
"LoadImagesToBatch":"Load Images(base64) ♾️Mixlab",
|
||||
"PreviewMask_":"Preview Mask",
|
||||
"AudioPlay":"Audio Play ♾️Mixlab",
|
||||
"AudioPlay":"Preview Audio ♾️Mixlab",
|
||||
|
||||
"MultiplicationNode":"Math Operation ♾️Mixlab",
|
||||
}
|
||||
@@ -1005,11 +1147,12 @@ logging.info('\033[91m ### Mixlab Nodes: \033[93mLoaded')
|
||||
# print('\033[91m ### Mixlab Nodes: \033[93mLoaded')
|
||||
|
||||
try:
|
||||
from .nodes.ChatGPT import ChatGPTNode,ShowTextForGPT,CharacterInText,TextSplitByDelimiter
|
||||
from .nodes.ChatGPT import ChatGPTNode,ShowTextForGPT,CharacterInText,TextSplitByDelimiter,SiliconflowFreeNode
|
||||
logging.info('ChatGPT.available True')
|
||||
|
||||
NODE_CLASS_MAPPINGS_V = {
|
||||
"ChatGPTOpenAI":ChatGPTNode,
|
||||
"SiliconflowLLM":SiliconflowFreeNode,
|
||||
"ShowTextForGPT":ShowTextForGPT,
|
||||
"CharacterInText":CharacterInText,
|
||||
"TextSplitByDelimiter":TextSplitByDelimiter,
|
||||
@@ -1018,6 +1161,7 @@ try:
|
||||
# 一个包含节点友好/可读的标题的字典
|
||||
NODE_DISPLAY_NAME_MAPPINGS_V = {
|
||||
"ChatGPTOpenAI":"ChatGPT & Local LLM ♾️Mixlab",
|
||||
"SiliconflowLLM":"LLM Siliconflow ♾️Mixlab",
|
||||
"ShowTextForGPT":"Show Text ♾️MixlabApp",
|
||||
"CharacterInText":"Character In Text",
|
||||
"TextSplitByDelimiter":"Text Split By Delimiter",
|
||||
|
||||
+7
-2
@@ -90,7 +90,7 @@ class AudioPlayNode:
|
||||
# {'waveform': tensor([], size=(1, 1, 0)), 'sample_rate': 44100}
|
||||
is_tensor=True
|
||||
|
||||
if is_tensor:
|
||||
if is_tensor and (not 'audio_path' in audio):
|
||||
filename_prefix=""
|
||||
# 保存
|
||||
filename_prefix += self.prefix_append
|
||||
@@ -108,7 +108,12 @@ class AudioPlayNode:
|
||||
})
|
||||
|
||||
else:
|
||||
results=[audio]
|
||||
results=[{
|
||||
"filename": audio['filename'],
|
||||
"subfolder":audio['subfolder'],
|
||||
"type": audio['type'],
|
||||
"audio_path":audio['audio_path']
|
||||
}]
|
||||
|
||||
|
||||
# print(audio)
|
||||
|
||||
+95
-5
@@ -53,8 +53,8 @@ def azure_client(key,url):
|
||||
|
||||
def openai_client(key,url):
|
||||
client = openai.OpenAI(
|
||||
api_key=key,
|
||||
base_url=url
|
||||
api_key=key,
|
||||
base_url=url
|
||||
)
|
||||
return client
|
||||
|
||||
@@ -163,7 +163,7 @@ def llama_cpp_client(file_name):
|
||||
|
||||
|
||||
def chat(client, model_name,messages ):
|
||||
|
||||
print('#chat',model_name,messages)
|
||||
try_count = 0
|
||||
while True:
|
||||
try_count += 1
|
||||
@@ -236,7 +236,11 @@ class ChatGPTNode:
|
||||
"moonshot-v1-8k",
|
||||
"moonshot-v1-32k",
|
||||
"moonshot-v1-128k",
|
||||
"deepseek-chat"
|
||||
"deepseek-chat",
|
||||
"Qwen/Qwen2-7B-Instruct",
|
||||
"THUDM/glm-4-9b-chat",
|
||||
"01-ai/Yi-1.5-9B-Chat-16K",
|
||||
"meta-llama/Meta-Llama-3.1-8B-Instruct"
|
||||
]
|
||||
return {
|
||||
"required": {
|
||||
@@ -300,7 +304,7 @@ class ChatGPTNode:
|
||||
client=llama_cpp_client(model)
|
||||
else :
|
||||
client = openai_client(api_key,api_url) # 使用 ChatGPT 的接口
|
||||
print('using ChatGPT interface')
|
||||
# print('using ChatGPT interface',api_key,api_url)
|
||||
|
||||
# 把用户的提示添加到会话历史中
|
||||
# 调用API时传递整个会话历史
|
||||
@@ -316,6 +320,7 @@ class ChatGPTNode:
|
||||
session_history=crop_list_tail(self.session_history,context_size)
|
||||
|
||||
messages=[{"role": "system", "content": self.system_content}]+session_history+[{"role": "user", "content": prompt}]
|
||||
|
||||
response_content = chat(client,model,messages)
|
||||
|
||||
self.session_history=self.session_history+[{"role": "user", "content": prompt}]+[{'role':'assistant',"content":response_content}]
|
||||
@@ -336,6 +341,91 @@ class ChatGPTNode:
|
||||
return (response_content,json.dumps(messages, indent=4),json.dumps(self.session_history, indent=4),)
|
||||
|
||||
|
||||
class SiliconflowFreeNode:
|
||||
def __init__(self):
|
||||
# self.__client = OpenAI()
|
||||
self.session_history = [] # 用于存储会话历史的列表
|
||||
# self.seed=0
|
||||
self.system_content="You are ChatGPT, a large language model trained by OpenAI. Answer as concisely as possible."
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
model_list= [
|
||||
"Qwen/Qwen2-7B-Instruct",
|
||||
"THUDM/glm-4-9b-chat",
|
||||
"01-ai/Yi-1.5-9B-Chat-16K",
|
||||
"meta-llama/Meta-Llama-3.1-8B-Instruct"
|
||||
]
|
||||
return {
|
||||
"required": {
|
||||
"api_key":("KEY", {"default": "", "multiline": True,"dynamicPrompts": False}),
|
||||
"prompt": ("STRING", {"multiline": True,"dynamicPrompts": False}),
|
||||
"system_content": ("STRING",
|
||||
{
|
||||
"default": "You are ChatGPT, a large language model trained by OpenAI. Answer as concisely as possible.",
|
||||
"multiline": True,"dynamicPrompts": False
|
||||
}),
|
||||
"model": ( model_list,
|
||||
{"default": model_list[0]}),
|
||||
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff, "step": 1}),
|
||||
"context_size":("INT", {"default": 1, "min": 0, "max":30, "step": 1}),
|
||||
},
|
||||
"hidden": {
|
||||
"unique_id": "UNIQUE_ID",
|
||||
"extra_pnginfo": "EXTRA_PNGINFO",
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING","STRING","STRING",)
|
||||
RETURN_NAMES = ("text","messages","session_history",)
|
||||
FUNCTION = "generate_contextual_text"
|
||||
CATEGORY = "♾️Mixlab/GPT"
|
||||
INPUT_IS_LIST = False
|
||||
OUTPUT_IS_LIST = (False,False,False,)
|
||||
|
||||
|
||||
def generate_contextual_text(self,
|
||||
api_key,
|
||||
prompt,
|
||||
system_content,
|
||||
model,
|
||||
seed,context_size,unique_id = None, extra_pnginfo=None):
|
||||
|
||||
api_url="https://api.siliconflow.cn/v1"
|
||||
|
||||
# 把系统信息和初始信息添加到会话历史中
|
||||
if system_content:
|
||||
self.system_content=system_content
|
||||
# self.session_history=[]
|
||||
# self.session_history.append({"role": "system", "content": system_content})
|
||||
|
||||
#
|
||||
client = openai_client(api_key,api_url) # 使用 ChatGPT 的接口
|
||||
# print('using ChatGPT interface',api_key,api_url)
|
||||
|
||||
# 把用户的提示添加到会话历史中
|
||||
# 调用API时传递整个会话历史
|
||||
|
||||
def crop_list_tail(lst, size):
|
||||
if size >= len(lst):
|
||||
return lst
|
||||
elif size==0:
|
||||
return []
|
||||
else:
|
||||
return lst[-size:]
|
||||
|
||||
session_history=crop_list_tail(self.session_history,context_size)
|
||||
|
||||
messages=[{"role": "system", "content": self.system_content}]+session_history+[{"role": "user", "content": prompt}]
|
||||
|
||||
response_content = chat(client,model,messages)
|
||||
|
||||
self.session_history=self.session_history+[{"role": "user", "content": prompt}]+[{'role':'assistant',"content":response_content}]
|
||||
|
||||
return (response_content,json.dumps(messages, indent=4),json.dumps(self.session_history, indent=4),)
|
||||
|
||||
|
||||
|
||||
|
||||
class ShowTextForGPT:
|
||||
@classmethod
|
||||
|
||||
+37
-16
@@ -30,17 +30,20 @@ def opencv_to_pil(image):
|
||||
return pil_image
|
||||
|
||||
# 列出目录下面的所有文件
|
||||
def get_files_with_extension(directory, extension):
|
||||
def get_files_with_extension(directory, extensions):
|
||||
file_list = []
|
||||
# 确保extensions参数是一个list,即使只有一个元素
|
||||
if not isinstance(extensions, (tuple, list)):
|
||||
extensions = [extensions]
|
||||
for root, dirs, files in os.walk(directory):
|
||||
# print(f"Files at {root}: {files}") # 确认files是一个字符串列表
|
||||
for file in files:
|
||||
if file.endswith(extension):
|
||||
file = os.path.splitext(file)[0]
|
||||
file_path = os.path.join(root, file)
|
||||
file_name = os.path.relpath(file_path, directory)
|
||||
file_list.append(file_name)
|
||||
# 检查文件是否以任何一个提供的扩展名结尾
|
||||
if any(file.endswith(ext) for ext in extensions):
|
||||
# 直接将文件名添加到列表中
|
||||
file_list.append(file)
|
||||
return file_list
|
||||
|
||||
|
||||
def composite_images(foreground, background, mask, is_multiply_blend=False, position="overall", scale=0.25):
|
||||
width, height = foreground.size
|
||||
bg_image = background
|
||||
@@ -909,7 +912,7 @@ def resize_image(layer_image, scale_option, width, height,color="white"):
|
||||
return layer_image
|
||||
|
||||
|
||||
def generate_text_image(text, font_path, font_size, text_color, vertical=True, stroke=False, stroke_color=(0, 0, 0), stroke_width=1, spacing=0, padding=4):
|
||||
def generate_text_image(text, font_path, font_size, text_color, vertical=True, stroke=False, stroke_color=(0, 0, 0), stroke_width=1, spacing=0, line_spacing=0,padding=4):
|
||||
# Split text into lines based on line breaks
|
||||
lines = text.split("\n")
|
||||
|
||||
@@ -935,9 +938,13 @@ def generate_text_image(text, font_path, font_size, text_color, vertical=True, s
|
||||
char_coordinates.append((x, y))
|
||||
y += char_height + spacing
|
||||
max_height = max(max_height, y + padding)
|
||||
x += max_char_width + spacing
|
||||
x += max_char_width + line_spacing
|
||||
y = padding
|
||||
max_width = x
|
||||
total_line_width = sum(font.getsize(line)[1] for line in lines)
|
||||
total_spacing = line_spacing * (len(lines) - 1)
|
||||
# 确保左边和右边的padding都被计入max_width
|
||||
max_width = total_line_width + total_spacing + padding * 2
|
||||
else:
|
||||
for line in lines:
|
||||
line_width, line_height = font.getsize(line)
|
||||
@@ -946,9 +953,13 @@ def generate_text_image(text, font_path, font_size, text_color, vertical=True, s
|
||||
char_coordinates.append((x, y))
|
||||
x += char_width + spacing
|
||||
max_width = max(max_width, x + padding)
|
||||
y += line_height + spacing
|
||||
y += line_height + line_spacing
|
||||
x = padding
|
||||
max_height = y
|
||||
# max_height = y
|
||||
total_line_heights = sum(font.getsize(line)[1] for line in lines)
|
||||
total_spacing = line_spacing * (len(lines) - 1)
|
||||
# 确保顶部和底部的padding都被计入max_height
|
||||
max_height = total_line_heights + total_spacing + padding * 2
|
||||
|
||||
# 3. Create image with calculated width and height
|
||||
image = Image.new('RGBA', (max_width, max_height), (255, 255, 255, 0))
|
||||
@@ -1288,6 +1299,9 @@ class LoadImages_:
|
||||
image=pil2tensor(image)
|
||||
ims.append(image)
|
||||
|
||||
if len(ims)==0:
|
||||
image1 = Image.new('RGB', (512, 512), color='black')
|
||||
return (pil2tensor(image1),)
|
||||
image1 = ims[0]
|
||||
for image2 in ims[1:]:
|
||||
if image1.shape[1:] != image2.shape[1:]:
|
||||
@@ -1498,7 +1512,7 @@ class TextImage:
|
||||
return {"required": {
|
||||
|
||||
"text": ("STRING",{"multiline": True,"default": "龍馬精神迎新歲","dynamicPrompts": False}),
|
||||
"font": (get_files_with_extension(FONT_PATH,'.ttf'),),#后缀为 ttf
|
||||
"font": (get_files_with_extension(FONT_PATH,['.ttf','.otf']),),#后缀为 ttf
|
||||
"font_size": ("INT",{
|
||||
"default":100,
|
||||
"min": 100, #Minimum value
|
||||
@@ -1513,6 +1527,13 @@ class TextImage:
|
||||
"step": 1, #Slider's step
|
||||
"display": "number" # Cosmetic only: display as "number" or "slider"
|
||||
}),
|
||||
"line_spacing": ("INT",{
|
||||
"default":12,
|
||||
"min": -200, #Minimum value
|
||||
"max": 200, #Maximum value
|
||||
"step": 1, #Slider's step
|
||||
"display": "number" # Cosmetic only: display as "number" or "slider"
|
||||
}),
|
||||
"padding": ("INT",{
|
||||
"default":8,
|
||||
"min": 0, #Minimum value
|
||||
@@ -1536,14 +1557,14 @@ class TextImage:
|
||||
INPUT_IS_LIST = False
|
||||
OUTPUT_IS_LIST = (False,False,)
|
||||
|
||||
def run(self,text,font,font_size,spacing,padding,text_color,vertical,stroke):
|
||||
def run(self,text,font,font_size,spacing,line_spacing,padding,text_color,vertical,stroke):
|
||||
|
||||
font_path=os.path.join(FONT_PATH,font+'.ttf')
|
||||
font_path=os.path.join(FONT_PATH,font)
|
||||
|
||||
if text=="":
|
||||
text=" "
|
||||
# stroke=False, stroke_color=(0, 0, 0), stroke_width=1, spacing=0
|
||||
img,mask=generate_text_image(text,font_path,font_size,text_color,vertical,stroke,(0, 0, 0),1,spacing,padding)
|
||||
img,mask=generate_text_image(text,font_path,font_size,text_color,vertical,stroke,(0, 0, 0),1,spacing,line_spacing,padding)
|
||||
|
||||
img=pil2tensor(img)
|
||||
mask=pil2tensor(mask)
|
||||
@@ -3201,4 +3222,4 @@ class ImageListToBatch_:
|
||||
|
||||
out = torch.cat(out, dim=0)
|
||||
|
||||
return (out,)
|
||||
return (out,)
|
||||
|
||||
+44
-16
@@ -545,20 +545,24 @@ class LoadAndCombinedAudio_:
|
||||
if duration > -1:
|
||||
crop_audio(audio_file, start_time, duration)
|
||||
|
||||
return (audio_file, {
|
||||
"filename": audio_file_name,
|
||||
"subfolder": "",
|
||||
"type": "output",
|
||||
"audio_path":audio_file
|
||||
} ,)
|
||||
waveform, sample_rate = torchaudio.load(audio_file)
|
||||
audio = {
|
||||
"filename": audio_file_name,
|
||||
"subfolder": "",
|
||||
"type": "output",
|
||||
"audio_path":audio_file,
|
||||
"waveform": waveform.unsqueeze(0),
|
||||
"sample_rate": sample_rate}
|
||||
|
||||
return (audio_file,audio ,)
|
||||
|
||||
class CombineAudioVideo:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
|
||||
return {"required": {
|
||||
"video_file_path": ("STRING", {"forceInput": True}),
|
||||
"audio_file_path": ("STRING", {"forceInput": True}),
|
||||
"video": ("SCENE_VIDEO",),
|
||||
"audio": ("AUDIO", ),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -566,23 +570,46 @@ class CombineAudioVideo:
|
||||
|
||||
OUTPUT_NODE = True
|
||||
FUNCTION = "run"
|
||||
RETURN_TYPES = ()
|
||||
RETURN_NAMES = ()
|
||||
RETURN_TYPES = ("SCENE_VIDEO",)
|
||||
RETURN_NAMES = ("SCENE_VIDEO",)
|
||||
|
||||
def run(self,video_file_path, audio_file_path):
|
||||
def run(self,video, audio):
|
||||
|
||||
output_dir = folder_paths.get_output_directory()
|
||||
|
||||
counter=get_new_counter(output_dir,'video_final_')
|
||||
|
||||
# 判断是否是 Tensor 类型
|
||||
is_tensor = not isinstance(audio, dict)
|
||||
# print('#判断是否是 Tensor 类型',is_tensor,audio)
|
||||
if not is_tensor and 'waveform' in audio and 'sample_rate' in audio:
|
||||
# {'waveform': tensor([], size=(1, 1, 0)), 'sample_rate': 44100}
|
||||
is_tensor=True
|
||||
|
||||
if "audio_path" in audio:
|
||||
is_tensor=False
|
||||
audio_file_path=audio["audio_path"]
|
||||
|
||||
if is_tensor:
|
||||
filename_prefix="audio_tmp"
|
||||
full_output_folder, filename, counter, subfolder, filename_prefix = folder_paths.get_save_image_path(
|
||||
filename_prefix,
|
||||
folder_paths.get_temp_directory())
|
||||
|
||||
filename_with_batch_num = filename.replace("%batch_num%", str(1))
|
||||
file = f"{filename_with_batch_num}_{counter:05}_.wav"
|
||||
|
||||
audio_file_path=os.path.join(full_output_folder, file)
|
||||
|
||||
torchaudio.save(audio_file_path, audio['waveform'].squeeze(0), audio["sample_rate"])
|
||||
|
||||
# 获取文件名和扩展名
|
||||
base, ext = os.path.splitext(video_file_path)
|
||||
base, ext = os.path.splitext(video)
|
||||
counter=get_new_counter(output_dir,'video_final_')
|
||||
|
||||
v_file = f"video_final_{counter:05}{ext}"
|
||||
|
||||
v_file_path=os.path.join(output_dir, v_file)
|
||||
|
||||
combine_audio_video(audio_file_path,video_file_path,v_file_path)
|
||||
combine_audio_video(audio_file_path,video,v_file_path)
|
||||
|
||||
previews = [
|
||||
{
|
||||
@@ -592,7 +619,8 @@ class CombineAudioVideo:
|
||||
"format": get_mime_type(v_file),
|
||||
}
|
||||
]
|
||||
return {"ui": {"gifs": previews}}
|
||||
|
||||
return {"ui": {"gifs": previews},"result":(v_file_path,)}
|
||||
|
||||
# The code is based on ComfyUI-VideoHelperSuite modification.
|
||||
class VideoCombine_Adv:
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
[project]
|
||||
name = "comfyui-mixlab-nodes"
|
||||
description = "3D, ScreenShareNode & FloatingVideoNode, SpeechRecognition & SpeechSynthesis, GPT, LoadImagesFromLocal, Layers, Other Nodes, ..."
|
||||
version = "0.30.3"
|
||||
version = "0.32.0"
|
||||
license = "MIT"
|
||||
dependencies = ["numpy", "pyOpenSSL", "watchdog", "opencv-python-headless", "matplotlib", "openai", "simple-lama-inpainting", "clip-interrogator==0.6.0", "transformers>=4.36.0", "lark-parser", "imageio-ffmpeg", "rembg[gpu]", "omegaconf==2.3.0", "Pillow>=9.5.0", "einops==0.7.0", "trimesh>=4.0.5", "huggingface-hub", "scikit-image"]
|
||||
|
||||
|
||||
+260
-62
@@ -641,8 +641,13 @@
|
||||
imageElement.appendChild(im);
|
||||
|
||||
let base64s = imageElement.querySelectorAll('.base64')
|
||||
//更新输入
|
||||
window._appData.data[nodeId].inputs.images.base64 = Array.from(base64s, (b) => b.src)
|
||||
//更新输入 //兼容 ,如果data没有则不更新data
|
||||
if (window._appData.data) window._appData.data[nodeId].inputs.images.base64 = Array.from(base64s, (b) => b.src)
|
||||
//更新输入参数
|
||||
updateInputData(nodeId, (inputs) => {
|
||||
inputs.images.base64 = Array.from(base64s, (b) => b.src)
|
||||
return inputs
|
||||
})
|
||||
|
||||
// 删除
|
||||
im.addEventListener('click', e => {
|
||||
@@ -650,7 +655,11 @@
|
||||
im.remove();
|
||||
let base64s = imageElement.querySelectorAll('.base64')
|
||||
//更新输入
|
||||
window._appData.data[nodeId].inputs.images.base64 = Array.from(base64s, (b) => b.src)
|
||||
if (window._appData.data) window._appData.data[nodeId].inputs.images.base64 = Array.from(base64s, (b) => b.src)
|
||||
updateInputData(nodeId, (inputs) => {
|
||||
inputs.images.base64 = Array.from(base64s, (b) => b.src)
|
||||
return inputs
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -974,7 +983,7 @@
|
||||
// // 获取读取的文件内容,即 Blob 对象
|
||||
let hashId = await calculateImageHash(fileBlob)
|
||||
|
||||
if (hashId == window._appData.data[data.id].hashId) {
|
||||
if (window._appData.data && hashId == window._appData.data[data.id].hashId) {
|
||||
document.body.querySelector('.app').style.display = 'flex'
|
||||
document.body.querySelector('#author').style.display = 'block'
|
||||
return;
|
||||
@@ -986,8 +995,15 @@
|
||||
let { url, name } = await uploadMask(fileBlob, imgurl);
|
||||
// 在这里可以对 Blob 对象进行进一步处理
|
||||
// imageElement.src = url;
|
||||
window._appData.data[data.id].inputs.image = name;
|
||||
window._appData.data[data.id].hashId = hashId;
|
||||
if (window._appData.data && window._appData.data[data.id]) window._appData.data[data.id].inputs.image = name;
|
||||
if (window._appData.data && window._appData.data[data.id]) window._appData.data[data.id].hashId = hashId;
|
||||
|
||||
//更新输入参数
|
||||
updateInputData(data.id, (inputs) => {
|
||||
inputs.image = name
|
||||
return inputs
|
||||
})
|
||||
|
||||
|
||||
// console.log("上传的文件:", url, data.id, name);
|
||||
//更新图片
|
||||
@@ -1116,10 +1132,31 @@
|
||||
|
||||
function updateSeed(id, val) {
|
||||
// console.log(val)
|
||||
if (window._appData.data[id].inputs.seed && !Array.isArray(window._appData.data[id].inputs.seed)) window._appData.data[id].inputs.seed = Math.round(val);
|
||||
if (window._appData.data[id].inputs.noise_seed && !Array.isArray(window._appData.data[id].inputs.noise_seed)) window._appData.data[id].inputs.noise_seed = Math.round(val);
|
||||
if (window._appData.data
|
||||
&& window._appData.data[id].inputs.seed
|
||||
&& !Array.isArray(window._appData.data[id].inputs.seed)) window._appData.data[id].inputs.seed = Math.round(val);
|
||||
if (window._appData.data
|
||||
&& window._appData.data[id].inputs.noise_seed
|
||||
&& !Array.isArray(window._appData.data[id].inputs.noise_seed)) window._appData.data[id].inputs.noise_seed = Math.round(val);
|
||||
|
||||
//更新输入参数
|
||||
updateInputData(id, (inputs) => {
|
||||
inputs.seed = Math.round(val);
|
||||
inputs.noise_seed = Math.round(val);
|
||||
return inputs
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
//更新输入参数
|
||||
function updateInputData(nodeId, callback) {
|
||||
for (let index = 0; index < window._appData.input.length; index++) {
|
||||
const inp = window._appData.input[index];
|
||||
if (inp.id === nodeId) {
|
||||
window._appData.input[index].inputs = callback(window._appData.input[index].inputs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function queuePrompt(appInfo, promptWorkflow, seed, client_id) {
|
||||
// appinfo升级后 兼容,补丁
|
||||
@@ -1131,21 +1168,6 @@
|
||||
// 随机seed
|
||||
promptWorkflow = randomSeed(seed, promptWorkflow);
|
||||
|
||||
// //动态提示,改为输入的时候,手动触发
|
||||
// for (const id in promptWorkflow) {
|
||||
// let node = promptWorkflow[id]
|
||||
// if (["TextInput_", "CLIPTextEncode", "PromptSimplification", "ChinesePrompt_Mix"].includes(
|
||||
// node.class_type
|
||||
// )) {
|
||||
// if (node.class_type == "PromptSimplification") {
|
||||
// promptWorkflow[id].inputs.prompt = dynamicPrompts(node.inputs.prompt);
|
||||
// } else {
|
||||
// promptWorkflow[id].inputs.text = dynamicPrompts(node.inputs.text);
|
||||
// }
|
||||
// console.log('#动态提示', promptWorkflow[id].inputs)
|
||||
// }
|
||||
// }
|
||||
|
||||
let url = get_url()
|
||||
const data = JSON.stringify({ prompt: promptWorkflow, client_id });
|
||||
fetch(`${url}/prompt`, {
|
||||
@@ -1170,6 +1192,24 @@
|
||||
}
|
||||
|
||||
|
||||
// 新的运行工作流的接口
|
||||
function queuePromptNew(filename, category, seed, input, client_id) {
|
||||
let url = get_url()
|
||||
// var filename = "Text-to-Image_1.json", category = "";
|
||||
|
||||
// 随机seed
|
||||
// promptWorkflow = randomSeed(seed, promptWorkflow);
|
||||
|
||||
const data = JSON.stringify({ filename, category, seed, input, client_id });
|
||||
fetch(`${url}/mixlab/prompt`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: data,
|
||||
})
|
||||
}
|
||||
|
||||
function success(isSuccess, btn, text) {
|
||||
isSuccess ? btn.innerText = 'success' : text;
|
||||
setTimeout(() => {
|
||||
@@ -1488,7 +1528,7 @@
|
||||
// // 获取读取的文件内容,即 Blob 对象
|
||||
let hashId = await calculateImageHash(fileBlob)
|
||||
|
||||
if (hashId == window._appData.data[data.id].hashId) return
|
||||
if (window._appData.data && hashId == window._appData.data[data.id].hashId) return
|
||||
|
||||
let base64 = await blobToBase64(fileBlob)
|
||||
|
||||
@@ -1498,10 +1538,16 @@
|
||||
let { url, name } = await uploadImage(fileBlob);
|
||||
// 在这里可以对 Blob 对象进行进一步处理
|
||||
imageElement.src = url;
|
||||
window._appData.data[data.id].inputs.image = name;
|
||||
window._appData.data[data.id].hashId = hashId;
|
||||
if (window._appData.data && window._appData.data[data.id]) window._appData.data[data.id].inputs.image = name;
|
||||
if (window._appData.data && window._appData.data[data.id]) window._appData.data[data.id].hashId = hashId;
|
||||
console.log("上传的文件:", url, data.id, name);
|
||||
|
||||
//更新输入参数
|
||||
updateInputData(data.id, (inputs) => {
|
||||
inputs.image = name;
|
||||
return inputs
|
||||
})
|
||||
|
||||
//更换option里的default image
|
||||
window._appData.input = Array.from(window._appData.input, inp => {
|
||||
if (inp.id === data.id) {
|
||||
@@ -1792,11 +1838,26 @@
|
||||
data.options.images,
|
||||
data.inputs.imageIndex,
|
||||
(base64, text) => {
|
||||
window._appData.data[data.id].inputs.image_base64 = base64;
|
||||
window._appData.data[data.id].inputs.text = text;
|
||||
if (window._appData.data && window._appData.data[data.id]) window._appData.data[data.id].inputs.image_base64 = base64;
|
||||
if (window._appData.data && window._appData.data[data.id]) window._appData.data[data.id].inputs.text = text;
|
||||
|
||||
//更新输入参数
|
||||
updateInputData(data.id, (inputs) => {
|
||||
inputs.image_base64 = base64;
|
||||
inputs.text = text;
|
||||
return inputs
|
||||
})
|
||||
|
||||
})
|
||||
uploadContainer.appendChild(imgDiv);
|
||||
window._appData.data[data.id].inputs.image_base64 = mainImage.querySelector('.images_prompt_main').src;
|
||||
if (window._appData.data && window._appData.data[data.id]) window._appData.data[data.id].inputs.image_base64 = mainImage.querySelector('.images_prompt_main').src;
|
||||
|
||||
//更新输入参数
|
||||
updateInputData(data.id, (inputs) => {
|
||||
inputs.image_base64 = mainImage.querySelector('.images_prompt_main').src;
|
||||
return inputs
|
||||
})
|
||||
|
||||
} else if (data.class_type === 'LoadImagesToBatch') {
|
||||
// 多张base64 图片
|
||||
let base64 = data.inputs.images.base64
|
||||
@@ -1836,7 +1897,7 @@
|
||||
// console.log( file.type.split('/')[1])
|
||||
let hashId = await calculateImageHash(fileBlob)
|
||||
|
||||
if (hashId == window._appData.data[data.id].hashId) return
|
||||
if (window._appData.data && hashId == window._appData.data[data.id].hashId) return
|
||||
|
||||
if (data.class_type === 'LoadImagesToBatch') {
|
||||
// 上传 ,转为base64
|
||||
@@ -1849,8 +1910,16 @@
|
||||
let base64 = await parseImageToBase64(url);
|
||||
|
||||
if (data.class_type === 'ImagesPrompt_') {
|
||||
uploadContainer.querySelector('.images_prompt_main').src = base64
|
||||
window._appData.data[data.id].inputs.image_base64 = base64;
|
||||
uploadContainer.querySelector('.images_prompt_main').src = base64;
|
||||
|
||||
if (window._appData.data && window._appData.data[data.id]) window._appData.data[data.id].inputs.image_base64 = base64;
|
||||
|
||||
//更新输入参数
|
||||
updateInputData(data.id, (inputs) => {
|
||||
inputs.image_base64 = base64;
|
||||
return inputs
|
||||
})
|
||||
|
||||
} else {
|
||||
if (isVideoUpload) {
|
||||
imageElement.srcObject = null;
|
||||
@@ -1859,7 +1928,14 @@
|
||||
imageElement.src = url;
|
||||
|
||||
if (isVideoUpload) {
|
||||
window._appData.data[data.id].inputs.video = name;
|
||||
if (window._appData.data && window._appData.data[data.id]) window._appData.data[data.id].inputs.video = name;
|
||||
|
||||
//更新输入参数
|
||||
updateInputData(data.id, (inputs) => {
|
||||
inputs.video = name;
|
||||
return inputs
|
||||
})
|
||||
|
||||
} else {
|
||||
//更换option里的default image
|
||||
window._appData.input = Array.from(window._appData.input, inp => {
|
||||
@@ -1869,13 +1945,19 @@
|
||||
return inp
|
||||
})
|
||||
|
||||
window._appData.data[data.id].inputs.image = name;
|
||||
if (window._appData.data && window._appData.data[data.id]) window._appData.data[data.id].inputs.image = name;
|
||||
//更新输入参数
|
||||
updateInputData(data.id, (inputs) => {
|
||||
inputs.image = name;
|
||||
return inputs
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
window._appData.data[data.id].hashId = hashId;
|
||||
if (window._appData.data) window._appData.data[data.id].hashId = hashId;
|
||||
|
||||
console.log("上传的文件:", url, data.id, name);
|
||||
}
|
||||
@@ -1915,7 +1997,12 @@
|
||||
let silde = createNumSlide(label,
|
||||
data.inputs.weight,
|
||||
(v) => {
|
||||
window._appData.data[data.id].inputs.weight = parseFloat(v);
|
||||
if (window._appData.data) window._appData.data[data.id].inputs.weight = parseFloat(v);
|
||||
//更新输入参数
|
||||
updateInputData(data.id, (inputs) => {
|
||||
inputs.weight = parseFloat(v);
|
||||
return inputs
|
||||
})
|
||||
},
|
||||
options.min,
|
||||
options.max,
|
||||
@@ -1939,7 +2026,14 @@
|
||||
data.inputs.number,
|
||||
(v) => {
|
||||
// console.log(data.id,window._appData.data[data.id])
|
||||
window._appData.data[data.id].inputs.number = data.class_type === 'IntNumber' ? parseInt(v) : parseFloat(v);
|
||||
if (window._appData.data) window._appData.data[data.id].inputs.number = data.class_type === 'IntNumber' ? parseInt(v) : parseFloat(v);
|
||||
|
||||
//更新输入参数
|
||||
updateInputData(data.id, (inputs) => {
|
||||
inputs.number = data.class_type === 'IntNumber' ? parseInt(v) : parseFloat(v);
|
||||
return inputs
|
||||
})
|
||||
|
||||
},
|
||||
options.min,
|
||||
options.max,
|
||||
@@ -1979,9 +2073,21 @@
|
||||
textInput.style.height = height;
|
||||
|
||||
if (data.class_type == "PromptSimplification") {
|
||||
window._appData.data[data.id].inputs.prompt = textInput.value;
|
||||
if (window._appData.data) window._appData.data[data.id].inputs.prompt = textInput.value;
|
||||
//更新输入参数
|
||||
updateInputData(data.id, (inputs) => {
|
||||
inputs.prompt = textInput.value;
|
||||
return inputs
|
||||
})
|
||||
} else {
|
||||
window._appData.data[data.id].inputs.text = textInput.value;
|
||||
if (window._appData.data) window._appData.data[data.id].inputs.text = textInput.value;
|
||||
|
||||
//更新输入参数
|
||||
updateInputData(data.id, (inputs) => {
|
||||
inputs.text = textInput.value;
|
||||
return inputs
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
@@ -2005,9 +2111,19 @@
|
||||
textInput.setAttribute('title', prompt)
|
||||
dynamicPromptsBtn.setAttribute('title', prompt)
|
||||
if (data.class_type == "PromptSimplification") {
|
||||
window._appData.data[data.id].inputs.prompt = prompt;
|
||||
if (window._appData.data) window._appData.data[data.id].inputs.prompt = prompt;
|
||||
//更新输入参数
|
||||
updateInputData(data.id, (inputs) => {
|
||||
inputs.prompt = prompt;
|
||||
return inputs
|
||||
})
|
||||
} else {
|
||||
window._appData.data[data.id].inputs.text = prompt;
|
||||
if (window._appData.data) window._appData.data[data.id].inputs.text = prompt;
|
||||
//更新输入参数
|
||||
updateInputData(data.id, (inputs) => {
|
||||
inputs.text = prompt;
|
||||
return inputs
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
@@ -2022,9 +2138,19 @@
|
||||
autoResize(textInput);
|
||||
|
||||
if (data.class_type == "PromptSimplification") {
|
||||
window._appData.data[data.id].inputs.prompt = textInput.value;
|
||||
if (window._appData.data) window._appData.data[data.id].inputs.prompt = textInput.value;
|
||||
//更新输入参数
|
||||
updateInputData(data.id, (inputs) => {
|
||||
inputs.prompt = textInput.value;
|
||||
return inputs
|
||||
})
|
||||
} else {
|
||||
window._appData.data[data.id].inputs.text = textInput.value;
|
||||
if (window._appData.data) window._appData.data[data.id].inputs.text = textInput.value;
|
||||
//更新输入参数
|
||||
updateInputData(data.id, (inputs) => {
|
||||
inputs.text = textInput.value;
|
||||
return inputs
|
||||
})
|
||||
}
|
||||
localStorage.setItem(`t_${data.id}`, JSON.stringify({
|
||||
value: textInput.value,
|
||||
@@ -2071,10 +2197,24 @@
|
||||
if (v) {
|
||||
value = v;
|
||||
if (data.class_type === 'CheckpointLoaderSimple') {
|
||||
window._appData.data[data.id].inputs.ckpt_name = value;
|
||||
|
||||
if (window._appData.data) window._appData.data[data.id].inputs.ckpt_name = value;
|
||||
//更新输入参数
|
||||
updateInputData(data.id, (inputs) => {
|
||||
inputs.ckpt_name = value;
|
||||
return inputs
|
||||
})
|
||||
|
||||
}
|
||||
if (data.class_type === 'LoraLoader') {
|
||||
window._appData.data[data.id].inputs.lora_name = value;
|
||||
|
||||
if (window._appData.data) window._appData.data[data.id].inputs.lora_name = value;
|
||||
//更新输入参数
|
||||
updateInputData(data.id, (inputs) => {
|
||||
inputs.lora_name = value;
|
||||
return inputs
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -2094,10 +2234,20 @@
|
||||
e.preventDefault();
|
||||
// console.log(selectDom.value)
|
||||
if (data.class_type === 'CheckpointLoaderSimple') {
|
||||
window._appData.data[data.id].inputs.ckpt_name = selectDom.value;
|
||||
if (window._appData.data) window._appData.data[data.id].inputs.ckpt_name = selectDom.value;
|
||||
//更新输入参数
|
||||
updateInputData(nodeId, (inputs) => {
|
||||
inputs.ckpt_name = selectDom.value;
|
||||
return inputs
|
||||
})
|
||||
}
|
||||
if (data.class_type === 'LoraLoader') {
|
||||
window._appData.data[data.id].inputs.lora_name = selectDom.value;
|
||||
if (window._appData.data) window._appData.data[data.id].inputs.lora_name = selectDom.value;
|
||||
//更新输入参数
|
||||
updateInputData(nodeId, (inputs) => {
|
||||
inputs.lora_name = selectDom.value;
|
||||
return inputs
|
||||
})
|
||||
}
|
||||
|
||||
localStorage.setItem(`_model_${data.id}_${data.class_type}`, selectDom.value)
|
||||
@@ -2161,7 +2311,15 @@
|
||||
|
||||
audioE.src = base64;
|
||||
|
||||
window._appData.data[data.id].inputs.audios.base64 = [base64];
|
||||
if (window._appData.data) window._appData.data[data.id].inputs.audios.base64 = [base64];
|
||||
|
||||
//更新输入参数
|
||||
updateInputData(data.id, (inputs) => {
|
||||
inputs.audios.base64 = [base64];
|
||||
return inputs
|
||||
})
|
||||
|
||||
|
||||
})
|
||||
|
||||
inp.click()
|
||||
@@ -2225,11 +2383,26 @@
|
||||
// console.log(color)
|
||||
// window._appData.data[data.id].inputs.color.hex = color.toHEXA().toString();
|
||||
let [r, g, b, a] = color.toRGBA();
|
||||
window._appData.data[nodeId].inputs.color = {
|
||||
...window._appData.data[nodeId].inputs.color,
|
||||
r = parseInt(r);
|
||||
g = parseInt(g);
|
||||
b = parseInt(b);
|
||||
|
||||
if (window._appData.data) window._appData.data[nodeId].inputs.color = {
|
||||
// ...window._appData.data[nodeId].inputs.color,
|
||||
r, g, b, a,
|
||||
hex: color.toHEXA().toString()
|
||||
}
|
||||
};
|
||||
|
||||
//更新输入参数 //todo color里的参数
|
||||
updateInputData(nodeId, (inputs) => {
|
||||
inputs.color = {
|
||||
// ...window._appData.data[nodeId].inputs.color,
|
||||
r, g, b, a,
|
||||
hex: color.toHEXA().toString()
|
||||
};
|
||||
return inputs
|
||||
})
|
||||
|
||||
} catch (error) { }
|
||||
})
|
||||
.on('cancel', instance => {
|
||||
@@ -2308,7 +2481,14 @@
|
||||
|
||||
let defaultValue = (targetId ? localStorage.getItem(`_slide_${targetId}`) : '') || keywords[0];
|
||||
|
||||
window._appData.data[targetId].inputs.prompt_keyword = defaultValue;
|
||||
if (window._appData.data) window._appData.data[targetId].inputs.prompt_keyword = defaultValue;
|
||||
|
||||
//更新输入参数 //todo color里的参数
|
||||
updateInputData(targetId, (inputs) => {
|
||||
inputs.prompt_keyword = defaultValue;
|
||||
return inputs
|
||||
})
|
||||
|
||||
|
||||
let selectTag = createSelect(Array.from(keywords, (k, i) => {
|
||||
return {
|
||||
@@ -2325,7 +2505,13 @@
|
||||
// selectTag.setAttribute('data-content',labelText);
|
||||
selectTag.addEventListener('change', e => {
|
||||
e.preventDefault();
|
||||
window._appData.data[targetId].inputs.prompt_keyword = selectTag.value;
|
||||
if (window._appData.data) window._appData.data[targetId].inputs.prompt_keyword = selectTag.value;
|
||||
|
||||
//更新输入参数 //todo color里的参数
|
||||
updateInputData(targetId, (inputs) => {
|
||||
inputs.prompt_keyword = selectTag.value;
|
||||
return inputs
|
||||
})
|
||||
|
||||
targetId ? localStorage.setItem(`_slide_${targetId}`, selectTag.value) : ''
|
||||
})
|
||||
@@ -2501,6 +2687,7 @@
|
||||
async function createUI(data, share = true) {
|
||||
// appData.input, appData.output, appData.seed, share, appData.link
|
||||
if (!data) return
|
||||
// console.log('#createUI', data)
|
||||
const { input: inputData, output: outputData, data: workflow, seed, seedTitle, link, name } = data;
|
||||
|
||||
let mainDiv = document.createElement('div');
|
||||
@@ -2584,6 +2771,7 @@
|
||||
// seeds.textContent = 'Status';
|
||||
seeds.className = 'seeds';
|
||||
|
||||
console.log('#createUI', seed, data.data)
|
||||
try {
|
||||
if (Object.keys(seed).length > 0) {
|
||||
seeds.innerHTML = `<summary>SEED</summary>
|
||||
@@ -2591,7 +2779,8 @@
|
||||
const content = seeds.querySelector('.content')
|
||||
for (const id in seed) {
|
||||
const s = seed[id];
|
||||
if (!Array.isArray(workflow[id].inputs.seed)) {
|
||||
console.log('#createUI', !!(workflow && !Array.isArray(workflow[id].inputs.seed) || !workflow))
|
||||
if (!!(workflow && !Array.isArray(workflow[id].inputs.seed) || !workflow)) {
|
||||
|
||||
let seedInput = document.createElement('div');
|
||||
content.appendChild(seedInput)
|
||||
@@ -2670,7 +2859,7 @@
|
||||
leftDiv.appendChild(input1);
|
||||
|
||||
submitDivBtn.appendChild(submitButton);
|
||||
if (typeof (data.data) == 'object') mainDiv.appendChild(submitDiv);
|
||||
mainDiv.appendChild(submitDiv);
|
||||
|
||||
rightDiv.appendChild(output);
|
||||
|
||||
@@ -3041,13 +3230,22 @@
|
||||
ui.submitButton.update(
|
||||
() => {
|
||||
// 在提交按钮点击时执行的逻辑
|
||||
queuePrompt({
|
||||
name: window._appData.name,
|
||||
id: window._appData.id,
|
||||
icon: window._appData.icon,
|
||||
category: window._appData.category,
|
||||
filename: window._appData.filename
|
||||
}, window._appData.data, window._appData.seed, api.clientId);
|
||||
// queuePrompt({
|
||||
// name: window._appData.name,
|
||||
// id: window._appData.id,
|
||||
// icon: window._appData.icon,
|
||||
// category: window._appData.category,
|
||||
// filename: window._appData.filename
|
||||
// }, window._appData.data, window._appData.seed, api.clientId);
|
||||
|
||||
queuePromptNew(
|
||||
window._appData.filename,
|
||||
window._appData.category,
|
||||
window._appData.seed,
|
||||
window._appData.input.filter(inp => inp),
|
||||
api.clientId
|
||||
)
|
||||
|
||||
}, () => {
|
||||
// 取消
|
||||
if (api.runningCancel) {
|
||||
|
||||
@@ -466,19 +466,18 @@ app.registerExtension({
|
||||
const { input, output } = getInputsAndOutputs()
|
||||
input_ids.value = input.join('\n')
|
||||
output_ids.value = output.join('\n')
|
||||
|
||||
const widget = {
|
||||
type: 'div',
|
||||
name: 'AppInfoRun',
|
||||
draw (ctx, node, widget_width, y, widget_height) {
|
||||
Object.assign(
|
||||
this.div.style,
|
||||
get_position_style(
|
||||
{...get_position_style(
|
||||
ctx,
|
||||
widget_width,
|
||||
node.size[1] - widget_height,
|
||||
node.size[1]
|
||||
)
|
||||
),zIndex:1}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -555,6 +555,7 @@ app.registerExtension({
|
||||
e.preventDefault()
|
||||
let inputAudio = document.createElement('input')
|
||||
inputAudio.type = 'file'
|
||||
inputAudio.accept = "audio/*"
|
||||
inputAudio.style.display = 'none'
|
||||
inputAudio.addEventListener('change', async e => {
|
||||
e.preventDefault()
|
||||
|
||||
@@ -3,7 +3,7 @@ import { app } from '../../../scripts/app.js'
|
||||
const repoOwner = 'shadowcz007' // 替换为仓库的所有者
|
||||
const repoName = 'comfyui-mixlab-nodes' // 替换为仓库的名称
|
||||
|
||||
const version = 'v0.30.3'
|
||||
const version = 'v0.32.0'
|
||||
|
||||
fetch(`https://api.github.com/repos/${repoOwner}/${repoName}/releases/latest`)
|
||||
.then(response => response.json())
|
||||
|
||||
@@ -199,6 +199,120 @@ app.registerExtension({
|
||||
url[id] || 'https://api.openai.com/v1'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
app.registerExtension({
|
||||
name: 'Mixlab.GPT.SiliconflowLLM',
|
||||
async getCustomWidgets (app) {
|
||||
return {
|
||||
KEY (node, inputName, inputData, app) {
|
||||
// console.log('##inputData', inputData)
|
||||
const widget = {
|
||||
type: inputData[0], // the type, CHEESE
|
||||
name: inputName, // the name, slice
|
||||
size: [128, 32], // a default size
|
||||
draw (ctx, node, width, y) {},
|
||||
computeSize (...args) {
|
||||
return [128, 32] // a method to compute the current size of the widget
|
||||
},
|
||||
async serializeValue (nodeId, widgetIndex) {
|
||||
let data = getLocalData('_mixlab_api_key')
|
||||
return data[node.id] || 'by Mixlab'
|
||||
}
|
||||
}
|
||||
// widget.something = something; // maybe adds stuff to it
|
||||
node.addCustomWidget(widget) // adds it to the node
|
||||
return widget // and returns it.
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
async beforeRegisterNodeDef (nodeType, nodeData, app) {
|
||||
if (nodeType.comfyClass == 'SiliconflowLLM') {
|
||||
const orig_nodeCreated = nodeType.prototype.onNodeCreated
|
||||
nodeType.prototype.onNodeCreated = function () {
|
||||
orig_nodeCreated?.apply(this, arguments)
|
||||
|
||||
const api_key = this.widgets.filter(w => w.name == 'api_key')[0]
|
||||
|
||||
const widget = {
|
||||
type: 'div',
|
||||
name: 'chatgptdiv',
|
||||
draw (ctx, node, widget_width, y, widget_height) {
|
||||
Object.assign(
|
||||
this.div.style,
|
||||
get_position_style(ctx, widget_width, api_key.y, node.size[1])
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
widget.div = $el('div', {})
|
||||
|
||||
document.body.appendChild(widget.div)
|
||||
|
||||
const inputDiv = (key, placeholder) => {
|
||||
let div = document.createElement('div')
|
||||
const ip = document.createElement('input')
|
||||
ip.type = placeholder === 'Key' ? 'password' : 'text'
|
||||
ip.className = `${'comfy-multiline-input'} ${placeholder}`
|
||||
div.style = `display: flex;
|
||||
align-items: center;
|
||||
margin: 6px 8px;
|
||||
margin-top: 0;`
|
||||
ip.placeholder = placeholder
|
||||
ip.value = placeholder
|
||||
|
||||
ip.style = `margin-left: 24px;
|
||||
outline: none;
|
||||
border: none;
|
||||
padding: 4px;width: 100%;`
|
||||
const label = document.createElement('label')
|
||||
label.style = 'font-size: 10px;min-width:32px'
|
||||
label.innerText = placeholder
|
||||
div.appendChild(label)
|
||||
div.appendChild(ip)
|
||||
|
||||
ip.addEventListener('change', () => {
|
||||
let data = getLocalData(key)
|
||||
data[this.id] = ip.value.trim()
|
||||
localStorage.setItem(key, JSON.stringify(data))
|
||||
console.log(this.id, key)
|
||||
})
|
||||
return div
|
||||
}
|
||||
|
||||
let inputKey = inputDiv('_mixlab_api_key', 'Key')
|
||||
|
||||
widget.div.appendChild(inputKey)
|
||||
|
||||
this.addCustomWidget(widget)
|
||||
|
||||
const onRemoved = this.onRemoved
|
||||
this.onRemoved = () => {
|
||||
inputKey.remove()
|
||||
widget.div.remove()
|
||||
return onRemoved?.()
|
||||
}
|
||||
|
||||
this.serialize_widgets = true //需要保存参数
|
||||
}
|
||||
}
|
||||
},
|
||||
async loadedGraphNode (node, app) {
|
||||
// Fires every time a node is constructed
|
||||
// You can modify widgets/add handlers/etc here
|
||||
|
||||
if (node.type === 'SiliconflowLLM') {
|
||||
let widget = node.widgets.filter(w => w.div)[0]
|
||||
|
||||
let apiKey = getLocalData('_mixlab_api_key');
|
||||
|
||||
let id = node.id
|
||||
|
||||
widget.div.querySelector('.Key').value = apiKey[id] || 'by Mixlab'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
app.registerExtension({
|
||||
|
||||
@@ -100,7 +100,7 @@ async function start_llama (model = 'Phi-3-mini-4k-instruct-Q5_K_S.gguf') {
|
||||
})
|
||||
|
||||
const data = await response.json()
|
||||
if (data.llama_cpp_error) {
|
||||
if (data.llama_cpp_error||!data.port) {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -468,6 +468,8 @@ app.registerExtension({
|
||||
const prefix = 'vhs_gif_preview_'
|
||||
const r = onExecuted ? onExecuted.apply(this, message) : undefined
|
||||
|
||||
if(!this.widgets) this.widgets=[]
|
||||
|
||||
if (this.widgets) {
|
||||
const pos = this.widgets.findIndex(w => w.name === `${prefix}_0`)
|
||||
if (pos !== -1) {
|
||||
@@ -488,6 +490,7 @@ app.registerExtension({
|
||||
params.format || 'image/gif'
|
||||
)
|
||||
)
|
||||
console.log(w)
|
||||
w.parent = this
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user