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 # By: KoboldAIDev
#==================================================================# #==================================================================#
# External packages
from os import path, getcwd from os import path, getcwd
import tkinter as tk import tkinter as tk
from tkinter import messagebox from tkinter import messagebox
import json import json
import torch import torch
# KoboldAI
import fileops import fileops
import gensettings import gensettings
from utils import debounce from utils import debounce
import utils
#==================================================================# #==================================================================#
# Variables & Storage # Variables & Storage
@ -69,6 +72,7 @@ class vars:
hascuda = False # Whether torch has detected CUDA on the system hascuda = False # Whether torch has detected CUDA on the system
usegpu = False # Whether to launch pipeline with GPU support usegpu = False # Whether to launch pipeline with GPU support
custmodpth = "" # Filesystem location of custom model to run custmodpth = "" # Filesystem location of custom model to run
formatoptns = {} # Container for state of formatting options
#==================================================================# #==================================================================#
# Function to get model selection at startup # Function to get model selection at startup
@ -349,6 +353,23 @@ def get_message(msg):
vars.andepth = int(msg['data']) vars.andepth = int(msg['data'])
emit('from_server', {'cmd': 'setlabelanotedepth', 'data': msg['data']}) emit('from_server', {'cmd': 'setlabelanotedepth', 'data': msg['data']})
settingschanged() 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: else:
for set in gensettings.gensettingsik: for set in gensettings.gensettingsik:
emit('from_server', {'cmd': 'addsetting', 'data': set}) 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(): def savesettings():
# Build json to write # Build json to write
js = {} js = {}
js["apikey"] = vars.apikey js["apikey"] = vars.apikey
js["andepth"] = vars.andepth js["andepth"] = vars.andepth
js["temp"] = vars.temp js["temp"] = vars.temp
js["top_p"] = vars.top_p js["top_p"] = vars.top_p
js["rep_pen"] = vars.rep_pen js["rep_pen"] = vars.rep_pen
js["genamt"] = vars.genamt js["genamt"] = vars.genamt
js["max_length"] = vars.max_length js["max_length"] = vars.max_length
js["ikgen"] = vars.ikgen js["ikgen"] = vars.ikgen
js["formatoptns"] = vars.formatoptns
# Write it # Write it
file = open("client.settings", "w") file = open("client.settings", "w")
@ -417,6 +446,8 @@ def loadsettings():
vars.max_length = js["max_length"] vars.max_length = js["max_length"]
if("ikgen" in js): if("ikgen" in js):
vars.ikgen = js["ikgen"] vars.ikgen = js["ikgen"]
if("formatoptns" in js):
vars.formatoptns = js["formatoptns"]
file.close() file.close()
@ -436,14 +467,22 @@ def actionsubmit(data):
return return
set_aibusy(1) set_aibusy(1)
if(not vars.gamestarted): if(not vars.gamestarted):
vars.gamestarted = True # Start the game # Start the game
vars.prompt = data # Save this first action as the prompt vars.gamestarted = True
emit('from_server', {'cmd': 'updatescreen', 'data': 'Please wait, generating story...'}) # Clear the startup text from game screen # 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 calcsubmit(data) # Run the first action through the generator
else: else:
# Dont append submission if it's a blank/continue action # Dont append submission if it's a blank/continue action
if(data != ""): if(data != ""):
# Apply input formatting & scripts before sending to tokenizer
data = applyinputformatting(data)
# Store the result in the Action log
vars.actions.append(data) vars.actions.append(data)
# Off to the tokenizer!
calcsubmit(data) calcsubmit(data)
#==================================================================# #==================================================================#
@ -594,7 +633,12 @@ def generate(txt, min, max):
temperature=vars.temp temperature=vars.temp
)[0]["generated_text"] )[0]["generated_text"]
print("{0}{1}{2}".format(colors.CYAN, genout, colors.END)) 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() refresh_story()
emit('from_server', {'cmd': 'texteffect', 'data': len(vars.actions)}) 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/>') 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): def getnewcontent(txt):
ln = len(vars.actions) ln = len(vars.actions)
@ -622,6 +666,35 @@ def getnewcontent(txt):
return (txt.split(delim)[-1]) 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 # 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 # Sends the current generator settings to the Game Menu
#==================================================================# #==================================================================#
def refresh_settings(): def refresh_settings():
# Suppress toggle change events while loading state
emit('from_server', {'cmd': 'allowtoggle', 'data': False})
if(vars.model != "InferKit"): if(vars.model != "InferKit"):
emit('from_server', {'cmd': 'updatetemp', 'data': vars.temp}) emit('from_server', {'cmd': 'updatetemp', 'data': vars.temp})
emit('from_server', {'cmd': 'updatetopp', 'data': vars.top_p}) 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': 'updateikgen', 'data': vars.ikgen})
emit('from_server', {'cmd': 'updateanotedepth', 'data': vars.andepth}) 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 # Sets the logical and display states for the AI Busy condition

View File

@ -86,4 +86,25 @@ gensettingsik =[{
"step": 2, "step": 2,
"default": 200, "default": 200,
"tooltip": "Number of characters the AI should generate." "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_save;
var button_load; var button_load;
var button_settings; var button_settings;
var button_format;
var button_send; var button_send;
var button_actedit; var button_actedit;
var button_actmem; var button_actmem;
@ -21,6 +22,7 @@ var game_text;
var input_text; var input_text;
var message_text; var message_text;
var settings_menu; var settings_menu;
var format_menu;
var anote_menu; var anote_menu;
var anote_input; var anote_input;
var anote_labelcur; var anote_labelcur;
@ -30,6 +32,10 @@ var anote_slider;
var shift_down = false; var shift_down = false;
var do_clear_ent = false; var do_clear_ent = false;
// Display vars
var allowtoggle = false;
var formatcount = 0;
//=================================================================// //=================================================================//
// METHODS // METHODS
//=================================================================// //=================================================================//
@ -58,16 +64,44 @@ function addSetting(ob) {
</div>\ </div>\
</div>"); </div>");
// Set references to HTML objects // Set references to HTML objects
refin = $("#"+ob.id); var refin = $("#"+ob.id);
reflb = $("#"+ob.id+"cur"); var reflb = $("#"+ob.id+"cur");
window["setting_"+ob.id] = refin; window["setting_"+ob.id] = refin; // Is this still needed?
window["label_"+ob.id] = reflb; window["label_"+ob.id] = reflb; // Is this still needed?
// Add event function to input // Add event function to input
refin.on("input", function () { refin.on("input", function () {
socket.send({'cmd': $(this).attr('id'), 'data': $(this).val()}); 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) { function enableButtons(refs) {
for(i=0; i<refs.length; i++) { for(i=0; i<refs.length; i++) {
refs[i].prop("disabled",false); refs[i].prop("disabled",false);
@ -207,6 +241,7 @@ $(document).ready(function(){
button_save = $('#btn_save'); button_save = $('#btn_save');
button_load = $('#btn_load'); button_load = $('#btn_load');
button_settings = $('#btn_settings'); button_settings = $('#btn_settings');
button_format = $('#btn_format');
button_send = $('#btnsend'); button_send = $('#btnsend');
button_actedit = $('#btn_actedit'); button_actedit = $('#btn_actedit');
button_actmem = $('#btn_actmem'); button_actmem = $('#btn_actmem');
@ -217,6 +252,7 @@ $(document).ready(function(){
input_text = $('#input_text'); input_text = $('#input_text');
message_text = $('#messagefield'); message_text = $('#messagefield');
settings_menu = $("#settingsmenu"); settings_menu = $("#settingsmenu");
format_menu = $('#formatmenu');
anote_menu = $('#anoterowcontainer'); anote_menu = $('#anoterowcontainer');
anote_input = $('#anoteinput'); anote_input = $('#anoteinput');
anote_labelcur = $('#anotecur'); anote_labelcur = $('#anotecur');
@ -233,6 +269,7 @@ $(document).ready(function(){
connect_status.addClass("color_green"); connect_status.addClass("color_green");
// Reset Settings Menu // Reset Settings Menu
settings_menu.html(""); settings_menu.html("");
format_menu.html("");
} else if(msg.cmd == "updatescreen") { } else if(msg.cmd == "updatescreen") {
// Send game content to Game Screen // Send game content to Game Screen
game_text.html(msg.data); game_text.html(msg.data);
@ -340,6 +377,24 @@ $(document).ready(function(){
} else if(msg.cmd == "addsetting") { } else if(msg.cmd == "addsetting") {
// Add setting controls // Add setting controls
addSetting(msg.data); 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"); $('#settingsmenu').slideToggle("slow");
}); });
button_format.on("click", function(ev) {
$('#formatmenu').slideToggle("slow");
});
$("#btn_savesettings").on("click", function(ev) { $("#btn_savesettings").on("click", function(ev) {
socket.send({'cmd': 'savesettings', 'data': ''}); 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; padding: 10px;
} }
#formatmenu {
display:none;
background-color: #295071;
padding: 10px;
}
#connectstatusdiv { #connectstatusdiv {
display: flex; display: flex;
} }
@ -99,7 +105,7 @@ chunk {
#waitanim { #waitanim {
position:absolute; position:absolute;
top:10px; top:18px;
left:5px; left:5px;
} }
@ -166,6 +172,26 @@ chunk {
color: #ff0000; 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 { .hidden {
display: none; display: none;
} }

View File

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

View File

@ -1,5 +1,10 @@
from threading import Timer 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 debounce(wait):
def decorator(fun): def decorator(fun):
def debounced(*args, **kwargs): def debounced(*args, **kwargs):
@ -16,4 +21,63 @@ def debounce(wait):
return debounced 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