mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
World Info: Add global multi-selection/merging
Global world info always involved some kind of merging leading to extremely large lorebook files that took a long time to import. This commit adds the ability to select more than one world info file and they will be merged together along with character world info. In short, multiple worlds can be meshed together to further contribute to context. You can also use this for world "DLCs" of sorts. Let's say someone else has more information to add regarding a world, but doesn't want to use a large world file. The JSONs can now be merged. Signed-off-by: kingbri <bdashore3@proton.me>
This commit is contained in:
@ -1876,17 +1876,17 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="wi-holder" class="margin5">
|
<div id="wi-holder" class="margin5">
|
||||||
<div class="wi-settings flex-container gap10px alignitemscenter">
|
<div class="wi-settings flex-container gap10px alignitemscenter">
|
||||||
<div class="flex1 flex-container flexFlowColumn">
|
|
||||||
<div class="flex range-block">
|
<div class="flex range-block">
|
||||||
<div class="range-block-title justifyLeft">
|
<div class="range-block-title justifyLeft">
|
||||||
<small>Active World</small>
|
<small>Active World</small>
|
||||||
</div>
|
</div>
|
||||||
<div class="range-block-range">
|
<div class="range-block-range">
|
||||||
<select id="world_info" class="flexGrow margin0">
|
<select id="world_info" multiple>
|
||||||
<option value="">--- None ---</option>
|
<option value="">-- World Info not found --</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="flex1 flex-container flexFlowColumn">
|
||||||
<div class="flex range-block">
|
<div class="flex range-block">
|
||||||
<div class="range-block-title justifyLeft">
|
<div class="range-block-title justifyLeft">
|
||||||
<label for="world_info_character_strategy">
|
<label for="world_info_character_strategy">
|
||||||
@ -1901,7 +1901,6 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div name="WIScanAndTokens" class="flex1 flex-container flexFlowColumn">
|
<div name="WIScanAndTokens" class="flex1 flex-container flexFlowColumn">
|
||||||
<div class="flex1 gap5px range-block">
|
<div class="flex1 gap5px range-block">
|
||||||
@ -1936,6 +1935,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="range-block flex-container flexFlowColumn">
|
<div class="range-block flex-container flexFlowColumn">
|
||||||
<label title="Entries can activate other entries by mentioning their keywords" class="checkbox_label">
|
<label title="Entries can activate other entries by mentioning their keywords" class="checkbox_label">
|
||||||
<input id="world_info_recursive" type="checkbox" />
|
<input id="world_info_recursive" type="checkbox" />
|
||||||
|
@ -2033,7 +2033,7 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
|
|||||||
let chat2 = [];
|
let chat2 = [];
|
||||||
for (let i = coreChat.length - 1, j = 0; i >= 0; i--, j++) {
|
for (let i = coreChat.length - 1, j = 0; i >= 0; i--, j++) {
|
||||||
// For OpenAI it's only used in WI
|
// For OpenAI it's only used in WI
|
||||||
if (main_api == 'openai' && !world_info) {
|
if (main_api == 'openai' && (!world_info || world_info.length === 0)) {
|
||||||
console.debug('No WI, skipping chat2 for OAI');
|
console.debug('No WI, skipping chat2 for OAI');
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import { saveSettings, callPopup, substituteParams, getTokenCount, getRequestHeaders, chat_metadata, this_chid, characters } from "../script.js";
|
import { saveSettings, callPopup, substituteParams, getTokenCount, getRequestHeaders, chat_metadata, this_chid, characters } from "../script.js";
|
||||||
import { download, debounce, initScrollHeight, resetScrollHeight, parseJsonFile, extractDataFromPng, getFileBuffer } from "./utils.js";
|
import { download, debounce, initScrollHeight, resetScrollHeight, parseJsonFile, extractDataFromPng, getFileBuffer, delay } from "./utils.js";
|
||||||
import { getContext } from "./extensions.js";
|
import { getContext } from "./extensions.js";
|
||||||
import { metadata_keys, shouldWIAddPrompt } from "./extensions/floating-prompt/index.js";
|
import { metadata_keys, shouldWIAddPrompt } from "./extensions/floating-prompt/index.js";
|
||||||
import { registerSlashCommand } from "./slash-commands.js";
|
import { registerSlashCommand } from "./slash-commands.js";
|
||||||
|
import { deviceInfo } from "./RossAscends-mods.js";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
world_info,
|
world_info,
|
||||||
@ -25,7 +26,7 @@ const world_info_insertion_strategy = {
|
|||||||
global_first: 2,
|
global_first: 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
let world_info = null;
|
let world_info = [];
|
||||||
let world_names;
|
let world_names;
|
||||||
let world_info_depth = 2;
|
let world_info_depth = 2;
|
||||||
let world_info_budget = 25;
|
let world_info_budget = 25;
|
||||||
@ -77,6 +78,12 @@ function setWorldInfoSettings(settings, data) {
|
|||||||
world_info_budget = 25;
|
world_info_budget = 25;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reset selected world from old string
|
||||||
|
if (typeof settings.world_info === "string") {
|
||||||
|
const existingWorldInfo = settings.world_info;
|
||||||
|
settings.world_info = [existingWorldInfo];
|
||||||
|
}
|
||||||
|
|
||||||
$("#world_info_depth_counter").text(world_info_depth);
|
$("#world_info_depth_counter").text(world_info_depth);
|
||||||
$("#world_info_depth").val(world_info_depth);
|
$("#world_info_depth").val(world_info_depth);
|
||||||
|
|
||||||
@ -91,20 +98,15 @@ function setWorldInfoSettings(settings, data) {
|
|||||||
$("#world_info_character_strategy").val(world_info_character_strategy);
|
$("#world_info_character_strategy").val(world_info_character_strategy);
|
||||||
|
|
||||||
world_names = data.world_names?.length ? data.world_names : [];
|
world_names = data.world_names?.length ? data.world_names : [];
|
||||||
|
world_info = settings.world_info?.filter((e) => world_names.includes(e)) ?? [];
|
||||||
|
|
||||||
if (settings.world_info != undefined) {
|
if (world_names.length > 0) {
|
||||||
if (world_names.includes(settings.world_info)) {
|
$("#world_info").empty();
|
||||||
world_info = settings.world_info;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
world_names.forEach((item, i) => {
|
world_names.forEach((item, i) => {
|
||||||
$("#world_info").append(`<option value='${i}'>${item}</option>`);
|
$("#world_info").append(`<option value='${i}'${world_info.includes(item) ? ' selected' : ''}>${item}</option>`);
|
||||||
$("#world_editor_select").append(`<option value='${i}'>${item}</option>`);
|
$("#world_editor_select").append(`<option value='${i}'>${item}</option>`);
|
||||||
// preselect world if saved
|
|
||||||
if (item == world_info) {
|
|
||||||
$("#world_info").val(i).trigger('change');
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
$("#world_editor_select").trigger("change");
|
$("#world_editor_select").trigger("change");
|
||||||
@ -160,7 +162,7 @@ async function updateWorldInfoList() {
|
|||||||
$("#world_editor_select").find('option[value!=""]').remove();
|
$("#world_editor_select").find('option[value!=""]').remove();
|
||||||
|
|
||||||
world_names.forEach((item, i) => {
|
world_names.forEach((item, i) => {
|
||||||
$("#world_info").append(`<option value='${i}'>${item}</option>`);
|
$("#world_info").append(`<option value='${i}'${world_info.includes(item) ? ' selected' : ''}>${item}</option>`);
|
||||||
$("#world_editor_select").append(`<option value='${i}'>${item}</option>`);
|
$("#world_editor_select").append(`<option value='${i}'>${item}</option>`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -170,6 +172,14 @@ function hideWorldEditor() {
|
|||||||
displayWorldEntries(null, null);
|
displayWorldEntries(null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getWIElement(name) {
|
||||||
|
const wiElement = $("#world_info").children().filter(function() {
|
||||||
|
return $(this).text().toLowerCase() === name.toLowerCase()
|
||||||
|
});
|
||||||
|
|
||||||
|
return wiElement;
|
||||||
|
}
|
||||||
|
|
||||||
function nullWorldInfo() {
|
function nullWorldInfo() {
|
||||||
toastr.info("Create or import a new World Info file first.", "World Info is not set", { timeOut: 10000, preventDuplicates: true });
|
toastr.info("Create or import a new World Info file first.", "World Info is not set", { timeOut: 10000, preventDuplicates: true });
|
||||||
}
|
}
|
||||||
@ -224,7 +234,8 @@ function displayWorldEntries(name, data) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await deleteWorldInfo(name, world_info);
|
// Selected world_info automatically refreshes
|
||||||
|
await deleteWorldInfo(name);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Check if a sortable instance exists
|
// Check if a sortable instance exists
|
||||||
@ -582,19 +593,16 @@ async function renameWorldInfo(name, data) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let selectNewName = null;
|
const entryPreviouslySelected = world_info.findIndex((e) => e === oldName);
|
||||||
if (oldName === world_info) {
|
|
||||||
console.debug("Renaming current world info");
|
|
||||||
world_info = newName;
|
|
||||||
selectNewName = newName;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
console.debug("Renaming non-current world info");
|
|
||||||
selectNewName = world_info;
|
|
||||||
}
|
|
||||||
|
|
||||||
await saveWorldInfo(newName, data, true);
|
await saveWorldInfo(newName, data, true);
|
||||||
await deleteWorldInfo(oldName, selectNewName);
|
await deleteWorldInfo(oldName);
|
||||||
|
|
||||||
|
if (entryPreviouslySelected !== -1) {
|
||||||
|
const wiElement = getWIElement(newName);
|
||||||
|
wiElement.prop("selected", true);
|
||||||
|
$("#world_info").trigger('change');
|
||||||
|
}
|
||||||
|
|
||||||
const selectedIndex = world_names.indexOf(newName);
|
const selectedIndex = world_names.indexOf(newName);
|
||||||
if (selectedIndex !== -1) {
|
if (selectedIndex !== -1) {
|
||||||
@ -602,7 +610,7 @@ async function renameWorldInfo(name, data) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteWorldInfo(worldInfoName, selectWorldName) {
|
async function deleteWorldInfo(worldInfoName) {
|
||||||
if (!world_names.includes(worldInfoName)) {
|
if (!world_names.includes(worldInfoName)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -614,15 +622,13 @@ async function deleteWorldInfo(worldInfoName, selectWorldName) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
await updateWorldInfoList();
|
const existingWorldIndex = world_info.findIndex((e) => e === worldInfoName);
|
||||||
|
if (existingWorldIndex !== -1) {
|
||||||
const selectedIndex = world_names.indexOf(selectWorldName);
|
world_info.splice(existingWorldIndex, 1);
|
||||||
if (selectedIndex !== -1) {
|
saveSettingsDebounced();
|
||||||
$("#world_info").val(selectedIndex).trigger('change');
|
|
||||||
} else {
|
|
||||||
$("#world_info").val("").trigger('change');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await updateWorldInfoList();
|
||||||
$('#world_editor_select').trigger('change');
|
$('#world_editor_select').trigger('change');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -663,16 +669,13 @@ async function createNewWorldInfo(worldInfoName) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
world_info = worldInfoName;
|
await saveWorldInfo(worldInfoName, worldInfoTemplate, true);
|
||||||
await saveWorldInfo(world_info, worldInfoTemplate, true);
|
|
||||||
await updateWorldInfoList();
|
await updateWorldInfoList();
|
||||||
|
|
||||||
const selectedIndex = world_names.indexOf(worldInfoName);
|
const selectedIndex = world_names.indexOf(worldInfoName);
|
||||||
if (selectedIndex !== -1) {
|
if (selectedIndex !== -1) {
|
||||||
$("#world_info").val(selectedIndex).trigger('change');
|
|
||||||
$('#world_editor_select').val(selectedIndex).trigger('change');
|
$('#world_editor_select').val(selectedIndex).trigger('change');
|
||||||
} else {
|
} else {
|
||||||
$("#world_info").val("").trigger('change');
|
|
||||||
hideWorldEditor();
|
hideWorldEditor();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -689,7 +692,7 @@ async function getCharacterLore() {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (name === world_info) {
|
if (world_info.includes(name)) {
|
||||||
console.debug(`Character ${characters[this_chid]?.name} world info is the same as global: ${name}. Skipping...`);
|
console.debug(`Character ${characters[this_chid]?.name} world info is the same as global: ${name}. Skipping...`);
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@ -710,13 +713,13 @@ async function getGlobalLore() {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!world_names.includes(world_info)) {
|
let entries = [];
|
||||||
console.log(`Global ${characters[this_chid]?.name} world info does not exist: ${world_info}`);
|
for (const worldName of world_info) {
|
||||||
return [];
|
const data = await loadWorldInfoData(worldName);
|
||||||
|
const newEntries = data ? Object.keys(data.entries).map((x) => data.entries[x]) : [];
|
||||||
|
entries = entries.concat(newEntries);
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await loadWorldInfoData(world_info);
|
|
||||||
const entries = data ? Object.keys(data.entries).map((x) => data.entries[x]) : [];
|
|
||||||
console.debug(`Global world info has ${entries.length} entries`);
|
console.debug(`Global world info has ${entries.length} entries`);
|
||||||
|
|
||||||
return entries;
|
return entries;
|
||||||
@ -1024,28 +1027,44 @@ export async function importEmbeddedWorldInfo() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function onWorldInfoChange(_, text) {
|
function onWorldInfoChange(_, text) {
|
||||||
let selectedWorld;
|
let selectedWorlds;
|
||||||
if (_ !== '__notSlashCommand__') { // if it's a slash command
|
if (_ !== '__notSlashCommand__') { // if it's a slash command
|
||||||
if (text !== undefined) { // and args are provided
|
if (text !== undefined) { // and args are provided
|
||||||
let slashInputWorld = text.toLowerCase();
|
const slashInputSplitText = text.trim().toLowerCase().split(",");
|
||||||
$("#world_info").find(`option`).filter(function () {
|
console.log(slashInputSplitText);
|
||||||
return $(this).text().toLowerCase() === slashInputWorld;
|
|
||||||
}).prop('selected', true); //matches arg with worldnames and selects; if none found, unsets world
|
slashInputSplitText.forEach((worldName) => {
|
||||||
let setWorldName = $("#world_info").find(":selected").text(); //only for toastr display
|
const wiElement = getWIElement(worldName);
|
||||||
toastr.success(`Active world: ${setWorldName}`);
|
if (wiElement.length > 0) {
|
||||||
selectedWorld = $("#world_info").find(":selected").val();
|
wiElement.prop("selected", true);
|
||||||
} else { //if no args, unset world
|
toastr.success(`Activated world: ${wiElement.text()}`);
|
||||||
toastr.success('Deselected World')
|
} else {
|
||||||
|
toastr.error(`No world found named: ${worldName}`);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else { // if no args, unset all worlds
|
||||||
|
toastr.success('Deactivated all worlds');
|
||||||
|
world_info = [];
|
||||||
$("#world_info").val("");
|
$("#world_info").val("");
|
||||||
}
|
}
|
||||||
} else { //if it's a pointer selection
|
} else { //if it's a pointer selection
|
||||||
selectedWorld = $("#world_info").find(":selected").val();
|
let tempWorldInfo = [];
|
||||||
|
let selectedWorlds = $("#world_info").val().map((e) => Number(e)).filter((e) => !isNaN(e));
|
||||||
|
if (selectedWorlds.length > 0) {
|
||||||
|
selectedWorlds.forEach((worldIndex) => {
|
||||||
|
const existingWorldName = world_names[worldIndex];
|
||||||
|
if (existingWorldName) {
|
||||||
|
tempWorldInfo.push(existingWorldName);
|
||||||
|
} else {
|
||||||
|
const wiElement = getWIElement(existingWorldName);
|
||||||
|
wiElement.prop("selected", false);
|
||||||
|
toastr.error(`The world with ${existingWorldName} is invalid or corrupted.`);
|
||||||
}
|
}
|
||||||
world_info = null;
|
});
|
||||||
if (selectedWorld !== "") {
|
|
||||||
const worldIndex = Number(selectedWorld);
|
|
||||||
world_info = !isNaN(worldIndex) ? world_names[worldIndex] : null;
|
|
||||||
}
|
}
|
||||||
|
world_info = tempWorldInfo;
|
||||||
|
}
|
||||||
|
|
||||||
saveSettingsDebounced();
|
saveSettingsDebounced();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1054,7 +1073,16 @@ jQuery(() => {
|
|||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
registerSlashCommand('world', onWorldInfoChange, [], "– sets active World, or unsets if no args provided", true, true);
|
registerSlashCommand('world', onWorldInfoChange, [], "– sets active World, or unsets if no args provided", true, true);
|
||||||
})
|
})
|
||||||
$("#world_info").on('change', async function () { onWorldInfoChange('__notSlashCommand__') });
|
$("#world_info").on('mousedown change', async function (e) {
|
||||||
|
if (deviceInfo.device.type === 'desktop') {
|
||||||
|
e.preventDefault();
|
||||||
|
const option = $(e.target);
|
||||||
|
option.prop('selected', !option.prop('selected'));
|
||||||
|
await delay(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
onWorldInfoChange('__notSlashCommand__');
|
||||||
|
});
|
||||||
|
|
||||||
//**************************WORLD INFO IMPORT EXPORT*************************//
|
//**************************WORLD INFO IMPORT EXPORT*************************//
|
||||||
$("#world_import_button").on('click', function () {
|
$("#world_import_button").on('click', function () {
|
||||||
|
Reference in New Issue
Block a user