Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 91 additions & 0 deletions app/static/js/speech.js
Original file line number Diff line number Diff line change
@@ -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.');
};
});
123 changes: 74 additions & 49 deletions app/templates/header.html
Original file line number Diff line number Diff line change
Expand Up @@ -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">
Expand Down Expand Up @@ -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>
Expand Down Expand Up @@ -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>
59 changes: 42 additions & 17 deletions app/templates/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -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() %}
Expand Down Expand Up @@ -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">
Expand All @@ -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 %}
Expand Down
5 changes: 3 additions & 2 deletions app/templates/search.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@
<input
type="text"
name="q"
style="width: 90%;"
style="width: 84%;"
autofocus="autofocus"
autocapitalize="none"
spellcheck="false"
autocorrect="off"
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