Allow deleting and renaming stories in the browser

This commit is contained in:
Gnome Ann 2021-08-31 18:22:30 -04:00
parent db284b2367
commit c276220a35
6 changed files with 260 additions and 27 deletions

View File

@ -110,6 +110,8 @@ class vars:
useprompt = True # Whether to send the full prompt with every submit action useprompt = True # Whether to send the full prompt with every submit action
breakmodel = False # For GPU users, whether to use both system RAM and VRAM to conserve VRAM while offering speedup compared to CPU-only breakmodel = False # For GPU users, whether to use both system RAM and VRAM to conserve VRAM while offering speedup compared to CPU-only
bmsupported = False # Whether the breakmodel option is supported (GPT-Neo/GPT-J only, currently) bmsupported = False # Whether the breakmodel option is supported (GPT-Neo/GPT-J only, currently)
smandelete = False # Whether stories can be deleted from inside the browser
smanrename = False # Whether stories can be renamed from inside the browser
acregex_ai = re.compile(r'\n* *>(.|\n)*') # Pattern for matching adventure actions from the AI so we can remove them acregex_ai = re.compile(r'\n* *>(.|\n)*') # Pattern for matching adventure actions from the AI so we can remove them
acregex_ui = re.compile(r'^ *(>.*)$', re.MULTILINE) # Pattern for matching actions in the HTML-escaped story so we can apply colouring, etc (make sure to encase part to format in parentheses) acregex_ui = re.compile(r'^ *(>.*)$', re.MULTILINE) # Pattern for matching actions in the HTML-escaped story so we can apply colouring, etc (make sure to encase part to format in parentheses)
actionmode = 1 actionmode = 1
@ -172,12 +174,17 @@ parser.add_argument("--path", help="Specify the Path for local models (For model
parser.add_argument("--cpu", action='store_true', help="By default unattended launches are on the GPU use this option to force CPU usage.") parser.add_argument("--cpu", action='store_true', help="By default unattended launches are on the GPU use this option to force CPU usage.")
parser.add_argument("--breakmodel", action='store_true', help="For models that support GPU-CPU hybrid generation, use this feature instead of GPU or CPU generation") parser.add_argument("--breakmodel", action='store_true', help="For models that support GPU-CPU hybrid generation, use this feature instead of GPU or CPU generation")
parser.add_argument("--breakmodel_layers", type=int, help="Specify the number of layers to commit to system RAM if --breakmodel is used") parser.add_argument("--breakmodel_layers", type=int, help="Specify the number of layers to commit to system RAM if --breakmodel is used")
parser.add_argument("--override_delete", action='store_true', help="Deleting stories from inside the browser is disabled if you are using --remote and enabled otherwise. Using this option will instead allow deleting stories if using --remote and prevent deleting stories otherwise.")
parser.add_argument("--override_rename", action='store_true', help="Renaming stories from inside the browser is disabled if you are using --remote and enabled otherwise. Using this option will instead allow renaming stories if using --remote and prevent renaming stories otherwise.")
args = parser.parse_args() args = parser.parse_args()
vars.model = args.model; vars.model = args.model;
if args.remote: if args.remote:
vars.remote = True; vars.remote = True;
vars.smandelete = vars.remote == args.override_delete
vars.smanrename = vars.remote == args.override_rename
# Select a model to run # Select a model to run
if args.model: if args.model:
print("Welcome to KoboldAI!\nYou have selected the following Model:", vars.model) print("Welcome to KoboldAI!\nYou have selected the following Model:", vars.model)
@ -500,7 +507,7 @@ def index():
@socketio.on('connect') @socketio.on('connect')
def do_connect(): def do_connect():
print("{0}Client connected!{1}".format(colors.GREEN, colors.END)) print("{0}Client connected!{1}".format(colors.GREEN, colors.END))
emit('from_server', {'cmd': 'connected'}) emit('from_server', {'cmd': 'connected', 'smandelete': vars.smandelete, 'smanrename': vars.smanrename})
if(vars.remote): if(vars.remote):
emit('from_server', {'cmd': 'runs_remotely'}) emit('from_server', {'cmd': 'runs_remotely'})
@ -686,7 +693,11 @@ def get_message(msg):
elif(msg['cmd'] == 'loadselect'): elif(msg['cmd'] == 'loadselect'):
vars.loadselect = msg["data"] vars.loadselect = msg["data"]
elif(msg['cmd'] == 'loadrequest'): elif(msg['cmd'] == 'loadrequest'):
loadRequest(getcwd()+"/stories/"+vars.loadselect+".json") loadRequest(fileops.storypath(vars.loadselect))
elif(msg['cmd'] == 'deletestory'):
deletesave(msg['data'])
elif(msg['cmd'] == 'renamestory'):
renamesave(msg['data'], msg['newname'])
elif(msg['cmd'] == 'clearoverwrite'): elif(msg['cmd'] == 'clearoverwrite'):
vars.svowname = "" vars.svowname = ""
vars.saveow = False vars.saveow = False
@ -1820,7 +1831,7 @@ def saveas(name):
name = utils.cleanfilename(name) name = utils.cleanfilename(name)
if(not fileops.saveexists(name) or (vars.saveow and vars.svowname == name)): if(not fileops.saveexists(name) or (vars.saveow and vars.svowname == name)):
# All clear to save # All clear to save
saveRequest(getcwd()+"/stories/"+name+".json") saveRequest(fileops.storypath(name))
emit('from_server', {'cmd': 'hidesaveas', 'data': ''}) emit('from_server', {'cmd': 'hidesaveas', 'data': ''})
vars.saveow = False vars.saveow = False
vars.svowname = "" vars.svowname = ""
@ -1830,6 +1841,48 @@ def saveas(name):
vars.svowname = name vars.svowname = name
emit('from_server', {'cmd': 'askforoverwrite', 'data': ''}) emit('from_server', {'cmd': 'askforoverwrite', 'data': ''})
#==================================================================#
# Launch in-browser story-delete prompt
#==================================================================#
def deletesave(name):
name = utils.cleanfilename(name)
e = fileops.deletesave(name)
if(e is None):
if(vars.smandelete):
emit('from_server', {'cmd': 'hidepopupdelete', 'data': ''})
getloadlist()
else:
emit('from_server', {'cmd': 'popuperror', 'data': "The server denied your request to delete this story"})
else:
print("{0}{1}{2}".format(colors.RED, str(e), colors.END))
emit('from_server', {'cmd': 'popuperror', 'data': str(e)})
#==================================================================#
# Launch in-browser story-rename prompt
#==================================================================#
def renamesave(name, newname):
# Check if filename exists already
name = utils.cleanfilename(name)
newname = utils.cleanfilename(newname)
if(not fileops.saveexists(newname) or name == newname or (vars.saveow and vars.svowname == newname)):
e = fileops.renamesave(name, newname)
vars.saveow = False
vars.svowname = ""
if(e is None):
if(vars.smanrename):
emit('from_server', {'cmd': 'hidepopuprename', 'data': ''})
getloadlist()
else:
emit('from_server', {'cmd': 'popuperror', 'data': "The server denied your request to rename this story"})
else:
print("{0}{1}{2}".format(colors.RED, str(e), colors.END))
emit('from_server', {'cmd': 'popuperror', 'data': str(e)})
else:
# File exists, prompt for overwrite
vars.saveow = True
vars.svowname = newname
emit('from_server', {'cmd': 'askforoverwrite', 'data': ''})
#==================================================================# #==================================================================#
# Save the currently running story # Save the currently running story
#==================================================================# #==================================================================#

View File

@ -1,7 +1,9 @@
import tkinter as tk import tkinter as tk
from tkinter import filedialog from tkinter import filedialog
from os import getcwd, listdir, path from os import getcwd, listdir, path
import os
import json import json
import string
#==================================================================# #==================================================================#
# Generic Method for prompting for file path # Generic Method for prompting for file path
@ -54,6 +56,12 @@ def getdirpath(dir, title):
else: else:
return None return None
#==================================================================#
# Returns the path (as a string) to the given story by its name
#==================================================================#
def storypath(name):
return path.join(path.dirname(path.realpath(__file__)), "stories", name + ".json")
#==================================================================# #==================================================================#
# Returns an array of dicts containing story files in /stories # Returns an array of dicts containing story files in /stories
#==================================================================# #==================================================================#
@ -83,4 +91,22 @@ def getstoryfiles():
# Returns True if json file exists with requested save name # Returns True if json file exists with requested save name
#==================================================================# #==================================================================#
def saveexists(name): def saveexists(name):
return path.exists(path.dirname(path.realpath(__file__))+"/stories/"+name+".json") return path.exists(storypath(name))
#==================================================================#
# Delete save file by name; returns None if successful, or the exception if not
#==================================================================#
def deletesave(name):
try:
os.remove(storypath(name))
except Exception as e:
return e
#==================================================================#
# Rename save file; returns None if successful, or the exception if not
#==================================================================#
def renamesave(name, new_name):
try:
os.replace(storypath(name), storypath(new_name))
except Exception as e:
return e

View File

@ -49,7 +49,6 @@ var saveasinput;
var topic; var topic;
var saveas_accept; var saveas_accept;
var saveas_close; var saveas_close;
var saveasoverwrite;
var loadpopup; var loadpopup;
var loadcontent; var loadcontent;
var load_accept; var load_accept;
@ -70,6 +69,8 @@ var connected = false;
var newly_loaded = true; var newly_loaded = true;
var current_editing_chunk = null; var current_editing_chunk = null;
var chunk_conflict = false; var chunk_conflict = false;
var sman_allow_delete = false;
var sman_allow_rename = false;
// Key states // Key states
var shift_down = false; var shift_down = false;
@ -542,7 +543,7 @@ function hideSaveAsPopup() {
saveaspopup.removeClass("flex"); saveaspopup.removeClass("flex");
saveaspopup.addClass("hidden"); saveaspopup.addClass("hidden");
saveasinput.val(""); saveasinput.val("");
hide([saveasoverwrite]); hide([$(".saveasoverwrite"), $(".popuperror")]);
} }
function sendSaveAsRequest() { function sendSaveAsRequest() {
@ -566,15 +567,70 @@ function buildLoadList(ar) {
showLoadPopup(); showLoadPopup();
var i; var i;
for(i=0; i<ar.length; i++) { for(i=0; i<ar.length; i++) {
loadcontent.append("<div class=\"loadlistitem\" id=\"load"+i+"\" name=\""+ar[i].name+"\">\ loadcontent.append("<div class=\"flex\">\
<div>"+ar[i].name+"</div>\ <div class=\"loadlistpadding\"></div>\
<div>"+ar[i].actions+"</div>\ <span class=\"loadlisticon loadlisticon-delete oi oi-x "+(sman_allow_delete ? "allowed" : "")+"\" id=\"loaddelete"+i+"\" "+(sman_allow_delete ? "title=\"Delete story\"" : "")+" aria-hidden=\"true\"></span>\
<div class=\"loadlistpadding\"></div>\
<span class=\"loadlisticon loadlisticon-rename oi oi-pencil "+(sman_allow_rename ? "allowed" : "")+"\" id=\"loadrename"+i+"\" "+(sman_allow_rename ? "title=\"Rename story\"" : "")+" aria-hidden=\"true\"></span>\
<div class=\"loadlistpadding\"></div>\
<div class=\"loadlistitem\" id=\"load"+i+"\" name=\""+ar[i].name+"\">\
<div>"+ar[i].name+"</div>\
<div class=\"flex-push-right\">"+ar[i].actions+"</div>\
</div>\
</div>"); </div>");
$("#load"+i).on("click", function () { $("#load"+i).on("click", function () {
enableButtons([load_accept]); enableButtons([load_accept]);
socket.send({'cmd': 'loadselect', 'data': $(this).attr("name")}); socket.send({'cmd': 'loadselect', 'data': $(this).attr("name")});
highlightLoadLine($(this)); highlightLoadLine($(this));
}); });
$("#loaddelete"+i).off("click").on("click", (function (name) {
return function () {
if(!sman_allow_delete) {
return;
}
$("#loadcontainerdelete-storyname").text(name);
$("#btn_dsaccept").off("click").on("click", (function (name) {
return function () {
hide([$(".saveasoverwrite"), $(".popuperror")]);
socket.send({'cmd': 'deletestory', 'data': name});
}
})(name));
$("#btn_dsclose").off("click").on("click", function () {
$("#loadcontainerdelete").removeClass("flex").addClass("hidden");
hide([$(".saveasoverwrite"), $(".popuperror")]);
});
$("#loadcontainerdelete").removeClass("hidden").addClass("flex");
}
})(ar[i].name));
$("#loadrename"+i).off("click").on("click", (function (name) {
return function () {
if(!sman_allow_rename) {
return;
}
$("#newsavename").val("")
$("#loadcontainerrename-storyname").text(name);
submit = (function (name) {
return function () {
hide([$(".saveasoverwrite"), $(".popuperror")]);
socket.send({'cmd': 'renamestory', 'data': name, 'newname': $("#newsavename").val()});
}
})(name);
$("#btn_rensaccept").off("click").on("click", submit);
$("#newsavename").off("keydown").on("keydown", function (ev) {
if (ev.which == 13 && $(this).val() != "") {
submit();
}
});
$("#btn_rensclose").off("click").on("click", function () {
$("#loadcontainerrename").removeClass("flex").addClass("hidden");
hide([$(".saveasoverwrite"), $(".popuperror")]);
});
$("#loadcontainerrename").removeClass("hidden").addClass("flex");
$("#newsavename").val(name).select();
}
})(ar[i].name));
} }
} }
@ -833,7 +889,6 @@ $(document).ready(function(){
topic = $("#topic"); topic = $("#topic");
saveas_accept = $("#btn_saveasaccept"); saveas_accept = $("#btn_saveasaccept");
saveas_close = $("#btn_saveasclose"); saveas_close = $("#btn_saveasclose");
saveasoverwrite = $("#saveasoverwrite");
loadpopup = $("#loadcontainer"); loadpopup = $("#loadcontainer");
loadcontent = $("#loadlistcontent"); loadcontent = $("#loadlistcontent");
load_accept = $("#btn_loadaccept"); load_accept = $("#btn_loadaccept");
@ -853,6 +908,8 @@ $(document).ready(function(){
socket.on('from_server', function(msg) { socket.on('from_server', function(msg) {
if(msg.cmd == "connected") { if(msg.cmd == "connected") {
// Connected to Server Actions // Connected to Server Actions
sman_allow_delete = msg.hasOwnProperty("smandelete") && msg.smandelete;
sman_allow_rename = msg.hasOwnProperty("smanrename") && msg.smanrename;
connected = true; connected = true;
connect_status.html("<b>Connected to KoboldAI Process!</b>"); connect_status.html("<b>Connected to KoboldAI Process!</b>");
connect_status.removeClass("color_orange"); connect_status.removeClass("color_orange");
@ -1048,6 +1105,14 @@ $(document).ready(function(){
} else if(msg.cmd == "popupshow") { } else if(msg.cmd == "popupshow") {
// Show/Hide Popup // Show/Hide Popup
popupShow(msg.data); popupShow(msg.data);
} else if(msg.cmd == "hidepopupdelete") {
// Hide the dialog box that asks you to confirm deletion of a story
$("#loadcontainerdelete").removeClass("flex").addClass("hidden");
hide([$(".saveasoverwrite"), $(".popuperror")]);
} else if(msg.cmd == "hidepopuprename") {
// Hide the story renaming dialog box
$("#loadcontainerrename").removeClass("flex").addClass("hidden");
hide([$(".saveasoverwrite"), $(".popuperror")]);
} else if(msg.cmd == "addimportline") { } else if(msg.cmd == "addimportline") {
// Add import popup entry // Add import popup entry
addImportLine(msg.data); addImportLine(msg.data);
@ -1081,7 +1146,11 @@ $(document).ready(function(){
buildLoadList(msg.data); buildLoadList(msg.data);
} else if(msg.cmd == "askforoverwrite") { } else if(msg.cmd == "askforoverwrite") {
// Show overwrite warning // Show overwrite warning
show([saveasoverwrite]); show([$(".saveasoverwrite")]);
} else if(msg.cmd == "popuperror") {
// Show error in the current dialog box
$(".popuperror").text(msg.data);
show([$(".popuperror")]);
} else if(msg.cmd == "genseqs") { } else if(msg.cmd == "genseqs") {
// Parse generator sequences to UI // Parse generator sequences to UI
parsegenseqs(msg.data); parsegenseqs(msg.data);
@ -1262,7 +1331,7 @@ $(document).ready(function(){
} else { } else {
enableButtons([saveas_accept]); enableButtons([saveas_accept]);
} }
hide([saveasoverwrite]); hide([$(".saveasoverwrite"), $(".popuperror")]);
}); });
// Bind Enter button to submit // Bind Enter button to submit

View File

@ -271,12 +271,6 @@ chunk, chunk * {
margin-top: 200px; margin-top: 200px;
} }
#saveasoverwrite {
color: #ff9900;
font-weight: bold;
text-align: center;
}
#loadpopup { #loadpopup {
width: 500px; width: 500px;
background-color: #262626; background-color: #262626;
@ -291,6 +285,18 @@ chunk, chunk * {
} }
} }
#loadpopupdelete {
width: 350px;
background-color: #262626;
margin-top: 200px;
}
#loadpopuprename {
width: 350px;
background-color: #262626;
margin-top: 200px;
}
#loadlistcontent { #loadlistcontent {
height: 325px; height: 325px;
overflow-y: scroll; overflow-y: scroll;
@ -319,6 +325,12 @@ chunk, chunk * {
text-align: center; text-align: center;
} }
.dialogheader {
padding: 10px 40px 10px 40px;
color: #737373;
text-align: center;
}
.anotelabel { .anotelabel {
font-size: 10pt; font-size: 10pt;
color: #ffffff; color: #ffffff;
@ -556,16 +568,16 @@ chunk, chunk * {
} }
.loadlistheader { .loadlistheader {
padding-left: 10px; padding-left: 68px;
display: grid; padding-right: 20px;
grid-template-columns: 80% 20%; display: flex;
color: #737373; color: #737373;
} }
.loadlistitem { .loadlistitem {
padding: 5px 10px 5px 10px; padding: 5px 10px 5px 10px;
display: grid; display: flex;
grid-template-columns: 80% 20%; flex-grow: 1;
color: #ffffff; color: #ffffff;
-moz-transition: background-color 0.25s ease-in; -moz-transition: background-color 0.25s ease-in;
@ -579,6 +591,30 @@ chunk, chunk * {
background-color: #688f1f; background-color: #688f1f;
} }
.loadlistpadding {
padding-right: 10px;
}
.loadlisticon {
color: #333
}
.loadlisticon.allowed {
color: #ddd
}
.loadlisticon.allowed:hover {
cursor: pointer;
}
.loadlisticon-delete.allowed:hover {
color: #ef2929
}
.loadlisticon-rename.allowed:hover {
color: #fce94f
}
.navbar .navbar-nav .nav-link:hover { .navbar .navbar-nav .nav-link:hover {
border-radius: 5px; border-radius: 5px;
background-color: #98bcdb; background-color: #98bcdb;
@ -678,6 +714,11 @@ chunk, chunk * {
font-size: 12pt; font-size: 12pt;
} }
.popuperror {
color: #ef2929;
text-align: center;
}
.popupfooter { .popupfooter {
width: 100%; width: 100%;
padding: 10px; padding: 10px;
@ -692,6 +733,12 @@ chunk, chunk * {
margin-right: 10px; margin-right: 10px;
} }
.saveasoverwrite {
color: #ff9900;
font-weight: bold;
text-align: center;
}
.seqselheader { .seqselheader {
color: #737373; color: #737373;
} }

View File

@ -193,7 +193,7 @@
<div class="aidgpopupcontent"> <div class="aidgpopupcontent">
<input class="form-control" type="text" placeholder="Save Name" id="savename"> <input class="form-control" type="text" placeholder="Save Name" id="savename">
</div> </div>
<div class="hidden" id="saveasoverwrite"> <div class="saveasoverwrite hidden">
<span>File already exists. Really overwrite?</span> <span>File already exists. Really overwrite?</span>
</div> </div>
<div class="popupfooter"> <div class="popupfooter">
@ -209,7 +209,7 @@
</div> </div>
<div class="loadlistheader"> <div class="loadlistheader">
<div>Save Name</div> <div>Save Name</div>
<div># Actions</div> <div class="flex-push-right"># Actions</div>
</div> </div>
<div id="loadlistcontent"> <div id="loadlistcontent">
</div> </div>
@ -219,6 +219,44 @@
</div> </div>
</div> </div>
</div> </div>
<div class="popupcontainer hidden" id="loadcontainerdelete">
<div id="loadpopupdelete">
<div class="popuptitlebar">
<div class="popuptitletext">Really Delete Story?</div>
</div>
<div class="dialogheader">
"<span id="loadcontainerdelete-storyname"></span>" will be PERMANENTLY deleted! You will not be able to recover this story later.
</div>
<div class="popuperror hidden">
<span></span>
</div>
<div class="popupfooter">
<button type="button" class="btn btn-danger" id="btn_dsaccept">Delete</button>
<button type="button" class="btn btn-primary" id="btn_dsclose">Cancel</button>
</div>
</div>
</div>
<div class="popupcontainer hidden" id="loadcontainerrename">
<div id="loadpopuprename">
<div class="popuptitlebar">
<div class="popuptitletext">Enter New Name For Story</div>
</div>
<div class="dialogheader">
What should the story "<span id="loadcontainerrename-storyname"></span>" be renamed to?
<input class="form-control" type="text" placeholder="New Save Name" id="newsavename">
</div>
<div class="popuperror hidden">
<span></span>
</div>
<div class="saveasoverwrite hidden">
<span>File already exists. Really overwrite?</span>
</div>
<div class="popupfooter">
<button type="button" class="btn btn-primary" id="btn_rensaccept">Accept</button>
<button type="button" class="btn btn-primary" id="btn_rensclose">Cancel</button>
</div>
</div>
</div>
<div class="popupcontainer hidden" id="newgamecontainer"> <div class="popupcontainer hidden" id="newgamecontainer">
<div id="nspopup"> <div id="nspopup">
<div class="popuptitlebar"> <div class="popuptitlebar">

View File

@ -88,8 +88,8 @@ def addsentencespacing(txt, vars):
# Cleans string for use in file name # Cleans string for use in file name
#==================================================================# #==================================================================#
def cleanfilename(filename): def cleanfilename(filename):
keepcharacters = (' ','.','_') filteredcharacters = ('/','\\')
filename = "".join(c for c in filename if c.isalnum() or c in keepcharacters).rstrip() filename = "".join(c for c in filename if c not in filteredcharacters).rstrip()
return filename return filename