Merge pull request #49 from VE-FORBRYDERNE/gui-and-scripting

Scripting and GUI improvements
This commit is contained in:
henk717 2021-12-24 06:21:12 +01:00 committed by GitHub
commit 726b42889b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 704 additions and 119 deletions

View File

@ -82,6 +82,7 @@ class vars:
submission = "" # Same as above, but after applying input formatting submission = "" # Same as above, but after applying input formatting
lastctx = "" # The last context submitted to the generator lastctx = "" # The last context submitted to the generator
model = "" # Model ID string chosen at startup model = "" # Model ID string chosen at startup
model_orig = "" # Original model string before being changed by auto model type detection
model_type = "" # Model Type (Automatically taken from the model config) model_type = "" # Model Type (Automatically taken from the model config)
noai = False # Runs the script without starting up the transformers pipeline noai = False # Runs the script without starting up the transformers pipeline
aibusy = False # Stops submissions while the AI is working aibusy = False # Stops submissions while the AI is working
@ -113,9 +114,12 @@ class vars:
lua_kobold = None # `kobold` from` bridge.lua lua_kobold = None # `kobold` from` bridge.lua
lua_koboldcore = None # `koboldcore` from bridge.lua lua_koboldcore = None # `koboldcore` from bridge.lua
lua_logname = ... # Name of previous userscript that logged to terminal lua_logname = ... # Name of previous userscript that logged to terminal
lua_running = False # Whether or not Lua is running (i.e. wasn't stopped due to an error)
lua_edited = set() # Set of chunk numbers that were edited from a Lua generation modifier lua_edited = set() # Set of chunk numbers that were edited from a Lua generation modifier
lua_deleted = set() # Set of chunk numbers that were deleted from a Lua generation modifier lua_deleted = set() # Set of chunk numbers that were deleted from a Lua generation modifier
spfilename = "" # Filename of soft prompt to load, or an empty string if not using a soft prompt
userscripts = [] # List of userscripts to load userscripts = [] # List of userscripts to load
last_userscripts = [] # List of previous userscript filenames from the previous time userscripts were send via usstatitems
corescript = "default.lua" # Filename of corescript to load corescript = "default.lua" # Filename of corescript to load
# badwords = [] # Array of str/chr values that should be removed from output # badwords = [] # Array of str/chr values that should be removed from output
badwordsids = [[13460], [6880], [50256], [42496], [4613], [17414], [22039], [16410], [27], [29], [38430], [37922], [15913], [24618], [28725], [58], [47175], [36937], [26700], [12878], [16471], [37981], [5218], [29795], [13412], [45160], [3693], [49778], [4211], [20598], [36475], [33409], [44167], [32406], [29847], [29342], [42669], [685], [25787], [7359], [3784], [5320], [33994], [33490], [34516], [43734], [17635], [24293], [9959], [23785], [21737], [28401], [18161], [26358], [32509], [1279], [38155], [18189], [26894], [6927], [14610], [23834], [11037], [14631], [26933], [46904], [22330], [25915], [47934], [38214], [1875], [14692], [41832], [13163], [25970], [29565], [44926], [19841], [37250], [49029], [9609], [44438], [16791], [17816], [30109], [41888], [47527], [42924], [23984], [49074], [33717], [31161], [49082], [30138], [31175], [12240], [14804], [7131], [26076], [33250], [3556], [38381], [36338], [32756], [46581], [17912], [49146]] # Tokenized array of badwords used to prevent AI artifacting badwordsids = [[13460], [6880], [50256], [42496], [4613], [17414], [22039], [16410], [27], [29], [38430], [37922], [15913], [24618], [28725], [58], [47175], [36937], [26700], [12878], [16471], [37981], [5218], [29795], [13412], [45160], [3693], [49778], [4211], [20598], [36475], [33409], [44167], [32406], [29847], [29342], [42669], [685], [25787], [7359], [3784], [5320], [33994], [33490], [34516], [43734], [17635], [24293], [9959], [23785], [21737], [28401], [18161], [26358], [32509], [1279], [38155], [18189], [26894], [6927], [14610], [23834], [11037], [14631], [26933], [46904], [22330], [25915], [47934], [38214], [1875], [14692], [41832], [13163], [25970], [29565], [44926], [19841], [37250], [49029], [9609], [44438], [16791], [17816], [30109], [41888], [47527], [42924], [23984], [49074], [33717], [31161], [49082], [30138], [31175], [12240], [14804], [7131], [26076], [33250], [3556], [38381], [36338], [32756], [46581], [17912], [49146]] # Tokenized array of badwords used to prevent AI artifacting
@ -140,6 +144,7 @@ class vars:
importjs = {} # Temporary storage for import data importjs = {} # Temporary storage for import data
loadselect = "" # Temporary storage for story filename to load loadselect = "" # Temporary storage for story filename to load
spselect = "" # Temporary storage for soft prompt filename to load spselect = "" # Temporary storage for soft prompt filename to load
spmeta = None # Metadata of current soft prompt, or None if not using a soft prompt
sp = None # Current soft prompt tensor (as a NumPy array) sp = None # Current soft prompt tensor (as a NumPy array)
sp_length = 0 # Length of current soft prompt in tokens, or 0 if not using a soft prompt sp_length = 0 # Length of current soft prompt in tokens, or 0 if not using a soft prompt
svowname = "" # Filename that was flagged for overwrite confirm svowname = "" # Filename that was flagged for overwrite confirm
@ -180,7 +185,7 @@ def getModelSelection():
while(vars.model == ''): while(vars.model == ''):
modelsel = input("Model #> ") modelsel = input("Model #> ")
if(modelsel.isnumeric() and int(modelsel) > 0 and int(modelsel) <= len(modellist)): if(modelsel.isnumeric() and int(modelsel) > 0 and int(modelsel) <= len(modellist)):
vars.model = modellist[int(modelsel)-1][1] vars.model = vars.model_orig = modellist[int(modelsel)-1][1]
else: else:
print("{0}Please enter a valid selection.{1}".format(colors.RED, colors.END)) print("{0}Please enter a valid selection.{1}".format(colors.RED, colors.END))
@ -361,7 +366,7 @@ parser.add_argument("--override_rename", action='store_true', help="Renaming sto
parser.add_argument("--configname", help="Force a fixed configuration name to aid with config management.") parser.add_argument("--configname", help="Force a fixed configuration name to aid with config management.")
args = parser.parse_args() args = parser.parse_args()
vars.model = args.model; vars.model = vars.model_orig = args.model;
if args.remote: if args.remote:
vars.remote = True; vars.remote = True;
@ -646,7 +651,22 @@ if(not vars.model in ["InferKit", "Colab", "OAI", "ReadOnly", "TPUMeshTransforme
# Patch transformers to use our custom logit warpers # Patch transformers to use our custom logit warpers
from transformers import LogitsProcessorList, LogitsWarper, LogitsProcessor, TopKLogitsWarper, TopPLogitsWarper, TemperatureLogitsWarper from transformers import LogitsProcessorList, LogitsWarper, LogitsProcessor, TopKLogitsWarper, TopPLogitsWarper, TemperatureLogitsWarper, RepetitionPenaltyLogitsProcessor
def dynamic_processor_wrap(cls, field_name, var_name, cond=None):
old_call = cls.__call__
def new_call(self, *args, **kwargs):
setattr(self, field_name, getattr(vars, var_name))
assert len(args) == 2
if(cond is None or cond(getattr(vars, var_name))):
return old_call(self, *args, **kwargs)
return args[1]
cls.__call__ = new_call
dynamic_processor_wrap(RepetitionPenaltyLogitsProcessor, "penalty", "rep_pen", cond=lambda x: x != 1.0)
dynamic_processor_wrap(TopKLogitsWarper, "top_k", "top_k", cond=lambda x: x > 0)
dynamic_processor_wrap(TopPLogitsWarper, "top_p", "top_p", cond=lambda x: x < 1.0)
dynamic_processor_wrap(TemperatureLogitsWarper, "temperature", "temp", cond=lambda x: x != 1.0)
class TailFreeLogitsWarper(LogitsWarper): class TailFreeLogitsWarper(LogitsWarper):
def __init__(self, tfs: float, filter_value: float = -float("Inf"), min_tokens_to_keep: int = 1): def __init__(self, tfs: float, filter_value: float = -float("Inf"), min_tokens_to_keep: int = 1):
@ -658,6 +678,8 @@ if(not vars.model in ["InferKit", "Colab", "OAI", "ReadOnly", "TPUMeshTransforme
self.min_tokens_to_keep = min_tokens_to_keep self.min_tokens_to_keep = min_tokens_to_keep
def __call__(self, input_ids: torch.LongTensor, scores: torch.FloatTensor) -> torch.FloatTensor: def __call__(self, input_ids: torch.LongTensor, scores: torch.FloatTensor) -> torch.FloatTensor:
self.tfs = vars.tfs
if self.filter_value >= 1.0: if self.filter_value >= 1.0:
return scores return scores
sorted_logits, sorted_indices = torch.sort(scores, descending=True) sorted_logits, sorted_indices = torch.sort(scores, descending=True)
@ -725,31 +747,17 @@ if(not vars.model in ["InferKit", "Colab", "OAI", "ReadOnly", "TPUMeshTransforme
new_get_logits_processor.old_get_logits_processor = transformers.generation_utils.GenerationMixin._get_logits_processor new_get_logits_processor.old_get_logits_processor = transformers.generation_utils.GenerationMixin._get_logits_processor
transformers.generation_utils.GenerationMixin._get_logits_processor = new_get_logits_processor transformers.generation_utils.GenerationMixin._get_logits_processor = new_get_logits_processor
def new_get_logits_warper( def new_get_logits_warper(beams: int = 1,) -> LogitsProcessorList:
top_k: int = None,
top_p: float = None,
tfs: float = None,
temp: float = None,
beams: int = 1,
) -> LogitsProcessorList:
warper_list = LogitsProcessorList() warper_list = LogitsProcessorList()
if(top_k is not None and top_k > 0): warper_list.append(TopKLogitsWarper(top_k=1, min_tokens_to_keep=1 + (beams > 1)))
warper_list.append(TopKLogitsWarper(top_k=top_k, min_tokens_to_keep=1 + (beams > 1))) warper_list.append(TopPLogitsWarper(top_p=0.5, min_tokens_to_keep=1 + (beams > 1)))
if(top_p is not None and top_p < 1.0): warper_list.append(TailFreeLogitsWarper(tfs=0.5, min_tokens_to_keep=1 + (beams > 1)))
warper_list.append(TopPLogitsWarper(top_p=top_p, min_tokens_to_keep=1 + (beams > 1))) warper_list.append(TemperatureLogitsWarper(temperature=0.5))
if(tfs is not None and tfs < 1.0):
warper_list.append(TailFreeLogitsWarper(tfs=tfs, min_tokens_to_keep=1 + (beams > 1)))
if(temp is not None and temp != 1.0):
warper_list.append(TemperatureLogitsWarper(temperature=temp))
return warper_list return warper_list
def new_sample(self, *args, **kwargs): def new_sample(self, *args, **kwargs):
assert kwargs.pop("logits_warper", None) is not None assert kwargs.pop("logits_warper", None) is not None
kwargs["logits_warper"] = new_get_logits_warper( kwargs["logits_warper"] = new_get_logits_warper(
top_k=vars.top_k,
top_p=vars.top_p,
tfs=vars.tfs,
temp=vars.temp,
beams=1, beams=1,
) )
return new_sample.old_sample(self, *args, **kwargs) return new_sample.old_sample(self, *args, **kwargs)
@ -1048,10 +1056,13 @@ def load_lua_scripts():
vars.lua_koboldbridge.obliterate_multiverse() vars.lua_koboldbridge.obliterate_multiverse()
tpool.execute(vars.lua_koboldbridge.load_corescript, vars.corescript) tpool.execute(vars.lua_koboldbridge.load_corescript, vars.corescript)
tpool.execute(vars.lua_koboldbridge.load_userscripts, filenames, modulenames, descriptions) tpool.execute(vars.lua_koboldbridge.load_userscripts, filenames, modulenames, descriptions)
vars.lua_running = True
except lupa.LuaError as e: except lupa.LuaError as e:
vars.lua_koboldbridge.obliterate_multiverse() vars.lua_koboldbridge.obliterate_multiverse()
vars.lua_running = False
if(vars.serverstarted): if(vars.serverstarted):
emit('from_server', {'cmd': 'errmsg', 'data': 'Lua script error, please check console.'}, broadcast=True) emit('from_server', {'cmd': 'errmsg', 'data': 'Lua script error, please check console.'}, broadcast=True)
sendUSStatItems()
print("{0}{1}{2}".format(colors.RED, "***LUA ERROR***: ", colors.END), end="", file=sys.stderr) print("{0}{1}{2}".format(colors.RED, "***LUA ERROR***: ", colors.END), end="", file=sys.stderr)
print("{0}{1}{2}".format(colors.RED, str(e).replace("\033", ""), colors.END), file=sys.stderr) print("{0}{1}{2}".format(colors.RED, str(e).replace("\033", ""), colors.END), file=sys.stderr)
print("{0}{1}{2}".format(colors.YELLOW, "Lua engine stopped; please open 'Userscripts' and press Load to reinitialize scripts.", colors.END), file=sys.stderr) print("{0}{1}{2}".format(colors.YELLOW, "Lua engine stopped; please open 'Userscripts' and press Load to reinitialize scripts.", colors.END), file=sys.stderr)
@ -1376,23 +1387,23 @@ def lua_get_modeltype():
return "readonly" return "readonly"
if(vars.model in ("Colab", "OAI", "InferKit")): if(vars.model in ("Colab", "OAI", "InferKit")):
return "api" return "api"
if(vars.model in ("GPT2Custom", "NeoCustom")): if(vars.model not in ("TPUMeshTransformerGPTJ",) and (vars.model in ("GPT2Custom", "NeoCustom") or vars.model_type in ("gpt2", "gpt_neo", "gptj"))):
hidden_size = get_hidden_size_from_model(model) hidden_size = get_hidden_size_from_model(model)
if(vars.model in ("gpt2",) or (vars.model == "GPT2Custom" and hidden_size == 768)): if(vars.model in ("gpt2",) or (vars.model_type == "gpt2" and hidden_size == 768)):
return "gpt2" return "gpt2"
if(vars.model in ("gpt2-medium",) or (vars.model == "GPT2Custom" and hidden_size == 1024)): if(vars.model in ("gpt2-medium",) or (vars.model_type == "gpt2" and hidden_size == 1024)):
return "gpt2-medium" return "gpt2-medium"
if(vars.model in ("gpt2-large",) or (vars.model == "GPT2Custom" and hidden_size == 1280)): if(vars.model in ("gpt2-large",) or (vars.model_type == "gpt2" and hidden_size == 1280)):
return "gpt2-large" return "gpt2-large"
if(vars.model in ("gpt2-xl",) or (vars.model == "GPT2Custom" and hidden_size == 1600)): if(vars.model in ("gpt2-xl",) or (vars.model_type == "gpt2" and hidden_size == 1600)):
return "gpt2-xl" return "gpt2-xl"
if(vars.model == "NeoCustom" and hidden_size == 768): if(vars.model_type == "gpt_neo" and hidden_size == 768):
return "gpt-neo-125M" return "gpt-neo-125M"
if(vars.model in ("EleutherAI/gpt-neo-1.3B",) or (vars.model == "NeoCustom" and hidden_size == 2048)): if(vars.model in ("EleutherAI/gpt-neo-1.3B",) or (vars.model_type == "gpt_neo" and hidden_size == 2048)):
return "gpt-neo-1.3B" return "gpt-neo-1.3B"
if(vars.model in ("EleutherAI/gpt-neo-2.7B",) or (vars.model == "NeoCustom" and hidden_size == 2560)): if(vars.model in ("EleutherAI/gpt-neo-2.7B",) or (vars.model_type == "gpt_neo" and hidden_size == 2560)):
return "gpt-neo-2.7B" return "gpt-neo-2.7B"
if(vars.model in ("EleutherAI/gpt-j-6B",) or (vars.model == "NeoCustom" and hidden_size == 4096) or (vars.model == "TPUMeshTransformerGPTJ" and tpu_mtj_backend.params["d_model"] == 4096)): if(vars.model in ("EleutherAI/gpt-j-6B",) or (vars.model == "TPUMeshTransformerGPTJ" and tpu_mtj_backend.params["d_model"] == 4096) or (vars.model_type in ("gpt_neo", "gptj") and hidden_size == 4096)):
return "gpt-j-6B" return "gpt-j-6B"
return "unknown" return "unknown"
@ -1423,7 +1434,9 @@ def execute_inmod():
tpool.execute(vars.lua_koboldbridge.execute_inmod) tpool.execute(vars.lua_koboldbridge.execute_inmod)
except lupa.LuaError as e: except lupa.LuaError as e:
vars.lua_koboldbridge.obliterate_multiverse() vars.lua_koboldbridge.obliterate_multiverse()
vars.lua_running = False
emit('from_server', {'cmd': 'errmsg', 'data': 'Lua script error, please check console.'}, broadcast=True) emit('from_server', {'cmd': 'errmsg', 'data': 'Lua script error, please check console.'}, broadcast=True)
sendUSStatItems()
print("{0}{1}{2}".format(colors.RED, "***LUA ERROR***: ", colors.END), end="", file=sys.stderr) print("{0}{1}{2}".format(colors.RED, "***LUA ERROR***: ", colors.END), end="", file=sys.stderr)
print("{0}{1}{2}".format(colors.RED, str(e).replace("\033", ""), colors.END), file=sys.stderr) print("{0}{1}{2}".format(colors.RED, str(e).replace("\033", ""), colors.END), file=sys.stderr)
print("{0}{1}{2}".format(colors.YELLOW, "Lua engine stopped; please open 'Userscripts' and press Load to reinitialize scripts.", colors.END), file=sys.stderr) print("{0}{1}{2}".format(colors.YELLOW, "Lua engine stopped; please open 'Userscripts' and press Load to reinitialize scripts.", colors.END), file=sys.stderr)
@ -1439,7 +1452,9 @@ def execute_outmod():
tpool.execute(vars.lua_koboldbridge.execute_outmod) tpool.execute(vars.lua_koboldbridge.execute_outmod)
except lupa.LuaError as e: except lupa.LuaError as e:
vars.lua_koboldbridge.obliterate_multiverse() vars.lua_koboldbridge.obliterate_multiverse()
vars.lua_running = False
emit('from_server', {'cmd': 'errmsg', 'data': 'Lua script error, please check console.'}, broadcast=True) emit('from_server', {'cmd': 'errmsg', 'data': 'Lua script error, please check console.'}, broadcast=True)
sendUSStatItems()
print("{0}{1}{2}".format(colors.RED, "***LUA ERROR***: ", colors.END), end="", file=sys.stderr) print("{0}{1}{2}".format(colors.RED, "***LUA ERROR***: ", colors.END), end="", file=sys.stderr)
print("{0}{1}{2}".format(colors.RED, str(e).replace("\033", ""), colors.END), file=sys.stderr) print("{0}{1}{2}".format(colors.RED, str(e).replace("\033", ""), colors.END), file=sys.stderr)
print("{0}{1}{2}".format(colors.YELLOW, "Lua engine stopped; please open 'Userscripts' and press Load to reinitialize scripts.", colors.END), file=sys.stderr) print("{0}{1}{2}".format(colors.YELLOW, "Lua engine stopped; please open 'Userscripts' and press Load to reinitialize scripts.", colors.END), file=sys.stderr)
@ -1465,7 +1480,8 @@ vars.lua_state = lupa.LuaRuntime(unpack_returned_tuples=True)
bridged = { bridged = {
"corescript_path": os.path.join(os.path.dirname(os.path.realpath(__file__)), "cores"), "corescript_path": os.path.join(os.path.dirname(os.path.realpath(__file__)), "cores"),
"userscript_path": os.path.join(os.path.dirname(os.path.realpath(__file__)), "userscripts"), "userscript_path": os.path.join(os.path.dirname(os.path.realpath(__file__)), "userscripts"),
"lib_path": os.path.join(os.path.dirname(os.path.realpath(__file__)), "extern", "lualibs"), "config_path": os.path.join(os.path.dirname(os.path.realpath(__file__)), "userscripts"),
"lib_paths": vars.lua_state.table(os.path.join(os.path.dirname(os.path.realpath(__file__)), "lualibs"), os.path.join(os.path.dirname(os.path.realpath(__file__)), "extern", "lualibs")),
"load_callback": load_callback, "load_callback": load_callback,
"print": lua_print, "print": lua_print,
"warn": lua_warn, "warn": lua_warn,
@ -1524,6 +1540,9 @@ def do_connect():
if(vars.allowsp): if(vars.allowsp):
emit('from_server', {'cmd': 'allowsp', 'data': vars.allowsp}) emit('from_server', {'cmd': 'allowsp', 'data': vars.allowsp})
sendUSStatItems()
emit('from_server', {'cmd': 'spstatitems', 'data': {vars.spfilename: vars.spmeta} if vars.allowsp and len(vars.spfilename) else {}}, broadcast=True)
if(not vars.gamestarted): if(not vars.gamestarted):
setStartState() setStartState()
sendsettings() sendsettings()
@ -1714,6 +1733,12 @@ def get_message(msg):
elif(msg['cmd'] == 'wiexpandfolder'): elif(msg['cmd'] == 'wiexpandfolder'):
assert 0 <= int(msg['data']) < len(vars.worldinfo) assert 0 <= int(msg['data']) < len(vars.worldinfo)
emit('from_server', {'cmd': 'wiexpandfolder', 'data': msg['data']}, broadcast=True) emit('from_server', {'cmd': 'wiexpandfolder', 'data': msg['data']}, broadcast=True)
elif(msg['cmd'] == 'wifoldercollapsecontent'):
vars.wifolders_d[msg['data']]['collapsed'] = True
emit('from_server', {'cmd': 'wifoldercollapsecontent', 'data': msg['data']}, broadcast=True)
elif(msg['cmd'] == 'wifolderexpandcontent'):
vars.wifolders_d[msg['data']]['collapsed'] = False
emit('from_server', {'cmd': 'wifolderexpandcontent', 'data': msg['data']}, broadcast=True)
elif(msg['cmd'] == 'wiupdate'): elif(msg['cmd'] == 'wiupdate'):
num = int(msg['num']) num = int(msg['num'])
fields = ("key", "keysecondary", "content", "comment") fields = ("key", "keysecondary", "content", "comment")
@ -1753,7 +1778,8 @@ def get_message(msg):
elif(msg['cmd'] == 'splistrequest'): elif(msg['cmd'] == 'splistrequest'):
getsplist() getsplist()
elif(msg['cmd'] == 'uslistrequest'): elif(msg['cmd'] == 'uslistrequest'):
getuslist() unloaded, loaded = getuslist()
emit('from_server', {'cmd': 'buildus', 'data': {"unloaded": unloaded, "loaded": loaded}})
elif(msg['cmd'] == 'usloaded'): elif(msg['cmd'] == 'usloaded'):
vars.userscripts = [] vars.userscripts = []
for userscript in msg['data']: for userscript in msg['data']:
@ -1765,8 +1791,8 @@ def get_message(msg):
settingschanged() settingschanged()
elif(msg['cmd'] == 'usload'): elif(msg['cmd'] == 'usload'):
load_lua_scripts() load_lua_scripts()
elif(msg['cmd'] == 'usload'): unloaded, loaded = getuslist()
getuslist() sendUSStatItems()
elif(msg['cmd'] == 'loadselect'): elif(msg['cmd'] == 'loadselect'):
vars.loadselect = msg["data"] vars.loadselect = msg["data"]
elif(msg['cmd'] == 'spselect'): elif(msg['cmd'] == 'spselect'):
@ -1775,6 +1801,7 @@ def get_message(msg):
loadRequest(fileops.storypath(vars.loadselect)) loadRequest(fileops.storypath(vars.loadselect))
elif(msg['cmd'] == 'sprequest'): elif(msg['cmd'] == 'sprequest'):
spRequest(vars.spselect) spRequest(vars.spselect)
emit('from_server', {'cmd': 'spstatitems', 'data': {vars.spfilename: vars.spmeta} if vars.allowsp and len(vars.spfilename) else {}}, broadcast=True)
elif(msg['cmd'] == 'deletestory'): elif(msg['cmd'] == 'deletestory'):
deletesave(msg['data']) deletesave(msg['data'])
elif(msg['cmd'] == 'renamestory'): elif(msg['cmd'] == 'renamestory'):
@ -1813,6 +1840,16 @@ def get_message(msg):
elif(not vars.remote and msg['cmd'] == 'importwi'): elif(not vars.remote and msg['cmd'] == 'importwi'):
wiimportrequest() wiimportrequest()
#==================================================================#
# Send userscripts list to client
#==================================================================#
def sendUSStatItems():
_, loaded = getuslist()
loaded = loaded if vars.lua_running else []
last_userscripts = [e["filename"] for e in loaded]
emit('from_server', {'cmd': 'usstatitems', 'data': loaded, 'flash': last_userscripts != vars.last_userscripts}, broadcast=True)
vars.last_userscripts = last_userscripts
#==================================================================# #==================================================================#
# Send start message and tell Javascript to set UI state # Send start message and tell Javascript to set UI state
#==================================================================# #==================================================================#
@ -1870,6 +1907,7 @@ def savesettings():
js["userscripts"] = vars.userscripts js["userscripts"] = vars.userscripts
js["corescript"] = vars.corescript js["corescript"] = vars.corescript
js["softprompt"] = vars.spfilename
# Write it # Write it
if not os.path.exists('settings'): if not os.path.exists('settings'):
@ -1939,6 +1977,11 @@ def loadsettings():
else: else:
vars.corescript = "default.lua" vars.corescript = "default.lua"
if(vars.allowsp and "softprompt" in js and type(js["softprompt"]) is str and all(q not in js["softprompt"] for q in ("..", ":")) and all(js["softprompt"][0] not in q for q in ("/", "\\"))):
spRequest(js["softprompt"])
else:
vars.spfilename = ""
file.close() file.close()
#==================================================================# #==================================================================#
@ -2445,7 +2488,9 @@ def generate(txt, minimum, maximum, found_entries=None):
except Exception as e: except Exception as e:
if(issubclass(type(e), lupa.LuaError)): if(issubclass(type(e), lupa.LuaError)):
vars.lua_koboldbridge.obliterate_multiverse() vars.lua_koboldbridge.obliterate_multiverse()
vars.lua_running = False
emit('from_server', {'cmd': 'errmsg', 'data': 'Lua script error, please check console.'}, broadcast=True) emit('from_server', {'cmd': 'errmsg', 'data': 'Lua script error, please check console.'}, broadcast=True)
sendUSStatItems()
print("{0}{1}{2}".format(colors.RED, "***LUA ERROR***: ", colors.END), end="", file=sys.stderr) print("{0}{1}{2}".format(colors.RED, "***LUA ERROR***: ", colors.END), end="", file=sys.stderr)
print("{0}{1}{2}".format(colors.RED, str(e).replace("\033", ""), colors.END), file=sys.stderr) print("{0}{1}{2}".format(colors.RED, str(e).replace("\033", ""), colors.END), file=sys.stderr)
print("{0}{1}{2}".format(colors.YELLOW, "Lua engine stopped; please open 'Userscripts' and press Load to reinitialize scripts.", colors.END), file=sys.stderr) print("{0}{1}{2}".format(colors.YELLOW, "Lua engine stopped; please open 'Userscripts' and press Load to reinitialize scripts.", colors.END), file=sys.stderr)
@ -2672,7 +2717,9 @@ def tpumtjgenerate(txt, minimum, maximum, found_entries=None):
except Exception as e: except Exception as e:
if(issubclass(type(e), lupa.LuaError)): if(issubclass(type(e), lupa.LuaError)):
vars.lua_koboldbridge.obliterate_multiverse() vars.lua_koboldbridge.obliterate_multiverse()
vars.lua_running = False
emit('from_server', {'cmd': 'errmsg', 'data': 'Lua script error, please check console.'}, broadcast=True) emit('from_server', {'cmd': 'errmsg', 'data': 'Lua script error, please check console.'}, broadcast=True)
sendUSStatItems()
print("{0}{1}{2}".format(colors.RED, "***LUA ERROR***: ", colors.END), end="", file=sys.stderr) print("{0}{1}{2}".format(colors.RED, "***LUA ERROR***: ", colors.END), end="", file=sys.stderr)
print("{0}{1}{2}".format(colors.RED, str(e).replace("\033", ""), colors.END), file=sys.stderr) print("{0}{1}{2}".format(colors.RED, str(e).replace("\033", ""), colors.END), file=sys.stderr)
print("{0}{1}{2}".format(colors.YELLOW, "Lua engine stopped; please open 'Userscripts' and press Load to reinitialize scripts.", colors.END), file=sys.stderr) print("{0}{1}{2}".format(colors.YELLOW, "Lua engine stopped; please open 'Userscripts' and press Load to reinitialize scripts.", colors.END), file=sys.stderr)
@ -3550,7 +3597,7 @@ def getsplist():
emit('from_server', {'cmd': 'buildsp', 'data': fileops.getspfiles(vars.modeldim)}) emit('from_server', {'cmd': 'buildsp', 'data': fileops.getspfiles(vars.modeldim)})
#==================================================================# #==================================================================#
# Show list of userscripts # Get list of userscripts
#==================================================================# #==================================================================#
def getuslist(): def getuslist():
files = {i: v for i, v in enumerate(fileops.getusfiles())} files = {i: v for i, v in enumerate(fileops.getusfiles())}
@ -3558,11 +3605,14 @@ def getuslist():
unloaded = [] unloaded = []
userscripts = set(vars.userscripts) userscripts = set(vars.userscripts)
for i in range(len(files)): for i in range(len(files)):
if files[i]["filename"] in userscripts: if files[i]["filename"] not in userscripts:
loaded.append(files[i])
else:
unloaded.append(files[i]) unloaded.append(files[i])
emit('from_server', {'cmd': 'buildus', 'data': {"unloaded": unloaded, "loaded": loaded}}) files = {files[k]["filename"]: files[k] for k in files}
userscripts = set(files.keys())
for filename in vars.userscripts:
if filename in userscripts:
loaded.append(files[filename])
return unloaded, loaded
#==================================================================# #==================================================================#
# Load a saved story via file browser # Load a saved story via file browser
@ -3684,6 +3734,9 @@ def loadRequest(loadpath, filename=None):
# Load a soft prompt from a file # Load a soft prompt from a file
#==================================================================# #==================================================================#
def spRequest(filename): def spRequest(filename):
vars.spfilename = ""
settingschanged()
if(len(filename) == 0): if(len(filename) == 0):
vars.sp = None vars.sp = None
vars.sp_length = 0 vars.sp_length = 0
@ -3695,6 +3748,8 @@ def spRequest(filename):
z, version, shape, fortran_order, dtype = fileops.checksp(filename, vars.modeldim) z, version, shape, fortran_order, dtype = fileops.checksp(filename, vars.modeldim)
assert isinstance(z, zipfile.ZipFile) assert isinstance(z, zipfile.ZipFile)
with z.open('meta.json') as f:
vars.spmeta = json.load(f)
z.close() z.close()
with np.load(fileops.sppath(filename), allow_pickle=False) as f: with np.load(fileops.sppath(filename), allow_pickle=False) as f:
@ -3725,6 +3780,9 @@ def spRequest(filename):
else: else:
vars.sp = torch.from_numpy(tensor) vars.sp = torch.from_numpy(tensor)
vars.spfilename = filename
settingschanged()
#==================================================================# #==================================================================#
# Import an AIDungon game exported with Mimi's tool # Import an AIDungon game exported with Mimi's tool
#==================================================================# #==================================================================#

View File

@ -3,7 +3,7 @@
---@param _python? table<string, any> ---@param _python? table<string, any>
---@param _bridged? table<string, any> ---@param _bridged? table<string, any>
---@return KoboldLib, KoboldCoreLib|nil ---@return KoboldLib, KoboldCoreLib?
return function(_python, _bridged) return function(_python, _bridged)
--========================================================================== --==========================================================================
@ -14,7 +14,7 @@ return function(_python, _bridged)
---@generic K, V ---@generic K, V
---@param t table<K, V> ---@param t table<K, V>
---@param k? K ---@param k? K
---@return K|nil, V|nil ---@return K?, V?
function next(t, k) function next(t, k)
local meta = getmetatable(t) local meta = getmetatable(t)
return ((meta ~= nil and type(rawget(t, "_name")) == "string" and string.match(rawget(t, "_name"), "^Kobold") and type(meta._kobold_next) == "function") and meta._kobold_next or old_next)(t, k) return ((meta ~= nil and type(rawget(t, "_name")) == "string" and string.match(rawget(t, "_name"), "^Kobold") and type(meta._kobold_next) == "function") and meta._kobold_next or old_next)(t, k)
@ -40,23 +40,30 @@ return function(_python, _bridged)
return original return original
end end
---@param path string ---@param paths string|table<integer, string>
---@return nil ---@return nil
function set_require_path(path) local function set_require_path(paths)
if type(paths) == "string" then
paths = {paths}
end
local config = {} local config = {}
local i = 1 local i = 1
for substring in string.gmatch(package.config, "[^\n]+") do for substring in string.gmatch(package.config, "[^\n]+") do
config[i] = substring config[i] = substring
i = i + 1 i = i + 1
end end
package.path = path .. config[1] .. config[3] .. ".lua" .. config[2] .. path .. config[1] .. config[3] .. config[1] .. "init.lua" local _paths = {}
for i, path in ipairs(paths) do
_paths[i] = path .. config[1] .. config[3] .. ".lua" .. config[2] .. path .. config[1] .. config[3] .. config[1] .. "init.lua"
end
package.path = table.concat(_paths, config[2])
package.cpath = "" package.cpath = ""
end end
---@param path string ---@param path string
---@param filename string ---@param filename string
---@return string ---@return string
function join_folder_and_filename(path, filename) local function join_folder_and_filename(path, filename)
return path .. string.match(package.config, "[^\n]+") .. filename return path .. string.match(package.config, "[^\n]+") .. filename
end end
@ -67,10 +74,10 @@ return function(_python, _bridged)
local bridged = {} local bridged = {}
for k in _python.iter(_bridged) do for k in _python.iter(_bridged) do
v = _bridged[k] local v = _bridged[k]
bridged[k] = type(v) == "userdata" and _python.as_attrgetter(v) or v bridged[k] = type(v) == "userdata" and _python.as_attrgetter(v) or v
end end
set_require_path(bridged.lib_path) set_require_path(bridged.lib_paths)
--========================================================================== --==========================================================================
@ -120,12 +127,12 @@ return function(_python, _bridged)
_needs_unwrap = true _needs_unwrap = true
wrapped = true wrapped = true
end end
local r = {wrapped_func(...)} local r = table.pack(wrapped_func(...))
if _needs_unwrap then if _needs_unwrap then
metatables:restore() metatables:restore()
wrapped = false wrapped = false
end end
return table.unpack(r) return table.unpack(r, 1, r.n)
end) end)
else else
return rawset(t, k, wrapped_func) return rawset(t, k, wrapped_func)
@ -153,6 +160,8 @@ return function(_python, _bridged)
---@field generated_cols integer ---@field generated_cols integer
---@field outputs table<integer, string> ---@field outputs table<integer, string>
---@field num_outputs integer ---@field num_outputs integer
---@field feedback string
---@field is_config_file_open boolean
local kobold = setmetatable({}, metawrapper) local kobold = setmetatable({}, metawrapper)
local KoboldLib_mt = setmetatable({}, metawrapper) local KoboldLib_mt = setmetatable({}, metawrapper)
local KoboldLib_getters = setmetatable({}, metawrapper) local KoboldLib_getters = setmetatable({}, metawrapper)
@ -214,7 +223,12 @@ return function(_python, _bridged)
koboldbridge.generated = {} koboldbridge.generated = {}
koboldbridge.generated_cols = 0 koboldbridge.generated_cols = 0
koboldbridge.outputs = {} koboldbridge.outputs = {}
koboldbridge.feedback = nil ---@type string|nil koboldbridge.feedback = nil ---@type string?
function koboldbridge:clear_userscript_metadata()
self.logging_name = nil
self.filename = nil
end
---@return nil ---@return nil
local function maybe_require_regeneration() local function maybe_require_regeneration()
@ -224,6 +238,84 @@ return function(_python, _bridged)
end end
--==========================================================================
-- Userscript API: Configuration
--==========================================================================
local config_files = {} ---@type table<string, file*>
local config_file_filename_map = {} ---@type table<file*, string>
---@return file*?
local function open_and_handle_errors(...)
local file, err_msg = io.open(...)
if err_msg ~= nil then
koboldbridge.obliterate_multiverse()
error(err_msg)
return
end
return file
end
---@param file? file*
local function new_close_pre(file)
if file == nil then
file = io.output()
end
local filename = config_file_filename_map[file]
if filename ~= nil then
config_file_filename_map[file] = nil
config_files[filename] = nil
end
end
---@param f fun(file?: file*)
local function _new_close(f)
---@param file? file*
return function(file)
new_close_pre(file)
return f(file)
end
end
debug.getmetatable(io.stdout).__index.close = _new_close(io.stdout.close)
---@param filename string
---@return boolean
local function is_config_file_open(filename)
return config_files[filename] ~= nil
end
---@param filename string
---@param clear? boolean
---@return file*
local function get_config_file(filename, clear)
if not is_config_file_open(filename) then
local config_filepath = join_folder_and_filename(bridged.config_path, filename .. ".conf")
open_and_handle_errors(config_filepath, "a"):close()
config_files[filename] = open_and_handle_errors(config_filepath, clear and "w+b" or "r+b")
config_file_filename_map[config_files[filename]] = filename
end
return config_files[filename]
end
---@param clear? boolean
---@return file*
function kobold.get_config_file(clear)
return get_config_file(koboldbridge.filename, clear)
end
---@param t KoboldLib
---@return boolean
function KoboldLib_getters.is_config_file_open(t)
return is_config_file_open(koboldbridge.filename)
end
---@param t KoboldLib
---@param v boolean
function KoboldLib_setters.is_config_file_open(t, v)
error("`KoboldLib.is_config_file_open` is a read-only attribute")
end
--========================================================================== --==========================================================================
-- Userscript API: World Info -- Userscript API: World Info
--========================================================================== --==========================================================================
@ -373,7 +465,7 @@ return function(_python, _bridged)
local KoboldWorldInfoFolder_mt = setmetatable({}, metawrapper) local KoboldWorldInfoFolder_mt = setmetatable({}, metawrapper)
---@param u integer ---@param u integer
---@return KoboldWorldInfoEntry|nil ---@return KoboldWorldInfoEntry?
function KoboldWorldInfoFolder:finduid(u) function KoboldWorldInfoFolder:finduid(u)
if not check_validity(self) or type(u) ~= "number" then if not check_validity(self) or type(u) ~= "number" then
return return
@ -440,7 +532,7 @@ return function(_python, _bridged)
KoboldWorldInfoFolder_mt.__pairs = KoboldWorldInfoEntry_mt.__pairs KoboldWorldInfoFolder_mt.__pairs = KoboldWorldInfoEntry_mt.__pairs
---@param t KoboldWorldInfoFolder|KoboldWorldInfo ---@param t KoboldWorldInfoFolder|KoboldWorldInfo
---@return KoboldWorldInfoEntry|nil ---@return KoboldWorldInfoEntry?
function KoboldWorldInfoFolder_mt.__index(t, k) function KoboldWorldInfoFolder_mt.__index(t, k)
if not check_validity(t) then if not check_validity(t) then
return return
@ -495,7 +587,7 @@ return function(_python, _bridged)
local KoboldWorldInfoFolderSelector_mt = setmetatable({}, metawrapper) local KoboldWorldInfoFolderSelector_mt = setmetatable({}, metawrapper)
---@param u integer ---@param u integer
---@return KoboldWorldInfoFolder|nil ---@return KoboldWorldInfoFolder?
function KoboldWorldInfoFolderSelector:finduid(u) function KoboldWorldInfoFolderSelector:finduid(u)
if not check_validity(self) or type(u) ~= "number" then if not check_validity(self) or type(u) ~= "number" then
return return
@ -528,7 +620,7 @@ return function(_python, _bridged)
KoboldWorldInfoFolderSelector_mt.__pairs = KoboldWorldInfoEntry_mt.__pairs KoboldWorldInfoFolderSelector_mt.__pairs = KoboldWorldInfoEntry_mt.__pairs
---@param t KoboldWorldInfoFolderSelector ---@param t KoboldWorldInfoFolderSelector
---@return KoboldWorldInfoFolder|nil ---@return KoboldWorldInfoFolder?
function KoboldWorldInfoFolderSelector_mt.__index(t, k) function KoboldWorldInfoFolderSelector_mt.__index(t, k)
if not check_validity(t) or type(k) ~= "number" or math.tointeger(k) == nil or k < 1 or k > #t then if not check_validity(t) or type(k) ~= "number" or math.tointeger(k) == nil or k < 1 or k > #t then
return return
@ -681,7 +773,7 @@ return function(_python, _bridged)
local actions = koboldbridge.userstate == "genmod" and bridged.vars._actions or bridged.vars.actions local actions = koboldbridge.userstate == "genmod" and bridged.vars._actions or bridged.vars.actions
local nxt, iterator = _python.iter(actions) local nxt, iterator = _python.iter(actions)
local run_once = false local run_once = false
local f = function() local function f()
if not bridged.vars.gamestarted then if not bridged.vars.gamestarted then
return return
end end
@ -709,7 +801,7 @@ return function(_python, _bridged)
local actions = koboldbridge.userstate == "genmod" and bridged.vars._actions or bridged.vars.actions local actions = koboldbridge.userstate == "genmod" and bridged.vars._actions or bridged.vars.actions
local nxt, iterator = _python.iter(_python.builtins.reversed(actions)) local nxt, iterator = _python.iter(_python.builtins.reversed(actions))
local last_run = false local last_run = false
local f = function() local function f()
if not bridged.vars.gamestarted or last_run then if not bridged.vars.gamestarted or last_run then
return return
end end
@ -946,7 +1038,7 @@ return function(_python, _bridged)
---@param t KoboldLib ---@param t KoboldLib
---@return string ---@return string
function KoboldLib_getters.model(t) function KoboldLib_getters.model(t)
return bridged.vars.model return bridged.vars.model_orig
end end
---@param t KoboldLib ---@param t KoboldLib
@ -1257,21 +1349,22 @@ return function(_python, _bridged)
-- Core script API -- Core script API
--========================================================================== --==========================================================================
koboldbridge.userscripts = {} ---@type table<integer, string> koboldbridge.userscripts = {} ---@type table<integer, KoboldUserScriptModule>
koboldbridge.userscriptmodule_filename_map = {} ---@type table<KoboldUserScriptModule, string>
koboldbridge.num_userscripts = 0 koboldbridge.num_userscripts = 0
koboldbridge.inmod = nil ---@type function|nil koboldbridge.inmod = nil ---@type function?
koboldbridge.genmod = nil ---@type function|nil koboldbridge.genmod = nil ---@type function?
koboldbridge.outmod = nil ---@type function|nil koboldbridge.outmod = nil ---@type function?
---@class KoboldUserScript ---@class KoboldUserScript
---@field inmod function|nil ---@field inmod? function
---@field genmod function|nil ---@field genmod? function
---@field outmod function|nil ---@field outmod? function
---@class KoboldCoreScript ---@class KoboldCoreScript
---@field inmod function|nil ---@field inmod? function
---@field genmod function|nil ---@field genmod? function
---@field outmod function|nil ---@field outmod? function
---------------------------------------------------------------------------- ----------------------------------------------------------------------------
@ -1280,9 +1373,10 @@ return function(_python, _bridged)
---@field filename string ---@field filename string
---@field modulename string ---@field modulename string
---@field description string ---@field description string
---@field inmod function|nil ---@field is_config_file_open boolean
---@field genmod function|nil ---@field inmod? function
---@field outmod function|nil ---@field genmod? function
---@field outmod? function
local KoboldUserScriptModule = setmetatable({ local KoboldUserScriptModule = setmetatable({
_name = "KoboldUserScriptModule", _name = "KoboldUserScriptModule",
}, metawrapper) }, metawrapper)
@ -1297,6 +1391,12 @@ return function(_python, _bridged)
outmod = false, outmod = false,
} }
---@param clear? boolean
---@return file*
function KoboldUserScriptModule:get_config_file(clear)
return get_config_file(koboldbridge.userscriptmodule_filename_map[self], clear)
end
---@generic K ---@generic K
---@param t KoboldUserScriptModule ---@param t KoboldUserScriptModule
---@param k K ---@param k K
@ -1316,6 +1416,8 @@ return function(_python, _bridged)
function KoboldUserScriptModule_mt.__index(t, k) function KoboldUserScriptModule_mt.__index(t, k)
if type(k) == "string" and KoboldUserScriptModule_fields[k] ~= nil then if type(k) == "string" and KoboldUserScriptModule_fields[k] ~= nil then
return rawget(t, "_" .. k) return rawget(t, "_" .. k)
elseif k == "is_config_file_open" then
return is_config_file_open(koboldbridge.userscriptmodule_filename_map[t])
end end
return rawget(t, k) return rawget(t, k)
end end
@ -1346,7 +1448,7 @@ return function(_python, _bridged)
---@param t KoboldUserScriptList ---@param t KoboldUserScriptList
---@param k integer ---@param k integer
---@return KoboldUserScriptModule|nil ---@return KoboldUserScriptModule?
function KoboldUserScriptList_mt.__index(t, k) function KoboldUserScriptList_mt.__index(t, k)
if type(k) == "number" and math.tointeger(k) ~= nil then if type(k) == "number" and math.tointeger(k) ~= nil then
return koboldbridge.userscripts[k] return koboldbridge.userscripts[k]
@ -1405,6 +1507,7 @@ return function(_python, _bridged)
local envs = {} local envs = {}
koboldbridge.logging_name = nil koboldbridge.logging_name = nil
koboldbridge.filename = nil
local old_load = load local old_load = load
local function _safe_load(_g) local function _safe_load(_g)
@ -1427,11 +1530,11 @@ return function(_python, _bridged)
local old_package_searchers = package.searchers local old_package_searchers = package.searchers
---@param modname string ---@param modname string
---@param env table<string, any> ---@param env table<string, any>
---@param search_path? string ---@param search_paths? string|table<integer, string>
---@return any, string|nil ---@return any, string?
local function requirex(modname, env, search_path) local function requirex(modname, env, search_paths)
if search_path == nil then if search_paths == nil then
search_path = bridged.lib_path search_paths = bridged.lib_paths
end end
if modname == "bridge" then if modname == "bridge" then
return function() return env.kobold, env.koboldcore end return function() return env.kobold, env.koboldcore end
@ -1449,7 +1552,7 @@ return function(_python, _bridged)
local loader, path local loader, path
local errors = {} local errors = {}
local n_errors = 0 local n_errors = 0
set_require_path(search_path) set_require_path(search_paths)
for k, v in ipairs(old_package_searchers) do for k, v in ipairs(old_package_searchers) do
loader, path = v(modname) loader, path = v(modname)
if allowsearch and type(loader) == "function" then if allowsearch and type(loader) == "function" then
@ -1459,7 +1562,7 @@ return function(_python, _bridged)
errors[n_errors] = "\n\t" .. loader errors[n_errors] = "\n\t" .. loader
end end
end end
set_require_path(bridged.lib_path) set_require_path(bridged.lib_paths)
if not allowsearch or type(loader) ~= "function" then if not allowsearch or type(loader) ~= "function" then
error("module '" .. modname .. "' not found:" .. table.concat(errors)) error("module '" .. modname .. "' not found:" .. table.concat(errors))
return return
@ -1470,7 +1573,7 @@ return function(_python, _bridged)
end end
local function _safe_require(_g) local function _safe_require(_g)
---@param modname string ---@param modname string
---@return any, string|nil ---@return any, string?
return function(modname) return function(modname)
return requirex(modname, _g) return requirex(modname, _g)
end end
@ -1610,6 +1713,8 @@ return function(_python, _bridged)
output = io.output, output = io.output,
read = io.read, read = io.read,
write = io.write, write = io.write,
close = _new_close(io.close),
lines = io.lines,
flush = io.flush, flush = io.flush,
type = io.type, type = io.type,
}, },
@ -1654,7 +1759,11 @@ return function(_python, _bridged)
end end
function koboldbridge.obliterate_multiverse() function koboldbridge.obliterate_multiverse()
for k, v in pairs(config_files) do
pcall(v.close, v)
end
envs = {} envs = {}
koboldbridge.userscripts = {}
koboldbridge.num_userscripts = 0 koboldbridge.num_userscripts = 0
koboldbridge.inmod = nil koboldbridge.inmod = nil
koboldbridge.genmod = nil koboldbridge.genmod = nil
@ -1668,23 +1777,49 @@ return function(_python, _bridged)
---@return nil ---@return nil
function koboldbridge.load_userscripts(filenames, modulenames, descriptions) function koboldbridge.load_userscripts(filenames, modulenames, descriptions)
set_require_path(bridged.userscript_path) config_files = {}
config_file_filename_map = {}
koboldbridge.userscripts = {} koboldbridge.userscripts = {}
koboldbridge.userscriptmodule_filename_map = {}
koboldbridge.num_userscripts = 0 koboldbridge.num_userscripts = 0
for i, filename in _python.enumerate(filenames) do for i, filename in _python.enumerate(filenames) do
bridged.load_callback(filename, modulenames[i]) bridged.load_callback(filename, modulenames[i])
koboldbridge.logging_name = modulenames[i] koboldbridge.logging_name = modulenames[i]
koboldbridge.filename = filename
---@type KoboldUserScript ---@type KoboldUserScript
local _userscript = old_loadfile(join_folder_and_filename(bridged.userscript_path, filename), "t", koboldbridge.get_universe(filename))() local _userscript = old_loadfile(join_folder_and_filename(bridged.userscript_path, filename), "t", koboldbridge.get_universe(filename))()
koboldbridge.logging_name = nil koboldbridge.logging_name = nil
koboldbridge.filename = nil
local userscript = deepcopy(KoboldUserScriptModule) local userscript = deepcopy(KoboldUserScriptModule)
rawset(userscript, "_inmod", function() koboldbridge.logging_name = modulenames[i]; if _userscript.inmod ~= nil then _userscript.inmod() end end) rawset(userscript, "_inmod", function()
rawset(userscript, "_genmod", function() koboldbridge.logging_name = modulenames[i]; if _userscript.genmod ~= nil then _userscript.genmod() end end) koboldbridge.logging_name = modulenames[i]
rawset(userscript, "_outmod", function() koboldbridge.logging_name = modulenames[i]; if _userscript.outmod ~= nil then _userscript.outmod() end end) koboldbridge.filename = filename
if _userscript.inmod ~= nil then
_userscript.inmod()
end
koboldbridge:clear_userscript_metadata()
end)
rawset(userscript, "_genmod", function()
koboldbridge.logging_name = modulenames[i]
koboldbridge.filename = filename
if _userscript.genmod ~= nil then
_userscript.genmod()
end
koboldbridge:clear_userscript_metadata()
end)
rawset(userscript, "_outmod", function()
koboldbridge.logging_name = modulenames[i]
koboldbridge.filename = filename
if _userscript.outmod ~= nil then
_userscript.outmod()
end
koboldbridge:clear_userscript_metadata()
end)
rawset(userscript, "_filename", filename) rawset(userscript, "_filename", filename)
rawset(userscript, "_modulename", modulenames[i]) rawset(userscript, "_modulename", modulenames[i])
rawset(userscript, "_description", descriptions[i]) rawset(userscript, "_description", descriptions[i])
koboldbridge.userscripts[i+1] = userscript koboldbridge.userscripts[i+1] = userscript
koboldbridge.userscriptmodule_filename_map[userscript] = filename
koboldbridge.num_userscripts = i + 1 koboldbridge.num_userscripts = i + 1
end end
end end
@ -1700,6 +1835,7 @@ return function(_python, _bridged)
function koboldbridge.execute_inmod() function koboldbridge.execute_inmod()
local r local r
koboldbridge:clear_userscript_metadata()
koboldbridge.restart_sequence = nil koboldbridge.restart_sequence = nil
koboldbridge.userstate = "inmod" koboldbridge.userstate = "inmod"
koboldbridge.regeneration_required = false koboldbridge.regeneration_required = false
@ -1722,6 +1858,7 @@ return function(_python, _bridged)
---@return any, boolean ---@return any, boolean
function koboldbridge.execute_genmod() function koboldbridge.execute_genmod()
local r local r
koboldbridge:clear_userscript_metadata()
koboldbridge.generating = true koboldbridge.generating = true
koboldbridge.userstate = "genmod" koboldbridge.userstate = "genmod"
if koboldbridge.genmod ~= nil then if koboldbridge.genmod ~= nil then
@ -1758,6 +1895,7 @@ return function(_python, _bridged)
function koboldbridge.execute_outmod() function koboldbridge.execute_outmod()
local r local r
koboldbridge:clear_userscript_metadata()
koboldbridge.generating = false koboldbridge.generating = false
koboldbridge.userstate = "outmod" koboldbridge.userstate = "outmod"
koboldbridge.num_outputs = kobold.settings.numseqs koboldbridge.num_outputs = kobold.settings.numseqs

1
extern/lualibs/lpeg.lua vendored Normal file
View File

@ -0,0 +1 @@
return require("lulpeg")

138
extern/lualibs/lulpeg.lua vendored Normal file

File diff suppressed because one or more lines are too long

6
extern/lualibs/pegex.lua vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -419,7 +419,7 @@ function addWiLine(ob) {
function addWiFolder(uid, ob) { function addWiFolder(uid, ob) {
if(uid !== null) { if(uid !== null) {
var uninitialized = $("#wilistfoldercontainer"+null); var uninitialized = $("#wilistfoldercontainer"+null);
var html = "<div class=\"wisortable-container\" id=\"wilistfoldercontainer"+uid+"\" folder-uid=\""+uid+"\">\ var html = "<div class=\"wisortable-container "+(ob.collapsed ? "" : "folder-expanded")+"\" id=\"wilistfoldercontainer"+uid+"\" folder-uid=\""+uid+"\">\
<div class=\"wilistfolder\" id=\"wilistfolder"+uid+"\">\ <div class=\"wilistfolder\" id=\"wilistfolder"+uid+"\">\
<div class=\"wiremove\">\ <div class=\"wiremove\">\
<button type=\"button\" class=\"btn btn-primary heightfull\" id=\"btn_wifolder"+uid+"\">X</button>\ <button type=\"button\" class=\"btn btn-primary heightfull\" id=\"btn_wifolder"+uid+"\">X</button>\
@ -428,7 +428,7 @@ function addWiFolder(uid, ob) {
</div>\ </div>\
<div class=\"wifoldericon\">\ <div class=\"wifoldericon\">\
<div class=\"wicentered\">\ <div class=\"wicentered\">\
<span class=\"oi oi-folder\" aria-hidden=\"true\"></span>\ <span class=\"oi oi-folder folder-expand "+(ob.collapsed ? "" : "folder-expanded")+"\" id=\"btn_wifolderexpand"+uid+"\" aria-hidden=\"true\"></span>\
</div>\ </div>\
</div>\ </div>\
<div class=\"wifoldername\">\ <div class=\"wifoldername\">\
@ -461,7 +461,7 @@ function addWiFolder(uid, ob) {
var onfocusout = function () { var onfocusout = function () {
socket.send({'cmd': 'wifolderupdate', 'uid': uid, 'data': { socket.send({'cmd': 'wifolderupdate', 'uid': uid, 'data': {
name: $("#wifoldername"+uid).val(), name: $("#wifoldername"+uid).val(),
collapsed: false, collapsed: !$("#btn_wifolderexpand"+uid).hasClass("folder-expanded"),
}}); }});
}; };
$("#wifoldergutter"+uid).on("click", function () { $("#wifoldergutter"+uid).on("click", function () {
@ -488,9 +488,22 @@ function addWiFolder(uid, ob) {
$(this).parent().parent().find(".wisortable-body").removeClass("hidden"); $(this).parent().parent().find(".wisortable-body").removeClass("hidden");
$(this).parent().css("max-height", "").find(".wifoldername").find(".form-control").css("max-height", ""); $(this).parent().css("max-height", "").find(".wifoldername").find(".form-control").css("max-height", "");
}); });
adjustWiFolderNameHeight($("#wifoldername"+uid)[0]); $("#btn_wifolderexpand"+uid).on("click", function () {
if($(this).hasClass("folder-expanded")) {
socket.send({'cmd': 'wifoldercollapsecontent', 'data': uid});
} else { } else {
wi_menu.append("<div class=\"wisortable-container\" id=\"wilistfoldercontainer"+uid+"\">\ socket.send({'cmd': 'wifolderexpandcontent', 'data': uid});
}
})
adjustWiFolderNameHeight($("#wifoldername"+uid)[0]);
if(ob.collapsed) {
setTimeout(function() {
var container = $("#wilistfoldercontainer"+uid);
hide([container.find(".wifoldergutter-container"), container.find(".wisortable-body")]);
}, 2);
}
} else {
wi_menu.append("<div class=\"wisortable-container folder-expanded\" id=\"wilistfoldercontainer"+uid+"\">\
<div class=\"wilistfolder\" id=\"wilistfolder"+uid+"\">\ <div class=\"wilistfolder\" id=\"wilistfolder"+uid+"\">\
<div class=\"wiremove\">\ <div class=\"wiremove\">\
<button type=\"button\" class=\"btn btn-primary heightfull\" id=\"btn_wifolder"+uid+"\">+</button>\ <button type=\"button\" class=\"btn btn-primary heightfull\" id=\"btn_wifolder"+uid+"\">+</button>\
@ -499,7 +512,7 @@ function addWiFolder(uid, ob) {
</div>\ </div>\
<div class=\"wifoldericon\">\ <div class=\"wifoldericon\">\
<div class=\"wicentered\">\ <div class=\"wicentered\">\
<span class=\"oi oi-folder\" aria-hidden=\"true\"></span>\ <span class=\"oi oi-folder folder-expand folder-expanded\" id=\"btn_wifolderexpand"+uid+"\" aria-hidden=\"true\"></span>\
</div>\ </div>\
</div>\ </div>\
<div class=\"wifoldername\">\ <div class=\"wifoldername\">\
@ -564,6 +577,18 @@ function hideWiFolderDeleteConfirm(num) {
hide([$("#btn_wifolderdel"+num), $("#btn_wifoldercan"+num)]); hide([$("#btn_wifolderdel"+num), $("#btn_wifoldercan"+num)]);
} }
function collapseWiFolderContent(uid) {
hide([$("#wifoldergutter"+uid), $(".wisortable-body[folder-uid="+uid+"]")]);
$("#btn_wifolderexpand"+uid).removeClass("folder-expanded");
$("#wilistfoldercontainer"+uid).removeClass("folder-expanded");
}
function expandWiFolderContent(uid) {
show([$("#wifoldergutter"+uid), $(".wisortable-body[folder-uid="+uid+"]")]);
$("#btn_wifolderexpand"+uid).addClass("folder-expanded");
$("#wilistfoldercontainer"+uid).addClass("folder-expanded");
}
function enableWiSelective(num) { function enableWiSelective(num) {
hide([$("#wikey"+num)]); hide([$("#wikey"+num)]);
$("#wikeyprimary"+num).val($("#wikey"+num).val()); $("#wikeyprimary"+num).val($("#wikey"+num).val());
@ -1028,6 +1053,59 @@ function hideRandomStoryPopup() {
rspopup.addClass("hidden"); rspopup.addClass("hidden");
} }
function statFlash(ref) {
ref.addClass("status-flash");
setTimeout(function () {
ref.addClass("colorfade");
ref.removeClass("status-flash");
setTimeout(function () {
ref.removeClass("colorfade");
}, 1000);
}, 50);
}
function updateUSStatItems(items, flash) {
var stat_us = $("#stat-us");
var stat_usactive = $("#stat-usactive");
if(flash || stat_usactive.find("li").length != items.length) {
statFlash(stat_us.closest(".statusicon").add("#usiconlabel"));
}
stat_usactive.html("");
if(items.length == 0) {
stat_us.html("No userscripts active");
$("#usiconlabel").html("");
stat_us.closest(".statusicon").removeClass("active");
return;
}
stat_us.html("Active userscripts:");
stat_us.closest(".statusicon").addClass("active");
var i;
for(i = 0; i < items.length; i++) {
stat_usactive.append($("<li filename=\""+items[i].filename+"\">"+items[i].modulename+" &lt;"+items[i].filename+"&gt;</li>"));
}
$("#usiconlabel").html(items.length);
}
function updateSPStatItems(items) {
var stat_sp = $("#stat-sp");
var stat_spactive = $("#stat-spactive");
var key = null;
var old_val = stat_spactive.html();
Object.keys(items).forEach(function(k) {key = k;});
if(key === null) {
stat_sp.html("No soft prompt active");
stat_sp.closest(".statusicon").removeClass("active");
stat_spactive.html("");
} else {
stat_sp.html("Active soft prompt:");
stat_sp.closest(".statusicon").addClass("active");
stat_spactive.html((items[key].name || key)+" &lt;"+key+"&gt;");
}
if(stat_spactive.html() !== old_val) {
statFlash(stat_sp.closest(".statusicon"));
}
}
function setStartState() { function setStartState() {
enableSendBtn(); enableSendBtn();
enableButtons([button_actmem, button_actwi]); enableButtons([button_actmem, button_actwi]);
@ -1398,14 +1476,59 @@ function syncAllModifiedChunks(including_selected_chunks=false) {
} }
function restorePrompt() { function restorePrompt() {
if(game_text[0].firstChild && game_text[0].firstChild.nodeType === 3) { if($("#n0").length && formatChunkInnerText($("#n0")[0]).length === 0) {
saved_prompt = formatChunkInnerText(game_text[0].firstChild); $("#n0").remove();
}
var shadow_text = $("<b>" + game_text.html() + "</b>");
var detected = false;
var ref = null;
try {
if(shadow_text.length && shadow_text[0].firstChild && (shadow_text[0].firstChild.nodeType === 3 || shadow_text[0].firstChild.tagName === "BR")) {
detected = true;
ref = shadow_text;
} else if(game_text.length && game_text[0].firstChild && game_text[0].firstChild.nodeType === 3 || game_text[0].firstChild.tagName === "BR") {
detected = true;
ref = game_text;
}
} catch (e) {
detected = false;
}
if(detected) {
unbindGametext(); unbindGametext();
game_text[0].innerText = ""; var text = [];
while(true) {
if(ref.length && ref[0].firstChild && ref[0].firstChild.nodeType === 3) {
text.push(ref[0].firstChild.textContent.replace(/\u00a0/g, " "));
} else if(ref.length && ref[0].firstChild && ref[0].firstChild.tagName === "BR") {
text.push("\n");
} else {
break;
}
ref[0].removeChild(ref[0].firstChild);
}
text = text.join("").trim();
if(text.length) {
saved_prompt = text;
}
game_text[0].innerHTML = "";
bindGametext(); bindGametext();
} }
if($("#n0").length) { game_text.children().each(function() {
$("#n0").remove(); if(this.tagName !== "CHUNK") {
this.parentNode.removeChild(this);
}
});
if(!detected) {
game_text.children().each(function() {
if(this.innerText.trim().length) {
saved_prompt = this.innerText.trim();
socket.send({'cmd': 'inlinedelete', 'data': this.getAttribute("n")});
this.parentNode.removeChild(this);
return false;
}
socket.send({'cmd': 'inlinedelete', 'data': this.getAttribute("n")});
this.parentNode.removeChild(this);
});
} }
var prompt_chunk = document.createElement("chunk"); var prompt_chunk = document.createElement("chunk");
prompt_chunk.setAttribute("n", "0"); prompt_chunk.setAttribute("n", "0");
@ -1880,6 +2003,10 @@ $(document).ready(function(){
} else if(msg.cmd == "allowtoggle") { } else if(msg.cmd == "allowtoggle") {
// Allow toggle change states to propagate // Allow toggle change states to propagate
allowtoggle = msg.data; allowtoggle = msg.data;
} else if(msg.cmd == "usstatitems") {
updateUSStatItems(msg.data, msg.flash);
} else if(msg.cmd == "spstatitems") {
updateSPStatItems(msg.data);
} else if(msg.cmd == "popupshow") { } else if(msg.cmd == "popupshow") {
// Show/Hide Popup // Show/Hide Popup
popupShow(msg.data); popupShow(msg.data);
@ -1922,6 +2049,10 @@ $(document).ready(function(){
expandWiLine(msg.data); expandWiLine(msg.data);
} else if(msg.cmd == "wiexpandfolder") { } else if(msg.cmd == "wiexpandfolder") {
expandWiFolderLine(msg.data); expandWiFolderLine(msg.data);
} else if(msg.cmd == "wifoldercollapsecontent") {
collapseWiFolderContent(msg.data);
} else if(msg.cmd == "wifolderexpandcontent") {
expandWiFolderContent(msg.data);
} else if(msg.cmd == "wiselon") { } else if(msg.cmd == "wiselon") {
enableWiSelective(msg.data); enableWiSelective(msg.data);
} else if(msg.cmd == "wiseloff") { } else if(msg.cmd == "wiseloff") {
@ -2031,6 +2162,8 @@ $(document).ready(function(){
connect_status.html("<b>Lost connection...</b>"); connect_status.html("<b>Lost connection...</b>");
connect_status.removeClass("color_green"); connect_status.removeClass("color_green");
connect_status.addClass("color_orange"); connect_status.addClass("color_orange");
updateUSStatItems([], false);
updateSPStatItems({});
}); });
// Register editing events // Register editing events
@ -2062,6 +2195,7 @@ $(document).ready(function(){
// Make the userscripts menu sortable // Make the userscripts menu sortable
var us_sortable_settings = { var us_sortable_settings = {
placeholder: "ussortable-placeholder",
delay: 2, delay: 2,
cursor: "move", cursor: "move",
tolerance: "pointer", tolerance: "pointer",
@ -2205,6 +2339,7 @@ $(document).ready(function(){
}); });
load_accept.on("click", function(ev) { load_accept.on("click", function(ev) {
hideMessage();
newly_loaded = true; newly_loaded = true;
socket.send({'cmd': 'loadrequest', 'data': ''}); socket.send({'cmd': 'loadrequest', 'data': ''});
hideLoadPopup(); hideLoadPopup();
@ -2215,6 +2350,7 @@ $(document).ready(function(){
}); });
sp_accept.on("click", function(ev) { sp_accept.on("click", function(ev) {
hideMessage();
socket.send({'cmd': 'sprequest', 'data': ''}); socket.send({'cmd': 'sprequest', 'data': ''});
hideSPPopup(); hideSPPopup();
}); });
@ -2225,6 +2361,7 @@ $(document).ready(function(){
}); });
us_accept.on("click", function(ev) { us_accept.on("click", function(ev) {
hideMessage();
socket.send({'cmd': 'usloaded', 'data': usloaded.find(".uslistitem").map(function() { return $(this).attr("name"); }).toArray()}); socket.send({'cmd': 'usloaded', 'data': usloaded.find(".uslistitem").map(function() { return $(this).attr("name"); }).toArray()});
socket.send({'cmd': 'usload', 'data': ''}); socket.send({'cmd': 'usload', 'data': ''});
hideUSPopup(); hideUSPopup();
@ -2235,6 +2372,7 @@ $(document).ready(function(){
}); });
ns_accept.on("click", function(ev) { ns_accept.on("click", function(ev) {
hideMessage();
socket.send({'cmd': 'newgame', 'data': ''}); socket.send({'cmd': 'newgame', 'data': ''});
hideNewStoryPopup(); hideNewStoryPopup();
}); });
@ -2267,6 +2405,7 @@ $(document).ready(function(){
}); });
rs_accept.on("click", function(ev) { rs_accept.on("click", function(ev) {
hideMessage();
socket.send({'cmd': 'rndgame', 'data': topic.val()}); socket.send({'cmd': 'rndgame', 'data': topic.val()});
hideRandomStoryPopup(); hideRandomStoryPopup();
}); });

View File

@ -29,11 +29,12 @@ chunk.editing, chunk.editing * {
#topmenu { #topmenu {
background-color: #337ab7; background-color: #337ab7;
padding: 10px; padding: 10px;
display: flex;
} }
#menuitems { #menuitems {
display: grid; display: flex;
grid-template-columns: 80% 20%; width: 100%;
} }
#navbar { #navbar {
@ -66,10 +67,7 @@ chunk.editing, chunk.editing * {
#connectstatusdiv { #connectstatusdiv {
display: flex; display: flex;
} text-align: right;
#connectstatusdiv span {
align-self: center;
} }
#gamescreen { #gamescreen {
@ -437,10 +435,10 @@ chunk.editing, chunk.editing * {
} }
.colorfade, .colorfade * { .colorfade, .colorfade * {
-moz-transition:color 1s ease-in; -moz-transition: color 1s ease-in, text-shadow 1s ease-in;
-o-transition:color 1s ease-in; -o-transition: color 1s ease-in, text-shadow 1s ease-in;
-webkit-transition:color 1s ease-in; -webkit-transition: color 1s ease-in, text-shadow 1s ease-in;
transition:color 1s ease-in; transition: color 1s ease-in, text-shadow 1s ease-in;
} }
.color_orange { .color_orange {
@ -484,6 +482,11 @@ chunk.editing, chunk.editing * {
color: #3bf723 !important; color: #3bf723 !important;
} }
.status-flash, .status-flash {
color: #fce94f !important;
text-shadow: 0 0 50px #fce94f, 0 0 50px #fce94f, 0 0 10px #fce94f, 0 0 10px #fce94f, 0 0 10px #fce94f, 0 0 10px #fce94f, 0 0 10px #fce94f;
}
.flex { .flex {
display: flex; display: flex;
align-items: center; align-items: center;
@ -603,7 +606,12 @@ chunk.editing, chunk.editing * {
background-color: #3bf723; background-color: #3bf723;
} }
.wisortable-container { .ussortable-placeholder {
height: 4px;
background-color: #3bf723;
}
.wisortable-container.folder-expanded {
padding-bottom: 50px; padding-bottom: 50px;
} }
@ -651,11 +659,30 @@ chunk.editing, chunk.editing * {
text-decoration: none; text-decoration: none;
} }
.helpicon:hover { .statusicon {
display: inline-block;
font-weight: bold;
text-align: center;
padding-left: 8px;
padding-right: 8px;
font-size: 30px !important;
font-weight: bold;
text-align: center;
font-size: 1.4ex;
line-height: 1.8ex;
text-decoration: none;
color: #68a2d4;
}
.statusicon.active {
color: #3bf723;
}
.helpicon:hover, .statusicon:hover {
cursor: pointer; cursor: pointer;
} }
.helpicon:hover .helptext { .helpicon:hover .helptext, .statusicon:hover .statustext, .statusicon.statustoggled .statustext {
display: inline-block; display: inline-block;
width: 250px; width: 250px;
background-color: #1f2931; background-color: #1f2931;
@ -667,13 +694,59 @@ chunk.editing, chunk.editing * {
padding: 15px; padding: 15px;
margin-left:10px; margin-left:10px;
border: 1px solid #337ab7; border: 1px solid #337ab7;
position: absolute;
z-index: 1;
} }
.helptext { .statusicon:hover .statustext.statustext-wide, .statusicon.statustoggled .statustext.statustext-wide {
width: 350px;
}
.statusiconlabel {
pointer-events: none;
color: #337ab7;
text-align: center;
font-weight: bold;
font-size: 13px;
}
#usiconlabel {
transform: translate(-3px, 10px);
-moz-transform: translate(-3px, 10px);
-webkit-transform: translate(-3px, 10px);
-ms-transform: translate(-3px, 10px);
-o-transform: translate(-3px, 10px);
}
.status-container {
z-index: 1;
text-shadow: none !important;
}
.helptext, .statustext {
display: none; display: none;
font-family: sans-serif;
position: absolute;
z-index: 1;
text-shadow: none !important;
}
.statustext {
transform: translate(-105%, 30px);
-moz-transform: translate(-105%, 30px);
-webkit-transform: translate(-105%, 30px);
-ms-transform: translate(-105%, 30px);
-o-transform: translate(-105%, 30px);
}
.statusheader {
padding-bottom: 10px;
}
#stat-usactive {
text-align: left;
height: 270px;
overflow-y: scroll;
position: relative;
padding-left: 20px;
} }
.justifyleft { .justifyleft {
@ -702,6 +775,23 @@ chunk.editing, chunk.editing * {
position: relative; position: relative;
} }
.folder-expand {
opacity: 20%;
}
.folder-expand:hover {
opacity: 44%;
cursor: pointer;
}
.folder-expand.folder-expanded {
opacity: 80% !important;
}
.folder-expand.folder-expanded:hover {
opacity: 100% !important;
}
.selective-key-icon { .selective-key-icon {
position: absolute !important; position: absolute !important;
top: 5px !important; top: 5px !important;
@ -893,7 +983,7 @@ chunk.editing, chunk.editing * {
} }
.navcontainer { .navcontainer {
width: 100%;
} }
.nowrap { .nowrap {

View File

@ -10,12 +10,12 @@
<script src="static/bootstrap.min.js"></script> <script src="static/bootstrap.min.js"></script>
<script src="static/bootstrap-toggle.min.js"></script> <script src="static/bootstrap-toggle.min.js"></script>
<script src="static/rangy-core.min.js"></script> <script src="static/rangy-core.min.js"></script>
<script src="static/application.js?ver=1.16.4i"></script> <script src="static/application.js?ver=1.16.4m"></script>
<link rel="stylesheet" href="static/jquery-ui.sortable.min.css"> <link rel="stylesheet" href="static/jquery-ui.sortable.min.css">
<link rel="stylesheet" href="static/bootstrap.min.css"> <link rel="stylesheet" href="static/bootstrap.min.css">
<link rel="stylesheet" href="static/bootstrap-toggle.min.css"> <link rel="stylesheet" href="static/bootstrap-toggle.min.css">
<link rel="stylesheet" href="static/custom.css?ver=1.16.4e"> <link rel="stylesheet" href="static/custom.css?ver=1.16.4g">
<link rel="stylesheet" href="static/open-iconic-bootstrap.min.css"> <link rel="stylesheet" href="static/open-iconic-bootstrap.min.css">
</head> </head>
<body> <body>
@ -80,8 +80,23 @@
</div> </div>
</nav> </nav>
</div> </div>
<div id="connectstatusdiv"> <div id="connectstatusdiv" class="flex-row-container">
<span id="connectstatus" class="color_orange">Waiting for connection...</span> <span id="connectstatus" class="color_orange flex-row">Waiting for connection...</span>
<div class="layer-container status-container flex-push-right">
<span class="oi oi-puzzle-piece statusicon layer-bottom" aria-hidden="true">
<div class="statustext statustext-wide">
<div id="stat-us" class="statusheader">No userscripts active</div>
<div id="stat-usactive"></div>
</div>
</span>
<div class="layer-top statusiconlabel" id="usiconlabel"></div>
</div>
<span class="oi oi-lightbulb statusicon" aria-hidden="true">
<div class="statustext">
<div id="stat-sp" class="statusheader">No soft prompt active</div>
<div id="stat-spactive"></div>
</div>
</span>
</div> </div>
</div> </div>
</div> </div>