Set model previews from model info view.

This commit is contained in:
Christian Bastian
2024-09-23 00:59:48 -04:00
parent 3b8735afef
commit c35cb757fa
3 changed files with 155 additions and 73 deletions

View File

@@ -299,6 +299,13 @@ class Civitai:
url += f"?modelVersionId={version_id}" url += f"?modelVersionId={version_id}"
return url 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 @staticmethod
def search_notes(model_version_info): def search_notes(model_version_info):
if len(model_version_info) == 0: if len(model_version_info) == 0:
@@ -423,6 +430,15 @@ class ModelInfo:
# TODO: search other websites # TODO: search other websites
return "" 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") @server.PromptServer.instance.routes.get("/model-manager/timestamp")
async def get_timestamp(request): async def get_timestamp(request):
@@ -1254,6 +1270,9 @@ async def get_model_metadata(request):
tags = list(tags.items()) tags = list(tags.items())
tags.sort(key=lambda x: x[1], reverse=True) 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["success"] = True
result["info"] = data result["info"] = data
if metadata is not None: if metadata is not None:
@@ -1262,6 +1281,7 @@ async def get_model_metadata(request):
result["tags"] = tags result["tags"] = tags
result["notes"] = notes result["notes"] = notes
result["url"] = web_url result["url"] = web_url
result["webPreviews"] = web_previews
return web.json_response(result) return web.json_response(result)

View File

@@ -407,6 +407,11 @@
border-radius: 8px; border-radius: 8px;
} }
.model-manager .model-info-container .item {
width: fit-content;
height: 50vh;
}
.model-manager .item img { .model-manager .item img {
width: 100%; width: 100%;
height: 100%; height: 100%;
@@ -414,15 +419,13 @@
border-radius: 8px; border-radius: 8px;
} }
.model-manager .model-info-container .item { .model-manager .model-info-container .item img,
width: auto; .model-manager .model-preview-full {
height: auto;
}
.model-manager .model-info-container .item img {
height: auto; height: auto;
width: auto; width: auto;
max-width: 100%; max-width: 100%;
max-height: 50vh; max-height: 50vh;
border-radius: 8px;
} }
.model-manager .model-preview-button-left, .model-manager .model-preview-button-left,

View File

@@ -673,6 +673,7 @@ class ImageSelect {
/** @type {HTMLImageElement} */ defaultPreviewNoImage: null, /** @type {HTMLImageElement} */ defaultPreviewNoImage: null,
/** @type {HTMLDivElement} */ defaultPreviews: null, /** @type {HTMLDivElement} */ defaultPreviews: null,
/** @type {HTMLDivElement} */ defaultUrl: null, /** @type {HTMLDivElement} */ defaultUrl: null,
/** @type {HTMLDivElement} */ previewButtons: null,
/** @type {HTMLImageElement} */ customUrlPreview: null, /** @type {HTMLImageElement} */ customUrlPreview: null,
/** @type {HTMLInputElement} */ customUrl: null, /** @type {HTMLInputElement} */ customUrl: null,
@@ -741,8 +742,11 @@ class ImageSelect {
return PREVIEW_NONE_URI; return PREVIEW_NONE_URI;
} }
/** @returns {void} */ /**
resetModelInfoPreview() { * @param {String[]} defaultPreviewUrls
* @returns {void}
*/
resetModelInfoPreview(defaultPreviewUrls = []) {
let noimage = this.elements.defaultUrl.dataset.noimage; let noimage = this.elements.defaultUrl.dataset.noimage;
[ [
this.elements.defaultPreviewNoImage, this.elements.defaultPreviewNoImage,
@@ -761,6 +765,18 @@ class ImageSelect {
el.src = PREVIEW_NONE_URI; 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.checkDefault();
this.elements.uploadFile.value = ''; this.elements.uploadFile.value = '';
this.elements.customUrl.value = ''; this.elements.customUrl.value = '';
@@ -821,6 +837,28 @@ class ImageSelect {
children[currentIndex].style.display = 'block'; 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} radioGroupName - Should be unique for every radio group.
* @param {string[]|undefined} defaultPreviews * @param {string[]|undefined} defaultPreviews
@@ -858,23 +896,7 @@ class ImageSelect {
height: '100%', height: '100%',
}, },
}, },
(() => { ImageSelect.generateDefaultPreviews(defaultPreviews),
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;
})(),
); );
const el_uploadPreview = $el('img', { const el_uploadPreview = $el('img', {
@@ -984,6 +1006,7 @@ class ImageSelect {
const el_previewButtons = $el( const el_previewButtons = $el(
'div.model-preview-overlay', 'div.model-preview-overlay',
{ {
$: (el) => (this.elements.previewButtons = el),
style: { style: {
display: el_defaultPreviews.children.length > 1 ? 'block' : 'none', display: el_defaultPreviews.children.length > 1 ? 'block' : 'none',
}, },
@@ -2480,11 +2503,6 @@ class ModelInfo {
}, },
}).element; }).element;
this.elements.setPreviewButton = setPreviewButton; this.elements.setPreviewButton = setPreviewButton;
previewSelect.elements.radioButtons.addEventListener('change', (e) => {
setPreviewButton.style.display = previewSelect.defaultIsChecked()
? 'none'
: 'block';
});
this.element = $el( this.element = $el(
'div', 'div',
@@ -2700,7 +2718,7 @@ class ModelInfo {
*/ */
async update(searchPath, updateModels, searchSeparator) { async update(searchPath, updateModels, searchSeparator) {
const path = encodeURIComponent(searchPath); 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}`, `/model-manager/model/info/${path}`,
) )
.then((result) => { .then((result) => {
@@ -2718,6 +2736,7 @@ class ModelInfo {
result['tags'], result['tags'],
result['notes'], result['notes'],
result['url'], result['url'],
result['webPreviews'],
]; ];
}) })
.catch((err) => { .catch((err) => {
@@ -2830,50 +2849,75 @@ class ModelInfo {
} else { } else {
defaultUrl.dataset.noimage = PREVIEW_NONE_URI; defaultUrl.dataset.noimage = PREVIEW_NONE_URI;
} }
previewSelect.resetModelInfoPreview(); previewSelect.resetModelInfoPreview(webPreviews);
const setPreviewButton = this.elements.setPreviewButton; const setPreviewDiv = $el('div.row.tab-header', {
setPreviewButton.style.display = previewSelect.defaultIsChecked() style: {
? 'none' display: "none"
: 'block'; }
}, [
$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( innerHtml.push(
$el('div', [ $el('div', [
previewSelect.elements.previews,
$el('div.row.tab-header', { style: { "flex-direction": "row" } }, [ $el('div.row.tab-header', { style: { "flex-direction": "row" } }, [
new ComfyButton({ new ComfyButton({
icon: 'arrow-bottom-left-bold-box-outline', icon: 'arrow-bottom-left-bold-box-outline',
tooltip: 'Attempt to load preview image workflow', tooltip: 'Attempt to load preview image workflow',
classList: 'comfyui-button icon-button', classList: 'comfyui-button icon-button',
action: async () => { action: async () => {
const urlString = await loadWorkflow(previewImage.src);
previewSelect.elements.defaultPreviews.children[0].src; },
await loadWorkflow(urlString); }).element,
}, new ComfyButton({
}).element, icon: 'open-in-new',
new ComfyButton({ tooltip: 'Attempt to open model url page in a new tab.',
icon: 'open-in-new', classList: 'comfyui-button icon-button',
tooltip: 'Attempt to open model url page in a new tab.', action: async (e) => {
classList: 'comfyui-button icon-button', const [button, icon, span] = comfyButtonDisambiguate(e.target);
action: async (e) => { button.disabled = true;
const [button, icon, span] = comfyButtonDisambiguate(e.target); let webUrl;
button.disabled = true; if (url !== undefined && url !== "") {
let webUrl; webUrl = url;
if (url !== undefined && url !== "") { }
webUrl = url; else {
} webUrl = await tryGetModelWebUrl(searchPath);
else { }
webUrl = await tryGetModelWebUrl(searchPath); const success = tryOpenUrl(webUrl, searchPath);
} comfyButtonAlert(e.target, success, "mdi-check-bold", "mdi-close-thick");
const success = tryOpenUrl(webUrl, searchPath); button.disabled = false;
comfyButtonAlert(e.target, success, "mdi-check-bold", "mdi-close-thick"); },
button.disabled = false;
},
}).element, }).element,
new ComfyButton({ new ComfyButton({
icon: 'earth-arrow-down', icon: 'earth-arrow-down',
tooltip: 'Try download model info.', tooltip: 'Hash model and try to download model info.',
classList: 'comfyui-button icon-button', classList: 'comfyui-button icon-button',
action: async(e) => { 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); const [button, icon, span] = comfyButtonDisambiguate(e.target);
button.disabled = true; button.disabled = true;
const success = await comfyRequest( const success = await comfyRequest(
@@ -2896,13 +2940,28 @@ class ModelInfo {
button.disabled = false; button.disabled = false;
}, },
}).element, }).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', [ previewImage,
$el('div.row.tab-header-flex-block', [ previewSelect.elements.previews,
previewSelect.elements.radioGroup, setPreviewDiv,
]),
$el('div.row.tab-header-flex-block', [setPreviewButton]),
]),
$el('h2', ['File Info:']), $el('h2', ['File Info:']),
$el( $el(
'div', 'div',