Initial commit

pull/1/head
ilian.iliev 2023-03-19 15:51:25 +02:00
commit 89a7ebc894
6 changed files with 319 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
.idea
__pycache__/

21
LICENSE Normal file
View File

@ -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.

27
README.md Normal file
View File

@ -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!

179
javascript/state.js Normal file
View File

@ -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 };
}());

30
scripts/state_api.py Normal file
View File

@ -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

60
scripts/state_settings.py Normal file
View File

@ -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)