mirror of
https://github.com/benbusby/whoogle-search.git
synced 2026-03-11 08:54:34 +00:00
adding speech-to-text func
This commit is contained in:
parent
5e4bfb1e2d
commit
09e36649a5
4 changed files with 210 additions and 68 deletions
91
app/static/js/speech.js
Normal file
91
app/static/js/speech.js
Normal 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.');
|
||||
};
|
||||
});
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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 %}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Reference in a new issue