torusvektor's picture
potrebuji aby se to dalo pouzit offline, abych bud mohl pozadovany jazyk CS-čestina pridat do offline reci v mobilu pri volbe klavesnice a hlasoveho zadavani google offline nebo mel moznost nainstalovat novou klavesnici s podporou offline hlasoveho zadavani s moznosti i českeho jazyka cs-CZ - Initial Deployment
24678c3 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Offline Speech Recognition & Synthesis</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<script>
tailwind.config = {
theme: {
extend: {
colors: {
primary: '#3b82f6',
secondary: '#1e40af',
dark: '#0f172a',
light: '#f8fafc'
}
}
}
}
</script>
<style>
.speech-bubble {
position: relative;
background: #e0f2ff;
border-radius: 12px;
padding: 20px;
margin: 20px 0;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.speech-bubble:after {
content: '';
position: absolute;
bottom: -15px;
left: 50px;
border-width: 15px 15px 0;
border-style: solid;
border-color: #e0f2ff transparent transparent;
display: block;
width: 0;
}
.pulse {
animation: pulse 1.5s infinite;
}
@keyframes pulse {
0% {
box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.4);
}
70% {
box-shadow: 0 0 0 10px rgba(59, 130, 246, 0);
}
100% {
box-shadow: 0 0 0 0 rgba(59, 130, 246, 0);
}
}
.mic-icon {
transition: all 0.3s ease;
}
.mic-active {
color: #ef4444;
transform: scale(1.1);
}
.mic-inactive {
color: #94a3b8;
}
.history-item {
transition: all 0.3s ease;
}
.history-item:hover {
background-color: #f1f5f9;
}
.scrollbar-thin::-webkit-scrollbar {
width: 6px;
}
.scrollbar-thin::-webkit-scrollbar-track {
background: #f1f5f9;
border-radius: 10px;
}
.scrollbar-thin::-webkit-scrollbar-thumb {
background: #cbd5e1;
border-radius: 10px;
}
.scrollbar-thin::-webkit-scrollbar-thumb:hover {
background: #94a3b8;
}
</style>
</head>
<body class="bg-gradient-to-br from-blue-50 to-indigo-100 min-h-screen p-4 md:p-8">
<div class="max-w-4xl mx-auto">
<!-- Header -->
<header class="text-center py-6 mb-8">
<h1 class="text-3xl md:text-4xl font-bold text-dark">Offline Speech Recognition & Synthesis</h1>
<p class="text-gray-600 mt-2">Convert speech to text and text to speech directly in your browser</p>
</header>
<!-- Main Container -->
<div class="bg-white rounded-2xl shadow-xl overflow-hidden">
<!-- Tabs -->
<div class="flex border-b">
<button id="speechToTextTab" class="flex-1 py-4 px-6 text-center font-medium bg-primary text-white">
<i class="fas fa-microphone mr-2"></i>Speech to Text
</button>
<button id="textToSpeechTab" class="flex-1 py-4 px-6 text-center font-medium bg-gray-100 text-gray-600 hover:bg-gray-200">
<i class="fas fa-volume-up mr-2"></i>Text to Speech
</button>
</div>
<!-- Speech to Text Panel -->
<div id="speechToTextPanel" class="p-6">
<div class="flex flex-col items-center justify-center py-8">
<div class="relative">
<button id="startRecognition" class="mic-icon pulse bg-primary rounded-full p-6 text-white hover:bg-secondary transition-all">
<i class="fas fa-microphone text-4xl"></i>
</button>
<div id="listeningIndicator" class="absolute -top-2 -right-2 bg-red-500 text-white text-xs px-2 py-1 rounded-full hidden">
Listening...
</div>
</div>
<p class="mt-4 text-gray-600 text-center">Click the microphone to start speech recognition</p>
<div class="w-full mt-8">
<div class="speech-bubble">
<p id="recognizedText" class="text-lg text-gray-800 min-h-24">
Your speech will appear here...
</p>
</div>
</div>
<div class="w-full mt-6">
<h3 class="font-semibold text-gray-700 mb-2">Recognition History</h3>
<div id="historyContainer" class="bg-gray-50 rounded-lg p-4 h-40 overflow-y-auto scrollbar-thin">
<p class="text-gray-500 text-sm">No history yet...</p>
</div>
</div>
</div>
</div>
<!-- Text to Speech Panel -->
<div id="textToSpeechPanel" class="hidden p-6">
<div class="flex flex-col md:flex-row gap-6">
<div class="flex-1">
<label for="textInput" class="block text-gray-700 font-medium mb-2">Enter text to speak:</label>
<textarea id="textInput" class="w-full h-40 p-4 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent" placeholder="Type something here...">Hello! This is a demonstration of text to speech conversion. You can type anything here and click the play button to hear it spoken aloud.</textarea>
<div class="mt-4 flex items-center">
<label for="voiceSelect" class="mr-2 text-gray-700">Voice:</label>
<select id="voiceSelect" class="border border-gray-300 rounded p-2 flex-1"></select>
<button id="playText" class="ml-4 bg-primary hover:bg-secondary text-white px-6 py-2 rounded-lg transition">
<i class="fas fa-play mr-2"></i>Play
</button>
</div>
</div>
<div class="flex-1">
<div class="bg-blue-50 rounded-xl p-6 h-64 flex flex-col justify-center items-center">
<div class="text-center">
<i class="fas fa-volume-up text-primary text-5xl mb-4"></i>
<h3 class="text-xl font-semibold text-gray-800">Text to Speech</h3>
<p class="text-gray-600 mt-2">Enter text and click play to hear it spoken</p>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Info Section -->
<div class="mt-8 bg-white rounded-2xl shadow-lg p-6">
<h2 class="text-xl font-bold text-dark mb-4">How It Works</h2>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div class="bg-blue-50 p-4 rounded-lg">
<div class="text-primary text-2xl mb-2"><i class="fas fa-microphone"></i></div>
<h3 class="font-semibold text-gray-800">Speech Recognition</h3>
<p class="text-gray-600 text-sm mt-1">Speak into your microphone and see your words appear in real-time. For offline Czech support, ensure you have Czech language pack installed in your device settings.</p>
</div>
<div class="bg-indigo-50 p-4 rounded-lg">
<div class="text-primary text-2xl mb-2"><i class="fas fa-volume-up"></i></div>
<h3 class="font-semibold text-gray-800">Text to Speech</h3>
<p class="text-gray-600 text-sm mt-1">Convert any text to speech with natural sounding voices. Czech voice support depends on your system's available voices.</p>
</div>
<div class="bg-blue-50 p-4 rounded-lg">
<div class="text-primary text-2xl mb-2"><i class="fas fa-shield-alt"></i></div>
<h3 class="font-semibold text-gray-800">Privacy Focused</h3>
<p class="text-gray-600 text-sm mt-1">All processing happens locally in your browser - no data leaves your device. Works offline when language packs are installed.</p>
</div>
</div>
</div>
<!-- Footer -->
<footer class="mt-8 text-center text-gray-600 text-sm">
<p>Offline Speech Recognition & Synthesis | Works directly in your browser</p>
<p class="mt-2 text-xs">For Czech language offline support: Install Czech language pack in your device settings or use a keyboard app with offline voice input capabilities.</p>
</footer>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Tab switching
const speechToTextTab = document.getElementById('speechToTextTab');
const textToSpeechTab = document.getElementById('textToSpeechTab');
const speechToTextPanel = document.getElementById('speechToTextPanel');
const textToSpeechPanel = document.getElementById('textToSpeechPanel');
speechToTextTab.addEventListener('click', () => {
speechToTextTab.classList.add('bg-primary', 'text-white');
speechToTextTab.classList.remove('bg-gray-100', 'text-gray-600');
textToSpeechTab.classList.add('bg-gray-100', 'text-gray-600');
textToSpeechTab.classList.remove('bg-primary', 'text-white');
speechToTextPanel.classList.remove('hidden');
textToSpeechPanel.classList.add('hidden');
});
textToSpeechTab.addEventListener('click', () => {
textToSpeechTab.classList.add('bg-primary', 'text-white');
textToSpeechTab.classList.remove('bg-gray-100', 'text-gray-600');
speechToTextTab.classList.add('bg-gray-100', 'text-gray-600');
textToSpeechTab.classList.remove('bg-primary', 'text-white');
textToSpeechPanel.classList.remove('hidden');
speechToTextPanel.classList.add('hidden');
});
// Speech Recognition
const startRecognition = document.getElementById('startRecognition');
const recognizedText = document.getElementById('recognizedText');
const listeningIndicator = document.getElementById('listeningIndicator');
const historyContainer = document.getElementById('historyContainer');
let recognition;
let isListening = false;
// Check if SpeechRecognition is available
if ('webkitSpeechRecognition' in window || 'SpeechRecognition' in window) {
// Initialize speech recognition
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
recognition = new SpeechRecognition();
recognition.continuous = true;
recognition.interimResults = true;
recognition.lang = 'cs-CZ'; // Set default language to Czech
recognition.onstart = function() {
isListening = true;
startRecognition.classList.add('mic-active');
startRecognition.classList.remove('mic-inactive');
listeningIndicator.classList.remove('hidden');
recognizedText.textContent = "Listening...";
};
recognition.onresult = function(event) {
let interimTranscript = '';
let finalTranscript = '';
for (let i = event.resultIndex; i < event.results.length; i++) {
const transcript = event.results[i][0].transcript;
if (event.results[i].isFinal) {
finalTranscript += transcript + ' ';
} else {
interimTranscript += transcript;
}
}
recognizedText.textContent = finalTranscript || interimTranscript || "Listening...";
if (finalTranscript) {
// Add to history
addToHistory(finalTranscript);
}
};
recognition.onerror = function(event) {
console.error('Speech recognition error', event.error);
recognizedText.textContent = "Error: " + event.error;
stopListening();
};
recognition.onend = function() {
isListening = false;
startRecognition.classList.remove('mic-active');
startRecognition.classList.add('mic-inactive');
listeningIndicator.classList.add('hidden');
};
startRecognition.addEventListener('click', function() {
if (isListening) {
recognition.stop();
} else {
try {
recognition.start();
} catch (e) {
console.error('Recognition error:', e);
recognizedText.textContent = "Error starting recognition: " + e.message;
}
}
});
} else {
// Speech recognition not supported
recognizedText.textContent = "Speech recognition is not supported in this browser.";
startRecognition.disabled = true;
startRecognition.classList.add('bg-gray-400');
}
function stopListening() {
isListening = false;
startRecognition.classList.remove('mic-active');
startRecognition.classList.add('mic-inactive');
listeningIndicator.classList.add('hidden');
}
function addToHistory(text) {
const historyItem = document.createElement('div');
historyItem.className = 'history-item p-3 mb-2 bg-white rounded-lg shadow-sm';
historyItem.innerHTML = `
<div class="flex items-start">
<i class="fas fa-comment text-primary mt-1 mr-2"></i>
<div>
<p class="text-gray-800">${text}</p>
<p class="text-xs text-gray-500 mt-1">${new Date().toLocaleTimeString()}</p>
</div>
</div>
`;
historyContainer.prepend(historyItem);
// Keep only the last 5 items
if (historyContainer.children.length > 5) {
historyContainer.removeChild(historyContainer.lastChild);
}
}
// Text to Speech
const textInput = document.getElementById('textInput');
const voiceSelect = document.getElementById('voiceSelect');
const playText = document.getElementById('playText');
let voices = [];
function populateVoiceList() {
voices = window.speechSynthesis.getVoices();
voiceSelect.innerHTML = '';
voices.forEach((voice, i) => {
const option = document.createElement('option');
option.textContent = voice.name + ' (' + voice.lang + ')';
option.setAttribute('data-lang', voice.lang);
option.setAttribute('data-name', voice.name);
voiceSelect.appendChild(option);
});
// Select a default voice
voiceSelect.selectedIndex = 0;
}
populateVoiceList();
if (window.speechSynthesis.onvoiceschanged !== undefined) {
window.speechSynthesis.onvoiceschanged = populateVoiceList;
}
playText.addEventListener('click', function() {
const text = textInput.value;
if (text) {
const utterance = new SpeechSynthesisUtterance(text);
utterance.voice = voices[voiceSelect.selectedIndex];
window.speechSynthesis.speak(utterance);
}
});
// Initialize with sample history
setTimeout(() => {
addToHistory("This is a sample history item");
addToHistory("Another example of speech recognition");
}, 1000);
});
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=torusvektor/offline-speech-recognition-synthesis" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>