$(document).ready(function() {
// --- DOM Element References ---
const amharicTextInput = $('#amharicText');
const ttsModelSelect = $('#ttsModel');
const speedInput = $('#speed');
const speedValueDisplay = $('#speed-value');
const generateBtn = $('#generateBtn');
const statusMessage = $('#statusMessage');
const audioPlayer = $('#audioPlayer');
const downloadBtn = $('#downloadBtn');
// --- Other References ---
const API_URL = '/api/tts';
let lastGeneratedAudioUrl = null; // Variable to store the blob URL
// --- Event Listeners ---
ttsModelSelect.on('change', updateConfigVisibility);
// THIS IS THE CORRECTED LINE
speedInput.on('input', function() {
speedValueDisplay.text($(this).val() + 'x');
});
generateBtn.on('click', function() {
const text = amharicTextInput.val();
if (!text.trim()) {
alert('Please enter some Amharic text.');
return;
}
const requestData = buildRequestData();
console.log("Sending data to backend:", JSON.stringify(requestData, null, 2));
// --- 1. Set UI to loading state ---
setLoadingState(true);
fetch(API_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(requestData),
})
.then(handleResponse)
.then(data => {
if (data instanceof Blob) {
console.log("Received audio blob from backend!");
if (lastGeneratedAudioUrl) {
URL.revokeObjectURL(lastGeneratedAudioUrl);
}
lastGeneratedAudioUrl = URL.createObjectURL(data);
audioPlayer.attr('src', lastGeneratedAudioUrl);
setControlsEnabled(true);
showStatusMessage("Audio generated successfully!", "success");
}
})
.catch(error => {
console.error('Error during fetch operation:', error);
showStatusMessage(`Error: ${error.message}`, "danger");
setControlsEnabled(false);
})
.finally(() => {
setLoadingState(false);
});
});
downloadBtn.on('click', function() {
if (!lastGeneratedAudioUrl) return;
const link = document.createElement('a');
link.href = lastGeneratedAudioUrl;
// Choose file extension based on model
const model = ttsModelSelect.val();
const ext = (model === 'mms') ? 'wav' : 'mp3';
link.download = `amharic_speech.${ext}`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
});
// --- Helper Functions ---
function buildRequestData() {
const model = ttsModelSelect.val();
let modelSettings = {};
switch (model) {
case 'openai': modelSettings.voice = $('#openai-voice').val(); break;
case 'azure': modelSettings.voice = $('#azure-voice').val(); modelSettings.style = $('#azure-style').val(); break;
case 'mms': modelSettings.voice = $('#mms-voice').val(); break;
}
return {
text: amharicTextInput.val(),
model: model,
speed: speedInput.val(),
settings: modelSettings
};
}
function setLoadingState(isLoading) {
if (isLoading) {
generateBtn.prop('disabled', true).html(' Generating...');
statusMessage.html('');
setControlsEnabled(false);
} else {
generateBtn.prop('disabled', false).html(' ድምፅ ፍጠር (Generate)');
}
}
function setControlsEnabled(isEnabled) {
if (isEnabled) {
audioPlayer.removeAttr('disabled');
downloadBtn.removeAttr('disabled');
} else {
audioPlayer.attr('disabled', 'disabled');
downloadBtn.attr('disabled', 'disabled');
}
}
function showStatusMessage(message, type) {
statusMessage.html(`
${message}
`);
}
async function handleResponse(response) {
if (!response.ok) {
let errJson = {};
try { errJson = await response.json(); } catch (e) { /* ignore */ }
const msg = errJson.details ? `${errJson.error || 'Server returned an error'} - ${errJson.details}` : (errJson.error || 'Server returned an error');
throw new Error(msg);
}
if (response.headers.get("Content-Type").includes("audio")) {
return response.blob();
}
return response.json();
}
function updateConfigVisibility() {
$('.model-config').hide();
const selectedModel = ttsModelSelect.val();
switch (selectedModel) {
case 'openai': $('#openai-config').show(); $('#speed-config').show(); break;
case 'azure': $('#azure-config').show(); $('#speed-config').show(); break;
case 'mms': $('#mms-config').show(); $('#speed-config').show(); break;
case 'gtts': $('#speed-config').show(); break;
}
}
// --- Initial page setup ---
const spinningGlyph = ``;
$('head').append(spinningGlyph);
updateConfigVisibility();
});
(function () {
var $text = $('#amharicText');
var $char = $('#charCount');
var $model = $('#ttsModel');
var $speed = $('#speed');
var $speedVal = $('#speed-value');
var $status = $('#statusMessage');
var $gen = $('#generateBtn');
var $spinner = $('#genSpinner');
var $audio = $('#audioPlayer');
var $download = $('#downloadBtn');
function updateCharCount() {
var n = ($text.val() || '').length;
$char.text(n + ' characters');
savePrefs();
}
function updateSpeedValue() {
$speedVal.text(parseFloat($speed.val()).toFixed(1) + 'x');
savePrefs();
}
function showModelConfig() {
var v = $model.val();
$('.model-config').hide();
if (v === 'openai') $('#openai-config').show();
if (v === 'azure') $('#azure-config').show();
if (v === 'mms') $('#mms-config').show();
// Hint about output format
var hint = (v === 'mms') ? 'Output: WAV for MMS.' : 'Output: MP3 for gTTS/OpenAI/Azure.';
$('#formatHint').text(hint);
savePrefs();
}
function loadPrefs() {
try {
var p = JSON.parse(localStorage.getItem('ta_prefs') || '{}');
if (p.text) $text.val(p.text);
if (p.model) $model.val(p.model);
if (p.speed) $speed.val(p.speed);
} catch (e) {}
}
function savePrefs() {
try {
localStorage.setItem('ta_prefs', JSON.stringify({
text: $text.val() || '',
model: $model.val(),
speed: $speed.val()
}));
} catch (e) {}
}
function bindButtons() {
$('#sampleBtn').on('click', function () {
var sample = 'ሰላም! ይህ ሳምፕል ጽሑፍ ነው። በቀላሉ የአማርኛ ንግግር ድምፅ ለመፍጠር፤ መጀመሪያ የአማርኛ ጽሁፉን እዚህ ይጻፉ ወይ ኮፒ ፔስት ያድርጉት። ቀጥሎ በስተቀኝ ያሉትን ማስተካከያዎች ያስተካክሉ። ከዛም ድምጽ ፍጠር የሚለውን በተን ይጫኑ። ከትንሽ ቆይታዎች በኋላ የተፈጠረውን ድምፅ ማጫወት ወም ማውረድ ይችላሉ። መልካም ግዜ። ';
$text.val(sample).trigger('input');
});
$('#clearBtn').on('click', function () {
$text.val('').trigger('input');
$text.focus();
});
}
// Optional helper: Call window.uiSetLoading(true/false) around your TTS request
function setLoading(isLoading, msg) {
if (isLoading) {
$gen.prop('disabled', true);
$spinner.show();
$status
.removeClass('alert-danger alert-success')
.addClass('alert alert-info')
.text(msg || 'Generating audio...');
} else {
$gen.prop('disabled', false);
$spinner.hide();
}
}
window.uiSetLoading = setLoading;
// When audio loads, enable controls and show success
$audio.on('loadeddata', function () {
$audio.prop('disabled', false);
$download.prop('disabled', false);
$status
.removeClass('alert-info alert-danger')
.addClass('alert alert-success')
.text('Ready. Press play or download.');
});
$(function () {
$('[data-toggle="tooltip"]').tooltip();
loadPrefs();
updateCharCount();
updateSpeedValue();
showModelConfig();
bindButtons();
$text.on('input', updateCharCount);
$model.on('change', showModelConfig);
$speed.on('input change', updateSpeedValue);
});
})();
$(function () {
// Smooth scroll for in-page links
$('a[href^="#"]').on('click', function (e) {
var target = this.getAttribute('href');
if (target.length > 1 && $(target).length) {
e.preventDefault();
$('html, body').animate({ scrollTop: $(target).offset().top - 60 }, 400);
}
});
// Back to top visibility
var $top = $('#backToTop');
$(window).on('scroll', function () {
if ($(this).scrollTop() > 200) $top.fadeIn();
else $top.fadeOut();
});
});