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
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)
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_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
@ -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("--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("--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()
vars.model = args.model;
if args.remote:
vars.remote = True;
vars.smandelete = vars.remote == args.override_delete
vars.smanrename = vars.remote == args.override_rename
# Select a model to run
if args.model:
print("Welcome to KoboldAI!\nYou have selected the following Model:", vars.model)
@ -500,7 +507,7 @@ def index():
@socketio.on('connect')
def do_connect():
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):
emit('from_server', {'cmd': 'runs_remotely'})
@ -686,7 +693,11 @@ def get_message(msg):
elif(msg['cmd'] == 'loadselect'):
vars.loadselect = msg["data"]
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'):
vars.svowname = ""
vars.saveow = False
@ -1820,7 +1831,7 @@ def saveas(name):
name = utils.cleanfilename(name)
if(not fileops.saveexists(name) or (vars.saveow and vars.svowname == name)):
# All clear to save
saveRequest(getcwd()+"/stories/"+name+".json")
saveRequest(fileops.storypath(name))
emit('from_server', {'cmd': 'hidesaveas', 'data': ''})
vars.saveow = False
vars.svowname = ""
@ -1830,6 +1841,48 @@ def saveas(name):
vars.svowname = name
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
#==================================================================#

View File

@ -1,7 +1,9 @@
import tkinter as tk
from tkinter import filedialog
from os import getcwd, listdir, path
import os
import json
import string
#==================================================================#
# Generic Method for prompting for file path
@ -54,6 +56,12 @@ def getdirpath(dir, title):
else:
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
#==================================================================#
@ -83,4 +91,22 @@ def getstoryfiles():
# Returns True if json file exists with requested save 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 saveas_accept;
var saveas_close;
var saveasoverwrite;
var loadpopup;
var loadcontent;
var load_accept;
@ -70,6 +69,8 @@ var connected = false;
var newly_loaded = true;
var current_editing_chunk = null;
var chunk_conflict = false;
var sman_allow_delete = false;
var sman_allow_rename = false;
// Key states
var shift_down = false;
@ -542,7 +543,7 @@ function hideSaveAsPopup() {
saveaspopup.removeClass("flex");
saveaspopup.addClass("hidden");
saveasinput.val("");
hide([saveasoverwrite]);
hide([$(".saveasoverwrite"), $(".popuperror")]);
}
function sendSaveAsRequest() {
@ -566,15 +567,70 @@ function buildLoadList(ar) {
showLoadPopup();
var i;
for(i=0; i<ar.length; i++) {
loadcontent.append("<div class=\"loadlistitem\" id=\"load"+i+"\" name=\""+ar[i].name+"\">\
<div>"+ar[i].name+"</div>\
<div>"+ar[i].actions+"</div>\
loadcontent.append("<div class=\"flex\">\
<div class=\"loadlistpadding\"></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>");
$("#load"+i).on("click", function () {
enableButtons([load_accept]);
socket.send({'cmd': 'loadselect', 'data': $(this).attr("name")});
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");
saveas_accept = $("#btn_saveasaccept");
saveas_close = $("#btn_saveasclose");
saveasoverwrite = $("#saveasoverwrite");
loadpopup = $("#loadcontainer");
loadcontent = $("#loadlistcontent");
load_accept = $("#btn_loadaccept");
@ -853,6 +908,8 @@ $(document).ready(function(){
socket.on('from_server', function(msg) {
if(msg.cmd == "connected") {
// Connected to Server Actions
sman_allow_delete = msg.hasOwnProperty("smandelete") && msg.smandelete;
sman_allow_rename = msg.hasOwnProperty("smanrename") && msg.smanrename;
connected = true;
connect_status.html("<b>Connected to KoboldAI Process!</b>");
connect_status.removeClass("color_orange");
@ -1048,6 +1105,14 @@ $(document).ready(function(){
} else if(msg.cmd == "popupshow") {
// Show/Hide Popup
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") {
// Add import popup entry
addImportLine(msg.data);
@ -1081,7 +1146,11 @@ $(document).ready(function(){
buildLoadList(msg.data);
} else if(msg.cmd == "askforoverwrite") {
// 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") {
// Parse generator sequences to UI
parsegenseqs(msg.data);
@ -1262,7 +1331,7 @@ $(document).ready(function(){
} else {
enableButtons([saveas_accept]);
}
hide([saveasoverwrite]);
hide([$(".saveasoverwrite"), $(".popuperror")]);
});
// Bind Enter button to submit

View File

@ -271,12 +271,6 @@ chunk, chunk * {
margin-top: 200px;
}
#saveasoverwrite {
color: #ff9900;
font-weight: bold;
text-align: center;
}
#loadpopup {
width: 500px;
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 {
height: 325px;
overflow-y: scroll;
@ -319,6 +325,12 @@ chunk, chunk * {
text-align: center;
}
.dialogheader {
padding: 10px 40px 10px 40px;
color: #737373;
text-align: center;
}
.anotelabel {
font-size: 10pt;
color: #ffffff;
@ -556,16 +568,16 @@ chunk, chunk * {
}
.loadlistheader {
padding-left: 10px;
display: grid;
grid-template-columns: 80% 20%;
padding-left: 68px;
padding-right: 20px;
display: flex;
color: #737373;
}
.loadlistitem {
padding: 5px 10px 5px 10px;
display: grid;
grid-template-columns: 80% 20%;
display: flex;
flex-grow: 1;
color: #ffffff;
-moz-transition: background-color 0.25s ease-in;
@ -579,6 +591,30 @@ chunk, chunk * {
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 {
border-radius: 5px;
background-color: #98bcdb;
@ -678,6 +714,11 @@ chunk, chunk * {
font-size: 12pt;
}
.popuperror {
color: #ef2929;
text-align: center;
}
.popupfooter {
width: 100%;
padding: 10px;
@ -692,6 +733,12 @@ chunk, chunk * {
margin-right: 10px;
}
.saveasoverwrite {
color: #ff9900;
font-weight: bold;
text-align: center;
}
.seqselheader {
color: #737373;
}

View File

@ -193,7 +193,7 @@
<div class="aidgpopupcontent">
<input class="form-control" type="text" placeholder="Save Name" id="savename">
</div>
<div class="hidden" id="saveasoverwrite">
<div class="saveasoverwrite hidden">
<span>File already exists. Really overwrite?</span>
</div>
<div class="popupfooter">
@ -209,7 +209,7 @@
</div>
<div class="loadlistheader">
<div>Save Name</div>
<div># Actions</div>
<div class="flex-push-right"># Actions</div>
</div>
<div id="loadlistcontent">
</div>
@ -219,6 +219,44 @@
</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 id="nspopup">
<div class="popuptitlebar">

View File

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