Model Previews can now be set in Model View.
- Abstracted out Radio Buttons for Preview selection (mostly clean). - Added REST API for preview/set and preview/delete. - Added dateModified to query string so the browser can detect out of date preview images. - Added image path and dateModified to Model Info payload.
This commit is contained in:
@@ -34,6 +34,7 @@ I made this fork because the original repo was inactive and missing many things
|
|||||||
- View model metadata, including training tags and bucket resolutions.
|
- View model metadata, including training tags and bucket resolutions.
|
||||||
- Delete or move a model.
|
- Delete or move a model.
|
||||||
- Read, edit and save notes in a `.txt` file beside the model.
|
- Read, edit and save notes in a `.txt` file beside the model.
|
||||||
|
- Change or remove preview image.
|
||||||
|
|
||||||
### ComfyUI Node Graph
|
### ComfyUI Node Graph
|
||||||
|
|
||||||
|
|||||||
213
__init__.py
213
__init__.py
@@ -1,4 +1,5 @@
|
|||||||
import os
|
import os
|
||||||
|
import io
|
||||||
import pathlib
|
import pathlib
|
||||||
import shutil
|
import shutil
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
@@ -30,7 +31,7 @@ ui_settings_uri = os.path.join(extension_uri, "ui_settings.yaml")
|
|||||||
server_settings_uri = os.path.join(extension_uri, "server_settings.yaml")
|
server_settings_uri = os.path.join(extension_uri, "server_settings.yaml")
|
||||||
|
|
||||||
fallback_model_extensions = set([".bin", ".ckpt", ".onnx", ".pt", ".pth", ".safetensors"]) # TODO: magic values
|
fallback_model_extensions = set([".bin", ".ckpt", ".onnx", ".pt", ".pth", ".safetensors"]) # TODO: magic values
|
||||||
image_extensions = (".apng", ".gif", ".jpeg", ".jpg", ".png", ".webp")
|
image_extensions = (".apng", ".gif", ".jpeg", ".jpg", ".png", ".webp") # TODO: JavaScript does not know about this (x2 states)
|
||||||
#video_extensions = (".avi", ".mp4", ".webm") # TODO: Requires ffmpeg or cv2. Cache preview frame?
|
#video_extensions = (".avi", ".mp4", ".webm") # TODO: Requires ffmpeg or cv2. Cache preview frame?
|
||||||
|
|
||||||
_folder_names_and_paths = None # dict[str, tuple[list[str], list[str]]]
|
_folder_names_and_paths = None # dict[str, tuple[list[str], list[str]]]
|
||||||
@@ -195,21 +196,11 @@ async def get_model_preview(request):
|
|||||||
image_path = no_preview_image
|
image_path = no_preview_image
|
||||||
image_extension = "png"
|
image_extension = "png"
|
||||||
|
|
||||||
if uri != "no-post":
|
if uri != "no-preview":
|
||||||
rel_image_path = os.path.dirname(uri)
|
sep = os.path.sep
|
||||||
|
uri = uri.replace("/" if sep == "\\" else "/", os.path.sep)
|
||||||
i = uri.find(os.path.sep)
|
image_path, _ = search_path_to_system_path(uri)
|
||||||
model_type = uri[0:i]
|
if os.path.exists(image_path):
|
||||||
|
|
||||||
j = uri.find(os.path.sep, i + len(os.path.sep))
|
|
||||||
if j == -1:
|
|
||||||
j = len(rel_image_path)
|
|
||||||
base_index = int(uri[i + len(os.path.sep):j])
|
|
||||||
base_path = folder_paths_get_folder_paths(model_type)[base_index]
|
|
||||||
|
|
||||||
abs_image_path = os.path.normpath(base_path + os.path.sep + uri[j:]) # do NOT use os.path.join
|
|
||||||
if os.path.exists(abs_image_path):
|
|
||||||
image_path = abs_image_path
|
|
||||||
_, image_extension = os.path.splitext(uri)
|
_, image_extension = os.path.splitext(uri)
|
||||||
image_extension = image_extension[1:]
|
image_extension = image_extension[1:]
|
||||||
|
|
||||||
@@ -219,6 +210,64 @@ async def get_model_preview(request):
|
|||||||
return web.Response(body=image_data, content_type="image/" + image_extension)
|
return web.Response(body=image_data, content_type="image/" + image_extension)
|
||||||
|
|
||||||
|
|
||||||
|
def download_model_preview(formdata):
|
||||||
|
path = formdata.get("path", None)
|
||||||
|
if type(path) is not str:
|
||||||
|
raise ("Invalid path!")
|
||||||
|
path, _ = search_path_to_system_path(path)
|
||||||
|
path_without_extension, _ = os.path.splitext(path)
|
||||||
|
|
||||||
|
overwrite = formdata.get("overwrite", "true").lower()
|
||||||
|
overwrite = True if overwrite == "true" else False
|
||||||
|
|
||||||
|
image = formdata.get("image", None)
|
||||||
|
if type(image) is str:
|
||||||
|
image_path = download_image(image, path, overwrite)
|
||||||
|
_, image_extension = os.path.splitext(image_path)
|
||||||
|
else:
|
||||||
|
content_type = image.content_type
|
||||||
|
if not content_type.startswith("image/"):
|
||||||
|
raise ("Invalid content type!")
|
||||||
|
image_extension = "." + content_type[len("image/"):]
|
||||||
|
if image_extension not in image_extensions:
|
||||||
|
raise ("Invalid extension!")
|
||||||
|
|
||||||
|
image_path = path_without_extension + image_extension
|
||||||
|
if not overwrite and os.path.isfile(image_path):
|
||||||
|
raise ("Image already exists!")
|
||||||
|
file: io.IOBase = image.file
|
||||||
|
image_data = file.read()
|
||||||
|
with open(image_path, "wb") as f:
|
||||||
|
f.write(image_data)
|
||||||
|
|
||||||
|
delete_same_name_files(path_without_extension, image_extensions, image_extension)
|
||||||
|
|
||||||
|
|
||||||
|
@server.PromptServer.instance.routes.post("/model-manager/preview/set")
|
||||||
|
async def set_model_preview(request):
|
||||||
|
formdata = await request.post()
|
||||||
|
try:
|
||||||
|
download_model_preview(formdata)
|
||||||
|
return web.json_response({ "success": True })
|
||||||
|
except ValueError as e:
|
||||||
|
print(e, file=sys.stderr, flush=True)
|
||||||
|
return web.json_response({ "success": False })
|
||||||
|
|
||||||
|
|
||||||
|
@server.PromptServer.instance.routes.post("/model-manager/preview/delete")
|
||||||
|
async def delete_model_preview(request):
|
||||||
|
model_path = request.query.get("path", None)
|
||||||
|
if model_path is None:
|
||||||
|
return web.json_response({ "success": False })
|
||||||
|
model_path = urllib.parse.unquote(model_path)
|
||||||
|
|
||||||
|
file, _ = search_path_to_system_path(model_path)
|
||||||
|
path_and_name, _ = os.path.splitext(file)
|
||||||
|
delete_same_name_files(path_and_name, image_extensions)
|
||||||
|
|
||||||
|
return web.json_response({ "success": True })
|
||||||
|
|
||||||
|
|
||||||
@server.PromptServer.instance.routes.get("/model-manager/models/list")
|
@server.PromptServer.instance.routes.get("/model-manager/models/list")
|
||||||
async def load_download_models(request):
|
async def load_download_models(request):
|
||||||
model_types = os.listdir(comfyui_model_uri)
|
model_types = os.listdir(comfyui_model_uri)
|
||||||
@@ -245,34 +294,40 @@ async def load_download_models(request):
|
|||||||
for model in dir_models:
|
for model in dir_models:
|
||||||
model_name, _ = os.path.splitext(model)
|
model_name, _ = os.path.splitext(model)
|
||||||
image = None
|
image = None
|
||||||
|
image_modified = None
|
||||||
for iImage in range(len(dir_images)-1, -1, -1):
|
for iImage in range(len(dir_images)-1, -1, -1):
|
||||||
image_name, _ = os.path.splitext(dir_images[iImage])
|
image_name, _ = os.path.splitext(dir_images[iImage])
|
||||||
if model_name == image_name:
|
if model_name == image_name:
|
||||||
image = end_swap_and_pop(dir_images, iImage)
|
image = end_swap_and_pop(dir_images, iImage)
|
||||||
|
img_abs_path = os.path.join(cwd, image)
|
||||||
|
image_modified = pathlib.Path(img_abs_path).stat().st_mtime_ns
|
||||||
break
|
break
|
||||||
abs_path = os.path.join(cwd, model)
|
abs_path = os.path.join(cwd, model)
|
||||||
stats = pathlib.Path(abs_path).stat()
|
stats = pathlib.Path(abs_path).stat()
|
||||||
date_modified = stats.st_mtime_ns
|
model_modified = stats.st_mtime_ns
|
||||||
date_created = stats.st_ctime_ns
|
model_created = stats.st_ctime_ns
|
||||||
rel_path = "" if cwd == model_base_path else os.path.relpath(cwd, model_base_path)
|
rel_path = "" if cwd == model_base_path else os.path.relpath(cwd, model_base_path)
|
||||||
info = (model, image, base_path_index, rel_path, date_modified, date_created)
|
info = (model, image, base_path_index, rel_path, model_modified, model_created, image_modified)
|
||||||
file_infos.append(info)
|
file_infos.append(info)
|
||||||
file_infos.sort(key=lambda tup: tup[4], reverse=True) # TODO: remove sort; sorted on client
|
file_infos.sort(key=lambda tup: tup[4], reverse=True) # TODO: remove sort; sorted on client
|
||||||
|
|
||||||
model_items = []
|
model_items = []
|
||||||
for model, image, base_path_index, rel_path, date_modified, date_created in file_infos:
|
for model, image, base_path_index, rel_path, model_modified, model_created, image_modified in file_infos:
|
||||||
item = {
|
item = {
|
||||||
"name": model,
|
"name": model,
|
||||||
"path": "/" + os.path.join(model_type, str(base_path_index), rel_path, model).replace(os.path.sep, "/"), # relative logical path
|
"path": "/" + os.path.join(model_type, str(base_path_index), rel_path, model).replace(os.path.sep, "/"), # relative logical path
|
||||||
#"systemPath": os.path.join(rel_path, model), # relative system path (less information than "search path")
|
#"systemPath": os.path.join(rel_path, model), # relative system path (less information than "search path")
|
||||||
"dateModified": date_modified,
|
"dateModified": model_modified,
|
||||||
"dateCreated": date_created,
|
"dateCreated": model_created,
|
||||||
#"dateLastUsed": "", # TODO: track server-side, send increment client-side
|
#"dateLastUsed": "", # TODO: track server-side, send increment client-side
|
||||||
#"countUsed": 0, # TODO: track server-side, send increment client-side
|
#"countUsed": 0, # TODO: track server-side, send increment client-side
|
||||||
}
|
}
|
||||||
if image is not None:
|
if image is not None:
|
||||||
raw_post = os.path.join(model_type, str(base_path_index), rel_path, image)
|
raw_post = os.path.join(model_type, str(base_path_index), rel_path, image)
|
||||||
item["post"] = urllib.parse.quote_plus(raw_post)
|
item["preview"] = {
|
||||||
|
"path": urllib.parse.quote_plus(raw_post),
|
||||||
|
"dateModified": urllib.parse.quote_plus(str(image_modified)),
|
||||||
|
}
|
||||||
model_items.append(item)
|
model_items.append(item)
|
||||||
|
|
||||||
models[model_type] = model_items
|
models[model_type] = model_items
|
||||||
@@ -342,13 +397,14 @@ async def directory_list(request):
|
|||||||
|
|
||||||
def download_file(url, filename, overwrite):
|
def download_file(url, filename, overwrite):
|
||||||
if not overwrite and os.path.isfile(filename):
|
if not overwrite and os.path.isfile(filename):
|
||||||
raise Exception("File already exists!")
|
raise ValueError("File already exists!")
|
||||||
|
|
||||||
filename_temp = filename + ".download"
|
filename_temp = filename + ".download"
|
||||||
|
|
||||||
def_headers = {
|
def_headers = {
|
||||||
"User-Agent": "Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148",
|
"User-Agent": "Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148",
|
||||||
}
|
}
|
||||||
|
|
||||||
if url.startswith("https://civitai.com/"):
|
if url.startswith("https://civitai.com/"):
|
||||||
api_key = server_settings["civitai_api_key"]
|
api_key = server_settings["civitai_api_key"]
|
||||||
if (api_key != ""):
|
if (api_key != ""):
|
||||||
@@ -358,10 +414,9 @@ def download_file(url, filename, overwrite):
|
|||||||
api_key = server_settings["huggingface_api_key"]
|
api_key = server_settings["huggingface_api_key"]
|
||||||
if api_key != "":
|
if api_key != "":
|
||||||
def_headers["Authorization"] = f"Bearer {api_key}"
|
def_headers["Authorization"] = f"Bearer {api_key}"
|
||||||
|
|
||||||
rh = requests.get(url=url, stream=True, verify=False, headers=def_headers, proxies=None, allow_redirects=False)
|
rh = requests.get(url=url, stream=True, verify=False, headers=def_headers, proxies=None, allow_redirects=False)
|
||||||
if not rh.ok:
|
if not rh.ok:
|
||||||
raise Exception("Unable to download")
|
raise ValueError("Unable to download")
|
||||||
|
|
||||||
downloaded_size = 0
|
downloaded_size = 0
|
||||||
if rh.status_code == 200 and os.path.exists(filename_temp):
|
if rh.status_code == 200 and os.path.exists(filename_temp):
|
||||||
@@ -369,7 +424,7 @@ def download_file(url, filename, overwrite):
|
|||||||
|
|
||||||
headers = {"Range": "bytes=%d-" % downloaded_size}
|
headers = {"Range": "bytes=%d-" % downloaded_size}
|
||||||
headers["User-Agent"] = def_headers["User-Agent"]
|
headers["User-Agent"] = def_headers["User-Agent"]
|
||||||
|
|
||||||
r = requests.get(url=url, stream=True, verify=False, headers=headers, proxies=None, allow_redirects=False)
|
r = requests.get(url=url, stream=True, verify=False, headers=headers, proxies=None, allow_redirects=False)
|
||||||
if rh.status_code == 307 and r.status_code == 307:
|
if rh.status_code == 307 and r.status_code == 307:
|
||||||
# Civitai redirect
|
# Civitai redirect
|
||||||
@@ -377,7 +432,7 @@ def download_file(url, filename, overwrite):
|
|||||||
if not redirect_url.startswith("http"):
|
if not redirect_url.startswith("http"):
|
||||||
# Civitai requires login (NSFW or user-required)
|
# Civitai requires login (NSFW or user-required)
|
||||||
# TODO: inform user WHY download failed
|
# TODO: inform user WHY download failed
|
||||||
raise Exception("Unable to download!")
|
raise ValueError("Unable to download!")
|
||||||
download_file(redirect_url, filename, overwrite)
|
download_file(redirect_url, filename, overwrite)
|
||||||
return
|
return
|
||||||
if rh.status_code == 302 and r.status_code == 302:
|
if rh.status_code == 302 and r.status_code == 302:
|
||||||
@@ -385,7 +440,7 @@ def download_file(url, filename, overwrite):
|
|||||||
redirect_url = r.content.decode("utf-8")
|
redirect_url = r.content.decode("utf-8")
|
||||||
redirect_url_index = redirect_url.find("http")
|
redirect_url_index = redirect_url.find("http")
|
||||||
if redirect_url_index == -1:
|
if redirect_url_index == -1:
|
||||||
raise Exception("Unable to download!")
|
raise ValueError("Unable to download!")
|
||||||
download_file(redirect_url[redirect_url_index:], filename, overwrite)
|
download_file(redirect_url[redirect_url_index:], filename, overwrite)
|
||||||
return
|
return
|
||||||
elif rh.status_code == 200 and r.status_code == 206:
|
elif rh.status_code == 200 and r.status_code == 206:
|
||||||
@@ -419,18 +474,33 @@ def download_file(url, filename, overwrite):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
|
|
||||||
print()
|
print()
|
||||||
|
|
||||||
if overwrite and os.path.isfile(filename):
|
if overwrite and os.path.isfile(filename):
|
||||||
os.remove(filename)
|
os.remove(filename)
|
||||||
os.rename(filename_temp, filename)
|
os.rename(filename_temp, filename)
|
||||||
|
|
||||||
|
|
||||||
|
def download_image(image_uri, model_path, overwrite):
|
||||||
|
extension = None # TODO: doesn't work for https://civitai.com/images/...
|
||||||
|
for image_extension in image_extensions:
|
||||||
|
if image_uri.endswith(image_extension):
|
||||||
|
extension = image_extension
|
||||||
|
break
|
||||||
|
if extension is None:
|
||||||
|
raise ValueError("Invalid image type!")
|
||||||
|
|
||||||
|
path_without_extension, _ = os.path.splitext(model_path)
|
||||||
|
file = path_without_extension + extension
|
||||||
|
download_file(image_uri, file, overwrite)
|
||||||
|
return file
|
||||||
|
|
||||||
|
|
||||||
@server.PromptServer.instance.routes.get("/model-manager/model/info")
|
@server.PromptServer.instance.routes.get("/model-manager/model/info")
|
||||||
async def get_model_info(request):
|
async def get_model_info(request):
|
||||||
model_path = request.query.get("path", None)
|
model_path = request.query.get("path", None)
|
||||||
if model_path is None:
|
if model_path is None:
|
||||||
return web.json_response({})
|
return web.json_response({ "success": False })
|
||||||
model_path = urllib.parse.unquote(model_path)
|
model_path = urllib.parse.unquote(model_path)
|
||||||
|
|
||||||
file, _ = search_path_to_system_path(model_path)
|
file, _ = search_path_to_system_path(model_path)
|
||||||
@@ -441,12 +511,25 @@ async def get_model_info(request):
|
|||||||
path, name = os.path.split(model_path)
|
path, name = os.path.split(model_path)
|
||||||
info["File Name"] = name
|
info["File Name"] = name
|
||||||
info["File Directory"] = path
|
info["File Directory"] = path
|
||||||
info["File Size"] = os.path.getsize(file)
|
info["File Size"] = str(os.path.getsize(file)) + " bytes"
|
||||||
stats = pathlib.Path(file).stat()
|
stats = pathlib.Path(file).stat()
|
||||||
date_format = "%Y/%m/%d %H:%M:%S"
|
date_format = "%Y/%m/%d %H:%M:%S"
|
||||||
info["Date Created"] = datetime.fromtimestamp(stats.st_ctime).strftime(date_format)
|
info["Date Created"] = datetime.fromtimestamp(stats.st_ctime).strftime(date_format)
|
||||||
info["Date Modified"] = datetime.fromtimestamp(stats.st_mtime).strftime(date_format)
|
info["Date Modified"] = datetime.fromtimestamp(stats.st_mtime).strftime(date_format)
|
||||||
|
|
||||||
|
file_name, _ = os.path.splitext(file)
|
||||||
|
|
||||||
|
for extension in image_extensions:
|
||||||
|
maybe_image = file_name + extension
|
||||||
|
if os.path.isfile(maybe_image):
|
||||||
|
image_path, _ = os.path.splitext(model_path)
|
||||||
|
image_modified = pathlib.Path(maybe_image).stat().st_mtime_ns
|
||||||
|
info["Preview"] = {
|
||||||
|
"path": urllib.parse.quote_plus(image_path + extension),
|
||||||
|
"dateModified": urllib.parse.quote_plus(str(image_modified)),
|
||||||
|
}
|
||||||
|
break
|
||||||
|
|
||||||
header = get_safetensor_header(file)
|
header = get_safetensor_header(file)
|
||||||
metadata = header.get("__metadata__", None)
|
metadata = header.get("__metadata__", None)
|
||||||
if metadata is not None:
|
if metadata is not None:
|
||||||
@@ -455,7 +538,6 @@ async def get_model_info(request):
|
|||||||
info["Hash"] = metadata.get("sshs_model_hash", "")
|
info["Hash"] = metadata.get("sshs_model_hash", "")
|
||||||
info["Output Name"] = metadata.get("ss_output_name", "")
|
info["Output Name"] = metadata.get("ss_output_name", "")
|
||||||
|
|
||||||
file_name, _ = os.path.splitext(file)
|
|
||||||
txt_file = file_name + ".txt"
|
txt_file = file_name + ".txt"
|
||||||
notes = ""
|
notes = ""
|
||||||
if os.path.isfile(txt_file):
|
if os.path.isfile(txt_file):
|
||||||
@@ -500,26 +582,27 @@ async def get_system_separator(request):
|
|||||||
|
|
||||||
@server.PromptServer.instance.routes.post("/model-manager/model/download")
|
@server.PromptServer.instance.routes.post("/model-manager/model/download")
|
||||||
async def download_model(request):
|
async def download_model(request):
|
||||||
body = await request.json()
|
formdata = await request.post()
|
||||||
result = {
|
result = {
|
||||||
"success": False,
|
"success": False,
|
||||||
"invalid": None,
|
"invalid": None,
|
||||||
}
|
}
|
||||||
|
|
||||||
overwrite = body.get("overwrite", False)
|
overwrite = formdata.get("overwrite", "false").lower()
|
||||||
|
overwrite = True if overwrite == "true" else False
|
||||||
|
|
||||||
model_path = body.get("path", "/0")
|
model_path = formdata.get("path", "/0")
|
||||||
directory, model_type = search_path_to_system_path(model_path)
|
directory, model_type = search_path_to_system_path(model_path)
|
||||||
if directory is None:
|
if directory is None:
|
||||||
result["invalid"] = "path"
|
result["invalid"] = "path"
|
||||||
return web.json_response(result)
|
return web.json_response(result)
|
||||||
|
|
||||||
download_uri = body.get("download")
|
download_uri = formdata.get("download")
|
||||||
if download_uri is None:
|
if download_uri is None:
|
||||||
result["invalid"] = "download"
|
result["invalid"] = "download"
|
||||||
return web.json_response(result)
|
return web.json_response(result)
|
||||||
|
|
||||||
name = body.get("name")
|
name = formdata.get("name")
|
||||||
model_extension = None
|
model_extension = None
|
||||||
for ext in folder_paths_get_supported_pt_extensions(model_type):
|
for ext in folder_paths_get_supported_pt_extensions(model_type):
|
||||||
if name.endswith(ext):
|
if name.endswith(ext):
|
||||||
@@ -531,27 +614,22 @@ async def download_model(request):
|
|||||||
file_name = os.path.join(directory, name)
|
file_name = os.path.join(directory, name)
|
||||||
try:
|
try:
|
||||||
download_file(download_uri, file_name, overwrite)
|
download_file(download_uri, file_name, overwrite)
|
||||||
except:
|
except Exception as e:
|
||||||
result["invalid"] = "download"
|
print(e, file=sys.stderr, flush=True)
|
||||||
|
result["invalid"] = "model"
|
||||||
return web.json_response(result)
|
return web.json_response(result)
|
||||||
|
|
||||||
image_uri = body.get("image")
|
image = formdata.get("image")
|
||||||
if image_uri is not None and image_uri != "":
|
if image is not None and image != "":
|
||||||
image_extension = None # TODO: doesn't work for https://civitai.com/images/...
|
try:
|
||||||
for ext in image_extensions:
|
download_model_preview({
|
||||||
if image_uri.endswith(ext):
|
"path": model_path + os.sep + name,
|
||||||
image_extension = ext
|
"image": image,
|
||||||
break
|
"overwrite": formdata.get("overwrite"),
|
||||||
if image_extension is not None:
|
})
|
||||||
file_path_without_extension = name[:len(name) - len(model_extension)]
|
except Exception as e:
|
||||||
image_name = os.path.join(
|
print(e, file=sys.stderr, flush=True)
|
||||||
directory,
|
result["invalid"] = "preview"
|
||||||
file_path_without_extension + image_extension
|
|
||||||
)
|
|
||||||
try:
|
|
||||||
download_file(image_uri, image_name, overwrite)
|
|
||||||
except Exception as e:
|
|
||||||
print(e, file=sys.stderr, flush=True)
|
|
||||||
|
|
||||||
result["success"] = True
|
result["success"] = True
|
||||||
return web.json_response(result)
|
return web.json_response(result)
|
||||||
@@ -579,7 +657,8 @@ async def move_model(request):
|
|||||||
new_file = os.path.join(new_path, filename)
|
new_file = os.path.join(new_path, filename)
|
||||||
try:
|
try:
|
||||||
shutil.move(old_file, new_file)
|
shutil.move(old_file, new_file)
|
||||||
except:
|
except ValueError as e:
|
||||||
|
print(e, file=sys.stderr, flush=True)
|
||||||
return web.json_response({ "success": False })
|
return web.json_response({ "success": False })
|
||||||
|
|
||||||
old_file_without_extension, _ = os.path.splitext(old_file)
|
old_file_without_extension, _ = os.path.splitext(old_file)
|
||||||
@@ -590,12 +669,20 @@ async def move_model(request):
|
|||||||
if os.path.isfile(old_file):
|
if os.path.isfile(old_file):
|
||||||
try:
|
try:
|
||||||
shutil.move(old_file, new_file_without_extension + extension)
|
shutil.move(old_file, new_file_without_extension + extension)
|
||||||
except Exception as e:
|
except ValueError as e:
|
||||||
print(e, file=sys.stderr, flush=True)
|
print(e, file=sys.stderr, flush=True)
|
||||||
|
|
||||||
return web.json_response({ "success": True })
|
return web.json_response({ "success": True })
|
||||||
|
|
||||||
|
|
||||||
|
def delete_same_name_files(path_without_extension, extensions, keep_extension=None):
|
||||||
|
for extension in extensions:
|
||||||
|
if extension == keep_extension: continue
|
||||||
|
image_file = path_without_extension + extension
|
||||||
|
if os.path.isfile(image_file):
|
||||||
|
os.remove(image_file)
|
||||||
|
|
||||||
|
|
||||||
@server.PromptServer.instance.routes.post("/model-manager/model/delete")
|
@server.PromptServer.instance.routes.post("/model-manager/model/delete")
|
||||||
async def delete_model(request):
|
async def delete_model(request):
|
||||||
result = { "success": False }
|
result = { "success": False }
|
||||||
@@ -623,10 +710,7 @@ async def delete_model(request):
|
|||||||
|
|
||||||
path_and_name, _ = os.path.splitext(file)
|
path_and_name, _ = os.path.splitext(file)
|
||||||
|
|
||||||
for img_ext in image_extensions:
|
delete_same_name_files(path_and_name, image_extensions)
|
||||||
image_file = path_and_name + img_ext
|
|
||||||
if os.path.isfile(image_file):
|
|
||||||
os.remove(image_file)
|
|
||||||
|
|
||||||
txt_file = path_and_name + ".txt"
|
txt_file = path_and_name + ".txt"
|
||||||
if os.path.isfile(txt_file):
|
if os.path.isfile(txt_file):
|
||||||
@@ -656,7 +740,8 @@ async def set_notes(request):
|
|||||||
try:
|
try:
|
||||||
with open(filename, "w", encoding="utf-8") as f:
|
with open(filename, "w", encoding="utf-8") as f:
|
||||||
f.write(text)
|
f.write(text)
|
||||||
except:
|
except ValueError as e:
|
||||||
|
print(e, file=sys.stderr, flush=True)
|
||||||
web.json_response({ "success": False })
|
web.json_response({ "success": False })
|
||||||
|
|
||||||
return web.json_response({ "success": True })
|
return web.json_response({ "success": True })
|
||||||
|
|||||||
@@ -159,6 +159,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.comfy-radio {
|
.comfy-radio {
|
||||||
@@ -358,6 +359,7 @@
|
|||||||
|
|
||||||
.model-manager .tab-header-flex-block {
|
.model-manager .tab-header-flex-block {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.model-manager .search-models {
|
.model-manager .search-models {
|
||||||
@@ -377,6 +379,7 @@
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
min-height: 36px;
|
min-height: 36px;
|
||||||
padding-block: 0;
|
padding-block: 0;
|
||||||
|
min-width: 36px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.model-manager .model-select-dropdown {
|
.model-manager .model-select-dropdown {
|
||||||
@@ -450,6 +453,15 @@
|
|||||||
resize: vertical;
|
resize: vertical;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.model-preview-select-radio-container {
|
||||||
|
min-width: 0;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-manager input[type="file"] {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.model-preview-select-radio-container img {
|
.model-preview-select-radio-container img {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 230px;
|
width: 230px;
|
||||||
@@ -485,7 +497,6 @@
|
|||||||
background-color: var(--bg-color);
|
background-color: var(--bg-color);
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
color: var(--fg-color);
|
color: var(--fg-color);
|
||||||
margin-top: 8px;
|
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
width: auto;
|
width: auto;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,10 +19,10 @@ function debounce(callback, delay) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} url
|
* @param {string} url
|
||||||
* @param {any} options
|
* @param {any} [options=undefined]
|
||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
function request(url, options) {
|
function request(url, options = undefined) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
api.fetchApi(url, options)
|
api.fetchApi(url, options)
|
||||||
.then((response) => response.json())
|
.then((response) => response.json())
|
||||||
@@ -58,6 +58,22 @@ const MODEL_SORT_DATE_NAME = "name";
|
|||||||
const MODEL_EXTENSIONS = [".bin", ".ckpt", ".onnx", ".pt", ".pth", ".safetensors"]; // TODO: ask server for?
|
const MODEL_EXTENSIONS = [".bin", ".ckpt", ".onnx", ".pt", ".pth", ".safetensors"]; // TODO: ask server for?
|
||||||
const IMAGE_EXTENSIONS = [".apng", ".gif", ".jpeg", ".jpg", ".png", ".webp"]; // TODO: ask server for?
|
const IMAGE_EXTENSIONS = [".apng", ".gif", ".jpeg", ".jpg", ".png", ".webp"]; // TODO: ask server for?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string | undefined} [searchPath=undefined]
|
||||||
|
* @param {string | undefined} [dateImageModified=undefined]
|
||||||
|
*
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
function imageUri(imageSearchPath = undefined, dateImageModified = undefined) {
|
||||||
|
const path = imageSearchPath ?? "no-preview";
|
||||||
|
const date = dateImageModified;
|
||||||
|
let uri = `/model-manager/preview/get?uri=${path}`;
|
||||||
|
if (date !== undefined && date !== null) {
|
||||||
|
uri += `&v=${date}`;
|
||||||
|
}
|
||||||
|
return uri;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tries to return the related ComfyUI model directory if unambigious.
|
* Tries to return the related ComfyUI model directory if unambigious.
|
||||||
*
|
*
|
||||||
@@ -1112,8 +1128,7 @@ class ModelGrid {
|
|||||||
const removeEmbeddingExtension = !settingsElements["model-add-embedding-extension"].checked;
|
const removeEmbeddingExtension = !settingsElements["model-add-embedding-extension"].checked;
|
||||||
if (models.length > 0) {
|
if (models.length > 0) {
|
||||||
return models.map((item) => {
|
return models.map((item) => {
|
||||||
const uri = item.post ?? "no-post";
|
const previewInfo = item.preview;
|
||||||
const imgUrl = `/model-manager/preview/get?uri=${uri}`;
|
|
||||||
const searchPath = item.path;
|
const searchPath = item.path;
|
||||||
const path = searchPathToSystemPath(searchPath, searchSeparator, systemSeparator);
|
const path = searchPathToSystemPath(searchPath, searchSeparator, systemSeparator);
|
||||||
let buttons = [];
|
let buttons = [];
|
||||||
@@ -1157,7 +1172,7 @@ class ModelGrid {
|
|||||||
);
|
);
|
||||||
return $el("div.item", {}, [
|
return $el("div.item", {}, [
|
||||||
$el("img.model-preview", {
|
$el("img.model-preview", {
|
||||||
src: imgUrl,
|
src: imageUri(previewInfo?.path, previewInfo?.dateModified),
|
||||||
draggable: false,
|
draggable: false,
|
||||||
}),
|
}),
|
||||||
$el("div.model-preview-overlay", {
|
$el("div.model-preview-overlay", {
|
||||||
@@ -1293,6 +1308,284 @@ function $radioGroup(attr) {
|
|||||||
return $el("div.comfy-radio-group", radioGroup);
|
return $el("div.comfy-radio-group", radioGroup);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {HTMLDivElement} previewImageContainer
|
||||||
|
* @param {Event} e
|
||||||
|
* @param {1 | -1} step
|
||||||
|
*/
|
||||||
|
function updateRadioPreview(previewImageContainer, step) {
|
||||||
|
const children = previewImageContainer.children;
|
||||||
|
if (children.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let currentIndex = -step;
|
||||||
|
for (let i = 0; i < children.length; i++) {
|
||||||
|
const previewImage = children[i];
|
||||||
|
const display = previewImage.style.display;
|
||||||
|
if (display !== "none") {
|
||||||
|
currentIndex = i;
|
||||||
|
}
|
||||||
|
previewImage.style.display = "none";
|
||||||
|
}
|
||||||
|
currentIndex = currentIndex + step;
|
||||||
|
if (currentIndex >= children.length) { currentIndex = 0; }
|
||||||
|
else if (currentIndex < 0) { currentIndex = children.length - 1; }
|
||||||
|
children[currentIndex].style.display = "block";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {String} uniqueName
|
||||||
|
* @param {String[]} defaultPreviews
|
||||||
|
* @returns {[]}
|
||||||
|
*/
|
||||||
|
function radioGroupImageSelect(uniqueName, defaultPreviews, defaultChanges=false) {
|
||||||
|
const defaultImageCount = defaultPreviews.length;
|
||||||
|
|
||||||
|
const el_defaultUri = $el("div", {
|
||||||
|
style: { display: "none" },
|
||||||
|
"data-noimage": imageUri(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const el_noImage = $el("img", {
|
||||||
|
src: imageUri(),
|
||||||
|
style: {
|
||||||
|
display: defaultImageCount === 0 ? "block" : "none",
|
||||||
|
},
|
||||||
|
loading: "lazy",
|
||||||
|
});
|
||||||
|
|
||||||
|
const el_defaultImages = $el("div", {
|
||||||
|
style: {
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
},
|
||||||
|
}, (() => {
|
||||||
|
const imgs = defaultPreviews.map((url) => {
|
||||||
|
return $el("img", {
|
||||||
|
src: url,
|
||||||
|
style: { display: "none" },
|
||||||
|
loading: "lazy",
|
||||||
|
onerror: (e) => {
|
||||||
|
e.target.src = el_defaultUri.dataset.noimage ?? imageUri();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
if (imgs.length > 0) {
|
||||||
|
imgs[0].style.display = "block";
|
||||||
|
}
|
||||||
|
return imgs;
|
||||||
|
})());
|
||||||
|
|
||||||
|
const el_uploadImage = $el("img", {
|
||||||
|
src: imageUri(),
|
||||||
|
style: { display : "none" },
|
||||||
|
onerror: (e) => {
|
||||||
|
e.target.src = el_defaultUri.dataset.noimage ?? imageUri();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const el_uploadFile = $el("input", {
|
||||||
|
type: "file",
|
||||||
|
accept: IMAGE_EXTENSIONS.join(", "),
|
||||||
|
onchange: (e) => {
|
||||||
|
const file = e.target.files[0];
|
||||||
|
if (file) {
|
||||||
|
el_uploadImage.src = URL.createObjectURL(file);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
el_uploadImage.src = el_defaultUri.dataset.noimage;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const el_upload = $el("div", {
|
||||||
|
style: { display: "none" },
|
||||||
|
}, [
|
||||||
|
el_uploadFile,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const el_urlImage = $el("img", {
|
||||||
|
src: imageUri(),
|
||||||
|
style: { display: "none" },
|
||||||
|
onerror: (e) => {
|
||||||
|
e.target.src = el_defaultUri.dataset.noimage ?? imageUri();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const el_customUrl = $el("input.search-text-area", {
|
||||||
|
type: "text",
|
||||||
|
placeholder: "https://custom-image-preview.png",
|
||||||
|
});
|
||||||
|
const el_custom = $el("div.row.tab-header-flex-block", {
|
||||||
|
style: { display: "none" },
|
||||||
|
}, [
|
||||||
|
el_customUrl,
|
||||||
|
$el("button.icon-button", {
|
||||||
|
textContent: "🔍︎",
|
||||||
|
onclick: (e) => {
|
||||||
|
el_urlImage.src = el_customUrl.value;
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const el_previewButtons = $el("div.model-preview-overlay", {
|
||||||
|
style: {
|
||||||
|
display: el_defaultImages.children.length > 1 ? "block" : "none",
|
||||||
|
},
|
||||||
|
}, [
|
||||||
|
$el("button.icon-button.model-preview-button-left", {
|
||||||
|
textContent: "←",
|
||||||
|
onclick: () => updateRadioPreview(el_defaultImages, -1),
|
||||||
|
}),
|
||||||
|
$el("button.icon-button.model-preview-button-right", {
|
||||||
|
textContent: "→",
|
||||||
|
onclick: () => updateRadioPreview(el_defaultImages, 1),
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
const previews = [
|
||||||
|
el_noImage,
|
||||||
|
el_defaultImages,
|
||||||
|
el_urlImage,
|
||||||
|
el_uploadImage,
|
||||||
|
];
|
||||||
|
const el_preview = $el("div.item", [
|
||||||
|
$el("div", {
|
||||||
|
style: {
|
||||||
|
"width": "100%",
|
||||||
|
"height": "100%",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
previews,
|
||||||
|
),
|
||||||
|
el_previewButtons,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const PREVIEW_NONE = "No Preview";
|
||||||
|
const PREVIEW_DEFAULT = "Default";
|
||||||
|
const PREVIEW_URL = "URL";
|
||||||
|
const PREVIEW_UPLOAD = "Upload";
|
||||||
|
|
||||||
|
const el_radioButtons = $radioGroup({
|
||||||
|
name: uniqueName,
|
||||||
|
onchange: (value) => {
|
||||||
|
el_custom.style.display = "none";
|
||||||
|
el_upload.style.display = "none";
|
||||||
|
|
||||||
|
el_defaultImages.style.display = "none";
|
||||||
|
el_previewButtons.style.display = "none";
|
||||||
|
|
||||||
|
el_noImage.style.display = "none";
|
||||||
|
el_uploadImage.style.display = "none";
|
||||||
|
el_urlImage.style.display = "none";
|
||||||
|
|
||||||
|
switch (value) {
|
||||||
|
case PREVIEW_NONE:
|
||||||
|
default:
|
||||||
|
el_noImage.style.display = "block";
|
||||||
|
break;
|
||||||
|
case PREVIEW_DEFAULT:
|
||||||
|
el_defaultImages.style.display = "block";
|
||||||
|
el_previewButtons.style.display = el_defaultImages.children.length > 1 ? "block" : "none";
|
||||||
|
break;
|
||||||
|
case PREVIEW_URL:
|
||||||
|
el_custom.style.display = "flex";
|
||||||
|
el_urlImage.style.display = "block";
|
||||||
|
break;
|
||||||
|
case PREVIEW_UPLOAD:
|
||||||
|
el_upload.style.display = "flex";
|
||||||
|
el_uploadImage.style.display = "block";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
options: (() => {
|
||||||
|
const radios = [];
|
||||||
|
radios.push({ value: PREVIEW_NONE });
|
||||||
|
if (defaultImageCount > 0) {
|
||||||
|
radios.push({ value: PREVIEW_DEFAULT });
|
||||||
|
}
|
||||||
|
radios.push({ value: PREVIEW_URL });
|
||||||
|
radios.push({ value: PREVIEW_UPLOAD })
|
||||||
|
return radios;
|
||||||
|
})(),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (defaultImageCount > 0) {
|
||||||
|
const children = el_radioButtons.children;
|
||||||
|
for (let i = 0; i < children.length; i++) {
|
||||||
|
const child = children[i];
|
||||||
|
const radioButton = child.children[0];
|
||||||
|
if (radioButton.value === PREVIEW_DEFAULT) {
|
||||||
|
radioButton.checked = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const resetModelInfoPreview = () => {
|
||||||
|
let noimage = el_defaultUri.dataset.noimage;
|
||||||
|
previews.forEach((el) => {
|
||||||
|
el.style.display = "none";
|
||||||
|
if (el_noImage !== el) {
|
||||||
|
if (el.nodeName === "IMG") {
|
||||||
|
el.src = noimage;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
el.children[0].src = noimage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
el.src = imageUri();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const children = el_radioButtons.children;
|
||||||
|
for (let i = 0; i < children.length; i++) {
|
||||||
|
const child = children[i];
|
||||||
|
const radioButton = child.children[0];
|
||||||
|
if (radioButton.value === PREVIEW_DEFAULT) {
|
||||||
|
el_defaultImages.style.display = "block";
|
||||||
|
radioButton.checked = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
el_uploadFile.value = "";
|
||||||
|
el_customUrl.value = "";
|
||||||
|
el_upload.style.display = "none";
|
||||||
|
el_custom.style.display = "none";
|
||||||
|
};
|
||||||
|
|
||||||
|
const getImage = () => {
|
||||||
|
const value = document.querySelector(`input[name="${uniqueName}"]:checked`).value;
|
||||||
|
switch (value) {
|
||||||
|
case PREVIEW_DEFAULT:
|
||||||
|
if (defaultImageCount === 0) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
const children = el_defaultImages.children;
|
||||||
|
for (let i = 0; i < children.length; i++) {
|
||||||
|
const child = children[i];
|
||||||
|
if (child.style.display !== "none") {
|
||||||
|
return child.src;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
case PREVIEW_URL:
|
||||||
|
return el_customUrl.value;
|
||||||
|
case PREVIEW_UPLOAD:
|
||||||
|
return el_uploadFile.files[0] ?? "";
|
||||||
|
case PREVIEW_NONE:
|
||||||
|
return imageUri();
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
};
|
||||||
|
|
||||||
|
const el_radioGroup = $el("div.model-preview-select-radio-container", [
|
||||||
|
$el("div.row.tab-header-flex-block", [el_radioButtons]),
|
||||||
|
$el("div", [
|
||||||
|
el_custom,
|
||||||
|
el_upload,
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return [el_radioGroup, el_preview, getImage, el_defaultUri, resetModelInfoPreview];
|
||||||
|
}
|
||||||
|
|
||||||
class ModelManager extends ComfyDialog {
|
class ModelManager extends ComfyDialog {
|
||||||
#el = {
|
#el = {
|
||||||
/** @type {HTMLDivElement} */ modelInfoView: null,
|
/** @type {HTMLDivElement} */ modelInfoView: null,
|
||||||
@@ -1300,6 +1593,8 @@ class ModelManager extends ComfyDialog {
|
|||||||
/** @type {HTMLDivElement} */ modelInfoUrl: null,
|
/** @type {HTMLDivElement} */ modelInfoUrl: null,
|
||||||
/** @type {HTMLDivElement} */ modelInfoOverwrite: null,
|
/** @type {HTMLDivElement} */ modelInfoOverwrite: null,
|
||||||
/** @type {HTMLDivElement} */ modelInfos: null,
|
/** @type {HTMLDivElement} */ modelInfos: null,
|
||||||
|
modelInfoPreview: null,
|
||||||
|
modelInfoDefaultUri: null,
|
||||||
|
|
||||||
/** @type {HTMLDivElement} */ modelGrid: null,
|
/** @type {HTMLDivElement} */ modelGrid: null,
|
||||||
/** @type {HTMLSelectElement} */ modelTypeSelect: null,
|
/** @type {HTMLSelectElement} */ modelTypeSelect: null,
|
||||||
@@ -1339,6 +1634,8 @@ class ModelManager extends ComfyDialog {
|
|||||||
/** @type {string} */
|
/** @type {string} */
|
||||||
#systemSeparator = null;
|
#systemSeparator = null;
|
||||||
|
|
||||||
|
#resetModelInfoPreview = () => {};
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
@@ -1359,7 +1656,17 @@ class ModelManager extends ComfyDialog {
|
|||||||
this.#searchSeparator,
|
this.#searchSeparator,
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const [el_radioGroup, el_preview, getImage, el_defaultUri, resetModelInfoPreview] = radioGroupImageSelect(
|
||||||
|
"model-info-preview-model-FYUIKMNVB",
|
||||||
|
[imageUri()],
|
||||||
|
);
|
||||||
|
el_preview.style.display = "flex";
|
||||||
|
this.#el.modelInfoRadioGroup = el_radioGroup;
|
||||||
|
this.#el.modelInfoPreview = el_preview;
|
||||||
|
this.#el.modelInfoDefaultUri = el_defaultUri;
|
||||||
|
this.#resetModelInfoPreview = resetModelInfoPreview;
|
||||||
|
|
||||||
this.element = $el(
|
this.element = $el(
|
||||||
"div.comfy-modal.model-manager",
|
"div.comfy-modal.model-manager",
|
||||||
{
|
{
|
||||||
@@ -1402,7 +1709,7 @@ class ModelManager extends ComfyDialog {
|
|||||||
}
|
}
|
||||||
return deleted;
|
return deleted;
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch((err) => {
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1633,7 +1940,7 @@ class ModelManager extends ComfyDialog {
|
|||||||
#modelTab_showModelInfo = async(searchPath) => {
|
#modelTab_showModelInfo = async(searchPath) => {
|
||||||
const path = encodeURIComponent(searchPath);
|
const path = encodeURIComponent(searchPath);
|
||||||
const info = await request(`/model-manager/model/info?path=${path}`)
|
const info = await request(`/model-manager/model/info?path=${path}`)
|
||||||
.catch(err => {
|
.catch((err) => {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
@@ -1648,66 +1955,152 @@ class ModelManager extends ComfyDialog {
|
|||||||
if (filename !== undefined && filename !== null && filename !== "") {
|
if (filename !== undefined && filename !== null && filename !== "") {
|
||||||
innerHtml.push($el("h1", [filename]));
|
innerHtml.push($el("h1", [filename]));
|
||||||
}
|
}
|
||||||
for (const [key, value] of Object.entries(info)) {
|
|
||||||
if (value === undefined || value === null) {
|
if (info["Preview"]) {
|
||||||
continue;
|
const imagePath = info["Preview"]["path"];
|
||||||
}
|
const imageDateModified = info["Preview"]["dateModified"];
|
||||||
|
this.#el.modelInfoDefaultUri.dataset.noimage = imageUri(imagePath, imageDateModified);
|
||||||
if (Array.isArray(value)) {
|
this.#resetModelInfoPreview();
|
||||||
if (value.length > 0) {
|
}
|
||||||
innerHtml.push($el("h2", [key + ":"]));
|
|
||||||
|
innerHtml.push($el("div", [
|
||||||
let text = "<p>";
|
this.#el.modelInfoPreview,
|
||||||
for (let i = 0; i < value.length; i++) {
|
$el("div.row.tab-header", [
|
||||||
const v = value[i];
|
$el("div.row.tab-header-flex-block", [
|
||||||
const tag = v[0];
|
$el("button", {
|
||||||
const count = v[1];
|
textContent: "Set as Preview",
|
||||||
text += tag + "<span class=\"no-select\"> (" + count + ")</span>";
|
onclick: async(e) => {
|
||||||
if (i !== value.length - 1) {
|
const confirmation = window.confirm("Change preview image PERMANENTLY?");
|
||||||
text += ", ";
|
let updatedPreview = false;
|
||||||
|
if (confirmation) {
|
||||||
|
e.target.disabled = true;
|
||||||
|
const container = this.#el.modelInfoContainer;
|
||||||
|
const path = container.dataset.path;
|
||||||
|
const imageUrl = getImage();
|
||||||
|
if (imageUrl === imageUri()) {
|
||||||
|
const encodedPath = encodeURIComponent(path);
|
||||||
|
updatedPreview = await request(
|
||||||
|
`/model-manager/preview/delete?path=${encodedPath}`,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify({}),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.then((result) => {
|
||||||
|
return result["success"];
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("path", path);
|
||||||
|
const image = imageUrl[0] == "/" ? "" : imageUrl;
|
||||||
|
formData.append("image", image);
|
||||||
|
updatedPreview = await request(
|
||||||
|
`/model-manager/preview/set`,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
body: formData,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.then((result) => {
|
||||||
|
return result["success"];
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (updatedPreview) {
|
||||||
|
this.#modelTab_updateModels();
|
||||||
|
this.#el.modelInfoDefaultUri.dataset.noimage = imageUri();
|
||||||
|
this.#resetModelInfoPreview();
|
||||||
|
this.#el.modelInfoView.style.display = "none";
|
||||||
|
}
|
||||||
|
|
||||||
|
e.target.disabled = false;
|
||||||
|
}
|
||||||
|
buttonAlert(e.target, updatedPreview);
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
$el("div.row.tab-header-flex-block", [
|
||||||
|
this.#el.modelInfoRadioGroup,
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
$el("div",
|
||||||
|
(() => {
|
||||||
|
const elements = [];
|
||||||
|
for (const [key, value] of Object.entries(info)) {
|
||||||
|
if (value === undefined || value === null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
if (value.length > 0) {
|
||||||
|
elements.push($el("h2", [key + ":"]));
|
||||||
|
|
||||||
|
let text = "<p>";
|
||||||
|
for (let i = 0; i < value.length; i++) {
|
||||||
|
const v = value[i];
|
||||||
|
const tag = v[0];
|
||||||
|
const count = v[1];
|
||||||
|
text += tag + "<span class=\"no-select\"> (" + count + ")</span>";
|
||||||
|
if (i !== value.length - 1) {
|
||||||
|
text += ", ";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
text += "</p>";
|
||||||
|
const div = $el("div");
|
||||||
|
div.innerHTML = text;
|
||||||
|
elements.push(div);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (key === "Notes") {
|
||||||
|
elements.push($el("h2", [key + ":"]));
|
||||||
|
const noteArea = $el("textarea.comfy-multiline-input", {
|
||||||
|
value: value,
|
||||||
|
rows: 5,
|
||||||
|
});
|
||||||
|
elements.push(noteArea);
|
||||||
|
elements.push($el("button", {
|
||||||
|
textContent: "Save Notes",
|
||||||
|
onclick: (e) => {
|
||||||
|
const saved = request(
|
||||||
|
"/model-manager/notes/save",
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify({
|
||||||
|
"path": this.#el.modelInfoContainer.dataset.path,
|
||||||
|
"notes": noteArea.value,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
).then((result) => {
|
||||||
|
return result["success"];
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
buttonAlert(e.target, saved);
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
else if (key === "Preview") {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (value !== "") {
|
||||||
|
elements.push($el("p", [key + ": " + value]));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
text += "</p>";
|
return elements;
|
||||||
const div = $el("div");
|
})(),
|
||||||
div.innerHTML = text;
|
),
|
||||||
innerHtml.push(div);
|
]));
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (key === "Notes") {
|
|
||||||
innerHtml.push($el("h2", [key + ":"]));
|
|
||||||
const noteArea = $el("textarea.comfy-multiline-input", {
|
|
||||||
value: value,
|
|
||||||
rows: 5,
|
|
||||||
});
|
|
||||||
innerHtml.push(noteArea);
|
|
||||||
innerHtml.push($el("button", {
|
|
||||||
textContent: "Save Notes",
|
|
||||||
onclick: (e) => {
|
|
||||||
const saved = request(
|
|
||||||
"/model-manager/notes/save",
|
|
||||||
{
|
|
||||||
method: "POST",
|
|
||||||
body: JSON.stringify({
|
|
||||||
"path": this.#el.modelInfoContainer.dataset.path,
|
|
||||||
"notes": noteArea.value,
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
).then((result) => {
|
|
||||||
return result["success"];
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
buttonAlert(e.target, saved);
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
innerHtml.push($el("p", [key + ": " + value]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
infoHtml.append.apply(infoHtml, innerHtml);
|
infoHtml.append.apply(infoHtml, innerHtml);
|
||||||
|
|
||||||
this.#el.modelInfoView.removeAttribute("style"); // remove "display: none"
|
this.#el.modelInfoView.removeAttribute("style"); // remove "display: none"
|
||||||
@@ -1912,31 +2305,6 @@ class ModelManager extends ComfyDialog {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {HTMLDivElement} previewImageContainer
|
|
||||||
* @param {Event} e
|
|
||||||
* @param {1 | -1} step
|
|
||||||
*/
|
|
||||||
static #downloadTab_updatePreview(previewImageContainer, step) {
|
|
||||||
const children = previewImageContainer.children;
|
|
||||||
if (children.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let currentIndex = -step;
|
|
||||||
for (let i = 0; i < children.length; i++) {
|
|
||||||
const previewImage = children[i];
|
|
||||||
const display = previewImage.style.display;
|
|
||||||
if (display !== "none") {
|
|
||||||
currentIndex = i;
|
|
||||||
}
|
|
||||||
previewImage.style.display = "none";
|
|
||||||
}
|
|
||||||
currentIndex = currentIndex + step;
|
|
||||||
if (currentIndex >= children.length) { currentIndex = 0; }
|
|
||||||
else if (currentIndex < 0) { currentIndex = children.length - 1; }
|
|
||||||
children[currentIndex].style.display = "block";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Object} info
|
* @param {Object} info
|
||||||
* @param {String[]} modelTypes
|
* @param {String[]} modelTypes
|
||||||
@@ -1946,35 +2314,12 @@ class ModelManager extends ComfyDialog {
|
|||||||
* @returns {HTMLDivElement}
|
* @returns {HTMLDivElement}
|
||||||
*/
|
*/
|
||||||
#downloadTab_modelInfo(info, modelTypes, modelDirectories, searchSeparator, id) {
|
#downloadTab_modelInfo(info, modelTypes, modelDirectories, searchSeparator, id) {
|
||||||
// TODO: use passed in info
|
const [el_radioGroup, el_preview, getImage, el_defaultUri, resetModelInfoPreview] = radioGroupImageSelect(
|
||||||
const RADIO_MODEL_PREVIEW_NONE = "No Preview";
|
"model-download-info-preview-model" + "-" + id,
|
||||||
const RADIO_MODEL_PREVIEW_DEFAULT = "Default Preview";
|
info["images"],
|
||||||
const RADIO_MODEL_PREVIEW_CUSTOM = "Custom Preview";
|
);
|
||||||
|
|
||||||
const els = {
|
const el_modelTypeSelect = $el("select.model-select-dropdown", (() => {
|
||||||
modelPreviewContainer: null,
|
|
||||||
previewImgs: null,
|
|
||||||
buttonLeft: null,
|
|
||||||
buttonRight: null,
|
|
||||||
|
|
||||||
customPreviewContainer: null,
|
|
||||||
customPreviewUrl: null,
|
|
||||||
|
|
||||||
modelTypeSelect: null,
|
|
||||||
saveDirectoryPath: null,
|
|
||||||
filename: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
$el("input.search-text-area", {
|
|
||||||
$: (el) => (els.saveDirectoryPath = el),
|
|
||||||
type: "text",
|
|
||||||
placeholder: this.#searchSeparator + "0",
|
|
||||||
value: this.#searchSeparator + "0",
|
|
||||||
});
|
|
||||||
|
|
||||||
$el("select.model-select-dropdown", {
|
|
||||||
$: (el) => (els.modelTypeSelect = el),
|
|
||||||
}, (() => {
|
|
||||||
const options = [$el("option", { value: "" }, ["-- Model Type --"])];
|
const options = [$el("option", { value: "" }, ["-- Model Type --"])];
|
||||||
modelTypes.forEach((modelType) => {
|
modelTypes.forEach((modelType) => {
|
||||||
options.push($el("option", { value: modelType }, [modelType]));
|
options.push($el("option", { value: modelType }, [modelType]));
|
||||||
@@ -1982,11 +2327,16 @@ class ModelManager extends ComfyDialog {
|
|||||||
return options;
|
return options;
|
||||||
})());
|
})());
|
||||||
|
|
||||||
|
const el_saveDirectoryPath = $el("input.search-text-area", {
|
||||||
|
type: "text",
|
||||||
|
placeholder: this.#searchSeparator + "0",
|
||||||
|
value: this.#searchSeparator + "0",
|
||||||
|
});
|
||||||
let searchDropdown = null;
|
let searchDropdown = null;
|
||||||
searchDropdown = new DirectoryDropdown(
|
searchDropdown = new DirectoryDropdown(
|
||||||
els.saveDirectoryPath,
|
el_saveDirectoryPath,
|
||||||
() => {
|
() => {
|
||||||
const modelType = els.modelTypeSelect.value;
|
const modelType = el_modelTypeSelect.value;
|
||||||
if (modelType === "") { return; }
|
if (modelType === "") { return; }
|
||||||
searchDropdown.update(
|
searchDropdown.update(
|
||||||
modelDirectories,
|
modelDirectories,
|
||||||
@@ -2000,36 +2350,13 @@ class ModelManager extends ComfyDialog {
|
|||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
|
|
||||||
const radioGroupName = "model-download-info-preview-model" + "-" + id;
|
const el_filename = $el("input.plain-text-area", {
|
||||||
const radioGroup = $radioGroup({
|
type: "text",
|
||||||
name: radioGroupName,
|
placeholder: (() => {
|
||||||
onchange: (value) => {
|
const filename = info["fileName"];
|
||||||
switch (value) {
|
// TODO: only remove valid model file extensions
|
||||||
case RADIO_MODEL_PREVIEW_DEFAULT:
|
const i = filename.lastIndexOf(".");
|
||||||
const bottonStyleDisplay = els.previewImgs.children.length > 1 ? "block" : "none";
|
return i === - 1 ? filename : filename.substring(0, i);
|
||||||
els.buttonLeft.style.display = bottonStyleDisplay;
|
|
||||||
els.buttonRight.style.display = bottonStyleDisplay;
|
|
||||||
els.modelPreviewContainer.style.display = "block";
|
|
||||||
els.customPreviewContainer.style.display = "none";
|
|
||||||
break;
|
|
||||||
case RADIO_MODEL_PREVIEW_CUSTOM:
|
|
||||||
els.modelPreviewContainer.style.display = "none";
|
|
||||||
els.customPreviewContainer.style.display = "flex";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
els.modelPreviewContainer.style.display = "none";
|
|
||||||
els.customPreviewContainer.style.display = "none";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
options: (() => {
|
|
||||||
const radios = [];
|
|
||||||
radios.push({ value: RADIO_MODEL_PREVIEW_NONE });
|
|
||||||
if (info["images"].length > 0) {
|
|
||||||
radios.push({ value: RADIO_MODEL_PREVIEW_DEFAULT });
|
|
||||||
}
|
|
||||||
radios.push({ value: RADIO_MODEL_PREVIEW_CUSTOM });
|
|
||||||
return radios;
|
|
||||||
})(),
|
})(),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -2039,82 +2366,32 @@ class ModelManager extends ComfyDialog {
|
|||||||
$el("div", {
|
$el("div", {
|
||||||
style: { display: "flex", "flex-wrap": "wrap", gap: "16px" },
|
style: { display: "flex", "flex-wrap": "wrap", gap: "16px" },
|
||||||
}, [
|
}, [
|
||||||
$el("div.item", {
|
el_preview,
|
||||||
$: (el) => (els.modelPreviewContainer = el),
|
|
||||||
style: { display: "none" },
|
|
||||||
}, [
|
|
||||||
$el("div", {
|
|
||||||
$: (el) => (els.previewImgs = el),
|
|
||||||
style: {
|
|
||||||
width: "100%",
|
|
||||||
height: "100%",
|
|
||||||
},
|
|
||||||
}, (() => {
|
|
||||||
const imgs = info["images"].map((url) => {
|
|
||||||
return $el("img", {
|
|
||||||
src: url,
|
|
||||||
style: { display: "none" },
|
|
||||||
loading: "lazy",
|
|
||||||
});
|
|
||||||
});
|
|
||||||
if (imgs.length > 0) {
|
|
||||||
imgs[0].style.display = "block";
|
|
||||||
}
|
|
||||||
return imgs;
|
|
||||||
})()),
|
|
||||||
$el("div.model-preview-overlay", [
|
|
||||||
$el("button.icon-button.model-preview-button-left", {
|
|
||||||
$: (el) => (els.buttonLeft = el),
|
|
||||||
onclick: () => ModelManager.#downloadTab_updatePreview(els.previewImgs, -1),
|
|
||||||
textContent: "←",
|
|
||||||
}),
|
|
||||||
$el("button.icon-button.model-preview-button-right", {
|
|
||||||
$: (el) => (els.buttonRight = el),
|
|
||||||
onclick: () => ModelManager.#downloadTab_updatePreview(els.previewImgs, 1),
|
|
||||||
textContent: "→",
|
|
||||||
}),
|
|
||||||
]),
|
|
||||||
]),
|
|
||||||
$el("div.download-settings", [
|
$el("div.download-settings", [
|
||||||
$el("div", {
|
$el("div", {
|
||||||
style: { "margin-top": "8px" }
|
style: { "margin-top": "8px" }
|
||||||
}, [
|
}, [
|
||||||
$el("div.model-preview-select-radio-container", [
|
$el("div.row.tab-header-flex-block", [
|
||||||
$el("div.row.tab-header-flex-block", [radioGroup]),
|
el_modelTypeSelect,
|
||||||
$el("div", [
|
|
||||||
$el("div.row.tab-header-flex-block", {
|
|
||||||
$: (el) => (els.customPreviewContainer = el),
|
|
||||||
style: { display: "none" },
|
|
||||||
}, [
|
|
||||||
$el("input.search-text-area", {
|
|
||||||
$: (el) => (els.customPreviewUrl = el),
|
|
||||||
type: "text",
|
|
||||||
placeholder: "https://custom-image-preview.png"
|
|
||||||
}),
|
|
||||||
]),
|
|
||||||
]),
|
|
||||||
]),
|
]),
|
||||||
$el("div.row.tab-header-flex-block", [
|
$el("div.row.tab-header-flex-block", [
|
||||||
els.modelTypeSelect,
|
el_saveDirectoryPath,
|
||||||
]),
|
|
||||||
$el("div.row.tab-header-flex-block", [
|
|
||||||
els.saveDirectoryPath,
|
|
||||||
searchDropdown.element,
|
searchDropdown.element,
|
||||||
]),
|
]),
|
||||||
$el("div.row.tab-header-flex-block", [
|
$el("div.row.tab-header-flex-block", [
|
||||||
$el("button.icon-button", {
|
$el("button.icon-button", {
|
||||||
textContent: "📥︎",
|
textContent: "📥︎",
|
||||||
onclick: async (e) => {
|
onclick: async (e) => {
|
||||||
const record = {};
|
const formData = new FormData();
|
||||||
record["download"] = info["downloadUrl"];
|
formData.append("download", info["downloadUrl"]);
|
||||||
record["path"] = (
|
formData.append("path",
|
||||||
els.modelTypeSelect.value +
|
el_modelTypeSelect.value +
|
||||||
this.#searchSeparator + // NOTE: this may add multiple separators (server should handle carefully)
|
this.#searchSeparator + // NOTE: this may add multiple separators (server should handle carefully)
|
||||||
els.saveDirectoryPath.value
|
el_saveDirectoryPath.value
|
||||||
);
|
);
|
||||||
record["name"] = (() => {
|
formData.append("name", (() => {
|
||||||
const filename = info["fileName"];
|
const filename = info["fileName"];
|
||||||
const name = els.filename.value;
|
const name = el_filename.value;
|
||||||
if (name === "") {
|
if (name === "") {
|
||||||
return filename;
|
return filename;
|
||||||
}
|
}
|
||||||
@@ -2122,36 +2399,23 @@ class ModelManager extends ComfyDialog {
|
|||||||
return filename.endsWith(ext);
|
return filename.endsWith(ext);
|
||||||
}) ?? "";
|
}) ?? "";
|
||||||
return name + ext;
|
return name + ext;
|
||||||
})();
|
})());
|
||||||
record["image"] = (() => {
|
formData.append("image", getImage());
|
||||||
const value = document.querySelector(`input[name="${radioGroupName}"]:checked`).value;
|
formData.append("overwrite", this.#el.modelInfoOverwrite.checked);
|
||||||
switch (value) {
|
|
||||||
case RADIO_MODEL_PREVIEW_DEFAULT:
|
|
||||||
const children = els.previewImgs.children;
|
|
||||||
for (let i = 0; i < children.length; i++) {
|
|
||||||
const child = children[i];
|
|
||||||
if (child.style.display !== "none") {
|
|
||||||
return child.src;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
case RADIO_MODEL_PREVIEW_CUSTOM:
|
|
||||||
return els.customPreviewUrl.value;
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
})();
|
|
||||||
record["overwrite"] = this.#el.modelInfoOverwrite.checked;
|
|
||||||
e.target.disabled = true;
|
e.target.disabled = true;
|
||||||
const [success, resultText] = await request(
|
const [success, resultText] = await request(
|
||||||
"/model-manager/model/download",
|
"/model-manager/model/download",
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: JSON.stringify(record),
|
body: formData,
|
||||||
}
|
}
|
||||||
).then(data => {
|
).then((data) => {
|
||||||
const success = data["success"];
|
const success = data["success"];
|
||||||
|
if (!success) {
|
||||||
|
console.warn(data["invalid"]);
|
||||||
|
}
|
||||||
return [success, success ? "✔" : "📥︎"];
|
return [success, success ? "✔" : "📥︎"];
|
||||||
}).catch(err => {
|
}).catch((err) => {
|
||||||
return [false, "📥︎"];
|
return [false, "📥︎"];
|
||||||
});
|
});
|
||||||
if (success) {
|
if (success) {
|
||||||
@@ -2161,59 +2425,26 @@ class ModelManager extends ComfyDialog {
|
|||||||
e.target.disabled = success;
|
e.target.disabled = success;
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
$el("input.plain-text-area", {
|
el_filename,
|
||||||
$: (el) => (els.filename = el),
|
|
||||||
type: "text",
|
|
||||||
placeholder: (() => {
|
|
||||||
const filename = info["fileName"];
|
|
||||||
// TODO: only remove valid model file extensions
|
|
||||||
const i = filename.lastIndexOf(".");
|
|
||||||
return i === - 1 ? filename : filename.substring(0, i);
|
|
||||||
})(),
|
|
||||||
}),
|
|
||||||
]),
|
]),
|
||||||
|
el_radioGroup,
|
||||||
]),
|
]),
|
||||||
/*
|
|
||||||
$el("div", (() => {
|
|
||||||
return Object.entries(info["details"]).filter(([, value]) => {
|
|
||||||
return value !== undefined && value !== null;
|
|
||||||
}).map(([key, value]) => {
|
|
||||||
const el = document.createElement("p");
|
|
||||||
el.innerText = key + ": " + value;
|
|
||||||
return el;
|
|
||||||
});
|
|
||||||
})()),
|
|
||||||
*/
|
|
||||||
]),
|
]),
|
||||||
]),
|
]),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (info["images"].length > 0) {
|
el_modelTypeSelect.selectedIndex = 0; // reset
|
||||||
const children = radioGroup.children;
|
|
||||||
for (let i = 0; i < children.length; i++) {
|
|
||||||
const child = children[i];
|
|
||||||
const radioButton = child.children[0];
|
|
||||||
if (radioButton.value === RADIO_MODEL_PREVIEW_DEFAULT) {
|
|
||||||
els.modelPreviewContainer.style.display = "block";
|
|
||||||
radioButton.checked = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const modelTypeSelect = els.modelTypeSelect;
|
|
||||||
modelTypeSelect.selectedIndex = 0; // reset
|
|
||||||
const comfyUIModelType = (
|
const comfyUIModelType = (
|
||||||
modelTypeToComfyUiDirectory(info["details"]["fileType"]) ??
|
modelTypeToComfyUiDirectory(info["details"]["fileType"]) ??
|
||||||
modelTypeToComfyUiDirectory(info["modelType"]) ??
|
modelTypeToComfyUiDirectory(info["modelType"]) ??
|
||||||
null
|
null
|
||||||
);
|
);
|
||||||
if (comfyUIModelType !== undefined && comfyUIModelType !== null) {
|
if (comfyUIModelType !== undefined && comfyUIModelType !== null) {
|
||||||
const modelTypeOptions = modelTypeSelect.children;
|
const modelTypeOptions = el_modelTypeSelect.children;
|
||||||
for (let i = 0; i < modelTypeOptions.length; i++) {
|
for (let i = 0; i < modelTypeOptions.length; i++) {
|
||||||
const option = modelTypeOptions[i];
|
const option = modelTypeOptions[i];
|
||||||
if (option.value === comfyUIModelType) {
|
if (option.value === comfyUIModelType) {
|
||||||
modelTypeSelect.selectedIndex = i;
|
el_modelTypeSelect.selectedIndex = i;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user