diff --git a/aiserver.py b/aiserver.py index a592b6f2..ef95efab 100644 --- a/aiserver.py +++ b/aiserver.py @@ -7,6 +7,7 @@ # External packages from dataclasses import dataclass +import random import shutil import eventlet eventlet.monkey_patch(all=True, thread=False, os=False) @@ -6261,6 +6262,8 @@ def generate(txt, minimum, maximum, found_entries=None): gc.collect() torch.cuda.empty_cache() + maybe_review_story() + set_aibusy(0) #==================================================================# @@ -9930,13 +9933,19 @@ def UI_2_refresh_auto_memory(data): #==================================================================# # Story review zero-shot #==================================================================# -@socketio.on("story_review") -@logger.catch -def UI_2_story_review(data): - who = data["who"] - template = data.get("template", "\n\n%s's thoughts on this situation:\n\"") - prompt = template % who - logger.info(prompt) +def maybe_review_story(): + if not ( + koboldai_vars.commentary_characters + and koboldai_vars.commentary_chance + and koboldai_vars.commentary_enabled + ): + return + + if random.randrange(100) > koboldai_vars.commentary_chance: + return + + speaker_id, speaker_name = random.choice(list(koboldai_vars.commentary_characters.items())) + prompt = "\n\n%s's thoughts on what just happened in this story: \"" % speaker_name context = koboldai_vars.calc_ai_text( prompt, @@ -9949,10 +9958,8 @@ def UI_2_story_review(data): max_new=30, ).decoded[0] - out_text = re.sub(r"[\s\(\)]", " ", out_text) - # Beware contractions! - # out_text = re.sub(r"[\s!\.\?]'", " ", out_text) + while " " in out_text: out_text = out_text.replace(" ", " ") @@ -9961,7 +9968,7 @@ def UI_2_story_review(data): out_text = out_text.strip() out_text = utils.trimincompletesentence(out_text) - emit("show_story_review", {"who": who, "review": out_text}) + emit("show_story_review", {"who": speaker_name, "review": out_text, "id": speaker_id}) #==================================================================# # Get next 100 actions for infinate scroll diff --git a/koboldai_settings.py b/koboldai_settings.py index 4a63dbcd..d4360b8d 100644 --- a/koboldai_settings.py +++ b/koboldai_settings.py @@ -3,7 +3,7 @@ from dataclasses import dataclass import importlib import os, re, time, threading, json, pickle, base64, copy, tqdm, datetime, sys import shutil -from typing import Union +from typing import List, Union from io import BytesIO from flask import has_request_context, session from flask_socketio import SocketIO, join_room, leave_room @@ -898,6 +898,12 @@ class story_settings(settings): self.memory_attn_bias = 1 self.an_attn_bias = 1 self.chat_style = 0 + + # In percent!!! + self.commentary_chance = 0 + # id: {name} + self.commentary_characters = {} + self.commentary_enabled = False self.save_paths = SavePaths(os.path.join("stories", self.story_name or "Untitled")) @@ -2443,6 +2449,10 @@ class SavePaths: @property def generated_images(self) -> str: return os.path.join(self.base, "generated_images") + + @property + def commentator_pictures(self) -> str: + return os.path.join(self.base, "commentator_pictures") default_rand_range = [0.1, 1, 2] default_creativity_range = [0.8, 1] diff --git a/static/koboldai.css b/static/koboldai.css index 366f043c..392092a7 100644 --- a/static/koboldai.css +++ b/static/koboldai.css @@ -183,6 +183,22 @@ border-top-right-radius: var(--tabs_rounding); } +.dynamic-setting-container { + background-color: var(--setting_background); + color: var(--setting_text); + border-radius: var(--radius_settings_background); + padding: 2px; + margin-bottom: 5px; + + width: 100%; +} + +.dynamic-setting-bounds, .dynamic-setting-top { + width: 100%; + display: flex; + justify-content: space-between; +} + .setting_container { display: grid; @@ -788,7 +804,7 @@ border-top-right-radius: var(--tabs_rounding); } /* Tweaks */ -.tweak-container { +.wide-boolean-setting { display: flex; align-items: center; justify-content: space-between; @@ -1188,10 +1204,11 @@ body { margin-left: var(--setting_menu_closed_width_no_pins_width); margin-right: var(--flyout_menu_closed_width); grid-template-areas: "menuicon gamescreen options lefticon" + "menuicon review review lefticon" "menuicon theme theme lefticon" "menuicon inputrow inputrow lefticon"; grid-template-columns: 30px auto 30% 30px; - grid-template-rows: auto min-content 100px; + grid-template-rows: auto min-content min-content 100px; } .main-grid[option_length="0"][model_numseqs="1"] { grid-template-columns: 30px auto 0px 30px; @@ -2810,6 +2827,79 @@ body { .genre-inner > .x:hover { opacity: 1; } +/* Story Commentary */ + +#story-review { + grid-area: review; + min-height: 52px; + padding: 10px; + background-color: var(--gamescreen_background); + margin: 10px 0px; +} + +#story-review-img { + width: 48px; + height: 48px; + object-fit: cover; + float: left; + margin-right: 10px; +} + +#story-review-author { + display: block; + font-weight: bold; +} + +#story-commentary-settings { width: 100%; } + +.story-commentary-character { + display: flex; + align-items: stretch; + margin-bottom: 5px; +} + +.story-commentary-character > .image-container { + width: 48px; + height: 48px; + display: flex; + justify-content: center; + align-items: center; + background-color: var(--setting_background); + margin-right: 10px; +} + +.story-commentary-character > .image-container > img { + width: 100%; + height: 100%; + object-fit: cover; +} + +.story-commentary-character > .name { + border: none; + flex-grow: 1; +} + +.story-commentary-character > .close { + float: right; + color: white !important; + transform: scale(1) !important; + display: flex; + align-items: center; + margin-left: 8px; +} + +#story-commentary-settings > .add { + display: flex; + justify-content: center; + align-items: center; + width: 100%; + transform: scale(1) !important; + margin-top: 3px; +} + +#story-commentary-settings > p { + font-size: calc(1.1em + var(--font_size_adjustment)); +} /*---------------------------------- Global ------------------------------------------------*/ .hidden { @@ -3045,6 +3135,7 @@ h2 .material-icons-outlined { .help_text, .sample_order, .table-header-label, +.disabled, .noselect { -webkit-touch-callout: none; -webkit-user-select: none; diff --git a/static/koboldai.js b/static/koboldai.js index 78740806..204825a4 100644 --- a/static/koboldai.js +++ b/static/koboldai.js @@ -34,7 +34,6 @@ socket.on("log_message", function(data){process_log_message(data);}); socket.on("debug_message", function(data){console.log(data);}); socket.on("scratchpad_response", recieveScratchpadResponse); socket.on("show_error_notification", function(data) { reportError(data.title, data.text) }); -socket.on("show_story_review", showStoryReview); //socket.onAny(function(event_name, data) {console.log({"event": event_name, "class": data.classname, "data": data});}); // Must be done before any elements are made; we track their changes. @@ -69,6 +68,7 @@ var actions_data = {}; var setup_wi_toggles = []; var scroll_trigger_element = undefined; //undefined means not currently set. If set to null, it's disabled. var drag_id = null; +var story_commentary_characters = {}; const on_colab = $el("#on_colab").textContent == "true"; // Each entry into this array should be an object that looks like: @@ -141,9 +141,9 @@ let context_menu_cache = []; const shortcuts = [ {key: "s", desc: "Save Story", func: save_story}, {key: "o", desc: "Open Story", func: load_story_list}, - {key: "z", desc: "Undoes last story action", func: () => socket.emit("back", {}), criteria: canNavigateStoryHistory}, - {key: "y", desc: "Redoes last story action", func: () => socket.emit("redo", {}), criteria: canNavigateStoryHistory}, - {key: "e", desc: "Retries last story action", func: () => socket.emit("retry", {}), criteria: canNavigateStoryHistory}, + {key: "z", desc: "Undoes last story action", func: storyBack, criteria: canNavigateStoryHistory}, + {key: "y", desc: "Redoes last story action", func: storyRedo, criteria: canNavigateStoryHistory}, + {key: "e", desc: "Retries last story action", func: storyRetry, criteria: canNavigateStoryHistory}, {key: "m", desc: "Focuses Memory", func: () => focusEl("#memory")}, {key: "u", desc: "Focuses Author's Note", func: () => focusEl("#authors_notes")}, // CTRL-N is reserved :^( {key: "g", desc: "Focuses game text", func: () => focusEl("#input_text")}, @@ -221,6 +221,34 @@ function disconnect() { document.getElementById("disconnect_message").classList.remove("hidden"); } +function storySubmit() { + disruptStoryState(); + socket.emit('submit', {'data': document.getElementById('input_text').value, 'theme': document.getElementById('themetext').value}); + document.getElementById('input_text').value = ''; + document.getElementById('themetext').value = ''; +} + +function storyBack() { + disruptStoryState(); + socket.emit('back', {}); +} + +function storyRedo() { + disruptStoryState(); + socket.emit('redo', {}); +} + +function storyRetry() { + disruptStoryState(); + socket.emit('retry', {}); +} + +function disruptStoryState() { + // This function is responsible for wiping things away which are sensitive + // to story state + $el("#story-review").classList.add("hidden"); +} + function reset_story() { //console.log("Resetting story"); clearTimeout(calc_token_usage_timeout); @@ -3117,7 +3145,7 @@ function retry_from_here() { action_count = parseInt(document.getElementById("action_count").textContent); //console.log(chunk); for (let i = 0; i < (action_count-chunk); i++) { - socket.emit('back', {}); + storyBack(); } socket.emit('submit', {'data': "", 'theme': ""}); document.getElementById('input_text').value = ''; @@ -6683,13 +6711,178 @@ function imgGenRetry() { })(); -function requestStoryReview(who, template=null) { - let data = {who: who}; - if (template) data.template = template; - socket.emit("story_review", data); +(function() { + const characterContainer = $el(".story-commentary-characters"); + const settingsContainer = $el("#story-commentary-settings"); + const storyReviewImg = $el("#story-review-img"); + + function syncCommentatorCards() { + story_commentary_characters = {}; + for (const card of document.getElementsByClassName("story-commentary-character")) { + let idString = card.getAttribute("commentator-id"); + story_commentary_characters[idString] = card.querySelector(".name").value; + } + + socket.emit("var_change", { + ID: "story_commentary_characters", + value: story_commentary_characters + }); + } + + sync_hooks.push({ + class: "story", + name: "commentary_characters", + func: function(commentators) { + $(".story-commentary-character").remove(); + for (const [idString, name] of Object.entries(commentators)) { + makeCommentatorCard(idString, name) + } + } + }) + + function makeCommentatorCard(idString=null, name=null) { + + // String due to JS array keys and DOM attributes being strings. Sux! + while (!idString || $el(`[commentator-id="${idString}"`)) { + idString = Math.floor(Math.random() * 1_000_000_000).toString(); + } + + let card = $e("div", characterContainer, { + classes: ["story-commentary-character"], + "commentator-id": idString, + }); + let imageContainer = $e("div", card, {classes: ["image-container"]}); + let placeholderImage = $e("span", imageContainer, { + classes: ["material-icons-outlined"], + tooltip: "Upload a picture for this character", + innerText: "add_a_photo" + }); + let image = $e("img", imageContainer, { + classes: ["hidden"], + src: `/get_commentator_picture/${idString}` + }); + + image.addEventListener("load", function() { + image.classList.remove("hidden"); + placeholderImage.classList.add("hidden"); + }); + + let input = $e("input", card, {classes: ["name"], placeholder: "Character name"}); + if (name) input.value = name; + input.addEventListener("change", syncCommentatorCards); + + let deleteButton = $e("span", card, { + classes: ["close", "material-icons-outlined"], + tooltip: "Upload a picture for this character", + innerText: "clear" + }); + deleteButton.addEventListener("click", function() { + card.remove(); + syncCommentatorCards(); + }); + + const imgInput = $e("input", null, {type: "file", accept: "image/png,image/x-png,image/gif,image/jpeg"}); + imgInput.addEventListener("change", async function() { + const file = imgInput.files[0]; + if (file.type.split("/")[0] !== "image") { + reportError("Unable to upload commentary image", `File type ${file.type} is not a compatible image type!`) + return; + } + + let objectUrl = URL.createObjectURL(file); + placeholderImage.classList.add("hidden"); + image.src = objectUrl; + image.classList.remove("hidden"); + + let r = await fetch(`/set_commentator_picture/${idString}`, { + method: "POST", + body: file + }); + }); + + imageContainer.addEventListener("click", function() { + imgInput.click(); + }); + + characterContainer.scrollIntoView(); + } + + $el("#story-commentary-settings > .add").addEventListener("click", () => makeCommentatorCard()); + + async function showStoryReview(data) { + console.log(`${data.who}: ${data.review}`) + storyReviewImg.src = `/get_commentator_picture/${data.id}`; + $el("#story-review-author").innerText = data.who; + $el("#story-review-content").innerText = data.review; + + $el("#story-review").classList.remove("hidden"); + } + socket.on("show_story_review", showStoryReview); + + let x = $el("#story-commentary-enable").querySelector("input") + console.log(x) + + // Bootstrap toggle requires jQuery for events + $($el("#story-commentary-enable").querySelector("input")).change(function() { + socket.emit("var_change", { + ID: "story_commentary_enabled", + value: this.checked + }); + }); + + sync_hooks.push({ + class: "story", + name: "commentary_enabled", + func: function(commentaryEnabled) { + if (commentaryEnabled) { + settingsContainer.classList.remove("disabled"); + } else { + settingsContainer.classList.add("disabled"); + } + } + }); + + storyReviewImg.addEventListener("error", function() { + if (storyReviewImg.src === "/static/default_pfp.png") { + // Something has gone horribly wrong + return; + } + storyReviewImg.src = "/static/default_pfp.png"; + }); + + $el("#story-review-img").addEventListener +})(); + +for (const el of document.querySelectorAll("[sync-var]")) { + let varName = el.getAttribute("sync-var"); + + el.addEventListener("change", function() { + sync_to_server(this); + }); + + const proxy = $el(`[sync-proxy-host="${varName}"]`); + if (proxy) { + el.addEventListener("input", function() { + proxy.value = this.value; + }); + } + + let slug = varName.replaceAll(".", "_"); + el.classList.add("var_sync_" + slug); } -function showStoryReview(data) { - console.log(`${data.who}: ${data.review}`) +for (const proxy of document.querySelectorAll("[sync-proxy-host]")) { + let varName = proxy.getAttribute("sync-proxy-host"); + const hostEl = $el(`[sync-var="${varName}"]`); + if (!hostEl) { + throw Error(`Bad sync proxy host ${varName}`) + } + proxy.addEventListener("change", function() { + hostEl.value = this.value; + socket.emit("var_change", { + ID: varName.replaceAll(".", "_"), + value: this.value + }); + }); } \ No newline at end of file diff --git a/templates/index_new.html b/templates/index_new.html index d684f2fe..d1632de0 100644 --- a/templates/index_new.html +++ b/templates/index_new.html @@ -60,6 +60,13 @@
+ +
Commentators
+ + add + +