Rename model in Model View added.

- Generalized model/move to also support renaming.
This commit is contained in:
Christian Bastian
2024-02-25 23:02:02 -05:00
parent 839b93c9be
commit a4dd2f570b
4 changed files with 135 additions and 48 deletions

View File

@@ -34,7 +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.
- Read, edit and save notes in a `.txt` file beside the model.
- Change or remove a model's preview image (add a different one using a url or local upload).
- Move or **permanently** remove models.
- Rename, move or **permanently** remove models.
### ComfyUI Node Graph
@@ -55,8 +55,16 @@ I made this fork because the original repo was inactive and missing many things
## TODO
### Download Model
- Checkbox to optionally save description in `.txt` file for Civitai. (what about "About Model"?)
- Server setting to enable creating new folders (on download, on move).
### Download Model Info
- Auto-save notes? (requires debounce and save confirmation)
- Load workflow from preview (Should be easy to add with ComfyUI built-in clipboard.)
- Default weights on add/drag? (optional override on drag?)
- Optional (re)download `📥︎` model info from the internet and cache the text file locally. (requires checksum?)
- Radio buttons to swap between downloaded and server view.
@@ -89,13 +97,14 @@ I made this fork because the original repo was inactive and missing many things
- Filter directory dropdown
- Filter directory content in auto-suggest dropdown (not clear how this should be implemented)
- Filters dropdown
- Stable Diffusion model version, if applicable
- Stable Diffusion model version, if applicable (Maybe dropdown list of "Base Models" is more pratical to impliment?)
- Favorites
- Swap between `and` and `or` keyword search? (currently `and`)
### Code
- Javascript cleanup.
- Stop abusing popup/modal.
- Better abstraction and objectification. (After codebase settles down)
- Separate into classes per tab?
- HTML generation all inside main class?

View File

@@ -486,14 +486,9 @@ def download_file(url, filename, overwrite):
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:
_, extension = os.path.splitext(image_uri) # TODO: doesn't work for https://civitai.com/images/...
if not extension in image_extensions:
raise ValueError("Invalid image type!")
path_without_extension, _ = os.path.splitext(model_path)
file = path_without_extension + extension
download_file(image_uri, file, overwrite)
@@ -607,12 +602,8 @@ async def download_model(request):
return web.json_response(result)
name = formdata.get("name")
model_extension = None
for ext in folder_paths_get_supported_pt_extensions(model_type):
if name.endswith(ext):
model_extension = ext
break
if model_extension is None:
_, model_extension = os.path.splitext(name)
if not model_extension in folder_paths_get_supported_pt_extensions(model_type):
result["invalid"] = "name"
return web.json_response(result)
file_name = os.path.join(directory, name)
@@ -646,21 +637,30 @@ async def move_model(request):
old_file = body.get("oldFile", None)
if old_file is None:
return web.json_response({ "success": False })
old_file, _ = 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):
return web.json_response({ "success": False })
_, filename = os.path.split(old_file)
new_path = body.get("newDirectory", None)
if new_path is None:
return web.json_response({ "success": False })
new_path, _ = search_path_to_system_path(new_path)
if new_path is None:
return web.json_response({ "success": False })
if not os.path.isdir(new_path):
_, model_extension = os.path.splitext(old_file)
if not model_extension in folder_paths_get_supported_pt_extensions(old_model_type):
# cannot move arbitrary files
return web.json_response({ "success": False })
new_file = body.get("newFile", None)
if new_file is None or new_file == "":
# cannot have empty name
return web.json_response({ "success": False })
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 })
if os.path.isfile(new_file):
# cannot overwrite existing file
return web.json_response({ "success": False })
if not model_extension in folder_paths_get_supported_pt_extensions(new_model_type):
return web.json_response({ "success": False })
new_file_dir, _ = os.path.split(new_file)
if not os.path.isdir(new_file_dir):
return web.json_response({ "success": False })
new_file = os.path.join(new_path, filename)
if old_file == new_file:
return web.json_response({ "success": False })
try:
@@ -704,12 +704,9 @@ async def delete_model(request):
if file is None:
return web.json_response(result)
is_model = None
for ext in folder_paths_get_supported_pt_extensions(model_type):
if file.endswith(ext):
is_model = True
break
if not is_model:
_, extension = os.path.split(file)
if not extension in folder_paths_get_supported_pt_extensions(model_type):
# cannot move arbitrary files
return web.json_response(result)
if os.path.isfile(file):

View File

@@ -225,6 +225,10 @@
}
/* model manager common */
.model-manager h1 {
min-width: 0;
}
.model-manager button,
.model-manager select,
.model-manager input {
@@ -504,7 +508,6 @@
background-color: var(--bg-color);
border-radius: 16px;
color: var(--fg-color);
padding: 16px;
width: auto;
}

View File

@@ -750,11 +750,24 @@ function modelWidgetIndex(nodeType) {
/**
* @param {string} path
* @returns {string}
* @returns {[string, string]}
*/
function pathToFileString(path) {
function searchPath_split(path) {
const i = Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\")) + 1;
return path.slice(i);
return [path.slice(0, i), path.slice(i)];
}
/**
* @param {string} path
* @param {string[]} extensions
* @returns {[string, string]}
*/
function searchPath_splitExtension(path) {
const i = path.lastIndexOf(".");
if (i === -1) {
return [path, ""];
}
return [path.slice(0, i), path.slice(i)];
}
/**
@@ -1016,7 +1029,7 @@ class ModelGrid {
event.stopPropagation();
}
else if (modelType === "embeddings") {
const embeddingFile = pathToFileString(path);
const [embeddingDirectory, embeddingFile] = searchPath_split(path);
const selectedNodes = app.canvas.selected_nodes;
for (var i in selectedNodes) {
const selectedNode = selectedNodes[i];
@@ -1079,7 +1092,7 @@ class ModelGrid {
const nodeAtPos = app.graph.getNodeOnPos(pos[0], pos[1], app.canvas.visible_nodes);
if (nodeAtPos) {
app.canvas.selectNode(nodeAtPos);
const embeddingFile = pathToFileString(path);
const [embeddingDirectory, embeddingFile] = searchPath_split(path);
target.value = insertEmbeddingIntoText(target.value, embeddingFile, removeEmbeddingExtension);
event.stopPropagation();
}
@@ -1097,7 +1110,7 @@ class ModelGrid {
let success = false;
if (nodeType === "Embedding") {
if (navigator.clipboard){
const embeddingFile = pathToFileString(path);
const [embeddingDirectory, embeddingFile] = searchPath_split(path);
const embeddingText = insertEmbeddingIntoText("", embeddingFile, removeEmbeddingExtension);
navigator.clipboard.writeText(embeddingText);
success = true;
@@ -1669,12 +1682,12 @@ class ModelManager extends ComfyDialog {
constructor() {
super();
const moveDestination = $el("input.search-text-area", {
const moveDestinationInput = $el("input.search-text-area", {
placeholder: "/",
});
let searchDropdown = null;
searchDropdown = new DirectoryDropdown(
moveDestination,
moveDestinationInput,
() => {
searchDropdown.update(
this.#data.modelDirectories,
@@ -1811,7 +1824,7 @@ class ModelManager extends ComfyDialog {
},
}),
$el("div.search-models", [
moveDestination,
moveDestinationInput,
searchDropdown.element,
]),
$el("button", {
@@ -1821,21 +1834,30 @@ class ModelManager extends ComfyDialog {
let moved = false;
if (confirmation) {
const container = this.#el.modelInfoContainer;
const oldFile = container.dataset.path;
const [oldFilePath, oldFileName] = searchPath_split(oldFile);
const [_, extension] = searchPath_splitExtension(oldFile);
const newFile = (
moveDestinationInput.value +
this.#searchSeparator +
oldFileName +
extension
);
moved = await request(
`/model-manager/model/move`,
{
method: "POST",
body: JSON.stringify({
"oldFile": container.dataset.path,
"newDirectory": moveDestination.value,
"oldFile": oldFile,
"newFile": newFile,
}),
}
)
.then((result) => {
const moved = result["success"];
if (moved)
if (moved)
{
moveDestination.value = "";
moveDestinationInput.value = "";
container.innerHTML = "";
this.#el.modelInfoView.style.display = "none";
this.#modelTab_updateModels();
@@ -2043,7 +2065,63 @@ class ModelManager extends ComfyDialog {
const innerHtml = [];
const filename = info["File Name"];
if (filename !== undefined && filename !== null && filename !== "") {
innerHtml.push($el("h1", [filename]));
innerHtml.push(
$el("div.row", {
style: { margin: "8px 0 16px 0" },
}, [
$el("h1", {
style: { margin: "0" },
}, [
filename,
]),
$el("div", [
$el("button.icon-button", {
textContent: "✎",
onclick: async(e) => {
const name = window.prompt("New model name:");
let renamed = false;
if (name !== null && name !== "") {
const container = this.#el.modelInfoContainer;
const oldFile = container.dataset.path;
const [oldFilePath, oldFileName] = searchPath_split(oldFile);
const [_, extension] = searchPath_splitExtension(oldFile);
const newFile = (
oldFilePath +
this.#searchSeparator +
name +
extension
);
renamed = await request(
`/model-manager/model/move`,
{
method: "POST",
body: JSON.stringify({
"oldFile": oldFile,
"newFile": newFile,
}),
}
)
.then((result) => {
const renamed = result["success"];
if (renamed)
{
container.innerHTML = "";
this.#el.modelInfoView.style.display = "none";
this.#modelTab_updateModels();
}
return renamed;
})
.catch(err => {
console.log(err);
return false;
});
}
buttonAlert(e.target, renamed);
},
}),
]),
]),
);
}
if (info["Preview"]) {
@@ -2102,7 +2180,7 @@ class ModelManager extends ComfyDialog {
elements.push($el("h2", [key + ":"]));
const noteArea = $el("textarea.comfy-multiline-input", {
value: value,
rows: 5,
rows: 10,
});
elements.push(noteArea);
elements.push($el("button", {