e9bedadc0b
* Macros: make category optional, default to UNCATEGORIZED
* Tests: Update macro tests to match new error handling behavior
- Change MacroRegistry tests from expecting thrown errors to capturing console.error logs
- Update MacroLexer tests to expect plaintext fallback instead of lexer errors for invalid tokens
- Fix MacroEngine test to use `char` macro instead of `newline` for arity validation
- Update MacroParser test with corrected expected error message for invalid identifiers
- Remove "[Error]" prefixes from test descriptions where lexer no longer errors
* Macros: Implement macro execution flags system
- Add MacroFlags module with flag parsing and validation (!, ?, ~, /, >, ., $, #)
- Update lexer to tokenize flags as separate tokens before macro identifier
- Modify parser to capture flags in CST under 'flags' label
- Update CST walker to parse flag tokens into MacroFlags object
- Pass parsed flags to macro handlers via MacroCall and MacroContext
- Update autocomplete parser to handle flags before identifier
- Add comprehensive tests for flag parsing,
* Macros: Fix autocomplete positioning for macros with flags and whitespace
- Add identifierStart to parseMacroContext to track where identifier begins in macro text
- Update autocomplete to use identifierStart for correct range calculation
- Simplify indexMacros regex to use global flag instead of manual loop
- Use parseMacroContext in indexMacros to extract identifier (handles flags/whitespace)
- Fix autocomplete range starting at wrong position when flags or whitespace present
* Macros: Add autocomplete support for macro execution flags
- Add MacroFlagAutoCompleteOption class for rendering flag options in autocomplete
- Extend parseMacroContext to track currentFlag (flag cursor is on) and isInFlagsArea
- Track flagEndPositions to determine which flag cursor is currently typing
- Update #buildEnhancedMacroOptions to show flag options when cursor is in flags area
- Show current flag first if cursor just typed it, then show remaining available flags
- Add renderItem and renderDetails methods to MacroFlag
* Macros: Implement scoped macro syntax with opening and closing tags
- Add scoped macro processing to MacroCstWalker to find and merge opening/closing pairs
- Parse closing block flag (/) to identify closing macros and match with opening tags
- Extract content between opening and closing tags as the last unnamed argument
- Add `isScoped` property to MacroCall and MacroContext to track scoped invocations
- Implement `#processScopedMacros` to find outermost matching pairs and handle nesting
* Macros: Add autocomplete warnings, scoped content info, and closing tag suggestions
- Add arity warning banners in autocomplete details for invalid argument counts
- Show warning when using space-separated args on multi-arg or no-arg macros
- Add scoped content info banner when cursor is inside unclosed scoped macro
- Implement MacroClosingTagAutoCompleteOption to suggest closing tags for scoped macros
- Add sortPriority property to AutoCompleteOption for controlling sort order
* SlashCommands: Disable unimplemented flags and closing flag when no unclosed scopes in autocomplete
- Set closing block flag as non-selectable when no unclosed scopes exist
- Set unimplemented flags as non-selectable with empty valueProvider
- Lower sort priority (12) for non-selectable flags vs selectable flags (10)
* Autocomplete: Show scoped content info and auto-close no-arg macros
- Show scoped content info when cursor is at closing }} of unclosed scoped macro
- Auto-complete no-arg macros with closing }} using valueProvider
- Trigger autocomplete on select (isSelect) to refresh after choosing an option
- Simplify MacroFlagAutoCompleteOption to use base makeItem for consistent styling
- Change closing tag icon from '{/}' to '{/' for better visual consistency
* Macros: Add scoped trim macro to trim content inside opening/closing tags
- Add scoped usage for {{trim}}content{{/trim}} to trim whitespace from content
- Keep non-scoped {{trim}} behavior (post-processing marker) for backward compatibility
- Add optional unnamed 'content' argument for scoped usage
- Update description to explain both scoped and non-scoped behavior
- Handler checks isScoped flag to determine which behavior to use
* Autocomplete: Fix closing tag parsing to prevent `/` being treated as flag
- Add special case in parseMacroContext to detect closing tags (`/` + identifier char)
- Stop flag parsing when `/` is followed by identifier character (closing tag syntax)
- Simplify MacroClosingTagAutoCompleteOption valueProvider to return full closing tag
- Remove input-based logic since autocomplete replaces entire identifier
* Macros: Add {{if}} conditional macro with auto-resolution of macro names
- Add {{if condition}}content{{/if}} macro to conditionally show content
- Auto-resolve condition if it matches a registered macro name (0 required args)
- Support both scoped content ({{if x}}...{{/if}}) and explicit args ({{if::x::content}})
- Treat empty string, "false", "off", "0" as falsy conditions
- Inherit environment context when resolving macro names
- Update autocomplete warning to allow space-separated syntax
* Macros: Add centralized identifier validation with pattern enforcement
- Export MACRO_IDENTIFIER_PATTERN from MacroLexer for reuse across modules
- Add isIdentifierValid() helper function to validate macro names and aliases
- Enforce identifier pattern: must start with letter, followed by word chars or hyphens
- Update macro registration to validate both primary names and alias identifiers
- Improve error messages to explain identifier requirements
- Add comprehensive e2e tests for valid/invalid identifier patterns
* Macros: Add tests for scoped {{trim}} macro functionality
* SlashCommands: Fix macro indexing to properly handle nested macros with brace depth tracking
- Replace regex-based macro detection with manual brace depth tracking
- Track opening/closing brace pairs to correctly identify macro boundaries
- Ensure nested macros like {{reverse::Hey {{user}}}} are properly indexed
- Index both outer and inner macros by scanning content recursively
- Handle unclosed macros by defaulting to end of text
* Macros: Add {{else}} branch support to {{if}} conditional macro
- Add {{else}} macro as marker to split then/else branches in {{if}} blocks
- Use control character sequence (\u0000\u001FELSE\u001F\u0000) as internal marker
- Split scoped content on else marker and trim both branches independently
- Return then-branch if condition is truthy, else-branch if falsy
- Auto-suggest {{else}} in autocomplete when inside scoped {{if}} block
- Make {{else}} non-selectable in autocomplete when outside {{if}} scope
* Macros: Add negation support to {{if}} conditional macro with ! prefix
- Add ! prefix support to invert condition evaluation in {{if}} macro
- Parse original macro text to detect ! prefix before macro resolution
- Strip ! from condition after detecting inversion to avoid double-negation
- Invert isFalsy result when ! prefix is detected in original condition
- Prevent ! in resolved values from triggering inversion (only original syntax)
* Autocomplete: Add {{if}} condition autocomplete with zero-arg macro suggestions
- Add EnhancedMacroAutoCompleteOptions typedef for noBraces/paddingAfter/closeWithBraces options
- Support options object in EnhancedMacroAutoCompleteOption constructor alongside context
- Add noBraces mode to display macro names without {{ }} braces (for use as values)
- Add paddingAfter option to match opening whitespace style before closing }}
* Autocomplete: Match opening whitespace padding when auto-closing macros
* Fix `{{if}}` example usages
* Macros: Hide `comment` alias from autocomplete suggestions for `//` macro
* Macros: Simplify {{trim}} handler with destructured parameters and clearer content check
* Macros: Use MacroEngine.evaluate for zero-arg macro resolution in {{if}} condition handler
* Macros: Add auto-trim for scoped content with # flag to preserve whitespace
- Auto-trim scoped content by default in MacroCstWalker before passing to handlers
- Add preserveWhitespace flag (# symbol) to prevent auto-trimming when needed
- Rename legacyHash flag to preserveWhitespace across engine and definitions
- Update {{trim}} handler to rely on engine auto-trim for scoped content
- Update {{if}} handler to respect # flag when trimming branches around {{else}} marker
* Macros: Clarify macro name validation error message to use "alphanumeric characters" instead of "word chars"
* Add 'setspriteoverride' optional 'name' argument
* Refactor ElevenLabs TTS API key handling (#4906)
* Refactor ElevenLabs TTS API key handling #4483
* Remove unused connection button and related event handler from ElevenLabs TTS provider
* Add ElevenLabs STT endpoint
* Add caching system prompt feature for OpenRouter Gemini (#4903)
* feat: add caching system prompt for OpenRouter Gemini
* fix: resolve reviews
* Update GitHub links to llama.cpp
* Add model selection support for llama.cpp router mode (#4910)
* Add model selection support for llama.cpp router mode
- Add llamacpp_model setting to textgen-settings.js
- Implement loadLlamaCppModels() function to fetch and populate models
- Add onLlamaCppModelSelect() handler for model selection
- Update status check to load llama.cpp models when connecting
- Update getTextGenModel() to return selected llama.cpp model
- Add model dropdown to HTML UI in llama.cpp section
- Initialize event handlers and Select2 for better UX
- Add llamacpp_model to preset manager for save/load support
- Add llamacpp_model to slash commands support
This implements model selection for llama.cpp router mode, allowing
users to select from multiple models without restarting the server.
Follows the same pattern as Ollama, Tabby, and vLLM implementations.
* Correct spelling
* Fix clear selection position
---------
Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>
* Add glm-4.7 model option and context mapping for Z.AI
* Add select2 style for NanoGPT list
Closes #4911
* Disable macro engine init traces
* Update type annotations for instruct presets and context presets
* Fix test for new error text
* Add MacroStoryString tests
* Add trimContent utility for consistent indentation dedenting in scoped macros
- Add trimScopedContent method to MacroEngine that trims and dedents scoped content based on first non-empty line indentation
- Pass trimContent utility through evaluation context to all macro handlers
- Update {{if}} macro to use trimContent instead of direct trim() call
- Update auto-trim logic in MacroCstWalker to use trimContent for consistent dedenting
- Add trimContent to MacroExecutionContext type definitions
* Add ELSE_MARKER export and cleanup leftover markers in macro processing
* Update trimContent parameter to use options object pattern in JSDoc
* Add processor registration system to MacroEngine with priority-based execution
- Add MacroProcessor callback and RegisteredProcessor typedef for pre/post processors
- Add addPreProcessor/removePreProcessor and addPostProcessor/removePostProcessor methods with priority-based sorting
- Refactor core legacy syntax handling into registered processors with reserved priorities (0-50)
- Move legacy time syntax, marker replacements, brace unescaping, trim macro, and ELSE_MARKER cleanup to registered processors
* Split core processor registration into separate pre and post processor methods
---------
Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>
4566 lines
170 KiB
JavaScript
4566 lines
170 KiB
JavaScript
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,
|
||
online_status,
|
||
messageFormatting,
|
||
extension_prompt_types,
|
||
extension_prompt_roles,
|
||
deleteMessage,
|
||
} 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,
|
||
updateBindModelTemplatesState,
|
||
} from './instruct-mode.js';
|
||
|
||
import { getTagsList, tag_import_setting, tag_map, tag_sort_mode, 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, getSanitizedFilename, 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';
|
||
import { bindModelTemplates } from './chat-templates.js';
|
||
import { IMAGE_OVERSWIPE, MEDIA_DISPLAY } from './constants.js';
|
||
import { t } from './i18n.js';
|
||
|
||
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,
|
||
};
|
||
|
||
export 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,
|
||
};
|
||
|
||
export const 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_no_think: false,
|
||
smooth_streaming_speed: 50,
|
||
stream_fade_in: false,
|
||
|
||
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,
|
||
tag_sort_mode: tag_sort_mode.MANUAL,
|
||
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: '',
|
||
story_string_prefix: '',
|
||
story_string_suffix: '',
|
||
stop_sequence: '',
|
||
wrap: true,
|
||
macro: true,
|
||
names_behavior: names_behavior_types.FORCE,
|
||
activation_regex: '',
|
||
bind_to_context: false,
|
||
user_alignment_message: '',
|
||
system_same_as_user: false,
|
||
/** @deprecated Use output_suffix instead */
|
||
separator_sequence: '',
|
||
sequences_as_stop_strings: true,
|
||
},
|
||
|
||
context: {
|
||
preset: 'Default',
|
||
story_string: defaultStoryString,
|
||
chat_start: defaultChatStart,
|
||
example_separator: defaultExampleSeparator,
|
||
use_stop_strings: true,
|
||
names_as_stop_strings: true,
|
||
story_string_position: extension_prompt_types.IN_PROMPT,
|
||
story_string_role: extension_prompt_roles.SYSTEM,
|
||
story_string_depth: 1,
|
||
},
|
||
|
||
instruct_derived: false,
|
||
context_derived: false,
|
||
context_size_derived: false,
|
||
/** User-defined model identifier / chat template hash to instruct/context template mappings */
|
||
model_templates_mappings: {},
|
||
/** The chat template hash of the currently loaded model, if any; used when deriving mappings */
|
||
chat_template_hash: '',
|
||
|
||
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,
|
||
experimental_macro_engine: 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,
|
||
media_display: MEDIA_DISPLAY.LIST,
|
||
image_overswipe: IMAGE_OVERSWIPE.GENERATE,
|
||
};
|
||
|
||
let themes = [];
|
||
let movingUIPresets = [];
|
||
/** @type {ContextSettings[]} */
|
||
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 },
|
||
{ id: 'context_story_string_position', property: 'story_string_position', isCheckbox: false, isGlobalSetting: false, defaultValue: extension_prompt_types.IN_PROMPT, trigger: true },
|
||
{ id: 'context_story_string_depth', property: 'story_string_depth', isCheckbox: false, isGlobalSetting: false, defaultValue: 1 },
|
||
{ id: 'context_story_string_role', property: 'story_string_role', isCheckbox: false, isGlobalSetting: false, defaultValue: extension_prompt_roles.SYSTEM },
|
||
|
||
// 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);
|
||
|
||
export 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"
|
||
*/
|
||
export 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"
|
||
*/
|
||
export 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(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: 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 = parseFloat(Number(ui.value).toFixed(decimals));
|
||
offVal = parseFloat(Number(offVal).toFixed(decimals));
|
||
allVal = parseFloat(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 = parseFloat(((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');
|
||
$('#creators_note_desc_hidden').show();
|
||
}
|
||
else {
|
||
$('#descriptionWrapper').show();
|
||
$('#firstMessageWrapper').show();
|
||
$('#spoiler_free_desc').removeClass('flex1');
|
||
$('#creators_note_desc_hidden').hide();
|
||
}
|
||
}
|
||
|
||
function peekSpoilerMode() {
|
||
$('#descriptionWrapper').toggle();
|
||
$('#firstMessageWrapper').toggle();
|
||
$('#spoiler_free_desc').toggleClass('flex1');
|
||
$('#creators_note_desc_hidden').toggle();
|
||
}
|
||
|
||
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 ([null, undefined].includes(power_user.chat_display)) {
|
||
console.debug('applyChatDisplay: saw no chat display type defined');
|
||
power_user.chat_display = chat_styles.DEFAULT;
|
||
}
|
||
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);
|
||
$(`#toastr_position option[value="${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);
|
||
}
|
||
|
||
/**
|
||
* Checks if the chat needs to be reloaded to apply media display settings.
|
||
* @returns {boolean} True if the chat needs reload to apply media display settings
|
||
*/
|
||
function isMediaDisplayReloadNeeded() {
|
||
// A user is not currently in a chat.
|
||
const chatId = getCurrentChatId();
|
||
if (!chatId) {
|
||
return false;
|
||
}
|
||
|
||
const firstDisplayedIndex = getFirstDisplayedMessageId();
|
||
const hasUnprocessedMediaMessages = chat.some((message, index) => {
|
||
// Skip messages that are not currently displayed
|
||
if (index < firstDisplayedIndex) {
|
||
return false;
|
||
}
|
||
const hasMediaAttachments = Array.isArray(message?.extra?.media) && message.extra.media.length > 0;
|
||
const lacksMediaDisplay = !message?.extra?.media_display;
|
||
return hasMediaAttachments && lacksMediaDisplay;
|
||
});
|
||
|
||
return hasUnprocessedMediaMessages;
|
||
}
|
||
|
||
/**
|
||
* Shows a toast notification prompting the user to reload the chat if media display settings have changed
|
||
* and there are messages with media attachments that haven't been processed with the new display format.
|
||
*/
|
||
function showMediaDisplayReloadPrompt() {
|
||
if (!isMediaDisplayReloadNeeded()) {
|
||
return;
|
||
}
|
||
toastr.info(
|
||
t`Reload the chat to apply the changes. Click here to reload.`,
|
||
t`Media Style changed`,
|
||
{ onclick: () => void reloadCurrentChat() },
|
||
);
|
||
}
|
||
|
||
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);
|
||
},
|
||
},
|
||
{
|
||
key: 'media_display',
|
||
action: (oldValue, newValue) => {
|
||
$('#media_display').val(power_user.media_display);
|
||
if (oldValue !== newValue) {
|
||
showMediaDisplayReloadPrompt();
|
||
}
|
||
},
|
||
},
|
||
];
|
||
|
||
for (const { key, selector, type, action } of themeProperties) {
|
||
if (theme[key] !== undefined) {
|
||
const oldValue = power_user[key];
|
||
const newValue = theme[key];
|
||
power_user[key] = newValue;
|
||
if (selector) $(selector).attr('color', newValue);
|
||
if (type) applyThemeColor(type);
|
||
if (action) action(oldValue, newValue);
|
||
} 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 });
|
||
}
|
||
|
||
export 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
|
||
export 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;
|
||
}
|
||
if (Object.hasOwn(settings.power_user, 'auto_sort_tags') && !Object.hasOwn(settings.power_user, 'tag_sort_mode')) {
|
||
settings.power_user.tag_sort_mode = settings.power_user.auto_sort_tags ? tag_sort_mode.ALPHABETICAL : tag_sort_mode.MANUAL;
|
||
delete settings.power_user.auto_sort_tags;
|
||
}
|
||
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 (typeof power_user.chat_display !== 'number') {
|
||
power_user.chat_display = chat_styles.DEFAULT;
|
||
}
|
||
|
||
if (typeof power_user.waifuMode !== 'boolean') {
|
||
power_user.waifuMode = false;
|
||
}
|
||
|
||
if (typeof power_user.chat_width !== 'number') {
|
||
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;
|
||
}
|
||
|
||
if (power_user?.instruct?.derived === true) {
|
||
power_user.instruct_derived = true;
|
||
delete power_user.instruct.derived;
|
||
}
|
||
|
||
// Reset the saved chat template hash
|
||
power_user.chat_template_hash = '';
|
||
|
||
$('#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);
|
||
$('#experimental_macro_engine').prop('checked', power_user.experimental_macro_engine);
|
||
$('#example_messages_behavior').val(getExampleMessagesBehavior());
|
||
$(`#example_messages_behavior option[value="${getExampleMessagesBehavior()}"]`).prop('selected', true);
|
||
$('#instruct_derived').parent().find('i').toggleClass('toggleEnabled', !!power_user.instruct_derived);
|
||
$('#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}"]`).prop('selected', true);
|
||
$(`#send_on_enter option[value=${power_user.send_on_enter}]`).prop('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}]`).prop('selected', true).trigger('change');
|
||
$(`#toastr_position option[value=${power_user.toastr_position}]`).prop('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_no_think').prop('checked', power_user.smooth_streaming_no_think);
|
||
$('#smooth_streaming_speed').val(power_user.smooth_streaming_speed);
|
||
|
||
$('#stream_fade_in').prop('checked', power_user.stream_fade_in);
|
||
|
||
$('#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);
|
||
$('#media_display').val(power_user.media_display);
|
||
$('#image_overswipe').val(power_user.image_overswipe);
|
||
|
||
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);
|
||
}
|
||
|
||
export 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
|
||
export 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() {
|
||
/**
|
||
* Auto-fix missing fields in the story string
|
||
* @param {ContextSettings} contextSettings Context settings instance
|
||
*/
|
||
function autoFixStoryString(contextSettings) {
|
||
// Already migrated, no need to fix
|
||
if (!contextSettings || Object.hasOwn(contextSettings, 'story_string_position')) {
|
||
return;
|
||
}
|
||
|
||
let storyString = contextSettings.story_string || '';
|
||
|
||
/**
|
||
* @param {string} field Missing field name
|
||
* @param {'start'|'end'} position Position of auto-fix
|
||
*/
|
||
function autoFixMissingField(field, position) {
|
||
if (storyString.includes(`{{${field}}}`)) {
|
||
return;
|
||
}
|
||
|
||
console.warn(`[Story String Validation] Story String is missing a field: ${field}. Adding it at the ${position}.`);
|
||
const fieldTemplate = `{{#if ${field}}}{{${field}}}\n{{/if}}`;
|
||
const firstCurlyPosition = storyString.includes('{{') ? storyString.indexOf('{{') : 0;
|
||
const lastCurlyPosition = storyString.includes('}}') ? storyString.lastIndexOf('}}') + '}}'.length : storyString.length;
|
||
const lastTrimPosition = storyString.includes('{{trim}}') ? storyString.lastIndexOf('{{trim}}') : storyString.length;
|
||
const endPosition = Math.min(lastTrimPosition, lastCurlyPosition);
|
||
storyString = position === 'start'
|
||
? storyString.substring(0, firstCurlyPosition) + fieldTemplate + storyString.substring(firstCurlyPosition)
|
||
: storyString.substring(0, endPosition) + fieldTemplate + storyString.substring(endPosition);
|
||
}
|
||
|
||
autoFixMissingField('anchorBefore', 'start');
|
||
autoFixMissingField('anchorAfter', 'end');
|
||
|
||
contextSettings.story_string = storyString;
|
||
}
|
||
|
||
// Migrate story string to add missing fields
|
||
autoFixStoryString(power_user.context);
|
||
|
||
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 () {
|
||
let value = control.isCheckbox ? !!$(this).prop('checked') : $(this).val();
|
||
if (typeof control.defaultValue === 'number') {
|
||
value = Number(value);
|
||
}
|
||
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();
|
||
});
|
||
|
||
if (control.trigger) {
|
||
$element.trigger('input');
|
||
}
|
||
});
|
||
|
||
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;
|
||
}
|
||
|
||
// Migrate story string to add missing fields
|
||
autoFixStoryString(preset);
|
||
|
||
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;
|
||
}
|
||
}
|
||
}
|
||
|
||
updateBindModelTemplatesState();
|
||
|
||
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.
|
||
* @param {ContextSettings} [options.customContextSettings] Custom context settings.
|
||
* @returns {string} The rendered story string.
|
||
*/
|
||
export function renderStoryString(params, { customStoryString = null, customInstructSettings = null, customContextSettings = null } = {}) {
|
||
try {
|
||
const instructSettings = structuredClone(customInstructSettings ?? power_user.instruct);
|
||
const contextSettings = structuredClone(customContextSettings ?? power_user.context);
|
||
const storyString = customStoryString ?? contextSettings.story_string;
|
||
const storyStringPosition = contextSettings.story_string_position ?? extension_prompt_types.IN_PROMPT;
|
||
|
||
// 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') && storyStringPosition !== extension_prompt_types.IN_CHAT) {
|
||
if (!instructSettings.enabled || (instructSettings.wrap && !instructSettings.story_string_suffix)) {
|
||
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 = await getSanitizedFilename(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}"]`).prop('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,
|
||
media_display: power_user.media_display,
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 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 = await getSanitizedFilename(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}"]`).prop('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 mesToCut = $('#chat').find(`.mes[mesid=${mesIDToCut}]`);
|
||
|
||
if (!mesToCut.length) {
|
||
mesToCut = await loadUntilMesId(mesIDToCut);
|
||
|
||
if (!mesToCut || !mesToCut.length) {
|
||
return;
|
||
}
|
||
}
|
||
|
||
setEditedMessageId(mesIDToCut);
|
||
await deleteMessage(mesIDToCut, null, false);
|
||
}
|
||
|
||
await saveChatConditional();
|
||
|
||
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');
|
||
}
|
||
|
||
jQuery(() => {
|
||
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 = parseFloat(Number(window.devicePixelRatio).toFixed(2)) || 1;
|
||
const winWidth = window.innerWidth;
|
||
const winHeight = window.innerHeight;
|
||
const originalWidth = winWidth * zoomLevel;
|
||
const originalHeight = winHeight * zoomLevel;
|
||
console.debug(`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 = parseFloat(Number(window.innerHeight / coreTruthWinHeight).toFixed(4));
|
||
const scaleX = parseFloat(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').on('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').on('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);
|
||
});
|
||
|
||
$('#instruct_derived').on('input', function () {
|
||
const value = !!$(this).prop('checked');
|
||
power_user.instruct_derived = value;
|
||
saveSettingsDebounced();
|
||
});
|
||
|
||
$('#instruct_derived').on('change', function () {
|
||
$('#instruct_derived').parent().find('i').toggleClass('toggleEnabled', !!power_user.instruct_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);
|
||
});
|
||
|
||
$('#context_story_string_position').on('input', function () {
|
||
const value = Number($(this).val());
|
||
$('#context_story_string_inject_settings').toggle(value === extension_prompt_types.IN_CHAT);
|
||
});
|
||
|
||
$('#bind_model_templates').on('input', function () {
|
||
if (bindModelTemplates(power_user, online_status)) {
|
||
saveSettingsDebounced();
|
||
}
|
||
});
|
||
|
||
$('#bind_model_templates').on('change', updateBindModelTemplatesState);
|
||
|
||
$('#always-force-name2-checkbox').on('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').on('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').on('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').on('change', function () {
|
||
power_user.movingUI = $(this).prop('checked');
|
||
switchMovingUI();
|
||
saveSettingsDebounced();
|
||
});
|
||
|
||
$('#noShadowsmode').on('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($(this).val());
|
||
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_no_think').on('input', function () {
|
||
power_user.smooth_streaming_no_think = !!$(this).prop('checked');
|
||
saveSettingsDebounced();
|
||
});
|
||
|
||
$('#smooth_streaming_speed').on('input', function () {
|
||
power_user.smooth_streaming_speed = Number($('#smooth_streaming_speed').val());
|
||
saveSettingsDebounced();
|
||
});
|
||
|
||
$('#stream_fade_in').on('input', function () {
|
||
power_user.stream_fade_in = !!$(this).prop('checked');
|
||
saveSettingsDebounced();
|
||
});
|
||
|
||
$('input[name="font_scale"]').on('input', async function (e, data) {
|
||
const applyMode = data?.forced ? 'forced' : 'normal';
|
||
power_user.font_scale = Number($(this).val());
|
||
$('#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($(this).val());
|
||
$('#blur_strength_counter').val(power_user.blur_strength);
|
||
applyBlurStrength();
|
||
saveSettingsDebounced();
|
||
});
|
||
|
||
$('input[name="shadow_width"]').on('input', async function (e) {
|
||
power_user.shadow_width = Number($(this).val());
|
||
$('#shadow_width_counter').val(power_user.shadow_width);
|
||
applyShadowWidth();
|
||
saveSettingsDebounced();
|
||
});
|
||
|
||
$('#main-text-color-picker').on('change', (/** @type {ColorPickerEvent} */ evt) => {
|
||
power_user.main_text_color = evt.detail.rgba;
|
||
applyThemeColor('main');
|
||
saveSettingsDebounced();
|
||
});
|
||
|
||
$('#italics-color-picker').on('change', (/** @type {ColorPickerEvent} */ evt) => {
|
||
power_user.italics_text_color = evt.detail.rgba;
|
||
applyThemeColor('italics');
|
||
saveSettingsDebounced();
|
||
});
|
||
|
||
$('#underline-color-picker').on('change', (/** @type {ColorPickerEvent} */ evt) => {
|
||
power_user.underline_text_color = evt.detail.rgba;
|
||
applyThemeColor('underline');
|
||
saveSettingsDebounced();
|
||
});
|
||
|
||
$('#quote-color-picker').on('change', (/** @type {ColorPickerEvent} */ evt) => {
|
||
power_user.quote_text_color = evt.detail.rgba;
|
||
applyThemeColor('quote');
|
||
saveSettingsDebounced();
|
||
});
|
||
|
||
$('#blur-tint-color-picker').on('change', (/** @type {ColorPickerEvent} */ evt) => {
|
||
power_user.blur_tint_color = evt.detail.rgba;
|
||
applyThemeColor('blurTint');
|
||
saveSettingsDebounced();
|
||
});
|
||
|
||
$('#chat-tint-color-picker').on('change', (/** @type {ColorPickerEvent} */ evt) => {
|
||
power_user.chat_tint_color = evt.detail.rgba;
|
||
applyThemeColor('chatTint');
|
||
saveSettingsDebounced();
|
||
});
|
||
|
||
$('#user-mes-blur-tint-color-picker').on('change', (/** @type {ColorPickerEvent} */ evt) => {
|
||
power_user.user_mes_blur_tint_color = evt.detail.rgba;
|
||
applyThemeColor('userMesBlurTint');
|
||
saveSettingsDebounced();
|
||
});
|
||
|
||
$('#bot-mes-blur-tint-color-picker').on('change', (/** @type {ColorPickerEvent} */ evt) => {
|
||
power_user.bot_mes_blur_tint_color = evt.detail.rgba;
|
||
applyThemeColor('botMesBlurTint');
|
||
saveSettingsDebounced();
|
||
});
|
||
|
||
$('#shadow-color-picker').on('change', (/** @type {ColorPickerEvent} */ evt) => {
|
||
power_user.shadow_color = evt.detail.rgba;
|
||
applyThemeColor('shadow');
|
||
saveSettingsDebounced();
|
||
});
|
||
|
||
$('#border-color-picker').on('change', (/** @type {ColorPickerEvent} */ 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 (e) {
|
||
e.stopPropagation();
|
||
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').on('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();
|
||
});
|
||
|
||
$('#experimental_macro_engine').on('input', function () {
|
||
power_user.experimental_macro_engine = !!$(this).prop('checked');
|
||
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();
|
||
});
|
||
|
||
$('#media_display').on('input', async function () {
|
||
power_user.media_display = $(this).val().toString();
|
||
saveSettingsDebounced();
|
||
if (isMediaDisplayReloadNeeded()) {
|
||
await reloadCurrentChat();
|
||
}
|
||
});
|
||
|
||
$('#image_overswipe').on('input', function () {
|
||
power_user.image_overswipe = $(this).val().toString();
|
||
saveSettingsDebounced();
|
||
});
|
||
|
||
$(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;
|
||
},
|
||
}));
|
||
});
|