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 @@
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