diff --git a/aiserver.py b/aiserver.py index 37cdff7c..becd469d 100644 --- a/aiserver.py +++ b/aiserver.py @@ -40,20 +40,22 @@ modellist = [ # Variables class vars: lastact = "" # The last action submitted to the generator - model = '' + model = "" noai = False # Runs the script without starting up the transformers pipeline aibusy = False # Stops submissions while the AI is working max_length = 500 # Maximum number of tokens to submit per action genamt = 60 # Amount of text for each action to generate - rep_pen = 1.0 # Generator repetition_penalty - temp = 0.9 # Generator temperature - top_p = 1.0 # Generator top_p + rep_pen = 1.0 # Default generator repetition_penalty + temp = 0.9 # Default generator temperature + top_p = 1.0 # Default generator top_p gamestarted = False prompt = "" memory = "" + authornote = "" + andepth = 3 # How far back in history to append author's note actions = [] mode = "play" # Whether the interface is in play, memory, or edit mode - editln = 0 # Which line was last selected in Edit Mode + editln = 0 # Which line was last selected in Edit Mode url = "https://api.inferkit.com/v1/models/standard/generate" # InferKit API URL apikey = "" # API key to use for InferKit API calls savedir = getcwd()+"\stories\\newstory.json" @@ -303,6 +305,13 @@ def get_message(msg): elif(msg['cmd'] == 'setoutput'): vars.genamt = int(msg['data']) emit('from_server', {'cmd': 'setlabeloutput', 'data': msg['data']}) + # Author's Note field update + elif(msg['cmd'] == 'anote'): + anotesubmit(msg['data']) + # Author's Note depth update + elif(msg['cmd'] == 'anotedepth'): + vars.andepth = int(msg['data']) + emit('from_server', {'cmd': 'setlabelanotedepth', 'data': msg['data']}) #==================================================================# # @@ -333,9 +342,20 @@ def actionsubmit(data): # Take submitted text and build the text to be given to generator #==================================================================# def calcsubmit(txt): + vars.lastact = txt # Store most recent action in memory (is this still needed?) + anotetxt = "" # Placeholder for Author's Note text + lnanote = 0 # Placeholder for Author's Note length + forceanote = False # In case we don't have enough actions to hit A.N. depth + anoteadded = False # In case our budget runs out before we hit A.N. depth + actionlen = len(vars.actions) + + # Build Author's Note if set + if(vars.authornote != ""): + anotetxt = "\n[Author's note: "+vars.authornote+"]\n" + # For all transformers models if(vars.model != "InferKit"): - vars.lastact = txt # Store most recent action in memory (is this still needed?) + anotetkns = [] # Placeholder for Author's Note tokens # Calculate token budget prompttkns = tokenizer.encode(vars.prompt) @@ -344,17 +364,28 @@ def calcsubmit(txt): memtokens = tokenizer.encode(vars.memory) lnmem = len(memtokens) - budget = vars.max_length - lnprompt - lnmem - vars.genamt + if(anotetxt != ""): + anotetkns = tokenizer.encode(anotetxt) + lnanote = len(anotetkns) - if(len(vars.actions) == 0): + budget = vars.max_length - lnprompt - lnmem - lnanote - vars.genamt + + if(actionlen == 0): # First/Prompt action - subtxt = vars.memory + vars.prompt - lnsub = len(memtokens+prompttkns) + subtxt = vars.memory + anotetxt + vars.prompt + lnsub = lnmem + lnprompt + lnanote + generate(subtxt, lnsub+1, lnsub+vars.genamt) else: + tokens = [] + + # Check if we have the action depth to hit our A.N. depth + if(anotetxt != "" and actionlen < vars.andepth): + forceanote = True + # Get most recent action tokens up to our budget - tokens = [] - for n in range(len(vars.actions)): + for n in range(actionlen): + if(budget <= 0): break acttkns = tokenizer.encode(vars.actions[(-1-n)]) @@ -366,9 +397,22 @@ def calcsubmit(txt): count = budget * -1 tokens = acttkns[count:] + tokens break - - # Add mmory & prompt tokens to beginning of bundle - tokens = memtokens + prompttkns + tokens + + # Inject Author's Note if we've reached the desired depth + if(n == vars.andepth-1): + if(anotetxt != ""): + tokens = anotetkns + tokens # A.N. len already taken from bdgt + anoteadded = True + + # Did we get to add the A.N.? If not, do it here + if(anotetxt != ""): + if((not anoteadded) or forceanote): + tokens = memtokens + anotetkns + prompttkns + tokens + else: + tokens = memtokens + prompttkns + tokens + else: + # Prepend Memory and Prompt before action tokens + tokens = memtokens + prompttkns + tokens # Send completed bundle to generator ln = len(tokens) @@ -379,9 +423,15 @@ def calcsubmit(txt): ) # For InferKit web API else: - budget = vars.max_length - len(vars.prompt) - len(vars.memory) - 1 + + # Check if we have the action depth to hit our A.N. depth + if(anotetxt != "" and actionlen < vars.andepth): + forceanote = True + + budget = vars.max_length - len(vars.prompt) - len(anotetxt) - len(vars.memory) - 1 subtxt = "" - for n in range(len(vars.actions)): + for n in range(actionlen): + if(budget <= 0): break actlen = len(vars.actions[(-1-n)]) @@ -392,12 +442,26 @@ def calcsubmit(txt): count = budget * -1 subtxt = vars.actions[(-1-n)][count:] + subtxt break + + # Inject Author's Note if we've reached the desired depth + if(n == vars.andepth-1): + if(anotetxt != ""): + subtxt = anotetxt + subtxt # A.N. len already taken from bdgt + anoteadded = True - # Add mmory & prompt tokens to beginning of bundle + # Format memory for inclusion (adding newline separator) + memsub = "" if(vars.memory != ""): - subtxt = vars.memory + "\n" + vars.prompt + subtxt + memsub = vars.memory + "\n" + + # Did we get to add the A.N.? If not, do it here + if(anotetxt != ""): + if((not anoteadded) or forceanote): + subtxt = memsub + anotetxt + vars.prompt + subtxt + else: + subtxt = memsub + vars.prompt + subtxt else: - subtxt = vars.prompt + subtxt + subtxt = memsub + vars.prompt + subtxt # Send it! ikrequest(subtxt) @@ -407,7 +471,12 @@ def calcsubmit(txt): #==================================================================# def generate(txt, min, max): print("{0}Min:{1}, Max:{2}, Txt:{3}{4}".format(colors.WARNING, min, max, txt, colors.ENDC)) - + + # Clear CUDA cache if using GPU + if(vars.hascuda and vars.usegpu): + torch.cuda.empty_cache() + + # Submit input text to generator genout = generator( txt, do_sample=True, @@ -421,6 +490,10 @@ def generate(txt, min, max): refresh_story() emit('from_server', {'cmd': 'texteffect', 'data': len(vars.actions)}) + # Clear CUDA cache again if using GPU + if(vars.hascuda and vars.usegpu): + torch.cuda.empty_cache() + set_aibusy(0) #==================================================================# @@ -460,6 +533,7 @@ def refresh_settings(): emit('from_server', {'cmd': 'updatetopp', 'data': vars.top_p}) emit('from_server', {'cmd': 'updatereppen', 'data': vars.rep_pen}) emit('from_server', {'cmd': 'updateoutlen', 'data': vars.genamt}) + emit('from_server', {'cmd': 'updatanotedepth', 'data': vars.andepth}) #==================================================================# # Sets the logical and display states for the AI Busy condition @@ -521,6 +595,7 @@ def togglememorymode(): vars.mode = "memory" emit('from_server', {'cmd': 'memmode', 'data': 'true'}) emit('from_server', {'cmd': 'setinputtext', 'data': vars.memory}) + emit('from_server', {'cmd': 'setanote', 'data': vars.authornote}) elif(vars.mode == "memory"): vars.mode = "play" emit('from_server', {'cmd': 'memmode', 'data': 'false'}) @@ -534,6 +609,17 @@ def memsubmit(data): vars.memory = data vars.mode = "play" emit('from_server', {'cmd': 'memmode', 'data': 'false'}) + + # Ask for contents of Author's Note field + emit('from_server', {'cmd': 'getanote', 'data': ''}) + +#==================================================================# +# Commit changes to Author's Note +#==================================================================# +def anotesubmit(data): + # Maybe check for length at some point + # For now just send it to storage + vars.authornote = data #==================================================================# # Assembles game data into a request to InferKit API @@ -611,11 +697,12 @@ def saveRequest(): js = {} #js["maxlegth"] = vars.max_length # This causes problems when switching to/from InfraKit #js["genamt"] = vars.genamt - js["rep_pen"] = vars.rep_pen - js["temp"] = vars.temp + #js["rep_pen"] = vars.rep_pen + #js["temp"] = vars.temp js["gamestarted"] = vars.gamestarted js["prompt"] = vars.prompt js["memory"] = vars.memory + js["authorsnote"] = vars.authornote js["actions"] = vars.actions js["savedir"] = path # Write it @@ -638,13 +725,18 @@ def loadRequest(): # Copy file contents to vars #vars.max_length = js["maxlegth"] # This causes problems when switching to/from InfraKit #vars.genamt = js["genamt"] - vars.rep_pen = js["rep_pen"] - vars.temp = js["temp"] + #vars.rep_pen = js["rep_pen"] + #vars.temp = js["temp"] vars.gamestarted = js["gamestarted"] vars.prompt = js["prompt"] vars.memory = js["memory"] vars.actions = js["actions"] vars.savedir = js["savedir"] + + # Try not to break older save files + if("authorsnote" in js): + vars.authornote = js["authorsnote"] + file.close() # Refresh game screen refresh_story() diff --git a/static/application.js b/static/application.js index eac60561..cc954558 100644 --- a/static/application.js +++ b/static/application.js @@ -1,8 +1,11 @@ //=================================================================// // VARIABLES //=================================================================// + +// Socket IO Object var socket; +// UI references for jQuery var button_newgame; var button_save; var button_load; @@ -20,33 +23,46 @@ var setting_temp; var setting_topp; var setting_reppen; var setting_outlen; +var label_temp; +var label_topp; +var label_reppen; +var label_outlen; +var anote_menu; +var anote_input; +var anote_labelcur; +var anote_slider; -var shift_down = false; +// Key states +var shift_down = false; var do_clear_ent = false; //=================================================================// // METHODS //=================================================================// -function enableButton(ref) { - ref.prop("disabled",false); - ref.removeClass("btn-secondary"); - ref.addClass("btn-primary"); +function enableButtons(refs) { + for(i=0; i *'); enableSendBtn(); - show(button_actback); - show(button_actmem); - show(button_actretry); - hide(button_delete); + show([button_actback, button_actmem, button_actretry]); + hide([button_delete]); input_text.val(""); } @@ -117,23 +133,20 @@ function editModeSelect(n) { } function enterMemoryMode() { - // Add class to each story chunk showMessage("Edit the memory to be sent with each request to the AI."); button_actmem.html("Cancel"); - hide(button_actback); - hide(button_actretry); - hide(button_actedit); - hide(button_delete); + hide([button_actback, button_actretry, button_actedit, button_delete]); + // Display Author's Note field + anote_menu.slideDown("fast"); } function exitMemoryMode() { - // Remove class to each story chunk hideMessage(); button_actmem.html("Memory"); - show(button_actback); - show(button_actretry); - show(button_actedit); + show([button_actback, button_actretry, button_actedit]); input_text.val(""); + // Hide Author's Note field + anote_menu.slideUp("fast"); } function dosubmit() { @@ -160,7 +173,7 @@ function newTextHighlight(ref) { $(document).ready(function(){ - // Bind references + // Bind UI references button_newgame = $('#btn_newgame'); button_save = $('#btn_save'); button_load = $('#btn_load'); @@ -182,6 +195,10 @@ $(document).ready(function(){ label_topp = $('#settoppcur'); label_reppen = $('#setreppencur'); label_outlen = $('#setoutputcur'); + anote_menu = $('#anoterowcontainer'); + anote_input = $('#anoteinput'); + anote_labelcur = $('#anotecur'); + anote_slider = $('#anotedepth'); // Connect to SocketIO server socket = io.connect('http://127.0.0.1:5000'); @@ -203,24 +220,16 @@ $(document).ready(function(){ // Enable or Disable buttons if(msg.data == "ready") { enableSendBtn(); - enableButton(button_actedit); - enableButton(button_actmem); - enableButton(button_actback); - enableButton(button_actretry); + enableButtons([button_actedit, button_actmem, button_actback, button_actretry]); hideWaitAnimation(); } else if(msg.data == "wait") { disableSendBtn(); - disableButton(button_actedit); - disableButton(button_actmem); - disableButton(button_actback); - disableButton(button_actretry); + disableButtons([button_actedit, button_actmem, button_actback, button_actretry]); showWaitAnimation(); } else if(msg.data == "start") { enableSendBtn(); - enableButton(button_actmem); - disableButton(button_actedit); - disableButton(button_actback); - disableButton(button_actretry); + enableButtons([button_actmem]); + disableButtons([button_actedit, button_actback, button_actretry]); } } else if(msg.cmd == "editmode") { // Enable or Disable edit mode @@ -276,6 +285,20 @@ $(document).ready(function(){ } else if(msg.cmd == "setlabeloutput") { // Update setting label with value from server label_outlen.html(msg.data); + } else if(msg.cmd == "updatanotedepth") { + // Send current Author's Note depth value to input + anote_slider.val(parseInt(msg.data)); + anote_labelcur.html(msg.data); + } else if(msg.cmd == "setlabelanotedepth") { + // Update setting label with value from server + anote_labelcur.html(msg.data); + } else if(msg.cmd == "getanote") { + // Request contents of Author's Note field + var txt = anote_input.val(); + socket.send({'cmd': 'anote', 'data': txt}); + } else if(msg.cmd == "setanote") { + // Set contents of Author's Note field + anote_input.val(msg.data); } }); @@ -341,6 +364,7 @@ $(document).ready(function(){ } }); + // Enter to submit, but not if holding shift input_text.keyup(function (ev) { if (ev.which == 13 && do_clear_ent) { input_text.val(""); diff --git a/static/custom.css b/static/custom.css index 2c45d319..97b6585e 100644 --- a/static/custom.css +++ b/static/custom.css @@ -85,7 +85,7 @@ chunk { } #input_text { - height: 60px; + height: 80px; resize: none; overflow:auto; background-color: #404040; @@ -103,6 +103,38 @@ chunk { left:5px; } +#anoterowcontainer { + display: none; +} + +#anoterow { + margin-top: 10px; + padding: 0px; + width: 100%; + display: grid; + grid-template-columns: 90% 10%; +} + +#anoterowleft { + padding-right: 10px; +} + +#anoteinput { + background-color: #404040; + color: #ffffff; +} + +#anoteslidelabel { + color: #ffffff; + display: grid; + grid-template-columns: 80% 20%; +} + +.anotelabel { + font-size: 10pt; + color: #ffffff; +} + .airange { width: 100px; } diff --git a/stories/sample_story.json b/stories/sample_story.json index d23ab757..9b4daaa9 100644 --- a/stories/sample_story.json +++ b/stories/sample_story.json @@ -1 +1 @@ -{"rep_pen": 1.2, "temp": 1.0, "gamestarted": true, "prompt": "Niko the kobold stalked carefully down the alley, his small scaly figure obscured by a dusky cloak that fluttered lightly in the cold winter breeze. Holding up his tail to keep it from dragging in the dirty snow that covered the cobblestone, he waited patiently for the butcher to turn his attention from his stall so that he could pilfer his next meal: a tender-looking", "memory": "Niko is a small red kobold.\nNiko has yellow, reptilian eyes and a long, scaly tail.\nNiko is hungry and looking to steal something to eat.", "actions": [" chicken. He crouched just slightly as he neared the stall to ensure that no one was watching, not that anyone would be dumb enough to hassle a small kobold. What else was there for a lowly kobold to", " do in a city? All that Niko needed to know was", " where to find the chicken and then how to make off with it.\n\nA soft thud caused Niko to quickly lift his head. Standing behind the stall where the butcher had been cutting his chicken,"], "savedir": ""} +{"gamestarted": true, "prompt": "Niko the kobold stalked carefully down the alley, his small scaly figure obscured by a dusky cloak that fluttered lightly in the cold winter breeze. Holding up his tail to keep it from dragging in the dirty snow that covered the cobblestone, he waited patiently for the butcher to turn his attention from his stall so that he could pilfer his next meal: a tender-looking", "memory": "Niko is a small red kobold.\nNiko has yellow, reptilian eyes and a long, scaly tail.\nNiko is hungry and looking to steal something to eat.", "authorsnote": "Things are about to get crazy for poor Niko.", "actions": [" chicken. He crouched just slightly as he neared the stall to ensure that no one was watching, not that anyone would be dumb enough to hassle a small kobold. What else was there for a lowly kobold to", " do in a city? All that Niko needed to know was", " where to find the chicken and then how to make off with it.\n\nA soft thud caused Niko to quickly lift his head. Standing behind the stall where the butcher had been cutting his chicken,"], "savedir": ""} \ No newline at end of file diff --git a/templates/index.html b/templates/index.html index e5545a7c..73fbeb13 100644 --- a/templates/index.html +++ b/templates/index.html @@ -102,7 +102,7 @@
- +
@@ -138,6 +138,39 @@
+
+
+
+
+ Author's Note +
+
+ +
+
+
+
+
+ Depth +
+
+ 3 +
+
+
+ +
+
+
+ 1 +
+
+ 5 +
+
+
+
+