diff --git a/aiserver.py b/aiserver.py index 3bcfc95c..18d81fdc 100644 --- a/aiserver.py +++ b/aiserver.py @@ -6698,8 +6698,7 @@ def file_popup(popup_title, starting_folder, return_event, upload=True, jailed=T editable=False, show_breadcrumbs=True, item_check=None, show_hidden=False, valid_only=False, hide_extention=False, extra_parameter_function=None, column_names=['File Name'], show_filename=True, show_folders=True, - column_widths=["100%"], - sort="Modified", desc=False): + column_widths=["100%"], sort="Modified", advanced_sort=None, desc=False): #starting_folder = The folder we're going to get folders and/or items from #return_event = the socketio event that will be emitted when the load button is clicked #jailed = if set to true will look for the session variable jailed_folder and prevent navigation outside of that folder @@ -6732,6 +6731,7 @@ def file_popup(popup_title, starting_folder, return_event, upload=True, jailed=T session['sort'] = sort session['desc'] = desc session['show_folders'] = show_folders + session['advanced_sort'] = advanced_sort socketio.emit("load_popup", {"popup_title": popup_title, "call_back": return_event, "renameable": renameable, "deleteable": deleteable, "editable": editable, 'upload': upload}, broadcast=False, room="UI_2") socketio.emit("load_popup", {"popup_title": popup_title, "call_back": return_event, "renameable": renameable, "deleteable": deleteable, "editable": editable, 'upload': upload}, broadcast=True, room="UI_1") @@ -6754,6 +6754,7 @@ def get_files_folders(starting_folder): sort = session['sort'] desc = session['desc'] show_folders = session['show_folders'] + advanced_sort = session['advanced_sort'] if starting_folder == 'This PC': breadcrumbs = [['This PC', 'This PC']] @@ -6780,7 +6781,11 @@ def get_files_folders(starting_folder): folders = [] files = [] base_path = os.path.abspath(starting_folder).replace("\\", "/") - for item in get_files_sorted(base_path, sort, desc=desc): + if advanced_sort is not None: + files_to_check = advanced_sort(base_path, desc=desc) + else: + files_to_check = get_files_sorted(base_path, sort, desc=desc) + for item in files_to_check: item_full_path = os.path.join(base_path, item).replace("\\", "/") if hasattr(os.stat(item_full_path), "st_file_attributes"): hidden = bool(os.stat(item_full_path).st_file_attributes & stat.FILE_ATTRIBUTE_HIDDEN) @@ -7075,7 +7080,7 @@ def UI_2_load_story_list(data): deleteable=True, show_breadcrumbs=True, item_check=valid_story, valid_only=True, hide_extention=True, extra_parameter_function=get_story_length, column_names=['Story Name', 'Action Count'], show_filename=False, - column_widths=['auto', '100px'], + column_widths=['auto', '100px'], advanced_sort=story_sort, sort="Modified", desc=True) def get_story_length(item_full_path, item, valid_selection): @@ -7102,6 +7107,24 @@ def valid_story(file): return 'actions' in js +def story_sort(base_path, desc=False): + files = {} + for file in os.scandir(path=base_path): + if file.name.endswith(".json"): + filename = os.path.join(base_path, file.name).replace("\\", "/") + with open(filename, "r") as f: + try: + js = json.load(f) + except: + pass + + if 'story_name' in js and js['story_name'] in koboldai_vars.story_loads: + files[file.name] = datetime.datetime.strptime(koboldai_vars.story_loads[js['story_name']], "%m/%d/%Y, %H:%M:%S") + else: + files[file.name] = datetime.datetime.fromtimestamp(file.stat().st_mtime) + return [key[0] for key in sorted(files.items(), key=lambda kv: (kv[1], kv[0]), reverse=desc)] + + #==================================================================# # Event triggered on load story #==================================================================# diff --git a/koboldai_settings.py b/koboldai_settings.py index 2059a150..da3075bb 100644 --- a/koboldai_settings.py +++ b/koboldai_settings.py @@ -12,6 +12,8 @@ def clean_var_for_emit(value): return value.to_json() elif isinstance(value, set): return list(value) + elif isinstance(value, datetime.datetime): + return str(value) else: return value @@ -58,12 +60,21 @@ class koboldai_vars(object): def load_story(self, story_name, json_data): #Story name here is intended for multiple users on multiple stories. Now always uses default #If we can figure out a way to get flask sessions into/through the lua bridge we could re-enable + original_story_name = story_name story_name = 'default' if story_name in self._story_settings: self._story_settings[story_name].socketio.emit("reset_story", {}, broadcast=True, room="UI_2") + self._story_settings[story_name].no_save = True self._story_settings[story_name].from_json(json_data) + self._story_settings[story_name].no_save = False else: + self._story_settings[story_name].no_save = True self.create_story(story_name, json_data=json_data) + self._story_settings[story_name].no_save = False + self._system_settings.story_loads[original_story_name] = datetime.datetime.now().strftime("%m/%d/%Y, %H:%M:%S") + with open("settings/system_settings.v2_settings", "w") as settings_file: + settings_file.write(self._system_settings.to_json()) + def save_story(self): self._story_settings['default'].save_story() @@ -319,6 +330,8 @@ class settings(object): return data.to_json() elif isinstance(data, KoboldWorldInfo): return data.to_json() + elif isinstance(data, datetime.datetime): + return str(data) output = BytesIO() pickle.dump(data, output) output.seek(0) @@ -330,11 +343,16 @@ class settings(object): json_data = json.loads(data) else: json_data = data + #since loading will trigger the autosave, we need to disable it + if 'no_save' in self.__dict__: + setattr(self, 'no_save', True) for key, value in json_data.items(): if key in self.__dict__: if key == 'sampler_order': if(len(value) < 7): value = [6] + value + if key == 'autosave': + autosave = value if isinstance(value, str): if value[:7] == 'base64:': value = pickle.loads(base64.b64decode(value[7:])) @@ -354,6 +372,9 @@ class settings(object): else: setattr(self, key, value) + if 'no_save' in self.__dict__: + setattr(self, 'no_save', False) + def send_to_ui(self): for (name, value) in vars(self).items(): if name not in self.local_only_variables and name[0] != "_": @@ -480,8 +501,8 @@ class model_settings(settings): process_variable_changes(self.socketio, self.__class__.__name__.replace("_settings", ""), name, value, old_value) class story_settings(settings): - local_only_variables = ['socketio', 'tokenizer', 'koboldai_vars'] - no_save_variables = ['socketio', 'tokenizer', 'koboldai_vars', 'context'] + local_only_variables = ['socketio', 'tokenizer', 'koboldai_vars', 'no_save'] + no_save_variables = ['socketio', 'tokenizer', 'koboldai_vars', 'context', 'no_save'] settings_name = "story" def __init__(self, socketio, koboldai_vars, tokenizer=None): self.socketio = socketio @@ -494,6 +515,7 @@ class story_settings(settings): self.gamestarted = False # Whether the game has started (disables UI elements) self.gamesaved = True # Whether or not current game is saved self.autosave = False # Whether or not to automatically save after each action + self.no_save = False #Temporary disable save (doesn't save with the file) self.prompt = "" # Prompt self.memory = "" # Text submitted to memory field self.authornote = "" # Text submitted to Author's Note field @@ -541,27 +563,30 @@ class story_settings(settings): self.max_authornote_length = 512 self.prompt_in_ai = False self.context = [] + self.last_story_load = None def save_story(self): - print("Saving") - save_name = self.story_name if self.story_name != "" else "untitled" - adder = "" - while True: - if os.path.exists("stories/{}{}_v2.json".format(save_name, adder)): - with open("stories/{}{}_v2.json".format(save_name, adder), "r") as f: - temp = json.load(f) - if 'story_id' in temp: - if self.story_id != temp['story_id']: - adder = 0 if adder == "" else adder+1 + if not self.no_save: + if self.prompt != "" or self.memory != "" or self.authornote != "" or len(self.actions) > 0 or len(self.worldinfo_v2) > 0: + print("Saving") + save_name = self.story_name if self.story_name != "" else "untitled" + adder = "" + while True: + if os.path.exists("stories/{}{}_v2.json".format(save_name, adder)): + with open("stories/{}{}_v2.json".format(save_name, adder), "r") as f: + temp = json.load(f) + if 'story_id' in temp: + if self.story_id != temp['story_id']: + adder = 0 if adder == "" else adder+1 + else: + break + else: + adder = 0 if adder == "" else adder+1 else: break - else: - adder = 0 if adder == "" else adder+1 - else: - break - with open("stories/{}{}_v2.json".format(save_name, adder), "w") as settings_file: - settings_file.write(self.to_json()) - self.gamesaved = True + with open("stories/{}{}_v2.json".format(save_name, adder), "w") as settings_file: + settings_file.write(self.to_json()) + self.gamesaved = True def reset(self): self.socketio.emit("reset_story", {}, broadcast=True, room="UI_2") @@ -735,6 +760,7 @@ class system_settings(settings): self.alt_gen = False # Use the calc_ai_text method for generating text to go to the AI self.theme_list = [".".join(f.split(".")[:-1]) for f in os.listdir("./themes") if os.path.isfile(os.path.join("./themes", f))] self.cloudflare_link = "" + self.story_loads = {} #dict of when each story was last loaded def __setattr__(self, name, value): @@ -855,6 +881,8 @@ class KoboldStoryRegister(object): self.actions = temp self.set_game_saved() self.recalc_token_length() + self.story_settings.save_story() + def append(self, text): self.clear_unused_options() @@ -1170,6 +1198,9 @@ class KoboldWorldInfo(object): def __getitem__(self, i): return self.self.world_info[i].copy() + def __len__(self): + return len(self.world_info) + def recalc_token_length(self): if self.tokenizer is not None: for uid in self.world_info: