Added OpenAI API support

Added in-browser Save/Load/New Story controls
(Force a full refresh in your browser!)
Fixed adding InferKit API key if client.settings already exists
Added cmd calls to bat files so they'll stay open on error
Wait animation now hidden on start state/restart
This commit is contained in:
KoboldAI Dev 2021-05-22 05:28:40 -04:00
parent 4996e0ff46
commit f9bbb174a6
8 changed files with 635 additions and 128 deletions

View File

@ -1,6 +1,6 @@
#==================================================================#
# KoboldAI Client
# Version: 1.14.0
# Version: 1.15.0
# By: KoboldAIDev
#==================================================================#
@ -43,40 +43,44 @@ modellist = [
["InferKit API (requires API key)", "InferKit", ""],
["Custom Neo (eg Neo-horni)", "NeoCustom", ""],
["Custom GPT-2 (eg CloverEdition)", "GPT2Custom", ""],
["Google Colab", "Colab", ""]
["Google Colab", "Colab", ""],
["OpenAI API (requires API key)", "OAI", ""]
]
# Variables
class vars:
lastact = "" # The last action received from the user
lastctx = "" # The last context submitted to the generator
model = ""
noai = False # Runs the script without starting up the transformers pipeline
aibusy = False # Stops submissions while the AI is working
max_length = 512 # Maximum number of tokens to submit per action
ikmax = 3000 # Maximum number of characters to submit to InferKit
genamt = 60 # Amount of text for each action to generate
ikgen = 200 # Number of characters for InferKit to generate
rep_pen = 1.0 # Default generator repetition_penalty
temp = 1.0 # Default generator temperature
top_p = 1.0 # Default generator top_p
gamestarted = False
prompt = ""
memory = ""
authornote = ""
lastact = "" # The last action received from the user
lastctx = "" # The last context submitted to the generator
model = "" # Model ID string chosen at startup
noai = False # Runs the script without starting up the transformers pipeline
aibusy = False # Stops submissions while the AI is working
max_length = 512 # Maximum number of tokens to submit per action
ikmax = 3000 # Maximum number of characters to submit to InferKit
genamt = 60 # Amount of text for each action to generate
ikgen = 200 # Number of characters for InferKit to generate
rep_pen = 1.0 # Default generator repetition_penalty
temp = 1.0 # Default generator temperature
top_p = 1.0 # Default generator top_p
gamestarted = False # Whether the game has started (disables UI elements)
prompt = "" # Prompt
memory = "" # Text submitted to memory field
authornote = "" # Text submitted to Author's Note field
andepth = 3 # How far back in history to append author's note
actions = []
worldinfo = []
badwords = []
badwordsids = []
actions = [] # Array of actions submitted by user and AI
worldinfo = [] # Array of World Info key/value objects
badwords = [] # Array of str/chr values that should be removed from output
badwordsids = [] # Tokenized array of badwords
deletewi = -1 # Temporary storage for index to delete
wirmvwhtsp = False # Whether to remove leading whitespace from WI entries
widepth = 1 # How many historical actions to scan for WI hits
mode = "play" # Whether the interface is in play, memory, or edit mode
editln = 0 # Which line was last selected in Edit Mode
url = "https://api.inferkit.com/v1/models/standard/generate" # InferKit API URL
oaiurl = "" # OpenAI API URL
oaiengines = "https://api.openai.com/v1/engines"
colaburl = "" # Ngrok url for Google Colab mode
apikey = "" # API key to use for InferKit API calls
oaiapikey = "" # API key to use for OpenAI API calls
savedir = getcwd()+"\stories"
hascuda = False # Whether torch has detected CUDA on the system
usegpu = False # Whether to launch pipeline with GPU support
@ -84,6 +88,9 @@ class vars:
formatoptns = {} # Container for state of formatting options
importnum = -1 # Selection on import popup list
importjs = {} # Temporary storage for import data
loadselect = "" # Temporary storage for filename to load
svowname = ""
saveow = False
#==================================================================#
# Function to get model selection at startup
@ -138,7 +145,7 @@ print("{0}Welcome to the KoboldAI Client!\nSelect an AI model to continue:{1}\n"
getModelSelection()
# If transformers model was selected & GPU available, ask to use CPU or GPU
if(not vars.model in ["InferKit", "Colab"]):
if(not vars.model in ["InferKit", "Colab", "OAI"]):
# Test for GPU support
import torch
print("{0}Looking for GPU support...{1}".format(colors.PURPLE, colors.END), end="")
@ -185,7 +192,7 @@ if(vars.model == "InferKit"):
file = open("client.settings", "r")
# Check if API key exists
js = json.load(file)
if(js["apikey"] != ""):
if("apikey" in js and js["apikey"] != ""):
# API key exists, grab it and close the file
vars.apikey = js["apikey"]
file.close()
@ -201,6 +208,73 @@ if(vars.model == "InferKit"):
finally:
file.close()
# Ask for API key if OpenAI was selected
if(vars.model == "OAI"):
if(not path.exists("client.settings")):
# If the client settings file doesn't exist, create it
print("{0}Please enter your OpenAI API key:{1}\n".format(colors.CYAN, colors.END))
vars.oaiapikey = input("Key> ")
# Write API key to file
file = open("client.settings", "w")
try:
js = {"oaiapikey": vars.oaiapikey}
file.write(json.dumps(js, indent=3))
finally:
file.close()
else:
# Otherwise open it up
file = open("client.settings", "r")
# Check if API key exists
js = json.load(file)
if("oaiapikey" in js and js["oaiapikey"] != ""):
# API key exists, grab it and close the file
vars.oaiapikey = js["oaiapikey"]
file.close()
else:
# Get API key, add it to settings object, and write it to disk
print("{0}Please enter your OpenAI API key:{1}\n".format(colors.CYAN, colors.END))
vars.oaiapikey = input("Key> ")
js["oaiapikey"] = vars.oaiapikey
# Write API key to file
file = open("client.settings", "w")
try:
file.write(json.dumps(js, indent=3))
finally:
file.close()
# Get list of models from OAI
print("{0}Retrieving engine list...{1}".format(colors.PURPLE, colors.END), end="")
req = requests.get(
vars.oaiengines,
headers = {
'Authorization': 'Bearer '+vars.oaiapikey
}
)
if(req.status_code == 200):
print("{0}OK!{1}".format(colors.GREEN, colors.END))
print("{0}Please select an engine to use:{1}\n".format(colors.CYAN, colors.END))
engines = req.json()["data"]
# Print list of engines
i = 0
for en in engines:
print(" {0} - {1} ({2})".format(i, en["id"], "\033[92mready\033[0m" if en["ready"] == True else "\033[91mnot ready\033[0m"))
i += 1
# Get engine to use
print("")
engselected = False
while(engselected == False):
engine = input("Engine #> ")
if(engine.isnumeric() and int(engine) < len(engines)):
vars.oaiurl = "https://api.openai.com/v1/engines/{0}/completions".format(engines[int(engine)]["id"])
engselected = True
else:
print("{0}Please enter a valid selection.{1}".format(colors.RED, colors.END))
else:
# Something went wrong, print the message and quit since we can't initialize an engine
print("{0}ERROR!{1}".format(colors.RED, colors.END))
print(req.json())
quit()
# Ask for ngrok url if Google Colab was selected
if(vars.model == "Colab"):
print("{0}Please enter the ngrok.io URL displayed in Google Colab:{1}\n".format(colors.CYAN, colors.END))
@ -221,7 +295,7 @@ socketio = SocketIO(app)
print("{0}OK!{1}".format(colors.GREEN, colors.END))
# Start transformers and create pipeline
if(not vars.model in ["InferKit", "Colab"]):
if(not vars.model in ["InferKit", "Colab", "OAI"]):
if(not vars.noai):
print("{0}Initializing transformers, please wait...{1}".format(colors.PURPLE, colors.END))
from transformers import pipeline, GPT2Tokenizer, GPT2LMHeadModel, GPTNeoForCausalLM
@ -262,10 +336,13 @@ if(not vars.model in ["InferKit", "Colab"]):
print("{0}OK! {1} pipeline created!{2}".format(colors.GREEN, vars.model, colors.END))
else:
# If we're running Colab, we still need a tokenizer.
# If we're running Colab or OAI, we still need a tokenizer.
if(vars.model == "Colab"):
from transformers import GPT2Tokenizer
tokenizer = GPT2Tokenizer.from_pretrained("EleutherAI/gpt-neo-2.7B")
elif(vars.model == "OAI"):
from transformers import GPT2Tokenizer
tokenizer = GPT2Tokenizer.from_pretrained("gpt2")
# Set up Flask routes
@app.route('/')
@ -354,10 +431,10 @@ def get_message(msg):
deleterequest()
elif(msg['cmd'] == 'memory'):
togglememorymode()
elif(msg['cmd'] == 'save'):
saveRequest()
elif(msg['cmd'] == 'load'):
loadRequest()
elif(msg['cmd'] == 'savetofile'):
savetofile()
elif(msg['cmd'] == 'loadfromfile'):
loadfromfile()
elif(msg['cmd'] == 'import'):
importRequest()
elif(msg['cmd'] == 'newgame'):
@ -431,16 +508,29 @@ def get_message(msg):
commitwi(msg['data'])
elif(msg['cmd'] == 'aidgimport'):
importAidgRequest(msg['data'])
elif(msg['cmd'] == 'saveasrequest'):
saveas(msg['data'])
elif(msg['cmd'] == 'saverequest'):
save()
elif(msg['cmd'] == 'loadlistrequest'):
getloadlist()
elif(msg['cmd'] == 'loadselect'):
vars.loadselect = msg["data"]
elif(msg['cmd'] == 'loadrequest'):
loadRequest(getcwd()+"/stories/"+vars.loadselect+".json")
elif(msg['cmd'] == 'clearoverwrite'):
vars.svowname = ""
vars.saveow = False
#==================================================================#
#
# Send start message and tell Javascript to set UI state
#==================================================================#
def setStartState():
emit('from_server', {'cmd': 'updatescreen', 'data': '<span>Welcome to <span class="color_cyan">KoboldAI Client</span>! You are running <span class="color_green">'+vars.model+'</span>.<br/>Please load a game or enter a prompt below to begin!</span>'})
emit('from_server', {'cmd': 'setgamestate', 'data': 'start'})
#==================================================================#
#
# Transmit applicable settings to SocketIO to build UI sliders/toggles
#==================================================================#
def sendsettings():
# Send settings for selected AI type
@ -459,7 +549,7 @@ def sendsettings():
vars.formatoptns[frm["id"]] = False;
#==================================================================#
#
# Take settings from vars and write them to client settings file
#==================================================================#
def savesettings():
# Build json to write
@ -482,7 +572,7 @@ def savesettings():
file.close()
#==================================================================#
#
# Read settings from client file JSON and send to vars
#==================================================================#
def loadsettings():
if(path.exists("client.settings")):
@ -513,7 +603,7 @@ def loadsettings():
file.close()
#==================================================================#
#
# Don't save settings unless 2 seconds have passed without modification
#==================================================================#
@debounce(2)
def settingschanged():
@ -521,7 +611,7 @@ def settingschanged():
savesettings()
#==================================================================#
#
# Take input text from SocketIO and decide what to do with it
#==================================================================#
def actionsubmit(data):
# Ignore new submissions if the AI is currently busy
@ -601,10 +691,12 @@ def calcsubmit(txt):
subtxt = vars.memory + winfo + anotetxt + vars.prompt
lnsub = lnmem + lnwi + lnprompt + lnanote
if(vars.model != "Colab"):
if(not vars.model in ["Colab", "OAI"]):
generate(subtxt, lnsub+1, lnsub+vars.genamt)
else:
elif(vars.model == "Colab"):
sendtocolab(subtxt, lnsub+1, lnsub+vars.genamt)
elif(vars.model == "OAI"):
oairequest(subtxt, lnsub+1, lnsub+vars.genamt)
else:
tokens = []
@ -643,23 +735,28 @@ def calcsubmit(txt):
# Prepend Memory, WI, and Prompt before action tokens
tokens = memtokens + witokens + prompttkns + tokens
# Send completed bundle to generator
ln = len(tokens)
if(vars.model != "Colab"):
if(not vars.model in ["Colab", "OAI"]):
generate (
tokenizer.decode(tokens),
ln+1,
ln+vars.genamt
)
else:
elif(vars.model == "Colab"):
sendtocolab(
tokenizer.decode(tokens),
ln+1,
ln+vars.genamt
)
elif(vars.model == "OAI"):
oairequest(
tokenizer.decode(tokens),
ln+1,
ln+vars.genamt
)
# For InferKit web API
else:
@ -1151,6 +1248,55 @@ def ikrequest(txt):
emit('from_server', {'cmd': 'errmsg', 'data': errmsg})
set_aibusy(0)
#==================================================================#
# Assembles game data into a request to OpenAI API
#==================================================================#
def oairequest(txt, min, max):
# Log request to console
print("{0}Len:{1}, Txt:{2}{3}".format(colors.YELLOW, len(txt), txt, colors.END))
# Store context in memory to use it for comparison with generated content
vars.lastctx = txt
# Build request JSON data
reqdata = {
'prompt': txt,
'max_tokens': max,
'temperature': vars.temp,
'top_p': vars.top_p,
'n': 1,
'stream': False
}
req = requests.post(
vars.oaiurl,
json = reqdata,
headers = {
'Authorization': 'Bearer '+vars.oaiapikey,
'Content-Type': 'application/json'
}
)
# Deal with the response
if(req.status_code == 200):
genout = req.json()["choices"][0]["text"]
print("{0}{1}{2}".format(colors.CYAN, genout, colors.END))
vars.actions.append(genout)
refresh_story()
emit('from_server', {'cmd': 'texteffect', 'data': len(vars.actions)})
set_aibusy(0)
else:
# Send error message to web client
er = req.json()
if("error" in er):
type = er["error"]["type"]
message = er["error"]["message"]
errmsg = "OpenAI API Error: {0} - {1}".format(type, message)
emit('from_server', {'cmd': 'errmsg', 'data': errmsg})
set_aibusy(0)
#==================================================================#
# Forces UI to Play mode
#==================================================================#
@ -1164,11 +1310,44 @@ def exitModes():
vars.mode = "play"
#==================================================================#
# Save the story to a file
# Launch in-browser save prompt
#==================================================================#
def saveRequest():
def saveas(name):
# Check if filename exists already
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")
emit('from_server', {'cmd': 'hidesaveas', 'data': ''})
vars.saveow = False
vars.svowname = ""
else:
# File exists, prompt for overwrite
vars.saveow = True
vars.svowname = name
emit('from_server', {'cmd': 'askforoverwrite', 'data': ''})
#==================================================================#
# Save the currently running story
#==================================================================#
def save():
# Check if a file is currently open
if(".json" in vars.savedir):
saveRequest(vars.savedir)
else:
emit('from_server', {'cmd': 'saveas', 'data': ''})
#==================================================================#
# Save the story via file browser
#==================================================================#
def savetofile():
savpath = fileops.getsavepath(vars.savedir, "Save Story As", [("Json", "*.json")])
saveRequest(savpath)
#==================================================================#
# Save the story to specified path
#==================================================================#
def saveRequest(savpath):
if(savpath):
# Leave Edit/Memory mode before continuing
exitModes()
@ -1200,12 +1379,23 @@ def saveRequest():
finally:
file.close()
#==================================================================#
# Load a saved story via file browser
#==================================================================#
def getloadlist():
emit('from_server', {'cmd': 'buildload', 'data': fileops.getstoryfiles()})
#==================================================================#
# Load a saved story via file browser
#==================================================================#
def loadfromfile():
loadpath = fileops.getloadpath(vars.savedir, "Select Story File", [("Json", "*.json")])
loadRequest(loadpath)
#==================================================================#
# Load a stored story from a file
#==================================================================#
def loadRequest():
loadpath = fileops.getloadpath(vars.savedir, "Select Story File", [("Json", "*.json")])
def loadRequest(loadpath):
if(loadpath):
# Leave Edit/Memory mode before continuing
exitModes()
@ -1242,6 +1432,12 @@ def loadRequest():
file.close()
# Save path for save button
vars.savedir = loadpath
# Clear loadselect var
vars.loadselect = ""
# Refresh game screen
sendwi()
refresh_story()
@ -1351,6 +1547,9 @@ def importgame():
# Clear import data
vars.importjs = {}
# Reset current save
vars.savedir = getcwd()+"\stories"
# Refresh game screen
sendwi()
refresh_story()
@ -1388,6 +1587,9 @@ def importAidgRequest(id):
})
num += 1
# Reset current save
vars.savedir = getcwd()+"\stories"
# Refresh game screen
sendwi()
refresh_story()
@ -1397,30 +1599,26 @@ def importAidgRequest(id):
# Starts a new story
#==================================================================#
def newGameRequest():
# Ask for confirmation
root = tk.Tk()
root.attributes("-topmost", True)
confirm = tk.messagebox.askquestion("Confirm New Game", "Really start new Story?")
root.destroy()
# Leave Edit/Memory mode before continuing
exitModes()
if(confirm == "yes"):
# Leave Edit/Memory mode before continuing
exitModes()
# Clear vars values
vars.gamestarted = False
vars.prompt = ""
vars.memory = ""
vars.actions = []
vars.savedir = getcwd()+"\stories"
vars.authornote = ""
vars.worldinfo = []
vars.lastact = ""
vars.lastctx = ""
# Refresh game screen
sendwi()
setStartState()
# Clear vars values
vars.gamestarted = False
vars.prompt = ""
vars.memory = ""
vars.actions = []
vars.authornote = ""
vars.worldinfo = []
vars.lastact = ""
vars.lastctx = ""
# Reset current save
vars.savedir = getcwd()+"\stories"
# Refresh game screen
sendwi()
setStartState()
#==================================================================#

View File

@ -1,5 +1,7 @@
import tkinter as tk
from tkinter import filedialog
from os import getcwd, listdir, path
import json
#==================================================================#
# Generic Method for prompting for file path
@ -50,4 +52,26 @@ def getdirpath(dir, title):
if(path != "" and path != None):
return path
else:
return None
return None
#==================================================================#
# Returns an array of dicts containing story files in /stories
#==================================================================#
def getstoryfiles():
list = []
for file in listdir(getcwd()+"/stories"):
if file.endswith(".json"):
ob = {}
ob["name"] = file.replace(".json", "")
f = open(getcwd()+"/stories/"+file, "r")
js = json.load(f)
f.close()
ob["actions"] = len(js["actions"])
list.append(ob)
return list
#==================================================================#
# Returns True if json file exists with requested save name
#==================================================================#
def saveexists(name):
return path.exists(getcwd()+"/stories/"+name+".json")

View File

@ -1 +1 @@
pip install -r requirements.txt
start cmd /k pip install -r requirements.txt

View File

@ -1 +1 @@
py -3 aiserver.py
start cmd /k py -3 aiserver.py

View File

@ -9,6 +9,8 @@ var socket;
var connect_status;
var button_newgame;
var button_save;
var button_saveas;
var button_savetofile;
var button_load;
var button_import;
var button_impaidg;
@ -40,6 +42,18 @@ var aidgpopup;
var aidgpromptnum;
var aidg_accept;
var aidg_close;
var saveaspopup;
var saveasinput;
var saveas_accept;
var saveas_close;
var saveasoverwrite;
var loadpopup;
var loadcontent;
var load_accept;
var load_close;
var nspopup;
var ns_accept;
var ns_close;
// Key states
var shift_down = false;
@ -201,6 +215,7 @@ function hideWiDeleteConfirm(num) {
function highlightImportLine(ref) {
$("#popupcontent > div").removeClass("popuplistselected");
ref.addClass("popuplistselected");
enableButtons([popup_accept]);
}
function enableButtons(refs) {
@ -270,6 +285,7 @@ function popupShow(state) {
if(state) {
popup.removeClass("hidden");
popup.addClass("flex");
disableButtons([popup_accept]);
} else {
popup.removeClass("flex");
popup.addClass("hidden");
@ -385,6 +401,68 @@ function sendAidgImportRequest() {
aidgpromptnum.val("");
}
function showSaveAsPopup() {
disableButtons([saveas_accept]);
saveaspopup.removeClass("hidden");
saveaspopup.addClass("flex");
saveasinput.focus();
}
function hideSaveAsPopup() {
saveaspopup.removeClass("flex");
saveaspopup.addClass("hidden");
saveasinput.val("");
hide([saveasoverwrite]);
}
function sendSaveAsRequest() {
socket.send({'cmd': 'saveasrequest', 'data': saveasinput.val()});
}
function showLoadPopup() {
loadpopup.removeClass("hidden");
loadpopup.addClass("flex");
}
function hideLoadPopup() {
loadpopup.removeClass("flex");
loadpopup.addClass("hidden");
loadcontent.html("");
}
function buildLoadList(ar) {
disableButtons([load_accept]);
loadcontent.html("");
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>\
</div>");
$("#load"+i).on("click", function () {
enableButtons([load_accept]);
socket.send({'cmd': 'loadselect', 'data': $(this).attr("name")});
highlightLoadLine($(this));
});
}
}
function highlightLoadLine(ref) {
$("#loadlistcontent > div").removeClass("popuplistselected");
ref.addClass("popuplistselected");
}
function showNewStoryPopup() {
nspopup.removeClass("hidden");
nspopup.addClass("flex");
}
function hideNewStoryPopup() {
nspopup.removeClass("flex");
nspopup.addClass("hidden");
}
//=================================================================//
// READY/RUNTIME
//=================================================================//
@ -392,40 +470,55 @@ function sendAidgImportRequest() {
$(document).ready(function(){
// Bind UI references
connect_status = $('#connectstatus');
button_newgame = $('#btn_newgame');
button_save = $('#btn_save');
button_load = $('#btn_load');
button_import = $("#btn_import");
button_impaidg = $("#btn_impaidg");
button_settings = $('#btn_settings');
button_format = $('#btn_format');
button_send = $('#btnsend');
button_actedit = $('#btn_actedit');
button_actmem = $('#btn_actmem');
button_actback = $('#btn_actundo');
button_actretry = $('#btn_actretry');
button_delete = $('#btn_delete');
button_actwi = $('#btn_actwi');
game_text = $('#gametext');
input_text = $('#input_text');
message_text = $('#messagefield');
settings_menu = $("#settingsmenu");
format_menu = $('#formatmenu');
anote_menu = $('#anoterowcontainer');
wi_menu = $('#wimenu');
anote_input = $('#anoteinput');
anote_labelcur = $('#anotecur');
anote_slider = $('#anotedepth');
popup = $("#popupcontainer");
popup_title = $("#popuptitletext");
popup_content = $("#popupcontent");
popup_accept = $("#btn_popupaccept");
popup_close = $("#btn_popupclose");
aidgpopup = $("#aidgpopupcontainer");
aidgpromptnum = $("#aidgpromptnum");
aidg_accept = $("#btn_aidgpopupaccept");
aidg_close = $("#btn_aidgpopupclose");
connect_status = $('#connectstatus');
button_newgame = $('#btn_newgame');
button_save = $('#btn_save');
button_saveas = $('#btn_saveas');
button_savetofile = $('#btn_savetofile');
button_load = $('#btn_load');
button_loadfrfile = $('#btn_loadfromfile');
button_import = $("#btn_import");
button_impaidg = $("#btn_impaidg");
button_settings = $('#btn_settings');
button_format = $('#btn_format');
button_send = $('#btnsend');
button_actedit = $('#btn_actedit');
button_actmem = $('#btn_actmem');
button_actback = $('#btn_actundo');
button_actretry = $('#btn_actretry');
button_delete = $('#btn_delete');
button_actwi = $('#btn_actwi');
game_text = $('#gametext');
input_text = $('#input_text');
message_text = $('#messagefield');
settings_menu = $("#settingsmenu");
format_menu = $('#formatmenu');
anote_menu = $('#anoterowcontainer');
wi_menu = $('#wimenu');
anote_input = $('#anoteinput');
anote_labelcur = $('#anotecur');
anote_slider = $('#anotedepth');
popup = $("#popupcontainer");
popup_title = $("#popuptitletext");
popup_content = $("#popupcontent");
popup_accept = $("#btn_popupaccept");
popup_close = $("#btn_popupclose");
aidgpopup = $("#aidgpopupcontainer");
aidgpromptnum = $("#aidgpromptnum");
aidg_accept = $("#btn_aidgpopupaccept");
aidg_close = $("#btn_aidgpopupclose");
saveaspopup = $("#saveascontainer");
saveasinput = $("#savename");
saveas_accept = $("#btn_saveasaccept");
saveas_close = $("#btn_saveasclose");
saveasoverwrite = $("#saveasoverwrite");
loadpopup = $("#loadcontainer");
loadcontent = $("#loadlistcontent");
load_accept = $("#btn_loadaccept");
load_close = $("#btn_loadclose");
nspopup = $("#newgamecontainer");
ns_accept = $("#btn_nsaccept");
ns_close = $("#btn_nsclose");
// Connect to SocketIO server
loc = window.document.location;
@ -465,10 +558,14 @@ $(document).ready(function(){
hide([wi_menu, button_delete]);
show([game_text, button_actedit, button_actmem, button_actwi, button_actback, button_actretry]);
hideMessage();
hideWaitAnimation();
button_actedit.html("Edit");
button_actmem.html("Memory");
button_actwi.html("W Info");
hideAidgPopup();
hideSaveAsPopup();
hideLoadPopup();
hideNewStoryPopup();
}
} else if(msg.cmd == "editmode") {
// Enable or Disable edit mode
@ -598,8 +695,20 @@ $(document).ready(function(){
} else if(msg.cmd == "requestwiitem") {
// Package WI contents and send back to server
returnWiList(msg.data);
} else if(msg.cmd == "saveas") {
// Show Save As prompt
showSaveAsPopup();
} else if(msg.cmd == "hidesaveas") {
// Hide Save As prompt
hideSaveAsPopup();
} else if(msg.cmd == "buildload") {
// Send array of save files to load UI
buildLoadList(msg.data);
} else if(msg.cmd == "askforoverwrite") {
// Show overwrite warning
show([saveasoverwrite]);
}
});
});
socket.on('disconnect', function() {
connect_status.html("<b>Lost connection...</b>");
@ -632,22 +741,18 @@ $(document).ready(function(){
socket.send({'cmd': 'memory', 'data': ''});
});
button_save.on("click", function(ev) {
socket.send({'cmd': 'save', 'data': ''});
button_savetofile.on("click", function(ev) {
socket.send({'cmd': 'savetofile', 'data': ''});
});
button_load.on("click", function(ev) {
socket.send({'cmd': 'load', 'data': ''});
button_loadfrfile.on("click", function(ev) {
socket.send({'cmd': 'loadfromfile', 'data': ''});
});
button_import.on("click", function(ev) {
socket.send({'cmd': 'import', 'data': ''});
});
button_newgame.on("click", function(ev) {
socket.send({'cmd': 'newgame', 'data': ''});
});
button_settings.on("click", function(ev) {
$('#settingsmenu').slideToggle("slow");
});
@ -680,6 +785,58 @@ $(document).ready(function(){
sendAidgImportRequest();
});
button_save.on("click", function(ev) {
socket.send({'cmd': 'saverequest', 'data': ''});
});
button_saveas.on("click", function(ev) {
showSaveAsPopup();
});
saveas_close.on("click", function(ev) {
hideSaveAsPopup();
socket.send({'cmd': 'clearoverwrite', 'data': ''});
});
saveas_accept.on("click", function(ev) {
sendSaveAsRequest();
});
button_load.on("click", function(ev) {
socket.send({'cmd': 'loadlistrequest', 'data': ''});
});
load_close.on("click", function(ev) {
hideLoadPopup();
});
load_accept.on("click", function(ev) {
socket.send({'cmd': 'loadrequest', 'data': ''});
hideLoadPopup();
});
button_newgame.on("click", function(ev) {
showNewStoryPopup();
});
ns_accept.on("click", function(ev) {
socket.send({'cmd': 'newgame', 'data': ''});
hideNewStoryPopup();
});
ns_close.on("click", function(ev) {
hideNewStoryPopup();
});
saveasinput.on("input", function () {
if(saveasinput.val() == "") {
disableButtons([saveas_accept]);
} else {
enableButtons([saveas_accept]);
}
hide([saveasoverwrite]);
});
// Bind Enter button to submit
input_text.keydown(function (ev) {
if (ev.which == 13 && !shift_down) {
@ -705,5 +862,11 @@ $(document).ready(function(){
sendAidgImportRequest();
}
});
saveasinput.keydown(function (ev) {
if (ev.which == 13 && saveasinput.val() != "") {
sendSaveAsRequest();
}
});
});

View File

@ -226,6 +226,43 @@ chunk {
color: #ffffff;
}
#saveaspopup {
width: 350px;
background-color: #262626;
margin-top: 200px;
}
#saveasoverwrite {
color: #ff9900;
font-weight: bold;
text-align: center;
}
#loadpopup {
width: 500px;
background-color: #262626;
margin-top: 100px;
}
@media (max-width: 768px) {
#loadpopup {
width: 100%;
background-color: #262626;
margin-top: 100px;
}
}
#loadlistcontent {
height: 325px;
overflow-y: scroll;
}
#nspopup {
width: 350px;
background-color: #262626;
margin-top: 200px;
}
/*================= Classes =================*/
.aidgpopupcontent {
@ -381,6 +418,29 @@ chunk {
text-align: right;
}
.loadlistheader {
padding-left: 10px;
display: grid;
grid-template-columns: 80% 20%;
color: #737373;
}
.loadlistitem {
padding: 5px 10px 5px 10px;
display: grid;
grid-template-columns: 80% 20%;
color: #ffffff;
-moz-transition: background-color 0.25s ease-in;
-o-transition: background-color 0.25s ease-in;
-webkit-transition: background-color 0.25s ease-in;
}
.loadlistitem:hover {
cursor: pointer;
background-color: #688f1f;
}
.navbar .navbar-nav .nav-link:hover {
border-radius: 5px;
background-color: #98bcdb;

View File

@ -6,13 +6,13 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="static/socket.io.min.js"></script>
<script src="static/application.js?ver=0.14.2"></script>
<script src="static/application.js?ver=0.15.0g"></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?ver=0.14.2">
<link rel="stylesheet" href="static/custom.css?ver=0.15.0g">
</head>
<body>
<div class="container">
@ -30,14 +30,23 @@
<li class="nav-item">
<a class="nav-link" href="#" id="btn_newgame">New Story</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#" id="btn_save">Save</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#" id="btn_load">Load</a>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Save</a>
<div class="dropdown-menu">
<a class="dropdown-item" href="#" id="btn_save">Save</a>
<a class="dropdown-item" href="#" id="btn_saveas">Save As</a>
<a class="dropdown-item" href="#" id="btn_savetofile">Save To File...</a>
</div>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Import...</a>
<a class="nav-link dropdown-toggle" href="#" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Load</a>
<div class="dropdown-menu">
<a class="dropdown-item" href="#" id="btn_load">Load</a>
<a class="dropdown-item" href="#" id="btn_loadfromfile">Load From File...</a>
</div>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Import</a>
<div class="dropdown-menu">
<a class="dropdown-item" href="#" id="btn_import">AI Dungeon Adventure</a>
<a class="dropdown-item" href="#" id="btn_impaidg">aidg.club Prompt</a>
@ -159,5 +168,53 @@
</div>
</div>
</div>
<div class="popupcontainer hidden" id="saveascontainer">
<div id="saveaspopup">
<div class="popuptitlebar">
<div class="popuptitletext">Enter Name For Save</div>
</div>
<div class="aidgpopupcontent">
<input class="form-control" type="text" placeholder="Save Name" id="savename">
</div>
<div class="hidden" id="saveasoverwrite">
<span>File already exists. Really overwrite?</span>
</div>
<div class="popupfooter">
<button type="button" class="btn btn-primary" id="btn_saveasaccept">Accept</button>
<button type="button" class="btn btn-primary" id="btn_saveasclose">Cancel</button>
</div>
</div>
</div>
<div class="popupcontainer hidden" id="loadcontainer">
<div id="loadpopup">
<div class="popuptitlebar">
<div class="popuptitletext">Select A Story To Load</div>
</div>
<div class="loadlistheader">
<div>Save Name</div>
<div># Actions</div>
</div>
<div id="loadlistcontent">
</div>
<div class="popupfooter">
<button type="button" class="btn btn-primary" id="btn_loadaccept">Load</button>
<button type="button" class="btn btn-primary" id="btn_loadclose">Cancel</button>
</div>
</div>
</div>
<div class="popupcontainer hidden" id="newgamecontainer">
<div id="nspopup">
<div class="popuptitlebar">
<div class="popuptitletext">Really Start A New Story?</div>
</div>
<div class="aidgpopuplistheader">
Unsaved data will be lost.
</div>
<div class="popupfooter">
<button type="button" class="btn btn-primary" id="btn_nsaccept">Accept</button>
<button type="button" class="btn btn-primary" id="btn_nsclose">Cancel</button>
</div>
</div>
</div>
</body>
</html>

View File

@ -80,9 +80,14 @@ def addsentencespacing(txt, vars):
if(lastchar == "." or lastchar == "!" or lastchar == "?" or lastchar == "," or lastchar == ";" or lastchar == ":"):
txt = " " + txt
return txt
#==================================================================#
# 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()
return filename