Remade download tab.

This commit is contained in:
Christian Bastian
2024-02-13 00:48:09 -05:00
parent 529115b0a4
commit d6b197d9c7
3 changed files with 262 additions and 181 deletions

View File

@@ -12,22 +12,34 @@ Currently it is still missing some features it should have.
## Fork Improvements
### Download Tab
- Remade download tab.
- View multiple models connected to url.
- Download preview images.
- Civitai and HuggingFace API token configurable in `server_settings.yaml`.
### Models Tab
- Search bar in models tab.
- Advanced keyword search using `"multiple words in quotes"` or a minus sign to `-exclude`.
- Search `/`subdirectories of model directories based on your file structure (for example, `/0/1.5/styles/clothing`).
- Add `/` at the start of the search bar to see auto-complete suggestions.
- Include models listed in ComfyUI's `extra_model_paths.yaml` or added in `ComfyUI/models`.
- Sort for models (Date Created, Date Modified, Name).
### ComfyUI Node Graph
- Button to copy a model to the ComfyUI clipboard or embedding to system clipboard. (Embedding copying requires secure http connection.)
- Button to add model to ComfyUI graph or embedding to selected nodes. (For small screens/low resolution.)
- Right, left, top and bottom toggleable sidebar modes.
- Drag a model onto the graph to add a new node.
- Drag a model onto an existing node to set the model field.
- Drag an embedding onto a text area to add it to the end.
- Increased supported preview image types.
### Settings Tab
- Correctly change colors using ComfyUI's theme colors.
- Simplified UI.
- Civitai and HuggingFace API token configurable in `server_settings.yaml`.
- Settings tab saved in `ui_settings.yaml`.
- Hide/Show 'add' and 'copy-to-clipboard' buttons.
- Text to always search.
@@ -89,11 +101,3 @@ Currently it is still missing some features it should have.
### Sidebar
- ☐ Drag sidebar width/height dynamically.
### Directory Browser and Downloading tab
(NOTE: It is a impossible to put a model automatically in the correct folder if model type information is not given or ambigious. To fully solve this requires making a file browser where files can be moved around.)
- ☐ Replace Install tab with Downloading tab (more practical IMO).
- ☐ Download a model from a url.
- ☐ Choose save path/directory to download within vaild model directories. (Alert Yes/No if need to create new dirs?)

View File

@@ -55,7 +55,7 @@
gap: 16px;
}
.comfy-grid .item {
.model-manager .item {
position: relative;
width: 230px;
height: 345px;
@@ -64,12 +64,29 @@
border-radius: 8px;
}
.comfy-grid .item img {
.model-manager .item img {
width: 100%;
height: 100%;
object-fit: cover;
}
.model-manager .model-preview-button-left,
.model-manager .model-preview-button-right {
position: absolute;
top: 0;
bottom: 0;
margin: auto;
border-radius: 20px;
}
.model-manager .model-preview-button-right {
right: 4px;
}
.model-manager .model-preview-button-left {
left: 4px;
}
.comfy-grid .model-label {
background-color: #000a;
width: 100%;
@@ -99,7 +116,7 @@
height: 0;
}
.comfy-grid .item .model-preview-overlay {
.model-manager .item .model-preview-overlay {
position: absolute;
top: 0;
left: 0;
@@ -306,13 +323,18 @@
}
.model-manager .row {
position: sticky;
position: relative;
padding-top: 2px;
margin-top: -2px;
padding-bottom: 18px;
margin-bottom: 1px;
top: -1px;
background-color: var(--comfy-input-bg);
}
.model-manager [data-name="Install"] .row,
.model-manager [data-name="Models"] .row {
position: sticky;
z-index: 1;
}
@@ -347,9 +369,15 @@
}
.model-manager .search-text-area,
.model-manager .source-text-area,
.model-manager .plain-text-area,
.model-manager .model-select-dropdown {
flex: 1;
min-height: 36px;
padding-block: 0;
}
.model-manager .model-select-dropdown {
min-height: 40px;
}
.model-manager .search-dropdown {
@@ -424,3 +452,12 @@
border-radius: 8px;
object-fit: cover;
}
.model-manager [data-name="Download"] summary {
padding: 16px;
}
.model-manager .download-details {
border-radius: 16px;
padding: 0px 16px 0px 16px;
}

View File

@@ -1439,10 +1439,10 @@ class ModelManager extends ComfyDialog {
]
),
$tabs([
$tab("Install", this.#createSourceInstall()),
$tab("Download", [this.#downloadTab_new()]),
//$tab("Install", this.#createSourceInstall()),
$tab("Models", this.#modelTab_new()),
$tab("Settings", [this.#settingsTab_new()]),
//$tab("Download2", [this.#downloadTab_new()]),
]),
]),
]
@@ -1453,7 +1453,7 @@ class ModelManager extends ComfyDialog {
#init() {
this.#settingsTab_reload(false);
this.#refreshSourceList();
//this.#refreshSourceList();
this.#modelTab_updateModels();
}
@@ -1471,7 +1471,7 @@ class ModelManager extends ComfyDialog {
$: (el) => (this.#el.loadSourceBtn = el),
onclick: () => this.#refreshSourceList(),
}),
$el("input.source-text-area", {
$el("input.plain-text-area", {
$: (el) => (this.#el.loadSourceFromInput = el),
placeholder: "https://ComfyUI-Model-Manager/index.json",
}),
@@ -1718,7 +1718,7 @@ class ModelManager extends ComfyDialog {
if (reloadData) {
// Is this slow?
this.#refreshSourceList();
//this.#refreshSourceList();
this.#modelTab_updateModels();
}
}
@@ -1940,11 +1940,11 @@ class ModelManager extends ComfyDialog {
* @param {String[]} modelTypes
* @param {DirectoryItem[]} modelDirectories
* @param {String} sep
* @param {int} id
* @returns {HTMLDivElement}
*/
#downloadTab_modelInfo(info, modelTypes, modelDirectories, sep) {
#downloadTab_modelInfo(info, modelTypes, modelDirectories, sep, id) {
// TODO: use passed in info
const RADIO_MODEL_PREVIEW_GROUP_NAME = "model-download-info-preview-model";
const RADIO_MODEL_PREVIEW_DEFAULT = "Default Preview";
const RADIO_MODEL_PREVIEW_CUSTOM = "Custom Preview Url";
@@ -1962,14 +1962,14 @@ class ModelManager extends ComfyDialog {
filename: null,
};
$el("input", {
$el("input.search-text-area", {
$: (el) => (els.saveDirectoryPath = el),
type: "text",
placeholder: "/0",
value: "/0",
});
$el("select", {
$el("select.model-select-dropdown", {
$: (el) => (els.modelTypeSelect = el),
}, (() => {
const options = [$el("option", { value: "" }, ["-- Model Type --"])];
@@ -1997,172 +1997,208 @@ class ModelManager extends ComfyDialog {
true,
);
const radioGroupName = "model-download-info-preview-model" + "-" + id;
const radioGroup = $radioGroup({
name: radioGroupName,
onchange: (value) => {
switch (value) {
case RADIO_MODEL_PREVIEW_DEFAULT:
const bottonStyleDisplay = els.previewImgs.children.length > 1 ? "block" : "none";
els.buttonLeft.style.display = bottonStyleDisplay;
els.buttonRight.style.display = bottonStyleDisplay;
els.modelPreviewContainer.style.display = "block";
els.customPreviewContainer.style.display = "none";
break;
case RADIO_MODEL_PREVIEW_CUSTOM:
els.modelPreviewContainer.style.display = "none";
els.customPreviewContainer.style.display = "flex";
break;
default:
els.modelPreviewContainer.style.display = "none";
els.customPreviewContainer.style.display = "none";
break;
}
},
options: (() => {
const radios = [];
radios.push({ value: "No Preview" });
if (info["images"].length > 0) {
radios.push({ value: RADIO_MODEL_PREVIEW_DEFAULT });
}
radios.push({ value: RADIO_MODEL_PREVIEW_CUSTOM });
return radios;
})(),
});
const filepath = info["downloadFilePath"];
const modelInfo = $el("details", [
const modelInfo = $el("details.download-details", [
$el("summary", [filepath + info["fileName"]]),
$el("div", [
$el("div", [
$el("button", {
onclick: async (e) => {
const record = {};
record["download"] = info["downloadUrl"];
record["type"] = els.modelTypeSelect.value;
if (record["type"] === "") { return; } // TODO: notify user in app
record["path"] = els.saveDirectoryPath.value;
if (record["path"] === "/") { return; } // TODO: notify user in app
record["name"] = (() => {
const filename = info["fileName"];
const name = els.filename.value;
if (name === "") {
return filename;
}
const ext = MODEL_EXTENSIONS.find((ext) => {
return filename.endsWith(ext);
}) ?? "";
return name + ext;
})();
record["image"] = (() => {
const value = document.querySelector(`input[name="${RADIO_MODEL_PREVIEW_GROUP_NAME}"]:checked`).value;
switch (value) {
case RADIO_MODEL_PREVIEW_DEFAULT:
const children = els.previewImgs.children;
for (let i = 0; i < children.length; i++) {
const child = children[i];
if (child.style.display !== "none") {
return child.src;
}
}
return "";
case RADIO_MODEL_PREVIEW_CUSTOM:
return els.customPreviewUrl.value;
}
return "";
})();
record["overwrite"] = true; // TODO: add to UI
e.disabled = true;
await request(
"/model-manager/download",
{
method: "POST",
body: JSON.stringify(record),
}
).then(data => {
if (data["success"] !== true) {
// TODO: notify user in app
console.error('Failed to download model:', data);
}
}).catch(err => {
// TODO: notify user in app
console.error('Failed to download model:', err);
$el("div", {
style: { display: "flex", gap: "16px" },
}, [
$el("div.item", {
$: (el) => (els.modelPreviewContainer = el),
style: { display: "none" },
}, [
$el("div", {
$: (el) => (els.previewImgs = el),
}, (() => {
const imgs = info["images"].map((url) => {
return $el("img", {
src: url,
style: { display: "none" },
loading: "lazy",
});
e.disabled = false;
},
}, ["Download"]),
els.modelTypeSelect,
$el("div", [
els.saveDirectoryPath,
searchDropdown.element,
});
if (imgs.length > 0) {
imgs[0].style.display = "block";
}
return imgs;
})()),
$el("div.model-preview-overlay", [
$el("button.icon-button.model-preview-button-left", {
$: (el) => (els.buttonLeft = el),
onclick: () => ModelManager.#downloadTab_updatePreview(els.previewImgs, -1),
textContent: "←",
}),
$el("button.icon-button.model-preview-button-right", {
$: (el) => (els.buttonRight = el),
onclick: () => ModelManager.#downloadTab_updatePreview(els.previewImgs, 1),
textContent: "→",
}),
]),
$el("input", {
$: (el) => (els.filename = el),
type: "text",
placeholder: (() => {
const filename = info["fileName"];
// TODO: only remove valid model file extensions
const i = filename.lastIndexOf(".");
return i === - 1 ? filename : filename.substring(0, i);
})(),
}),
]),
/*
$el("div", (() => {
return Object.entries(info["details"]).filter(([, value]) => {
return value !== undefined && value !== null;
}).map(([key, value]) => {
const el = document.createElement("p");
el.innerText = key + ": " + value;
return el;
});
})()),
*/
$el("div.model-preview-select-radio-container", [
$radioGroup({
name: RADIO_MODEL_PREVIEW_GROUP_NAME,
onchange: (value) => {
switch (value) {
case RADIO_MODEL_PREVIEW_DEFAULT:
const bottonStyleDisplay = els.previewImgs.children.length > 1 ? "block" : "none";
els.buttonLeft.style.display = bottonStyleDisplay;
els.buttonRight.style.display = bottonStyleDisplay;
els.modelPreviewContainer.style.display = "block";
els.customPreviewContainer.style.display = "none";
break;
case RADIO_MODEL_PREVIEW_CUSTOM:
els.modelPreviewContainer.style.display = "none";
els.customPreviewContainer.style.display = "block";
break;
default:
els.modelPreviewContainer.style.display = "none";
els.customPreviewContainer.style.display = "none";
break;
}
},
options: (() => {
const radios = [];
radios.push({ value: "No Preview" });
if (info["images"].length > 0) {
radios.push({ value: RADIO_MODEL_PREVIEW_DEFAULT });
}
radios.push({ value: RADIO_MODEL_PREVIEW_CUSTOM });
return radios;
})(),
}),
$el("div", [
$el("div", {
$: (el) => (els.modelPreviewContainer = el),
style: { display: "none" },
}, [
$el("div", {
$: (el) => (els.previewImgs = el),
}, (() => {
const imgs = info["images"].map((url) => {
return $el("img", {
src: url,
style: { display: "none" },
loading: "lazy",
});
});
if (imgs.length > 0) {
imgs[0].style.display = "block";
}
return imgs;
})()),
$el("div", [
$el("div", {
style: { "margin-top": "8px" }
}, [
$el("div.model-preview-select-radio-container", [
$el("div.row.tab-header-flex-block", [radioGroup]),
$el("div", [
$el("button", {
$: (el) => (els.buttonLeft = el),
onclick: () => ModelManager.#downloadTab_updatePreview(els.previewImgs, -1),
}, ["LEFT"]),
$el("button", {
$: (el) => (els.buttonRight = el),
onclick: () => ModelManager.#downloadTab_updatePreview(els.previewImgs, 1),
}, ["RIGHT"]),
$el("div.row.tab-header-flex-block", {
$: (el) => (els.customPreviewContainer = el),
style: { display: "none" },
}, [
$el("input.search-text-area", {
$: (el) => (els.customPreviewUrl = el),
type: "text",
placeholder: "https://custom-image-preview.png"
}),
]),
]),
]),
$el("div", {
$: (el) => (els.customPreviewContainer = el),
style: { display: "none" },
}, [
$el("input.search-text-area", {
$: (el) => (els.customPreviewUrl = el),
$el("div.row.tab-header-flex-block", [
els.modelTypeSelect,
]),
$el("div.row.tab-header-flex-block", [
els.saveDirectoryPath,
searchDropdown.element,
]),
$el("div.row.tab-header-flex-block", [
$el("button.icon-button", {
textContent: "📥︎",
onclick: async (e) => {
const record = {};
record["download"] = info["downloadUrl"];
record["type"] = els.modelTypeSelect.value;
if (record["type"] === "") { return; } // TODO: notify user in app
record["path"] = els.saveDirectoryPath.value;
if (record["path"] === "/") { return; } // TODO: notify user in app
record["name"] = (() => {
const filename = info["fileName"];
const name = els.filename.value;
if (name === "") {
return filename;
}
const ext = MODEL_EXTENSIONS.find((ext) => {
return filename.endsWith(ext);
}) ?? "";
return name + ext;
})();
record["image"] = (() => {
const value = document.querySelector(`input[name="${radioGroupName}"]:checked`).value;
switch (value) {
case RADIO_MODEL_PREVIEW_DEFAULT:
const children = els.previewImgs.children;
for (let i = 0; i < children.length; i++) {
const child = children[i];
if (child.style.display !== "none") {
return child.src;
}
}
return "";
case RADIO_MODEL_PREVIEW_CUSTOM:
return els.customPreviewUrl.value;
}
return "";
})();
record["overwrite"] = false; // TODO: add to UI
e.target.disabled = true;
let success = true;
let resultText = "✔";
await request(
"/model-manager/download",
{
method: "POST",
body: JSON.stringify(record),
}
).then(data => {
if (data["success"] !== true) {
// TODO: notify user in app
console.error('Failed to download model:', data);
success = false;
resultText = "📥︎";
}
}).catch(err => {
// TODO: notify user in app
console.error('Failed to download model:', err);
success = false;
resultText = "📥︎";
});
buttonAlert(e.target, success, "✔", "✖", resultText);
e.target.disabled = success;
},
}),
$el("input.plain-text-area", {
$: (el) => (els.filename = el),
type: "text",
placeholder: "(preview image url)"
placeholder: (() => {
const filename = info["fileName"];
// TODO: only remove valid model file extensions
const i = filename.lastIndexOf(".");
return i === - 1 ? filename : filename.substring(0, i);
})(),
}),
]),
]),
/*
$el("div", (() => {
return Object.entries(info["details"]).filter(([, value]) => {
return value !== undefined && value !== null;
}).map(([key, value]) => {
const el = document.createElement("p");
el.innerText = key + ": " + value;
return el;
});
})()),
*/
]),
]),
]);
if (info["images"].length > 0) {
const children = radioGroup.children;
for (let i = 0; i < children.length; i++) {
const child = children[i];
const radioButton = child.children[0];
if (radioButton.value === RADIO_MODEL_PREVIEW_DEFAULT) {
els.modelPreviewContainer.style.display = "block";
radioButton.checked = true;
break;
}
};
}
const modelTypeSelect = els.modelTypeSelect;
modelTypeSelect.selectedIndex = 0; // reset
const comfyUIModelType = (
@@ -2257,12 +2293,13 @@ class ModelManager extends ComfyDialog {
return MODEL_EXTENSIONS.find((ext) => {
return filename.endsWith(ext);
}) ?? false;
}).map((modelInfo) => {
}).map((modelInfo, id) => {
return this.#downloadTab_modelInfo(
modelInfo,
modelTypes,
this.#data.modelDirectories,
this.#sep,
id,
);
});
if (modelInfos.length === 0) {
@@ -2279,11 +2316,11 @@ class ModelManager extends ComfyDialog {
*/
#downloadTab_new() {
return $el("div", [
$el("div", [
$el("div.row.tab-header-flex-block", [
$el("input.search-text-area", {
$: (el) => (this.#el.modelInfoUrl = el),
type: "text",
placeholder: "Civitai or HuggingFace model",
placeholder: "example: https://civitai.com/models/207992/stable-video-diffusion-svd",
onkeydown: (e) => {
if (e.key === "Enter") {
e.stopPropagation();
@@ -2291,13 +2328,16 @@ class ModelManager extends ComfyDialog {
}
},
}),
$el("button", {
$el("button.icon-button", {
onclick: () => this.#downloadTab_search(),
}, ["Search"]),
textContent: "🔍︎",
}),
]),
$el("div", {
$: (el) => (this.#el.modelInfos = el),
}),
}, [
$el("div", ["Input a URL to view download settings."]),
]),
]);
}
}