Skip TTS for voices explicitly set to disabled (fixes #4970) (#5367) * Skip TTS for voices explicitly set to disabled (fixes #4970) * Always show disabled message in commands and fix restoring voice map UI * Always show a message on manual TTS trigger * Fix null current job on disabled * Adjust type annotation * Force update worker when disabled play is attempted * Treat audio control queue as manual * Update TTS message processing to include manual flag * Don't show toast if was already shown by manual playback --------- Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>

128888f605381d1d4b3c999fb39ca52f65932f00

Tony Gies <tgies@tgies.net>

Signed
1 files changed, +37 -11Ignore whitespace
public/scripts/extensions/tts/index.js+37 -11
@@ -1,6 +1,7 @@
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, regexFromString } from '../../utils.js';3import { delay, escapeRegex, getBase64Async, getStringHash, onlyUnique, regexFromString } from '../../utils.js';
4import { accountStorage } from '../../util/AccountStorage.js';
4import { EdgeTtsProvider } from './edge.js';5import { EdgeTtsProvider } from './edge.js';
5import { ElevenLabsTtsProvider } from './elevenlabs.js';6import { ElevenLabsTtsProvider } from './elevenlabs.js';
6import { SileroTtsProvider } from './silerotts.js';7import { SileroTtsProvider } from './silerotts.js';
@@ -165,7 +166,7 @@ async function onNarrateOneMessage() {
165 }166 }
166167
167 resetTtsPlayback();168 resetTtsPlayback();
168 processAndQueueTtsMessage(message, Number(id));169 processAndQueueTtsMessage(message, Number(id), { manual: true });
169 moduleWorker();170 moduleWorker();
170}171}
171172
@@ -186,13 +187,20 @@ async function onNarrateText(args, text) {
186 ? voiceMap[DEFAULT_VOICE_MARKER]187 ? voiceMap[DEFAULT_VOICE_MARKER]
187 : voiceMap[name];188 : voiceMap[name];
188189
189 if (!voiceMapEntry || voiceMapEntry === DISABLED_VOICE_MARKER) {190 if (voiceMapEntry === DISABLED_VOICE_MARKER) {
191 toastr.info(`TTS voice for ${name} is disabled.`);
192 await initVoiceMap(false);
193 return;
194 }
195
196 if (!voiceMapEntry) {
190 toastr.info(`Specified voice for ${name} was not found. Check the TTS extension settings.`);197 toastr.info(`Specified voice for ${name} was not found. Check the TTS extension settings.`);
198 await initVoiceMap(false);
191 return;199 return;
192 }200 }
193201
194 resetTtsPlayback();202 resetTtsPlayback();
195 processAndQueueTtsMessage({ mes: text, name: name });203 processAndQueueTtsMessage({ mes: text, name: name }, null, { manual: true });
196 await moduleWorker();204 await moduleWorker();
197205
198 // Return back to the chat voices206 // Return back to the chat voices
@@ -245,7 +253,7 @@ function isTtsProcessing() {
245}253}
246254
247/**255/**
248 * @typedef {ChatMessage & { id?: number }} TtsMessage256 * @typedef {ChatMessage & { id?: number, manual?: boolean, segmentText?: string, segmentType?: string }} TtsMessage
249 */257 */
250258
251/**259/**
@@ -253,12 +261,15 @@ function isTtsProcessing() {
253 * (if enabled) and adds each part to the TTS job queue.261 * (if enabled) and adds each part to the TTS job queue.
254 * @param {ChatMessage} message - The message object to be processed.262 * @param {ChatMessage} message - The message object to be processed.
255 * @param {number|null} [messageId=null] - The chat message index to associate with TTS events.263 * @param {number|null} [messageId=null] - The chat message index to associate with TTS events.
264 * @param {object} [options={}] - Additional options for processing.
265 * @param {boolean} [options.manual=false] - Whether this TTS job was manually triggered (e.g., from the UI) rather than automatically from a new chat message.
256 * @returns {void}266 * @returns {void}
257 */267 */
258function processAndQueueTtsMessage(message, messageId = null) {268function processAndQueueTtsMessage(message, messageId = null, { manual = false } = {}) {
259 /** @type {TtsMessage} */269 /** @type {TtsMessage} */
260 const clone = structuredClone(message);270 const clone = structuredClone(message);
261 clone.id = messageId ?? null;271 clone.id = messageId ?? null;
272 clone.manual = manual ?? false;
262273
263 if (!extension_settings.tts.narrate_by_paragraphs) {274 if (!extension_settings.tts.narrate_by_paragraphs) {
264 ttsJobQueue.push(clone);275 ttsJobQueue.push(clone);
@@ -408,9 +419,10 @@ function onAudioControlClicked() {
408 // Not pausing, doing a full stop to anything TTS is doing. Better UX as pause is not as useful419 // Not pausing, doing a full stop to anything TTS is doing. Better UX as pause is not as useful
409 if (!audioElement.paused || isTtsProcessing()) {420 if (!audioElement.paused || isTtsProcessing()) {
410 resetTtsPlayback();421 resetTtsPlayback();
411 } else {422 } else if (context?.chat?.length > 0) {
412 // Default play behavior if not processing or playing is to play the last message.423 // Default play behavior if not processing or playing is to play the last message.
413 processAndQueueTtsMessage(context.chat[context.chat.length - 1]);424 const id = context.chat.length - 1;
425 processAndQueueTtsMessage(context.chat[id], id, { manual: true });
414 }426 }
415 updateUiAudioPlayState();427 updateUiAudioPlayState();
416}428}
@@ -481,8 +493,10 @@ async function processAudioJobQueue() {
481// TTS Control //493// TTS Control //
482//################//494//################//
483495
496/** @type {TtsMessage[]} */
484const ttsJobQueue = [];497const ttsJobQueue = [];
485let currentTtsJob; // Null if nothing is currently being processed498/** @type {TtsMessage|null} */
499let currentTtsJob = null; // Null if nothing is currently being processed
486500
487function completeTtsJob() {501function completeTtsJob() {
488 console.info(`Current TTS job for ${currentTtsJob?.name} completed.`);502 console.info(`Current TTS job for ${currentTtsJob?.name} completed.`);
@@ -621,7 +635,18 @@ async function processTtsQueue() {
621635
622 const voiceMapEntry = voiceMap[voiceMapKey] === DEFAULT_VOICE_MARKER ? voiceMap[DEFAULT_VOICE_MARKER] : voiceMap[voiceMapKey];636 const voiceMapEntry = voiceMap[voiceMapKey] === DEFAULT_VOICE_MARKER ? voiceMap[DEFAULT_VOICE_MARKER] : voiceMap[voiceMapKey];
623637
624 if (!voiceMapEntry || voiceMapEntry === DISABLED_VOICE_MARKER) {638 if (voiceMapEntry === DISABLED_VOICE_MARKER) {
639 const storageKey = `tts_disabled_warned_${char}`;
640 if (!accountStorage.getItem(storageKey) || currentTtsJob.manual) {
641 accountStorage.setItem(storageKey, 'true');
642 toastr.info(`TTS voice for ${char} is disabled.`);
643 }
644 currentTtsJob = null;
645 setTimeout(() => wrapper.update(), 0);
646 return;
647 }
648
649 if (!voiceMapEntry) {
625 throw `${char} not in voicemap. Configure character in extension settings voice map`;650 throw `${char} not in voicemap. Configure character in extension settings voice map`;
626 }651 }
627652
@@ -724,6 +749,7 @@ async function processTtsQueue() {
724 mes: currentTtsJob.mes,749 mes: currentTtsJob.mes,
725 extra: currentTtsJob.extra,750 extra: currentTtsJob.extra,
726 id: currentTtsJob.id,751 id: currentTtsJob.id,
752 manual: currentTtsJob.manual,
727 };753 };
728 ttsJobQueue.unshift(segmentJob);754 ttsJobQueue.unshift(segmentJob);
729 }755 }
@@ -824,7 +850,7 @@ async function playFullConversation() {
824850
825 context.chat.forEach((msg, i) => {851 context.chat.forEach((msg, i) => {
826 if (!msg.is_system && msg.mes !== '...' && msg.mes !== '') {852 if (!msg.is_system && msg.mes !== '...' && msg.mes !== '') {
827 processAndQueueTtsMessage(msg, i);853 processAndQueueTtsMessage(msg, i, { manual: false });
828 }854 }
829 });855 });
830856
@@ -1164,7 +1190,7 @@ async function onMessageEvent(messageId, lastCharIndex) {
1164 message.id = messageId;1190 message.id = messageId;
1165 ttsJobQueue.push(message);1191 ttsJobQueue.push(message);
1166 } else {1192 } else {
1167 processAndQueueTtsMessage(message, messageId);1193 processAndQueueTtsMessage(message, messageId, { manual: false });
1168 }1194 }
1169}1195}
11701196