Add Setting to optionally download descriptions as Notes.
- Added more server print statements for saving/moving/deleting files. - Added slight gap between download inputs.
This commit is contained in:
10
__init__.py
10
__init__.py
@@ -185,6 +185,7 @@ def ui_rules():
|
||||
Rule("model-add-embedding-extension", False, bool),
|
||||
Rule("model-add-drag-strict-on-field", False, bool),
|
||||
Rule("model-add-offset", 25, int),
|
||||
Rule("download-save-description-as-text-file", False, bool),
|
||||
]
|
||||
|
||||
|
||||
@@ -232,6 +233,7 @@ async def save_ui_settings(request):
|
||||
rules = ui_rules()
|
||||
validated_settings = config_loader.validated(rules, settings)
|
||||
success = config_loader.yaml_save(ui_settings_uri, rules, validated_settings)
|
||||
print("Saved file: " + ui_settings_uri)
|
||||
return web.json_response({
|
||||
"success": success,
|
||||
"settings": validated_settings if success else "",
|
||||
@@ -920,6 +922,7 @@ async def move_model(request):
|
||||
return web.json_response({ "success": False })
|
||||
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 })
|
||||
@@ -928,8 +931,10 @@ async def move_model(request):
|
||||
for extension in preview_extensions + (model_info_extension,):
|
||||
old_file = old_file_without_extension + extension
|
||||
if os.path.isfile(old_file):
|
||||
new_file = new_file_without_extension + extension
|
||||
try:
|
||||
shutil.move(old_file, new_file_without_extension + extension)
|
||||
shutil.move(old_file, new_file)
|
||||
print("Moved file: " + new_file)
|
||||
except ValueError as e:
|
||||
print(e, file=sys.stderr, flush=True)
|
||||
|
||||
@@ -942,6 +947,7 @@ def delete_same_name_files(path_without_extension, extensions, keep_extension=No
|
||||
file = path_without_extension + extension
|
||||
if os.path.isfile(file):
|
||||
os.remove(file)
|
||||
print("Deleted file: " + file)
|
||||
|
||||
|
||||
@server.PromptServer.instance.routes.post("/model-manager/model/delete")
|
||||
@@ -965,6 +971,7 @@ async def delete_model(request):
|
||||
if os.path.isfile(model_path):
|
||||
os.remove(model_path)
|
||||
result["success"] = True
|
||||
print("Deleted file: " + model_path)
|
||||
|
||||
delete_same_name_files(path_and_name, preview_extensions)
|
||||
delete_same_name_files(path_and_name, (model_info_extension,))
|
||||
@@ -994,6 +1001,7 @@ async def set_notes(request):
|
||||
try:
|
||||
with open(filename, "w", encoding="utf-8") as f:
|
||||
f.write(text)
|
||||
print("Saved file: " + filename)
|
||||
except ValueError as e:
|
||||
print(e, file=sys.stderr, flush=True)
|
||||
web.json_response({ "success": False })
|
||||
|
||||
@@ -239,10 +239,16 @@
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.model-manager [data-name="Download"] .download-settings {
|
||||
.model-manager [data-name="Download"] .download-settings-wrapper {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.model-manager [data-name="Download"] .download-settings {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
row-gap: 8px;
|
||||
}
|
||||
|
||||
.model-manager .download-model-infos {
|
||||
padding: 16px 0;
|
||||
}
|
||||
|
||||
@@ -175,6 +175,31 @@ function buttonAlert(element, success, successText = "", failureText = "", reset
|
||||
}, 1000, element, name, resetText);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} modelPath
|
||||
* @param {string} newValue
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
async function saveNotes(modelPath, newValue) {
|
||||
return request(
|
||||
"/model-manager/notes/save",
|
||||
{
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
"path": modelPath,
|
||||
"notes": newValue,
|
||||
}),
|
||||
}
|
||||
).then((result) => {
|
||||
return result["success"];
|
||||
})
|
||||
.catch((err) => {
|
||||
console.warn(err);
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
class Tabs {
|
||||
/** @type {Record<string, HTMLDivElement>} */
|
||||
#head = {};
|
||||
@@ -1938,7 +1963,8 @@ class ModelInfoView {
|
||||
if (noteValue.trim() !== savedNotesValue.trim()) {
|
||||
const saveChanges = window.confirm("Save notes?");
|
||||
if (saveChanges) {
|
||||
const saved = await this.#saveNotes(noteValue);
|
||||
const path = this.elements.info.dataset.path;
|
||||
const saved = await saveNotes(path, noteValue);
|
||||
if (!saved) {
|
||||
window.alert("Failed to save notes!");
|
||||
return;
|
||||
@@ -1957,32 +1983,6 @@ class ModelInfoView {
|
||||
this.element.style.display = "none";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} newValue
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
async #saveNotes(newValue) {
|
||||
return request(
|
||||
"/model-manager/notes/save",
|
||||
{
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
"path": this.elements.info.dataset.path,
|
||||
"notes": newValue,
|
||||
}),
|
||||
}
|
||||
).then((result) => {
|
||||
const success = result["success"];
|
||||
if (success) {
|
||||
this.#savedNotesValue = newValue;
|
||||
}
|
||||
return success;
|
||||
})
|
||||
.catch((err) => {
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} searchPath
|
||||
* @param {() => Promise<void>} updateModels
|
||||
@@ -2140,7 +2140,12 @@ class ModelInfoView {
|
||||
elements.push($el("button", {
|
||||
textContent: "Save Notes",
|
||||
onclick: async (e) => {
|
||||
const saved = await this.#saveNotes(notes.value);
|
||||
const path = this.elements.info.dataset.path;
|
||||
const newValue = notes.value;
|
||||
const saved = await saveNotes(path, newValue);
|
||||
if (saved) {
|
||||
this.#savedNotesValue = newValue;
|
||||
}
|
||||
buttonAlert(e.target, saved);
|
||||
},
|
||||
}));
|
||||
@@ -2240,6 +2245,7 @@ class Civitai {
|
||||
return image["url"];
|
||||
}),
|
||||
"name": modelVersionInfo["name"],
|
||||
"description": modelVersionInfo["description"] ?? "",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2275,6 +2281,7 @@ class Civitai {
|
||||
return {
|
||||
"name": modelVersionInfo["model"]["name"],
|
||||
"type": modelVersionInfo["model"]["type"],
|
||||
"description": modelVersionInfo["description"] ?? "",
|
||||
"versions": [filesInfo]
|
||||
}
|
||||
}
|
||||
@@ -2306,7 +2313,8 @@ class Civitai {
|
||||
return {
|
||||
"name": modelInfo["name"],
|
||||
"type": modelInfo["type"],
|
||||
"versions": modelVersions
|
||||
"description": modelInfo["description"] ?? "",
|
||||
"versions": modelVersions,
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -2506,9 +2514,51 @@ class DownloadTab {
|
||||
/** @type {HTMLInputElement} */ overwrite: null,
|
||||
};
|
||||
|
||||
/** @type {DOMParser} */
|
||||
#domParser = null;
|
||||
|
||||
/** @type {() => Promise<void>} */
|
||||
#updateModels = () => {};
|
||||
|
||||
/**
|
||||
* @param {ModelData} modelData
|
||||
* @param {any} settings
|
||||
* @param {() => Promise<void>} updateModels
|
||||
*/
|
||||
constructor(modelData, settings, updateModels) {
|
||||
this.#domParser = new DOMParser();
|
||||
this.#updateModels = updateModels;
|
||||
const search = async() => this.search(modelData, settings);
|
||||
$el("div.tab-header", {
|
||||
$: (el) => (this.element = el),
|
||||
}, [
|
||||
$el("div.row.tab-header-flex-block", [
|
||||
$el("input.search-text-area", {
|
||||
$: (el) => (this.elements.url = el),
|
||||
type: "text",
|
||||
name: "model download url",
|
||||
autocomplete: "off",
|
||||
placeholder: "example: https://civitai.com/models/207992/stable-video-diffusion-svd",
|
||||
onkeydown: (e) => {
|
||||
if (e.key === "Enter") {
|
||||
e.stopPropagation();
|
||||
search();
|
||||
}
|
||||
},
|
||||
}),
|
||||
$el("button.icon-button", {
|
||||
onclick: () => search(),
|
||||
textContent: "🔍︎",
|
||||
}),
|
||||
]),
|
||||
$el("div.download-model-infos", {
|
||||
$: (el) => (this.elements.infos = el),
|
||||
}, [
|
||||
$el("div", ["Input a URL to select a model to download."]),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to return the related ComfyUI model directory if unambiguous.
|
||||
*
|
||||
@@ -2554,9 +2604,10 @@ class DownloadTab {
|
||||
* @param {Object} info
|
||||
* @param {ModelData} modelData
|
||||
* @param {int} id
|
||||
* @param {any} settings
|
||||
* @returns {HTMLDivElement}
|
||||
*/
|
||||
#modelInfo(info, modelData, id) {
|
||||
#modelInfo(info, modelData, id, settings) {
|
||||
const downloadPreviewSelect = new ImageSelect(
|
||||
"model-download-info-preview-model" + "-" + id,
|
||||
info["images"],
|
||||
@@ -2604,17 +2655,13 @@ class DownloadTab {
|
||||
style: { display: "flex", "flex-wrap": "wrap", gap: "16px" },
|
||||
}, [
|
||||
downloadPreviewSelect.elements.previews,
|
||||
$el("div.download-settings", [
|
||||
$el("div", {
|
||||
style: { "margin-top": "8px" }
|
||||
}, [
|
||||
$el("div.download-settings-wrapper", [
|
||||
$el("div.download-settings", [
|
||||
$el("button.icon-button", {
|
||||
textContent: "📥︎",
|
||||
onclick: async (e) => {
|
||||
const formData = new FormData();
|
||||
formData.append("download", info["downloadUrl"]);
|
||||
formData.append("path", el_saveDirectoryPath.value);
|
||||
formData.append("name", (() => {
|
||||
const pathDirectory = el_saveDirectoryPath.value;
|
||||
const modelName = (() => {
|
||||
const filename = info["fileName"];
|
||||
const name = el_filename.value;
|
||||
if (name === "") {
|
||||
@@ -2624,7 +2671,11 @@ class DownloadTab {
|
||||
return filename.endsWith(ext);
|
||||
}) ?? "";
|
||||
return name + ext;
|
||||
})());
|
||||
})();
|
||||
const formData = new FormData();
|
||||
formData.append("download", info["downloadUrl"]);
|
||||
formData.append("path", pathDirectory);
|
||||
formData.append("name", modelName);
|
||||
const image = await downloadPreviewSelect.getImage();
|
||||
formData.append("image", image === PREVIEW_NONE_URI ? "" : image);
|
||||
formData.append("overwrite", this.elements.overwrite.checked);
|
||||
@@ -2645,6 +2696,14 @@ class DownloadTab {
|
||||
return [false, "📥︎"];
|
||||
});
|
||||
if (success) {
|
||||
const description = info["description"];
|
||||
if (settings["download-save-description-as-text-file"].checked && description !== "") {
|
||||
const modelPath = pathDirectory + searchSeparator + modelName;
|
||||
const saved = await saveNotes(modelPath, description);
|
||||
if (!saved) {
|
||||
console.warn("Description was note saved as notes!");
|
||||
}
|
||||
}
|
||||
this.#updateModels();
|
||||
}
|
||||
buttonAlert(e.target, success, "✔", "✖", resultText);
|
||||
@@ -2669,8 +2728,9 @@ class DownloadTab {
|
||||
|
||||
/**
|
||||
* @param {ModelData} modelData
|
||||
* @param {any} settings
|
||||
*/
|
||||
async search(modelData) {
|
||||
async search(modelData, settings) {
|
||||
const infosHtml = this.elements.infos;
|
||||
infosHtml.innerHTML = "";
|
||||
|
||||
@@ -2683,8 +2743,11 @@ class DownloadTab {
|
||||
}
|
||||
const infos = [];
|
||||
const type = civitaiInfo["type"];
|
||||
const modelInfo = civitaiInfo["description"]?? "";
|
||||
civitaiInfo["versions"].forEach((version) => {
|
||||
const images = version["images"];
|
||||
const versionDescription = version["description"]??"";
|
||||
const description = (versionDescription + "\n\n" + modelInfo).trim().replace(/<[^>]+>/g, ""); // quick hack
|
||||
version["files"].forEach((file) => {
|
||||
infos.push({
|
||||
"images": images,
|
||||
@@ -2692,6 +2755,7 @@ class DownloadTab {
|
||||
"modelType": type,
|
||||
"downloadUrl": file["downloadUrl"],
|
||||
"downloadFilePath": "",
|
||||
"description": description,
|
||||
"details": {
|
||||
"fileSizeKB": file["sizeKB"],
|
||||
"fileType": file["type"],
|
||||
@@ -2724,6 +2788,7 @@ class DownloadTab {
|
||||
"modelType": "",
|
||||
"downloadUrl": baseDownloadUrl + "/" + file + "?download=true",
|
||||
"downloadFilePath": file.substring(0, indexSep + 1),
|
||||
"description": "",
|
||||
"details": {
|
||||
"fileSizeKB": undefined, // TODO: too hard?
|
||||
},
|
||||
@@ -2739,6 +2804,7 @@ class DownloadTab {
|
||||
"modelType": DownloadTab.modelTypeToComfyUiDirectory(file["type"], "") ?? "",
|
||||
"downloadUrl": file["download"],
|
||||
"downloadFilePath": "",
|
||||
"description": file["description"],
|
||||
"details": {},
|
||||
};
|
||||
});
|
||||
@@ -2756,6 +2822,7 @@ class DownloadTab {
|
||||
modelInfo,
|
||||
modelData,
|
||||
id,
|
||||
settings,
|
||||
);
|
||||
});
|
||||
if (modelInfosHtml.length === 0) {
|
||||
@@ -2765,51 +2832,18 @@ class DownloadTab {
|
||||
if (modelInfosHtml.length === 1) {
|
||||
modelInfosHtml[0].open = true;
|
||||
}
|
||||
const label = $checkbox({
|
||||
$: (el) => { this.elements.overwrite = el; },
|
||||
textContent: "Overwrite Existing Files",
|
||||
});
|
||||
modelInfosHtml.unshift(label);
|
||||
|
||||
const downloadSettings = $el("div", [
|
||||
$checkbox({
|
||||
$: (el) => { this.elements.overwrite = el; },
|
||||
textContent: "Overwrite Existing Files.",
|
||||
checked: false,
|
||||
}),
|
||||
]);
|
||||
modelInfosHtml.unshift(downloadSettings);
|
||||
}
|
||||
infosHtml.append.apply(infosHtml, modelInfosHtml);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ModelData} modelData
|
||||
* @param {() => Promise<void>} updateModels
|
||||
*/
|
||||
constructor(modelData, updateModels) {
|
||||
this.#updateModels = updateModels;
|
||||
const search = async() => this.search(modelData);
|
||||
$el("div.tab-header", {
|
||||
$: (el) => (this.element = el),
|
||||
}, [
|
||||
$el("div.row.tab-header-flex-block", [
|
||||
$el("input.search-text-area", {
|
||||
$: (el) => (this.elements.url = el),
|
||||
type: "text",
|
||||
name: "model download url",
|
||||
autocomplete: "off",
|
||||
placeholder: "example: https://civitai.com/models/207992/stable-video-diffusion-svd",
|
||||
onkeydown: (e) => {
|
||||
if (e.key === "Enter") {
|
||||
e.stopPropagation();
|
||||
search();
|
||||
}
|
||||
},
|
||||
}),
|
||||
$el("button.icon-button", {
|
||||
onclick: () => search(),
|
||||
textContent: "🔍︎",
|
||||
}),
|
||||
]),
|
||||
$el("div.download-model-infos", {
|
||||
$: (el) => (this.elements.infos = el),
|
||||
}, [
|
||||
$el("div", ["Input a URL to select a model to download."]),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
class ModelTab {
|
||||
@@ -2986,6 +3020,8 @@ class SettingsTab {
|
||||
/** @type {HTMLInputElement} */ "model-add-embedding-extension": null,
|
||||
/** @type {HTMLInputElement} */ "model-add-drag-strict-on-field": null,
|
||||
/** @type {HTMLInputElement} */ "model-add-offset": null,
|
||||
|
||||
/** @type {HTMLInputElement} */ "download-save-description-as-text-file": null,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -3164,6 +3200,11 @@ class SettingsTab {
|
||||
}),
|
||||
$el("p", ["Add model offset"]),
|
||||
]),
|
||||
$el("h2", ["Download"]),
|
||||
$checkbox({
|
||||
$: (el) => (settings["download-save-description-as-text-file"] = el),
|
||||
textContent: "Save descriptions as notes (in .txt file).",
|
||||
}),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -3284,6 +3325,7 @@ class ModelManager extends ComfyDialog {
|
||||
|
||||
const downloadTab = new DownloadTab(
|
||||
this.#modelData,
|
||||
this.#settingsTab.elements.settings,
|
||||
this.#refreshModels,
|
||||
);
|
||||
this.#downloadTab = DownloadTab;
|
||||
|
||||
Reference in New Issue
Block a user