Spaces:
Paused
Paused
| import { Fuse, Handlebars } from '../lib.js'; | |
| import { | |
| saveSettingsDebounced, | |
| scrollChatToBottom, | |
| characters, | |
| reloadMarkdownProcessor, | |
| reloadCurrentChat, | |
| getRequestHeaders, | |
| substituteParams, | |
| eventSource, | |
| event_types, | |
| getCurrentChatId, | |
| printCharactersDebounced, | |
| setCharacterId, | |
| setEditedMessageId, | |
| chat, | |
| getFirstDisplayedMessageId, | |
| showMoreMessages, | |
| saveSettings, | |
| saveChatConditional, | |
| setAnimationDuration, | |
| ANIMATION_DURATION_DEFAULT, | |
| setActiveGroup, | |
| setActiveCharacter, | |
| entitiesFilter, | |
| doNewChat, | |
| messageFormatting, | |
| } from '../script.js'; | |
| import { isMobile, initMovingUI, favsToHotswap } from './RossAscends-mods.js'; | |
| import { | |
| groups, | |
| resetSelectedGroup, | |
| } from './group-chats.js'; | |
| import { | |
| instruct_presets, | |
| loadInstructMode, | |
| names_behavior_types, | |
| selectInstructPreset, | |
| } from './instruct-mode.js'; | |
| import { getTagsList, tag_import_setting, tag_map, tags } from './tags.js'; | |
| import { tokenizers } from './tokenizers.js'; | |
| import { BIAS_CACHE } from './logit-bias.js'; | |
| import { renderTemplateAsync } from './templates.js'; | |
| import { countOccurrences, debounce, delay, download, getFileText, getStringHash, isOdd, isTrueBoolean, onlyUnique, resetScrollHeight, shuffle, sortMoments, stringToRange, timestampToMoment } from './utils.js'; | |
| import { FILTER_TYPES } from './filters.js'; | |
| import { PARSER_FLAG, SlashCommandParser } from './slash-commands/SlashCommandParser.js'; | |
| import { SlashCommand } from './slash-commands/SlashCommand.js'; | |
| import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js'; | |
| import { AUTOCOMPLETE_SELECT_KEY, AUTOCOMPLETE_STATE, AUTOCOMPLETE_WIDTH } from './autocomplete/AutoComplete.js'; | |
| import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js'; | |
| import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js'; | |
| import { POPUP_TYPE, callGenericPopup, fixToastrForDialogs } from './popup.js'; | |
| import { loadSystemPrompts } from './sysprompt.js'; | |
| import { fuzzySearchCategories } from './filters.js'; | |
| import { accountStorage } from './util/AccountStorage.js'; | |
| import { DEFAULT_REASONING_TEMPLATE, loadReasoningTemplates } from './reasoning.js'; | |
| export { | |
| loadPowerUserSettings, | |
| loadMovingUIState, | |
| collapseNewlines, | |
| playMessageSound, | |
| fixMarkdown, | |
| power_user, | |
| send_on_enter_options, | |
| getContextSettings, | |
| applyPowerUserSettings, | |
| }; | |
| export const toastPositionClasses = [ | |
| 'toast-top-left', | |
| 'toast-top-center', | |
| 'toast-top-right', | |
| 'toast-bottom-left', | |
| 'toast-bottom-center', | |
| 'toast-bottom-right', | |
| ]; | |
| export const MAX_CONTEXT_DEFAULT = 8192; | |
| export const MAX_RESPONSE_DEFAULT = 2048; | |
| const MAX_CONTEXT_UNLOCKED = 512 * 1024; | |
| const MAX_RESPONSE_UNLOCKED = 64 * 1024; | |
| const unlockedMaxContextStep = 512; | |
| const maxContextMin = 512; | |
| const maxContextStep = 64; | |
| const defaultStoryString = '{{#if system}}{{system}}\n{{/if}}{{#if description}}{{description}}\n{{/if}}{{#if personality}}{{char}}\'s personality: {{personality}}\n{{/if}}{{#if scenario}}Scenario: {{scenario}}\n{{/if}}{{#if persona}}{{persona}}\n{{/if}}'; | |
| const defaultExampleSeparator = '***'; | |
| const defaultChatStart = '***'; | |
| const defaultToastPosition = 'toast-top-center'; | |
| const avatar_styles = { | |
| ROUND: 0, | |
| RECTANGULAR: 1, | |
| SQUARE: 2, | |
| ROUNDED: 3, | |
| }; | |
| export const chat_styles = { | |
| DEFAULT: 0, | |
| BUBBLES: 1, | |
| DOCUMENT: 2, | |
| }; | |
| const send_on_enter_options = { | |
| DISABLED: -1, | |
| AUTO: 0, | |
| ENABLED: 1, | |
| }; | |
| export const persona_description_positions = { | |
| IN_PROMPT: 0, | |
| /** | |
| * @deprecated Use persona_description_positions.IN_PROMPT instead. | |
| */ | |
| AFTER_CHAR: 1, | |
| TOP_AN: 2, | |
| BOTTOM_AN: 3, | |
| AT_DEPTH: 4, | |
| NONE: 9, | |
| }; | |
| let power_user = { | |
| charListGrid: false, | |
| tokenizer: tokenizers.BEST_MATCH, | |
| token_padding: 64, | |
| collapse_newlines: false, | |
| pin_examples: false, | |
| strip_examples: false, | |
| trim_sentences: false, | |
| always_force_name2: false, | |
| user_prompt_bias: '', | |
| show_user_prompt_bias: true, | |
| auto_continue: { | |
| enabled: false, | |
| allow_chat_completions: false, | |
| target_length: 400, | |
| }, | |
| markdown_escape_strings: '', | |
| chat_truncation: 100, | |
| streaming_fps: 30, | |
| smooth_streaming: false, | |
| smooth_streaming_speed: 50, | |
| fast_ui_mode: true, | |
| avatar_style: avatar_styles.ROUND, | |
| chat_display: chat_styles.DEFAULT, | |
| toastr_position: defaultToastPosition, | |
| chat_width: 50, | |
| never_resize_avatars: false, | |
| show_card_avatar_urls: false, | |
| play_message_sound: false, | |
| play_sound_unfocused: true, | |
| auto_save_msg_edits: false, | |
| confirm_message_delete: true, | |
| sort_field: 'name', | |
| sort_order: 'asc', | |
| sort_rule: null, | |
| font_scale: 1, | |
| blur_strength: 10, | |
| shadow_width: 2, | |
| main_text_color: `${getComputedStyle(document.documentElement).getPropertyValue('--SmartThemeBodyColor').trim()}`, | |
| italics_text_color: `${getComputedStyle(document.documentElement).getPropertyValue('--SmartThemeEmColor').trim()}`, | |
| underline_text_color: `${getComputedStyle(document.documentElement).getPropertyValue('--SmartThemeUnderlineColor').trim()}`, | |
| quote_text_color: `${getComputedStyle(document.documentElement).getPropertyValue('--SmartThemeQuoteColor').trim()}`, | |
| blur_tint_color: `${getComputedStyle(document.documentElement).getPropertyValue('--SmartThemeBlurTintColor').trim()}`, | |
| chat_tint_color: `${getComputedStyle(document.documentElement).getPropertyValue('--SmartThemeChatTintColor').trim()}`, | |
| user_mes_blur_tint_color: `${getComputedStyle(document.documentElement).getPropertyValue('--SmartThemeUserMesBlurTintColor').trim()}`, | |
| bot_mes_blur_tint_color: `${getComputedStyle(document.documentElement).getPropertyValue('--SmartThemeBotMesBlurTintColor').trim()}`, | |
| shadow_color: `${getComputedStyle(document.documentElement).getPropertyValue('--SmartThemeShadowColor').trim()}`, | |
| border_color: `${getComputedStyle(document.documentElement).getPropertyValue('--SmartThemeBorderColor').trim()}`, | |
| custom_css: '', | |
| waifuMode: false, | |
| movingUI: false, | |
| movingUIState: {}, | |
| movingUIPreset: '', | |
| noShadows: false, | |
| theme: 'Default (Dark) 1.7.1', | |
| gestures: true, | |
| auto_swipe: false, | |
| auto_swipe_minimum_length: 0, | |
| auto_swipe_blacklist: [], | |
| auto_swipe_blacklist_threshold: 2, | |
| auto_scroll_chat_to_bottom: true, | |
| auto_fix_generated_markdown: true, | |
| send_on_enter: send_on_enter_options.AUTO, | |
| console_log_prompts: false, | |
| request_token_probabilities: false, | |
| show_group_chat_queue: false, | |
| allow_name1_display: false, | |
| allow_name2_display: false, | |
| hotswap_enabled: true, | |
| timer_enabled: true, | |
| timestamps_enabled: true, | |
| timestamp_model_icon: false, | |
| mesIDDisplay_enabled: false, | |
| hideChatAvatars_enabled: false, | |
| max_context_unlocked: false, | |
| message_token_count_enabled: false, | |
| expand_message_actions: false, | |
| enableZenSliders: false, | |
| enableLabMode: false, | |
| prefer_character_prompt: true, | |
| prefer_character_jailbreak: true, | |
| quick_continue: false, | |
| quick_impersonate: false, | |
| continue_on_send: false, | |
| trim_spaces: true, | |
| relaxed_api_urls: false, | |
| world_import_dialog: true, | |
| enable_auto_select_input: false, | |
| enable_md_hotkeys: false, | |
| tag_import_setting: tag_import_setting.ASK, | |
| disable_group_trimming: false, | |
| single_line: false, | |
| instruct: { | |
| enabled: false, | |
| preset: 'Alpaca', | |
| input_sequence: '### Instruction:', | |
| input_suffix: '', | |
| output_sequence: '### Response:', | |
| output_suffix: '', | |
| system_sequence: '', | |
| system_suffix: '', | |
| last_system_sequence: '', | |
| first_input_sequence: '', | |
| first_output_sequence: '', | |
| last_input_sequence: '', | |
| last_output_sequence: '', | |
| system_sequence_prefix: '', | |
| system_sequence_suffix: '', | |
| stop_sequence: '', | |
| wrap: true, | |
| macro: true, | |
| names_behavior: names_behavior_types.FORCE, | |
| activation_regex: '', | |
| derived: false, | |
| bind_to_context: false, | |
| user_alignment_message: '', | |
| system_same_as_user: false, | |
| /** @deprecated Use output_suffix instead */ | |
| separator_sequence: '', | |
| }, | |
| context: { | |
| preset: 'Default', | |
| story_string: defaultStoryString, | |
| chat_start: defaultChatStart, | |
| example_separator: defaultExampleSeparator, | |
| use_stop_strings: true, | |
| names_as_stop_strings: true, | |
| }, | |
| context_derived: false, | |
| context_size_derived: false, | |
| sysprompt: { | |
| enabled: true, | |
| name: 'Neutral - Chat', | |
| content: 'Write {{char}}\'s next reply in a fictional chat between {{char}} and {{user}}.', | |
| post_history: '', | |
| }, | |
| reasoning: { | |
| name: DEFAULT_REASONING_TEMPLATE, | |
| auto_parse: false, | |
| add_to_prompts: false, | |
| auto_expand: false, | |
| show_hidden: false, | |
| prefix: '<think>\n', | |
| suffix: '\n</think>', | |
| separator: '\n\n', | |
| max_additions: 1, | |
| }, | |
| personas: {}, | |
| default_persona: null, | |
| persona_descriptions: {}, | |
| persona_description: '', | |
| persona_description_position: persona_description_positions.IN_PROMPT, | |
| persona_description_role: 0, | |
| persona_description_depth: 2, | |
| persona_description_lorebook: '', | |
| persona_show_notifications: true, | |
| persona_sort_order: 'asc', | |
| custom_stopping_strings: '', | |
| custom_stopping_strings_macro: true, | |
| fuzzy_search: false, | |
| encode_tags: false, | |
| servers: [], | |
| bogus_folders: false, | |
| zoomed_avatar_magnification: false, | |
| show_tag_filters: false, | |
| aux_field: 'character_version', | |
| stscript: { | |
| matching: 'fuzzy', | |
| autocomplete: { | |
| state: AUTOCOMPLETE_STATE.ALWAYS, | |
| autoHide: false, | |
| style: 'theme', | |
| font: { | |
| scale: 0.8, | |
| }, | |
| width: { | |
| left: AUTOCOMPLETE_WIDTH.CHAT, | |
| right: AUTOCOMPLETE_WIDTH.CHAT, | |
| }, | |
| select: AUTOCOMPLETE_SELECT_KEY.TAB + AUTOCOMPLETE_SELECT_KEY.ENTER, | |
| }, | |
| parser: { | |
| /**@type {Object.<PARSER_FLAG,boolean>} */ | |
| flags: {}, | |
| }, | |
| }, | |
| restore_user_input: true, | |
| reduced_motion: false, | |
| compact_input_area: true, | |
| show_swipe_num_all_messages: false, | |
| auto_connect: false, | |
| auto_load_chat: false, | |
| forbid_external_media: true, | |
| external_media_allowed_overrides: [], | |
| external_media_forbidden_overrides: [], | |
| pin_styles: true, | |
| click_to_edit: false, | |
| }; | |
| let themes = []; | |
| let movingUIPresets = []; | |
| export let context_presets = []; | |
| const storage_keys = { | |
| storyStringValidationCache: 'StoryStringValidationCache', | |
| }; | |
| const contextControls = [ | |
| // Power user context scoped settings | |
| { id: 'context_story_string', property: 'story_string', isCheckbox: false, isGlobalSetting: false }, | |
| { id: 'context_example_separator', property: 'example_separator', isCheckbox: false, isGlobalSetting: false }, | |
| { id: 'context_chat_start', property: 'chat_start', isCheckbox: false, isGlobalSetting: false }, | |
| { id: 'context_use_stop_strings', property: 'use_stop_strings', isCheckbox: true, isGlobalSetting: false, defaultValue: false }, | |
| { id: 'context_names_as_stop_strings', property: 'names_as_stop_strings', isCheckbox: true, isGlobalSetting: false, defaultValue: true }, | |
| // Existing power user settings | |
| { id: 'always-force-name2-checkbox', property: 'always_force_name2', isCheckbox: true, isGlobalSetting: true, defaultValue: true }, | |
| { id: 'trim_sentences_checkbox', property: 'trim_sentences', isCheckbox: true, isGlobalSetting: true, defaultValue: false }, | |
| { id: 'single_line', property: 'single_line', isCheckbox: true, isGlobalSetting: true, defaultValue: false }, | |
| ]; | |
| let browser_has_focus = true; | |
| const debug_functions = []; | |
| const setHotswapsDebounced = debounce(favsToHotswap); | |
| function playMessageSound() { | |
| if (!power_user.play_message_sound) { | |
| return; | |
| } | |
| if (power_user.play_sound_unfocused && browser_has_focus) { | |
| return; | |
| } | |
| const audio = document.getElementById('audio_message_sound'); | |
| if (audio instanceof HTMLAudioElement) { | |
| audio.volume = 0.8; | |
| audio.pause(); | |
| audio.currentTime = 0; | |
| audio.play(); | |
| } | |
| } | |
| /** | |
| * Replaces consecutive newlines with a single newline. | |
| * @param {string} x String to be processed. | |
| * @returns {string} Processed string. | |
| * @example | |
| * collapseNewlines("\n\n\n"); // "\n" | |
| */ | |
| function collapseNewlines(x) { | |
| return x.replaceAll(/\n+/g, '\n'); | |
| } | |
| /** | |
| * Fix formatting problems in markdown. | |
| * @param {string} text Text to be processed. | |
| * @param {boolean} forDisplay Whether the text is being processed for display. | |
| * @returns {string} Processed text. | |
| * @example | |
| * "^example * text*\n" // "^example *text*\n" | |
| * "^*example * text\n"// "^*example* text\n" | |
| * "^example *text *\n" // "^example *text*\n" | |
| * "^* example * text\n" // "^*example* text\n" | |
| * // take note that the side you move the asterisk depends on where its pairing is | |
| * // i.e. both of the following strings have the same broken asterisk ' * ', | |
| * // but you move the first to the left and the second to the right, to match the non-broken asterisk | |
| * "^example * text*\n" // "^*example * text\n" | |
| * // and you HAVE to handle the cases where multiple pairs of asterisks exist in the same line | |
| * "^example * text* * harder problem *\n" // "^example *text* *harder problem*\n" | |
| */ | |
| function fixMarkdown(text, forDisplay) { | |
| // Find pairs of formatting characters and capture the text in between them | |
| const format = /([*_]{1,2})([\s\S]*?)\1/gm; | |
| let matches = []; | |
| let match; | |
| while ((match = format.exec(text)) !== null) { | |
| matches.push(match); | |
| } | |
| // Iterate through the matches and replace adjacent spaces immediately beside formatting characters | |
| let newText = text; | |
| for (let i = matches.length - 1; i >= 0; i--) { | |
| let matchText = matches[i][0]; | |
| let replacementText = matchText.replace(/(\*|_)([\t \u00a0\u1680\u2000-\u200a\u202f\u205f\u3000\ufeff]+)|([\t \u00a0\u1680\u2000-\u200a\u202f\u205f\u3000\ufeff]+)(\*|_)/g, '$1$4'); | |
| newText = newText.slice(0, matches[i].index) + replacementText + newText.slice(matches[i].index + matchText.length); | |
| } | |
| // Don't auto-fix asterisks if this is a message clean-up procedure. | |
| // It botches the continue function. Apply this to display only. | |
| if (!forDisplay) { | |
| return newText; | |
| } | |
| const splitText = newText.split('\n'); | |
| // Fix asterisks, and quotes that are not paired | |
| for (let index = 0; index < splitText.length; index++) { | |
| const line = splitText[index]; | |
| const charsToCheck = ['*', '"']; | |
| for (const char of charsToCheck) { | |
| if (line.includes(char) && isOdd(countOccurrences(line, char))) { | |
| splitText[index] = line.trimEnd() + char; | |
| } | |
| } | |
| } | |
| newText = splitText.join('\n'); | |
| return newText; | |
| } | |
| function switchHotswap() { | |
| $('body').toggleClass('no-hotswap', !power_user.hotswap_enabled); | |
| $('#hotswapEnabled').prop('checked', power_user.hotswap_enabled); | |
| } | |
| function switchTimer() { | |
| $('body').toggleClass('no-timer', !power_user.timer_enabled); | |
| $('#messageTimerEnabled').prop('checked', power_user.timer_enabled); | |
| } | |
| function switchTimestamps() { | |
| $('body').toggleClass('no-timestamps', !power_user.timestamps_enabled); | |
| $('#messageTimestampsEnabled').prop('checked', power_user.timestamps_enabled); | |
| } | |
| function switchIcons() { | |
| $('body').toggleClass('no-modelIcons', !power_user.timestamp_model_icon); | |
| $('#messageModelIconEnabled').prop('checked', power_user.timestamp_model_icon); | |
| } | |
| function switchTokenCount() { | |
| $('body').toggleClass('no-tokenCount', !power_user.message_token_count_enabled); | |
| $('#messageTokensEnabled').prop('checked', power_user.message_token_count_enabled); | |
| } | |
| function switchMesIDDisplay() { | |
| $('body').toggleClass('no-mesIDDisplay', !power_user.mesIDDisplay_enabled); | |
| $('#mesIDDisplayEnabled').prop('checked', power_user.mesIDDisplay_enabled); | |
| } | |
| function switchHideChatAvatars() { | |
| $('body').toggleClass('hideChatAvatars', power_user.hideChatAvatars_enabled); | |
| $('#hideChatAvatarsEnabled').prop('checked', power_user.hideChatAvatars_enabled); | |
| } | |
| function switchMessageActions() { | |
| $('body').toggleClass('expandMessageActions', power_user.expand_message_actions); | |
| $('#expandMessageActions').prop('checked', power_user.expand_message_actions); | |
| $('.extraMesButtons, .extraMesButtonsHint').removeAttr('style'); | |
| } | |
| function switchReducedMotion() { | |
| jQuery.fx.off = power_user.reduced_motion; | |
| const overrideDuration = power_user.reduced_motion ? 0 : ANIMATION_DURATION_DEFAULT; | |
| setAnimationDuration(overrideDuration); | |
| $('#reduced_motion').prop('checked', power_user.reduced_motion); | |
| $('body').toggleClass('reduced-motion', power_user.reduced_motion); | |
| } | |
| function switchCompactInputArea() { | |
| $('#send_form').toggleClass('compact', power_user.compact_input_area); | |
| $('#compact_input_area').prop('checked', power_user.compact_input_area); | |
| } | |
| function switchSwipeNumAllMessages() { | |
| $('#show_swipe_num_all_messages').prop('checked', power_user.show_swipe_num_all_messages); | |
| $('body').toggleClass('swipeAllMessages', !!power_user.show_swipe_num_all_messages); | |
| } | |
| var originalSliderValues = []; | |
| async function switchLabMode({ noReset = false } = {}) { | |
| /* if (power_user.enableZenSliders && power_user.enableLabMode) { | |
| toastr.warning("Can't start Lab Mode while Zen Sliders are active") | |
| return | |
| //$("#enableZenSliders").trigger('click') | |
| } | |
| */ | |
| await delay(100); | |
| $('body').toggleClass('enableLabMode', power_user.enableLabMode); | |
| $('#enableLabMode').prop('checked', power_user.enableLabMode); | |
| if (power_user.enableLabMode) { | |
| //save all original slider values into an array | |
| $('#advanced-ai-config-block input').each(function () { | |
| let id = $(this).attr('id'); | |
| let min = $(this).attr('min'); | |
| let max = $(this).attr('max'); | |
| let step = $(this).attr('step'); | |
| originalSliderValues.push({ id, min, max, step }); | |
| }); | |
| //console.log(originalSliderValues) | |
| //remove limits on all inputs and hide sliders | |
| $('#advanced-ai-config-block input') | |
| .attr('min', '-99999') | |
| .attr('max', '99999') | |
| .attr('step', '0.001'); | |
| $('#labModeWarning').removeClass('displayNone'); | |
| //$("#advanced-ai-config-block input[type='range']").hide() | |
| $('#amount_gen_counter').attr('min', '1') | |
| .attr('max', '99999') | |
| .attr('step', '1'); | |
| $('#amount_gen').attr('min', '1') | |
| .attr('max', '99999') | |
| .attr('step', '1'); | |
| } else if (!noReset) { | |
| //re apply the original sliders values to each input | |
| originalSliderValues.forEach(function (slider) { | |
| $('#' + slider.id) | |
| .attr('min', slider.min) | |
| .attr('max', slider.max) | |
| .attr('step', slider.step) | |
| .trigger('input'); | |
| }); | |
| $('#advanced-ai-config-block input[type=\'range\']').show(); | |
| $('#labModeWarning').addClass('displayNone'); | |
| // To set the correct amount_gen back, we just call the function calculating it correctly | |
| switchMaxContextSize(); | |
| } | |
| } | |
| async function switchZenSliders() { | |
| await delay(100); | |
| $('body').toggleClass('enableZenSliders', power_user.enableZenSliders); | |
| $('#enableZenSliders').prop('checked', power_user.enableZenSliders); | |
| if (power_user.enableZenSliders) { | |
| $('#clickSlidersTips').hide(); | |
| $('#pro-settings-block input[type=\'number\']').hide(); | |
| //hide number inputs that are not 'seed' inputs | |
| $(`#textgenerationwebui_api-settings :input[type='number']:not([id^='seed']):not([id^='n_']), | |
| #kobold_api-settings :input[type='number']:not([id^='seed'])`).hide(); | |
| //hide original sliders | |
| $(`#textgenerationwebui_api-settings input[type='range'], | |
| #kobold_api-settings input[type='range'], | |
| #pro-settings-block input[type='range']:not(#max_context)`) //exclude max context because its creation is handled by switchMaxContext() | |
| .hide() | |
| .each(function () { | |
| //make a zen slider for each original slider | |
| CreateZenSliders($(this)); | |
| }); | |
| //this is for when zensliders is toggled after pageload | |
| switchMaxContextSize(); | |
| } else { | |
| $('#clickSlidersTips').show(); | |
| revertOriginalSliders(); | |
| } | |
| function revertOriginalSliders() { | |
| $('#pro-settings-block input[type=\'number\']').show(); | |
| $(`#textgenerationwebui_api-settings input[type='number'], | |
| #kobold_api-settings input[type='number']`).show(); | |
| $(`#textgenerationwebui_api-settings input[type='range'], | |
| #kobold_api-settings input[type='range'], | |
| #pro-settings-block input[type='range']`).each(function () { | |
| $(this).show(); | |
| }); | |
| $('div[id$="_zenslider"]').remove(); | |
| } | |
| } | |
| async function CreateZenSliders(elmnt) { | |
| var originalSlider = elmnt; | |
| var sliderID = originalSlider.attr('id'); | |
| var sliderMin = Number(originalSlider.attr('min')); | |
| var sliderMax = Number(originalSlider.attr('max')); | |
| var sliderValue = originalSlider.val(); | |
| var sliderRange = sliderMax - sliderMin; | |
| var numSteps = 20; | |
| var decimals = 2; | |
| var offVal, allVal; | |
| var stepScale; | |
| var steps; | |
| if (sliderID == 'amount_gen') { | |
| decimals = 0; | |
| steps = [16, 50, 100, 150, 200, 256, 300, 400, 512, 1024]; | |
| sliderMin = 0; | |
| sliderMax = steps.length - 1; | |
| stepScale = 1; | |
| numSteps = 10; | |
| sliderValue = steps.indexOf(Number(sliderValue)); | |
| if (sliderValue === -1) { sliderValue = 4; } // default to '200' if origSlider has value we can't use | |
| } | |
| if (sliderID == 'rep_pen_range_textgenerationwebui') { | |
| if (power_user.max_context_unlocked) { | |
| steps = [0, 256, 512, 768, 1024, 2048, 4096, 8192, 16355, 24576, 32768, 49152, 65536, -1]; | |
| numSteps = 13; | |
| allVal = 13; | |
| } else { | |
| steps = [0, 256, 512, 768, 1024, 2048, 4096, 8192, -1]; | |
| numSteps = 8; | |
| allVal = 8; | |
| } | |
| decimals = 0; | |
| offVal = 0; | |
| sliderMin = 0; | |
| sliderMax = steps.length - 1; | |
| stepScale = 1; | |
| sliderValue = steps.indexOf(Number(sliderValue)); | |
| if (sliderValue === -1) { sliderValue = allVal; } // default to allValue if origSlider has value we can't use | |
| } | |
| //customize decimals | |
| if (sliderID == 'max_context' || | |
| sliderID == 'mirostat_mode_textgenerationwebui' || | |
| sliderID == 'mirostat_tau_textgenerationwebui' || | |
| sliderID == 'top_k_textgenerationwebui' || | |
| sliderID == 'num_beams_textgenerationwebui' || | |
| sliderID == 'no_repeat_ngram_size_textgenerationwebui' || | |
| sliderID == 'min_length_textgenerationwebui' || | |
| sliderID == 'top_k' || | |
| sliderID == 'mirostat_mode_kobold' || | |
| sliderID == 'rep_pen_range' || | |
| sliderID == 'dry_allowed_length_textgenerationwebui' || | |
| sliderID == 'rep_pen_decay_textgenerationwebui' || | |
| sliderID == 'dry_penalty_last_n_textgenerationwebui' || | |
| sliderID == 'max_tokens_second_textgenerationwebui') { | |
| decimals = 0; | |
| } | |
| if (sliderID == 'min_temp_textgenerationwebui' || | |
| sliderID == 'max_temp_textgenerationwebui' || | |
| sliderID == 'smoothing_curve_textgenerationwebui' || | |
| sliderID == 'smoothing_factor_textgenerationwebui' || | |
| sliderID == 'dry_multiplier_textgenerationwebui' || | |
| sliderID == 'dry_base_textgenerationwebui') { | |
| decimals = 2; | |
| } | |
| if (sliderID == 'eta_cutoff_textgenerationwebui' || | |
| sliderID == 'epsilon_cutoff_textgenerationwebui') { | |
| numSteps = 50; | |
| decimals = 1; | |
| } | |
| if (sliderID == 'nsigma') { | |
| numSteps = 50; | |
| decimals = 1; | |
| } | |
| //customize steps | |
| if (sliderID == 'mirostat_mode_textgenerationwebui' || | |
| sliderID == 'mirostat_mode_kobold') { | |
| numSteps = 2; | |
| } | |
| if (sliderID == 'encoder_rep_pen_textgenerationwebui') { | |
| numSteps = 14; | |
| } | |
| if (sliderID == 'max_context') { | |
| numSteps = 15; | |
| } | |
| if (sliderID == 'mirostat_tau_textgenerationwebui' || | |
| sliderID == 'top_k_textgenerationwebui' || | |
| sliderID == 'num_beams_textgenerationwebui' || | |
| sliderID == 'no_repeat_ngram_size_textgenerationwebui' || | |
| sliderID == 'epsilon_cutoff_textgenerationwebui' || | |
| sliderID == 'tfs_textgenerationwebui' || | |
| sliderID == 'min_p_textgenerationwebui' || | |
| sliderID == 'temp_textgenerationwebui' || | |
| sliderID == 'temp') { | |
| numSteps = 20; | |
| } | |
| if (sliderID == 'mirostat_eta_textgenerationwebui' || | |
| sliderID == 'penalty_alpha_textgenerationwebui' || | |
| sliderID == 'length_penalty_textgenerationwebui' || | |
| sliderID == 'min_temp_textgenerationwebui' || | |
| sliderID == 'max_temp_textgenerationwebui') { | |
| numSteps = 50; | |
| } | |
| //customize off values | |
| if (sliderID == 'presence_pen_textgenerationwebui' || | |
| sliderID == 'freq_pen_textgenerationwebui' || | |
| sliderID == 'mirostat_mode_textgenerationwebui' || | |
| sliderID == 'mirostat_mode_kobold' || | |
| sliderID == 'mirostat_tau_textgenerationwebui' || | |
| sliderID == 'mirostat_tau_kobold' || | |
| sliderID == 'mirostat_eta_textgenerationwebui' || | |
| sliderID == 'mirostat_eta_kobold' || | |
| sliderID == 'min_p_textgenerationwebui' || | |
| sliderID == 'min_p' || | |
| sliderID == 'no_repeat_ngram_size_textgenerationwebui' || | |
| sliderID == 'penalty_alpha_textgenerationwebui' || | |
| sliderID == 'length_penalty_textgenerationwebui' || | |
| sliderID == 'epsilon_cutoff_textgenerationwebui' || | |
| sliderID == 'nsigma' || | |
| sliderID == 'rep_pen_range' || | |
| sliderID == 'eta_cutoff_textgenerationwebui' || | |
| sliderID == 'top_a_textgenerationwebui' || | |
| sliderID == 'top_a' || | |
| sliderID == 'top_k_textgenerationwebui' || | |
| sliderID == 'top_k' || | |
| sliderID == 'rep_pen_slope' || | |
| sliderID == 'smoothing_factor_textgenerationwebui' || | |
| sliderID == 'smoothing_curve_textgenerationwebui' || | |
| sliderID == 'skew_textgenerationwebui' || | |
| sliderID == 'dry_multiplier_textgenerationwebui' || | |
| sliderID == 'min_length_textgenerationwebui') { | |
| offVal = 0; | |
| } | |
| if (sliderID == 'rep_pen_textgenerationwebui' || | |
| sliderID == 'rep_pen' || | |
| sliderID == 'tfs_textgenerationwebui' || | |
| sliderID == 'tfs' || | |
| sliderID == 'top_p_textgenerationwebui' || | |
| sliderID == 'top_p' || | |
| sliderID == 'typical_p_textgenerationwebui' || | |
| sliderID == 'typical_p' || | |
| sliderID == 'encoder_rep_pen_textgenerationwebui' || | |
| sliderID == 'temp_textgenerationwebui' || | |
| sliderID == 'temp' || | |
| sliderID == 'min_temp_textgenerationwebui' || | |
| sliderID == 'max_temp_textgenerationwebui' || | |
| sliderID == 'dynatemp_exponent_textgenerationwebui' || | |
| sliderID == 'guidance_scale_textgenerationwebui' || | |
| sliderID == 'rep_pen_slope_textgenerationwebui' || | |
| sliderID == 'guidance_scale') { | |
| offVal = 1; | |
| } | |
| if (sliderID == 'guidance_scale_textgenerationwebui') { | |
| numSteps = 78; | |
| } | |
| if (sliderID == 'top_k_textgenerationwebui') { | |
| sliderMin = 0; | |
| } | |
| //customize amt gen steps | |
| if (sliderID !== 'amount_gen' && sliderID !== 'rep_pen_range_textgenerationwebui') { | |
| stepScale = sliderRange / numSteps; | |
| } | |
| var newSlider = $('<div>') | |
| .attr('id', `${sliderID}_zenslider`) | |
| .css('width', '100%') | |
| .insertBefore(originalSlider); | |
| newSlider.slider({ | |
| value: sliderValue, | |
| step: stepScale, | |
| min: sliderMin, | |
| max: sliderMax, | |
| create: async function () { | |
| await delay(100); | |
| var handle = $(this).find('.ui-slider-handle'); | |
| var handleText, stepNumber, leftMargin; | |
| //handling creation of amt_gen | |
| if (newSlider.attr('id') == 'amount_gen_zenslider') { | |
| handleText = steps[sliderValue]; | |
| stepNumber = sliderValue; | |
| leftMargin = ((stepNumber) / numSteps) * 50 * -1; | |
| handle.text(handleText) | |
| .css('margin-left', `${leftMargin}px`); | |
| //console.log(`${newSlider.attr('id')} initial value:${handleText}, stepNum:${stepNumber}, numSteps:${numSteps}, left-margin:${leftMargin}`) | |
| } | |
| //handling creation of rep_pen_range for ooba | |
| else if (newSlider.attr('id') == 'rep_pen_range_textgenerationwebui_zenslider') { | |
| if ($('#rep_pen_range_textgenerationwebui_zensliders').length !== 0) { | |
| $('#rep_pen_range_textgenerationwebui_zensliders').remove(); | |
| } | |
| handleText = steps[sliderValue]; | |
| stepNumber = sliderValue; | |
| leftMargin = ((stepNumber) / numSteps) * 50 * -1; | |
| if (sliderValue === offVal) { | |
| handleText = 'Off'; | |
| handle.css('color', 'rgba(128,128,128,0.5'); | |
| } | |
| else if (sliderValue === allVal) { handleText = 'All'; } | |
| else { handle.css('color', ''); } | |
| handle.text(handleText) | |
| .css('margin-left', `${leftMargin}px`); | |
| //console.log(sliderValue, handleText, offVal, allVal) | |
| //console.log(`${newSlider.attr('id')} sliderValue = ${sliderValue}, handleText:${handleText}, stepNum:${stepNumber}, numSteps:${numSteps}, left-margin:${leftMargin}`) | |
| originalSlider.val(steps[sliderValue]); | |
| } | |
| //create all other sliders | |
| else { | |
| var numVal = Number(sliderValue).toFixed(decimals); | |
| offVal = Number(offVal).toFixed(decimals); | |
| if (numVal === offVal) { | |
| handle.text('Off').css('color', 'rgba(128,128,128,0.5'); | |
| } else { | |
| handle.text(numVal).css('color', ''); | |
| } | |
| stepNumber = ((sliderValue - sliderMin) / stepScale); | |
| leftMargin = (stepNumber / numSteps) * 50 * -1; | |
| originalSlider.val(numVal) | |
| .data('newSlider', newSlider); | |
| //console.log(`${newSlider.attr('id')} sliderValue = ${sliderValue}, handleText:${handleText, numVal}, stepNum:${stepNumber}, numSteps:${numSteps}, left-margin:${leftMargin}`) | |
| var isManualInput = false; | |
| var valueBeforeManualInput; | |
| handle.css('margin-left', `${leftMargin}px`) | |
| .attr('contenteditable', 'true') | |
| //these sliders need listeners for manual inputs | |
| .on('click', function () { | |
| //this just selects all the text in the handle so user can overwrite easily | |
| //needed because JQUery UI uses left/right arrow keys as well as home/end to move the slider.. | |
| valueBeforeManualInput = newSlider.val(); | |
| console.log(valueBeforeManualInput); | |
| let handleElement = handle.get(0); | |
| let range = document.createRange(); | |
| range.selectNodeContents(handleElement); | |
| let selection = window.getSelection(); | |
| selection.removeAllRanges(); | |
| selection.addRange(range); | |
| }) | |
| .on('keyup', function (e) { | |
| valueBeforeManualInput = numVal; | |
| //console.log(valueBeforeManualInput, numVal, handleText); | |
| isManualInput = true; | |
| //allow enter to trigger slider update | |
| if (e.key === 'Enter') { | |
| e.preventDefault(); | |
| handle.trigger('blur'); | |
| } | |
| }) | |
| //trigger slider changes when user clicks away | |
| .on('mouseup blur', function () { | |
| let manualInput = parseFloat(handle.text()).toFixed(decimals); | |
| if (isManualInput) { | |
| //disallow manual inputs outside acceptable range | |
| if (manualInput >= sliderMin && manualInput <= sliderMax) { | |
| //if value is ok, assign to slider and update handle text and position | |
| newSlider.val(manualInput); | |
| handleSlideEvent.call(newSlider, null, { value: parseFloat(manualInput) }, 'manual'); | |
| valueBeforeManualInput = manualInput; | |
| } else { | |
| //if value not ok, warn and reset to last known valid value | |
| toastr.warning(`Invalid value. Must be between ${sliderMin} and ${sliderMax}`); | |
| console.log(valueBeforeManualInput); | |
| newSlider.val(valueBeforeManualInput); | |
| handle.text(valueBeforeManualInput); | |
| handleSlideEvent.call(newSlider, null, { value: parseFloat(valueBeforeManualInput) }, 'manual'); | |
| } | |
| } | |
| isManualInput = false; | |
| }); | |
| } | |
| //zenSlider creation done, hide the original | |
| originalSlider.hide(); | |
| }, | |
| slide: handleSlideEvent, | |
| }); | |
| function handleSlideEvent(event, ui, type) { | |
| var handle = $(this).find('.ui-slider-handle'); | |
| var numVal = Number(ui.value).toFixed(decimals); | |
| offVal = Number(offVal).toFixed(decimals); | |
| allVal = Number(allVal).toFixed(decimals); | |
| console.log(numVal, sliderMin, sliderMax, numVal > sliderMax, numVal < sliderMin); | |
| if (numVal > sliderMax) { numVal = sliderMax; } | |
| if (numVal < sliderMin) { numVal = sliderMin; } | |
| var stepNumber = ((ui.value - sliderMin) / stepScale).toFixed(0); | |
| var handleText = (ui.value); | |
| var leftMargin = (stepNumber / numSteps) * 50 * -1; | |
| var perStepPercent = 1 / numSteps; //how far in % each step should be on the slider | |
| var leftPos = newSlider.width() * (stepNumber * perStepPercent); //how big of a left margin to give the slider for manual inputs | |
| /* console.log(` | |
| numVal: ${numVal}, | |
| sliderMax: ${sliderMax} | |
| sliderMin: ${sliderMin} | |
| sliderValRange: ${sliderValRange} | |
| stepScale: ${stepScale} | |
| Step: ${stepNumber} of ${numSteps} | |
| offVal: ${offVal} | |
| allVal = ${allVal} | |
| initial value: ${handleText} | |
| left-margin: ${leftMargin} | |
| width: ${newSlider.width()} | |
| percent of max: ${percentOfMax} | |
| left: ${leftPos}`) */ | |
| //special handling for response length slider, pulls text aliases for step values from an array | |
| if (newSlider.attr('id') == 'amount_gen_zenslider') { | |
| handleText = steps[stepNumber]; | |
| handle.text(handleText); | |
| newSlider.val(stepNumber); | |
| numVal = steps[stepNumber]; | |
| } | |
| //special handling for TextCompletion rep pen range slider, pulls text aliases for step values from an array | |
| else if (newSlider.attr('id') == 'rep_pen_range_textgenerationwebui_zenslider') { | |
| handleText = steps[stepNumber]; | |
| handle.text(handleText); | |
| newSlider.val(stepNumber); | |
| if (numVal === offVal) { handle.text('Off').css('color', 'rgba(128,128,128,0.5'); } | |
| else if (numVal === allVal) { handle.text('All'); } | |
| else { handle.css('color', ''); } | |
| numVal = steps[stepNumber]; | |
| } | |
| //everything else uses the flat slider value | |
| //also note: the above sliders are not custom inputtable due to the array aliasing | |
| else { | |
| //show 'off' if disabled value is set | |
| if (numVal === offVal) { handle.text('Off').css('color', 'rgba(128,128,128,0.5'); } | |
| else { handle.text(ui.value.toFixed(decimals)).css('color', ''); } | |
| newSlider.val(handleText); | |
| } | |
| //for manually typed-in values we must adjust left position because JQUI doesn't do it for us | |
| handle.css('left', leftPos); | |
| //adjust a negative left margin to avoid overflowing right side of slider body | |
| handle.css('margin-left', `${leftMargin}px`); | |
| originalSlider.val(numVal); | |
| originalSlider.trigger('input'); | |
| originalSlider.trigger('change'); | |
| } | |
| } | |
| function switchUiMode() { | |
| $('body').toggleClass('no-blur', power_user.fast_ui_mode); | |
| $('#fast_ui_mode').prop('checked', power_user.fast_ui_mode); | |
| if (power_user.fast_ui_mode) { | |
| $('#blur-strength-block').css('opacity', '0.2'); | |
| $('#blur_strength').prop('disabled', true); | |
| } else { | |
| $('#blur-strength-block').css('opacity', '1'); | |
| $('#blur_strength').prop('disabled', false); | |
| } | |
| } | |
| function toggleWaifu() { | |
| $('#waifuMode').trigger('click'); | |
| return ''; | |
| } | |
| function switchWaifuMode() { | |
| $('body').toggleClass('waifuMode', power_user.waifuMode); | |
| $('#waifuMode').prop('checked', power_user.waifuMode); | |
| scrollChatToBottom(); | |
| } | |
| function switchSpoilerMode() { | |
| if (power_user.spoiler_free_mode) { | |
| $('#descriptionWrapper').hide(); | |
| $('#firstMessageWrapper').hide(); | |
| $('#spoiler_free_desc').addClass('flex1'); | |
| $('#creator_notes_spoiler').show(); | |
| } | |
| else { | |
| $('#descriptionWrapper').show(); | |
| $('#firstMessageWrapper').show(); | |
| $('#spoiler_free_desc').removeClass('flex1'); | |
| $('#creator_notes_spoiler').hide(); | |
| } | |
| } | |
| function peekSpoilerMode() { | |
| $('#descriptionWrapper').toggle(); | |
| $('#firstMessageWrapper').toggle(); | |
| $('#creator_notes_spoiler').toggle(); | |
| $('#spoiler_free_desc').toggleClass('flex1'); | |
| } | |
| function switchMovingUI() { | |
| $('.drawer-content.maximized').each(function () { | |
| $(this).find('.inline-drawer-maximize').trigger('click'); | |
| }); | |
| $('body').toggleClass('movingUI', power_user.movingUI); | |
| if (power_user.movingUI === true) { | |
| initMovingUI(); | |
| if (power_user.movingUIState) { | |
| loadMovingUIState(); | |
| } | |
| } else { | |
| if (Object.keys(power_user.movingUIState).length !== 0) { | |
| power_user.movingUIState = {}; | |
| resetMovablePanels(); | |
| saveSettingsDebounced(); | |
| } | |
| } | |
| } | |
| function applyNoShadows() { | |
| $('body').toggleClass('noShadows', power_user.noShadows); | |
| $('#noShadowsmode').prop('checked', power_user.noShadows); | |
| if (power_user.noShadows) { | |
| $('#shadow-width-block').css('opacity', '0.2'); | |
| $('#shadow_width').prop('disabled', true); | |
| } else { | |
| $('#shadow-width-block').css('opacity', '1'); | |
| $('#shadow_width').prop('disabled', false); | |
| } | |
| scrollChatToBottom(); | |
| } | |
| function applyAvatarStyle() { | |
| $('body').toggleClass('big-avatars', power_user.avatar_style === avatar_styles.RECTANGULAR); | |
| $('body').toggleClass('square-avatars', power_user.avatar_style === avatar_styles.SQUARE); | |
| $('body').toggleClass('rounded-avatars', power_user.avatar_style === avatar_styles.ROUNDED); | |
| $('#avatar_style').val(power_user.avatar_style).prop('selected', true); | |
| } | |
| function applyChatDisplay() { | |
| if (!power_user.chat_display === (null || undefined)) { | |
| console.debug('applyChatDisplay: saw no chat display type defined'); | |
| return; | |
| } | |
| console.debug(`poweruser.chat_display ${power_user.chat_display}`); | |
| $('#chat_display').val(power_user.chat_display).prop('selected', true); | |
| switch (power_user.chat_display) { | |
| case 0: { | |
| console.debug('applying default chat'); | |
| $('body').removeClass('bubblechat'); | |
| $('body').removeClass('documentstyle'); | |
| break; | |
| } | |
| case 1: { | |
| console.debug('applying bubblechat'); | |
| $('body').addClass('bubblechat'); | |
| $('body').removeClass('documentstyle'); | |
| break; | |
| } | |
| case 2: { | |
| console.debug('applying document style'); | |
| $('body').removeClass('bubblechat'); | |
| $('body').addClass('documentstyle'); | |
| break; | |
| } | |
| } | |
| } | |
| function applyToastrPosition() { | |
| if (!toastPositionClasses.includes(power_user.toastr_position)) { | |
| power_user.toastr_position = defaultToastPosition; | |
| console.warn(`applyToastrPosition: invalid toastr position, defaulting to ${defaultToastPosition}`); | |
| } | |
| toastr.options.positionClass = power_user.toastr_position; | |
| fixToastrForDialogs(); | |
| $('#toastr_position').val(power_user.toastr_position).prop('selected', true); | |
| } | |
| function applyChatWidth(type) { | |
| if (type === 'forced') { | |
| let r = document.documentElement; | |
| r.style.setProperty('--sheldWidth', `${power_user.chat_width}vw`); | |
| $('#chat_width_slider').val(power_user.chat_width); | |
| //document.documentElement.style.setProperty('--sheldWidth', power_user.chat_width); | |
| } else { | |
| //this is to prevent the slider from updating page in real time | |
| $('#chat_width_slider').off('mouseup touchend').on('mouseup touchend', async () => { | |
| // This is a hack for Firefox to let it render before applying the block width. | |
| // Otherwise it takes the incorrect slider position with the new value AFTER the resizing. | |
| await delay(1); | |
| document.documentElement.style.setProperty('--sheldWidth', `${power_user.chat_width}vw`); | |
| await delay(1); | |
| }); | |
| } | |
| $('#chat_width_slider_counter').val(power_user.chat_width); | |
| } | |
| function applyThemeColor(type) { | |
| if (type === 'main') { | |
| document.documentElement.style.setProperty('--SmartThemeBodyColor', power_user.main_text_color); | |
| const color = power_user.main_text_color.split('(')[1].split(')')[0].split(','); | |
| document.documentElement.style.setProperty('--SmartThemeCheckboxBgColorR', color[0]); | |
| document.documentElement.style.setProperty('--SmartThemeCheckboxBgColorG', color[1]); | |
| document.documentElement.style.setProperty('--SmartThemeCheckboxBgColorB', color[2]); | |
| document.documentElement.style.setProperty('--SmartThemeCheckboxBgColorA', color[3]); | |
| } | |
| if (type === 'italics') { | |
| document.documentElement.style.setProperty('--SmartThemeEmColor', power_user.italics_text_color); | |
| } | |
| if (type === 'underline') { | |
| document.documentElement.style.setProperty('--SmartThemeUnderlineColor', power_user.underline_text_color); | |
| } | |
| if (type === 'quote') { | |
| document.documentElement.style.setProperty('--SmartThemeQuoteColor', power_user.quote_text_color); | |
| } | |
| /* if (type === 'fastUIBG') { | |
| document.documentElement.style.setProperty('--SmartThemeFastUIBGColor', power_user.fastui_bg_color); | |
| } */ | |
| if (type === 'blurTint') { | |
| let metaThemeColor = document.querySelector('meta[name=theme-color]'); | |
| document.documentElement.style.setProperty('--SmartThemeBlurTintColor', power_user.blur_tint_color); | |
| metaThemeColor.setAttribute('content', power_user.blur_tint_color); | |
| } | |
| if (type === 'chatTint') { | |
| document.documentElement.style.setProperty('--SmartThemeChatTintColor', power_user.chat_tint_color); | |
| } | |
| if (type === 'userMesBlurTint') { | |
| document.documentElement.style.setProperty('--SmartThemeUserMesBlurTintColor', power_user.user_mes_blur_tint_color); | |
| } | |
| if (type === 'botMesBlurTint') { | |
| document.documentElement.style.setProperty('--SmartThemeBotMesBlurTintColor', power_user.bot_mes_blur_tint_color); | |
| } | |
| if (type === 'shadow') { | |
| document.documentElement.style.setProperty('--SmartThemeShadowColor', power_user.shadow_color); | |
| } | |
| if (type === 'border') { | |
| document.documentElement.style.setProperty('--SmartThemeBorderColor', power_user.border_color); | |
| } | |
| } | |
| function applyCustomCSS() { | |
| $('#customCSS').val(power_user.custom_css); | |
| var styleId = 'custom-style'; | |
| var style = document.getElementById(styleId); | |
| if (!style) { | |
| style = document.createElement('style'); | |
| style.setAttribute('type', 'text/css'); | |
| style.setAttribute('id', styleId); | |
| document.head.appendChild(style); | |
| } | |
| style.innerHTML = power_user.custom_css; | |
| } | |
| function applyBlurStrength() { | |
| document.documentElement.style.setProperty('--blurStrength', String(power_user.blur_strength)); | |
| $('#blur_strength_counter').val(power_user.blur_strength); | |
| $('#blur_strength').val(power_user.blur_strength); | |
| } | |
| function applyShadowWidth() { | |
| document.documentElement.style.setProperty('--shadowWidth', String(power_user.shadow_width)); | |
| $('#shadow_width_counter').val(power_user.shadow_width); | |
| $('#shadow_width').val(power_user.shadow_width); | |
| } | |
| function applyFontScale(type) { | |
| //this is to allow forced setting on page load, theme swap, etc | |
| if (type === 'forced') { | |
| document.documentElement.style.setProperty('--fontScale', String(power_user.font_scale)); | |
| } else { | |
| //this is to prevent the slider from updating page in real time | |
| $('#font_scale').off('mouseup touchend').on('mouseup touchend', () => { | |
| document.documentElement.style.setProperty('--fontScale', String(power_user.font_scale)); | |
| }); | |
| } | |
| $('#font_scale_counter').val(power_user.font_scale); | |
| $('#font_scale').val(power_user.font_scale); | |
| } | |
| function applyTheme(name) { | |
| const theme = themes.find(x => x.name == name); | |
| if (!theme) { | |
| return; | |
| } | |
| const themeProperties = [ | |
| { key: 'main_text_color', selector: '#main-text-color-picker', type: 'main' }, | |
| { key: 'italics_text_color', selector: '#italics-color-picker', type: 'italics' }, | |
| { key: 'underline_text_color', selector: '#underline-color-picker', type: 'underline' }, | |
| { key: 'quote_text_color', selector: '#quote-color-picker', type: 'quote' }, | |
| { key: 'blur_tint_color', selector: '#blur-tint-color-picker', type: 'blurTint' }, | |
| { key: 'chat_tint_color', selector: '#chat-tint-color-picker', type: 'chatTint' }, | |
| { key: 'user_mes_blur_tint_color', selector: '#user-mes-blur-tint-color-picker', type: 'userMesBlurTint' }, | |
| { key: 'bot_mes_blur_tint_color', selector: '#bot-mes-blur-tint-color-picker', type: 'botMesBlurTint' }, | |
| { key: 'shadow_color', selector: '#shadow-color-picker', type: 'shadow' }, | |
| { key: 'border_color', selector: '#border-color-picker', type: 'border' }, | |
| { | |
| key: 'blur_strength', | |
| action: () => { | |
| applyBlurStrength(); | |
| }, | |
| }, | |
| { | |
| key: 'custom_css', | |
| action: () => { | |
| applyCustomCSS(); | |
| }, | |
| }, | |
| { | |
| key: 'shadow_width', | |
| action: () => { | |
| applyShadowWidth(); | |
| }, | |
| }, | |
| { | |
| key: 'font_scale', | |
| action: () => { | |
| applyFontScale('forced'); | |
| }, | |
| }, | |
| { | |
| key: 'fast_ui_mode', | |
| action: () => { | |
| switchUiMode(); | |
| }, | |
| }, | |
| { | |
| key: 'waifuMode', | |
| action: () => { | |
| switchWaifuMode(); | |
| }, | |
| }, | |
| { | |
| key: 'chat_display', | |
| action: () => { | |
| applyChatDisplay(); | |
| }, | |
| }, | |
| { | |
| key: 'toastr_position', | |
| action: () => { | |
| applyToastrPosition(); | |
| }, | |
| }, | |
| { | |
| key: 'avatar_style', | |
| action: () => { | |
| applyAvatarStyle(); | |
| }, | |
| }, | |
| { | |
| key: 'noShadows', | |
| action: () => { | |
| applyNoShadows(); | |
| }, | |
| }, | |
| { | |
| key: 'chat_width', | |
| action: () => { | |
| // If chat width is not set, set it to 50 | |
| if (!power_user.chat_width) { | |
| power_user.chat_width = 50; | |
| } | |
| applyChatWidth('forced'); | |
| }, | |
| }, | |
| { | |
| key: 'timer_enabled', | |
| action: () => { | |
| switchTimer(); | |
| }, | |
| }, | |
| { | |
| key: 'timestamps_enabled', | |
| action: () => { | |
| switchTimestamps(); | |
| }, | |
| }, | |
| { | |
| key: 'timestamp_model_icon', | |
| action: () => { | |
| switchIcons(); | |
| }, | |
| }, | |
| { | |
| key: 'message_token_count_enabled', | |
| action: () => { | |
| switchTokenCount(); | |
| }, | |
| }, | |
| { | |
| key: 'mesIDDisplay_enabled', | |
| action: () => { | |
| switchMesIDDisplay(); | |
| }, | |
| }, | |
| { | |
| key: 'hideChatAvatars_enabled', | |
| action: () => { | |
| switchHideChatAvatars(); | |
| }, | |
| }, | |
| { | |
| key: 'expand_message_actions', | |
| action: () => { | |
| switchMessageActions(); | |
| }, | |
| }, | |
| { | |
| key: 'enableZenSliders', | |
| action: () => { | |
| switchMessageActions(); | |
| }, | |
| }, | |
| { | |
| key: 'enableLabMode', | |
| action: () => { | |
| switchMessageActions(); | |
| }, | |
| }, | |
| { | |
| key: 'hotswap_enabled', | |
| action: () => { | |
| switchHotswap(); | |
| }, | |
| }, | |
| { | |
| key: 'bogus_folders', | |
| action: () => { | |
| $('#bogus_folders').prop('checked', power_user.bogus_folders); | |
| printCharactersDebounced(); | |
| }, | |
| }, | |
| { | |
| key: 'zoomed_avatar_magnification', | |
| action: () => { | |
| $('#zoomed_avatar_magnification').prop('checked', power_user.zoomed_avatar_magnification); | |
| printCharactersDebounced(); | |
| }, | |
| }, | |
| { | |
| key: 'reduced_motion', | |
| action: () => { | |
| $('#reduced_motion').prop('checked', power_user.reduced_motion); | |
| switchReducedMotion(); | |
| }, | |
| }, | |
| { | |
| key: 'compact_input_area', | |
| action: () => { | |
| $('#compact_input_area').prop('checked', power_user.compact_input_area); | |
| switchCompactInputArea(); | |
| }, | |
| }, | |
| { | |
| key: 'show_swipe_num_all_messages', | |
| action: () => { | |
| $('#show_swipe_num_all_messages').prop('checked', power_user.show_swipe_num_all_messages); | |
| switchSwipeNumAllMessages(); | |
| }, | |
| }, | |
| { | |
| key: 'click_to_edit', | |
| action: () => { | |
| $('#click_to_edit').prop('checked', power_user.click_to_edit); | |
| }, | |
| }, | |
| ]; | |
| for (const { key, selector, type, action } of themeProperties) { | |
| if (theme[key] !== undefined) { | |
| power_user[key] = theme[key]; | |
| if (selector) $(selector).attr('color', power_user[key]); | |
| if (type) applyThemeColor(type); | |
| if (action) action(); | |
| } else { | |
| console.debug(`Empty theme key: ${key}`); | |
| } | |
| } | |
| console.log('theme applied: ' + name); | |
| } | |
| async function applyMovingUIPreset(name) { | |
| await resetMovablePanels('quiet'); | |
| const movingUIPreset = movingUIPresets.find(x => x.name == name); | |
| if (!movingUIPreset) { | |
| return; | |
| } | |
| power_user.movingUIState = movingUIPreset.movingUIState; | |
| console.log('MovingUI Preset applied: ' + name); | |
| loadMovingUIState(); | |
| saveSettingsDebounced(); | |
| } | |
| /** | |
| * Register a function to be executed when the debug menu is opened. | |
| * @param {string} functionId Unique ID for the function. | |
| * @param {string} name Name of the function. | |
| * @param {string} description Description of the function. | |
| * @param {function} func Function to be executed. | |
| */ | |
| export function registerDebugFunction(functionId, name, description, func) { | |
| debug_functions.push({ functionId, name, description, func }); | |
| } | |
| async function showDebugMenu() { | |
| const template = await renderTemplateAsync('debug', { functions: debug_functions }); | |
| callGenericPopup(template, POPUP_TYPE.TEXT, '', { wide: true, large: true, allowVerticalScrolling: true }); | |
| } | |
| function applyPowerUserSettings() { | |
| switchUiMode(); | |
| applyFontScale('forced'); | |
| applyThemeColor(); | |
| applyChatWidth('forced'); | |
| applyAvatarStyle(); | |
| applyBlurStrength(); | |
| applyShadowWidth(); | |
| applyCustomCSS(); | |
| switchMovingUI(); | |
| applyNoShadows(); | |
| switchHotswap(); | |
| switchTimer(); | |
| switchTimestamps(); | |
| switchIcons(); | |
| switchMesIDDisplay(); | |
| switchHideChatAvatars(); | |
| switchTokenCount(); | |
| switchMessageActions(); | |
| switchSwipeNumAllMessages(); | |
| } | |
| export function applyStylePins() { | |
| try { | |
| const existingPins = document.querySelector('#chat > .style-pins'); | |
| if (existingPins) { | |
| existingPins.remove(); | |
| } | |
| if (!power_user.pin_styles) { | |
| return; | |
| } | |
| const firstDisplayed = getFirstDisplayedMessageId(); | |
| if (firstDisplayed === 0 || !isFinite(firstDisplayed)) { | |
| return; | |
| } | |
| const chatElement = document.getElementById('chat'); | |
| if (!chatElement) { | |
| return; | |
| } | |
| const firstMessage = chat[0]; | |
| if (!firstMessage) { | |
| return; | |
| } | |
| const formattedMessage = messageFormatting(firstMessage.mes, firstMessage.name, firstMessage.is_system, firstMessage.is_user, 0, {}, false); | |
| const htmlElement = document.createElement('div'); | |
| htmlElement.innerHTML = formattedMessage; | |
| const styleTags = htmlElement.querySelectorAll('style'); | |
| if (styleTags.length === 0) { | |
| return; | |
| } | |
| const pinsElement = document.createElement('div'); | |
| pinsElement.classList.add('style-pins'); | |
| pinsElement.append(...Array.from(styleTags)); | |
| chatElement.prepend(pinsElement); | |
| } catch (error) { | |
| console.error('Error applying style pins:', error); | |
| } | |
| } | |
| function getExampleMessagesBehavior() { | |
| if (power_user.strip_examples) { | |
| return 'strip'; | |
| } | |
| if (power_user.pin_examples) { | |
| return 'keep'; | |
| } | |
| return 'normal'; | |
| } | |
| //MARK: loadPowerUser | |
| async function loadPowerUserSettings(settings, data) { | |
| const defaultStscript = JSON.parse(JSON.stringify(power_user.stscript)); | |
| // Load from settings.json | |
| if (settings.power_user !== undefined) { | |
| // Migrate old preference to a new setting | |
| if (settings.power_user.click_to_edit === undefined && settings.power_user.chat_display === chat_styles.DOCUMENT) { | |
| settings.power_user.click_to_edit = true; | |
| } | |
| Object.assign(power_user, settings.power_user); | |
| } | |
| if (power_user.stscript === undefined) { | |
| power_user.stscript = defaultStscript; | |
| } else { | |
| if (power_user.stscript.autocomplete === undefined) { | |
| power_user.stscript.autocomplete = defaultStscript.autocomplete; | |
| } else { | |
| if (power_user.stscript.autocomplete.state === undefined) { | |
| power_user.stscript.autocomplete.state = defaultStscript.autocomplete.state; | |
| } | |
| if (power_user.stscript.autocomplete.width === undefined) { | |
| power_user.stscript.autocomplete.width = defaultStscript.autocomplete.width; | |
| } | |
| if (power_user.stscript.autocomplete.font === undefined) { | |
| power_user.stscript.autocomplete.font = defaultStscript.autocomplete.font; | |
| } | |
| if (power_user.stscript.autocomplete.style === undefined) { | |
| power_user.stscript.autocomplete.style = power_user.stscript.autocomplete_style || defaultStscript.autocomplete.style; | |
| } | |
| if (power_user.stscript.autocomplete.select === undefined) { | |
| power_user.stscript.autocomplete.select = defaultStscript.autocomplete.select; | |
| } | |
| } | |
| if (power_user.stscript.parser === undefined) { | |
| power_user.stscript.parser = defaultStscript.parser; | |
| } else if (power_user.stscript.parser.flags === undefined) { | |
| power_user.stscript.parser.flags = defaultStscript.parser.flags; | |
| } | |
| // Cleanup old flags | |
| delete power_user.stscript.autocomplete_style; | |
| } | |
| if (data.themes !== undefined) { | |
| themes = data.themes; | |
| } | |
| if (data.movingUIPresets !== undefined) { | |
| movingUIPresets = data.movingUIPresets; | |
| } | |
| if (data.context !== undefined) { | |
| context_presets = data.context; | |
| } | |
| if (power_user.chat_display === '') { | |
| power_user.chat_display = chat_styles.DEFAULT; | |
| } | |
| if (power_user.waifuMode === '') { | |
| power_user.waifuMode = false; | |
| } | |
| if (power_user.chat_width === '') { | |
| power_user.chat_width = 50; | |
| } | |
| if (power_user.tokenizer === tokenizers.LEGACY) { | |
| power_user.tokenizer = tokenizers.GPT2; | |
| } | |
| // Clean up old/legacy settings | |
| if (power_user.import_card_tags !== undefined) { | |
| power_user.tag_import_setting = power_user.import_card_tags ? tag_import_setting.ASK : tag_import_setting.NONE; | |
| delete power_user.import_card_tags; | |
| } | |
| $('#single_line').prop('checked', power_user.single_line); | |
| $('#relaxed_api_urls').prop('checked', power_user.relaxed_api_urls); | |
| $('#world_import_dialog').prop('checked', power_user.world_import_dialog); | |
| $('#enable_auto_select_input').prop('checked', power_user.enable_auto_select_input); | |
| $('#enable_md_hotkeys').prop('checked', power_user.enable_md_hotkeys); | |
| $('#trim_spaces').prop('checked', power_user.trim_spaces); | |
| $('#continue_on_send').prop('checked', power_user.continue_on_send); | |
| $('#quick_continue').prop('checked', power_user.quick_continue); | |
| $('#quick_impersonate').prop('checked', power_user.quick_continue); | |
| $('#mes_continue').css('display', power_user.quick_continue ? '' : 'none'); | |
| $('#mes_impersonate').css('display', power_user.quick_impersonate ? '' : 'none'); | |
| $('#gestures-checkbox').prop('checked', power_user.gestures); | |
| $('#auto_swipe').prop('checked', power_user.auto_swipe); | |
| $('#auto_swipe_minimum_length').val(power_user.auto_swipe_minimum_length); | |
| $('#auto_swipe_blacklist').val(power_user.auto_swipe_blacklist.join(', ')); | |
| $('#auto_swipe_blacklist_threshold').val(power_user.auto_swipe_blacklist_threshold); | |
| $('#custom_stopping_strings').text(power_user.custom_stopping_strings); | |
| $('#custom_stopping_strings_macro').prop('checked', power_user.custom_stopping_strings_macro); | |
| $('#fuzzy_search_checkbox').prop('checked', power_user.fuzzy_search); | |
| $('#persona_show_notifications').prop('checked', power_user.persona_show_notifications); | |
| $('#persona_allow_multi_connections').prop('checked', power_user.persona_allow_multi_connections); | |
| $('#persona_auto_lock').prop('checked', power_user.persona_auto_lock); | |
| $('#encode_tags').prop('checked', power_user.encode_tags); | |
| $('#example_messages_behavior').val(getExampleMessagesBehavior()); | |
| $(`#example_messages_behavior option[value="${getExampleMessagesBehavior()}"]`).prop('selected', true); | |
| $('#context_derived').parent().find('i').toggleClass('toggleEnabled', !!power_user.context_derived); | |
| $('#context_size_derived').prop('checked', !!power_user.context_size_derived); | |
| $('#console_log_prompts').prop('checked', power_user.console_log_prompts); | |
| $('#request_token_probabilities').prop('checked', power_user.request_token_probabilities); | |
| $('#show_group_chat_queue').prop('checked', power_user.show_group_chat_queue); | |
| $('#auto_fix_generated_markdown').prop('checked', power_user.auto_fix_generated_markdown); | |
| $('#auto_scroll_chat_to_bottom').prop('checked', power_user.auto_scroll_chat_to_bottom); | |
| $('#bogus_folders').prop('checked', power_user.bogus_folders); | |
| $('#zoomed_avatar_magnification').prop('checked', power_user.zoomed_avatar_magnification); | |
| $(`#tokenizer option[value="${power_user.tokenizer}"]`).attr('selected', true); | |
| $(`#send_on_enter option[value=${power_user.send_on_enter}]`).attr('selected', true); | |
| $('#confirm_message_delete').prop('checked', power_user.confirm_message_delete !== undefined ? !!power_user.confirm_message_delete : true); | |
| $('#spoiler_free_mode').prop('checked', power_user.spoiler_free_mode); | |
| $('#collapse-newlines-checkbox').prop('checked', power_user.collapse_newlines); | |
| $('#always-force-name2-checkbox').prop('checked', power_user.always_force_name2); | |
| $('#trim_sentences_checkbox').prop('checked', power_user.trim_sentences); | |
| $('#disable_group_trimming').prop('checked', power_user.disable_group_trimming); | |
| $('#markdown_escape_strings').val(power_user.markdown_escape_strings); | |
| $('#fast_ui_mode').prop('checked', power_user.fast_ui_mode); | |
| $('#waifuMode').prop('checked', power_user.waifuMode); | |
| $('#movingUImode').prop('checked', power_user.movingUI); | |
| $('#noShadowsmode').prop('checked', power_user.noShadows); | |
| $('#start_reply_with').text(power_user.user_prompt_bias); | |
| $('#chat-show-reply-prefix-checkbox').prop('checked', power_user.show_user_prompt_bias); | |
| $('#auto_continue_enabled').prop('checked', power_user.auto_continue.enabled); | |
| $('#auto_continue_allow_chat_completions').prop('checked', power_user.auto_continue.allow_chat_completions); | |
| $('#auto_continue_target_length').val(power_user.auto_continue.target_length); | |
| $('#play_message_sound').prop('checked', power_user.play_message_sound); | |
| $('#play_sound_unfocused').prop('checked', power_user.play_sound_unfocused); | |
| $('#never_resize_avatars').prop('checked', power_user.never_resize_avatars); | |
| $('#show_card_avatar_urls').prop('checked', power_user.show_card_avatar_urls); | |
| $('#auto_save_msg_edits').prop('checked', power_user.auto_save_msg_edits); | |
| $('#allow_name1_display').prop('checked', power_user.allow_name1_display); | |
| $('#allow_name2_display').prop('checked', power_user.allow_name2_display); | |
| //$("#removeXML").prop("checked", power_user.removeXML); | |
| $('#hotswapEnabled').prop('checked', power_user.hotswap_enabled); | |
| $('#messageTimerEnabled').prop('checked', power_user.timer_enabled); | |
| $('#messageTimestampsEnabled').prop('checked', power_user.timestamps_enabled); | |
| $('#messageModelIconEnabled').prop('checked', power_user.timestamp_model_icon); | |
| $('#mesIDDisplayEnabled').prop('checked', power_user.mesIDDisplay_enabled); | |
| $('#hideChatAvatarsEnabled').prop('checked', power_user.hideChatAvatars_enabled); | |
| $('#prefer_character_prompt').prop('checked', power_user.prefer_character_prompt); | |
| $('#prefer_character_jailbreak').prop('checked', power_user.prefer_character_jailbreak); | |
| $('#enableZenSliders').prop('checked', power_user.enableZenSliders).trigger('input'); | |
| $('#enableLabMode').prop('checked', power_user.enableLabMode).trigger('input', { fromInit: true }); | |
| $(`input[name="avatar_style"][value="${power_user.avatar_style}"]`).prop('checked', true); | |
| $(`#chat_display option[value=${power_user.chat_display}]`).attr('selected', true).trigger('change'); | |
| $(`#toastr_position option[value=${power_user.toastr_position}]`).attr('selected', true).trigger('change'); | |
| $('#chat_width_slider').val(power_user.chat_width); | |
| $('#token_padding').val(power_user.token_padding); | |
| $('#aux_field').val(power_user.aux_field); | |
| $('#tag_import_setting').val(power_user.tag_import_setting); | |
| $('#stscript_autocomplete_state').val(power_user.stscript.autocomplete.state).trigger('input'); | |
| $('#stscript_autocomplete_autoHide').prop('checked', power_user.stscript.autocomplete.autoHide ?? false).trigger('input'); | |
| $('#stscript_matching').val(power_user.stscript.matching ?? 'fuzzy'); | |
| $('#stscript_autocomplete_style').val(power_user.stscript.autocomplete.style ?? 'theme'); | |
| document.body.setAttribute('data-stscript-style', power_user.stscript.autocomplete.style); | |
| $('#stscript_autocomplete_select').val(power_user.stscript.autocomplete.select ?? (AUTOCOMPLETE_SELECT_KEY.TAB + AUTOCOMPLETE_SELECT_KEY.ENTER)); | |
| $('#stscript_parser_flag_strict_escaping').prop('checked', power_user.stscript.parser.flags[PARSER_FLAG.STRICT_ESCAPING] ?? false); | |
| $('#stscript_parser_flag_replace_getvar').prop('checked', power_user.stscript.parser.flags[PARSER_FLAG.REPLACE_GETVAR] ?? false); | |
| $('#stscript_autocomplete_font_scale').val(power_user.stscript.autocomplete.font.scale ?? defaultStscript.autocomplete.font.scale); | |
| $('#stscript_autocomplete_font_scale_counter').val(power_user.stscript.autocomplete.font.scale ?? defaultStscript.autocomplete.font.scale); | |
| document.body.style.setProperty('--ac-font-scale', power_user.stscript.autocomplete.font.scale ?? defaultStscript.autocomplete.font.scale.toString()); | |
| $('#stscript_autocomplete_width_left').val(power_user.stscript.autocomplete.width.left ?? AUTOCOMPLETE_WIDTH.CHAT); | |
| document.querySelector('#stscript_autocomplete_width_left')?.dispatchEvent(new Event('input', { bubbles: true })); | |
| $('#stscript_autocomplete_width_right').val(power_user.stscript.autocomplete.width.right ?? AUTOCOMPLETE_WIDTH.CHAT); | |
| document.querySelector('#stscript_autocomplete_width_right')?.dispatchEvent(new Event('input', { bubbles: true })); | |
| $('#restore_user_input').prop('checked', power_user.restore_user_input); | |
| $('#chat_truncation').val(power_user.chat_truncation); | |
| $('#chat_truncation_counter').val(power_user.chat_truncation); | |
| $('#streaming_fps').val(power_user.streaming_fps); | |
| $('#streaming_fps_counter').val(power_user.streaming_fps); | |
| $('#smooth_streaming').prop('checked', power_user.smooth_streaming); | |
| $('#smooth_streaming_speed').val(power_user.smooth_streaming_speed); | |
| $('#font_scale').val(power_user.font_scale); | |
| $('#font_scale_counter').val(power_user.font_scale); | |
| $('#blur_strength').val(power_user.blur_strength); | |
| $('#blur_strength_counter').val(power_user.blur_strength); | |
| $('#shadow_width').val(power_user.shadow_width); | |
| $('#shadow_width_counter').val(power_user.shadow_width); | |
| $('#main-text-color-picker').attr('color', power_user.main_text_color); | |
| $('#italics-color-picker').attr('color', power_user.italics_text_color); | |
| $('#underline-color-picker').attr('color', power_user.underline_text_color); | |
| $('#quote-color-picker').attr('color', power_user.quote_text_color); | |
| $('#blur-tint-color-picker').attr('color', power_user.blur_tint_color); | |
| $('#chat-tint-color-picker').attr('color', power_user.chat_tint_color); | |
| $('#user-mes-blur-tint-color-picker').attr('color', power_user.user_mes_blur_tint_color); | |
| $('#bot-mes-blur-tint-color-picker').attr('color', power_user.bot_mes_blur_tint_color); | |
| $('#shadow-color-picker').attr('color', power_user.shadow_color); | |
| $('#border-color-picker').attr('color', power_user.border_color); | |
| $('#reduced_motion').prop('checked', power_user.reduced_motion); | |
| $('#auto-connect-checkbox').prop('checked', power_user.auto_connect); | |
| $('#auto-load-chat-checkbox').prop('checked', power_user.auto_load_chat); | |
| $('#forbid_external_media').prop('checked', power_user.forbid_external_media); | |
| $('#pin_styles').prop('checked', power_user.pin_styles); | |
| $('#click_to_edit').prop('checked', power_user.click_to_edit); | |
| for (const theme of themes) { | |
| const option = document.createElement('option'); | |
| option.value = theme.name; | |
| option.innerText = theme.name; | |
| option.selected = theme.name == power_user.theme; | |
| $('#themes').append(option); | |
| } | |
| for (const movingUIPreset of movingUIPresets) { | |
| const option = document.createElement('option'); | |
| option.value = movingUIPreset.name; | |
| option.innerText = movingUIPreset.name; | |
| option.selected = movingUIPreset.name == power_user.movingUIPreset; | |
| $('#movingUIPresets').append(option); | |
| } | |
| $(`#character_sort_order option[data-order="${power_user.sort_order}"][data-field="${power_user.sort_field}"]`).prop('selected', true); | |
| switchReducedMotion(); | |
| switchCompactInputArea(); | |
| reloadMarkdownProcessor(); | |
| await loadInstructMode(data); | |
| await loadContextSettings(); | |
| await loadSystemPrompts(data); | |
| await loadReasoningTemplates(data); | |
| loadMaxContextUnlocked(); | |
| switchWaifuMode(); | |
| switchSpoilerMode(); | |
| loadMovingUIState(); | |
| loadCharListState(); | |
| toggleMDHotkeyIconDisplay(); | |
| applyToastrPosition(); | |
| } | |
| function toggleMDHotkeyIconDisplay() { | |
| if (power_user.enable_md_hotkeys) { | |
| $('.mdhotkey_location').each(function () { | |
| $(this).parent().append('<i class="fa-brands fa-markdown mdhotkey_icon"></i>'); | |
| }); | |
| } else { | |
| $('.mdhotkey_icon').remove(); | |
| } | |
| } | |
| function loadCharListState() { | |
| document.body.classList.toggle('charListGrid', power_user.charListGrid); | |
| } | |
| function loadMovingUIState() { | |
| if (!isMobile() | |
| && power_user.movingUIState | |
| && power_user.movingUI === true) { | |
| console.debug('loading movingUI state'); | |
| for (var elmntName of Object.keys(power_user.movingUIState)) { | |
| var elmntState = power_user.movingUIState[elmntName]; | |
| try { | |
| var elmnt = $('#' + $.escapeSelector(elmntName)); | |
| if (elmnt.length) { | |
| console.debug(`loading state for ${elmntName}`); | |
| elmnt.css(elmntState); | |
| } else { | |
| console.debug(`skipping ${elmntName} because it doesn't exist in the DOM`); | |
| } | |
| } catch (err) { | |
| console.debug(`error occurred while processing ${elmntName}: ${err}`); | |
| } | |
| } | |
| } else { | |
| console.debug('skipping movingUI state load'); | |
| return; | |
| } | |
| } | |
| function loadMaxContextUnlocked() { | |
| $('#max_context_unlocked').prop('checked', power_user.max_context_unlocked); | |
| $('#max_context_unlocked').on('change', function () { | |
| power_user.max_context_unlocked = !!$(this).prop('checked'); | |
| switchMaxContextSize(); | |
| saveSettingsDebounced(); | |
| }); | |
| switchMaxContextSize(); | |
| } | |
| function switchMaxContextSize() { | |
| const elements = [ | |
| $('#max_context'), | |
| $('#max_context_counter'), | |
| $('#rep_pen_range'), | |
| $('#rep_pen_range_counter'), | |
| $('#rep_pen_range_textgenerationwebui'), | |
| $('#rep_pen_range_counter_textgenerationwebui'), | |
| $('#dry_penalty_last_n_textgenerationwebui'), | |
| $('#dry_penalty_last_n_counter_textgenerationwebui'), | |
| $('#rep_pen_decay_textgenerationwebui'), | |
| $('#rep_pen_decay_counter_textgenerationwebui'), | |
| ]; | |
| const maxValue = power_user.max_context_unlocked ? MAX_CONTEXT_UNLOCKED : MAX_CONTEXT_DEFAULT; | |
| const minValue = power_user.max_context_unlocked ? maxContextMin : maxContextMin; | |
| const steps = power_user.max_context_unlocked ? unlockedMaxContextStep : maxContextStep; | |
| $('#rep_pen_range_textgenerationwebui_zenslider').remove(); //unsure why, but this is necessary. | |
| $('#dry_penalty_last_n_textgenerationwebui_zenslider').remove(); | |
| $('#rep_pen_decay_textgenerationwebui_zenslider').remove(); | |
| for (const element of elements) { | |
| const id = element.attr('id'); | |
| element.attr('max', maxValue); | |
| if (typeof id === 'string' && id?.indexOf('max_context') !== -1) { | |
| element.attr('min', minValue); | |
| element.attr('step', steps); //only change setps for max context, because rep pen range needs step of 1 due to important values of -1 and 0 | |
| } | |
| const value = Number(element.val()); | |
| if (value >= maxValue) { | |
| element.val(maxValue).trigger('input'); | |
| } | |
| } | |
| const maxAmountGen = power_user.max_context_unlocked ? MAX_RESPONSE_UNLOCKED : MAX_RESPONSE_DEFAULT; | |
| $('#amount_gen').attr('max', maxAmountGen); | |
| $('#amount_gen_counter').attr('max', maxAmountGen); | |
| if (Number($('#amount_gen').val()) >= maxAmountGen) { | |
| $('#amount_gen').val(maxAmountGen).trigger('input'); | |
| } | |
| if (power_user.enableZenSliders) { | |
| $('#max_context_zenslider').remove(); | |
| CreateZenSliders($('#max_context')); | |
| $('#rep_pen_range_textgenerationwebui_zenslider').remove(); | |
| CreateZenSliders($('#rep_pen_range_textgenerationwebui')); | |
| $('#dry_penalty_last_n_textgenerationwebui_zenslider').remove(); | |
| CreateZenSliders($('#dry_penalty_last_n_textgenerationwebui')); | |
| $('#rep_pen_decay_textgenerationwebui_zenslider').remove(); | |
| CreateZenSliders($('#rep_pen_decay_textgenerationwebui')); | |
| } | |
| } | |
| // Fetch a compiled object of all preset settings | |
| function getContextSettings() { | |
| let compiledSettings = {}; | |
| contextControls.forEach((control) => { | |
| let value = control.isGlobalSetting ? power_user[control.property] : power_user.context[control.property]; | |
| // Force to a boolean if the setting is a checkbox | |
| if (control.isCheckbox) { | |
| value = !!value; | |
| } | |
| compiledSettings[control.property] = value; | |
| }); | |
| return compiledSettings; | |
| } | |
| // TODO: Maybe add a refresh button to reset settings to preset | |
| // TODO: Add "global state" if a preset doesn't set the power_user checkboxes | |
| async function loadContextSettings() { | |
| contextControls.forEach(control => { | |
| const $element = $(`#${control.id}`); | |
| if (control.isGlobalSetting) { | |
| return; | |
| } | |
| if (control.defaultValue !== undefined && power_user.context[control.property] === undefined) { | |
| power_user.context[control.property] = control.defaultValue; | |
| } | |
| if (control.isCheckbox) { | |
| $element.prop('checked', power_user.context[control.property]); | |
| } else { | |
| $element.val(power_user.context[control.property]); | |
| } | |
| console.debug(`Setting ${$element.prop('id')} to ${power_user.context[control.property]}`); | |
| // If the setting already exists, no need to duplicate it | |
| // TODO: Maybe check the power_user object for the setting instead of a flag? | |
| $element.on('input', async function () { | |
| const value = control.isCheckbox ? !!$(this).prop('checked') : $(this).val(); | |
| if (control.isGlobalSetting) { | |
| power_user[control.property] = value; | |
| } else { | |
| power_user.context[control.property] = value; | |
| } | |
| console.debug(`Setting ${$element.prop('id')} to ${value}`); | |
| if (!CSS.supports('field-sizing', 'content') && $(this).is('textarea')) { | |
| await resetScrollHeight($(this)); | |
| } | |
| saveSettingsDebounced(); | |
| }); | |
| }); | |
| context_presets.forEach((preset) => { | |
| const name = preset.name; | |
| const option = document.createElement('option'); | |
| option.value = name; | |
| option.innerText = name; | |
| option.selected = name === power_user.context.preset; | |
| $('#context_presets').append(option); | |
| }); | |
| $('#context_presets').on('change', function () { | |
| const name = String($(this).find(':selected').text()); | |
| const preset = context_presets.find(x => x.name === name); | |
| if (!preset) { | |
| return; | |
| } | |
| power_user.context.preset = name; | |
| contextControls.forEach(control => { | |
| const presetValue = preset[control.property] ?? control.defaultValue; | |
| if (presetValue !== undefined) { | |
| if (control.isGlobalSetting) { | |
| power_user[control.property] = presetValue; | |
| } else { | |
| power_user.context[control.property] = presetValue; | |
| } | |
| const $element = $(`#${control.id}`); | |
| if (control.isCheckbox) { | |
| $element | |
| .prop('checked', control.isGlobalSetting ? power_user[control.property] : power_user.context[control.property]) | |
| .trigger('input'); | |
| } else { | |
| $element.val(control.isGlobalSetting ? power_user[control.property] : power_user.context[control.property]); | |
| $element.trigger('input'); | |
| } | |
| } | |
| }); | |
| if (power_user.instruct.bind_to_context) { | |
| // Select matching instruct preset | |
| for (const instruct_preset of instruct_presets) { | |
| // If instruct preset matches the context template | |
| if (instruct_preset.name === name) { | |
| selectInstructPreset(instruct_preset.name, { isAuto: true }); | |
| break; | |
| } | |
| } | |
| } | |
| saveSettingsDebounced(); | |
| }); | |
| } | |
| /** | |
| * Common function to perform fuzzy search with optional caching | |
| * @template T | |
| * @param {string} type - Type of search from fuzzySearchCategories | |
| * @param {T[]} data - Data array to search in | |
| * @param {Array<{name: string, weight: number, getFn?: (obj: T) => string}>} keys - Fuse.js keys configuration | |
| * @param {string} searchValue - The search term | |
| * @param {Object.<string, { resultMap: Map<string, any> }>} [fuzzySearchCaches=null] - Optional fuzzy search caches | |
| * @returns {import('fuse.js').FuseResult<T>[]} Results as items with their score | |
| */ | |
| export function performFuzzySearch(type, data, keys, searchValue, fuzzySearchCaches = null) { | |
| // Check cache if provided | |
| if (fuzzySearchCaches) { | |
| const cache = fuzzySearchCaches[type]; | |
| if (cache?.resultMap.has(searchValue)) { | |
| return cache.resultMap.get(searchValue); | |
| } | |
| } | |
| const fuse = new Fuse(data, { | |
| keys: keys, | |
| includeScore: true, | |
| ignoreLocation: true, | |
| useExtendedSearch: true, | |
| threshold: 0.2, | |
| }); | |
| const results = fuse.search(searchValue); | |
| // Store in cache if provided | |
| if (fuzzySearchCaches) { | |
| fuzzySearchCaches[type].resultMap.set(searchValue, results); | |
| } | |
| return results; | |
| } | |
| /** | |
| * Fuzzy search characters by a search term | |
| * @param {string} searchValue - The search term | |
| * @param {Object.<string, { resultMap: Map<string, any> }>} [fuzzySearchCaches=null] - Optional fuzzy search caches | |
| * @returns {import('fuse.js').FuseResult<any>[]} Results as items with their score | |
| */ | |
| export function fuzzySearchCharacters(searchValue, fuzzySearchCaches = null) { | |
| const keys = [ | |
| { name: 'data.name', weight: 20 }, | |
| { name: '#tags', weight: 10, getFn: (character) => getTagsList(character.avatar).map(x => x.name).join('||') }, | |
| { name: 'data.description', weight: 3 }, | |
| { name: 'data.mes_example', weight: 3 }, | |
| { name: 'data.scenario', weight: 2 }, | |
| { name: 'data.personality', weight: 2 }, | |
| { name: 'data.first_mes', weight: 2 }, | |
| { name: 'data.creator_notes', weight: 2 }, | |
| { name: 'data.creator', weight: 1 }, | |
| { name: 'data.tags', weight: 1 }, | |
| { name: 'data.alternate_greetings', weight: 1 }, | |
| ]; | |
| return performFuzzySearch(fuzzySearchCategories.characters, characters, keys, searchValue, fuzzySearchCaches); | |
| } | |
| /** | |
| * Fuzzy search world info entries by a search term | |
| * @param {*[]} data - WI items data array | |
| * @param {string} searchValue - The search term | |
| * @param {Object.<string, { resultMap: Map<string, any> }>} [fuzzySearchCaches=null] - Optional fuzzy search caches | |
| * @returns {import('fuse.js').FuseResult<any>[]} Results as items with their score | |
| */ | |
| export function fuzzySearchWorldInfo(data, searchValue, fuzzySearchCaches = null) { | |
| const keys = [ | |
| { name: 'key', weight: 20 }, | |
| { name: 'group', weight: 15 }, | |
| { name: 'comment', weight: 10 }, | |
| { name: 'keysecondary', weight: 10 }, | |
| { name: 'content', weight: 3 }, | |
| { name: 'uid', weight: 1 }, | |
| { name: 'automationId', weight: 1 }, | |
| ]; | |
| return performFuzzySearch(fuzzySearchCategories.worldInfo, data, keys, searchValue, fuzzySearchCaches); | |
| } | |
| /** | |
| * Fuzzy search persona entries by a search term | |
| * @param {*[]} data - persona data array | |
| * @param {string} searchValue - The search term | |
| * @param {Object.<string, { resultMap: Map<string, any> }>} [fuzzySearchCaches=null] - Optional fuzzy search caches | |
| * @returns {import('fuse.js').FuseResult<any>[]} Results as items with their score | |
| */ | |
| export function fuzzySearchPersonas(data, searchValue, fuzzySearchCaches = null) { | |
| const mappedData = data.map(x => ({ | |
| key: x, | |
| name: power_user.personas[x] ?? '', | |
| description: power_user.persona_descriptions[x]?.description ?? '', | |
| })); | |
| const keys = [ | |
| { name: 'name', weight: 20 }, | |
| { name: 'description', weight: 3 }, | |
| ]; | |
| return performFuzzySearch(fuzzySearchCategories.personas, mappedData, keys, searchValue, fuzzySearchCaches); | |
| } | |
| /** | |
| * Fuzzy search tags by a search term | |
| * @param {string} searchValue - The search term | |
| * @param {Object.<string, { resultMap: Map<string, any> }>} [fuzzySearchCaches=null] - Optional fuzzy search caches | |
| * @returns {import('fuse.js').FuseResult<any>[]} Results as items with their score | |
| */ | |
| export function fuzzySearchTags(searchValue, fuzzySearchCaches = null) { | |
| const keys = [ | |
| { name: 'name', weight: 1 }, | |
| ]; | |
| return performFuzzySearch(fuzzySearchCategories.tags, tags, keys, searchValue, fuzzySearchCaches); | |
| } | |
| /** | |
| * Fuzzy search groups by a search term | |
| * @param {string} searchValue - The search term | |
| * @param {Object.<string, { resultMap: Map<string, any> }>} [fuzzySearchCaches=null] - Optional fuzzy search caches | |
| * @returns {import('fuse.js').FuseResult<any>[]} Results as items with their score | |
| */ | |
| export function fuzzySearchGroups(searchValue, fuzzySearchCaches = null) { | |
| const keys = [ | |
| { name: 'name', weight: 20 }, | |
| { name: 'members', weight: 15 }, | |
| { name: '#tags', weight: 10, getFn: (group) => getTagsList(group.id).map(x => x.name).join('||') }, | |
| { name: 'id', weight: 1 }, | |
| ]; | |
| return performFuzzySearch(fuzzySearchCategories.groups, groups, keys, searchValue, fuzzySearchCaches); | |
| } | |
| /** | |
| * Renders a story string template with the given parameters. | |
| * @param {object} params Template parameters. | |
| * @param {object} [options] Additional options. | |
| * @param {string} [options.customStoryString] Custom story string template. | |
| * @param {InstructSettings} [options.customInstructSettings] Custom instruct settings. | |
| * @returns {string} The rendered story string. | |
| */ | |
| export function renderStoryString(params, { customStoryString = null, customInstructSettings = null } = {}) { | |
| try { | |
| const storyString = customStoryString ?? power_user.context.story_string; | |
| const instructSettings = structuredClone(customInstructSettings ?? power_user.instruct); | |
| // Validate and log possible warnings/errors | |
| validateStoryString(storyString, params); | |
| // compile the story string template into a function, with no HTML escaping | |
| const compiledTemplate = Handlebars.compile(storyString, { noEscape: true }); | |
| // render the story string template with the given params | |
| let output = compiledTemplate(params); | |
| // substitute {{macro}} params that are not defined in the story string | |
| output = substituteParams(output, params.user, params.char); | |
| // remove leading newlines | |
| output = output.replace(/^\n+/, ''); | |
| // add a newline to the end of the story string if it doesn't have one | |
| if (output.length > 0 && !output.endsWith('\n')) { | |
| if (!instructSettings.enabled || instructSettings.wrap) { | |
| output += '\n'; | |
| } | |
| } | |
| return output; | |
| } catch (e) { | |
| toastr.error('Check the story string template for validity', 'Error rendering story string'); | |
| console.error('Error rendering story string', e); | |
| throw e; // rethrow the error | |
| } | |
| } | |
| /** | |
| * Validate the story string for possible warnings or issues | |
| * | |
| * @param {string} storyString - The story string | |
| * @param {Object} params - The story string parameters | |
| */ | |
| function validateStoryString(storyString, params) { | |
| /** @type {{hashCache: {[hash: string]: {fieldsWarned: {[key: string]: boolean}}}}} */ | |
| const cache = JSON.parse(accountStorage.getItem(storage_keys.storyStringValidationCache)) ?? { hashCache: {} }; | |
| const hash = getStringHash(storyString); | |
| // Initialize the cache for the current hash if it doesn't exist | |
| if (!cache.hashCache[hash]) { | |
| cache.hashCache[hash] = { fieldsWarned: {} }; | |
| } | |
| const currentCache = cache.hashCache[hash]; | |
| const fieldsToWarn = []; | |
| function validateMissingField(field, fallbackLegacyField = null) { | |
| const contains = storyString.includes(`{{${field}}}`) || (!!fallbackLegacyField && storyString.includes(`{{${fallbackLegacyField}}}`)); | |
| if (!contains && params[field]) { | |
| const wasLogged = currentCache.fieldsWarned[field]; | |
| if (!wasLogged) { | |
| fieldsToWarn.push(field); | |
| currentCache.fieldsWarned[field] = true; | |
| } | |
| console.warn(`The story string does not contain {{${field}}}, but it would contain content:\n`, params[field]); | |
| } | |
| } | |
| validateMissingField('description'); | |
| validateMissingField('personality'); | |
| validateMissingField('persona'); | |
| validateMissingField('scenario'); | |
| // validateMissingField('system'); | |
| validateMissingField('wiBefore', 'loreBefore'); | |
| validateMissingField('wiAfter', 'loreAfter'); | |
| if (fieldsToWarn.length > 0) { | |
| const fieldsList = fieldsToWarn.map(field => `{{${field}}}`).join(', '); | |
| toastr.warning(`The story string does not contain the following fields, but they would contain content: ${fieldsList}`, 'Story String Validation'); | |
| } | |
| accountStorage.setItem(storage_keys.storyStringValidationCache, JSON.stringify(cache)); | |
| } | |
| const sortFunc = (a, b) => power_user.sort_order == 'asc' ? compareFunc(a, b) : compareFunc(b, a); | |
| const compareFunc = (first, second) => { | |
| const a = first[power_user.sort_field]; | |
| const b = second[power_user.sort_field]; | |
| if (power_user.sort_field === 'create_date') { | |
| return sortMoments(timestampToMoment(b), timestampToMoment(a)); | |
| } | |
| switch (power_user.sort_rule) { | |
| case 'boolean': | |
| if (a === true || a === 'true') return 1; // Prioritize 'true' or true | |
| if (b === true || b === 'true') return -1; // Prioritize 'true' or true | |
| if (a && !b) return -1; // Move truthy values to the end | |
| if (!a && b) return 1; // Move falsy values to the beginning | |
| if (a === b) return 0; // Sort equal values normally | |
| return a < b ? -1 : 1; // Sort non-boolean values normally | |
| default: | |
| return typeof a == 'string' | |
| ? a.localeCompare(b) | |
| : a - b; | |
| } | |
| }; | |
| /** | |
| * Sorts an array of entities based on the current sort settings | |
| * @param {any[]} entities An array of objects with an `item` property | |
| * @param {boolean} forceSearch Whether to force search sorting | |
| * @param {import('./filters.js').FilterHelper} [filterHelper=null] Filter helper to use | |
| */ | |
| export function sortEntitiesList(entities, forceSearch, filterHelper = null) { | |
| filterHelper = filterHelper ?? entitiesFilter; | |
| if (power_user.sort_field == undefined || entities.length === 0) { | |
| return; | |
| } | |
| const isSearch = forceSearch || $('#character_sort_order option[data-field="search"]').is(':selected'); | |
| if (!isSearch && power_user.sort_order === 'random') { | |
| shuffle(entities); | |
| return; | |
| } | |
| entities.sort((a, b) => { | |
| // Sort tags/folders will always be at the top. Their original sorting will be kept, to respect manual tag sorting. | |
| if (a.type === 'tag' || b.type === 'tag') { | |
| // The one that is a tag will be at the top | |
| return (a.type === 'tag' ? -1 : 1) - (b.type === 'tag' ? -1 : 1); | |
| } | |
| // If we have search sorting, we take scores and use those | |
| if (isSearch) { | |
| const aScore = filterHelper.getScore(FILTER_TYPES.SEARCH, `${a.type}.${a.id}`); | |
| const bScore = filterHelper.getScore(FILTER_TYPES.SEARCH, `${b.type}.${b.id}`); | |
| return (aScore - bScore); | |
| } | |
| return sortFunc(a.item, b.item); | |
| }); | |
| } | |
| /** | |
| * Updates the current UI theme file. | |
| */ | |
| async function updateTheme() { | |
| await saveTheme(power_user.theme); | |
| toastr.success('Theme saved.'); | |
| } | |
| async function deleteTheme() { | |
| const themeName = power_user.theme; | |
| if (!themeName) { | |
| toastr.info('No theme selected.'); | |
| return; | |
| } | |
| const template = $(await renderTemplateAsync('themeDelete', { themeName })); | |
| const confirm = await callGenericPopup(template, POPUP_TYPE.CONFIRM); | |
| if (!confirm) { | |
| return; | |
| } | |
| const response = await fetch('/api/themes/delete', { | |
| method: 'POST', | |
| headers: getRequestHeaders(), | |
| body: JSON.stringify({ name: themeName }), | |
| }); | |
| if (!response.ok) { | |
| toastr.error('Failed to delete theme. Check the console for more information.'); | |
| return; | |
| } | |
| const themeIndex = themes.findIndex(x => x.name == themeName); | |
| if (themeIndex !== -1) { | |
| themes.splice(themeIndex, 1); | |
| $(`#themes option[value="${themeName}"]`).remove(); | |
| power_user.theme = themes[0]?.name; | |
| saveSettingsDebounced(); | |
| if (power_user.theme) { | |
| applyTheme(power_user.theme); | |
| } | |
| toastr.success('Theme deleted.'); | |
| } | |
| } | |
| /** | |
| * Exports the current theme to a file. | |
| */ | |
| async function exportTheme() { | |
| const themeFile = await saveTheme(power_user.theme); | |
| const fileName = `${themeFile.name}.json`; | |
| download(JSON.stringify(themeFile, null, 4), fileName, 'application/json'); | |
| } | |
| /** | |
| * Imports a theme from a file. | |
| * @param {File} file File to import. | |
| * @returns {Promise<void>} A promise that resolves when the theme is imported. | |
| */ | |
| async function importTheme(file) { | |
| if (!file) { | |
| return; | |
| } | |
| const fileText = await getFileText(file); | |
| const parsed = JSON.parse(fileText); | |
| if (!parsed.name) { | |
| throw new Error('Missing name'); | |
| } | |
| if (themes.some(t => t.name === parsed.name)) { | |
| throw new Error('Theme with that name already exists'); | |
| } | |
| if (typeof parsed.custom_css === 'string' && parsed.custom_css.includes('@import')) { | |
| const template = $(await renderTemplateAsync('themeImportWarning')); | |
| const confirm = await callGenericPopup(template, POPUP_TYPE.CONFIRM); | |
| if (!confirm) { | |
| throw new Error('Theme contains @import lines'); | |
| } | |
| } | |
| themes.push(parsed); | |
| await saveTheme(parsed.name, getNewTheme(parsed)); | |
| const option = document.createElement('option'); | |
| option.selected = false; | |
| option.value = parsed.name; | |
| option.innerText = parsed.name; | |
| $('#themes').append(option); | |
| saveSettingsDebounced(); | |
| toastr.success(parsed.name, 'Theme imported'); | |
| } | |
| /** | |
| * Saves the current theme to the server. | |
| * @param {string|undefined} name Theme name. If undefined, a popup will be shown to enter a name. | |
| * @param {object|undefined} theme Theme object. If undefined, the current theme will be saved. | |
| * @returns {Promise<object>} A promise that resolves when the theme is saved. | |
| */ | |
| async function saveTheme(name = undefined, theme = undefined) { | |
| if (typeof name !== 'string') { | |
| const newName = await callGenericPopup('Enter a theme preset name:', POPUP_TYPE.INPUT, power_user.theme); | |
| if (!newName) { | |
| return; | |
| } | |
| name = String(newName); | |
| } | |
| if (typeof theme !== 'object') { | |
| theme = getThemeObject(name); | |
| } | |
| const response = await fetch('/api/themes/save', { | |
| method: 'POST', | |
| headers: getRequestHeaders(), | |
| body: JSON.stringify(theme), | |
| }); | |
| if (!response.ok) { | |
| toastr.error('Check the server connection and reload the page to prevent data loss.', 'Theme could not be saved'); | |
| console.error('Theme could not be saved', response); | |
| throw new Error('Theme could not be saved'); | |
| } | |
| const themeIndex = themes.findIndex(x => x.name == name); | |
| if (themeIndex == -1) { | |
| themes.push(theme); | |
| const option = document.createElement('option'); | |
| option.selected = true; | |
| option.value = name; | |
| option.innerText = name; | |
| $('#themes').append(option); | |
| } | |
| else { | |
| themes[themeIndex] = theme; | |
| $(`#themes option[value="${name}"]`).attr('selected', true); | |
| } | |
| power_user.theme = name; | |
| saveSettingsDebounced(); | |
| return theme; | |
| } | |
| /** | |
| * Gets a snapshot of the current theme settings. | |
| * @param {string} name Name of the theme | |
| * @returns {object} Theme object | |
| */ | |
| function getThemeObject(name) { | |
| return { | |
| name, | |
| blur_strength: power_user.blur_strength, | |
| main_text_color: power_user.main_text_color, | |
| italics_text_color: power_user.italics_text_color, | |
| underline_text_color: power_user.underline_text_color, | |
| quote_text_color: power_user.quote_text_color, | |
| blur_tint_color: power_user.blur_tint_color, | |
| chat_tint_color: power_user.chat_tint_color, | |
| user_mes_blur_tint_color: power_user.user_mes_blur_tint_color, | |
| bot_mes_blur_tint_color: power_user.bot_mes_blur_tint_color, | |
| shadow_color: power_user.shadow_color, | |
| shadow_width: power_user.shadow_width, | |
| border_color: power_user.border_color, | |
| font_scale: power_user.font_scale, | |
| fast_ui_mode: power_user.fast_ui_mode, | |
| waifuMode: power_user.waifuMode, | |
| avatar_style: power_user.avatar_style, | |
| chat_display: power_user.chat_display, | |
| toastr_position: power_user.toastr_position, | |
| noShadows: power_user.noShadows, | |
| chat_width: power_user.chat_width, | |
| timer_enabled: power_user.timer_enabled, | |
| timestamps_enabled: power_user.timestamps_enabled, | |
| timestamp_model_icon: power_user.timestamp_model_icon, | |
| mesIDDisplay_enabled: power_user.mesIDDisplay_enabled, | |
| hideChatAvatars_enabled: power_user.hideChatAvatars_enabled, | |
| message_token_count_enabled: power_user.message_token_count_enabled, | |
| expand_message_actions: power_user.expand_message_actions, | |
| enableZenSliders: power_user.enableZenSliders, | |
| enableLabMode: power_user.enableLabMode, | |
| hotswap_enabled: power_user.hotswap_enabled, | |
| custom_css: power_user.custom_css, | |
| bogus_folders: power_user.bogus_folders, | |
| zoomed_avatar_magnification: power_user.zoomed_avatar_magnification, | |
| reduced_motion: power_user.reduced_motion, | |
| compact_input_area: power_user.compact_input_area, | |
| show_swipe_num_all_messages: power_user.show_swipe_num_all_messages, | |
| click_to_edit: power_user.click_to_edit, | |
| }; | |
| } | |
| /** | |
| * Applies imported theme properties to the theme object. | |
| * @param {object} parsed Parsed object to get the theme from. | |
| * @returns {object} Theme assigned to the parsed object. | |
| */ | |
| function getNewTheme(parsed) { | |
| const theme = getThemeObject(parsed.name); | |
| for (const key in parsed) { | |
| if (Object.hasOwn(theme, key)) { | |
| theme[key] = parsed[key]; | |
| } | |
| } | |
| return theme; | |
| } | |
| async function saveMovingUI() { | |
| const popupResult = await callGenericPopup('Enter a name for the MovingUI Preset:', POPUP_TYPE.INPUT); | |
| if (!popupResult) { | |
| return; | |
| } | |
| const name = String(popupResult); | |
| const movingUIPreset = { | |
| name, | |
| movingUIState: power_user.movingUIState, | |
| }; | |
| console.log(movingUIPreset); | |
| const response = await fetch('/api/moving-ui/save', { | |
| method: 'POST', | |
| headers: getRequestHeaders(), | |
| body: JSON.stringify(movingUIPreset), | |
| }); | |
| if (response.ok) { | |
| const movingUIPresetIndex = movingUIPresets.findIndex(x => x.name == name); | |
| if (movingUIPresetIndex == -1) { | |
| movingUIPresets.push(movingUIPreset); | |
| const option = document.createElement('option'); | |
| option.selected = true; | |
| option.value = name; | |
| option.innerText = name; | |
| $('#movingUIPresets').append(option); | |
| } | |
| else { | |
| movingUIPresets[movingUIPresetIndex] = movingUIPreset; | |
| $(`#movingUIPresets option[value="${name}"]`).attr('selected', true); | |
| } | |
| power_user.movingUIPreset = name; | |
| saveSettingsDebounced(); | |
| } else { | |
| toastr.error('Failed to save MovingUI state.'); | |
| console.error('MovingUI could not be saved', response); | |
| } | |
| } | |
| /** | |
| * Resets the movable styles of the given element to their unset values. | |
| * @param {string} id Element ID | |
| */ | |
| export function resetMovableStyles(id) { | |
| const panelStyles = ['top', 'left', 'right', 'bottom', 'height', 'width', 'margin']; | |
| const panel = document.getElementById(id); | |
| if (panel) { | |
| panelStyles.forEach((style) => { | |
| panel.style[style] = ''; | |
| }); | |
| } | |
| } | |
| async function resetMovablePanels(type) { | |
| const panelIds = [ | |
| 'sheld', | |
| 'left-nav-panel', | |
| 'right-nav-panel', | |
| 'WorldInfo', | |
| 'floatingPrompt', | |
| 'expression-holder', | |
| 'groupMemberListPopout', | |
| 'summaryExtensionPopout', | |
| 'gallery', | |
| 'logprobsViewer', | |
| 'cfgConfig', | |
| ]; | |
| /** | |
| * @type {HTMLElement[]} Generic panels that don't have a known ID | |
| */ | |
| const draggedElements = Array.from(document.querySelectorAll('[data-dragged]')); | |
| const allDraggable = panelIds.map(id => document.getElementById(id)).concat(draggedElements).filter(onlyUnique); | |
| const panelStyles = ['top', 'left', 'right', 'bottom', 'height', 'width', 'margin']; | |
| allDraggable.forEach((panel) => { | |
| if (panel) { | |
| $(panel).addClass('resizing'); | |
| panelStyles.forEach((style) => { | |
| panel.style[style] = ''; | |
| }); | |
| } | |
| }); | |
| /** | |
| * @type {HTMLElement[]} Zoomed avatars that are currently being resized | |
| */ | |
| const zoomedAvatars = Array.from(document.querySelectorAll('.zoomed_avatar')); | |
| if (zoomedAvatars.length > 0) { | |
| zoomedAvatars.forEach((avatar) => { | |
| avatar.classList.add('resizing'); | |
| panelStyles.forEach((style) => { | |
| avatar.style[style] = ''; | |
| }); | |
| }); | |
| } | |
| $('[data-dragged="true"]').removeAttr('data-dragged'); | |
| await delay(50); | |
| power_user.movingUIState = {}; | |
| //if user manually resets panels, deselect the current preset | |
| if (type !== 'quiet' && type !== 'resize') { | |
| power_user.movingUIPreset = 'Default'; | |
| $('#movingUIPresets option[value="Default"]').prop('selected', true); | |
| } | |
| saveSettingsDebounced(); | |
| await eventSource.emit(event_types.MOVABLE_PANELS_RESET); | |
| eventSource.once(event_types.SETTINGS_UPDATED, () => { | |
| $('.resizing').removeClass('resizing'); | |
| //if happening as part of preset application, do it quietly. | |
| if (type === 'quiet') { | |
| return; | |
| //if happening due to resize, tell user. | |
| } else if (type === 'resize') { | |
| toastr.warning('Panel positions reset due to zoom/resize'); | |
| //if happening due to manual button press | |
| } else { | |
| toastr.success('Panel positions reset'); | |
| } | |
| }); | |
| } | |
| /** | |
| * Finds the ID of the tag with the given name. | |
| * @param {string} name | |
| * @returns {string} The ID of the tag with the given name. | |
| */ | |
| function findTagIdByName(name) { | |
| const matchTypes = [ | |
| (a, b) => a === b, | |
| (a, b) => a.startsWith(b), | |
| (a, b) => a.includes(b), | |
| ]; | |
| // Only get tags that contain at least one record in the tag_map | |
| const liveTagIds = new Set(Object.values(tag_map).flat()); | |
| const liveTags = tags.filter(x => liveTagIds.has(x.id)); | |
| const exactNameMatchIndex = liveTags.map(x => x.name.toLowerCase()).indexOf(name.toLowerCase()); | |
| if (exactNameMatchIndex !== -1) { | |
| return liveTags[exactNameMatchIndex].id; | |
| } | |
| for (const matchType of matchTypes) { | |
| const index = liveTags.findIndex(x => matchType(x.name.toLowerCase(), name.toLowerCase())); | |
| if (index !== -1) { | |
| return liveTags[index].id; | |
| } | |
| } | |
| } | |
| async function doRandomChat(_, tagName) { | |
| /** | |
| * Gets the ID of a random character. | |
| * @returns {string} The order index of the randomly selected character. | |
| */ | |
| function getRandomCharacterId() { | |
| if (!tagName) { | |
| return Math.floor(Math.random() * characters.length).toString(); | |
| } | |
| const tagId = findTagIdByName(tagName); | |
| const taggedCharacters = Object.entries(tag_map) | |
| .filter(x => x[1].includes(tagId)) // Get only records that include the tag | |
| .map(x => x[0]) // Map the character avatar | |
| .filter(x => characters.find(y => y.avatar === x)); // Filter out characters that don't exist | |
| const randomCharacter = taggedCharacters[Math.floor(Math.random() * taggedCharacters.length)]; | |
| const randomIndex = characters.findIndex(x => x.avatar === randomCharacter); | |
| if (randomIndex === -1) { | |
| return; | |
| } | |
| return randomIndex.toString(); | |
| } | |
| resetSelectedGroup(); | |
| const characterId = getRandomCharacterId(); | |
| if (!characterId) { | |
| toastr.error('No characters found'); | |
| return; | |
| } | |
| setCharacterId(characterId); | |
| setActiveCharacter(characters[characterId]?.avatar); | |
| setActiveGroup(null); | |
| await delay(1); | |
| await reloadCurrentChat(); | |
| return characters[characterId]?.name; | |
| } | |
| /** | |
| * Loads the chat until the given message ID is displayed. | |
| * @param {number} mesId | |
| * @returns JQuery<HTMLElement> | |
| */ | |
| async function loadUntilMesId(mesId) { | |
| let target; | |
| while (getFirstDisplayedMessageId() > mesId && getFirstDisplayedMessageId() !== 0) { | |
| await showMoreMessages(); | |
| await delay(1); | |
| target = $('#chat').find(`.mes[mesid=${mesId}]`); | |
| if (target.length) { | |
| break; | |
| } | |
| } | |
| if (!target.length) { | |
| toastr.error(`Could not find message with ID: ${mesId}`); | |
| return target; | |
| } | |
| return target; | |
| } | |
| async function doMesCut(_, text) { | |
| console.debug(`was asked to cut message id #${text}`); | |
| const range = stringToRange(text, 0, chat.length - 1); | |
| //reject invalid args or no args | |
| if (!range) { | |
| toastr.warning('Must provide a Message ID or a range to cut.'); | |
| return; | |
| } | |
| let totalMesToCut = (range.end - range.start) + 1; | |
| let mesIDToCut = range.start; | |
| let cutText = ''; | |
| for (let i = 0; i < totalMesToCut; i++) { | |
| cutText += (chat[mesIDToCut]?.mes || '') + '\n'; | |
| let done = false; | |
| let mesToCut = $('#chat').find(`.mes[mesid=${mesIDToCut}]`); | |
| if (!mesToCut.length) { | |
| mesToCut = await loadUntilMesId(mesIDToCut); | |
| if (!mesToCut || !mesToCut.length) { | |
| return; | |
| } | |
| } | |
| setEditedMessageId(mesIDToCut); | |
| eventSource.once(event_types.MESSAGE_DELETED, () => { | |
| done = true; | |
| }); | |
| mesToCut.find('.mes_edit_delete').trigger('click', { fromSlashCommand: true }); | |
| while (!done) { | |
| await delay(1); | |
| } | |
| } | |
| return cutText; | |
| } | |
| async function doDelMode(_, text) { | |
| //reject invalid args | |
| if (text && isNaN(text)) { | |
| toastr.warning('Must enter a number or nothing.'); | |
| return ''; | |
| } | |
| // Just enter the delete mode. | |
| if (!text) { | |
| $('#option_delete_mes').trigger('click', { fromSlashCommand: true }); | |
| return ''; | |
| } | |
| const count = Number(text); | |
| // Nothing to delete. | |
| if (count < 1) { | |
| return ''; | |
| } | |
| if (count > chat.length) { | |
| toastr.warning(`Cannot delete more than ${chat.length} messages.`); | |
| return ''; | |
| } | |
| const range = `${chat.length - count}-${chat.length - 1}`; | |
| return doMesCut(_, range); | |
| } | |
| function doResetPanels() { | |
| $('#movingUIreset').trigger('click'); | |
| return ''; | |
| } | |
| function setAvgBG() { | |
| const bgimg = new Image(); | |
| bgimg.src = $('#bg1') | |
| .css('background-image') | |
| .replace(/^url\(['"]?/, '') | |
| .replace(/['"]?\)$/, ''); | |
| /* const charAvatar = new Image() | |
| charAvatar.src = $("#avatar_load_preview") | |
| .attr('src') | |
| .replace(/^url\(['"]?/, '') | |
| .replace(/['"]?\)$/, ''); | |
| const userAvatar = new Image() | |
| userAvatar.src = $("#user_avatar_block .avatar.selected img") | |
| .attr('src') | |
| .replace(/^url\(['"]?/, '') | |
| .replace(/['"]?\)$/, ''); */ | |
| bgimg.onload = function () { | |
| var rgb = getAverageRGB(bgimg); | |
| //console.log(`average color of the bg is:`) | |
| //console.log(rgb); | |
| $('#blur-tint-color-picker').attr('color', 'rgb(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ')'); | |
| const backgroundColorString = $('#blur-tint-color-picker').attr('color') | |
| .replace('rgba', '') | |
| .replace('rgb', '') | |
| .replace('(', '[') | |
| .replace(')', ']'); //[50, 120, 200, 1]; // Example background color | |
| const backgroundColorArray = JSON.parse(backgroundColorString); //[200, 200, 200, 1] | |
| console.log(backgroundColorArray); | |
| $('#main-text-color-picker').attr('color', getReadableTextColor(backgroundColorArray)); | |
| console.log($('#main-text-color-picker').attr('color')); // Output: 'rgba(0, 47, 126, 1)' | |
| }; | |
| /* charAvatar.onload = function () { | |
| var rgb = getAverageRGB(charAvatar); | |
| //console.log(`average color of the AI avatar is:`); | |
| //console.log(rgb); | |
| $("#bot-mes-blur-tint-color-picker").attr('color', 'rgb(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ')'); | |
| } | |
| userAvatar.onload = function () { | |
| var rgb = getAverageRGB(userAvatar); | |
| //console.log(`average color of the user avatar is:`); | |
| //console.log(rgb); | |
| $("#user-mes-blur-tint-color-picker").attr('color', 'rgb(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ')'); | |
| } */ | |
| function getAverageRGB(imgEl) { | |
| var blockSize = 5, // only visit every 5 pixels | |
| defaultRGB = { r: 0, g: 0, b: 0 }, // for non-supporting envs | |
| canvas = document.createElement('canvas'), | |
| context = canvas.getContext && canvas.getContext('2d'), | |
| data, width, height, | |
| i = -4, | |
| length, | |
| rgb = { r: 0, g: 0, b: 0 }, | |
| count = 0; | |
| if (!context) { | |
| return defaultRGB; | |
| } | |
| height = canvas.height = imgEl.naturalHeight || imgEl.offsetHeight || imgEl.height; | |
| width = canvas.width = imgEl.naturalWidth || imgEl.offsetWidth || imgEl.width; | |
| context.drawImage(imgEl, 0, 0); | |
| try { | |
| data = context.getImageData(0, 0, width, height); | |
| } catch (e) { | |
| /* security error, img on diff domain */alert('x'); | |
| return defaultRGB; | |
| } | |
| length = data.data.length; | |
| while ((i += blockSize * 4) < length) { | |
| ++count; | |
| rgb.r += data.data[i]; | |
| rgb.g += data.data[i + 1]; | |
| rgb.b += data.data[i + 2]; | |
| } | |
| // ~~ used to floor values | |
| rgb.r = ~~(rgb.r / count); | |
| rgb.g = ~~(rgb.g / count); | |
| rgb.b = ~~(rgb.b / count); | |
| return rgb; | |
| } | |
| /** | |
| * Converts an HSL color value to RGB. | |
| * @param {number} h Hue value | |
| * @param {number} s Saturation value | |
| * @param {number} l Luminance value | |
| * @return {Array} The RGB representation | |
| */ | |
| function hslToRgb(h, s, l) { | |
| const hueToRgb = (p, q, t) => { | |
| if (t < 0) t += 1; | |
| if (t > 1) t -= 1; | |
| if (t < 1 / 6) return p + (q - p) * 6 * t; | |
| if (t < 1 / 2) return q; | |
| if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6; | |
| return p; | |
| }; | |
| if (s === 0) { | |
| return [l, l, l]; | |
| } | |
| const q = l < 0.5 ? l * (1 + s) : l + s - l * s; | |
| const p = 2 * l - q; | |
| const r = hueToRgb(p, q, h + 1 / 3); | |
| const g = hueToRgb(p, q, h); | |
| const b = hueToRgb(p, q, h - 1 / 3); | |
| return [r * 255, g * 255, b * 255]; | |
| } | |
| //this version keeps BG and main text in same hue | |
| /* function getReadableTextColor(rgb) { | |
| const [r, g, b] = rgb; | |
| // Convert RGB to HSL | |
| const rgbToHsl = (r, g, b) => { | |
| const max = Math.max(r, g, b); | |
| const min = Math.min(r, g, b); | |
| const d = max - min; | |
| const l = (max + min) / 2; | |
| if (d === 0) return [0, 0, l]; | |
| const s = l > 0.5 ? d / (2 - max - min) : d / (max + min); | |
| const h = (() => { | |
| switch (max) { | |
| case r: | |
| return (g - b) / d + (g < b ? 6 : 0); | |
| case g: | |
| return (b - r) / d + 2; | |
| case b: | |
| return (r - g) / d + 4; | |
| } | |
| })() / 6; | |
| return [h, s, l]; | |
| }; | |
| const [h, s, l] = rgbToHsl(r / 255, g / 255, b / 255); | |
| // Calculate appropriate text color based on background color | |
| const targetLuminance = l > 0.5 ? 0.2 : 0.8; | |
| const targetSaturation = s > 0.5 ? s - 0.2 : s + 0.2; | |
| const [rNew, gNew, bNew] = hslToRgb(h, targetSaturation, targetLuminance); | |
| // Return the text color in RGBA format | |
| return `rgba(${rNew.toFixed(0)}, ${gNew.toFixed(0)}, ${bNew.toFixed(0)}, 1)`; | |
| }*/ | |
| //this version makes main text complimentary color to BG color | |
| function getReadableTextColor(rgb) { | |
| const [r, g, b] = rgb; | |
| // Convert RGB to HSL | |
| const rgbToHsl = (r, g, b) => { | |
| const max = Math.max(r, g, b); | |
| const min = Math.min(r, g, b); | |
| const d = max - min; | |
| const l = (max + min) / 2; | |
| if (d === 0) return [0, 0, l]; | |
| const s = l > 0.5 ? d / (2 - max - min) : d / (max + min); | |
| const h = (() => { | |
| switch (max) { | |
| case r: | |
| return (g - b) / d + (g < b ? 6 : 0); | |
| case g: | |
| return (b - r) / d + 2; | |
| case b: | |
| return (r - g) / d + 4; | |
| } | |
| })() / 6; | |
| return [h, s, l]; | |
| }; | |
| const [h, s, l] = rgbToHsl(r / 255, g / 255, b / 255); | |
| // Calculate complementary color based on background color | |
| const complementaryHue = (h + 0.5) % 1; | |
| const complementarySaturation = s > 0.5 ? s - 0.6 : s + 0.6; | |
| const complementaryLuminance = l > 0.5 ? 0.2 : 0.8; | |
| // Convert complementary color back to RGB | |
| const [rNew, gNew, bNew] = hslToRgb(complementaryHue, complementarySaturation, complementaryLuminance); | |
| // Return the text color in RGBA format | |
| return `rgba(${rNew.toFixed(0)}, ${gNew.toFixed(0)}, ${bNew.toFixed(0)}, 1)`; | |
| } | |
| return ''; | |
| } | |
| async function setThemeCallback(_, themeName) { | |
| if (!themeName) { | |
| // allow reporting of the theme name if called without args | |
| // for use in ST Scripts via pipe | |
| return power_user.theme; | |
| } | |
| // @ts-ignore | |
| const fuse = new Fuse(themes, { | |
| keys: [ | |
| { name: 'name', weight: 1 }, | |
| ], | |
| }); | |
| const results = fuse.search(themeName); | |
| console.debug('Theme fuzzy search results for ' + themeName, results); | |
| const theme = results[0]?.item; | |
| if (!theme) { | |
| toastr.warning(`Could not find theme with name: ${themeName}`); | |
| return; | |
| } | |
| power_user.theme = theme.name; | |
| applyTheme(theme.name); | |
| $('#themes').val(theme.name); | |
| saveSettingsDebounced(); | |
| return ''; | |
| } | |
| async function setmovingUIPreset(_, text) { | |
| // @ts-ignore | |
| const fuse = new Fuse(movingUIPresets, { | |
| keys: [ | |
| { name: 'name', weight: 1 }, | |
| ], | |
| }); | |
| const results = fuse.search(text); | |
| console.debug('movingUI preset fuzzy search results for ' + text, results); | |
| const preset = results[0]?.item; | |
| if (!preset) { | |
| toastr.warning(`Could not find preset with name: ${text}`); | |
| return; | |
| } | |
| power_user.movingUIPreset = preset.name; | |
| applyMovingUIPreset(preset.name); | |
| $('#movingUIPresets').val(preset.name); | |
| saveSettingsDebounced(); | |
| return ''; | |
| } | |
| const EPHEMERAL_STOPPING_STRINGS = []; | |
| /** | |
| * Adds a stopping string to the list of stopping strings that are only used for the next generation. | |
| * @param {string} value The stopping string to add | |
| */ | |
| export function addEphemeralStoppingString(value) { | |
| if (!EPHEMERAL_STOPPING_STRINGS.includes(value)) { | |
| console.debug('Adding ephemeral stopping string:', value); | |
| EPHEMERAL_STOPPING_STRINGS.push(value); | |
| } | |
| } | |
| export function flushEphemeralStoppingStrings() { | |
| if (EPHEMERAL_STOPPING_STRINGS.length === 0) { | |
| return; | |
| } | |
| console.debug('Flushing ephemeral stopping strings:', EPHEMERAL_STOPPING_STRINGS); | |
| EPHEMERAL_STOPPING_STRINGS.splice(0, EPHEMERAL_STOPPING_STRINGS.length); | |
| } | |
| /** | |
| * Checks if the generated text should be filtered based on the auto-swipe settings. | |
| * @param {string} text The text to check | |
| * @returns {boolean} If the generated text should be filtered | |
| */ | |
| export function generatedTextFiltered(text) { | |
| /** | |
| * Checks if the given text contains any of the blacklisted words. | |
| * @param {string} text The text to check | |
| * @param {string[]} blacklist The list of blacklisted words | |
| * @param {number} threshold The number of blacklisted words that need to be present to trigger the check | |
| * @returns {boolean} Whether the text contains blacklisted words | |
| */ | |
| function containsBlacklistedWords(text, blacklist, threshold) { | |
| const regex = new RegExp(`\\b(${blacklist.join('|')})\\b`, 'gi'); | |
| const matches = text.match(regex) || []; | |
| return matches.length >= threshold; | |
| } | |
| // Make sure a generated text is non-empty | |
| // Otherwise we might get in a loop with a broken API | |
| text = text.trim(); | |
| if (text.length > 0) { | |
| if (power_user.auto_swipe_minimum_length) { | |
| if (text.length < power_user.auto_swipe_minimum_length) { | |
| console.log('Generated text size too small'); | |
| return true; | |
| } | |
| } | |
| if (power_user.auto_swipe_blacklist.length && 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; | |
| } | |
| /** | |
| * Gets the custom stopping strings from the power user settings. | |
| * @param {number | undefined} limit Number of strings to return. If 0 or undefined, returns all strings. | |
| * @returns {string[]} An array of custom stopping strings | |
| */ | |
| export function getCustomStoppingStrings(limit = undefined) { | |
| function getPermanent() { | |
| try { | |
| // If there's no custom stopping strings, return an empty array | |
| if (!power_user.custom_stopping_strings) { | |
| return []; | |
| } | |
| // Parse the JSON string | |
| let strings = JSON.parse(power_user.custom_stopping_strings); | |
| // Make sure it's an array | |
| if (!Array.isArray(strings)) { | |
| return []; | |
| } | |
| // Make sure all the elements are strings and non-empty. | |
| strings = strings.filter(s => typeof s === 'string' && s.length > 0); | |
| // Substitute params if necessary | |
| if (power_user.custom_stopping_strings_macro) { | |
| strings = strings.map(x => substituteParams(x)); | |
| } | |
| return strings; | |
| } catch (error) { | |
| // If there's an error, return an empty array | |
| console.warn('Error parsing custom stopping strings:', error); | |
| return []; | |
| } | |
| } | |
| const permanent = getPermanent(); | |
| const ephemeral = EPHEMERAL_STOPPING_STRINGS; | |
| const strings = [...permanent, ...ephemeral]; | |
| // Apply the limit. If limit is 0, return all strings. | |
| if (limit > 0) { | |
| return strings.slice(0, limit); | |
| } | |
| return strings; | |
| } | |
| export function forceCharacterEditorTokenize() { | |
| $('[data-token-counter]').each(function () { | |
| $(document.getElementById($(this).data('token-counter'))).data('last-value-hash', ''); | |
| }); | |
| $('#rm_ch_create_block').trigger('input'); | |
| $('#character_popup').trigger('input'); | |
| } | |
| $(document).ready(() => { | |
| const adjustAutocompleteDebounced = debounce(() => { | |
| $('.ui-autocomplete-input').each(function () { | |
| const isOpen = $(this).autocomplete('widget')[0].style.display !== 'none'; | |
| if (isOpen) { | |
| $(this).autocomplete('search'); | |
| } | |
| }); | |
| }); | |
| const reportZoomLevelDebounced = debounce(() => { | |
| const zoomLevel = Number(window.devicePixelRatio).toFixed(2) || 1; | |
| const winWidth = window.innerWidth; | |
| const winHeight = window.innerHeight; | |
| const originalWidth = winWidth * zoomLevel; | |
| const originalHeight = winHeight * zoomLevel; | |
| console.log(`Window resize: ${coreTruthWinWidth}x${coreTruthWinHeight} -> ${window.innerWidth}x${window.innerHeight}`); | |
| console.debug(`Zoom: ${zoomLevel}, X:${winWidth}, Y:${winHeight}, original: ${originalWidth}x${originalHeight} `); | |
| return zoomLevel; | |
| }); | |
| var coreTruthWinWidth = window.innerWidth; | |
| var coreTruthWinHeight = window.innerHeight; | |
| $(window).on('resize', async () => { | |
| adjustAutocompleteDebounced(); | |
| setHotswapsDebounced(); | |
| if (isMobile()) { | |
| return; | |
| } | |
| reportZoomLevelDebounced(); | |
| //attempt to scale movingUI elements naturally across window resizing/zooms | |
| //this will still break if the zoom level causes mobile styles to come into play. | |
| const scaleY = Number(window.innerHeight / coreTruthWinHeight).toFixed(4); | |
| const scaleX = Number(window.innerWidth / coreTruthWinWidth).toFixed(4); | |
| if (Object.keys(power_user.movingUIState).length > 0) { | |
| for (var elmntName of Object.keys(power_user.movingUIState)) { | |
| var elmntState = power_user.movingUIState[elmntName]; | |
| var oldHeight = elmntState.height; | |
| var oldWidth = elmntState.width; | |
| var oldLeft = elmntState.left; | |
| var oldTop = elmntState.top; | |
| var oldBottom = elmntState.bottom; | |
| var oldRight = elmntState.right; | |
| var newHeight, newWidth, newTop, newBottom, newLeft, newRight; | |
| newHeight = Number(oldHeight * scaleY).toFixed(0); | |
| newWidth = Number(oldWidth * scaleX).toFixed(0); | |
| newLeft = Number(oldLeft * scaleX).toFixed(0); | |
| newTop = Number(oldTop * scaleY).toFixed(0); | |
| newBottom = Number(oldBottom * scaleY).toFixed(0); | |
| newRight = Number(oldRight * scaleX).toFixed(0); | |
| try { | |
| var elmnt = $('#' + $.escapeSelector(elmntName)); | |
| if (elmnt.length) { | |
| console.log(`scaling ${elmntName} by ${scaleX}x${scaleY} to ${newWidth}x${newHeight}`); | |
| elmnt.css('height', newHeight); | |
| elmnt.css('width', newWidth); | |
| elmnt.css('inset', `${newTop}px ${newRight}px ${newBottom}px ${newLeft}px`); | |
| power_user.movingUIState[elmntName].height = newHeight; | |
| power_user.movingUIState[elmntName].width = newWidth; | |
| power_user.movingUIState[elmntName].top = newTop; | |
| power_user.movingUIState[elmntName].bottom = newBottom; | |
| power_user.movingUIState[elmntName].left = newLeft; | |
| power_user.movingUIState[elmntName].right = newRight; | |
| } else { | |
| console.log(`skipping ${elmntName} because it doesn't exist in the DOM`); | |
| } | |
| } catch (err) { | |
| console.log(`error occurred while processing ${elmntName}: ${err}`); | |
| } | |
| } | |
| } else { | |
| console.debug('aborting MUI reset', Object.keys(power_user.movingUIState).length); | |
| } | |
| saveSettingsDebounced(); | |
| coreTruthWinWidth = window.innerWidth; | |
| coreTruthWinHeight = window.innerHeight; | |
| }); | |
| // Settings that go to settings.json | |
| $('#collapse-newlines-checkbox').change(function () { | |
| power_user.collapse_newlines = !!$(this).prop('checked'); | |
| saveSettingsDebounced(); | |
| }); | |
| // include newline is the child of trim sentences | |
| // if include newline is checked, trim sentences must be checked | |
| // if trim sentences is unchecked, include newline must be unchecked | |
| $('#trim_sentences_checkbox').change(function () { | |
| power_user.trim_sentences = !!$(this).prop('checked'); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#single_line').on('input', function () { | |
| const value = !!$(this).prop('checked'); | |
| power_user.single_line = value; | |
| saveSettingsDebounced(); | |
| }); | |
| $('#context_derived').on('input', function () { | |
| const value = !!$(this).prop('checked'); | |
| power_user.context_derived = value; | |
| saveSettingsDebounced(); | |
| }); | |
| $('#context_derived').on('change', function () { | |
| $('#context_derived').parent().find('i').toggleClass('toggleEnabled', !!power_user.context_derived); | |
| }); | |
| $('#context_size_derived').on('input', function () { | |
| const value = !!$(this).prop('checked'); | |
| power_user.context_size_derived = value; | |
| saveSettingsDebounced(); | |
| }); | |
| $('#context_size_derived').on('change', function () { | |
| $('#context_size_derived').prop('checked', !!power_user.context_size_derived); | |
| }); | |
| $('#always-force-name2-checkbox').change(function () { | |
| power_user.always_force_name2 = !!$(this).prop('checked'); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#markdown_escape_strings').on('input', function () { | |
| power_user.markdown_escape_strings = String($(this).val()); | |
| saveSettingsDebounced(); | |
| reloadMarkdownProcessor(); | |
| }); | |
| $('#start_reply_with').on('input', function () { | |
| power_user.user_prompt_bias = String($(this).val()); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#chat-show-reply-prefix-checkbox').change(function () { | |
| power_user.show_user_prompt_bias = !!$(this).prop('checked'); | |
| reloadCurrentChat(); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#auto_continue_enabled').on('change', function () { | |
| power_user.auto_continue.enabled = $(this).prop('checked'); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#auto_continue_allow_chat_completions').on('change', function () { | |
| power_user.auto_continue.allow_chat_completions = !!$(this).prop('checked'); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#auto_continue_target_length').on('input', function () { | |
| power_user.auto_continue.target_length = Number($(this).val()); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#example_messages_behavior').on('change', function () { | |
| const selectedOption = String($(this).find(':selected').val()); | |
| console.log('Setting example messages behavior to', selectedOption); | |
| switch (selectedOption) { | |
| case 'normal': | |
| power_user.pin_examples = false; | |
| power_user.strip_examples = false; | |
| break; | |
| case 'keep': | |
| power_user.pin_examples = true; | |
| power_user.strip_examples = false; | |
| break; | |
| case 'strip': | |
| power_user.pin_examples = false; | |
| power_user.strip_examples = true; | |
| break; | |
| } | |
| console.debug('power_user.pin_examples', power_user.pin_examples); | |
| console.debug('power_user.strip_examples', power_user.strip_examples); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#fast_ui_mode').change(function () { | |
| power_user.fast_ui_mode = $(this).prop('checked'); | |
| switchUiMode(); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#waifuMode').on('change', () => { | |
| power_user.waifuMode = $('#waifuMode').prop('checked'); | |
| switchWaifuMode(); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#customCSS').on('input', () => { | |
| power_user.custom_css = String($('#customCSS').val()); | |
| saveSettingsDebounced(); | |
| applyCustomCSS(); | |
| }); | |
| $('#movingUImode').change(function () { | |
| power_user.movingUI = $(this).prop('checked'); | |
| switchMovingUI(); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#noShadowsmode').change(function () { | |
| power_user.noShadows = $(this).prop('checked'); | |
| applyNoShadows(); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#movingUIreset').on('click', resetMovablePanels); | |
| $('#avatar_style').on('change', function () { | |
| const value = $(this).find(':selected').val(); | |
| power_user.avatar_style = Number(value); | |
| applyAvatarStyle(); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#chat_display').on('change', function () { | |
| const value = $(this).find(':selected').val(); | |
| power_user.chat_display = Number(value); | |
| applyChatDisplay(); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#toastr_position').on('change', function () { | |
| const value = $(this).find(':selected').val(); | |
| power_user.toastr_position = String(value); | |
| applyToastrPosition(); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#chat_width_slider').on('input', function (e, data) { | |
| const applyMode = data?.forced ? 'forced' : 'normal'; | |
| power_user.chat_width = Number(e.target.value); | |
| applyChatWidth(applyMode); | |
| saveSettingsDebounced(); | |
| setHotswapsDebounced(); | |
| }); | |
| $('#chat_truncation').on('input', function () { | |
| power_user.chat_truncation = Number($('#chat_truncation').val()); | |
| $('#chat_truncation_counter').val(power_user.chat_truncation); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#streaming_fps').on('input', function () { | |
| power_user.streaming_fps = Number($('#streaming_fps').val()); | |
| $('#streaming_fps_counter').val(power_user.streaming_fps); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#smooth_streaming').on('input', function () { | |
| power_user.smooth_streaming = !!$(this).prop('checked'); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#smooth_streaming_speed').on('input', function () { | |
| power_user.smooth_streaming_speed = Number($('#smooth_streaming_speed').val()); | |
| saveSettingsDebounced(); | |
| }); | |
| $('input[name="font_scale"]').on('input', async function (e, data) { | |
| const applyMode = data?.forced ? 'forced' : 'normal'; | |
| power_user.font_scale = Number(e.target.value); | |
| $('#font_scale_counter').val(power_user.font_scale); | |
| applyFontScale(applyMode); | |
| saveSettingsDebounced(); | |
| }); | |
| $('input[name="blur_strength"]').on('input', async function (e) { | |
| power_user.blur_strength = Number(e.target.value); | |
| $('#blur_strength_counter').val(power_user.blur_strength); | |
| applyBlurStrength(); | |
| saveSettingsDebounced(); | |
| }); | |
| $('input[name="shadow_width"]').on('input', async function (e) { | |
| power_user.shadow_width = Number(e.target.value); | |
| $('#shadow_width_counter').val(power_user.shadow_width); | |
| applyShadowWidth(); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#main-text-color-picker').on('change', (evt) => { | |
| power_user.main_text_color = evt.detail.rgba; | |
| applyThemeColor('main'); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#italics-color-picker').on('change', (evt) => { | |
| power_user.italics_text_color = evt.detail.rgba; | |
| applyThemeColor('italics'); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#underline-color-picker').on('change', (evt) => { | |
| power_user.underline_text_color = evt.detail.rgba; | |
| applyThemeColor('underline'); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#quote-color-picker').on('change', (evt) => { | |
| power_user.quote_text_color = evt.detail.rgba; | |
| applyThemeColor('quote'); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#blur-tint-color-picker').on('change', (evt) => { | |
| power_user.blur_tint_color = evt.detail.rgba; | |
| applyThemeColor('blurTint'); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#chat-tint-color-picker').on('change', (evt) => { | |
| power_user.chat_tint_color = evt.detail.rgba; | |
| applyThemeColor('chatTint'); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#user-mes-blur-tint-color-picker').on('change', (evt) => { | |
| power_user.user_mes_blur_tint_color = evt.detail.rgba; | |
| applyThemeColor('userMesBlurTint'); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#bot-mes-blur-tint-color-picker').on('change', (evt) => { | |
| power_user.bot_mes_blur_tint_color = evt.detail.rgba; | |
| applyThemeColor('botMesBlurTint'); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#shadow-color-picker').on('change', (evt) => { | |
| power_user.shadow_color = evt.detail.rgba; | |
| applyThemeColor('shadow'); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#border-color-picker').on('change', (evt) => { | |
| power_user.border_color = evt.detail.rgba; | |
| applyThemeColor('border'); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#themes').on('change', function () { | |
| const themeSelected = String($(this).find(':selected').val()); | |
| power_user.theme = themeSelected; | |
| applyTheme(themeSelected); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#movingUIPresets').on('change', async function () { | |
| console.log('saw MUI preset change'); | |
| const movingUIPresetSelected = String($(this).find(':selected').val()); | |
| power_user.movingUIPreset = movingUIPresetSelected; | |
| applyMovingUIPreset(movingUIPresetSelected); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#ui-preset-save-button').on('click', () => saveTheme()); | |
| $('#ui-preset-update-button').on('click', () => updateTheme()); | |
| $('#ui-preset-delete-button').on('click', () => deleteTheme()); | |
| $('#movingui-preset-save-button').on('click', saveMovingUI); | |
| $('#never_resize_avatars').on('input', function () { | |
| power_user.never_resize_avatars = !!$(this).prop('checked'); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#show_card_avatar_urls').on('input', function () { | |
| power_user.show_card_avatar_urls = !!$(this).prop('checked'); | |
| printCharactersDebounced(); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#play_message_sound').on('input', function () { | |
| power_user.play_message_sound = !!$(this).prop('checked'); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#play_sound_unfocused').on('input', function () { | |
| power_user.play_sound_unfocused = !!$(this).prop('checked'); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#auto_save_msg_edits').on('input', function () { | |
| power_user.auto_save_msg_edits = !!$(this).prop('checked'); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#character_sort_order').on('change', function () { | |
| const field = String($(this).find(':selected').data('field')); | |
| // Save sort order, but do not save search sorting, as this is a temporary sorting option | |
| if (field !== 'search') { | |
| power_user.sort_field = field; | |
| power_user.sort_order = $(this).find(':selected').data('order'); | |
| power_user.sort_rule = $(this).find(':selected').data('rule'); | |
| } | |
| printCharactersDebounced(); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#gestures-checkbox').on('change', function () { | |
| power_user.gestures = !!$('#gestures-checkbox').prop('checked'); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#auto_swipe').on('input', function () { | |
| power_user.auto_swipe = !!$(this).prop('checked'); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#auto_swipe_blacklist').on('input', function () { | |
| power_user.auto_swipe_blacklist = String($(this).val()) | |
| .split(',') | |
| .map(str => str.trim()) | |
| .filter(str => str); | |
| console.log('power_user.auto_swipe_blacklist', power_user.auto_swipe_blacklist); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#auto_swipe_minimum_length').on('input', function () { | |
| const number = Number($(this).val()); | |
| if (!isNaN(number)) { | |
| power_user.auto_swipe_minimum_length = number; | |
| saveSettingsDebounced(); | |
| } | |
| }); | |
| $('#auto_swipe_blacklist_threshold').on('input', function () { | |
| const number = Number($(this).val()); | |
| if (!isNaN(number)) { | |
| power_user.auto_swipe_blacklist_threshold = number; | |
| saveSettingsDebounced(); | |
| } | |
| }); | |
| $('#auto_fix_generated_markdown').on('input', function () { | |
| power_user.auto_fix_generated_markdown = !!$(this).prop('checked'); | |
| reloadCurrentChat(); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#console_log_prompts').on('input', function () { | |
| power_user.console_log_prompts = !!$(this).prop('checked'); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#request_token_probabilities').on('input', function () { | |
| power_user.request_token_probabilities = !!$(this).prop('checked'); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#show_group_chat_queue').on('input', function () { | |
| power_user.show_group_chat_queue = !!$(this).prop('checked'); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#auto_scroll_chat_to_bottom').on('input', function () { | |
| power_user.auto_scroll_chat_to_bottom = !!$(this).prop('checked'); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#tokenizer').on('change', function () { | |
| const value = $(this).find(':selected').val(); | |
| power_user.tokenizer = Number(value); | |
| BIAS_CACHE.clear(); | |
| saveSettingsDebounced(); | |
| // Trigger character editor re-tokenize | |
| forceCharacterEditorTokenize(); | |
| }); | |
| $('#send_on_enter').on('change', function () { | |
| const value = $(this).find(':selected').val(); | |
| power_user.send_on_enter = Number(value); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#confirm_message_delete').on('input', function () { | |
| power_user.confirm_message_delete = !!$(this).prop('checked'); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#reload_chat').on('click', async function () { | |
| const currentChatId = getCurrentChatId(); | |
| if (currentChatId !== undefined && currentChatId !== null) { | |
| await saveSettings(); | |
| await saveChatConditional(); | |
| await reloadCurrentChat(); | |
| } | |
| }); | |
| $('#allow_name1_display').on('input', function () { | |
| power_user.allow_name1_display = !!$(this).prop('checked'); | |
| reloadCurrentChat(); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#allow_name2_display').on('input', function () { | |
| power_user.allow_name2_display = !!$(this).prop('checked'); | |
| reloadCurrentChat(); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#token_padding').on('input', function () { | |
| power_user.token_padding = Number($(this).val()); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#messageTimerEnabled').on('input', function () { | |
| const value = !!$(this).prop('checked'); | |
| power_user.timer_enabled = value; | |
| switchTimer(); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#messageTimestampsEnabled').on('input', function () { | |
| const value = !!$(this).prop('checked'); | |
| power_user.timestamps_enabled = value; | |
| switchTimestamps(); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#messageModelIconEnabled').on('input', function () { | |
| const value = !!$(this).prop('checked'); | |
| power_user.timestamp_model_icon = value; | |
| switchIcons(); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#messageTokensEnabled').on('input', function () { | |
| const value = !!$(this).prop('checked'); | |
| power_user.message_token_count_enabled = value; | |
| switchTokenCount(); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#expandMessageActions').on('input', function () { | |
| const value = !!$(this).prop('checked'); | |
| power_user.expand_message_actions = value; | |
| switchMessageActions(); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#enableZenSliders').on('input', function () { | |
| const value = !!$(this).prop('checked'); | |
| if (power_user.enableLabMode === true && value === true) { | |
| //disallow zenSliders while Lab Mode is active | |
| toastr.warning('Disable Mad Lab Mode before enabling Zen Sliders'); | |
| $(this).prop('checked', false).trigger('input'); | |
| return; | |
| } | |
| power_user.enableZenSliders = value; | |
| switchZenSliders(); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#enableLabMode').on('input', function (event, { fromInit = false } = {}) { | |
| const value = !!$(this).prop('checked'); | |
| if (power_user.enableZenSliders === true && value === true) { | |
| //disallow Lab Mode if ZenSliders are active | |
| toastr.warning('Disable Zen Sliders before enabling Mad Lab Mode'); | |
| $(this).prop('checked', false).trigger('input'); | |
| return; | |
| } | |
| power_user.enableLabMode = value; | |
| switchLabMode({ noReset: fromInit }); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#mesIDDisplayEnabled').on('input', function () { | |
| const value = !!$(this).prop('checked'); | |
| power_user.mesIDDisplay_enabled = value; | |
| switchMesIDDisplay(); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#hideChatAvatarsEnabled').on('input', function () { | |
| const value = !!$(this).prop('checked'); | |
| power_user.hideChatAvatars_enabled = value; | |
| switchHideChatAvatars(); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#hotswapEnabled').on('input', function () { | |
| const value = !!$(this).prop('checked'); | |
| power_user.hotswap_enabled = value; | |
| switchHotswap(); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#prefer_character_prompt').on('input', function () { | |
| const value = !!$(this).prop('checked'); | |
| power_user.prefer_character_prompt = value; | |
| saveSettingsDebounced(); | |
| }); | |
| $('#prefer_character_jailbreak').on('input', function () { | |
| const value = !!$(this).prop('checked'); | |
| power_user.prefer_character_jailbreak = value; | |
| saveSettingsDebounced(); | |
| }); | |
| $('#continue_on_send').on('input', function () { | |
| const value = !!$(this).prop('checked'); | |
| power_user.continue_on_send = value; | |
| saveSettingsDebounced(); | |
| }); | |
| $('#quick_continue').on('input', function () { | |
| const value = !!$(this).prop('checked'); | |
| power_user.quick_continue = value; | |
| $('#mes_continue').css('display', value ? '' : 'none'); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#quick_impersonate').on('input', function () { | |
| const value = !!$(this).prop('checked'); | |
| power_user.quick_impersonate = value; | |
| $('#mes_impersonate').css('display', value ? '' : 'none'); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#trim_spaces').on('input', function () { | |
| const value = !!$(this).prop('checked'); | |
| power_user.trim_spaces = value; | |
| saveSettingsDebounced(); | |
| }); | |
| $('#relaxed_api_urls').on('input', function () { | |
| const value = !!$(this).prop('checked'); | |
| power_user.relaxed_api_urls = value; | |
| saveSettingsDebounced(); | |
| }); | |
| $('#world_import_dialog').on('input', function () { | |
| const value = !!$(this).prop('checked'); | |
| power_user.world_import_dialog = value; | |
| saveSettingsDebounced(); | |
| }); | |
| $('#enable_auto_select_input').on('input', function () { | |
| const value = !!$(this).prop('checked'); | |
| power_user.enable_auto_select_input = value; | |
| saveSettingsDebounced(); | |
| }); | |
| $('#enable_md_hotkeys').on('input', function () { | |
| const value = !!$(this).prop('checked'); | |
| power_user.enable_md_hotkeys = value; | |
| toggleMDHotkeyIconDisplay(); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#spoiler_free_mode').on('input', function () { | |
| power_user.spoiler_free_mode = !!$(this).prop('checked'); | |
| switchSpoilerMode(); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#spoiler_free_desc_button').on('click', function () { | |
| peekSpoilerMode(); | |
| $(this).toggleClass('fa-eye fa-eye-slash'); | |
| }); | |
| $('#custom_stopping_strings').on('input', function () { | |
| power_user.custom_stopping_strings = String($(this).val()).trim(); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#custom_stopping_strings_macro').change(function () { | |
| power_user.custom_stopping_strings_macro = !!$(this).prop('checked'); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#fuzzy_search_checkbox').on('input', function () { | |
| power_user.fuzzy_search = !!$(this).prop('checked'); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#persona_show_notifications').on('input', function () { | |
| power_user.persona_show_notifications = !!$(this).prop('checked'); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#persona_allow_multi_connections').on('input', function () { | |
| power_user.persona_allow_multi_connections = !!$(this).prop('checked'); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#persona_auto_lock').on('input', function () { | |
| power_user.persona_auto_lock = !!$(this).prop('checked'); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#encode_tags').on('input', async function () { | |
| power_user.encode_tags = !!$(this).prop('checked'); | |
| await reloadCurrentChat(); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#disable_group_trimming').on('input', function () { | |
| power_user.disable_group_trimming = !!$(this).prop('checked'); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#debug_menu').on('click', function () { | |
| showDebugMenu(); | |
| }); | |
| $('#bogus_folders').on('input', function () { | |
| power_user.bogus_folders = !!$(this).prop('checked'); | |
| printCharactersDebounced(); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#zoomed_avatar_magnification').on('input', function () { | |
| power_user.zoomed_avatar_magnification = !!$(this).prop('checked'); | |
| printCharactersDebounced(); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#aux_field').on('change', function () { | |
| const value = $(this).find(':selected').val(); | |
| power_user.aux_field = String(value); | |
| printCharactersDebounced(); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#tag_import_setting').on('change', function () { | |
| const value = $(this).find(':selected').val(); | |
| power_user.tag_import_setting = Number(value); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#stscript_autocomplete_state').on('input', function () { | |
| power_user.stscript.autocomplete.state = Number($(this).val()); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#stscript_autocomplete_autoHide').on('input', function () { | |
| power_user.stscript.autocomplete.autoHide = !!$(this).prop('checked'); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#stscript_matching').on('change', function () { | |
| const value = $(this).find(':selected').val(); | |
| power_user.stscript.matching = String(value); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#stscript_autocomplete_style').on('change', function () { | |
| const value = $(this).find(':selected').val(); | |
| power_user.stscript.autocomplete.style = String(value); | |
| document.body.setAttribute('data-stscript-style', power_user.stscript.autocomplete.style); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#stscript_autocomplete_select').on('change', function () { | |
| const value = $(this).find(':selected').val(); | |
| power_user.stscript.autocomplete.select = parseInt(String(value)); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#stscript_autocomplete_font_scale').on('input', function () { | |
| const value = $(this).val(); | |
| $('#stscript_autocomplete_font_scale_counter').val(value); | |
| power_user.stscript.autocomplete.font.scale = Number(value); | |
| document.body.style.setProperty('--ac-font-scale', value.toString()); | |
| window.dispatchEvent(new Event('resize', { bubbles: true })); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#stscript_autocomplete_font_scale_counter').on('input', function () { | |
| const value = $(this).val(); | |
| $('#stscript_autocomplete_font_scale').val(value); | |
| power_user.stscript.autocomplete.font.scale = Number(value); | |
| document.body.style.setProperty('--ac-font-scale', value.toString()); | |
| window.dispatchEvent(new Event('resize', { bubbles: true })); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#stscript_autocomplete_width_left').on('input', function () { | |
| const value = $(this).val(); | |
| power_user.stscript.autocomplete.width.left = Number(value); | |
| /**@type {HTMLElement}*/(this.closest('.doubleRangeInputContainer')).style.setProperty('--value', value.toString()); | |
| window.dispatchEvent(new Event('resize', { bubbles: true })); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#stscript_autocomplete_width_right').on('input', function () { | |
| const value = $(this).val(); | |
| power_user.stscript.autocomplete.width.right = Number(value); | |
| /**@type {HTMLElement}*/(this.closest('.doubleRangeInputContainer')).style.setProperty('--value', value.toString()); | |
| window.dispatchEvent(new Event('resize', { bubbles: true })); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#stscript_parser_flag_strict_escaping').on('click', function () { | |
| const value = $(this).prop('checked'); | |
| power_user.stscript.parser.flags[PARSER_FLAG.STRICT_ESCAPING] = value; | |
| saveSettingsDebounced(); | |
| }); | |
| $('#stscript_parser_flag_replace_getvar').on('click', function () { | |
| const value = $(this).prop('checked'); | |
| power_user.stscript.parser.flags[PARSER_FLAG.REPLACE_GETVAR] = value; | |
| saveSettingsDebounced(); | |
| }); | |
| $('#restore_user_input').on('input', function () { | |
| power_user.restore_user_input = !!$(this).prop('checked'); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#reduced_motion').on('input', function () { | |
| power_user.reduced_motion = !!$(this).prop('checked'); | |
| switchReducedMotion(); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#compact_input_area').on('input', function () { | |
| power_user.compact_input_area = !!$(this).prop('checked'); | |
| switchCompactInputArea(); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#show_swipe_num_all_messages').on('input', function () { | |
| power_user.show_swipe_num_all_messages = !!$(this).prop('checked'); | |
| switchSwipeNumAllMessages(); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#auto-connect-checkbox').on('input', function () { | |
| power_user.auto_connect = !!$(this).prop('checked'); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#auto-load-chat-checkbox').on('input', function () { | |
| power_user.auto_load_chat = !!$(this).prop('checked'); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#forbid_external_media').on('input', function () { | |
| power_user.forbid_external_media = !!$(this).prop('checked'); | |
| saveSettingsDebounced(); | |
| reloadCurrentChat(); | |
| }); | |
| $('#pin_styles').on('input', function () { | |
| power_user.pin_styles = !!$(this).prop('checked'); | |
| saveSettingsDebounced(); | |
| applyStylePins(); | |
| }); | |
| $('#click_to_edit').on('input', function () { | |
| power_user.click_to_edit = !!$(this).prop('checked'); | |
| saveSettingsDebounced(); | |
| }); | |
| $('#ui_preset_import_button').on('click', function () { | |
| $('#ui_preset_import_file').trigger('click'); | |
| }); | |
| $('#ui_preset_import_file').on('change', async function () { | |
| const inputElement = this instanceof HTMLInputElement && this; | |
| try { | |
| const file = inputElement?.files?.[0]; | |
| await importTheme(file); | |
| } catch (error) { | |
| console.error('Error importing UI theme', error); | |
| toastr.error(String(error), 'Failed to import UI theme'); | |
| } finally { | |
| if (inputElement) { | |
| inputElement.value = null; | |
| } | |
| } | |
| }); | |
| $('#ui_preset_export_button').on('click', async function () { | |
| await exportTheme(); | |
| }); | |
| $(document).on('click', '#debug_table [data-debug-function]', function () { | |
| const functionId = $(this).data('debug-function'); | |
| const functionRecord = debug_functions.find(f => f.functionId === functionId); | |
| if (functionRecord) { | |
| functionRecord.func(); | |
| } else { | |
| console.warn(`Debug function ${functionId} not found`); | |
| } | |
| }); | |
| $(window).on('focus', function () { | |
| browser_has_focus = true; | |
| }); | |
| $(window).on('blur', function () { | |
| browser_has_focus = false; | |
| }); | |
| SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | |
| name: 'vn', | |
| callback: toggleWaifu, | |
| helpString: 'Swaps Visual Novel Mode On/Off', | |
| })); | |
| SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | |
| name: 'newchat', | |
| /** @type {(args: { delete: string?}, string) => Promise<''>} */ | |
| callback: async (args, _) => { | |
| await doNewChat({ deleteCurrentChat: isTrueBoolean(args.delete) }); | |
| return ''; | |
| }, | |
| namedArgumentList: [ | |
| SlashCommandNamedArgument.fromProps({ | |
| name: 'delete', | |
| description: 'delete the current chat', | |
| typeList: [ARGUMENT_TYPE.BOOLEAN], | |
| defaultValue: 'false', | |
| enumList: commonEnumProviders.boolean('trueFalse')(), | |
| }), | |
| ], | |
| helpString: 'Start a new chat with the current character', | |
| })); | |
| SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | |
| name: 'random', | |
| callback: doRandomChat, | |
| unnamedArgumentList: [ | |
| SlashCommandArgument.fromProps({ | |
| description: 'optional tag name', | |
| typeList: [ARGUMENT_TYPE.STRING], | |
| enumProvider: () => tags.filter(tag => Object.values(tag_map).some(x => x.includes(tag.id))).map(tag => new SlashCommandEnumValue(tag.name, null, enumTypes.enum, enumIcons.tag)), | |
| }), | |
| ], | |
| helpString: 'Start a new chat with a random character. If an argument is provided, only considers characters that have the specified tag.', | |
| })); | |
| SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | |
| name: 'del', | |
| callback: doDelMode, | |
| aliases: ['delete', 'delmode'], | |
| unnamedArgumentList: [ | |
| new SlashCommandArgument( | |
| 'optional number', [ARGUMENT_TYPE.NUMBER], false, | |
| ), | |
| ], | |
| helpString: 'Enter message deletion mode, and auto-deletes last N messages if numeric argument is provided.', | |
| returns: 'The text of the deleted messages.', | |
| })); | |
| SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | |
| name: 'cut', | |
| callback: doMesCut, | |
| returns: 'the text of cut messages separated by a newline', | |
| unnamedArgumentList: [ | |
| SlashCommandArgument.fromProps({ | |
| description: 'number or range', | |
| typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.RANGE], | |
| isRequired: true, | |
| acceptsMultiple: true, | |
| enumProvider: commonEnumProviders.messages(), | |
| }), | |
| ], | |
| helpString: ` | |
| <div> | |
| Cuts the specified message or continuous chunk from the chat. | |
| </div> | |
| <div> | |
| Ranges are inclusive! | |
| </div> | |
| <div> | |
| <strong>Example:</strong> | |
| <ul> | |
| <li> | |
| <pre><code>/cut 0-10</code></pre> | |
| </li> | |
| </ul> | |
| </div> | |
| `, | |
| aliases: [], | |
| })); | |
| SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | |
| name: 'resetpanels', | |
| callback: doResetPanels, | |
| helpString: 'resets UI panels to original state', | |
| aliases: ['resetui'], | |
| })); | |
| SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | |
| name: 'bgcol', | |
| callback: setAvgBG, | |
| helpString: '– WIP test of auto-bg avg coloring', | |
| })); | |
| SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | |
| name: 'theme', | |
| callback: setThemeCallback, | |
| unnamedArgumentList: [ | |
| SlashCommandArgument.fromProps({ | |
| description: 'theme name', | |
| typeList: [ARGUMENT_TYPE.STRING], | |
| enumProvider: () => themes.map(theme => new SlashCommandEnumValue(theme.name)), | |
| }), | |
| ], | |
| helpString: ` | |
| <div> | |
| Sets a UI theme by name. | |
| </div> | |
| <div> | |
| If no theme name is is provided, this will return the currently active theme. | |
| </div> | |
| <div> | |
| <strong>Example:</strong> | |
| <ul> | |
| <li> | |
| <pre><code>/theme Cappuccino</code></pre> | |
| </li> | |
| <li> | |
| <pre><code>/theme</code></pre> | |
| </li> | |
| </ul> | |
| </div> | |
| `, | |
| })); | |
| SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | |
| name: 'css-var', | |
| /** @param {{to: string, varname: string }} args @param {string} value @returns {string} */ | |
| callback: (args, value) => { | |
| // Map enum to target selector | |
| const targetSelector = { | |
| chat: '#chat', | |
| background: '#bg1', | |
| gallery: '#gallery', | |
| zoomedAvatar: 'div.zoomed_avatar', | |
| }[args.to || 'chat']; | |
| if (!targetSelector) { | |
| toastr.error(`Invalid target: ${args.to}`); | |
| return; | |
| } | |
| if (!args.varname) { | |
| toastr.error('CSS variable name is required'); | |
| return; | |
| } | |
| if (!args.varname.startsWith('--')) { | |
| toastr.error('CSS variable names must start with "--"'); | |
| return; | |
| } | |
| const elements = document.querySelectorAll(targetSelector); | |
| if (elements.length === 0) { | |
| toastr.error(`No elements found for ${args.to ?? 'chat'} with selector "${targetSelector}"`); | |
| return; | |
| } | |
| elements.forEach(element => { | |
| element.style.setProperty(args.varname, value); | |
| }); | |
| console.info(`Set CSS variable "${args.varname}" to "${value}" on "${targetSelector}"`); | |
| }, | |
| namedArgumentList: [ | |
| SlashCommandNamedArgument.fromProps({ | |
| name: 'varname', | |
| description: 'CSS variable name (starting with double dashes)', | |
| typeList: [ARGUMENT_TYPE.STRING], | |
| isRequired: true, | |
| }), | |
| SlashCommandNamedArgument.fromProps({ | |
| name: 'to', | |
| description: 'The target element to which the CSS variable will be applied', | |
| typeList: [ARGUMENT_TYPE.STRING], | |
| enumList: [ | |
| new SlashCommandEnumValue('chat', null, enumTypes.enum, enumIcons.message), | |
| new SlashCommandEnumValue('background', null, enumTypes.enum, enumIcons.image), | |
| new SlashCommandEnumValue('zoomedAvatar', null, enumTypes.enum, enumIcons.character), | |
| new SlashCommandEnumValue('gallery', null, enumTypes.enum, enumIcons.image), | |
| ], | |
| defaultValue: 'chat', | |
| }), | |
| ], | |
| unnamedArgumentList: [ | |
| SlashCommandArgument.fromProps({ | |
| description: 'CSS variable value', | |
| typeList: [ARGUMENT_TYPE.STRING], | |
| isRequired: true, | |
| }), | |
| ], | |
| helpString: ` | |
| <div> | |
| Sets a CSS variable to a specified value on a target element. | |
| <br /> | |
| Only setting of variable names is supported. They have to be prefixed with double dashes ("--exampleVar"). | |
| Setting actual CSS properties is not supported. Custom CSS in the theme settings can be used for that. | |
| <br /><br /> | |
| <b>This value will be gone after a page reload!</b> | |
| </div> | |
| <div> | |
| <strong>Example:</strong> | |
| <ul> | |
| <li> | |
| <pre><code>/css-var varname="--SmartThemeBodyColor" #ff0000</code></pre> | |
| Sets the text color of the chat to red | |
| </li> | |
| <li> | |
| <pre><code>/css-var to=zoomedAvatar varname="--SmartThemeBlurStrength" 0</code></pre> | |
| Remove the blur from the zoomed avatar | |
| </li> | |
| </ul> | |
| </div> | |
| `, | |
| })); | |
| SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | |
| name: 'movingui', | |
| callback: setmovingUIPreset, | |
| unnamedArgumentList: [ | |
| SlashCommandArgument.fromProps({ | |
| description: 'name', | |
| typeList: [ARGUMENT_TYPE.STRING], | |
| isRequired: true, | |
| enumProvider: () => movingUIPresets.map(preset => new SlashCommandEnumValue(preset.name)), | |
| }), | |
| ], | |
| helpString: 'activates a movingUI preset by name', | |
| })); | |
| SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | |
| name: 'stop-strings', | |
| aliases: ['stopping-strings', 'custom-stopping-strings', 'custom-stop-strings'], | |
| helpString: ` | |
| <div> | |
| Sets a list of custom stopping strings. Gets the list if no value is provided. | |
| Use a "force" argument to force set an empty value. | |
| </div> | |
| <div> | |
| <strong>Examples:</strong> | |
| </div> | |
| <ul> | |
| <li>Force set an empty value: <pre><code class="language-stscript">/stop-strings force="true" {{noop}}</code></pre></li> | |
| <li>Value must be a JSON-serialized array: <pre><code class="language-stscript">/stop-strings ["goodbye", "farewell"]</code></pre></li> | |
| <li>Pipe characters must be escaped with a backslash: <pre><code class="language-stscript">/stop-strings ["left\\|right"]</code></pre></li> | |
| </ul> | |
| `, | |
| returns: ARGUMENT_TYPE.LIST, | |
| namedArgumentList: [ | |
| SlashCommandNamedArgument.fromProps({ | |
| name: 'force', | |
| description: 'force set a value if empty', | |
| typeList: [ARGUMENT_TYPE.BOOLEAN], | |
| defaultValue: 'false', | |
| enumList: commonEnumProviders.boolean('trueFalse')(), | |
| }), | |
| ], | |
| unnamedArgumentList: [ | |
| SlashCommandArgument.fromProps({ | |
| description: 'list of strings', | |
| typeList: [ARGUMENT_TYPE.LIST], | |
| acceptsMultiple: false, | |
| isRequired: false, | |
| }), | |
| ], | |
| callback: (args, value) => { | |
| const force = isTrueBoolean(String(args?.force ?? false)); | |
| value = String(value ?? '').trim(); | |
| // Skip processing if no value and not forced | |
| if (!force && !value) { | |
| return power_user.custom_stopping_strings; | |
| } | |
| // Use empty array for forced empty value | |
| if (force && !value) { | |
| value = JSON.stringify([]); | |
| } | |
| const parsedValue = ((x) => { try { return JSON.parse(x.toString()); } catch { return null; } })(value); | |
| if (!parsedValue || !Array.isArray(parsedValue)) { | |
| throw new Error('Invalid list format. The value must be a JSON-serialized array of strings.'); | |
| } | |
| parsedValue.forEach((item, index) => { | |
| parsedValue[index] = String(item); | |
| }); | |
| power_user.custom_stopping_strings = JSON.stringify(parsedValue); | |
| $('#custom_stopping_strings').val(power_user.custom_stopping_strings); | |
| saveSettingsDebounced(); | |
| return power_user.custom_stopping_strings; | |
| }, | |
| })); | |
| SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | |
| name: 'start-reply-with', | |
| helpString: ` | |
| <div> | |
| Sets a "Start Reply With". Gets the current value if no value is provided. | |
| Use a "force" argument to force set an empty value. | |
| </div> | |
| <div> | |
| <strong>Examples:</strong> | |
| </div> | |
| <ul> | |
| <li>Set the field value: <pre><code class="language-stscript">/start-reply-with Sure!</code></pre></li> | |
| <li>Force set an empty value: <pre><code class="language-stscript">/start-reply-with force="true" {{noop}}</code></pre></li> | |
| </ul> | |
| `, | |
| namedArgumentList: [ | |
| SlashCommandNamedArgument.fromProps({ | |
| name: 'force', | |
| description: 'force set a value if empty', | |
| typeList: [ARGUMENT_TYPE.BOOLEAN], | |
| defaultValue: 'false', | |
| enumList: commonEnumProviders.boolean('trueFalse')(), | |
| }), | |
| ], | |
| unnamedArgumentList: [ | |
| SlashCommandArgument.fromProps({ | |
| description: 'value', | |
| typeList: [ARGUMENT_TYPE.STRING], | |
| acceptsMultiple: false, | |
| isRequired: false, | |
| }), | |
| ], | |
| callback: (args, value) => { | |
| const force = isTrueBoolean(String(args?.force ?? false)); | |
| // Skip processing if no value and not forced | |
| if (!force && !value) { | |
| return power_user.user_prompt_bias; | |
| } | |
| power_user.user_prompt_bias = String(value ?? ''); | |
| $('#start_reply_with').val(power_user.user_prompt_bias); | |
| saveSettingsDebounced(); | |
| return power_user.user_prompt_bias; | |
| }, | |
| })); | |
| }); | |