From 5d142f499a728684a56798307d64df31901193d6 Mon Sep 17 00:00:00 2001 From: ouoertheo Date: Sun, 23 Apr 2023 12:43:59 -0500 Subject: [PATCH 1/2] generalized tts settings --- public/scripts/extensions.js | 2 +- .../{elevenlabstts => tts}/elevenlabs.js | 102 +++++++++-- .../{elevenlabstts => tts}/index.js | 162 ++++++++++-------- .../{elevenlabstts => tts}/manifest.json | 0 .../{elevenlabstts => tts}/style.css | 0 5 files changed, 182 insertions(+), 84 deletions(-) rename public/scripts/extensions/{elevenlabstts => tts}/elevenlabs.js (56%) rename public/scripts/extensions/{elevenlabstts => tts}/index.js (69%) rename public/scripts/extensions/{elevenlabstts => tts}/manifest.json (100%) rename public/scripts/extensions/{elevenlabstts => tts}/style.css (100%) diff --git a/public/scripts/extensions.js b/public/scripts/extensions.js index 8e6527ee0..0380f065f 100644 --- a/public/scripts/extensions.js +++ b/public/scripts/extensions.js @@ -24,7 +24,7 @@ const extension_settings = { caption: {}, expressions: {}, dice: {}, - elevenlabstts: {}, + tts: {}, }; let modules = []; diff --git a/public/scripts/extensions/elevenlabstts/elevenlabs.js b/public/scripts/extensions/tts/elevenlabs.js similarity index 56% rename from public/scripts/extensions/elevenlabstts/elevenlabs.js rename to public/scripts/extensions/tts/elevenlabs.js index e033de663..5f6b75c50 100644 --- a/public/scripts/extensions/elevenlabstts/elevenlabs.js +++ b/public/scripts/extensions/tts/elevenlabs.js @@ -1,13 +1,93 @@ export { ElevenLabsTtsProvider } class ElevenLabsTtsProvider { + //########// + // Config // + //########// + API_KEY + settings = this.defaultSettings + voices = [] + set API_KEY(apiKey) { this.API_KEY = apiKey } get API_KEY() { return this.API_KEY } + get settings() { + return this.settings + } + + updateSettings(settings) { + console.info("Settings updated") + if("stability" in settings && "similarity_boost" in settings){ + this.settings = settings + $('#elevenlabs_tts_stability').val(this.settings.stability) + $('#elevenlabs_tts_similarity_boost').val(this.settings.similarity_boost) + this.onSettingsChange() + } else { + throw `Invalid settings passed to ElevenLabs: ${JSON.stringify(settings)}` + } + } + + defaultSettings = { + stability: 0.75, + similarity_boost: 0.75 + } + + onSettingsChange() { + this.settings = { + stability: $('#elevenlabs_tts_stability').val(), + similarity_boost: $('#elevenlabs_tts_similarity_boost').val() + } + $('#elevenlabs_tts_stability_output').text(this.settings.stability) + $('#elevenlabs_tts_similarity_boost_output').text(this.settings.similarity_boost) + } + + get settingsHtml() { + let html = ` + + + + + ` + return html + } + + //#############// + // Management // + //#############// + + async getVoice(voiceName) { + if (this.voices.length == 0) { + this.voices = await this.fetchTtsVoiceIds() + } + const match = this.voices.filter( + elevenVoice => elevenVoice.name == voiceName + )[0] + if (!match) { + throw `TTS Voice name ${voiceName} not found in ElevenLabs account` + } + return match + } + + async findTtsGenerationInHistory(message, voiceId) { + const ttsHistory = await this.fetchTtsHistory() + for (const history of ttsHistory) { + const text = history.text + const itemId = history.history_item_id + if (message === text && history.voice_id == voiceId) { + console.info(`Existing TTS history item ${itemId} found: ${text} `) + return itemId + } + } + return '' + } + + //###########// + // API CALLS // + //###########// async fetchTtsVoiceIds() { const headers = { 'xi-api-key': this.API_KEY @@ -22,6 +102,10 @@ class ElevenLabsTtsProvider { return responseJson.voices } + /** + * + * @returns + */ async fetchTtsVoiceSettings() { const headers = { 'xi-api-key': this.API_KEY @@ -48,7 +132,10 @@ class ElevenLabsTtsProvider { 'xi-api-key': this.API_KEY, 'Content-Type': 'application/json' }, - body: JSON.stringify({ text: text }) + body: JSON.stringify({ + text: text, + voice_settings: this.settings + }) } ) if (!response.ok) { @@ -86,17 +173,4 @@ class ElevenLabsTtsProvider { const responseJson = await response.json() return responseJson.history } - - async findTtsGenerationInHistory(message, voiceId) { - const ttsHistory = await this.fetchTtsHistory() - for (const history of ttsHistory) { - const text = history.text - const itemId = history.history_item_id - if (message === text && history.voice_id == voiceId) { - console.info(`Existing TTS history item ${itemId} found: ${text} `) - return itemId - } - } - return '' - } } diff --git a/public/scripts/extensions/elevenlabstts/index.js b/public/scripts/extensions/tts/index.js similarity index 69% rename from public/scripts/extensions/elevenlabstts/index.js rename to public/scripts/extensions/tts/index.js index 1b0716ca3..479828d05 100644 --- a/public/scripts/extensions/elevenlabstts/index.js +++ b/public/scripts/extensions/tts/index.js @@ -6,7 +6,6 @@ import { ElevenLabsTtsProvider } from './elevenlabs.js' const UPDATE_INTERVAL = 1000 let voiceMap = {} // {charName:voiceid, charName2:voiceid2} -let elevenlabsTtsVoices = [] let audioControl let lastCharacterId = null @@ -14,11 +13,16 @@ let lastGroupId = null let lastChatId = null let lastMessageHash = null -let ttsProvider = new ElevenLabsTtsProvider() + +let ttsProviders = { + elevenLabs: ElevenLabsTtsProvider +} +let ttsProvider +let ttsProviderName async function moduleWorker() { // Primarily determinign when to add new chat to the TTS queue - const enabled = $('#elevenlabs_enabled').is(':checked') + const enabled = $('#tts_enabled').is(':checked') if (!enabled) { return } @@ -104,19 +108,19 @@ async function playAudioData(audioBlob) { }) } -window['elevenlabsPreview'] = function (id) { +window['tts_preview'] = function (id) { const audio = document.getElementById(id) audio.play() } -async function onElevenlabsVoicesClick() { +async function onTtsVoicesClick() { let popupText = '' try { const voiceIds = await ttsProvider.fetchTtsVoiceIds() for (const voice of voiceIds) { - popupText += `
${voice.name}
` + popupText += `
${voice.name}
` popupText += `` } } catch { @@ -213,7 +217,7 @@ async function processTtsQueue() { if (!voiceMap[char]) { throw `${char} not in voicemap. Configure character in extension settings voice map` } - const voice = await getTtsVoice(voiceMap[char]) + const voice = await ttsProvider.getVoice((voiceMap[char])) const voiceId = voice.voice_id if (voiceId == null) { throw `Unable to attain voiceId for ${char}` @@ -239,50 +243,53 @@ window.playFullConversation = playFullConversation function loadSettings() { const context = getContext() - if (Object.keys(extension_settings.elevenlabstts).length === 0) { - Object.assign(extension_settings.elevenlabstts, defaultSettings) + if (!ttsProviderName in extension_settings.tts){ + extension_settings.tts[ttsProviderName] = {} + } + if (Object.keys(extension_settings.tts[ttsProviderName]).length === 0) { + Object.assign(extension_settings.tts[ttsProviderName], defaultSettings) } - $('#elevenlabs_api_key').val( - extension_settings.elevenlabstts.elevenlabsApiKey + $('#tts_api_key').val( + extension_settings.tts[ttsProviderName].apiKey ) - $('#elevenlabs_voice_map').val( - extension_settings.elevenlabstts.elevenlabsVoiceMap + $('#tts_voice_map').val( + extension_settings.tts[ttsProviderName].voiceMap ) - $('#elevenlabs_enabled').prop( + $('#tts_enabled').prop( 'checked', - extension_settings.elevenlabstts.enabled + extension_settings.tts.enabled ) - onElevenlabsApplyClick() + ttsProvider.updateSettings(extension_settings.tts[ttsProviderName].settings) + onApplyClick() } const defaultSettings = { - elevenlabsApiKey: '', - elevenlabsVoiceMap: '', - elevenlabsEnabed: false + apiKey: '', + voiceMap: '', + ttsEnabled: false } -function setElevenLabsStatus(status, success) { - $('#elevenlabs_status').text(status) +function setTtsStatus(status, success) { + $('#tts_status').text(status) if (success) { - $('#elevenlabs_status').removeAttr('style') + $('#tts_status').removeAttr('style') } else { - $('#elevenlabs_status').css('color', 'red') + $('#tts_status').css('color', 'red') } } async function updateApiKey() { - const context = getContext() - const value = $('#elevenlabs_api_key').val() + const value = $('#tts_api_key').val() // Using this call to validate API key ttsProvider.API_KEY = String(value) await ttsProvider.fetchTtsVoiceIds().catch(error => { ttsProvider.API_KEY = null - throw `ElevenLabs TTS API key invalid` + throw `TTS API key invalid` }) - extension_settings.elevenlabstts.elevenlabsApiKey = String(value) + extension_settings.tts[ttsProviderName].apiKey = String(value) console.debug(`Saved new API_KEY: ${value}`) saveSettingsDebounced() } @@ -299,26 +306,12 @@ function parseVoiceMap(voiceMapString) { return parsedVoiceMap } -async function getTtsVoice(name) { - // We're caching the list of voice_ids. This might cause trouble if the user creates a new voice without restarting - if (elevenlabsTtsVoices.length == 0) { - elevenlabsTtsVoices = await ttsProvider.fetchTtsVoiceIds() - } - const match = elevenlabsTtsVoices.filter( - elevenVoice => elevenVoice.name == name - )[0] - if (!match) { - throw `TTS Voice name ${name} not found in ElevenLabs account` - } - return match -} - async function voicemapIsValid(parsedVoiceMap) { let valid = true for (const characterName in parsedVoiceMap) { const parsedVoiceName = parsedVoiceMap[characterName] try { - await getTtsVoice(parsedVoiceName) + await ttsProvider.getVoice(parsedVoiceName) } catch (error) { console.error(error) valid = false @@ -330,13 +323,13 @@ async function voicemapIsValid(parsedVoiceMap) { async function updateVoiceMap() { let isValidResult = false const context = getContext() - // console.debug("onElevenlabsVoiceMapSubmit"); - const value = $('#elevenlabs_voice_map').val() + // console.debug("onvoiceMapSubmit"); + const value = $('#tts_voice_map').val() const parsedVoiceMap = parseVoiceMap(value) isValidResult = await voicemapIsValid(parsedVoiceMap) if (isValidResult) { - extension_settings.elevenlabstts.elevenlabsVoiceMap = String(value) - context.elevenlabsVoiceMap = String(value) + extension_settings.tts[ttsProviderName].voiceMap = String(value) + context.voiceMap = String(value) voiceMap = parsedVoiceMap console.debug(`Saved new voiceMap: ${value}`) saveSettingsDebounced() @@ -345,19 +338,19 @@ async function updateVoiceMap() { } } -function onElevenlabsApplyClick() { +function onApplyClick() { Promise.all([updateApiKey(), updateVoiceMap()]) .then(([result1, result2]) => { updateUiAudioPlayState() - setElevenLabsStatus('Successfully applied settings', true) + setTtsStatus('Successfully applied settings', true) }) .catch(error => { - setElevenLabsStatus(error, false) + setTtsStatus(error, false) }) } -function onElevenlabsEnableClick() { - extension_settings.elevenlabstts.enabled = $('#elevenlabs_enabled').is( +function onEnableClick() { + extension_settings.tts.enabled = $('#tts_enabled').is( ':checked' ) updateUiAudioPlayState() @@ -365,7 +358,7 @@ function onElevenlabsEnableClick() { } function updateUiAudioPlayState() { - if (extension_settings.elevenlabstts.enabled == true) { + if (extension_settings.tts.enabled == true) { audioControl.style.display = 'flex' const img = !audioElement.paused ? 'fa-solid fa-circle-pause' @@ -388,44 +381,75 @@ function addAudioControl() { updateUiAudioPlayState() } +function addUiTtsProviderConfig() { + $('#tts_provider_settings').append(ttsProvider.settingsHtml) + ttsProvider.onSettingsChange() +} + +function loadTtsProvider(provider){ + // Set up provider references. No init dependencies + extension_settings.tts.currentProvider = provider + ttsProviderName = provider + ttsProvider = new ttsProviders[provider] + saveSettingsDebounced() +} + +function onTtsProviderSettingsInput(){ + ttsProvider.onSettingsChange() + extension_settings.tts[ttsProviderName].settings = ttsProvider.settings + saveSettingsDebounced() +} + $(document).ready(function () { function addExtensionControls() { const settingsHtml = ` -
+
- ElevenLabs TTS + TTS
- + - -
- - +
+ +
-
-
+
+
+ +
+
+ TTS Config +
+
+
+
` $('#extensions_settings').append(settingsHtml) - $('#elevenlabs_apply').on('click', onElevenlabsApplyClick) - $('#elevenlabs_enabled').on('click', onElevenlabsEnableClick) - $('#elevenlabs_voices').on('click', onElevenlabsVoicesClick) + $('#tts_apply').on('click', onApplyClick) + $('#tts_enabled').on('click', onEnableClick) + $('#tts_voices').on('click', onTtsVoicesClick) + $('#tts_provider_settings').on('input', onTtsProviderSettingsInput) } - addAudioControl() - addExtensionControls() - loadSettings() - setInterval(moduleWorker, UPDATE_INTERVAL) + loadTtsProvider("elevenLabs") // No init dependencies + addExtensionControls() // No init dependencies + addUiTtsProviderConfig() // Depends on ttsProvider being loaded + loadSettings() // Depends on Extension Controls and ttsProvider + addAudioControl() // Depends on Extension Controls + setInterval(moduleWorker, UPDATE_INTERVAL) // Init depends on all the things }) diff --git a/public/scripts/extensions/elevenlabstts/manifest.json b/public/scripts/extensions/tts/manifest.json similarity index 100% rename from public/scripts/extensions/elevenlabstts/manifest.json rename to public/scripts/extensions/tts/manifest.json diff --git a/public/scripts/extensions/elevenlabstts/style.css b/public/scripts/extensions/tts/style.css similarity index 100% rename from public/scripts/extensions/elevenlabstts/style.css rename to public/scripts/extensions/tts/style.css From cafa3d582c210eaa0ee89206b9e5037f7ca3abb1 Mon Sep 17 00:00:00 2001 From: ouoertheo Date: Sun, 23 Apr 2023 12:58:49 -0500 Subject: [PATCH 2/2] remove docstring --- public/scripts/extensions/tts/elevenlabs.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/public/scripts/extensions/tts/elevenlabs.js b/public/scripts/extensions/tts/elevenlabs.js index 5f6b75c50..f0d0c19ed 100644 --- a/public/scripts/extensions/tts/elevenlabs.js +++ b/public/scripts/extensions/tts/elevenlabs.js @@ -102,10 +102,6 @@ class ElevenLabsTtsProvider { return responseJson.voices } - /** - * - * @returns - */ async fetchTtsVoiceSettings() { const headers = { 'xi-api-key': this.API_KEY