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;
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 = "";
html += "
" + text + "
"; 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, "" + name + "
", 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 = "" + model_name + "
"; 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, "", 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, "" + name + "
", 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 = "