From 9559347f82529f43a0a2f239b7830722c66b93f3 Mon Sep 17 00:00:00 2001 From: Javalar <39832337+Javalar@users.noreply.github.com> Date: Tue, 15 Jun 2021 00:59:08 -0400 Subject: [PATCH 01/14] Update or remove targeted chunks in Game Screen (#2) --- .gitignore | 8 +++++- aiserver.py | 59 +++++++++++++++++++++++++++++++++---------- static/application.js | 33 ++++++++++++++++++++++++ 3 files changed, 86 insertions(+), 14 deletions(-) diff --git a/.gitignore b/.gitignore index ff83d6e7..33d3c719 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,10 @@ client.settings # Ignore stories file except for test_story stories/* -!stories/sample_story.json \ No newline at end of file +!stories/sample_story.json + +# Ignore PyCharm project files. +.idea + +# Ignore compiled Python files. +*.pyc diff --git a/aiserver.py b/aiserver.py index 8dcec2af..c6b27672 100644 --- a/aiserver.py +++ b/aiserver.py @@ -9,6 +9,8 @@ from os import path, getcwd import tkinter as tk from tkinter import messagebox import json +from typing import Literal, Union + import requests import html @@ -666,12 +668,12 @@ def actionsubmit(data): data = applyinputformatting(data) # Store the result in the Action log vars.actions.append(data) - + update_story_chunk('last') + if(not vars.noai): # Off to the tokenizer! calcsubmit(data) else: - refresh_story() set_aibusy(0) #==================================================================# @@ -687,7 +689,7 @@ def actionretry(data): # Remove last action if possible and resubmit if(len(vars.actions) > 0): vars.actions.pop() - refresh_story() + remove_story_chunk(len(vars.actions) + 1) calcsubmit('') #==================================================================# @@ -698,8 +700,9 @@ def actionback(): return # Remove last index of actions and refresh game screen if(len(vars.actions) > 0): + action_index = len(vars.actions) vars.actions.pop() - refresh_story() + remove_story_chunk(len(vars.actions) + 1) #==================================================================# # Take submitted text and build the text to be given to generator @@ -936,7 +939,7 @@ def genresult(genout): # Add formatted text to Actions array and refresh the game screen vars.actions.append(genout) - refresh_story() + update_story_chunk('last') emit('from_server', {'cmd': 'texteffect', 'data': len(vars.actions)}) #==================================================================# @@ -955,9 +958,6 @@ def genselect(genout): # Send sequences to UI for selection emit('from_server', {'cmd': 'genseqs', 'data': genout}) - - # Refresh story for any input text - refresh_story() #==================================================================# # Send selected sequence to action log and refresh UI @@ -966,7 +966,7 @@ def selectsequence(n): if(len(vars.genseqs) == 0): return vars.actions.append(vars.genseqs[int(n)]["generated_text"]) - refresh_story() + update_story_chunk('last') emit('from_server', {'cmd': 'texteffect', 'data': len(vars.actions)}) emit('from_server', {'cmd': 'hidegenseqs', 'data': ''}) vars.genseqs = [] @@ -1096,6 +1096,39 @@ def refresh_story(): text_parts.extend(('', html.escape(item), '')) emit('from_server', {'cmd': 'updatescreen', 'data': formatforhtml(''.join(text_parts))}) + +#==================================================================# +# Signals the Game Screen to update one of the chunks +#==================================================================# +def update_story_chunk(idx: Union[int, Literal['last']]): + if idx == 'last': + if len(vars.actions) <= 1: + # In this case, we are better off just refreshing the whole thing as the + # prompt might not have been shown yet (with a "Generating story..." + # messsage instead). + refresh_story() + return + + idx = len(vars.actions) + + if idx == 0: + text = vars.prompt + else: + # Actions are 0 based, but in chunks 0 is the prompt. + # So the chunk index is one more than the corresponding action index. + text = vars.actions[idx - 1] + + chunk_text = f'{formatforhtml(html.escape(text))}' + emit('from_server', {'cmd': 'updatechunk', 'data': {'index': idx, 'html': chunk_text, 'last': (idx == len(vars.actions))}}) + + +#==================================================================# +# Signals the Game Screen to remove one of the chunks +#==================================================================# +def remove_story_chunk(idx: int): + emit('from_server', {'cmd': 'removechunk', 'data': idx}) + + #==================================================================# # Sends the current generator settings to the Game Menu #==================================================================# @@ -1161,7 +1194,7 @@ def editsubmit(data): vars.actions[vars.editln-1] = data vars.mode = "play" - refresh_story() + update_story_chunk(vars.editln) emit('from_server', {'cmd': 'texteffect', 'data': vars.editln}) emit('from_server', {'cmd': 'editmode', 'data': 'false'}) @@ -1176,7 +1209,7 @@ def deleterequest(): else: del vars.actions[vars.editln-1] vars.mode = "play" - refresh_story() + remove_story_chunk(vars.editln) emit('from_server', {'cmd': 'editmode', 'data': 'false'}) #==================================================================# @@ -1382,7 +1415,7 @@ def ikrequest(txt): genout = req.json()["data"]["text"] print("{0}{1}{2}".format(colors.CYAN, genout, colors.END)) vars.actions.append(genout) - refresh_story() + update_story_chunk('last') emit('from_server', {'cmd': 'texteffect', 'data': len(vars.actions)}) set_aibusy(0) @@ -1432,7 +1465,7 @@ def oairequest(txt, min, max): genout = req.json()["choices"][0]["text"] print("{0}{1}{2}".format(colors.CYAN, genout, colors.END)) vars.actions.append(genout) - refresh_story() + update_story_chunk('last') emit('from_server', {'cmd': 'texteffect', 'data': len(vars.actions)}) set_aibusy(0) diff --git a/static/application.js b/static/application.js index e5ebc806..2cf66dfe 100644 --- a/static/application.js +++ b/static/application.js @@ -599,6 +599,39 @@ $(document).ready(function(){ setTimeout(function () { $('#gamescreen').animate({scrollTop: $('#gamescreen').prop('scrollHeight')}, 1000); }, 5); + } else if(msg.cmd == "updatechunk") { + const {index, html, last} = msg.data; + const existingChunk = game_text.children(`#n${index}`) + const newChunk = $(html); + if (existingChunk.length > 0) { + // Update existing chunk + existingChunk.before(newChunk); + existingChunk.remove(); + } else { + // Append at the end + game_text.append(newChunk); + } + if(last) { + // Scroll to bottom of text if it's the last element + setTimeout(function () { + $('#gamescreen').animate({scrollTop: $('#gamescreen').prop('scrollHeight')}, 1000); + }, 5); + } + } else if(msg.cmd == "removechunk") { + let index = msg.data; + // Remove the chunk + game_text.children(`#n${index}`).remove() + // Shift all existing chunks by 1 + index++; + while (true) { + const chunk = game_text.children(`#n${index}`) + if(chunk.length === 0) { + break; + } + const newIndex = index - 1; + chunk.attr('n', newIndex.toString()).attr('id', `n${newIndex}`); + index++; + } } else if(msg.cmd == "setgamestate") { // Enable or Disable buttons if(msg.data == "ready") { From 81aba7cba8070426ae09361763caa634a325030e Mon Sep 17 00:00:00 2001 From: Yves Dubois Date: Tue, 15 Jun 2021 01:02:11 -0400 Subject: [PATCH 02/14] Fix typo --- aiserver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aiserver.py b/aiserver.py index c6b27672..6703fb53 100644 --- a/aiserver.py +++ b/aiserver.py @@ -1105,7 +1105,7 @@ def update_story_chunk(idx: Union[int, Literal['last']]): if len(vars.actions) <= 1: # In this case, we are better off just refreshing the whole thing as the # prompt might not have been shown yet (with a "Generating story..." - # messsage instead). + # message instead). refresh_story() return From 62ad2f0228f199c668b5499bfc3344649cf97cac Mon Sep 17 00:00:00 2001 From: Gnome Ann <> Date: Tue, 24 Aug 2021 18:32:48 -0400 Subject: [PATCH 03/14] Code indentation consistency --- static/application.js | 24 ++++++++++----------- static/custom.css | 50 +++++++++++++++++++++---------------------- templates/index.html | 2 +- 3 files changed, 38 insertions(+), 38 deletions(-) diff --git a/static/application.js b/static/application.js index 014cf86d..eb93a51c 100644 --- a/static/application.js +++ b/static/application.js @@ -813,11 +813,11 @@ $(document).ready(function(){ seqselmenu = $("#seqselmenu"); seqselcontents = $("#seqselcontents"); - // Connect to SocketIO server - socket = io.connect(window.document.origin); + // Connect to SocketIO server + socket = io.connect(window.document.origin); socket.on('from_server', function(msg) { - if(msg.cmd == "connected") { + if(msg.cmd == "connected") { // Connected to Server Actions connected = true; connect_status.html("Connected to KoboldAI Process!"); @@ -882,19 +882,19 @@ $(document).ready(function(){ }, 5); } } else if(msg.cmd == "removechunk") { - let index = msg.data; - // Remove the chunk - game_text.children(`#n${index}`).remove() + let index = msg.data; + // Remove the chunk + game_text.children(`#n${index}`).remove() // Shift all existing chunks by 1 index++; - while (true) { - const chunk = game_text.children(`#n${index}`) + while(true) { + const chunk = game_text.children(`#n${index}`) if(chunk.length === 0) { break; } - const newIndex = index - 1; - chunk.attr('n', newIndex.toString()).attr('id', `n${newIndex}`); - index++; + const newIndex = index - 1; + chunk.attr('n', newIndex.toString()).attr('id', `n${newIndex}`); + index++; } } else if(msg.cmd == "setgamestate") { // Enable or Disable buttons @@ -1093,7 +1093,7 @@ $(document).ready(function(){ } else if(msg.cmd == "runs_remotely") { hide([button_loadfrfile, button_savetofile, button_import, button_importwi]); } - }); + }); socket.on('disconnect', function() { connected = false; diff --git a/static/custom.css b/static/custom.css index 70a23124..15ee3521 100644 --- a/static/custom.css +++ b/static/custom.css @@ -355,8 +355,8 @@ chunk, chunk * { .colorfade, .colorfade * { -moz-transition:color 1s ease-in; - -o-transition:color 1s ease-in; - -webkit-transition:color 1s ease-in; + -o-transition:color 1s ease-in; + -webkit-transition:color 1s ease-in; transition:color 1s ease-in; } @@ -443,21 +443,21 @@ chunk, chunk * { } .helpicon { - display: inline-block; - font-family: sans-serif; - font-weight: bold; - text-align: center; - width: 2.2ex; - height: 2.4ex; - font-size: 1.4ex; - line-height: 1.8ex; - border-radius: 1.2ex; - margin-right: 4px; - padding: 1px; - color: #295071; - background: #ffffff; - border: 1px solid white; - text-decoration: none; + display: inline-block; + font-family: sans-serif; + font-weight: bold; + text-align: center; + width: 2.2ex; + height: 2.4ex; + font-size: 1.4ex; + line-height: 1.8ex; + border-radius: 1.2ex; + margin-right: 4px; + padding: 1px; + color: #295071; + background: #ffffff; + border: 1px solid white; + text-decoration: none; } .helpicon:hover { @@ -521,8 +521,8 @@ chunk, chunk * { color: #ffffff; -moz-transition: background-color 0.25s ease-in; - -o-transition: background-color 0.25s ease-in; - -webkit-transition: background-color 0.25s ease-in; + -o-transition: background-color 0.25s ease-in; + -webkit-transition: background-color 0.25s ease-in; transition: background-color 0.25s ease-in; } @@ -532,12 +532,12 @@ chunk, chunk * { } .navbar .navbar-nav .nav-link:hover { - border-radius: 5px; + border-radius: 5px; background-color: #98bcdb; } .navbar .navbar-nav .nav-link:focus { - border-radius: 5px; + border-radius: 5px; background-color: #98bcdb; } @@ -603,8 +603,8 @@ chunk, chunk * { color: #ffffff; -moz-transition: background-color 0.25s ease-in; - -o-transition: background-color 0.25s ease-in; - -webkit-transition: background-color 0.25s ease-in; + -o-transition: background-color 0.25s ease-in; + -webkit-transition: background-color 0.25s ease-in; transition: background-color 0.25s ease-in; } @@ -654,8 +654,8 @@ chunk, chunk * { padding: 5px; color: #ffffff; -moz-transition: all 0.15s ease-in; - -o-transition: all 0.15s ease-in; - -webkit-transition: all 0.15s ease-in; + -o-transition: all 0.15s ease-in; + -webkit-transition: all 0.15s ease-in; transition: all 0.15s ease-in; } diff --git a/templates/index.html b/templates/index.html index 97eee72d..196b6038 100644 --- a/templates/index.html +++ b/templates/index.html @@ -246,7 +246,7 @@ Unsaved data will be lost.

Below you can input a genre suggestion for the AI to loosely base the story on (For example Horror or Cowboy).
-
+
From 09030573e5a5735420f706f25f104d2ea0cc5b2f Mon Sep 17 00:00:00 2001 From: Gnome Ann <> Date: Tue, 24 Aug 2021 18:40:12 -0400 Subject: [PATCH 04/14] Broadcast updatechunk and removechunk --- aiserver.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/aiserver.py b/aiserver.py index 7c59e3de..9ece1f24 100644 --- a/aiserver.py +++ b/aiserver.py @@ -1344,14 +1344,14 @@ def update_story_chunk(idx: Union[int, Literal['last']]): text = vars.actions[idx - 1] chunk_text = f'{formatforhtml(html.escape(text))}' - emit('from_server', {'cmd': 'updatechunk', 'data': {'index': idx, 'html': chunk_text, 'last': (idx == len(vars.actions))}}) + emit('from_server', {'cmd': 'updatechunk', 'data': {'index': idx, 'html': chunk_text, 'last': (idx == len(vars.actions))}}, broadcast=True) #==================================================================# # Signals the Game Screen to remove one of the chunks #==================================================================# def remove_story_chunk(idx: int): - emit('from_server', {'cmd': 'removechunk', 'data': idx}) + emit('from_server', {'cmd': 'removechunk', 'data': idx}, broadcast=True) #==================================================================# From 735fc9431bebe9fa595f251dca10d51019a4dbb2 Mon Sep 17 00:00:00 2001 From: Gnome Ann <> Date: Thu, 19 Aug 2021 11:06:39 -0400 Subject: [PATCH 05/14] Still HTML-escape chunks if Adventure is off (cherry picked from commit 3409d8c12e3fbb1e3232f2df82740b012e8f3604) --- aiserver.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/aiserver.py b/aiserver.py index 9ece1f24..22fdd64d 100644 --- a/aiserver.py +++ b/aiserver.py @@ -1316,8 +1316,9 @@ def applyoutputformatting(txt): def refresh_story(): text_parts = ['', html.escape(vars.prompt), ''] for idx, item in enumerate(vars.actions, start=1): + item = html.escape(item) if vars.adventure: # Add special formatting to adventure actions - item = vars.acregex_ui.sub('\\1', html.escape(item)) + item = vars.acregex_ui.sub('\\1', item) text_parts.extend(('', item, '')) emit('from_server', {'cmd': 'updatescreen', 'gamestarted': vars.gamestarted, 'data': formatforhtml(''.join(text_parts))}, broadcast=True) From b1c6aee8d31589bc0f620d7ea8a87be69a4162d3 Mon Sep 17 00:00:00 2001 From: Gnome Ann <> Date: Tue, 24 Aug 2021 19:02:52 -0400 Subject: [PATCH 06/14] Integrate inline chunk editor and Adventure mode with Javalar's branch --- aiserver.py | 16 ++++++++++------ static/application.js | 4 +++- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/aiserver.py b/aiserver.py index 22fdd64d..a5d7e339 100644 --- a/aiserver.py +++ b/aiserver.py @@ -1314,12 +1314,12 @@ def applyoutputformatting(txt): # Sends the current story content to the Game Screen #==================================================================# def refresh_story(): - text_parts = ['', html.escape(vars.prompt), ''] + text_parts = ['', html.escape(vars.prompt), ''] for idx, item in enumerate(vars.actions, start=1): item = html.escape(item) if vars.adventure: # Add special formatting to adventure actions item = vars.acregex_ui.sub('\\1', item) - text_parts.extend(('', item, '')) + text_parts.extend(('', item, '')) emit('from_server', {'cmd': 'updatescreen', 'gamestarted': vars.gamestarted, 'data': formatforhtml(''.join(text_parts))}, broadcast=True) @@ -1344,7 +1344,11 @@ def update_story_chunk(idx: Union[int, Literal['last']]): # So the chunk index is one more than the corresponding action index. text = vars.actions[idx - 1] - chunk_text = f'{formatforhtml(html.escape(text))}' + item = html.escape(text) + if vars.adventure: # Add special formatting to adventure actions + item = vars.acregex_ui.sub('\\1', item) + + chunk_text = f'{formatforhtml(item)}' emit('from_server', {'cmd': 'updatechunk', 'data': {'index': idx, 'html': chunk_text, 'last': (idx == len(vars.actions))}}, broadcast=True) @@ -1451,7 +1455,7 @@ def inlineedit(chunk, data): else: vars.actions[chunk-1] = data - refresh_story() + update_story_chunk(chunk) emit('from_server', {'cmd': 'texteffect', 'data': chunk}, broadcast=True) emit('from_server', {'cmd': 'editmode', 'data': 'false'}, broadcast=True) @@ -1463,12 +1467,12 @@ def inlinedelete(chunk): # Don't delete prompt if(chunk == 0): # Send error message - refresh_story() + update_story_chunk(chunk) emit('from_server', {'cmd': 'errmsg', 'data': "Cannot delete the prompt."}) emit('from_server', {'cmd': 'editmode', 'data': 'false'}, broadcast=True) else: del vars.actions[chunk-1] - refresh_story() + remove_story_chunk(chunk) emit('from_server', {'cmd': 'editmode', 'data': 'false'}, broadcast=True) #==================================================================# diff --git a/static/application.js b/static/application.js index eb93a51c..b8035582 100644 --- a/static/application.js +++ b/static/application.js @@ -848,7 +848,6 @@ $(document).ready(function(){ } game_text.html(msg.data); // Make content editable if need be - $("chunk").attr('tabindex', -1) $('chunk').attr('contenteditable', allowedit); hide([$('#curtain')]); // Scroll to bottom of text @@ -875,6 +874,8 @@ $(document).ready(function(){ // Append at the end game_text.append(newChunk); } + newChunk.attr('contenteditable', allowedit); + hide([$('#curtain')]); if(last) { // Scroll to bottom of text if it's the last element setTimeout(function () { @@ -896,6 +897,7 @@ $(document).ready(function(){ chunk.attr('n', newIndex.toString()).attr('id', `n${newIndex}`); index++; } + hide([$('#curtain')]); } else if(msg.cmd == "setgamestate") { // Enable or Disable buttons if(msg.data == "ready") { From b52f782d92fd1850bfe1644a1f90f92ae33277de Mon Sep 17 00:00:00 2001 From: Gnome Ann <> Date: Wed, 25 Aug 2021 01:21:01 -0400 Subject: [PATCH 07/14] Disable post-edit blurring and the curtain Because we don't need them anymore. This, combined with the chunk optimization, will allow you to seamlessly continue editing a different chunk after you've finished editing your current one; e.g. if you edit a chunk and use arrow keys, backspace or the mouse to select a different chunk, you can immediately start editing your new chunk without it defocusing. This patch really has killed two birds with one stone, eh? Thanks, Javalar! --- static/application.js | 8 -------- 1 file changed, 8 deletions(-) diff --git a/static/application.js b/static/application.js index b8035582..65b65009 100644 --- a/static/application.js +++ b/static/application.js @@ -730,13 +730,6 @@ function submitEditedChunk(event) { return; } - show([$('#curtain')]); - setTimeout(function () { - if(document.activeElement.tagName == "CHUNK") { - document.activeElement.blur(); - } - }, 5); - chunk = current_editing_chunk; current_editing_chunk = null; @@ -849,7 +842,6 @@ $(document).ready(function(){ game_text.html(msg.data); // Make content editable if need be $('chunk').attr('contenteditable', allowedit); - hide([$('#curtain')]); // Scroll to bottom of text if(newly_loaded) { setTimeout(function () { From 5603bf0584c9872c7aeed03d9f84ea755a28239b Mon Sep 17 00:00:00 2001 From: Gnome Ann <> Date: Wed, 25 Aug 2021 19:22:35 -0400 Subject: [PATCH 08/14] Hide messages on updatechunk and removechunk --- static/application.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/static/application.js b/static/application.js index 65b65009..905242d5 100644 --- a/static/application.js +++ b/static/application.js @@ -855,6 +855,7 @@ $(document).ready(function(){ $('#gamescreen').animate({scrollTop: $('#gamescreen').prop('scrollHeight')}, 1000); }, 5); } else if(msg.cmd == "updatechunk") { + hideMessage(); const {index, html, last} = msg.data; const existingChunk = game_text.children(`#n${index}`) const newChunk = $(html); @@ -875,6 +876,7 @@ $(document).ready(function(){ }, 5); } } else if(msg.cmd == "removechunk") { + hideMessage(); let index = msg.data; // Remove the chunk game_text.children(`#n${index}`).remove() From 5604d7f3172e19cbc4dbc66e47f31e89c46f7ce9 Mon Sep 17 00:00:00 2001 From: Gnome Ann <> Date: Wed, 25 Aug 2021 19:25:57 -0400 Subject: [PATCH 09/14] Replace non-breaking spaces with regular spaces --- static/application.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/application.js b/static/application.js index 905242d5..c7eab749 100644 --- a/static/application.js +++ b/static/application.js @@ -735,7 +735,7 @@ function submitEditedChunk(event) { // Submit the edited chunk if it's not empty, otherwise delete it if(chunk.innerText.length) { - socket.send({'cmd': 'inlineedit', 'chunk': chunk.getAttribute("n"), 'data': chunk.innerText}); + socket.send({'cmd': 'inlineedit', 'chunk': chunk.getAttribute("n"), 'data': chunk.innerText.replace(/\u00a0/g, " ")}); } else { socket.send({'cmd': 'inlinedelete', 'data': chunk.getAttribute("n")}); } From 796f5ffd05558be497bd86feced1fd1a56ba2c76 Mon Sep 17 00:00:00 2001 From: Gnome Ann <> Date: Wed, 25 Aug 2021 19:28:26 -0400 Subject: [PATCH 10/14] Make vars.actions a dictionary instead of a list --- aiserver.py | 74 ++++++++++++++++++++++++++++--------------- static/application.js | 11 ------- structures.py | 40 +++++++++++++++++++++++ 3 files changed, 89 insertions(+), 36 deletions(-) create mode 100644 structures.py diff --git a/aiserver.py b/aiserver.py index a5d7e339..a1d734cd 100644 --- a/aiserver.py +++ b/aiserver.py @@ -10,6 +10,7 @@ import re import tkinter as tk from tkinter import messagebox import json +import collections from typing import Literal, Union import requests @@ -23,6 +24,7 @@ import fileops import gensettings from utils import debounce import utils +import structures import breakmodel #==================================================================# @@ -78,7 +80,7 @@ class vars: memory = "" # Text submitted to memory field authornote = "" # Text submitted to Author's Note field andepth = 3 # How far back in history to append author's note - actions = [] # Array of actions submitted by user and AI + actions = structures.KoboldStoryRegister() # Actions submitted by user and AI worldinfo = [] # Array of World Info key/value objects badwords = [] # Array of str/chr values that should be removed from output badwordsids = [] # Tokenized array of badwords @@ -883,8 +885,9 @@ def actionretry(data): set_aibusy(1) # Remove last action if possible and resubmit if(len(vars.actions) > 0): + last_key = vars.actions.get_last_key() vars.actions.pop() - remove_story_chunk(len(vars.actions) + 1) + remove_story_chunk(last_key) calcsubmit('') #==================================================================# @@ -895,9 +898,9 @@ def actionback(): return # Remove last index of actions and refresh game screen if(len(vars.actions) > 0): - action_index = len(vars.actions) + last_key = vars.actions.get_last_key() vars.actions.pop() - remove_story_chunk(len(vars.actions) + 1) + remove_story_chunk(last_key) #==================================================================# # Take submitted text and build the text to be given to generator @@ -964,11 +967,13 @@ def calcsubmit(txt): forceanote = True # Get most recent action tokens up to our budget - for n in range(actionlen): + n = 0 + for key in reversed(vars.actions): + chunk = vars.actions[key] if(budget <= 0): break - acttkns = tokenizer.encode(vars.actions[(-1-n)]) + acttkns = tokenizer.encode(chunk) tknlen = len(acttkns) if(tknlen < budget): tokens = acttkns + tokens @@ -984,6 +989,7 @@ def calcsubmit(txt): if(anotetxt != ""): tokens = anotetkns + tokens # A.N. len already taken from bdgt anoteadded = True + n += 1 # If we're not using the prompt every time and there's still budget left, # add some prompt. @@ -1038,17 +1044,19 @@ def calcsubmit(txt): subtxt = "" prompt = vars.prompt - for n in range(actionlen): + n = 0 + for key in reversed(vars.actions): + chunk = vars.actions[key] if(budget <= 0): break - actlen = len(vars.actions[(-1-n)]) + actlen = len(chunk) if(actlen < budget): - subtxt = vars.actions[(-1-n)] + subtxt + subtxt = chunk + subtxt budget -= actlen else: count = budget * -1 - subtxt = vars.actions[(-1-n)][count:] + subtxt + subtxt = chunk[count:] + subtxt budget = 0 break @@ -1065,6 +1073,7 @@ def calcsubmit(txt): if(anotetxt != ""): subtxt = anotetxt + subtxt # A.N. len already taken from bdgt anoteadded = True + n += 1 # Did we get to add the A.N.? If not, do it here if(anotetxt != ""): @@ -1157,7 +1166,7 @@ def genresult(genout): # Add formatted text to Actions array and refresh the game screen vars.actions.append(genout) update_story_chunk('last') - emit('from_server', {'cmd': 'texteffect', 'data': len(vars.actions)}, broadcast=True) + emit('from_server', {'cmd': 'texteffect', 'data': vars.actions.get_last_key()}, broadcast=True) #==================================================================# # Send generator sequences to the UI for selection @@ -1184,7 +1193,7 @@ def selectsequence(n): return vars.actions.append(vars.genseqs[int(n)]["generated_text"]) update_story_chunk('last') - emit('from_server', {'cmd': 'texteffect', 'data': len(vars.actions)}, broadcast=True) + emit('from_server', {'cmd': 'texteffect', 'data': vars.actions.get_last_key()}, broadcast=True) emit('from_server', {'cmd': 'hidegenseqs', 'data': ''}, broadcast=True) vars.genseqs = [] @@ -1243,7 +1252,7 @@ def sendtocolab(txt, min, max): # Add formatted text to Actions array and refresh the game screen #vars.actions.append(genout) #refresh_story() - #emit('from_server', {'cmd': 'texteffect', 'data': len(vars.actions)}) + #emit('from_server', {'cmd': 'texteffect', 'data': vars.actions.get_last_key()}) set_aibusy(0) else: @@ -1315,7 +1324,9 @@ def applyoutputformatting(txt): #==================================================================# def refresh_story(): text_parts = ['', html.escape(vars.prompt), ''] - for idx, item in enumerate(vars.actions, start=1): + for idx in vars.actions: + item = vars.actions[idx] + idx += 1 item = html.escape(item) if vars.adventure: # Add special formatting to adventure actions item = vars.acregex_ui.sub('\\1', item) @@ -1335,7 +1346,7 @@ def update_story_chunk(idx: Union[int, Literal['last']]): refresh_story() return - idx = len(vars.actions) + idx = vars.actions.get_last_key() + 1 if idx == 0: text = vars.prompt @@ -1349,7 +1360,7 @@ def update_story_chunk(idx: Union[int, Literal['last']]): item = vars.acregex_ui.sub('\\1', item) chunk_text = f'{formatforhtml(item)}' - emit('from_server', {'cmd': 'updatechunk', 'data': {'index': idx, 'html': chunk_text, 'last': (idx == len(vars.actions))}}, broadcast=True) + emit('from_server', {'cmd': 'updatechunk', 'data': {'index': idx, 'html': chunk_text, 'last': (idx == vars.actions.get_last_key())}}, broadcast=True) #==================================================================# @@ -1601,10 +1612,19 @@ def checkworldinfo(txt): txt = "" depth += 1 + if(ln > 0): + chunks = collections.deque() + i = 0 + for key in reversed(vars.actions): + chunk = vars.actions[key] + chunks.appendleft(chunk) + if(i == depth): + break + if(ln >= depth): - txt = "".join(vars.actions[(depth*-1):]) + txt = "".join(chunks) elif(ln > 0): - txt = vars.prompt + "".join(vars.actions[(depth*-1):]) + txt = vars.prompt + "".join(chunks) elif(ln == 0): txt = vars.prompt @@ -1697,7 +1717,7 @@ def ikrequest(txt): print("{0}{1}{2}".format(colors.CYAN, genout, colors.END)) vars.actions.append(genout) update_story_chunk('last') - emit('from_server', {'cmd': 'texteffect', 'data': len(vars.actions)}, broadcast=True) + emit('from_server', {'cmd': 'texteffect', 'data': vars.actions.get_last_key()}, broadcast=True) set_aibusy(0) else: @@ -1747,7 +1767,7 @@ def oairequest(txt, min, max): print("{0}{1}{2}".format(colors.CYAN, genout, colors.END)) vars.actions.append(genout) update_story_chunk('last') - emit('from_server', {'cmd': 'texteffect', 'data': len(vars.actions)}, broadcast=True) + emit('from_server', {'cmd': 'texteffect', 'data': vars.actions.get_last_key()}, broadcast=True) set_aibusy(0) else: @@ -1825,7 +1845,7 @@ def saveRequest(savpath): js["prompt"] = vars.prompt js["memory"] = vars.memory js["authorsnote"] = vars.authornote - js["actions"] = vars.actions + js["actions"] = tuple(vars.actions.values()) js["worldinfo"] = [] # Extract only the important bits of WI @@ -1876,10 +1896,14 @@ def loadRequest(loadpath): vars.gamestarted = js["gamestarted"] vars.prompt = js["prompt"] vars.memory = js["memory"] - vars.actions = js["actions"] vars.worldinfo = [] vars.lastact = "" vars.lastctx = "" + + del vars.actions + vars.actions = structures.KoboldStoryRegister() + for s in js["actions"]: + vars.actions.append(s) # Try not to break older save files if("authorsnote" in js): @@ -1988,7 +2012,7 @@ def importgame(): vars.prompt = "" vars.memory = ref["memory"] vars.authornote = ref["authorsNote"] if type(ref["authorsNote"]) is str else "" - vars.actions = [] + vars.actions = structures.KoboldStoryRegister() vars.worldinfo = [] vars.lastact = "" vars.lastctx = "" @@ -2047,7 +2071,7 @@ def importAidgRequest(id): vars.prompt = js["promptContent"] vars.memory = js["memory"] vars.authornote = js["authorsNote"] - vars.actions = [] + vars.actions = structures.KoboldStoryRegister() vars.worldinfo = [] vars.lastact = "" vars.lastctx = "" @@ -2113,7 +2137,7 @@ def newGameRequest(): vars.gamestarted = False vars.prompt = "" vars.memory = "" - vars.actions = [] + vars.actions = structures.KoboldStoryRegister() vars.authornote = "" vars.worldinfo = [] diff --git a/static/application.js b/static/application.js index c7eab749..d6574b8f 100644 --- a/static/application.js +++ b/static/application.js @@ -880,17 +880,6 @@ $(document).ready(function(){ let index = msg.data; // Remove the chunk game_text.children(`#n${index}`).remove() - // Shift all existing chunks by 1 - index++; - while(true) { - const chunk = game_text.children(`#n${index}`) - if(chunk.length === 0) { - break; - } - const newIndex = index - 1; - chunk.attr('n', newIndex.toString()).attr('id', `n${newIndex}`); - index++; - } hide([$('#curtain')]); } else if(msg.cmd == "setgamestate") { // Enable or Disable buttons diff --git a/structures.py b/structures.py new file mode 100644 index 00000000..287f92c1 --- /dev/null +++ b/structures.py @@ -0,0 +1,40 @@ +import collections +from typing import Iterable, Tuple + + +class KoboldStoryRegister(collections.OrderedDict): + ''' + Complexity-optimized class for keeping track of story chunks + ''' + + def __init__(self, sequence: Iterable[Tuple[int, str]] = ()): + super().__init__(sequence) + self.__next_id: int = len(sequence) + + def append(self, v: str) -> None: + self[self.__next_id] = v + self.increment_id() + + def pop(self) -> str: + return self.popitem()[1] + + def get_first_key(self) -> int: + return next(iter(self)) + + def get_last_key(self) -> int: + return next(reversed(self)) + + def __getitem__(self, k: int) -> str: + return super().__getitem__(k) + + def __setitem__(self, k: int, v: str) -> None: + return super().__setitem__(k, v) + + def increment_id(self) -> None: + self.__next_id += 1 + + def get_next_id(self) -> int: + return self.__next_id + + def set_next_id(self, x: int) -> None: + self.__next_id = x From b0d64985bb101c910f955b1d9034816a4f1fe754 Mon Sep 17 00:00:00 2001 From: Gnome Ann <> Date: Wed, 25 Aug 2021 19:56:57 -0400 Subject: [PATCH 11/14] Fix Retry and Back buttons popping the wrong chunk --- aiserver.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/aiserver.py b/aiserver.py index a1d734cd..4ff0c3a3 100644 --- a/aiserver.py +++ b/aiserver.py @@ -887,7 +887,7 @@ def actionretry(data): if(len(vars.actions) > 0): last_key = vars.actions.get_last_key() vars.actions.pop() - remove_story_chunk(last_key) + remove_story_chunk(last_key + 1) calcsubmit('') #==================================================================# @@ -900,7 +900,7 @@ def actionback(): if(len(vars.actions) > 0): last_key = vars.actions.get_last_key() vars.actions.pop() - remove_story_chunk(last_key) + remove_story_chunk(last_key + 1) #==================================================================# # Take submitted text and build the text to be given to generator From 27c7baab92ea37e162a701997572f1c58c8dacca Mon Sep 17 00:00:00 2001 From: Gnome Ann <> Date: Wed, 25 Aug 2021 23:58:12 -0400 Subject: [PATCH 12/14] Prevent some errors when the prompt is the only chunk --- aiserver.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/aiserver.py b/aiserver.py index 4ff0c3a3..5065f86b 100644 --- a/aiserver.py +++ b/aiserver.py @@ -1166,7 +1166,7 @@ def genresult(genout): # Add formatted text to Actions array and refresh the game screen vars.actions.append(genout) update_story_chunk('last') - emit('from_server', {'cmd': 'texteffect', 'data': vars.actions.get_last_key()}, broadcast=True) + emit('from_server', {'cmd': 'texteffect', 'data': vars.actions.get_last_key() if len(vars.actions) else 0}, broadcast=True) #==================================================================# # Send generator sequences to the UI for selection @@ -1193,7 +1193,7 @@ def selectsequence(n): return vars.actions.append(vars.genseqs[int(n)]["generated_text"]) update_story_chunk('last') - emit('from_server', {'cmd': 'texteffect', 'data': vars.actions.get_last_key()}, broadcast=True) + emit('from_server', {'cmd': 'texteffect', 'data': vars.actions.get_last_key() if len(vars.actions) else 0}, broadcast=True) emit('from_server', {'cmd': 'hidegenseqs', 'data': ''}, broadcast=True) vars.genseqs = [] @@ -1252,7 +1252,7 @@ def sendtocolab(txt, min, max): # Add formatted text to Actions array and refresh the game screen #vars.actions.append(genout) #refresh_story() - #emit('from_server', {'cmd': 'texteffect', 'data': vars.actions.get_last_key()}) + #emit('from_server', {'cmd': 'texteffect', 'data': vars.actions.get_last_key() if len(vars.actions) else 0}) set_aibusy(0) else: @@ -1346,7 +1346,7 @@ def update_story_chunk(idx: Union[int, Literal['last']]): refresh_story() return - idx = vars.actions.get_last_key() + 1 + idx = (vars.actions.get_last_key() if len(vars.actions) else 0) + 1 if idx == 0: text = vars.prompt @@ -1360,7 +1360,7 @@ def update_story_chunk(idx: Union[int, Literal['last']]): item = vars.acregex_ui.sub('\\1', item) chunk_text = f'{formatforhtml(item)}' - emit('from_server', {'cmd': 'updatechunk', 'data': {'index': idx, 'html': chunk_text, 'last': (idx == vars.actions.get_last_key())}}, broadcast=True) + emit('from_server', {'cmd': 'updatechunk', 'data': {'index': idx, 'html': chunk_text, 'last': (idx == (vars.actions.get_last_key() if len(vars.actions) else 0))}}, broadcast=True) #==================================================================# @@ -1717,7 +1717,7 @@ def ikrequest(txt): print("{0}{1}{2}".format(colors.CYAN, genout, colors.END)) vars.actions.append(genout) update_story_chunk('last') - emit('from_server', {'cmd': 'texteffect', 'data': vars.actions.get_last_key()}, broadcast=True) + emit('from_server', {'cmd': 'texteffect', 'data': vars.actions.get_last_key() if len(vars.actions) else 0}, broadcast=True) set_aibusy(0) else: @@ -1767,7 +1767,7 @@ def oairequest(txt, min, max): print("{0}{1}{2}".format(colors.CYAN, genout, colors.END)) vars.actions.append(genout) update_story_chunk('last') - emit('from_server', {'cmd': 'texteffect', 'data': vars.actions.get_last_key()}, broadcast=True) + emit('from_server', {'cmd': 'texteffect', 'data': vars.actions.get_last_key() if len(vars.actions) else 0}, broadcast=True) set_aibusy(0) else: From 8fd8612cca1111905ee212aebbeb1cb25fe6abe1 Mon Sep 17 00:00:00 2001 From: Gnome Ann <> Date: Thu, 26 Aug 2021 01:06:57 -0400 Subject: [PATCH 13/14] Adventure mode colouring now controlled by a CSS class So that we can just toggle the class instead of having aiserver.py send back the entire story. --- aiserver.py | 7 ++----- static/application.js | 5 +++++ static/custom.css | 6 +++--- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/aiserver.py b/aiserver.py index 5065f86b..781c335a 100644 --- a/aiserver.py +++ b/aiserver.py @@ -705,7 +705,6 @@ def get_message(msg): vars.adventure = msg['data'] settingschanged() refresh_settings() - refresh_story() elif(msg['cmd'] == 'importwi'): wiimportrequest() @@ -1328,8 +1327,7 @@ def refresh_story(): item = vars.actions[idx] idx += 1 item = html.escape(item) - if vars.adventure: # Add special formatting to adventure actions - item = vars.acregex_ui.sub('\\1', item) + item = vars.acregex_ui.sub('\\1', item) # Add special formatting to adventure actions text_parts.extend(('', item, '')) emit('from_server', {'cmd': 'updatescreen', 'gamestarted': vars.gamestarted, 'data': formatforhtml(''.join(text_parts))}, broadcast=True) @@ -1356,8 +1354,7 @@ def update_story_chunk(idx: Union[int, Literal['last']]): text = vars.actions[idx - 1] item = html.escape(text) - if vars.adventure: # Add special formatting to adventure actions - item = vars.acregex_ui.sub('\\1', item) + item = vars.acregex_ui.sub('\\1', item) # Add special formatting to adventure actions chunk_text = f'{formatforhtml(item)}' emit('from_server', {'cmd': 'updatechunk', 'data': {'index': idx, 'html': chunk_text, 'last': (idx == (vars.actions.get_last_key() if len(vars.actions) else 0))}}, broadcast=True) diff --git a/static/application.js b/static/application.js index d6574b8f..6de2a044 100644 --- a/static/application.js +++ b/static/application.js @@ -615,6 +615,11 @@ function setmodevisibility(state) { function setadventure(state) { adventure = state; + if(state) { + game_text.addClass("adventure"); + } else { + game_text.removeClass("adventure"); + } if(!memorymode){ setmodevisibility(state); } diff --git a/static/custom.css b/static/custom.css index 15ee3521..46df0afa 100644 --- a/static/custom.css +++ b/static/custom.css @@ -6,14 +6,14 @@ chunk { color: #ffffff; } -action { +#gametext.adventure action { color: #9ff7fa; font-weight: bold; } chunk[contenteditable="true"]:focus, chunk[contenteditable="true"]:focus * { - color: #cdf; - font-weight: normal; + color: #cdf !important; + font-weight: normal !important; } chunk, chunk * { From 6a0299837ce3e3491bda6c5a43ab3f76acb082b1 Mon Sep 17 00:00:00 2001 From: Gnome Ann <> Date: Thu, 26 Aug 2021 01:22:07 -0400 Subject: [PATCH 14/14] Make the green editing flash !important --- static/custom.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/custom.css b/static/custom.css index 46df0afa..69e96ee4 100644 --- a/static/custom.css +++ b/static/custom.css @@ -398,7 +398,7 @@ chunk, chunk * { } .edit-flash, .edit-flash * { - color: #3bf723; + color: #3bf723 !important; } .flex {