import { doExtrasFetch, getApiUrl, modules } from "../../extensions.js" import { saveTtsProviderSettings } from "./index.js" export { SileroTtsProvider } class SileroTtsProvider { //########// // Config // //########// settings ready = false voices = [] separator = ' .. ' defaultSettings = { provider_endpoint: "http://localhost:8001/tts", voiceMap: {} } get settingsHtml() { let html = ` Use SillyTavern Extras API or Silero TTS Server. ` return html } onSettingsChange() { // Used when provider settings are updated from UI this.settings.provider_endpoint = $('#silero_tts_endpoint').val() saveTtsProviderSettings() this.refreshSession() } async loadSettings(settings) { // Pupulate Provider UI given input settings if (Object.keys(settings).length == 0) { console.info("Using default TTS Provider settings") } // Only accept keys defined in defaultSettings this.settings = this.defaultSettings for (const key in settings) { if (key in this.settings) { this.settings[key] = settings[key] } else { throw `Invalid setting passed to TTS Provider: ${key}` } } const apiCheckInterval = setInterval(() => { // Use Extras API if TTS support is enabled if (modules.includes('tts') || modules.includes('silero-tts')) { const baseUrl = new URL(getApiUrl()); baseUrl.pathname = '/api/tts'; this.settings.provider_endpoint = baseUrl.toString(); $('#silero_tts_endpoint').val(this.settings.provider_endpoint); clearInterval(apiCheckInterval); } }, 2000); $('#silero_tts_endpoint').val(this.settings.provider_endpoint) $('#silero_tts_endpoint').on("input", () => { this.onSettingsChange() }) this.refreshSession() await this.checkReady() console.debug("SileroTTS: Settings loaded") } // Perform a simple readiness check by trying to fetch voiceIds async checkReady() { await this.fetchTtsVoiceObjects() } async onRefreshClick() { return } async refreshSession() { await this.initSession() } //#################// // TTS Interfaces // //#################// async getVoice(voiceName) { if (this.voices.length == 0) { this.voices = await this.fetchTtsVoiceObjects() } const match = this.voices.filter( sileroVoice => sileroVoice.name == voiceName )[0] if (!match) { throw `TTS Voice name ${voiceName} not found` } return match } async generateTts(text, voiceId) { const response = await this.fetchTtsGeneration(text, voiceId) return response } //###########// // API CALLS // //###########// async fetchTtsVoiceObjects() { const response = await doExtrasFetch(`${this.settings.provider_endpoint}/speakers`) if (!response.ok) { throw new Error(`HTTP ${response.status}: ${await response.json()}`) } const responseJson = await response.json() return responseJson } async fetchTtsGeneration(inputText, voiceId) { console.info(`Generating new TTS for voice_id ${voiceId}`) const response = await doExtrasFetch( `${this.settings.provider_endpoint}/generate`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Cache-Control': 'no-cache' // Added this line to disable caching of file so new files are always played - Rolyat 7/7/23 }, body: JSON.stringify({ "text": inputText, "speaker": voiceId, "session": "sillytavern" }) } ) if (!response.ok) { toastr.error(response.statusText, 'TTS Generation Failed'); throw new Error(`HTTP ${response.status}: ${await response.text()}`); } return response } async initSession() { console.info(`Silero TTS: requesting new session`); try { const response = await doExtrasFetch( `${this.settings.provider_endpoint}/session`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Cache-Control': 'no-cache', }, body: JSON.stringify({ "path": "sillytavern", }), } ) if (!response.ok && response.status !== 404) { throw new Error(`HTTP ${response.status}: ${await response.text()}`); } } catch (error) { console.info('Silero TTS: endpoint not available', error); } } // Interface not used by Silero TTS async fetchTtsFromHistory(history_item_id) { return Promise.resolve(history_item_id); } }