diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..b512c09d4 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules \ No newline at end of file diff --git a/public/KoboldAI Worlds/Sample.json b/public/KoboldAI Worlds/Sample.json new file mode 100644 index 000000000..082684e84 --- /dev/null +++ b/public/KoboldAI Worlds/Sample.json @@ -0,0 +1,30 @@ +{ + "folders": { + "Sample Folder": [ + 0, + 1 + ] + }, + "entries": { + "0": { + "uid": 0, + "title": "AAA", + "key": [ "AAA" ], + "keysecondary": [ ], + "constant": false, + "content": "AAA is a city where BBB lives.", + "comment": "AAA definition", + "selective": true + }, + "1": { + "uid": 1, + "title": "BBB", + "key": [ "BBB" ], + "keysecondary": [ ], + "constant": false, + "content": "BBB is a 21-year old female student of CCC academy.", + "comment": "BBB definition", + "selective": true + } + } +} \ No newline at end of file diff --git a/public/index.html b/public/index.html index 7d4b0f3fa..b16c1cabf 100644 --- a/public/index.html +++ b/public/index.html @@ -1,6 +1,17 @@ + + + + + + + + + + + @@ -37,7 +48,7 @@ create_date: 0, mes: '\n*You went inside. The air smelled of fried meat, tobacco and a hint of wine. A dim light was cast by candles, and a fire crackled in the fireplace. It seems to be a very pleasant place. Behind the wooden bar is an elf waitress, she is smiling. Her ears are very pointy, and there is a twinkle in her eye. She wears glasses and a white apron. As soon as she noticed you, she immediately came right up close to you.*\n\n' + ' Hello there! How is your evening going?\n' + - '\n@@@TavernAI v'+VERSION+'@@@
Cloud




' + '\n@@@TavernAI v'+VERSION+'@@@
Cloud




' }]; var chat_create_date = 0; @@ -54,6 +65,7 @@ var is_mes_reload_avatar = false; var is_advanced_char_open = false; + var is_world_edit_open = false; var menu_type = '';//what is selected in the menu var selected_button = '';//which button pressed @@ -67,6 +79,8 @@ var create_save_mes_example = ''; var timerSaveEdit; + var timerKoboldSync; + var timerWorldSave; var durationSaveEdit = 200; //animation right menu var animation_rm_duration = 200; @@ -102,6 +116,13 @@ var user_avatar = 'you.png'; var temp = 0.5; var amount_gen = 80; + var kobold_world = null; + var koboldai_world_names; + var kobold_world_synced = false; + var kobold_sync_failed = false; + var kobold_is_united = false; + var kobold_world_data = null; + var imported_world_name = ''; var max_context = 2048;//2048; var rep_pen = 1; var rep_pen_size = 100; @@ -136,6 +157,29 @@ var css_send_form_display = $('
').css('display'); var colab_ini_step = 1; + + // VARIABLES DONE NOW THE REAL CODE + // + // + // + + // Dealing with Textarea Height + + //function textAreaAdjust(element) { + // element.style.height = "1px"; + // element.style.height = (25+element.scrollHeight)+"px"; + //} + //function calcHeight(value) { + // let numberOfLineBreaks = (value.match(/\n/g) || []).length; + // // min-height + lines x line-height + padding + border + // let newHeight = 25 + numberOfLineBreaks * 25 + 6 + 1; + // return newHeight; + //} + //let textarea = document.querySelector(".resize-ta"); + //textarea.addEventListener("keyup", () => { + //textarea.style.height = calcHeight(textarea.value) + "px"; + //}); + setInterval(function() { switch(colab_ini_step){ case 0: @@ -157,6 +201,9 @@ } }, 500); ///////////// + + + getSettings("def"); getLastVersion(); @@ -197,14 +244,13 @@ is_get_status_novel = false; }else{ $("#online_status_indicator").css("background-color", "black"); - $("#online_status").css("opacity", 0.0); + $("#online_status").css("display", "none"); $("#online_status_text").html(""); $("#online_status_indicator2").css("background-color", "green"); $("#online_status_text2").html(online_status); $("#online_status_indicator3").css("background-color", "green"); $("#online_status_text3").html(online_status); } - } async function getLastVersion(){ @@ -264,6 +310,9 @@ online_status = data.result; if(online_status == undefined){ online_status = 'no_connection'; + + kobold_world_synced = false; + updateWorldStatus(); } if(online_status.toLowerCase().indexOf('pygmalion') != -1){ is_pygmalion = true; @@ -276,6 +325,7 @@ resultCheckStatus(); if(online_status !== 'no_connection'){ var checkStatusNow = setTimeout(getStatus, 3000);//getStatus(); + syncKoboldWorldInfo(false); } }, error: function (jqXHR, exception) { @@ -283,12 +333,18 @@ console.log(jqXHR); online_status = 'no_connection'; + // invalidate world info when losing connection to kobold + kobold_world_synced = false; + updateWorldStatus(); + resultCheckStatus(); } }); }else{ if(is_get_status_novel != true){ online_status = 'no_connection'; + kobold_world_synced = false; + updateWorldStatus(); } } } @@ -591,7 +647,7 @@ //$("#send_textarea").blur(); $( "#send_but" ).css("display", "none"); - $( "#loading_mes" ).css("display", "block"); + $( "#loading_mes" ).css("display", "inline-block"); var storyString = ""; @@ -909,7 +965,8 @@ var generate_data; if(main_api == 'kobold'){ - var generate_data = {prompt: finalPromt, gui_settings: true,max_length: amount_gen,temperature: temp, max_context_length: max_context}; + const use_world_info = Boolean(kobold_world && kobold_world_synced); + var generate_data = {prompt: finalPromt, gui_settings: true,max_length: amount_gen,temperature: temp, max_context_length: max_context, use_world_info}; if(preset_settings != 'gui'){ var this_settings = koboldai_settings[koboldai_setting_names[preset_settings]]; @@ -950,7 +1007,8 @@ s4:this_settings.sampler_order[3], s5:this_settings.sampler_order[4], s6:this_settings.sampler_order[5], - s7:this_settings.sampler_order[6] + s7:this_settings.sampler_order[6], + use_world_info: use_world_info, }; } } @@ -1048,7 +1106,7 @@ getMessage = $.trim(getMessage); chat[chat.length-1]['mes'] = getMessage; addOneMessage(chat[chat.length-1]); - $( "#send_but" ).css("display", "block"); + $( "#send_but" ).css("display", "inline"); $( "#loading_mes" ).css("display", "none"); saveChat(); }else{ @@ -1056,7 +1114,7 @@ Generate('force_name2'); } }else{ - $( "#send_but" ).css("display", "block"); + $( "#send_but" ).css("display", "inline"); $( "#loading_mes" ).css("display", "none"); } }, @@ -1064,7 +1122,7 @@ $("#send_textarea").removeAttr('disabled'); is_send_press = false; - $( "#send_but" ).css("display", "block"); + $( "#send_but" ).css("display", "inline"); $( "#loading_mes" ).css("display", "none"); console.log(exception); console.log(jqXHR); @@ -1212,6 +1270,9 @@ $( "#rm_button_characters" ).children("h2").css(deselected_button_style); $( "#rm_button_settings" ).children("h2").css(seleced_button_style); $( "#rm_button_selected_ch" ).children("h2").css(deselected_button_style); + + // Dumb call, but won't need an interval + updateWorldStatus(); }); $( "#rm_button_characters" ).click(function() { selected_button = 'characters'; @@ -1517,6 +1578,17 @@ } }); } + if (popup_type === 'world_imported' && imported_world_name) { + koboldai_world_names.forEach((item, i) => { + if (item === imported_world_name) { + $('#world_info').val(i).change(); + } + }) + imported_world_name = ''; + } + if (popup_type === 'del_world' && kobold_world) { + deleteWorldInfo(kobold_world); + } if(popup_type == 'new_chat' && this_chid != undefined && menu_type != "create"){//Fix it; New chat doesn't create while open create character menu clearChat(); chat.length = 0; @@ -1542,11 +1614,13 @@ $("#dialogue_popup_cancel").css("display", "none"); break; + case 'world_imported': case 'new_chat': $("#dialogue_popup_ok").css("background-color", "#191b31CC"); $("#dialogue_popup_ok").text("Yes"); break; + case 'del_world': default: $("#dialogue_popup_ok").css("background-color", "#791b31"); $("#dialogue_popup_ok").text("Delete"); @@ -1842,6 +1916,7 @@ is_get_status = true; is_api_button_press = true; getStatus(); + detectUnitedKobold(); } }); @@ -1940,6 +2015,23 @@ }); + $("#world_info").change(function() { + const selectedWorld = $('#world_info').find(":selected").val(); + kobold_world_synced = false; + kobold_sync_failed = false; + kobold_world = null; + + if (selectedWorld !== 'None') { + const worldIndex = Number(selectedWorld); + kobold_world = !isNaN(worldIndex) ? koboldai_world_names[worldIndex] : null; + } + + hideWorldEditor(); + syncKoboldWorldInfo(true); + saveSettings(); + updateWorldStatus(); + }); + $( "#settings_perset" ).change(function() { if($('#settings_perset').find(":selected").val() != 'gui'){ @@ -2018,6 +2110,7 @@ main_api = 'kobold'; $('#max_context_block').css('display', 'block'); $('#amount_gen_block').css('display', 'block'); + $('#world_info_block').css('display', 'flex'); } if($('#main_api').find(":selected").val() == 'novel'){ $('#kobold_api').css("display", "none"); @@ -2025,7 +2118,10 @@ main_api = 'novel'; $('#max_context_block').css('display', 'none'); $('#amount_gen_block').css('display', 'none'); + $('#world_info_block').css('display', 'none'); } + + updateWorldStatus(); } async function getUserAvatars(){ const response = await fetch("/getuseravatars", { @@ -2050,7 +2146,100 @@ } } - + + function updateWorldStatus() { + if($('#world_info_block').is(':visible') && kobold_world) { + $('#world_info_edit_button').show(); + $('#world_status').show(); + + if (kobold_world_synced) { + $("#world_status_indicator").css("background-color", "green"); + $("#world_status_text").html("Synchronized with KoboldAI") + } + else { + let statusText = online_status === 'no_connection' + ? "Waiting for connection" + : "Synchronizing..."; + + if (kobold_sync_failed) { + statusText = "Synchronization failed (see console)"; + } + + $("#world_status_text").html(statusText); + $("#world_status_indicator").css("background-color", "red"); + } + + if (kobold_is_united) { + $("#world_status_text").html('KoboldAI United detected. WI may not work as intended.
If experiencing issues, please select "None".
'); + } + } else { + $('#world_status').hide(); + $('#world_info_edit_button').hide(); + } + } + + async function detectUnitedKobold() { + if (!api_server || main_api !== 'kobold') { + return; + } + + // If we can reach Kobold's new ui, then it should be United branch + kobold_is_united = false; + try { + const kobold_united_ui2 = api_server.replace('/api', '/new_ui'); + const response = await fetch(kobold_united_ui2, { method: 'HEAD'}); + + if (response.ok && response.status == 200) { + kobold_is_united = true; + } + } + catch { + // empty catch + } + } + + async function syncKoboldWorldInfo(force) { + // Don't sync if no world selected or if synced and not forcing + if (online_status === 'no_connection' || (!kobold_world && !force) || (kobold_world_synced && !force)) { + updateWorldStatus(); + return; + } + + const response = await fetch("/synckoboldworld", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ "name": kobold_world }) + }); + + if (response.ok) { + const syncData = await response.json(); + + if (syncData.ok) { + kobold_world_synced = true; + kobold_sync_failed = false; + } + + if (syncData.busy) { + // console.log('Sync API is busy. Retrying in 3sec'); + clearTimeout(timerKoboldSync); + timerKoboldSync = setTimeout(() => syncKoboldWorldInfo(force), 3000); + return; + } + } else { + kobold_sync_failed = true; + let responseLog = response.statusText; + + try { + var responseBody = await response.text(); + responseLog += ('\n' + responseBody); + } catch { + // empty catch + } + console.error(`Sync API response: ${responseLog}`); + } + + updateWorldStatus(); + } $(document).on('input', '#temp', function() { temp = $(this).val(); @@ -2269,7 +2458,6 @@ preset_settings = 'gui'; $("#settings_perset option[value=gui]").attr('selected', 'true'); } - } //User @@ -2282,6 +2470,26 @@ api_server = settings.api_server; $('#api_url_text').val(api_server); + + // world info settings + koboldai_world_names = data.koboldai_world_names?.length ? data.koboldai_world_names : []; + + if(settings.kobold_world != undefined) { + if (koboldai_world_names.includes(settings.kobold_world)) { + kobold_world = settings.kobold_world; + kobold_world_synced = false; + kobold_sync_failed = false; + } + } + + koboldai_world_names.forEach((item, i) => { + $('#world_info').append(``); + // preselect world if saved + if (item == kobold_world) { + $('#world_info').val(i).change(); + } + }); + // end world info settings } if(!is_checked_colab) isColab(); @@ -2319,7 +2527,8 @@ model_novel: model_novel, temp_novel: temp_novel, rep_pen_novel: rep_pen_novel, - rep_pen_size_novel: rep_pen_size_novel + rep_pen_size_novel: rep_pen_size_novel, + kobold_world: kobold_world,                  }), beforeSend: function(){ @@ -2334,7 +2543,8 @@ if(type === 'change_name'){ location.reload(); } - + + syncKoboldWorldInfo(false); }, error: function (jqXHR, exception) { console.log(exception); @@ -2752,8 +2962,421 @@ $('#load_select_chat_div').css('display', 'block'); }); + + //**************************WORLD INFO IMPORT EXPORT*************************// + $("#world_import_button" ).click(function() { + $("#world_import_file").click(); + }); + + $("#world_import_file").on("change", function(e) { + var file = e.target.files[0]; + + if (!file) { + return; + } + + const ext = file.name.match(/\.(\w+)$/); + if (!ext || (ext[1].toLowerCase() !== "json")){ + return; + } + + var formData = new FormData($("#form_world_import").get(0)); + + jQuery.ajax({ + type: 'POST', + url: '/importworldinfo', + data: formData, + beforeSend: () => {}, + cache: false, + contentType: false, + processData: false, + success: function(data){ + if (data.name) { + imported_world_name = data.name; + updateWorldInfoList(imported_world_name); + } + }, + error: (jqXHR, exception) => {}, + }); + + // Will allow to select the same file twice in a row + $('#form_world_import').trigger("reset"); + }); + + async function updateWorldInfoList(importedWorldName) { + var result = await fetch('/getsettings', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({}) + }); + + if (result.ok) { + var data = await result.json(); + koboldai_world_names = data.koboldai_world_names?.length ? data.koboldai_world_names : []; + $('#world_info').find('option[value!="None"]').remove(); + + koboldai_world_names.forEach((item, i) => { + $('#world_info').append(``); + }); + + if (importedWorldName) { + const indexOf = koboldai_world_names.indexOf(kobold_world); + $('#world_info').val(indexOf); + + popup_type = 'world_imported'; + callPopup('

World imported successfully! Select it now?

'); + } + } + } + + function download(content, fileName, contentType) { + var a = document.createElement("a"); + var file = new Blob([content], {type: contentType}); + a.href = URL.createObjectURL(file); + a.download = fileName; + a.click(); + } + + // World Info Editor + async function showWorldEditor() { + is_world_edit_open = true; + $('#world_popup_name').val(kobold_world); + $('#world_popup').css('display', 'flex'); + + if (kobold_world) { + const response = await fetch("/getworldinfo", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ name: kobold_world }) + }); + + if (response.ok) { + kobold_world_data = await response.json(); + displayWorldEntries(kobold_world_data); + } + } + } + + function hideWorldEditor() { + is_world_edit_open = false; + $('#world_popup').css('display', 'none'); + syncKoboldWorldInfo(true); + } + + function displayWorldEntries(data) { + $('#world_popup_entries_list').empty(); + + if (!data || !('entries' in data)) { + return; + } + + for (const entryUid in data.entries) { + const entry = data.entries[entryUid]; + appendWorldEntry(entry); + } + } + + function appendWorldEntry(entry) { + const template = $('#entry_edit_template .world_entry').clone(); + template.data('uid', entry.uid); + + // key + const keyInput = template.find('input[name="key"]'); + keyInput.data('uid', entry.uid); + keyInput.on('input', function () { + const uid = $(this).data('uid'); + const value = $(this).val(); + kobold_world_data.entries[uid].key = value.split(',').map(x => x.trim()).filter(x => x); + saveWorldInfo(); + }); + keyInput.val(entry.key.join(',')).trigger('input'); + + // keysecondary + const keySecondaryInput = template.find('input[name="keysecondary"]'); + keySecondaryInput.data('uid', entry.uid); + keySecondaryInput.on('input', function() { + const uid = $(this).data('uid'); + const value = $(this).val(); + kobold_world_data.entries[uid].keysecondary = value.split(',').map(x => x.trim()).filter(x => x); + saveWorldInfo(); + }); + keySecondaryInput.val(entry.keysecondary.join(',')).trigger('input'); + + // comment + const commentInput = template.find('input[name="comment"]'); + commentInput.data('uid', entry.uid); + commentInput.on('input', function() { + const uid = $(this).data('uid'); + const value = $(this).val(); + kobold_world_data.entries[uid].comment = value; + saveWorldInfo(); + }); + commentInput.val(entry.comment).trigger('input'); + + // content + const contentInput = template.find('textarea[name="content"]'); + contentInput.data('uid', entry.uid); + contentInput.on('input', function() { + const uid = $(this).data('uid'); + const value = $(this).val(); + kobold_world_data.entries[uid].content = value; + saveWorldInfo(); + + // count tokens + const numberOfTokens = encode(value).length; + $(this).closest('.world_entry').find('.world_entry_form_token_counter').html(numberOfTokens); + }); + contentInput.val(entry.content).trigger('input'); + + // selective + const selectiveInput = template.find('input[name="selective"]') + selectiveInput.data('uid', entry.uid); + selectiveInput.on('input', function() { + const uid = $(this).data('uid'); + const value = $(this).prop('checked'); + kobold_world_data.entries[uid].selective = value; + saveWorldInfo(); + + const keysecondary = $(this).closest('.world_entry').find('.keysecondary'); + value ? keysecondary.show() : keysecondary.hide(); + }); + selectiveInput.prop('checked', entry.selective).trigger('input'); + selectiveInput.siblings('.checkbox_fancy').click(function() { + $(this).siblings('input').click(); + }); + + + // constant + const constantInput = template.find('input[name="constant"]') + constantInput.data('uid', entry.uid); + constantInput.on('input', function() { + const uid = $(this).data('uid'); + const value = $(this).prop('checked'); + kobold_world_data.entries[uid].constant = value; + saveWorldInfo(); + }); + constantInput.prop('checked', entry.constant).trigger('input'); + constantInput.siblings('.checkbox_fancy').click(function() { + $(this).siblings('input').click(); + }); + + // display uid + template.find('.world_entry_form_uid_value').html(entry.uid); + + // delete button + const deleteButton = template.find('input.delete_entry_button'); + deleteButton.data('uid', entry.uid); + deleteButton.on('click', function() { + const uid = $(this).data('uid'); + deleteWorldInfoEntry(uid); + $(this).closest('.world_entry').remove(); + saveWorldInfo(); + }); + + template.appendTo('#world_popup_entries_list'); + return template; + } + + async function deleteWorldInfoEntry(uid) { + if (!kobold_world_data || !('entries' in kobold_world_data)) { + return; + } + + delete kobold_world_data.entries[uid]; + + if ('folders' in kobold_world_data) { + for (const folderName in kobold_world_data.folders) { + const folder = kobold_world_data.folders[folderName] + const index = folder.indexOf(Number(uid)); + + if (index !== -1) { + folder.splice(index, 1); + } + } + } + } + + function createWorldInfoEntry() { + const newEntryTemplate = { + key: [], + keysecondary: [], + comment: '', + content: '', + constant: false, + selective: false, + }; + const newUid = getFreeWorldEntryUid(); + + if (!Number.isInteger(newUid)) { + console.error("Couldn't assign UID to a new entry"); + return; + } + + const newEntry = { uid: newUid, ...newEntryTemplate }; + kobold_world_data.entries[newUid] = newEntry; + + if ('folders' in kobold_world_data) { + if (kobold_world in kobold_world_data.folders && Array.isArray(kobold_world_data.folders)) { + kobold_world_data.folders[kobold_world].push(newUid); + } else { + kobold_world_data.folders[kobold_world] = [newUid]; + } + } + + const entryTemplate = appendWorldEntry(newEntry); + entryTemplate.get(0).scrollIntoView({behavior: 'smooth'}); + } + + async function saveWorldInfo(immediately) { + if (!kobold_world || !kobold_world_data) { + return; + } + + async function _save() { + const response = await fetch("/editworldinfo", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ name: kobold_world, data: kobold_world_data }) + }); + + if (response.ok) { + kobold_world_synced = false; + } + } + + if (immediately) { + return await _save(); + } + + clearTimeout(timerWorldSave); + timerWorldSave = setTimeout(async () => await _save(), durationSaveEdit); + } + + async function renameWorldInfo() { + const oldName = kobold_world; + const newName = $('#world_popup_name').val(); + + if (oldName === newName) { + return; + } + + kobold_world = newName; + await saveWorldInfo(true); + await deleteWorldInfo(oldName, newName); + } + + async function deleteWorldInfo(worldInfoName, selectWorldName) { + if (!koboldai_world_names.includes(worldInfoName)) { + return; + } + + const response = await fetch("/deleteworldinfo", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ name: worldInfoName }) + }); + + if (response.ok) { + await updateWorldInfoList(); + + const selectedIndex = koboldai_world_names.indexOf(selectWorldName); + if (selectedIndex !== -1) { + $('#world_info').val(selectedIndex).change(); + } + else { + $('#world_info').val('None').change(); + } + + hideWorldEditor(); + } + } + + function getFreeWorldEntryUid() { + if (!kobold_world_data || !('entries' in kobold_world_data)) { + return null; + } + + const MAX_UID = 1_000_000; // <- should be safe enough :) + for (let uid = 0; uid < MAX_UID; uid++) { + if (uid in kobold_world_data.entries) { + continue; + } + return uid; + } + + return null; + } + + function getFreeWorldName() { + const MAX_FREE_NAME = 100_000; + for (let index = 1; index < MAX_FREE_NAME; index++) { + const newName = `New World (${index})`; + if (koboldai_world_names.includes(newName)) { + continue; + } + return newName; + } + + return undefined; + } + + async function createNewWorldInfo() { + const worldInfoTemplate = { folders: {}, entries: {} }; + const worldInfoName = getFreeWorldName(); + + if (!worldInfoName) { + return; + } + + kobold_world = worldInfoName; + kobold_world_data = { ...worldInfoTemplate }; + await saveWorldInfo(true); + await updateWorldInfoList(); + + const selectedIndex = koboldai_world_names.indexOf(worldInfoName); + if (selectedIndex !== -1) { + $('#world_info').val(selectedIndex).change(); + } + else { + $('#world_info').val('None').change(); + } + } + + $('#world_info_edit_button').click(() => { + is_world_edit_open ? hideWorldEditor() : showWorldEditor(); + }); + + $('#world_popup_export').click(() => { + if (kobold_world && kobold_world_data) { + const jsonValue = JSON.stringify(kobold_world_data); + const fileName = `${kobold_world}.json`; + download(jsonValue, fileName, 'application/json'); + } + }); + + $('#world_popup_delete').click(() => { + popup_type = 'del_world'; + callPopup('

Delete the World Info?

'); + }); + + $('#world_popup_new').click(() => { + createWorldInfoEntry(); + }); + + $('#world_cross').click(() => { + hideWorldEditor(); + }); + + $('#world_popup_name_button').click(() => { + renameWorldInfo(); + }); + + $('#world_create_button').click(() => { + createNewWorldInfo(); + }); });      + Tavern.AI @@ -2809,6 +3432,95 @@
+ +
+
+ +
+ + +

+ World Info Editor + (?) +

+
 
+
+ + +
+
+
+ +
+
+ +
+ +
 
+ + +
+ +
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +   +
+ UID: +   + +
+
+ Tokens used: +   + 0 +
+ +
+
+
+
+
+
@@ -2959,6 +3671,26 @@

Repetition Penalty Range

select
+
+

+ World Info +

+Create

+

+Import

+

+
How to use (?)
+ + + +
+
+
+