Files
efficiency-nodes-comfyui/js/node_options/common/modelInfoDialog.js
2023-10-20 15:49:32 -05:00

304 lines
7.2 KiB
JavaScript
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { $el, ComfyDialog } from "../../../../scripts/ui.js";
import { api } from "../../../../scripts/api.js";
import { addStylesheet } from "./utils.js";
addStylesheet(import.meta.url);
class MetadataDialog extends ComfyDialog {
constructor() {
super();
this.element.classList.add("pysssss-model-metadata");
}
show(metadata) {
super.show(
$el(
"div",
Object.keys(metadata).map((k) =>
$el("div", [$el("label", { textContent: k }), $el("span", { textContent: metadata[k] })])
)
)
);
}
}
export class ModelInfoDialog extends ComfyDialog {
constructor(name) {
super();
this.name = name;
this.element.classList.add("pysssss-model-info");
}
get customNotes() {
return this.metadata["pysssss.notes"];
}
set customNotes(v) {
this.metadata["pysssss.notes"] = v;
}
get hash() {
return this.metadata["pysssss.sha256"];
}
async show(type, value) {
this.type = type;
const req = api.fetchApi("/pysssss/metadata/" + encodeURIComponent(`${type}/${value}`));
this.info = $el("div", { style: { flex: "auto" } });
this.img = $el("img", { style: { display: "none" } });
this.imgWrapper = $el("div.pysssss-preview", [this.img]);
this.main = $el("main", { style: { display: "flex" } }, [this.info, this.imgWrapper]);
this.content = $el("div.pysssss-model-content", [$el("h2", { textContent: this.name }), this.main]);
const loading = $el("div", { textContent: " Loading...", parent: this.content });
super.show(this.content);
this.metadata = await (await req).json();
this.viewMetadata.style.cursor = this.viewMetadata.style.opacity = "";
this.viewMetadata.removeAttribute("disabled");
loading.remove();
this.addInfo();
}
createButtons() {
const btns = super.createButtons();
this.viewMetadata = $el("button", {
type: "button",
textContent: "View raw metadata",
disabled: "disabled",
style: {
opacity: 0.5,
cursor: "not-allowed",
},
onclick: (e) => {
if (this.metadata) {
new MetadataDialog().show(this.metadata);
}
},
});
btns.unshift(this.viewMetadata);
return btns;
}
getNoteInfo() {
function parseNote() {
if (!this.customNotes) return [];
let notes = [];
// Extract links from notes
const r = new RegExp("(\\bhttps?:\\/\\/[^\\s]+)", "g");
let end = 0;
let m;
do {
m = r.exec(this.customNotes);
let pos;
let fin = 0;
if (m) {
pos = m.index;
fin = m.index + m[0].length;
} else {
pos = this.customNotes.length;
}
let pre = this.customNotes.substring(end, pos);
if (pre) {
pre = pre.replaceAll("\n", "<br>");
notes.push(
$el("span", {
innerHTML: pre,
})
);
}
if (m) {
notes.push(
$el("a", {
href: m[0],
textContent: m[0],
target: "_blank",
})
);
}
end = fin;
} while (m);
return notes;
}
let textarea;
let notesContainer;
const editText = "✏️ Edit";
const edit = $el("a", {
textContent: editText,
href: "#",
style: {
float: "right",
color: "greenyellow",
textDecoration: "none",
},
onclick: async (e) => {
e.preventDefault();
if (textarea) {
this.customNotes = textarea.value;
const resp = await api.fetchApi(
"/pysssss/metadata/notes/" + encodeURIComponent(`${this.type}/${this.name}`),
{
method: "POST",
body: this.customNotes,
}
);
if (resp.status !== 200) {
console.error(resp);
alert(`Error saving notes (${req.status}) ${req.statusText}`);
return;
}
e.target.textContent = editText;
textarea.remove();
textarea = null;
notesContainer.replaceChildren(...parseNote.call(this));
} else {
e.target.textContent = "💾 Save";
textarea = $el("textarea", {
style: {
width: "100%",
minWidth: "200px",
minHeight: "50px",
},
textContent: this.customNotes,
});
e.target.after(textarea);
notesContainer.replaceChildren();
textarea.style.height = Math.min(textarea.scrollHeight, 300) + "px";
}
},
});
notesContainer = $el("div.pysssss-model-notes", parseNote.call(this));
return $el(
"div",
{
style: { display: "contents" },
},
[edit, notesContainer]
);
}
addInfo() {
this.addInfoEntry("Notes", this.getNoteInfo());
}
addInfoEntry(name, value) {
return $el(
"p",
{
parent: this.info,
},
[
typeof name === "string" ? $el("label", { textContent: name + ": " }) : name,
typeof value === "string" ? $el("span", { textContent: value }) : value,
]
);
}
async getCivitaiDetails() {
const req = await fetch("https://civitai.com/api/v1/model-versions/by-hash/" + this.hash);
if (req.status === 200) {
return await req.json();
} else if (req.status === 404) {
throw new Error("Model not found");
} else {
throw new Error(`Error loading info (${req.status}) ${req.statusText}`);
}
}
addCivitaiInfo() {
const promise = this.getCivitaiDetails();
const content = $el("span", { textContent: " Loading..." });
this.addInfoEntry(
$el("label", [
$el("img", {
style: {
width: "18px",
position: "relative",
top: "3px",
margin: "0 5px 0 0",
},
src: "https://civitai.com/favicon.ico",
}),
$el("span", { textContent: "Civitai: " }),
]),
content
);
return promise
.then((info) => {
content.replaceChildren(
$el("a", {
href: "https://civitai.com/models/" + info.modelId,
textContent: "View " + info.model.name,
target: "_blank",
})
);
if (info.images?.length) {
this.img.src = info.images[0].url;
this.img.style.display = "";
this.imgSave = $el("button", {
textContent: "Use as preview",
parent: this.imgWrapper,
onclick: async () => {
// Convert the preview to a blob
const blob = await (await fetch(this.img.src)).blob();
// Store it in temp
const name = "temp_preview." + new URL(this.img.src).pathname.split(".")[1];
const body = new FormData();
body.append("image", new File([blob], name));
body.append("overwrite", "true");
body.append("type", "temp");
const resp = await api.fetchApi("/upload/image", {
method: "POST",
body,
});
if (resp.status !== 200) {
console.error(resp);
alert(`Error saving preview (${req.status}) ${req.statusText}`);
return;
}
// Use as preview
await api.fetchApi("/pysssss/save/" + encodeURIComponent(`${this.type}/${this.name}`), {
method: "POST",
body: JSON.stringify({
filename: name,
type: "temp",
}),
headers: {
"content-type": "application/json",
},
});
app.refreshComboInNodes();
},
});
}
return info;
})
.catch((err) => {
content.textContent = "⚠️ " + err.message;
});
}
}