mirror of
				https://github.com/SillyTavern/SillyTavern.git
				synced 2025-06-05 21:59:27 +02:00 
			
		
		
		
	
		
			
				
	
	
		
			321 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			321 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/*
 | 
						|
TODO:
 | 
						|
*/
 | 
						|
//const DEBUG_TONY_SAMA_FORK_MODE = true
 | 
						|
 | 
						|
import { getRequestHeaders, callPopup } from '../../../script.js';
 | 
						|
import { deleteExtension, extensionNames, installExtension, renderExtensionTemplate } from '../../extensions.js';
 | 
						|
import { getStringHash, isValidUrl } from '../../utils.js';
 | 
						|
export { MODULE_NAME };
 | 
						|
 | 
						|
const MODULE_NAME = 'assets';
 | 
						|
const DEBUG_PREFIX = '<Assets module> ';
 | 
						|
let previewAudio = null;
 | 
						|
let ASSETS_JSON_URL = 'https://raw.githubusercontent.com/SillyTavern/SillyTavern-Content/main/index.json';
 | 
						|
 | 
						|
 | 
						|
// DBG
 | 
						|
//if (DEBUG_TONY_SAMA_FORK_MODE)
 | 
						|
//    ASSETS_JSON_URL = "https://raw.githubusercontent.com/Tony-sama/SillyTavern-Content/main/index.json"
 | 
						|
let availableAssets = {};
 | 
						|
let currentAssets = {};
 | 
						|
 | 
						|
//#############################//
 | 
						|
//  Extension UI and Settings  //
 | 
						|
//#############################//
 | 
						|
 | 
						|
function downloadAssetsList(url) {
 | 
						|
    updateCurrentAssets().then(function () {
 | 
						|
        fetch(url, { cache: 'no-cache' })
 | 
						|
            .then(response => response.json())
 | 
						|
            .then(json => {
 | 
						|
 | 
						|
                availableAssets = {};
 | 
						|
                $('#assets_menu').empty();
 | 
						|
 | 
						|
                console.debug(DEBUG_PREFIX, 'Received assets dictionary', json);
 | 
						|
 | 
						|
                for (const i of json) {
 | 
						|
                    //console.log(DEBUG_PREFIX,i)
 | 
						|
                    if (availableAssets[i['type']] === undefined)
 | 
						|
                        availableAssets[i['type']] = [];
 | 
						|
 | 
						|
                    availableAssets[i['type']].push(i);
 | 
						|
                }
 | 
						|
 | 
						|
                console.debug(DEBUG_PREFIX, 'Updated available assets to', availableAssets);
 | 
						|
                // First extensions, then everything else
 | 
						|
                const assetTypes = Object.keys(availableAssets).sort((a, b) => (a === 'extension') ? -1 : (b === 'extension') ? 1 : 0);
 | 
						|
 | 
						|
                for (const assetType of assetTypes) {
 | 
						|
                    let assetTypeMenu = $('<div />', { id: 'assets_audio_ambient_div', class: 'assets-list-div' });
 | 
						|
                    assetTypeMenu.append(`<h3>${assetType}</h3>`);
 | 
						|
 | 
						|
                    if (assetType == 'extension') {
 | 
						|
                        assetTypeMenu.append(`
 | 
						|
                        <div class="assets-list-git">
 | 
						|
                            To download extensions from this page, you need to have <a href="https://git-scm.com/downloads" target="_blank">Git</a> installed.
 | 
						|
                        </div>`);
 | 
						|
                    }
 | 
						|
 | 
						|
                    for (const i in availableAssets[assetType]) {
 | 
						|
                        const asset = availableAssets[assetType][i];
 | 
						|
                        const elemId = `assets_install_${assetType}_${i}`;
 | 
						|
                        let element = $('<button />', { id: elemId, type: 'button', class: 'asset-download-button menu_button' });
 | 
						|
                        const label = $('<i class="fa-fw fa-solid fa-download fa-xl"></i>');
 | 
						|
                        element.append(label);
 | 
						|
 | 
						|
                        //if (DEBUG_TONY_SAMA_FORK_MODE)
 | 
						|
                        //    asset["url"] = asset["url"].replace("https://github.com/SillyTavern/","https://github.com/Tony-sama/"); // DBG
 | 
						|
 | 
						|
                        console.debug(DEBUG_PREFIX, 'Checking asset', asset['id'], asset['url']);
 | 
						|
 | 
						|
                        const assetInstall = async function () {
 | 
						|
                            element.off('click');
 | 
						|
                            label.removeClass('fa-download');
 | 
						|
                            this.classList.add('asset-download-button-loading');
 | 
						|
                            await installAsset(asset['url'], assetType, asset['id']);
 | 
						|
                            label.addClass('fa-check');
 | 
						|
                            this.classList.remove('asset-download-button-loading');
 | 
						|
                            element.on('click', assetDelete);
 | 
						|
                            element.on('mouseenter', function () {
 | 
						|
                                label.removeClass('fa-check');
 | 
						|
                                label.addClass('fa-trash');
 | 
						|
                                label.addClass('redOverlayGlow');
 | 
						|
                            }).on('mouseleave', function () {
 | 
						|
                                label.addClass('fa-check');
 | 
						|
                                label.removeClass('fa-trash');
 | 
						|
                                label.removeClass('redOverlayGlow');
 | 
						|
                            });
 | 
						|
                        };
 | 
						|
 | 
						|
                        const assetDelete = async function () {
 | 
						|
                            element.off('click');
 | 
						|
                            await deleteAsset(assetType, asset['id']);
 | 
						|
                            label.removeClass('fa-check');
 | 
						|
                            label.removeClass('redOverlayGlow');
 | 
						|
                            label.removeClass('fa-trash');
 | 
						|
                            label.addClass('fa-download');
 | 
						|
                            element.off('mouseenter').off('mouseleave');
 | 
						|
                            element.on('click', assetInstall);
 | 
						|
                        };
 | 
						|
 | 
						|
                        if (isAssetInstalled(assetType, asset['id'])) {
 | 
						|
                            console.debug(DEBUG_PREFIX, 'installed, checked');
 | 
						|
                            label.toggleClass('fa-download');
 | 
						|
                            label.toggleClass('fa-check');
 | 
						|
                            element.on('click', assetDelete);
 | 
						|
                            element.on('mouseenter', function () {
 | 
						|
                                label.removeClass('fa-check');
 | 
						|
                                label.addClass('fa-trash');
 | 
						|
                                label.addClass('redOverlayGlow');
 | 
						|
                            }).on('mouseleave', function () {
 | 
						|
                                label.addClass('fa-check');
 | 
						|
                                label.removeClass('fa-trash');
 | 
						|
                                label.removeClass('redOverlayGlow');
 | 
						|
                            });
 | 
						|
                        }
 | 
						|
                        else {
 | 
						|
                            console.debug(DEBUG_PREFIX, 'not installed, unchecked');
 | 
						|
                            element.prop('checked', false);
 | 
						|
                            element.on('click', assetInstall);
 | 
						|
                        }
 | 
						|
 | 
						|
                        console.debug(DEBUG_PREFIX, 'Created element for ', asset['id']);
 | 
						|
 | 
						|
                        const displayName = DOMPurify.sanitize(asset['name'] || asset['id']);
 | 
						|
                        const description = DOMPurify.sanitize(asset['description'] || '');
 | 
						|
                        const url = isValidUrl(asset['url']) ? asset['url'] : '';
 | 
						|
                        const previewIcon = assetType == 'extension' ? 'fa-arrow-up-right-from-square' : 'fa-headphones-simple';
 | 
						|
 | 
						|
                        $('<i></i>')
 | 
						|
                            .append(element)
 | 
						|
                            .append(`<div class="flex-container flexFlowColumn">
 | 
						|
                                        <span class="flex-container alignitemscenter">
 | 
						|
                                            <b>${displayName}</b>
 | 
						|
                                            <a class="asset_preview" href="${url}" target="_blank" title="Preview in browser">
 | 
						|
                                                <i class="fa-solid fa-sm ${previewIcon}"></i>
 | 
						|
                                            </a>
 | 
						|
                                        </span>
 | 
						|
                                        <span>${description}</span>
 | 
						|
                                     </div>`)
 | 
						|
                            .appendTo(assetTypeMenu);
 | 
						|
                    }
 | 
						|
                    assetTypeMenu.appendTo('#assets_menu');
 | 
						|
                    assetTypeMenu.on('click', 'a.asset_preview', previewAsset);
 | 
						|
                }
 | 
						|
 | 
						|
                $('#assets_menu').show();
 | 
						|
            })
 | 
						|
            .catch((error) => {
 | 
						|
                console.error(error);
 | 
						|
                toastr.error('Problem with assets URL', DEBUG_PREFIX + 'Cannot get assets list');
 | 
						|
                $('#assets-connect-button').addClass('fa-plug-circle-exclamation');
 | 
						|
                $('#assets-connect-button').addClass('redOverlayGlow');
 | 
						|
            });
 | 
						|
    });
 | 
						|
}
 | 
						|
 | 
						|
function previewAsset(e) {
 | 
						|
    const href = $(this).attr('href');
 | 
						|
    const audioExtensions = ['.mp3', '.ogg', '.wav'];
 | 
						|
 | 
						|
    if (audioExtensions.some(ext => href.endsWith(ext))) {
 | 
						|
        e.preventDefault();
 | 
						|
 | 
						|
        if (previewAudio) {
 | 
						|
            previewAudio.pause();
 | 
						|
 | 
						|
            if (previewAudio.src === href) {
 | 
						|
                previewAudio = null;
 | 
						|
                return;
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        previewAudio = new Audio(href);
 | 
						|
        previewAudio.play();
 | 
						|
        return;
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
function isAssetInstalled(assetType, filename) {
 | 
						|
    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;
 | 
						|
    }
 | 
						|
 | 
						|
    return false;
 | 
						|
}
 | 
						|
 | 
						|
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',
 | 
						|
            headers: getRequestHeaders(),
 | 
						|
            body: JSON.stringify(body),
 | 
						|
            cache: 'no-cache',
 | 
						|
        });
 | 
						|
        if (result.ok) {
 | 
						|
            console.debug(DEBUG_PREFIX, 'Download success.');
 | 
						|
        }
 | 
						|
    }
 | 
						|
    catch (err) {
 | 
						|
        console.log(err);
 | 
						|
        return [];
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
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',
 | 
						|
            headers: getRequestHeaders(),
 | 
						|
            body: JSON.stringify(body),
 | 
						|
            cache: 'no-cache',
 | 
						|
        });
 | 
						|
        if (result.ok) {
 | 
						|
            console.debug(DEBUG_PREFIX, 'Deletion success.');
 | 
						|
        }
 | 
						|
    }
 | 
						|
    catch (err) {
 | 
						|
        console.log(err);
 | 
						|
        return [];
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
//#############################//
 | 
						|
//  API Calls                  //
 | 
						|
//#############################//
 | 
						|
 | 
						|
async function updateCurrentAssets() {
 | 
						|
    console.debug(DEBUG_PREFIX, 'Checking installed assets...');
 | 
						|
    try {
 | 
						|
        const result = await fetch('/api/assets/get', {
 | 
						|
            method: 'POST',
 | 
						|
            headers: getRequestHeaders(),
 | 
						|
        });
 | 
						|
        currentAssets = result.ok ? (await result.json()) : {};
 | 
						|
    }
 | 
						|
    catch (err) {
 | 
						|
        console.log(err);
 | 
						|
    }
 | 
						|
    console.debug(DEBUG_PREFIX, 'Current assets found:', currentAssets);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
//#############################//
 | 
						|
//  Extension load             //
 | 
						|
//#############################//
 | 
						|
 | 
						|
// This function is called when the extension is loaded
 | 
						|
jQuery(async () => {
 | 
						|
    // This is an example of loading HTML from a file
 | 
						|
    const windowHtml = $(renderExtensionTemplate(MODULE_NAME, 'window', {}));
 | 
						|
 | 
						|
    const assetsJsonUrl = windowHtml.find('#assets-json-url-field');
 | 
						|
    assetsJsonUrl.val(ASSETS_JSON_URL);
 | 
						|
 | 
						|
    const connectButton = windowHtml.find('#assets-connect-button');
 | 
						|
    connectButton.on('click', async function () {
 | 
						|
        const url = String(assetsJsonUrl.val());
 | 
						|
        const rememberKey = `Assets_SkipConfirm_${getStringHash(url)}`;
 | 
						|
        const skipConfirm = localStorage.getItem(rememberKey) === 'true';
 | 
						|
 | 
						|
        const template = renderExtensionTemplate(MODULE_NAME, 'confirm', { url });
 | 
						|
        const confirmation = skipConfirm || await callPopup(template, 'confirm');
 | 
						|
 | 
						|
        if (confirmation) {
 | 
						|
            try {
 | 
						|
                if (!skipConfirm) {
 | 
						|
                    const rememberValue = Boolean($('#assets-remember').prop('checked'));
 | 
						|
                    localStorage.setItem(rememberKey, String(rememberValue));
 | 
						|
                }
 | 
						|
 | 
						|
                console.debug(DEBUG_PREFIX, 'Confimation, loading assets...');
 | 
						|
                downloadAssetsList(url);
 | 
						|
                connectButton.removeClass('fa-plug-circle-exclamation');
 | 
						|
                connectButton.removeClass('redOverlayGlow');
 | 
						|
                connectButton.addClass('fa-plug-circle-check');
 | 
						|
            } catch (error) {
 | 
						|
                console.error('Error:', error);
 | 
						|
                toastr.error(`Cannot get assets list from ${url}`);
 | 
						|
                connectButton.removeClass('fa-plug-circle-check');
 | 
						|
                connectButton.addClass('fa-plug-circle-exclamation');
 | 
						|
                connectButton.removeClass('redOverlayGlow');
 | 
						|
            }
 | 
						|
        }
 | 
						|
        else {
 | 
						|
            console.debug(DEBUG_PREFIX, 'Connection refused by user');
 | 
						|
        }
 | 
						|
    });
 | 
						|
 | 
						|
    $('#extensions_settings').append(windowHtml);
 | 
						|
});
 |