From d4a339802f901d5abe828b9222c7e44ac50a5453 Mon Sep 17 00:00:00 2001 From: Christian Bastian <80225746+cdb-boop@users.noreply.github.com> Date: Fri, 5 Apr 2024 20:36:41 -0400 Subject: [PATCH] Added helpful browser window alerts on expected server model failures. - UI displays alerts sent by server on expected failures. - Adjusted top bar CSS layout slightly. - Fixed invalid filename turning into empty string but bypassing empty string check. - Server prints download url too. --- __init__.py | 118 ++++++++++++++++++++++++++++-------------- web/model-manager.css | 9 +++- web/model-manager.js | 55 ++++++++++++++++---- 3 files changed, 133 insertions(+), 49 deletions(-) diff --git a/__init__.py b/__init__.py index 814cea8..fa78ec0 100644 --- a/__init__.py +++ b/__init__.py @@ -425,14 +425,20 @@ async def set_model_preview(request): return web.json_response({ "success": True }) except ValueError as e: print(e, file=sys.stderr, flush=True) - return web.json_response({ "success": False }) + return web.json_response({ + "success": False, + "alert": "Failed to set preview!\n\n" + str(e), + }) @server.PromptServer.instance.routes.post("/model-manager/preview/delete") async def delete_model_preview(request): + result = { "success": False } + model_path = request.query.get("path", None) if model_path is None: - return web.json_response({ "success": False }) + result["alert"] = "Missing model path!" + return web.json_response(result) model_path = urllib.parse.unquote(model_path) model_path, model_type = search_path_to_system_path(model_path) @@ -440,7 +446,8 @@ async def delete_model_preview(request): path_and_name, _ = split_valid_ext(model_path, model_extensions) delete_same_name_files(path_and_name, preview_extensions) - return web.json_response({ "success": True }) + result["success"] = True + return web.json_response(result) @server.PromptServer.instance.routes.get("/model-manager/models/list") @@ -653,7 +660,7 @@ def download_file(url, filename, overwrite): total_size = int(rh.headers.get("Content-Length", 0)) # TODO: pass in total size earlier - print("Download file: " + filename) + print("Downloading file: " + url) if total_size != 0: print("Download file size: " + str(total_size)) @@ -683,6 +690,7 @@ def download_file(url, filename, overwrite): if overwrite and os.path.isfile(filename): os.remove(filename) os.rename(filename_temp, filename) + print("Saved file: " + filename) def bytes_to_size(total_bytes): @@ -700,14 +708,18 @@ def bytes_to_size(total_bytes): @server.PromptServer.instance.routes.get("/model-manager/model/info") async def get_model_info(request): + result = { "success": False } + model_path = request.query.get("path", None) if model_path is None: - return web.json_response({ "success": False }) + result["alert"] = "Missing model path!" + return web.json_response(result) model_path = urllib.parse.unquote(model_path) abs_path, model_type = search_path_to_system_path(model_path) if abs_path is None: - return web.json_response({}) + result["alert"] = "Invalid model path!" + return web.json_response(result) info = {} comfyui_directory, name = os.path.split(model_path) @@ -832,7 +844,9 @@ async def get_model_info(request): tags.sort(key=lambda x: x[1], reverse=True) info["Tags"] = tags - return web.json_response(info) + result["success"] = True + result["info"] = info + return web.json_response(result) @server.PromptServer.instance.routes.get("/model-manager/system-separator") @@ -843,10 +857,7 @@ async def get_system_separator(request): @server.PromptServer.instance.routes.post("/model-manager/model/download") async def download_model(request): formdata = await request.post() - result = { - "success": False, - "invalid": None, - } + result = { "success": False } overwrite = formdata.get("overwrite", "false").lower() overwrite = True if overwrite == "true" else False @@ -854,26 +865,30 @@ async def download_model(request): model_path = formdata.get("path", "/0") directory, model_type = search_path_to_system_path(model_path) if directory is None: - result["invalid"] = "path" + result["alert"] = "Invalid save path!" return web.json_response(result) download_uri = formdata.get("download") if download_uri is None: - result["invalid"] = "download" + result["alert"] = "Invalid download url!" return web.json_response(result) name = formdata.get("name") model_extensions = folder_paths_get_supported_pt_extensions(model_type) - _, model_extension = split_valid_ext(name, model_extensions) + name_head, model_extension = split_valid_ext(name, model_extensions) + name_without_extension = os.path.split(name_head)[1] + if name_without_extension == "": + result["alert"] = "Cannot have empty model name!" + return web.json_response(result) if model_extension == "": - result["invalid"] = "name" + result["alert"] = "Unrecognized model extension!" return web.json_response(result) file_name = os.path.join(directory, name) try: download_file(download_uri, file_name, overwrite) except Exception as e: print(e, file=sys.stderr, flush=True) - result["invalid"] = "model" + result["alert"] = "Failed to download model!\n\n" + str(e) return web.json_response(result) image = formdata.get("image") @@ -886,7 +901,7 @@ async def download_model(request): }) except Exception as e: print(e, file=sys.stderr, flush=True) - result["invalid"] = "preview" + result["alert"] = "Failed to download preview!\n\n" + str(e) result["success"] = True return web.json_response(result) @@ -895,48 +910,60 @@ async def download_model(request): @server.PromptServer.instance.routes.post("/model-manager/model/move") async def move_model(request): body = await request.json() + result = { "success": False } old_file = body.get("oldFile", None) if old_file is None: - return web.json_response({ "success": False }) + result["alert"] = "No model was given!" + return web.json_response(result) old_file, old_model_type = search_path_to_system_path(old_file) if not os.path.isfile(old_file): - return web.json_response({ "success": False }) + result["alert"] = "Model does not exist!" + return web.json_response(result) old_model_extensions = folder_paths_get_supported_pt_extensions(old_model_type) old_file_without_extension, model_extension = split_valid_ext(old_file, old_model_extensions) if model_extension == "": - # cannot move arbitrary files - return web.json_response({ "success": False }) + result["alert"] = "Invalid model extension!" + return web.json_response(result) new_file = body.get("newFile", None) if new_file is None or new_file == "": - # cannot have empty name - return web.json_response({ "success": False }) + result["alert"] = "New model name was invalid!" + return web.json_response(result) new_file, new_model_type = search_path_to_system_path(new_file) if not new_file.endswith(model_extension): - return web.json_response({ "success": False }) + result["alert"] = "Cannot change model extension!" + return web.json_response(result) if os.path.isfile(new_file): - # cannot overwrite existing file - return web.json_response({ "success": False }) + result["alert"] = "Cannot overwrite existing model!" + return web.json_response(result) new_model_extensions = folder_paths_get_supported_pt_extensions(new_model_type) new_file_without_extension, new_model_extension = split_valid_ext(new_file, new_model_extensions) if model_extension != new_model_extension: - # cannot change extension - return web.json_response({ "success": False }) - new_file_dir, _ = os.path.split(new_file) + result["alert"] = "Cannot change model extension!" + return web.json_response(result) + new_file_dir, new_file_name = os.path.split(new_file) if not os.path.isdir(new_file_dir): - return web.json_response({ "success": False }) + result["alert"] = "Destination directory does not exist!" + return web.json_response(result) + new_name_without_extension = os.path.splitext(new_file_name)[0] + if new_file_name == new_name_without_extension or new_name_without_extension == "": + result["alert"] = "New model name was empty!" + return web.json_response(result) if old_file == new_file: - return web.json_response({ "success": False }) + # no-op + result["success"] = True + return web.json_response(result) try: shutil.move(old_file, new_file) print("Moved file: " + new_file) except ValueError as e: print(e, file=sys.stderr, flush=True) - return web.json_response({ "success": False }) + result["alert"] = "Failed to move model!\n\n" + str(e) + return web.json_response(result) - # TODO: this could overwrite existing files in destination... + # TODO: this could overwrite existing files in destination; do a check beforehand? for extension in preview_extensions + (model_info_extension,): old_file = old_file_without_extension + extension if os.path.isfile(old_file): @@ -946,8 +973,14 @@ async def move_model(request): print("Moved file: " + new_file) except ValueError as e: print(e, file=sys.stderr, flush=True) + msg = result.get("alert","") + if msg == "": + result["alert"] = "Failed to move model resource file!\n\n" + str(e) + else: + result["alert"] = msg + "\n" + str(e) - return web.json_response({ "success": True }) + result["success"] = True + return web.json_response(result) def delete_same_name_files(path_without_extension, extensions, keep_extension=None): @@ -965,16 +998,18 @@ async def delete_model(request): model_path = request.query.get("path", None) if model_path is None: + result["alert"] = "Missing model path!" return web.json_response(result) model_path = urllib.parse.unquote(model_path) model_path, model_type = search_path_to_system_path(model_path) if model_path is None: + result["alert"] = "Invalid model path!" return web.json_response(result) model_extensions = folder_paths_get_supported_pt_extensions(model_type) path_and_name, model_extension = split_valid_ext(model_path, model_extensions) if model_extension == "": - # cannot delete arbitrary files + result["alert"] = "Cannot delete file!" return web.json_response(result) if os.path.isfile(model_path): @@ -991,14 +1026,17 @@ async def delete_model(request): @server.PromptServer.instance.routes.post("/model-manager/notes/save") async def set_notes(request): body = await request.json() + result = { "success": False } text = body.get("notes", None) if type(text) is not str: - return web.json_response({ "success": False }) + result["alert"] = "Invalid note!" + return web.json_response(result) model_path = body.get("path", None) if type(model_path) is not str: - return web.json_response({ "success": False }) + result["alert"] = "Missing model path!" + return web.json_response(result) model_path, model_type = search_path_to_system_path(model_path) model_extensions = folder_paths_get_supported_pt_extensions(model_type) file_path_without_extension, _ = split_valid_ext(model_path, model_extensions) @@ -1013,9 +1051,11 @@ async def set_notes(request): print("Saved file: " + filename) except ValueError as e: print(e, file=sys.stderr, flush=True) - web.json_response({ "success": False }) + result["alert"] = "Failed to save notes!\n\n" + str(e) + web.json_response(result) - return web.json_response({ "success": True }) + result["success"] = True + return web.json_response(result) WEB_DIRECTORY = "web" diff --git a/web/model-manager.css b/web/model-manager.css index d2522a0..ced1a9c 100644 --- a/web/model-manager.css +++ b/web/model-manager.css @@ -452,6 +452,13 @@ width: 33px; } +.model-manager .model-manager-head { + display: flex; + flex-direction: row-reverse; /* `row` to swap topbar direction */ + justify-content: space-between; + align-items: flex-end; +} + .model-manager .model-manager-head .topbar-left { display: flex; float: left; @@ -460,7 +467,7 @@ .model-manager .model-manager-head .topbar-right { column-gap: 4px; display: flex; - flex-direction: row-reverse; + flex-direction: row-reverse; /* `row` to swap topbar direction */ float: right; } diff --git a/web/model-manager.js b/web/model-manager.js index 3ed1d10..09c0408 100644 --- a/web/model-manager.js +++ b/web/model-manager.js @@ -182,7 +182,7 @@ function buttonAlert(element, success, successText = "", failureText = "", reset * @returns {Promise} */ async function saveNotes(modelPath, newValue) { - return request( + return await request( "/model-manager/notes/save", { method: "POST", @@ -192,7 +192,12 @@ async function saveNotes(modelPath, newValue) { }), } ).then((result) => { - return result["success"]; + const saved = result["success"]; + const message = result["alert"]; + if (message !== undefined) { + window.alert(message); + } + return saved; }) .catch((err) => { console.warn(err); @@ -1728,6 +1733,10 @@ class ModelInfoView { } ) .then((result) => { + const message = result["alert"]; + if (message !== undefined) { + window.alert(message); + } return result["success"]; }) .catch((err) => { @@ -1747,6 +1756,10 @@ class ModelInfoView { } ) .then((result) => { + const message = result["alert"]; + if (message !== undefined) { + window.alert(message); + } return result["success"]; }) .catch((err) => { @@ -1794,7 +1807,11 @@ class ModelInfoView { ) .then((result) => { const deleted = result["success"]; - if (deleted) + const message = result["alert"]; + if (message !== undefined) { + window.alert(message); + } + if (deleted) { container.innerHTML = ""; this.element.style.display = "none"; @@ -1841,6 +1858,10 @@ class ModelInfoView { ) .then((result) => { const moved = result["success"]; + const message = result["alert"]; + if (message !== undefined) { + window.alert(message); + } if (moved) { moveDestinationInput.value = ""; @@ -1934,11 +1955,22 @@ class ModelInfoView { async update(searchPath, updateModels, searchSeparator) { const path = encodeURIComponent(searchPath); const info = await request(`/model-manager/model/info?path=${path}`) + .then((result) => { + const success = result["success"]; + const message = result["alert"]; + if (message !== undefined) { + window.alert(message); + } + if (!success) { + return undefined; + } + return result["info"]; + }) .catch((err) => { console.log(err); - return null; + return undefined; }); - if (info === null) { + if (info === undefined || info === null) { return; } const infoHtml = this.elements.info; @@ -1985,6 +2017,10 @@ class ModelInfoView { ) .then((result) => { const renamed = result["success"]; + const message = result["alert"]; + if (message !== undefined) { + window.alert(message); + } if (renamed) { container.innerHTML = ""; @@ -2713,8 +2749,9 @@ class DownloadTab { } ).then((data) => { const success = data["success"]; - if (!success) { - console.warn(data["invalid"]); + const message = data["alert"]; + if (message !== undefined) { + window.alert(message); } return [success, success ? "✔" : "📥︎"]; }).catch((err) => { @@ -3053,13 +3090,13 @@ class SettingsTab { $: (el) => (this.elements.reloadButton = el), type: "button", textContent: "Reload", // ⟳ - onclick: () => this.reload(true), + onclick: async () => { await this.reload(true); }, }), $el("button", { $: (el) => (this.elements.saveButton = el), type: "button", textContent: "Save", // 💾︎ - onclick: () => this.save(), + onclick: async () => { await this.save(); }, }), ]), /*