import { humanizedDateTime, favsToHotswap, getMessageTimeStamp, dragElement, isMobile, initRossMods, } from "./scripts/RossAscends-mods.js"; import { userStatsHandler, statMesProcess } from './scripts/stats.js'; import { generateKoboldWithStreaming, kai_settings, loadKoboldSettings, formatKoboldUrl, getKoboldGenerationData, kai_flags, setKoboldFlags, } from "./scripts/kai-settings.js"; import { textgenerationwebui_settings, loadTextGenSettings, generateTextGenWithStreaming, getTextGenGenerationData, formatTextGenURL, } from "./scripts/textgen-settings.js"; import { world_info, getWorldInfoPrompt, getWorldInfoSettings, setWorldInfoSettings, world_names, importEmbeddedWorldInfo, checkEmbeddedWorld, setWorldInfoButtonClass, importWorldInfo, } from "./scripts/world-info.js"; import { groups, selected_group, saveGroupChat, getGroups, generateGroupWrapper, deleteGroup, is_group_generating, resetSelectedGroup, select_group_chats, regenerateGroup, group_generation_id, getGroupChat, renameGroupMember, createNewGroupChat, getGroupPastChats, getGroupAvatar, openGroupChat, editGroup, deleteGroupChat, renameGroupChat, importGroupChat, getGroupBlock, getGroupChatNames, } from "./scripts/group-chats.js"; import { collapseNewlines, loadPowerUserSettings, playMessageSound, fixMarkdown, power_user, persona_description_positions, loadMovingUIState, getCustomStoppingStrings, MAX_CONTEXT_DEFAULT, renderStoryString, sortEntitiesList, registerDebugFunction, ui_mode, switchSimpleMode, } from "./scripts/power-user.js"; import { setOpenAIMessageExamples, setOpenAIMessages, setupChatCompletionPromptManager, prepareOpenAIMessages, sendOpenAIRequest, loadOpenAISettings, setOpenAIOnlineStatus, generateOpenAIPromptCache, oai_settings, is_get_status_openai, openai_messages_count, chat_completion_sources, getChatCompletionModel, } from "./scripts/openai.js"; import { generateNovelWithStreaming, getNovelGenerationData, getKayraMaxContextTokens, getNovelTier, loadNovelPreset, loadNovelSettings, nai_settings, adjustNovelInstructionPrompt, loadNovelSubscriptionData, } from "./scripts/nai-settings.js"; import { createNewBookmark, showBookmarksButtons } from "./scripts/bookmarks.js"; import { horde_settings, loadHordeSettings, generateHorde, checkHordeStatus, getHordeModels, adjustHordeGenerationParams, MIN_AMOUNT_GEN, } from "./scripts/horde.js"; import { debounce, delay, restoreCaretPosition, saveCaretPosition, end_trim_to_sentence, countOccurrences, isOdd, sortMoments, timestampToMoment, download, isDataURL, getCharaFilename, isDigitsOnly, PAGINATION_TEMPLATE, waitUntilCondition, escapeRegex, resetScrollHeight, onlyUnique, } from "./scripts/utils.js"; import { extension_settings, getContext, loadExtensionSettings, processExtensionHelpers, registerExtensionHelper, runGenerationInterceptors, saveMetadataDebounced } from "./scripts/extensions.js"; import { COMMENT_NAME_DEFAULT, executeSlashCommands, getSlashCommandsHelp, registerSlashCommand } from "./scripts/slash-commands.js"; import { tag_map, tags, loadTagsSettings, printTagFilters, getTagsList, appendTagToList, createTagMapFromList, renameTagKey, importTags, tag_filter_types, } from "./scripts/tags.js"; import { SECRET_KEYS, readSecretState, secret_state, writeSecret } from "./scripts/secrets.js"; import { EventEmitter } from './lib/eventemitter.js'; import { markdownExclusionExt } from "./scripts/showdown-exclusion.js"; import { NOTE_MODULE_NAME, initAuthorsNote, metadata_keys, setFloatingPrompt, shouldWIAddPrompt } from "./scripts/authors-note.js"; import { getDeviceInfo } from "./scripts/RossAscends-mods.js"; import { registerPromptManagerMigration } from "./scripts/PromptManager.js"; import { getRegexedString, regex_placement } from "./scripts/extensions/regex/engine.js"; import { FILTER_TYPES, FilterHelper } from "./scripts/filters.js"; import { getCfgPrompt, getGuidanceScale } from "./scripts/extensions/cfg/util.js"; import { force_output_sequence, formatInstructModeChat, formatInstructModePrompt, formatInstructModeExamples, getInstructStoppingSequences, autoSelectInstructPreset, formatInstructModeSystemPrompt, } from "./scripts/instruct-mode.js"; import { applyLocale } from "./scripts/i18n.js"; import { getTokenCount, getTokenizerModel, saveTokenCache } from "./scripts/tokenizers.js"; import { initPersonas, selectCurrentPersona, setPersonaDescription } from "./scripts/personas.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, replaceCurrentChat, setOnlineStatus, checkOnlineStatus, setEditedMessageId, setSendButtonState, selectRightMenuWithAnimation, openCharacterChat, saveChat, messageFormatting, getExtensionPrompt, showSwipeButtons, hideSwipeButtons, changeMainAPI, setGenerationProgress, updateChatMetadata, scrollChatToBottom, isStreamingEnabled, getThumbnailUrl, getStoppingStrings, getStatus, reloadMarkdownProcessor, getCurrentChatId, chat, this_chid, selected_button, menu_type, settings, characters, online_status, main_api, api_server, system_messages, nai_settings, token, name1, name2, is_send_press, api_server_textgenerationwebui, max_context, chat_metadata, streamingProcessor, default_avatar, system_message_types, talkativeness_default, default_ch_mes, extension_prompt_types, mesForShowdownParse, printCharacters, } // Allow target="_blank" in links DOMPurify.addHook('afterSanitizeAttributes', function (node) { if ('target' in node) { node.setAttribute('target', '_blank'); node.setAttribute('rel', 'noopener'); } }); // API OBJECT FOR EXTERNAL WIRING window["SillyTavern"] = {}; // Event source init export const event_types = { EXTRAS_CONNECTED: 'extras_connected', MESSAGE_SWIPED: 'message_swiped', MESSAGE_SENT: 'message_sent', MESSAGE_RECEIVED: 'message_received', MESSAGE_EDITED: 'message_edited', MESSAGE_DELETED: 'message_deleted', IMPERSONATE_READY: 'impersonate_ready', CHAT_CHANGED: 'chat_id_changed', GENERATION_STOPPED: 'generation_stopped', EXTENSIONS_FIRST_LOAD: 'extensions_first_load', SETTINGS_LOADED: 'settings_loaded', SETTINGS_UPDATED: 'settings_updated', GROUP_UPDATED: 'group_updated', MOVABLE_PANELS_RESET: 'movable_panels_reset', SETTINGS_LOADED_BEFORE: 'settings_loaded_before', SETTINGS_LOADED_AFTER: 'settings_loaded_after', CHATCOMPLETION_SOURCE_CHANGED: 'chatcompletion_source_changed', CHATCOMPLETION_MODEL_CHANGED: 'chatcompletion_model_changed', OAI_BEFORE_CHATCOMPLETION: 'oai_before_chatcompletion', OAI_PRESET_CHANGED_BEFORE: 'oai_preset_changed_before', OAI_PRESET_CHANGED_AFTER: 'oai_preset_changed_after', WORLDINFO_SETTINGS_UPDATED: 'worldinfo_settings_updated', CHARACTER_EDITED: 'character_edited', USER_MESSAGE_RENDERED: 'user_message_rendered', CHARACTER_MESSAGE_RENDERED: 'character_message_rendered', FORCE_SET_BACKGROUND: 'force_set_background,' } export const eventSource = new EventEmitter(); // Check for override warnings every 5 seconds... setInterval(displayOverrideWarnings, 5000); // ...or when the chat changes eventSource.on(event_types.SETTINGS_LOADED, () => { settingsReady = true; }); eventSource.on(event_types.CHAT_CHANGED, displayOverrideWarnings); eventSource.on(event_types.MESSAGE_RECEIVED, processExtensionHelpers); eventSource.on(event_types.MESSAGE_SENT, processExtensionHelpers); hljs.addPlugin({ "before:highlightElement": ({ el }) => { el.textContent = el.innerText } }); // Markdown converter let mesForShowdownParse; //intended to be used as a context to compare showdown strings against let converter; reloadMarkdownProcessor(); // array for prompt token calculations console.debug('initializing Prompt Itemization Array on Startup'); let itemizedPrompts = []; /* let bg_menu_toggle = false; */ export const systemUserName = "SillyTavern System"; let default_user_name = "User"; let name1 = default_user_name; let name2 = "SillyTavern System"; let chat = []; let safetychat = [ { name: systemUserName, is_user: false, is_name: true, create_date: 0, mes: "You deleted a character/chat and arrived back here for safety reasons! Pick another character!", }, ]; let chatSaveTimeout; let importFlashTimeout; export let isChatSaving = false; let chat_create_date = 0; let firstRun = false; let settingsReady = false; const default_ch_mes = "Hello"; let count_view_mes = 0; let generatedPromptCache = ""; let generation_started = new Date(); let characters = []; let this_chid; let saveCharactersPage = 0; let backgrounds = []; const default_avatar = "img/ai4.png"; export const system_avatar = "img/five.png"; export const comment_avatar = "img/quill.png"; export let CLIENT_VERSION = 'SillyTavern:UNKNOWN:Cohee#1207'; // For Horde header let optionsPopper = Popper.createPopper(document.getElementById('options_button'), document.getElementById('options'), { placement: 'top-start' }); let exportPopper = Popper.createPopper(document.getElementById('export_button'), document.getElementById('export_format_popup'), { placement: 'left' }); let rawPromptPopper = Popper.createPopper(document.getElementById('dialogue_popup'), document.getElementById('rawPromptPopup'), { placement: 'right' }); let dialogueResolve = null; let chat_metadata = {}; let streamingProcessor = null; let crop_data = undefined; let is_delete_mode = false; let fav_ch_checked = false; let scrollLock = false; const durationSaveEdit = 1000; const saveSettingsDebounced = debounce(() => saveSettings(), durationSaveEdit); export const saveCharacterDebounced = debounce(() => $("#create_button").trigger('click'), durationSaveEdit); const system_message_types = { HELP: "help", WELCOME: "welcome", GROUP: "group", EMPTY: "empty", GENERIC: "generic", BOOKMARK_CREATED: "bookmark_created", BOOKMARK_BACK: "bookmark_back", NARRATOR: "narrator", COMMENT: "comment", SLASH_COMMANDS: "slash_commands", FORMATTING: "formatting", HOTKEYS: "hotkeys", MACROS: "macros", }; const extension_prompt_types = { /** * @deprecated Outdated term. In reality it's "after main prompt or story string" */ AFTER_SCENARIO: 0, IN_CHAT: 1 }; let system_messages = {}; function getSystemMessages() { system_messages = { help: { name: systemUserName, force_avatar: system_avatar, is_user: false, is_system: true, is_name: true, mes: renderTemplate("help"), }, slash_commands: { name: systemUserName, force_avatar: system_avatar, is_user: false, is_system: true, is_name: true, mes: '', }, hotkeys: { name: systemUserName, force_avatar: system_avatar, is_user: false, is_system: true, is_name: true, mes: renderTemplate("hotkeys"), }, formatting: { name: systemUserName, force_avatar: system_avatar, is_user: false, is_system: true, is_name: true, mes: renderTemplate("formatting"), }, macros: { name: systemUserName, force_avatar: system_avatar, is_user: false, is_system: true, is_name: true, mes: renderTemplate("macros"), }, welcome: { name: systemUserName, force_avatar: system_avatar, is_user: false, is_system: true, is_name: true, mes: renderTemplate("welcome"), }, group: { name: systemUserName, force_avatar: system_avatar, is_user: false, is_system: true, is_name: true, is_group: true, mes: "Group chat created. Say 'Hi' to lovely people!", }, empty: { name: systemUserName, force_avatar: system_avatar, is_user: false, is_system: true, is_name: true, mes: "No one hears you. Hint: add more members to the group!", }, generic: { name: systemUserName, force_avatar: system_avatar, is_user: false, is_system: true, is_name: true, mes: "Generic system message. User `text` parameter to override the contents", }, bookmark_created: { name: systemUserName, force_avatar: system_avatar, is_user: false, is_system: true, is_name: true, mes: `Bookmark created! Click here to open the bookmark chat: {1}`, }, bookmark_back: { name: systemUserName, force_avatar: system_avatar, is_user: false, is_system: true, is_name: true, mes: `Click here to return to the previous chat: Return`, }, }; } // Register configuration migrations registerPromptManagerMigration(); $(document).ajaxError(function myErrorHandler(_, xhr) { if (xhr.status == 403) { toastr.warning( "doubleCsrf errors in console are NORMAL in this case. If you want to run ST in multiple tabs, start the server with --disableCsrf option.", "Looks like you've opened SillyTavern in another browser tab", { timeOut: 0, extendedTimeOut: 0, preventDuplicates: true }, ); } }); function getUrlSync(url, cache = true) { return $.ajax({ type: "GET", url: url, cache: cache, async: false }).responseText; } const templateCache = {}; export function renderTemplate(templateId, templateData = {}, sanitize = true, localize = true, fullPath = false) { try { const pathToTemplate = fullPath ? templateId : `/scripts/templates/${templateId}.html`; const templateContent = (pathToTemplate in templateCache) ? templateCache[pathToTemplate] : getUrlSync(pathToTemplate); templateCache[pathToTemplate] = templateContent; const template = Handlebars.compile(templateContent); let result = template(templateData); if (sanitize) { result = DOMPurify.sanitize(result); } if (localize) { result = applyLocale(result); } return result; } catch (err) { console.error("Error rendering template", templateId, templateData, err); toastr.error("Check the DevTools console for more information.", "Error rendering template"); } } async function getClientVersion() { try { const response = await fetch('/version'); const data = await response.json(); CLIENT_VERSION = data.agent; let displayVersion = `SillyTavern ${data.pkgVersion}`; if (data.gitRevision && data.gitBranch) { displayVersion += ` '${data.gitBranch}' (${data.gitRevision})`; } $('#version_display').text(displayVersion); $('#version_display_welcome').text(displayVersion); } catch (err) { console.error("Couldn't get client version", err); } } function reloadMarkdownProcessor(render_formulas = false) { if (render_formulas) { converter = new showdown.Converter({ emoji: true, underline: true, tables: true, parseImgDimensions: true, extensions: [ showdownKatex( { delimiters: [ { left: '$$', right: '$$', display: true, asciimath: false }, { left: '$', right: '$', display: false, asciimath: true }, ] } )], }); } else { converter = new showdown.Converter({ emoji: true, literalMidWordUnderscores: true, parseImgDimensions: true, tables: true, }); } // Inject the dinkus extension after creating the converter // Maybe move this into power_user init? setTimeout(() => { if (power_user) { converter.addExtension(markdownExclusionExt(), 'exclusion'); } }, 1) return converter; } function getCurrentChatId() { if (selected_group) { return groups.find(x => x.id == selected_group)?.chat_id; } else if (this_chid) { return characters[this_chid].chat; } } const talkativeness_default = 0.5; var is_advanced_char_open = false; var menu_type = ""; //what is selected in the menu var selected_button = ""; //which button pressed //create pole save let create_save = { name: "", description: "", creator_notes: "", post_history_instructions: "", character_version: "", system_prompt: "", tags: "", creator: "", personality: "", first_message: "", avatar: "", scenario: "", mes_example: "", world: "", talkativeness: talkativeness_default, alternate_greetings: [] }; //animation right menu let animation_duration = 125; let animation_easing = "ease-in-out"; let popup_type = ""; let bg_file_for_del = ""; let chat_file_for_del = ""; let online_status = "no_connection"; let api_server = ""; let api_server_textgenerationwebui = ""; //var interval_timer = setInterval(getStatus, 2000); //let interval_timer_novel = setInterval(getStatusNovel, 90000); let is_get_status = false; let is_get_status_novel = false; let is_api_button_press = false; let is_api_button_press_novel = false; let api_use_mancer_webui = false; let is_send_press = false; //Send generation let this_del_mes = 0; //message editing and chat scroll posistion persistence var this_edit_mes_text = ""; var this_edit_mes_chname = ""; var this_edit_mes_id; var scroll_holder = 0; var is_use_scroll_holder = false; //settings var settings; export let koboldai_settings; export let koboldai_setting_names; var preset_settings = "gui"; export let user_avatar = "you.png"; export var amount_gen = 80; //default max length of AI generated responses var max_context = 2048; var tokens_already_generated = 0; var message_already_generated = ""; var cycle_count_generation = 0; var swipes = true; let extension_prompts = {}; var main_api;// = "kobold"; //novel settings export let novelai_settings; export let novelai_setting_names; let abortController; //css var css_mes_bg = $('
').css("background"); var css_send_form_display = $("").css("display"); let generate_loop_counter = 0; const MAX_GENERATION_LOOPS = 5; var kobold_horde_model = ""; let token; var PromptArrayItemForRawPromptDisplay; export let active_character = ""; export let active_group = ""; export const entitiesFilter = new FilterHelper(debounce(printCharacters, 100)); export function getRequestHeaders() { return { "Content-Type": "application/json", "X-CSRF-Token": token, }; } $.ajaxPrefilter((options, originalOptions, xhr) => { xhr.setRequestHeader("X-CSRF-Token", token); }); async function firstLoadInit() { try { const tokenResponse = await fetch('/csrf-token'); const tokenData = await tokenResponse.json(); token = tokenData.token; } catch { toastr.error("Couldn't get CSRF token. Please refresh the page.", "Error", { timeOut: 0, extendedTimeOut: 0, preventDuplicates: true }); throw new Error("Initialization failed"); } getSystemMessages(); sendSystemMessage(system_message_types.WELCOME); await readSecretState(); await getClientVersion(); await getSettings("def"); await getUserAvatars(); await getCharacters(); await getBackgrounds(); initAuthorsNote(); initPersonas(); initRossMods(); } function checkOnlineStatus() { ///////// REMOVED LINES THAT DUPLICATE RA_CHeckOnlineStatus FEATURES if (online_status == "no_connection") { $("#online_status_indicator2").css("background-color", "red"); //Kobold $("#online_status_text2").html("No connection..."); $("#online_status_indicator_horde").css("background-color", "red"); //Kobold Horde $("#online_status_text_horde").html("No connection..."); $("#online_status_indicator3").css("background-color", "red"); //Novel $("#online_status_text3").html("No connection..."); $(".online_status_indicator4").css("background-color", "red"); //OAI / ooba $(".online_status_text4").html("No connection..."); is_get_status = false; is_get_status_novel = false; setOpenAIOnlineStatus(false); } else { $("#online_status_indicator2").css("background-color", "green"); //kobold $("#online_status_text2").html(online_status); $("#online_status_indicator_horde").css("background-color", "green"); //Kobold Horde $("#online_status_text_horde").html(online_status); $("#online_status_indicator3").css("background-color", "green"); //novel $("#online_status_text3").html(online_status); $(".online_status_indicator4").css("background-color", "green"); //OAI / ooba $(".online_status_text4").html(online_status); } } export function setActiveCharacter(character) { active_character = character; } export function setActiveGroup(group) { active_group = group; } async function getStatus() { if (is_get_status) { if (main_api == "koboldhorde") { try { const hordeStatus = await checkHordeStatus(); online_status = hordeStatus ? 'Connected' : 'no_connection'; resultCheckStatus(); } catch { online_status = "no_connection"; resultCheckStatus(); } return; } jQuery.ajax({ type: "POST", // url: "/getstatus", // data: JSON.stringify({ api_server: main_api == "kobold" ? api_server : api_server_textgenerationwebui, main_api: main_api, use_mancer: main_api == "textgenerationwebui" ? api_use_mancer_webui : false, }), beforeSend: function () { }, cache: false, dataType: "json", crossDomain: true, contentType: "application/json", //processData: false, success: function (data) { online_status = data.result; if (online_status == undefined) { online_status = "no_connection"; } // Determine instruct mode preset autoSelectInstructPreset(online_status); // determine if we can use stop sequence and streaming if (main_api === "kobold" || main_api === "koboldhorde") { setKoboldFlags(data.version, data.koboldVersion); } // We didn't get a 200 status code, but the endpoint has an explanation. Which means it DID connect, but I digress. if (online_status == "no_connection" && data.response) { toastr.error(data.response, "API Error", { timeOut: 5000, preventDuplicates: true }) } //console.log(online_status); resultCheckStatus(); }, error: function (jqXHR, exception) { console.log(exception); console.log(jqXHR); online_status = "no_connection"; resultCheckStatus(); }, }); } else { if (is_get_status_novel != true && is_get_status_openai != true) { online_status = "no_connection"; } } } function resultCheckStatus() { is_api_button_press = false; checkOnlineStatus(); $("#api_loading").css("display", "none"); $("#api_button").css("display", "inline-block"); $("#api_loading_textgenerationwebui").css("display", "none"); $("#api_button_textgenerationwebui").css("display", "inline-block"); } export function selectCharacterById(id) { if (characters[id] == undefined) { return; } if (isChatSaving) { toastr.info("Please wait until the chat is saved before switching characters.", "Your chat is still saving..."); return; } if (selected_group && is_group_generating) { return; } if (selected_group || this_chid !== id) { //if clicked on a different character from what was currently selected if (!is_send_press) { cancelTtsPlay(); resetSelectedGroup(); this_edit_mes_id = undefined; selected_button = "character_edit"; this_chid = id; clearChat(); chat.length = 0; chat_metadata = {}; getChat(); } } else { //if clicked on character that was already selected selected_button = "character_edit"; select_selected_character(this_chid); } } function getCharacterBlock(item, id) { let this_avatar = default_avatar; if (item.avatar != "none") { this_avatar = getThumbnailUrl('avatar', item.avatar); } // Populate the template const template = $('#character_template .character_select').clone(); template.attr({ 'chid': id, 'id': `CharID${id}` }); template.find('img').attr('src', this_avatar); template.find('.avatar').attr('title', item.avatar); template.find('.ch_name').text(item.name); if (power_user.show_card_avatar_urls) { template.find('.ch_avatar_url').text(item.avatar); } template.find('.ch_fav_icon').css("display", 'none'); template.toggleClass('is_fav', item.fav || item.fav == 'true'); template.find('.ch_fav').val(item.fav); const description = item.data?.creator_notes?.split('\n', 1)[0] || ''; if (description) { template.find('.ch_description').text(description); } else { template.find('.ch_description').hide(); } const version = item.data?.character_version || ''; if (version) { template.find('.character_version').text(version); } else { template.find('.character_version').hide(); } // Display inline tags const tags = getTagsList(item.avatar); const tagsElement = template.find('.tags'); tags.forEach(tag => appendTagToList(tagsElement, tag, {})); // Add to the list return template; } async function printCharacters(fullRefresh = false) { if (fullRefresh) { saveCharactersPage = 0; printTagFilters(tag_filter_types.character); printTagFilters(tag_filter_types.group_member); await delay(1); displayOverrideWarnings(); } const storageKey = 'Characters_PerPage'; $("#rm_print_characters_pagination").pagination({ dataSource: getEntitiesList({ doFilter: true }), pageSize: Number(localStorage.getItem(storageKey)) || 50, sizeChangerOptions: [10, 25, 50, 100, 250, 500, 1000], pageRange: 1, pageNumber: saveCharactersPage || 1, position: 'top', showPageNumbers: false, showSizeChanger: true, prevText: '<', nextText: '>', formatNavigator: PAGINATION_TEMPLATE, showNavigator: true, callback: function (data) { $("#rm_print_characters_block").empty(); for (const i of data) { if (i.type === 'character') { $("#rm_print_characters_block").append(getCharacterBlock(i.item, i.id)); } if (i.type === 'group') { $("#rm_print_characters_block").append(getGroupBlock(i.item)); } } }, afterSizeSelectorChange: function (e) { localStorage.setItem(storageKey, e.target.value); }, afterPaging: function (e) { saveCharactersPage = e; }, afterRender: function () { $('#rm_print_characters_block').scrollTop(0); }, }); favsToHotswap(); } export function getEntitiesList({ doFilter } = {}) { let entities = []; entities.push(...characters.map((item, index) => ({ item, id: index, type: 'character' }))); entities.push(...groups.map((item) => ({ item, id: item.id, type: 'group' }))); if (doFilter) { entities = entitiesFilter.applyFilters(entities); } sortEntitiesList(entities); return entities; } async function getOneCharacter(avatarUrl) { const response = await fetch("/getonecharacter", { method: "POST", headers: getRequestHeaders(), body: JSON.stringify({ avatar_url: avatarUrl, }), }); if (response.ok) { const getData = await response.json(); getData['name'] = DOMPurify.sanitize(getData['name']); getData['chat'] = String(getData['chat']); const indexOf = characters.findIndex(x => x.avatar === avatarUrl); if (indexOf !== -1) { characters[indexOf] = getData; } else { toastr.error(`Character ${avatarUrl} not found in the list`, "Error", { timeOut: 5000, preventDuplicates: true }); } } } async function getCharacters() { var response = await fetch("/getcharacters", { method: "POST", headers: getRequestHeaders(), body: JSON.stringify({ "": "", }), }); if (response.ok === true) { var getData = ""; //RossAscends: reset to force array to update to account for deleted character. getData = await response.json(); const load_ch_count = Object.getOwnPropertyNames(getData); for (var i = 0; i < load_ch_count.length; i++) { characters[i] = []; characters[i] = getData[i]; characters[i]['name'] = DOMPurify.sanitize(characters[i]['name']); // For dropped-in cards if (!characters[i]['chat']) { characters[i]['chat'] = `${characters[i]['name']} - ${humanizedDateTime()}`; } characters[i]['chat'] = String(characters[i]['chat']); } if (this_chid != undefined && this_chid != "invalid-safety-id") { $("#avatar_url_pole").val(characters[this_chid].avatar); } await getGroups(); await printCharacters(true); } } async function getBackgrounds() { const response = await fetch("/getbackgrounds", { method: "POST", headers: getRequestHeaders(), body: JSON.stringify({ "": "", }), }); if (response.ok === true) { const getData = await response.json(); //background = getData; //console.log(getData.length); $("#bg_menu_content").children('div').remove(); for (const bg of getData) { const template = getBackgroundFromTemplate(bg); $("#bg_menu_content").append(template); } } } function getBackgroundFromTemplate(bg) { const thumbPath = getThumbnailUrl('bg', bg); const template = $('#background_template .bg_example').clone(); template.attr('bgfile', bg); template.attr('title', bg); template.find('.bg_button').attr('bgfile', bg); template.css('background-image', `url('${thumbPath}')`); template.find('.BGSampleTitle').text(bg.slice(0, bg.lastIndexOf('.'))); return template; } async function setBackground(bg) { jQuery.ajax({ type: "POST", // url: "/setbackground", // data: JSON.stringify({ bg: bg, }), beforeSend: function () { }, cache: false, dataType: "json", contentType: "application/json", //processData: false, success: function (html) { }, error: function (jqXHR, exception) { console.log(exception); console.log(jqXHR); }, }); } async function delBackground(bg) { const response = await fetch("/delbackground", { method: "POST", headers: getRequestHeaders(), body: JSON.stringify({ bg: bg, }), }); if (response.ok === true) { } } async function delChat(chatfile) { const response = await fetch("/delchat", { method: "POST", headers: getRequestHeaders(), body: JSON.stringify({ chatfile: chatfile, avatar_url: characters[this_chid].avatar, }), }); if (response.ok === true) { // choose another chat if current was deleted if (chatfile.replace('.jsonl', '') === characters[this_chid].chat) { chat_metadata = {}; await replaceCurrentChat(); } } } async function replaceCurrentChat() { clearChat(); chat.length = 0; const chatsResponse = await fetch("/getallchatsofcharacter", { method: 'POST', headers: getRequestHeaders(), body: JSON.stringify({ avatar_url: characters[this_chid].avatar }) }); if (chatsResponse.ok) { const chats = Object.values(await chatsResponse.json()); // pick existing chat if (chats.length && typeof chats[0] === 'object') { characters[this_chid].chat = chats[0].file_name.replace('.jsonl', ''); $("#selected_chat_pole").val(characters[this_chid].chat); saveCharacterDebounced(); await getChat(); } // start new chat else { characters[this_chid].chat = name2 + " - " + humanizedDateTime(); $("#selected_chat_pole").val(characters[this_chid].chat); saveCharacterDebounced(); await getChat(); } } } function printMessages() { chat.forEach(function (item, i, arr) { addOneMessage(item, { scroll: i === arr.length - 1 }); }); if (power_user.lazy_load > 0) { const height = $('#chat').height(); const scrollHeight = $('#chat').prop('scrollHeight'); // Only hide if oveflowing the scroll if (scrollHeight > height) { $('#chat').children('.mes').slice(0, -power_user.lazy_load).hide(); } } } function clearChat() { count_view_mes = 0; extension_prompts = {}; $("#chat").children().remove(); if ($('.zoomed_avatar[forChar]').length) { console.debug('saw avatars to remove') $('.zoomed_avatar[forChar]').remove(); } else { console.debug('saw no avatars') } itemizedPrompts = []; } async function deleteLastMessage() { count_view_mes--; chat.length = chat.length - 1; $('#chat').children('.mes').last().remove(); await eventSource.emit(event_types.MESSAGE_DELETED, chat.length); } export async function reloadCurrentChat() { clearChat(); chat.length = 0; if (selected_group) { await getGroupChat(selected_group); } else if (this_chid) { await getChat(); } else { resetChatState(); printMessages(); } await eventSource.emit(event_types.CHAT_CHANGED, getCurrentChatId()); } function messageFormatting(mes, ch_name, isSystem, isUser) { if (mes) { mesForShowdownParse = mes; } if (!mes) { mes = ''; } // Force isSystem = false on comment messages so they get formatted properly if (ch_name === COMMENT_NAME_DEFAULT && isSystem && !isUser) { isSystem = false; } // Prompt bias replacement should be applied on the raw message if (!power_user.show_user_prompt_bias && ch_name && !isUser && !isSystem) { mes = mes.replaceAll(substituteParams(power_user.user_prompt_bias), ""); } if (!isSystem) { let regexPlacement; if (isUser) { regexPlacement = regex_placement.USER_INPUT; } else if (ch_name !== name2) { regexPlacement = regex_placement.SLASH_COMMAND; } else { regexPlacement = regex_placement.AI_OUTPUT; } // Always override the character name mes = getRegexedString(mes, regexPlacement, { characterOverride: ch_name, isMarkdown: true }); } if (power_user.auto_fix_generated_markdown) { mes = fixMarkdown(mes, true); } if (!isSystem && power_user.encode_tags) { mes = mes.replaceAll("<", "<").replaceAll(">", ">"); } if ((this_chid === undefined || this_chid === "invalid-safety-id") && !selected_group) { mes = mes .replace(/\*\*(.+?)\*\*/g, "$1") .replace(/\n/g, ""' + p1.replace(/\"/g, "") + '"'; } else if (p2) { return '
“' + p2.replace(/\u201C|\u201D/g, "") + '”'; } else { return match; } }); mes = mes.replaceAll('\\begin{align*}', '$$'); mes = mes.replaceAll('\\end{align*}', '$$'); mes = converter.makeHtml(mes); mes = replaceBiasMarkup(mes); mes = mes.replace(/
[\s\S]*?<\/code>/g, function (match) {
// Firefox creates extra newlines from
s in code blocks, so we replace them before converting newlines to
s.
return match.replace(/\n/gm, '\u0000');
})
mes = mes.replace(/\n/g, "
");
mes = mes.replace(/\u0000/g, "\n"); // Restore converted newlines
mes = mes.trim();
mes = mes.replace(/[\s\S]*?<\/code>/g, function (match) {
return match.replace(/&/g, '&');
});
}
/*
// Hides bias from empty messages send with slash commands
if (isSystem) {
mes = mes.replace(/\{\{[\s\S]*?\}\}/gm, "");
}
*/
if (!power_user.allow_name2_display && ch_name && !isUser && !isSystem) {
mes = mes.replace(new RegExp(`(^|\n)${ch_name}:`, 'g'), "$1");
}
mes = DOMPurify.sanitize(mes);
return mes;
}
/**
* Inserts or replaces an SVG icon adjacent to the provided message's timestamp.
*
* If the `extra.api` is "openai" and `extra.model` contains the substring "claude",
* the function fetches the "claude.svg". Otherwise, it fetches the SVG named after
* the value in `extra.api`.
*
* @param {JQuery} mes - The message element containing the timestamp where the icon should be inserted or replaced.
* @param {Object} extra - Contains the API and model details.
* @param {string} extra.api - The name of the API, used to determine which SVG to fetch.
* @param {string} extra.model - The model name, used to check for the substring "claude".
*/
function insertSVGIcon(mes, extra) {
// Determine the SVG filename
let modelName;
// Claude on OpenRouter or Anthropic
if (extra.api === "openai" && extra.model?.toLowerCase().includes("claude")) {
modelName = "claude";
}
// OpenAI on OpenRouter
else if (extra.api === "openai" && extra.model?.toLowerCase().includes("openai")) {
modelName = "openai";
}
// OpenRouter website model or other models
else if (extra.api === "openai" && (extra.model === null || extra.model?.toLowerCase().includes("/"))) {
modelName = "openrouter";
}
// Everything else
else {
modelName = extra.api;
}
const image = new Image();
// Add classes for styling and identification
image.classList.add('icon-svg', 'timestamp-icon');
image.src = `/img/${modelName}.svg`;
image.title = `${extra?.api ? extra.api + ' - ' : ''}${extra?.model ?? ''}`;
image.onload = async function () {
// Check if an SVG already exists adjacent to the timestamp
let existingSVG = mes.find('.timestamp').next('.timestamp-icon');
if (existingSVG.length) {
// Replace existing SVG
existingSVG.replaceWith(image);
} else {
// Append the new SVG if none exists
mes.find('.timestamp').after(image);
}
await SVGInject(this);
};
}
function getMessageFromTemplate({
mesId,
characterName,
isUser,
avatarImg,
bias,
isSystem,
title,
timerValue,
timerTitle,
bookmarkLink,
forceAvatar,
timestamp,
tokenCount,
extra,
} = {}) {
const mes = $('#message_template .mes').clone();
mes.attr({
'mesid': mesId,
'ch_name': characterName,
'is_user': isUser,
'is_system': !!isSystem,
'bookmark_link': bookmarkLink,
'force_avatar': !!forceAvatar,
'timestamp': timestamp,
});
mes.find('.avatar img').attr('src', avatarImg);
mes.find('.ch_name .name_text').text(characterName);
mes.find('.mes_bias').html(bias);
mes.find('.timestamp').text(timestamp).attr('title', `${extra?.api ? extra.api + ' - ' : ''}${extra?.model ?? ''}`);
mes.find('.mesIDDisplay').text(`#${mesId}`);
tokenCount && mes.find('.tokenCounterDisplay').text(`${tokenCount}t`);
title && mes.attr('title', title);
timerValue && mes.find('.mes_timer').attr('title', timerTitle).text(timerValue);
if (power_user.timestamp_model_icon && extra?.api) {
insertSVGIcon(mes, extra);
}
return mes;
}
export function updateMessageBlock(messageId, message) {
const messageElement = $(`#chat [mesid="${messageId}"]`);
const text = message?.extra?.display_text ?? message.mes;
messageElement.find('.mes_text').html(messageFormatting(text, message.name, message.is_system, message.is_user));
addCopyToCodeBlocks(messageElement)
appendImageToMessage(message, messageElement);
}
export function appendImageToMessage(mes, messageElement) {
if (mes.extra?.image) {
const image = messageElement.find('.mes_img');
const text = messageElement.find('.mes_text');
const isInline = !!mes.extra?.inline_image;
image.attr('src', mes.extra?.image);
image.attr('title', mes.extra?.title || mes.title || '');
messageElement.find(".mes_img_container").addClass("img_extra");
image.toggleClass("img_inline", isInline);
text.toggleClass('displayNone', !isInline);
}
}
export function addCopyToCodeBlocks(messageElement) {
const codeBlocks = $(messageElement).find("pre code");
for (let i = 0; i < codeBlocks.length; i++) {
hljs.highlightElement(codeBlocks.get(i));
if (navigator.clipboard !== undefined) {
const copyButton = document.createElement('i');
copyButton.classList.add('fa-solid', 'fa-copy', 'code-copy');
copyButton.title = 'Copy code';
codeBlocks.get(i).appendChild(copyButton);
copyButton.addEventListener('pointerup', function (event) {
navigator.clipboard.writeText(codeBlocks.get(i).innerText);
toastr.info('Copied!', '', { timeOut: 2000 });
});
}
}
}
function addOneMessage(mes, { type = "normal", insertAfter = null, scroll = true } = {}) {
var messageText = mes["mes"];
const momentDate = timestampToMoment(mes.send_date);
const timestamp = momentDate.isValid() ? momentDate.format('LL LT') : '';
if (mes?.extra?.display_text) {
messageText = mes.extra.display_text;
}
// Forbidden black magic
// This allows to use "continue" on user messages
if (type === 'swipe' && mes.swipe_id === undefined) {
mes.swipe_id = 0;
mes.swipes = [mes.mes];
}
if (mes.name === name1) {
var characterName = name1; //set to user's name by default
} else { var characterName = mes.name }
var avatarImg = getUserAvatar(user_avatar);
const isSystem = mes.is_system;
const title = mes.title;
generatedPromptCache = "";
//for non-user mesages
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);
} else {
avatarImg = default_avatar;
}
}
//old processing:
//if messge is from sytem, use the name provided in the message JSONL to proceed,
//if not system message, use name2 (char's name) to proceed
//characterName = mes.is_system || mes.force_avatar ? mes.name : name2;
} else if (mes["is_user"] && mes["force_avatar"]) {
// Special case for persona images.
avatarImg = mes["force_avatar"];
}
if (count_view_mes == 0) {
messageText = substituteParams(messageText);
}
messageText = messageFormatting(
messageText,
characterName,
isSystem,
mes.is_user,
);
const bias = messageFormatting(mes.extra?.bias ?? "");
let bookmarkLink = mes?.extra?.bookmark_link ?? '';
// Verify bookmarked chat still exists
// Cohee: Commented out for now. I'm worried of performance issues.
/*if (bookmarkLink !== '') {
let chat_names = selected_group
? getGroupChatNames(selected_group)
: Object.values(getPastCharacterChats()).map(({ file_name }) => file_name);
if (!chat_names.includes(bookmarkLink)) {
bookmarkLink = ''
}
}*/
let params = {
mesId: count_view_mes,
characterName: characterName,
isUser: mes.is_user,
avatarImg: avatarImg,
bias: bias,
isSystem: isSystem,
title: title,
bookmarkLink: bookmarkLink,
forceAvatar: mes.force_avatar,
timestamp: timestamp,
extra: mes.extra,
tokenCount: mes.extra?.token_count,
...formatGenerationTimer(mes.gen_started, mes.gen_finished, mes.extra?.token_count),
};
const HTMLForEachMes = getMessageFromTemplate(params);
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 newMessageId = type == 'swipe' ? count_view_mes - 1 : count_view_mes;
const newMessage = $(`#chat [mesid="${newMessageId}"]`);
const isSmallSys = mes?.extra?.isSmallSys;
newMessage.data("isSystem", isSystem);
if (isSystem) {
// newMessage.find(".mes_edit").hide();
newMessage.find(".mes_prompt").hide(); //don't need prompt button for sys
}
if (isSmallSys === true) {
newMessage.addClass('smallSysMes');
}
// don't need prompt button for user
if (params.isUser === true) {
newMessage.find(".mes_prompt").hide();
//console.log(`hiding prompt for user mesID ${params.mesId}`);
}
//shows or hides the Prompt display button
let mesIdToFind = type == 'swipe' ? params.mesId - 1 : params.mesId; //Number(newMessage.attr('mesId'));
//if we have itemized messages, and the array isn't null..
if (params.isUser === false && itemizedPrompts.length !== 0 && itemizedPrompts.length !== null) {
// console.log('looking through itemized prompts...');
//console.log(`mesIdToFind = ${mesIdToFind} from ${params.avatarImg}`);
//console.log(`itemizedPrompts.length = ${itemizedPrompts.length}`)
//console.log(itemizedPrompts);
for (var i = 0; i < itemizedPrompts.length; i++) {
//console.log(`itemized array item ${i} is MesID ${Number(itemizedPrompts[i].mesId)}, does it match ${Number(mesIdToFind)}?`);
if (Number(itemizedPrompts[i].mesId) === Number(mesIdToFind)) {
newMessage.find(".mes_prompt").show();
//console.log(`showing button for mesID ${params.mesId} from ${params.characterName}`);
break;
} /*else {
console.log(`no cache obj for mesID ${mesIdToFind}, hiding this prompt button`);
newMessage.find(".mes_prompt").hide();
console.log(itemizedPrompts);
} */
}
} else {
//console.log('itemizedprompt array empty null, or user, hiding this prompt buttons');
//$(".mes_prompt").hide();
newMessage.find(".mes_prompt").hide();
//console.log(itemizedPrompts);
}
newMessage.find('.avatar img').on('error', function () {
$(this).hide();
$(this).parent().html(``);
});
if (type === 'swipe') {
const swipeMessage = $("#chat").find(`[mesid="${count_view_mes - 1}"]`);
swipeMessage.find('.mes_text').html('');
swipeMessage.find('.mes_text').append(messageText);
appendImageToMessage(mes, swipeMessage);
swipeMessage.attr('title', title);
swipeMessage.find('.timestamp').text(timestamp).attr('title', `${params.extra.api} - ${params.extra.model}`);
if (power_user.timestamp_model_icon && params.extra?.api) {
insertSVGIcon(swipeMessage, params.extra);
}
if (mes.swipe_id == mes.swipes.length - 1) {
swipeMessage.find('.mes_timer').text(params.timerValue);
swipeMessage.find('.mes_timer').attr('title', params.timerTitle);
swipeMessage.find('.tokenCounterDisplay').text(`${params.tokenCount}t`);
} else {
swipeMessage.find('.mes_timer').html('');
swipeMessage.find('.tokenCounterDisplay').html('');
}
} else {
$("#chat").find(`[mesid="${count_view_mes}"]`).find('.mes_text').append(messageText);
appendImageToMessage(mes, newMessage);
hideSwipeButtons();
count_view_mes++;
}
addCopyToCodeBlocks(newMessage);
// Don't scroll if not inserting last
if (!insertAfter && scroll) {
$('#chat .mes').last().addClass('last_mes');
$('#chat .mes').eq(-2).removeClass('last_mes');
hideSwipeButtons();
showSwipeButtons();
scrollChatToBottom();
}
}
function getUserAvatar(avatarImg) {
return `User Avatars/${avatarImg}`;
}
/**
* Formats the title for the generation timer.
* @param {Date} gen_started Date when generation was started
* @param {Date} gen_finished Date when generation was finished
* @param {number} tokenCount Number of tokens generated (0 if not available)
* @returns {Object} Object containing the formatted timer value and title
* @example
* const { timerValue, timerTitle } = formatGenerationTimer(gen_started, gen_finished, tokenCount);
* console.log(timerValue); // 1.2s
* console.log(timerTitle); // Generation queued: 12:34:56 7 Jan 2021\nReply received: 12:34:57 7 Jan 2021\nTime to generate: 1.2 seconds\nToken rate: 5 t/s
*/
function formatGenerationTimer(gen_started, gen_finished, tokenCount) {
if (!gen_started || !gen_finished) {
return {};
}
const dateFormat = 'HH:mm:ss D MMM YYYY';
const start = moment(gen_started);
const finish = moment(gen_finished);
const seconds = finish.diff(start, 'seconds', true);
const timerValue = `${seconds.toFixed(1)}s`;
const timerTitle = [
`Generation queued: ${start.format(dateFormat)}`,
`Reply received: ${finish.format(dateFormat)}`,
`Time to generate: ${seconds} seconds`,
tokenCount > 0 ? `Token rate: ${Number(tokenCount / seconds).toFixed(1)} t/s` : '',
].join('\n');
return { timerValue, timerTitle };
}
function scrollChatToBottom() {
if (power_user.auto_scroll_chat_to_bottom) {
const chatElement = $("#chat");
let position = chatElement[0].scrollHeight;
if (power_user.waifuMode) {
const lastMessage = chatElement.find('.mes').last();
if (lastMessage.length) {
const lastMessagePosition = lastMessage.position().top;
position = chatElement.scrollTop() + lastMessagePosition;
}
}
chatElement.scrollTop(position);
}
}
/**
* Substitutes {{macro}} parameters in a string.
* @param {string} content - The string to substitute parameters in.
* @param {*} _name1 - The name of the user. Uses global name1 if not provided.
* @param {*} _name2 - The name of the character. Uses global name2 if not provided.
* @param {*} _original - The original message for {{original}} substitution.
* @param {*} _group - The group members list for {{group}} substitution.
* @returns {string} The string with substituted parameters.
*/
function substituteParams(content, _name1, _name2, _original, _group) {
_name1 = _name1 ?? name1;
_name2 = _name2 ?? name2;
_group = _group ?? name2;
if (!content) {
return '';
}
// Replace {{original}} with the original message
// Note: only replace the first instance of {{original}}
// This will hopefully prevent the abuse
if (typeof _original === 'string') {
content = content.replace(/{{original}}/i, _original);
}
content = content.replace(/{{input}}/gi, $('#send_textarea').val());
content = content.replace(/{{user}}/gi, _name1);
content = content.replace(/{{char}}/gi, _name2);
content = content.replace(/{{charIfNotGroup}}/gi, _group);
content = content.replace(/{{group}}/gi, _group);
content = content.replace(//gi, _name1);
content = content.replace(//gi, _name2);
content = content.replace(//gi, _group);
content = content.replace(//gi, _group);
content = content.replace(/{{time}}/gi, moment().format('LT'));
content = content.replace(/{{date}}/gi, moment().format('LL'));
content = content.replace(/{{idle_duration}}/gi, () => getTimeSinceLastMessage());
content = content.replace(/{{time_UTC([-+]\d+)}}/gi, (_, offset) => {
const utcOffset = parseInt(offset, 10);
const utcTime = moment().utc().utcOffset(utcOffset).format('LT');
return utcTime;
});
content = randomReplace(content);
content = diceRollReplace(content);
return content;
}
function getTimeSinceLastMessage() {
const now = moment();
if (Array.isArray(chat) && chat.length > 0) {
let lastMessage;
let takeNext = false;
for (let i = chat.length - 1; i >= 0; i--) {
const message = chat[i];
if (message.is_system) {
continue;
}
if (message.is_user && takeNext) {
lastMessage = message;
break;
}
takeNext = true;
}
if (lastMessage?.send_date) {
const lastMessageDate = timestampToMoment(lastMessage.send_date);
const duration = moment.duration(now.diff(lastMessageDate));
return duration.humanize();
}
}
return 'just now';
}
function randomReplace(input, emptyListPlaceholder = '') {
const randomPattern = /{{random[ : ]([^}]+)}}/gi;
return input.replace(randomPattern, (match, listString) => {
const list = listString.split(',').map(item => item.trim()).filter(item => item.length > 0);
if (list.length === 0) {
return emptyListPlaceholder;
}
var rng = new Math.seedrandom('added entropy.', { entropy: true });
const randomIndex = Math.floor(rng() * list.length);
//const randomIndex = Math.floor(Math.random() * list.length);
return list[randomIndex];
});
}
function diceRollReplace(input, invalidRollPlaceholder = '') {
const rollPattern = /{{roll[ : ]([^}]+)}}/gi;
return input.replace(rollPattern, (match, matchValue) => {
let formula = matchValue.trim();
if (isDigitsOnly(formula)) {
formula = `1d${formula}`;
}
const isValid = droll.validate(formula);
if (!isValid) {
console.debug(`Invalid roll formula: ${formula}`);
return invalidRollPlaceholder;
}
const result = droll.roll(formula);
return new String(result.total);
});
}
function getStoppingStrings(isImpersonate) {
const charString = `\n${name2}:`;
const userString = `\n${name1}:`;
const result = isImpersonate ? [charString] : [userString];
result.push(userString);
// Add other group members as the stopping strings
if (selected_group) {
const group = groups.find(x => x.id === selected_group);
if (group && Array.isArray(group.members)) {
const names = group.members
.map(x => characters.find(y => y.avatar == x))
.filter(x => x && x.name !== name2)
.map(x => `\n${x.name}:`);
result.push(...names);
}
}
result.push(...getInstructStoppingSequences());
if (power_user.custom_stopping_strings) {
const customStoppingStrings = getCustomStoppingStrings();
result.push(...customStoppingStrings);
}
return result.filter(onlyUnique);
}
// Background prompt generation
export async function generateQuietPrompt(quiet_prompt) {
return await new Promise(
async function promptPromise(resolve, reject) {
try {
await Generate('quiet', { resolve, reject, quiet_prompt, force_name2: true, });
}
catch {
reject();
}
});
}
function processCommands(message, type) {
if (type == "regenerate" || type == "swipe" || type == 'quiet') {
return null;
}
const result = executeSlashCommands(message);
$("#send_textarea").val(result.newText).trigger('input');
// interrupt generation if the input was nothing but a command
if (message.length > 0 && result.newText.length === 0) {
return true;
}
return result.interrupt;
}
function sendSystemMessage(type, text, extra = {}) {
const systemMessage = system_messages[type];
if (!systemMessage) {
return;
}
const newMessage = { ...systemMessage, send_date: getMessageTimeStamp() };
if (text) {
newMessage.mes = text;
}
if (type == system_message_types.SLASH_COMMANDS) {
newMessage.mes = getSlashCommandsHelp();
}
if (!newMessage.extra) {
newMessage.extra = {};
}
newMessage.extra = Object.assign(newMessage.extra, extra);
newMessage.extra.type = type;
chat.push(newMessage);
addOneMessage(newMessage);
is_send_press = false;
}
export function extractMessageBias(message) {
if (!message) {
return null;
}
try {
const biasHandlebars = Handlebars.create();
const biasMatches = [];
biasHandlebars.registerHelper('bias', function (text) {
biasMatches.push(text);
return '';
});
const template = biasHandlebars.compile(message);
template({});
if (biasMatches && biasMatches.length > 0) {
return ` ${biasMatches.join(" ")}`;
}
return '';
} catch {
return '';
}
}
/**
* Removes impersonated group member lines from the group member messages.
* Doesn't do anything if group reply trimming is disabled.
* @param {string} getMessage Group message
* @returns Cleaned-up group message
*/
function cleanGroupMessage(getMessage) {
if (power_user.disable_group_trimming) {
return getMessage;
}
const group = groups.find((x) => x.id == selected_group);
if (group && Array.isArray(group.members) && group.members) {
for (let member of group.members) {
const character = characters.find(x => x.avatar == member);
if (!character) {
continue;
}
const name = character.name;
// Skip current speaker.
if (name === name2) {
continue;
}
const regex = new RegExp(`(^|\n)${escapeRegex(name)}:`);
const nameMatch = getMessage.match(regex);
if (nameMatch) {
getMessage = getMessage.substring(0, nameMatch.index);
}
}
}
return getMessage;
}
function addPersonaDescriptionExtensionPrompt() {
if (!power_user.persona_description) {
return;
}
const promptPositions = [persona_description_positions.BOTTOM_AN, persona_description_positions.TOP_AN];
if (promptPositions.includes(power_user.persona_description_position) && shouldWIAddPrompt) {
const originalAN = extension_prompts[NOTE_MODULE_NAME].value
const ANWithDesc = power_user.persona_description_position === persona_description_positions.TOP_AN
? `${power_user.persona_description}\n${originalAN}`
: `${originalAN}\n${power_user.persona_description}`;
setExtensionPrompt(NOTE_MODULE_NAME, ANWithDesc, chat_metadata[metadata_keys.position], chat_metadata[metadata_keys.depth]);
}
}
function getAllExtensionPrompts() {
const value = Object
.values(extension_prompts)
.filter(x => x.value)
.map(x => x.value.trim())
.join('\n');
return value.length ? substituteParams(value) : '';
}
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;
}
if (extension_prompt.length) {
extension_prompt = substituteParams(extension_prompt);
}
return extension_prompt;
}
function baseChatReplace(value, name1, name2) {
if (value !== undefined && value.length > 0) {
value = substituteParams(value, name1, name2);
if (power_user.collapse_newlines) {
value = collapseNewlines(value);
}
value = value.replace(/\r/g, '');
}
return value;
}
function isStreamingEnabled() {
return ((main_api == 'openai' && oai_settings.stream_openai && oai_settings.chat_completion_source !== chat_completion_sources.SCALE && oai_settings.chat_completion_source !== chat_completion_sources.AI21)
|| (main_api == 'kobold' && kai_settings.streaming_kobold && kai_flags.can_use_streaming)
|| (main_api == 'novel' && nai_settings.streaming_novel)
|| (main_api == 'textgenerationwebui' && textgenerationwebui_settings.streaming))
&& !isMultigenEnabled(); // Multigen has a quasi-streaming mode which breaks the real streaming
}
function showStopButton() {
$('#mes_stop').css({ 'display': 'flex' });
}
function hideStopButton() {
$('#mes_stop').css({ 'display': 'none' });
}
class StreamingProcessor {
showMessageButtons(messageId) {
if (messageId == -1) {
return;
}
showStopButton();
$(`#chat .mes[mesid="${messageId}"] .mes_buttons`).css({ 'display': 'none' });
}
hideMessageButtons(messageId) {
if (messageId == -1) {
return;
}
hideStopButton();
$(`#chat .mes[mesid="${messageId}"] .mes_buttons`).css({ 'display': 'flex' });
}
async onStartStreaming(text) {
let messageId = -1;
if (this.type == "impersonate") {
$('#send_textarea').val('').trigger('input');
}
else {
await saveReply(this.type, text);
messageId = count_view_mes - 1;
this.showMessageButtons(messageId);
}
hideSwipeButtons();
scrollChatToBottom();
return messageId;
}
removePrefix(text) {
const name1Marker = `${name1}: `;
const name2Marker = `${name2}: `;
if (text) {
if (text.startsWith(name1Marker)) {
text = text.replace(name1Marker, '');
}
if (text.startsWith(name2Marker)) {
text = text.replace(name2Marker, '');
}
}
return text;
}
onProgressStreaming(messageId, text, isFinal) {
const isImpersonate = this.type == "impersonate";
const isContinue = this.type == "continue";
text = this.removePrefix(text);
let processedText = cleanUpMessage(text, isImpersonate, isContinue, !isFinal);
let result = extractNameFromMessage(processedText, this.force_name2, isImpersonate);
let isName = result.this_mes_is_name;
processedText = result.getMessage;
// Predict unbalanced asterisks / quotes during streaming
const charsToBalance = ['*', '"'];
for (const char of charsToBalance) {
if (!isFinal && isOdd(countOccurrences(processedText, char))) {
// Add character at the end to balance it
processedText = processedText.trimEnd() + char;
}
}
if (isImpersonate) {
$('#send_textarea').val(processedText).trigger('input');
}
else {
let currentTime = new Date();
// Don't waste time calculating token count for streaming
let currentTokenCount = isFinal && power_user.message_token_count_enabled ? getTokenCount(processedText, 0) : 0;
const timePassed = formatGenerationTimer(this.timeStarted, currentTime, currentTokenCount);
chat[messageId]['is_name'] = isName;
chat[messageId]['mes'] = processedText;
chat[messageId]['gen_started'] = this.timeStarted;
chat[messageId]['gen_finished'] = currentTime;
if (currentTokenCount) {
if (!chat[messageId]['extra']) {
chat[messageId]['extra'] = {};
}
chat[messageId]['extra']['token_count'] = currentTokenCount;
const tokenCounter = $(`#chat .mes[mesid="${messageId}"] .tokenCounterDisplay`);
tokenCounter.text(`${currentTokenCount}t`);
}
if ((this.type == 'swipe' || this.type === 'continue') && Array.isArray(chat[messageId]['swipes'])) {
chat[messageId]['swipes'][chat[messageId]['swipe_id']] = processedText;
chat[messageId]['swipe_info'][chat[messageId]['swipe_id']] = { 'send_date': chat[messageId]['send_date'], 'gen_started': chat[messageId]['gen_started'], 'gen_finished': chat[messageId]['gen_finished'], 'extra': JSON.parse(JSON.stringify(chat[messageId]['extra'])) };
}
let formattedText = messageFormatting(
processedText,
chat[messageId].name,
chat[messageId].is_system,
chat[messageId].is_user,
);
const mesText = $(`#chat .mes[mesid="${messageId}"] .mes_text`);
mesText.html(formattedText);
$(`#chat .mes[mesid="${messageId}"] .mes_timer`).text(timePassed.timerValue).attr('title', timePassed.timerTitle);
this.setFirstSwipe(messageId);
}
if (!scrollLock) {
scrollChatToBottom();
}
}
async onFinishStreaming(messageId, text) {
this.hideMessageButtons(this.messageId);
this.onProgressStreaming(messageId, text, true);
addCopyToCodeBlocks($(`#chat .mes[mesid="${messageId}"]`));
if (this.type !== 'impersonate') {
await eventSource.emit(event_types.MESSAGE_RECEIVED, this.messageId);
await eventSource.emit(event_types.CHARACTER_MESSAGE_RENDERED, this.messageId);
} else {
await eventSource.emit(event_types.IMPERSONATE_READY, text);
}
await saveChatConditional();
activateSendButtons();
showSwipeButtons();
setGenerationProgress(0);
generatedPromptCache = '';
//console.log("Generated text size:", text.length, text)
if (power_user.auto_swipe) {
function containsBlacklistedWords(str, blacklist, threshold) {
const regex = new RegExp(`\\b(${blacklist.join('|')})\\b`, 'gi');
const matches = str.match(regex) || [];
return matches.length >= threshold;
}
const generatedTextFiltered = (text) => {
if (text) {
if (power_user.auto_swipe_minimum_length) {
if (text.length < power_user.auto_swipe_minimum_length && text.length !== 0) {
console.log("Generated text size too small")
return true
}
}
if (power_user.auto_swipe_blacklist_threshold) {
if (containsBlacklistedWords(text, power_user.auto_swipe_blacklist, power_user.auto_swipe_blacklist_threshold)) {
console.log("Generated text has blacklisted words")
return true
}
}
}
return false
}
if (generatedTextFiltered(text)) {
swipe_right()
return
}
}
playMessageSound();
}
onErrorStreaming() {
this.hideMessageButtons(this.messageId);
$("#send_textarea").removeAttr('disabled');
is_send_press = false;
activateSendButtons();
setGenerationProgress(0);
showSwipeButtons();
}
setFirstSwipe(messageId) {
if (this.type !== 'swipe' && this.type !== 'impersonate') {
if (Array.isArray(chat[messageId]['swipes']) && chat[messageId]['swipes'].length === 1 && chat[messageId]['swipe_id'] === 0) {
chat[messageId]['swipes'][0] = chat[messageId]['mes'];
chat[messageId]['swipe_info'][0] = { 'send_date': chat[messageId]['send_date'], 'gen_started': chat[messageId]['gen_started'], 'gen_finished': chat[messageId]['gen_finished'], 'extra': JSON.parse(JSON.stringify(chat[messageId]['extra'])) };
}
}
}
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;
this.abortController = new AbortController();
this.firstMessageText = '...';
this.timeStarted = new Date();
}
async generate() {
if (this.messageId == -1) {
this.messageId = await this.onStartStreaming(this.firstMessageText);
await delay(1); // delay for message to be rendered
scrollLock = false;
}
try {
for await (const text of this.generator()) {
if (this.isStopped) {
this.onStopStreaming();
return;
}
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, resolve, reject, quiet_prompt, force_chid, signal } = {}, dryRun = false) {
//console.log('Generate entered');
setGenerationProgress(0);
tokens_already_generated = 0;
generation_started = new Date();
// Don't recreate abort controller if signal is passed
if (!(abortController && signal)) {
abortController = new AbortController();
}
// OpenAI doesn't need instruct mode. Use OAI main prompt instead.
const isInstruct = power_user.instruct.enabled && main_api !== 'openai';
const isImpersonate = type == "impersonate";
message_already_generated = isImpersonate ? `${name1}: ` : `${name2}: `;
// Name for the multigen prefix
const magName = isImpersonate ? name1 : name2;
if (isInstruct) {
message_already_generated = formatInstructModePrompt(magName, isImpersonate, '', name1, name2);
} else {
message_already_generated = `${magName}: `;
}
// To trim after multigen ended
const magFirst = message_already_generated;
const interruptedByCommand = processCommands($("#send_textarea").val(), type);
if (interruptedByCommand) {
$("#send_textarea").val('').trigger('input');
is_send_press = false;
return;
}
if (main_api == 'textgenerationwebui' && textgenerationwebui_settings.streaming && !textgenerationwebui_settings.streaming_url) {
toastr.error('Streaming URL is not set. Look it up in the console window when starting TextGen Web UI');
is_send_press = false;
return;
}
if (main_api == 'kobold' && kai_settings.streaming_kobold && !kai_flags.can_use_streaming) {
toastr.error('Streaming is enabled, but the version of Kobold used does not support token streaming.', undefined, { timeOut: 10000, preventDuplicates: true, });
is_send_press = false;
return;
}
if (main_api == 'kobold' && kai_settings.streaming_kobold && power_user.multigen) {
toastr.error('Multigen is not supported with Kobold streaming enabled. Disable streaming in "AI Response Configuration" or multigen in "Advanced Formatting" to proceed.', undefined, { timeOut: 10000, preventDuplicates: true, });
is_send_press = false;
return;
}
if (isHordeGenerationNotAllowed()) {
is_send_press = false;
return;
}
// Hide swipes if not in a dry run.
if (!dryRun) {
hideSwipeButtons();
}
// Set empty promise resolution functions
if (typeof resolve !== 'function') {
resolve = () => { };
}
if (typeof reject !== 'function') {
reject = () => { };
}
if (selected_group && !is_group_generating && !dryRun) {
generateGroupWrapper(false, type, { resolve, reject, quiet_prompt, force_chid, signal: abortController.signal });
return;
} else if (selected_group && !is_group_generating && dryRun) {
const characterIndexMap = new Map(characters.map((char, index) => [char.avatar, index]));
const group = groups.find((x) => x.id === selected_group);
const enabledMembers = group.members.reduce((acc, member) => {
if (!group.disabled_members.includes(member) && !acc.includes(member)) {
acc.push(member);
}
return acc;
}, []);
const memberIds = enabledMembers
.map((member) => characterIndexMap.get(member))
.filter((index) => index !== undefined && index !== null);
if (memberIds.length > 0) {
setCharacterId(memberIds[0]);
setCharacterName('');
} else {
console.log('No enabled members found');
is_send_press = false;
return;
}
}
if (quiet_prompt) {
quiet_prompt = substituteParams(quiet_prompt);
quiet_prompt = main_api == 'novel' ? adjustNovelInstructionPrompt(quiet_prompt) : quiet_prompt;
}
if (true === dryRun ||
(online_status != 'no_connection' && this_chid != undefined && this_chid !== 'invalid-safety-id')) {
let textareaText;
if (type !== 'regenerate' && type !== "swipe" && type !== 'quiet' && !isImpersonate && !dryRun) {
is_send_press = true;
textareaText = $("#send_textarea").val();
$("#send_textarea").val('').trigger('input');
} else {
textareaText = "";
if (chat.length && chat[chat.length - 1]['is_user']) {
//do nothing? why does this check exist?
}
else if (type !== 'quiet' && type !== "swipe" && !isImpersonate && !dryRun && chat.length) {
chat.length = chat.length - 1;
count_view_mes -= 1;
$('#chat').children().last().hide(250, function () {
$(this).remove();
});
await eventSource.emit(event_types.MESSAGE_DELETED, chat.length);
}
}
if (!type && !textareaText && power_user.continue_on_send && !selected_group && chat.length && !chat[chat.length - 1]['is_user'] && !chat[chat.length - 1]['is_system']) {
type = 'continue';
}
const isContinue = type == 'continue';
if (!dryRun) {
deactivateSendButtons();
}
let { messageBias, promptBias, isUserPromptBias } = getBiasStrings(textareaText, type);
//*********************************
//PRE FORMATING STRING
//*********************************
//for normal messages sent from user..
if (textareaText != "" && !automatic_trigger && type !== 'quiet') {
// If user message contains no text other than bias - send as a system message
if (messageBias && replaceBiasMarkup(textareaText).trim().length === 0) {
sendSystemMessage(system_message_types.GENERIC, ' ', { bias: messageBias });
}
else {
await sendMessageAsUser(textareaText, messageBias);
}
}
else if (textareaText == "" && !automatic_trigger && type === undefined && main_api == 'openai' && oai_settings.send_if_empty.trim().length > 0) {
// Use send_if_empty if set and the user message is empty. Only when sending messages normally
await sendMessageAsUser(oai_settings.send_if_empty.trim(), messageBias);
}
////////////////////////////////////
const scenarioText = chat_metadata['scenario'] || characters[this_chid].scenario;
let charDescription = baseChatReplace(characters[this_chid].description.trim(), name1, name2);
let charPersonality = baseChatReplace(characters[this_chid].personality.trim(), name1, name2);
let personaDescription = baseChatReplace(power_user.persona_description.trim(), name1, name2);
let Scenario = baseChatReplace(scenarioText.trim(), name1, name2);
let mesExamples = baseChatReplace(characters[this_chid].mes_example.trim(), name1, name2);
let systemPrompt = power_user.prefer_character_prompt ? baseChatReplace(characters[this_chid].data?.system_prompt?.trim(), name1, name2) : '';
let jailbreakPrompt = power_user.prefer_character_jailbreak ? baseChatReplace(characters[this_chid].data?.post_history_instructions?.trim(), name1, name2) : '';
if (isInstruct) {
systemPrompt = power_user.prefer_character_prompt && systemPrompt ? systemPrompt : baseChatReplace(power_user.instruct.system_prompt, name1, name2);
systemPrompt = formatInstructModeSystemPrompt(substituteParams(systemPrompt, name1, name2, power_user.instruct.system_prompt));
}
// Parse example messages
if (!mesExamples.startsWith('')) {
mesExamples = '\n' + mesExamples.trim();
}
if (mesExamples.replace(//gi, '').trim().length === 0) {
mesExamples = '';
}
if (mesExamples && isInstruct) {
mesExamples = formatInstructModeExamples(mesExamples, name1, name2)
}
const exampleSeparator = power_user.context.example_separator ? `${power_user.context.example_separator}\n` : '';
const blockHeading = main_api === 'openai' ? '\n' : exampleSeparator;
let mesExamplesArray = mesExamples.split(//gi).slice(1).map(block => `${blockHeading}${block.trim()}\n`);
if (power_user.strip_examples)
mesExamplesArray = []
// First message in fresh 1-on-1 chat reacts to user/character settings changes
if (chat.length) {
chat[0].mes = substituteParams(chat[0].mes);
}
// Collect messages with usable content
let coreChat = chat.filter(x => !x.is_system);
if (type === 'swipe') {
coreChat.pop();
}
// Determine token limit
let this_max_context = getMaxContextSize();
if (!dryRun) {
console.debug('Running extension interceptors');
await runGenerationInterceptors(coreChat, this_max_context);
} else {
console.debug('Skipping extension interceptors for dry run');
}
console.log(`Core/all messages: ${coreChat.length}/${chat.length}`);
// kingbri MARK: - Make sure the prompt bias isn't the same as the user bias
if ((promptBias && !isUserPromptBias) || power_user.always_force_name2) {
force_name2 = true;
}
if (isImpersonate) {
force_name2 = false;
}
//////////////////////////////////
let chat2 = [];
let continue_mag = '';
for (let i = coreChat.length - 1, j = 0; i >= 0; i--, j++) {
// For OpenAI it's only used in WI
if (main_api == 'openai' && (!world_info || world_info.length === 0)) {
console.debug('No WI, skipping chat2 for OAI');
break;
}
chat2[i] = formatMessageHistoryItem(coreChat[j], isInstruct, false);
if (j === 0 && isInstruct) {
// Reformat with the first output sequence (if any)
chat2[i] = formatMessageHistoryItem(coreChat[j], isInstruct, force_output_sequence.FIRST);
}
// Do not suffix the message for continuation
if (i === 0 && isContinue) {
if (isInstruct) {
// Reformat with the last output sequence (if any)
chat2[i] = formatMessageHistoryItem(coreChat[j], isInstruct, force_output_sequence.LAST);
}
chat2[i] = chat2[i].slice(0, chat2[i].lastIndexOf(coreChat[j].mes) + coreChat[j].mes.length);
continue_mag = coreChat[j].mes;
}
}
// Adjust token limit for Horde
let adjustedParams;
if (main_api == 'koboldhorde' && (horde_settings.auto_adjust_context_length || horde_settings.auto_adjust_response_length)) {
try {
adjustedParams = await adjustHordeGenerationParams(max_context, amount_gen);
}
catch {
activateSendButtons();
return;
}
if (horde_settings.auto_adjust_context_length) {
this_max_context = (adjustedParams.maxContextLength - adjustedParams.maxLength);
}
}
// Extension added strings
// Set non-WI AN
setFloatingPrompt();
// Add WI to prompt (and also inject WI to AN value via hijack)
let { worldInfoString, worldInfoBefore, worldInfoAfter } = await getWorldInfoPrompt(chat2, this_max_context);
// Add persona description to prompt
addPersonaDescriptionExtensionPrompt();
// Call combined AN into Generate
let allAnchors = getAllExtensionPrompts();
const afterScenarioAnchor = getExtensionPrompt(extension_prompt_types.AFTER_SCENARIO);
let zeroDepthAnchor = getExtensionPrompt(extension_prompt_types.IN_CHAT, 0, ' ');
const storyStringParams = {
description: charDescription,
personality: charPersonality,
persona: personaDescription,
scenario: Scenario,
system: isInstruct ? systemPrompt : '',
char: name2,
user: name1,
wiBefore: worldInfoBefore,
wiAfter: worldInfoAfter,
loreBefore: worldInfoBefore,
loreAfter: worldInfoAfter,
};
const storyString = renderStoryString(storyStringParams);
if (main_api === 'openai') {
message_already_generated = ''; // OpenAI doesn't have multigen
setOpenAIMessages(coreChat);
setOpenAIMessageExamples(mesExamplesArray);
}
// hack for regeneration of the first message
if (chat2.length == 0) {
chat2.push('');
}
let examplesString = '';
let chatString = '';
let cyclePrompt = '';
function getMessagesTokenCount() {
const encodeString = [
storyString,
examplesString,
chatString,
allAnchors,
quiet_prompt,
cyclePrompt,
].join('').replace(/\r/gm, '');
return getTokenCount(encodeString, power_user.token_padding);
}
// Force pinned examples into the context
let pinExmString;
if (power_user.pin_examples) {
pinExmString = examplesString = mesExamplesArray.join('');
}
if (isContinue) {
cyclePrompt = chat2.shift();
}
// Collect enough messages to fill the context
let arrMes = [];
let tokenCount = getMessagesTokenCount();
for (let item of chat2) {
// not needed for OAI prompting
if (main_api == 'openai') {
break;
}
tokenCount += getTokenCount(item.replace(/\r/gm, ''))
chatString = item + chatString;
if (tokenCount < this_max_context) {
arrMes[arrMes.length] = item;
} else {
break;
}
// Prevent UI thread lock on tokenization
await delay(1);
}
if (main_api !== 'openai') {
setInContextMessages(arrMes.length, type);
}
// Estimate how many unpinned example messages fit in the context
tokenCount = getMessagesTokenCount();
let count_exm_add = 0;
if (!power_user.pin_examples) {
for (let example of mesExamplesArray) {
tokenCount += getTokenCount(example.replace(/\r/gm, ''))
examplesString += example;
if (tokenCount < this_max_context) {
count_exm_add++;
} else {
break;
}
await delay(1);
}
}
let mesSend = [];
console.debug('calling runGenerate');
if (!dryRun) {
streamingProcessor = isStreamingEnabled() ? new StreamingProcessor(type, force_name2) : false;
}
if (isContinue) {
// Coping mechanism for OAI spacing
if ((main_api === 'openai') && !cyclePrompt.endsWith(' ')) {
cyclePrompt += ' ';
continue_mag += ' ';
}
// Save reply does add cycle text to the prompt, so it's not needed here
streamingProcessor && (streamingProcessor.firstMessageText = '');
message_already_generated = continue_mag;
tokens_already_generated = 1; // Multigen copium
}
// Multigen rewrites the type and I don't know why
const originalType = type;
runGenerate(cyclePrompt);
async function runGenerate(cycleGenerationPrompt = '') {
if (!dryRun) {
is_send_press = true;
}
generatedPromptCache += cycleGenerationPrompt;
if (generatedPromptCache.length == 0 || type === 'continue') {
if (main_api === 'openai') {
generateOpenAIPromptCache();
}
console.debug('generating prompt');
chatString = "";
arrMes = arrMes.reverse();
arrMes.forEach(function (item, i, arr) {// For added anchors and others
// OAI doesn't need all of this
if (main_api === 'openai') {
return;
}
// Cohee: I'm not even sure what this is for anymore
if (i === arrMes.length - 1 && type !== 'continue') {
item = item.replace(/\n?$/, '');
}
mesSend[mesSend.length] = { message: item, extensionPrompts: [] };
});
}
let mesExmString = '';
function setPromptString() {
if (main_api == 'openai') {
return;
}
console.debug('--setting Prompt string');
mesExmString = pinExmString ?? mesExamplesArray.slice(0, count_exm_add).join('');
if (mesSend.length) {
mesSend[mesSend.length - 1].message = modifyLastPromptLine(mesSend[mesSend.length - 1].message);
}
}
function modifyLastPromptLine(lastMesString) {
// Add quiet generation prompt at depth 0
if (quiet_prompt && quiet_prompt.length) {
const name = name1;
const quietAppend = isInstruct ? formatInstructModeChat(name, quiet_prompt, false, true, '', name1, name2, false) : `\n${name}: ${quiet_prompt}`;
lastMesString += quietAppend;
// Bail out early
return lastMesString;
}
// Get instruct mode line
if (isInstruct && tokens_already_generated === 0) {
const name = isImpersonate ? name1 : name2;
lastMesString += formatInstructModePrompt(name, isImpersonate, promptBias, name1, name2);
}
// Get non-instruct impersonation line
if (!isInstruct && isImpersonate && tokens_already_generated === 0) {
const name = name1;
if (!lastMesString.endsWith('\n')) {
lastMesString += '\n';
}
lastMesString += name + ':';
}
// Add character's name
// Force name append on continue
if (!isInstruct && force_name2 && (tokens_already_generated === 0 || isContinue)) {
if (!lastMesString.endsWith('\n')) {
lastMesString += '\n';
}
lastMesString += `${name2}:`;
}
return lastMesString;
}
// Clean up the already generated prompt for seamless addition
function cleanupPromptCache(promptCache) {
// Remove the first occurrance of character's name
if (promptCache.trimStart().startsWith(`${name2}:`)) {
promptCache = promptCache.replace(`${name2}:`, '').trimStart();
}
// Remove the first occurrance of prompt bias
if (promptCache.trimStart().startsWith(promptBias)) {
promptCache = promptCache.replace(promptBias, '');
}
// Add a space if prompt cache doesn't start with one
if (!/^\s/.test(promptCache) && !isInstruct) {
promptCache = ' ' + promptCache;
}
return promptCache;
}
function checkPromptSize() {
console.debug('---checking Prompt size');
setPromptString();
const prompt = [
storyString,
mesExmString,
mesSend.join(''),
generatedPromptCache,
allAnchors,
quiet_prompt,
].join('').replace(/\r/gm, '');
let thisPromptContextSize = getTokenCount(prompt, power_user.token_padding);
if (thisPromptContextSize > this_max_context) { //if the prepared prompt is larger than the max context size...
if (count_exm_add > 0) { // ..and we have example mesages..
count_exm_add--; // remove the example messages...
checkPromptSize(); // and try agin...
} else if (mesSend.length > 0) { // if the chat history is longer than 0
mesSend.shift(); // remove the first (oldest) chat entry..
checkPromptSize(); // and check size again..
} else {
//end
console.debug(`---mesSend.length = ${mesSend.length}`);
}
}
}
if (generatedPromptCache.length > 0 && main_api !== 'openai') {
console.debug('---Generated Prompt Cache length: ' + generatedPromptCache.length);
checkPromptSize();
} else {
console.debug('---calling setPromptString ' + generatedPromptCache.length)
setPromptString();
}
// Fetches the combined prompt for both negative and positive prompts
const cfgGuidanceScale = getGuidanceScale();
// For prompt bit itemization
let mesSendString = '';
function getCombinedPrompt(isNegative) {
// Only return if the guidance scale doesn't exist or the value is 1
// Also don't return if constructing the neutral prompt
if (isNegative && (!cfgGuidanceScale || cfgGuidanceScale?.value === 1)) {
return;
}
// OAI has its own prompt manager. No need to do anything here
if (main_api === 'openai') {
return ''
}
// Deep clone
let finalMesSend = structuredClone(mesSend);
// TODO: Rewrite getExtensionPrompt to not require multiple for loops
// Set all extension prompts where insertion depth > mesSend length
if (finalMesSend.length) {
for (let upperDepth = 100; upperDepth >= finalMesSend.length; upperDepth--) {
const upperAnchor = getExtensionPrompt(extension_prompt_types.IN_CHAT, upperDepth);
if (upperAnchor && upperAnchor.length) {
finalMesSend[0].extensionPrompts.push(upperAnchor);
}
}
}
finalMesSend.forEach((mesItem, index) => {
if (index === 0) {
return;
}
const anchorDepth = Math.abs(index - finalMesSend.length);
// NOTE: Depth injected here!
const extensionAnchor = getExtensionPrompt(extension_prompt_types.IN_CHAT, anchorDepth);
if (anchorDepth >= 0 && extensionAnchor && extensionAnchor.length) {
mesItem.extensionPrompts.push(extensionAnchor);
}
});
// TODO: Move zero-depth anchor append to work like CFG and bias appends
if (zeroDepthAnchor && zeroDepthAnchor.length) {
if (!isMultigenEnabled() || tokens_already_generated == 0) {
console.log(/\s/.test(finalMesSend[finalMesSend.length - 1].message.slice(-1)))
finalMesSend[finalMesSend.length - 1].message +=
/\s/.test(finalMesSend[finalMesSend.length - 1].message.slice(-1))
? zeroDepthAnchor
: `${zeroDepthAnchor}`;
}
}
let cfgPrompt = {};
if (cfgGuidanceScale && cfgGuidanceScale?.value !== 1) {
cfgPrompt = getCfgPrompt(cfgGuidanceScale, isNegative);
}
if (cfgPrompt && cfgPrompt?.value) {
if (cfgPrompt?.depth === 0) {
finalMesSend[finalMesSend.length - 1].message +=
/\s/.test(finalMesSend[finalMesSend.length - 1].message.slice(-1))
? cfgPrompt.value
: ` ${cfgPrompt.value}`;
} else {
// TODO: Make all extension prompts use an array/splice method
finalMesSend[mesSend.length - cfgPrompt.depth].extensionPrompts.push(`${cfgPrompt.value}\n`);
}
}
// Add prompt bias after everything else
// Always run with continue
if (!isInstruct && !isImpersonate && (tokens_already_generated === 0 || isContinue)) {
if (promptBias.trim().length !== 0) {
finalMesSend[finalMesSend.length - 1].message +=
/\s/.test(finalMesSend[finalMesSend.length - 1].message.slice(-1))
? promptBias.trimStart()
: ` ${promptBias.trimStart()}`;
}
}
// Prune from prompt cache if it exists
if (generatedPromptCache.length !== 0) {
generatedPromptCache = cleanupPromptCache(generatedPromptCache);
}
// Right now, everything is suffixed with a newline
mesSendString = finalMesSend.map((e) => `${e.extensionPrompts.join('')}${e.message}`).join('');
// add chat preamble
mesSendString = addChatsPreamble(mesSendString);
// add a custom dingus (if defined)
mesSendString = addChatsSeparator(mesSendString);
let combinedPrompt =
storyString +
afterScenarioAnchor +
mesExmString +
mesSendString +
generatedPromptCache;
combinedPrompt = combinedPrompt.replace(/\r/gm, '');
if (power_user.collapse_newlines) {
combinedPrompt = collapseNewlines(combinedPrompt);
}
return combinedPrompt;
}
// Get the negative prompt first since it has the unmodified mesSend array
let negativePrompt = main_api == 'textgenerationwebui' ? getCombinedPrompt(true) : undefined;
let finalPrompt = getCombinedPrompt(false);
// Include the entire guidance scale object
const cfgValues = cfgGuidanceScale && cfgGuidanceScale?.value !== 1 ? ({ guidanceScale: cfgGuidanceScale, negativePrompt: negativePrompt }) : null;
let this_amount_gen = Number(amount_gen); // how many tokens the AI will be requested to generate
let this_settings = koboldai_settings[koboldai_setting_names[preset_settings]];
if (isMultigenEnabled() && type !== 'quiet') {
// if nothing has been generated yet..
this_amount_gen = getMultigenAmount();
}
let thisPromptBits = [];
// TODO: Make this a switch
if (main_api == 'koboldhorde' && horde_settings.auto_adjust_response_length) {
this_amount_gen = Math.min(this_amount_gen, adjustedParams.maxLength);
this_amount_gen = Math.max(this_amount_gen, MIN_AMOUNT_GEN); // prevent validation errors
}
let generate_data;
if (main_api == 'koboldhorde' || main_api == 'kobold') {
generate_data = {
prompt: finalPrompt,
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') {
const maxContext = (adjustedParams && horde_settings.auto_adjust_context_length) ? adjustedParams.maxContextLength : max_context;
generate_data = getKoboldGenerationData(finalPrompt, this_settings, this_amount_gen, maxContext, isImpersonate, type);
}
}
else if (main_api == 'textgenerationwebui') {
generate_data = getTextGenGenerationData(finalPrompt, this_amount_gen, isImpersonate, cfgValues);
generate_data.use_mancer = api_use_mancer_webui;
}
else if (main_api == 'novel') {
const this_settings = novelai_settings[novelai_setting_names[nai_settings.preset_settings_novel]];
generate_data = getNovelGenerationData(finalPrompt, this_settings, this_amount_gen, isImpersonate, cfgValues);
}
else if (main_api == 'openai') {
let [prompt, counts] = prepareOpenAIMessages({
name2: name2,
charDescription: charDescription,
charPersonality: charPersonality,
Scenario: Scenario,
worldInfoBefore: worldInfoBefore,
worldInfoAfter: worldInfoAfter,
extensionPrompts: extension_prompts,
bias: promptBias,
type: type,
quietPrompt: quiet_prompt,
cyclePrompt: cyclePrompt,
systemPromptOverride: systemPrompt,
jailbreakPromptOverride: jailbreakPrompt,
personaDescription: personaDescription
}, dryRun);
generate_data = { prompt: prompt };
// counts will return false if the user has not enabled the token breakdown feature
if (counts) {
parseTokenCounts(counts, thisPromptBits);
}
if (!dryRun) {
setInContextMessages(openai_messages_count, type);
}
}
if (true === dryRun) return onSuccess({ error: 'dryRun' });
if (power_user.console_log_prompts) {
console.log(generate_data.prompt);
}
let generate_url = getGenerateUrl();
console.debug('rungenerate calling API');
showStopButton();
//set array object for prompt token itemization of this message
let currentArrayEntry = Number(thisPromptBits.length - 1);
let additionalPromptStuff = {
...thisPromptBits[currentArrayEntry],
rawPrompt: generate_data.prompt || generate_data.input,
mesId: getNextMessageId(type),
allAnchors: allAnchors,
summarizeString: (extension_prompts['1_memory']?.value || ''),
authorsNoteString: (extension_prompts['2_floating_prompt']?.value || ''),
smartContextString: (extension_prompts['chromadb']?.value || ''),
worldInfoString: worldInfoString,
storyString: storyString,
afterScenarioAnchor: afterScenarioAnchor,
examplesString: examplesString,
mesSendString: mesSendString,
generatedPromptCache: generatedPromptCache,
promptBias: promptBias,
finalPrompt: finalPrompt,
charDescription: charDescription,
charPersonality: charPersonality,
scenarioText: scenarioText,
this_max_context: this_max_context,
padding: power_user.token_padding,
main_api: main_api,
instruction: isInstruct ? substituteParams(power_user.prefer_character_prompt && systemPrompt ? systemPrompt : power_user.instruct.system_prompt) : '',
userPersona: (power_user.persona_description || ''),
};
thisPromptBits = additionalPromptStuff;
//console.log(thisPromptBits);
itemizedPrompts.push(thisPromptBits);
console.debug(`pushed prompt bits to itemizedPrompts array. Length is now: ${itemizedPrompts.length}`);
if (main_api == 'openai') {
if (isStreamingEnabled() && type !== 'quiet') {
streamingProcessor.generator = await sendOpenAIRequest(type, generate_data.prompt, streamingProcessor.abortController.signal);
}
else {
sendOpenAIRequest(type, generate_data.prompt, abortController.signal).then(onSuccess).catch(onError);
}
}
else if (main_api == 'koboldhorde') {
generateHorde(finalPrompt, generate_data, abortController.signal).then(onSuccess).catch(onError);
}
else if (main_api == 'textgenerationwebui' && isStreamingEnabled() && type !== 'quiet') {
streamingProcessor.generator = await generateTextGenWithStreaming(generate_data, streamingProcessor.abortController.signal);
}
else if (main_api == 'novel' && isStreamingEnabled() && type !== 'quiet') {
streamingProcessor.generator = await generateNovelWithStreaming(generate_data, streamingProcessor.abortController.signal);
}
else if (main_api == 'kobold' && isStreamingEnabled() && type !== 'quiet') {
streamingProcessor.generator = await generateKoboldWithStreaming(generate_data, streamingProcessor.abortController.signal);
}
else {
try {
const response = await fetch(generate_url, {
method: 'POST',
headers: getRequestHeaders(),
cache: 'no-cache',
body: JSON.stringify(generate_data),
signal: abortController.signal,
});
if (!response.ok) {
const error = await response.json();
throw error;
}
const data = await response.json();
onSuccess(data);
} catch (error) {
onError(error);
}
}
if (isStreamingEnabled() && type !== 'quiet') {
hideSwipeButtons();
let getMessage = await streamingProcessor.generate();
if (isContinue) {
getMessage = continue_mag + getMessage;
}
if (streamingProcessor && !streamingProcessor.isStopped && streamingProcessor.isFinished) {
await streamingProcessor.onFinishStreaming(streamingProcessor.messageId, getMessage);
streamingProcessor = null;
}
}
async function onSuccess(data) {
if (data.error == 'dryRun') {
generatedPromptCache = '';
resolve();
return;
}
if (!data.error) {
//const getData = await response.json();
let getMessage = extractMessageFromData(data);
let title = extractTitleFromData(data);
kobold_horde_model = title;
// 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() && type !== 'quiet') {
message_already_generated += getMessage;
promptBias = '';
let this_mes_is_name;
({ this_mes_is_name, getMessage } = extractNameFromMessage(getMessage, force_name2, isImpersonate));
if (!isImpersonate) {
if (tokens_already_generated == 0) {
console.debug("New message");
({ type, getMessage } = await saveReply(type, getMessage, this_mes_is_name, title));
}
else {
console.debug("Should append message");
({ type, getMessage } = await saveReply('append', getMessage, this_mes_is_name, title));
}
} else {
let chunk = cleanUpMessage(message_already_generated, true, isContinue, true);
let extract = extractNameFromMessage(chunk, force_name2, isImpersonate);
$('#send_textarea').val(extract.getMessage).trigger('input');
}
if (shouldContinueMultigen(getMessage, isImpersonate, isInstruct)) {
hideSwipeButtons();
tokens_already_generated += this_amount_gen; // add new gen amt to any prev gen counter..
getMessage = message_already_generated;
// if any tokens left to generate
if (getMultigenAmount() > 0) {
runGenerate(getMessage);
console.debug('returning to make generate again');
return;
}
}
tokens_already_generated = 0;
generatedPromptCache = "";
const substringStart = originalType !== 'continue' ? magFirst.length : 0;
getMessage = message_already_generated.substring(substringStart);
}
if (isContinue) {
getMessage = continue_mag + getMessage;
}
//Formating
const displayIncomplete = type == 'quiet';
getMessage = cleanUpMessage(getMessage, isImpersonate, isContinue, displayIncomplete);
let this_mes_is_name;
({ this_mes_is_name, getMessage } = extractNameFromMessage(getMessage, force_name2, isImpersonate));
if (getMessage.length > 0) {
if (isImpersonate) {
$('#send_textarea').val(getMessage).trigger('input');
generatedPromptCache = "";
await eventSource.emit(event_types.IMPERSONATE_READY, getMessage);
}
else if (type == 'quiet') {
resolve(getMessage);
}
else {
// Without streaming we'll be having a full message on continuation. Treat it as a multigen last chunk.
if (!isMultigenEnabled() && originalType !== 'continue') {
({ type, getMessage } = await saveReply(type, getMessage, this_mes_is_name, title));
}
else {
({ type, getMessage } = await saveReply('appendFinal', getMessage, this_mes_is_name, title));
}
}
activateSendButtons();
if (type !== 'quiet') {
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(() => {
Generate(type, { automatic_trigger, force_name2: true, resolve, reject, quiet_prompt, force_chid });
}, generate_loop_counter * 1000);
}
if (power_user.auto_swipe) {
console.debug('checking for autoswipeblacklist on non-streaming message');
function containsBlacklistedWords(getMessage, blacklist, threshold) {
console.debug('checking blacklisted words');
const regex = new RegExp(`\\b(${blacklist.join('|')})\\b`, 'gi');
const matches = getMessage.match(regex) || [];
return matches.length >= threshold;
}
const generatedTextFiltered = (getMessage) => {
if (power_user.auto_swipe_blacklist_threshold) {
if (containsBlacklistedWords(getMessage, power_user.auto_swipe_blacklist, power_user.auto_swipe_blacklist_threshold)) {
console.debug("Generated text has blacklisted words")
return true
}
}
return false
}
if (generatedTextFiltered(getMessage)) {
console.debug('swiping right automatically');
is_send_press = false;
swipe_right();
return
}
}
} else {
generatedPromptCache = '';
activateSendButtons();
//console.log('runGenerate calling showSwipeBtns');
showSwipeButtons();
if (main_api == 'textgenerationwebui' && api_use_mancer_webui) {
const errorText = `Inferencer endpoint is unhappy!
Returned status ${data.status} with the reason:
${data.response}`;
callPopup(errorText, 'text');
}
}
console.debug('/savechat called by /Generate');
await saveChatConditional();
is_send_press = false;
hideStopButton();
activateSendButtons();
showSwipeButtons();
setGenerationProgress(0);
if (type !== 'quiet') {
resolve();
}
};
function onError(exception) {
if (typeof exception?.error?.message === 'string') {
toastr.error(exception.error.message, 'Error', { timeOut: 10000, extendedTimeOut: 20000 });
}
reject(exception);
$("#send_textarea").removeAttr('disabled');
is_send_press = false;
activateSendButtons();
showSwipeButtons();
setGenerationProgress(0);
console.log(exception);
};
} //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') {
toastr.warning('Сharacter is not selected');
}
is_send_press = false;
}
//console.log('generate ending');
} //generate ends
function getNextMessageId(type) {
return type == 'swipe' ? Number(count_view_mes - 1) : Number(count_view_mes);
}
export function getBiasStrings(textareaText, type) {
if (type == 'impersonate' || type == 'continue') {
return { messageBias: '', promptBias: '', isUserPromptBias: false };
}
let promptBias = '';
let messageBias = extractMessageBias(textareaText);
// If user input is not provided, retrieve the bias of the most recent relevant message
if (!textareaText) {
for (let i = chat.length - 1; i >= 0; i--) {
const mes = chat[i];
if (type === 'swipe' && chat.length - 1 === i) {
continue;
}
if (mes && (mes.is_user || mes.is_system || mes.extra?.type === system_message_types.NARRATOR)) {
if (mes.extra?.bias?.trim()?.length > 0) {
promptBias = mes.extra.bias;
}
break;
}
}
}
promptBias = messageBias || promptBias || power_user.user_prompt_bias || '';
const isUserPromptBias = promptBias === power_user.user_prompt_bias;
// Substitute params for everything
messageBias = substituteParams(messageBias);
promptBias = substituteParams(promptBias);
return { messageBias, promptBias, isUserPromptBias };
}
/**
* @param {Object} chatItem Message history item.
* @param {boolean} isInstruct Whether instruct mode is enabled.
* @param {boolean|number} forceOutputSequence Whether to force the first/last output sequence for instruct mode.
*/
function formatMessageHistoryItem(chatItem, isInstruct, forceOutputSequence) {
const isNarratorType = chatItem?.extra?.type === system_message_types.NARRATOR;
const characterName = (selected_group || chatItem.force_avatar) ? chatItem.name : name2;
const itemName = chatItem.is_user ? chatItem['name'] : characterName;
const shouldPrependName = (chatItem.is_name || chatItem.force_avatar || selected_group) && !isNarratorType;
let textResult = shouldPrependName ? `${itemName}: ${chatItem.mes}\n` : `${chatItem.mes}\n`;
if (isInstruct) {
textResult = formatInstructModeChat(itemName, chatItem.mes, chatItem.is_user, isNarratorType, chatItem.force_avatar, name1, name2, forceOutputSequence);
}
textResult = replaceBiasMarkup(textResult);
return textResult;
}
export function replaceBiasMarkup(str) {
return (str ?? '').replace(/\{\{[\s\S]*?\}\}/gm, '');
}
export async function sendMessageAsUser(textareaText, messageBias) {
textareaText = getRegexedString(textareaText, regex_placement.USER_INPUT);
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'] = getMessageTimeStamp();
chat[chat.length - 1]['mes'] = substituteParams(textareaText);
chat[chat.length - 1]['extra'] = {};
if (power_user.message_token_count_enabled) {
chat[chat.length - 1]['extra']['token_count'] = getTokenCount(chat[chat.length - 1]['mes'], 0);
}
// Lock user avatar to a persona.
if (user_avatar in power_user.personas) {
chat[chat.length - 1]['force_avatar'] = getUserAvatar(user_avatar);
}
if (messageBias) {
console.debug('checking bias');
chat[chat.length - 1]['extra']['bias'] = messageBias;
}
statMesProcess(chat[chat.length - 1], 'user', characters, this_chid, '');
// Wait for all handlers to finish before continuing with the prompt
await eventSource.emit(event_types.MESSAGE_SENT, (chat.length - 1));
addOneMessage(chat[chat.length - 1]);
await eventSource.emit(event_types.USER_MESSAGE_RENDERED, (chat.length - 1));
console.debug('message sent as user');
}
function getMaxContextSize() {
let this_max_context = 1487;
if (main_api == 'kobold' || main_api == 'koboldhorde' || main_api == 'textgenerationwebui') {
this_max_context = (max_context - amount_gen);
}
if (main_api == 'novel') {
this_max_context = Number(max_context);
if (nai_settings.model_novel.includes('clio')) {
this_max_context = Math.min(max_context, 8192);
}
if (nai_settings.model_novel.includes('kayra')) {
this_max_context = Math.min(max_context, 8192);
const subscriptionLimit = getKayraMaxContextTokens();
if (typeof subscriptionLimit === "number" && this_max_context > subscriptionLimit) {
this_max_context = subscriptionLimit;
console.log(`NovelAI subscription limit reached. Max context size is now ${this_max_context}`);
}
}
this_max_context = this_max_context - amount_gen;
}
if (main_api == 'openai') {
this_max_context = oai_settings.openai_max_context;
}
return this_max_context;
}
function parseTokenCounts(counts, thisPromptBits) {
/**
* @param {any[]} numbers
*/
function getSum(...numbers) {
return numbers.map(x => Number(x)).filter(x => !Number.isNaN(x)).reduce((acc, val) => acc + val, 0);
}
const total = getSum(Object.values(counts));
thisPromptBits.push({
oaiStartTokens: (counts?.start + counts?.controlPrompts) || 0,
oaiPromptTokens: getSum(counts?.prompt, counts?.charDescription, counts?.charPersonality, counts?.scenario) || 0,
oaiBiasTokens: counts?.bias || 0,
oaiNudgeTokens: counts?.nudge || 0,
oaiJailbreakTokens: counts?.jailbreak || 0,
oaiImpersonateTokens: counts?.impersonate || 0,
oaiExamplesTokens: (counts?.dialogueExamples + counts?.examples) || 0,
oaiConversationTokens: (counts?.conversation + counts?.chatHistory) || 0,
oaiNsfwTokens: counts?.nsfw || 0,
oaiMainTokens: counts?.main || 0,
oaiTotalTokens: total,
});
}
function addChatsPreamble(mesSendString) {
return main_api === 'novel'
? nai_settings.preamble + '\n' + mesSendString
: mesSendString;
}
function addChatsSeparator(mesSendString) {
if (power_user.context.chat_start) {
return power_user.context.chat_start + '\n' + mesSendString;
}
else {
return mesSendString;
}
}
function appendZeroDepthAnchor(force_name2, zeroDepthAnchor, finalPrompt) {
const trimBothEnds = !force_name2;
let trimmedPrompt = (trimBothEnds ? zeroDepthAnchor.trim() : zeroDepthAnchor.trimEnd());
if (trimBothEnds && !finalPrompt.endsWith('\n')) {
finalPrompt += '\n';
}
finalPrompt += trimmedPrompt;
if (force_name2) {
finalPrompt += ' ';
}
return finalPrompt;
}
function getMultigenAmount() {
let this_amount_gen = Number(amount_gen);
if (tokens_already_generated === 0) {
// if the max gen setting is > 50...(
if (Number(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 = Number(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 (Number(amount_gen) - tokens_already_generated < power_user.multigen_next_chunks) {
// subtract already generated amount from the desired max gen amount
this_amount_gen = Number(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;
}
}
return this_amount_gen;
}
async function DupeChar() {
if (!this_chid) {
toastr.warning('You must first select a character to duplicate!')
return;
}
const confirm = await callPopup(`
Are you sure you want to duplicate this character?
If you just want to start a new chat with the same character, use "Start new chat" option in the bottom-left options menu.
`,
'confirm',
);
if (!confirm) {
console.log('User cancelled duplication');
return;
}
const body = { avatar_url: characters[this_chid].avatar };
const response = await fetch('/dupecharacter', {
method: 'POST',
headers: getRequestHeaders(),
body: JSON.stringify(body),
});
if (response.ok) {
toastr.success("Character Duplicated");
getCharacters();
}
}
function promptItemize(itemizedPrompts, requestedMesId) {
console.log('PROMPT ITEMIZE ENTERED');
var incomingMesId = Number(requestedMesId);
console.debug(`looking for MesId ${incomingMesId}`);
var thisPromptSet = undefined;
for (var i = 0; i < itemizedPrompts.length; i++) {
console.log(`looking for ${incomingMesId} vs ${itemizedPrompts[i].mesId}`)
if (itemizedPrompts[i].mesId === incomingMesId) {
console.log(`found matching mesID ${i}`);
thisPromptSet = i;
PromptArrayItemForRawPromptDisplay = i;
console.log(`wanting to raw display of ArrayItem: ${PromptArrayItemForRawPromptDisplay} which is mesID ${incomingMesId}`);
console.log(itemizedPrompts[thisPromptSet]);
}
}
if (thisPromptSet === undefined) {
console.log(`couldnt find the right mesId. looked for ${incomingMesId}`);
console.log(itemizedPrompts);
return null;
}
//these happen regardless of API
var charDescriptionTokens = getTokenCount(itemizedPrompts[thisPromptSet].charDescription);
var charPersonalityTokens = getTokenCount(itemizedPrompts[thisPromptSet].charPersonality);
var scenarioTextTokens = getTokenCount(itemizedPrompts[thisPromptSet].scenarioText);
var userPersonaStringTokens = getTokenCount(itemizedPrompts[thisPromptSet].userPersona);
var worldInfoStringTokens = getTokenCount(itemizedPrompts[thisPromptSet].worldInfoString);
var allAnchorsTokens = getTokenCount(itemizedPrompts[thisPromptSet].allAnchors);
var summarizeStringTokens = getTokenCount(itemizedPrompts[thisPromptSet].summarizeString);
var authorsNoteStringTokens = getTokenCount(itemizedPrompts[thisPromptSet].authorsNoteString);
var smartContextStringTokens = getTokenCount(itemizedPrompts[thisPromptSet].smartContextString);
var afterScenarioAnchorTokens = getTokenCount(itemizedPrompts[thisPromptSet].afterScenarioAnchor);
var zeroDepthAnchorTokens = getTokenCount(itemizedPrompts[thisPromptSet].zeroDepthAnchor);
var thisPrompt_max_context = itemizedPrompts[thisPromptSet].this_max_context;
var thisPrompt_padding = itemizedPrompts[thisPromptSet].padding;
var this_main_api = itemizedPrompts[thisPromptSet].main_api;
if (this_main_api == 'openai') {
//for OAI API
//console.log('-- Counting OAI Tokens');
//var finalPromptTokens = itemizedPrompts[thisPromptSet].oaiTotalTokens;
var oaiMainTokens = itemizedPrompts[thisPromptSet].oaiMainTokens;
var oaiStartTokens = itemizedPrompts[thisPromptSet].oaiStartTokens;
var ActualChatHistoryTokens = itemizedPrompts[thisPromptSet].oaiConversationTokens;
var examplesStringTokens = itemizedPrompts[thisPromptSet].oaiExamplesTokens;
var oaiPromptTokens = itemizedPrompts[thisPromptSet].oaiPromptTokens - afterScenarioAnchorTokens + examplesStringTokens;
var oaiBiasTokens = itemizedPrompts[thisPromptSet].oaiBiasTokens;
var oaiJailbreakTokens = itemizedPrompts[thisPromptSet].oaiJailbreakTokens;
var oaiNudgeTokens = itemizedPrompts[thisPromptSet].oaiNudgeTokens;
var oaiImpersonateTokens = itemizedPrompts[thisPromptSet].oaiImpersonateTokens;
var oaiNsfwTokens = itemizedPrompts[thisPromptSet].oaiNsfwTokens;
var finalPromptTokens =
oaiStartTokens +
oaiPromptTokens +
oaiMainTokens +
oaiNsfwTokens +
oaiBiasTokens +
oaiImpersonateTokens +
oaiJailbreakTokens +
oaiNudgeTokens +
ActualChatHistoryTokens +
//charDescriptionTokens +
//charPersonalityTokens +
//allAnchorsTokens +
worldInfoStringTokens +
afterScenarioAnchorTokens;
// OAI doesn't use padding
thisPrompt_padding = 0;
// Max context size - max completion tokens
thisPrompt_max_context = (oai_settings.openai_max_context - oai_settings.openai_max_tokens);
} else {
//for non-OAI APIs
//console.log('-- Counting non-OAI Tokens');
var finalPromptTokens = getTokenCount(itemizedPrompts[thisPromptSet].finalPrompt);
var storyStringTokens = getTokenCount(itemizedPrompts[thisPromptSet].storyString) - worldInfoStringTokens;
var examplesStringTokens = getTokenCount(itemizedPrompts[thisPromptSet].examplesString);
var mesSendStringTokens = getTokenCount(itemizedPrompts[thisPromptSet].mesSendString)
var ActualChatHistoryTokens = mesSendStringTokens - (allAnchorsTokens - afterScenarioAnchorTokens) + power_user.token_padding;
var instructionTokens = getTokenCount(itemizedPrompts[thisPromptSet].instruction);
var promptBiasTokens = getTokenCount(itemizedPrompts[thisPromptSet].promptBias);
var totalTokensInPrompt =
storyStringTokens + //chardefs total
worldInfoStringTokens +
examplesStringTokens + // example messages
ActualChatHistoryTokens + //chat history
allAnchorsTokens + // AN and/or legacy anchors
//afterScenarioAnchorTokens + //only counts if AN is set to 'after scenario'
//zeroDepthAnchorTokens + //same as above, even if AN not on 0 depth
promptBiasTokens; //{{}}
//- thisPrompt_padding; //not sure this way of calculating is correct, but the math results in same value as 'finalPrompt'
}
if (this_main_api == 'openai') {
//console.log('-- applying % on OAI tokens');
var oaiStartTokensPercentage = ((oaiStartTokens / (finalPromptTokens)) * 100).toFixed(2);
var storyStringTokensPercentage = (((afterScenarioAnchorTokens + oaiPromptTokens) / (finalPromptTokens)) * 100).toFixed(2);
var ActualChatHistoryTokensPercentage = ((ActualChatHistoryTokens / (finalPromptTokens)) * 100).toFixed(2);
var promptBiasTokensPercentage = ((oaiBiasTokens / (finalPromptTokens)) * 100).toFixed(2);
var worldInfoStringTokensPercentage = ((worldInfoStringTokens / (finalPromptTokens)) * 100).toFixed(2);
var allAnchorsTokensPercentage = ((allAnchorsTokens / (finalPromptTokens)) * 100).toFixed(2);
var selectedTokenizer = `tiktoken (${getTokenizerModel()})`;
var oaiSystemTokens = oaiImpersonateTokens + oaiJailbreakTokens + oaiNudgeTokens + oaiStartTokens + oaiNsfwTokens + oaiMainTokens;
var oaiSystemTokensPercentage = ((oaiSystemTokens / (finalPromptTokens)) * 100).toFixed(2);
} else {
//console.log('-- applying % on non-OAI tokens');
var storyStringTokensPercentage = ((storyStringTokens / (totalTokensInPrompt)) * 100).toFixed(2);
var ActualChatHistoryTokensPercentage = ((ActualChatHistoryTokens / (totalTokensInPrompt)) * 100).toFixed(2);
var promptBiasTokensPercentage = ((promptBiasTokens / (totalTokensInPrompt)) * 100).toFixed(2);
var worldInfoStringTokensPercentage = ((worldInfoStringTokens / (totalTokensInPrompt)) * 100).toFixed(2);
var allAnchorsTokensPercentage = ((allAnchorsTokens / (totalTokensInPrompt)) * 100).toFixed(2);
var selectedTokenizer = $("#tokenizer").find(':selected').text();
}
const params = {
selectedTokenizer,
this_main_api,
storyStringTokensPercentage,
worldInfoStringTokensPercentage,
ActualChatHistoryTokensPercentage,
allAnchorsTokensPercentage,
promptBiasTokensPercentage,
storyStringTokens,
charDescriptionTokens,
charPersonalityTokens,
scenarioTextTokens,
examplesStringTokens,
userPersonaStringTokens,
instructionTokens,
worldInfoStringTokens,
ActualChatHistoryTokens,
allAnchorsTokens,
summarizeStringTokens,
authorsNoteStringTokens,
smartContextStringTokens,
promptBiasTokens,
totalTokensInPrompt,
finalPromptTokens,
thisPrompt_max_context,
thisPrompt_padding,
thisPrompt_actual: thisPrompt_max_context - thisPrompt_padding,
oaiSystemTokensPercentage,
oaiStartTokensPercentage,
oaiSystemTokens,
oaiStartTokens,
oaiJailbreakTokens,
oaiNudgeTokens,
oaiImpersonateTokens,
oaiPromptTokens,
oaiBiasTokens,
oaiNsfwTokens,
oaiMainTokens,
};
if (this_main_api == 'openai') {
callPopup(renderTemplate('itemizationChat', params), 'text');
} else {
callPopup(renderTemplate('itemizationText', params), 'text');
}
}
function setInContextMessages(lastmsg, type) {
$("#chat .mes").removeClass('lastInContext');
if (type === 'swipe' || type === 'regenerate' || type === 'continue') {
lastmsg++;
}
$('#chat .mes:not([is_system="true"])').eq(-lastmsg).addClass('lastInContext');
}
function getGenerateUrl() {
let 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';
}
return generate_url;
}
function shouldContinueMultigen(getMessage, isImpersonate, isInstruct) {
if (isInstruct && power_user.instruct.stop_sequence) {
if (message_already_generated.indexOf(power_user.instruct.stop_sequence) !== -1) {
return false;
}
}
// stopping name string
const nameString = isImpersonate ? `${name2}:` : `${name1}:`;
// if there is no 'You:' in the response msg
const doesNotContainName = message_already_generated.indexOf(nameString) === -1;
//if there is no stamp in the response msg
const isNotEndOfText = message_already_generated.indexOf('<|endoftext|>') === -1;
//if the gen'd msg is less than the max response length..
const notReachedMax = tokens_already_generated < Number(amount_gen);
//if we actually have gen'd text at all...
const msgHasText = getMessage.length > 0;
return doesNotContainName && isNotEndOfText && notReachedMax && msgHasText;
}
function extractNameFromMessage(getMessage, force_name2, isImpersonate) {
const nameToTrim = isImpersonate ? name1 : name2;
let this_mes_is_name = true;
if (getMessage.startsWith(nameToTrim + ":")) {
getMessage = getMessage.replace(nameToTrim + ':', '');
getMessage = getMessage.trimStart();
} else {
this_mes_is_name = false;
}
if (force_name2 || power_user.instruct.enabled)
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();
throw new Error('Generate circuit breaker interruption');
}
function extractTitleFromData(data) {
if (main_api == 'koboldhorde') {
return data.workerName;
}
return undefined;
}
function extractMessageFromData(data) {
switch (main_api) {
case 'kobold':
return data.results[0].text;
case 'koboldhorde':
return data.text;
case 'textgenerationwebui':
return data.results[0].text;
case 'novel':
return data.output;
case 'openai':
return data;
default:
return ''
}
}
function cleanUpMessage(getMessage, isImpersonate, isContinue, displayIncompleteSentences = false) {
// Add the prompt bias before anything else
if (
power_user.user_prompt_bias &&
!isImpersonate &&
!isContinue &&
power_user.user_prompt_bias.length !== 0
) {
getMessage = substituteParams(power_user.user_prompt_bias) + getMessage;
}
// Regex uses vars, so add before formatting
getMessage = getRegexedString(getMessage, isImpersonate ? regex_placement.USER_INPUT : regex_placement.AI_OUTPUT);
if (!displayIncompleteSentences && power_user.trim_sentences) {
getMessage = end_trim_to_sentence(getMessage, power_user.include_newline);
}
if (power_user.collapse_newlines) {
getMessage = collapseNewlines(getMessage);
}
if (power_user.trim_spaces) {
getMessage = getMessage.trim();
}
// trailing invisible whitespace before every newlines, on a multiline string
// "trailing whitespace on newlines \nevery line of the string \n?sample text" ->
// "trailing whitespace on newlines\nevery line of the string\nsample text"
getMessage = getMessage.replace(/[^\S\r\n]+$/gm, "");
let nameToTrim = isImpersonate ? name2 : name1;
if (isImpersonate) {
nameToTrim = power_user.allow_name2_display ? '' : name2;
}
else {
nameToTrim = power_user.allow_name1_display ? '' : name1;
}
if (nameToTrim && getMessage.indexOf(`${nameToTrim}:`) == 0) {
getMessage = getMessage.substr(0, getMessage.indexOf(`${nameToTrim}:`));
}
if (nameToTrim && getMessage.indexOf(`\n${nameToTrim}:`) > 0) {
getMessage = getMessage.substr(0, getMessage.indexOf(`\n${nameToTrim}:`));
}
if (getMessage.indexOf('<|endoftext|>') != -1) {
getMessage = getMessage.substr(0, getMessage.indexOf('<|endoftext|>'));
}
const isInstruct = power_user.instruct.enabled && main_api !== 'openai';
if (isInstruct && power_user.instruct.stop_sequence) {
if (getMessage.indexOf(power_user.instruct.stop_sequence) != -1) {
getMessage = getMessage.substring(0, getMessage.indexOf(power_user.instruct.stop_sequence));
}
}
// Hana: Only use the first sequence (should be <|model|>)
// of the prompt before <|user|> (as KoboldAI Lite does it).
if (isInstruct && power_user.instruct.input_sequence) {
if (getMessage.indexOf(power_user.instruct.input_sequence) != -1) {
getMessage = getMessage.substring(0, getMessage.indexOf(power_user.instruct.input_sequence));
}
}
if (isInstruct && power_user.instruct.input_sequence && isImpersonate) {
//getMessage = getMessage.replaceAll(power_user.instruct.input_sequence, '');
power_user.instruct.input_sequence.split('\n')
.filter(line => line.trim() !== '')
.forEach(line => {
getMessage = getMessage.replaceAll(line, '');
});
}
if (isInstruct && power_user.instruct.output_sequence && !isImpersonate) {
//getMessage = getMessage.replaceAll(power_user.instruct.output_sequence, '');
power_user.instruct.output_sequence.split('\n')
.filter(line => line.trim() !== '')
.forEach(line => {
getMessage = getMessage.replaceAll(line, '');
});
}
if (isInstruct && power_user.instruct.last_output_sequence && !isImpersonate) {
//getMessage = getMessage.replaceAll(power_user.instruct.last_output_sequence, '');
power_user.instruct.last_output_sequence.split('\n')
.filter(line => line.trim() !== '')
.forEach(line => {
getMessage = getMessage.replaceAll(line, '');
});
}
// clean-up group message from excessive generations
if (selected_group) {
getMessage = cleanGroupMessage(getMessage);
}
if (!power_user.allow_name2_display) {
getMessage = getMessage.replace(new RegExp(`(^|\n)${name2}:`, 'g'), "$1");
}
if (isImpersonate) {
getMessage = getMessage.trim();
}
const stoppingStrings = getStoppingStrings(isImpersonate);
for (const stoppingString of stoppingStrings) {
if (stoppingString.length) {
for (let j = stoppingString.length - 1; j > 0; j--) {
if (getMessage.slice(-j) === stoppingString.slice(0, j)) {
getMessage = getMessage.slice(0, -j);
break;
}
}
}
}
if (power_user.auto_fix_generated_markdown) {
getMessage = fixMarkdown(getMessage, false);
}
return getMessage;
}
async function saveReply(type, getMessage, this_mes_is_name, title) {
if (type != 'append' && type != 'continue' && type != 'appendFinal' && chat.length && (chat[chat.length - 1]['swipe_id'] === undefined ||
chat[chat.length - 1]['is_user'])) {
type = 'normal';
}
if (chat.length && typeof chat[chat.length - 1]['extra'] !== 'object') {
chat[chat.length - 1]['extra'] = {};
}
let oldMessage = ''
const generationFinished = new Date();
const img = extractImageFromMessage(getMessage);
getMessage = img.getMessage;
if (type === 'swipe') {
oldMessage = chat[chat.length - 1]['mes'];
chat[chat.length - 1]['swipes'].length++;
if (chat[chat.length - 1]['swipe_id'] === chat[chat.length - 1]['swipes'].length - 1) {
chat[chat.length - 1]['title'] = title;
chat[chat.length - 1]['mes'] = getMessage;
chat[chat.length - 1]['gen_started'] = generation_started;
chat[chat.length - 1]['gen_finished'] = generationFinished;
chat[chat.length - 1]['send_date'] = getMessageTimeStamp();
chat[chat.length - 1]['extra']['api'] = getGeneratingApi();
chat[chat.length - 1]['extra']['model'] = getGeneratingModel();
if (power_user.message_token_count_enabled) {
chat[chat.length - 1]['extra']['token_count'] = getTokenCount(chat[chat.length - 1]['mes'], 0);
}
await eventSource.emit(event_types.MESSAGE_RECEIVED, (chat.length - 1));
addOneMessage(chat[chat.length - 1], { type: 'swipe' });
await eventSource.emit(event_types.CHARACTER_MESSAGE_RENDERED, (chat.length - 1));
} else {
chat[chat.length - 1]['mes'] = getMessage;
}
} else if (type === 'append' || type === 'continue') {
console.debug("Trying to append.")
oldMessage = chat[chat.length - 1]['mes'];
chat[chat.length - 1]['title'] = title;
chat[chat.length - 1]['mes'] += getMessage;
chat[chat.length - 1]['gen_started'] = generation_started;
chat[chat.length - 1]['gen_finished'] = generationFinished;
chat[chat.length - 1]['send_date'] = getMessageTimeStamp();
chat[chat.length - 1]["extra"]["api"] = getGeneratingApi();
chat[chat.length - 1]["extra"]["model"] = getGeneratingModel();
if (power_user.message_token_count_enabled) {
chat[chat.length - 1]['extra']['token_count'] = getTokenCount(chat[chat.length - 1]['mes'], 0);
}
await eventSource.emit(event_types.MESSAGE_RECEIVED, (chat.length - 1));
addOneMessage(chat[chat.length - 1], { type: 'swipe' });
await eventSource.emit(event_types.CHARACTER_MESSAGE_RENDERED, (chat.length - 1));
} else if (type === 'appendFinal') {
oldMessage = chat[chat.length - 1]['mes'];
console.debug("Trying to appendFinal.")
chat[chat.length - 1]['title'] = title;
chat[chat.length - 1]['mes'] = getMessage;
chat[chat.length - 1]['gen_started'] = generation_started;
chat[chat.length - 1]['gen_finished'] = generationFinished;
chat[chat.length - 1]['send_date'] = getMessageTimeStamp();
chat[chat.length - 1]["extra"]["api"] = getGeneratingApi();
chat[chat.length - 1]["extra"]["model"] = getGeneratingModel();
if (power_user.message_token_count_enabled) {
chat[chat.length - 1]['extra']['token_count'] = getTokenCount(chat[chat.length - 1]['mes'], 0);
}
await eventSource.emit(event_types.MESSAGE_RECEIVED, (chat.length - 1));
addOneMessage(chat[chat.length - 1], { type: 'swipe' });
await eventSource.emit(event_types.CHARACTER_MESSAGE_RENDERED, (chat.length - 1));
} else {
console.debug('entering chat update routine for non-swipe post');
chat[chat.length] = {};
chat[chat.length - 1]['extra'] = {};
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'] = getMessageTimeStamp();
chat[chat.length - 1]["extra"]["api"] = getGeneratingApi();
chat[chat.length - 1]["extra"]["model"] = getGeneratingModel();
if (power_user.trim_spaces) {
getMessage = getMessage.trim();
}
chat[chat.length - 1]['mes'] = getMessage;
chat[chat.length - 1]['title'] = title;
chat[chat.length - 1]['gen_started'] = generation_started;
chat[chat.length - 1]['gen_finished'] = generationFinished;
if (power_user.message_token_count_enabled) {
chat[chat.length - 1]['extra']['token_count'] = getTokenCount(chat[chat.length - 1]['mes'], 0);
}
if (selected_group) {
console.debug('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;
chat[chat.length - 1]['original_avatar'] = characters[this_chid].avatar;
chat[chat.length - 1]['extra']['gen_id'] = group_generation_id;
}
saveImageToMessage(img, chat[chat.length - 1]);
await eventSource.emit(event_types.MESSAGE_RECEIVED, (chat.length - 1));
addOneMessage(chat[chat.length - 1]);
await eventSource.emit(event_types.CHARACTER_MESSAGE_RENDERED, (chat.length - 1));
}
const item = chat[chat.length - 1];
if (item["swipe_info"] === undefined) {
item["swipe_info"] = [];
}
if (item["swipe_id"] !== undefined) {
const swipeId = item["swipe_id"];
item["swipes"][swipeId] = item["mes"];
item["swipe_info"][swipeId] = {
send_date: item["send_date"],
gen_started: item["gen_started"],
gen_finished: item["gen_finished"],
extra: JSON.parse(JSON.stringify(item["extra"])),
};
} else {
item["swipe_id"] = 0;
item["swipes"] = [];
item["swipes"][0] = chat[chat.length - 1]["mes"];
item["swipe_info"][0] = {
send_date: chat[chat.length - 1]["send_date"],
gen_started: chat[chat.length - 1]["gen_started"],
gen_finished: chat[chat.length - 1]["gen_finished"],
extra: JSON.parse(JSON.stringify(chat[chat.length - 1]["extra"])),
};
}
statMesProcess(chat[chat.length - 1], type, characters, this_chid, oldMessage);
return { type, getMessage };
}
function saveImageToMessage(img, mes) {
if (mes && img.image) {
if (typeof mes.extra !== 'object') {
mes.extra = {};
}
mes.extra.image = img.image;
mes.extra.title = img.title;
}
}
function getGeneratingApi() {
switch (main_api) {
case 'openai':
return oai_settings.chat_completion_source || 'openai';
default:
return main_api;
}
}
function getGeneratingModel(mes) {
let model = '';
switch (main_api) {
case 'kobold':
model = online_status;
break;
case 'novel':
model = nai_settings.model_novel;
break;
case 'openai':
model = getChatCompletionModel();
break;
case 'textgenerationwebui':
model = online_status;
break;
case 'koboldhorde':
model = kobold_horde_model;
break;
}
return model
}
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 };
}
export function isMultigenEnabled() {
return power_user.multigen && (main_api == 'textgenerationwebui' || main_api == 'kobold' || main_api == 'koboldhorde' || main_api == 'novel');
}
export function activateSendButtons() {
is_send_press = false;
$("#send_but").removeClass("displayNone");
$("#mes_continue").removeClass("displayNone");
$("#send_textarea").attr("disabled", false);
$('.mes_buttons:last').show();
hideStopButton();
}
export function deactivateSendButtons() {
$("#send_but").addClass("displayNone");
$("#mes_continue").addClass("displayNone");
showStopButton();
}
function resetChatState() {
//unsets expected chid before reloading (related to getCharacters/printCharacters from using old arrays)
this_chid = "invalid-safety-id";
// replaces deleted charcter name with system user since it will be displayed next.
name2 = systemUserName;
// sets up system user to tell user about having deleted a character
chat = [...safetychat];
// resets chat metadata
chat_metadata = {};
// resets the characters array, forcing getcharacters to reset
characters.length = 0;
}
export function setMenuType(value) {
menu_type = value;
}
export function setExternalAbortController(controller) {
abortController = controller;
}
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 renameCharacter() {
const oldAvatar = characters[this_chid].avatar;
const newValue = await callPopup('New name:
', 'input', characters[this_chid].name);
if (newValue && newValue !== characters[this_chid].name) {
const body = JSON.stringify({ avatar_url: oldAvatar, new_name: newValue });
const response = await fetch('/renamecharacter', {
method: 'POST',
headers: getRequestHeaders(),
body,
});
try {
if (response.ok) {
const data = await response.json();
const newAvatar = data.avatar;
// Replace tags list
renameTagKey(oldAvatar, newAvatar);
// Reload characters list
await getCharacters();
// Find newly renamed character
const newChId = characters.findIndex(c => c.avatar == data.avatar);
if (newChId !== -1) {
// Select the character after the renaming
this_chid = -1;
selectCharacterById(String(newChId));
// Async delay to update UI
await delay(1);
if (this_chid === -1) {
throw new Error('New character not selected');
}
// Also rename as a group member
await renameGroupMember(oldAvatar, newAvatar, newValue);
const renamePastChatsConfirm = await callPopup(`Character renamed!
Past chats will still contain the old character name. Would you like to update the character name in previous chats as well?
Sprites folder (if any) should be renamed manually.`, 'confirm');
if (renamePastChatsConfirm) {
await renamePastChats(newAvatar, newValue);
await reloadCurrentChat();
toastr.success('Character renamed and past chats updated!');
}
}
else {
throw new Error('Newly renamed character was lost?');
}
}
else {
throw new Error('Could not rename the character');
}
}
catch {
// Reloading to prevent data corruption
await callPopup('Something went wrong. The page will be reloaded.', 'text');
location.reload();
}
}
}
async function renamePastChats(newAvatar, newValue) {
const pastChats = await getPastCharacterChats();
for (const { file_name } of pastChats) {
try {
const fileNameWithoutExtension = file_name.replace('.jsonl', '');
const getChatResponse = await fetch('/getchat', {
method: 'POST',
headers: getRequestHeaders(),
body: JSON.stringify({
ch_name: newValue,
file_name: fileNameWithoutExtension,
avatar_url: newAvatar,
}),
cache: 'no-cache',
});
if (getChatResponse.ok) {
const currentChat = await getChatResponse.json();
for (const message of currentChat) {
if (message.is_user || message.is_system || message.extra?.type == system_message_types.NARRATOR) {
continue;
}
if (message.name !== undefined) {
message.name = newValue;
}
}
const saveChatResponse = await fetch('/savechat', {
method: "POST",
headers: getRequestHeaders(),
body: JSON.stringify({
ch_name: newValue,
file_name: fileNameWithoutExtension,
chat: currentChat,
avatar_url: newAvatar,
}),
cache: 'no-cache',
});
if (!saveChatResponse.ok) {
throw new Error('Could not save chat');
}
}
} catch (error) {
toastr.error(`Past chat could not be updated: ${file_name}`);
console.error(error);
}
}
}
function saveChatDebounced() {
const chid = this_chid;
const selectedGroup = selected_group;
if (chatSaveTimeout) {
console.debug('Clearing chat save timeout');
clearTimeout(chatSaveTimeout);
}
chatSaveTimeout = setTimeout(async () => {
if (selectedGroup !== selected_group) {
console.warn('Chat save timeout triggered, but group changed. Aborting.');
return;
}
if (chid !== this_chid) {
console.warn('Chat save timeout triggered, but chid changed. Aborting.');
return;
}
console.debug('Chat save timeout triggered');
await saveChatConditional();
console.debug('Chat saved');
}, 1000);
}
async function saveChat(chat_name, withMetadata, mesId) {
const metadata = { ...chat_metadata, ...(withMetadata || {}) };
let file_name = chat_name ?? characters[this_chid].chat;
characters[this_chid]['date_last_chat'] = Date.now();
chat.forEach(function (item, i) {
if (item["is_group"]) {
toastr.error('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}:`, `${name1}:`);
//chat[i].mes = str;
//chat[i].name = name1;
} else if (i !== chat.length - 1 && chat[i].swipe_id !== undefined) {
// delete chat[i].swipes;
// delete chat[i].swipe_id;
}
*/
});
const trimmed_chat = (mesId !== undefined && mesId >= 0 && mesId < chat.length)
? chat.slice(0, parseInt(mesId) + 1)
: chat;
var save_chat = [
{
user_name: name1,
character_name: name2,
create_date: chat_create_date,
chat_metadata: metadata,
},
...trimmed_chat,
];
return 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 () {
},
cache: false,
dataType: "json",
contentType: "application/json",
success: function (data) { },
error: function (jqXHR, exception) {
console.log(exception);
console.log(jqXHR);
},
});
}
async function read_avatar_load(input) {
if (input.files && input.files[0]) {
if (selected_button == "create") {
create_save.avatar = input.files;
}
const e = await new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = resolve;
reader.onerror = reject;
reader.readAsDataURL(input.files[0]);
})
$('#dialogue_popup').addClass('large_dialogue_popup wide_dialogue_popup');
const croppedImage = await callPopup(getCropPopup(e.target.result), 'avatarToCrop');
if (!croppedImage) {
return;
}
$("#avatar_load_preview").attr("src", croppedImage || e.target.result);
if (menu_type == "create") {
return;
}
await createOrEditCharacter();
await delay(durationSaveEdit);
const formData = new FormData($("#form_create").get(0));
await fetch(getThumbnailUrl('avatar', formData.get('avatar_url')), {
method: 'GET',
cache: 'no-cache',
headers: {
'pragma': 'no-cache',
'cache-control': 'no-cache',
}
});
$(".mes").each(async function () {
if ($(this).attr("is_system") == 'true') {
return;
}
if ($(this).attr("is_user") == 'true') {
return;
}
if ($(this).attr("ch_name") == formData.get('ch_name')) {
const previewSrc = $("#avatar_load_preview").attr("src");
const avatar = $(this).find(".avatar img");
avatar.attr('src', default_avatar);
await delay(1);
avatar.attr('src', previewSrc);
}
});
console.log('Avatar refreshed');
}
}
export function getCropPopup(src) {
return `Set the crop position of the avatar image and click Accept to confirm.
`;
}
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();
}
await getChatResult();
eventSource.emit('chatLoaded', { detail: { id: this_chid, character: characters[this_chid] } });
setTimeout(function () {
$('#send_textarea').click();
$('#send_textarea').focus();
}, 200);
} catch (error) {
await getChatResult();
console.log(error);
}
}
async function getChatResult() {
name2 = characters[this_chid].name;
if (chat.length === 0) {
const message = getFirstMessage();
chat.push(message);
await saveChatConditional();
}
printMessages();
select_selected_character(this_chid);
await eventSource.emit(event_types.CHAT_CHANGED, (getCurrentChatId()));
if (chat.length === 1) {
await eventSource.emit(event_types.MESSAGE_RECEIVED, (chat.length - 1));
await eventSource.emit(event_types.CHARACTER_MESSAGE_RENDERED, (chat.length - 1));
}
}
function getFirstMessage() {
const firstMes = characters[this_chid].first_mes || default_ch_mes;
const alternateGreetings = characters[this_chid]?.data?.alternate_greetings;
const message = {
name: name2,
is_user: false,
is_system: false,
is_name: true,
send_date: getMessageTimeStamp(),
mes: getRegexedString(firstMes, regex_placement.AI_OUTPUT),
extra: {},
};
if (Array.isArray(alternateGreetings) && alternateGreetings.length > 0) {
const swipes = [message.mes, ...(alternateGreetings.map(greeting => substituteParams(getRegexedString(greeting, regex_placement.AI_OUTPUT))))];
message['swipe_id'] = 0;
message['swipes'] = swipes;
message['swipe_info'] = [];
}
return message;
}
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);
await createOrEditCharacter();
}
////////// OPTIMZED MAIN API CHANGE FUNCTION ////////////
function changeMainAPI() {
const selectedVal = $("#main_api").val();
//console.log(selectedVal);
const apiElements = {
"koboldhorde": {
apiSettings: $("#kobold_api-settings"),
apiConnector: $("#kobold_horde"),
apiPresets: $('#kobold_api-presets'),
apiRanges: $("#range_block"),
maxContextElem: $("#max_context_block"),
amountGenElem: $("#amount_gen_block"),
},
"kobold": {
apiSettings: $("#kobold_api-settings"),
apiConnector: $("#kobold_api"),
apiPresets: $('#kobold_api-presets'),
apiRanges: $("#range_block"),
maxContextElem: $("#max_context_block"),
amountGenElem: $("#amount_gen_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"),
},
"novel": {
apiSettings: $("#novel_api-settings"),
apiConnector: $("#novel_api"),
apiPresets: $('#novel_api-presets'),
apiRanges: $("#range_block_novel"),
maxContextElem: $("#max_context_block"),
amountGenElem: $("#amount_gen_block"),
},
"openai": {
apiSettings: $("#openai_settings"),
apiConnector: $("#openai_api"),
apiPresets: $('#openai_api-presets'),
apiRanges: $("#range_block_openai"),
maxContextElem: $("#max_context_block"),
amountGenElem: $("#amount_gen_block"),
}
};
//console.log('--- apiElements--- ');
//console.log(apiElements);
//first, disable everything so the old elements stop showing
for (const apiName in apiElements) {
const apiObj = apiElements[apiName];
//do not hide items to then proceed to immediately show them.
if (selectedVal === apiName) {
continue;
}
apiObj.apiSettings.css("display", "none");
apiObj.apiConnector.css("display", "none");
apiObj.apiRanges.css("display", "none");
apiObj.apiPresets.css("display", "none");
}
//then, find and enable the active item.
//This is split out of the loop so that different apis can share settings divs
let activeItem = apiElements[selectedVal];
activeItem.apiSettings.css("display", "block");
activeItem.apiConnector.css("display", "block");
activeItem.apiRanges.css("display", "block");
activeItem.apiPresets.css("display", "block");
if (selectedVal === "openai") {
activeItem.apiPresets.css("display", "flex");
}
if (selectedVal === "textgenerationwebui" || selectedVal === "novel") {
console.log("enabling amount_gen for ooba/novel");
activeItem.amountGenElem.find('input').prop("disabled", false);
activeItem.amountGenElem.css("opacity", 1.0);
}
if (selectedVal === "novel") {
$("#ai_module_block_novel").css("display", "block");
} else {
$("#ai_module_block_novel").css("display", "none");
}
// Hide common settings for OpenAI
console.debug('value?', selectedVal);
if (selectedVal == "openai") {
console.debug('hiding settings?');
$("#common-gen-settings-block").css("display", "none");
} else {
$("#common-gen-settings-block").css("display", "block");
}
main_api = selectedVal;
online_status = "no_connection";
if (main_api == 'openai' && oai_settings.chat_completion_source == chat_completion_sources.WINDOWAI) {
$('#api_button_openai').trigger('click');
}
if (main_api == "koboldhorde") {
is_get_status = true;
getStatus();
getHordeModels();
}
switch (oai_settings.chat_completion_source) {
case chat_completion_sources.SCALE:
case chat_completion_sources.OPENROUTER:
case chat_completion_sources.WINDOWAI:
case chat_completion_sources.CLAUDE:
case chat_completion_sources.OPENAI:
case chat_completion_sources.AI21:
default:
setupChatCompletionPromptManager(oai_settings);
break;
}
}
////////////////////////////////////////////////////
export async function getUserAvatars() {
const response = await fetch("/getuseravatars", {
method: "POST",
headers: getRequestHeaders(),
body: JSON.stringify({
"": "",
}),
});
if (response.ok === true) {
const getData = await response.json();
//background = getData;
//console.log(getData.length);
$("#user_avatar_block").html(""); //RossAscends: necessary to avoid doubling avatars each refresh.
$("#user_avatar_block").append('+');
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 template = $('#user_avatar_template .avatar-container').clone();
const personaName = power_user.personas[name];
if (personaName) {
template.attr('title', personaName);
} else {
template.attr('title', '[Unnamed Persona]');
}
template.find('.avatar').attr('imgfile', name);
template.toggleClass('default_persona', name === power_user.default_persona)
template.find('img').attr('src', getUserAvatar(name));
$("#user_avatar_block").append(template);
highlightSelectedAvatar();
}
function reloadUserAvatar(force = false) {
$(".mes").each(function () {
const avatarImg = $(this).find(".avatar img");
if (force) {
avatarImg.attr("src", avatarImg.attr("src"));
}
if ($(this).attr("is_user") == 'true' && $(this).attr('force_avatar') == 'false') {
avatarImg.attr("src", getUserAvatar(user_avatar));
}
});
}
export function setUserName(value) {
name1 = value;
if (name1 === undefined || name1 == "")
name1 = default_user_name;
console.log(`User name changed to ${name1}`);
$("#your_name").val(name1);
if (power_user.persona_show_notifications) {
toastr.success(`Your messages will now be sent as ${name1}`, 'Current persona updated');
}
saveSettings("change_name");
}
function setUserAvatar() {
user_avatar = $(this).attr("imgfile");
reloadUserAvatar();
saveSettingsDebounced();
highlightSelectedAvatar();
selectCurrentPersona();
$('.zoomed_avatar[forchar]').remove();
}
async function uploadUserAvatar(e) {
const file = e.target.files[0];
if (!file) {
$("#form_upload_avatar").trigger("reset");
return;
}
const formData = new FormData($("#form_upload_avatar").get(0));
const dataUrl = await new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = resolve;
reader.onerror = reject;
reader.readAsDataURL(file);
});
$('#dialogue_popup').addClass('large_dialogue_popup wide_dialogue_popup');
const confirmation = await callPopup(getCropPopup(dataUrl.target.result), 'avatarToCrop');
if (!confirmation) {
return;
}
let url = "/uploaduseravatar";
if (crop_data !== undefined) {
url += `?crop=${encodeURIComponent(JSON.stringify(crop_data))}`;
}
jQuery.ajax({
type: "POST",
url: url,
data: formData,
beforeSend: () => { },
cache: false,
contentType: false,
processData: false,
success: async function () {
// If the user uploaded a new avatar, we want to make sure it's not cached
const name = formData.get("overwrite_name");
if (name) {
await fetch(getUserAvatar(name), { cache: "no-cache" });
reloadUserAvatar(true);
}
crop_data = undefined;
await getUserAvatars();
},
error: (jqXHR, exception) => { },
});
// Will allow to select the same file twice in a row
$("#form_upload_avatar").trigger("reset");
}
async function doOnboarding(avatarId) {
let simpleUiMode = false;
const template = $('#onboarding_template .onboarding');
template.find('input[name="enable_simple_mode"]').on('input', function () {
simpleUiMode = $(this).is(':checked');
});
const userName = await callPopup(template, 'input', name1);
if (userName) {
setUserName(userName);
console.log(`Binding persona ${avatarId} to name ${userName}`);
power_user.personas[avatarId] = userName;
power_user.persona_descriptions[avatarId] = {
description: '',
position: persona_description_positions.IN_PROMPT,
};
}
if (simpleUiMode) {
power_user.ui_mode = ui_mode.SIMPLE;
$('#ui_mode_select').val(power_user.ui_mode);
switchSimpleMode();
}
}
//***************SETTINGS****************//
///////////////////////////////////////////
async function getSettings(type) {
const response = await fetch("/getsettings", {
method: "POST",
headers: getRequestHeaders(),
body: JSON.stringify({}),
cache: "no-cache",
});
if (!response.ok) {
toastr.error('Settings could not be loaded. Try reloading the page.');
throw new Error('Error getting settings');
}
const data = await response.json();
if (data.result != "file not find" && data.settings) {
settings = JSON.parse(data.settings);
if (settings.username !== undefined && settings.username !== "") {
name1 = settings.username;
$("#your_name").val(name1);
}
// Allow subscribers to mutate settings
eventSource.emit(event_types.SETTINGS_LOADED_BEFORE, settings);
//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;
//Load AI model config settings
amount_gen = settings.amount_gen;
if (settings.max_context !== undefined)
max_context = parseInt(settings.max_context);
swipes = settings.swipes !== undefined ? !!settings.swipes : true; // enable swipes by default
$('#swipes-checkbox').prop('checked', swipes); /// swipecode
hideSwipeButtons();
showSwipeButtons();
// Kobold
loadKoboldSettings(settings.kai_settings ?? settings);
// Novel
loadNovelSettings(settings.nai_settings ?? settings);
$(`#settings_perset_novel option[value=${novelai_setting_names[nai_settings.preset_settings_novel]}]`).attr("selected", "true");
// TextGen
loadTextGenSettings(data, settings);
// OpenAI
loadOpenAISettings(data, settings.oai_settings ?? settings);
// Horde
loadHordeSettings(settings);
// Load power user settings
loadPowerUserSettings(settings, data);
// Load character tags
loadTagsSettings(settings);
// Allow subscribers to mutate settings
eventSource.emit(event_types.SETTINGS_LOADED_AFTER, settings);
// Set context size after loading power user (may override the max value)
$("#max_context").val(max_context);
$("#max_context_counter").text(`${max_context}`);
$("#amount_gen").val(amount_gen);
$("#amount_gen_counter").text(`${amount_gen}`);
//Load which API we are using
if (settings.main_api == undefined) {
settings.main_api = 'kobold';
}
if (settings.main_api == 'poe') {
settings.main_api = 'openai';
}
main_api = settings.main_api;
$('#main_api').val(main_api);
$("#main_api option[value=" + main_api + "]").attr(
"selected",
"true"
);
changeMainAPI();
//Load User's Name and Avatar
user_avatar = settings.user_avatar;
firstRun = !!settings.firstRun;
if (firstRun) {
await doOnboarding(user_avatar);
firstRun = false;
}
reloadUserAvatar();
highlightSelectedAvatar();
setPersonaDescription();
//Load the active character and group
active_character = settings.active_character;
active_group = settings.active_group;
//Load the API server URL from settings
api_server = settings.api_server;
$("#api_url_text").val(api_server);
setWorldInfoSettings(settings.world_info_settings ?? settings, data);
api_server_textgenerationwebui = settings.api_server_textgenerationwebui;
$("#textgenerationwebui_api_url_text").val(api_server_textgenerationwebui);
$("#mancer_api_url_text").val(api_server_textgenerationwebui);
api_use_mancer_webui = settings.api_use_mancer_webui
$('#use-mancer-api-checkbox').prop("checked", api_use_mancer_webui);
$('#use-mancer-api-checkbox').trigger("change");
selected_button = settings.selected_button;
if (data.enable_extensions) {
await loadExtensionSettings(settings);
eventSource.emit(event_types.EXTENSION_SETTINGS_LOADED);
}
}
eventSource.emit(event_types.SETTINGS_LOADED);
}
function selectKoboldGuiPreset() {
$("#settings_perset option[value=gui]")
.attr("selected", "true")
.trigger("change");
}
async function saveSettings(type) {
if (!settingsReady) {
console.warn('Settings not ready, aborting save');
return;
}
//console.log('Entering settings with name1 = '+name1);
return jQuery.ajax({
type: "POST",
url: "/savesettings",
data: JSON.stringify({
firstRun: firstRun,
username: name1,
active_character: active_character,
active_group: active_group,
api_server: api_server,
api_server_textgenerationwebui: api_server_textgenerationwebui,
api_use_mancer_webui: api_use_mancer_webui,
preset_settings: preset_settings,
user_avatar: user_avatar,
amount_gen: amount_gen,
max_context: max_context,
main_api: main_api,
world_info_settings: getWorldInfoSettings(),
textgenerationwebui_settings: textgenerationwebui_settings,
swipes: swipes,
horde_settings: horde_settings,
power_user: power_user,
extension_settings: extension_settings,
tags: tags,
tag_map: tag_map,
nai_settings: nai_settings,
kai_settings: kai_settings,
oai_settings: oai_settings,
}, null, 4),
beforeSend: function () {
if (type == "change_name") {
//let nameBeforeChange = name1;
name1 = $("#your_name").val();
//$(`.mes[ch_name="${nameBeforeChange}"]`).attr('ch_name' === name1);
//console.log('beforeSend name1 = ' + nameBeforeChange);
//console.log('new name: ' + name1);
}
},
cache: false,
dataType: "json",
contentType: "application/json",
//processData: false,
success: function (data) {
//online_status = data.result;
eventSource.emit(event_types.SETTINGS_UPDATED);
if (type == "change_name") {
clearChat();
printMessages();
}
},
error: function (jqXHR, exception) {
toastr.error('Check the server connection and reload the page to prevent data loss.', 'Settings could not be saved');
console.log(exception);
console.log(jqXHR);
},
});
}
export function setGenerationParamsFromPreset(preset) {
if (preset.genamt !== undefined) {
amount_gen = preset.genamt;
$("#amount_gen").val(amount_gen);
$("#amount_gen_counter").text(`${amount_gen}`);
}
if (preset.max_length !== undefined) {
max_context = preset.max_length;
const needsUnlock = max_context > MAX_CONTEXT_DEFAULT;
$('#max_context_unlocked').prop('checked', needsUnlock).trigger('change');
$("#max_context").val(max_context);
$("#max_context_counter").text(`${max_context}`);
}
}
// Common code for message editor done and auto-save
function updateMessage(div) {
const mesBlock = div.closest(".mes_block");
let text = mesBlock.find(".edit_textarea").val();
const mes = chat[this_edit_mes_id];
let regexPlacement;
if (mes.is_name && mes.is_user) {
regexPlacement = regex_placement.USER_INPUT;
} else if (mes.is_name && mes.name === name2) {
regexPlacement = regex_placement.AI_OUTPUT;
} else if (mes.is_name && mes.name !== name2 || mes.extra?.type === "narrator") {
regexPlacement = regex_placement.SLASH_COMMAND;
}
// Ignore character override if sent as system
text = getRegexedString(
text,
regexPlacement,
{ characterOverride: mes.extra?.type === "narrator" ? undefined : mes.name }
);
if (power_user.trim_spaces) {
text = text.trim();
}
const bias = extractMessageBias(text);
mes["mes"] = text;
if (mes["swipe_id"] !== undefined) {
mes["swipes"][mes["swipe_id"]] = text;
}
// editing old messages
if (!mes.extra) {
mes.extra = {};
}
if (mes.is_system || mes.is_user || mes.extra.type === system_message_types.NARRATOR) {
mes.extra.bias = bias ?? null;
} else {
mes.extra.bias = null;
}
return { mesBlock, text, mes, bias };
}
function openMessageDelete() {
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");
}
});
} else {
console.debug(`
ERR -- could not enter del mode
this_chid: ${this_chid}
is_send_press: ${is_send_press}
selected_group: ${selected_group}
is_group_generating: ${is_group_generating}`);
}
is_delete_mode = true;
}
function messageEditAuto(div) {
const { mesBlock, text, mes } = updateMessage(div);
mesBlock.find(".mes_text").val('');
mesBlock.find(".mes_text").val(messageFormatting(
text,
this_edit_mes_chname,
mes.is_system,
mes.is_user,
));
saveChatDebounced();
}
async function messageEditDone(div) {
let { mesBlock, text, mes, bias } = updateMessage(div);
if (this_edit_mes_id == 0) {
text = substituteParams(text);
}
mesBlock.find(".mes_text").empty();
mesBlock.find(".mes_edit_buttons").css("display", "none");
mesBlock.find(".mes_buttons").css("display", "");
mesBlock.find(".mes_text").append(
messageFormatting(
text,
this_edit_mes_chname,
mes.is_system,
mes.is_user,
)
);
mesBlock.find(".mes_bias").empty();
mesBlock.find(".mes_bias").append(messageFormatting(bias));
appendImageToMessage(mes, div.closest(".mes"));
addCopyToCodeBlocks(div.closest(".mes"));
await eventSource.emit(event_types.MESSAGE_EDITED, this_edit_mes_id);
this_edit_mes_id = undefined;
await saveChatConditional();
}
/**
* Fetches the chat content for each chat file from the server and compiles them into a dictionary.
* The function iterates over a provided list of chat metadata and requests the actual chat content
* for each chat, either as an individual chat or a group chat based on the context.
*
* @param {Array} data - An array containing metadata about each chat such as file_name.
* @param {boolean} isGroupChat - A flag indicating if the chat is a group chat.
* @returns {Promise