diff --git a/public/scripts/extensions.js b/public/scripts/extensions.js index 115a70c01..313a002ec 100644 --- a/public/scripts/extensions.js +++ b/public/scripts/extensions.js @@ -8,12 +8,38 @@ export { defaultRequestArgs, modules, extension_settings, + ModuleWorkerWrapper, }; let extensionNames = []; let manifests = []; const defaultUrl = "http://localhost:5100"; +// Disables parallel updates +class ModuleWorkerWrapper { + constructor(callback) { + this.isBusy = false; + this.callback = callback; + } + + // Called by the extension + async update() { + // Don't touch me I'm busy... + if (this.isBusy) { + return; + } + + // I'm free. Let's update! + try { + this.isBusy = true; + await this.callback(); + } + finally { + this.isBusy = false; + } + } +} + const extension_settings = { apiUrl: defaultUrl, autoConnect: false, diff --git a/public/scripts/extensions/caption/index.js b/public/scripts/extensions/caption/index.js index 535be7620..314f57b7c 100644 --- a/public/scripts/extensions/caption/index.js +++ b/public/scripts/extensions/caption/index.js @@ -1,122 +1,118 @@ -import { getBase64Async } from "../../utils.js"; -import { getContext, getApiUrl } from "../../extensions.js"; -export { MODULE_NAME }; - -const MODULE_NAME = 'caption'; -const UPDATE_INTERVAL = 1000; - -async function moduleWorker() { - const context = getContext(); - - context.onlineStatus === 'no_connection' - ? $('#send_picture').hide(200) - : $('#send_picture').show(200); -} - -async function setImageIcon() { - try { - const sendButton = $('#send_picture .extensionsMenuExtensionButton'); - sendButton.addClass('fa-image'); - sendButton.removeClass('fa-hourglass-half'); - } - catch (error) { - console.log(error); - } -} - -async function setSpinnerIcon() { - try { - const sendButton = $('#send_picture .extensionsMenuExtensionButton'); - sendButton.removeClass('fa-image'); - sendButton.addClass('fa-hourglass-half'); - } - catch (error) { - console.log(error); - } -} - -async function sendCaptionedMessage(caption, image) { - const context = getContext(); - const messageText = `[${context.name1} sends ${context.name2 ?? ''} a picture that contains: ${caption}]`; - const message = { - name: context.name1, - is_user: true, - is_name: true, - send_date: Date.now(), - mes: messageText, - extra: { - image: image, - title: caption, - }, - }; - context.chat.push(message); - context.addOneMessage(message); - await context.generate(); -} - -async function onSelectImage(e) { - setSpinnerIcon(); - const file = e.target.files[0]; - - if (!file) { - return; - } - - try { - const base64Img = await getBase64Async(file); - const url = new URL(getApiUrl()); - url.pathname = '/api/caption'; - - const apiResult = await fetch(url, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Bypass-Tunnel-Reminder': 'bypass', - }, - body: JSON.stringify({ image: base64Img.split(',')[1] }) - }); - - if (apiResult.ok) { - const data = await apiResult.json(); - const caption = data.caption; - const imageToSave = data.thumbnail ? `data:image/jpeg;base64,${data.thumbnail}` : base64Img; - await sendCaptionedMessage(caption, imageToSave); - } - } - catch (error) { - console.log(error); - } - finally { - e.target.form.reset(); - setImageIcon(); - } -} - -jQuery(function () { - function addSendPictureButton() { - const sendButton = $(` -
-
- Send a picture -
`); - - $('#extensionsMenu').prepend(sendButton); - $(sendButton).hide(); - $(sendButton).on('click', () => $('#img_file').trigger('click')); - } - function addPictureSendForm() { - const inputHtml = ``; - const imgForm = document.createElement('form'); - imgForm.id = 'img_form'; - $(imgForm).append(inputHtml); - $(imgForm).hide(); - $('#form_sheld').append(imgForm); - $('#img_file').on('change', onSelectImage); - } - - addPictureSendForm(); - addSendPictureButton(); - setImageIcon(); - moduleWorker(); - setInterval(moduleWorker, UPDATE_INTERVAL); -}); \ No newline at end of file +import { getBase64Async } from "../../utils.js"; +import { getContext, getApiUrl } from "../../extensions.js"; +export { MODULE_NAME }; + +const MODULE_NAME = 'caption'; +const UPDATE_INTERVAL = 1000; + +async function moduleWorker() { + $('#send_picture').toggle(getContext().onlineStatus !== 'no_connection'); +} + +async function setImageIcon() { + try { + const sendButton = $('#send_picture .extensionsMenuExtensionButton'); + sendButton.addClass('fa-image'); + sendButton.removeClass('fa-hourglass-half'); + } + catch (error) { + console.log(error); + } +} + +async function setSpinnerIcon() { + try { + const sendButton = $('#send_picture .extensionsMenuExtensionButton'); + sendButton.removeClass('fa-image'); + sendButton.addClass('fa-hourglass-half'); + } + catch (error) { + console.log(error); + } +} + +async function sendCaptionedMessage(caption, image) { + const context = getContext(); + const messageText = `[${context.name1} sends ${context.name2 ?? ''} a picture that contains: ${caption}]`; + const message = { + name: context.name1, + is_user: true, + is_name: true, + send_date: Date.now(), + mes: messageText, + extra: { + image: image, + title: caption, + }, + }; + context.chat.push(message); + context.addOneMessage(message); + await context.generate(); +} + +async function onSelectImage(e) { + setSpinnerIcon(); + const file = e.target.files[0]; + + if (!file) { + return; + } + + try { + const base64Img = await getBase64Async(file); + const url = new URL(getApiUrl()); + url.pathname = '/api/caption'; + + const apiResult = await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Bypass-Tunnel-Reminder': 'bypass', + }, + body: JSON.stringify({ image: base64Img.split(',')[1] }) + }); + + if (apiResult.ok) { + const data = await apiResult.json(); + const caption = data.caption; + const imageToSave = data.thumbnail ? `data:image/jpeg;base64,${data.thumbnail}` : base64Img; + await sendCaptionedMessage(caption, imageToSave); + } + } + catch (error) { + console.log(error); + } + finally { + e.target.form.reset(); + setImageIcon(); + } +} + +jQuery(function () { + function addSendPictureButton() { + const sendButton = $(` +
+
+ Send a picture +
`); + + $('#extensionsMenu').prepend(sendButton); + $(sendButton).hide(); + $(sendButton).on('click', () => $('#img_file').trigger('click')); + } + function addPictureSendForm() { + const inputHtml = ``; + const imgForm = document.createElement('form'); + imgForm.id = 'img_form'; + $(imgForm).append(inputHtml); + $(imgForm).hide(); + $('#form_sheld').append(imgForm); + $('#img_file').on('change', onSelectImage); + } + + addPictureSendForm(); + addSendPictureButton(); + setImageIcon(); + moduleWorker(); + setInterval(moduleWorker, UPDATE_INTERVAL); +}); diff --git a/public/scripts/extensions/dice/index.js b/public/scripts/extensions/dice/index.js index ce161a40d..c49cdcdfc 100644 --- a/public/scripts/extensions/dice/index.js +++ b/public/scripts/extensions/dice/index.js @@ -1,100 +1,97 @@ -import { callPopup } from "../../../script.js"; -import { getContext } from "../../extensions.js"; -export { MODULE_NAME }; - -const MODULE_NAME = 'dice'; -const UPDATE_INTERVAL = 1000; - -function setDiceIcon() { - const sendButton = document.getElementById('roll_dice'); - /* sendButton.style.backgroundImage = `url(/img/dice-solid.svg)`; */ - //sendButton.classList.remove('spin'); -} - -async function doDiceRoll() { - let value = $(this).data('value'); - - if (value == 'custom') { - value = await callPopup('Enter the dice formula:
(for example, 2d6)', 'input'); - } - - const isValid = droll.validate(value); - - if (isValid) { - const result = droll.roll(value); - const context = getContext(); - context.sendSystemMessage('generic', `${context.name1} rolls a ${value}. The result is: ${result.total} (${result.rolls})`, { isSmallSys: true }); - } -} - -function addDiceRollButton() { - const buttonHtml = ` -
-
- Roll Dice -
- `; - const dropdownHtml = ` -
- -
`; - - $('#extensionsMenu').prepend(buttonHtml); - - $(document.body).append(dropdownHtml) - $('#dice_dropdown li').on('click', doDiceRoll); - const button = $('#roll_dice'); - const dropdown = $('#dice_dropdown'); - dropdown.hide(); - button.hide(); - - let popper = Popper.createPopper(button.get(0), dropdown.get(0), { - placement: 'bottom', - }); - - $(document).on('click touchend', function (e) { - const target = $(e.target); - if (target.is(dropdown)) return; - if (target.is(button) && !dropdown.is(":visible")) { - e.preventDefault(); - - dropdown.show(200); - popper.update(); - } else { - dropdown.hide(200); - } - }); -} - -function addDiceScript() { - if (!window.droll) { - const script = document.createElement('script'); - script.src = "/scripts/extensions/dice/droll.js"; - document.body.appendChild(script); - } -} - -async function moduleWorker() { - const context = getContext(); - - context.onlineStatus === 'no_connection' - ? $('#roll_dice').hide(200) - : $('#roll_dice').show(200); -} - -$(document).ready(function () { - addDiceScript(); - addDiceRollButton(); - setDiceIcon(); - moduleWorker(); - setInterval(moduleWorker, UPDATE_INTERVAL); -}); \ No newline at end of file +import { callPopup } from "../../../script.js"; +import { getContext } from "../../extensions.js"; +import { registerSlashCommand } from "../../slash-commands.js"; +export { MODULE_NAME }; + +const MODULE_NAME = 'dice'; +const UPDATE_INTERVAL = 1000; + +async function doDiceRoll(customDiceFormula) { + let value = typeof customDiceFormula === 'string' ? customDiceFormula.trim() : $(this).data('value'); + + if (value == 'custom') { + value = await callPopup('Enter the dice formula:
(for example, 2d6)', 'input');x + } + + if (!value) { + return; + } + + const isValid = droll.validate(value); + + if (isValid) { + const result = droll.roll(value); + const context = getContext(); + context.sendSystemMessage('generic', `${context.name1} rolls a ${value}. The result is: ${result.total} (${result.rolls})`, { isSmallSys: true }); + } else { + toastr.warning('Invalid dice formula'); + } +} + +function addDiceRollButton() { + const buttonHtml = ` +
+
+ Roll Dice +
+ `; + const dropdownHtml = ` +
+ +
`; + + $('#extensionsMenu').prepend(buttonHtml); + + $(document.body).append(dropdownHtml) + $('#dice_dropdown li').on('click', doDiceRoll); + const button = $('#roll_dice'); + const dropdown = $('#dice_dropdown'); + dropdown.hide(); + button.hide(); + + let popper = Popper.createPopper(button.get(0), dropdown.get(0), { + placement: 'bottom', + }); + + $(document).on('click touchend', function (e) { + const target = $(e.target); + if (target.is(dropdown)) return; + if (target.is(button) && !dropdown.is(":visible")) { + e.preventDefault(); + + dropdown.show(200); + popper.update(); + } else { + dropdown.hide(200); + } + }); +} + +function addDiceScript() { + if (!window.droll) { + const script = document.createElement('script'); + script.src = "/scripts/extensions/dice/droll.js"; + document.body.appendChild(script); + } +} + +async function moduleWorker() { + $('#roll_dice').toggle(getContext().onlineStatus !== 'no_connection'); +} + +jQuery(function () { + addDiceScript(); + addDiceRollButton(); + moduleWorker(); + setInterval(moduleWorker, UPDATE_INTERVAL); + registerSlashCommand('roll', (_, value) => doDiceRoll(value), [], "(dice formula) – roll the dice. For example, /roll 2d6", false, true); +}); diff --git a/public/scripts/extensions/expressions/index.js b/public/scripts/extensions/expressions/index.js index 14141dce1..5426837ec 100644 --- a/public/scripts/extensions/expressions/index.js +++ b/public/scripts/extensions/expressions/index.js @@ -1,5 +1,5 @@ import { callPopup, getRequestHeaders, saveSettingsDebounced } from "../../../script.js"; -import { getContext, getApiUrl, modules, extension_settings } from "../../extensions.js"; +import { getContext, getApiUrl, modules, extension_settings, ModuleWorkerWrapper } from "../../extensions.js"; export { MODULE_NAME }; const MODULE_NAME = 'expressions'; @@ -58,24 +58,6 @@ function onExpressionsShowDefaultInput() { } } -let isWorkerBusy = false; - -async function moduleWorkerWrapper() { - // Don't touch me I'm busy... - if (isWorkerBusy) { - return; - } - - // I'm free. Let's update! - try { - isWorkerBusy = true; - await moduleWorker(); - } - finally { - isWorkerBusy = false; - } -} - async function moduleWorker() { const context = getContext(); @@ -509,6 +491,7 @@ async function onClickExpressionDelete(event) { addExpressionImage(); addSettings(); - setInterval(moduleWorkerWrapper, UPDATE_INTERVAL); - moduleWorkerWrapper(); + const wrapper = new ModuleWorkerWrapper(moduleWorker); + setInterval(wrapper.update.bind(wrapper), UPDATE_INTERVAL); + moduleWorker(); })(); diff --git a/public/scripts/extensions/floating-prompt/index.js b/public/scripts/extensions/floating-prompt/index.js index f1763732b..a454d56b5 100644 --- a/public/scripts/extensions/floating-prompt/index.js +++ b/public/scripts/extensions/floating-prompt/index.js @@ -1,234 +1,217 @@ -import { chat_metadata, saveSettingsDebounced } from "../../../script.js"; -import { extension_settings, getContext } from "../../extensions.js"; -import { registerSlashCommand } from "../../slash-commands.js"; -import { debounce } from "../../utils.js"; -export { MODULE_NAME }; - -const saveMetadataDebounced = debounce(async () => await getContext().saveMetadata(), 1000); - -const MODULE_NAME = '2_floating_prompt'; // <= Deliberate, for sorting lower than memory -const UPDATE_INTERVAL = 1000; - -const DEFAULT_DEPTH = 4; -const DEFAULT_POSITION = 1; -const DEFAULT_INTERVAL = 1; - -const metadata_keys = { - prompt: 'note_prompt', - interval: 'note_interval', - depth: 'note_depth', - position: 'note_position', -} - -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"); -} - -async function onExtensionFloatingPromptInput() { - chat_metadata[metadata_keys.prompt] = $(this).val(); - saveMetadataDebounced(); -} - -async function onExtensionFloatingIntervalInput() { - chat_metadata[metadata_keys.interval] = Number($(this).val()); - 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; - saveMetadataDebounced(); -} - -async function onExtensionFloatingPositionInput(e) { - chat_metadata[metadata_keys.position] = e.target.value; - saveMetadataDebounced(); -} - -function onExtensionFloatingDefaultInput() { - extension_settings.note.default = $(this).val(); - saveSettingsDebounced(); -} - -function loadSettings() { - chat_metadata[metadata_keys.prompt] = chat_metadata[metadata_keys.prompt] ?? extension_settings.note.default ?? ''; - chat_metadata[metadata_keys.interval] = chat_metadata[metadata_keys.interval] ?? DEFAULT_INTERVAL; - chat_metadata[metadata_keys.position] = chat_metadata[metadata_keys.position] ?? DEFAULT_POSITION; - chat_metadata[metadata_keys.depth] = chat_metadata[metadata_keys.depth] ?? DEFAULT_DEPTH; - $('#extension_floating_prompt').val(chat_metadata[metadata_keys.prompt]); - $('#extension_floating_interval').val(chat_metadata[metadata_keys.interval]); - $('#extension_floating_depth').val(chat_metadata[metadata_keys.depth]); - $(`input[name="extension_floating_position"][value="${chat_metadata[metadata_keys.position]}"]`).prop('checked', true); - $('#extension_floating_default').val(extension_settings.note.default); -} - -let isWorkerBusy = false; - -async function moduleWorkerWrapper() { - // Don't touch me I'm busy... - if (isWorkerBusy) { - return; - } - - // I'm free. Let's update! - try { - isWorkerBusy = true; - await moduleWorker(); - } - finally { - isWorkerBusy = false; - } -} - -async function moduleWorker() { - const context = getContext(); - - if (!context.groupId && context.characterId === undefined) { - return; - } - - loadSettings(); - - // take the count of messages - let lastMessageNumber = Array.isArray(context.chat) && context.chat.length ? context.chat.filter(m => m.is_user).length : 0; - - // special case for new chat - if (Array.isArray(context.chat) && context.chat.length === 1) { - lastMessageNumber = 1; - } - - if (lastMessageNumber <= 0 || chat_metadata[metadata_keys.interval] <= 0) { - context.setExtensionPrompt(MODULE_NAME, ''); - $('#extension_floating_counter').text('(disabled)'); - 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; - const prompt = shouldAddPrompt ? $('#extension_floating_prompt').val() : ''; - context.setExtensionPrompt(MODULE_NAME, prompt, chat_metadata[metadata_keys.position], chat_metadata[metadata_keys.depth]); - $('#extension_floating_counter').text(shouldAddPrompt ? '0' : messagesTillInsertion); -} - -(function () { - function addExtensionsSettings() { - const settingsHtml = ` -
-
-
-
-
- Author's Note -
-
-
- - Unique to this chat.
- Bookmarks inherit the Note from their parent, and can be changed individually after that.
-
- - - -
- - -
- - - - - (0 = Disable) -
- - User inputs until next insertion: (disabled) - -
-
-
-
-
- Default Author's Note -
-
-
- Will be automatically added as the Author's Note for all new chats. - - -
-
-
-
- `; - - $('#movingDivs').append(settingsHtml); - $('#extension_floating_prompt').on('input', onExtensionFloatingPromptInput); - $('#extension_floating_interval').on('input', onExtensionFloatingIntervalInput); - $('#extension_floating_depth').on('input', onExtensionFloatingDepthInput); - $('#extension_floating_default').on('input', onExtensionFloatingDefaultInput); - $('input[name="extension_floating_position"]').on('change', onExtensionFloatingPositionInput); - } - - addExtensionsSettings(); - setInterval(moduleWorkerWrapper, UPDATE_INTERVAL); - registerSlashCommand('note', setNoteTextCommand, [], "(text) – sets an author's note for the currently selected chat", true, true); - registerSlashCommand('depth', setNoteDepthCommand, [], "(number) – sets an author's note depth for in-chat positioning", true, true); - registerSlashCommand('freq', setNoteIntervalCommand, ['interval'], "(number) – sets an author's note insertion frequency", true, true); - registerSlashCommand('pos', setNotePositionCommand, ['position'], "(chat or scenario) – sets an author's note position", true, true); -})(); \ No newline at end of file +import { chat_metadata, saveSettingsDebounced } from "../../../script.js"; +import { ModuleWorkerWrapper, extension_settings, getContext } from "../../extensions.js"; +import { registerSlashCommand } from "../../slash-commands.js"; +import { debounce } from "../../utils.js"; +export { MODULE_NAME }; + +const saveMetadataDebounced = debounce(async () => await getContext().saveMetadata(), 1000); + +const MODULE_NAME = '2_floating_prompt'; // <= Deliberate, for sorting lower than memory +const UPDATE_INTERVAL = 1000; + +const DEFAULT_DEPTH = 4; +const DEFAULT_POSITION = 1; +const DEFAULT_INTERVAL = 1; + +const metadata_keys = { + prompt: 'note_prompt', + interval: 'note_interval', + depth: 'note_depth', + position: 'note_position', +} + +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"); +} + +async function onExtensionFloatingPromptInput() { + chat_metadata[metadata_keys.prompt] = $(this).val(); + saveMetadataDebounced(); +} + +async function onExtensionFloatingIntervalInput() { + chat_metadata[metadata_keys.interval] = Number($(this).val()); + 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; + saveMetadataDebounced(); +} + +async function onExtensionFloatingPositionInput(e) { + chat_metadata[metadata_keys.position] = e.target.value; + saveMetadataDebounced(); +} + +function onExtensionFloatingDefaultInput() { + extension_settings.note.default = $(this).val(); + saveSettingsDebounced(); +} + +function loadSettings() { + chat_metadata[metadata_keys.prompt] = chat_metadata[metadata_keys.prompt] ?? extension_settings.note.default ?? ''; + chat_metadata[metadata_keys.interval] = chat_metadata[metadata_keys.interval] ?? DEFAULT_INTERVAL; + chat_metadata[metadata_keys.position] = chat_metadata[metadata_keys.position] ?? DEFAULT_POSITION; + chat_metadata[metadata_keys.depth] = chat_metadata[metadata_keys.depth] ?? DEFAULT_DEPTH; + $('#extension_floating_prompt').val(chat_metadata[metadata_keys.prompt]); + $('#extension_floating_interval').val(chat_metadata[metadata_keys.interval]); + $('#extension_floating_depth').val(chat_metadata[metadata_keys.depth]); + $(`input[name="extension_floating_position"][value="${chat_metadata[metadata_keys.position]}"]`).prop('checked', true); + $('#extension_floating_default').val(extension_settings.note.default); +} + +async function moduleWorker() { + const context = getContext(); + + if (!context.groupId && context.characterId === undefined) { + return; + } + + loadSettings(); + + // take the count of messages + let lastMessageNumber = Array.isArray(context.chat) && context.chat.length ? context.chat.filter(m => m.is_user).length : 0; + + // special case for new chat + if (Array.isArray(context.chat) && context.chat.length === 1) { + lastMessageNumber = 1; + } + + if (lastMessageNumber <= 0 || chat_metadata[metadata_keys.interval] <= 0) { + context.setExtensionPrompt(MODULE_NAME, ''); + $('#extension_floating_counter').text('(disabled)'); + 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; + const prompt = shouldAddPrompt ? $('#extension_floating_prompt').val() : ''; + context.setExtensionPrompt(MODULE_NAME, prompt, chat_metadata[metadata_keys.position], chat_metadata[metadata_keys.depth]); + $('#extension_floating_counter').text(shouldAddPrompt ? '0' : messagesTillInsertion); +} + +(function () { + function addExtensionsSettings() { + const settingsHtml = ` +
+
+
+
+
+ Author's Note +
+
+
+ + Unique to this chat.
+ Bookmarks inherit the Note from their parent, and can be changed individually after that.
+
+ + + +
+ + +
+ + + + + (0 = Disable) +
+ + User inputs until next insertion: (disabled) + +
+
+
+
+
+ Default Author's Note +
+
+
+ Will be automatically added as the Author's Note for all new chats. + + +
+
+
+
+ `; + + $('#movingDivs').append(settingsHtml); + $('#extension_floating_prompt').on('input', onExtensionFloatingPromptInput); + $('#extension_floating_interval').on('input', onExtensionFloatingIntervalInput); + $('#extension_floating_depth').on('input', onExtensionFloatingDepthInput); + $('#extension_floating_default').on('input', onExtensionFloatingDefaultInput); + $('input[name="extension_floating_position"]').on('change', onExtensionFloatingPositionInput); + } + + addExtensionsSettings(); + const wrapper = new ModuleWorkerWrapper(moduleWorker); + setInterval(wrapper.update.bind(wrapper), UPDATE_INTERVAL); + registerSlashCommand('note', setNoteTextCommand, [], "(text) – sets an author's note for the currently selected chat", true, true); + registerSlashCommand('depth', setNoteDepthCommand, [], "(number) – sets an author's note depth for in-chat positioning", true, true); + registerSlashCommand('freq', setNoteIntervalCommand, ['interval'], "(number) – sets an author's note insertion frequency", true, true); + registerSlashCommand('pos', setNotePositionCommand, ['position'], "(chat or scenario) – sets an author's note position", true, true); +})(); diff --git a/public/scripts/extensions/memory/index.js b/public/scripts/extensions/memory/index.js index a508c2d5a..c58888710 100644 --- a/public/scripts/extensions/memory/index.js +++ b/public/scripts/extensions/memory/index.js @@ -1,388 +1,371 @@ -import { getStringHash, debounce } from "../../utils.js"; -import { getContext, getApiUrl, extension_settings } from "../../extensions.js"; -import { extension_prompt_types, is_send_press, saveSettingsDebounced } from "../../../script.js"; -export { MODULE_NAME }; - -const MODULE_NAME = '1_memory'; -const UPDATE_INTERVAL = 5000; - -let lastCharacterId = null; -let lastGroupId = null; -let lastChatId = null; -let lastMessageHash = null; -let lastMessageId = null; -let inApiCall = false; - -const formatMemoryValue = (value) => value ? `Context: ${value.trim()}` : ''; -const saveChatDebounced = debounce(() => getContext().saveChat(), 2000); - -const defaultSettings = { - minLongMemory: 16, - maxLongMemory: 1024, - longMemoryLength: 128, - shortMemoryLength: 512, - minShortMemory: 128, - maxShortMemory: 1024, - shortMemoryStep: 16, - longMemoryStep: 8, - repetitionPenaltyStep: 0.05, - repetitionPenalty: 1.2, - maxRepetitionPenalty: 2.0, - minRepetitionPenalty: 1.0, - temperature: 1.0, - minTemperature: 0.1, - maxTemperature: 2.0, - temperatureStep: 0.05, - lengthPenalty: 1, - minLengthPenalty: -4, - maxLengthPenalty: 4, - lengthPenaltyStep: 0.1, - memoryFrozen: false, -}; - -function loadSettings() { - if (Object.keys(extension_settings.memory).length === 0) { - Object.assign(extension_settings.memory, defaultSettings); - } - - $('#memory_long_length').val(extension_settings.memory.longMemoryLength).trigger('input'); - $('#memory_short_length').val(extension_settings.memory.shortMemoryLength).trigger('input'); - $('#memory_repetition_penalty').val(extension_settings.memory.repetitionPenalty).trigger('input'); - $('#memory_temperature').val(extension_settings.memory.temperature).trigger('input'); - $('#memory_length_penalty').val(extension_settings.memory.lengthPenalty).trigger('input'); - $('#memory_frozen').prop('checked', extension_settings.memory.memoryFrozen).trigger('input'); -} - -function onMemoryShortInput() { - const value = $(this).val(); - extension_settings.memory.shortMemoryLength = Number(value); - $('#memory_short_length_tokens').text(value); - saveSettingsDebounced(); - - // Don't let long buffer be bigger than short - if (extension_settings.memory.longMemoryLength > extension_settings.memory.shortMemoryLength) { - $('#memory_long_length').val(extension_settings.memory.shortMemoryLength).trigger('input'); - } -} - -function onMemoryLongInput() { - const value = $(this).val(); - extension_settings.memory.longMemoryLength = Number(value); - $('#memory_long_length_tokens').text(value); - saveSettingsDebounced(); - - // Don't let long buffer be bigger than short - if (extension_settings.memory.longMemoryLength > extension_settings.memory.shortMemoryLength) { - $('#memory_short_length').val(extension_settings.memory.longMemoryLength).trigger('input'); - } -} - -function onMemoryRepetitionPenaltyInput() { - const value = $(this).val(); - extension_settings.memory.repetitionPenalty = Number(value); - $('#memory_repetition_penalty_value').text(extension_settings.memory.repetitionPenalty.toFixed(2)); - saveSettingsDebounced(); -} - -function onMemoryTemperatureInput() { - const value = $(this).val(); - extension_settings.memory.temperature = Number(value); - $('#memory_temperature_value').text(extension_settings.memory.temperature.toFixed(2)); - saveSettingsDebounced(); -} - -function onMemoryLengthPenaltyInput() { - const value = $(this).val(); - extension_settings.memory.lengthPenalty = Number(value); - $('#memory_length_penalty_value').text(extension_settings.memory.lengthPenalty.toFixed(2)); - saveSettingsDebounced(); -} - -function onMemoryFrozenInput() { - const value = Boolean($(this).prop('checked')); - extension_settings.memory.memoryFrozen = value; - saveSettingsDebounced(); -} - -function saveLastValues() { - const context = getContext(); - lastGroupId = context.groupId; - lastCharacterId = context.characterId; - lastChatId = context.chatId; - lastMessageId = context.chat?.length ?? null; - lastMessageHash = getStringHash((context.chat.length && context.chat[context.chat.length - 1]['mes']) ?? ''); -} - -function getLatestMemoryFromChat(chat) { - if (!Array.isArray(chat) || !chat.length) { - return ''; - } - - const reversedChat = chat.slice().reverse(); - reversedChat.shift(); - for (let mes of reversedChat) { - if (mes.extra && mes.extra.memory) { - return mes.extra.memory; - } - } - - return ''; -} - -let isWorkerBusy = false; - -async function moduleWorkerWrapper() { - // Don't touch me I'm busy... - if (isWorkerBusy) { - return; - } - - // I'm free. Let's update! - try { - isWorkerBusy = true; - await moduleWorker(); - } - finally { - isWorkerBusy = false; - } -} - -async function moduleWorker() { - const context = getContext(); - const chat = context.chat; - - // no characters or group selected - if (!context.groupId && context.characterId === undefined) { - return; - } - - // Generation is in progress, summary prevented - if (is_send_press) { - return; - } - - // Chat/character/group changed - if ((context.groupId && lastGroupId !== context.groupId) || (context.characterId !== lastCharacterId) || (context.chatId !== lastChatId)) { - const latestMemory = getLatestMemoryFromChat(chat); - setMemoryContext(latestMemory, false); - saveLastValues(); - return; - } - - // Currently summarizing or frozen state - skip - if (inApiCall || extension_settings.memory.memoryFrozen) { - return; - } - - // No new messages - do nothing - if (chat.length === 0 || (lastMessageId === chat.length && getStringHash(chat[chat.length - 1].mes) === lastMessageHash)) { - return; - } - - // Messages has been deleted - rewrite the context with the latest available memory - if (chat.length < lastMessageId) { - const latestMemory = getLatestMemoryFromChat(chat); - setMemoryContext(latestMemory, false); - } - - // Message has been edited / regenerated - delete the saved memory - if (chat.length - && chat[chat.length - 1].extra - && chat[chat.length - 1].extra.memory - && lastMessageId === chat.length - && getStringHash(chat[chat.length - 1].mes) !== lastMessageHash) { - delete chat[chat.length - 1].extra.memory; - } - - try { - await summarizeChat(context); - } - catch (error) { - console.log(error); - } - finally { - saveLastValues(); - } -} - -async function summarizeChat(context) { - function getMemoryString() { - return (longMemory + '\n\n' + memoryBuffer.slice().reverse().join('\n\n')).trim(); - } - - const chat = context.chat; - const longMemory = getLatestMemoryFromChat(chat); - const reversedChat = chat.slice().reverse(); - reversedChat.shift(); - let memoryBuffer = []; - - for (let mes of reversedChat) { - // we reached the point of latest memory - if (longMemory && mes.extra && mes.extra.memory == longMemory) { - break; - } - - // don't care about system - if (mes.is_system) { - continue; - } - - // determine the sender's name - const name = mes.is_user ? (context.name1 ?? 'You') : (mes.force_avatar ? mes.name : context.name2); - const entry = `${name}:\n${mes['mes']}`; - memoryBuffer.push(entry); - - // check if token limit was reached - if (context.getTokenCount(getMemoryString()) >= extension_settings.memory.shortMemoryLength) { - break; - } - } - - const resultingString = getMemoryString(); - - if (context.getTokenCount(resultingString) < extension_settings.memory.shortMemoryLength) { - return; - } - - // perform the summarization API call - try { - inApiCall = true; - const url = new URL(getApiUrl()); - url.pathname = '/api/summarize'; - - const apiResult = await fetch(url, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Bypass-Tunnel-Reminder': 'bypass', - }, - body: JSON.stringify({ - text: resultingString, - params: { - min_length: extension_settings.memory.longMemoryLength * 0, // testing how it behaves 0 min length - max_length: extension_settings.memory.longMemoryLength, - repetition_penalty: extension_settings.memory.repetitionPenalty, - temperature: extension_settings.memory.temperature, - length_penalty: extension_settings.memory.lengthPenalty, - } - }) - }); - - if (apiResult.ok) { - const data = await apiResult.json(); - const summary = data.summary; - - const newContext = getContext(); - - // something changed during summarization request - if (newContext.groupId !== context.groupId - || newContext.chatId !== context.chatId - || (!newContext.groupId && (newContext.characterId !== context.characterId))) { - console.log('Context changed, summary discarded'); - return; - } - - setMemoryContext(summary, true); - } - } - catch (error) { - console.log(error); - } - finally { - inApiCall = false; - } -} - -function onMemoryRestoreClick() { - const context = getContext(); - const content = $('#memory_contents').val(); - const reversedChat = context.chat.slice().reverse(); - reversedChat.shift(); - - for (let mes of reversedChat) { - if (mes.extra && mes.extra.memory == content) { - delete mes.extra.memory; - break; - } - } - - const newContent = getLatestMemoryFromChat(context.chat); - setMemoryContext(newContent, false); -} - -function onMemoryContentInput() { - const value = $(this).val(); - setMemoryContext(value, true); -} - -function setMemoryContext(value, saveToMessage) { - const context = getContext(); - context.setExtensionPrompt(MODULE_NAME, formatMemoryValue(value), extension_prompt_types.AFTER_SCENARIO); - $('#memory_contents').val(value); - - if (saveToMessage && context.chat.length) { - const idx = context.chat.length - 2; - const mes = context.chat[idx < 0 ? 0 : idx]; - - if (!mes.extra) { - mes.extra = {}; - } - - mes.extra.memory = value; - saveChatDebounced(); - } -} - -$(document).ready(function () { - function addExtensionControls() { - const settingsHtml = ` -
-
-
- Chat memory -
-
-
- - -
- - -
-
-
-
-
- Summarization parameters -
-
-
- - - - - - - - - - -
-
-
- `; - $('#extensions_settings').append(settingsHtml); - $('#memory_restore').on('click', onMemoryRestoreClick); - $('#memory_contents').on('input', onMemoryContentInput); - $('#memory_long_length').on('input', onMemoryLongInput); - $('#memory_short_length').on('input', onMemoryShortInput); - $('#memory_repetition_penalty').on('input', onMemoryRepetitionPenaltyInput); - $('#memory_temperature').on('input', onMemoryTemperatureInput); - $('#memory_length_penalty').on('input', onMemoryLengthPenaltyInput); - $('#memory_frozen').on('input', onMemoryFrozenInput); - } - - addExtensionControls(); - loadSettings(); - setInterval(moduleWorkerWrapper, UPDATE_INTERVAL); -}); \ No newline at end of file +import { getStringHash, debounce } from "../../utils.js"; +import { getContext, getApiUrl, extension_settings, ModuleWorkerWrapper } from "../../extensions.js"; +import { extension_prompt_types, is_send_press, saveSettingsDebounced } from "../../../script.js"; +export { MODULE_NAME }; + +const MODULE_NAME = '1_memory'; +const UPDATE_INTERVAL = 5000; + +let lastCharacterId = null; +let lastGroupId = null; +let lastChatId = null; +let lastMessageHash = null; +let lastMessageId = null; +let inApiCall = false; + +const formatMemoryValue = (value) => value ? `Context: ${value.trim()}` : ''; +const saveChatDebounced = debounce(() => getContext().saveChat(), 2000); + +const defaultSettings = { + minLongMemory: 16, + maxLongMemory: 1024, + longMemoryLength: 128, + shortMemoryLength: 512, + minShortMemory: 128, + maxShortMemory: 1024, + shortMemoryStep: 16, + longMemoryStep: 8, + repetitionPenaltyStep: 0.05, + repetitionPenalty: 1.2, + maxRepetitionPenalty: 2.0, + minRepetitionPenalty: 1.0, + temperature: 1.0, + minTemperature: 0.1, + maxTemperature: 2.0, + temperatureStep: 0.05, + lengthPenalty: 1, + minLengthPenalty: -4, + maxLengthPenalty: 4, + lengthPenaltyStep: 0.1, + memoryFrozen: false, +}; + +function loadSettings() { + if (Object.keys(extension_settings.memory).length === 0) { + Object.assign(extension_settings.memory, defaultSettings); + } + + $('#memory_long_length').val(extension_settings.memory.longMemoryLength).trigger('input'); + $('#memory_short_length').val(extension_settings.memory.shortMemoryLength).trigger('input'); + $('#memory_repetition_penalty').val(extension_settings.memory.repetitionPenalty).trigger('input'); + $('#memory_temperature').val(extension_settings.memory.temperature).trigger('input'); + $('#memory_length_penalty').val(extension_settings.memory.lengthPenalty).trigger('input'); + $('#memory_frozen').prop('checked', extension_settings.memory.memoryFrozen).trigger('input'); +} + +function onMemoryShortInput() { + const value = $(this).val(); + extension_settings.memory.shortMemoryLength = Number(value); + $('#memory_short_length_tokens').text(value); + saveSettingsDebounced(); + + // Don't let long buffer be bigger than short + if (extension_settings.memory.longMemoryLength > extension_settings.memory.shortMemoryLength) { + $('#memory_long_length').val(extension_settings.memory.shortMemoryLength).trigger('input'); + } +} + +function onMemoryLongInput() { + const value = $(this).val(); + extension_settings.memory.longMemoryLength = Number(value); + $('#memory_long_length_tokens').text(value); + saveSettingsDebounced(); + + // Don't let long buffer be bigger than short + if (extension_settings.memory.longMemoryLength > extension_settings.memory.shortMemoryLength) { + $('#memory_short_length').val(extension_settings.memory.longMemoryLength).trigger('input'); + } +} + +function onMemoryRepetitionPenaltyInput() { + const value = $(this).val(); + extension_settings.memory.repetitionPenalty = Number(value); + $('#memory_repetition_penalty_value').text(extension_settings.memory.repetitionPenalty.toFixed(2)); + saveSettingsDebounced(); +} + +function onMemoryTemperatureInput() { + const value = $(this).val(); + extension_settings.memory.temperature = Number(value); + $('#memory_temperature_value').text(extension_settings.memory.temperature.toFixed(2)); + saveSettingsDebounced(); +} + +function onMemoryLengthPenaltyInput() { + const value = $(this).val(); + extension_settings.memory.lengthPenalty = Number(value); + $('#memory_length_penalty_value').text(extension_settings.memory.lengthPenalty.toFixed(2)); + saveSettingsDebounced(); +} + +function onMemoryFrozenInput() { + const value = Boolean($(this).prop('checked')); + extension_settings.memory.memoryFrozen = value; + saveSettingsDebounced(); +} + +function saveLastValues() { + const context = getContext(); + lastGroupId = context.groupId; + lastCharacterId = context.characterId; + lastChatId = context.chatId; + lastMessageId = context.chat?.length ?? null; + lastMessageHash = getStringHash((context.chat.length && context.chat[context.chat.length - 1]['mes']) ?? ''); +} + +function getLatestMemoryFromChat(chat) { + if (!Array.isArray(chat) || !chat.length) { + return ''; + } + + const reversedChat = chat.slice().reverse(); + reversedChat.shift(); + for (let mes of reversedChat) { + if (mes.extra && mes.extra.memory) { + return mes.extra.memory; + } + } + + return ''; +} + +async function moduleWorker() { + const context = getContext(); + const chat = context.chat; + + // no characters or group selected + if (!context.groupId && context.characterId === undefined) { + return; + } + + // Generation is in progress, summary prevented + if (is_send_press) { + return; + } + + // Chat/character/group changed + if ((context.groupId && lastGroupId !== context.groupId) || (context.characterId !== lastCharacterId) || (context.chatId !== lastChatId)) { + const latestMemory = getLatestMemoryFromChat(chat); + setMemoryContext(latestMemory, false); + saveLastValues(); + return; + } + + // Currently summarizing or frozen state - skip + if (inApiCall || extension_settings.memory.memoryFrozen) { + return; + } + + // No new messages - do nothing + if (chat.length === 0 || (lastMessageId === chat.length && getStringHash(chat[chat.length - 1].mes) === lastMessageHash)) { + return; + } + + // Messages has been deleted - rewrite the context with the latest available memory + if (chat.length < lastMessageId) { + const latestMemory = getLatestMemoryFromChat(chat); + setMemoryContext(latestMemory, false); + } + + // Message has been edited / regenerated - delete the saved memory + if (chat.length + && chat[chat.length - 1].extra + && chat[chat.length - 1].extra.memory + && lastMessageId === chat.length + && getStringHash(chat[chat.length - 1].mes) !== lastMessageHash) { + delete chat[chat.length - 1].extra.memory; + } + + try { + await summarizeChat(context); + } + catch (error) { + console.log(error); + } + finally { + saveLastValues(); + } +} + +async function summarizeChat(context) { + function getMemoryString() { + return (longMemory + '\n\n' + memoryBuffer.slice().reverse().join('\n\n')).trim(); + } + + const chat = context.chat; + const longMemory = getLatestMemoryFromChat(chat); + const reversedChat = chat.slice().reverse(); + reversedChat.shift(); + let memoryBuffer = []; + + for (let mes of reversedChat) { + // we reached the point of latest memory + if (longMemory && mes.extra && mes.extra.memory == longMemory) { + break; + } + + // don't care about system + if (mes.is_system) { + continue; + } + + // determine the sender's name + const name = mes.is_user ? (context.name1 ?? 'You') : (mes.force_avatar ? mes.name : context.name2); + const entry = `${name}:\n${mes['mes']}`; + memoryBuffer.push(entry); + + // check if token limit was reached + if (context.getTokenCount(getMemoryString()) >= extension_settings.memory.shortMemoryLength) { + break; + } + } + + const resultingString = getMemoryString(); + + if (context.getTokenCount(resultingString) < extension_settings.memory.shortMemoryLength) { + return; + } + + // perform the summarization API call + try { + inApiCall = true; + const url = new URL(getApiUrl()); + url.pathname = '/api/summarize'; + + const apiResult = await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Bypass-Tunnel-Reminder': 'bypass', + }, + body: JSON.stringify({ + text: resultingString, + params: { + min_length: extension_settings.memory.longMemoryLength * 0, // testing how it behaves 0 min length + max_length: extension_settings.memory.longMemoryLength, + repetition_penalty: extension_settings.memory.repetitionPenalty, + temperature: extension_settings.memory.temperature, + length_penalty: extension_settings.memory.lengthPenalty, + } + }) + }); + + if (apiResult.ok) { + const data = await apiResult.json(); + const summary = data.summary; + + const newContext = getContext(); + + // something changed during summarization request + if (newContext.groupId !== context.groupId + || newContext.chatId !== context.chatId + || (!newContext.groupId && (newContext.characterId !== context.characterId))) { + console.log('Context changed, summary discarded'); + return; + } + + setMemoryContext(summary, true); + } + } + catch (error) { + console.log(error); + } + finally { + inApiCall = false; + } +} + +function onMemoryRestoreClick() { + const context = getContext(); + const content = $('#memory_contents').val(); + const reversedChat = context.chat.slice().reverse(); + reversedChat.shift(); + + for (let mes of reversedChat) { + if (mes.extra && mes.extra.memory == content) { + delete mes.extra.memory; + break; + } + } + + const newContent = getLatestMemoryFromChat(context.chat); + setMemoryContext(newContent, false); +} + +function onMemoryContentInput() { + const value = $(this).val(); + setMemoryContext(value, true); +} + +function setMemoryContext(value, saveToMessage) { + const context = getContext(); + context.setExtensionPrompt(MODULE_NAME, formatMemoryValue(value), extension_prompt_types.AFTER_SCENARIO); + $('#memory_contents').val(value); + + if (saveToMessage && context.chat.length) { + const idx = context.chat.length - 2; + const mes = context.chat[idx < 0 ? 0 : idx]; + + if (!mes.extra) { + mes.extra = {}; + } + + mes.extra.memory = value; + saveChatDebounced(); + } +} + +$(document).ready(function () { + function addExtensionControls() { + const settingsHtml = ` +
+
+
+ Chat memory +
+
+
+ + +
+ + +
+
+
+
+
+ Summarization parameters +
+
+
+ + + + + + + + + + +
+
+
+ `; + $('#extensions_settings').append(settingsHtml); + $('#memory_restore').on('click', onMemoryRestoreClick); + $('#memory_contents').on('input', onMemoryContentInput); + $('#memory_long_length').on('input', onMemoryLongInput); + $('#memory_short_length').on('input', onMemoryShortInput); + $('#memory_repetition_penalty').on('input', onMemoryRepetitionPenaltyInput); + $('#memory_temperature').on('input', onMemoryTemperatureInput); + $('#memory_length_penalty').on('input', onMemoryLengthPenaltyInput); + $('#memory_frozen').on('input', onMemoryFrozenInput); + } + + addExtensionControls(); + loadSettings(); + const wrapper = new ModuleWorkerWrapper(moduleWorker); + setInterval(wrapper.update.bind(wrapper), UPDATE_INTERVAL); +}); diff --git a/public/scripts/extensions/tts/index.js b/public/scripts/extensions/tts/index.js index d08efcfd6..2a75accfe 100644 --- a/public/scripts/extensions/tts/index.js +++ b/public/scripts/extensions/tts/index.js @@ -1,5 +1,5 @@ import { callPopup, cancelTtsPlay, eventSource, event_types, isMultigenEnabled, is_send_press, saveSettingsDebounced } from '../../../script.js' -import { extension_settings, getContext } from '../../extensions.js' +import { ModuleWorkerWrapper, extension_settings, getContext } from '../../extensions.js' import { getStringHash } from '../../utils.js' import { ElevenLabsTtsProvider } from './elevenlabs.js' import { SileroTtsProvider } from './silerotts.js' @@ -38,24 +38,6 @@ async function onNarrateOneMessage() { moduleWorker(); } -let isWorkerBusy = false; - -async function moduleWorkerWrapper() { - // Don't touch me I'm busy... - if (isWorkerBusy) { - return; - } - - // I'm free. Let's update! - try { - isWorkerBusy = true; - await moduleWorker(); - } - finally { - isWorkerBusy = false; - } -} - async function moduleWorker() { // Primarily determining when to add new chat to the TTS queue const enabled = $('#tts_enabled').is(':checked') @@ -661,6 +643,7 @@ $(document).ready(function () { loadSettings() // Depends on Extension Controls and loadTtsProvider loadTtsProvider(extension_settings.tts.currentProvider) // No dependencies addAudioControl() // Depends on Extension Controls - setInterval(moduleWorkerWrapper, UPDATE_INTERVAL) // Init depends on all the things + const wrapper = new ModuleWorkerWrapper(moduleWorker); + setInterval(wrapper.update.bind(wrapper), UPDATE_INTERVAL) // Init depends on all the things eventSource.on(event_types.MESSAGE_SWIPED, resetTtsPlayback); })