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.
This commit is contained in:
Christian Bastian
2024-04-05 20:36:41 -04:00
parent fc5eccb0f8
commit d4a339802f
3 changed files with 133 additions and 49 deletions

View File

@@ -425,14 +425,20 @@ async def set_model_preview(request):
return web.json_response({ "success": True }) return web.json_response({ "success": True })
except ValueError 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": 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") @server.PromptServer.instance.routes.post("/model-manager/preview/delete")
async def delete_model_preview(request): async def delete_model_preview(request):
result = { "success": False }
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({ "success": False }) result["alert"] = "Missing model path!"
return web.json_response(result)
model_path = urllib.parse.unquote(model_path) model_path = urllib.parse.unquote(model_path)
model_path, model_type = search_path_to_system_path(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) path_and_name, _ = split_valid_ext(model_path, model_extensions)
delete_same_name_files(path_and_name, preview_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") @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 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: if total_size != 0:
print("Download file size: " + str(total_size)) print("Download file size: " + str(total_size))
@@ -683,6 +690,7 @@ def download_file(url, filename, overwrite):
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)
print("Saved file: " + filename)
def bytes_to_size(total_bytes): 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") @server.PromptServer.instance.routes.get("/model-manager/model/info")
async def get_model_info(request): async def get_model_info(request):
result = { "success": False }
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({ "success": False }) result["alert"] = "Missing model path!"
return web.json_response(result)
model_path = urllib.parse.unquote(model_path) model_path = urllib.parse.unquote(model_path)
abs_path, model_type = search_path_to_system_path(model_path) abs_path, model_type = search_path_to_system_path(model_path)
if abs_path is None: if abs_path is None:
return web.json_response({}) result["alert"] = "Invalid model path!"
return web.json_response(result)
info = {} info = {}
comfyui_directory, name = os.path.split(model_path) 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) tags.sort(key=lambda x: x[1], reverse=True)
info["Tags"] = tags 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") @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") @server.PromptServer.instance.routes.post("/model-manager/model/download")
async def download_model(request): async def download_model(request):
formdata = await request.post() formdata = await request.post()
result = { result = { "success": False }
"success": False,
"invalid": None,
}
overwrite = formdata.get("overwrite", "false").lower() overwrite = formdata.get("overwrite", "false").lower()
overwrite = True if overwrite == "true" else False overwrite = True if overwrite == "true" else False
@@ -854,26 +865,30 @@ async def download_model(request):
model_path = formdata.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["alert"] = "Invalid save path!"
return web.json_response(result) return web.json_response(result)
download_uri = formdata.get("download") download_uri = formdata.get("download")
if download_uri is None: if download_uri is None:
result["invalid"] = "download" result["alert"] = "Invalid download url!"
return web.json_response(result) return web.json_response(result)
name = formdata.get("name") name = formdata.get("name")
model_extensions = folder_paths_get_supported_pt_extensions(model_type) 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 == "": if model_extension == "":
result["invalid"] = "name" result["alert"] = "Unrecognized model extension!"
return web.json_response(result) return web.json_response(result)
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 Exception as e: except Exception as e:
print(e, file=sys.stderr, flush=True) 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) return web.json_response(result)
image = formdata.get("image") image = formdata.get("image")
@@ -886,7 +901,7 @@ async def download_model(request):
}) })
except Exception as e: except Exception as e:
print(e, file=sys.stderr, flush=True) print(e, file=sys.stderr, flush=True)
result["invalid"] = "preview" result["alert"] = "Failed to download preview!\n\n" + str(e)
result["success"] = True result["success"] = True
return web.json_response(result) return web.json_response(result)
@@ -895,48 +910,60 @@ async def download_model(request):
@server.PromptServer.instance.routes.post("/model-manager/model/move") @server.PromptServer.instance.routes.post("/model-manager/model/move")
async def move_model(request): async def move_model(request):
body = await request.json() body = await request.json()
result = { "success": False }
old_file = body.get("oldFile", None) old_file = body.get("oldFile", None)
if old_file is 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) old_file, old_model_type = search_path_to_system_path(old_file)
if not os.path.isfile(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_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) old_file_without_extension, model_extension = split_valid_ext(old_file, old_model_extensions)
if model_extension == "": if model_extension == "":
# cannot move arbitrary files result["alert"] = "Invalid model extension!"
return web.json_response({ "success": False }) return web.json_response(result)
new_file = body.get("newFile", None) new_file = body.get("newFile", None)
if new_file is None or new_file == "": if new_file is None or new_file == "":
# cannot have empty name result["alert"] = "New model name was invalid!"
return web.json_response({ "success": False }) return web.json_response(result)
new_file, new_model_type = search_path_to_system_path(new_file) new_file, new_model_type = search_path_to_system_path(new_file)
if not new_file.endswith(model_extension): 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): if os.path.isfile(new_file):
# cannot overwrite existing file result["alert"] = "Cannot overwrite existing model!"
return web.json_response({ "success": False }) return web.json_response(result)
new_model_extensions = folder_paths_get_supported_pt_extensions(new_model_type) 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) new_file_without_extension, new_model_extension = split_valid_ext(new_file, new_model_extensions)
if model_extension != new_model_extension: if model_extension != new_model_extension:
# cannot change extension result["alert"] = "Cannot change model extension!"
return web.json_response({ "success": False }) return web.json_response(result)
new_file_dir, _ = os.path.split(new_file) new_file_dir, new_file_name = os.path.split(new_file)
if not os.path.isdir(new_file_dir): 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: if old_file == new_file:
return web.json_response({ "success": False }) # no-op
result["success"] = True
return web.json_response(result)
try: try:
shutil.move(old_file, new_file) shutil.move(old_file, new_file)
print("Moved file: " + new_file) print("Moved file: " + new_file)
except ValueError 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": 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,): for extension in preview_extensions + (model_info_extension,):
old_file = old_file_without_extension + extension old_file = old_file_without_extension + extension
if os.path.isfile(old_file): if os.path.isfile(old_file):
@@ -946,8 +973,14 @@ async def move_model(request):
print("Moved file: " + new_file) print("Moved file: " + new_file)
except ValueError as e: except ValueError as e:
print(e, file=sys.stderr, flush=True) 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): 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) model_path = request.query.get("path", None)
if model_path is None: if model_path is None:
result["alert"] = "Missing model path!"
return web.json_response(result) return web.json_response(result)
model_path = urllib.parse.unquote(model_path) model_path = urllib.parse.unquote(model_path)
model_path, model_type = search_path_to_system_path(model_path) model_path, model_type = search_path_to_system_path(model_path)
if model_path is None: if model_path is None:
result["alert"] = "Invalid model path!"
return web.json_response(result) return web.json_response(result)
model_extensions = folder_paths_get_supported_pt_extensions(model_type) model_extensions = folder_paths_get_supported_pt_extensions(model_type)
path_and_name, model_extension = split_valid_ext(model_path, model_extensions) path_and_name, model_extension = split_valid_ext(model_path, model_extensions)
if model_extension == "": if model_extension == "":
# cannot delete arbitrary files result["alert"] = "Cannot delete file!"
return web.json_response(result) return web.json_response(result)
if os.path.isfile(model_path): if os.path.isfile(model_path):
@@ -991,14 +1026,17 @@ async def delete_model(request):
@server.PromptServer.instance.routes.post("/model-manager/notes/save") @server.PromptServer.instance.routes.post("/model-manager/notes/save")
async def set_notes(request): async def set_notes(request):
body = await request.json() body = await request.json()
result = { "success": False }
text = body.get("notes", None) text = body.get("notes", None)
if type(text) is not str: 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) model_path = body.get("path", None)
if type(model_path) is not str: 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_path, model_type = search_path_to_system_path(model_path)
model_extensions = folder_paths_get_supported_pt_extensions(model_type) model_extensions = folder_paths_get_supported_pt_extensions(model_type)
file_path_without_extension, _ = split_valid_ext(model_path, model_extensions) file_path_without_extension, _ = split_valid_ext(model_path, model_extensions)
@@ -1013,9 +1051,11 @@ async def set_notes(request):
print("Saved file: " + filename) print("Saved file: " + filename)
except ValueError as e: except ValueError as e:
print(e, file=sys.stderr, flush=True) 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" WEB_DIRECTORY = "web"

View File

@@ -452,6 +452,13 @@
width: 33px; 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 { .model-manager .model-manager-head .topbar-left {
display: flex; display: flex;
float: left; float: left;
@@ -460,7 +467,7 @@
.model-manager .model-manager-head .topbar-right { .model-manager .model-manager-head .topbar-right {
column-gap: 4px; column-gap: 4px;
display: flex; display: flex;
flex-direction: row-reverse; flex-direction: row-reverse; /* `row` to swap topbar direction */
float: right; float: right;
} }

View File

@@ -182,7 +182,7 @@ function buttonAlert(element, success, successText = "", failureText = "", reset
* @returns {Promise<boolean>} * @returns {Promise<boolean>}
*/ */
async function saveNotes(modelPath, newValue) { async function saveNotes(modelPath, newValue) {
return request( return await request(
"/model-manager/notes/save", "/model-manager/notes/save",
{ {
method: "POST", method: "POST",
@@ -192,7 +192,12 @@ async function saveNotes(modelPath, newValue) {
}), }),
} }
).then((result) => { ).then((result) => {
return result["success"]; const saved = result["success"];
const message = result["alert"];
if (message !== undefined) {
window.alert(message);
}
return saved;
}) })
.catch((err) => { .catch((err) => {
console.warn(err); console.warn(err);
@@ -1728,6 +1733,10 @@ class ModelInfoView {
} }
) )
.then((result) => { .then((result) => {
const message = result["alert"];
if (message !== undefined) {
window.alert(message);
}
return result["success"]; return result["success"];
}) })
.catch((err) => { .catch((err) => {
@@ -1747,6 +1756,10 @@ class ModelInfoView {
} }
) )
.then((result) => { .then((result) => {
const message = result["alert"];
if (message !== undefined) {
window.alert(message);
}
return result["success"]; return result["success"];
}) })
.catch((err) => { .catch((err) => {
@@ -1794,7 +1807,11 @@ class ModelInfoView {
) )
.then((result) => { .then((result) => {
const deleted = result["success"]; const deleted = result["success"];
if (deleted) const message = result["alert"];
if (message !== undefined) {
window.alert(message);
}
if (deleted)
{ {
container.innerHTML = ""; container.innerHTML = "";
this.element.style.display = "none"; this.element.style.display = "none";
@@ -1841,6 +1858,10 @@ class ModelInfoView {
) )
.then((result) => { .then((result) => {
const moved = result["success"]; const moved = result["success"];
const message = result["alert"];
if (message !== undefined) {
window.alert(message);
}
if (moved) if (moved)
{ {
moveDestinationInput.value = ""; moveDestinationInput.value = "";
@@ -1934,11 +1955,22 @@ class ModelInfoView {
async update(searchPath, updateModels, searchSeparator) { async update(searchPath, updateModels, searchSeparator) {
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}`)
.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) => { .catch((err) => {
console.log(err); console.log(err);
return null; return undefined;
}); });
if (info === null) { if (info === undefined || info === null) {
return; return;
} }
const infoHtml = this.elements.info; const infoHtml = this.elements.info;
@@ -1985,6 +2017,10 @@ class ModelInfoView {
) )
.then((result) => { .then((result) => {
const renamed = result["success"]; const renamed = result["success"];
const message = result["alert"];
if (message !== undefined) {
window.alert(message);
}
if (renamed) if (renamed)
{ {
container.innerHTML = ""; container.innerHTML = "";
@@ -2713,8 +2749,9 @@ class DownloadTab {
} }
).then((data) => { ).then((data) => {
const success = data["success"]; const success = data["success"];
if (!success) { const message = data["alert"];
console.warn(data["invalid"]); if (message !== undefined) {
window.alert(message);
} }
return [success, success ? "✔" : "📥︎"]; return [success, success ? "✔" : "📥︎"];
}).catch((err) => { }).catch((err) => {
@@ -3053,13 +3090,13 @@ class SettingsTab {
$: (el) => (this.elements.reloadButton = el), $: (el) => (this.elements.reloadButton = el),
type: "button", type: "button",
textContent: "Reload", // ⟳ textContent: "Reload", // ⟳
onclick: () => this.reload(true), onclick: async () => { await this.reload(true); },
}), }),
$el("button", { $el("button", {
$: (el) => (this.elements.saveButton = el), $: (el) => (this.elements.saveButton = el),
type: "button", type: "button",
textContent: "Save", // 💾︎ textContent: "Save", // 💾︎
onclick: () => this.save(), onclick: async () => { await this.save(); },
}), }),
]), ]),
/* /*