From fe355c5d4f0ab44da1c75e19aa22abcaae8ec0ed Mon Sep 17 00:00:00 2001
From: Cohee <18619528+Cohee1207@users.noreply.github.com>
Date: Fri, 1 Dec 2023 01:50:10 +0200
Subject: [PATCH] Add MVP set of WI manipulation commands
---
public/scripts/variables.js | 4 +
public/scripts/world-info.js | 152 ++++++++++++++++++++++++++++++++++-
2 files changed, 154 insertions(+), 2 deletions(-)
diff --git a/public/scripts/variables.js b/public/scripts/variables.js
index 5cf2f06e2..26480befa 100644
--- a/public/scripts/variables.js
+++ b/public/scripts/variables.js
@@ -95,6 +95,10 @@ function decrementGlobalVariable(name) {
* @returns {string} Variable value or the string literal
*/
export function resolveVariable(name) {
+ if (name === undefined) {
+ return '';
+ }
+
if (existsLocalVariable(name)) {
return getLocalVariable(name);
}
diff --git a/public/scripts/world-info.js b/public/scripts/world-info.js
index af0aa29a8..0df3ff95b 100644
--- a/public/scripts/world-info.js
+++ b/public/scripts/world-info.js
@@ -8,6 +8,7 @@ import { FILTER_TYPES, FilterHelper } from "./filters.js";
import { getTokenCount } from "./tokenizers.js";
import { power_user } from "./power-user.js";
import { getTagKeyForCharacter } from "./tags.js";
+import { resolveVariable } from "./variables.js";
export {
world_info,
@@ -189,6 +190,149 @@ function setWorldInfoSettings(settings, data) {
const hasWorldInfo = !!chat_metadata[METADATA_KEY] && world_names.includes(chat_metadata[METADATA_KEY]);
$('.chat_lorebook_button').toggleClass('world_set', hasWorldInfo);
});
+
+ // Add slash commands
+ registerWorldInfoSlashCommands();
+}
+
+function registerWorldInfoSlashCommands() {
+ async function getEntriesFromFile(file) {
+ if (!file || !world_names.includes(file)) {
+ return '';
+ }
+
+ const data = await loadWorldInfoData(file);
+
+ if (!data || !("entries" in data)) {
+ return '';
+ }
+
+ const entries = Object.values(data.entries);
+
+ if (!entries || entries.length === 0) {
+ return '';
+ }
+
+ return entries;
+ }
+
+ async function getChatBookCallback() {
+ const chatId = getCurrentChatId();
+
+ if (!chatId) {
+ return '';
+ }
+
+ if (chat_metadata[METADATA_KEY] && world_names.includes(chat_metadata[METADATA_KEY])) {
+ return chat_metadata[METADATA_KEY];
+ }
+
+ // Replace non-alphanumeric characters with underscores, cut to 64 characters
+ const name = `Chat Book ${getCurrentChatId()}`.replace(/[^a-z0-9]/gi, '_').replace(/_{2,}/g, '_').substring(0, 64);
+ await createNewWorldInfo(name);
+
+ chat_metadata[METADATA_KEY] = name;
+ await saveMetadata();
+ $('.chat_lorebook_button').addClass('world_set');
+ return name;
+ }
+
+ async function findBookEntryCallback(args, value) {
+ const file = resolveVariable(args.file);
+ const field = args.field || 'key';
+
+ const entries = await getEntriesFromFile(file);
+
+ if (!entries) {
+ return '';
+ }
+
+ const fuse = new Fuse(entries, {
+ keys: [{ name: field, weight: 1 }],
+ includeScore: true,
+ threshold: 0.3,
+ });
+
+ const results = fuse.search(value);
+
+ if (!results || results.length === 0) {
+ return '';
+ }
+
+ const result = results[0]?.item?.uid;
+
+ if (result === undefined) {
+ return '';
+ }
+
+ return result;
+ }
+
+ async function getEntryFieldCallback(args, uid) {
+ const file = resolveVariable(args.file);
+ const field = args.field || 'content';
+
+ const entries = await getEntriesFromFile(file);
+
+ if (!entries) {
+ return '';
+ }
+
+ const entry = entries.find(x => x.uid === uid);
+
+ if (!entry) {
+ return '';
+ }
+
+ const fieldValue = entry[field];
+
+ if (fieldValue === undefined) {
+ return '';
+ }
+
+ if (Array.isArray(fieldValue)) {
+ return fieldValue.map(x => substituteParams(x)).join(', ');
+ }
+
+ return substituteParams(fieldValue);
+ }
+
+ async function createEntryCallback(args, content) {
+ const file = resolveVariable(args.file);
+ const key = args.key;
+
+ const data = await loadWorldInfoData(file);
+
+ if (!data || !("entries" in data)) {
+ return '';
+ }
+
+ const entry = createWorldInfoEntry(file, data, true);
+
+ if (key) {
+ entry.key.push(key);
+ entry.addMemo = true;
+ entry.comment = key;
+ }
+
+ if (content) {
+ entry.content = content;
+ }
+
+ await saveWorldInfo(file, data, true);
+
+ const selectedIndex = world_names.indexOf(file);
+ if (selectedIndex !== -1) {
+ $('#world_editor_select').val(selectedIndex).trigger('change');
+ }
+
+ return entry.uid;
+ }
+
+ registerSlashCommand('getchatbook', getChatBookCallback, ['getchatlore', 'getchatwi'], '– get a name of the chat-bound lorebook or create a new one if was unbound, and pass it down the pipe', true, true);
+ registerSlashCommand('findentry', findBookEntryCallback, ['findlore', 'findwi'], `(file=bookName by=field [texts]) – find a UID of the record from the specified book using the fuzzy match of a field value (default: key) and pass it down the pipe, e.g. /findentry file=chatLore by=key Shadowfang`, true, true);
+ registerSlashCommand('getentryfield', getEntryFieldCallback, ['getlorefield', 'getwifield'], '(file=bookName field=field [UID]) – get a field value (default: content) of the record with the UID from the specified book and pass it down the pipe, e.g. /getentryfield file=chatLore field=content 123', true, true);
+ registerSlashCommand('createentry', createEntryCallback, ['createlore', 'createwi'], '(file=bookName key=key [content]) – create a new record in the specified book with the key and content (both are optional) and pass the UID down the pipe, e.g. /createentry file=chatLore key=Shadowfang The sword of the king', true, true);
}
// World Info Editor
@@ -1134,7 +1278,7 @@ async function deleteWorldInfoEntry(data, uid) {
delete data.entries[uid];
}
-function createWorldInfoEntry(name, data) {
+function createWorldInfoEntry(name, data, fromSlashCommand = false) {
const newEntryTemplate = {
key: [],
keysecondary: [],
@@ -1161,7 +1305,11 @@ function createWorldInfoEntry(name, data) {
const newEntry = { uid: newUid, ...newEntryTemplate };
data.entries[newUid] = newEntry;
- updateEditor(newUid);
+ if (!fromSlashCommand) {
+ updateEditor(newUid);
+ }
+
+ return newEntry;
}
async function _save(name, data) {