diff --git a/aiserver.py b/aiserver.py index da4a7c73..ca2f92cc 100644 --- a/aiserver.py +++ b/aiserver.py @@ -1024,8 +1024,12 @@ def get_model_info(model, directory=""): breakmodel = False else: breakmodel = True - if path.exists("settings/{}.breakmodel".format(model.replace("/", "_"))): - with open("settings/{}.breakmodel".format(model.replace("/", "_")), "r") as file: + if model in ["NeoCustom", "GPT2Custom"]: + filename = os.path.basename(os.path.normpath(directory)) + else: + filename = "settings/{}.breakmodel".format(model.replace("/", "_")) + if path.exists(filename): + with open(filename, "r") as file: data = file.read().split("\n")[:2] if len(data) < 2: data.append("0") @@ -1034,10 +1038,6 @@ def get_model_info(model, directory=""): else: break_values = [layer_count] break_values += [0] * (gpu_count - len(break_values)) - #print("Model_info: {}".format({'cmd': 'selected_model_info', 'key_value': key_value, 'key':key, - # 'gpu':gpu, 'layer_count':layer_count, 'breakmodel':breakmodel, - # 'break_values': break_values, 'gpu_count': gpu_count, - # 'url': url, 'gpu_names': gpu_names})) emit('from_server', {'cmd': 'selected_model_info', 'key_value': key_value, 'key':key, 'gpu':gpu, 'layer_count':layer_count, 'breakmodel':breakmodel, 'disk_break_value': disk_blocks, 'accelerate': utils.HAS_ACCELERATE, @@ -1341,10 +1341,10 @@ def patch_transformers(): scores: torch.FloatTensor, **kwargs, ) -> bool: - story_settings.generated_tkns += 1 - if(system_settings.lua_koboldbridge.generated_cols and story_settings.generated_tkns != system_settings.lua_koboldbridge.generated_cols): - raise RuntimeError(f"Inconsistency detected between KoboldAI Python and Lua backends ({story_settings.generated_tkns} != {system_settings.lua_koboldbridge.generated_cols})") - if(system_settings.abort or story_settings.generated_tkns >= model_settings.genamt): + model_settings.generated_tkns += 1 + if(system_settings.lua_koboldbridge.generated_cols and model_settings.generated_tkns != system_settings.lua_koboldbridge.generated_cols): + raise RuntimeError(f"Inconsistency detected between KoboldAI Python and Lua backends ({model_settings.generated_tkns} != {system_settings.lua_koboldbridge.generated_cols})") + if(system_settings.abort or model_settings.generated_tkns >= model_settings.genamt): self.regeneration_required = False self.halt = False return True @@ -1356,11 +1356,11 @@ def patch_transformers(): system_settings.lua_koboldbridge.regeneration_required = False for i in range(model_settings.numseqs): - system_settings.lua_koboldbridge.generated[i+1][story_settings.generated_tkns] = int(input_ids[i, -1].item()) + system_settings.lua_koboldbridge.generated[i+1][model_settings.generated_tkns] = int(input_ids[i, -1].item()) if(not story_settings.dynamicscan): return self.regeneration_required or self.halt - tail = input_ids[..., -story_settings.generated_tkns:] + tail = input_ids[..., -model_settings.generated_tkns:] for i, t in enumerate(tail): decoded = utils.decodenewlines(tokenizer.decode(t)) _, found = checkworldinfo(decoded, force_use_txt=True, actions=story_settings._actions) @@ -1970,17 +1970,17 @@ def load_model(use_gpu=True, gpu_layers=None, disk_layers=None, initial_load=Fal return scores def tpumtjgenerate_stopping_callback(generated, n_generated, excluded_world_info) -> Tuple[List[set], bool, bool]: - story_settings.generated_tkns += 1 + model_settings.generated_tkns += 1 assert len(excluded_world_info) == len(generated) regeneration_required = system_settings.lua_koboldbridge.regeneration_required - halt = system_settings.abort or not system_settings.lua_koboldbridge.generating or story_settings.generated_tkns >= model_settings.genamt + halt = system_settings.abort or not system_settings.lua_koboldbridge.generating or model_settings.generated_tkns >= model_settings.genamt system_settings.lua_koboldbridge.regeneration_required = False global past for i in range(model_settings.numseqs): - system_settings.lua_koboldbridge.generated[i+1][story_settings.generated_tkns] = int(generated[i, tpu_mtj_backend.params["seq"] + n_generated - 1].item()) + system_settings.lua_koboldbridge.generated[i+1][model_settings.generated_tkns] = int(generated[i, tpu_mtj_backend.params["seq"] + n_generated - 1].item()) if(not story_settings.dynamicscan or halt): return excluded_world_info, regeneration_required, halt @@ -2067,7 +2067,16 @@ def load_model(use_gpu=True, gpu_layers=None, disk_layers=None, initial_load=Fal setStartState() sendsettings() refresh_settings() - + + #Let's load the presets + with open('settings/preset/official.presets') as f: + presets = json.load(f) + if model_settings.model in presets: + model_settings.presets = presets[model_settings.model] + elif model_settings.model.replace("/", "_") in presets: + model_settings.presets = presets[model_settings.model.replace("/", "_")] + else: + model_settings.presets = {} # Set up Flask routes @app.route('/') @@ -2770,6 +2779,9 @@ def do_connect(): return join_room("UI_{}".format(request.args.get('ui'))) print("Joining Room UI_{}".format(request.args.get('ui'))) + if request.args.get("ui") == 2: + ui2_connect() + return #Send all variables to client model_settings.send_to_ui() story_settings.send_to_ui() @@ -3808,9 +3820,9 @@ def _generate(txt, minimum, maximum, found_entries): break assert genout.ndim >= 2 assert genout.shape[0] == model_settings.numseqs - if(system_settings.lua_koboldbridge.generated_cols and story_settings.generated_tkns != system_settings.lua_koboldbridge.generated_cols): + if(system_settings.lua_koboldbridge.generated_cols and model_settings.generated_tkns != system_settings.lua_koboldbridge.generated_cols): raise RuntimeError("Inconsistency detected between KoboldAI Python and Lua backends") - if(already_generated != story_settings.generated_tkns): + if(already_generated != model_settings.generated_tkns): raise RuntimeError("WI scanning error") for r in range(model_settings.numseqs): for c in range(already_generated): @@ -3850,7 +3862,7 @@ def _generate(txt, minimum, maximum, found_entries): def generate(txt, minimum, maximum, found_entries=None): - story_settings.generated_tkns = 0 + model_settings.generated_tkns = 0 if(found_entries is None): found_entries = set() @@ -3886,7 +3898,7 @@ def generate(txt, minimum, maximum, found_entries=None): return for i in range(model_settings.numseqs): - system_settings.lua_koboldbridge.generated[i+1][story_settings.generated_tkns] = int(genout[i, -1].item()) + system_settings.lua_koboldbridge.generated[i+1][model_settings.generated_tkns] = int(genout[i, -1].item()) system_settings.lua_koboldbridge.outputs[i+1] = utils.decodenewlines(tokenizer.decode(genout[i, -already_generated:])) execute_outmod() @@ -4086,7 +4098,7 @@ def sendtocolab(txt, min, max): # Send text to TPU mesh transformer backend #==================================================================# def tpumtjgenerate(txt, minimum, maximum, found_entries=None): - story_settings.generated_tkns = 0 + model_settings.generated_tkns = 0 if(found_entries is None): found_entries = set() @@ -4173,7 +4185,7 @@ def tpumtjgenerate(txt, minimum, maximum, found_entries=None): past = genout for i in range(model_settings.numseqs): system_settings.lua_koboldbridge.generated[i+1] = system_settings.lua_state.table(*genout[i].tolist()) - system_settings.lua_koboldbridge.generated_cols = story_settings.generated_tkns = genout[0].shape[-1] + system_settings.lua_koboldbridge.generated_cols = model_settings.generated_tkns = genout[0].shape[-1] except Exception as e: if(issubclass(type(e), lupa.LuaError)): @@ -5196,6 +5208,7 @@ def loadRequest(loadpath, filename=None): # Leave Edit/Memory mode before continuing exitModes() + # Read file contents into JSON object if(isinstance(loadpath, str)): with open(loadpath, "r") as file: @@ -5206,107 +5219,115 @@ def loadRequest(loadpath, filename=None): js = loadpath if(filename is None): filename = "untitled.json" - - # Copy file contents to vars - story_settings.gamestarted = js["gamestarted"] - story_settings.prompt = js["prompt"] - story_settings.memory = js["memory"] - story_settings.worldinfo = [] - story_settings.worldinfo = [] - story_settings.worldinfo_u = {} - story_settings.wifolders_d = {int(k): v for k, v in js.get("wifolders_d", {}).items()} - story_settings.wifolders_l = js.get("wifolders_l", []) - story_settings.wifolders_u = {uid: [] for uid in story_settings.wifolders_d} - story_settings.lastact = "" - story_settings.submission = "" - story_settings.lastctx = "" - story_settings.genseqs = [] + js['v1_loadpath'] = loadpath + js['v1_filename'] = filename + loadJSON(js) - del story_settings.actions - story_settings.actions = koboldai_settings.KoboldStoryRegister() - actions = collections.deque(js["actions"]) - - - if "actions_metadata" in js: - - if type(js["actions_metadata"]) == dict: - temp = js["actions_metadata"] - story_settings.actions_metadata = {} - #we need to redo the numbering of the actions_metadata since the actions list doesn't preserve it's number on saving - if len(temp) > 0: - counter = 0 - temp = {int(k):v for k,v in temp.items()} - for i in range(max(temp)+1): - if i in temp: - story_settings.actions_metadata[counter] = temp[i] - counter += 1 - del temp - else: - #fix if we're using the old metadata format - story_settings.actions_metadata = {} - i = 0 - - for text in js['actions']: - story_settings.actions_metadata[i] = {'Selected Text': text, 'Alternative Text': []} - i+=1 +def loadJSON(json_text_or_dict): + if isinstance(json_text_or_dict, str): + json_data = json.loads(json_text_or_dict) + else: + json_data = json_text_or_dict + if "file_version" in json_data: + if json_data['file_version'] == 2: + load_story_v2(json_data) else: + load_story_v1(json_data) + else: + load_story_v1(json_data) + +def load_story_v1(js): + loadpath = js['v1_loadpath'] + filename = js['v1_filename'] + + # Copy file contents to vars + story_settings.gamestarted = js["gamestarted"] + story_settings.prompt = js["prompt"] + story_settings.memory = js["memory"] + story_settings.worldinfo = [] + story_settings.worldinfo = [] + story_settings.worldinfo_u = {} + story_settings.wifolders_d = {int(k): v for k, v in js.get("wifolders_d", {}).items()} + story_settings.wifolders_l = js.get("wifolders_l", []) + story_settings.wifolders_u = {uid: [] for uid in story_settings.wifolders_d} + story_settings.lastact = "" + story_settings.submission = "" + story_settings.lastctx = "" + story_settings.genseqs = [] + + del story_settings.actions + story_settings.actions = koboldai_settings.KoboldStoryRegister() + actions = collections.deque(js["actions"]) + + + if "actions_metadata" in js: + + if type(js["actions_metadata"]) == dict: + temp = js["actions_metadata"] + story_settings.actions_metadata = {} + #we need to redo the numbering of the actions_metadata since the actions list doesn't preserve it's number on saving + if len(temp) > 0: + counter = 0 + temp = {int(k):v for k,v in temp.items()} + for i in range(max(temp)+1): + if i in temp: + story_settings.actions_metadata[counter] = temp[i] + counter += 1 + del temp + else: + #fix if we're using the old metadata format story_settings.actions_metadata = {} i = 0 for text in js['actions']: story_settings.actions_metadata[i] = {'Selected Text': text, 'Alternative Text': []} i+=1 - - - if(len(story_settings.prompt.strip()) == 0): - while(len(actions)): - action = actions.popleft() - if(len(action.strip()) != 0): - story_settings.prompt = action - break - else: - story_settings.gamestarted = False - if(story_settings.gamestarted): - for s in actions: - story_settings.actions.append(s) + else: + story_settings.actions_metadata = {} + i = 0 - # Try not to break older save files - if("authorsnote" in js): - story_settings.authornote = js["authorsnote"] - else: - story_settings.authornote = "" - if("anotetemplate" in js): - story_settings.authornotetemplate = js["anotetemplate"] - else: - story_settings.authornotetemplate = "[Author's note: <|>]" - - if("worldinfo" in js): - num = 0 - for wi in js["worldinfo"]: - story_settings.worldinfo.append({ - "key": wi["key"], - "keysecondary": wi.get("keysecondary", ""), - "content": wi["content"], - "comment": wi.get("comment", ""), - "folder": wi.get("folder", None), - "num": num, - "init": True, - "selective": wi.get("selective", False), - "constant": wi.get("constant", False), - "uid": None, - }) - while(True): - uid = int.from_bytes(os.urandom(4), "little", signed=True) - if(uid not in story_settings.worldinfo_u): - break - story_settings.worldinfo_u[uid] = story_settings.worldinfo[-1] - story_settings.worldinfo[-1]["uid"] = uid - if(story_settings.worldinfo[-1]["folder"] is not None): - story_settings.wifolders_u[story_settings.worldinfo[-1]["folder"]].append(story_settings.worldinfo[-1]) - num += 1 + for text in js['actions']: + story_settings.actions_metadata[i] = {'Selected Text': text, 'Alternative Text': []} + i+=1 + - for uid in story_settings.wifolders_l + [None]: - story_settings.worldinfo.append({"key": "", "keysecondary": "", "content": "", "comment": "", "folder": uid, "num": None, "init": False, "selective": False, "constant": False, "uid": None}) + if(len(story_settings.prompt.strip()) == 0): + while(len(actions)): + action = actions.popleft() + if(len(action.strip()) != 0): + story_settings.prompt = action + break + else: + story_settings.gamestarted = False + if(story_settings.gamestarted): + for s in actions: + story_settings.actions.append(s) + + # Try not to break older save files + if("authorsnote" in js): + story_settings.authornote = js["authorsnote"] + else: + story_settings.authornote = "" + if("anotetemplate" in js): + story_settings.authornotetemplate = js["anotetemplate"] + else: + story_settings.authornotetemplate = "[Author's note: <|>]" + + if("worldinfo" in js): + num = 0 + for wi in js["worldinfo"]: + story_settings.worldinfo.append({ + "key": wi["key"], + "keysecondary": wi.get("keysecondary", ""), + "content": wi["content"], + "comment": wi.get("comment", ""), + "folder": wi.get("folder", None), + "num": num, + "init": True, + "selective": wi.get("selective", False), + "constant": wi.get("constant", False), + "uid": None, + }) while(True): uid = int.from_bytes(os.urandom(4), "little", signed=True) if(uid not in story_settings.worldinfo_u): @@ -5315,32 +5336,49 @@ def loadRequest(loadpath, filename=None): story_settings.worldinfo[-1]["uid"] = uid if(story_settings.worldinfo[-1]["folder"] is not None): story_settings.wifolders_u[story_settings.worldinfo[-1]["folder"]].append(story_settings.worldinfo[-1]) - stablesortwi() - story_settings.worldinfo_i = [wi for wi in story_settings.worldinfo if wi["init"]] + num += 1 - # Save path for save button - system_settings.savedir = loadpath - - # Clear loadselect var - user_settings.loadselect = "" - - # Refresh game screen - _filename = filename - if(filename.endswith('.json')): - _filename = filename[:-5] - user_settings.laststory = _filename - emit('from_server', {'cmd': 'setstoryname', 'data': user_settings.laststory}, broadcast=True, room="UI_1") - setgamesaved(True) - sendwi() - emit('from_server', {'cmd': 'setmemory', 'data': story_settings.memory}, broadcast=True, room="UI_1") - emit('from_server', {'cmd': 'setanote', 'data': story_settings.authornote}, broadcast=True, room="UI_1") - emit('from_server', {'cmd': 'setanotetemplate', 'data': story_settings.authornotetemplate}, broadcast=True, room="UI_1") - refresh_story() - emit('from_server', {'cmd': 'setgamestate', 'data': 'ready'}, broadcast=True, room="UI_1") - emit('from_server', {'cmd': 'hidegenseqs', 'data': ''}, broadcast=True, room="UI_1") - print("{0}Story loaded from {1}!{2}".format(colors.GREEN, filename, colors.END)) - - send_debug() + for uid in story_settings.wifolders_l + [None]: + story_settings.worldinfo.append({"key": "", "keysecondary": "", "content": "", "comment": "", "folder": uid, "num": None, "init": False, "selective": False, "constant": False, "uid": None}) + while(True): + uid = int.from_bytes(os.urandom(4), "little", signed=True) + if(uid not in story_settings.worldinfo_u): + break + story_settings.worldinfo_u[uid] = story_settings.worldinfo[-1] + story_settings.worldinfo[-1]["uid"] = uid + if(story_settings.worldinfo[-1]["folder"] is not None): + story_settings.wifolders_u[story_settings.worldinfo[-1]["folder"]].append(story_settings.worldinfo[-1]) + stablesortwi() + story_settings.worldinfo_i = [wi for wi in story_settings.worldinfo if wi["init"]] + + # Save path for save button + system_settings.savedir = loadpath + + # Clear loadselect var + user_settings.loadselect = "" + + # Refresh game screen + _filename = filename + if(filename.endswith('.json')): + _filename = filename[:-5] + user_settings.laststory = _filename + #set the story_name + story_settings.story_name = _filename + emit('from_server', {'cmd': 'setstoryname', 'data': user_settings.laststory}, broadcast=True, room="UI_1") + setgamesaved(True) + sendwi() + emit('from_server', {'cmd': 'setmemory', 'data': story_settings.memory}, broadcast=True, room="UI_1") + emit('from_server', {'cmd': 'setanote', 'data': story_settings.authornote}, broadcast=True, room="UI_1") + emit('from_server', {'cmd': 'setanotetemplate', 'data': story_settings.authornotetemplate}, broadcast=True, room="UI_1") + refresh_story() + emit('from_server', {'cmd': 'setgamestate', 'data': 'ready'}, broadcast=True, room="UI_1") + emit('from_server', {'cmd': 'hidegenseqs', 'data': ''}, broadcast=True, room="UI_1") + print("{0}Story loaded from {1}!{2}".format(colors.GREEN, filename, colors.END)) + + send_debug() + +def load_story_v2(js): + story_settings.from_json(js) #==================================================================# # Import an AIDungon game exported with Mimi's tool @@ -5769,11 +5807,23 @@ def send_debug(): emit('from_server', {'cmd': 'debug_info', 'data': debug_info}, broadcast=True, room="UI_1") + +#==================================================================# +# UI V2 CODE +#==================================================================# +@app.route('/new_ui') +def new_ui_index(): + return render_template('index_new.html', settings=gensettings.gensettingstf if model_settings.model != "InferKit" else gensettings.gensettingsik ) + +def ui2_connect(): + pass + #==================================================================# # Event triggered when browser SocketIO detects a variable change #==================================================================# @socketio.on('var_change') def UI_2_var_change(data): + print(data) classname = data['ID'].split("_")[0] name = data['ID'][len(classname)+1:] classname += "_settings" @@ -5793,15 +5843,24 @@ def UI_2_var_change(data): print("{} {} = {}".format(classname, name, value)) setattr(globals()[classname], name, value) - - + + #Now let's save except for story changes + if classname != "story_settings": + with open("settings/{}.v2_settings".format(classname), "w") as settings_file: + settings_file.write(globals()[classname].to_json()) + #==================================================================# -# UI V2 CODE +# Saving Story #==================================================================# -@app.route('/new_ui') -def new_ui_index(): - return render_template('index_new.html', settings=gensettings.gensettingstf if model_settings.model != "InferKit" else gensettings.gensettingsik ) - +@socketio.on('save_story') +def UI_2_save_story(data): + json_data = story_settings.to_json() + save_name = story_settings.story_name if story_settings.story_name is not None else "untitled" + with open("stories/{}_v2.json".format(save_name), "w") as settings_file: + settings_file.write(story_settings.to_json()) + story_settings.gamesaved = True + + #==================================================================# # Event triggered when Selected Text is edited, Option is Selected, etc #==================================================================# @@ -5824,10 +5883,7 @@ def UI_2_submit(data): #==================================================================# @socketio.on('Pinning') def UI_2_Pinning(data): - if data['set']: - story_settings.actions.set_pin(int(data['chunk']), int(data['option'])) - else: - story_settings.actions.unset_pin(int(data['chunk']), int(data['option'])) + story_settings.actions.toggle_pin(int(data['chunk']), int(data['option'])) #==================================================================# # Event triggered when user clicks the back button @@ -5870,6 +5926,10 @@ def UI_2_relay(data): def show_actions(): return story_settings.actions.actions +@app.route("/story") +def show_story(): + return story_settings.to_json() + #==================================================================# @@ -5952,3 +6012,4 @@ else: model_settings.model = "ReadOnly" load_model(initial_load=True) print("{0}\nServer started in WSGI mode!{1}".format(colors.GREEN, colors.END), flush=True) + diff --git a/koboldai_settings.py b/koboldai_settings.py index 34f89152..6ea3ee1a 100644 --- a/koboldai_settings.py +++ b/koboldai_settings.py @@ -1,6 +1,7 @@ from flask_socketio import emit, join_room, leave_room, rooms -import os, re, time, threading +import os, re, time, threading, json, pickle, base64, copy, tqdm, datetime import socketio as socketio_client +from io import BytesIO socketio = None main_thread_id = threading.get_ident() @@ -47,8 +48,50 @@ def process_variable_changes(classname, name, value, old_value, debug_message=No else: socketio.emit("var_changed", {"classname": classname, "name": name, "old_value": clean_var_for_emit(old_value), "value": clean_var_for_emit(value)}, include_self=True, broadcast=True, room="UI_2") + + class settings(object): + def to_json(self): + json_data = {'file_version': 2} + for (name, value) in vars(self).items(): + if name not in self.no_save_variables: + json_data[name] = value + def to_base64(data): + if isinstance(data, KoboldStoryRegister): + return data.to_json() + output = BytesIO() + pickle.dump(data, output) + output.seek(0) + return "base64:{}".format(base64.encodebytes(output.read()).decode()) + return json.dumps(json_data, default=to_base64) + + def from_json(self, data): + if isinstance(data, str): + json_data = json.loads(data) + else: + json_data = data + for key, value in data.items(): + if key in self.__dict__: + if isinstance(value, str): + if value[:7] == 'base64:': + value = pickle.loads(base64.b64decode(value[7:])) + #Need to fix the data type of value to match the module + if type(getattr(self, key)) == int: + value = int(value) + elif type(getattr(self, key)) == float: + value = float(value) + elif type(getattr(self, key)) == bool: + value = bool(value) + elif type(getattr(self, key)) == str: + value = str(value) + if isinstance(getattr(self, key), KoboldStoryRegister): + self.actions.load_json(value) + else: + setattr(self, key, value) + + + def send_to_ui(self): if socketio is not None: for (name, value) in vars(self).items(): @@ -58,7 +101,8 @@ class settings(object): class model_settings(settings): - local_only_variables = ['badwordsids', 'apikey', '_class_init'] + local_only_variables = ['badwordsids', 'apikey', '_class_init', 'tqdm'] + no_save_variables = ['tqdm'] settings_name = "model" def __init__(self): self.model = "" # Model ID string chosen at startup @@ -79,6 +123,9 @@ class model_settings(settings): self.tfs = 1.0 # Default generator tfs (tail-free sampling) self.typical = 1.0 # Default generator typical sampling threshold self.numseqs = 1 # Number of sequences to ask the generator to create + self.generated_tkns = 0 # If using a backend that supports Lua generation modifiers, how many tokens have already been generated, otherwise 0 + self.tqdm = tqdm.tqdm(total=self.genamt, file=self.ignore_tqdm()) # tqdm agent for generating tokens. This will allow us to calculate the remaining time + self.tqdm_rem_time = 0 # tqdm calculated reemaining time self.badwordsids = [] self.fp32_model = False # Whether or not the most recently loaded HF model was in fp32 format self.url = "https://api.inferkit.com/v1/models/standard/generate" # InferKit API URL @@ -96,20 +143,35 @@ class model_settings(settings): self.selected_preset = "" + #dummy class to eat the tqdm output + class ignore_tqdm(object): + def write(self, bar): + pass + def __setattr__(self, name, value): old_value = getattr(self, name, None) super().__setattr__(name, value) #Put variable change actions here + + #Setup TQDP + if name == "generated_tkns" and 'tqdm' in self.__dict__: + if value == 0: + self.tqdm.reset(total=self.genamt) + else: + self.tqdm.update(1) + self.tqdm_rem_time = str(datetime.timedelta(seconds=int(float(self.genamt-self.generated_tkns)/self.tqdm.format_dict['rate']))) + + if name not in self.local_only_variables and name[0] != "_": process_variable_changes(self.__class__.__name__.replace("_settings", ""), name, value, old_value) - - class story_settings(settings): #local_only_variables = ['generated_tkns'] local_only_variables = [] + no_save_variables = [] settings_name = "story" def __init__(self): + self.story_name = None # Title of the story self.lastact = "" # The last action received from the user self.submission = "" # Same as above, but after applying input formatting self.lastctx = "" # The last context submitted to the generator @@ -136,7 +198,6 @@ class story_settings(settings): self.wifolders_u = {} # Dictionary of pairs of folder UID - list of WI UID self.lua_edited = set() # Set of chunk numbers that were edited from a Lua generation modifier self.lua_deleted = set() # Set of chunk numbers that were deleted from a Lua generation modifier - self.generated_tkns = 0 # If using a backend that supports Lua generation modifiers, how many tokens have already been generated, otherwise 0 self.deletewi = None # Temporary storage for UID to delete self.mode = "play" # Whether the interface is in play, memory, or edit mode self.editln = 0 # Which line was last selected in Edit Mode @@ -159,9 +220,14 @@ class story_settings(settings): #Put variable change actions here if name not in self.local_only_variables and name[0] != "_": process_variable_changes(self.__class__.__name__.replace("_settings", ""), name, value, old_value) + #We want to automatically set gamesaved to false if something happens to the actions list (pins, redos, generations, text, etc) + #To do that we need to give the actions list a copy of this data so it can set the gamesaved variable as needed + if name == 'actions': + self.actions.story_settings = self class user_settings(settings): local_only_variables = [] + no_save_variables = [] settings_name = "user" def __init__(self): self.wirmvwhtsp = False # Whether to remove leading whitespace from WI entries @@ -190,9 +256,9 @@ class user_settings(settings): if name not in self.local_only_variables and name[0] != "_": process_variable_changes(self.__class__.__name__.replace("_settings", ""), name, value, old_value) - class system_settings(settings): local_only_variables = ['lua_state', 'lua_logname', 'lua_koboldbridge', 'lua_kobold', 'lua_koboldcore', 'regex_sl', 'acregex_ai', 'acregex_ui', 'comregex_ai', 'comregex_ui'] + no_save_variables = [] settings_name = "system" def __init__(self): self.noai = False # Runs the script without starting up the transformers pipeline @@ -248,7 +314,6 @@ class system_settings(settings): if name not in self.local_only_variables and name[0] != "_": process_variable_changes(self.__class__.__name__.replace("_settings", ""), name, value, old_value) - class KoboldStoryRegister(object): def __init__(self, sequence=[]): self.actions = {} @@ -290,6 +355,7 @@ class KoboldStoryRegister(object): old_text = None self.actions[i] = {"Selected Text": text, "Options": []} process_variable_changes("actions", "Selected Text", {"id": i, "text": text}, {"id": i, "text": old_text}) + self.set_game_saved() def __len__(self): return self.action_count+1 if self.action_count >=0 else 0 @@ -317,6 +383,7 @@ class KoboldStoryRegister(object): self.action_count = json_data['action_count'] self.actions = temp + self.set_game_saved() def get_action(self, action_id): if action_id not in actions: @@ -342,15 +409,17 @@ class KoboldStoryRegister(object): else: self.actions[self.action_count] = {"Selected Text": text, "Options": []} process_variable_changes("actions", "Selected Text", {"id": self.action_count, "text": text}, None) + self.set_game_saved() def append_options(self, option_list): if self.action_count+1 in self.actions: - old_options = self.actions[self.action_count+1]["Options"].copy() + old_options = copy.deepcopy(self.actions[self.action_count+1]["Options"]) self.actions[self.action_count+1]['Options'].extend([{"text": x, "Pinned": False, "Previous Selection": False, "Edited": False} for x in option_list]) else: old_options = None self.actions[self.action_count+1] = {"Selected Text": "", "Options": [{"text": x, "Pinned": False, "Previous Selection": False, "Edited": False} for x in option_list]} process_variable_changes("actions", "Options", {"id": self.action_count+1, "options": self.actions[self.action_count+1]["Options"]}, {"id": self.action_count+1, "options": old_options}) + self.set_game_saved() def clear_unused_options(self, pointer=None): new_options = [] @@ -358,10 +427,11 @@ class KoboldStoryRegister(object): if pointer is None: pointer = self.action_count+1 if pointer in self.actions: - old_options = self.actions[pointer]["Options"].copy() + old_options = copy.deepcopy(self.actions[pointer]["Options"]) self.actions[pointer]["Options"] = [x for x in self.actions[pointer]["Options"] if x["Pinned"] or x["Previous Selection"] or x["Edited"]] new_options = self.actions[pointer]["Options"] process_variable_changes("actions", "Options", {"id": pointer, "options": new_options}, {"id": pointer, "options": old_options}) + self.set_game_saved() def toggle_pin(self, action_step, option_number): if action_step in self.actions: @@ -374,22 +444,32 @@ class KoboldStoryRegister(object): def set_pin(self, action_step, option_number): if action_step in self.actions: if option_number < len(self.actions[action_step]['Options']): - old_options = self.actions[action_step]["Options"].copy() + old_options = copy.deepcopy(self.actions[action_step]["Options"]) self.actions[action_step]['Options'][option_number]['Pinned'] = True process_variable_changes("actions", "Options", {"id": action_step, "options": self.actions[action_step]["Options"]}, {"id": action_step, "options": old_options}) + self.set_game_saved() def unset_pin(self, action_step, option_number): if action_step in self.actions: - old_options = self.actions[action_step]["Options"].copy() + old_options = copy.deepcopy(self.actions[action_step]["Options"]) if option_number < len(self.actions[action_step]['Options']): self.actions[action_step]['Options'][option_number]['Pinned'] = False process_variable_changes("actions", "Options", {"id": action_step, "options": self.actions[action_step]["Options"]}, {"id": action_step, "options": old_options}) + self.set_game_saved() + + def toggle_pin(self, action_step, option_number): + if action_step in self.actions: + old_options = copy.deepcopy(self.actions[action_step]["Options"]) + if option_number < len(self.actions[action_step]['Options']): + self.actions[action_step]['Options'][option_number]['Pinned'] = not self.actions[action_step]['Options'][option_number]['Pinned'] + process_variable_changes("actions", "Options", {"id": action_step, "options": self.actions[action_step]["Options"]}, {"id": action_step, "options": old_options}) + self.set_game_saved() def use_option(self, option_number, action_step=None): if action_step is None: action_step = self.action_count+1 if action_step in self.actions: - old_options = self.actions[action_step]["Options"].copy() + old_options = copy.deepcopy(self.actions[action_step]["Options"]) old_text = self.actions[action_step]["Selected Text"] if option_number < len(self.actions[action_step]['Options']): self.actions[action_step]["Selected Text"] = self.actions[action_step]['Options'][option_number]['text'] @@ -400,22 +480,25 @@ class KoboldStoryRegister(object): socketio.emit("var_changed", {"classname": "actions", "name": "Action Count", "old_value": None, "value":self.action_count}, broadcast=True, room="UI_2") process_variable_changes("actions", "Options", {"id": action_step, "options": self.actions[action_step]["Options"]}, {"id": action_step, "options": old_options}) process_variable_changes("actions", "Selected Text", {"id": action_step, "text": self.actions[action_step]["Selected Text"]}, {"id": action_step, "Selected Text": old_text}) + self.set_game_saved() def delete_action(self, action_id): if action_id in self.actions: - old_options = self.actions[action_id]["Options"].copy() + old_options = copy.deepcopy(self.actions[action_id]["Options"]) old_text = self.actions[action_id]["Selected Text"] self.actions[action_id]["Options"].append({"text": self.actions[action_id]["Selected Text"], "Pinned": False, "Previous Selection": True, "Edited": False}) self.actions[action_id]["Selected Text"] = "" self.action_count -= 1 process_variable_changes("actions", "Selected Text", {"id": action_id, "text": None}, {"id": action_id, "text": old_text}) process_variable_changes("actions", "Options", {"id": action_id, "options": self.actions[action_id]["Options"]}, {"id": action_id, "options": old_options}) + self.set_game_saved() def pop(self): if self.action_count >= 0: text = self.actions[self.action_count] self.delete_action(self.action_count) process_variable_changes("actions", "Selected Text", {"id": self.action_count, "text": None}, {"id": self.action_count, "text": text}) + self.set_game_saved() return text else: return None @@ -493,7 +576,10 @@ class KoboldStoryRegister(object): return [x for x in self.actions[self.action_count+1]['Options'] if x['Pinned'] or x['Previous Selection']] else: return [] - + + def set_game_saved(self): + if 'story_settings' in self.__dict__: + self.story_settings.gamesaved = False def __setattr__(self, name, value): old_value = getattr(self, name, None) super().__setattr__(name, value) diff --git a/settings/preset/official.presets b/settings/preset/official.presets new file mode 100644 index 00000000..3e3259de --- /dev/null +++ b/settings/preset/official.presets @@ -0,0 +1,95 @@ +{"EleutherAI_gpt-neo-1.3B": + { + "Storywriter": { + "temp": 0.72, + "genamt": 40, + "rep_pen": 1.2, + "top_p": 0.725, + "top_k": 0, + "tfs": 1, + "rep_pen_slope": 2048, + "rep_pen_range": 0.18, + "typical": 1, + "top_a": 0, + "description": "Optimized settings for relevant output." + }, + "Coherent Creativity": { + "temp": 0.51, + "genamt": 40, + "rep_pen": 1.2, + "top_p": 1, + "top_k": 0, + "tfs": 0.9, + "rep_pen_slope": 2048, + "rep_pen_range": 0, + "typical": 1, + "top_a": 0, + "description": "A good balance between coherence, creativity, and quality of prose." + }, + "Luna Moth": { + "temp": 2, + "genamt": 40, + "rep_pen": 1.2, + "top_p": 0.235, + "top_k": 85, + "tfs": 1, + "rep_pen_slope": 2048, + "rep_pen_range": 0, + "typical": 1, + "top_a": 0, + "description": "A great degree of creativity without losing coherency." + }, + "Sphinx Moth": { + "temp": 2, + "genamt": 40, + "rep_pen": 1.2, + "top_p": 0.175, + "top_k": 30, + "tfs": 1, + "rep_pen_slope": 2048, + "rep_pen_range": 0, + "typical": 1, + "top_a": 0, + "description": "Maximum randomness while still being plot relevant. Like Sphinx riddles!" + }, + "Emperor Moth": { + "temp": 1.25, + "genamt": 40, + "rep_pen": 1.2, + "top_p": 0.235, + "top_k": 0, + "tfs": 1, + "rep_pen_slope": 2048, + "rep_pen_range": 0, + "typical": 1, + "top_a": 0, + "description": "Medium randomness with a decent bit of creative writing." + }, + "Best Guess": { + "temp": 0.8, + "genamt": 40, + "rep_pen": 1.2, + "top_p": 0.9, + "top_k": 100, + "tfs": 1, + "rep_pen_slope": 512, + "rep_pen_range": 3.33, + "typical": 1, + "top_a": 0, + "description": "A subtle change with alternative context settings." + }, + "Pleasing Results": { + "temp": 0.44, + "genamt": 40, + "rep_pen": 1.2, + "top_p": 1, + "top_k": 0, + "tfs": 0.9, + "rep_pen_slope": 1024, + "rep_pen_range": 6.75, + "typical": 1, + "top_a": 0, + "description": "Expectable output with alternative context settings." + } + } +} \ No newline at end of file diff --git a/static/koboldai.css b/static/koboldai.css index 3c8afbaf..7cf2f8f2 100644 --- a/static/koboldai.css +++ b/static/koboldai.css @@ -327,6 +327,8 @@ td.server_vars { } + + /* ---------------------------- OVERALL PAGE CONFIG ------------------------------*/ body { background-color: var(--background); @@ -336,7 +338,7 @@ body { .main-grid { transition: margin-left .5s; display: grid; - min-height: 98vh; + height: 98vh; margin-left: var(--flyout_menu_closed_width); /* grid-template-areas: "menuicon gamescreen lefticon" "menuicon actions lefticon" @@ -371,6 +373,13 @@ body { margin-top: auto; padding-bottom: 1px; vertical-align: bottom; + overflow-y: scroll; + -ms-overflow-style: none; /* IE and Edge */ + scrollbar-width: none; /* Firefox */ +} +/* Hide scrollbar for Chrome, Safari and Opera */ +.gametext::-webkit-scrollbar { + display: none; } [contenteditable="true"]:active, @@ -384,6 +393,7 @@ body { margin-top: 10px; grid-area: options; background-color: var(--gamescreen_background); + overflow-y: scroll; } table.sequence { @@ -446,14 +456,14 @@ td.sequence:hover { color: var(--text); } -.inputrow .submit[server_value=false] { +.inputrow .submit[system_aibusy=false] { grid-area: submit; height: 100%; width: 100%; text-align: center; overflow: hidden; } -.inputrow .submit[server_value=true] { +.inputrow .submit[system_aibusy=true] { grid-area: submit; height: 100%; width: 100%; @@ -462,7 +472,7 @@ td.sequence:hover { display: none; } -.inputrow .submited[server_value=false] { +.inputrow .submited[system_aibusy=false] { grid-area: submit; height: 100%; width: 100%; @@ -470,7 +480,7 @@ td.sequence:hover { overflow: hidden; display: none; } -.inputrow .submited[server_value=true] { +.inputrow .submited[system_aibusy=true] { grid-area: submit; height: 100%; width: 100%; diff --git a/static/koboldai.js b/static/koboldai.js index 5dfdbb91..69da49a9 100644 --- a/static/koboldai.js +++ b/static/koboldai.js @@ -12,6 +12,7 @@ socket.on('var_changed', function(data){var_changed(data);}); var backend_vars = {}; var presets = {} +var ai_busy_start = Date.now(); //-----------------------------------Server to UI Functions----------------------------------------------- function connect() { console.log("connected"); @@ -54,14 +55,11 @@ function create_options(data) { var chunk = children[i]; if (chunk.id == "Select Options Chunk " + current_chunk) { chunk.classList.remove("hidden"); - console.log(current_chunk); } else { chunk.classList.add("hidden"); } } - console.log(current_chunk); - console.log(data); if (document.getElementById("Select Options Chunk "+data.value.id)) { var option_chunk = document.getElementById("Select Options Chunk "+data.value.id) } else { @@ -77,37 +75,6 @@ function create_options(data) { var table = document.createElement("table"); table.classList.add("sequence"); table.style = "border-spacing: 0;"; - //Add pins - i=0; - for (item of data.value.options) { - if (item.Pinned) { - var row = document.createElement("tr"); - row.classList.add("sequence"); - var textcell = document.createElement("td"); - textcell.textContent = item.text; - textcell.classList.add("sequence"); - textcell.setAttribute("option_id", i); - textcell.setAttribute("option_chunk", data.value.id); - var iconcell = document.createElement("td"); - iconcell.setAttribute("option_id", i); - iconcell.setAttribute("option_chunk", data.value.id); - var icon = document.createElement("span"); - icon.id = "Pin_"+i; - icon.classList.add("oi"); - icon.setAttribute('data-glyph', "pin"); - iconcell.append(icon); - textcell.onclick = function () { - socket.emit("Set Selected Text", {"chunk": this.getAttribute("option_chunk"), "option": this.getAttribute("option_id")}); - }; - iconcell.onclick = function () { - socket.emit("Pinning", {"chunk": this.getAttribute("option_chunk"), "option": this.getAttribute("option_id"), "set": false}); - }; - row.append(textcell); - row.append(iconcell); - table.append(row); - } - i+=1; - } //Add Redo options i=0; for (item of data.value.options) { @@ -139,7 +106,7 @@ function create_options(data) { //Add general options i=0; for (item of data.value.options) { - if (!(item.Edited) && !(item.Pinned) && !(item['Previous Selection'])) { + if (!(item.Edited) && !(item['Previous Selection'])) { var row = document.createElement("tr"); row.classList.add("sequence"); var textcell = document.createElement("td"); @@ -154,10 +121,12 @@ function create_options(data) { icon.id = "Pin_"+i; icon.classList.add("oi"); icon.setAttribute('data-glyph', "pin"); - icon.setAttribute('style', "filter: brightness(50%);"); + if (!(item.Pinned)) { + icon.setAttribute('style', "filter: brightness(50%);"); + } iconcell.append(icon); iconcell.onclick = function () { - socket.emit("Pinning", {"chunk": this.getAttribute("option_chunk"), "option": this.getAttribute("option_id"), "set": true}); + socket.emit("Pinning", {"chunk": this.getAttribute("option_chunk"), "option": this.getAttribute("option_id")}); }; textcell.onclick = function () { socket.emit("Set Selected Text", {"chunk": this.getAttribute("option_chunk"), "option": this.getAttribute("option_id")}); @@ -193,6 +162,7 @@ function do_story_text_updates(data) { } function do_presets(data) { + console.log(data); var select = document.getElementById('presets'); //clear out the preset list while (select.firstChild) { @@ -203,11 +173,11 @@ function do_presets(data) { option.value=""; option.text="presets"; select.append(option); - for (item of data.value) { - presets[item.preset] = item; + for (const [key, value] of Object.entries(data.value)) { + presets[key] = value; var option = document.createElement("option"); - option.value=item.preset; - option.text=item.preset; + option.value=key; + option.text=key; select.append(option); } } @@ -251,6 +221,20 @@ function update_status_bar(data) { document.title = "KoboldAI Client Generating (" + percent_complete + "%)"; } } + +function do_ai_busy(data) { + if (data.value) { + ai_busy_start = Date.now(); + favicon.start_swap() + } else { + runtime = Date.now() - ai_busy_start; + if (document.getElementById("Execution Time")) { + document.getElementById("Execution Time").textContent = Math.round(runtime/1000).toString().toHHMMSS(); + } + favicon.stop_swap() + } +} + function var_changed(data) { //Special Case for Story Text if ((data.classname == "actions") && (data.name == "Selected Text")) { @@ -276,22 +260,18 @@ function var_changed(data) { //alternative syncing method var elements_to_change = document.getElementsByClassName("var_sync_alt_"+data.classname.replace(" ", "_")+"_"+data.name.replace(" ", "_")); for (item of elements_to_change) { - item.setAttribute("server_value", fix_text(data.value)); + item.setAttribute(data.classname.replace(" ", "_")+"_"+data.name.replace(" ", "_"), fix_text(data.value)); } } //if we're updating generated tokens, let's show that in our status bar - if ((data.classname == 'story') && (data.name == 'generated_tkns')) { + if ((data.classname == 'model') && (data.name == 'generated_tkns')) { update_status_bar(data); } //If we have ai_busy, start the favicon swapping if ((data.classname == 'system') && (data.name == 'aibusy')) { - if (data.value) { - favicon.start_swap() - } else { - favicon.stop_swap() - } + do_ai_busy(data); } } @@ -299,6 +279,18 @@ function var_changed(data) { //--------------------------------------------General UI Functions------------------------------------ +String.prototype.toHHMMSS = function () { + var sec_num = parseInt(this, 10); // don't forget the second param + var hours = Math.floor(sec_num / 3600); + var minutes = Math.floor((sec_num - (hours * 3600)) / 60); + var seconds = sec_num - (hours * 3600) - (minutes * 60); + + if (hours < 10) {hours = "0"+hours;} + if (minutes < 10) {minutes = "0"+minutes;} + if (seconds < 10) {seconds = "0"+seconds;} + return hours+':'+minutes+':'+seconds; +} + function toggle_flyout(x) { if (document.getElementById("SideMenu").classList.contains("open")) { x.classList.remove("change"); diff --git a/templates/index_new.html b/templates/index_new.html index 1b11b16d..f9f6cc43 100644 --- a/templates/index_new.html +++ b/templates/index_new.html @@ -34,7 +34,7 @@