')
        .attr('id', `${sliderID}_zenslider`)
        .css('width', '100%')
        .insertBefore(originalSlider);
    newSlider.slider({
        value: sliderValue,
        step: stepScale,
        min: sliderMin,
        max: sliderMax,
        create: async function () {
            await delay(100);
            var handle = $(this).find('.ui-slider-handle');
            var handleText, stepNumber, leftMargin;
            //handling creation of amt_gen
            if (newSlider.attr('id') == 'amount_gen_zenslider') {
                handleText = steps[sliderValue];
                stepNumber = sliderValue;
                leftMargin = ((stepNumber) / numSteps) * 50 * -1;
                handle.text(handleText)
                    .css('margin-left', `${leftMargin}px`);
                //console.log(`${newSlider.attr('id')} initial value:${handleText}, stepNum:${stepNumber}, numSteps:${numSteps}, left-margin:${leftMargin}`)
            }
            //handling creation of rep_pen_range for ooba
            else if (newSlider.attr('id') == 'rep_pen_range_textgenerationwebui_zenslider') {
                if ($('#rep_pen_range_textgenerationwebui_zensliders').length !== 0) {
                    $('#rep_pen_range_textgenerationwebui_zensliders').remove();
                }
                handleText = steps[sliderValue];
                stepNumber = sliderValue;
                leftMargin = ((stepNumber) / numSteps) * 50 * -1;
                if (sliderValue === offVal) {
                    handleText = 'Off';
                    handle.css('color', 'rgba(128,128,128,0.5');
                }
                else if (sliderValue === allVal) { handleText = 'All'; }
                else { handle.css('color', ''); }
                handle.text(handleText)
                    .css('margin-left', `${leftMargin}px`);
                //console.log(sliderValue, handleText, offVal, allVal)
                //console.log(`${newSlider.attr('id')} sliderValue = ${sliderValue}, handleText:${handleText}, stepNum:${stepNumber}, numSteps:${numSteps}, left-margin:${leftMargin}`)
                originalSlider.val(steps[sliderValue]);
            }
            //create all other sliders
            else {
                var numVal = Number(sliderValue).toFixed(decimals);
                offVal = Number(offVal).toFixed(decimals);
                if (numVal === offVal) {
                    handle.text('Off').css('color', 'rgba(128,128,128,0.5');
                } else {
                    handle.text(numVal).css('color', '');
                }
                stepNumber = ((sliderValue - sliderMin) / stepScale);
                leftMargin = (stepNumber / numSteps) * 50 * -1;
                originalSlider.val(numVal)
                    .data('newSlider', newSlider);
                //console.log(`${newSlider.attr('id')} sliderValue = ${sliderValue}, handleText:${handleText, numVal}, stepNum:${stepNumber}, numSteps:${numSteps}, left-margin:${leftMargin}`)
                var isManualInput = false;
                var valueBeforeManualInput;
                handle.css('margin-left', `${leftMargin}px`)
                    .attr('contenteditable', 'true')
                    //these sliders need listeners for manual inputs
                    .on('click', function () {
                        //this just selects all the text in the handle so user can overwrite easily
                        //needed because JQUery UI uses left/right arrow keys as well as home/end to move the slider..
                        valueBeforeManualInput = newSlider.val();
                        console.log(valueBeforeManualInput);
                        let handleElement = handle.get(0);
                        let range = document.createRange();
                        range.selectNodeContents(handleElement);
                        let selection = window.getSelection();
                        selection.removeAllRanges();
                        selection.addRange(range);
                    })
                    .on('keyup', function (e) {
                        valueBeforeManualInput = numVal;
                        //console.log(valueBeforeManualInput, numVal, handleText);
                        isManualInput = true;
                        //allow enter to trigger slider update
                        if (e.key === 'Enter') {
                            e.preventDefault();
                            handle.trigger('blur');
                        }
                    })
                    //trigger slider changes when user clicks away
                    .on('mouseup blur', function () {
                        let manualInput = parseFloat(handle.text()).toFixed(decimals);
                        if (isManualInput) {
                            //disallow manual inputs outside acceptable range
                            if (manualInput >= sliderMin && manualInput <= sliderMax) {
                                //if value is ok, assign to slider and update handle text and position
                                newSlider.val(manualInput);
                                handleSlideEvent.call(newSlider, null, { value: parseFloat(manualInput) }, 'manual');
                                valueBeforeManualInput = manualInput;
                            } else {
                                //if value not ok, warn and reset to last known valid value
                                toastr.warning(`Invalid value. Must be between ${sliderMin} and ${sliderMax}`);
                                console.log(valueBeforeManualInput);
                                newSlider.val(valueBeforeManualInput);
                                handle.text(valueBeforeManualInput);
                                handleSlideEvent.call(newSlider, null, { value: parseFloat(valueBeforeManualInput) }, 'manual');
                            }
                        }
                        isManualInput = false;
                    });
            }
            //zenSlider creation done, hide the original
            originalSlider.hide();
        },
        slide: handleSlideEvent,
    });
    function handleSlideEvent(event, ui, type) {
        var handle = $(this).find('.ui-slider-handle');
        var numVal = Number(ui.value).toFixed(decimals);
        offVal = Number(offVal).toFixed(decimals);
        allVal = Number(allVal).toFixed(decimals);
        console.log(numVal, sliderMin, sliderMax, numVal > sliderMax, numVal < sliderMin);
        if (numVal > sliderMax) { numVal = sliderMax; }
        if (numVal < sliderMin) { numVal = sliderMin; }
        var stepNumber = ((ui.value - sliderMin) / stepScale).toFixed(0);
        var handleText = (ui.value);
        var leftMargin = (stepNumber / numSteps) * 50 * -1;
        var perStepPercent = 1 / numSteps; //how far in % each step should be on the slider
        var leftPos = newSlider.width() * (stepNumber * perStepPercent); //how big of a left margin to give the slider for manual inputs
        /*         console.log(`
                numVal: ${numVal},
                sliderMax: ${sliderMax}
                sliderMin: ${sliderMin}
                sliderValRange: ${sliderValRange}
                stepScale: ${stepScale}
                Step: ${stepNumber} of ${numSteps}
                offVal: ${offVal}
                allVal = ${allVal}
                initial value: ${handleText}
                left-margin: ${leftMargin}
                width: ${newSlider.width()}
                percent of max: ${percentOfMax}
                left: ${leftPos}`) */
        //special handling for response length slider, pulls text aliases for step values from an array
        if (newSlider.attr('id') == 'amount_gen_zenslider') {
            handleText = steps[stepNumber];
            handle.text(handleText);
            newSlider.val(stepNumber);
            numVal = steps[stepNumber];
        }
        //special handling for TextCompletion rep pen range slider, pulls text aliases for step values from an array
        else if (newSlider.attr('id') == 'rep_pen_range_textgenerationwebui_zenslider') {
            handleText = steps[stepNumber];
            handle.text(handleText);
            newSlider.val(stepNumber);
            if (numVal === offVal) { handle.text('Off').css('color', 'rgba(128,128,128,0.5'); }
            else if (numVal === allVal) { handle.text('All'); }
            else { handle.css('color', ''); }
            numVal = steps[stepNumber];
        }
        //everything else uses the flat slider value
        //also note: the above sliders are not custom inputtable due to the array aliasing
        else {
            //show 'off' if disabled value is set
            if (numVal === offVal) { handle.text('Off').css('color', 'rgba(128,128,128,0.5'); }
            else { handle.text(ui.value.toFixed(decimals)).css('color', ''); }
            newSlider.val(handleText);
        }
        //for manually typed-in values we must adjust left position because JQUI doesn't do it for us
        handle.css('left', leftPos);
        //adjust a negative left margin to avoid overflowing right side of slider body
        handle.css('margin-left', `${leftMargin}px`);
        originalSlider.val(numVal);
        originalSlider.trigger('input');
        originalSlider.trigger('change');
    }
}
function switchUiMode() {
    $('body').toggleClass('no-blur', power_user.fast_ui_mode);
    $('#fast_ui_mode').prop('checked', power_user.fast_ui_mode);
    if (power_user.fast_ui_mode) {
        $('#blur-strength-block').css('opacity', '0.2');
        $('#blur_strength').prop('disabled', true);
    } else {
        $('#blur-strength-block').css('opacity', '1');
        $('#blur_strength').prop('disabled', false);
    }
}
function toggleWaifu() {
    $('#waifuMode').trigger('click');
    return '';
}
function switchWaifuMode() {
    $('body').toggleClass('waifuMode', power_user.waifuMode);
    $('#waifuMode').prop('checked', power_user.waifuMode);
    scrollChatToBottom();
}
function switchSpoilerMode() {
    if (power_user.spoiler_free_mode) {
        $('#descriptionWrapper').hide();
        $('#firstMessageWrapper').hide();
        $('#spoiler_free_desc').addClass('flex1');
        $('#creator_notes_spoiler').show();
    }
    else {
        $('#descriptionWrapper').show();
        $('#firstMessageWrapper').show();
        $('#spoiler_free_desc').removeClass('flex1');
        $('#creator_notes_spoiler').hide();
    }
}
function peekSpoilerMode() {
    $('#descriptionWrapper').toggle();
    $('#firstMessageWrapper').toggle();
    $('#creator_notes_spoiler').toggle();
    $('#spoiler_free_desc').toggleClass('flex1');
}
function switchMovingUI() {
    $('.drawer-content.maximized').each(function () {
        $(this).find('.inline-drawer-maximize').trigger('click');
    });
    $('body').toggleClass('movingUI', power_user.movingUI);
    if (power_user.movingUI === true) {
        initMovingUI();
        if (power_user.movingUIState) {
            loadMovingUIState();
        }
    } else {
        if (Object.keys(power_user.movingUIState).length !== 0) {
            power_user.movingUIState = {};
            resetMovablePanels();
            saveSettingsDebounced();
        }
    }
}
function applyNoShadows() {
    $('body').toggleClass('noShadows', power_user.noShadows);
    $('#noShadowsmode').prop('checked', power_user.noShadows);
    if (power_user.noShadows) {
        $('#shadow-width-block').css('opacity', '0.2');
        $('#shadow_width').prop('disabled', true);
    } else {
        $('#shadow-width-block').css('opacity', '1');
        $('#shadow_width').prop('disabled', false);
    }
    scrollChatToBottom();
}
function applyAvatarStyle() {
    $('body').toggleClass('big-avatars', power_user.avatar_style === avatar_styles.RECTANGULAR);
    $('body').toggleClass('square-avatars', power_user.avatar_style === avatar_styles.SQUARE);
    $('body').toggleClass('rounded-avatars', power_user.avatar_style === avatar_styles.ROUNDED);
    $('#avatar_style').val(power_user.avatar_style).prop('selected', true);
}
function applyChatDisplay() {
    if (!power_user.chat_display === (null || undefined)) {
        console.debug('applyChatDisplay: saw no chat display type defined');
        return;
    }
    console.debug(`poweruser.chat_display ${power_user.chat_display}`);
    $('#chat_display').val(power_user.chat_display).prop('selected', true);
    switch (power_user.chat_display) {
        case 0: {
            console.debug('applying default chat');
            $('body').removeClass('bubblechat');
            $('body').removeClass('documentstyle');
            break;
        }
        case 1: {
            console.debug('applying bubblechat');
            $('body').addClass('bubblechat');
            $('body').removeClass('documentstyle');
            break;
        }
        case 2: {
            console.debug('applying document style');
            $('body').removeClass('bubblechat');
            $('body').addClass('documentstyle');
            break;
        }
    }
}
function applyToastrPosition() {
    if (!toastPositionClasses.includes(power_user.toastr_position)) {
        power_user.toastr_position = defaultToastPosition;
        console.warn(`applyToastrPosition: invalid toastr position, defaulting to ${defaultToastPosition}`);
    }
    toastr.options.positionClass = power_user.toastr_position;
    fixToastrForDialogs();
    $('#toastr_position').val(power_user.toastr_position).prop('selected', true);
}
function applyChatWidth(type) {
    if (type === 'forced') {
        let r = document.documentElement;
        r.style.setProperty('--sheldWidth', `${power_user.chat_width}vw`);
        $('#chat_width_slider').val(power_user.chat_width);
        //document.documentElement.style.setProperty('--sheldWidth', power_user.chat_width);
    } else {
        //this is to prevent the slider from updating page in real time
        $('#chat_width_slider').off('mouseup touchend').on('mouseup touchend', async () => {
            // This is a hack for Firefox to let it render before applying the block width.
            // Otherwise it takes the incorrect slider position with the new value AFTER the resizing.
            await delay(1);
            document.documentElement.style.setProperty('--sheldWidth', `${power_user.chat_width}vw`);
            await delay(1);
        });
    }
    $('#chat_width_slider_counter').val(power_user.chat_width);
}
function applyThemeColor(type) {
    if (type === 'main') {
        document.documentElement.style.setProperty('--SmartThemeBodyColor', power_user.main_text_color);
        const color = power_user.main_text_color.split('(')[1].split(')')[0].split(',');
        document.documentElement.style.setProperty('--SmartThemeCheckboxBgColorR', color[0]);
        document.documentElement.style.setProperty('--SmartThemeCheckboxBgColorG', color[1]);
        document.documentElement.style.setProperty('--SmartThemeCheckboxBgColorB', color[2]);
        document.documentElement.style.setProperty('--SmartThemeCheckboxBgColorA', color[3]);
    }
    if (type === 'italics') {
        document.documentElement.style.setProperty('--SmartThemeEmColor', power_user.italics_text_color);
    }
    if (type === 'underline') {
        document.documentElement.style.setProperty('--SmartThemeUnderlineColor', power_user.underline_text_color);
    }
    if (type === 'quote') {
        document.documentElement.style.setProperty('--SmartThemeQuoteColor', power_user.quote_text_color);
    }
    /*     if (type === 'fastUIBG') {
            document.documentElement.style.setProperty('--SmartThemeFastUIBGColor', power_user.fastui_bg_color);
        } */
    if (type === 'blurTint') {
        let metaThemeColor = document.querySelector('meta[name=theme-color]');
        document.documentElement.style.setProperty('--SmartThemeBlurTintColor', power_user.blur_tint_color);
        metaThemeColor.setAttribute('content', power_user.blur_tint_color);
    }
    if (type === 'chatTint') {
        document.documentElement.style.setProperty('--SmartThemeChatTintColor', power_user.chat_tint_color);
    }
    if (type === 'userMesBlurTint') {
        document.documentElement.style.setProperty('--SmartThemeUserMesBlurTintColor', power_user.user_mes_blur_tint_color);
    }
    if (type === 'botMesBlurTint') {
        document.documentElement.style.setProperty('--SmartThemeBotMesBlurTintColor', power_user.bot_mes_blur_tint_color);
    }
    if (type === 'shadow') {
        document.documentElement.style.setProperty('--SmartThemeShadowColor', power_user.shadow_color);
    }
    if (type === 'border') {
        document.documentElement.style.setProperty('--SmartThemeBorderColor', power_user.border_color);
    }
}
function applyCustomCSS() {
    $('#customCSS').val(power_user.custom_css);
    var styleId = 'custom-style';
    var style = document.getElementById(styleId);
    if (!style) {
        style = document.createElement('style');
        style.setAttribute('type', 'text/css');
        style.setAttribute('id', styleId);
        document.head.appendChild(style);
    }
    style.innerHTML = power_user.custom_css;
}
function applyBlurStrength() {
    document.documentElement.style.setProperty('--blurStrength', String(power_user.blur_strength));
    $('#blur_strength_counter').val(power_user.blur_strength);
    $('#blur_strength').val(power_user.blur_strength);
}
function applyShadowWidth() {
    document.documentElement.style.setProperty('--shadowWidth', String(power_user.shadow_width));
    $('#shadow_width_counter').val(power_user.shadow_width);
    $('#shadow_width').val(power_user.shadow_width);
}
function applyFontScale(type) {
    //this is to allow forced setting on page load, theme swap, etc
    if (type === 'forced') {
        document.documentElement.style.setProperty('--fontScale', String(power_user.font_scale));
    } else {
        //this is to prevent the slider from updating page in real time
        $('#font_scale').off('mouseup touchend').on('mouseup touchend', () => {
            document.documentElement.style.setProperty('--fontScale', String(power_user.font_scale));
        });
    }
    $('#font_scale_counter').val(power_user.font_scale);
    $('#font_scale').val(power_user.font_scale);
}
function applyTheme(name) {
    const theme = themes.find(x => x.name == name);
    if (!theme) {
        return;
    }
    const themeProperties = [
        { key: 'main_text_color', selector: '#main-text-color-picker', type: 'main' },
        { key: 'italics_text_color', selector: '#italics-color-picker', type: 'italics' },
        { key: 'underline_text_color', selector: '#underline-color-picker', type: 'underline' },
        { key: 'quote_text_color', selector: '#quote-color-picker', type: 'quote' },
        { key: 'blur_tint_color', selector: '#blur-tint-color-picker', type: 'blurTint' },
        { key: 'chat_tint_color', selector: '#chat-tint-color-picker', type: 'chatTint' },
        { key: 'user_mes_blur_tint_color', selector: '#user-mes-blur-tint-color-picker', type: 'userMesBlurTint' },
        { key: 'bot_mes_blur_tint_color', selector: '#bot-mes-blur-tint-color-picker', type: 'botMesBlurTint' },
        { key: 'shadow_color', selector: '#shadow-color-picker', type: 'shadow' },
        { key: 'border_color', selector: '#border-color-picker', type: 'border' },
        {
            key: 'blur_strength',
            action: () => {
                applyBlurStrength();
            },
        },
        {
            key: 'custom_css',
            action: () => {
                applyCustomCSS();
            },
        },
        {
            key: 'shadow_width',
            action: () => {
                applyShadowWidth();
            },
        },
        {
            key: 'font_scale',
            action: () => {
                applyFontScale('forced');
            },
        },
        {
            key: 'fast_ui_mode',
            action: () => {
                switchUiMode();
            },
        },
        {
            key: 'waifuMode',
            action: () => {
                switchWaifuMode();
            },
        },
        {
            key: 'chat_display',
            action: () => {
                applyChatDisplay();
            },
        },
        {
            key: 'toastr_position',
            action: () => {
                applyToastrPosition();
            },
        },
        {
            key: 'avatar_style',
            action: () => {
                applyAvatarStyle();
            },
        },
        {
            key: 'noShadows',
            action: () => {
                applyNoShadows();
            },
        },
        {
            key: 'chat_width',
            action: () => {
                // If chat width is not set, set it to 50
                if (!power_user.chat_width) {
                    power_user.chat_width = 50;
                }
                applyChatWidth('forced');
            },
        },
        {
            key: 'timer_enabled',
            action: () => {
                switchTimer();
            },
        },
        {
            key: 'timestamps_enabled',
            action: () => {
                switchTimestamps();
            },
        },
        {
            key: 'timestamp_model_icon',
            action: () => {
                switchIcons();
            },
        },
        {
            key: 'message_token_count_enabled',
            action: () => {
                switchTokenCount();
            },
        },
        {
            key: 'mesIDDisplay_enabled',
            action: () => {
                switchMesIDDisplay();
            },
        },
        {
            key: 'hideChatAvatars_enabled',
            action: () => {
                switchHideChatAvatars();
            },
        },
        {
            key: 'expand_message_actions',
            action: () => {
                switchMessageActions();
            },
        },
        {
            key: 'enableZenSliders',
            action: () => {
                switchMessageActions();
            },
        },
        {
            key: 'enableLabMode',
            action: () => {
                switchMessageActions();
            },
        },
        {
            key: 'hotswap_enabled',
            action: () => {
                switchHotswap();
            },
        },
        {
            key: 'bogus_folders',
            action: () => {
                $('#bogus_folders').prop('checked', power_user.bogus_folders);
                printCharactersDebounced();
            },
        },
        {
            key: 'zoomed_avatar_magnification',
            action: () => {
                $('#zoomed_avatar_magnification').prop('checked', power_user.zoomed_avatar_magnification);
                printCharactersDebounced();
            },
        },
        {
            key: 'reduced_motion',
            action: () => {
                $('#reduced_motion').prop('checked', power_user.reduced_motion);
                switchReducedMotion();
            },
        },
        {
            key: 'compact_input_area',
            action: () => {
                $('#compact_input_area').prop('checked', power_user.compact_input_area);
                switchCompactInputArea();
            },
        },
        {
            key: 'show_swipe_num_all_messages',
            action: () => {
                $('#show_swipe_num_all_messages').prop('checked', power_user.show_swipe_num_all_messages);
                switchSwipeNumAllMessages();
            },
        },
        {
            key: 'click_to_edit',
            action: () => {
                $('#click_to_edit').prop('checked', power_user.click_to_edit);
            },
        },
    ];
    for (const { key, selector, type, action } of themeProperties) {
        if (theme[key] !== undefined) {
            power_user[key] = theme[key];
            if (selector) $(selector).attr('color', power_user[key]);
            if (type) applyThemeColor(type);
            if (action) action();
        } else {
            console.debug(`Empty theme key: ${key}`);
        }
    }
    console.log('theme applied: ' + name);
}
async function applyMovingUIPreset(name) {
    await resetMovablePanels('quiet');
    const movingUIPreset = movingUIPresets.find(x => x.name == name);
    if (!movingUIPreset) {
        return;
    }
    power_user.movingUIState = movingUIPreset.movingUIState;
    console.log('MovingUI Preset applied: ' + name);
    loadMovingUIState();
    saveSettingsDebounced();
}
/**
 * Register a function to be executed when the debug menu is opened.
 * @param {string} functionId Unique ID for the function.
 * @param {string} name Name of the function.
 * @param {string} description Description of the function.
 * @param {function} func Function to be executed.
 */
export function registerDebugFunction(functionId, name, description, func) {
    debug_functions.push({ functionId, name, description, func });
}
async function showDebugMenu() {
    const template = await renderTemplateAsync('debug', { functions: debug_functions });
    callGenericPopup(template, POPUP_TYPE.TEXT, '', { wide: true, large: true, allowVerticalScrolling: true });
}
function applyPowerUserSettings() {
    switchUiMode();
    applyFontScale('forced');
    applyThemeColor();
    applyChatWidth('forced');
    applyAvatarStyle();
    applyBlurStrength();
    applyShadowWidth();
    applyCustomCSS();
    switchMovingUI();
    applyNoShadows();
    switchHotswap();
    switchTimer();
    switchTimestamps();
    switchIcons();
    switchMesIDDisplay();
    switchHideChatAvatars();
    switchTokenCount();
    switchMessageActions();
    switchSwipeNumAllMessages();
}
export function applyStylePins() {
    try {
        const existingPins = document.querySelector('#chat > .style-pins');
        if (existingPins) {
            existingPins.remove();
        }
        if (!power_user.pin_styles) {
            return;
        }
        const firstDisplayed = getFirstDisplayedMessageId();
        if (firstDisplayed === 0 || !isFinite(firstDisplayed)) {
            return;
        }
        const chatElement = document.getElementById('chat');
        if (!chatElement) {
            return;
        }
        const firstMessage = chat[0];
        if (!firstMessage) {
            return;
        }
        const formattedMessage = messageFormatting(firstMessage.mes, firstMessage.name, firstMessage.is_system, firstMessage.is_user, 0, {}, false);
        const htmlElement = document.createElement('div');
        htmlElement.innerHTML = formattedMessage;
        const styleTags = htmlElement.querySelectorAll('style');
        if (styleTags.length === 0) {
            return;
        }
        const pinsElement = document.createElement('div');
        pinsElement.classList.add('style-pins');
        pinsElement.append(...Array.from(styleTags));
        chatElement.prepend(pinsElement);
    } catch (error) {
        console.error('Error applying style pins:', error);
    }
}
function getExampleMessagesBehavior() {
    if (power_user.strip_examples) {
        return 'strip';
    }
    if (power_user.pin_examples) {
        return 'keep';
    }
    return 'normal';
}
//MARK: loadPowerUser
async function loadPowerUserSettings(settings, data) {
    const defaultStscript = JSON.parse(JSON.stringify(power_user.stscript));
    // Load from settings.json
    if (settings.power_user !== undefined) {
        // Migrate old preference to a new setting
        if (settings.power_user.click_to_edit === undefined && settings.power_user.chat_display === chat_styles.DOCUMENT) {
            settings.power_user.click_to_edit = true;
        }
        Object.assign(power_user, settings.power_user);
    }
    if (power_user.stscript === undefined) {
        power_user.stscript = defaultStscript;
    } else {
        if (power_user.stscript.autocomplete === undefined) {
            power_user.stscript.autocomplete = defaultStscript.autocomplete;
        } else {
            if (power_user.stscript.autocomplete.state === undefined) {
                power_user.stscript.autocomplete.state = defaultStscript.autocomplete.state;
            }
            if (power_user.stscript.autocomplete.width === undefined) {
                power_user.stscript.autocomplete.width = defaultStscript.autocomplete.width;
            }
            if (power_user.stscript.autocomplete.font === undefined) {
                power_user.stscript.autocomplete.font = defaultStscript.autocomplete.font;
            }
            if (power_user.stscript.autocomplete.style === undefined) {
                power_user.stscript.autocomplete.style = power_user.stscript.autocomplete_style || defaultStscript.autocomplete.style;
            }
            if (power_user.stscript.autocomplete.select === undefined) {
                power_user.stscript.autocomplete.select = defaultStscript.autocomplete.select;
            }
        }
        if (power_user.stscript.parser === undefined) {
            power_user.stscript.parser = defaultStscript.parser;
        } else if (power_user.stscript.parser.flags === undefined) {
            power_user.stscript.parser.flags = defaultStscript.parser.flags;
        }
        // Cleanup old flags
        delete power_user.stscript.autocomplete_style;
    }
    if (data.themes !== undefined) {
        themes = data.themes;
    }
    if (data.movingUIPresets !== undefined) {
        movingUIPresets = data.movingUIPresets;
    }
    if (data.context !== undefined) {
        context_presets = data.context;
    }
    if (power_user.chat_display === '') {
        power_user.chat_display = chat_styles.DEFAULT;
    }
    if (power_user.waifuMode === '') {
        power_user.waifuMode = false;
    }
    if (power_user.chat_width === '') {
        power_user.chat_width = 50;
    }
    if (power_user.tokenizer === tokenizers.LEGACY) {
        power_user.tokenizer = tokenizers.GPT2;
    }
    // Clean up old/legacy settings
    if (power_user.import_card_tags !== undefined) {
        power_user.tag_import_setting = power_user.import_card_tags ? tag_import_setting.ASK : tag_import_setting.NONE;
        delete power_user.import_card_tags;
    }
    $('#single_line').prop('checked', power_user.single_line);
    $('#relaxed_api_urls').prop('checked', power_user.relaxed_api_urls);
    $('#world_import_dialog').prop('checked', power_user.world_import_dialog);
    $('#enable_auto_select_input').prop('checked', power_user.enable_auto_select_input);
    $('#enable_md_hotkeys').prop('checked', power_user.enable_md_hotkeys);
    $('#trim_spaces').prop('checked', power_user.trim_spaces);
    $('#continue_on_send').prop('checked', power_user.continue_on_send);
    $('#quick_continue').prop('checked', power_user.quick_continue);
    $('#quick_impersonate').prop('checked', power_user.quick_continue);
    $('#mes_continue').css('display', power_user.quick_continue ? '' : 'none');
    $('#mes_impersonate').css('display', power_user.quick_impersonate ? '' : 'none');
    $('#gestures-checkbox').prop('checked', power_user.gestures);
    $('#auto_swipe').prop('checked', power_user.auto_swipe);
    $('#auto_swipe_minimum_length').val(power_user.auto_swipe_minimum_length);
    $('#auto_swipe_blacklist').val(power_user.auto_swipe_blacklist.join(', '));
    $('#auto_swipe_blacklist_threshold').val(power_user.auto_swipe_blacklist_threshold);
    $('#custom_stopping_strings').text(power_user.custom_stopping_strings);
    $('#custom_stopping_strings_macro').prop('checked', power_user.custom_stopping_strings_macro);
    $('#fuzzy_search_checkbox').prop('checked', power_user.fuzzy_search);
    $('#persona_show_notifications').prop('checked', power_user.persona_show_notifications);
    $('#persona_allow_multi_connections').prop('checked', power_user.persona_allow_multi_connections);
    $('#persona_auto_lock').prop('checked', power_user.persona_auto_lock);
    $('#encode_tags').prop('checked', power_user.encode_tags);
    $('#example_messages_behavior').val(getExampleMessagesBehavior());
    $(`#example_messages_behavior option[value="${getExampleMessagesBehavior()}"]`).prop('selected', true);
    $('#context_derived').parent().find('i').toggleClass('toggleEnabled', !!power_user.context_derived);
    $('#context_size_derived').prop('checked', !!power_user.context_size_derived);
    $('#console_log_prompts').prop('checked', power_user.console_log_prompts);
    $('#request_token_probabilities').prop('checked', power_user.request_token_probabilities);
    $('#show_group_chat_queue').prop('checked', power_user.show_group_chat_queue);
    $('#auto_fix_generated_markdown').prop('checked', power_user.auto_fix_generated_markdown);
    $('#auto_scroll_chat_to_bottom').prop('checked', power_user.auto_scroll_chat_to_bottom);
    $('#bogus_folders').prop('checked', power_user.bogus_folders);
    $('#zoomed_avatar_magnification').prop('checked', power_user.zoomed_avatar_magnification);
    $(`#tokenizer option[value="${power_user.tokenizer}"]`).attr('selected', true);
    $(`#send_on_enter option[value=${power_user.send_on_enter}]`).attr('selected', true);
    $('#confirm_message_delete').prop('checked', power_user.confirm_message_delete !== undefined ? !!power_user.confirm_message_delete : true);
    $('#spoiler_free_mode').prop('checked', power_user.spoiler_free_mode);
    $('#collapse-newlines-checkbox').prop('checked', power_user.collapse_newlines);
    $('#always-force-name2-checkbox').prop('checked', power_user.always_force_name2);
    $('#trim_sentences_checkbox').prop('checked', power_user.trim_sentences);
    $('#disable_group_trimming').prop('checked', power_user.disable_group_trimming);
    $('#markdown_escape_strings').val(power_user.markdown_escape_strings);
    $('#fast_ui_mode').prop('checked', power_user.fast_ui_mode);
    $('#waifuMode').prop('checked', power_user.waifuMode);
    $('#movingUImode').prop('checked', power_user.movingUI);
    $('#noShadowsmode').prop('checked', power_user.noShadows);
    $('#start_reply_with').text(power_user.user_prompt_bias);
    $('#chat-show-reply-prefix-checkbox').prop('checked', power_user.show_user_prompt_bias);
    $('#auto_continue_enabled').prop('checked', power_user.auto_continue.enabled);
    $('#auto_continue_allow_chat_completions').prop('checked', power_user.auto_continue.allow_chat_completions);
    $('#auto_continue_target_length').val(power_user.auto_continue.target_length);
    $('#play_message_sound').prop('checked', power_user.play_message_sound);
    $('#play_sound_unfocused').prop('checked', power_user.play_sound_unfocused);
    $('#never_resize_avatars').prop('checked', power_user.never_resize_avatars);
    $('#show_card_avatar_urls').prop('checked', power_user.show_card_avatar_urls);
    $('#auto_save_msg_edits').prop('checked', power_user.auto_save_msg_edits);
    $('#allow_name1_display').prop('checked', power_user.allow_name1_display);
    $('#allow_name2_display').prop('checked', power_user.allow_name2_display);
    //$("#removeXML").prop("checked", power_user.removeXML);
    $('#hotswapEnabled').prop('checked', power_user.hotswap_enabled);
    $('#messageTimerEnabled').prop('checked', power_user.timer_enabled);
    $('#messageTimestampsEnabled').prop('checked', power_user.timestamps_enabled);
    $('#messageModelIconEnabled').prop('checked', power_user.timestamp_model_icon);
    $('#mesIDDisplayEnabled').prop('checked', power_user.mesIDDisplay_enabled);
    $('#hideChatAvatarsEnabled').prop('checked', power_user.hideChatAvatars_enabled);
    $('#prefer_character_prompt').prop('checked', power_user.prefer_character_prompt);
    $('#prefer_character_jailbreak').prop('checked', power_user.prefer_character_jailbreak);
    $('#enableZenSliders').prop('checked', power_user.enableZenSliders).trigger('input');
    $('#enableLabMode').prop('checked', power_user.enableLabMode).trigger('input', { fromInit: true });
    $(`input[name="avatar_style"][value="${power_user.avatar_style}"]`).prop('checked', true);
    $(`#chat_display option[value=${power_user.chat_display}]`).attr('selected', true).trigger('change');
    $(`#toastr_position option[value=${power_user.toastr_position}]`).attr('selected', true).trigger('change');
    $('#chat_width_slider').val(power_user.chat_width);
    $('#token_padding').val(power_user.token_padding);
    $('#aux_field').val(power_user.aux_field);
    $('#tag_import_setting').val(power_user.tag_import_setting);
    $('#stscript_autocomplete_state').val(power_user.stscript.autocomplete.state).trigger('input');
    $('#stscript_autocomplete_autoHide').prop('checked', power_user.stscript.autocomplete.autoHide ?? false).trigger('input');
    $('#stscript_matching').val(power_user.stscript.matching ?? 'fuzzy');
    $('#stscript_autocomplete_style').val(power_user.stscript.autocomplete.style ?? 'theme');
    document.body.setAttribute('data-stscript-style', power_user.stscript.autocomplete.style);
    $('#stscript_autocomplete_select').val(power_user.stscript.autocomplete.select ?? (AUTOCOMPLETE_SELECT_KEY.TAB + AUTOCOMPLETE_SELECT_KEY.ENTER));
    $('#stscript_parser_flag_strict_escaping').prop('checked', power_user.stscript.parser.flags[PARSER_FLAG.STRICT_ESCAPING] ?? false);
    $('#stscript_parser_flag_replace_getvar').prop('checked', power_user.stscript.parser.flags[PARSER_FLAG.REPLACE_GETVAR] ?? false);
    $('#stscript_autocomplete_font_scale').val(power_user.stscript.autocomplete.font.scale ?? defaultStscript.autocomplete.font.scale);
    $('#stscript_autocomplete_font_scale_counter').val(power_user.stscript.autocomplete.font.scale ?? defaultStscript.autocomplete.font.scale);
    document.body.style.setProperty('--ac-font-scale', power_user.stscript.autocomplete.font.scale ?? defaultStscript.autocomplete.font.scale.toString());
    $('#stscript_autocomplete_width_left').val(power_user.stscript.autocomplete.width.left ?? AUTOCOMPLETE_WIDTH.CHAT);
    document.querySelector('#stscript_autocomplete_width_left')?.dispatchEvent(new Event('input', { bubbles: true }));
    $('#stscript_autocomplete_width_right').val(power_user.stscript.autocomplete.width.right ?? AUTOCOMPLETE_WIDTH.CHAT);
    document.querySelector('#stscript_autocomplete_width_right')?.dispatchEvent(new Event('input', { bubbles: true }));
    $('#restore_user_input').prop('checked', power_user.restore_user_input);
    $('#chat_truncation').val(power_user.chat_truncation);
    $('#chat_truncation_counter').val(power_user.chat_truncation);
    $('#streaming_fps').val(power_user.streaming_fps);
    $('#streaming_fps_counter').val(power_user.streaming_fps);
    $('#smooth_streaming').prop('checked', power_user.smooth_streaming);
    $('#smooth_streaming_speed').val(power_user.smooth_streaming_speed);
    $('#font_scale').val(power_user.font_scale);
    $('#font_scale_counter').val(power_user.font_scale);
    $('#blur_strength').val(power_user.blur_strength);
    $('#blur_strength_counter').val(power_user.blur_strength);
    $('#shadow_width').val(power_user.shadow_width);
    $('#shadow_width_counter').val(power_user.shadow_width);
    $('#main-text-color-picker').attr('color', power_user.main_text_color);
    $('#italics-color-picker').attr('color', power_user.italics_text_color);
    $('#underline-color-picker').attr('color', power_user.underline_text_color);
    $('#quote-color-picker').attr('color', power_user.quote_text_color);
    $('#blur-tint-color-picker').attr('color', power_user.blur_tint_color);
    $('#chat-tint-color-picker').attr('color', power_user.chat_tint_color);
    $('#user-mes-blur-tint-color-picker').attr('color', power_user.user_mes_blur_tint_color);
    $('#bot-mes-blur-tint-color-picker').attr('color', power_user.bot_mes_blur_tint_color);
    $('#shadow-color-picker').attr('color', power_user.shadow_color);
    $('#border-color-picker').attr('color', power_user.border_color);
    $('#reduced_motion').prop('checked', power_user.reduced_motion);
    $('#auto-connect-checkbox').prop('checked', power_user.auto_connect);
    $('#auto-load-chat-checkbox').prop('checked', power_user.auto_load_chat);
    $('#forbid_external_media').prop('checked', power_user.forbid_external_media);
    $('#pin_styles').prop('checked', power_user.pin_styles);
    $('#click_to_edit').prop('checked', power_user.click_to_edit);
    for (const theme of themes) {
        const option = document.createElement('option');
        option.value = theme.name;
        option.innerText = theme.name;
        option.selected = theme.name == power_user.theme;
        $('#themes').append(option);
    }
    for (const movingUIPreset of movingUIPresets) {
        const option = document.createElement('option');
        option.value = movingUIPreset.name;
        option.innerText = movingUIPreset.name;
        option.selected = movingUIPreset.name == power_user.movingUIPreset;
        $('#movingUIPresets').append(option);
    }
    $(`#character_sort_order option[data-order="${power_user.sort_order}"][data-field="${power_user.sort_field}"]`).prop('selected', true);
    switchReducedMotion();
    switchCompactInputArea();
    reloadMarkdownProcessor();
    await loadInstructMode(data);
    await loadContextSettings();
    await loadSystemPrompts(data);
    await loadReasoningTemplates(data);
    loadMaxContextUnlocked();
    switchWaifuMode();
    switchSpoilerMode();
    loadMovingUIState();
    loadCharListState();
    toggleMDHotkeyIconDisplay();
    applyToastrPosition();
}
function toggleMDHotkeyIconDisplay() {
    if (power_user.enable_md_hotkeys) {
        $('.mdhotkey_location').each(function () {
            $(this).parent().append('
');
        });
    } else {
        $('.mdhotkey_icon').remove();
    }
}
function loadCharListState() {
    document.body.classList.toggle('charListGrid', power_user.charListGrid);
}
function loadMovingUIState() {
    if (!isMobile()
        && power_user.movingUIState
        && power_user.movingUI === true) {
        console.debug('loading movingUI state');
        for (var elmntName of Object.keys(power_user.movingUIState)) {
            var elmntState = power_user.movingUIState[elmntName];
            try {
                var elmnt = $('#' + $.escapeSelector(elmntName));
                if (elmnt.length) {
                    console.debug(`loading state for ${elmntName}`);
                    elmnt.css(elmntState);
                } else {
                    console.debug(`skipping ${elmntName} because it doesn't exist in the DOM`);
                }
            } catch (err) {
                console.debug(`error occurred while processing ${elmntName}: ${err}`);
            }
        }
    } else {
        console.debug('skipping movingUI state load');
        return;
    }
}
function loadMaxContextUnlocked() {
    $('#max_context_unlocked').prop('checked', power_user.max_context_unlocked);
    $('#max_context_unlocked').on('change', function () {
        power_user.max_context_unlocked = !!$(this).prop('checked');
        switchMaxContextSize();
        saveSettingsDebounced();
    });
    switchMaxContextSize();
}
function switchMaxContextSize() {
    const elements = [
        $('#max_context'),
        $('#max_context_counter'),
        $('#rep_pen_range'),
        $('#rep_pen_range_counter'),
        $('#rep_pen_range_textgenerationwebui'),
        $('#rep_pen_range_counter_textgenerationwebui'),
        $('#dry_penalty_last_n_textgenerationwebui'),
        $('#dry_penalty_last_n_counter_textgenerationwebui'),
        $('#rep_pen_decay_textgenerationwebui'),
        $('#rep_pen_decay_counter_textgenerationwebui'),
    ];
    const maxValue = power_user.max_context_unlocked ? MAX_CONTEXT_UNLOCKED : MAX_CONTEXT_DEFAULT;
    const minValue = power_user.max_context_unlocked ? maxContextMin : maxContextMin;
    const steps = power_user.max_context_unlocked ? unlockedMaxContextStep : maxContextStep;
    $('#rep_pen_range_textgenerationwebui_zenslider').remove(); //unsure why, but this is necessary.
    $('#dry_penalty_last_n_textgenerationwebui_zenslider').remove();
    $('#rep_pen_decay_textgenerationwebui_zenslider').remove();
    for (const element of elements) {
        const id = element.attr('id');
        element.attr('max', maxValue);
        if (typeof id === 'string' && id?.indexOf('max_context') !== -1) {
            element.attr('min', minValue);
            element.attr('step', steps); //only change setps for max context, because rep pen range needs step of 1 due to important values of -1 and 0
        }
        const value = Number(element.val());
        if (value >= maxValue) {
            element.val(maxValue).trigger('input');
        }
    }
    const maxAmountGen = power_user.max_context_unlocked ? MAX_RESPONSE_UNLOCKED : MAX_RESPONSE_DEFAULT;
    $('#amount_gen').attr('max', maxAmountGen);
    $('#amount_gen_counter').attr('max', maxAmountGen);
    if (Number($('#amount_gen').val()) >= maxAmountGen) {
        $('#amount_gen').val(maxAmountGen).trigger('input');
    }
    if (power_user.enableZenSliders) {
        $('#max_context_zenslider').remove();
        CreateZenSliders($('#max_context'));
        $('#rep_pen_range_textgenerationwebui_zenslider').remove();
        CreateZenSliders($('#rep_pen_range_textgenerationwebui'));
        $('#dry_penalty_last_n_textgenerationwebui_zenslider').remove();
        CreateZenSliders($('#dry_penalty_last_n_textgenerationwebui'));
        $('#rep_pen_decay_textgenerationwebui_zenslider').remove();
        CreateZenSliders($('#rep_pen_decay_textgenerationwebui'));
    }
}
// Fetch a compiled object of all preset settings
function getContextSettings() {
    let compiledSettings = {};
    contextControls.forEach((control) => {
        let value = control.isGlobalSetting ? power_user[control.property] : power_user.context[control.property];
        // Force to a boolean if the setting is a checkbox
        if (control.isCheckbox) {
            value = !!value;
        }
        compiledSettings[control.property] = value;
    });
    return compiledSettings;
}
// TODO: Maybe add a refresh button to reset settings to preset
// TODO: Add "global state" if a preset doesn't set the power_user checkboxes
async function loadContextSettings() {
    contextControls.forEach(control => {
        const $element = $(`#${control.id}`);
        if (control.isGlobalSetting) {
            return;
        }
        if (control.defaultValue !== undefined && power_user.context[control.property] === undefined) {
            power_user.context[control.property] = control.defaultValue;
        }
        if (control.isCheckbox) {
            $element.prop('checked', power_user.context[control.property]);
        } else {
            $element.val(power_user.context[control.property]);
        }
        console.debug(`Setting ${$element.prop('id')} to ${power_user.context[control.property]}`);
        // If the setting already exists, no need to duplicate it
        // TODO: Maybe check the power_user object for the setting instead of a flag?
        $element.on('input', async function () {
            const value = control.isCheckbox ? !!$(this).prop('checked') : $(this).val();
            if (control.isGlobalSetting) {
                power_user[control.property] = value;
            } else {
                power_user.context[control.property] = value;
            }
            console.debug(`Setting ${$element.prop('id')} to ${value}`);
            if (!CSS.supports('field-sizing', 'content') && $(this).is('textarea')) {
                await resetScrollHeight($(this));
            }
            saveSettingsDebounced();
        });
    });
    context_presets.forEach((preset) => {
        const name = preset.name;
        const option = document.createElement('option');
        option.value = name;
        option.innerText = name;
        option.selected = name === power_user.context.preset;
        $('#context_presets').append(option);
    });
    $('#context_presets').on('change', function () {
        const name = String($(this).find(':selected').text());
        const preset = context_presets.find(x => x.name === name);
        if (!preset) {
            return;
        }
        power_user.context.preset = name;
        contextControls.forEach(control => {
            const presetValue = preset[control.property] ?? control.defaultValue;
            if (presetValue !== undefined) {
                if (control.isGlobalSetting) {
                    power_user[control.property] = presetValue;
                } else {
                    power_user.context[control.property] = presetValue;
                }
                const $element = $(`#${control.id}`);
                if (control.isCheckbox) {
                    $element
                        .prop('checked', control.isGlobalSetting ? power_user[control.property] : power_user.context[control.property])
                        .trigger('input');
                } else {
                    $element.val(control.isGlobalSetting ? power_user[control.property] : power_user.context[control.property]);
                    $element.trigger('input');
                }
            }
        });
        if (power_user.instruct.bind_to_context) {
            // Select matching instruct preset
            for (const instruct_preset of instruct_presets) {
                // If instruct preset matches the context template
                if (instruct_preset.name === name) {
                    selectInstructPreset(instruct_preset.name, { isAuto: true });
                    break;
                }
            }
        }
        saveSettingsDebounced();
    });
}
/**
 * Common function to perform fuzzy search with optional caching
 * @template T
 * @param {string} type - Type of search from fuzzySearchCategories
 * @param {T[]} data - Data array to search in
 * @param {Array<{name: string, weight: number, getFn?: (obj: T) => string}>} keys - Fuse.js keys configuration
 * @param {string} searchValue - The search term
 * @param {Object.
 }>} [fuzzySearchCaches=null] - Optional fuzzy search caches
 * @returns {import('fuse.js').FuseResult[]} Results as items with their score
 */
export function performFuzzySearch(type, data, keys, searchValue, fuzzySearchCaches = null) {
    // Check cache if provided
    if (fuzzySearchCaches) {
        const cache = fuzzySearchCaches[type];
        if (cache?.resultMap.has(searchValue)) {
            return cache.resultMap.get(searchValue);
        }
    }
    const fuse = new Fuse(data, {
        keys: keys,
        includeScore: true,
        ignoreLocation: true,
        useExtendedSearch: true,
        threshold: 0.2,
    });
    const results = fuse.search(searchValue);
    // Store in cache if provided
    if (fuzzySearchCaches) {
        fuzzySearchCaches[type].resultMap.set(searchValue, results);
    }
    return results;
}
/**
 * Fuzzy search characters by a search term
 * @param {string} searchValue - The search term
 * @param {Object. }>} [fuzzySearchCaches=null] - Optional fuzzy search caches
 * @returns {import('fuse.js').FuseResult[]} Results as items with their score
 */
export function fuzzySearchCharacters(searchValue, fuzzySearchCaches = null) {
    const keys = [
        { name: 'data.name', weight: 20 },
        { name: '#tags', weight: 10, getFn: (character) => getTagsList(character.avatar).map(x => x.name).join('||') },
        { name: 'data.description', weight: 3 },
        { name: 'data.mes_example', weight: 3 },
        { name: 'data.scenario', weight: 2 },
        { name: 'data.personality', weight: 2 },
        { name: 'data.first_mes', weight: 2 },
        { name: 'data.creator_notes', weight: 2 },
        { name: 'data.creator', weight: 1 },
        { name: 'data.tags', weight: 1 },
        { name: 'data.alternate_greetings', weight: 1 },
    ];
    return performFuzzySearch(fuzzySearchCategories.characters, characters, keys, searchValue, fuzzySearchCaches);
}
/**
 * Fuzzy search world info entries by a search term
 * @param {*[]} data - WI items data array
 * @param {string} searchValue - The search term
 * @param {Object. }>} [fuzzySearchCaches=null] - Optional fuzzy search caches
 * @returns {import('fuse.js').FuseResult[]} Results as items with their score
 */
export function fuzzySearchWorldInfo(data, searchValue, fuzzySearchCaches = null) {
    const keys = [
        { name: 'key', weight: 20 },
        { name: 'group', weight: 15 },
        { name: 'comment', weight: 10 },
        { name: 'keysecondary', weight: 10 },
        { name: 'content', weight: 3 },
        { name: 'uid', weight: 1 },
        { name: 'automationId', weight: 1 },
    ];
    return performFuzzySearch(fuzzySearchCategories.worldInfo, data, keys, searchValue, fuzzySearchCaches);
}
/**
 * Fuzzy search persona entries by a search term
 * @param {*[]} data - persona data array
 * @param {string} searchValue - The search term
 * @param {Object. }>} [fuzzySearchCaches=null] - Optional fuzzy search caches
 * @returns {import('fuse.js').FuseResult[]} Results as items with their score
 */
export function fuzzySearchPersonas(data, searchValue, fuzzySearchCaches = null) {
    const mappedData = data.map(x => ({
        key: x,
        name: power_user.personas[x] ?? '',
        description: power_user.persona_descriptions[x]?.description ?? '',
    }));
    const keys = [
        { name: 'name', weight: 20 },
        { name: 'description', weight: 3 },
    ];
    return performFuzzySearch(fuzzySearchCategories.personas, mappedData, keys, searchValue, fuzzySearchCaches);
}
/**
 * Fuzzy search tags by a search term
 * @param {string} searchValue - The search term
 * @param {Object. }>} [fuzzySearchCaches=null] - Optional fuzzy search caches
 * @returns {import('fuse.js').FuseResult[]} Results as items with their score
 */
export function fuzzySearchTags(searchValue, fuzzySearchCaches = null) {
    const keys = [
        { name: 'name', weight: 1 },
    ];
    return performFuzzySearch(fuzzySearchCategories.tags, tags, keys, searchValue, fuzzySearchCaches);
}
/**
 * Fuzzy search groups by a search term
 * @param {string} searchValue - The search term
 * @param {Object. }>} [fuzzySearchCaches=null] - Optional fuzzy search caches
 * @returns {import('fuse.js').FuseResult[]} Results as items with their score
 */
export function fuzzySearchGroups(searchValue, fuzzySearchCaches = null) {
    const keys = [
        { name: 'name', weight: 20 },
        { name: 'members', weight: 15 },
        { name: '#tags', weight: 10, getFn: (group) => getTagsList(group.id).map(x => x.name).join('||') },
        { name: 'id', weight: 1 },
    ];
    return performFuzzySearch(fuzzySearchCategories.groups, groups, keys, searchValue, fuzzySearchCaches);
}
/**
 * Renders a story string template with the given parameters.
 * @param {object} params Template parameters.
 * @param {object} [options] Additional options.
 * @param {string} [options.customStoryString] Custom story string template.
 * @param {InstructSettings} [options.customInstructSettings] Custom instruct settings.
 * @returns {string} The rendered story string.
 */
export function renderStoryString(params, { customStoryString = null, customInstructSettings = null } = {}) {
    try {
        const storyString = customStoryString ?? power_user.context.story_string;
        const instructSettings = structuredClone(customInstructSettings ?? power_user.instruct);
        // Validate and log possible warnings/errors
        validateStoryString(storyString, params);
        // compile the story string template into a function, with no HTML escaping
        const compiledTemplate = Handlebars.compile(storyString, { noEscape: true });
        // render the story string template with the given params
        let output = compiledTemplate(params);
        // substitute {{macro}} params that are not defined in the story string
        output = substituteParams(output, params.user, params.char);
        // remove leading newlines
        output = output.replace(/^\n+/, '');
        // add a newline to the end of the story string if it doesn't have one
        if (output.length > 0 && !output.endsWith('\n')) {
            if (!instructSettings.enabled || instructSettings.wrap) {
                output += '\n';
            }
        }
        return output;
    } catch (e) {
        toastr.error('Check the story string template for validity', 'Error rendering story string');
        console.error('Error rendering story string', e);
        throw e; // rethrow the error
    }
}
/**
 * Validate the story string for possible warnings or issues
 *
 * @param {string} storyString - The story string
 * @param {Object} params - The story string parameters
 */
function validateStoryString(storyString, params) {
    /** @type {{hashCache: {[hash: string]: {fieldsWarned: {[key: string]: boolean}}}}} */
    const cache = JSON.parse(accountStorage.getItem(storage_keys.storyStringValidationCache)) ?? { hashCache: {} };
    const hash = getStringHash(storyString);
    // Initialize the cache for the current hash if it doesn't exist
    if (!cache.hashCache[hash]) {
        cache.hashCache[hash] = { fieldsWarned: {} };
    }
    const currentCache = cache.hashCache[hash];
    const fieldsToWarn = [];
    function validateMissingField(field, fallbackLegacyField = null) {
        const contains = storyString.includes(`{{${field}}}`) || (!!fallbackLegacyField && storyString.includes(`{{${fallbackLegacyField}}}`));
        if (!contains && params[field]) {
            const wasLogged = currentCache.fieldsWarned[field];
            if (!wasLogged) {
                fieldsToWarn.push(field);
                currentCache.fieldsWarned[field] = true;
            }
            console.warn(`The story string does not contain {{${field}}}, but it would contain content:\n`, params[field]);
        }
    }
    validateMissingField('description');
    validateMissingField('personality');
    validateMissingField('persona');
    validateMissingField('scenario');
    // validateMissingField('system');
    validateMissingField('wiBefore', 'loreBefore');
    validateMissingField('wiAfter', 'loreAfter');
    if (fieldsToWarn.length > 0) {
        const fieldsList = fieldsToWarn.map(field => `{{${field}}}`).join(', ');
        toastr.warning(`The story string does not contain the following fields, but they would contain content: ${fieldsList}`, 'Story String Validation');
    }
    accountStorage.setItem(storage_keys.storyStringValidationCache, JSON.stringify(cache));
}
const sortFunc = (a, b) => power_user.sort_order == 'asc' ? compareFunc(a, b) : compareFunc(b, a);
const compareFunc = (first, second) => {
    const a = first[power_user.sort_field];
    const b = second[power_user.sort_field];
    if (power_user.sort_field === 'create_date') {
        return sortMoments(timestampToMoment(b), timestampToMoment(a));
    }
    switch (power_user.sort_rule) {
        case 'boolean':
            if (a === true || a === 'true') return 1;  // Prioritize 'true' or true
            if (b === true || b === 'true') return -1; // Prioritize 'true' or true
            if (a && !b) return -1;        // Move truthy values to the end
            if (!a && b) return 1;         // Move falsy values to the beginning
            if (a === b) return 0;         // Sort equal values normally
            return a < b ? -1 : 1;         // Sort non-boolean values normally
        default:
            return typeof a == 'string'
                ? a.localeCompare(b)
                : a - b;
    }
};
/**
 * Sorts an array of entities based on the current sort settings
 * @param {any[]} entities An array of objects with an `item` property
 * @param {boolean} forceSearch Whether to force search sorting
 * @param {import('./filters.js').FilterHelper} [filterHelper=null] Filter helper to use
 */
export function sortEntitiesList(entities, forceSearch, filterHelper = null) {
    filterHelper = filterHelper ?? entitiesFilter;
    if (power_user.sort_field == undefined || entities.length === 0) {
        return;
    }
    const isSearch = forceSearch || $('#character_sort_order option[data-field="search"]').is(':selected');
    if (!isSearch && power_user.sort_order === 'random') {
        shuffle(entities);
        return;
    }
    entities.sort((a, b) => {
        // Sort tags/folders will always be at the top. Their original sorting will be kept, to respect manual tag sorting.
        if (a.type === 'tag' || b.type === 'tag') {
            // The one that is a tag will be at the top
            return (a.type === 'tag' ? -1 : 1) - (b.type === 'tag' ? -1 : 1);
        }
        // If we have search sorting, we take scores and use those
        if (isSearch) {
            const aScore = filterHelper.getScore(FILTER_TYPES.SEARCH, `${a.type}.${a.id}`);
            const bScore = filterHelper.getScore(FILTER_TYPES.SEARCH, `${b.type}.${b.id}`);
            return (aScore - bScore);
        }
        return sortFunc(a.item, b.item);
    });
}
/**
 * Updates the current UI theme file.
 */
async function updateTheme() {
    await saveTheme(power_user.theme);
    toastr.success('Theme saved.');
}
async function deleteTheme() {
    const themeName = power_user.theme;
    if (!themeName) {
        toastr.info('No theme selected.');
        return;
    }
    const template = $(await renderTemplateAsync('themeDelete', { themeName }));
    const confirm = await callGenericPopup(template, POPUP_TYPE.CONFIRM);
    if (!confirm) {
        return;
    }
    const response = await fetch('/api/themes/delete', {
        method: 'POST',
        headers: getRequestHeaders(),
        body: JSON.stringify({ name: themeName }),
    });
    if (!response.ok) {
        toastr.error('Failed to delete theme. Check the console for more information.');
        return;
    }
    const themeIndex = themes.findIndex(x => x.name == themeName);
    if (themeIndex !== -1) {
        themes.splice(themeIndex, 1);
        $(`#themes option[value="${themeName}"]`).remove();
        power_user.theme = themes[0]?.name;
        saveSettingsDebounced();
        if (power_user.theme) {
            applyTheme(power_user.theme);
        }
        toastr.success('Theme deleted.');
    }
}
/**
 * Exports the current theme to a file.
 */
async function exportTheme() {
    const themeFile = await saveTheme(power_user.theme);
    const fileName = `${themeFile.name}.json`;
    download(JSON.stringify(themeFile, null, 4), fileName, 'application/json');
}
/**
 * Imports a theme from a file.
 * @param {File} file File to import.
 * @returns {Promise} A promise that resolves when the theme is imported.
 */
async function importTheme(file) {
    if (!file) {
        return;
    }
    const fileText = await getFileText(file);
    const parsed = JSON.parse(fileText);
    if (!parsed.name) {
        throw new Error('Missing name');
    }
    if (themes.some(t => t.name === parsed.name)) {
        throw new Error('Theme with that name already exists');
    }
    if (typeof parsed.custom_css === 'string' && parsed.custom_css.includes('@import')) {
        const template = $(await renderTemplateAsync('themeImportWarning'));
        const confirm = await callGenericPopup(template, POPUP_TYPE.CONFIRM);
        if (!confirm) {
            throw new Error('Theme contains @import lines');
        }
    }
    themes.push(parsed);
    await saveTheme(parsed.name, getNewTheme(parsed));
    const option = document.createElement('option');
    option.selected = false;
    option.value = parsed.name;
    option.innerText = parsed.name;
    $('#themes').append(option);
    saveSettingsDebounced();
    toastr.success(parsed.name, 'Theme imported');
}
/**
 * Saves the current theme to the server.
 * @param {string|undefined} name Theme name. If undefined, a popup will be shown to enter a name.
 * @param {object|undefined} theme Theme object. If undefined, the current theme will be saved.
 * @returns {Promise