From cf27d44f622a1a601ee152bad0c0702af47fc433 Mon Sep 17 00:00:00 2001 From: somebody Date: Sat, 22 Jul 2023 16:50:29 -0500 Subject: [PATCH 01/14] WI: Tag polish - make the button thingeys look more button-ey - enter now saves the focused tag and focuses the placeholder tag - reduced code duplication for primary vs secondary and normal vs placeholder tags --- static/koboldai.css | 26 +++- static/koboldai.js | 284 ++++++++++++++++++++------------------------ 2 files changed, 157 insertions(+), 153 deletions(-) diff --git a/static/koboldai.css b/static/koboldai.css index 3252c21a..0f83a159 100644 --- a/static/koboldai.css +++ b/static/koboldai.css @@ -1268,8 +1268,32 @@ td.server_vars { border-color: var(--wi_tag_color); } -.tag .delete_icon { +.tag .tag_button { cursor: pointer; + opacity: 0.4; + font-size: 16px; + position: relative; +} + +.tag .delete_icon { + top: 3px; + right: 3px; +} + +.tag .add_icon { + top: 3px; + right: 3px; +} + +.tag .tag_text { + display: inline-block; + outline: none; + position: relative; + right: 3px; +} + +.placeholder_tag .tag_text:empty { + opacity: 0.4; } .oi[folder] { diff --git a/static/koboldai.js b/static/koboldai.js index dc616b19..00bb43ee 100644 --- a/static/koboldai.js +++ b/static/koboldai.js @@ -83,6 +83,7 @@ let story_id = -1; var dirty_chunks = []; var initial_socketio_connection_occured = false; var selected_model_data; +var attention_wanting_wi_bar = null; // Each entry into this array should be an object that looks like: // {class: "class", key: "key", func: callback} @@ -2230,15 +2231,16 @@ function world_info_entry(data) { this.classList.add("pulse"); }) - tags = world_info_card.querySelector('.world_info_tag_primary_area'); + const tags = world_info_card.querySelector('.world_info_tag_primary_area'); tags.id = "world_info_tags_"+data.uid; //add tag content here - add_tags(tags, data); + add_tags(tags, data, "primary"); - secondarytags = world_info_card.querySelector('.world_info_tag_secondary_area'); + const secondarytags = world_info_card.querySelector('.world_info_tag_secondary_area'); secondarytags.id = "world_info_secondtags_"+data.uid; //add second tag content here - add_secondary_tags(secondarytags, data); + add_tags(secondarytags, data, "secondary"); + //w++ toggle wpp_toggle_area = world_info_card.querySelector('.world_info_wpp_toggle_area'); wpp_toggle_area.id = "world_info_wpp_toggle_area_"+data.uid; @@ -4229,161 +4231,139 @@ function removeA(arr) { return arr; } -function add_tags(tags, data) { - while (tags.firstChild) { - tags.removeChild(tags.firstChild); +function create_tag_element(tagText, uid, tagType) { + // tagText is string, or null for empty tag at end. + // barType should be "primary" or "secondary" + const isPlaceholderTag = tagText === null; + + const wiCardEl = document.querySelector(`.world_info_card[uid="${uid}"]`) + const keyField = {primary: "key", secondary: "keysecondary"}[tagType]; + const tagClassFragment = {primary: "tags", primary: "secondtags"}[tagType]; + + const tagEl = document.createElement("span"); + tagEl.classList.add("tag"); + if (isPlaceholderTag) tagEl.classList.add("placeholder_tag"); + + const xEl = document.createElement("span"); + xEl.classList.add("material-icons-outlined"); + xEl.classList.add("tag_button"); + + if (!isPlaceholderTag) { + xEl.classList.add("delete_icon"); + xEl.textContent = "close"; + } else { + xEl.classList.add("add_icon"); + xEl.textContent = "add"; } - for (tag of data.key) { - tag_item = document.createElement("span"); - tag_item.classList.add("tag"); - x = document.createElement("span"); - x.textContent = "x "; - x.classList.add("delete_icon"); - x.setAttribute("uid", data.uid); - x.setAttribute("tag", tag); - x.onclick = function () { - removeA(world_info_data[this.getAttribute('uid')]['key'], this.getAttribute('tag')); - send_world_info(this.getAttribute('uid')); - this.classList.add("pulse"); - }; - text = document.createElement("span"); - text.textContent = tag; - text.setAttribute("contenteditable", true); - text.setAttribute("uid", data.uid); - text.setAttribute("tag", tag); - text.id = "world_info_tags_text_"+data.uid+"_"+tag; - text.ondragstart=function() {event.preventDefault();event.stopPropagation();}; - text.setAttribute("draggable", "true"); - text.onfocus=function() {this.parentElement.parentElement.parentElement.setAttribute('draggable', 'false');this.setAttribute('draggable', 'false');}; - text.onblur = function () { - this.parentElement.parentElement.parentElement.setAttribute('draggable', 'true'); - this.setAttribute('draggable', 'true'); - for (var i = 0; i < world_info_data[this.getAttribute('uid')]['key'].length; i++) { - if (world_info_data[this.getAttribute('uid')]['key'][i] == this.getAttribute("tag")) { - world_info_data[this.getAttribute('uid')]['key'][i] = this.textContent; - } - } - send_world_info(this.getAttribute('uid')); - this.classList.add("pulse"); - }; - tag_item.append(x); - tag_item.append(text); - tag_item.id = "world_info_tags_"+data.uid+"_"+tag; - tags.append(tag_item); - } - //add the blank tag - tag_item = document.createElement("span"); - tag_item.classList.add("tag"); - x = document.createElement("span"); - x.textContent = "+ "; - tag_item.append(x); - text = document.createElement("span"); - text.classList.add("rawtext"); - text.textContent = " "; - text.setAttribute("uid", data.uid); - text.setAttribute("contenteditable", true); - text.id = "world_info_tags_text_"+data.uid+"_blank"; - text.ondragstart=function() {event.preventDefault();event.stopPropagation();}; - text.setAttribute("draggable", "true"); - text.onfocus=function() {this.parentElement.parentElement.parentElement.setAttribute('draggable', 'false');this.setAttribute('draggable', 'false');}; - text.onblur = function () { - this.parentElement.parentElement.parentElement.setAttribute('draggable', 'true'); - this.setAttribute('draggable', 'true'); - if (this.textContent.trim() != "") { - //console.log(this.textContent); - on_new_wi_item = this.id; - world_info_data[this.getAttribute('uid')]['key'].push(this.textContent); - send_world_info(this.getAttribute('uid')); - this.classList.add("pulse"); - } else { - this.textContent = " "; - } - }; - text.onclick = function () { - this.textContent = ""; - }; - tag_item.append(text); - tag_item.id = "world_info_secondtags_"+data.uid+"_new"; - tags.append(tag_item); + + xEl.setAttribute("uid", uid); + xEl.setAttribute("tag", tagText); + xEl.addEventListener("click", function() { + removeA( + world_info_data[uid][keyField], + tagText + ); + send_world_info(uid); + this.classList.add("pulse"); + }); + + const textEl = document.createElement("span"); + textEl.classList.add("tag_text"); + textEl.textContent = tagText; + + textEl.setAttribute("data-placeholder", "Tag") + textEl.setAttribute("contenteditable", true); + textEl.setAttribute("uid", uid); + textEl.setAttribute("tag", tagText); + textEl.setAttribute("draggable", "true"); + textEl.id = `world_info_${tagClassFragment}_text_${uid}_${tagText || "blank"}`; + + textEl.addEventListener("dragstart", function(event) { + event.preventDefault(); + event.stopPropagation(); + }); + + textEl.addEventListener("focus", function(event) { + wiCardEl.setAttribute('draggable', 'false'); + this.setAttribute('draggable', 'false'); + }); + + textEl.addEventListener("blur", function () { + wiCardEl.setAttribute('draggable', 'true'); + this.setAttribute('draggable', 'true'); + + if (!isPlaceholderTag) { + // Normal tag + for (var i = 0; i < world_info_data[uid][keyField].length; i++) { + if (world_info_data[uid][keyField][i] !== tagText) { + world_info_data[uid][keyField][i] = this.innerText; + } + } + } else { + // Placeholder tag + if (!this.textContent.trim()) return; + + on_new_wi_item = this.id; + world_info_data[uid][keyField].push(this.textContent); + } + + send_world_info(uid); + this.classList.add("pulse"); + }); + + textEl.addEventListener("keydown", function(event) { + if (event.key === "Enter") { + // Press Enter to save tag and focus next one + event.preventDefault(); + + // HACK: Work around the fact that the server is in control of + // placing these elements + attention_wanting_wi_bar = tagType; + // And don't wait for like 10000 years to randomly take focus from + // the user + setTimeout(() => attention_wanting_wi_bar = null, 500); + + this.blur(); + } else if (event.key === "Escape") { + + } + }) + + tagEl.append(xEl); + tagEl.append(textEl); + tagEl.id = `world_info_${tagClassFragment}_${uid}_${tagText || "new"}`; + + return tagEl; } -function add_secondary_tags(tags, data) { - while (tags.firstChild) { - tags.removeChild(tags.firstChild); +function add_tags(tagBarEl, data, tagType) { + // tagType is either "primary" or "secondary" + + // Remove existing tags + while (tagBarEl.firstChild) { + tagBarEl.removeChild(tagBarEl.firstChild); } - for (tag of data.keysecondary) { - tag_item = document.createElement("span"); - tag_item.classList.add("tag"); - x = document.createElement("span"); - x.textContent = "x "; - x.classList.add("delete_icon"); - x.setAttribute("uid", data.uid); - x.setAttribute("tag", tag); - x.onclick = function () { - removeA(world_info_data[this.getAttribute('uid')]['keysecondary'], this.getAttribute('tag')); - send_world_info(this.getAttribute('uid')); - this.classList.add("pulse"); - }; - text = document.createElement("span"); - text.textContent = tag; - text.setAttribute("contenteditable", true); - text.setAttribute("uid", data.uid); - text.setAttribute("tag", tag); - text.id = "world_info_secondtags_text_"+data.uid+"_"+tag; - text.ondragstart=function() {event.preventDefault();event.stopPropagation();}; - text.setAttribute("draggable", "true"); - text.onfocus=function() {this.parentElement.parentElement.parentElement.setAttribute('draggable', 'false');this.setAttribute('draggable', 'false');}; - text.onblur = function () { - this.parentElement.parentElement.parentElement.setAttribute('draggable', 'true'); - this.setAttribute('draggable', 'true'); - for (var i = 0; i < world_info_data[this.getAttribute('uid')]['keysecondary'].length; i++) { - if (world_info_data[this.getAttribute('uid')]['keysecondary'][i] == this.getAttribute("tag")) { - world_info_data[this.getAttribute('uid')]['keysecondary'][i] = this.textContent; - } - } - send_world_info(this.getAttribute('uid')); - this.classList.add("pulse"); - }; - tag_item.append(x); - tag_item.append(text); - tag_item.id = "world_info_secondtags_"+data.uid+"_"+tag; - tags.append(tag_item); + + const tagList = { + primary: data.key, + secondary: data.keysecondary + }[tagType]; + + for (tag of tagList) { + tagBarEl.append(create_tag_element(tag, data.uid, tagType)); } + //add the blank tag - tag_item = document.createElement("span"); - tag_item.classList.add("tag"); - x = document.createElement("span"); - x.textContent = "+ "; - tag_item.append(x); - text = document.createElement("span"); - text.classList.add("rawtext"); - text.textContent = " "; - text.setAttribute("uid", data.uid); - text.setAttribute("contenteditable", true); - text.id = "world_info_secondtags_text_"+data.uid+"_blank"; - text.ondragstart=function() {event.preventDefault();event.stopPropagation();}; - text.setAttribute("draggable", "true"); - text.onfocus=function() {this.parentElement.parentElement.parentElement.setAttribute('draggable', 'false');this.setAttribute('draggable', 'false');}; - text.onblur = function () { - this.parentElement.parentElement.parentElement.setAttribute('draggable', 'true'); - this.setAttribute('draggable', 'true'); - if (this.textContent.trim() != "") { - on_new_wi_item = this.id; - world_info_data[this.getAttribute('uid')]['keysecondary'].push(this.textContent); - send_world_info(this.getAttribute('uid')); - this.classList.add("pulse"); - } else { - this.textContent = " "; - } - }; - text.onclick = function () { - this.textContent = ""; - }; - tag_item.append(text); - tag_item.id = "world_info_secondtags_"+data.uid+"_new"; - tags.append(tag_item); + const placeholderTagEl = create_tag_element(null, data.uid, tagType); + tagBarEl.append(placeholderTagEl); + + if (attention_wanting_wi_bar === tagType) { + const textEl = placeholderTagEl.querySelector(".tag_text"); + // HACK: Please don't ask because I do not know + setTimeout(() => textEl.focus(), 1); + } } - + function create_new_wi_entry(folder) { var uid = -1; for (item of document.getElementsByClassName('world_info_card')) { From 68c6030ab063a4a58366b69046c41439b0fcfe10 Mon Sep 17 00:00:00 2001 From: somebody Date: Sat, 22 Jul 2023 17:04:45 -0500 Subject: [PATCH 02/14] WI: Don't explode when user uploads image without a save This is a band-aid on an underlying problem: there is no save directory to put blobs like images in before the user saves the game. The way forward is probably to have an in-memory (no disk, Colab privacy (thats kind of an oxymoron)) folder or something. I want to expand the data storage functionality into an api in the future so devs can seamlessly do something like: Data.get_file_contents("image/blahblah.png") and they won't have to actually worry about stuff like this --- aiserver.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/aiserver.py b/aiserver.py index dc565c97..8c07efa8 100644 --- a/aiserver.py +++ b/aiserver.py @@ -6715,11 +6715,18 @@ def UI_2_set_wi_image(uid): except FileNotFoundError: pass else: - # Otherwise assign image - with open(path, "wb") as file: - file.write(data) + try: + # Otherwise assign image + with open(path, "wb") as file: + file.write(data) + except FileNotFoundError: + show_error_notification( + "Unable to write image", + "Please save the game before uploading images." + ) + return ":(", 500 koboldai_vars.gamesaved = False - return ":)" + return ":)", 200 @app.route("/get_wi_image/", methods=["GET"]) @require_allowed_ip From 6b26cbbd0a50f7deb3b4c5551aee87cc81ea575a Mon Sep 17 00:00:00 2001 From: somebody Date: Sat, 22 Jul 2023 17:20:40 -0500 Subject: [PATCH 03/14] Backends: Fix ReadOnly Since somewhere in the pipeline ReadOnly is ignored, the bug wasn't actually apparent unless using things like the Robot Button in WI cards. --- modeling/inference_model.py | 7 ++- modeling/inference_models/readonly/class.py | 49 ++++++++++++--------- 2 files changed, 34 insertions(+), 22 deletions(-) diff --git a/modeling/inference_model.py b/modeling/inference_model.py index a2d4fa63..28d96473 100644 --- a/modeling/inference_model.py +++ b/modeling/inference_model.py @@ -597,7 +597,12 @@ class InferenceModel: ) time_end = round(time.time() - time_start, 2) - tokens_per_second = round(len(result.encoded[0]) / time_end, 2) + + try: + tokens_per_second = round(len(result.encoded[0]) / time_end, 2) + except ZeroDivisionError: + # Introducing KoboldAI's fastest model: ReadOnly! + tokens_per_second = 0 if not utils.koboldai_vars.quiet: logger.info( diff --git a/modeling/inference_models/readonly/class.py b/modeling/inference_models/readonly/class.py index 13c38baf..cbdb298d 100644 --- a/modeling/inference_models/readonly/class.py +++ b/modeling/inference_models/readonly/class.py @@ -1,12 +1,10 @@ from __future__ import annotations import torch -import requests import numpy as np from typing import List, Optional, Union import utils -from logger import logger from modeling.inference_model import ( GenerationResult, GenerationSettings, @@ -15,29 +13,46 @@ from modeling.inference_model import ( ) model_backend_name = "Read Only" -model_backend_type = "Read Only" #This should be a generic name in case multiple model backends are compatible (think Hugging Face Custom and Basic Hugging Face) +model_backend_type = "Read Only" # This should be a generic name in case multiple model backends are compatible (think Hugging Face Custom and Basic Hugging Face) -class BasicAPIException(Exception): - """To be used for errors when using the Basic API as an interface.""" + +class DummyHFTokenizerOut: + input_ids = np.array([[]]) + + +class FacadeTokenizer: + def __init__(self): + self._koboldai_header = [] + + def decode(self, _input): + return "" + + def encode(self, input_text): + return [] + + def __call__(self, *args, **kwargs) -> DummyHFTokenizerOut: + return DummyHFTokenizerOut() class model_backend(InferenceModel): def __init__(self) -> None: super().__init__() - # Do not allow API to be served over the API + # Do not allow ReadOnly to be served over the API self.capabilties = ModelCapabilities(api_host=False) - self.tokenizer = self._tokenizer() + self.tokenizer: FacadeTokenizer = None self.model = None self.model_name = "Read Only" - + def is_valid(self, model_name, model_path, menu_path): return model_name == "ReadOnly" - - def get_requested_parameters(self, model_name, model_path, menu_path, parameters = {}): + + def get_requested_parameters( + self, model_name, model_path, menu_path, parameters={} + ): requested_parameters = [] return requested_parameters - + def set_input_parameters(self, parameters): return @@ -46,17 +61,9 @@ class model_backend(InferenceModel): def _initialize_model(self): return - - class _tokenizer(): - def __init__(self): - self._koboldai_header = [] - def decode(self, _input): - return "" - def encode(self, input_text): - return [] def _load(self, save_model: bool = False, initial_load: bool = False) -> None: - self.tokenizer = self.tokenizer + self.tokenizer = FacadeTokenizer() self.model = None utils.koboldai_vars.noai = True @@ -72,7 +79,7 @@ class model_backend(InferenceModel): ): return GenerationResult( model=self, - out_batches=np.array([]), + out_batches=np.array([[]]), prompt=prompt_tokens, is_whole_generation=True, single_line=single_line, From c7b128829c8e430423088750a4a71c8f8ba52b22 Mon Sep 17 00:00:00 2001 From: somebody Date: Sat, 22 Jul 2023 17:30:43 -0500 Subject: [PATCH 04/14] WI: Fix visual oddness with more than one row of tags --- static/koboldai.css | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/static/koboldai.css b/static/koboldai.css index 0f83a159..75293eaa 100644 --- a/static/koboldai.css +++ b/static/koboldai.css @@ -1259,13 +1259,18 @@ td.server_vars { .world_info_label_container > .generate-button:hover { opacity: 1.0; } .tag { + display: inline-block; background-color: var(--wi_tag_color); color: var(--wi_tag_text_color); + + margin-right: 3px; + margin-top: 3px; + padding: 2px; - margin-right: 2px; + padding-left: 3px; + padding-right: 3px; + border-radius: var(--radius_wi_card); - border: solid; - border-color: var(--wi_tag_color); } .tag .tag_button { From ccbfad1a13809ed6e18deea1c531a94990b5be6a Mon Sep 17 00:00:00 2001 From: somebody Date: Sat, 22 Jul 2023 17:35:16 -0500 Subject: [PATCH 05/14] UI: Make welcome text links have underlines Would love to make em' a different color but that would require going through all the themes and im laaaazy --- static/koboldai.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/static/koboldai.css b/static/koboldai.css index 75293eaa..51a70f18 100644 --- a/static/koboldai.css +++ b/static/koboldai.css @@ -2849,6 +2849,10 @@ body { height: 100%; } +#welcome_text a { + text-decoration: underline; +} + .welcome_text { display: flex; height: 100%; From 432418ed1e3be0a8573f2d7ab5e36c8f63d68d37 Mon Sep 17 00:00:00 2001 From: somebody Date: Sat, 22 Jul 2023 17:44:32 -0500 Subject: [PATCH 06/14] UI: Possibly more clear tooltips --- gensettings.py | 2 +- templates/story flyout.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gensettings.py b/gensettings.py index 8d68b4b5..765f6370 100644 --- a/gensettings.py +++ b/gensettings.py @@ -296,7 +296,7 @@ gensettingstf = [ "max": 1, "step": 1, "default": 0, - "tooltip": "Scans the AI's output for World Info keys as it is generating the one.", + "tooltip": "Look for World Info keys in the AI's response while it is still being generated.", "menu_path": "World Info", "sub_path": "", "classname": "story", diff --git a/templates/story flyout.html b/templates/story flyout.html index 514edbb9..193881d7 100644 --- a/templates/story flyout.html +++ b/templates/story flyout.html @@ -97,7 +97,7 @@ X From 3995b3f93b8d67e44a298f8a9034e5adac21c656 Mon Sep 17 00:00:00 2001 From: somebody Date: Sat, 22 Jul 2023 18:18:21 -0500 Subject: [PATCH 11/14] WI: Make delete button pretty --- templates/templates.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/templates.html b/templates/templates.html index ca6e3734..b87cf812 100644 --- a/templates/templates.html +++ b/templates/templates.html @@ -31,7 +31,7 @@ >help_icon - X + close
From 8cc0a8cab97cebfa1e33f021a14cc14ab4a5fa95 Mon Sep 17 00:00:00 2001 From: somebody Date: Sun, 30 Jul 2023 14:25:09 -0500 Subject: [PATCH 12/14] WI: Fix UI1 WI errors --- aiserver.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/aiserver.py b/aiserver.py index b7e2be66..133d20bc 100644 --- a/aiserver.py +++ b/aiserver.py @@ -4396,8 +4396,8 @@ def requestwi(): # and items in different folders are sorted based on the order of the folders #==================================================================# def stablesortwi(): - mapping = {uid: index for index, uid in enumerate(koboldai_vars.wifolders_l)} - koboldai_vars.worldinfo.sort(key=lambda x: mapping[str(x["folder"])] if x["folder"] is not None else float("inf")) + mapping = {int(uid): index for index, uid in enumerate(koboldai_vars.wifolders_l)} + koboldai_vars.worldinfo.sort(key=lambda x: mapping[int(x["folder"])] if x["folder"] is not None else float("inf")) last_folder = ... last_wi = None for i, wi in enumerate(koboldai_vars.worldinfo): From 23e54b6658dc48867f3bb443884d79f5ac349f59 Mon Sep 17 00:00:00 2001 From: somebody Date: Mon, 31 Jul 2023 12:30:37 -0500 Subject: [PATCH 13/14] WI: Workaround for Chrome order weirdness Chrome fires `blur()` before deleting nodes, meaning the -1 WI was getting sent after being deleted, resulting in two `delete_new_world_info_entry` packets being sent to the browser. Really, it would be better to not do this full WI reset/sync cycle and just send state changes and update accordingly. That would stop all the WI weirdness probably. --- static/koboldai.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/static/koboldai.js b/static/koboldai.js index f7067851..3a646c5c 100644 --- a/static/koboldai.js +++ b/static/koboldai.js @@ -23,7 +23,13 @@ socket.on('error_popup', function(data){error_popup(data);}); socket.on("world_info_entry", function(data){process_world_info_entry(data);}); socket.on("world_info_entry_used_in_game", function(data){world_info_entry_used_in_game(data);}); socket.on("world_info_folder", function(data){world_info_folder(data);}); -socket.on("delete_new_world_info_entry", function(data){document.getElementById("world_info_-1").remove();}); +socket.on("delete_new_world_info_entry", function(data) { + const card = $el("#world_info_-1"); + // Prevent weird race condition/strange event call order where blur event + // fires before removal is finished on Chrome + card.removing = true + card.remove(); +}); socket.on("delete_world_info_entry", function(data){document.getElementById("world_info_"+data).remove();}); socket.on("delete_world_info_folder", function(data){document.getElementById("world_info_folder_"+data).remove();}); socket.on("error", function(data){show_error_message(data);}); @@ -3254,6 +3260,8 @@ function upload_file_without_save(file_box) { } function send_world_info(uid) { + const cardEl = document.getElementById(`world_info_${uid}`); + if (cardEl.removing) return; socket.emit("edit_world_info", world_info_data[uid]); } From d4001186df267997c7879911665e4d033caf4095 Mon Sep 17 00:00:00 2001 From: somebody Date: Mon, 31 Jul 2023 12:41:19 -0500 Subject: [PATCH 14/14] UI: Hold shift to skip confirmation dialog idea stolen from discord, who likely stole it from somebody else --- static/koboldai.js | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/static/koboldai.js b/static/koboldai.js index 3a646c5c..e1953525 100644 --- a/static/koboldai.js +++ b/static/koboldai.js @@ -1288,7 +1288,7 @@ function redrawPopup() { delete_icon.setAttribute("tooltip", "Delete"); delete_icon.id = row.path; delete_icon.setAttribute("folder", row.isFolder); - delete_icon.onclick = function () { + delete_icon.addEventListener("click", function(event) { const message = this.getAttribute("folder") == "true" ? "Do you really want to delete this folder and ALL files under it?" : "Do you really want to delete this file?"; const delId = this.id; @@ -1298,9 +1298,11 @@ function redrawPopup() { denyText="I've changed my mind!", confirmCallback=function() { socket.emit("popup_delete", delId); - } + }, + null, + event.shiftKey ); - }; + }); } icon_area.append(delete_icon); tr.append(icon_area); @@ -2248,10 +2250,11 @@ function world_info_entry(data) { delete_icon.id = "world_info_delete_"+data.uid; delete_icon.setAttribute("uid", data.uid); delete_icon.setAttribute("wi-title", data.title); - delete_icon.onclick = function () { + delete_icon.addEventListener("click", function (event) { const wiTitle = this.getAttribute("wi-title"); const wiUid = parseInt(this.getAttribute("uid")); const wiElement = this.parentElement.parentElement; + deleteConfirmation([ {text: "You're about to delete World Info entry "}, {text: wiTitle, format: "bold"}, @@ -2265,9 +2268,11 @@ function world_info_entry(data) { } else { socket.emit("delete_world_info", wiUid); } - } + }, + null, + event.shiftKey ); - } + }); const wiImgContainer = world_info_card.querySelector(".world_info_image_container"); const wiImg = wiImgContainer.querySelector(".world_info_image"); @@ -2742,8 +2747,9 @@ function world_info_folder(data) { delete_button.classList.add("cursor"); delete_button.setAttribute("folder", folder_name); delete_button.textContent = "delete"; - delete_button.onclick = function () { + delete_button.addEventListener("click", function (event) { const folderName = this.getAttribute("folder"); + deleteConfirmation([ {text: "You're about to delete World Info folder "}, {text: folderName, format: "bold"}, @@ -2753,9 +2759,11 @@ function world_info_folder(data) { ], confirmText="Go for it.", denyText="I've changed my mind!", - confirmCallback=function() { socket.emit("delete_wi_folder", folderName); } + confirmCallback=function() { socket.emit("delete_wi_folder", folderName); }, + null, + event.shiftKey ); - }; + }); delete_button.classList.add("delete"); title.append(delete_button); @@ -6946,9 +6954,13 @@ function sFormatted2HTML(sFormatted) { return outHTML; } -function deleteConfirmation(sFormatted, confirmText, denyText, confirmCallback, denyCallback) { +function deleteConfirmation(sFormatted, confirmText, denyText, confirmCallback, denyCallback=null, bypass=false) { + if (bypass) { + confirmCallback(); + return; + } + $el("#confirm-text").innerHTML = sFormatted2HTML(sFormatted); - $el("#confirm-confirm-button > .text").innerText = confirmText; $el("#confirm-deny-button > .text").innerText = denyText;