diff --git a/webui/app.py b/webui/app.py index c2fa909f..c85bff92 100644 --- a/webui/app.py +++ b/webui/app.py @@ -15,6 +15,7 @@ app = Flask(__name__) app.static_folder = 'static' +app.config['TEMPLATES_AUTO_RELOAD'] = True generate_lock = Lock() session: Session @@ -45,7 +46,7 @@ def api_edit_block(): @app.route("/api/delete_block", methods=['POST']) def api_delete_block(): global session - data = request.get_json() + data = request.get_json()['uuid'] session.api_delete_block(data) return json.dumps({"result": "ok"}) + "\n" @@ -117,6 +118,21 @@ def api_userinput(): with generate_lock: result = Response(stream_with_context(session.respond_multi(user_input)), mimetype = 'application/json') return result + +@app.route("/api/continue_gen", methods=['POST']) +def api_continue_gen(): + with generate_lock: + result = Response(stream_with_context(session.respond_multi('')), mimetype = 'application/json') + return result + +@app.route("/api/regen_block", methods=['POST']) +def api_regen(): + global session + uuid = request.get_json()['uuid']; + + with generate_lock: + result = Response(stream_with_context(session.regen_at(uuid)), mimetype = 'application/json') + return result @app.route("/api/append_block", methods=['POST']) def api_append_block(): diff --git a/webui/session.py b/webui/session.py index 5a715113..879aec81 100644 --- a/webui/session.py +++ b/webui/session.py @@ -22,6 +22,10 @@ sessions_dir: str +def get_generator(): + global generator + return generator + def _sessions_dir(filename = None): global sessions_dir @@ -183,6 +187,9 @@ def __init__(self, filename, load): self.max_response_tokens = saved.get("max_response_tokens", 512) self.chunk_size = saved.get("chunk_size", 128) + self.format_use_italic = saved.get("format_use_italic", False) + self.format_use_bold = saved.get("format_use_bold", False) + self.session_color = saved.get("session_color", '#383848') # Save new session @@ -205,6 +212,9 @@ def save(self): "break_on_newline": self.break_on_newline, "max_response_tokens": self.max_response_tokens, "chunk_size": self.chunk_size, + "format_use_italic": self.format_use_italic, + "format_use_bold": self.format_use_bold, + "session_color": self.session_color, "token_repetition_penalty_max": generator.settings.token_repetition_penalty_max, "token_repetition_penalty_sustain": generator.settings.token_repetition_penalty_sustain, "token_repetition_penalty_decay": generator.settings.token_repetition_penalty_decay} @@ -295,6 +305,9 @@ def api_populate(self): "break_on_newline": self.break_on_newline, "max_response_tokens": self.max_response_tokens, "chunk_size": self.chunk_size, + "format_use_italic": self.format_use_italic, + "format_use_bold": self.format_use_bold, + "session_color": self.session_color, "token_repetition_penalty_max": generator.settings.token_repetition_penalty_max, "token_repetition_penalty_sustain": generator.settings.token_repetition_penalty_sustain, "token_repetition_penalty_decay": generator.settings.token_repetition_penalty_decay, @@ -311,16 +324,15 @@ def api_populate(self): return json_object + "\n" - def api_delete_block(self, data): + def api_delete_block(self, block_id): - block_id = data["uuid"] idx = -1 for i in range(len(self.history)): if self.history[i].uuid == block_id: idx = i if idx == -1: return - self.history.pop(idx) + self.history = self.history[0:idx] self.first_history_idx = 0 self.save() @@ -379,6 +391,9 @@ def api_set_gen_settings(self, data): generator.settings.token_repetition_penalty_max = data["token_repetition_penalty_max"] generator.settings.token_repetition_penalty_sustain = data["token_repetition_penalty_sustain"] generator.settings.token_repetition_penalty_decay = data["token_repetition_penalty_decay"] + self.format_use_italic = data["format_use_italic"] + self.format_use_bold = data["format_use_bold"] + self.session_color = data["session_color"] self.save() @@ -700,4 +715,6 @@ def respond_multi(self, user_input): self.save() - + def regen_at(self, uuid): + self.api_delete_block(uuid) + return self.respond_multi('') \ No newline at end of file diff --git a/webui/static/main.js b/webui/static/main.js index 09cf1ce1..a3c6474e 100644 --- a/webui/static/main.js +++ b/webui/static/main.js @@ -1,12 +1,18 @@ var participants = []; -var chat_colors = [ "#343434", - "#383848", - "#374a48", - "#4a373d", - "#374a3d", - "#57503b", - "#3e2845", - "#452927" ] +[ + "#343434", + "#383848", + "#374a48", + "#4a373d", + "#374a3d", + "#57503b", + "#3e2845", + "#452927" +].forEach((color, index) => +{ + document.documentElement.style.setProperty('--chat-back-color-' + index, hexToRgb(color)); +}); + var textbox_initial = ""; @@ -61,6 +67,10 @@ function sendGenSettings() { json.max_response_tokens = getTBNumber("sl_maxtokens_tb"); json.gen_endnewline = document.querySelector("#cb_gen_endnewline input").checked; + json.format_use_italic = document.querySelector("#cb_use_italic input").checked; + json.format_use_bold = document.querySelector("#cb_use_bold input").checked; + json.session_color = document.querySelector("#clr_session_color input").value; + json.token_repetition_penalty_max = getTBNumber("sl_repp_penalty_tb"); json.token_repetition_penalty_sustain = getTBNumber("sl_repp_sustain_tb"); json.token_repetition_penalty_decay = getTBNumber("sl_repp_decay_tb"); @@ -92,6 +102,12 @@ function setSlider(id, value, override_max = null) { } } +function setCheckbox(id, value) +{ + let checkbox = document.querySelector('#' + id + ' input'); + checkbox.checked = value; +} + function sliderChanged(slider) { setSlider(slider.id, Number(slider.value) / Math.pow(10, slider.dataset.decimals)); } @@ -145,6 +161,16 @@ function createSlider(text, id, min, max, actualmax, decimals, text0 = null) { return newDiv; } +function createCheckbox(text, id, value, onchanged = 'sendGenSettings()') { + + let newDiv = document.createElement('label'); + newDiv.className = 'custom-checkbox no-select'; + newDiv.id = id; + newDiv.innerHTML = '' + text + ''; + + return newDiv; +} + function createGenSettings() { // Sampling @@ -170,6 +196,10 @@ function createGenSettings() { setSlider("sl_topk", 40); setSlider("sl_typical", 0); + // Context + + document.getElementById("context-settings").appendChild(createCheckbox("Keep context", 'cb_keep_fixed_prompt', false, 'sendFixedPromptSettings()')); + // Stop conditions let stopDiv = document.getElementById("stop_settings"); @@ -184,7 +214,7 @@ function createGenSettings() { setSlider("sl_maxtokens", 512); setSlider("sl_chunksize", 128); - document.getElementById("cb_gen_endnewline").addEventListener('change', sendGenSettings); + stopDiv.appendChild(createCheckbox("End on newline", 'cb_gen_endnewline', false)); // Repetition penalty @@ -203,6 +233,12 @@ function createGenSettings() { setSlider("sl_repp_sustain", 1024); setSlider("sl_repp_decay", 512); + // Formatting settings + let formatDiv = document.getElementById("format_settings"); + formatDiv.innerHTML = ""; + + formatDiv.appendChild(createCheckbox("Use italic", 'cb_use_italic', false)); + formatDiv.appendChild(createCheckbox("Use bold", 'cb_use_bold', false)); } // Populate view @@ -224,7 +260,10 @@ function populate() { let newDiv = document.createElement('div'); if (sessions[i] == current_session) + { newDiv.className = 'session session_current no-select'; + newDiv.style.background = 'var(--chat-back-color-1)'; + } else newDiv.className = 'session no-select'; @@ -243,15 +282,15 @@ function populate() { icon.addEventListener('click', function() { makeEditable(this.parentNode, renameSession) }); } - document.getElementById('sessions-column').appendChild(newDiv); + document.getElementById('sessions').appendChild(newDiv); } let newDiv = document.createElement('div'); newDiv.className = 'session session_new no-select'; newDiv.id = "session-button-new"; - newDiv.innerHTML = '
+ New...
'; + newDiv.innerHTML = '+
'; newDiv.addEventListener('click', function() { setSession("."); }); - document.getElementById('sessions-column').appendChild(newDiv); + document.getElementById('sessions').appendChild(newDiv); // Model info @@ -266,9 +305,7 @@ function populate() { tf_fixed_prompt.value = data.fixed_prompt; addTextboxEvents(tf_fixed_prompt, sendFixedPromptSettings); - let cb_keep_fixed_prompt = document.querySelector("#cb_keep_fixed_prompt input") - cb_keep_fixed_prompt.checked = data.keep_fixed_prompt; - cb_keep_fixed_prompt.addEventListener('change', sendFixedPromptSettings); + setCheckbox('cb_keep_fixed_prompt', data.keep_fixed_prompt); // Generator settings @@ -283,8 +320,7 @@ function populate() { setSlider("sl_maxtokens", data.max_response_tokens); setSlider("sl_chunksize", data.chunk_size); - let cb_gen_endnewline = document.querySelector("#cb_gen_endnewline input") - cb_gen_endnewline.checked = data.break_on_newline; + setCheckbox('cb_gen_endnewline', data.break_on_newline); // Repetition penalty @@ -297,6 +333,13 @@ function populate() { participants = data.participants; updateParticipants(); + // Formatting + setCheckbox('cb_use_italic', data.format_use_italic); + setCheckbox('cb_use_bold', data.format_use_bold); + document.querySelector('#clr_session_color input').value = data.session_color; + document.documentElement.style.setProperty('--chat-back-color-1', hexToRgb(data.session_color)); + formatter.updateTagSearcher(); + // Chat data let history = data.history; @@ -323,7 +366,7 @@ function populate() { }) .catch((error) => { - console.error('Error:', error); + console.error('Error: ', error, '\n', error.stack); }); } @@ -339,10 +382,25 @@ function editChatBlock(div, value) { function deleteChatBlock(uuid, div) { + let nextChild = div.nextElementSibling; + while (nextChild) { + nextChild.remove(); + nextChild = div.nextElementSibling; + } div.remove(); send("/api/delete_block", { uuid: uuid }, null, null); +} + +function regenChatBlock(uuid, div) { - $("#user-input").focus(); + let nextChild = div.nextElementSibling; + while (nextChild) { + nextChild.remove(); + nextChild = div.nextElementSibling; + } + div.remove(); + + handleOutput("/api/regen_block", { uuid: uuid }); } // Delete session @@ -351,7 +409,6 @@ function deleteSession(session_name, div) { div.parentNode.remove(); send("/api/delete_session", { session: session_name }, null, null); - } // Rename session @@ -361,7 +418,6 @@ function renameSession(div, value) { send("/api/rename_session", { new_name: value.trim() }, null, function() { div.firstElementChild.textContent = textbox_initial; } ); - } // Fixed prompt settings @@ -406,7 +462,8 @@ function updateParticipants() { let remDiv = document.createElement('div'); remDiv.id = "remove_participant_" + i; remDiv.className = 'custom-textbox-rlabel no-select'; - remDiv.innerHTML = "✕ Remove"; + remDiv.innerHTML = "✕"; + remDiv.title = 'Remove'; remDiv.addEventListener('click', function() { removeParticipant(remDiv.id); }); newDiv.appendChild(remDiv); } @@ -662,6 +719,45 @@ function setSession(name) { // Handle text input +function handleOutput(url, data) +{ + let timeout = new Promise((resolve, reject) => { + let id = setTimeout(() => { + clearTimeout(id); + reject('No response from server') + }, 10000) + }) + + // Fetch request + + let fetchRequest = fetch(url, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(data) + }); + + // Pass input to server and setup response reader + + 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(body => { + processStream(body); + }) + .catch(error => { + appendErrorMessage("Error: " + error); + console.error('Error:', error); + }); +} + $(document).ready(function() { $("#chat-input-form").on('submit', function(e) { @@ -673,47 +769,17 @@ $(document).ready(function() { $("#user-input").prop("disabled", true); $("#user-input").attr('placeholder', '...'); - // Timeout - - let timeout = new Promise((resolve, reject) => { - let id = setTimeout(() => { - clearTimeout(id); - reject('No response from server') - }, 10000) - }) - - // Fetch request - - let fetchRequest = fetch("/api/userinput", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ user_input: userInput }) - }); - - // Pass input to server and setup response reader - - 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(body => { - processStream(body); - }) - .catch(error => { - appendErrorMessage("Error: " + error); - console.error('Error:', error); - }); - + handleOutput("/api/userinput", { user_input: userInput}); }); }); +$("#user-send").click(() => $("#chat-input-form").submit()); +$("#user-continue").click(() => handleOutput("/api/continue_gen", {})); +$("#user-regen-last").click(function(){ + var div = document.getElementById("chatbox").lastElementChild; + regenChatBlock(div.dataset.uuid, div); +}); + // Read stream let data = ''; @@ -727,9 +793,9 @@ function processStream(stream) { // console.log("Received chunk:", decoder.decode(value)); if (done) { // console.log("Stream complete"); + formatter.updateFormatting(); $("#user-input").prop("disabled", false); $("#user-input").attr('placeholder', 'Type here...'); - $("#user-input").focus(); return; } @@ -752,19 +818,85 @@ function processStream(stream) { }); } + +var formatter = new (function() +{ + let tagSearcher = this.tagSearcher = { 'br': /\n/ }; + + let useItalicToggle = $('#cb_use_italic input'); + let useBoldToggle = $('#cb_use_bold input'); + Object.defineProperty(this, 'useItalic', { get(){ return ; } }); + Object.defineProperty(this, 'useBold', { get(){ return useBoldToggle.prop('checked'); } }); + + this.updateTagSearcher = function() + { + if (!useItalicToggle.prop('checked')) + delete tagSearcher['i']; + else + tagSearcher['i'] = /(\\?\*){1}/; + if (!useBoldToggle.prop('checked')) + delete tagSearcher['b']; + else + tagSearcher['b'] = /(\\?\*){2}/; + } + this.updateFormatting = function() + { + this.updateTagSearcher(); + $('#chatbox .text').each(function() + { + let node = $(this); + node.text(' '); + appendWithLineBreaks(this.lastChild, this.parentNode.dataset.text); + }); + } + useItalicToggle.on('change', this.updateFormatting); + useBoldToggle.on('change', this.updateFormatting); +})(); + function appendWithLineBreaks(currentNode, text) { - var textChunks = text.split("\n"); - for (var i = 0; i < textChunks.length - 1; i++) { - currentNode.textContent += textChunks[i]; - let br = document.createElement('br'); - currentNode.parentNode.appendChild(br); - newNode = document.createTextNode(''); - currentNode.parentNode.appendChild(newNode); - currentNode = newNode; + var parent = currentNode.parentNode; + var html = parent.innerHTML; + + let italicOpen = false, boldOpen = false; + while (true) + { + let searchResults = []; + Object.keys(formatter.tagSearcher).forEach((key) => + { + let match = formatter.tagSearcher[key].exec(text); + if (match) { + searchResults.push({ + tag: key, + start: match.index, + end: match.index + match[0].length + }); + } + }); + + searchResults.sort((a, b) => a.start - b.start); + let nearestResult = searchResults[0]; + if (nearestResult === undefined) break; + + html += document.createTextNode(text.substring(0, nearestResult.start)).textContent; + switch(nearestResult.tag) + { + case 'br': html += '