Compare commits
49
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5d864e7ea2 | ||
|
|
7a1c91a2d5 | ||
|
|
1339c8a1f4 | ||
|
|
27025579d6 | ||
|
|
473d70f818 | ||
|
|
907a5d8d7e | ||
|
|
28c349fd7e | ||
|
|
df9545015f | ||
|
|
07b0b94e46 | ||
|
|
92f85b6d9c | ||
|
|
6919eadb21 | ||
|
|
be76280fd2 | ||
|
|
6b673bdd44 | ||
|
|
c6591db45e | ||
|
|
5813ebaa1c | ||
|
|
743e752b0f | ||
|
|
707cd28cb7 | ||
|
|
ecdb687c25 | ||
|
|
ba68d2dd45 | ||
|
|
d8259de52f | ||
|
|
07cec3566b | ||
|
|
dd8c531889 | ||
|
|
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 |
+2
-1
@@ -3,4 +3,5 @@ https/
|
||||
nodes/config.json
|
||||
workflow/my_workflow.json
|
||||
workflow/my_workflow_app.json
|
||||
app/*
|
||||
workflow/prompt_result.json
|
||||
app/*
|
||||
|
||||
@@ -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)
|
||||
@@ -45,7 +45,12 @@ APP-JSON:
|
||||
|
||||
> seed统一输入控件,支持:SamplerCustom、KSampler
|
||||
|
||||
> [ps插件](https://github.com/shadowcz007/comfyui-ps-plugin)
|
||||
> 配套[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
|
||||
@@ -68,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
|
||||
|
||||

|
||||
|
||||
@@ -163,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
|
||||
@@ -226,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)
|
||||
|
||||
|
||||
+67
-4
@@ -6,7 +6,7 @@ import sys,json
|
||||
import urllib
|
||||
import hashlib
|
||||
import datetime
|
||||
|
||||
import folder_paths
|
||||
|
||||
python = sys.executable
|
||||
|
||||
@@ -252,7 +252,8 @@ def get_my_workflow_for_app(filename="my_workflow_app.json",category="",is_all=F
|
||||
"name":x['app']['name'],
|
||||
"version":x['app']['version'],
|
||||
"input":input,
|
||||
"output":output
|
||||
"output":output,
|
||||
"id":x['app']['id']
|
||||
}
|
||||
},
|
||||
"date":item["date"]
|
||||
@@ -300,7 +301,8 @@ def get_my_workflow_for_app(filename="my_workflow_app.json",category="",is_all=F
|
||||
"name":x['app']['name'],
|
||||
"version":x['app']['version'],
|
||||
"input":input,
|
||||
"output":output
|
||||
"output":output,
|
||||
"id":x['app']['id']
|
||||
}
|
||||
},
|
||||
"date":item["date"]
|
||||
@@ -308,6 +310,31 @@ def get_my_workflow_for_app(filename="my_workflow_app.json",category="",is_all=F
|
||||
|
||||
return apps
|
||||
|
||||
# 历史记录
|
||||
def save_prompt_result(id,data):
|
||||
prompt_result_path=os.path.join(current_path, "workflow/prompt_result.json")
|
||||
prompt_result={}
|
||||
if os.path.exists(prompt_result_path):
|
||||
with open(prompt_result_path) as json_file:
|
||||
prompt_result = json.load(json_file)
|
||||
|
||||
prompt_result[id]=data
|
||||
|
||||
with open(prompt_result_path, 'w') as file:
|
||||
json.dump(prompt_result, file)
|
||||
return prompt_result_path
|
||||
|
||||
def get_prompt_result():
|
||||
prompt_result_path=os.path.join(current_path, "workflow/prompt_result.json")
|
||||
prompt_result={}
|
||||
if os.path.exists(prompt_result_path):
|
||||
with open(prompt_result_path) as json_file:
|
||||
prompt_result = json.load(json_file)
|
||||
res=list(prompt_result.values())
|
||||
# print(res)
|
||||
return res
|
||||
|
||||
|
||||
def save_workflow_json(data):
|
||||
workflow_path=os.path.join(current_path, "workflow/my_workflow.json")
|
||||
with open(workflow_path, 'w') as file:
|
||||
@@ -518,6 +545,40 @@ async def nodes_map_hander(request):
|
||||
return web.json_response(result)
|
||||
|
||||
|
||||
@routes.post("/mixlab/folder_paths")
|
||||
async def get_checkpoints(request):
|
||||
data = await request.json()
|
||||
t="checkpoints"
|
||||
try:
|
||||
t=data['type']
|
||||
except Exception as e:
|
||||
print('/mixlab/folder_paths',False,e)
|
||||
|
||||
names = folder_paths.get_filename_list(t)
|
||||
|
||||
return web.json_response({"names":names,"types":list(folder_paths.folder_names_and_paths.keys())})
|
||||
|
||||
|
||||
@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})
|
||||
|
||||
|
||||
|
||||
|
||||
# 扩展api接口
|
||||
# from server import PromptServer
|
||||
# from aiohttp import web
|
||||
@@ -535,7 +596,7 @@ 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_,TESTNODE_TOKEN,AppInfo,IntNumber,FloatSlider,TextInput,ColorInput,FontInput,TextToNumber,DynamicDelayProcessor,LimitNumber,SwitchByIndex,MultiplicationNode
|
||||
from .nodes.Mask import OutlineMask,FeatheredMask
|
||||
@@ -548,6 +609,7 @@ NODE_CLASS_MAPPINGS = {
|
||||
"TESTNODE_":TESTNODE_,
|
||||
"TESTNODE_TOKEN":TESTNODE_TOKEN,
|
||||
"RandomPrompt":RandomPrompt,
|
||||
# "LoraPrompt":LoraPrompt,
|
||||
"EmbeddingPrompt":EmbeddingPrompt,
|
||||
"PromptSlide":PromptSlide,
|
||||
"PromptSimplification":PromptSimplification,
|
||||
@@ -584,6 +646,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("目录已创建")
|
||||
|
||||
+133
-2
@@ -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')
|
||||
@@ -15,6 +21,7 @@ from PIL.PngImagePlugin import PngInfo
|
||||
embeddings_path=os.path.join(folder_paths.models_dir, "embeddings")
|
||||
|
||||
def get_files_with_extension(directory, extension):
|
||||
|
||||
file_list = []
|
||||
for root, dirs, files in os.walk(directory):
|
||||
for file in files:
|
||||
@@ -28,6 +35,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,16 +436,92 @@ 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(','),)
|
||||
|
||||
|
||||
import folder_paths
|
||||
class EmbeddingPrompt:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
|
||||
return {
|
||||
"required": {
|
||||
"embedding":(get_files_with_extension(embeddings_path,'.pt'),),
|
||||
"embedding":(folder_paths.get_filename_list("embeddings"),),
|
||||
"weight": ("FLOAT", {"default": 1, "min": -2, "max": 2,"step":0.01 ,"display": "slider"}),
|
||||
},
|
||||
|
||||
@@ -426,7 +553,7 @@ class JoinWithDelimiter:
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"text_list": (any_type,),
|
||||
"delimiter":(["newline","comma"],),
|
||||
"delimiter":(["newline","comma","backslash","space"],),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -445,6 +572,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)
|
||||
|
||||
+136
-15
@@ -62,7 +62,14 @@ except:
|
||||
|
||||
|
||||
|
||||
def translate(zh_en_tokenizer,zh_en_model,text):
|
||||
def translate(text):
|
||||
global text_pipe,zh_en_model,zh_en_tokenizer
|
||||
|
||||
if zh_en_model==None:
|
||||
zh_en_model = AutoModelForSeq2SeqLM.from_pretrained(zh_en_model_path).eval()
|
||||
zh_en_tokenizer = AutoTokenizer.from_pretrained(zh_en_model_path,padding=True, truncation=True)
|
||||
|
||||
zh_en_model.to("cuda" if torch.cuda.is_available() else "cpu")
|
||||
with torch.no_grad():
|
||||
encoded = zh_en_tokenizer([text], return_tensors="pt")
|
||||
encoded.to(zh_en_model.device)
|
||||
@@ -104,16 +111,22 @@ import re
|
||||
|
||||
def correct_prompt_syntax(prompt):
|
||||
|
||||
print("input prompt",prompt)
|
||||
# print("input prompt",prompt)
|
||||
corrected_elements = []
|
||||
# 处理成统一的英文标点
|
||||
prompt = prompt.replace('(', '(').replace(')', ')').replace(',', ',').replace(';', ',').replace('。', '.').replace(':',':')
|
||||
# 删除多余的空格
|
||||
prompt = re.sub(r'\s+', ' ', prompt).strip()
|
||||
prompt = prompt.replace("< ","<").replace(" >",">").replace("( ","(").replace(" )",")").replace("[ ","[").replace(' ]',']')
|
||||
|
||||
# 分词
|
||||
prompt_elements = prompt.split(',')
|
||||
|
||||
def balance_brackets(element, open_bracket, close_bracket):
|
||||
open_brackets_count = element.count(open_bracket)
|
||||
close_brackets_count = element.count(close_bracket)
|
||||
return element + close_bracket * (open_brackets_count - close_brackets_count)
|
||||
|
||||
for element in prompt_elements:
|
||||
element = element.strip()
|
||||
|
||||
@@ -133,21 +146,118 @@ def correct_prompt_syntax(prompt):
|
||||
corrected_elements.append(corrected_element)
|
||||
|
||||
# 重组修正后的prompt
|
||||
corrected_prompt = ', '.join(corrected_elements)
|
||||
print("output prompt",corrected_prompt)
|
||||
return corrected_prompt
|
||||
return ','.join(corrected_elements)
|
||||
|
||||
def balance_brackets(element, open_bracket, close_bracket):
|
||||
open_brackets_count = element.count(open_bracket)
|
||||
close_brackets_count = element.count(close_bracket)
|
||||
return element + close_bracket * (open_brackets_count - close_brackets_count)
|
||||
|
||||
# # 示例使用
|
||||
# test_prompt = "((middle-century castles)), [forsaken: 0.8], (mystery dragons: 1.3, mist forests, sunsets, quiet; (((dummy)), [fisting city: 0.5] background, radiant, soft and flavoured,] promising mountains, ((starry: 1.6), [[crowds], [middle-century castle: urban landscapes of the future: 0.5], [yellow: bright sun: 0.7], overlooking"
|
||||
# corrected_prompt = correct_prompt_syntax(test_prompt)
|
||||
# print(corrected_prompt)
|
||||
|
||||
def detect_language(input_str):
|
||||
# 统计中文和英文字符的数量
|
||||
count_cn = count_en = 0
|
||||
for char in input_str:
|
||||
if '\u4e00' <= char <= '\u9fff':
|
||||
count_cn += 1
|
||||
elif char.isalpha():
|
||||
count_en += 1
|
||||
|
||||
# 根据统计的字符数量判断主要语言
|
||||
if count_cn > count_en:
|
||||
return "cn"
|
||||
elif count_en > count_cn:
|
||||
return "en"
|
||||
else:
|
||||
return "unknow"
|
||||
|
||||
|
||||
|
||||
|
||||
#定义Prompt文法
|
||||
grammar = """
|
||||
start: sentence
|
||||
sentence: phrase ("," phrase)*
|
||||
phrase: emphasis | weight | word | lora | embedding | schedule
|
||||
emphasis: "(" sentence ")" -> emphasis
|
||||
| "[" sentence "]" -> weak_emphasis
|
||||
weight: "(" word ":" NUMBER ")"
|
||||
schedule: "[" word ":" word ":" NUMBER "]"
|
||||
lora: "<" WORD ":" WORD (":" NUMBER)? (":" NUMBER)? ">"
|
||||
embedding: "embedding" ":" WORD (":" NUMBER)? (":" NUMBER)?
|
||||
word: WORD
|
||||
|
||||
NUMBER: /\s*-?\d+(\.\d+)?\s*/
|
||||
WORD: /[^,:\(\)\[\]<>]+/
|
||||
"""
|
||||
|
||||
from lark import Lark, Transformer, v_args
|
||||
|
||||
@v_args(inline=True) # Decorator to flatten the tree directly into the function arguments
|
||||
class ChinesePromptTranslate(Transformer):
|
||||
|
||||
def sentence(self, *args):
|
||||
return ", ".join(args)
|
||||
|
||||
def phrase(self, *args):
|
||||
return "".join(args)
|
||||
|
||||
def emphasis(self, *args):
|
||||
# Reconstruct the emphasis with translated content
|
||||
return "(" + "".join(args) + ")"
|
||||
|
||||
def weak_emphasis(self, *args):
|
||||
print('weak_emphasis:',args)
|
||||
return "[" + "".join(args) + "]"
|
||||
|
||||
def embedding(self,*args):
|
||||
print('prompt embedding',args[0])
|
||||
if len(args) == 1:
|
||||
# print('prompt embedding',str(args[0]))
|
||||
# 只传递了一个参数,意味着只有embedding名称没有数字
|
||||
embedding_name = str(args[0])
|
||||
return f"embedding:{embedding_name}"
|
||||
elif len(args) > 1:
|
||||
embedding_name,*numbers = args
|
||||
|
||||
if len(numbers)==2:
|
||||
return f"embedding:{embedding_name}:{numbers[0]}:{numbers[1]}"
|
||||
elif len(numbers)==1:
|
||||
return f"embedding:{embedding_name}:{numbers[0]}"
|
||||
else:
|
||||
return f"embedding:{embedding_name}"
|
||||
|
||||
def lora(self,*args):
|
||||
print('lora prompt',*args)
|
||||
if len(args) == 1:
|
||||
return f"<lora:{loar_name}>"
|
||||
elif len(args) > 1:
|
||||
# print('lora', args)
|
||||
_,loar_name,*numbers = args
|
||||
loar_name = str(loar_name).strip()
|
||||
if len(numbers)==2:
|
||||
return f"<lora:{loar_name}:{numbers[0]}:{numbers[1]}>"
|
||||
elif len(numbers)==1:
|
||||
return f"<lora:{loar_name}:{numbers[0]}>"
|
||||
else:
|
||||
return f"<lora:{loar_name}>"
|
||||
|
||||
def weight(self, word,number):
|
||||
translated_word = translate(str(word)).rstrip('.')
|
||||
return f"({translated_word}:{str(number).strip()})"
|
||||
|
||||
def schedule(self,*args):
|
||||
print('prompt schedule',args)
|
||||
data = [str(arg).strip() for arg in args]
|
||||
|
||||
return f"[{':'.join(data)}]"
|
||||
|
||||
def word(self, word):
|
||||
# Translate each word using the dictionary
|
||||
if detect_language(str(word)) == "cn":
|
||||
return translate(str(word)).rstrip('.')
|
||||
else:
|
||||
return str(word).rstrip('.')
|
||||
|
||||
class ChinesePrompt:
|
||||
|
||||
@@ -185,16 +295,17 @@ class ChinesePrompt:
|
||||
zh_en_tokenizer=None
|
||||
|
||||
def run(self,text,seed,generation):
|
||||
global text_pipe,zh_en_model,zh_en_tokenizer
|
||||
|
||||
|
||||
seed=seed[0]
|
||||
generation=generation[0]
|
||||
|
||||
# 进度条
|
||||
pbar = comfy.utils.ProgressBar(len(text)+1)
|
||||
|
||||
texts = [correct_prompt_syntax(t) for t in text]
|
||||
print('correct_prompt_syntax::',texts)
|
||||
|
||||
|
||||
global text_pipe,zh_en_model,zh_en_tokenizer
|
||||
if zh_en_model==None:
|
||||
zh_en_model = AutoModelForSeq2SeqLM.from_pretrained(zh_en_model_path).eval()
|
||||
zh_en_tokenizer = AutoTokenizer.from_pretrained(zh_en_model_path,padding=True, truncation=True)
|
||||
@@ -210,9 +321,15 @@ class ChinesePrompt:
|
||||
|
||||
# print('zh_en_model device',zh_en_model.device,text_pipe.model.device,torch.cuda.current_device() )
|
||||
en_texts=[]
|
||||
|
||||
for t in texts:
|
||||
en_text=translate(zh_en_tokenizer,zh_en_model,t)
|
||||
en_texts.append(en_text)
|
||||
# translated_text = translated_word = translate(zh_en_tokenizer,zh_en_model,str(t))
|
||||
parser = Lark(grammar, start="start", parser="lalr", transformer=ChinesePromptTranslate())
|
||||
# print('t',t)
|
||||
result = parser.parse(t).children
|
||||
# print('en_result',result)
|
||||
# en_text=translate(zh_en_tokenizer,zh_en_model,text_without_syntax)
|
||||
en_texts.append(result[0])
|
||||
|
||||
zh_en_model.to('cpu')
|
||||
print("test en_text",en_texts)
|
||||
@@ -232,7 +349,9 @@ class ChinesePrompt:
|
||||
pbar.update(1)
|
||||
|
||||
text_pipe.model.to('cpu')
|
||||
prompt_result = [correct_prompt_syntax(p) for p in prompt_result]
|
||||
|
||||
print('prompt_result',prompt_result,)
|
||||
# prompt_result = [','.join(correct_prompt_syntax(p)) for p in prompt_result]
|
||||
|
||||
return {
|
||||
"ui":{
|
||||
@@ -241,6 +360,8 @@ class ChinesePrompt:
|
||||
"result": (prompt_result,)}
|
||||
|
||||
|
||||
|
||||
|
||||
class PromptGenerate:
|
||||
|
||||
global _available
|
||||
|
||||
+28
-12
@@ -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
|
||||
@@ -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),)
|
||||
|
||||
|
||||
|
||||
@@ -711,12 +713,26 @@ 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,)}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import folder_paths
|
||||
|
||||
# 外挂一个文件,用来编写新的节点
|
||||
def run(v):
|
||||
|
||||
output_dir = folder_paths.get_temp_directory()
|
||||
|
||||
print('1323',v,output_dir)
|
||||
+3
-1
@@ -6,4 +6,6 @@ matplotlib
|
||||
openai
|
||||
simple-lama-inpainting
|
||||
clip-interrogator==0.6.0
|
||||
transformers>=4.36.0
|
||||
transformers>=4.36.0
|
||||
zhipuai
|
||||
lark-parser
|
||||
+493
-119
@@ -11,12 +11,19 @@
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.app {
|
||||
display: flex;
|
||||
width: 90%;
|
||||
min-width: 400px;
|
||||
margin-left: 5%;
|
||||
user-select: none;
|
||||
margin-top: 32px;
|
||||
}
|
||||
|
||||
.apps {
|
||||
@@ -35,6 +42,16 @@
|
||||
|
||||
|
||||
.apps .card {
|
||||
width: 320px;
|
||||
margin: 12px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
background: #ffffff;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
#history_container .card {
|
||||
width: 200px;
|
||||
margin: 12px;
|
||||
display: flex;
|
||||
@@ -44,25 +61,25 @@
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.apps .selected {
|
||||
.selected {
|
||||
box-shadow: 0px 0px 10px 10px #fbe9f0
|
||||
}
|
||||
|
||||
.apps .card:hover {
|
||||
.card:hover {
|
||||
box-shadow: 0px 0px 10px 10px #e9fbfa
|
||||
}
|
||||
|
||||
.apps .card h5 {
|
||||
.card h5 {
|
||||
font-size: 14px;
|
||||
margin: 10px 0;
|
||||
margin: 0 0 10px 0
|
||||
}
|
||||
|
||||
.apps .card p {
|
||||
.card p {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
max-height: 35px;
|
||||
/* max-height: 35px; */
|
||||
overflow: hidden;
|
||||
width: 98px;
|
||||
/* width: 98px; */
|
||||
}
|
||||
|
||||
.apps .card img {
|
||||
@@ -71,14 +88,24 @@
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
.apps .card .item {
|
||||
#history_container .card img {
|
||||
width: auto;
|
||||
height: 100%;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
.toggle {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.card .item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.apps .card .icon {
|
||||
.card .icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
overflow: hidden;
|
||||
@@ -89,9 +116,8 @@
|
||||
}
|
||||
|
||||
|
||||
.apps .card .version {
|
||||
.card .version {
|
||||
font-size: 12px;
|
||||
|
||||
}
|
||||
|
||||
.card .toggle {
|
||||
@@ -142,6 +168,10 @@
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.description img {
|
||||
min-height: unset !important;
|
||||
}
|
||||
|
||||
.panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -172,6 +202,7 @@
|
||||
width: fit-content;
|
||||
max-width: 100%;
|
||||
margin-left: 12px;
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
.input_card {
|
||||
@@ -243,6 +274,7 @@
|
||||
border-radius: 100%;
|
||||
cursor: pointer;
|
||||
border: 3px solid;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
@@ -374,13 +406,13 @@
|
||||
<link href="/extensions/comfyui-mixlab-nodes/lib/classic.min.css" rel="stylesheet">
|
||||
<script src="/extensions/comfyui-mixlab-nodes/lib/pickr.min.js"></script>
|
||||
<script src="/extensions/comfyui-mixlab-nodes/lib/filerobot-image-editor.min.js"></script>
|
||||
<link rel="stylesheet" href="/extensions/comfyui-mixlab-nodes/lib/login.css">
|
||||
</head>
|
||||
|
||||
<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;
|
||||
@@ -391,18 +423,18 @@
|
||||
<a class="link" href="https://www.mixcomfy.com" target="_blank">ComfyUI中文爱好者社区推荐</a>
|
||||
</div>
|
||||
|
||||
<a target="_blank" href="https://www.mixcomfy.com/blog/" style="text-decoration: none;
|
||||
<a target="_blank" id="login_btn" href="https://www.mixcomfy.com/blog/" style="text-decoration: none;
|
||||
color: black;font-size:12px">
|
||||
<svg height="32" aria-hidden="true" viewBox="0 0 16 16" version="1.1" width="32" data-view-component="true"
|
||||
class="octicon octicon-mark-github v-align-middle color-fg-default">
|
||||
<path
|
||||
d="M8 0c4.42 0 8 3.58 8 8a8.013 8.013 0 0 1-5.45 7.59c-.4.08-.55-.17-.55-.38 0-.27.01-1.13.01-2.2 0-.75-.25-1.23-.54-1.48 1.78-.2 3.65-.88 3.65-3.95 0-.88-.31-1.59-.82-2.15.08-.2.36-1.02-.08-2.12 0 0-.67-.22-2.2.82-.64-.18-1.32-.27-2-.27-.68 0-1.36.09-2 .27-1.53-1.03-2.2-.82-2.2-.82-.44 1.1-.16 1.92-.08 2.12-.51.56-.82 1.28-.82 2.15 0 3.06 1.86 3.75 3.64 3.95-.23.2-.44.55-.51 1.07-.46.21-1.61.55-2.33-.66-.15-.24-.6-.83-1.23-.82-.67.01-.27.38.01.53.34.19.73.9.82 1.13.16.45.68 1.31 2.69.94 0 .67.01 1.3.01 1.49 0 .21-.15.45-.55.38A7.995 7.995 0 0 1 0 8c0-4.42 3.58-8 8-8Z">
|
||||
</path>
|
||||
</svg> Community
|
||||
</svg> Community</a>
|
||||
|
||||
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<a id="author"></a>
|
||||
<!-- <script type="module" src="https://cdn.bootcdn.net/ajax/libs/photoswipe/5.4.0/photoswipe-lightbox.esm.min.js"></script> -->
|
||||
<script type="module">
|
||||
import PhotoSwipeLightbox from '/extensions/comfyui-mixlab-nodes/lib/photoswipe-lightbox.esm.min.js'
|
||||
@@ -435,7 +467,6 @@
|
||||
body
|
||||
})
|
||||
|
||||
|
||||
let data = await resp.json()
|
||||
// console.log(data)
|
||||
let { name, subfolder } = data
|
||||
@@ -671,7 +702,7 @@
|
||||
}
|
||||
|
||||
|
||||
function queuePrompt(promptWorkflow, seed, client_id) {
|
||||
function queuePrompt(appInfo, promptWorkflow, seed, client_id) {
|
||||
// appinfo升级后 兼容,补丁
|
||||
for (const id in promptWorkflow) {
|
||||
if (promptWorkflow[id].class_type == 'AppInfo') {
|
||||
@@ -690,9 +721,14 @@
|
||||
},
|
||||
body: data,
|
||||
})
|
||||
.then(response => {
|
||||
.then(async response => {
|
||||
// Handle response here
|
||||
console.log(response)
|
||||
// console.log(response)
|
||||
let res = await response.json();
|
||||
window.prompt_ids[res.prompt_id] = {
|
||||
appInfo,
|
||||
prompt_id: res.prompt_id
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
// Handle error here
|
||||
@@ -730,6 +766,31 @@
|
||||
}
|
||||
} catch (error) {
|
||||
}
|
||||
|
||||
// 排序
|
||||
let appSelected = localStorage.getItem('app_selected')
|
||||
if (appSelected) {
|
||||
async function moveElementToFront(array, targetId) {
|
||||
|
||||
for (let i = 0; i < array.length; i++) {
|
||||
if (array[i].id === targetId) {
|
||||
|
||||
if (i !== 0) {
|
||||
const targetElement = array.splice(i, 1)[0];
|
||||
|
||||
let nt = (await get_my_app(targetElement.category, targetElement.filename))[0];
|
||||
|
||||
array.unshift(nt);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
data = await moveElementToFront(data, appSelected)
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
@@ -890,6 +951,7 @@
|
||||
|
||||
|
||||
function generateRainbowVideo() {
|
||||
|
||||
// 创建一个canvas元素
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = 640; // 设置canvas宽度
|
||||
@@ -908,30 +970,26 @@
|
||||
context.fillStyle = 'green';
|
||||
context.fillRect(canvas.width / 2, 0, canvas.width / 2, canvas.height);
|
||||
|
||||
// 创建一个新的video元素
|
||||
const video = document.createElement('video');
|
||||
video.setAttribute('controls', true)
|
||||
// 将canvas绘制到video中
|
||||
const stream = canvas.captureStream();
|
||||
video.srcObject = stream;
|
||||
|
||||
// 导出视频
|
||||
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);
|
||||
video.src = url;
|
||||
};
|
||||
mediaRecorder.start();
|
||||
setTimeout(function () {
|
||||
mediaRecorder.stop();
|
||||
}, 1000); // 设置录制时长,这里设置为1秒
|
||||
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秒
|
||||
})
|
||||
|
||||
return video;
|
||||
}
|
||||
|
||||
|
||||
@@ -1127,9 +1185,10 @@
|
||||
// ];
|
||||
inputData = inputData.filter(inp => inp);
|
||||
// console.log('inputData',inputData)
|
||||
inputData.forEach(data => {
|
||||
inputData.forEach(async data => {
|
||||
console.log(data)
|
||||
// Check if the class_type is "LoadImage"
|
||||
|
||||
// 图片 or 视频输入
|
||||
if (data.class_type === "LoadImage" || data.class_type === "VHS_LoadVideo") {
|
||||
|
||||
let isVideoUpload = data.class_type === "VHS_LoadVideo";
|
||||
@@ -1145,6 +1204,7 @@
|
||||
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");
|
||||
@@ -1172,6 +1232,7 @@
|
||||
// Create an image element to display the uploaded image
|
||||
let imageElement = document.createElement("img");
|
||||
if (isVideoUpload) {
|
||||
// 视频
|
||||
imageElement = document.createElement('video');
|
||||
imageElement.setAttribute('controls', true)
|
||||
let [subfolder, name] = data.inputs.video.split('/');
|
||||
@@ -1182,6 +1243,9 @@
|
||||
}
|
||||
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('/');
|
||||
@@ -1192,6 +1256,9 @@
|
||||
// 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';
|
||||
@@ -1254,6 +1321,7 @@
|
||||
container.appendChild(uploadContainer);
|
||||
}
|
||||
|
||||
// 滑块输入
|
||||
if (["PromptSlide"].includes(data.class_type)) {
|
||||
// 滑块输入
|
||||
let options = data.options || {
|
||||
@@ -1283,6 +1351,7 @@
|
||||
}
|
||||
|
||||
|
||||
// 数字输入支持
|
||||
if (['FloatSlider', 'IntNumber'].includes(data.class_type)) {
|
||||
// console.log('data.options',data.options)
|
||||
// 滑块输入
|
||||
@@ -1305,8 +1374,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';
|
||||
@@ -1371,10 +1440,36 @@
|
||||
container.appendChild(uploadContainer);
|
||||
}
|
||||
|
||||
|
||||
// lora的输入支持
|
||||
if (["CheckpointLoaderSimple", "LoraLoader"].includes(data.class_type)) {
|
||||
let value = data.inputs.ckpt_name || data.inputs.lora_name;
|
||||
|
||||
|
||||
try {
|
||||
let t = '';
|
||||
if (data.class_type == 'CheckpointLoaderSimple') {
|
||||
t = 'checkpoints'
|
||||
} else if (data.class_type == 'LoraLoader') {
|
||||
t = 'loras'
|
||||
}
|
||||
if (t) {
|
||||
const response = await fetch(`${get_url()}/mixlab/folder_paths`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ type: t })
|
||||
});
|
||||
const data = await response.json();
|
||||
data.options = data.names;
|
||||
console.log(data.names);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
let v = localStorage.getItem(`_model_${data.id}_${data.class_type}`)
|
||||
if (v) {
|
||||
@@ -1415,6 +1510,7 @@
|
||||
container.appendChild(div);
|
||||
}
|
||||
|
||||
// 色彩选择器
|
||||
if (["Color"].includes(data.class_type)) {
|
||||
let value = data.inputs.color.hex || '#000000';
|
||||
let d = document.createElement('div');
|
||||
@@ -1695,11 +1791,25 @@
|
||||
|
||||
function createUI(data, share = true) {
|
||||
// appData.input, appData.output, appData.seed, share, appData.link
|
||||
const { input: inputData, output: outputData, data: workflow, seed, link } = data;
|
||||
const { input: inputData, output: outputData, data: workflow, seed, link, name } = data;
|
||||
|
||||
let mainDiv = document.createElement('div');
|
||||
|
||||
if (document.body.querySelector('#app_container')) document.body.querySelector('#app_container').remove()
|
||||
let appDetails = document.createElement('details');
|
||||
appDetails.id = "app_container"
|
||||
appDetails.setAttribute('open', true)
|
||||
appDetails.innerHTML = `<summary>${name}</summary>`;
|
||||
appDetails.style = `background: whitesmoke;
|
||||
color: black;
|
||||
padding: 12px;
|
||||
cursor: pointer;
|
||||
margin: 8px 44px;`
|
||||
|
||||
|
||||
let leftDetails = document.createElement('details');
|
||||
leftDetails.setAttribute('open', 'true')
|
||||
leftDetails.id = 'app_input_pannel'
|
||||
leftDetails.innerHTML = `<summary>INPUT</summary>
|
||||
<div class="content"></div>`
|
||||
|
||||
@@ -1845,7 +1955,13 @@
|
||||
mainDiv.appendChild(leftDetails);
|
||||
mainDiv.appendChild(rightDiv);
|
||||
|
||||
document.body.appendChild(mainDiv)
|
||||
appDetails.appendChild(mainDiv);
|
||||
document.body.appendChild(appDetails)
|
||||
|
||||
appDetails.addEventListener('toggle', e => {
|
||||
e.preventDefault();
|
||||
document.body.querySelector('#author').style.display = appDetails.open ? 'flex' : 'none'
|
||||
})
|
||||
|
||||
// 返回每个UI元素的引用和对应的更新方法
|
||||
return {
|
||||
@@ -2058,6 +2174,71 @@
|
||||
return div
|
||||
}
|
||||
|
||||
function executed(detail, show) {
|
||||
console.log('#executed', window.prompt_ids, detail)
|
||||
if (detail?.node
|
||||
&& window.prompt_ids[detail.prompt_id]
|
||||
&& window._appData?.output.filter(f => f.id === detail.node)[0]) {
|
||||
// 保存结果到记录里
|
||||
window.prompt_ids[detail.prompt_id].data = detail
|
||||
window.prompt_ids[detail.prompt_id].createTime = (new Date()).getTime()
|
||||
savePromptResult({
|
||||
...window.prompt_ids[detail.prompt_id],
|
||||
prompt_id: detail.prompt_id
|
||||
})
|
||||
}
|
||||
// if (!enabled) return;
|
||||
const images = detail?.output?.images;
|
||||
const text = detail?.output?.text;
|
||||
const gifs = detail?.output?.gifs;
|
||||
|
||||
const prompt = detail?.output?.prompt;
|
||||
const analysis = detail?.output?.analysis;
|
||||
|
||||
const _images = detail?.output?._images;
|
||||
const prompts = detail?.output?.prompts;
|
||||
|
||||
if (images) {
|
||||
// if (!images) return;
|
||||
|
||||
let url = get_url();
|
||||
|
||||
show(Array.from(images, img => {
|
||||
return `${url}/view?filename=${encodeURIComponent(img.filename)}&type=${img.type}&subfolder=${encodeURIComponent(img.subfolder)}&t=${+new Date()}`;
|
||||
}), detail.node, 'images');
|
||||
|
||||
} else if (_images && prompts) {
|
||||
let url = get_url();
|
||||
|
||||
let items = [];
|
||||
// 支持图片的batch
|
||||
Array.from(_images, (imgs, i) => {
|
||||
|
||||
for (const img of imgs) {
|
||||
items.push([`${url}/view?filename=${encodeURIComponent(img.filename)
|
||||
}&type=${img.type}&subfolder=${encodeURIComponent(img.subfolder)
|
||||
}&t=${+new Date()}`, prompts[i]])
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
show(items, detail.node, 'images_prompts');
|
||||
|
||||
} else if (text) {
|
||||
show(Array.isArray(text) ? text.join('\n\n') : text, detail.node, 'text')
|
||||
} else if (gifs && gifs[0]) {
|
||||
// if (!images) return;
|
||||
const src = `${get_url()}/view?filename=${encodeURIComponent(gifs[0].filename)}&type=${gifs[0].type}&subfolder=${encodeURIComponent(gifs[0].subfolder)
|
||||
}&&format=${gifs[0].format}&t=${+new Date()}`;
|
||||
|
||||
show(src, detail.node, gifs[0].format.match('video') ? 'video' : 'image');
|
||||
} else if (prompt && analysis) {
|
||||
// #ClipInterrogator: ……
|
||||
show(`${prompt.join('\n\n')}\n${JSON.stringify(analysis, null, 2)}`, detail.node, 'text')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
async function createApp(appData, share = true) {
|
||||
// console.log(appData)
|
||||
@@ -2079,7 +2260,13 @@
|
||||
ui.submitButton.update(
|
||||
() => {
|
||||
// 在提交按钮点击时执行的逻辑
|
||||
queuePrompt(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);
|
||||
}, () => {
|
||||
// 取消
|
||||
if (api.runningCancel) {
|
||||
@@ -2095,11 +2282,14 @@
|
||||
// console.log(src)
|
||||
ui.output.update(type, src, id)
|
||||
};
|
||||
// 暴露给history使用
|
||||
window._show = show;
|
||||
|
||||
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()
|
||||
@@ -2107,6 +2297,7 @@
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
window.parent.postMessage({ cmd: 'status' }, '*');
|
||||
}
|
||||
|
||||
});
|
||||
@@ -2124,57 +2315,7 @@
|
||||
|
||||
api.addEventListener("executed", async ({ detail }) => {
|
||||
console.log("executed", detail)
|
||||
// if (!enabled) return;
|
||||
const images = detail?.output?.images;
|
||||
const text = detail?.output?.text;
|
||||
const gifs = detail?.output?.gifs;
|
||||
|
||||
const prompt = detail?.output?.prompt;
|
||||
const analysis = detail?.output?.analysis;
|
||||
|
||||
const _images = detail?.output?._images;
|
||||
const prompts = detail?.output?.prompts;
|
||||
|
||||
if (images) {
|
||||
// if (!images) return;
|
||||
|
||||
let url = get_url();
|
||||
|
||||
show(Array.from(images, img => {
|
||||
return `${url}/view?filename=${encodeURIComponent(img.filename)}&type=${img.type}&subfolder=${encodeURIComponent(img.subfolder)}&t=${+new Date()}`;
|
||||
}), detail.node, 'images');
|
||||
|
||||
} else if (_images && prompts) {
|
||||
let url = get_url();
|
||||
|
||||
let items = [];
|
||||
// 支持图片的batch
|
||||
Array.from(_images, (imgs, i) => {
|
||||
|
||||
for (const img of imgs) {
|
||||
items.push([`${url}/view?filename=${encodeURIComponent(img.filename)
|
||||
}&type=${img.type}&subfolder=${encodeURIComponent(img.subfolder)
|
||||
}&t=${+new Date()}`, prompts[i]])
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
show(items, detail.node, 'images_prompts');
|
||||
|
||||
} else if (text) {
|
||||
ui.output.update("text", Array.isArray(text) ? text.join('\n\n') : text, detail.node)
|
||||
} else if (gifs && gifs[0]) {
|
||||
// if (!images) return;
|
||||
const src = `${get_url()}/view?filename=${encodeURIComponent(gifs[0].filename)}&type=${gifs[0].type}&subfolder=${encodeURIComponent(gifs[0].subfolder)
|
||||
}&&format=${gifs[0].format}&t=${+new Date()}`;
|
||||
|
||||
show(src, detail.node, gifs[0].format.match('video') ? 'video' : 'image');
|
||||
} else if (prompt && analysis) {
|
||||
// #ClipInterrogator: ……
|
||||
ui.output.update("text", `${prompt.join('\n\n')}\n${JSON.stringify(analysis, null, 2)}`, detail.node)
|
||||
}
|
||||
|
||||
|
||||
executed(detail, show);
|
||||
|
||||
try {
|
||||
ui.status.update(`executed_#${window._appData.data[detail.node]?.class_type}`);
|
||||
@@ -2206,6 +2347,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));
|
||||
});
|
||||
|
||||
@@ -2235,7 +2377,6 @@
|
||||
api.init();
|
||||
|
||||
// 外挂的UI
|
||||
|
||||
createAllColorInput();
|
||||
|
||||
|
||||
@@ -2283,14 +2424,26 @@
|
||||
// return true;
|
||||
// });
|
||||
|
||||
// author信息
|
||||
if (appData.author) {
|
||||
let div = document.body.querySelector('#author');
|
||||
if (appData.author.link) div.href = appData.author.link
|
||||
div.style = `z-index:20;display: flex;flex-direction: column;position: fixed;bottom: 12px;right: 24px;cursor: pointer;text-decoration: none;color: black;`
|
||||
div.innerHTML = `<p style="font-size:12px">Author:</p>
|
||||
<div style="display: flex;"> <img style="width:32px;height:32px;border-radius: 100%;"
|
||||
src="${appData.author.avatar || base64Df}"/>
|
||||
<p style="margin-left:8px;font-size:12px;font-weight:800">${appData.author.name || '-'}</p></div>`
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 创建app的选择菜单
|
||||
function createAppList(apps = []) {
|
||||
function createAppList(apps = [], innerApp = false) {
|
||||
window.prompt_ids = {};
|
||||
let details = document.createElement('details');
|
||||
details.className = 'apps';
|
||||
|
||||
details.innerHTML = `<summary>ComfyUI APP Store / ${apps.length}</summary>
|
||||
details.innerHTML = `<summary>APP Store / ${apps.length}</summary>
|
||||
<div class="content"> </div>`
|
||||
|
||||
let div = details.querySelector('div');
|
||||
@@ -2311,52 +2464,273 @@
|
||||
<p>${app.description}</p>
|
||||
</div>
|
||||
<div >
|
||||
<p class="version">version: ${app.version}</p>
|
||||
|
||||
<p class="version">Version: ${app.version}</p>
|
||||
</div>
|
||||
<br>
|
||||
${app.author && app.author.name ? `<p class="version">Author:</p><div
|
||||
style="display: flex;justify-content: center;align-items: center;margin-top: 8px;">
|
||||
<img style="width:28px;height:28px;border-radius: 100%;"
|
||||
src="${app.author.avatar || base64Df}"/>
|
||||
<p class="version" style="margin-left: 12px;">${app.author.name}</p>
|
||||
</div>`: ''}
|
||||
</div>
|
||||
`
|
||||
|
||||
|
||||
div.appendChild(d);
|
||||
dd.addEventListener('click', async e => {
|
||||
e.preventDefault();
|
||||
Array.from(div.querySelectorAll('.card'), c => c.classList.remove('selected'));
|
||||
dd.className = 'card selected'
|
||||
// console.log(app.filename)
|
||||
|
||||
window._appData = (await get_my_app(app.category, app.filename))[0];
|
||||
if (document.body.querySelector('.app')) document.body.querySelector('.app').remove()
|
||||
createApp(window._appData);
|
||||
details.removeAttribute('open');
|
||||
let res = (await get_my_app(app.category, app.filename)).filter(n => n.filename === app.filename)[0];
|
||||
|
||||
if (res) {
|
||||
window._appData = res;
|
||||
if (document.body.querySelector('.app')) document.body.querySelector('.app').remove()
|
||||
createApp(window._appData);
|
||||
localStorage.setItem('app_selected', window._appData.id)
|
||||
}
|
||||
|
||||
})
|
||||
// console.log(div)
|
||||
};
|
||||
|
||||
let uploadApp = createUploadJson(details);
|
||||
div.appendChild(uploadApp);
|
||||
if (!innerApp) {
|
||||
let uploadApp = createUploadJson(details);
|
||||
div.appendChild(uploadApp);
|
||||
}
|
||||
|
||||
document.body.appendChild(details);
|
||||
details.addEventListener('toggle', e => {
|
||||
e.preventDefault();
|
||||
if (document.body.querySelector('#app_container')) {
|
||||
document.body.querySelector('#app_container').removeAttribute('open');
|
||||
document.body.querySelector('#author').style.display = 'none'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 请求历史数据
|
||||
async function getPromptResult(category) {
|
||||
let url = get_url()
|
||||
try {
|
||||
const response = await fetch(`${url}/mixlab/prompt_result`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
action: "all",
|
||||
}),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
console.log("#getPromptResult:", category, data);
|
||||
|
||||
return data.result.filter(r => r.appInfo.category == category)
|
||||
// 处理返回的数据
|
||||
} else {
|
||||
console.log("Error:", response.status);
|
||||
// 处理错误情况
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("Error:", error);
|
||||
// 处理异常情况
|
||||
}
|
||||
}
|
||||
// 保存历史数据
|
||||
async function savePromptResult(data) {
|
||||
let url = get_url()
|
||||
try {
|
||||
const response = await fetch(`${url}/mixlab/prompt_result`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
action: "save",
|
||||
data
|
||||
}),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const res = await response.json();
|
||||
console.log("Response:", res);
|
||||
return res
|
||||
// 处理返回的数据
|
||||
} else {
|
||||
console.log("Error:", response.status);
|
||||
// 处理错误情况
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("Error:", error);
|
||||
// 处理异常情况
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
async function createHistoryList(category) {
|
||||
if (document.body.querySelector('#history_container')) document.body.querySelector('#history_container').remove();
|
||||
|
||||
window._historyData = await getPromptResult(category);
|
||||
|
||||
if (!window._historyData || (window._historyData && window._historyData.length === 0)) return
|
||||
|
||||
let details = document.createElement('details');
|
||||
details.id = "history_container"
|
||||
details.innerHTML = `<summary>历史</summary>`;
|
||||
details.style = `background: whitesmoke;
|
||||
color: black;
|
||||
padding: 12px;
|
||||
cursor: pointer;
|
||||
margin: 8px 44px;`;
|
||||
|
||||
details.addEventListener('toggle', function (event) {
|
||||
if (details.open) {
|
||||
// console.log('details被展开了');
|
||||
// 在这里执行展开后的回调操作
|
||||
if (document.body.querySelector('#app_container')) {
|
||||
document.body.querySelector('#app_container').removeAttribute('open')
|
||||
}
|
||||
if (document.body.querySelector('.apps')) {
|
||||
document.body.querySelector('.apps').removeAttribute('open')
|
||||
}
|
||||
} else {
|
||||
// console.log('details被收起了');
|
||||
// 在这里执行收起后的回调操作
|
||||
if (document.body.querySelector('#app_container')) {
|
||||
document.body.querySelector('#app_container').removeAttribute('open')
|
||||
}
|
||||
if (document.body.querySelector('.apps')) {
|
||||
document.body.querySelector('.apps').removeAttribute('open')
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
let cards = document.createElement('div');
|
||||
cards.style = `display: flex;flex-wrap: wrap;`
|
||||
|
||||
const addCard = (title, createTime, imgurl) => {
|
||||
let card = document.createElement('div')
|
||||
card.className = 'card';
|
||||
card.innerHTML = `<div class="item icon">
|
||||
<img src="${imgurl || base64Df}"/>
|
||||
</div>
|
||||
<div class="item" style="margin-left: 24px;">
|
||||
<div>
|
||||
<h5>${title}</h5>
|
||||
<p></p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="version">${new Date(createTime || (new Date()))}</p>
|
||||
|
||||
</div>
|
||||
</div>`
|
||||
return card
|
||||
}
|
||||
|
||||
for (const c of window._historyData) {
|
||||
let card = addCard(c.appInfo.name, c.createTime, c.appInfo.icon)
|
||||
cards.appendChild(card)
|
||||
card.addEventListener('click', async e => {
|
||||
e.preventDefault();
|
||||
// console.log(c)
|
||||
try {
|
||||
document.body.querySelector('#app_container').setAttribute('open', true)
|
||||
document.body.querySelector('#app_input_pannel').removeAttribute('open')
|
||||
document.body.querySelector('.apps').removeAttribute('open')
|
||||
} catch (error) {
|
||||
|
||||
}
|
||||
|
||||
const { category, filename } = c.appInfo;
|
||||
window._appData = (await get_my_app(category, filename))[0];
|
||||
|
||||
createApp(window._appData);
|
||||
|
||||
executed(c.data, window._show);
|
||||
})
|
||||
}
|
||||
|
||||
details.appendChild(cards);
|
||||
|
||||
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);
|
||||
|
||||
if (window._apps.length > 0) await createHistoryList(category || '');
|
||||
|
||||
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", async 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);
|
||||
|
||||
// await createHistoryList();
|
||||
|
||||
createApp(window._appData);
|
||||
} else {
|
||||
// todo welcome页面
|
||||
document.body.innerHTML = `<h3 style="padding: 99px;">Welcome to Mixlab Nodes App!</h3>`
|
||||
}
|
||||
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
return innerApp == 1
|
||||
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<!-- <script src="/extensions/comfyui-mixlab-nodes/lib/login.js"></script> -->
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -2,6 +2,9 @@ import { app } from '../../../scripts/app.js'
|
||||
import { $el } from '../../../scripts/ui.js'
|
||||
import { api } from '../../../scripts/api.js'
|
||||
|
||||
const base64Df =
|
||||
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAAAXNSR0IArs4c6QAAALZJREFUKFOFkLERwjAQBPdbgBkInECGaMLUQDsE0AkRVRAYWqAByxldPPOWHwnw4OBGye1p50UDSoA+W2ABLPN7i+C5dyC6R/uiAUXRQCs0bXoNIu4QPQzAxDKxHoALOrZcqtiyR/T6CXw7+3IGHhkYcy6BOR2izwT8LptG8rbMiCRAUb+CQ6WzQVb0SNOi5Z2/nX35DRyb/ENazhpWKoGwrpD6nICp5c2qogc4of+c7QcrhgF4Aa/aoAFHiL+RAAAAAElFTkSuQmCC'
|
||||
|
||||
function get_position_style (ctx, widget_width, y, node_height) {
|
||||
const MARGIN = 12 // the margin around the html element
|
||||
|
||||
@@ -28,7 +31,7 @@ function get_position_style (ctx, widget_width, y, node_height) {
|
||||
// height: `${node_height * 0.3 - MARGIN * 2}px`,
|
||||
// background: '#EEEEEE',
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
flexDirection: 'column',
|
||||
// alignItems: 'center',
|
||||
justifyContent: 'flex-start'
|
||||
}
|
||||
@@ -234,6 +237,14 @@ async function save (json, download = false, showInfo = true) {
|
||||
outputIds
|
||||
)
|
||||
|
||||
let authorAvatar =
|
||||
localStorage.getItem('_mixlab_author_avatar') || base64Df,
|
||||
authorName =
|
||||
localStorage.getItem('_mixlab_author_name') ||
|
||||
localStorage.getItem('Comfy.userName'),
|
||||
authorLink =
|
||||
localStorage.getItem('_mixlab_author_link') || ''
|
||||
|
||||
data.app = {
|
||||
name,
|
||||
description,
|
||||
@@ -244,7 +255,12 @@ async function save (json, download = false, showInfo = true) {
|
||||
share_prefix,
|
||||
link,
|
||||
category,
|
||||
filename: `${name}_${version}.json`
|
||||
filename: `${name}_${version}.json`,
|
||||
author: {
|
||||
avatar: authorAvatar,
|
||||
name: authorName,
|
||||
link:authorLink
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -377,10 +393,167 @@ app.registerExtension({
|
||||
}
|
||||
})
|
||||
|
||||
document.body.appendChild(widget.div)
|
||||
widget.div.appendChild(btn)
|
||||
widget.div.appendChild(download)
|
||||
// author
|
||||
let author = document.createElement('div')
|
||||
// author.style=`display: flex`
|
||||
|
||||
let authorAvatar = document.createElement('img')
|
||||
authorAvatar.className = `${'comfy-multiline-input'}`
|
||||
authorAvatar.style = `outline: none;
|
||||
border: none;
|
||||
padding: 4px;
|
||||
width: 32px;
|
||||
cursor: pointer;
|
||||
height: 32px;`
|
||||
|
||||
if (localStorage.getItem('_mixlab_author_avatar')) {
|
||||
authorAvatar.src =
|
||||
localStorage.getItem('_mixlab_author_avatar') || base64Df
|
||||
}
|
||||
|
||||
let authorAvatarUpload = document.createElement('input')
|
||||
authorAvatarUpload.type = 'file'
|
||||
authorAvatarUpload.style = `display:none`
|
||||
|
||||
let authorAvatarInput = document.createElement('div')
|
||||
authorAvatarInput.style = `display: flex;justify-content: flex-start;
|
||||
align-items: center;`
|
||||
let authorAvatarInputLabel = document.createElement('p')
|
||||
authorAvatarInputLabel.innerText = 'Author Avatar'
|
||||
authorAvatarInputLabel.className = `${'comfy-multiline-input'}`
|
||||
authorAvatarInputLabel.style = `font-size:12px`
|
||||
|
||||
authorAvatar.addEventListener('click', e => {
|
||||
authorAvatarUpload.click()
|
||||
})
|
||||
|
||||
authorAvatarInputLabel.addEventListener('click', e => {
|
||||
authorAvatarUpload.click()
|
||||
})
|
||||
|
||||
authorAvatarUpload.addEventListener('change', event => {
|
||||
const file = event.target.files[0]
|
||||
const reader = new FileReader()
|
||||
|
||||
reader.onload = async e => {
|
||||
let im = new Image()
|
||||
im.src = e.target.result
|
||||
authorAvatar.src = e.target.result
|
||||
im.onload = () => {
|
||||
let c = document.createElement('canvas')
|
||||
let ctx = c.getContext('2d')
|
||||
c.width = 72
|
||||
c.height = 72
|
||||
ctx.drawImage(
|
||||
im,
|
||||
0,
|
||||
0,
|
||||
im.naturalWidth,
|
||||
im.naturalHeight,
|
||||
0,
|
||||
0,
|
||||
c.width,
|
||||
c.height
|
||||
)
|
||||
window._mixlab_author_avatar = c.toDataURL()
|
||||
localStorage.setItem(
|
||||
'_mixlab_author_avatar',
|
||||
window._mixlab_author_avatar
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 以文本形式读取文件
|
||||
reader.readAsDataURL(file)
|
||||
})
|
||||
|
||||
author.appendChild(authorAvatarInput)
|
||||
authorAvatarInput.appendChild(authorAvatarInputLabel)
|
||||
authorAvatarInput.appendChild(authorAvatar)
|
||||
authorAvatarInput.appendChild(authorAvatarUpload)
|
||||
|
||||
let authorName = document.createElement('input')
|
||||
authorName.type = 'text'
|
||||
authorName.value =
|
||||
localStorage.getItem('_mixlab_author_name') ||
|
||||
localStorage.getItem('Comfy.userName')
|
||||
authorName.placeholder = 'author name'
|
||||
authorName.className = `${'comfy-multiline-input'}`
|
||||
authorName.style = `
|
||||
outline: none;
|
||||
border: none;
|
||||
padding: 4px;
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
height: 32px;`
|
||||
|
||||
let authorNameInput = document.createElement('div')
|
||||
authorNameInput.style = `display: flex;justify-content: flex-start;
|
||||
align-items: center;`
|
||||
let authorNameInputLabel = document.createElement('p')
|
||||
authorNameInputLabel.innerText = 'Author Name'
|
||||
authorNameInputLabel.className = `${'comfy-multiline-input'}`
|
||||
authorNameInputLabel.style = `font-size:12px;width: 110px`
|
||||
|
||||
authorName.addEventListener('change', e => {
|
||||
window._mixlab_author_name = authorName.value.trim()
|
||||
localStorage.setItem(
|
||||
'_mixlab_author_name',
|
||||
window._mixlab_author_name
|
||||
)
|
||||
})
|
||||
|
||||
author.appendChild(authorNameInput)
|
||||
authorNameInput.appendChild(authorNameInputLabel)
|
||||
authorNameInput.appendChild(authorName)
|
||||
|
||||
|
||||
// 社交链接
|
||||
let authorLink = document.createElement('input')
|
||||
authorLink.type = 'text'
|
||||
authorLink.value =
|
||||
localStorage.getItem('_mixlab_author_link') ||''
|
||||
authorLink.placeholder = 'author link'
|
||||
authorLink.className = `${'comfy-multiline-input'}`
|
||||
authorLink.style = `
|
||||
outline: none;
|
||||
border: none;
|
||||
padding: 4px;
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
height: 32px;`
|
||||
|
||||
let authorLinkInput = document.createElement('div')
|
||||
authorLinkInput.style = `display: flex;justify-content: flex-start;
|
||||
align-items: center;`
|
||||
let authorLinkInputLabel = document.createElement('p')
|
||||
authorLinkInputLabel.innerText = 'Author Link'
|
||||
authorLinkInputLabel.className = `${'comfy-multiline-input'}`
|
||||
authorLinkInputLabel.style = `font-size:12px;width: 110px`
|
||||
|
||||
authorLink.addEventListener('change', e => {
|
||||
window._mixlab_author_link = authorLink.value.trim()
|
||||
localStorage.setItem(
|
||||
'_mixlab_author_link',
|
||||
window._mixlab_author_link
|
||||
)
|
||||
})
|
||||
|
||||
author.appendChild(authorLinkInput)
|
||||
authorLinkInput.appendChild(authorLinkInputLabel)
|
||||
authorLinkInput.appendChild(authorLink)
|
||||
|
||||
|
||||
widget.div.appendChild(author)
|
||||
|
||||
let btns = document.createElement('div')
|
||||
|
||||
widget.div.appendChild(btns)
|
||||
|
||||
btns.appendChild(btn)
|
||||
btns.appendChild(download)
|
||||
|
||||
document.body.appendChild(widget.div)
|
||||
this.addCustomWidget(widget)
|
||||
|
||||
const onRemoved = this.onRemoved
|
||||
|
||||
@@ -3,7 +3,7 @@ import { app } from '../../../scripts/app.js'
|
||||
const repoOwner = 'shadowcz007' // 替换为仓库的所有者
|
||||
const repoName = 'comfyui-mixlab-nodes' // 替换为仓库的名称
|
||||
|
||||
const version = 'v0.17.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)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
+83
-29
@@ -282,18 +282,22 @@ function get_url () {
|
||||
|
||||
async function get_my_app (filename = null, category = '') {
|
||||
let url = get_url()
|
||||
const res = await fetch(`${url}/mixlab/workflow`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
task: 'my_app',
|
||||
filename,
|
||||
category,
|
||||
admin: true
|
||||
})
|
||||
})
|
||||
let result = await res.json()
|
||||
let data = []
|
||||
let data = null
|
||||
|
||||
try {
|
||||
const res = await fetch(`${url}/mixlab/workflow`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
task: 'my_app',
|
||||
filename,
|
||||
category,
|
||||
admin: true
|
||||
})
|
||||
})
|
||||
let result = await res.json()
|
||||
|
||||
data = []
|
||||
|
||||
for (const res of result.data) {
|
||||
let { app, workflow } = res.data
|
||||
if (app.filename)
|
||||
@@ -1126,6 +1130,7 @@ app.registerExtension({
|
||||
const orig = LGraphCanvas.prototype.getCanvasMenuOptions
|
||||
|
||||
const apps = await get_my_app()
|
||||
if (!apps) return
|
||||
|
||||
let apps_map = { 0: [] }
|
||||
|
||||
@@ -1150,8 +1155,30 @@ app.registerExtension({
|
||||
has_submenu: false,
|
||||
callback: async () => {
|
||||
try {
|
||||
let item = (await get_my_app(a.filename))[0]
|
||||
let ddd = await get_my_app(a.filename)
|
||||
if (!ddd) return
|
||||
let item = ddd[0]
|
||||
if (item) {
|
||||
if (item.author) {
|
||||
// 有作者信息
|
||||
if (item.author.avatar)
|
||||
localStorage.setItem(
|
||||
'_mixlab_author_avatar',
|
||||
item.author.avatar
|
||||
)
|
||||
if (item.author.name)
|
||||
localStorage.setItem(
|
||||
'_mixlab_author_name',
|
||||
item.author.name
|
||||
)
|
||||
|
||||
if (item.author.link)
|
||||
localStorage.setItem(
|
||||
'_mixlab_author_link',
|
||||
item.author.link
|
||||
)
|
||||
}
|
||||
|
||||
// console.log(item.data)
|
||||
app.loadGraphData(item.data)
|
||||
setTimeout(() => {
|
||||
@@ -1179,8 +1206,31 @@ app.registerExtension({
|
||||
content: `${a.name}_${a.version}`,
|
||||
callback: async () => {
|
||||
try {
|
||||
let item = (await get_my_app(a.filename, a.category))[0]
|
||||
let ddd = await get_my_app(a.filename, a.category)
|
||||
|
||||
if (!ddd) return
|
||||
let item = ddd[0]
|
||||
if (item) {
|
||||
console.log(item)
|
||||
if (item.author) {
|
||||
// 有作者信息
|
||||
if (item.author.avatar)
|
||||
localStorage.setItem(
|
||||
'_mixlab_author_avatar',
|
||||
item.author.avatar
|
||||
)
|
||||
if (item.author.name)
|
||||
localStorage.setItem(
|
||||
'_mixlab_author_name',
|
||||
item.author.name
|
||||
)
|
||||
if (item.author.link)
|
||||
localStorage.setItem(
|
||||
'_mixlab_author_link',
|
||||
item.author.link
|
||||
)
|
||||
}
|
||||
|
||||
// console.log(item.data)
|
||||
app.loadGraphData(item.data)
|
||||
setTimeout(() => {
|
||||
@@ -1478,31 +1528,35 @@ app.registerExtension({
|
||||
|
||||
widget.element.addEventListener('mouseover', e => {
|
||||
// console.log(node.widgets_values[index])
|
||||
if (node.widgets_values[index])
|
||||
if (node.widgets_values && node.widgets_values[index])
|
||||
widget.element.setAttribute('title', node.widgets_values[index])
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 右上角的badge是否已经绘制
|
||||
if (!node.badge_enabled) {
|
||||
if (!node.getNickname) {
|
||||
node.getNickname = function () {
|
||||
if (node.nickname) {
|
||||
return node.nickname
|
||||
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())
|
||||
}
|
||||
}
|
||||
return
|
||||
// return getNickname(node, node.comfyClass.trim())
|
||||
|
||||
const orig = node.__proto__.onDrawForeground
|
||||
node.onDrawForeground = function (ctx) {
|
||||
drawBadge(node, orig, arguments)
|
||||
}
|
||||
node.badge_enabled = true
|
||||
}
|
||||
}
|
||||
|
||||
const orig = node.__proto__.onDrawForeground
|
||||
node.onDrawForeground = function (ctx) {
|
||||
drawBadge(node, orig, arguments)
|
||||
}
|
||||
node.badge_enabled = true
|
||||
}
|
||||
})
|
||||
},
|
||||
async loadedGraphNode (node, app) {
|
||||
// console.log(
|
||||
|
||||
@@ -0,0 +1,277 @@
|
||||
* {
|
||||
transition: all 0.6s cubic-bezier(0.77, 0, 0.175, 1);
|
||||
}
|
||||
|
||||
#app-login {
|
||||
width: 480px;
|
||||
height: 90vh;
|
||||
padding: 6vh;
|
||||
background: white;
|
||||
box-shadow: 0 0 2rem rgba(0, 0, 0, 0.1);
|
||||
z-index: 999;
|
||||
position: fixed;
|
||||
top: 5vh;
|
||||
left: calc(50vw - 240px);
|
||||
}
|
||||
|
||||
.login-app-view {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.login-background {
|
||||
background-color: #202020e6;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
left: 0;
|
||||
top: 0;
|
||||
z-index: 998;
|
||||
}
|
||||
|
||||
.app-header {
|
||||
padding: 6vh;
|
||||
}
|
||||
|
||||
.app-header,
|
||||
.app-header>* {
|
||||
font-size: 1.2em;
|
||||
margin: 0;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.app-header>h1 {
|
||||
font-size: 4.8vh;
|
||||
font-weight: 400;
|
||||
margin-bottom: 4.8vh;
|
||||
}
|
||||
|
||||
.app-header>h2 {
|
||||
font-size: 3vh;
|
||||
}
|
||||
|
||||
.app-subheading {
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
}
|
||||
|
||||
.app-register {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
height: 10vh;
|
||||
line-height: 10vh;
|
||||
padding: 0 6vh;
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
}
|
||||
|
||||
.app-register>a {
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
|
||||
#app-login input {
|
||||
font-size: 2.5vh;
|
||||
width: calc(100% - 13vh);
|
||||
height: 7.5vh;
|
||||
margin-bottom: 2vh;
|
||||
background: transparent;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 6.5vh;
|
||||
z-index: 2;
|
||||
border: none;
|
||||
box-shadow: inset 0 -0.5vh rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
#app-login input:focus {
|
||||
outline: none;
|
||||
box-shadow: inset 0 -0.5vh transparent;
|
||||
}
|
||||
|
||||
#app-login input[type=email] {
|
||||
top: 58%;
|
||||
}
|
||||
|
||||
#app-login input[type=password] {
|
||||
top: calc(58% + 7.5vh);
|
||||
}
|
||||
|
||||
#app-login input[type=email]:valid~* .st1 {
|
||||
transition-timing-function: ease-in-out;
|
||||
stroke-dasharray: 50, 153;
|
||||
stroke-dashoffset: 25;
|
||||
}
|
||||
|
||||
#app-login input[type=password]:focus~* .st0,
|
||||
#app-login input[type=password]:valid~* .st0,
|
||||
#login_run:focus~* .st0 {
|
||||
stroke-dasharray: 210, 900;
|
||||
stroke-dashoffset: -305;
|
||||
}
|
||||
|
||||
#app-login input[type=email]:focus~* .st0 {
|
||||
stroke-dasharray: 210, 900;
|
||||
stroke-dashoffset: 0;
|
||||
}
|
||||
|
||||
#app-login input:not(:valid)~#login_run {
|
||||
/* pointer-events: none; */
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
#login_run {
|
||||
text-decoration: none;
|
||||
color: #0f9ede;
|
||||
font-size: 1.5em;
|
||||
padding: 0 6vh;
|
||||
position: absolute;
|
||||
bottom: 10vh;
|
||||
font-weight: 400;
|
||||
z-index: 998;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#login_run:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.login-app-view:nth-child(2) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.login-app-view:nth-child(2)>.app-header {
|
||||
font-size: 1rem;
|
||||
flex-basis: 25%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
padding: 4vh;
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
.login-app-view:nth-child(2)>.app-header>h2 {
|
||||
transform: translateY(1rem);
|
||||
}
|
||||
|
||||
.login-app-view:nth-child(2)>.app-header>h2>em {
|
||||
color: #0f9ede;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.login-app-view:nth-child(2)>.app-header>h2,
|
||||
.login-app-view:nth-child(2) .app-item>*:not(.app-graphic) {
|
||||
transition-duration: 0.9s;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
|
||||
.st0,
|
||||
.st1,
|
||||
.svg-loader-segment {
|
||||
fill: none;
|
||||
stroke: #0f9ede;
|
||||
stroke-width: 0.5vh;
|
||||
stroke-alignment: inside;
|
||||
opacity: 1;
|
||||
transition: all 0.6s cubic-bezier(0.77, 0, 0.175, 1);
|
||||
}
|
||||
|
||||
.svg-loader {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.st0 {
|
||||
stroke-dasharray: 0, 900;
|
||||
stroke-dashoffset: 0;
|
||||
}
|
||||
|
||||
.st1 {
|
||||
transition-delay: 0.3s;
|
||||
stroke-dasharray: 50, 153;
|
||||
stroke-dashoffset: -153;
|
||||
}
|
||||
|
||||
.svg-loader-segment {
|
||||
transition: transform 1.2s cubic-bezier(0.77, 0, 0.175, 1), opacity 0.85s cubic-bezier(0.77, 0, 0.175, 1), stroke 0.85s cubic-bezier(0.77, 0, 0.175, 1);
|
||||
}
|
||||
|
||||
#svg-lines {
|
||||
position: absolute;
|
||||
top: 45%;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
z-index: 0;
|
||||
overflow: visible;
|
||||
transform-origin: center 4vh;
|
||||
}
|
||||
|
||||
.svg-data {
|
||||
fill: none;
|
||||
stroke-width: 0.5vh;
|
||||
}
|
||||
|
||||
.svg-data.-temp {
|
||||
stroke: #f4814b;
|
||||
stroke-dasharray: 20, 118;
|
||||
}
|
||||
|
||||
.svg-data.-cal {
|
||||
stroke: #08b5cf;
|
||||
stroke-dasharray: 20, 113;
|
||||
}
|
||||
|
||||
.svg-data.-steps-bg {
|
||||
stroke: #e0e1e0;
|
||||
stroke-dasharray: 40, 100;
|
||||
stroke-dashoffset: -60;
|
||||
}
|
||||
|
||||
.svg-data.-steps {
|
||||
stroke: #0f9ede;
|
||||
stroke-dasharray: 20, 73;
|
||||
stroke-dashoffset: -53;
|
||||
}
|
||||
|
||||
.svg-data.-heart {
|
||||
stroke: #9965aa;
|
||||
stroke-dasharray: 50, 200;
|
||||
stroke-dashoffset: -150;
|
||||
}
|
||||
|
||||
.svg-activity-fill {
|
||||
fill: #c4e4f8;
|
||||
}
|
||||
|
||||
.svg-activity-line {
|
||||
fill: none;
|
||||
stroke: #65bcea;
|
||||
stroke-miterlimit: 10;
|
||||
stroke-width: 0.25vh;
|
||||
}
|
||||
|
||||
.svg-activity-avg,
|
||||
.svg-activity-indicator {
|
||||
fill: none;
|
||||
stroke: #d0dff0;
|
||||
stroke-width: 0.25vh;
|
||||
mix-blend-mode: multiply;
|
||||
}
|
||||
|
||||
.svg-activity-fill,
|
||||
.svg-activity-line {
|
||||
transform: translateY(10vh);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
|
||||
*,
|
||||
*:before,
|
||||
*:after {
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
;(() => {
|
||||
let div = document.createElement('div')
|
||||
|
||||
div.innerHTML = `
|
||||
<div id="app-login">
|
||||
<div class="login-app-view">
|
||||
<header class="app-header">
|
||||
<h1>Hi</h1>
|
||||
Welcome back,<br />
|
||||
<span class="app-subheading">
|
||||
sign in to continue<br />
|
||||
|
||||
</span>
|
||||
</header>
|
||||
<input class="email" type="email" required pattern=".*\.\w{2,}" placeholder="Email Address" />
|
||||
<input class="password" type="password" required placeholder="Password" />
|
||||
<a class="app-button" id="login_run">登录</a>
|
||||
<!-- <div class="app-register">
|
||||
Don't have an account? <a>Sign Up</a>
|
||||
</div> -->
|
||||
<svg id="svg-lines" version="1.1" xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 284.2 152.7"
|
||||
xml:space="preserve">
|
||||
<path class="st0"
|
||||
d="M37.7,107.3h222.6c12,0,21.8,9.7,21.8,21.7s-9.7,21.8-21.8,21.8c0,0-203.6,0-222.6,0S2.2,138.6,2.2,103.3 c0-52,113.5-101.5,141-101.5c13.5,0,21.8,9.7,21.8,21.8s-9.7,21.7-21.8,21.7s-21.8-9.7-21.8-21.7s9.7-21.8,21.8-21.8" />
|
||||
<path class="st1"
|
||||
d="M260.2,76.3L250,87.8l-9-9c-6.2-6.2,2-24.7,17.2-24.7c15.2,0,23.9,17.7,23.9,29.7s-11.7,23.5-23.9,23.5h-10.2">
|
||||
</path>
|
||||
<g class="svg-loader" xmlns="http://www.w3.org/2000/svg">
|
||||
<path class="svg-loader-segment -cal" d="M164.7,23.5c0-12-9.7-21.8-21.8-21.8" />
|
||||
<path class="svg-loader-segment -heart" d="M143,45.2c12,0,21.8-9.7,21.8-21.7" />
|
||||
<path class="svg-loader-segment -steps" d="M121.2,23.5c0,12,9.7,21.7,21.8,21.7" />
|
||||
<path class="svg-loader-segment -temp" d="M143,1.7c-12,0-21.8,9.7-21.8,21.8" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="login-background"></div>
|
||||
`
|
||||
|
||||
document.body.appendChild(div)
|
||||
let bg = div.querySelector('.login-background')
|
||||
bg.addEventListener('click', e => {
|
||||
div.style.display = 'none'
|
||||
})
|
||||
let login_btn = document.body.querySelector('#login_btn')
|
||||
// login_btn.href="";
|
||||
if (login_btn) {
|
||||
login_btn.innerHTML =
|
||||
'<svg stroke="currentColor" fill="none" stroke-width="0" viewBox="0 0 24 24" height="40px" width="40px" xmlns="http://www.w3.org/2000/svg"><path d="M12 17C14.2091 17 16 15.2091 16 13H8C8 15.2091 9.79086 17 12 17Z" fill="currentColor"></path><path d="M10 10C10 10.5523 9.55228 11 9 11C8.44772 11 8 10.5523 8 10C8 9.44772 8.44772 9 9 9C9.55228 9 10 9.44772 10 10Z" fill="currentColor"></path><path d="M15 11C15.5523 11 16 10.5523 16 10C16 9.44772 15.5523 9 15 9C14.4477 9 14 9.44772 14 10C14 10.5523 14.4477 11 15 11Z" fill="currentColor"></path><path fill-rule="evenodd" clip-rule="evenodd" d="M22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12ZM20 12C20 16.4183 16.4183 20 12 20C7.58172 20 4 16.4183 4 12C4 7.58172 7.58172 4 12 4C16.4183 4 20 7.58172 20 12Z" fill="currentColor"></path></svg>LOGIN'
|
||||
login_btn.addEventListener('click', e => {
|
||||
e.preventDefault()
|
||||
div.style.display = 'block'
|
||||
})
|
||||
}
|
||||
|
||||
let login_run = div.querySelector('#login_run')
|
||||
if (login_run) {
|
||||
login_run.addEventListener('click', e => {
|
||||
e.preventDefault()
|
||||
let ps = div.querySelector('.password')
|
||||
let email = div.querySelector('.email')
|
||||
div.style.display = 'none'
|
||||
console.log(ps.value, email.value)
|
||||
})
|
||||
}
|
||||
})()
|
||||
@@ -1,24 +0,0 @@
|
||||
::-webkit-scrollbar {
|
||||
width: 2px;
|
||||
}
|
||||
|
||||
@keyframes loading_mixlab {
|
||||
0% {
|
||||
background-color: green;
|
||||
}
|
||||
|
||||
50% {
|
||||
background-color: lightgreen;
|
||||
}
|
||||
|
||||
100% {
|
||||
background-color: green;
|
||||
}
|
||||
}
|
||||
|
||||
.loading_mixlab {
|
||||
background-color: green;
|
||||
animation-name: loading_mixlab;
|
||||
animation-duration: 2s;
|
||||
animation-iteration-count: infinite;
|
||||
}
|
||||
@@ -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"
|
||||
]
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user