mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
TTS: add slash command to speak
This commit is contained in:
@ -6697,6 +6697,7 @@ window["SillyTavern"].getContext = function () {
|
||||
deactivateSendButtons,
|
||||
saveReply,
|
||||
registerSlashCommand: registerSlashCommand,
|
||||
executeSlashCommands: executeSlashCommands,
|
||||
registerHelper: registerExtensionHelper,
|
||||
registedDebugFunction: registerDebugFunction,
|
||||
renderExtensionTemplate: renderExtensionTemplate,
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { callPopup, cancelTtsPlay, eventSource, event_types, saveSettingsDebounced } from '../../../script.js'
|
||||
import { callPopup, cancelTtsPlay, eventSource, event_types, name2, saveSettingsDebounced } from '../../../script.js'
|
||||
import { ModuleWorkerWrapper, doExtrasFetch, extension_settings, getApiUrl, getContext, modules } from '../../extensions.js'
|
||||
import { escapeRegex, getStringHash } from '../../utils.js'
|
||||
import { EdgeTtsProvider } from './edge.js'
|
||||
@ -8,6 +8,7 @@ import { CoquiTtsProvider } from './coqui.js'
|
||||
import { SystemTtsProvider } from './system.js'
|
||||
import { NovelTtsProvider } from './novel.js'
|
||||
import { power_user } from '../../power-user.js'
|
||||
import { registerSlashCommand } from '../../slash-commands.js'
|
||||
export { talkingAnimation };
|
||||
|
||||
const UPDATE_INTERVAL = 1000
|
||||
@ -91,6 +92,36 @@ async function onNarrateOneMessage() {
|
||||
moduleWorker();
|
||||
}
|
||||
|
||||
async function onNarrateText(args, text) {
|
||||
if (!text) {
|
||||
return;
|
||||
}
|
||||
|
||||
audioElement.src = '/sounds/silence.mp3';
|
||||
|
||||
// To load all characters in the voice map, set unrestricted to true
|
||||
await initVoiceMap(true);
|
||||
|
||||
const baseName = args?.voice || name2;
|
||||
const name = (baseName === 'SillyTavern System' ? DEFAULT_VOICE_MARKER : baseName) || DEFAULT_VOICE_MARKER;
|
||||
|
||||
const voiceMapEntry = voiceMap[name] === DEFAULT_VOICE_MARKER
|
||||
? voiceMap[DEFAULT_VOICE_MARKER]
|
||||
: voiceMap[name];
|
||||
|
||||
if (!voiceMapEntry || voiceMapEntry === DISABLED_VOICE_MARKER) {
|
||||
toastr.info(`Specified voice for ${name} was not found. Check the TTS extension settings.`);
|
||||
return;
|
||||
}
|
||||
|
||||
resetTtsPlayback()
|
||||
ttsJobQueue.push({ mes: text, name: name });
|
||||
await moduleWorker();
|
||||
|
||||
// Return back to the chat voices
|
||||
await initVoiceMap(false);
|
||||
}
|
||||
|
||||
async function moduleWorker() {
|
||||
// Primarily determining when to add new chat to the TTS queue
|
||||
const enabled = $('#tts_enabled').is(':checked')
|
||||
@ -636,8 +667,20 @@ async function onChatChanged() {
|
||||
await initVoiceMap()
|
||||
}
|
||||
|
||||
function getCharacters(){
|
||||
/**
|
||||
* Get characters in current chat
|
||||
* @param {boolean} unrestricted - If true, will include all characters in voiceMapEntries, even if they are not in the current chat.
|
||||
* @returns {string[]} - Array of character names
|
||||
*/
|
||||
function getCharacters(unrestricted) {
|
||||
const context = getContext()
|
||||
|
||||
if (unrestricted) {
|
||||
const names = context.characters.map(char => char.name);
|
||||
names.unshift(DEFAULT_VOICE_MARKER);
|
||||
return names;
|
||||
}
|
||||
|
||||
let characters = []
|
||||
if (context.groupId === null) {
|
||||
// Single char chat
|
||||
@ -754,9 +797,9 @@ class VoiceMapEntry {
|
||||
|
||||
/**
|
||||
* Init voiceMapEntries for character select list.
|
||||
*
|
||||
* @param {boolean} unrestricted - If true, will include all characters in voiceMapEntries, even if they are not in the current chat.
|
||||
*/
|
||||
export async function initVoiceMap(){
|
||||
export async function initVoiceMap(unrestricted = false) {
|
||||
// Gate initialization if not enabled or TTS Provider not ready. Prevents error popups.
|
||||
const enabled = $('#tts_enabled').is(':checked')
|
||||
if (!enabled) {
|
||||
@ -779,7 +822,7 @@ export async function initVoiceMap(){
|
||||
voiceMapEntries = []
|
||||
|
||||
// Get characters in current chat
|
||||
const characters = getCharacters()
|
||||
const characters = getCharacters(unrestricted);
|
||||
|
||||
// Get saved voicemap from provider settings, handling new and old representations
|
||||
let voiceMapFromSettings = {}
|
||||
@ -904,4 +947,5 @@ $(document).ready(function () {
|
||||
eventSource.on(event_types.MESSAGE_SWIPED, resetTtsPlayback);
|
||||
eventSource.on(event_types.CHAT_CHANGED, onChatChanged)
|
||||
eventSource.on(event_types.GROUP_UPDATED, onChatChanged)
|
||||
registerSlashCommand('speak', onNarrateText, ['narrate', 'tts'], `<span class="monospace">(text)</span> – narrate any text using currently selected character's voice. Use voice="Character Name" argument to set other voice from the voice map, example: <tt>/speak voice="Donald Duck" Quack!</tt>`, true, true);
|
||||
})
|
||||
|
@ -171,10 +171,16 @@ class SystemTtsProvider {
|
||||
return [];
|
||||
}
|
||||
|
||||
return speechSynthesis
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
const voices = speechSynthesis
|
||||
.getVoices()
|
||||
.sort((a, b) => a.lang.localeCompare(b.lang) || a.name.localeCompare(b.name))
|
||||
.map(x => ({ name: x.name, voice_id: x.voiceURI, preview_url: false, lang: x.lang }));
|
||||
|
||||
resolve(voices);
|
||||
}, 1);
|
||||
});
|
||||
}
|
||||
|
||||
previewTtsVoice(voiceId) {
|
||||
|
@ -82,7 +82,8 @@ class SlashCommandParser {
|
||||
if (equalsIndex !== -1) {
|
||||
const key = arg.substring(0, equalsIndex);
|
||||
const value = arg.substring(equalsIndex + 1);
|
||||
argObj[key] = value;
|
||||
// Replace "wrapping quotes" used for escaping spaces
|
||||
argObj[key] = value.replace(/(^")|("$)/g, '');
|
||||
}
|
||||
else {
|
||||
break;
|
||||
|
Reference in New Issue
Block a user