import { humanizedDateTime } from "./scripts/RossAscends-mods.js"; import { encode } from "../scripts/gpt-2-3-tokenizer/mod.js"; import { GPT3BrowserTokenizer } from "../scripts/gpt-3-tokenizer/gpt3-tokenizer.js"; import { kai_settings, loadKoboldSettings, formatKoboldUrl, getKoboldGenerationData, canUseKoboldStopSequence, } from "./scripts/kai-settings.js"; import { textgenerationwebui_settings, loadTextGenSettings, generateTextGenWithStreaming, } from "./scripts/textgen-settings.js"; import { world_info_budget, world_info_data, world_info_depth, world_info, getWorldInfoPrompt, selectImportedWorldInfo, setWorldInfoSettings, deleteWorldInfo, } from "./scripts/world-info.js"; import { groups, selected_group, saveGroupChat, getGroups, generateGroupWrapper, deleteGroup, is_group_generating, printGroups, resetSelectedGroup, select_group_chats, regenerateGroup, } from "./scripts/group-chats.js"; import { collapseNewlines, loadPowerUserSettings, playMessageSound, sortCharactersList, power_user, pygmalion_options, } from "./scripts/power-user.js"; import { setOpenAIMessageExamples, setOpenAIMessages, prepareOpenAIMessages, sendOpenAIRequest, loadOpenAISettings, setOpenAIOnlineStatus, generateOpenAIPromptCache, oai_settings, is_get_status_openai, openai_msgs, } from "./scripts/openai.js"; import { getNovelTier, loadNovelPreset, loadNovelSettings, nai_settings, } from "./scripts/nai-settings.js"; import { showBookmarksButtons } from "./scripts/bookmarks.js"; import { horde_settings, loadHordeSettings, generateHorde, checkHordeStatus, getHordeModels, adjustHordeGenerationParams, } from "./scripts/horde.js"; import { poe_settings, loadPoeSettings, generatePoe, is_get_status_poe, setPoeOnlineStatus, } from "./scripts/poe.js"; import { debounce, delay } from "./scripts/utils.js"; import { extension_settings, loadExtensionSettings } from "./scripts/extensions.js"; //exporting functions and vars for mods export { Generate, getSettings, saveSettings, saveSettingsDebounced, printMessages, clearChat, getChat, getCharacters, callPopup, substituteParams, sendSystemMessage, addOneMessage, deleteLastMessage, resetChatState, select_rm_info, setCharacterId, setCharacterName, setOnlineStatus, checkOnlineStatus, setEditedMessageId, setSendButtonState, selectRightMenuWithAnimation, setRightTabSelectedClass, openCharacterChat, saveChat, messageFormating, getExtensionPrompt, showSwipeButtons, hideSwipeButtons, changeMainAPI, setGenerationProgress, updateChatMetadata, scrollChatToBottom, getTokenCount, isStreamingEnabled, getThumbnailUrl, getStoppingStrings, getStatus, chat, this_chid, settings, characters, online_status, main_api, api_server, system_messages, nai_settings, token, name1, name2, is_send_press, api_server_textgenerationwebui, count_view_mes, max_context, chat_metadata, default_avatar, system_message_types, talkativeness_default, default_ch_mes, extension_prompt_types, } // API OBJECT FOR EXTERNAL WIRING window["TavernAI"] = {}; let converter = new showdown.Converter({ emoji: "true" }); const gpt3 = new GPT3BrowserTokenizer({ type: 'gpt3' }); /* let bg_menu_toggle = false; */ const systemUserName = "TavernAI"; let default_user_name = "You"; let name1 = default_user_name; let name2 = "TavernAI"; let chat = []; let safetychat = [ { name: systemUserName, is_user: false, is_name: true, create_date: 0, mes: "\n*You deleted a character/chat and arrived back here for safety reasons! Pick another character!*\n\n", }, ]; let chat_create_date = 0; let prev_selected_char = null; const default_ch_mes = "Hello"; let count_view_mes = 0; let mesStr = ""; let generatedPromtCache = ""; let characters = []; let this_chid; let active_character; let backgrounds = []; const default_avatar = "img/ai4.png"; const system_avatar = "img/five.png"; let is_colab = false; let is_checked_colab = false; let is_mes_reload_avatar = false; let optionsPopper = Popper.createPopper(document.getElementById('send_form'), document.getElementById('options'), { placement: 'top-start' }); let exportPopper = Popper.createPopper(document.getElementById('export_button'), document.getElementById('export_format_popup'), { placement: 'left', }); let dialogueResolve = null; let chat_metadata = {}; let streamingProcessor = null; const durationSaveEdit = 200; const saveSettingsDebounced = debounce(() => saveSettings(), durationSaveEdit); const saveCharacterDebounced = debounce(() => $("#create_button").click(), durationSaveEdit); const getStatusDebounced = debounce(() => getStatus(), 90000); const system_message_types = { HELP: "help", WELCOME: "welcome", GROUP: "group", EMPTY: "empty", GENERIC: "generic", BOOKMARK_CREATED: "bookmark_created", BOOKMARK_BACK: "bookmark_back", }; const extension_prompt_types = { AFTER_SCENARIO: 0, IN_CHAT: 1 }; const system_messages = { help: { name: systemUserName, force_avatar: system_avatar, is_user: false, is_system: true, is_name: true, mes: [ 'Hi there! The following chat formatting commands are supported:', '
[\s\S]*?<\/code>/g, function (match) {
            return match.replace(/&/g, '&');
        });
    }
    if (forceAvatar) {
        mes = mes.replaceAll(ch_name + ":", "");
    }
    if (ch_name !== name1) {
        mes = mes.replaceAll(name2 + ":", "");
    }
    return mes;
}
function getMessageFromTemplate(mesId, characterName, isUser, avatarImg, bias, isSystem) {
    const mes = $('#message_template .mes').clone();
    mes.attr({ 'mesid': mesId, 'ch_name': characterName, 'is_user': isUser, 'is_system': !!isSystem });
    mes.find('.avatar img').attr('src', avatarImg);
    mes.find('.ch_name .name_text').text(characterName);
    mes.find('.mes_bias').html(bias);
    return mes;
}
function appendImageToMessage(mes, messageElement) {
    if (mes.extra?.image) {
        const image = document.createElement("img");
        image.src = mes.extra?.image;
        image.title = mes.title;
        image.classList.add("img_extra");
        messageElement.find(".mes_text").prepend(image);
    }
}
function addOneMessage(mes, type = "normal", insertAfter = null) {
    var messageText = mes["mes"];
    var characterName = name1;
    var avatarImg = "User Avatars/" + user_avatar;
    const isSystem = mes.is_system;
    generatedPromtCache = "";
    if (!mes["is_user"]) {
        if (mes.force_avatar) {
            avatarImg = mes.force_avatar;
        } else if (this_chid == undefined || this_chid == "invalid-safety-id") {
            avatarImg = system_avatar;
        } else {
            if (characters[this_chid].avatar != "none") {
                avatarImg = getThumbnailUrl('avatar', characters[this_chid].avatar);
                if (is_mes_reload_avatar !== false) {
                    avatarImg += "&" + is_mes_reload_avatar;
                }
            } else {
                avatarImg = default_avatar;
            }
        }
        characterName = mes.is_system || mes.force_avatar ? mes.name : name2;
    }
    if (count_view_mes == 0) {
        messageText = substituteParams(messageText);
    }
    messageText = messageFormating(
        messageText,
        characterName,
        isSystem,
        mes.force_avatar
    );
    const bias = messageFormating(mes.extra?.bias ?? "");
    var HTMLForEachMes = getMessageFromTemplate(count_view_mes, characterName, mes.is_user, avatarImg, bias, isSystem);
    if (type !== 'swipe') {
        if (!insertAfter) {
            $("#chat").append(HTMLForEachMes);
        }
        else {
            const target = $("#chat").find(`.mes[mesid="${insertAfter}"]`);
            $(HTMLForEachMes).insertAfter(target);
            $(HTMLForEachMes).find('.swipe_left').css('display', 'none');
            $(HTMLForEachMes).find('.swipe_right').css('display', 'none');
        }
    }
    const newMessage = $(`#chat [mesid="${count_view_mes}"]`);
    newMessage.data("isSystem", isSystem);
    appendImageToMessage(mes, newMessage);
    if (isSystem) {
        newMessage.find(".mes_edit").hide();
    }
    newMessage.find('.avatar img').on('error', function () {
        /* $(this).attr("src", "/img/user-slash-solid.svg"); */
        $(this).hide();
        $(this).parent().html(``);
    });
    if (type === 'swipe') {
        $("#chat").children().filter('[mesid="' + (count_view_mes - 1) + '"]').children('.mes_block').children('.mes_text').html('');
        $("#chat").children().filter('[mesid="' + (count_view_mes - 1) + '"]').children('.mes_block').children('.mes_text').append(messageText);
        //console.log(mes);
    } else {
        $("#chat").children().filter('[mesid="' + count_view_mes + '"]').children('.mes_block').children('.mes_text').append(messageText);
        hideSwipeButtons();
        count_view_mes++;
    }
    /*
    const lastMes = $('#chat .mes').last().get(0);
    const rect = lastMes.getBoundingClientRect();
    lastMes.style.containIntrinsicSize = `${rect.width}px ${rect.height}px`;
    */
    // Don't scroll if not inserting last
    if (!insertAfter) {
        $('#chat .mes').last().addClass('last_mes');
        $('#chat .mes').eq(-2).removeClass('last_mes');
        hideSwipeButtons();
        showSwipeButtons();
        scrollChatToBottom();
    }
}
function scrollChatToBottom() {
    var $textchat = $("#chat");
    $textchat.scrollTop(($textchat[0].scrollHeight));
}
function substituteParams(content, _name1, _name2) {
    _name1 = _name1 ?? name1;
    _name2 = _name2 ?? name2;
    content = content.replace(/{{user}}/gi, _name1);
    content = content.replace(/{{char}}/gi, _name2);
    content = content.replace(//gi, _name1);
    content = content.replace(//gi, _name2);
    return content;
}
function getStoppingStrings(isImpersonate, wrapInQuotes) {
    const charString = `\n${name2}: `;
    const userString = is_pygmalion ? `\nYou: ` : `\n${name1}: `;
    const result = isImpersonate ? charString : userString;
    return wrapInQuotes ? `"${result}"` : result;
}
function getSlashCommand(message, type) {
    if (type == "regenerate" || type == "swipe") {
        return null;
    }
    const commandMap = {
        "/?": system_message_types.HELP,
        "/help": system_message_types.HELP
    };
    const activationText = message.trim().toLowerCase();
    if (Object.keys(commandMap).includes(activationText)) {
        return commandMap[activationText];
    }
    return null;
}
function sendSystemMessage(type, text) {
    const systemMessage = system_messages[type];
    if (!systemMessage) {
        return;
    }
    const newMessage = { ...systemMessage, send_date: humanizedDateTime() };
    if (text) {
        newMessage.mes = text;
    }
    if (!newMessage.extras) {
        newMessage.extras = {};
    }
    newMessage.extras.type = type;
    chat.push(newMessage);
    addOneMessage(newMessage);
    is_send_press = false;
}
function extractMessageBias(message) {
    if (!message) {
        return null;
    }
    const found = [];
    const rxp = /{{(\*?.+?\*?)}}/g;
    //const rxp = /{([^}]+)}/g;
    let curMatch;
    while ((curMatch = rxp.exec(message))) {
        found.push(curMatch[1].trim());
    }
    if (!found.length) {
        // cancels a bias
        if (message.includes('{') && message.includes('}')) {
            return '';
        }
        return null;
    }
    return ` ${found.join(" ")} `;
}
function cleanGroupMessage(getMessage) {
    const group = groups.find((x) => x.id == selected_group);
    if (group && Array.isArray(group.members) && group.members) {
        for (let member of group.members) {
            // Skip current speaker.
            if (member === name2) {
                continue;
            }
            const indexOfMember = getMessage.indexOf(member + ":");
            if (indexOfMember != -1) {
                getMessage = getMessage.substr(0, indexOfMember);
            }
        }
    }
    return getMessage;
}
function getAllExtensionPrompts() {
    return substituteParams(Object
        .values(extension_prompts)
        .filter(x => x.value)
        .map(x => x.value.trim())
        .join('\n'));
}
function getExtensionPrompt(position = 0, depth = undefined, separator = "\n") {
    let extension_prompt = Object.keys(extension_prompts)
        .sort()
        .map((x) => extension_prompts[x])
        .filter(x => x.position == position && x.value && (depth === undefined || x.depth == depth))
        .map(x => x.value.trim())
        .join(separator);
    if (extension_prompt.length && !extension_prompt.startsWith(separator)) {
        extension_prompt = separator + extension_prompt;
    }
    if (extension_prompt.length && !extension_prompt.endsWith(separator)) {
        extension_prompt = extension_prompt + separator;
    }
    extension_prompt = substituteParams(extension_prompt);
    return extension_prompt;
}
function baseChatReplace(value, name1, name2) {
    if (value !== undefined && value.length > 0) {
        if (is_pygmalion) {
            value = value.replace(/{{user}}:/gi, "You:");
            value = value.replace(/:/gi, "You:");
        }
        value = value.replace(/{{user}}/gi, name1);
        value = value.replace(/{{char}}/gi, name2);
        value = value.replace(//gi, name1);
        value = value.replace(//gi, name2);
        if (power_user.collapse_newlines) {
            value = collapseNewlines(value);
        }
    }
    return value;
}
function appendToStoryString(value, prefix) {
    if (value !== undefined && value.length > 0) {
        return prefix + value + '\n';
    }
    return '';
}
function isStreamingEnabled() {
    return (main_api == 'openai' && oai_settings.stream_openai)
        || (main_api == 'poe' && poe_settings.streaming)
        || (main_api == 'textgenerationwebui' && textgenerationwebui_settings.streaming);
}
class StreamingProcessor {
    showStopButton(messageId) {
        if (messageId == -1) {
            return;
        }
        $(`#chat .mes[mesid="${messageId}"] .mes_stop`).css({ 'display': 'block' });
        $(`#chat .mes[mesid="${messageId}"] .mes_edit`).css({ 'display': 'none' });
    }
    hideStopButton(messageId) {
        if (messageId == -1) {
            return;
        }
        $(`#chat .mes[mesid="${messageId}"] .mes_stop`).css({ 'display': 'none' });
        $(`#chat .mes[mesid="${messageId}"] .mes_edit`).css({ 'display': 'block' });
    }
    onStartStreaming(text) {
        let messageId = -1;
        if (this.type == "impersonate") {
            $('#send_textarea').val('').trigger('input');
        }
        else {
            saveReply(this.type, text);
            messageId = count_view_mes - 1;
            this.showStopButton(messageId);
        }
        hideSwipeButtons();
        scrollChatToBottom();
        return messageId;
    }
    onProgressStreaming(messageId, text) {
        const isImpersonate = this.type == "impersonate";
        let processedText = cleanUpMessage(text, isImpersonate);
        let result = extractNameFromMessage(processedText, this.force_name2, isImpersonate);
        let isName = result.this_mes_is_name;
        processedText = result.getMessage;
        if (isImpersonate) {
            $('#send_textarea').val(processedText).trigger('input');
        }
        else {
            chat[messageId]['is_name'] = isName;
            chat[messageId]['mes'] = processedText;
            if (this.type == 'swipe' && Array.isArray(chat[messageId]['swipes'])) {
                chat[messageId]['swipes'][chat[messageId]['swipe_id']] = processedText;
            }
            let formattedText = messageFormating(processedText, chat[messageId].name, chat[messageId].is_system, chat[messageId].force_avatar);
            const mesText = $(`#chat .mes[mesid="${messageId}"] .mes_text`);
            mesText.html(formattedText);
        }
        scrollChatToBottom();
    }
    onFinishStreaming(messageId, text) {
        this.hideStopButton(this.messageId);
        this.onProgressStreaming(messageId, text);
        playMessageSound();
        saveChatConditional();
        activateSendButtons();
        showSwipeButtons();
        setGenerationProgress(0);
        $('.mes_edit:last').show();
        generatedPromtCache = '';
    }
    onErrorStreaming() {
        this.hideStopButton(this.messageId);
        $("#send_textarea").removeAttr('disabled');
        is_send_press = false;
        activateSendButtons();
        setGenerationProgress(0);
        showSwipeButtons();
    }
    onStopStreaming() {
        this.onErrorStreaming();
    }
    nullStreamingGeneration() {
        throw new Error('Generation function for streaming is not hooked up');
    }
    constructor(type, force_name2) {
        this.result = "";
        this.messageId = -1;
        this.type = type;
        this.force_name2 = force_name2;
        this.isStopped = false;
        this.isFinished = false;
        this.generator = this.nullStreamingGeneration;
    }
    async generate() {
        if (this.messageId == -1) {
            this.messageId = this.onStartStreaming('...');
            await delay(1); // delay for message to be rendered
        }
        for await (const text of this.generator()) {
            if (this.isStopped) {
                this.onStopStreaming();
                return;
            }
            try {
                this.result = text;
                this.onProgressStreaming(this.messageId, message_already_generated + text);
            }
            catch (err) {
                console.error(err);
                this.onErrorStreaming();
                this.isStopped = true;
                return;
            }
        }
        this.isFinished = true;
        return this.result;
    }
}
async function Generate(type, automatic_trigger, force_name2) {
    console.log('Generate entered');
    setGenerationProgress(0);
    tokens_already_generated = 0;
    const isImpersonate = type == "impersonate";
    message_already_generated = isImpersonate ? `${name1}: ` : `${name2}: `;
    const slashCommand = getSlashCommand($("#send_textarea").val(), type);
    if (slashCommand == system_message_types.HELP) {
        sendSystemMessage(system_message_types.HELP);
        $("#send_textarea").val('').trigger('input');
        return;
    }
    if (isHordeGenerationNotAllowed()) {
        is_send_press = false;
        return;
    }
    if (isStreamingEnabled()) {
        streamingProcessor = new StreamingProcessor(type, force_name2);
    }
    else {
        streamingProcessor = false;
    }
    if (selected_group && !is_group_generating && !isImpersonate) {
        generateGroupWrapper(false, type = type);
        return;
    }
    if (online_status != 'no_connection' && this_chid != undefined && this_chid !== 'invalid-safety-id') {
        if (type !== 'regenerate' && type !== "swipe" && !isImpersonate) {
            is_send_press = true;
            var textareaText = $("#send_textarea").val();
            //console.log('Not a Regenerate call, so posting normall with input of: ' +textareaText);
            $("#send_textarea").val('').trigger('input');
        } else {
            //console.log('Regenerate call detected')
            var textareaText = "";
            if (chat[chat.length - 1]['is_user']) {//If last message from You
            }
            else if (type !== "swipe" && !isImpersonate) {
                chat.length = chat.length - 1;
                count_view_mes -= 1;
                openai_msgs.pop();
                $('#chat').children().last().hide(500, function () {
                    $(this).remove();
                });
            }
        }
        deactivateSendButtons();
        let promptBias = null;
        let messageBias = extractMessageBias(textareaText);
        // gets bias of the latest message where it was applied
        for (let mes of chat.slice().reverse()) {
            if (mes && mes.is_user && mes.extra && mes.extra.bias) {
                if (mes.extra.bias.trim().length > 0) {
                    promptBias = mes.extra.bias;
                }
                break;
            }
        }
        // bias from the latest message is top priority//
        promptBias = messageBias ?? promptBias ?? '';
        var storyString = "";
        var userSendString = "";
        var finalPromt = "";
        var postAnchorChar = "Elaborate speaker";
        var postAnchorStyle = "Writing style: very long messages";//"[Genre: roleplay chat][Tone: very long messages with descriptions]";
        var anchorTop = '';
        var anchorBottom = '';
        var topAnchorDepth = 8;
        if (character_anchor && !is_pygmalion) {
            console.log('saw not pyg');
            if (anchor_order === 0) {
                anchorTop = name2 + " " + postAnchorChar;
            } else {
                console.log('saw pyg, adding anchors')
                anchorBottom = "[" + name2 + " " + postAnchorChar + "]";
            }
        }
        if (style_anchor && !is_pygmalion) {
            if (anchor_order === 1) {
                anchorTop = postAnchorStyle;
            } else {
                anchorBottom = "[" + postAnchorStyle + "]";
            }
        }
        //*********************************
        //PRE FORMATING STRING
        //*********************************
        if (textareaText != "" && !automatic_trigger) {
            chat[chat.length] = {};
            chat[chat.length - 1]['name'] = name1;
            chat[chat.length - 1]['is_user'] = true;
            chat[chat.length - 1]['is_name'] = true;
            chat[chat.length - 1]['send_date'] = humanizedDateTime();
            chat[chat.length - 1]['mes'] = textareaText;
            chat[chat.length - 1]['extra'] = {};
            if (messageBias) {
                console.log('checking bias');
                chat[chat.length - 1]['extra']['bias'] = messageBias;
            }
            //console.log('Generate calls addOneMessage');
            addOneMessage(chat[chat.length - 1]);
        }
        ////////////////////////////////////
        let chatString = '';
        let arrMes = [];
        let mesSend = [];
        let charDescription = baseChatReplace($.trim(characters[this_chid].description), name1, name2);
        let charPersonality = baseChatReplace($.trim(characters[this_chid].personality), name1, name2);
        let Scenario = baseChatReplace($.trim(characters[this_chid].scenario), name1, name2);
        let mesExamples = baseChatReplace($.trim(characters[this_chid].mes_example), name1, name2);
        if (!mesExamples.startsWith('')) {
            mesExamples = '\n' + mesExamples.trim();
        }
        if (mesExamples.replace(//gi, '').trim().length === 0) {
            mesExamples = '';
        }
        let mesExamplesArray = mesExamples.split(//gi).slice(1).map(block => `\n${block.trim()}\n`);
        if (main_api === 'openai') {
            const oai_chat = [...chat].filter(x => !x.is_system);
            if (type == 'swipe') {
                oai_chat.pop();
            }
            setOpenAIMessages(oai_chat);
            setOpenAIMessageExamples(mesExamplesArray);
        }
        if (is_pygmalion) {
            storyString += appendToStoryString(charDescription, power_user.disable_description_formatting ? '' : name2 + "'s Persona: ");
            storyString += appendToStoryString(charPersonality, power_user.disable_personality_formatting ? '' : 'Personality: ');
            storyString += appendToStoryString(Scenario, power_user.disable_scenario_formatting ? '' : 'Scenario: ');
        } else {
            if (charDescription !== undefined) {
                if (charPersonality.length > 0 && !power_user.disable_personality_formatting) {
                    charPersonality = name2 + "'s personality: " + charPersonality;
                }
            }
            storyString += appendToStoryString(charDescription, '');
            if (storyString.endsWith('\n')) {
                storyString = storyString.slice(0, -1);
            }
            if (count_view_mes < topAnchorDepth) {
                storyString += '\n' + appendToStoryString(charPersonality, '');
            }
        }
        if (power_user.custom_chat_separator && power_user.custom_chat_separator.length) {
            for (let i = 0; i < mesExamplesArray.length; i++) {
                mesExamplesArray[i] = mesExamplesArray[i].replace(//gi, power_user.custom_chat_separator);
            }
        }
        if (power_user.pin_examples && main_api !== 'openai') {
            for (let example of mesExamplesArray) {
                if (!is_pygmalion) {
                    if (!storyString.endsWith('\n')) {
                        storyString += '\n';
                    }
                    const replaceString = power_user.disable_examples_formatting ? `This is how ${name2} should talk` : '';
                    example = example.replace(//i, replaceString);
                }
                storyString += appendToStoryString(example, '');
            }
        }
        // Pygmalion does that anyway
        if (power_user.always_force_name2 && !is_pygmalion) {
            force_name2 = true;
        }
        if (isImpersonate) {
            force_name2 = false;
        }
        //////////////////////////////////
        var count_exm_add = 0;
        console.log('emptying chat2');
        var chat2 = [];
        var j = 0;
        console.log('pre-replace chat.length = ' + chat.length);
        for (var i = chat.length - 1; i >= 0; i--) {
            let charName = selected_group ? chat[j].name : name2;
            if (j == 0) {
                chat[j]['mes'] = chat[j]['mes'].replace(/{{user}}/gi, name1);
                chat[j]['mes'] = chat[j]['mes'].replace(/{{char}}/gi, charName);
                chat[j]['mes'] = chat[j]['mes'].replace(//gi, name1);
                chat[j]['mes'] = chat[j]['mes'].replace(//gi, charName);
            }
            let this_mes_ch_name = '';
            if (chat[j]['is_user']) {
                this_mes_ch_name = name1;
            } else {
                this_mes_ch_name = charName;
            }
            if (chat[j]['is_name']) {
                chat2[i] = this_mes_ch_name + ': ' + chat[j]['mes'] + '\n';
            } else {
                chat2[i] = chat[j]['mes'] + '\n';
            }
            // system messages produce no text
            if (chat[j]['is_system']) {
                chat2[i] = '';
            }
            // replace bias markup
            //chat2[i] = (chat2[i] ?? '').replace(/{.*}/g, '');
            chat2[i] = (chat2[i] ?? '').replace(/{{(\*?.+?\*?)}}/g, '');
            //console.log('replacing chat2 {}s');
            j++;
        }
        console.log('post replace chat.length = ' + chat.length);
        //chat2 = chat2.reverse();
        var this_max_context = 1487;
        if (main_api == 'kobold' || main_api == 'textgenerationwebui') {
            this_max_context = (max_context - amount_gen);
        }
        if (main_api == 'novel') {
            if (novel_tier === 1) {
                this_max_context = 1024;
            } else {
                this_max_context = 2048 - 60;//fix for fat tokens 
                if (nai_settings.model_novel == 'krake-v2') {
                    this_max_context -= 160;
                }
            }
        }
        if (main_api == 'openai') {
            this_max_context = oai_settings.openai_max_context;
        }
        if (main_api == 'poe') {
            this_max_context = Number(max_context);
        }
        let hordeAmountGen = null;
        if (main_api == 'kobold' && horde_settings.use_horde && horde_settings.auto_adjust) {
            let adjustedParams;
            try {
                adjustedParams = await adjustHordeGenerationParams(this_max_context, amount_gen);
            }
            catch {
                activateSendButtons();
                return;
            }
            this_max_context = adjustedParams.maxContextLength;
            hordeAmountGen = adjustedParams.maxLength;
        }
        let { worldInfoString, worldInfoBefore, worldInfoAfter } = getWorldInfoPrompt(chat2);
        // Extension added strings
        const allAnchors = getAllExtensionPrompts();
        const afterScenarioAnchor = getExtensionPrompt(extension_prompt_types.AFTER_SCENARIO);
        const zeroDepthAnchor = getExtensionPrompt(extension_prompt_types.IN_CHAT, 0, ' ');
        /////////////////////// swipecode
        if (type == 'swipe') {
            console.log('pre swipe shift: ' + chat2.length);
            console.log('shifting swipe chat2');
            chat2.shift();
        }
        console.log('post swipe shift:' + chat2.length);
        var i = 0;
        // hack for regeneration of the first message
        if (chat2.length == 0) {
            chat2.push('');
        }
        for (var item of chat2) {
            chatString = item + chatString;
            const encodeString = JSON.stringify(
                worldInfoString + storyString + chatString +
                anchorTop + anchorBottom +
                charPersonality + promptBias + allAnchors
            );
            const tokenCount = getTokenCount(encodeString, padding_tokens);
            if (tokenCount < this_max_context) { //(The number of tokens in the entire promt) need fix, it must count correctly (added +120, so that the description of the character does not hide)
                //if (is_pygmalion && i == chat2.length-1) item='\n'+item;
                arrMes[arrMes.length] = item;
            } else {
                console.log('reducing chat.length by 1');
                i = chat2.length - 1;
            }
            await delay(1); //For disable slow down (encode gpt-2 need fix)
            // console.log(i+' '+chat.length);
            count_exm_add = 0;
            if (i === chat2.length - 1) {
                if (!power_user.pin_examples) {
                    let mesExmString = '';
                    for (let iii = 0; iii < mesExamplesArray.length; iii++) {
                        mesExmString += mesExamplesArray[iii];
                        const prompt = JSON.stringify(worldInfoString + storyString + mesExmString + chatString + anchorTop + anchorBottom + charPersonality + promptBias + allAnchors);
                        const tokenCount = getTokenCount(prompt, padding_tokens);
                        if (tokenCount < this_max_context) {
                            if (power_user.disable_examples_formatting) {
                                mesExamplesArray[iii] = mesExamplesArray[iii].replace(//i, '');
                            }
                            if (!is_pygmalion) {
                                mesExamplesArray[iii] = mesExamplesArray[iii].replace(//i, `This is how ${name2} should talk`);
                            }
                            count_exm_add++;
                            await delay(1);
                        } else {
                            iii = mesExamplesArray.length;
                        }
                    }
                }
                if (!is_pygmalion && Scenario && Scenario.length > 0) {
                    if (!storyString.endsWith('\n')) {
                        storyString += '\n';
                    }
                    storyString += !power_user.disable_scenario_formatting ? `Circumstances and context of the dialogue: ${Scenario}\n` : `${Scenario}\n`;
                }
                console.log('calling runGenerate');
                await runGenerate();
                return;
            }
            i++;
        }
        async function runGenerate(cycleGenerationPromt = '') {
            is_send_press = true;
            generatedPromtCache += cycleGenerationPromt;
            if (generatedPromtCache.length == 0) {
                if (main_api === 'openai') {
                    generateOpenAIPromptCache(charPersonality, topAnchorDepth, anchorTop, anchorBottom);
                }
                console.log('generating prompt');
                chatString = "";
                arrMes = arrMes.reverse();
                var is_add_personality = false;
                arrMes.forEach(function (item, i, arr) {//For added anchors and others
                    if (i >= arrMes.length - 1 && $.trim(item).substr(0, (name1 + ":").length) != name1 + ":") {
                        if (textareaText == "") {
                            item = item.substr(0, item.length - 1);
                        }
                    }
                    if (i === arrMes.length - topAnchorDepth && count_view_mes >= topAnchorDepth && !is_add_personality) {
                        is_add_personality = true;
                        //chatString = chatString.substr(0,chatString.length-1);
                        //anchorAndPersonality = "[Genre: roleplay chat][Tone: very long messages with descriptions]";
                        if ((anchorTop != "" || charPersonality != "") && !is_pygmalion) {
                            if (anchorTop != "") charPersonality += ' ';
                            item += "[" + charPersonality + anchorTop + ']\n';
                        }
                    }
                    if (i >= arrMes.length - 1 && count_view_mes > 8 && $.trim(item).substr(0, (name1 + ":").length) == name1 + ":" && !is_pygmalion) {//For add anchor in end
                        item = item.substr(0, item.length - 1);
                        //chatString+=postAnchor+"\n";//"[Writing style: very long messages]\n";
                        item = item + anchorBottom + "\n";
                    }
                    if (is_pygmalion) {
                        if (i >= arrMes.length - 1 && $.trim(item).substr(0, (name1 + ":").length) == name1 + ":") {//for add name2 when user sent
                            item = item + name2 + ":";
                        }
                        if (i >= arrMes.length - 1 && $.trim(item).substr(0, (name1 + ":").length) != name1 + ":") {//for add name2 when continue
                            if (textareaText == "") {
                                item = item + '\n' + name2 + ":";
                            }
                        }
                        if ($.trim(item).indexOf(name1) === 0) {
                            item = item.replace(name1 + ':', 'You:');
                        }
                    }
                    if (i === 0) {
                        // Process those that couldn't get that far
                        for (let upperDepth = 100; upperDepth >= arrMes.length; upperDepth--) {
                            const upperAnchor = getExtensionPrompt(extension_prompt_types.IN_CHAT, upperDepth);
                            if (upperAnchor && upperAnchor.length) {
                                item = upperAnchor + item;
                            }
                        }
                    }
                    const anchorDepth = Math.abs(i - arrMes.length + 1);
                    const extensionAnchor = getExtensionPrompt(extension_prompt_types.IN_CHAT, anchorDepth);
                    if (anchorDepth > 0 && extensionAnchor && extensionAnchor.length) {
                        item += extensionAnchor;
                    }
                    mesSend[mesSend.length] = item;
                });
            }
            let mesSendString = '';
            let mesExmString = '';
            function setPromtString() {
                mesSendString = '';
                mesExmString = '';
                for (let j = 0; j < count_exm_add; j++) {
                    mesExmString += mesExamplesArray[j];
                }
                for (let j = 0; j < mesSend.length; j++) {
                    mesSendString += mesSend[j];
                    if (isImpersonate && j === mesSend.length - 1 && tokens_already_generated === 0) {
                        const name = is_pygmalion ? 'You' : name1;
                        if (!mesSendString.endsWith('\n')) {
                            mesSendString += '\n';
                        }
                        mesSendString += name + ':';
                    }
                    if (force_name2 && j === mesSend.length - 1 && tokens_already_generated === 0) {
                        if (!mesSendString.endsWith('\n')) {
                            mesSendString += '\n';
                        }
                        mesSendString += name2 + ':';
                    }
                }
            }
            function checkPromtSize() {
                setPromtString();
                const prompt = JSON.stringify(worldInfoString + storyString + mesExmString + mesSendString + anchorTop + anchorBottom + charPersonality + generatedPromtCache + promptBias + allAnchors);
                let thisPromtContextSize = getTokenCount(prompt, padding_tokens);
                if (thisPromtContextSize > this_max_context) {		//if the prepared prompt is larger than the max context size...
                    if (count_exm_add > 0) {							// ..and we have example mesages..
                        //console.log('Context size: '+thisPromtContextSize+' -- too big, removing example message');
                        //mesExamplesArray.length = mesExamplesArray.length-1;
                        count_exm_add--;							// remove the example messages...
                        checkPromtSize();							// and try agin...
                    } else if (mesSend.length > 0) {					// if the chat history is longer than 0
                        //console.log('Context size: '+thisPromtContextSize+' -- too big, removing oldest chat message');
                        mesSend.shift();							// remove the first (oldest) chat entry..
                        checkPromtSize();							// and check size again..
                    } else {
                        //end
                    }
                }
            }
            if (generatedPromtCache.length > 0) {
                //console.log('Generated Prompt Cache length: '+generatedPromtCache.length);
                checkPromtSize();
            } else {
                console.log('calling setPromtString')
                setPromtString();
            }
            // add a custom dingus (if defined)
            if (power_user.custom_chat_separator && power_user.custom_chat_separator.length) {
                mesSendString = power_user.custom_chat_separator + '\n' + mesSendString;
            }
            // if chat start formatting is disabled
            else if (power_user.disable_start_formatting) {
                mesSendString = mesSendString;
            }
            // add non-pygma dingus
            else if (!is_pygmalion) {
                mesSendString = '\nThen the roleplay chat between ' + name1 + ' and ' + name2 + ' begins.\n' + mesSendString;
            }
            // add pygma 
            else {
                mesSendString = '\n' + mesSendString;
                //mesSendString = mesSendString; //This edit simply removes the first "" that is prepended to all context prompts
            }
            finalPromt = worldInfoBefore + storyString + worldInfoAfter + afterScenarioAnchor + mesExmString + mesSendString + generatedPromtCache + promptBias;
            if (zeroDepthAnchor && zeroDepthAnchor.length) {
                if (!isMultigenEnabled() || tokens_already_generated == 0) {
                    const trimBothEnds = !force_name2 && !is_pygmalion;
                    let trimmedPrompt = (trimBothEnds ? zeroDepthAnchor.trim() : zeroDepthAnchor.trimEnd());
                    if (trimBothEnds && !finalPromt.endsWith('\n')) {
                        finalPromt += '\n';
                    }
                    finalPromt += trimmedPrompt;
                    if (force_name2 || is_pygmalion) {
                        finalPromt += ' ';
                    }
                }
            }
            finalPromt = finalPromt.replace(/\r/gm, '');
            if (power_user.collapse_newlines) {
                finalPromt = collapseNewlines(finalPromt);
            }
            let this_amount_gen = parseInt(amount_gen); // how many tokens the AI will be requested to generate
            let this_settings = koboldai_settings[koboldai_setting_names[preset_settings]];
            if (isMultigenEnabled()) {
                // if nothing has been generated yet..
                if (tokens_already_generated === 0) {
                    // if the max gen setting is > 50...(
                    if (parseInt(amount_gen) >= power_user.multigen_first_chunk) {
                        // then only try to make 50 this cycle..
                        this_amount_gen = power_user.multigen_first_chunk;
                    }
                    else {
                        // otherwise, make as much as the max amount request.
                        this_amount_gen = parseInt(amount_gen);
                    }
                }
                // if we already received some generated text...
                else {
                    // if the remaining tokens to be made is less than next potential cycle count
                    if (parseInt(amount_gen) - tokens_already_generated < power_user.multigen_next_chunks) {
                        // subtract already generated amount from the desired max gen amount
                        this_amount_gen = parseInt(amount_gen) - tokens_already_generated;
                    }
                    else {
                        // otherwise make the standard cycle amount (first 50, and 30 after that)
                        this_amount_gen = power_user.multigen_next_chunks;
                    }
                }
            }
            if (main_api == 'kobold' && horde_settings.use_horde && hordeAmountGen) {
                this_amount_gen = Math.min(this_amount_gen, hordeAmountGen);
            }
            var generate_data;
            if (main_api == 'kobold') {
                var generate_data = {
                    prompt: finalPromt,
                    gui_settings: true,
                    max_length: amount_gen,
                    temperature: kai_settings.temp,
                    max_context_length: max_context,
                    singleline: kai_settings.single_line,
                };
                if (preset_settings != 'gui' || horde_settings.use_horde) {
                    generate_data = getKoboldGenerationData(finalPromt, this_settings, this_amount_gen, this_max_context, isImpersonate);
                }
            }
            if (main_api == 'textgenerationwebui') {
                let data = [
                    finalPromt,
                    {
                        'max_new_tokens': this_amount_gen,
                        'do_sample': textgenerationwebui_settings.do_sample,
                        'temperature': textgenerationwebui_settings.temp,
                        'top_p': textgenerationwebui_settings.top_p,
                        'typical_p': textgenerationwebui_settings.typical_p,
                        'repetition_penalty': textgenerationwebui_settings.rep_pen,
                        'encoder_repetition_penalty': textgenerationwebui_settings.encoder_rep_pen,
                        'top_k': textgenerationwebui_settings.top_k,
                        'min_length': textgenerationwebui_settings.min_length,
                        'no_repeat_ngram_size': textgenerationwebui_settings.no_repeat_ngram_size,
                        'num_beams': textgenerationwebui_settings.num_beams,
                        'penalty_alpha': textgenerationwebui_settings.penalty_alpha,
                        'length_penalty': textgenerationwebui_settings.length_penalty,
                        'early_stopping': textgenerationwebui_settings.early_stopping,
                        'seed': textgenerationwebui_settings.seed,
                        'add_bos_token': textgenerationwebui_settings.add_bos_token,
                        'custom_stopping_strings': JSON.stringify(getStoppingStrings(isImpersonate, false)),
                        'truncation_length': max_context,
                        'ban_eos_token': textgenerationwebui_settings.ban_eos_token,
                        'skip_special_tokens': textgenerationwebui_settings.skip_special_tokens,
                    }
                ];
                generate_data = { "data": [JSON.stringify(data)] };
            }
            if (main_api == 'novel') {
                const this_settings = novelai_settings[novelai_setting_names[nai_settings.preset_settings_novel]];
                generate_data = {
                    "input": finalPromt,
                    "model": nai_settings.model_novel,
                    "use_string": true,
                    "temperature": parseFloat(nai_settings.temp_novel),
                    "max_length": this_settings.max_length,
                    "min_length": this_settings.min_length,
                    "tail_free_sampling": this_settings.tail_free_sampling,
                    "repetition_penalty": parseFloat(nai_settings.rep_pen_novel),
                    "repetition_penalty_range": parseInt(nai_settings.rep_pen_size_novel),
                    "repetition_penalty_frequency": this_settings.repetition_penalty_frequency,
                    "repetition_penalty_presence": this_settings.repetition_penalty_presence,
                    //"stop_sequences": {{187}},
                    //bad_words_ids = {{50256}, {0}, {1}};
                    //generate_until_sentence = true;
                    "use_cache": false,
                    //use_string = true;
                    "return_full_text": false,
                    "prefix": "vanilla",
                    "order": this_settings.order
                };
            }
            var generate_url = '';
            if (main_api == 'kobold') {
                generate_url = '/generate';
            } else if (main_api == 'textgenerationwebui') {
                generate_url = '/generate_textgenerationwebui';
            } else if (main_api == 'novel') {
                generate_url = '/generate_novelai';
            }
            console.log('rungenerate calling API');
            if (main_api == 'openai') {
                let prompt = await prepareOpenAIMessages(name2, storyString, worldInfoBefore, worldInfoAfter, afterScenarioAnchor, promptBias, type);
                if (isStreamingEnabled()) {
                    streamingProcessor.generator = await sendOpenAIRequest(prompt);
                }
                else {
                    sendOpenAIRequest(prompt).then(onSuccess).catch(onError);
                }
            }
            else if (main_api == 'kobold' && horde_settings.use_horde) {
                generateHorde(finalPromt, generate_data).then(onSuccess).catch(onError);
            }
            else if (main_api == 'poe') {
                if (isStreamingEnabled()) {
                    streamingProcessor.generator = await generatePoe(type, finalPromt);
                }
                else {
                    generatePoe(type, finalPromt).then(onSuccess).catch(onError);
                }
            }
            else if (main_api == 'textgenerationwebui' && textgenerationwebui_settings.streaming) {
                streamingProcessor.generator = await generateTextGenWithStreaming(generate_data);
            }
            else {
                jQuery.ajax({
                    type: 'POST', // 
                    url: generate_url, // 
                    data: JSON.stringify(generate_data),
                    beforeSend: function () {
                        //$('#create_button').attr('value','Creating...'); 
                    },
                    cache: false,
                    dataType: "json",
                    contentType: "application/json",
                    success: onSuccess,
                    error: onError
                }); //end of "if not data error"
            }
            if (isStreamingEnabled()) {
                hideSwipeButtons();
                let getMessage = await streamingProcessor.generate();
                if (isMultigenEnabled()) {
                    tokens_already_generated += this_amount_gen;    // add new gen amt to any prev gen counter..
                    message_already_generated += getMessage;
                    promptBias = '';
                    if (!streamingProcessor.isStopped && shouldContinueMultigen(getMessage)) {
                        streamingProcessor.isFinished = false;
                        runGenerate(getMessage);
                        console.log('returning to make generate again');
                        return;
                    }
                    getMessage = message_already_generated;
                }
                if (streamingProcessor && !streamingProcessor.isStopped && streamingProcessor.isFinished) {
                    streamingProcessor.onFinishStreaming(streamingProcessor.messageId, getMessage);
                    streamingProcessor = null;
                }
            }
            function onSuccess(data) {
                tokens_already_generated += this_amount_gen;			// add new gen amt to any prev gen counter..
                is_send_press = false;
                if (!data.error) {
                    //const getData = await response.json();
                    let getMessage = extractMessageFromData(data, finalPromt);
                    //Pygmalion run again
                    // to make it continue generating so long as it's under max_amount and hasn't signaled
                    // an end to the character's response via typing "You:" or adding ""
                    if (isMultigenEnabled()) {
                        message_already_generated += getMessage;
                        promptBias = '';
                        if (shouldContinueMultigen(getMessage)) {
                            runGenerate(getMessage);
                            console.log('returning to make generate again');
                            return;
                        }
                        getMessage = message_already_generated;
                    }
                    //Formating
                    getMessage = cleanUpMessage(getMessage, isImpersonate);
                    let this_mes_is_name;
                    ({ this_mes_is_name, getMessage } = extractNameFromMessage(getMessage, force_name2, isImpersonate));
                    //getMessage = getMessage.replace(/^\s+/g, '');
                    if (getMessage.length > 0) {
                        if (isImpersonate) {
                            $('#send_textarea').val(getMessage).trigger('input');
                        }
                        else {
                            ({ type, getMessage } = saveReply(type, getMessage, this_mes_is_name));
                        }
                        activateSendButtons();
                        playMessageSound();
                        generate_loop_counter = 0;
                    } else {
                        ++generate_loop_counter;
                        if (generate_loop_counter > MAX_GENERATION_LOOPS) {
                            throwCircuitBreakerError();
                        }
                        // regenerate with character speech reenforced
                        // to make sure we leave on swipe type while also adding the name2 appendage
                        setTimeout(() => {
                            let newType = type == "swipe" ? "swipe" : "force_name2";
                            newType = isImpersonate ? type : newType;
                            Generate(newType, automatic_trigger = false, force_name2 = true);
                        }, generate_loop_counter * 1000);
                    }
                } else {
                    activateSendButtons();
                    //console.log('runGenerate calling showSwipeBtns');
                    showSwipeButtons();
                }
                console.log('/savechat called by /Generate');
                saveChatConditional();
                activateSendButtons();
                showSwipeButtons();
                setGenerationProgress(0);
                $('.mes_edit:last').show();
            };
            function onError(jqXHR, exception) {
                $("#send_textarea").removeAttr('disabled');
                is_send_press = false;
                activateSendButtons();
                setGenerationProgress(0);
                console.log(exception);
                console.log(jqXHR);
            };
        } //rungenerate ends
    } else {    //generate's primary loop ends, after this is error handling for no-connection or safety-id
        if (this_chid == undefined || this_chid == 'invalid-safety-id') {
            //send ch sel
            popup_type = 'char_not_selected';
            callPopup('Сharacter is not selected
');
        }
        is_send_press = false;
    }
    console.log('generate ending');
} //generate ends
function shouldContinueMultigen(getMessage) {
    const nameString = is_pygmalion ? 'You:' : `${name1}:`;
    return message_already_generated.indexOf(nameString) === -1 && //if there is no 'You:' in the response msg
        message_already_generated.indexOf('<|endoftext|>') === -1 && //if there is no  stamp in the response msg
        tokens_already_generated < parseInt(amount_gen) && //if the gen'd msg is less than the max response length..
        getMessage.length > 0;     //if we actually have gen'd text at all...
}
function extractNameFromMessage(getMessage, force_name2, isImpersonate) {
    const nameToTrim = isImpersonate ? name1 : name2;
    let this_mes_is_name = true;
    if (getMessage.indexOf(nameToTrim + ":") === 0) {
        getMessage = getMessage.replace(nameToTrim + ':', '');
        getMessage = getMessage.trimStart();
    } else {
        this_mes_is_name = false;
    }
    if (force_name2)
        this_mes_is_name = true;
    if (isImpersonate) {
        getMessage = getMessage.trim();
    }
    return { this_mes_is_name, getMessage };
}
function throwCircuitBreakerError() {
    callPopup(`Could not extract reply in ${MAX_GENERATION_LOOPS} attempts. Try generating again`, 'text');
    generate_loop_counter = 0;
    $("#send_textarea").removeAttr('disabled');
    is_send_press = false;
    activateSendButtons();
    setGenerationProgress(0);
    showSwipeButtons();
    $('.mes_edit:last').show();
    throw new Error('Generate circuit breaker interruption');
}
function extractMessageFromData(data, finalPromt) {
    let getMessage = "";
    if (main_api == 'kobold' && !horde_settings.use_horde) {
        getMessage = data.results[0].text;
    }
    if (main_api == 'kobold' && horde_settings.use_horde) {
        getMessage = data;
    }
    if (main_api == 'textgenerationwebui') {
        getMessage = data.data[0];
        if (getMessage == null || data.error) {
            activateSendButtons();
            callPopup('Got empty response from Text generation web UI. Try restarting the API with recommended options.
', 'text');
            return;
        }
        getMessage = getMessage.substring(finalPromt.length);
    }
    if (main_api == 'novel') {
        getMessage = data.output;
    }
    if (main_api == 'openai' || main_api == 'poe') {
        getMessage = data;
    }
    return getMessage;
}
function cleanUpMessage(getMessage, isImpersonate) {
    const nameToTrim = isImpersonate ? name2 : name1;
    if (power_user.collapse_newlines) {
        getMessage = collapseNewlines(getMessage);
    }
    getMessage = $.trim(getMessage);
    if (is_pygmalion) {
        getMessage = getMessage.replace(//g, name1);
        getMessage = getMessage.replace(//g, name2);
        getMessage = getMessage.replace(/You:/g, name1 + ':');
    }
    if (getMessage.indexOf(nameToTrim + ":") != -1) {
        getMessage = getMessage.substr(0, getMessage.indexOf(nameToTrim + ":"));
    }
    if (getMessage.indexOf('<|endoftext|>') != -1) {
        getMessage = getMessage.substr(0, getMessage.indexOf('<|endoftext|>'));
    }
    // clean-up group message from excessive generations
    if (selected_group) {
        getMessage = cleanGroupMessage(getMessage);
    }
    if (isImpersonate) {
        getMessage = getMessage.trim();
    }
    return getMessage;
}
function saveReply(type, getMessage, this_mes_is_name) {
    if (chat.length && (chat[chat.length - 1]['swipe_id'] === undefined ||
        chat[chat.length - 1]['is_user'])) {
        type = 'normal';
    }
    const img = extractImageFromMessage(getMessage);
    getMessage = img.getMessage;
    if (type === 'swipe') {
        chat[chat.length - 1]['swipes'][chat[chat.length - 1]['swipes'].length] = getMessage;
        if (chat[chat.length - 1]['swipe_id'] === chat[chat.length - 1]['swipes'].length - 1) {
            //console.log(getMessage);
            chat[chat.length - 1]['mes'] = getMessage;
            // console.log('runGenerate calls addOneMessage for swipe');
            addOneMessage(chat[chat.length - 1], 'swipe');
        } else {
            chat[chat.length - 1]['mes'] = getMessage;
        }
    } else {
        console.log('entering chat update routine for non-swipe post');
        chat[chat.length] = {};
        chat[chat.length - 1]['name'] = name2;
        chat[chat.length - 1]['is_user'] = false;
        chat[chat.length - 1]['is_name'] = this_mes_is_name;
        chat[chat.length - 1]['send_date'] = humanizedDateTime();
        getMessage = $.trim(getMessage);
        chat[chat.length - 1]['mes'] = getMessage;
        if (selected_group) {
            console.log('entering chat update for groups');
            let avatarImg = 'img/ai4.png';
            if (characters[this_chid].avatar != 'none') {
                avatarImg = getThumbnailUrl('avatar', characters[this_chid].avatar);
            }
            chat[chat.length - 1]['is_name'] = true;
            chat[chat.length - 1]['force_avatar'] = avatarImg;
        }
        saveImageToMessage(img, chat[chat.length - 1]);
        addOneMessage(chat[chat.length - 1]);
    }
    return { type, getMessage };
}
function saveImageToMessage(img, mes) {
    if (mes && img.image) {
        if (typeof mes.extra !== 'object') {
            mes.extra = {};
        }
        mes.extra.image = img.image;
        mes.title = img.title;
    }
}
function extractImageFromMessage(getMessage) {
    const regex = /
/g;
    const results = regex.exec(getMessage);
    const image = results ? results[1] : '';
    const title = results ? results[2] : '';
    getMessage = getMessage.replace(regex, '');
    return { getMessage, image, title };
}
function isMultigenEnabled() {
    return power_user.multigen && (main_api == 'textgenerationwebui' || main_api == 'kobold' || main_api == 'novel');
}
function activateSendButtons() {
    is_send_press = false;
    $("#send_but").css("display", "flex");
    $("#loading_mes").css("display", "none");
}
function deactivateSendButtons() {
    $("#send_but").css("display", "none");
    $("#loading_mes").css("display", "flex");
}
function resetChatState() {
    active_character = "invalid-safety-id"; //unsets the chid in settings (this prevents AutoLoadChat from trying to load the wrong ChID
    this_chid = "invalid-safety-id"; //unsets expected chid before reloading (related to getCharacters/printCharacters from using old arrays)
    name2 = systemUserName; // replaces deleted charcter name with system user since it will be displayed next.
    chat = [...safetychat]; // sets up system user to tell user about having deleted a character
    chat_metadata = {}; // resets chat metadata
    characters.length = 0; // resets the characters array, forcing getcharacters to reset
}
function setCharacterId(value) {
    this_chid = value;
}
function setCharacterName(value) {
    name2 = value;
}
function setOnlineStatus(value) {
    online_status = value;
}
function setEditedMessageId(value) {
    this_edit_mes_id = value;
}
function setSendButtonState(value) {
    is_send_press = value;
}
function resultCheckStatusNovel() {
    is_api_button_press_novel = false;
    checkOnlineStatus();
    $("#api_loading_novel").css("display", "none");
    $("#api_button_novel").css("display", "inline-block");
}
async function saveChat(chat_name, withMetadata) {
    const metadata = { ...chat_metadata, ...(withMetadata || {}) };
    let file_name = chat_name ?? characters[this_chid].chat;
    chat.forEach(function (item, i) {
        if (item["is_group"]) {
            alert('Trying to save group chat with regular saveChat function. Aborting to prevent corruption.');
            throw new Error('Group chat saved from saveChat');
        }
        if (item.is_user) {
            var str = item.mes.replace(`${name1}:`, `${default_user_name}:`);
            chat[i].mes = str;
            chat[i].name = default_user_name;
        } else if (i !== chat.length - 1 && chat[i].swipe_id !== undefined) {
            //  delete chat[i].swipes;
            //  delete chat[i].swipe_id;
        }
    });
    var save_chat = [
        {
            user_name: default_user_name,
            character_name: name2,
            create_date: chat_create_date,
            chat_metadata: metadata,
        },
        ...chat,
    ];
    jQuery.ajax({
        type: "POST",
        url: "/savechat",
        data: JSON.stringify({
            ch_name: characters[this_chid].name,
            file_name: file_name,
            chat: save_chat,
            avatar_url: characters[this_chid].avatar,
        }),
        beforeSend: function () {
            //$('#create_button').attr('value','Creating...');
        },
        cache: false,
        dataType: "json",
        contentType: "application/json",
        success: function (data) { },
        error: function (jqXHR, exception) {
            console.log(exception);
            console.log(jqXHR);
        },
    });
}
function read_avatar_load(input) {
    if (input.files && input.files[0]) {
        const reader = new FileReader();
        if (selected_button == "create") {
            create_save_avatar = input.files;
        }
        reader.onload = function (e) {
            if (selected_button == "character_edit") {
                saveCharacterDebounced();
            }
            $("#avatar_load_preview").attr("src", e.target.result);
            //.width(103)
            //.height(83);
            //console.log(e.target.result.name);
        };
        reader.readAsDataURL(input.files[0]);
        if (this_chid) {
            fetch(getThumbnailUrl('avatar', characters[this_chid].avatar), {
                method: 'GET',
                headers: {
                    'pragma': 'no-cache',
                    'cache-control': 'no-cache',
                }
            }).then(() => console.log('Avatar refreshed'));
        }
    }
}
function getThumbnailUrl(type, file) {
    return `/thumbnail?type=${type}&file=${encodeURIComponent(file)}`;
}
async function getChat() {
    //console.log('/getchat -- entered for -- ' + characters[this_chid].name);
    try {
        const response = await $.ajax({
            type: 'POST',
            url: '/getchat',
            data: JSON.stringify({
                ch_name: characters[this_chid].name,
                file_name: characters[this_chid].chat,
                avatar_url: characters[this_chid].avatar
            }),
            dataType: 'json',
            contentType: 'application/json',
        });
        if (response[0] !== undefined) {
            chat.push(...response);
            chat_create_date = chat[0]['create_date'];
            chat_metadata = chat[0]['chat_metadata'] ?? {};
            chat.shift();
        } else {
            chat_create_date = humanizedDateTime();
        }
        getChatResult();
        saveChat();
    } catch (error) {
        getChatResult();
        console.log(error);
    }
}
function getChatResult() {
    name2 = characters[this_chid].name;
    if (chat.length > 1) {
        for (let i = 0; i < chat.length; i++) {
            const item = chat[i];
            if (item["is_user"]) {
                item['mes'] = item['mes'].replace(default_user_name + ':', name1 + ':');
                item['name'] = name1;
            }
        }
    } else {
        const firstMes = characters[this_chid].first_mes || default_ch_mes;
        chat[0] = {
            name: name2,
            is_user: false,
            is_name: true,
            send_date: humanizedDateTime(),
            mes: firstMes
        };
    }
    printMessages();
    select_selected_character(this_chid);
}
async function openCharacterChat(file_name) {
    characters[this_chid]["chat"] = file_name;
    clearChat();
    chat.length = 0;
    chat_metadata = {};
    await getChat();
    $("#selected_chat_pole").val(file_name);
    $("#create_button").click();
    $("#shadow_select_chat_popup").css("display", "none");
    $("#load_select_chat_div").css("display", "block");
}
/* function openNavToggle() {
    if (!$("#nav-toggle").prop("checked")) {
        $("#nav-toggle").trigger("click");
    }
} */
////////// OPTIMZED MAIN API CHANGE FUNCTION ////////////
function changeMainAPI() {
    const selectedVal = $("#main_api").val();
    //console.log(selectedVal);
    const apiElements = {
        "kobold": {
            apiSettings: $("#kobold_api-settings"),
            apiConnector: $("#kobold_api"),
            apiPresets: $('#kobold_api-presets'),
            apiRanges: $("#range_block"),
            maxContextElem: $("#max_context_block"),
            amountGenElem: $("#amount_gen_block"),
            softPromptElem: $("#softprompt_block")
        },
        "textgenerationwebui": {
            apiSettings: $("#textgenerationwebui_api-settings"),
            apiConnector: $("#textgenerationwebui_api"),
            apiPresets: $('#textgenerationwebui_api-presets'),
            apiRanges: $("#range_block_textgenerationwebui"),
            maxContextElem: $("#max_context_block"),
            amountGenElem: $("#amount_gen_block"),
            softPromptElem: $("#softprompt_block")
        },
        "novel": {
            apiSettings: $("#novel_api-settings"),
            apiConnector: $("#novel_api"),
            apiPresets: $('#novel_api-presets'),
            apiRanges: $("#range_block_novel"),
            maxContextElem: $("#max_context_block"),
            amountGenElem: $("#amount_gen_block"),
            softPromptElem: $("#softprompt_block")
        },
        "openai": {
            apiSettings: $("#openai_settings"),
            apiConnector: $("#openai_api"),
            apiPresets: $('#openai_api-presets'),
            apiRanges: $("#range_block_openai"),
            maxContextElem: $("#max_context_block"),
            amountGenElem: $("#amount_gen_block"),
            softPromptElem: $("#softprompt_block"),
        },
        "poe": {
            apiSettings: $("#poe_settings"),
            apiConnector: $("#poe_api"),
            apiPresets: $("#poe_api-presets"),
            apiRanges: $("#range_block_poe"),
            maxContextElem: $("#max_context_block"),
            amountGenElem: $("#amount_gen_block"),
            softPromptElem: $("#softprompt_block"),
        }
    };
    //console.log('--- apiElements--- ');
    //console.log(apiElements);
    for (const apiName in apiElements) {
        const apiObj = apiElements[apiName];
        const isCurrentApi = selectedVal === apiName;
        apiObj.apiSettings.css("display", isCurrentApi ? "block" : "none");
        apiObj.apiConnector.css("display", isCurrentApi ? "block" : "none");
        apiObj.apiRanges.css("display", isCurrentApi ? "block" : "none");
        apiObj.apiPresets.css("display", isCurrentApi ? "block" : "none");
        if (isCurrentApi && apiName === "openai") {
            apiObj.apiPresets.css("display", "flex");
        }
        if (isCurrentApi && apiName === "kobold") {
            //console.log("enabling SP for kobold");
            $("#softprompt_block").css("display", "block");
        }
        if (isCurrentApi && (apiName === "textgenerationwebui" || apiName === "novel")) {
            console.log("enabling amount_gen for ooba/novel");
            apiObj.amountGenElem.find('input').prop("disabled", false);
            apiObj.amountGenElem.css("opacity", 1.0);
        }
        // Hide common settings for OpenAI
        if (selectedVal == "openai") {
            $("#common-gen-settings-block").css("display", "none");
        } else {
            $("#common-gen-settings-block").css("display", "block");
        }
        // Hide amount gen for poe
        if (selectedVal == "poe") {
            $("#amount_gen_block").css("display", "none");
        } else {
            $("#amount_gen_block").css("display", "flex");
        }
    }
    main_api = selectedVal;
    online_status = "no_connection";
    if (main_api == "kobold" && horde_settings.use_horde) {
        is_get_status = true;
        getStatus();
        getHordeModels();
    }
}
////////////////////////////////////////////////////
async function getUserAvatars() {
    $("#user_avatar_block").html(""); //RossAscends: necessary to avoid doubling avatars each refresh.
    $("#user_avatar_block").append('+');
    const response = await fetch("/getuseravatars", {
        method: "POST",
        headers: {
            "Content-Type": "application/json",
            "X-CSRF-Token": token,
        },
        body: JSON.stringify({
            "": "",
        }),
    });
    if (response.ok === true) {
        const getData = await response.json();
        //background = getData;
        //console.log(getData.length);
        for (var i = 0; i < getData.length; i++) {
            //console.log(1);
            appendUserAvatar(getData[i]);
        }
        //var aa = JSON.parse(getData[0]);
        //const load_ch_coint = Object.getOwnPropertyNames(getData);
    }
}
function highlightSelectedAvatar() {
    $("#user_avatar_block").find(".avatar").removeClass("selected");
    $("#user_avatar_block")
        .find(`.avatar[imgfile='${user_avatar}']`)
        .addClass("selected");
}
function appendUserAvatar(name) {
    const block = $("#user_avatar_block").append(
        '
'
    );
    highlightSelectedAvatar();
}
//***************SETTINGS****************//
///////////////////////////////////////////
async function getSettings(type) {
    //timer
    //console.log('getSettings() pinging server for settings request');
    jQuery.ajax({
        type: "POST",
        url: "/getsettings",
        data: JSON.stringify({}),
        beforeSend: function () { },
        cache: false,
        dataType: "json",
        contentType: "application/json",
        //processData: false,
        success: function (data) {
            if (data.result != "file not find" && data.settings) {
                settings = JSON.parse(data.settings);
                if (settings.username !== undefined) {
                    if (settings.username !== "") {
                        name1 = settings.username;
                        $("#your_name").val(name1);
                    }
                }
                //Load which API we are using
                if (settings.main_api != undefined) {
                    main_api = settings.main_api;
                    $("#main_api option[value=" + main_api + "]").attr(
                        "selected",
                        "true"
                    );
                    changeMainAPI();
                }
                //Load KoboldAI settings
                koboldai_setting_names = data.koboldai_setting_names;
                koboldai_settings = data.koboldai_settings;
                koboldai_settings.forEach(function (item, i, arr) {
                    koboldai_settings[i] = JSON.parse(item);
                });
                let arr_holder = {};
                $("#settings_perset").empty(); //RossAscends: uncommented this to prevent settings selector from doubling preset list on refresh
                $("#settings_perset").append(
                    ''
                ); //adding in the GUI settings, since it is not loaded dynamically
                koboldai_setting_names.forEach(function (item, i, arr) {
                    arr_holder[item] = i;
                    $("#settings_perset").append(``);
                    //console.log('loading preset #'+i+' -- '+item);
                });
                koboldai_setting_names = {};
                koboldai_setting_names = arr_holder;
                preset_settings = settings.preset_settings;
                if (preset_settings == "gui") {
                    selectKoboldGuiPreset();
                } else {
                    if (typeof koboldai_setting_names[preset_settings] !== "undefined") {
                        $(`#settings_perset option[value=${koboldai_setting_names[preset_settings]}]`)
                            .attr("selected", "true");
                    } else {
                        preset_settings = "gui";
                        selectKoboldGuiPreset();
                    }
                }
                novelai_setting_names = data.novelai_setting_names;
                novelai_settings = data.novelai_settings;
                novelai_settings.forEach(function (item, i, arr) {
                    novelai_settings[i] = JSON.parse(item);
                });
                arr_holder = {};
                $("#settings_perset_novel").empty();
                novelai_setting_names.forEach(function (item, i, arr) {
                    arr_holder[item] = i;
                    $("#settings_perset_novel").append(``);
                });
                novelai_setting_names = {};
                novelai_setting_names = arr_holder;
                nai_settings.preset_settings_novel = settings.preset_settings_novel;
                $(
                    `#settings_perset_novel option[value=${novelai_setting_names[nai_settings.preset_settings_novel]}]`
                ).attr("selected", "true");
                //Load AI model config settings (temp, context length, anchors, and anchor order)
                amount_gen = settings.amount_gen;
                if (settings.max_context !== undefined)
                    max_context = parseInt(settings.max_context);
                if (settings.anchor_order !== undefined)
                    anchor_order = parseInt(settings.anchor_order);
                if (settings.style_anchor !== undefined)
                    style_anchor = !!settings.style_anchor;
                if (settings.character_anchor !== undefined)
                    character_anchor = !!settings.character_anchor;
                $("#style_anchor").prop("checked", style_anchor);
                $("#character_anchor").prop("checked", character_anchor);
                $("#anchor_order option[value=" + anchor_order + "]").attr(
                    "selected",
                    "true"
                );
                $("#max_context").val(max_context);
                $("#max_context_counter").text(`${max_context}`);
                $("#amount_gen").val(amount_gen);
                $("#amount_gen_counter").text(`${amount_gen}`);
                swipes = !!settings.swipes;  //// swipecode
                $('#swipes-checkbox').prop('checked', swipes); /// swipecode
                //console.log('getSettings -- swipes = ' + swipes + '. toggling box');
                hideSwipeButtons();
                //console.log('getsettings calling showswipebtns');
                showSwipeButtons();
                // Kobold
                loadKoboldSettings(settings);
                // Novel
                loadNovelSettings(settings);
                // TextGen
                loadTextGenSettings(data, settings);
                // OpenAI
                loadOpenAISettings(data, settings);
                // Horde
                loadHordeSettings(settings);
                // Poe
                loadPoeSettings(settings);
                // Load power user settings
                loadPowerUserSettings(settings, data);
                //Enable GUI deference settings if GUI is selected for Kobold
                if (main_api === "kobold") {
                }
                //Load User's Name and Avatar
                user_avatar = settings.user_avatar;
                $(".mes").each(function () {
                    if ($(this).attr("ch_name") == name1) {
                        $(this)
                            .children(".avatar")
                            .children("img")
                            .attr("src", "User Avatars/" + user_avatar);
                    }
                });
                highlightSelectedAvatar();
                //Load the API server URL from settings
                api_server = settings.api_server;
                $("#api_url_text").val(api_server);
                setWorldInfoSettings(settings, data);
                if (data.enable_extensions) {
                    const src = "scripts/extensions.js";
                    if ($(`script[src="${src}"]`).length === 0) {
                        const script = document.createElement("script");
                        script.type = "module";
                        script.src = src;
                        $("body").append(script);
                    }
                    loadExtensionSettings(settings);
                }
                //get the character to auto-load
                if (settings.active_character !== undefined) {
                    if (settings.active_character !== "") {
                        active_character = settings.active_character;
                    }
                }
                api_server_textgenerationwebui =
                    settings.api_server_textgenerationwebui;
                $("#textgenerationwebui_api_url_text").val(
                    api_server_textgenerationwebui
                );
                selected_button = settings.selected_button;
            }
            if (!is_checked_colab) isColab();
        },
        error: function (jqXHR, exception) {
            console.log(exception);
            console.log(jqXHR);
        },
    });
}
function selectKoboldGuiPreset() {
    $("#settings_perset option[value=gui]")
        .attr("selected", "true")
        .trigger("change");
}
async function saveSettings(type) {
    //console.log('Entering settings with name1 = '+name1);
    jQuery.ajax({
        type: "POST",
        url: "/savesettings",
        data: JSON.stringify({
            username: name1,
            api_server: api_server,
            api_server_textgenerationwebui: api_server_textgenerationwebui,
            preset_settings: preset_settings,
            user_avatar: user_avatar,
            amount_gen: amount_gen,
            max_context: max_context,
            anchor_order: anchor_order,
            style_anchor: style_anchor,
            character_anchor: character_anchor,
            main_api: main_api,
            world_info: world_info,
            world_info_depth: world_info_depth,
            world_info_budget: world_info_budget,
            active_character: active_character,
            textgenerationwebui_settings: textgenerationwebui_settings,
            swipes: swipes,
            horde_settings: horde_settings,
            power_user: power_user,
            poe_settings: poe_settings,
            extension_settings: extension_settings,
            ...nai_settings,
            ...kai_settings,
            ...oai_settings,
        }),
        beforeSend: function () {
            //console.log('saveSettings() -- active_character -- '+active_character);
            if (type == "change_name") {
                name1 = $("#your_name").val();
                //     console.log('beforeSend name1 = '+name1);
            }
        },
        cache: false,
        dataType: "json",
        contentType: "application/json",
        //processData: false,
        success: function (data) {
            //online_status = data.result;
            if (type == "change_name") {
                clearChat();
                printMessages();
            }
        },
        error: function (jqXHR, exception) {
            console.log(exception);
            console.log(jqXHR);
        },
    });
}
function isInt(value) {
    return (
        !isNaN(value) &&
        parseInt(Number(value)) == value &&
        !isNaN(parseInt(value, 10))
    );
}
function messageEditDone(div) {
    let mesBlock = div.closest(".mes_block");
    var text = mesBlock.find(".edit_textarea").val().trim();
    const bias = extractMessageBias(text);
    chat[this_edit_mes_id]["mes"] = text;
    // editing old messages
    if (!chat[this_edit_mes_id]["extra"]) {
        chat[this_edit_mes_id]["extra"] = {};
    }
    chat[this_edit_mes_id]["extra"]["bias"] = bias ?? null;
    mesBlock.find(".mes_text").empty();
    mesBlock.find(".mes_edit_buttons").css("display", "none");
    mesBlock.find(".mes_edit").css("display", "inline-block");
    mesBlock.find(".mes_text").append(messageFormating(text, this_edit_mes_chname, chat[this_edit_mes_id].is_system, chat[this_edit_mes_id].force_avatar));
    mesBlock.find(".mes_bias").empty();
    mesBlock.find(".mes_bias").append(messageFormating(bias));
    appendImageToMessage(chat[this_edit_mes_id], div.closest(".mes"));
    this_edit_mes_id = undefined;
    saveChatConditional();
}
async function getAllCharaChats() {
    //console.log('getAllCharaChats() pinging server for character chat history.');
    $("#select_chat_div").html("");
    //console.log(characters[this_chid].chat);
    jQuery.ajax({
        type: "POST",
        url: "/getallchatsofcharacter",
        data: JSON.stringify({ avatar_url: characters[this_chid].avatar }),
        beforeSend: function () {
            //$('#create_button').attr('value','Creating...');
        },
        cache: false,
        dataType: "json",
        contentType: "application/json",
        success: function (data) {
            $("#load_select_chat_div").css("display", "none");
            let dataArr = Object.values(data);
            data = dataArr.sort((a, b) =>
                a["file_name"].localeCompare(b["file_name"])
            );
            data = data.reverse();
            $("#ChatHistoryCharName").html(characters[this_chid].name);
            for (const key in data) {
                let strlen = 300;
                let mes = data[key]["mes"];
                if (mes !== undefined) {
                    if (mes.length > strlen) {
                        mes = "..." + mes.substring(mes.length - strlen);
                    }
                    $("#select_chat_div").append(
                        '' +
                        '' +
                        '
' +
                        '' + data[key]["file_name"] + '' +
                        '' +
                        mes +
                        "" +
                        "" +
                        '' +
                        ''
                    );
                    if (
                        characters[this_chid]["chat"] ==
                        data[key]["file_name"].replace(".jsonl", "")
                    ) {
                        //children().last()
                        $("#select_chat_div")
                            .find(".select_chat_block:last")
                            .attr("highlight", true);
                    }
                }
            }
        },
        error: function (jqXHR, exception) {
            console.log(exception);
            console.log(jqXHR);
        },
    });
}
//************************************************************
//************************Novel.AI****************************
//************************************************************
async function getStatusNovel() {
    if (is_get_status_novel) {
        const data = { key: nai_settings.api_key_novel };
        jQuery.ajax({
            type: "POST", //
            url: "/getstatus_novelai", //
            data: JSON.stringify(data),
            beforeSend: function () {
                //$('#create_button').attr('value','Creating...');
            },
            cache: false,
            dataType: "json",
            contentType: "application/json",
            success: function (data) {
                if (data.error != true) {
                    novel_tier = data.tier;
                    online_status = getNovelTier(novel_tier);
                }
                resultCheckStatusNovel();
            },
            error: function (jqXHR, exception) {
                online_status = "no_connection";
                console.log(exception);
                console.log(jqXHR);
                resultCheckStatusNovel();
            },
        });
    } else {
        if (is_get_status != true && is_get_status_openai != true && is_get_status_poe != true) {
            online_status = "no_connection";
        }
    }
}
function compareVersions(v1, v2) {
    const v1parts = v1.split(".");
    const v2parts = v2.split(".");
    for (let i = 0; i < v1parts.length; ++i) {
        if (v2parts.length === i) {
            return 1;
        }
        if (v1parts[i] === v2parts[i]) {
            continue;
        }
        if (v1parts[i] > v2parts[i]) {
            return 1;
        } else {
            return -1;
        }
    }
    if (v1parts.length != v2parts.length) {
        return -1;
    }
    return 0;
}
function selectRightMenuWithAnimation(selectedMenuId) {
    const displayModes = {
        'rm_info_block': 'flex',
        'rm_group_chats_block': 'flex',
        'rm_api_block': 'grid',
        'rm_characters_block': 'flex',
    };
    document.querySelectorAll('#right-nav-panel .right_menu').forEach((menu) => {
        $(menu).css('display', 'none');
        if (selectedMenuId && selectedMenuId.replace('#', '') === menu.id) {
            const mode = displayModes[menu.id] ?? 'block';
            $(menu).css('display', mode);
            $(menu).css("opacity", 0.0);
            $(menu).transition({
                opacity: 1.0,
                duration: animation_rm_duration,
                easing: animation_rm_easing,
                complete: function () { },
            });
        }
    })
}
function setRightTabSelectedClass(selectedButtonId) {
    document.querySelectorAll('#right-nav-panel-tabs .right_menu_button').forEach((button) => {
        button.classList.remove('selected-right-tab');
        if (selectedButtonId && selectedButtonId.replace('#', '') === button.id) {
            button.classList.add('selected-right-tab');
        }
    });
}
function select_rm_info(text, charId = null) {
    $("#rm_info_text").html("" + text + "
");
    selectRightMenuWithAnimation('rm_info_block');
    setRightTabSelectedClass();
    prev_selected_char = charId;
    if (prev_selected_char) {
        const newId = characters.findIndex((x) => x.name == prev_selected_char);
        if (newId >= 0) {
            this_chid = newId;
        }
    }
}
function select_selected_character(chid) {
    //character select
    //console.log('select_selected_character() -- starting with input of -- '+chid+' (name:'+characters[chid].name+')');
    select_rm_create();
    menu_type = "character_edit";
    $("#delete_button").css("display", "flex");
    $("#export_button").css("display", "flex");
    setRightTabSelectedClass('rm_button_selected_ch');
    var display_name = characters[chid].name;
    //create text poles
    $("#rm_button_back").css("display", "none");
    //$("#character_import_button").css("display", "none");
    $("#create_button").attr("value", "Save");              // what is the use case for this?
    $("#create_button_label").css("display", "none");
    $("#rm_button_selected_ch").children("h2").text(display_name);
    $("#add_avatar_button").val("");
    $("#character_popup_text_h3").text(characters[chid].name);
    $("#character_name_pole").val(characters[chid].name);
    $("#description_textarea").val(characters[chid].description);
    $("#personality_textarea").val(characters[chid].personality);
    $("#firstmessage_textarea").val(characters[chid].first_mes);
    $("#scenario_pole").val(characters[chid].scenario);
    $("#talkativeness_slider").val(
        characters[chid].talkativeness ?? talkativeness_default
    );
    $("#mes_example_textarea").val(characters[chid].mes_example);
    $("#selected_chat_pole").val(characters[chid].chat);
    $("#create_date_pole").val(characters[chid].create_date);
    $("#avatar_url_pole").val(characters[chid].avatar);
    $("#chat_import_avatar_url").val(characters[chid].avatar);
    $("#chat_import_character_name").val(characters[chid].name);
    //$("#avatar_div").css("display", "none");
    var this_avatar = default_avatar;
    if (characters[chid].avatar != "none") {
        this_avatar = getThumbnailUrl('avatar', characters[chid].avatar);
    }
    $("#avatar_load_preview").attr("src", this_avatar);
    $("#name_div").css("display", "none");
    $("#form_create").attr("actiontype", "editcharacter");
    active_character = chid;
    //console.log('select_selected_character() -- active_character -- '+chid+'(ChID of '+display_name+')');
    saveSettingsDebounced();
    //console.log('select_selected_character() -- called saveSettings() to save -- active_character -- '+active_character+'(ChID of '+display_name+')');
}
function select_rm_create() {
    menu_type = "create";
    //console.log('select_rm_Create() -- selected button: '+selected_button);
    if (selected_button == "create") {
        if (create_save_avatar != "") {
            $("#add_avatar_button").get(0).files = create_save_avatar;
            read_avatar_load($("#add_avatar_button").get(0));
        }
    }
    selectRightMenuWithAnimation('rm_ch_create_block');
    setRightTabSelectedClass();
    $("#delete_button_div").css("display", "none");
    $("#delete_button").css("display", "none");
    $("#export_button").css("display", "none");
    $("#create_button_label").css("display", "block");
    $("#create_button").attr("value", "Create");
    //RossAscends: commented this out as part of the auto-loading token counter
    //$('#result_info').html(' ');
    //create text poles
    $("#rm_button_back").css("display", "inline-block");
    $("#character_import_button").css("display", "flex");
    $("#character_popup_text_h3").text("Create character");
    $("#character_name_pole").val(create_save_name);
    $("#description_textarea").val(create_save_description);
    $("#personality_textarea").val(create_save_personality);
    $("#firstmessage_textarea").val(create_save_first_message);
    $("#talkativeness_slider").val(create_save_talkativeness);
    $("#scenario_pole").val(create_save_scenario);
    if ($.trim(create_save_mes_example).length == 0) {
        $("#mes_example_textarea").val("");
    } else {
        $("#mes_example_textarea").val(create_save_mes_example);
    }
    $("#avatar_div").css("display", "flex");
    $("#avatar_load_preview").attr("src", default_avatar);
    $("#name_div").css("display", "block");
    $("#form_create").attr("actiontype", "createcharacter");
}
function select_rm_characters() {
    restoreSelectedCharacter();
    menu_type = "characters";
    selectRightMenuWithAnimation('rm_characters_block');
    setRightTabSelectedClass('rm_button_characters');
}
function restoreSelectedCharacter() {
    if (prev_selected_char) {
        let newChId = characters.findIndex((x) => x.name == prev_selected_char);
        $(`.character_select[chid="${newChId}"]`).trigger("click");
        prev_selected_char = null;
    }
}
function setExtensionPrompt(key, value, position, depth) {
    extension_prompts[key] = { value, position, depth };
}
function updateChatMetadata(newValues, reset) {
    chat_metadata = reset ? { ...newValues } : { ...chat_metadata, ...newValues };
}
function callPopup(text, type) {
    if (type) {
        popup_type = type;
    }
    $("#dialogue_popup_cancel").css("display", "inline-block");
    switch (popup_type) {
        case "text":
        case "char_not_selected":
            $("#dialogue_popup_ok").text("Ok");
            $("#dialogue_popup_cancel").css("display", "none");
            break;
        case "world_imported":
        case "new_chat":
            $("#dialogue_popup_ok").text("Yes");
            break;
        case "del_world":
        case "del_group":
        case "del_chat":
        default:
            $("#dialogue_popup_ok").text("Delete");
    }
    $("#dialogue_popup_input").val('');
    if (popup_type == 'input') {
        $("#dialogue_popup_input").css("display", "block");
        $("#dialogue_popup_ok").text("Save");
    }
    else {
        $("#dialogue_popup_input").css("display", "none");
    }
    $("#dialogue_popup_text").html(text);
    $("#shadow_popup").css("display", "block");
    if (popup_type == 'input') {
        $("#dialogue_popup_input").focus();
    }
    $("#shadow_popup").transition({
        opacity: 1.0,
        duration: animation_rm_duration,
        easing: animation_rm_easing,
    });
    return new Promise((resolve) => {
        dialogueResolve = resolve;
    });
}
function read_bg_load(input) {
    if (input.files && input.files[0]) {
        var reader = new FileReader();
        reader.onload = function (e) {
            $("#bg_load_preview")
                .attr("src", e.target.result)
                .width(103)
                .height(83);
            var formData = new FormData($("#form_bg_download").get(0));
            //console.log(formData);
            jQuery.ajax({
                type: "POST",
                url: "/downloadbackground",
                data: formData,
                beforeSend: function () {
                    //$('#create_button').attr('value','Creating...');
                },
                cache: false,
                contentType: false,
                processData: false,
                success: function (html) {
                    setBackground(html);
                    $("#bg1").css(
                        "background-image",
                        `url(${e.target.result})`
                    );
                    $("#form_bg_download").after(
                        `
                            
                        `
                    );
                },
                error: function (jqXHR, exception) {
                    console.log(exception);
                    console.log(jqXHR);
                },
            });
        };
        reader.readAsDataURL(input.files[0]);
    }
}
function showSwipeButtons() {
    if (
        chat[chat.length - 1].is_system ||
        !swipes ||
        $('.mes:last').attr('mesid') <= 0 ||
        chat[chat.length - 1].is_user ||
        count_view_mes <= 1 ||
        (selected_group && is_group_generating)
    ) { return; }
    //had to add this to make the swipe counter work
    //(copied from the onclick functions for swipe buttons..
    //don't know why the array isn't set for non-swipe messsages in Generate or addOneMessage..)
    if (chat[chat.length - 1]['swipe_id'] === undefined) {              // if there is no swipe-message in the last spot of the chat array
        chat[chat.length - 1]['swipe_id'] = 0;                        // set it to id 0
        chat[chat.length - 1]['swipes'] = [];                         // empty the array
        chat[chat.length - 1]['swipes'][0] = chat[chat.length - 1]['mes'];  //assign swipe array with last message from chat
    }
    const currentMessage = $("#chat").children().filter(`[mesid="${count_view_mes - 1}"]`);
    const swipeId = chat[chat.length - 1].swipe_id;
    var swipesCounterHTML = (`${(swipeId + 1)}/${(chat[chat.length - 1].swipes.length)}`);
    if (swipeId !== undefined && swipeId != 0) {
        currentMessage.children('.swipe_left').css('display', 'flex');
    }
    //only show right when generate is off, or when next right swipe would not make a generate happen
    if (is_send_press === false || chat[chat.length - 1].swipes.length >= swipeId) {
        currentMessage.children('.swipe_right').css('display', 'flex');
        currentMessage.children('.swipe_right').css('opacity', '0.3');
    }
    //console.log((chat[chat.length - 1]));
    if ((chat[chat.length - 1].swipes.length - swipeId) === 1) {
        //console.log('highlighting R swipe');
        currentMessage.children('.swipe_right').css('opacity', '0.7');
    }
    //console.log(swipesCounterHTML);
    $(".swipes-counter").html(swipesCounterHTML);
    //console.log(swipeId);
    //console.log(chat[chat.length - 1].swipes.length);
}
function hideSwipeButtons() {
    //console.log('hideswipebuttons entered');
    $("#chat").children().filter('[mesid="' + (count_view_mes - 1) + '"]').children('.swipe_right').css('display', 'none');
    $("#chat").children().filter('[mesid="' + (count_view_mes - 1) + '"]').children('.swipe_left').css('display', 'none');
}
function saveChatConditional() {
    if (selected_group) {
        saveGroupChat(selected_group);
    }
    else {
        saveChat();
    }
}
function updateViewMessageIds() {
    $('#chat').find(".mes").each(function (index, element) {
        $(element).attr("mesid", index);
    });
    $('#chat .mes').removeClass('last_mes');
    $('#chat .mes').last().addClass('last_mes');
    updateEditArrowClasses();
}
function updateEditArrowClasses() {
    $("#chat .mes .mes_edit_up").removeClass("disabled");
    $("#chat .mes .mes_edit_down").removeClass("disabled");
    if (this_edit_mes_id !== undefined) {
        const down = $(`#chat .mes[mesid="${this_edit_mes_id}"] .mes_edit_down`);
        const up = $(`#chat .mes[mesid="${this_edit_mes_id}"] .mes_edit_up`);
        const lastId = Number($("#chat .mes").last().attr("mesid"));
        const firstId = Number($("#chat .mes").first().attr("mesid"));
        if (lastId == Number(this_edit_mes_id)) {
            down.addClass("disabled");
        }
        if (firstId == Number(this_edit_mes_id)) {
            up.addClass("disabled");
        }
    }
}
function closeMessageEditor() {
    if (this_edit_mes_id) {
        $(`#chat .mes[mesid="${this_edit_mes_id}"] .mes_edit_cancel`).click();
    }
}
function setGenerationProgress(progress) {
    if (!progress) {
        $('#send_textarea').css({ 'background': '', 'transition': '' });
    }
    else {
        $('#send_textarea').css({
            'background': `linear-gradient(90deg, #008000d6 ${progress}%, transparent ${progress}%)`,
            'transition': '0.25s ease-in-out'
        });
    }
}
function isHordeGenerationNotAllowed() {
    if (main_api == "kobold" && horde_settings.use_horde && preset_settings == "gui") {
        callPopup('GUI Settings preset is not supported for Horde. Please select another preset.', 'text');
        return true;
    }
    return false;
}
window["TavernAI"].getContext = function () {
    return {
        chat: chat,
        characters: characters,
        groups: groups,
        worldInfo: world_info_data,
        name1: name1,
        name2: name2,
        characterId: this_chid,
        groupId: selected_group,
        chatId: this_chid && characters[this_chid] && characters[this_chid].chat,
        onlineStatus: online_status,
        maxContext: Number(max_context),
        chatMetadata: chat_metadata,
        addOneMessage: addOneMessage,
        generate: Generate,
        getTokenCount: getTokenCount,
        extensionPrompts: extension_prompts,
        setExtensionPrompt: setExtensionPrompt,
        updateChatMetadata: updateChatMetadata,
        saveChat: saveChatConditional,
        sendSystemMessage: sendSystemMessage,
        activateSendButtons,
        deactivateSendButtons,
        saveReply,
    };
};
$(document).ready(function () {
    $('#swipes-checkbox').change(function () {
        console.log('detected swipes-checkbox changed values')
        swipes = !!$('#swipes-checkbox').prop('checked');
        if (swipes) {
            //console.log('toggle change calling showswipebtns');
            showSwipeButtons();
        } else {
            hideSwipeButtons();
        }
        saveSettingsDebounced();
    });
    ///// SWIPE BUTTON CLICKS ///////
    $(document).on('click', '.swipe_right', function () {               //when we click swipe right button
        if (chat.length - 1 === Number(this_edit_mes_id)) {
            closeMessageEditor();
        }
        if (isHordeGenerationNotAllowed()) {
            return;
        }
        const swipe_duration = 120;
        const swipe_range = 700;
        //console.log(swipe_range);
        let run_generate = false;
        let run_swipe_right = false;
        if (chat[chat.length - 1]['swipe_id'] === undefined) {              // if there is no swipe-message in the last spot of the chat array
            chat[chat.length - 1]['swipe_id'] = 0;                        // set it to id 0
            chat[chat.length - 1]['swipes'] = [];                         // empty the array
            chat[chat.length - 1]['swipes'][0] = chat[chat.length - 1]['mes'];  //assign swipe array with last message from chat
        }
        chat[chat.length - 1]['swipe_id']++;                                      //make new slot in array
        //console.log(chat[chat.length-1]['swipes']);
        if (parseInt(chat[chat.length - 1]['swipe_id']) === chat[chat.length - 1]['swipes'].length) { //if swipe id of last message is the same as the length of the 'swipes' array
            run_generate = true;
        } else if (parseInt(chat[chat.length - 1]['swipe_id']) < chat[chat.length - 1]['swipes'].length) { //otherwise, if the id is less than the number of swipes
            chat[chat.length - 1]['mes'] = chat[chat.length - 1]['swipes'][chat[chat.length - 1]['swipe_id']]; //load the last mes box with the latest generation
            run_swipe_right = true; //then prepare to do normal right swipe to show next message
        }
        if (chat[chat.length - 1]['swipe_id'] > chat[chat.length - 1]['swipes'].length) { //if we swipe right while generating (the swipe ID is greater than what we are viewing now)
            chat[chat.length - 1]['swipe_id'] = chat[chat.length - 1]['swipes'].length; //show that message slot (will be '...' while generating)
        }
        if (run_generate) {               //hide swipe arrows while generating
            $(this).css('display', 'none');
        }
        if (run_generate || run_swipe_right) {                // handles animated transitions when swipe right, specifically height transitions between messages
            let this_mes_div = $(this).parent();
            let this_mes_block = $(this).parent().children('.mes_block').children('.mes_text');
            const this_mes_div_height = this_mes_div[0].scrollHeight;
            const this_mes_block_height = this_mes_block[0].scrollHeight;
            this_mes_div.children('.swipe_left').css('display', 'flex');
            this_mes_div.children('.mes_block').transition({        // this moves the div back and forth
                x: '-' + swipe_range,
                duration: swipe_duration,
                easing: animation_rm_easing,
                queue: false,
                complete: function () {
                    /*if (!selected_group) {
                        var typingIndicator = $("#typing_indicator_template .typing_indicator").clone();
                        typingIndicator.find(".typing_indicator_name").text(characters[this_chid].name);
                    } */
                    /* $("#chat").append(typingIndicator); */
                    const is_animation_scroll = ($('#chat').scrollTop() >= ($('#chat').prop("scrollHeight") - $('#chat').outerHeight()) - 10);
                    //console.log(parseInt(chat[chat.length-1]['swipe_id']));
                    //console.log(chat[chat.length-1]['swipes'].length);
                    if (run_generate && parseInt(chat[chat.length - 1]['swipe_id']) === chat[chat.length - 1]['swipes'].length) {
                        //console.log('showing ""..."');
                        /* if (!selected_group) {
                        } else { */
                        $("#chat").children().filter('[mesid="' + (count_view_mes - 1) + '"]').children('.mes_block').children('.mes_text').html('...');  //shows "..." while generating
                        /* } */
                    } else {
                        //console.log('showing previously generated swipe candidate, or "..."');
                        //console.log('onclick right swipe calling addOneMessage');
                        addOneMessage(chat[chat.length - 1], 'swipe');
                    }
                    let new_height = this_mes_div_height - (this_mes_block_height - this_mes_block[0].scrollHeight);
                    if (new_height < 103) new_height = 103;
                    this_mes_div.animate({ height: new_height + 'px' }, {
                        duration: 100,
                        queue: false,
                        progress: function () {
                            // Scroll the chat down as the message expands
                            if (is_animation_scroll) $("#chat").scrollTop($("#chat")[0].scrollHeight);
                        },
                        complete: function () {
                            this_mes_div.css('height', 'auto');
                            // Scroll the chat down to the bottom once the animation is complete
                            if (is_animation_scroll) $("#chat").scrollTop($("#chat")[0].scrollHeight);
                        }
                    });
                    this_mes_div.children('.mes_block').transition({
                        x: swipe_range,
                        duration: 0,
                        easing: animation_rm_easing,
                        queue: false,
                        complete: function () {
                            this_mes_div.children('.mes_block').transition({
                                x: '0px',
                                duration: swipe_duration,
                                easing: animation_rm_easing,
                                queue: false,
                                complete: function () {
                                    if (run_generate && !is_send_press && parseInt(chat[chat.length - 1]['swipe_id']) === chat[chat.length - 1]['swipes'].length) {
                                        console.log('caught here 2');
                                        is_send_press = true;
                                        $('.mes_edit:last').hide();
                                        Generate('swipe');
                                    } else {
                                        if (parseInt(chat[chat.length - 1]['swipe_id']) !== chat[chat.length - 1]['swipes'].length) {
                                            saveChatConditional();
                                        }
                                    }
                                }
                            });
                        }
                    });
                }
            });
            $(this).parent().children('.avatar').transition({ // moves avatar aong with swipe
                x: '-' + swipe_range,
                duration: swipe_duration,
                easing: animation_rm_easing,
                queue: false,
                complete: function () {
                    $(this).parent().children('.avatar').transition({
                        x: swipe_range,
                        duration: 0,
                        easing: animation_rm_easing,
                        queue: false,
                        complete: function () {
                            $(this).parent().children('.avatar').transition({
                                x: '0px',
                                duration: swipe_duration,
                                easing: animation_rm_easing,
                                queue: false,
                                complete: function () {
                                }
                            });
                        }
                    });
                }
            });
        }
    });
    $(document).on('click', '.swipe_left', function () {      // when we swipe left..but no generation.
        if (chat.length - 1 === Number(this_edit_mes_id)) {
            closeMessageEditor();
        }
        if (isStreamingEnabled() && streamingProcessor) {
            streamingProcessor.isStopped = true;
        }
        const swipe_duration = 120;
        const swipe_range = '700px';
        chat[chat.length - 1]['swipe_id']--;
        if (chat[chat.length - 1]['swipe_id'] >= 0) {
            /*$(this).parent().children('swipe_right').css('display', 'flex');
            if (chat[chat.length - 1]['swipe_id'] === 0) {
                $(this).css('display', 'none');
            }*/ // Just in case
            let this_mes_div = $(this).parent();
            let this_mes_block = $(this).parent().children('.mes_block').children('.mes_text');
            const this_mes_div_height = this_mes_div[0].scrollHeight;
            this_mes_div.css('height', this_mes_div_height);
            const this_mes_block_height = this_mes_block[0].scrollHeight;
            chat[chat.length - 1]['mes'] = chat[chat.length - 1]['swipes'][chat[chat.length - 1]['swipe_id']];
            $(this).parent().children('.mes_block').transition({
                x: swipe_range,
                duration: swipe_duration,
                easing: animation_rm_easing,
                queue: false,
                complete: function () {
                    const is_animation_scroll = ($('#chat').scrollTop() >= ($('#chat').prop("scrollHeight") - $('#chat').outerHeight()) - 10);
                    //console.log('on left swipe click calling addOneMessage');
                    addOneMessage(chat[chat.length - 1], 'swipe');
                    let new_height = this_mes_div_height - (this_mes_block_height - this_mes_block[0].scrollHeight);
                    if (new_height < 103) new_height = 103;
                    this_mes_div.animate({ height: new_height + 'px' }, {
                        duration: 100,
                        queue: false,
                        progress: function () {
                            // Scroll the chat down as the message expands
                            if (is_animation_scroll) $("#chat").scrollTop($("#chat")[0].scrollHeight);
                        },
                        complete: function () {
                            this_mes_div.css('height', 'auto');
                            // Scroll the chat down to the bottom once the animation is complete
                            if (is_animation_scroll) $("#chat").scrollTop($("#chat")[0].scrollHeight);
                        }
                    });
                    $(this).parent().children('.mes_block').transition({
                        x: '-' + swipe_range,
                        duration: 0,
                        easing: animation_rm_easing,
                        queue: false,
                        complete: function () {
                            $(this).parent().children('.mes_block').transition({
                                x: '0px',
                                duration: swipe_duration,
                                easing: animation_rm_easing,
                                queue: false,
                                complete: function () {
                                    saveChatConditional();
                                }
                            });
                        }
                    });
                }
            });
            $(this).parent().children('.avatar').transition({
                x: swipe_range,
                duration: swipe_duration,
                easing: animation_rm_easing,
                queue: false,
                complete: function () {
                    $(this).parent().children('.avatar').transition({
                        x: '-' + swipe_range,
                        duration: 0,
                        easing: animation_rm_easing,
                        queue: false,
                        complete: function () {
                            $(this).parent().children('.avatar').transition({
                                x: '0px',
                                duration: swipe_duration,
                                easing: animation_rm_easing,
                                queue: false,
                                complete: function () {
                                }
                            });
                        }
                    });
                }
            });
        }
        if (chat[chat.length - 1]['swipe_id'] < 0) {
            chat[chat.length - 1]['swipe_id'] = 0;
        }
    });
    $("#character_search_bar").on("input", function () {
        const selector = ['#rm_print_characters_block .character_select', '#rm_print_characters_block .group_select'].join(',');
        const searchValue = $(this).val().trim().toLowerCase();
        if (!searchValue) {
            $(selector).show();
        } else {
            $(selector).each(function () {
                $(this).children(".ch_name").text().toLowerCase().includes(searchValue)
                    ? $(this).show()
                    : $(this).hide();
            });
        }
    });
    $("#send_but").click(function () {
        if (is_send_press == false) {
            is_send_press = true;
            Generate();
        }
    });
    $("#send_textarea").keydown(function (e) {
        if (!e.shiftKey && !e.ctrlKey && e.key == "Enter" && is_send_press == false) {
            is_send_press = true;
            e.preventDefault();
            Generate();
        }
    });
    //menu buttons setup
    $("#rm_button_settings").click(function () {
        selected_button = "settings";
        menu_type = "settings";
        selectRightMenuWithAnimation('rm_api_block');
        setRightTabSelectedClass('rm_button_settings');
    });
    $("#rm_button_characters").click(function () {
        selected_button = "characters";
        select_rm_characters();
    });
    $("#rm_button_back").click(function () {
        selected_button = "characters";
        select_rm_characters();
    });
    $("#rm_button_create").click(function () {
        selected_button = "create";
        select_rm_create();
    });
    $("#rm_button_selected_ch").click(function () {
        if (selected_group) {
            select_group_chats(selected_group);
        } else {
            selected_button = "character_edit";
            select_selected_character(this_chid);
        }
    });
    $(document).on("click", ".character_select", function () {
        if (selected_group && is_group_generating) {
            return;
        }
        if (this_chid !== $(this).attr("chid")) {
            //if clicked on a different character from what was currently selected
            if (!is_send_press) {
                resetSelectedGroup();
                this_edit_mes_id = undefined;
                selected_button = "character_edit";
                this_chid = $(this).attr("chid");
                active_character = this_chid;
                clearChat();
                chat.length = 0;
                chat_metadata = {};
                getChat();
                //console.log('Clicked on '+characters[this_chid].name+' Active_Character set to: '+active_character+' (ChID:'+this_chid+')');
            }
        } else {
            //if clicked on character that was already selected
            selected_button = "character_edit";
            select_selected_character(this_chid);
        }
        $("#character_search_bar").val("").trigger("input");
    });
    $(document).on("input", ".edit_textarea", function () {
        scroll_holder = $("#chat").scrollTop();
        $(this).height(0).height(this.scrollHeight);
        is_use_scroll_holder = true;
    });
    $("#chat").on("scroll", function () {
        if (is_use_scroll_holder) {
            $("#chat").scrollTop(scroll_holder);
            is_use_scroll_holder = false;
        }
    });
    $(document).on("click", ".del_checkbox", function () {
        //when a 'delete message' checkbox is clicked
        $(".del_checkbox").each(function () {
            $(this).prop("checked", false);
            $(this).parent().css("background", css_mes_bg);
        });
        $(this).parent().css("background", "#600"); //sets the bg of the mes selected for deletion
        var i = $(this).parent().attr("mesid"); //checks the message ID in the chat
        this_del_mes = i;
        while (i < chat.length) {
            //as long as the current message ID is less than the total chat length
            $(".mes[mesid='" + i + "']").css("background", "#600"); //sets the bg of the all msgs BELOW the selected .mes
            $(".mes[mesid='" + i + "']")
                .children(".del_checkbox")
                .prop("checked", true);
            i++;
            //console.log(i);
        }
    });
    $(document).on("click", "#user_avatar_block .avatar", function () {
        user_avatar = $(this).attr("imgfile");
        $(".mes").each(function () {
            if ($(this).attr("ch_name") == name1) {
                $(this)
                    .children(".avatar")
                    .children("img")
                    .attr("src", "User Avatars/" + user_avatar);
            }
        });
        saveSettingsDebounced();
        highlightSelectedAvatar();
    });
    $(document).on("click", "#user_avatar_block .avatar_upload", function () {
        $("#avatar_upload_file").click();
    });
    $("#avatar_upload_file").on("change", function (e) {
        const file = e.target.files[0];
        if (!file) {
            return;
        }
        const formData = new FormData($("#form_upload_avatar").get(0));
        jQuery.ajax({
            type: "POST",
            url: "/uploaduseravatar",
            data: formData,
            beforeSend: () => { },
            cache: false,
            contentType: false,
            processData: false,
            success: function (data) {
                if (data.path) {
                    appendUserAvatar(data.path);
                }
            },
            error: (jqXHR, exception) => { },
        });
        // Will allow to select the same file twice in a row
        $("#form_upload_avatar").trigger("reset");
    });
    $(document).on("click", ".bg_example", async function () {
        //when user clicks on a BG thumbnail...
        const this_bgfile = $(this).attr("bgfile"); // this_bgfile = whatever they clicked
        const customBg = window.getComputedStyle(document.getElementById('bg_custom')).backgroundImage;
        // custom background is set. Do not override the layer below
        if (customBg !== 'none') {
            return;
        }
        // if clicked on upload button
        if (!this_bgfile) {
            return;
        }
        const backgroundUrl = `backgrounds/${this_bgfile}`;
        // fetching to browser memory to reduce flicker
        fetch(backgroundUrl).then(() => {
            $("#bg1").css(
                "background-image",
                `url("${backgroundUrl}")`
            );
            setBackground(this_bgfile);
        }).catch(() => {
            console.log('Background could not be set: ' + backgroundUrl);
        });
    });
    $(document).on("click", ".bg_example_cross", function (e) {
        e.stopPropagation();
        bg_file_for_del = $(this);
        //$(this).parent().remove();
        //delBackground(this_bgfile);
        popup_type = "del_bg";
        callPopup("Delete the background?
");
    });
    $(document).on("click", ".PastChat_cross", function () {
        chat_file_for_del = $(this).attr('file_name');
        console.log('detected cross click for' + chat_file_for_del);
        popup_type = "del_chat";
        callPopup("Delete the Chat File?
");
    });
    $("#advanced_div").click(function () {
        if (!is_advanced_char_open) {
            is_advanced_char_open = true;
            $("#character_popup").css("display", "grid");
            $("#character_popup").css("opacity", 0.0);
            $("#character_popup").transition({
                opacity: 1.0,
                duration: animation_rm_duration,
                easing: animation_rm_easing,
            });
        } else {
            is_advanced_char_open = false;
            $("#character_popup").css("display", "none");
        }
    });
    $("#character_cross").click(function () {
        is_advanced_char_open = false;
        $("#character_popup").css("display", "none");
    });
    $("#character_popup_ok").click(function () {
        is_advanced_char_open = false;
        $("#character_popup").css("display", "none");
    });
    $("#dialogue_popup_ok").click(function (e) {
        $("#shadow_popup").css("display", "none");
        $("#shadow_popup").css("opacity:", 0.0);
        if (popup_type == "del_bg") {
            delBackground(bg_file_for_del.attr("bgfile"));
            bg_file_for_del.parent().remove();
        }
        if (popup_type == "del_chat") {
            delChat(chat_file_for_del);
        }
        if (popup_type == "del_ch") {
            console.log(
                "Deleting character -- ChID: " +
                this_chid +
                " -- Name: " +
                characters[this_chid].name
            );
            var msg = jQuery("#form_create").serialize(); // ID form
            jQuery.ajax({
                method: "POST",
                url: "/deletecharacter",
                beforeSend: function () {
                    select_rm_info("Character deleted");
                    //$('#create_button').attr('value','Deleting...');
                },
                data: msg,
                cache: false,
                success: function (html) {
                    //RossAscends: New handling of character deletion that avoids page refreshes and should have fixed char corruption due to cache problems.
                    //due to how it is handled with 'popup_type', i couldn't find a way to make my method completely modular, so keeping it in TAI-main.js as a new default.
                    //this allows for dynamic refresh of character list after deleting a character.
                    $("#character_cross").click(); // closes advanced editing popup
                    this_chid = "invalid-safety-id"; // unsets expected chid before reloading (related to getCharacters/printCharacters from using old arrays)
                    characters.length = 0; // resets the characters array, forcing getcharacters to reset
                    name2 = systemUserName; // replaces deleted charcter name with system user since she will be displayed next.
                    chat = [...safetychat]; // sets up system user to tell user about having deleted a character
                    chat_metadata = {}; // resets chat metadata
                    setRightTabSelectedClass() // 'deselects' character's tab panel
                    $(document.getElementById("rm_button_selected_ch"))
                        .children("h2")
                        .text(""); // removes character name from nav tabs
                    clearChat(); // removes deleted char's chat
                    this_chid = undefined; // prevents getCharacters from trying to load an invalid char.
                    getCharacters(); // gets the new list of characters (that doesn't include the deleted one)
                    printMessages(); // prints out system user's 'deleted character' message
                    //console.log("#dialogue_popup_ok(del-char) >>>> saving");
                    saveSettingsDebounced(); // saving settings to keep changes to variables
                    //getCharacters();
                    //$('#create_button_div').html(html);
                },
            });
        }
        if (popup_type === "world_imported") {
            selectImportedWorldInfo();
        }
        if (popup_type === "del_world" && world_info) {
            deleteWorldInfo(world_info);
        }
        if (popup_type === "del_group") {
            const groupId = $("#dialogue_popup").data("group_id");
            if (groupId) {
                deleteGroup(groupId);
            }
        }
        //Make a new chat for selected character
        if (
            popup_type == "new_chat" &&
            this_chid != undefined &&
            menu_type != "create"
        ) {
            //Fix it; New chat doesn't create while open create character menu
            clearChat();
            chat.length = 0;
            chat_metadata = {};
            characters[this_chid].chat = name2 + " - " + humanizedDateTime(); //RossAscends: added character name to new chat filenames and replaced Date.now() with humanizedDateTime;
            $("#selected_chat_pole").val(characters[this_chid].chat);
            saveCharacterDebounced();
            getChat();
        }
        if (dialogueResolve) {
            if (popup_type == 'input') {
                dialogueResolve($("#dialogue_popup_input").val());
                $("#dialogue_popup_input").val('');
            }
            else {
                dialogueResolve(true);
            }
            dialogueResolve = null;
        }
    });
    $("#dialogue_popup_cancel").click(function (e) {
        $("#shadow_popup").css("display", "none");
        $("#shadow_popup").css("opacity:", 0.0);
        popup_type = "";
        if (dialogueResolve) {
            dialogueResolve(false);
            dialogueResolve = null;
        }
    });
    $("#add_bg_button").change(function () {
        read_bg_load(this);
    });
    $("#add_avatar_button").change(function () {
        is_mes_reload_avatar = Date.now();
        read_avatar_load(this);
    });
    $("#form_create").submit(function (e) {
        $("#rm_info_avatar").html("");
        let save_name = create_save_name;
        var formData = new FormData($("#form_create").get(0));
        if ($("#form_create").attr("actiontype") == "createcharacter") {
            if ($("#character_name_pole").val().length > 0) {
                //if the character name text area isn't empty (only posible when creating a new character)
                //console.log('/createcharacter entered');
                jQuery.ajax({
                    type: "POST",
                    url: "/createcharacter",
                    data: formData,
                    beforeSend: function () {
                        $("#create_button").attr("disabled", true);
                        $("#create_button").attr("value", "⏳");
                    },
                    cache: false,
                    contentType: false,
                    processData: false,
                    success: async function (html) {
                        $("#character_cross").click(); //closes the advanced character editing popup
                        $("#character_name_pole").val("");
                        create_save_name = "";
                        $("#description_textarea").val("");
                        create_save_description = "";
                        $("#personality_textarea").val("");
                        create_save_personality = "";
                        $("#firstmessage_textarea").val("");
                        create_save_first_message = "";
                        $("#talkativeness_slider").val(talkativeness_default);
                        create_save_talkativeness = talkativeness_default;
                        $("#character_popup_text_h3").text("Create character");
                        $("#scenario_pole").val("");
                        create_save_scenario = "";
                        $("#mes_example_textarea").val("");
                        create_save_mes_example = "";
                        create_save_avatar = "";
                        $("#create_button").removeAttr("disabled");
                        $("#add_avatar_button").replaceWith(
                            $("#add_avatar_button").val("").clone(true)
                        );
                        $("#create_button").attr("value", "✅");
                        if (true) {
                            let oldSelectedChar = null;
                            if (this_chid != undefined && this_chid != "invalid-safety-id") {
                                oldSelectedChar = characters[this_chid].name;
                            }
                            await getCharacters();
                            $("#rm_info_block").transition({ opacity: 0, duration: 0 });
                            var $prev_img = $("#avatar_div_div").clone();
                            $("#rm_info_avatar").append($prev_img);
                            select_rm_info(`Character created
${DOMPurify.sanitize(save_name)}
`, oldSelectedChar);
                            $("#rm_info_block").transition({ opacity: 1.0, duration: 2000 });
                        } else {
                            $("#result_info").html(html);
                        }
                    },
                    error: function (jqXHR, exception) {
                        //alert('ERROR: '+xhr.status+ ' Status Text: '+xhr.statusText+' '+xhr.responseText);
                        $("#create_button").removeAttr("disabled");
                    },
                });
            } else {
                $("#result_info").html("Name not entered");
            }
        } else {
            //console.log('/editcharacter -- entered.');
            //console.log('Avatar Button Value:'+$("#add_avatar_button").val());
            jQuery.ajax({
                type: "POST",
                url: "/editcharacter",
                data: formData,
                beforeSend: function () {
                    $("#create_button").attr("disabled", true);
                    $("#create_button").attr("value", "Save");
                },
                cache: false,
                contentType: false,
                processData: false,
                success: function (html) {
                    $(".mes").each(function () {
                        if ($(this).attr("is_system") == 'true') {
                            return;
                        }
                        if ($(this).attr("ch_name") != name1) {
                            $(this)
                                .children(".avatar")
                                .children("img")
                                .attr("src", $("#avatar_load_preview").attr("src"));
                        }
                    });
                    if (chat.length === 1) {
                        var this_ch_mes = default_ch_mes;
                        if ($("#firstmessage_textarea").val() != "") {
                            this_ch_mes = $("#firstmessage_textarea").val();
                        }
                        if (
                            this_ch_mes !=
                            $.trim(
                                $("#chat")
                                    .children(".mes")
                                    .children(".mes_block")
                                    .children(".mes_text")
                                    .text()
                            )
                        ) {
                            clearChat();
                            chat.length = 0;
                            chat[0] = {};
                            chat[0]["name"] = name2;
                            chat[0]["is_user"] = false;
                            chat[0]["is_name"] = true;
                            chat[0]["mes"] = this_ch_mes;
                            add_mes_without_animation = true;
                            //console.log('form create submission calling addOneMessage');
                            addOneMessage(chat[0]);
                        }
                    }
                    $("#create_button").removeAttr("disabled");
                    getCharacters();
                    $("#add_avatar_button").replaceWith(
                        $("#add_avatar_button").val("").clone(true)
                    );
                    $("#create_button").attr("value", "Save");
                },
                error: function (jqXHR, exception) {
                    $("#create_button").removeAttr("disabled");
                    $("#result_info").html("Error: no connection");
                },
            });
        }
    });
    $("#delete_button").click(function () {
        popup_type = "del_ch";
        callPopup(
            "Delete the character?
Your chat will be closed."
        );
    });
    $("#rm_info_button").click(function () {
        $("#rm_info_avatar").html("");
        select_rm_characters();
    });
    //////// OPTIMIZED ALL CHAR CREATION/EDITING TEXTAREA LISTENERS ///////////////
    $("#character_name_pole").on("input", function () {
        if (menu_type == "create") {
            create_save_name = $("#character_name_pole").val();
        }
    });
    $("#description_textarea, #personality_textarea, #scenario_pole, #mes_example_textarea, #firstmessage_textarea")
        .on("input", function () {
            if (menu_type == "create") {
                create_save_description = $("#description_textarea").val();
                create_save_personality = $("#personality_textarea").val();
                create_save_scenario = $("#scenario_pole").val();
                create_save_mes_example = $("#mes_example_textarea").val();
                create_save_first_message = $("#firstmessage_textarea").val();
            } else {
                saveCharacterDebounced();
            }
        });
    $("#talkativeness_slider").on("input", function () {
        if (menu_type == "create") {
            create_save_talkativeness = $("#talkativeness_slider").val();
        } else {
            saveCharacterDebounced();
        }
    });
    ///////////////////////////////////////////////////////////////////////////////////
    $("#api_button").click(function (e) {
        e.stopPropagation();
        if ($("#api_url_text").val() != "" && !horde_settings.use_horde) {
            let value = formatKoboldUrl($.trim($("#api_url_text").val()));
            if (!value) {
                callPopup('Please enter a valid URL.', 'text');
                return;
            }
            $("#api_url_text").val(value);
            api_server = value;
            $("#api_loading").css("display", "inline-block");
            $("#api_button").css("display", "none");
            main_api = "kobold";
            saveSettingsDebounced();
            is_get_status = true;
            is_api_button_press = true;
            getStatus();
            clearSoftPromptsList();
            getSoftPromptsList();
        }
        else if (horde_settings.use_horde) {
            main_api = "kobold";
            is_get_status = true;
            getStatus();
            clearSoftPromptsList();
        }
    });
    $("#api_button_textgenerationwebui").click(function (e) {
        e.stopPropagation();
        if ($("#textgenerationwebui_api_url_text").val() != "") {
            $("#api_loading_textgenerationwebui").css("display", "inline-block");
            $("#api_button_textgenerationwebui").css("display", "none");
            api_server_textgenerationwebui = $(
                "#textgenerationwebui_api_url_text"
            ).val();
            api_server_textgenerationwebui = $.trim(api_server_textgenerationwebui);
            if (
                api_server_textgenerationwebui.substr(
                    api_server_textgenerationwebui.length - 1,
                    1
                ) == "/"
            ) {
                api_server_textgenerationwebui = api_server_textgenerationwebui.substr(
                    0,
                    api_server_textgenerationwebui.length - 1
                );
            }
            //console.log("2: "+api_server_textgenerationwebui);
            main_api = "textgenerationwebui";
            saveSettingsDebounced();
            is_get_status = true;
            is_api_button_press = true;
            getStatus();
        }
    });
    $("body").click(function () {
        if ($("#options").css("opacity") == 1.0) {
            $("#options").transition({
                opacity: 0.0,
                duration: 100, //animation_rm_duration,
                easing: animation_rm_easing,
                complete: function () {
                    $("#options").css("display", "none");
                },
            });
        }
    });
    $("#options_button").click(function () {
        // this is the options button click function, shows the options menu if closed
        if (
            $("#options").css("display") === "none" &&
            $("#options").css("opacity") == 0.0
        ) {
            optionsPopper.update();
            showBookmarksButtons();
            $("#options").css("display", "block");
            $("#options").transition({
                opacity: 1.0, // the manual setting of CSS via JS is what allows the click-away feature to work
                duration: 100,
                easing: animation_rm_easing,
                complete: function () { optionsPopper.update(); },
            });
        }
    });
    ///////////// OPTIMIZED LISTENERS FOR LEFT SIDE OPTIONS POPUP MENU //////////////////////
    $("#options [id]").on("click", function () {
        var id = $(this).attr("id");
        if (id == "option_select_chat") {
            if (selected_group) {
                // will open a chat selection screen
                /* openNavToggle(); */
                $("#rm_button_characters").trigger("click");
                return;
            }
            if (this_chid != undefined && !is_send_press) {
                getAllCharaChats();
                $("#shadow_select_chat_popup").css("display", "block");
                $("#shadow_select_chat_popup").css("opacity", 0.0);
                $("#shadow_select_chat_popup").transition({
                    opacity: 1.0,
                    duration: animation_rm_duration,
                    easing: animation_rm_easing,
                });
            }
        }
        else if (id == "option_start_new_chat") {
            if (selected_group) {
                // will open a group creation screen
                /* openNavToggle(); */
                $("#rm_button_group_chats").trigger("click");
                return;
            }
            if (this_chid != undefined && !is_send_press) {
                popup_type = "new_chat";
                callPopup("Start new chat?
");
            }
        }
        else if (id == "option_regenerate") {
            if (is_send_press == false) {
                //hideSwipeButtons();
                if (selected_group) {
                    regenerateGroup();
                }
                else {
                    is_send_press = true;
                    Generate("regenerate");
                }
            }
        }
        else if (id == "option_impersonate") {
            if (is_send_press == false) {
                is_send_press = true;
                Generate("impersonate");
            }
        }
        else if (id == "option_delete_mes") {
            closeMessageEditor();
            hideSwipeButtons();
            if ((this_chid != undefined && !is_send_press) || (selected_group && !is_group_generating)) {
                $("#dialogue_del_mes").css("display", "block");
                $("#send_form").css("display", "none");
                $(".del_checkbox").each(function () {
                    if ($(this).parent().attr("mesid") != 0) {
                        $(this).css("display", "block");
                        $(this).parent().children(".for_checkbox").css("display", "none");
                    }
                });
            }
        }
    });
    //////////////////////////////////////////////////////////////////////////////////////////////
    //functionality for the cancel delete messages button, reverts to normal display of input form
    $("#dialogue_del_mes_cancel").click(function () {
        $("#dialogue_del_mes").css("display", "none");
        $("#send_form").css("display", css_send_form_display);
        $(".del_checkbox").each(function () {
            $(this).css("display", "none");
            $(this).parent().children(".for_checkbox").css("display", "block");
            $(this).parent().css("background", css_mes_bg);
            $(this).prop("checked", false);
        });
        this_del_mes = 0;
        console.log('canceled del msgs, calling showswipesbtns');
        showSwipeButtons();
    });
    //confirms message delation with the "ok" button
    $("#dialogue_del_mes_ok").click(function () {
        $("#dialogue_del_mes").css("display", "none");
        $("#send_form").css("display", css_send_form_display);
        $(".del_checkbox").each(function () {
            $(this).css("display", "none");
            $(this).parent().children(".for_checkbox").css("display", "block");
            $(this).parent().css("background", css_mes_bg);
            $(this).prop("checked", false);
        });
        if (this_del_mes != 0) {
            $(".mes[mesid='" + this_del_mes + "']")
                .nextAll("div")
                .remove();
            $(".mes[mesid='" + this_del_mes + "']").remove();
            chat.length = this_del_mes;
            count_view_mes = this_del_mes;
            saveChatConditional();
            var $textchat = $("#chat");
            $textchat.scrollTop($textchat[0].scrollHeight);
        }
        this_del_mes = 0;
        $('#chat .mes').last().addClass('last_mes');
        $('#chat .mes').eq(-2).removeClass('last_mes');
        console.log('confirmed del msgs, calling showswipesbtns');
        showSwipeButtons();
    });
    $("#settings_perset").change(function () {
        if ($("#settings_perset").find(":selected").val() != "gui") {
            preset_settings = $("#settings_perset").find(":selected").text();
            const preset = koboldai_settings[koboldai_setting_names[preset_settings]];
            loadKoboldSettings(preset);
            amount_gen = preset.genamt;
            $("#amount_gen").val(amount_gen);
            $("#amount_gen_counter").text(`${amount_gen}`);
            max_context = preset.max_length;
            $("#max_context").val(max_context);
            $("#max_context_counter").text(`${max_context}`);
            $("#range_block").find('input').prop("disabled", false);
            $("#kobold-advanced-config").find('input').prop("disabled", false);
            $("#kobold-advanced-config").css('opacity', 1.0);
            $("#range_block").css("opacity", 1.0);
            $("#amount_gen_block").find('input').prop("disabled", false);
            $("#amount_gen_block").css("opacity", 1.0);
        } else {
            //$('.button').disableSelection();
            preset_settings = "gui";
            $("#range_block").find('input').prop("disabled", true);
            $("#kobold-advanced-config").find('input').prop("disabled", true);
            $("#kobold-advanced-config").css('opacity', 0.5);
            $("#range_block").css("opacity", 0.5);
            $("#amount_gen_block").find('input').prop("disabled", true);
            $("#amount_gen_block").css("opacity", 0.45);
        }
        saveSettingsDebounced();
    });
    $("#settings_perset_novel").change(function () {
        nai_settings.preset_settings_novel = $("#settings_perset_novel")
            .find(":selected")
            .text();
        const preset = novelai_settings[novelai_setting_names[nai_settings.preset_settings_novel]];
        loadNovelPreset(preset);
        saveSettingsDebounced();
    });
    $("#main_api").change(function () {
        is_pygmalion = false;
        is_get_status = false;
        is_get_status_novel = false;
        setOpenAIOnlineStatus(false);
        setPoeOnlineStatus(false);
        online_status = "no_connection";
        clearSoftPromptsList();
        checkOnlineStatus();
        changeMainAPI();
        saveSettingsDebounced();
    });
    $("#softprompt").change(async function () {
        if (!api_server) {
            return;
        }
        const selected = $("#softprompt").find(":selected").val();
        const response = await fetch("/setsoftprompt", {
            method: "POST",
            headers: {
                "Content-Type": "application/json",
                "X-CSRF-Token": token,
            },
            body: JSON.stringify({ name: selected, api_server: api_server }),
        });
        if (!response.ok) {
            console.error("Couldn't change soft prompt");
        }
    });
    ////////////////// OPTIMIZED RANGE SLIDER LISTENERS////////////////
    const sliders = [
        {
            sliderId: "#amount_gen",
            counterId: "#amount_gen_counter",
            format: (val) => `${val}`,
            setValue: (val) => { amount_gen = Number(val); },
        },
        {
            sliderId: "#max_context",
            counterId: "#max_context_counter",
            format: (val) => `${val}`,
            setValue: (val) => { max_context = Number(val); },
        }
    ];
    sliders.forEach(slider => {
        $(document).on("input", slider.sliderId, function () {
            const value = $(this).val();
            const formattedValue = slider.format(value);
            slider.setValue(value);
            $(slider.counterId).text(formattedValue);
            console.log('saving');
            saveSettingsDebounced();
        });
    });
    //////////////////////////////////////////////////////////////
    $("#style_anchor").change(function () {
        style_anchor = !!$("#style_anchor").prop("checked");
        saveSettingsDebounced();
    });
    $("#character_anchor").change(function () {
        character_anchor = !!$("#character_anchor").prop("checked");
        saveSettingsDebounced();
    });
    $("#donation").click(function () {
        $("#shadow_tips_popup").css("display", "block");
        $("#shadow_tips_popup").transition({
            opacity: 1.0,
            duration: 100,
            easing: animation_rm_easing,
            complete: function () { },
        });
    });
    $("#tips_cross").click(function () {
        $("#shadow_tips_popup").transition({
            opacity: 0.0,
            duration: 100,
            easing: animation_rm_easing,
            complete: function () {
                $("#shadow_tips_popup").css("display", "none");
            },
        });
    });
    $("#select_chat_cross").click(function () {
        $("#shadow_select_chat_popup").css("display", "none");
        $("#load_select_chat_div").css("display", "block");
    });
    //********************
    //***Message Editor***
    $(document).on("click", ".mes_edit", function () {
        if (this_chid !== undefined || selected_group) {
            const message = $(this).closest(".mes");
            if (message.data("isSystem")) {
                return;
            }
            let chatScrollPosition = $("#chat").scrollTop();
            if (this_edit_mes_id !== undefined) {
                let mes_edited = $("#chat")
                    .children()
                    .filter('[mesid="' + this_edit_mes_id + '"]')
                    .find(".mes_block")
                    .find(".mes_edit_done");
                if (edit_mes_id == count_view_mes - 1) { //if the generating swipe (...)
                    if (chat[edit_mes_id]['swipe_id'] !== undefined) {
                        if (chat[edit_mes_id]['swipes'].length === chat[edit_mes_id]['swipe_id']) {
                            run_edit = false;
                        }
                    }
                    if (run_edit) {
                        hideSwipeButtons();
                    }
                }
                messageEditDone(mes_edited);
            }
            $(this).closest(".mes_block").find(".mes_text").empty();
            $(this).css("display", "none");
            $(this).closest(".mes_block").find(".mes_edit_buttons").css("display", "inline-flex");
            var edit_mes_id = $(this).closest(".mes").attr("mesid");
            this_edit_mes_id = edit_mes_id;
            var text = chat[edit_mes_id]["mes"];
            if (chat[edit_mes_id]["is_user"]) {
                this_edit_mes_chname = name1;
            } else if (chat[edit_mes_id]["forced_avatar"]) {
                this_edit_mes_chname = chat[edit_mes_id]["name"];
            } else {
                this_edit_mes_chname = name2;
            }
            text = text.trim();
            $(this)
                .closest(".mes_block")
                .find(".mes_text")
                .append(
                    '"
                );
            let edit_textarea = $(this)
                .closest(".mes_block")
                .find(".edit_textarea");
            edit_textarea.height(0);
            edit_textarea.height(edit_textarea[0].scrollHeight);
            edit_textarea.focus();
            edit_textarea[0].setSelectionRange(     //this sets the cursor at the end of the text
                edit_textarea.val().length,
                edit_textarea.val().length
            );
            if (this_edit_mes_id == count_view_mes - 1) {
                $("#chat").scrollTop(chatScrollPosition);
            }
            updateEditArrowClasses();
        }
    });
    $(document).on("click", ".mes_edit_cancel", function () {
        let text = chat[this_edit_mes_id]["mes"];
        $(this).closest(".mes_block").find(".mes_text").empty();
        $(this).closest(".mes_edit_buttons").css("display", "none");
        $(this).closest(".mes_block").find(".mes_edit").css("display", "inline-block");
        $(this)
            .closest(".mes_block")
            .find(".mes_text")
            .append(messageFormating(text, this_edit_mes_chname));
        appendImageToMessage(chat[this_edit_mes_id], $(this).closest(".mes"));
        this_edit_mes_id = undefined;
    });
    $(document).on("click", ".mes_edit_up", function () {
        if (is_send_press || this_edit_mes_id <= 0) {
            return;
        }
        hideSwipeButtons();
        const targetId = Number(this_edit_mes_id) - 1;
        const target = $(`#chat .mes[mesid="${targetId}"]`);
        const root = $(this).closest('.mes');
        if (root.length === 0 || target.length === 0) {
            return;
        }
        root.insertBefore(target);
        target.attr("mesid", this_edit_mes_id);
        root.attr("mesid", targetId);
        const temp = chat[targetId];
        chat[targetId] = chat[this_edit_mes_id];
        chat[this_edit_mes_id] = temp;
        this_edit_mes_id = targetId;
        updateViewMessageIds();
        saveChatConditional();
        showSwipeButtons();
    });
    $(document).on("click", ".mes_edit_down", function () {
        if (is_send_press || this_edit_mes_id >= chat.length - 1) {
            return;
        }
        hideSwipeButtons();
        const targetId = Number(this_edit_mes_id) + 1;
        const target = $(`#chat .mes[mesid="${targetId}"]`);
        const root = $(this).closest('.mes');
        if (root.length === 0 || target.length === 0) {
            return;
        }
        root.insertAfter(target);
        target.attr("mesid", this_edit_mes_id);
        root.attr("mesid", targetId);
        const temp = chat[targetId];
        chat[targetId] = chat[this_edit_mes_id];
        chat[this_edit_mes_id] = temp;
        this_edit_mes_id = targetId;
        updateViewMessageIds();
        saveChatConditional();
        showSwipeButtons();
    });
    $(document).on("click", ".mes_edit_copy", function () {
        if (!confirm('Create a copy of this message?')) {
            return;
        }
        hideSwipeButtons();
        let oldScroll = $('#chat')[0].scrollTop;
        const clone = JSON.parse(JSON.stringify(chat[this_edit_mes_id])); // quick and dirty clone
        clone.send_date = Date.now();
        clone.mes = $(this).closest(".mes").find('.edit_textarea').val().trim();
        chat.splice(Number(this_edit_mes_id) + 1, 0, clone);
        addOneMessage(clone, 'normal', this_edit_mes_id);
        updateViewMessageIds();
        saveChatConditional();
        $('#chat')[0].scrollTop = oldScroll;
        showSwipeButtons();
    });
    $(document).on("click", ".mes_edit_delete", function () {
        if (!confirm("Are you sure you want to delete this message?")) {
            return;
        }
        const mes = $(this).closest(".mes");
        if (!mes) {
            return;
        }
        chat.splice(this_edit_mes_id, 1);
        this_edit_mes_id = undefined;
        mes.remove();
        count_view_mes--;
        updateViewMessageIds();
        saveChatConditional();
        hideSwipeButtons();
        showSwipeButtons();
    });
    $(document).on("click", ".mes_edit_done", function () {
        messageEditDone($(this));
    });
    $("#your_name_button").click(function () {
        if (!is_send_press) {
            name1 = $("#your_name").val();
            if (name1 === undefined || name1 == "") name1 = default_user_name;
            console.log(name1);
            saveSettings("change_name");
        }
    });
    //Select chat
    $("#api_button_novel").click(function (e) {
        e.stopPropagation();
        if ($("#api_key_novel").val() != "") {
            $("#api_loading_novel").css("display", "inline-block");
            $("#api_button_novel").css("display", "none");
            nai_settings.api_key_novel = $.trim($("#api_key_novel").val());
            saveSettingsDebounced();
            is_get_status_novel = true;
            is_api_button_press_novel = true;
        }
    });
    $("#anchor_order").change(function () {
        anchor_order = parseInt($("#anchor_order").find(":selected").val());
        saveSettingsDebounced();
    });
    //**************************CHARACTER IMPORT EXPORT*************************//
    $("#character_import_button").click(function () {
        $("#character_import_file").click();
    });
    $("#character_import_file").on("change", function (e) {
        $("#rm_info_avatar").html("");
        if (!e.target.files.length) {
            return;
        }
        let names = [];
        for (const file of e.target.files) {
            var ext = file.name.match(/\.(\w+)$/);
            if (
                !ext ||
                (ext[1].toLowerCase() != "json" && ext[1].toLowerCase() != "png" && ext[1] != "webp")
            ) {
                continue;
            }
            var format = ext[1].toLowerCase();
            $("#character_import_file_type").val(format);
            var formData = new FormData();
            formData.append('avatar', file);
            formData.append('file_type', format);
            jQuery.ajax({
                type: "POST",
                url: "/importcharacter",
                data: formData,
                async: false,
                beforeSend: function () {
                },
                cache: false,
                contentType: false,
                processData: false,
                success: async function (data) {
                    if (data.file_name !== undefined) {
                        $("#rm_info_block").transition({ opacity: 0, duration: 0 });
                        var $prev_img = $("#avatar_div_div").clone();
                        $prev_img
                            .children("img")
                            .attr("src", "characters/" + data.file_name + ".png");
                        $("#rm_info_avatar").append($prev_img);
                        let oldSelectedChar = null;
                        if (this_chid != undefined && this_chid != "invalid-safety-id") {
                            oldSelectedChar = characters[this_chid].name;
                        }
                        names.push(data.file_name);
                        let nameString = DOMPurify.sanitize(names.join(', '));
                        await getCharacters();
                        select_rm_info(`Character imported
${nameString}
`, oldSelectedChar);
                        $("#rm_info_block").transition({ opacity: 1, duration: 1000 });
                    }
                },
                error: function (jqXHR, exception) {
                    $("#create_button").removeAttr("disabled");
                },
            });
        }
    });
    $("#export_button").click(function (e) {
        $('#export_format_popup').toggle();
        exportPopper.update();
    });
    $(document).on('click', '.export_format', async function () {
        const format = $(this).data('format');
        if (!format) {
            return;
        }
        const body = { format, avatar_url: characters[this_chid].avatar };
        const response = await fetch('/exportcharacter', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'X-CSRF-Token': token,
            },
            body: JSON.stringify(body),
        });
        if (response.ok) {
            const filename = characters[this_chid].avatar.replace('.png', `.${format}`);
            const blob = await response.blob();
            const a = document.createElement("a");
            a.href = URL.createObjectURL(blob);
            a.setAttribute("download", filename);
            document.body.appendChild(a);
            a.click();
            document.body.removeChild(a);
        }
        $('#export_format_popup').hide();
    });
    //**************************CHAT IMPORT EXPORT*************************//
    $("#chat_import_button").click(function () {
        $("#chat_import_file").click();
    });
    $("#chat_import_file").on("change", function (e) {
        var file = e.target.files[0];
        //console.log(1);
        if (!file) {
            return;
        }
        var ext = file.name.match(/\.(\w+)$/);
        if (
            !ext ||
            (ext[1].toLowerCase() != "json" && ext[1].toLowerCase() != "jsonl")
        ) {
            return;
        }
        var format = ext[1].toLowerCase();
        $("#chat_import_file_type").val(format);
        //console.log(format);
        var formData = new FormData($("#form_import_chat").get(0));
        //console.log('/importchat entered with: '+formData);
        jQuery.ajax({
            type: "POST",
            url: "/importchat",
            data: formData,
            beforeSend: function () {
                $("#select_chat_div").html("");
                $("#load_select_chat_div").css("display", "block");
                //$('#create_button').attr('value','Creating...');
            },
            cache: false,
            contentType: false,
            processData: false,
            success: function (data) {
                //console.log(data);
                if (data.res) {
                    getAllCharaChats();
                }
            },
            error: function (jqXHR, exception) {
                $("#create_button").removeAttr("disabled");
            },
        });
    });
    $("#rm_button_group_chats").click(function () {
        selected_button = "group_chats";
        select_group_chats();
    });
    $("#rm_button_back_from_group").click(function () {
        selected_button = "characters";
        select_rm_characters();
    });
    $("#rm_button_extensions").click(function () {
        menu_type = 'extennsions';
        selected_button = 'extensions';
        setRightTabSelectedClass('rm_button_extensions');
        selectRightMenuWithAnimation('rm_extensions_block');
    });
    $(document).on("click", ".select_chat_block, .bookmark_link", async function () {
        let file_name = $(this).attr("file_name").replace(".jsonl", "");
        openCharacterChat(file_name);
    });
    $(document).on("click", ".mes_stop", function () {
        if (streamingProcessor) {
            streamingProcessor.isStopped = true;
            streamingProcessor.onStopStreaming();
            streamingProcessor = null;
        }
    });
    $('.drawer-toggle').click(function () {
        var icon = $(this).find('.drawer-icon');
        var drawer = $(this).parent().find('.drawer-content');
        var drawerWasOpenAlready = $(this).parent().find('.drawer-content').hasClass('openDrawer');
        const pinnedDrawerClicked = drawer.hasClass('pinnedOpen');
        if (!drawerWasOpenAlready) {
            $('.openDrawer').not('.pinnedOpen').slideToggle(200, "swing");
            $('.openIcon').toggleClass('closedIcon openIcon');
            $('.openDrawer').not('.pinnedOpen').toggleClass('closedDrawer openDrawer');
            icon.toggleClass('openIcon closedIcon');
            drawer.toggleClass('openDrawer closedDrawer');
            $(this).closest('.drawer').find('.drawer-content').slideToggle(200, "swing");
        } else if (drawerWasOpenAlready) {
            icon.toggleClass('closedIcon openIcon');
            if (pinnedDrawerClicked) {
                $(drawer).slideToggle(200, "swing");
            }
            else {
                $('.openDrawer').not('.pinnedOpen').slideToggle(200, "swing");
            }
            drawer.toggleClass('closedDrawer openDrawer');
        }
    });
    $("html").on('touchstart mousedown', function (e) {
        var clickTarget = $(e.target);
        if ($('#export_format_popup').is(':visible') && clickTarget.closest('#export_button').length == 0 && clickTarget.closest('#export_format_popup').length == 0) {
            $('#export_format_popup').hide();
        }
        const forbiddenTargets = ['#character_cross', '#avatar-and-name-block', '#shadow_popup', '#world_popup'];
        for (const id of forbiddenTargets) {
            if (clickTarget.closest(id).length > 0) {
                return;
            }
        }
        var targetParentHasOpenDrawer = clickTarget.parents('.openDrawer').length;
        if (clickTarget.hasClass('drawer-icon') == false && !clickTarget.hasClass('openDrawer')) {
            if (jQuery.find('.openDrawer').length !== 0) {
                if (targetParentHasOpenDrawer === 0) {
                    //console.log($('.openDrawer').not('.pinnedOpen').length);
                    $('.openDrawer').not('.pinnedOpen').slideToggle(200, "swing");
                    $('.openIcon').toggleClass('closedIcon openIcon');
                    $('.openDrawer').not('.pinnedOpen').toggleClass('closedDrawer openDrawer');
                }
            }
        }
    });
    $(document).on('click', '.inline-drawer-toggle', function () {
        var icon = $(this).find('.inline-drawer-icon');
        icon.toggleClass('down up');
        icon.toggleClass('fa-circle-chevron-down fa-circle-chevron-up');
        $(this).closest('.inline-drawer').find('.inline-drawer-content').slideToggle();
    });
    $(document).keyup(function (e) {
        if (e.key === "Escape") {
            closeMessageEditor();
        }
    });
})