SillyTavern/public/scripts/extensions/tts/coqui.js

772 lines
29 KiB
JavaScript
Raw Normal View History

/*
TODO:
2023-08-14 04:03:28 +02:00
- Hide voice map its just confusing
- Delete useless call
*/
2023-12-02 20:11:06 +01:00
import { doExtrasFetch, extension_settings, getApiUrl, modules } from '../../extensions.js';
import { callPopup } from '../../../script.js';
import { initVoiceMap } from './index.js';
2023-12-02 20:11:06 +01:00
export { CoquiTtsProvider };
2023-12-02 19:04:51 +01:00
const DEBUG_PREFIX = '<Coqui TTS module> ';
let inApiCall = false;
let coquiApiModels = {}; // Initialized only once
let coquiApiModelsFull = {}; // Initialized only once
let coquiLocalModels = []; // Initialized only once
let coquiLocalModelsReceived = false;
/*
coquiApiModels format [language][dataset][name]:coqui-api-model-id, example:
{
"en": {
"vctk": {
"vits": "tts_models/en/vctk/vits"
}
},
"ja": {
"kokoro": {
"tacotron2-DDC": "tts_models/ja/kokoro/tacotron2-DDC"
}
}
}
*/
const languageLabels = {
2023-12-02 19:04:51 +01:00
'multilingual': 'Multilingual',
'en': 'English',
'fr': 'French',
'es': 'Spanish',
'ja': 'Japanese'
2023-12-02 20:11:06 +01:00
};
function throwIfModuleMissing() {
if (!modules.includes('coqui-tts')) {
2023-12-02 20:11:06 +01:00
const message = 'Coqui TTS module not loaded. Add coqui-tts to enable-modules and restart the Extras API.';
// toastr.error(message, { timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true });
throw new Error(DEBUG_PREFIX, message);
}
}
function resetModelSettings() {
2023-12-02 19:04:51 +01:00
$('#coqui_api_model_settings_language').val('none');
$('#coqui_api_model_settings_speaker').val('none');
}
class CoquiTtsProvider {
//#############################//
// Extension UI and Settings //
//#############################//
2023-12-02 20:11:06 +01:00
settings;
defaultSettings = {
2023-08-26 05:51:58 +02:00
voiceMap: {},
customVoices: {},
voiceIds: [],
2023-08-17 11:05:17 +02:00
voiceMapDict: {}
2023-12-02 20:11:06 +01:00
};
get settingsHtml() {
let html = `
<div class="flex wide100p flexGap10 alignitemscenter">
<div>
<div style="flex: 50%;">
<small>To use CoquiTTS, select the origin, language, and model, then click Add Voice. The voice will then be available to add to a character. Voices are saved globally. </small><br>
2023-08-26 05:51:58 +02:00
<label for="coqui_voicename_select">Select Saved Voice:</label>
<select id="coqui_voicename_select">
<!-- Populated by JS -->
</select>
<div class="tts_block">
<input id="coqui_remove_voiceId_mapping" class="menu_button" type="button" value="Remove Voice" />
<input id="coqui_add_voiceId_mapping" class="menu_button" type="button" value="Add Voice" />
</div>
<label for="coqui_model_origin">Models:</label>
2023-08-14 04:03:28 +02:00
<select id="coqui_model_origin">gpu_mode
<option value="none">Select Origin</option>
<option value="coqui-api">Coqui API (Tested)</option>
<option value="coqui-api-full">Coqui API (Experimental)</option>
<option value="local">My Models</option>
</select>
<div id="coqui_api_model_div">
<select id="coqui_api_language">
<!-- Populated by JS and request -->
</select>
<select id="coqui_api_model_name">
<!-- Populated by JS and request -->
</select>
<div id="coqui_api_model_settings">
<select id="coqui_api_model_settings_language">
<!-- Populated by JS and request -->
</select>
<select id="coqui_api_model_settings_speaker">
<!-- Populated by JS and request -->
</select>
</div>
2023-08-14 04:03:28 +02:00
<span id="coqui_api_model_install_status">Model installed on extras server</span>
<input id="coqui_api_model_install_button" class="menu_button" type="button" value="Install" />
</div>
<div id="coqui_local_model_div">
<select id="coqui_local_model_name">
<!-- Populated by JS and request -->
</select>
</div>
</div>
</div>
</div>
2023-12-02 20:11:06 +01:00
`;
return html;
}
async loadSettings(settings) {
// Only accept keys defined in defaultSettings
2023-12-02 20:11:06 +01:00
this.settings = this.defaultSettings;
2023-08-17 11:05:17 +02:00
for (const key in settings) {
if (key in this.settings) {
2023-12-02 20:11:06 +01:00
this.settings[key] = settings[key];
} else {
2023-12-02 20:11:06 +01:00
throw DEBUG_PREFIX + `Invalid setting passed to extension: ${key}`;
}
}
await initLocalModels();
2023-08-26 05:51:58 +02:00
this.updateCustomVoices(); // Overide any manual modification
2023-08-14 04:03:28 +02:00
2023-12-02 19:04:51 +01:00
$('#coqui_api_model_div').hide();
$('#coqui_local_model_div').hide();
2023-08-17 11:05:17 +02:00
2023-12-02 19:04:51 +01:00
$('#coqui_api_language').show();
$('#coqui_api_model_name').hide();
$('#coqui_api_model_settings').hide();
$('#coqui_api_model_install_status').hide();
$('#coqui_api_model_install_button').hide();
2023-08-14 04:03:28 +02:00
2023-12-02 20:11:06 +01:00
let that = this;
$('#coqui_model_origin').on('change', function () { that.onModelOriginChange(); });
$('#coqui_api_language').on('change', function () { that.onModelLanguageChange(); });
$('#coqui_api_model_name').on('change', function () { that.onModelNameChange(); });
2023-12-02 20:11:06 +01:00
$('#coqui_remove_voiceId_mapping').on('click', function () { that.onRemoveClick(); });
$('#coqui_add_voiceId_mapping').on('click', function () { that.onAddClick(); });
2023-08-14 04:03:28 +02:00
// Load coqui-api settings from json file
2023-12-02 19:04:51 +01:00
await fetch('/scripts/extensions/tts/coqui_api_models_settings.json')
2023-08-14 04:03:28 +02:00
.then(response => response.json())
.then(json => {
coquiApiModels = json;
2023-12-02 19:04:51 +01:00
console.debug(DEBUG_PREFIX,'initialized coqui-api model list to', coquiApiModels);
/*
2023-08-14 04:03:28 +02:00
$('#coqui_api_language')
.find('option')
.remove()
.end()
.append('<option value="none">Select model language</option>')
.val('none');
for(let language in coquiApiModels) {
$("#coqui_api_language").append(new Option(languageLabels[language],language));
console.log(DEBUG_PREFIX,"added language",language);
}*/
});
// Load coqui-api FULL settings from json file
2023-12-02 19:04:51 +01:00
await fetch('/scripts/extensions/tts/coqui_api_models_settings_full.json')
.then(response => response.json())
.then(json => {
coquiApiModelsFull = json;
2023-12-02 19:04:51 +01:00
console.debug(DEBUG_PREFIX,'initialized coqui-api full model list to', coquiApiModelsFull);
/*
$('#coqui_api_full_language')
.find('option')
.remove()
.end()
.append('<option value="none">Select model language</option>')
.val('none');
for(let language in coquiApiModelsFull) {
$("#coqui_api_full_language").append(new Option(languageLabels[language],language));
console.log(DEBUG_PREFIX,"added language",language);
}*/
2023-08-14 04:03:28 +02:00
});
}
2023-08-22 15:30:33 +02:00
// Perform a simple readiness check by trying to fetch voiceIds
async checkReady(){
2023-12-02 20:11:06 +01:00
throwIfModuleMissing();
await this.fetchTtsVoiceObjects();
2023-08-22 15:30:33 +02:00
}
2023-08-26 05:51:58 +02:00
updateCustomVoices() {
// Takes voiceMapDict and converts it to a string to save to voiceMap
2023-08-26 05:51:58 +02:00
this.settings.customVoices = {};
for (let voiceName in this.settings.voiceMapDict) {
const voiceId = this.settings.voiceMapDict[voiceName];
2023-12-02 19:04:51 +01:00
this.settings.customVoices[voiceName] = voiceId['model_id'];
2023-08-14 04:03:28 +02:00
2023-12-02 19:04:51 +01:00
if (voiceId['model_language'] != null)
this.settings.customVoices[voiceName] += '[' + voiceId['model_language'] + ']';
2023-08-14 04:03:28 +02:00
2023-12-02 19:04:51 +01:00
if (voiceId['model_speaker'] != null)
this.settings.customVoices[voiceName] += '[' + voiceId['model_speaker'] + ']';
2023-08-14 04:03:28 +02:00
}
// Update UI select list with voices
2023-12-02 20:11:06 +01:00
$('#coqui_voicename_select').empty();
2023-08-26 05:51:58 +02:00
$('#coqui_voicename_select')
.find('option')
.remove()
.end()
2023-08-26 05:51:58 +02:00
.append('<option value="none">Select Voice</option>')
2023-12-02 20:11:06 +01:00
.val('none');
2023-08-26 05:51:58 +02:00
for (const voiceName in this.settings.voiceMapDict) {
2023-12-02 19:04:51 +01:00
$('#coqui_voicename_select').append(new Option(voiceName, voiceName));
}
2023-12-02 20:11:06 +01:00
this.onSettingsChange();
}
2023-08-17 11:05:17 +02:00
onSettingsChange() {
2023-12-02 19:04:51 +01:00
console.debug(DEBUG_PREFIX, 'Settings changes', this.settings);
2023-08-14 04:03:28 +02:00
extension_settings.tts.Coqui = this.settings;
}
2023-08-26 05:51:58 +02:00
async onRefreshClick() {
2023-12-02 20:11:06 +01:00
this.checkReady();
}
async onAddClick() {
if (inApiCall) {
return; //TODO: block dropdown
}
// Ask user for voiceId name to save voice
2023-12-02 20:11:06 +01:00
const voiceName = await callPopup('<h3>Name of Coqui voice to add to voice select dropdown:</h3>', 'input');
2023-12-02 19:04:51 +01:00
const model_origin = $('#coqui_model_origin').val();
const model_language = $('#coqui_api_language').val();
const model_name = $('#coqui_api_model_name').val();
let model_setting_language = $('#coqui_api_model_settings_language').val();
let model_setting_speaker = $('#coqui_api_model_settings_speaker').val();
2023-08-17 11:05:17 +02:00
2023-08-26 05:51:58 +02:00
if (!voiceName) {
2023-12-02 19:04:51 +01:00
toastr.error('Voice name empty, please enter one.', DEBUG_PREFIX + ' voice mapping voice name', { timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true });
2023-08-26 05:51:58 +02:00
this.updateCustomVoices(); // Overide any manual modification
return;
}
2023-12-02 19:04:51 +01:00
if (model_origin == 'none') {
toastr.error('Origin not selected, please select one.', DEBUG_PREFIX + ' voice mapping origin', { timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true });
2023-08-26 05:51:58 +02:00
this.updateCustomVoices(); // Overide any manual modification
return;
}
2023-12-02 19:04:51 +01:00
if (model_origin == 'local') {
const model_id = $('#coqui_local_model_name').val();
2023-12-02 19:04:51 +01:00
if (model_name == 'none') {
toastr.error('Model not selected, please select one.', DEBUG_PREFIX + ' voice mapping model', { timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true });
2023-08-26 05:51:58 +02:00
this.updateCustomVoices(); // Overide any manual modification
return;
}
2023-12-02 19:04:51 +01:00
this.settings.voiceMapDict[voiceName] = { model_type: 'local', model_id: 'local/' + model_id };
console.debug(DEBUG_PREFIX, 'Registered new voice map: ', voiceName, ':', this.settings.voiceMapDict[voiceName]);
2023-08-26 05:51:58 +02:00
this.updateCustomVoices(); // Overide any manual modification
return;
}
2023-12-02 19:04:51 +01:00
if (model_language == 'none') {
toastr.error('Language not selected, please select one.', DEBUG_PREFIX + ' voice mapping language', { timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true });
2023-08-26 05:51:58 +02:00
this.updateCustomVoices(); // Overide any manual modification
return;
}
2023-12-02 19:04:51 +01:00
if (model_name == 'none') {
toastr.error('Model not selected, please select one.', DEBUG_PREFIX + ' voice mapping model', { timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true });
2023-08-26 05:51:58 +02:00
this.updateCustomVoices(); // Overide any manual modification
return;
}
2023-12-02 19:04:51 +01:00
if (model_setting_language == 'none')
model_setting_language = null;
2023-08-17 11:05:17 +02:00
2023-12-02 19:04:51 +01:00
if (model_setting_speaker == 'none')
model_setting_speaker = null;
2023-08-17 11:05:17 +02:00
2023-12-02 19:04:51 +01:00
const tokens = $('#coqui_api_model_name').val().split('/');
2023-08-14 04:03:28 +02:00
const model_dataset = tokens[0];
const model_label = tokens[1];
2023-12-02 20:11:06 +01:00
const model_id = 'tts_models/' + model_language + '/' + model_dataset + '/' + model_label;
2023-12-02 20:11:06 +01:00
let modelDict = coquiApiModels;
2023-12-02 19:04:51 +01:00
if (model_origin == 'coqui-api-full')
2023-12-02 20:11:06 +01:00
modelDict = coquiApiModelsFull;
2023-12-02 19:04:51 +01:00
if (model_setting_language == null & 'languages' in modelDict[model_language][model_dataset][model_label]) {
toastr.error('Model language not selected, please select one.', DEBUG_PREFIX+' voice mapping model language', { timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true });
2023-08-14 04:03:28 +02:00
return;
}
2023-12-02 19:04:51 +01:00
if (model_setting_speaker == null & 'speakers' in modelDict[model_language][model_dataset][model_label]) {
toastr.error('Model speaker not selected, please select one.', DEBUG_PREFIX+' voice mapping model speaker', { timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true });
2023-08-14 04:03:28 +02:00
return;
}
2023-12-02 19:04:51 +01:00
console.debug(DEBUG_PREFIX, 'Current custom voices: ', this.settings.customVoices);
2023-12-02 19:04:51 +01:00
this.settings.voiceMapDict[voiceName] = { model_type: 'coqui-api', model_id: model_id, model_language: model_setting_language, model_speaker: model_setting_speaker };
2023-12-02 19:04:51 +01:00
console.debug(DEBUG_PREFIX, 'Registered new voice map: ', voiceName, ':', this.settings.voiceMapDict[voiceName]);
2023-08-26 05:51:58 +02:00
this.updateCustomVoices();
2023-12-02 20:11:06 +01:00
initVoiceMap(); // Update TTS extension voiceMap
2023-12-02 19:04:51 +01:00
let successMsg = voiceName + ':' + model_id;
2023-08-14 04:03:28 +02:00
if (model_setting_language != null)
2023-12-02 19:04:51 +01:00
successMsg += '[' + model_setting_language + ']';
2023-08-14 04:03:28 +02:00
if (model_setting_speaker != null)
2023-12-02 19:04:51 +01:00
successMsg += '[' + model_setting_speaker + ']';
toastr.info(successMsg, DEBUG_PREFIX + ' voice map updated', { timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true });
2023-08-26 05:51:58 +02:00
2023-12-02 20:11:06 +01:00
return;
}
async getVoice(voiceName) {
2023-12-02 20:11:06 +01:00
let match = await this.fetchTtsVoiceObjects();
2023-08-26 05:51:58 +02:00
match = match.filter(
voice => voice.name == voiceName
2023-12-02 20:11:06 +01:00
)[0];
2023-08-26 05:51:58 +02:00
if (!match) {
2023-12-02 20:11:06 +01:00
throw `TTS Voice name ${voiceName} not found in CoquiTTS Provider voice list`;
2023-08-26 05:51:58 +02:00
}
return match;
}
async onRemoveClick() {
2023-12-02 19:04:51 +01:00
const voiceName = $('#coqui_voicename_select').val();
2023-12-02 19:04:51 +01:00
if (voiceName === 'none') {
toastr.error('Voice not selected, please select one.', DEBUG_PREFIX + ' voice mapping voiceId', { timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true });
return;
}
// Todo erase from voicemap
2023-08-26 05:51:58 +02:00
delete (this.settings.voiceMapDict[voiceName]);
this.updateCustomVoices();
2023-12-02 20:11:06 +01:00
initVoiceMap(); // Update TTS extension voiceMap
}
async onModelOriginChange() {
2023-12-02 20:11:06 +01:00
throwIfModuleMissing();
resetModelSettings();
const model_origin = $('#coqui_model_origin').val();
2023-12-02 19:04:51 +01:00
if (model_origin == 'none') {
$('#coqui_local_model_div').hide();
$('#coqui_api_model_div').hide();
}
// show coqui model selected list (SAFE)
2023-12-02 19:04:51 +01:00
if (model_origin == 'coqui-api') {
$('#coqui_local_model_div').hide();
$('#coqui_api_language')
.find('option')
.remove()
.end()
.append('<option value="none">Select model language</option>')
.val('none');
for(let language in coquiApiModels) {
2023-12-02 20:11:06 +01:00
let languageLabel = language;
if (language in languageLabels)
2023-12-02 20:11:06 +01:00
languageLabel = languageLabels[language];
2023-12-02 19:04:51 +01:00
$('#coqui_api_language').append(new Option(languageLabel,language));
console.log(DEBUG_PREFIX,'added language',languageLabel,'(',language,')');
}
2023-12-02 19:04:51 +01:00
$('#coqui_api_model_div').show();
}
// show coqui model full list (UNSAFE)
2023-12-02 19:04:51 +01:00
if (model_origin == 'coqui-api-full') {
$('#coqui_local_model_div').hide();
$('#coqui_api_language')
.find('option')
.remove()
.end()
.append('<option value="none">Select model language</option>')
.val('none');
for(let language in coquiApiModelsFull) {
2023-12-02 20:11:06 +01:00
let languageLabel = language;
if (language in languageLabels)
2023-12-02 20:11:06 +01:00
languageLabel = languageLabels[language];
2023-12-02 19:04:51 +01:00
$('#coqui_api_language').append(new Option(languageLabel,language));
console.log(DEBUG_PREFIX,'added language',languageLabel,'(',language,')');
}
2023-12-02 19:04:51 +01:00
$('#coqui_api_model_div').show();
}
2023-08-17 11:05:17 +02:00
// show local model list
2023-12-02 19:04:51 +01:00
if (model_origin == 'local') {
$('#coqui_api_model_div').hide();
$('#coqui_local_model_div').show();
}
}
async onModelLanguageChange() {
throwIfModuleMissing();
resetModelSettings();
2023-12-02 19:04:51 +01:00
$('#coqui_api_model_settings').hide();
const model_origin = $('#coqui_model_origin').val();
const model_language = $('#coqui_api_language').val();
console.debug(model_language);
2023-08-17 11:05:17 +02:00
2023-12-02 19:04:51 +01:00
if (model_language == 'none') {
$('#coqui_api_model_name').hide();
return;
}
2023-08-17 11:05:17 +02:00
2023-12-02 19:04:51 +01:00
$('#coqui_api_model_name').show();
$('#coqui_api_model_name')
.find('option')
.remove()
.end()
.append('<option value="none">Select model</option>')
.val('none');
2023-12-02 20:11:06 +01:00
let modelDict = coquiApiModels;
2023-12-02 19:04:51 +01:00
if (model_origin == 'coqui-api-full')
2023-12-02 20:11:06 +01:00
modelDict = coquiApiModelsFull;
for(let model_dataset in modelDict[model_language])
for(let model_name in modelDict[model_language][model_dataset]) {
2023-12-02 20:11:06 +01:00
const model_id = model_dataset + '/' + model_name;
const model_label = model_name + ' (' + model_dataset + ' dataset)';
2023-12-02 19:04:51 +01:00
$('#coqui_api_model_name').append(new Option(model_label, model_id));
2023-08-17 11:05:17 +02:00
}
}
async onModelNameChange() {
throwIfModuleMissing();
resetModelSettings();
2023-12-02 19:04:51 +01:00
$('#coqui_api_model_settings').hide();
const model_origin = $('#coqui_model_origin').val();
2023-08-14 04:03:28 +02:00
// No model selected
2023-12-02 19:04:51 +01:00
if ($('#coqui_api_model_name').val() == 'none') {
$('#coqui_api_model_install_button').off('click');
$('#coqui_api_model_install_button').hide();
return;
}
2023-08-14 04:03:28 +02:00
// Get languages and speakers options
const model_language = $('#coqui_api_language').val();
2023-12-02 19:04:51 +01:00
const tokens = $('#coqui_api_model_name').val().split('/');
2023-08-14 04:03:28 +02:00
const model_dataset = tokens[0];
const model_name = tokens[1];
2023-12-02 20:11:06 +01:00
let modelDict = coquiApiModels;
2023-12-02 19:04:51 +01:00
if (model_origin == 'coqui-api-full')
2023-12-02 20:11:06 +01:00
modelDict = coquiApiModelsFull;
2023-12-02 20:11:06 +01:00
const model_settings = modelDict[model_language][model_dataset][model_name];
2023-12-02 19:04:51 +01:00
if ('languages' in model_settings) {
$('#coqui_api_model_settings').show();
$('#coqui_api_model_settings_language').show();
$('#coqui_api_model_settings_language')
2023-08-17 11:05:17 +02:00
.find('option')
.remove()
.end()
.append('<option value="none">Select language</option>')
.val('none');
2023-12-02 19:04:51 +01:00
for (let i = 0; i < model_settings['languages'].length; i++) {
const language_label = JSON.stringify(model_settings['languages'][i]).replaceAll('"', '');
$('#coqui_api_model_settings_language').append(new Option(language_label, i));
}
}
else {
2023-12-02 19:04:51 +01:00
$('#coqui_api_model_settings_language').hide();
}
2023-12-02 19:04:51 +01:00
if ('speakers' in model_settings) {
$('#coqui_api_model_settings').show();
$('#coqui_api_model_settings_speaker').show();
$('#coqui_api_model_settings_speaker')
2023-08-17 11:05:17 +02:00
.find('option')
.remove()
.end()
.append('<option value="none">Select speaker</option>')
.val('none');
2023-12-02 19:04:51 +01:00
for (let i = 0; i < model_settings['speakers'].length; i++) {
const speaker_label = JSON.stringify(model_settings['speakers'][i]).replaceAll('"', '');
$('#coqui_api_model_settings_speaker').append(new Option(speaker_label, i));
}
}
else {
2023-12-02 19:04:51 +01:00
$('#coqui_api_model_settings_speaker').hide();
}
2023-12-02 19:04:51 +01:00
$('#coqui_api_model_install_status').text('Requesting model to extras server...');
$('#coqui_api_model_install_status').show();
2023-08-14 04:03:28 +02:00
// Check if already installed and propose to do it otherwise
2023-12-02 20:11:06 +01:00
const model_id = modelDict[model_language][model_dataset][model_name]['id'];
2023-12-02 19:04:51 +01:00
console.debug(DEBUG_PREFIX,'Check if model is already installed',model_id);
2023-08-14 04:03:28 +02:00
let result = await CoquiTtsProvider.checkmodel_state(model_id);
result = await result.json();
2023-12-02 19:04:51 +01:00
const model_state = result['model_state'];
2023-12-02 20:11:06 +01:00
console.debug(DEBUG_PREFIX, ' Model state:', model_state);
2023-12-02 19:04:51 +01:00
if (model_state == 'installed') {
$('#coqui_api_model_install_status').text('Model already installed on extras server');
$('#coqui_api_model_install_button').hide();
}
2023-08-14 04:03:28 +02:00
else {
2023-12-02 20:11:06 +01:00
let action = 'download';
2023-12-02 19:04:51 +01:00
if (model_state == 'corrupted') {
2023-12-02 20:11:06 +01:00
action = 'repare';
2023-08-14 04:03:28 +02:00
//toastr.error("Click install button to reinstall the model "+$("#coqui_api_model_name").find(":selected").text(), DEBUG_PREFIX+" corrupted model install", { timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true });
2023-12-02 19:04:51 +01:00
$('#coqui_api_model_install_status').text('Model found but incomplete try install again (maybe still downloading)'); // (remove and download again)
2023-08-14 04:03:28 +02:00
}
else {
2023-12-02 19:04:51 +01:00
toastr.info('Click download button to install the model ' + $('#coqui_api_model_name').find(':selected').text(), DEBUG_PREFIX + ' model not installed', { timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true });
$('#coqui_api_model_install_status').text('Model not found on extras server');
2023-08-14 04:03:28 +02:00
}
2023-08-14 04:03:28 +02:00
const onModelNameChange_pointer = this.onModelNameChange;
2023-12-02 19:04:51 +01:00
$('#coqui_api_model_install_button').off('click').on('click', async function () {
2023-08-14 04:03:28 +02:00
try {
2023-12-02 19:04:51 +01:00
$('#coqui_api_model_install_status').text('Downloading model...');
$('#coqui_api_model_install_button').hide();
2023-08-14 04:03:28 +02:00
//toastr.info("For model "+model_id, DEBUG_PREFIX+" Started "+action, { timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true });
let apiResult = await CoquiTtsProvider.installModel(model_id, action);
apiResult = await apiResult.json();
2023-12-02 19:04:51 +01:00
console.debug(DEBUG_PREFIX, 'Response:', apiResult);
2023-08-14 04:03:28 +02:00
2023-12-02 19:04:51 +01:00
if (apiResult['status'] == 'done') {
$('#coqui_api_model_install_status').text('Model installed and ready to use!');
$('#coqui_api_model_install_button').hide();
2023-08-14 04:03:28 +02:00
onModelNameChange_pointer();
}
2023-08-17 11:05:17 +02:00
2023-12-02 19:04:51 +01:00
if (apiResult['status'] == 'downloading') {
toastr.error('Check extras console for progress', DEBUG_PREFIX + ' already downloading', { timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true });
$('#coqui_api_model_install_status').text('Already downloading a model, check extras console!');
$('#coqui_api_model_install_button').show();
2023-08-14 04:03:28 +02:00
}
} catch (error) {
2023-12-02 20:11:06 +01:00
console.error(error);
2023-12-02 19:04:51 +01:00
toastr.error(error, DEBUG_PREFIX + ' error with model download', { timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true });
2023-08-14 04:03:28 +02:00
onModelNameChange_pointer();
}
2023-08-17 11:05:17 +02:00
// will refresh model status
2023-08-14 04:03:28 +02:00
});
2023-08-17 11:05:17 +02:00
2023-12-02 19:04:51 +01:00
$('#coqui_api_model_install_button').show();
2023-08-14 04:03:28 +02:00
return;
}
2023-08-17 11:05:17 +02:00
}
2023-08-17 11:05:17 +02:00
2023-08-14 04:03:28 +02:00
//#############################//
// API Calls //
//#############################//
/*
Check model installation state, return one of ["installed", "corrupted", "absent"]
*/
2023-08-14 04:03:28 +02:00
static async checkmodel_state(model_id) {
2023-12-02 20:11:06 +01:00
throwIfModuleMissing();
const url = new URL(getApiUrl());
url.pathname = '/api/text-to-speech/coqui/coqui-api/check-model-state';
const apiResult = await doExtrasFetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Cache-Control': 'no-cache'
},
body: JSON.stringify({
2023-12-02 19:04:51 +01:00
'model_id': model_id,
})
});
if (!apiResult.ok) {
2023-08-17 11:05:17 +02:00
toastr.error(apiResult.statusText, DEBUG_PREFIX + ' Check model state request failed');
throw new Error(`HTTP ${apiResult.status}: ${await apiResult.text()}`);
}
2023-12-02 20:11:06 +01:00
return apiResult;
}
2023-08-14 04:03:28 +02:00
static async installModel(model_id, action) {
2023-12-02 20:11:06 +01:00
throwIfModuleMissing();
const url = new URL(getApiUrl());
url.pathname = '/api/text-to-speech/coqui/coqui-api/install-model';
const apiResult = await doExtrasFetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Cache-Control': 'no-cache'
},
body: JSON.stringify({
2023-12-02 19:04:51 +01:00
'model_id': model_id,
'action': action
})
});
if (!apiResult.ok) {
2023-08-17 11:05:17 +02:00
toastr.error(apiResult.statusText, DEBUG_PREFIX + ' Install model ' + model_id + ' request failed');
throw new Error(`HTTP ${apiResult.status}: ${await apiResult.text()}`);
}
2023-12-02 20:11:06 +01:00
return apiResult;
}
/*
Retrieve user custom models
*/
static async getLocalModelList() {
2023-12-02 20:11:06 +01:00
throwIfModuleMissing();
const url = new URL(getApiUrl());
url.pathname = '/api/text-to-speech/coqui/local/get-models';
const apiResult = await doExtrasFetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Cache-Control': 'no-cache'
},
body: JSON.stringify({
2023-12-02 19:04:51 +01:00
'model_id': 'model_id',
'action': 'action'
})
2023-12-02 20:11:06 +01:00
});
if (!apiResult.ok) {
2023-08-17 11:05:17 +02:00
toastr.error(apiResult.statusText, DEBUG_PREFIX + ' Get local model list request failed');
throw new Error(`HTTP ${apiResult.status}: ${await apiResult.text()}`);
}
2023-12-02 20:11:06 +01:00
return apiResult;
}
// Expect voiceId format to be like:
// tts_models/multilingual/multi-dataset/your_tts[2][1]
// tts_models/en/ljspeech/glow-tts
// ts_models/ja/kokoro/tacotron2-DDC
async generateTts(text, voiceId) {
2023-12-02 20:11:06 +01:00
throwIfModuleMissing();
voiceId = this.settings.customVoices[voiceId];
2023-08-26 05:51:58 +02:00
const url = new URL(getApiUrl());
2023-08-14 04:03:28 +02:00
url.pathname = '/api/text-to-speech/coqui/generate-tts';
2023-12-02 20:11:06 +01:00
let language = 'none';
let speaker = 'none';
2023-12-02 19:04:51 +01:00
const tokens = voiceId.replaceAll(']', '').replaceAll('"', '').split('[');
2023-12-02 20:11:06 +01:00
const model_id = tokens[0];
2023-12-02 20:11:06 +01:00
console.debug(DEBUG_PREFIX, 'Preparing TTS request for', tokens);
// First option
if (tokens.length > 1) {
2023-12-02 20:11:06 +01:00
const option1 = tokens[1];
2023-12-02 19:04:51 +01:00
if (model_id.includes('multilingual'))
2023-12-02 20:11:06 +01:00
language = option1;
else
2023-12-02 20:11:06 +01:00
speaker = option1;
}
// Second option
if (tokens.length > 2)
speaker = tokens[2];
const apiResult = await doExtrasFetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Cache-Control': 'no-cache'
},
body: JSON.stringify({
2023-12-02 19:04:51 +01:00
'text': text,
'model_id': model_id,
'language_id': parseInt(language),
'speaker_id': parseInt(speaker)
})
});
if (!apiResult.ok) {
toastr.error(apiResult.statusText, 'TTS Generation Failed');
throw new Error(`HTTP ${apiResult.status}: ${await apiResult.text()}`);
}
2023-12-02 20:11:06 +01:00
return apiResult;
}
2023-08-14 04:03:28 +02:00
// Dirty hack to say not implemented
2023-08-26 05:51:58 +02:00
async fetchTtsVoiceObjects() {
const voiceIds = Object
.keys(this.settings.voiceMapDict)
.map(voice => ({ name: voice, voice_id: voice, preview_url: false }));
2023-12-02 20:11:06 +01:00
return voiceIds;
2023-08-14 04:03:28 +02:00
}
// Do nothing
previewTtsVoice(id) {
2023-12-02 20:11:06 +01:00
return;
2023-08-14 04:03:28 +02:00
}
async fetchTtsFromHistory(history_item_id) {
return Promise.resolve(history_item_id);
}
}
async function initLocalModels() {
if (!modules.includes('coqui-tts'))
2023-12-02 20:11:06 +01:00
return;
// Initialized local model once
2023-08-17 11:05:17 +02:00
if (!coquiLocalModelsReceived) {
let result = await CoquiTtsProvider.getLocalModelList();
result = await result.json();
2023-12-02 19:04:51 +01:00
coquiLocalModels = result['models_list'];
2023-12-02 19:04:51 +01:00
$('#coqui_local_model_name').show();
$('#coqui_local_model_name')
.find('option')
.remove()
.end()
.append('<option value="none">Select model</option>')
.val('none');
2023-08-17 11:05:17 +02:00
for (const model_dataset of coquiLocalModels)
2023-12-02 19:04:51 +01:00
$('#coqui_local_model_name').append(new Option(model_dataset, model_dataset));
coquiLocalModelsReceived = true;
}
}