Merge pull request #74 from one-some/ui2-context-viewer

Context viewer
This commit is contained in:
ebolam
2022-08-28 19:30:04 -04:00
committed by GitHub
5 changed files with 218 additions and 20 deletions

View File

@@ -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
#==================================================================#

View File

@@ -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")

View File

@@ -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;

View File

@@ -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("<br>", '<span class="material-icons-outlined context-symbol">keyboard_return</span>');
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");
});
});

View File

@@ -112,5 +112,32 @@
{% include 'templates.html' %}
</div>
<iframe id="download_iframe" style="display:none;"></iframe>
<div id="context-viewer-container" class="hidden">
<div id="context-viewer">
<div id="context-viewer-header">
<h3 class="noselect">Context Viewer</h3>
<div id="context-viewer-header-right">
<div>
<span class="noselect">Key:</span>
<div>
<span class="noselect context-block-example context-sp">Soft Prompt</span>
<span class="noselect context-block-example context-prompt">Prompt</span>
<span class="noselect context-block-example context-wi">World Info</span>
<span class="noselect context-block-example context-memory">Memory</span>
<span class="noselect context-block-example context-an">Author's Note</span>
<span class="noselect context-block-example context-action">Action</span>
</div>
</div>
<span id="context-viewer-close" class="material-icons-outlined">close</span>
</div>
</div>
<span class="help_text">
Context is the text the AI is sent when you ask it to generate text.
As the context is limited in size, you can use the Context Viewer to check if things you want the AI to know are in the context.
</span>
<div id="context-container"></div>
</div>
</div>
</body>
</html>