diff --git a/public/index.html b/public/index.html index 3e1ea7a85..c2a48ddc0 100644 --- a/public/index.html +++ b/public/index.html @@ -874,6 +874,7 @@

Active extensions

+

Missing something? Press here for more details!

Extension settings

diff --git a/public/scripts/extensions.js b/public/scripts/extensions.js index 82a6f043a..4a94fb5d2 100644 --- a/public/scripts/extensions.js +++ b/public/scripts/extensions.js @@ -1,3 +1,4 @@ +import { callPopup } from "../script.js"; import { isSubsetOf } from "./utils.js"; export { getContext, @@ -9,7 +10,10 @@ const extensionNames = ['caption', 'dice', 'expressions', 'floating-prompt', 'me const manifests = await getManifests(extensionNames); const extensions_urlKey = 'extensions_url'; const extensions_autoConnectKey = 'extensions_autoconnect'; +const extensions_disabledKey = 'extensions_disabled'; + let modules = []; +let disabledExtensions = getDisabledExtensions(); let activeExtensions = new Set(); const getContext = () => window['TavernAI'].getContext(); @@ -18,6 +22,33 @@ const defaultUrl = "http://localhost:5100"; const defaultRequestArgs = { method: 'GET', headers: { 'Bypass-Tunnel-Reminder': 'bypass' } }; let connectedToApi = false; +function getDisabledExtensions() { + const value = localStorage.getItem(extensions_disabledKey); + return value ? JSON.parse(value) : []; +} + +function onDisableExtensionClick() { + const name = $(this).data('name'); + disableExtension(name); +} + +function onEnableExtensionClick() { + const name = $(this).data('name'); + enableExtension(name); +} + +function enableExtension(name) { + disabledExtensions = disabledExtensions.filter(x => x !== name); + localStorage.setItem(extensions_disabledKey, JSON.stringify(disabledExtensions)); + location.reload(); +} + +function disableExtension(name) { + disabledExtensions.push(name); + localStorage.setItem(extensions_disabledKey, JSON.stringify(disabledExtensions)); + location.reload(); +} + async function getManifests(names) { const obj = {}; for (const name of names) { @@ -45,10 +76,22 @@ async function activateExtensions() { // all required modules are active (offline extensions require none) if (isSubsetOf(modules, manifest.requires)) { try { - await addExtensionScript(name, manifest); - await addExtensionStyle(name, manifest); - activeExtensions.add(name); - $('#extensions_list').append(`
  • ${manifest.display_name}
  • `); + const isDisabled = disabledExtensions.includes(name); + const li = document.createElement('li'); + + if (!isDisabled) { + await addExtensionScript(name, manifest); + await addExtensionStyle(name, manifest); + activeExtensions.add(name); + } + else { + li.classList.add('disabled'); + } + + li.id = name; + li.innerText = manifest.display_name; + + $('#extensions_list').append(li); } catch (error) { console.error(`Could not activate extension: ${name}`); @@ -156,6 +199,32 @@ function addExtensionScript(name, manifest) { return Promise.resolve(); } +function showExtensionsDetails() { + let html = '

    Modules provided by your Extensions API:

    '; + html += modules.length ? modules.join(', ') : '

    Not connected to the API!

    '; + html += '

    Available extensions:

    '; + + Object.entries(manifests).sort((a, b) => a[1].loading_order - b[1].loading_order).forEach(extension => { + const name = extension[0]; + const manifest = extension[1]; + html += `

    ${manifest.display_name}

    `; + if (activeExtensions.has(name)) { + html += `

    Extension is active. Disable

    `; + } + else if (disabledExtensions.includes(name)) { + html += `

    Extension is disabled. Enable

    `; + } + else { + const requirements = new Set(manifest.requires); + modules.forEach(x => requirements.delete(x)); + const requirementsString = [...requirements].join(', '); + html += `

    Missing modules: ${requirementsString}

    ` + } + }); + + callPopup(`
    ${html}
    `, 'text'); +} + $(document).ready(async function () { const url = localStorage.getItem(extensions_urlKey) ?? defaultUrl; const autoConnect = localStorage.getItem(extensions_autoConnectKey) == 'true'; @@ -163,6 +232,9 @@ $(document).ready(async function () { $("#extensions_connect").on('click', connectClickHandler); $("#extensions_autoconnect").on('input', autoConnectInputHandler); $("#extensions_autoconnect").prop('checked', autoConnect).trigger('input'); + $("#extensions_details").on('click', showExtensionsDetails); + $(document).on('click', '.disable_extension', onDisableExtensionClick); + $(document).on('click', '.enable_extension', onEnableExtensionClick); // Activate offline extensions activateExtensions(); diff --git a/public/scripts/extensions/floating-prompt/index.js b/public/scripts/extensions/floating-prompt/index.js index e9ec59b8e..95b79c5cc 100644 --- a/public/scripts/extensions/floating-prompt/index.js +++ b/public/scripts/extensions/floating-prompt/index.js @@ -2,8 +2,6 @@ import { getContext } from "../../extensions.js"; export { MODULE_NAME }; const MODULE_NAME = '2_floating_prompt'; // <= Deliberate, for sorting lower than memory -const PROMPT_KEY = 'extensions_floating_prompt'; -const INTERVAL_KEY = 'extensions_floating_interval'; const UPDATE_INTERVAL = 1000; let lastMessageNumber = null; @@ -18,20 +16,29 @@ function onExtensionFloatingIntervalInput() { saveSettings(); } +function getLocalStorageKeys() { + const context = getContext(); + const keySuffix = context.groupId ? context.groupId : `${context.characters[context.characterId].name}_${context.chatId}`; + return { prompt: `extensions_floating_prompt_${keySuffix}`, interval: `extensions_floating_interval_${keySuffix}` }; +} + function loadSettings() { - const prompt = localStorage.getItem(PROMPT_KEY); - const interval = localStorage.getItem(INTERVAL_KEY); + const keys = getLocalStorageKeys(); + const prompt = localStorage.getItem(keys.prompt) ?? ''; + const interval = localStorage.getItem(keys.interval) ?? 0; $('#extension_floating_prompt').val(prompt).trigger('input'); $('#extension_floating_interval').val(interval).trigger('input'); } function saveSettings() { - localStorage.setItem(PROMPT_KEY, $('#extension_floating_prompt').val()); - localStorage.setItem(INTERVAL_KEY, $('#extension_floating_interval').val()); + const keys = getLocalStorageKeys(); + localStorage.setItem(keys.prompt, $('#extension_floating_prompt').val()); + localStorage.setItem(keys.interval, $('#extension_floating_interval').val()); } async function moduleWorker() { const context = getContext(); + loadSettings(); // take the count of messages lastMessageNumber = Array.isArray(context.chat) && context.chat.length ? context.chat.filter(m => m.is_user).length : 0; @@ -41,7 +48,9 @@ async function moduleWorker() { return; } - const messagesTillInsertion = (lastMessageNumber % promptInsertionInterval); + const messagesTillInsertion = lastMessageNumber >= promptInsertionInterval + ? (lastMessageNumber % promptInsertionInterval) + : (promptInsertionInterval - lastMessageNumber); const shouldAddPrompt = messagesTillInsertion == 0; const prompt = shouldAddPrompt ? $('#extension_floating_prompt').val() : ''; context.setExtensionPrompt(MODULE_NAME, prompt); @@ -67,6 +76,5 @@ async function moduleWorker() { } addExtensionsSettings(); - loadSettings(); setInterval(moduleWorker, UPDATE_INTERVAL); })(); \ No newline at end of file diff --git a/public/style.css b/public/style.css index b3d7e3098..deadb6daa 100644 --- a/public/style.css +++ b/public/style.css @@ -1171,8 +1171,9 @@ input[type=search]:focus::-webkit-search-cancel-button { margin-right: auto; left: 0; right: 0; + top: 50%; + transform: translateY(-50%); text-align: center; - margin-top: 36vh; box-shadow: 0 0 5px 5px var(--fullred); padding: 4px; background-color: var(--black70a); @@ -2818,4 +2819,30 @@ label[for="extensions_autoconnect"] { .drawer-content { display: none; +} + +.extensions_info { + text-align: left; + margin: 0 1em; +} + +.extensions_info h3 { + margin-bottom: 0.5em; +} + +.extensions_info h4 { + margin-bottom: 0.5em; +} + +.extensions_info p { + margin-bottom: 0.5em; +} + +.extensions_info .disabled { + color: lightgray; +} + +#extensions_list .disabled { + text-decoration: line-through; + color: lightgray; } \ No newline at end of file