diff --git a/aiserver.py b/aiserver.py index db8304ef..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): @@ -6692,11 +6692,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 diff --git a/gensettings.py b/gensettings.py index 09e1abcb..ae84edaa 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", @@ -756,7 +756,7 @@ gensettingstf = [ "max": 1, "step": 1, "default": 0, - "tooltip": "If enabled, experimental features will be displayed in the UI.", + "tooltip": "If enabled, experimental features will be displayed in the UI. Note: These features have been determined to be too unstable for standard use, and may corrupt your data. You're on your own from here.", "menu_path": "Interface", "sub_path": "UI", "classname": "system", diff --git a/modeling/inference_model.py b/modeling/inference_model.py index 8b7f0e3e..48691bce 100644 --- a/modeling/inference_model.py +++ b/modeling/inference_model.py @@ -637,7 +637,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, diff --git a/static/koboldai.css b/static/koboldai.css index a08cae4d..ea5c00d3 100644 --- a/static/koboldai.css +++ b/static/koboldai.css @@ -1259,17 +1259,46 @@ 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 { + cursor: pointer; + opacity: 0.4; + font-size: 16px; + position: relative; } .tag .delete_icon { - cursor: pointer; + 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] { @@ -2851,6 +2880,10 @@ body { height: 100%; } +#welcome_text a { + text-decoration: underline; +} + .welcome_text { display: flex; height: 100%; @@ -3569,10 +3602,15 @@ h2 .material-icons-outlined { } .section_header { + font-weight: bold; margin-left: 2px; margin-bottom: 2px; } +.story_category_area > * > label { + user-select: none +} + .help_text { margin-left: 6px; margin-bottom: 0.7em; diff --git a/static/koboldai.js b/static/koboldai.js index fe154605..31f8c962 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);}); @@ -87,6 +93,7 @@ var initial_socketio_connection_occured = false; var selected_model_data; var supported_gen_modes = []; var privacy_mode_enabled = false; +var attention_wanting_wi_bar = null; var ai_busy = false; var can_show_options = false; @@ -1281,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; @@ -1291,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); @@ -2203,6 +2212,7 @@ function world_info_entry(data) { } else { world_info_card.classList.remove("used_in_game"); } + const title = world_info_card.querySelector('.world_info_title'); title.id = "world_info_title_"+data.uid; title.textContent = data.title; @@ -2210,7 +2220,7 @@ function world_info_entry(data) { title.setAttribute("original_text", data.title); title.setAttribute("contenteditable", true); title.classList.remove("pulse"); - title.ondragstart=function() {event.preventDefault();event.stopPropagation();}; + title.ondragstart=function(event) {event.preventDefault();event.stopPropagation();}; title.onblur = function () { this.parentElement.parentElement.setAttribute('draggable', 'true'); this.setAttribute('draggable', 'true'); @@ -2220,20 +2230,31 @@ function world_info_entry(data) { this.classList.add("pulse"); } } - world_info_card.addEventListener('dragstart', dragStart); - world_info_card.addEventListener('dragend', dragend); + + title.addEventListener("keydown", function(event) { + if (event.key === "Enter") { + event.preventDefault(); + this.blur(); + } + }); + title.addEventListener('dragenter', dragEnter) title.addEventListener('dragover', dragOver); title.addEventListener('dragleave', dragLeave); title.addEventListener('drop', drop); - delete_icon = world_info_card.querySelector('.world_info_delete'); + + world_info_card.addEventListener('dragstart', dragStart); + world_info_card.addEventListener('dragend', dragend); + + const delete_icon = world_info_card.querySelector('.world_info_delete'); 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"}, @@ -2247,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"); @@ -2342,15 +2365,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; @@ -2723,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"}, @@ -2734,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); @@ -3241,6 +3268,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]); } @@ -4443,161 +4472,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')) { @@ -6947,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; diff --git a/templates/story flyout.html b/templates/story flyout.html index d08bf8b8..e84001f6 100644 --- a/templates/story flyout.html +++ b/templates/story flyout.html @@ -97,7 +97,7 @@ - X + close