Set model previews from model info view.
This commit is contained in:
20
__init__.py
20
__init__.py
@@ -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)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
Reference in New Issue
Block a user