Settings popup and persistent settings

This commit is contained in:
turboderp
2023-11-05 04:47:07 +01:00
parent a6c665dbb7
commit e0c189ac12
14 changed files with 245 additions and 222 deletions

27
backend/settings.py Normal file
View File

@@ -0,0 +1,27 @@
import json
from backend.config import config_filename
def default_settings():
j = {}
j["smooth_scrolling"] = True
j["show_stats"] = False
return j
def get_settings():
s_file = config_filename("settings.json")
j = default_settings()
try:
with open(s_file, "r") as s:
jl = json.load(s)
j.update(jl)
except FileNotFoundError:
pass
return j
def set_settings(data_settings):
s_file = config_filename("settings.json")
j = data_settings
jd = json.dumps(j, indent = 4)
with open(s_file, "w") as outfile:
outfile.write(jd)

View File

@@ -12,6 +12,7 @@ from backend.models import update_model, load_models, get_model_info, list_model
from backend.config import set_config_dir, global_state
from backend.sessions import list_sessions, set_session, get_session, get_default_session_settings, new_session, delete_session
from backend.prompts import list_prompt_formats
from backend.settings import get_settings, set_settings
app = Flask("ExUI")
app.static_folder = 'static'
@@ -167,9 +168,6 @@ def api_update_settings():
print("->", result)
return json.dumps(result) + "\n"
@app.route("/api/user_input", methods=['POST'])
def api_user_input():
global api_lock
@@ -183,10 +181,6 @@ def api_user_input():
print("->", result)
return json.dumps(result) + "\n"
@app.route("/api/list_prompt_formats")
def api_list_prompt_formats():
global api_lock
@@ -196,10 +190,6 @@ def api_list_prompt_formats():
print("->", result)
return json.dumps(result) + "\n"
@app.route("/api/delete_block", methods=['POST'])
def api_delete_block():
global api_lock
@@ -226,7 +216,6 @@ def api_edit_block():
print("->", result)
return json.dumps(result) + "\n"
@app.route("/api/generate", methods=['POST'])
def api_generate():
global api_lock
@@ -240,9 +229,6 @@ def api_generate():
print("->", result)
return result
@app.route("/api/delete_session", methods=['POST'])
def api_delete_session():
global api_lock
@@ -255,7 +241,6 @@ def api_delete_session():
print("->", result)
return json.dumps(result) + "\n"
@app.route("/api/remove_model", methods=['POST'])
def api_remove_model():
global api_lock
@@ -268,6 +253,28 @@ def api_remove_model():
print("->", result)
return json.dumps(result) + "\n"
@app.route("/api/get_settings")
def api_get_settings():
global api_lock
print("/api/get_settings")
with api_lock:
settings = get_settings()
result = { "result": "ok",
"settings": settings }
print("->", result)
return json.dumps(result) + "\n"
@app.route("/api/set_settings", methods=['POST'])
def api_set_settings():
global api_lock
print("/api/set_settings")
with api_lock:
data = request.get_json()
print("<-", data)
set_settings(data["settings"])
result = { "result": "ok" }
print("->", result)
return json.dumps(result) + "\n"
# Prepare torch

View File

@@ -201,6 +201,7 @@
border-radius: 6px;
padding: 6px;
padding-left: 10px;
display: var(--show-stats);
}
.session-block-text p.name-assistant + .session-block-text p {

View File

@@ -293,11 +293,8 @@ class SessionView {
this.stickyScroll = true;
requestAnimationFrame(() => {
requestAnimationFrame(() => {
// smoothscroll = document.getElementById("cb-smooth-scroll").checked;
let smoothscroll = true;
let behavior = smoothscroll ? 'smooth' : 'auto';
let behavior = globals.g.smoothScrolling ? 'smooth' : 'auto';
this.chatHistory.scroll({ top: this.chatHistory.scrollHeight, behavior: behavior });
//this.chatHistory.scroll({ top: this.chatHistory.scrollHeight, behavior: behavior });
});
});
}

View File

@@ -19,6 +19,7 @@ export const g = {
// ..
promptFormats: null,
smoothScrolling: true,
}

View File

@@ -2,191 +2,19 @@ import * as util from "./util.js";
import * as mainmenu from "./mainmenu.js";
import * as models from "./models.js";
import * as chat from "./chat.js";
import * as settings from "./settings.js";
var settingsPopup = new settings.SettingsPopup();
var mainMenu = new mainmenu.MainMenu();
mainMenu.add("models", new mainmenu.MainTab("/static/gfx/icon_model.png", "Models", new models.Models()));
mainMenu.add("chat", new mainmenu.MainTab("/static/gfx/icon_chat.png", "Chat", new chat.Chat()));
mainMenu.addExtra("settings", new mainmenu.MainTab("/static/gfx/icon_settings.png", "Settings", null, () => { settingsPopup.toggle(); }));
mainMenu.setPage("models");
let statsvisible = false;
let smoothscroll = true;
function toggleSmoothScroll() {
smoothscroll = !smoothscroll;
}
function newDiv(className = null, id = null, html = null, parent = null, editFunc = null) {
let nd = document.createElement('div');
if (className) nd.className = className;
if (id) nd.id = id;
if (html) nd.innerHTML = html;
if (parent) parent.appendChild(nd);
if (editFunc) makeDivEditable(nd, editFunc);
return nd;
}
function makeDivEditable(nd, editFunc) {
nd.classList.add("editable");
nd.addEventListener('click', function() { makeEditable(nd, editFunc) });
}
function send(api, packet, ok_func = null, fail_func = null) {
fetch(api, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(packet) })
.then(response => response.json())
.then(json => {
if (json.hasOwnProperty("result")) {
if (ok_func && json.result == "ok") ok_func(json);
if (fail_func && json.result == "fail") fail_func();
}
else {
//console.log(json);
throw new Error("Bad response from server.");
}
})
.catch(error => {
alert("Error: " + error);
console.error('Error:', error);
});
}
function getTextBoxValue(id) {
let d = document.getElementById(id);
if (!d) return d;
//console.log(id, d.value);
return d.value;
}
function getDivValue(id) {
let d = document.getElementById(id);
if (!d) return d;
return d.innerHTML;
}
function getComboBoxValue(id) {
let d = document.getElementById(id);
if (!d) return d;
return d.value;
}
function getCheckboxValue(id) {
let d = document.getElementById(id);
if (!d) return d;
return d.checked;
}
function showSession(sessionID) {
//console.log("sessionID:", sessionID)
if (sessionID != "new") {
currentSessionUUID = sessionID;
let packet = {};
packet.session_uuid = sessionID;
// console.log(packet);
fetch("/api/set_session", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(packet) })
.then(response => response.json())
.then(json => {
//console.log(json);
showSession_(json);
});
} else {
currentSessionUUID = null;
showSession_(null);
}
}
function setChatBlockActions(nd, block)
{
}
function showHideStats() {
statsvisible = document.getElementById("cb-show-stats").checked;
var divs = document.getElementsByClassName("meta");
for(var i = 0; i < divs.length; i++) {
if (statsvisible) divs[i].classList.remove("hidden");
else divs[i].classList.add("hidden");
}
}
function saveFocus() {
let focus = {};
focus.focusedDiv = document.activeElement;
focus.focusedID = null;
focus.selectionStart = -1;
focus.selectionEnd = -1;
if (focus.focusedDiv) {
focus.focusedID = focus.focusedDiv.id;
if (focus.focusedDiv.tagName === 'INPUT' && focus.focusedDiv.type === 'text') {
focus.selectionStart = focus.focusedDiv.selectionStart;
focus.selectionEnd = focus.focusedDiv.selectionEnd;
}
}
// console.log(focus);
return focus;
}
function restoreFocus(focus) {
// console.log("restore:", focus);
if (focus.focusedID && focus.focusedID != "")
{
let focusedElement = document.getElementById(focus.focusedID);
if (focusedElement) {
focusedElement.focus();
if (focus.selectionStart != -1)
{
focusedElement.selectionStart = focus.selectionStart;
focusedElement.selectionEnd = focus.selectionEnd;
}
}
}
}

View File

@@ -1,12 +1,23 @@
.mainmenu-main {
background-color: var(--background-color-menu);
display: flex;
flex-direction: column;
height: calc(100vh);
overflow-y: auto;
}
.mainmenu-spacer {
margin-top: auto;
}
.mainmenu {
background-color: var(--background-color-menu);
display: flex;
flex-direction: column;
width: 80px;
padding: 0px;
overflow-y: auto;
height: calc(100vh);
}
.mainmenu-page {

View File

@@ -5,7 +5,14 @@ export class MainMenu {
let layout = util.newVFlex("grow");
document.body.appendChild(layout);
this.tabs = util.newDiv(null, "mainmenu");
this.tabs = util.newDiv(null, "mainmenu-main");
this.tabsTop = util.newDiv(null, "mainmenu");
this.tabsSpacer = util.newDiv(null, "mainmenu-spacer");
this.tabsBot = util.newDiv(null, "mainmenu");
this.tabs.appendChild(this.tabsTop);
this.tabs.appendChild(this.tabsSpacer);
this.tabs.appendChild(this.tabsBot);
this.pages = util.newDiv(null, "mainmenu-page");
layout.appendChild(this.tabs);
layout.appendChild(this.pages);
@@ -16,7 +23,7 @@ export class MainMenu {
add(tab, control) {
//console.log(tab, control);
this.items.set(tab, control);
this.tabs.appendChild(control.tab);
this.tabsTop.appendChild(control.tab);
if (control.page) this.pages.appendChild(control.page);
control.tab.addEventListener("click", () => {
@@ -26,6 +33,14 @@ export class MainMenu {
return control;
}
addExtra(tab, control) {
//console.log(tab, control);
this.items.set(tab, control);
this.tabsBot.appendChild(control.tab);
return control;
}
setPage(tab) {
this.items.forEach((v, k) => { v.setActive(tab === k); });
this.items.get(tab).child.onEnter();
@@ -33,7 +48,7 @@ export class MainMenu {
}
export class MainTab {
constructor(graphic, text, child) {
constructor(graphic, text, child, clickFunc = null) {
this.tab = document.createElement("div");
this.tab.className = "mainmenu-button inactive";
this.tab.innerHTML = "<img src='" + graphic + "' width='100%' draggable='False'>" +
@@ -43,6 +58,10 @@ export class MainTab {
this.child = child;
this.page = child.page;
}
if (clickFunc) {
this.tab.addEventListener("click", () => { clickFunc() });
}
}
setActive(active) {

View File

@@ -333,7 +333,7 @@ export class ModelView {
this.stats2 = new controls.LabelText("model-view-item-left", "Dimension", "model-view-item-right", this.modelInfo_compiled, "stats2");
this.stats3 = new controls.LabelText("model-view-item-left", "Heads", "model-view-item-right", this.modelInfo_compiled, "stats3");
this.stats4 = new controls.LabelText("model-view-item-left", "Vocab size", "model-view-item-right", this.modelInfo_compiled, "stats4");
this.stats5 = new controls.LabelText("model-view-item-left", "Default seq. length", "model-view-item-right", this.modelInfo_compiled, "stats5");
this.stats5 = new controls.LabelText("model-view-item-left", "Default context length", "model-view-item-right", this.modelInfo_compiled, "stats5");
this.element_model.appendChild(this.stats1.element);
this.element_model.appendChild(this.stats2.element);
this.element_model.appendChild(this.stats3.element);

40
static/settings.css Normal file
View File

@@ -0,0 +1,40 @@
.settings-float {
position: fixed;
bottom: 10px;
left: 90px;
width: 228px;
height: 65px;
background: var(--background-color-view);
border-radius: 10px;
box-shadow: 5px 5px 15px rgba(0,0,0,0.2);
color: white;
padding: 10px;
z-index: 999;
border: 1px solid var(--background-color-view);
}
.settings-float::before {
content: '';
position: absolute;
right: 100%;
top: 30%;
border-width: 15px 22px 15px 0;
border-style: solid;
border-color: transparent var(--background-color-view); transparent transparent;
}
.checkbox-sc {
color: var(--textcolor-menu);
align-items: center;
padding: 0px;
margin: 0px;
padding-top: 5px;
padding-bottom: 5px;
padding-left: 5px;
}
.checkbox-sc:hover {
filter: brightness(140%);
cursor: pointer;
}

67
static/settings.js Normal file
View File

@@ -0,0 +1,67 @@
import * as util from "./util.js";
import * as controls from "./controls.js";
import * as globals from "./globals.js";
export class SettingsPopup {
constructor() {
this.element = util.newDiv(null, "settings-float");
this.element.classList.add("hidden");
document.body.appendChild(this.element);
this.populated = false;
this.loadSettings();
}
toggle() {
if (this.element.classList.contains("hidden")) {
this.element.classList.remove("hidden");
} else {
this.element.classList.add("hidden");
}
}
loadSettings() {
fetch("/api/get_settings")
.then(response => response.json())
.then(response => {
this.settings = response.settings;
this.populate();
});
}
populate() {
if (this.populated) return;
this.s_smoothScrolling = new controls.CheckboxLabel("checkbox-sc noselect", "Smooth scrolling", this.settings, "smooth_scrolling", () => { this.saveSettings(); });
this.s_showStats = new controls.CheckboxLabel("checkbox-sc noselect", "Display stats", this.settings, "show_stats", () => { this.saveSettings(); });
this.element.appendChild(this.s_smoothScrolling.element);
this.element.appendChild(this.s_showStats.element);
this.refresh();
}
refresh() {
this.s_smoothScrolling.refresh();
this.s_showStats.refresh();
}
saveSettings() {
this.applyGlobals();
let packet = {}
packet.settings = this.settings;
fetch("/api/set_settings", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(packet) })
.then(response => response.json())
.then(response => {
});
}
applyGlobals() {
// console.log(this.settings);
globals.g.smoothScrolling = this.settings.smooth_scrolling;
let r = document.querySelector(':root');
r.style.setProperty("--show-stats", this.settings.show_stats ? "flex" : "none");
}
}

View File

@@ -43,6 +43,8 @@
--slider-thumb-color: rgb(135, 150, 155);
--slider-track-color: rgb(77, 77, 77);
--slider-hover-color: rgb(88, 88, 88);
--show-stats: none;
}
body {
@@ -97,23 +99,3 @@ svg {
svg.active {
fill: var(--iconcolor-active);
}
.checkbox-sc {
align-items: center;
padding: 0px;
margin: 0px;
padding-top: 10px;
padding-bottom: 5px;
}
.checkbox-sc:hover {
filter: brightness(130%);
}
.checkbox-sc label {
color: var(--textcolor-head);
cursor: pointer;
user-drag: none;
user-select: none;
padding-left: 2px;
}

View File

@@ -144,3 +144,44 @@ export function makeEditable(div, validateFunc, onValueChange, onExit, in_text =
textarea.focus();
}
/*
function saveFocus() {
let focus = {};
focus.focusedDiv = document.activeElement;
focus.focusedID = null;
focus.selectionStart = -1;
focus.selectionEnd = -1;
if (focus.focusedDiv) {
focus.focusedID = focus.focusedDiv.id;
if (focus.focusedDiv.tagName === 'INPUT' && focus.focusedDiv.type === 'text') {
focus.selectionStart = focus.focusedDiv.selectionStart;
focus.selectionEnd = focus.focusedDiv.selectionEnd;
}
}
// console.log(focus);
return focus;
}
function restoreFocus(focus) {
// console.log("restore:", focus);
if (focus.focusedID && focus.focusedID != "")
{
let focusedElement = document.getElementById(focus.focusedID);
if (focusedElement) {
focusedElement.focus();
if (focus.selectionStart != -1)
{
focusedElement.selectionStart = focus.selectionStart;
focusedElement.selectionEnd = focus.selectionEnd;
}
}
}
}
*/

View File

@@ -7,6 +7,7 @@
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='slider.css') }}">
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='controls.css') }}">
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='mainmenu.css') }}">
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='settings.css') }}">
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='spinner.css') }}">
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='models.css') }}">
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='chat.css') }}">
@@ -22,6 +23,7 @@
<script type="module" src="{{ url_for('static', filename='overlay.js') }}"></script>
<script type="module" src="{{ url_for('static', filename='controls.js') }}"></script>
<script type="module" src="{{ url_for('static', filename='mainmenu.js') }}"></script>
<script type="module" src="{{ url_for('static', filename='settings.js') }}"></script>
<script type="module" src="{{ url_for('static', filename='models.js') }}"></script>
<script type="module" src="{{ url_for('static', filename='chat.js') }}"></script>
<script type="module" src="{{ url_for('static', filename='chatsettings.js') }}"></script>