voice / templates /index.html
AiDeveloper1's picture
Update templates/index.html
17fee24 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Voice Command</title>
<style>
.chat-container {
max-width: 400px;
margin: 20px auto;
padding: 10px;
border: 1px solid #ccc;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
font-family: Arial, sans-serif;
}
.user-message {
background-color: #f0f0f0;
border-radius: 5px;
padding: 5px 10px;
margin: 5px 0;
text-align: right;
}
.bot-message {
background-color: #d3e9ff;
border-radius: 5px;
padding: 5px 10px;
margin: 5px 0;
}
#languageSelector {
width: 100%;
margin-top: 10px;
padding: 5px;
border-radius: 5px;
border: 1px solid #ccc;
}
#status {
color: grey;
font-weight: 600;
margin-top: 10px;
text-align: center;
}
#permissionModal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
}
#permissionModal div {
background: white;
padding: 20px;
border-radius: 5px;
text-align: center;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
}
#permissionModal button {
margin: 10px;
padding: 10px 20px;
border: none;
border-radius: 5px;
background: #007bff;
color: white;
cursor: pointer;
}
#permissionModal button:hover {
background: #0056b3;
}
#testSpeakerButton {
display: block;
margin: 10px auto;
padding: 10px 20px;
border: none;
border-radius: 5px;
background: #28a745;
color: white;
cursor: pointer;
font-family: Arial, sans-serif;
font-weight: 600;
}
#testSpeakerButton:hover {
background: #218838;
}
</style>
</head>
<body>
<div id="permissionModal" style="display: none;">
<div>
<p>This site requires microphone and speaker permissions to enable voice input and output.</p>
<button id="grantPermissions">Grant Permissions</button>
</div>
</div>
<button id="testSpeakerButton">Speaker Test</button>
<div class="chat-container">
<div id="chat-box"></div>
<select id="languageSelector">
<option value="English (US)">English (US)</option>
<option value="Hindi (India)">Hindi (India)</option>
<option value="Spanish (Spain)">Spanish (Spain)</option>
<option value="French (France)">French (France)</option>
<option value="German (Germany)">German (Germany)</option>
<option value="Arabic (Saudi Arabia)">Arabic (Saudi Arabia)</option>
</select>
<div class="speaker" style="display: flex; justify-content: space-between; width: 100%; box-shadow: 0 0 13px #0000003d; border-radius: 5px; margin-top: 10px;">
<p id="action" style="color: grey; font-weight: 800; padding: 0; padding-left: 2rem;"></p>
<button id="speech" style="border: transparent; padding: 0 0.5rem;">
Tap to Speak
</button>
</div>
<p id="status"></p>
</div>
<script>
// Browser detection
function detectBrowser() {
const ua = navigator.userAgent.toLowerCase();
if (ua.includes('safari') && !ua.includes('chrome')) return 'Safari';
if (ua.includes('firefox')) return 'Firefox';
if (ua.includes('edg')) return 'Edge';
if (ua.includes('chrome')) return 'Chrome';
return 'Unknown';
}
const browser = detectBrowser();
const statusBar = document.getElementById('status');
const permissionModal = document.getElementById('permissionModal');
const grantPermissionsButton = document.getElementById('grantPermissions');
const testSpeakerButton = document.getElementById('testSpeakerButton');
// Language mapping
const speechLangMap = {
'English (US)': 'en-US',
'Hindi (India)': 'hi-IN',
'Spanish (Spain)': 'es-ES',
'French (France)': 'fr-FR',
'German (Germany)': 'de-DE',
'Arabic (Saudi Arabia)': 'ar-SA'
};
// Initialize speech synthesis
const synth = window.speechSynthesis || null;
// const synth = 'en-US';
let voices = [];
// Load voices asynchronously
function loadVoices() {
return new Promise((resolve) => {
if (!synth) {
statusBar.textContent = 'Text-to-speech not supported in this browser.';
resolve([]);
return;
}
voices = synth.getVoices();
if (voices.length > 0) {
resolve(voices);
} else {
synth.addEventListener('voiceschanged', () => {
voices = synth.getVoices();
resolve(voices);
}, { once: true });
}
});
}
// Speak text with fallback
async function speakResponse(text, language) {
if (!synth) {
statusBar.textContent = 'Text-to-speech is unavailable. Displaying text only.';
showBotMessage(text);
return;
}
// const langCode = speechLangMap[language] || 'en-US';
const langCode = 'en-US';
await loadVoices();
const utterance = new SpeechSynthesisUtterance(text);
let selectedVoice = voices.find(voice => voice.lang === langCode);
if (!selectedVoice) {
console.warn(`No voice for ${langCode}. Falling back to English.`);
selectedVoice = voices.find(voice => voice.lang.startsWith('en')) || voices[0];
statusBar.textContent = `Voice for ${language} unavailable. Using English voice.`;
}
if (selectedVoice) {
utterance.voice = selectedVoice;
utterance.lang = selectedVoice.lang;
} else {
statusBar.textContent = 'No voices available for text-to-speech.';
showBotMessage(text);
return;
}
utterance.volume = 1.0;
utterance.rate = 1.0;
utterance.pitch = 1.0;
utterance.onerror = (event) => {
console.error('TTS error:', event.error);
statusBar.textContent = 'Error in text-to-speech. Displaying text only.';
showBotMessage(text);
};
utterance.onend = () => {
console.log('TTS finished.');
statusBar.textContent = '';
};
if (synth.speaking || synth.paused) {
synth.cancel();
}
try {
synth.speak(utterance);
} catch (error) {
console.error('TTS failed:', error);
statusBar.textContent = 'Failed to play speech. Displaying text only.';
showBotMessage(text);
}
document.getElementById('speech').addEventListener('click', () => {
if (synth.speaking) synth.cancel();
}, { once: true });
}
// Initialize TTS and test speaker
async function testSpeaker() {
if (!synth) {
statusBar.textContent = 'Text-to-speech not supported in this browser.';
return false;
}
try {
await loadVoices();
// Silent utterance to unlock audio in Safari
const silentUtterance = new SpeechSynthesisUtterance('');
silentUtterance.volume = 0;
silentUtterance.onend = () => synth.cancel();
silentUtterance.onerror = (event) => {
console.error('Silent TTS error:', event.error);
synth.cancel();
};
synth.speak(silentUtterance);
await new Promise(resolve => setTimeout(resolve, 100));
synth.cancel();
// Test utterance
const selectedLang = document.getElementById('languageSelector').value;
// const langCode = speechLangMap[selectedLang] || 'en-US';
const langCode = 'en-US';
const utterance = new SpeechSynthesisUtterance('Speaker works fine');
let selectedVoice = voices.find(voice => voice.lang === langCode);
if (!selectedVoice) {
selectedVoice = voices.find(voice => voice.lang.startsWith('en')) || voices[0];
statusBar.textContent = `Voice for ${selectedLang} unavailable. Using English voice.`;
}
if (selectedVoice) {
utterance.voice = selectedVoice;
utterance.lang = selectedVoice.lang;
} else {
statusBar.textContent = 'No voices available for text-to-speech.';
return false;
}
utterance.volume = 1.0;
utterance.rate = 1.0;
utterance.pitch = 1.0;
utterance.onerror = (event) => {
console.error('Test TTS error:', event.error);
statusBar.textContent = 'Error testing speaker.';
};
utterance.onend = () => {
console.log('Test TTS finished.');
statusBar.textContent = 'Speaker test successful.';
};
synth.speak(utterance);
return true;
} catch (error) {
console.error('TTS test failed:', error);
statusBar.textContent = 'Failed to test speaker.';
return false;
}
}
// Request microphone permission
async function requestMicPermission() {
try {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
stream.getTracks().forEach(track => track.stop());
return true;
} catch (error) {
console.error('Microphone permission error:', error);
statusBar.textContent = 'Microphone access denied. Voice input unavailable.';
return false;
}
}
// Check and request all permissions
async function checkAndRequestPermissions() {
if (browser !== 'Safari') return true;
try {
const permissionStatus = await navigator.permissions.query({ name: 'microphone' });
if (permissionStatus.state === 'granted') {
return await testSpeaker(); // Test speaker if mic is granted
}
} catch (error) {
console.warn('Permission query not supported:', error);
}
permissionModal.style.display = 'flex';
return new Promise((resolve) => {
grantPermissionsButton.onclick = async () => {
permissionModal.style.display = 'none';
const micGranted = await requestMicPermission();
const ttsReady = micGranted ? await testSpeaker() : false;
if (!micGranted || !ttsReady) {
statusBar.textContent = 'Some permissions were not granted. Features may be limited.';
}
resolve(micGranted && ttsReady);
};
});
}
// Speech recognition
function runSpeechRecog() {
const selectedLang = document.getElementById('languageSelector').value;
const action = document.getElementById('action');
if (!window.SpeechRecognition && !window.webkitSpeechRecognition) {
statusBar.textContent = 'Speech recognition not supported in this browser.';
return;
}
const recognition = new (window.SpeechRecognition || window.webkitSpeechRecognition)();
// recognition.lang = speechLangMap[selectedLang] || 'en-US';
recognition.lang = 'en-US';
recognition.interimResults = false;
recognition.continuous = false;
recognition.onstart = () => {
action.textContent = 'Listening...';
statusBar.textContent = '';
};
recognition.onresult = (event) => {
const transcript = event.results[0][0].transcript;
action.textContent = '';
sendMessage(transcript, selectedLang);
};
recognition.onerror = (event) => {
action.textContent = '';
statusBar.textContent = `Speech recognition error: ${event.error}`;
};
recognition.onend = () => {
action.textContent = '';
};
try {
recognition.start();
} catch (error) {
statusBar.textContent = 'Failed to start speech recognition.';
console.error('STT error:', error);
}
}
// Send message to Flask API
async function sendMessage(message, language) {
showUserMessage(message);
try {
const response = await fetch('/api/process_text', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text: message, language })
});
const data = await response.json();
console.log('Response from Flask API:', data);
handleResponse(data);
} catch (error) {
console.error('Error sending data to Flask API:', error);
statusBar.textContent = 'Error: Unable to process request';
showBotMessage('Error: Unable to process request');
}
}
// Handle API response
function handleResponse(data) {
if (data.error) {
statusBar.textContent = data.error;
showBotMessage(data.error);
return;
}
showBotMessage(data.response);
speakResponse(data.response, data.language);
}
// Show user message
function showUserMessage(message) {
const chatBox = document.getElementById('chat-box');
chatBox.innerHTML += `<div class="user-message">${message}</div>`;
chatBox.scrollTop = chatBox.scrollHeight;
}
// Show bot message
function showBotMessage(message) {
const chatBox = document.getElementById('chat-box');
chatBox.innerHTML += `<div class="bot-message">${message}</div>`;
chatBox.scrollTop = chatBox.scrollHeight;
}
// Initialize
window.addEventListener('load', async () => {
await loadVoices();
if (browser === 'Safari') {
statusBar.textContent = 'Safari detected. Please grant microphone and speaker permissions.';
const permissionsGranted = await checkAndRequestPermissions();
if (!permissionsGranted) {
statusBar.textContent = 'Permissions denied. Some features may not work.';
}
}
document.getElementById('speech').addEventListener('click', runSpeechRecog);
testSpeakerButton.addEventListener('click', testSpeaker);
});
// Clean up on unload
window.addEventListener('beforeunload', () => {
if (synth && synth.speaking) synth.cancel();
});
</script>
</body>
</html>