Notepad initial commit (not functional)

This commit is contained in:
turboderp
2023-11-16 10:58:13 +01:00
parent f1f5c2c45f
commit 29ba6c6faf
13 changed files with 1013 additions and 9 deletions

191
backend/notepads.py Normal file
View File

@@ -0,0 +1,191 @@
import json, uuid, os, gc, glob, time
import torch
from exllamav2 import (
ExLlamaV2,
ExLlamaV2Config,
ExLlamaV2Cache,
ExLlamaV2Cache_8bit,
ExLlamaV2Tokenizer,
)
from exllamav2.generator import (
ExLlamaV2StreamingGenerator,
ExLlamaV2Sampler
)
from backend.config import set_config_dir, global_state, config_filename
from backend.models import get_loaded_model
from backend.prompts import prompt_formats
from backend.util import MultiTimer
notepad_list: dict or None = None
current_notepad = None
def list_notepads():
global notepad_list
if notepad_list is None:
s_pattern = config_filename("notepad_*.json")
s_files = glob.glob(s_pattern)
s_files = sorted(s_files, key = os.path.getctime)
notepad_list = {}
for s_file in s_files:
with open(s_file, "r") as s:
j = json.load(s)
i = j["notepad_uuid"]
n = j["name"]
notepad_list[i] = (n, s_file)
sl = {}
for k, v in notepad_list.items(): sl[k] = v[0]
return sl, current_notepad.notepad_uuid if current_notepad is not None else None
# Notepad
def get_notepad():
global current_notepad
return current_notepad
def set_notepad(data):
global current_notepad
current_notepad = Notepad(data["notepad_uuid"])
current_notepad.load()
result = { "notepad": current_notepad.to_json() }
if get_loaded_model():
result["tokenized_text"] = current_notepad.get_tokenized_text()
return result
def new_notepad():
global current_notepad, notepad_list
current_notepad = Notepad()
current_notepad.init_new()
print(f"Created notepad {current_notepad.notepad_uuid}")
filename = current_notepad.save()
notepad_list[current_notepad.notepad_uuid] = (current_notepad.name, filename)
return current_notepad.to_json()
def delete_notepad(d_notepad):
global current_notepad, notepad_list
if d_notepad in notepad_list:
filename = notepad_list[d_notepad][1]
os.remove(filename)
del notepad_list[d_notepad]
if current_notepad is not None and current_notepad.notepad_uuid == d_notepad:
current_notepad = None
def get_default_notepad_settings():
return \
{
"maxtokens": 1024,
"chunktokens": 512,
"temperature": 0.8,
"top_k": 50,
"top_p": 0.8,
"min_p": 0.0,
"tfs": 0.0,
"mirostat": False,
"mirostat_tau": 1.25,
"mirostat_eta": 0.1,
"typical": 0.0,
"repp": 1.15,
"repr": 1024,
"repd": 512
}
class Notepad:
name: str = None
notepad_uuid: str = None
text = ""
settings: {} = None
def __init__(self, notepad_uuid = None):
self.notepad_uuid = notepad_uuid
def filename(self):
return config_filename("notepad_" + self.notepad_uuid + ".json")
def init_new(self):
self.name = "Unnamed notepad"
self.notepad_uuid = str(uuid.uuid4())
self.text = "Once upon a time,"
self.settings = get_default_notepad_settings()
def to_json(self):
j = {}
j["notepad_uuid"] = self.notepad_uuid
j["name"] = self.name
j["text"] = self.text
j["settings"] = self.settings
return j
def from_json(self, j):
self.name = j["name"]
self.notepad_uuid = j["notepad_uuid"]
self.text = j["text"]
settings = get_default_notepad_settings()
if "settings" in j: settings.update(j["settings"])
self.settings = settings
def rename(self, data):
global notepad_list
if "notepad_uuid" in data:
assert data["notepad_uuid"] == self.notepad_uuid
notepad_list[self.notepad_uuid] = (data["new_name"], notepad_list[self.notepad_uuid][1])
self.name = data["new_name"]
self.save()
def save(self):
print(f"Saving notepad: {self.filename()}")
jd = json.dumps(self.to_json(), indent = 4)
with open(self.filename(), "w") as outfile:
outfile.write(jd)
return self.filename()
def load(self):
print(f"Loading notepad: {self.filename()}")
with open(self.filename(), "r") as s:
j = json.load(s)
self.from_json(j)
def update_settings(self, settings):
self.settings = settings
self.save()
def set_text(self, text):
self.text = text
self.save()
def get_tokenized_text(self):
m = get_loaded_model()
if not m: return None
tokens = m.tokenizer.encode(self.text, encode_special_tokens = True)[0].tolist()
id_to_piece = m.tokenizer.get_id_to_piece_list()
tokenized = []
for token in tokens:
t = {}
t["id"] = token
t["piece"] = m.tokenizer.extended_id_to_piece.get(token, id_to_piece[token])
tokenized.append(t)
return tokenized

103
server.py
View File

@@ -8,9 +8,10 @@ import webbrowser
import torch
from backend.models import update_model, load_models, get_model_info, list_models, remove_model, load_model, unload_model
from backend.models import update_model, load_models, get_model_info, list_models, remove_model, load_model, unload_model, get_loaded_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, set_cancel_signal
from backend.notepads import list_notepads, set_notepad, get_notepad, get_default_notepad_settings, new_notepad, delete_notepad
from backend.prompts import list_prompt_formats
from backend.settings import get_settings, set_settings
@@ -109,7 +110,8 @@ def api_get_default_settings():
if verbose: print("/api/get_default_settings")
with api_lock:
result = { "result": "ok",
"settings": get_default_session_settings(),
"session_settings": get_default_session_settings(),
"notepad_settings": get_default_notepad_settings(),
"prompt_formats": list_prompt_formats() }
return json.dumps(result) + "\n"
@@ -290,6 +292,103 @@ def api_set_settings():
if verbose: print("->", result)
return json.dumps(result) + "\n"
@app.route("/api/list_notepads")
def api_list_notepads():
global api_lock, verbose
if verbose: print("/api/list_notepads")
with api_lock:
n, c = list_notepads()
result = { "result": "ok", "notepads": n, "current_notepad": c }
if verbose: print("-> (...)")
return json.dumps(result) + "\n"
@app.route("/api/set_notepad", methods=['POST'])
def api_set_notepad():
global api_lock, verbose
if verbose: print("/api/set_notepad")
with api_lock:
data = request.get_json()
if verbose: print("<-", data)
r = set_notepad(data)
if r["notepad"] is not None:
result = { "result": "ok",
"notepad": r["notepad"] }
if "tokenized_text" in r:
result["tokenized_text"] = r["tokenized_text"]
if verbose: print("-> (...)")
else:
result = { "result": "fail" }
if verbose: print("->", result)
return json.dumps(result) + "\n"
@app.route("/api/new_notepad", methods=['POST'])
def api_new_notepad():
global api_lock, verbose
if verbose: print("/api/new_notepad")
with api_lock:
data = request.get_json()
if verbose: print("<-", data)
notepad = new_notepad()
if "settings" in data: get_notepad().update_settings(data["settings"])
if "text" in data: get_notepad().set_text(data["text"])
if "new_name" in data: get_notepad().rename(data)
result = { "result": "ok", "notepad": notepad }
if verbose: print("-> (...)")
return json.dumps(result) + "\n"
@app.route("/api/rename_notepad", methods=['POST'])
def api_rename_notepad():
global api_lock, verbose
if verbose: print("/api/rename_notepad")
with api_lock:
data = request.get_json()
if verbose: print("<-", data)
s = get_notepad()
s.rename(data)
result = { "result": "ok" }
if verbose: print("->", result)
return json.dumps(result) + "\n"
@app.route("/api/delete_notepad", methods=['POST'])
def api_delete_notepad():
global api_lock, verbose
if verbose: print("/api/delete_notepad")
with api_lock:
data = request.get_json()
if verbose: print("<-", data)
delete_notepad(data["notepad_uuid"]);
result = { "result": "ok" }
if verbose: print("->", result)
return json.dumps(result) + "\n"
@app.route("/api/update_notepad_settings", methods=['POST'])
def update_notepad_settings():
global api_lock, verbose
if verbose: print("/api/update_notepad_settings")
with api_lock:
n = get_notepad()
data = request.get_json()
if verbose: print("<-", data)
n.update_settings(data["settings"])
result = { "result": "ok" }
if verbose: print("->", result)
return json.dumps(result) + "\n"
@app.route("/api/set_notepad_text", methods=['POST'])
def set_notepad_text():
global api_lock, verbose
if verbose: print("/api/set_notepad_text")
with api_lock:
n = get_notepad()
data = request.get_json()
if verbose: print("<-", data)
n.set_text(data["text"])
tokenized_text = n.get_tokenized_text()
result = { "result": "ok", "tokenized_text": tokenized_text }
if verbose: print("-> (...)")
return json.dumps(result) + "\n"
# Prepare torch
# torch.cuda._lazy_init()

View File

@@ -65,9 +65,9 @@ export class Chat {
});
let label = new controls.EditableLabel(name, false, (new_name) => {
this.currentView.setName(new_name, () => {
this.currentView.setName(new_name, (response) => {
if (sessionID == "new") {
this.lastSessionUUID = this.currentView.sessionID;
this.lastSessionUUID = response.session.session_uuid;
this.onEnter();
} else {
}
@@ -203,7 +203,7 @@ class SessionView {
.then(response => response.json())
.then(response => {
globals.receiveGlobals(response);
this.chatSettings = response.settings;
this.chatSettings = response.session_settings;
this.history = new Map();
this.name = "New session";
this.populate();
@@ -248,13 +248,13 @@ class SessionView {
fetch("/api/new_session", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(packet) })
.then(response => response.json())
.then(response => {
if (post) post();
if (post) post(response);
});
} else {
fetch("/api/rename_session", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(packet) })
.then(response => response.json())
.then(response => {
if (post) post();
if (post) post(response);
});
}
}

8
static/controls.js vendored
View File

@@ -1,5 +1,7 @@
import * as util from "./util.js";
let control_serial = 0;
export class Label {
constructor(className, data, data_id) {
this.data = data;
@@ -72,7 +74,8 @@ export class LabelTextbox {
if (this.cb_auto_id) {
this.chkb = document.createElement("input");
this.chkb.type = "checkbox";
this.chkb.id = "checkbox_" + cb_auto_id;
this.chkb.id = "checkbox_" + control_serial + "_" + cb_auto_id;
control_serial++;
this.chkb.className = "checkbox";
this.element.appendChild(this.chkb);
this.chkb_label = document.createElement("label");
@@ -225,7 +228,8 @@ export class LabelCheckbox {
this.chkb = document.createElement("input");
this.chkb.type = "checkbox";
this.chkb.id = "checkbox_" + data_id;
this.chkb.id = "checkbox_" + control_serial + "_" + data_id;
control_serial++;
this.chkb.className = "checkbox";
this.element.appendChild(this.chkb);
this.chkb_label = document.createElement("label");

BIN
static/gfx/icon_notepad.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -3,12 +3,14 @@ import * as mainmenu from "./mainmenu.js";
import * as models from "./models.js";
import * as chat from "./chat.js";
import * as settings from "./settings.js";
import * as notepad from "./notepad.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.add("notepad", new mainmenu.MainTab("/static/gfx/icon_notepad.png", "Notepad", new notepad.Notepad()));
mainMenu.addExtra("settings", new mainmenu.MainTab("/static/gfx/icon_settings.png", "Settings", null, () => { settingsPopup.toggle(); }));
mainMenu.setPage("models");

194
static/notepad.css Normal file
View File

@@ -0,0 +1,194 @@
.notepad-list {
background-color: var(--background-color-body);
display: flex;
flex-direction: column;
width: 250px;
min-width: 250px;
height: calc(100vh - 56px);
flex-grow: 0;
padding: 0px;
padding-top: 10px;
padding-left: 10px;
padding-right: 10px;
overflow-y: auto;
}
.notepad-list-entry {
display: flex;
flex-direction: row;
font-size: var(--font-size-medium);
color: var(--textcolor-menu);
border-radius: 10px;
user-drag: none;
user-select: none;
}
.notepad-list-entry p {
margin: 4px;
margin-top: 14px;
width: 185px;
overflow-x: auto;
}
.notepad-list-entry svg {
text-shadow: 5px 5px 5px rgba(0, 0, 0, 1.0);
padding: 8px;
margin-left: 5px;
}
.notepad-list-entry.active {
background-color: var(--background-color-active);
color: var(--textcolor-text);
filter: brightness(var(--select-brightness));
}
.notepad-list-entry.active:hover {
text-decoration: underline;
cursor: pointer;
}
.notepad-list-controls {
width: 250px;
min-width: 250px;
padding-left: 15px;
padding-top: 15px;
padding-bottom: 10px;
display: flex;
flex-direction: column;
flex-grow: 1;
justify-content: end;
}
.notepad-view {
background-color: var(--background-color-chat);
display: flex;
flex-direction: column;
height: 100vh;
}
.notepad-settings {
background-color: var(--background-color-chatsettings);
display: flex;
flex-direction: column;
width: 310px;
min-width: 310px;
height: calc(100vh - 10px);
flex-grow: 0;
padding: 0px;
padding-top: 10px;
padding-left: 10px;
padding-right: 10px;
overflow-y: auto;
}
.notepad-editor {
display: flex;
flex-direction: column;
overflow-y: auto;
padding: 20px;
padding-bottom: 0px;
width: calc(100vw - 725px);
color: var(--textcolor-text);
font-size: var(--font-size-notepad);
font-family: var(--font-family-notepad);
line-height: var(--line-height-notepad);
white-space: pre-line;
height: 100%;
}
.notepad-editor:focus {
filter: brightness(110%);
outline: none;
}
.token-view {
width: calc(100vw - 705px);
flex-grow: 1;
border: 1px solid var(--textcolor-dim2);
margin-top: 5px;
margin-bottom: 5px;
margin-left: 20px;
border-radius: 5px;
overflow-y: auto;
flex-shrink: 1;
min-height: 0px;
height: 0px;
}
.token-view-inner {
width: calc(100vw - 725px);
display: flex;
padding: 10px;
flex-wrap: wrap;
line-height: 20px;
}
.notepad-view-divider {
margin-left: 20px;
height: 30px;
cursor: ns-resize;
user-drag: none;
user-select: none;
display: flex;
flex-direction: row;
padding-left: 20px;
padding-right: 20px;
padding-top: 8px;
color: var(--textcolor-head);
font-size: var(--font-size-medium);
background-color: var(--background-color-control);
border-radius: 5px;
}
.notepad-view-divider:hover {
filter: brightness(var(--hover-brightness));
}
.divider-bar {
margin-top: 7px;
margin-bottom: 12px;
margin-left: 10px;
flex-grow: 1;
}
.notepad-token {
border: 1px solid var(--textcolor-dim);
border-radius: 5px;
color: var(--textcolor-text);
padding: 3px;
padding-left: 5px;
padding-right: 5px;
margin-right: 5px;
margin-bottom: 5px;
display: flex;
flex-direction: column;
line-height: 10px;
align-items: center;
}
.notepad-break {
flex-basis: 100%;
height: 0;
}
.notepad-token.color-1 { background-color: var(--token-color-1); }
.notepad-token.color-2 { background-color: var(--token-color-2); }
.notepad-token.color-3 { background-color: var(--token-color-3); }
.notepad-token.color-4 { background-color: var(--token-color-4); }
.notepad-token.color-5 { background-color: var(--token-color-5); }
.notepad-token.id {
font-size: var(--font-size-tiny);
font-weight: bold;
padding: 0px;
border: 0px;
margin-right: 0px;
opacity: 0.55;
}
.notepad-token.piece {
padding: 0px;
border: 0px;
margin-right: 0px;
}

358
static/notepad.js Normal file
View File

@@ -0,0 +1,358 @@
import * as util from "./util.js";
import * as mainmenu from "./mainmenu.js";
import * as globals from "./globals.js";
import * as controls from "./controls.js";
import * as overlay from "./overlay.js";
import * as notepadsettings from "./notepadsettings.js";
import * as roles from "./roles.js";
export class Notepad {
constructor() {
this.page = util.newDiv(null, "models");
let layout = util.newVFlex("grow");
this.page.appendChild(layout);
let layout_l = util.newHFlex();
this.notepadList = util.newDiv(null, "notepad-list");
this.notepadView = util.newDiv(null, "notepad-view");
let panel = util.newDiv(null, "notepad-list-controls");
layout.appendChild(layout_l);
layout_l.appendChild(this.notepadList);
layout_l.appendChild(panel);
layout.appendChild(this.notepadView);
this.removeButton = new controls.LinkButton("✖ Delete notepad", "✖ Confirm", () => { this.deleteNotepad(this.lastNotepadUUID); });
panel.appendChild(this.removeButton.element);
this.lastNotepadUUID = null;
this.items = new Map();
this.labels = new Map();
this.currentView = null;
}
onEnter(getResponse = false) {
fetch("/api/list_notepads")
.then(response => response.json())
.then(response => {
globals.receiveGlobals(response);
this.populateNotepadList(response, getResponse);
});
}
populateNotepadList(response, getResponse = false) {
//console.log(response);
this.notepadList.innerHTML = "";
for (let notepad_uuid in response.notepads)
if (response.notepads.hasOwnProperty(notepad_uuid))
this.addNotepad(response.notepads[notepad_uuid], notepad_uuid);
this.addNotepad("New notepad", "new");
let m = this.lastNotepadUUID ? this.lastNotepadUUID : "new";
this.setNotepad(m, getResponse);
}
addNotepad(name, notepadID) {
let nd = util.newDiv("notepad_" + notepadID, "notepad-list-entry inactive");
nd.addEventListener("click", () => {
if (nd.classList.contains("inactive"))
this.setNotepad(notepadID);
});
let label = new controls.EditableLabel(name, false, (new_name) => {
this.currentView.setName(new_name, (response) => {
if (notepadID == "new") {
this.lastNotepadUUID = response.notepad.notepad_uuid;
this.onEnter();
} else {
}
});
});
let nd2 = util.newIcon(notepadID == "new" ? "notepad-new-icon" : "notepad-icon");
//nd2.classList.add("hidden");
nd.appendChild(nd2);
nd.appendChild(label.element);
this.notepadList.appendChild(nd);
this.items.set(notepadID, nd);
this.labels.set(notepadID, label);
}
setNotepad(notepadID, getResponse = false) {
this.items.forEach((v, k) => {
let div = this.items.get(k);
if (k == notepadID) {
div.classList.add("active");
div.classList.remove("inactive");
} else {
div.classList.remove("active");
div.classList.add("inactive");
}
let label = this.labels.get(k);
label.setEditable(k == notepadID);
});
this.currentView = new NotepadView(notepadID, this);
this.currentView.updateView(getResponse);
this.notepadView.innerHTML = "";
this.notepadView.appendChild(this.currentView.element);
this.lastNotepadUUID = notepadID;
this.removeButton.setEnabled(notepadID && notepadID != "new");
}
deleteNotepad(notepadID) {
let packet = {};
packet.notepad_uuid = this.currentView.notepadID;
util.assert(packet.notepad_uuid == this.lastNotepadUUID);
fetch("/api/delete_notepad", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(packet) })
.then(response => response.json())
.then(response => {
this.lastNotepadUUID = util.getNextKey(this.items, notepadID);
this.onEnter();
});
}
}
class NotepadView {
constructor(notepadID, parent) {
this.notepadID = notepadID;
this.parent = parent;
this.element = util.newVFlex();
this.editorView = util.newDiv("notepad-view", "notepad-view");
this.settingsView = util.newDiv(null, "notepad-settings");
this.element.appendChild(this.editorView);
this.element.appendChild(this.settingsView);
this.editor = document.createElement("div");
this.editor.className = "notepad-editor";
this.editor.contentEditable = "true";
this.editor.spellcheck = false;
this.editor.useRichtext = true;
this.editor.addEventListener("paste", (event) => { this.paste(event); });
this.editor.addEventListener("input", (event) => { this.input(event); });
this.editor.addEventListener("blur", (event) => { this.blur(event); });
this.divider = util.newDiv("notepad-view-divider", "notepad-view-divider");
this.tokenView = util.newDiv("token-view", "token-view");
this.tokenViewInner = util.newDiv("token-view-inner", "token-view-inner");
this.editorView.appendChild(this.editor);
this.editorView.appendChild(this.divider);
this.editorView.appendChild(this.tokenView);
this.tokenView.appendChild(this.tokenViewInner);
this.isResizing = false;
this.divider.addEventListener("mousedown", (event) => { this.isResizing = true; });
document.addEventListener("mouseup", (event) => { this.isResizing = false; });
document.addEventListener("mousemove", (event) => {
if (!this.isResizing) return;
let r = this.editor.getBoundingClientRect();
let r2 = this.element.getBoundingClientRect();
let newHeight = event.clientY - r.top - 35;
let newHeight2 = r2.height - newHeight - 70;
if (newHeight2 < 5) newHeight2 = 0;
this.editor.style.height = "" + newHeight + "px";
this.tokenView.style.height = "" + newHeight2 + "px";
});
this.divider.innerHTML = "<div>🯊 Tokens</div><div class='divider-bar'></div>";
}
updateView(getResponse = false) {
if (!this.notepadID || this.notepadID == "new") {
fetch("/api/get_default_settings")
.then(response => response.json())
.then(response => {
globals.receiveGlobals(response);
this.notepadSettings = response.notepad_settings;
this.name = "New notepad";
this.setText("Once upon a time,");
this.populate();
});
} else {
let packet = {};
packet.notepad_uuid = this.notepadID;
fetch("/api/set_notepad", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(packet) })
.then(response => response.json())
.then(response => {
globals.receiveGlobals(response);
this.notepadSettings = response.notepad.settings;
// this.history = new Map();
// for (const block of response.session.history)
// this.history.set(block.block_uuid, block);
this.name = response.name;
this.populate();
this.setText(response.notepad.text);
if (response.tokenized_text) {
this.updateTokens(response.tokenized_text);
}
});
}
}
populate() {
this.settings = new notepadsettings.NotepadSettings(this);
this.settingsView.innerHTML = "";
this.settingsView.appendChild(this.settings.element);
// this.chatHistory.innerHTML = "";
// for (let block of this.history.values()) this.setChatBlock(block);
// this.scrollToBottom();
// setTimeout(() => {
// this.scrollToBottom();
// }, 200);
}
setName(new_name, post = null) {
let packet = {};
packet.new_name = new_name;
if (!this.notepadID || this.notepadID == "new") {
fetch("/api/new_notepad", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(packet) })
.then(response => response.json())
.then(response => {
if (post) post(response);
});
} else {
fetch("/api/rename_notepad", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(packet) })
.then(response => response.json())
.then(response => {
if (post) post(response);
});
}
}
paste(event) {
event.preventDefault();
let text = (event.clipboardData || window.clipboardData).getData('text');
document.execCommand("insertText", false, text);
// let t = this.getText();
// this.setText(t);
}
setText(text) {
this.editor.innerHTML = "";
let lines = text.split('\n');
for (let i = 0; i < lines.length; i++) {
let div = document.createElement("div");
let t = lines[i];
if (t.startsWith(" ")) t = "\xa0" + t.slice(1);
t = t.replace(/( +) /g, function(match, p1) { return p1.replace(/ /g, "\xa0") + " "; });
div.innerText = t;
if (lines[i] == "") div.innerHTML += "<br>";
this.editor.appendChild(div);
}
}
getTextFrom(element) {
let cs = element.childNodes;
let text = "";
for (var i = 0; i < cs.length; i++) {
let c = cs[i];
if (c.nodeType == Node.TEXT_NODE)
{
text += c.textContent.replace(/\xa0/g, " ");
if (i == cs.length -1) text += "\n";
}
else if (c.tagName == "BR") text += "\n";
else text += this.getTextFrom(c);
}
return text;
}
getText() {
let t = this.getTextFrom(this.editor).slice(0, -1);
return t;
}
input(event) {
if (!this.notepadID || this.notepadID == "new") {
let packet = {};
packet.text = this.editor.innerText;
fetch("/api/new_notepad", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(packet) })
.then(response => response.json())
.then(response => {
this.parent.lastNotepadUUID = response.notepad.notepad_uuid;
this.parent.onEnter();
});
return;
}
if (this.sendTextTimeout) {
clearTimeout(this.sendTextTimeout);
this.sendTextTimeout = null;
}
this.sendTextTimeout = setTimeout(() => { this.sendText(); }, 1000);
}
blur() {
if (this.sendTextTimeout) this.sendText();
}
sendText() {
if (this.parent.lastNotepadUUID != this.notepadID) return;
if (this.sendTextTimeout) {
clearTimeout(this.sendTextTimeout);
this.sendTextTimeout = null;
}
let packet = {};
packet.text = this.getText();
fetch("/api/set_notepad_text", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(packet) })
.then(response => response.json())
.then(response => {
console.log(response);
if (response.tokenized_text) this.updateTokens(response.tokenized_text);
});
}
updateTokens(tokens) {
this.tokenViewInner.innerHTML = "";
let prevHeight = this.tokenView.style.height;
let col = 1;
for (let i = 0; i < tokens.length; i++) {
let token = tokens[i];
let div = document.createElement("div");
div.className = "notepad-token";
div.classList.add("color-" + col);
col++;
if (col > 5) col = 1;
let div1 = document.createElement("span");
div1.className = "notepad-token id";
div1.innerText = "" + token.id;
let div2 = document.createElement("span");
div2.className = "notepad-token piece";
div2.innerText = util.escape(token.piece).replace(" ", "␣");
div.appendChild(div1);
div.appendChild(div2);
this.tokenViewInner.appendChild(div);
if (token.piece.includes("\n")) {
div = document.createElement("div");
div.className = "notepad-break";
this.tokenViewInner.appendChild(div);
col = 1;
}
this.tokenView.style.height = prevHeight;
}
}
}

103
static/notepadsettings.js Normal file
View File

@@ -0,0 +1,103 @@
import * as util from "./util.js";
import * as mainmenu from "./mainmenu.js";
import * as globals from "./globals.js";
import * as controls from "./controls.js";
import * as overlay from "./overlay.js";
export class NotepadSettings {
constructor(parent) {
this.element = util.newHFlex();
this.parent = parent;
this.settings = this.parent.notepadSettings;
//console.log(this.settings);
this.populate();
}
populate() {
this.element.innerHTML = "";
this.sss_genParams = new controls.CollapsibleSection(null, "Generation parameters");
this.sss_sampling = new controls.CollapsibleSection(null, "Sampling");
this.element.appendChild(this.sss_genParams.element);
this.element.appendChild(this.sss_sampling.element);
// Generation params
this.sss_i_maxTokens = new controls.SettingsSlider("sss-item-left", "Max tokens", "sss-item-mid", "sss-item-right sss-item-textbox-r", 0, 16, 2048, null, this.settings, "maxtokens", () => { this.updateView(true); });
this.sss_i_chunkTokens = new controls.SettingsSlider("sss-item-left", "Chunk tokens", "sss-item-mid", "sss-item-right sss-item-textbox-r", 0, 16, 2048, null, this.settings, "chunktokens", () => { this.updateView(true); });
this.sss_genParams.inner.appendChild(this.sss_i_maxTokens.element);
this.sss_genParams.inner.appendChild(this.sss_i_chunkTokens.element);
// Sampling
this.sss_i_temperature = new controls.SettingsSlider("sss-item-left", "Temperature", "sss-item-mid", "sss-item-right sss-item-textbox-r", 2, 0, 3, null, this.settings, "temperature", () => { this.updateView(true); });
this.sss_i_topK = new controls.SettingsSlider("sss-item-left", "Top K", "sss-item-mid", "sss-item-right sss-item-textbox-r", 0, 0, 1000, { "0": "off" }, this.settings, "top_k", () => { this.updateView(true); });
this.sss_i_topP = new controls.SettingsSlider("sss-item-left", "Top P", "sss-item-mid", "sss-item-right sss-item-textbox-r", 2, 0, 1, { "0.00": "off", "1.00": "off" }, this.settings, "top_p", () => { this.updateView(true); });
this.sss_i_minP = new controls.SettingsSlider("sss-item-left", "Min P", "sss-item-mid", "sss-item-right sss-item-textbox-r", 2, 0, 1, { "0.00": "off", "1.00": "off" }, this.settings, "min_p", () => { this.updateView(true); });
this.sss_i_tfs = new controls.SettingsSlider("sss-item-left", "TFS", "sss-item-mid", "sss-item-right sss-item-textbox-r", 2, 0, 1, { "0.00": "off", "1.00": "off" }, this.settings, "tfs", () => { this.updateView(true); });
this.sss_i_typical = new controls.SettingsSlider("sss-item-left", "Typical", "sss-item-mid", "sss-item-right sss-item-textbox-r", 2, 0, 1, { "0.00": "off", "1.00": "off" }, this.settings, "typical", () => { this.updateView(true); });
this.sss_i_repPenalty = new controls.SettingsSlider("sss-item-left", "Rep. penalty", "sss-item-mid", "sss-item-right sss-item-textbox-r", 2, 1, 3, { "1.00": "off" }, this.settings, "repp", () => { this.updateView(true); });
this.sss_i_repRange = new controls.SettingsSlider("sss-item-left", "Rep. range", "sss-item-mid", "sss-item-right sss-item-textbox-r", 0, 0, 4096, { "0": "off" }, this.settings, "repr", () => { this.updateView(true); });
this.sss_i_mirostat = new controls.CheckboxLabel("sss-item-right clickable", "Mirostat", this.settings, "mirostat", () => { this.updateView(true); });
this.sss_i_mirostat_tau = new controls.SettingsSlider("sss-item-left", "Mirostat tau", "sss-item-mid", "sss-item-right sss-item-textbox-r", 2, 0.01, 10, null, this.settings, "mirostat_tau", () => { this.updateView(true); });
this.sss_i_mirostat_eta = new controls.SettingsSlider("sss-item-left", "Mirostat eta", "sss-item-mid", "sss-item-right sss-item-textbox-r", 2, 0.01, 5, null, this.settings, "mirostat_eta", () => { this.updateView(true); });
this.sss_sampling.inner.appendChild(this.sss_i_temperature.element);
this.sss_sampling.inner.appendChild(this.sss_i_topK.element);
this.sss_sampling.inner.appendChild(this.sss_i_topP.element);
this.sss_sampling.inner.appendChild(this.sss_i_minP.element);
this.sss_sampling.inner.appendChild(this.sss_i_tfs.element);
this.sss_sampling.inner.appendChild(this.sss_i_typical.element);
this.sss_sampling.inner.appendChild(this.sss_i_repPenalty.element);
this.sss_sampling.inner.appendChild(this.sss_i_repRange.element);
this.sss_sampling.inner.appendChild(this.sss_i_mirostat.element);
this.sss_sampling.inner.appendChild(this.sss_i_mirostat_tau.element);
this.sss_sampling.inner.appendChild(this.sss_i_mirostat_eta.element);
// .
this.updateView();
}
updateView(send = false) {
// Settings visibility
let mirostat = this.settings.mirostat;
this.sss_i_mirostat_tau.setVisible(mirostat);
this.sss_i_mirostat_eta.setVisible(mirostat);
// Send
if (send) this.send();
//console.log(this.settings);
}
send(post = null) {
console.log(this.settings);
let packet = {};
packet.settings = this.settings;
if (!this.parent.notepadID || this.parent.notepadID == "new") {
fetch("/api/new_notepad", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(packet) })
.then(response => response.json())
.then(response => {
this.parent.parent.lastNotepadUUID = response.notepad.notepad_uuid;
this.parent.parent.onEnter();
if (post) post(response);
});
} else {
fetch("/api/update_notepad_settings", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(packet) })
.then(response => response.json())
.then(response => {
if (post) post(response);
});
}
}
}

View File

@@ -6,6 +6,11 @@
--font-size-chat: 16px;
--font-size-medium: 14px;
--font-size-small: 12px;
--font-size-tiny: 9px;
--font-family-notepad: Consolas, "Andale Mono WT", "Andale Mono", "Lucida Console", "Lucida Sans Typewriter", "DejaVu Sans Mono", "Bitstream Vera Sans Mono", "Liberation Mono", "Nimbus Mono L", Monaco, "Courier New", Courier, monospace;
--font-size-notepad: 16px;
--line-height-notepad: 23px;
--textcolor-menu: rgba(217, 217, 217, 255);
--textcolor-text: rgba(217, 217, 217, 255);
@@ -59,6 +64,12 @@
--hover-brightness: 150%;
--show-stats: none;
--token-color-1: rgb(79, 66, 81);
--token-color-2: rgb(80, 70, 104);
--token-color-3: rgb(83, 59, 75);
--token-color-4: rgb(104, 62, 75);
--token-color-5: rgb(113, 91, 83);
}
/* Dark */
@@ -176,6 +187,12 @@
--checkbox-accent-color: rgba(142, 175, 222, 255);
--checkbox-accent-border: rgba(142, 175, 222, 255);
--token-color-1: rgb(224, 187, 228);
--token-color-2: rgb(199, 176, 255);
--token-color-3: rgb(210, 145, 188);
--token-color-4: rgb(254, 200, 216);
--token-color-5: rgb(255, 223, 211);
}
/* Unholy */
@@ -229,4 +246,10 @@
--checkbox-accent-color: rgba(242, 162, 92, 255);
--checkbox-accent-border: rgba(242, 162, 92, 255);
--token-color-1: rgb(224, 187, 228);
--token-color-2: rgb(199, 176, 255);
--token-color-3: rgb(210, 145, 188);
--token-color-4: rgb(254, 200, 216);
--token-color-5: rgb(255, 223, 211);
}

View File

@@ -185,3 +185,15 @@ function restoreFocus(focus) {
}
*/
export function escape(str) {
return str
.replace(/\\/g, '\\\\')
.replace(/\n/g, '\\n')
.replace(/\r/g, '\\r')
.replace(/\t/g, '\\t')
.replace(/\v/g, '\\v')
// .replace(/\b/g, '\\b')
.replace(/\f/g, '\\f')
// .replace(/"/g, '\\"')
// .replace(/'/g, "\\'");
}

View File

@@ -12,6 +12,7 @@
<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') }}">
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='notepad.css') }}">
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='overlay.css') }}">
<link rel="icon" type="image/png" sizes="32x32" href="{{ url_for('static', filename='gfx/favicon.png') }}">
{% include 'svg_icons.html' %}
@@ -29,6 +30,8 @@
<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>
<script type="module" src="{{ url_for('static', filename='notepad.js') }}"></script>
<script type="module" src="{{ url_for('static', filename='notepadsettings.js') }}"></script>
<script type="module" src="{{ url_for('static', filename='main.js') }}"></script>
</body>
</html>

View File

@@ -56,3 +56,18 @@
</defs>
</svg>
<svg style="display: none;">
<defs>
<symbol id="notepad-new-icon" x="0px" y="0px" width="24" height="24" viewBox="0 0 24 24">
<path d="M 6 2 C 4.9057453 2 4 2.9057453 4 4 L 4 20 C 4 21.094255 4.9057453 22 6 22 L 18 22 C 19.094255 22 20 21.094255 20 20 L 20 8 L 14 2 L 6 2 z M 6 4 L 13 4 L 13 9 L 18 9 L 18 20 L 6 20 L 6 4 z M 8 12 L 8 14 L 16 14 L 16 12 L 8 12 z M 8 16 L 8 18 L 16 18 L 16 16 L 8 16 z"></path>
</symbol>
</defs>
</svg>
<svg style="display: none;">
<defs>
<symbol id="notepad-icon" x="0px" y="0px" width="24" height="24" viewBox="0 0 24 24">
<path d="M14,2H6C4.9,2,4,2.9,4,4v16c0,1.1,0.9,2,2,2h12c1.1,0,2-0.9,2-2V8L14,2z M16,18H8v-2h8V18z M16,14H8v-2h8V14z M13,9V3.5 L18.5,9H13z"></path>
</symbol>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB