mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
Export characters with world info embeds.
This commit is contained in:
@ -2887,8 +2887,7 @@
|
|||||||
<div class="range-block-counter justifyLeft flex-container flexFlowColumn margin-bot-10px">
|
<div class="range-block-counter justifyLeft flex-container flexFlowColumn margin-bot-10px">
|
||||||
A selected World Info / Lorebook will be bound to this character.
|
A selected World Info / Lorebook will be bound to this character.
|
||||||
When generating an AI reply, it will be combined with the entries from a global World Info / Lorebook selector.
|
When generating an AI reply, it will be combined with the entries from a global World Info / Lorebook selector.
|
||||||
<!-- Telling lies? Uncomment when actually implemented. -->
|
Exporting a character would also export the selected World Info file embedded in the JSON data.
|
||||||
<!-- Exporting a character would also export the selected World Info file embedded in the JSON data. -->
|
|
||||||
</div>
|
</div>
|
||||||
<div class="range-block-range wide100p">
|
<div class="range-block-range wide100p">
|
||||||
<select class="character_world_info_selector">
|
<select class="character_world_info_selector">
|
||||||
|
@ -4891,7 +4891,8 @@ function checkEmbeddedWorld(chid) {
|
|||||||
|
|
||||||
// Only show the alert once per character
|
// Only show the alert once per character
|
||||||
const checkKey = `AlertWI_${characters[chid].avatar}`;
|
const checkKey = `AlertWI_${characters[chid].avatar}`;
|
||||||
if (!localStorage.getItem(checkKey) && !(characters[chid]?.data?.extensions?.world)) {
|
const worldName = characters[chid]?.data?.extensions?.world;
|
||||||
|
if (!localStorage.getItem(checkKey) && (!worldName || !world_names.includes(worldName))) {
|
||||||
toastr.info(
|
toastr.info(
|
||||||
'To import and use it, select "Import Embedded World Info" in the Options menu.',
|
'To import and use it, select "Import Embedded World Info" in the Options menu.',
|
||||||
`${characters[chid].name} has an embedded World/Lorebook`,
|
`${characters[chid].name} has an embedded World/Lorebook`,
|
||||||
|
@ -250,6 +250,41 @@ function displayWorldEntries(name, data) {
|
|||||||
$("#world_popup_entries_list").disableSelection();
|
$("#world_popup_entries_list").disableSelection();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setOriginalDataValue(data, uid, key, value) {
|
||||||
|
if (data.originalData && Array.isArray(data.originalData.entries)) {
|
||||||
|
let originalEntry = data.originalData.entries.find(x => x.uid === uid);
|
||||||
|
|
||||||
|
if (!originalEntry) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const keyParts = key.split('.');
|
||||||
|
let currentObject = originalEntry;
|
||||||
|
|
||||||
|
for (let i = 0; i < keyParts.length - 1; i++) {
|
||||||
|
const part = keyParts[i];
|
||||||
|
|
||||||
|
if (!currentObject.hasOwnProperty(part)) {
|
||||||
|
currentObject[part] = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
currentObject = currentObject[part];
|
||||||
|
}
|
||||||
|
|
||||||
|
currentObject[keyParts[keyParts.length - 1]] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteOriginalDataValue(data, uid) {
|
||||||
|
if (data.originalData && Array.isArray(data.originalData.entries)) {
|
||||||
|
const originalIndex = data.originalData.entries.findIndex(x => x.uid === uid);
|
||||||
|
|
||||||
|
if (originalIndex >= 0) {
|
||||||
|
data.originalData.entries.splice(originalIndex, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function appendWorldEntry(name, data, entry) {
|
function appendWorldEntry(name, data, entry) {
|
||||||
const template = $("#entry_edit_template .world_entry").clone();
|
const template = $("#entry_edit_template .world_entry").clone();
|
||||||
template.data("uid", entry.uid);
|
template.data("uid", entry.uid);
|
||||||
@ -270,6 +305,8 @@ function appendWorldEntry(name, data, entry) {
|
|||||||
.split(",")
|
.split(",")
|
||||||
.map((x) => x.trim())
|
.map((x) => x.trim())
|
||||||
.filter((x) => x);
|
.filter((x) => x);
|
||||||
|
|
||||||
|
setOriginalDataValue(data, uid, "keys", data.entries[uid].key);
|
||||||
saveWorldInfo(name, data);
|
saveWorldInfo(name, data);
|
||||||
});
|
});
|
||||||
keyInput.val(entry.key.join(",")).trigger("input");
|
keyInput.val(entry.key.join(",")).trigger("input");
|
||||||
@ -286,6 +323,8 @@ function appendWorldEntry(name, data, entry) {
|
|||||||
.split(",")
|
.split(",")
|
||||||
.map((x) => x.trim())
|
.map((x) => x.trim())
|
||||||
.filter((x) => x);
|
.filter((x) => x);
|
||||||
|
|
||||||
|
setOriginalDataValue(data, uid, "secondary_keys", data.entries[uid].keysecondary);
|
||||||
saveWorldInfo(name, data);
|
saveWorldInfo(name, data);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -300,6 +339,8 @@ function appendWorldEntry(name, data, entry) {
|
|||||||
const uid = $(this).data("uid");
|
const uid = $(this).data("uid");
|
||||||
const value = $(this).val();
|
const value = $(this).val();
|
||||||
data.entries[uid].comment = value;
|
data.entries[uid].comment = value;
|
||||||
|
|
||||||
|
setOriginalDataValue(data, uid, "comment", data.entries[uid].comment);
|
||||||
saveWorldInfo(name, data);
|
saveWorldInfo(name, data);
|
||||||
});
|
});
|
||||||
commentToggle.data("uid", entry.uid);
|
commentToggle.data("uid", entry.uid);
|
||||||
@ -333,6 +374,8 @@ function appendWorldEntry(name, data, entry) {
|
|||||||
const uid = $(this).data("uid");
|
const uid = $(this).data("uid");
|
||||||
const value = $(this).val();
|
const value = $(this).val();
|
||||||
data.entries[uid].content = value;
|
data.entries[uid].content = value;
|
||||||
|
|
||||||
|
setOriginalDataValue(data, uid, "content", data.entries[uid].content);
|
||||||
saveWorldInfo(name, data);
|
saveWorldInfo(name, data);
|
||||||
|
|
||||||
// count tokens
|
// count tokens
|
||||||
@ -348,6 +391,8 @@ function appendWorldEntry(name, data, entry) {
|
|||||||
const uid = $(this).data("uid");
|
const uid = $(this).data("uid");
|
||||||
const value = $(this).prop("checked");
|
const value = $(this).prop("checked");
|
||||||
data.entries[uid].selective = value;
|
data.entries[uid].selective = value;
|
||||||
|
|
||||||
|
setOriginalDataValue(data, uid, "selective", data.entries[uid].selective);
|
||||||
saveWorldInfo(name, data);
|
saveWorldInfo(name, data);
|
||||||
|
|
||||||
const keysecondary = $(this)
|
const keysecondary = $(this)
|
||||||
@ -378,6 +423,7 @@ function appendWorldEntry(name, data, entry) {
|
|||||||
const uid = $(this).data("uid");
|
const uid = $(this).data("uid");
|
||||||
const value = $(this).prop("checked");
|
const value = $(this).prop("checked");
|
||||||
data.entries[uid].constant = value;
|
data.entries[uid].constant = value;
|
||||||
|
setOriginalDataValue(data, uid, "constant", data.entries[uid].constant);
|
||||||
saveWorldInfo(name, data);
|
saveWorldInfo(name, data);
|
||||||
});
|
});
|
||||||
constantInput.prop("checked", entry.constant).trigger("input");
|
constantInput.prop("checked", entry.constant).trigger("input");
|
||||||
@ -390,6 +436,7 @@ function appendWorldEntry(name, data, entry) {
|
|||||||
const value = Number($(this).val());
|
const value = Number($(this).val());
|
||||||
|
|
||||||
data.entries[uid].order = !isNaN(value) ? value : 0;
|
data.entries[uid].order = !isNaN(value) ? value : 0;
|
||||||
|
setOriginalDataValue(data, uid, "insertion_order", data.entries[uid].order);
|
||||||
saveWorldInfo(name, data);
|
saveWorldInfo(name, data);
|
||||||
});
|
});
|
||||||
orderInput.val(entry.order).trigger("input");
|
orderInput.val(entry.order).trigger("input");
|
||||||
@ -405,6 +452,10 @@ function appendWorldEntry(name, data, entry) {
|
|||||||
const uid = $(this).data("uid");
|
const uid = $(this).data("uid");
|
||||||
const value = Number($(this).val());
|
const value = Number($(this).val());
|
||||||
data.entries[uid].position = !isNaN(value) ? value : 0;
|
data.entries[uid].position = !isNaN(value) ? value : 0;
|
||||||
|
// Spec v2 only supports before_char and after_char
|
||||||
|
setOriginalDataValue(data, uid, "position", data.entries[uid].position == 0 ? 'before_char' : 'after_char');
|
||||||
|
// Write the original value as extensions field
|
||||||
|
setOriginalDataValue(data, uid, "extensions.position", data.entries[uid].position);
|
||||||
saveWorldInfo(name, data);
|
saveWorldInfo(name, data);
|
||||||
});
|
});
|
||||||
template
|
template
|
||||||
@ -422,6 +473,7 @@ function appendWorldEntry(name, data, entry) {
|
|||||||
const uid = $(this).data("uid");
|
const uid = $(this).data("uid");
|
||||||
const value = $(this).prop("checked");
|
const value = $(this).prop("checked");
|
||||||
data.entries[uid].disable = value;
|
data.entries[uid].disable = value;
|
||||||
|
setOriginalDataValue(data, uid, "enabled", !data.entries[uid].disable);
|
||||||
saveWorldInfo(name, data);
|
saveWorldInfo(name, data);
|
||||||
});
|
});
|
||||||
disableInput.prop("checked", entry.disable).trigger("input");
|
disableInput.prop("checked", entry.disable).trigger("input");
|
||||||
@ -432,6 +484,7 @@ function appendWorldEntry(name, data, entry) {
|
|||||||
const uid = $(this).data("uid");
|
const uid = $(this).data("uid");
|
||||||
const value = $(this).prop("checked");
|
const value = $(this).prop("checked");
|
||||||
data.entries[uid].excludeRecursion = value;
|
data.entries[uid].excludeRecursion = value;
|
||||||
|
setOriginalDataValue(data, uid, "extensions.exclude_recursion", data.entries[uid].excludeRecursion);
|
||||||
saveWorldInfo(name, data);
|
saveWorldInfo(name, data);
|
||||||
});
|
});
|
||||||
excludeRecursionInput.prop("checked", entry.excludeRecursion).trigger("input");
|
excludeRecursionInput.prop("checked", entry.excludeRecursion).trigger("input");
|
||||||
@ -442,6 +495,7 @@ function appendWorldEntry(name, data, entry) {
|
|||||||
deleteButton.on("click", function () {
|
deleteButton.on("click", function () {
|
||||||
const uid = $(this).data("uid");
|
const uid = $(this).data("uid");
|
||||||
deleteWorldInfoEntry(data, uid);
|
deleteWorldInfoEntry(data, uid);
|
||||||
|
deleteOriginalDataValue(data, uid);
|
||||||
$(this).closest(".world_entry").remove();
|
$(this).closest(".world_entry").remove();
|
||||||
saveWorldInfo(name, data);
|
saveWorldInfo(name, data);
|
||||||
});
|
});
|
||||||
@ -877,9 +931,14 @@ function convertNovelLorebook(inputObj) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function convertCharacterBook(characterBook) {
|
function convertCharacterBook(characterBook) {
|
||||||
const result = { entries: {} };
|
const result = { entries: {}, originalData: characterBook };
|
||||||
|
|
||||||
characterBook.entries.forEach((entry, index) => {
|
characterBook.entries.forEach((entry, index) => {
|
||||||
|
// Not in the spec, but this is needed to find the entry in the original data
|
||||||
|
if (entry.id === undefined) {
|
||||||
|
entry.id = index;
|
||||||
|
}
|
||||||
|
|
||||||
result.entries[index] = {
|
result.entries[index] = {
|
||||||
uid: entry.id || index,
|
uid: entry.id || index,
|
||||||
key: entry.keys,
|
key: entry.keys,
|
||||||
@ -889,7 +948,8 @@ function convertCharacterBook(characterBook) {
|
|||||||
constant: entry.constant || false,
|
constant: entry.constant || false,
|
||||||
selective: entry.selective || false,
|
selective: entry.selective || false,
|
||||||
order: entry.insertion_order,
|
order: entry.insertion_order,
|
||||||
position: entry.position === "before_char" ? world_info_position.before : world_info_position.after,
|
position: entry.extensions?.position ?? (entry.position === "before_char" ? world_info_position.before : world_info_position.after),
|
||||||
|
excludeRecursion: entry.extensions?.exclude_recursion ?? false,
|
||||||
disable: !entry.enabled,
|
disable: !entry.enabled,
|
||||||
addMemo: entry.comment ? true : false,
|
addMemo: entry.comment ? true : false,
|
||||||
};
|
};
|
||||||
|
49
server.js
49
server.js
@ -835,8 +835,24 @@ function charaFormatData(data) {
|
|||||||
//_.set(char, 'data.extensions.avatar', 'none');
|
//_.set(char, 'data.extensions.avatar', 'none');
|
||||||
//_.set(char, 'data.extensions.chat', data.ch_name + ' - ' + humanizedISO8601DateTime());
|
//_.set(char, 'data.extensions.chat', data.ch_name + ' - ' + humanizedISO8601DateTime());
|
||||||
|
|
||||||
// TODO: Character book
|
if (data.world) {
|
||||||
_//.set(char, 'data.character_book', undefined);
|
try {
|
||||||
|
const file = readWorldInfoFile(data.world);
|
||||||
|
|
||||||
|
// File was imported - save it to the character book
|
||||||
|
if (file && file.originalData) {
|
||||||
|
_.set(char, 'data.character_book', file.originalData);
|
||||||
|
}
|
||||||
|
|
||||||
|
// File was not imported - convert the world info to the character book
|
||||||
|
if (file && file.entries) {
|
||||||
|
_.set(char, 'data.character_book', convertWorldInfoToCharacterBook(data.world, file.entries));
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch {
|
||||||
|
console.debug(`Failed to read world info file: ${data.world}. Character book will not be available.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return char;
|
return char;
|
||||||
}
|
}
|
||||||
@ -1437,6 +1453,35 @@ app.post('/savetheme', jsonParser, (request, response) => {
|
|||||||
return response.sendStatus(200);
|
return response.sendStatus(200);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function convertWorldInfoToCharacterBook(name, entries) {
|
||||||
|
const result = { entries: [], name };
|
||||||
|
|
||||||
|
for (const index in entries) {
|
||||||
|
const entry = entries[index];
|
||||||
|
|
||||||
|
const originalEntry = {
|
||||||
|
id: entry.uid,
|
||||||
|
keys: entry.key,
|
||||||
|
secondary_keys: entry.keysecondary,
|
||||||
|
comment: entry.comment,
|
||||||
|
content: entry.content,
|
||||||
|
constant: entry.constant,
|
||||||
|
selective: entry.selective,
|
||||||
|
insertion_order: entry.order,
|
||||||
|
enabled: !entry.disable,
|
||||||
|
position: entry.position == 0 ? 'before_char' : 'after_char',
|
||||||
|
extensions: {
|
||||||
|
position: entry.position,
|
||||||
|
exclude_recursion: entry.excludeRecursion,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
result.entries.push(originalEntry);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
function readWorldInfoFile(worldInfoName) {
|
function readWorldInfoFile(worldInfoName) {
|
||||||
if (!worldInfoName) {
|
if (!worldInfoName) {
|
||||||
return { entries: {} };
|
return { entries: {} };
|
||||||
|
Reference in New Issue
Block a user