Files
exui/static/controls.js
2023-10-30 22:46:33 -05:00

1938 lines
60 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const noop = () => {};
createUIElements();
let currentModelUUID = null;
let loadedModelUUID = null;
let failedModelUUID = null;
let failedModelError = null;
let currentSessionUUID = null;
let currentSettings = null;
let currentStreamingBlock = null;
let keyboard_disabled = false;
document.addEventListener("keydown", function (e) {
if (keyboard_disabled) e.preventDefault();
});
document.addEventListener('DOMContentLoaded', () => {
const textarea = document.getElementById('session-input');
textarea.addEventListener('input', autoGrow);
textarea.addEventListener('keydown', function(event) {
if (event.key === 'Enter' && !event.shiftKey) {
event.preventDefault();
submitInput();
}
});
function autoGrow() {
textarea.style.height = 'auto';
textarea.style.height = (textarea.scrollHeight) + 'px';
}
});
let textbox_initial = "";
let roleAvatars = [
"/static/gfx/avatar_frog.png",
"/static/gfx/avatar_cat.png",
"/static/gfx/avatar_dog.png",
"/static/gfx/avatar_monke.png",
"/static/gfx/avatar_unicorn.png",
"/static/gfx/avatar_squirrel.png",
"/static/gfx/avatar_penguin.png",
"/static/gfx/avatar_notcat.png"
];
let roleColors = [
"rgba(167, 231, 0, 255)",
"rgba(136, 70, 104, 255)",
"rgba(58, 90, 208, 255)",
"rgba(215, 86, 98, 255)",
"rgba(220, 170, 62, 255)",
"rgba(40, 170, 170, 255)",
"rgba(170, 170, 170, 255)",
"rgba(120, 120, 120, 255)"
];
let fallbackAvatar = "/static/gfx/avatar_notcat.png";
let fallbackColor = "rgba(120, 120, 120, 255)";
let instructAvatars = [
"/static/gfx/avatar_frog.png",
"/static/gfx/avatar_cat.png",
];
let instructColors = [
"rgba(167, 231, 0, 255)",
"rgba(136, 70, 104, 255)",
];
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 getTextWithLineBreaks(node) {
let text = "";
for (let child of node.childNodes) {
if (child.nodeType === Node.TEXT_NODE) {
text += child.textContent.trim();
} else if (child.tagName === "BR") {
text += "\n";
}
}
return text;
}
function addTextboxEvents(ctb, onValueChange, multiline = false, onExit = null)
{
ctb.addEventListener("focus", function() {
textbox_initial = this.value;
});
ctb.addEventListener("focusout", function() {
if (textbox_initial !== this.value) {
//console.log(this.id, this.value);
onValueChange(this.id, this.value);
}
if (onExit) {
onExit(this.id);
}
});
ctb.addEventListener("keydown", function(event) {
if (event.key === "Escape") {
this.value = textbox_initial;
this.blur();
event.preventDefault();
}
if (event.key === "Enter" && !event.shiftKey) {
event.preventDefault();
this.blur();
}
if (event.key == "Enter" && !multiline)
{
this.blur();
event.preventDefault();
}
});
}
function makeEditable(div, onValueChange, onExit, in_text = null, multiline = false) {
let dstyle = window.getComputedStyle(div);
let text = in_text;
if (!in_text) {
text = getTextWithLineBreaks(div);
}
let rect = div.getBoundingClientRect();
let computedStyle = window.getComputedStyle(div);
let textarea = document.createElement("textarea");
textbox_initial = text;
addTextboxEvents(textarea, onValueChange, multiline, onExit);
textarea.style.top = `${rect.top}px`;
textarea.style.left = `${rect.left}px`;
textarea.style.width = `${rect.width}px`;
textarea.style.height = `${rect.height}px`;
textarea.style.border = "0px";
textarea.style.outline = computedStyle.outline;
textarea.style.padding = computedStyle.padding;
textarea.style.margin = computedStyle.margin;
textarea.style.resize = computedStyle.resize;
textarea.style.fontFamily = computedStyle.fontFamily;
textarea.style.fontSize = computedStyle.fontSize;
textarea.style.fontWeight = computedStyle.fontWeight;
textarea.style.color = computedStyle.color;
textarea.style.backgroundColor = computedStyle.backgroundColor;
textarea.style.borderRadius = computedStyle.borderRadius;
textarea.style.boxSizing = 'border-box';
textarea.style.verticalAlign = computedStyle.verticalAlign;
textarea.style.lineHeight = computedStyle.lineHeight;
textarea.style.textAlign = computedStyle.textAlign;
textarea.style.padding = computedStyle.padding;
textarea.style.minHeight = `${rect.height}px`;
textarea.rows = 1;
textarea.value = text;
textarea.addEventListener('blur', function () {
div.innerHTML = textarea.value;
div.style.display = "";
textarea.parentNode.removeChild(textarea);
});
div.parentNode.insertBefore(textarea, div.nextSibling);
div.style.display = "none";
textarea.focus();
}
function newButton(html = null, parent = null, style = null, clickFunc = null, enabled = true) {
let nd = newDiv("textbutton", null, html, parent);
if (style) nd.classList.add(style);
if (enabled) nd.classList.add("enabled");
else nd.classList.add("disabled");
nd.addEventListener('click', clickFunc);
return nd;
}
function createMainMenuButton(id, text, graphic, tabName, enterFunc) {
let menu = document.getElementById('mainmenu');
let html = "<img src='" + graphic + "' width='100%' draggable='False'>";
html += "<p>" + text + "</p>";
var nd = newDiv("mainmenu-button inactive", id, html, menu);
nd.dataset.tabName = tabName;
document.getElementById(tabName).classList.add('hidden');
nd.addEventListener('click', function() {
let menu = document.getElementById('mainmenu');
for (let i = 0; i < menu.children.length; i++) {
let c = menu.children[i];
if (c.nodeName === 'DIV') {
var page = c.dataset.tabName;
if (c == nd) {
c.classList.add("active");
c.classList.remove("inactive");
document.getElementById(page).classList.remove('hidden');
} else {
c.classList.add("inactive");
c.classList.remove("active");
document.getElementById(page).classList.add('hidden');
}
}
}
enterFunc();
});
return nd;
}
function addIcon(div, icon_id, size = 24) {
var ns = "http://www.w3.org/2000/svg";
var svg = document.createElementNS(ns, "svg");
svg.setAttributeNS(null, "width", size);
svg.setAttributeNS(null, "height", size);
svg.style.top = "8px";
var use = document.createElementNS(ns, "use");
use.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', "#" + icon_id);
svg.appendChild(use);
div.style.position = "relative";
div.appendChild(svg);
return svg;
}
function addModel(name, modelID) {
let model_list = document.getElementById('model-list');
var nd = newDiv("model-list-entry inactive", "model_" + modelID, null, model_list);
if (modelID == "new") {
addIcon(nd, "model-plus-icon");
} else if (modelID == loadedModelUUID) {
let svg = addIcon(nd, "model-loaded-icon");
svg.classList.add("active");
} else {
addIcon(nd, "model-icon");
}
var ndi = newDiv(null, null, "<p>" + name + "</p>", nd);
nd.dataset.modelID = modelID;
nd.addEventListener('click', function() {
model_list = document.getElementById('model-list');
for (let i = 0; i < model_list.children.length; i++) {
let c = model_list.children[i];
if (c.nodeName === 'DIV') {
if (c == nd) {
c.classList.add("active");
c.classList.remove("inactive");
} else {
c.classList.add("inactive");
c.classList.remove("active");
}
}
}
showModel(nd.dataset.modelID);
});
}
function populateModelList(model_names) {
model_list = document.getElementById('model-list');
model_list.innerHTML = "";
for (let model_uuid in model_names) {
if (model_names.hasOwnProperty(model_uuid)) {
// console.log("----");
// console.log(model_uuid, model_names[model_uuid]);
addModel(model_names[model_uuid], model_uuid);
}
}
addModel("New model", "new");
let def = document.getElementById(currentModelUUID ? "model_" + currentModelUUID : "model_new");
//console.log(def);
//console.log(currentModelUUID);
def.click();
}
function enterModelPage() {
fetch("/api/list_models")
.then(response => response.json())
.then(json => {
//console.log(json);
if (json.current_model) loadedModelUUID = json.current_model;
if (!currentModelUUID && json.current_model) currentModelUUID = json.current_model;
populateModelList(json.models);
});
}
function addModelItem(left, right, style = null)
{
let model_view = document.getElementById('model-view');
var nd = newDiv("model-view-item", null, null, model_view);
var ndl = newDiv("model-view-item-left", null, left, nd);
var ndr = newDiv("model-view-item-right" + (style ? (" " + style) : ""), null, right, nd);
return ndr;
}
function addModelItem_textbox(left, id = null, text = null, editFunc = null, default_text = null, extra_style = null, append = null, prepend = null)
{
let model_view = document.getElementById('model-view');
var ndr;
if (!append && !prepend)
ndr = addModelItem(left, null);
else
ndr = append ? append : prepend;
var tb = document.createElement('input');
tb.className = "textbox";
tb.type = 'text';
if (text) tb.value = text;
if (id) tb.id = id;
if (default_text) tb.placeholder = default_text;
if (extra_style) tb.classList.add(extra_style);
if (editFunc) {
tb.addEventListener("focus", function() {
textbox_initial = this.value;
});
tb.addEventListener("focusout", function() {
if (textbox_initial !== this.value) {
editFunc();
}
});
tb.addEventListener("keydown", function(event) {
if (event.key === "Escape") {
this.value = textbox_initial;
this.blur();
event.preventDefault();
}
if (event.key === "Enter" && !event.shiftKey) {
this.blur();
}
// if (event.key == "Enter" && !multiline)
// {
// this.blur();
// event.preventDefault();
// }
});
}
if (prepend)
ndr.prepend(tb);
else
ndr.appendChild(tb);
return ndr;
}
function addModelItem_checkbox(left, id = null, text = null, state = null, editFunc = null, extra_style = null)
{
let model_view = document.getElementById('model-view');
//console.log("state", state);
var ndr = addModelItem(left, null);
var ndri = document.createElement('div');
ndri.className = "checkbox";
if (extra_style) ndri.classList.add(extra_style);
var cb = document.createElement('input');
cb.type = 'checkbox';
//cb.className = "checkbox";
cb.checked = state;
if (id) cb.id = id;
var label = document.createElement('label');
if (id) label.htmlFor = id;
//label.className = "checkbox"
label.appendChild(document.createTextNode(text));
if (editFunc) cb.addEventListener("change", function(event) { editFunc() } );
ndri.appendChild(cb);
ndri.appendChild(label);
ndr.appendChild(ndri);
return ndr;
}
function addModelItem_combo(left, id = null, options = null, selected = null, editFunc = null, extra_style = null ) {
let model_view = document.getElementById('model-view');
var ndr = addModelItem(left, null);
var cb = document.createElement('select');
cb.className = "combobox";
if (id) cb.id = id;
if (extra_style) cb.classList.add(extra_style);
for (let i = 0; i < options.length; i++) cb.add(new Option(options[i], options[i]));
cb.value = selected;
if (editFunc) cb.addEventListener("change", function(event) { editFunc() } );
ndr.appendChild(cb);
return ndr;
}
function showModel_(model_info) {
//console.log(model_info);
let focus = saveFocus();
let model_view = document.getElementById('model-view');
model_view.innerHTML = "";
// Name
var model_name = model_info.name;
var nd = newDiv("vflex", null, "", model_view);
newDiv("model-view-text heading", "div-model-name", model_name, nd, sendCurrentModelView);
if (model_info.model_uuid != "new")
newButton("✖ Remove", nd, "small", removeCurrentModel);
let def = document.getElementById("model_" + model_info.model_uuid);
def.children[1].innerHTML = "<p>" + model_name + "</p>";
newDiv("model-view-text divider", null, "", model_view);
newDiv("model-view-text spacer", null, "", model_view);
// Directory
var model_dir = model_info["model_directory"] || null;
nd = addModelItem_textbox("Model path", "tb-model-view-directory", model_dir, sendCurrentModelView, "~/models/my_model/", "wide");
if (!model_dir || model_dir == "") return;
newDiv("model-view-text spacer", null, "", model_view);
if (model_info["config_status"] != "ok") {
nd = addModelItem("", model_info["config_status_error"], "error");
return;
}
// Stats
var stats = model_info["stats"];
addModelItem("Layers", stats["num_hidden_layers"]);
addModelItem("Dimension", stats["hidden_size"] + " (" + stats["intermediate_size"] + ")");
var heads_q = stats["num_attention_heads"];
var heads_kv = stats["num_key_value_heads"];
var head_dim = stats["head_dim"];
if (heads_q == heads_kv) {
addModelItem("Heads", heads_q + ", dim: " + head_dim);
} else {
addModelItem("Heads", "Q: " + heads_q + ", K/V: " + heads_kv + " (GQA), dim: " + head_dim);
}
addModelItem("Vocab size", stats["vocab_size"]);
// Context
// newDiv("model-view-text spacer", null, "", model_view);
// newDiv("model-view-text divider", null, "", model_view);
newDiv("model-view-text spacer", null, "", model_view);
nd = addModelItem_textbox("Context length", "tb-model-seq-len", "" + model_info.seq_len, sendCurrentModelView, null, "shortright");
newDiv("model-view-text extra", null, "(native: " + model_info.default_seq_len + ")", nd);
addModelItem_textbox("RoPE scale", "tb-model-rope-scale", model_info.rope_scale.toFixed(2), sendCurrentModelView, null, "shortright");
addModelItem_textbox("RoPE alpha", "tb-model-rope-alpha", model_info.rope_alpha.toFixed(2), sendCurrentModelView, null, "shortright");
addModelItem_combo("Cache mode", "cb-model-cache-mode", [ "FP16", "FP8" ], model_info.cache_mode, sendCurrentModelView, "short");
addModelItem_textbox("Chunk size", "tb-model-chunk-size", "" + model_info.chunk_size, sendCurrentModelView, null, "shortright");
nd = addModelItem_checkbox("GPU split", "cb-model-gpu-split-auto", "Auto", model_info.gpu_split_auto, sendCurrentModelView, "short");
if (!model_info.gpu_split_auto) addModelItem_textbox("GPU split", "tb-model-gpu-split", "" + model_info.gpu_split, sendCurrentModelView, null, "shortright", null, nd);
// Draft model
newDiv("model-view-text spacer", null, "", model_view);
newDiv("model-view-text divider", null, "", model_view);
newDiv("model-view-text spacer", null, "", model_view);
addModelItem_checkbox("Speculative decoding", "cb-model-spec-dec", "Enabled", model_info.draft_enabled, sendCurrentModelView, "short");
if (model_info.draft_enabled) {
var draft_model_dir = model_info["draft_model_directory"] || null;
addModelItem_textbox("Draft model path", "tb-model-view-draft-directory", draft_model_dir, sendCurrentModelView, "~/models/my_model/", "wide");
if (!draft_model_dir || draft_model_dir == "") return;
if (model_info["draft_config_status"] != "ok") {
nd = addModelItem("", model_info["draft_config_status_error"], "error");
return;
}
newDiv("model-view-text spacer", null, "", model_view);
// Draft model stats
var stats = model_info["draft_stats"];
addModelItem("Layers", stats["num_hidden_layers"]);
addModelItem("Dimension", stats["hidden_size"] + " (" + stats["intermediate_size"] + ")");
var heads_q = stats["num_attention_heads"];
var heads_kv = stats["num_key_value_heads"];
var head_dim = stats["head_dim"];
if (heads_q == heads_kv) {
addModelItem("Heads", heads_q + ", dim: " + head_dim);
} else {
addModelItem("Heads", "Q: " + heads_q + ", K/V: " + heads_kv + " (GQA), dim: " + head_dim);
}
addModelItem("Vocab size", stats["vocab_size"]);
// Draft options
newDiv("model-view-text spacer", null, "", model_view);
if (draft_model_dir && draft_model_dir != "") {
nd = addModelItem_checkbox("RoPE alpha", "cb-model-draft-rope-alpha-auto", "Auto", model_info.draft_rope_alpha_auto, sendCurrentModelView, "short");
if (!model_info.draft_rope_alpha_auto) addModelItem_textbox("RoPE alpha", "tb-model-draft-rope-alpha", (model_info.draft_rope_alpha || 1.0).toFixed(2), sendCurrentModelView, null, "shortright", null, nd);
}
}
// Load
newDiv("model-view-text spacer", null, "", model_view);
newDiv("model-view-text divider", null, "", model_view);
newDiv("model-view-text spacer", null, "", model_view);
newDiv("model-view-text spacer", null, "", model_view);
nd = newDiv("vflex", null, "", model_view);
button_load = newButton("⏵ Load model", nd, null, loadCurrentModel, (model_info.model_uuid != loadedModelUUID));
button_unload = newButton("⏹ Unload model", nd, null, unloadCurrentModel, (model_info.model_uuid == loadedModelUUID));
newDiv("model-view-text spacer", null, "", model_view);
// Error while loading
if (model_info.model_uuid == failedModelUUID) {
newDiv("model-view-text divider", null, "", model_view);
newDiv("model-view-text spacer", null, "", model_view);
newDiv("model-view-text spacer", null, "", model_view);
addModelItem("Error", failedModelError, "error");
}
// Restore focus
restoreFocus(focus);
}
function showModel(modelID) {
if (modelID == "new")
currentModelUUID = null;
else
currentModelUUID = modelID;
let packet = {};
packet.model_uuid = modelID;
if (modelID == "new") {
let model_info = {};
model_info.model_uuid = "new";
model_info.name = "New model";
showModel_(model_info);
} else {
// console.log(packet);
fetch("/api/get_model_info", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(packet) })
.then(response => response.json())
.then(json => {
//console.log(json);
showModel_(json.model_info);
});
}
}
function createUIElements() {
let def = createMainMenuButton("mainmenu-model", "Model", "/static/gfx/icon_model.png", "model-page", enterModelPage );
createMainMenuButton("mainmenu-chat", "Chat", "/static/gfx/icon_chat.png", "chat-page", enterChatPage );
def.click();
}
function disablePage(mode) {
let page_disabled = document.getElementById('page-disabled');
let busy = document.getElementById('busy');
let loading = document.getElementById('loading');
if (mode == "busy") busy.style.display = 'flex'; else busy.style.display = 'none';
if (mode == "loading") loading.style.display = 'flex'; else loading.style.display = 'none';
page_disabled.style.display = 'flex';
keyboard_disabled = true;
}
function enablePage() {
let page_disabled = document.getElementById('page-disabled');
page_disabled.style.display = 'none';
keyboard_disabled = false;
}
function setLoadingProgress(a, b) {
var progressBar = document.getElementById('loading-progress');
percentage = 100 * (a / b);
progressBar.style.width = percentage + '%';
//progressBar.innerHTML = percentage + '%';
}
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 newModel(json) {
let uuid = json.new_model_uuid;
if (uuid)
{
//console.log("new model", uuid);
let model_list = document.getElementById('model-list');
let model_new = document.getElementById('model_new');
model_new.id = "model_" + uuid;
model_new.innerHTML = "";
addIcon(model_new, "model-icon")
newDiv(null, null, "<p></p>", model_new);
model_new.dataset.modelID = uuid;
addModel("New model", "new");
showModel(uuid);
} else {
showModel(currentModelUUID);
}
}
function numerical(text, int, min, max) {
let num = 0;
if (int) num = parseInt(text, 10);
else num = parseFloat(text);
if (isNaN(num)) return num;
if (num < min) return min;
if (num > max) return max;
return num;
}
function sendCurrentModelView() {
let packet = {};
packet.model_uuid = currentModelUUID;
packet.name = getDivValue("div-model-name");
if (packet.name == null || packet.name == "") packet.name = "Unnamed model";
packet.model_directory = getTextBoxValue("tb-model-view-directory");
let seq_len = numerical(getTextBoxValue("tb-model-seq-len"), true, 16, 1024*1024);
let rope_scale = numerical(getTextBoxValue("tb-model-rope-scale"), false, 0.01, 1024);
let rope_alpha = numerical(getTextBoxValue("tb-model-rope-alpha"), false, 0.01, 1024);
if (!isNaN(seq_len)) packet.seq_len = seq_len;
if (!isNaN(rope_scale)) packet.rope_scale = rope_scale;
if (!isNaN(rope_alpha)) packet.rope_alpha = rope_alpha;
let cache_mode = getComboBoxValue("cb-model-cache-mode");
let chunk_size = numerical(getTextBoxValue("tb-model-chunk-size"), true, 128, 65536);
let gpu_split_auto = getCheckboxValue("cb-model-gpu-split-auto");
let gpu_split = getTextBoxValue("tb-model-gpu-split");
if (cache_mode) packet.cache_mode = cache_mode;
if (chunk_size) packet.chunk_size = chunk_size;
if (gpu_split_auto != null) packet.gpu_split_auto = gpu_split_auto;
if (gpu_split != null) packet.gpu_split = gpu_split;
let draft_enabled = getCheckboxValue("cb-model-spec-dec");
packet.draft_enabled = draft_enabled;
if (draft_enabled) {
packet.draft_model_directory = getTextBoxValue("tb-model-view-draft-directory");
let draft_rope_alpha = numerical(getTextBoxValue("tb-model-draft-rope-alpha"), false, 0.01, 1024);
let draft_rope_alpha_auto = getCheckboxValue("cb-model-draft-rope-alpha-auto");
if (draft_rope_alpha_auto != null) packet.draft_rope_alpha_auto = draft_rope_alpha_auto;
if (!isNaN(rope_alpha)) packet.draft_rope_alpha = draft_rope_alpha;
}
//console.log(gpu_split_auto);
console.log(packet);
send("/api/update_model", packet, newModel);
}
function removeCurrentModel() {
let packet = {};
packet.model_uuid = currentModelUUID;
currentModelUUID = null;
send("/api/remove_model", packet, enterModelPage);
}
function unloadCurrentModel() {
disablePage("busy");
fetch("/api/unload_model")
.then(response => response.json())
.then(json => {
if (json.result == "ok") {
loadedModelUUID = null;
failedModelUUID = null;
}
enablePage();
enterModelPage();
});
}
function loadCurrentModel() {
setLoadingProgress(0, 1);
disablePage("loading");
let packet = {};
packet.model_uuid = currentModelUUID;
let timeout = new Promise((resolve, reject) => {
let id = setTimeout(() => {
clearTimeout(id);
reject('No response from server')
}, 10000)
});
let fetchRequest = fetch("/api/load_model", {
method: "POST",
headers: { "Content-Type": "application/json", },
body: JSON.stringify(packet)
});
Promise.race([fetchRequest, timeout])
.then(response => {
if (response.ok) {
return response.body;
} else {
//appendErrorMessage("Network response was not ok");
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) {
enablePage();
enterModelPage();
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 == "ok") {
loadedModelUUID = packet.model_uuid;
failedModelUUID = null;
} else if (json.result == "progress") {
setLoadingProgress(json.module, json.num_modules);
//console.log(json);
} else {
loadedModelUUID = null;
failedModelUUID = packet.model_uuid;
failedModelError = json.error;
console.log(json);
}
}
data = lines[lines.length - 1];
reader.read().then(process);
});
})
.catch(error => {
loadedModelUUID = null;
failedModelUUID = packet.model_uuid;
failedModelError = "" + error;
console.error('Error:', error);
enablePage();
enterModelPage();
});
}
function enterChatPage(subsFunc = null) {
fetch("/api/list_sessions")
.then(response => response.json())
.then(json => {
//console.log(json);
if (!currentSessionUUID && json.current_session) currentSessionUUID = json.current_session;
populateSessionList(json.sessions);
if (subsFunc) subsFunc();
});
}
function populateSessionList(session_names) {
session_list = document.getElementById('session-list');
session_list.innerHTML = "";
for (let session_uuid in session_names) {
if (session_names.hasOwnProperty(session_uuid)) {
addSession(session_names[session_uuid], session_uuid);
}
}
addSession("New session", "new");
let def = document.getElementById(currentSessionUUID ? "session_" + currentSessionUUID : "session_new");
def.click();
}
function addSession(name, sessionID) {
let session_list = document.getElementById('session-list');
var nd = newDiv("session-list-entry inactive", "session_" + sessionID, null, session_list);
if (sessionID == "new") {
addIcon(nd, "session-new-icon");
// } else if (sessionID == loadedSessionUUID) {
// let svg = addIcon(nd, "model-loaded-icon");
// svg.classList.add("active");
} else {
addIcon(nd, "session-icon");
}
var ndi = newDiv(null, null, "<p>" + name + "</p>", nd);
nd.dataset.sessionID = sessionID;
nd.addEventListener('click', function(event) {
let session_list = document.getElementById('session-list');
let refresh = false;
for (let i = 0; i < session_list.children.length; i++) {
let c = session_list.children[i];
if (c.nodeName === 'DIV') {
if (c == nd) {
if (c.classList.contains("active")) {
renameSession(c.dataset.sessionID);
} else {
refresh = true;
c.classList.add("active");
c.classList.remove("inactive");
}
} else {
c.classList.add("inactive");
c.classList.remove("active");
}
}
}
if (refresh) showSession(nd.dataset.sessionID);
});
}
let lockEdit = false;
function renameSession(sessionID) {
if (lockEdit) return;
lockEdit = true;
let div = document.getElementById("session_" + sessionID);
div = div.children[1].children[0];
let prev = div.innerHTML;
makeEditable(div, function(id, val) {
if (val.trim() != "")
{
if (sessionID == "new")
{
fetch("/api/get_default_settings")
.then(response => response.json())
.then(json => {
let packet = {};
packet.settings = json.settings;
packet.new_name = val;
fetch("/api/new_session", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(packet) })
.then(response => response.json())
.then(json => {
currentSessionUUID = json.session.session_uuid;
enterChatPage(function() { } );
});
});
} else {
let packet = {};
packet.session_uuid = sessionID;
packet.new_name = val;
send("/api/rename_session", packet);
}
} else {
div.innerHTML = prev;
}
}, function() {
lockEdit = false;
});
}
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 showSession_(json) {
// console.log("xxxxxxxxxxx", json);
let session_view = document.getElementById("session-view-history");
session_view.innerHTML = "";
let settings = null;
if (json) {
settings = json.session.settings;
currentSettings = settings;
prompt_formats = json.prompt_formats;
}
if (json)
{
let history = json.session.history;
for (let i = 0; i < history.length; i++) {
setChatBlock(history[i]);
}
}
if (settings) {
populateSessionSettings(settings, prompt_formats);
} else {
fetch("/api/get_default_settings")
.then(response => response.json())
.then(json => {
populateSessionSettings(json.settings, json.prompt_formats);
});
}
document.getElementById('session-input').value = "";
document.getElementById('session-input').focus();
scrollToBottom();
}
function setChatBlock(block, busy_anim = false) {
let uuid = block.block_uuid;
let nd = document.getElementById('chat_block_' + uuid);
if (!nd)
{
let session_view = document.getElementById("session-view-history");
nd = document.createElement('div');
nd.id = 'chat_block_' + uuid;
nd.className = "session-block";
session_view.appendChild(nd);
let ndl = newDiv("vflex", null, null, nd, null);
let nda = newDiv("avatar-img", null, null, ndl, null);
let ndt = newDiv("session-block-text", null, null, ndl, null);
nda.setAttribute("data-label", "avatar-img");
ndt.setAttribute("data-label", "text");
}
setChatBlockAvatarImg(nd, block);
setChatBlockText(nd, block, busy_anim);
setChatBlockActions(nd, block);
setChatBlockMeta(nd, block);
if (block.author == "user")
nd.classList.add("user");
}
function setChatBlockMeta(nd, block)
{
let textdiv = nd.querySelector('.session-block-text');
if (Object.keys(block).includes("meta") && block.meta) {
if (block.meta.overflow > 0) {
let p = document.createElement('p');
p.classList.add("error");
p.innerHTML = "‼ Response exceeded " + block.meta.overflow + " tokens and was cut short.";
textdiv.appendChild(p);
}
let p = document.createElement('p');
p.classList.add("meta");
if (!statsvisible) p.classList.add("hidden");
let ptps = block.meta.prompt_speed.toFixed(2)
if (block.meta.prompt_speed > 50000) ptps = "∞";
let html = "prompt: " + block.meta.prompt_tokens.toFixed(0) + " tokens, " + ptps + " tokens/s";
html += "";
html += "response: " + block.meta.gen_tokens.toFixed(0) + " tokens, " + block.meta.gen_speed.toFixed(2) + " tokens/s";
p.innerHTML = html;
textdiv.appendChild(p);
}
}
function setChatBlockActions(nd, block)
{
let uuid = block.block_uuid;
let actdiv = document.createElement('div');
actdiv.classList.add("block-actions");
actdiv.classList.add("no-select");
let span = document.createElement("span");
span.classList.add("action");
span.innerHTML = "✕ Delete";
actdiv.appendChild(span);
span.addEventListener('click', function() {
let packet = {};
packet.block_uuid = uuid;
send("/api/delete_block", packet, function() {
let d = document.getElementById('chat_block_' + uuid);
d.remove();
document.getElementById('session-input').focus();
});
});
span = document.createElement("span");
span.classList.add("action");
span.innerHTML = "🖉 Edit";
actdiv.appendChild(span);
span.addEventListener('click', function() {
//console.log(nd.dataset.rawtext);
actdiv.parentNode.classList.add("hidden_h");
let textdiv = nd.querySelector('.session-block-text');
makeEditable(textdiv, function(id, val) {
// console.log("edit");
let new_block = { ...block };
new_block.text = val.trim();
new_block.meta = null;
let packet = {};
packet.block = new_block;
send("/api/edit_block", packet, function() {
new_block = block;
new_block.text = val;
new_block.meta = null;
setChatBlockText(nd, new_block);
setChatBlockMeta(nd, new_block);
setChatBlockAvatarImg(nd, new_block);
actdiv.parentNode.classList.remove("hidden_h");
document.getElementById('session-input').focus();
});
}, function(id) {
// console.log("not edit");
setChatBlockText(nd, block);
setChatBlockMeta(nd, block);
actdiv.parentNode.classList.remove("hidden_h");
document.getElementById('session-input').focus();
}, in_text = nd.dataset.rawtext, multiline = true);
});
//↻
nd.appendChild(actdiv);
}
function getRoleID(block)
{
if (!currentSettings) return -1;
let t = block.text.toUpperCase();
for (let i = 0; i < 8; i++)
{
if (t.startsWith(currentSettings.roles[i].toUpperCase() + ":")) return i;
}
return -1;
}
function setChatBlockAvatarImg(nd, block)
{
let graphic = fallbackAvatar;
if (currentSettings.prompt_format == "Chat-RP")
{
let ri = getRoleID(block);
if (ri != -1) graphic = roleAvatars[ri];
} else {
if (block.author == "user") graphic = instructAvatars[0];
if (block.author == "assistant") graphic = instructAvatars[1];
}
let nda = nd.querySelector('div[data-label="avatar-img"]');
nda.innerHTML = "<img src='" + graphic + "' width='64px' draggable='False'>";
}
function setChatBlockText(nd, block, busy_anim = false)
{
let nda = nd.querySelector('div[data-label="text"]');
let name = null;
let col = fallbackColor;
let text = block.text.trimEnd();
if (currentSettings.prompt_format == "Chat-RP")
{
let ri = getRoleID(block);
if (ri != -1) {
col = roleColors[ri];
name = currentSettings.roles[ri];
text = text.slice(name.length + 1).trimStart();
}
} else {
if (block.author == "user") { col = instructColors[0]; name = "User"; }
if (block.author == "assistant") { col = instructColors[1]; name = "Assistant"; }
}
let html = "";
if (name) html += "<div class='name' style='color: " + col + "'>" + name + "</div>"
if (busy_anim) {
// html += "<div class='lds-ellipsis'><div></div><div></div><div></div><div></div></div>"
}
html += marked.parse(text);
nda.innerHTML = html;
nd.dataset.rawtext = block.text.trimEnd();
//nd.dataset.rawblock = JSON.stringify(block);
}
function addSessionSettingsSection(heading, id = null) {
let div = document.createElement('div');
div.className = "sss-block";
if (id) div.id = id;
let div_head = document.createElement('div');
div_head.className = "sss-block-header no-select";
div_head.innerHTML = "<span class='arrow'>⯆</span>" + heading;
div.appendChild(div_head);
div_head.addEventListener('click', function() {
let c = this.nextElementSibling;
let a = this.children[0];
if (c.classList.contains("shown")) {
a.innerHTML = "⯈";
c.classList.remove("shown");
} else {
a.innerHTML = "⯆";
c.classList.add("shown");
}
});
let div_contents = document.createElement('div');
div_contents.className = "sss-block-contents shown";
div.appendChild(div_contents);
let nd = document.getElementById("session-settings");
nd.appendChild(div);
return div_contents;
}
function addSessionSettings_combo(parent, left, id = null, options = null, selected = null, editFunc = null) {
let div = document.createElement('div');
div.className = "sss-item-split";
let h = document.createElement('div');
h.className = "sss-item-left";
h.innerHTML = left;
let cb = document.createElement('select');
cb.className = "sss-item-right sss-item-combobox";
if (id) cb.id = id;
for (let i = 0; i < options.length; i++) cb.add(new Option(options[i], options[i]));
cb.value = selected;
if (editFunc) cb.addEventListener("change", function(event) { editFunc(event) } );
div.appendChild(h);
div.appendChild(cb);
parent.appendChild(div);
return cb;
}
function addSessionSettings_sep(parent) {
let div = document.createElement('div');
div.className = "sss-item-sep";
parent.appendChild(div);
}
function addSessionSettings_textbox(parent, left, id = null, value = null, editFunc = null, rows = 1, right = null, rightFunc = null) {
let div = document.createElement('div');
div.className = "sss-item-split";
let h = document.createElement('div');
h.className = "sss-item-left";
h.innerHTML = left;
let tb = document.createElement('input');
tb.type= "text";
if (right) tb.className = "sss-item-mid sss-item-textbox";
else tb.className = "sss-item-right sss-item-textbox";
tb.rows = rows;
tb.value = value;
tb.spellcheck = false;
if (id) tb.id = id;
if (editFunc) tb.addEventListener("change", function(event) { editFunc(event) } );
let r = null;
if (right) {
r = document.createElement('div');
r.className = "sss-item-right";
r.innerHTML = right;
if (rightFunc) {
r.classList.add("clickable");
r.addEventListener("click", function(event) { rightFunc(event) } );
}
}
div.appendChild(h);
div.appendChild(tb);
if (right) div.appendChild(r);
parent.appendChild(div);
return tb;
}
function addSessionSettings_bigTextbox(parent, id = null, value = null, editFunc = null) {
let tb = document.createElement('textarea');
tb.className = "sss-item-big-textbox";
tb.rows = 6;
tb.value = value;
if (id) tb.id = id;
if (editFunc) tb.addEventListener("change", function(event) { editFunc(event) } );
parent.appendChild(tb);
return tb;
}
function addSessionSettings_link(parent, text, id = null, rightFunc = null) {
let div = document.createElement('div');
div.className = "sss-item-split";
let r = document.createElement('div');
r.className = "sss-item-left";
r.innerHTML = text;
if (id != null) r.id = id;
if (rightFunc) {
r.classList.add("clickable");
r.addEventListener("click", function(event) { rightFunc(event) } );
}
div.appendChild(r);
parent.appendChild(div);
}
function addSessionSettings_slider(parent, id, left, minimum, maximum, is_float, editFunc, special = null) {
let div = document.createElement('div');
div.className = "sss-item-split";
let h = document.createElement('div');
h.className = "sss-item-left";
h.innerHTML = left;
let slider = document.createElement('input');
slider.type= "range";
slider.className = "sss-item-mid";
slider.min = minimum * (is_float ? 100 : 1);
slider.max = maximum * (is_float ? 100 : 1);
slider.value = slider.min;
if (id) slider.id = id;
let tb = document.createElement('input');
tb.type= "text";
tb.className = "sss-item-right sss-item-textbox-r";
tb.rows = 1;
tb.value = is_float ? "0.0" : "0";
tb.spellcheck = false;
if (id) tb.id = id + "_tb";
if (is_float) slider.dataset.is_float = "f";
else slider.dataset.is_float = "i";
slider.addEventListener("input", function(event) {
let v = parseFloat(slider.value);
let t = "";
if (is_float) {
t = (v / 100.0).toFixed(2);
} else {
t = v.toFixed(0);
}
let st = "";
if (special) {
if (Object.keys(special).includes(t))
st = special[t];
}
if (st != "") { tb.value = st; tb.classList.add("special"); }
else { tb.value = t; tb.classList.remove("special"); }
});
slider.addEventListener("change", function(event) {
if (editFunc) editFunc(event);
});
let originalValue = "";
tb.addEventListener("focus", function(event) {
originalValue = tb.value;
});
tb.addEventListener("blur", function(event) {
let nvalue = parseFloat(tb.value);
if (isNaN(nvalue)) {
tb.value = originalValue;
return;
}
if (nvalue < minimum) nvalue = minimum;
if (nvalue > maximum) nvalue = maximum;
if (is_float) nvalue *= 100;
slider.value = "" + nvalue;
slider.dispatchEvent(new Event('input'));
slider.dispatchEvent(new Event('change'));
});
div.appendChild(h);
div.appendChild(slider);
div.appendChild(tb);
parent.appendChild(div);
return slider;
}
function setSlider(id, value) {
let slider = document.getElementById(id);
slider.value = "" + slider.dataset.is_float == "f" ? value * 100 : value;
slider.dispatchEvent(new Event('input'));
}
function readSlider(id) {
let slider = document.getElementById(id);
let v = parseFloat(slider.value);
if (slider.dataset.is_float == "f") v /= 100.0;
return v;
}
function addSessionSettings_checkbox(parent, id, left, state, editFunc) {
let div = document.createElement('div');
div.className = "sss-item-split";
let ndri = document.createElement('div');
ndri.className = "checkbox";
var cb = document.createElement('input');
cb.type = 'checkbox';
cb.checked = state;
if (id) cb.id = id;
var label = document.createElement('label');
if (id) label.htmlFor = id;
label.appendChild(document.createTextNode(left));
if (editFunc) cb.addEventListener("change", function(event) { editFunc() } );
ndri.appendChild(cb);
ndri.appendChild(label);
div.appendChild(ndri);
parent.appendChild(div);
}
function populateSessionSettings(settings, prompt_formats) {
//console.log("xxxx", prompt_formats);
let nd = document.getElementById("session-settings");
nd.innerHTML = "";
let div = null;
div = addSessionSettingsSection("Prompt format", "sss-prompt-format");
addSessionSettings_combo(div, "Format", "sss-prompt-format-format", prompt_formats, "", updateSessionSettings);
div = addSessionSettingsSection("Roles", "sss-roles");
addSessionSettings_textbox(div, "User", "sss-role-0", "", updateSessionSettings, 1, "<p></p>", null);
addSessionSettings_textbox(div, "Bot #1", "sss-role-1", "", updateSessionSettings, 1, "<p></p>", null );
for (let i = 2; i < 8; i++) {
addSessionSettings_textbox(div, "Bot #" + i, "sss-role-" + i, "", updateSessionSettings, 1, "✕ Remove", function(event) {
//console.log(i);
for (let j = i; j < 8; j++) {
document.getElementById("sss-role-" + j).value = (j == 7 ? "" : document.getElementById("sss-role-" + (j + 1)).value);
}
updateSessionSettings();
});
}
addSessionSettings_link(div, "+ Add...", "sss-role-add", function(event) {
for (let j = 1; j < 8; j++) {
tb = document.getElementById("sss-role-" + j);
if (tb.value == "") {
tb.value = "Assistant " + j;
break;
}
}
updateSessionSettings();
});
div = addSessionSettingsSection("System prompt", "sss-system-prompt");
addSessionSettings_bigTextbox(div, "system-prompt", "", updateSessionSettings);
div = addSessionSettingsSection("Generation parameters", "sss-generation-params");
addSessionSettings_slider(div, "sss-slider-maxtokens", "Max tokens", 16, 2048, false, updateSessionSettings);
addSessionSettings_slider(div, "sss-slider-chunktokens", "Chunk tokens", 16, 2048, false, updateSessionSettings);
div = addSessionSettingsSection("Sampling", "sss-sampling");
addSessionSettings_slider(div, "sss-slider-temperature", "Temperature", 0, 3, true, updateSessionSettings);
addSessionSettings_slider(div, "sss-slider-topk", "Top K", 0, 1000, false, updateSessionSettings, { "0": "off" } );
addSessionSettings_slider(div, "sss-slider-topp", "Top P", 0, 1, true, updateSessionSettings, { "0.00": "off", "1.00": "off" });
addSessionSettings_slider(div, "sss-slider-typical", "Typical", 0, 1, true, updateSessionSettings, { "0.00": "off", "1.00": "off" });
addSessionSettings_slider(div, "sss-slider-repp", "Rep. penalty", 1, 3, true, updateSessionSettings, { "1.00": "off" });
addSessionSettings_slider(div, "sss-slider-repr", "Rep. range", 0, 4096, false, updateSessionSettings);
div = addSessionSettingsSection("Stop conditions", "sss-stop-conditions");
addSessionSettings_checkbox(div, "sss-checkbox-stopnewline", "Stop on newline", false, updateSessionSettings);
if (settings) {
setSessionSettings(settings);
updateSessionSettings(null, false);
}
// Session controls
nd = document.getElementById("session-list-controls");
nd.innerHTML = "";
if (currentSessionUUID) {
let ndb = document.createElement("div");
let ndbi = document.createElement("span");
ndbi.innerHTML = "✖ Delete session";
ndbi.classList.add("linkbutton");
ndbi.addEventListener('click', function() {
if (ndbi.classList.contains("danger")) {
let packet = {};
packet.session_uuid = currentSessionUUID;
currentSessionUUID = null;
send("/api/delete_session", packet, function() {
enterChatPage( function() {
document.getElementById('session-input').focus();
});
});
} else {
let saveHTML = ndbi.innerHTML;
ndbi.innerHTML = "✖ Confirm";
ndbi.classList.add("danger");
setTimeout(function() {
ndbi.classList.remove("danger");
ndbi.innerHTML = saveHTML;
}, 2000);
}
});
nd.appendChild(ndb);
ndb.appendChild(ndbi);
}
//nd.appendChild(document.createElement("p"));
createSessionControlCheckbox(nd, "cb-show-stats", "Show generation stats", statsvisible, showHideStats);
createSessionControlCheckbox(nd, "cb-smooth-scroll", "Smooth scrolling", smoothscroll, toggleSmoothScroll);
}
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 createSessionControlCheckbox(parent, id, text, state, editFunc = null) {
let div = document.createElement('div');
let ndri = document.createElement('div');
ndri.className = "checkbox-sc";
var cb = document.createElement('input');
cb.type = 'checkbox';
cb.checked = state;
if (id) cb.id = id;
var label = document.createElement('label');
if (id) label.htmlFor = id;
label.appendChild(document.createTextNode(text));
if (editFunc) cb.addEventListener("change", function(event) { editFunc() } );
ndri.appendChild(cb);
ndri.appendChild(label);
div.appendChild(ndri);
parent.appendChild(div);
}
function setSessionSettings(settings) {
//console.log(settings);
document.getElementById("sss-prompt-format-format").value = settings.prompt_format;
for (let i = 0; i < 8; i++) document.getElementById("sss-role-" + i).value = settings.roles[i];
document.getElementById("system-prompt").value = settings.system_prompt;
setSlider("sss-slider-maxtokens", settings.maxtokens);
setSlider("sss-slider-chunktokens", settings.chunktokens);
document.getElementById("sss-checkbox-stopnewline").checked = settings.stop_newline ?? false;
setSlider("sss-slider-temperature", settings.temperature ?? 0.8);
setSlider("sss-slider-topk", settings.top_k ?? 50);
setSlider("sss-slider-topp", settings.top_p ?? 0.8);
setSlider("sss-slider-typical", settings.typical ?? 0.0);
setSlider("sss-slider-repp", settings.repp ?? 1.15);
setSlider("sss-slider-repr", settings.repr ?? 1024);
}
function updateSessionSettings(event, send_updates = true) {
let focus = saveFocus();
let settings = {};
settings.prompt_format = document.getElementById("sss-prompt-format-format").value;
// Roles
if (settings.prompt_format == "Chat-RP") {
document.getElementById("sss-roles").style.display = "";
document.getElementById("sss-stop-conditions").style.display = "";
} else {
document.getElementById("sss-roles").style.display = "none";
document.getElementById("sss-stop-conditions").style.display = "none";
}
let roles = [];
let numroles = 1;
for (let i = 0; i < 8; i++) roles.push(document.getElementById("sss-role-" + i).value.trim());
//console.log(roles);
if (roles[0] == "") roles[0] = "User";
for (let i = 1; i < 8; i++) if (roles[i] != "" && i + 1 > numroles) numroles = i + 1;
for (let i = 2; i < numroles; i++) if (roles[i] == "") roles[i] = "Assistant " + i;
for (let i = 0; i < 8; i++) {
document.getElementById("sss-role-" + i).parentNode.style.display = (i < numroles) ? "" : "none";
}
document.getElementById("sss-role-add").style.display = (numroles < 8 ? "" : "none");
settings.roles = roles;
// System prompt
settings.system_prompt = document.getElementById("system-prompt").value.trim();
// Generation params
settings.maxtokens = readSlider("sss-slider-maxtokens");
settings.chunktokens = readSlider("sss-slider-chunktokens");
settings.stop_newline = document.getElementById("sss-checkbox-stopnewline").checked;
// Sampling
settings.temperature = readSlider("sss-slider-temperature");
settings.top_k = readSlider("sss-slider-topk");
settings.top_p = readSlider("sss-slider-topp");
settings.typical = readSlider("sss-slider-typical");
settings.repp = readSlider("sss-slider-repp");
settings.repr = readSlider("sss-slider-repr");
// Update history if prompt format changes
let updateHistory = false;
if (!currentSettings || currentSettings.prompt_format != settings.prompt_format)
updateHistory = true;
// Update
currentSettings = settings;
// Send to server
let packet = {};
packet.settings = settings;
if (send_updates) {
if (currentSessionUUID) {
send("/api/update_settings", packet, function() { if (updateHistory) showSession(currentSessionUUID); } );
} else {
fetch("/api/new_session", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(packet) })
.then(response => response.json())
.then(json => {
currentSessionUUID = json.session.session_uuid;
enterChatPage(function() { restoreFocus(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;
}
}
}
}
function submitInput() {
const textarea = document.getElementById('session-input');
input = textarea.value.trim();
textarea.value = "";
scrollToBottom();
if (currentSessionUUID) {
submitInput_(input);
} else {
fetch("/api/get_default_settings")
.then(response => response.json())
.then(json => {
let packet = {};
packet.settings = json.settings;
fetch("/api/new_session", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(packet) })
.then(response => response.json())
.then(json => {
currentSessionUUID = json.session.session_uuid;
enterChatPage(function() { submitInput_(input) } );
});
});
}
}
function submitInput_(input) {
if (input == "") {
getResponse();
} else if (loadedModelUUID) {
supplyUserInput(input, getResponse);
} else {
supplyUserInput(input);
}
}
function supplyUserInput(input, responseFunc = null) {
let packet = {};
packet.user_input_text = input;
fetch("/api/user_input", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(packet) })
.then(response => response.json())
.then(json => {
setChatBlock(json.new_block);
if (responseFunc) responseFunc();
});
}
function getResponse() {
let packet = {};
let timeout = new Promise((resolve, reject) => {
let id = setTimeout(() => {
clearTimeout(id);
reject('No response from server')
}, 10000)
});
let fetchRequest = fetch("/api/generate", {
method: "POST",
headers: { "Content-Type": "application/json", },
body: JSON.stringify(packet)
});
Promise.race([fetchRequest, timeout])
.then(response => {
if (response.ok) {
return response.body;
} else {
//appendErrorMessage("Network response was not ok");
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");
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);
return;
} else {
receivedStreamResponse(json);
}
}
data = lines[lines.length - 1];
reader.read().then(process);
});
})
.catch(error => {
console.error('Error:', error);
});
}
function receivedStreamResponse(json) {
// sticky = isNearBottom();
sticky = true;
if (json.result == "begin_block") {
setChatBlock(json.block, true);
currentStreamingBlock = json.block;
}
if (json.result == "prompt_eval") {
let uuid = currentStreamingBlock.block_uuid;
let nd = document.getElementById('chat_block_' + uuid);
nd.children[0].children[1].innerHTML += "<div class='lds-ellipsis'><div></div><div></div><div></div><div></div></div>";
}
if (json.result == "stream_to_block") {
currentStreamingBlock.text += json.text;
let nd = document.getElementById('chat_block_' + json.block_uuid);
setChatBlockText(nd, currentStreamingBlock);
}
if (json.result == "ok") {
let nd = document.getElementById('chat_block_' + json.new_block.block_uuid);
setChatBlockText(nd, json.new_block);
setChatBlockMeta(nd, json.new_block);
}
if (sticky) scrollToBottom();
}
function isNearBottom() {
const element = document.getElementById('session-view-history');
const threshold = 10;
const position = element.scrollTop + element.offsetHeight;
const height = element.scrollHeight;
console.log(element.scrollTop, element.offsetHeight, element.scrollTop + element.offsetHeight, element.scrollHeight);
return height - position < threshold;
}
function scrollToBottom() {
requestAnimationFrame(() => {
requestAnimationFrame(() => {
const element = document.getElementById('session-view-history');
smoothscroll = document.getElementById("cb-smooth-scroll").checked;
let behavior = smoothscroll ? 'smooth' : 'auto';
element.scroll({ top: element.scrollHeight, behavior: behavior });
element.scroll({ top: element.scrollHeight, behavior: behavior });
});
});
}