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
diff --git a/public/scripts/extensions/stable-diffusion/index.js b/public/scripts/extensions/stable-diffusion/index.js
index 3cee28c0a..523889ef8 100644
--- a/public/scripts/extensions/stable-diffusion/index.js
+++ b/public/scripts/extensions/stable-diffusion/index.js
@@ -156,6 +156,7 @@ const defaultSettings = {
horde: false,
horde_nsfw: false,
horde_karras: true,
+ horde_sanitize: true,
// Refine mode
refine_mode: false,
@@ -252,6 +253,7 @@ async function loadSettings() {
$('#sd_horde').prop('checked', extension_settings.sd.horde);
$('#sd_horde_nsfw').prop('checked', extension_settings.sd.horde_nsfw);
$('#sd_horde_karras').prop('checked', extension_settings.sd.horde_karras);
+ $('#sd_horde_sanitize').prop('checked', extension_settings.sd.horde_sanitize);
$('#sd_restore_faces').prop('checked', extension_settings.sd.restore_faces);
$('#sd_enable_hr').prop('checked', extension_settings.sd.enable_hr);
$('#sd_refine_mode').prop('checked', extension_settings.sd.refine_mode);
@@ -439,16 +441,21 @@ function onNovelAnlasGuardInput() {
saveSettingsDebounced();
}
-async function onHordeNsfwInput() {
+function onHordeNsfwInput() {
extension_settings.sd.horde_nsfw = !!$(this).prop('checked');
saveSettingsDebounced();
}
-async function onHordeKarrasInput() {
+function onHordeKarrasInput() {
extension_settings.sd.horde_karras = !!$(this).prop('checked');
saveSettingsDebounced();
}
+function onHordeSanitizeInput() {
+ extension_settings.sd.horde_sanitize = !!$(this).prop('checked');
+ saveSettingsDebounced();
+}
+
function onRestoreFacesInput() {
extension_settings.sd.restore_faces = !!$(this).prop('checked');
saveSettingsDebounced();
@@ -1243,6 +1250,7 @@ async function generateHordeImage(prompt) {
nsfw: extension_settings.sd.horde_nsfw,
restore_faces: !!extension_settings.sd.restore_faces,
enable_hr: !!extension_settings.sd.enable_hr,
+ sanitize: !!extension_settings.sd.horde_sanitize,
}),
});
@@ -1584,6 +1592,7 @@ jQuery(async () => {
$('#sd_height').on('input', onHeightInput);
$('#sd_horde_nsfw').on('input', onHordeNsfwInput);
$('#sd_horde_karras').on('input', onHordeKarrasInput);
+ $('#sd_horde_sanitize').on('input', onHordeSanitizeInput);
$('#sd_restore_faces').on('input', onRestoreFacesInput);
$('#sd_enable_hr').on('input', onHighResFixInput);
$('#sd_refine_mode').on('input', onRefineModeInput);
diff --git a/public/scripts/extensions/stable-diffusion/settings.html b/public/scripts/extensions/stable-diffusion/settings.html
index bdcd9224b..5e37fdc31 100644
--- a/public/scripts/extensions/stable-diffusion/settings.html
+++ b/public/scripts/extensions/stable-diffusion/settings.html
@@ -58,6 +58,12 @@
Allow NSFW images from Horde
+