diff --git a/backend/notepads.py b/backend/notepads.py
index 882cee3..0bb61a6 100644
--- a/backend/notepads.py
+++ b/backend/notepads.py
@@ -21,6 +21,14 @@ from backend.util import MultiTimer
notepad_list: dict or None = None
current_notepad = None
+# Cancel
+
+cancel_signal = False
+def set_notepad_cancel_signal():
+ global cancel_signal
+ cancel_signal = True
+
+
def list_notepads():
global notepad_list
@@ -84,8 +92,8 @@ def delete_notepad(d_notepad):
def get_default_notepad_settings():
return \
{
- "maxtokens": 1024,
- "chunktokens": 512,
+ "maxtokens": 256,
+ "chunktokens": 64,
"temperature": 0.8,
"top_k": 50,
"top_p": 0.8,
@@ -97,7 +105,8 @@ def get_default_notepad_settings():
"typical": 0.0,
"repp": 1.15,
"repr": 1024,
- "repd": 512
+ "repd": 512,
+ "stop_conditions": [ { "text": "", "inclusive": False } ],
}
@@ -108,8 +117,11 @@ class Notepad:
text = ""
settings: {} = None
+ context_head = 0
+
def __init__(self, notepad_uuid = None):
self.notepad_uuid = notepad_uuid
+ self.context_head = 0
def filename(self):
@@ -153,7 +165,7 @@ class Notepad:
def save(self):
- print(f"Saving notepad: {self.filename()}")
+ # print(f"Saving notepad: {self.filename()}")
jd = json.dumps(self.to_json(), indent = 4)
with open(self.filename(), "w") as outfile:
outfile.write(jd)
@@ -161,7 +173,7 @@ class Notepad:
def load(self):
- print(f"Loading notepad: {self.filename()}")
+ # print(f"Loading notepad: {self.filename()}")
with open(self.filename(), "r") as s:
j = json.load(s)
self.from_json(j)
@@ -188,4 +200,218 @@ class Notepad:
t["id"] = token
t["piece"] = m.tokenizer.extended_id_to_piece.get(token, id_to_piece[token])
tokenized.append(t)
- return tokenized
\ No newline at end of file
+ return tokenized
+
+
+ def get_gen_settings(self):
+
+ gen_settings = ExLlamaV2Sampler.Settings()
+ gen_settings.temperature = self.settings["temperature"]
+ gen_settings.top_k = self.settings["top_k"]
+ gen_settings.top_p = self.settings["top_p"]
+ gen_settings.min_p = self.settings["min_p"]
+ gen_settings.tfs = self.settings["tfs"]
+ gen_settings.typical = self.settings["typical"]
+ gen_settings.mirostat = self.settings["mirostat"]
+ gen_settings.mirostat_tau = self.settings["mirostat_tau"]
+ gen_settings.mirostat_eta = self.settings["mirostat_eta"]
+ gen_settings.token_repetition_penalty = self.settings["repp"]
+ gen_settings.token_repetition_range = self.settings["repr"]
+ gen_settings.token_repetition_decay = self.settings["repr"]
+
+ if gen_settings.temperature == 0:
+ gen_settings.temperature = 1.0
+ gen_settings.top_k = 1
+ gen_settings.top_p = 0
+ gen_settings.typical = 0
+
+ return gen_settings
+
+
+ def generate_single_token(self, data):
+
+ if get_loaded_model() is None:
+ packet = { "result": "fail", "error": "No model loaded." }
+ return packet
+
+ model = get_loaded_model().model
+ generator = get_loaded_model().generator
+ tokenizer = get_loaded_model().tokenizer
+ cache = get_loaded_model().cache
+
+ # Sampling settings
+
+ gen_settings = self.get_gen_settings()
+
+ # Context
+
+ context_str = data["context"]
+ context_post_str = data["context_post"]
+ context_ids = tokenizer.encode(context_str, encode_special_tokens = True)
+
+ # Truncate past
+
+ head_ideal = context_ids.shape[-1] - model.config.max_seq_len
+
+ chunk_size = self.settings["chunktokens"]
+ while head_ideal < self.context_head - chunk_size:
+ self.context_head -= chunk_size
+ if head_ideal > self.context_head: self.context_head = head_ideal + chunk_size - 1
+ if self.context_head < 0: self.context_head = 0
+
+ context_ids = context_ids[:, self.context_head:]
+ # print(head_ideal, self.context_head)
+
+ # Generate
+
+ generator.begin_stream(context_ids, gen_settings, token_healing = True)
+ generator.set_stop_conditions([])
+
+ # Get one token
+
+ chunk, eos, tokens = generator.stream()
+ t = tokens.item()
+ if t in tokenizer.extended_id_to_piece: chunk += tokenizer.extended_id_to_piece[t]
+ self.text = context_str + chunk + context_post_str
+
+ # Save
+
+ self.save()
+
+ # Response
+
+ packet = {}
+ packet["result"] = "ok"
+ packet["text"] = chunk
+ packet["tokenized_text"] = self.get_tokenized_text()
+ return packet
+
+
+ def generate(self, data):
+ global cancel_signal
+
+ if get_loaded_model() is None:
+ packet = { "result": "fail", "error": "No model loaded." }
+ return packet
+
+ model = get_loaded_model().model
+ generator = get_loaded_model().generator
+ tokenizer = get_loaded_model().tokenizer
+ cache = get_loaded_model().cache
+
+ # Sampling settings
+
+ gen_settings = self.get_gen_settings()
+
+ # Context
+
+ context_str = data["context"]
+ context_post_str = data["context_post"]
+ full_context_ids = tokenizer.encode(context_str, encode_special_tokens = True)
+ build_str = ""
+
+ # Stop conditions
+
+ exclusive_sc = []
+ inclusive_sc = []
+ for stop_condition in self.settings["stop_conditions"]:
+ text = stop_condition["text"].encode().decode('unicode_escape')
+ inclusive = stop_condition["inclusive"]
+ if inclusive:
+ inclusive_sc.append(text)
+ else:
+ if stop_condition["text"] in tokenizer.extended_piece_to_id:
+ exclusive_sc.append(tokenizer.extended_piece_to_id[text])
+ else:
+ exclusive_sc.append(text)
+
+ # Truncate past
+
+ head_ideal = full_context_ids.shape[-1] - model.config.max_seq_len
+ chunk_size = self.settings["chunktokens"]
+ while head_ideal < self.context_head - chunk_size:
+ self.context_head -= chunk_size
+
+ # Generator loop
+
+ cancel_signal = False
+
+ total_tokens = 0
+ max_tokens = self.settings["maxtokens"]
+ prev_head = -1
+ token_healing = True
+ while True:
+
+ if cancel_signal:
+ break
+
+ # Adjust context
+
+ head_ideal = full_context_ids.shape[-1] - model.config.max_seq_len
+ if head_ideal > self.context_head: self.context_head = head_ideal + chunk_size - 1
+ if self.context_head < 0: self.context_head = 0
+
+ # Begin stream
+
+ if self.context_head != prev_head:
+ prev_head = self.context_head
+ context_ids = full_context_ids[:, self.context_head:]
+ generator.begin_stream(context_ids, gen_settings, token_healing = token_healing)
+ generator.set_stop_conditions(exclusive_sc)
+ token_healing = False
+
+ # Get single token
+
+ chunk, eos, tokens = generator.stream()
+ for i in range(tokens.shape[-1]):
+ t = tokens[0, i].item()
+ if t in tokenizer.extended_id_to_piece: chunk += tokenizer.extended_id_to_piece[t]
+ build_str += chunk
+ self.text = context_str + build_str + context_post_str
+
+ # Stop conditions
+
+ total_tokens += 1
+ if total_tokens >= max_tokens: eos = True
+ else:
+ for s in inclusive_sc:
+ if s in build_str:
+ extra_chars = len(build_str) - (build_str.find(s) + len(s))
+ if extra_chars > 0:
+ chunk = chunk[:-extra_chars]
+ build_str = build_str[:-extra_chars]
+ eos = True
+ break
+
+ # Stream
+
+ if chunk != "":
+ packet = {}
+ packet["result"] = "stream_chunk"
+ packet["text"] = chunk
+ yield json.dumps(packet) + "\n"
+
+ if eos: break
+
+ # Save
+
+ self.save()
+
+ # Response
+
+ packet = {}
+ if cancel_signal:
+ packet["result"] = "cancel"
+ else:
+ packet["result"] = "ok"
+ packet["tokenized_text"] = self.get_tokenized_text()
+ yield json.dumps(packet) + "\n"
+
+ packet = {}
+ packet["result"] = "ok"
+ return packet
+
+
+
+
+
diff --git a/backend/sessions.py b/backend/sessions.py
index 72d5268..e68cdc4 100644
--- a/backend/sessions.py
+++ b/backend/sessions.py
@@ -162,14 +162,14 @@ class Session:
def load(self):
- print(f"Loading session: {self.filename()}")
+ # print(f"Loading session: {self.filename()}")
with open(self.filename(), "r") as s:
j = json.load(s)
self.from_json(j)
def save(self):
- print(f"Saving session: {self.filename()}")
+ # print(f"Saving session: {self.filename()}")
jd = json.dumps(self.to_json(), indent = 4)
with open(self.filename(), "w") as outfile:
outfile.write(jd)
diff --git a/server.py b/server.py
index 010ddb7..d22c4ee 100644
--- a/server.py
+++ b/server.py
@@ -11,7 +11,7 @@ import torch
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.notepads import list_notepads, set_notepad, get_notepad, get_default_notepad_settings, new_notepad, delete_notepad, set_notepad_cancel_signal
from backend.prompts import list_prompt_formats
from backend.settings import get_settings, set_settings
@@ -239,7 +239,7 @@ def api_generate():
def api_cancel_generate():
global api_lock_cancel, verbose
if verbose: print("/api/cancel_generate")
- with api_lock:
+ with api_lock_cancel:
set_cancel_signal()
result = { "result": "ok" }
if verbose: print("->", result)
@@ -362,7 +362,7 @@ def api_delete_notepad():
return json.dumps(result) + "\n"
@app.route("/api/update_notepad_settings", methods=['POST'])
-def update_notepad_settings():
+def api_update_notepad_settings():
global api_lock, verbose
if verbose: print("/api/update_notepad_settings")
with api_lock:
@@ -375,7 +375,7 @@ def update_notepad_settings():
return json.dumps(result) + "\n"
@app.route("/api/set_notepad_text", methods=['POST'])
-def set_notepad_text():
+def api_set_notepad_text():
global api_lock, verbose
if verbose: print("/api/set_notepad_text")
with api_lock:
@@ -388,6 +388,42 @@ def set_notepad_text():
if verbose: print("-> (...)")
return json.dumps(result) + "\n"
+@app.route("/api/notepad_single_token", methods=['POST'])
+def api_notepad_single_token():
+ global api_lock, verbose
+ if verbose: print("/api/notepad_single_token")
+ with api_lock:
+ n = get_notepad()
+ data = request.get_json()
+ if verbose: print("<-", data)
+ result = n.generate_single_token(data)
+ if verbose: print("-> (...)")
+ return json.dumps(result) + "\n"
+
+
+@app.route("/api/notepad_generate", methods=['POST'])
+def api_notepad_generate():
+ global api_lock, verbose
+ if verbose: print("/api/notepad_generate")
+ with api_lock:
+ data = request.get_json()
+ if verbose: print("<-", data)
+ n = get_notepad()
+ if verbose: print("-> ...");
+ result = Response(stream_with_context(n.generate(data)), mimetype = 'application/json')
+ if verbose: print("->", result)
+ return result
+
+@app.route("/api/cancel_notepad_generate")
+def api_cancel_notepad_generate():
+ global api_lock_cancel, verbose
+ if verbose: print("/api/cancel_notepad_generate")
+ with api_lock_cancel:
+ set_notepad_cancel_signal()
+ result = { "result": "ok" }
+ if verbose: print("->", result)
+ return result
+
# Prepare torch
diff --git a/static/chat.css b/static/chat.css
index ae584c5..b171085 100644
--- a/static/chat.css
+++ b/static/chat.css
@@ -95,7 +95,9 @@
align-items: center;
display: flex;
width: 60px;
+ height: 25px;
justify-content: center;
+ padding-top: 8px;
}
.session-input {
@@ -329,6 +331,7 @@
min-width: 90px;
align-items: center;
display: flex;
+ height: 28px;
}
.sss-item-sep {
diff --git a/static/controls.js b/static/controls.js
index 2d9b3f1..f3cc770 100644
--- a/static/controls.js
+++ b/static/controls.js
@@ -142,6 +142,106 @@ export class LabelTextboxButton extends LabelTextbox {
}
}
+export class CheckboxTextboxButton {
+ constructor(nameid, classNameCheckbox, textCheckbox, classNameTextbox, placeholder, data, arrayName, index, data_id_text, data_id_cb, validateFunc, updateFunc, buttonText, buttonFunc) {
+
+ this.data = data;
+ this.data_id_text = data_id_text;
+ this.data_id_cb = data_id_cb;
+ this.arrayName = arrayName;
+ this.index = index;
+ this.element = util.newVFlex("vflex_line");
+
+ // Checkbox
+
+ this.cb = util.newDiv(null, classNameCheckbox);
+
+ this.chkb = document.createElement("input");
+ this.chkb.type = "checkbox";
+ this.chkb.id = "checkbox_" + control_serial + "_" + nameid;
+ control_serial++;
+ this.chkb.className = "checkbox";
+
+ this.chkb_label = document.createElement("label");
+ this.chkb_label.htmlFor = this.chkb.id;
+ this.chkb_label.className = "checkbox-label";
+ this.chkb_label.innerHTML = textCheckbox;
+
+ this.cb.appendChild(this.chkb);
+ this.cb.appendChild(this.chkb_label);
+ this.element.appendChild(this.cb);
+
+ this.chkb.addEventListener("change", () => {
+ this.data[this.arrayName][this.index][this.data_id_cb] = this.chkb.checked;
+ if (updateFunc) updateFunc();
+ });
+
+ // Textbox
+
+ this.tb = document.createElement("input");
+ this.tb.className = classNameTextbox;
+ this.tb.type = "text";
+
+ this.tb.addEventListener("focus", () => {
+ this.textbox_initial = this.tb.value;
+ });
+
+ this.tb.addEventListener("focusout", () => {
+ if (this.textbox_initial != this.tb.value) {
+ if (!validateFunc || validateFunc(this.tb.value)) {
+ this.data[this.arrayName][this.index][this.data_id_text] = this.interpret(this.tb.value);
+ if (updateFunc) updateFunc();
+ } else {
+ this.tb.value = this.textbox_initial;
+ }
+ }
+ });
+
+ this.tb.addEventListener("keydown", (event) => {
+ if (event.key === "Escape") {
+ this.tb.value = this.textbox_initial;
+ this.tb.blur();
+ event.preventDefault();
+ }
+ if (event.key === "Enter" && !event.shiftKey) {
+ this.tb.blur();
+ }
+ });
+
+ this.element.appendChild(this.tb);
+
+ // Button
+
+ this.buttonFunc = buttonFunc;
+
+ this.button = document.createElement("span");
+ this.button.className = "linkbutton enabled";
+ this.button.innerHTML = buttonText;
+
+ this.button.addEventListener("click", () => {
+ if (this.buttonFunc) this.buttonFunc();
+ });
+
+ this.element.appendChild(this.button);
+
+ this.refresh();
+ }
+
+ interpret(value) {
+ return value;
+ }
+
+ refresh() {
+ //console.log(this.data);
+ this.chkb.checked = this.data[this.arrayName][this.index][this.data_id_cb];
+
+ let v = this.data[this.arrayName][this.index][this.data_id_text];
+ v = v ? v : null;
+ this.tb.value = v;
+ }
+}
+
+
export class LabelNumbox extends LabelTextbox {
constructor(classNameLabel, textLabel, className, placeholder, data, data_id, min, max, decimals, updateFunc, cb_auto_id = null) {
super(classNameLabel, textLabel, className, placeholder, data, data_id, null, updateFunc, cb_auto_id);
@@ -259,22 +359,38 @@ export class LabelCheckbox {
}
export class Button {
- constructor(text, clickFunc, extra_style = null) {
- this.element = util.newDiv(null, "textbutton", text);
+ constructor(text, clickFunc, extra_style = null, extra_text = null) {
+ if (!extra_text) {
+ this.element = util.newDiv(null, "textbutton", text);
+ } else {
+ this.element = util.newDiv(null, "textbutton");
+ let div1 = util.newDiv(null, null, text);
+ let div2 = util.newDiv(null, "sub", extra_text);
+ this.element.appendChild(div1);
+ this.element.appendChild(div2);
+ }
+
if (extra_style) this.element.classList.add(extra_style);
this.enabled = true;
this.clickFunc = clickFunc;
this.hidden = false;
- this.element.addEventListener("click", () => {
+ this.element.addEventListener("mousedown", (event) => {
+ event.preventDefault();
+ });
+
+ this.element.addEventListener("click", (event) => {
if (!this.enabled) return;
- if (this.clickFunc) this.clickFunc();
+ if (this.clickFunc) {
+ event.preventDefault();
+ this.clickFunc();
+ }
});
}
- setEnabled(enabled) {
+ setEnabled(enabled, delay = 0) {
this.enabled = enabled;
- this.refresh();
+ this.refresh(delay);
}
setHidden(hidden) {
@@ -282,13 +398,26 @@ export class Button {
this.refresh();
}
- refresh() {
+ setVisible(visible) {
+ this.setHidden(!visible);
+ }
+
+ refresh(delay = 0) {
if (this.enabled) {
+ if (this.disablerTimeout) {
+ clearTimeout(this.disablerTimeout);
+ this.disablerTimeout = null;
+ }
this.element.classList.add("enabled");
this.element.classList.remove("disabled");
} else {
- this.element.classList.remove("enabled");
- this.element.classList.add("disabled");
+ if (!this.disablerTimeout) {
+ this.disablerTimeout = setTimeout(() => {
+ this.element.classList.remove("enabled");
+ this.element.classList.add("disabled");
+ this.disabledTimeout = null;
+ }, delay);
+ }
}
if (this.hidden) {
this.element.classList.add("hidden");
diff --git a/static/notepad.css b/static/notepad.css
index 60e716f..265523e 100644
--- a/static/notepad.css
+++ b/static/notepad.css
@@ -83,22 +83,22 @@
}
.notepad-editor {
- display: flex;
- flex-direction: column;
- overflow-y: auto;
- padding: 20px;
+ padding: 10px;
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%;
+ border: 0px;
+ margin-left: 20px;
+ background-color: var(--background-color-chat);
+ resize: none;
+ min-height: 40px;
}
.notepad-editor:focus {
- filter: brightness(110%);
outline: none;
}
@@ -116,8 +116,12 @@
height: 0px;
}
+.token-view:focus {
+ outline: none;
+}
+
.token-view-inner {
- width: calc(100vw - 725px);
+ width: calc(100vw - 730px);
display: flex;
padding: 10px;
flex-wrap: wrap;
@@ -191,4 +195,40 @@
padding: 0px;
border: 0px;
margin-right: 0px;
+ text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
+}
+
+.notepad-bar {
+ height: 60px;
+ min-height: 60px;
+ max-height: 60px;
+ display: flex;
+ justify-content: right;
+ padding-top: 5px;
+}
+
+.notepad-generate-button {
+ margin-top: auto;
+ margin-right: 0px;
+ margin-bottom: 10px;
+ margin-left: 10px;
+ padding-left: 12px;
+ align-items: center;
+ display: flex;
+ flex-direction: column;
+ width: 85px;
+ justify-content: center;
+ user-drag: none;
+ user-select: none;
+ line-height: 15px;
+ color: var(--textcolor-text);
+}
+
+.notepad-generate-button .sub {
+ font-size: var(--font-size-tiny);
+}
+
+.notepad-generate-button:hover {
+ filter: brightness(var(--hover-brightness));
+ cursor: pointer;
}
diff --git a/static/notepad.js b/static/notepad.js
index 59b54f1..56eb4de 100644
--- a/static/notepad.js
+++ b/static/notepad.js
@@ -32,12 +32,13 @@ export class Notepad {
this.currentView = null;
}
- onEnter(getResponse = false) {
+ onEnter(getResponse = false, post = null) {
fetch("/api/list_notepads")
.then(response => response.json())
.then(response => {
globals.receiveGlobals(response);
this.populateNotepadList(response, getResponse);
+ if (post) post();
});
}
@@ -131,20 +132,40 @@ class NotepadView {
this.element.appendChild(this.editorView);
this.element.appendChild(this.settingsView);
- this.editor = document.createElement("div");
+ this.editor = document.createElement("textarea");
this.editor.className = "notepad-editor";
- this.editor.contentEditable = "true";
this.editor.spellcheck = false;
- this.editor.useRichtext = true;
+ //this.editor.contentEditable = "true";
+ //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.editor.addEventListener("keydown", (event) => { this.keydown(event); });
+
+ document.addEventListener("keydown", (event) => { this.keydownView(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.controlBar = util.newDiv("notepad-bar", "notepad-bar");
+
+ this.generateTokenButton = new controls.Button("⏵ Token", () => { this.generateToken() }, "notepad-generate-button", "ctrl + enter");
+ this.generateButton = new controls.Button("⯮ Generate", () => { this.generate() }, "notepad-generate-button", "shift + enter");
+ this.cancelButton = new controls.Button("⏹ Stop", () => { this.cancelGenerate() }, "notepad-generate-button", "escape");
+ if (!notepadID || notepadID == "new" || !globals.g.loadedModelUUID) {
+ this.generateTokenButton.setEnabled(false);
+ this.generateButton.setEnabled(false);
+ this.cancelButton.setEnabled(false);
+ }
+ this.cancelButton.setEnabled(false);
+ this.cancelButton.setVisible(false);
+
+ this.controlBar.appendChild(this.generateTokenButton.element);
+ this.controlBar.appendChild(this.generateButton.element);
+ this.controlBar.appendChild(this.cancelButton.element);
this.editorView.appendChild(this.editor);
+ this.editorView.appendChild(this.controlBar);
this.editorView.appendChild(this.divider);
this.editorView.appendChild(this.tokenView);
this.tokenView.appendChild(this.tokenViewInner);
@@ -154,11 +175,11 @@ class NotepadView {
document.addEventListener("mouseup", (event) => { this.isResizing = false; });
document.addEventListener("mousemove", (event) => {
if (!this.isResizing) return;
- let r = this.editor.getBoundingClientRect();
+ //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;
+ let newHeight = event.clientY - 95;
+ let newHeight2 = r2.height - newHeight - 165;
+ //if (newHeight2 < 5) newHeight2 = 0;
this.editor.style.height = "" + newHeight + "px";
this.tokenView.style.height = "" + newHeight2 + "px";
});
@@ -240,17 +261,19 @@ class NotepadView {
}
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 += "
";
- this.editor.appendChild(div);
- }
+ this.editor.value = text;
+ this.restoreCursor();
+// 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 += "
";
+// this.editor.appendChild(div);
+// }
}
getTextFrom(element) {
@@ -270,19 +293,37 @@ class NotepadView {
}
getText() {
- let t = this.getTextFrom(this.editor).slice(0, -1);
- return t;
+ //let t = this.getTextFrom(this.editor).slice(0, -1);
+ //return t;
+ return this.editor.value;
+ }
+
+ saveCursor() {
+ this.parent.selectionStart = this.editor.selectionStart;
+ this.parent.selectionEnd = this.editor.selectionEnd;
+ //console.log("save");
+ }
+
+ restoreCursor() {
+ if (this.parent.selectionStart) {
+ console.log("restore");
+ this.editor.setSelectionRange(this.parent.selectionStart, this.parent.selectionEnd);
+ this.editor.focus();
+ this.parent.selectionStart = null;
+ this.parent.selectionEnd = null;
+ }
}
input(event) {
if (!this.notepadID || this.notepadID == "new") {
+ this.saveCursor();
let packet = {};
- packet.text = this.editor.innerText;
+ packet.text = this.getText();
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();
+ this.parent.onEnter(false);
});
return;
}
@@ -310,7 +351,7 @@ class NotepadView {
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);
+ //console.log(response);
if (response.tokenized_text) this.updateTokens(response.tokenized_text);
});
}
@@ -351,6 +392,199 @@ class NotepadView {
this.tokenView.style.height = prevHeight;
}
}
+
+ keydown(event) {
+ if (event.key === "Enter" && event.shiftKey) {
+ event.preventDefault();
+ if (this.generateButton.enabled)
+ this.generate();
+ }
+ if (event.key === "Enter" && event.ctrlKey) {
+ event.preventDefault();
+ if (this.generateTokenButton.enabled)
+ this.generateToken();
+ }
+ }
+
+ keydownView(event) {
+ if (event.key === "Escape") {
+ if (this.cancelButton.enabled) {
+ this.cancelGenerate();
+ }
+ }
+ }
+
+ generateToken() {
+ if (!globals.g.loadedModelUUID) return;
+
+ this.generateButton.setEnabled(false, 200);
+ this.generateTokenButton.setEnabled(false, 200);
+ this.editor.disabled = true;
+
+ let pos = this.editor.selectionStart;
+ let end = this.editor.selectionEnd;
+ let text = this.editor.value;
+
+ if (end > pos) {
+ this.editor.value = this.editor.value.slice(0, pos) + this.editor.value.slice(end);
+ }
+
+ let packet = {};
+ packet.position = pos;
+ packet.context = this.editor.value.slice(0, pos);
+ packet.context_post = this.editor.value.slice(pos);
+
+ fetch("/api/notepad_single_token", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(packet) })
+ .then(response => response.json())
+ .then(response => {
+ if (response.text)
+ {
+// console.log(pos, response.text.length);
+ this.editor.value = packet.context + response.text + packet.context_post;
+ this.editor.setSelectionRange(pos + response.text.length, pos + response.text.length);
+ this.updateTokens(response.tokenized_text);
+ this.editor.disabled = false;
+ this.editor.focus();
+ }
+ this.generateButton.setEnabled(true);
+ this.generateTokenButton.setEnabled(true);
+ this.editor.disabled = false;
+ });
+ }
+
+ generate() {
+ if (!globals.g.loadedModelUUID) return;
+
+ this.generateButton.setEnabled(false);
+ this.generateButton.setVisible(false);
+ this.cancelButton.setEnabled(true);
+ this.cancelButton.setVisible(true);
+ this.generateTokenButton.setEnabled(false);
+ this.editor.disabled = true;
+
+ let pos = this.editor.selectionStart;
+ let end = this.editor.selectionEnd;
+ let text = this.editor.value;
+
+ if (end > pos) {
+ this.editor.value = this.editor.value.slice(0, pos) + this.editor.value.slice(end);
+ }
+
+ this.receiveStreamPos = pos;
+
+ let packet = {};
+ packet.position = pos;
+ packet.context = this.editor.value.slice(0, pos);
+ packet.context_post = this.editor.value.slice(pos);
+
+ let timeout = new Promise((resolve, reject) => {
+ let id = setTimeout(() => {
+ clearTimeout(id);
+ reject('No response from server')
+ }, 10000)
+ });
+
+ let fetchRequest = fetch("/api/notepad_generate", {
+ method: "POST",
+ headers: { "Content-Type": "application/json", },
+ body: JSON.stringify(packet)
+ });
+
+ const self = this;
+ Promise.race([fetchRequest, timeout])
+ .then(response => {
+ if (response.ok) {
+ return response.body;
+ } else {
+ throw new Error("Network response was not ok.");
+ }
+ })
+ .then(stream => {
+ let reader = stream.getReader();
+ let decoder = new TextDecoder();
+ let data = '';
+ reader.read().then(function process({done, value}) {
+ // console.log("Received chunk:", decoder.decode(value));
+ if (done) {
+ //console.log("DONE");
+ self.generateButton.setEnabled(true);
+ self.generateButton.setVisible(true);
+ self.generateTokenButton.setEnabled(true);
+ self.cancelButton.setEnabled(false);
+ self.cancelButton.setVisible(false);
+ self.editor.disabled = false;
+ self.editor.focus();
+ return;
+ }
+ data += decoder.decode(value, {stream: true});
+ let lines = data.split('\n');
+ for (let i = 0; i < lines.length - 1; i++) {
+ let json = null;
+ try {
+ json = JSON.parse(lines[i]);
+ } catch(e) {
+ console.error("Invalid JSON:", lines[i]);
+ break;
+ }
+ if (json.result == "fail") {
+ console.error('Error:', json.error);
+ self.generateButton.setEnabled(true);
+ self.generateButton.setVisible(true);
+ self.generateTokenButton.setEnabled(true);
+ self.cancelButton.setEnabled(false);
+ self.cancelButton.setVisible(false);
+ self.editor.disabled = false;
+ self.editor.focus();
+ return;
+ } else {
+ self.receivedStreamResponse(json);
+ }
+ }
+ data = lines[lines.length - 1];
+ reader.read().then(process);
+ });
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ self.enableInput();
+ self.focusInputField();
+ })
+ }
+
+ receivedStreamResponse(response) {
+ //console.log(response);
+
+ if (response.result == "stream_chunk") {
+ let chunk = response.text;
+ let pos = this.receiveStreamPos;
+ this.editor.value = this.editor.value.slice(0, pos) + chunk + this.editor.value.slice(pos);
+ pos += chunk.length;
+ this.editor.setSelectionRange(pos, pos);
+ this.receiveStreamPos = pos;
+ }
+
+ if (response.result == "ok") {
+ if (response.tokenized_text) {
+ this.updateTokens(response.tokenized_text);
+ }
+ }
+
+ if (response.result == "cancel") {
+ if (response.tokenized_text) {
+ this.updateTokens(response.tokenized_text);
+ }
+ }
+ }
+
+ cancelGenerate() {
+ this.cancelButton.setEnabled(false);
+ //console.log("cancel");
+ fetch("/api/cancel_notepad_generate")
+ .then(response => response.json())
+ .then(response => {
+ });
+ }
+
}
diff --git a/static/notepadsettings.js b/static/notepadsettings.js
index b7e9826..5accd6d 100644
--- a/static/notepadsettings.js
+++ b/static/notepadsettings.js
@@ -26,8 +26,10 @@ export class NotepadSettings {
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_stopConditions = new controls.CollapsibleSection(null, "Stop conditions");
this.sss_genParams.inner.appendChild(this.sss_i_maxTokens.element);
this.sss_genParams.inner.appendChild(this.sss_i_chunkTokens.element);
+ this.element.appendChild(this.sss_stopConditions.element);
// Sampling
@@ -57,11 +59,53 @@ export class NotepadSettings {
this.sss_sampling.inner.appendChild(this.sss_i_mirostat_tau.element);
this.sss_sampling.inner.appendChild(this.sss_i_mirostat_eta.element);
- // .
+ // Stop conditions
+
+ this.populate_stop_conditions();
this.updateView();
}
+ populate_stop_conditions() {
+ this.sss_stopConditions.inner.innerHTML = "";
+ this.sss_i_stopconditions = [];
+
+ for (let i = 0; i < this.settings.stop_conditions.length; i++) {
+ this.sss_i_stopconditions[i] = new controls.CheckboxTextboxButton(
+ "stop_condition_" + i,
+ "sss-item-left",
+ "Incl.",
+ "sss-item-mid sss-item-textbox",
+ "",
+ this.settings,
+ "stop_conditions",
+ i,
+ "text",
+ "inclusive",
+ (v) => { return v != ""; },
+ () => { this.updateView(true); },
+ "✕ Remove",
+ () => {
+ this.settings.stop_conditions.splice(i, 1);
+ this.populate_stop_conditions();
+ this.updateView(true);
+ }
+ );
+ }
+
+ for (let i = 0; i < this.settings.stop_conditions.length; i++)
+ this.sss_stopConditions.inner.appendChild(this.sss_i_stopconditions[i].element);
+
+ if (this.settings.stop_conditions.length < 10) {
+ this.sss_i_addStopCondition = new controls.LinkButton("+ Add...", null, () => {
+ this.settings.stop_conditions.push({text: "", inclusive: false});
+ this.populate_stop_conditions();
+ this.updateView(true);
+ }, "sss-item-link");
+ this.sss_stopConditions.inner.appendChild(this.sss_i_addStopCondition.element);
+ }
+ }
+
updateView(send = false) {
// Settings visibility
@@ -77,7 +121,7 @@ export class NotepadSettings {
}
send(post = null) {
- console.log(this.settings);
+ //console.log(this.settings);
let packet = {};
packet.settings = this.settings;
diff --git a/static/theme.css b/static/theme.css
index 10bf677..9ec5466 100644
--- a/static/theme.css
+++ b/static/theme.css
@@ -6,7 +6,7 @@
--font-size-chat: 16px;
--font-size-medium: 14px;
--font-size-small: 12px;
- --font-size-tiny: 9px;
+ --font-size-tiny: 10px;
--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;
@@ -130,8 +130,12 @@
--background-color-code: rgba(23, 26, 28, 255);
--border-color-code: rgba(44, 47, 52, 255);
--textcolor-code: rgba(200, 217, 208, 255);
+
+ --checkbox-accent-color: rgba(84, 90, 118, 255);
+ --checkbox-accent-border: rgba(84, 90, 118, 255);
}
+
/* Light */
.theme-light {