test / javascript /extraNetworks.js
bilegentile's picture
Upload folder using huggingface_hub
c19ca42 verified
const activePromptTextarea = {};
let sortVal = 0;
// helpers
const requestGet = (url, data, handler) => {
const xhr = new XMLHttpRequest();
const args = Object.keys(data).map((k) => `${encodeURIComponent(k)}=${encodeURIComponent(data[k])}`).join('&');
xhr.open('GET', `${url}?${args}`, true);
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
if (xhr.status === 200) handler(JSON.parse(xhr.responseText));
else console.error(`Request: url=${url} status=${xhr.status} err`);
}
};
xhr.send(JSON.stringify(data));
};
const getENActiveTab = () => {
if (gradioApp().getElementById('tab_txt2img').style.display === 'block') return 'txt2img';
if (gradioApp().getElementById('tab_img2img').style.display === 'block') return 'img2img';
if (gradioApp().getElementById('tab_control').style.display === 'block') return 'control';
return '';
};
const getENActivePage = () => {
const tabname = getENActiveTab();
const page = gradioApp().querySelector(`#${tabname}_extra_networks > .tabs > .tab-nav > .selected`);
const pageName = page ? page.innerText : '';
const btnApply = gradioApp().getElementById(`${tabname}_extra_apply`);
if (btnApply) btnApply.style.display = pageName === 'Style' ? 'inline-flex' : 'none';
return pageName;
};
const setENState = (state) => {
if (!state) return;
state.tab = getENActiveTab();
state.page = getENActivePage();
// log('setENState', state);
const el = gradioApp().querySelector(`#${state.tab}_extra_state > label > textarea`);
el.value = JSON.stringify(state);
updateInput(el);
};
// methods
function showCardDetails(event) {
console.log('showCardDetails', event);
const tabname = getENActiveTab();
const btn = gradioApp().getElementById(`${tabname}_extra_details_btn`);
btn.click();
event.stopPropagation();
event.preventDefault();
}
function getCardDetails(...args) {
const el = event?.target?.parentElement?.parentElement;
if (el?.classList?.contains('card')) setENState({ op: 'getCardDetails', item: el.dataset.name });
else setENState({ op: 'getCardDetails', item: null });
return [...args];
}
function readCardTags(el, tags) {
const replaceOutsideBrackets = (input, target, replacement) => input.split(/(<[^>]*>|\{[^}]*\})/g).map((part, i) => {
if (i % 2 === 0) return part.split(target).join(replacement); // Only replace in the parts that are not inside brackets (which are at even indices)
return part;
}).join('');
const clickTag = (e, tag) => {
e.preventDefault();
e.stopPropagation();
const textarea = activePromptTextarea[getENActiveTab()];
let new_prompt = textarea.value;
new_prompt = replaceOutsideBrackets(new_prompt, ` ${tag}`, ''); // try to remove tag
new_prompt = replaceOutsideBrackets(new_prompt, `${tag} `, '');
if (new_prompt === textarea.value) new_prompt += ` ${tag}`; // if not removed, then append it
textarea.value = new_prompt;
updateInput(textarea);
};
if (tags.length === 0) return;
const cardTags = tags.split('|');
if (!cardTags || cardTags.length === 0) return;
const tagsEl = el.getElementsByClassName('tags')[0];
if (!tagsEl?.children || tagsEl.children.length > 0) return;
for (const tag of cardTags) {
const span = document.createElement('span');
span.classList.add('tag');
span.textContent = tag;
span.onclick = (e) => clickTag(e, tag);
tagsEl.appendChild(span);
}
}
function readCardDescription(page, item) {
requestGet('/sd_extra_networks/description', { page, item }, (data) => {
const tabname = getENActiveTab();
const description = gradioApp().querySelector(`#${tabname}_description > label > textarea`);
description.value = data?.description?.trim() || '';
// description.focus();
updateInput(description);
setENState({ op: 'readCardDescription', page, item });
});
}
function getCardsForActivePage() {
const pagename = getENActivePage();
if (!pagename) return [];
const allCards = Array.from(gradioApp().querySelectorAll('.extra-network-cards > .card'));
const cards = allCards.filter((el) => el.dataset.page.toLowerCase().includes(pagename.toLowerCase()));
log('getCardsForActivePage', pagename, cards.length);
return allCards;
}
async function filterExtraNetworksForTab(searchTerm) {
let found = 0;
let items = 0;
const t0 = performance.now();
const pagename = getENActivePage();
if (!pagename) return;
const allPages = Array.from(gradioApp().querySelectorAll('.extra-network-cards'));
const pages = allPages.filter((el) => el.id.toLowerCase().includes(pagename.toLowerCase()));
for (const pg of pages) {
const cards = Array.from(pg.querySelectorAll('.card') || []);
// We will always have as many items as cards
items += cards.length;
// Reset the results to show all cards if the search term is empty
if (searchTerm === '') {
cards.forEach((elem) => {
elem.style.display = '';
});
} else {
// Do not account for case or whitespace
searchTerm = searchTerm.toLowerCase().trim();
// If the searchTerm starts with "r#", then we are using regex search
if (searchTerm.startsWith('r#')) {
searchTerm = searchTerm.substring(2);
// Insensitive regex search based on the searchTerm
// The regex can be invalid -> then it will error out of this function, so the timing log will be missing, instead the error will be logged to console
const re = new RegExp(searchTerm, 'i');
cards.forEach((elem) => {
// Construct the search text, which is the concatenation of all data elements with a prefix to make it unique
// This combined text allows to exclude search terms for example by using negative lookahead
if (re.test(`filename: ${elem.dataset.filename}|name: ${elem.dataset.name}|tags: ${elem.dataset.tags}`)) {
elem.style.display = '';
found += 1;
} else {
elem.style.display = 'none';
}
});
} else {
// If we are not using regex search, we still use an extended syntax to allow for searching for multiple keywords, or also excluding keywords
// Keywords are separated by |, and keywords that should be excluded are prefixed with -
const searchList = searchTerm.split('|').filter((s) => s !== '' && !s.startsWith('-')).map((s) => s.trim());
const excludeList = searchTerm.split('|').filter((s) => s !== '' && s.trim().startsWith('-')).map((s) => s.trim().substring(1).trim());
// In addition, both the searchList, and exclude List can be separated by &, which means that all keywords in the searchList must be present, and none of the excludeList
// So we construct an array of arrays, which we will then use to filter the cards
const searchListAll = searchList.map((s) => s.split('&').map((t) => t.trim()));
const excludeListAll = excludeList.map((s) => s.split('&').map((t) => t.trim()));
cards.forEach((elem) => {
let text = '';
if (elem.dataset.filename) text += `${elem.dataset.filename} `;
if (elem.dataset.name) text += `${elem.dataset.name} `;
if (elem.dataset.tags) text += `${elem.dataset.tags} `;
text = text.toLowerCase().replace('models--', 'diffusers').replaceAll('\\', '/');
if (
// In searchListAll we have a list of lists, in the sublist, every keyword must be present
// In the top level list, at least one sublist must be present
// In excludeListAll we have a list of lists, in the sublist, the keywords may not appear together
// In the top level list, none of the sublists must be present
searchListAll.some((sl) => sl.every((st) => text.includes(st))) && !excludeListAll.some((el) => el.every((et) => text.includes(et)))
) {
elem.style.display = '';
found += 1;
} else {
elem.style.display = 'none';
}
});
}
}
}
const t1 = performance.now();
if (searchTerm !== '') log(`filterExtraNetworks: text=${searchTerm} items=${items} match=${found} time=${Math.round(1000 * (t1 - t0)) / 1000000}`);
else log(`filterExtraNetworks: text=all items=${items} time=${Math.round(1000 * (t1 - t0)) / 1000000}`);
}
function tryToRemoveExtraNetworkFromPrompt(textarea, text) {
const re_extranet = /<([^:]+:[^:]+):[\d\.]+>/;
const re_extranet_g = /\s+<([^:]+:[^:]+):[\d\.]+>/g;
let m = text.match(re_extranet);
let replaced = false;
let newTextareaText;
if (m) {
const partToSearch = m[1];
newTextareaText = textarea.value.replaceAll(re_extranet_g, (found) => {
m = found.match(re_extranet);
if (m[1] === partToSearch) {
replaced = true;
return '';
}
return found;
});
} else {
newTextareaText = textarea.value.replaceAll(new RegExp(text, 'g'), (found) => {
if (found === text) {
replaced = true;
return '';
}
return found;
});
}
if (replaced) {
textarea.value = newTextareaText;
return true;
}
return false;
}
function sortExtraNetworks() {
const sortDesc = ['Name [A-Z]', 'Name [Z-A]', 'Date [Newest]', 'Date [Oldest]', 'Size [Largest]', 'Size [Smallest]'];
const pagename = getENActivePage();
if (!pagename) return 'sort error: unknown page';
const allPages = Array.from(gradioApp().querySelectorAll('.extra-network-cards'));
const pages = allPages.filter((el) => el.id.toLowerCase().includes(pagename.toLowerCase()));
let num = 0;
for (const pg of pages) {
const cards = Array.from(pg.querySelectorAll('.card') || []);
num = cards.length;
if (num === 0) return 'sort: no cards';
cards.sort((a, b) => { // eslint-disable-line no-loop-func
switch (sortVal) {
case 0: return a.dataset.name ? a.dataset.name.localeCompare(b.dataset.name) : 0;
case 1: return b.dataset.name ? b.dataset.name.localeCompare(a.dataset.name) : 0;
case 2: return a.dataset.mtime && !isNaN(a.dataset.mtime) ? parseFloat(b.dataset.mtime) - parseFloat(a.dataset.mtime) : 0;
case 3: return b.dataset.mtime && !isNaN(b.dataset.mtime) ? parseFloat(a.dataset.mtime) - parseFloat(b.dataset.mtime) : 0;
case 4: return a.dataset.size && !isNaN(a.dataset.size) ? parseFloat(b.dataset.size) - parseFloat(a.dataset.size) : 0;
case 5: return b.dataset.size && !isNaN(b.dataset.size) ? parseFloat(a.dataset.size) - parseFloat(b.dataset.size) : 0;
}
return 0;
});
for (const card of cards) pg.appendChild(card);
}
const desc = sortDesc[sortVal];
sortVal = (sortVal + 1) % sortDesc.length;
log('sortExtraNetworks', pagename, num, desc);
return `sort page ${pagename} cards ${num} by ${desc}`;
}
function refreshENInput(tabname) {
log('refreshExtraNetworks', tabname, gradioApp().querySelector(`#${tabname}_extra_networks textarea`)?.value);
gradioApp().querySelector(`#${tabname}_extra_networks textarea`)?.dispatchEvent(new Event('input'));
}
function cardClicked(textToAdd, allowNegativePrompt) {
const tabname = getENActiveTab();
const textarea = allowNegativePrompt ? activePromptTextarea[tabname] : gradioApp().querySelector(`#${tabname}_prompt > label > textarea`);
if (textarea.value.indexOf(textToAdd) !== -1) textarea.value = textarea.value.replace(textToAdd, '');
else textarea.value += textToAdd;
updateInput(textarea);
}
function extraNetworksSearchButton(event) {
const tabname = getENActiveTab();
const searchTextarea = gradioApp().querySelector(`#${tabname}_extra_search textarea`);
const button = event.target;
if (button.classList.contains('search-all')) {
searchTextarea.value = '';
} else {
searchTextarea.value = `${button.textContent.trim()}/`;
}
updateInput(searchTextarea);
}
let desiredStyle = '';
function selectStyle(name) {
desiredStyle = name;
const tabname = getENActiveTab();
const button = gradioApp().querySelector(`#${tabname}_styles_select`);
button.click();
}
function applyStyles(styles) {
let newStyles = [];
if (styles) newStyles = Array.isArray(styles) ? styles : [styles];
const index = newStyles.indexOf(desiredStyle);
if (index > -1) newStyles.splice(index, 1);
else newStyles.push(desiredStyle);
return newStyles.join('|');
}
function quickApplyStyle() {
const tabname = getENActiveTab();
const btnApply = gradioApp().getElementById(`${tabname}_extra_apply`);
if (btnApply) btnApply.click();
}
function quickSaveStyle() {
const tabname = getENActiveTab();
const btnSave = gradioApp().getElementById(`${tabname}_extra_quicksave`);
if (btnSave) btnSave.click();
}
let enDirty = false;
function closeDetailsEN(...args) {
// log('closeDetailsEN');
enDirty = true;
const tabname = getENActiveTab();
const btnClose = gradioApp().getElementById(`${tabname}_extra_details_close`);
if (btnClose) setTimeout(() => btnClose.click(), 100);
const btnRefresh = gradioApp().getElementById(`${tabname}_extra_refresh`);
if (btnRefresh && enDirty) setTimeout(() => btnRefresh.click(), 100);
return [...args];
}
function refeshDetailsEN(args) {
log(`refeshDetailsEN: ${enDirty}`);
const tabname = getENActiveTab();
const btnRefresh = gradioApp().getElementById(`${tabname}_extra_refresh`);
if (btnRefresh && enDirty) setTimeout(() => btnRefresh.click(), 100);
enDirty = false;
return args;
}
// refresh on en show
function refreshENpage() {
if (getCardsForActivePage().length === 0) {
log('refreshENpage');
const tabname = getENActiveTab();
const btnRefresh = gradioApp().getElementById(`${tabname}_extra_refresh`);
if (btnRefresh) btnRefresh.click();
}
}
// init
function setupExtraNetworksForTab(tabname) {
let tabs = gradioApp().querySelector(`#${tabname}_extra_tabs`);
if (tabs) tabs.classList.add('extra-networks');
const en = gradioApp().getElementById(`${tabname}_extra_networks`);
tabs = gradioApp().querySelector(`#${tabname}_extra_tabs > div`);
if (!tabs) return;
// buttons
const btnRefresh = gradioApp().getElementById(`${tabname}_extra_refresh`);
const btnScan = gradioApp().getElementById(`${tabname}_extra_scan`);
const btnSave = gradioApp().getElementById(`${tabname}_extra_save`);
const btnClose = gradioApp().getElementById(`${tabname}_extra_close`);
const btnSort = gradioApp().getElementById(`${tabname}_extra_sort`);
const btnView = gradioApp().getElementById(`${tabname}_extra_view`);
const btnModel = gradioApp().getElementById(`${tabname}_extra_model`);
const btnApply = gradioApp().getElementById(`${tabname}_extra_apply`);
const buttons = document.createElement('span');
buttons.classList.add('buttons');
if (btnRefresh) buttons.appendChild(btnRefresh);
if (btnModel) buttons.appendChild(btnModel);
if (btnApply) buttons.appendChild(btnApply);
if (btnScan) buttons.appendChild(btnScan);
if (btnSave) buttons.appendChild(btnSave);
if (btnSort) buttons.appendChild(btnSort);
if (btnView) buttons.appendChild(btnView);
if (btnClose) buttons.appendChild(btnClose);
btnModel.onclick = () => btnModel.classList.toggle('toolbutton-selected');
tabs.appendChild(buttons);
// details
const detailsImg = gradioApp().getElementById(`${tabname}_extra_details_img`);
const detailsClose = gradioApp().getElementById(`${tabname}_extra_details_close`);
if (detailsImg && detailsClose) {
detailsImg.title = 'Close details';
detailsImg.onclick = () => detailsClose.click();
}
// search and description
const div = document.createElement('div');
div.classList.add('second-line');
tabs.appendChild(div);
const txtSearch = gradioApp().querySelector(`#${tabname}_extra_search`);
const txtSearchValue = gradioApp().querySelector(`#${tabname}_extra_search textarea`);
const txtDescription = gradioApp().getElementById(`${tabname}_description`);
txtSearch.classList.add('search');
txtDescription.classList.add('description');
div.appendChild(txtSearch);
div.appendChild(txtDescription);
let searchTimer = null;
txtSearchValue.addEventListener('input', (evt) => {
if (searchTimer) clearTimeout(searchTimer);
searchTimer = setTimeout(async () => {
await filterExtraNetworksForTab(txtSearchValue.value.toLowerCase());
searchTimer = null;
}, 100);
});
// card hover
let hoverTimer = null;
let previousCard = null;
gradioApp().getElementById(`${tabname}_extra_tabs`).onmouseover = (e) => {
const el = e.target.closest('.card'); // bubble-up to card
if (!el || (el.title === previousCard)) return;
if (!hoverTimer) {
hoverTimer = setTimeout(() => {
readCardDescription(el.dataset.page, el.dataset.name);
readCardTags(el, el.dataset.tags);
previousCard = el.title;
}, 300);
}
el.onmouseout = () => {
clearTimeout(hoverTimer);
hoverTimer = null;
};
};
// en style
if (!en) return;
const intersectionObserver = new IntersectionObserver((entries) => {
for (const el of Array.from(gradioApp().querySelectorAll('.extra-networks-page'))) {
el.style.height = `${window.opts.extra_networks_height}vh`;
el.parentElement.style.width = '-webkit-fill-available';
}
if (entries[0].intersectionRatio > 0) {
refreshENpage();
if (window.opts.extra_networks_card_cover === 'cover') {
en.style.transition = '';
en.style.zIndex = 100;
en.style.top = '13em';
en.style.position = 'absolute';
en.style.right = 'unset';
en.style.width = 'unset';
en.style.height = 'unset';
gradioApp().getElementById(`${tabname}_settings`).parentNode.style.width = 'unset';
} else if (window.opts.extra_networks_card_cover === 'sidebar') {
en.style.zIndex = 100;
en.style.position = 'absolute';
en.style.right = '0';
en.style.top = '13em';
en.style.height = '-webkit-fill-available';
en.style.transition = 'width 0.3s ease';
en.style.width = `${window.opts.extra_networks_sidebar_width}vw`;
gradioApp().getElementById(`${tabname}_settings`).parentNode.style.width = `${100 - 2 - window.opts.extra_networks_sidebar_width}vw`;
} else {
en.style.transition = '';
en.style.zIndex = 0;
en.style.top = 0;
en.style.position = 'relative';
en.style.right = 'unset';
en.style.width = 'unset';
en.style.height = 'unset';
gradioApp().getElementById(`${tabname}_settings`).parentNode.style.width = 'unset';
}
} else {
if (window.opts.extra_networks_card_cover === 'sidebar') en.style.width = 0;
gradioApp().getElementById(`${tabname}_settings`).parentNode.style.width = 'unset';
}
});
intersectionObserver.observe(en); // monitor visibility of
}
async function setupExtraNetworks() {
setupExtraNetworksForTab('txt2img');
setupExtraNetworksForTab('img2img');
setupExtraNetworksForTab('control');
function registerPrompt(tabname, id) {
const textarea = gradioApp().querySelector(`#${id} > label > textarea`);
if (!textarea) return;
if (!activePromptTextarea[tabname]) activePromptTextarea[tabname] = textarea;
textarea.addEventListener('focus', () => { activePromptTextarea[tabname] = textarea; });
}
registerPrompt('txt2img', 'txt2img_prompt');
registerPrompt('txt2img', 'txt2img_neg_prompt');
registerPrompt('img2img', 'img2img_prompt');
registerPrompt('img2img', 'img2img_neg_prompt');
registerPrompt('control', 'control_prompt');
registerPrompt('control', 'control_neg_prompt');
log('initExtraNetworks');
}
onUiLoaded(setupExtraNetworks);