Compare commits
22
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 |
+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/*
|
||||
|
||||
+64
-3
@@ -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
|
||||
|
||||
+4
-2
@@ -21,6 +21,7 @@ import json
|
||||
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:
|
||||
@@ -513,13 +514,14 @@ class RandomPrompt:
|
||||
# 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"}),
|
||||
},
|
||||
|
||||
|
||||
+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
|
||||
|
||||
@@ -8,3 +8,4 @@ simple-lama-inpainting
|
||||
clip-interrogator==0.6.0
|
||||
transformers>=4.36.0
|
||||
zhipuai
|
||||
lark-parser
|
||||
+395
-86
@@ -23,6 +23,7 @@
|
||||
min-width: 400px;
|
||||
margin-left: 5%;
|
||||
user-select: none;
|
||||
margin-top: 32px;
|
||||
}
|
||||
|
||||
.apps {
|
||||
@@ -41,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;
|
||||
@@ -50,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 {
|
||||
@@ -77,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;
|
||||
@@ -95,9 +116,8 @@
|
||||
}
|
||||
|
||||
|
||||
.apps .card .version {
|
||||
.card .version {
|
||||
font-size: 12px;
|
||||
|
||||
}
|
||||
|
||||
.card .toggle {
|
||||
@@ -254,6 +274,7 @@
|
||||
border-radius: 100%;
|
||||
cursor: pointer;
|
||||
border: 3px solid;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
@@ -385,9 +406,11 @@
|
||||
<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 class="header">
|
||||
<div style="margin: 0 24px;
|
||||
@@ -400,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'
|
||||
@@ -444,7 +467,6 @@
|
||||
body
|
||||
})
|
||||
|
||||
|
||||
let data = await resp.json()
|
||||
// console.log(data)
|
||||
let { name, subfolder } = data
|
||||
@@ -680,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') {
|
||||
@@ -699,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
|
||||
@@ -739,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
|
||||
}
|
||||
|
||||
@@ -1133,7 +1185,7 @@
|
||||
// ];
|
||||
inputData = inputData.filter(inp => inp);
|
||||
// console.log('inputData',inputData)
|
||||
inputData.forEach(data => {
|
||||
inputData.forEach(async data => {
|
||||
console.log(data)
|
||||
|
||||
// 图片 or 视频输入
|
||||
@@ -1204,9 +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.setAttribute('onerror', `this.src='${base64Df}'`)
|
||||
|
||||
|
||||
}
|
||||
|
||||
imageElement.style.maxWidth = '200px';
|
||||
@@ -1323,7 +1375,7 @@
|
||||
}
|
||||
|
||||
// 文本输入支持
|
||||
if (["TextInput_", "CLIPTextEncode", "PromptSimplification","ChinesePrompt_Mix"].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';
|
||||
@@ -1392,6 +1444,32 @@
|
||||
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) {
|
||||
@@ -1713,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>`
|
||||
|
||||
@@ -1863,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 {
|
||||
@@ -2076,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)
|
||||
@@ -2097,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) {
|
||||
@@ -2113,6 +2282,8 @@
|
||||
// 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);
|
||||
@@ -2144,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}`);
|
||||
@@ -2303,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 = [], 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');
|
||||
@@ -2331,22 +2464,34 @@
|
||||
<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)
|
||||
@@ -2358,7 +2503,165 @@
|
||||
}
|
||||
|
||||
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 innerApp = checkIsInnerApp();
|
||||
@@ -2371,6 +2674,8 @@
|
||||
|
||||
createAppList(window._apps);
|
||||
|
||||
if (window._apps.length > 0) await createHistoryList(category || '');
|
||||
|
||||
createApp(window._appData);
|
||||
}
|
||||
|
||||
@@ -2389,7 +2694,7 @@
|
||||
window.parent.postMessage({ innerApp, cmd: 'init' }, '*');
|
||||
|
||||
// 在iframe中监听来自父窗口的消息
|
||||
window.addEventListener("message", function (event) {
|
||||
window.addEventListener("message", async function (event) {
|
||||
console.log("Received message from parent:", event.data);
|
||||
const { init, url } = event.data;
|
||||
|
||||
@@ -2402,6 +2707,8 @@
|
||||
if (window._appData) {
|
||||
createAppList(window._apps, innerApp);
|
||||
|
||||
// await createHistoryList();
|
||||
|
||||
createApp(window._appData);
|
||||
} else {
|
||||
// todo welcome页面
|
||||
@@ -2422,6 +2729,8 @@
|
||||
|
||||
</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
|
||||
|
||||
+64
-14
@@ -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,7 +1528,7 @@ app.registerExtension({
|
||||
|
||||
widget.element.addEventListener('mouseover', e => {
|
||||
// console.log(node.widgets_values[index])
|
||||
if (node.widgets_values&&node.widgets_values[index])
|
||||
if (node.widgets_values && node.widgets_values[index])
|
||||
widget.element.setAttribute('title', node.widgets_values[index])
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user