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.
|
- View model metadata, including training tags and bucket resolutions.
|
||||||
- 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 a model's preview image (add a different one using a url or local upload).
|
- 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
|
### ComfyUI Node Graph
|
||||||
|
|
||||||
@@ -55,8 +55,16 @@ I made this fork because the original repo was inactive and missing many things
|
|||||||
|
|
||||||
## TODO
|
## 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
|
### 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?)
|
- 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.
|
- 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 dropdown
|
||||||
- Filter directory content in auto-suggest dropdown (not clear how this should be implemented)
|
- Filter directory content in auto-suggest dropdown (not clear how this should be implemented)
|
||||||
- Filters dropdown
|
- 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
|
- Favorites
|
||||||
- Swap between `and` and `or` keyword search? (currently `and`)
|
- Swap between `and` and `or` keyword search? (currently `and`)
|
||||||
|
|
||||||
### Code
|
### Code
|
||||||
|
|
||||||
- Javascript cleanup.
|
- Javascript cleanup.
|
||||||
|
- Stop abusing popup/modal.
|
||||||
- Better abstraction and objectification. (After codebase settles down)
|
- Better abstraction and objectification. (After codebase settles down)
|
||||||
- Separate into classes per tab?
|
- Separate into classes per tab?
|
||||||
- HTML generation all inside main class?
|
- 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):
|
def download_image(image_uri, model_path, overwrite):
|
||||||
extension = None # TODO: doesn't work for https://civitai.com/images/...
|
_, extension = os.path.splitext(image_uri) # TODO: doesn't work for https://civitai.com/images/...
|
||||||
for image_extension in image_extensions:
|
if not extension in image_extensions:
|
||||||
if image_uri.endswith(image_extension):
|
|
||||||
extension = image_extension
|
|
||||||
break
|
|
||||||
if extension is None:
|
|
||||||
raise ValueError("Invalid image type!")
|
raise ValueError("Invalid image type!")
|
||||||
|
|
||||||
path_without_extension, _ = os.path.splitext(model_path)
|
path_without_extension, _ = os.path.splitext(model_path)
|
||||||
file = path_without_extension + extension
|
file = path_without_extension + extension
|
||||||
download_file(image_uri, file, overwrite)
|
download_file(image_uri, file, overwrite)
|
||||||
@@ -607,12 +602,8 @@ async def download_model(request):
|
|||||||
return web.json_response(result)
|
return web.json_response(result)
|
||||||
|
|
||||||
name = formdata.get("name")
|
name = formdata.get("name")
|
||||||
model_extension = None
|
_, model_extension = os.path.splitext(name)
|
||||||
for ext in folder_paths_get_supported_pt_extensions(model_type):
|
if not model_extension in folder_paths_get_supported_pt_extensions(model_type):
|
||||||
if name.endswith(ext):
|
|
||||||
model_extension = ext
|
|
||||||
break
|
|
||||||
if model_extension is None:
|
|
||||||
result["invalid"] = "name"
|
result["invalid"] = "name"
|
||||||
return web.json_response(result)
|
return web.json_response(result)
|
||||||
file_name = os.path.join(directory, name)
|
file_name = os.path.join(directory, name)
|
||||||
@@ -646,21 +637,30 @@ async def move_model(request):
|
|||||||
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 })
|
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):
|
if not os.path.isfile(old_file):
|
||||||
return web.json_response({ "success": False })
|
return web.json_response({ "success": False })
|
||||||
_, filename = os.path.split(old_file)
|
_, model_extension = os.path.splitext(old_file)
|
||||||
|
if not model_extension in folder_paths_get_supported_pt_extensions(old_model_type):
|
||||||
new_path = body.get("newDirectory", None)
|
# cannot move arbitrary files
|
||||||
if new_path is None:
|
return web.json_response({ "success": False })
|
||||||
return web.json_response({ "success": False })
|
|
||||||
new_path, _ = search_path_to_system_path(new_path)
|
new_file = body.get("newFile", None)
|
||||||
if new_path is None:
|
if new_file is None or new_file == "":
|
||||||
return web.json_response({ "success": False })
|
# cannot have empty name
|
||||||
if not os.path.isdir(new_path):
|
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 })
|
return web.json_response({ "success": False })
|
||||||
|
|
||||||
new_file = os.path.join(new_path, filename)
|
|
||||||
if old_file == new_file:
|
if old_file == new_file:
|
||||||
return web.json_response({ "success": False })
|
return web.json_response({ "success": False })
|
||||||
try:
|
try:
|
||||||
@@ -704,12 +704,9 @@ async def delete_model(request):
|
|||||||
if file is None:
|
if file is None:
|
||||||
return web.json_response(result)
|
return web.json_response(result)
|
||||||
|
|
||||||
is_model = None
|
_, extension = os.path.split(file)
|
||||||
for ext in folder_paths_get_supported_pt_extensions(model_type):
|
if not extension in folder_paths_get_supported_pt_extensions(model_type):
|
||||||
if file.endswith(ext):
|
# cannot move arbitrary files
|
||||||
is_model = True
|
|
||||||
break
|
|
||||||
if not is_model:
|
|
||||||
return web.json_response(result)
|
return web.json_response(result)
|
||||||
|
|
||||||
if os.path.isfile(file):
|
if os.path.isfile(file):
|
||||||
|
|||||||
@@ -225,6 +225,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* model manager common */
|
/* model manager common */
|
||||||
|
.model-manager h1 {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.model-manager button,
|
.model-manager button,
|
||||||
.model-manager select,
|
.model-manager select,
|
||||||
.model-manager input {
|
.model-manager input {
|
||||||
@@ -504,7 +508,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);
|
||||||
padding: 16px;
|
|
||||||
width: auto;
|
width: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -750,11 +750,24 @@ function modelWidgetIndex(nodeType) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} path
|
* @param {string} path
|
||||||
* @returns {string}
|
* @returns {[string, string]}
|
||||||
*/
|
*/
|
||||||
function pathToFileString(path) {
|
function searchPath_split(path) {
|
||||||
const i = Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\")) + 1;
|
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();
|
event.stopPropagation();
|
||||||
}
|
}
|
||||||
else if (modelType === "embeddings") {
|
else if (modelType === "embeddings") {
|
||||||
const embeddingFile = pathToFileString(path);
|
const [embeddingDirectory, embeddingFile] = searchPath_split(path);
|
||||||
const selectedNodes = app.canvas.selected_nodes;
|
const selectedNodes = app.canvas.selected_nodes;
|
||||||
for (var i in selectedNodes) {
|
for (var i in selectedNodes) {
|
||||||
const selectedNode = selectedNodes[i];
|
const selectedNode = selectedNodes[i];
|
||||||
@@ -1079,7 +1092,7 @@ class ModelGrid {
|
|||||||
const nodeAtPos = app.graph.getNodeOnPos(pos[0], pos[1], app.canvas.visible_nodes);
|
const nodeAtPos = app.graph.getNodeOnPos(pos[0], pos[1], app.canvas.visible_nodes);
|
||||||
if (nodeAtPos) {
|
if (nodeAtPos) {
|
||||||
app.canvas.selectNode(nodeAtPos);
|
app.canvas.selectNode(nodeAtPos);
|
||||||
const embeddingFile = pathToFileString(path);
|
const [embeddingDirectory, embeddingFile] = searchPath_split(path);
|
||||||
target.value = insertEmbeddingIntoText(target.value, embeddingFile, removeEmbeddingExtension);
|
target.value = insertEmbeddingIntoText(target.value, embeddingFile, removeEmbeddingExtension);
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
}
|
}
|
||||||
@@ -1097,7 +1110,7 @@ class ModelGrid {
|
|||||||
let success = false;
|
let success = false;
|
||||||
if (nodeType === "Embedding") {
|
if (nodeType === "Embedding") {
|
||||||
if (navigator.clipboard){
|
if (navigator.clipboard){
|
||||||
const embeddingFile = pathToFileString(path);
|
const [embeddingDirectory, embeddingFile] = searchPath_split(path);
|
||||||
const embeddingText = insertEmbeddingIntoText("", embeddingFile, removeEmbeddingExtension);
|
const embeddingText = insertEmbeddingIntoText("", embeddingFile, removeEmbeddingExtension);
|
||||||
navigator.clipboard.writeText(embeddingText);
|
navigator.clipboard.writeText(embeddingText);
|
||||||
success = true;
|
success = true;
|
||||||
@@ -1669,12 +1682,12 @@ class ModelManager extends ComfyDialog {
|
|||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
const moveDestination = $el("input.search-text-area", {
|
const moveDestinationInput = $el("input.search-text-area", {
|
||||||
placeholder: "/",
|
placeholder: "/",
|
||||||
});
|
});
|
||||||
let searchDropdown = null;
|
let searchDropdown = null;
|
||||||
searchDropdown = new DirectoryDropdown(
|
searchDropdown = new DirectoryDropdown(
|
||||||
moveDestination,
|
moveDestinationInput,
|
||||||
() => {
|
() => {
|
||||||
searchDropdown.update(
|
searchDropdown.update(
|
||||||
this.#data.modelDirectories,
|
this.#data.modelDirectories,
|
||||||
@@ -1811,7 +1824,7 @@ class ModelManager extends ComfyDialog {
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
$el("div.search-models", [
|
$el("div.search-models", [
|
||||||
moveDestination,
|
moveDestinationInput,
|
||||||
searchDropdown.element,
|
searchDropdown.element,
|
||||||
]),
|
]),
|
||||||
$el("button", {
|
$el("button", {
|
||||||
@@ -1821,21 +1834,30 @@ class ModelManager extends ComfyDialog {
|
|||||||
let moved = false;
|
let moved = false;
|
||||||
if (confirmation) {
|
if (confirmation) {
|
||||||
const container = this.#el.modelInfoContainer;
|
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(
|
moved = await request(
|
||||||
`/model-manager/model/move`,
|
`/model-manager/model/move`,
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
"oldFile": container.dataset.path,
|
"oldFile": oldFile,
|
||||||
"newDirectory": moveDestination.value,
|
"newFile": newFile,
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
const moved = result["success"];
|
const moved = result["success"];
|
||||||
if (moved)
|
if (moved)
|
||||||
{
|
{
|
||||||
moveDestination.value = "";
|
moveDestinationInput.value = "";
|
||||||
container.innerHTML = "";
|
container.innerHTML = "";
|
||||||
this.#el.modelInfoView.style.display = "none";
|
this.#el.modelInfoView.style.display = "none";
|
||||||
this.#modelTab_updateModels();
|
this.#modelTab_updateModels();
|
||||||
@@ -2043,7 +2065,63 @@ class ModelManager extends ComfyDialog {
|
|||||||
const innerHtml = [];
|
const innerHtml = [];
|
||||||
const filename = info["File Name"];
|
const filename = info["File Name"];
|
||||||
if (filename !== undefined && filename !== null && filename !== "") {
|
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"]) {
|
if (info["Preview"]) {
|
||||||
@@ -2102,7 +2180,7 @@ class ModelManager extends ComfyDialog {
|
|||||||
elements.push($el("h2", [key + ":"]));
|
elements.push($el("h2", [key + ":"]));
|
||||||
const noteArea = $el("textarea.comfy-multiline-input", {
|
const noteArea = $el("textarea.comfy-multiline-input", {
|
||||||
value: value,
|
value: value,
|
||||||
rows: 5,
|
rows: 10,
|
||||||
});
|
});
|
||||||
elements.push(noteArea);
|
elements.push(noteArea);
|
||||||
elements.push($el("button", {
|
elements.push($el("button", {
|
||||||
|
|||||||
Reference in New Issue
Block a user