add:COMOSITION of preset in easy ipadapterApply

This commit is contained in:
yolain
2024-04-05 12:33:18 +08:00
parent 5858fb0606
commit 70ca7cc35f
13 changed files with 934 additions and 11 deletions
+1
View File
@@ -33,6 +33,7 @@
**v1.1.3 (2024/4/4)**
- `easy ipadapterApply` Added **COMPOSITION** preset
- Supported [ResAdapter](https://huggingface.co/jiaxiangc/res-adapter) when load ResAdapter lora
- Added `easy promptLine`
- Added `easy promptReplace`
+1
View File
@@ -37,6 +37,7 @@
**v1.1.3 (2024/4/4)**
- `easy ipadapterApply` 增加 **COMPOSITION** 预置项
- 增加 对[ResAdapter](https://huggingface.co/jiaxiangc/res-adapter) lora模型 的加载支持
- 增加 `easy promptLine`
- 增加 `easy promptReplace`
+60 -3
View File
@@ -1,14 +1,13 @@
import re
import os
import torch
import hashlib
import sys
import json
import folder_paths
from server import PromptServer
from .config import RESOURCES_DIR, FOOOCUS_STYLES_DIR, FOOOCUS_STYLES_SAMPLES
from .easyNodes import easyCache
from .logic import ConvertAnything
from .libs.model import easyModelManager
from .libs.utils import getMetadata
try:
import aiohttp
@@ -129,5 +128,63 @@ async def getModelsThumbnail(request):
loras_full.append(full_path)
return web.json_response(checkpoints_full + loras_full)
@PromptServer.instance.routes.get("/easyuse/metadata/{name}")
async def load_metadata(request):
name = request.match_info["name"]
pos = name.index("/")
type = name[0:pos]
name = name[pos+1:]
file_path = None
if type == "embeddings":
name = name.lower()
files = folder_paths.get_filename_list(type)
for f in files:
lower_f = f.lower()
if lower_f == name:
file_path = folder_paths.get_full_path(type, f)
else:
n = os.path.splitext(f)[0].lower()
if n == name:
file_path = folder_paths.get_full_path(type, f)
if file_path is not None:
break
else:
file_path = folder_paths.get_full_path(type, name)
if not file_path:
return web.Response(status=404)
try:
header = getMetadata(file_path)
header_json = json.loads(header)
meta = header_json["__metadata__"] if "__metadata__" in header_json else None
except:
meta = None
if meta is None:
meta = {}
file_no_ext = os.path.splitext(file_path)[0]
info_file = file_no_ext + ".txt"
if os.path.isfile(info_file):
with open(info_file, "r") as f:
meta["easyuse.notes"] = f.read()
hash_file = file_no_ext + ".sha256"
if os.path.isfile(hash_file):
with open(hash_file, "rt") as f:
meta["easyuse.sha256"] = f.read()
else:
with open(file_path, "rb") as f:
meta["easyuse.sha256"] = hashlib.sha256(f.read()).hexdigest()
with open(hash_file, "wt") as f:
f.write(meta["easyuse.sha256"])
return web.json_response(meta)
NODE_CLASS_MAPPINGS = {}
NODE_DISPLAY_NAME_MAPPINGS = {}
+8
View File
@@ -231,6 +231,14 @@ IPADAPTER_MODELS = {
"sdxl": {
"model_url": "https://huggingface.co/h94/IP-Adapter-FaceID/resolve/main/ip-adapter-faceid-portrait_sdxl.bin",
}
},
"COMPOSITION": {
"sd15": {
"model_url": "https://huggingface.co/ostris/ip-composition-adapter/resolve/main/ip_plus_composition_sd15.safetensors"
},
"sdxl": {
"model_url": "https://huggingface.co/ostris/ip-composition-adapter/resolve/main/ip_plus_composition_sdxl.safetensors"
}
}
}
+7 -1
View File
@@ -2029,7 +2029,8 @@ class ipadapter:
'VIT-G (medium strength)',
'PLUS (high strength)',
'PLUS FACE (portraits)',
'FULL FACE - SD1.5 only (portraits stronger)'
'FULL FACE - SD1.5 only (portraits stronger)',
'COMPOSITION'
]
self.faceid_presets = [
'FACEID',
@@ -2099,6 +2100,11 @@ class ipadapter:
if is_sdxl:
raise Exception("full face model is not supported for SDXL")
pattern = 'full.face.sd15\.(safetensors|bin)$'
elif preset.startswith("composition"):
if is_sdxl:
pattern = 'plus.composition.sdxl\.(safetensors|bin)$'
else:
pattern = 'plus.composition.sd15\.(safetensors|bin)$'
elif preset.startswith("faceid portrait"):
if is_sdxl:
pattern = 'portrait.sdxl\.(safetensors|bin)$'
+22 -2
View File
@@ -121,11 +121,13 @@ def is_linked_styles_selector(prompt, my_unique_id, prompt_type='positive'):
else:
return False
use_mirror = False
def get_local_filepath(url, dirname, local_file_name=None):
"""Get local file path when is already downloaded or download it"""
import os
from urllib.parse import urlparse
from torch.hub import download_url_to_file
global use_mirror
if not os.path.exists(dirname):
os.makedirs(dirname)
if not local_file_name:
@@ -133,13 +135,16 @@ def get_local_filepath(url, dirname, local_file_name=None):
local_file_name = os.path.basename(parsed_url.path)
destination = os.path.join(dirname, local_file_name)
if not os.path.exists(destination):
print(f'downloading {url} to {destination}')
try:
if use_mirror:
url = url.replace('huggingface.co', 'hf-mirror.com')
print(f'downloading {url} to {destination}')
download_url_to_file(url, destination)
except Exception as e:
args = e.args[0] if e.args is not None and len(e.args) > 0 else None
if isinstance(args, TimeoutError):
print(f'无法从 {url} 下载,正在尝试使用镜像地址...')
use_mirror = True
url = url.replace('huggingface.co', 'hf-mirror.com')
try:
download_url_to_file(url, destination)
@@ -177,4 +182,19 @@ def easySave(images, filename_prefix, output_type, prompt=None, extra_pnginfo=No
return results['ui']['images']
else:
results = SaveImage().save_images(images, filename_prefix, prompt, extra_pnginfo)
return results['ui']['images']
return results['ui']['images']
def getMetadata(filepath):
with open(filepath, "rb") as file:
# https://github.com/huggingface/safetensors#format
# 8 bytes: N, an unsigned little-endian 64-bit integer, containing the size of the header
header_size = int.from_bytes(file.read(8), "little", signed=False)
if header_size <= 0:
raise BufferError("Invalid header size")
header = file.read(header_size)
if header_size <= 0:
raise BufferError("Invalid header")
return header
+2 -1
View File
@@ -2,4 +2,5 @@
@import "dropdown.css";
@import "selector.css";
@import "groupmap.css";
@import "contextmenu.css";
@import "contextmenu.css";
@import "modelinfo.css";
+164
View File
@@ -0,0 +1,164 @@
.easyuse-model-info {
color: white;
max-width: 90vw;
font-family: var(--font-family);
}
.easyuse-model-content {
display: flex;
flex-direction: column;
overflow: hidden;
}
.easyuse-model-header{
margin:0 0 15px 0;
}
.easyuse-model-header-remark{
display: flex;
align-items: center;
margin-top:5px;
}
.easyuse-model-info h2 {
text-align: left;
margin:0;
}
.easyuse-model-info h5 {
text-align: left;
margin:0 15px 0 0px;
font-weight: 400;
color:var(--descrip-text);
}
.easyuse-model-info p {
margin: 5px 0;
}
.easyuse-model-info a {
color: var(--theme-color-light);
}
.easyuse-model-info a:hover {
text-decoration: underline;
}
.easyuse-model-tags-list {
display: flex;
flex-wrap: wrap;
list-style: none;
gap: 10px;
max-height: 200px;
overflow: auto;
margin: 10px 0;
padding: 0;
}
.easyuse-model-tag {
background-color: rgb(128, 213, 247);
color: #000;
display: flex;
align-items: center;
gap: 5px;
border-radius: 5px;
padding: 2px 5px;
cursor: pointer;
}
.easyuse-model-tag--selected span::before {
content: "✅";
position: absolute;
background-color: var(--theme-color-light);
left: 0;
top: 0;
right: 0;
bottom: 0;
text-align: center;
}
.easyuse-model-tag:hover {
outline: 2px solid var(--theme-color-light);
}
.easyuse-model-tag p {
margin: 0;
}
.easyuse-model-tag span {
text-align: center;
border-radius: 5px;
background-color: var(--theme-color-light);
padding: 2px;
position: relative;
min-width: 20px;
overflow: hidden;
}
.easyuse-model-metadata .comfy-modal-content {
max-width: 100%;
}
.easyuse-model-metadata label {
margin-right: 1ch;
color: #ccc;
}
.easyuse-model-metadata span {
color: var(--theme-color-light);
}
.easyuse-preview {
max-width:660px;
margin-right: 15px;
position: relative;
}
.easyuse-preview-group{
position: relative;
overflow: hidden;
border-radius:.5rem;
width: 660px;
}
.easyuse-preview-list{
display: flex;
flex-wrap: nowrap;
width: 100%;
transition: all .5s ease-in-out;
}
.easyuse-preview-list.no-transition{
transition: none;
}
.easyuse-preview-slide{
display: flex;
flex-basis: 50%;
flex-grow: 0;
flex-shrink: 0;
position: relative;
justify-content: center;
align-items: center;
}
.easyuse-preview-slide img {
border-radius: 14px;
object-position: center center;
max-width: 100%;
max-height:700px;
border-style: none;
vertical-align: middle;
}
.easyuse-preview button {
position: absolute;
z-index:10;
top: 50%;
display: flex;
align-items: center;
justify-content: center;
width:30px;
height:30px;
border-radius:15px;
border:1px solid rgba(66, 63, 78, .3);
background-color: rgba(66, 63, 78, .5);
color:hsla(0, 0%, 100%, .8);
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke;
transition-timing-function: cubic-bezier(.4,0,.2,1);
transition-duration: .15s;
transform: translateY(-50%);
}
.easyuse-preview button.left{
left:10px;
}
.easyuse-preview button.right{
right:10px;
}
.easyuse-model-notes {
background-color: rgba(0, 0, 0, 0.25);
padding: 5px;
margin-top: 5px;
}
.easyuse-model-notes:empty {
display: none;
}
+11 -1
View File
@@ -2,6 +2,13 @@ import {getLocale} from './utils.js'
const locale = getLocale()
const zhCN = {
// ExtraMenu
"💎 View Checkpoint Info...": "💎 查看 Checkpoint 信息...",
"💎 View Lora Info...": "💎 查看 Lora 信息...",
"🔃 Reload Node": "🔃 刷新节点",
// ModelInfo
"Updated At:": "最近更新:",
"Created At:": "首次发布:",
// GroupMap
"Groups Map (EasyUse)": "管理组 (EasyUse)",
"Always": "启用中",
@@ -13,8 +20,11 @@ const zhCN = {
"Enable ALT+1~9 to paste nodes from nodes template (ComfyUI-Easy-Use)": "启用ALT1~9从节点模板粘贴到工作流 (ComfyUI-Easy-Use)",
"Enable process bar in queue button (ComfyUI-Easy-Use)": "启用提示词队列进度显示条 (ComfyUI-Easy-Use)",
"Enable ContextMenu Auto Nest Subdirectories (ComfyUI-Easy-Use)": "启用上下文菜单自动嵌套子目录 (ComfyUI-Easy-Use)"
}
export const $t = (key) => {
return locale === 'zh-CN' ? zhCN[key] : key
const cn = zhCN[key]
return locale === 'zh-CN' && cn ? cn : key
}
+615
View File
@@ -0,0 +1,615 @@
import { $el, ComfyDialog } from "/scripts/ui.js";
import { api } from "/scripts/api.js";
import {formatTime} from './utils.js';
import {$t} from "./i18n.js";
class MetadataDialog extends ComfyDialog {
constructor() {
super();
this.element.classList.add("easyuse-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("easyuse-model-info");
}
get customNotes() {
return this.metadata["easyuse.notes"];
}
set customNotes(v) {
this.metadata["easyuse.notes"] = v;
}
get hash() {
return this.metadata["easyuse.sha256"];
}
async show(type, value) {
this.type = type;
const req = api.fetchApi("/easyuse/metadata/" + encodeURIComponent(`${type}/${value}`));
this.info = $el("div", { style: { flex: "auto" } });
// this.img = $el("img", { style: { display: "none" } });
this.imgCurrent = 0
this.imgList = $el("div.easyuse-preview-list",{
style: { display: "none" }
})
this.imgWrapper = $el("div.easyuse-preview", [
$el("div.easyuse-preview-group",[
this.imgList
]),
]);
this.main = $el("main", { style: { display: "flex" } }, [this.imgWrapper, this.info]);
this.content = $el("div.easyuse-model-content", [
$el("div.easyuse-model-header",[$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.easyuse-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) => {
// 变更标题信息
let header = this.element.querySelector('.easyuse-model-header')
if(header){
header.replaceChildren(
$el("h2", { textContent: this.name }),
$el("div.easyuse-model-header-remark",[
$el("h5", { textContent: $t("Updated At:") + formatTime(new Date(info.updatedAt),'yyyy/MM/dd')}),
$el("h5", { textContent: $t("Created At:") + formatTime(new Date(info.updatedAt),'yyyy/MM/dd')}),
])
)
}
// 替换内容
content.replaceChildren(
$el("a", {
href: "https://civitai.com/models/" + info.modelId,
textContent: "View " + info.model.name,
target: "_blank",
})
);
if (info.images?.length) {
this.imgCurrent = 0
info.images.map(cate=>
cate.url &&
this.imgList.appendChild(
$el('div.easyuse-preview-slide',[
$el('img',{src:(cate.url)})
])
)
)
let _this = this
this.imgDistance = (-660 * this.imgCurrent).toString()
this.imgList.style.display = ''
this.imgList.style.transform = 'translate3d(' + this.imgDistance +'px, 0px, 0px)'
this.slides = this.imgList.querySelectorAll('.easyuse-preview-slide')
// 添加按钮
this.slideLeftButton = $el("button.left",{
parent: this.imgWrapper,
innerHTML:`<svg viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg" width="16" height="16" style="transform: rotate(90deg);"><path d="M3.13523 6.15803C3.3241 5.95657 3.64052 5.94637 3.84197 6.13523L7.5 9.56464L11.158 6.13523C11.3595 5.94637 11.6759 5.95657 11.8648 6.15803C12.0536 6.35949 12.0434 6.67591 11.842 6.86477L7.84197 10.6148C7.64964 10.7951 7.35036 10.7951 7.15803 10.6148L3.15803 6.86477C2.95657 6.67591 2.94637 6.35949 3.13523 6.15803Z" fill="currentColor" fill-rule="evenodd" clip-rule="evenodd"></path></svg>`,
onclick: ()=>{
_this.imgList.classList.remove("no-transition")
if(_this.imgCurrent == 0){
_this.imgCurrent = (info.images.length/2)-1
this.slides[this.slides.length-1].style.transform = 'translate3d(' + (-660 * (this.imgCurrent+1)).toString()+'px, 0px, 0px)'
this.slides[this.slides.length-2].style.transform = 'translate3d(' + (-660 * (this.imgCurrent+1)).toString()+'px, 0px, 0px)'
_this.imgList.style.transform = 'translate3d(660px, 0px, 0px)'
setTimeout(_=>{
this.slides[this.slides.length-1].style.transform = 'translate3d(0px, 0px, 0px)'
this.slides[this.slides.length-2].style.transform = 'translate3d(0px, 0px, 0px)'
_this.imgDistance = (-660 * this.imgCurrent).toString()
_this.imgList.style.transform = 'translate3d(' + _this.imgDistance +'px, 0px, 0px)'
_this.imgList.classList.add("no-transition")
},500)
}
else {
_this.imgCurrent = _this.imgCurrent-1
_this.imgDistance = (-660 * this.imgCurrent).toString()
_this.imgList.style.transform = 'translate3d(' + _this.imgDistance +'px, 0px, 0px)'
}
}
})
this.slideRightButton = $el("button.right",{
parent: this.imgWrapper,
innerHTML:`<svg viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg" width="16" height="16" style="transform: rotate(-90deg);"><path d="M3.13523 6.15803C3.3241 5.95657 3.64052 5.94637 3.84197 6.13523L7.5 9.56464L11.158 6.13523C11.3595 5.94637 11.6759 5.95657 11.8648 6.15803C12.0536 6.35949 12.0434 6.67591 11.842 6.86477L7.84197 10.6148C7.64964 10.7951 7.35036 10.7951 7.15803 10.6148L3.15803 6.86477C2.95657 6.67591 2.94637 6.35949 3.13523 6.15803Z" fill="currentColor" fill-rule="evenodd" clip-rule="evenodd"></path></svg>`,
onclick: ()=>{
_this.imgList.classList.remove("no-transition")
if( _this.imgCurrent >= (info.images.length/2)-1){
_this.imgCurrent = 0
const max = info.images.length/2
this.slides[0].style.transform = 'translate3d(' + (660 * max).toString()+'px, 0px, 0px)'
this.slides[1].style.transform = 'translate3d(' + (660 * max).toString()+'px, 0px, 0px)'
_this.imgList.style.transform = 'translate3d(' + (-660 * max).toString()+'px, 0px, 0px)'
setTimeout(_=>{
this.slides[0].style.transform = 'translate3d(0px, 0px, 0px)'
this.slides[1].style.transform = 'translate3d(0px, 0px, 0px)'
_this.imgDistance = (-660 * this.imgCurrent).toString()
_this.imgList.style.transform = 'translate3d(' + _this.imgDistance +'px, 0px, 0px)'
_this.imgList.classList.add("no-transition")
},500)
}
else {
_this.imgCurrent = _this.imgCurrent+1
_this.imgDistance = (-660 * this.imgCurrent).toString()
_this.imgList.style.transform = 'translate3d(' + _this.imgDistance +'px, 0px, 0px)'
}
}
})
// 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;
});
}
}
export class CheckpointInfoDialog extends ModelInfoDialog {
async addInfo() {
super.addInfo();
const info = await this.addCivitaiInfo();
if (info) {
this.addInfoEntry("Base Model", info.baseModel || "⚠️ Unknown");
$el("div", {
parent: this.content,
innerHTML: info.description,
style: {
maxHeight: "250px",
overflow: "auto",
},
});
}
}
}
const MAX_TAGS = 500
export class LoraInfoDialog extends ModelInfoDialog {
getTagFrequency() {
if (!this.metadata.ss_tag_frequency) return [];
const datasets = JSON.parse(this.metadata.ss_tag_frequency);
const tags = {};
for (const setName in datasets) {
const set = datasets[setName];
for (const t in set) {
if (t in tags) {
tags[t] += set[t];
} else {
tags[t] = set[t];
}
}
}
return Object.entries(tags).sort((a, b) => b[1] - a[1]);
}
getResolutions() {
let res = [];
if (this.metadata.ss_bucket_info) {
const parsed = JSON.parse(this.metadata.ss_bucket_info);
if (parsed?.buckets) {
for (const { resolution, count } of Object.values(parsed.buckets)) {
res.push([count, `${resolution.join("x")} * ${count}`]);
}
}
}
res = res.sort((a, b) => b[0] - a[0]).map((a) => a[1]);
let r = this.metadata.ss_resolution;
if (r) {
const s = r.split(",");
const w = s[0].replace("(", "");
const h = s[1].replace(")", "");
res.push(`${w.trim()}x${h.trim()} (Base res)`);
} else if ((r = this.metadata["modelspec.resolution"])) {
res.push(r + " (Base res");
}
if (!res.length) {
res.push("⚠️ Unknown");
}
return res;
}
getTagList(tags) {
return tags.map((t) =>
$el(
"li.easyuse-model-tag",
{
dataset: {
tag: t[0],
},
$: (el) => {
el.onclick = () => {
el.classList.toggle("easyuse-model-tag--selected");
};
},
},
[
$el("p", {
textContent: t[0],
}),
$el("span", {
textContent: t[1],
}),
]
)
);
}
addTags() {
let tags = this.getTagFrequency();
let hasMore;
if (tags?.length) {
const c = tags.length;
let list;
if (c > MAX_TAGS) {
tags = tags.slice(0, MAX_TAGS);
hasMore = $el("p", [
$el("span", { textContent: `⚠️ Only showing first ${MAX_TAGS} tags ` }),
$el("a", {
href: "#",
textContent: `Show all ${c}`,
onclick: () => {
list.replaceChildren(...this.getTagList(this.getTagFrequency()));
hasMore.remove();
},
}),
]);
}
list = $el("ol.easyuse-model-tags-list", this.getTagList(tags));
this.tags = $el("div", [list]);
} else {
this.tags = $el("p", { textContent: "⚠️ No tag frequency metadata found" });
}
this.content.append(this.tags);
if (hasMore) {
this.content.append(hasMore);
}
}
async addInfo() {
this.addInfoEntry("Name", this.metadata.ss_output_name || "⚠️ Unknown");
this.addInfoEntry("Base Model", this.metadata.ss_sd_model_name || "⚠️ Unknown");
this.addInfoEntry("Clip Skip", this.metadata.ss_clip_skip || "⚠️ Unknown");
this.addInfoEntry(
"Resolution",
$el(
"select",
this.getResolutions().map((r) => $el("option", { textContent: r }))
)
);
super.addInfo();
const p = this.addCivitaiInfo();
this.addTags();
const info = await p;
if (info) {
$el(
"p",
{
parent: this.content,
textContent: "Trained Words: ",
},
[
$el("pre", {
textContent: info.trainedWords.join(", "),
style: {
whiteSpace: "pre-wrap",
margin: "10px 0",
background: "#222",
padding: "5px",
borderRadius: "5px",
maxHeight: "250px",
overflow: "auto",
},
}),
]
);
$el("div", {
parent: this.content,
innerHTML: info.description,
style: {
maxHeight: "250px",
overflow: "auto",
},
});
}
}
createButtons() {
const btns = super.createButtons();
function copyTags(e, tags) {
const textarea = $el("textarea", {
parent: document.body,
style: {
position: "fixed",
},
textContent: tags.map((el) => el.dataset.tag).join(", "),
});
textarea.select();
try {
document.execCommand("copy");
if (!e.target.dataset.text) {
e.target.dataset.text = e.target.textContent;
}
e.target.textContent = "Copied " + tags.length + " tags";
setTimeout(() => {
e.target.textContent = e.target.dataset.text;
}, 1000);
} catch (ex) {
prompt("Copy to clipboard: Ctrl+C, Enter", text);
} finally {
document.body.removeChild(textarea);
}
}
btns.unshift(
$el("button", {
type: "button",
textContent: "Copy Selected",
onclick: (e) => {
copyTags(e, [...this.tags.querySelectorAll(".easyuse-model-tag--selected")]);
},
}),
$el("button", {
type: "button",
textContent: "Copy All",
onclick: (e) => {
copyTags(e, [...this.tags.querySelectorAll(".easyuse-model-tag")]);
},
})
);
return btns;
}
}
+19 -1
View File
@@ -45,4 +45,22 @@ export function spliceExtension(fileName){
}
export function getExtension(fileName){
return fileName.substring(fileName.lastIndexOf('.') + 1)
}
}
export function formatTime(time, format) {
time = typeof (time) === "number" ? time : (time instanceof Date ? time.getTime() : parseInt(time));
if (isNaN(time)) return null;
if (typeof (format) !== 'string' || !format) format = 'yyyy-MM-dd hh:mm:ss';
let _time = new Date(time);
time = _time.toString().split(/[\s\:]/g).slice(0, -2);
time[1] = ['01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12'][_time.getMonth()];
let _mapping = {
MM: 1,
dd: 2,
yyyy: 3,
hh: 4,
mm: 5,
ss: 6
};
return format.replace(/([Mmdhs]|y{2})\1/g, (key) => time[_mapping[key]]);
}
+2 -1
View File
@@ -18,6 +18,7 @@ app.registerExtension({
emptyImg.src = "data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs=";
options.push(null,
// Groups Map
{
content: '📜 '+ $t('Groups Map (EasyUse)'),
callback: async() => {
@@ -250,7 +251,7 @@ app.registerExtension({
}
}
},
}
);
return options;
};
+22 -1
View File
@@ -1,4 +1,6 @@
import {app} from "/scripts/app.js";
import {$t} from '../common/i18n.js'
import {CheckpointInfoDialog, LoraInfoDialog} from "../common/model.js";
const loaders = ['easy fullLoader', 'easy a1111Loader', 'easy comfyLoader']
const preSampling = ['easy preSampling', 'easy preSamplingAdvanced', 'easy preSamplingDynamicCFG', 'easy preSamplingNoiseIn', 'easy preSamplingLayerDiffusion', 'easy fullkSampler']
@@ -264,6 +266,25 @@ const addMenu = (content, type, nodes_include, nodeType, has_submenu=true) => {
has_submenu: has_submenu,
callback: (value, options, e, menu, node) => showSwapMenu(value, options, e, menu, node, type, nodes_include)
})
// if(type == 'loaders'){
// options.unshift({
// content: $t("💎 View Lora Info..."),
// callback: (value, options, e, menu, node) => {
// const widget = node.widgets.find(cate=> cate.name == 'lora_name')
// let name = widget.value;
// if (!name || name == 'None') return
// new LoraInfoDialog(name).show('loras', name);
// }
// })
// options.unshift({
// content: $t("💎 View Checkpoint Info..."),
// callback: (value, options, e, menu, node) => {
// let name = node.widgets[0].value;
// if (!name || name == 'None') return
// new CheckpointInfoDialog(name).show('checkpoints', name);
// }
// })
// }
})
}
const showSwapMenu = (value, options, e, menu, node, type, nodes_include) => {
@@ -482,7 +503,7 @@ app.registerExtension({
// 刷新节点
addMenuHandler(nodeType, function (_, options) {
options.unshift({
content: "🔃 Reload Node",
content: $t("🔃 Reload Node"),
callback: (value, options, e, menu, node) => {
let graphcanvas = LGraphCanvas.active_canvas;
if (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1) {