Added Formatting options

Added Bootstrap toggle library for UI
Added injection points for input/output modification
This commit is contained in:
KoboldAI Dev 2021-05-10 19:17:10 -04:00
parent 0e0947d93a
commit b55266a7c8
8 changed files with 316 additions and 20 deletions

View File

@ -4,15 +4,18 @@
# By: KoboldAIDev
#==================================================================#
# External packages
from os import path, getcwd
import tkinter as tk
from tkinter import messagebox
import json
import torch
# KoboldAI
import fileops
import gensettings
from utils import debounce
import utils
#==================================================================#
# Variables & Storage
@ -69,6 +72,7 @@ class vars:
hascuda = False # Whether torch has detected CUDA on the system
usegpu = False # Whether to launch pipeline with GPU support
custmodpth = "" # Filesystem location of custom model to run
formatoptns = {} # Container for state of formatting options
#==================================================================#
# Function to get model selection at startup
@ -349,6 +353,23 @@ def get_message(msg):
vars.andepth = int(msg['data'])
emit('from_server', {'cmd': 'setlabelanotedepth', 'data': msg['data']})
settingschanged()
# Format - Trim incomplete sentences
elif(msg['cmd'] == 'frmttriminc'):
if('frmttriminc' in vars.formatoptns):
vars.formatoptns["frmttriminc"] = msg['data']
settingschanged()
elif(msg['cmd'] == 'frmtrmblln'):
if('frmtrmblln' in vars.formatoptns):
vars.formatoptns["frmtrmblln"] = msg['data']
settingschanged()
elif(msg['cmd'] == 'frmtrmspch'):
if('frmtrmspch' in vars.formatoptns):
vars.formatoptns["frmtrmspch"] = msg['data']
settingschanged()
elif(msg['cmd'] == 'frmtadsnsp'):
if('frmtadsnsp' in vars.formatoptns):
vars.formatoptns["frmtadsnsp"] = msg['data']
settingschanged()
#==================================================================#
#
@ -368,6 +389,13 @@ def sendsettings():
else:
for set in gensettings.gensettingsik:
emit('from_server', {'cmd': 'addsetting', 'data': set})
# Send formatting options
for frm in gensettings.formatcontrols:
emit('from_server', {'cmd': 'addformat', 'data': frm})
# Add format key to vars if it wasn't loaded with client.settings
if(not frm["id"] in vars.formatoptns):
vars.formatoptns[frm["id"]] = False;
#==================================================================#
#
@ -375,14 +403,15 @@ def sendsettings():
def savesettings():
# Build json to write
js = {}
js["apikey"] = vars.apikey
js["andepth"] = vars.andepth
js["temp"] = vars.temp
js["top_p"] = vars.top_p
js["rep_pen"] = vars.rep_pen
js["genamt"] = vars.genamt
js["max_length"] = vars.max_length
js["ikgen"] = vars.ikgen
js["apikey"] = vars.apikey
js["andepth"] = vars.andepth
js["temp"] = vars.temp
js["top_p"] = vars.top_p
js["rep_pen"] = vars.rep_pen
js["genamt"] = vars.genamt
js["max_length"] = vars.max_length
js["ikgen"] = vars.ikgen
js["formatoptns"] = vars.formatoptns
# Write it
file = open("client.settings", "w")
@ -417,6 +446,8 @@ def loadsettings():
vars.max_length = js["max_length"]
if("ikgen" in js):
vars.ikgen = js["ikgen"]
if("formatoptns" in js):
vars.formatoptns = js["formatoptns"]
file.close()
@ -436,14 +467,22 @@ def actionsubmit(data):
return
set_aibusy(1)
if(not vars.gamestarted):
vars.gamestarted = True # Start the game
vars.prompt = data # Save this first action as the prompt
emit('from_server', {'cmd': 'updatescreen', 'data': 'Please wait, generating story...'}) # Clear the startup text from game screen
# Start the game
vars.gamestarted = True
# Save this first action as the prompt
vars.prompt = data
# Clear the startup text from game screen
emit('from_server', {'cmd': 'updatescreen', 'data': 'Please wait, generating story...'})
calcsubmit(data) # Run the first action through the generator
else:
# Dont append submission if it's a blank/continue action
if(data != ""):
# Apply input formatting & scripts before sending to tokenizer
data = applyinputformatting(data)
# Store the result in the Action log
vars.actions.append(data)
# Off to the tokenizer!
calcsubmit(data)
#==================================================================#
@ -594,7 +633,12 @@ def generate(txt, min, max):
temperature=vars.temp
)[0]["generated_text"]
print("{0}{1}{2}".format(colors.CYAN, genout, colors.END))
vars.actions.append(getnewcontent(genout))
# Format output before continuing
genout = applyoutputformatting(getnewcontent(genout))
# 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)})
@ -611,7 +655,7 @@ def formatforhtml(txt):
return txt.replace("\\r", "<br/>").replace("\\n", "<br/>").replace('\n', '<br/>').replace('\r', '<br/>')
#==================================================================#
# Strips submitted text from the text returned by the AI
# Strips submitted text from the text returned by the AI
#==================================================================#
def getnewcontent(txt):
ln = len(vars.actions)
@ -622,6 +666,35 @@ def getnewcontent(txt):
return (txt.split(delim)[-1])
#==================================================================#
# Applies chosen formatting options to text submitted to AI
#==================================================================#
def applyinputformatting(txt):
# Add sentence spacing
if(vars.formatoptns["frmtadsnsp"]):
txt = utils.addsentencespacing(txt, vars.actions)
return txt
#==================================================================#
# Applies chosen formatting options to text returned from AI
#==================================================================#
def applyoutputformatting(txt):
# Use standard quotes and apostrophes
txt = utils.fixquotes(txt)
# Trim incomplete sentences
if(vars.formatoptns["frmttriminc"]):
txt = utils.trimincompletesentence(txt)
# Replace blank lines
if(vars.formatoptns["frmtrmblln"]):
txt = utils.replaceblanklines(txt)
# Remove special characters
if(vars.formatoptns["frmtrmspch"]):
txt = utils.removespecialchars(txt)
return txt
#==================================================================#
# Sends the current story content to the Game Screen
#==================================================================#
@ -637,6 +710,9 @@ def refresh_story():
# Sends the current generator settings to the Game Menu
#==================================================================#
def refresh_settings():
# Suppress toggle change events while loading state
emit('from_server', {'cmd': 'allowtoggle', 'data': False})
if(vars.model != "InferKit"):
emit('from_server', {'cmd': 'updatetemp', 'data': vars.temp})
emit('from_server', {'cmd': 'updatetopp', 'data': vars.top_p})
@ -649,6 +725,14 @@ def refresh_settings():
emit('from_server', {'cmd': 'updateikgen', 'data': vars.ikgen})
emit('from_server', {'cmd': 'updateanotedepth', 'data': vars.andepth})
emit('from_server', {'cmd': 'updatefrmttriminc', 'data': vars.formatoptns["frmttriminc"]})
emit('from_server', {'cmd': 'updatefrmtrmblln', 'data': vars.formatoptns["frmtrmblln"]})
emit('from_server', {'cmd': 'updatefrmtrmspch', 'data': vars.formatoptns["frmtrmspch"]})
emit('from_server', {'cmd': 'updatefrmtadsnsp', 'data': vars.formatoptns["frmtadsnsp"]})
# Allow toggle events again
emit('from_server', {'cmd': 'allowtoggle', 'data': True})
#==================================================================#
# Sets the logical and display states for the AI Busy condition

View File

@ -86,4 +86,25 @@ gensettingsik =[{
"step": 2,
"default": 200,
"tooltip": "Number of characters the AI should generate."
}]
}]
formatcontrols = [{
"label": "Trim incomplete sentences",
"id": "frmttriminc",
"tooltip": "Remove text after last sentence closure. If no closure is found, all tokens will be returned."
},
{
"label": "Remove blank lines",
"id": "frmtrmblln",
"tooltip": "Replace double newlines (\\n\\n) with single newlines to avoid blank lines."
},
{
"label": "Remove special characters",
"id": "frmtrmspch",
"tooltip": "Remove special characters (@,#,%,^, etc)"
},
{
"label": "Add sentence spacing",
"id": "frmtadsnsp",
"tooltip": "If the last action ended with punctuation, add a space to the beginning of the next action."
}]

View File

@ -11,6 +11,7 @@ var button_newgame;
var button_save;
var button_load;
var button_settings;
var button_format;
var button_send;
var button_actedit;
var button_actmem;
@ -21,6 +22,7 @@ var game_text;
var input_text;
var message_text;
var settings_menu;
var format_menu;
var anote_menu;
var anote_input;
var anote_labelcur;
@ -30,6 +32,10 @@ var anote_slider;
var shift_down = false;
var do_clear_ent = false;
// Display vars
var allowtoggle = false;
var formatcount = 0;
//=================================================================//
// METHODS
//=================================================================//
@ -58,16 +64,44 @@ function addSetting(ob) {
</div>\
</div>");
// Set references to HTML objects
refin = $("#"+ob.id);
reflb = $("#"+ob.id+"cur");
window["setting_"+ob.id] = refin;
window["label_"+ob.id] = reflb;
var refin = $("#"+ob.id);
var reflb = $("#"+ob.id+"cur");
window["setting_"+ob.id] = refin; // Is this still needed?
window["label_"+ob.id] = reflb; // Is this still needed?
// Add event function to input
refin.on("input", function () {
socket.send({'cmd': $(this).attr('id'), 'data': $(this).val()});
});
}
function addFormat(ob) {
// Check if we need to make a new column for this button
if(formatcount == 0) {
format_menu.append("<div class=\"formatcolumn\"></div>");
}
// Get reference to the last child column
var ref = $("#formatmenu > div").last();
// Add format block to Format Menu
ref.append("<div class=\"formatrow\">\
<input type=\"checkbox\" data-toggle=\"toggle\" data-onstyle=\"success\" id=\""+ob.id+"\">\
<span class=\"formatlabel\">"+ob.label+" </span>\
<span class=\"helpicon\">?<span class=\"helptext\">"+ob.tooltip+"</span></span>\
</div>");
// Tell Bootstrap-Toggle to render the new checkbox
$("input[type=checkbox]").bootstrapToggle();
// Add event to input
$("#"+ob.id).on("change", function () {
if(allowtoggle) {
socket.send({'cmd': $(this).attr('id'), 'data': $(this).prop('checked')});
}
});
// Increment display variable
formatcount++;
if(formatcount == 2) {
formatcount = 0;
}
}
function enableButtons(refs) {
for(i=0; i<refs.length; i++) {
refs[i].prop("disabled",false);
@ -207,6 +241,7 @@ $(document).ready(function(){
button_save = $('#btn_save');
button_load = $('#btn_load');
button_settings = $('#btn_settings');
button_format = $('#btn_format');
button_send = $('#btnsend');
button_actedit = $('#btn_actedit');
button_actmem = $('#btn_actmem');
@ -217,6 +252,7 @@ $(document).ready(function(){
input_text = $('#input_text');
message_text = $('#messagefield');
settings_menu = $("#settingsmenu");
format_menu = $('#formatmenu');
anote_menu = $('#anoterowcontainer');
anote_input = $('#anoteinput');
anote_labelcur = $('#anotecur');
@ -233,6 +269,7 @@ $(document).ready(function(){
connect_status.addClass("color_green");
// Reset Settings Menu
settings_menu.html("");
format_menu.html("");
} else if(msg.cmd == "updatescreen") {
// Send game content to Game Screen
game_text.html(msg.data);
@ -340,6 +377,24 @@ $(document).ready(function(){
} else if(msg.cmd == "addsetting") {
// Add setting controls
addSetting(msg.data);
} else if(msg.cmd == "addformat") {
// Add setting controls
addFormat(msg.data);
} else if(msg.cmd == "updatefrmttriminc") {
// Update toggle state
$("#frmttriminc").prop('checked', msg.data).change()
} else if(msg.cmd == "updatefrmtrmblln") {
// Update toggle state
$("#frmtrmblln").prop('checked', msg.data).change()
} else if(msg.cmd == "updatefrmtrmspch") {
// Update toggle state
$("#frmtrmspch").prop('checked', msg.data).change()
} else if(msg.cmd == "updatefrmtadsnsp") {
// Update toggle state
$("#frmtadsnsp").prop('checked', msg.data).change()
} else if(msg.cmd == "allowtoggle") {
// Allow toggle change states to propagate
allowtoggle = msg.data;
}
});
@ -390,6 +445,10 @@ $(document).ready(function(){
$('#settingsmenu').slideToggle("slow");
});
button_format.on("click", function(ev) {
$('#formatmenu').slideToggle("slow");
});
$("#btn_savesettings").on("click", function(ev) {
socket.send({'cmd': 'savesettings', 'data': ''});
});

28
static/bootstrap-toggle.min.css vendored Normal file
View File

@ -0,0 +1,28 @@
/*! ========================================================================
* Bootstrap Toggle: bootstrap-toggle.css v2.2.0
* http://www.bootstraptoggle.com
* ========================================================================
* Copyright 2014 Min Hur, The New York Times Company
* Licensed under MIT
* ======================================================================== */
.checkbox label .toggle,.checkbox-inline .toggle{margin-left:-20px;margin-right:5px}
.toggle{position:relative;overflow:hidden}
.toggle input[type=checkbox]{display:none}
.toggle-group{position:absolute;width:200%;top:0;bottom:0;left:0;transition:left .35s;-webkit-transition:left .35s;-moz-user-select:none;-webkit-user-select:none}
.toggle.off .toggle-group{left:-100%}
.toggle-on{position:absolute;top:0;bottom:0;left:0;right:50%;margin:0;border:0;border-radius:0}
.toggle-off{position:absolute;top:0;bottom:0;left:50%;right:0;margin:0;border:0;border-radius:0}
.toggle-handle{position:relative;margin:0 auto;padding-top:0;padding-bottom:0;height:100%;width:0;border-width:0 1px}
.toggle.btn{min-width:59px;min-height:34px}
.toggle-on.btn{padding-right:24px}
.toggle-off.btn{padding-left:24px}
.toggle.btn-lg{min-width:79px;min-height:45px}
.toggle-on.btn-lg{padding-right:31px}
.toggle-off.btn-lg{padding-left:31px}
.toggle-handle.btn-lg{width:40px}
.toggle.btn-sm{min-width:50px;min-height:30px}
.toggle-on.btn-sm{padding-right:20px}
.toggle-off.btn-sm{padding-left:20px}
.toggle.btn-xs{min-width:35px;min-height:22px}
.toggle-on.btn-xs{padding-right:12px}
.toggle-off.btn-xs{padding-left:12px}

9
static/bootstrap-toggle.min.js vendored Normal file
View File

@ -0,0 +1,9 @@
/*! ========================================================================
* Bootstrap Toggle: bootstrap-toggle.js v2.2.0
* http://www.bootstraptoggle.com
* ========================================================================
* Copyright 2014 Min Hur, The New York Times Company
* Licensed under MIT
* ======================================================================== */
+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.toggle"),f="object"==typeof b&&b;e||d.data("bs.toggle",e=new c(this,f)),"string"==typeof b&&e[b]&&e[b]()})}var c=function(b,c){this.$element=a(b),this.options=a.extend({},this.defaults(),c),this.render()};c.VERSION="2.2.0",c.DEFAULTS={on:"On",off:"Off",onstyle:"primary",offstyle:"default",size:"normal",style:"",width:null,height:null},c.prototype.defaults=function(){return{on:this.$element.attr("data-on")||c.DEFAULTS.on,off:this.$element.attr("data-off")||c.DEFAULTS.off,onstyle:this.$element.attr("data-onstyle")||c.DEFAULTS.onstyle,offstyle:this.$element.attr("data-offstyle")||c.DEFAULTS.offstyle,size:this.$element.attr("data-size")||c.DEFAULTS.size,style:this.$element.attr("data-style")||c.DEFAULTS.style,width:this.$element.attr("data-width")||c.DEFAULTS.width,height:this.$element.attr("data-height")||c.DEFAULTS.height}},c.prototype.render=function(){this._onstyle="btn-"+this.options.onstyle,this._offstyle="btn-"+this.options.offstyle;var b="large"===this.options.size?"btn-lg":"small"===this.options.size?"btn-sm":"mini"===this.options.size?"btn-xs":"",c=a('<label class="btn">').html(this.options.on).addClass(this._onstyle+" "+b),d=a('<label class="btn">').html(this.options.off).addClass(this._offstyle+" "+b+" active"),e=a('<span class="toggle-handle btn btn-default">').addClass(b),f=a('<div class="toggle-group">').append(c,d,e),g=a('<div class="toggle btn" data-toggle="toggle">').addClass(this.$element.prop("checked")?this._onstyle:this._offstyle+" off").addClass(b).addClass(this.options.style);this.$element.wrap(g),a.extend(this,{$toggle:this.$element.parent(),$toggleOn:c,$toggleOff:d,$toggleGroup:f}),this.$toggle.append(f);var h=this.options.width||Math.max(c.outerWidth(),d.outerWidth())+e.outerWidth()/2,i=this.options.height||Math.max(c.outerHeight(),d.outerHeight());c.addClass("toggle-on"),d.addClass("toggle-off"),this.$toggle.css({width:h,height:i}),this.options.height&&(c.css("line-height",c.height()+"px"),d.css("line-height",d.height()+"px")),this.update(!0),this.trigger(!0)},c.prototype.toggle=function(){this.$element.prop("checked")?this.off():this.on()},c.prototype.on=function(a){return this.$element.prop("disabled")?!1:(this.$toggle.removeClass(this._offstyle+" off").addClass(this._onstyle),this.$element.prop("checked",!0),void(a||this.trigger()))},c.prototype.off=function(a){return this.$element.prop("disabled")?!1:(this.$toggle.removeClass(this._onstyle).addClass(this._offstyle+" off"),this.$element.prop("checked",!1),void(a||this.trigger()))},c.prototype.enable=function(){this.$toggle.removeAttr("disabled"),this.$element.prop("disabled",!1)},c.prototype.disable=function(){this.$toggle.attr("disabled","disabled"),this.$element.prop("disabled",!0)},c.prototype.update=function(a){this.$element.prop("disabled")?this.disable():this.enable(),this.$element.prop("checked")?this.on(a):this.off(a)},c.prototype.trigger=function(b){this.$element.off("change.bs.toggle"),b||this.$element.change(),this.$element.on("change.bs.toggle",a.proxy(function(){this.update()},this))},c.prototype.destroy=function(){this.$element.off("change.bs.toggle"),this.$toggleGroup.remove(),this.$element.removeData("bs.toggle"),this.$element.unwrap()};var d=a.fn.bootstrapToggle;a.fn.bootstrapToggle=b,a.fn.bootstrapToggle.Constructor=c,a.fn.toggle.noConflict=function(){return a.fn.bootstrapToggle=d,this},a(function(){a("input[type=checkbox][data-toggle^=toggle]").bootstrapToggle()}),a(document).on("click.bs.toggle","div[data-toggle^=toggle]",function(b){var c=a(this).find("input[type=checkbox]");c.bootstrapToggle("toggle"),b.preventDefault()})}(jQuery);
//# sourceMappingURL=bootstrap-toggle.min.js.map

View File

@ -22,6 +22,12 @@ chunk {
padding: 10px;
}
#formatmenu {
display:none;
background-color: #295071;
padding: 10px;
}
#connectstatusdiv {
display: flex;
}
@ -99,7 +105,7 @@ chunk {
#waitanim {
position:absolute;
top:10px;
top:18px;
left:5px;
}
@ -166,6 +172,26 @@ chunk {
color: #ff0000;
}
.formatcolumn {
width: 25%;
padding-left: 10px;
padding-right: 10px;
display: inline-block;
}
.formatcolumn > div:first-child {
margin-bottom: 5px;
}
.formatrow {
}
.formatlabel {
color: #ffffff;
padding-left: 5px;
}
.hidden {
display: none;
}

View File

@ -8,8 +8,10 @@
<script src="static/socket.io.min.js"></script>
<script src="static/application.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>
<script src="static/bootstrap-toggle.min.js"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">
<link rel="stylesheet" href="static/bootstrap-toggle.min.css">
<link rel="stylesheet" href="static/custom.css">
</head>
<body>
@ -22,6 +24,7 @@
<button type="button" class="btn btn-primary" id="btn_load">Load</button>
<div class="spacer"></div>
<button type="button" class="btn btn-primary" id="btn_settings">Settings</button>
<button type="button" class="btn btn-primary" id="btn_format">Formatting</button>
</div>
<div id="connectstatusdiv">
<span id="connectstatus" class="color_orange">Waiting for connection...</span>
@ -30,6 +33,8 @@
</div>
<div class="row" id="settingsmenu">
</div>
<div class="row" id="formatmenu">
</div>
<div class="row" id="gamescreen">
<span id="gametext">...</span>
</div>

View File

@ -1,5 +1,10 @@
from threading import Timer
import re
#==================================================================#
# Decorator to prevent a function's actions from being run until
# at least x seconds have passed without the function being called
#==================================================================#
def debounce(wait):
def decorator(fun):
def debounced(*args, **kwargs):
@ -16,4 +21,63 @@ def debounce(wait):
return debounced
return decorator
return decorator
#==================================================================#
# Replace fancy quotes and apostrope's with standard ones
#==================================================================#
def fixquotes(txt):
txt = txt.replace("", '"')
txt = txt.replace("", '"')
txt = txt.replace("", "'")
txt = txt.replace("`", "'")
return txt
#==================================================================#
#
#==================================================================#
def trimincompletesentence(txt):
# Cache length of text
ln = len(txt)
# Find last instance of punctuation (Borrowed from Clover-Edition by cloveranon)
lastpunc = max(txt.rfind("."), txt.rfind("!"), txt.rfind("?"))
# Is this the end of a quote?
if(lastpunc < ln-1):
if(txt[lastpunc+1] == '"'):
lastpunc = lastpunc + 1
if(lastpunc >= 0):
txt = txt[:lastpunc+1]
return txt
#==================================================================#
#
#==================================================================#
def replaceblanklines(txt):
txt = txt.replace("\n\n", "\n")
return txt
#==================================================================#
#
#==================================================================#
def removespecialchars(txt):
txt = re.sub(r"[#/@%<>{}+=~|\^]", "", txt)
return txt
#==================================================================#
# If the next action follows a sentence closure, add a space
#==================================================================#
def addsentencespacing(txt, acts):
# Get last character of last action
lastchar = acts[-1][-1]
if(lastchar == "." or lastchar == "!" or lastchar == "?"):
txt = " " + txt
return txt