Improved versions of this default script:
Script with a 4-second pause before stopping:
If you want to change the time (for example, to 5 or 6 seconds), just change 4000 to 5000 or 6000:
<script>
(function() {
const MIC_CLASS = 'mic-button';
function injectMic() {
document.querySelectorAll('div.flex.items-center.gap-x-2').forEach(container => {
if (container.querySelector(`button.${MIC_CLASS}`)) return;
const btn = document.createElement('button');
btn.className = `${MIC_CLASS} outline-none w-8 h-8 flex items-center justify-center rounded-full duration-200 transition-colors ease-in-out`;
btn.style.backgroundColor = 'rgba(0, 0, 0, 0.063)';
btn.style.marginLeft = '4px';
btn.innerHTML = `
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
class="h-6 w-6 shrink-0 duration-200 transition-colors ease-in-out"
style="color: rgba(0, 0, 0, 0.5);">
<path fill="currentColor"
d="M12 14a3 3 0 0 0 3-3V6a3 3 0 0 0-6 0v5a3 3 0 0 0 3 3Zm5-3a5 5 0 0 1-10 0H5a7 7 0 0 0 14 0h-2ZM11 21h2v-2h-2v2Z"/>
</svg>
`;
btn.addEventListener('click', () => {
const ta = document.querySelector('textarea.resize-none');
if (!ta || !('webkitSpeechRecognition' in window)) {
return alert('Your browser does not support SpeechRecognition');
}
let finalTranscript = '';
const rec = new webkitSpeechRecognition();
rec.lang = 'es-ES';
rec.interimResults = true;
rec.maxAlternatives = 1;
rec.continuous = true;
let pauseTimer = null;
rec.start();
rec.onresult = e => {
clearTimeout(pauseTimer);
let interim = '';
for (let i = e.resultIndex; i < e.results.length; i++) {
const r = e.results[i];
if (r.isFinal) finalTranscript += r[0].transcript + ' ';
else interim += r[0].transcript;
}
const text = (finalTranscript + interim).trimEnd();
const setter = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value').set;
setter.call(ta, text);
ta.dispatchEvent(new Event('input', { bubbles: true }));
pauseTimer = setTimeout(() => {
rec.stop();
}, 4000);
};
rec.onerror = err => console.error('SpeechRecognition Error:', err.error);
rec.onend = () => {
clearTimeout(pauseTimer);
ta.dispatchEvent(new KeyboardEvent('keydown', {
bubbles: true, cancelable: true,
key: 'Enter', code: 'Enter', which: 13, keyCode: 13
}));
};
});
container.appendChild(btn);
});
}
injectMic();
const chatRoot = document.querySelector('div.fixed.flex.w-full.flex-col');
const target = chatRoot || document.body;
new MutationObserver(injectMic).observe(target, { childList: true, subtree: true });
})();
</script>
Still bothered by the waiting time?
Here is a version similar to WhatsApp’s audio function: it records your voice and does not stop until you click the microphone button:
<script>
(function() {
const MIC_CLASS = 'mic-button';
let recognition = null;
let listening = false;
function injectMic() {
document.querySelectorAll('div.flex.items-center.gap-x-2').forEach(container => {
if (container.querySelector(`button.${MIC_CLASS}`)) return;
const btn = document.createElement('button');
btn.className = `${MIC_CLASS} outline-none w-8 h-8 flex items-center justify-center rounded-full duration-200 transition-colors ease-in-out`;
btn.style.backgroundColor = 'rgba(0, 0, 0, 0.063)';
btn.style.marginLeft = '4px';
btn.innerHTML = `
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
class="h-6 w-6 shrink-0 duration-200 transition-colors ease-in-out"
style="color: rgba(0, 0, 0, 0.5);">
<path fill="currentColor"
d="M12 14a3 3 0 0 0 3-3V6a3 3 0 0 0-6 0v5a3 3 0 0 0 3 3Zm5-3a5 5 0 0 1-10 0H5a7 7 0 0 0 14 0h-2ZM11 21h2v-2h-2v2Z"/>
</svg>
`;
btn.addEventListener('click', () => {
const ta = document.querySelector('textarea.resize-none');
if (!ta || !('webkitSpeechRecognition' in window)) {
return alert('Your browser does not support SpeechRecognition');
}
if (!recognition) {
recognition = new webkitSpeechRecognition();
recognition.lang = 'es-ES';
recognition.interimResults = true;
recognition.maxAlternatives = 1;
recognition.continuous = true;
}
if (listening) {
recognition.stop();
listening = false;
btn.style.backgroundColor = 'rgba(0, 0, 0, 0.063)';
return;
}
let finalTranscript = '';
listening = true;
btn.style.backgroundColor = 'rgba(255, 0, 0, 0.3)';
recognition.start();
recognition.onresult = e => {
let interim = '';
for (let i = e.resultIndex; i < e.results.length; i++) {
const r = e.results[i];
if (r.isFinal) finalTranscript += r[0].transcript + ' ';
else interim += r[0].transcript;
}
const text = (finalTranscript + interim).trimEnd();
const setter = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value').set;
setter.call(ta, text);
ta.dispatchEvent(new Event('input', { bubbles: true }));
};
recognition.onerror = err => {
console.error('SpeechRecognition Error:', err.error);
};
recognition.onend = () => {
if (listening) {
recognition.start();
} else {
btn.style.backgroundColor = 'rgba(0, 0, 0, 0.063)';
ta.dispatchEvent(new KeyboardEvent('keydown', {
bubbles: true, cancelable: true,
key: 'Enter', code: 'Enter', which: 13, keyCode: 13
}));
}
};
});
container.appendChild(btn);
});
}
injectMic();
const chatRoot = document.querySelector('div.fixed.flex.w-full.flex-col');
const target = chatRoot || document.body;
new MutationObserver(injectMic).observe(target, { childList: true, subtree: true });
})();
</script>
Annoyed by the automatic message sending?
Here is a version that removes the automatic sending of the message after the audio:
<script>
(function() {
const MIC_CLASS = 'mic-button';
let recognition = null;
let listening = false;
function injectMic() {
document.querySelectorAll('div.flex.items-center.gap-x-2').forEach(container => {
if (container.querySelector(`button.${MIC_CLASS}`)) return;
const btn = document.createElement('button');
btn.className = `${MIC_CLASS} outline-none w-8 h-8 flex items-center justify-center rounded-full duration-200 transition-colors ease-in-out`;
btn.style.backgroundColor = 'rgba(0, 0, 0, 0.063)';
btn.style.marginLeft = '4px';
btn.innerHTML = `
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
class="h-6 w-6 shrink-0 duration-200 transition-colors ease-in-out"
style="color: rgba(0, 0, 0, 0.5);">
<path fill="currentColor"
d="M12 14a3 3 0 0 0 3-3V6a3 3 0 0 0-6 0v5a3 3 0 0 0 3 3Zm5-3a5 5 0 0 1-10 0H5a7 7 0 0 0 14 0h-2ZM11 21h2v-2h-2v2Z"/>
</svg>
`;
btn.addEventListener('click', () => {
const ta = document.querySelector('textarea.resize-none');
if (!ta || !('webkitSpeechRecognition' in window)) {
return alert('Your browser does not support SpeechRecognition');
}
if (!recognition) {
recognition = new webkitSpeechRecognition();
recognition.lang = 'es-ES';
recognition.interimResults = true;
recognition.maxAlternatives = 1;
recognition.continuous = true;
}
if (listening) {
recognition.stop();
listening = false;
btn.style.backgroundColor = 'rgba(0, 0, 0, 0.063)';
return;
}
let finalTranscript = '';
listening = true;
btn.style.backgroundColor = 'rgba(255, 0, 0, 0.3)';
recognition.start();
recognition.onresult = e => {
let interim = '';
for (let i = e.resultIndex; i < e.results.length; i++) {
const r = e.results[i];
if (r.isFinal) finalTranscript += r[0].transcript + ' ';
else interim += r[0].transcript;
}
const text = (finalTranscript + interim).trimEnd();
const setter = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value').set;
setter.call(ta, text);
ta.dispatchEvent(new Event('input', { bubbles: true }));
};
recognition.onerror = err => {
console.error('SpeechRecognition Error:', err.error);
};
recognition.onend = () => {
btn.style.backgroundColor = 'rgba(0, 0, 0, 0.063)';
if (listening) {
recognition.start();
}
};
});
container.appendChild(btn);
});
}
injectMic();
const chatRoot = document.querySelector('div.fixed.flex.w-full.flex-col');
const target = chatRoot || document.body;
new MutationObserver(injectMic).observe(target, { childList: true, subtree: true });
})();
</script>
~All the glory is to God.