mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
#21 Add world info editor
This commit is contained in:
@@ -69,6 +69,7 @@
|
|||||||
|
|
||||||
var timerSaveEdit;
|
var timerSaveEdit;
|
||||||
var timerKoboldSync;
|
var timerKoboldSync;
|
||||||
|
var timerWorldSave;
|
||||||
var durationSaveEdit = 200;
|
var durationSaveEdit = 200;
|
||||||
//animation right menu
|
//animation right menu
|
||||||
var animation_rm_duration = 200;
|
var animation_rm_duration = 200;
|
||||||
@@ -109,6 +110,7 @@
|
|||||||
var kobold_world_synced = false;
|
var kobold_world_synced = false;
|
||||||
var kobold_sync_failed = false;
|
var kobold_sync_failed = false;
|
||||||
var kobold_is_united = false;
|
var kobold_is_united = false;
|
||||||
|
var kobold_world_data = null;
|
||||||
var imported_world_name = '';
|
var imported_world_name = '';
|
||||||
var max_context = 2048;//2048;
|
var max_context = 2048;//2048;
|
||||||
var rep_pen = 1;
|
var rep_pen = 1;
|
||||||
@@ -2133,11 +2135,16 @@
|
|||||||
|
|
||||||
// If we can reach Kobold's new ui, then it should be United branch
|
// If we can reach Kobold's new ui, then it should be United branch
|
||||||
kobold_is_united = false;
|
kobold_is_united = false;
|
||||||
const kobold_united_ui2 = api_server.replace('/api', '/new_ui');
|
try {
|
||||||
const response = await fetch(kobold_united_ui2, { method: 'HEAD'});
|
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) {
|
if (response.ok && response.status == 200) {
|
||||||
kobold_is_united = true;
|
kobold_is_united = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
// empty catch
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2927,7 +2934,7 @@
|
|||||||
|
|
||||||
jQuery.ajax({
|
jQuery.ajax({
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
url: '/importworld',
|
url: '/importworldinfo',
|
||||||
data: formData,
|
data: formData,
|
||||||
beforeSend: () => {},
|
beforeSend: () => {},
|
||||||
cache: false,
|
cache: false,
|
||||||
@@ -2964,7 +2971,7 @@
|
|||||||
|
|
||||||
if (importedWorldName) {
|
if (importedWorldName) {
|
||||||
const indexOf = koboldai_world_names.indexOf(kobold_world);
|
const indexOf = koboldai_world_names.indexOf(kobold_world);
|
||||||
$(`#world_info`).val(indexOf);
|
$('#world_info').val(indexOf);
|
||||||
|
|
||||||
popup_type = 'world_imported';
|
popup_type = 'world_imported';
|
||||||
callPopup('<h3>World imported successfully! Select it now?</h3>');
|
callPopup('<h3>World imported successfully! Select it now?</h3>');
|
||||||
@@ -2983,7 +2990,7 @@
|
|||||||
// World Info Editor
|
// World Info Editor
|
||||||
async function showWorldEditor() {
|
async function showWorldEditor() {
|
||||||
is_world_edit_open = true;
|
is_world_edit_open = true;
|
||||||
$('#world_text_content').val('');
|
$('#world_popup_name').val(kobold_world);
|
||||||
$('#world_popup').css('display', 'flex');
|
$('#world_popup').css('display', 'flex');
|
||||||
|
|
||||||
if (kobold_world) {
|
if (kobold_world) {
|
||||||
@@ -2994,8 +3001,8 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const worldInfoData = await response.text();
|
kobold_world_data = await response.json();
|
||||||
$('#world_text_content').val(worldInfoData);
|
displayWorldEntries(kobold_world_data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3003,10 +3010,209 @@
|
|||||||
function hideWorldEditor() {
|
function hideWorldEditor() {
|
||||||
is_world_edit_open = false;
|
is_world_edit_open = false;
|
||||||
$('#world_popup').css('display', 'none');
|
$('#world_popup').css('display', 'none');
|
||||||
$('#world_text_content').val('');
|
syncKoboldWorldInfo(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteWorldInfo(worldInfoName) {
|
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();
|
||||||
|
});
|
||||||
|
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)) {
|
if (!koboldai_world_names.includes(worldInfoName)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -3019,18 +3225,42 @@
|
|||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
await updateWorldInfoList();
|
await updateWorldInfoList();
|
||||||
$('#world_info').val('None').change();
|
|
||||||
|
const selectedIndex = koboldai_world_names.indexOf(selectWorldName);
|
||||||
|
if (selectedIndex !== -1) {
|
||||||
|
$('#world_info').val(selectedIndex).change();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$('#world_info').val('None').change();
|
||||||
|
}
|
||||||
|
|
||||||
hideWorldEditor();
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
$('#world_info_edit_button').click(() => {
|
$('#world_info_edit_button').click(() => {
|
||||||
is_world_edit_open ? hideWorldEditor() : showWorldEditor();
|
is_world_edit_open ? hideWorldEditor() : showWorldEditor();
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#world_popup_export').click(() => {
|
$('#world_popup_export').click(() => {
|
||||||
const jsonValue = $('#world_text_content').val();
|
if (kobold_world && kobold_world_data) {
|
||||||
if (kobold_world && jsonValue) {
|
const jsonValue = JSON.stringify(kobold_world_data);
|
||||||
const fileName = `${kobold_world}.json`;
|
const fileName = `${kobold_world}.json`;
|
||||||
download(jsonValue, fileName, 'application/json');
|
download(jsonValue, fileName, 'application/json');
|
||||||
}
|
}
|
||||||
@@ -3041,9 +3271,17 @@
|
|||||||
callPopup('<h3>Delete the World Info?</h3>');
|
callPopup('<h3>Delete the World Info?</h3>');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$('#world_popup_new').click(() => {
|
||||||
|
createWorldInfoEntry();
|
||||||
|
});
|
||||||
|
|
||||||
$('#world_cross').click(() => {
|
$('#world_cross').click(() => {
|
||||||
hideWorldEditor();
|
hideWorldEditor();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$('#world_popup_name_button').click(() => {
|
||||||
|
renameWorldInfo();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<title>Tavern.AI</title>
|
<title>Tavern.AI</title>
|
||||||
@@ -3105,20 +3343,88 @@
|
|||||||
<div id="world_popup">
|
<div id="world_popup">
|
||||||
<div id="world_popup_text">
|
<div id="world_popup_text">
|
||||||
<img id="world_cross" src="img/cross.png">
|
<img id="world_cross" src="img/cross.png">
|
||||||
<div>
|
<div id="world_popup_header">
|
||||||
<!-- Probably needs a logo (probably) -->
|
<!-- Consider changing logo to something else -->
|
||||||
<!-- <img src="img/book2.png" id="world_logo"> -->
|
<img src="img/book2.png" id="world_logo">
|
||||||
<h3>World Info creation</h3>
|
<h3>
|
||||||
|
World Info Editor
|
||||||
|
<span>(<a href="/notes/13" target="_blank">?</a>)</span>
|
||||||
|
</h3>
|
||||||
|
<div class="world_popup_expander"> </div>
|
||||||
|
<form id="form_rename_world" action="javascript:void(null);" method="post" enctype="multipart/form-data">
|
||||||
|
<input id="world_popup_name" name="world_popup_name" class="text_pole" maxlength="99" size="32" value="" autocomplete="off">
|
||||||
|
<input id="world_popup_name_button" type="submit" value="Rename">
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Placeholder until actual editor is implemented -->
|
<div id="world_popup_entries_list">
|
||||||
<h4>File content (read-only)</h4>
|
</div>
|
||||||
<textarea id="world_text_content" placeholder="Loading..." form="form_create" disabled></textarea>
|
|
||||||
|
|
||||||
<div id="world_popup_bottom_holder">
|
<div id="world_popup_bottom_holder">
|
||||||
|
<div id="world_popup_new" class="menu_button">New Entry</div>
|
||||||
|
<div class="world_popup_expander"> </div>
|
||||||
<div id="world_popup_export" class="menu_button">Export</div>
|
<div id="world_popup_export" class="menu_button">Export</div>
|
||||||
<div id="world_popup_delete" class="menu_button">Delete</div>
|
<div id="world_popup_delete" class="menu_button">Delete World</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="entry_edit_template">
|
||||||
|
<div class="world_entry">
|
||||||
|
<form class="world_entry_form">
|
||||||
|
<div class="world_entry_form_control">
|
||||||
|
<label for="key">
|
||||||
|
<h4>Key</h4>
|
||||||
|
<h5>Comma-separated list of keywords (e.g: foo,bar).</h5>
|
||||||
|
</label>
|
||||||
|
<input class="text_pole" type="text" name="key" placeholder=""/>
|
||||||
|
</div>
|
||||||
|
<div class="world_entry_form_control">
|
||||||
|
<label for="keysecondary">
|
||||||
|
<h4>Secondary Key</h4>
|
||||||
|
<h5>Comma-separated list of additional keywords (e.g: foo,bar).</h5>
|
||||||
|
</label>
|
||||||
|
<input class="text_pole" type="text" name="keysecondary" placeholder=""/>
|
||||||
|
</div>
|
||||||
|
<div class="world_entry_form_control">
|
||||||
|
<label for="comment">
|
||||||
|
<h4>Comment</h4>
|
||||||
|
<h5>Optional comment (doesn't affect the AI).</h5>
|
||||||
|
</label>
|
||||||
|
<input class="text_pole" type="text" name="comment" placeholder=""/>
|
||||||
|
</div>
|
||||||
|
<div class="world_entry_form_control">
|
||||||
|
<label for="content">
|
||||||
|
<h4>Content</h4>
|
||||||
|
<h5>Text that will be inserted to the prompt upon activation.</h5>
|
||||||
|
</label>
|
||||||
|
<textarea class="text_pole" name="content" rows="4" placeholder=""></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="world_entry_form_control world_entry_form_horizontal">
|
||||||
|
<label class="checkbox" for="constant">
|
||||||
|
<input type="checkbox" name="constant" />
|
||||||
|
<span class="checkbox_fancy"></span>
|
||||||
|
<h4>Constant</h4>
|
||||||
|
</label>
|
||||||
|
<label class="checkbox" for="selective">
|
||||||
|
<input type="checkbox" name="selective" />
|
||||||
|
<span class="checkbox_fancy"></span>
|
||||||
|
<h4>Selective</h4>
|
||||||
|
</label>
|
||||||
|
<span class="world_popup_expander"> </span>
|
||||||
|
<h5 class="world_entry_form_uid">
|
||||||
|
UID:
|
||||||
|
|
||||||
|
<span class="world_entry_form_uid_value"></span>
|
||||||
|
</h5>
|
||||||
|
<h5 class="world_entry_form_tokens">
|
||||||
|
Tokens used:
|
||||||
|
|
||||||
|
<span class="world_entry_form_token_counter">0</span>
|
||||||
|
</h5>
|
||||||
|
<input class="delete_entry_button" type="button" value="Delete Entry" />
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
141
public/style.css
141
public/style.css
@@ -1012,7 +1012,6 @@ input[type=button] {
|
|||||||
height: 83vh;
|
height: 83vh;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 2060;
|
z-index: 2060;
|
||||||
background-color: blue;
|
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
left: 0;
|
left: 0;
|
||||||
@@ -1024,11 +1023,6 @@ input[type=button] {
|
|||||||
border-radius: 1px;
|
border-radius: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#world_text_content {
|
|
||||||
margin: 0;
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
#world_popup_bottom_holder {
|
#world_popup_bottom_holder {
|
||||||
padding: 1rem 0;
|
padding: 1rem 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -1040,6 +1034,123 @@ input[type=button] {
|
|||||||
margin-left: 1rem;
|
margin-left: 1rem;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
#entry_edit_template {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.world_entry:not(:last-child)::after {
|
||||||
|
margin-top: 1rem;
|
||||||
|
height: 1px;
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
content: '';
|
||||||
|
background-image: linear-gradient(270deg, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.75), rgba(0, 0, 0, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
#world_popup_header {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
margin-left: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#form_rename_world {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-right: 20px;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
#form_rename_world input[type="submit"] {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
#form_rename_world input:not(:last-child) {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#world_popup_header h5 {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.world_popup_expander {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#world_popup_entries_list {
|
||||||
|
flex-grow: 1;
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
#world_popup_entries_list:empty {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#world_popup_entries_list:empty::before {
|
||||||
|
content: 'No entries exist. Try creating one!';
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: bolder;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.world_entry_form_control {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin: 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.world_entry_form_control label {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.world_entry_form_control label h4 {
|
||||||
|
margin-bottom: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.world_entry_form_control label h5 {
|
||||||
|
margin-top: 3px;
|
||||||
|
margin-bottom: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.world_entry_form_control textarea {
|
||||||
|
height: auto;
|
||||||
|
width: auto;
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.world_entry_form_control.world_entry_form_horizontal {
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.world_entry_form_control input[type=button] {
|
||||||
|
opacity: 0.7;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.world_entry_form_horizontal h5 {
|
||||||
|
margin: 0 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.world_entry_form_control .checkbox h4 {
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
margin-top: 0;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.world_entry_form_control .checkbox:not(:first-child) {
|
||||||
|
margin-left: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
#world_cross {
|
#world_cross {
|
||||||
@@ -1052,19 +1163,17 @@ input[type=button] {
|
|||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
#world_popup h5 a {
|
#world_logo {
|
||||||
|
width: 35px;
|
||||||
|
height: 35px;
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#world_popup h4 a, #world_popup h5 a, #world_popup h3 a {
|
||||||
color: #936f4a;
|
color: #936f4a;
|
||||||
}
|
}
|
||||||
|
|
||||||
#world_popup h5 a:hover {
|
#world_popup h5 a:hover, #world_popup h4 a:hover, #world_popup h4 a:hover a {
|
||||||
color: #998e6b;
|
|
||||||
}
|
|
||||||
|
|
||||||
#world_popup h4 a {
|
|
||||||
color: #936f4a;
|
|
||||||
}
|
|
||||||
|
|
||||||
#world_popup h4 a:hover {
|
|
||||||
color: #998e6b;
|
color: #998e6b;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
39
server.js
39
server.js
@@ -1098,7 +1098,7 @@ app.post("/importchat", urlencodedParser, function(request, response){
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post('/importworld', urlencodedParser, (request, response) => {
|
app.post('/importworldinfo', urlencodedParser, (request, response) => {
|
||||||
if(!request.file) return response.sendStatus(400);
|
if(!request.file) return response.sendStatus(400);
|
||||||
|
|
||||||
const filename = request.file.originalname;
|
const filename = request.file.originalname;
|
||||||
@@ -1130,12 +1130,37 @@ app.post('/importworld', urlencodedParser, (request, response) => {
|
|||||||
return response.send({ name: worldName });
|
return response.send({ name: worldName });
|
||||||
});
|
});
|
||||||
|
|
||||||
function findTavernWorldEntry(info, key) {
|
app.post('/editworldinfo', jsonParser, (request, response) => {
|
||||||
|
if (!request.body) {
|
||||||
|
return response.sendStatus(400);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!request.body.name) {
|
||||||
|
return response.status(400).send('World file must have a name');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!('entries' in request.body.data)) {
|
||||||
|
throw new Error('World info must contain an entries list');
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
return response.status(400).send('Is not a valid world info file');
|
||||||
|
}
|
||||||
|
|
||||||
|
const filename = `${request.body.name}.json`;
|
||||||
|
const pathToFile = path.join(directories.worlds, filename);
|
||||||
|
|
||||||
|
fs.writeFileSync(pathToFile, JSON.stringify(request.body.data));
|
||||||
|
|
||||||
|
return response.send({ ok: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
function findTavernWorldEntry(info, key, content) {
|
||||||
for (const entryId in info.entries) {
|
for (const entryId in info.entries) {
|
||||||
const entry = info.entries[entryId];
|
const entry = info.entries[entryId];
|
||||||
const keyString = entry.key.join(',');
|
const keyString = entry.key.join(',');
|
||||||
|
|
||||||
if (keyString === key) {
|
if (keyString === key && entry.content === content) {
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1300,6 +1325,10 @@ async function validateKoboldWorldInfo(koboldFolderName, koboldWorldInfo, tavern
|
|||||||
// Other Tavern folders should be deleted (including dupes). If folder name selected is null, then delete anyway to clean-up
|
// 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) {
|
if (!koboldFolderName || folder.name !== koboldFolderName || existingFolderAlreadyFound) {
|
||||||
koboldFoldersToDelete.push(folder.uid);
|
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
|
// Validate existing entries in Kobold world
|
||||||
@@ -1310,7 +1339,7 @@ async function validateKoboldWorldInfo(koboldFolderName, koboldWorldInfo, tavern
|
|||||||
if (folder.entries?.length) {
|
if (folder.entries?.length) {
|
||||||
const foundTavernEntries = [];
|
const foundTavernEntries = [];
|
||||||
for (const koboldEntry of folder.entries) {
|
for (const koboldEntry of folder.entries) {
|
||||||
const tavernEntry = findTavernWorldEntry(tavernWorldInfo, koboldEntry.key);
|
const tavernEntry = findTavernWorldEntry(tavernWorldInfo, koboldEntry.key, koboldEntry.content);
|
||||||
|
|
||||||
if (tavernEntry) {
|
if (tavernEntry) {
|
||||||
foundTavernEntries.push(tavernEntry.uid);
|
foundTavernEntries.push(tavernEntry.uid);
|
||||||
@@ -1348,7 +1377,7 @@ function isEntryOutOfSync(tavernEntry, koboldEntry) {
|
|||||||
tavernEntry.selective !== koboldEntry.selective ||
|
tavernEntry.selective !== koboldEntry.selective ||
|
||||||
tavernEntry.constant !== koboldEntry.constant ||
|
tavernEntry.constant !== koboldEntry.constant ||
|
||||||
tavernEntry.key.join(',') !== koboldEntry.key ||
|
tavernEntry.key.join(',') !== koboldEntry.key ||
|
||||||
tavernEntry.keysecondary(',') !== koboldEntry.keysecondary;
|
tavernEntry.keysecondary.join(',') !== koboldEntry.keysecondary;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ** REST CLIENT ASYNC WRAPPERS **
|
// ** REST CLIENT ASYNC WRAPPERS **
|
||||||
|
Reference in New Issue
Block a user