Rearrange ext.panel. Add ext.update notifications. Improve performance on large number of extensions
This commit is contained in:
parent
e082138c18
commit
f0b20b67de
|
@ -3282,14 +3282,14 @@
|
||||||
<div id="rm_extensions_block" class="drawer-content closedDrawer">
|
<div id="rm_extensions_block" class="drawer-content closedDrawer">
|
||||||
<div class="extensions_block flex-container">
|
<div class="extensions_block flex-container">
|
||||||
<div class="alignitemscenter flex-container justifyCenter wide100p" style="justify-content: space-between;">
|
<div class="alignitemscenter flex-container justifyCenter wide100p" style="justify-content: space-between;">
|
||||||
<h3 class="margin0" data-i18n="Extensions API:">Extensions API:
|
<h3 class="margin0" data-i18n="Extras API:">Extras API:
|
||||||
<a target="_blank" href="https://github.com/SillyTavern/SillyTavern-extras">
|
<a target="_blank" href="https://github.com/SillyTavern/SillyTavern-Extras">
|
||||||
SillyTavern-extras
|
SillyTavern-Extras
|
||||||
</a>
|
</a>
|
||||||
</h3>
|
</h3>
|
||||||
<div class="flex-container">
|
<div class="flex-container">
|
||||||
<div id="extensions_status" data-i18n="Not connected...">Not connected...</div>
|
<div id="extensions_status" data-i18n="Not connected...">Not connected...</div>
|
||||||
<label for="extensions_autoconnect">
|
<label for="extensions_autoconnect" class="checkbox_label flexNoGap">
|
||||||
<input id="extensions_autoconnect" type="checkbox">
|
<input id="extensions_autoconnect" type="checkbox">
|
||||||
<span data-i18n="Auto-connect">Auto-connect</span>
|
<span data-i18n="Auto-connect">Auto-connect</span>
|
||||||
</label>
|
</label>
|
||||||
|
@ -3297,13 +3297,27 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="alignitemsflexstart flex-container wide100p">
|
<div class="alignitemsflexstart flex-container wide100p">
|
||||||
<input id="extensions_url" type="text" class="flex1 heightFitContent text_pole widthNatural" maxlength="250" data-i18n="[placeholder]Extensions URL" placeholder="Extensions URL">
|
<input id="extensions_url" type="text" class="flex1 heightFitContent text_pole widthNatural" maxlength="250" data-i18n="[placeholder]Extensions URL" placeholder="Extensions URL">
|
||||||
<input id="extensions_api_key" type="text" class="flex1 heightFitContent text_pole widthNatural" maxlength="250" data-i18n="[placeholder]API key" placeholder="Extras API key">
|
<input id="extensions_api_key" type="text" class="flex1 heightFitContent text_pole widthNatural" maxlength="250" data-i18n="[placeholder]API key" placeholder="Extras API key (optional)">
|
||||||
<div class="extensions_url_block">
|
<div class="extensions_url_block">
|
||||||
<div id="extensions_connect" class="menu_button" data-i18n="Connect">Connect</div>
|
<div id="extensions_connect" class="menu_button" data-i18n="Connect">Connect</div>
|
||||||
<div id="extensions_details" class="menu_button_icon menu_button">
|
</div>
|
||||||
Manage extensions
|
</div>
|
||||||
</div>
|
<hr class="wide100p">
|
||||||
<div id="third_party_extension_button" title="Import Extension From Git Repo" class="menu_button fa-solid fa-cloud-arrow-down faSmallFontSquareFix"></div>
|
<div class="alignitemscenter flex-container wide100p">
|
||||||
|
<h3 class="margin0 flex1" data-i18n="Extensions">
|
||||||
|
Extensions
|
||||||
|
</h3>
|
||||||
|
<label for="extensions_notify_updates" class="checkbox_label flexNoGap">
|
||||||
|
<input id="extensions_notify_updates" type="checkbox">
|
||||||
|
<span data-i18n="Notify on extension updates">Notify on extension updates</span>
|
||||||
|
</label>
|
||||||
|
<div id="extensions_details" class="menu_button_icon menu_button">
|
||||||
|
<i class="fa-solid fa-cubes"></i>
|
||||||
|
Manage extensions
|
||||||
|
</div>
|
||||||
|
<div id="third_party_extension_button" title="Import Extension From Git Repo" class="menu_button menu_button_icon">
|
||||||
|
<i class="fa-solid fa-cloud-arrow-down"></i>
|
||||||
|
Install extension
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="extensions_settings" class="flex1 wide50p">
|
<div id="extensions_settings" class="flex1 wide50p">
|
||||||
|
|
|
@ -142,7 +142,7 @@ import {
|
||||||
onlyUnique,
|
onlyUnique,
|
||||||
} from "./scripts/utils.js";
|
} from "./scripts/utils.js";
|
||||||
|
|
||||||
import { extension_settings, getContext, installExtension, loadExtensionSettings, processExtensionHelpers, registerExtensionHelper, runGenerationInterceptors, saveMetadataDebounced } from "./scripts/extensions.js";
|
import { extension_settings, getContext, loadExtensionSettings, processExtensionHelpers, registerExtensionHelper, runGenerationInterceptors, saveMetadataDebounced } from "./scripts/extensions.js";
|
||||||
import { COMMENT_NAME_DEFAULT, executeSlashCommands, getSlashCommandsHelp, registerSlashCommand } from "./scripts/slash-commands.js";
|
import { COMMENT_NAME_DEFAULT, executeSlashCommands, getSlashCommandsHelp, registerSlashCommand } from "./scripts/slash-commands.js";
|
||||||
import {
|
import {
|
||||||
tag_map,
|
tag_map,
|
||||||
|
@ -8804,34 +8804,6 @@ jQuery(async function () {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles the click event for the third-party extension import button.
|
|
||||||
* Prompts the user to enter the Git URL of the extension to import.
|
|
||||||
* After obtaining the Git URL, makes a POST request to '/api/extensions/install' to import the extension.
|
|
||||||
* If the extension is imported successfully, a success message is displayed.
|
|
||||||
* If the extension import fails, an error message is displayed and the error is logged to the console.
|
|
||||||
* After successfully importing the extension, the extension settings are reloaded and a 'EXTENSION_SETTINGS_LOADED' event is emitted.
|
|
||||||
*
|
|
||||||
* @listens #third_party_extension_button#click - The click event of the '#third_party_extension_button' element.
|
|
||||||
*/
|
|
||||||
$('#third_party_extension_button').on('click', async () => {
|
|
||||||
const html = `<h3>Enter the Git URL of the extension to import</h3>
|
|
||||||
<br>
|
|
||||||
<p><b>Disclaimer:</b> Please be aware that using external extensions can have unintended side effects and may pose security risks. Always make sure you trust the source before importing an extension. We are not responsible for any damage caused by third-party extensions.</p>
|
|
||||||
<br>
|
|
||||||
<p>Example: <tt> https://github.com/author/extension-name </tt></p>`
|
|
||||||
const input = await callPopup(html, 'input');
|
|
||||||
|
|
||||||
if (!input) {
|
|
||||||
console.debug('Extension import cancelled');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const url = input.trim();
|
|
||||||
await installExtension(url);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
const $dropzone = $(document.body);
|
const $dropzone = $(document.body);
|
||||||
|
|
||||||
$dropzone.on('dragover', (event) => {
|
$dropzone.on('dragover', (event) => {
|
||||||
|
|
|
@ -123,6 +123,7 @@ const extension_settings = {
|
||||||
apiUrl: defaultUrl,
|
apiUrl: defaultUrl,
|
||||||
apiKey: '',
|
apiKey: '',
|
||||||
autoConnect: false,
|
autoConnect: false,
|
||||||
|
notifyUpdates: false,
|
||||||
disabledExtensions: [],
|
disabledExtensions: [],
|
||||||
expressionOverrides: [],
|
expressionOverrides: [],
|
||||||
memory: {},
|
memory: {},
|
||||||
|
@ -367,6 +368,15 @@ function addExtensionsButtonAndMenu() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function notifyUpdatesInputHandler() {
|
||||||
|
extension_settings.notifyUpdates = !!$('#extensions_notify_updates').prop('checked');
|
||||||
|
saveSettingsDebounced();
|
||||||
|
|
||||||
|
if (extension_settings.notifyUpdates) {
|
||||||
|
checkForExtensionUpdates(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* $(document).on('click', function (e) {
|
/* $(document).on('click', function (e) {
|
||||||
const target = $(e.target);
|
const target = $(e.target);
|
||||||
if (target.is(dropdown)) return;
|
if (target.is(dropdown)) return;
|
||||||
|
@ -582,16 +592,25 @@ async function showExtensionsDetails() {
|
||||||
let htmlExternal = '<h3>External Extensions:</h3>';
|
let htmlExternal = '<h3>External Extensions:</h3>';
|
||||||
|
|
||||||
const extensions = Object.entries(manifests).sort((a, b) => a[1].loading_order - b[1].loading_order);
|
const extensions = Object.entries(manifests).sort((a, b) => a[1].loading_order - b[1].loading_order);
|
||||||
|
const promises = [];
|
||||||
|
|
||||||
for (const extension of extensions) {
|
for (const extension of extensions) {
|
||||||
const { isExternal, extensionHtml } = await getExtensionData(extension);
|
promises.push(getExtensionData(extension));
|
||||||
if (isExternal) {
|
|
||||||
htmlExternal += extensionHtml;
|
|
||||||
} else {
|
|
||||||
htmlDefault += extensionHtml;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const settledPromises = await Promise.allSettled(promises);
|
||||||
|
|
||||||
|
settledPromises.forEach(promise => {
|
||||||
|
if (promise.status === 'fulfilled') {
|
||||||
|
const { isExternal, extensionHtml } = promise.value;
|
||||||
|
if (isExternal) {
|
||||||
|
htmlExternal += extensionHtml;
|
||||||
|
} else {
|
||||||
|
htmlDefault += extensionHtml;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const html = `
|
const html = `
|
||||||
${getModuleInformation()}
|
${getModuleInformation()}
|
||||||
${htmlDefault}
|
${htmlDefault}
|
||||||
|
@ -703,9 +722,9 @@ async function getExtensionVersion(extensionName) {
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
export async function installExtension(url) {
|
export async function installExtension(url) {
|
||||||
console.debug('Extension import started', url);
|
console.debug('Extension installation started', url);
|
||||||
|
|
||||||
toastr.info('Please wait...', 'Importing extension');
|
toastr.info('Please wait...', 'Installing extension');
|
||||||
|
|
||||||
const request = await fetch('/api/extensions/install', {
|
const request = await fetch('/api/extensions/install', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
@ -714,14 +733,14 @@ export async function installExtension(url) {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!request.ok) {
|
if (!request.ok) {
|
||||||
toastr.info(request.statusText, 'Extension import failed');
|
toastr.info(request.statusText, 'Extension installation failed');
|
||||||
console.error('Extension import failed', request.status, request.statusText);
|
console.error('Extension installation failed', request.status, request.statusText);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await request.json();
|
const response = await request.json();
|
||||||
toastr.success(`Extension "${response.display_name}" by ${response.author} (version ${response.version}) has been imported successfully!`, 'Extension import successful');
|
toastr.success(`Extension "${response.display_name}" by ${response.author} (version ${response.version}) has been installed successfully!`, 'Extension installation successful');
|
||||||
console.debug(`Extension "${response.display_name}" has been imported successfully at ${response.extensionPath}`);
|
console.debug(`Extension "${response.display_name}" has been installed successfully at ${response.extensionPath}`);
|
||||||
await loadExtensionSettings({}, false);
|
await loadExtensionSettings({}, false);
|
||||||
eventSource.emit(event_types.EXTENSION_SETTINGS_LOADED);
|
eventSource.emit(event_types.EXTENSION_SETTINGS_LOADED);
|
||||||
}
|
}
|
||||||
|
@ -739,6 +758,7 @@ async function loadExtensionSettings(settings, versionChanged) {
|
||||||
$("#extensions_url").val(extension_settings.apiUrl);
|
$("#extensions_url").val(extension_settings.apiUrl);
|
||||||
$("#extensions_api_key").val(extension_settings.apiKey);
|
$("#extensions_api_key").val(extension_settings.apiKey);
|
||||||
$("#extensions_autoconnect").prop('checked', extension_settings.autoConnect);
|
$("#extensions_autoconnect").prop('checked', extension_settings.autoConnect);
|
||||||
|
$("#extensions_notify_updates").prop('checked', extension_settings.notifyUpdates);
|
||||||
|
|
||||||
// Activate offline extensions
|
// Activate offline extensions
|
||||||
eventSource.emit(event_types.EXTENSIONS_FIRST_LOAD);
|
eventSource.emit(event_types.EXTENSIONS_FIRST_LOAD);
|
||||||
|
@ -754,6 +774,55 @@ async function loadExtensionSettings(settings, versionChanged) {
|
||||||
connectToApi(extension_settings.apiUrl);
|
connectToApi(extension_settings.apiUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (extension_settings.notifyUpdates) {
|
||||||
|
checkForExtensionUpdates(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if there are updates available for 3rd-party extensions.
|
||||||
|
* @param {boolean} force Skip nag check
|
||||||
|
* @returns {Promise<any>}
|
||||||
|
*/
|
||||||
|
async function checkForExtensionUpdates(force) {
|
||||||
|
if (!force) {
|
||||||
|
const STORAGE_NAG_KEY = 'extension_update_nag';
|
||||||
|
const currentDate = new Date().toDateString();
|
||||||
|
|
||||||
|
// Don't nag more than once a day
|
||||||
|
if (localStorage.getItem(STORAGE_NAG_KEY) === currentDate) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
localStorage.setItem(STORAGE_NAG_KEY, currentDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatesAvailable = [];
|
||||||
|
const promises = [];
|
||||||
|
|
||||||
|
for (const [id, manifest] of Object.entries(manifests)) {
|
||||||
|
if (manifest.auto_update && id.startsWith('third-party')) {
|
||||||
|
const promise = new Promise(async (resolve, reject) => {
|
||||||
|
try {
|
||||||
|
const data = await getExtensionVersion(id.replace('third-party', ''));
|
||||||
|
if (data.isUpToDate === false) {
|
||||||
|
updatesAvailable.push(manifest.display_name);
|
||||||
|
}
|
||||||
|
resolve();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error checking for extension updates', error);
|
||||||
|
reject();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
promises.push(promise);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.allSettled(promises);
|
||||||
|
|
||||||
|
if (updatesAvailable.length > 0) {
|
||||||
|
toastr.info(`${updatesAvailable.map(x => `• ${x}`).join('\n')}`, 'Extension updates available');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function autoUpdateExtensions() {
|
async function autoUpdateExtensions() {
|
||||||
|
@ -785,8 +854,36 @@ jQuery(function () {
|
||||||
$("#extensions_connect").on('click', connectClickHandler);
|
$("#extensions_connect").on('click', connectClickHandler);
|
||||||
$("#extensions_autoconnect").on('input', autoConnectInputHandler);
|
$("#extensions_autoconnect").on('input', autoConnectInputHandler);
|
||||||
$("#extensions_details").on('click', showExtensionsDetails);
|
$("#extensions_details").on('click', showExtensionsDetails);
|
||||||
|
$("#extensions_notify_updates").on('input', notifyUpdatesInputHandler);
|
||||||
$(document).on('click', '.toggle_disable', onDisableExtensionClick);
|
$(document).on('click', '.toggle_disable', onDisableExtensionClick);
|
||||||
$(document).on('click', '.toggle_enable', onEnableExtensionClick);
|
$(document).on('click', '.toggle_enable', onEnableExtensionClick);
|
||||||
$(document).on('click', '.btn_update', onUpdateClick);
|
$(document).on('click', '.btn_update', onUpdateClick);
|
||||||
$(document).on('click', '.btn_delete', onDeleteClick);
|
$(document).on('click', '.btn_delete', onDeleteClick);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the click event for the third-party extension import button.
|
||||||
|
* Prompts the user to enter the Git URL of the extension to import.
|
||||||
|
* After obtaining the Git URL, makes a POST request to '/api/extensions/install' to import the extension.
|
||||||
|
* If the extension is imported successfully, a success message is displayed.
|
||||||
|
* If the extension import fails, an error message is displayed and the error is logged to the console.
|
||||||
|
* After successfully importing the extension, the extension settings are reloaded and a 'EXTENSION_SETTINGS_LOADED' event is emitted.
|
||||||
|
*
|
||||||
|
* @listens #third_party_extension_button#click - The click event of the '#third_party_extension_button' element.
|
||||||
|
*/
|
||||||
|
$('#third_party_extension_button').on('click', async () => {
|
||||||
|
const html = `<h3>Enter the Git URL of the extension to install</h3>
|
||||||
|
<br>
|
||||||
|
<p><b>Disclaimer:</b> Please be aware that using external extensions can have unintended side effects and may pose security risks. Always make sure you trust the source before importing an extension. We are not responsible for any damage caused by third-party extensions.</p>
|
||||||
|
<br>
|
||||||
|
<p>Example: <tt> https://github.com/author/extension-name </tt></p>`
|
||||||
|
const input = await callPopup(html, 'input');
|
||||||
|
|
||||||
|
if (!input) {
|
||||||
|
console.debug('Extension install cancelled');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = input.trim();
|
||||||
|
await installExtension(url);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue