diff --git a/aiserver.py b/aiserver.py index 4c40ce9c..96249ce7 100644 --- a/aiserver.py +++ b/aiserver.py @@ -4233,14 +4233,17 @@ def apiactionsubmit(data, use_memory=False, use_world_info=False, use_story=Fals mem = koboldai_vars.memory + "\n" else: mem = koboldai_vars.memory + if(use_authors_note and koboldai_vars.authornote != ""): anotetxt = ("\n" + koboldai_vars.authornotetemplate + "\n").replace("<|>", koboldai_vars.authornote) else: anotetxt = "" + MIN_STORY_TOKENS = 8 story_tokens = [] mem_tokens = [] wi_tokens = [] + story_budget = lambda: koboldai_vars.max_length - koboldai_vars.sp_length - koboldai_vars.genamt - len(tokenizer._koboldai_header) - len(story_tokens) - len(mem_tokens) - len(wi_tokens) budget = lambda: story_budget() + MIN_STORY_TOKENS if budget() < 0: @@ -4248,15 +4251,20 @@ def apiactionsubmit(data, use_memory=False, use_world_info=False, use_story=Fals "msg": f"Your Max Tokens setting is too low for your current soft prompt and tokenizer to handle. It needs to be at least {koboldai_vars.max_length - budget()}.", "type": "token_overflow", }}), mimetype="application/json", status=500)) + if use_memory: mem_tokens = tokenizer.encode(utils.encodenewlines(mem))[-budget():] + if use_world_info: world_info, _ = checkworldinfo(data, force_use_txt=True, scan_story=use_story) wi_tokens = tokenizer.encode(utils.encodenewlines(world_info))[-budget():] + if use_story: if koboldai_vars.useprompt: story_tokens = tokenizer.encode(utils.encodenewlines(koboldai_vars.prompt))[-budget():] + story_tokens = tokenizer.encode(utils.encodenewlines(data))[-story_budget():] + story_tokens + if use_story: for i, action in enumerate(reversed(koboldai_vars.actions.values())): if story_budget() <= 0: @@ -4267,6 +4275,7 @@ def apiactionsubmit(data, use_memory=False, use_world_info=False, use_story=Fals story_tokens = tokenizer.encode(utils.encodenewlines(anotetxt))[-story_budget():] + story_tokens if not koboldai_vars.useprompt: story_tokens = tokenizer.encode(utils.encodenewlines(koboldai_vars.prompt))[-budget():] + story_tokens + tokens = tokenizer._koboldai_header + mem_tokens + wi_tokens + story_tokens assert story_budget() >= 0 minimum = len(tokens) + 1 @@ -4428,7 +4437,8 @@ def calcsubmitbudget(actionlen, winfo, mem, anotetxt, actions, submission=None, budget -= tknlen else: count = budget * -1 - tokens = acttkns[count:] + tokens + truncated_action_tokens = acttkns[count:] + tokens = truncated_action_tokens + tokens budget = 0 break @@ -4450,6 +4460,7 @@ def calcsubmitbudget(actionlen, winfo, mem, anotetxt, actions, submission=None, # Did we get to add the A.N.? If not, do it here if(anotetxt != ""): if((not anoteadded) or forceanote): + # header, mem, wi, anote, prompt, actions tokens = (tokenizer._koboldai_header if koboldai_vars.model not in ("Colab", "API", "OAI") else []) + memtokens + witokens + anotetkns + prompttkns + tokens else: tokens = (tokenizer._koboldai_header if koboldai_vars.model not in ("Colab", "API", "OAI") else []) + memtokens + witokens + prompttkns + tokens @@ -4460,6 +4471,7 @@ def calcsubmitbudget(actionlen, winfo, mem, anotetxt, actions, submission=None, # Send completed bundle to generator assert len(tokens) <= koboldai_vars.max_length - lnsp - koboldai_vars.genamt - budget_deduction ln = len(tokens) + lnsp + return tokens, ln+1, ln+koboldai_vars.genamt #==================================================================# diff --git a/koboldai_settings.py b/koboldai_settings.py index c3c497b2..44d9638e 100644 --- a/koboldai_settings.py +++ b/koboldai_settings.py @@ -98,6 +98,7 @@ class koboldai_vars(object): self._model_settings.reset_for_model_load() def calc_ai_text(self, submitted_text=""): + context = [] token_budget = self.max_length used_world_info = [] if self.tokenizer is None: @@ -105,19 +106,32 @@ class koboldai_vars(object): else: used_tokens = self.sp_length text = "" + + # TODO: We may want to replace the "text" variable with a list-type + # class of context blocks, the class having a __str__ function. + if self.sp: + context.append({"type": "soft_prompt", "text": f"<{self.sp_length} tokens of Soft Prompt.>"}) + # Header is never used? + # if koboldai_vars.model not in ("Colab", "API", "OAI") and self.tokenizer._koboldai_header: + # context.append({"type": "header", "text": f"{len(self.tokenizer._koboldai_header}) self.worldinfo_v2.reset_used_in_game() #Add memory memory_length = self.max_memory_length if self.memory_length > self.max_memory_length else self.memory_length + memory_text = None if memory_length+used_tokens <= token_budget: if self.memory_length > self.max_memory_length: if self.tokenizer is None: - text = self.memory + memory_text = self.memory else: - text += self.tokenizer.decode(self.tokenizer.encode(self.memory)[-self.max_memory_length-1:]) + memory_text = self.tokenizer.decode(self.tokenizer.encode(self.memory)[-self.max_memory_length-1:]) else: - text += self.memory + memory_text = self.memory + + context.append({"type": "memory", "text": memory_text}) + if memory_text: + text += memory_text #Add constant world info entries to memory for wi in self.worldinfo_v2: @@ -126,7 +140,9 @@ class koboldai_vars(object): used_tokens+=wi['token_length'] used_world_info.append(wi['uid']) self.worldinfo_v2.set_world_info_used(wi['uid']) - text += wi['content'] + wi_text = wi['content'] + context.append({"type": "world_info", "text": wi_text}) + text += wi_text #Add prompt lenght/text if we're set to always use prompt if self.useprompt: @@ -151,15 +167,18 @@ class koboldai_vars(object): if used_tokens+0 if 'token_length' not in wi else wi['token_length'] <= token_budget: used_tokens+=wi['token_length'] used_world_info.append(wi['uid']) - text += wi['content'] + wi_text = wi['content'] + context.append({"type": "world_info", "text": wi_text}) + text += wi_text self.worldinfo_v2.set_world_info_used(wi['uid']) - if self.prompt_length > self.max_prompt_length: - if self.tokenizer is None: - text += self.prompt - else: - text += self.tokenizer.decode(self.tokenizer.encode(self.prompt)[-self.max_prompt_length-1:]) - else: - text += self.prompt + + prompt_text = self.prompt + if self.tokenizer and self.prompt_length > self.max_prompt_length: + if self.tokenizer: + prompt_text += self.tokenizer.decode(self.tokenizer.encode(self.prompt)[-self.max_prompt_length-1:]) + + text += prompt_text + context.append({"type": "prompt", "text": self.prompt}) self.prompt_in_ai = True else: self.prompt_in_ai = False @@ -169,13 +188,18 @@ class koboldai_vars(object): #Start going through the actions backwards, adding it to the text if it fits and look for world info entries game_text = "" + game_context = [] + authors_note_final = self.authornotetemplate.replace("<|>", self.authornote) used_all_tokens = False for i in range(len(self.actions)-1, -1, -1): if len(self.actions) - i == self.andepth and self.authornote != "": - game_text = "{}{}".format(self.authornotetemplate.replace("<|>", self.authornote), game_text) + game_text = "{}{}".format(authors_note_final, game_text) + game_context.insert(0, {"type": "authors_note", "text": authors_note_final}) if self.actions.actions[i]["Selected Text Length"]+used_tokens <= token_budget and not used_all_tokens: used_tokens += self.actions.actions[i]["Selected Text Length"] - game_text = "{}{}".format(self.actions.actions[i]["Selected Text"], game_text) + selected_text = self.actions.actions[i]["Selected Text"] + game_text = "{}{}".format(selected_text, game_text) + game_context.insert(0, {"type": "action", "text": selected_text}) self.actions.set_action_in_ai(i) #Now we need to check for used world info entries for wi in self.worldinfo_v2: @@ -196,7 +220,9 @@ class koboldai_vars(object): if used_tokens+0 if 'token_length' not in wi else wi['token_length'] <= token_budget: used_tokens+=wi['token_length'] used_world_info.append(wi['uid']) - game_text = "{}{}".format(wi['content'], game_text) + wi_text = wi["content"] + game_text = "{}{}".format(wi_text, game_text) + game_context.insert(0, {"type": "world_info", "text": wi_text}) self.worldinfo_v2.set_world_info_used(wi['uid']) else: self.actions.set_action_in_ai(i, used=False) @@ -204,7 +230,8 @@ class koboldai_vars(object): #if we don't have enough actions to get to author's note depth then we just add it right before the game text if len(self.actions) < self.andepth and self.authornote != "": - game_text = "{}{}".format(self.authornotetemplate.replace("<|>", self.authornote), game_text) + game_text = "{}{}".format(authors_note_final, game_text) + game_context.insert(0, {"type": "authors_note", "text": authors_note_final}) if not self.useprompt: if self.prompt_length + used_tokens < token_budget: @@ -228,18 +255,25 @@ class koboldai_vars(object): if used_tokens+0 if 'token_length' not in wi else wi['token_length'] <= token_budget: used_tokens+=wi['token_length'] used_world_info.append(wi['uid']) - text += wi['content'] + wi_text = wi["content"] + text += wi_text + context.append({"type": "world_info", "text": wi_text}) self.worldinfo_v2.set_world_info_used(wi['uid']) self.prompt_in_ai = True else: self.prompt_in_ai = False text += self.prompt + context.append({"type": "prompt", "text": self.prompt}) text += game_text + context += game_context + if self.tokenizer is None: tokens = [] else: tokens = self.tokenizer.encode(text) + + self.context = context return tokens, used_tokens, used_tokens+self.genamt def __setattr__(self, name, value): @@ -444,7 +478,7 @@ class model_settings(settings): class story_settings(settings): local_only_variables = ['socketio', 'tokenizer', 'koboldai_vars'] - no_save_variables = ['socketio', 'tokenizer', 'koboldai_vars'] + no_save_variables = ['socketio', 'tokenizer', 'koboldai_vars', 'context'] settings_name = "story" def __init__(self, socketio, koboldai_vars, tokenizer=None): self.socketio = socketio @@ -503,6 +537,7 @@ class story_settings(settings): self.max_prompt_length = 512 self.max_authornote_length = 512 self.prompt_in_ai = False + self.context = [] def save_story(self): print("Saving") diff --git a/static/koboldai.css b/static/koboldai.css index e12d25c6..fe784ae9 100644 --- a/static/koboldai.css +++ b/static/koboldai.css @@ -804,6 +804,7 @@ td.server_vars { padding-right: 35px; margin-bottom: 10px; flex-shrink: 0; + cursor: pointer; } .token_breakdown div { @@ -1555,6 +1556,87 @@ body { .model_setting_item_input { width:95%; } +/*------------------------------ Context Viewer --------------------------------------------*/ +#context-viewer-container { + position: absolute; + left: 0px; + top: 0px; + + display: flex; + justify-content: center; + align-items: center; + + background-color: rgba(0, 0, 0, 0.7); + + width: 100vw; + height: 100vh; + + z-index: 20; +} + +#context-viewer { + display: flex; + flex-direction: column; + + width: 50%; + height: 75%; + padding-bottom: 10px; + background-color: var(--layer1_palette); +} + +#context-viewer-header { + display: flex; + justify-content: space-between; + + padding: 5px; + + background-color: var(--background); + margin-bottom: 3px; +} + +#context-viewer-header-right { + display: flex; + flex-direction: row; +} + +#context-viewer-close { + cursor: pointer; + float: right; +} + +#context-viewer-header > h3 { + margin: 0px; + margin-top: 3px; +} + +#context-container { + overflow-y: auto; + height: 100%; + flex-grow: 1; + padding: 0px 10px; +} + +.context-symbol { + font-size: 1em !important; + position: relative; + top: 3px; + opacity: 0.5; +} + +.context-block, .context-block-example{ + margin: 0px 2px; +} + +.context-block:hover { + outline: 1px solid gray; +} + +.context-sp {background-color: var(--context_colors_soft_prompt);} +.context-prompt {background-color: var(--context_colors_prompt);} +.context-wi {background-color: var(--context_colors_world_info);} +.context-memory {background-color: var(--context_colors_memory);} +.context-an {background-color: var(--context_colors_authors_notes);} +.context-action {background-color: var(--context_colors_game_text);} /*---------------------------------- Global ------------------------------------------------*/ .hidden { @@ -1719,7 +1801,11 @@ h2 .material-icons-outlined { cursor: pointer; } -.material-icons-outlined, .collapsable_header, .section_header, .help_text { +.material-icons-outlined, +.collapsable_header, +.section_header, +.help_text, +.noselect { -webkit-touch-callout: none; -webkit-user-select: none; -khtml-user-select: none; diff --git a/static/koboldai.js b/static/koboldai.js index 79bf5507..f974bc6d 100644 --- a/static/koboldai.js +++ b/static/koboldai.js @@ -470,6 +470,9 @@ function var_changed(data) { option.setAttribute("title", sp[1][1]); item.append(option); } + //Special case for context viewer + } else if (data.classname == "story" && data.name == "context") { + update_context(data.value); //Basic Data Syncing } else { var elements_to_change = document.getElementsByClassName("var_sync_"+data.classname.replace(" ", "_")+"_"+data.name.replace(" ", "_")); @@ -2042,6 +2045,33 @@ function update_bias_slider_value(slider) { slider.parentElement.parentElement.querySelector(".bias_slider_cur").textContent = slider.value; } +function update_context(data) { + $(".context-block").remove(); + + for (const entry of data) { + console.log(entry); + let contextClass = "context-" + ({ + soft_prompt: "sp", + prompt: "prompt", + world_info: "wi", + memory: "memory", + authors_note: "an", + action: "action" + }[entry.type]); + + let el = document.createElement("span"); + el.classList.add("context-block"); + el.classList.add(contextClass); + el.innerText = entry.text; + + el.innerHTML = el.innerHTML.replaceAll("
", 'keyboard_return'); + + document.getElementById("context-container").appendChild(el); + } + + +} + function save_model_settings(settings = saved_settings) { for (item of document.getElementsByClassName('setting_item_input')) { if (item.id.includes("model")) { @@ -2877,4 +2907,12 @@ $(document).ready(function(){ if (enabledTweaks.includes(path)) $(toggle).bootstrapToggle("on"); } + + $("#context-viewer-close").click(function() { + document.getElementById("context-viewer-container").classList.add("hidden"); + }); + + $(".token_breakdown").click(function() { + document.getElementById("context-viewer-container").classList.remove("hidden"); + }); }); diff --git a/templates/index_new.html b/templates/index_new.html index 0afad93e..f21e54f8 100644 --- a/templates/index_new.html +++ b/templates/index_new.html @@ -112,5 +112,32 @@ {% include 'templates.html' %} + + \ No newline at end of file