diff --git a/aiserver.py b/aiserver.py index aa93a301..e1a7ac12 100644 --- a/aiserver.py +++ b/aiserver.py @@ -6,6 +6,7 @@ #==================================================================# # External packages +from dataclasses import dataclass import eventlet eventlet.monkey_patch(all=True, thread=False, os=False) import os @@ -239,6 +240,189 @@ class Send_to_socketio(object): def flush(self): pass + +@dataclass +class ImportBuffer: + # Singleton!!! + prompt: Optional[str] = None + memory: Optional[str] = None + authors_note: Optional[str] = None + notes: Optional[str] = None + world_infos: Optional[dict] = None + + @dataclass + class PromptPlaceholder: + id: str + order: Optional[int] = None + default: Optional[str] = None + title: Optional[str] = None + description: Optional[str] = None + value: Optional[str] = None + + def to_json(self) -> dict: + return {key: getattr(self, key) for key in [ + "id", + "order", + "default", + "title", + "description" + ]} + + def request_client_configuration(self, placeholders: List[PromptPlaceholder]) -> None: + emit("request_prompt_config", [x.to_json() for x in placeholders], broadcast=False, room="UI_2") + + def extract_placeholders(self, text: str) -> List[PromptPlaceholder]: + placeholders = [] + + for match in re.finditer(r"\${(.*?)}", text): + ph_text = match.group(1) + + try: + ph_order, ph_text = ph_text.split("#") + except ValueError: + ph_order = None + + if "[" not in ph_text: + ph_id = ph_text + + # Already have it! + if any([x.id == ph_id for x in placeholders]): + continue + + # Apparently, none of these characters are supported: + # "${}[]#:@^|", however I have found some prompts using these, + # so they will be allowed. + for char in "${}[]": + if char in ph_text: + print("[eph] Weird char") + print(f"Char: {char}") + print(f"Ph_id: {ph_id}") + return + + placeholders.append(self.PromptPlaceholder( + id=ph_id, + order=int(ph_order) if ph_order else None, + )) + continue + + ph_id, _ = ph_text.split("[") + ph_text = ph_text.replace(ph_id, "", 1) + + # Already have it! + if any([x.id == ph_id for x in placeholders]): + continue + + # Match won't match it for some reason (???), so we use finditer and next() + try: + default_match = next(re.finditer(r"\[(.*?)\]", ph_text)) + except StopIteration: + print("[eph] Weird brackets") + return placeholders + + ph_default = default_match.group(1) + ph_text = ph_text.replace(default_match.group(0), "") + + try: + ph_title, ph_desc = ph_text.split(":") + except ValueError: + ph_title = ph_text or None + ph_desc=None + + placeholders.append(self.PromptPlaceholder( + id=ph_id, + order=int(ph_order) if ph_order else None, + default=ph_default, + title=ph_title, + description=ph_desc + )) + return placeholders + + def _replace_placeholders(self, text: str, ph_ids: dict): + for ph_id, value in ph_ids.items(): + pattern = "\${(?:\d#)?%s.*?}" % re.escape(ph_id) + for ph_text in re.findall(pattern, text): + text = text.replace(ph_text, value) + return text + + def replace_placeholders(self, ph_ids: dict): + self.prompt = self._replace_placeholders(self.prompt, ph_ids) + self.memory = self._replace_placeholders(self.memory, ph_ids) + self.authors_note = self._replace_placeholders(self.authors_note, ph_ids) + + for i in range(len(self.world_infos)): + for key in ["content", "comment"]: + self.world_infos[i][key] = self._replace_placeholders(self.world_infos[i][key]) + + def from_club(self, club_id): + # Maybe it is a better to parse the NAI Scenario (if available), it has more data + r = requests.get(f"https://aetherroom.club/api/{club_id}") + + if not r.ok: + # TODO: Show error message on client + print(f"[import] Got {r.status_code} on request to club :^(") + return + + j = r.json() + + self.prompt = j["promptContent"] + self.memory = j["memory"] + self.authors_note = j["authorsNote"] + self.notes = j["description"] + + self.world_infos = [] + + for wi in j["worldInfos"]: + self.world_infos.append({ + "key_list": wi["keysList"], + "keysecondary": [], + "content": wi["entry"], + "comment": "", + "folder": wi.get("folder", None), + "num": 0, + "init": True, + "selective": wi.get("selective", False), + "constant": wi.get("constant", False), + "uid": None, + }) + + placeholders = self.extract_placeholders(self.prompt) + if not placeholders: + self.commit() + else: + self.request_client_configuration(placeholders) + + def commit(self): + # Push buffer story to actual story + exitModes() + + koboldai_vars.create_story("") + koboldai_vars.gamestarted = True + koboldai_vars.prompt = self.prompt + koboldai_vars.memory = self.memory or "" + koboldai_vars.authornote = self.authors_note or "" + koboldai_vars.notes = self.notes + + for wi in self.world_infos: + koboldai_vars.worldinfo_v2.add_item( + wi["key_list"][0], + wi["key_list"], + wi.get("keysecondary", []), + wi.get("folder", "root"), + wi.get("constant", False), + wi["content"], + wi.get("comment", "") + ) + + # Reset current save + koboldai_vars.savedir = getcwd()+"\\stories" + + # Refresh game screen + koboldai_vars.laststory = None + setgamesaved(False) + sendwi() + refresh_story() + +import_buffer = ImportBuffer() # Set logging level to reduce chatter from Flask import logging @@ -286,6 +470,16 @@ def emit(*args, **kwargs): except AttributeError: return socketio.emit(*args, **kwargs) +#replacement for tpool.execute to maintain request contexts +def replacement_tpool_execute(function, *args, **kwargs): + temp = {} + socketio.start_background_task(tpool.execute_2, function, temp, *args, **kwargs).join() + print(temp) + return temp[1] + +def replacement_tpool_execute_2(function, temp, *args, **kwargs): + temp[1] = function(*args, **kwargs) + # marshmallow/apispec setup from apispec import APISpec from apispec.ext.marshmallow import MarshmallowPlugin @@ -1181,6 +1375,7 @@ def get_model_info(model, directory=""): default_url = None models_on_url = False multi_online_models = False + show_online_model_select=False gpu_count = torch.cuda.device_count() gpu_names = [] for i in range(gpu_count): @@ -1189,6 +1384,7 @@ def get_model_info(model, directory=""): url = True elif model == 'CLUSTER': models_on_url = True + show_online_model_select=True url = True key = True default_url = 'https://koboldai.net' @@ -1206,6 +1402,7 @@ def get_model_info(model, directory=""): default_url = js['oaiurl'] get_cluster_models({'model': model, 'key': key_value, 'url': default_url}) elif model in [x[1] for x in model_menu['apilist']]: + show_online_model_select=True if path.exists("settings/{}.v2_settings".format(model)): with open("settings/{}.v2_settings".format(model), "r") as file: # Check if API key exists @@ -1254,7 +1451,7 @@ def get_model_info(model, directory=""): 'gpu':gpu, 'layer_count':layer_count, 'breakmodel':breakmodel, 'multi_online_models': multi_online_models, 'default_url': default_url, 'disk_break_value': disk_blocks, 'disk_break': utils.HAS_ACCELERATE, 'break_values': break_values, 'gpu_count': gpu_count, - 'url': url, 'gpu_names': gpu_names, 'models_on_url': models_on_url}, broadcast=False, room="UI_2") + 'url': url, 'gpu_names': gpu_names, 'models_on_url': models_on_url, 'show_online_model_select': show_online_model_select}, broadcast=False, room="UI_2") @@ -1927,6 +2124,7 @@ def load_model(use_gpu=True, gpu_layers=None, disk_layers=None, initial_load=Fal if not utils.HAS_ACCELERATE: disk_layers = None koboldai_vars.reset_model() + koboldai_vars.cluster_requested_models = online_model koboldai_vars.noai = False if not use_breakmodel_args: set_aibusy(True) @@ -1990,7 +2188,7 @@ def load_model(use_gpu=True, gpu_layers=None, disk_layers=None, initial_load=Fal koboldai_vars.configname = f"{koboldai_vars.model}_{online_model.replace('/', '_')}" if path.exists(get_config_filename()): changed=False - with open("settings/{}.v2_settings".format(koboldai_vars.model), "r") as file: + with open(get_config_filename(), "r") as file: # Check if API key exists js = json.load(file) if 'online_model' in js: @@ -5134,6 +5332,7 @@ def sendtoapi(txt, min, max): errmsg = "KoboldAI API Error: Failed to get a reply from the server. Please check the console." print("{0}{1}{2}".format(colors.RED, json.dumps(js, indent=2), colors.END)) emit('from_server', {'cmd': 'errmsg', 'data': errmsg}, broadcast=True) + emit("error", errmsg, broadcast=True, room="UI_2") set_aibusy(0) return @@ -6986,6 +7185,7 @@ def ui2_connect(): #Send all variables to client koboldai_vars.send_to_ui() UI_2_load_cookies() + UI_2_theme_list_refresh(None) pass #==================================================================# @@ -7271,6 +7471,11 @@ def get_files_sorted(path, sort, desc=False): return [key[0] for key in sorted(data.items(), key=lambda kv: (kv[1], kv[0]), reverse=desc)] +@socketio.on("configure_prompt") +def UI_2_configure_prompt(data): + import_buffer.replace_placeholders(data) + import_buffer.commit() + #==================================================================# # Event triggered when browser SocketIO detects a variable change #==================================================================# @@ -7538,7 +7743,7 @@ def get_story_listing_data(item_full_path, item, valid_selection): if js.get("file_version", 1) == 1: return [title, action_count, last_loaded] - action_count = 0 if js['actions']['action_count'] == -1 else js['actions']['action_count'] + action_count = js['actions']['action_count']+1 return [title, action_count, last_loaded] @@ -7929,7 +8134,8 @@ def UI_2_unload_userscripts(data): def UI_2_load_aidg_club(data): if koboldai_vars.debug: print("Load aidg.club: {}".format(data)) - importAidgRequest(data) + import_buffer.from_club(data) + # importAidgRequest(data) #==================================================================# @@ -8033,6 +8239,13 @@ def get_model_size(model_name): elif "1.3B" in model_name: return "1.3B" +#==================================================================# +# Save New Preset +#==================================================================# +@socketio.on('save_revision') +def UI_2_save_revision(data): + koboldai_vars.save_revision() + #==================================================================# # Test #==================================================================# diff --git a/environments/huggingface.yml b/environments/huggingface.yml index 7261cada..47eb2e97 100644 --- a/environments/huggingface.yml +++ b/environments/huggingface.yml @@ -23,7 +23,7 @@ dependencies: - flask-cloudflared - flask-ngrok - lupa==1.10 - - transformers>=4.20.1 + - transformers==4.21.3 - accelerate - flask-session - python-socketio[client] \ No newline at end of file diff --git a/koboldai_settings.py b/koboldai_settings.py index 931039c0..b4553fdd 100644 --- a/koboldai_settings.py +++ b/koboldai_settings.py @@ -78,10 +78,12 @@ class koboldai_vars(object): 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() + def save_revision(self): + self._story_settings['default'].save_revision() + def create_story(self, story_name, json_data=None): #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 @@ -91,7 +93,7 @@ class koboldai_vars(object): else: self._story_settings[story_name] = story_settings(self.socketio) if json_data is not None: - self.load_story(sotry_name, json_data) + self.load_story(story_name, json_data) self._story_settings['default'].send_to_ui() def story_list(self): @@ -198,7 +200,7 @@ class koboldai_vars(object): authors_note_final = self.authornotetemplate.replace("<|>", self.authornote) used_all_tokens = False for i in range(len(self.actions)-1, -1, -1): - if len(self.actions) - i == self.andepth and self.authornote != "": + if len(self.actions) - i - 1 == self.andepth and self.authornote != "": game_text = "{}{}".format(authors_note_final, game_text) game_context.insert(0, {"type": "authors_note", "text": authors_note_final}) if self.actions.actions[i]["Selected Text Length"]+used_tokens <= token_budget and not used_all_tokens: @@ -440,6 +442,8 @@ class model_settings(settings): self.selected_preset = "" self.uid_presets = [] self.default_preset = {} + self.cluster_requested_models = [] # The models which we allow to generate during cluster mode + #dummy class to eat the tqdm output class ignore_tqdm(object): @@ -504,7 +508,7 @@ 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'] + local_only_variables = ['socketio', 'tokenizer', 'koboldai_vars', 'no_save', 'revisions'] no_save_variables = ['socketio', 'tokenizer', 'koboldai_vars', 'context', 'no_save'] settings_name = "story" def __init__(self, socketio, koboldai_vars, tokenizer=None): @@ -567,6 +571,7 @@ class story_settings(settings): self.prompt_in_ai = False self.context = [] self.last_story_load = None + self.revisions = [] #must be at bottom self.no_save = False #Temporary disable save (doesn't save with the file) @@ -594,6 +599,12 @@ class story_settings(settings): settings_file.write(self.to_json()) self.gamesaved = True + def save_revision(self): + game = json.loads(self.to_json()) + del game['revisions'] + self.revisions.append(game) + self.gamesaved = False + def reset(self): self.no_save = True self.socketio.emit("reset_story", {}, broadcast=True, room="UI_2") diff --git a/static/koboldai.css b/static/koboldai.css index 8ac52911..32feed87 100644 --- a/static/koboldai.css +++ b/static/koboldai.css @@ -1114,6 +1114,7 @@ body { grid-template-columns: 30px auto var(--story_options_size) 30px; grid-template-rows: auto min-content 100px; } + .main-grid.settings_pinned { margin-left: var(--flyout_menu_width); grid-template-columns: 30px auto var(--story_options_size) 30px; @@ -1180,6 +1181,7 @@ body { padding: 0px 5px 10px 10px; vertical-align: bottom; overflow-y: scroll; + outline: none; -ms-overflow-style: none; /* IE and Edge */ scrollbar-width: none; /* Firefox */ } @@ -1326,10 +1328,10 @@ body { } #themetext{ - grid-area: textarea; height: 100%; width: 100%; padding: 5px; + grid-area: textarea; border-radius: var(--radius_inputbox); } @@ -1853,7 +1855,8 @@ body { /* Finder */ #finder-container, -#debug-file-container { +#debug-file-container, +#prompt-config-container { display: flex; justify-content: center; align-items: center; @@ -2059,6 +2062,66 @@ body { background-color: rgb(158, 2, 2); } +/* Prompt Config */ +#prompt-config { + display: flex; + flex-direction: column; + position: relative; + background-color: var(--popup_background_color); + width: 25%; + height: 75%; +} + +#prompt-config-header { + /* HACK: Need to add (or use) a theme color for this! */ + background-color: #212a33; + padding: 7px; +} + +#prompt-config-header > h3 { + margin-top: 5px; + margin-bottom: 5px; +} + +#prompt-config-placeholders { + display: flex; + flex-direction: column; + row-gap: 16px; + overflow-y: scroll; + padding: 7px; + padding-left: 14px +} + +.prompt-config-title { + display: block; + font-size: 20px; + font-weight: bold; +} + +.prompt-config-value { + padding: 7px; +} + +#prompt-config-done { + display: flex; + justify-content: center; + align-items: center; + + width: 100px; + height: 32px; + + position: absolute; + + right: 10px; + bottom: 10px; + + + cursor: pointer; + color: var(--button_text); + background-color: var(--button_background); + border-radius: var(--radius_settings_button); +} + /*---------------------------------- Global ------------------------------------------------*/ .hidden { display: none; @@ -2289,6 +2352,21 @@ h2 .material-icons-outlined { } } -.horde_trigger[model_model="ReadOnly"] { +.horde_trigger[model_model="ReadOnly"], +.horde_trigger[model_model="CLUSTER"] { display: none; +} + +.preset_area { + width: 100%; + padding: 10px; + text-align: center; +} + +.preset_area .settings_button { + transform: translateY(6px); +} + +input[type='range'] { + border: none !important; } \ No newline at end of file diff --git a/static/koboldai.js b/static/koboldai.js index a3326a7e..d0f2a34e 100644 --- a/static/koboldai.js +++ b/static/koboldai.js @@ -28,6 +28,7 @@ socket.on("error", function(data){show_error_message(data);}); socket.on('load_cookies', function(data){load_cookies(data)}); socket.on('load_tweaks', function(data){load_tweaks(data);}); socket.on("wi_results", updateWISearchListings); +socket.on("request_prompt_config", configurePrompt); //socket.onAny(function(event_name, data) {console.log({"event": event_name, "class": data.classname, "data": data});}); var presets = {}; @@ -115,13 +116,16 @@ function reset_story() { while (story_area.lastChild.id != 'story_prompt') { story_area.removeChild(story_area.lastChild); } - dummy_span = document.createElement("span"); + dummy_span = document.createElement("div"); dummy_span.id = "Delete Me"; + dummy_span.classList.add("noselect"); + document.getElementById("Selected Text").setAttribute("contenteditable", "false"); text = ""; for (i=0;i<154;i++) { text += "\xa0 "; } dummy_span.textContent = text; + dummy_span.setAttribute("contenteditable", false); story_area.append(dummy_span); var option_area = document.getElementById("Select Options"); while (option_area.firstChild) { @@ -310,6 +314,8 @@ function do_prompt(data) { if (document.getElementById("Delete Me")) { document.getElementById("Delete Me").remove(); } + //enable editing + document.getElementById("Selected Text").setAttribute("contenteditable", "true"); } else { document.getElementById('input_text').placeholder = "Enter Prompt Here (shift+enter for new line)"; document.getElementById('input_text').disabled = false; @@ -1062,6 +1068,7 @@ function show_model_menu(data) { document.getElementById("modelurl").classList.add("hidden"); document.getElementById("use_gpu_div").classList.add("hidden"); document.getElementById("modellayers").classList.add("hidden"); + document.getElementById("oaimodel").classList.add("hidden"); var model_layer_bars = document.getElementById('model_layer_bars'); while (model_layer_bars.firstChild) { model_layer_bars.removeChild(model_layer_bars.firstChild); @@ -1169,15 +1176,27 @@ function selected_model_info(data) { document.getElementById("modelurl").classList.add("hidden"); } + //default URL loading + if (data.default_url != null) { + document.getElementById("modelurl").value = data.default_url; + } + //change model loading on url if needed if (data.models_on_url) { - document.getElementById("modelurl").onchange = function () {socket.emit('get_cluster_models', {'key': document.getElementById("modelkey").value, 'url': this.value});}; - document.getElementById("modelkey").onchange = function () {socket.emit('get_cluster_models', {'key': this.value, 'url': document.getElementById("modelurl").value});}; + document.getElementById("modelurl").onchange = function () {socket.emit('get_cluster_models', {'model': document.getElementById('btn_loadmodelaccept').getAttribute('selected_model'), 'key': document.getElementById("modelkey").value, 'url': this.value});}; + document.getElementById("modelkey").onchange = function () {socket.emit('get_cluster_models', {'model': document.getElementById('btn_loadmodelaccept').getAttribute('selected_model'), 'key': this.value, 'url': document.getElementById("modelurl").value});}; } else { document.getElementById("modelkey").ochange = function () {socket.emit('OAI_Key_Update', {'model': document.getElementById('btn_loadmodelaccept').getAttribute('selected_model'), 'key': this.value});}; document.getElementById("modelurl").ochange = null; } + //show model select for APIs + if (data.show_online_model_select) { + document.getElementById("oaimodel").classList.remove("hidden"); + } else { + document.getElementById("oaimodel").classList.add("hidden"); + } + //Multiple Model Select? if (data.multi_online_models) { document.getElementById("oaimodel").setAttribute("multiple", ""); @@ -1372,15 +1391,28 @@ function load_model() { var path = ""; } + let selected_models = []; + for (item of document.getElementById("oaimodel").selectedOptions) { + selected_models.push(item.value); + } + if (selected_models == []) { + selected_models = ""; + } else if (selected_models.length == 1) { + selected_models = selected_models[0]; + } + message = {'model': model, 'path': path, 'use_gpu': document.getElementById("use_gpu").checked, 'key': document.getElementById('modelkey').value, 'gpu_layers': gpu_layers.join(), 'disk_layers': disk_layers, 'url': document.getElementById("modelurl").value, - 'online_model': document.getElementById("oaimodel").value}; + 'online_model': selected_models}; socket.emit("load_model", message); document.getElementById("loadmodelcontainer").classList.add("hidden"); } function world_info_entry_used_in_game(data) { + if (!(data.uid in world_info_data)) { + world_info_data[data.uid] = {}; + } world_info_data[data.uid]['used_in_game'] = data['used_in_game']; world_info_card = document.getElementById("world_info_"+data.uid); if (data.used_in_game) { @@ -2087,34 +2119,54 @@ function select_game_text(event) { if (document.selection) { if (document.selection.createRange().parentElement().id == 'story_prompt') { new_selected_game_chunk = document.selection.createRange().parentElement(); + } else if (document.selection.createRange().parentElement().id == 'gamescreen') { + new_selected_game_chunk = null; + console.log("Do nothing"); } else { new_selected_game_chunk = document.selection.createRange().parentElement().parentElement(); } } else { - if (window.getSelection().anchorNode.parentNode.id == 'story_prompt') { - new_selected_game_chunk = window.getSelection().anchorNode.parentNode; + if(window.getSelection().anchorNode.parentNode) { + if (window.getSelection().anchorNode.parentNode.id == 'story_prompt') { + new_selected_game_chunk = window.getSelection().anchorNode.parentNode; + } else if (window.getSelection().anchorNode.parentNode.id == "gamescreen") { + new_selected_game_chunk = null; + console.log("Do nothing"); + } else { + new_selected_game_chunk = window.getSelection().anchorNode.parentNode.parentNode; + } } else { - new_selected_game_chunk = window.getSelection().anchorNode.parentNode.parentNode; + new_selected_game_chunk = null; } } //if we've moved to a new game chunk we need to save the old chunk if ((new_selected_game_chunk != selected_game_chunk) && (selected_game_chunk != null)) { edit_game_text(); } - if (new_selected_game_chunk != selected_game_chunk) { - selected_game_chunk = new_selected_game_chunk - } - //set editting class - for (item of document.getElementsByClassName("editing")) { - item.classList.remove("editing"); + //Check to see if new selection is a game chunk or something else + + if ((new_selected_game_chunk == null) || (((new_selected_game_chunk.id == "story_prompt") || (new_selected_game_chunk.id.slice(0,20) == "Selected Text Chunk ")) && (document.activeElement.isContentEditable))) { + if (new_selected_game_chunk != selected_game_chunk) { + for (item of document.getElementsByClassName("editing")) { + item.classList.remove("editing"); + } + selected_game_chunk = new_selected_game_chunk; + selected_game_chunk.classList.add("editing"); + } + + } else { + selected_game_chunk = null; + for (item of document.getElementsByClassName("editing")) { + item.classList.remove("editing"); + } + window.getSelection().removeAllRanges() } - selected_game_chunk.classList.add("editing"); } } function edit_game_text() { - if ((selected_game_chunk != null) && (selected_game_chunk.textContent != selected_game_chunk.original_text)) { + if ((selected_game_chunk != null) && (selected_game_chunk.textContent != selected_game_chunk.original_text) && (selected_game_chunk != document.getElementById("Delete Me"))) { if (selected_game_chunk.id == "story_prompt") { sync_to_server(selected_game_chunk); } else { @@ -2125,28 +2177,6 @@ function edit_game_text() { } } -function clear_edit_game_text_tag() { - let id = null; - if (document.selection) { - if (document.selection.createRange().parentElement().id == 'story_prompt') { - id = document.selection.createRange().parentElement().id; - } else { - id = document.selection.createRange().parentElement().parentElement().id; - } - } else { - if (window.getSelection().anchorNode.parentNode.id == 'story_prompt') { - id = window.getSelection().anchorNode.parentNode.id; - } else { - id = window.getSelection().anchorNode.parentNode.parentNode.id; - } - } - if ((id != 'story_prompt') && (id.slice(0, 20) != "Selected Text Chunk ")) { - for (item of document.getElementsByClassName("editing")) { - item.classList.remove("editing"); - } - } -} - function save_preset() { socket.emit("save_new_preset", {"preset": document.getElementById("new_preset_name").value, "description": document.getElementById("new_preset_description").value}); document.getElementById('save_preset').classList.add('hidden'); @@ -3398,6 +3428,51 @@ async function downloadDebugFile(redact=true) { downloadString(JSON.stringify(debug_info, null, 4), "kobold_debug.json"); } +function configurePrompt(placeholderData) { + console.log(placeholderData); + const container = document.querySelector("#prompt-config-container"); + container.classList.remove("hidden"); + + const placeholders = document.querySelector("#prompt-config-placeholders"); + + for (const phData of placeholderData) { + let placeholder = $e("div", placeholders, {classes: ["prompt-config-ph"]}); + + + // ${character.name} is an AI Dungeon thing, although I believe NAI + // supports it as well. Many prompts use it. I think this is the only + // hardcoded thing like this. + let titleText = phData.title || phData.id; + if (titleText === "character.name") titleText = "Character Name"; + + let title = $e("span", placeholder, {classes: ["prompt-config-title"], innerText: titleText}); + + if (phData.description) $e("span", placeholder, { + classes: ["prompt-config-desc", "help_text"], + innerText: phData.description + }); + + let input = $e("input", placeholder, { + classes: ["prompt-config-value"], + value: phData.default || "", + placeholder: phData.default || "", + "placeholder-id": phData.id + }); + } +} + +function sendPromptConfiguration() { + let data = {}; + for (const configInput of document.querySelectorAll(".prompt-config-value")) { + data[configInput.getAttribute("placeholder-id")] = configInput.value; + } + + socket.emit("configure_prompt", data); + + document.querySelector("#prompt-config-container").classList.add("hidden"); + $(".prompt-config-ph").remove(); +} + function loadNAILorebook(data, filename) { let lorebookVersion = data.lorebookVersion; let wi_data = {folders: {[filename]: []}, entries: {}}; diff --git a/templates/index_new.html b/templates/index_new.html index 70e2d7f1..82171801 100644 --- a/templates/index_new.html +++ b/templates/index_new.html @@ -47,9 +47,10 @@

Disconnected

-
+
- +
+                                                                            @@ -57,12 +58,15 @@                                                                            - + +
+
+
diff --git a/templates/popups.html b/templates/popups.html index 4a0a353a..2ea3b71b 100644 --- a/templates/popups.html +++ b/templates/popups.html @@ -113,7 +113,7 @@
@@ -192,4 +192,18 @@
+ + + + \ No newline at end of file diff --git a/templates/settings flyout.html b/templates/settings flyout.html index 75212d09..71ef97a4 100644 --- a/templates/settings flyout.html +++ b/templates/settings flyout.html @@ -107,12 +107,12 @@ Download debug dump