From a801a5d8b6f2e5b401caa28517029a2d6807bc64 Mon Sep 17 00:00:00 2001
From: patriceac <48073125+patriceac@users.noreply.github.com>
Date: Sun, 12 Feb 2023 01:18:09 -0800
Subject: [PATCH] Searchable models (#786)
* Searchable models
Creates searchable dropdowns for SD, VAE, or HN models. Also adds a reload models button (placed next to SD models, reloads everything including VAE and HN models).
* Fixing the editor pane display
* Revert "Fixing the editor pane display"
This reverts commit de902a634005224d744298c53500a5f33c96ab2a.
* Move formatting to the CSS file
* Rewritten the siblings functions
I like these much better, and I imagine you will too. :)
* Code cleanup
* Minor tweak in list ordering
Minor tweak to move the root folder's content at the end of the list (similar to the current version).
---
ui/index.html | 17 +-
ui/media/css/searchable-models.css | 92 +++++
ui/media/js/auto-save.js | 7 +
ui/media/js/main.js | 81 +---
ui/media/js/searchable-models.js | 591 +++++++++++++++++++++++++++++
5 files changed, 701 insertions(+), 87 deletions(-)
create mode 100644 ui/media/css/searchable-models.css
create mode 100644 ui/media/js/searchable-models.js
diff --git a/ui/index.html b/ui/index.html
index 6f6fab2d..1f29b855 100644
--- a/ui/index.html
+++ b/ui/index.html
@@ -14,6 +14,7 @@
+
@@ -125,10 +126,9 @@
Seed: Random
Number of Images: (total) (in parallel)
- Model:
-
-
-
+ Model:
+
+
Click to learn more about custom models
Custom VAE:
-
-
-
+
Click to learn more about VAEs
Sampler:
@@ -210,9 +208,7 @@
Guidance Scale:
Prompt Strength:
Hypernetwork:
-
-
-
+
Hypernetwork Strength:
@@ -427,6 +423,7 @@
+
diff --git a/ui/media/css/searchable-models.css b/ui/media/css/searchable-models.css
new file mode 100644
index 00000000..418e1a60
--- /dev/null
+++ b/ui/media/css/searchable-models.css
@@ -0,0 +1,92 @@
+.model-list {
+ position: absolute;
+ margin-block-start: 2px;
+ display: none;
+ padding-inline-start: 0;
+ max-height: 200px;
+ overflow: auto;
+ background: var(--input-background-color);
+ border: var(--input-border-size) solid var(--input-border-color);
+ border-radius: var(--input-border-radius);
+ color: var(--input-text-color);
+ z-index: 1;
+ line-height: normal;
+}
+
+.model-list ul {
+ padding-right: 20px;
+ padding-inline-start: 0;
+ padding-bottom: 0px;
+}
+
+.model-list li {
+ padding-bottom: 0px;
+}
+
+.model-result {
+ list-style: none;
+}
+
+.model-no-result {
+ color: var(--text-color);
+ list-style: none;
+ padding: 3px 6px 3px 6px;
+ font-size: 10pt;
+ font-style: italic;
+ display: none;
+}
+
+.model-list li.model-folder {
+ color: var(--text-color);
+ list-style: none;
+ padding: 6px 6px 6px 6px;
+ font-size: 9pt;
+ font-weight: bold;
+}
+
+.model-list li.model-file {
+ color: var(--input-text-color);
+ list-style: none;
+ padding-left: 12px;
+ padding-right:20px;
+ font-size: 9pt;
+ font-weight: normal;
+ transition: none;
+ transition:property: none;
+ cursor: default;
+}
+
+.model-list li.model-file.in-root-folder {
+ padding-left: 6px;
+}
+
+.model-list li.model-file.selected {
+ background: grey;
+}
+
+.model-selector {
+ cursor: pointer;
+}
+
+.model-selector-arrow {
+ position: absolute;
+ width: 17px;
+ margin: 5px -17px;
+ padding-top: 3px;
+ cursor: pointer;
+ font-size: 8pt;
+}
+
+.model-input {
+ white-space: nowrap;
+}
+
+.reloadModels {
+ background: var(--background-color2);
+ border: none;
+ padding: 0px 0px;
+}
+
+#reload-models.secondaryButton:hover {
+ background: var(--background-color2);
+}
diff --git a/ui/media/js/auto-save.js b/ui/media/js/auto-save.js
index d17f0b29..95fa7f8a 100644
--- a/ui/media/js/auto-save.js
+++ b/ui/media/js/auto-save.js
@@ -93,6 +93,9 @@ async function initSettings() {
}
function getSetting(element) {
+ if (element.dataset && 'path' in element.dataset) {
+ return element.dataset.path
+ }
if (typeof element === "string" || element instanceof String) {
element = SETTINGS[element].element
}
@@ -102,6 +105,10 @@ function getSetting(element) {
return element.value
}
function setSetting(element, value) {
+ if (element.dataset && 'path' in element.dataset) {
+ element.dataset.path = value
+ return // no need to dispatch any event here because the models are not loaded yet
+ }
if (typeof element === "string" || element instanceof String) {
element = SETTINGS[element].element
}
diff --git a/ui/media/js/main.js b/ui/media/js/main.js
index 1e8f63e9..2b190654 100644
--- a/ui/media/js/main.js
+++ b/ui/media/js/main.js
@@ -37,9 +37,9 @@ let gfpganModelField = document.querySelector("#gfpgan_model")
let useUpscalingField = document.querySelector("#use_upscale")
let upscaleModelField = document.querySelector("#upscale_model")
let upscaleAmountField = document.querySelector("#upscale_amount")
-let stableDiffusionModelField = document.querySelector('#stable_diffusion_model')
-let vaeModelField = document.querySelector('#vae_model')
-let hypernetworkModelField = document.querySelector('#hypernetwork_model')
+let stableDiffusionModelField = new ModelDropdown(document.querySelector('#stable_diffusion_model'), 'stable-diffusion')
+let vaeModelField = new ModelDropdown(document.querySelector('#vae_model'), 'vae', 'None')
+let hypernetworkModelField = new ModelDropdown(document.querySelector('#hypernetwork_model'), 'hypernetwork', 'None')
let hypernetworkStrengthSlider = document.querySelector('#hypernetwork_strength_slider')
let hypernetworkStrengthField = document.querySelector('#hypernetwork_strength')
let outputFormatField = document.querySelector('#output_format')
@@ -962,7 +962,7 @@ function getCurrentUserRequest() {
// allow_nsfw: allowNSFWField.checked,
vram_usage_level: vramUsageLevelField.value,
//render_device: undefined, // Set device affinity. Prefer this device, but wont activate.
- use_stable_diffusion_model: stableDiffusionModelField.value,
+ use_stable_diffusion_ll: stableDiffusionModelField.value,
use_vae_model: vaeModelField.value,
stream_progress_updates: true,
stream_image_progress: (numOutputsTotal > 50 ? false : streamImageProgressField.checked),
@@ -1315,79 +1315,6 @@ outputFormatField.addEventListener('change', e => {
}
})
-async function getModels() {
- try {
- const sd_model_setting_key = "stable_diffusion_model"
- const vae_model_setting_key = "vae_model"
- const hypernetwork_model_key = "hypernetwork_model"
- const gfpgan_model_key = "gfpgan_model"
- const selectedSDModel = SETTINGS[sd_model_setting_key].value
- const selectedVaeModel = SETTINGS[vae_model_setting_key].value
- const selectedHypernetworkModel = SETTINGS[hypernetwork_model_key].value
- const selectedGfpganModel = SETTINGS[gfpgan_model_key].value
-
- const models = await SD.getModels()
- const modelsOptions = models['options']
- if ("scan-error" in models) {
- // let previewPane = document.getElementById('tab-content-wrapper')
- let previewPane = document.getElementById('preview')
- previewPane.style.background="red"
- previewPane.style.textAlign="center"
- previewPane.innerHTML = '🔥Malware alert!🔥 The file ' + models['scan-error'] + ' in your models/stable-diffusion folder is probably malware infected. Please delete this file from the folder before proceeding! After deleting the file, reload this page.Reload Page '
- makeImageBtn.disabled = true
- }
-
- const stableDiffusionOptions = modelsOptions['stable-diffusion']
- const vaeOptions = modelsOptions['vae']
- const hypernetworkOptions = modelsOptions['hypernetwork']
- const gfpganOptions = modelsOptions['gfpgan']
-
- vaeOptions.unshift('') // add a None option
- hypernetworkOptions.unshift('') // add a None option
-
- function createModelOptions(modelField, selectedModel, path="") {
- return function fn(modelName) {
- if (typeof(modelName) == 'string') {
- const modelOption = document.createElement('option')
- modelOption.value = path + modelName
- modelOption.innerHTML = modelName !== '' ? (path != "" ? " "+modelName : modelName) : 'None'
-
- if (path + modelName === selectedModel) {
- modelOption.selected = true
- }
- modelField.appendChild(modelOption)
- } else {
- // Since s can't be nested, don't show empty groups
- if (modelName[1].some(child => typeof(child) == 'string')) {
- const modelGroup = document.createElement('optgroup')
- modelGroup.label = path + modelName[0]
- modelField.appendChild(modelGroup)
- }
- modelName[1].forEach( createModelOptions(modelField, selectedModel, path + modelName[0] + "/" ) )
- }
- }
- }
-
- stableDiffusionOptions.forEach(createModelOptions(stableDiffusionModelField, selectedSDModel))
- vaeOptions.forEach(createModelOptions(vaeModelField, selectedVaeModel))
- hypernetworkOptions.forEach(createModelOptions(hypernetworkModelField, selectedHypernetworkModel))
- gfpganOptions.forEach(createModelOptions(gfpganModelField,selectedGfpganModel))
-
-
- stableDiffusionModelField.dispatchEvent(new Event('change'))
- vaeModelField.dispatchEvent(new Event('change'))
- hypernetworkModelField.dispatchEvent(new Event('change'))
-
- // TODO: set default for model here too
- SETTINGS[sd_model_setting_key].default = stableDiffusionOptions[0]
- if (getSetting(sd_model_setting_key) == '' || SETTINGS[sd_model_setting_key].value == '') {
- setSetting(sd_model_setting_key, stableDiffusionOptions[0])
- }
- } catch (e) {
- console.log('get models error', e)
- }
-}
-
function checkRandomSeed() {
if (randomSeedField.checked) {
seedField.disabled = true
diff --git a/ui/media/js/searchable-models.js b/ui/media/js/searchable-models.js
new file mode 100644
index 00000000..7ffb4a5a
--- /dev/null
+++ b/ui/media/js/searchable-models.js
@@ -0,0 +1,591 @@
+"use strict"
+
+let modelsCache
+let modelsOptions
+
+/*
+*** SEARCHABLE MODELS ***
+Creates searchable dropdowns for SD, VAE, or HN models.
+Also adds a reload models button (placed next to SD models, reloads everything including VAE and HN models).
+More reload buttons may be added at strategic UI locations as needed.
+Merely calling getModels() makes all the magic happen behind the scene to refresh the dropdowns.
+
+HOW TO CREATE A MODEL DROPDOWN:
+1) Create an input element. Make sure to add a data-path property, as this is how model dropdowns are identified in auto-save.js.
+
+
+2) Just declare one of these for your own dropdown (remember to change the element id, e.g. #stable_diffusion_models to your own input's id).
+let stableDiffusionModelField = new ModelDropdown(document.querySelector('#stable_diffusion_model'), 'stable-diffusion')
+let vaeModelField = new ModelDropdown(document.querySelector('#vae_model'), 'vae', 'None')
+let hypernetworkModelField = new ModelDropdown(document.querySelector('#hypernetwork_model'), 'hypernetwork', 'None')
+
+3) Model dropdowns will be refreshed automatically when the reload models button is invoked.
+*/
+class ModelDropdown
+{
+ modelFilter //= document.querySelector("#model-filter")
+ modelFilterArrow //= document.querySelector("#model-filter-arrow")
+ modelList //= document.querySelector("#model-list")
+ modelResult //= document.querySelector("#model-result")
+ modelNoResult //= document.querySelector("#model-no-result")
+
+ currentSelection //= { elem: undefined, value: '', path: ''}
+ highlightedModelEntry //= undefined
+ activeModel //= undefined
+
+ inputModels //= undefined
+ modelKey //= undefined
+ flatModelList //= []
+ noneEntry //= ''
+
+ /* MIMIC A REGULAR INPUT FIELD */
+ get parentElement() {
+ return this.modelFilter.parentElement
+ }
+ get parentNode() {
+ return this.modelFilter.parentNode
+ }
+ get value() {
+ return this.modelFilter.dataset.path
+ }
+ set value(path) {
+ this.modelFilter.dataset.path = path
+ this.selectEntry(path)
+ }
+ addEventListener(type, listener, options) {
+ return this.modelFilter.addEventListener(type, listener, options)
+ }
+ dispatchEvent(event) {
+ return this.modelFilter.dispatchEvent(event)
+ }
+ appendChild(option) {
+ // do nothing
+ }
+
+ // remember 'this' - http://blog.niftysnippets.org/2008/04/you-must-remember-this.html
+ bind(f, obj) {
+ return function() {
+ return f.apply(obj, arguments)
+ }
+ }
+
+ /* SEARCHABLE INPUT */
+ constructor (input, modelKey, noneEntry = '') {
+ this.modelFilter = input
+ this.noneEntry = noneEntry
+ this.modelKey = modelKey
+
+ if (modelsOptions !== undefined) { // reuse models from cache (only useful for plugins, which are loaded after models)
+ this.inputModels = modelsOptions[this.modelKey]
+ this.populateModels()
+ }
+ document.addEventListener("refreshModels", this.bind(function(e) {
+ // reload the models
+ this.inputModels = modelsOptions[this.modelKey]
+ this.populateModels()
+ }, this))
+ }
+
+ saveCurrentSelection(elem, value, path) {
+ this.currentSelection.elem = elem
+ this.currentSelection.value = value
+ this.currentSelection.path = path
+ this.modelFilter.dataset.path = path
+ this.modelFilter.value = value
+ this.modelFilter.dispatchEvent(new Event('change'))
+ }
+
+ processClick(e) {
+ e.preventDefault()
+ if (e.srcElement.classList.contains('model-file')) {
+ this.saveCurrentSelection(e.srcElement, e.srcElement.innerText, e.srcElement.dataset.path)
+ this.hideModelList()
+ this.modelFilter.focus()
+ this.modelFilter.select()
+ }
+ }
+
+ findPreviousSibling(elem) {
+ // is there an immediate sibling?
+ let prevSibling = elem.previousElementSibling
+ if (prevSibling) {
+ // if the previous sibling is a model file, just select it
+ if (prevSibling.classList.contains('model-file')) return prevSibling
+
+ // if the previous sibling is a model folder, select the last model file it contains
+ if (prevSibling.classList.contains('model-folder')) return prevSibling.firstElementChild.lastElementChild
+ }
+
+ // no sibling model file and no sibling model folder. look for siblings around the parent element.
+ prevSibling = elem.parentElement.parentElement.previousElementSibling
+ if (prevSibling) {
+ // if the previous entry is a model file, select it
+ if (prevSibling.classList.contains('model-file')) return prevSibling
+
+ // is there another model folder to jump to before the current one?
+ if (prevSibling.classList.contains('model-folder')) return prevSibling.firstElementChild.lastElementChild
+ }
+ }
+
+ findNextSibling(elem) {
+ // is there an immediate sibling?
+ let nextSibling = elem.nextElementSibling
+ if (nextSibling) {
+ // if the next sibling is a model file, just select it
+ if (nextSibling.classList.contains('model-file')) return nextSibling
+
+ // if the next sibling is a model folder, select the first model file it contains
+ if (nextSibling.classList.contains('model-folder')) return nextSibling.firstElementChild.firstElementChild
+ }
+
+ // no sibling model file and no sibling model folder. look for siblings around the parent element.
+ nextSibling = elem.parentElement.parentElement.nextElementSibling
+ if (nextSibling) {
+ // if the next entry is a model file, select it
+ if (nextSibling.classList.contains('model-file')) return nextSibling
+
+ // is there another model folder to jump to after the current one?
+ if (nextSibling.classList.contains('model-folder')) return nextSibling.firstElementChild.firstElementChild
+ }
+ }
+
+ selectModelEntry(elem) {
+ if (elem) {
+ if (this.highlightedModelEntry !== undefined) {
+ this.highlightedModelEntry.classList.remove('selected')
+ }
+ this.saveCurrentSelection(elem, elem.innerText, elem.dataset.path)
+ elem.classList.add('selected')
+ elem.scrollIntoView({block: 'nearest'})
+ this.highlightedModelEntry = elem
+ }
+ }
+
+ selectPreviousFile() {
+ const elem = this.findPreviousSibling(this.highlightedModelEntry)
+ if (elem) {
+ this.selectModelEntry(elem)
+ }
+ else
+ {
+ //this.highlightedModelEntry.parentElement.parentElement.scrollIntoView({block: 'nearest'})
+ this.highlightedModelEntry.closest('.model-list').scrollTop = 0
+ }
+ this.modelFilter.select()
+ }
+
+ selectNextFile() {
+ this.selectModelEntry(this.findNextSibling(this.highlightedModelEntry))
+ this.modelFilter.select()
+ }
+
+ selectFirstFile() {
+ this.selectModelEntry(this.modelList.querySelector('.model-file'))
+ this.highlightedModelEntry.scrollIntoView({block: 'nearest'})
+ this.modelFilter.select()
+ }
+
+ selectLastFile() {
+ const elems = this.modelList.querySelectorAll('.model-file:last-child')
+ this.selectModelEntry(elems[elems.length -1])
+ this.modelFilter.select()
+ }
+
+ resetSelection() {
+ this.hideModelList()
+ this.showAllEntries()
+ this.modelFilter.value = this.currentSelection.value
+ this.modelFilter.focus()
+ this.modelFilter.select()
+ }
+
+ validEntrySelected() {
+ return (this.modelNoResult.style.display === 'none')
+ }
+
+ processKey(e) {
+ switch (e.key) {
+ case 'Escape':
+ e.preventDefault()
+ this.resetSelection()
+ break
+ case 'Enter':
+ e.preventDefault()
+ if (this.validEntrySelected()) {
+ if (this.modelList.style.display != 'block') {
+ this.showModelList()
+ }
+ else
+ {
+ this.saveCurrentSelection(this.highlightedModelEntry, this.highlightedModelEntry.innerText, this.highlightedModelEntry.dataset.path)
+ this.hideModelList()
+ }
+ this.modelFilter.focus()
+ }
+ else
+ {
+ this.resetSelection()
+ }
+ break
+ case 'ArrowUp':
+ e.preventDefault()
+ if (this.validEntrySelected()) {
+ this.selectPreviousFile()
+ }
+ break
+ case 'ArrowDown':
+ e.preventDefault()
+ if (this.validEntrySelected()) {
+ this.selectNextFile()
+ }
+ break
+ case 'ArrowLeft':
+ if (this.modelList.style.display != 'block') {
+ e.preventDefault()
+ }
+ break
+ case 'ArrowRight':
+ if (this.modelList.style.display != 'block') {
+ e.preventDefault()
+ }
+ break
+ case 'PageUp':
+ e.preventDefault()
+ if (this.validEntrySelected()) {
+ this.selectPreviousFile()
+ this.selectPreviousFile()
+ this.selectPreviousFile()
+ this.selectPreviousFile()
+ this.selectPreviousFile()
+ this.selectPreviousFile()
+ this.selectPreviousFile()
+ this.selectPreviousFile()
+ }
+ break
+ case 'PageDown':
+ e.preventDefault()
+ if (this.validEntrySelected()) {
+ this.selectNextFile()
+ this.selectNextFile()
+ this.selectNextFile()
+ this.selectNextFile()
+ this.selectNextFile()
+ this.selectNextFile()
+ this.selectNextFile()
+ this.selectNextFile()
+ }
+ break
+ case 'Home':
+ //if (this.modelList.style.display != 'block') {
+ e.preventDefault()
+ if (this.validEntrySelected()) {
+ this.selectFirstFile()
+ }
+ //}
+ break
+ case 'End':
+ //if (this.modelList.style.display != 'block') {
+ e.preventDefault()
+ if (this.validEntrySelected()) {
+ this.selectLastFile()
+ }
+ //}
+ break
+ default:
+ //console.log(e.key)
+ }
+ }
+
+ modelListFocus() {
+ this.selectEntry()
+ this.showAllEntries()
+ }
+
+ showModelList() {
+ this.modelList.style.display = 'block'
+ this.selectEntry()
+ this.showAllEntries()
+ this.modelFilter.value = ''
+ this.modelFilter.focus()
+ this.modelFilter.style.cursor = 'auto'
+ }
+
+ hideModelList() {
+ this.modelList.style.display = 'none'
+ this.modelFilter.value = this.currentSelection.value
+ this.modelFilter.style.cursor = ''
+ }
+
+ toggleModelList(e) {
+ e.preventDefault()
+ if (this.modelList.style.display != 'block') {
+ this.showModelList()
+ }
+ else
+ {
+ this.hideModelList()
+ this.modelFilter.select()
+ }
+ }
+
+ selectEntry(path) {
+ if (path !== undefined) {
+ const entries = this.modelList.querySelectorAll('.model-file');
+
+ for (let i = 0; i < entries.length; i++) {
+ if (entries[i].dataset.path == path) {
+ this.saveCurrentSelection(entries[i], entries[i].innerText, entries[i].dataset.path)
+ this.highlightedModelEntry = entries[i]
+ entries[i].scrollIntoView({block: 'nearest'})
+ break
+ }
+ }
+ }
+
+ if (this.currentSelection.elem !== undefined) {
+ // select the previous element
+ if (this.highlightedModelEntry !== undefined && this.highlightedModelEntry != this.currentSelection.elem) {
+ this.highlightedModelEntry.classList.remove('selected')
+ }
+ this.currentSelection.elem.classList.add('selected')
+ this.highlightedModelEntry = this.currentSelection.elem
+ this.currentSelection.elem.scrollIntoView({block: 'nearest'})
+ }
+ else
+ {
+ this.selectFirstFile()
+ }
+ }
+
+ highlightModelAtPosition(e) {
+ let elem = document.elementFromPoint(e.clientX, e.clientY)
+
+ if (elem.classList.contains('model-file')) {
+ this.highlightModel(elem)
+ }
+ }
+
+ highlightModel(elem) {
+ if (elem.classList.contains('model-file')) {
+ if (this.highlightedModelEntry !== undefined && this.highlightedModelEntry != elem) {
+ this.highlightedModelEntry.classList.remove('selected')
+ }
+ elem.classList.add('selected')
+ this.highlightedModelEntry = elem
+ }
+ }
+
+ showAllEntries() {
+ this.modelList.querySelectorAll('li').forEach(function(li) {
+ if (li.id !== 'model-no-result') {
+ li.style.display = 'list-item'
+ }
+ })
+ this.modelNoResult.style.display = 'none'
+ }
+
+ filterList(e) {
+ const filter = this.modelFilter.value.toLowerCase()
+ let found = false
+ let showAllChildren = false
+
+ this.modelList.querySelectorAll('li').forEach(function(li) {
+ if (li.classList.contains('model-folder')) {
+ showAllChildren = false
+ }
+ if (filter == '') {
+ li.style.display = 'list-item'
+ found = true
+ } else if (showAllChildren || li.textContent.toLowerCase().match(filter)) {
+ li.style.display = 'list-item'
+ if (li.classList.contains('model-folder') && li.firstChild.textContent.toLowerCase().match(filter)) {
+ showAllChildren = true
+ }
+ found = true
+ } else {
+ li.style.display = 'none'
+ }
+ })
+
+ if (found) {
+ this.modelResult.style.display = 'list-item'
+ this.modelNoResult.style.display = 'none'
+ const elem = this.findNextSibling(this.modelList.querySelector('.model-file'))
+ this.highlightModel(elem)
+ elem.scrollIntoView({block: 'nearest'})
+ }
+ else
+ {
+ this.modelResult.style.display = 'none'
+ this.modelNoResult.style.display = 'list-item'
+ }
+ this.modelList.style.display = 'block'
+ }
+
+ /* MODEL LOADER */
+ flattenModelList(models, path) {
+ models.forEach(entry => {
+ if (Array.isArray(entry)) {
+ this.flattenModelList(entry[1], path + '/' + entry[0])
+ }
+ else
+ {
+ this.flatModelList.push(path == '' ? entry : path + '/' + entry)
+ }
+ })
+ }
+
+ // sort models
+ getFolder(model) {
+ return model.substring(0, model.lastIndexOf('/') + 1)
+ }
+
+ sortModels(models) {
+ // sort the models in alphabetical order, root folder models last
+ models.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()))
+
+ /*
+ // sort the models in alphabetical order, root folder models first
+ models = models.sort()
+ let found
+ do {
+ found = false
+ for (let i = 0; i < models.length - 1; i++) {
+ if (
+ (this.getFolder(models[i]) === this.getFolder(models[i+1]) && models[i].toLowerCase().localeCompare(models[i+1].toLowerCase()) > 0) // same folder, sort by alphabetical order
+ || (this.getFolder(models[i]).toLowerCase().localeCompare(this.getFolder(models[i+1]).toLowerCase()) > 0) // L1 folder > L2 folder
+ ) {
+ [models[i], models[i+1]] = [models[i+1], models[i]]
+ found = true
+ }
+ }
+ } while (found)
+ */
+ }
+
+ populateModels() {
+ this.activeModel = this.modelFilter.dataset.path
+
+ this.currentSelection = { elem: undefined, value: '', path: ''}
+ this.highlightedModelEntry = undefined
+ this.flatModelList = []
+
+ if(this.modelList !== undefined) {
+ this.modelList.remove()
+ this.modelFilterArrow.remove()
+ }
+ this.createDropdown()
+ }
+
+ createDropdown() {
+ // prepare to sort the models
+ this.flattenModelList(this.inputModels, '')
+ this.sortModels(this.flatModelList)
+
+ // create dropdown entries
+ this.modelFilter.insertAdjacentHTML('afterend', this.parseModels(this.flatModelList))
+ this.modelFilter.classList.add('model-selector')
+ this.modelFilterArrow = document.querySelector(`#${this.modelFilter.id}-model-filter-arrow`)
+ this.modelList = document.querySelector(`#${this.modelFilter.id}-model-list`)
+ this.modelResult = document.querySelector(`#${this.modelFilter.id}-model-result`)
+ this.modelNoResult = document.querySelector(`#${this.modelFilter.id}-model-no-result`)
+ this.modelList.style.display = 'block'
+ this.modelFilter.style.width = this.modelList.offsetWidth + 'px'
+ this.modelFilterArrow.style.height = this.modelFilter.offsetHeight + 'px'
+ this.modelList.style.display = 'none'
+
+ this.modelFilter.addEventListener('input', this.bind(this.filterList, this))
+ this.modelFilter.addEventListener('focus', this.bind(this.modelListFocus, this))
+ this.modelFilter.addEventListener('blur', this.bind(this.hideModelList, this))
+ this.modelFilter.addEventListener('click', this.bind(this.showModelList, this))
+ this.modelFilter.addEventListener('keydown', this.bind(this.processKey, this))
+ this.modelFilterArrow.addEventListener('mousedown', this.bind(this.toggleModelList, this))
+ this.modelList.addEventListener('mousemove', this.bind(this.highlightModelAtPosition, this))
+ this.modelList.addEventListener('mousedown', this.bind(this.processClick, this))
+
+ this.selectEntry(this.activeModel)
+ }
+
+ parseModels(models) {
+ let html = `
+
+ No result
+
+
+ `
+ if (this.noneEntry != '') {
+ html += `${this.noneEntry} `
+ }
+
+ let currentFolder = ''
+ models.forEach(entry => {
+ const folder = entry.substring(0, 1) == '/' ? entry.substring(1, entry.lastIndexOf('/')) : ''
+ if (folder !== '' && folder !== currentFolder) {
+ if (currentFolder != '') {
+ html += ' '
+ }
+ html += `/${folder}`
+ currentFolder = folder
+ }
+ else if (folder == '' && currentFolder !== '') {
+ currentFolder = ''
+ html += ' '
+ }
+ const modelName = entry.substring(entry.lastIndexOf('/') + 1)
+ if (entry.substring(0, 1) == '/') {
+ entry = entry.substring(1)
+ }
+ html += `${modelName} `
+ })
+ if (currentFolder != '') {
+ html += ' '
+ }
+
+ html += `
+
+
+
+ `
+ return html
+ }
+}
+
+/* (RE)LOAD THE MODELS */
+async function getModels() {
+ try {
+ modelsCache = await SD.getModels()
+ modelsOptions = modelsCache['options']
+ if ("scan-error" in modelsCache) {
+ // let previewPane = document.getElementById('tab-content-wrapper')
+ let previewPane = document.getElementById('preview')
+ previewPane.style.background="red"
+ previewPane.style.textAlign="center"
+ previewPane.innerHTML = '🔥Malware alert!🔥 The file ' + modelsCache['scan-error'] + ' in your models/stable-diffusion folder is probably malware infected. Please delete this file from the folder before proceeding! After deleting the file, reload this page.Reload Page '
+ makeImageBtn.disabled = true
+ }
+
+ /* This code should no longer be needed. Commenting out for now, will cleanup later.
+ const sd_model_setting_key = "stable_diffusion_model"
+ const vae_model_setting_key = "vae_model"
+ const hypernetwork_model_key = "hypernetwork_model"
+
+ const stableDiffusionOptions = modelsOptions['stable-diffusion']
+ const vaeOptions = modelsOptions['vae']
+ const hypernetworkOptions = modelsOptions['hypernetwork']
+
+ // TODO: set default for model here too
+ SETTINGS[sd_model_setting_key].default = stableDiffusionOptions[0]
+ if (getSetting(sd_model_setting_key) == '' || SETTINGS[sd_model_setting_key].value == '') {
+ setSetting(sd_model_setting_key, stableDiffusionOptions[0])
+ }
+ */
+
+ // notify ModelDropdown objects to refresh
+ document.dispatchEvent(new Event('refreshModels'))
+ } catch (e) {
+ console.log('get models error', e)
+ }
+}
+
+// reload models button
+document.querySelector('#reload-models').addEventListener('click', getModels)