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:
parent
4996e0ff46
commit
f9bbb174a6
340
aiserver.py
340
aiserver.py
|
@ -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()
|
||||
|
||||
|
||||
#==================================================================#
|
||||
|
|
26
fileops.py
26
fileops.py
|
@ -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")
|
|
@ -1 +1 @@
|
|||
pip install -r requirements.txt
|
||||
start cmd /k pip install -r requirements.txt
|
|
@ -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();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
11
utils.py
11
utils.py
|
@ -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
|
||||
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue