Rename model in Model View added.
- Generalized model/move to also support renaming.
This commit is contained in:
13
README.md
13
README.md
@@ -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?
|
||||
|
||||
57
__init__.py
57
__init__.py
@@ -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):
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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", {
|
||||
|
||||
Reference in New Issue
Block a user