adding speech-to-text func

This commit is contained in:
saifxyzyz 2025-10-31 18:42:12 +05:30
parent 5e4bfb1e2d
commit 09e36649a5
4 changed files with 210 additions and 68 deletions

91
app/static/js/speech.js Normal file
View file

@ -0,0 +1,91 @@
document.addEventListener('DOMContentLoaded', () => {
const speechToTextBtns = document.querySelectorAll('.speech-to-text-btn');
if (speechToTextBtns.length === 0) {
console.warn('Speech-to-text buttons not found.');
return;
}
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
if (!SpeechRecognition) {
console.warn('Web Speech API is not supported in this browser.');
speechToTextBtns.forEach(btn => btn.style.display = 'none'); // Hide all buttons if API not supported
return;
}
const recognition = new SpeechRecognition();
recognition.continuous = false;
recognition.lang = 'en-US';
recognition.interimResults = false;
recognition.maxAlternatives = 1;
let activeBtn = null;
let activeSearchInput = null;
let activeSearchForm = null;
speechToTextBtns.forEach(btn => {
btn.addEventListener('click', () => {
activeBtn = btn;
activeSearchForm = btn.closest('form');
if (activeSearchForm) {
activeSearchInput = activeSearchForm.querySelector('input[name="q"]');
}
if (!activeSearchInput) {
console.error('Could not find search input associated with the button.');
return;
}
activeBtn.disabled = true;
activeBtn.textContent = '🔴'; // Indicate listening
activeSearchInput.placeholder = 'Listening...';
recognition.start();
});
});
recognition.onresult = (event) => {
if (!activeSearchInput) return;
const speechResult = event.results[0][0].transcript;
activeSearchInput.value = speechResult;
console.log('Speech result: ' + speechResult);
console.log('Confidence: ' + event.results[0][0].confidence);
};
recognition.onspeechend = () => {
recognition.stop();
if (activeBtn) {
activeBtn.disabled = false;
activeBtn.textContent = '🎙️'; // Reset button text
}
if (activeSearchInput) {
activeSearchInput.placeholder = 'Whoogle Search';
}
if (activeSearchForm) {
activeSearchForm.submit(); // Automatically submit the form
}
};
recognition.onerror = (event) => {
if (activeBtn) {
activeBtn.disabled = false;
activeBtn.textContent = '🎙️'; // Reset button text
}
if (activeSearchInput) {
activeSearchInput.placeholder = 'Whoogle Search';
}
console.error('Speech recognition error: ' + event.error);
if (event.error === 'no-speech') {
alert('No speech was detected. Please try again.');
} else if (event.error === 'not-allowed') {
alert('Microphone access was denied. Please allow access to use speech-to-text.');
} else {
alert('An error occurred during speech recognition: ' + event.error);
}
};
recognition.onnomatch = () => {
console.log('Speech not recognized.');
alert('Could not understand speech. Please try again.');
};
});

View file

@ -9,31 +9,31 @@
{{ logo|safe }}
</div>
</a>
<div class="H0PQec mobile-input-div">
<div class="autocomplete-mobile esbc autocomplete">
{% if config.preferences %}
<input type="hidden" name="preferences" value="{{ config.preferences }}" />
{% endif %}
<input
id="search-bar"
class="mobile-search-bar"
autocapitalize="none"
autocomplete="off"
autocorrect="off"
spellcheck="false"
class="search-bar-input"
name="q"
type="text"
value="{{ clean_query(query) }}"
dir="auto">
<input id="search-reset" type="reset" value="x">
<input name="tbm" value="{{ search_type }}" style="display: none">
<input name="country" value="{{ config.country }}" style="display: none;">
<input type="submit" style="display: none;">
<div class="sc"></div>
</div>
</div>
</form>
<div class="H0PQec mobile-input-div">
<div class="autocomplete-mobile esbc autocomplete search-input-container">
{% if config.preferences %}
<input type="hidden" name="preferences" value="{{ config.preferences }}" />
{% endif %}
<input
id="search-bar"
class="mobile-search-bar"
autocapitalize="none"
autocomplete="off"
autocorrect="off"
spellcheck="false"
class="search-bar-input"
name="q"
type="text"
value="{{ clean_query(query) }}"
dir="auto">
<button type="button" id="speech-to-text-btn-header-mobile" class="speech-to-text-btn">🎙️</button>
<input id="search-reset" type="reset" value="x">
<input name="tbm" value="{{ search_type }}" style="display: none">
<input name="country" value="{{ config.country }}" style="display: none;">
<input type="submit" style="display: none;">
<div class="sc"></div>
</div>
</div> </form>
</div>
<div>
<div class="header-tab-div">
@ -71,30 +71,30 @@
class="search-form"
id="sf"
method="{{ 'GET' if config.get_only else 'POST' }}">
<div class="autocomplete header-autocomplete">
<div style="width: 100%; display: flex">
{% if config.preferences %}
<input type="hidden" name="preferences" value="{{ config.preferences }}" />
{% endif %}
<input
id="search-bar"
autocapitalize="none"
autocomplete="off"
autocorrect="off"
class="search-bar-desktop search-bar-input"
name="q"
spellcheck="false"
type="text"
value="{{ clean_query(query) }}"
dir="auto">
<input name="tbm" value="{{ search_type }}" style="display: none">
<input name="country" value="{{ config.country }}" style="display: none;">
<input name="tbs" value="{{ config.tbs }}" style="display: none;">
<input type="submit" style="display: none;">
<div class="sc"></div>
</div>
</div>
</form>
<div class="autocomplete header-autocomplete search-input-container">
<div style="width: 100%; display: flex">
{% if config.preferences %}
<input type="hidden" name="preferences" value="{{ config.preferences }}" />
{% endif %}
<input
id="search-bar"
autocapitalize="none"
autocomplete="off"
autocorrect="off"
class="search-bar-desktop search-bar-input"
name="q"
spellcheck="false"
type="text"
value="{{ clean_query(query) }}"
dir="auto">
<button type="button" id="speech-to-text-btn-header-desktop" class="speech-to-text-btn">🎙️</button>
<input name="tbm" value="{{ search_type }}" style="display: none">
<input name="country" value="{{ config.country }}" style="display: none;">
<input name="tbs" value="{{ config.tbs }}" style="display: none;">
<input type="submit" style="display: none;">
<div class="sc"></div>
</div>
</div> </form>
</div>
</header>
<div>
@ -160,3 +160,28 @@
{% else %}
<script type="text/javascript" src="{{ cb_url('header.js') }}"></script>
{% endif %}
<script type="text/javascript" src="{{ cb_url('speech.js') }}"></script>
<style>
.search-input-container {
position: relative;
display: flex;
align-items: center;
}
.search-input-container input[type="text"] {
padding-right: 40px;
flex-grow: 1;
}
.search-input-container .speech-to-text-btn {
position: absolute;
right: 5px;
background: none !important;
border: none !important;
cursor: pointer;
font-size: 1.2em;
padding: 0;
line-height: 1;
z-index: 10;
}
</style>

View file

@ -25,6 +25,7 @@
{% endif %}
<script type="text/javascript" src="{{ cb_url('controller.js') }}"></script>
{% endif %}
<script type="text/javascript" src="{{ cb_url('speech.js') }}"></script>
<link rel="search" href="opensearch.xml" type="application/opensearchdescription+xml" title="Whoogle Search">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
{% if bundle_static() %}
@ -66,6 +67,30 @@
</style>
</noscript>
<style>{{ config.style }}</style>
<style>
.search-input-container {
position: relative;
display: flex;
align-items: center;
}
.search-input-container input[type="text"] {
padding-right: 40px; /* Make space for the microphone icon */
flex-grow: 1;
}
.search-input-container .speech-to-text-btn {
position: absolute;
right: 5px;
background: none !important;
border: none !important;
cursor: pointer;
font-size: 1.2em;
padding: 0;
line-height: 1;
z-index: 10;
}
</style>
<title>Whoogle Search</title>
</head>
<body id="main">
@ -75,23 +100,23 @@
</div>
<form id="search-form" action="search" method="{{ 'get' if config.get_only else 'post' }}">
<div class="search-fields">
<div class="autocomplete">
{% if config.preferences %}
<input type="hidden" name="preferences" value="{{ config.preferences }}" />
{% endif %}
<input
type="text"
name="q"
id="search-bar"
class="home-search"
autofocus="autofocus"
autocapitalize="none"
spellcheck="false"
autocorrect="off"
autocomplete="off"
dir="auto">
</div>
<input type="submit" id="search-submit" value="{{ translation['search'] }}">
<div class="autocomplete search-input-container">
{% if config.preferences %}
<input type="hidden" name="preferences" value="{{ config.preferences }}" />
{% endif %}
<input
type="text"
name="q"
id="search-bar"
class="home-search"
autofocus="autofocus"
autocapitalize="none"
spellcheck="false"
autocorrect="off"
autocomplete="off"
dir="auto">
<button type="button" class="speech-to-text-btn">🎙️</button>
</div> <input type="submit" id="search-submit" value="{{ translation['search'] }}">
</div>
</form>
{% if not config_disabled %}

View file

@ -2,7 +2,7 @@
<input
type="text"
name="q"
style="width: 90%;"
style="width: 84%;"
autofocus="autofocus"
autocapitalize="none"
spellcheck="false"
@ -10,5 +10,6 @@
placeholder="Whoogle Search"
autocomplete="off"
dir="auto">
<input type="submit" style="width: 9%" id="search-submit" value="Search">
<button type="button" id="speech-to-text-btn" class="speech-to-text-btn">🎙️</button>
<input type="submit" style="width: 8%" id="search-submit" value="Search">
</form>