diff --git a/__init__.py b/__init__.py index d996187..8500264 100644 --- a/__init__.py +++ b/__init__.py @@ -299,6 +299,13 @@ class Civitai: url += f"?modelVersionId={version_id}" return url + @staticmethod + def get_preview_urls(model_version_info): + images = model_version_info.get("images", None) + if images is None: + return [] + return [image_info["url"] for image_info in images] + @staticmethod def search_notes(model_version_info): if len(model_version_info) == 0: @@ -423,6 +430,15 @@ class ModelInfo: # TODO: search other websites return "" + @staticmethod + def get_web_preview_urls(model_info): + if len(model_info) == 0: + return [] + preview_urls = Civitai.get_preview_urls(model_info) + if len(preview_urls) > 0: + return preview_urls + # TODO: support other websites + return [] @server.PromptServer.instance.routes.get("/model-manager/timestamp") async def get_timestamp(request): @@ -1254,6 +1270,9 @@ async def get_model_metadata(request): tags = list(tags.items()) tags.sort(key=lambda x: x[1], reverse=True) + model_info = ModelInfo.try_load_cached(abs_path) + web_previews = ModelInfo.get_web_preview_urls(model_info) + result["success"] = True result["info"] = data if metadata is not None: @@ -1262,6 +1281,7 @@ async def get_model_metadata(request): result["tags"] = tags result["notes"] = notes result["url"] = web_url + result["webPreviews"] = web_previews return web.json_response(result) diff --git a/web/model-manager.css b/web/model-manager.css index 1e6c8bc..1543137 100644 --- a/web/model-manager.css +++ b/web/model-manager.css @@ -407,6 +407,11 @@ border-radius: 8px; } +.model-manager .model-info-container .item { + width: fit-content; + height: 50vh; +} + .model-manager .item img { width: 100%; height: 100%; @@ -414,15 +419,13 @@ border-radius: 8px; } -.model-manager .model-info-container .item { - width: auto; - height: auto; -} -.model-manager .model-info-container .item img { +.model-manager .model-info-container .item img, +.model-manager .model-preview-full { height: auto; width: auto; max-width: 100%; max-height: 50vh; + border-radius: 8px; } .model-manager .model-preview-button-left, diff --git a/web/model-manager.js b/web/model-manager.js index d0c270a..96ee8b8 100644 --- a/web/model-manager.js +++ b/web/model-manager.js @@ -673,6 +673,7 @@ class ImageSelect { /** @type {HTMLImageElement} */ defaultPreviewNoImage: null, /** @type {HTMLDivElement} */ defaultPreviews: null, /** @type {HTMLDivElement} */ defaultUrl: null, + /** @type {HTMLDivElement} */ previewButtons: null, /** @type {HTMLImageElement} */ customUrlPreview: null, /** @type {HTMLInputElement} */ customUrl: null, @@ -741,8 +742,11 @@ class ImageSelect { return PREVIEW_NONE_URI; } - /** @returns {void} */ - resetModelInfoPreview() { + /** + * @param {String[]} defaultPreviewUrls + * @returns {void} + */ + resetModelInfoPreview(defaultPreviewUrls = []) { let noimage = this.elements.defaultUrl.dataset.noimage; [ this.elements.defaultPreviewNoImage, @@ -761,6 +765,18 @@ class ImageSelect { el.src = PREVIEW_NONE_URI; } }); + const defaultPreviews = this.elements.defaultPreviews; + defaultPreviews.innerHTML = ''; + if (defaultPreviewUrls.length > 0) { + ImageSelect.generateDefaultPreviews(defaultPreviewUrls).forEach(previewElement => { + defaultPreviews.appendChild(previewElement); + }); + } + else { + const defaultImage = ImageSelect.generateDefaultPreviews([PREVIEW_NONE_URI]); + defaultPreviews.appendChild(defaultImage[0]); + } + this.elements.previewButtons.style.display = defaultPreviewUrls.length > 1 ? 'block' : 'none'; this.checkDefault(); this.elements.uploadFile.value = ''; this.elements.customUrl.value = ''; @@ -821,6 +837,28 @@ class ImageSelect { children[currentIndex].style.display = 'block'; } + /** + * @param {string[]|undefined} defaultPreviewUrls + * @returns {HTMLImageElement[]} + */ + static generateDefaultPreviews(defaultPreviewUrls) { + const imgs = defaultPreviewUrls.map((url) => { + return $el('img', { + loading: + 'lazy' /* `loading` BEFORE `src`; Known bug in Firefox 124.0.2 and Safari for iOS 17.4.1 (https://stackoverflow.com/a/76252772) */, + src: url, + style: { display: 'none' }, + onerror: (e) => { + e.target.src = PREVIEW_NONE_URI; + }, + }); + }); + if (imgs.length > 0) { + imgs[0].style.display = 'block'; + } + return imgs; + } + /** * @param {string} radioGroupName - Should be unique for every radio group. * @param {string[]|undefined} defaultPreviews @@ -858,23 +896,7 @@ class ImageSelect { height: '100%', }, }, - (() => { - const imgs = defaultPreviews.map((url) => { - return $el('img', { - loading: - 'lazy' /* `loading` BEFORE `src`; Known bug in Firefox 124.0.2 and Safari for iOS 17.4.1 (https://stackoverflow.com/a/76252772) */, - src: url, - style: { display: 'none' }, - onerror: (e) => { - e.target.src = el_defaultUri.dataset.noimage ?? PREVIEW_NONE_URI; - }, - }); - }); - if (imgs.length > 0) { - imgs[0].style.display = 'block'; - } - return imgs; - })(), + ImageSelect.generateDefaultPreviews(defaultPreviews), ); const el_uploadPreview = $el('img', { @@ -984,6 +1006,7 @@ class ImageSelect { const el_previewButtons = $el( 'div.model-preview-overlay', { + $: (el) => (this.elements.previewButtons = el), style: { display: el_defaultPreviews.children.length > 1 ? 'block' : 'none', }, @@ -2480,11 +2503,6 @@ class ModelInfo { }, }).element; this.elements.setPreviewButton = setPreviewButton; - previewSelect.elements.radioButtons.addEventListener('change', (e) => { - setPreviewButton.style.display = previewSelect.defaultIsChecked() - ? 'none' - : 'block'; - }); this.element = $el( 'div', @@ -2700,7 +2718,7 @@ class ModelInfo { */ async update(searchPath, updateModels, searchSeparator) { const path = encodeURIComponent(searchPath); - const [info, metadata, tags, noteText, url] = await comfyRequest( + const [info, metadata, tags, noteText, url, webPreviews] = await comfyRequest( `/model-manager/model/info/${path}`, ) .then((result) => { @@ -2716,8 +2734,9 @@ class ModelInfo { result['info'], result['metadata'], result['tags'], - result['notes'], - result['url'], + result['notes'], + result['url'], + result['webPreviews'], ]; }) .catch((err) => { @@ -2830,50 +2849,75 @@ class ModelInfo { } else { defaultUrl.dataset.noimage = PREVIEW_NONE_URI; } - previewSelect.resetModelInfoPreview(); - const setPreviewButton = this.elements.setPreviewButton; - setPreviewButton.style.display = previewSelect.defaultIsChecked() - ? 'none' - : 'block'; + previewSelect.resetModelInfoPreview(webPreviews); + const setPreviewDiv = $el('div.row.tab-header', { + style: { + display: "none" + } + }, [ + $el('div.row.tab-header-flex-block', [ + previewSelect.elements.radioGroup, + ]), + $el('div.row.tab-header-flex-block', [ + this.elements.setPreviewButton, + ]), + ]); + previewSelect.elements.previews.style.display = "none"; + + let previewUri; + if (info['Preview']) { + const imagePath = encodeURIComponent(info['Preview']['path']); + const imageDateModified = encodeURIComponent(info['Preview']['dateModified']); + previewUri = imageUri(imagePath, imageDateModified); + } else { + previewUri = PREVIEW_NONE_URI; + } + const previewImage = $el('img.model-preview-full', { + loading: + 'lazy' /* `loading` BEFORE `src`; Known bug in Firefox 124.0.2 and Safari for iOS 17.4.1 (https://stackoverflow.com/a/76252772) */, + src: previewUri, + }); innerHtml.push( $el('div', [ - previewSelect.elements.previews, $el('div.row.tab-header', { style: { "flex-direction": "row" } }, [ - new ComfyButton({ - icon: 'arrow-bottom-left-bold-box-outline', - tooltip: 'Attempt to load preview image workflow', - classList: 'comfyui-button icon-button', - action: async () => { - const urlString = - previewSelect.elements.defaultPreviews.children[0].src; - await loadWorkflow(urlString); - }, - }).element, - new ComfyButton({ - icon: 'open-in-new', - tooltip: 'Attempt to open model url page in a new tab.', - classList: 'comfyui-button icon-button', - action: async (e) => { - const [button, icon, span] = comfyButtonDisambiguate(e.target); - button.disabled = true; - let webUrl; - if (url !== undefined && url !== "") { - webUrl = url; - } - else { - webUrl = await tryGetModelWebUrl(searchPath); - } - const success = tryOpenUrl(webUrl, searchPath); - comfyButtonAlert(e.target, success, "mdi-check-bold", "mdi-close-thick"); - button.disabled = false; - }, + new ComfyButton({ + icon: 'arrow-bottom-left-bold-box-outline', + tooltip: 'Attempt to load preview image workflow', + classList: 'comfyui-button icon-button', + action: async () => { + await loadWorkflow(previewImage.src); + }, + }).element, + new ComfyButton({ + icon: 'open-in-new', + tooltip: 'Attempt to open model url page in a new tab.', + classList: 'comfyui-button icon-button', + action: async (e) => { + const [button, icon, span] = comfyButtonDisambiguate(e.target); + button.disabled = true; + let webUrl; + if (url !== undefined && url !== "") { + webUrl = url; + } + else { + webUrl = await tryGetModelWebUrl(searchPath); + } + const success = tryOpenUrl(webUrl, searchPath); + comfyButtonAlert(e.target, success, "mdi-check-bold", "mdi-close-thick"); + button.disabled = false; + }, }).element, new ComfyButton({ icon: 'earth-arrow-down', - tooltip: 'Try download model info.', + tooltip: 'Hash model and try to download model info.', classList: 'comfyui-button icon-button', action: async(e) => { + const confirm = window.confirm('Overwrite model info?'); + if (!confirm) { + comfyButtonAlert(e.target, false, 'mdi-check-bold', 'mdi-close-thick'); + return; + } const [button, icon, span] = comfyButtonDisambiguate(e.target); button.disabled = true; const success = await comfyRequest( @@ -2896,13 +2940,28 @@ class ModelInfo { button.disabled = false; }, }).element, + new ComfyButton({ + icon: 'image-edit-outline', + tooltip: 'Open preview edit dialog.', + classList: 'comfyui-button icon-button', + action: () => { + // TODO: toggle button border highlight + if (previewImage.style.display === "none") { + setPreviewDiv.style.display = "none"; + previewSelect.elements.previews.style.display = "none"; + previewImage.style.display = ""; + } + else { + previewImage.style.display = "none"; + previewSelect.elements.previews.style.display = ""; + setPreviewDiv.style.display = ""; + } + }, + }).element, ]), - $el('div.row.tab-header', [ - $el('div.row.tab-header-flex-block', [ - previewSelect.elements.radioGroup, - ]), - $el('div.row.tab-header-flex-block', [setPreviewButton]), - ]), + previewImage, + previewSelect.elements.previews, + setPreviewDiv, $el('h2', ['File Info:']), $el( 'div',