Added regex filter option to TTS extension (#4924) * Add regex filter option to TTS extension - Added checkbox to enable/disable regex filtering of TTS text - Added text input for custom regex patterns - Default pattern removes emojis and non-English text while preserving letters, numbers, spaces, and punctuation - Regex is applied after asterisk removal and other text processing - Includes error handling for invalid regex patterns - Automatically cleans up extra spaces after regex removal * reverted the accidental change in tts_skip_tags description clarified the regex description * Add regex validation warning to TTS settings * fixed regex field in UI * reworked the regex warning * changed the regex event to change (input might not have worked correctly) * Apply review comments * Spacing fix --------- Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>
Signed| @@ -1,6 +1,6 @@ | |||
| 1 | import { cancelTtsPlay, eventSource, event_types, getCurrentChatId, isStreamingEnabled, name2, saveSettingsDebounced, substituteParams } from '../../../script.js'; | 1 | import { cancelTtsPlay, eventSource, event_types, getCurrentChatId, isStreamingEnabled, name2, saveSettingsDebounced, substituteParams } from '../../../script.js'; |
| 2 | import { ModuleWorkerWrapper, extension_settings, getContext, renderExtensionTemplateAsync } from '../../extensions.js'; | 2 | import { ModuleWorkerWrapper, extension_settings, getContext, renderExtensionTemplateAsync } from '../../extensions.js'; |
| 3 | import { delay, escapeRegex, getBase64Async, getStringHash, onlyUnique } from '../../utils.js'; | 3 | import { delay, escapeRegex, getBase64Async, getStringHash, onlyUnique, regexFromString } from '../../utils.js'; |
| 4 | import { EdgeTtsProvider } from './edge.js'; | 4 | import { EdgeTtsProvider } from './edge.js'; |
| 5 | import { ElevenLabsTtsProvider } from './elevenlabs.js'; | 5 | import { ElevenLabsTtsProvider } from './elevenlabs.js'; |
| 6 | import { SileroTtsProvider } from './silerotts.js'; | 6 | import { SileroTtsProvider } from './silerotts.js'; |
| @@ -640,6 +640,16 @@ async function processTtsQueue() { | |||
| 640 | : text.replaceAll('*', '').trim(); // remove just the asterisks | 640 | : text.replaceAll('*', '').trim(); // remove just the asterisks |
| 641 | } | 641 | } |
| 642 | 642 | ||
| 643 | if (extension_settings.tts.apply_regex && extension_settings.tts.regex_pattern) { | ||
| 644 | const regex = regexFromString(extension_settings.tts.regex_pattern); | ||
| 645 | if (regex) { | ||
| 646 | // Clean up extra spaces that might be left after removal | ||
| 647 | text = text.replace(regex, '').replace(/\s+/g, ' ').trim(); | ||
| 648 | } else { | ||
| 649 | console.warn('Invalid regex pattern:', extension_settings.tts.regex_pattern); | ||
| 650 | } | ||
| 651 | } | ||
| 652 | |||
| 643 | if (extension_settings.tts.narrate_quoted_only) { | 653 | if (extension_settings.tts.narrate_quoted_only) { |
| 644 | const partJoiner = (ttsProvider?.separator || ' ... '); | 654 | const partJoiner = (ttsProvider?.separator || ' ... '); |
| 645 | text = joinQuotedBlocks(text, { separator: partJoiner, includeQuotes: true }); | 655 | text = joinQuotedBlocks(text, { separator: partJoiner, includeQuotes: true }); |
| @@ -827,6 +837,10 @@ function loadSettings() { | |||
| 827 | $('#tts_skip_codeblocks').prop('checked', extension_settings.tts.skip_codeblocks); | 837 | $('#tts_skip_codeblocks').prop('checked', extension_settings.tts.skip_codeblocks); |
| 828 | $('#tts_skip_tags').prop('checked', extension_settings.tts.skip_tags); | 838 | $('#tts_skip_tags').prop('checked', extension_settings.tts.skip_tags); |
| 829 | $('#tts_multi_voice_enabled').prop('checked', extension_settings.tts.multi_voice_enabled); | 839 | $('#tts_multi_voice_enabled').prop('checked', extension_settings.tts.multi_voice_enabled); |
| 840 | $('#tts_apply_regex').prop('checked', extension_settings.tts.apply_regex); | ||
| 841 | $('#tts_regex_pattern').val(extension_settings.tts.regex_pattern); | ||
| 842 | $('#tts_regex_block').toggle(extension_settings.tts.apply_regex); | ||
| 843 | updateRegexPatternWarning(); | ||
| 830 | $('#playback_rate').val(extension_settings.tts.playback_rate); | 844 | $('#playback_rate').val(extension_settings.tts.playback_rate); |
| 831 | $('#playback_rate_counter').val(Number(extension_settings.tts.playback_rate).toFixed(2)); | 845 | $('#playback_rate_counter').val(Number(extension_settings.tts.playback_rate).toFixed(2)); |
| 832 | $('#playback_rate_block').toggle(extension_settings.tts.currentProvider !== 'System'); | 846 | $('#playback_rate_block').toggle(extension_settings.tts.currentProvider !== 'System'); |
| @@ -842,6 +856,8 @@ const defaultSettings = { | |||
| 842 | narrate_user: false, | 856 | narrate_user: false, |
| 843 | playback_rate: 1, | 857 | playback_rate: 1, |
| 844 | multi_voice_enabled: false, | 858 | multi_voice_enabled: false, |
| 859 | apply_regex: false, | ||
| 860 | regex_pattern: '', | ||
| 845 | }; | 861 | }; |
| 846 | 862 | ||
| 847 | function setTtsStatus(status, success) { | 863 | function setTtsStatus(status, success) { |
| @@ -942,6 +958,36 @@ function onMultiVoiceClick() { | |||
| 942 | initVoiceMap(); | 958 | initVoiceMap(); |
| 943 | } | 959 | } |
| 944 | 960 | ||
| 961 | function onApplyRegexChange() { | ||
| 962 | extension_settings.tts.apply_regex = !!$('#tts_apply_regex').prop('checked'); | ||
| 963 | saveSettingsDebounced(); | ||
| 964 | $('#tts_regex_block').toggle(extension_settings.tts.apply_regex); | ||
| 965 | updateRegexPatternWarning(); | ||
| 966 | } | ||
| 967 | |||
| 968 | function onRegexPatternChange() { | ||
| 969 | extension_settings.tts.regex_pattern = $('#tts_regex_pattern').val().toString(); | ||
| 970 | saveSettingsDebounced(); | ||
| 971 | updateRegexPatternWarning(); | ||
| 972 | } | ||
| 973 | |||
| 974 | function updateRegexPatternWarning() { | ||
| 975 | const warning = $('#tts_regex_warning'); | ||
| 976 | if (!extension_settings.tts.apply_regex) { | ||
| 977 | warning.hide(); | ||
| 978 | return; | ||
| 979 | } | ||
| 980 | |||
| 981 | const pattern = extension_settings.tts.regex_pattern; | ||
| 982 | if (!pattern) { | ||
| 983 | warning.hide(); | ||
| 984 | return; | ||
| 985 | } | ||
| 986 | |||
| 987 | const regex = regexFromString(pattern); | ||
| 988 | warning.toggle(!regex); | ||
| 989 | } | ||
| 990 | |||
| 945 | //##############// | 991 | //##############// |
| 946 | // TTS Provider // | 992 | // TTS Provider // |
| 947 | //##############// | 993 | //##############// |
| @@ -1451,6 +1497,8 @@ jQuery(async function () { | |||
| 1451 | $('#tts_narrate_by_paragraphs').on('click', onNarrateByParagraphsClick); | 1497 | $('#tts_narrate_by_paragraphs').on('click', onNarrateByParagraphsClick); |
| 1452 | $('#tts_narrate_user').on('click', onNarrateUserClick); | 1498 | $('#tts_narrate_user').on('click', onNarrateUserClick); |
| 1453 | $('#tts_multi_voice_enabled').on('click', onMultiVoiceClick); | 1499 | $('#tts_multi_voice_enabled').on('click', onMultiVoiceClick); |
| 1500 | $('#tts_apply_regex').on('change', onApplyRegexChange); | ||
| 1501 | $('#tts_regex_pattern').on('input', onRegexPatternChange); | ||
| 1454 | 1502 | ||
| 1455 | $('#playback_rate').on('input', function () { | 1503 | $('#playback_rate').on('input', function () { |
| 1456 | const value = $(this).val(); | 1504 | const value = $(this).val(); |
| @@ -67,6 +67,17 @@ | |||
| 67 | Different voices for "quotes", *text inside asterisks* and other text | 67 | Different voices for "quotes", *text inside asterisks* and other text |
| 68 | </small> | 68 | </small> |
| 69 | </label> | 69 | </label> |
| 70 | <label class="checkbox_label" for="tts_apply_regex" title="Applies the regex pattern to filter text before TTS."> | ||
| 71 | <input type="checkbox" id="tts_apply_regex"> | ||
| 72 | <small data-i18n="Apply regex filter">Apply regex filter to text</small> | ||
| 73 | </label> | ||
| 74 | <div id="tts_regex_block" class="marginTop5"> | ||
| 75 | <div class="range-block-title justifyLeft"> | ||
| 76 | <small data-i18n="Regex Pattern">Regex Pattern (removes matching text)</small> | ||
| 77 | </div> | ||
| 78 | <input type="text" id="tts_regex_pattern" class="text_pole width100p" placeholder="Example: /[^a-zA-Z0-9\s.,!?;:'"()]+/g" /> | ||
| 79 | <small id="tts_regex_warning" class="warning" data-i18n="Invalid regex pattern.">Invalid regex pattern.</small> | ||
| 80 | </div> | ||
| 70 | </div> | 81 | </div> |
| 71 | <div id="playback_rate_block" class="range-block"> | 82 | <div id="playback_rate_block" class="range-block"> |
| 72 | <hr> | 83 | <hr> |