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>

ef89d0f291a788ba1529cb009bdc988c452ec5d8

ZhenyaPav <61346893+ZhenyaPav@users.noreply.github.com>

Signed
2 files changed, +60 -1Ignore whitespace
public/scripts/extensions/tts/index.js+49 -1
@@ -1,6 +1,6 @@
1import { cancelTtsPlay, eventSource, event_types, getCurrentChatId, isStreamingEnabled, name2, saveSettingsDebounced, substituteParams } from '../../../script.js';1import { cancelTtsPlay, eventSource, event_types, getCurrentChatId, isStreamingEnabled, name2, saveSettingsDebounced, substituteParams } from '../../../script.js';
2import { ModuleWorkerWrapper, extension_settings, getContext, renderExtensionTemplateAsync } from '../../extensions.js';2import { ModuleWorkerWrapper, extension_settings, getContext, renderExtensionTemplateAsync } from '../../extensions.js';
3import { delay, escapeRegex, getBase64Async, getStringHash, onlyUnique } from '../../utils.js';3import { delay, escapeRegex, getBase64Async, getStringHash, onlyUnique, regexFromString } from '../../utils.js';
4import { EdgeTtsProvider } from './edge.js';4import { EdgeTtsProvider } from './edge.js';
5import { ElevenLabsTtsProvider } from './elevenlabs.js';5import { ElevenLabsTtsProvider } from './elevenlabs.js';
6import { SileroTtsProvider } from './silerotts.js';6import { SileroTtsProvider } from './silerotts.js';
@@ -640,6 +640,16 @@ async function processTtsQueue() {
640 : text.replaceAll('*', '').trim(); // remove just the asterisks640 : text.replaceAll('*', '').trim(); // remove just the asterisks
641 }641 }
642642
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};
846862
847function setTtsStatus(status, success) {863function setTtsStatus(status, success) {
@@ -942,6 +958,36 @@ function onMultiVoiceClick() {
942 initVoiceMap();958 initVoiceMap();
943}959}
944960
961function 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
968function onRegexPatternChange() {
969 extension_settings.tts.regex_pattern = $('#tts_regex_pattern').val().toString();
970 saveSettingsDebounced();
971 updateRegexPatternWarning();
972}
973
974function 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);
14541502
1455 $('#playback_rate').on('input', function () {1503 $('#playback_rate').on('input', function () {
1456 const value = $(this).val();1504 const value = $(this).val();
public/scripts/extensions/tts/settings.html+11 -0
@@ -67,6 +67,17 @@
67 Different voices for "quotes", *text inside asterisks* and other text67 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.,!?;:'&quot;()]+/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>