diff --git a/aiserver.py b/aiserver.py index e1080791..2d050acd 100644 --- a/aiserver.py +++ b/aiserver.py @@ -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 #==================================================================# diff --git a/fileops.py b/fileops.py index 7ae96836..f3be4f6d 100644 --- a/fileops.py +++ b/fileops.py @@ -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 diff --git a/static/application.js b/static/application.js index b9b6a527..ecbc3929 100644 --- a/static/application.js +++ b/static/application.js @@ -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[i].name+"
\ -
"+ar[i].actions+"
\ + loadcontent.append("
\ +
\ + \ +
\ + \ +
\ +
\ +
"+ar[i].name+"
\ +
"+ar[i].actions+"
\ +
\
"); $("#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("Connected to KoboldAI Process!"); 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 diff --git a/static/custom.css b/static/custom.css index 19349702..d9363914 100644 --- a/static/custom.css +++ b/static/custom.css @@ -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; } diff --git a/templates/index.html b/templates/index.html index 085d9245..b0b7a3f8 100644 --- a/templates/index.html +++ b/templates/index.html @@ -193,7 +193,7 @@
- + +