mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2024-12-13 10:00:36 +01:00
2c1a6ca67c
Add support for adding extension prompts into WI scanning. Doing this required adding a method to get extension prompts by name. Now, AN and depth prompts can be added to world info scanning. However, since scanning just looks for keys in the chatlog, append the extension prompts on the top of scanned text. Signed-off-by: kingbri <bdashore3@proton.me>
431 lines
16 KiB
JavaScript
431 lines
16 KiB
JavaScript
import {
|
||
chat_metadata,
|
||
eventSource,
|
||
event_types,
|
||
saveSettingsDebounced,
|
||
this_chid,
|
||
} from "../script.js";
|
||
import { selected_group } from "./group-chats.js";
|
||
import { extension_settings, getContext, saveMetadataDebounced } from "./extensions.js";
|
||
import { registerSlashCommand } from "./slash-commands.js";
|
||
import { getCharaFilename, debounce, waitUntilCondition, delay } from "./utils.js";
|
||
import { getTokenCount } from "./tokenizers.js";
|
||
export { MODULE_NAME as NOTE_MODULE_NAME };
|
||
|
||
const MODULE_NAME = '2_floating_prompt'; // <= Deliberate, for sorting lower than memory
|
||
|
||
export var shouldWIAddPrompt = false;
|
||
|
||
export const metadata_keys = {
|
||
prompt: 'note_prompt',
|
||
interval: 'note_interval',
|
||
depth: 'note_depth',
|
||
position: 'note_position',
|
||
}
|
||
|
||
const chara_note_position = {
|
||
replace: 0,
|
||
before: 1,
|
||
after: 2,
|
||
}
|
||
|
||
function setNoteTextCommand(_, text) {
|
||
$('#extension_floating_prompt').val(text).trigger('input');
|
||
toastr.success("Author's Note text updated");
|
||
}
|
||
|
||
function setNoteDepthCommand(_, text) {
|
||
const value = Number(text);
|
||
|
||
if (Number.isNaN(value)) {
|
||
toastr.error('Not a valid number');
|
||
return;
|
||
}
|
||
|
||
$('#extension_floating_depth').val(Math.abs(value)).trigger('input');
|
||
toastr.success("Author's Note depth updated");
|
||
}
|
||
|
||
function setNoteIntervalCommand(_, text) {
|
||
const value = Number(text);
|
||
|
||
if (Number.isNaN(value)) {
|
||
toastr.error('Not a valid number');
|
||
return;
|
||
}
|
||
|
||
$('#extension_floating_interval').val(Math.abs(value)).trigger('input');
|
||
toastr.success("Author's Note frequency updated");
|
||
}
|
||
|
||
function setNotePositionCommand(_, text) {
|
||
const validPositions = {
|
||
'scenario': 0,
|
||
'chat': 1,
|
||
};
|
||
|
||
const position = validPositions[text?.trim()];
|
||
|
||
if (Number.isNaN(position)) {
|
||
toastr.error('Not a valid position');
|
||
return;
|
||
}
|
||
|
||
$(`input[name="extension_floating_position"][value="${position}"]`).prop('checked', true).trigger('input');
|
||
toastr.info("Author's Note position updated");
|
||
}
|
||
|
||
function updateSettings() {
|
||
saveSettingsDebounced();
|
||
loadSettings();
|
||
setFloatingPrompt();
|
||
}
|
||
|
||
const setMainPromptTokenCounterDebounced = debounce((value) => $('#extension_floating_prompt_token_counter').text(getTokenCount(value)), 1000);
|
||
const setCharaPromptTokenCounterDebounced = debounce((value) => $('#extension_floating_chara_token_counter').text(getTokenCount(value)), 1000);
|
||
const setDefaultPromptTokenCounterDebounced = debounce((value) => $('#extension_floating_default_token_counter').text(getTokenCount(value)), 1000);
|
||
|
||
async function onExtensionFloatingPromptInput() {
|
||
chat_metadata[metadata_keys.prompt] = $(this).val();
|
||
setMainPromptTokenCounterDebounced(chat_metadata[metadata_keys.prompt]);
|
||
updateSettings();
|
||
saveMetadataDebounced();
|
||
}
|
||
|
||
async function onExtensionFloatingIntervalInput() {
|
||
chat_metadata[metadata_keys.interval] = Number($(this).val());
|
||
updateSettings();
|
||
saveMetadataDebounced();
|
||
}
|
||
|
||
async function onExtensionFloatingDepthInput() {
|
||
let value = Number($(this).val());
|
||
|
||
if (value < 0) {
|
||
value = Math.abs(value);
|
||
$(this).val(value);
|
||
}
|
||
|
||
chat_metadata[metadata_keys.depth] = value;
|
||
updateSettings();
|
||
saveMetadataDebounced();
|
||
}
|
||
|
||
async function onExtensionFloatingPositionInput(e) {
|
||
chat_metadata[metadata_keys.position] = e.target.value;
|
||
updateSettings();
|
||
saveMetadataDebounced();
|
||
}
|
||
|
||
async function onDefaultPositionInput(e) {
|
||
extension_settings.note.defaultPosition = e.target.value;
|
||
saveSettingsDebounced();
|
||
}
|
||
|
||
async function onDefaultDepthInput() {
|
||
let value = Number($(this).val());
|
||
|
||
if (value < 0) {
|
||
value = Math.abs(value);
|
||
$(this).val(value);
|
||
}
|
||
|
||
extension_settings.note.defaultDepth = value;
|
||
saveSettingsDebounced();
|
||
}
|
||
|
||
async function onDefaultIntervalInput() {
|
||
extension_settings.note.defaultInterval = Number($(this).val());
|
||
saveSettingsDebounced();
|
||
}
|
||
|
||
async function onExtensionFloatingCharPositionInput(e) {
|
||
const value = e.target.value;
|
||
const charaNote = extension_settings.note.chara.find((e) => e.name === getCharaFilename());
|
||
|
||
if (charaNote) {
|
||
charaNote.position = Number(value);
|
||
updateSettings();
|
||
}
|
||
}
|
||
|
||
function onExtensionFloatingCharaPromptInput() {
|
||
const tempPrompt = $(this).val();
|
||
const avatarName = getCharaFilename();
|
||
let tempCharaNote = {
|
||
name: avatarName,
|
||
prompt: tempPrompt
|
||
}
|
||
|
||
setCharaPromptTokenCounterDebounced(tempPrompt);
|
||
|
||
let existingCharaNoteIndex;
|
||
let existingCharaNote;
|
||
|
||
if (extension_settings.note.chara) {
|
||
existingCharaNoteIndex = extension_settings.note.chara.findIndex((e) => e.name === avatarName);
|
||
existingCharaNote = extension_settings.note.chara[existingCharaNoteIndex]
|
||
}
|
||
|
||
if (tempPrompt.length === 0 &&
|
||
extension_settings.note.chara &&
|
||
existingCharaNote &&
|
||
!existingCharaNote.useChara
|
||
) {
|
||
extension_settings.note.chara.splice(existingCharaNoteIndex, 1);
|
||
}
|
||
else if (extension_settings.note.chara && existingCharaNote) {
|
||
Object.assign(existingCharaNote, tempCharaNote);
|
||
}
|
||
else if (avatarName && tempPrompt.length > 0) {
|
||
if (!extension_settings.note.chara) {
|
||
extension_settings.note.chara = []
|
||
}
|
||
Object.assign(tempCharaNote, { useChara: false, position: chara_note_position.replace })
|
||
|
||
extension_settings.note.chara.push(tempCharaNote);
|
||
} else {
|
||
console.log("Character author's note error: No avatar name key could be found.");
|
||
toastr.error("Something went wrong. Could not save character's author's note.");
|
||
|
||
// Don't save settings if something went wrong
|
||
return;
|
||
}
|
||
|
||
updateSettings();
|
||
}
|
||
|
||
function onExtensionFloatingCharaCheckboxChanged() {
|
||
const value = !!$(this).prop('checked');
|
||
const charaNote = extension_settings.note.chara.find((e) => e.name === getCharaFilename());
|
||
|
||
if (charaNote) {
|
||
charaNote.useChara = value;
|
||
|
||
updateSettings();
|
||
}
|
||
}
|
||
|
||
function onExtensionFloatingDefaultInput() {
|
||
extension_settings.note.default = $(this).val();
|
||
setDefaultPromptTokenCounterDebounced(extension_settings.note.default);
|
||
updateSettings();
|
||
}
|
||
|
||
function loadSettings() {
|
||
const DEFAULT_DEPTH = 4;
|
||
const DEFAULT_POSITION = 1;
|
||
const DEFAULT_INTERVAL = 1;
|
||
|
||
if (extension_settings.note.defaultPosition === undefined) {
|
||
extension_settings.note.defaultPosition = DEFAULT_POSITION;
|
||
}
|
||
|
||
if (extension_settings.note.defaultDepth === undefined) {
|
||
extension_settings.note.defaultDepth = DEFAULT_DEPTH;
|
||
}
|
||
|
||
if (extension_settings.note.defaultInterval === undefined) {
|
||
extension_settings.note.defaultInterval = DEFAULT_INTERVAL;
|
||
}
|
||
|
||
chat_metadata[metadata_keys.prompt] = chat_metadata[metadata_keys.prompt] ?? extension_settings.note.default ?? '';
|
||
chat_metadata[metadata_keys.interval] = chat_metadata[metadata_keys.interval] ?? extension_settings.note.defaultInterval ?? DEFAULT_INTERVAL;
|
||
chat_metadata[metadata_keys.position] = chat_metadata[metadata_keys.position] ?? extension_settings.note.defaultPosition ?? DEFAULT_POSITION;
|
||
chat_metadata[metadata_keys.depth] = chat_metadata[metadata_keys.depth] ?? extension_settings.note.defaultDepth ?? DEFAULT_DEPTH;
|
||
$('#extension_floating_prompt').val(chat_metadata[metadata_keys.prompt]);
|
||
$('#extension_floating_interval').val(chat_metadata[metadata_keys.interval]);
|
||
$('#extension_floating_allow_wi_scan').prop('checked', extension_settings.note.allowWIScan ?? false);
|
||
$('#extension_floating_depth').val(chat_metadata[metadata_keys.depth]);
|
||
$(`input[name="extension_floating_position"][value="${chat_metadata[metadata_keys.position]}"]`).prop('checked', true);
|
||
|
||
if (extension_settings.note.chara && getContext().characterId) {
|
||
const charaNote = extension_settings.note.chara.find((e) => e.name === getCharaFilename());
|
||
|
||
$('#extension_floating_chara').val(charaNote ? charaNote.prompt : '');
|
||
$('#extension_use_floating_chara').prop('checked', charaNote ? charaNote.useChara : false);
|
||
$(`input[name="extension_floating_char_position"][value="${charaNote?.position ?? chara_note_position.replace}"]`).prop('checked', true);
|
||
} else {
|
||
$('#extension_floating_chara').val('');
|
||
$('#extension_use_floating_chara').prop('checked', false);
|
||
$(`input[name="extension_floating_char_position"][value="${chara_note_position.replace}"]`).prop('checked', true);
|
||
}
|
||
|
||
$('#extension_floating_default').val(extension_settings.note.default);
|
||
$('#extension_default_depth').val(extension_settings.note.defaultDepth);
|
||
$('#extension_default_interval').val(extension_settings.note.defaultInterval);
|
||
$(`input[name="extension_default_position"][value="${extension_settings.note.defaultPosition}"]`).prop('checked', true);
|
||
}
|
||
|
||
export function setFloatingPrompt() {
|
||
const context = getContext();
|
||
if (!context.groupId && context.characterId === undefined) {
|
||
console.debug('setFloatingPrompt: Not in a chat. Skipping.');
|
||
shouldWIAddPrompt = false;
|
||
return;
|
||
}
|
||
|
||
// take the count of messages
|
||
let lastMessageNumber = Array.isArray(context.chat) && context.chat.length ? context.chat.filter(m => m.is_user).length : 0;
|
||
|
||
console.debug(`
|
||
setFloatingPrompt entered
|
||
------
|
||
lastMessageNumber = ${lastMessageNumber}
|
||
metadata_keys.interval = ${chat_metadata[metadata_keys.interval]}
|
||
`)
|
||
|
||
// interval 1 should be inserted no matter what
|
||
if (chat_metadata[metadata_keys.interval] === 1) {
|
||
lastMessageNumber = 1;
|
||
}
|
||
|
||
if (lastMessageNumber <= 0 || chat_metadata[metadata_keys.interval] <= 0) {
|
||
context.setExtensionPrompt(MODULE_NAME, '');
|
||
$('#extension_floating_counter').text('(disabled)');
|
||
shouldWIAddPrompt = false;
|
||
return;
|
||
}
|
||
|
||
const messagesTillInsertion = lastMessageNumber >= chat_metadata[metadata_keys.interval]
|
||
? (lastMessageNumber % chat_metadata[metadata_keys.interval])
|
||
: (chat_metadata[metadata_keys.interval] - lastMessageNumber);
|
||
const shouldAddPrompt = messagesTillInsertion == 0;
|
||
shouldWIAddPrompt = shouldAddPrompt;
|
||
|
||
let prompt = shouldAddPrompt ? $('#extension_floating_prompt').val() : '';
|
||
if (shouldAddPrompt && extension_settings.note.chara && getContext().characterId) {
|
||
const charaNote = extension_settings.note.chara.find((e) => e.name === getCharaFilename());
|
||
|
||
// Only replace with the chara note if the user checked the box
|
||
if (charaNote && charaNote.useChara) {
|
||
switch (charaNote.position) {
|
||
case chara_note_position.before:
|
||
prompt = charaNote.prompt + '\n' + prompt;
|
||
break;
|
||
case chara_note_position.after:
|
||
prompt = prompt + '\n' + charaNote.prompt;
|
||
break;
|
||
default:
|
||
prompt = charaNote.prompt;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
context.setExtensionPrompt(MODULE_NAME, prompt, chat_metadata[metadata_keys.position], chat_metadata[metadata_keys.depth]);
|
||
$('#extension_floating_counter').text(shouldAddPrompt ? '0' : messagesTillInsertion);
|
||
}
|
||
|
||
function onANMenuItemClick() {
|
||
if (selected_group || this_chid) {
|
||
//show AN if it's hidden
|
||
if ($("#floatingPrompt").css("display") !== 'flex') {
|
||
$("#floatingPrompt").addClass('resizing')
|
||
$("#floatingPrompt").css("display", "flex");
|
||
$("#floatingPrompt").css("opacity", 0.0);
|
||
$("#floatingPrompt").transition({
|
||
opacity: 1.0,
|
||
duration: 250,
|
||
}, async function () {
|
||
await delay(50);
|
||
$("#floatingPrompt").removeClass('resizing')
|
||
});
|
||
|
||
//auto-open the main AN inline drawer
|
||
if ($("#ANBlockToggle")
|
||
.siblings('.inline-drawer-content')
|
||
.css('display') !== 'block') {
|
||
$("#floatingPrompt").addClass('resizing')
|
||
$("#ANBlockToggle").click();
|
||
}
|
||
} else {
|
||
//hide AN if it's already displayed
|
||
$("#floatingPrompt").addClass('resizing')
|
||
$("#floatingPrompt").transition({
|
||
opacity: 0.0,
|
||
duration: 250,
|
||
},
|
||
async function () {
|
||
await delay(50);
|
||
$("#floatingPrompt").removeClass('resizing')
|
||
});
|
||
setTimeout(function () {
|
||
$("#floatingPrompt").hide();
|
||
}, 250);
|
||
|
||
}
|
||
//duplicate options menu close handler from script.js
|
||
//because this listener takes priority
|
||
$("#options").stop().fadeOut(250);
|
||
} else {
|
||
toastr.warning(`Select a character before trying to use Author's Note`, '', { timeOut: 2000 });
|
||
}
|
||
}
|
||
|
||
function onChatChanged() {
|
||
loadSettings();
|
||
setFloatingPrompt();
|
||
const context = getContext();
|
||
|
||
// Disable the chara note if in a group
|
||
$('#extension_floating_chara').prop('disabled', context.groupId ? true : false);
|
||
|
||
const tokenCounter1 = chat_metadata[metadata_keys.prompt] ? getTokenCount(chat_metadata[metadata_keys.prompt]) : 0;
|
||
$('#extension_floating_prompt_token_counter').text(tokenCounter1);
|
||
|
||
let tokenCounter2;
|
||
if (extension_settings.note.chara && context.characterId) {
|
||
const charaNote = extension_settings.note.chara.find((e) => e.name === getCharaFilename());
|
||
|
||
if (charaNote) {
|
||
tokenCounter2 = getTokenCount(charaNote.prompt);
|
||
}
|
||
}
|
||
|
||
if (tokenCounter2) {
|
||
$('#extension_floating_chara_token_counter').text(tokenCounter2);
|
||
}
|
||
|
||
const tokenCounter3 = extension_settings.note.default ? getTokenCount(extension_settings.note.default) : 0;
|
||
$('#extension_floating_default_token_counter').text(tokenCounter3);
|
||
}
|
||
|
||
function onAllowWIScanCheckboxChanged() {
|
||
extension_settings.note.allowWIScan = !!$(this).prop('checked');
|
||
updateSettings();
|
||
}
|
||
|
||
/**
|
||
* Inject author's note options and setup event listeners.
|
||
*/
|
||
// Inserts the extension first since it's statically imported
|
||
export function initAuthorsNote() {
|
||
$('#extension_floating_prompt').on('input', onExtensionFloatingPromptInput);
|
||
$('#extension_floating_interval').on('input', onExtensionFloatingIntervalInput);
|
||
$('#extension_floating_depth').on('input', onExtensionFloatingDepthInput);
|
||
$('#extension_floating_chara').on('input', onExtensionFloatingCharaPromptInput);
|
||
$('#extension_use_floating_chara').on('input', onExtensionFloatingCharaCheckboxChanged);
|
||
$('#extension_floating_default').on('input', onExtensionFloatingDefaultInput);
|
||
$('#extension_default_depth').on('input', onDefaultDepthInput);
|
||
$('#extension_default_interval').on('input', onDefaultIntervalInput);
|
||
$('#extension_floating_allow_wi_scan').on('input', onAllowWIScanCheckboxChanged);
|
||
$('input[name="extension_floating_position"]').on('change', onExtensionFloatingPositionInput);
|
||
$('input[name="extension_default_position"]').on('change', onDefaultPositionInput);
|
||
$('input[name="extension_floating_char_position"]').on('change', onExtensionFloatingCharPositionInput);
|
||
$('#ANClose').on('click', function () {
|
||
$("#floatingPrompt").transition({
|
||
opacity: 0,
|
||
duration: 200,
|
||
easing: 'ease-in-out',
|
||
});
|
||
setTimeout(function () { $('#floatingPrompt').hide() }, 200);
|
||
})
|
||
$("#option_toggle_AN").on('click', onANMenuItemClick);
|
||
|
||
registerSlashCommand('note', setNoteTextCommand, [], "<span class='monospace'>(text)</span> – sets an author's note for the currently selected chat", true, true);
|
||
registerSlashCommand('depth', setNoteDepthCommand, [], "<span class='monospace'>(number)</span> – sets an author's note depth for in-chat positioning", true, true);
|
||
registerSlashCommand('freq', setNoteIntervalCommand, ['interval'], "<span class='monospace'>(number)</span> – sets an author's note insertion frequency", true, true);
|
||
registerSlashCommand('pos', setNotePositionCommand, ['position'], "(<span class='monospace'>chat</span> or <span class='monospace'>scenario</span>) – sets an author's note position", true, true);
|
||
eventSource.on(event_types.CHAT_CHANGED, onChatChanged);
|
||
}
|