diff --git a/aiserver.py b/aiserver.py index ee2d3a77..8b062e5c 100644 --- a/aiserver.py +++ b/aiserver.py @@ -3597,6 +3597,40 @@ def get_message(msg): emit('from_server', {'cmd': 'set_debug', 'data': msg['data']}, broadcast=True) if vars.debug: send_debug() + elif(msg['cmd'] == 'getfieldbudget'): + unencoded = msg["data"]["unencoded"] + field = msg["data"]["field"] + + # Tokenizer may be undefined here when a model has not been chosen. + if "tokenizer" not in globals(): + # We don't have a tokenizer, just return nulls. + emit( + 'from_server', + {'cmd': 'showfieldbudget', 'data': {"length": None, "max": None, "field": field}}, + broadcast=True + ) + return + + header_length = len(tokenizer._koboldai_header) + max_tokens = vars.max_length - header_length - vars.sp_length - vars.genamt + + if not unencoded: + # Unencoded is empty, just return 0 + emit( + 'from_server', + {'cmd': 'showfieldbudget', 'data': {"length": 0, "max": max_tokens, "field": field}}, + broadcast=True + ) + else: + if field == "anoteinput": + unencoded = buildauthorsnote(unencoded, msg["data"]["anotetemplate"]) + tokens_length = len(tokenizer.encode(unencoded)) + + emit( + 'from_server', + {'cmd': 'showfieldbudget', 'data': {"length": tokens_length, "max": max_tokens, "field": field}}, + broadcast=True + ) #==================================================================# # Send userscripts list to client @@ -3927,6 +3961,12 @@ def actionredo(): #==================================================================# # #==================================================================# +def buildauthorsnote(authorsnote, template): + # Build Author's Note if set + if authorsnote == "": + return "" + return ("\n" + template + "\n").replace("<|>", authorsnote) + def calcsubmitbudgetheader(txt, **kwargs): # Scan for WorldInfo matches winfo, found_entries = checkworldinfo(txt, **kwargs) @@ -3937,11 +3977,7 @@ def calcsubmitbudgetheader(txt, **kwargs): else: mem = vars.memory - # Build Author's Note if set - if(vars.authornote != ""): - anotetxt = ("\n" + vars.authornotetemplate + "\n").replace("<|>", vars.authornote) - else: - anotetxt = "" + anotetxt = buildauthorsnote(vars.authornote, vars.authornotetemplate) return winfo, mem, anotetxt, found_entries diff --git a/static/application.js b/static/application.js index 4ba8e0b2..5bd66125 100644 --- a/static/application.js +++ b/static/application.js @@ -512,6 +512,16 @@ function addWiLine(ob) { $(".wisortable-excluded-dynamic").removeClass("wisortable-excluded-dynamic"); $(this).parent().css("max-height", "").find(".wicomment").find(".form-control").css("max-height", ""); }); + + for (const wientry of document.getElementsByClassName("wientry")) { + // If we are uninitialized, skip. + if ($(wientry).closest(".wilistitem-uninitialized").length) continue; + + // add() will not add if the class is already present + wientry.classList.add("tokens-counted"); + } + + registerTokenCounters(); } function addWiFolder(uid, ob) { @@ -835,6 +845,7 @@ function exitMemoryMode() { button_actmem.html("Memory"); show([button_actback, button_actfwd, button_actretry, button_actwi]); input_text.val(""); + updateInputBudget(input_text[0]); // Hide Author's Note field anote_menu.slideUp("fast"); } @@ -2152,6 +2163,37 @@ function interpolateRGB(color0, color1, t) { ] } +function updateInputBudget(inputElement) { + let data = {"unencoded": inputElement.value, "field": inputElement.id}; + + if (inputElement.id === "anoteinput") { + data["anotetemplate"] = $("#anotetemplate").val(); + } + + socket.send({"cmd": "getfieldbudget", "data": data}); +} + +function registerTokenCounters() { + // Add token counters to all input containers with the class of "tokens-counted", + // if a token counter is not already a child of said container. + for (const el of document.getElementsByClassName("tokens-counted")) { + if (el.getElementsByClassName("input-token-usage").length) continue; + + let span = document.createElement("span"); + span.classList.add("input-token-usage"); + span.innerText = "?/? Tokens"; + el.appendChild(span); + + let inputElement = el.querySelector("input, textarea"); + + inputElement.addEventListener("input", function() { + updateInputBudget(this); + }); + + updateInputBudget(inputElement); + } +} + //=================================================================// // READY/RUNTIME //=================================================================// @@ -2494,6 +2536,7 @@ $(document).ready(function(){ memorytext = msg.data; input_text.val(msg.data); } + updateInputBudget(input_text[0]); } else if(msg.cmd == "setmemory") { memorytext = msg.data; if(memorymode) { @@ -2615,6 +2658,7 @@ $(document).ready(function(){ } else if(msg.cmd == "setanote") { // Set contents of Author's Note field anote_input.val(msg.data); + updateInputBudget(anote_input[0]); } else if(msg.cmd == "setanotetemplate") { // Set contents of Author's Note Template field $("#anotetemplate").val(msg.data); @@ -2930,8 +2974,18 @@ $(document).ready(function(){ opt.innerHTML = engine[1]; $("#oaimodel")[0].appendChild(opt); } - enableButtons([load_model_accept]); + } else if(msg.cmd == 'showfieldbudget') { + let inputElement = document.getElementById(msg.data.field); + let tokenBudgetElement = inputElement.parentNode.getElementsByClassName("input-token-usage")[0]; + if (msg.data.max === null) { + tokenBudgetElement.innerText = ""; + } else { + let tokenLength = msg.data.length ?? "?"; + let tokenMax = msg.data.max ?? "?"; + tokenBudgetElement.innerText = `${tokenLength}/${tokenMax} Tokens`; + } } + enableButtons([load_model_accept]); }); socket.on('disconnect', function() { @@ -3399,6 +3453,15 @@ $(document).ready(function(){ if (handled) ev.preventDefault(); }); + + $("#anotetemplate").on("input", function() { + updateInputBudget(anote_input[0]); + }) + + registerTokenCounters(); + + updateInputBudget(input_text[0]); + }); diff --git a/static/custom.css b/static/custom.css index 082c4230..af238dc7 100644 --- a/static/custom.css +++ b/static/custom.css @@ -1695,3 +1695,30 @@ body.connected .popupfooter, .popupfooter.always-available { overflow-x: auto; white-space: nowrap; } + +.tokens-counted { + position: relative; +} + +.input-token-usage { + color: white; + position: absolute; + font-size: 10px; + bottom: 2px; + right: 5px; + + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +/* Override needed here due to the 10px right padding on inputrowleft; add 10 px. */ +#inputrowleft > .input-token-usage { + right: 15px; + bottom: 1px; +} + +.wientry > .input-token-usage { + bottom: 8px; +} \ No newline at end of file diff --git a/templates/index.html b/templates/index.html index 82e4ca93..27b50b78 100644 --- a/templates/index.html +++ b/templates/index.html @@ -157,7 +157,7 @@
-
+
@@ -170,7 +170,7 @@
Author's Note
-
+