mirror of
https://github.com/easydiffusion/easydiffusion.git
synced 2024-11-22 16:23:28 +01:00
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 de902a6340
.
* 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).
This commit is contained in:
parent
04e8458ce2
commit
a801a5d8b6
@ -14,6 +14,7 @@
|
||||
<link rel="stylesheet" href="/media/css/modifier-thumbnails.css">
|
||||
<link rel="stylesheet" href="/media/css/fontawesome-all.min.css">
|
||||
<link rel="stylesheet" href="/media/css/image-editor.css">
|
||||
<link rel="stylesheet" href="/media/css/searchable-models.css">
|
||||
<link rel="manifest" href="/media/manifest.webmanifest">
|
||||
<script src="/media/js/jquery-3.6.1.min.js"></script>
|
||||
<script src="/media/js/jquery-confirm.min.js"></script>
|
||||
@ -125,10 +126,9 @@
|
||||
<tr><b class="settings-subheader">Image Settings</b></tr>
|
||||
<tr class="pl-5"><td><label for="seed">Seed:</label></td><td><input id="seed" name="seed" size="10" value="0" onkeypress="preventNonNumericalInput(event)"> <input id="random_seed" name="random_seed" type="checkbox" checked><label for="random_seed">Random</label></td></tr>
|
||||
<tr class="pl-5"><td><label for="num_outputs_total">Number of Images:</label></td><td><input id="num_outputs_total" name="num_outputs_total" value="1" size="1" onkeypress="preventNonNumericalInput(event)"> <label><small>(total)</small></label> <input id="num_outputs_parallel" name="num_outputs_parallel" value="1" size="1" onkeypress="preventNonNumericalInput(event)"> <label for="num_outputs_parallel"><small>(in parallel)</small></label></td></tr>
|
||||
<tr class="pl-5"><td><label for="stable_diffusion_model">Model:</label></td><td>
|
||||
<select id="stable_diffusion_model" name="stable_diffusion_model">
|
||||
<!-- <option value="sd-v1-4" selected>sd-v1-4</option> -->
|
||||
</select>
|
||||
<tr class="pl-5"><td><label for="stable_diffusion_model">Model:</label></td><td class="model-input">
|
||||
<input id="stable_diffusion_model" type="text" spellcheck="false" class="model-filter" data-path="" />
|
||||
<button id="reload-models" class="secondaryButton reloadModels"><i class='fa-solid fa-rotate'></i></button>
|
||||
<a href="https://github.com/cmdr2/stable-diffusion-ui/wiki/Custom-Models" target="_blank"><i class="fa-solid fa-circle-question help-btn"><span class="simple-tooltip top-left">Click to learn more about custom models</span></i></a>
|
||||
</td></tr>
|
||||
<!-- <tr id="modelConfigSelection" class="pl-5"><td><label for="model_config">Model Config:</i></label></td><td>
|
||||
@ -136,9 +136,7 @@
|
||||
</select>
|
||||
</td></tr> -->
|
||||
<tr class="pl-5"><td><label for="vae_model">Custom VAE:</i></label></td><td>
|
||||
<select id="vae_model" name="vae_model">
|
||||
<!-- <option value="" selected>None</option> -->
|
||||
</select>
|
||||
<input id="vae_model" type="text" spellcheck="false" class="model-filter" data-path="" />
|
||||
<a href="https://github.com/cmdr2/stable-diffusion-ui/wiki/VAE-Variational-Auto-Encoder" target="_blank"><i class="fa-solid fa-circle-question help-btn"><span class="simple-tooltip top-left">Click to learn more about VAEs</span></i></a>
|
||||
</td></tr>
|
||||
<tr id="samplerSelection" class="pl-5"><td><label for="sampler_name">Sampler:</label></td><td>
|
||||
@ -210,9 +208,7 @@
|
||||
<tr class="pl-5"><td><label for="guidance_scale_slider">Guidance Scale:</label></td><td> <input id="guidance_scale_slider" name="guidance_scale_slider" class="editor-slider" value="75" type="range" min="11" max="500"> <input id="guidance_scale" name="guidance_scale" size="4" pattern="^[0-9\.]+$" onkeypress="preventNonNumericalInput(event)"></td></tr>
|
||||
<tr id="prompt_strength_container" class="pl-5"><td><label for="prompt_strength_slider">Prompt Strength:</label></td><td> <input id="prompt_strength_slider" name="prompt_strength_slider" class="editor-slider" value="80" type="range" min="0" max="99"> <input id="prompt_strength" name="prompt_strength" size="4" pattern="^[0-9\.]+$" onkeypress="preventNonNumericalInput(event)"><br/></td></tr>
|
||||
<tr class="pl-5"><td><label for="hypernetwork_model">Hypernetwork:</i></label></td><td>
|
||||
<select id="hypernetwork_model" name="hypernetwork_model">
|
||||
<!-- <option value="" selected>None</option> -->
|
||||
</select>
|
||||
<input id="hypernetwork_model" type="text" spellcheck="false" class="model-filter" data-path="" />
|
||||
</td></tr>
|
||||
<tr id="hypernetwork_strength_container" class="pl-5">
|
||||
<td><label for="hypernetwork_strength_slider">Hypernetwork Strength:</label></td>
|
||||
@ -427,6 +423,7 @@
|
||||
<script src="media/js/image-modifiers.js"></script>
|
||||
<script src="media/js/auto-save.js"></script>
|
||||
|
||||
<script src="media/js/searchable-models.js"></script>
|
||||
<script src="media/js/main.js"></script>
|
||||
<script src="media/js/themes.js"></script>
|
||||
<script src="media/js/dnd.js"></script>
|
||||
|
92
ui/media/css/searchable-models.css
Normal file
92
ui/media/css/searchable-models.css
Normal file
@ -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);
|
||||
}
|
@ -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
|
||||
}
|
||||
|
@ -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 = '<H1>🔥Malware alert!🔥</H1><h2>The file <i>' + models['scan-error'] + '</i> in your <tt>models/stable-diffusion</tt> folder is probably malware infected.</h2><h2>Please delete this file from the folder before proceeding!</h2>After deleting the file, reload this page.<br><br><button onClick="window.location.reload();">Reload Page</button>'
|
||||
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 <optgroup/>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
|
||||
|
591
ui/media/js/searchable-models.js
Normal file
591
ui/media/js/searchable-models.js
Normal file
@ -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.
|
||||
<input id="stable_diffusion_model" type="text" spellcheck="false" class="model-filter" data-path="" />
|
||||
|
||||
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 = `<i id="${this.modelFilter.id}-model-filter-arrow" class="model-selector-arrow fa-solid fa-angle-down"></i>
|
||||
<ul id="${this.modelFilter.id}-model-list" class="model-list">
|
||||
<li id="${this.modelFilter.id}-model-no-result" class="model-no-result">No result</li>
|
||||
<li id="${this.modelFilter.id}-model-result" class="model-result">
|
||||
<ul>
|
||||
`
|
||||
if (this.noneEntry != '') {
|
||||
html += `<li data-path='' class='model-file in-root-folder'>${this.noneEntry}</li>`
|
||||
}
|
||||
|
||||
let currentFolder = ''
|
||||
models.forEach(entry => {
|
||||
const folder = entry.substring(0, 1) == '/' ? entry.substring(1, entry.lastIndexOf('/')) : ''
|
||||
if (folder !== '' && folder !== currentFolder) {
|
||||
if (currentFolder != '') {
|
||||
html += '</ul></li>'
|
||||
}
|
||||
html += `<li class='model-folder'>/${folder}<ul>`
|
||||
currentFolder = folder
|
||||
}
|
||||
else if (folder == '' && currentFolder !== '') {
|
||||
currentFolder = ''
|
||||
html += '</ul></li>'
|
||||
}
|
||||
const modelName = entry.substring(entry.lastIndexOf('/') + 1)
|
||||
if (entry.substring(0, 1) == '/') {
|
||||
entry = entry.substring(1)
|
||||
}
|
||||
html += `<li data-path='${entry}' class='model-file${currentFolder == '' ? ' in-root-folder' : ''}'>${modelName}</li>`
|
||||
})
|
||||
if (currentFolder != '') {
|
||||
html += '</ul></li>'
|
||||
}
|
||||
|
||||
html += `
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
`
|
||||
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 = '<H1>🔥Malware alert!🔥</H1><h2>The file <i>' + modelsCache['scan-error'] + '</i> in your <tt>models/stable-diffusion</tt> folder is probably malware infected.</h2><h2>Please delete this file from the folder before proceeding!</h2>After deleting the file, reload this page.<br><br><button onClick="window.location.reload();">Reload Page</button>'
|
||||
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)
|
Loading…
Reference in New Issue
Block a user