diff --git a/public/KoboldAI Worlds/Sample.json b/public/KoboldAI Worlds/Sample.json deleted file mode 100644 index 082684e84..000000000 --- a/public/KoboldAI Worlds/Sample.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "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 e5c648609..20fcd4262 100644 --- a/public/index.html +++ b/public/index.html @@ -99,7 +99,6 @@ var create_save_mes_example = ''; var timerSaveEdit; - var timerKoboldSync; var timerWorldSave; var durationSaveEdit = 200; //animation right menu @@ -136,12 +135,10 @@ 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 world_info = null; + var world_names; + var world_info_data = null; + var world_info_depth = 3; var imported_world_name = ''; var max_context = 2048;//2048; var rep_pen = 1; @@ -338,9 +335,6 @@ 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; @@ -353,7 +347,6 @@ resultCheckStatus(); if(online_status !== 'no_connection'){ var checkStatusNow = setTimeout(getStatus, 3000);//getStatus(); - syncKoboldWorldInfo(false); } }, error: function (jqXHR, exception) { @@ -361,18 +354,12 @@ 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(); } } } @@ -731,6 +718,63 @@ return name; } + function checkWorldInfo(chat) { + if (world_info_data.entries.length == 0) { + return ''; + } + + const messagesToLookBack = world_info_depth * 2; + let textToScan = chat.slice(0, messagesToLookBack).join('').toLowerCase(); + let worldInfo = ''; + let needsToScan = true; + let allActivatedEntries = new Set(); + + while (needsToScan) { + let activatedNow = new Set(); + + for (let entryUid in world_info_data.entries) { + const entry = world_info_data.entries[entryUid]; + if (allActivatedEntries.has(entry.uid)) { + continue; + } + + if (entry.constant) { + activatedNow.add(entry.uid); + } + + if (Array.isArray(entry.key) && entry.key.length) { + primary: for (let key of entry.key) { + if (key && textToScan.includes(key.trim().toLowerCase())) { + if (entry.selective && Array.isArray(entry.keysecondary) && entry.keysecondary.length) { + secondary: for (let keysecondary of entry.keysecondary) { + if (keysecondary && textToScan.includes(keysecondary.trim().toLowerCase())) { + activatedNow.add(entry.uid); + break secondary; + } + } + } else { + activatedNow.add(entry.uid); + break primary; + } + } + } + } + } + + needsToScan = activatedNow.size > 0; + const newContents = [...activatedNow].map(x => world_info_data.entries[x]).map(x => x.content).join('\n'); + worldInfo = worldInfo + newContents; + + if (needsToScan) { + textToScan = newContents.toLowerCase(); + } + + allActivatedEntries = new Set([...allActivatedEntries, ...activatedNow]); + } + + return worldInfo; + } + function isHelpRequest(message) { const helpTokens = ['/?', '/help']; return helpTokens.includes(message.trim().toLowerCase()); @@ -846,7 +890,6 @@ } } - //********************************* //PRE FORMATING STRING //********************************* @@ -992,6 +1035,12 @@ } } + let worldInfoString = ''; + + if (world_info && world_info_data) { + worldInfoString = checkWorldInfo(chat2); + } + var i = 0; for (var item of chat2) {//console.log(encode("dsfs").length); @@ -1145,8 +1194,7 @@ var generate_data; if(main_api == 'kobold'){ - 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}; + var generate_data = {prompt: finalPromt, gui_settings: true,max_length: amount_gen,temperature: temp, max_context_length: max_context}; if(preset_settings != 'gui'){ var this_settings = koboldai_settings[koboldai_setting_names[preset_settings]]; @@ -1188,7 +1236,7 @@ s5:this_settings.sampler_order[4], s6:this_settings.sampler_order[5], s7:this_settings.sampler_order[6], - use_world_info: use_world_info, + use_world_info: false, }; } } @@ -1454,9 +1502,6 @@ $( "#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'; @@ -1763,15 +1808,15 @@ }); } if (popup_type === 'world_imported' && imported_world_name) { - koboldai_world_names.forEach((item, i) => { + 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 === 'del_world' && world_info) { + deleteWorldInfo(world_info); } if(popup_type == 'new_chat' && this_chid != undefined && menu_type != "create"){//Fix it; New chat doesn't create while open create character menu clearChat(); @@ -2100,7 +2145,6 @@ is_get_status = true; is_api_button_press = true; getStatus(); - detectUnitedKobold(); clearSoftPromptsList(); getSoftPromptsList(); } @@ -2201,21 +2245,19 @@ }); - $("#world_info").change(function() { + $("#world_info").change(async function() { const selectedWorld = $('#world_info').find(":selected").val(); - kobold_world_synced = false; - kobold_sync_failed = false; - kobold_world = null; + world_info = null; + world_info_data = null; if (selectedWorld !== 'None') { const worldIndex = Number(selectedWorld); - kobold_world = !isNaN(worldIndex) ? koboldai_world_names[worldIndex] : null; + world_info = !isNaN(worldIndex) ? world_names[worldIndex] : null; + await loadWorldInfoData(); } hideWorldEditor(); - syncKoboldWorldInfo(true); saveSettings(); - updateWorldStatus(); }); $( "#settings_perset" ).change(function() { @@ -2316,7 +2358,6 @@ main_api = 'kobold'; $('#max_context_block').css('display', 'block'); $('#amount_gen_block').css('display', 'block'); - $('#world_info_block').css('display', 'flex'); $('#softprompt_block').css('display', 'block'); } if($('#main_api').find(":selected").val() == 'novel'){ @@ -2325,11 +2366,8 @@ main_api = 'novel'; $('#max_context_block').css('display', 'none'); $('#amount_gen_block').css('display', 'none'); - $('#world_info_block').css('display', 'none'); $('#softprompt_block').css('display', 'none'); } - - updateWorldStatus(); } async function getUserAvatars(){ const response = await fetch("/getuseravatars", { @@ -2358,103 +2396,6 @@ } } - 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", - "X-CSRF-Token": token, - }, - 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(); if(isInt(temp)){ @@ -2474,6 +2415,11 @@ $('#max_context_counter').html( $(this).val() +' Tokens'); var max_contextTimer = setTimeout(saveSettings, 500); }); + $(document).on('input', '#world_info_depth', function() { + world_info_depth = parseInt($(this).val()); + $('#world_info_depth_counter').html(`${$(this).val()} Messages`); + setTimeout(saveSettings, 500); + }); $('#style_anchor').change(function() { style_anchor = !!$('#style_anchor').prop('checked'); saveSettings(); @@ -2605,6 +2551,7 @@ if(settings.anchor_order !== undefined) anchor_order = parseInt(settings.anchor_order); if(settings.style_anchor !== undefined) style_anchor = !!settings.style_anchor; if(settings.character_anchor !== undefined) character_anchor = !!settings.character_anchor; + if(settings.world_info_depth !== undefined) world_info_depth = parseInt(settings.world_info_depth); rep_pen = settings.rep_pen; rep_pen_size = settings.rep_pen_size; @@ -2623,6 +2570,9 @@ $('#amount_gen').val(amount_gen); $('#amount_gen_counter').html(amount_gen+' Tokens'); + + $('#world_info_depth_counter').html(`${world_info_depth} Messages`); + $('#world_info_depth').val(world_info_depth); addZeros = ""; if(isInt(rep_pen)) addZeros = ".00"; @@ -2686,20 +2636,18 @@ $('#api_url_text').val(api_server); // world info settings - koboldai_world_names = data.koboldai_world_names?.length ? data.koboldai_world_names : []; + world_names = data.world_names?.length ? data.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; + if(settings.world_info != undefined) { + if (world_names.includes(settings.world_info)) { + world_info = settings.world_info; } } - koboldai_world_names.forEach((item, i) => { + world_names.forEach((item, i) => { $('#world_info').append(``); // preselect world if saved - if (item == kobold_world) { + if (item == world_info) { $('#world_info').val(i).change(); } }); @@ -2742,7 +2690,8 @@ temp_novel: temp_novel, rep_pen_novel: rep_pen_novel, rep_pen_size_novel: rep_pen_size_novel, - kobold_world: kobold_world, + world_info: world_info, + world_info_depth: world_info_depth,                  }), beforeSend: function(){ @@ -2756,9 +2705,7 @@ //online_status = data.result; if(type === 'change_name'){ location.reload(); - } - - syncKoboldWorldInfo(false); + } }, error: function (jqXHR, exception) { console.log(exception); @@ -3246,15 +3193,15 @@ if (result.ok) { var data = await result.json(); - koboldai_world_names = data.koboldai_world_names?.length ? data.koboldai_world_names : []; + world_names = data.world_names?.length ? data.world_names : []; $('#world_info').find('option[value!="None"]').remove(); - koboldai_world_names.forEach((item, i) => { + world_names.forEach((item, i) => { $('#world_info').append(``); }); if (importedWorldName) { - const indexOf = koboldai_world_names.indexOf(kobold_world); + const indexOf = world_names.indexOf(world_info); $('#world_info').val(indexOf); popup_type = 'world_imported'; @@ -3274,30 +3221,34 @@ // World Info Editor async function showWorldEditor() { is_world_edit_open = true; - $('#world_popup_name').val(kobold_world); + $('#world_popup_name').val(world_info); $('#world_popup').css('display', 'flex'); + await loadWorldInfoData(); + displayWorldEntries(world_info_data); + } - if (kobold_world) { - const response = await fetch("/getworldinfo", { - method: "POST", - headers: { - "Content-Type": "application/json", - "X-CSRF-Token": token, - }, - body: JSON.stringify({ name: kobold_world }) - }); + async function loadWorldInfoData() { + if (!world_info) { + return; + } - if (response.ok) { - kobold_world_data = await response.json(); - displayWorldEntries(kobold_world_data); - } + const response = await fetch("/getworldinfo", { + method: "POST", + headers: { + "Content-Type": "application/json", + "X-CSRF-Token": token, + }, + body: JSON.stringify({ name: world_info }) + }); + + if (response.ok) { + world_info_data = await response.json(); } } function hideWorldEditor() { is_world_edit_open = false; $('#world_popup').css('display', 'none'); - syncKoboldWorldInfo(true); } function displayWorldEntries(data) { @@ -3323,7 +3274,7 @@ 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); + world_info_data.entries[uid].key = value.split(',').map(x => x.trim()).filter(x => x); saveWorldInfo(); }); keyInput.val(entry.key.join(',')).trigger('input'); @@ -3334,7 +3285,7 @@ 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); + world_info_data.entries[uid].keysecondary = value.split(',').map(x => x.trim()).filter(x => x); saveWorldInfo(); }); keySecondaryInput.val(entry.keysecondary.join(',')).trigger('input'); @@ -3345,7 +3296,7 @@ commentInput.on('input', function() { const uid = $(this).data('uid'); const value = $(this).val(); - kobold_world_data.entries[uid].comment = value; + world_info_data.entries[uid].comment = value; saveWorldInfo(); }); commentInput.val(entry.comment).trigger('input'); @@ -3356,7 +3307,7 @@ contentInput.on('input', function() { const uid = $(this).data('uid'); const value = $(this).val(); - kobold_world_data.entries[uid].content = value; + world_info_data.entries[uid].content = value; saveWorldInfo(); // count tokens @@ -3371,7 +3322,7 @@ selectiveInput.on('input', function() { const uid = $(this).data('uid'); const value = $(this).prop('checked'); - kobold_world_data.entries[uid].selective = value; + world_info_data.entries[uid].selective = value; saveWorldInfo(); const keysecondary = $(this).closest('.world_entry').find('.keysecondary'); @@ -3389,7 +3340,7 @@ constantInput.on('input', function() { const uid = $(this).data('uid'); const value = $(this).prop('checked'); - kobold_world_data.entries[uid].constant = value; + world_info_data.entries[uid].constant = value; saveWorldInfo(); }); constantInput.prop('checked', entry.constant).trigger('input'); @@ -3415,22 +3366,11 @@ } async function deleteWorldInfoEntry(uid) { - if (!kobold_world_data || !('entries' in kobold_world_data)) { + if (!world_info_data || !('entries' in world_info_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); - } - } - } + delete world_info_data.entries[uid]; } function createWorldInfoEntry() { @@ -3450,22 +3390,14 @@ } 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]; - } - } + world_info_data.entries[newUid] = newEntry; const entryTemplate = appendWorldEntry(newEntry); entryTemplate.get(0).scrollIntoView({behavior: 'smooth'}); } async function saveWorldInfo(immediately) { - if (!kobold_world || !kobold_world_data) { + if (!world_info || !world_info_data) { return; } @@ -3476,12 +3408,8 @@ "Content-Type": "application/json", "X-CSRF-Token": token, }, - body: JSON.stringify({ name: kobold_world, data: kobold_world_data }) + body: JSON.stringify({ name: world_info, data: world_info_data }) }); - - if (response.ok) { - kobold_world_synced = false; - } } if (immediately) { @@ -3493,20 +3421,20 @@ } async function renameWorldInfo() { - const oldName = kobold_world; + const oldName = world_info; const newName = $('#world_popup_name').val(); if (oldName === newName) { return; } - kobold_world = newName; + world_info = newName; await saveWorldInfo(true); await deleteWorldInfo(oldName, newName); } async function deleteWorldInfo(worldInfoName, selectWorldName) { - if (!koboldai_world_names.includes(worldInfoName)) { + if (!world_names.includes(worldInfoName)) { return; } @@ -3522,7 +3450,7 @@ if (response.ok) { await updateWorldInfoList(); - const selectedIndex = koboldai_world_names.indexOf(selectWorldName); + const selectedIndex = world_names.indexOf(selectWorldName); if (selectedIndex !== -1) { $('#world_info').val(selectedIndex).change(); } @@ -3535,13 +3463,13 @@ } function getFreeWorldEntryUid() { - if (!kobold_world_data || !('entries' in kobold_world_data)) { + if (!world_info_data || !('entries' in world_info_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) { + if (uid in world_info_data.entries) { continue; } return uid; @@ -3554,7 +3482,7 @@ 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)) { + if (world_names.includes(newName)) { continue; } return newName; @@ -3564,19 +3492,19 @@ } async function createNewWorldInfo() { - const worldInfoTemplate = { folders: {}, entries: {} }; + const worldInfoTemplate = { entries: {} }; const worldInfoName = getFreeWorldName(); if (!worldInfoName) { return; } - kobold_world = worldInfoName; - kobold_world_data = { ...worldInfoTemplate }; + world_info = worldInfoName; + world_info_data = { ...worldInfoTemplate }; await saveWorldInfo(true); await updateWorldInfoList(); - const selectedIndex = koboldai_world_names.indexOf(worldInfoName); + const selectedIndex = world_names.indexOf(worldInfoName); if (selectedIndex !== -1) { $('#world_info').val(selectedIndex).change(); } @@ -3590,9 +3518,9 @@ }); $('#world_popup_export').click(() => { - if (kobold_world && kobold_world_data) { - const jsonValue = JSON.stringify(kobold_world_data); - const fileName = `${kobold_world}.json`; + if (world_info && world_info_data) { + const jsonValue = JSON.stringify(world_info_data); + const fileName = `${world_info}.json`; download(jsonValue, fileName, 'application/json'); } }); @@ -3733,7 +3661,7 @@

Content

Text that will be inserted to the prompt upon activation.
- +
-
-

- World Info -

+Create

-

+Import

-

-
How to use (?)
- - - -
-
-

Soft Prompt

About soft prompts (?)
@@ -3983,6 +3892,29 @@
+ +
+

World Info

+
+

+Create

+

+Import

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

Scan Depth

select
+ +
+

Pro Settings

diff --git a/public/style.css b/public/style.css index 16142d553..e436e3468 100644 --- a/public/style.css +++ b/public/style.css @@ -1165,7 +1165,7 @@ input[type=button] { } #world_info { - margin-bottom: 12px; + margin-bottom: 0; } #world_info_block { @@ -1173,26 +1173,6 @@ input[type=button] { align-items: center; } -#world_status{ - opacity: 0.5; - margin-top: 2px; - margin-left: 10px; - margin-bottom: 20px; -} - -#world_status_indicator{ - border-radius: 7px; - width: 14px; - height: 14px; - background-color: red; - display: inline-block; -} - -#world_status_text { - margin-left: 4px; - display: inline-block; -} - #world_import_button, #world_create_button { cursor: pointer; display: inline-block; @@ -1219,7 +1199,7 @@ input[type=button] { backdrop-filter: blur(50px); flex-direction: column; max-width: 800px; - height: 83vh; + height: 100vh; position: absolute; z-index: 2064; margin-left: auto; @@ -1248,6 +1228,19 @@ input[type=button] { opacity: 0.8; } +#world_info_buttons { + margin-left: 1rem; +} + +#world_info_depth_block { + opacity: 0.8; + margin-top: 20px; +} + +#world_info_depth_block input[type="range"] { + margin-left: 10px; +} + #entry_edit_template { display: none !important; } diff --git a/public/worlds/Toaru.json b/public/worlds/Toaru.json new file mode 100644 index 000000000..cd1e5b55d --- /dev/null +++ b/public/worlds/Toaru.json @@ -0,0 +1,131 @@ +{ + "entries": { + "0": { + "uid": 0, + "key": [ + "Tokiwadai Middle School", + "Tokiwadai" + ], + "keysecondary": [], + "comment": "", + "content": "Place(\"Tokiwadai Middle School\")[\"prestigious girls' school\", \"females only\", \"renowned in the world\", \"requires Esper ability Level Three or higher\"]", + "constant": false, + "selective": false + }, + "1": { + "uid": 1, + "key": [ + "Esper", + "Ability User", + "Level" + ], + "keysecondary": [], + "comment": "", + "content": "Category(\"Esper\")[\"psychic\", \"user of supernatural powers\", \"scientifically based\", \"emits AIM\", \"ranked by Levels of strength\"]", + "constant": false, + "selective": false + }, + "2": { + "uid": 2, + "key": [ + "Anti-Skill", + "Guard" + ], + "keysecondary": [], + "comment": "", + "content": "Faction(\"Anti-Skill\")[\"police of Academy City\", \"security forces\", \"SWAT unit\", \"purple symbol\", \"blue uniform\", \"SWAT armor\", \"riot shields\", \"firearms\"]", + "constant": false, + "selective": false + }, + "3": { + "uid": 3, + "key": [ + "Level 5", + "Level Five" + ], + "keysecondary": [], + "comment": "", + "content": "Category(\"Level Five Rank\")[\"highest esper level\", \"only seven people in Academy City have this rank\"]", + "constant": false, + "selective": false + }, + "4": { + "uid": 4, + "key": [ + "Academy City", + "Gakuen Toshi", + "City" + ], + "keysecondary": [], + "comment": "", + "content": "Place(\"Academy City\")[\"Located west of Tokyo\", \"city of several schools\", \"most advanced city in the world\", \"scientists research on psychic powers and higher technology\", \"composed of 23 districts\", \"population over 2 millions\"]", + "constant": false, + "selective": false + }, + "5": { + "uid": 5, + "key": [ + "Saten Ruiko", + "Saten", + "Ruiko", + "Saten-san" + ], + "keysecondary": [], + "comment": "", + "content": "Character(\"Saten Ruiko\")[\"Female\", \"outgoing\", \"friendly\", \"shameless\", \"Level Zero\", \"friends with: {Misaka, Uiharu, Kuroko}\", \"Student (Sakugawa Middle School)\", \"Classmate of Uiharu\"]", + "constant": false, + "selective": false + }, + "6": { + "uid": 6, + "key": [ + "Misaka Mikoto", + "Misaka", + "Mikoto", + "Onee-sama" + ], + "keysecondary": [], + "comment": "", + "content": "Character(\"Misaka Mikoto\")[\"Female\", \"tsundere\", \"short tempered\", \"boyish\", \"Level Five Esper\", \"third most powerful esper of Academy City\", \"Student (Tokiwadai Middle School)\", \"Railgun\", \"Electromaster\", \"Roommate of Kuroko\", \"Friends with: {Kuroko, Uiharu, Saten}\"]", + "constant": false, + "selective": false + }, + "7": { + "uid": 7, + "key": [ + "Kuroko", + "Shirai Kuroko", + "Shirai" + ], + "keysecondary": [], + "comment": "", + "content": "Character(\"Shirai Kuroko\")[\"Female\", \"Level Four Esper\", \"Teleporter powers\", \"Student (Tokiwadai Middle School)\", \"works at Judgement with Uiharu\", \"Roommate of Misaka\", \"obsessed with Misaka\", \"calls Misaka Onee-sama\", \"Friends with: {Misaka, Uiharu, Saten}\"]", + "constant": false, + "selective": false + }, + "8": { + "uid": 8, + "key": [ + "Uihari", + "Uiharu Kazari", + "Kazari" + ], + "keysecondary": [], + "comment": "", + "content": "Character(\"Uiharu Kazari\")[\"Female\", \"works for Judgement with Kuroko\", \"expert in computers and hacking\", \"wears a flower circlet\", \"Level One\", \"friends with: {Misaka, Saten, Kuroko}\", \"Student (Sakugawa Middle School)\", \"Classmate of Saten\"]", + "constant": false, + "selective": false + }, + "9": { + "uid": 9, + "key": [ + "Judgement" + ], + "keysecondary": [], + "comment": "", + "content": "Faction(\"Judgement\")[\"composed of students\", \"student-based disciplinary committee\", \"tasked to maintain peace-and-order within the school system\", \"members wear armbands on right sleeves\", \"green shield symbol with white stripes\"]", + "constant": false, + "selective": false + } + } +} \ No newline at end of file diff --git a/server.js b/server.js index 476a1b3cb..f1c3c6a69 100644 --- a/server.js +++ b/server.js @@ -61,7 +61,7 @@ if (is_colab && process.env.googledrive == 2){ const jsonParser = express.json({limit: '100mb'}); const urlencodedParser = express.urlencoded({extended: true, limit: '100mb'}); const baseRequestArgs = { headers: { "Content-Type": "application/json" } }; -const directories = { worlds: 'public/KoboldAI Worlds/' }; +const directories = { worlds: 'public/worlds/' }; // CSRF Protection // const doubleCsrf = require('csrf-csrf').doubleCsrf; @@ -753,7 +753,7 @@ app.post('/getsettings', jsonParser, (request, response) => { //Wintermute's cod .readdirSync(directories.worlds) .filter(file => path.extname(file).toLowerCase() === '.json') .sort((a, b) => a < b); - const koboldai_world_names = worldFiles.map(item => path.parse(item).name); + const world_names = worldFiles.map(item => path.parse(item).name); files.forEach(item => { const file = fs.readFileSync( @@ -797,37 +797,12 @@ app.post('/getsettings', jsonParser, (request, response) => { //Wintermute's cod settings, koboldai_settings, koboldai_setting_names, - koboldai_world_names, + world_names, novelai_settings, novelai_setting_names }); }); -// Work around to disable parallel requests to endpoint -let kobold_world_sync_busy = false; - -app.post('/synckoboldworld', jsonParser, async (request, response) => { - if(!request.body) return response.sendStatus(400); - - if (!api_server || kobold_world_sync_busy) { - response.send({ busy: true }); - return; - } - - try { - kobold_world_sync_busy = true; - const worldName = request.body.name; - await synchronizeKoboldWorldInfo(worldName); - response.send({ ok: true }); - } catch (err) { - var message = JSON.stringify(err); - console.error(`Error during world synchronization: ${message}`); - response.status(500).send(message); - } finally { - kobold_world_sync_busy = false; - } -}); - app.post('/getworldinfo', jsonParser, (request, response) => { if (!request.body?.name) { return response.sendStatus(400); @@ -835,7 +810,7 @@ app.post('/getworldinfo', jsonParser, (request, response) => { const file = readWorldInfoFile(request.body.name); - return response.send(file.tavernWorldInfo); + return response.send(file); }); app.post('/deleteworldinfo', jsonParser, (request, response) => { @@ -856,6 +831,23 @@ app.post('/deleteworldinfo', jsonParser, (request, response) => { return response.sendStatus(200); }); +function readWorldInfoFile(worldInfoName) { + if (!worldInfoName) { + return { entries: {} }; + } + + const filename = `${worldInfoName}.json`; + const pathToWorldInfo = path.join(directories.worlds, filename); + + if (!fs.existsSync(pathToWorldInfo)) { + throw new Error(`World info file ${filename} doesn't exist.`); + } + + const worldInfoText = fs.readFileSync(pathToWorldInfo, 'utf8'); + const worldInfo = JSON.parse(worldInfoText); + return worldInfo; +} + function getCharaterFile(directories,response,i){ //old need del if(directories.length > i){ @@ -1286,234 +1278,6 @@ app.post('/editworldinfo', jsonParser, (request, response) => { return response.send({ ok: true }); }); -function findTavernWorldEntry(info, key, content) { - for (const entryId in info.entries) { - const entry = info.entries[entryId]; - const keyString = entry.key.join(','); - - if (keyString === key && entry.content === content) { - return entry; - } - } - - return null; -} - -async function synchronizeKoboldWorldInfo(worldInfoName) { - const { koboldFolderName, tavernWorldInfo } = readWorldInfoFile(worldInfoName); - - // Get existing world info - const koboldWorldInfo = await getAsync(`${api_server}/v1/world_info`, baseRequestArgs); - - // Validate kobold world info - let { - shouldCreateWorld, - koboldWorldUid, - tavernEntriesToCreate, - koboldEntriesToDelete, - koboldFoldersToDelete, - } = await validateKoboldWorldInfo(koboldFolderName, koboldWorldInfo, tavernWorldInfo); - - // Create folder if not already exists - if (koboldFolderName && shouldCreateWorld) { - koboldWorldUid = await createKoboldFolder(koboldFolderName, tavernEntriesToCreate, tavernWorldInfo); - } - - await deleteKoboldFolders(koboldFoldersToDelete); - await deleteKoboldEntries(koboldEntriesToDelete); - await createTavernEntries(tavernEntriesToCreate, koboldWorldUid, tavernWorldInfo); -} - -function readWorldInfoFile(worldInfoName) { - if (!worldInfoName) { - return { koboldFolderName: null, tavernWorldInfo: { entries: {}, folders: {} }}; - } - - const koboldFolderName = getKoboldWorldInfoName(worldInfoName); - const filename = `${worldInfoName}.json`; - const pathToWorldInfo = path.join(directories.worlds, filename); - - if (!fs.existsSync(pathToWorldInfo)) { - throw new Error(`World info file ${filename} doesn't exist.`); - } - - const tavernWorldInfoText = fs.readFileSync(pathToWorldInfo, 'utf8'); - const tavernWorldInfo = JSON.parse(tavernWorldInfoText); - return { koboldFolderName, tavernWorldInfo }; -} - -async function createKoboldFolder(koboldFolderName, tavernEntriesToCreate, tavernWorldInfo) { - const createdFolder = await postAsync(`${api_server}/v1/world_info/folders`, { data: {}, ...baseRequestArgs }); - const koboldWorldUid = createdFolder.uid; - - // Set a name so we could find the folder later - const setNameArgs = { data: { value: koboldFolderName }, ...baseRequestArgs }; - await putAsync(`${api_server}/v1/world_info/folders/${koboldWorldUid}/name`, setNameArgs); - - // Create all world info entries - tavernEntriesToCreate.push(...Object.keys(tavernWorldInfo.entries)); - return koboldWorldUid; -} - -async function createTavernEntries(tavernEntriesToCreate, koboldWorldUid, tavernWorldInfo) { - if (tavernEntriesToCreate.length && koboldWorldUid) { - for (const tavernUid of tavernEntriesToCreate) { - try { - const tavernEntry = tavernWorldInfo.entries[tavernUid]; - const koboldEntry = await postAsync(`${api_server}/v1/world_info/folders/${koboldWorldUid}`, { data: {}, ...baseRequestArgs }); - await setKoboldEntryData(tavernEntry, koboldEntry); - } catch (err) { - console.error(`Couldn't create Kobold world info entry, tavernUid=${tavernUid}. Skipping...`); - console.error(err); - } - } - } -} - -async function deleteKoboldEntries(koboldEntriesToDelete) { - if (koboldEntriesToDelete.length) { - for (const uid of koboldEntriesToDelete) { - try { - await deleteAsync(`${api_server}/v1/world_info/${uid}`); - } catch (err) { - console.error(`Couldn't delete Kobold world info entry, uid=${uid}. Skipping...`); - console.error(err); - } - } - } -} - -async function deleteKoboldFolders(koboldFoldersToDelete) { - if (koboldFoldersToDelete.length) { - for (const uid of koboldFoldersToDelete) { - try { - await deleteAsync(api_server + `/v1/world_info/folders/${uid}`, baseRequestArgs); - } catch (err) { - console.error(`Couldn't delete Kobold world info folder, uid=${uid}. Skipping...`); - console.error(err); - } - } - } -} - -async function setKoboldEntryData(tavernEntry, koboldEntry) { - // 1. Set primary key - if (tavernEntry.key?.length) { - const keyArgs = { data: { value: tavernEntry.key.join(',') }, ...baseRequestArgs }; - await putAsync(`${api_server}/v1/world_info/${koboldEntry.uid}/key`, keyArgs); - } - - // 2. Set secondary key - if (tavernEntry.keysecondary?.length) { - const keySecondaryArgs = { data: { value: tavernEntry.keysecondary.join(',') }, ...baseRequestArgs }; - await putAsync(`${api_server}/v1/world_info/${koboldEntry.uid}/keysecondary`, keySecondaryArgs); - } - - // 3. Set content - if (tavernEntry.content) { - const contentArgs = { data: { value: tavernEntry.content }, ...baseRequestArgs }; - await putAsync(`${api_server}/v1/world_info/${koboldEntry.uid}/content`, contentArgs); - } - - // 4. Set comment - if (tavernEntry.comment) { - const commentArgs = { data: { value: tavernEntry.comment }, ...baseRequestArgs }; - await putAsync(`${api_server}/v1/world_info/${koboldEntry.uid}/comment`, commentArgs); - }; - - /* Fixed in Kobold United only: https://github.com/henk717/KoboldAI/pull/280 */ - try { - // 5. Set constant flag - if (tavernEntry.constant) { - const constantArgs = { data: { value: tavernEntry.constant.toString() }, ...baseRequestArgs }; - await putToPromise(`${api_server}/v1/world_info/${koboldEntry.uid}/constant`, constantArgs); - } - - // 6. Set selective flag - if (tavernEntry.selective) { - const selectiveArgs = { data: { value: tavernEntry.selective.toString() }, ...baseRequestArgs }; - await putToPromise(`${api_server}/v1/world_info/${koboldEntry.uid}/selective`, selectiveArgs); - } - } catch { - // couldn't set fields = ignore - } -} - -async function validateKoboldWorldInfo(koboldFolderName, koboldWorldInfo, tavernWorldInfo) { - let shouldCreateWorld = true; - let koboldWorldUid = null; - - const koboldEntriesToDelete = []; // KoboldUIDs - const koboldFoldersToDelete = []; // KoboldUIDs - const tavernEntriesToCreate = []; // TavernUIDs - - if (koboldWorldInfo?.folders?.length) { - let existingFolderAlreadyFound = false; - - for (const folder of koboldWorldInfo.folders) { - // Don't care about non-Tavern folders - if (!isTavernKoboldWorldInfo(folder.name)) { - continue; - } - - // Other Tavern folders should be deleted (including dupes). If folder name selected is null, then delete anyway to clean-up - if (!koboldFolderName || folder.name !== koboldFolderName || existingFolderAlreadyFound) { - koboldFoldersToDelete.push(folder.uid); - // Should also delete all entries in folder otherwise they will be detached - if (Array.isArray(folder.entries)) { - koboldEntriesToDelete.push(...folder.entries.map(entry => entry.uid)); - } - } - - // Validate existing entries in Kobold world - if (folder.name === koboldFolderName) { - existingFolderAlreadyFound = true; - shouldCreateWorld = false; - koboldWorldUid = folder.uid; - if (folder.entries?.length) { - const foundTavernEntries = []; - for (const koboldEntry of folder.entries) { - const tavernEntry = findTavernWorldEntry(tavernWorldInfo, koboldEntry.key, koboldEntry.content); - - if (tavernEntry) { - foundTavernEntries.push(tavernEntry.uid); - if (isEntryOutOfSync(tavernEntry, koboldEntry)) { - // Entry is out of sync. Should be recreated - koboldEntriesToDelete.push(koboldEntry.uid); - tavernEntriesToCreate.push(tavernEntry.uid); - } - } - else { - // We don't have that entry in our world. It should be deleted - koboldEntriesToDelete.push(koboldEntry.uid); - } - } - - // Check if every tavern entry was found in kobold world - // BTW. Entries is an object, not an array! - for (const tavernEntryUid in tavernWorldInfo.entries) { - const tavernEntry = tavernWorldInfo.entries[tavernEntryUid]; - if (!foundTavernEntries.includes(tavernEntry.uid)) { - tavernEntriesToCreate.push(tavernEntry.uid); - } - } - } - } - } - } - - return { shouldCreateWorld, koboldWorldUid, tavernEntriesToCreate, koboldEntriesToDelete, koboldFoldersToDelete }; -} - -function isEntryOutOfSync(tavernEntry, koboldEntry) { - return tavernEntry.content !== koboldEntry.content || - tavernEntry.comment !== koboldEntry.comment || - tavernEntry.selective !== koboldEntry.selective || - tavernEntry.constant !== koboldEntry.constant || - tavernEntry.key.join(',') !== koboldEntry.key || - (koboldEntry.selective ? tavernEntry.keysecondary.join(',') !== koboldEntry.keysecondary : false); -} - // ** REST CLIENT ASYNC WRAPPERS ** function deleteAsync(url, args) { return new Promise((resolve, reject) => { @@ -1560,15 +1324,6 @@ function getAsync(url, args) { } // ** END ** -function getKoboldWorldInfoName(worldInfoName) { - return worldInfoName ? `TavernAI_${worldInfoName}_WI` : null; -} - -function isTavernKoboldWorldInfo(folderName) { - return /^TavernAI_(.*)_WI$/.test(folderName); -} - - app.listen(server_port, function() { if(process.env.colab !== undefined){ if(process.env.colab == 2){