Initial commit
commit
89a7ebc894
|
|
@ -0,0 +1,2 @@
|
||||||
|
.idea
|
||||||
|
__pycache__/
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2023 Ilian Iliev
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
<img alt="" src="https://img.shields.io/badge/JavaScript-323330?style=for-the-badge&logo=javascript&logoColor=F7DF1E" />
|
||||||
|
<img alt="" src="https://img.shields.io/badge/Python-FFD43B?style=for-the-badge&logo=python&logoColor=blue" />
|
||||||
|
|
||||||
|
# stable-diffusion-webui-state
|
||||||
|
|
||||||
|
This extension is for AUTOMATIC1111's [Stable Diffusion web UI](https://github.com/AUTOMATIC1111/stable-diffusion-webui)
|
||||||
|
|
||||||
|
### Capabilities
|
||||||
|
|
||||||
|
* Preserve web UI parameters (inputs, sliders, checkboxes etc.) after page reload.
|
||||||
|
* It can be extended to preserve basically everything in the UI.
|
||||||
|
|
||||||
|
### Install
|
||||||
|
|
||||||
|
Use **Install from URL** option with this repo url.
|
||||||
|
|
||||||
|
### Requirements
|
||||||
|
|
||||||
|
None at all.
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
Go to **Settings->State** and check all parameters that you want to be preserved after page reload.
|
||||||
|
|
||||||
|
### Contributing
|
||||||
|
|
||||||
|
Feel free to submit PRs to develop!
|
||||||
|
|
@ -0,0 +1,179 @@
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
onUiLoaded(StateController.init);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
const StateController = (function () {
|
||||||
|
|
||||||
|
const LS_PREFIX = 'state-';
|
||||||
|
const TABS = ['txt2img', 'img2img'];
|
||||||
|
const ELEMENTS = {
|
||||||
|
'prompt': 'prompt',
|
||||||
|
'negative_prompt': 'neg_prompt',
|
||||||
|
'sampling': 'sampling',
|
||||||
|
'sampling_steps': 'steps',
|
||||||
|
'restore_faces': 'restore_faces',
|
||||||
|
'tiling': 'tiling',
|
||||||
|
'hires_fix': 'enable_hr',
|
||||||
|
'hires_upscaler': 'hr_upscaler',
|
||||||
|
'hires_steps': 'hires_steps',
|
||||||
|
'hires_scale': 'hr_scale',
|
||||||
|
'hires_resize_x': 'hr_resize_x',
|
||||||
|
'hires_resize_y': 'hr_resize_y',
|
||||||
|
'hires_denoising_strength': 'denoising_strength',
|
||||||
|
'width': 'width',
|
||||||
|
'height': 'height',
|
||||||
|
'batch_count': 'batch_count',
|
||||||
|
'batch_size': 'batch_size',
|
||||||
|
'cfg_scale': 'cfg_scale',
|
||||||
|
'denoising_strength': 'denoising_strength',
|
||||||
|
'seed': 'seed'
|
||||||
|
};
|
||||||
|
|
||||||
|
const ELEMENTS_WITHOUT_PREFIX = {
|
||||||
|
'resize_mode': 'resize_mode',
|
||||||
|
};
|
||||||
|
|
||||||
|
function triggerEvent(element, event) {
|
||||||
|
if (! element) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
element.dispatchEvent(new Event(event.trim()));
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasSetting(id, tab) {
|
||||||
|
const suffix = tab ? `_${tab}` : '';
|
||||||
|
return this[`state${suffix}`] && this[`state${suffix}`].indexOf(id) > -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
fetch('/state/config.json?_=' + (+new Date()))
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(config => {
|
||||||
|
try {
|
||||||
|
config.hasSetting = hasSetting
|
||||||
|
load(config);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[state]: Error:', error);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => console.error('[state]: Error getting JSON file:', error));
|
||||||
|
}
|
||||||
|
|
||||||
|
function load(config) {
|
||||||
|
|
||||||
|
restoreTabs(config);
|
||||||
|
|
||||||
|
for (const [settingId, element] of Object.entries(ELEMENTS)) {
|
||||||
|
TABS.forEach(tab => {
|
||||||
|
if (config.hasSetting(settingId, tab)) {
|
||||||
|
handleSavedInput(`${tab}_${element}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [settingId, element] of Object.entries(ELEMENTS_WITHOUT_PREFIX)) {
|
||||||
|
TABS.forEach(tab => {
|
||||||
|
if (config.hasSetting(settingId, tab)) {
|
||||||
|
handleSavedInput(`${element}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function restoreTabs(config) {
|
||||||
|
|
||||||
|
if (! config.hasSetting('tabs')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tabs = gradioApp().querySelectorAll('#tabs > div:first-child button');
|
||||||
|
|
||||||
|
tabs.forEach(tab => {
|
||||||
|
tab.addEventListener('click', function () {
|
||||||
|
localStorage.setItem(LS_PREFIX + 'tab', this.textContent);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const value = localStorage.getItem(LS_PREFIX + 'tab');
|
||||||
|
|
||||||
|
if (value) {
|
||||||
|
for (var i = 0; i < tabs.length; i++) {
|
||||||
|
if (tabs[i].textContent === value) {
|
||||||
|
triggerEvent(tabs[i], 'click');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSavedInput(id) {
|
||||||
|
|
||||||
|
const elements = gradioApp().querySelectorAll(`#${id} textarea, #${id} select, #${id} input`);
|
||||||
|
const events = ['change', 'input'];
|
||||||
|
|
||||||
|
if (! elements || ! elements.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let forEach = function (action) {
|
||||||
|
events.forEach(function(event) {
|
||||||
|
elements.forEach(function (element) {
|
||||||
|
action.call(element, event);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
forEach(function (event) {
|
||||||
|
this.addEventListener(event, function () {
|
||||||
|
let value = this.value;
|
||||||
|
if (this.type && this.type === 'checkbox') {
|
||||||
|
value = this.checked;
|
||||||
|
}
|
||||||
|
localStorage.setItem(LS_PREFIX + id, value);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
TABS.forEach(tab => {
|
||||||
|
const seedInput = gradioApp().querySelector(`#${tab}_seed input`);
|
||||||
|
['random_seed', 'reuse_seed'].forEach(id => {
|
||||||
|
const btn = gradioApp().querySelector(`#${tab}_${id}`);
|
||||||
|
btn.addEventListener('click', () => {
|
||||||
|
setTimeout(() => {
|
||||||
|
triggerEvent(seedInput, 'change');
|
||||||
|
}, 100);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
let value = localStorage.getItem(LS_PREFIX + id);
|
||||||
|
|
||||||
|
if (! value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
forEach(function (event) {
|
||||||
|
switch (this.type) {
|
||||||
|
case 'checkbox':
|
||||||
|
this.checked = value === 'true';
|
||||||
|
triggerEvent(this, event);
|
||||||
|
break;
|
||||||
|
case 'radio':
|
||||||
|
if (this.value === value) {
|
||||||
|
this.checked = true;
|
||||||
|
triggerEvent(this, event);
|
||||||
|
} else {
|
||||||
|
this.checked = false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
this.value = value;
|
||||||
|
triggerEvent(this, event);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return { init };
|
||||||
|
}());
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
from fastapi import FastAPI, Body, HTTPException, Request, Response
|
||||||
|
from fastapi.responses import FileResponse
|
||||||
|
|
||||||
|
import gradio as gr
|
||||||
|
import modules.script_callbacks as script_callbacks
|
||||||
|
|
||||||
|
|
||||||
|
class StateApi():
|
||||||
|
|
||||||
|
BASE_PATH = '/state'
|
||||||
|
|
||||||
|
def get_path(self, path):
|
||||||
|
return f"{self.BASE_PATH}{path}"
|
||||||
|
|
||||||
|
def add_api_route(self, path: str, endpoint, **kwargs):
|
||||||
|
return self.app.add_api_route(self.get_path(path), endpoint, **kwargs)
|
||||||
|
|
||||||
|
def start(self, _: gr.Blocks, app: FastAPI):
|
||||||
|
self.app = app
|
||||||
|
self.add_api_route('/config.json', self.get_config, methods=['GET'])
|
||||||
|
|
||||||
|
def get_config(self):
|
||||||
|
return FileResponse('config.json')
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
api = StateApi()
|
||||||
|
script_callbacks.on_app_started(api.start)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
import gradio as gr
|
||||||
|
import modules.shared as shared
|
||||||
|
from modules import scripts
|
||||||
|
|
||||||
|
|
||||||
|
def on_ui_settings():
|
||||||
|
|
||||||
|
section = ("state", "State")
|
||||||
|
|
||||||
|
shared.opts.add_option("state", shared.OptionInfo([], "Saved main elements", gr.CheckboxGroup, lambda: {
|
||||||
|
"choices": [
|
||||||
|
"tabs"
|
||||||
|
]
|
||||||
|
}, section=section))
|
||||||
|
|
||||||
|
shared.opts.add_option("state_txt2img", shared.OptionInfo([], "Saved elements from txt2img", gr.CheckboxGroup, lambda: {
|
||||||
|
"choices": [
|
||||||
|
"prompt",
|
||||||
|
"negative_prompt",
|
||||||
|
"sampling",
|
||||||
|
"sampling_steps",
|
||||||
|
"width",
|
||||||
|
"height",
|
||||||
|
"batch_count",
|
||||||
|
"batch_size",
|
||||||
|
"cfg_scale",
|
||||||
|
"seed",
|
||||||
|
"restore_faces",
|
||||||
|
"tiling",
|
||||||
|
"hires_fix",
|
||||||
|
"hires_upscaler",
|
||||||
|
"hires_steps",
|
||||||
|
"hires_scale",
|
||||||
|
"hires_resize_x",
|
||||||
|
"hires_resize_y",
|
||||||
|
"hires_denoising_strength",
|
||||||
|
]
|
||||||
|
}, section=section))
|
||||||
|
|
||||||
|
shared.opts.add_option("state_img2img", shared.OptionInfo([], "Saved elements from img2img", gr.CheckboxGroup, lambda: {
|
||||||
|
"choices": [
|
||||||
|
"prompt",
|
||||||
|
"negative_prompt",
|
||||||
|
"sampling",
|
||||||
|
"resize_mode",
|
||||||
|
"sampling_steps",
|
||||||
|
"restore_faces",
|
||||||
|
"tiling",
|
||||||
|
"width",
|
||||||
|
"height",
|
||||||
|
"batch_count",
|
||||||
|
"batch_size",
|
||||||
|
"cfg_scale",
|
||||||
|
"denoising_strength",
|
||||||
|
"seed"
|
||||||
|
]
|
||||||
|
}, section=section))
|
||||||
|
|
||||||
|
|
||||||
|
scripts.script_callbacks.on_ui_settings(on_ui_settings)
|
||||||
Loading…
Reference in New Issue