From 2e0d9c5a01874a3cfea16beaff627e908bc5c5df Mon Sep 17 00:00:00 2001 From: Christian Bastian <80225746+cdb-boop@users.noreply.github.com> Date: Wed, 3 Apr 2024 15:57:30 -0400 Subject: [PATCH] Improved style of Download Tab. - Slightly adjusted preview select radio button spacing. - Adding missing async/await to in download tab code. --- web/model-manager.css | 35 ++++++-- web/model-manager.js | 201 ++++++++++++++++++++++-------------------- 2 files changed, 135 insertions(+), 101 deletions(-) diff --git a/web/model-manager.css b/web/model-manager.css index a460585..b11aacb 100644 --- a/web/model-manager.css +++ b/web/model-manager.css @@ -234,11 +234,33 @@ /* download tab */ -.model-manager [data-name="Download"] summary { +.model-manager .download-model-infos { + display: flex; + flex-direction: column; + padding: 16px 0; + row-gap: 10px; +} + +.model-manager .download-details summary { + background-color: var(--comfy-menu-bg); + border-radius: 16px; padding: 16px; word-wrap: break-word; } +.model-manager .download-details[open] summary { + background-color: var(--border-color); +} + +.model-manager .download-details > div { + column-gap: 8px; + display: flex; + flex-direction: row; + flex-wrap: wrap; + padding: 8px; + row-gap: 16px; +} + .model-manager [data-name="Download"] .download-settings-wrapper { flex: 1; } @@ -246,11 +268,7 @@ .model-manager [data-name="Download"] .download-settings { display: flex; flex-direction: column; - row-gap: 8px; -} - -.model-manager .download-model-infos { - padding: 16px 0; + row-gap: 16px; } /* models tab */ @@ -406,6 +424,11 @@ flex: 1; } +.model-preview-select-radio-inputs > div { + height: 40px; + padding: 16px 0 8px 0; +} + .model-manager .model-preview-select-radio-container img { position: relative; width: 230px; diff --git a/web/model-manager.js b/web/model-manager.js index 42760a5..9d83414 100644 --- a/web/model-manager.js +++ b/web/model-manager.js @@ -714,7 +714,7 @@ class ImageSelect { $: (el) => (this.elements.radioGroup = el), }, [ $el("div.row.tab-header-flex-block", [el_radioButtons]), - $el("div", [ + $el("div.model-preview-select-radio-inputs", [ el_custom, el_upload, ]), @@ -2500,10 +2500,99 @@ class HuggingFace { "baseDownloadUrl": baseDownloadUrl, "modelFiles": modelFiles, "images": images, + "name": modelId, }; } } +/** + * @param {string} urlText + * @returns {Promise<[string, any[]]>} [name, modelInfos] + */ +async function getModelInfos(urlText) { + // TODO: class for proper return type + return await (async () => { + if (urlText.startsWith("https://civitai.com")) { + const civitaiInfo = await Civitai.getFilteredInfo(urlText); + if (Object.keys(civitaiInfo).length === 0) { + return ["", []]; + } + const name = civitaiInfo["name"]; + 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, + "fileName": file["name"], + "modelType": type, + "downloadUrl": file["downloadUrl"], + "downloadFilePath": "", + "description": description, + "details": { + "fileSizeKB": file["sizeKB"], + "fileType": file["type"], + "fp": file["fp"], + "quant": file["size"], + "fileFormat": file["format"], + }, + }); + }); + }); + return [name, infos]; + } + if (urlText.startsWith("https://huggingface.co")) { + const hfInfo = await HuggingFace.getFilteredInfo(urlText); + if (Object.keys(hfInfo).length === 0) { + return ["", []]; + } + const files = hfInfo["modelFiles"]; + if (files.length === 0) { + return ["", []]; + } + const name = hfInfo["name"]; + const baseDownloadUrl = hfInfo["baseDownloadUrl"]; + const infos = hfInfo["modelFiles"].map((file) => { + const indexSep = file.lastIndexOf("/"); + const filename = file.substring(indexSep + 1); + return { + "images": hfInfo["images"], + "fileName": filename, + "modelType": "", + "downloadUrl": baseDownloadUrl + "/" + file + "?download=true", + "downloadFilePath": file.substring(0, indexSep + 1), + "description": "", + "details": { + "fileSizeKB": undefined, // TODO: too hard? + }, + }; + }); + return [name, infos]; + } + if (urlText.endsWith(".json")) { + const indexInfo = await request(urlText).catch(() => []); + const name = urlText.substring(math.max(urlText.lastIndexOf("/"), 0)); + const infos = indexInfo.map((file) => { + return { + "images": [], + "fileName": file["name"], + "modelType": DownloadTab.modelTypeToComfyUiDirectory(file["type"], "") ?? "", + "downloadUrl": file["download"], + "downloadFilePath": "", + "description": file["description"], + "details": {}, + }; + }); + return [name, infos]; + } + return ["", []]; + })(); +} + class DownloadTab { /** @type {HTMLDivElement} */ element = null; @@ -2528,7 +2617,7 @@ class DownloadTab { constructor(modelData, settings, updateModels) { this.#domParser = new DOMParser(); this.#updateModels = updateModels; - const search = async() => this.search(modelData, settings); + const update = async() => { await this.#update(modelData, settings); }; $el("div.tab-header", { $: (el) => (this.element = el), }, [ @@ -2539,15 +2628,15 @@ class DownloadTab { name: "model download url", autocomplete: "off", placeholder: "example: https://civitai.com/models/207992/stable-video-diffusion-svd", - onkeydown: (e) => { + onkeydown: async (e) => { if (e.key === "Enter") { e.stopPropagation(); - search(); + await update(); } }, }), $el("button.icon-button", { - onclick: () => search(), + onclick: async () => { await update(); }, textContent: "🔍︎", }), ]), @@ -2607,7 +2696,7 @@ class DownloadTab { * @param {any} settings * @returns {HTMLDivElement} */ - #modelInfo(info, modelData, id, settings) { + #modelInfoHtml(info, modelData, id, settings) { const downloadPreviewSelect = new ImageSelect( "model-download-info-preview-model" + "-" + id, info["images"], @@ -2651,9 +2740,7 @@ class DownloadTab { const filepath = info["downloadFilePath"]; const modelInfo = $el("details.download-details", [ $el("summary", [filepath + info["fileName"]]), - $el("div", { - style: { display: "flex", "flex-wrap": "wrap", gap: "16px" }, - }, [ + $el("div", [ downloadPreviewSelect.elements.previews, $el("div.download-settings-wrapper", [ $el("div.download-settings", [ @@ -2725,100 +2812,20 @@ class DownloadTab { return modelInfo; } - + /** * @param {ModelData} modelData * @param {any} settings */ - async search(modelData, settings) { - const infosHtml = this.elements.infos; - infosHtml.innerHTML = ""; - - const urlText = this.elements.url.value; - const modelInfos = await (async () => { - if (urlText.startsWith("https://civitai.com")) { - const civitaiInfo = await Civitai.getFilteredInfo(urlText); - if (Object.keys(civitaiInfo).length === 0) { - return []; - } - 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, - "fileName": file["name"], - "modelType": type, - "downloadUrl": file["downloadUrl"], - "downloadFilePath": "", - "description": description, - "details": { - "fileSizeKB": file["sizeKB"], - "fileType": file["type"], - "fp": file["fp"], - "quant": file["size"], - "fileFormat": file["format"], - }, - }); - }); - }); - return infos; - } - if (urlText.startsWith("https://huggingface.co")) { - const hfInfo = await HuggingFace.getFilteredInfo(urlText); - if (Object.keys(hfInfo).length === 0) { - return []; - } - const files = hfInfo["modelFiles"]; - if (files.length === 0) { - return []; - } - - const baseDownloadUrl = hfInfo["baseDownloadUrl"]; - return hfInfo["modelFiles"].map((file) => { - const indexSep = file.lastIndexOf("/"); - const filename = file.substring(indexSep + 1); - return { - "images": hfInfo["images"], - "fileName": filename, - "modelType": "", - "downloadUrl": baseDownloadUrl + "/" + file + "?download=true", - "downloadFilePath": file.substring(0, indexSep + 1), - "description": "", - "details": { - "fileSizeKB": undefined, // TODO: too hard? - }, - }; - }); - } - if (urlText.endsWith(".json")) { - const indexInfo = await request(urlText).catch(() => []); - return indexInfo.map((file) => { - return { - "images": [], - "fileName": file["name"], - "modelType": DownloadTab.modelTypeToComfyUiDirectory(file["type"], "") ?? "", - "downloadUrl": file["download"], - "downloadFilePath": "", - "description": file["description"], - "details": {}, - }; - }); - } - return []; - })(); - + async #update(modelData, settings) { + const [name, modelInfos] = await getModelInfos(this.elements.url.value); const modelInfosHtml = modelInfos.filter((modelInfo) => { const filename = modelInfo["fileName"]; return MODEL_EXTENSIONS.find((ext) => { return filename.endsWith(ext); }) ?? false; }).map((modelInfo, id) => { - return this.#modelInfo( + return this.#modelInfoHtml( modelInfo, modelData, id, @@ -2833,15 +2840,19 @@ class DownloadTab { modelInfosHtml[0].open = true; } - const downloadSettings = $el("div", [ + const header = $el("div", [ + $el("h1", [name]), $checkbox({ $: (el) => { this.elements.overwrite = el; }, textContent: "Overwrite Existing Files.", checked: false, }), ]); - modelInfosHtml.unshift(downloadSettings); + modelInfosHtml.unshift(header); } + + const infosHtml = this.elements.infos; + infosHtml.innerHTML = ""; infosHtml.append.apply(infosHtml, modelInfosHtml); } }