diff --git a/public/script.js b/public/script.js index 5d2bf4bec..c24ba3be2 100644 --- a/public/script.js +++ b/public/script.js @@ -143,7 +143,7 @@ import { onlyUnique, } from "./scripts/utils.js"; -import { extension_settings, getContext, loadExtensionSettings, processExtensionHelpers, registerExtensionHelper, runGenerationInterceptors, saveMetadataDebounced } from "./scripts/extensions.js"; +import { extension_settings, getContext, installExtension, loadExtensionSettings, processExtensionHelpers, registerExtensionHelper, runGenerationInterceptors, saveMetadataDebounced } from "./scripts/extensions.js"; import { COMMENT_NAME_DEFAULT, executeSlashCommands, getSlashCommandsHelp, registerSlashCommand } from "./scripts/slash-commands.js"; import { tag_map, @@ -8898,25 +8898,7 @@ jQuery(async function () { } const url = input.trim(); - console.debug('Extension import started', url); - - const request = await fetch('/api/extensions/install', { - method: 'POST', - headers: getRequestHeaders(), - body: JSON.stringify({ url }), - }); - - if (!request.ok) { - toastr.info(request.statusText, 'Extension import failed'); - console.error('Extension import failed', request.status, request.statusText); - return; - } - - const response = await request.json(); - toastr.success(`Extension "${response.display_name}" by ${response.author} (version ${response.version}) has been imported successfully!`, 'Extension import successful'); - console.debug(`Extension "${response.display_name}" has been imported successfully at ${response.extensionPath}`); - await loadExtensionSettings(settings); - eventSource.emit(event_types.EXTENSION_SETTINGS_LOADED); + await installExtension(url); }); diff --git a/public/scripts/extensions.js b/public/scripts/extensions.js index 0b0a5d547..c62a24e5c 100644 --- a/public/scripts/extensions.js +++ b/public/scripts/extensions.js @@ -11,7 +11,7 @@ export { ModuleWorkerWrapper, }; -let extensionNames = []; +export let extensionNames = []; let manifests = {}; const defaultUrl = "http://localhost:5100"; @@ -639,23 +639,26 @@ async function onDeleteClick() { // use callPopup to create a popup for the user to confirm before delete const confirmation = await callPopup(`Are you sure you want to delete ${extensionName}?`, 'delete_extension'); if (confirmation) { - try { - const response = await fetch('/api/extensions/delete', { - method: 'POST', - headers: getRequestHeaders(), - body: JSON.stringify({ extensionName }) - }); - } catch (error) { - console.error('Error:', error); - } - toastr.success(`Extension ${extensionName} deleted`); - showExtensionsDetails(); - // reload the page to remove the extension from the list - location.reload(); + await deleteExtension(extensionName); } }; +export async function deleteExtension(extensionName) { + try { + const response = await fetch('/api/extensions/delete', { + method: 'POST', + headers: getRequestHeaders(), + body: JSON.stringify({ extensionName }) + }); + } catch (error) { + console.error('Error:', error); + } + toastr.success(`Extension ${extensionName} deleted`); + showExtensionsDetails(); + // reload the page to remove the extension from the list + location.reload(); +} /** * Fetches the version details of a specific extension. @@ -680,7 +683,32 @@ async function getExtensionVersion(extensionName) { } } +/** + * Installs a third-party extension via the API. + * @param {string} url Extension repository URL + * @returns {Promise} + */ +export async function installExtension(url) { + console.debug('Extension import started', url); + const request = await fetch('/api/extensions/install', { + method: 'POST', + headers: getRequestHeaders(), + body: JSON.stringify({ url }), + }); + + if (!request.ok) { + toastr.info(request.statusText, 'Extension import failed'); + console.error('Extension import failed', request.status, request.statusText); + return; + } + + const response = await request.json(); + toastr.success(`Extension "${response.display_name}" by ${response.author} (version ${response.version}) has been imported successfully!`, 'Extension import successful'); + console.debug(`Extension "${response.display_name}" has been imported successfully at ${response.extensionPath}`); + await loadExtensionSettings({}); + eventSource.emit(event_types.EXTENSION_SETTINGS_LOADED); +} async function loadExtensionSettings(settings) { if (settings.extension_settings) { diff --git a/public/scripts/extensions/assets/index.js b/public/scripts/extensions/assets/index.js index 7728ba1b4..ca149a1cb 100644 --- a/public/scripts/extensions/assets/index.js +++ b/public/scripts/extensions/assets/index.js @@ -5,6 +5,7 @@ TODO: //const DEBUG_TONY_SAMA_FORK_MODE = false import { getRequestHeaders, callPopup } from "../../../script.js"; +import { deleteExtension, extensionNames, installExtension } from "../../extensions.js"; export { MODULE_NAME }; const MODULE_NAME = 'Assets'; @@ -116,9 +117,13 @@ function downloadAssetsList(url) { console.debug(DEBUG_PREFIX, "Created element for BGM", asset["id"]) + const displayName = DOMPurify.sanitize(asset["name"] || asset["id"]); + const description = DOMPurify.sanitize(asset["description"] || ""); + $(``) .append(element) - .append(`${asset["id"]}`) + .append(`${displayName}`) + .append(`${description}`) .appendTo(assetTypeMenu); } assetTypeMenu.appendTo("#assets_menu"); @@ -136,7 +141,14 @@ function downloadAssetsList(url) { } function isAssetInstalled(assetType, filename) { - for (const i of currentAssets[assetType]) { + let assetList = currentAssets[assetType]; + + if (assetType == 'extension') { + const thirdPartyMarker = "third-party/"; + assetList = extensionNames.filter(x => x.startsWith(thirdPartyMarker)).map(x => x.replace(thirdPartyMarker, '')); + } + + for (const i of assetList) { //console.debug(DEBUG_PREFIX,i,filename) if (i.includes(filename)) return true; @@ -149,6 +161,13 @@ async function installAsset(url, assetType, filename) { console.debug(DEBUG_PREFIX, "Downloading ", url); const category = assetType; try { + if (category === 'extension') { + console.debug(DEBUG_PREFIX, "Installing extension ", url) + await installExtension(url); + console.debug(DEBUG_PREFIX, "Extension installed.") + return; + } + const body = { url, category, filename }; const result = await fetch('/api/assets/download', { method: 'POST', @@ -170,6 +189,12 @@ async function deleteAsset(assetType, filename) { console.debug(DEBUG_PREFIX, "Deleting ", assetType, filename); const category = assetType; try { + if (category === 'extension') { + console.debug(DEBUG_PREFIX, "Deleting extension ", filename) + await deleteExtension(filename); + console.debug(DEBUG_PREFIX, "Extension deleted.") + } + const body = { category, filename }; const result = await fetch('/api/assets/delete', { method: 'POST', diff --git a/public/scripts/extensions/assets/style.css b/public/scripts/extensions/assets/style.css index af55c7681..bceff2ea0 100644 --- a/public/scripts/extensions/assets/style.css +++ b/public/scripts/extensions/assets/style.css @@ -19,6 +19,7 @@ align-items: center; justify-content: left; padding: 5px; + font-style: normal; } .assets-list-div i span{ @@ -34,7 +35,7 @@ border-radius: 2px; cursor: pointer; } - + .asset-download-button:active { background: #007a63; } @@ -75,5 +76,3 @@ to { transform: rotate(1turn); } } - - \ No newline at end of file