diff --git a/CHANGELOG.md b/CHANGELOG.md index c18ddceab..f6198cea4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ - add support for torch **tunable ops**, this can speed up operations by up to *10-30%* on some platforms *set in settings -> backend settings -> torch* and *paths -> tunable ops cache* - enhanced error logging +- **Other**: + - **Networks**: imporove search/filter and add visual indicators for types - **Refactor**: - unified trace handler with configurable tracebacks - **Fixes**: diff --git a/javascript/extraNetworks.js b/javascript/extraNetworks.js index 3f27cbba2..516f346eb 100644 --- a/javascript/extraNetworks.js +++ b/javascript/extraNetworks.js @@ -106,8 +106,9 @@ function getCardsForActivePage() { } async function filterExtraNetworksForTab(searchTerm) { - let found = 0; let items = 0; + let found = 0; + searchTerm = searchTerm.toLowerCase().trim(); const t0 = performance.now(); const pagename = getENActivePage(); if (!pagename) return; @@ -115,73 +116,47 @@ async function filterExtraNetworksForTab(searchTerm) { 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 = ''; - }); + if (searchTerm === '' || searchTerm === 'all/') { + cards.forEach((elem) => elem.style.display = ''); + } else if (searchTerm === 'reference/') { + cards.forEach((elem) => elem.style.display = elem.dataset.name + .toLowerCase() + .includes('reference/') ? '' : 'none'); + } else if (searchTerm === 'local/') { + cards.forEach((elem) => elem.style.display = elem.dataset.name + .toLowerCase() + .includes('reference/') ? 'none' : ''); + } else if (searchTerm === 'diffusers/') { + cards.forEach((elem) => elem.style.display = elem.dataset.name + .toLowerCase().replace('models--', 'diffusers').replaceAll('\\', '/') + .includes('diffusers/') ? '' : 'none'); + } else if (searchTerm.startsWith('r#')) { + searchTerm = searchTerm.substring(2); + const re = new RegExp(searchTerm, 'i'); + cards.forEach((elem) => elem.style.display = re.test(`filename: ${elem.dataset.filename}|name: ${elem.dataset.name}|tags: ${elem.dataset.tags}`) ? '' : 'none'); } 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 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()); + 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 (searchListAll.some((sl) => sl.every((st) => text.includes(st))) && !excludeListAll.some((el) => el.every((et) => text.includes(et)))) { + elem.style.display = ''; + } else { + elem.style.display = 'none'; + } + }); } + found += cards.filter((elem) => elem.style.display === '').length; } 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}`); + log(`filterExtraNetworks: text="${searchTerm}" items=${items} match=${found} time=${Math.round(1000 * (t1 - t0)) / 1000000}`); } function tryToRemoveExtraNetworkFromPrompt(textarea, text) { @@ -267,8 +242,7 @@ 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()}/`; + searchTextarea.value = `${button.textContent.trim()}/`; updateInput(searchTextarea); } diff --git a/modules/shared.py b/modules/shared.py index faca98a91..a7833ac2f 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -937,7 +937,7 @@ options_templates.update(options_section(('extra_networks', "Networks"), { "lora_quant": OptionInfo("NF4","LoRA precision when quantized", gr.Radio, {"choices": ["NF4", "FP4"]}), "extra_networks_styles_sep": OptionInfo("

Styles

", "", gr.HTML), - "extra_networks_styles": OptionInfo(True, "Show built-in styles"), + "extra_networks_styles": OptionInfo(True, "Show reference styles"), "extra_networks_embed_sep": OptionInfo("

Embeddings

", "", gr.HTML), "diffusers_enable_embed": OptionInfo(True, "Enable embeddings support", gr.Checkbox, {"visible": native}), diff --git a/modules/styles.py b/modules/styles.py index 1517236be..de395c705 100644 --- a/modules/styles.py +++ b/modules/styles.py @@ -240,7 +240,7 @@ class StyleDatabase: self.styles = dict(sorted(self.styles.items(), key=lambda style: style[1].filename)) if self.built_in: fn = os.path.join('html', 'art-styles.json') - future_items[executor.submit(self.load_style, fn, 'built-in')] = fn + future_items[executor.submit(self.load_style, fn, 'Reference')] = fn for future in concurrent.futures.as_completed(future_items): future.result() diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index c9d860ce9..7f4e37d08 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -238,19 +238,31 @@ class ExtraNetworksPage: subdir = subdir[1:] if not subdir: continue - # if not self.is_empty(tgt): subdirs[subdir] = 1 debug(f"Networks: page='{self.name}' subfolders={list(subdirs)}") subdirs = OrderedDict(sorted(subdirs.items())) if self.name == 'model' and shared.opts.extra_network_reference_enable: + subdirs['Local'] = 1 subdirs['Reference'] = 1 subdirs[os.path.basename(shared.opts.diffusers_dir)] = 1 - subdirs.move_to_end(os.path.basename(shared.opts.diffusers_dir)) - subdirs.move_to_end('Reference') if self.name == 'style' and shared.opts.extra_networks_styles: - subdirs['built-in'] = 1 - subdirs_html = "
" - subdirs_html += "".join([f"
" for subdir in subdirs if subdir != '']) + subdirs['Local'] = 1 + subdirs['Reference'] = 1 + subdirs['All'] = 1 + if 'All' in subdirs: + subdirs.move_to_end('All', last=False) + if 'Local' in subdirs: + subdirs.move_to_end('Local', last=True) + if os.path.basename(shared.opts.diffusers_dir) in subdirs: + subdirs.move_to_end(os.path.basename(shared.opts.diffusers_dir), last=True) + if 'Reference' in subdirs: + subdirs.move_to_end('Reference', last=True) + subdirs_html = '' + for subdir in subdirs: + if len(subdir) == 0: + continue + style = 'color: var(--color-accent)' if subdir in ['All', 'Local', 'Diffusers', 'Reference'] else '' + subdirs_html += f'
' self.html = '' self.create_items(tabname) self.create_xyz_grid()