Files
JaredTherriault-ComfyUI-JNodes/web/js/ImageDrawer/ExtraNetworks.js
T
JaredTherriault f0e9347640 Major video update, ConfigSettings improvements, js async cancellation, load optimizations
-Roll UiSettings into SettingsManager
-Account for ConfigSettings that are objects by loading default values then loading config value son top
-Set ConfigSetting values using JSON.stringify
-Separate flyout generation into generic function
-Add video settings flyout in subfolderexplorer contexts
-Add option to control slider range value label fractional digits
-Update default slider range values to reflect 1-100 values rather than 0-1
-Rename ImageDrawerContext description to tooltip
-Update 'flex' alignment to 'normal' in context toolbars
-Automatically cancel async js operations when switching contexts
-blur or unfocus toolbar buttons on click to avoid accidental clearing or refreshing
-Get setting_ModelCardAspectRatio value as number
-Implement method to avoid lazy loading of images
-Lay some groundwork for unimplemented cancellation requests for python tasks
-Separated image list and search variables and functions to a separate script, ImageListAndSearch
-Update extra networks toolbar buttons to have a bespoke class and use specific cursor styles
-Update ImageElements 'complete' to 'bComplete' to differentiate from the img property
-Add lastSeekTime property initialization to img element
-Don't run onload if image element is already bComplete
-Sort and execute search after image is loaded (if requested). This allows search and sort to work with all of the meta data in the image.
-Click videos to pause/play
-Double click videos to toggle fullscreen
-Mute videos based on video options
-Set video volume from video options flyout
-Use scrollwheel on video to seek forward or back (seek direction depends on video options)
-Update imports
2024-04-17 14:58:59 -07:00

479 lines
12 KiB
JavaScript

import { api } from "/scripts/api.js";
import { $el } from "/scripts/ui.js";
import { copyToClipboard, createDarkContainer, getDarkColor } from "../common/Utilities.js"
import { setSearchTextAndExecute } from "./ImageListAndSearch.js";
import { setting_ModelCardAspectRatio } from "../common/SettingsManager.js";
const NoImagePlaceholder = new URL(`../assets/NoImage.png`, import.meta.url);
let cachedLorasObject = undefined;
let cachedEmbeddingsObject = undefined;
/**
* Gets a list of lora names
* @returns An array of script urls to import
*/
export async function getLoras(bForceRefresh = false) {
if (bForceRefresh || !cachedLorasObject) {
const resp = await api.fetchApi('/jnodes_model_items?type=loras');
const asJson = await resp.json();
//console.log("Size of loras info: " + JSON.stringify(asJson).length)
cachedLorasObject = asJson;
return asJson;
}
return cachedLorasObject;
}
/**
* Gets a list of embedding names
* @returns An array of script urls to import
*/
export async function getEmbeddings(bForceRefresh = false) {
if (bForceRefresh || !cachedEmbeddingsObject) {
const resp = await api.fetchApi('/jnodes_model_items?type=embeddings');
const asJson = await resp.json();
//console.log("Size of embeddings info: " + JSON.stringify(asJson).length)
cachedLorasObject = asJson;
return asJson;
}
return cachedLorasObject;
}
function getLoraTextFontSize(emMultiplier) {
const out = `${emMultiplier}em`;
//console.log(out);
return out;
}
export async function createExtraNetworkCard(nameText, familiars, type) {
if (!nameText) {
return;
}
let nameToUse = nameText;
let trainedWords = [];
let tags = [];
let modelId; // The base model ID for civit.ai. Not specific to any version of the model.
let lastViewedImageIndex = 0;
const bIsLora = type == "loras";
const modelElement =
$el("div.extraNetworksCard", {
id: "extra-networks-card",
"data-name": nameToUse,
style: {
textAlign: 'center',
objectFit: 'var(--div-fit, contain)',
width: 'calc(var(--drawer-width) / var(--column-count))',
borderRadius: '4px',
position: "relative",
aspectRatio: setting_ModelCardAspectRatio.value,
},
});
// Load the first image as the object cover image.
// If one does not exist, fall back to placeholder image.
function getHrefForFamiliarImage(index) {
if (familiars?.familiar_images?.length > 0) {
return `/jnodes_view_image?filename=${encodeURIComponent(familiars.familiar_images[index])}&type=${type}`;
}
return NoImagePlaceholder;
}
async function parseFamiliarInfos() {
let infoMap = {};
if (familiars?.familiar_infos?.length > 0) {
for (const familiar_info of familiars.familiar_infos) {
try {
let loadedJson = JSON.parse(familiar_info);
if (loadedJson) {
let jsonKeys = Object.keys(loadedJson);
for (const key of jsonKeys) {
infoMap[key] = loadedJson[key];
}
}
} catch (jsonError) {
console.error(`Error parsing JSON: ${jsonError}, orginal text: ${familiar_info}`);
}
}
}
// Prefer user set friendlyName over data from civit.ai
if (infoMap.friendlyName) {
if (infoMap.friendlyName.trim() != nameToUse.trim()) {
nameToUse = `${infoMap.friendlyName} (${nameToUse})`;
}
} else if (infoMap.model && infoMap.model.name) {
if (infoMap.model.name.trim() != nameToUse.trim()) {
nameToUse = `${infoMap.model.name} (${nameToUse})`;
}
}
// Prefer user set tags over data from civit.ai
if (infoMap.userTags) {
for (const tag of infoMap.userTags) {
tags.push(tag.trim());
}
} else if (infoMap.tags) {
for (const tag of infoMap.tags) {
tags.push(tag.trim());
}
}
// Prefer user set trainedWords over data from civit.ai
if (infoMap.userTrainedWords) {
trainedWords = (`${infoMap.userTrainedWords}`).trim();
if (!trainedWords.endsWith(",")) {
trainedWords += ",";
}
trainedWords = trainedWords.replace("\\(", "(").replace("\\)", ")").replace('\\"', '"');
} else if (infoMap.trainedWords) {
trainedWords = (`${infoMap.trainedWords}`).trim();
if (!trainedWords.endsWith(",")) {
trainedWords += ",";
}
trainedWords = trainedWords.replace("\\(", "(").replace("\\)", ")").replace('\\"', '"');
}
if (infoMap.lastViewedImageIndex) {
lastViewedImageIndex = infoMap.lastViewedImageIndex;
}
modelId = infoMap.modelId || infoMap.id || undefined;
return infoMap;
}
const infoMap = await parseFamiliarInfos();
function createImageSwitchButton(bIsLeft) {
if (familiars.familiar_images.length < 2) {
return;
}
function onClickLeft(e) {
backgroundImage.lastViewedImageIndex -= 1;
if (backgroundImage.lastViewedImageIndex < 0) {
backgroundImage.lastViewedImageIndex = familiars.familiar_images.length - 1;
}
backgroundImage.src = getHrefForFamiliarImage(backgroundImage.lastViewedImageIndex);
updateImageCounterText();
}
function onClickRight(e) {
backgroundImage.lastViewedImageIndex += 1;
if (backgroundImage.lastViewedImageIndex == familiars.familiar_images.length) {
backgroundImage.lastViewedImageIndex = 0;
}
backgroundImage.src = getHrefForFamiliarImage(backgroundImage.lastViewedImageIndex);
updateImageCounterText();
}
return $el('button', {
style: {
position: "absolute",
top: '50%',
left: bIsLeft ? '5%' : '85%',
transformOrigin: 'center',
fontSize: getLoraTextFontSize(1),
},
onclick: bIsLeft ? onClickLeft : onClickRight,
}, [
$el("label", {
textContent: bIsLeft ? "<" : ">",
}),
]);
}
const backgroundImage =
$el("img", {
lastViewedImageIndex: lastViewedImageIndex,
style: {
objectFit: "cover",
width: "100%",
height: "100%",
}
});
backgroundImage.dataSrc = getHrefForFamiliarImage(lastViewedImageIndex);
let imageCounterLabel;
function updateImageCounterText() {
if (imageCounterLabel) {
if (familiars.familiar_images.length < 1) {
return;
}
const CurrentImageIndex =
parseInt(backgroundImage.lastViewedImageIndex);
imageCounterLabel.textContent =
`${CurrentImageIndex + 1}/${familiars.familiar_images.length}`
}
}
function createImageCounterElement() {
imageCounterLabel = $el("label", {
textContent: 'No preview images found',
});
const imageCounterElement = createDarkContainer();
imageCounterElement.style.top = '2%';
imageCounterElement.style.right = '2%';
imageCounterElement.appendChild(imageCounterLabel);
updateImageCounterText();
return imageCounterElement;
}
function createButtonToolbar() {
function createButtons() {
if (!buttonsRow) { return; }
function createButton(foregroundElement, tooltipText, onClickFunction, dragText) {
const buttonElement = $el("button.JNodes-extra-networks-toolbar-button", {
title: tooltipText,
style: {
background: 'none',
border: 'none',
padding: 0,
}
}, [
foregroundElement
]);
if (onClickFunction) {
buttonElement.addEventListener('click', onClickFunction);
}
if (dragText) {
buttonElement.draggable = true;
buttonElement.addEventListener("dragstart", function (event) {
// Set data to be transferred during drag
event.dataTransfer.setData('text/plain', dragText);
});
}
return buttonElement;
}
let copyModelText = bIsLora ? `<lora:${nameText}:1:1>` : `(embedding:${nameText}:1)`;
if (bIsLora && trainedWords.length > 0) {
//Copy all
let copyAllText = copyModelText + " " + trainedWords;
buttonsRow.appendChild(
createButton(
$el("label", {
textContent: "📋",
style: {
cursor: 'pointer',
}
}),
`Copy lora as a1111-style text + trained words (${copyAllText})`,
function (e) {
copyToClipboard(copyAllText)
e.preventDefault();
},
`modelInsertText=${copyAllText}`
)
);
// Copy trained words button
buttonsRow.appendChild(
createButton(
$el("label", {
textContent: "📝",
style: {
cursor: 'pointer',
}
}),
`Copy trained words (${trainedWords})`,
function (e) {
copyToClipboard(trainedWords)
e.preventDefault();
},
`modelInsertText=${trainedWords}`
)
);
}
// Copy model text button
buttonsRow.appendChild(
createButton(
$el("label", {
textContent: "📜",
style: {
cursor: 'pointer',
}
}),
bIsLora ? `Copy lora as a1111-style text (${copyModelText})` : `Copy embedding as comfy-style text (${copyModelText})`,
function (e) {
copyToClipboard(copyModelText)
e.preventDefault();
},
`modelInsertText=${copyModelText}`
)
);
if (bIsLora) {
// Drag node button
buttonsRow.appendChild(createButton(
$el("label", {
textContent: "📓",
style: {
cursor: 'grab',
}
}),
`Drag the current model into the graph to create a node with the name ${familiars.full_name}`,
undefined,
`loraNodeName=${familiars.full_name}`
));
}
// Go to civit.ai link
if (modelId) {
const href = `https://civitai.com/models/${modelId}`;
buttonsRow.appendChild(
createButton(
$el("a", {
target: "_blank",
href,
textContent: "🔗",
style: {
cursor: 'pointer',
}
}),
`View model on civit.ai (${href})`
)
);
}
}
const buttonsRow = $el("div", {
style: {
width: '100%',
display: 'flex',
flexDirection: 'row',
}
});
const buttonToolbarContainerElement = createDarkContainer();
buttonToolbarContainerElement.style.top = '2%';
buttonToolbarContainerElement.style.left = '2%';
buttonToolbarContainerElement.appendChild(buttonsRow);
createButtons();
return buttonToolbarContainerElement;
}
function createNameAndTagContainer() {
function createTagButton(tagName) {
const buttonElement = $el("button", {
title: `Click to search "${tagName}"`,
style: {
background: 'none',
border: 'none',
padding: '0',
}
}, [
$el("label", {
textContent: tagName,
style: {
fontSize: getLoraTextFontSize(1),
color: 'rgb(250,250,250)',
wordBreak: 'keep-all',
}
})
]);
buttonElement.addEventListener('click', () => {
setSearchTextAndExecute(tagName);
});
return buttonElement;
}
const tagButtonContainer = $el("div", {
id: 'tag-container',
style: {
display: 'flex',
flexWrap: 'wrap',
justifyContent: 'center',
gap: '3.5%',
width: '90%',
}
});
const mainContainer = $el("div", {
id: 'darkened-text-bg',
style: {
position: "absolute",
bottom: 0,
left: 0,
width: "100%",
backgroundColor: getDarkColor(),
minHeight: '15%',
maxHeight: '90%',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
}
}, [
$el("label", {
textContent: nameToUse,
style: {
fontSize: getLoraTextFontSize(1.3),
top: '5%',
wordBreak: 'break-all',
}
}),
tagButtonContainer
]);
for (const tag of tags) {
tagButtonContainer.appendChild(createTagButton(tag));
}
return mainContainer;
}
//console.log("nameToUse: " + nameToUse);
modelElement.appendChild(backgroundImage);
const leftImageSwitchButton = createImageSwitchButton(true);
if (leftImageSwitchButton) { modelElement.appendChild(leftImageSwitchButton); }
const rightImageSwitchButton = createImageSwitchButton(false);
if (rightImageSwitchButton) { modelElement.appendChild(rightImageSwitchButton); }
modelElement.appendChild(createImageCounterElement());
modelElement.appendChild(createButtonToolbar());
modelElement.appendChild(createNameAndTagContainer());
modelElement.title = JSON.stringify(infoMap);
// Sorting meta information
modelElement.filename = nameText;
modelElement.friendlyName = nameToUse;
modelElement.file_age = familiars.file_age;
modelElement.searchTerms = `${nameToUse}, ${trainedWords}, ${tags.join(', ')}`;
modelElement.draggable = true; // Made draggable to allow image drag and drop onto canvas / nodes / file explorer
return modelElement;
}