From 42aa7fd316854af8e46189ea94e27171c072176f Mon Sep 17 00:00:00 2001 From: based Date: Sun, 31 Dec 2023 06:21:40 +1000 Subject: [PATCH 001/151] mistral proxy support --- public/index.html | 6 +++--- public/scripts/openai.js | 4 ++-- src/endpoints/backends/chat-completions.js | 11 ++++++----- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/public/index.html b/public/index.html index 28d41d52d..6366b0992 100644 --- a/public/index.html +++ b/public/index.html @@ -707,8 +707,8 @@ -
-
+
+
OpenAI / Claude Reverse Proxy
@@ -735,7 +735,7 @@
-
+
Proxy Password
diff --git a/public/scripts/openai.js b/public/scripts/openai.js index ffb4a9efe..0de1672fd 100644 --- a/public/scripts/openai.js +++ b/public/scripts/openai.js @@ -1562,8 +1562,8 @@ async function sendOpenAIRequest(type, messages, signal) { delete generate_data.stop; } - // Proxy is only supported for Claude and OpenAI - if (oai_settings.reverse_proxy && [chat_completion_sources.CLAUDE, chat_completion_sources.OPENAI].includes(oai_settings.chat_completion_source)) { + // Proxy is only supported for Claude, OpenAI and Mistral + if (oai_settings.reverse_proxy && [chat_completion_sources.CLAUDE, chat_completion_sources.OPENAI, chat_completion_sources.MISTRALAI].includes(oai_settings.chat_completion_source)) { validateReverseProxy(); generate_data['reverse_proxy'] = oai_settings.reverse_proxy; generate_data['proxy_password'] = oai_settings.proxy_password; diff --git a/src/endpoints/backends/chat-completions.js b/src/endpoints/backends/chat-completions.js index 712ed4485..d95415a70 100644 --- a/src/endpoints/backends/chat-completions.js +++ b/src/endpoints/backends/chat-completions.js @@ -12,7 +12,7 @@ const { getTokenizerModel, getSentencepiceTokenizer, getTiktokenTokenizer, sente const API_OPENAI = 'https://api.openai.com/v1'; const API_CLAUDE = 'https://api.anthropic.com/v1'; - +const API_MISTRAL = 'https://api.mistral.ai/v1'; /** * Sends a request to Claude API. * @param {express.Request} request Express request @@ -431,7 +431,8 @@ async function sendAI21Request(request, response) { * @param {express.Response} response Express response */ async function sendMistralAIRequest(request, response) { - const apiKey = readSecret(SECRET_KEYS.MISTRALAI); + const apiUrl = new URL(request.body.reverse_proxy || API_MISTRAL).toString(); + const apiKey = request.body.reverse_proxy ? request.body.proxy_password : readSecret(SECRET_KEYS.MISTRALAI); if (!apiKey) { console.log('MistralAI API key is missing.'); @@ -495,7 +496,7 @@ async function sendMistralAIRequest(request, response) { console.log('MisralAI request:', requestBody); - const generateResponse = await fetch('https://api.mistral.ai/v1/chat/completions', config); + const generateResponse = await fetch(apiUrl + '/chat/completions', config); if (request.body.stream) { forwardFetchResponse(generateResponse, response); } else { @@ -538,8 +539,8 @@ router.post('/status', jsonParser, async function (request, response_getstatus_o // OpenRouter needs to pass the referer: https://openrouter.ai/docs headers = { 'HTTP-Referer': request.headers.referer }; } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.MISTRALAI) { - api_url = 'https://api.mistral.ai/v1'; - api_key_openai = readSecret(SECRET_KEYS.MISTRALAI); + api_url = new URL(request.body.reverse_proxy || API_MISTRAL).toString(); + api_key_openai = request.body.reverse_proxy ? request.body.proxy_password : readSecret(SECRET_KEYS.MISTRALAI); } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.CUSTOM) { api_url = request.body.custom_url; api_key_openai = readSecret(SECRET_KEYS.CUSTOM); From 72d78fbe9695b1e0b2b27b43394bc5bb093a76fb Mon Sep 17 00:00:00 2001 From: Tony Ribeiro Date: Fri, 5 Jan 2024 07:00:23 +0100 Subject: [PATCH 002/151] Add VRM import map and assets endpoint. --- public/index.html | 13 +++++++++++++ src/endpoints/assets.js | 29 ++++++++++++++++++++++++++++- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/public/index.html b/public/index.html index dfbdcf032..f2d903bcd 100644 --- a/public/index.html +++ b/public/index.html @@ -22,6 +22,19 @@ + + + + + diff --git a/src/endpoints/assets.js b/src/endpoints/assets.js index 4ff1d91bb..0bd160cc2 100644 --- a/src/endpoints/assets.js +++ b/src/endpoints/assets.js @@ -8,7 +8,7 @@ const { DIRECTORIES, UNSAFE_EXTENSIONS } = require('../constants'); const { jsonParser } = require('../express-common'); const { clientRelativePath } = require('../util'); -const VALID_CATEGORIES = ['bgm', 'ambient', 'blip', 'live2d']; +const VALID_CATEGORIES = ['bgm', 'ambient', 'blip', 'live2d', 'vrm']; /** * Validates the input filename for the asset. @@ -106,6 +106,33 @@ router.post('/get', jsonParser, async (_, response) => { continue; } + // VRM assets + if (folder == 'vrm') { + output[folder] = {'model':[], 'animation':[]}; + // Extract models + const vrm_model_folder = path.normalize(path.join(folderPath, 'vrm', 'model')); + let files = getFiles(vrm_model_folder); + //console.debug("FILE FOUND:",files) + for (let file of files) { + if (!file.endsWith('.placeholder')) { + //console.debug("Asset VRM model found:",file) + output['vrm']['model'].push(clientRelativePath(file)); + } + } + + // Extract models + const vrm_animation_folder = path.normalize(path.join(folderPath, 'vrm', 'animation')); + files = getFiles(vrm_animation_folder); + //console.debug("FILE FOUND:",files) + for (let file of files) { + if (!file.endsWith('.placeholder')) { + //console.debug("Asset VRM animation found:",file) + output['vrm']['animation'].push(clientRelativePath(file)); + } + } + continue; + } + // Other assets (bgm/ambient/blip) const files = fs.readdirSync(path.join(folderPath, folder)) .filter(filename => { From 4414de90879695e26d077f96f4b8a7cc9f44bce2 Mon Sep 17 00:00:00 2001 From: Tony Ribeiro Date: Fri, 5 Jan 2024 22:28:18 +0100 Subject: [PATCH 003/151] Add vrm assets folders. --- public/assets/vrm/animation/.placeholder | 1 + public/assets/vrm/model/.placeholder | 1 + 2 files changed, 2 insertions(+) create mode 100644 public/assets/vrm/animation/.placeholder create mode 100644 public/assets/vrm/model/.placeholder diff --git a/public/assets/vrm/animation/.placeholder b/public/assets/vrm/animation/.placeholder new file mode 100644 index 000000000..c7a29571f --- /dev/null +++ b/public/assets/vrm/animation/.placeholder @@ -0,0 +1 @@ +Put VRM animation files here diff --git a/public/assets/vrm/model/.placeholder b/public/assets/vrm/model/.placeholder new file mode 100644 index 000000000..14ce3cf88 --- /dev/null +++ b/public/assets/vrm/model/.placeholder @@ -0,0 +1 @@ +Put VRM model files here From 9354697753f264fadadf619ce143119054b16647 Mon Sep 17 00:00:00 2001 From: h-a-s-k <140975143+h-a-s-k@users.noreply.github.com> Date: Sat, 13 Jan 2024 13:06:51 -0300 Subject: [PATCH 004/151] Actually call them example chats --- default/settings.json | 2 +- public/scripts/openai.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/default/settings.json b/default/settings.json index 75e785724..200cfbc21 100644 --- a/default/settings.json +++ b/default/settings.json @@ -562,7 +562,7 @@ "impersonation_prompt": "[Write your next reply from the point of view of {{user}}, using the chat history so far as a guideline for the writing style of {{user}}. Write 1 reply only in internet RP style. Don't write as {{char}} or system. Don't describe actions of {{char}}.]", "new_chat_prompt": "[Start a new Chat]", "new_group_chat_prompt": "[Start a new group chat. Group members: {{group}}]", - "new_example_chat_prompt": "[Start a new Chat]", + "new_example_chat_prompt": "[Example Chat]", "continue_nudge_prompt": "[Continue the following message. Do not include ANY parts of the original message. Use capitalization and punctuation as if your reply is a part of the original message: {{lastChatMessage}}]", "bias_preset_selected": "Default (none)", "bias_presets": { diff --git a/public/scripts/openai.js b/public/scripts/openai.js index 099b3d5f9..bca5d7bec 100644 --- a/public/scripts/openai.js +++ b/public/scripts/openai.js @@ -89,7 +89,7 @@ const default_enhance_definitions_prompt = 'If you have more knowledge of {{char const default_wi_format = '[Details of the fictional world the RP is set in:\n{0}]\n'; const default_new_chat_prompt = '[Start a new Chat]'; const default_new_group_chat_prompt = '[Start a new group chat. Group members: {{group}}]'; -const default_new_example_chat_prompt = '[Start a new Chat]'; +const default_new_example_chat_prompt = '[Example Chat]'; const default_claude_human_sysprompt_message = 'Let\'s get started. Please generate your response based on the information and instructions provided above.'; const default_continue_nudge_prompt = '[Continue the following message. Do not include ANY parts of the original message. Use capitalization and punctuation as if your reply is a part of the original message: {{lastChatMessage}}]'; const default_bias = 'Default (none)'; From 074cc13e6051cb4abb59ca86e5e216034881d5df Mon Sep 17 00:00:00 2001 From: h-a-s-k <140975143+h-a-s-k@users.noreply.github.com> Date: Sat, 13 Jan 2024 14:34:17 -0300 Subject: [PATCH 005/151] Fix group chat example messages not including character name --- public/scripts/openai.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/public/scripts/openai.js b/public/scripts/openai.js index bca5d7bec..3c24c4269 100644 --- a/public/scripts/openai.js +++ b/public/scripts/openai.js @@ -464,7 +464,7 @@ function setOpenAIMessages(chat) { } /** - * Formats chat messages into chat completion messages. + * Formats chat examples into chat completion messages. * @param {string[]} mesExamplesArray - Array containing all examples. * @returns {object[]} - Array containing all examples formatted for chat completion. */ @@ -541,14 +541,14 @@ function parseExampleIntoIndividual(messageExampleString) { let cur_msg_lines = []; let in_user = false; let in_bot = false; - // DRY my cock and balls + // DRY my cock and balls :) function add_msg(name, role, system_name) { // join different newlines (we split them by \n and join by \n) // remove char name // strip to remove extra spaces let parsed_msg = cur_msg_lines.join('\n').replace(name + ':', '').trim(); - if (selected_group && role == 'assistant') { + if (selected_group && ['example_user', 'example_assistant'].includes(system_name)) { parsed_msg = `${name}: ${parsed_msg}`; } From b78350cc8e70a0d7599122e734c6ac93459e6fc1 Mon Sep 17 00:00:00 2001 From: erew123 <35898566+erew123@users.noreply.github.com> Date: Sat, 13 Jan 2024 19:43:38 +0000 Subject: [PATCH 006/151] AllTalk initial commit AllTalk TTS initial commit from https://github.com/erew123/alltalk_tts ST index.js is updated to have "Pass Asterisks to TTS Engine" which allows AllTalk to split text as character/narrator voices. This setting has been left disabled as standard, so wont affect other TTS engines. The setting will save along with other settings on the page (if checked). --- public/scripts/extensions/tts/alltalk.js | 938 +++++++++++++++++++++++ public/scripts/extensions/tts/index.js | 24 +- 2 files changed, 959 insertions(+), 3 deletions(-) create mode 100644 public/scripts/extensions/tts/alltalk.js diff --git a/public/scripts/extensions/tts/alltalk.js b/public/scripts/extensions/tts/alltalk.js new file mode 100644 index 000000000..e44b31240 --- /dev/null +++ b/public/scripts/extensions/tts/alltalk.js @@ -0,0 +1,938 @@ +import { doExtrasFetch, getApiUrl, modules } from '../../extensions.js'; +import { saveTtsProviderSettings } from './index.js'; + +export { AllTalkTtsProvider }; + +const css = ` +.settings-row { + display: flex; + justify-content: space-between; /* Distribute space evenly between the children */ + align-items: center; + width: 100%; /* Full width to spread out the children */ +} + +.settings-option { + flex: 1; /* Each child will take equal space */ + margin: 0 10px; /* Adjust spacing as needed */ +} + +.endpoint-option { + flex: 1; /* Each child will take equal space */ + margin: 0 10px; /* Adjust spacing as needed */ + margin-right: 25px; + width: 38%; /* Full width to spread out the children */ +} + +.website-row { + display: flex; + justify-content: start; /* Aligns items to the start of the row */ + align-items: center; + margin-top: 10px; /* Top margin */ + margin-bottom: 10px; /* Bottom margin */ +} + +.website-option { + flex: 1; /* Allows each option to take equal space */ + margin-right: 10px; /* Spacing between the two options, adjust as needed */ + margin-left: 10px; /* Adjust this value to align as desired */ + /* Remove the individual margin top and bottom declarations if they are set globally on the .settings-row */ +} + +.settings-separator { + margin-top: 10px; + margin-bottom: 10px; + padding: 18x; + font-weight: bold; + border-top: 1px solid #e1e1e1; /* Grey line */ + border-bottom: 1px solid #e1e1e1; /* Grey line */ + text-align: center; +} + +.status-message { + flex: 1; /* Adjust as needed */ + margin: 0 10px; + /* Additional styling */ +} + +.model-endpoint-row { + display: flex; + justify-content: space-between; + align-items: center; + width: 100%; +} + +.model-option, .endpoint-option { + flex: 1; + margin: 0 10px; + margin-left: 10px; /* Adjust this value to align as desired */ + /* Adjust width as needed to fit in one line */ +} + +.endpoint-option { + /* Adjust width to align with model dropdown */ + width: 38%; +} + +#status_info { + color: lightgreen; /* Or any specific shade of green you prefer */ +} +`; + +const style = document.createElement('style'); +style.type = 'text/css'; +style.appendChild(document.createTextNode(css)); +document.head.appendChild(style); + +class AllTalkTtsProvider { + //########// + // Config // + //########// + + settings; + ready = false; + voices = []; + separator = '. '; + audioElement = document.createElement('audio'); + + languageLabels = { + 'Arabic': 'ar', + 'Brazilian Portuguese': 'pt', + 'Chinese': 'zh-cn', + 'Czech': 'cs', + 'Dutch': 'nl', + 'English': 'en', + 'French': 'fr', + 'German': 'de', + 'Italian': 'it', + 'Polish': 'pl', + 'Russian': 'ru', + 'Spanish': 'es', + 'Turkish': 'tr', + 'Japanese': 'ja', + 'Korean': 'ko', + 'Hungarian': 'hu', + 'Hindi': 'hi', + }; + + Settings = { + provider_endpoint: 'http://localhost:7851', + language: 'en', + voiceMap: {}, + at_generation_method: 'standard_generation', + narrator_enabled: 'false', + at_narrator_text_not_inside: 'narrator', + narrator_voice_gen: 'female_01.wav', + finetuned_model: 'false' + }; + + get settingsHtml() { + let html = `
AllTalk Settings
`; + + html += `
+ +
+ + +
+
`; + + html += `
+ +
+ + +
+ +
+ + +
+ +
`; + + html += `
+
+ + +
+
+ + +
+
`; + + + html += `
+
+ + +
+ +
+ + +
+
`; + + + html += `
+
+ + +
+
+ + +
+
+ Status: Ready +
+
+ +
+
`; + + + html += `
+
+ AllTalk Config & Docs. +
+ +
+ AllTalk Website. +
+
`; + +html += `
+
+Text-generation-webui users - Uncheck Enable TTS in Text-generation-webui. +
+
`; + + + + return html; + } + + + //#################// + // Startup ST & AT // + //#################// + + async loadSettings(settings) { + updateStatus('Offline'); + // Use default settings if no settings are provided + this.settings = Object.keys(settings).length === 0 ? this.Settings : settings; + + try { + // Check if TTS provider is ready + await this.checkReady(); + await this.fetchTtsVoiceObjects(); // Fetch voices only if service is ready + await this.updateSettingsFromServer(); + this.updateNarratorVoicesDropdown(); + this.updateLanguageDropdown(); + this.setupEventListeners(); + this.applySettingsToHTML(); + updateStatus('Ready'); + } catch (error) { + console.error("Error loading settings:", error); + // Set status to Error if there is an issue in loading settings + updateStatus('Offline'); + } + } + + applySettingsToHTML() { + // Load AllTalk specific settings first + const loadedSettings = loadAllTalkSettings(); + // Apply loaded settings or use defaults + this.settings = loadedSettings ? { ...this.Settings, ...loadedSettings } : this.Settings; + const narratorVoiceSelect = document.getElementById('narrator_voice'); + const atNarratorSelect = document.getElementById('at_narrator_enabled'); + const textNotInsideSelect = document.getElementById('at_narrator_text_not_inside'); + const generationMethodSelect = document.getElementById('at_generation_method'); + // Apply settings to Narrator Voice dropdown + if (narratorVoiceSelect && this.settings.narrator_voice) { + narratorVoiceSelect.value = this.settings.narrator_voice.replace('.wav', ''); + this.settings.narrator_voice_gen = this.settings.narrator_voice; + //console.log(this.settings.narrator_voice_gen) + } + // Apply settings to AT Narrator Enabled dropdown + if (atNarratorSelect) { + //console.log(this.settings.narrator_enabled) + // Sync the state with the checkbox in index.js + const ttsPassAsterisksCheckbox = document.getElementById('tts_pass_asterisks'); // Access the checkbox from index.js + const ttsNarrateQuotedCheckbox = document.getElementById('tts_narrate_quoted'); // Access the checkbox from index.js + const ttsNarrateDialoguesCheckbox = document.getElementById('tts_narrate_dialogues'); // Access the checkbox from index.js + // Sync the state with the checkbox in index.js + if (this.settings.narrator_enabled) { + ttsPassAsterisksCheckbox.checked = false; + $('#tts_pass_asterisks').click(); // Simulate a click event + $('#tts_pass_asterisks').trigger('change'); + } + if (!this.settings.narrator_enabled) { + ttsPassAsterisksCheckbox.checked = true; + $('#tts_pass_asterisks').click(); // Simulate a click event + $('#tts_pass_asterisks').trigger('change'); + } + // Uncheck and set tts_narrate_quoted to false if narrator is enabled + if (this.settings.narrator_enabledd) { + ttsNarrateQuotedCheckbox.checked = true; + ttsNarrateDialoguesCheckbox.checked = true; + // Trigger click events instead of change events + $('#tts_narrate_quoted').click(); + $('#tts_narrate_quoted').trigger('change'); + $('#tts_narrate_dialogues').click(); + $('#tts_narrate_dialogues').trigger('change'); + } + atNarratorSelect.value = this.settings.narrator_enabled.toString(); + this.settings.narrator_enabled = this.settings.narrator_enabled.toString(); + } + // Apply settings to the Language dropdown + const languageSelect = document.getElementById('language_options'); + if (languageSelect && this.settings.language) { + languageSelect.value = this.settings.language; + //console.log(this.settings.language) + } + // Apply settings to Text Not Inside dropdown + if (textNotInsideSelect && this.settings.text_not_inside) { + textNotInsideSelect.value = this.settings.text_not_inside; + this.settings.at_narrator_text_not_inside = this.settings.text_not_inside; + //console.log(this.settings.at_narrator_text_not_inside) + } + // Apply settings to Generation Method dropdown + if (generationMethodSelect && this.settings.at_generation_method) { + generationMethodSelect.value = this.settings.at_generation_method; + //console.log(this.settings.at_generation_method) + } + // Additional logic to disable/enable dropdowns based on the selected generation method + const isStreamingEnabled = this.settings.at_generation_method === 'streaming_enabled'; + if (isStreamingEnabled) { + // Disable certain dropdowns when streaming is enabled + if (atNarratorSelect) atNarratorSelect.disabled = true; + if (textNotInsideSelect) textNotInsideSelect.disabled = true; + if (narratorVoiceSelect) narratorVoiceSelect.disabled = true; + } else { + // Enable dropdowns for standard generation + if (atNarratorSelect) atNarratorSelect.disabled = false; + if (textNotInsideSelect) textNotInsideSelect.disabled = !this.settings.narrator_enabled; + if (narratorVoiceSelect) narratorVoiceSelect.disabled = !this.settings.narrator_enabled; + } + const modelSelect = document.getElementById('switch_model'); + if (this.settings.finetuned_model === 'true') { + const ftOption = document.createElement('option'); + ftOption.value = 'XTTSv2 FT'; + ftOption.textContent = 'XTTSv2 FT'; + modelSelect.appendChild(ftOption); + } + //console.log("Settings applied to HTML."); + } + + //##############################// + // Check AT Server is Available // + //##############################// + + async checkReady() { + try { + const response = await fetch(`${this.settings.provider_endpoint}/api/ready`); + // Check if the HTTP request was successful + if (!response.ok) { + throw new Error(`HTTP Error Response: ${response.status} ${response.statusText}`); + } + const statusText = await response.text(); + // Check if the response is 'Ready' + if (statusText === 'Ready') { + this.ready = true; // Set the ready flag to true + console.log('TTS service is ready.'); + } else { + this.ready = false; + console.log('TTS service is not ready.'); + } + } catch (error) { + console.error('Error checking TTS service readiness:', error); + this.ready = false; // Ensure ready flag is set to false in case of error + } + } + + //######################// + // Get Available Voices // + //######################// + + async fetchTtsVoiceObjects() { + const response = await fetch(`${this.settings.provider_endpoint}/api/voices`); + if (!response.ok) { + const errorText = await response.text(); + throw new Error(`HTTP ${response.status}: ${errorText}`); + } + const data = await response.json(); + const voices = data.voices.map(filename => { + const voiceName = filename.replace('.wav', ''); + return { + name: voiceName, + voice_id: voiceName, + preview_url: null, // Preview URL will be dynamically generated + lang: 'en' // Default language + }; + }); + this.voices = voices; // Assign to the class property + return voices; // Also return this list + } + + //##########################################// + // Get Current AT Server Config & Update ST // + //##########################################// + + async updateSettingsFromServer() { + try { + // Fetch current settings + const response = await fetch(`${this.settings.provider_endpoint}/api/currentsettings`); + if (!response.ok) { + throw new Error(`Failed to fetch current settings: ${response.statusText}`); + } + const currentSettings = await response.json(); + // Update internal settings + this.settings.modelsAvailable = currentSettings.models_available; + this.settings.currentModel = currentSettings.current_model_loaded; + this.settings.deepspeed_available = currentSettings.deepspeed_available; + this.settings.deepSpeedEnabled = currentSettings.deepspeed_status; + this.settings.lowVramEnabled = currentSettings.low_vram_status; + this.settings.finetuned_model = currentSettings.finetuned_model + // Update HTML elements + this.updateModelDropdown(); + this.updateCheckboxes(); + } catch (error) { + console.error(`Error updating settings from server: ${error}`); + } + } + + //###################################################// + // Get Current AT Server Config & Update ST (Models) // + //###################################################// + + updateModelDropdown() { + const modelSelect = document.getElementById('switch_model'); + if (modelSelect) { + modelSelect.innerHTML = ''; // Clear existing options + this.settings.modelsAvailable.forEach(model => { + const option = document.createElement('option'); + option.value = model.model_name; + option.textContent = model.model_name; // Use model_name instead of name + option.selected = model.model_name === this.settings.currentModel; + modelSelect.appendChild(option); + }); + } + } + + //#######################################################// + // Get Current AT Server Config & Update ST (DS and LVR) // + //#######################################################// + + updateCheckboxes() { + const deepspeedCheckbox = document.getElementById('deepspeed'); + const lowVramCheckbox = document.getElementById('low_vram'); + if (lowVramCheckbox) lowVramCheckbox.checked = this.settings.lowVramEnabled; + if (deepspeedCheckbox) { + deepspeedCheckbox.checked = this.settings.deepSpeedEnabled; + deepspeedCheckbox.disabled = !this.settings.deepspeed_available; // Disable checkbox if deepspeed is not available + } + } + + //###############################################################// + // Get Current AT Server Config & Update ST (AT Narrator Voices) // + //###############################################################// + + updateNarratorVoicesDropdown() { + const narratorVoiceSelect = document.getElementById('narrator_voice'); + if (narratorVoiceSelect && this.voices) { + // Clear existing options + narratorVoiceSelect.innerHTML = ''; + // Add new options + for (let voice of this.voices) { + const option = document.createElement('option'); + option.value = voice.voice_id; + option.textContent = voice.name; + narratorVoiceSelect.appendChild(option); + } + } + } + + //######################################################// + // Get Current AT Server Config & Update ST (Languages) // + //######################################################// + + updateLanguageDropdown() { + const languageSelect = document.getElementById('language_options'); + if (languageSelect) { + // Ensure default language is set + this.settings.language = this.Settings.language; + + languageSelect.innerHTML = ''; + for (let language in this.languageLabels) { + const option = document.createElement('option'); + option.value = this.languageLabels[language]; + option.textContent = language; + if (this.languageLabels[language] === this.settings.language) { + option.selected = true; + } + languageSelect.appendChild(option); + } + } + } + + //########################################// + // Start AT TTS extenstion page listeners // + //########################################// + + setupEventListeners() { + + let debounceTimeout; + const debounceDelay = 500; // Milliseconds + + // Define the event handler function + const onModelSelectChange = async (event) => { + console.log("Model select change event triggered"); // Debugging statement + const selectedModel = event.target.value; + console.log(`Selected model: ${selectedModel}`); // Debugging statement + // Set status to Processing + updateStatus('Processing'); + try { + const response = await fetch(`${this.settings.provider_endpoint}/api/reload?tts_method=${encodeURIComponent(selectedModel)}`, { + method: 'POST' + }); + if (!response.ok) { + throw new Error(`HTTP Error: ${response.status}`); + } + const data = await response.json(); + console.log("POST response data:", data); // Debugging statement + // Set status to Ready if successful + updateStatus('Ready'); + } catch (error) { + console.error("POST request error:", error); // Debugging statement + // Set status to Error in case of failure + updateStatus('Error'); + } + + // Handle response or error + }; + + const debouncedModelSelectChange = (event) => { + clearTimeout(debounceTimeout); + debounceTimeout = setTimeout(() => { + onModelSelectChange(event); + }, debounceDelay); + }; + + // Switch Model Listener + const modelSelect = document.getElementById('switch_model'); + if (modelSelect) { + // Remove the event listener if it was previously added + modelSelect.removeEventListener('change', debouncedModelSelectChange); + // Add the debounced event listener + modelSelect.addEventListener('change', debouncedModelSelectChange); + } + + // DeepSpeed Listener + const deepspeedCheckbox = document.getElementById('deepspeed'); + if (deepspeedCheckbox) { + deepspeedCheckbox.addEventListener('change', async (event) => { + const deepSpeedValue = event.target.checked ? 'True' : 'False'; + // Set status to Processing + updateStatus('Processing'); + try { + const response = await fetch(`${this.settings.provider_endpoint}/api/deepspeed?new_deepspeed_value=${deepSpeedValue}`, { + method: 'POST' + }); + if (!response.ok) { + throw new Error(`HTTP Error: ${response.status}`); + } + const data = await response.json(); + console.log("POST response data:", data); // Debugging statement + // Set status to Ready if successful + updateStatus('Ready'); + } catch (error) { + console.error("POST request error:", error); // Debugging statement + // Set status to Error in case of failure + updateStatus('Error'); + } + }); + } + + // Low VRAM Listener + const lowVramCheckbox = document.getElementById('low_vram'); + if (lowVramCheckbox) { + lowVramCheckbox.addEventListener('change', async (event) => { + const lowVramValue = event.target.checked ? 'True' : 'False'; + // Set status to Processing + updateStatus('Processing'); + try { + const response = await fetch(`${this.settings.provider_endpoint}/api/lowvramsetting?new_low_vram_value=${lowVramValue}`, { + method: 'POST' + }); + if (!response.ok) { + throw new Error(`HTTP Error: ${response.status}`); + } + const data = await response.json(); + console.log("POST response data:", data); // Debugging statement + // Set status to Ready if successful + updateStatus('Ready'); + } catch (error) { + console.error("POST request error:", error); // Debugging statement + // Set status to Error in case of failure + updateStatus('Error'); + } + }); + } + + // Narrator Voice Dropdown Listener + const narratorVoiceSelect = document.getElementById('narrator_voice'); + if (narratorVoiceSelect) { + narratorVoiceSelect.addEventListener('change', (event) => { + this.settings.narrator_voice_gen = `${event.target.value}.wav`; + this.onSettingsChangeAllTalk(); // Save the settings after change + }); + } + + const textNotInsideSelect = document.getElementById('at_narrator_text_not_inside'); + if (textNotInsideSelect) { + textNotInsideSelect.addEventListener('change', (event) => { + this.settings.text_not_inside = event.target.value; + this.onSettingsChangeAllTalk(); // Save the settings after change + }); + } + + // AT Narrator Dropdown Listener + const atNarratorSelect = document.getElementById('at_narrator_enabled'); + const ttsPassAsterisksCheckbox = document.getElementById('tts_pass_asterisks'); // Access the checkbox from index.js + const ttsNarrateQuotedCheckbox = document.getElementById('tts_narrate_quoted'); // Access the checkbox from index.js + const ttsNarrateDialoguesCheckbox = document.getElementById('tts_narrate_dialogues'); // Access the checkbox from index.js + + if (atNarratorSelect && textNotInsideSelect && narratorVoiceSelect) { + atNarratorSelect.addEventListener('change', (event) => { + const isNarratorEnabled = event.target.value === 'true'; + this.settings.narrator_enabled = isNarratorEnabled; // Update the setting here + textNotInsideSelect.disabled = !isNarratorEnabled; + narratorVoiceSelect.disabled = !isNarratorEnabled; + + // Sync the state with the checkbox in index.js + if (isNarratorEnabled) { + ttsPassAsterisksCheckbox.checked = false; + $('#tts_pass_asterisks').click(); // Simulate a click event + $('#tts_pass_asterisks').trigger('change'); + } + if (!isNarratorEnabled) { + ttsPassAsterisksCheckbox.checked = true; + $('#tts_pass_asterisks').click(); // Simulate a click event + $('#tts_pass_asterisks').trigger('change'); + } + // Uncheck and set tts_narrate_quoted to false if narrator is enabled + if (isNarratorEnabled) { + ttsNarrateQuotedCheckbox.checked = true; + ttsNarrateDialoguesCheckbox.checked = true; + // Trigger click events instead of change events + $('#tts_narrate_quoted').click(); + $('#tts_narrate_quoted').trigger('change'); + $('#tts_narrate_dialogues').click(); + $('#tts_narrate_dialogues').trigger('change'); + } + this.onSettingsChangeAllTalk(); // Save the settings after change + }); + } + + + + // Event Listener for AT Generation Method Dropdown + const atGenerationMethodSelect = document.getElementById('at_generation_method'); + const atNarratorEnabledSelect = document.getElementById('at_narrator_enabled'); + if (atGenerationMethodSelect) { + atGenerationMethodSelect.addEventListener('change', (event) => { + const selectedMethod = event.target.value; + + if (selectedMethod === 'streaming_enabled') { + // Disable and unselect AT Narrator + atNarratorEnabledSelect.disabled = true; + atNarratorEnabledSelect.value = 'false'; + textNotInsideSelect.disabled = true; + narratorVoiceSelect.disabled = true; + } else if (selectedMethod === 'standard_generation') { + // Enable AT Narrator + atNarratorEnabledSelect.disabled = false; + } + this.settings.at_generation_method = selectedMethod; // Update the setting here + this.onSettingsChangeAllTalk(); // Save the settings after change + }); + } + + // Listener for Language Dropdown + const languageSelect = document.getElementById('language_options'); + if (languageSelect) { + languageSelect.addEventListener('change', (event) => { + this.settings.language = event.target.value; + this.onSettingsChangeAllTalk(); // Save the settings after change + }); + } + } + + //#############################// + // Store ST interface settings // + //#############################// + + onSettingsChange() { + // Update settings that SillyTavern will save + this.settings.provider_endpoint = $('#at_server').val(); + this.settings.language = $('#language_options').val(); + saveTtsProviderSettings(); // This function should save settings handled by SillyTavern + // Call the function to handle AllTalk specific settings + this.onSettingsChangeAllTalk(); // Save the settings after change + } + + onSettingsChangeAllTalk() { + // Update AllTalk specific settings and save to localStorage + this.settings.narrator_enabled = $('#at_narrator_enabled').val() === 'true'; + this.settings.narrator_voice = $('#narrator_voice').val() + '.wav'; // assuming you need to append .wav + this.settings.text_not_inside = $('#at_narrator_text_not_inside').val(); // "character" or "narrator" + this.settings.at_generation_method = $('#at_generation_method').val(); // Streaming or standard + this.settings.language = $('#language_options').val(); // Streaming or standard + + // Call the save function with the current settings + saveAllTalkSettings({ + narrator_voice: this.settings.narrator_voice, + narrator_enabled: this.settings.narrator_enabled, + text_not_inside: this.settings.text_not_inside, + at_generation_method: this.settings.at_generation_method, + language: this.settings.language + }); + } + + //#########################// + // ST Handle Reload button // + //#########################// + + async onRefreshClick() { + await this.checkReady(); // Check if the TTS provider is ready + await this.loadSettings(this.settings); // Reload the settings + // Additional actions as needed + } + + //##################// + // Preview AT Voice // + //##################// + + async previewTtsVoice(voiceName) { + try { + // Prepare data for POST request + const postData = new URLSearchParams(); + postData.append("voice", `${voiceName}.wav`); + // Making the POST request + const response = await fetch(`${this.settings.provider_endpoint}/api/previewvoice/`, { + method: "POST", + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + }, + body: postData, + }); + if (!response.ok) { + const errorText = await response.text(); + console.error(`[previewTtsVoice] Error Response Text:`, errorText); + throw new Error(`HTTP ${response.status}: ${errorText}`); + } + // Assuming the server returns a URL to the .wav file + const data = await response.json(); + if (data.output_file_url) { + // Use an audio element to play the .wav file + const audioElement = new Audio(data.output_file_url); + audioElement.play().catch(e => console.error("Error playing audio:", e)); + } else { + console.warn("[previewTtsVoice] No output file URL received in the response"); + throw new Error("No output file URL received in the response"); + } + + } catch (error) { + console.error("[previewTtsVoice] Exception caught during preview generation:", error); + throw error; + } + } + + //#####################// + // Populate ST voices // + //#####################// + + async getVoice(voiceName, generatePreview = false) { + // Ensure this.voices is populated + if (this.voices.length === 0) { + // Fetch voice objects logic + } + // Find the object where the name matches voiceName + const match = this.voices.find(voice => voice.name === voiceName); + if (!match) { + // Error handling + } + // Generate preview URL only if requested + if (!match.preview_url && generatePreview) { + // Generate preview logic + } + return match; // Return the found voice object + } + + //##########################################// + // Generate TTS Streaming or call Standard // + //##########################################// + + async generateTts(inputText, voiceId) { + try { + if (this.settings.at_generation_method === 'streaming_enabled') { + // For streaming method + const streamingUrl = `${this.settings.provider_endpoint}/api/tts-generate-streaming?text=${encodeURIComponent(inputText)}&voice=${encodeURIComponent(voiceId)}.wav&language=${encodeURIComponent(this.settings.language)}&output_file=stream_output.wav`; + const audioElement = new Audio(streamingUrl); + audioElement.play(); // Play the audio stream directly + console.log("Streaming URL:", streamingUrl); + return new Response(null, { + status: 200, + statusText: "OK", + headers: { + "Content-Type": "audio/wav", + "Content-Location": streamingUrl + } + }); + } else { + // For standard method + const outputUrl = await this.fetchTtsGeneration(inputText, voiceId); + // Fetch the audio data as a blob from the URL + const audioResponse = await fetch(outputUrl); + if (!audioResponse.ok) { + throw new Error(`HTTP ${audioResponse.status}: Failed to fetch audio data`); + } + const audioBlob = await audioResponse.blob(); + if (!audioBlob.type.startsWith('audio/')) { + throw new Error(`Invalid audio data format. Expecting audio/*, got ${audioBlob.type}`); + } + return new Response(audioBlob, { + status: 200, + statusText: "OK", + headers: { + "Content-Type": audioBlob.type + } + }); + } + } catch (error) { + console.error("Error in generateTts:", error); + throw error; + } + } + + //####################// + // Generate Standard // + //####################// + + async fetchTtsGeneration(inputText, voiceId) { + // Prepare the request payload + const requestBody = new URLSearchParams({ + 'text_input': inputText, + 'text_filtering': "standard", + 'character_voice_gen': voiceId + ".wav", + 'narrator_enabled': this.settings.narrator_enabled, + 'narrator_voice_gen': this.settings.narrator_voice_gen, + 'text_not_inside': this.settings.text_not_inside, + 'language': this.settings.language, + 'output_file_name': "st_output", + 'output_file_timestamp': "true", + 'autoplay': "false", + 'autoplay_volume': "0.8" + }).toString(); + + try { + const response = await doExtrasFetch( + `${this.settings.provider_endpoint}/api/tts-generate`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Cache-Control': 'no-cache', + }, + body: requestBody + } + ); + + if (!response.ok) { + const errorText = await response.text(); + console.error(`[fetchTtsGeneration] Error Response Text:`, errorText); + // Uncomment the following line if you have a UI element for displaying errors + // toastr.error(response.statusText, 'TTS Generation Failed'); + throw new Error(`HTTP ${response.status}: ${errorText}`); + } + const data = await response.json(); + const outputUrl = data.output_file_url; + return outputUrl; // Return only the output_file_url + } catch (error) { + console.error("[fetchTtsGeneration] Exception caught:", error); + throw error; // Rethrow the error for further handling + } + } +} + +//#########################// +// Update Status Messages // +//#########################// + +function updateStatus(message) { + const statusElement = document.getElementById('status_info'); + if (statusElement) { + statusElement.textContent = message; + switch (message) { + case 'Offline': + statusElement.style.color = 'red'; + break; + case 'Ready': + statusElement.style.color = 'lightgreen'; + break; + case 'Processing': + statusElement.style.color = 'blue'; + break; + case 'Error': + statusElement.style.color = 'red'; + break; + // Add more cases as needed + } + } +} + +//########################// +// Save/load AT Settings // +//########################// + +function saveAllTalkSettings(settingsToSave) { + // Save the specific settings to localStorage + console.log("settings", settingsToSave) + localStorage.setItem('AllTalkSettings', JSON.stringify(settingsToSave)); +} + +function loadAllTalkSettings() { + // Retrieve the settings from localStorage + const settings = localStorage.getItem('AllTalkSettings'); + // If settings exist, parse them back into an object + if (settings) { + return JSON.parse(settings); + } + return null; +} diff --git a/public/scripts/extensions/tts/index.js b/public/scripts/extensions/tts/index.js index e4f7a0a91..107f58bd3 100644 --- a/public/scripts/extensions/tts/index.js +++ b/public/scripts/extensions/tts/index.js @@ -11,6 +11,7 @@ import { power_user } from '../../power-user.js'; import { registerSlashCommand } from '../../slash-commands.js'; import { OpenAITtsProvider } from './openai.js'; import { XTTSTtsProvider } from './xtts.js'; +import { AllTalkTtsProvider } from './alltalk.js'; export { talkingAnimation }; const UPDATE_INTERVAL = 1000; @@ -74,6 +75,7 @@ let ttsProviders = { Edge: EdgeTtsProvider, Novel: NovelTtsProvider, OpenAI: OpenAITtsProvider, + AllTalk: AllTalkTtsProvider, }; let ttsProvider; let ttsProviderName; @@ -488,9 +490,11 @@ async function processTtsQueue() { text = text.replace(/```.*?```/gs, '').trim(); } - text = extension_settings.tts.narrate_dialogues_only - ? text.replace(/\*[^*]*?(\*|$)/g, '').trim() // remove asterisks content - : text.replaceAll('*', '').trim(); // remove just the asterisks + if (!extension_settings.tts.pass_asterisks) { + text = extension_settings.tts.narrate_dialogues_only + ? text.replace(/\*[^*]*?(\*|$)/g, '').trim() // remove asterisks content + : text.replaceAll('*', '').trim(); // remove just the asterisks + } if (extension_settings.tts.narrate_quoted_only) { const special_quotes = /[“”]/g; // Extend this regex to include other special quotes @@ -572,6 +576,7 @@ function loadSettings() { $('#tts_auto_generation').prop('checked', extension_settings.tts.auto_generation); $('#tts_narrate_translated_only').prop('checked', extension_settings.tts.narrate_translated_only); $('#tts_narrate_user').prop('checked', extension_settings.tts.narrate_user); + $('#tts_pass_asterisks').prop('checked', extension_settings.tts.pass_asterisks); $('body').toggleClass('tts', extension_settings.tts.enabled); } @@ -627,6 +632,7 @@ function onAutoGenerationClick() { function onNarrateDialoguesClick() { extension_settings.tts.narrate_dialogues_only = !!$('#tts_narrate_dialogues').prop('checked'); saveSettingsDebounced(); + console.log("setting narrate changed", extension_settings.tts.narrate_dialogues_only) } function onNarrateUserClick() { @@ -637,6 +643,7 @@ function onNarrateUserClick() { function onNarrateQuotedClick() { extension_settings.tts.narrate_quoted_only = !!$('#tts_narrate_quoted').prop('checked'); saveSettingsDebounced(); + console.log("setting narrate quoted changed", extension_settings.tts.narrate_quoted_only) } @@ -650,6 +657,12 @@ function onSkipCodeblocksClick() { saveSettingsDebounced(); } +function onPassAsterisksClick() { + extension_settings.tts.pass_asterisks = !!$('#tts_pass_asterisks').prop('checked'); + saveSettingsDebounced(); + console.log("setting pass asterisks", extension_settings.tts.pass_asterisks) +} + //##############// // TTS Provider // //##############// @@ -968,6 +981,10 @@ $(document).ready(function () { Skip codeblocks +
@@ -989,6 +1006,7 @@ $(document).ready(function () { $('#tts_narrate_quoted').on('click', onNarrateQuotedClick); $('#tts_narrate_translated_only').on('click', onNarrateTranslatedOnlyClick); $('#tts_skip_codeblocks').on('click', onSkipCodeblocksClick); + $('#tts_pass_asterisks').on('click', onPassAsterisksClick); $('#tts_auto_generation').on('click', onAutoGenerationClick); $('#tts_narrate_user').on('click', onNarrateUserClick); $('#tts_voices').on('click', onTtsVoicesClick); From 017ac8a51568c3b1b7a6e898bcdf13f4e1cf5f4d Mon Sep 17 00:00:00 2001 From: Tony Ribeiro Date: Sun, 14 Jan 2024 04:26:06 +0100 Subject: [PATCH 007/151] Added vrm tts lip sync call --- public/scripts/extensions/tts/index.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/public/scripts/extensions/tts/index.js b/public/scripts/extensions/tts/index.js index 7bca9c258..c37e97ebc 100644 --- a/public/scripts/extensions/tts/index.js +++ b/public/scripts/extensions/tts/index.js @@ -474,6 +474,10 @@ async function tts(text, voiceId, char) { if (extension_settings.rvc.enabled && typeof window['rvcVoiceConversion'] === 'function') response = await window['rvcVoiceConversion'](response, char, text); + // VRM injection + if (extension_settings.vrm.enabled && typeof window['vrmLipSync'] === 'function') + await window['vrmLipSync'](response, char); + await addAudioJob(response); } From a8a993c5ba1a65d8f07dbe052150a4617c5e9364 Mon Sep 17 00:00:00 2001 From: Tony Ribeiro Date: Tue, 16 Jan 2024 04:54:14 +0100 Subject: [PATCH 008/151] Update tts processing to keep track of character for VRM lip sync. --- public/scripts/extensions/tts/index.js | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/public/scripts/extensions/tts/index.js b/public/scripts/extensions/tts/index.js index c37e97ebc..1b4020a50 100644 --- a/public/scripts/extensions/tts/index.js +++ b/public/scripts/extensions/tts/index.js @@ -311,13 +311,18 @@ let currentAudioJob; let audioPaused = false; let audioQueueProcessorReady = true; -async function playAudioData(audioBlob) { +async function playAudioData(audioJob) { + const audioBlob = audioJob["audioBlob"]; // Since current audio job can be cancelled, don't playback if it is null if (currentAudioJob == null) { console.log('Cancelled TTS playback because currentAudioJob was null'); } if (audioBlob instanceof Blob) { const srcUrl = await getBase64Async(audioBlob); + // VRM inject + if (extension_settings.vrm.enabled && typeof window['vrmLipSync'] === 'function') { + await window['vrmLipSync'](audioBlob, audioJob["char"]); + } audioElement.src = srcUrl; } else if (typeof audioBlob === 'string') { audioElement.src = audioBlob; @@ -418,15 +423,15 @@ function completeCurrentAudioJob() { * Accepts an HTTP response containing audio/mpeg data, and puts the data as a Blob() on the queue for playback * @param {Response} response */ -async function addAudioJob(response) { +async function addAudioJob(response, char) { if (typeof response === 'string') { - audioJobQueue.push(response); + audioJobQueue.push({"audioBlob":response, "char":char}); } else { const audioData = await response.blob(); if (!audioData.type.startsWith('audio/')) { throw `TTS received HTTP response with invalid data format. Expecting audio/*, got ${audioData.type}`; } - audioJobQueue.push(audioData); + audioJobQueue.push({"audioBlob":audioData, "char":char}); } console.debug('Pushed audio job to queue.'); } @@ -474,11 +479,12 @@ async function tts(text, voiceId, char) { if (extension_settings.rvc.enabled && typeof window['rvcVoiceConversion'] === 'function') response = await window['rvcVoiceConversion'](response, char, text); - // VRM injection - if (extension_settings.vrm.enabled && typeof window['vrmLipSync'] === 'function') + /*/ VRM injection + if (extension_settings.vrm.enabled && typeof window['vrmLipSync'] === 'function') { await window['vrmLipSync'](response, char); + }*/ - await addAudioJob(response); + await addAudioJob(response, char); } let response = await ttsProvider.generateTts(text, voiceId); From 7af43023b1258f4f64949c134cf3c203bc4f0f2a Mon Sep 17 00:00:00 2001 From: LenAnderson Date: Tue, 16 Jan 2024 21:04:25 +0000 Subject: [PATCH 009/151] init quick replies immediately --- public/scripts/extensions/quick-reply/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/scripts/extensions/quick-reply/index.js b/public/scripts/extensions/quick-reply/index.js index 376fd7152..c0142978b 100644 --- a/public/scripts/extensions/quick-reply/index.js +++ b/public/scripts/extensions/quick-reply/index.js @@ -185,7 +185,7 @@ const init = async () => { await autoExec.handleStartup(); }; -eventSource.on(event_types.APP_READY, init); +await init(); const onChatChanged = async (chatIdx) => { log('CHAT_CHANGED', chatIdx); From d1966d9d5f7cf47aa2c229a1df94a7b9d57b525f Mon Sep 17 00:00:00 2001 From: LenAnderson Date: Tue, 16 Jan 2024 22:20:46 +0000 Subject: [PATCH 010/151] fix addvar on array using setGlobalVariable --- public/scripts/variables.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/scripts/variables.js b/public/scripts/variables.js index 2bbf275f6..ff9d6bd42 100644 --- a/public/scripts/variables.js +++ b/public/scripts/variables.js @@ -116,7 +116,7 @@ function addLocalVariable(name, value) { const parsedValue = JSON.parse(currentValue); if (Array.isArray(parsedValue)) { parsedValue.push(value); - setGlobalVariable(name, JSON.stringify(parsedValue)); + setLocalVariable(name, JSON.stringify(parsedValue)); return parsedValue; } } catch { From acaae1b9735057c4e054900a03ca9d0a04c231d5 Mon Sep 17 00:00:00 2001 From: LenAnderson Date: Tue, 16 Jan 2024 22:36:55 +0000 Subject: [PATCH 011/151] fix type in helpstring --- .../scripts/extensions/quick-reply/src/SlashCommandHandler.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/scripts/extensions/quick-reply/src/SlashCommandHandler.js b/public/scripts/extensions/quick-reply/src/SlashCommandHandler.js index 5a7ac617e..eb1b81733 100644 --- a/public/scripts/extensions/quick-reply/src/SlashCommandHandler.js +++ b/public/scripts/extensions/quick-reply/src/SlashCommandHandler.js @@ -35,7 +35,7 @@ export class SlashCommandHandler { user - bool - auto execute on user message, e.g., user=true bot - bool - auto execute on AI message, e.g., bot=true load - bool - auto execute on chat load, e.g., load=true - title - bool - title / tooltip to be shown on button, e.g., title="My Fancy Button" + title - string - title / tooltip to be shown on button, e.g., title="My Fancy Button" `.trim(); const qrUpdateArgs = ` newlabel - string - new text for the button, e.g. newlabel=MyRenamedButton From 9f4ae351dbbf5823690654eab98c46eddcd25fa7 Mon Sep 17 00:00:00 2001 From: LenAnderson Date: Tue, 16 Jan 2024 22:37:12 +0000 Subject: [PATCH 012/151] fix bools in /qr-update --- .../extensions/quick-reply/src/SlashCommandHandler.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/public/scripts/extensions/quick-reply/src/SlashCommandHandler.js b/public/scripts/extensions/quick-reply/src/SlashCommandHandler.js index eb1b81733..4fc3cfbb1 100644 --- a/public/scripts/extensions/quick-reply/src/SlashCommandHandler.js +++ b/public/scripts/extensions/quick-reply/src/SlashCommandHandler.js @@ -1,4 +1,5 @@ import { registerSlashCommand } from '../../../slash-commands.js'; +import { isTrueBoolean } from '../../../utils.js'; // eslint-disable-next-line no-unused-vars import { QuickReplyApi } from '../api/QuickReplyApi.js'; @@ -162,11 +163,11 @@ export class SlashCommandHandler { newLabel: args.newlabel, message: (message ?? '').trim().length > 0 ? message : undefined, title: args.title, - isHidden: args.hidden, - executeOnStartup: args.startup, - executeOnUser: args.user, - executeOnAi: args.bot, - executeOnChatChange: args.load, + isHidden: args.hidden === undefined ? undefined : isTrueBoolean(args.hidden), + executeOnStartup: args.startup === undefined ? undefined : isTrueBoolean(args.startup), + executeOnUser: args.user === undefined ? undefined : isTrueBoolean(args.user), + executeOnAi: args.bot === undefined ? undefined : isTrueBoolean(args.bot), + executeOnChatChange: args.load === undefined ? undefined : isTrueBoolean(args.load), }, ); } catch (ex) { From 1a50c9f976416f175818199253a9b40f180b3960 Mon Sep 17 00:00:00 2001 From: LenAnderson Date: Tue, 16 Jan 2024 22:40:40 +0000 Subject: [PATCH 013/151] use isTrueBoolean on all bools --- .../quick-reply/src/SlashCommandHandler.js | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/public/scripts/extensions/quick-reply/src/SlashCommandHandler.js b/public/scripts/extensions/quick-reply/src/SlashCommandHandler.js index 4fc3cfbb1..857714c0c 100644 --- a/public/scripts/extensions/quick-reply/src/SlashCommandHandler.js +++ b/public/scripts/extensions/quick-reply/src/SlashCommandHandler.js @@ -91,14 +91,14 @@ export class SlashCommandHandler { toggleGlobalSet(name, args = {}) { try { - this.api.toggleGlobalSet(name, JSON.parse(args.visible ?? 'true') === true); + this.api.toggleGlobalSet(name, isTrueBoolean(args.visible ?? 'true')); } catch (ex) { toastr.error(ex.message); } } addGlobalSet(name, args = {}) { try { - this.api.addGlobalSet(name, JSON.parse(args.visible ?? 'true') === true); + this.api.addGlobalSet(name, isTrueBoolean(args.visible ?? 'true')); } catch (ex) { toastr.error(ex.message); } @@ -114,14 +114,14 @@ export class SlashCommandHandler { toggleChatSet(name, args = {}) { try { - this.api.toggleChatSet(name, JSON.parse(args.visible ?? 'true') === true); + this.api.toggleChatSet(name, isTrueBoolean(args.visible ?? 'true')); } catch (ex) { toastr.error(ex.message); } } addChatSet(name, args = {}) { try { - this.api.addChatSet(name, JSON.parse(args.visible ?? 'true') === true); + this.api.addChatSet(name, isTrueBoolean(args.visible ?? 'true')); } catch (ex) { toastr.error(ex.message); } @@ -143,11 +143,11 @@ export class SlashCommandHandler { { message: message ?? '', title: args.title, - isHidden: JSON.parse(args.hidden ?? 'false') === true, - executeOnStartup: JSON.parse(args.startup ?? 'false') === true, - executeOnUser: JSON.parse(args.user ?? 'false') === true, - executeOnAi: JSON.parse(args.bot ?? 'false') === true, - executeOnChatChange: JSON.parse(args.load ?? 'false') === true, + isHidden: isTrueBoolean(args.hidden), + executeOnStartup: isTrueBoolean(args.startup), + executeOnUser: isTrueBoolean(args.user), + executeOnAi: isTrueBoolean(args.bot), + executeOnChatChange: isTrueBoolean(args.load), }, ); } catch (ex) { @@ -189,7 +189,7 @@ export class SlashCommandHandler { args.set, args.label, name, - JSON.parse(args.chain ?? 'false') === true, + isTrueBoolean(args.chain), ); } catch (ex) { toastr.error(ex.message); @@ -216,9 +216,9 @@ export class SlashCommandHandler { this.api.createSet( args.name ?? name ?? '', { - disableSend: JSON.parse(args.nosend ?? 'false') === true, - placeBeforeInput: JSON.parse(args.before ?? 'false') === true, - injectInput: JSON.parse(args.inject ?? 'false') === true, + disableSend: isTrueBoolean(args.nosend), + placeBeforeInput: isTrueBoolean(args.before), + injectInput: isTrueBoolean(args.inject), }, ); } catch (ex) { @@ -230,9 +230,9 @@ export class SlashCommandHandler { this.api.updateSet( args.name ?? name ?? '', { - disableSend: args.nosend !== undefined ? JSON.parse(args.nosend ?? 'false') === true : undefined, - placeBeforeInput: args.before !== undefined ? JSON.parse(args.before ?? 'false') === true : undefined, - injectInput: args.inject !== undefined ? JSON.parse(args.inject ?? 'false') === true : undefined, + disableSend: args.nosend !== undefined ? isTrueBoolean(args.nosend) : undefined, + placeBeforeInput: args.before !== undefined ? isTrueBoolean(args.before) : undefined, + injectInput: args.inject !== undefined ? isTrueBoolean(args.inject) : undefined, }, ); } catch (ex) { From 7ceaeed9adf06ca5597da805faf69f45582c9fc2 Mon Sep 17 00:00:00 2001 From: RossAscends <124905043+RossAscends@users.noreply.github.com> Date: Wed, 17 Jan 2024 17:12:43 +0900 Subject: [PATCH 014/151] filter DynTemp UI display to ooba and kcpp only for now --- public/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/index.html b/public/index.html index cc468feae..b28e5cb35 100644 --- a/public/index.html +++ b/public/index.html @@ -1302,7 +1302,7 @@
--> -
+

From 6fe17a1bed99ca8b7d8468f47bfdfa71cbe5c235 Mon Sep 17 00:00:00 2001 From: LenAnderson Date: Wed, 17 Jan 2024 20:32:25 +0000 Subject: [PATCH 015/151] queue all auto-executes until APP_READY --- .../scripts/extensions/quick-reply/index.js | 33 ++++++++++++++++--- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/public/scripts/extensions/quick-reply/index.js b/public/scripts/extensions/quick-reply/index.js index c0142978b..48d200ae3 100644 --- a/public/scripts/extensions/quick-reply/index.js +++ b/public/scripts/extensions/quick-reply/index.js @@ -58,6 +58,10 @@ const defaultSettings = { }; +/** @type {Boolean}*/ +let isReady = false; +/** @type {Function[]}*/ +let executeQueue = []; /** @type {QuickReplySettings}*/ let settings; /** @type {SettingsUi} */ @@ -144,6 +148,16 @@ const loadSettings = async () => { } }; +const executeIfReadyElseQueue = async (functionToCall, args) => { + if (isReady) { + log('calling', { functionToCall, args }); + await functionToCall(...args); + } else { + log('queueing', { functionToCall, args }); + executeQueue.push(async()=>await functionToCall(...args)); + } +}; + @@ -183,9 +197,20 @@ const init = async () => { slash.init(); autoExec = new AutoExecuteHandler(settings); + log('executing startup'); await autoExec.handleStartup(); + log('/executing startup'); + + log(`executing queue (${executeQueue.length} items)`); + while (executeQueue.length > 0) { + const func = executeQueue.shift(); + await func(); + } + log('/executing queue'); + isReady = true; + log('READY'); }; -await init(); +init(); const onChatChanged = async (chatIdx) => { log('CHAT_CHANGED', chatIdx); @@ -199,14 +224,14 @@ const onChatChanged = async (chatIdx) => { await autoExec.handleChatChanged(); }; -eventSource.on(event_types.CHAT_CHANGED, onChatChanged); +eventSource.on(event_types.CHAT_CHANGED, (...args)=>executeIfReadyElseQueue(onChatChanged, args)); const onUserMessage = async () => { await autoExec.handleUser(); }; -eventSource.on(event_types.USER_MESSAGE_RENDERED, onUserMessage); +eventSource.on(event_types.USER_MESSAGE_RENDERED, (...args)=>executeIfReadyElseQueue(onUserMessage, args)); const onAiMessage = async () => { await autoExec.handleAi(); }; -eventSource.on(event_types.CHARACTER_MESSAGE_RENDERED, onAiMessage); +eventSource.on(event_types.CHARACTER_MESSAGE_RENDERED, (...args)=>executeIfReadyElseQueue(onAiMessage, args)); From 12a40c25a07c03cfc5203690dbc14b59d32ca58b Mon Sep 17 00:00:00 2001 From: LenAnderson Date: Wed, 17 Jan 2024 20:41:59 +0000 Subject: [PATCH 016/151] fix QR settings UI out of sync after update via API --- public/scripts/extensions/quick-reply/api/QuickReplyApi.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/public/scripts/extensions/quick-reply/api/QuickReplyApi.js b/public/scripts/extensions/quick-reply/api/QuickReplyApi.js index 712b10d58..ddb12c29e 100644 --- a/public/scripts/extensions/quick-reply/api/QuickReplyApi.js +++ b/public/scripts/extensions/quick-reply/api/QuickReplyApi.js @@ -248,9 +248,9 @@ export class QuickReplyApi { if (!qr) { throw new Error(`No quick reply with label "${label}" in set "${setName}" found.`); } - qr.label = newLabel ?? qr.label; - qr.message = message ?? qr.message; - qr.title = title ?? qr.title; + qr.updateLabel(newLabel ?? qr.label); + qr.updateMessage(message ?? qr.message); + qr.updateTitle(title ?? qr.title); qr.isHidden = isHidden ?? qr.isHidden; qr.executeOnStartup = executeOnStartup ?? qr.executeOnStartup; qr.executeOnUser = executeOnUser ?? qr.executeOnUser; From 3af2164187c13d817623125f9785afd76e0761c3 Mon Sep 17 00:00:00 2001 From: erew123 <35898566+erew123@users.noreply.github.com> Date: Wed, 17 Jan 2024 21:55:24 +0000 Subject: [PATCH 017/151] AllTalk Updates Streaming passed URL to global ST audio. Localstorage removed for saving TTS elements. Styles stored in CSS Duplicate checks on fetchresponse removed. --- public/scripts/extensions/tts/alltalk.js | 315 ++-- public/scripts/extensions/tts/index.js | 2094 +++++++++++----------- public/scripts/extensions/tts/style.css | 69 + 3 files changed, 1220 insertions(+), 1258 deletions(-) diff --git a/public/scripts/extensions/tts/alltalk.js b/public/scripts/extensions/tts/alltalk.js index e44b31240..f70daf5ad 100644 --- a/public/scripts/extensions/tts/alltalk.js +++ b/public/scripts/extensions/tts/alltalk.js @@ -3,92 +3,33 @@ import { saveTtsProviderSettings } from './index.js'; export { AllTalkTtsProvider }; -const css = ` -.settings-row { - display: flex; - justify-content: space-between; /* Distribute space evenly between the children */ - align-items: center; - width: 100%; /* Full width to spread out the children */ -} - -.settings-option { - flex: 1; /* Each child will take equal space */ - margin: 0 10px; /* Adjust spacing as needed */ -} - -.endpoint-option { - flex: 1; /* Each child will take equal space */ - margin: 0 10px; /* Adjust spacing as needed */ - margin-right: 25px; - width: 38%; /* Full width to spread out the children */ -} - -.website-row { - display: flex; - justify-content: start; /* Aligns items to the start of the row */ - align-items: center; - margin-top: 10px; /* Top margin */ - margin-bottom: 10px; /* Bottom margin */ -} - -.website-option { - flex: 1; /* Allows each option to take equal space */ - margin-right: 10px; /* Spacing between the two options, adjust as needed */ - margin-left: 10px; /* Adjust this value to align as desired */ - /* Remove the individual margin top and bottom declarations if they are set globally on the .settings-row */ -} - -.settings-separator { - margin-top: 10px; - margin-bottom: 10px; - padding: 18x; - font-weight: bold; - border-top: 1px solid #e1e1e1; /* Grey line */ - border-bottom: 1px solid #e1e1e1; /* Grey line */ - text-align: center; -} - -.status-message { - flex: 1; /* Adjust as needed */ - margin: 0 10px; - /* Additional styling */ -} - -.model-endpoint-row { - display: flex; - justify-content: space-between; - align-items: center; - width: 100%; -} - -.model-option, .endpoint-option { - flex: 1; - margin: 0 10px; - margin-left: 10px; /* Adjust this value to align as desired */ - /* Adjust width as needed to fit in one line */ -} - -.endpoint-option { - /* Adjust width to align with model dropdown */ - width: 38%; -} - -#status_info { - color: lightgreen; /* Or any specific shade of green you prefer */ -} -`; - -const style = document.createElement('style'); -style.type = 'text/css'; -style.appendChild(document.createTextNode(css)); -document.head.appendChild(style); - class AllTalkTtsProvider { //########// // Config // //########// - settings; + settings = {}; + constructor() { + // Initialize with default settings if they are not already set + this.settings = { + provider_endpoint: this.settings.provider_endpoint || 'http://localhost:7851', + language: this.settings.language || 'en', + voiceMap: this.settings.voiceMap || {}, + at_generation_method: this.settings.at_generation_method || 'standard_generation', + narrator_enabled: this.settings.narrator_enabled || 'false', + at_narrator_text_not_inside: this.settings.at_narrator_text_not_inside || 'narrator', + narrator_voice_gen: this.settings.narrator_voice_gen || 'female_01.wav', + finetuned_model: this.settings.finetuned_model || 'false' + }; + // Separate property for dynamically updated settings from the server + this.dynamicSettings = { + modelsAvailable: [], + currentModel: '', + deepspeed_available: false, + deepSpeedEnabled: false, + lowVramEnabled: false, + }; + } ready = false; voices = []; separator = '. '; @@ -114,23 +55,12 @@ class AllTalkTtsProvider { 'Hindi': 'hi', }; - Settings = { - provider_endpoint: 'http://localhost:7851', - language: 'en', - voiceMap: {}, - at_generation_method: 'standard_generation', - narrator_enabled: 'false', - at_narrator_text_not_inside: 'narrator', - narrator_voice_gen: 'female_01.wav', - finetuned_model: 'false' - }; - get settingsHtml() { - let html = `
AllTalk Settings
`; + let html = `
AllTalk Settings
`; - html += `
+ html += `
-
+
@@ -149,7 +79,7 @@ class AllTalkTtsProvider {
-
+
`; if (this.voices) { @@ -170,7 +100,7 @@ class AllTalkTtsProvider { } html += `
-
+
@@ -191,49 +121,47 @@ class AllTalkTtsProvider {
-
+
- +
`; - html += `
-
+ html += `
+
-
+
-
+
Status: Ready
-
+
`; - html += `
-
- AllTalk Config & Docs. + html += `
+
+ AllTalk Config & Docs.
-
+
AllTalk Website.
`; -html += `
-
+ html += `
+
Text-generation-webui users - Uncheck Enable TTS in Text-generation-webui.
`; - - return html; } @@ -244,14 +172,35 @@ html += `
async loadSettings(settings) { updateStatus('Offline'); - // Use default settings if no settings are provided - this.settings = Object.keys(settings).length === 0 ? this.Settings : settings; + if (Object.keys(settings).length === 0) { + console.info('Using default AllTalk TTS Provider settings'); + } else { + // Populate settings with provided values, ignoring server-provided settings + for (const key in settings) { + if (key in this.settings) { + this.settings[key] = settings[key]; + } else { + console.debug(`Ignoring non-user-configurable setting: ${key}`); + } + } + } + + // Update UI elements to reflect the loaded settings + $('#at_server').val(this.settings.provider_endpoint); + $('#language_options').val(this.settings.language); + //$('#voicemap').val(this.settings.voiceMap); + $('#at_generation_method').val(this.settings.at_generation_method); + $('#at_narrator_enabled').val(this.settings.narrator_enabled); + $('#at_narrator_text_not_inside').val(this.settings.at_narrator_text_not_inside); + $('#narrator_voice').val(this.settings.narrator_voice_gen); + + console.debug('AllTalkTTS: Settings loaded'); try { // Check if TTS provider is ready await this.checkReady(); + await this.updateSettingsFromServer(); // Fetch dynamic settings from the TTS server await this.fetchTtsVoiceObjects(); // Fetch voices only if service is ready - await this.updateSettingsFromServer(); this.updateNarratorVoicesDropdown(); this.updateLanguageDropdown(); this.setupEventListeners(); @@ -259,29 +208,23 @@ html += `
updateStatus('Ready'); } catch (error) { console.error("Error loading settings:", error); - // Set status to Error if there is an issue in loading settings updateStatus('Offline'); } } applySettingsToHTML() { - // Load AllTalk specific settings first - const loadedSettings = loadAllTalkSettings(); // Apply loaded settings or use defaults - this.settings = loadedSettings ? { ...this.Settings, ...loadedSettings } : this.Settings; const narratorVoiceSelect = document.getElementById('narrator_voice'); const atNarratorSelect = document.getElementById('at_narrator_enabled'); const textNotInsideSelect = document.getElementById('at_narrator_text_not_inside'); const generationMethodSelect = document.getElementById('at_generation_method'); + this.settings.narrator_voice = this.settings.narrator_voice_gen; // Apply settings to Narrator Voice dropdown if (narratorVoiceSelect && this.settings.narrator_voice) { narratorVoiceSelect.value = this.settings.narrator_voice.replace('.wav', ''); - this.settings.narrator_voice_gen = this.settings.narrator_voice; - //console.log(this.settings.narrator_voice_gen) } // Apply settings to AT Narrator Enabled dropdown if (atNarratorSelect) { - //console.log(this.settings.narrator_enabled) // Sync the state with the checkbox in index.js const ttsPassAsterisksCheckbox = document.getElementById('tts_pass_asterisks'); // Access the checkbox from index.js const ttsNarrateQuotedCheckbox = document.getElementById('tts_narrate_quoted'); // Access the checkbox from index.js @@ -314,18 +257,15 @@ html += `
const languageSelect = document.getElementById('language_options'); if (languageSelect && this.settings.language) { languageSelect.value = this.settings.language; - //console.log(this.settings.language) } // Apply settings to Text Not Inside dropdown if (textNotInsideSelect && this.settings.text_not_inside) { textNotInsideSelect.value = this.settings.text_not_inside; this.settings.at_narrator_text_not_inside = this.settings.text_not_inside; - //console.log(this.settings.at_narrator_text_not_inside) } // Apply settings to Generation Method dropdown if (generationMethodSelect && this.settings.at_generation_method) { generationMethodSelect.value = this.settings.at_generation_method; - //console.log(this.settings.at_generation_method) } // Additional logic to disable/enable dropdowns based on the selected generation method const isStreamingEnabled = this.settings.at_generation_method === 'streaming_enabled'; @@ -347,7 +287,6 @@ html += `
ftOption.textContent = 'XTTSv2 FT'; modelSelect.appendChild(ftOption); } - //console.log("Settings applied to HTML."); } //##############################// @@ -486,7 +425,7 @@ html += `
const languageSelect = document.getElementById('language_options'); if (languageSelect) { // Ensure default language is set - this.settings.language = this.Settings.language; + this.settings.language = this.settings.language; languageSelect.innerHTML = ''; for (let language in this.languageLabels) { @@ -610,7 +549,7 @@ html += `
if (narratorVoiceSelect) { narratorVoiceSelect.addEventListener('change', (event) => { this.settings.narrator_voice_gen = `${event.target.value}.wav`; - this.onSettingsChangeAllTalk(); // Save the settings after change + this.onSettingsChange(); // Save the settings after change }); } @@ -618,7 +557,7 @@ html += `
if (textNotInsideSelect) { textNotInsideSelect.addEventListener('change', (event) => { this.settings.text_not_inside = event.target.value; - this.onSettingsChangeAllTalk(); // Save the settings after change + this.onSettingsChange(); // Save the settings after change }); } @@ -656,12 +595,11 @@ html += `
$('#tts_narrate_dialogues').click(); $('#tts_narrate_dialogues').trigger('change'); } - this.onSettingsChangeAllTalk(); // Save the settings after change + this.onSettingsChange(); // Save the settings after change }); } - // Event Listener for AT Generation Method Dropdown const atGenerationMethodSelect = document.getElementById('at_generation_method'); const atNarratorEnabledSelect = document.getElementById('at_narrator_enabled'); @@ -680,7 +618,7 @@ html += `
atNarratorEnabledSelect.disabled = false; } this.settings.at_generation_method = selectedMethod; // Update the setting here - this.onSettingsChangeAllTalk(); // Save the settings after change + this.onSettingsChange(); // Save the settings after change }); } @@ -689,9 +627,19 @@ html += `
if (languageSelect) { languageSelect.addEventListener('change', (event) => { this.settings.language = event.target.value; - this.onSettingsChangeAllTalk(); // Save the settings after change + this.onSettingsChange(); // Save the settings after change }); } + + // Listener for AllTalk Endpoint Input + const atServerInput = document.getElementById('at_server'); + if (atServerInput) { + atServerInput.addEventListener('input', (event) => { + this.settings.provider_endpoint = event.target.value; + this.onSettingsChange(); // Save the settings after change + }); + } + } //#############################// @@ -699,30 +647,16 @@ html += `
//#############################// onSettingsChange() { - // Update settings that SillyTavern will save - this.settings.provider_endpoint = $('#at_server').val(); + // Update settings based on the UI elements + //this.settings.provider_endpoint = $('#at_server').val(); this.settings.language = $('#language_options').val(); - saveTtsProviderSettings(); // This function should save settings handled by SillyTavern - // Call the function to handle AllTalk specific settings - this.onSettingsChangeAllTalk(); // Save the settings after change - } - - onSettingsChangeAllTalk() { - // Update AllTalk specific settings and save to localStorage - this.settings.narrator_enabled = $('#at_narrator_enabled').val() === 'true'; - this.settings.narrator_voice = $('#narrator_voice').val() + '.wav'; // assuming you need to append .wav - this.settings.text_not_inside = $('#at_narrator_text_not_inside').val(); // "character" or "narrator" - this.settings.at_generation_method = $('#at_generation_method').val(); // Streaming or standard - this.settings.language = $('#language_options').val(); // Streaming or standard - - // Call the save function with the current settings - saveAllTalkSettings({ - narrator_voice: this.settings.narrator_voice, - narrator_enabled: this.settings.narrator_enabled, - text_not_inside: this.settings.text_not_inside, - at_generation_method: this.settings.at_generation_method, - language: this.settings.language - }); + //this.settings.voiceMap = $('#voicemap').val(); + this.settings.at_generation_method = $('#at_generation_method').val(); + this.settings.narrator_enabled = $('#at_narrator_enabled').val(); + this.settings.at_narrator_text_not_inside = $('#at_narrator_text_not_inside').val(); + this.settings.narrator_voice_gen = $('#narrator_voice').val(); + // Save the updated settings + saveTtsProviderSettings(); } //#########################// @@ -802,38 +736,20 @@ html += `
async generateTts(inputText, voiceId) { try { if (this.settings.at_generation_method === 'streaming_enabled') { - // For streaming method + // Construct the streaming URL const streamingUrl = `${this.settings.provider_endpoint}/api/tts-generate-streaming?text=${encodeURIComponent(inputText)}&voice=${encodeURIComponent(voiceId)}.wav&language=${encodeURIComponent(this.settings.language)}&output_file=stream_output.wav`; - const audioElement = new Audio(streamingUrl); - audioElement.play(); // Play the audio stream directly console.log("Streaming URL:", streamingUrl); - return new Response(null, { - status: 200, - statusText: "OK", - headers: { - "Content-Type": "audio/wav", - "Content-Location": streamingUrl - } - }); + + // Return the streaming URL directly + return streamingUrl; } else { // For standard method const outputUrl = await this.fetchTtsGeneration(inputText, voiceId); - // Fetch the audio data as a blob from the URL const audioResponse = await fetch(outputUrl); if (!audioResponse.ok) { throw new Error(`HTTP ${audioResponse.status}: Failed to fetch audio data`); } - const audioBlob = await audioResponse.blob(); - if (!audioBlob.type.startsWith('audio/')) { - throw new Error(`Invalid audio data format. Expecting audio/*, got ${audioBlob.type}`); - } - return new Response(audioBlob, { - status: 200, - statusText: "OK", - headers: { - "Content-Type": audioBlob.type - } - }); + return audioResponse; // Return the fetch response directly } } catch (error) { console.error("Error in generateTts:", error); @@ -841,6 +757,7 @@ html += `
} } + //####################// // Generate Standard // //####################// @@ -852,8 +769,8 @@ html += `
'text_filtering': "standard", 'character_voice_gen': voiceId + ".wav", 'narrator_enabled': this.settings.narrator_enabled, - 'narrator_voice_gen': this.settings.narrator_voice_gen, - 'text_not_inside': this.settings.text_not_inside, + 'narrator_voice_gen': this.settings.narrator_voice_gen + ".wav", + 'text_not_inside': this.settings.at_narrator_text_not_inside, 'language': this.settings.language, 'output_file_name': "st_output", 'output_file_timestamp': "true", @@ -877,7 +794,6 @@ html += `
if (!response.ok) { const errorText = await response.text(); console.error(`[fetchTtsGeneration] Error Response Text:`, errorText); - // Uncomment the following line if you have a UI element for displaying errors // toastr.error(response.statusText, 'TTS Generation Failed'); throw new Error(`HTTP ${response.status}: ${errorText}`); } @@ -912,27 +828,6 @@ function updateStatus(message) { case 'Error': statusElement.style.color = 'red'; break; - // Add more cases as needed } } -} - -//########################// -// Save/load AT Settings // -//########################// - -function saveAllTalkSettings(settingsToSave) { - // Save the specific settings to localStorage - console.log("settings", settingsToSave) - localStorage.setItem('AllTalkSettings', JSON.stringify(settingsToSave)); -} - -function loadAllTalkSettings() { - // Retrieve the settings from localStorage - const settings = localStorage.getItem('AllTalkSettings'); - // If settings exist, parse them back into an object - if (settings) { - return JSON.parse(settings); - } - return null; -} +} \ No newline at end of file diff --git a/public/scripts/extensions/tts/index.js b/public/scripts/extensions/tts/index.js index 11f8ed4ec..dd84a22b5 100644 --- a/public/scripts/extensions/tts/index.js +++ b/public/scripts/extensions/tts/index.js @@ -1,1048 +1,1046 @@ -import { callPopup, cancelTtsPlay, eventSource, event_types, name2, saveSettingsDebounced } from '../../../script.js'; -import { ModuleWorkerWrapper, doExtrasFetch, extension_settings, getApiUrl, getContext, modules } from '../../extensions.js'; -import { delay, escapeRegex, getBase64Async, getStringHash, onlyUnique } from '../../utils.js'; -import { EdgeTtsProvider } from './edge.js'; -import { ElevenLabsTtsProvider } from './elevenlabs.js'; -import { SileroTtsProvider } from './silerotts.js'; -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'; -import { OpenAITtsProvider } from './openai.js'; -import { XTTSTtsProvider } from './xtts.js'; -import { AllTalkTtsProvider } from './alltalk.js'; -export { talkingAnimation }; - -const UPDATE_INTERVAL = 1000; - -let voiceMapEntries = []; -let voiceMap = {}; // {charName:voiceid, charName2:voiceid2} -let storedvalue = false; -let lastChatId = null; -let lastMessageHash = null; - -const DEFAULT_VOICE_MARKER = '[Default Voice]'; -const DISABLED_VOICE_MARKER = 'disabled'; - -export function getPreviewString(lang) { - const previewStrings = { - 'en-US': 'The quick brown fox jumps over the lazy dog', - 'en-GB': 'Sphinx of black quartz, judge my vow', - 'fr-FR': 'Portez ce vieux whisky au juge blond qui fume', - 'de-DE': 'Victor jagt zwölf Boxkämpfer quer über den großen Sylter Deich', - 'it-IT': 'Pranzo d\'acqua fa volti sghembi', - 'es-ES': 'Quiere la boca exhausta vid, kiwi, piña y fugaz jamón', - 'es-MX': 'Fabio me exige, sin tapujos, que añada cerveza al whisky', - 'ru-RU': 'В чащах юга жил бы цитрус? Да, но фальшивый экземпляр!', - 'pt-BR': 'Vejo xá gritando que fez show sem playback.', - 'pt-PR': 'Todo pajé vulgar faz boquinha sexy com kiwi.', - 'uk-UA': 'Фабрикуймо гідність, лящім їжею, ґав хапаймо, з\'єднавці чаш!', - 'pl-PL': 'Pchnąć w tę łódź jeża lub ośm skrzyń fig', - 'cs-CZ': 'Příliš žluťoučký kůň úpěl ďábelské ódy', - 'sk-SK': 'Vyhŕňme si rukávy a vyprážajme čínske ryžové cestoviny', - 'hu-HU': 'Árvíztűrő tükörfúrógép', - 'tr-TR': 'Pijamalı hasta yağız şoföre çabucak güvendi', - 'nl-NL': 'De waard heeft een kalfje en een pinkje opgegeten', - 'sv-SE': 'Yxskaftbud, ge vårbygd, zinkqvarn', - 'da-DK': 'Quizdeltagerne spiste jordbær med fløde, mens cirkusklovnen Walther spillede på xylofon', - 'ja-JP': 'いろはにほへと ちりぬるを わかよたれそ つねならむ うゐのおくやま けふこえて あさきゆめみし ゑひもせす', - 'ko-KR': '가나다라마바사아자차카타파하', - 'zh-CN': '我能吞下玻璃而不伤身体', - 'ro-RO': 'Muzicologă în bej vând whisky și tequila, preț fix', - 'bg-BG': 'Щъркелите се разпръснаха по цялото небе', - 'el-GR': 'Ταχίστη αλώπηξ βαφής ψημένη γη, δρασκελίζει υπέρ νωθρού κυνός', - 'fi-FI': 'Voi veljet, miksi juuri teille myin nämä vehkeet?', - 'he-IL': 'הקצינים צעקו: "כל הכבוד לצבא הצבאות!"', - 'id-ID': 'Jangkrik itu memang enak, apalagi kalau digoreng', - 'ms-MY': 'Muzik penyanyi wanita itu menggambarkan kehidupan yang penuh dengan duka nestapa', - 'th-TH': 'เป็นไงบ้างครับ ผมชอบกินข้าวผัดกระเพราหมูกรอบ', - 'vi-VN': 'Cô bé quàng khăn đỏ đang ngồi trên bãi cỏ xanh', - 'ar-SA': 'أَبْجَدِيَّة عَرَبِيَّة', - 'hi-IN': 'श्वेता ने श्वेता के श्वेते हाथों में श्वेता का श्वेता चावल पकड़ा', - }; - const fallbackPreview = 'Neque porro quisquam est qui dolorem ipsum quia dolor sit amet'; - - return previewStrings[lang] ?? fallbackPreview; -} - -let ttsProviders = { - ElevenLabs: ElevenLabsTtsProvider, - Silero: SileroTtsProvider, - XTTSv2: XTTSTtsProvider, - System: SystemTtsProvider, - Coqui: CoquiTtsProvider, - Edge: EdgeTtsProvider, - Novel: NovelTtsProvider, - OpenAI: OpenAITtsProvider, - AllTalk: AllTalkTtsProvider, -}; -let ttsProvider; -let ttsProviderName; - -let ttsLastMessage = null; - -async function onNarrateOneMessage() { - audioElement.src = '/sounds/silence.mp3'; - const context = getContext(); - const id = $(this).closest('.mes').attr('mesid'); - const message = context.chat[id]; - - if (!message) { - return; - } - - resetTtsPlayback(); - ttsJobQueue.push(message); - 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'); - $('body').toggleClass('tts', enabled); - if (!enabled) { - return; - } - - const context = getContext(); - const chat = context.chat; - - processTtsQueue(); - processAudioJobQueue(); - updateUiAudioPlayState(); - - // Auto generation is disabled - if (extension_settings.tts.auto_generation == false) { - return; - } - - // no characters or group selected - if (!context.groupId && context.characterId === undefined) { - return; - } - - // Chat changed - if ( - context.chatId !== lastChatId - ) { - currentMessageNumber = context.chat.length ? context.chat.length : 0; - saveLastValues(); - - // Force to speak on the first message in the new chat - if (context.chat.length === 1) { - lastMessageHash = -1; - } - - return; - } - - // take the count of messages - let lastMessageNumber = context.chat.length ? context.chat.length : 0; - - // There's no new messages - let diff = lastMessageNumber - currentMessageNumber; - let hashNew = getStringHash((chat.length && chat[chat.length - 1].mes) ?? ''); - - // if messages got deleted, diff will be < 0 - if (diff < 0) { - // necessary actions will be taken by the onChatDeleted() handler - return; - } - - // if no new messages, or same message, or same message hash, do nothing - if (diff == 0 && hashNew === lastMessageHash) { - return; - } - - // If streaming, wait for streaming to finish before processing new messages - if (context.streamingProcessor && !context.streamingProcessor.isFinished) { - return; - } - - // clone message object, as things go haywire if message object is altered below (it's passed by reference) - const message = structuredClone(chat[chat.length - 1]); - - // if last message within current message, message got extended. only send diff to TTS. - if (ttsLastMessage !== null && message.mes.indexOf(ttsLastMessage) !== -1) { - let tmp = message.mes; - message.mes = message.mes.replace(ttsLastMessage, ''); - ttsLastMessage = tmp; - } else { - ttsLastMessage = message.mes; - } - - // We're currently swiping. Don't generate voice - if (!message || message.mes === '...' || message.mes === '') { - return; - } - - // Don't generate if message doesn't have a display text - if (extension_settings.tts.narrate_translated_only && !(message?.extra?.display_text)) { - return; - } - - // Don't generate if message is a user message and user message narration is disabled - if (message.is_user && !extension_settings.tts.narrate_user) { - return; - } - - // New messages, add new chat to history - lastMessageHash = hashNew; - currentMessageNumber = lastMessageNumber; - - console.debug( - `Adding message from ${message.name} for TTS processing: "${message.mes}"`, - ); - ttsJobQueue.push(message); -} - -function talkingAnimation(switchValue) { - if (!modules.includes('talkinghead')) { - console.debug('Talking Animation module not loaded'); - return; - } - - const apiUrl = getApiUrl(); - const animationType = switchValue ? 'start' : 'stop'; - - if (switchValue !== storedvalue) { - try { - console.log(animationType + ' Talking Animation'); - doExtrasFetch(`${apiUrl}/api/talkinghead/${animationType}_talking`); - storedvalue = switchValue; // Update the storedvalue to the current switchValue - } catch (error) { - // Handle the error here or simply ignore it to prevent logging - } - } - updateUiAudioPlayState(); -} - -function resetTtsPlayback() { - // Stop system TTS utterance - cancelTtsPlay(); - - // Clear currently processing jobs - currentTtsJob = null; - currentAudioJob = null; - - // Reset audio element - audioElement.currentTime = 0; - audioElement.src = ''; - - // Clear any queue items - ttsJobQueue.splice(0, ttsJobQueue.length); - audioJobQueue.splice(0, audioJobQueue.length); - - // Set audio ready to process again - audioQueueProcessorReady = true; -} - -function isTtsProcessing() { - let processing = false; - - // Check job queues - if (ttsJobQueue.length > 0 || audioJobQueue.length > 0) { - processing = true; - } - // Check current jobs - if (currentTtsJob != null || currentAudioJob != null) { - processing = true; - } - return processing; -} - -function debugTtsPlayback() { - console.log(JSON.stringify( - { - 'ttsProviderName': ttsProviderName, - 'voiceMap': voiceMap, - 'currentMessageNumber': currentMessageNumber, - 'audioPaused': audioPaused, - 'audioJobQueue': audioJobQueue, - 'currentAudioJob': currentAudioJob, - 'audioQueueProcessorReady': audioQueueProcessorReady, - 'ttsJobQueue': ttsJobQueue, - 'currentTtsJob': currentTtsJob, - 'ttsConfig': extension_settings.tts, - }, - )); -} -window.debugTtsPlayback = debugTtsPlayback; - -//##################// -// Audio Control // -//##################// - -let audioElement = new Audio(); -audioElement.id = 'tts_audio'; -audioElement.autoplay = true; - -let audioJobQueue = []; -let currentAudioJob; -let audioPaused = false; -let audioQueueProcessorReady = true; - -async function playAudioData(audioBlob) { - // Since current audio job can be cancelled, don't playback if it is null - if (currentAudioJob == null) { - console.log('Cancelled TTS playback because currentAudioJob was null'); - } - if (audioBlob instanceof Blob) { - const srcUrl = await getBase64Async(audioBlob); - audioElement.src = srcUrl; - } else if (typeof audioBlob === 'string') { - audioElement.src = audioBlob; - } else { - throw `TTS received invalid audio data type ${typeof audioBlob}`; - } - audioElement.addEventListener('ended', completeCurrentAudioJob); - audioElement.addEventListener('canplay', () => { - console.debug('Starting TTS playback'); - audioElement.play(); - }); -} - -window['tts_preview'] = function (id) { - const audio = document.getElementById(id); - - if (audio && !$(audio).data('disabled')) { - audio.play(); - } - else { - ttsProvider.previewTtsVoice(id); - } -}; - -async function onTtsVoicesClick() { - let popupText = ''; - - try { - const voiceIds = await ttsProvider.fetchTtsVoiceObjects(); - - for (const voice of voiceIds) { - popupText += ` -
- ${voice.lang || ''} - ${voice.name} - -
`; - if (voice.preview_url) { - popupText += ``; - } - } - } catch { - popupText = 'Could not load voices list. Check your API key.'; - } - - callPopup(popupText, 'text'); -} - -function updateUiAudioPlayState() { - if (extension_settings.tts.enabled == true) { - $('#ttsExtensionMenuItem').show(); - let img; - // Give user feedback that TTS is active by setting the stop icon if processing or playing - if (!audioElement.paused || isTtsProcessing()) { - img = 'fa-solid fa-stop-circle extensionsMenuExtensionButton'; - } else { - img = 'fa-solid fa-circle-play extensionsMenuExtensionButton'; - } - $('#tts_media_control').attr('class', img); - } else { - $('#ttsExtensionMenuItem').hide(); - } -} - -function onAudioControlClicked() { - audioElement.src = '/sounds/silence.mp3'; - let context = getContext(); - // Not pausing, doing a full stop to anything TTS is doing. Better UX as pause is not as useful - if (!audioElement.paused || isTtsProcessing()) { - resetTtsPlayback(); - talkingAnimation(false); - } else { - // Default play behavior if not processing or playing is to play the last message. - ttsJobQueue.push(context.chat[context.chat.length - 1]); - } - updateUiAudioPlayState(); -} - -function addAudioControl() { - - $('#extensionsMenu').prepend(` -
-
- TTS Playback -
`); - $('#ttsExtensionMenuItem').attr('title', 'TTS play/pause').on('click', onAudioControlClicked); - updateUiAudioPlayState(); -} - -function completeCurrentAudioJob() { - audioQueueProcessorReady = true; - currentAudioJob = null; - talkingAnimation(false); //stop lip animation - // updateUiPlayState(); -} - -/** - * Accepts an HTTP response containing audio/mpeg data, and puts the data as a Blob() on the queue for playback - * @param {Response} response - */ -async function addAudioJob(response) { - if (typeof response === 'string') { - audioJobQueue.push(response); - } else { - const audioData = await response.blob(); - if (!audioData.type.startsWith('audio/')) { - throw `TTS received HTTP response with invalid data format. Expecting audio/*, got ${audioData.type}`; - } - audioJobQueue.push(audioData); - } - console.debug('Pushed audio job to queue.'); -} - -async function processAudioJobQueue() { - // Nothing to do, audio not completed, or audio paused - stop processing. - if (audioJobQueue.length == 0 || !audioQueueProcessorReady || audioPaused) { - return; - } - try { - audioQueueProcessorReady = false; - currentAudioJob = audioJobQueue.shift(); - playAudioData(currentAudioJob); - talkingAnimation(true); - } catch (error) { - console.error(error); - audioQueueProcessorReady = true; - } -} - -//################// -// TTS Control // -//################// - -let ttsJobQueue = []; -let currentTtsJob; // Null if nothing is currently being processed -let currentMessageNumber = 0; - -function completeTtsJob() { - console.info(`Current TTS job for ${currentTtsJob?.name} completed.`); - currentTtsJob = null; -} - -function saveLastValues() { - const context = getContext(); - lastChatId = context.chatId; - lastMessageHash = getStringHash( - (context.chat.length && context.chat[context.chat.length - 1].mes) ?? '', - ); -} - -async function tts(text, voiceId, char) { - async function processResponse(response) { - // RVC injection - if (extension_settings.rvc.enabled && typeof window['rvcVoiceConversion'] === 'function') - response = await window['rvcVoiceConversion'](response, char, text); - - await addAudioJob(response); - } - - let response = await ttsProvider.generateTts(text, voiceId); - - // If async generator, process every chunk as it comes in - if (typeof response[Symbol.asyncIterator] === 'function') { - for await (const chunk of response) { - await processResponse(chunk); - } - } else { - await processResponse(response); - } - - completeTtsJob(); -} - -async function processTtsQueue() { - // Called each moduleWorker iteration to pull chat messages from queue - if (currentTtsJob || ttsJobQueue.length <= 0 || audioPaused) { - return; - } - - console.debug('New message found, running TTS'); - currentTtsJob = ttsJobQueue.shift(); - let text = extension_settings.tts.narrate_translated_only ? (currentTtsJob?.extra?.display_text || currentTtsJob.mes) : currentTtsJob.mes; - - if (extension_settings.tts.skip_codeblocks) { - text = text.replace(/^\s{4}.*$/gm, '').trim(); - text = text.replace(/```.*?```/gs, '').trim(); - } - - if (!extension_settings.tts.pass_asterisks) { - text = extension_settings.tts.narrate_dialogues_only - ? text.replace(/\*[^*]*?(\*|$)/g, '').trim() // remove asterisks content - : text.replaceAll('*', '').trim(); // remove just the asterisks - } - - if (extension_settings.tts.narrate_quoted_only) { - const special_quotes = /[“”]/g; // Extend this regex to include other special quotes - text = text.replace(special_quotes, '"'); - const matches = text.match(/".*?"/g); // Matches text inside double quotes, non-greedily - const partJoiner = (ttsProvider?.separator || ' ... '); - text = matches ? matches.join(partJoiner) : text; - } - - if (typeof ttsProvider?.processText === 'function') { - text = await ttsProvider.processText(text); - } - - // Collapse newlines and spaces into single space - text = text.replace(/\s+/g, ' ').trim(); - - console.log(`TTS: ${text}`); - const char = currentTtsJob.name; - - // Remove character name from start of the line if power user setting is disabled - if (char && !power_user.allow_name2_display) { - const escapedChar = escapeRegex(char); - text = text.replace(new RegExp(`^${escapedChar}:`, 'gm'), ''); - } - - try { - if (!text) { - console.warn('Got empty text in TTS queue job.'); - completeTtsJob(); - return; - } - - const voiceMapEntry = voiceMap[char] === DEFAULT_VOICE_MARKER ? voiceMap[DEFAULT_VOICE_MARKER] : voiceMap[char]; - - if (!voiceMapEntry || voiceMapEntry === DISABLED_VOICE_MARKER) { - throw `${char} not in voicemap. Configure character in extension settings voice map`; - } - const voice = await ttsProvider.getVoice(voiceMapEntry); - const voiceId = voice.voice_id; - if (voiceId == null) { - toastr.error(`Specified voice for ${char} was not found. Check the TTS extension settings.`); - throw `Unable to attain voiceId for ${char}`; - } - tts(text, voiceId, char); - } catch (error) { - console.error(error); - currentTtsJob = null; - } -} - -// Secret function for now -async function playFullConversation() { - const context = getContext(); - const chat = context.chat; - ttsJobQueue = chat; -} -window.playFullConversation = playFullConversation; - -//#############################// -// Extension UI and Settings // -//#############################// - -function loadSettings() { - if (Object.keys(extension_settings.tts).length === 0) { - Object.assign(extension_settings.tts, defaultSettings); - } - for (const key in defaultSettings) { - if (!(key in extension_settings.tts)) { - extension_settings.tts[key] = defaultSettings[key]; - } - } - $('#tts_provider').val(extension_settings.tts.currentProvider); - $('#tts_enabled').prop( - 'checked', - extension_settings.tts.enabled, - ); - $('#tts_narrate_dialogues').prop('checked', extension_settings.tts.narrate_dialogues_only); - $('#tts_narrate_quoted').prop('checked', extension_settings.tts.narrate_quoted_only); - $('#tts_auto_generation').prop('checked', extension_settings.tts.auto_generation); - $('#tts_narrate_translated_only').prop('checked', extension_settings.tts.narrate_translated_only); - $('#tts_narrate_user').prop('checked', extension_settings.tts.narrate_user); - $('#tts_pass_asterisks').prop('checked', extension_settings.tts.pass_asterisks); - $('body').toggleClass('tts', extension_settings.tts.enabled); -} - -const defaultSettings = { - voiceMap: '', - ttsEnabled: false, - currentProvider: 'ElevenLabs', - auto_generation: true, - narrate_user: false, -}; - -function setTtsStatus(status, success) { - $('#tts_status').text(status); - if (success) { - $('#tts_status').removeAttr('style'); - } else { - $('#tts_status').css('color', 'red'); - } -} - -function onRefreshClick() { - Promise.all([ - ttsProvider.onRefreshClick(), - // updateVoiceMap() - ]).then(() => { - extension_settings.tts[ttsProviderName] = ttsProvider.settings; - saveSettingsDebounced(); - setTtsStatus('Successfully applied settings', true); - console.info(`Saved settings ${ttsProviderName} ${JSON.stringify(ttsProvider.settings)}`); - initVoiceMap(); - updateVoiceMap(); - }).catch(error => { - console.error(error); - setTtsStatus(error, false); - }); -} - -function onEnableClick() { - extension_settings.tts.enabled = $('#tts_enabled').is( - ':checked', - ); - updateUiAudioPlayState(); - saveSettingsDebounced(); -} - - -function onAutoGenerationClick() { - extension_settings.tts.auto_generation = !!$('#tts_auto_generation').prop('checked'); - saveSettingsDebounced(); -} - - -function onNarrateDialoguesClick() { - extension_settings.tts.narrate_dialogues_only = !!$('#tts_narrate_dialogues').prop('checked'); - saveSettingsDebounced(); - console.log("setting narrate changed", extension_settings.tts.narrate_dialogues_only) -} - -function onNarrateUserClick() { - extension_settings.tts.narrate_user = !!$('#tts_narrate_user').prop('checked'); - saveSettingsDebounced(); -} - -function onNarrateQuotedClick() { - extension_settings.tts.narrate_quoted_only = !!$('#tts_narrate_quoted').prop('checked'); - saveSettingsDebounced(); - console.log("setting narrate quoted changed", extension_settings.tts.narrate_quoted_only) -} - - -function onNarrateTranslatedOnlyClick() { - extension_settings.tts.narrate_translated_only = !!$('#tts_narrate_translated_only').prop('checked'); - saveSettingsDebounced(); -} - -function onSkipCodeblocksClick() { - extension_settings.tts.skip_codeblocks = !!$('#tts_skip_codeblocks').prop('checked'); - saveSettingsDebounced(); -} - -function onPassAsterisksClick() { - extension_settings.tts.pass_asterisks = !!$('#tts_pass_asterisks').prop('checked'); - saveSettingsDebounced(); - console.log("setting pass asterisks", extension_settings.tts.pass_asterisks) -} - -//##############// -// TTS Provider // -//##############// - -async function loadTtsProvider(provider) { - //Clear the current config and add new config - $('#tts_provider_settings').html(''); - - if (!provider) { - return; - } - - // Init provider references - extension_settings.tts.currentProvider = provider; - ttsProviderName = provider; - ttsProvider = new ttsProviders[provider]; - - // Init provider settings - $('#tts_provider_settings').append(ttsProvider.settingsHtml); - if (!(ttsProviderName in extension_settings.tts)) { - console.warn(`Provider ${ttsProviderName} not in Extension Settings, initiatilizing provider in settings`); - extension_settings.tts[ttsProviderName] = {}; - } - await ttsProvider.loadSettings(extension_settings.tts[ttsProviderName]); - await initVoiceMap(); -} - -function onTtsProviderChange() { - const ttsProviderSelection = $('#tts_provider').val(); - extension_settings.tts.currentProvider = ttsProviderSelection; - loadTtsProvider(ttsProviderSelection); -} - -// Ensure that TTS provider settings are saved to extension settings. -export function saveTtsProviderSettings() { - extension_settings.tts[ttsProviderName] = ttsProvider.settings; - updateVoiceMap(); - saveSettingsDebounced(); - console.info(`Saved settings ${ttsProviderName} ${JSON.stringify(ttsProvider.settings)}`); -} - - -//###################// -// voiceMap Handling // -//###################// - -async function onChatChanged() { - await resetTtsPlayback(); - const voiceMapInit = initVoiceMap(); - await Promise.race([voiceMapInit, delay(1000)]); - ttsLastMessage = null; -} - -async function onChatDeleted() { - const context = getContext(); - - // update internal references to new last message - lastChatId = context.chatId; - currentMessageNumber = context.chat.length ? context.chat.length : 0; - - // compare against lastMessageHash. If it's the same, we did not delete the last chat item, so no need to reset tts queue - let messageHash = getStringHash((context.chat.length && context.chat[context.chat.length - 1].mes) ?? ''); - if (messageHash === lastMessageHash) { - return; - } - lastMessageHash = messageHash; - ttsLastMessage = (context.chat.length && context.chat[context.chat.length - 1].mes) ?? ''; - - // stop any tts playback since message might not exist anymore - await resetTtsPlayback(); -} - -/** - * 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.filter(onlyUnique); - } - - let characters = []; - if (context.groupId === null) { - // Single char chat - characters.push(DEFAULT_VOICE_MARKER); - characters.push(context.name1); - characters.push(context.name2); - } else { - // Group chat - characters.push(DEFAULT_VOICE_MARKER); - characters.push(context.name1); - const group = context.groups.find(group => context.groupId == group.id); - for (let member of group.members) { - const character = context.characters.find(char => char.avatar == member); - if (character) { - characters.push(character.name); - } - } - } - return characters.filter(onlyUnique); -} - -function sanitizeId(input) { - // Remove any non-alphanumeric characters except underscore (_) and hyphen (-) - let sanitized = input.replace(/[^a-zA-Z0-9-_]/g, ''); - - // Ensure first character is always a letter - if (!/^[a-zA-Z]/.test(sanitized)) { - sanitized = 'element_' + sanitized; - } - - return sanitized; -} - -function parseVoiceMap(voiceMapString) { - let parsedVoiceMap = {}; - for (const [charName, voiceId] of voiceMapString - .split(',') - .map(s => s.split(':'))) { - if (charName && voiceId) { - parsedVoiceMap[charName.trim()] = voiceId.trim(); - } - } - return parsedVoiceMap; -} - - - -/** - * Apply voiceMap based on current voiceMapEntries - */ -function updateVoiceMap() { - const tempVoiceMap = {}; - for (const voice of voiceMapEntries) { - if (voice.voiceId === null) { - continue; - } - tempVoiceMap[voice.name] = voice.voiceId; - } - if (Object.keys(tempVoiceMap).length !== 0) { - voiceMap = tempVoiceMap; - console.log(`Voicemap updated to ${JSON.stringify(voiceMap)}`); - } - if (!extension_settings.tts[ttsProviderName].voiceMap) { - extension_settings.tts[ttsProviderName].voiceMap = {}; - } - Object.assign(extension_settings.tts[ttsProviderName].voiceMap, voiceMap); - saveSettingsDebounced(); -} - -class VoiceMapEntry { - name; - voiceId; - selectElement; - constructor(name, voiceId = DEFAULT_VOICE_MARKER) { - this.name = name; - this.voiceId = voiceId; - this.selectElement = null; - } - - addUI(voiceIds) { - let sanitizedName = sanitizeId(this.name); - let defaultOption = this.name === DEFAULT_VOICE_MARKER ? - `` : - ``; - let template = ` -
- ${this.name} - -
- `; - $('#tts_voicemap_block').append(template); - - // Populate voice ID select list - for (const voiceId of voiceIds) { - const option = document.createElement('option'); - option.innerText = voiceId.name; - option.value = voiceId.name; - $(`#tts_voicemap_char_${sanitizedName}_voice`).append(option); - } - - this.selectElement = $(`#tts_voicemap_char_${sanitizedName}_voice`); - this.selectElement.on('change', args => this.onSelectChange(args)); - this.selectElement.val(this.voiceId); - } - - onSelectChange(args) { - this.voiceId = this.selectElement.find(':selected').val(); - updateVoiceMap(); - } -} - -/** - * 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(unrestricted = false) { - // Gate initialization if not enabled or TTS Provider not ready. Prevents error popups. - const enabled = $('#tts_enabled').is(':checked'); - if (!enabled) { - return; - } - - // Keep errors inside extension UI rather than toastr. Toastr errors for TTS are annoying. - try { - await ttsProvider.checkReady(); - } catch (error) { - const message = `TTS Provider not ready. ${error}`; - setTtsStatus(message, false); - return; - } - - setTtsStatus('TTS Provider Loaded', true); - - // Clear existing voiceMap state - $('#tts_voicemap_block').empty(); - voiceMapEntries = []; - - // Get characters in current chat - const characters = getCharacters(unrestricted); - - // Get saved voicemap from provider settings, handling new and old representations - let voiceMapFromSettings = {}; - if ('voiceMap' in extension_settings.tts[ttsProviderName]) { - // Handle previous representation - if (typeof extension_settings.tts[ttsProviderName].voiceMap === 'string') { - voiceMapFromSettings = parseVoiceMap(extension_settings.tts[ttsProviderName].voiceMap); - // Handle new representation - } else if (typeof extension_settings.tts[ttsProviderName].voiceMap === 'object') { - voiceMapFromSettings = extension_settings.tts[ttsProviderName].voiceMap; - } - } - - // Get voiceIds from provider - let voiceIdsFromProvider; - try { - voiceIdsFromProvider = await ttsProvider.fetchTtsVoiceObjects(); - } - catch { - toastr.error('TTS Provider failed to return voice ids.'); - } - - // Build UI using VoiceMapEntry objects - for (const character of characters) { - if (character === 'SillyTavern System') { - continue; - } - // Check provider settings for voiceIds - let voiceId; - if (character in voiceMapFromSettings) { - voiceId = voiceMapFromSettings[character]; - } else if (character === DEFAULT_VOICE_MARKER) { - voiceId = DISABLED_VOICE_MARKER; - } else { - voiceId = DEFAULT_VOICE_MARKER; - } - const voiceMapEntry = new VoiceMapEntry(character, voiceId); - voiceMapEntry.addUI(voiceIdsFromProvider); - voiceMapEntries.push(voiceMapEntry); - } - updateVoiceMap(); -} - -$(document).ready(function () { - function addExtensionControls() { - const settingsHtml = ` -
-
-
- TTS -
-
-
-
-
- Select TTS Provider
-
- - -
-
- - - - - - - - -
-
-
-
-
-
-
- -
-
-
-
-
- `; - $('#extensions_settings').append(settingsHtml); - $('#tts_refresh').on('click', onRefreshClick); - $('#tts_enabled').on('click', onEnableClick); - $('#tts_narrate_dialogues').on('click', onNarrateDialoguesClick); - $('#tts_narrate_quoted').on('click', onNarrateQuotedClick); - $('#tts_narrate_translated_only').on('click', onNarrateTranslatedOnlyClick); - $('#tts_skip_codeblocks').on('click', onSkipCodeblocksClick); - $('#tts_pass_asterisks').on('click', onPassAsterisksClick); - $('#tts_auto_generation').on('click', onAutoGenerationClick); - $('#tts_narrate_user').on('click', onNarrateUserClick); - $('#tts_voices').on('click', onTtsVoicesClick); - for (const provider in ttsProviders) { - $('#tts_provider').append($('` : + ``; + let template = ` +
+ ${this.name} + +
+ `; + $('#tts_voicemap_block').append(template); + + // Populate voice ID select list + for (const voiceId of voiceIds) { + const option = document.createElement('option'); + option.innerText = voiceId.name; + option.value = voiceId.name; + $(`#tts_voicemap_char_${sanitizedName}_voice`).append(option); + } + + this.selectElement = $(`#tts_voicemap_char_${sanitizedName}_voice`); + this.selectElement.on('change', args => this.onSelectChange(args)); + this.selectElement.val(this.voiceId); + } + + onSelectChange(args) { + this.voiceId = this.selectElement.find(':selected').val(); + updateVoiceMap(); + } +} + +/** + * 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(unrestricted = false) { + // Gate initialization if not enabled or TTS Provider not ready. Prevents error popups. + const enabled = $('#tts_enabled').is(':checked'); + if (!enabled) { + return; + } + + // Keep errors inside extension UI rather than toastr. Toastr errors for TTS are annoying. + try { + await ttsProvider.checkReady(); + } catch (error) { + const message = `TTS Provider not ready. ${error}`; + setTtsStatus(message, false); + return; + } + + setTtsStatus('TTS Provider Loaded', true); + + // Clear existing voiceMap state + $('#tts_voicemap_block').empty(); + voiceMapEntries = []; + + // Get characters in current chat + const characters = getCharacters(unrestricted); + + // Get saved voicemap from provider settings, handling new and old representations + let voiceMapFromSettings = {}; + if ('voiceMap' in extension_settings.tts[ttsProviderName]) { + // Handle previous representation + if (typeof extension_settings.tts[ttsProviderName].voiceMap === 'string') { + voiceMapFromSettings = parseVoiceMap(extension_settings.tts[ttsProviderName].voiceMap); + // Handle new representation + } else if (typeof extension_settings.tts[ttsProviderName].voiceMap === 'object') { + voiceMapFromSettings = extension_settings.tts[ttsProviderName].voiceMap; + } + } + + // Get voiceIds from provider + let voiceIdsFromProvider; + try { + voiceIdsFromProvider = await ttsProvider.fetchTtsVoiceObjects(); + } + catch { + toastr.error('TTS Provider failed to return voice ids.'); + } + + // Build UI using VoiceMapEntry objects + for (const character of characters) { + if (character === 'SillyTavern System') { + continue; + } + // Check provider settings for voiceIds + let voiceId; + if (character in voiceMapFromSettings) { + voiceId = voiceMapFromSettings[character]; + } else if (character === DEFAULT_VOICE_MARKER) { + voiceId = DISABLED_VOICE_MARKER; + } else { + voiceId = DEFAULT_VOICE_MARKER; + } + const voiceMapEntry = new VoiceMapEntry(character, voiceId); + voiceMapEntry.addUI(voiceIdsFromProvider); + voiceMapEntries.push(voiceMapEntry); + } + updateVoiceMap(); +} + +$(document).ready(function () { + function addExtensionControls() { + const settingsHtml = ` +
+
+
+ TTS +
+
+
+
+
+ Select TTS Provider
+
+ + +
+
+ + + + + + + + +
+
+
+
+
+
+
+ +
+
+
+
+
+ `; + $('#extensions_settings').append(settingsHtml); + $('#tts_refresh').on('click', onRefreshClick); + $('#tts_enabled').on('click', onEnableClick); + $('#tts_narrate_dialogues').on('click', onNarrateDialoguesClick); + $('#tts_narrate_quoted').on('click', onNarrateQuotedClick); + $('#tts_narrate_translated_only').on('click', onNarrateTranslatedOnlyClick); + $('#tts_skip_codeblocks').on('click', onSkipCodeblocksClick); + $('#tts_pass_asterisks').on('click', onPassAsterisksClick); + $('#tts_auto_generation').on('click', onAutoGenerationClick); + $('#tts_narrate_user').on('click', onNarrateUserClick); + $('#tts_voices').on('click', onTtsVoicesClick); + for (const provider in ttsProviders) { + $('#tts_provider').append($('` : - ``; - let template = ` -
- ${this.name} - -
- `; - $('#tts_voicemap_block').append(template); - - // Populate voice ID select list - for (const voiceId of voiceIds) { - const option = document.createElement('option'); - option.innerText = voiceId.name; - option.value = voiceId.name; - $(`#tts_voicemap_char_${sanitizedName}_voice`).append(option); - } - - this.selectElement = $(`#tts_voicemap_char_${sanitizedName}_voice`); - this.selectElement.on('change', args => this.onSelectChange(args)); - this.selectElement.val(this.voiceId); - } - - onSelectChange(args) { - this.voiceId = this.selectElement.find(':selected').val(); - updateVoiceMap(); - } -} - -/** - * 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(unrestricted = false) { - // Gate initialization if not enabled or TTS Provider not ready. Prevents error popups. - const enabled = $('#tts_enabled').is(':checked'); - if (!enabled) { - return; - } - - // Keep errors inside extension UI rather than toastr. Toastr errors for TTS are annoying. - try { - await ttsProvider.checkReady(); - } catch (error) { - const message = `TTS Provider not ready. ${error}`; - setTtsStatus(message, false); - return; - } - - setTtsStatus('TTS Provider Loaded', true); - - // Clear existing voiceMap state - $('#tts_voicemap_block').empty(); - voiceMapEntries = []; - - // Get characters in current chat - const characters = getCharacters(unrestricted); - - // Get saved voicemap from provider settings, handling new and old representations - let voiceMapFromSettings = {}; - if ('voiceMap' in extension_settings.tts[ttsProviderName]) { - // Handle previous representation - if (typeof extension_settings.tts[ttsProviderName].voiceMap === 'string') { - voiceMapFromSettings = parseVoiceMap(extension_settings.tts[ttsProviderName].voiceMap); - // Handle new representation - } else if (typeof extension_settings.tts[ttsProviderName].voiceMap === 'object') { - voiceMapFromSettings = extension_settings.tts[ttsProviderName].voiceMap; - } - } - - // Get voiceIds from provider - let voiceIdsFromProvider; - try { - voiceIdsFromProvider = await ttsProvider.fetchTtsVoiceObjects(); - } - catch { - toastr.error('TTS Provider failed to return voice ids.'); - } - - // Build UI using VoiceMapEntry objects - for (const character of characters) { - if (character === 'SillyTavern System') { - continue; - } - // Check provider settings for voiceIds - let voiceId; - if (character in voiceMapFromSettings) { - voiceId = voiceMapFromSettings[character]; - } else if (character === DEFAULT_VOICE_MARKER) { - voiceId = DISABLED_VOICE_MARKER; - } else { - voiceId = DEFAULT_VOICE_MARKER; - } - const voiceMapEntry = new VoiceMapEntry(character, voiceId); - voiceMapEntry.addUI(voiceIdsFromProvider); - voiceMapEntries.push(voiceMapEntry); - } - updateVoiceMap(); -} - -$(document).ready(function () { - function addExtensionControls() { - const settingsHtml = ` -
-
-
- TTS -
-
-
-
-
- Select TTS Provider
-
- - -
-
- - - - - - - - -
-
-
-
-
-
-
- -
-
-
-
-
- `; - $('#extensions_settings').append(settingsHtml); - $('#tts_refresh').on('click', onRefreshClick); - $('#tts_enabled').on('click', onEnableClick); - $('#tts_narrate_dialogues').on('click', onNarrateDialoguesClick); - $('#tts_narrate_quoted').on('click', onNarrateQuotedClick); - $('#tts_narrate_translated_only').on('click', onNarrateTranslatedOnlyClick); - $('#tts_skip_codeblocks').on('click', onSkipCodeblocksClick); - $('#tts_pass_asterisks').on('click', onPassAsterisksClick); - $('#tts_auto_generation').on('click', onAutoGenerationClick); - $('#tts_narrate_user').on('click', onNarrateUserClick); - $('#tts_voices').on('click', onTtsVoicesClick); - for (const provider in ttsProviders) { - $('#tts_provider').append($('` : + ``; + let template = ` +
+ ${this.name} + +
+ `; + $('#tts_voicemap_block').append(template); + + // Populate voice ID select list + for (const voiceId of voiceIds) { + const option = document.createElement('option'); + option.innerText = voiceId.name; + option.value = voiceId.name; + $(`#tts_voicemap_char_${sanitizedName}_voice`).append(option); + } + + this.selectElement = $(`#tts_voicemap_char_${sanitizedName}_voice`); + this.selectElement.on('change', args => this.onSelectChange(args)); + this.selectElement.val(this.voiceId); + } + + onSelectChange(args) { + this.voiceId = this.selectElement.find(':selected').val(); + updateVoiceMap(); + } +} + +/** + * 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(unrestricted = false) { + // Gate initialization if not enabled or TTS Provider not ready. Prevents error popups. + const enabled = $('#tts_enabled').is(':checked'); + if (!enabled) { + return; + } + + // Keep errors inside extension UI rather than toastr. Toastr errors for TTS are annoying. + try { + await ttsProvider.checkReady(); + } catch (error) { + const message = `TTS Provider not ready. ${error}`; + setTtsStatus(message, false); + return; + } + + setTtsStatus('TTS Provider Loaded', true); + + // Clear existing voiceMap state + $('#tts_voicemap_block').empty(); + voiceMapEntries = []; + + // Get characters in current chat + const characters = getCharacters(unrestricted); + + // Get saved voicemap from provider settings, handling new and old representations + let voiceMapFromSettings = {}; + if ('voiceMap' in extension_settings.tts[ttsProviderName]) { + // Handle previous representation + if (typeof extension_settings.tts[ttsProviderName].voiceMap === 'string') { + voiceMapFromSettings = parseVoiceMap(extension_settings.tts[ttsProviderName].voiceMap); + // Handle new representation + } else if (typeof extension_settings.tts[ttsProviderName].voiceMap === 'object') { + voiceMapFromSettings = extension_settings.tts[ttsProviderName].voiceMap; + } + } + + // Get voiceIds from provider + let voiceIdsFromProvider; + try { + voiceIdsFromProvider = await ttsProvider.fetchTtsVoiceObjects(); + } + catch { + toastr.error('TTS Provider failed to return voice ids.'); + } + + // Build UI using VoiceMapEntry objects + for (const character of characters) { + if (character === 'SillyTavern System') { + continue; + } + // Check provider settings for voiceIds + let voiceId; + if (character in voiceMapFromSettings) { + voiceId = voiceMapFromSettings[character]; + } else if (character === DEFAULT_VOICE_MARKER) { + voiceId = DISABLED_VOICE_MARKER; + } else { + voiceId = DEFAULT_VOICE_MARKER; + } + const voiceMapEntry = new VoiceMapEntry(character, voiceId); + voiceMapEntry.addUI(voiceIdsFromProvider); + voiceMapEntries.push(voiceMapEntry); + } + updateVoiceMap(); +} + +$(document).ready(function () { + function addExtensionControls() { + const settingsHtml = ` +
+
+
+ TTS +
+
+
+
+
+ Select TTS Provider
+
+ + +
+
+ + + + + + + + +
+
+
+
+
+
+
+ +
+
+
+
+
+ `; + $('#extensions_settings').append(settingsHtml); + $('#tts_refresh').on('click', onRefreshClick); + $('#tts_enabled').on('click', onEnableClick); + $('#tts_narrate_dialogues').on('click', onNarrateDialoguesClick); + $('#tts_narrate_quoted').on('click', onNarrateQuotedClick); + $('#tts_narrate_translated_only').on('click', onNarrateTranslatedOnlyClick); + $('#tts_skip_codeblocks').on('click', onSkipCodeblocksClick); + $('#tts_pass_asterisks').on('click', onPassAsterisksClick); + $('#tts_auto_generation').on('click', onAutoGenerationClick); + $('#tts_narrate_user').on('click', onNarrateUserClick); + $('#tts_voices').on('click', onTtsVoicesClick); + for (const provider in ttsProviders) { + $('#tts_provider').append($('
diff --git a/public/scripts/extensions/quick-reply/index.js b/public/scripts/extensions/quick-reply/index.js index 48d200ae3..af09a9c49 100644 --- a/public/scripts/extensions/quick-reply/index.js +++ b/public/scripts/extensions/quick-reply/index.js @@ -103,6 +103,7 @@ const loadSets = async () => { qr.executeOnUser = slot.autoExecute_userMessage ?? false; qr.executeOnAi = slot.autoExecute_botMessage ?? false; qr.executeOnChatChange = slot.autoExecute_chatLoad ?? false; + qr.executeOnGroupMemberDraft = slot.autoExecute_groupMemberDraft ?? false; qr.contextList = (slot.contextMenu ?? []).map(it=>({ set: it.preset, isChained: it.chain, @@ -235,3 +236,8 @@ const onAiMessage = async () => { await autoExec.handleAi(); }; eventSource.on(event_types.CHARACTER_MESSAGE_RENDERED, (...args)=>executeIfReadyElseQueue(onAiMessage, args)); + +const onGroupMemberDraft = async () => { + await autoExec.handleGroupMemberDraft(); +}; +eventSource.on(event_types.GROUP_MEMBER_DRAFTED, (...args) => executeIfReadyElseQueue(onGroupMemberDraft, args)); diff --git a/public/scripts/extensions/quick-reply/src/AutoExecuteHandler.js b/public/scripts/extensions/quick-reply/src/AutoExecuteHandler.js index 4d53b23dd..06656d51b 100644 --- a/public/scripts/extensions/quick-reply/src/AutoExecuteHandler.js +++ b/public/scripts/extensions/quick-reply/src/AutoExecuteHandler.js @@ -73,4 +73,13 @@ export class AutoExecuteHandler { ]; await this.performAutoExecute(qrList); } + + async handleGroupMemberDraft() { + if (!this.checkExecute()) return; + const qrList = [ + ...this.settings.config.setList.map(link=>link.set.qrList.filter(qr=>qr.executeOnGroupMemberDraft)).flat(), + ...(this.settings.chatConfig?.setList?.map(link=>link.set.qrList.filter(qr=>qr.executeOnGroupMemberDraft))?.flat() ?? []), + ]; + await this.performAutoExecute(qrList); + } } diff --git a/public/scripts/extensions/quick-reply/src/QuickReply.js b/public/scripts/extensions/quick-reply/src/QuickReply.js index e247aa940..a5ebb1f66 100644 --- a/public/scripts/extensions/quick-reply/src/QuickReply.js +++ b/public/scripts/extensions/quick-reply/src/QuickReply.js @@ -30,6 +30,7 @@ export class QuickReply { /**@type {Boolean}*/ executeOnUser = false; /**@type {Boolean}*/ executeOnAi = false; /**@type {Boolean}*/ executeOnChatChange = false; + /**@type {Boolean}*/ executeOnGroupMemberDraft = false; /**@type {Function}*/ onExecute; /**@type {Function}*/ onDelete; @@ -351,7 +352,13 @@ export class QuickReply { this.executeOnChatChange = executeOnChatChange.checked; this.updateContext(); }); - + /**@type {HTMLInputElement}*/ + const executeOnGroupMemberDraft = dom.querySelector('#qr--executeOnGroupMemberDraft'); + executeOnGroupMemberDraft.checked = this.executeOnGroupMemberDraft; + executeOnGroupMemberDraft.addEventListener('click', ()=>{ + this.executeOnGroupMemberDraft = executeOnGroupMemberDraft.checked; + this.updateContext(); + }); /**@type {HTMLElement}*/ const executeErrors = dom.querySelector('#qr--modal-executeErrors'); @@ -484,6 +491,7 @@ export class QuickReply { executeOnUser: this.executeOnUser, executeOnAi: this.executeOnAi, executeOnChatChange: this.executeOnChatChange, + executeOnGroupMemberDraft: this.executeOnGroupMemberDraft, }; } } diff --git a/public/scripts/extensions/quick-reply/src/SlashCommandHandler.js b/public/scripts/extensions/quick-reply/src/SlashCommandHandler.js index 857714c0c..9ad225091 100644 --- a/public/scripts/extensions/quick-reply/src/SlashCommandHandler.js +++ b/public/scripts/extensions/quick-reply/src/SlashCommandHandler.js @@ -36,6 +36,7 @@ export class SlashCommandHandler { user - bool - auto execute on user message, e.g., user=true bot - bool - auto execute on AI message, e.g., bot=true load - bool - auto execute on chat load, e.g., load=true + group - bool - auto execute on group member selection, e.g., group=true title - string - title / tooltip to be shown on button, e.g., title="My Fancy Button" `.trim(); const qrUpdateArgs = ` @@ -148,6 +149,7 @@ export class SlashCommandHandler { executeOnUser: isTrueBoolean(args.user), executeOnAi: isTrueBoolean(args.bot), executeOnChatChange: isTrueBoolean(args.load), + executeOnGroupMemberDraft: isTrueBoolean(args.group), }, ); } catch (ex) { @@ -168,6 +170,7 @@ export class SlashCommandHandler { executeOnUser: args.user === undefined ? undefined : isTrueBoolean(args.user), executeOnAi: args.bot === undefined ? undefined : isTrueBoolean(args.bot), executeOnChatChange: args.load === undefined ? undefined : isTrueBoolean(args.load), + executeOnGroupMemberDraft: args.group === undefined ? undefined : isTrueBoolean(args.group), }, ); } catch (ex) { diff --git a/public/scripts/group-chats.js b/public/scripts/group-chats.js index bfd49f7c8..d43322e0b 100644 --- a/public/scripts/group-chats.js +++ b/public/scripts/group-chats.js @@ -729,6 +729,7 @@ async function generateGroupWrapper(by_auto_mode, type = null, params = {}) { const generateType = type == 'swipe' || type == 'impersonate' || type == 'quiet' || type == 'continue' ? type : 'group_chat'; setCharacterId(chId); setCharacterName(characters[chId].name); + await eventSource.emit(event_types.GROUP_MEMBER_DRAFTED, chId); if (type !== 'swipe' && type !== 'impersonate' && !isStreamingEnabled()) { // update indicator and scroll down From 8874ffffc59c93c137bf1e28fbd7219266fec16b Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Thu, 18 Jan 2024 18:23:00 +0200 Subject: [PATCH 023/151] Adjust UI label. Group members are peacenik --- public/scripts/extensions/quick-reply/html/qrEditor.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/scripts/extensions/quick-reply/html/qrEditor.html b/public/scripts/extensions/quick-reply/html/qrEditor.html index d57ff565d..ad5304c22 100644 --- a/public/scripts/extensions/quick-reply/html/qrEditor.html +++ b/public/scripts/extensions/quick-reply/html/qrEditor.html @@ -68,7 +68,7 @@
From 65d9c944d8a89c0f2b7a2c632474c2ac4f3548cb Mon Sep 17 00:00:00 2001 From: LenAnderson Date: Thu, 18 Jan 2024 20:43:31 +0000 Subject: [PATCH 024/151] await init and wait for APP_READY --- public/scripts/extensions/quick-reply/index.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/public/scripts/extensions/quick-reply/index.js b/public/scripts/extensions/quick-reply/index.js index af09a9c49..5cb636d79 100644 --- a/public/scripts/extensions/quick-reply/index.js +++ b/public/scripts/extensions/quick-reply/index.js @@ -198,6 +198,9 @@ const init = async () => { slash.init(); autoExec = new AutoExecuteHandler(settings); + eventSource.on(event_types.APP_READY, ()=>finalizeInit()); +}; +const finalizeInit = async () => { log('executing startup'); await autoExec.handleStartup(); log('/executing startup'); @@ -211,7 +214,7 @@ const init = async () => { isReady = true; log('READY'); }; -init(); +await init(); const onChatChanged = async (chatIdx) => { log('CHAT_CHANGED', chatIdx); From 9ce2771dad7494dbb078724ac476b20a62127447 Mon Sep 17 00:00:00 2001 From: LenAnderson Date: Thu, 18 Jan 2024 20:47:46 +0000 Subject: [PATCH 025/151] make finalizeInit blocking just to be sure --- public/scripts/extensions/quick-reply/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/scripts/extensions/quick-reply/index.js b/public/scripts/extensions/quick-reply/index.js index 5cb636d79..735337795 100644 --- a/public/scripts/extensions/quick-reply/index.js +++ b/public/scripts/extensions/quick-reply/index.js @@ -198,7 +198,7 @@ const init = async () => { slash.init(); autoExec = new AutoExecuteHandler(settings); - eventSource.on(event_types.APP_READY, ()=>finalizeInit()); + eventSource.on(event_types.APP_READY, async()=>await finalizeInit()); }; const finalizeInit = async () => { log('executing startup'); From 0b322c0e3d97b76bad7aee7109c239f4d4a04343 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Thu, 18 Jan 2024 23:55:09 +0200 Subject: [PATCH 026/151] Add repetition penalty control for OpenRouter --- public/index.html | 15 ++++++++++++++- public/scripts/openai.js | 14 ++++++++++++++ src/endpoints/backends/chat-completions.js | 4 ++++ 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/public/index.html b/public/index.html index b28e5cb35..abdf53a5b 100644 --- a/public/index.html +++ b/public/index.html @@ -523,6 +523,19 @@
+
+
+ Repetition Penalty +
+
+
+ +
+
+ +
+
+
Min P @@ -5235,4 +5248,4 @@ - \ No newline at end of file + diff --git a/public/scripts/openai.js b/public/scripts/openai.js index 099b3d5f9..c16280491 100644 --- a/public/scripts/openai.js +++ b/public/scripts/openai.js @@ -190,6 +190,7 @@ const default_settings = { top_k_openai: 0, min_p_openai: 0, top_a_openai: 1, + repetition_penalty_openai: 1, stream_openai: false, openai_max_context: max_4k, openai_max_tokens: 300, @@ -257,6 +258,7 @@ const oai_settings = { top_k_openai: 0, min_p_openai: 0, top_a_openai: 1, + repetition_penalty_openai: 1, stream_openai: false, openai_max_context: max_4k, openai_max_tokens: 300, @@ -1605,6 +1607,7 @@ async function sendOpenAIRequest(type, messages, signal) { if (isOpenRouter) { generate_data['top_k'] = Number(oai_settings.top_k_openai); generate_data['min_p'] = Number(oai_settings.min_p_openai); + generate_data['repetition_penalty'] = Number(oai_settings.repetition_penalty_openai); generate_data['top_a'] = Number(oai_settings.top_a_openai); generate_data['use_fallback'] = oai_settings.openrouter_use_fallback; @@ -2369,6 +2372,7 @@ function loadOpenAISettings(data, settings) { oai_settings.top_k_openai = settings.top_k_openai ?? default_settings.top_k_openai; oai_settings.top_a_openai = settings.top_a_openai ?? default_settings.top_a_openai; oai_settings.min_p_openai = settings.min_p_openai ?? default_settings.min_p_openai; + oai_settings.repetition_penalty_openai = settings.repetition_penalty_openai ?? default_settings.repetition_penalty_openai; oai_settings.stream_openai = settings.stream_openai ?? default_settings.stream_openai; oai_settings.openai_max_context = settings.openai_max_context ?? default_settings.openai_max_context; oai_settings.openai_max_tokens = settings.openai_max_tokens ?? default_settings.openai_max_tokens; @@ -2504,6 +2508,8 @@ function loadOpenAISettings(data, settings) { $('#top_a_counter_openai').val(Number(oai_settings.top_a_openai)); $('#min_p_openai').val(oai_settings.min_p_openai); $('#min_p_counter_openai').val(Number(oai_settings.min_p_openai)); + $('#repetition_penalty_openai').val(oai_settings.repetition_penalty_openai); + $('#repetition_penalty_counter_openai').val(Number(oai_settings.repetition_penalty_openai)); $('#seed_openai').val(oai_settings.seed); if (settings.reverse_proxy !== undefined) oai_settings.reverse_proxy = settings.reverse_proxy; @@ -2650,6 +2656,7 @@ async function saveOpenAIPreset(name, settings, triggerUi = true) { top_k: settings.top_k_openai, top_a: settings.top_a_openai, min_p: settings.min_p_openai, + repetition_penalty: settings.repetition_penalty_openai, openai_max_context: settings.openai_max_context, openai_max_tokens: settings.openai_max_tokens, wrap_in_quotes: settings.wrap_in_quotes, @@ -3011,6 +3018,7 @@ function onSettingsPresetChange() { top_k: ['#top_k_openai', 'top_k_openai', false], top_a: ['#top_a_openai', 'top_a_openai', false], min_p: ['#min_p_openai', 'min_p_openai', false], + repetition_penalty: ['#repetition_penalty_openai', 'repetition_penalty_openai', false], max_context_unlocked: ['#oai_max_context_unlocked', 'max_context_unlocked', true], openai_model: ['#model_openai_select', 'openai_model', false], claude_model: ['#model_claude_select', 'claude_model', false], @@ -3747,6 +3755,12 @@ $(document).ready(async function () { saveSettingsDebounced(); }); + $('#repetition_penalty_openai').on('input', function () { + oai_settings.repetition_penalty_openai = Number($(this).val()); + $('#repetition_penalty_counter_openai').val(Number($(this).val())); + saveSettingsDebounced(); + }); + $('#openai_max_context').on('input', function () { oai_settings.openai_max_context = Number($(this).val()); $('#openai_max_context_counter').val(`${$(this).val()}`); diff --git a/src/endpoints/backends/chat-completions.js b/src/endpoints/backends/chat-completions.js index cef8b906d..7fc294e04 100644 --- a/src/endpoints/backends/chat-completions.js +++ b/src/endpoints/backends/chat-completions.js @@ -730,6 +730,10 @@ router.post('/generate', jsonParser, function (request, response) { bodyParams['top_a'] = request.body.top_a; } + if (request.body.repetition_penalty !== undefined) { + bodyParams['repetition_penalty'] = request.body.repetition_penalty; + } + if (request.body.use_fallback) { bodyParams['route'] = 'fallback'; } From 49a5031e58ea053101e14947f07f51fae3761c83 Mon Sep 17 00:00:00 2001 From: Tony Ribeiro Date: Fri, 19 Jan 2024 09:29:49 +0100 Subject: [PATCH 027/151] Clean debug comments --- public/index.html | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/public/index.html b/public/index.html index 81d9d5551..09416f5f3 100644 --- a/public/index.html +++ b/public/index.html @@ -22,8 +22,7 @@ - - + - - From b741f32ae941bab1ccdb5c2791ac3c698debef3e Mon Sep 17 00:00:00 2001 From: Tony Ribeiro Date: Fri, 19 Jan 2024 09:34:32 +0100 Subject: [PATCH 028/151] Clean comments --- public/scripts/extensions/tts/index.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/public/scripts/extensions/tts/index.js b/public/scripts/extensions/tts/index.js index 36a80ecc5..d9143998a 100644 --- a/public/scripts/extensions/tts/index.js +++ b/public/scripts/extensions/tts/index.js @@ -321,10 +321,12 @@ async function playAudioData(audioJob) { } if (audioBlob instanceof Blob) { const srcUrl = await getBase64Async(audioBlob); - // VRM inject + + // VRM lip sync if (extension_settings.vrm.enabled && typeof window['vrmLipSync'] === 'function') { await window['vrmLipSync'](audioBlob, audioJob["char"]); } + audioElement.src = srcUrl; } else if (typeof audioBlob === 'string') { audioElement.src = audioBlob; @@ -481,11 +483,6 @@ async function tts(text, voiceId, char) { if (extension_settings.rvc.enabled && typeof window['rvcVoiceConversion'] === 'function') response = await window['rvcVoiceConversion'](response, char, text); - /*/ VRM injection - if (extension_settings.vrm.enabled && typeof window['vrmLipSync'] === 'function') { - await window['vrmLipSync'](response, char); - }*/ - await addAudioJob(response, char); } From bce5352c943014621408fbcaa0b0ef962ebfa6ea Mon Sep 17 00:00:00 2001 From: Tony Ribeiro Date: Fri, 19 Jan 2024 17:07:10 +0100 Subject: [PATCH 029/151] Removed VRM importmap. --- public/index.html | 9 --------- 1 file changed, 9 deletions(-) diff --git a/public/index.html b/public/index.html index 07af050ea..37e4971ed 100644 --- a/public/index.html +++ b/public/index.html @@ -22,15 +22,6 @@ - - From b7f46b1cdf545d7449ec8f2dbe67747f23d7eeb6 Mon Sep 17 00:00:00 2001 From: Tony Ribeiro Date: Fri, 19 Jan 2024 17:08:45 +0100 Subject: [PATCH 030/151] Remove typo --- public/index.html | 1 - 1 file changed, 1 deletion(-) diff --git a/public/index.html b/public/index.html index 37e4971ed..abdf53a5b 100644 --- a/public/index.html +++ b/public/index.html @@ -22,7 +22,6 @@ - From 2846d0fd58e19a30d1df869e25f740762e18ea21 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sat, 20 Jan 2024 19:48:56 +0200 Subject: [PATCH 031/151] #1720 Fetch no-cache images when uploading --- .../scripts/extensions/expressions/index.js | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/public/scripts/extensions/expressions/index.js b/public/scripts/extensions/expressions/index.js index ee3e9b5a1..39f296545 100644 --- a/public/scripts/extensions/expressions/index.js +++ b/public/scripts/extensions/expressions/index.js @@ -1376,6 +1376,7 @@ async function handleFileUpload(url, formData) { // Refresh sprites list const name = formData.get('name'); delete spriteCache[name]; + await fetchImagesNoCache(); await validateImages(name); return data; @@ -1552,6 +1553,7 @@ async function onClickExpressionDelete(event) { // Refresh sprites list delete spriteCache[name]; + await fetchImagesNoCache(); await validateImages(name); } @@ -1577,6 +1579,30 @@ function setExpressionOverrideHtml(forceClear = false) { } } +async function fetchImagesNoCache() { + const promises = []; + $('#image_list img').each(function () { + const src = $(this).attr('src'); + + if (!src) { + return; + } + + const promise = fetch(src, { + method: 'GET', + cache: 'no-cache', + headers: { + 'Cache-Control': 'no-cache', + 'Pragma': 'no-cache', + 'Expires': '0', + }, + }); + promises.push(promise); + }); + + return await Promise.allSettled(promises); +} + (function () { function addExpressionImage() { const html = ` From 67c8970373b206d9a97edf323fec1a48dc042e84 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sat, 20 Jan 2024 19:51:08 +0200 Subject: [PATCH 032/151] #1719 Hide HTML formulas --- public/css/st-tailwind.css | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/public/css/st-tailwind.css b/public/css/st-tailwind.css index 015cf6f51..2472b1a8f 100644 --- a/public/css/st-tailwind.css +++ b/public/css/st-tailwind.css @@ -432,6 +432,7 @@ line-height: 1.2; } +.custom-katex-html, .katex-html { display: none; } @@ -530,4 +531,4 @@ textarea:disabled { height: 30px; text-align: center; padding: 5px; -} \ No newline at end of file +} From 3cb94135412d4f6dad433c3fd25252a008a3fdd9 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sat, 20 Jan 2024 20:13:41 +0200 Subject: [PATCH 033/151] #1718 Fix message search opening wrong chats --- public/script.js | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/public/script.js b/public/script.js index b34c88d58..3e491540a 100644 --- a/public/script.js +++ b/public/script.js @@ -5866,6 +5866,7 @@ export async function getPastCharacterChats(characterId = null) { */ export async function displayPastChats() { $('#select_chat_div').empty(); + $('#select_chat_search').val('').off('input'); const group = selected_group ? groups.find(x => x.id === selected_group) : null; const data = await (selected_group ? getGroupPastChats(selected_group) : getPastCharacterChats()); @@ -5895,25 +5896,25 @@ export async function displayPastChats() { return chatContent && Object.values(chatContent).some(message => message?.mes?.toLowerCase()?.includes(searchQuery.toLowerCase())); }); - console.log(filteredData); - for (const key in filteredData) { + console.debug(filteredData); + for (const value of filteredData.values()) { let strlen = 300; - let mes = filteredData[key]['mes']; + let mes = value['mes']; if (mes !== undefined) { if (mes.length > strlen) { mes = '...' + mes.substring(mes.length - strlen); } - const chat_items = data[key]['chat_items']; - const file_size = data[key]['file_size']; - const fileName = data[key]['file_name']; - const timestamp = timestampToMoment(data[key]['last_mes']).format('lll'); + const fileSize = value['file_size']; + const fileName = value['file_name']; + const chatItems = rawChats[fileName].length; + const timestamp = timestampToMoment(value['last_mes']).format('lll'); const template = $('#past_chat_template .select_chat_block_wrapper').clone(); template.find('.select_chat_block').attr('file_name', fileName); template.find('.avatar img').attr('src', avatarImg); template.find('.select_chat_block_filename').text(fileName); - template.find('.chat_file_size').text(`(${file_size},`); - template.find('.chat_messages_num').text(`${chat_items}💬)`); + template.find('.chat_file_size').text(`(${fileSize},`); + template.find('.chat_messages_num').text(`${chatItems}💬)`); template.find('.select_chat_block_mes').text(mes); template.find('.PastChat_cross').attr('file_name', fileName); template.find('.chat_messages_date').text(timestamp); @@ -8380,7 +8381,7 @@ jQuery(async function () { if (id == 'option_select_chat') { if ((selected_group && !is_group_generating) || (this_chid !== undefined && !is_send_press) || fromSlashCommand) { - displayPastChats(); + await displayPastChats(); //this is just to avoid the shadow for past chat view when using /delchat //however, the dialog popup still gets one.. if (!fromSlashCommand) { From 570d5a30bda7103ac92cef7087b9c5f7760ab6d1 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sat, 20 Jan 2024 20:40:40 +0200 Subject: [PATCH 034/151] [skip ci] Lint fix --- public/scripts/extensions/tts/alltalk.js | 1666 +++++++++++----------- public/scripts/extensions/tts/index.js | 2 +- public/scripts/textgen-settings.js | 2 +- 3 files changed, 835 insertions(+), 835 deletions(-) diff --git a/public/scripts/extensions/tts/alltalk.js b/public/scripts/extensions/tts/alltalk.js index f70daf5ad..fe48043f0 100644 --- a/public/scripts/extensions/tts/alltalk.js +++ b/public/scripts/extensions/tts/alltalk.js @@ -1,833 +1,833 @@ -import { doExtrasFetch, getApiUrl, modules } from '../../extensions.js'; -import { saveTtsProviderSettings } from './index.js'; - -export { AllTalkTtsProvider }; - -class AllTalkTtsProvider { - //########// - // Config // - //########// - - settings = {}; - constructor() { - // Initialize with default settings if they are not already set - this.settings = { - provider_endpoint: this.settings.provider_endpoint || 'http://localhost:7851', - language: this.settings.language || 'en', - voiceMap: this.settings.voiceMap || {}, - at_generation_method: this.settings.at_generation_method || 'standard_generation', - narrator_enabled: this.settings.narrator_enabled || 'false', - at_narrator_text_not_inside: this.settings.at_narrator_text_not_inside || 'narrator', - narrator_voice_gen: this.settings.narrator_voice_gen || 'female_01.wav', - finetuned_model: this.settings.finetuned_model || 'false' - }; - // Separate property for dynamically updated settings from the server - this.dynamicSettings = { - modelsAvailable: [], - currentModel: '', - deepspeed_available: false, - deepSpeedEnabled: false, - lowVramEnabled: false, - }; - } - ready = false; - voices = []; - separator = '. '; - audioElement = document.createElement('audio'); - - languageLabels = { - 'Arabic': 'ar', - 'Brazilian Portuguese': 'pt', - 'Chinese': 'zh-cn', - 'Czech': 'cs', - 'Dutch': 'nl', - 'English': 'en', - 'French': 'fr', - 'German': 'de', - 'Italian': 'it', - 'Polish': 'pl', - 'Russian': 'ru', - 'Spanish': 'es', - 'Turkish': 'tr', - 'Japanese': 'ja', - 'Korean': 'ko', - 'Hungarian': 'hu', - 'Hindi': 'hi', - }; - - get settingsHtml() { - let html = `
AllTalk Settings
`; - - html += `
- -
- - -
-
`; - - html += `
- -
- - -
- -
- - -
- -
`; - - html += `
-
- - -
-
- - -
-
`; - - - html += `
-
- - -
- -
- - -
-
`; - - - html += `
-
- - -
-
- - -
-
- Status: Ready -
-
- -
-
`; - - - html += `
-
- AllTalk Config & Docs. -
- -
- AllTalk Website. -
-
`; - - html += `
-
-Text-generation-webui users - Uncheck Enable TTS in Text-generation-webui. -
-
`; - - return html; - } - - - //#################// - // Startup ST & AT // - //#################// - - async loadSettings(settings) { - updateStatus('Offline'); - - if (Object.keys(settings).length === 0) { - console.info('Using default AllTalk TTS Provider settings'); - } else { - // Populate settings with provided values, ignoring server-provided settings - for (const key in settings) { - if (key in this.settings) { - this.settings[key] = settings[key]; - } else { - console.debug(`Ignoring non-user-configurable setting: ${key}`); - } - } - } - - // Update UI elements to reflect the loaded settings - $('#at_server').val(this.settings.provider_endpoint); - $('#language_options').val(this.settings.language); - //$('#voicemap').val(this.settings.voiceMap); - $('#at_generation_method').val(this.settings.at_generation_method); - $('#at_narrator_enabled').val(this.settings.narrator_enabled); - $('#at_narrator_text_not_inside').val(this.settings.at_narrator_text_not_inside); - $('#narrator_voice').val(this.settings.narrator_voice_gen); - - console.debug('AllTalkTTS: Settings loaded'); - try { - // Check if TTS provider is ready - await this.checkReady(); - await this.updateSettingsFromServer(); // Fetch dynamic settings from the TTS server - await this.fetchTtsVoiceObjects(); // Fetch voices only if service is ready - this.updateNarratorVoicesDropdown(); - this.updateLanguageDropdown(); - this.setupEventListeners(); - this.applySettingsToHTML(); - updateStatus('Ready'); - } catch (error) { - console.error("Error loading settings:", error); - updateStatus('Offline'); - } - } - - applySettingsToHTML() { - // Apply loaded settings or use defaults - const narratorVoiceSelect = document.getElementById('narrator_voice'); - const atNarratorSelect = document.getElementById('at_narrator_enabled'); - const textNotInsideSelect = document.getElementById('at_narrator_text_not_inside'); - const generationMethodSelect = document.getElementById('at_generation_method'); - this.settings.narrator_voice = this.settings.narrator_voice_gen; - // Apply settings to Narrator Voice dropdown - if (narratorVoiceSelect && this.settings.narrator_voice) { - narratorVoiceSelect.value = this.settings.narrator_voice.replace('.wav', ''); - } - // Apply settings to AT Narrator Enabled dropdown - if (atNarratorSelect) { - // Sync the state with the checkbox in index.js - const ttsPassAsterisksCheckbox = document.getElementById('tts_pass_asterisks'); // Access the checkbox from index.js - const ttsNarrateQuotedCheckbox = document.getElementById('tts_narrate_quoted'); // Access the checkbox from index.js - const ttsNarrateDialoguesCheckbox = document.getElementById('tts_narrate_dialogues'); // Access the checkbox from index.js - // Sync the state with the checkbox in index.js - if (this.settings.narrator_enabled) { - ttsPassAsterisksCheckbox.checked = false; - $('#tts_pass_asterisks').click(); // Simulate a click event - $('#tts_pass_asterisks').trigger('change'); - } - if (!this.settings.narrator_enabled) { - ttsPassAsterisksCheckbox.checked = true; - $('#tts_pass_asterisks').click(); // Simulate a click event - $('#tts_pass_asterisks').trigger('change'); - } - // Uncheck and set tts_narrate_quoted to false if narrator is enabled - if (this.settings.narrator_enabledd) { - ttsNarrateQuotedCheckbox.checked = true; - ttsNarrateDialoguesCheckbox.checked = true; - // Trigger click events instead of change events - $('#tts_narrate_quoted').click(); - $('#tts_narrate_quoted').trigger('change'); - $('#tts_narrate_dialogues').click(); - $('#tts_narrate_dialogues').trigger('change'); - } - atNarratorSelect.value = this.settings.narrator_enabled.toString(); - this.settings.narrator_enabled = this.settings.narrator_enabled.toString(); - } - // Apply settings to the Language dropdown - const languageSelect = document.getElementById('language_options'); - if (languageSelect && this.settings.language) { - languageSelect.value = this.settings.language; - } - // Apply settings to Text Not Inside dropdown - if (textNotInsideSelect && this.settings.text_not_inside) { - textNotInsideSelect.value = this.settings.text_not_inside; - this.settings.at_narrator_text_not_inside = this.settings.text_not_inside; - } - // Apply settings to Generation Method dropdown - if (generationMethodSelect && this.settings.at_generation_method) { - generationMethodSelect.value = this.settings.at_generation_method; - } - // Additional logic to disable/enable dropdowns based on the selected generation method - const isStreamingEnabled = this.settings.at_generation_method === 'streaming_enabled'; - if (isStreamingEnabled) { - // Disable certain dropdowns when streaming is enabled - if (atNarratorSelect) atNarratorSelect.disabled = true; - if (textNotInsideSelect) textNotInsideSelect.disabled = true; - if (narratorVoiceSelect) narratorVoiceSelect.disabled = true; - } else { - // Enable dropdowns for standard generation - if (atNarratorSelect) atNarratorSelect.disabled = false; - if (textNotInsideSelect) textNotInsideSelect.disabled = !this.settings.narrator_enabled; - if (narratorVoiceSelect) narratorVoiceSelect.disabled = !this.settings.narrator_enabled; - } - const modelSelect = document.getElementById('switch_model'); - if (this.settings.finetuned_model === 'true') { - const ftOption = document.createElement('option'); - ftOption.value = 'XTTSv2 FT'; - ftOption.textContent = 'XTTSv2 FT'; - modelSelect.appendChild(ftOption); - } - } - - //##############################// - // Check AT Server is Available // - //##############################// - - async checkReady() { - try { - const response = await fetch(`${this.settings.provider_endpoint}/api/ready`); - // Check if the HTTP request was successful - if (!response.ok) { - throw new Error(`HTTP Error Response: ${response.status} ${response.statusText}`); - } - const statusText = await response.text(); - // Check if the response is 'Ready' - if (statusText === 'Ready') { - this.ready = true; // Set the ready flag to true - console.log('TTS service is ready.'); - } else { - this.ready = false; - console.log('TTS service is not ready.'); - } - } catch (error) { - console.error('Error checking TTS service readiness:', error); - this.ready = false; // Ensure ready flag is set to false in case of error - } - } - - //######################// - // Get Available Voices // - //######################// - - async fetchTtsVoiceObjects() { - const response = await fetch(`${this.settings.provider_endpoint}/api/voices`); - if (!response.ok) { - const errorText = await response.text(); - throw new Error(`HTTP ${response.status}: ${errorText}`); - } - const data = await response.json(); - const voices = data.voices.map(filename => { - const voiceName = filename.replace('.wav', ''); - return { - name: voiceName, - voice_id: voiceName, - preview_url: null, // Preview URL will be dynamically generated - lang: 'en' // Default language - }; - }); - this.voices = voices; // Assign to the class property - return voices; // Also return this list - } - - //##########################################// - // Get Current AT Server Config & Update ST // - //##########################################// - - async updateSettingsFromServer() { - try { - // Fetch current settings - const response = await fetch(`${this.settings.provider_endpoint}/api/currentsettings`); - if (!response.ok) { - throw new Error(`Failed to fetch current settings: ${response.statusText}`); - } - const currentSettings = await response.json(); - // Update internal settings - this.settings.modelsAvailable = currentSettings.models_available; - this.settings.currentModel = currentSettings.current_model_loaded; - this.settings.deepspeed_available = currentSettings.deepspeed_available; - this.settings.deepSpeedEnabled = currentSettings.deepspeed_status; - this.settings.lowVramEnabled = currentSettings.low_vram_status; - this.settings.finetuned_model = currentSettings.finetuned_model - // Update HTML elements - this.updateModelDropdown(); - this.updateCheckboxes(); - } catch (error) { - console.error(`Error updating settings from server: ${error}`); - } - } - - //###################################################// - // Get Current AT Server Config & Update ST (Models) // - //###################################################// - - updateModelDropdown() { - const modelSelect = document.getElementById('switch_model'); - if (modelSelect) { - modelSelect.innerHTML = ''; // Clear existing options - this.settings.modelsAvailable.forEach(model => { - const option = document.createElement('option'); - option.value = model.model_name; - option.textContent = model.model_name; // Use model_name instead of name - option.selected = model.model_name === this.settings.currentModel; - modelSelect.appendChild(option); - }); - } - } - - //#######################################################// - // Get Current AT Server Config & Update ST (DS and LVR) // - //#######################################################// - - updateCheckboxes() { - const deepspeedCheckbox = document.getElementById('deepspeed'); - const lowVramCheckbox = document.getElementById('low_vram'); - if (lowVramCheckbox) lowVramCheckbox.checked = this.settings.lowVramEnabled; - if (deepspeedCheckbox) { - deepspeedCheckbox.checked = this.settings.deepSpeedEnabled; - deepspeedCheckbox.disabled = !this.settings.deepspeed_available; // Disable checkbox if deepspeed is not available - } - } - - //###############################################################// - // Get Current AT Server Config & Update ST (AT Narrator Voices) // - //###############################################################// - - updateNarratorVoicesDropdown() { - const narratorVoiceSelect = document.getElementById('narrator_voice'); - if (narratorVoiceSelect && this.voices) { - // Clear existing options - narratorVoiceSelect.innerHTML = ''; - // Add new options - for (let voice of this.voices) { - const option = document.createElement('option'); - option.value = voice.voice_id; - option.textContent = voice.name; - narratorVoiceSelect.appendChild(option); - } - } - } - - //######################################################// - // Get Current AT Server Config & Update ST (Languages) // - //######################################################// - - updateLanguageDropdown() { - const languageSelect = document.getElementById('language_options'); - if (languageSelect) { - // Ensure default language is set - this.settings.language = this.settings.language; - - languageSelect.innerHTML = ''; - for (let language in this.languageLabels) { - const option = document.createElement('option'); - option.value = this.languageLabels[language]; - option.textContent = language; - if (this.languageLabels[language] === this.settings.language) { - option.selected = true; - } - languageSelect.appendChild(option); - } - } - } - - //########################################// - // Start AT TTS extenstion page listeners // - //########################################// - - setupEventListeners() { - - let debounceTimeout; - const debounceDelay = 500; // Milliseconds - - // Define the event handler function - const onModelSelectChange = async (event) => { - console.log("Model select change event triggered"); // Debugging statement - const selectedModel = event.target.value; - console.log(`Selected model: ${selectedModel}`); // Debugging statement - // Set status to Processing - updateStatus('Processing'); - try { - const response = await fetch(`${this.settings.provider_endpoint}/api/reload?tts_method=${encodeURIComponent(selectedModel)}`, { - method: 'POST' - }); - if (!response.ok) { - throw new Error(`HTTP Error: ${response.status}`); - } - const data = await response.json(); - console.log("POST response data:", data); // Debugging statement - // Set status to Ready if successful - updateStatus('Ready'); - } catch (error) { - console.error("POST request error:", error); // Debugging statement - // Set status to Error in case of failure - updateStatus('Error'); - } - - // Handle response or error - }; - - const debouncedModelSelectChange = (event) => { - clearTimeout(debounceTimeout); - debounceTimeout = setTimeout(() => { - onModelSelectChange(event); - }, debounceDelay); - }; - - // Switch Model Listener - const modelSelect = document.getElementById('switch_model'); - if (modelSelect) { - // Remove the event listener if it was previously added - modelSelect.removeEventListener('change', debouncedModelSelectChange); - // Add the debounced event listener - modelSelect.addEventListener('change', debouncedModelSelectChange); - } - - // DeepSpeed Listener - const deepspeedCheckbox = document.getElementById('deepspeed'); - if (deepspeedCheckbox) { - deepspeedCheckbox.addEventListener('change', async (event) => { - const deepSpeedValue = event.target.checked ? 'True' : 'False'; - // Set status to Processing - updateStatus('Processing'); - try { - const response = await fetch(`${this.settings.provider_endpoint}/api/deepspeed?new_deepspeed_value=${deepSpeedValue}`, { - method: 'POST' - }); - if (!response.ok) { - throw new Error(`HTTP Error: ${response.status}`); - } - const data = await response.json(); - console.log("POST response data:", data); // Debugging statement - // Set status to Ready if successful - updateStatus('Ready'); - } catch (error) { - console.error("POST request error:", error); // Debugging statement - // Set status to Error in case of failure - updateStatus('Error'); - } - }); - } - - // Low VRAM Listener - const lowVramCheckbox = document.getElementById('low_vram'); - if (lowVramCheckbox) { - lowVramCheckbox.addEventListener('change', async (event) => { - const lowVramValue = event.target.checked ? 'True' : 'False'; - // Set status to Processing - updateStatus('Processing'); - try { - const response = await fetch(`${this.settings.provider_endpoint}/api/lowvramsetting?new_low_vram_value=${lowVramValue}`, { - method: 'POST' - }); - if (!response.ok) { - throw new Error(`HTTP Error: ${response.status}`); - } - const data = await response.json(); - console.log("POST response data:", data); // Debugging statement - // Set status to Ready if successful - updateStatus('Ready'); - } catch (error) { - console.error("POST request error:", error); // Debugging statement - // Set status to Error in case of failure - updateStatus('Error'); - } - }); - } - - // Narrator Voice Dropdown Listener - const narratorVoiceSelect = document.getElementById('narrator_voice'); - if (narratorVoiceSelect) { - narratorVoiceSelect.addEventListener('change', (event) => { - this.settings.narrator_voice_gen = `${event.target.value}.wav`; - this.onSettingsChange(); // Save the settings after change - }); - } - - const textNotInsideSelect = document.getElementById('at_narrator_text_not_inside'); - if (textNotInsideSelect) { - textNotInsideSelect.addEventListener('change', (event) => { - this.settings.text_not_inside = event.target.value; - this.onSettingsChange(); // Save the settings after change - }); - } - - // AT Narrator Dropdown Listener - const atNarratorSelect = document.getElementById('at_narrator_enabled'); - const ttsPassAsterisksCheckbox = document.getElementById('tts_pass_asterisks'); // Access the checkbox from index.js - const ttsNarrateQuotedCheckbox = document.getElementById('tts_narrate_quoted'); // Access the checkbox from index.js - const ttsNarrateDialoguesCheckbox = document.getElementById('tts_narrate_dialogues'); // Access the checkbox from index.js - - if (atNarratorSelect && textNotInsideSelect && narratorVoiceSelect) { - atNarratorSelect.addEventListener('change', (event) => { - const isNarratorEnabled = event.target.value === 'true'; - this.settings.narrator_enabled = isNarratorEnabled; // Update the setting here - textNotInsideSelect.disabled = !isNarratorEnabled; - narratorVoiceSelect.disabled = !isNarratorEnabled; - - // Sync the state with the checkbox in index.js - if (isNarratorEnabled) { - ttsPassAsterisksCheckbox.checked = false; - $('#tts_pass_asterisks').click(); // Simulate a click event - $('#tts_pass_asterisks').trigger('change'); - } - if (!isNarratorEnabled) { - ttsPassAsterisksCheckbox.checked = true; - $('#tts_pass_asterisks').click(); // Simulate a click event - $('#tts_pass_asterisks').trigger('change'); - } - // Uncheck and set tts_narrate_quoted to false if narrator is enabled - if (isNarratorEnabled) { - ttsNarrateQuotedCheckbox.checked = true; - ttsNarrateDialoguesCheckbox.checked = true; - // Trigger click events instead of change events - $('#tts_narrate_quoted').click(); - $('#tts_narrate_quoted').trigger('change'); - $('#tts_narrate_dialogues').click(); - $('#tts_narrate_dialogues').trigger('change'); - } - this.onSettingsChange(); // Save the settings after change - }); - } - - - // Event Listener for AT Generation Method Dropdown - const atGenerationMethodSelect = document.getElementById('at_generation_method'); - const atNarratorEnabledSelect = document.getElementById('at_narrator_enabled'); - if (atGenerationMethodSelect) { - atGenerationMethodSelect.addEventListener('change', (event) => { - const selectedMethod = event.target.value; - - if (selectedMethod === 'streaming_enabled') { - // Disable and unselect AT Narrator - atNarratorEnabledSelect.disabled = true; - atNarratorEnabledSelect.value = 'false'; - textNotInsideSelect.disabled = true; - narratorVoiceSelect.disabled = true; - } else if (selectedMethod === 'standard_generation') { - // Enable AT Narrator - atNarratorEnabledSelect.disabled = false; - } - this.settings.at_generation_method = selectedMethod; // Update the setting here - this.onSettingsChange(); // Save the settings after change - }); - } - - // Listener for Language Dropdown - const languageSelect = document.getElementById('language_options'); - if (languageSelect) { - languageSelect.addEventListener('change', (event) => { - this.settings.language = event.target.value; - this.onSettingsChange(); // Save the settings after change - }); - } - - // Listener for AllTalk Endpoint Input - const atServerInput = document.getElementById('at_server'); - if (atServerInput) { - atServerInput.addEventListener('input', (event) => { - this.settings.provider_endpoint = event.target.value; - this.onSettingsChange(); // Save the settings after change - }); - } - - } - - //#############################// - // Store ST interface settings // - //#############################// - - onSettingsChange() { - // Update settings based on the UI elements - //this.settings.provider_endpoint = $('#at_server').val(); - this.settings.language = $('#language_options').val(); - //this.settings.voiceMap = $('#voicemap').val(); - this.settings.at_generation_method = $('#at_generation_method').val(); - this.settings.narrator_enabled = $('#at_narrator_enabled').val(); - this.settings.at_narrator_text_not_inside = $('#at_narrator_text_not_inside').val(); - this.settings.narrator_voice_gen = $('#narrator_voice').val(); - // Save the updated settings - saveTtsProviderSettings(); - } - - //#########################// - // ST Handle Reload button // - //#########################// - - async onRefreshClick() { - await this.checkReady(); // Check if the TTS provider is ready - await this.loadSettings(this.settings); // Reload the settings - // Additional actions as needed - } - - //##################// - // Preview AT Voice // - //##################// - - async previewTtsVoice(voiceName) { - try { - // Prepare data for POST request - const postData = new URLSearchParams(); - postData.append("voice", `${voiceName}.wav`); - // Making the POST request - const response = await fetch(`${this.settings.provider_endpoint}/api/previewvoice/`, { - method: "POST", - headers: { - 'Content-Type': 'application/x-www-form-urlencoded' - }, - body: postData, - }); - if (!response.ok) { - const errorText = await response.text(); - console.error(`[previewTtsVoice] Error Response Text:`, errorText); - throw new Error(`HTTP ${response.status}: ${errorText}`); - } - // Assuming the server returns a URL to the .wav file - const data = await response.json(); - if (data.output_file_url) { - // Use an audio element to play the .wav file - const audioElement = new Audio(data.output_file_url); - audioElement.play().catch(e => console.error("Error playing audio:", e)); - } else { - console.warn("[previewTtsVoice] No output file URL received in the response"); - throw new Error("No output file URL received in the response"); - } - - } catch (error) { - console.error("[previewTtsVoice] Exception caught during preview generation:", error); - throw error; - } - } - - //#####################// - // Populate ST voices // - //#####################// - - async getVoice(voiceName, generatePreview = false) { - // Ensure this.voices is populated - if (this.voices.length === 0) { - // Fetch voice objects logic - } - // Find the object where the name matches voiceName - const match = this.voices.find(voice => voice.name === voiceName); - if (!match) { - // Error handling - } - // Generate preview URL only if requested - if (!match.preview_url && generatePreview) { - // Generate preview logic - } - return match; // Return the found voice object - } - - //##########################################// - // Generate TTS Streaming or call Standard // - //##########################################// - - async generateTts(inputText, voiceId) { - try { - if (this.settings.at_generation_method === 'streaming_enabled') { - // Construct the streaming URL - const streamingUrl = `${this.settings.provider_endpoint}/api/tts-generate-streaming?text=${encodeURIComponent(inputText)}&voice=${encodeURIComponent(voiceId)}.wav&language=${encodeURIComponent(this.settings.language)}&output_file=stream_output.wav`; - console.log("Streaming URL:", streamingUrl); - - // Return the streaming URL directly - return streamingUrl; - } else { - // For standard method - const outputUrl = await this.fetchTtsGeneration(inputText, voiceId); - const audioResponse = await fetch(outputUrl); - if (!audioResponse.ok) { - throw new Error(`HTTP ${audioResponse.status}: Failed to fetch audio data`); - } - return audioResponse; // Return the fetch response directly - } - } catch (error) { - console.error("Error in generateTts:", error); - throw error; - } - } - - - //####################// - // Generate Standard // - //####################// - - async fetchTtsGeneration(inputText, voiceId) { - // Prepare the request payload - const requestBody = new URLSearchParams({ - 'text_input': inputText, - 'text_filtering': "standard", - 'character_voice_gen': voiceId + ".wav", - 'narrator_enabled': this.settings.narrator_enabled, - 'narrator_voice_gen': this.settings.narrator_voice_gen + ".wav", - 'text_not_inside': this.settings.at_narrator_text_not_inside, - 'language': this.settings.language, - 'output_file_name': "st_output", - 'output_file_timestamp': "true", - 'autoplay': "false", - 'autoplay_volume': "0.8" - }).toString(); - - try { - const response = await doExtrasFetch( - `${this.settings.provider_endpoint}/api/tts-generate`, - { - method: 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - 'Cache-Control': 'no-cache', - }, - body: requestBody - } - ); - - if (!response.ok) { - const errorText = await response.text(); - console.error(`[fetchTtsGeneration] Error Response Text:`, errorText); - // toastr.error(response.statusText, 'TTS Generation Failed'); - throw new Error(`HTTP ${response.status}: ${errorText}`); - } - const data = await response.json(); - const outputUrl = data.output_file_url; - return outputUrl; // Return only the output_file_url - } catch (error) { - console.error("[fetchTtsGeneration] Exception caught:", error); - throw error; // Rethrow the error for further handling - } - } -} - -//#########################// -// Update Status Messages // -//#########################// - -function updateStatus(message) { - const statusElement = document.getElementById('status_info'); - if (statusElement) { - statusElement.textContent = message; - switch (message) { - case 'Offline': - statusElement.style.color = 'red'; - break; - case 'Ready': - statusElement.style.color = 'lightgreen'; - break; - case 'Processing': - statusElement.style.color = 'blue'; - break; - case 'Error': - statusElement.style.color = 'red'; - break; - } - } -} \ No newline at end of file +import { doExtrasFetch } from '../../extensions.js'; +import { saveTtsProviderSettings } from './index.js'; + +export { AllTalkTtsProvider }; + +class AllTalkTtsProvider { + //########// + // Config // + //########// + + settings = {}; + constructor() { + // Initialize with default settings if they are not already set + this.settings = { + provider_endpoint: this.settings.provider_endpoint || 'http://localhost:7851', + language: this.settings.language || 'en', + voiceMap: this.settings.voiceMap || {}, + at_generation_method: this.settings.at_generation_method || 'standard_generation', + narrator_enabled: this.settings.narrator_enabled || 'false', + at_narrator_text_not_inside: this.settings.at_narrator_text_not_inside || 'narrator', + narrator_voice_gen: this.settings.narrator_voice_gen || 'female_01.wav', + finetuned_model: this.settings.finetuned_model || 'false', + }; + // Separate property for dynamically updated settings from the server + this.dynamicSettings = { + modelsAvailable: [], + currentModel: '', + deepspeed_available: false, + deepSpeedEnabled: false, + lowVramEnabled: false, + }; + } + ready = false; + voices = []; + separator = '. '; + audioElement = document.createElement('audio'); + + languageLabels = { + 'Arabic': 'ar', + 'Brazilian Portuguese': 'pt', + 'Chinese': 'zh-cn', + 'Czech': 'cs', + 'Dutch': 'nl', + 'English': 'en', + 'French': 'fr', + 'German': 'de', + 'Italian': 'it', + 'Polish': 'pl', + 'Russian': 'ru', + 'Spanish': 'es', + 'Turkish': 'tr', + 'Japanese': 'ja', + 'Korean': 'ko', + 'Hungarian': 'hu', + 'Hindi': 'hi', + }; + + get settingsHtml() { + let html = '
AllTalk Settings
'; + + html += `
+ +
+ + +
+
`; + + html += `
+ +
+ + +
+ +
+ + +
+ +
`; + + html += `
+
+ + +
+
+ + +
+
`; + + + html += `
+
+ + +
+ +
+ + +
+
`; + + + html += `
+
+ + +
+
+ + +
+
+ Status: Ready +
+
+ +
+
`; + + + html += `
+
+ AllTalk Config & Docs. +
+ +
+ AllTalk Website. +
+
`; + + html += `
+
+Text-generation-webui users - Uncheck Enable TTS in Text-generation-webui. +
+
`; + + return html; + } + + + //#################// + // Startup ST & AT // + //#################// + + async loadSettings(settings) { + updateStatus('Offline'); + + if (Object.keys(settings).length === 0) { + console.info('Using default AllTalk TTS Provider settings'); + } else { + // Populate settings with provided values, ignoring server-provided settings + for (const key in settings) { + if (key in this.settings) { + this.settings[key] = settings[key]; + } else { + console.debug(`Ignoring non-user-configurable setting: ${key}`); + } + } + } + + // Update UI elements to reflect the loaded settings + $('#at_server').val(this.settings.provider_endpoint); + $('#language_options').val(this.settings.language); + //$('#voicemap').val(this.settings.voiceMap); + $('#at_generation_method').val(this.settings.at_generation_method); + $('#at_narrator_enabled').val(this.settings.narrator_enabled); + $('#at_narrator_text_not_inside').val(this.settings.at_narrator_text_not_inside); + $('#narrator_voice').val(this.settings.narrator_voice_gen); + + console.debug('AllTalkTTS: Settings loaded'); + try { + // Check if TTS provider is ready + await this.checkReady(); + await this.updateSettingsFromServer(); // Fetch dynamic settings from the TTS server + await this.fetchTtsVoiceObjects(); // Fetch voices only if service is ready + this.updateNarratorVoicesDropdown(); + this.updateLanguageDropdown(); + this.setupEventListeners(); + this.applySettingsToHTML(); + updateStatus('Ready'); + } catch (error) { + console.error('Error loading settings:', error); + updateStatus('Offline'); + } + } + + applySettingsToHTML() { + // Apply loaded settings or use defaults + const narratorVoiceSelect = document.getElementById('narrator_voice'); + const atNarratorSelect = document.getElementById('at_narrator_enabled'); + const textNotInsideSelect = document.getElementById('at_narrator_text_not_inside'); + const generationMethodSelect = document.getElementById('at_generation_method'); + this.settings.narrator_voice = this.settings.narrator_voice_gen; + // Apply settings to Narrator Voice dropdown + if (narratorVoiceSelect && this.settings.narrator_voice) { + narratorVoiceSelect.value = this.settings.narrator_voice.replace('.wav', ''); + } + // Apply settings to AT Narrator Enabled dropdown + if (atNarratorSelect) { + // Sync the state with the checkbox in index.js + const ttsPassAsterisksCheckbox = document.getElementById('tts_pass_asterisks'); // Access the checkbox from index.js + const ttsNarrateQuotedCheckbox = document.getElementById('tts_narrate_quoted'); // Access the checkbox from index.js + const ttsNarrateDialoguesCheckbox = document.getElementById('tts_narrate_dialogues'); // Access the checkbox from index.js + // Sync the state with the checkbox in index.js + if (this.settings.narrator_enabled) { + ttsPassAsterisksCheckbox.checked = false; + $('#tts_pass_asterisks').click(); // Simulate a click event + $('#tts_pass_asterisks').trigger('change'); + } + if (!this.settings.narrator_enabled) { + ttsPassAsterisksCheckbox.checked = true; + $('#tts_pass_asterisks').click(); // Simulate a click event + $('#tts_pass_asterisks').trigger('change'); + } + // Uncheck and set tts_narrate_quoted to false if narrator is enabled + if (this.settings.narrator_enabledd) { + ttsNarrateQuotedCheckbox.checked = true; + ttsNarrateDialoguesCheckbox.checked = true; + // Trigger click events instead of change events + $('#tts_narrate_quoted').click(); + $('#tts_narrate_quoted').trigger('change'); + $('#tts_narrate_dialogues').click(); + $('#tts_narrate_dialogues').trigger('change'); + } + atNarratorSelect.value = this.settings.narrator_enabled.toString(); + this.settings.narrator_enabled = this.settings.narrator_enabled.toString(); + } + // Apply settings to the Language dropdown + const languageSelect = document.getElementById('language_options'); + if (languageSelect && this.settings.language) { + languageSelect.value = this.settings.language; + } + // Apply settings to Text Not Inside dropdown + if (textNotInsideSelect && this.settings.text_not_inside) { + textNotInsideSelect.value = this.settings.text_not_inside; + this.settings.at_narrator_text_not_inside = this.settings.text_not_inside; + } + // Apply settings to Generation Method dropdown + if (generationMethodSelect && this.settings.at_generation_method) { + generationMethodSelect.value = this.settings.at_generation_method; + } + // Additional logic to disable/enable dropdowns based on the selected generation method + const isStreamingEnabled = this.settings.at_generation_method === 'streaming_enabled'; + if (isStreamingEnabled) { + // Disable certain dropdowns when streaming is enabled + if (atNarratorSelect) atNarratorSelect.disabled = true; + if (textNotInsideSelect) textNotInsideSelect.disabled = true; + if (narratorVoiceSelect) narratorVoiceSelect.disabled = true; + } else { + // Enable dropdowns for standard generation + if (atNarratorSelect) atNarratorSelect.disabled = false; + if (textNotInsideSelect) textNotInsideSelect.disabled = !this.settings.narrator_enabled; + if (narratorVoiceSelect) narratorVoiceSelect.disabled = !this.settings.narrator_enabled; + } + const modelSelect = document.getElementById('switch_model'); + if (this.settings.finetuned_model === 'true') { + const ftOption = document.createElement('option'); + ftOption.value = 'XTTSv2 FT'; + ftOption.textContent = 'XTTSv2 FT'; + modelSelect.appendChild(ftOption); + } + } + + //##############################// + // Check AT Server is Available // + //##############################// + + async checkReady() { + try { + const response = await fetch(`${this.settings.provider_endpoint}/api/ready`); + // Check if the HTTP request was successful + if (!response.ok) { + throw new Error(`HTTP Error Response: ${response.status} ${response.statusText}`); + } + const statusText = await response.text(); + // Check if the response is 'Ready' + if (statusText === 'Ready') { + this.ready = true; // Set the ready flag to true + console.log('TTS service is ready.'); + } else { + this.ready = false; + console.log('TTS service is not ready.'); + } + } catch (error) { + console.error('Error checking TTS service readiness:', error); + this.ready = false; // Ensure ready flag is set to false in case of error + } + } + + //######################// + // Get Available Voices // + //######################// + + async fetchTtsVoiceObjects() { + const response = await fetch(`${this.settings.provider_endpoint}/api/voices`); + if (!response.ok) { + const errorText = await response.text(); + throw new Error(`HTTP ${response.status}: ${errorText}`); + } + const data = await response.json(); + const voices = data.voices.map(filename => { + const voiceName = filename.replace('.wav', ''); + return { + name: voiceName, + voice_id: voiceName, + preview_url: null, // Preview URL will be dynamically generated + lang: 'en', // Default language + }; + }); + this.voices = voices; // Assign to the class property + return voices; // Also return this list + } + + //##########################################// + // Get Current AT Server Config & Update ST // + //##########################################// + + async updateSettingsFromServer() { + try { + // Fetch current settings + const response = await fetch(`${this.settings.provider_endpoint}/api/currentsettings`); + if (!response.ok) { + throw new Error(`Failed to fetch current settings: ${response.statusText}`); + } + const currentSettings = await response.json(); + // Update internal settings + this.settings.modelsAvailable = currentSettings.models_available; + this.settings.currentModel = currentSettings.current_model_loaded; + this.settings.deepspeed_available = currentSettings.deepspeed_available; + this.settings.deepSpeedEnabled = currentSettings.deepspeed_status; + this.settings.lowVramEnabled = currentSettings.low_vram_status; + this.settings.finetuned_model = currentSettings.finetuned_model; + // Update HTML elements + this.updateModelDropdown(); + this.updateCheckboxes(); + } catch (error) { + console.error(`Error updating settings from server: ${error}`); + } + } + + //###################################################// + // Get Current AT Server Config & Update ST (Models) // + //###################################################// + + updateModelDropdown() { + const modelSelect = document.getElementById('switch_model'); + if (modelSelect) { + modelSelect.innerHTML = ''; // Clear existing options + this.settings.modelsAvailable.forEach(model => { + const option = document.createElement('option'); + option.value = model.model_name; + option.textContent = model.model_name; // Use model_name instead of name + option.selected = model.model_name === this.settings.currentModel; + modelSelect.appendChild(option); + }); + } + } + + //#######################################################// + // Get Current AT Server Config & Update ST (DS and LVR) // + //#######################################################// + + updateCheckboxes() { + const deepspeedCheckbox = document.getElementById('deepspeed'); + const lowVramCheckbox = document.getElementById('low_vram'); + if (lowVramCheckbox) lowVramCheckbox.checked = this.settings.lowVramEnabled; + if (deepspeedCheckbox) { + deepspeedCheckbox.checked = this.settings.deepSpeedEnabled; + deepspeedCheckbox.disabled = !this.settings.deepspeed_available; // Disable checkbox if deepspeed is not available + } + } + + //###############################################################// + // Get Current AT Server Config & Update ST (AT Narrator Voices) // + //###############################################################// + + updateNarratorVoicesDropdown() { + const narratorVoiceSelect = document.getElementById('narrator_voice'); + if (narratorVoiceSelect && this.voices) { + // Clear existing options + narratorVoiceSelect.innerHTML = ''; + // Add new options + for (let voice of this.voices) { + const option = document.createElement('option'); + option.value = voice.voice_id; + option.textContent = voice.name; + narratorVoiceSelect.appendChild(option); + } + } + } + + //######################################################// + // Get Current AT Server Config & Update ST (Languages) // + //######################################################// + + updateLanguageDropdown() { + const languageSelect = document.getElementById('language_options'); + if (languageSelect) { + // Ensure default language is set + this.settings.language = this.settings.language; + + languageSelect.innerHTML = ''; + for (let language in this.languageLabels) { + const option = document.createElement('option'); + option.value = this.languageLabels[language]; + option.textContent = language; + if (this.languageLabels[language] === this.settings.language) { + option.selected = true; + } + languageSelect.appendChild(option); + } + } + } + + //########################################// + // Start AT TTS extenstion page listeners // + //########################################// + + setupEventListeners() { + + let debounceTimeout; + const debounceDelay = 500; // Milliseconds + + // Define the event handler function + const onModelSelectChange = async (event) => { + console.log('Model select change event triggered'); // Debugging statement + const selectedModel = event.target.value; + console.log(`Selected model: ${selectedModel}`); // Debugging statement + // Set status to Processing + updateStatus('Processing'); + try { + const response = await fetch(`${this.settings.provider_endpoint}/api/reload?tts_method=${encodeURIComponent(selectedModel)}`, { + method: 'POST', + }); + if (!response.ok) { + throw new Error(`HTTP Error: ${response.status}`); + } + const data = await response.json(); + console.log('POST response data:', data); // Debugging statement + // Set status to Ready if successful + updateStatus('Ready'); + } catch (error) { + console.error('POST request error:', error); // Debugging statement + // Set status to Error in case of failure + updateStatus('Error'); + } + + // Handle response or error + }; + + const debouncedModelSelectChange = (event) => { + clearTimeout(debounceTimeout); + debounceTimeout = setTimeout(() => { + onModelSelectChange(event); + }, debounceDelay); + }; + + // Switch Model Listener + const modelSelect = document.getElementById('switch_model'); + if (modelSelect) { + // Remove the event listener if it was previously added + modelSelect.removeEventListener('change', debouncedModelSelectChange); + // Add the debounced event listener + modelSelect.addEventListener('change', debouncedModelSelectChange); + } + + // DeepSpeed Listener + const deepspeedCheckbox = document.getElementById('deepspeed'); + if (deepspeedCheckbox) { + deepspeedCheckbox.addEventListener('change', async (event) => { + const deepSpeedValue = event.target.checked ? 'True' : 'False'; + // Set status to Processing + updateStatus('Processing'); + try { + const response = await fetch(`${this.settings.provider_endpoint}/api/deepspeed?new_deepspeed_value=${deepSpeedValue}`, { + method: 'POST', + }); + if (!response.ok) { + throw new Error(`HTTP Error: ${response.status}`); + } + const data = await response.json(); + console.log('POST response data:', data); // Debugging statement + // Set status to Ready if successful + updateStatus('Ready'); + } catch (error) { + console.error('POST request error:', error); // Debugging statement + // Set status to Error in case of failure + updateStatus('Error'); + } + }); + } + + // Low VRAM Listener + const lowVramCheckbox = document.getElementById('low_vram'); + if (lowVramCheckbox) { + lowVramCheckbox.addEventListener('change', async (event) => { + const lowVramValue = event.target.checked ? 'True' : 'False'; + // Set status to Processing + updateStatus('Processing'); + try { + const response = await fetch(`${this.settings.provider_endpoint}/api/lowvramsetting?new_low_vram_value=${lowVramValue}`, { + method: 'POST', + }); + if (!response.ok) { + throw new Error(`HTTP Error: ${response.status}`); + } + const data = await response.json(); + console.log('POST response data:', data); // Debugging statement + // Set status to Ready if successful + updateStatus('Ready'); + } catch (error) { + console.error('POST request error:', error); // Debugging statement + // Set status to Error in case of failure + updateStatus('Error'); + } + }); + } + + // Narrator Voice Dropdown Listener + const narratorVoiceSelect = document.getElementById('narrator_voice'); + if (narratorVoiceSelect) { + narratorVoiceSelect.addEventListener('change', (event) => { + this.settings.narrator_voice_gen = `${event.target.value}.wav`; + this.onSettingsChange(); // Save the settings after change + }); + } + + const textNotInsideSelect = document.getElementById('at_narrator_text_not_inside'); + if (textNotInsideSelect) { + textNotInsideSelect.addEventListener('change', (event) => { + this.settings.text_not_inside = event.target.value; + this.onSettingsChange(); // Save the settings after change + }); + } + + // AT Narrator Dropdown Listener + const atNarratorSelect = document.getElementById('at_narrator_enabled'); + const ttsPassAsterisksCheckbox = document.getElementById('tts_pass_asterisks'); // Access the checkbox from index.js + const ttsNarrateQuotedCheckbox = document.getElementById('tts_narrate_quoted'); // Access the checkbox from index.js + const ttsNarrateDialoguesCheckbox = document.getElementById('tts_narrate_dialogues'); // Access the checkbox from index.js + + if (atNarratorSelect && textNotInsideSelect && narratorVoiceSelect) { + atNarratorSelect.addEventListener('change', (event) => { + const isNarratorEnabled = event.target.value === 'true'; + this.settings.narrator_enabled = isNarratorEnabled; // Update the setting here + textNotInsideSelect.disabled = !isNarratorEnabled; + narratorVoiceSelect.disabled = !isNarratorEnabled; + + // Sync the state with the checkbox in index.js + if (isNarratorEnabled) { + ttsPassAsterisksCheckbox.checked = false; + $('#tts_pass_asterisks').click(); // Simulate a click event + $('#tts_pass_asterisks').trigger('change'); + } + if (!isNarratorEnabled) { + ttsPassAsterisksCheckbox.checked = true; + $('#tts_pass_asterisks').click(); // Simulate a click event + $('#tts_pass_asterisks').trigger('change'); + } + // Uncheck and set tts_narrate_quoted to false if narrator is enabled + if (isNarratorEnabled) { + ttsNarrateQuotedCheckbox.checked = true; + ttsNarrateDialoguesCheckbox.checked = true; + // Trigger click events instead of change events + $('#tts_narrate_quoted').click(); + $('#tts_narrate_quoted').trigger('change'); + $('#tts_narrate_dialogues').click(); + $('#tts_narrate_dialogues').trigger('change'); + } + this.onSettingsChange(); // Save the settings after change + }); + } + + + // Event Listener for AT Generation Method Dropdown + const atGenerationMethodSelect = document.getElementById('at_generation_method'); + const atNarratorEnabledSelect = document.getElementById('at_narrator_enabled'); + if (atGenerationMethodSelect) { + atGenerationMethodSelect.addEventListener('change', (event) => { + const selectedMethod = event.target.value; + + if (selectedMethod === 'streaming_enabled') { + // Disable and unselect AT Narrator + atNarratorEnabledSelect.disabled = true; + atNarratorEnabledSelect.value = 'false'; + textNotInsideSelect.disabled = true; + narratorVoiceSelect.disabled = true; + } else if (selectedMethod === 'standard_generation') { + // Enable AT Narrator + atNarratorEnabledSelect.disabled = false; + } + this.settings.at_generation_method = selectedMethod; // Update the setting here + this.onSettingsChange(); // Save the settings after change + }); + } + + // Listener for Language Dropdown + const languageSelect = document.getElementById('language_options'); + if (languageSelect) { + languageSelect.addEventListener('change', (event) => { + this.settings.language = event.target.value; + this.onSettingsChange(); // Save the settings after change + }); + } + + // Listener for AllTalk Endpoint Input + const atServerInput = document.getElementById('at_server'); + if (atServerInput) { + atServerInput.addEventListener('input', (event) => { + this.settings.provider_endpoint = event.target.value; + this.onSettingsChange(); // Save the settings after change + }); + } + + } + + //#############################// + // Store ST interface settings // + //#############################// + + onSettingsChange() { + // Update settings based on the UI elements + //this.settings.provider_endpoint = $('#at_server').val(); + this.settings.language = $('#language_options').val(); + //this.settings.voiceMap = $('#voicemap').val(); + this.settings.at_generation_method = $('#at_generation_method').val(); + this.settings.narrator_enabled = $('#at_narrator_enabled').val(); + this.settings.at_narrator_text_not_inside = $('#at_narrator_text_not_inside').val(); + this.settings.narrator_voice_gen = $('#narrator_voice').val(); + // Save the updated settings + saveTtsProviderSettings(); + } + + //#########################// + // ST Handle Reload button // + //#########################// + + async onRefreshClick() { + await this.checkReady(); // Check if the TTS provider is ready + await this.loadSettings(this.settings); // Reload the settings + // Additional actions as needed + } + + //##################// + // Preview AT Voice // + //##################// + + async previewTtsVoice(voiceName) { + try { + // Prepare data for POST request + const postData = new URLSearchParams(); + postData.append('voice', `${voiceName}.wav`); + // Making the POST request + const response = await fetch(`${this.settings.provider_endpoint}/api/previewvoice/`, { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + body: postData, + }); + if (!response.ok) { + const errorText = await response.text(); + console.error('[previewTtsVoice] Error Response Text:', errorText); + throw new Error(`HTTP ${response.status}: ${errorText}`); + } + // Assuming the server returns a URL to the .wav file + const data = await response.json(); + if (data.output_file_url) { + // Use an audio element to play the .wav file + const audioElement = new Audio(data.output_file_url); + audioElement.play().catch(e => console.error('Error playing audio:', e)); + } else { + console.warn('[previewTtsVoice] No output file URL received in the response'); + throw new Error('No output file URL received in the response'); + } + + } catch (error) { + console.error('[previewTtsVoice] Exception caught during preview generation:', error); + throw error; + } + } + + //#####################// + // Populate ST voices // + //#####################// + + async getVoice(voiceName, generatePreview = false) { + // Ensure this.voices is populated + if (this.voices.length === 0) { + // Fetch voice objects logic + } + // Find the object where the name matches voiceName + const match = this.voices.find(voice => voice.name === voiceName); + if (!match) { + // Error handling + } + // Generate preview URL only if requested + if (!match.preview_url && generatePreview) { + // Generate preview logic + } + return match; // Return the found voice object + } + + //##########################################// + // Generate TTS Streaming or call Standard // + //##########################################// + + async generateTts(inputText, voiceId) { + try { + if (this.settings.at_generation_method === 'streaming_enabled') { + // Construct the streaming URL + const streamingUrl = `${this.settings.provider_endpoint}/api/tts-generate-streaming?text=${encodeURIComponent(inputText)}&voice=${encodeURIComponent(voiceId)}.wav&language=${encodeURIComponent(this.settings.language)}&output_file=stream_output.wav`; + console.log('Streaming URL:', streamingUrl); + + // Return the streaming URL directly + return streamingUrl; + } else { + // For standard method + const outputUrl = await this.fetchTtsGeneration(inputText, voiceId); + const audioResponse = await fetch(outputUrl); + if (!audioResponse.ok) { + throw new Error(`HTTP ${audioResponse.status}: Failed to fetch audio data`); + } + return audioResponse; // Return the fetch response directly + } + } catch (error) { + console.error('Error in generateTts:', error); + throw error; + } + } + + + //####################// + // Generate Standard // + //####################// + + async fetchTtsGeneration(inputText, voiceId) { + // Prepare the request payload + const requestBody = new URLSearchParams({ + 'text_input': inputText, + 'text_filtering': 'standard', + 'character_voice_gen': voiceId + '.wav', + 'narrator_enabled': this.settings.narrator_enabled, + 'narrator_voice_gen': this.settings.narrator_voice_gen + '.wav', + 'text_not_inside': this.settings.at_narrator_text_not_inside, + 'language': this.settings.language, + 'output_file_name': 'st_output', + 'output_file_timestamp': 'true', + 'autoplay': 'false', + 'autoplay_volume': '0.8', + }).toString(); + + try { + const response = await doExtrasFetch( + `${this.settings.provider_endpoint}/api/tts-generate`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Cache-Control': 'no-cache', + }, + body: requestBody, + }, + ); + + if (!response.ok) { + const errorText = await response.text(); + console.error('[fetchTtsGeneration] Error Response Text:', errorText); + // toastr.error(response.statusText, 'TTS Generation Failed'); + throw new Error(`HTTP ${response.status}: ${errorText}`); + } + const data = await response.json(); + const outputUrl = data.output_file_url; + return outputUrl; // Return only the output_file_url + } catch (error) { + console.error('[fetchTtsGeneration] Exception caught:', error); + throw error; // Rethrow the error for further handling + } + } +} + +//#########################// +// Update Status Messages // +//#########################// + +function updateStatus(message) { + const statusElement = document.getElementById('status_info'); + if (statusElement) { + statusElement.textContent = message; + switch (message) { + case 'Offline': + statusElement.style.color = 'red'; + break; + case 'Ready': + statusElement.style.color = 'lightgreen'; + break; + case 'Processing': + statusElement.style.color = 'blue'; + break; + case 'Error': + statusElement.style.color = 'red'; + break; + } + } +} diff --git a/public/scripts/extensions/tts/index.js b/public/scripts/extensions/tts/index.js index 8c72d7498..45a8d9d16 100644 --- a/public/scripts/extensions/tts/index.js +++ b/public/scripts/extensions/tts/index.js @@ -676,7 +676,7 @@ function onSkipCodeblocksClick() { function onPassAsterisksClick() { extension_settings.tts.pass_asterisks = !!$('#tts_pass_asterisks').prop('checked'); saveSettingsDebounced(); - console.log("setting pass asterisks", extension_settings.tts.pass_asterisks) + console.log('setting pass asterisks', extension_settings.tts.pass_asterisks); } //##############// diff --git a/public/scripts/textgen-settings.js b/public/scripts/textgen-settings.js index 51b08538d..df28104bf 100644 --- a/public/scripts/textgen-settings.js +++ b/public/scripts/textgen-settings.js @@ -547,7 +547,7 @@ jQuery(function () { inputElement.val(value).trigger('input'); if (power_user.enableZenSliders) { let masterElementID = inputElement.prop('id'); - console.log(masterElementID) + console.log(masterElementID); let zenSlider = $(`#${masterElementID}_zenslider`).slider(); zenSlider.slider('option', 'value', value); zenSlider.slider('option', 'slide') From b2509f8de475eb8b907e57c5fea2248a5aa419e4 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sat, 20 Jan 2024 20:44:11 +0200 Subject: [PATCH 035/151] Rethrow AllTalk init error --- public/scripts/extensions/tts/alltalk.js | 1 + 1 file changed, 1 insertion(+) diff --git a/public/scripts/extensions/tts/alltalk.js b/public/scripts/extensions/tts/alltalk.js index fe48043f0..d51ac056c 100644 --- a/public/scripts/extensions/tts/alltalk.js +++ b/public/scripts/extensions/tts/alltalk.js @@ -312,6 +312,7 @@ class AllTalkTtsProvider { } catch (error) { console.error('Error checking TTS service readiness:', error); this.ready = false; // Ensure ready flag is set to false in case of error + throw error; // Rethrow the error for further handling } } From 4bc7fbcfd79fb76394e5ab39d680700be9af03c1 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sun, 21 Jan 2024 15:07:35 +0200 Subject: [PATCH 036/151] Bump package version --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 248e48e13..36cfb9528 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "sillytavern", - "version": "1.11.2", + "version": "1.11.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "sillytavern", - "version": "1.11.2", + "version": "1.11.3", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { diff --git a/package.json b/package.json index db996b17c..1d8eb4781 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,7 @@ "type": "git", "url": "https://github.com/SillyTavern/SillyTavern.git" }, - "version": "1.11.2", + "version": "1.11.3", "scripts": { "start": "node server.js", "start-multi": "node server.js --disableCsrf", From ffbf35e468627ef82764e785476282f6a5bb68e0 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sun, 21 Jan 2024 15:11:47 +0200 Subject: [PATCH 037/151] Update index.js --- public/scripts/extensions/tts/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/scripts/extensions/tts/index.js b/public/scripts/extensions/tts/index.js index d9143998a..c018cc030 100644 --- a/public/scripts/extensions/tts/index.js +++ b/public/scripts/extensions/tts/index.js @@ -323,7 +323,7 @@ async function playAudioData(audioJob) { const srcUrl = await getBase64Async(audioBlob); // VRM lip sync - if (extension_settings.vrm.enabled && typeof window['vrmLipSync'] === 'function') { + if (extension_settings.vrm?.enabled && typeof window['vrmLipSync'] === 'function') { await window['vrmLipSync'](audioBlob, audioJob["char"]); } From e2becdf7a957284ebda60f0e4c1efc224cf153ca Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sun, 21 Jan 2024 15:19:13 +0200 Subject: [PATCH 038/151] Add typedefs for TTS audioJob --- public/scripts/extensions/tts/index.js | 26 +++++++++++++++++++------- src/endpoints/assets.js | 2 +- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/public/scripts/extensions/tts/index.js b/public/scripts/extensions/tts/index.js index 6411bd2f7..992d973da 100644 --- a/public/scripts/extensions/tts/index.js +++ b/public/scripts/extensions/tts/index.js @@ -298,7 +298,7 @@ function debugTtsPlayback() { }, )); } -window.debugTtsPlayback = debugTtsPlayback; +window['debugTtsPlayback'] = debugTtsPlayback; //##################// // Audio Control // @@ -308,13 +308,25 @@ let audioElement = new Audio(); audioElement.id = 'tts_audio'; audioElement.autoplay = true; +/** + * @type AudioJob[] Audio job queue + * @typedef {{audioBlob: Blob | string, char: string}} AudioJob Audio job object + */ let audioJobQueue = []; +/** + * @type AudioJob Current audio job + */ let currentAudioJob; let audioPaused = false; let audioQueueProcessorReady = true; +/** + * Play audio data from audio job object. + * @param {AudioJob} audioJob Audio job object + * @returns {Promise} Promise that resolves when audio playback is started + */ async function playAudioData(audioJob) { - const audioBlob = audioJob["audioBlob"]; + const { audioBlob, char } = audioJob; // Since current audio job can be cancelled, don't playback if it is null if (currentAudioJob == null) { console.log('Cancelled TTS playback because currentAudioJob was null'); @@ -324,7 +336,7 @@ async function playAudioData(audioJob) { // VRM lip sync if (extension_settings.vrm?.enabled && typeof window['vrmLipSync'] === 'function') { - await window['vrmLipSync'](audioBlob, audioJob["char"]); + await window['vrmLipSync'](audioBlob, char); } audioElement.src = srcUrl; @@ -343,7 +355,7 @@ async function playAudioData(audioJob) { window['tts_preview'] = function (id) { const audio = document.getElementById(id); - if (audio && !$(audio).data('disabled')) { + if (audio instanceof HTMLAudioElement && !$(audio).data('disabled')) { audio.play(); } else { @@ -429,13 +441,13 @@ function completeCurrentAudioJob() { */ async function addAudioJob(response, char) { if (typeof response === 'string') { - audioJobQueue.push({"audioBlob":response, "char":char}); + audioJobQueue.push({ audioBlob: response, char: char }); } else { const audioData = await response.blob(); if (!audioData.type.startsWith('audio/')) { throw `TTS received HTTP response with invalid data format. Expecting audio/*, got ${audioData.type}`; } - audioJobQueue.push({"audioBlob":audioData, "char":char}); + audioJobQueue.push({ audioBlob: audioData, char: char }); } console.debug('Pushed audio job to queue.'); } @@ -576,7 +588,7 @@ async function playFullConversation() { const chat = context.chat; ttsJobQueue = chat; } -window.playFullConversation = playFullConversation; +window['playFullConversation'] = playFullConversation; //#############################// // Extension UI and Settings // diff --git a/src/endpoints/assets.js b/src/endpoints/assets.js index 0bd160cc2..0284c9e82 100644 --- a/src/endpoints/assets.js +++ b/src/endpoints/assets.js @@ -108,7 +108,7 @@ router.post('/get', jsonParser, async (_, response) => { // VRM assets if (folder == 'vrm') { - output[folder] = {'model':[], 'animation':[]}; + output[folder] = { 'model': [], 'animation': [] }; // Extract models const vrm_model_folder = path.normalize(path.join(folderPath, 'vrm', 'model')); let files = getFiles(vrm_model_folder); From 814ed49c3138eda604d5773f0f20166e65f8e738 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sun, 21 Jan 2024 17:27:09 +0200 Subject: [PATCH 039/151] #1719 Clear text nodes in rendered formulas --- public/scripts/RossAscends-mods.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/public/scripts/RossAscends-mods.js b/public/scripts/RossAscends-mods.js index 7b76ba010..a75e58ec5 100644 --- a/public/scripts/RossAscends-mods.js +++ b/public/scripts/RossAscends-mods.js @@ -60,6 +60,16 @@ const observer = new MutationObserver(function (mutations) { RA_checkOnlineStatus(); } else if (mutation.target.parentNode === SelectedCharacterTab) { setTimeout(RA_CountCharTokens, 200); + } else if (mutation.target.classList.contains('mes_text')) { + if (mutation.target instanceof HTMLElement) { + for (const element of mutation.target.getElementsByTagName('math')) { + element.childNodes.forEach(function (child) { + if (child.nodeType === Node.TEXT_NODE) { + child.textContent = ''; + } + }); + } + } } }); }); @@ -1006,7 +1016,7 @@ export function initRossMods() { Don't ask again `; - callPopup(popupText, 'confirm').then(result =>{ + callPopup(popupText, 'confirm').then(result => { if (!result) { return; } From 3cd935c0d26718afe84cd7bbc048d6fa3a7736f2 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sun, 21 Jan 2024 23:13:01 +0200 Subject: [PATCH 040/151] Fix possible prompt overflow on message examples push-out --- public/script.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/public/script.js b/public/script.js index 3e491540a..d8d7f8537 100644 --- a/public/script.js +++ b/public/script.js @@ -3314,7 +3314,8 @@ async function Generate(type, { automatic_trigger, force_name2, quiet_prompt, qu const prompt = [ storyString, mesExmString, - mesSend.join(''), + mesSend.map((e) => `${e.extensionPrompts.join('')}${e.message}`).join(''), + '\n', generatedPromptCache, allAnchors, quiet_prompt, From 958cf6a373d612485ccfaaec681797521f750cfe Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sun, 21 Jan 2024 23:20:29 +0200 Subject: [PATCH 041/151] Don't append name2 in non-instruct mode if continuing on first message --- public/script.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/public/script.js b/public/script.js index d8d7f8537..37d3a4633 100644 --- a/public/script.js +++ b/public/script.js @@ -3275,8 +3275,9 @@ async function Generate(type, { automatic_trigger, force_name2, quiet_prompt, qu } // Add character's name - // Force name append on continue (if not continuing on user message) - if (!isInstruct && force_name2) { + // Force name append on continue (if not continuing on user message or first message) + const isContinuingOnFirstMessage = chat.length === 1 && isContinue; + if (!isInstruct && force_name2 && !isContinuingOnFirstMessage) { if (!lastMesString.endsWith('\n')) { lastMesString += '\n'; } From 6a03980db699f2713ca1bdcbff18e7b52f02817f Mon Sep 17 00:00:00 2001 From: Juha Jeronen Date: Mon, 22 Jan 2024 15:56:12 +0200 Subject: [PATCH 042/151] add/improve tooltips --- public/index.html | 168 ++++++++++-------- public/scripts/extensions/memory/index.js | 8 +- .../scripts/extensions/vectors/settings.html | 2 +- 3 files changed, 103 insertions(+), 75 deletions(-) diff --git a/public/index.html b/public/index.html index abdf53a5b..16380a573 100644 --- a/public/index.html +++ b/public/index.html @@ -259,7 +259,7 @@
@@ -432,7 +432,7 @@

-

@@ -1341,7 +1356,7 @@

Mirostat -
+

@@ -1350,12 +1365,18 @@
- Tau + + Tau +
+
- Eta + + Eta +
+
@@ -1385,11 +1406,14 @@
-

Contrast Search -
+

Contrastive Search +

- Penalty Alpha + + Penalty Alpha +
+
@@ -1427,7 +1451,7 @@ @@ -1469,7 +1493,7 @@

CFG -
+

Scale @@ -3189,59 +3213,59 @@

Theme Toggles

-
-